feat: 集成玩家余额 WebSocket 监听并增强风控预警处理

新增 PlayerBalanceWsListener,用于处理玩家余额的实时更新。
引入 RiskWarningWsEvent 类型,并更新 HallBettingGrid 以支持实时风控预警处理。
增强 cellRiskState 方法,新增 warning 状态支持,提升风控管理能力。
更新英文、尼泊尔语及中文翻译,新增玩家余额更新相关文案。
This commit is contained in:
2026-05-26 17:13:49 +08:00
parent ab81da3199
commit adae4a0be1
8 changed files with 158 additions and 0 deletions

View File

@@ -91,6 +91,14 @@ type RiskSoldOutWsEvent = {
normalized_number?: string;
};
type RiskWarningWsEvent = {
draw_id?: number;
draw_no?: string;
normalized_number?: string;
usage_ratio?: number;
usage_percent?: number;
};
type CellRiskState = "open" | "warning" | "sold_out";
type QuickFillState = Record<HallCategory, { favorites: string[]; history: string[] }>;
@@ -394,6 +402,7 @@ function cellRiskState(
category: Exclude<HallCategory, "JACKPOT">,
alertRows: DrawCurrentRiskPoolAlert[] | undefined,
liveSoldOutNumbers: ReadonlySet<string>,
liveWarningNumbers: ReadonlySet<string>,
digitSlot?: number,
): CellRiskState {
const normalizedRow = rowNumber.trim().toUpperCase();
@@ -403,6 +412,10 @@ function cellRiskState(
return "sold_out";
}
if (liveWarningNumbers.has(normalizedRow)) {
return "warning";
}
const alerts = alertRows ?? [];
if (alerts.length === 0) return "open";
@@ -444,6 +457,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
const [resultData, setResultData] = useState<TicketPlaceData | null>(null);
const [quickFillState, setQuickFillState] = useState<QuickFillState>(() => loadQuickFillState());
const [liveSoldOutNumbers, setLiveSoldOutNumbers] = useState<Set<string>>(() => new Set());
const [liveWarningNumbers, setLiveWarningNumbers] = useState<Set<string>>(() => new Set());
const [debouncedSummary, setDebouncedSummary] = useState({ bet: 0, rebate: 0, actual: 0 });
const holdFavoriteRef = useRef<{ timer: number | null; number: string | null; longPress: boolean }>({
timer: null,
@@ -553,6 +567,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
useEffect(() => {
setLiveSoldOutNumbers(new Set());
setLiveWarningNumbers(new Set());
}, [drawNo]);
const alertRows = display?.risk_pool_alerts ?? [];
@@ -755,17 +770,37 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
next.add(normalized);
return next;
});
setLiveWarningNumbers((prev) => {
const next = new Set(prev);
next.delete(normalized);
return next;
});
void reloadDraw();
};
const onRiskWarning = (evt: RiskWarningWsEvent) => {
const normalized = evt.normalized_number?.trim().toUpperCase();
if (!normalized) return;
if (drawNo !== null && evt.draw_no !== undefined && evt.draw_no !== drawNo) {
return;
}
setLiveWarningNumbers((prev) => {
const next = new Set(prev);
next.add(normalized);
return next;
});
};
channel.listen(".play.toggle", onPlayToggle);
channel.listen(".odds.update", onOddsUpdate);
channel.listen(".risk.sold_out", onRiskSoldOut);
channel.listen(".risk.warning", onRiskWarning);
return () => {
channel.stopListening(".play.toggle");
channel.stopListening(".odds.update");
channel.stopListening(".risk.sold_out");
channel.stopListening(".risk.warning");
};
}, [clearAmountsForPlay, drawNo, loadCatalog, reloadDraw, t]);
@@ -1378,6 +1413,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
activeCategory as Exclude<HallCategory, "JACKPOT">,
alertRows,
liveSoldOutNumbers,
liveWarningNumbers,
column.digitSlot,
);
const disabled = tableDisabled || status === "sold_out" || (play.config !== null && !play.config.is_enabled);