|
@@ -0,0 +1,269 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="register-page">
|
|
|
|
|
+ <div class="register-container">
|
|
|
|
|
+ <!-- Logo 区域 -->
|
|
|
|
|
+ <div class="register-header">
|
|
|
|
|
+ <div class="logo">🚀 灵越智报</div>
|
|
|
|
|
+ <p class="subtitle">智能报告生成平台</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 注册表单 -->
|
|
|
|
|
+ <el-card class="register-card">
|
|
|
|
|
+ <template #header>
|
|
|
|
|
+ <div class="card-header">
|
|
|
|
|
+ <span>用户注册</span>
|
|
|
|
|
+ <router-link to="/login" class="switch-link">已有账号?立即登录</router-link>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form
|
|
|
|
|
+ ref="formRef"
|
|
|
|
|
+ :model="form"
|
|
|
|
|
+ :rules="rules"
|
|
|
|
|
+ label-width="0"
|
|
|
|
|
+ size="large"
|
|
|
|
|
+ @submit.prevent="handleRegister"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-form-item prop="username">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="form.username"
|
|
|
|
|
+ placeholder="用户名(3-20个字符,字母、数字、下划线)"
|
|
|
|
|
+ :prefix-icon="User"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item prop="email">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="form.email"
|
|
|
|
|
+ placeholder="邮箱地址"
|
|
|
|
|
+ :prefix-icon="Message"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item prop="password">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="form.password"
|
|
|
|
|
+ type="password"
|
|
|
|
|
+ placeholder="密码(8-32位,包含字母和数字)"
|
|
|
|
|
+ :prefix-icon="Lock"
|
|
|
|
|
+ show-password
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item prop="confirmPassword">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="form.confirmPassword"
|
|
|
|
|
+ type="password"
|
|
|
|
|
+ placeholder="确认密码"
|
|
|
|
|
+ :prefix-icon="Lock"
|
|
|
|
|
+ show-password
|
|
|
|
|
+ @keyup.enter="handleRegister"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item prop="agreement">
|
|
|
|
|
+ <el-checkbox v-model="form.agreement">
|
|
|
|
|
+ 我已阅读并同意
|
|
|
|
|
+ <a href="#" class="link">《用户协议》</a>
|
|
|
|
|
+ 和
|
|
|
|
|
+ <a href="#" class="link">《隐私政策》</a>
|
|
|
|
|
+ </el-checkbox>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ class="register-btn"
|
|
|
|
|
+ :loading="loading"
|
|
|
|
|
+ @click="handleRegister"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ loading ? '注册中...' : '立即注册' }}
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 底部信息 -->
|
|
|
|
|
+ <div class="register-footer">
|
|
|
|
|
+ <p>© 2026 灵越智报 - 企业级智能报告生成平台</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup>
|
|
|
|
|
+import { ref, reactive } from 'vue'
|
|
|
|
|
+import { useRouter } from 'vue-router'
|
|
|
|
|
+import { User, Lock, Message } from '@element-plus/icons-vue'
|
|
|
|
|
+import { ElMessage } from 'element-plus'
|
|
|
|
|
+import { authApi } from '@/api'
|
|
|
|
|
+
|
|
|
|
|
+const router = useRouter()
|
|
|
|
|
+const formRef = ref(null)
|
|
|
|
|
+const loading = ref(false)
|
|
|
|
|
+
|
|
|
|
|
+const form = reactive({
|
|
|
|
|
+ username: '',
|
|
|
|
|
+ email: '',
|
|
|
|
|
+ password: '',
|
|
|
|
|
+ confirmPassword: '',
|
|
|
|
|
+ agreement: false
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 确认密码验证
|
|
|
|
|
+const validateConfirmPassword = (rule, value, callback) => {
|
|
|
|
|
+ if (value !== form.password) {
|
|
|
|
|
+ callback(new Error('两次输入的密码不一致'))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ callback()
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 用户协议验证
|
|
|
|
|
+const validateAgreement = (rule, value, callback) => {
|
|
|
|
|
+ if (!value) {
|
|
|
|
|
+ callback(new Error('请阅读并同意用户协议'))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ callback()
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const rules = {
|
|
|
|
|
+ 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: 8, max: 32, message: '密码长度必须在8-32个字符之间', trigger: 'blur' },
|
|
|
|
|
+ { pattern: /^(?=.*[a-zA-Z])(?=.*\d).+$/, message: '密码必须包含字母和数字', trigger: 'blur' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ confirmPassword: [
|
|
|
|
|
+ { required: true, message: '请再次输入密码', trigger: 'blur' },
|
|
|
|
|
+ { validator: validateConfirmPassword, trigger: 'blur' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ agreement: [
|
|
|
|
|
+ { validator: validateAgreement, trigger: 'change' }
|
|
|
|
|
+ ]
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function handleRegister() {
|
|
|
|
|
+ if (!formRef.value) return
|
|
|
|
|
+
|
|
|
|
|
+ await formRef.value.validate(async (valid) => {
|
|
|
|
|
+ if (!valid) return
|
|
|
|
|
+
|
|
|
|
|
+ loading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await authApi.register({
|
|
|
|
|
+ username: form.username,
|
|
|
|
|
+ email: form.email,
|
|
|
|
|
+ password: form.password,
|
|
|
|
|
+ confirmPassword: form.confirmPassword
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 注册成功后自动登录
|
|
|
|
|
+ localStorage.setItem('accessToken', response.accessToken)
|
|
|
|
|
+ localStorage.setItem('refreshToken', response.refreshToken)
|
|
|
|
|
+ localStorage.setItem('userId', response.userId)
|
|
|
|
|
+ localStorage.setItem('username', response.username)
|
|
|
|
|
+
|
|
|
|
|
+ ElMessage.success('注册成功')
|
|
|
|
|
+ router.push('/')
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('注册失败:', error)
|
|
|
|
|
+ ElMessage.error(error.message || '注册失败,请稍后重试')
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
|
+.register-page {
|
|
|
|
|
+ min-height: 100vh;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.register-container {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ max-width: 420px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.register-header {
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ margin-bottom: 30px;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+
|
|
|
|
|
+ .logo {
|
|
|
|
|
+ font-size: 32px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .subtitle {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ opacity: 0.9;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.register-card {
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
|
|
|
|
|
+
|
|
|
|
|
+ .card-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+
|
|
|
|
|
+ span {
|
|
|
|
|
+ font-size: 18px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .switch-link {
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: var(--el-color-primary);
|
|
|
|
|
+ text-decoration: none;
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ text-decoration: underline;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.link {
|
|
|
|
|
+ color: var(--el-color-primary);
|
|
|
|
|
+ text-decoration: none;
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ text-decoration: underline;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.register-btn {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 44px;
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.register-footer {
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ margin-top: 24px;
|
|
|
|
|
+ color: rgba(255, 255, 255, 0.7);
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|