feat: 优化多语言文案接入并升级大厅与钱包交互体验

- 补充大厅下注结果、快速填单、查中奖、分页与通用操作多语言文案
- 移除多处界面默认文案回退,统一改用 i18n 配置输出
- 优化大厅快速填单、开奖结果入口与注单筛选区域视觉交互
- 重构钱包转入转出弹窗与流水卡片样式,增强金额与状态信息展示
This commit is contained in:
2026-05-20 17:56:18 +08:00
parent a2a29107f8
commit 626914feb6
20 changed files with 578 additions and 359 deletions

View File

@@ -82,15 +82,15 @@ function SubmittingPanel() {
</div> </div>
<DialogHeader className="mt-8 items-center gap-2"> <DialogHeader className="mt-8 items-center gap-2">
<DialogTitle className="text-xl font-black text-slate-950"> <DialogTitle className="text-xl font-black text-slate-950">
{t("hall.preview.processingTitle", { defaultValue: "正在提交下注" })} {t("hall.preview.processingTitle")}
</DialogTitle> </DialogTitle>
<DialogDescription className="max-w-[220px] text-center text-sm leading-relaxed text-slate-500"> <DialogDescription className="max-w-[220px] text-center text-sm leading-relaxed text-slate-500">
{t("hall.preview.processingDescription", { defaultValue: "请勿关闭页面或返回上一页。" })} {t("hall.preview.processingDescription")}
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="mt-7 inline-flex items-center gap-2 text-sm font-semibold text-slate-500"> <div className="mt-7 inline-flex items-center gap-2 text-sm font-semibold text-slate-500">
<LoaderCircle className="size-4 animate-spin text-[#0755c7]" /> <LoaderCircle className="size-4 animate-spin text-[#0755c7]" />
{t("hall.preview.processingProgress", { defaultValue: "正在处理注单..." })} {t("hall.preview.processingProgress")}
</div> </div>
</div> </div>
</DialogContent> </DialogContent>
@@ -143,7 +143,7 @@ export function HallBetPreviewDialog({
onClick={() => onOpenChange(false)} onClick={() => onOpenChange(false)}
disabled={placing} disabled={placing}
className="absolute right-3 top-3 inline-flex size-9 items-center justify-center rounded-full text-slate-400 transition-colors hover:bg-slate-100 hover:text-slate-700 disabled:opacity-50" className="absolute right-3 top-3 inline-flex size-9 items-center justify-center rounded-full text-slate-400 transition-colors hover:bg-slate-100 hover:text-slate-700 disabled:opacity-50"
aria-label={t("actions.close", { defaultValue: "关闭" })} aria-label={t("actions.close")}
> >
<XIcon className="size-5" /> <XIcon className="size-5" />
</button> </button>
@@ -193,10 +193,10 @@ export function HallBetPreviewDialog({
<tr> <tr>
<th className="w-10 border-r border-[#dfe8f6] px-2 py-3 text-center font-black">No.</th> <th className="w-10 border-r border-[#dfe8f6] px-2 py-3 text-center font-black">No.</th>
<th className="border-r border-[#dfe8f6] px-2 py-3 text-center font-black"> <th className="border-r border-[#dfe8f6] px-2 py-3 text-center font-black">
{t("hall.result.number", { defaultValue: "号码" })} {t("hall.result.number")}
</th> </th>
<th className="border-r border-[#dfe8f6] px-2 py-3 text-center font-black"> <th className="border-r border-[#dfe8f6] px-2 py-3 text-center font-black">
{t("orders.play", { defaultValue: "玩法" })} {t("orders.play")}
</th> </th>
<th className="border-r border-[#dfe8f6] px-2 py-3 text-center font-black"> <th className="border-r border-[#dfe8f6] px-2 py-3 text-center font-black">
{t("hall.preview.amount")} {t("hall.preview.amount")}
@@ -283,7 +283,7 @@ export function HallBetPreviewDialog({
) : ( ) : (
<div className="flex items-center gap-2 rounded-xl border border-emerald-100 bg-emerald-50 px-3 py-3 text-sm font-semibold text-emerald-700"> <div className="flex items-center gap-2 rounded-xl border border-emerald-100 bg-emerald-50 px-3 py-3 text-sm font-semibold text-emerald-700">
<CheckCircle2 className="size-5" /> <CheckCircle2 className="size-5" />
{t("hall.preview.noWarnings", { defaultValue: "当前预览未发现明显风险。" })} {t("hall.preview.noWarnings")}
</div> </div>
)} )}
</> </>

View File

@@ -47,7 +47,7 @@ export function HallBetResultDialog({
type="button" type="button"
onClick={() => onOpenChange(false)} onClick={() => onOpenChange(false)}
className="absolute right-3 top-3 inline-flex size-9 items-center justify-center rounded-full text-slate-400 transition-colors hover:bg-slate-100 hover:text-slate-700" className="absolute right-3 top-3 inline-flex size-9 items-center justify-center rounded-full text-slate-400 transition-colors hover:bg-slate-100 hover:text-slate-700"
aria-label={t("actions.close", { defaultValue: "关闭" })} aria-label={t("actions.close")}
> >
<XIcon className="size-5" /> <XIcon className="size-5" />
</button> </button>
@@ -56,11 +56,11 @@ export function HallBetResultDialog({
</div> </div>
<DialogHeader className="mt-4 items-center gap-2"> <DialogHeader className="mt-4 items-center gap-2">
<DialogTitle className="text-2xl font-black text-slate-950"> <DialogTitle className="text-2xl font-black text-slate-950">
{t("hall.result.title", { defaultValue: "下注结果" })} {t("hall.result.title")}
</DialogTitle> </DialogTitle>
{data ? ( {data ? (
<DialogDescription className="text-sm leading-relaxed text-slate-500"> <DialogDescription className="text-sm leading-relaxed text-slate-500">
{t("hall.result.draw", { defaultValue: "期号" })}{" "} {t("hall.result.draw")}{" "}
<span className="font-mono font-black text-[#e5002c]">{data.draw.draw_id}</span> <span className="font-mono font-black text-[#e5002c]">{data.draw.draw_id}</span>
</DialogDescription> </DialogDescription>
) : null} ) : null}
@@ -73,20 +73,20 @@ export function HallBetResultDialog({
<div className="space-y-4 py-4"> <div className="space-y-4 py-4">
{!data ? ( {!data ? (
<p className="text-sm text-slate-500"> <p className="text-sm text-slate-500">
{t("hall.result.empty", { defaultValue: "暂无结果。" })} {t("hall.result.empty")}
</p> </p>
) : ( ) : (
<> <>
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<div className="rounded-lg border border-emerald-100 bg-emerald-50 px-3 py-4 text-center"> <div className="rounded-lg border border-emerald-100 bg-emerald-50 px-3 py-4 text-center">
<p className="text-sm font-bold text-emerald-700"> <p className="text-sm font-bold text-emerald-700">
{t("hall.result.successCount", { defaultValue: "成功注项" })} {t("hall.result.successCount")}
</p> </p>
<p className="mt-2 text-4xl font-black tabular-nums text-emerald-600">{totalSuccess}</p> <p className="mt-2 text-4xl font-black tabular-nums text-emerald-600">{totalSuccess}</p>
</div> </div>
<div className="rounded-lg border border-rose-100 bg-rose-50 px-3 py-4 text-center"> <div className="rounded-lg border border-rose-100 bg-rose-50 px-3 py-4 text-center">
<p className="text-sm font-bold text-rose-600"> <p className="text-sm font-bold text-rose-600">
{t("hall.result.failureCount", { defaultValue: "失败注项" })} {t("hall.result.failureCount")}
</p> </p>
<p className="mt-2 text-4xl font-black tabular-nums text-[#e5002c]">{totalFailure}</p> <p className="mt-2 text-4xl font-black tabular-nums text-[#e5002c]">{totalFailure}</p>
</div> </div>
@@ -98,7 +98,7 @@ export function HallBetResultDialog({
<ClipboardList className="size-5" /> <ClipboardList className="size-5" />
</span> </span>
<span className="truncate text-sm font-black text-[#0b3f96]"> <span className="truncate text-sm font-black text-[#0b3f96]">
{t("hall.result.actual", { defaultValue: "实扣金额" })} {t("hall.result.actual")}
</span> </span>
</div> </div>
<span className="shrink-0 font-mono text-base font-black tabular-nums text-[#0b3f96]"> <span className="shrink-0 font-mono text-base font-black tabular-nums text-[#0b3f96]">
@@ -108,11 +108,11 @@ export function HallBetResultDialog({
<div className="grid gap-2 rounded-lg border border-[#e8eef7] bg-white px-4 py-3 text-xs text-slate-600 sm:grid-cols-2"> <div className="grid gap-2 rounded-lg border border-[#e8eef7] bg-white px-4 py-3 text-xs text-slate-600 sm:grid-cols-2">
<p> <p>
{t("hall.result.orderNo", { defaultValue: "订单号" })}:{" "} {t("hall.result.orderNo")}:{" "}
<span className="font-mono font-black text-[#0b3f96]">{data.order_no}</span> <span className="font-mono font-black text-[#0b3f96]">{data.order_no}</span>
</p> </p>
<p> <p>
{t("hall.result.balanceAfter", { defaultValue: "剩余余额" })}:{" "} {t("hall.result.balanceAfter")}:{" "}
<span className="font-semibold text-slate-950"> <span className="font-semibold text-slate-950">
{formatMinorAsCurrency(data.balance_after, currencyCode)} {formatMinorAsCurrency(data.balance_after, currencyCode)}
</span> </span>
@@ -121,9 +121,7 @@ export function HallBetResultDialog({
<div className="space-y-2"> <div className="space-y-2">
<p className="text-sm font-black text-[#e5002c]"> <p className="text-sm font-black text-[#e5002c]">
{t("hall.result.items", { {t("hall.result.items")}
defaultValue: "成功注项明细",
})}
</p> </p>
<div className="overflow-x-auto rounded-xl border border-[#dfe8f6]"> <div className="overflow-x-auto rounded-xl border border-[#dfe8f6]">
<table className="min-w-[430px] w-full border-collapse text-xs"> <table className="min-w-[430px] w-full border-collapse text-xs">
@@ -131,13 +129,13 @@ export function HallBetResultDialog({
<tr> <tr>
<th className="w-10 border-r border-[#dfe8f6] px-2 py-2.5 text-center font-black">No.</th> <th className="w-10 border-r border-[#dfe8f6] px-2 py-2.5 text-center font-black">No.</th>
<th className="border-r border-[#dfe8f6] px-2 py-2.5 text-center font-black"> <th className="border-r border-[#dfe8f6] px-2 py-2.5 text-center font-black">
{t("hall.result.number", { defaultValue: "号码" })} {t("hall.result.number")}
</th> </th>
<th className="border-r border-[#dfe8f6] px-2 py-2.5 text-center font-black"> <th className="border-r border-[#dfe8f6] px-2 py-2.5 text-center font-black">
{t("orders.play", { defaultValue: "玩法" })} {t("orders.play")}
</th> </th>
<th className="px-2 py-2.5 text-center font-black"> <th className="px-2 py-2.5 text-center font-black">
{t("hall.result.actualDeduct", { defaultValue: "实扣" })} {t("hall.result.actualDeduct")}
</th> </th>
</tr> </tr>
</thead> </thead>
@@ -171,12 +169,12 @@ export function HallBetResultDialog({
</div> </div>
{totalFailure === 0 ? ( {totalFailure === 0 ? (
<div className="rounded-lg border border-emerald-100 bg-emerald-50 px-3 py-3 text-sm font-semibold text-emerald-700"> <div className="rounded-lg border border-emerald-100 bg-emerald-50 px-3 py-3 text-sm font-semibold text-emerald-700">
{t("hall.result.noFailures", { defaultValue: "本次提交没有失败注项。" })} {t("hall.result.noFailures")}
</div> </div>
) : ( ) : (
<div className="space-y-2 rounded-lg border border-rose-100 bg-rose-50 px-3 py-3"> <div className="space-y-2 rounded-lg border border-rose-100 bg-rose-50 px-3 py-3">
<p className="text-sm font-black text-[#e5002c]"> <p className="text-sm font-black text-[#e5002c]">
{t("hall.result.failedItems", { defaultValue: "失败注项明细" })} {t("hall.result.failedItems")}
</p> </p>
{failedItems.map((item, index) => ( {failedItems.map((item, index) => (
<div <div
@@ -188,7 +186,7 @@ export function HallBetResultDialog({
{playLabel(item.play_code, t)} {playLabel(item.play_code, t)}
</span> </span>
<span className="shrink-0 font-bold text-[#e5002c]"> <span className="shrink-0 font-bold text-[#e5002c]">
{item.fail_reason_text ?? item.fail_reason_code ?? t("hall.result.failed", { defaultValue: "失败" })} {item.fail_reason_text ?? item.fail_reason_code ?? t("hall.result.failed")}
</span> </span>
</div> </div>
))} ))}
@@ -208,7 +206,7 @@ export function HallBetResultDialog({
render={<Link href="/orders" />} render={<Link href="/orders" />}
> >
<ClipboardList className="size-5" /> <ClipboardList className="size-5" />
{t("hall.result.viewBets", { defaultValue: "查看注单" })} {t("hall.result.viewBets")}
</Button> </Button>
<Button <Button
type="button" type="button"
@@ -216,7 +214,7 @@ export function HallBetResultDialog({
className="h-12 rounded-lg border-0 bg-[#07459f] text-base font-black text-white shadow-[0_10px_24px_rgba(7,69,159,0.24)] hover:bg-[#063b88]" className="h-12 rounded-lg border-0 bg-[#07459f] text-base font-black text-white shadow-[0_10px_24px_rgba(7,69,159,0.24)] hover:bg-[#063b88]"
> >
<Ticket className="size-5" /> <Ticket className="size-5" />
{t("hall.result.continueBetting", { defaultValue: "继续下注" })} {t("hall.result.continueBetting")}
</Button> </Button>
</div> </div>
</DialogContent> </DialogContent>

View File

@@ -625,19 +625,13 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
removed removed
? t("hall.playConfig.playClosedDraftCleared", { ? t("hall.playConfig.playClosedDraftCleared", {
playCode: evt.play_code, playCode: evt.play_code,
defaultValue: "{{playCode}} 已关闭,相关草稿金额已清除。",
}) })
: t("hall.playConfig.playClosed", { : t("hall.playConfig.playClosed", {
playCode: evt.play_code, playCode: evt.play_code,
defaultValue: "{{playCode}} 已关闭。",
}), }),
); );
} else { } else {
toast.message( toast.message(t("hall.playConfig.updated"));
t("hall.playConfig.updated", {
defaultValue: "玩法配置已更新,已刷新下注表格。",
}),
);
} }
}; };
@@ -645,12 +639,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
void loadCatalog(); void loadCatalog();
setPreviewOpen(false); setPreviewOpen(false);
setPreviewData(null); setPreviewData(null);
toast.message( toast.message(evt.message ?? t("hall.playConfig.oddsUpdated"));
evt.message ??
t("hall.playConfig.oddsUpdated", {
defaultValue: "赔率已更新,请重新预览注单。",
}),
);
}; };
channel.listen(".play.toggle", onPlayToggle); channel.listen(".play.toggle", onPlayToggle);
@@ -840,7 +829,6 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
t("hall.placePartialFailed", { t("hall.placePartialFailed", {
success: data.summary.success_count ?? 0, success: data.summary.success_count ?? 0,
failed: data.summary.failure_count ?? 0, failed: data.summary.failure_count ?? 0,
defaultValue: "{{success}} 个成功,{{failed}} 个失败",
}), }),
); );
} }
@@ -947,7 +935,6 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
<p className="rounded-full bg-amber-100 px-3 py-1 text-xs font-bold text-amber-800"> <p className="rounded-full bg-amber-100 px-3 py-1 text-xs font-bold text-amber-800">
{t("results.jackpotGap", { {t("results.jackpotGap", {
count: jackpot.draws_since_last_burst, count: jackpot.draws_since_last_burst,
defaultValue: "距上次爆池 {{count}} 期",
})} })}
</p> </p>
) : null} ) : null}
@@ -970,49 +957,74 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
</div> </div>
) : ( ) : (
<> <>
<div className="space-y-3 rounded-xl border border-[#e9eef7] bg-white p-4 shadow-[0_8px_28px_rgba(15,23,42,0.05)]"> <div className="rounded-xl border border-[#e3ebf7] bg-white px-3 py-3 shadow-[0_8px_24px_rgba(15,23,42,0.045)]">
<div className="flex flex-wrap items-center justify-between gap-2"> <div className="flex items-start justify-between gap-3">
<div> <div className="min-w-0">
<p className="text-sm font-semibold text-slate-900"> <p className="text-sm font-bold leading-5 text-slate-950">
{t("hall.quickFill.title", { defaultValue: "快速填单" })} {t("hall.quickFill.title")}
</p> </p>
<p className="text-xs text-slate-500"> <p className="mt-0.5 text-xs leading-5 text-slate-500">
{t("hall.quickFill.description", { {t("hall.quickFill.description")}
defaultValue: "收藏号码、最近号码可一键填入当前行。",
})}
</p> </p>
</div> </div>
<div className="flex flex-wrap gap-2"> <div className="flex shrink-0 items-center gap-1.5">
<Button type="button" variant="outline" size="sm" onClick={clearAllRows}>
{t("hall.quickFill.clearAll", { defaultValue: "批量清空" })}
</Button>
{activeRow?.number ? ( {activeRow?.number ? (
<Button <Button
type="button" type="button"
variant="outline" variant="ghost"
size="sm" size="icon"
aria-label={
favorites.includes(activeRow.number)
? t("hall.quickFill.unfavorite")
: t("hall.quickFill.favorite")
}
aria-pressed={favorites.includes(activeRow.number)}
title={
favorites.includes(activeRow.number)
? t("hall.quickFill.unfavorite")
: t("hall.quickFill.favorite")
}
className={cn(
"size-8 rounded-full border border-[#ffd7db] bg-[#fff7f8] text-[#d81435] hover:bg-[#fff1f3] hover:text-[#b80f2b]",
favorites.includes(activeRow.number) &&
"border-[#d81435] bg-[#d81435] text-white hover:bg-[#c51230] hover:text-white",
)}
onClick={() => toggleFavoriteNumber(activeRow.number)} onClick={() => toggleFavoriteNumber(activeRow.number)}
> >
<Star className="mr-1 size-4" /> <Star
{favorites.includes(activeRow.number) className={cn(
? t("hall.quickFill.unfavorite", { defaultValue: "取消收藏" }) "size-4",
: t("hall.quickFill.favorite", { defaultValue: "收藏号码" })} favorites.includes(activeRow.number) && "fill-current",
)}
aria-hidden
/>
</Button> </Button>
) : null} ) : null}
<Button
type="button"
variant="ghost"
size="icon"
aria-label={t("hall.quickFill.clearAll")}
title={t("hall.quickFill.clearAll")}
className="size-8 rounded-full border border-[#dfe6f0] bg-[#f8fafc] text-slate-500 hover:bg-slate-100 hover:text-slate-900"
onClick={clearAllRows}
>
<Trash2 className="size-4" aria-hidden />
</Button>
</div> </div>
</div> </div>
<div className="space-y-2"> <div className="mt-3 space-y-2">
<p className="text-[11px] font-medium uppercase tracking-wide text-slate-400"> {favoriteChips.length > 0 ? (
{t("hall.quickFill.favorites", { defaultValue: "收藏" })} <div className="flex flex-wrap items-center gap-1.5">
</p> <span className="mr-0.5 text-[11px] font-semibold text-[#d81435]">
<div className="flex flex-wrap gap-2"> {t("hall.quickFill.favorites")}
{favoriteChips.length > 0 ? ( </span>
favoriteChips.map((number) => ( {favoriteChips.map((number) => (
<button <button
key={`fav-${number}`} key={`fav-${number}`}
type="button" type="button"
className="inline-flex items-center gap-1 rounded-full border border-[#ffd7db] bg-[#fff3f5] px-3 py-1 text-xs font-medium text-[#d81435]" className="inline-flex h-7 items-center gap-1 rounded-full border border-[#ffd7db] bg-[#fff3f5] px-2.5 text-xs font-semibold text-[#d81435] transition-colors hover:bg-[#ffe9ed]"
onPointerDown={() => { onPointerDown={() => {
const current = holdFavoriteRef.current; const current = holdFavoriteRef.current;
current.number = number; current.number = number;
@@ -1044,38 +1056,32 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
}} }}
> >
{number} {number}
<span className="text-[10px] opacity-70"> <span className="text-[10px] font-medium opacity-70">
{t("hall.quickFill.tapHold", { defaultValue: "长按取消" })} {t("hall.quickFill.tapHold")}
</span> </span>
</button> </button>
)) ))}
) : ( </div>
<span className="text-xs text-slate-400"> ) : null}
{t("hall.quickFill.emptyFavorites", { defaultValue: "暂无收藏" })}
</span>
)}
</div>
</div>
<div className="space-y-2"> <div className="flex flex-wrap items-center gap-1.5">
<p className="text-[11px] font-medium uppercase tracking-wide text-slate-400"> <span className="mr-0.5 text-[11px] font-semibold text-slate-400">
{t("hall.quickFill.history", { defaultValue: "最近 20 个历史号码" })} {t("hall.quickFill.history")}
</p> </span>
<div className="flex flex-wrap gap-2">
{historyChips.length > 0 ? ( {historyChips.length > 0 ? (
historyChips.map((number) => ( historyChips.map((number) => (
<button <button
key={`his-${number}`} key={`his-${number}`}
type="button" type="button"
className="inline-flex items-center rounded-full border border-[#dce7f7] bg-[#f8fbff] px-3 py-1 text-xs font-medium text-[#07459f]" className="inline-flex h-7 min-w-10 items-center justify-center rounded-full border border-[#d7e5f8] bg-[#f8fbff] px-3 text-xs font-bold text-[#07459f] transition-colors hover:border-[#b9d0f3] hover:bg-[#eef6ff]"
onClick={() => fillCurrentRow(number)} onClick={() => fillCurrentRow(number)}
> >
{number} {number}
</button> </button>
)) ))
) : ( ) : (
<span className="text-xs text-slate-400"> <span className="inline-flex h-7 items-center rounded-full border border-dashed border-slate-200 bg-slate-50 px-3 text-xs text-slate-400">
{t("hall.quickFill.emptyHistory", { defaultValue: "暂无历史号码" })} {t("hall.quickFill.emptyHistory")}
</span> </span>
)} )}
</div> </div>
@@ -1122,7 +1128,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
: ""} : ""}
</span> </span>
<span className="block text-[9px] font-medium text-[#9aa8bd]"> <span className="block text-[9px] font-medium text-[#9aa8bd]">
{t("hall.table.amountPlaceholder", { defaultValue: "金额" })} {t("hall.table.amountPlaceholder")}
</span> </span>
</th> </th>
))} ))}
@@ -1177,7 +1183,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
inputMode="decimal" inputMode="decimal"
placeholder={ placeholder={
status === "sold_out" status === "sold_out"
? t("hall.table.soldOut", { defaultValue: "售罄" }) ? t("hall.table.soldOut")
: "-" : "-"
} }
onFocus={() => setActiveRowId(row.id)} onFocus={() => setActiveRowId(row.id)}
@@ -1192,11 +1198,11 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
/> />
{status === "sold_out" ? ( {status === "sold_out" ? (
<p className="mt-0.5 text-center text-[9px] font-bold text-slate-500"> <p className="mt-0.5 text-center text-[9px] font-bold text-slate-500">
{t("hall.table.soldOut", { defaultValue: "售罄" })} {t("hall.table.soldOut")}
</p> </p>
) : status === "warning" ? ( ) : status === "warning" ? (
<p className="mt-0.5 text-center text-[9px] font-bold text-amber-700"> <p className="mt-0.5 text-center text-[9px] font-bold text-amber-700">
{t("hall.table.warning", { defaultValue: "接近售罄" })} {t("hall.table.warning")}
</p> </p>
) : null} ) : null}
</td> </td>
@@ -1227,13 +1233,13 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
className="flex h-10 w-full items-center justify-center gap-1.5 border-t border-[#edf2f9] bg-white text-xs font-bold text-[#1d57b7] hover:bg-[#f7faff] disabled:text-slate-300" className="flex h-10 w-full items-center justify-center gap-1.5 border-t border-[#edf2f9] bg-white text-xs font-bold text-[#1d57b7] hover:bg-[#f7faff] disabled:text-slate-300"
> >
<CirclePlus className="size-4" aria-hidden /> <CirclePlus className="size-4" aria-hidden />
{t("hall.table.addRow", { defaultValue: "添加一行" })} {t("hall.table.addRow")}
</button> </button>
</div> </div>
<div className="flex items-center justify-between rounded-xl border border-[#e9eef7] bg-[#f8fbff] px-4 py-3 text-sm shadow-[0_6px_20px_rgba(15,23,42,0.04)]"> <div className="flex items-center justify-between rounded-xl border border-[#e9eef7] bg-[#f8fbff] px-4 py-3 text-sm shadow-[0_6px_20px_rgba(15,23,42,0.04)]">
<span className="text-xs font-medium text-slate-500"> <span className="text-xs font-medium text-slate-500">
{t("hall.table.draftTotal", { defaultValue: "投注金额" })} {t("hall.table.draftTotal")}
</span> </span>
<span className="text-base font-black tabular-nums text-[#0b3f96]"> <span className="text-base font-black tabular-nums text-[#0b3f96]">
{formatMinorAsCurrency(debouncedSummary.actual, currencyCode)} {formatMinorAsCurrency(debouncedSummary.actual, currencyCode)}
@@ -1254,12 +1260,12 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
> >
<Ticket className="size-5" aria-hidden /> <Ticket className="size-5" aria-hidden />
{previewLoading {previewLoading
? t("hall.table.previewing", { defaultValue: "预览中..." }) ? t("hall.table.previewing")
: !isBettable : !isBettable
? t("hall.closed.title") ? t("hall.closed.title")
: availableMinor < debouncedSummary.actual : availableMinor < debouncedSummary.actual
? t("hall.table.insufficientBalance", { defaultValue: "余额不足" }) ? t("hall.table.insufficientBalance")
: t("hall.table.submitBet", { defaultValue: "提交下注" })} : t("hall.table.submitBet")}
</Button> </Button>
</> </>
)} )}

View File

@@ -47,7 +47,7 @@ export function HallScreen() {
href="/rules" href="/rules"
className="shrink-0 rounded-full border border-[#e4eaf4] bg-[#f8fafc] px-3 py-1.5 text-xs font-bold text-[#0b3f96] hover:bg-[#f1f6ff]" className="shrink-0 rounded-full border border-[#e4eaf4] bg-[#f8fafc] px-3 py-1.5 text-xs font-bold text-[#0b3f96] hover:bg-[#f1f6ff]"
> >
{tp("nav.rules", { defaultValue: "规则" })} {tp("nav.rules")}
</Link> </Link>
<button <button
type="button" type="button"

View File

@@ -42,7 +42,7 @@ export function JackpotBurstOverlay({ event, onClose }: JackpotBurstOverlayProps
className="fixed inset-0 z-[90] flex items-center justify-center bg-[#08111f]/95 px-4 py-6 text-white" className="fixed inset-0 z-[90] flex items-center justify-center bg-[#08111f]/95 px-4 py-6 text-white"
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
aria-label={t("hall.jackpotBurst.title", { defaultValue: "Jackpot 爆池" })} aria-label={t("hall.jackpotBurst.title")}
> >
<div className="pointer-events-none absolute inset-0 overflow-hidden"> <div className="pointer-events-none absolute inset-0 overflow-hidden">
<div className="absolute left-0 top-1/4 h-px w-full animate-[pulse_1.8s_ease-in-out_infinite] bg-[#f5c542]" /> <div className="absolute left-0 top-1/4 h-px w-full animate-[pulse_1.8s_ease-in-out_infinite] bg-[#f5c542]" />
@@ -56,7 +56,7 @@ export function JackpotBurstOverlay({ event, onClose }: JackpotBurstOverlayProps
size="icon" size="icon"
className="absolute right-2 top-2 text-white hover:bg-white/10 hover:text-white" className="absolute right-2 top-2 text-white hover:bg-white/10 hover:text-white"
onClick={onClose} onClick={onClose}
aria-label={t("hall.jackpotBurst.close", { defaultValue: "关闭" })} aria-label={t("hall.jackpotBurst.close")}
> >
<X className="size-4" aria-hidden /> <X className="size-4" aria-hidden />
</Button> </Button>
@@ -66,11 +66,10 @@ export function JackpotBurstOverlay({ event, onClose }: JackpotBurstOverlayProps
</div> </div>
<p className="text-xs font-bold uppercase tracking-[0.18em] text-[#f5c542]"> <p className="text-xs font-bold uppercase tracking-[0.18em] text-[#f5c542]">
{t("hall.jackpotBurst.title", { defaultValue: "Jackpot 爆池" })} {t("hall.jackpotBurst.title")}
</p> </p>
<p className="mt-2 text-sm font-semibold text-slate-200"> <p className="mt-2 text-sm font-semibold text-slate-200">
{t("hall.jackpotBurst.subtitle", { {t("hall.jackpotBurst.subtitle", {
defaultValue: "期号 {{drawNo}} 触发奖池派发",
drawNo: event.draw_no, drawNo: event.draw_no,
})} })}
</p> </p>
@@ -80,26 +79,26 @@ export function JackpotBurstOverlay({ event, onClose }: JackpotBurstOverlayProps
{event.first_prize_number || "----"} {event.first_prize_number || "----"}
</div> </div>
<p className="mt-2 text-xs font-semibold text-slate-300"> <p className="mt-2 text-xs font-semibold text-slate-300">
{t("hall.jackpotBurst.number", { defaultValue: "头奖号码" })} {t("hall.jackpotBurst.number")}
</p> </p>
</div> </div>
<div className="w-full space-y-2 text-left text-sm"> <div className="w-full space-y-2 text-left text-sm">
<div className="flex items-center justify-between gap-4 rounded-md bg-white/[0.05] px-3 py-2"> <div className="flex items-center justify-between gap-4 rounded-md bg-white/[0.05] px-3 py-2">
<span className="text-slate-300"> <span className="text-slate-300">
{t("hall.jackpotBurst.amount", { defaultValue: "爆池金额" })} {t("hall.jackpotBurst.amount")}
</span> </span>
<span className="text-right font-black text-[#f5c542]">{amount}</span> <span className="text-right font-black text-[#f5c542]">{amount}</span>
</div> </div>
<div className="flex items-center justify-between gap-4 rounded-md bg-white/[0.05] px-3 py-2"> <div className="flex items-center justify-between gap-4 rounded-md bg-white/[0.05] px-3 py-2">
<span className="text-slate-300"> <span className="text-slate-300">
{t("hall.jackpotBurst.winners", { defaultValue: "中奖人数" })} {t("hall.jackpotBurst.winners")}
</span> </span>
<span className="font-bold">{event.winner_count}</span> <span className="font-bold">{event.winner_count}</span>
</div> </div>
<div className="flex items-center justify-between gap-4 rounded-md bg-white/[0.05] px-3 py-2"> <div className="flex items-center justify-between gap-4 rounded-md bg-white/[0.05] px-3 py-2">
<span className="text-slate-300"> <span className="text-slate-300">
{t("hall.jackpotBurst.triggerLabel", { defaultValue: "触发方式" })} {t("hall.jackpotBurst.triggerLabel")}
</span> </span>
<span className="font-bold">{triggerLabel(event.trigger_type, t)}</span> <span className="font-bold">{triggerLabel(event.trigger_type, t)}</span>
</div> </div>

View File

@@ -14,11 +14,8 @@ function notifyBrowser(event: JackpotBurstEvent, t: TFunction<"player">): void {
const currency = event.currency_code.toUpperCase(); const currency = event.currency_code.toUpperCase();
const amount = formatMinorAsCurrency(event.total_payout_amount, currency); const amount = formatMinorAsCurrency(event.total_payout_amount, currency);
const title = t("hall.jackpotBurst.notificationTitle", { const title = t("hall.jackpotBurst.notificationTitle");
defaultValue: "Jackpot 爆池",
});
const body = t("hall.jackpotBurst.notificationBody", { const body = t("hall.jackpotBurst.notificationBody", {
defaultValue: "期号 {{drawNo}} 派发 {{amount}}",
drawNo: event.draw_no, drawNo: event.draw_no,
amount, amount,
}); });

View File

@@ -8,17 +8,17 @@ export function ticketStatusDisplay(
): { label: string; dotClass: string; ring?: boolean } { ): { label: string; dotClass: string; ring?: boolean } {
const total = winMinor + jackpotMinor; const total = winMinor + jackpotMinor;
if (status === "success") { if (status === "success") {
return { label: t?.("ticketStatus.success") ?? "待开奖", dotClass: "bg-sky-500" }; return { label: t?.("ticketStatus.success") ?? status, dotClass: "bg-sky-500" };
} }
if (status === "pending_payout") { if (status === "pending_payout") {
return { label: t?.("ticketStatus.pending_payout") ?? "已中奖待派彩", dotClass: "bg-amber-500" }; return { label: t?.("ticketStatus.pending_payout") ?? status, dotClass: "bg-amber-500" };
} }
if (status === "settled_win" && total > 0) { if (status === "settled_win" && total > 0) {
return { label: t?.("ticketStatus.settled_win") ?? "已派彩", dotClass: "bg-emerald-500" }; return { label: t?.("ticketStatus.settled_win") ?? status, dotClass: "bg-emerald-500" };
} }
if (status === "settled_lose" || status === "settled_win") { if (status === "settled_lose" || status === "settled_win") {
return { return {
label: t?.("ticketStatus.settled_lose") ?? "未中奖", label: t?.("ticketStatus.settled_lose") ?? status,
dotClass: "bg-background", dotClass: "bg-background",
ring: true, ring: true,
}; };

View File

@@ -264,7 +264,7 @@ export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) {
) : null} ) : null}
{!hasSettlement ? ( {!hasSettlement ? (
<p className="rounded-lg border border-[#dce7f7] bg-[#f8fbff] px-3 py-2 text-xs text-[#32518d]"> <p className="rounded-lg border border-[#dce7f7] bg-[#f8fbff] px-3 py-2 text-xs text-[#32518d]">
{t("orders.matchPendingSettlement", { defaultValue: "已开奖,等待系统结算后显示中奖结果。" })} {t("orders.matchPendingSettlement")}
</p> </p>
) : null} ) : null}
</div> </div>
@@ -272,7 +272,7 @@ export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) {
<div className="rounded-lg border border-[#dce7f7] bg-[#f8fbff] px-3 py-3 text-xs"> <div className="rounded-lg border border-[#dce7f7] bg-[#f8fbff] px-3 py-3 text-xs">
<p className="font-bold text-[#0b3f96]">{t("orders.drawNumbers")}</p> <p className="font-bold text-[#0b3f96]">{t("orders.drawNumbers")}</p>
<p className="mt-1 text-[#32518d]"> <p className="mt-1 text-[#32518d]">
{t("orders.drawPendingMatch", { defaultValue: "本期开奖号码尚未发布,暂不能判断是否中奖。" })} {t("orders.drawPendingMatch")}
</p> </p>
</div> </div>
)} )}
@@ -310,7 +310,7 @@ export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) {
{matchResult && hasSettlement ? ( {matchResult && hasSettlement ? (
<div className="rounded-lg border border-[#dce7f7] bg-[#f8fbff] px-3 py-3 text-xs"> <div className="rounded-lg border border-[#dce7f7] bg-[#f8fbff] px-3 py-3 text-xs">
<p className="font-bold text-[#0b3f96]"> <p className="font-bold text-[#0b3f96]">
{t("orders.matchResult", { defaultValue: "匹配结果" })} {t("orders.matchResult")}
</p> </p>
<p className="mt-1 text-slate-600"> <p className="mt-1 text-slate-600">
{matchResult.matched {matchResult.matched
@@ -332,7 +332,7 @@ export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) {
{timeline.length > 0 ? ( {timeline.length > 0 ? (
<div className="rounded-xl border border-[#e8eef7] bg-[#f8fbff] px-3 py-3"> <div className="rounded-xl border border-[#e8eef7] bg-[#f8fbff] px-3 py-3">
<p className="text-sm font-bold text-[#0b3f96]"> <p className="text-sm font-bold text-[#0b3f96]">
{t("orders.timeline", { defaultValue: "时间线" })} {t("orders.timeline")}
</p> </p>
<div className="mt-2 space-y-2"> <div className="mt-2 space-y-2">
{timeline.map((row) => ( {timeline.map((row) => (

View File

@@ -83,7 +83,7 @@ export function TicketOrdersListScreen() {
}, []); }, []);
const dateLabel = useMemo(() => { const dateLabel = useMemo(() => {
if (!fromDate && !toDate) return t("orders.dateRange", { defaultValue: "日期范围" }); if (!fromDate && !toDate) return t("orders.dateRange");
if (fromDate && toDate) return `${formatCompactDate(fromDate)} ~ ${formatCompactDate(toDate)}`; if (fromDate && toDate) return `${formatCompactDate(fromDate)} ~ ${formatCompactDate(toDate)}`;
return `${fromDate ? formatCompactDate(fromDate) : "..." } ~ ${toDate ? formatCompactDate(toDate) : "..."}`; return `${fromDate ? formatCompactDate(fromDate) : "..." } ~ ${toDate ? formatCompactDate(toDate) : "..."}`;
}, [formatCompactDate, fromDate, t, toDate]); }, [formatCompactDate, fromDate, t, toDate]);
@@ -156,20 +156,20 @@ export function TicketOrdersListScreen() {
containerClassName="max-w-[720px]" containerClassName="max-w-[720px]"
> >
<div className="space-y-4"> <div className="space-y-4">
<div className="rounded-xl border border-[#e6edf8] bg-white p-3 shadow-[0_8px_24px_rgba(15,23,42,0.04)] sm:p-4"> <div className="rounded-2xl border border-[#dfe9f8] bg-white p-3 shadow-[0_10px_28px_rgba(15,23,42,0.05)] sm:p-4">
<div className="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between"> <div className="flex items-center justify-between gap-3">
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<p className="text-xs font-bold text-[#32518d]"> <p className="text-[11px] font-black uppercase tracking-wide text-[#6f86ad]">
{drawNoFilter ? t("orders.filteredIssue") : t("orders.totalRecords")} {drawNoFilter ? t("orders.filteredIssue") : t("orders.totalRecords")}
</p> </p>
<p className="mt-1 truncate font-mono text-lg font-black text-[#0b3f96]"> <p className="mt-0.5 truncate font-mono text-2xl font-black leading-none text-[#0b3f96]">
{drawNoFilter || total} {drawNoFilter || total}
</p> </p>
</div> </div>
<div className="flex shrink-0 items-center gap-2 self-start sm:self-auto"> <div className="flex shrink-0 items-center gap-2">
<Link <Link
href="/hall" href="/hall"
className="inline-flex h-10 shrink-0 items-center rounded-full bg-[#e5002c] px-5 text-sm font-bold text-white" className="inline-flex h-9 shrink-0 items-center rounded-full bg-[#e5002c] px-5 text-sm font-black text-white shadow-[0_8px_18px_rgba(229,0,44,0.22)]"
> >
{t("orders.betNow")} {t("orders.betNow")}
</Link> </Link>
@@ -177,7 +177,7 @@ export function TicketOrdersListScreen() {
<Button <Button
type="button" type="button"
variant="outline" variant="outline"
className="h-10 rounded-full border-[#dce7f7] bg-white px-4 text-sm font-semibold text-[#32518d] hover:bg-[#f8fbff]" className="h-9 rounded-full border-[#dce7f7] bg-white px-3 text-xs font-bold text-[#32518d] hover:bg-[#f8fbff]"
onClick={() => { onClick={() => {
setQueryDrawNo(drawNoFilter); setQueryDrawNo(drawNoFilter);
setQueryNumber(""); setQueryNumber("");
@@ -194,25 +194,25 @@ export function TicketOrdersListScreen() {
</div> </div>
</div> </div>
<div className="mt-4 grid grid-cols-1 gap-2 min-[420px]:grid-cols-2 lg:grid-cols-4"> <div className="mt-3 grid grid-cols-2 gap-2 lg:grid-cols-4">
<div className="flex h-10 items-center rounded-full border border-[#dce7f7] bg-[#fbfdff] px-3"> <div className="flex h-9 min-w-0 items-center rounded-full border border-[#dce7f7] bg-[#fbfdff] px-3">
<Input <Input
value={queryDrawNo} value={queryDrawNo}
onChange={(e) => setQueryDrawNo(e.target.value)} onChange={(e) => setQueryDrawNo(e.target.value)}
placeholder={t("orders.drawNo")} placeholder={t("orders.drawNo")}
aria-label={t("orders.drawNo")} aria-label={t("orders.drawNo")}
className="h-8 border-0 bg-transparent px-0 text-sm shadow-none focus-visible:ring-0" className="h-7 border-0 bg-transparent px-0 text-sm shadow-none focus-visible:ring-0"
/> />
</div> </div>
<div className="flex h-10 items-center gap-2 rounded-full border border-[#dce7f7] bg-[#fbfdff] px-3"> <div className="flex h-9 min-w-0 items-center gap-2 rounded-full border border-[#dce7f7] bg-[#fbfdff] px-3">
<Search className="size-3.5 shrink-0 text-slate-400" /> <Search className="size-3.5 shrink-0 text-slate-400" />
<Input <Input
value={queryNumber} value={queryNumber}
onChange={(e) => setQueryNumber(e.target.value)} onChange={(e) => setQueryNumber(e.target.value)}
placeholder={t("orders.number")} placeholder={t("orders.number")}
aria-label={t("orders.number")} aria-label={t("orders.number")}
className="h-8 border-0 bg-transparent px-0 text-sm shadow-none focus-visible:ring-0" className="h-7 border-0 bg-transparent px-0 text-sm shadow-none focus-visible:ring-0"
/> />
</div> </div>
@@ -222,7 +222,7 @@ export function TicketOrdersListScreen() {
<Button <Button
type="button" type="button"
variant="outline" variant="outline"
className="h-10 w-full justify-start gap-2 rounded-full border-[#dce7f7] bg-[#fbfdff] px-3 text-left text-sm font-semibold text-[#32518d] hover:bg-white" className="h-9 w-full justify-start gap-2 rounded-full border-[#dce7f7] bg-[#fbfdff] px-3 text-left text-sm font-bold text-[#32518d] hover:bg-white"
> >
<CalendarRange className="size-4 text-[#7890b8]" /> <CalendarRange className="size-4 text-[#7890b8]" />
<span className="truncate">{dateLabel}</span> <span className="truncate">{dateLabel}</span>
@@ -256,10 +256,10 @@ export function TicketOrdersListScreen() {
setToDate(""); setToDate("");
}} }}
> >
{t("actions.clear")}
</Button> </Button>
<Button type="button" variant="secondary" size="xs" onClick={() => setRangeOpen(false)}> <Button type="button" variant="secondary" size="xs" onClick={() => setRangeOpen(false)}>
{t("actions.done")}
</Button> </Button>
</div> </div>
</PopoverContent> </PopoverContent>
@@ -271,10 +271,10 @@ export function TicketOrdersListScreen() {
<Button <Button
type="button" type="button"
variant="outline" variant="outline"
className="h-10 w-full justify-between gap-2 rounded-full border-[#dce7f7] bg-[#fbfdff] px-3 text-sm font-semibold text-[#32518d] hover:bg-white" className="h-9 w-full justify-between gap-2 rounded-full border-[#dce7f7] bg-[#fbfdff] px-3 text-sm font-bold text-[#32518d] hover:bg-white"
> >
<span className="flex min-w-0 items-center gap-1.5"> <span className="flex min-w-0 items-center gap-1.5">
<span className="shrink-0"></span> <span className="shrink-0">{t("orders.status")}</span>
{queryStatuses.length ? ( {queryStatuses.length ? (
<span className="inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-[#0b56b7] px-1 text-[10px] font-black text-white"> <span className="inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-[#0b56b7] px-1 text-[10px] font-black text-white">
{queryStatuses.length} {queryStatuses.length}
@@ -290,7 +290,7 @@ export function TicketOrdersListScreen() {
<PopoverContent align="start" className="w-56 border-[#dce7f7] p-2 shadow-[0_16px_40px_rgba(15,23,42,0.14)]"> <PopoverContent align="start" className="w-56 border-[#dce7f7] p-2 shadow-[0_16px_40px_rgba(15,23,42,0.14)]">
<div className="space-y-1"> <div className="space-y-1">
<p className="px-1 pb-1 text-[11px] font-bold text-[#32518d]"> <p className="px-1 pb-1 text-[11px] font-bold text-[#32518d]">
{t("orders.statusFilter", { defaultValue: "状态筛选" })} {t("orders.statusFilter")}
</p> </p>
{STATUS_OPTIONS.map((status) => { {STATUS_OPTIONS.map((status) => {
const checked = queryStatuses.includes(status); const checked = queryStatuses.includes(status);
@@ -408,7 +408,7 @@ export function TicketOrdersListScreen() {
disabled={loadingMore} disabled={loadingMore}
onClick={() => void fetchPage(page + 1, true)} onClick={() => void fetchPage(page + 1, true)}
> >
{loadingMore ? t("actions.loading", { defaultValue: "加载中..." }) : t("actions.loadMore", { defaultValue: "加载更多" })} {loadingMore ? t("actions.loading") : t("actions.loadMore")}
</Button> </Button>
) : !isMobile && lastPage > 1 ? ( ) : !isMobile && lastPage > 1 ? (
<div className="flex flex-wrap items-center justify-center gap-2 rounded-xl border border-[#e6edf8] bg-white px-3 py-3"> <div className="flex flex-wrap items-center justify-center gap-2 rounded-xl border border-[#e6edf8] bg-white px-3 py-3">
@@ -420,7 +420,7 @@ export function TicketOrdersListScreen() {
disabled={loading || page <= 1} disabled={loading || page <= 1}
onClick={() => void fetchPage(Math.max(1, page - 1), false)} onClick={() => void fetchPage(Math.max(1, page - 1), false)}
> >
{t("actions.previous", { defaultValue: "上一页" })} {t("actions.previous")}
</Button> </Button>
{visiblePages.map((p) => ( {visiblePages.map((p) => (
<Button <Button
@@ -448,12 +448,12 @@ export function TicketOrdersListScreen() {
disabled={loading || page >= lastPage} disabled={loading || page >= lastPage}
onClick={() => void fetchPage(Math.min(lastPage, page + 1), false)} onClick={() => void fetchPage(Math.min(lastPage, page + 1), false)}
> >
{t("actions.next", { defaultValue: "下一页" })} {t("actions.next")}
</Button> </Button>
</div> </div>
) : ( ) : (
<p className="py-2 text-center text-xs text-slate-400"> <p className="py-2 text-center text-xs text-slate-400">
{t("orders.noMore", { defaultValue: "没有更多注单" })} {t("orders.noMore")}
</p> </p>
)} )}
</> </>

View File

@@ -78,14 +78,14 @@ export function CheckWinningScreen() {
setResult(next); setResult(next);
setRecent((current) => [normalizedTicketNo, ...current.filter((x) => x !== normalizedTicketNo)].slice(0, 5)); setRecent((current) => [normalizedTicketNo, ...current.filter((x) => x !== normalizedTicketNo)].slice(0, 5));
} catch { } catch {
setError(t("results.check.loadFailed", { defaultValue: "查询失败,请稍后重试。" })); setError(t("results.check.loadFailed"));
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [latestDraw, normalizedTicketNo, t]); }, [latestDraw, normalizedTicketNo, t]);
return ( return (
<PlayerPanel title={t("results.check.title", { defaultValue: "查我的中奖" })} backHref="/results" backLabel={t("results.title")}> <PlayerPanel title={t("results.check.title")} backHref="/results" backLabel={t("results.title")}>
<div className="space-y-4"> <div className="space-y-4">
<section className="overflow-hidden rounded-2xl border border-red-100 bg-white shadow-[0_12px_32px_rgba(15,23,42,0.06)]"> <section className="overflow-hidden rounded-2xl border border-red-100 bg-white shadow-[0_12px_32px_rgba(15,23,42,0.06)]">
<div className="bg-gradient-to-b from-red-50 to-white px-5 pb-5 pt-8 text-center"> <div className="bg-gradient-to-b from-red-50 to-white px-5 pb-5 pt-8 text-center">
@@ -93,28 +93,28 @@ export function CheckWinningScreen() {
<BriefcaseBusiness className="size-12" /> <BriefcaseBusiness className="size-12" />
</div> </div>
<h2 className="mt-5 text-lg font-black text-slate-950"> <h2 className="mt-5 text-lg font-black text-slate-950">
{t("results.check.enterTicket", { defaultValue: "输入你的票号或号码" })} {t("results.check.enterTicket")}
</h2> </h2>
<p className="mx-auto mt-2 max-w-xs text-sm leading-relaxed text-slate-500"> <p className="mx-auto mt-2 max-w-xs text-sm leading-relaxed text-slate-500">
{t("results.check.description", { defaultValue: "系统会按最新已发布期号查询你的注单和中奖情况。" })} {t("results.check.description")}
</p> </p>
</div> </div>
<div className="space-y-3 px-4 pb-4"> <div className="space-y-3 px-4 pb-4">
<label className="block space-y-1.5"> <label className="block space-y-1.5">
<span className="text-xs font-black text-slate-700"> <span className="text-xs font-black text-slate-700">
{t("results.check.ticketNumber", { defaultValue: "票号 / 号码" })} {t("results.check.ticketNumber")}
</span> </span>
<Input <Input
value={ticketNo} value={ticketNo}
placeholder={t("results.check.placeholder", { defaultValue: "请输入票号或号码" })} placeholder={t("results.check.placeholder")}
onChange={(e) => setTicketNo(e.target.value)} onChange={(e) => setTicketNo(e.target.value)}
className="h-12 rounded-xl border-[#dce7f7] bg-white font-mono text-base font-bold" className="h-12 rounded-xl border-[#dce7f7] bg-white font-mono text-base font-bold"
/> />
</label> </label>
{latestDraw ? ( {latestDraw ? (
<p className="text-xs text-slate-500"> <p className="text-xs text-slate-500">
{t("results.check.latestDraw", { drawNo: latestDraw.draw_no, defaultValue: "最新期号 {{drawNo}}" })} {t("results.check.latestDraw", { drawNo: latestDraw.draw_no })}
</p> </p>
) : null} ) : null}
{error ? <p className="text-sm font-semibold text-[#e5002c]">{error}</p> : null} {error ? <p className="text-sm font-semibold text-[#e5002c]">{error}</p> : null}
@@ -124,7 +124,7 @@ export function CheckWinningScreen() {
onClick={() => void runCheck()} onClick={() => void runCheck()}
className="h-12 w-full rounded-xl bg-[#e5002c] text-base font-black text-white hover:bg-[#d10028]" className="h-12 w-full rounded-xl bg-[#e5002c] text-base font-black text-white hover:bg-[#d10028]"
> >
{loading ? t("actions.loading", { defaultValue: "查询中..." }) : t("results.check.submit", { defaultValue: "立即查询" })} {loading ? t("results.check.loading") : t("results.check.submit")}
</Button> </Button>
</div> </div>
</section> </section>
@@ -132,18 +132,18 @@ export function CheckWinningScreen() {
<section className="rounded-2xl border border-[#dfe8f6] bg-white p-4 shadow-[0_10px_26px_rgba(15,23,42,0.05)]"> <section className="rounded-2xl border border-[#dfe8f6] bg-white p-4 shadow-[0_10px_26px_rgba(15,23,42,0.05)]">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h3 className="font-black text-slate-950"> <h3 className="font-black text-slate-950">
{t("results.check.recent", { defaultValue: "最近查询" })} {t("results.check.recent")}
</h3> </h3>
{recent.length > 0 ? ( {recent.length > 0 ? (
<button type="button" className="text-sm font-bold text-[#0b56b7]" onClick={() => setRecent([])}> <button type="button" className="text-sm font-bold text-[#0b56b7]" onClick={() => setRecent([])}>
{t("actions.clear", { defaultValue: "清空" })} {t("actions.clear")}
</button> </button>
) : null} ) : null}
</div> </div>
<div className="mt-3 divide-y divide-[#edf2f9]"> <div className="mt-3 divide-y divide-[#edf2f9]">
{recent.length === 0 ? ( {recent.length === 0 ? (
<p className="py-4 text-sm text-slate-500"> <p className="py-4 text-sm text-slate-500">
{t("results.check.noRecent", { defaultValue: "暂无查询记录。" })} {t("results.check.noRecent")}
</p> </p>
) : ( ) : (
recent.map((row) => ( recent.map((row) => (
@@ -213,7 +213,7 @@ function WinningResultDialog({
type="button" type="button"
onClick={() => onOpenChange(false)} onClick={() => onOpenChange(false)}
className="absolute right-3 top-3 z-10 inline-flex size-9 items-center justify-center rounded-full text-slate-500 hover:bg-slate-100" className="absolute right-3 top-3 z-10 inline-flex size-9 items-center justify-center rounded-full text-slate-500 hover:bg-slate-100"
aria-label={t("actions.close", { defaultValue: "关闭" })} aria-label={t("actions.close")}
> >
<XIcon className="size-5" /> <XIcon className="size-5" />
</button> </button>
@@ -224,11 +224,11 @@ function WinningResultDialog({
</div> </div>
<DialogTitle className="mt-3 text-xl font-black text-slate-950"> <DialogTitle className="mt-3 text-xl font-black text-slate-950">
{isWon {isWon
? t("results.check.winTitle", { defaultValue: "恭喜,你中奖了" }) ? t("results.check.winTitle")
: t("results.check.noWinTitle", { defaultValue: "未查询到中奖" })} : t("results.check.noWinTitle")}
</DialogTitle> </DialogTitle>
<DialogDescription className="text-sm text-slate-500"> <DialogDescription className="text-sm text-slate-500">
{t("results.check.ticketNumber", { defaultValue: "票号 / 号码" })} {t("results.check.ticketNumber")}
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
@@ -239,15 +239,15 @@ function WinningResultDialog({
<div className="mt-5 grid grid-cols-2 overflow-hidden rounded-xl border border-emerald-100 bg-emerald-50 text-center"> <div className="mt-5 grid grid-cols-2 overflow-hidden rounded-xl border border-emerald-100 bg-emerald-50 text-center">
<div className="border-r border-emerald-100 px-3 py-4"> <div className="border-r border-emerald-100 px-3 py-4">
<p className="text-xs font-medium text-slate-500"> <p className="text-xs font-medium text-slate-500">
{t("results.check.match", { defaultValue: "匹配" })} {t("results.check.match")}
</p> </p>
<p className="mt-2 text-lg font-black text-[#0a8f3e]"> <p className="mt-2 text-lg font-black text-[#0a8f3e]">
{firstTicket ? playLabel(firstTicket.play_code, t) : isWon ? t("orders.hit", { defaultValue: "命中" }) : "—"} {firstTicket ? playLabel(firstTicket.play_code, t) : isWon ? t("orders.hit") : "—"}
</p> </p>
</div> </div>
<div className="px-3 py-4"> <div className="px-3 py-4">
<p className="text-xs font-medium text-slate-500"> <p className="text-xs font-medium text-slate-500">
{t("results.check.amount", { defaultValue: "中奖金额" })} {t("results.check.amount")}
</p> </p>
<p className="mt-2 font-mono text-lg font-black text-[#0a8f3e]"> <p className="mt-2 font-mono text-lg font-black text-[#0a8f3e]">
{formatMinorAsCurrency(totalWin, firstTicket?.currency_code ?? "NPR")} {formatMinorAsCurrency(totalWin, firstTicket?.currency_code ?? "NPR")}
@@ -257,11 +257,11 @@ function WinningResultDialog({
<div className="mt-5 rounded-xl border border-[#e8eef7] bg-white p-4 text-sm"> <div className="mt-5 rounded-xl border border-[#e8eef7] bg-white p-4 text-sm">
<p className="font-black text-slate-950"> <p className="font-black text-slate-950">
{t("results.check.drawInfo", { defaultValue: "开奖信息" })} {t("results.check.drawInfo")}
</p> </p>
<div className="mt-3 grid grid-cols-2 gap-4 text-slate-500"> <div className="mt-3 grid grid-cols-2 gap-4 text-slate-500">
<div> <div>
<p className="text-xs">{t("results.check.issueNo", { defaultValue: "期号" })}</p> <p className="text-xs">{t("results.check.issueNo")}</p>
<p className="mt-1 font-mono font-black text-slate-900">{data.draw.draw_no}</p> <p className="mt-1 font-mono font-black text-slate-900">{data.draw.draw_no}</p>
</div> </div>
<div> <div>
@@ -283,7 +283,7 @@ function WinningResultDialog({
className="h-12 rounded-xl bg-[#07459f] text-base font-black text-white hover:bg-[#063b88]" className="h-12 rounded-xl bg-[#07459f] text-base font-black text-white hover:bg-[#063b88]"
render={<Link href={`/orders?draw_no=${encodeURIComponent(data.draw.draw_no)}&number=${encodeURIComponent(query)}`} />} render={<Link href={`/orders?draw_no=${encodeURIComponent(data.draw.draw_no)}&number=${encodeURIComponent(query)}`} />}
> >
{t("results.check.viewBetDetails", { defaultValue: "查看注单详情" })} {t("results.check.viewBetDetails")}
</Button> </Button>
<Button <Button
type="button" type="button"
@@ -292,7 +292,7 @@ function WinningResultDialog({
onClick={onCheckAnother} onClick={onCheckAnother}
> >
<RefreshCw className="size-5" /> <RefreshCw className="size-5" />
{t("results.check.checkAnother", { defaultValue: "查询另一张票" })} {t("results.check.checkAnother")}
</Button> </Button>
</div> </div>
</div> </div>

View File

@@ -27,7 +27,7 @@ const RESULTS_PAGE_SIZE = 10;
const MONTH_OPTIONS = Array.from({ length: 12 }, (_, value) => ({ const MONTH_OPTIONS = Array.from({ length: 12 }, (_, value) => ({
value, value,
label: `${value + 1}`, labelKey: `calendar.months.${value + 1}`,
})); }));
export function DrawResultsListScreen() { export function DrawResultsListScreen() {
@@ -46,7 +46,6 @@ export function DrawResultsListScreen() {
const businessDate = /^\d{4}-\d{2}-\d{2}$/.test(date) ? date : undefined; const businessDate = /^\d{4}-\d{2}-\d{2}$/.test(date) ? date : undefined;
const quickYears = useMemo(() => buildYearOptions(calendarMonth), [calendarMonth]); const quickYears = useMemo(() => buildYearOptions(calendarMonth), [calendarMonth]);
const featured = items?.[0] ?? null; const featured = items?.[0] ?? null;
const olderDraw = items?.[1] ?? null;
const fetchList = useCallback(async (targetPage = 1, append = false) => { const fetchList = useCallback(async (targetPage = 1, append = false) => {
setError(null); setError(null);
@@ -121,7 +120,10 @@ export function DrawResultsListScreen() {
className="h-10 flex-1 justify-start rounded-lg border-[#dce7f7] bg-white px-3 text-left text-sm font-semibold text-[#32518d] hover:bg-[#f8fbff]" className="h-10 flex-1 justify-start rounded-lg border-[#dce7f7] bg-white px-3 text-left text-sm font-semibold text-[#32518d] hover:bg-[#f8fbff]"
> >
<CalendarIcon className="mr-2 size-4 text-[#7890b8]" /> <CalendarIcon className="mr-2 size-4 text-[#7890b8]" />
{date || t("results.selectBusinessDate", { defaultValue: "选择日期" })} {date ||
t("results.selectBusinessDate", {
defaultValue: "选择日期",
})}
</Button> </Button>
} }
/> />
@@ -139,7 +141,7 @@ export function DrawResultsListScreen() {
<SelectContent className="max-h-64 min-w-24"> <SelectContent className="max-h-64 min-w-24">
{MONTH_OPTIONS.map((month) => ( {MONTH_OPTIONS.map((month) => (
<SelectItem key={month.value} value={String(month.value)}> <SelectItem key={month.value} value={String(month.value)}>
{month.label} {t(month.labelKey)}
</SelectItem> </SelectItem>
))} ))}
</SelectContent> </SelectContent>
@@ -183,7 +185,7 @@ export function DrawResultsListScreen() {
size="icon" size="icon"
variant="outline" variant="outline"
className="h-10 w-10 rounded-lg border-[#dce7f7] bg-white text-[#7890b8] hover:bg-[#f8fbff] hover:text-[#32518d]" className="h-10 w-10 rounded-lg border-[#dce7f7] bg-white text-[#7890b8] hover:bg-[#f8fbff] hover:text-[#32518d]"
aria-label={t("actions.clear", { defaultValue: "清除" })} aria-label={t("actions.clear")}
onClick={() => setDate("")} onClick={() => setDate("")}
> >
<XIcon className="size-4" /> <XIcon className="size-4" />
@@ -226,44 +228,28 @@ export function DrawResultsListScreen() {
<div className="space-y-3"> <div className="space-y-3">
{featured ? ( {featured ? (
<div className="rounded-xl border border-[#e5edf8] bg-white p-3 shadow-[0_10px_28px_rgba(15,23,42,0.06)]"> <div className="rounded-xl border border-[#e5edf8] bg-white p-3 shadow-[0_10px_28px_rgba(15,23,42,0.06)]">
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-[#edf2f9] pb-3"> <div className="flex flex-wrap items-start justify-between gap-3 border-b border-[#edf2f9] pb-3">
<Button <div className="min-w-0">
type="button" <p className="text-[11px] font-black uppercase tracking-normal text-[#0b56b7]">
variant="outline" {t("results.detailTitle")}
size="sm" </p>
className="min-w-[5rem] rounded-full border-[#e6edf8] bg-white text-slate-400" <p className="mt-1 font-mono text-lg font-black text-[#0b3f96]">
disabled
>
{t("results.next")}
</Button>
<div className="min-w-0 flex-1 text-center">
<p className="font-mono text-lg font-black text-[#0b3f96]">
{featured.draw_no} {featured.draw_no}
</p> </p>
<p className="mt-1 font-mono text-xs text-slate-500"> <p className="mt-1 font-mono text-xs text-slate-500">
{t("results.drawTime", { {t("results.drawTime", {
time: formatLotteryInstant(featured.draw_time_iso ?? featured.draw_time ?? null), time: formatLotteryInstant(
featured.draw_time_iso ?? featured.draw_time ?? null,
),
})} })}
</p> </p>
</div> </div>
{olderDraw ? ( <Link
<Link href={`/results/${encodeURIComponent(featured.draw_no)}`}
href={`/results/${encodeURIComponent(olderDraw.draw_no)}`} className="inline-flex h-8 shrink-0 items-center justify-center rounded-full border border-[#dce7f7] bg-white px-3 text-sm font-semibold text-[#0b56b7] transition-colors hover:bg-[#f1f6ff]"
className="inline-flex h-7 min-w-[5rem] items-center justify-center rounded-full border border-[#dce7f7] bg-white px-2.5 text-[0.8rem] font-medium text-[#0b56b7] transition-colors hover:bg-[#f1f6ff]" >
> {t("results.openDetail", { defaultValue: "查看详情" })}
{t("results.previous")} </Link>
</Link>
) : (
<Button
type="button"
variant="outline"
size="sm"
className="min-w-[5rem] rounded-full border-[#e6edf8] bg-white text-slate-400"
disabled
>
{t("results.previous")}
</Button>
)}
</div> </div>
<div className="pt-4"> <div className="pt-4">
<TwentyThreeResultsGrid numbers={featured.results} /> <TwentyThreeResultsGrid numbers={featured.results} />
@@ -323,12 +309,12 @@ export function DrawResultsListScreen() {
onClick={() => void fetchList(page + 1, true)} onClick={() => void fetchList(page + 1, true)}
> >
{loadingMore {loadingMore
? t("actions.loading", { defaultValue: "加载中..." }) ? t("actions.loading")
: t("actions.loadMore", { defaultValue: "加载更多" })} : t("actions.loadMore")}
</Button> </Button>
) : items && items.length > 0 ? ( ) : items && items.length > 0 ? (
<p className="py-2 text-center text-xs text-slate-400"> <p className="py-2 text-center text-xs text-slate-400">
{t("results.noMore", { defaultValue: "没有更多开奖结果" })} {t("results.noMore")}
</p> </p>
) : null} ) : null}
</div> </div>

View File

@@ -56,7 +56,7 @@ export function JackpotResultsStrip({
</p> </p>
{gap !== null ? ( {gap !== null ? (
<p className="mt-1 text-xs font-semibold text-amber-800"> <p className="mt-1 text-xs font-semibold text-amber-800">
{t("results.jackpotGap", { count: gap, defaultValue: "距上次爆池 {{count}} 期" })} {t("results.jackpotGap", { count: gap })}
</p> </p>
) : null} ) : null}
</div> </div>

View File

@@ -14,69 +14,63 @@ import {
import { PlayerPanel } from "@/components/layout/player-panel"; import { PlayerPanel } from "@/components/layout/player-panel";
const prizeRows = [ const prizeRows = [
{ label: "rules.prizes.first", defaultValue: "头奖", count: "1" }, { label: "rules.prizes.first", count: "1" },
{ label: "rules.prizes.second", defaultValue: "二奖", count: "1" }, { label: "rules.prizes.second", count: "1" },
{ label: "rules.prizes.third", defaultValue: "三奖", count: "1" }, { label: "rules.prizes.third", count: "1" },
{ label: "rules.prizes.starter", defaultValue: "特别奖", count: "10" }, { label: "rules.prizes.starter", count: "10" },
{ label: "rules.prizes.consolation", defaultValue: "安慰奖", count: "10" }, { label: "rules.prizes.consolation", count: "10" },
] as const; ] as const;
const playSections = [ const playSections = [
{ {
title: "rules.sections.dimensions.title", title: "rules.sections.dimensions.title",
titleDefault: "什么是 2D、3D、4D",
items: [ items: [
["rules.sections.dimensions.d4", "4D按完整 4 位号码判断,例如 1234。"], "rules.sections.dimensions.d4",
["rules.sections.dimensions.d3", "3D按下注后三位与开奖号码后三位匹配例如 234 可命中 1234。"], "rules.sections.dimensions.d3",
["rules.sections.dimensions.d2", "2D按下注后二位与开奖号码后二位匹配例如 34 可命中 1234。"], "rules.sections.dimensions.d2",
], ],
}, },
{ {
title: "rules.sections.bigSmall.title", title: "rules.sections.bigSmall.title",
titleDefault: "Big vs Small",
items: [ items: [
["rules.sections.bigSmall.big", "Big覆盖全部 23 个奖项,命中头奖、二奖、三奖、特别奖或安慰奖任一组即中奖。"], "rules.sections.bigSmall.big",
["rules.sections.bigSmall.small", "Small只覆盖头奖、二奖、三奖命中特别奖或安慰奖不中奖。"], "rules.sections.bigSmall.small",
], ],
}, },
{ {
title: "rules.sections.positions.title", title: "rules.sections.positions.title",
titleDefault: "位置类玩法",
items: [ items: [
["rules.sections.positions.d4", "4A / 4B / 4C 分别对应头奖、二奖、三奖4D 命中特别奖任一组4E 命中安慰奖任一组。"], "rules.sections.positions.d4",
["rules.sections.positions.d3", "3A / 3B / 3C 分别匹配头奖、二奖、三奖后三位3ABC 命中头/二/三任一后三位。"], "rules.sections.positions.d3",
["rules.sections.positions.d2", "2A / 2B / 2C 分别匹配头奖、二奖、三奖后二位2ABC 命中头/二/三任一后二位。"], "rules.sections.positions.d2",
], ],
}, },
{ {
title: "rules.sections.box.title", title: "rules.sections.box.title",
titleDefault: "包号类玩法",
items: [ items: [
["rules.sections.box.straight", "Straight顺序完全一致才中奖。"], "rules.sections.box.straight",
["rules.sections.box.box", "Box系统展开不重复排列组合中奖按命中的展开号码结算。"], "rules.sections.box.box",
["rules.sections.box.ibox", "iBox每个排列都按单注金额下注总扣款 = 单注金额 × 组合数 × (1 - 回水率)。"], "rules.sections.box.ibox",
["rules.sections.box.mbox", "mBox总输入金额平摊到所有组合不能整除的尾差不扣除相当于退回玩家钱包。"], "rules.sections.box.mbox",
["rules.sections.box.roll", "Roll用 R 表示滚动位,每个 R 覆盖 0-9组合数 = 10 的滚动位数次方。"], "rules.sections.box.roll",
["rules.sections.box.half", "Half Box一期仅预留数据结构前台不开放下注。"], "rules.sections.box.half",
], ],
}, },
{ {
title: "rules.sections.attributes.title", title: "rules.sections.attributes.title",
titleDefault: "Head / Tail、单双、数字大小",
items: [ items: [
["rules.sections.attributes.headTail", "Head / Tail只看头奖首位数字5-9 为 Head0-4 为 Tail。"], "rules.sections.attributes.headTail",
["rules.sections.attributes.oddEven", "单双按号码末位判断1/3/5/7/9 为单0/2/4/6/8 为双,可叠加 2D / 3D / 4D 维度。"], "rules.sections.attributes.oddEven",
["rules.sections.attributes.digitSize", "Big Digit / Small Digit按指定位数字判断5-9 为大0-4 为小;多位分别下注时独立结算。"], "rules.sections.attributes.digitSize",
], ],
}, },
{ {
title: "rules.sections.wallet.title", title: "rules.sections.wallet.title",
titleDefault: "Jackpot、回水、封盘与售罄",
items: [ items: [
["rules.sections.wallet.rebate", "回水 / 佣金:下注时锁定到注单赔率快照,结算不会受之后配置变化影响。"], "rules.sections.wallet.rebate",
["rules.sections.wallet.jackpot", "Jackpot命中规则与后台奖池配置相关爆池、蓄水和派彩以系统记录为准。"], "rules.sections.wallet.jackpot",
["rules.sections.wallet.close", "封盘:到达封盘时间后大厅不可继续编辑或提交注单。"], "rules.sections.wallet.close",
["rules.sections.wallet.soldOut", "售罄:号码赔付池额度不足时,预览会提示风险,提交时额度不足会整单拒绝。"], "rules.sections.wallet.soldOut",
], ],
}, },
] as const; ] as const;
@@ -86,10 +80,8 @@ export function PlayRulesScreen() {
return ( return (
<PlayerPanel <PlayerPanel
title={t("rules.title", { defaultValue: "玩法规则" })} title={t("rules.title")}
subtitle={t("rules.subtitle", { subtitle={t("rules.subtitle")}
defaultValue: "2D / 3D / 4D、包号、回水、封盘与售罄说明",
})}
backHref="/hall" backHref="/hall"
className="bg-[#f7f9fd]" className="bg-[#f7f9fd]"
> >
@@ -102,12 +94,10 @@ export function PlayRulesScreen() {
</span> </span>
<div className="min-w-0"> <div className="min-w-0">
<CardTitle className="text-base text-[#0b3f96]"> <CardTitle className="text-base text-[#0b3f96]">
{t("rules.quick.title", { defaultValue: "开奖结构" })} {t("rules.quick.title")}
</CardTitle> </CardTitle>
<p className="mt-0.5 text-xs text-slate-500"> <p className="mt-0.5 text-xs text-slate-500">
{t("rules.quick.description", { {t("rules.quick.description")}
defaultValue: "每期开奖 23 组 4 位号码,结算以下注时锁定的赔率快照为准。",
})}
</p> </p>
</div> </div>
</div> </div>
@@ -115,19 +105,19 @@ export function PlayRulesScreen() {
<div className="rounded-2xl bg-[#eef5ff] p-3"> <div className="rounded-2xl bg-[#eef5ff] p-3">
<p className="text-lg font-black text-[#0b3f96]">23</p> <p className="text-lg font-black text-[#0b3f96]">23</p>
<p className="text-[11px] font-semibold text-slate-500"> <p className="text-[11px] font-semibold text-slate-500">
{t("rules.quick.totalPrizes", { defaultValue: "奖项组数" })} {t("rules.quick.totalPrizes")}
</p> </p>
</div> </div>
<div className="rounded-2xl bg-[#fff2f4] p-3"> <div className="rounded-2xl bg-[#fff2f4] p-3">
<p className="text-lg font-black text-[#f10b32]">15</p> <p className="text-lg font-black text-[#f10b32]">15</p>
<p className="text-[11px] font-semibold text-slate-500"> <p className="text-[11px] font-semibold text-slate-500">
{t("rules.quick.cooldown", { defaultValue: "冷静期分钟" })} {t("rules.quick.cooldown")}
</p> </p>
</div> </div>
<div className="rounded-2xl bg-[#eefbf4] p-3"> <div className="rounded-2xl bg-[#eefbf4] p-3">
<p className="text-lg font-black text-[#168a4a]">1</p> <p className="text-lg font-black text-[#168a4a]">1</p>
<p className="text-[11px] font-semibold text-slate-500"> <p className="text-[11px] font-semibold text-slate-500">
{t("rules.quick.snapshot", { defaultValue: "赔率快照" })} {t("rules.quick.snapshot")}
</p> </p>
</div> </div>
</div> </div>
@@ -140,7 +130,7 @@ export function PlayRulesScreen() {
className="flex items-center justify-between rounded-xl border border-[#e6edf7] bg-white px-3 py-2 text-sm" className="flex items-center justify-between rounded-xl border border-[#e6edf7] bg-white px-3 py-2 text-sm"
> >
<span className="font-semibold text-slate-700"> <span className="font-semibold text-slate-700">
{t(row.label, { defaultValue: row.defaultValue })} {t(row.label)}
</span> </span>
<Badge variant="secondary">{row.count}</Badge> <Badge variant="secondary">{row.count}</Badge>
</div> </div>
@@ -154,17 +144,17 @@ export function PlayRulesScreen() {
<Card key={section.title} className="border-[#e1e8f2] bg-white shadow-sm"> <Card key={section.title} className="border-[#e1e8f2] bg-white shadow-sm">
<CardHeader className="pb-2"> <CardHeader className="pb-2">
<CardTitle className="text-sm font-black text-slate-900"> <CardTitle className="text-sm font-black text-slate-900">
{t(section.title, { defaultValue: section.titleDefault })} {t(section.title)}
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<ul className="space-y-2"> <ul className="space-y-2">
{section.items.map(([item, defaultValue]) => ( {section.items.map((item) => (
<li <li
key={item} key={item}
className="rounded-xl bg-[#f8fafc] px-3 py-2 text-sm leading-6 text-slate-700" className="rounded-xl bg-[#f8fafc] px-3 py-2 text-sm leading-6 text-slate-700"
> >
{t(item, { defaultValue })} {t(item)}
</li> </li>
))} ))}
</ul> </ul>
@@ -178,24 +168,20 @@ export function PlayRulesScreen() {
<div className="flex gap-3"> <div className="flex gap-3">
<ShieldCheck className="mt-0.5 size-5 shrink-0" aria-hidden /> <ShieldCheck className="mt-0.5 size-5 shrink-0" aria-hidden />
<p className="text-sm leading-6 text-white/90"> <p className="text-sm leading-6 text-white/90">
{t("rules.footer.config", { {t("rules.footer.config")}
defaultValue: "下注大厅的玩法列、启用状态、限额和赔率来自后台生效玩法配置。",
})}
</p> </p>
</div> </div>
<div className="flex gap-3"> <div className="flex gap-3">
<TimerReset className="mt-0.5 size-5 shrink-0" aria-hidden /> <TimerReset className="mt-0.5 size-5 shrink-0" aria-hidden />
<p className="text-sm leading-6 text-white/90"> <p className="text-sm leading-6 text-white/90">
{t("rules.footer.phaseTwo", { {t("rules.footer.phaseTwo")}
defaultValue: "5D / 6D 属于二期扩展,一期前后端均不提供可用入口。",
})}
</p> </p>
</div> </div>
<Link <Link
href="/hall" href="/hall"
className="inline-flex w-full items-center justify-center rounded-full bg-white px-4 py-2 text-sm font-black text-[#0b3f96]" className="inline-flex w-full items-center justify-center rounded-full bg-white px-4 py-2 text-sm font-black text-[#0b3f96]"
> >
{t("rules.footer.backBet", { defaultValue: "返回下注大厅" })} {t("rules.footer.backBet")}
</Link> </Link>
</CardContent> </CardContent>
</Card> </Card>

View File

@@ -152,12 +152,12 @@ export function WalletLogsBlock({
onClick={onLoadMore} onClick={onLoadMore}
> >
{loadingMore {loadingMore
? t("actions.loading", { defaultValue: "加载中..." }) ? t("actions.loading")
: t("actions.loadMore", { defaultValue: "加载更多" })} : t("actions.loadMore")}
</Button> </Button>
) : ( ) : (
<p className="py-2 text-center text-xs text-slate-400"> <p className="py-2 text-center text-xs text-slate-400">
{t("wallet.noMoreLogs", { defaultValue: "没有更多流水" })} {t("wallet.noMoreLogs")}
</p> </p>
)} )}
</> </>
@@ -179,29 +179,50 @@ export function LogRow({
const { t } = useTranslation("player"); const { t } = useTranslation("player");
const ccy = item.currency_code || currency; const ccy = item.currency_code || currency;
const isIn = item.direction === "in"; const isIn = item.direction === "in";
const statusTone = item.status === "posted"
? "border-emerald-200 bg-emerald-50 text-emerald-700"
: item.status === "pending_reconcile"
? "border-amber-200 bg-amber-50 text-amber-700"
: item.status === "reversed"
? "border-slate-200 bg-slate-50 text-slate-600"
: "border-blue-200 bg-blue-50 text-blue-700";
return ( return (
<li className="rounded-xl border border-[#e5edf8] bg-white px-3 py-3 text-sm shadow-[0_8px_24px_rgba(15,23,42,0.05)]"> <li className="rounded-2xl border border-[#e1eaf6] bg-white px-4 py-3.5 text-sm shadow-[0_10px_28px_rgba(15,23,42,0.06)]">
<div className="flex items-start justify-between gap-2"> <div className="flex items-start justify-between gap-3">
<div> <div className="min-w-0 flex-1">
<span className="font-medium"> <div className="flex items-center gap-2">
{logTypeLabel(item.type, t)}{" "} <span className={isIn ? "size-2.5 rounded-full bg-emerald-500" : "size-2.5 rounded-full bg-[#0b3f96]"} aria-hidden />
<span className={isIn ? "font-black text-emerald-600" : "font-black text-[#0b3f96]"}> <p className="truncate text-base font-black leading-tight text-[#101a33]">
{isIn ? "+" : ""} {logTypeLabel(item.type, t)}
{formatMinorAsCurrency(item.amount_abs, ccy)} </p>
</span> </div>
</span> <p className="mt-1.5 text-xs font-medium text-slate-500">
<p className="mt-1 text-xs text-muted-foreground"> {formatLocalDateTime(item.created_at)}
{formatLocalDateTime(item.created_at)}{" "}
<span className="text-foreground/80">
· {txnStatusLabel(item.status, t)}
</span>
</p> </p>
{item.ref_id ? ( {item.ref_id ? (
<p className="mt-0.5 font-mono text-[11px] text-muted-foreground"> <p className="mt-1 truncate font-mono text-[11px] text-slate-400">
{item.ref_id} {item.ref_id}
</p> </p>
) : null} ) : null}
</div> </div>
<div className="flex shrink-0 flex-col items-end gap-2 text-right">
<p className={isIn ? "text-lg font-black tabular-nums text-emerald-600" : "text-lg font-black tabular-nums text-[#0b3f96]"}>
{isIn ? "+" : ""}
{formatMinorAsCurrency(item.amount_abs, ccy)}
</p>
<span className={`inline-flex items-center rounded-full border px-2.5 py-1 text-[11px] font-black ${statusTone}`}>
{txnStatusLabel(item.status, t)}
</span>
</div>
</div>
<div className="mt-3 flex items-center justify-between rounded-xl bg-[#f8fbff] px-3 py-2 text-xs">
<span className="font-semibold text-slate-500">{t("wallet.balanceAfter")}</span>
<span className="font-mono font-black tabular-nums text-[#32518d]">
{formatMinorAsCurrency(item.balance_after, ccy)}
</span>
</div> </div>
</li> </li>
); );

View File

@@ -194,8 +194,8 @@ export function WalletScreen() {
lotteryMinor={Number(balance?.balance ?? 0)} lotteryMinor={Number(balance?.balance ?? 0)}
onSuccess={refreshAll} onSuccess={refreshAll}
triggerVariant="hall" triggerVariant="hall"
triggerLabel={t("wallet.transferIn")} triggerLabel={t("wallet.transferIn", { defaultValue: "Transfer In" })}
triggerClassName="h-12 rounded-lg text-base font-bold" triggerClassName="h-14 rounded-2xl text-base font-black"
/> />
<TransferOutDialog <TransferOutDialog
idPrefix="wallet-" idPrefix="wallet-"
@@ -203,8 +203,8 @@ export function WalletScreen() {
availableMinor={Number(balance?.available_balance ?? 0)} availableMinor={Number(balance?.available_balance ?? 0)}
onSuccess={refreshAll} onSuccess={refreshAll}
triggerVariant="hall" triggerVariant="hall"
triggerLabel={t("wallet.transferOut")} triggerLabel={t("wallet.transferOut", { defaultValue: "Transfer Out" })}
triggerClassName="h-12 rounded-lg text-base font-bold" triggerClassName="h-14 rounded-2xl text-base font-black"
/> />
</div> </div>

View File

@@ -26,15 +26,13 @@ type BaseProps = {
}; };
const defaultInTrigger = const defaultInTrigger =
"!bg-[#52c41a] !text-white shadow-none hover:!bg-[#52c41a]/90"; "!bg-[#0b3f96] !text-white shadow-[0_8px_18px_rgba(11,63,150,0.18)] hover:!bg-[#08357f]";
const defaultOutTrigger = "flex-1"; const defaultOutTrigger = "flex-1";
/** 高保真稿:蓝底白字 */
const hallInTrigger = const hallInTrigger =
"rounded-lg border-0 !bg-[#1677ff] !text-white shadow-sm hover:!bg-[#1677ff]/90"; "rounded-2xl border border-[#d7e5fb] !bg-[#0b3f96] !text-white shadow-[0_10px_24px_rgba(11,63,150,0.22)] hover:!bg-[#08357f]";
/** 高保真稿:白底红框红字 */
const hallOutTrigger = const hallOutTrigger =
"rounded-lg !border-2 !border-[#ff4d4f] !bg-white !text-[#ff4d4f] shadow-sm hover:!bg-red-50 dark:!bg-card dark:hover:!bg-red-950/30"; "rounded-2xl border border-[#ffc7d2] !bg-white !text-[#e5002c] shadow-[0_8px_22px_rgba(216,20,53,0.08)] hover:!bg-[#fff5f7]";
export function TransferInDialog({ export function TransferInDialog({
currency, currency,
@@ -71,24 +69,26 @@ export function TransferInDialog({
<ArrowDownLeft className="size-4 shrink-0" /> <ArrowDownLeft className="size-4 shrink-0" />
{resolvedTriggerLabel} {resolvedTriggerLabel}
</Button> </Button>
<DialogContent showCloseButton> <DialogContent showCloseButton className="overflow-hidden rounded-[28px] border border-[#dbe7fb] bg-white p-0 shadow-[0_24px_70px_rgba(11,63,150,0.18)] sm:max-w-[430px] [&_[data-slot=dialog-close]]:right-3 [&_[data-slot=dialog-close]]:top-3 [&_[data-slot=dialog-close]]:text-white [&_[data-slot=dialog-close]]:hover:bg-white/15 [&_[data-slot=dialog-close]]:hover:text-white">
<DialogHeader> <DialogHeader className="bg-gradient-to-br from-[#0b3f96] via-[#1456bd] to-[#e5002c] px-5 pb-5 pt-6 text-white">
<DialogTitle>{t("wallet.transferInTitle")}</DialogTitle> <DialogTitle className="text-xl font-black text-white">{t("wallet.transferInTitle")}</DialogTitle>
<DialogDescription> <DialogDescription className="text-sm leading-6 text-white/85">
{t("wallet.dialogInDescription", { currency })} {t("wallet.dialogInDescription", { currency })}
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<TransferInPanel <div className="px-5 pb-5 pt-4">
variant="dialog" <TransferInPanel
currency={currency} variant="dialog"
lotteryMinor={lotteryMinor} currency={currency}
idPrefix={idPrefix} lotteryMinor={lotteryMinor}
onCancel={() => setOpen(false)} idPrefix={idPrefix}
onSuccess={async () => { onCancel={() => setOpen(false)}
await onSuccess(); onSuccess={async () => {
setOpen(false); await onSuccess();
}} setOpen(false);
/> }}
/>
</div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
); );
@@ -134,24 +134,26 @@ export function TransferOutDialog({
<ArrowUpRight className="size-4 shrink-0" /> <ArrowUpRight className="size-4 shrink-0" />
{resolvedTriggerLabel} {resolvedTriggerLabel}
</Button> </Button>
<DialogContent showCloseButton> <DialogContent showCloseButton className="overflow-hidden rounded-[28px] border border-[#ffd7df] bg-white p-0 shadow-[0_24px_70px_rgba(216,20,53,0.16)] sm:max-w-[430px] [&_[data-slot=dialog-close]]:right-3 [&_[data-slot=dialog-close]]:top-3 [&_[data-slot=dialog-close]]:text-white [&_[data-slot=dialog-close]]:hover:bg-white/15 [&_[data-slot=dialog-close]]:hover:text-white">
<DialogHeader> <DialogHeader className="bg-gradient-to-br from-[#e5002c] via-[#d81435] to-[#0b3f96] px-5 pb-5 pt-6 text-white">
<DialogTitle>{t("wallet.transferOutTitle")}</DialogTitle> <DialogTitle className="text-xl font-black text-white">{t("wallet.transferOutTitle")}</DialogTitle>
<DialogDescription> <DialogDescription className="text-sm leading-6 text-white/85">
{t("wallet.dialogOutDescription")} {t("wallet.dialogOutDescription")}
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<TransferOutPanel <div className="px-5 pb-5 pt-4">
variant="dialog" <TransferOutPanel
currency={currency} variant="dialog"
availableMinor={availableMinor} currency={currency}
idPrefix={idPrefix} availableMinor={availableMinor}
onCancel={() => setOpen(false)} idPrefix={idPrefix}
onSuccess={async () => { onCancel={() => setOpen(false)}
await onSuccess(); onSuccess={async () => {
setOpen(false); await onSuccess();
}} setOpen(false);
/> }}
/>
</div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
); );

View File

@@ -121,17 +121,10 @@ export function TransferInPanel({
)} )}
</Button> </Button>
) : ( ) : (
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end"> <div className="grid gap-2">
<Button
type="button"
variant="outline"
disabled={submitting}
onClick={onCancel}
>
{t("actions.cancel")}
</Button>
<Button <Button
type="button" type="button"
className="h-12 w-full rounded-2xl bg-[#0b3f96] text-base font-black text-white shadow-[0_10px_22px_rgba(11,63,150,0.18)] hover:bg-[#08357f]"
disabled={submitting} disabled={submitting}
onClick={() => void submit()} onClick={() => void submit()}
> >
@@ -144,26 +137,35 @@ export function TransferInPanel({
t("wallet.confirmIn") t("wallet.confirmIn")
)} )}
</Button> </Button>
<Button
type="button"
variant="outline"
className="h-11 rounded-2xl border-[#dce7f7] bg-white text-base font-bold text-[#32518d] hover:bg-[#f8fbff]"
disabled={submitting}
onClick={onCancel}
>
{t("actions.cancel")}
</Button>
</div> </div>
); );
return ( return (
<> <>
<div className="grid gap-3 py-1"> <div className="grid gap-4 py-1">
<div className="rounded-xl border border-[#e5edf8] bg-[#f8fbff] px-3 py-2 text-xs"> <div className="rounded-2xl border border-[#dbe7fb] bg-gradient-to-br from-[#f8fbff] to-white px-4 py-3 text-sm shadow-[0_8px_22px_rgba(11,63,150,0.06)]">
<p> <p className="text-slate-500">
{t("wallet.mainBalance")}{" "} {t("wallet.mainBalance")}{" "}
<span className="text-muted-foreground">{t("wallet.mainPending")}</span> <span className="font-semibold text-slate-700">{t("wallet.mainPending")}</span>
</p> </p>
<p className="mt-1"> <p className="mt-2 text-slate-500">
{t("wallet.lotteryBalance")}{" "} {t("wallet.lotteryBalance")}{" "}
<span className="font-medium text-foreground"> <span className="font-black tabular-nums text-[#0b3f96]">
{formatMinorAsCurrency(lotteryMinor, currency)} {formatMinorAsCurrency(lotteryMinor, currency)}
</span> </span>
</p> </p>
</div> </div>
<div className="grid gap-2"> <div className="grid gap-2.5">
<Label htmlFor={tid}>{t("wallet.inAmount")}</Label> <Label htmlFor={tid} className="text-sm font-black text-[#101a33]">{t("wallet.inAmount")}</Label>
<Input <Input
id={tid} id={tid}
inputMode="decimal" inputMode="decimal"
@@ -172,16 +174,16 @@ export function TransferInPanel({
onChange={(ev) => setAmountText(ev.target.value)} onChange={(ev) => setAmountText(ev.target.value)}
disabled={submitting} disabled={submitting}
autoComplete="off" autoComplete="off"
className="h-11 rounded-lg border-[#dce7f7] bg-white text-base" className="h-13 rounded-2xl border-[#d7e5fb] bg-white px-4 text-lg font-bold tabular-nums text-[#101a33] shadow-inner focus-visible:ring-[#0b3f96]/20"
/> />
<p className="text-xs text-muted-foreground"> <p className="rounded-2xl bg-[#f8fbff] px-3 py-2 text-xs font-semibold leading-5 text-[#32518d]">
{t("wallet.afterInPreview", { {t("wallet.afterInPreview", {
amount: formatMinorAsCurrency(previewAfter, currency), amount: formatMinorAsCurrency(previewAfter, currency),
})} })}
</p> </p>
</div> </div>
{localError ? ( {localError ? (
<p className="text-sm text-[#ff4d4f]">{localError}</p> <p className="rounded-2xl border border-red-200 bg-red-50 px-3 py-2 text-sm font-semibold text-[#d81435]">{localError}</p>
) : null} ) : null}
</div> </div>
{footer} {footer}
@@ -270,17 +272,10 @@ export function TransferOutPanel({
)} )}
</Button> </Button>
) : ( ) : (
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end"> <div className="grid gap-2">
<Button
type="button"
variant="outline"
disabled={submitting}
onClick={onCancel}
>
{t("actions.cancel")}
</Button>
<Button <Button
type="button" type="button"
className="h-12 w-full rounded-2xl bg-[#e5002c] text-base font-black text-white shadow-[0_10px_22px_rgba(229,0,44,0.18)] hover:bg-[#d10028]"
disabled={submitting} disabled={submitting}
onClick={() => void submit()} onClick={() => void submit()}
> >
@@ -293,27 +288,36 @@ export function TransferOutPanel({
t("wallet.confirmOut") t("wallet.confirmOut")
)} )}
</Button> </Button>
<Button
type="button"
variant="outline"
className="h-11 rounded-2xl border-[#ffd7df] bg-white text-base font-bold text-[#d81435] hover:bg-[#fff5f7]"
disabled={submitting}
onClick={onCancel}
>
{t("actions.cancel")}
</Button>
</div> </div>
); );
return ( return (
<> <>
<div className="grid gap-3 py-1"> <div className="grid gap-4 py-1">
<div className="rounded-xl border border-[#e5edf8] bg-[#f8fbff] px-3 py-2 text-xs"> <div className="rounded-2xl border border-[#ffd7df] bg-gradient-to-br from-[#fff7f8] to-white px-4 py-3 text-sm shadow-[0_8px_22px_rgba(216,20,53,0.06)]">
<p> <p className="text-slate-500">
{t("wallet.lotteryAvailable")}{" "} {t("wallet.lotteryAvailable")}{" "}
<span className="font-medium text-foreground"> <span className="font-black tabular-nums text-[#d81435]">
{formatMinorAsCurrency(availableMinor, currency)} {formatMinorAsCurrency(availableMinor, currency)}
</span> </span>
</p> </p>
</div> </div>
<div className="grid gap-2"> <div className="grid gap-2.5">
<div className="flex items-end justify-between gap-2"> <div className="flex items-end justify-between gap-2">
<Label htmlFor={tid}>{t("wallet.outAmount")}</Label> <Label htmlFor={tid} className="text-sm font-black text-[#101a33]">{t("wallet.outAmount")}</Label>
<Button <Button
type="button" type="button"
variant="link" variant="link"
className="h-auto p-0 text-xs" className="h-auto p-0 text-xs font-black text-[#d81435] hover:text-[#b80f2b]"
onClick={fillAll} onClick={fillAll}
disabled={submitting || availableMinor < 1} disabled={submitting || availableMinor < 1}
> >
@@ -330,16 +334,16 @@ export function TransferOutPanel({
onChange={(ev) => setAmountText(ev.target.value)} onChange={(ev) => setAmountText(ev.target.value)}
disabled={submitting} disabled={submitting}
autoComplete="off" autoComplete="off"
className="h-11 rounded-lg border-[#dce7f7] bg-white text-base" className="h-13 rounded-2xl border-[#ffd7df] bg-white px-4 text-lg font-bold tabular-nums text-[#101a33] shadow-inner focus-visible:ring-[#e5002c]/20"
/> />
<p className="text-xs text-muted-foreground"> <p className="rounded-2xl bg-[#fff7f8] px-3 py-2 text-xs font-semibold leading-5 text-[#9f1730]">
{t("wallet.afterOutPreview", { {t("wallet.afterOutPreview", {
amount: formatMinorAsCurrency(previewAfter, currency), amount: formatMinorAsCurrency(previewAfter, currency),
})} })}
</p> </p>
</div> </div>
{localError ? ( {localError ? (
<p className="text-sm text-[#ff4d4f]">{localError}</p> <p className="rounded-2xl border border-red-200 bg-red-50 px-3 py-2 text-sm font-semibold text-[#d81435]">{localError}</p>
) : null} ) : null}
</div> </div>
{footer} {footer}

View File

@@ -9,8 +9,12 @@
"clear": "Clear", "clear": "Clear",
"cancel": "Cancel", "cancel": "Cancel",
"confirm": "Confirm", "confirm": "Confirm",
"close": "Close",
"loading": "Loading...", "loading": "Loading...",
"loadMore": "Load More", "loadMore": "Load More",
"previous": "Previous",
"next": "Next",
"done": "Done",
"processing": "Processing...", "processing": "Processing...",
"deleteRow": "Delete row {{row}}" "deleteRow": "Delete row {{row}}"
}, },
@@ -102,6 +106,13 @@
"changedBeforeSubmit": "Your draft changed before submission. Close the preview and try again.", "changedBeforeSubmit": "Your draft changed before submission. Close the preview and try again.",
"placeFailed": "Submission failed", "placeFailed": "Submission failed",
"placeSuccess": "Bet submitted. Order {{orderNo}}, deducted {{amount}}.", "placeSuccess": "Bet submitted. Order {{orderNo}}, deducted {{amount}}.",
"placePartialFailed": "{{success}} succeeded, {{failed}} failed",
"playConfig": {
"playClosedDraftCleared": "{{playCode}} is closed. Related draft amounts have been cleared.",
"playClosed": "{{playCode}} is closed.",
"updated": "Play configuration updated. The betting table has been refreshed.",
"oddsUpdated": "Odds updated. Please preview the ticket again."
},
"jackpotBurst": { "jackpotBurst": {
"title": "Jackpot Burst", "title": "Jackpot Burst",
"subtitle": "Issue {{drawNo}} triggered a pool payout", "subtitle": "Issue {{drawNo}} triggered a pool payout",
@@ -145,13 +156,28 @@
"sealedHint": "Closed: this table is locked. Please wait for the next issue.", "sealedHint": "Closed: this table is locked. Please wait for the next issue.",
"previewing": "Previewing...", "previewing": "Previewing...",
"submitBet": "Submit Bet", "submitBet": "Submit Bet",
"insufficientBalance": "Insufficient balance",
"amountPlaceholder": "Amount", "amountPlaceholder": "Amount",
"filledPlayCount": "{{count}} plays filled", "filledPlayCount": "{{count}} plays filled",
"tapToFill": "Tap to enter amounts", "tapToFill": "Tap to enter amounts",
"rowActual": "Actual", "rowActual": "Actual",
"editRow": "Edit row {{row}}", "editRow": "Edit row {{row}}",
"scrollHint": "This table is wide: swipe or scroll horizontally, then enter your stake in each play column on the right (e.g. Big/Small, position plays).", "scrollHint": "This table is wide: swipe or scroll horizontally, then enter your stake in each play column on the right (e.g. Big/Small, position plays).",
"noPlaysInCategory": "No open play types in this tab, so there are no amount fields. Try 2D / 3D / 4D, or ask an admin to enable plays for this category." "noPlaysInCategory": "No open play types in this tab, so there are no amount fields. Try 2D / 3D / 4D, or ask an admin to enable plays for this category.",
"soldOut": "Sold out",
"warning": "Almost sold out"
},
"quickFill": {
"title": "Quick fill",
"description": "Fill the current row with favorite or recent numbers in one tap.",
"clearAll": "Clear all",
"favorite": "Favorite number",
"unfavorite": "Remove favorite",
"favorites": "Favorites",
"tapHold": "Hold to remove",
"emptyFavorites": "No favorites yet",
"history": "Recent 20 numbers",
"emptyHistory": "No recent numbers"
}, },
"preview": { "preview": {
"title": "Confirm bet", "title": "Confirm bet",
@@ -183,6 +209,24 @@
"warningsTitle": "Payout pool warning", "warningsTitle": "Payout pool warning",
"warningsDescription": "The following numbers have high payout pool usage for this issue. Betting is still allowed, but the order may be rejected as sold out if capacity is insufficient." "warningsDescription": "The following numbers have high payout pool usage for this issue. Betting is still allowed, but the order may be rejected as sold out if capacity is insufficient."
}, },
"result": {
"title": "Bet result",
"draw": "Issue",
"empty": "No result yet.",
"successCount": "Successful lines",
"failureCount": "Failed lines",
"actual": "Actual deduction",
"orderNo": "Order No.",
"balanceAfter": "Remaining balance",
"items": "Successful line details",
"number": "Number",
"actualDeduct": "Deducted",
"noFailures": "No ticket lines failed in this submission.",
"failedItems": "Failed line details",
"failed": "Failed",
"viewBets": "View tickets",
"continueBetting": "Continue betting"
},
"amountInput": { "amountInput": {
"limit": "Limit {{min}} - {{max}}", "limit": "Limit {{min}} - {{max}}",
"placeholder": "e.g. 100.00" "placeholder": "e.g. 100.00"
@@ -280,6 +324,7 @@
"flowsTitle": "Wallet logs", "flowsTitle": "Wallet logs",
"totalRecords": "{{total}} records", "totalRecords": "{{total}} records",
"emptyLogs": "No wallet logs", "emptyLogs": "No wallet logs",
"balanceAfter": "Balance after",
"flow": { "flow": {
"all": "All", "all": "All",
"transfer_in": "Transfer in", "transfer_in": "Transfer in",
@@ -322,7 +367,9 @@
"betNow": "Bet Now", "betNow": "Bet Now",
"empty": "No bet records yet.", "empty": "No bet records yet.",
"dateRange": "Date range", "dateRange": "Date range",
"status": "Status",
"statusFilter": "Status filter", "statusFilter": "Status filter",
"noMore": "No more tickets",
"submitBet": "Submit Bet", "submitBet": "Submit Bet",
"stake": "Stake", "stake": "Stake",
"deduction": "Deduction", "deduction": "Deduction",
@@ -364,8 +411,10 @@
"subtitle": "Latest draw history", "subtitle": "Latest draw history",
"detailTitle": "Result Detail", "detailTitle": "Result Detail",
"businessDate": "Business Date", "businessDate": "Business Date",
"selectBusinessDate": "Select date",
"empty": "No results yet.", "empty": "No results yet.",
"detail": "Detail", "detail": "Detail",
"openDetail": "Open detail",
"loadFailed": "Failed to load", "loadFailed": "Failed to load",
"unavailable": "This result is unavailable or does not exist.", "unavailable": "This result is unavailable or does not exist.",
"noData": "No data", "noData": "No data",
@@ -376,10 +425,32 @@
"regular": "Regular: {{amount}}", "regular": "Regular: {{amount}}",
"jackpot": "Jackpot: {{amount}}", "jackpot": "Jackpot: {{amount}}",
"hitPending": "Your ticket has hit a result cell in this issue. The amount summary will show after payout is completed.", "hitPending": "Your ticket has hit a result cell in this issue. The amount summary will show after payout is completed.",
"hitHint": "If you win, numbers matched by your tickets are highlighted in gold (login required).", "hitHint": "If you win, numbers matched by your tickets are highlighted in gold.",
"viewMyWinning": "View my winning status", "viewMyWinning": "View my winning status",
"jackpotLabel": "Jackpot", "jackpotLabel": "Jackpot",
"jackpotGap": "{{count}} draws since last burst", "jackpotGap": "{{count}} draws since last burst",
"noMore": "No more results",
"check": {
"title": "Check My Winnings",
"loadFailed": "Query failed. Please try again later.",
"enterTicket": "Enter your ticket number or number",
"description": "The system checks your tickets and winnings against the latest published draw.",
"ticketNumber": "Ticket No. / Number",
"placeholder": "Enter ticket number or number",
"latestDraw": "Latest issue {{drawNo}}",
"loading": "Checking...",
"submit": "Check now",
"recent": "Recent checks",
"noRecent": "No recent checks.",
"winTitle": "Congratulations, you won",
"noWinTitle": "No winning result found",
"match": "Match",
"amount": "Winning amount",
"drawInfo": "Draw info",
"issueNo": "Issue No.",
"viewBetDetails": "View bet details",
"checkAnother": "Check another ticket"
},
"tier": { "tier": {
"first": "First prize", "first": "First prize",
"second": "Second prize", "second": "Second prize",

View File

@@ -9,8 +9,12 @@
"clear": "हटाउनुहोस्", "clear": "हटाउनुहोस्",
"cancel": "रद्द", "cancel": "रद्द",
"confirm": "पुष्टि", "confirm": "पुष्टि",
"close": "बन्द",
"loading": "लोड हुँदैछ...", "loading": "लोड हुँदैछ...",
"loadMore": "थप लोड गर्नुहोस्", "loadMore": "थप लोड गर्नुहोस्",
"previous": "अघिल्लो",
"next": "अर्को",
"done": "पूरा भयो",
"processing": "प्रक्रिया हुँदैछ...", "processing": "प्रक्रिया हुँदैछ...",
"deleteRow": "{{row}} नम्बर पंक्ति हटाउनुहोस्" "deleteRow": "{{row}} नम्बर पंक्ति हटाउनुहोस्"
}, },
@@ -102,6 +106,13 @@
"changedBeforeSubmit": "पेश गर्नु अघि ड्राफ्ट परिवर्तन भयो। पूर्वावलोकन बन्द गरी फेरि प्रयास गर्नुहोस्।", "changedBeforeSubmit": "पेश गर्नु अघि ड्राफ्ट परिवर्तन भयो। पूर्वावलोकन बन्द गरी फेरि प्रयास गर्नुहोस्।",
"placeFailed": "पेश गर्न असफल", "placeFailed": "पेश गर्न असफल",
"placeSuccess": "बेट पेश भयो। अर्डर {{orderNo}}, कट्टा {{amount}}।", "placeSuccess": "बेट पेश भयो। अर्डर {{orderNo}}, कट्टा {{amount}}।",
"placePartialFailed": "{{success}} सफल, {{failed}} असफल",
"playConfig": {
"playClosedDraftCleared": "{{playCode}} बन्द छ। सम्बन्धित ड्राफ्ट रकम हटाइएको छ।",
"playClosed": "{{playCode}} बन्द छ।",
"updated": "प्ले कन्फिगरेसन अपडेट भयो। बेटिङ तालिका रिफ्रेस गरियो।",
"oddsUpdated": "Odds अपडेट भयो। कृपया टिकट फेरि पूर्वावलोकन गर्नुहोस्।"
},
"jackpotBurst": { "jackpotBurst": {
"title": "Jackpot Burst", "title": "Jackpot Burst",
"subtitle": "इश्यू {{drawNo}} मा पूल payout ट्रिगर भयो", "subtitle": "इश्यू {{drawNo}} मा पूल payout ट्रिगर भयो",
@@ -145,13 +156,28 @@
"sealedHint": "बन्द: यो तालिका लक छ। कृपया अर्को इश्यू पर्खनुहोस्।", "sealedHint": "बन्द: यो तालिका लक छ। कृपया अर्को इश्यू पर्खनुहोस्।",
"previewing": "पूर्वावलोकन...", "previewing": "पूर्वावलोकन...",
"submitBet": "बेट पेश गर्नुहोस्", "submitBet": "बेट पेश गर्नुहोस्",
"insufficientBalance": "ब्यालेन्स अपुग",
"amountPlaceholder": "रकम", "amountPlaceholder": "रकम",
"filledPlayCount": "{{count}} प्ले भरियो", "filledPlayCount": "{{count}} प्ले भरियो",
"tapToFill": "रकम लेख्न ट्याप गर्नुहोस्", "tapToFill": "रकम लेख्न ट्याप गर्नुहोस्",
"rowActual": "वास्तविक", "rowActual": "वास्तविक",
"editRow": "पंक्ति {{row}} सम्पादन गर्नुहोस्", "editRow": "पंक्ति {{row}} सम्पादन गर्नुहोस्",
"scrollHint": "तालिका फराकिलो छ: दायाँतिर स्क्रोल गर्नुहोस्, दायाँका प्रत्येक खेल स्तम्भमा बेट रकम लेख्नुहोस्।", "scrollHint": "तालिका फराकिलो छ: दायाँतिर स्क्रोल गर्नुहोस्, दायाँका प्रत्येक खेल स्तम्भमा बेट रकम लेख्नुहोस्।",
"noPlaysInCategory": "यस ट्याबमा खुला खेल प्रकार छैन। २D / ३D / ४D प्रयास गर्नुहोस् वा व्यवस्थापकले खेल खोल्नुपर्छ।" "noPlaysInCategory": "यस ट्याबमा खुला खेल प्रकार छैन। २D / ३D / ४D प्रयास गर्नुहोस् वा व्यवस्थापकले खेल खोल्नुपर्छ।",
"soldOut": "Sold out",
"warning": "लगभग sold out"
},
"quickFill": {
"title": "छिटो भर्ने",
"description": "मनपर्ने वा पछिल्ला नम्बरहरू एक ट्यापमा हालको पंक्तिमा भर्नुहोस्।",
"clearAll": "सबै हटाउनुहोस्",
"favorite": "नम्बर मनपर्नेमा राख्नुहोस्",
"unfavorite": "मनपर्नेबाट हटाउनुहोस्",
"favorites": "मनपर्ने",
"tapHold": "हटाउन होल्ड गर्नुहोस्",
"emptyFavorites": "मनपर्ने छैन",
"history": "पछिल्ला 20 नम्बर",
"emptyHistory": "इतिहास नम्बर छैन"
}, },
"preview": { "preview": {
"title": "बेट पुष्टि गर्नुहोस्", "title": "बेट पुष्टि गर्नुहोस्",
@@ -176,9 +202,31 @@
"backEdit": "सम्पादनमा फर्कनुहोस्", "backEdit": "सम्पादनमा फर्कनुहोस्",
"submitting": "पेश हुँदैछ...", "submitting": "पेश हुँदैछ...",
"confirmSubmit": "पेश पुष्टि", "confirmSubmit": "पेश पुष्टि",
"processingTitle": "बेट पेश हुँदैछ",
"processingDescription": "कृपया पृष्ठ बन्द नगर्नुहोस् वा पछाडि नजानुहोस्।",
"processingProgress": "टिकट प्रक्रिया हुँदैछ...",
"noWarnings": "यस पूर्वावलोकनमा स्पष्ट जोखिम भेटिएन।",
"warningsTitle": "भुक्तानी पूल चेतावनी", "warningsTitle": "भुक्तानी पूल चेतावनी",
"warningsDescription": "यी नम्बरहरूमा यस इश्यूमा भुक्तानी पूल प्रयोग उच्च छ। बेट अझै गर्न सकिन्छ, तर क्षमता अपुग भए अर्डर sold out हुन सक्छ।" "warningsDescription": "यी नम्बरहरूमा यस इश्यूमा भुक्तानी पूल प्रयोग उच्च छ। बेट अझै गर्न सकिन्छ, तर क्षमता अपुग भए अर्डर sold out हुन सक्छ।"
}, },
"result": {
"title": "बेट नतिजा",
"draw": "इश्यू",
"empty": "नतिजा छैन।",
"successCount": "सफल लाइनहरू",
"failureCount": "असफल लाइनहरू",
"actual": "वास्तविक कट्टा",
"orderNo": "अर्डर नं.",
"balanceAfter": "बाँकी ब्यालेन्स",
"items": "सफल लाइन विवरण",
"number": "नम्बर",
"actualDeduct": "कट्टा",
"noFailures": "यस पेशमा असफल टिकट लाइन छैन।",
"failedItems": "असफल लाइन विवरण",
"failed": "असफल",
"viewBets": "टिकट हेर्नुहोस्",
"continueBetting": "बेट जारी राख्नुहोस्"
},
"amountInput": { "amountInput": {
"limit": "सीमा {{min}} - {{max}}", "limit": "सीमा {{min}} - {{max}}",
"placeholder": "जस्तै 100.00" "placeholder": "जस्तै 100.00"
@@ -276,6 +324,7 @@
"flowsTitle": "वालेट लग", "flowsTitle": "वालेट लग",
"totalRecords": "{{total}} रेकर्ड", "totalRecords": "{{total}} रेकर्ड",
"emptyLogs": "वालेट लग छैन", "emptyLogs": "वालेट लग छैन",
"balanceAfter": "पछि बाँकी ब्यालेन्स",
"flow": { "flow": {
"all": "सबै", "all": "सबै",
"transfer_in": "ट्रान्सफर इन", "transfer_in": "ट्रान्सफर इन",
@@ -317,6 +366,10 @@
"totalRecords": "कुल रेकर्ड", "totalRecords": "कुल रेकर्ड",
"betNow": "अहिले बेट", "betNow": "अहिले बेट",
"empty": "अहिलेसम्म बेट रेकर्ड छैन।", "empty": "अहिलेसम्म बेट रेकर्ड छैन।",
"dateRange": "मिति दायरा",
"status": "स्थिति",
"statusFilter": "स्थिति फिल्टर",
"noMore": "थप टिकट छैन",
"submitBet": "बेट पेश गर्नुहोस्", "submitBet": "बेट पेश गर्नुहोस्",
"stake": "बेट", "stake": "बेट",
"deduction": "कट्टा", "deduction": "कट्टा",
@@ -353,8 +406,10 @@
"subtitle": "हालका ड्र इतिहास", "subtitle": "हालका ड्र इतिहास",
"detailTitle": "नतिजा विवरण", "detailTitle": "नतिजा विवरण",
"businessDate": "व्यावसायिक मिति", "businessDate": "व्यावसायिक मिति",
"selectBusinessDate": "मिति छान्नुहोस्",
"empty": "अहिलेसम्म नतिजा छैन।", "empty": "अहिलेसम्म नतिजा छैन।",
"detail": "विवरण", "detail": "विवरण",
"openDetail": "विवरण खोल्नुहोस्",
"loadFailed": "लोड असफल", "loadFailed": "लोड असफल",
"unavailable": "यो नतिजा उपलब्ध छैन वा अस्तित्वमा छैन।", "unavailable": "यो नतिजा उपलब्ध छैन वा अस्तित्वमा छैन।",
"noData": "डेटा छैन", "noData": "डेटा छैन",
@@ -365,10 +420,32 @@
"regular": "सामान्य: {{amount}}", "regular": "सामान्य: {{amount}}",
"jackpot": "Jackpot: {{amount}}", "jackpot": "Jackpot: {{amount}}",
"hitPending": "तपाईंको टिकटले यस इश्यूको नतिजा सेल हिट गरेको छ। भुक्तानी पूरा भएपछि रकम देखिनेछ।", "hitPending": "तपाईंको टिकटले यस इश्यूको नतिजा सेल हिट गरेको छ। भुक्तानी पूरा भएपछि रकम देखिनेछ।",
"hitHint": "तपाईं जित्नुभयो भने, तपाईंका टिकटसँग मिलेका नम्बरहरू सुनौलो रंगमा देखिन्छन् (लगइन आवश्यक)।", "hitHint": "तपाईं जित्नुभयो भने, तपाईंका टिकटसँग मिलेका नम्बरहरू सुनौलो रंगमा देखिन्छन्।",
"viewMyWinning": "मेरो जित स्थिति हेर्नुहोस्", "viewMyWinning": "मेरो जित स्थिति हेर्नुहोस्",
"jackpotLabel": "Jackpot", "jackpotLabel": "Jackpot",
"jackpotGap": "पछिल्लो burst देखि {{count}} draw", "jackpotGap": "पछिल्लो burst देखि {{count}} draw",
"noMore": "थप नतिजा छैन",
"check": {
"title": "मेरो जित जाँच",
"loadFailed": "जाँच असफल भयो। कृपया पछि प्रयास गर्नुहोस्।",
"enterTicket": "टिकट नम्बर वा नम्बर लेख्नुहोस्",
"description": "सिस्टमले पछिल्लो प्रकाशित ड्र अनुसार तपाईंको टिकट र जित जाँच गर्छ।",
"ticketNumber": "टिकट नं. / नम्बर",
"placeholder": "टिकट नम्बर वा नम्बर लेख्नुहोस्",
"latestDraw": "पछिल्लो इश्यू {{drawNo}}",
"loading": "जाँच हुँदैछ...",
"submit": "अहिले जाँच गर्नुहोस्",
"recent": "हालका जाँच",
"noRecent": "हालका जाँच छैनन्।",
"winTitle": "बधाई छ, तपाईं जित्नुभयो",
"noWinTitle": "जित नतिजा भेटिएन",
"match": "मिलान",
"amount": "जित रकम",
"drawInfo": "ड्र जानकारी",
"issueNo": "इश्यू नं.",
"viewBetDetails": "बेट विवरण हेर्नुहोस्",
"checkAnother": "अर्को टिकट जाँच गर्नुहोस्"
},
"tier": { "tier": {
"first": "पहिलो पुरस्कार", "first": "पहिलो पुरस्कार",
"second": "दोस्रो पुरस्कार", "second": "दोस्रो पुरस्कार",
@@ -450,6 +527,7 @@
}, },
"ticketStatus": { "ticketStatus": {
"success": "ड्र पर्खँदै", "success": "ड्र पर्खँदै",
"pending_payout": "जितेको, भुक्तानी बाँकी",
"settled_win": "भुक्तानी भयो", "settled_win": "भुक्तानी भयो",
"settled_lose": "जितेन", "settled_lose": "जितेन",
"unknown": "{{status}}" "unknown": "{{status}}"

View File

@@ -9,8 +9,12 @@
"clear": "清除", "clear": "清除",
"cancel": "取消", "cancel": "取消",
"confirm": "确认", "confirm": "确认",
"close": "关闭",
"loading": "加载中...", "loading": "加载中...",
"loadMore": "加载更多", "loadMore": "加载更多",
"previous": "上一页",
"next": "下一页",
"done": "完成",
"processing": "处理中...", "processing": "处理中...",
"deleteRow": "删除第 {{row}} 行" "deleteRow": "删除第 {{row}} 行"
}, },
@@ -102,6 +106,13 @@
"changedBeforeSubmit": "提交前数据已变化,请关闭预览后重试。", "changedBeforeSubmit": "提交前数据已变化,请关闭预览后重试。",
"placeFailed": "提交失败", "placeFailed": "提交失败",
"placeSuccess": "下注成功,订单号 {{orderNo}},实扣 {{amount}}。", "placeSuccess": "下注成功,订单号 {{orderNo}},实扣 {{amount}}。",
"placePartialFailed": "{{success}} 个成功,{{failed}} 个失败",
"playConfig": {
"playClosedDraftCleared": "{{playCode}} 已关闭,相关草稿金额已清除。",
"playClosed": "{{playCode}} 已关闭。",
"updated": "玩法配置已更新,已刷新下注表格。",
"oddsUpdated": "赔率已更新,请重新预览注单。"
},
"jackpotBurst": { "jackpotBurst": {
"title": "Jackpot 爆池", "title": "Jackpot 爆池",
"subtitle": "期号 {{drawNo}} 触发奖池派发", "subtitle": "期号 {{drawNo}} 触发奖池派发",
@@ -145,13 +156,28 @@
"sealedHint": "已封盘:当前表格不可编辑,请等待下一期。", "sealedHint": "已封盘:当前表格不可编辑,请等待下一期。",
"previewing": "预览中...", "previewing": "预览中...",
"submitBet": "提交下注", "submitBet": "提交下注",
"insufficientBalance": "余额不足",
"amountPlaceholder": "金额", "amountPlaceholder": "金额",
"filledPlayCount": "已填写 {{count}} 个玩法", "filledPlayCount": "已填写 {{count}} 个玩法",
"tapToFill": "点击填写玩法金额", "tapToFill": "点击填写玩法金额",
"rowActual": "实扣", "rowActual": "实扣",
"editRow": "编辑第 {{row}} 行", "editRow": "编辑第 {{row}} 行",
"scrollHint": "表格较宽:请向右滑动,在右侧各玩法列(如 Big / Small、位置玩法等输入下注金额。", "scrollHint": "表格较宽:请向右滑动,在右侧各玩法列(如 Big / Small、位置玩法等输入下注金额。",
"noPlaysInCategory": "当前分类没有已开放的玩法,无法填写金额。请尝试切换 2D / 3D / 4D或在后台开放对应玩法。" "noPlaysInCategory": "当前分类没有已开放的玩法,无法填写金额。请尝试切换 2D / 3D / 4D或在后台开放对应玩法。",
"soldOut": "售罄",
"warning": "接近售罄"
},
"quickFill": {
"title": "快速填单",
"description": "收藏号码、最近号码可一键填入当前行。",
"clearAll": "批量清空",
"favorite": "收藏号码",
"unfavorite": "取消收藏",
"favorites": "收藏",
"tapHold": "长按取消",
"emptyFavorites": "暂无收藏",
"history": "最近 20 个历史号码",
"emptyHistory": "暂无历史号码"
}, },
"preview": { "preview": {
"title": "确认下注", "title": "确认下注",
@@ -183,6 +209,24 @@
"warningsTitle": "赔付池预警", "warningsTitle": "赔付池预警",
"warningsDescription": "以下号码本期赔付池占用较高,仍允许下注;若实际占用不足将售罄拒单。" "warningsDescription": "以下号码本期赔付池占用较高,仍允许下注;若实际占用不足将售罄拒单。"
}, },
"result": {
"title": "下注结果",
"draw": "期号",
"empty": "暂无结果。",
"successCount": "成功注项",
"failureCount": "失败注项",
"actual": "实扣金额",
"orderNo": "订单号",
"balanceAfter": "剩余余额",
"items": "成功注项明细",
"number": "号码",
"actualDeduct": "实扣",
"noFailures": "本次提交没有失败注项。",
"failedItems": "失败注项明细",
"failed": "失败",
"viewBets": "查看注单",
"continueBetting": "继续下注"
},
"amountInput": { "amountInput": {
"limit": "限额 {{min}} - {{max}}", "limit": "限额 {{min}} - {{max}}",
"placeholder": "例如 100.00" "placeholder": "例如 100.00"
@@ -280,6 +324,7 @@
"flowsTitle": "资金流水", "flowsTitle": "资金流水",
"totalRecords": "共 {{total}} 条记录", "totalRecords": "共 {{total}} 条记录",
"emptyLogs": "暂无流水", "emptyLogs": "暂无流水",
"balanceAfter": "变更后余额",
"flow": { "flow": {
"all": "全部", "all": "全部",
"transfer_in": "转入", "transfer_in": "转入",
@@ -322,7 +367,9 @@
"betNow": "立即下注", "betNow": "立即下注",
"empty": "暂无下注记录。", "empty": "暂无下注记录。",
"dateRange": "日期范围", "dateRange": "日期范围",
"status": "状态",
"statusFilter": "状态筛选", "statusFilter": "状态筛选",
"noMore": "没有更多注单",
"submitBet": "提交下注", "submitBet": "提交下注",
"stake": "下注", "stake": "下注",
"deduction": "实扣", "deduction": "实扣",
@@ -364,8 +411,10 @@
"subtitle": "最新开奖历史", "subtitle": "最新开奖历史",
"detailTitle": "开奖详情", "detailTitle": "开奖详情",
"businessDate": "业务日期", "businessDate": "业务日期",
"selectBusinessDate": "选择日期",
"empty": "暂无开奖结果。", "empty": "暂无开奖结果。",
"detail": "详情", "detail": "详情",
"openDetail": "查看详情",
"loadFailed": "加载失败", "loadFailed": "加载失败",
"unavailable": "该期开奖结果不可用或不存在", "unavailable": "该期开奖结果不可用或不存在",
"noData": "无数据", "noData": "无数据",
@@ -376,10 +425,32 @@
"regular": "常规:{{amount}}", "regular": "常规:{{amount}}",
"jackpot": "Jackpot{{amount}}", "jackpot": "Jackpot{{amount}}",
"hitPending": "您的注单已命中本期开奖号码中的格子;派彩完成后将显示金额汇总。", "hitPending": "您的注单已命中本期开奖号码中的格子;派彩完成后将显示金额汇总。",
"hitHint": "如果您中奖,与注单匹配的号码将以金色高亮显示(需登录)。", "hitHint": "如果您中奖,与注单匹配的号码将以金色高亮显示。",
"viewMyWinning": "查看我的中奖情况", "viewMyWinning": "查看我的中奖情况",
"jackpotLabel": "Jackpot", "jackpotLabel": "Jackpot",
"jackpotGap": "距上次爆池 {{count}} 期", "jackpotGap": "距上次爆池 {{count}} 期",
"noMore": "没有更多开奖结果",
"check": {
"title": "查我的中奖",
"loadFailed": "查询失败,请稍后重试。",
"enterTicket": "输入你的票号或号码",
"description": "系统会按最新已发布期号查询你的注单和中奖情况。",
"ticketNumber": "票号 / 号码",
"placeholder": "请输入票号或号码",
"latestDraw": "最新期号 {{drawNo}}",
"loading": "查询中...",
"submit": "立即查询",
"recent": "最近查询",
"noRecent": "暂无查询记录。",
"winTitle": "恭喜,你中奖了",
"noWinTitle": "未查询到中奖",
"match": "匹配",
"amount": "中奖金额",
"drawInfo": "开奖信息",
"issueNo": "期号",
"viewBetDetails": "查看注单详情",
"checkAnother": "查询另一张票"
},
"tier": { "tier": {
"first": "头奖", "first": "头奖",
"second": "二奖", "second": "二奖",