Files
sundynix-pets/pets-fe/utils/request.js
T
Blizzard 7093e6517d perf(fe): 修复 token 过期白屏、请求翻倍与失败伪装成空态
token 过期后小程序静默白屏(影响所有用户)
- JWT 有效期 7 天,过期后 40100 只 clearToken 不重新登录,而
  store.ready 的 promise 已缓存不会重跑,加上 23 处静默 catch,
  用户看到的是「什么都没有、也没有任何提示」,只能重启小程序
- request 层加 token 失效钩子:自动重新登录并重试一次(只重试一次,
  防止登录接口本身故障时死循环);store 注册钩子重置引导缓存
- 补 15s 请求超时、60s 上传超时,超时给明确文案(原先默认 60s 卡住无反馈)

请求翻倍
- 每个 tab 页同时用 store.subscribe 和 onShow→ready().then() 加载,
  而 ready() 内部 loadPets 会 notify 触发 subscriber,首次进入请求翻倍
- subscriber 改为只处理「切换宠物 / 数据变更」,首次加载交给 onShow
- 首页 bind:save 与组件实际触发的 saved 事件名不一致,onSheetSave
  从未生效;一并修正,并去掉关闭弹层时的全量重载

失败伪装成空态
- 社区请求失败时显示「还没有帖子,来发第一条吧」,把故障说成没数据。
  改为区分「加载失败(可点击重试)」「关注 tab 为空」「确实没有帖子」
- 新增 utils/ui.js 统一错误提示(同文案 3 秒内去重,避免并发失败刷屏),
  首页与记录页主数据加载失败不再静默

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 17:56:35 +08:00

91 lines
2.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const { BASE_URL } = require('./config.js');
const TOKEN_KEY = 'pets_token';
function getToken() {
return wx.getStorageSync(TOKEN_KEY) || '';
}
function setToken(t) {
wx.setStorageSync(TOKEN_KEY, t);
}
function clearToken() {
wx.removeStorageSync(TOKEN_KEY);
}
// token 失效时的重新登录钩子,由 store 注册(放在这里避免 request ← api ← store 循环依赖)
let reAuth = null;
function setReAuthHandler(fn) {
reAuth = fn;
}
const TIMEOUT = 15000;
// 统一请求:注入 token,解析 {code,message,data},成功返回 data,失败 reject(Error)。
// token 失效(40100)时自动重新登录并重试一次,避免 JWT 过期后小程序静默白屏。
function request(options, _retried) {
const { url, method = 'GET', data, header = {} } = options;
return new Promise((resolve, reject) => {
const token = getToken();
wx.request({
url: BASE_URL + url,
method,
data,
timeout: TIMEOUT,
header: Object.assign(
{ 'Content-Type': 'application/json' },
token ? { Authorization: 'Bearer ' + token } : {},
header,
),
success(res) {
const body = res.data;
if (body && typeof body === 'object' && 'code' in body) {
if (body.code === 0) return resolve(body.data);
if (body.code === 40100) {
clearToken();
// 只重试一次,防止登录接口本身出问题时无限循环
if (!_retried && reAuth) {
return reAuth()
.then(() => request(options, true).then(resolve, reject))
.catch(() => reject(new Error(body.message || '登录已过期,请重进小程序')));
}
}
return reject(new Error(body.message || '请求失败'));
}
resolve(body);
},
fail(err) {
const msg = (err && err.errMsg) || '网络错误';
reject(new Error(/timeout/i.test(msg) ? '网络超时,请稍后重试' : msg));
},
});
});
}
// 文件上传(multipart
function uploadFile(filePath) {
return new Promise((resolve, reject) => {
const token = getToken();
wx.uploadFile({
url: BASE_URL + '/api/upload',
filePath,
name: 'file',
timeout: 60000,
header: token ? { Authorization: 'Bearer ' + token } : {},
success(res) {
try {
const body = JSON.parse(res.data);
if (body.code === 0) return resolve(body.data);
reject(new Error(body.message || '上传失败'));
} catch (e) {
reject(new Error('上传响应解析失败'));
}
},
fail(err) {
reject(new Error((err && err.errMsg) || '上传失败'));
},
});
});
}
module.exports = { request, uploadFile, getToken, setToken, clearToken, setReAuthHandler };