feat: 优化大厅 Jackpot 参与提示与玩家页头布局

This commit is contained in:
2026-05-22 14:34:11 +08:00
parent 0cd85ae287
commit b61a2ab07b
7 changed files with 104 additions and 34 deletions

View File

@@ -39,12 +39,12 @@ export function PlayerPanel({
<div className={cn("mx-auto w-full max-w-[480px]", containerClassName)}>
<section
className={cn(
"overflow-hidden bg-white text-slate-900",
playerPageInset,
"bg-white text-slate-900 min-h-screen",
"px-3 pb-6",
className,
)}
>
<header className={playerPageHeader}>
<header className={cn(playerPageHeader, "sticky top-0 z-50 bg-white/95 backdrop-blur pt-2 pb-2 -mx-3 px-3")}>
<div className="flex min-w-0 justify-start">
<Link
href={backHref}

View File

@@ -31,6 +31,7 @@ type HallBetPreviewDialogProps = {
currencyCode: string;
data: TicketPreviewData | null;
placing: boolean;
jackpotEnabled?: boolean;
/** 界面 §4.2:封盘后禁止提交,主按钮文案为「已封盘」 */
allowSubmit?: boolean;
onConfirmPlace: () => void;
@@ -107,6 +108,7 @@ export function HallBetPreviewDialog({
currencyCode,
data,
placing,
jackpotEnabled = false,
allowSubmit = true,
onConfirmPlace,
}: HallBetPreviewDialogProps) {
@@ -188,6 +190,16 @@ export function HallBetPreviewDialog({
</span>
</div>
{jackpotEnabled ? (
<Alert className="border-[#d9ecff] bg-[#f4f9ff] text-[#0b3f96]">
<CheckCircle2 className="size-4" />
<AlertTitle>{t("hall.jackpotParticipation.title")}</AlertTitle>
<AlertDescription className="text-xs leading-relaxed">
{t("hall.jackpotParticipation.previewDescription")}
</AlertDescription>
</Alert>
) : null}
<div className="overflow-x-auto rounded-xl border border-[#dfe8f6]">
<table className="min-w-[640px] w-full border-collapse text-xs">
<thead className="bg-[#f4f7fd] text-[#304f86]">

View File

@@ -21,6 +21,7 @@ type HallBetResultDialogProps = {
onOpenChange: (open: boolean) => void;
currencyCode: string;
data: TicketPlaceData | null;
jackpotEnabled?: boolean;
};
export function HallBetResultDialog({
@@ -28,6 +29,7 @@ export function HallBetResultDialog({
onOpenChange,
currencyCode,
data,
jackpotEnabled = false,
}: HallBetResultDialogProps) {
const { t } = useTranslation("player");
@@ -119,6 +121,13 @@ export function HallBetResultDialog({
</p>
</div>
{jackpotEnabled ? (
<div className="rounded-lg border border-[#d9ecff] bg-[#f4f9ff] px-3 py-3 text-sm leading-relaxed text-[#0b3f96]">
<p className="font-black">{t("hall.jackpotParticipation.title")}</p>
<p className="mt-1 text-xs">{t("hall.jackpotParticipation.resultDescription")}</p>
</div>
) : null}
<div className="space-y-2">
<p className="text-sm font-black text-[#e5002c]">
{t("hall.result.items")}

View File

@@ -84,7 +84,6 @@ const categoryTabs: { value: HallCategory; label: string }[] = [
{ value: "D2", label: "2D" },
{ value: "D3", label: "3D" },
{ value: "D4", label: "4D" },
{ value: "JACKPOT", label: "Jackpot" },
];
const D2_PLAY_ORDER = ["pos_2a", "pos_2b", "pos_2c", "pos_2abc"] as const;
@@ -496,8 +495,25 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
const currentQuickFill = quickFillState[activeCategory] ?? { favorites: [], history: [] };
const favorites = currentQuickFill.favorites;
const historyNumbers = currentQuickFill.history;
const jackpotPanelCopy = !jackpot?.enabled
? {
title: t("hall.jackpotPanel.disabledTitle"),
subtitle: t("hall.jackpotPanel.disabledSubtitle"),
description: t("hall.jackpotPanel.disabledDescription"),
}
: isBettable
? {
title: t("hall.jackpotPanel.infoTitle"),
subtitle: t("hall.jackpotPanel.infoSubtitle"),
description: t("hall.jackpotPanel.infoDescription"),
}
: {
title: t("hall.jackpotPanel.closedInfoTitle"),
subtitle: t("hall.jackpotPanel.closedInfoSubtitle"),
description: t("hall.jackpotPanel.closedInfoDescription"),
};
const tableDisabled = activeCategory === "JACKPOT" || !isBettable || catalogState.kind !== "ok";
const tableDisabled = !isBettable || catalogState.kind !== "ok";
const sealedBetUi = Boolean(display && isHallSealedCountdownUi(display.status));
const numberPlaceholder = activeCategory === "D2" ? "00" : activeCategory === "D3" ? "000" : "0000";
@@ -888,7 +904,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
<>
<section className="space-y-3" aria-label={t("hall.aria")}>
<div className="rounded-xl border border-[#e8eef7] bg-white p-1 shadow-[0_6px_18px_rgba(30,64,175,0.06)]">
<div className="grid grid-cols-4 gap-1">
<div className="grid grid-cols-3 gap-1">
{categoryTabs.map((tab) => {
const active = activeCategory === tab.value;
return (
@@ -941,33 +957,18 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
</div>
) : null}
{activeCategory === "JACKPOT" ? (
<div className="rounded-xl border border-[#edf1f7] bg-[#f7f9fc] p-7 text-center text-slate-500">
<div className="mx-auto flex size-14 items-center justify-center rounded-full bg-slate-200 text-slate-600">
<Ticket className="size-7" aria-hidden />
<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="mt-0.5 text-xs leading-5 text-slate-500">
{t("hall.quickFill.description")}
</p>
</div>
<p className="mt-3 text-lg font-bold text-slate-900">
{t("hall.closed.title")}
</p>
<p className="mt-1 text-xs">{t("hall.closed.subtitle")}</p>
<div className="mt-5 rounded-lg border border-[#cbdcf7] bg-white px-3 py-3 text-left text-xs text-[#315a9f]">
{t("hall.closed.description")}
</div>
</div>
) : (
<>
<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="mt-0.5 text-xs leading-5 text-slate-500">
{t("hall.quickFill.description")}
</p>
</div>
<div className="flex shrink-0 items-center gap-1.5">
{activeRow?.number ? (
<div className="flex shrink-0 items-center gap-1.5">
{activeRow?.number ? (
<Button
type="button"
variant="ghost"
@@ -1275,8 +1276,6 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
? t("hall.table.insufficientBalance")
: t("hall.table.submitBet")}
</Button>
</>
)}
</section>
<HallBetPreviewDialog
@@ -1288,6 +1287,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
currencyCode={currencyCode}
data={previewData}
placing={placeLoading}
jackpotEnabled={Boolean(jackpot?.enabled)}
allowSubmit={isBettable && availableMinor >= debouncedSummary.actual}
onConfirmPlace={() => void handlePlace()}
/>
@@ -1300,6 +1300,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
}}
currencyCode={currencyCode}
data={resultData}
jackpotEnabled={Boolean(jackpot?.enabled)}
/>
</>
);

View File

@@ -135,6 +135,22 @@
"subtitle": "This issue is now closed.",
"description": "The betting window has closed. Please wait for the next issue to place your bets."
},
"jackpotPanel": {
"disabledTitle": "Unavailable",
"disabledSubtitle": "Jackpot is currently disabled.",
"disabledDescription": "The admin has turned off Jackpot for now. This tab remains visible for information only.",
"infoTitle": "Pool Information",
"infoSubtitle": "Jackpot is currently enabled.",
"infoDescription": "Jackpot is not a standalone play. Eligible tickets placed in the active plays will join the pool automatically based on admin rules.",
"closedInfoTitle": "Pool Information",
"closedInfoSubtitle": "Jackpot is currently enabled.",
"closedInfoDescription": "Jackpot uses automatic participation for eligible tickets. This issue is closed, so please wait for the next issue."
},
"jackpotParticipation": {
"title": "Automatic Jackpot Participation",
"previewDescription": "If this ticket is submitted successfully and meets the configured rules, it will join the Jackpot pool automatically. No separate Jackpot bet is needed.",
"resultDescription": "The successful ticket lines in this order will join the Jackpot pool automatically under the configured rules. If a first-prize hit later triggers the pool, it will settle together with the normal payout."
},
"boxMode": {
"iboxTitle": "iBox",
"iboxDesc": "Divide all by amount",

View File

@@ -135,6 +135,22 @@
"subtitle": "यो इश्यू बन्द भएको छ।",
"description": "बेटिङ समय बन्द भएको छ। अर्को इश्यू पर्खेर बेट राख्नुहोस्।"
},
"jackpotPanel": {
"disabledTitle": "बन्द गरिएको छ",
"disabledSubtitle": "Jackpot अहिले सक्षम छैन।",
"disabledDescription": "एडमिनले Jackpot बन्द गरेको छ। यो ट्याब अहिले जानकारीका लागि मात्र देखाइन्छ।",
"infoTitle": "पूल जानकारी",
"infoSubtitle": "Jackpot अहिले सक्षम छ।",
"infoDescription": "Jackpot छुट्टै बेट होइन। उपलब्ध खेलमा गरिएको योग्य टिकटहरू एडमिन नियमअनुसार स्वतः Jackpot पूलमा सहभागी हुन्छन्।",
"closedInfoTitle": "पूल जानकारी",
"closedInfoSubtitle": "Jackpot अहिले सक्षम छ।",
"closedInfoDescription": "योग्य टिकटहरू स्वतः Jackpot मा सहभागी हुन्छन्। यो इश्यू बन्द भइसकेको छ, त्यसैले अर्को इश्यू पर्खनुहोस्।"
},
"jackpotParticipation": {
"title": "Jackpot स्वतः सहभागिता",
"previewDescription": "यदि यो टिकट सफलतापूर्वक पेश भयो र नियम पूरा गर्यो भने, यो स्वतः Jackpot पूलमा सहभागी हुनेछ। छुट्टै Jackpot बेट आवश्यक छैन।",
"resultDescription": "यस अर्डरका सफल टिकटहरू कन्फिगर गरिएको नियमअनुसार स्वतः Jackpot पूलमा सहभागी हुनेछन्। पछि पहिलो पुरस्कार र पूल ट्रिगर सर्त पूरा भएमा, नियमित payout सँगै सेटल हुनेछ।"
},
"boxMode": {
"iboxTitle": "iBox",
"iboxDesc": "रकम सबैमा बाँड्नुहोस्",

View File

@@ -135,6 +135,22 @@
"subtitle": "当前期已停止接收注单。",
"description": "下注窗口已关闭,请等待下一期再下注。"
},
"jackpotPanel": {
"disabledTitle": "已关闭",
"disabledSubtitle": "Jackpot 当前未启用。",
"disabledDescription": "后台已关闭 Jackpot 功能,当前仅保留展示页签。",
"infoTitle": "奖池信息",
"infoSubtitle": "Jackpot 当前已启用。",
"infoDescription": "Jackpot 不是单独下注玩法。玩家在开放玩法中提交有效注单后,将按后台规则自动参与奖池。",
"closedInfoTitle": "奖池信息",
"closedInfoSubtitle": "Jackpot 当前已启用。",
"closedInfoDescription": "Jackpot 采用有效注单自动参与机制。本期下注窗口已关闭,请等待下一期开放后参与。"
},
"jackpotParticipation": {
"title": "Jackpot 自动参与",
"previewDescription": "若本单成功提交且满足后台规则,系统会自动按有效注单参与 Jackpot 奖池,无需单独再下一笔。",
"resultDescription": "本次成功注项将按后台规则自动参与 Jackpot 奖池,后续若命中头奖并满足触发条件,将与常规派彩一并结算。"
},
"boxMode": {
"iboxTitle": "iBox",
"iboxDesc": "按金额分摊全部组合",