ruoyi-geek-App/pages_system/pages/register/index.vue

510 lines
9.9 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>
<view class="register-container">
<view class="register-header">
<text class="back-btn" @click="goBack"></text>
<text class="title">用户注册</text>
<view class="placeholder"></view>
</view>
<view class="register-content">
<view class="register-form">
<view class="form-item">
<text class="label">用户名</text>
<input v-model="registerForm.username" class="input" placeholder="请输入用户名"
placeholder-class="placeholder" />
</view>
<view class="form-item">
<text class="label">手机号</text>
<input v-model="registerForm.phone" class="input" type="number" placeholder="请输入手机号"
placeholder-class="placeholder" />
</view>
<view class="form-item">
<text class="label">验证码</text>
<view class="code-input">
<input v-model="registerForm.code" class="input" type="number" placeholder="请输入验证码"
placeholder-class="placeholder" />
<button class="send-code-btn" :disabled="countdown > 0" @click="sendCode">
{{ countdown > 0 ? `${countdown}s` : '获取验证码' }}
</button>
</view>
</view>
<view class="form-item">
<text class="label">密码</text>
<input v-model="registerForm.password" class="input" password placeholder="请输入密码"
placeholder-class="placeholder" />
</view>
<view class="form-item">
<text class="label">确认密码</text>
<input v-model="registerForm.confirmPassword" class="input" password placeholder="请再次输入密码"
placeholder-class="placeholder" />
</view>
<view class="form-item" v-if="registerForm.showEmail">
<text class="label">邮箱</text>
<input v-model="registerForm.email" class="input" type="email" placeholder="请输入邮箱(选填)"
placeholder-class="placeholder" />
</view>
<view class="agreement">
<checkbox-group @change="toggleAgreement">
<label class="checkbox-label">
<checkbox :checked="agreed" color="#007aff" />
<text class="agreement-text">我已阅读并同意</text>
</label>
</checkbox-group>
<text class="agreement-link" @click="showAgreement">《用户协议》</text>
<text class="agreement-link" @click="showPrivacy">《隐私政策》</text>
</view>
<button class="register-btn" @click="handleRegister" :disabled="registering">
{{ registering ? '注册中...' : '注册' }}
</button>
<view class="login-link">
<text>已有账号? </text>
<text class="link" @click="goLogin">立即登录</text>
</view>
</view>
<!-- 第三方注册 -->
<view class="third-register">
<view class="divider">
<text class="divider-text">第三方注册</text>
</view>
<view class="register-methods">
<view class="method-item" @click="handleWechatRegister">
<view class="method-icon wechat">
<text class="iconfont">💬</text>
</view>
<text class="method-text">微信注册</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
reactive
} from 'vue'
import {
onLoad,
onShow
} from '@dcloudio/uni-app'
import modal from '@/plugins/modal'
import {
getCodeImg,
login,
sendPhoneCode,
verifyPhoneCode
} from '@/api/login'
import {
getWxCode
} from '@/utils/geek'
import {
wxLogin
} from '@/api/oauth'
import useUserStore from '@/store/modules/user'
import {
setToken
} from '@/utils/auth'
import config from '@/config.js'
const userStore = useUserStore()
// 响应式数据
const registering = ref(false)
const agreed = ref(false)
const countdown = ref(0)
let countdownTimer = null
// 注册表单
const registerForm = reactive({
username: '',
phone: '',
code: '',
password: '',
confirmPassword: '',
email: '',
showEmail: false
})
// 发送验证码
const sendCode = async () => {
if (!registerForm.phone) {
modal.alert('请输入手机号')
return
}
if (!/^1[3-9]\d{9}$/.test(registerForm.phone)) {
modal.alert('请输入正确的手机号')
return
}
try {
await sendPhoneCode({
phone: registerForm.phone
}, 'register')
modal.alert('验证码已发送')
// 开始倒计时
countdown.value = 60
countdownTimer = setInterval(() => {
countdown.value--
if (countdown.value <= 0) {
clearInterval(countdownTimer)
}
}, 1000)
} catch (error) {
modal.alert(error.message || '验证码发送失败')
}
}
// 处理注册
const handleRegister = async () => {
if (!validateForm()) return
registering.value = true
try {
// 验证验证码
await verifyPhoneCode({
phone: registerForm.phone,
code: registerForm.code
}, 'register')
// 执行注册
const params = {
username: registerForm.username,
password: registerForm.password,
phonenumber: registerForm.phone
}
if (registerForm.email) {
params.email = registerForm.email
}
const res = await register(params)
if (res.token) {
setToken(res.token)
await userStore.getInfo()
uni.switchTab({
url: '/pages/login'
});
}
} catch (error) {
modal.alert(error.message || '注册失败')
} finally {
registering.value = false
}
}
// 表单验证
const validateForm = () => {
if (!registerForm.username) {
modal.alert('请输入用户名')
return false
}
if (!registerForm.phone) {
modal.alert('请输入手机号')
return false
}
if (!/^1[3-9]\d{9}$/.test(registerForm.phone)) {
modal.alert('请输入正确的手机号')
return false
}
if (!registerForm.code) {
modal.alert('请输入验证码')
return false
}
if (!registerForm.password) {
modal.alert('请输入密码')
return false
}
if (registerForm.password.length < 6) {
modal.alert('密码长度不能少于6位')
return false
}
if (registerForm.password !== registerForm.confirmPassword) {
modal.alert('两次输入的密码不一致')
return false
}
if (!agreed.value) {
modal.alert('请同意用户协议和隐私政策')
return false
}
return true
}
// 微信注册
const handleWechatRegister = async () => {
try {
const code = await getWxCode()
const res = await wxRegister('miniapp', code)
if (res.token) {
setToken(res.token)
await userStore.getInfo()
modal.alert('注册成功', () => {
uni.reLaunch({
url: '/pages/index'
})
})
}
} catch (error) {
modal.alert(error.message || '微信注册失败')
}
}
// 切换协议同意状态
const toggleAgreement = (e) => {
agreed.value = e.detail.value.length > 0
}
// 显示用户协议
const showAgreement = () => {
uni.navigateTo({
url: '/pages_system/pages/login/agreement?type=user'
})
}
// 显示隐私政策
const showPrivacy = () => {
uni.navigateTo({
url: '/pages_system/pages/login/agreement?type=privacy'
})
}
// 返回登录页面
const goBack = () => {
uni.navigateBack()
}
const goLogin = () => {
uni.redirectTo({
url: '/pages/login'
})
}
// 清理计时器
import {
onUnmounted
} from 'vue'
onUnmounted(() => {
if (countdownTimer) {
clearInterval(countdownTimer)
}
})
</script>
<style scoped>
.register-container {
min-height: 100vh;
background: #f5f5f5;
}
.register-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 40rpx 30rpx;
background: #fff;
border-bottom: 1rpx solid #e0e0e0;
}
.back-btn {
font-size: 50rpx;
color: #333;
width: 60rpx;
height: 60rpx;
line-height: 60rpx;
text-align: center;
}
.title {
font-size: 36rpx;
color: #333;
font-weight: bold;
}
.placeholder {
width: 60rpx;
}
.register-content {
padding: 40rpx;
}
.register-form {
background: #fff;
border-radius: 20rpx;
padding: 40rpx;
margin-bottom: 40rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.form-item {
margin-bottom: 30rpx;
}
.label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 10rpx;
}
.input {
width: 100%;
height: 80rpx;
border: 2rpx solid #e0e0e0;
border-radius: 10rpx;
padding: 0 20rpx;
font-size: 28rpx;
box-sizing: border-box;
}
.code-input {
display: flex;
align-items: center;
gap: 20rpx;
}
.send-code-btn {
width: 200rpx;
height: 80rpx;
background: #007aff;
color: #fff;
border: none;
border-radius: 10rpx;
font-size: 24rpx;
white-space: nowrap;
}
.send-code-btn[disabled] {
background: #ccc;
}
.agreement {
display: flex;
align-items: center;
flex-wrap: wrap;
margin: 40rpx 0;
font-size: 24rpx;
}
.checkbox-label {
display: flex;
align-items: center;
margin-right: 10rpx;
}
.agreement-text {
margin-left: 10rpx;
color: #666;
}
.agreement-link {
color: #007aff;
margin: 0 5rpx;
}
.register-btn {
width: 100%;
height: 88rpx;
background: #007aff;
color: #fff;
border: none;
border-radius: 44rpx;
font-size: 32rpx;
margin-bottom: 30rpx;
}
.register-btn[disabled] {
background: #ccc;
}
.login-link {
text-align: center;
font-size: 26rpx;
color: #666;
}
.link {
color: #007aff;
}
.third-register {
background: #fff;
border-radius: 20rpx;
padding: 40rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.divider {
position: relative;
text-align: center;
margin-bottom: 40rpx;
}
.divider::before {
content: '';
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 1rpx;
background: #e0e0e0;
}
.divider-text {
background: #fff;
padding: 0 20rpx;
color: #999;
font-size: 24rpx;
}
.register-methods {
display: flex;
justify-content: center;
}
.method-item {
display: flex;
flex-direction: column;
align-items: center;
}
.method-icon {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10rpx;
font-size: 40rpx;
background: #07c160;
color: #fff;
}
.method-text {
font-size: 24rpx;
color: #666;
}
</style>