diff --git a/src/pages/friend-battle-result.vue b/src/pages/friend-battle-result.vue index 3e3ac6c..6d09031 100644 --- a/src/pages/friend-battle-result.vue +++ b/src/pages/friend-battle-result.vue @@ -27,6 +27,8 @@ const data = ref({}); const showOverlay = ref(false); /** 自动关闭覆盖层的计时器 */ const overlayTimer = ref(null); +const overlayDelayTimer = ref(null); +let loadGeneration = 0; /** 控制经验条是否执行进场动画(弹窗关闭后才置 true,避免动画被遮挡) */ const showExpAnim = ref(false); @@ -175,9 +177,11 @@ function getMeleeAvatarBorderColor(rank) { // ---- 生命周期 ---- onLoad(async (options) => { + const currentLoadId = ++loadGeneration; if (!options.battleId) return; const result = await getBattleAPI(options.battleId); + if (currentLoadId !== loadGeneration) return; data.value = result; // 从 teams 各队伍的 players 中找到当前用户,解析经验进度条所需字段 @@ -219,7 +223,8 @@ onLoad(async (options) => { } // 数据加载完成后延迟 1 秒再显示激励弹窗,避免进场时画面太杂 - setTimeout(() => { + overlayDelayTimer.value = setTimeout(() => { + overlayDelayTimer.value = null; showOverlay.value = true; // 弹窗显示后 2.5 秒自动关闭 overlayTimer.value = setTimeout(() => { @@ -229,7 +234,15 @@ onLoad(async (options) => { }); onBeforeUnmount(() => { - if (overlayTimer.value) clearTimeout(overlayTimer.value); + loadGeneration += 1; + if (overlayDelayTimer.value) { + clearTimeout(overlayDelayTimer.value); + overlayDelayTimer.value = null; + } + if (overlayTimer.value) { + clearTimeout(overlayTimer.value); + overlayTimer.value = null; + } }); // ---- 方法 ---- diff --git a/src/pages/team-battle/index.vue b/src/pages/team-battle/index.vue index 32d3a19..e699b21 100644 --- a/src/pages/team-battle/index.vue +++ b/src/pages/team-battle/index.vue @@ -105,6 +105,8 @@ let restoreLoadingTimer = null; let pendingRoundAudio = false; // 一旦收到 BattleEnd,后续普通消息就不再进入队列。 let battleEnded = false; +let battleEndTaskRunning = false; +let resultNavigationPending = false; let skipNextRestoreOnShow = false; let pendingReturnSnapshot = null; const handledMessageKeys = new Set(); @@ -1094,39 +1096,70 @@ async function runNewRoundTask(task, runId) { pendingRoundAudio = true; } +function navigateToBattleResultOnce(matchId) { + const targetMatchId = matchId || battleId.value; + if (!targetMatchId || resultNavigationPending) return false; + + resultNavigationPending = true; + battleEnded = true; + restoreGeneration += 1; + if (pendingRestoreTimer) { + clearTimeout(pendingRestoreTimer); + pendingRestoreTimer = null; + } + hideRestoreLoading(); + + uni.redirectTo({ + url: `/pages/friend-battle-result?battleId=${targetMatchId}`, + fail: (err) => { + resultNavigationPending = false; + console.log("navigate to battle result failed:", err); + }, + }); + return true; +} + // 终局任务:播放结束语音后,根据状态跳结果页或返回上一页。 async function runBattleEndTask(task, runId) { - const battleInfo = task.message; - applyBattleBase(battleInfo); - battleEnded = true; - clearXRingStreaks(); - matchStatus.value = battleInfo.status; - if (battleInfo.status === 4) { - showRoundTip.value = true; - currentBluePoint.value = 0; - currentRedPoint.value = 0; + if (resultNavigationPending) { + notifyMatchAudioAck(task); + return; } - // 终局语音必须完整播完,再决定跳转或返回。 - await playAudioKeys("比赛结束", { interrupt: false, timeout: AUDIO_TIMEOUT_MAX }); - notifyMatchAudioAck(task); - if (!isQueueAlive(runId)) return; + battleEndTaskRunning = true; + try { + const battleInfo = task.message; + applyBattleBase(battleInfo); + battleEnded = true; + clearXRingStreaks(); + matchStatus.value = battleInfo.status; + if (battleInfo.status === 4) { + showRoundTip.value = true; + currentBluePoint.value = 0; + currentRedPoint.value = 0; + } - if (matchStatus.value === 2) { - uni.redirectTo({ - url: `/pages/friend-battle-result?battleId=${battleId.value}`, - }); - } else if (matchStatus.value === 4) { - const roomNumber = battleInfo.roomId || store.game.roomNumber || store.game.roomID; - setTimeout(() => { - if (roomNumber) { - uni.redirectTo({ - url: `/pages/battle-room?roomNumber=${encodeURIComponent(roomNumber)}&fromResult=1`, - }); - } else { - uni.navigateBack(); - } - }, BATTLE_CANCEL_RETURN_DELAY); + // 终局语音必须完整播完,再决定跳转或返回。 + await playAudioKeys("比赛结束", { interrupt: false, timeout: AUDIO_TIMEOUT_MAX }); + notifyMatchAudioAck(task); + if (!isQueueAlive(runId)) return; + + if (matchStatus.value === 2) { + navigateToBattleResultOnce(battleId.value); + } else if (matchStatus.value === 4) { + const roomNumber = battleInfo.roomId || store.game.roomNumber || store.game.roomID; + setTimeout(() => { + if (roomNumber) { + uni.redirectTo({ + url: `/pages/battle-room?roomNumber=${encodeURIComponent(roomNumber)}&fromResult=1`, + }); + } else { + uni.navigateBack(); + } + }, BATTLE_CANCEL_RETURN_DELAY); + } + } finally { + battleEndTaskRunning = false; } } @@ -1143,7 +1176,7 @@ async function runInvalidShotTask(task, runId) { // 回前台恢复入口:拉取服务端快照,处理结束态,然后继续消费增量队列。 async function restoreLatestBattle() { - if (!battleId.value) return; + if (!battleId.value || resultNavigationPending) return; const currentRestoreId = ++restoreGeneration; showRestoreLoading(); @@ -1175,9 +1208,8 @@ async function restoreLatestBattle() { if (result.status === 2) { clearXRingStreaks(); hideRestoreLoading(); - uni.redirectTo({ - url: `/pages/friend-battle-result?battleId=${result.matchId}`, - }); + battleEnded = true; + if (!battleEndTaskRunning) navigateToBattleResultOnce(result.matchId); return; } if (result.status === 4) { @@ -1284,6 +1316,8 @@ onLoad((options) => { restoreGeneration += 1; pendingRoundAudio = false; battleEnded = false; + battleEndTaskRunning = false; + resultNavigationPending = false; handledMessageKeys.clear(); handledMessageKeyOrder.length = 0; queuedMessageKeys.clear(); @@ -1328,6 +1362,8 @@ onMounted(async () => { // 离开页面时清理监听、停止音频,并让当前队列整体失效。 onBeforeUnmount(() => { + restoreGeneration += 1; + battleEndTaskRunning = false; uni.setKeepScreenOn({ keepScreenOn: false, });