feat: 增加管理端多语言与多模块界面国际化支持
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
export const settlementModuleMeta = {
|
||||
title: "结算",
|
||||
title: "Settlement",
|
||||
description: "",
|
||||
} as const;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import Link from "next/link";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import {
|
||||
@@ -38,6 +39,7 @@ type Props = {
|
||||
};
|
||||
|
||||
export function SettlementBatchDetailsConsole({ batchId }: Props) {
|
||||
const { t } = useTranslation(["settlement", "common"]);
|
||||
const formatDt = useAdminDateTimeFormatter();
|
||||
const [summary, setSummary] = useState<AdminSettlementBatchShowData | null>(null);
|
||||
const [details, setDetails] = useState<AdminSettlementBatchDetailsData | null>(null);
|
||||
@@ -58,29 +60,29 @@ export function SettlementBatchDetailsConsole({ batchId }: Props) {
|
||||
setSummary(s);
|
||||
setDetails(d);
|
||||
} catch (e) {
|
||||
setErr(e instanceof LotteryApiBizError ? e.message : "加载失败");
|
||||
setErr(e instanceof LotteryApiBizError ? e.message : t("loadFailed"));
|
||||
setSummary(null);
|
||||
setDetails(null);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [batchId, page, perPage]);
|
||||
}, [batchId, page, perPage, t]);
|
||||
|
||||
async function runAction(label: string, action: () => Promise<unknown>): Promise<void> {
|
||||
setActing(label);
|
||||
try {
|
||||
await action();
|
||||
toast.success(`${label}成功`);
|
||||
toast.success(t("actionSuccess", { name: label }));
|
||||
await load();
|
||||
} catch (e) {
|
||||
toast.error(e instanceof LotteryApiBizError ? e.message : `${label}失败`);
|
||||
toast.error(e instanceof LotteryApiBizError ? e.message : t("actionFailed", { name: label }));
|
||||
} finally {
|
||||
setActing(null);
|
||||
}
|
||||
}
|
||||
|
||||
async function exportCsv(): Promise<void> {
|
||||
setActing("导出");
|
||||
setActing(t("export"));
|
||||
try {
|
||||
const blob = await downloadAdminSettlementBatchExport(batchId);
|
||||
const url = URL.createObjectURL(blob);
|
||||
@@ -92,7 +94,7 @@ export function SettlementBatchDetailsConsole({ batchId }: Props) {
|
||||
a.remove();
|
||||
URL.revokeObjectURL(url);
|
||||
} catch (e) {
|
||||
toast.error(e instanceof LotteryApiBizError ? e.message : "导出失败");
|
||||
toast.error(e instanceof LotteryApiBizError ? e.message : t("exportFailed"));
|
||||
} finally {
|
||||
setActing(null);
|
||||
}
|
||||
@@ -107,19 +109,19 @@ export function SettlementBatchDetailsConsole({ batchId }: Props) {
|
||||
<ModuleScaffold>
|
||||
<div className="mb-4">
|
||||
<Link href="/admin/settlement-batches" className={cn(buttonVariants({ variant: "ghost", size: "sm" }), "px-0")}>
|
||||
← 返回批次列表
|
||||
← {t("backToList")}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{err ? (
|
||||
<Card className="border-destructive/40">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">错误</CardTitle>
|
||||
<CardTitle className="text-base">{t("errorTitle")}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<p className="text-sm text-destructive">{err}</p>
|
||||
<Button type="button" size="sm" variant="secondary" onClick={() => void load()}>
|
||||
重试
|
||||
{t("retry")}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -128,44 +130,47 @@ export function SettlementBatchDetailsConsole({ batchId }: Props) {
|
||||
{summary ? (
|
||||
<Card className="mb-6">
|
||||
<CardHeader>
|
||||
<CardTitle className="font-mono text-base">批次 #{summary.id}</CardTitle>
|
||||
<CardTitle className="font-mono text-base">{t("batchSummary", { id: summary.id })}</CardTitle>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
期号 {summary.draw_no ?? "—"} · 期状态 {summary.draw_status ?? "—"} · 结果批次 v
|
||||
{summary.result_batch_version ?? "—"}
|
||||
{t("summaryMeta", {
|
||||
drawNo: summary.draw_no ?? "—",
|
||||
drawStatus: summary.draw_status ?? "—",
|
||||
version: summary.result_batch_version ?? "—",
|
||||
})}
|
||||
</p>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-2 text-sm sm:grid-cols-2">
|
||||
<p>
|
||||
<span className="text-muted-foreground">结算状态</span>{" "}
|
||||
<span className="text-muted-foreground">{t("settlementStatus")}</span>{" "}
|
||||
<span className="font-mono">{summary.status}</span>
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-muted-foreground">审核状态</span>{" "}
|
||||
<span className="text-muted-foreground">{t("reviewState")}</span>{" "}
|
||||
<span className="font-mono">{summary.review_status ?? "—"}</span>
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-muted-foreground">注单数</span>{" "}
|
||||
<span className="text-muted-foreground">{t("ticketTotal")}</span>{" "}
|
||||
<span className="tabular-nums">{summary.total_ticket_count}</span>
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-muted-foreground">中奖笔数</span>{" "}
|
||||
<span className="text-muted-foreground">{t("winTotal")}</span>{" "}
|
||||
<span className="tabular-nums">{summary.total_win_count}</span>
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-muted-foreground">派彩合计</span>{" "}
|
||||
<span className="text-muted-foreground">{t("payoutAmount")}</span>{" "}
|
||||
<span className="font-mono tabular-nums">{formatAdminMinorUnits(summary.total_payout_amount)}</span>
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-muted-foreground">Jackpot 划出</span>{" "}
|
||||
<span className="text-muted-foreground">{t("jackpotPayout")}</span>{" "}
|
||||
<span className="font-mono tabular-nums">
|
||||
{formatAdminMinorUnits(summary.total_jackpot_payout_amount)}
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-muted-foreground">开始</span> {formatDt(summary.started_at)}
|
||||
<span className="text-muted-foreground">{t("startedAt")}</span> {formatDt(summary.started_at)}
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-muted-foreground">结束</span> {formatDt(summary.finished_at)}
|
||||
<span className="text-muted-foreground">{t("endedAt")}</span> {formatDt(summary.finished_at)}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2 sm:col-span-2">
|
||||
<Button
|
||||
@@ -173,40 +178,40 @@ export function SettlementBatchDetailsConsole({ batchId }: Props) {
|
||||
size="sm"
|
||||
variant="outline"
|
||||
disabled={acting !== null || summary.status !== "pending_review"}
|
||||
onClick={() => void runAction("审核通过", () => postAdminApproveSettlementBatch(batchId))}
|
||||
onClick={() => void runAction(t("approve"), () => postAdminApproveSettlementBatch(batchId))}
|
||||
>
|
||||
审核通过
|
||||
{t("approve")}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
disabled={acting !== null || summary.status !== "pending_review"}
|
||||
onClick={() => void runAction("驳回", () => postAdminRejectSettlementBatch(batchId))}
|
||||
onClick={() => void runAction(t("reject"), () => postAdminRejectSettlementBatch(batchId))}
|
||||
>
|
||||
驳回
|
||||
{t("reject")}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
disabled={acting !== null || summary.status !== "approved"}
|
||||
onClick={() => void runAction("执行派彩", () => postAdminPayoutSettlementBatch(batchId))}
|
||||
onClick={() => void runAction(t("runPayout"), () => postAdminPayoutSettlementBatch(batchId))}
|
||||
>
|
||||
执行派彩
|
||||
{t("runPayout")}
|
||||
</Button>
|
||||
<Button type="button" size="sm" variant="secondary" disabled={acting !== null} onClick={() => void exportCsv()}>
|
||||
导出结算报表
|
||||
{t("exportSettlementReport")}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : loading ? (
|
||||
<p className="text-muted-foreground text-sm">加载摘要…</p>
|
||||
<p className="text-muted-foreground text-sm">{t("loadingSummary")}</p>
|
||||
) : null}
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">注单结算明细</CardTitle>
|
||||
<CardTitle className="text-base">{t("detailTitle")}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{details ? (
|
||||
@@ -214,12 +219,12 @@ export function SettlementBatchDetailsConsole({ batchId }: Props) {
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>注单号</TableHead>
|
||||
<TableHead>玩法</TableHead>
|
||||
<TableHead>玩家</TableHead>
|
||||
<TableHead>匹配档</TableHead>
|
||||
<TableHead className="text-right">常规派彩</TableHead>
|
||||
<TableHead className="text-right">Jackpot</TableHead>
|
||||
<TableHead>{t("ticketNo")}</TableHead>
|
||||
<TableHead>{t("playCode")}</TableHead>
|
||||
<TableHead>{t("player")}</TableHead>
|
||||
<TableHead>{t("matchedTier")}</TableHead>
|
||||
<TableHead className="text-right">{t("regularPayout")}</TableHead>
|
||||
<TableHead className="text-right">{t("jackpot")}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -256,7 +261,9 @@ export function SettlementBatchDetailsConsole({ batchId }: Props) {
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<p className="text-muted-foreground text-sm">{loading ? "加载明细…" : "无数据"}</p>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{loading ? t("loadingDetails") : t("states.noData", { ns: "common" })}
|
||||
</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import Link from "next/link";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import {
|
||||
@@ -42,13 +43,14 @@ import { settlementModuleMeta } from "@/modules/settlement/meta";
|
||||
|
||||
const STATUS_ALL = "__all__";
|
||||
const STATUS_OPTIONS: { value: string; label: string }[] = [
|
||||
{ value: STATUS_ALL, label: "不限" },
|
||||
{ value: "running", label: "进行中" },
|
||||
{ value: "completed", label: "已完成" },
|
||||
{ value: "failed", label: "失败" },
|
||||
{ value: STATUS_ALL, label: "statusOptions.all" },
|
||||
{ value: "running", label: "statusOptions.running" },
|
||||
{ value: "completed", label: "statusOptions.completed" },
|
||||
{ value: "failed", label: "statusOptions.failed" },
|
||||
];
|
||||
|
||||
export function SettlementBatchesConsole() {
|
||||
const { t } = useTranslation(["settlement", "common"]);
|
||||
const formatDt = useAdminDateTimeFormatter();
|
||||
const [data, setData] = useState<AdminSettlementBatchListData | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -76,12 +78,12 @@ export function SettlementBatchesConsole() {
|
||||
});
|
||||
setData(d);
|
||||
} catch (e) {
|
||||
setError(e instanceof LotteryApiBizError ? e.message : "加载失败");
|
||||
setError(e instanceof LotteryApiBizError ? e.message : t("loadFailed"));
|
||||
setData(null);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [page, perPage, appliedDrawNo, appliedStatus]);
|
||||
}, [page, perPage, appliedDrawNo, appliedStatus, t]);
|
||||
|
||||
useEffect(() => {
|
||||
const t = window.setTimeout(() => void load(), 0);
|
||||
@@ -98,10 +100,10 @@ export function SettlementBatchesConsole() {
|
||||
setActingId(batchId);
|
||||
try {
|
||||
await action();
|
||||
toast.success(`${label}成功`);
|
||||
toast.success(t("actionSuccess", { name: label }));
|
||||
await load();
|
||||
} catch (e) {
|
||||
toast.error(e instanceof LotteryApiBizError ? e.message : `${label}失败`);
|
||||
toast.error(e instanceof LotteryApiBizError ? e.message : t("actionFailed", { name: label }));
|
||||
} finally {
|
||||
setActingId(null);
|
||||
}
|
||||
@@ -120,7 +122,7 @@ export function SettlementBatchesConsole() {
|
||||
a.remove();
|
||||
URL.revokeObjectURL(url);
|
||||
} catch (e) {
|
||||
toast.error(e instanceof LotteryApiBizError ? e.message : "导出失败");
|
||||
toast.error(e instanceof LotteryApiBizError ? e.message : t("exportFailed"));
|
||||
} finally {
|
||||
setActingId(null);
|
||||
}
|
||||
@@ -133,21 +135,21 @@ export function SettlementBatchesConsole() {
|
||||
</div>
|
||||
<Card className="mb-6">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-base">筛选</CardTitle>
|
||||
<CardTitle className="text-base">{t("filter")}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4 sm:flex-row sm:flex-wrap sm:items-end">
|
||||
<div className="flex min-w-[12rem] flex-1 flex-col gap-1.5">
|
||||
<Label htmlFor="sb-draw-no">期号</Label>
|
||||
<Label htmlFor="sb-draw-no">{t("drawNo")}</Label>
|
||||
<Input
|
||||
id="sb-draw-no"
|
||||
value={draftDrawNo}
|
||||
onChange={(e) => setDraftDrawNo(e.target.value)}
|
||||
placeholder="如 20260511-001"
|
||||
placeholder={t("placeholderDrawNo")}
|
||||
className="font-mono"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex min-w-[10rem] flex-col gap-1.5">
|
||||
<Label>状态</Label>
|
||||
<Label>{t("status")}</Label>
|
||||
<Select value={draftStatus} onValueChange={(v) => setDraftStatus(v ?? STATUS_ALL)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
@@ -155,40 +157,40 @@ export function SettlementBatchesConsole() {
|
||||
<SelectContent>
|
||||
{STATUS_OPTIONS.map((o) => (
|
||||
<SelectItem key={o.value} value={o.value}>
|
||||
{o.label}
|
||||
{t(o.label)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<Button type="button" onClick={applyFilters}>
|
||||
应用
|
||||
{t("apply")}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">结算批次</CardTitle>
|
||||
<CardTitle className="text-base">{t("batchList")}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{error ? <p className="text-destructive text-sm">{error}</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>
|
||||
) : (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>ID</TableHead>
|
||||
<TableHead>期号</TableHead>
|
||||
<TableHead>版本</TableHead>
|
||||
<TableHead>审核状态</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead className="text-right">注单数</TableHead>
|
||||
<TableHead className="text-right">中奖笔数</TableHead>
|
||||
<TableHead className="text-right">派彩合计</TableHead>
|
||||
<TableHead className="text-right">Jackpot</TableHead>
|
||||
<TableHead>完成时间</TableHead>
|
||||
<TableHead>{t("drawNo")}</TableHead>
|
||||
<TableHead>{t("version", { ns: "draws", version: "" }).replace(" v", "").trim()}</TableHead>
|
||||
<TableHead>{t("reviewStatus")}</TableHead>
|
||||
<TableHead>{t("status")}</TableHead>
|
||||
<TableHead className="text-right">{t("ticketCount")}</TableHead>
|
||||
<TableHead className="text-right">{t("winCount")}</TableHead>
|
||||
<TableHead className="text-right">{t("payoutTotal")}</TableHead>
|
||||
<TableHead className="text-right">{t("jackpot")}</TableHead>
|
||||
<TableHead>{t("finishedAt")}</TableHead>
|
||||
<TableHead />
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
@@ -230,33 +232,33 @@ export function SettlementBatchesConsole() {
|
||||
href={`/admin/settlement-batches/${row.id}/details`}
|
||||
className={cn(buttonVariants({ variant: "link", size: "sm" }), "px-0")}
|
||||
>
|
||||
明细
|
||||
{t("details")}
|
||||
</Link>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
disabled={actingId !== null || row.status !== "pending_review"}
|
||||
onClick={() => void runBatchAction(row.id, "审核通过", () => postAdminApproveSettlementBatch(row.id))}
|
||||
onClick={() => void runBatchAction(row.id, t("approve"), () => postAdminApproveSettlementBatch(row.id))}
|
||||
>
|
||||
通过
|
||||
{t("pass")}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
disabled={actingId !== null || row.status !== "pending_review"}
|
||||
onClick={() => void runBatchAction(row.id, "驳回", () => postAdminRejectSettlementBatch(row.id))}
|
||||
onClick={() => void runBatchAction(row.id, t("reject"), () => postAdminRejectSettlementBatch(row.id))}
|
||||
>
|
||||
驳回
|
||||
{t("reject")}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
disabled={actingId !== null || row.status !== "approved"}
|
||||
onClick={() => void runBatchAction(row.id, "执行派彩", () => postAdminPayoutSettlementBatch(row.id))}
|
||||
onClick={() => void runBatchAction(row.id, t("runPayout"), () => postAdminPayoutSettlementBatch(row.id))}
|
||||
>
|
||||
派彩
|
||||
{t("payout")}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
@@ -265,7 +267,7 @@ export function SettlementBatchesConsole() {
|
||||
disabled={actingId !== null}
|
||||
onClick={() => void exportBatch(row.id)}
|
||||
>
|
||||
导出
|
||||
{t("export")}
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
Reference in New Issue
Block a user