feat: 接入玩家入口与API代理
- 新增 /api 重写代理,支持 LOTTERY_API_PROXY_TARGET 配置 - 玩家首页切换为 EntryGate,并移除 layout 对 PlayerAppShell 的包裹 - 请求层拆分语言头与玩家鉴权注入逻辑,引入 zustand 依赖 - 允许提交 .env.example 供本地配置参考
This commit is contained in:
102
src/stores/player-session-store.ts
Normal file
102
src/stores/player-session-store.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { create } from "zustand";
|
||||
|
||||
import { setPlayerBearerToken } from "@/lib/lottery-auth";
|
||||
import {
|
||||
clearPersistedPlayerBearerToken,
|
||||
persistPlayerBearerToken,
|
||||
readPersistedPlayerBearerToken,
|
||||
} from "@/lib/player-session";
|
||||
import type { PlayerMeData } from "@/types/api/player-me";
|
||||
|
||||
export type PlayerEntryPhase = "loading" | "error" | "success";
|
||||
export type PlayerEntryStepId = "token" | "account" | "hall";
|
||||
export type PlayerEntryStepStatus = "pending" | "active" | "done";
|
||||
|
||||
export type PlayerEntryStep = {
|
||||
id: PlayerEntryStepId;
|
||||
label: string;
|
||||
status: PlayerEntryStepStatus;
|
||||
};
|
||||
|
||||
type PlayerSessionState = {
|
||||
bearerToken: string | null;
|
||||
profile: PlayerMeData | null;
|
||||
phase: PlayerEntryPhase;
|
||||
progress: number;
|
||||
errorMessage: string | null;
|
||||
steps: PlayerEntryStep[];
|
||||
setBearerToken: (token: string | null) => void;
|
||||
restoreBearerToken: () => string | null;
|
||||
clearBearerToken: () => void;
|
||||
setProfile: (profile: PlayerMeData | null) => void;
|
||||
setPhase: (phase: PlayerEntryPhase) => void;
|
||||
setProgress: (progress: number) => void;
|
||||
setErrorMessage: (message: string | null) => void;
|
||||
updateStep: (id: PlayerEntryStepId, status: PlayerEntryStepStatus) => void;
|
||||
resetEntryFlow: () => void;
|
||||
};
|
||||
|
||||
function initialSteps(): PlayerEntryStep[] {
|
||||
return [
|
||||
{ id: "token", label: "校验登录凭证", status: "active" },
|
||||
{ id: "account", label: "同步玩家账号", status: "pending" },
|
||||
{ id: "hall", label: "加载彩票大厅", status: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
export const usePlayerSessionStore = create<PlayerSessionState>((set) => ({
|
||||
bearerToken: null,
|
||||
profile: null,
|
||||
phase: "loading",
|
||||
progress: 0,
|
||||
errorMessage: null,
|
||||
steps: initialSteps(),
|
||||
|
||||
setBearerToken: (token) => {
|
||||
const normalized = token?.trim() || null;
|
||||
setPlayerBearerToken(normalized);
|
||||
if (normalized) {
|
||||
persistPlayerBearerToken(normalized);
|
||||
} else {
|
||||
clearPersistedPlayerBearerToken();
|
||||
}
|
||||
set({ bearerToken: normalized });
|
||||
},
|
||||
|
||||
restoreBearerToken: () => {
|
||||
const raw = readPersistedPlayerBearerToken();
|
||||
const normalized = raw?.trim() || null;
|
||||
if (normalized) {
|
||||
setPlayerBearerToken(normalized);
|
||||
set({ bearerToken: normalized });
|
||||
return normalized;
|
||||
}
|
||||
setPlayerBearerToken(null);
|
||||
set({ bearerToken: null });
|
||||
return null;
|
||||
},
|
||||
|
||||
clearBearerToken: () => {
|
||||
setPlayerBearerToken(null);
|
||||
clearPersistedPlayerBearerToken();
|
||||
set({ bearerToken: null, profile: null });
|
||||
},
|
||||
|
||||
setProfile: (profile) => set({ profile }),
|
||||
setPhase: (phase) => set({ phase }),
|
||||
setProgress: (progress) => set({ progress: Math.max(0, Math.min(100, progress)) }),
|
||||
setErrorMessage: (errorMessage) => set({ errorMessage }),
|
||||
updateStep: (id, status) =>
|
||||
set((state) => ({
|
||||
steps: state.steps.map((step) =>
|
||||
step.id === id ? { ...step, status } : step,
|
||||
),
|
||||
})),
|
||||
resetEntryFlow: () =>
|
||||
set({
|
||||
phase: "loading",
|
||||
progress: 0,
|
||||
errorMessage: null,
|
||||
steps: initialSteps(),
|
||||
}),
|
||||
}));
|
||||
Reference in New Issue
Block a user