Files
consumer-front/src/pages/register/register.vue
T

395 lines
9.3 KiB
Vue
Raw Normal View History

2026-06-26 18:12:26 +08:00
<template>
<view class="register-page">
<!-- 顶部标题 -->
<view class="header">
<view class="title">用户注册</view>
<view class="subtitle">请输入手机号获取验证码完成注册</view>
</view>
2026-07-02 18:25:23 +08:00
<!-- 用户信息展示来自微信授权 -->
<view v-if="authInfo.nickname" class="auth-info">
<image class="auth-avatar" :src="authInfo.avatar || '/static/default-avatar.png'" mode="aspectFill" />
<text class="auth-nickname">{{ authInfo.nickname }}</text>
</view>
2026-06-26 18:12:26 +08:00
<!-- 表单区域 -->
<view class="form-area">
<!-- 手机号 -->
<view class="input-group">
<view class="input-label">手机号</view>
<view class="input-wrapper">
<input
class="input-field"
type="number"
maxlength="11"
placeholder="请输入手机号"
placeholder-class="placeholder"
v-model="phone"
/>
</view>
</view>
<!-- 验证码 -->
<view class="input-group">
<view class="input-label">验证码</view>
<view class="input-wrapper code-wrapper">
<input
class="input-field"
type="number"
maxlength="6"
placeholder="请输入验证码"
placeholder-class="placeholder"
v-model="code"
/>
<view
class="code-btn"
:class="{ disabled: countdown > 0 }"
@click="handleGetCode"
>
{{ countdown > 0 ? countdown + 's' : '获取验证码' }}
</view>
</view>
</view>
<!-- 注册按钮 -->
<view class="submit-btn" :class="{ active: canSubmit }" @click="handleRegister">
注册
</view>
</view>
<!-- 底部协议 -->
<view class="agreement">
<text class="agreement-text">注册表示同意</text>
<text class="agreement-link" @click="showAgreement('user')">用户协议</text>
<text class="agreement-text"></text>
<text class="agreement-link" @click="showAgreement('privacy')">隐私政策</text>
</view>
</view>
</template>
<script>
2026-07-02 18:25:23 +08:00
import { gatewayPost } from '@/utils/request.js';
import config from '@/config/env.js';
2026-06-26 18:12:26 +08:00
export default {
data() {
return {
phone: '',
code: '',
countdown: 0,
2026-07-02 18:25:23 +08:00
timer: null,
tempToken: '',
authInfo: { nickname: '', avatar: '' },
deviceId: '',
2026-06-26 18:12:26 +08:00
};
},
computed: {
canSubmit() {
return this.phone.length === 11 && this.code.length >= 4;
}
},
2026-07-02 18:25:23 +08:00
onLoad(options) {
this.deviceId = options.device_id || '';
// 从缓存读取授权信息(loading 页写入)
this.tempToken = uni.getStorageSync('temp_token') || '';
const cachedAuthInfo = uni.getStorageSync('auth_info');
if (cachedAuthInfo) {
this.authInfo = cachedAuthInfo;
}
// 如果没有 temp_token,提示用户重新授权
if (!this.tempToken) {
uni.showToast({ title: '授权信息已过期,请重新登录', icon: 'none' });
}
},
2026-06-26 18:12:26 +08:00
beforeDestroy() {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
},
methods: {
// 获取验证码
async handleGetCode() {
if (this.countdown > 0) return;
if (!/^1[3-9]\d{9}$/.test(this.phone)) {
uni.showToast({ title: '请输入正确的手机号', icon: 'none' });
return;
}
2026-07-02 18:25:23 +08:00
if (!this.tempToken) {
uni.showToast({ title: '授权信息已过期,请重新登录', icon: 'none' });
return;
}
2026-06-26 18:12:26 +08:00
try {
2026-07-02 18:25:23 +08:00
const res = await gatewayPost('/api/v1/user/sms/send', {
phone: this.phone,
temp_token: this.tempToken,
purpose: 'register',
2026-06-26 18:12:26 +08:00
});
const ret = res.data || {};
if (ret.code === 0) {
uni.showToast({ title: '验证码已发送', icon: 'success' });
this.startCountdown();
} else {
uni.showToast({ title: ret.message || '发送失败', icon: 'none' });
}
} catch (e) {
uni.showToast({ title: '网络请求失败', icon: 'none' });
}
},
startCountdown() {
this.countdown = 60;
this.timer = setInterval(() => {
this.countdown--;
if (this.countdown <= 0) {
clearInterval(this.timer);
this.timer = null;
}
}, 1000);
},
// 注册(Mock 模式也走真实接口)
2026-06-26 18:12:26 +08:00
async handleRegister() {
if (!this.canSubmit) return;
if (!/^1[3-9]\d{9}$/.test(this.phone)) {
uni.showToast({ title: '请输入正确的手机号', icon: 'none' });
return;
}
if (this.code.length < 4) {
uni.showToast({ title: '请输入验证码', icon: 'none' });
return;
}
2026-07-02 18:25:23 +08:00
if (!this.tempToken) {
uni.showToast({ title: '授权信息已过期,请重新登录', icon: 'none' });
return;
}
2026-06-26 18:12:26 +08:00
uni.showLoading({ title: '注册中...' });
try {
2026-07-02 18:25:23 +08:00
const res = await gatewayPost('/api/v1/user/register', {
temp_token: this.tempToken,
2026-06-26 18:12:26 +08:00
phone: this.phone,
2026-07-02 18:25:23 +08:00
code: this.code,
2026-06-26 18:12:26 +08:00
});
const ret = res.data || {};
2026-07-02 18:25:23 +08:00
if (ret.code === 0 && ret.data) {
// 保存 token
2026-07-02 18:25:23 +08:00
uni.setStorageSync('token', ret.data.token);
2026-07-11 18:05:45 +08:00
// 存储 user_info(至少有 user_id 就能开门)
const existing = uni.getStorageSync('user_info') || {};
const userInfo = {
user_id: ret.data.user_id || existing.user_id || '',
phone: this.phone || existing.phone || '',
nickname: existing.nickname || '',
avatar: existing.avatar || '',
};
// Mock 模式下补充 mock 数据
if (config.mockWechatLogin) {
const mockInfo = config.mockUser.user_info;
2026-07-11 18:05:45 +08:00
userInfo.nickname = userInfo.nickname || mockInfo.nickname;
userInfo.avatar = userInfo.avatar || mockInfo.avatar;
}
2026-07-02 18:25:23 +08:00
2026-07-11 18:05:45 +08:00
uni.setStorageSync('user_info', userInfo);
console.log('[register] 存储 user_info:', userInfo);
2026-07-02 18:25:23 +08:00
// 清除临时数据
uni.removeStorageSync('temp_token');
uni.removeStorageSync('auth_info');
2026-06-26 18:12:26 +08:00
uni.hideLoading();
uni.showToast({ title: '注册成功', icon: 'success' });
setTimeout(() => this.navigateToTarget(), 1500);
2026-06-26 18:12:26 +08:00
} else {
uni.hideLoading();
uni.showToast({ title: ret.message || '注册失败', icon: 'none' });
}
} catch (e) {
uni.hideLoading();
uni.showToast({ title: '网络请求失败', icon: 'none' });
}
},
// 跳转到目标页面
navigateToTarget() {
const scanRedirectUrl = uni.getStorageSync('__scan_redirect_url');
uni.removeStorageSync('__scan_redirect_url');
const url = scanRedirectUrl
? scanRedirectUrl
: this.deviceId
? `/pages/index/index?device_id=${this.deviceId}`
: '/pages/index/index';
uni.redirectTo({ url });
},
2026-06-26 18:12:26 +08:00
showAgreement(type) {
const title = type === 'user' ? '用户协议' : '隐私政策';
uni.showToast({ title: title + '开发中', icon: 'none' });
}
}
};
</script>
<style scoped>
page {
height: 100%;
background-color: #fff;
}
.register-page {
min-height: 100vh;
background-color: #fff;
padding: 0 60rpx;
}
/* 顶部标题 */
.header {
padding-top: 180rpx;
2026-07-02 18:25:23 +08:00
margin-bottom: 40rpx;
2026-06-26 18:12:26 +08:00
}
.title {
font-size: 48rpx;
font-weight: bold;
color: #333;
margin-bottom: 16rpx;
}
.subtitle {
font-size: 28rpx;
color: #999;
}
2026-07-02 18:25:23 +08:00
/* 微信授权信息 */
.auth-info {
display: flex;
align-items: center;
padding: 24rpx 0;
margin-bottom: 40rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.auth-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
margin-right: 20rpx;
background-color: #e0e0e0;
}
.auth-nickname {
font-size: 30rpx;
color: #333;
font-weight: 500;
}
2026-06-26 18:12:26 +08:00
/* 表单区域 */
.form-area {
width: 100%;
}
.input-group {
margin-bottom: 40rpx;
}
.input-label {
font-size: 28rpx;
color: #333;
margin-bottom: 16rpx;
font-weight: 500;
}
.input-wrapper {
display: flex;
align-items: center;
border-bottom: 2rpx solid #e5e5e5;
padding-bottom: 16rpx;
}
.input-field {
flex: 1;
height: 80rpx;
font-size: 32rpx;
color: #333;
}
.placeholder {
color: #ccc;
font-size: 32rpx;
}
/* 验证码输入框 */
.code-wrapper {
position: relative;
}
.code-btn {
flex-shrink: 0;
font-size: 28rpx;
color: #2979ff;
padding: 12rpx 24rpx;
border: 2rpx solid #2979ff;
border-radius: 8rpx;
white-space: nowrap;
}
.code-btn.disabled {
color: #999;
border-color: #e5e5e5;
}
/* 注册按钮 */
.submit-btn {
width: 100%;
height: 96rpx;
background: #e5e5e5;
border-radius: 48rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: #fff;
margin-top: 60rpx;
transition: all 0.3s;
}
.submit-btn.active {
background: linear-gradient(135deg, #2979ff, #1e60e0);
box-shadow: 0 8rpx 24rpx rgba(41, 121, 255, 0.4);
}
/* 底部协议 */
.agreement {
2026-07-09 18:49:56 +08:00
margin-top: 80rpx;
padding-bottom: env(safe-area-inset-bottom);
2026-06-26 18:12:26 +08:00
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
}
.agreement-text {
font-size: 24rpx;
color: #999;
}
.agreement-link {
font-size: 24rpx;
color: #2979ff;
}
</style>