741 lines
17 KiB
Vue
741 lines
17 KiB
Vue
<template>
|
||
<div class="register-page">
|
||
<div class="register-container">
|
||
<div class="register-card">
|
||
<div class="register-header">
|
||
<h2>用户注册</h2>
|
||
<p>创建你的账号,开始使用前端H5系统</p>
|
||
</div>
|
||
|
||
<el-form
|
||
ref="registerFormRef"
|
||
:model="registerForm"
|
||
:rules="registerRules"
|
||
class="register-form"
|
||
@submit.prevent="handleRegister"
|
||
>
|
||
<el-form-item prop="username">
|
||
<el-input
|
||
v-model="registerForm.username"
|
||
placeholder="请输入用户名"
|
||
size="large"
|
||
:prefix-icon="User"
|
||
clearable
|
||
/>
|
||
</el-form-item>
|
||
|
||
<el-form-item prop="phone">
|
||
<el-input
|
||
v-model="registerForm.phone"
|
||
placeholder="请输入手机号"
|
||
size="large"
|
||
:prefix-icon="Message"
|
||
clearable
|
||
/>
|
||
</el-form-item>
|
||
|
||
<!-- 短信验证码组件 - 已注释 -->
|
||
<!-- <el-form-item prop="smsCode">
|
||
<div class="sms-code-group">
|
||
<el-input
|
||
v-model="registerForm.smsCode"
|
||
placeholder="请输入短信验证码"
|
||
size="large"
|
||
:prefix-icon="ChatDotRound"
|
||
clearable
|
||
class="sms-input"
|
||
/>
|
||
<el-button
|
||
type="primary"
|
||
size="large"
|
||
:disabled="!canSendSMS || smsCountdown > 0"
|
||
:loading="sendingSMS"
|
||
@click="sendSMSCode"
|
||
class="sms-button"
|
||
>
|
||
{{ smsCountdown > 0 ? `${smsCountdown}s后重发` : '发送验证码' }}
|
||
</el-button>
|
||
</div>
|
||
</el-form-item> -->
|
||
|
||
<el-form-item prop="registrationCode">
|
||
<el-input
|
||
v-model="registerForm.registrationCode"
|
||
placeholder="请输入激活码"
|
||
size="large"
|
||
:prefix-icon="Ticket"
|
||
clearable
|
||
/>
|
||
</el-form-item>
|
||
|
||
|
||
|
||
<el-form-item prop="password">
|
||
<el-input
|
||
v-model="registerForm.password"
|
||
type="password"
|
||
placeholder="请输入密码"
|
||
size="large"
|
||
:prefix-icon="Lock"
|
||
show-password
|
||
clearable
|
||
/>
|
||
</el-form-item>
|
||
|
||
<el-form-item prop="confirmPassword">
|
||
<el-input
|
||
v-model="registerForm.confirmPassword"
|
||
type="password"
|
||
placeholder="请确认密码"
|
||
size="large"
|
||
:prefix-icon="Lock"
|
||
show-password
|
||
clearable
|
||
/>
|
||
</el-form-item>
|
||
|
||
<el-form-item prop="captcha">
|
||
<Captcha
|
||
ref="captchaRef"
|
||
v-model="registerForm.captcha"
|
||
placeholder="请输入验证码"
|
||
size="large"
|
||
/>
|
||
</el-form-item>
|
||
|
||
<el-form-item prop="agreement">
|
||
<el-checkbox v-model="registerForm.agreement">
|
||
我已阅读并同意
|
||
<el-link type="primary" @click="showAgreement">
|
||
《用户协议》
|
||
</el-link>
|
||
和
|
||
<el-link type="primary" @click="showPrivacy">
|
||
《隐私政策》
|
||
</el-link>
|
||
</el-checkbox>
|
||
</el-form-item>
|
||
|
||
<el-form-item>
|
||
<el-button
|
||
type="primary"
|
||
size="large"
|
||
class="register-button"
|
||
:loading="userStore.loading"
|
||
@click="handleRegister"
|
||
>
|
||
{{ userStore.loading ? '注册中...' : '立即注册' }}
|
||
</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<div class="register-footer">
|
||
<p>
|
||
已有账号?
|
||
<el-link type="primary" @click="$router.push('/login')">
|
||
立即登录
|
||
</el-link>
|
||
</p>
|
||
</div>
|
||
|
||
<div class="features-preview">
|
||
<el-divider>注册后你可以</el-divider>
|
||
<div class="features-list">
|
||
<div class="feature-item">
|
||
<el-icon><User /></el-icon>
|
||
<span>个性化用户中心</span>
|
||
</div>
|
||
<div class="feature-item">
|
||
<el-icon><CreditCard /></el-icon>
|
||
<span>积分商城购物</span>
|
||
</div>
|
||
<div class="feature-item">
|
||
<el-icon><ChatDotRound /></el-icon>
|
||
<span>积分转账功能</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 背景装饰 -->
|
||
<div class="background-decoration">
|
||
<div class="decoration-shape shape-1"></div>
|
||
<div class="decoration-shape shape-2"></div>
|
||
<div class="decoration-shape shape-3"></div>
|
||
<div class="decoration-shape shape-4"></div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive, computed, onMounted } from 'vue'
|
||
import { useRouter, useRoute } from 'vue-router'
|
||
import { useUserStore } from '@/stores/user'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import { User, Lock, Message, Edit, ChatDotRound, CreditCard, Ticket } from '@element-plus/icons-vue'
|
||
import Captcha from '@/components/Captcha.vue'
|
||
|
||
const router = useRouter()
|
||
const route = useRoute()
|
||
const userStore = useUserStore()
|
||
|
||
// 表单引用
|
||
const registerFormRef = ref()
|
||
const captchaRef = ref()
|
||
|
||
// 表单数据
|
||
const registerForm = reactive({
|
||
username: '',
|
||
phone: '',
|
||
registrationCode: '',
|
||
password: '',
|
||
confirmPassword: '',
|
||
captcha: '',
|
||
// smsCode: '', // 短信验证码字段 - 已注释
|
||
agreement: false
|
||
})
|
||
|
||
// 短信验证码相关状态 - 已注释
|
||
// const sendingSMS = ref(false)
|
||
// const smsCountdown = ref(0)
|
||
// const canSendSMS = computed(() => {
|
||
// const phoneRegex = /^1[3-9]\d{9}$/
|
||
// return phoneRegex.test(registerForm.phone)
|
||
// })
|
||
|
||
// 自定义验证函数
|
||
const validateUsername = (rule, value, callback) => {
|
||
if (!value) {
|
||
callback(new Error('请输入用户名'))
|
||
} else if (value.length < 3) {
|
||
callback(new Error('用户名至少3个字符'))
|
||
} else if (value.length > 20) {
|
||
callback(new Error('用户名不能超过20个字符'))
|
||
} else if (!/^[a-zA-Z0-9_\u4e00-\u9fa5]+$/.test(value)) {
|
||
callback(new Error('用户名只能包含字母、数字、下划线和中文'))
|
||
} else {
|
||
callback()
|
||
}
|
||
}
|
||
|
||
const validatePassword = (rule, value, callback) => {
|
||
if (!value) {
|
||
callback(new Error('请输入密码'))
|
||
} else if (value.length < 6) {
|
||
callback(new Error('密码至少6个字符'))
|
||
} else if (value.length > 20) {
|
||
callback(new Error('密码不能超过20个字符'))
|
||
} else if (!/(?=.*[a-zA-Z])(?=.*\d)/.test(value)) {
|
||
callback(new Error('密码必须包含字母和数字'))
|
||
} else {
|
||
// 如果确认密码已输入,重新验证确认密码
|
||
if (registerForm.confirmPassword) {
|
||
registerFormRef.value?.validateField('confirmPassword')
|
||
}
|
||
callback()
|
||
}
|
||
}
|
||
|
||
const validateConfirmPassword = (rule, value, callback) => {
|
||
if (!value) {
|
||
callback(new Error('请确认密码'))
|
||
} else if (value !== registerForm.password) {
|
||
callback(new Error('两次输入的密码不一致'))
|
||
} else {
|
||
callback()
|
||
}
|
||
}
|
||
|
||
const validateAgreement = (rule, value, callback) => {
|
||
if (!value) {
|
||
callback(new Error('请阅读并同意用户协议和隐私政策'))
|
||
} else {
|
||
callback()
|
||
}
|
||
}
|
||
|
||
// 表单验证规则
|
||
const registerRules = {
|
||
username: [{ validator: validateUsername, trigger: 'blur' }],
|
||
phone: [
|
||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
|
||
],
|
||
registrationCode: [
|
||
{ required: true, message: '请输入激活码', trigger: 'blur' },
|
||
{ len: 6, message: '激活码长度为6位', trigger: 'blur' },
|
||
{ pattern: /^[A-Z0-9]{6}$/, message: '激活码只能包含大写字母和数字', trigger: 'blur' }
|
||
],
|
||
// 短信验证码验证规则 - 已注释
|
||
// smsCode: [
|
||
// { required: true, message: '请输入短信验证码', trigger: 'blur' },
|
||
// { pattern: /^\d{6}$/, message: '短信验证码为6位数字', trigger: 'blur' }
|
||
// ],
|
||
|
||
password: [{ validator: validatePassword, trigger: 'blur' }],
|
||
confirmPassword: [{ validator: validateConfirmPassword, trigger: 'blur' }],
|
||
captcha: [
|
||
{ required: true, message: '请输入验证码', trigger: 'blur' },
|
||
{ min: 4, max: 4, message: '验证码长度为4位', trigger: 'blur' }
|
||
],
|
||
agreement: [{ validator: validateAgreement, trigger: 'change' }]
|
||
}
|
||
|
||
// 发送短信验证码 - 已注释
|
||
// const sendSMSCode = async () => {
|
||
// if (!canSendSMS.value || sendingSMS.value || smsCountdown.value > 0) {
|
||
// return
|
||
// }
|
||
//
|
||
// try {
|
||
// sendingSMS.value = true
|
||
//
|
||
// const response = await fetch('/api/sms/send', {
|
||
// method: 'POST',
|
||
// headers: {
|
||
// 'Content-Type': 'application/json'
|
||
// },
|
||
// body: JSON.stringify({
|
||
// phone: registerForm.phone
|
||
// })
|
||
// })
|
||
//
|
||
// const result = await response.json()
|
||
//
|
||
// if (result.success) {
|
||
// ElMessage.success('验证码发送成功,请查收短信')
|
||
// // 开始倒计时
|
||
// startCountdown()
|
||
// } else {
|
||
// ElMessage.error(result.message || '发送失败,请重试')
|
||
// }
|
||
// } catch (error) {
|
||
// console.error('发送短信验证码失败:', error)
|
||
// ElMessage.error('发送失败,请检查网络连接')
|
||
// } finally {
|
||
// sendingSMS.value = false
|
||
// }
|
||
// }
|
||
|
||
// 开始倒计时 - 已注释
|
||
// const startCountdown = () => {
|
||
// smsCountdown.value = 60
|
||
// const timer = setInterval(() => {
|
||
// smsCountdown.value--
|
||
// if (smsCountdown.value <= 0) {
|
||
// clearInterval(timer)
|
||
// }
|
||
// }, 1000)
|
||
// }
|
||
|
||
// 处理注册
|
||
const handleRegister = async () => {
|
||
if (!registerFormRef.value || !captchaRef.value) return
|
||
|
||
try {
|
||
// 先验证表单
|
||
const valid = await registerFormRef.value.validate()
|
||
if (!valid) return
|
||
|
||
// 验证验证码
|
||
const captchaValid = await captchaRef.value.verifyCaptcha(registerForm.captcha)
|
||
if (!captchaValid) {
|
||
registerForm.captcha = ''
|
||
return
|
||
}
|
||
|
||
// 获取验证码信息
|
||
const captchaInfo = captchaRef.value.getCaptchaInfo()
|
||
|
||
// 提交注册请求(包含验证码信息)
|
||
const registerData = {
|
||
username: registerForm.username,
|
||
phone: registerForm.phone,
|
||
registrationCode: registerForm.registrationCode,
|
||
password: registerForm.password,
|
||
// smsCode: registerForm.smsCode, // 短信验证码 - 已注释
|
||
captchaId: captchaInfo.captchaId,
|
||
captchaText: captchaInfo.captchaText
|
||
}
|
||
|
||
const result = await userStore.register(registerData)
|
||
|
||
if (result.success) {
|
||
ElMessage.success('注册成功!请登录')
|
||
router.push('/login')
|
||
}
|
||
} catch (error) {
|
||
console.error('注册失败:', error)
|
||
// 注册失败后刷新验证码
|
||
if (captchaRef.value) {
|
||
await captchaRef.value.refreshCaptcha()
|
||
}
|
||
registerForm.captcha = ''
|
||
}
|
||
}
|
||
|
||
// 显示用户协议
|
||
const showAgreement = () => {
|
||
ElMessageBox.alert(
|
||
`<div style="text-align: left; line-height: 1.6;">
|
||
<h3>用户协议</h3>
|
||
<p>1. 用户应当遵守法律法规,不得发布违法违规内容。</p>
|
||
<p>2. 用户对自己发布的内容承担全部责任。</p>
|
||
<p>3. 平台有权对违规内容进行删除或限制。</p>
|
||
<p>4. 用户应当保护好自己的账号安全。</p>
|
||
<p>5. 平台保留修改本协议的权利。</p>
|
||
</div>`,
|
||
'用户协议',
|
||
{
|
||
confirmButtonText: '我已了解',
|
||
dangerouslyUseHTMLString: true,
|
||
customClass: 'agreement-dialog'
|
||
}
|
||
)
|
||
}
|
||
|
||
// 显示隐私政策
|
||
const showPrivacy = () => {
|
||
ElMessageBox.alert(
|
||
`<div style="text-align: left; line-height: 1.6;">
|
||
<h3>隐私政策</h3>
|
||
<p>1. 我们重视用户隐私保护。</p>
|
||
<p>2. 我们只收集必要的用户信息。</p>
|
||
<p>3. 用户信息仅用于提供服务。</p>
|
||
<p>4. 我们不会向第三方泄露用户信息。</p>
|
||
<p>5. 用户有权查看、修改或删除个人信息。</p>
|
||
</div>`,
|
||
'隐私政策',
|
||
{
|
||
confirmButtonText: '我已了解',
|
||
dangerouslyUseHTMLString: true,
|
||
customClass: 'privacy-dialog'
|
||
}
|
||
)
|
||
}
|
||
|
||
// 图片上传成功处理
|
||
const handleUploadSuccess = (response, field) => {
|
||
ElMessage.success('图片上传成功')
|
||
}
|
||
|
||
// 图片上传失败处理
|
||
const handleUploadError = (error) => {
|
||
ElMessage.error('图片上传失败,请重试')
|
||
}
|
||
|
||
// 组件挂载时的处理
|
||
onMounted(() => {
|
||
// 如果已经登录,直接跳转
|
||
if (userStore.isAuthenticated) {
|
||
const redirectPath = route.query.redirect || '/'
|
||
router.push(redirectPath)
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.register-page {
|
||
min-height: 100vh;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.register-container {
|
||
width: 100%;
|
||
max-width: 450px;
|
||
padding: 20px;
|
||
position: relative;
|
||
z-index: 10;
|
||
}
|
||
|
||
.register-card {
|
||
background: rgba(255, 255, 255, 0.95);
|
||
backdrop-filter: blur(10px);
|
||
border-radius: 16px;
|
||
padding: 40px 30px;
|
||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
}
|
||
|
||
.register-header {
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.register-header h2 {
|
||
color: #303133;
|
||
margin-bottom: 8px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.register-header p {
|
||
color: #909399;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.register-form {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.register-button {
|
||
width: 100%;
|
||
height: 44px;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.register-footer {
|
||
text-align: center;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.register-footer p {
|
||
color: #606266;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.document-upload-section {
|
||
margin: 20px 0;
|
||
}
|
||
|
||
.document-upload-section .el-divider {
|
||
margin: 20px 0;
|
||
}
|
||
|
||
.document-upload-section .el-form-item {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.document-upload-section .el-form-item .el-form-item__label {
|
||
font-weight: 500;
|
||
color: #606266;
|
||
}
|
||
|
||
.features-preview {
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.features-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
margin-top: 15px;
|
||
}
|
||
|
||
.feature-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
color: #606266;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.feature-item .el-icon {
|
||
color: #409eff;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.upload-preview {
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.upload-preview img {
|
||
width: 100px;
|
||
height: 100px;
|
||
object-fit: cover;
|
||
border-radius: 8px;
|
||
border: 1px solid #dcdfe6;
|
||
}
|
||
|
||
.upload-demo {
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.background-decoration {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.decoration-shape {
|
||
position: absolute;
|
||
background: rgba(255, 255, 255, 0.1);
|
||
animation: float 8s ease-in-out infinite;
|
||
}
|
||
|
||
.shape-1 {
|
||
width: 100px;
|
||
height: 100px;
|
||
border-radius: 50%;
|
||
top: 15%;
|
||
left: 15%;
|
||
animation-delay: 0s;
|
||
}
|
||
|
||
.shape-2 {
|
||
width: 80px;
|
||
height: 80px;
|
||
border-radius: 20px;
|
||
top: 70%;
|
||
right: 20%;
|
||
animation-delay: 2s;
|
||
}
|
||
|
||
.shape-3 {
|
||
width: 60px;
|
||
height: 60px;
|
||
border-radius: 50%;
|
||
bottom: 25%;
|
||
left: 25%;
|
||
animation-delay: 4s;
|
||
}
|
||
|
||
.shape-4 {
|
||
width: 120px;
|
||
height: 40px;
|
||
border-radius: 20px;
|
||
top: 40%;
|
||
right: 10%;
|
||
animation-delay: 6s;
|
||
}
|
||
|
||
@keyframes float {
|
||
0%, 100% {
|
||
transform: translateY(0px) rotate(0deg);
|
||
opacity: 0.7;
|
||
}
|
||
50% {
|
||
transform: translateY(-15px) rotate(180deg);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
/* 响应式设计 */
|
||
@media (max-width: 480px) {
|
||
.register-container {
|
||
padding: 15px;
|
||
}
|
||
|
||
.register-card {
|
||
padding: 30px 20px;
|
||
}
|
||
|
||
.features-list {
|
||
gap: 8px;
|
||
}
|
||
}
|
||
|
||
/* Element Plus 组件样式覆盖 */
|
||
:deep(.el-input__wrapper) {
|
||
border-radius: 8px;
|
||
}
|
||
|
||
:deep(.el-button) {
|
||
border-radius: 8px;
|
||
}
|
||
|
||
:deep(.el-divider__text) {
|
||
background-color: rgba(255, 255, 255, 0.95);
|
||
color: #909399;
|
||
}
|
||
|
||
:deep(.el-checkbox__label) {
|
||
font-size: 14px;
|
||
color: #606266;
|
||
}
|
||
|
||
/* 输入框聚焦效果 */
|
||
:deep(.el-input__wrapper:hover),
|
||
:deep(.el-input__wrapper.is-focus) {
|
||
box-shadow: 0 0 0 1px #409eff inset;
|
||
}
|
||
|
||
/* 加载状态样式 */
|
||
.register-button.is-loading {
|
||
pointer-events: none;
|
||
}
|
||
|
||
/* 动画效果 */
|
||
.register-card {
|
||
animation: slideInUp 0.6s ease-out;
|
||
}
|
||
|
||
@keyframes slideInUp {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(30px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
/* 错误状态样式 */
|
||
:deep(.el-form-item.is-error .el-input__wrapper) {
|
||
box-shadow: 0 0 0 1px #f56c6c inset;
|
||
}
|
||
|
||
/* 成功状态样式 */
|
||
:deep(.el-form-item.is-success .el-input__wrapper) {
|
||
box-shadow: 0 0 0 1px #67c23a inset;
|
||
}
|
||
|
||
/* 协议对话框样式 */
|
||
:deep(.agreement-dialog),
|
||
:deep(.privacy-dialog) {
|
||
.el-message-box__content {
|
||
max-height: 400px;
|
||
overflow-y: auto;
|
||
}
|
||
}
|
||
|
||
/* 密码强度指示器 */
|
||
.password-strength {
|
||
margin-top: 5px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.strength-weak {
|
||
color: #f56c6c;
|
||
}
|
||
|
||
.strength-medium {
|
||
color: #e6a23c;
|
||
}
|
||
|
||
.strength-strong {
|
||
color: #67c23a;
|
||
}
|
||
|
||
/* 短信验证码样式 */
|
||
.sms-code-group {
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: center;
|
||
}
|
||
|
||
.sms-input {
|
||
flex: 1;
|
||
}
|
||
|
||
.sms-button {
|
||
flex-shrink: 0;
|
||
min-width: 120px;
|
||
height: 40px;
|
||
}
|
||
|
||
.sms-button:disabled {
|
||
opacity: 0.6;
|
||
cursor: not-allowed;
|
||
}
|
||
</style> |