feat: 统一管理端多语言、配置与票据/结算页面重构

This commit is contained in:
2026-05-20 16:27:06 +08:00
parent 37b13278ef
commit 08a11a1589
81 changed files with 2059 additions and 490 deletions

View File

@@ -218,11 +218,11 @@ export function RebateConfigDocScreen() {
setP2(inferPercentFrom(2, rows, types));
setP3(inferPercentFrom(3, rows, types));
setP4(inferPercentFrom(4, rows, types));
toast.success("Published odds version with rebate");
toast.success(t("rebate.publishSuccess", { ns: "config" }));
void refreshList();
setSelectedId(String(d.id));
} catch (e) {
toast.error(e instanceof LotteryApiBizError ? e.message : "Publish failed");
toast.error(e instanceof LotteryApiBizError ? e.message : t("rebate.publishFailed", { ns: "config" }));
} finally {
setSaving(false);
}
@@ -236,7 +236,7 @@ export function RebateConfigDocScreen() {
reason: `rebate draft ${new Date().toISOString()}`,
clone_from_version_id: active?.id ?? null,
});
toast.success(`Created draft v${d.version_no}`);
toast.success(t("rebate.createDraftSuccess", { ns: "config", version: d.version_no }));
await refreshList();
setSelectedId(String(d.id));
const rows = d.items.map((it) => ({ ...it }));
@@ -246,7 +246,7 @@ export function RebateConfigDocScreen() {
setP3(inferPercentFrom(3, rows, types));
setP4(inferPercentFrom(4, rows, types));
} catch (e) {
toast.error(e instanceof LotteryApiBizError ? e.message : "Create draft failed");
toast.error(e instanceof LotteryApiBizError ? e.message : t("rebate.createDraftFailed", { ns: "config" }));
} finally {
setSaving(false);
}
@@ -260,7 +260,7 @@ export function RebateConfigDocScreen() {
toast.success(t("versionSwitcher.delete", { ns: "config" }));
await refreshList();
} catch (e) {
toast.error(e instanceof LotteryApiBizError ? e.message : "Delete failed");
toast.error(e instanceof LotteryApiBizError ? e.message : t("rebate.deleteFailed", { ns: "config" }));
throw e;
}
}
@@ -278,7 +278,7 @@ export function RebateConfigDocScreen() {
onSelectedIdChange={setSelectedId}
loading={loading}
sheetTitle={`${t("nav.items.rebate", { ns: "config" })} ${t("versionSwitcher.sheetTitle", { ns: "config" })}`}
sheetDescription="Rebate is stored in the odds draft version and shares the same version set as odds."
sheetDescription={t("rebate.sheetDescription", { ns: "config" })}
onDeleteVersion={handleDeleteVersion}
className="w-auto min-w-0"
/>
@@ -288,7 +288,7 @@ export function RebateConfigDocScreen() {
loadingList={loading}
loadingDetail={loadingDetail}
saving={saving}
publishLabel="Publish"
publishLabel={t("rebate.publishLabel", { ns: "config" })}
onRefresh={() => void refreshList()}
onNewDraft={() => void handleNewDraft()}
onSaveDraft={() => void handleSave()}
@@ -297,9 +297,18 @@ export function RebateConfigDocScreen() {
{detail ? (
<p className="text-sm text-muted-foreground">
Editing version v{detail.version_no} · {detail.status === "draft" ? "Draft" : detail.status === "active" ? "Active" : "Archived"}
{t("rebate.editingVersion", {
ns: "config",
version: detail.version_no,
status:
detail.status === "draft"
? t("versionStatus.draft", { ns: "config" })
: detail.status === "active"
? t("versionStatus.active", { ns: "config" })
: t("versionStatus.archived", { ns: "config" }),
})}
{!isDraft ? (
<span className="text-amber-600 dark:text-amber-400"> - Create a draft before editing rebate.</span>
<span className="text-amber-600 dark:text-amber-400"> - {t("rebate.readOnlyHint", { ns: "config" })}</span>
) : null}
</p>
) : null}
@@ -307,7 +316,7 @@ export function RebateConfigDocScreen() {
<div className="grid gap-4 sm:grid-cols-3">
<div className="grid gap-2">
<Label>2D Rebate Rate (%)</Label>
<Label>{t("rebate.fields.d2", { ns: "config" })}</Label>
{isDraft ? (
<Input
type="number"
@@ -323,7 +332,7 @@ export function RebateConfigDocScreen() {
)}
</div>
<div className="grid gap-2">
<Label>3D Rebate Rate (%)</Label>
<Label>{t("rebate.fields.d3", { ns: "config" })}</Label>
{isDraft ? (
<Input
type="number"
@@ -339,7 +348,7 @@ export function RebateConfigDocScreen() {
)}
</div>
<div className="grid gap-2">
<Label>4D Rebate Rate (%)</Label>
<Label>{t("rebate.fields.d4", { ns: "config" })}</Label>
{isDraft ? (
<Input
type="number"
@@ -357,19 +366,25 @@ export function RebateConfigDocScreen() {
</div>
<div className="flex items-start gap-3 rounded-lg border bg-muted/30 p-4">
<Checkbox id="win-enjoy" checked aria-disabled disabled aria-label="Apply rebate on winning tickets" />
<Checkbox
id="win-enjoy"
checked
aria-disabled
disabled
aria-label={t("rebate.winEnjoy.label", { ns: "config" })}
/>
<div className="grid gap-1">
<Label htmlFor="win-enjoy" className="font-medium leading-snug">
Apply rebate on winning tickets
{t("rebate.winEnjoy.label", { ns: "config" })}
</Label>
<p className="text-sm text-muted-foreground">
Placeholder field. It can later be aligned with risk and settlement rules and persisted.
{t("rebate.winEnjoy.description", { ns: "config" })}
</p>
</div>
</div>
<div className="grid gap-1 text-sm">
<span className="text-muted-foreground">Effective Time (current active odds version)</span>
<span className="text-muted-foreground">{t("rebate.effectiveTime", { ns: "config" })}</span>
<span className="font-mono text-sm">
{activeHead?.effective_at ? formatDt(activeHead.effective_at) : "—"}
</span>