From 75b6ef7809c21e12a8e5593d77a9cfb89d2a7f4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=96=B9=E6=88=90?= Date: Sun, 4 Jan 2026 16:29:57 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=20WebSocket=20?= =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E5=92=8C=E6=B8=B8=E6=88=8F=E9=87=8D=E8=BF=9E?= =?UTF-8?q?=E9=80=BB=E8=BE=91=EF=BC=8C=E5=B9=B6=E6=94=B9=E8=BF=9B=E5=9B=9E?= =?UTF-8?q?=E5=90=88=E8=AE=A1=E6=97=B6=E5=99=A8=E5=90=8C=E6=AD=A5=E5=8F=8A?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E6=93=8D=E4=BD=9C=E5=8F=8D=E9=A6=88=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages-game/game/minesweeper/play.vue | 31 +++++++++++++++++++++------- utils/nakamaManager.js | 20 ++++++++++++++++++ 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/pages-game/game/minesweeper/play.vue b/pages-game/game/minesweeper/play.vue index c4f3a7e..f4f813d 100644 --- a/pages-game/game/minesweeper/play.vue +++ b/pages-game/game/minesweeper/play.vue @@ -615,6 +615,9 @@ export default { // 自动重连 try { + if (this._isReconnecting) return; + this._isReconnecting = true; + console.log('[重连] 开始重新连接...'); const session = await nakamaManager.authenticateWithGameToken(this.gameToken, this.stableUserId); this.myUserId = session.user_id; @@ -636,6 +639,8 @@ export default { } catch (e) { console.error('[重连] 失败:', e); this.addLog('system', `❌ 重连失败: ${e.message}`); + } finally { + this._isReconnecting = false; } } }); @@ -666,6 +671,8 @@ export default { if (opCode === 1) { this.gameState = data; + const limit = data.turnDuration || 15; + this.resetTurnTimer(limit, limit); this.addLog('system', '战局开始,准备翻格!'); } else if (opCode === 2) { // 状态更新 - 检测 HP 变化触发特效 @@ -691,10 +698,13 @@ export default { } }); } - this.gameState = data; - // 收到状态更新时重置计时器(处理重连场景) + // 收到状态更新时重置计时器(使用服务器时间校准) if (data.gameStarted) { - this.resetTurnTimer(); + const serverTime = data.serverTime || Math.floor(Date.now() / 1000); + const elapsed = serverTime - (data.lastMoveTimestamp || serverTime); + const limit = data.turnDuration || 15; + const remaining = Math.max(0, limit - elapsed); + this.resetTurnTimer(remaining, limit); } } else if (opCode === 5) { this.handleEvent(data); @@ -817,9 +827,15 @@ export default { handleCellClick(idx) { // 前置条件检查 if (this.isSpectator) return; - if (!this.gameState?.gameStarted) return; + if (!this.gameState?.gameStarted) { + uni.showToast({ title: '游戏尚未开始', icon: 'none' }); + return; + } if (this.showResultModal || this.showSettlement) return; - if (!this.isMyTurn) return; + if (!this.isMyTurn) { + uni.showToast({ title: '等待对方行动...', icon: 'none' }); + return; + } if (this.gameState.grid[idx].revealed) return; // 发送移动指令 @@ -832,8 +848,9 @@ export default { closeResultModal() { this.showResultModal = false; }, - resetTurnTimer() { - this.turnTimer = 15; + resetTurnTimer(initialTime, limit) { + const turnLimit = limit || (this.gameState?.turnDuration) || 15; + this.turnTimer = Math.floor(initialTime !== undefined ? initialTime : turnLimit); clearInterval(this.turnInterval); this.turnInterval = setInterval(() => { if (this.turnTimer > 0) this.turnTimer--; diff --git a/utils/nakamaManager.js b/utils/nakamaManager.js index d26c336..39d0c3b 100644 --- a/utils/nakamaManager.js +++ b/utils/nakamaManager.js @@ -15,6 +15,7 @@ class NakamaManager { this.gameToken = null; this.socketTask = null; this.isConnected = false; + this.isConnecting = false; // 正在连接标志位 // 消息 ID 和待处理的 Promise this.nextCid = 1; @@ -132,7 +133,24 @@ class NakamaManager { * 建立 WebSocket 连接 */ _connectWebSocket() { + if (this.isConnecting) { + console.log('[Nakama] Already connecting, skipping...'); + return Promise.resolve(); + } + return new Promise((resolve, reject) => { + // 确保清理旧连接 + if (this.socketTask) { + console.log('[Nakama] Closing existing socket before new connection'); + try { + this.socketTask.close(); + } catch (e) { + console.warn('[Nakama] Error closing old socket:', e); + } + this.socketTask = null; + } + + this.isConnecting = true; const scheme = this.useSSL ? 'wss://' : 'ws://'; const portSuffix = (this.useSSL && this.port === '443') || (!this.useSSL && this.port === '80') ? '' : `:${this.port}`; const wsUrl = `${scheme}${this.host}${portSuffix}/ws?lang=en&status=true&token=${encodeURIComponent(this.session.token)}`; @@ -151,6 +169,7 @@ class NakamaManager { this.socketTask.onOpen(() => { clearTimeout(connectTimeout); this.isConnected = true; + this.isConnecting = false; console.log('[Nakama] WebSocket connected'); this._startHeartbeat(); resolve(); @@ -169,6 +188,7 @@ class NakamaManager { clearTimeout(connectTimeout); console.error('[Nakama] WebSocket error:', err); this.isConnected = false; + this.isConnecting = false; if (this.listeners.onerror) { this.listeners.onerror(err); }