feat: 增加 Jackpot 爆池实时弹层与奖池信息展示

This commit is contained in:
2026-05-18 15:08:29 +08:00
parent 418b446c09
commit 321b56e997
11 changed files with 379 additions and 0 deletions

View File

@@ -22,6 +22,7 @@ import {
} from "@/features/hall/hall-bet-rules";
import type { HallDrawLiveSnapshot } from "@/features/hall/use-hall-draw-live";
import { triggerWalletPollingAfterBet } from "@/hooks/use-wallet-polling";
import { getLotteryEcho } from "@/lib/lottery-echo";
import { getLotteryRequestLocale } from "@/lib/lottery-locale";
import { formatMinorAsCurrency, parseDecimalInputToMinor } from "@/lib/money";
import { cn } from "@/lib/utils";
@@ -63,6 +64,16 @@ type ClosedPlayCleanupData = {
cleanup_lines?: Array<{ client_line_no?: number; play_code?: string }>;
};
type PlayToggleWsEvent = {
play_code?: string;
enabled?: boolean;
action?: string;
};
type OddsUpdateWsEvent = {
message?: string;
};
type CellRiskState = "open" | "warning" | "sold_out";
type QuickFillState = Record<HallCategory, { favorites: string[]; history: string[] }>;
@@ -482,6 +493,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
);
const alertRows = display?.risk_pool_alerts ?? [];
const jackpot = display?.jackpot;
const currentQuickFill = quickFillState[activeCategory] ?? { favorites: [], history: [] };
const favorites = currentQuickFill.favorites;
const historyNumbers = currentQuickFill.history;
@@ -577,6 +589,79 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
});
};
const clearAmountsForPlay = useCallback((playCode: string): boolean => {
let removed = false;
setRows((current) =>
current.map((row) => {
const nextAmounts = { ...row.amounts };
let changed = false;
Object.keys(nextAmounts).forEach((amountKey) => {
const keyPlayCode = amountKey.split("@")[0];
if (keyPlayCode !== playCode || !nextAmounts[amountKey]) return;
nextAmounts[amountKey] = "";
changed = true;
removed = true;
});
return changed ? { ...row, amounts: nextAmounts } : row;
}),
);
return removed;
}, []);
useEffect(() => {
const echo = getLotteryEcho();
if (!echo) return;
const channel = echo.channel("lottery-hall");
const onPlayToggle = (evt: PlayToggleWsEvent) => {
void loadCatalog();
if (evt.enabled === false && typeof evt.play_code === "string") {
const removed = clearAmountsForPlay(evt.play_code);
setPreviewOpen(false);
setPreviewData(null);
toast.warning(
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: "玩法配置已更新,已刷新下注表格。",
}),
);
}
};
const onOddsUpdate = (evt: OddsUpdateWsEvent) => {
void loadCatalog();
setPreviewOpen(false);
setPreviewData(null);
toast.message(
evt.message ??
t("hall.playConfig.oddsUpdated", {
defaultValue: "赔率已更新,请重新预览注单。",
}),
);
};
channel.listen(".play.toggle", onPlayToggle);
channel.listen(".odds.update", onOddsUpdate);
return () => {
channel.stopListening(".play.toggle");
channel.stopListening(".odds.update");
};
}, [clearAmountsForPlay, loadCatalog, t]);
const collectEntries = useCallback((): DraftEntry[] => {
if (activeCategory === "JACKPOT") return [];
const entries: DraftEntry[] = [];
@@ -847,6 +932,29 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
</div>
</div>
{jackpot?.enabled ? (
<div className="rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 via-white to-[#f8fbff] px-4 py-3">
<div className="flex flex-wrap items-center justify-between gap-2">
<div>
<p className="text-[11px] font-black uppercase tracking-normal text-amber-700">
{t("results.jackpotLabel", { defaultValue: "Jackpot" })}
</p>
<p className="font-mono text-xl font-black tabular-nums text-[#07459f]">
{formatMinorAsCurrency(jackpot.current_amount_minor, jackpot.currency_code)}
</p>
</div>
{jackpot.draws_since_last_burst !== null ? (
<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}
</div>
</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">