- 添加基于 VbenAdmin + Vue3 + Element Plus 的前端管理系统 - 包含完整的 UI 组件库和工具链 - 支持多应用架构 (web-ele, backend-mock, playground) - 包含完整的开发规范和配置 - 修复 admin 目录的子模块问题,确保正确提交
621 lines
16 KiB
Vue
621 lines
16 KiB
Vue
<template>
|
|
<div class="p-4">
|
|
<VbenTabs v-model:active-key="activeTab" type="card">
|
|
<!-- 登录方式设置 -->
|
|
<VbenTabPane key="methods" tab="登录方式">
|
|
<VbenForm
|
|
ref="methodsFormRef"
|
|
:schema="methodsFormSchema"
|
|
:form-options="{
|
|
layout: 'vertical',
|
|
labelCol: { span: 24 },
|
|
wrapperCol: { span: 24 },
|
|
}"
|
|
@submit="handleSaveMethods"
|
|
>
|
|
<template #submitButton>
|
|
<div class="flex gap-2">
|
|
<VbenButton type="primary" :loading="saveLoading" @click="handleSaveMethods">
|
|
保存配置
|
|
</VbenButton>
|
|
<VbenButton @click="handleResetMethods">
|
|
重置配置
|
|
</VbenButton>
|
|
</div>
|
|
</template>
|
|
</VbenForm>
|
|
</VbenTabPane>
|
|
|
|
<!-- 安全设置 -->
|
|
<VbenTabPane key="security" tab="安全设置">
|
|
<VbenForm
|
|
ref="securityFormRef"
|
|
:schema="securityFormSchema"
|
|
:form-options="{
|
|
layout: 'vertical',
|
|
labelCol: { span: 24 },
|
|
wrapperCol: { span: 24 },
|
|
}"
|
|
@submit="handleSaveSecurity"
|
|
>
|
|
<template #submitButton>
|
|
<div class="flex gap-2">
|
|
<VbenButton type="primary" :loading="saveLoading" @click="handleSaveSecurity">
|
|
保存配置
|
|
</VbenButton>
|
|
<VbenButton @click="handleResetSecurity">
|
|
重置配置
|
|
</VbenButton>
|
|
</div>
|
|
</template>
|
|
</VbenForm>
|
|
</VbenTabPane>
|
|
|
|
<!-- 第三方登录 -->
|
|
<VbenTabPane key="oauth" tab="第三方登录">
|
|
<VbenForm
|
|
ref="oauthFormRef"
|
|
:schema="oauthFormSchema"
|
|
:form-options="{
|
|
layout: 'vertical',
|
|
labelCol: { span: 24 },
|
|
wrapperCol: { span: 24 },
|
|
}"
|
|
@submit="handleSaveOauth"
|
|
>
|
|
<template #submitButton>
|
|
<div class="flex gap-2">
|
|
<VbenButton type="primary" :loading="saveLoading" @click="handleSaveOauth">
|
|
保存配置
|
|
</VbenButton>
|
|
<VbenButton @click="handleResetOauth">
|
|
重置配置
|
|
</VbenButton>
|
|
</div>
|
|
</template>
|
|
</VbenForm>
|
|
</VbenTabPane>
|
|
|
|
<!-- 注册设置 -->
|
|
<VbenTabPane key="register" tab="注册设置">
|
|
<VbenForm
|
|
ref="registerFormRef"
|
|
:schema="registerFormSchema"
|
|
:form-options="{
|
|
layout: 'vertical',
|
|
labelCol: { span: 24 },
|
|
wrapperCol: { span: 24 },
|
|
}"
|
|
@submit="handleSaveRegister"
|
|
>
|
|
<template #submitButton>
|
|
<div class="flex gap-2">
|
|
<VbenButton type="primary" :loading="saveLoading" @click="handleSaveRegister">
|
|
保存配置
|
|
</VbenButton>
|
|
<VbenButton @click="handleResetRegister">
|
|
重置配置
|
|
</VbenButton>
|
|
</div>
|
|
</template>
|
|
</VbenForm>
|
|
</VbenTabPane>
|
|
</VbenTabs>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onMounted } from 'vue'
|
|
import { VbenForm, VbenButton, VbenTabs, VbenTabPane } from '@vben/components'
|
|
import { message } from 'ant-design-vue'
|
|
import type { FormSchema } from '@vben/types'
|
|
import {
|
|
getLoginConfigApi,
|
|
updateLoginConfigApi,
|
|
resetLoginConfigApi,
|
|
type LoginConfig,
|
|
} from '@/api/common/login'
|
|
|
|
const methodsFormRef = ref()
|
|
const securityFormRef = ref()
|
|
const oauthFormRef = ref()
|
|
const registerFormRef = ref()
|
|
const saveLoading = ref(false)
|
|
const activeTab = ref('methods')
|
|
|
|
// 登录方式表单配置
|
|
const methodsFormSchema: FormSchema[] = [
|
|
{
|
|
field: 'username_enabled',
|
|
label: '用户名登录',
|
|
component: 'Switch',
|
|
defaultValue: true,
|
|
helpMessage: '允许用户使用用户名进行登录',
|
|
},
|
|
{
|
|
field: 'mobile_enabled',
|
|
label: '手机号登录',
|
|
component: 'Switch',
|
|
defaultValue: true,
|
|
helpMessage: '允许用户使用手机号进行登录',
|
|
},
|
|
{
|
|
field: 'email_enabled',
|
|
label: '邮箱登录',
|
|
component: 'Switch',
|
|
defaultValue: true,
|
|
helpMessage: '允许用户使用邮箱进行登录',
|
|
},
|
|
{
|
|
field: 'sms_enabled',
|
|
label: '短信验证码登录',
|
|
component: 'Switch',
|
|
defaultValue: false,
|
|
helpMessage: '允许用户使用短信验证码进行登录',
|
|
},
|
|
{
|
|
field: 'oauth_enabled',
|
|
label: '第三方登录',
|
|
component: 'Switch',
|
|
defaultValue: false,
|
|
helpMessage: '启用微信、QQ等第三方登录方式',
|
|
},
|
|
{
|
|
field: 'guest_enabled',
|
|
label: '游客模式',
|
|
component: 'Switch',
|
|
defaultValue: false,
|
|
helpMessage: '允许游客访问部分功能,无需注册登录',
|
|
},
|
|
]
|
|
|
|
// 安全设置表单配置
|
|
const securityFormSchema: FormSchema[] = [
|
|
{
|
|
field: 'captcha_enabled',
|
|
label: '登录验证码',
|
|
component: 'Switch',
|
|
defaultValue: true,
|
|
helpMessage: '登录时需要输入图形验证码',
|
|
},
|
|
{
|
|
field: 'captcha_type',
|
|
label: '验证码类型',
|
|
component: 'RadioGroup',
|
|
show: ({ values }) => values.captcha_enabled,
|
|
componentProps: {
|
|
options: [
|
|
{ label: '图形验证码', value: 'image' },
|
|
{ label: '滑动验证码', value: 'slide' },
|
|
{ label: '点击验证码', value: 'click' },
|
|
],
|
|
},
|
|
defaultValue: 'image',
|
|
},
|
|
{
|
|
field: 'max_fail_attempts',
|
|
label: '最大登录失败次数',
|
|
component: 'InputNumber',
|
|
required: true,
|
|
componentProps: {
|
|
min: 3,
|
|
max: 20,
|
|
addonAfter: '次',
|
|
},
|
|
defaultValue: 5,
|
|
helpMessage: '超过此次数将锁定账户',
|
|
},
|
|
{
|
|
field: 'lock_duration',
|
|
label: '账户锁定时间',
|
|
component: 'InputNumber',
|
|
required: true,
|
|
componentProps: {
|
|
min: 5,
|
|
max: 1440,
|
|
addonAfter: '分钟',
|
|
},
|
|
defaultValue: 30,
|
|
helpMessage: '账户被锁定后的解锁时间',
|
|
},
|
|
{
|
|
field: 'password_strength_enabled',
|
|
label: '密码强度检查',
|
|
component: 'Switch',
|
|
defaultValue: true,
|
|
helpMessage: '强制用户使用强密码',
|
|
},
|
|
{
|
|
field: 'password_min_length',
|
|
label: '密码最小长度',
|
|
component: 'InputNumber',
|
|
show: ({ values }) => values.password_strength_enabled,
|
|
componentProps: {
|
|
min: 6,
|
|
max: 20,
|
|
addonAfter: '位',
|
|
},
|
|
defaultValue: 8,
|
|
},
|
|
{
|
|
field: 'password_complexity',
|
|
label: '密码复杂度要求',
|
|
component: 'CheckboxGroup',
|
|
show: ({ values }) => values.password_strength_enabled,
|
|
componentProps: {
|
|
options: [
|
|
{ label: '包含大写字母', value: 'uppercase' },
|
|
{ label: '包含小写字母', value: 'lowercase' },
|
|
{ label: '包含数字', value: 'number' },
|
|
{ label: '包含特殊字符', value: 'special' },
|
|
],
|
|
},
|
|
defaultValue: ['lowercase', 'number'],
|
|
},
|
|
{
|
|
field: 'password_expire_enabled',
|
|
label: '强制定期修改密码',
|
|
component: 'Switch',
|
|
defaultValue: false,
|
|
helpMessage: '强制用户定期修改密码',
|
|
},
|
|
{
|
|
field: 'password_expire_days',
|
|
label: '密码有效期',
|
|
component: 'InputNumber',
|
|
show: ({ values }) => values.password_expire_enabled,
|
|
componentProps: {
|
|
min: 30,
|
|
max: 365,
|
|
addonAfter: '天',
|
|
},
|
|
defaultValue: 90,
|
|
},
|
|
{
|
|
field: 'single_sign_on_enabled',
|
|
label: '单点登录',
|
|
component: 'Switch',
|
|
defaultValue: false,
|
|
helpMessage: '同一账户只能在一个设备上登录',
|
|
},
|
|
]
|
|
|
|
// 第三方登录表单配置
|
|
const oauthFormSchema: FormSchema[] = [
|
|
{
|
|
field: 'wechat_enabled',
|
|
label: '启用微信登录',
|
|
component: 'Switch',
|
|
defaultValue: false,
|
|
},
|
|
{
|
|
field: 'wechat_app_id',
|
|
label: '微信应用ID',
|
|
component: 'Input',
|
|
show: ({ values }) => values.wechat_enabled,
|
|
required: true,
|
|
colProps: { span: 12 },
|
|
},
|
|
{
|
|
field: 'wechat_secret',
|
|
label: '微信应用密钥',
|
|
component: 'InputPassword',
|
|
show: ({ values }) => values.wechat_enabled,
|
|
required: true,
|
|
colProps: { span: 12 },
|
|
},
|
|
{
|
|
field: 'wechat_redirect_uri',
|
|
label: '微信回调地址',
|
|
component: 'Input',
|
|
show: ({ values }) => values.wechat_enabled,
|
|
helpMessage: '微信登录授权回调地址',
|
|
},
|
|
{
|
|
field: 'qq_enabled',
|
|
label: '启用QQ登录',
|
|
component: 'Switch',
|
|
defaultValue: false,
|
|
},
|
|
{
|
|
field: 'qq_app_id',
|
|
label: 'QQ应用ID',
|
|
component: 'Input',
|
|
show: ({ values }) => values.qq_enabled,
|
|
required: true,
|
|
colProps: { span: 12 },
|
|
},
|
|
{
|
|
field: 'qq_secret',
|
|
label: 'QQ应用密钥',
|
|
component: 'InputPassword',
|
|
show: ({ values }) => values.qq_enabled,
|
|
required: true,
|
|
colProps: { span: 12 },
|
|
},
|
|
{
|
|
field: 'qq_redirect_uri',
|
|
label: 'QQ回调地址',
|
|
component: 'Input',
|
|
show: ({ values }) => values.qq_enabled,
|
|
helpMessage: 'QQ登录授权回调地址',
|
|
},
|
|
{
|
|
field: 'github_enabled',
|
|
label: '启用GitHub登录',
|
|
component: 'Switch',
|
|
defaultValue: false,
|
|
},
|
|
{
|
|
field: 'github_client_id',
|
|
label: 'GitHub客户端ID',
|
|
component: 'Input',
|
|
show: ({ values }) => values.github_enabled,
|
|
required: true,
|
|
colProps: { span: 12 },
|
|
},
|
|
{
|
|
field: 'github_secret',
|
|
label: 'GitHub客户端密钥',
|
|
component: 'InputPassword',
|
|
show: ({ values }) => values.github_enabled,
|
|
required: true,
|
|
colProps: { span: 12 },
|
|
},
|
|
{
|
|
field: 'github_redirect_uri',
|
|
label: 'GitHub回调地址',
|
|
component: 'Input',
|
|
show: ({ values }) => values.github_enabled,
|
|
helpMessage: 'GitHub登录授权回调地址',
|
|
},
|
|
]
|
|
|
|
// 注册设置表单配置
|
|
const registerFormSchema: FormSchema[] = [
|
|
{
|
|
field: 'register_enabled',
|
|
label: '开放注册',
|
|
component: 'Switch',
|
|
defaultValue: true,
|
|
helpMessage: '是否允许用户注册新账户',
|
|
},
|
|
{
|
|
field: 'register_methods',
|
|
label: '注册方式',
|
|
component: 'CheckboxGroup',
|
|
show: ({ values }) => values.register_enabled,
|
|
componentProps: {
|
|
options: [
|
|
{ label: '用户名注册', value: 'username' },
|
|
{ label: '手机号注册', value: 'mobile' },
|
|
{ label: '邮箱注册', value: 'email' },
|
|
],
|
|
},
|
|
defaultValue: ['username', 'mobile'],
|
|
},
|
|
{
|
|
field: 'register_verification',
|
|
label: '注册验证方式',
|
|
component: 'RadioGroup',
|
|
show: ({ values }) => values.register_enabled,
|
|
componentProps: {
|
|
options: [
|
|
{ label: '无需验证', value: 'none' },
|
|
{ label: '邮箱验证', value: 'email' },
|
|
{ label: '短信验证', value: 'sms' },
|
|
{ label: '人工审核', value: 'manual' },
|
|
],
|
|
},
|
|
defaultValue: 'sms',
|
|
},
|
|
{
|
|
field: 'register_captcha_enabled',
|
|
label: '注册验证码',
|
|
component: 'Switch',
|
|
show: ({ values }) => values.register_enabled,
|
|
defaultValue: true,
|
|
helpMessage: '注册时需要输入验证码',
|
|
},
|
|
{
|
|
field: 'agreement_required',
|
|
label: '用户协议',
|
|
component: 'Switch',
|
|
show: ({ values }) => values.register_enabled,
|
|
defaultValue: true,
|
|
helpMessage: '用户注册时是否必须同意用户协议',
|
|
},
|
|
{
|
|
field: 'agreement_content',
|
|
label: '协议内容',
|
|
component: 'InputTextArea',
|
|
show: ({ values }) => values.register_enabled && values.agreement_required,
|
|
componentProps: {
|
|
rows: 6,
|
|
placeholder: '请输入用户协议内容',
|
|
},
|
|
},
|
|
{
|
|
field: 'default_role',
|
|
label: '默认用户组',
|
|
component: 'Select',
|
|
show: ({ values }) => values.register_enabled,
|
|
componentProps: {
|
|
options: [
|
|
{ label: '普通用户', value: 'user' },
|
|
{ label: 'VIP用户', value: 'vip' },
|
|
{ label: '会员', value: 'member' },
|
|
],
|
|
},
|
|
defaultValue: 'user',
|
|
},
|
|
{
|
|
field: 'reward_enabled',
|
|
label: '注册奖励',
|
|
component: 'Switch',
|
|
show: ({ values }) => values.register_enabled,
|
|
defaultValue: true,
|
|
helpMessage: '新用户注册时给予奖励',
|
|
},
|
|
{
|
|
field: 'reward_points',
|
|
label: '奖励积分',
|
|
component: 'InputNumber',
|
|
show: ({ values }) => values.register_enabled && values.reward_enabled,
|
|
componentProps: {
|
|
min: 0,
|
|
},
|
|
defaultValue: 100,
|
|
colProps: { span: 12 },
|
|
},
|
|
{
|
|
field: 'reward_balance',
|
|
label: '奖励余额',
|
|
component: 'InputNumber',
|
|
show: ({ values }) => values.register_enabled && values.reward_enabled,
|
|
componentProps: {
|
|
min: 0,
|
|
precision: 2,
|
|
addonAfter: '元',
|
|
},
|
|
defaultValue: 0,
|
|
colProps: { span: 12 },
|
|
},
|
|
]
|
|
|
|
// 加载配置
|
|
const loadConfig = async () => {
|
|
try {
|
|
const data = await getLoginConfigApi()
|
|
methodsFormRef.value?.setFieldsValue(data.methods || {})
|
|
securityFormRef.value?.setFieldsValue(data.security || {})
|
|
oauthFormRef.value?.setFieldsValue(data.oauth || {})
|
|
registerFormRef.value?.setFieldsValue(data.register || {})
|
|
} catch (error) {
|
|
message.error('加载配置失败')
|
|
}
|
|
}
|
|
|
|
// 保存登录方式配置
|
|
const handleSaveMethods = async () => {
|
|
try {
|
|
const values = await methodsFormRef.value?.validate()
|
|
if (!values) return
|
|
|
|
saveLoading.value = true
|
|
await updateLoginConfigApi({ type: 'methods', config: values })
|
|
message.success('登录方式配置保存成功')
|
|
} catch (error) {
|
|
message.error('保存失败')
|
|
} finally {
|
|
saveLoading.value = false
|
|
}
|
|
}
|
|
|
|
// 保存安全设置配置
|
|
const handleSaveSecurity = async () => {
|
|
try {
|
|
const values = await securityFormRef.value?.validate()
|
|
if (!values) return
|
|
|
|
saveLoading.value = true
|
|
await updateLoginConfigApi({ type: 'security', config: values })
|
|
message.success('安全设置配置保存成功')
|
|
} catch (error) {
|
|
message.error('保存失败')
|
|
} finally {
|
|
saveLoading.value = false
|
|
}
|
|
}
|
|
|
|
// 保存第三方登录配置
|
|
const handleSaveOauth = async () => {
|
|
try {
|
|
const values = await oauthFormRef.value?.validate()
|
|
if (!values) return
|
|
|
|
saveLoading.value = true
|
|
await updateLoginConfigApi({ type: 'oauth', config: values })
|
|
message.success('第三方登录配置保存成功')
|
|
} catch (error) {
|
|
message.error('保存失败')
|
|
} finally {
|
|
saveLoading.value = false
|
|
}
|
|
}
|
|
|
|
// 保存注册设置配置
|
|
const handleSaveRegister = async () => {
|
|
try {
|
|
const values = await registerFormRef.value?.validate()
|
|
if (!values) return
|
|
|
|
saveLoading.value = true
|
|
await updateLoginConfigApi({ type: 'register', config: values })
|
|
message.success('注册设置配置保存成功')
|
|
} catch (error) {
|
|
message.error('保存失败')
|
|
} finally {
|
|
saveLoading.value = false
|
|
}
|
|
}
|
|
|
|
// 重置配置
|
|
const handleResetMethods = async () => {
|
|
try {
|
|
await resetLoginConfigApi('methods')
|
|
await loadConfig()
|
|
message.success('登录方式配置已重置')
|
|
} catch (error) {
|
|
message.error('重置失败')
|
|
}
|
|
}
|
|
|
|
const handleResetSecurity = async () => {
|
|
try {
|
|
await resetLoginConfigApi('security')
|
|
await loadConfig()
|
|
message.success('安全设置配置已重置')
|
|
} catch (error) {
|
|
message.error('重置失败')
|
|
}
|
|
}
|
|
|
|
const handleResetOauth = async () => {
|
|
try {
|
|
await resetLoginConfigApi('oauth')
|
|
await loadConfig()
|
|
message.success('第三方登录配置已重置')
|
|
} catch (error) {
|
|
message.error('重置失败')
|
|
}
|
|
}
|
|
|
|
const handleResetRegister = async () => {
|
|
try {
|
|
await resetLoginConfigApi('register')
|
|
await loadConfig()
|
|
message.success('注册设置配置已重置')
|
|
} catch (error) {
|
|
message.error('重置失败')
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
loadConfig()
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.p-4 {
|
|
padding: 16px;
|
|
}
|
|
|
|
.flex {
|
|
display: flex;
|
|
}
|
|
|
|
.gap-2 {
|
|
gap: 8px;
|
|
}
|
|
</style> |