Files
shoot-miniprograms/src/components/HeaderProgress.vue

159 lines
4.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import { ref, watch, onMounted, onBeforeUnmount } from "vue";
import audioManager from "@/audioManager";
import { MESSAGETYPESV2 } from "@/constants";
import { getDirectionText } from "@/util";
import useStore from "@/store";
import { storeToRefs } from "pinia";
const store = useStore();
const { user } = storeToRefs(store);
const tips = ref("");
const melee = ref(false);
const timer = ref(null);
const sound = ref(true);
const currentRound = ref(0);
const currentRoundEnded = ref(false);
const ended = ref(false);
const halfTime = ref(false);
const currentShot = ref(0);
const totalShot = ref(0);
watch(
() => tips.value,
(newVal) => {
let key = [];
if (newVal.includes("重回")) return;
if (currentRoundEnded.value) {
// 播放当前轮次语音
key.push(`${["一", "二", "三", "四", "五"][currentRound.value]}`);
}
key.push(
newVal.includes("你")
? "轮到你了"
: newVal.includes("红队")
? "请红方射箭"
: "请蓝方射箭"
);
audioManager.play(key, false);
currentRoundEnded.value = false;
}
);
const updateSound = () => {
sound.value = !sound.value;
audioManager.setMuted(!sound.value);
};
async function onReceiveMessage(message) {
if (ended.value) return;
if (Array.isArray(message)) return;
const { type, mode, current, shootData } = message;
if (type === MESSAGETYPESV2.BattleStart) {
melee.value = Boolean(mode > 3);
// 优先使用后端返回的 shootNumber降级则根据 mode 推算
totalShot.value = message.shootNumber ?? (mode === 1 ? 3 : 2);
currentRoundEnded.value = true;
audioManager.play("比赛开始");
} else if (type === MESSAGETYPESV2.BattleEnd) {
audioManager.play("比赛结束", false);
} else if (type === MESSAGETYPESV2.ShootResult) {
if (melee.value && current.playerId !== user.value.id) return;
// 直接使用后端返回的 myIndex当前用户已射箭数不在前端自增
if (current.playerId === user.value.id) currentShot.value = current.myIndex ?? currentShot.value;
if (message.shootData) {
let key = [];
key.push(
shootData.ring
? `${shootData.ringX ? "X" : shootData.ring}`
: "未上靶"
);
if (shootData.angle !== null)
key.push(`${getDirectionText(shootData.angle)}调整`);
audioManager.play(key, false);
}
} else if (type === MESSAGETYPESV2.NewRound) {
currentShot.value = 0;
currentRound.value = current.round;
currentRoundEnded.value = true;
} else if (type === MESSAGETYPESV2.InvalidShot) {
uni.showToast({
title: "距离不足,无效",
icon: "none",
});
audioManager.play("射击无效");
}
}
const playSound = (key) => {
audioManager.play(key);
};
const onUpdateTips = (newVal) => {
tips.value = newVal;
};
// 监听 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-tips", onUpdateTips);
uni.$on("socket-inbox", onReceiveMessage);
uni.$on("play-sound", playSound);
});
onBeforeUnmount(() => {
uni.$off("socket-inbox", onReceiveMessage);
uni.$off("play-sound", playSound);
// 补充取消 update-tips 监听,防止页面重建时监听器叠加
uni.$off("update-tips", onUpdateTips);
if (timer.value) clearInterval(timer.value);
});
</script>
<template>
<view class="container">
<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" />
</button>
</view>
</template>
<style scoped>
.container {
width: 50vw;
color: #fed847;
display: flex;
align-items: center;
justify-content: center;
font-weight: 500;
}
.container>button:last-child {
width: 36px;
height: 36px;
}
.container>button:last-child>image {
width: 36px;
min-height: 36px;
}
</style>