feat: 新增玩法规则页并增强注单筛选与结果展示
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { AlertTriangleIcon } from "lucide-react";
|
||||
import {
|
||||
AlertTriangleIcon,
|
||||
CheckCircle2,
|
||||
LoaderCircle,
|
||||
WalletCards,
|
||||
XIcon,
|
||||
} from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
@@ -12,9 +18,8 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { formatMinorAsCurrency } from "@/lib/money";
|
||||
import { playLabel } from "@/lib/play-labels";
|
||||
import type { TicketPreviewData, TicketPreviewWarning } from "@/types/api/ticket";
|
||||
|
||||
type HallBetPreviewDialogProps = {
|
||||
@@ -33,17 +38,17 @@ function WarningsBlock({ warnings }: { warnings: TicketPreviewWarning[] }) {
|
||||
|
||||
if (warnings.length === 0) return null;
|
||||
return (
|
||||
<Alert className="border-amber-200 bg-amber-50 text-amber-950">
|
||||
<AlertTriangleIcon />
|
||||
<AlertTitle>{t("hall.preview.warningsTitle")}</AlertTitle>
|
||||
<AlertDescription className="space-y-1">
|
||||
<p className="text-xs leading-relaxed">
|
||||
<Alert className="rounded-xl border-[#ffd7b8] bg-[#fff7ed] p-3 text-[#b45309]">
|
||||
<AlertTriangleIcon className="size-5" />
|
||||
<AlertTitle className="text-sm font-black">{t("hall.preview.warningsTitle")}</AlertTitle>
|
||||
<AlertDescription className="space-y-2">
|
||||
<p className="text-xs leading-relaxed text-[#92400e]">
|
||||
{t("hall.preview.warningsDescription")}
|
||||
</p>
|
||||
<ul className="list-inside list-disc text-xs">
|
||||
<ul className="space-y-1 text-xs text-slate-700">
|
||||
{warnings.map((w, i) => (
|
||||
<li key={`${w.number_4d}-${i}`}>
|
||||
<span className="font-mono">{w.number_4d}</span> — {w.message}
|
||||
<span className="font-mono font-black text-[#e5002c]">{w.number_4d}</span> · {w.message}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
@@ -52,6 +57,38 @@ function WarningsBlock({ warnings }: { warnings: TicketPreviewWarning[] }) {
|
||||
);
|
||||
}
|
||||
|
||||
function SubmittingPanel() {
|
||||
const { t } = useTranslation("player");
|
||||
|
||||
return (
|
||||
<DialogContent
|
||||
showCloseButton={false}
|
||||
className="max-w-[340px] overflow-hidden rounded-2xl border border-white/70 bg-white p-0 shadow-[0_24px_70px_rgba(15,23,42,0.22)]"
|
||||
>
|
||||
<div className="px-8 py-10 text-center">
|
||||
<div className="mx-auto flex size-20 rotate-[-8deg] items-center justify-center rounded-2xl bg-[#e5002c] text-4xl font-black italic text-white shadow-[0_16px_28px_rgba(229,0,44,0.28)]">
|
||||
N
|
||||
</div>
|
||||
<div className="mx-auto mt-8 h-2 w-44 overflow-hidden rounded-full bg-[#dbe3f1]">
|
||||
<div className="h-full w-2/3 rounded-full bg-[#0755c7] shadow-[0_0_14px_rgba(7,85,199,0.45)]" />
|
||||
</div>
|
||||
<DialogHeader className="mt-8 items-center gap-2">
|
||||
<DialogTitle className="text-xl font-black text-slate-950">
|
||||
{t("hall.preview.processingTitle", { defaultValue: "正在提交下注" })}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="max-w-[220px] text-center text-sm leading-relaxed text-slate-500">
|
||||
{t("hall.preview.processingDescription", { defaultValue: "请勿关闭页面或返回上一页。" })}
|
||||
</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: "正在处理注单..." })}
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览弹窗 + 提交确认(产品文档 §10.1.2:预览不下单,确认后 place)。
|
||||
*/
|
||||
@@ -68,15 +105,40 @@ export function HallBetPreviewDialog({
|
||||
const summary = data?.summary;
|
||||
const lines = data?.lines ?? [];
|
||||
|
||||
if (placing) {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={() => {}}>
|
||||
<SubmittingPanel />
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-h-[min(90vh,560px)] gap-0 overflow-hidden border-[#e8eef7] p-0 shadow-[0_12px_40px_rgba(15,23,42,0.12)] ring-[#e8eef7] sm:max-w-md">
|
||||
<div className="p-4 pb-2">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-lg font-black text-[#0b3f96]">
|
||||
{t("hall.preview.title")}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-xs leading-relaxed text-slate-500">
|
||||
<DialogContent
|
||||
showCloseButton={false}
|
||||
className="max-h-[min(92vh,760px)] gap-0 overflow-hidden rounded-2xl border-[#e4ebf6] bg-white p-0 shadow-[0_18px_60px_rgba(15,23,42,0.18)] ring-[#e8eef7] sm:max-w-lg"
|
||||
>
|
||||
<div className="relative px-4 pb-3 pt-5 sm:px-5">
|
||||
<button
|
||||
type="button"
|
||||
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: "关闭" })}
|
||||
>
|
||||
<XIcon className="size-5" />
|
||||
</button>
|
||||
<DialogHeader className="pr-10">
|
||||
<div className="mb-2 flex items-center gap-3">
|
||||
<span className="flex size-11 items-center justify-center rounded-full bg-[#fff0f3] text-[#e5002c]">
|
||||
<WalletCards className="size-6" />
|
||||
</span>
|
||||
<DialogTitle className="text-xl font-black text-slate-950">
|
||||
{t("hall.preview.title")}
|
||||
</DialogTitle>
|
||||
</div>
|
||||
<DialogDescription className="text-sm leading-relaxed text-slate-500">
|
||||
{t("hall.preview.description")}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
@@ -91,109 +153,122 @@ export function HallBetPreviewDialog({
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<ScrollArea className="max-h-[min(52vh,360px)] border-y border-[#e8eef7] px-4">
|
||||
<div className="space-y-4 py-3 pr-3">
|
||||
<div
|
||||
className="overflow-y-auto border-y border-[#e8eef7] px-4 sm:px-5"
|
||||
style={{ maxHeight: "min(58vh, 470px)" }}
|
||||
>
|
||||
<div className="space-y-4 py-4">
|
||||
{!data ? (
|
||||
<p className="text-sm text-slate-500">{t("hall.preview.empty")}</p>
|
||||
) : (
|
||||
<>
|
||||
<div className="rounded-xl border border-[#e8eef7] bg-[#f8fbff] p-3 text-xs shadow-[0_4px_14px_rgba(15,23,42,0.04)]">
|
||||
<p className="text-slate-600">
|
||||
{t("hall.preview.draw")}{" "}
|
||||
<span className="font-mono font-black text-[#0b3f96]">{data.draw.draw_id}</span> ·{" "}
|
||||
{t("hall.preview.status")}{" "}
|
||||
<span className="font-semibold text-[#32518d]">{data.draw.status}</span>
|
||||
</p>
|
||||
{summary ? (
|
||||
<ul className="mt-2 space-y-1 tabular-nums text-slate-700">
|
||||
<li>
|
||||
{t("hall.preview.amount")}{" "}
|
||||
<span className="font-semibold text-[#d81435]">
|
||||
{formatMinorAsCurrency(summary.total_bet_amount, currencyCode)}
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
{t("hall.preview.rebate")}{" "}
|
||||
<span className="font-semibold text-emerald-600">
|
||||
{formatMinorAsCurrency(summary.total_rebate_amount, currencyCode)}
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
{t("hall.preview.actual")}{" "}
|
||||
<span className="font-bold text-[#0b3f96]">
|
||||
{formatMinorAsCurrency(summary.total_actual_deduct, currencyCode)}
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
{t("hall.preview.total")}{" "}
|
||||
<span className="font-medium text-slate-800">
|
||||
{formatMinorAsCurrency(summary.total_actual_deduct, currencyCode)}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
) : null}
|
||||
<div className="flex flex-wrap items-center gap-2 text-sm">
|
||||
<span className="text-slate-500">{t("hall.preview.draw")}:</span>
|
||||
<span className="font-mono font-black text-[#e5002c]">{data.draw.draw_id}</span>
|
||||
<span className="rounded-full bg-[#edf4ff] px-2 py-0.5 text-xs font-bold text-[#0b3f96]">
|
||||
{data.draw.status}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<WarningsBlock warnings={data.warnings} />
|
||||
|
||||
<div className="space-y-2">
|
||||
<p className="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
||||
{t("hall.preview.lines")}
|
||||
</p>
|
||||
<ul className="space-y-2 text-sm">
|
||||
{lines.map((ln) => (
|
||||
<li
|
||||
key={ln.client_line_no}
|
||||
className="rounded-xl border border-[#e6edf8] bg-white px-3 py-2 shadow-[0_4px_14px_rgba(15,23,42,0.04)]"
|
||||
>
|
||||
<div className="flex flex-wrap items-baseline justify-between gap-2">
|
||||
<span className="font-mono text-xs text-slate-400">
|
||||
#{ln.client_line_no}
|
||||
</span>
|
||||
<span className="font-mono text-sm font-semibold text-[#32518d]">
|
||||
<div className="overflow-x-auto rounded-xl border border-[#dfe8f6]">
|
||||
<table className="min-w-[520px] w-full border-collapse text-xs">
|
||||
<thead className="bg-[#f4f7fd] text-[#304f86]">
|
||||
<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: "号码" })}
|
||||
</th>
|
||||
<th className="border-r border-[#dfe8f6] px-2 py-3 text-center font-black">
|
||||
{t("orders.play", { defaultValue: "玩法" })}
|
||||
</th>
|
||||
<th className="border-r border-[#dfe8f6] px-2 py-3 text-center font-black">
|
||||
{t("hall.preview.amount")}
|
||||
</th>
|
||||
<th className="border-r border-[#dfe8f6] px-2 py-3 text-center font-black">
|
||||
{t("hall.preview.rebate")}
|
||||
</th>
|
||||
<th className="px-2 py-3 text-center font-black">
|
||||
{t("hall.preview.actual")}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{lines.map((ln) => (
|
||||
<tr key={ln.client_line_no} className="border-t border-[#e8eef7] bg-white">
|
||||
<td className="border-r border-[#e8eef7] px-2 py-3 text-center">
|
||||
<span className="inline-flex size-6 items-center justify-center rounded-full border border-[#dfe8f6] font-black text-[#304f86]">
|
||||
{ln.client_line_no}
|
||||
</span>
|
||||
</td>
|
||||
<td className="border-r border-[#e8eef7] px-2 py-3 text-center">
|
||||
<span className="block font-mono text-base font-black text-slate-950">{ln.number}</span>
|
||||
<span className="text-[11px] font-bold text-[#0755c7]">{playLabel(ln.play_code, t)}</span>
|
||||
</td>
|
||||
<td className="border-r border-[#e8eef7] px-2 py-3 text-center font-semibold text-slate-700">
|
||||
{ln.play_code}
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-1 font-mono text-lg font-black text-[#0b3f96]">{ln.number}</p>
|
||||
<Separator className="my-2 bg-[#e8eef7]" />
|
||||
<div className="grid grid-cols-2 gap-x-2 gap-y-1 text-xs tabular-nums">
|
||||
<span className="text-slate-500">
|
||||
{t("hall.preview.normalizedNumber")}
|
||||
</span>
|
||||
<span className="text-right font-mono">{ln.normalized_number}</span>
|
||||
<span className="text-slate-500">
|
||||
{t("hall.preview.combinationCount")}
|
||||
</span>
|
||||
<span className="text-right">{ln.combination_count}</span>
|
||||
<span className="text-slate-500">
|
||||
{t("hall.preview.actual")}
|
||||
</span>
|
||||
<span className="text-right font-semibold text-[#0b3f96]">
|
||||
</td>
|
||||
<td className="border-r border-[#e8eef7] px-2 py-3 text-center font-semibold tabular-nums">
|
||||
{formatMinorAsCurrency(ln.total_bet_amount, currencyCode)}
|
||||
</td>
|
||||
<td className="border-r border-[#e8eef7] px-2 py-3 text-center font-semibold tabular-nums text-emerald-600">
|
||||
-{formatMinorAsCurrency(ln.rebate_amount, currencyCode).replace(`${currencyCode} `, "")}
|
||||
</td>
|
||||
<td className="px-2 py-3 text-center font-black tabular-nums text-[#0b3f96]">
|
||||
{formatMinorAsCurrency(ln.actual_deduct_amount, currencyCode)}
|
||||
</span>
|
||||
<span className="text-slate-500">
|
||||
{t("hall.preview.estimatedMax")}
|
||||
</span>
|
||||
<span className="text-right">
|
||||
{formatMinorAsCurrency(ln.estimated_max_payout, currencyCode)}
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{summary ? (
|
||||
<div className="grid grid-cols-3 overflow-hidden rounded-xl border border-[#dfe8f6] bg-[#f8fbff] text-center text-xs">
|
||||
<div className="border-r border-[#dfe8f6] px-2 py-3">
|
||||
<p className="font-bold text-[#304f86]">{t("hall.preview.totalBet")}</p>
|
||||
<p className="mt-1 font-black tabular-nums text-[#0b3f96]">
|
||||
{formatMinorAsCurrency(summary.total_bet_amount, currencyCode)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="border-r border-[#dfe8f6] px-2 py-3">
|
||||
<p className="font-bold text-[#304f86]">{t("hall.preview.rebate")}</p>
|
||||
<p className="mt-1 font-black tabular-nums text-emerald-600">
|
||||
-
|
||||
{formatMinorAsCurrency(summary.total_rebate_amount, currencyCode).replace(
|
||||
`${currencyCode} `,
|
||||
"",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="px-2 py-3">
|
||||
<p className="font-bold text-[#304f86]">{t("hall.preview.actualDeduct")}</p>
|
||||
<p className="mt-1 font-black tabular-nums text-[#e5002c]">
|
||||
{formatMinorAsCurrency(summary.total_actual_deduct, currencyCode)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{data.warnings.length > 0 ? (
|
||||
<WarningsBlock warnings={data.warnings} />
|
||||
) : (
|
||||
<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: "当前预览未发现明显风险。" })}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col-reverse gap-2 border-t border-[#e8eef7] bg-[#f8fbff] p-4 sm:flex-row sm:justify-between">
|
||||
<div className="grid grid-cols-2 gap-3 border-t border-[#e8eef7] bg-white p-4 sm:p-5">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => onOpenChange(false)}
|
||||
disabled={placing}
|
||||
className="h-11 rounded-xl border-[#dce7f7] bg-white text-sm font-semibold text-[#07459f] hover:bg-[#f1f6ff]"
|
||||
className="h-12 rounded-lg border-[#ff3650] bg-white text-base font-black text-[#e5002c] hover:bg-[#fff5f6]"
|
||||
>
|
||||
{t("hall.preview.backEdit")}
|
||||
</Button>
|
||||
@@ -201,7 +276,7 @@ export function HallBetPreviewDialog({
|
||||
type="button"
|
||||
onClick={onConfirmPlace}
|
||||
disabled={!data || placing || !allowSubmit}
|
||||
className="h-11 rounded-xl border-0 bg-[#e5002c] text-sm font-bold text-white shadow-[0_8px_20px_rgba(229,0,44,0.26)] hover:bg-[#d10028] disabled:bg-slate-300 disabled:shadow-none"
|
||||
className="h-12 rounded-lg border-0 bg-[#e5002c] text-base font-black text-white shadow-[0_10px_24px_rgba(229,0,44,0.28)] hover:bg-[#d10028] disabled:bg-slate-300 disabled:shadow-none"
|
||||
>
|
||||
{placing
|
||||
? t("hall.preview.submitting")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { CheckCircle2, ChevronRight } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { CheckCircle2, ClipboardList, Ticket } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -11,8 +12,6 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { formatMinorAsCurrency } from "@/lib/money";
|
||||
import { playLabel } from "@/lib/play-labels";
|
||||
import type { TicketPlaceData } from "@/types/api/ticket";
|
||||
@@ -38,163 +37,155 @@ export function HallBetResultDialog({
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-h-[min(90vh,620px)] gap-0 overflow-hidden border-[#e8eef7] p-0 shadow-[0_12px_40px_rgba(15,23,42,0.12)] ring-[#e8eef7] sm:max-w-md">
|
||||
<div className="p-4 pb-2">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-lg font-black text-[#0b3f96]">
|
||||
<DialogContent className="max-h-[min(92vh,760px)] gap-0 overflow-hidden rounded-2xl border-[#e4ebf6] bg-white p-0 shadow-[0_18px_60px_rgba(15,23,42,0.18)] ring-[#e8eef7] sm:max-w-lg">
|
||||
<div className="px-4 pb-3 pt-7 text-center sm:px-5">
|
||||
<div className="mx-auto flex size-16 items-center justify-center rounded-full border-4 border-emerald-100 bg-white text-emerald-600 shadow-[0_10px_24px_rgba(22,163,74,0.12)]">
|
||||
<CheckCircle2 className="size-11" strokeWidth={2.5} />
|
||||
</div>
|
||||
<DialogHeader className="mt-4 items-center gap-2">
|
||||
<DialogTitle className="text-2xl font-black text-slate-950">
|
||||
{t("hall.result.title", { defaultValue: "下注结果" })}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-xs leading-relaxed text-slate-500">
|
||||
{t("hall.result.description", {
|
||||
defaultValue: "本次提交已完成,以下为本次结果明细。",
|
||||
})}
|
||||
</DialogDescription>
|
||||
{data ? (
|
||||
<DialogDescription className="text-sm leading-relaxed text-slate-500">
|
||||
{t("hall.result.draw", { defaultValue: "期号" })}{" "}
|
||||
<span className="font-mono font-black text-[#e5002c]">{data.draw.draw_id}</span>
|
||||
</DialogDescription>
|
||||
) : null}
|
||||
</DialogHeader>
|
||||
</div>
|
||||
|
||||
<ScrollArea className="max-h-[min(56vh,400px)] border-y border-[#e8eef7] px-4">
|
||||
<div className="space-y-4 py-3 pr-3">
|
||||
<div
|
||||
className="overflow-y-auto border-y border-[#e8eef7] px-4 sm:px-5"
|
||||
style={{ maxHeight: "min(58vh, 470px)" }}
|
||||
>
|
||||
<div className="space-y-4 py-4">
|
||||
{!data ? (
|
||||
<p className="text-sm text-slate-500">
|
||||
{t("hall.result.empty", { defaultValue: "暂无结果。" })}
|
||||
</p>
|
||||
) : (
|
||||
<>
|
||||
<div className="rounded-xl border border-[#e8eef7] bg-[#f8fbff] p-3 text-sm shadow-[0_4px_14px_rgba(15,23,42,0.04)]">
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle2 className="size-4 text-emerald-600" />
|
||||
<span className="font-semibold text-slate-800">
|
||||
{t("hall.result.orderNo", {
|
||||
defaultValue: "订单号",
|
||||
})}{" "}
|
||||
<span className="font-mono font-black text-[#0b3f96]">{data.order_no}</span>
|
||||
<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: "成功注项" })}
|
||||
</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: "失败注项" })}
|
||||
</p>
|
||||
<p className="mt-2 text-4xl font-black tabular-nums text-[#e5002c]">{totalFailure}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between gap-3 rounded-lg border border-[#dbe7ff] bg-[#f4f8ff] px-4 py-3">
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<span className="flex size-10 shrink-0 items-center justify-center rounded-full bg-[#0755c7] text-white">
|
||||
<ClipboardList className="size-5" />
|
||||
</span>
|
||||
<span className="truncate text-sm font-black text-[#0b3f96]">
|
||||
{t("hall.result.actual", { defaultValue: "实扣金额" })}
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-2 text-xs text-slate-500">
|
||||
{t("hall.result.draw", { defaultValue: "期号" })}{" "}
|
||||
<span className="font-mono font-semibold text-[#32518d]">{data.draw.draw_id}</span>
|
||||
<span className="shrink-0 font-mono text-base font-black tabular-nums text-[#0b3f96]">
|
||||
{formatMinorAsCurrency(data.summary.total_actual_deduct, currencyCode)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<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: "订单号" })}:{" "}
|
||||
<span className="font-mono font-black text-[#0b3f96]">{data.order_no}</span>
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-slate-500">
|
||||
{t("hall.result.status", { defaultValue: "注单状态" })}{" "}
|
||||
<span className="font-semibold text-[#32518d]">
|
||||
{t("ticketStatus.success", { defaultValue: "待开奖" })}
|
||||
<p>
|
||||
{t("hall.result.balanceAfter", { defaultValue: "剩余余额" })}:{" "}
|
||||
<span className="font-semibold text-slate-950">
|
||||
{formatMinorAsCurrency(data.balance_after, currencyCode)}
|
||||
</span>
|
||||
</p>
|
||||
<Separator className="my-3 bg-[#e8eef7]" />
|
||||
<ul className="space-y-1 text-xs tabular-nums text-slate-700">
|
||||
<li>
|
||||
{t("hall.result.successCount", { defaultValue: "成功数" })}{" "}
|
||||
<span className="font-semibold text-emerald-700">{totalSuccess}</span>
|
||||
</li>
|
||||
<li>
|
||||
{t("hall.result.failureCount", { defaultValue: "失败数" })}{" "}
|
||||
<span className="font-semibold text-rose-600">{totalFailure}</span>
|
||||
</li>
|
||||
<li>
|
||||
{t("hall.result.amount", { defaultValue: "金额" })}{" "}
|
||||
<span className="font-semibold text-slate-800">
|
||||
{formatMinorAsCurrency(data.summary.total_bet_amount, currencyCode)}
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
{t("hall.result.rebate", { defaultValue: "回水" })}{" "}
|
||||
<span className="font-semibold text-emerald-700">
|
||||
{formatMinorAsCurrency(data.summary.total_rebate_amount, currencyCode)}
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
{t("hall.result.actual", { defaultValue: "实扣" })}{" "}
|
||||
<span className="font-bold text-[#0b3f96]">
|
||||
{formatMinorAsCurrency(data.summary.total_actual_deduct, currencyCode)}
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
{t("hall.result.total", { defaultValue: "合计" })}{" "}
|
||||
<span className="font-bold text-[#0b3f96]">
|
||||
{formatMinorAsCurrency(data.summary.total_actual_deduct, currencyCode)}
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
{t("hall.result.balanceAfter", { defaultValue: "剩余余额" })}{" "}
|
||||
<span className="font-semibold text-slate-800">
|
||||
{formatMinorAsCurrency(data.balance_after, currencyCode)}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<p className="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
||||
<p className="text-sm font-black text-[#e5002c]">
|
||||
{t("hall.result.items", {
|
||||
defaultValue: "每一注成功/失败详情",
|
||||
defaultValue: "成功注项明细",
|
||||
})}
|
||||
</p>
|
||||
<ul className="space-y-2 text-sm">
|
||||
{data.items.map((item, index) => (
|
||||
<li
|
||||
key={`${item.ticket_no}-${index}`}
|
||||
className="rounded-xl border border-[#e6edf8] bg-white px-3 py-2 shadow-[0_4px_14px_rgba(15,23,42,0.04)]"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="font-mono text-xs text-slate-400">
|
||||
#{index + 1}
|
||||
</span>
|
||||
<span className="text-xs font-semibold text-emerald-700">
|
||||
{t("hall.result.success", { defaultValue: "成功" })}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-1 flex items-center justify-between gap-2">
|
||||
<span className="font-semibold text-[#32518d]">{playLabel(item.play_code, t)}</span>
|
||||
<span className="font-mono text-xs text-slate-500">
|
||||
{item.ticket_no}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-2 grid grid-cols-2 gap-x-2 gap-y-1 text-xs tabular-nums">
|
||||
<span className="text-slate-500">
|
||||
<div className="overflow-x-auto rounded-xl border border-[#dfe8f6]">
|
||||
<table className="min-w-[430px] w-full border-collapse text-xs">
|
||||
<thead className="bg-[#f4f7fd] text-[#304f86]">
|
||||
<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: "号码" })}
|
||||
</span>
|
||||
<span className="text-right font-mono font-black text-[#0b3f96]">{item.number}</span>
|
||||
<span className="text-slate-500">
|
||||
{t("hall.result.comboCount", { defaultValue: "组合数" })}
|
||||
</span>
|
||||
<span className="text-right">{item.combination_count}</span>
|
||||
<span className="text-slate-500">
|
||||
</th>
|
||||
<th className="border-r border-[#dfe8f6] px-2 py-2.5 text-center font-black">
|
||||
{t("orders.play", { defaultValue: "玩法" })}
|
||||
</th>
|
||||
<th className="px-2 py-2.5 text-center font-black">
|
||||
{t("hall.result.actualDeduct", { defaultValue: "实扣" })}
|
||||
</span>
|
||||
<span className="text-right font-semibold text-[#0b3f96]">
|
||||
{formatMinorAsCurrency(item.actual_deduct_amount, currencyCode)}
|
||||
</span>
|
||||
<span className="text-slate-500">
|
||||
{t("hall.result.estimatedMax", { defaultValue: "最坏赔付" })}
|
||||
</span>
|
||||
<span className="text-right">
|
||||
{formatMinorAsCurrency(item.estimated_max_payout, currencyCode)}
|
||||
</span>
|
||||
</div>
|
||||
{item.combination_count > 1 ? (
|
||||
<div className="mt-2 inline-flex items-center gap-1 rounded-full border border-[#dce7f7] bg-[#f8fbff] px-2 py-1 text-[11px] text-[#07459f]">
|
||||
<ChevronRight className="size-3" />
|
||||
{t("hall.result.comboHint", {
|
||||
defaultValue: "已按展开组合分摊",
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.items.map((item, index) => (
|
||||
<tr
|
||||
key={`${item.ticket_no}-${index}`}
|
||||
className="border-t border-[#e8eef7] bg-white"
|
||||
>
|
||||
<td className="border-r border-[#e8eef7] px-2 py-3 text-center font-black text-[#304f86]">
|
||||
{index + 1}
|
||||
</td>
|
||||
<td className="border-r border-[#e8eef7] px-2 py-3 text-center">
|
||||
<span className="block font-mono text-base font-black text-slate-950">
|
||||
{item.number}
|
||||
</span>
|
||||
<span className="block truncate text-[10px] text-slate-400">
|
||||
{item.ticket_no}
|
||||
</span>
|
||||
</td>
|
||||
<td className="border-r border-[#e8eef7] px-2 py-3 text-center font-semibold text-[#0755c7]">
|
||||
{playLabel(item.play_code, t)}
|
||||
</td>
|
||||
<td className="px-2 py-3 text-center font-black tabular-nums text-[#0b3f96]">
|
||||
{formatMinorAsCurrency(item.actual_deduct_amount, currencyCode)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</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: "本次提交没有失败注项。" })}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end border-t border-[#e8eef7] bg-[#f8fbff] p-4">
|
||||
<div className="grid grid-cols-2 gap-3 border-t border-[#e8eef7] bg-white p-4 sm:p-5">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="h-12 rounded-lg border-[#8dadf0] bg-white text-base font-black text-[#0b3f96] hover:bg-[#f4f8ff]"
|
||||
render={<Link href="/orders" />}
|
||||
>
|
||||
<ClipboardList className="size-5" />
|
||||
{t("hall.result.viewBets", { defaultValue: "查看注单" })}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => onOpenChange(false)}
|
||||
className="h-11 rounded-xl border-0 bg-[#07459f] px-6 text-sm font-bold text-white 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]"
|
||||
>
|
||||
{t("actions.close", { defaultValue: "关闭" })}
|
||||
<Ticket className="size-5" />
|
||||
{t("hall.result.continueBetting", { defaultValue: "继续下注" })}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { Bell } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { LanguageSwitcher } from "@/components/language-switcher";
|
||||
@@ -15,6 +16,7 @@ import { useHallDrawLive } from "@/features/hall/use-hall-draw-live";
|
||||
*/
|
||||
export function HallScreen() {
|
||||
const { t } = useTranslation("common");
|
||||
const { t: tp } = useTranslation("player");
|
||||
const drawLive = useHallDrawLive();
|
||||
|
||||
return (
|
||||
@@ -38,6 +40,12 @@ export function HallScreen() {
|
||||
showFlag={false}
|
||||
className="shrink-0 rounded-full border border-[#e4eaf4] bg-[#f8fafc]"
|
||||
/>
|
||||
<Link
|
||||
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: "规则" })}
|
||||
</Link>
|
||||
<button
|
||||
type="button"
|
||||
className="relative flex size-8 shrink-0 items-center justify-center rounded-full text-[#1d57b7] hover:bg-[#f4f7fb]"
|
||||
|
||||
Reference in New Issue
Block a user