v3.5.1 版本更新
CI / verify (pull_request) Has been cancelled

This commit is contained in:
2026-08-19 15:54:51 +08:00
parent 7dd5fdfb12
commit 233ed0af3b
103 changed files with 5764 additions and 185 deletions
+23 -4
View File
@@ -202,7 +202,7 @@ async function rawRequest<T>(
if (authToken) headers.Authorization = `Bearer ${authToken}`;
const res = await fetch(`${apiBase}${path}`, { ...options, headers });
let json: { code: number; message?: string; data?: T };
let json: { code: number; message?: string; data?: T; reason?: string };
try {
json = await res.json();
} catch {
@@ -213,8 +213,12 @@ async function rawRequest<T>(
throw err;
}
if (json.code !== 0) {
const err = new Error(json.message || '请求失败') as Error & { status?: number };
const err = new Error(json.message || '请求失败') as Error & {
status?: number;
reason?: string;
};
err.status = res.status >= 500 ? res.status : json.code;
err.reason = json.reason;
if (json.code === 400) {
reportApiError(
{ apiBase, clientApp: CLIENT_APP, getToken: () => localStorage.getItem(ACCESS_TOKEN) },
@@ -254,7 +258,15 @@ async function requestWithAuthRetry<T>(
try {
return await rawRequest<T>(path, options);
} catch (e) {
const err = e as Error & { status?: number };
const err = e as Error & { status?: number; reason?: string };
// 账号停用 / 门店关闭 / 合伙人绑定失效:强制退出登录
if (err.reason === 'ACCOUNT_DISABLED') {
clearAuth();
if (typeof window !== 'undefined' && !window.location.pathname.startsWith('/login')) {
window.location.replace('/login?disabled=1');
}
throw e;
}
const canRecover =
err.status === 401 &&
!retried &&
@@ -303,7 +315,14 @@ export async function ensureSession(): Promise<{
needsSelectStore: needsStoreSelection({ store, stores: me.stores }),
};
} catch (e) {
const err = e as Error & { status?: number };
const err = e as Error & { status?: number; reason?: string };
if (err.reason === 'ACCOUNT_DISABLED') {
clearAuth();
if (typeof window !== 'undefined' && !window.location.pathname.startsWith('/login')) {
window.location.replace('/login?disabled=1');
}
return { authenticated: false, store: null, needsSelectStore: false };
}
if (err.status === 401) {
const refreshed = await refreshSession();
if (refreshed) {
@@ -0,0 +1,42 @@
import { useEffect, useRef } from 'react';
/**
* v3.5.1 #2:门店端核销即时刷新。
* 核销成功后,通过浏览器自定义事件通知「门店信息页 / 提现页」即时刷新余额与提现按钮状态,
* 避免用户手动下拉刷新。
*/
const REDEEM_SUCCESS_EVENT = 'shop:redeem-success';
export type RedeemSuccessPayload = {
redeemNo?: string;
amount?: number;
storeId?: string;
};
/** 核销成功页 mount 时调用,广播核销成功事件 */
export function notifyRedeemSuccess(payload: RedeemSuccessPayload = {}) {
if (typeof window === 'undefined') return;
window.dispatchEvent(new CustomEvent(REDEEM_SUCCESS_EVENT, { detail: payload }));
}
/** 订阅核销成功事件,回调在事件触发时执行(通常用于刷新余额/提现状态) */
export function useRedeemSuccessListener(
callback: (payload: RedeemSuccessPayload) => void,
deps: React.DependencyList = [],
) {
const savedCallback = useRef(callback);
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
function handler(e: Event) {
const payload = (e as CustomEvent<RedeemSuccessPayload>).detail ?? {};
savedCallback.current(payload);
}
window.addEventListener(REDEEM_SUCCESS_EVENT, handler);
return () => window.removeEventListener(REDEEM_SUCCESS_EVENT, handler);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps);
}