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

@@ -0,0 +1,80 @@
"use client";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { getLotteryEcho } from "@/lib/lottery-echo";
import { useActivePlayerCurrency } from "@/hooks/use-active-player-currency";
import { usePlayerSessionStore } from "@/stores/player-session-store";
type BalanceUpdateWsEvent = {
player_id?: number;
currency_code?: string;
balance_minor?: number;
change_minor?: number;
change_formatted?: string;
reason?: string;
};
const REASON_I18N_KEY: Record<string, string> = {
transfer_in: "wallet.wsReason.transferIn",
transfer_out: "wallet.wsReason.transferOut",
bet: "wallet.wsReason.bet",
prize: "wallet.wsReason.prize",
refund: "wallet.wsReason.refund",
};
/**
* 订阅 `player.{id}` 私有频道的 `balance.update`,刷新余额并 Toast。
*/
export function usePlayerBalanceWs(): void {
const { t } = useTranslation("player");
const playerId = usePlayerSessionStore((state) => state.profile?.id);
const bearerToken = usePlayerSessionStore((state) => state.bearerToken);
const { activeCurrency } = useActivePlayerCurrency();
useEffect(() => {
if (!playerId || !bearerToken) {
return;
}
const echo = getLotteryEcho();
if (!echo) {
return;
}
const channelName = `player.${playerId}`;
const channel = echo.channel(channelName);
const onBalanceUpdate = (evt: BalanceUpdateWsEvent): void => {
const currency = evt.currency_code?.trim().toUpperCase();
if (currency && currency !== activeCurrency.toUpperCase()) {
return;
}
window.dispatchEvent(new Event("lottery-wallet-refresh"));
const changeLabel =
typeof evt.change_formatted === "string" && evt.change_formatted !== ""
? evt.change_formatted
: evt.change_minor != null
? String(evt.change_minor)
: "";
const reasonKey =
evt.reason && REASON_I18N_KEY[evt.reason]
? REASON_I18N_KEY[evt.reason]
: "wallet.wsReason.unknown";
const reasonLabel = t(reasonKey);
toast.message(t("wallet.wsBalanceUpdated", { change: changeLabel, reason: reasonLabel }));
};
channel.listen(".balance.update", onBalanceUpdate);
return () => {
channel.stopListening(".balance.update");
};
}, [activeCurrency, bearerToken, playerId, t]);
}