feat: 集成网络连接管理与降级轮询功能

- 在 PlayerAppShell 中引入 NetworkStatusBanner 组件以显示网络状态
- 在 HallBettingGrid 中实现下注后触发钱包轮询
- 在 HallWalletStrip 中添加网络连接状态管理与定期刷新逻辑
- 在 useHallDrawLive 中集成 WebSocket 连接状态与降级轮询机制,确保在断开时自动切换到轮询模式
This commit is contained in:
2026-05-13 14:44:58 +08:00
parent 377e03e167
commit 1e7a06dc86
9 changed files with 974 additions and 16 deletions

View File

@@ -0,0 +1,148 @@
"use client";
import { create } from "zustand";
export type NetworkMode = "websocket" | "polling" | "offline";
export type NetworkConnectionState = {
/** 当前网络模式WebSocket 正常 / 降级轮询 / 离线 */
mode: NetworkMode;
/** WebSocket 是否已连接 */
isWebSocketConnected: boolean;
/** 是否正在尝试重连 */
isReconnecting: boolean;
/** 重连尝试次数 */
reconnectAttempts: number;
/** 上次连接成功时间 */
lastConnectedAt: number | null;
/** 上次断开连接时间 */
lastDisconnectedAt: number | null;
/** 当前活跃的画作数据轮询计时器ID */
drawPollingIntervalId: number | null;
/** 当前活跃的钱包余额轮询计时器ID */
walletPollingIntervalId: number | null;
/** 钱包余额轮询过期时间戳用于bet后的限时轮询 */
walletPollingExpiryAt: number | null;
// Actions
setMode: (mode: NetworkMode) => void;
setWebSocketConnected: (connected: boolean) => void;
setReconnecting: (reconnecting: boolean) => void;
incrementReconnectAttempts: () => void;
resetReconnectAttempts: () => void;
setLastConnectedAt: (timestamp: number | null) => void;
setLastDisconnectedAt: (timestamp: number | null) => void;
setDrawPollingIntervalId: (id: number | null) => void;
setWalletPollingIntervalId: (id: number | null) => void;
setWalletPollingExpiryAt: (timestamp: number | null) => void;
/** 切换到降级模式WebSocket断开时调用 */
switchToPollingMode: () => void;
/** 切换回WebSocket模式WebSocket重连成功时调用 */
switchToWebSocketMode: () => void;
/** 清除所有轮询计时器 */
clearAllPollingIntervals: () => void;
/** 停止钱包轮询 */
clearWalletPolling: () => void;
/** 停止画作数据轮询 */
clearDrawPolling: () => void;
};
export const useNetworkConnectionStore = create<NetworkConnectionState>(
(set, get) => ({
mode: "websocket",
isWebSocketConnected: false,
isReconnecting: false,
reconnectAttempts: 0,
lastConnectedAt: null,
lastDisconnectedAt: null,
drawPollingIntervalId: null,
walletPollingIntervalId: null,
walletPollingExpiryAt: null,
setMode: (mode) => set({ mode }),
setWebSocketConnected: (connected) =>
set({ isWebSocketConnected: connected }),
setReconnecting: (reconnecting) => set({ isReconnecting: reconnecting }),
incrementReconnectAttempts: () =>
set((state) => ({ reconnectAttempts: state.reconnectAttempts + 1 })),
resetReconnectAttempts: () => set({ reconnectAttempts: 0 }),
setLastConnectedAt: (timestamp) => set({ lastConnectedAt: timestamp }),
setLastDisconnectedAt: (timestamp) =>
set({ lastDisconnectedAt: timestamp }),
setDrawPollingIntervalId: (id) => set({ drawPollingIntervalId: id }),
setWalletPollingIntervalId: (id) => set({ walletPollingIntervalId: id }),
setWalletPollingExpiryAt: (timestamp) =>
set({ walletPollingExpiryAt: timestamp }),
switchToPollingMode: () =>
set({
mode: "polling",
isWebSocketConnected: false,
lastDisconnectedAt: Date.now(),
}),
switchToWebSocketMode: () => {
const { drawPollingIntervalId, walletPollingIntervalId } = get();
// 清除轮询计时器
if (drawPollingIntervalId) {
window.clearInterval(drawPollingIntervalId);
}
if (walletPollingIntervalId) {
window.clearInterval(walletPollingIntervalId);
}
set({
mode: "websocket",
isWebSocketConnected: true,
lastConnectedAt: Date.now(),
drawPollingIntervalId: null,
walletPollingIntervalId: null,
walletPollingExpiryAt: null,
reconnectAttempts: 0,
isReconnecting: false,
});
},
clearAllPollingIntervals: () => {
const { drawPollingIntervalId, walletPollingIntervalId } = get();
if (drawPollingIntervalId) {
window.clearInterval(drawPollingIntervalId);
}
if (walletPollingIntervalId) {
window.clearInterval(walletPollingIntervalId);
}
set({
drawPollingIntervalId: null,
walletPollingIntervalId: null,
walletPollingExpiryAt: null,
});
},
clearWalletPolling: () => {
const { walletPollingIntervalId } = get();
if (walletPollingIntervalId) {
window.clearInterval(walletPollingIntervalId);
}
set({
walletPollingIntervalId: null,
walletPollingExpiryAt: null,
});
},
clearDrawPolling: () => {
const { drawPollingIntervalId } = get();
if (drawPollingIntervalId) {
window.clearInterval(drawPollingIntervalId);
}
set({ drawPollingIntervalId: null });
},
}),
);