update:报名&支付
This commit is contained in:
parent
c21de31c69
commit
44c46b78ea
@ -607,10 +607,174 @@ Page({
|
||||
},
|
||||
|
||||
/** 线上支付 */
|
||||
onPayOnline() {
|
||||
async onPayOnline() {
|
||||
const payload = this._validateAndGetPayload(2);
|
||||
if (!payload) return;
|
||||
this._submitApply(payload);
|
||||
|
||||
try {
|
||||
// 1. 获取 openid
|
||||
const openid = await this._getOpenId();
|
||||
if (!openid) {
|
||||
wx.showToast({ title: '获取 openid 失败', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 提交报名
|
||||
this.setData({ submitting: true });
|
||||
const registrationId = await this._submitApplyForPayment(payload);
|
||||
|
||||
// 3. 选择支付方式并获取支付参数
|
||||
await this._selectPaymentMethod(registrationId, openid, 2);
|
||||
|
||||
// 4. 获取支付信息
|
||||
const paymentInfo = await this._getPaymentInfo(registrationId, openid);
|
||||
|
||||
// 5. 调起微信支付
|
||||
await this._invokeWeChatPay(paymentInfo.wechatPayParams, registrationId);
|
||||
|
||||
} catch (error) {
|
||||
console.error('支付流程异常:', error);
|
||||
wx.showToast({
|
||||
title: error && error.message ? error.message : '支付失败,请稍后重试',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
this.setData({ submitting: false });
|
||||
}
|
||||
},
|
||||
|
||||
/** 获取 openid */
|
||||
async _getOpenId() {
|
||||
const app = getApp();
|
||||
const cached =
|
||||
(app && app.globalData && app.globalData.openid) || wx.getStorageSync('openid') || '';
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.login({
|
||||
success: (loginRes) => {
|
||||
const code = loginRes.code;
|
||||
if (!code) {
|
||||
reject(new Error('未获取到 code'));
|
||||
return;
|
||||
}
|
||||
|
||||
request({
|
||||
path: API.wechatOpenidByJsCode,
|
||||
method: 'POST',
|
||||
needAuth: false,
|
||||
data: { code },
|
||||
contentType: 'application/json'
|
||||
})
|
||||
.then((data) => {
|
||||
const oid =
|
||||
data && typeof data === 'object'
|
||||
? data.openId || data.OpenId || data.openid || ''
|
||||
: '';
|
||||
const openid = typeof oid === 'string' ? oid : '';
|
||||
if (openid && app && typeof app.setAuth === 'function') {
|
||||
app.setAuth({ openid });
|
||||
}
|
||||
if (openid) {
|
||||
resolve(openid);
|
||||
} else {
|
||||
reject(new Error('接口未返回 openid'));
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
},
|
||||
fail: () => {
|
||||
reject(new Error('wx.login 失败'));
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/** 提交报名(用于支付流程) */
|
||||
_submitApplyForPayment(payload) {
|
||||
return request({
|
||||
path: API.apply,
|
||||
method: 'POST',
|
||||
needAuth: true,
|
||||
data: payload,
|
||||
contentType: 'application/json'
|
||||
});
|
||||
},
|
||||
|
||||
/** 选择支付方式 */
|
||||
_selectPaymentMethod(registrationId, openId, paymentMethod) {
|
||||
return request({
|
||||
path: '/api/eventclient/select-payment-method',
|
||||
method: 'POST',
|
||||
needAuth: true,
|
||||
data: { registrationId, openId, paymentMethod },
|
||||
contentType: 'application/json'
|
||||
});
|
||||
},
|
||||
|
||||
/** 获取支付信息 */
|
||||
_getPaymentInfo(registrationId, openId) {
|
||||
return request({
|
||||
path: '/api/eventclient/payment-info',
|
||||
method: 'POST',
|
||||
needAuth: true,
|
||||
data: { registrationId, openId },
|
||||
contentType: 'application/json'
|
||||
});
|
||||
},
|
||||
|
||||
/** 调起微信支付 */
|
||||
_invokeWeChatPay(wechatPayParams, registrationId) {
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.requestPayment({
|
||||
...wechatPayParams,
|
||||
success: async () => {
|
||||
try {
|
||||
// 支付成功,同步支付状态
|
||||
await request({
|
||||
path: `/api/eventclient/sync-payment/${registrationId}`,
|
||||
method: 'POST',
|
||||
needAuth: true,
|
||||
data: {},
|
||||
contentType: 'application/json'
|
||||
});
|
||||
|
||||
wx.showToast({ title: '支付成功', icon: 'success' });
|
||||
setTimeout(() => {
|
||||
wx.switchTab({ url: '/pages/mine/mine' });
|
||||
}, 1500);
|
||||
resolve();
|
||||
} catch (err) {
|
||||
console.error('同步支付状态失败:', err);
|
||||
wx.showModal({
|
||||
title: '提示',
|
||||
content: '支付成功,但同步状态失败,请稍后在订单列表中查看',
|
||||
showCancel: false,
|
||||
success: () => {
|
||||
wx.switchTab({ url: '/pages/mine/mine' });
|
||||
}
|
||||
});
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('微信支付失败:', err);
|
||||
if (err.errMsg && err.errMsg.includes('cancel')) {
|
||||
wx.showToast({ title: '支付已取消', icon: 'none' });
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: err.errMsg || '支付失败,请稍后重试',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_submitApply(payload) {
|
||||
|
||||
@ -24,12 +24,10 @@ Page({
|
||||
wx.redirectTo({ url: '/subpackages/mine/login/login' });
|
||||
return;
|
||||
}
|
||||
this.loadProfile();
|
||||
this.resetAndLoad();
|
||||
},
|
||||
|
||||
onPullDownRefresh() {
|
||||
this.loadProfile();
|
||||
this.resetAndLoad()
|
||||
.finally(() => wx.stopPullDownRefresh());
|
||||
},
|
||||
@ -37,7 +35,6 @@ Page({
|
||||
onRefresh() {
|
||||
if (this.data.refresherTriggered) return;
|
||||
this.setData({ refresherTriggered: true });
|
||||
this.loadProfile();
|
||||
this.resetAndLoad()
|
||||
.finally(() => this.setData({ refresherTriggered: false }));
|
||||
},
|
||||
@ -102,7 +99,23 @@ Page({
|
||||
const formatted = rows.map((item, idx) => ({
|
||||
...item,
|
||||
id: item.id != null ? item.id : `${activeTab}-${pageNo}-${idx}`,
|
||||
timeStr: item.createdTime ? formatTime(item.createdTime) : (item.timeStr || '')
|
||||
// 赛事名称
|
||||
eventName: item.eventName || '',
|
||||
// 项目-组别展示,优先用 projectGroupDisplay,否则拼接 projectName + groupName
|
||||
projectGroupDisplay:
|
||||
item.projectGroupDisplay ||
|
||||
[item.projectName, item.groupName].filter(Boolean).join(' - ') ||
|
||||
'-',
|
||||
// 证书链接
|
||||
certificateUrl: item.certificateUrl || '',
|
||||
// 证书名称(用于查看证书时的标题)
|
||||
certificateName: item.certificateName || item.eventName || '证书',
|
||||
// 颁发时间
|
||||
timeStr: item.issuedTime
|
||||
? formatTime(item.issuedTime)
|
||||
: item.createdTime
|
||||
? formatTime(item.createdTime)
|
||||
: ''
|
||||
}));
|
||||
|
||||
const newRecords = this.data.records.concat(formatted);
|
||||
|
||||
@ -47,12 +47,12 @@
|
||||
</view>
|
||||
<view wx:for="{{records}}" wx:key="id" class="record-row">
|
||||
<view class="td td-event">
|
||||
<text class="event-name">{{item.title || item.eventTitle || '-'}}</text>
|
||||
<text class="event-time">{{item.timeStr || item.createdTimeStr || ''}}</text>
|
||||
<text class="event-name">{{item.eventName || '-'}}</text>
|
||||
<text class="event-time">{{item.timeStr}}</text>
|
||||
</view>
|
||||
<text class="td td-project">{{item.projectInfo || item.projectName || '-'}}</text>
|
||||
<text class="td td-project">{{item.projectGroupDisplay || '-'}}</text>
|
||||
<view class="td td-cert">
|
||||
<text class="cert-link" bindtap="onViewCert" data-url="{{item.certificateUrl || item.url}}" data-name="{{item.title || item.eventTitle || '证书'}}">查看证书</text>
|
||||
<text class="cert-link" bindtap="onViewCert" data-url="{{item.certificateUrl}}" data-name="{{item.certificateName}}">查看证书</text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
@ -1,51 +1,70 @@
|
||||
const auth = require('../../../utils/auth');
|
||||
const { formatTime } = require('../../../utils/format');
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
// orderStatus 枚举 → 显示文案 & 样式 key
|
||||
const STATUS_MAP = {
|
||||
Pending: { text: '待支付', key: 'pending' },
|
||||
Paid: { text: '交易成功', key: 'success' },
|
||||
Cancelled: { text: '已取消', key: 'cancelled' },
|
||||
Refunded: { text: '已退款', key: 'refunded' },
|
||||
Refunding: { text: '退款中', key: 'pending' }
|
||||
};
|
||||
|
||||
function formatOrder(item) {
|
||||
const statusInfo = STATUS_MAP[item.orderStatus] || { text: item.orderStatus || '-', key: 'default' };
|
||||
return {
|
||||
...item,
|
||||
statusText: statusInfo.text,
|
||||
statusKey: statusInfo.key,
|
||||
payTimeStr: item.paymentTime ? formatTime(item.paymentTime) : (item.createdTime ? formatTime(item.createdTime) : '-'),
|
||||
actualAmount: item.actualAmount != null ? item.actualAmount : '-',
|
||||
refundAmount: item.refundAmount != null && item.refundAmount > 0 ? item.refundAmount : ''
|
||||
};
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
loading: false,
|
||||
noMore: false,
|
||||
orders: [
|
||||
{
|
||||
id: 1,
|
||||
eventName: '2024上海市学生运动会跳绳比赛',
|
||||
statusText: '交易成功',
|
||||
statusKey: 'success',
|
||||
payTimeStr: '2025-08-05 16:26',
|
||||
actualAmount: '859',
|
||||
refundAmount: ''
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
eventName: '2024上海市学生运动会跳绳比赛',
|
||||
statusText: '交易取消',
|
||||
statusKey: 'cancelled',
|
||||
payTimeStr: '2025-08-05 16:26',
|
||||
actualAmount: '859',
|
||||
refundAmount: '859'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
eventName: '2024上海市学生运动会跳绳比赛',
|
||||
statusText: '交易成功',
|
||||
statusKey: 'success',
|
||||
payTimeStr: '2025-08-05 16:26',
|
||||
actualAmount: '859',
|
||||
refundAmount: ''
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
eventName: '2024上海市学生运动会跳绳比赛',
|
||||
statusText: '交易成功',
|
||||
statusKey: 'success',
|
||||
payTimeStr: '2025-08-05 16:26',
|
||||
actualAmount: '859',
|
||||
refundAmount: ''
|
||||
}
|
||||
]
|
||||
pageNo: 1,
|
||||
total: 0,
|
||||
orders: []
|
||||
},
|
||||
|
||||
onLoad() {},
|
||||
onLoad() {
|
||||
this.loadOrders(true);
|
||||
},
|
||||
|
||||
onPullDownRefresh() {
|
||||
wx.stopPullDownRefresh();
|
||||
this.loadOrders(true).finally(() => wx.stopPullDownRefresh());
|
||||
},
|
||||
|
||||
async loadOrders(refresh = false) {
|
||||
if (this.data.loading) return;
|
||||
const pageNo = refresh ? 1 : this.data.pageNo;
|
||||
if (!refresh && this.data.noMore) return;
|
||||
|
||||
this.setData({ loading: true });
|
||||
try {
|
||||
const res = await auth.fetchMyOrders({ pageNo, pageSize: PAGE_SIZE });
|
||||
const rows = (res && res.rows) || [];
|
||||
const total = (res && res.total) || 0;
|
||||
const formatted = rows.map(formatOrder);
|
||||
const newOrders = refresh ? formatted : [...this.data.orders, ...formatted];
|
||||
const noMore = newOrders.length >= total;
|
||||
this.setData({
|
||||
orders: newOrders,
|
||||
pageNo: pageNo + 1,
|
||||
total,
|
||||
noMore
|
||||
});
|
||||
} catch (err) {
|
||||
wx.showToast({ title: err?.message || '加载失败', icon: 'none' });
|
||||
} finally {
|
||||
this.setData({ loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
onBack() {
|
||||
@ -62,6 +81,6 @@ Page({
|
||||
},
|
||||
|
||||
onLoadMore() {
|
||||
// TODO: 分页加载
|
||||
this.loadOrders(false);
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "我的订单",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"enablePullDownRefresh": true,
|
||||
"usingComponents": {
|
||||
"mp-navigation-bar": "weui-miniprogram/navigation-bar/navigation-bar"
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
<view class="order-page">
|
||||
<mp-navigation-bar
|
||||
<!-- <mp-navigation-bar
|
||||
title="我的订单"
|
||||
background-color="#ffffff"
|
||||
color="#333"
|
||||
bind:click-left="onBack"
|
||||
left-arrow
|
||||
></mp-navigation-bar>
|
||||
></mp-navigation-bar> -->
|
||||
|
||||
<scroll-view
|
||||
class="list"
|
||||
|
||||
@ -1,18 +1,48 @@
|
||||
const auth = require('../../../utils/auth');
|
||||
|
||||
const PAGE_SIZE = 20;
|
||||
|
||||
Page({
|
||||
data: {
|
||||
orgName: '上海市跳绳键球协会', // 所在单位,后续接口替换
|
||||
orgName: '',
|
||||
loading: false,
|
||||
teams: [
|
||||
{ id: 1, name: '队伍名称', memberCount: 9, pendingCount: 0 },
|
||||
{ id: 2, name: '队伍名称', memberCount: 91, pendingCount: 0 },
|
||||
{ id: 3, name: '队伍名称', memberCount: 9, pendingCount: 3 }
|
||||
]
|
||||
noMore: false,
|
||||
pageNo: 1,
|
||||
total: 0,
|
||||
teams: []
|
||||
},
|
||||
|
||||
onLoad() {},
|
||||
onLoad() {
|
||||
this.loadTeams(true);
|
||||
},
|
||||
|
||||
onPullDownRefresh() {
|
||||
wx.stopPullDownRefresh();
|
||||
this.loadTeams(true).finally(() => wx.stopPullDownRefresh());
|
||||
},
|
||||
|
||||
async loadTeams(refresh = false) {
|
||||
if (this.data.loading) return;
|
||||
const pageNo = refresh ? 1 : this.data.pageNo;
|
||||
if (!refresh && this.data.noMore) return;
|
||||
|
||||
this.setData({ loading: true });
|
||||
try {
|
||||
const res = await auth.fetchMyTeams({ pageNo, pageSize: PAGE_SIZE });
|
||||
const rows = (res && res.rows) || [];
|
||||
const total = (res && res.total) || 0;
|
||||
const newTeams = refresh ? rows : [...this.data.teams, ...rows];
|
||||
const noMore = newTeams.length >= total;
|
||||
this.setData({
|
||||
teams: newTeams,
|
||||
pageNo: pageNo + 1,
|
||||
total,
|
||||
noMore
|
||||
});
|
||||
} catch (err) {
|
||||
wx.showToast({ title: err?.message || '加载失败', icon: 'none' });
|
||||
} finally {
|
||||
this.setData({ loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
onBack() {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "我的团队",
|
||||
"enablePullDownRefresh": true,
|
||||
"usingComponents": {
|
||||
"mp-navigation-bar": "weui-miniprogram/navigation-bar/navigation-bar"
|
||||
|
||||
@ -1,12 +1,4 @@
|
||||
<view class="team-page">
|
||||
<!-- 导航栏 -->
|
||||
<mp-navigation-bar
|
||||
title="我的团队"
|
||||
background-color="#ffffff"
|
||||
color="#333"
|
||||
bind:click-left="onBack"
|
||||
left-arrow
|
||||
></mp-navigation-bar>
|
||||
|
||||
<!-- 所在单位卡片 -->
|
||||
<view class="org-card" wx:if="{{orgName}}">
|
||||
|
||||
@ -4,6 +4,10 @@
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
view{
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* ===== 单位卡片 ===== */
|
||||
.org-card {
|
||||
position: relative;
|
||||
@ -109,12 +113,30 @@
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.team-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.team-name {
|
||||
font-size: 30rpx;
|
||||
color: #1a1a1a;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.leader-tag {
|
||||
font-size: 20rpx;
|
||||
color: #1a56db;
|
||||
background: rgba(26, 86, 219, 0.1);
|
||||
border: 1rpx solid rgba(26, 86, 219, 0.3);
|
||||
border-radius: 8rpx;
|
||||
padding: 2rpx 12rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.team-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -146,3 +168,15 @@
|
||||
color: #999;
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
/* ===== 加载更多 ===== */
|
||||
.list-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 32rpx 0;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
@ -259,6 +259,30 @@ async function fetchMyPoints({
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchMyTeams({ pageNo = 1, pageSize = 20 } = {}) {
|
||||
return request({
|
||||
path: API.myTeams,
|
||||
method: 'GET',
|
||||
needAuth: true,
|
||||
data: {
|
||||
PageNo: pageNo,
|
||||
PageSize: pageSize
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchMyOrders({ pageNo = 1, pageSize = 10 } = {}) {
|
||||
return request({
|
||||
path: API.myOrders,
|
||||
method: 'GET',
|
||||
needAuth: true,
|
||||
data: {
|
||||
PageNo: pageNo,
|
||||
PageSize: pageSize
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchMyCertificates({ pageNo = 1, pageSize = 10, type } = {}) {
|
||||
const data = {
|
||||
PageNo: pageNo,
|
||||
@ -286,6 +310,8 @@ module.exports = {
|
||||
submitMyAuth,
|
||||
fetchMyRegistrations,
|
||||
fetchMyPoints,
|
||||
fetchMyTeams,
|
||||
fetchMyOrders,
|
||||
fetchMyCertificates,
|
||||
refreshAccessToken
|
||||
};
|
||||
|
||||
188
utils/http.js
188
utils/http.js
@ -1,15 +1,27 @@
|
||||
const { getBaseUrl } = require('./constants');
|
||||
|
||||
function withAuthHeader(needAuth, header) {
|
||||
// Token刷新状态和请求队列
|
||||
let isRefreshing = false;
|
||||
let requestQueue = [];
|
||||
|
||||
function withAuthHeader(needAuth, header, useRefreshTokenOnly = false) {
|
||||
const app = getApp();
|
||||
if (!needAuth) return header;
|
||||
const token = app && app.globalData ? app.globalData.accessToken : '';
|
||||
const accessToken = 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}`;
|
||||
|
||||
header = header || {};
|
||||
|
||||
if (useRefreshTokenOnly && refreshToken) {
|
||||
// 只使用 refresh token 模式(用于刷新)
|
||||
header['X-Authorization'] = `Bearer ${refreshToken}`;
|
||||
} else if (accessToken) {
|
||||
// 正常模式:使用 access token
|
||||
header.Authorization = `Bearer ${accessToken}`;
|
||||
// 同时带上 refresh token,供后端在需要时自动刷新
|
||||
if (refreshToken) {
|
||||
header['X-Authorization'] = `Bearer ${refreshToken}`;
|
||||
}
|
||||
}
|
||||
return header;
|
||||
}
|
||||
@ -37,18 +49,68 @@ function getHeaderValue(headers, headerName) {
|
||||
|
||||
function tryUpdateAccessTokenFromResponseHeaders(res) {
|
||||
// wx.request 的返回头在 res.header(部分环境也可能是 res.headers)
|
||||
console.log( 'res', res);
|
||||
const headers = (res && res.header) || (res && res.headers) || null;
|
||||
const newAccessToken = getHeaderValue(headers, 'x-access-token');
|
||||
if (!newAccessToken) return;
|
||||
|
||||
// 从响应头获取新的 token
|
||||
const newAccessToken = getHeaderValue(headers, 'access-token') || getHeaderValue(headers, 'x-access-token');
|
||||
const newRefreshToken = getHeaderValue(headers, 'x-access-token') || getHeaderValue(headers, 'x-refresh-token');
|
||||
|
||||
if (!newAccessToken && !newRefreshToken) return false;
|
||||
|
||||
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;
|
||||
const authData = {};
|
||||
let hasUpdate = false;
|
||||
|
||||
if (newAccessToken && newAccessToken !== 'invalid_token') {
|
||||
authData.accessToken = newAccessToken;
|
||||
hasUpdate = true;
|
||||
}
|
||||
if (newRefreshToken && newRefreshToken !== 'invalid_token') {
|
||||
authData.refreshToken = newRefreshToken;
|
||||
hasUpdate = true;
|
||||
}
|
||||
|
||||
if (hasUpdate) {
|
||||
if (app && typeof app.setAuth === 'function') {
|
||||
app.setAuth(authData);
|
||||
} else {
|
||||
if (authData.accessToken) {
|
||||
wx.setStorageSync('accessToken', authData.accessToken);
|
||||
if (app && app.globalData) app.globalData.accessToken = authData.accessToken;
|
||||
}
|
||||
if (authData.refreshToken) {
|
||||
wx.setStorageSync('refreshToken', authData.refreshToken);
|
||||
if (app && app.globalData) app.globalData.refreshToken = authData.refreshToken;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果正在刷新Token且获取到了新Token,执行队列中的请求
|
||||
if (isRefreshing && newAccessToken && newAccessToken !== 'invalid_token') {
|
||||
isRefreshing = false;
|
||||
requestQueue.forEach((callback) => callback(newAccessToken));
|
||||
requestQueue = [];
|
||||
return true;
|
||||
}
|
||||
|
||||
return hasUpdate;
|
||||
}
|
||||
|
||||
function clearAllTokensAndRedirect() {
|
||||
wx.clearStorageSync();
|
||||
const app = getApp();
|
||||
if (app && app.globalData) {
|
||||
app.globalData.accessToken = '';
|
||||
app.globalData.refreshToken = '';
|
||||
}
|
||||
wx.showModal({
|
||||
title: '登录失效',
|
||||
content: '您的登录已失效,请重新登录。',
|
||||
showCancel: false,
|
||||
complete() {
|
||||
wx.navigateTo({ url: '/subpackages/mine/login/login' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function request({
|
||||
@ -58,14 +120,18 @@ function request({
|
||||
needAuth = false,
|
||||
contentType = 'application/json',
|
||||
// Internal flag: prevent infinite retry when refresh token也失败
|
||||
__retry = false
|
||||
__retry = false,
|
||||
// Internal flag: 标记这是刷新token的重试请求
|
||||
__refreshRetry = false
|
||||
}) {
|
||||
const baseUrl = getBaseUrl();
|
||||
const url = `${baseUrl}${path}`;
|
||||
|
||||
// 正常请求:同时带上 access token 和 refresh token
|
||||
// 刷新重试请求:只使用 refresh token
|
||||
const header = withAuthHeader(needAuth, {
|
||||
'content-type': contentType
|
||||
});
|
||||
}, __refreshRetry);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.request({
|
||||
@ -75,31 +141,65 @@ function request({
|
||||
header: header,
|
||||
success: async (res) => {
|
||||
// 如果后端在 Response Headers 下发新的 token,就替换本地 token
|
||||
tryUpdateAccessTokenFromResponseHeaders(res);
|
||||
const tokenUpdated = tryUpdateAccessTokenFromResponseHeaders(res);
|
||||
const statusCode = res && res.data && res.data.statusCode ? res.data.statusCode : 0;
|
||||
const payload = res && res.data !== undefined ? res.data : null;
|
||||
|
||||
// ── 401 未授权 ──────────────────────────────────────────────
|
||||
if (statusCode === 401) {
|
||||
wx.clearStorageSync();
|
||||
const app = getApp();
|
||||
if (app && app.globalData) {
|
||||
app.globalData.accessToken = '';
|
||||
app.globalData.refreshToken = '';
|
||||
// ── 401 未授权:尝试刷新Token ──
|
||||
if (statusCode === 401 && needAuth && !__retry) {
|
||||
// 如果已经在刷新Token,将请求加入队列
|
||||
if (isRefreshing) {
|
||||
requestQueue.push((newToken) => {
|
||||
resolve(request({ path, method, data, needAuth, contentType, __retry: true }));
|
||||
});
|
||||
return;
|
||||
}
|
||||
wx.showModal({
|
||||
title: '登录失效',
|
||||
content: '您的登录已失效,请重新登录。',
|
||||
showCancel: false,
|
||||
complete() {
|
||||
wx.navigateTo({ url: '/subpackages/mine/login/login' });
|
||||
|
||||
// 开始刷新Token
|
||||
// 重发原请求
|
||||
isRefreshing = true;
|
||||
|
||||
try {
|
||||
// 检查是否有 refresh token
|
||||
const app = getApp();
|
||||
const refreshToken = app && app.globalData ? app.globalData.refreshToken : '';
|
||||
if (!refreshToken) {
|
||||
throw new Error('No refresh token available');
|
||||
}
|
||||
});
|
||||
|
||||
// 重发请求
|
||||
await request({
|
||||
path,
|
||||
method,
|
||||
data,
|
||||
needAuth,
|
||||
contentType,
|
||||
__retry: false,
|
||||
__refreshRetry: true
|
||||
});
|
||||
|
||||
// 刷新成功,现在重试原请求
|
||||
isRefreshing = false;
|
||||
resolve(request({ path, method, data, needAuth, contentType, __retry: true }));
|
||||
return;
|
||||
} catch (refreshErr) {
|
||||
// 刷新失败,清除Token并跳转登录
|
||||
isRefreshing = false;
|
||||
requestQueue = [];
|
||||
clearAllTokensAndRedirect();
|
||||
reject(new Error('Unauthorized'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 401 未授权(重试后仍然失败) ──
|
||||
if (statusCode === 401) {
|
||||
clearAllTokensAndRedirect();
|
||||
reject(new Error('Unauthorized'));
|
||||
return;
|
||||
}
|
||||
|
||||
// ── 403 禁止访问 ─────────────────────────────────────────────
|
||||
// ── 403 禁止访问 ──
|
||||
if (statusCode === 403) {
|
||||
wx.showToast({ title: '无访问权限', icon: 'none' });
|
||||
reject(new Error('Forbidden'));
|
||||
@ -141,27 +241,7 @@ 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);
|
||||
}
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user