From f41a3d7a3a400c816a00132d3cf6666c2a0dd775 Mon Sep 17 00:00:00 2001 From: kron Date: Wed, 12 Nov 2025 16:14:48 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=B8=BA=E4=B8=8A=E9=9D=B6?= =?UTF-8?q?=E7=9A=84=E6=96=B9=E5=90=91=E6=8C=87=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/audioManager.js | 35 +++++++++++++++ src/components/BowTarget.vue | 70 +++++++++++++++++++++++++++--- src/components/HeaderProgress.vue | 18 ++++---- src/components/ShootProgress.vue | 20 +++++---- src/static/arrow-direction.png | Bin 0 -> 3169 bytes src/util.js | 21 +++++++++ 6 files changed, 140 insertions(+), 24 deletions(-) create mode 100644 src/static/arrow-direction.png diff --git a/src/audioManager.js b/src/audioManager.js index b002632..c0d5beb 100644 --- a/src/audioManager.js +++ b/src/audioManager.js @@ -109,6 +109,9 @@ class AudioManager { this.sequenceIndex = 0; this.isSequenceRunning = false; + // 静音开关 + this.isMuted = false; + // 网络状态相关 this.networkOnline = true; this.pendingPlayKey = null; @@ -176,6 +179,15 @@ class AudioManager { audio.autoplay = false; audio.src = src; + // 初始化音量(支持的平台用 volume,H5 也可用 muted 作为兜底) + try { + if (typeof audio.volume === "number") { + audio.volume = this.isMuted ? 0 : 1; + } else if (typeof audio.muted !== "undefined") { + audio.muted = this.isMuted; + } + } catch (_) {} + // 初始化为不允许播放,只有显式 play() 才允许 this.allowPlayMap.set(key, false); @@ -340,6 +352,14 @@ class AudioManager { const audio = this.audioMap.get(key); if (audio) { + // 播放前确保遵循当前静音状态 + try { + if (typeof audio.volume === "number") { + audio.volume = this.isMuted ? 0 : 1; + } else if (typeof audio.muted !== "undefined") { + audio.muted = this.isMuted; + } + } catch (_) {} // 同一音频:避免 stop() 触发 onStop 清除授权,使用 pause()+seek(0) try { audio.pause(); @@ -458,6 +478,21 @@ class AudioManager { this.retryLoadAudio(key); } } + + // 设置静音开关:true 静音,false 取消静音 + setMuted(muted) { + this.isMuted = !!muted; + for (const audio of this.audioMap.values()) { + try { + if (typeof audio.volume === "number") { + audio.volume = this.isMuted ? 0 : 1; + } else if (typeof audio.muted !== "undefined") { + audio.muted = this.isMuted; + } + } catch (_) {} + } + debugLog(`静音状态已设置为: ${this.isMuted}`); + } } // 导出单例 diff --git a/src/components/BowTarget.vue b/src/components/BowTarget.vue index 80d7884..330f8ca 100644 --- a/src/components/BowTarget.vue +++ b/src/components/BowTarget.vue @@ -1,6 +1,7 @@ @@ -122,6 +157,9 @@ onBeforeUnmount(() => { }} + + + 中场休息 { - + @@ -339,4 +377,24 @@ onBeforeUnmount(() => { z-index: 99; font-weight: bold; } +@keyframes spring-in { + 0% { + transform: translateY(-20px); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} +.arrow-dir { + position: absolute; + width: 36%; + left: 50%; + bottom: 50%; +} +.arrow-dir > image { + animation: spring-in 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards; + width: 100%; +} diff --git a/src/components/HeaderProgress.vue b/src/components/HeaderProgress.vue index 9ab2254..11bc382 100644 --- a/src/components/HeaderProgress.vue +++ b/src/components/HeaderProgress.vue @@ -2,6 +2,8 @@ import { ref, watch, onMounted, onBeforeUnmount } from "vue"; import audioManager from "@/audioManager"; import { MESSAGETYPES } from "@/constants"; +import { getDirectionText } from "@/util"; + import useStore from "@/store"; import { storeToRefs } from "pinia"; const store = useStore(); @@ -11,7 +13,6 @@ const tips = ref(""); const melee = ref(false); const timer = ref(null); const sound = ref(true); -const currentSound = ref(""); const currentRound = ref(0); const currentRoundEnded = ref(false); const ended = ref(false); @@ -22,7 +23,6 @@ const totalShot = ref(0); watch( () => tips.value, (newVal) => { - if (!sound.value) return; let key = []; if (newVal.includes("重回")) return; if (currentRoundEnded.value) { @@ -44,11 +44,11 @@ watch( const updateSound = () => { sound.value = !sound.value; - if (!sound.value) audioManager.stop(currentSound.value); + audioManager.setMuted(!sound.value); }; async function onReceiveMessage(messages = []) { - if (!sound.value || ended.value) return; + if (ended.value) return; messages.forEach((msg) => { if (msg.constructor === MESSAGETYPES.ShootResult) { if (melee.value && msg.userId !== user.value.id) return; @@ -72,10 +72,11 @@ async function onReceiveMessage(messages = []) { } catch (_) {} } if (!halfTime.value && msg.target) { - currentSound.value = msg.target.ring - ? `${msg.target.ring}环` - : "未上靶"; - audioManager.play(currentSound.value); + let key = []; + key.push(msg.target.ring ? `${msg.target.ring}环` : "未上靶"); + if (!msg.target.ring) + key.push(`向${getDirectionText(msg.target.angle)}调整`); + audioManager.play(key); } } else if (msg.constructor === MESSAGETYPES.InvalidShot) { if (msg.userId === user.value.id) { @@ -123,7 +124,6 @@ async function onReceiveMessage(messages = []) { } const playSound = (key) => { - currentSound.value = key; audioManager.play(key); }; diff --git a/src/components/ShootProgress.vue b/src/components/ShootProgress.vue index 3044204..635379a 100644 --- a/src/components/ShootProgress.vue +++ b/src/components/ShootProgress.vue @@ -2,10 +2,13 @@ import { ref, watch, onMounted, onBeforeUnmount } from "vue"; import audioManager from "@/audioManager"; import { MESSAGETYPES } from "@/constants"; +import { getDirectionText } from "@/util"; + import useStore from "@/store"; import { storeToRefs } from "pinia"; const store = useStore(); const { user } = storeToRefs(store); + const props = defineProps({ show: { type: Boolean, @@ -41,7 +44,6 @@ const barColor = ref("#fed847"); const remain = ref(props.total); const timer = ref(null); const sound = ref(true); -const currentSound = ref(""); const currentRound = ref(props.currentRound); const currentRoundEnded = ref(false); const ended = ref(false); @@ -53,7 +55,7 @@ watch( let key = ""; if (newVal.includes("红队")) key = "请红方射箭"; if (newVal.includes("蓝队")) key = "请蓝方射箭"; - if (key && sound.value) { + if (key) { if (currentRoundEnded.value) { currentRound.value += 1; currentRoundEnded.value = false; @@ -118,11 +120,11 @@ const updateRemain = (value) => { const updateSound = () => { sound.value = !sound.value; - if (!sound.value) audioManager.stop(currentSound.value); + audioManager.setMuted(!sound.value); }; async function onReceiveMessage(messages = []) { - if (!sound.value || ended.value) return; + if (ended.value) return; messages.forEach((msg) => { if ( (props.battleId && msg.constructor === MESSAGETYPES.ShootResult) || @@ -130,10 +132,11 @@ async function onReceiveMessage(messages = []) { ) { if (props.melee && msg.userId !== user.value.id) return; if (!halfTime.value && msg.target) { - currentSound.value = msg.target.ring - ? `${msg.target.ring}环` - : "未上靶"; - audioManager.play(currentSound.value); + let key = []; + key.push(msg.target.ring ? `${msg.target.ring}环` : "未上靶"); + if (!msg.target.ring) + key.push(`向${getDirectionText(msg.target.angle)}调整`); + audioManager.play(key); } } else if (msg.constructor === MESSAGETYPES.InvalidShot) { if (msg.userId === user.value.id) { @@ -164,7 +167,6 @@ async function onReceiveMessage(messages = []) { } const playSound = (key) => { - currentSound.value = key; audioManager.play(key); }; diff --git a/src/static/arrow-direction.png b/src/static/arrow-direction.png new file mode 100644 index 0000000000000000000000000000000000000000..670272ee1277d0270c7d02791354aae59e85b996 GIT binary patch literal 3169 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 Hu0mjfW(cMP literal 0 HcmV?d00001 diff --git a/src/util.js b/src/util.js index f9dbca2..f1743b0 100644 --- a/src/util.js +++ b/src/util.js @@ -676,3 +676,24 @@ export const generateShareCard = (canvasId, date, actual, total) => { }; export const generateShareImage = (canvasId) => {}; + +export const getDirectionText = (angle = 0) => { + if (angle < 0) return ""; + if (angle >= 337.5 || angle < 22.5) { + return "下"; + } else if (angle >= 22.5 && angle < 67.5) { + return "左下"; + } else if (angle >= 67.5 && angle < 112.5) { + return "左"; + } else if (angle >= 112.5 && angle < 157.5) { + return "左上"; + } else if (angle >= 157.5 && angle < 202.5) { + return "上"; + } else if (angle >= 202.5 && angle < 247.5) { + return "右上"; + } else if (angle >= 247.5 && angle < 292.5) { + return "右"; + } else if (angle >= 292.5 && angle < 337.5) { + return "右下"; + } +};