@@ -4,7 +4,7 @@
< div class = "register-card" >
< div class = "register-header" >
< h2 > 用户注册 < / h2 >
< p > 创建你的账号 , 开始使用前端H5系统 < / p >
< p > 创建你的账号 , 开始使用炬融圈 < / p >
< / div >
< el-form
@@ -57,51 +57,27 @@
< / div >
< / el-form-item >
< el-form-item prop = "city" >
< el-select
v-mo del = "registerForm.city "
placeholder = "请选择城市"
< el-form-item prop = "region" >
< el-cascader
v-model = "registerForm.region"
:options = "regionOptions"
placehol der = "请选择省市区 "
size = "large"
clearable
@change ="onCityChange"
style = "width: 100%"
: props = "{ expandTrigger: 'hover', value: 'code' }"
:show-all-levels = "true"
:collapse-tags = "true"
:max-collapse-tags = "1"
:teleported = "false"
popper -class = " mobile -cascader -popper "
@change ="handleRegionChange"
>
< template # prefix >
< el-icon > < Location / > < / el-icon >
< / template >
< el-option
v-for = "city in cities"
:key = "city"
:label = "city"
:value = "city"
/ >
< / el-select >
< / el-cascader >
< / el-form-item >
< el-form-item prop = "district_id" >
< el-select
v-model = "registerForm.district_id"
placeholder = "请选择区域"
size = "large"
clearable
:disabled = "!registerForm.city"
style = "width: 100%"
>
< template # prefix >
< el-icon > < Location / > < / el-icon >
< / template >
< el-option
v-for = "district in filteredDistricts"
:key = "district.id"
:label = "district.district_name"
:value = "district.id"
/ >
< / el-select >
< / el-form-item >
< el-form-item prop = "password" >
< el-input
v-model = "registerForm.password"
@@ -144,7 +120,7 @@
< el-link
type = "primary"
@click ="showAgreement"
: class = "{ ' viewed' : agreementViewed }"
: class = "{ viewed: agreementViewed }"
>
《 用户协议 》
< / el-link >
@@ -152,7 +128,7 @@
< el-link
type = "primary"
@click ="showPrivacy"
: class = "{ ' viewed' : privacyViewed }"
: class = "{ viewed: privacyViewed }"
>
《 隐私政策 》
< / el-link >
@@ -184,24 +160,6 @@
< / 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 >
@@ -216,235 +174,269 @@
< / 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 , Location , InfoFilled } from '@element-plus/icons-vue'
import Captcha from '@/components/Captcha.vue'
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 ,
Location ,
InfoFilled ,
} from '@element-plus/icons-vue' ;
import Captcha from '@/components/Captcha.vue' ;
import api from '@/utils/api'
const router = useRouter ( )
const route = useRoute ( )
const userStore = useUserStore ( )
const router = useRouter ( ) ;
const route = useRoute ( ) ;
const userStore = useUserStore ( ) ;
// 表单引用
const registerFormRef = ref ( )
const captchaRef = ref ( )
const registerFormRef = ref ( ) ;
const captchaRef = ref ( ) ;
// 表单数据
const registerForm = reactive ( {
username : '' ,
phone : '' ,
region : [ ] ,
province : '' ,
city : '' ,
district : '' ,
district _id : '' ,
password : '' ,
confirmPassword : '' ,
captcha : '' ,
smsCode : '' ,
agreement : false
} )
agreement : false ,
} ) ;
// 协议查看状态
const agreementViewed = ref ( false )
const privacyViewed = ref ( false )
const canCheckAgreement = computed ( ( ) => agreementViewed . value && privacyViewed . value )
const agreementViewed = ref ( false ) ;
const privacyViewed = ref ( false ) ;
const canCheckAgreement = computed (
( ) => agreementViewed . value && privacyViewed . value
) ;
// 短信验证码相关状态
const sendingSMS = ref ( false )
const smsCountdown = ref ( 0 )
const sendingSMS = ref ( false ) ;
const smsCountdown = ref ( 0 ) ;
const canSendSMS = computed ( ( ) => {
const phoneRegex = /^1[3-9]\d{9}$/
return phoneRegex . test ( registerForm . phone )
} )
const phoneRegex = /^1[3-9]\d{9}$/ ;
return phoneRegex . test ( registerForm . phone ) ;
} ) ;
// 地区数据
const regions = ref ( [ ] )
const regions = ref ( [ ] ) ;
const regionOptions = ref ( [ ] ) ;
const cities = computed ( ( ) => {
const citySet = new Set ( )
regions . value . forEach ( region => {
const citySet = new Set ( ) ;
regions . value . forEach ( ( region ) => {
if ( region . city _name ) {
citySet . add ( region . city _name )
citySet . add ( region . city _name ) ;
}
} )
return Array . from ( citySet ) . sort ( )
} )
} ) ;
return Array . from ( citySet ) . sort ( ) ;
} ) ;
const filteredDistricts = computed ( ( ) => {
if ( ! registerForm . city ) return [ ]
return regions . value . filter ( region => region . city _name === registerForm . city )
} )
if ( ! registerForm . city ) return [ ] ;
return regions . value . filter (
( region ) => region . city _name === registerForm . city
) ;
} ) ;
// 自定义验证函数
const validateUsername = ( rule , value , callback ) => {
if ( ! value ) {
callback ( new Error ( '请输入用户名' ) )
callback ( new Error ( '请输入用户名' ) ) ;
} else if ( value . length < 3 ) {
callback ( new Error ( '用户名至少3个字符' ) )
callback ( new Error ( '用户名至少3个字符' ) ) ;
} else if ( value . length > 20 ) {
callback ( new Error ( '用户名不能超过20个字符' ) )
callback ( new Error ( '用户名不能超过20个字符' ) ) ;
} else if ( ! /^[a-zA-Z0-9_\u4e00-\u9fa5]+$/ . test ( value ) ) {
callback ( new Error ( '用户名只能包含字母、数字、下划线和中文' ) )
callback ( new Error ( '用户名只能包含字母、数字、下划线和中文' ) ) ;
} else {
callback ( )
}
callback ( ) ;
}
} ;
const validatePassword = ( rule , value , callback ) => {
if ( ! value ) {
callback ( new Error ( '请输入密码' ) )
callback ( new Error ( '请输入密码' ) ) ;
} else if ( value . length < 6 ) {
callback ( new Error ( '密码至少6个字符' ) )
callback ( new Error ( '密码至少6个字符' ) ) ;
} else if ( value . length > 20 ) {
callback ( new Error ( '密码不能超过20个字符' ) )
callback ( new Error ( '密码不能超过20个字符' ) ) ;
} else if ( ! /(?=.*[a-zA-Z])(?=.*\d)/ . test ( value ) ) {
callback ( new Error ( '密码必须包含字母和数字' ) )
callback ( new Error ( '密码必须包含字母和数字' ) ) ;
} else {
// 如果确认密码已输入,重新验证确认密码
if ( registerForm . confirmPassword ) {
registerFormRef . value ? . validateField ( 'confirmPassword' )
}
callback ( )
registerFormRef . value ? . validateField ( 'confirmPassword' ) ;
}
callback ( ) ;
}
} ;
const validateConfirmPassword = ( rule , value , callback ) => {
if ( ! value ) {
callback ( new Error ( '请确认密码' ) )
callback ( new Error ( '请确认密码' ) ) ;
} else if ( value !== registerForm . password ) {
callback ( new Error ( '两次输入的密码不一致' ) )
callback ( new Error ( '两次输入的密码不一致' ) ) ;
} else {
callback ( )
}
callback ( ) ;
}
} ;
const validateAgreement = ( rule , value , callback ) => {
if ( ! value ) {
callback ( new Error ( '请阅读并同意用户协议和隐私政策' ) )
callback ( new Error ( '请阅读并同意用户协议和隐私政策' ) ) ;
} else {
callback ( )
}
callback ( ) ;
}
} ;
// 表单验证规则
const registerRules = {
username : [ { validator : validateUsername , trigger : 'blur' } ] ,
phone : [
{ required : true , message : '请输入手机号' , trigger : 'blur' } ,
{ pattern : /^1[3-9]\d{9}$/ , message : '请输入正确的手机号' , trigger : 'blur' }
{
pattern : /^1[3-9]\d{9}$/ ,
message : '请输入正确的手机号' ,
trigger : 'blur' ,
} ,
] ,
smsCode : [
{ required : true , message : '请输入短信验证码' , trigger : 'blur' } ,
{ pattern : /^\d{6}$/ , message : '短信验证码为6位数字' , trigger : 'blur' }
] ,
city : [
{ required : true , message : '请选择城市' , trigger : 'change' }
] ,
district _id : [
{ required : true , message : '请选择区域' , trigger : 'change' }
{ pattern : /^\d{6}$/ , message : '短信验证码为6位数字' , trigger : 'blur' } ,
] ,
region : [ { required : true , message : '请选择省市区' , trigger : 'change' } ] ,
password : [ { validator : validatePassword , trigger : 'blur' } ] ,
confirmPassword : [ { validator : validateConfirmPassword , trigger : 'blur' } ] ,
captcha : [
{ required : true , message : '请输入验证码' , trigger : 'blur' } ,
{ min : 4 , max : 4 , message : '验证码长度为4位' , trigger : 'blur' }
{ min : 4 , max : 4 , message : '验证码长度为4位' , trigger : 'blur' } ,
] ,
agreement : [ { validator : validateAgreement , trigger : 'change' } ]
}
agreement : [ { validator : validateAgreement , trigger : 'change' } ] ,
} ;
// 发送短信验证码
const sendSMSCode = async ( ) => {
if ( ! canSendSMS . value || sendingSMS . value || smsCountdown . value > 0 ) {
return
return ;
}
try {
sendingSMS . value = true
sendingSMS . value = true ;
const response = await fetch ( '/api/sms/send' , {
method : 'POST' ,
headers : {
'Content-Type' : 'application/json'
'Content-Type' : 'application/json' ,
} ,
body : JSON . stringify ( {
phone : registerForm . phone
} )
} )
phone : registerForm . phone ,
} ) ,
} ) ;
const result = await response . json ( )
const result = await response . json ( ) ;
if ( result . success ) {
ElMessage . success ( '验证码发送成功,请查收短信' )
ElMessage . success ( '验证码发送成功,请查收短信' ) ;
// 开始倒计时
startCountdown ( )
startCountdown ( ) ;
} else {
ElMessage . error ( result . message || '发送失败,请重试' )
ElMessage . error ( result . message || '发送失败,请重试' ) ;
}
} catch ( error ) {
console . error ( '发送短信验证码失败:' , error )
ElMessage . error ( '发送失败,请检查网络连接' )
console . error ( '发送短信验证码失败:' , error ) ;
ElMessage . error ( '发送失败,请检查网络连接' ) ;
} finally {
sendingSMS . value = false
}
sendingSMS . value = false ;
}
} ;
// 开始倒计时
const startCountdown = ( ) => {
smsCountdown . value = 60
smsCountdown . value = 60 ;
const timer = setInterval ( ( ) => {
smsCountdown . value --
smsCountdown . value -- ;
if ( smsCountdown . value <= 0 ) {
clearInterval ( timer )
}
} , 1000 )
clearInterval ( timer ) ;
}
} , 1000 ) ;
} ;
/**
* 获取地区数据
*/
const fetchRegions = async ( ) => {
try {
const response = await fetch ( '/api/regions/zhejiang' )
const result = await response . json ( )
const response = await fetch ( '/api/regions/zhejiang' ) ;
const result = await response . json ( ) ;
if ( result . success ) {
regions . value = result . data
regions . value = result . data ;
} else {
ElMessage . error ( '获取地区数据失败' )
ElMessage . error ( '获取地区数据失败' ) ;
}
} catch ( error ) {
console . error ( '获取地区数据失败:' , error )
ElMessage . error ( '获取地区数据失败,请刷新页面重试' )
console . error ( '获取地区数据失败:' , error ) ;
ElMessage . error ( '获取地区数据失败,请刷新页面重试' ) ;
}
} ;
// 加载省市区级联数据
const loadRegionOptions = async ( ) => {
const provincesResponse = await api . get ( '/regions/provinces' ) ;
regionOptions . value = provincesResponse . data . data || [ ] ;
} ;
// 处理级联选择器变化
const handleRegionChange = ( value ) => {
if ( value && value . length === 3 ) {
registerForm . province = value [ 0 ] ;
registerForm . city = value [ 1 ] ;
registerForm . district = value [ 2 ] ;
registerForm . district _id = value [ 2 ] ;
}
} ;
/**
* 城市变化处理
*/
const onCityChange = ( ) => {
// 清空区域选择
registerForm . district _id = ''
}
registerForm . district _id = '' ;
} ;
// 处理注册
const handleRegister = async ( ) => {
if ( ! registerFormRef . value || ! captchaRef . value ) return
if ( ! registerFormRef . value || ! captchaRef . value ) return ;
try {
// 先验证表单
const valid = await registerFormRef . value . validate ( )
if ( ! valid ) return
const valid = await registerFormRef . value . validate ( ) ;
if ( ! valid ) return ;
// 验证验证码
const captchaValid = await captchaRef . value . verifyCaptcha ( registerForm . captcha )
const captchaValid = await captchaRef . value . verifyCaptcha (
registerForm . captcha
) ;
if ( ! captchaValid ) {
registerForm . captcha = ''
return
registerForm . captcha = '' ;
return ;
}
// 获取验证码信息
const captchaInfo = captchaRef . value . getCaptchaInfo ( )
const captchaInfo = captchaRef . value . getCaptchaInfo ( ) ;
// 提交注册请求(包含验证码信息)
const registerData = {
@@ -455,33 +447,35 @@ const handleRegister = async () => {
password : registerForm . password ,
smsCode : registerForm . smsCode ,
captchaId : captchaInfo . captchaId ,
captchaText : captchaInfo . captchaText
}
const result = await userStore . register( registerData )
captchaText : captchaInfo . captchaText ,
province : registerForm . province ,
} ;
console . log ( registerData , ' registerData' ) ;
console . log ( registerForm , 'registerForm' )
const result = await userStore . register ( registerData ) ;
if ( result . success ) {
// 检查是否需要支付
if ( result . needPayment ) {
ElMessage . success ( '用户信息创建成功,请完成支付以激活账户' )
ElMessage . success ( '用户信息创建成功,请完成支付以激活账户' ) ;
// 跳转到支付页面
router . push ( {
path : '/payment' ,
} )
} ) ;
} else {
ElMessage . success ( '注册成功!请登录' )
router . push ( '/login' )
ElMessage . success ( '注册成功!请登录' ) ;
router . push ( '/login' ) ;
}
}
} catch ( error ) {
console . error ( '注册失败:' , error )
console . error ( '注册失败:' , error ) ;
// 注册失败后刷新验证码
if ( captchaRef . value ) {
await captchaRef . value . refreshCaptcha ( )
}
registerForm . captcha = ''
await captchaRef . value . refreshCaptcha ( ) ;
}
registerForm . captcha = '' ;
}
} ;
// 显示用户协议
const showAgreement = ( ) => {
@@ -498,13 +492,13 @@ const showAgreement = () => {
{
confirmButtonText : '我已了解' ,
dangerouslyUseHTMLString : true ,
customClass : 'agreement-dialog'
customClass : 'agreement-dialog' ,
}
) . then ( ( ) => {
agreementViewed . value = true
ElMessage . success ( '已查看用户协议' )
} )
}
agreementViewed . value = true ;
ElMessage . success ( '已查看用户协议' ) ;
} ) ;
} ;
// 显示隐私政策
const showPrivacy = ( ) => {
@@ -521,47 +515,48 @@ const showPrivacy = () => {
{
confirmButtonText : '我已了解' ,
dangerouslyUseHTMLString : true ,
customClass : 'privacy-dialog'
customClass : 'privacy-dialog' ,
}
) . then ( ( ) => {
privacyViewed . value = true
ElMessage . success ( '已查看隐私政策' )
} )
}
privacyViewed . value = true ;
ElMessage . success ( '已查看隐私政策' ) ;
} ) ;
} ;
// 处理协议勾选
const handleAgreementChange = ( value ) => {
if ( value && ! canCheckAgreement . value ) {
registerForm . agreement = false
ElMessage . warning ( '请先查看用户协议和隐私政策后再勾选' )
return false
}
return true
registerForm . agreement = false ;
ElMessage . warning ( '请先查看用户协议和隐私政策后再勾选' ) ;
return false ;
}
return true ;
} ;
// 图片上传成功处理
const handleUploadSuccess = ( response , field ) => {
ElMessage . success ( '图片上传成功' )
}
ElMessage . success ( '图片上传成功' ) ;
} ;
// 图片上传失败处理
const handleUploadError = ( error ) => {
ElMessage . error ( '图片上传失败,请重试' )
}
ElMessage . error ( '图片上传失败,请重试' ) ;
} ;
// 组件挂载时的处理
onMounted ( ( ) => {
// 如果已经登录,直接跳转
if ( userStore . isAuthenticated ) {
const redirectPath = route . query . redirect || '/'
router . push ( redirectPath )
const redirectPath = route . query . redirect || '/' ;
router . push ( redirectPath ) ;
}
// 获取地区数据
fetchRegions ( )
} )
fetchRegions ( ) ;
// 加载省市区级联数据
loadRegionOptions ( ) ;
} ) ;
< / script >
< style lang = "scss" scoped >
@@ -646,16 +641,9 @@ onMounted(() => {
color : # 606266 ;
}
. features - preview {
margin - top : 20 px ;
}
. features - list {
display : flex ;
flex - direction : column ;
gap : 12 px ;
margin - top : 15 px ;
}
. feature - item {
display : flex ;
@@ -738,7 +726,8 @@ onMounted(() => {
}
@ keyframes float {
0 % , 100 % {
0 % ,
100 % {
transform : translateY ( 0 px ) rotate ( 0 deg ) ;
opacity : 0.7 ;
}