818 lines
17 KiB
Vue
818 lines
17 KiB
Vue
<template>
|
||
<view class="register-page">
|
||
<!-- 背景装饰 -->
|
||
<view class="bg-decoration">
|
||
<view class="circle circle-1"></view>
|
||
<view class="circle circle-2"></view>
|
||
<view class="circle circle-3"></view>
|
||
</view>
|
||
|
||
<!-- 顶部Logo区域 -->
|
||
<view class="register-header">
|
||
<view class="logo-container">
|
||
<image class="logo" src="/static/logo.png" mode="aspectFit"></image>
|
||
</view>
|
||
<text class="title">用户注册</text>
|
||
<text class="subtitle">CREATE YOUR ACCOUNT</text>
|
||
</view>
|
||
|
||
<!-- 注册表单 -->
|
||
<view class="register-form">
|
||
<view class="form-title">创建账号</view>
|
||
|
||
<view class="form-item">
|
||
<view class="input-wrapper">
|
||
<view class="input-icon">👤</view>
|
||
<input
|
||
class="form-input"
|
||
v-model="form.account"
|
||
placeholder="请输入账号(4-20位字母或数字)"
|
||
placeholder-class="placeholder"
|
||
/>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<view class="input-wrapper">
|
||
<view class="input-icon">📱</view>
|
||
<input
|
||
class="form-input"
|
||
v-model="form.phone"
|
||
type="number"
|
||
maxlength="11"
|
||
placeholder="请输入手机号"
|
||
placeholder-class="placeholder"
|
||
/>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<view class="code-input-wrapper">
|
||
<view class="input-wrapper code-input">
|
||
<view class="input-icon">🔢</view>
|
||
<input
|
||
class="form-input"
|
||
v-model="form.code"
|
||
type="number"
|
||
maxlength="6"
|
||
placeholder="请输入验证码"
|
||
placeholder-class="placeholder"
|
||
/>
|
||
</view>
|
||
<view
|
||
class="get-code-btn"
|
||
:class="{ disabled: countdown > 0 }"
|
||
@click="handleGetCode"
|
||
>
|
||
{{ countdown > 0 ? `${countdown}s` : '获取验证码' }}
|
||
</view>
|
||
</view>
|
||
<view class="test-tip">💡 测试提示:可使用万能验证码 888888</view>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<view class="input-wrapper">
|
||
<view class="input-icon">✏️</view>
|
||
<input
|
||
class="form-input"
|
||
v-model="form.realName"
|
||
placeholder="请输入真实姓名"
|
||
placeholder-class="placeholder"
|
||
/>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<view class="gender-label">性别</view>
|
||
<view class="gender-selector">
|
||
<view
|
||
class="gender-option"
|
||
:class="{ active: form.sex === 1 }"
|
||
@click="form.sex = 1"
|
||
>
|
||
<text class="gender-icon">👨</text>
|
||
<text>男</text>
|
||
</view>
|
||
<view
|
||
class="gender-option"
|
||
:class="{ active: form.sex === 2 }"
|
||
@click="form.sex = 2"
|
||
>
|
||
<text class="gender-icon">👩</text>
|
||
<text>女</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<view class="input-wrapper">
|
||
<view class="input-icon">🔒</view>
|
||
<input
|
||
class="form-input"
|
||
v-model="form.password"
|
||
:password="!showPassword"
|
||
placeholder="请输入密码(6-20位)"
|
||
placeholder-class="placeholder"
|
||
/>
|
||
<view class="eye-icon" @click="showPassword = !showPassword">
|
||
<text>{{ showPassword ? '👁️' : '👁️🗨️' }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<view class="input-wrapper">
|
||
<view class="input-icon">🔐</view>
|
||
<input
|
||
class="form-input"
|
||
v-model="form.confirmPassword"
|
||
:password="!showConfirmPassword"
|
||
placeholder="请再次输入密码"
|
||
placeholder-class="placeholder"
|
||
/>
|
||
<view class="eye-icon" @click="showConfirmPassword = !showConfirmPassword">
|
||
<text>{{ showConfirmPassword ? '👁️' : '👁️🗨️' }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="agreement-wrapper">
|
||
<label class="checkbox-label" @click="agreeTerms = !agreeTerms">
|
||
<view :class="['checkbox', agreeTerms ? 'checked' : '']">
|
||
<text v-if="agreeTerms" class="check-icon">✓</text>
|
||
</view>
|
||
<text class="agreement-text">
|
||
我已阅读并同意
|
||
<text class="link-text" @click.stop="showAgreement">《用户协议》</text>
|
||
和
|
||
<text class="link-text" @click.stop="showPrivacy">《隐私政策》</text>
|
||
</text>
|
||
</label>
|
||
</view>
|
||
|
||
<button class="register-btn" @click="handleRegister" :loading="loading" :disabled="loading">
|
||
<text class="btn-text">{{ loading ? '注册中...' : '立即注册' }}</text>
|
||
</button>
|
||
|
||
<view class="login-link">
|
||
已有账号?<text class="link-text" @click="goToLogin">立即登录</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部装饰 -->
|
||
<view class="footer-decoration">
|
||
<view class="wave"></view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import userAPI from '@/api/user.js'
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
form: {
|
||
account: '',
|
||
phone: '',
|
||
code: '',
|
||
realName: '',
|
||
sex: 1,
|
||
password: '',
|
||
confirmPassword: ''
|
||
},
|
||
showPassword: false,
|
||
showConfirmPassword: false,
|
||
agreeTerms: false,
|
||
loading: false,
|
||
countdown: 0,
|
||
timer: null
|
||
}
|
||
},
|
||
onUnload() {
|
||
if (this.timer) {
|
||
clearInterval(this.timer)
|
||
}
|
||
},
|
||
methods: {
|
||
/**
|
||
* 获取验证码
|
||
*/
|
||
async handleGetCode() {
|
||
if (this.countdown > 0) return
|
||
|
||
// 验证手机号
|
||
if (!this.form.phone) {
|
||
uni.showToast({
|
||
title: '请输入手机号',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
if (!/^1[3-9]\d{9}$/.test(this.form.phone)) {
|
||
uni.showToast({
|
||
title: '请输入正确的手机号',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
try {
|
||
await userAPI.getCaptcha(this.form.phone)
|
||
|
||
uni.showToast({
|
||
title: '验证码已发送',
|
||
icon: 'success'
|
||
})
|
||
|
||
// 开始倒计时
|
||
this.countdown = 60
|
||
this.timer = setInterval(() => {
|
||
this.countdown--
|
||
if (this.countdown <= 0) {
|
||
clearInterval(this.timer)
|
||
this.timer = null
|
||
}
|
||
}, 1000)
|
||
} catch (error) {
|
||
console.error('获取验证码失败:', error)
|
||
uni.showToast({
|
||
title: error.message || '获取验证码失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 表单验证
|
||
*/
|
||
validateForm() {
|
||
if (!this.form.account) {
|
||
uni.showToast({
|
||
title: '请输入账号',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
if (!/^[a-zA-Z0-9]{4,20}$/.test(this.form.account)) {
|
||
uni.showToast({
|
||
title: '账号为4-20位字母或数字',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
if (!this.form.phone) {
|
||
uni.showToast({
|
||
title: '请输入手机号',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
if (!/^1[3-9]\d{9}$/.test(this.form.phone)) {
|
||
uni.showToast({
|
||
title: '请输入正确的手机号',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
if (!this.form.code) {
|
||
uni.showToast({
|
||
title: '请输入验证码',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
// 测试用万能验证码:888888
|
||
// 如果不是万能验证码,则需要验证码长度为6位
|
||
if (this.form.code !== '888888' && this.form.code.length !== 6) {
|
||
uni.showToast({
|
||
title: '请输入6位验证码',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
if (!this.form.realName) {
|
||
uni.showToast({
|
||
title: '请输入真实姓名',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
if (!/^[\u4e00-\u9fa5]{2,10}$/.test(this.form.realName)) {
|
||
uni.showToast({
|
||
title: '请输入正确的姓名(2-10个汉字)',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
if (!this.form.password) {
|
||
uni.showToast({
|
||
title: '请输入密码',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
if (this.form.password.length < 6 || this.form.password.length > 20) {
|
||
uni.showToast({
|
||
title: '密码长度为6-20位',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
if (!this.form.confirmPassword) {
|
||
uni.showToast({
|
||
title: '请确认密码',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
if (this.form.password !== this.form.confirmPassword) {
|
||
uni.showToast({
|
||
title: '两次密码输入不一致',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
if (!this.agreeTerms) {
|
||
uni.showToast({
|
||
title: '请阅读并同意用户协议',
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
}
|
||
|
||
return true
|
||
},
|
||
|
||
/**
|
||
* 注册
|
||
*/
|
||
async handleRegister() {
|
||
if (!this.validateForm()) return
|
||
|
||
this.loading = true
|
||
|
||
try {
|
||
await userAPI.register({
|
||
account: this.form.account,
|
||
phone: this.form.phone,
|
||
code: this.form.code,
|
||
realName: this.form.realName,
|
||
sex: this.form.sex,
|
||
password: this.form.password
|
||
})
|
||
|
||
uni.showToast({
|
||
title: '注册成功',
|
||
icon: 'success'
|
||
})
|
||
|
||
// 延迟跳转到登录页
|
||
setTimeout(() => {
|
||
uni.navigateBack()
|
||
}, 1500)
|
||
} catch (error) {
|
||
console.error('注册失败:', error)
|
||
uni.showToast({
|
||
title: error.message || '注册失败',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
this.loading = false
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 跳转到登录页
|
||
*/
|
||
goToLogin() {
|
||
uni.navigateBack()
|
||
},
|
||
|
||
/**
|
||
* 显示用户协议
|
||
*/
|
||
showAgreement() {
|
||
uni.showToast({
|
||
title: '用户协议',
|
||
icon: 'none'
|
||
})
|
||
// TODO: 跳转到用户协议页面
|
||
},
|
||
|
||
/**
|
||
* 显示隐私政策
|
||
*/
|
||
showPrivacy() {
|
||
uni.showToast({
|
||
title: '隐私政策',
|
||
icon: 'none'
|
||
})
|
||
// TODO: 跳转到隐私政策页面
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.register-page {
|
||
min-height: 100vh;
|
||
background: linear-gradient(135deg, #C93639 0%, #A82E31 50%, #8B1F22 100%);
|
||
padding: 0;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* 背景装饰圆圈 */
|
||
.bg-decoration {
|
||
position: absolute;
|
||
width: 100%;
|
||
height: 100%;
|
||
top: 0;
|
||
left: 0;
|
||
overflow: hidden;
|
||
z-index: 0;
|
||
}
|
||
|
||
.circle {
|
||
position: absolute;
|
||
border-radius: 50%;
|
||
background: rgba(255, 255, 255, 0.08);
|
||
animation: float 6s ease-in-out infinite;
|
||
}
|
||
|
||
.circle-1 {
|
||
width: 300rpx;
|
||
height: 300rpx;
|
||
top: -100rpx;
|
||
right: -50rpx;
|
||
animation-delay: 0s;
|
||
}
|
||
|
||
.circle-2 {
|
||
width: 200rpx;
|
||
height: 200rpx;
|
||
bottom: 100rpx;
|
||
left: -50rpx;
|
||
animation-delay: 2s;
|
||
}
|
||
|
||
.circle-3 {
|
||
width: 150rpx;
|
||
height: 150rpx;
|
||
top: 30%;
|
||
right: 50rpx;
|
||
animation-delay: 4s;
|
||
}
|
||
|
||
@keyframes float {
|
||
0%, 100% {
|
||
transform: translateY(0) scale(1);
|
||
}
|
||
50% {
|
||
transform: translateY(-30rpx) scale(1.05);
|
||
}
|
||
}
|
||
|
||
/* 顶部Logo区域 */
|
||
.register-header {
|
||
text-align: center;
|
||
padding: 80rpx 60rpx 60rpx;
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
.logo-container {
|
||
width: 160rpx;
|
||
height: 160rpx;
|
||
margin: 0 auto 30rpx;
|
||
background: rgba(255, 255, 255, 0.15);
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
backdrop-filter: blur(10rpx);
|
||
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.logo {
|
||
width: 120rpx;
|
||
height: 120rpx;
|
||
}
|
||
|
||
.title {
|
||
font-size: 44rpx;
|
||
font-weight: bold;
|
||
color: #fff;
|
||
margin-bottom: 12rpx;
|
||
letter-spacing: 4rpx;
|
||
text-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
||
}
|
||
|
||
.subtitle {
|
||
font-size: 22rpx;
|
||
color: rgba(255, 255, 255, 0.85);
|
||
letter-spacing: 2rpx;
|
||
font-weight: 300;
|
||
}
|
||
|
||
/* 注册表单 */
|
||
.register-form {
|
||
background: #fff;
|
||
border-radius: 40rpx 40rpx 0 0;
|
||
padding: 50rpx 40rpx 80rpx;
|
||
position: relative;
|
||
z-index: 1;
|
||
box-shadow: 0 -8rpx 32rpx rgba(0, 0, 0, 0.08);
|
||
max-height: calc(100vh - 300rpx);
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.form-title {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 40rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
.form-item {
|
||
margin-bottom: 28rpx;
|
||
}
|
||
|
||
.input-wrapper {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
background: #F7F8FA;
|
||
border-radius: 16rpx;
|
||
border: 2rpx solid transparent;
|
||
transition: all 0.3s ease;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.input-wrapper:focus-within {
|
||
background: #fff;
|
||
border-color: #C93639;
|
||
box-shadow: 0 4rpx 16rpx rgba(201, 54, 57, 0.1);
|
||
}
|
||
|
||
.input-icon {
|
||
width: 70rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 32rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.form-input {
|
||
flex: 1;
|
||
height: 88rpx;
|
||
font-size: 26rpx;
|
||
color: #333;
|
||
background: transparent;
|
||
border: none;
|
||
padding-right: 30rpx;
|
||
}
|
||
|
||
.placeholder {
|
||
color: #999;
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
.eye-icon {
|
||
width: 70rpx;
|
||
height: 88rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 32rpx;
|
||
flex-shrink: 0;
|
||
cursor: pointer;
|
||
transition: opacity 0.3s;
|
||
}
|
||
|
||
.eye-icon:active {
|
||
opacity: 0.6;
|
||
}
|
||
|
||
/* 验证码输入 */
|
||
.code-input-wrapper {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.code-input {
|
||
flex: 1;
|
||
}
|
||
|
||
.get-code-btn {
|
||
height: 88rpx;
|
||
padding: 0 24rpx;
|
||
background: linear-gradient(135deg, #C93639 0%, #A82E31 100%);
|
||
color: #fff;
|
||
border-radius: 16rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 24rpx;
|
||
white-space: nowrap;
|
||
transition: all 0.3s;
|
||
box-shadow: 0 4rpx 12rpx rgba(201, 54, 57, 0.2);
|
||
font-weight: bold;
|
||
}
|
||
|
||
.get-code-btn:active {
|
||
transform: scale(0.95);
|
||
}
|
||
|
||
.get-code-btn.disabled {
|
||
opacity: 0.6;
|
||
background: #999;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.test-tip {
|
||
margin-top: 10rpx;
|
||
font-size: 22rpx;
|
||
color: #ff9800;
|
||
padding-left: 10rpx;
|
||
}
|
||
|
||
/* 性别选择 */
|
||
.gender-label {
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
margin-bottom: 12rpx;
|
||
padding-left: 10rpx;
|
||
}
|
||
|
||
.gender-selector {
|
||
display: flex;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.gender-option {
|
||
flex: 1;
|
||
height: 88rpx;
|
||
background: #F7F8FA;
|
||
border-radius: 16rpx;
|
||
border: 2rpx solid transparent;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 10rpx;
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.gender-option.active {
|
||
background: linear-gradient(135deg, #C93639 0%, #A82E31 100%);
|
||
color: #fff;
|
||
border-color: #C93639;
|
||
box-shadow: 0 4rpx 16rpx rgba(201, 54, 57, 0.2);
|
||
}
|
||
|
||
.gender-icon {
|
||
font-size: 32rpx;
|
||
}
|
||
|
||
/* 用户协议 */
|
||
.agreement-wrapper {
|
||
margin: 30rpx 0 40rpx;
|
||
}
|
||
|
||
.checkbox-label {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.checkbox {
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
border: 2rpx solid #ddd;
|
||
border-radius: 8rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-right: 10rpx;
|
||
margin-top: 2rpx;
|
||
flex-shrink: 0;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.checkbox.checked {
|
||
background: #C93639;
|
||
border-color: #C93639;
|
||
}
|
||
|
||
.check-icon {
|
||
color: #fff;
|
||
font-size: 20rpx;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.agreement-text {
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
flex: 1;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.link-text {
|
||
color: #C93639;
|
||
font-weight: bold;
|
||
cursor: pointer;
|
||
transition: opacity 0.3s;
|
||
}
|
||
|
||
.link-text:active {
|
||
opacity: 0.7;
|
||
}
|
||
|
||
/* 注册按钮 */
|
||
.register-btn {
|
||
width: 100%;
|
||
height: 92rpx;
|
||
background: linear-gradient(135deg, #C93639 0%, #A82E31 100%);
|
||
border-radius: 16rpx;
|
||
border: none;
|
||
box-shadow: 0 8rpx 24rpx rgba(201, 54, 57, 0.3);
|
||
transition: all 0.3s ease;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.register-btn::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: -100%;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||
transition: left 0.5s;
|
||
}
|
||
|
||
.register-btn:active {
|
||
transform: scale(0.98);
|
||
box-shadow: 0 4rpx 16rpx rgba(201, 54, 57, 0.2);
|
||
}
|
||
|
||
.register-btn:active::before {
|
||
left: 100%;
|
||
}
|
||
|
||
.btn-text {
|
||
font-size: 30rpx;
|
||
font-weight: bold;
|
||
color: #fff;
|
||
letter-spacing: 2rpx;
|
||
}
|
||
|
||
/* 登录链接 */
|
||
.login-link {
|
||
text-align: center;
|
||
margin-top: 40rpx;
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
}
|
||
|
||
/* 底部装饰 */
|
||
.footer-decoration {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100rpx;
|
||
z-index: 0;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.wave {
|
||
position: absolute;
|
||
bottom: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: linear-gradient(180deg, transparent 0%, rgba(255, 255, 255, 0.05) 100%);
|
||
}
|
||
</style>
|