diff --git a/src/audioManager.js b/src/audioManager.js index 4e46697..53d1d65 100644 --- a/src/audioManager.js +++ b/src/audioManager.js @@ -23,10 +23,9 @@ export const getPrecisionShotAudioKeys = (shootData, directionText = "") => { return ["未上靶", directionAudioKey].filter(Boolean); } - const ringAudioKey = shootData.ringX === true ? "X环" : `${ring}环`; // protobuf 的 false 可能不编码;只要不是明确命中,本箭就反馈未命中。 const hitAudioKey = shootData.ok === true ? "Bingo命中目标" : "未命中"; - return [ringAudioKey, directionAudioKey, hitAudioKey].filter(Boolean); + return [directionAudioKey, hitAudioKey].filter(Boolean); }; export const audioFils = { diff --git a/src/components/TestDistance.vue b/src/components/TestDistance.vue index 7beb4f2..13ce54f 100644 --- a/src/components/TestDistance.vue +++ b/src/components/TestDistance.vue @@ -6,11 +6,16 @@ import Avatar from "@/components/Avatar.vue"; import audioManager from "@/audioManager"; import { simulShootAPI } from "@/apis"; import { MESSAGETYPESV2 } from "@/constants"; -import { getDistanceCheckAudioKey, getDistanceCheckText } from "@/util"; +import { + getDistanceCheckAudioKey, + getDistanceCheckText, + getShootValidation, +} from "@/util"; import useStore from "@/store"; import { storeToRefs } from "pinia"; const store = useStore(); const { user, device } = storeToRefs(store); +const emit = defineEmits(["passed"]); const props = defineProps({ guide: { type: Boolean, @@ -24,6 +29,10 @@ const props = defineProps({ type: Number, default: 15, }, + autoStart: { + type: Boolean, + default: false, + }, }); const arrow = ref({}); const distance = ref(0); @@ -31,6 +40,29 @@ const statusText = ref(""); const showsimul = ref(false); const count = ref(props.count); const timer = ref(null); +const autoStartPending = ref(false); +const autoStartTriggered = ref(false); +let autoStartTimer = null; +const DISTANCE_PASSED_AUDIO_KEY = "站距合格,靶纸正确"; +const AUTO_START_TIMEOUT_MS = 6000; + +const clearAutoStartTimer = () => { + if (!autoStartTimer) return; + clearTimeout(autoStartTimer); + autoStartTimer = null; +}; + +const triggerAutoStart = () => { + if (!autoStartPending.value || autoStartTriggered.value) return; + clearAutoStartTimer(); + autoStartPending.value = false; + autoStartTriggered.value = true; + emit("passed"); +}; + +const onAudioEnded = (key) => { + if (key === DISTANCE_PASSED_AUDIO_KEY) triggerAutoStart(); +}; const updateTimer = (value) => { count.value = Math.round(value); @@ -44,21 +76,31 @@ onMounted(() => { }, 1000); } uni.$on("update-timer", updateTimer); + uni.$on("audioEnded", onAudioEnded); }); onBeforeUnmount(() => { if (timer.value) clearInterval(timer.value); + clearAutoStartTimer(); uni.$off("update-timer", updateTimer); + uni.$off("audioEnded", onAudioEnded); }); async function onReceiveMessage(msg) { if (Array.isArray(msg)) return; if (msg.type === MESSAGETYPESV2.TestDistance) { + if (autoStartPending.value || autoStartTriggered.value) return; const rawDistance = Number(msg.shootData?.distance ?? msg.shootData?.dst); distance.value = Number.isFinite(rawDistance) ? Number((rawDistance / 100).toFixed(2)) : 0; statusText.value = getDistanceCheckText(msg.shootData); - audioManager.play(getDistanceCheckAudioKey(msg.shootData)); + const audioKey = getDistanceCheckAudioKey(msg.shootData); + const validation = getShootValidation(msg.shootData); + if (props.autoStart && validation.distanceOk && validation.targetOk) { + autoStartPending.value = true; + autoStartTimer = setTimeout(triggerAutoStart, AUTO_START_TIMEOUT_MS); + } + audioManager.play(audioKey); } } diff --git a/src/pages/practise-one.vue b/src/pages/practise-one.vue index f3d94ef..96a4462 100644 --- a/src/pages/practise-one.vue +++ b/src/pages/practise-one.vue @@ -37,6 +37,7 @@ const store = useStore(); const { user, device } = storeToRefs(store); const start = ref(false); +const practiceStarting = ref(false); const scores = ref([]); const isSvip = ref(false); const total = 12; @@ -156,12 +157,18 @@ const beginPractise = async () => { }; const onReady = async () => { - const result = await beginPractise(); - if (!result) return; - scores.value = []; - isSvip.value = false; - start.value = true; - audioManager.play("练习开始"); + if (practiceStarting.value || start.value) return; + practiceStarting.value = true; + try { + const result = await beginPractise(); + if (!result) return; + scores.value = []; + isSvip.value = false; + start.value = true; + audioManager.play("练习开始"); + } finally { + practiceStarting.value = false; + } }; const onOver = async (message) => { @@ -438,7 +445,11 @@ onBeforeUnmount(() => { :onBack="exitPractise" > - + { @@ -452,7 +459,11 @@ onBeforeUnmount(() => { :onBack="exitPractise" > - + { + if (!autoStartTimer) return; + clearTimeout(autoStartTimer); + autoStartTimer = null; +}; + +const triggerAutoStart = () => { + if (!autoStartPending.value || autoStartTriggered.value) return; + clearAutoStartTimer(); + autoStartPending.value = false; + autoStartTriggered.value = true; + emit("passed"); +}; + +const onAudioEnded = (key) => { + if (key === DISTANCE_PASSED_AUDIO_KEY) triggerAutoStart(); +}; const updateTimer = (value) => { count.value = Math.round(value); @@ -48,21 +80,31 @@ onMounted(() => { }, 1000); } uni.$on("update-timer", updateTimer); + uni.$on("audioEnded", onAudioEnded); }); onBeforeUnmount(() => { if (timer.value) clearInterval(timer.value); + clearAutoStartTimer(); uni.$off("update-timer", updateTimer); + uni.$off("audioEnded", onAudioEnded); }); async function onReceiveMessage(msg) { if (Array.isArray(msg)) return; if (msg.type === MESSAGETYPESV2.TestDistance) { + if (autoStartPending.value || autoStartTriggered.value) return; const rawDistance = Number(msg.shootData?.distance ?? msg.shootData?.dst); distance.value = Number.isFinite(rawDistance) ? Number((rawDistance / 100).toFixed(2)) : 0; statusText.value = getDistanceCheckText(msg.shootData); - audioManager.play(getDistanceCheckAudioKey(msg.shootData)); + const audioKey = getDistanceCheckAudioKey(msg.shootData); + const validation = getShootValidation(msg.shootData); + if (props.autoStart && validation.distanceOk && validation.targetOk) { + autoStartPending.value = true; + autoStartTimer = setTimeout(triggerAutoStart, AUTO_START_TIMEOUT_MS); + } + audioManager.play(audioKey); } } diff --git a/src/pages/training/practise-one.vue b/src/pages/training/practise-one.vue index f3588f4..29aa613 100644 --- a/src/pages/training/practise-one.vue +++ b/src/pages/training/practise-one.vue @@ -41,6 +41,7 @@ const { user } = storeToRefs(store); const sound = ref(true); const start = ref(false); +const practiceStarting = ref(false); const pageStages = Object.freeze({ DISTANCE: "distance", SHOOTING: "shooting", @@ -1031,6 +1032,7 @@ onLoad((options = {}) => { }); const onReady = async () => { + if (practiceStarting.value) return; if ( !practiseId.value || practiceEnded.value || @@ -1044,6 +1046,7 @@ const onReady = async () => { return; } + practiceStarting.value = true; pageStage.value = pageStages.LOADING; clearHighlightTestTimer(); useHighlightTest.value = false; @@ -1066,6 +1069,8 @@ const onReady = async () => { pageStage.value = pageStages.DISTANCE; setPracticeAppHideResumable(true); throw error; + } finally { + practiceStarting.value = false; } }; @@ -1332,6 +1337,8 @@ onBeforeUnmount(() => {