docs: 新增 AI 代理学习偏好与信用盘术语规范

feat: 优化登录与入口页面加载体验

- 为登录页面添加 Suspense 边界,统一 fallback 样式
- 重构入口守卫逻辑,在布局阶段处理未登录重定向,避免闪烁
- 登录页面支持会话过期提示,并自动清理 URL 参数

feat: 信用盘与钱包盘文案差异化展示

- 底部导航「钱包」标签在信用盘模式下显示为「信用」
- 大厅余额条在信用盘模式下使用「可
This commit is contained in:
2026-06-12 20:48:10 +08:00
parent 20d2befa55
commit 7c283627d3
24 changed files with 347 additions and 92 deletions

View File

@@ -1,9 +1,11 @@
import axios, {
isAxiosError,
type AxiosError,
type AxiosRequestConfig,
type AxiosResponse,
} from "axios";
import { isInIframe } from "@/components/iframe-bridge";
import { withPlayerAuthHeader } from "@/lib/lottery-auth";
import { usePlayerSessionStore } from "@/stores/player-session-store";
import { withLotteryLocaleHeaders } from "@/lib/lottery-locale";
@@ -14,6 +16,7 @@ import {
import { isApiEnvelope } from "@/types/api/envelope";
import { useErrorStore } from "@/stores/error-store";
import { resolveLotteryApiV1Base } from "@/lib/lottery-api-base";
import i18n from "@/i18n";
/**
* **第一层**`baseURL` 对齐 Laravel `api/v1`;各 `api/*.ts` 只写业务 path如 `/currencies`)。
@@ -24,6 +27,34 @@ export const lotteryHttp = axios.create({
headers: { Accept: "application/json" },
});
/** 凭据类 401非 token 失效),不应触发「会话过期」跳转 */
const PLAYER_CREDENTIAL_BIZ_CODES = new Set([8006]);
function isPlayerAuthLoginRequest(config: AxiosRequestConfig | undefined): boolean {
if (!config || (config.method ?? "get").toLowerCase() !== "post") {
return false;
}
return `${config.url ?? ""}`.includes("/player/auth/login");
}
/** 是否应将 401 视为已登录态失效并跳转入口(见 {@link EntryGate} `session=expired` */
function shouldRedirectPlayerSessionExpired(error: AxiosError): boolean {
if (isPlayerAuthLoginRequest(error.config)) {
return false;
}
if (typeof window !== "undefined" && window.location.pathname === "/login") {
return false;
}
const body = error.response?.data;
if (isApiEnvelope(body) && PLAYER_CREDENTIAL_BIZ_CODES.has(body.code)) {
return false;
}
return true;
}
/** 站内接口 401清本地会话并回入口与 {@link EntryGate} `session=expired` 衔接 */
/** 500 错误:更新全局服务器错误状态 */
lotteryHttp.interceptors.response.use(
@@ -32,20 +63,28 @@ lotteryHttp.interceptors.response.use(
if (isAxiosError(error) && typeof window !== "undefined") {
const status = error.response?.status;
// 401: 会话过期,清除令牌并重定向
// 401: 已登录态 token 失效 → 清会话并回入口;登录凭据错误等由页面自行提示
if (status === 401) {
usePlayerSessionStore.getState().clearBearerToken();
const onEntry = window.location.pathname === "/";
const alreadyExpired = window.location.search.includes("session=expired");
if (!onEntry || !alreadyExpired) {
window.location.replace("/?session=expired");
const redirectSessionExpired = shouldRedirectPlayerSessionExpired(error);
if (redirectSessionExpired || window.location.pathname === "/login") {
usePlayerSessionStore.getState().clearBearerToken();
}
if (redirectSessionExpired) {
const pathname = window.location.pathname;
const alreadyExpired = window.location.search.includes("session=expired");
const expiredTarget = isInIframe() ? "/?session=expired" : "/login?session=expired";
const onExpiredPage =
(pathname === "/" || pathname === "/login") && alreadyExpired;
if (!onExpiredPage) {
window.location.replace(expiredTarget);
}
}
}
// 500/502/503: 服务器错误,更新全局错误状态
if (status === 500 || status === 502 || status === 503) {
const setServerError = useErrorStore.getState().setServerError;
let message = "服务器暂时不可用,请稍后重试";
let message = i18n.t("serverError.serverMessage", { ns: "player" });
// 尝试从响应中获取更详细的错误信息
const responseData = error.response?.data;