update:新增页面&登录注册
This commit is contained in:
parent
738fe18086
commit
e757da69d8
@ -15,7 +15,7 @@ page {
|
||||
.home-scroll {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
|
||||
/* padding-bottom: calc( env(safe-area-inset-bottom)); */
|
||||
}
|
||||
|
||||
.banner {
|
||||
|
||||
@ -6,29 +6,26 @@ function authStatusText(status) {
|
||||
1: '已认证',
|
||||
2: '审核中'
|
||||
};
|
||||
return map[status] || `状态${status}`;
|
||||
return map[status] || '未认证';
|
||||
}
|
||||
|
||||
function maskPhone(phone) {
|
||||
if (!phone || phone.length < 7) return phone || '';
|
||||
return phone.substring(0, 3) + '****' + phone.substring(7);
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
loggedIn: false,
|
||||
profile: null,
|
||||
authStatusText: '-',
|
||||
|
||||
showAuthForm: false,
|
||||
|
||||
authForm: {
|
||||
name: '',
|
||||
mobile: '',
|
||||
idCardNumber: ''
|
||||
}
|
||||
|
||||
profile: {},
|
||||
maskedPhone: '',
|
||||
authStatusText: '未认证'
|
||||
},
|
||||
|
||||
onShow() {
|
||||
const accessToken = wx.getStorageSync('accessToken') || '';
|
||||
if (!accessToken) {
|
||||
this.setData({ loggedIn: false, profile: null, authStatusText: '-' });
|
||||
this.setData({ loggedIn: false, profile: {}, maskedPhone: '', authStatusText: '未认证' });
|
||||
return;
|
||||
}
|
||||
this.setData({ loggedIn: true });
|
||||
@ -44,9 +41,11 @@ Page({
|
||||
.fetchMyProfile()
|
||||
.then((profile) => {
|
||||
const p = profile || {};
|
||||
const st = p && typeof p.authStatus === 'number' ? p.authStatus : 0;
|
||||
const st = typeof p.authStatus === 'number' ? p.authStatus : 0;
|
||||
const phone = p.mobile || p.phone || '';
|
||||
this.setData({
|
||||
profile: p,
|
||||
maskedPhone: maskPhone(phone),
|
||||
authStatusText: authStatusText(st)
|
||||
});
|
||||
})
|
||||
@ -69,38 +68,32 @@ Page({
|
||||
wx.navigateTo({ url: '/subpackages/mine/records/records' });
|
||||
},
|
||||
|
||||
toggleAuthForm() {
|
||||
this.setData({ showAuthForm: !this.data.showAuthForm });
|
||||
goToAuth() {
|
||||
// 跳转到实名认证页面(可按需调整)
|
||||
wx.showToast({ title: '身份认证功能开发中', icon: 'none' });
|
||||
},
|
||||
|
||||
onAuthInput(e) {
|
||||
const key = e.currentTarget.dataset.key;
|
||||
this.setData({
|
||||
authForm: {
|
||||
...this.data.authForm,
|
||||
[key]: e.detail.value
|
||||
goToOrders() {
|
||||
wx.showToast({ title: '我的订单功能开发中', icon: 'none' });
|
||||
},
|
||||
|
||||
goToTeam() {
|
||||
wx.showToast({ title: '我的团队功能开发中', icon: 'none' });
|
||||
},
|
||||
|
||||
onChooseAvatar() {
|
||||
wx.chooseMedia({
|
||||
count: 1,
|
||||
mediaType: ['image'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: (res) => {
|
||||
const tempFilePath = res.tempFiles[0].tempFilePath;
|
||||
this.setData({
|
||||
'profile.avatar': tempFilePath
|
||||
});
|
||||
// TODO: 上传头像到服务器
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
submitAuth() {
|
||||
const { name, mobile, idCardNumber } = this.data.authForm;
|
||||
if (!name) return wx.showToast({ title: '请输入姓名', icon: 'none' });
|
||||
if (!mobile) return wx.showToast({ title: '请输入手机号', icon: 'none' });
|
||||
if (!idCardNumber) return wx.showToast({ title: '请输入身份证号', icon: 'none' });
|
||||
|
||||
wx.showLoading({ title: '提交中...' });
|
||||
auth
|
||||
.submitMyAuth({ name, mobile, idCardNumber })
|
||||
.then(() => {
|
||||
wx.showToast({ title: '提交成功', icon: 'success' });
|
||||
this.setData({ showAuthForm: false });
|
||||
return this.loadProfile();
|
||||
})
|
||||
.catch(() => {
|
||||
wx.showToast({ title: '提交失败', icon: 'none' });
|
||||
})
|
||||
.finally(() => wx.hideLoading());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
4
pages/mine/mine.json
Normal file
4
pages/mine/mine.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationStyle": "custom",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
@ -1,50 +1,83 @@
|
||||
<view class="container">
|
||||
<view wx:if="{{!loggedIn}}" class="center">
|
||||
<button class="login-btn" type="primary" bindtap="goToLogin">登录</button>
|
||||
<view class="hint">登录后可查看我的信息与实名认证状态</view>
|
||||
</view>
|
||||
|
||||
<view wx:else>
|
||||
<view class="card profile-card">
|
||||
<view class="row">
|
||||
<image class="avatar" wx:if="{{profile && profile.avatar}}" src="{{profile.avatar}}" mode="aspectFill" />
|
||||
<view class="avatar placeholder" wx:else>头像</view>
|
||||
<view class="profile-main">
|
||||
<view class="nick">{{profile && profile.nickName ? profile.nickName : '用户'}}</view>
|
||||
<view class="sub">积分:{{profile && profile.points != null ? profile.points : '-'}}</view>
|
||||
<view class="sub">证书:{{profile && profile.certificateCount != null ? profile.certificateCount : '-'}}</view>
|
||||
<view class="sub">实名认证:{{authStatusText}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="actions">
|
||||
<button size="mini" type="default" bindtap="goToPoints">积分</button>
|
||||
<button size="mini" type="default" bindtap="goToCert">证书</button>
|
||||
<button size="mini" type="default" bindtap="goToRecords">参赛记录</button>
|
||||
<button size="mini" type="primary" bindtap="toggleAuthForm">
|
||||
{{showAuthForm ? '收起实名认证' : '实名认证'}}
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view class="card" wx:if="{{showAuthForm}}">
|
||||
<view class="section-title">实名认证提交</view>
|
||||
<view class="form-item">
|
||||
<text class="label">姓名</text>
|
||||
<input class="input" placeholder="请输入姓名" value="{{authForm.name}}" bindinput="onAuthInput" data-key="name" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">手机号</text>
|
||||
<input class="input" placeholder="请输入手机号" value="{{authForm.mobile}}" bindinput="onAuthInput" data-key="mobile" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">身份证号</text>
|
||||
<input class="input" placeholder="请输入身份证号" value="{{authForm.idCardNumber}}" bindinput="onAuthInput" data-key="idCardNumber" />
|
||||
</view>
|
||||
|
||||
<button class="submit-btn" type="primary" bindtap="submitAuth">提交审核</button>
|
||||
</view>
|
||||
|
||||
<!-- 未登录 -->
|
||||
<view wx:if="{{!loggedIn}}" class="not-logged">
|
||||
<view class="not-logged-inner">
|
||||
<text class="not-logged-tip">登录后可查看我的信息与参赛记录</text>
|
||||
<button class="go-login-btn" bindtap="goToLogin">去登录</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 已登录 -->
|
||||
<view wx:else class="mine-page">
|
||||
<!-- 蓝色头部区域 -->
|
||||
<view class="header">
|
||||
<view class="header-bg"></view>
|
||||
<view class="status-bar"></view>
|
||||
<view class="nav-bar">
|
||||
<text class="nav-title">我的</text>
|
||||
</view>
|
||||
<!-- 头像 + 手机号 -->
|
||||
<view class="profile-section">
|
||||
<view class="avatar-wrap" bindtap="onChooseAvatar">
|
||||
<image
|
||||
class="avatar"
|
||||
src="{{profile.avatar || '/assets/tab_mine.png'}}"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view class="camera-icon">
|
||||
<text class="camera-text">📷</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="phone-text">{{maskedPhone}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 内容区 -->
|
||||
<view class="content">
|
||||
<!-- 积分 & 证书双卡 -->
|
||||
<view class="stat-row">
|
||||
<view class="stat-card" bindtap="goToPoints">
|
||||
<view class="stat-left">
|
||||
<text class="stat-num">{{profile.points || 0}}</text>
|
||||
<text class="stat-label">我的积分</text>
|
||||
</view>
|
||||
<image class="stat-icon" src="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/event_miniapp/point.png" mode="aspectFit" />
|
||||
</view>
|
||||
<view class="stat-card" bindtap="goToCert">
|
||||
<view class="stat-left">
|
||||
<text class="stat-num">{{profile.certificateCount || 0}}</text>
|
||||
<text class="stat-label">我的证书</text>
|
||||
</view>
|
||||
<image class="stat-icon" src="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/event_miniapp/book.png" mode="aspectFit" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 身份认证条 -->
|
||||
<view class="auth-bar" bindtap="goToAuth">
|
||||
<view class="auth-left">
|
||||
<text class="auth-title">身份认证</text>
|
||||
<text class="auth-desc">点击进行身份认证</text>
|
||||
</view>
|
||||
<view class="auth-badge">
|
||||
<text class="auth-badge-icon">🛡</text>
|
||||
<text class="auth-badge-text">{{authStatusText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 功能入口 -->
|
||||
<view class="func-card">
|
||||
<view class="func-item" bindtap="goToOrders">
|
||||
<image class="func-icon" src="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/event_miniapp/order.png" mode="aspectFit" />
|
||||
<text class="func-text">我的订单</text>
|
||||
</view>
|
||||
<view class="func-item" bindtap="goToRecords">
|
||||
<image class="func-icon" src="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/event_miniapp/history.png" mode="aspectFit" />
|
||||
<text class="func-text">参赛记录</text>
|
||||
</view>
|
||||
<view class="func-item" bindtap="goToTeam">
|
||||
<image class="func-icon" src="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/event_miniapp/team.png" mode="aspectFit" />
|
||||
<text class="func-text">我的团队</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
@ -1,153 +1,252 @@
|
||||
.container {
|
||||
padding: 16rpx;
|
||||
/* ====== 未登录 ====== */
|
||||
.not-logged {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
.center {
|
||||
padding: 40rpx 0;
|
||||
.not-logged-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 18rpx;
|
||||
gap: 32rpx;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
.not-logged-tip {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.go-login-btn {
|
||||
width: 400rpx !important;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
background: #1a56db;
|
||||
color: #fff;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
border-radius: 12rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.go-login-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* ====== 已登录页面 ====== */
|
||||
.mine-page {
|
||||
min-height: 100vh;
|
||||
background: #f0f2f5;
|
||||
}
|
||||
|
||||
/* ====== 蓝色头部 ====== */
|
||||
.header {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 420rpx;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.hint {
|
||||
color: #888;
|
||||
font-size: 24rpx;
|
||||
text-align: center;
|
||||
.header-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 520rpx;
|
||||
background: linear-gradient(180deg, #1a3a8a 0%, #1a56db 100%);
|
||||
border-radius: 0 0 40rpx 40rpx;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #ffffff;
|
||||
border-radius: 18rpx;
|
||||
padding: 16rpx;
|
||||
box-shadow: 0 8rpx 22rpx rgba(0, 0, 0, 0.04);
|
||||
.status-bar {
|
||||
height: env(safe-area-inset-top, 44px);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.profile-card {
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.row {
|
||||
.nav-bar {
|
||||
position: relative;
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* ====== 头像区 ====== */
|
||||
.profile-section {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20rpx 0 40rpx;
|
||||
}
|
||||
|
||||
.avatar-wrap {
|
||||
position: relative;
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 110rpx;
|
||||
height: 110rpx;
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 50%;
|
||||
background: #f2f2f2;
|
||||
border: 6rpx solid rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.avatar.placeholder {
|
||||
.camera-icon {
|
||||
position: absolute;
|
||||
right: 4rpx;
|
||||
bottom: 4rpx;
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
background: #1a56db;
|
||||
border: 4rpx solid #fff;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #aaa;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.camera-text {
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.profile-main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.nick {
|
||||
.phone-text {
|
||||
margin-top: 16rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 900;
|
||||
color: #222;
|
||||
margin-bottom: 10rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
|
||||
.sub {
|
||||
color: #666;
|
||||
font-size: 26rpx;
|
||||
/* ====== 内容区 ====== */
|
||||
.content {
|
||||
position: relative;
|
||||
margin-top: -20rpx;
|
||||
padding: 0 28rpx;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
/* ====== 积分 & 证书双卡 ====== */
|
||||
.stat-row {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
flex: 1;
|
||||
background: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 28rpx 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.stat-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.stat-num {
|
||||
font-size: 48rpx;
|
||||
font-weight: 800;
|
||||
color: #1a1a1a;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: 12rpx;
|
||||
.stat-icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
margin-top: 18rpx;
|
||||
margin-bottom: 14rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 900;
|
||||
color: #222;
|
||||
/* ====== 身份认证条 ====== */
|
||||
.auth-bar {
|
||||
margin-top: 20rpx;
|
||||
background: linear-gradient(135deg, #1a3a8a 0%, #1a56db 100%);
|
||||
border-radius: 16rpx;
|
||||
padding: 28rpx 30rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
margin-top: 22rpx;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
.auth-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10rpx;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #666;
|
||||
font-size: 24rpx;
|
||||
.auth-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.input {
|
||||
height: 72rpx;
|
||||
background: #f6f7fb;
|
||||
border-radius: 14rpx;
|
||||
padding: 0 16rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
margin-top: 22rpx;
|
||||
width: 100%;
|
||||
height: 84rpx;
|
||||
}
|
||||
|
||||
.list {
|
||||
height: calc(100vh - 420rpx);
|
||||
}
|
||||
|
||||
.empty {
|
||||
text-align: center;
|
||||
padding: 40rpx 0;
|
||||
color: #888;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 24rpx 0;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.reg-card {
|
||||
padding: 16rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 8rpx 22rpx rgba(0, 0, 0, 0.03);
|
||||
margin-bottom: 14rpx;
|
||||
}
|
||||
|
||||
.r-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 900;
|
||||
color: #222;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.r-sub {
|
||||
color: #666;
|
||||
font-size: 26rpx;
|
||||
.auth-desc {
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
|
||||
.auth-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 30rpx;
|
||||
padding: 10rpx 24rpx;
|
||||
}
|
||||
|
||||
.auth-badge-icon {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.auth-badge-text {
|
||||
font-size: 24rpx;
|
||||
color: #ffffff;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ====== 功能入口卡 ====== */
|
||||
.func-card {
|
||||
margin-top: 20rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 36rpx 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.func-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 14rpx;
|
||||
}
|
||||
|
||||
.func-icon {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.func-text {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
|
||||
@ -34,5 +34,6 @@
|
||||
"ignore": [],
|
||||
"include": []
|
||||
},
|
||||
"editorSetting": {}
|
||||
"editorSetting": {},
|
||||
"libVersion": "3.15.1"
|
||||
}
|
||||
@ -1,8 +1,8 @@
|
||||
{
|
||||
"libVersion": "3.15.1",
|
||||
"libVersion": "3.15.0",
|
||||
"projectname": "YD_Event.miniApp",
|
||||
"setting": {
|
||||
"urlCheck": true,
|
||||
"urlCheck": false,
|
||||
"coverView": false,
|
||||
"lazyloadPlaceholderEnable": false,
|
||||
"skylineRenderEnable": false,
|
||||
|
||||
@ -1,14 +1,26 @@
|
||||
const auth = require('../../../utils/auth');
|
||||
|
||||
const PHONE_RE = /^1[3456789]\d{9}$/;
|
||||
const CODE_COUNTDOWN = 60;
|
||||
|
||||
Page({
|
||||
data: {
|
||||
loginMode: 'password', // 'code' | 'password'
|
||||
mobile: '',
|
||||
password: '',
|
||||
logging: false
|
||||
verificationCode: '',
|
||||
showPassword: false,
|
||||
logging: false,
|
||||
agreed: false,
|
||||
|
||||
// 验证码倒计时
|
||||
codeText: '发送验证码',
|
||||
codeBtnDisabled: false,
|
||||
countdown: 0
|
||||
},
|
||||
|
||||
_timer: null,
|
||||
|
||||
onShow() {
|
||||
const accessToken = wx.getStorageSync('accessToken') || '';
|
||||
if (accessToken) {
|
||||
@ -16,43 +28,165 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
this._clearTimer();
|
||||
},
|
||||
|
||||
// ========= 事件处理 =========
|
||||
|
||||
/** 切换登录方式 */
|
||||
onSwitchMode(e) {
|
||||
const mode = e.currentTarget.dataset.mode;
|
||||
if (mode === this.data.loginMode) return;
|
||||
this.setData({
|
||||
loginMode: mode,
|
||||
verificationCode: '',
|
||||
password: ''
|
||||
});
|
||||
},
|
||||
|
||||
/** 手机号输入 */
|
||||
onMobileInput(e) {
|
||||
this.setData({ mobile: (e.detail.value || '').trim() });
|
||||
},
|
||||
|
||||
/** 清除手机号 */
|
||||
onClearMobile() {
|
||||
this.setData({ mobile: '' });
|
||||
},
|
||||
|
||||
/** 验证码输入 */
|
||||
onCodeInput(e) {
|
||||
this.setData({ verificationCode: (e.detail.value || '').trim() });
|
||||
},
|
||||
|
||||
/** 密码输入 */
|
||||
onPasswordInput(e) {
|
||||
this.setData({ password: e.detail.value || '' });
|
||||
},
|
||||
|
||||
onLoginTap() {
|
||||
if (this.data.logging) return;
|
||||
const { mobile, password } = this.data;
|
||||
/** 显示/隐藏密码 */
|
||||
onTogglePassword() {
|
||||
this.setData({ showPassword: !this.data.showPassword });
|
||||
},
|
||||
|
||||
/** 勾选协议 */
|
||||
onToggleAgreement() {
|
||||
this.setData({ agreed: !this.data.agreed });
|
||||
},
|
||||
|
||||
/** 忘记密码 */
|
||||
onForgetPassword() {
|
||||
wx.navigateTo({ url: '/subpackages/mine/register/register' });
|
||||
},
|
||||
|
||||
// ========= 发送验证码 =========
|
||||
|
||||
onSendCode() {
|
||||
if (this.data.codeBtnDisabled) return;
|
||||
const { mobile } = this.data;
|
||||
if (!PHONE_RE.test(mobile)) {
|
||||
wx.showToast({ title: '请输入正确手机号', icon: 'none' });
|
||||
wx.showToast({ title: '请输入正确的手机号', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (!password) {
|
||||
|
||||
this.setData({ codeBtnDisabled: true, codeText: '发送中...' });
|
||||
|
||||
auth
|
||||
.sendVerificationCode(mobile)
|
||||
.then(() => {
|
||||
wx.showToast({ title: '验证码已发送', icon: 'success' });
|
||||
this._startCountdown();
|
||||
})
|
||||
.catch((err) => {
|
||||
wx.showToast({ title: err.message || '发送失败', icon: 'none' });
|
||||
this.setData({ codeBtnDisabled: false, codeText: '发送验证码' });
|
||||
});
|
||||
},
|
||||
|
||||
// ========= 登录 =========
|
||||
|
||||
onLoginTap() {
|
||||
if (this.data.logging) return;
|
||||
|
||||
if (!this.data.agreed) {
|
||||
wx.showToast({ title: '请先同意用户协议与隐私政策', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
const { mobile, loginMode, password, verificationCode } = this.data;
|
||||
|
||||
if (!PHONE_RE.test(mobile)) {
|
||||
wx.showToast({ title: '请输入正确的手机号', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (loginMode === 'password' && !password) {
|
||||
wx.showToast({ title: '请输入密码', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (loginMode === 'code' && !verificationCode) {
|
||||
wx.showToast({ title: '请输入验证码', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
this.setData({ logging: true });
|
||||
wx.showLoading({ title: '登录中...' });
|
||||
|
||||
auth
|
||||
.loginWithMobilePassword({ mobile, password })
|
||||
const loginPromise =
|
||||
loginMode === 'code'
|
||||
? auth.loginWithMobileCode({ mobile, verificationCode })
|
||||
: auth.loginWithMobilePassword({ mobile, password });
|
||||
|
||||
loginPromise
|
||||
.then(() => {
|
||||
wx.showToast({ title: '登录成功', icon: 'success' });
|
||||
setTimeout(() => {
|
||||
wx.redirectTo({ url: '/pages/mine/mine' });
|
||||
}, 400);
|
||||
})
|
||||
.catch(() => {
|
||||
wx.showToast({ title: '登录失败', icon: 'none' });
|
||||
.catch((err) => {
|
||||
wx.showToast({ title: err.message || '登录失败', icon: 'none' });
|
||||
})
|
||||
.finally(() => {
|
||||
this.setData({ logging: false });
|
||||
wx.hideLoading();
|
||||
});
|
||||
},
|
||||
|
||||
// ========= 内部方法 =========
|
||||
|
||||
_startCountdown() {
|
||||
let seconds = CODE_COUNTDOWN;
|
||||
this.setData({
|
||||
countdown: seconds,
|
||||
codeText: `${seconds}s`,
|
||||
codeBtnDisabled: true
|
||||
});
|
||||
|
||||
this._timer = setInterval(() => {
|
||||
seconds -= 1;
|
||||
if (seconds <= 0) {
|
||||
this._clearTimer();
|
||||
this.setData({
|
||||
countdown: 0,
|
||||
codeText: '发送验证码',
|
||||
codeBtnDisabled: false
|
||||
});
|
||||
} else {
|
||||
this.setData({
|
||||
countdown: seconds,
|
||||
codeText: `${seconds}s`
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
_clearTimer() {
|
||||
if (this._timer) {
|
||||
clearInterval(this._timer);
|
||||
this._timer = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
{
|
||||
"navigationBarTitleText": "登录"
|
||||
"navigationBarTitleText": "登录",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black"
|
||||
}
|
||||
|
||||
|
||||
@ -1,32 +1,101 @@
|
||||
<view class="container">
|
||||
<view class="card">
|
||||
<view class="form-item">
|
||||
<text class="label">手机号</text>
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
maxlength="11"
|
||||
placeholder="请输入手机号"
|
||||
value="{{mobile}}"
|
||||
bindinput="onMobileInput"
|
||||
/>
|
||||
<view class="login-page">
|
||||
<!-- Tab 切换 -->
|
||||
<view class="tab-bar">
|
||||
<view class="tab-item {{loginMode === 'password' ? 'active' : ''}}" bindtap="onSwitchMode" data-mode="password">
|
||||
<text class="tab-text">账号登录</text>
|
||||
<view class="tab-line" wx:if="{{loginMode === 'password'}}"></view>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">密码</text>
|
||||
<input
|
||||
class="input"
|
||||
password
|
||||
placeholder="请输入密码"
|
||||
value="{{password}}"
|
||||
bindinput="onPasswordInput"
|
||||
/>
|
||||
<view class="tab-item {{loginMode === 'code' ? 'active' : ''}}" bindtap="onSwitchMode" data-mode="code">
|
||||
<text class="tab-text">短信登录</text>
|
||||
<view class="tab-line" wx:if="{{loginMode === 'code'}}"></view>
|
||||
</view>
|
||||
<button class="submit-btn" type="primary" bindtap="onLoginTap" loading="{{logging}}">
|
||||
登录
|
||||
</button>
|
||||
<navigator class="link-register" url="/subpackages/mine/register/register" hover-class="none">
|
||||
没有账号?去注册
|
||||
</navigator>
|
||||
<view class="hint">登录后可查看我的信息与参赛记录</view>
|
||||
</view>
|
||||
|
||||
<!-- 账号登录模式 -->
|
||||
<block wx:if="{{loginMode === 'password'}}">
|
||||
<view class="field">
|
||||
<text class="field-label">账号</text>
|
||||
<view class="field-input-box">
|
||||
<input
|
||||
class="field-input"
|
||||
type="number"
|
||||
maxlength="11"
|
||||
placeholder="请输入手机号"
|
||||
placeholder-class="placeholder"
|
||||
value="{{mobile}}"
|
||||
bindinput="onMobileInput"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="field">
|
||||
<text class="field-label">密码</text>
|
||||
<view class="field-input-box">
|
||||
<input
|
||||
class="field-input"
|
||||
password="{{!showPassword}}"
|
||||
placeholder="请输入密码"
|
||||
placeholder-class="placeholder"
|
||||
value="{{password}}"
|
||||
bindinput="onPasswordInput"
|
||||
/>
|
||||
<view class="eye-btn" bindtap="onTogglePassword">
|
||||
<image class="eye-icon" src="/assets/icon-eye{{showPassword ? '-open' : '-close'}}.png" mode="aspectFit" wx:if="{{false}}" />
|
||||
<text class="eye-text">{{showPassword ? '🙈' : '👁'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 短信登录模式 -->
|
||||
<block wx:if="{{loginMode === 'code'}}">
|
||||
<view class="field">
|
||||
<text class="field-label">手机号</text>
|
||||
<view class="field-input-box">
|
||||
<input
|
||||
class="field-input"
|
||||
type="number"
|
||||
maxlength="11"
|
||||
placeholder="请输入手机号"
|
||||
placeholder-class="placeholder"
|
||||
value="{{mobile}}"
|
||||
bindinput="onMobileInput"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="field">
|
||||
<text class="field-label">验证码</text>
|
||||
<view class="field-input-box">
|
||||
<input
|
||||
class="field-input"
|
||||
type="number"
|
||||
maxlength="6"
|
||||
placeholder="请输入验证码"
|
||||
placeholder-class="placeholder"
|
||||
value="{{verificationCode}}"
|
||||
bindinput="onCodeInput"
|
||||
/>
|
||||
<text class="send-code-link {{codeBtnDisabled ? 'disabled' : ''}}" bindtap="onSendCode">{{codeText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 协议 + 忘记密码 -->
|
||||
<view class="agreement-row">
|
||||
<view class="agreement-left" bindtap="onToggleAgreement">
|
||||
<view class="check-box {{agreed ? 'checked' : ''}}">
|
||||
<text class="check-icon" wx:if="{{agreed}}">✓</text>
|
||||
</view>
|
||||
<text class="agreement-text">我同意</text>
|
||||
<text class="agreement-link">用户协议</text>
|
||||
<text class="agreement-text">与</text>
|
||||
<text class="agreement-link">隐私政策</text>
|
||||
</view>
|
||||
<text class="forget-link" bindtap="onForgetPassword">忘记密码?</text>
|
||||
</view>
|
||||
|
||||
<!-- 按钮区域 -->
|
||||
<view class="btn-area">
|
||||
<button class="btn-login" bindtap="onLoginTap" loading="{{logging}}">登录</button>
|
||||
<navigator class="btn-register" url="/subpackages/mine/register/register" hover-class="btn-register-hover">注册</navigator>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@ -1,59 +1,215 @@
|
||||
.container {
|
||||
/* ===== 页面 ===== */
|
||||
.login-page {
|
||||
min-height: 100vh;
|
||||
background: #f7f8fa;
|
||||
padding: 24rpx 16rpx;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #ffffff;
|
||||
border-radius: 18rpx;
|
||||
padding: 24rpx 20rpx;
|
||||
box-shadow: 0 8rpx 22rpx rgba(0, 0, 0, 0.04);
|
||||
max-width: 520rpx;
|
||||
margin: 40rpx auto 0;
|
||||
padding: 0 48rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
/* ===== Tab 栏 ===== */
|
||||
.tab-bar {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 48rpx;
|
||||
padding-top: 40rpx;
|
||||
padding-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10rpx;
|
||||
margin-top: 16rpx;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
padding-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.form-item:first-child {
|
||||
margin-top: 0;
|
||||
.tab-text {
|
||||
font-size: 30rpx;
|
||||
color: #999;
|
||||
transition: all 0.25s;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #666;
|
||||
font-size: 24rpx;
|
||||
.tab-item.active .tab-text {
|
||||
font-size: 34rpx;
|
||||
color: #1a1a1a;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.input {
|
||||
height: 72rpx;
|
||||
background: #f6f7fb;
|
||||
border-radius: 14rpx;
|
||||
padding: 0 16rpx;
|
||||
font-size: 26rpx;
|
||||
.tab-line {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 48rpx;
|
||||
height: 6rpx;
|
||||
background: #1a56db;
|
||||
border-radius: 3rpx;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
margin-top: 28rpx;
|
||||
width: 100%;
|
||||
height: 84rpx;
|
||||
/* ===== 表单字段 ===== */
|
||||
.field {
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
|
||||
.link-register {
|
||||
.field-label {
|
||||
display: block;
|
||||
margin-top: 20rpx;
|
||||
text-align: center;
|
||||
color: #576b95;
|
||||
font-size: 26rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.hint {
|
||||
margin-top: 24rpx;
|
||||
color: #888;
|
||||
font-size: 24rpx;
|
||||
text-align: center;
|
||||
.field-input-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 96rpx;
|
||||
border: 2rpx solid #d9d9d9;
|
||||
border-radius: 12rpx;
|
||||
padding: 0 24rpx;
|
||||
background: #fff;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
|
||||
.field-input-box:focus-within {
|
||||
border-color: #1a56db;
|
||||
}
|
||||
|
||||
.field-input {
|
||||
flex: 1;
|
||||
height: 96rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: #c0c4cc;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* ===== 眼睛按钮 ===== */
|
||||
.eye-btn {
|
||||
flex-shrink: 0;
|
||||
padding: 10rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.eye-text {
|
||||
font-size: 36rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* ===== 发送验证码链接 ===== */
|
||||
.send-code-link {
|
||||
flex-shrink: 0;
|
||||
font-size: 26rpx;
|
||||
color: #1a56db;
|
||||
font-weight: 500;
|
||||
padding-left: 16rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.send-code-link.disabled {
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
/* ===== 协议行 ===== */
|
||||
.agreement-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
|
||||
.agreement-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.check-box {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
border: 2rpx solid #d9d9d9;
|
||||
border-radius: 6rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 12rpx;
|
||||
flex-shrink: 0;
|
||||
transition: all 0.25s;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.check-box.checked {
|
||||
background: #1a56db;
|
||||
border-color: #1a56db;
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
font-size: 22rpx;
|
||||
color: #ffffff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.agreement-text {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.agreement-link {
|
||||
font-size: 24rpx;
|
||||
color: #1a1a1a;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.forget-link {
|
||||
font-size: 24rpx;
|
||||
color: #1a56db;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ===== 按钮区域 ===== */
|
||||
.btn-area {
|
||||
margin-top: 60rpx;
|
||||
}
|
||||
|
||||
.btn-login {
|
||||
width: 100% !important;
|
||||
height: 96rpx;
|
||||
line-height: 96rpx;
|
||||
background: #1a56db;
|
||||
color: #ffffff;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
border-radius: 12rpx;
|
||||
border: none;
|
||||
letter-spacing: 4rpx;
|
||||
}
|
||||
|
||||
.btn-login::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-register {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
line-height: 96rpx;
|
||||
background: #ffffff;
|
||||
color: #1a56db;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
border-radius: 12rpx;
|
||||
border: 2rpx solid #1a56db;
|
||||
margin-top: 24rpx;
|
||||
letter-spacing: 4rpx;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.btn-register-hover {
|
||||
background: #f0f5ff;
|
||||
}
|
||||
|
||||
@ -1,10 +1,16 @@
|
||||
const auth = require('../../../utils/auth');
|
||||
const { formatTime } = require('../../../utils/format');
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
Page({
|
||||
data: {
|
||||
nickName: '',
|
||||
points: '-',
|
||||
loading: false
|
||||
totalPoints: 0,
|
||||
activeTab: 1, // 1-获取记录 2-消耗记录
|
||||
records: [],
|
||||
loading: false,
|
||||
noMore: false,
|
||||
pageNo: 1
|
||||
},
|
||||
|
||||
onShow() {
|
||||
@ -14,22 +20,85 @@ Page({
|
||||
return;
|
||||
}
|
||||
this.loadProfile();
|
||||
this.resetAndLoad();
|
||||
},
|
||||
|
||||
onPullDownRefresh() {
|
||||
this.loadProfile();
|
||||
this.resetAndLoad().finally(() => wx.stopPullDownRefresh());
|
||||
},
|
||||
|
||||
/** 返回 */
|
||||
onBack() {
|
||||
wx.navigateBack({ delta: 1 });
|
||||
},
|
||||
|
||||
/** 加载积分总数 */
|
||||
loadProfile() {
|
||||
if (this.data.loading) return;
|
||||
auth.fetchMyProfile().then((profile) => {
|
||||
const p = profile || {};
|
||||
this.setData({ totalPoints: p.points || 0 });
|
||||
}).catch(() => {});
|
||||
},
|
||||
|
||||
/** 切换Tab */
|
||||
onSwitchTab(e) {
|
||||
const tab = Number(e.currentTarget.dataset.tab);
|
||||
if (tab === this.data.activeTab) return;
|
||||
this.setData({ activeTab: tab });
|
||||
this.resetAndLoad();
|
||||
},
|
||||
|
||||
/** 重置并加载 */
|
||||
resetAndLoad() {
|
||||
this.setData({ records: [], pageNo: 1, noMore: false });
|
||||
return this.loadRecords();
|
||||
},
|
||||
|
||||
/** 加载更多 */
|
||||
onLoadMore() {
|
||||
if (this.data.loading || this.data.noMore) return;
|
||||
this.loadRecords();
|
||||
},
|
||||
|
||||
/** 加载积分记录 */
|
||||
loadRecords() {
|
||||
if (this.data.loading) return Promise.resolve();
|
||||
this.setData({ loading: true });
|
||||
auth
|
||||
.fetchMyProfile()
|
||||
.then((profile) => {
|
||||
const p = profile || {};
|
||||
|
||||
const { pageNo, activeTab } = this.data;
|
||||
|
||||
return auth
|
||||
.fetchMyPoints({ pageNo, pageSize: PAGE_SIZE })
|
||||
.then((result) => {
|
||||
const res = result || {};
|
||||
const allRows = res.rows || [];
|
||||
// 按类型筛选: type 1=获取 2=消耗
|
||||
const filtered = allRows.filter((item) => item.type === activeTab);
|
||||
|
||||
const formatted = filtered.map((item) => ({
|
||||
...item,
|
||||
createdTimeStr: item.createdTime ? formatTime(item.createdTime) : '',
|
||||
projectInfo: item.projectInfo || item.projectName || '',
|
||||
rank: item.rank || ''
|
||||
}));
|
||||
|
||||
const newRecords = this.data.records.concat(formatted);
|
||||
const totalRows = res.total || 0;
|
||||
const currentTotal = pageNo * PAGE_SIZE;
|
||||
|
||||
this.setData({
|
||||
nickName: p.nickName || '',
|
||||
points: p.points != null ? p.points : '-'
|
||||
records: newRecords,
|
||||
pageNo: pageNo + 1,
|
||||
noMore: currentTotal >= totalRows
|
||||
});
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => this.setData({ loading: false }));
|
||||
.catch(() => {
|
||||
wx.showToast({ title: '加载失败', icon: 'none' });
|
||||
})
|
||||
.finally(() => {
|
||||
this.setData({ loading: false });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
{
|
||||
"navigationBarTitleText": "我的积分"
|
||||
"navigationStyle": "custom",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,81 @@
|
||||
<view class="container">
|
||||
<view class="card">
|
||||
<view class="title">{{nickName || '用户'}}</view>
|
||||
<view class="value">积分:{{points}}</view>
|
||||
<view class="points-page">
|
||||
<!-- 蓝色头部 -->
|
||||
<view class="header">
|
||||
<view class="header-bg"></view>
|
||||
<view class="status-bar"></view>
|
||||
<view class="nav-bar">
|
||||
<view class="nav-back" bindtap="onBack">
|
||||
<text class="back-arrow">←</text>
|
||||
</view>
|
||||
<text class="nav-title">我的积分</text>
|
||||
<view class="nav-placeholder"></view>
|
||||
</view>
|
||||
<view class="points-total">
|
||||
<text class="points-num">{{totalPoints}}</text>
|
||||
<text class="points-label">我的积分</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Tab 切换 -->
|
||||
<view class="tab-bar">
|
||||
<view class="tab-item {{activeTab === 1 ? 'active' : ''}}" bindtap="onSwitchTab" data-tab="1">
|
||||
<text class="tab-text">获取记录</text>
|
||||
<view class="tab-line" wx:if="{{activeTab === 1}}"></view>
|
||||
</view>
|
||||
<view class="tab-item {{activeTab === 2 ? 'active' : ''}}" bindtap="onSwitchTab" data-tab="2">
|
||||
<text class="tab-text">消耗记录</text>
|
||||
<view class="tab-line" wx:if="{{activeTab === 2}}"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表 -->
|
||||
<scroll-view
|
||||
class="record-list"
|
||||
scroll-y
|
||||
enhanced
|
||||
show-scrollbar="{{false}}"
|
||||
bindscrolltolower="onLoadMore"
|
||||
>
|
||||
<!-- 空状态 -->
|
||||
<view wx:if="{{records.length === 0 && !loading}}" class="empty">
|
||||
<view class="empty-icon">
|
||||
<view class="empty-doc">
|
||||
<view class="empty-doc-top"></view>
|
||||
<view class="empty-doc-line"></view>
|
||||
<view class="empty-doc-line short"></view>
|
||||
<view class="empty-doc-line"></view>
|
||||
</view>
|
||||
<view class="empty-doc-shadow"></view>
|
||||
</view>
|
||||
<text class="empty-text">暂无{{activeTab === 1 ? '获取' : '消耗'}}记录</text>
|
||||
</view>
|
||||
|
||||
<!-- 有数据时:表头 + 列表 -->
|
||||
<block wx:if="{{records.length > 0}}">
|
||||
<view class="table-header">
|
||||
<text class="th th-event">赛事</text>
|
||||
<text class="th th-project">项目-组别</text>
|
||||
<text class="th th-rank">名次</text>
|
||||
<text class="th th-points">积分</text>
|
||||
</view>
|
||||
|
||||
<view wx:for="{{records}}" wx:key="id" class="record-row">
|
||||
<view class="td td-event">
|
||||
<text class="event-name">{{item.title || '-'}}</text>
|
||||
<text class="event-time">{{item.createdTimeStr}}</text>
|
||||
</view>
|
||||
<text class="td td-project">{{item.projectInfo || '-'}}</text>
|
||||
<text class="td td-rank">{{item.rank || '-'}}</text>
|
||||
<text class="td td-points {{item.points > 0 ? 'positive' : 'negative'}}">{{item.points > 0 ? '+' : ''}}{{item.points}}</text>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<view wx:if="{{loading}}" class="loading-tip">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
<view wx:if="{{noMore && records.length > 0}}" class="loading-tip">
|
||||
<text>没有更多了</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
|
||||
@ -1,26 +1,298 @@
|
||||
.container {
|
||||
/* ===== 页面 ===== */
|
||||
.points-page {
|
||||
min-height: 100vh;
|
||||
background: #f7f8fa;
|
||||
padding: 24rpx 16rpx;
|
||||
background: #f5f7fa;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #ffffff;
|
||||
border-radius: 18rpx;
|
||||
padding: 20rpx 18rpx;
|
||||
box-shadow: 0 8rpx 22rpx rgba(0, 0, 0, 0.03);
|
||||
/* ===== 蓝色头部 ===== */
|
||||
.header {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 900;
|
||||
color: #222;
|
||||
margin-bottom: 12rpx;
|
||||
.header-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 420rpx;
|
||||
background: linear-gradient(180deg, #1a3a8a 0%, #3a7bd5 100%);
|
||||
border-radius: 0 0 40rpx 40rpx;
|
||||
}
|
||||
|
||||
.value {
|
||||
.status-bar {
|
||||
height: env(safe-area-inset-top, 44px);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
position: relative;
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.nav-back {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.back-arrow {
|
||||
font-size: 40rpx;
|
||||
font-weight: 900;
|
||||
color: #1aad19;
|
||||
color: #ffffff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.nav-placeholder {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
/* ===== 积分总数 ===== */
|
||||
.points-total {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20rpx 0 50rpx;
|
||||
}
|
||||
|
||||
.points-num {
|
||||
font-size: 80rpx;
|
||||
font-weight: 800;
|
||||
color: #ffffff;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.points-label {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
/* ===== Tab 栏 ===== */
|
||||
.tab-bar {
|
||||
display: flex;
|
||||
background: #ffffff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 26rpx 0;
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
font-size: 30rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.tab-item.active .tab-text {
|
||||
color: #1a1a1a;
|
||||
font-weight: 700;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.tab-line {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 48rpx;
|
||||
height: 6rpx;
|
||||
background: #1a56db;
|
||||
border-radius: 3rpx;
|
||||
}
|
||||
|
||||
/* ===== 表头 ===== */
|
||||
.table-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx 28rpx;
|
||||
background: #ffffff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.th {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.th-event {
|
||||
width: 240rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.th-project {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.th-rank {
|
||||
width: 80rpx;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.th-points {
|
||||
width: 80rpx;
|
||||
text-align: right;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ===== 记录列表 ===== */
|
||||
.record-list {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.record-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx 28rpx;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
}
|
||||
|
||||
.td {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.td-event {
|
||||
width: 240rpx;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.event-name {
|
||||
font-size: 26rpx;
|
||||
color: #1a1a1a;
|
||||
font-weight: 500;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.event-time {
|
||||
font-size: 22rpx;
|
||||
color: #bbb;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
|
||||
.td-project {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
padding: 0 8rpx;
|
||||
}
|
||||
|
||||
.td-rank {
|
||||
width: 80rpx;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.td-points {
|
||||
width: 80rpx;
|
||||
text-align: right;
|
||||
flex-shrink: 0;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.td-points.positive {
|
||||
color: #1a56db;
|
||||
}
|
||||
|
||||
.td-points.negative {
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
/* ===== 空状态 ===== */
|
||||
.empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: 240rpx;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.empty-doc {
|
||||
width: 160rpx;
|
||||
height: 180rpx;
|
||||
background: #f5f5f5;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-top: 30rpx;
|
||||
gap: 14rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.empty-doc-top {
|
||||
width: 80rpx;
|
||||
height: 20rpx;
|
||||
background: #e8e8e8;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
|
||||
.empty-doc-line {
|
||||
width: 100rpx;
|
||||
height: 10rpx;
|
||||
background: #e8e8e8;
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
|
||||
.empty-doc-line.short {
|
||||
width: 70rpx;
|
||||
}
|
||||
|
||||
.empty-doc-shadow {
|
||||
width: 140rpx;
|
||||
height: 16rpx;
|
||||
background: radial-gradient(ellipse, rgba(0,0,0,0.08) 0%, transparent 70%);
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
/* ===== 加载提示 ===== */
|
||||
.loading-tip {
|
||||
text-align: center;
|
||||
padding: 24rpx 0;
|
||||
color: #ccc;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
|
||||
@ -56,6 +56,57 @@ async function loginWithMobilePassword({ mobile, password }) {
|
||||
};
|
||||
}
|
||||
|
||||
async function loginWithMobileCode({ mobile, verificationCode }) {
|
||||
const loginData = await request({
|
||||
path: API.loginByCode,
|
||||
method: 'POST',
|
||||
needAuth: false,
|
||||
data: {
|
||||
mobile,
|
||||
verificationCode
|
||||
},
|
||||
contentType: 'application/json'
|
||||
});
|
||||
|
||||
const app = getApp();
|
||||
const accessToken = pickString(loginData, 'accessToken');
|
||||
const refreshToken = pickString(loginData, 'refreshToken');
|
||||
const userInfo =
|
||||
loginData && loginData.userInfo && typeof loginData.userInfo === 'object'
|
||||
? loginData.userInfo
|
||||
: null;
|
||||
const openid = userInfo ? pickString(userInfo, 'openid') : '';
|
||||
|
||||
app.setAuth({
|
||||
accessToken,
|
||||
refreshToken,
|
||||
openid,
|
||||
userProfile: userInfo
|
||||
});
|
||||
|
||||
let myProfile = null;
|
||||
try {
|
||||
myProfile = await request({
|
||||
path: API.myProfile,
|
||||
method: 'GET',
|
||||
needAuth: true
|
||||
});
|
||||
} catch (e) {
|
||||
myProfile = null;
|
||||
}
|
||||
|
||||
if (myProfile && typeof myProfile === 'object') {
|
||||
app.setAuth({
|
||||
userProfile: myProfile
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
loginData,
|
||||
myProfile
|
||||
};
|
||||
}
|
||||
|
||||
async function sendVerificationCode(phone) {
|
||||
return request({
|
||||
path: API.sendVerificationCode,
|
||||
@ -130,12 +181,26 @@ async function fetchMyRegistrations({ pageNo = 1, pageSize = 10 } = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchMyPoints({ pageNo = 1, pageSize = 10 } = {}) {
|
||||
return request({
|
||||
path: API.myPoints,
|
||||
method: 'GET',
|
||||
needAuth: true,
|
||||
data: {
|
||||
PageNo: pageNo,
|
||||
PageSize: pageSize
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loginWithMobilePassword,
|
||||
loginWithMobileCode,
|
||||
sendVerificationCode,
|
||||
registerWithVerification,
|
||||
fetchMyProfile,
|
||||
fetchMyAuth,
|
||||
submitMyAuth,
|
||||
fetchMyRegistrations
|
||||
fetchMyRegistrations,
|
||||
fetchMyPoints
|
||||
};
|
||||
|
||||
@ -11,12 +11,17 @@ const API = {
|
||||
apply: '/api/eventclient/apply',
|
||||
|
||||
wechatLogin: '/api/oauth/login',
|
||||
loginByCode: '/api/oauth/login',
|
||||
oauthRegister: '/api/oauth/register',
|
||||
oauthResetPassword: '/api/oauth/reset-password',
|
||||
sendVerificationCode: '/api/sms/sendverificationcode',
|
||||
myProfile: '/api/my/profile',
|
||||
myAuth: '/api/my/auth',
|
||||
myRegistrations: '/api/my/registrations'
|
||||
myRegistrations: '/api/my/registrations',
|
||||
myPoints: '/api/my/points',
|
||||
myOrders: '/api/my/orders',
|
||||
myTeams: '/api/my/teams',
|
||||
myCertificates: '/api/my/certificates'
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
||||
@ -6,6 +6,14 @@ function formatDateTime(iso) {
|
||||
return s;
|
||||
}
|
||||
|
||||
function formatTime(iso) {
|
||||
if (!iso || typeof iso !== 'string') return '-';
|
||||
const s = iso.replace('T', ' ').replace('Z', '');
|
||||
// 返回 yyyy-MM-dd HH:mm 格式
|
||||
if (s.length >= 16) return s.slice(0, 16);
|
||||
return s;
|
||||
}
|
||||
|
||||
function eventStatusText(status) {
|
||||
if (status === 'Enrolling') return '报名中';
|
||||
if (status === 'Ongoing') return '比赛中';
|
||||
@ -26,6 +34,7 @@ function eventFormatText(v) {
|
||||
|
||||
module.exports = {
|
||||
formatDateTime,
|
||||
formatTime,
|
||||
eventStatusText,
|
||||
eventFormatText
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user