From 2d4a23968e8411fd61a922eb1600fc98e21eebaf Mon Sep 17 00:00:00 2001 From: kang Date: Fri, 22 May 2026 16:11:36 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=9B=B4=E6=96=B0=E9=A3=8E?= =?UTF-8?q?=E9=99=A9=E7=9B=91=E6=8E=A7=E9=A1=B5=E9=9D=A2=E6=A0=87=E9=A2=98?= =?UTF-8?q?=E4=B8=BA=E5=9B=BD=E9=99=85=E5=8C=96=E6=94=AF=E6=8C=81=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=A3=8E=E9=99=A9=E6=B1=A0=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E5=8F=B0=E7=9A=84=E6=A0=87=E9=A2=98=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(shell)/draws/[drawId]/risk/hot/page.tsx | 2 +- .../draws/[drawId]/risk/pools/page.tsx | 2 +- .../draws/[drawId]/risk/sold-out/page.tsx | 2 +- src/hooks/use-admin-play-type-catalog.ts | 40 +++++++ src/i18n/locales/en/adminUsers.json | 5 +- src/i18n/locales/en/draws.json | 20 +++- src/i18n/locales/en/risk.json | 8 ++ src/i18n/locales/ne/adminUsers.json | 5 +- src/i18n/locales/ne/draws.json | 20 +++- src/i18n/locales/ne/risk.json | 8 ++ src/i18n/locales/zh/adminUsers.json | 5 +- src/i18n/locales/zh/draws.json | 14 ++- src/i18n/locales/zh/risk.json | 12 ++- src/lib/admin-play-types.ts | 102 ++++++++++++++++++ src/modules/draws/draw-detail-console.tsx | 15 +-- src/modules/draws/draw-display.ts | 62 +++++++++++ src/modules/draws/draw-finance-console.tsx | 33 +++--- src/modules/draws/draw-publish-console.tsx | 16 ++- src/modules/draws/draw-results-console.tsx | 5 +- src/modules/draws/draw-review-console.tsx | 14 +-- src/modules/draws/draws-index-console.tsx | 25 +++-- src/modules/reports/reports-console.tsx | 27 +++-- src/modules/risk/risk-display.ts | 27 +++++ src/modules/risk/risk-lock-logs-console.tsx | 21 ++-- src/modules/risk/risk-pool-detail-console.tsx | 9 +- src/modules/risk/risk-pools-console.tsx | 11 +- .../settings/system-settings-screen.tsx | 58 +++++++++- .../settlement-batch-details-console.tsx | 4 +- .../tickets/player-tickets-console.tsx | 13 ++- 29 files changed, 491 insertions(+), 94 deletions(-) create mode 100644 src/hooks/use-admin-play-type-catalog.ts create mode 100644 src/lib/admin-play-types.ts create mode 100644 src/modules/draws/draw-display.ts create mode 100644 src/modules/risk/risk-display.ts diff --git a/src/app/admin/(shell)/draws/[drawId]/risk/hot/page.tsx b/src/app/admin/(shell)/draws/[drawId]/risk/hot/page.tsx index e0a4cd4..6b1daa3 100644 --- a/src/app/admin/(shell)/draws/[drawId]/risk/hot/page.tsx +++ b/src/app/admin/(shell)/draws/[drawId]/risk/hot/page.tsx @@ -9,7 +9,7 @@ export default async function AdminDrawRiskHotPage(props: { return ( diff --git a/src/app/admin/(shell)/draws/[drawId]/risk/pools/page.tsx b/src/app/admin/(shell)/draws/[drawId]/risk/pools/page.tsx index c4412ce..370308b 100644 --- a/src/app/admin/(shell)/draws/[drawId]/risk/pools/page.tsx +++ b/src/app/admin/(shell)/draws/[drawId]/risk/pools/page.tsx @@ -9,7 +9,7 @@ export default async function AdminDrawRiskPoolsPage(props: { return ( diff --git a/src/hooks/use-admin-play-type-catalog.ts b/src/hooks/use-admin-play-type-catalog.ts new file mode 100644 index 0000000..21c1e9f --- /dev/null +++ b/src/hooks/use-admin-play-type-catalog.ts @@ -0,0 +1,40 @@ +"use client"; + +import { useCallback, useEffect } from "react"; +import { useTranslation } from "react-i18next"; + +import { getAdminPlayTypes } from "@/api/admin-config"; +import { + formatAdminPlayCodeLabel, + getAdminPlayTypesLoadPromise, + resolveAdminPlayTypeDisplayName, +} from "@/lib/admin-play-types"; + +export function useAdminPlayTypeCatalog(): void { + useEffect(() => { + void getAdminPlayTypesLoadPromise(getAdminPlayTypes); + }, []); +} + +/** 返回按当前界面语言解析的玩法标签(显示名 + 编码) */ +export function useAdminPlayCodeLabel(): (playCode: string | null | undefined) => string { + const { i18n } = useTranslation(); + useAdminPlayTypeCatalog(); + + return useCallback( + (playCode: string | null | undefined) => formatAdminPlayCodeLabel(playCode, i18n.language), + [i18n.language], + ); +} + +/** 仅显示名,不含编码(用于下拉选项等) */ +export function useAdminPlayCodeDisplayName(): (playCode: string | null | undefined) => string { + const { i18n } = useTranslation(); + useAdminPlayTypeCatalog(); + + return useCallback( + (playCode: string | null | undefined) => + resolveAdminPlayTypeDisplayName(playCode, i18n.language), + [i18n.language], + ); +} diff --git a/src/i18n/locales/en/adminUsers.json b/src/i18n/locales/en/adminUsers.json index ff50dff..064a803 100644 --- a/src/i18n/locales/en/adminUsers.json +++ b/src/i18n/locales/en/adminUsers.json @@ -162,8 +162,7 @@ "prd.payout.manage": "Payout Confirmation · Manage", "prd.payout.review": "Payout Confirmation · Review", "prd.payout.view": "Payout Confirmation · View", - "prd.audit.all": "Audit Logs · All", - "prd.audit.self": "Audit Logs · Related to Self", - "prd.audit.finance": "Audit Logs · Finance Related" + "prd.audit.view": "Audit Logs · View", + "prd.report.view": "Reports · View" } } diff --git a/src/i18n/locales/en/draws.json b/src/i18n/locales/en/draws.json index b69b861..e333c95 100644 --- a/src/i18n/locales/en/draws.json +++ b/src/i18n/locales/en/draws.json @@ -26,6 +26,14 @@ "plannedDraw": "Planned draw", "coolingEndTime": "Cooling ends at", "resultSource": "Result source", + "resultSourceOptions": { + "rng": "RNG auto-generated", + "manual": "Manual entry" + }, + "batchStatusOptions": { + "pending_review": "Pending review", + "published": "Published" + }, "currentResultVersion": "Current result version", "settleVersion": "Settlement version", "isReopened": "Reopened", @@ -61,6 +69,7 @@ "ticketCount": "Tickets", "winCount": "Wins", "finishedAt": "Finished at", + "jackpotPayout": "Jackpot payout", "resultsTitle": "Results", "reviewAndPublish": "Review / publish", "viewReviewQueue": "View review queue", @@ -77,6 +86,9 @@ "headTail": "Head/Tail", "manualResultEntry": "Manual result entry", "currentStatusAndDraft": "Current status {{status}}. Saving creates a pending batch and does not publish it.", + "currentStatusLabel": "Current status", + "currentStatusDraftHint": "Saving creates a pending batch and does not publish it.", + "hallPreviewStatusLabel": "Hall preview", "enter23Numbers": "Please enter all 23 groups of 4 digits", "draftSaved": "Draft v{{version}} saved, waiting to be published", "saveFailed": "Failed to save", @@ -84,7 +96,7 @@ "saveDraft": "Save draft", "saving": "Saving…", "pendingBatches": "Pending batches", - "noPendingBatches": "There are no pending_review batches.", + "noPendingBatches": "There are no batches pending review.", "batchId": "Batch ID", "numberCount": "Number count", "reviewAndPublishAction": "Review and publish", @@ -107,7 +119,11 @@ "status": "Draw status", "results": "Results", "finance": "Draw finance", - "review": "Review & publish" + "review": "Review & publish", + "riskOccupancy": "Risk occupancy", + "riskHot": "Hot numbers", + "riskSoldOut": "Sold-out numbers", + "riskPools": "Risk pools" }, "statusOptions": { "all": "All", diff --git a/src/i18n/locales/en/risk.json b/src/i18n/locales/en/risk.json index 45ffab1..55173fc 100644 --- a/src/i18n/locales/en/risk.json +++ b/src/i18n/locales/en/risk.json @@ -12,6 +12,14 @@ "loadDrawListFailed": "Failed to load draw list", "enterRisk": "Enter risk", "poolsTitle": "Risk pools", + "hotPageTitle": "Hot numbers monitor", + "soldOutPageTitle": "Sold-out numbers", + "allPoolsPageTitle": "All risk pools", + "sourceReasonOptions": { + "ticket_place": "Bet placement", + "admin_manual_close": "Manual close", + "admin_manual_recover": "Manual recover" + }, "searchNumber": "Search number", "searchNumberPlaceholder": "For example 8888", "riskFilter": "Risk filter", diff --git a/src/i18n/locales/ne/adminUsers.json b/src/i18n/locales/ne/adminUsers.json index f918055..67874ef 100644 --- a/src/i18n/locales/ne/adminUsers.json +++ b/src/i18n/locales/ne/adminUsers.json @@ -162,8 +162,7 @@ "prd.payout.manage": "भुक्तानी पुष्टि · व्यवस्थापन", "prd.payout.review": "भुक्तानी पुष्टि · समीक्षा", "prd.payout.view": "भुक्तानी पुष्टि · हेर्नुहोस्", - "prd.audit.all": "अडिट लग · सबै", - "prd.audit.self": "अडिट लग · आफूसँग सम्बन्धित", - "prd.audit.finance": "अडिट लग · वित्त सम्बन्धित" + "prd.audit.view": "अडिट लग · हेर्नुहोस्", + "prd.report.view": "रिपोर्ट · हेर्नुहोस्" } } diff --git a/src/i18n/locales/ne/draws.json b/src/i18n/locales/ne/draws.json index 2de274b..fba13ea 100644 --- a/src/i18n/locales/ne/draws.json +++ b/src/i18n/locales/ne/draws.json @@ -26,6 +26,14 @@ "plannedDraw": "योजनाबद्ध ड्रअ", "coolingEndTime": "कुलिङ समाप्ति", "resultSource": "नतिजा स्रोत", + "resultSourceOptions": { + "rng": "RNG स्वचालित", + "manual": "म्यानुअल प्रविष्टि" + }, + "batchStatusOptions": { + "pending_review": "समीक्षा बाँकी", + "published": "प्रकाशित" + }, "currentResultVersion": "हालको नतिजा संस्करण", "settleVersion": "सेटलमेन्ट संस्करण", "isReopened": "फेरि खोलिएको", @@ -61,6 +69,7 @@ "ticketCount": "टिकट", "winCount": "जित", "finishedAt": "समाप्त समय", + "jackpotPayout": "ज्याकपट भुक्तानी", "resultsTitle": "परिणाम", "reviewAndPublish": "समीक्षा / प्रकाशित", "viewReviewQueue": "समीक्षा सूची हेर्नुहोस्", @@ -77,6 +86,9 @@ "headTail": "हेड/टेल", "manualResultEntry": "म्यानुअल परिणाम प्रविष्टि", "currentStatusAndDraft": "हालको स्थिति {{status}} · सेभ गरेपछि pending batch बन्छ, सिधै प्रकाशित हुँदैन", + "currentStatusLabel": "हालको स्थिति", + "currentStatusDraftHint": "सेभ गरेपछि समीक्षा बाँकी ब्याच बन्छ, सिधै प्रकाशित हुँदैन", + "hallPreviewStatusLabel": "हल पूर्वावलोकन", "enter23Numbers": "कृपया 23 वटा 4-अङ्क समूह पूरा भर्नुहोस्", "draftSaved": "ड्राफ्ट v{{version}} सुरक्षित भयो, प्रकाशनको प्रतिक्षामा", "saveFailed": "सेभ असफल भयो", @@ -84,7 +96,7 @@ "saveDraft": "ड्राफ्ट सुरक्षित गर्नुहोस्", "saving": "सेभ हुँदैछ…", "pendingBatches": "बाँकी ब्याच", - "noPendingBatches": "pending_review ब्याच छैन।", + "noPendingBatches": "समीक्षा बाँकी ब्याच छैन।", "batchId": "ब्याच ID", "numberCount": "नम्बर संख्या", "reviewAndPublishAction": "जाँचेर प्रकाशित गर्नुहोस्", @@ -107,7 +119,11 @@ "status": "ड्रअ स्थिति", "results": "परिणाम", "finance": "ड्रअ वित्त", - "review": "समीक्षा र प्रकाशन" + "review": "समीक्षा र प्रकाशन", + "riskOccupancy": "जोखिम अकुपेन्सी", + "riskHot": "हट नम्बर", + "riskSoldOut": "बिक्री समाप्त नम्बर", + "riskPools": "जोखिम पूल" }, "statusOptions": { "all": "सबै", diff --git a/src/i18n/locales/ne/risk.json b/src/i18n/locales/ne/risk.json index 5908006..88a9fed 100644 --- a/src/i18n/locales/ne/risk.json +++ b/src/i18n/locales/ne/risk.json @@ -12,6 +12,14 @@ "loadDrawListFailed": "ड्रअ सूची लोड असफल भयो", "enterRisk": "जोखिममा जानुहोस्", "poolsTitle": "जोखिम पूल", + "hotPageTitle": "हट नम्बर निगरानी", + "soldOutPageTitle": "बिक्री समाप्त नम्बर सूची", + "allPoolsPageTitle": "सबै जोखिम पूल", + "sourceReasonOptions": { + "ticket_place": "बेट अकुपेन्सी", + "admin_manual_close": "म्यानुअल बन्द", + "admin_manual_recover": "म्यानुअल पुनर्स्थापना" + }, "searchNumber": "नम्बर खोज्नुहोस्", "searchNumberPlaceholder": "जस्तै 8888", "riskFilter": "जोखिम फिल्टर", diff --git a/src/i18n/locales/zh/adminUsers.json b/src/i18n/locales/zh/adminUsers.json index 161c8d9..b452217 100644 --- a/src/i18n/locales/zh/adminUsers.json +++ b/src/i18n/locales/zh/adminUsers.json @@ -162,8 +162,7 @@ "prd.payout.manage": "派彩确认·可管理", "prd.payout.review": "派彩确认·可审核", "prd.payout.view": "派彩确认·查看", - "prd.audit.all": "审计日志·全部", - "prd.audit.self": "审计日志·自身相关", - "prd.audit.finance": "审计日志·资金相关" + "prd.audit.view": "审计日志·查看", + "prd.report.view": "报表中心·查看" } } diff --git a/src/i18n/locales/zh/draws.json b/src/i18n/locales/zh/draws.json index bbc63b9..55275d5 100644 --- a/src/i18n/locales/zh/draws.json +++ b/src/i18n/locales/zh/draws.json @@ -26,6 +26,14 @@ "plannedDraw": "计划开奖", "coolingEndTime": "冷静期结束", "resultSource": "结果来源", + "resultSourceOptions": { + "rng": "RNG 自动生成", + "manual": "人工录入" + }, + "batchStatusOptions": { + "pending_review": "待审核", + "published": "已发布" + }, "currentResultVersion": "当前结果版本", "settleVersion": "结算版本", "isReopened": "是否重开", @@ -61,6 +69,7 @@ "ticketCount": "票数", "winCount": "中奖数", "finishedAt": "完成时间", + "jackpotPayout": "奖池派彩", "resultsTitle": "开奖结果", "reviewAndPublish": "去审核 / 发布", "viewReviewQueue": "查看审核队列", @@ -77,6 +86,9 @@ "headTail": "头/尾", "manualResultEntry": "人工录入开奖结果", "currentStatusAndDraft": "当前状态 {{status}} · 保存后生成待确认批次,不会直接发布", + "currentStatusLabel": "当前状态", + "currentStatusDraftHint": "保存后生成待确认批次,不会直接发布", + "hallPreviewStatusLabel": "大厅预览", "enter23Numbers": "请完整输入 23 组 4 位数字", "draftSaved": "已保存草稿 v{{version}},等待确认发布", "saveFailed": "保存失败", @@ -84,7 +96,7 @@ "saveDraft": "保存草稿", "saving": "保存中…", "pendingBatches": "待确认批次", - "noPendingBatches": "当前没有待审核(pending_review)批次。", + "noPendingBatches": "当前没有待审核批次。", "batchId": "批次 ID", "numberCount": "号码条数", "reviewAndPublishAction": "核对并发布", diff --git a/src/i18n/locales/zh/risk.json b/src/i18n/locales/zh/risk.json index 6e27dbc..720b94c 100644 --- a/src/i18n/locales/zh/risk.json +++ b/src/i18n/locales/zh/risk.json @@ -12,6 +12,14 @@ "loadDrawListFailed": "加载期号列表失败", "enterRisk": "进入风控", "poolsTitle": "风险池", + "hotPageTitle": "热门号码监控", + "soldOutPageTitle": "售罄号码列表", + "allPoolsPageTitle": "全部风险池", + "sourceReasonOptions": { + "ticket_place": "下注占用", + "admin_manual_close": "人工关闭", + "admin_manual_recover": "人工恢复" + }, "searchNumber": "搜索号码", "searchNumberPlaceholder": "如 8888", "riskFilter": "风险筛选", @@ -73,8 +81,8 @@ "optional": "可选", "actionFilter": "动作", "noLimit": "不限", - "lock": "锁定 lock", - "release": "释放 release", + "lock": "锁定", + "release": "释放", "applyFilter": "应用筛选", "statusOptions": { "pending": "未开始", diff --git a/src/lib/admin-play-types.ts b/src/lib/admin-play-types.ts new file mode 100644 index 0000000..5331f0b --- /dev/null +++ b/src/lib/admin-play-types.ts @@ -0,0 +1,102 @@ +import type { AdminPlayTypeRow } from "@/types/api/admin-config"; + +let cachedByCode = new Map(); +let inflightLoad: Promise | null = null; + +export function getCachedAdminPlayType(playCode: string): AdminPlayTypeRow | undefined { + return cachedByCode.get(playCode); +} + +export function getCachedAdminPlayTypes(): AdminPlayTypeRow[] { + return [...cachedByCode.values()]; +} + +export function setCachedAdminPlayTypes(items: AdminPlayTypeRow[]): void { + cachedByCode = new Map(items.map((row) => [row.play_code, row])); +} + +export function clearCachedAdminPlayTypes(): void { + cachedByCode = new Map(); + inflightLoad = null; +} + +export function getAdminPlayTypesLoadPromise( + loader: () => Promise<{ items: AdminPlayTypeRow[] }>, +): Promise { + if (cachedByCode.size > 0) { + return Promise.resolve(); + } + if (inflightLoad !== null) { + return inflightLoad; + } + + inflightLoad = loader() + .then((data) => { + setCachedAdminPlayTypes(data.items); + }) + .catch(() => { + // 玩法目录加载失败时回退展示 play_code,不阻断页面。 + }) + .finally(() => { + inflightLoad = null; + }); + + return inflightLoad; +} + +function pickDisplayName(row: AdminPlayTypeRow, language: string): string | null { + const lang = language.split("-")[0]?.toLowerCase() ?? "zh"; + + if (lang === "en" && row.display_name_en?.trim()) { + return row.display_name_en.trim(); + } + if (lang === "ne" && row.display_name_ne?.trim()) { + return row.display_name_ne.trim(); + } + if (row.display_name_zh?.trim()) { + return row.display_name_zh.trim(); + } + if (row.display_name_en?.trim()) { + return row.display_name_en.trim(); + } + if (row.display_name_ne?.trim()) { + return row.display_name_ne.trim(); + } + + return null; +} + +/** 按当前语言解析玩法显示名;无配置时回退 play_code */ +export function resolveAdminPlayTypeDisplayName( + playCode: string | null | undefined, + language: string, + row?: AdminPlayTypeRow, +): string { + if (playCode == null || playCode === "") { + return "—"; + } + + const resolved = row ?? cachedByCode.get(playCode); + if (!resolved) { + return playCode; + } + + return pickDisplayName(resolved, language) ?? playCode; +} + +/** 表格展示:显示名 + 编码(与报表筛选一致) */ +export function formatAdminPlayCodeLabel( + playCode: string | null | undefined, + language: string, +): string { + if (playCode == null || playCode === "") { + return "—"; + } + + const name = resolveAdminPlayTypeDisplayName(playCode, language); + if (name === playCode) { + return playCode; + } + + return `${name} (${playCode})`; +} diff --git a/src/modules/draws/draw-detail-console.tsx b/src/modules/draws/draw-detail-console.tsx index cd720ac..f4126b2 100644 --- a/src/modules/draws/draw-detail-console.tsx +++ b/src/modules/draws/draw-detail-console.tsx @@ -25,6 +25,7 @@ import { useAdminProfile } from "@/stores/admin-session"; import { cn } from "@/lib/utils"; +import { drawResultSourceLabel, drawStatusLabel } from "./draw-display"; import { DrawStatusBadge } from "./draw-status-badge"; import { PRD_DRAW_REOPEN_MANAGE, @@ -33,12 +34,6 @@ import { PRD_PAYOUT_REVIEW, } from "./draw-prd"; -function drawStatusText(status: string, t: (key: string) => string): string { - const key = `statusOptions.${status}`; - const translated = t(key); - return translated === key ? status : translated; -} - function Field({ label, children }: { label: string; children: React.ReactNode }) { return (
@@ -123,13 +118,13 @@ export function DrawDetailConsole({ drawId }: { drawId: string }) {

- {t("hallPreviewStatus", { status: "" }).replace(/\{\{status\}\}/, "").replace(/\s+$/, "")} + {t("hallPreviewStatusLabel")}

@@ -147,7 +142,7 @@ export function DrawDetailConsole({ drawId }: { drawId: string }) {
- {data.result_source ?? "—"} + {drawResultSourceLabel(data.result_source, t)} {data.current_result_version} {data.settle_version} {data.is_reopened ? t("yes") : t("no")} diff --git a/src/modules/draws/draw-display.ts b/src/modules/draws/draw-display.ts new file mode 100644 index 0000000..69f5a32 --- /dev/null +++ b/src/modules/draws/draw-display.ts @@ -0,0 +1,62 @@ +type DrawTranslate = ( + key: string, + options?: { ns?: string; index?: number }, +) => string; + +/** 期号状态文案(draws.statusOptions) */ +export function drawStatusLabel(status: string, t: DrawTranslate): string { + const key = `statusOptions.${status}`; + const translated = t(key, { ns: "draws" }); + return translated === key ? status : translated; +} + +/** 开奖结果来源(draws.resultSourceOptions) */ +export function drawResultSourceLabel( + source: string | null | undefined, + t: DrawTranslate, +): string { + if (source == null || source === "") { + return "—"; + } + const key = `resultSourceOptions.${source}`; + const translated = t(key, { ns: "draws" }); + return translated === key ? source : translated; +} + +/** 开奖结果批次状态(draws.batchStatusOptions) */ +export function drawBatchStatusLabel(status: string, t: DrawTranslate): string { + const key = `batchStatusOptions.${status}`; + const translated = t(key, { ns: "draws" }); + return translated === key ? status : translated; +} + +/** 结算批次状态(settlement.statusOptions) */ +export function settlementBatchStatusLabel(status: string, t: DrawTranslate): string { + const key = `statusOptions.${status}`; + const translated = t(key, { ns: "settlement" }); + return translated === key ? status : translated; +} + +/** 奖项类型 + 序号 → 展示名(draws.resultSlots) */ +export function drawPrizeTypeLabel( + prizeType: string, + prizeIndex: number, + t: DrawTranslate, +): string { + if (prizeType === "first") { + return t("resultSlots.first", { ns: "draws" }); + } + if (prizeType === "second") { + return t("resultSlots.second", { ns: "draws" }); + } + if (prizeType === "third") { + return t("resultSlots.third", { ns: "draws" }); + } + if (prizeType === "starter") { + return t("resultSlots.starter", { ns: "draws", index: prizeIndex + 1 }); + } + if (prizeType === "consolation") { + return t("resultSlots.consolation", { ns: "draws", index: prizeIndex + 1 }); + } + return prizeType; +} diff --git a/src/modules/draws/draw-finance-console.tsx b/src/modules/draws/draw-finance-console.tsx index 8e4baed..10a94ed 100644 --- a/src/modules/draws/draw-finance-console.tsx +++ b/src/modules/draws/draw-finance-console.tsx @@ -26,18 +26,16 @@ import { LotteryApiBizError } from "@/types/api/errors"; import type { AdminDrawFinanceSummaryData } from "@/types/api/admin-draw-finance"; import { toast } from "sonner"; +import { useAdminCurrencyCatalog } from "@/hooks/use-admin-currency-catalog"; import { useExportLabels } from "@/hooks/use-export-labels"; +import { formatAdminMinorUnits } from "@/lib/money"; +import { drawStatusLabel, settlementBatchStatusLabel } from "./draw-display"; import { PRD_PAYOUT_MANAGE, PRD_PAYOUT_REVIEW } from "./draw-prd"; -function drawStatusText(status: string, t: (key: string) => string): string { - const key = `statusOptions.${status}`; - const translated = t(key); - return translated === key ? status : translated; -} - export function DrawFinanceConsole({ drawId }: { drawId: string }): React.ReactElement { - const { t } = useTranslation(["draws", "common"]); + const { t } = useTranslation(["draws", "settlement", "common"]); + useAdminCurrencyCatalog(); const idNum = Number(drawId); const profile = useAdminProfile(); const canRunSettlement = adminHasAnyPermission(profile?.permissions, [ @@ -96,6 +94,9 @@ export function DrawFinanceConsole({ drawId }: { drawId: string }): React.ReactE return

{err ?? t("states.noData", { ns: "common" })}

; } + const currencyCode = data.currency_code ?? "NPR"; + const formatMoney = (minor: number) => formatAdminMinorUnits(minor, currencyCode); + return (
@@ -110,7 +111,7 @@ export function DrawFinanceConsole({ drawId }: { drawId: string }): React.ReactE
{t("status")}

- +

@@ -121,11 +122,11 @@ export function DrawFinanceConsole({ drawId }: { drawId: string }): React.ReactE
{t("actualBet")} -

{data.total_bet_minor}

+

{formatMoney(data.total_bet_minor)}

{t("currentPayout")} -

{data.total_payout_minor}

+

{formatMoney(data.total_payout_minor)}

{t("grossProfit")} @@ -135,7 +136,7 @@ export function DrawFinanceConsole({ drawId }: { drawId: string }): React.ReactE data.approx_house_gross_minor >= 0 ? "text-emerald-600" : "text-destructive", )} > - {data.approx_house_gross_minor} + {formatMoney(data.approx_house_gross_minor)}

@@ -185,7 +186,7 @@ export function DrawFinanceConsole({ drawId }: { drawId: string }): React.ReactE {t("ticketCount")} {t("winCount")} {t("payoutTotal")} - {t("jackpot")} + {t("jackpotPayout")} {t("finishedAt")} @@ -194,7 +195,9 @@ export function DrawFinanceConsole({ drawId }: { drawId: string }): React.ReactE {b.id} - {drawStatusText(b.status, t)} + + {settlementBatchStatusLabel(b.status, t)} + {b.total_ticket_count} @@ -203,10 +206,10 @@ export function DrawFinanceConsole({ drawId }: { drawId: string }): React.ReactE {b.total_win_count} - {b.total_payout_amount} + {formatMoney(b.total_payout_amount)} - {b.total_jackpot_payout_amount} + {formatMoney(b.total_jackpot_payout_amount)} {b.finished_at ?? "—"} diff --git a/src/modules/draws/draw-publish-console.tsx b/src/modules/draws/draw-publish-console.tsx index 3f392d2..abd55ea 100644 --- a/src/modules/draws/draw-publish-console.tsx +++ b/src/modules/draws/draw-publish-console.tsx @@ -23,6 +23,7 @@ import { useAdminProfile } from "@/stores/admin-session"; import { LotteryApiBizError } from "@/types/api/errors"; import type { AdminDrawBatchRow, AdminDrawBatchesData } from "@/types/api/admin-draws"; +import { drawBatchStatusLabel, drawPrizeTypeLabel, drawStatusLabel } from "./draw-display"; import { PRD_DRAW_RESULT_MANAGE } from "./draw-prd"; export function DrawPublishConsole({ drawId, batchId }: { drawId: string; batchId: string }) { @@ -73,7 +74,12 @@ export function DrawPublishConsole({ drawId, batchId }: { drawId: string; batchI setPublishing(true); try { const res = await postAdminPublishResultBatch(idNum, batchNum); - toast.success(t("publishSuccess", { drawNo: res.draw_no, status: res.status })); + toast.success( + t("publishSuccess", { + drawNo: res.draw_no, + status: drawStatusLabel(res.status, t), + }), + ); await load(); } catch (e) { const msg = e instanceof LotteryApiBizError ? e.message : t("publishFailed"); @@ -125,7 +131,9 @@ export function DrawPublishConsole({ drawId, batchId }: { drawId: string; batchI {!canPublish && canManageDraw ? ( {t("cannotPublish")} - {t("cannotPublishDesc", { status: batch.status })} + + {t("cannotPublishDesc", { status: drawBatchStatusLabel(batch.status, t) })} + ) : null} {canPublish ? ( @@ -147,7 +155,9 @@ export function DrawPublishConsole({ drawId, batchId }: { drawId: string; batchI {batch.items.map((it) => ( - {it.prize_type} + + {drawPrizeTypeLabel(it.prize_type, it.prize_index, t)} + {it.prize_index} {it.number_4d} diff --git a/src/modules/draws/draw-results-console.tsx b/src/modules/draws/draw-results-console.tsx index eee06b9..4604eb8 100644 --- a/src/modules/draws/draw-results-console.tsx +++ b/src/modules/draws/draw-results-console.tsx @@ -21,6 +21,7 @@ import { useAdminProfile } from "@/stores/admin-session"; import { LotteryApiBizError } from "@/types/api/errors"; import type { AdminDrawBatchRow, AdminDrawBatchesData } from "@/types/api/admin-draws"; +import { drawPrizeTypeLabel } from "./draw-display"; import { PRD_DRAW_RESULT_MANAGE } from "./draw-prd"; import { DrawStatusBadge } from "./draw-status-badge"; @@ -129,7 +130,9 @@ function BatchTable({ batch }: { batch: AdminDrawBatchRow }) { {batch.items.map((it) => ( - {it.prize_type} + + {drawPrizeTypeLabel(it.prize_type, it.prize_index, t)} + {it.prize_index} {it.number_4d} diff --git a/src/modules/draws/draw-review-console.tsx b/src/modules/draws/draw-review-console.tsx index 66eef77..dd9261d 100644 --- a/src/modules/draws/draw-review-console.tsx +++ b/src/modules/draws/draw-review-console.tsx @@ -23,6 +23,7 @@ import { useAdminProfile } from "@/stores/admin-session"; import { LotteryApiBizError } from "@/types/api/errors"; import type { AdminDrawBatchesData } from "@/types/api/admin-draws"; +import { drawStatusLabel } from "./draw-display"; import { PRD_DRAW_RESULT_MANAGE } from "./draw-prd"; import { DrawStatusBadge } from "./draw-status-badge"; @@ -129,13 +130,12 @@ export function DrawReviewConsole({ drawId }: { drawId: string }) { {t("manualResultEntry")}

- {t("currentStatusAndDraft", { - status: data.draw_status, - }).split(data.draw_status)[0]} - - {t("currentStatusAndDraft", { - status: data.draw_status, - }).split(data.draw_status)[1] ?? ""} + {t("currentStatusLabel")} + + · {t("currentStatusDraftHint")}

diff --git a/src/modules/draws/draws-index-console.tsx b/src/modules/draws/draws-index-console.tsx index b8304c5..07d2e90 100644 --- a/src/modules/draws/draws-index-console.tsx +++ b/src/modules/draws/draws-index-console.tsx @@ -27,7 +27,9 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; +import { useAdminCurrencyCatalog } from "@/hooks/use-admin-currency-catalog"; import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter"; +import { formatAdminMinorUnits } from "@/lib/money"; import { useExportLabels } from "@/hooks/use-export-labels"; import { adminHasAnyPermission } from "@/lib/admin-permissions"; import { cn } from "@/lib/utils"; @@ -35,6 +37,7 @@ import { useAdminProfile } from "@/stores/admin-session"; import { LotteryApiBizError } from "@/types/api/errors"; import type { AdminDrawListData, AdminDrawListItem } from "@/types/api/admin-draws"; +import { drawStatusLabel } from "./draw-display"; import { PRD_DRAW_RESULT_MANAGE } from "./draw-prd"; import { DrawStatusBadge } from "./draw-status-badge"; @@ -67,7 +70,9 @@ function drawAdminStatusSelectLabel(raw: unknown, t: (key: string) => string): s export function DrawsIndexConsole() { const { t } = useTranslation(["draws", "common"]); const exportLabels = useExportLabels("drawsList"); + useAdminCurrencyCatalog(); const formatDt = useAdminDateTimeFormatter(); + const defaultCurrency = "NPR"; const profile = useAdminProfile(); const canManageDraw = adminHasAnyPermission(profile?.permissions, [PRD_DRAW_RESULT_MANAGE]); const [data, setData] = useState(null); @@ -271,22 +276,28 @@ export function DrawsIndexConsole() { - - {row.total_bet_minor ?? "—"} + + {row.total_bet_minor != null + ? formatAdminMinorUnits(row.total_bet_minor, defaultCurrency) + : "—"} - - {row.total_payout_minor ?? "—"} + + {row.total_payout_minor != null + ? formatAdminMinorUnits(row.total_payout_minor, defaultCurrency) + : "—"} - {row.profit_loss_minor ?? "—"} + {row.profit_loss_minor != null + ? formatAdminMinorUnits(row.profit_loss_minor, defaultCurrency) + : "—"} (REPORTS[0].key); const [filters, setFilters] = useState(emptyFilters); @@ -388,17 +396,20 @@ export function ReportsConsole() { const loadPlayOptions = useCallback(async () => { try { - const payload = await getAdminPlayTypes(); + await getAdminPlayTypesLoadPromise(getAdminPlayTypes); setPlayOptions( - payload.items.map((item) => ({ + getCachedAdminPlayTypes().map((item) => ({ code: item.play_code, - label: optionText(item.display_name_zh, item.play_code), + label: optionText( + resolveAdminPlayTypeDisplayName(item.play_code, i18n.language, item), + item.play_code, + ), })), ); } catch { setPlayOptions([]); } - }, []); + }, [i18n.language]); useEffect(() => { void loadPlayOptions(); @@ -1056,7 +1067,7 @@ export function ReportsConsole() { #{item.id} {item.action_type} {formatPlainMoney(item.amount, result.raw.currency_code)} - {item.play_code || "-"} + {playCodeLabel(item.play_code)} {item.ticket_no || "-"} {item.player_id || "-"} {item.source_reason || "-"} @@ -1115,7 +1126,7 @@ export function ReportsConsole() { if (result.key === "play_dimension") { return result.raw.map((item) => ( - {item.play_code} + {playCodeLabel(item.play_code)} {item.dimension}D {formatPlainMoney(item.total_bet_minor, "NPR")} {formatPlainMoney(item.total_payout_minor, "NPR")} @@ -1130,7 +1141,7 @@ export function ReportsConsole() { if (result.key === "rebate_commission") { return result.raw.map((item) => ( - {item.play_code} + {playCodeLabel(item.play_code)} {item.order_count} {formatPlainMoney(item.total_rebate_minor, "NPR")} {item.ticket_item_count} diff --git a/src/modules/risk/risk-display.ts b/src/modules/risk/risk-display.ts new file mode 100644 index 0000000..0785acc --- /dev/null +++ b/src/modules/risk/risk-display.ts @@ -0,0 +1,27 @@ +type RiskTranslate = (key: string, options?: { ns?: string }) => string; + +/** 风控占用流水来源(risk.sourceReasonOptions) */ +export function riskSourceReasonLabel( + reason: string | null | undefined, + t: RiskTranslate, +): string { + if (reason == null || reason === "") { + return "—"; + } + const key = `sourceReasonOptions.${reason}`; + const translated = t(key, { ns: "risk" }); + return translated === key ? reason : translated; +} + +export type RiskPoolsPageTitleKey = "hotPageTitle" | "soldOutPageTitle" | "allPoolsPageTitle"; + +/** 锁定 / 释放动作(risk.lock / risk.release) */ +export function riskActionTypeLabel(action: string, t: RiskTranslate): string { + if (action === "lock") { + return t("lock", { ns: "risk" }); + } + if (action === "release") { + return t("release", { ns: "risk" }); + } + return action; +} diff --git a/src/modules/risk/risk-lock-logs-console.tsx b/src/modules/risk/risk-lock-logs-console.tsx index 833e622..19365a2 100644 --- a/src/modules/risk/risk-lock-logs-console.tsx +++ b/src/modules/risk/risk-lock-logs-console.tsx @@ -27,33 +27,30 @@ import { TableRow, } from "@/components/ui/table"; import { useAdminCurrencyCatalog } from "@/hooks/use-admin-currency-catalog"; +import { useAdminPlayCodeLabel } from "@/hooks/use-admin-play-type-catalog"; import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter"; +import { riskActionTypeLabel, riskSourceReasonLabel } from "@/modules/risk/risk-display"; import { formatAdminMinorUnits } from "@/lib/money"; import { LotteryApiBizError } from "@/types/api/errors"; import type { AdminRiskLockLogListData, AdminRiskLockLogRow } from "@/types/api/admin-risk"; const ACTION_ALL = "__all__"; -function riskActionLabel( +function riskActionFilterLabel( value: string, t: (key: string) => string, ): string { if (value === ACTION_ALL) { return t("noLimit"); } - if (value === "lock") { - return t("lock"); - } - if (value === "release") { - return t("release"); - } - return value; + return riskActionTypeLabel(value, t); } export function RiskLockLogsConsole({ drawId }: { drawId: number }) { const { t } = useTranslation(["risk", "common"]); const exportLabels = useExportLabels("riskLockLogs"); useAdminCurrencyCatalog(); + const playCodeLabel = useAdminPlayCodeLabel(); const formatDt = useAdminDateTimeFormatter(); const [page, setPage] = useState(1); const [perPage, setPerPage] = useState(10); @@ -129,7 +126,7 @@ export function RiskLockLogsConsole({ drawId }: { drawId: number }) { }} > - {riskActionLabel(draftAction, t)} + {riskActionFilterLabel(draftAction, t)} {t("noLimit")} @@ -185,16 +182,16 @@ export function RiskLockLogsConsole({ drawId }: { drawId: number }) { {row.normalized_number} - {riskActionLabel(row.action_type, t)} + {riskActionTypeLabel(row.action_type, t)} {formatAdminMinorUnits(row.amount, data?.currency_code ?? "NPR")} - {row.source_reason ?? "—"} + {riskSourceReasonLabel(row.source_reason, t)} {row.ticket_no ?? "—"} - {row.play_code ?? "—"} + {playCodeLabel(row.play_code)}
))}
diff --git a/src/modules/risk/risk-pool-detail-console.tsx b/src/modules/risk/risk-pool-detail-console.tsx index 394531b..bf2367a 100644 --- a/src/modules/risk/risk-pool-detail-console.tsx +++ b/src/modules/risk/risk-pool-detail-console.tsx @@ -18,8 +18,10 @@ import { TableRow, } from "@/components/ui/table"; import { useAdminCurrencyCatalog } from "@/hooks/use-admin-currency-catalog"; +import { useAdminPlayCodeLabel } from "@/hooks/use-admin-play-type-catalog"; import { useExportLabels } from "@/hooks/use-export-labels"; import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter"; +import { riskActionTypeLabel, riskSourceReasonLabel } from "@/modules/risk/risk-display"; import { formatAdminMinorUnits } from "@/lib/money"; import { cn } from "@/lib/utils"; import { LotteryApiBizError } from "@/types/api/errors"; @@ -35,6 +37,7 @@ export function RiskPoolDetailConsole({ const { t } = useTranslation(["risk", "common"]); const exportLabels = useExportLabels("riskPoolDetail", { number: number4d }); useAdminCurrencyCatalog(); + const playCodeLabel = useAdminPlayCodeLabel(); const formatDt = useAdminDateTimeFormatter(); const [page, setPage] = useState(1); const [perPage, setPerPage] = useState(10); @@ -172,15 +175,15 @@ export function RiskPoolDetailConsole({ {row.created_at ? formatDt(row.created_at) : "—"} - {row.action_type} + {riskActionTypeLabel(row.action_type, t)} {formatAdminMinorUnits(row.amount, currencyCode)} - {row.source_reason ?? "—"} + {riskSourceReasonLabel(row.source_reason, t)} {row.ticket_no ?? "—"} - {row.play_code ?? "—"} + {playCodeLabel(row.play_code)}
))} diff --git a/src/modules/risk/risk-pools-console.tsx b/src/modules/risk/risk-pools-console.tsx index bf44130..6611513 100644 --- a/src/modules/risk/risk-pools-console.tsx +++ b/src/modules/risk/risk-pools-console.tsx @@ -37,6 +37,7 @@ import { useExportLabels } from "@/hooks/use-export-labels"; import { formatAdminMinorUnits } from "@/lib/money"; import { cn } from "@/lib/utils"; import { LotteryApiBizError } from "@/types/api/errors"; +import type { RiskPoolsPageTitleKey } from "@/modules/risk/risk-display"; import type { AdminRiskPoolListData, AdminRiskPoolRow } from "@/types/api/admin-risk"; const SORT_OPTIONS: { value: "usage_desc" | "locked_desc" | "remaining_asc" | "number_asc"; label: string }[] = @@ -59,7 +60,9 @@ type RiskFilter = "all" | "sold_out" | "high_risk"; type RiskPoolsConsoleProps = { drawId: number; - title: string; + /** @deprecated 优先使用 titleKey */ + title?: string; + titleKey?: RiskPoolsPageTitleKey; soldOutOnly: boolean; defaultSort: "usage_desc" | "locked_desc" | "remaining_asc" | "number_asc"; allowSortChange?: boolean; @@ -68,11 +71,13 @@ type RiskPoolsConsoleProps = { export function RiskPoolsConsole({ drawId, title, + titleKey, soldOutOnly, defaultSort, allowSortChange = false, }: RiskPoolsConsoleProps) { const { t } = useTranslation(["risk", "common"]); + const pageTitle = titleKey ? t(titleKey) : (title ?? t("poolsTitle")); const exportLabels = useExportLabels("riskPools"); useAdminCurrencyCatalog(); const [sort, setSort] = useState(defaultSort); @@ -145,7 +150,7 @@ export function RiskPoolsConsole({ return ( - {title} + {pageTitle}
+
+
+

{t("system.frontendConfig", { ns: "config", defaultValue: "前端配置" })}

+
+ +
+
+ +

+ {t("system.fields.playRulesHtmlDesc", { ns: "config", defaultValue: "该内容将直接在玩家端的玩法规则页面作为 HTML 渲染。留空则显示前端默认提示。" })} +

+