feat:管理后台系统设置页面 + 小程序登录页条件渲染
This commit is contained in:
@@ -30,13 +30,13 @@
|
||||
<text v-else>微信一键登录</text>
|
||||
</view>
|
||||
|
||||
<view class="login-divider">
|
||||
<view class="login-divider" v-if="loginConfig.smsLoginEnabled">
|
||||
<view></view>
|
||||
<text>或使用手机号登录</text>
|
||||
<view></view>
|
||||
</view>
|
||||
|
||||
<view class="form">
|
||||
<view class="form" v-if="loginConfig.smsLoginEnabled">
|
||||
<view class="input-group">
|
||||
<text class="input-label">手机号码</text>
|
||||
<input
|
||||
@@ -73,6 +73,7 @@
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="loginConfig.smsLoginEnabled"
|
||||
class="btn-primary login-btn"
|
||||
:class="{ disabled: !canSubmit || loading }"
|
||||
@click="handleLogin"
|
||||
@@ -92,7 +93,7 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useAppStore } from '../../stores/app.js'
|
||||
import { getSmsCode } from '../../services/auth.js'
|
||||
import { getSmsCode, getLoginConfig } from '../../services/auth.js'
|
||||
|
||||
const store = useAppStore()
|
||||
|
||||
@@ -100,11 +101,21 @@ const statusBarHeight = ref(uni.getStorageSync('statusBarHeight') || 20)
|
||||
const safeAreaTop = ref(uni.getStorageSync('safeAreaTop') || 20)
|
||||
const safeAreaBottom = ref(uni.getStorageSync('safeAreaBottom') || 0)
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
const windowInfo = uni.getWindowInfo()
|
||||
statusBarHeight.value = windowInfo.statusBarHeight || 20
|
||||
safeAreaTop.value = windowInfo.safeAreaInsets?.top || windowInfo.statusBarHeight || 20
|
||||
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('')
|
||||
@@ -114,6 +125,8 @@ const activeMethod = ref('')
|
||||
const countdown = ref(60)
|
||||
const isCountingDown = ref(false)
|
||||
|
||||
const loginConfig = ref({ wechatLoginEnabled: true, smsLoginEnabled: false })
|
||||
|
||||
const canSubmit = computed(() => {
|
||||
return phone.value.length === 11 && code.value.length === 6
|
||||
})
|
||||
|
||||
@@ -16,6 +16,15 @@ export const getSmsCode = async (phone) => {
|
||||
return response
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取登录方式配置
|
||||
* @returns {Promise<Object>} 登录方式配置
|
||||
*/
|
||||
export const getLoginConfig = async () => {
|
||||
const response = await get('/auth/loginConfig')
|
||||
return response
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户登录(手机号 + 验证码)
|
||||
* @param {Object} params - 登录参数
|
||||
@@ -134,6 +143,7 @@ export const checkPhone = async (phone) => {
|
||||
}
|
||||
|
||||
export default {
|
||||
getLoginConfig,
|
||||
getSmsCode,
|
||||
login,
|
||||
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: '用户管理',
|
||||
icon: 'UserFilled'
|
||||
},
|
||||
{
|
||||
path: '/system',
|
||||
title: '系统设置',
|
||||
icon: 'Setting'
|
||||
},
|
||||
{
|
||||
path: '/aiconfig',
|
||||
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',
|
||||
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