feat:管理后台系统设置页面 + 小程序登录页条件渲染
This commit is contained in:
@@ -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
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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配置管理',
|
||||||
|
|||||||
@@ -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>
|
||||||
Reference in New Issue
Block a user