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 0000000..670272e
Binary files /dev/null and b/src/static/arrow-direction.png differ
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 "右下";
+ }
+};