Files
jurong_circle_frontdesk/src/views/Register.vue
2025-07-28 12:22:11 +08:00

741 lines
17 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-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>