Merge branch 'new-race-mode' into test

This commit is contained in:
2026-05-13 13:42:51 +08:00
4 changed files with 83 additions and 31 deletions

View File

@@ -57,7 +57,6 @@ const signin = () => {
const loading = ref(false);
const pointBook = ref(null);
const showProgress = ref(false);
const heat = ref(0);
/** 房间号按钮动态定位样式position: fixed根据胶囊真实位置计算脱离 flex 流避免挤压标题) */
const battleRoomBtnStyle = ref({});
@@ -82,9 +81,6 @@ onMounted(() => {
pointBook.value = uni.getStorageSync("last-point-book");
}
}
if (currentPage.route === "pages/team-battle") {
showProgress.value = true;
}
// 仅在对战房间页获取胶囊位置,按钮用 fixed 定位精确贴靠胶囊左侧(脱离 flex 流,不挤压标题)
if (currentPage.route === "pages/battle-room") {
try {
@@ -189,7 +185,7 @@ onBeforeUnmount(() => {
}}</text
>
</view>
<view v-if="showProgress" class="battle-progress">
<view v-if="currentPage === 'pages/team-battle'" class="battle-progress">
<HeaderProgress />
</view>
<!-- 对战房间:整个胶囊为分享按钮,房号从 Store 读取fixed 定位紧靠系统胶囊左侧 -->

View File

@@ -60,7 +60,8 @@ async function onReceiveMessage(message) {
audioManager.play("比赛结束", false);
} else if (type === MESSAGETYPESV2.ShootResult) {
if (melee.value && current.playerId !== user.value.id) return;
if (current.playerId === user.value.id) currentShot.value++;
// 仅计用户自己的命中箭,并加上限防护,防止超过 totalShot如重入时 currentShot 初始值偏高)
if (current.playerId === user.value.id && currentShot.value < totalShot.value) currentShot.value++;
if (message.shootData) {
let key = [];
key.push(
@@ -93,22 +94,34 @@ const onUpdateTips = (newVal) => {
tips.value = newVal;
};
const onUpdateTotalShot = (newVal) => {
currentShot.value = newVal.currentShot;
totalShot.value = newVal.totalShot;
};
// 监听 Pinia store 中 totalShot 变化,用于比赛恢复时同步箭数(替代 uni.$emit 避免时序问题)
// 使用 immediate: true 确保组件创建时立即读取 store 当前值(解决重入时 totalShot 值不变 watch 不触发的问题)
watch(() => store.game.totalShot, (newVal) => {
if (newVal > 0) {
totalShot.value = newVal;
currentShot.value = store.game.currentShot;
}
}, { immediate: true });
// 监听 Pinia store 中 tips 变化,用于比赛恢复时同步提示文案(替代 uni.$emit 避免时序问题)
// 使用 immediate: true 确保组件创建时立即读取 store 当前值(解决 onShow 早于 onMounted 导致 uni.$emit 事件丢失的问题)
watch(() => store.game.tips, (newVal) => {
if (newVal) {
tips.value = newVal;
}
}, { immediate: true });
onMounted(() => {
uni.$on("update-shot", onUpdateTotalShot);
uni.$on("update-tips", onUpdateTips);
uni.$on("socket-inbox", onReceiveMessage);
uni.$on("play-sound", playSound);
});
onBeforeUnmount(() => {
uni.$off("update-shot", onUpdateTotalShot);
uni.$off("socket-inbox", onReceiveMessage);
uni.$off("play-sound", playSound);
// 补充取消 update-tips 监听,防止页面重建时监听器叠加
uni.$off("update-tips", onUpdateTips);
if (timer.value) clearInterval(timer.value);
});
</script>
@@ -118,10 +131,7 @@ onBeforeUnmount(() => {
<text>{{ (tips || "").replace(/你/g, "").replace(/重回/g, "") }}</text>
<text v-if="totalShot > 0"> ({{ currentShot }}/{{ totalShot }}) </text>
<button v-if="!!tips" hover-class="none" @click="updateSound">
<image
:src="`../static/sound${sound ? '' : '-off'}-yellow.png`"
mode="widthFix"
/>
<image :src="`../static/sound${sound ? '' : '-off'}-yellow.png`" mode="widthFix" />
</button>
</view>
</template>
@@ -135,11 +145,13 @@ onBeforeUnmount(() => {
justify-content: center;
font-weight: 500;
}
.container > button:last-child {
.container>button:last-child {
width: 36px;
height: 36px;
}
.container > button:last-child > image {
.container>button:last-child>image {
width: 36px;
min-height: 36px;
}

View File

@@ -113,17 +113,39 @@ const recoverData = (battleInfo, {force = false, arrowOnly = false} = {}) => {
audioManager.play(audioKey, false);
}
tips.value = nextTips;
// 同步写入 Pinia store供 HeaderProgress 通过 immediate watch 在 onMounted 时立即读取
// 规避 WeChat 小程序 onShow 在组件 onMounted 之前触发导致 uni.$emit 事件丢失的时序问题
store.updateTips(nextTips);
// force 模式下改为在 nextTick 内发送 update-tips规避 WeChat 小程序
// onShow 与 onMounted 时序问题导致 HeaderProgress 监听器尚未注册的边界情况
if (!force) {
uni.$emit("update-tips", nextTips);
// redPlayer 已在上方 find() 确认:不为 null 则当前射手在红队
uni.$emit("update-remain", {reset: true, value: 15, team: redPlayer?'red':'blue'});
if (force) {
const remain = (Date.now() - battleInfo.current.startTime) / 1000;
console.log(`当前轮已进行${remain}`);
if (remain > 0 && remain < 15) {
updateRemainSecond.value = 15 - remain - 0.2
}
// redPlayer 已在上方 find() 确认:不为 null 则当前射手在红队
const targetTeam = redPlayer ? 'red' : 'blue';
if (force) {
// force 模式下 start.value 刚由 null 变 trueVue 异步调度渲染,
// ShootProgress2v-if="start"尚未挂载update-remain 事件会被丢弃。
// 同时 HeaderProgress 对含"重回"的 tips 拦截音频,倒计时无法依赖
// onAudioEnded 驱动,需在 nextTick 后直接启动。
const elapsed = (Date.now() - battleInfo.current.startTime) / 1000;
console.log(`当前轮已进行${elapsed}`);
if (elapsed > 0 && elapsed < 15) {
updateRemainSecond.value = 15 - elapsed - 0.2;
}
const actualRemain = updateRemainSecond.value;
nextTick(() => {
// 在 nextTick 内统一发送,确保 ShootProgress2 和 HeaderProgress 均已挂载
uni.$emit("update-tips", nextTips);
uni.$emit("update-remain", {reset: true, value: 15, team: targetTeam});
// 重置动画完成后,直接启动倒计时(无需依赖音频结束回调)
setTimeout(() => {
uni.$emit("update-remain", {stop: false, value: actualRemain, team: targetTeam});
}, 100);
});
} else {
updateRemainSecond.value = battleInfo.readyTime
uni.$emit("update-remain", {reset: true, value: 15, team: targetTeam});
updateRemainSecond.value = battleInfo.readyTime;
}
} else {
currentRound.value = battleInfo.current.round || 1;
@@ -176,7 +198,7 @@ function onNewRound(msg, prevRound) {
isFinalShoot.value = msg.current.goldRound;
// 决金箭轮每人只射一箭,重置箭数显示为 (0/1)
if (msg.current.goldRound) {
uni.$emit("update-shot", { currentShot: 0, totalShot: 1 });
store.updateShotInfo(0, 1);
}
// 用传入的 prevRound捕获时刻的旧轮次展示结算 Tip与进度条 currentRound 解耦
roundTipRound.value = prevRound;
@@ -228,6 +250,9 @@ async function onReceiveMessage(msg) {
onLoad(async (options) => {
if (options.battleId) battleId.value = options.battleId;
// 重置箭数和提示文案,防止因 Pinia 保留上一场比赛的旧值而错误展示
store.updateShotInfo(0, 0);
store.updateTips("");
// uni.enableAlertBeforeUnload({
// message: "离开比赛可能导致比赛失败,是否继续?",
// success: (res) => {
@@ -263,9 +288,16 @@ onShow(async () => {
url: `/pages/friend-battle-result?battleId=${result.matchId}`,
});
} else {
// shootNumber 来自后端,恢复状态时同步 totalShot防止 BattleStart 不重新推送导致显示错误
if (result.status !== 0) {
// 比赛进行中,从后端恢复箭数(测距阶段不展示)
if (result.shootNumber) {
uni.$emit("update-shot", { currentShot: 0, totalShot: result.shootNumber });
// current.index 为 0 基的已射箭数0=尚未射出),与 ShootResult 累加语义一致
// 注意:不再 +1避免重进时与后续 ShootResult 自增形成双重计数导致超出 totalShot
store.updateShotInfo(result.current?.index ?? 0, result.shootNumber);
}
} else {
// 测距阶段重置箭数,防止 ImmediateWatcher 读取 Pinia 保留的旧值
store.updateShotInfo(0, 0);
}
recoverData(result, {force: true});
}

View File

@@ -80,6 +80,9 @@ export default defineStore("store", {
roomID: "",
inBattle: false,
roomNumber: "", // 当前房间号,供 Header 展示房号胶囊
currentShot: 0, // 当前已射箭数(用于 HeaderProgress 恢复状态)
totalShot: 0, // 轮次总箭数(用于 HeaderProgress 恢复状态)
tips: "", // 当前提示文案(用于 HeaderProgress 恢复状态,替代 uni.$emit 避免时序问题)
},
}),
@@ -136,6 +139,15 @@ export default defineStore("store", {
this.game.roomID = roomID;
this.game.inBattle = inBattle;
},
/** 更新当前射箭进度(用于 HeaderProgress 恢复状态,替代 uni.$emit 避免时序问题) */
updateShotInfo(currentShot = 0, totalShot = 0) {
this.game.currentShot = currentShot;
this.game.totalShot = totalShot;
},
/** 更新当前提示文案(用于 HeaderProgress 恢复状态,替代 uni.$emit 避免时序问题) */
updateTips(tips = "") {
this.game.tips = tips;
},
/** 更新当前房间号,供 Header 组件展示房号胶囊 */
updateRoomNumber(number) {
this.game.roomNumber = number;