From a99924c229ca7a64432aab559a2c19b56f646644 Mon Sep 17 00:00:00 2001 From: ltx <1006158920@qq.com> Date: Fri, 3 Apr 2026 17:57:08 +0800 Subject: [PATCH] =?UTF-8?q?update=EF=BC=9Aadd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/mine/mine.js | 3 +- pages/mine/mine.json | 6 +- pages/mine/mine.wxml | 59 +++++----- pages/mine/mine.wxss | 147 ++++++++++++++---------- subpackages/mine/auth/auth.wxml | 25 ++-- subpackages/mine/auth/auth.wxss | 14 +++ subpackages/mine/login/login.js | 2 +- subpackages/mine/login/login.wxss | 14 +-- subpackages/mine/points/points.js | 10 +- subpackages/mine/register/register.wxss | 16 +-- utils/auth.js | 75 ++++++++++-- utils/constants.js | 2 + utils/http.js | 58 +++++++++- 13 files changed, 293 insertions(+), 138 deletions(-) diff --git a/pages/mine/mine.js b/pages/mine/mine.js index 83467d4..a81cf29 100644 --- a/pages/mine/mine.js +++ b/pages/mine/mine.js @@ -46,7 +46,8 @@ Page({ this.setData({ profile: p, maskedPhone: maskPhone(phone), - authStatusText: authStatusText(st) + authStatusText: authStatusText(st), + authStatus: st }); }) .catch(() => {}); diff --git a/pages/mine/mine.json b/pages/mine/mine.json index 68522ed..4b5d0ce 100644 --- a/pages/mine/mine.json +++ b/pages/mine/mine.json @@ -1,4 +1,8 @@ { "navigationStyle": "custom", - "enablePullDownRefresh": true + "enablePullDownRefresh": true, + "usingComponents": { + "mp-navigation-bar": "weui-miniprogram/navigation-bar/navigation-bar", + "mp-icon": "weui-miniprogram/icon/icon" + } } diff --git a/pages/mine/mine.wxml b/pages/mine/mine.wxml index 6e246f9..41ce1b2 100644 --- a/pages/mine/mine.wxml +++ b/pages/mine/mine.wxml @@ -5,32 +5,25 @@ - - - - - 我的 - - + + + + + - + + - 📷 + {{maskedPhone}} - - @@ -45,39 +38,39 @@ {{profile.certificateCount || 0}} - 我的证书 + 我的证书 - 身份认证 点击进行身份认证 - - 🛡 + + {{authStatusText}} - - - - - - 我的订单 + + + + + 我的订单 + › - - - 参赛记录 + + + 参赛记录 + › - - - 我的团队 + + + 我的团队 + › - - + \ No newline at end of file diff --git a/pages/mine/mine.wxss b/pages/mine/mine.wxss index 4820ea5..8bb332c 100644 --- a/pages/mine/mine.wxss +++ b/pages/mine/mine.wxss @@ -39,52 +39,44 @@ /* ====== 已登录页面 ====== */ .mine-page { min-height: 100vh; - background: #f0f2f5; + background: #fff; } /* ====== 蓝色头部 ====== */ .header { position: relative; width: 100%; - overflow: visible; + height: 340rpx; + overflow: hidden; + border-radius: 0 0 100rpx 100rpx; + margin-bottom: 40rpx; } .header-bg { position: absolute; top: 0; left: 0; - right: 0; - height: 520rpx; - background: linear-gradient(180deg, #1a3a8a 0%, #1a57b2 100%); - border-radius: 0 0 40rpx 40rpx; + width: 100%; + height: 100%; } -.status-bar { - height: env(safe-area-inset-top, 44px); - position: relative; -} - -.nav-bar { - position: relative; - height: 88rpx; - display: flex; - align-items: center; - justify-content: center; -} - -.nav-title { - font-size: 34rpx; - font-weight: 600; - color: #ffffff; +.header-mask { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(26, 86, 219, 0.55); } /* ====== 头像区 ====== */ .profile-section { - position: relative; display: flex; flex-direction: column; align-items: center; - padding: 20rpx 0 40rpx; + /* padding: 20rpx 0 40rpx; */ + margin-bottom: 80rpx; + margin-top: -70px; } .avatar-wrap { @@ -97,17 +89,17 @@ width: 160rpx; height: 160rpx; border-radius: 50%; - border: 6rpx solid rgba(255, 255, 255, 0.4); + border: 2rpx solid rgba(255, 255, 255, 0.4); } .camera-icon { position: absolute; - right: 4rpx; - bottom: 4rpx; - width: 48rpx; - height: 48rpx; + right: -4rpx; + bottom: -4rpx; + width: 44rpx; + height: 44rpx; background: #1a57b2; - border: 4rpx solid #fff; + /* border: 4rpx solid #fff; */ border-radius: 50%; display: flex; align-items: center; @@ -118,12 +110,15 @@ font-size: 24rpx; } +.auth-badge-icon { + width: 40rpx; + height: 40rpx; +} .phone-text { margin-top: 16rpx; - font-size: 32rpx; + font-size: 28rpx; font-weight: 600; - color: #ffffff; - letter-spacing: 2rpx; + color: #000; } /* ====== 内容区 ====== */ @@ -144,13 +139,21 @@ flex: 1; background: #ffffff; border-radius: 16rpx; - padding: 28rpx 24rpx; + padding: 6rpx 24rpx; display: flex; align-items: center; justify-content: space-between; box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05); } +.stat-card:first-child { + background: linear-gradient(135deg, #A6ADEF 0%, #00000000 100%); +} + +.stat-card:last-child { + background: linear-gradient(135deg, #E5CD6790 0%, #00000000 100%); +} + .stat-left { display: flex; flex-direction: column; @@ -165,20 +168,22 @@ .stat-label { font-size: 24rpx; - color: #999; + color: #313666; margin-top: 8rpx; } .stat-icon { - width: 80rpx; - height: 80rpx; + width: 154rpx; + height: 154rpx; flex-shrink: 0; } /* ====== 身份认证条 ====== */ .auth-bar { margin-top: 20rpx; - background: linear-gradient(135deg, #1a3a8a 0%, #1a57b2 100%); + /* background: linear-gradient(135deg, #1a3a8a 0%, #1a57b2 100%); */ + background: url('https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/event_miniapp/idcard-bg.png'); + background-size: 100% 100%; border-radius: 16rpx; padding: 28rpx 30rpx; display: flex; @@ -207,47 +212,65 @@ display: flex; align-items: center; gap: 8rpx; - background: rgba(255, 255, 255, 0.2); - border-radius: 30rpx; - padding: 10rpx 24rpx; + background: linear-gradient(135deg, #ECEBEB 0%, #AEAEAE 100%); + border-radius: 34rpx; + width: 180rpx; + height: 64rpx; + display: flex; + align-items: center; + justify-content: center; + /* padding: 10rpx 24rpx; */ } -.auth-badge-icon { - font-size: 28rpx; +.auth-badge-y { + background: linear-gradient(135deg, #E5A84D 0%, #CC8213 100%); } + + .auth-badge-text { font-size: 24rpx; color: #ffffff; white-space: nowrap; } -/* ====== 功能入口卡 ====== */ -.func-card { +/* ====== 功能入口:三个独立阴影卡片 ====== */ +.func-list { 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; + gap: 20rpx; +} + +.func-menu-card { + display: flex; + flex-direction: row; align-items: center; - gap: 14rpx; + background: #ffffff; + border-radius: 16rpx; + padding: 28rpx 28rpx 28rpx 24rpx; + box-shadow: 0 8rpx 28rpx rgba(26, 87, 178, 0.08), 0 2rpx 8rpx rgba(0, 0, 0, 0.04); } -.func-icon { - width: 64rpx; - height: 64rpx; +.func-menu-icon { + width: 48rpx; + height: 48rpx; + flex-shrink: 0; } -.func-text { +.func-menu-text { + flex: 1; + margin-left: 24rpx; font-size: 24rpx; - color: #333; + color: #1a1a1a; + font-weight: 500; +} + +.func-menu-arrow { + flex-shrink: 0; + font-size: 40rpx; + line-height: 1; + color: #c8c8c8; + font-weight: 300; } diff --git a/subpackages/mine/auth/auth.wxml b/subpackages/mine/auth/auth.wxml index 0b9def2..0557557 100644 --- a/subpackages/mine/auth/auth.wxml +++ b/subpackages/mine/auth/auth.wxml @@ -22,16 +22,18 @@ - - + + 上传身份证人像面 - - + + 上传身份证国徽面 @@ -41,8 +43,7 @@ - - + 上传护照 @@ -53,17 +54,17 @@ 姓名 - + 身份证号 - + 出生日期 - + {{birthDate || '请选择'}} @@ -77,10 +78,10 @@ 联系电话 - - + + {{countdown>0 ? countdown + 's' : '发送验证码'}} - + diff --git a/subpackages/mine/auth/auth.wxss b/subpackages/mine/auth/auth.wxss index 93ec013..19b940d 100644 --- a/subpackages/mine/auth/auth.wxss +++ b/subpackages/mine/auth/auth.wxss @@ -2,6 +2,10 @@ page { background: #f5f7fa; } +view{ + box-sizing: border-box; +} + .auth-page { min-height: 100vh; display: flex; @@ -73,6 +77,11 @@ page { overflow: hidden; } +.ph-img { + width: 280rpx; + height: 160rpx; +} + .upload-card { padding: 22rpx; } @@ -84,6 +93,8 @@ page { .upload-row.single { justify-content: center; + width: 70%; + margin-left: 15%; } .upload-box { @@ -116,6 +127,9 @@ page { background: #eef2ff; position: relative; overflow: hidden; + display: flex; + align-items: center; + justify-content: center; } .ph-lines { diff --git a/subpackages/mine/login/login.js b/subpackages/mine/login/login.js index ac246f8..4e80b8b 100644 --- a/subpackages/mine/login/login.js +++ b/subpackages/mine/login/login.js @@ -158,7 +158,7 @@ Page({ .then(() => { wx.showToast({ title: '登录成功', icon: 'success' }); setTimeout(() => { - wx.redirectTo({ url: '/pages/mine/mine' }); + wx.switchTab({ url: '/pages/mine/mine' }); }, 400); }) .catch((err) => { diff --git a/subpackages/mine/login/login.wxss b/subpackages/mine/login/login.wxss index 9312324..3132b3d 100644 --- a/subpackages/mine/login/login.wxss +++ b/subpackages/mine/login/login.wxss @@ -8,7 +8,7 @@ .header { position: relative; width: 100%; - height: 440rpx; + height: 360rpx; overflow: hidden; border-radius: 0 0 100rpx 100rpx; margin-bottom: 40rpx; @@ -28,7 +28,7 @@ left: 0; width: 100%; height: 100%; - background: rgba(26, 86, 219, 0.55); + background: rgba(26, 87, 178, 0.85); } /* 导航栏 */ @@ -80,17 +80,17 @@ } .welcome-text { - font-size: 48rpx; + font-size: 46rpx; color: #ffffff; /* font-weight: 700; */ letter-spacing: 2rpx; - margin-right: 30rpx; + margin-right: 50rpx; } .logo-img { - width: 108rpx; - height: 108rpx; - border-radius: 20rpx; + width: 100rpx; + height: 100rpx; + border-radius: 40rpx; background: #fff; padding: 10rpx; } diff --git a/subpackages/mine/points/points.js b/subpackages/mine/points/points.js index 8c833c8..9140c0f 100644 --- a/subpackages/mine/points/points.js +++ b/subpackages/mine/points/points.js @@ -69,14 +69,12 @@ Page({ const { pageNo, activeTab } = this.data; return auth - .fetchMyPoints({ pageNo, pageSize: PAGE_SIZE }) + .fetchMyPoints({ pageNo, pageSize: PAGE_SIZE, type: activeTab }) .then((result) => { const res = result || {}; - const allRows = res.rows || []; - // 按类型筛选: type 1=获取 2=消耗 - const filtered = allRows.filter((item) => item.type === activeTab); + const allRows = res.rows || res.list || res.items || []; - const formatted = filtered.map((item) => ({ + const formatted = allRows.map((item) => ({ ...item, createdTimeStr: item.createdTime ? formatTime(item.createdTime) : '', projectInfo: item.projectInfo || item.projectName || '', @@ -84,7 +82,7 @@ Page({ })); const newRecords = this.data.records.concat(formatted); - const totalRows = res.total || 0; + const totalRows = res.total || res.totalCount || 0; const currentTotal = pageNo * PAGE_SIZE; this.setData({ diff --git a/subpackages/mine/register/register.wxss b/subpackages/mine/register/register.wxss index 3fb3bc3..faa841d 100644 --- a/subpackages/mine/register/register.wxss +++ b/subpackages/mine/register/register.wxss @@ -8,7 +8,7 @@ .header { position: relative; width: 100%; - height: 440rpx; + height: 360rpx; overflow: hidden; border-radius: 0 0 100rpx 100rpx; margin-bottom: 40rpx; @@ -31,7 +31,8 @@ left: 0; width: 100%; height: 100%; - background: rgba(26, 86, 219, 0.55); + background: rgba(26, 87, 178, 0.85); + } /* 导航栏 */ @@ -82,16 +83,17 @@ } .welcome-text { - font-size: 48rpx; + font-size: 46rpx; color: #ffffff; + /* font-weight: 700; */ letter-spacing: 2rpx; - margin-right: 30rpx; + margin-right: 50rpx; } .logo-img { - width: 108rpx; - height: 108rpx; - border-radius: 20rpx; + width: 100rpx; + height: 100rpx; + border-radius: 40rpx; background: #fff; padding: 10rpx; } diff --git a/utils/auth.js b/utils/auth.js index 02d0d1b..828b920 100644 --- a/utils/auth.js +++ b/utils/auth.js @@ -5,6 +5,49 @@ function pickString(obj, key) { return obj && typeof obj[key] === 'string' ? obj[key] : ''; } +let refreshPromise = null; + +async function refreshAccessToken() { + if (refreshPromise) return refreshPromise; + + const app = getApp(); + const storedRefreshToken = wx.getStorageSync('refreshToken') || ''; + const globalRefreshToken = app && app.globalData ? app.globalData.refreshToken : ''; + const refreshToken = typeof globalRefreshToken === 'string' && globalRefreshToken.length > 0 ? globalRefreshToken : storedRefreshToken; + + if (!refreshToken) throw new Error('refreshAccessToken: missing refreshToken'); + + refreshPromise = (async () => { + const tokenData = await request({ + path: API.oauthRefreshToken, + method: 'POST', + needAuth: false, + data: { refreshToken }, + contentType: 'application/json' + }); + + const accessToken = pickString(tokenData, 'accessToken'); + const nextRefreshToken = pickString(tokenData, 'refreshToken') || refreshToken; + + if (!accessToken) throw new Error('refreshAccessToken: missing accessToken'); + + app.setAuth({ + accessToken, + refreshToken: nextRefreshToken + }); + + return { + accessToken, + refreshToken: nextRefreshToken, + tokenData + }; + })().finally(() => { + refreshPromise = null; + }); + + return refreshPromise; +} + async function loginWithMobilePassword({ mobile, password }) { const loginData = await request({ path: API.wechatLogin, @@ -140,7 +183,7 @@ async function resetPassword({ mobile, password, verificationCode }) { path: API.oauthResetPassword, method: 'POST', needAuth: false, - data: { mobile, password, verificationCode }, + data: { mobile, newPassword: password, verificationCode }, contentType: 'application/json' }); } @@ -187,15 +230,32 @@ async function fetchMyRegistrations({ pageNo = 1, pageSize = 10 } = {}) { }); } -async function fetchMyPoints({ pageNo = 1, pageSize = 10 } = {}) { +async function fetchMyPoints({ + pageNo = 1, + pageSize = 10, + // 新接口参数(按 swagger):1=获取,2=消耗,不传查全部 + type, + // 单位认证ID筛选,不传查全部 + merchantCertificationId, + // 兼容旧调用方:也可能传 Type / MerchantCertificationId + Type, + MerchantCertificationId +} = {}) { + const t = type != null ? type : Type; + const mcId = merchantCertificationId != null ? merchantCertificationId : MerchantCertificationId; + + const data = { + PageNo: pageNo, + PageSize: pageSize + }; + if (t != null && t !== '') data.Type = t; + if (mcId != null && mcId !== '') data.MerchantCertificationId = mcId; + return request({ path: API.myPoints, method: 'GET', needAuth: true, - data: { - PageNo: pageNo, - PageSize: pageSize - } + data }); } @@ -226,5 +286,6 @@ module.exports = { submitMyAuth, fetchMyRegistrations, fetchMyPoints, - fetchMyCertificates + fetchMyCertificates, + refreshAccessToken }; diff --git a/utils/constants.js b/utils/constants.js index 66a279f..fdf1654 100644 --- a/utils/constants.js +++ b/utils/constants.js @@ -12,6 +12,8 @@ const API = { wechatLogin: '/api/oauth/login', loginByCode: '/api/oauth/login', + // Refresh token endpoint (请按后端实际接口路径修改) + oauthRefreshToken: '/api/oauth/refresh-token', oauthRegister: '/api/oauth/register', oauthResetPassword: '/api/oauth/reset-password', sendVerificationCode: '/api/sms/sendverificationcode', diff --git a/utils/http.js b/utils/http.js index 90e1026..14befa9 100644 --- a/utils/http.js +++ b/utils/http.js @@ -4,9 +4,12 @@ function withAuthHeader(needAuth, header) { const app = getApp(); if (!needAuth) return header; const token = app && app.globalData ? app.globalData.accessToken : ''; + const refreshToken = app && app.globalData ? app.globalData.refreshToken : ''; if (typeof token === 'string' && token.length > 0) { header = header || {}; header.Authorization = `Bearer ${token}`; + header['x-access-token'] = `Bearer ${token}`; + header['x-refresh-token'] = `Bearer ${refreshToken}`; } return header; } @@ -20,12 +23,41 @@ function normalizeError(payload) { return new Error(JSON.stringify(payload)); } +function getHeaderValue(headers, headerName) { + if (!headers || typeof headers !== 'object') return ''; + const target = String(headerName || '').toLowerCase(); + for (const k in headers) { + if (Object.prototype.hasOwnProperty.call(headers, k) && String(k).toLowerCase() === target) { + const v = headers[k]; + return typeof v === 'string' ? v : ''; + } + } + return ''; +} + +function tryUpdateAccessTokenFromResponseHeaders(res) { + // wx.request 的返回头在 res.header(部分环境也可能是 res.headers) + const headers = (res && res.header) || (res && res.headers) || null; + const newAccessToken = getHeaderValue(headers, 'x-access-token'); + if (!newAccessToken) return; + + const app = getApp(); + if (app && typeof app.setAuth === 'function') { + app.setAuth({ accessToken: newAccessToken }); + } else { + wx.setStorageSync('accessToken', newAccessToken); + if (app && app.globalData) app.globalData.accessToken = newAccessToken; + } +} + function request({ path, method = 'GET', data = {}, needAuth = false, - contentType = 'application/json' + contentType = 'application/json', + // Internal flag: prevent infinite retry when refresh token也失败 + __retry = false }) { const baseUrl = getBaseUrl(); const url = `${baseUrl}${path}`; @@ -41,6 +73,9 @@ function request({ data: method.toUpperCase() === 'GET' ? data : data, header: header, success(res) { + // 如果后端在 Response Headers 下发新的 token,就替换本地 token + tryUpdateAccessTokenFromResponseHeaders(res); + const payload = res && res.data !== undefined ? res.data : null; // Most backend uses RESTfulResult wrapper @@ -54,6 +89,27 @@ function request({ resolve(payload); }, fail(err) { + if (err && err.statusCode === 401 && needAuth && !__retry) { + // 401: try refresh token, then retry the original request once. + const originalErr = err; + try { + const auth = require('./auth'); + if (auth && typeof auth.refreshAccessToken === 'function') { + auth + .refreshAccessToken() + .then(() => { + return request({ path, method, data, needAuth, contentType, __retry: true }); + }) + .then(resolve) + .catch(() => { + reject(originalErr); + }); + return; + } + } catch (e) { + // ignore and fall through to rejecting original error + } + } reject(err); } });