feat: 增强开奖 API 的币种支持并优化钱包处理逻辑
更新 getDrawCurrent、getDrawResults 与 getDrawResultByNo 方法,新增币种参数支持,以适配玩家币种偏好。 优化 HallBettingGrid 及相关组件:支持币种切换时自动刷新钱包数据。 重构钱包处理逻辑,简化余额更新流程并提升用户体验。 新增会话过期相关多语言提示文案,并优化现有翻译内容,提升多语言环境下的提示清晰度。
This commit is contained in:
@@ -116,7 +116,7 @@ export function IframeBridge({ children }: { children: ReactNode }): ReactNode {
|
||||
console.log("[IframeBridge] Received initial token");
|
||||
setBearerToken(data.token);
|
||||
setPlayerBearerToken(data.token);
|
||||
notifyReady();
|
||||
// 勿再 notifyReady(),否则主站会重复 MAIN_INIT_TOKEN 导致消息刷屏
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
12
src/components/play-effective-ws-listener.tsx
Normal file
12
src/components/play-effective-ws-listener.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import { usePlayEffectiveWs } from "@/hooks/use-play-effective-ws";
|
||||
|
||||
/** 全局挂载:后台发布玩法/赔率/封顶后刷新 `play/effective` 订阅方。 */
|
||||
export function PlayEffectiveWsListener(): ReactNode {
|
||||
usePlayEffectiveWs();
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -5,8 +5,9 @@ import { useEffect, type ReactNode } from "react";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import { ErrorProvider } from "@/components/error-provider";
|
||||
import { IframeBridge } from "@/components/iframe-bridge";
|
||||
import { PlayEffectiveWsListener } from "@/components/play-effective-ws-listener";
|
||||
import { PlayerBalanceWsListener } from "@/components/player-balance-ws-listener";
|
||||
import { TokenRefreshIndicator } from "@/components/token-refresh-indicator";
|
||||
import { TokenSilentRefresh } from "@/components/token-silent-refresh";
|
||||
import "@/i18n";
|
||||
import { syncPreferredLanguage } from "@/i18n";
|
||||
|
||||
@@ -26,8 +27,9 @@ export function Providers({ children }: ProvidersProps): ReactNode {
|
||||
<IframeBridge>
|
||||
{children}
|
||||
<PlayerBalanceWsListener />
|
||||
{/* Token 续签指示器 - 显示在右下角 */}
|
||||
<TokenRefreshIndicator />
|
||||
<PlayEffectiveWsListener />
|
||||
{/* Token 静默续签(无 UI) */}
|
||||
<TokenSilentRefresh />
|
||||
</IframeBridge>
|
||||
</ErrorProvider>
|
||||
<Toaster />
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { RefreshCw, AlertCircle } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { useTokenRefresh } from "@/hooks/use-token-refresh";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ERROR_COLORS } from "@/stores/error-store";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
/**
|
||||
* Token 续签状态指示器组件
|
||||
*
|
||||
* 当 Token 即将过期或正在刷新时显示提示
|
||||
*/
|
||||
export function TokenRefreshIndicator(): React.ReactElement | null {
|
||||
const { isTokenExpiringSoon, getTokenRemainingTime, refreshToken } =
|
||||
useTokenRefresh();
|
||||
const { t } = useTranslation("player");
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
const [showWarning, setShowWarning] = useState(false);
|
||||
const [remainingSeconds, setRemainingSeconds] = useState<number | null>(null);
|
||||
|
||||
// 检测 Token 状态
|
||||
useEffect(() => {
|
||||
const checkToken = (): void => {
|
||||
const remaining = getTokenRemainingTime();
|
||||
|
||||
if (remaining > 0 && remaining < 120000) {
|
||||
// 2 分钟内显示
|
||||
setShowWarning(true);
|
||||
setRemainingSeconds(Math.floor(remaining / 1000));
|
||||
} else {
|
||||
setShowWarning(false);
|
||||
setRemainingSeconds(null);
|
||||
}
|
||||
};
|
||||
|
||||
checkToken();
|
||||
const interval = setInterval(checkToken, 10000); // 每 10 秒检查
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [getTokenRemainingTime, isTokenExpiringSoon]);
|
||||
|
||||
// 手动刷新
|
||||
const handleRefresh = async (): Promise<void> => {
|
||||
setIsRefreshing(true);
|
||||
try {
|
||||
await refreshToken();
|
||||
} finally {
|
||||
setIsRefreshing(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!showWarning) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isCritical = remainingSeconds !== null && remainingSeconds < 60;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"fixed bottom-4 right-4 z-50 flex items-center gap-3 rounded-lg px-4 py-3 shadow-lg",
|
||||
"animate-in slide-in-from-bottom-4 duration-300",
|
||||
)}
|
||||
style={{
|
||||
backgroundColor: isCritical
|
||||
? `${ERROR_COLORS.error}15`
|
||||
: `${ERROR_COLORS.warning}15`,
|
||||
border: `1px solid ${isCritical ? ERROR_COLORS.error : ERROR_COLORS.warning}`,
|
||||
}}
|
||||
>
|
||||
<AlertCircle
|
||||
className="size-5 shrink-0"
|
||||
style={{
|
||||
color: isCritical ? ERROR_COLORS.error : ERROR_COLORS.warning,
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<span
|
||||
className="text-sm font-medium"
|
||||
style={{
|
||||
color: isCritical ? ERROR_COLORS.error : ERROR_COLORS.warning,
|
||||
}}
|
||||
>
|
||||
{isCritical ? t("token.critical") : t("token.warning")}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{remainingSeconds !== null && (
|
||||
<>
|
||||
{t("token.remaining", {
|
||||
time: `${Math.floor(remainingSeconds / 60)}:${String(
|
||||
remainingSeconds % 60,
|
||||
).padStart(2, "0")}`,
|
||||
})}{" "}
|
||||
</>
|
||||
)}
|
||||
{t("token.autoRenewing")}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className={cn("ml-2 h-8 gap-1 border-current")}
|
||||
style={{
|
||||
color: isCritical ? ERROR_COLORS.error : ERROR_COLORS.warning,
|
||||
borderColor: isCritical ? ERROR_COLORS.error : ERROR_COLORS.warning,
|
||||
}}
|
||||
onClick={handleRefresh}
|
||||
disabled={isRefreshing}
|
||||
>
|
||||
<RefreshCw
|
||||
className={cn("size-3.5", isRefreshing && "animate-spin")}
|
||||
/>
|
||||
{t("token.renewNow")}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
11
src/components/token-silent-refresh.tsx
Normal file
11
src/components/token-silent-refresh.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { useTokenRefresh } from "@/hooks/use-token-refresh";
|
||||
|
||||
/**
|
||||
* 挂载 Token 自动续签逻辑,不展示任何 UI(产品要求静默续签)。
|
||||
*/
|
||||
export function TokenSilentRefresh(): null {
|
||||
useTokenRefresh();
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user