feat: 增加管理端多语言与多模块界面国际化支持
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
import Link from "next/link";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { getAdminDrawResultBatches, postAdminCreateManualResultBatch } from "@/api/admin-draws";
|
||||
@@ -26,22 +27,25 @@ import { PRD_DRAW_RESULT_MANAGE } from "./draw-prd";
|
||||
import { DrawStatusBadge } from "./draw-status-badge";
|
||||
|
||||
const RESULT_SLOTS = [
|
||||
{ prize_type: "first", prize_index: 0, label: "头奖" },
|
||||
{ prize_type: "second", prize_index: 0, label: "二奖" },
|
||||
{ prize_type: "third", prize_index: 0, label: "三奖" },
|
||||
{ prize_type: "first", prize_index: 0, label: "resultSlots.first" },
|
||||
{ prize_type: "second", prize_index: 0, label: "resultSlots.second" },
|
||||
{ prize_type: "third", prize_index: 0, label: "resultSlots.third" },
|
||||
...Array.from({ length: 10 }, (_, i) => ({
|
||||
prize_type: "starter",
|
||||
prize_index: i,
|
||||
label: `特别奖 ${i + 1}`,
|
||||
label: `resultSlots.starter`,
|
||||
labelIndex: i + 1,
|
||||
})),
|
||||
...Array.from({ length: 10 }, (_, i) => ({
|
||||
prize_type: "consolation",
|
||||
prize_index: i,
|
||||
label: `安慰奖 ${i + 1}`,
|
||||
label: `resultSlots.consolation`,
|
||||
labelIndex: i + 1,
|
||||
})),
|
||||
] as const;
|
||||
|
||||
export function DrawReviewConsole({ drawId }: { drawId: string }) {
|
||||
const { t } = useTranslation(["draws", "common"]);
|
||||
const profile = useAdminProfile();
|
||||
const canManageDraw = adminHasAnyPermission(profile?.permissions, [
|
||||
PRD_DRAW_RESULT_MANAGE,
|
||||
@@ -57,7 +61,7 @@ export function DrawReviewConsole({ drawId }: { drawId: string }) {
|
||||
|
||||
const load = useCallback(async () => {
|
||||
if (!Number.isFinite(idNum)) {
|
||||
setError("无效的期号 ID");
|
||||
setError(t("invalidDrawId"));
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
@@ -67,11 +71,11 @@ export function DrawReviewConsole({ drawId }: { drawId: string }) {
|
||||
setData(await getAdminDrawResultBatches(idNum));
|
||||
} catch (e) {
|
||||
setData(null);
|
||||
setError(e instanceof LotteryApiBizError ? e.message : "加载失败");
|
||||
setError(e instanceof LotteryApiBizError ? e.message : t("errors.loadFailed", { ns: "common" }));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [idNum]);
|
||||
}, [idNum, t]);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = window.setTimeout(() => {
|
||||
@@ -88,7 +92,7 @@ export function DrawReviewConsole({ drawId }: { drawId: string }) {
|
||||
if (!Number.isFinite(idNum)) return;
|
||||
const invalid = manualNumbers.some((n) => !/^[0-9]{4}$/.test(n));
|
||||
if (invalid) {
|
||||
toast.error("请完整输入 23 组 4 位数字");
|
||||
toast.error(t("enter23Numbers"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -101,38 +105,46 @@ export function DrawReviewConsole({ drawId }: { drawId: string }) {
|
||||
number_4d: manualNumbers[i],
|
||||
})),
|
||||
});
|
||||
toast.success(`已保存草稿 v${res.batch.result_version},等待确认发布`);
|
||||
toast.success(t("draftSaved", { version: res.batch.result_version }));
|
||||
setManualNumbers(RESULT_SLOTS.map(() => ""));
|
||||
await load();
|
||||
} catch (e) {
|
||||
toast.error(e instanceof LotteryApiBizError ? e.message : "保存失败");
|
||||
toast.error(e instanceof LotteryApiBizError ? e.message : t("saveFailed"));
|
||||
} finally {
|
||||
setSavingManual(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (loading && !data) {
|
||||
return <p className="text-sm text-muted-foreground">加载中…</p>;
|
||||
return <p className="text-sm text-muted-foreground">{t("states.loading", { ns: "common" })}</p>;
|
||||
}
|
||||
|
||||
if (error || !data) {
|
||||
return <p className="text-sm text-destructive">{error ?? "无数据"}</p>;
|
||||
return <p className="text-sm text-destructive">{error ?? t("states.noData", { ns: "common" })}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">人工录入开奖结果</CardTitle>
|
||||
<CardTitle className="text-lg">{t("manualResultEntry")}</CardTitle>
|
||||
<p className="flex flex-wrap items-center gap-2 text-sm text-muted-foreground">
|
||||
当前状态 <DrawStatusBadge status={data.draw_status} /> · 保存后生成待确认批次,不会直接发布
|
||||
{t("currentStatusAndDraft", {
|
||||
status: data.draw_status,
|
||||
}).split(data.draw_status)[0]}
|
||||
<DrawStatusBadge status={data.draw_status} />
|
||||
{t("currentStatusAndDraft", {
|
||||
status: data.draw_status,
|
||||
}).split(data.draw_status)[1] ?? ""}
|
||||
</p>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{RESULT_SLOTS.map((slot, i) => (
|
||||
<label key={`${slot.prize_type}-${slot.prize_index}`} className="space-y-1.5">
|
||||
<span className="text-xs font-medium text-muted-foreground">{slot.label}</span>
|
||||
<span className="text-xs font-medium text-muted-foreground">
|
||||
{t(slot.label, { index: "labelIndex" in slot ? slot.labelIndex : undefined })}
|
||||
</span>
|
||||
<Input
|
||||
inputMode="numeric"
|
||||
maxLength={4}
|
||||
@@ -155,14 +167,14 @@ export function DrawReviewConsole({ drawId }: { drawId: string }) {
|
||||
disabled={savingManual}
|
||||
onClick={() => setManualNumbers(RESULT_SLOTS.map(() => ""))}
|
||||
>
|
||||
清空
|
||||
{t("clear")}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
disabled={!canManageDraw || savingManual || !["closed", "review"].includes(data.draw_status)}
|
||||
onClick={() => void saveManualDraft()}
|
||||
>
|
||||
{savingManual ? "保存中…" : "保存草稿"}
|
||||
{savingManual ? t("saving") : t("saveDraft")}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -170,21 +182,21 @@ export function DrawReviewConsole({ drawId }: { drawId: string }) {
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">待确认批次</CardTitle>
|
||||
<CardTitle className="text-lg">{t("pendingBatches")}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{pending.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground py-6 text-center">
|
||||
当前没有待审核(pending_review)批次。
|
||||
{t("noPendingBatches")}
|
||||
</p>
|
||||
) : (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>批次 ID</TableHead>
|
||||
<TableHead>版本</TableHead>
|
||||
<TableHead>号码条数</TableHead>
|
||||
<TableHead className="text-right">操作</TableHead>
|
||||
<TableHead>{t("batchId")}</TableHead>
|
||||
<TableHead>{t("version", { version: "" }).replace(" v", "").trim()}</TableHead>
|
||||
<TableHead>{t("numberCount")}</TableHead>
|
||||
<TableHead className="text-right">{t("actions")}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -199,10 +211,10 @@ export function DrawReviewConsole({ drawId }: { drawId: string }) {
|
||||
href={`/admin/draws/${drawId}/publish/${b.id}`}
|
||||
className={cn(buttonVariants({ size: "sm" }))}
|
||||
>
|
||||
核对并发布
|
||||
{t("reviewAndPublishAction")}
|
||||
</Link>
|
||||
) : (
|
||||
<span className="text-xs text-muted-foreground">无发布权限</span>
|
||||
<span className="text-xs text-muted-foreground">{t("noPublishPermission")}</span>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
Reference in New Issue
Block a user