feat: 优化多语言文案接入并升级大厅与钱包交互体验
- 补充大厅下注结果、快速填单、查中奖、分页与通用操作多语言文案 - 移除多处界面默认文案回退,统一改用 i18n 配置输出 - 优化大厅快速填单、开奖结果入口与注单筛选区域视觉交互 - 重构钱包转入转出弹窗与流水卡片样式,增强金额与状态信息展示
This commit is contained in:
@@ -82,15 +82,15 @@ function SubmittingPanel() {
|
||||
</div>
|
||||
<DialogHeader className="mt-8 items-center gap-2">
|
||||
<DialogTitle className="text-xl font-black text-slate-950">
|
||||
{t("hall.preview.processingTitle", { defaultValue: "正在提交下注" })}
|
||||
{t("hall.preview.processingTitle")}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="max-w-[220px] text-center text-sm leading-relaxed text-slate-500">
|
||||
{t("hall.preview.processingDescription", { defaultValue: "请勿关闭页面或返回上一页。" })}
|
||||
{t("hall.preview.processingDescription")}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<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]" />
|
||||
{t("hall.preview.processingProgress", { defaultValue: "正在处理注单..." })}
|
||||
{t("hall.preview.processingProgress")}
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
@@ -143,7 +143,7 @@ export function HallBetPreviewDialog({
|
||||
onClick={() => onOpenChange(false)}
|
||||
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"
|
||||
aria-label={t("actions.close", { defaultValue: "关闭" })}
|
||||
aria-label={t("actions.close")}
|
||||
>
|
||||
<XIcon className="size-5" />
|
||||
</button>
|
||||
@@ -193,10 +193,10 @@ export function HallBetPreviewDialog({
|
||||
<tr>
|
||||
<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">
|
||||
{t("hall.result.number", { defaultValue: "号码" })}
|
||||
{t("hall.result.number")}
|
||||
</th>
|
||||
<th className="border-r border-[#dfe8f6] px-2 py-3 text-center font-black">
|
||||
{t("orders.play", { defaultValue: "玩法" })}
|
||||
{t("orders.play")}
|
||||
</th>
|
||||
<th className="border-r border-[#dfe8f6] px-2 py-3 text-center font-black">
|
||||
{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">
|
||||
<CheckCircle2 className="size-5" />
|
||||
{t("hall.preview.noWarnings", { defaultValue: "当前预览未发现明显风险。" })}
|
||||
{t("hall.preview.noWarnings")}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -47,7 +47,7 @@ export function HallBetResultDialog({
|
||||
type="button"
|
||||
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"
|
||||
aria-label={t("actions.close", { defaultValue: "关闭" })}
|
||||
aria-label={t("actions.close")}
|
||||
>
|
||||
<XIcon className="size-5" />
|
||||
</button>
|
||||
@@ -56,11 +56,11 @@ export function HallBetResultDialog({
|
||||
</div>
|
||||
<DialogHeader className="mt-4 items-center gap-2">
|
||||
<DialogTitle className="text-2xl font-black text-slate-950">
|
||||
{t("hall.result.title", { defaultValue: "下注结果" })}
|
||||
{t("hall.result.title")}
|
||||
</DialogTitle>
|
||||
{data ? (
|
||||
<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>
|
||||
</DialogDescription>
|
||||
) : null}
|
||||
@@ -73,20 +73,20 @@ export function HallBetResultDialog({
|
||||
<div className="space-y-4 py-4">
|
||||
{!data ? (
|
||||
<p className="text-sm text-slate-500">
|
||||
{t("hall.result.empty", { defaultValue: "暂无结果。" })}
|
||||
{t("hall.result.empty")}
|
||||
</p>
|
||||
) : (
|
||||
<>
|
||||
<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">
|
||||
<p className="text-sm font-bold text-emerald-700">
|
||||
{t("hall.result.successCount", { defaultValue: "成功注项" })}
|
||||
{t("hall.result.successCount")}
|
||||
</p>
|
||||
<p className="mt-2 text-4xl font-black tabular-nums text-emerald-600">{totalSuccess}</p>
|
||||
</div>
|
||||
<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">
|
||||
{t("hall.result.failureCount", { defaultValue: "失败注项" })}
|
||||
{t("hall.result.failureCount")}
|
||||
</p>
|
||||
<p className="mt-2 text-4xl font-black tabular-nums text-[#e5002c]">{totalFailure}</p>
|
||||
</div>
|
||||
@@ -98,7 +98,7 @@ export function HallBetResultDialog({
|
||||
<ClipboardList className="size-5" />
|
||||
</span>
|
||||
<span className="truncate text-sm font-black text-[#0b3f96]">
|
||||
{t("hall.result.actual", { defaultValue: "实扣金额" })}
|
||||
{t("hall.result.actual")}
|
||||
</span>
|
||||
</div>
|
||||
<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">
|
||||
<p>
|
||||
{t("hall.result.orderNo", { defaultValue: "订单号" })}:{" "}
|
||||
{t("hall.result.orderNo")}:{" "}
|
||||
<span className="font-mono font-black text-[#0b3f96]">{data.order_no}</span>
|
||||
</p>
|
||||
<p>
|
||||
{t("hall.result.balanceAfter", { defaultValue: "剩余余额" })}:{" "}
|
||||
{t("hall.result.balanceAfter")}:{" "}
|
||||
<span className="font-semibold text-slate-950">
|
||||
{formatMinorAsCurrency(data.balance_after, currencyCode)}
|
||||
</span>
|
||||
@@ -121,9 +121,7 @@ export function HallBetResultDialog({
|
||||
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-black text-[#e5002c]">
|
||||
{t("hall.result.items", {
|
||||
defaultValue: "成功注项明细",
|
||||
})}
|
||||
{t("hall.result.items")}
|
||||
</p>
|
||||
<div className="overflow-x-auto rounded-xl border border-[#dfe8f6]">
|
||||
<table className="min-w-[430px] w-full border-collapse text-xs">
|
||||
@@ -131,13 +129,13 @@ export function HallBetResultDialog({
|
||||
<tr>
|
||||
<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">
|
||||
{t("hall.result.number", { defaultValue: "号码" })}
|
||||
{t("hall.result.number")}
|
||||
</th>
|
||||
<th className="border-r border-[#dfe8f6] px-2 py-2.5 text-center font-black">
|
||||
{t("orders.play", { defaultValue: "玩法" })}
|
||||
{t("orders.play")}
|
||||
</th>
|
||||
<th className="px-2 py-2.5 text-center font-black">
|
||||
{t("hall.result.actualDeduct", { defaultValue: "实扣" })}
|
||||
{t("hall.result.actualDeduct")}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -171,12 +169,12 @@ export function HallBetResultDialog({
|
||||
</div>
|
||||
{totalFailure === 0 ? (
|
||||
<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 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]">
|
||||
{t("hall.result.failedItems", { defaultValue: "失败注项明细" })}
|
||||
{t("hall.result.failedItems")}
|
||||
</p>
|
||||
{failedItems.map((item, index) => (
|
||||
<div
|
||||
@@ -188,7 +186,7 @@ export function HallBetResultDialog({
|
||||
{playLabel(item.play_code, t)}
|
||||
</span>
|
||||
<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>
|
||||
</div>
|
||||
))}
|
||||
@@ -208,7 +206,7 @@ export function HallBetResultDialog({
|
||||
render={<Link href="/orders" />}
|
||||
>
|
||||
<ClipboardList className="size-5" />
|
||||
{t("hall.result.viewBets", { defaultValue: "查看注单" })}
|
||||
{t("hall.result.viewBets")}
|
||||
</Button>
|
||||
<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]"
|
||||
>
|
||||
<Ticket className="size-5" />
|
||||
{t("hall.result.continueBetting", { defaultValue: "继续下注" })}
|
||||
{t("hall.result.continueBetting")}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
@@ -625,19 +625,13 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
removed
|
||||
? t("hall.playConfig.playClosedDraftCleared", {
|
||||
playCode: evt.play_code,
|
||||
defaultValue: "{{playCode}} 已关闭,相关草稿金额已清除。",
|
||||
})
|
||||
: t("hall.playConfig.playClosed", {
|
||||
playCode: evt.play_code,
|
||||
defaultValue: "{{playCode}} 已关闭。",
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
toast.message(
|
||||
t("hall.playConfig.updated", {
|
||||
defaultValue: "玩法配置已更新,已刷新下注表格。",
|
||||
}),
|
||||
);
|
||||
toast.message(t("hall.playConfig.updated"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -645,12 +639,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
void loadCatalog();
|
||||
setPreviewOpen(false);
|
||||
setPreviewData(null);
|
||||
toast.message(
|
||||
evt.message ??
|
||||
t("hall.playConfig.oddsUpdated", {
|
||||
defaultValue: "赔率已更新,请重新预览注单。",
|
||||
}),
|
||||
);
|
||||
toast.message(evt.message ?? t("hall.playConfig.oddsUpdated"));
|
||||
};
|
||||
|
||||
channel.listen(".play.toggle", onPlayToggle);
|
||||
@@ -840,7 +829,6 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
t("hall.placePartialFailed", {
|
||||
success: data.summary.success_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">
|
||||
{t("results.jackpotGap", {
|
||||
count: jackpot.draws_since_last_burst,
|
||||
defaultValue: "距上次爆池 {{count}} 期",
|
||||
})}
|
||||
</p>
|
||||
) : null}
|
||||
@@ -970,49 +957,74 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
</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="flex flex-wrap items-center justify-between gap-2">
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-slate-900">
|
||||
{t("hall.quickFill.title", { defaultValue: "快速填单" })}
|
||||
<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 items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-bold leading-5 text-slate-950">
|
||||
{t("hall.quickFill.title")}
|
||||
</p>
|
||||
<p className="text-xs text-slate-500">
|
||||
{t("hall.quickFill.description", {
|
||||
defaultValue: "收藏号码、最近号码可一键填入当前行。",
|
||||
})}
|
||||
<p className="mt-0.5 text-xs leading-5 text-slate-500">
|
||||
{t("hall.quickFill.description")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button type="button" variant="outline" size="sm" onClick={clearAllRows}>
|
||||
{t("hall.quickFill.clearAll", { defaultValue: "批量清空" })}
|
||||
</Button>
|
||||
<div className="flex shrink-0 items-center gap-1.5">
|
||||
{activeRow?.number ? (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
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)}
|
||||
>
|
||||
<Star className="mr-1 size-4" />
|
||||
{favorites.includes(activeRow.number)
|
||||
? t("hall.quickFill.unfavorite", { defaultValue: "取消收藏" })
|
||||
: t("hall.quickFill.favorite", { defaultValue: "收藏号码" })}
|
||||
<Star
|
||||
className={cn(
|
||||
"size-4",
|
||||
favorites.includes(activeRow.number) && "fill-current",
|
||||
)}
|
||||
aria-hidden
|
||||
/>
|
||||
</Button>
|
||||
) : 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 className="space-y-2">
|
||||
<p className="text-[11px] font-medium uppercase tracking-wide text-slate-400">
|
||||
{t("hall.quickFill.favorites", { defaultValue: "收藏" })}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{favoriteChips.length > 0 ? (
|
||||
favoriteChips.map((number) => (
|
||||
<div className="mt-3 space-y-2">
|
||||
{favoriteChips.length > 0 ? (
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
<span className="mr-0.5 text-[11px] font-semibold text-[#d81435]">
|
||||
{t("hall.quickFill.favorites")}
|
||||
</span>
|
||||
{favoriteChips.map((number) => (
|
||||
<button
|
||||
key={`fav-${number}`}
|
||||
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={() => {
|
||||
const current = holdFavoriteRef.current;
|
||||
current.number = number;
|
||||
@@ -1044,38 +1056,32 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
}}
|
||||
>
|
||||
{number}
|
||||
<span className="text-[10px] opacity-70">
|
||||
{t("hall.quickFill.tapHold", { defaultValue: "长按取消" })}
|
||||
<span className="text-[10px] font-medium opacity-70">
|
||||
{t("hall.quickFill.tapHold")}
|
||||
</span>
|
||||
</button>
|
||||
))
|
||||
) : (
|
||||
<span className="text-xs text-slate-400">
|
||||
{t("hall.quickFill.emptyFavorites", { defaultValue: "暂无收藏" })}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="space-y-2">
|
||||
<p className="text-[11px] font-medium uppercase tracking-wide text-slate-400">
|
||||
{t("hall.quickFill.history", { defaultValue: "最近 20 个历史号码" })}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
<span className="mr-0.5 text-[11px] font-semibold text-slate-400">
|
||||
{t("hall.quickFill.history")}
|
||||
</span>
|
||||
{historyChips.length > 0 ? (
|
||||
historyChips.map((number) => (
|
||||
<button
|
||||
key={`his-${number}`}
|
||||
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)}
|
||||
>
|
||||
{number}
|
||||
</button>
|
||||
))
|
||||
) : (
|
||||
<span className="text-xs text-slate-400">
|
||||
{t("hall.quickFill.emptyHistory", { defaultValue: "暂无历史号码" })}
|
||||
<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")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -1122,7 +1128,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
: ""}
|
||||
</span>
|
||||
<span className="block text-[9px] font-medium text-[#9aa8bd]">
|
||||
{t("hall.table.amountPlaceholder", { defaultValue: "金额" })}
|
||||
{t("hall.table.amountPlaceholder")}
|
||||
</span>
|
||||
</th>
|
||||
))}
|
||||
@@ -1177,7 +1183,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
inputMode="decimal"
|
||||
placeholder={
|
||||
status === "sold_out"
|
||||
? t("hall.table.soldOut", { defaultValue: "售罄" })
|
||||
? t("hall.table.soldOut")
|
||||
: "-"
|
||||
}
|
||||
onFocus={() => setActiveRowId(row.id)}
|
||||
@@ -1192,11 +1198,11 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
/>
|
||||
{status === "sold_out" ? (
|
||||
<p className="mt-0.5 text-center text-[9px] font-bold text-slate-500">
|
||||
{t("hall.table.soldOut", { defaultValue: "售罄" })}
|
||||
{t("hall.table.soldOut")}
|
||||
</p>
|
||||
) : status === "warning" ? (
|
||||
<p className="mt-0.5 text-center text-[9px] font-bold text-amber-700">
|
||||
{t("hall.table.warning", { defaultValue: "接近售罄" })}
|
||||
{t("hall.table.warning")}
|
||||
</p>
|
||||
) : null}
|
||||
</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"
|
||||
>
|
||||
<CirclePlus className="size-4" aria-hidden />
|
||||
{t("hall.table.addRow", { defaultValue: "添加一行" })}
|
||||
{t("hall.table.addRow")}
|
||||
</button>
|
||||
</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)]">
|
||||
<span className="text-xs font-medium text-slate-500">
|
||||
{t("hall.table.draftTotal", { defaultValue: "投注金额" })}
|
||||
{t("hall.table.draftTotal")}
|
||||
</span>
|
||||
<span className="text-base font-black tabular-nums text-[#0b3f96]">
|
||||
{formatMinorAsCurrency(debouncedSummary.actual, currencyCode)}
|
||||
@@ -1254,12 +1260,12 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
>
|
||||
<Ticket className="size-5" aria-hidden />
|
||||
{previewLoading
|
||||
? t("hall.table.previewing", { defaultValue: "预览中..." })
|
||||
? t("hall.table.previewing")
|
||||
: !isBettable
|
||||
? t("hall.closed.title")
|
||||
: availableMinor < debouncedSummary.actual
|
||||
? t("hall.table.insufficientBalance", { defaultValue: "余额不足" })
|
||||
: t("hall.table.submitBet", { defaultValue: "提交下注" })}
|
||||
? t("hall.table.insufficientBalance")
|
||||
: t("hall.table.submitBet")}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -47,7 +47,7 @@ export function HallScreen() {
|
||||
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]"
|
||||
>
|
||||
{tp("nav.rules", { defaultValue: "规则" })}
|
||||
{tp("nav.rules")}
|
||||
</Link>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -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"
|
||||
role="dialog"
|
||||
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="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"
|
||||
className="absolute right-2 top-2 text-white hover:bg-white/10 hover:text-white"
|
||||
onClick={onClose}
|
||||
aria-label={t("hall.jackpotBurst.close", { defaultValue: "关闭" })}
|
||||
aria-label={t("hall.jackpotBurst.close")}
|
||||
>
|
||||
<X className="size-4" aria-hidden />
|
||||
</Button>
|
||||
@@ -66,11 +66,10 @@ export function JackpotBurstOverlay({ event, onClose }: JackpotBurstOverlayProps
|
||||
</div>
|
||||
|
||||
<p className="text-xs font-bold uppercase tracking-[0.18em] text-[#f5c542]">
|
||||
{t("hall.jackpotBurst.title", { defaultValue: "Jackpot 爆池" })}
|
||||
{t("hall.jackpotBurst.title")}
|
||||
</p>
|
||||
<p className="mt-2 text-sm font-semibold text-slate-200">
|
||||
{t("hall.jackpotBurst.subtitle", {
|
||||
defaultValue: "期号 {{drawNo}} 触发奖池派发",
|
||||
drawNo: event.draw_no,
|
||||
})}
|
||||
</p>
|
||||
@@ -80,26 +79,26 @@ export function JackpotBurstOverlay({ event, onClose }: JackpotBurstOverlayProps
|
||||
{event.first_prize_number || "----"}
|
||||
</div>
|
||||
<p className="mt-2 text-xs font-semibold text-slate-300">
|
||||
{t("hall.jackpotBurst.number", { defaultValue: "头奖号码" })}
|
||||
{t("hall.jackpotBurst.number")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<span className="text-slate-300">
|
||||
{t("hall.jackpotBurst.amount", { defaultValue: "爆池金额" })}
|
||||
{t("hall.jackpotBurst.amount")}
|
||||
</span>
|
||||
<span className="text-right font-black text-[#f5c542]">{amount}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-4 rounded-md bg-white/[0.05] px-3 py-2">
|
||||
<span className="text-slate-300">
|
||||
{t("hall.jackpotBurst.winners", { defaultValue: "中奖人数" })}
|
||||
{t("hall.jackpotBurst.winners")}
|
||||
</span>
|
||||
<span className="font-bold">{event.winner_count}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-4 rounded-md bg-white/[0.05] px-3 py-2">
|
||||
<span className="text-slate-300">
|
||||
{t("hall.jackpotBurst.triggerLabel", { defaultValue: "触发方式" })}
|
||||
{t("hall.jackpotBurst.triggerLabel")}
|
||||
</span>
|
||||
<span className="font-bold">{triggerLabel(event.trigger_type, t)}</span>
|
||||
</div>
|
||||
|
||||
@@ -14,11 +14,8 @@ function notifyBrowser(event: JackpotBurstEvent, t: TFunction<"player">): void {
|
||||
|
||||
const currency = event.currency_code.toUpperCase();
|
||||
const amount = formatMinorAsCurrency(event.total_payout_amount, currency);
|
||||
const title = t("hall.jackpotBurst.notificationTitle", {
|
||||
defaultValue: "Jackpot 爆池",
|
||||
});
|
||||
const title = t("hall.jackpotBurst.notificationTitle");
|
||||
const body = t("hall.jackpotBurst.notificationBody", {
|
||||
defaultValue: "期号 {{drawNo}} 派发 {{amount}}",
|
||||
drawNo: event.draw_no,
|
||||
amount,
|
||||
});
|
||||
|
||||
@@ -8,17 +8,17 @@ export function ticketStatusDisplay(
|
||||
): { label: string; dotClass: string; ring?: boolean } {
|
||||
const total = winMinor + jackpotMinor;
|
||||
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") {
|
||||
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) {
|
||||
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") {
|
||||
return {
|
||||
label: t?.("ticketStatus.settled_lose") ?? "未中奖",
|
||||
label: t?.("ticketStatus.settled_lose") ?? status,
|
||||
dotClass: "bg-background",
|
||||
ring: true,
|
||||
};
|
||||
|
||||
@@ -264,7 +264,7 @@ export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) {
|
||||
) : null}
|
||||
{!hasSettlement ? (
|
||||
<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>
|
||||
) : null}
|
||||
</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">
|
||||
<p className="font-bold text-[#0b3f96]">{t("orders.drawNumbers")}</p>
|
||||
<p className="mt-1 text-[#32518d]">
|
||||
{t("orders.drawPendingMatch", { defaultValue: "本期开奖号码尚未发布,暂不能判断是否中奖。" })}
|
||||
{t("orders.drawPendingMatch")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -310,7 +310,7 @@ export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) {
|
||||
{matchResult && hasSettlement ? (
|
||||
<div className="rounded-lg border border-[#dce7f7] bg-[#f8fbff] px-3 py-3 text-xs">
|
||||
<p className="font-bold text-[#0b3f96]">
|
||||
{t("orders.matchResult", { defaultValue: "匹配结果" })}
|
||||
{t("orders.matchResult")}
|
||||
</p>
|
||||
<p className="mt-1 text-slate-600">
|
||||
{matchResult.matched
|
||||
@@ -332,7 +332,7 @@ export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) {
|
||||
{timeline.length > 0 ? (
|
||||
<div className="rounded-xl border border-[#e8eef7] bg-[#f8fbff] px-3 py-3">
|
||||
<p className="text-sm font-bold text-[#0b3f96]">
|
||||
{t("orders.timeline", { defaultValue: "时间线" })}
|
||||
{t("orders.timeline")}
|
||||
</p>
|
||||
<div className="mt-2 space-y-2">
|
||||
{timeline.map((row) => (
|
||||
|
||||
@@ -83,7 +83,7 @@ export function TicketOrdersListScreen() {
|
||||
}, []);
|
||||
|
||||
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)}`;
|
||||
return `${fromDate ? formatCompactDate(fromDate) : "..." } ~ ${toDate ? formatCompactDate(toDate) : "..."}`;
|
||||
}, [formatCompactDate, fromDate, t, toDate]);
|
||||
@@ -156,20 +156,20 @@ export function TicketOrdersListScreen() {
|
||||
containerClassName="max-w-[720px]"
|
||||
>
|
||||
<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="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
|
||||
<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 items-center justify-between gap-3">
|
||||
<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")}
|
||||
</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}
|
||||
</p>
|
||||
</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
|
||||
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")}
|
||||
</Link>
|
||||
@@ -177,7 +177,7 @@ export function TicketOrdersListScreen() {
|
||||
<Button
|
||||
type="button"
|
||||
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={() => {
|
||||
setQueryDrawNo(drawNoFilter);
|
||||
setQueryNumber("");
|
||||
@@ -194,25 +194,25 @@ export function TicketOrdersListScreen() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 grid grid-cols-1 gap-2 min-[420px]:grid-cols-2 lg:grid-cols-4">
|
||||
<div className="flex h-10 items-center rounded-full border border-[#dce7f7] bg-[#fbfdff] px-3">
|
||||
<div className="mt-3 grid grid-cols-2 gap-2 lg:grid-cols-4">
|
||||
<div className="flex h-9 min-w-0 items-center rounded-full border border-[#dce7f7] bg-[#fbfdff] px-3">
|
||||
<Input
|
||||
value={queryDrawNo}
|
||||
onChange={(e) => setQueryDrawNo(e.target.value)}
|
||||
placeholder={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 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" />
|
||||
<Input
|
||||
value={queryNumber}
|
||||
onChange={(e) => setQueryNumber(e.target.value)}
|
||||
placeholder={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>
|
||||
|
||||
@@ -222,7 +222,7 @@ export function TicketOrdersListScreen() {
|
||||
<Button
|
||||
type="button"
|
||||
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]" />
|
||||
<span className="truncate">{dateLabel}</span>
|
||||
@@ -256,10 +256,10 @@ export function TicketOrdersListScreen() {
|
||||
setToDate("");
|
||||
}}
|
||||
>
|
||||
清除
|
||||
{t("actions.clear")}
|
||||
</Button>
|
||||
<Button type="button" variant="secondary" size="xs" onClick={() => setRangeOpen(false)}>
|
||||
完成
|
||||
{t("actions.done")}
|
||||
</Button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
@@ -271,10 +271,10 @@ export function TicketOrdersListScreen() {
|
||||
<Button
|
||||
type="button"
|
||||
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="shrink-0">状态</span>
|
||||
<span className="shrink-0">{t("orders.status")}</span>
|
||||
{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">
|
||||
{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)]">
|
||||
<div className="space-y-1">
|
||||
<p className="px-1 pb-1 text-[11px] font-bold text-[#32518d]">
|
||||
{t("orders.statusFilter", { defaultValue: "状态筛选" })}
|
||||
{t("orders.statusFilter")}
|
||||
</p>
|
||||
{STATUS_OPTIONS.map((status) => {
|
||||
const checked = queryStatuses.includes(status);
|
||||
@@ -408,7 +408,7 @@ export function TicketOrdersListScreen() {
|
||||
disabled={loadingMore}
|
||||
onClick={() => void fetchPage(page + 1, true)}
|
||||
>
|
||||
{loadingMore ? t("actions.loading", { defaultValue: "加载中..." }) : t("actions.loadMore", { defaultValue: "加载更多" })}
|
||||
{loadingMore ? t("actions.loading") : t("actions.loadMore")}
|
||||
</Button>
|
||||
) : !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">
|
||||
@@ -420,7 +420,7 @@ export function TicketOrdersListScreen() {
|
||||
disabled={loading || page <= 1}
|
||||
onClick={() => void fetchPage(Math.max(1, page - 1), false)}
|
||||
>
|
||||
{t("actions.previous", { defaultValue: "上一页" })}
|
||||
{t("actions.previous")}
|
||||
</Button>
|
||||
{visiblePages.map((p) => (
|
||||
<Button
|
||||
@@ -448,12 +448,12 @@ export function TicketOrdersListScreen() {
|
||||
disabled={loading || page >= lastPage}
|
||||
onClick={() => void fetchPage(Math.min(lastPage, page + 1), false)}
|
||||
>
|
||||
{t("actions.next", { defaultValue: "下一页" })}
|
||||
{t("actions.next")}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<p className="py-2 text-center text-xs text-slate-400">
|
||||
{t("orders.noMore", { defaultValue: "没有更多注单" })}
|
||||
{t("orders.noMore")}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -78,14 +78,14 @@ export function CheckWinningScreen() {
|
||||
setResult(next);
|
||||
setRecent((current) => [normalizedTicketNo, ...current.filter((x) => x !== normalizedTicketNo)].slice(0, 5));
|
||||
} catch {
|
||||
setError(t("results.check.loadFailed", { defaultValue: "查询失败,请稍后重试。" }));
|
||||
setError(t("results.check.loadFailed"));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [latestDraw, normalizedTicketNo, t]);
|
||||
|
||||
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">
|
||||
<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">
|
||||
@@ -93,28 +93,28 @@ export function CheckWinningScreen() {
|
||||
<BriefcaseBusiness className="size-12" />
|
||||
</div>
|
||||
<h2 className="mt-5 text-lg font-black text-slate-950">
|
||||
{t("results.check.enterTicket", { defaultValue: "输入你的票号或号码" })}
|
||||
{t("results.check.enterTicket")}
|
||||
</h2>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3 px-4 pb-4">
|
||||
<label className="block space-y-1.5">
|
||||
<span className="text-xs font-black text-slate-700">
|
||||
{t("results.check.ticketNumber", { defaultValue: "票号 / 号码" })}
|
||||
{t("results.check.ticketNumber")}
|
||||
</span>
|
||||
<Input
|
||||
value={ticketNo}
|
||||
placeholder={t("results.check.placeholder", { defaultValue: "请输入票号或号码" })}
|
||||
placeholder={t("results.check.placeholder")}
|
||||
onChange={(e) => setTicketNo(e.target.value)}
|
||||
className="h-12 rounded-xl border-[#dce7f7] bg-white font-mono text-base font-bold"
|
||||
/>
|
||||
</label>
|
||||
{latestDraw ? (
|
||||
<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>
|
||||
) : null}
|
||||
{error ? <p className="text-sm font-semibold text-[#e5002c]">{error}</p> : null}
|
||||
@@ -124,7 +124,7 @@ export function CheckWinningScreen() {
|
||||
onClick={() => void runCheck()}
|
||||
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>
|
||||
</div>
|
||||
</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)]">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-black text-slate-950">
|
||||
{t("results.check.recent", { defaultValue: "最近查询" })}
|
||||
{t("results.check.recent")}
|
||||
</h3>
|
||||
{recent.length > 0 ? (
|
||||
<button type="button" className="text-sm font-bold text-[#0b56b7]" onClick={() => setRecent([])}>
|
||||
{t("actions.clear", { defaultValue: "清空" })}
|
||||
{t("actions.clear")}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mt-3 divide-y divide-[#edf2f9]">
|
||||
{recent.length === 0 ? (
|
||||
<p className="py-4 text-sm text-slate-500">
|
||||
{t("results.check.noRecent", { defaultValue: "暂无查询记录。" })}
|
||||
{t("results.check.noRecent")}
|
||||
</p>
|
||||
) : (
|
||||
recent.map((row) => (
|
||||
@@ -213,7 +213,7 @@ function WinningResultDialog({
|
||||
type="button"
|
||||
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"
|
||||
aria-label={t("actions.close", { defaultValue: "关闭" })}
|
||||
aria-label={t("actions.close")}
|
||||
>
|
||||
<XIcon className="size-5" />
|
||||
</button>
|
||||
@@ -224,11 +224,11 @@ function WinningResultDialog({
|
||||
</div>
|
||||
<DialogTitle className="mt-3 text-xl font-black text-slate-950">
|
||||
{isWon
|
||||
? t("results.check.winTitle", { defaultValue: "恭喜,你中奖了" })
|
||||
: t("results.check.noWinTitle", { defaultValue: "未查询到中奖" })}
|
||||
? t("results.check.winTitle")
|
||||
: t("results.check.noWinTitle")}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-sm text-slate-500">
|
||||
{t("results.check.ticketNumber", { defaultValue: "票号 / 号码" })}
|
||||
{t("results.check.ticketNumber")}
|
||||
</DialogDescription>
|
||||
</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="border-r border-emerald-100 px-3 py-4">
|
||||
<p className="text-xs font-medium text-slate-500">
|
||||
{t("results.check.match", { defaultValue: "匹配" })}
|
||||
{t("results.check.match")}
|
||||
</p>
|
||||
<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>
|
||||
</div>
|
||||
<div className="px-3 py-4">
|
||||
<p className="text-xs font-medium text-slate-500">
|
||||
{t("results.check.amount", { defaultValue: "中奖金额" })}
|
||||
{t("results.check.amount")}
|
||||
</p>
|
||||
<p className="mt-2 font-mono text-lg font-black text-[#0a8f3e]">
|
||||
{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">
|
||||
<p className="font-black text-slate-950">
|
||||
{t("results.check.drawInfo", { defaultValue: "开奖信息" })}
|
||||
{t("results.check.drawInfo")}
|
||||
</p>
|
||||
<div className="mt-3 grid grid-cols-2 gap-4 text-slate-500">
|
||||
<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>
|
||||
</div>
|
||||
<div>
|
||||
@@ -283,7 +283,7 @@ function WinningResultDialog({
|
||||
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)}`} />}
|
||||
>
|
||||
{t("results.check.viewBetDetails", { defaultValue: "查看注单详情" })}
|
||||
{t("results.check.viewBetDetails")}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
@@ -292,7 +292,7 @@ function WinningResultDialog({
|
||||
onClick={onCheckAnother}
|
||||
>
|
||||
<RefreshCw className="size-5" />
|
||||
{t("results.check.checkAnother", { defaultValue: "查询另一张票" })}
|
||||
{t("results.check.checkAnother")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -27,7 +27,7 @@ const RESULTS_PAGE_SIZE = 10;
|
||||
|
||||
const MONTH_OPTIONS = Array.from({ length: 12 }, (_, value) => ({
|
||||
value,
|
||||
label: `${value + 1}月`,
|
||||
labelKey: `calendar.months.${value + 1}`,
|
||||
}));
|
||||
|
||||
export function DrawResultsListScreen() {
|
||||
@@ -46,7 +46,6 @@ export function DrawResultsListScreen() {
|
||||
const businessDate = /^\d{4}-\d{2}-\d{2}$/.test(date) ? date : undefined;
|
||||
const quickYears = useMemo(() => buildYearOptions(calendarMonth), [calendarMonth]);
|
||||
const featured = items?.[0] ?? null;
|
||||
const olderDraw = items?.[1] ?? null;
|
||||
|
||||
const fetchList = useCallback(async (targetPage = 1, append = false) => {
|
||||
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]"
|
||||
>
|
||||
<CalendarIcon className="mr-2 size-4 text-[#7890b8]" />
|
||||
{date || t("results.selectBusinessDate", { defaultValue: "选择日期" })}
|
||||
{date ||
|
||||
t("results.selectBusinessDate", {
|
||||
defaultValue: "选择日期",
|
||||
})}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
@@ -139,7 +141,7 @@ export function DrawResultsListScreen() {
|
||||
<SelectContent className="max-h-64 min-w-24">
|
||||
{MONTH_OPTIONS.map((month) => (
|
||||
<SelectItem key={month.value} value={String(month.value)}>
|
||||
{month.label}
|
||||
{t(month.labelKey)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
@@ -183,7 +185,7 @@ export function DrawResultsListScreen() {
|
||||
size="icon"
|
||||
variant="outline"
|
||||
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("")}
|
||||
>
|
||||
<XIcon className="size-4" />
|
||||
@@ -226,44 +228,28 @@ export function DrawResultsListScreen() {
|
||||
<div className="space-y-3">
|
||||
{featured ? (
|
||||
<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">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="min-w-[5rem] rounded-full border-[#e6edf8] bg-white text-slate-400"
|
||||
disabled
|
||||
>
|
||||
{t("results.next")}
|
||||
</Button>
|
||||
<div className="min-w-0 flex-1 text-center">
|
||||
<p className="font-mono text-lg font-black text-[#0b3f96]">
|
||||
<div className="flex flex-wrap items-start justify-between gap-3 border-b border-[#edf2f9] pb-3">
|
||||
<div className="min-w-0">
|
||||
<p className="text-[11px] font-black uppercase tracking-normal text-[#0b56b7]">
|
||||
{t("results.detailTitle")}
|
||||
</p>
|
||||
<p className="mt-1 font-mono text-lg font-black text-[#0b3f96]">
|
||||
{featured.draw_no}
|
||||
</p>
|
||||
<p className="mt-1 font-mono text-xs text-slate-500">
|
||||
{t("results.drawTime", {
|
||||
time: formatLotteryInstant(featured.draw_time_iso ?? featured.draw_time ?? null),
|
||||
time: formatLotteryInstant(
|
||||
featured.draw_time_iso ?? featured.draw_time ?? null,
|
||||
),
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
{olderDraw ? (
|
||||
<Link
|
||||
href={`/results/${encodeURIComponent(olderDraw.draw_no)}`}
|
||||
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.previous")}
|
||||
</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>
|
||||
)}
|
||||
<Link
|
||||
href={`/results/${encodeURIComponent(featured.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]"
|
||||
>
|
||||
{t("results.openDetail", { defaultValue: "查看详情" })}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="pt-4">
|
||||
<TwentyThreeResultsGrid numbers={featured.results} />
|
||||
@@ -323,12 +309,12 @@ export function DrawResultsListScreen() {
|
||||
onClick={() => void fetchList(page + 1, true)}
|
||||
>
|
||||
{loadingMore
|
||||
? t("actions.loading", { defaultValue: "加载中..." })
|
||||
: t("actions.loadMore", { defaultValue: "加载更多" })}
|
||||
? t("actions.loading")
|
||||
: t("actions.loadMore")}
|
||||
</Button>
|
||||
) : items && items.length > 0 ? (
|
||||
<p className="py-2 text-center text-xs text-slate-400">
|
||||
{t("results.noMore", { defaultValue: "没有更多开奖结果" })}
|
||||
{t("results.noMore")}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -56,7 +56,7 @@ export function JackpotResultsStrip({
|
||||
</p>
|
||||
{gap !== null ? (
|
||||
<p className="mt-1 text-xs font-semibold text-amber-800">
|
||||
{t("results.jackpotGap", { count: gap, defaultValue: "距上次爆池 {{count}} 期" })}
|
||||
{t("results.jackpotGap", { count: gap })}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -14,69 +14,63 @@ import {
|
||||
import { PlayerPanel } from "@/components/layout/player-panel";
|
||||
|
||||
const prizeRows = [
|
||||
{ label: "rules.prizes.first", defaultValue: "头奖", count: "1" },
|
||||
{ label: "rules.prizes.second", defaultValue: "二奖", count: "1" },
|
||||
{ label: "rules.prizes.third", defaultValue: "三奖", count: "1" },
|
||||
{ label: "rules.prizes.starter", defaultValue: "特别奖", count: "10" },
|
||||
{ label: "rules.prizes.consolation", defaultValue: "安慰奖", count: "10" },
|
||||
{ label: "rules.prizes.first", count: "1" },
|
||||
{ label: "rules.prizes.second", count: "1" },
|
||||
{ label: "rules.prizes.third", count: "1" },
|
||||
{ label: "rules.prizes.starter", count: "10" },
|
||||
{ label: "rules.prizes.consolation", count: "10" },
|
||||
] as const;
|
||||
|
||||
const playSections = [
|
||||
{
|
||||
title: "rules.sections.dimensions.title",
|
||||
titleDefault: "什么是 2D、3D、4D",
|
||||
items: [
|
||||
["rules.sections.dimensions.d4", "4D:按完整 4 位号码判断,例如 1234。"],
|
||||
["rules.sections.dimensions.d3", "3D:按下注后三位与开奖号码后三位匹配,例如 234 可命中 1234。"],
|
||||
["rules.sections.dimensions.d2", "2D:按下注后二位与开奖号码后二位匹配,例如 34 可命中 1234。"],
|
||||
"rules.sections.dimensions.d4",
|
||||
"rules.sections.dimensions.d3",
|
||||
"rules.sections.dimensions.d2",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "rules.sections.bigSmall.title",
|
||||
titleDefault: "Big vs Small",
|
||||
items: [
|
||||
["rules.sections.bigSmall.big", "Big:覆盖全部 23 个奖项,命中头奖、二奖、三奖、特别奖或安慰奖任一组即中奖。"],
|
||||
["rules.sections.bigSmall.small", "Small:只覆盖头奖、二奖、三奖;命中特别奖或安慰奖不中奖。"],
|
||||
"rules.sections.bigSmall.big",
|
||||
"rules.sections.bigSmall.small",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "rules.sections.positions.title",
|
||||
titleDefault: "位置类玩法",
|
||||
items: [
|
||||
["rules.sections.positions.d4", "4A / 4B / 4C 分别对应头奖、二奖、三奖;4D 命中特别奖任一组;4E 命中安慰奖任一组。"],
|
||||
["rules.sections.positions.d3", "3A / 3B / 3C 分别匹配头奖、二奖、三奖后三位;3ABC 命中头/二/三任一后三位。"],
|
||||
["rules.sections.positions.d2", "2A / 2B / 2C 分别匹配头奖、二奖、三奖后二位;2ABC 命中头/二/三任一后二位。"],
|
||||
"rules.sections.positions.d4",
|
||||
"rules.sections.positions.d3",
|
||||
"rules.sections.positions.d2",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "rules.sections.box.title",
|
||||
titleDefault: "包号类玩法",
|
||||
items: [
|
||||
["rules.sections.box.straight", "Straight:顺序完全一致才中奖。"],
|
||||
["rules.sections.box.box", "Box:系统展开不重复排列组合,中奖按命中的展开号码结算。"],
|
||||
["rules.sections.box.ibox", "iBox:每个排列都按单注金额下注,总扣款 = 单注金额 × 组合数 × (1 - 回水率)。"],
|
||||
["rules.sections.box.mbox", "mBox:总输入金额平摊到所有组合,不能整除的尾差不扣除,相当于退回玩家钱包。"],
|
||||
["rules.sections.box.roll", "Roll:用 R 表示滚动位,每个 R 覆盖 0-9;组合数 = 10 的滚动位数次方。"],
|
||||
["rules.sections.box.half", "Half Box:一期仅预留数据结构,前台不开放下注。"],
|
||||
"rules.sections.box.straight",
|
||||
"rules.sections.box.box",
|
||||
"rules.sections.box.ibox",
|
||||
"rules.sections.box.mbox",
|
||||
"rules.sections.box.roll",
|
||||
"rules.sections.box.half",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "rules.sections.attributes.title",
|
||||
titleDefault: "Head / Tail、单双、数字大小",
|
||||
items: [
|
||||
["rules.sections.attributes.headTail", "Head / Tail:只看头奖首位数字;5-9 为 Head,0-4 为 Tail。"],
|
||||
["rules.sections.attributes.oddEven", "单双:按号码末位判断,1/3/5/7/9 为单,0/2/4/6/8 为双,可叠加 2D / 3D / 4D 维度。"],
|
||||
["rules.sections.attributes.digitSize", "Big Digit / Small Digit:按指定位数字判断,5-9 为大,0-4 为小;多位分别下注时独立结算。"],
|
||||
"rules.sections.attributes.headTail",
|
||||
"rules.sections.attributes.oddEven",
|
||||
"rules.sections.attributes.digitSize",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "rules.sections.wallet.title",
|
||||
titleDefault: "Jackpot、回水、封盘与售罄",
|
||||
items: [
|
||||
["rules.sections.wallet.rebate", "回水 / 佣金:下注时锁定到注单赔率快照,结算不会受之后配置变化影响。"],
|
||||
["rules.sections.wallet.jackpot", "Jackpot:命中规则与后台奖池配置相关;爆池、蓄水和派彩以系统记录为准。"],
|
||||
["rules.sections.wallet.close", "封盘:到达封盘时间后大厅不可继续编辑或提交注单。"],
|
||||
["rules.sections.wallet.soldOut", "售罄:号码赔付池额度不足时,预览会提示风险,提交时额度不足会整单拒绝。"],
|
||||
"rules.sections.wallet.rebate",
|
||||
"rules.sections.wallet.jackpot",
|
||||
"rules.sections.wallet.close",
|
||||
"rules.sections.wallet.soldOut",
|
||||
],
|
||||
},
|
||||
] as const;
|
||||
@@ -86,10 +80,8 @@ export function PlayRulesScreen() {
|
||||
|
||||
return (
|
||||
<PlayerPanel
|
||||
title={t("rules.title", { defaultValue: "玩法规则" })}
|
||||
subtitle={t("rules.subtitle", {
|
||||
defaultValue: "2D / 3D / 4D、包号、回水、封盘与售罄说明",
|
||||
})}
|
||||
title={t("rules.title")}
|
||||
subtitle={t("rules.subtitle")}
|
||||
backHref="/hall"
|
||||
className="bg-[#f7f9fd]"
|
||||
>
|
||||
@@ -102,12 +94,10 @@ export function PlayRulesScreen() {
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
<CardTitle className="text-base text-[#0b3f96]">
|
||||
{t("rules.quick.title", { defaultValue: "开奖结构" })}
|
||||
{t("rules.quick.title")}
|
||||
</CardTitle>
|
||||
<p className="mt-0.5 text-xs text-slate-500">
|
||||
{t("rules.quick.description", {
|
||||
defaultValue: "每期开奖 23 组 4 位号码,结算以下注时锁定的赔率快照为准。",
|
||||
})}
|
||||
{t("rules.quick.description")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -115,19 +105,19 @@ export function PlayRulesScreen() {
|
||||
<div className="rounded-2xl bg-[#eef5ff] p-3">
|
||||
<p className="text-lg font-black text-[#0b3f96]">23</p>
|
||||
<p className="text-[11px] font-semibold text-slate-500">
|
||||
{t("rules.quick.totalPrizes", { defaultValue: "奖项组数" })}
|
||||
{t("rules.quick.totalPrizes")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-2xl bg-[#fff2f4] p-3">
|
||||
<p className="text-lg font-black text-[#f10b32]">15</p>
|
||||
<p className="text-[11px] font-semibold text-slate-500">
|
||||
{t("rules.quick.cooldown", { defaultValue: "冷静期分钟" })}
|
||||
{t("rules.quick.cooldown")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-2xl bg-[#eefbf4] p-3">
|
||||
<p className="text-lg font-black text-[#168a4a]">1</p>
|
||||
<p className="text-[11px] font-semibold text-slate-500">
|
||||
{t("rules.quick.snapshot", { defaultValue: "赔率快照" })}
|
||||
{t("rules.quick.snapshot")}
|
||||
</p>
|
||||
</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"
|
||||
>
|
||||
<span className="font-semibold text-slate-700">
|
||||
{t(row.label, { defaultValue: row.defaultValue })}
|
||||
{t(row.label)}
|
||||
</span>
|
||||
<Badge variant="secondary">{row.count}</Badge>
|
||||
</div>
|
||||
@@ -154,17 +144,17 @@ export function PlayRulesScreen() {
|
||||
<Card key={section.title} className="border-[#e1e8f2] bg-white shadow-sm">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-black text-slate-900">
|
||||
{t(section.title, { defaultValue: section.titleDefault })}
|
||||
{t(section.title)}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ul className="space-y-2">
|
||||
{section.items.map(([item, defaultValue]) => (
|
||||
{section.items.map((item) => (
|
||||
<li
|
||||
key={item}
|
||||
className="rounded-xl bg-[#f8fafc] px-3 py-2 text-sm leading-6 text-slate-700"
|
||||
>
|
||||
{t(item, { defaultValue })}
|
||||
{t(item)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
@@ -178,24 +168,20 @@ export function PlayRulesScreen() {
|
||||
<div className="flex gap-3">
|
||||
<ShieldCheck className="mt-0.5 size-5 shrink-0" aria-hidden />
|
||||
<p className="text-sm leading-6 text-white/90">
|
||||
{t("rules.footer.config", {
|
||||
defaultValue: "下注大厅的玩法列、启用状态、限额和赔率来自后台生效玩法配置。",
|
||||
})}
|
||||
{t("rules.footer.config")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<TimerReset className="mt-0.5 size-5 shrink-0" aria-hidden />
|
||||
<p className="text-sm leading-6 text-white/90">
|
||||
{t("rules.footer.phaseTwo", {
|
||||
defaultValue: "5D / 6D 属于二期扩展,一期前后端均不提供可用入口。",
|
||||
})}
|
||||
{t("rules.footer.phaseTwo")}
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
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]"
|
||||
>
|
||||
{t("rules.footer.backBet", { defaultValue: "返回下注大厅" })}
|
||||
{t("rules.footer.backBet")}
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -152,12 +152,12 @@ export function WalletLogsBlock({
|
||||
onClick={onLoadMore}
|
||||
>
|
||||
{loadingMore
|
||||
? t("actions.loading", { defaultValue: "加载中..." })
|
||||
: t("actions.loadMore", { defaultValue: "加载更多" })}
|
||||
? t("actions.loading")
|
||||
: t("actions.loadMore")}
|
||||
</Button>
|
||||
) : (
|
||||
<p className="py-2 text-center text-xs text-slate-400">
|
||||
{t("wallet.noMoreLogs", { defaultValue: "没有更多流水" })}
|
||||
{t("wallet.noMoreLogs")}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
@@ -179,29 +179,50 @@ export function LogRow({
|
||||
const { t } = useTranslation("player");
|
||||
const ccy = item.currency_code || currency;
|
||||
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 (
|
||||
<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)]">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div>
|
||||
<span className="font-medium">
|
||||
{logTypeLabel(item.type, t)}{" "}
|
||||
<span className={isIn ? "font-black text-emerald-600" : "font-black text-[#0b3f96]"}>
|
||||
{isIn ? "+" : "−"}
|
||||
{formatMinorAsCurrency(item.amount_abs, ccy)}
|
||||
</span>
|
||||
</span>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
{formatLocalDateTime(item.created_at)}{" "}
|
||||
<span className="text-foreground/80">
|
||||
· {txnStatusLabel(item.status, t)}
|
||||
</span>
|
||||
<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-3">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={isIn ? "size-2.5 rounded-full bg-emerald-500" : "size-2.5 rounded-full bg-[#0b3f96]"} aria-hidden />
|
||||
<p className="truncate text-base font-black leading-tight text-[#101a33]">
|
||||
{logTypeLabel(item.type, t)}
|
||||
</p>
|
||||
</div>
|
||||
<p className="mt-1.5 text-xs font-medium text-slate-500">
|
||||
{formatLocalDateTime(item.created_at)}
|
||||
</p>
|
||||
{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}
|
||||
</p>
|
||||
) : null}
|
||||
</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>
|
||||
</li>
|
||||
);
|
||||
|
||||
@@ -194,8 +194,8 @@ export function WalletScreen() {
|
||||
lotteryMinor={Number(balance?.balance ?? 0)}
|
||||
onSuccess={refreshAll}
|
||||
triggerVariant="hall"
|
||||
triggerLabel={t("wallet.transferIn")}
|
||||
triggerClassName="h-12 rounded-lg text-base font-bold"
|
||||
triggerLabel={t("wallet.transferIn", { defaultValue: "Transfer In" })}
|
||||
triggerClassName="h-14 rounded-2xl text-base font-black"
|
||||
/>
|
||||
<TransferOutDialog
|
||||
idPrefix="wallet-"
|
||||
@@ -203,8 +203,8 @@ export function WalletScreen() {
|
||||
availableMinor={Number(balance?.available_balance ?? 0)}
|
||||
onSuccess={refreshAll}
|
||||
triggerVariant="hall"
|
||||
triggerLabel={t("wallet.transferOut")}
|
||||
triggerClassName="h-12 rounded-lg text-base font-bold"
|
||||
triggerLabel={t("wallet.transferOut", { defaultValue: "Transfer Out" })}
|
||||
triggerClassName="h-14 rounded-2xl text-base font-black"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -26,15 +26,13 @@ type BaseProps = {
|
||||
};
|
||||
|
||||
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 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 =
|
||||
"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({
|
||||
currency,
|
||||
@@ -71,24 +69,26 @@ export function TransferInDialog({
|
||||
<ArrowDownLeft className="size-4 shrink-0" />
|
||||
{resolvedTriggerLabel}
|
||||
</Button>
|
||||
<DialogContent showCloseButton>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("wallet.transferInTitle")}</DialogTitle>
|
||||
<DialogDescription>
|
||||
<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 className="bg-gradient-to-br from-[#0b3f96] via-[#1456bd] to-[#e5002c] px-5 pb-5 pt-6 text-white">
|
||||
<DialogTitle className="text-xl font-black text-white">{t("wallet.transferInTitle")}</DialogTitle>
|
||||
<DialogDescription className="text-sm leading-6 text-white/85">
|
||||
{t("wallet.dialogInDescription", { currency })}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<TransferInPanel
|
||||
variant="dialog"
|
||||
currency={currency}
|
||||
lotteryMinor={lotteryMinor}
|
||||
idPrefix={idPrefix}
|
||||
onCancel={() => setOpen(false)}
|
||||
onSuccess={async () => {
|
||||
await onSuccess();
|
||||
setOpen(false);
|
||||
}}
|
||||
/>
|
||||
<div className="px-5 pb-5 pt-4">
|
||||
<TransferInPanel
|
||||
variant="dialog"
|
||||
currency={currency}
|
||||
lotteryMinor={lotteryMinor}
|
||||
idPrefix={idPrefix}
|
||||
onCancel={() => setOpen(false)}
|
||||
onSuccess={async () => {
|
||||
await onSuccess();
|
||||
setOpen(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
@@ -134,24 +134,26 @@ export function TransferOutDialog({
|
||||
<ArrowUpRight className="size-4 shrink-0" />
|
||||
{resolvedTriggerLabel}
|
||||
</Button>
|
||||
<DialogContent showCloseButton>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("wallet.transferOutTitle")}</DialogTitle>
|
||||
<DialogDescription>
|
||||
<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 className="bg-gradient-to-br from-[#e5002c] via-[#d81435] to-[#0b3f96] px-5 pb-5 pt-6 text-white">
|
||||
<DialogTitle className="text-xl font-black text-white">{t("wallet.transferOutTitle")}</DialogTitle>
|
||||
<DialogDescription className="text-sm leading-6 text-white/85">
|
||||
{t("wallet.dialogOutDescription")}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<TransferOutPanel
|
||||
variant="dialog"
|
||||
currency={currency}
|
||||
availableMinor={availableMinor}
|
||||
idPrefix={idPrefix}
|
||||
onCancel={() => setOpen(false)}
|
||||
onSuccess={async () => {
|
||||
await onSuccess();
|
||||
setOpen(false);
|
||||
}}
|
||||
/>
|
||||
<div className="px-5 pb-5 pt-4">
|
||||
<TransferOutPanel
|
||||
variant="dialog"
|
||||
currency={currency}
|
||||
availableMinor={availableMinor}
|
||||
idPrefix={idPrefix}
|
||||
onCancel={() => setOpen(false)}
|
||||
onSuccess={async () => {
|
||||
await onSuccess();
|
||||
setOpen(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -121,17 +121,10 @@ export function TransferInPanel({
|
||||
)}
|
||||
</Button>
|
||||
) : (
|
||||
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
disabled={submitting}
|
||||
onClick={onCancel}
|
||||
>
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
<div className="grid gap-2">
|
||||
<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}
|
||||
onClick={() => void submit()}
|
||||
>
|
||||
@@ -144,26 +137,35 @@ export function TransferInPanel({
|
||||
t("wallet.confirmIn")
|
||||
)}
|
||||
</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>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid gap-3 py-1">
|
||||
<div className="rounded-xl border border-[#e5edf8] bg-[#f8fbff] px-3 py-2 text-xs">
|
||||
<p>
|
||||
<div className="grid gap-4 py-1">
|
||||
<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 className="text-slate-500">
|
||||
{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 className="mt-1">
|
||||
<p className="mt-2 text-slate-500">
|
||||
{t("wallet.lotteryBalance")}{" "}
|
||||
<span className="font-medium text-foreground">
|
||||
<span className="font-black tabular-nums text-[#0b3f96]">
|
||||
{formatMinorAsCurrency(lotteryMinor, currency)}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor={tid}>{t("wallet.inAmount")}</Label>
|
||||
<div className="grid gap-2.5">
|
||||
<Label htmlFor={tid} className="text-sm font-black text-[#101a33]">{t("wallet.inAmount")}</Label>
|
||||
<Input
|
||||
id={tid}
|
||||
inputMode="decimal"
|
||||
@@ -172,16 +174,16 @@ export function TransferInPanel({
|
||||
onChange={(ev) => setAmountText(ev.target.value)}
|
||||
disabled={submitting}
|
||||
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", {
|
||||
amount: formatMinorAsCurrency(previewAfter, currency),
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
{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}
|
||||
</div>
|
||||
{footer}
|
||||
@@ -270,17 +272,10 @@ export function TransferOutPanel({
|
||||
)}
|
||||
</Button>
|
||||
) : (
|
||||
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
disabled={submitting}
|
||||
onClick={onCancel}
|
||||
>
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
<div className="grid gap-2">
|
||||
<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}
|
||||
onClick={() => void submit()}
|
||||
>
|
||||
@@ -293,27 +288,36 @@ export function TransferOutPanel({
|
||||
t("wallet.confirmOut")
|
||||
)}
|
||||
</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>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid gap-3 py-1">
|
||||
<div className="rounded-xl border border-[#e5edf8] bg-[#f8fbff] px-3 py-2 text-xs">
|
||||
<p>
|
||||
<div className="grid gap-4 py-1">
|
||||
<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 className="text-slate-500">
|
||||
{t("wallet.lotteryAvailable")}{" "}
|
||||
<span className="font-medium text-foreground">
|
||||
<span className="font-black tabular-nums text-[#d81435]">
|
||||
{formatMinorAsCurrency(availableMinor, currency)}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<div className="grid gap-2.5">
|
||||
<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
|
||||
type="button"
|
||||
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}
|
||||
disabled={submitting || availableMinor < 1}
|
||||
>
|
||||
@@ -330,16 +334,16 @@ export function TransferOutPanel({
|
||||
onChange={(ev) => setAmountText(ev.target.value)}
|
||||
disabled={submitting}
|
||||
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", {
|
||||
amount: formatMinorAsCurrency(previewAfter, currency),
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
{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}
|
||||
</div>
|
||||
{footer}
|
||||
|
||||
@@ -9,8 +9,12 @@
|
||||
"clear": "Clear",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Confirm",
|
||||
"close": "Close",
|
||||
"loading": "Loading...",
|
||||
"loadMore": "Load More",
|
||||
"previous": "Previous",
|
||||
"next": "Next",
|
||||
"done": "Done",
|
||||
"processing": "Processing...",
|
||||
"deleteRow": "Delete row {{row}}"
|
||||
},
|
||||
@@ -102,6 +106,13 @@
|
||||
"changedBeforeSubmit": "Your draft changed before submission. Close the preview and try again.",
|
||||
"placeFailed": "Submission failed",
|
||||
"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": {
|
||||
"title": "Jackpot Burst",
|
||||
"subtitle": "Issue {{drawNo}} triggered a pool payout",
|
||||
@@ -145,13 +156,28 @@
|
||||
"sealedHint": "Closed: this table is locked. Please wait for the next issue.",
|
||||
"previewing": "Previewing...",
|
||||
"submitBet": "Submit Bet",
|
||||
"insufficientBalance": "Insufficient balance",
|
||||
"amountPlaceholder": "Amount",
|
||||
"filledPlayCount": "{{count}} plays filled",
|
||||
"tapToFill": "Tap to enter amounts",
|
||||
"rowActual": "Actual",
|
||||
"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).",
|
||||
"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": {
|
||||
"title": "Confirm bet",
|
||||
@@ -183,6 +209,24 @@
|
||||
"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."
|
||||
},
|
||||
"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": {
|
||||
"limit": "Limit {{min}} - {{max}}",
|
||||
"placeholder": "e.g. 100.00"
|
||||
@@ -280,6 +324,7 @@
|
||||
"flowsTitle": "Wallet logs",
|
||||
"totalRecords": "{{total}} records",
|
||||
"emptyLogs": "No wallet logs",
|
||||
"balanceAfter": "Balance after",
|
||||
"flow": {
|
||||
"all": "All",
|
||||
"transfer_in": "Transfer in",
|
||||
@@ -322,7 +367,9 @@
|
||||
"betNow": "Bet Now",
|
||||
"empty": "No bet records yet.",
|
||||
"dateRange": "Date range",
|
||||
"status": "Status",
|
||||
"statusFilter": "Status filter",
|
||||
"noMore": "No more tickets",
|
||||
"submitBet": "Submit Bet",
|
||||
"stake": "Stake",
|
||||
"deduction": "Deduction",
|
||||
@@ -364,8 +411,10 @@
|
||||
"subtitle": "Latest draw history",
|
||||
"detailTitle": "Result Detail",
|
||||
"businessDate": "Business Date",
|
||||
"selectBusinessDate": "Select date",
|
||||
"empty": "No results yet.",
|
||||
"detail": "Detail",
|
||||
"openDetail": "Open detail",
|
||||
"loadFailed": "Failed to load",
|
||||
"unavailable": "This result is unavailable or does not exist.",
|
||||
"noData": "No data",
|
||||
@@ -376,10 +425,32 @@
|
||||
"regular": "Regular: {{amount}}",
|
||||
"jackpot": "Jackpot: {{amount}}",
|
||||
"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",
|
||||
"jackpotLabel": "Jackpot",
|
||||
"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": {
|
||||
"first": "First prize",
|
||||
"second": "Second prize",
|
||||
|
||||
@@ -9,8 +9,12 @@
|
||||
"clear": "हटाउनुहोस्",
|
||||
"cancel": "रद्द",
|
||||
"confirm": "पुष्टि",
|
||||
"close": "बन्द",
|
||||
"loading": "लोड हुँदैछ...",
|
||||
"loadMore": "थप लोड गर्नुहोस्",
|
||||
"previous": "अघिल्लो",
|
||||
"next": "अर्को",
|
||||
"done": "पूरा भयो",
|
||||
"processing": "प्रक्रिया हुँदैछ...",
|
||||
"deleteRow": "{{row}} नम्बर पंक्ति हटाउनुहोस्"
|
||||
},
|
||||
@@ -102,6 +106,13 @@
|
||||
"changedBeforeSubmit": "पेश गर्नु अघि ड्राफ्ट परिवर्तन भयो। पूर्वावलोकन बन्द गरी फेरि प्रयास गर्नुहोस्।",
|
||||
"placeFailed": "पेश गर्न असफल",
|
||||
"placeSuccess": "बेट पेश भयो। अर्डर {{orderNo}}, कट्टा {{amount}}।",
|
||||
"placePartialFailed": "{{success}} सफल, {{failed}} असफल",
|
||||
"playConfig": {
|
||||
"playClosedDraftCleared": "{{playCode}} बन्द छ। सम्बन्धित ड्राफ्ट रकम हटाइएको छ।",
|
||||
"playClosed": "{{playCode}} बन्द छ।",
|
||||
"updated": "प्ले कन्फिगरेसन अपडेट भयो। बेटिङ तालिका रिफ्रेस गरियो।",
|
||||
"oddsUpdated": "Odds अपडेट भयो। कृपया टिकट फेरि पूर्वावलोकन गर्नुहोस्।"
|
||||
},
|
||||
"jackpotBurst": {
|
||||
"title": "Jackpot Burst",
|
||||
"subtitle": "इश्यू {{drawNo}} मा पूल payout ट्रिगर भयो",
|
||||
@@ -145,13 +156,28 @@
|
||||
"sealedHint": "बन्द: यो तालिका लक छ। कृपया अर्को इश्यू पर्खनुहोस्।",
|
||||
"previewing": "पूर्वावलोकन...",
|
||||
"submitBet": "बेट पेश गर्नुहोस्",
|
||||
"insufficientBalance": "ब्यालेन्स अपुग",
|
||||
"amountPlaceholder": "रकम",
|
||||
"filledPlayCount": "{{count}} प्ले भरियो",
|
||||
"tapToFill": "रकम लेख्न ट्याप गर्नुहोस्",
|
||||
"rowActual": "वास्तविक",
|
||||
"editRow": "पंक्ति {{row}} सम्पादन गर्नुहोस्",
|
||||
"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": {
|
||||
"title": "बेट पुष्टि गर्नुहोस्",
|
||||
@@ -176,9 +202,31 @@
|
||||
"backEdit": "सम्पादनमा फर्कनुहोस्",
|
||||
"submitting": "पेश हुँदैछ...",
|
||||
"confirmSubmit": "पेश पुष्टि",
|
||||
"processingTitle": "बेट पेश हुँदैछ",
|
||||
"processingDescription": "कृपया पृष्ठ बन्द नगर्नुहोस् वा पछाडि नजानुहोस्।",
|
||||
"processingProgress": "टिकट प्रक्रिया हुँदैछ...",
|
||||
"noWarnings": "यस पूर्वावलोकनमा स्पष्ट जोखिम भेटिएन।",
|
||||
"warningsTitle": "भुक्तानी पूल चेतावनी",
|
||||
"warningsDescription": "यी नम्बरहरूमा यस इश्यूमा भुक्तानी पूल प्रयोग उच्च छ। बेट अझै गर्न सकिन्छ, तर क्षमता अपुग भए अर्डर sold out हुन सक्छ।"
|
||||
},
|
||||
"result": {
|
||||
"title": "बेट नतिजा",
|
||||
"draw": "इश्यू",
|
||||
"empty": "नतिजा छैन।",
|
||||
"successCount": "सफल लाइनहरू",
|
||||
"failureCount": "असफल लाइनहरू",
|
||||
"actual": "वास्तविक कट्टा",
|
||||
"orderNo": "अर्डर नं.",
|
||||
"balanceAfter": "बाँकी ब्यालेन्स",
|
||||
"items": "सफल लाइन विवरण",
|
||||
"number": "नम्बर",
|
||||
"actualDeduct": "कट्टा",
|
||||
"noFailures": "यस पेशमा असफल टिकट लाइन छैन।",
|
||||
"failedItems": "असफल लाइन विवरण",
|
||||
"failed": "असफल",
|
||||
"viewBets": "टिकट हेर्नुहोस्",
|
||||
"continueBetting": "बेट जारी राख्नुहोस्"
|
||||
},
|
||||
"amountInput": {
|
||||
"limit": "सीमा {{min}} - {{max}}",
|
||||
"placeholder": "जस्तै 100.00"
|
||||
@@ -276,6 +324,7 @@
|
||||
"flowsTitle": "वालेट लग",
|
||||
"totalRecords": "{{total}} रेकर्ड",
|
||||
"emptyLogs": "वालेट लग छैन",
|
||||
"balanceAfter": "पछि बाँकी ब्यालेन्स",
|
||||
"flow": {
|
||||
"all": "सबै",
|
||||
"transfer_in": "ट्रान्सफर इन",
|
||||
@@ -317,6 +366,10 @@
|
||||
"totalRecords": "कुल रेकर्ड",
|
||||
"betNow": "अहिले बेट",
|
||||
"empty": "अहिलेसम्म बेट रेकर्ड छैन।",
|
||||
"dateRange": "मिति दायरा",
|
||||
"status": "स्थिति",
|
||||
"statusFilter": "स्थिति फिल्टर",
|
||||
"noMore": "थप टिकट छैन",
|
||||
"submitBet": "बेट पेश गर्नुहोस्",
|
||||
"stake": "बेट",
|
||||
"deduction": "कट्टा",
|
||||
@@ -353,8 +406,10 @@
|
||||
"subtitle": "हालका ड्र इतिहास",
|
||||
"detailTitle": "नतिजा विवरण",
|
||||
"businessDate": "व्यावसायिक मिति",
|
||||
"selectBusinessDate": "मिति छान्नुहोस्",
|
||||
"empty": "अहिलेसम्म नतिजा छैन।",
|
||||
"detail": "विवरण",
|
||||
"openDetail": "विवरण खोल्नुहोस्",
|
||||
"loadFailed": "लोड असफल",
|
||||
"unavailable": "यो नतिजा उपलब्ध छैन वा अस्तित्वमा छैन।",
|
||||
"noData": "डेटा छैन",
|
||||
@@ -365,10 +420,32 @@
|
||||
"regular": "सामान्य: {{amount}}",
|
||||
"jackpot": "Jackpot: {{amount}}",
|
||||
"hitPending": "तपाईंको टिकटले यस इश्यूको नतिजा सेल हिट गरेको छ। भुक्तानी पूरा भएपछि रकम देखिनेछ।",
|
||||
"hitHint": "तपाईं जित्नुभयो भने, तपाईंका टिकटसँग मिलेका नम्बरहरू सुनौलो रंगमा देखिन्छन् (लगइन आवश्यक)।",
|
||||
"hitHint": "तपाईं जित्नुभयो भने, तपाईंका टिकटसँग मिलेका नम्बरहरू सुनौलो रंगमा देखिन्छन्।",
|
||||
"viewMyWinning": "मेरो जित स्थिति हेर्नुहोस्",
|
||||
"jackpotLabel": "Jackpot",
|
||||
"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": {
|
||||
"first": "पहिलो पुरस्कार",
|
||||
"second": "दोस्रो पुरस्कार",
|
||||
@@ -450,6 +527,7 @@
|
||||
},
|
||||
"ticketStatus": {
|
||||
"success": "ड्र पर्खँदै",
|
||||
"pending_payout": "जितेको, भुक्तानी बाँकी",
|
||||
"settled_win": "भुक्तानी भयो",
|
||||
"settled_lose": "जितेन",
|
||||
"unknown": "{{status}}"
|
||||
|
||||
@@ -9,8 +9,12 @@
|
||||
"clear": "清除",
|
||||
"cancel": "取消",
|
||||
"confirm": "确认",
|
||||
"close": "关闭",
|
||||
"loading": "加载中...",
|
||||
"loadMore": "加载更多",
|
||||
"previous": "上一页",
|
||||
"next": "下一页",
|
||||
"done": "完成",
|
||||
"processing": "处理中...",
|
||||
"deleteRow": "删除第 {{row}} 行"
|
||||
},
|
||||
@@ -102,6 +106,13 @@
|
||||
"changedBeforeSubmit": "提交前数据已变化,请关闭预览后重试。",
|
||||
"placeFailed": "提交失败",
|
||||
"placeSuccess": "下注成功,订单号 {{orderNo}},实扣 {{amount}}。",
|
||||
"placePartialFailed": "{{success}} 个成功,{{failed}} 个失败",
|
||||
"playConfig": {
|
||||
"playClosedDraftCleared": "{{playCode}} 已关闭,相关草稿金额已清除。",
|
||||
"playClosed": "{{playCode}} 已关闭。",
|
||||
"updated": "玩法配置已更新,已刷新下注表格。",
|
||||
"oddsUpdated": "赔率已更新,请重新预览注单。"
|
||||
},
|
||||
"jackpotBurst": {
|
||||
"title": "Jackpot 爆池",
|
||||
"subtitle": "期号 {{drawNo}} 触发奖池派发",
|
||||
@@ -145,13 +156,28 @@
|
||||
"sealedHint": "已封盘:当前表格不可编辑,请等待下一期。",
|
||||
"previewing": "预览中...",
|
||||
"submitBet": "提交下注",
|
||||
"insufficientBalance": "余额不足",
|
||||
"amountPlaceholder": "金额",
|
||||
"filledPlayCount": "已填写 {{count}} 个玩法",
|
||||
"tapToFill": "点击填写玩法金额",
|
||||
"rowActual": "实扣",
|
||||
"editRow": "编辑第 {{row}} 行",
|
||||
"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": {
|
||||
"title": "确认下注",
|
||||
@@ -183,6 +209,24 @@
|
||||
"warningsTitle": "赔付池预警",
|
||||
"warningsDescription": "以下号码本期赔付池占用较高,仍允许下注;若实际占用不足将售罄拒单。"
|
||||
},
|
||||
"result": {
|
||||
"title": "下注结果",
|
||||
"draw": "期号",
|
||||
"empty": "暂无结果。",
|
||||
"successCount": "成功注项",
|
||||
"failureCount": "失败注项",
|
||||
"actual": "实扣金额",
|
||||
"orderNo": "订单号",
|
||||
"balanceAfter": "剩余余额",
|
||||
"items": "成功注项明细",
|
||||
"number": "号码",
|
||||
"actualDeduct": "实扣",
|
||||
"noFailures": "本次提交没有失败注项。",
|
||||
"failedItems": "失败注项明细",
|
||||
"failed": "失败",
|
||||
"viewBets": "查看注单",
|
||||
"continueBetting": "继续下注"
|
||||
},
|
||||
"amountInput": {
|
||||
"limit": "限额 {{min}} - {{max}}",
|
||||
"placeholder": "例如 100.00"
|
||||
@@ -280,6 +324,7 @@
|
||||
"flowsTitle": "资金流水",
|
||||
"totalRecords": "共 {{total}} 条记录",
|
||||
"emptyLogs": "暂无流水",
|
||||
"balanceAfter": "变更后余额",
|
||||
"flow": {
|
||||
"all": "全部",
|
||||
"transfer_in": "转入",
|
||||
@@ -322,7 +367,9 @@
|
||||
"betNow": "立即下注",
|
||||
"empty": "暂无下注记录。",
|
||||
"dateRange": "日期范围",
|
||||
"status": "状态",
|
||||
"statusFilter": "状态筛选",
|
||||
"noMore": "没有更多注单",
|
||||
"submitBet": "提交下注",
|
||||
"stake": "下注",
|
||||
"deduction": "实扣",
|
||||
@@ -364,8 +411,10 @@
|
||||
"subtitle": "最新开奖历史",
|
||||
"detailTitle": "开奖详情",
|
||||
"businessDate": "业务日期",
|
||||
"selectBusinessDate": "选择日期",
|
||||
"empty": "暂无开奖结果。",
|
||||
"detail": "详情",
|
||||
"openDetail": "查看详情",
|
||||
"loadFailed": "加载失败",
|
||||
"unavailable": "该期开奖结果不可用或不存在",
|
||||
"noData": "无数据",
|
||||
@@ -376,10 +425,32 @@
|
||||
"regular": "常规:{{amount}}",
|
||||
"jackpot": "Jackpot:{{amount}}",
|
||||
"hitPending": "您的注单已命中本期开奖号码中的格子;派彩完成后将显示金额汇总。",
|
||||
"hitHint": "如果您中奖,与注单匹配的号码将以金色高亮显示(需登录)。",
|
||||
"hitHint": "如果您中奖,与注单匹配的号码将以金色高亮显示。",
|
||||
"viewMyWinning": "查看我的中奖情况",
|
||||
"jackpotLabel": "Jackpot",
|
||||
"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": {
|
||||
"first": "头奖",
|
||||
"second": "二奖",
|
||||
|
||||
Reference in New Issue
Block a user