diff --git a/src/audioManager.js b/src/audioManager.js index 53d1d65..baeb623 100644 --- a/src/audioManager.js +++ b/src/audioManager.js @@ -1,10 +1,12 @@ export const AUDIO_INTERRUPTION_BEGIN_EVENT = "audio-interruption-begin"; export const AUDIO_INTERRUPTION_END_EVENT = "audio-interruption-end"; +export const RHYTHM_SHOOT_WINDOW_AUDIO_KEY = "请射箭"; const TRAINING_START_AUDIO_KEY_MAP = Object.freeze({ base: "请于计时前完成指定环数对应箭量", endurance: "请于计时前完成累计环数和箭量", precision: "请射箭命中高亮区域", + rhythm: "请在读条推进到标记区间内射箭", }); export const getTrainingStartAudioKey = (trainingType, fallback = "") => @@ -28,6 +30,17 @@ export const getPrecisionShotAudioKeys = (shootData, directionText = "") => { return [directionAudioKey, hitAudioKey].filter(Boolean); }; +export const getRhythmShotAudioKeys = (shootData) => { + if (!shootData || typeof shootData !== "object") return []; + const ring = Number(shootData.ring); + const ringAudioKey = + Number.isFinite(ring) && ring > 0 + ? `${shootData.ringX ? "X" : ring}环` + : "未上靶"; + // 节奏训练是否达标完全由服务端 ok 字段决定,前端不根据环数复算。 + return [ringAudioKey, shootData.ok === true ? "Perfect" : "miss"]; +}; + export const audioFils = { tententen: "https://static.shelingxingqiu.com/shootmini/static/audio/tententen.mp3", 点击按钮: "https://static.shelingxingqiu.com/shootmini/static/audio/%E7%82%B9%E5%87%BB%E6%8C%89%E9%92%AE.mp3", @@ -152,6 +165,14 @@ export const audioFils = { "https://static.shelingxingqiu.com/shootmini/static/audio0820/%E8%AF%B7%E4%BA%8E%E8%AE%A1%E6%97%B6%E5%89%8D%E5%AE%8C%E6%88%90%E7%B4%AF%E8%AE%A1%E7%8E%AF%E6%95%B0%E5%92%8C%E7%AE%AD%E9%87%8F.MP3", 请射箭命中高亮区域: "https://static.shelingxingqiu.com/shootmini/static/audio0820/%E8%AF%B7%E5%B0%84%E7%AE%AD%E5%91%BD%E4%B8%AD%E9%AB%98%E4%BA%AE%E5%8C%BA%E5%9F%9F.MP3", + 请在读条推进到标记区间内射箭: + "https://static.shelingxingqiu.com/shootmini/static/audio0820/%E8%AF%B7%E5%9C%A8%E8%AF%BB%E6%9D%A1%E6%8E%A8%E8%BF%9B%E5%88%B0%E6%A0%87%E8%AE%B0%E5%8C%BA%E9%97%B4%E5%86%85%E5%B0%84%E7%AE%AD.MP3", + [RHYTHM_SHOOT_WINDOW_AUDIO_KEY]: + "https://static.shelingxingqiu.com/shootmini/static/audio0820/%E8%AF%B7%E5%B0%84%E7%AE%AD.MP3", + Perfect: + "https://static.shelingxingqiu.com/shootmini/static/audio0820/Perfect.MP3", + miss: + "https://static.shelingxingqiu.com/shootmini/static/audio0820/miss.MP3", Bingo命中目标: "https://static.shelingxingqiu.com/shootmini/static/audio0820/Bingo%E5%91%BD%E4%B8%AD%E7%9B%AE%E6%A0%87.MP3", 未命中: diff --git a/src/matchWebsocket.js b/src/matchWebsocket.js index 13e0865..37d105b 100644 --- a/src/matchWebsocket.js +++ b/src/matchWebsocket.js @@ -16,6 +16,7 @@ import { } from "@/util"; import { getPrecisionShotAudioKeys, + getRhythmShotAudioKeys, getTrainingStartAudioKey, } from "@/audioManager"; import { @@ -405,10 +406,9 @@ function getAckAudioKeys(message, businessMessage) { ) { return []; } - if ( - (businessMessage?.trainingType || currentContext?.trainingType) === - "precision" - ) { + const trainingType = + businessMessage?.trainingType || currentContext?.trainingType; + if (trainingType === "precision") { const shootData = businessMessage?.shootData; const directionText = shootData?.angle !== null && shootData?.angle !== undefined @@ -416,6 +416,9 @@ function getAckAudioKeys(message, businessMessage) { : ""; return getPrecisionShotAudioKeys(shootData, directionText); } + if (trainingType === "rhythm") { + return getRhythmShotAudioKeys(businessMessage?.shootData); + } return getShootResultAudioKeys(businessMessage?.shootData); case ServerMessageType.SERVER_MSG_MATCH_END: return ["比赛结束"]; @@ -680,7 +683,9 @@ function sendHeartbeatAck() { } function sendPracticeInfoSync() { - if (!socket || !currentContext?.matchId || !currentContext?.userId) return; + if (!socket || !currentContext?.matchId || !currentContext?.userId) { + return false; + } const clientMessage = { type: ClientMessageType.CLIENT_MSG_SYNC_PRACTICE_INFO, @@ -695,6 +700,12 @@ function sendPracticeInfoSync() { "CLIENT_MSG_SYNC_PRACTICE_INFO", clientMessage ); + return true; +} + +// 训练开始后可在不重连 WebSocket 的情况下主动刷新完整练习快照。 +export function requestPracticeInfoSync() { + return sendPracticeInfoSync(); } function sendAck({ matchId, sequence }) { diff --git a/src/pages/training/components/ScoreResult.vue b/src/pages/training/components/ScoreResult.vue index 135a62f..dd7f2b6 100644 --- a/src/pages/training/components/ScoreResult.vue +++ b/src/pages/training/components/ScoreResult.vue @@ -54,6 +54,18 @@ const showComment = ref(false); const showBowData = ref(false); const showUpgrade = ref(false); +const isCompleted = computed(() => props.result.completed === true); +const heroBackground = computed(() => + isCompleted.value + ? "https://static.shelingxingqiu.com/shootmini/static/training-difficulty-design/result-bg.png" + : "https://static.shelingxingqiu.com/shootmini/static/training-difficulty-design/result-bg-fail.png" +); +const titleBackground = computed(() => + isCompleted.value + ? "https://static.shelingxingqiu.com/shootmini/static/training-difficulty-design/result-t-bg.png" + : "https://static.shelingxingqiu.com/shootmini/static/training-difficulty-design/result-t-bg-fail.png" +); + const closePanel = () => { showPanel.value = false; setTimeout(() => { @@ -321,13 +333,13 @@ const calories = computed(