feat(admin): 仪表盘重构、代理线路修复与后台体验优化
Some checks failed
lotteryadmin CI / build (push) Has been cancelled

- 各角色仪表盘:KPI 可点击跳转、快捷入口、去重冗余区块,代理/站点降级 analytics
- 代理线路:创建玩家权限、侧栏搜索、深链 URL、档案保存与删除预检等修复
- 统一密码最短 6 位;代理模块表格/侧栏背景色一致(admin-table-inset)
- 报表、钱包、对账、开奖、结算、配置等模块结构与 i18n 同步更新
This commit is contained in:
2026-06-26 14:39:22 +08:00
parent 7d075ceb62
commit 0d984b00f0
126 changed files with 7423 additions and 7084 deletions

View 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;
}