Files
AIGC/demo/frontend/src/views/Register.vue

515 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="register">
<el-row justify="center" align="middle" class="register-container">
<el-col :xs="22" :sm="16" :md="12" :lg="8" :xl="6">
<el-card class="register-card">
<template #header>
<div class="register-header">
<el-icon size="32" color="#67C23A"><User /></el-icon>
<h2>用户注册</h2>
</div>
</template>
<el-form
ref="registerFormRef"
:model="registerForm"
:rules="registerRules"
label-width="80px"
@submit.prevent="handleRegister"
>
<el-form-item label="用户名" prop="username">
<el-input
v-model="registerForm.username"
placeholder="请输入用户名"
prefix-icon="User"
clearable
@blur="checkUsername"
/>
<div v-if="usernameChecking" class="checking-text">
<el-icon class="is-loading"><Loading /></el-icon>
检查中...
</div>
<div v-if="usernameExists" class="error-text">
<el-icon><CircleCloseFilled /></el-icon>
用户名已存在
</div>
<div v-if="usernameAvailable" class="success-text">
<el-icon><CircleCheckFilled /></el-icon>
用户名可用
</div>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input
v-model="registerForm.email"
placeholder="请输入邮箱"
prefix-icon="Message"
clearable
@blur="checkEmail"
/>
<div v-if="emailChecking" class="checking-text">
<el-icon class="is-loading"><Loading /></el-icon>
检查中...
</div>
<div v-if="emailExists" class="error-text">
<el-icon><CircleCloseFilled /></el-icon>
邮箱已存在
</div>
<div v-if="emailAvailable" class="success-text">
<el-icon><CircleCheckFilled /></el-icon>
邮箱可用
</div>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input
v-model="registerForm.password"
type="password"
placeholder="请输入密码"
prefix-icon="Lock"
show-password
clearable
@input="checkPasswordStrength"
/>
<div v-if="passwordStrength" class="password-strength">
<div class="strength-bar">
<div
class="strength-fill"
:class="strengthClass"
:style="{ width: strengthWidth }"
></div>
</div>
<span class="strength-text">{{ strengthText }}</span>
</div>
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input
v-model="registerForm.confirmPassword"
type="password"
placeholder="请再次输入密码"
prefix-icon="Lock"
show-password
clearable
/>
</el-form-item>
<el-form-item>
<el-checkbox v-model="agreeTerms">
我已阅读并同意
<a href="#" class="terms-link">用户协议</a>
<a href="#" class="terms-link">隐私政策</a>
</el-checkbox>
</el-form-item>
<el-form-item>
<el-button
type="success"
size="large"
:loading="userStore.loading"
:disabled="!canRegister"
@click="handleRegister"
class="register-button"
>
{{ userStore.loading ? '注册中...' : '注册' }}
</el-button>
</el-form-item>
</el-form>
<div class="register-footer">
<p>已有账号<router-link to="/login" class="login-link">立即登录</router-link></p>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/stores/user'
import { checkUsernameExists, checkEmailExists } from '@/api/auth'
import { ElMessage } from 'element-plus'
import {
User,
Lock,
Message,
Phone,
Calendar,
Location,
Check,
Close,
ArrowLeft,
ArrowRight
} from '@element-plus/icons-vue'
const router = useRouter()
const userStore = useUserStore()
const registerFormRef = ref()
const agreeTerms = ref(false)
// 用户名检查状态
const usernameChecking = ref(false)
const usernameExists = ref(false)
const usernameAvailable = ref(false)
// 邮箱检查状态
const emailChecking = ref(false)
const emailExists = ref(false)
const emailAvailable = ref(false)
// 密码强度
const passwordStrength = ref(false)
const strengthLevel = ref(0)
const registerForm = reactive({
username: '',
email: '',
password: '',
confirmPassword: ''
})
const registerRules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 20, message: '用户名长度在 3 到 20 个字符', trigger: 'blur' },
{ pattern: /^[a-zA-Z0-9_]+$/, message: '用户名只能包含字母、数字和下划线', trigger: 'blur' }
],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, max: 20, message: '密码长度在 6 到 20 个字符', trigger: 'blur' }
],
confirmPassword: [
{ required: true, message: '请确认密码', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (value !== registerForm.password) {
callback(new Error('两次输入密码不一致'))
} else {
callback()
}
},
trigger: 'blur'
}
]
}
// 检查用户名是否存在
const checkUsername = async () => {
if (!registerForm.username || registerForm.username.length < 3) {
resetUsernameCheck()
return
}
try {
usernameChecking.value = true
usernameExists.value = false
usernameAvailable.value = false
const response = await checkUsernameExists(registerForm.username)
if (response.success) {
usernameExists.value = response.data.exists
usernameAvailable.value = !response.data.exists
}
} catch (error) {
console.error('Check username error:', error)
} finally {
usernameChecking.value = false
}
}
// 检查邮箱是否存在
const checkEmail = async () => {
if (!registerForm.email || !isValidEmail(registerForm.email)) {
resetEmailCheck()
return
}
try {
emailChecking.value = true
emailExists.value = false
emailAvailable.value = false
const response = await checkEmailExists(registerForm.email)
if (response.success) {
emailExists.value = response.data.exists
emailAvailable.value = !response.data.exists
}
} catch (error) {
console.error('Check email error:', error)
} finally {
emailChecking.value = false
}
}
// 检查密码强度
const checkPasswordStrength = () => {
const password = registerForm.password
if (!password) {
passwordStrength.value = false
return
}
passwordStrength.value = true
let score = 0
if (password.length >= 6) score++
if (password.length >= 8) score++
if (/[a-z]/.test(password)) score++
if (/[A-Z]/.test(password)) score++
if (/[0-9]/.test(password)) score++
if (/[^A-Za-z0-9]/.test(password)) score++
strengthLevel.value = Math.min(score, 4)
}
// 重置用户名检查状态
const resetUsernameCheck = () => {
usernameChecking.value = false
usernameExists.value = false
usernameAvailable.value = false
}
// 重置邮箱检查状态
const resetEmailCheck = () => {
emailChecking.value = false
emailExists.value = false
emailAvailable.value = false
}
// 验证邮箱格式
const isValidEmail = (email) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return emailRegex.test(email)
}
// 计算属性
const strengthClass = computed(() => {
const classes = ['weak', 'fair', 'good', 'strong']
return classes[strengthLevel.value - 1] || 'weak'
})
const strengthWidth = computed(() => {
return `${(strengthLevel.value / 4) * 100}%`
})
const strengthText = computed(() => {
const texts = ['弱', '一般', '良好', '强']
return texts[strengthLevel.value - 1] || '弱'
})
const canRegister = computed(() => {
return agreeTerms.value &&
usernameAvailable.value &&
emailAvailable.value &&
registerForm.password &&
registerForm.confirmPassword &&
registerForm.password === registerForm.confirmPassword
})
const handleRegister = async () => {
if (!registerFormRef.value) return
try {
const valid = await registerFormRef.value.validate()
if (!valid) return
if (!agreeTerms.value) {
ElMessage.warning('请先同意用户协议和隐私政策')
return
}
const result = await userStore.registerUser(registerForm)
if (result.success) {
ElMessage.success(result.message || '注册成功')
router.push('/login')
} else {
ElMessage.error(result.message || '注册失败')
}
} catch (error) {
console.error('Register error:', error)
ElMessage.error('注册失败,请重试')
}
}
</script>
<style scoped>
.register {
min-height: calc(100vh - 120px);
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 40px 0;
position: relative;
overflow-x: hidden;
}
/* 页面特殊效果 */
.register::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
radial-gradient(circle at 25% 25%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
radial-gradient(circle at 75% 75%, rgba(255, 255, 255, 0.05) 0%, transparent 50%);
animation: registerFloat 4s ease-in-out infinite alternate;
pointer-events: none;
z-index: 1;
}
@keyframes registerFloat {
0% { transform: translateY(0px) rotate(0deg); }
100% { transform: translateY(-10px) rotate(1deg); }
}
/* 内容层级 */
.register > * {
position: relative;
z-index: 2;
}
.register-container {
min-height: calc(100vh - 200px);
}
.register-card {
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
border-radius: 12px;
backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.95);
}
.register-header {
text-align: center;
margin-bottom: 20px;
}
.register-header h2 {
margin: 12px 0 0 0;
color: #303133;
font-weight: 600;
}
.register-button {
width: 100%;
height: 45px;
font-size: 16px;
}
.register-footer {
text-align: center;
margin-top: 20px;
}
.register-footer p {
margin: 0;
color: #606266;
}
.login-link {
color: #67C23A;
text-decoration: none;
font-weight: 500;
}
.login-link:hover {
text-decoration: underline;
}
.terms-link {
color: #409EFF;
text-decoration: none;
}
.terms-link:hover {
text-decoration: underline;
}
.checking-text, .error-text, .success-text {
font-size: 12px;
margin-top: 4px;
display: flex;
align-items: center;
gap: 4px;
}
.checking-text {
color: #909399;
}
.error-text {
color: #F56C6C;
}
.success-text {
color: #67C23A;
}
.password-strength {
margin-top: 8px;
}
.strength-bar {
height: 4px;
background-color: #EBEEF5;
border-radius: 2px;
overflow: hidden;
margin-bottom: 4px;
}
.strength-fill {
height: 100%;
transition: all 0.3s;
}
.strength-fill.weak {
background-color: #F56C6C;
}
.strength-fill.fair {
background-color: #E6A23C;
}
.strength-fill.good {
background-color: #409EFF;
}
.strength-fill.strong {
background-color: #67C23A;
}
.strength-text {
font-size: 12px;
color: #606266;
}
@media (max-width: 768px) {
.register {
padding: 20px 0;
}
.register-container {
min-height: calc(100vh - 160px);
}
}
</style>