优化注册页 添加注册配置拉取

This commit is contained in:
root 2024-12-07 19:55:01 +08:00
parent 4f3622a553
commit 04afcb743e
4 changed files with 344 additions and 55 deletions

View file

@ -0,0 +1,51 @@
{
"message": "success",
"data": {
"username": {
"minLength": 4,
"maxLength": 16,
"pattern": "^[a-zA-Z0-9][a-zA-Z0-9_-]*$",
"description": "用户名只能包含字母、数字、下划线和横杠"
},
"nickname": {
"minLength": 2,
"maxLength": 12,
"pattern": "^[\\u4e00-\\u9fa5a-zA-Z0-9_-]*$",
"description": "昵称可以包含中文、字母、数字、下划线和横杠"
},
"password": {
"minLength": 8,
"maxLength": 20,
"requireNumber": true,
"requireLowercase": true,
"requireUppercase": true,
"requireSpecial": true,
"specialChars": "!@#$%^&*",
"description": "密码必须包含大小写字母、数字和特殊字符"
},
"email": {
"pattern": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
"description": "请输入有效的电子邮件地址",
"allowedDomains": [
"gmail.com",
"outlook.com",
"qq.com",
"163.com",
"126.com"
]
},
"registration": {
"enabled": true,
"requireEmailVerification": false,
"maxAttemptsPerIP": 5,
"cooldownMinutes": 60,
"blockedUsernames": [
"admin",
"root",
"system",
"administrator"
]
}
},
"retcode": 0
}

View file

@ -116,5 +116,12 @@ export const UserAPI = {
*/ */
addUser(data) { addUser(data) {
return request.post('/Management/AddUser', data) return request.post('/Management/AddUser', data)
},
/**
* 获取注册配置
*/
getRegisterConfig() {
return request.get('/RegisterConfig.json')
} }
} }

View file

@ -1,6 +1,6 @@
<template> <template>
<div class="register-container"> <div class="register-container">
<el-card class="register-card"> <el-card class="register-card" v-loading="configLoading">
<template #header> <template #header>
<div class="register-header"> <div class="register-header">
<img src="../assets/logo.png" alt="logo" class="register-logo"> <img src="../assets/logo.png" alt="logo" class="register-logo">
@ -12,9 +12,18 @@
ref="registerFormRef" ref="registerFormRef"
:model="registerForm" :model="registerForm"
:rules="registerRules" :rules="registerRules"
label-width="100px"> :label-position="isMobile ? 'top' : 'right'"
:label-width="isMobile ? 'auto' : '100px'"
:validate-on-rule-change="false"
:hide-required-asterisk="true">
<el-form-item label="用户名" prop="username"> <el-form-item
label="用户名"
prop="username"
:class="{ 'mobile-form-item': isMobile }">
<div v-if="isMobile" class="input-description">
{{ registerConfig.username?.description }}
</div>
<el-input <el-input
v-model="registerForm.username" v-model="registerForm.username"
placeholder="请输入用户名" placeholder="请输入用户名"
@ -78,7 +87,7 @@
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import { ref, onMounted, computed, onUnmounted, nextTick } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { User, UserFilled, Lock, Message } from '@element-plus/icons-vue' import { User, UserFilled, Lock, Message } from '@element-plus/icons-vue'
@ -87,7 +96,9 @@ import { UserAPI } from '../api/user'
const router = useRouter() const router = useRouter()
const registerFormRef = ref(null) const registerFormRef = ref(null)
const loading = ref(false) const loading = ref(false)
const configLoading = ref(true)
//
const registerForm = ref({ const registerForm = ref({
username: '', username: '',
nickname: '', nickname: '',
@ -96,6 +107,51 @@ const registerForm = ref({
confirmPassword: '' confirmPassword: ''
}) })
//
const registerConfig = ref({})
//
const registerRules = computed(() => {
const config = registerConfig.value
if (!config.username) return {} //
return {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: config.username.minLength, max: config.username.maxLength,
message: `长度在 ${config.username.minLength}${config.username.maxLength} 个字符`,
trigger: 'blur' },
{ pattern: new RegExp(config.username.pattern),
message: config.username.description,
trigger: 'blur' }
],
nickname: [
{ required: true, message: '请输入昵称', trigger: 'blur' },
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
],
email: [
{ required: true, validator: validateEmail, trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, message: '密码长度不能小于6位', trigger: 'blur' }
],
confirmPassword: [
{ required: true, message: '请再次输入密码', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (value !== registerForm.value.password) {
callback(new Error('两次输入的密码不一致'))
} else {
callback()
}
},
trigger: 'blur'
}
]
}
})
// //
const validateEmail = (rule, value, callback) => { const validateEmail = (rule, value, callback) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
@ -108,37 +164,45 @@ const validateEmail = (rule, value, callback) => {
} }
} }
const registerRules = { //
username: [ const fetchRegisterConfig = async () => {
{ required: true, message: '请输入用户名', trigger: 'blur' }, configLoading.value = true
{ min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' } try {
], const response = await UserAPI.getRegisterConfig()
nickname: [ if (response.retcode === 0) {
{ required: true, message: '请输入昵称', trigger: 'blur' }, registerConfig.value = response.data
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' } //
], nextTick(() => {
email: [ if (registerFormRef.value) {
{ required: true, validator: validateEmail, trigger: 'blur' } registerFormRef.value.clearValidate()
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, message: '密码长度不能小于6位', trigger: 'blur' }
],
confirmPassword: [
{ required: true, message: '请再次输入密码', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (value !== registerForm.value.password) {
callback(new Error('两次输入的密码不一致'))
} else {
callback()
} }
}, })
trigger: 'blur'
} }
] } catch (error) {
console.error('获取注册配置失败:', error)
ElMessage.error('获取注册配置失败')
} finally {
configLoading.value = false
}
} }
//
const isMobile = computed(() => window.innerWidth <= 768)
//
const handleResize = () => {
isMobile.value = window.innerWidth <= 768
}
onMounted(() => {
fetchRegisterConfig()
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
})
const handleRegister = async () => { const handleRegister = async () => {
if (!registerFormRef.value) return if (!registerFormRef.value) return
@ -188,6 +252,8 @@ const handleRegister = async () => {
margin: 20px; margin: 20px;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1); box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
background-color: rgba(255, 255, 255, 0.409);
backdrop-filter: blur(8px);
} }
.register-header { .register-header {
@ -198,7 +264,6 @@ const handleRegister = async () => {
.register-logo { .register-logo {
width: 64px; width: 64px;
height: 64px; height: 64px;
margin-bottom: 16px;
} }
.register-button { .register-button {
@ -214,4 +279,79 @@ const handleRegister = async () => {
:deep(.el-form-item__label) { :deep(.el-form-item__label) {
font-weight: 500; font-weight: 500;
} }
/* 移动端样式优化 */
@media screen and (max-width: 768px) {
.register-card {
width: 100%;
margin: 0;
border-radius: 0;
min-height: 100vh;
display: flex;
flex-direction: column;
}
.register-container {
padding: 0;
}
.register-header {
padding: 20px 0;
.register-logo {
width: 48px;
height: 48px;
margin-bottom: 12px;
}
h2 {
font-size: 20px;
margin: 0;
}
}
:deep(.el-card__body) {
flex: 1;
padding: 16px;
display: flex;
flex-direction: column;
}
:deep(.el-form) {
flex: 1;
display: flex;
flex-direction: column;
}
.mobile-form-item {
margin-bottom: 24px;
}
.input-description {
font-size: 12px;
color: #909399;
margin-bottom: 4px;
line-height: 1.4;
}
:deep(.el-form-item__error) {
position: static;
margin-top: 4px;
}
:deep(.el-form-item__label) {
padding-bottom: 4px;
line-height: 1.4;
font-size: 14px;
}
.register-button {
margin-top: auto;
margin-bottom: 20px;
}
.form-footer {
margin-bottom: 20px;
}
}
</style> </style>

View file

@ -181,11 +181,12 @@
</template> </template>
</el-dialog> </el-dialog>
<!-- 添加用户对话框 --> <!-- 修改添加用户对话框 -->
<el-dialog <el-dialog
v-model="addUserDialogVisible" v-model="addUserDialogVisible"
title="添加用户" title="添加用户"
width="500px"> width="500px"
v-loading="configLoading">
<el-form <el-form
ref="addUserFormRef" ref="addUserFormRef"
:model="addUserForm" :model="addUserForm"
@ -195,24 +196,32 @@
<el-input <el-input
v-model="addUserForm.username" v-model="addUserForm.username"
placeholder="请输入用户名" /> placeholder="请输入用户名" />
<div class="form-tip">{{ registerConfig.username?.description }}</div>
</el-form-item> </el-form-item>
<el-form-item label="昵称" prop="nickname"> <el-form-item label="昵称" prop="nickname">
<el-input <el-input
v-model="addUserForm.nickname" v-model="addUserForm.nickname"
placeholder="请输入昵称" /> placeholder="请输入昵称" />
<div class="form-tip">{{ registerConfig.nickname?.description }}</div>
</el-form-item> </el-form-item>
<el-form-item label="邮箱" prop="email"> <el-form-item label="邮箱" prop="email">
<el-input <el-input
v-model="addUserForm.email" v-model="addUserForm.email"
placeholder="请输入邮箱" /> placeholder="请输入邮箱" />
<div class="form-tip">{{ registerConfig.email?.description }}</div>
</el-form-item> </el-form-item>
<el-form-item label="密码" prop="password"> <el-form-item label="密码" prop="password">
<el-input <el-input
v-model="addUserForm.password" v-model="addUserForm.password"
type="password" type="password"
show-password show-password
placeholder="请输入密码" /> placeholder="请输入密码" />
<div class="form-tip">{{ registerConfig.password?.description }}</div>
</el-form-item> </el-form-item>
<el-form-item label="权限" prop="mask"> <el-form-item label="权限" prop="mask">
<el-select v-model="addUserForm.mask" placeholder="请选择权限"> <el-select v-model="addUserForm.mask" placeholder="请选择权限">
<el-option :value="0" label="普通用户" /> <el-option :value="0" label="普通用户" />
@ -454,7 +463,7 @@ const submitResetPassword = async () => {
resetPasswordForm.value.newPassword resetPasswordForm.value.newPassword
) )
if (response.retcode === 0) { if (response.retcode === 0) {
ElMessage.success('<EFBFBD><EFBFBD><EFBFBD>码重置成功') ElMessage.success('码重置成功')
resetPasswordDialogVisible.value = false resetPasswordDialogVisible.value = false
} }
} catch (error) { } catch (error) {
@ -558,28 +567,94 @@ const validateEmail = (rule, value, callback) => {
} }
} }
const addUserRules = { //
username: [ const registerConfig = ref({})
{ required: true, message: '请输入用户名', trigger: 'blur' }, const configLoading = ref(false)
{ min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' }
], //
nickname: [ const fetchRegisterConfig = async () => {
{ required: true, message: '请输入昵称', trigger: 'blur' }, configLoading.value = true
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' } try {
], const response = await UserAPI.getRegisterConfig()
email: [ if (response.retcode === 0) {
{ required: true, validator: validateEmail, trigger: 'blur' } registerConfig.value = response.data
], }
password: [ } catch (error) {
{ required: true, message: '请输入密码', trigger: 'blur' }, console.error('获取注册配置失败:', error)
{ min: 6, message: '密码长度不能小于6位', trigger: 'blur' } } finally {
], configLoading.value = false
mask: [ }
{ required: true, message: '请选择权限', trigger: 'change' }
]
} }
// //
const addUserRules = computed(() => {
const config = registerConfig.value
if (!config.username) return {}
return {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: config.username.minLength, max: config.username.maxLength,
message: `长度在 ${config.username.minLength}${config.username.maxLength} 个字符`,
trigger: 'blur' },
{ pattern: new RegExp(config.username.pattern),
message: config.username.description,
trigger: 'blur' }
],
nickname: [
{ required: true, message: '请输入昵称', trigger: 'blur' },
{ min: config.nickname.minLength, max: config.nickname.maxLength,
message: `长度在 ${config.nickname.minLength}${config.nickname.maxLength} 个字符`,
trigger: 'blur' }
],
email: [
{ required: true, message: '请输入电子邮件', trigger: 'blur' },
{ pattern: new RegExp(config.email.pattern),
message: config.email.description,
trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: config.password.minLength, max: config.password.maxLength,
message: `密码长度在 ${config.password.minLength}${config.password.maxLength} 位之间`,
trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (!value) {
callback()
return
}
const errors = []
if (config.password.requireNumber && !/\d/.test(value)) {
errors.push('需要包含数字')
}
if (config.password.requireLowercase && !/[a-z]/.test(value)) {
errors.push('需要包含小写字母')
}
if (config.password.requireUppercase && !/[A-Z]/.test(value)) {
errors.push('需要包含大写字母')
}
if (config.password.requireSpecial && !/[!@#$%^&*]/.test(value)) {
errors.push('需要包含特殊字符')
}
if (errors.length) {
callback(new Error('密码' + errors.join('、')))
} else {
callback()
}
},
trigger: 'blur'
}
],
mask: [
{ required: true, message: '请选择权限', trigger: 'change' }
]
}
})
//
const showAddUserDialog = () => { const showAddUserDialog = () => {
addUserForm.value = { addUserForm.value = {
username: '', username: '',
@ -588,9 +663,18 @@ const showAddUserDialog = () => {
password: '', password: '',
mask: 0 mask: 0
} }
//
if (!registerConfig.value.username) {
fetchRegisterConfig()
}
addUserDialogVisible.value = true addUserDialogVisible.value = true
} }
//
onMounted(() => {
fetchRegisterConfig()
})
// //
const handleAddUser = async () => { const handleAddUser = async () => {
if (!addUserFormRef.value) return if (!addUserFormRef.value) return
@ -789,4 +873,11 @@ const isSuperAdmin = computed(() => userInfo.value?.mask === 2)
} }
} }
} }
.form-tip {
font-size: 12px;
color: #909399;
margin-top: 4px;
line-height: 1.4;
}
</style> </style>