<template>
|
<view class="register-page">
|
<!-- 顶部品牌导航栏:深蓝底 + 品牌 logo(与登录页保持一致) -->
|
<view class="nav-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
<view class="nav-inner">
|
<image class="brand-logo" src="/static/logo.png" mode="heightFix" />
|
</view>
|
</view>
|
|
<!-- 表单区 -->
|
<view class="form-area">
|
<!-- 手机号 -->
|
<view class="field-block">
|
<text class="field-label">手机号</text>
|
<view class="input-wrap">
|
<view class="field-icon icon-phone"></view>
|
<input
|
v-model="form.phone"
|
class="field-input"
|
type="number"
|
maxlength="11"
|
placeholder="请输入账号"
|
placeholder-style="color:#b8bec7;font-size:28rpx"
|
/>
|
</view>
|
</view>
|
|
<!-- 验证码 -->
|
<view class="field-block">
|
<text class="field-label">验证码</text>
|
<view class="code-row">
|
<view class="input-wrap input-wrap--flex">
|
<view class="field-icon icon-shield"></view>
|
<input
|
v-model="form.code"
|
class="field-input"
|
type="number"
|
maxlength="6"
|
placeholder="请输入验证码"
|
placeholder-style="color:#b8bec7;font-size:28rpx"
|
/>
|
</view>
|
<!-- 获取验证码:倒计时期间禁用,显示剩余秒数 -->
|
<view
|
class="code-btn"
|
:class="{ 'code-btn--disabled': counting }"
|
@tap="_handleSendCode"
|
>
|
{{ counting ? countdown + 's' : '获取验证码' }}
|
</view>
|
</view>
|
</view>
|
|
<!-- 推荐码(必填,无推荐码无法注册) -->
|
<view class="field-block">
|
<text class="field-label">推荐码</text>
|
<view class="input-wrap">
|
<view class="field-icon icon-grid"></view>
|
<input
|
v-model="form.referralCode"
|
class="field-input"
|
maxlength="20"
|
placeholder="请输入推荐人码"
|
placeholder-style="color:#b8bec7;font-size:28rpx"
|
/>
|
</view>
|
</view>
|
|
<!-- 已有账号:右对齐,点击返回登录页 -->
|
<view class="has-account" @tap="_handleBackLogin">
|
<text class="has-account-text">已有账号</text>
|
<text class="has-account-link">去登录</text>
|
</view>
|
</view>
|
|
<!-- 底部通栏注册按钮 -->
|
<view class="bottom-bar">
|
<view class="submit-btn" @tap="_handleRegister">注册</view>
|
</view>
|
</view>
|
</template>
|
|
<script setup>
|
import { ref, reactive, computed } from 'vue'
|
import { onUnload } from '@dcloudio/uni-app'
|
import { sendSmsCode, register } from '@/api/index.js'
|
|
// ======================== 数据定义区域 ========================
|
|
// 注册表单数据
|
const form = reactive({
|
phone: '',
|
code: '',
|
referralCode: ''
|
})
|
|
// 验证码倒计时剩余秒数(0 表示未在倒计时)
|
const countdown = ref(0)
|
|
// 是否处于倒计时状态(用于禁用「获取验证码」按钮)
|
const counting = computed(() => countdown.value > 0)
|
|
// 注册请求进行中标记,防止重复提交
|
const registering = ref(false)
|
|
// 倒计时定时器句柄
|
let countdownTimer = null
|
|
// 状态栏高度(px),用于顶部品牌区留白
|
const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 0
|
|
// ======================== 方法定义区域 ========================
|
|
/** 校验手机号格式 */
|
const _isValidPhone = (phone) => /^1[3-9]\d{9}$/.test(phone)
|
|
/** 启动 30 秒倒计时,期间禁用「获取验证码」,30 秒后才可再次发送 */
|
const _startCountdown = () => {
|
countdown.value = 30
|
countdownTimer = setInterval(() => {
|
countdown.value -= 1
|
if (countdown.value <= 0) {
|
clearInterval(countdownTimer)
|
countdownTimer = null
|
}
|
}, 1000)
|
}
|
|
/**
|
* 获取验证码:未输入手机号 / 格式错误时前端直接提示;
|
* 手机号已注册的情况由后端返回「手机号已注册,请前往登录」,此处统一展示错误信息
|
*/
|
const _handleSendCode = async () => {
|
if (counting.value) return
|
|
const phone = form.phone.trim()
|
if (!phone) {
|
uni.showToast({ title: '请输入手机号', icon: 'none' })
|
return
|
}
|
if (!_isValidPhone(phone)) {
|
uni.showToast({ title: '请输入正确手机号', icon: 'none' })
|
return
|
}
|
|
try {
|
await sendSmsCode(phone)
|
uni.showToast({ title: '验证码已发送', icon: 'none' })
|
_startCountdown()
|
} catch (e) {
|
uni.showToast({ title: e.message || '验证码发送失败', icon: 'none' })
|
}
|
}
|
|
/** 表单校验,返回校验失败原因(通过则返回空串) */
|
const _validateForm = () => {
|
if (!form.phone.trim()) return '请输入手机号'
|
if (!_isValidPhone(form.phone.trim())) return '请输入正确手机号'
|
if (!form.code.trim()) return '请输入验证码'
|
// 推荐码为必填,没有推荐码无法注册
|
if (!form.referralCode.trim()) return '请输入推荐人码'
|
return ''
|
}
|
|
/** 提交注册:校验 → 调接口 → 跳转注册成功页 */
|
const _handleRegister = async () => {
|
if (registering.value) return
|
|
const errorMsg = _validateForm()
|
if (errorMsg) {
|
uni.showToast({ title: errorMsg, icon: 'none' })
|
return
|
}
|
|
registering.value = true
|
try {
|
await register({
|
phone: form.phone.trim(),
|
code: form.code.trim(),
|
referralCode: form.referralCode.trim()
|
})
|
// 替换当前页,注册成功页返回时直接回到登录页
|
uni.redirectTo({ url: '/pages/login/register-success' })
|
} catch (e) {
|
uni.showToast({ title: e.message || '注册失败', icon: 'none' })
|
} finally {
|
registering.value = false
|
}
|
}
|
|
/** 返回登录页 */
|
const _handleBackLogin = () => {
|
uni.navigateBack()
|
}
|
|
// 页面卸载时清理倒计时,避免定时器泄漏
|
onUnload(() => {
|
if (countdownTimer) clearInterval(countdownTimer)
|
})
|
</script>
|
|
<style scoped lang="scss">
|
/* 页面最外层:纵向弹性布局,撑满整屏 */
|
.register-page {
|
display: flex;
|
flex-direction: column;
|
height: 100vh;
|
background-color: #ffffff;
|
}
|
|
/* 顶部品牌导航栏:深蓝底,与登录页品牌区一致 */
|
.nav-bar {
|
background-color: #0b3f6f;
|
}
|
|
.nav-inner {
|
height: 88rpx;
|
display: flex;
|
align-items: center;
|
padding-left: 28rpx;
|
box-sizing: border-box;
|
}
|
|
.brand-logo {
|
height: 56rpx;
|
display: block;
|
}
|
|
.form-area {
|
flex: 1;
|
padding: 40rpx 28rpx 0;
|
box-sizing: border-box;
|
}
|
|
/* 单个输入项 */
|
.field-block {
|
margin-bottom: 40rpx;
|
}
|
|
.field-label {
|
display: block;
|
font-size: 28rpx;
|
color: #1f2329;
|
margin-bottom: 20rpx;
|
}
|
|
/* 带图标的输入框 */
|
.input-wrap {
|
display: flex;
|
align-items: center;
|
height: 96rpx;
|
padding: 0 24rpx;
|
border: 1rpx solid #dcdfe6;
|
border-radius: 8rpx;
|
box-sizing: border-box;
|
}
|
|
/* 验证码行:输入框自适应占满剩余宽度 */
|
.code-row {
|
display: flex;
|
align-items: center;
|
}
|
|
.input-wrap--flex {
|
flex: 1;
|
min-width: 0;
|
margin-right: 20rpx;
|
}
|
|
.field-icon {
|
width: 44rpx;
|
height: 44rpx;
|
margin-right: 20rpx;
|
flex-shrink: 0;
|
background-repeat: no-repeat;
|
background-position: center center;
|
background-size: 100% 100%;
|
}
|
|
/* 手机号图标:手机外框 + 底部听筒线 */
|
.icon-phone {
|
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0NCA0NCI+CiAgPHJlY3QgeD0iMTIuNSIgeT0iNS41IiB3aWR0aD0iMTkiIGhlaWdodD0iMzMiIHJ4PSI0LjUiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzBiM2Y2ZiIgc3Ryb2tlLXdpZHRoPSIzIi8+CiAgPGxpbmUgeDE9IjE4LjUiIHkxPSIzMi41IiB4Mj0iMjUuNSIgeTI9IjMyLjUiIHN0cm9rZT0iIzBiM2Y2ZiIgc3Ryb2tlLXdpZHRoPSIzIiBzdHJva2UtbGluZWNhcD0icm91bmQiLz4KPC9zdmc+Cg==');
|
}
|
|
/* 验证码图标:上凸尖角盾牌 + 四宫格棋盘 */
|
.icon-shield {
|
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0NCA0NCI+CiAgPGRlZnM+CiAgICA8Y2xpcFBhdGggaWQ9InNjIj4KICAgICAgPHBhdGggZD0iTTIyIDkgTDMzIDE3LjUgTDMzIDI1IEMzMyAzMS41IDI3LjUgMzYgMjIgMzggQzE2LjUgMzYgMTEgMzEuNSAxMSAyNSBMMTEgMTcuNSBaIi8+CiAgICA8L2NsaXBQYXRoPgogIDwvZGVmcz4KICA8cGF0aCBkPSJNMjIgNCBMMzggMTUgTDM4IDI1IEMzOCAzNCAzMCA0MCAyMiA0MiBDMTQgNDAgNiAzNCA2IDI1IEw2IDE1IFoiIGZpbGw9IiMwYjNmNmYiLz4KICA8ZyBjbGlwLXBhdGg9InVybCgjc2MpIj4KICAgIDxyZWN0IHg9IjExIiB5PSI5IiB3aWR0aD0iMjIiIGhlaWdodD0iMjkiIGZpbGw9IiNmZmZmZmYiLz4KICAgIDxyZWN0IHg9IjIyIiB5PSI5IiB3aWR0aD0iMTEiIGhlaWdodD0iMTQuNSIgZmlsbD0iIzBiM2Y2ZiIvPgogICAgPHJlY3QgeD0iMTEiIHk9IjIzLjUiIHdpZHRoPSIxMSIgaGVpZ2h0PSIxNC41IiBmaWxsPSIjMGIzZjZmIi8+CiAgPC9nPgo8L3N2Zz4K');
|
}
|
|
/* 推荐码图标:2×2 宫格 */
|
.icon-grid {
|
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0NCA0NCI+CiAgPHJlY3QgeD0iNiIgeT0iNiIgd2lkdGg9IjEzLjUiIGhlaWdodD0iMTMuNSIgcng9IjIuNSIgZmlsbD0iIzBiM2Y2ZiIvPgogIDxyZWN0IHg9IjI0LjUiIHk9IjYiIHdpZHRoPSIxMy41IiBoZWlnaHQ9IjEzLjUiIHJ4PSIyLjUiIGZpbGw9IiMwYjNmNmYiLz4KICA8cmVjdCB4PSI2IiB5PSIyNC41IiB3aWR0aD0iMTMuNSIgaGVpZ2h0PSIxMy41IiByeD0iMi41IiBmaWxsPSIjMGIzZjZmIi8+CiAgPHJlY3QgeD0iMjQuNSIgeT0iMjQuNSIgd2lkdGg9IjEzLjUiIGhlaWdodD0iMTMuNSIgcng9IjIuNSIgZmlsbD0iIzBiM2Y2ZiIvPgo8L3N2Zz4K');
|
}
|
|
.field-input {
|
flex: 1;
|
height: 100%;
|
font-size: 28rpx;
|
color: #1f2329;
|
min-width: 0;
|
}
|
|
/* 获取验证码按钮:绿色实心,倒计时期间置灰不可点 */
|
.code-btn {
|
width: 190rpx;
|
height: 96rpx;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
border-radius: 8rpx;
|
background-color: #8bc34a;
|
color: #ffffff;
|
font-size: 28rpx;
|
flex-shrink: 0;
|
}
|
|
.code-btn--disabled {
|
background-color: #c3dd9d;
|
}
|
|
/* 已有账号:右对齐,点击返回登录 */
|
.has-account {
|
display: flex;
|
align-items: center;
|
justify-content: flex-end;
|
margin-top: 8rpx;
|
}
|
|
.has-account-text {
|
font-size: 28rpx;
|
color: #6b7280;
|
}
|
|
.has-account-link {
|
margin-left: 8rpx;
|
font-size: 28rpx;
|
color: #1a73e8;
|
}
|
|
/* 底部按钮区:白底 + 左右留边距,底部叠加安全区 */
|
.bottom-bar {
|
padding: 20rpx 32rpx;
|
padding-bottom: calc(20rpx + constant(safe-area-inset-bottom));
|
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
background-color: #ffffff;
|
}
|
|
/* 注册按钮:绿色实心,圆角与登录页「手机号一键登录」保持一致 */
|
.submit-btn {
|
height: 96rpx;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
border-radius: 16rpx;
|
background-color: #8bc34a;
|
color: #ffffff;
|
font-size: 32rpx;
|
font-weight: 600;
|
}
|
</style>
|