feat: 增加管理端多语言与多模块界面国际化支持

This commit is contained in:
2026-05-19 09:11:55 +08:00
parent 49a4caf01e
commit 1b1dfc92ab
110 changed files with 4053 additions and 1308 deletions

View File

@@ -1,6 +1,7 @@
"use client";
import { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { getAdminAuditLogs } from "@/api/admin-audit";
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
@@ -21,6 +22,7 @@ import { LotteryApiBizError } from "@/types/api/errors";
import type { AdminAuditLogListData } from "@/types/api/admin-audit";
export function AuditLogsConsole(): React.ReactElement {
const { t } = useTranslation(["audit", "common"]);
const formatTs = useAdminDateTimeFormatter();
const [data, setData] = useState<AdminAuditLogListData | null>(null);
const [loading, setLoading] = useState(true);
@@ -47,12 +49,12 @@ export function AuditLogsConsole(): React.ReactElement {
});
setData(d);
} catch (e) {
setErr(e instanceof LotteryApiBizError ? e.message : "加载失败");
setErr(e instanceof LotteryApiBizError ? e.message : t("errors.loadFailed", { ns: "common" }));
setData(null);
} finally {
setLoading(false);
}
}, [page, perPage, appliedModule, appliedAction, appliedOpType]);
}, [page, perPage, appliedModule, appliedAction, appliedOpType, t]);
useEffect(() => {
queueMicrotask(() => {
@@ -66,39 +68,39 @@ export function AuditLogsConsole(): React.ReactElement {
<Card className="w-full max-w-none">
<CardHeader className="flex flex-row flex-wrap items-end justify-between gap-4">
<div>
<CardTitle></CardTitle>
<CardTitle>{t("title")}</CardTitle>
</div>
<Button type="button" variant="secondary" size="sm" onClick={() => void load()}>
{t("actions.refresh", { ns: "common" })}
</Button>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
<div className="grid gap-1.5">
<Label htmlFor="aud-mod">module_code</Label>
<Label htmlFor="aud-mod">{t("moduleCode")}</Label>
<Input
id="aud-mod"
value={moduleCode}
onChange={(e) => setModuleCode(e.target.value)}
placeholder="精确匹配"
placeholder={t("exactMatch")}
/>
</div>
<div className="grid gap-1.5">
<Label htmlFor="aud-act">action_code</Label>
<Label htmlFor="aud-act">{t("actionCode")}</Label>
<Input
id="aud-act"
value={actionCode}
onChange={(e) => setActionCode(e.target.value)}
placeholder="精确匹配"
placeholder={t("exactMatch")}
/>
</div>
<div className="grid gap-1.5">
<Label htmlFor="aud-op">operator_type</Label>
<Label htmlFor="aud-op">{t("operatorType")}</Label>
<Input
id="aud-op"
value={operatorType}
onChange={(e) => setOperatorType(e.target.value)}
placeholder="如 admin / system"
placeholder={t("operatorTypePlaceholder")}
/>
</div>
<div className="flex items-end">
@@ -111,14 +113,14 @@ export function AuditLogsConsole(): React.ReactElement {
setPage(1);
}}
>
{t("actions.search", { ns: "common" })}
</Button>
</div>
</div>
{err ? <p className="text-sm text-red-600 dark:text-red-400">{err}</p> : null}
{loading && !data ? (
<p className="text-muted-foreground text-sm"></p>
<p className="text-muted-foreground text-sm">{t("states.loading", { ns: "common" })}</p>
) : null}
{data ? (
@@ -128,18 +130,18 @@ export function AuditLogsConsole(): React.ReactElement {
<TableHeader>
<TableRow>
<TableHead className="w-20">ID</TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead>{t("operator")}</TableHead>
<TableHead>{t("module")}</TableHead>
<TableHead>{t("action")}</TableHead>
<TableHead>{t("target")}</TableHead>
<TableHead>{t("time")}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.items.length === 0 ? (
<TableRow>
<TableCell colSpan={6} className="text-muted-foreground">
{t("empty")}
</TableCell>
</TableRow>
) : (