From a79486ad50f4b5231edfe291c3c82346d21007da Mon Sep 17 00:00:00 2001 From: chenlimao Date: Tue, 12 May 2026 09:54:07 +0800 Subject: [PATCH 1/4] =?UTF-8?q?fix=EF=BC=9A=E6=88=91=E7=9A=84=E6=88=90?= =?UTF-8?q?=E9=95=BF=E8=84=9A=E5=8D=B0=E9=A1=B5=E9=9D=A2=E5=B1=95=E7=A4=BA?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/BattleHeader.vue | 51 ++++++++++++++++++++------------- src/pages/my-growth.vue | 4 +-- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/components/BattleHeader.vue b/src/components/BattleHeader.vue index b7b3716..b12d84f 100644 --- a/src/components/BattleHeader.vue +++ b/src/components/BattleHeader.vue @@ -80,28 +80,35 @@ defineProps({ /> - + + + - - - {{ player.name }} + + + + {{ player.name }} + - + @@ -144,17 +151,21 @@ defineProps({ justify-content: center; } .players-melee { - display: flex; height: 80px; width: 100%; - overflow-x: auto; } .players-melee::-webkit-scrollbar { width: 0; height: 0; color: transparent; } -.players-melee > view { +/* 小程序 scroll-view 不支持直接 flex,通过内层 wrapper 承载横向排列 */ +.players-melee-inner { + display: flex; + height: 100%; + flex-wrap: nowrap; +} +.players-melee-inner > view { display: flex; flex-direction: column; align-items: center; diff --git a/src/pages/my-growth.vue b/src/pages/my-growth.vue index 56a9719..5a2e3b8 100644 --- a/src/pages/my-growth.vue +++ b/src/pages/my-growth.vue @@ -115,7 +115,7 @@ onLoad((options) => { :blueTeam="item.teams[1] ? item.teams[1].players : []" :redTeam="item.teams[2] ? item.teams[2].players : []" :winner="item.winTeam" - :showRank="item.teams[0]" + :showRank="!!item.teams[0]" :showHeader="false" /> @@ -138,7 +138,7 @@ onLoad((options) => { :blueTeam="item.teams[1] ? item.teams[1].players : []" :redTeam="item.teams[2] ? item.teams[2].players : []" :winner="item.winTeam" - :showRank="item.teams[0]" + :showRank="!!item.teams[0]" :showHeader="false" /> From b7fdf97156522fb05070193c0d92fd672ca49e8f Mon Sep 17 00:00:00 2001 From: chenlimao Date: Tue, 12 May 2026 10:36:27 +0800 Subject: [PATCH 2/4] =?UTF-8?q?fix=EF=BC=9A=E6=8E=92=E4=BD=8D=E8=B5=9B?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=E9=A1=B5=E9=9D=A2=E9=80=BB=E8=BE=91=E4=BC=98?= =?UTF-8?q?=E5=8C=96=EF=BC=9A=E5=A2=9E=E5=8A=A0=E8=B6=85=E6=97=B6=E5=88=A4?= =?UTF-8?q?=E6=96=AD&websocket=E4=B8=A2=E5=A4=B1=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/match-page.vue | 82 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 4 deletions(-) diff --git a/src/pages/match-page.vue b/src/pages/match-page.vue index c1c1102..05358f2 100644 --- a/src/pages/match-page.vue +++ b/src/pages/match-page.vue @@ -3,22 +3,90 @@ import { ref, onMounted, onBeforeUnmount } from "vue"; import { onLoad, onShow, onHide } from "@dcloudio/uni-app"; import Container from "@/components/Container.vue"; import Matching from "@/components/Matching.vue"; -import { matchGameAPI } from "@/apis"; +import { matchGameAPI, getBattleAPI } from "@/apis"; import { MESSAGETYPESV2 } from "@/constants"; const gameType = ref(0); const teamSize = ref(0); const onComplete = ref(null); +/** 匹配超时计时器,用于检测 WS 消息丢失或真正超时 */ +const matchTimeoutTimer = ref(null); + +/** 匹配超时阈值(ms),后端设置 15s 匹配,前端预留 5s 冗余 */ +const MATCH_TIMEOUT_MS = 20 * 1000; + +/** 清除超时计时器 */ +const clearMatchTimeout = () => { + if (matchTimeoutTimer.value) { + clearTimeout(matchTimeoutTimer.value); + matchTimeoutTimer.value = null; + } +}; + +/** + * 超时处理:查询后端是否已分配对局 + * - 有对局 → WS 消息丢失场景,自动跳入 + * - 无对局 → 真正超时,提示用户并返回大厅 + */ +const handleMatchTimeout = async () => { + try { + const battle = await getBattleAPI(); + if (battle && battle.matchId) { + uni.showToast({ title: "匹配成功,正在进入...", icon: "none" }); + if (battle.mode <= 3) { + uni.redirectTo({ url: `/pages/team-battle?battleId=${battle.matchId}` }); + } else { + uni.redirectTo({ url: `/pages/melee-battle?battleId=${battle.matchId}` }); + } + } else { + uni.showToast({ title: "匹配超时,请重试", icon: "none" }); + try { + if (gameType.value && teamSize.value) { + await matchGameAPI(false, gameType.value, teamSize.value); + } + } catch (_) { /* 取消失败忽略 */ } + uni.navigateBack(); + } + } catch (e) { + uni.showToast({ title: "匹配超时,请重试", icon: "none" }); + uni.navigateBack(); + } +}; + async function stopMatch() { uni.$showHint(3); } +/** + * 取消匹配,带容错处理: + * - 取消成功 → 返回大厅 + * - 取消失败(后端已分配对局但 WS 未到达)→ 查询并跳入当前对局 + */ async function cancelMatch() { - if (gameType.value && teamSize.value) { - await matchGameAPI(false, gameType.value, teamSize.value); + clearMatchTimeout(); + try { + if (gameType.value && teamSize.value) { + await matchGameAPI(false, gameType.value, teamSize.value); + } + uni.navigateBack(); + } catch (e) { + // 取消匹配接口失败,尝试查询是否已被分配对局 + try { + const battle = await getBattleAPI(); + if (battle && battle.matchId) { + if (battle.mode <= 3) { + uni.redirectTo({ url: `/pages/team-battle?battleId=${battle.matchId}` }); + } else { + uni.redirectTo({ url: `/pages/melee-battle?battleId=${battle.matchId}` }); + } + } else { + uni.navigateBack(); + } + } catch (_) { + uni.navigateBack(); + } } - uni.navigateBack() } async function onReceiveMessage(msg) { @@ -26,6 +94,8 @@ async function onReceiveMessage(msg) { onComplete.value = () => {} } if (msg.type === MESSAGETYPESV2.AboutToStart) { + // 收到开始消息,清除超时计时器 + clearMatchTimeout(); // 使用后端下发的 mode 字段判断跳转目标,与好友约战(battle-room.vue)保持一致 // mode <= 3 为团队对抗,mode > 3 为大乱斗,覆盖全部 gameType(1~5),不再遗漏 if (msg.mode <= 3) { @@ -56,6 +126,7 @@ onMounted(() => { }); onBeforeUnmount(() => { + clearMatchTimeout(); uni.setKeepScreenOn({ keepScreenOn: false, }); @@ -66,6 +137,9 @@ onBeforeUnmount(() => { onShow(async () => { if (gameType.value && teamSize.value) { matchGameAPI(true, gameType.value, teamSize.value); + // 启动超时计时器,防止 WS 消息丢失或长时间无对手导致用户卡死 + clearMatchTimeout(); + matchTimeoutTimer.value = setTimeout(handleMatchTimeout, MATCH_TIMEOUT_MS); } }); From cf8d6135ff3360b607bef9568a695fc81fa7bf7f Mon Sep 17 00:00:00 2001 From: chenlimao Date: Tue, 12 May 2026 10:46:27 +0800 Subject: [PATCH 3/4] =?UTF-8?q?bug=EF=BC=9A=E4=BF=AE=E5=A4=8D=E5=A4=A7?= =?UTF-8?q?=E4=B9=B1=E6=96=97=E5=89=8D6=E7=AE=AD=E4=B8=8D=E5=B0=84?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E5=88=86=E6=95=B0=E5=9D=97=E5=B1=95=E7=A4=BA?= =?UTF-8?q?=E9=94=99=E4=B9=B1=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/PlayerScore.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PlayerScore.vue b/src/components/PlayerScore.vue index 08931a6..54fea32 100644 --- a/src/components/PlayerScore.vue +++ b/src/components/PlayerScore.vue @@ -43,7 +43,7 @@ const rowCount = new Array(6).fill(0); {{ - scores[1] && scores[1][index] ? `${scores[0][index].ring}环` : "-" + scores[1] && scores[1][index] ? `${scores[1][index].ring}环` : "-" }} From 956e82e10c52c5c9a884ae936f71416ecca210f2 Mon Sep 17 00:00:00 2001 From: chenlimao Date: Tue, 12 May 2026 16:01:29 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix=EF=BC=9A=20onBeforeUnmount=20=E8=A1=A5?= =?UTF-8?q?=E5=85=85=20uni.$off("socket-inbox",=20onReceiveMessage)&ToSome?= =?UTF-8?q?oneShoot=20=E6=B6=88=E6=81=AF=E5=8E=BB=E9=87=8D=E9=98=B2?= =?UTF-8?q?=E6=8A=A4=EF=BC=88lastToSomeoneShootKey=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/HeaderProgress.vue | 3 ++- src/pages/team-battle.vue | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/components/HeaderProgress.vue b/src/components/HeaderProgress.vue index bad1920..887afc9 100644 --- a/src/components/HeaderProgress.vue +++ b/src/components/HeaderProgress.vue @@ -52,7 +52,8 @@ async function onReceiveMessage(message) { const { type, mode, current, shootData } = message; if (type === MESSAGETYPESV2.BattleStart) { melee.value = Boolean(mode > 3); - totalShot.value = mode === 1 ? 3 : 2; + // 优先使用后端返回的 shootNumber,降级则根据 mode 推算 + totalShot.value = message.shootNumber ?? (mode === 1 ? 3 : 2); currentRoundEnded.value = true; audioManager.play("比赛开始"); } else if (type === MESSAGETYPESV2.BattleEnd) { diff --git a/src/pages/team-battle.vue b/src/pages/team-battle.vue index 3f5e7ea..6c3a3ff 100644 --- a/src/pages/team-battle.vue +++ b/src/pages/team-battle.vue @@ -42,6 +42,8 @@ const matchStatus = ref(undefined); const updateRemainSecond = ref(0); /** 对战来源类型(1=好友约战,2=匹配对战),用于结算页分流 */ const battleWay = ref(0); +/** 上一次处理的 ToSomeoneShoot 关键字段,用于去重过滤重复消息 */ +const lastToSomeoneShootKey = ref(""); const recoverData = (battleInfo, {force = false, arrowOnly = false} = {}) => { try { @@ -183,6 +185,11 @@ async function onReceiveMessage(msg) { if (msg.type === MESSAGETYPESV2.BattleStart) { start.value = true; } else if (msg.type === MESSAGETYPESV2.ToSomeoneShoot) { + // 去重防护:如果 playerId + round 与上一次处理完全相同,则忽略重复推送的消息 + // (防止 onBeforeUnmount 未及时 off 和页面重创建导致同一事件被处理多次) + const key = `${msg?.current?.playerId}-${msg?.current?.round}`; + if (key === lastToSomeoneShootKey.value) return; + lastToSomeoneShootKey.value = key; recoverData(msg); } else if (msg.type === MESSAGETYPESV2.ShootResult) { uni.$emit("update-remain", {stop: true}) @@ -228,6 +235,8 @@ onBeforeUnmount(() => { uni.setKeepScreenOn({ keepScreenOn: false, }); + // 注销所有第三方事件盓听,防止页面重创建时监听叠加导致消息重复处理 + uni.$off("socket-inbox", onReceiveMessage); uni.$off("audioEnded", onAudioEnded); audioManager.stopAll(); }); @@ -242,6 +251,10 @@ onShow(async () => { url: `/pages/friend-battle-result?battleId=${result.matchId}`, }); } else { + // shootNumber 来自后端,恢复状态时同步 totalShot,防止 BattleStart 不重新推送导致显示错误 + if (result.shootNumber) { + uni.$emit("update-shot", { currentShot: 0, totalShot: result.shootNumber }); + } recoverData(result, {force: true}); } }