feat(admin): 仪表盘重构、代理线路修复与后台体验优化
Some checks failed
lotteryadmin CI / build (push) Has been cancelled
Some checks failed
lotteryadmin CI / build (push) Has been cancelled
- 各角色仪表盘:KPI 可点击跳转、快捷入口、去重冗余区块,代理/站点降级 analytics - 代理线路:创建玩家权限、侧栏搜索、深链 URL、档案保存与删除预检等修复 - 统一密码最短 6 位;代理模块表格/侧栏背景色一致(admin-table-inset) - 报表、钱包、对账、开奖、结算、配置等模块结构与 i18n 同步更新
This commit is contained in:
79
src/modules/draws/draw-detail-context.tsx
Normal file
79
src/modules/draws/draw-detail-context.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useMemo,
|
||||
useState,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { getAdminDraw } from "@/api/admin-draws";
|
||||
import { useAsyncEffect } from "@/hooks/use-async-effect";
|
||||
import { useTranslationRef } from "@/hooks/use-translation-ref";
|
||||
import { LotteryApiBizError } from "@/types/api/errors";
|
||||
import type { AdminDrawShowData } from "@/types/api/admin-draws";
|
||||
|
||||
type DrawDetailContextValue = {
|
||||
drawId: number;
|
||||
draw: AdminDrawShowData | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
refresh: () => Promise<void>;
|
||||
};
|
||||
|
||||
const DrawDetailContext = createContext<DrawDetailContextValue | null>(null);
|
||||
|
||||
export function DrawDetailProvider({
|
||||
drawId,
|
||||
children,
|
||||
}: {
|
||||
drawId: string;
|
||||
children: ReactNode;
|
||||
}): React.ReactElement {
|
||||
const tRef = useTranslationRef(["draws", "common"]);
|
||||
const idNum = Number(drawId);
|
||||
const [draw, setDraw] = useState<AdminDrawShowData | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
if (!Number.isFinite(idNum)) {
|
||||
setError(tRef.current("invalidDrawId"));
|
||||
setDraw(null);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
setDraw(await getAdminDraw(idNum));
|
||||
} catch (e) {
|
||||
setDraw(null);
|
||||
setError(e instanceof LotteryApiBizError ? e.message : tRef.current("errors.loadFailed", { ns: "common" }));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [idNum, tRef]);
|
||||
|
||||
useAsyncEffect(() => {
|
||||
void refresh();
|
||||
}, [refresh]);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({ drawId: idNum, draw, loading, error, refresh }),
|
||||
[draw, error, idNum, loading, refresh],
|
||||
);
|
||||
|
||||
return <DrawDetailContext.Provider value={value}>{children}</DrawDetailContext.Provider>;
|
||||
}
|
||||
|
||||
export function useDrawDetail(): DrawDetailContextValue {
|
||||
const ctx = useContext(DrawDetailContext);
|
||||
if (ctx == null) {
|
||||
throw new Error("useDrawDetail must be used within DrawDetailProvider");
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
Reference in New Issue
Block a user