feat:管理后台系统设置页面 + 小程序登录页条件渲染

This commit is contained in:
2026-06-27 10:27:14 +08:00
parent 9131203d1c
commit e3b21cac3e
6 changed files with 247 additions and 4 deletions
+17 -4
View File
@@ -30,13 +30,13 @@
<text v-else>微信一键登录</text> <text v-else>微信一键登录</text>
</view> </view>
<view class="login-divider"> <view class="login-divider" v-if="loginConfig.smsLoginEnabled">
<view></view> <view></view>
<text>或使用手机号登录</text> <text>或使用手机号登录</text>
<view></view> <view></view>
</view> </view>
<view class="form"> <view class="form" v-if="loginConfig.smsLoginEnabled">
<view class="input-group"> <view class="input-group">
<text class="input-label">手机号码</text> <text class="input-label">手机号码</text>
<input <input
@@ -73,6 +73,7 @@
</view> </view>
<view <view
v-if="loginConfig.smsLoginEnabled"
class="btn-primary login-btn" class="btn-primary login-btn"
:class="{ disabled: !canSubmit || loading }" :class="{ disabled: !canSubmit || loading }"
@click="handleLogin" @click="handleLogin"
@@ -92,7 +93,7 @@
<script setup> <script setup>
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
import { useAppStore } from '../../stores/app.js' import { useAppStore } from '../../stores/app.js'
import { getSmsCode } from '../../services/auth.js' import { getSmsCode, getLoginConfig } from '../../services/auth.js'
const store = useAppStore() const store = useAppStore()
@@ -100,11 +101,21 @@ const statusBarHeight = ref(uni.getStorageSync('statusBarHeight') || 20)
const safeAreaTop = ref(uni.getStorageSync('safeAreaTop') || 20) const safeAreaTop = ref(uni.getStorageSync('safeAreaTop') || 20)
const safeAreaBottom = ref(uni.getStorageSync('safeAreaBottom') || 0) const safeAreaBottom = ref(uni.getStorageSync('safeAreaBottom') || 0)
onMounted(() => { onMounted(async () => {
const windowInfo = uni.getWindowInfo() const windowInfo = uni.getWindowInfo()
statusBarHeight.value = windowInfo.statusBarHeight || 20 statusBarHeight.value = windowInfo.statusBarHeight || 20
safeAreaTop.value = windowInfo.safeAreaInsets?.top || windowInfo.statusBarHeight || 20 safeAreaTop.value = windowInfo.safeAreaInsets?.top || windowInfo.statusBarHeight || 20
safeAreaBottom.value = windowInfo.safeAreaInsets?.bottom || 0 safeAreaBottom.value = windowInfo.safeAreaInsets?.bottom || 0
try {
const res = await getLoginConfig()
if (res.data) {
loginConfig.value = res.data
}
} catch (e) {
// 接口失败时默认只显示微信登录
loginConfig.value = { wechatLoginEnabled: true, smsLoginEnabled: false }
}
}) })
const phone = ref('') const phone = ref('')
@@ -114,6 +125,8 @@ const activeMethod = ref('')
const countdown = ref(60) const countdown = ref(60)
const isCountingDown = ref(false) const isCountingDown = ref(false)
const loginConfig = ref({ wechatLoginEnabled: true, smsLoginEnabled: false })
const canSubmit = computed(() => { const canSubmit = computed(() => {
return phone.value.length === 11 && code.value.length === 6 return phone.value.length === 11 && code.value.length === 6
}) })
+10
View File
@@ -16,6 +16,15 @@ export const getSmsCode = async (phone) => {
return response return response
} }
/**
* 获取登录方式配置
* @returns {Promise<Object>} 登录方式配置
*/
export const getLoginConfig = async () => {
const response = await get('/auth/loginConfig')
return response
}
/** /**
* 用户登录(手机号 + 验证码) * 用户登录(手机号 + 验证码)
* @param {Object} params - 登录参数 * @param {Object} params - 登录参数
@@ -134,6 +143,7 @@ export const checkPhone = async (phone) => {
} }
export default { export default {
getLoginConfig,
getSmsCode, getSmsCode,
login, login,
wechatLogin, wechatLogin,
+18
View File
@@ -0,0 +1,18 @@
import request from '@/utils/request'
// 获取所有可见配置列表
export function getSystemConfigList() {
return request({
url: '/admin/systemConfig/list',
method: 'get'
})
}
// 批量更新配置
export function updateSystemConfig(data: { configKey: string; configValue: string }[]) {
return request({
url: '/admin/systemConfig/update',
method: 'put',
data
})
}
+5
View File
@@ -27,6 +27,11 @@ export const menuConfig: MenuItem[] = [
title: '用户管理', title: '用户管理',
icon: 'UserFilled' icon: 'UserFilled'
}, },
{
path: '/system',
title: '系统设置',
icon: 'Setting'
},
{ {
path: '/aiconfig', path: '/aiconfig',
title: 'AI配置管理', title: 'AI配置管理',
+14
View File
@@ -55,6 +55,20 @@ const routes: RouteRecordRaw[] = [
} }
] ]
}, },
{
path: '/system',
component: Layout,
redirect: '/system/settings',
meta: { title: '系统设置', icon: 'Setting' },
children: [
{
path: 'settings',
name: 'SystemSettings',
component: () => import('@/views/system/SystemSettings.vue'),
meta: { title: '基础设置' }
}
]
},
{ {
path: '/aiconfig', path: '/aiconfig',
component: Layout, component: Layout,
@@ -0,0 +1,183 @@
<template>
<div class="system-settings">
<div class="page-header">
<h2>基础设置</h2>
<el-button type="primary" :loading="saving" @click="handleSave">保存</el-button>
</div>
<el-card v-if="loading" v-loading="true" class="loading-card" />
<div v-else class="config-groups">
<el-card v-for="(items, group) in groupedConfigs" :key="group" class="config-group">
<template #header>
<span class="group-title">{{ groupLabels[group] || group }}</span>
</template>
<div v-for="item in items" :key="item.configKey" class="config-item">
<div class="config-label">
<span class="config-name">{{ item.configName }}</span>
<span class="config-desc">{{ item.description }}</span>
</div>
<div class="config-control">
<el-switch
v-if="item.valueType === 'boolean'"
v-model="editValues[item.configKey]"
:active-value="'true'"
:inactive-value="'false'"
/>
<el-input
v-else-if="item.valueType === 'string'"
v-model="editValues[item.configKey]"
style="width: 300px"
/>
<el-input-number
v-else-if="item.valueType === 'number'"
v-model="editValues[item.configKey]"
style="width: 200px"
/>
<span v-else>{{ editValues[item.configKey] }}</span>
</div>
</div>
</el-card>
<el-empty v-if="Object.keys(groupedConfigs).length === 0" description="暂无配置项" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { getSystemConfigList, updateSystemConfig } from '@/api/systemConfig'
interface ConfigItem {
configKey: string
configValue: string
valueType: string
configGroup: string
configName: string
description: string
sortOrder: number
}
const loading = ref(false)
const saving = ref(false)
const configs = ref<ConfigItem[]>([])
const editValues = ref<Record<string, string | boolean>>({})
const groupLabels: Record<string, string> = {
system: '系统配置',
login: '登录设置',
notification: '通知设置'
}
const groupedConfigs = computed(() => {
const groups: Record<string, ConfigItem[]> = {}
for (const item of configs.value) {
if (!groups[item.configGroup]) {
groups[item.configGroup] = []
}
groups[item.configGroup].push(item)
}
return groups
})
const fetchConfigs = async () => {
loading.value = true
try {
const res = await getSystemConfigList()
configs.value = res.data || []
// 初始化编辑值
const values: Record<string, string | boolean> = {}
for (const item of configs.value) {
values[item.configKey] = item.configValue
}
editValues.value = values
} catch (error) {
ElMessage.error('获取配置列表失败')
} finally {
loading.value = false
}
}
const handleSave = async () => {
saving.value = true
try {
const updates = configs.value.map(item => ({
configKey: item.configKey,
configValue: String(editValues.value[item.configKey])
}))
await updateSystemConfig(updates)
ElMessage.success('保存成功')
await fetchConfigs()
} catch (error) {
ElMessage.error('保存失败')
} finally {
saving.value = false
}
}
onMounted(() => {
fetchConfigs()
})
</script>
<style scoped>
.system-settings {
padding: 20px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.page-header h2 {
margin: 0;
font-size: 20px;
}
.loading-card {
height: 200px;
}
.config-group {
margin-bottom: 20px;
}
.group-title {
font-weight: 600;
font-size: 16px;
}
.config-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 0;
border-bottom: 1px solid var(--el-border-color-lighter);
}
.config-item:last-child {
border-bottom: none;
}
.config-label {
display: flex;
flex-direction: column;
gap: 4px;
}
.config-name {
font-size: 14px;
font-weight: 500;
color: var(--el-text-color-primary);
}
.config-desc {
font-size: 12px;
color: var(--el-text-color-secondary);
}
</style>