初次提交

This commit is contained in:
szz
2025-07-26 15:35:53 +08:00
commit 6da8ea34cb
64 changed files with 15380 additions and 0 deletions

738
src/views/Register.vue Normal file
View File

@@ -0,0 +1,738 @@
<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' },
{ min: 6, message: '激活码长度不能少于6位', 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>