Files
medical-mall/pages/user/register.uvue
2026-01-22 21:15:02 +08:00

559 lines
11 KiB
Plaintext
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-wrapper">
<!-- Header Logo -->
<view class="header">
<image :src="logoUrl" mode="aspectFit" class="logo" />
</view>
<!-- 注册表单区域 -->
<view class="register-box">
<view class="title">注册账号</view>
<!-- 注册表单 -->
<view class="form-content">
<!-- 手机号 -->
<view class="input-group">
<view class="input-wrapper">
<image src="/static/user/phone_1.png" class="input-icon" />
<input
type="text"
placeholder="输入手机号码"
:value="phone"
@input="(e: any) => phone = e.detail.value"
maxlength="11"
class="input-field"
/>
</view>
</view>
<!-- 验证码 -->
<view class="input-group">
<view class="input-wrapper">
<image src="/static/user/code_2.png" class="input-icon" />
<input
type="text"
placeholder="填写验证码"
:value="captcha"
@input="(e: any) => captcha = e.detail.value"
maxlength="6"
class="input-field code-input"
/>
<button
class="code-btn"
:disabled="codeDisabled"
:class="{ 'disabled': codeDisabled }"
@click="getCode"
>
{{ codeText }}
</button>
</view>
</view>
<!-- 密码 -->
<view class="input-group">
<view class="input-wrapper">
<image src="/static/user/code_1.png" class="input-icon" />
<input
type="password"
placeholder="填写密码"
:value="password"
@input="(e: any) => password = e.detail.value"
class="input-field"
/>
</view>
</view>
<!-- 确认密码 -->
<view class="input-group">
<view class="input-wrapper">
<image src="/static/user/code_1.png" class="input-icon" />
<input
type="password"
placeholder="确认密码"
:value="confirmPassword"
@input="(e: any) => confirmPassword = e.detail.value"
class="input-field"
/>
</view>
</view>
</view>
<!-- 注册按钮 -->
<view class="register-btn" @click="handleRegister" :class="{ 'disabled': isLoading }">
注册
</view>
<!-- 已有账号 -->
<view class="tips">
<text class="tips-text">已有账号?</text>
<text class="tips-link" @click="navigateToLogin">立即登录</text>
</view>
<!-- 协议勾选 -->
<view class="protocol">
<checkbox-group @change="handleProtocolChange">
<checkbox
:checked="protocol"
:class="{ 'trembling': inAnimation }"
@animationend="inAnimation = false"
/>
<text class="protocol-text">
已阅读并同意
<text class="main-color" @click="navigateToTerms(3)">《用户协议》</text>
<text class="main-color" @click="navigateToTerms(4)">《隐私协议》</text>
</text>
</checkbox-group>
</view>
</view>
<!-- 底部版权信息 -->
<view class="footer">
<text class="footer-text">Copyright ©2024 Mall. All Rights Reserved</text>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, onMounted } from 'vue'
import supa from '@/components/supadb/aksupainstance.uts'
// 响应式数据
const phone = ref<string>('')
const captcha = ref<string>('')
const password = ref<string>('')
const confirmPassword = ref<string>('')
const protocol = ref<boolean>(false)
const inAnimation = ref<boolean>(false)
const isLoading = ref<boolean>(false)
const logoUrl = ref<string>('/static/logo.png')
// 验证码相关
const codeDisabled = ref<boolean>(false)
const codeText = ref<string>('获取验证码')
const codeCountdown = ref<number>(0)
let codeTimer: number | null = null
// 处理协议勾选变化
const handleProtocolChange = (e: any) => {
protocol.value = !protocol.value
}
// 验证手机号
const validatePhone = (): boolean => {
if (phone.value.trim() === '') {
uni.showToast({
title: '请填写手机号码',
icon: 'none'
})
return false
}
if (!/^1[3-9]\d{9}$/.test(phone.value)) {
uni.showToast({
title: '请输入正确的手机号码',
icon: 'none'
})
return false
}
return true
}
// 验证验证码
const validateCaptcha = (): boolean => {
if (captcha.value.trim() === '') {
uni.showToast({
title: '请填写验证码',
icon: 'none'
})
return false
}
if (!/^\d{6}$/.test(captcha.value)) {
uni.showToast({
title: '请输入正确的验证码',
icon: 'none'
})
return false
}
return true
}
// 验证密码
const validatePassword = (): boolean => {
if (password.value.trim() === '') {
uni.showToast({
title: '请填写密码',
icon: 'none'
})
return false
}
if (password.value.length < 6) {
uni.showToast({
title: '密码长度不能少于6位',
icon: 'none'
})
return false
}
// 密码不能过于简单
if (/^([0-9]|[a-z]|[A-Z]){0,6}$/i.test(password.value)) {
uni.showToast({
title: '您输入的密码过于简单',
icon: 'none'
})
return false
}
return true
}
// 验证确认密码
const validateConfirmPassword = (): boolean => {
if (confirmPassword.value.trim() === '') {
uni.showToast({
title: '请确认密码',
icon: 'none'
})
return false
}
if (confirmPassword.value !== password.value) {
uni.showToast({
title: '两次输入的密码不一致',
icon: 'none'
})
return false
}
return true
}
// 获取验证码
const getCode = async () => {
if (!protocol.value) {
inAnimation.value = true
uni.showToast({
title: '请先阅读并同意协议',
icon: 'none'
})
return
}
if (!validatePhone()) {
return
}
// TODO: 调用获取验证码接口
uni.showToast({
title: '验证码已发送',
icon: 'success'
})
// 开始倒计时
codeDisabled.value = true
codeCountdown.value = 60
codeText.value = `${codeCountdown.value}秒后重试`
codeTimer = setInterval(() => {
codeCountdown.value--
if (codeCountdown.value > 0) {
codeText.value = `${codeCountdown.value}秒后重试`
} else {
codeDisabled.value = false
codeText.value = '获取验证码'
if (codeTimer != null) {
clearInterval(codeTimer)
codeTimer = null
}
}
}, 1000) as unknown as number
}
// 处理注册
const handleRegister = async () => {
// 检查协议
if (!protocol.value) {
inAnimation.value = true
uni.showToast({
title: '请先阅读并同意协议',
icon: 'none'
})
return
}
// 表单验证
if (!validatePhone()) {
return
}
if (!validateCaptcha()) {
return
}
if (!validatePassword()) {
return
}
if (!validateConfirmPassword()) {
return
}
isLoading.value = true
try {
// TODO: 调用注册接口(手机号+验证码+密码)
// 目前先使用邮箱注册作为临时方案
// 注意CRMEB 使用手机号注册,但 Supabase Auth 默认支持邮箱注册
// 需要根据实际后端 API 调整
// 临时方案:使用手机号作为邮箱格式注册
const email = `${phone.value}@phone.mall`
const result = await supa.signUp(email, password.value)
if (result != null && result.user != null) {
// 创建用户资料
const user = result.user as UTSJSONObject
const authId = user.getString('id')
if (authId != null) {
const userData = {
auth_id: authId,
phone: phone.value,
email: email,
username: phone.value,
user_type: 1, // 默认消费者
status: 1
} as UTSJSONObject
try {
await supa.from('ak_users').insert(userData).execute()
} catch (profileErr) {
console.error('创建用户资料失败:', profileErr)
}
}
uni.showToast({
title: '注册成功',
icon: 'success'
})
// 跳转到登录页
setTimeout(() => {
uni.redirectTo({
url: '/pages/user/login'
})
}, 1500)
} else {
throw new Error('注册失败')
}
} catch (err) {
console.error('注册错误:', err)
let errorMessage = '注册失败,请重试'
if (err != null && typeof err === 'object') {
const error = err as Error
if (error.message != null && error.message.trim() !== '') {
errorMessage = error.message
}
}
uni.showToast({
title: errorMessage,
icon: 'none'
})
} finally {
isLoading.value = false
}
}
// 跳转到登录页
const navigateToLogin = () => {
uni.navigateTo({
url: '/pages/user/login'
})
}
// 跳转到协议页面
const navigateToTerms = (type: number) => {
uni.navigateTo({
url: `/pages/user/terms?type=${type}`
})
}
</script>
<style>
page {
background: #F5F5F5;
}
.register-wrapper {
min-height: 100vh;
display: flex;
flex-direction: column;
background: #F5F5F5;
}
/* Header Logo */
.header {
padding: 40rpx 0 0 60rpx;
background: #F5F5F5;
}
.logo {
width: 200rpx;
height: 80rpx;
}
/* 注册表单区域 */
.register-box {
flex: 1;
background: #FFFFFF;
margin: 60rpx 40rpx 0;
border-radius: 8rpx;
padding: 60rpx 50rpx 40rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
}
.title {
font-size: 40rpx;
font-weight: 600;
color: #333333;
text-align: center;
margin-bottom: 50rpx;
}
/* 表单内容 */
.form-content {
margin-bottom: 40rpx;
}
.input-group {
margin-bottom: 30rpx;
}
.input-wrapper {
position: relative;
display: flex;
align-items: center;
padding: 0 20rpx;
height: 88rpx;
border: 1rpx solid #E0E0E0;
border-radius: 4rpx;
background: #FFFFFF;
}
.input-wrapper:focus-within {
border-color: var(--view-theme, #FF4D4F);
}
.input-icon {
width: 32rpx;
height: 32rpx;
flex-shrink: 0;
margin-right: 20rpx;
}
.input-field {
flex: 1;
font-size: 28rpx;
height: 100%;
color: #333333;
}
.code-input {
flex: 1;
}
.code-btn {
position: absolute;
right: 20rpx;
top: 50%;
transform: translateY(-50%);
color: var(--view-theme, #FF4D4F);
font-size: 26rpx;
background: transparent;
border: none;
padding: 0;
line-height: 1;
}
.code-btn.disabled {
color: #999999;
}
/* 注册按钮 */
.register-btn {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 88rpx;
margin-top: 50rpx;
background: linear-gradient(135deg, #FF4D4F 0%, #FF7A45 100%);
border-radius: 4rpx;
color: #FFFFFF;
font-size: 32rpx;
font-weight: 500;
box-shadow: 0 4rpx 12rpx rgba(255, 77, 79, 0.3);
}
.register-btn.disabled {
background: #D9D9D9;
box-shadow: none;
opacity: 0.6;
}
/* 已有账号提示 */
.tips {
margin-top: 30rpx;
text-align: center;
}
.tips-text {
font-size: 28rpx;
color: #666666;
}
.tips-link {
font-size: 28rpx;
color: var(--view-theme, #FF4D4F);
margin-left: 8rpx;
}
/* 协议区域 */
.protocol {
margin-top: 40rpx;
display: flex;
align-items: center;
justify-content: center;
}
.protocol checkbox {
margin-right: 10rpx;
}
.protocol-text {
font-size: 24rpx;
color: #999999;
}
.main-color {
color: var(--view-theme, #FF4D4F);
}
.trembling {
animation: shake 0.6s;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-10rpx); }
20%, 40%, 60%, 80% { transform: translateX(10rpx); }
}
/* 底部版权 */
.footer {
padding: 40rpx 0;
text-align: center;
background: #F5F5F5;
}
.footer-text {
font-size: 22rpx;
color: #999999;
}
</style>