From 7c34b3ca94c3b0472333127f556a7d47be4c218a Mon Sep 17 00:00:00 2001 From: AoXuan Date: Sun, 21 Sep 2025 01:45:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E9=9F=B3=E9=A2=91?= =?UTF-8?q?=E6=92=AD=E6=94=BE=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/NoticeModal.vue | 10 ++- frontend/src/composables/useAudioPlayer.ts | 99 ++++++++++++++++++++++ frontend/src/views/Home.vue | 8 ++ 3 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 frontend/src/composables/useAudioPlayer.ts diff --git a/frontend/src/components/NoticeModal.vue b/frontend/src/components/NoticeModal.vue index 1db6594..187c06c 100644 --- a/frontend/src/components/NoticeModal.vue +++ b/frontend/src/components/NoticeModal.vue @@ -59,6 +59,7 @@ import { ref, computed, watch } from 'vue' import { message } from 'ant-design-vue' import MarkdownIt from 'markdown-it' import { Service } from '@/api/services/Service' +import { useAudioPlayer } from '@/composables/useAudioPlayer' interface Props { visible: boolean @@ -81,6 +82,9 @@ const visible = computed({ const confirming = ref(false) const activeNoticeKey = ref('') +// 音频播放器 +const { playSound } = useAudioPlayer() + // 初始化 markdown 解析器 const md = new MarkdownIt({ html: true, @@ -159,10 +163,12 @@ watch( { immediate: true } ) -// 监听弹窗显示状态,重置到第一个公告 -watch(visible, newVisible => { +// 监听弹窗显示状态,重置到第一个公告并播放音频 +watch(visible, async (newVisible) => { if (newVisible && notices.value.length > 0) { activeNoticeKey.value = notices.value[0] + // 当公告模态框显示时播放音频 + await playSound('simple/announcement_display') } }) diff --git a/frontend/src/composables/useAudioPlayer.ts b/frontend/src/composables/useAudioPlayer.ts new file mode 100644 index 0000000..277e803 --- /dev/null +++ b/frontend/src/composables/useAudioPlayer.ts @@ -0,0 +1,99 @@ +import { ref } from 'vue' +import { message } from 'ant-design-vue' +import { useSettingsApi } from '@/composables/useSettingsApi' +import { API_ENDPOINTS } from '@/config/mirrors' + +export function useAudioPlayer() { + const { getSettings } = useSettingsApi() + const currentAudio = ref(null) + const isPlaying = ref(false) + const loading = ref(false) + + /** + * 停止当前播放的音频 + */ + const stopCurrentAudio = () => { + if (currentAudio.value) { + currentAudio.value.pause() + currentAudio.value.currentTime = 0 + currentAudio.value = null + isPlaying.value = false + } + } + + /** + * 播放音频 + * @param soundPath 音频路径,例如: "noisy/welcome" 或 "welcome" + */ + const playSound = async (soundPath: string): Promise => { + if (!soundPath) { + console.warn('音频路径不能为空') + return false + } + + loading.value = true + + try { + // 首先检查语音设置 + const settings = await getSettings() + if (!settings?.Voice?.Enabled) { + console.log('语音功能已禁用,跳过音频播放') + return false + } + + // 停止当前播放的音频 + stopCurrentAudio() + + // 构建音频URL + const audioUrl = `${API_ENDPOINTS.local}/api/res/sounds/${soundPath}.wav` + + // 创建新的音频对象 + const audio = new Audio(audioUrl) + currentAudio.value = audio + + // 设置音频事件监听器 + audio.addEventListener('loadstart', () => { + isPlaying.value = true + }) + + audio.addEventListener('ended', () => { + isPlaying.value = false + currentAudio.value = null + }) + + audio.addEventListener('error', (e) => { + console.error('音频播放失败:', e) + message.error(`音频播放失败: ${soundPath}`) + isPlaying.value = false + currentAudio.value = null + }) + + // 播放音频 + await audio.play() + return true + + } catch (error) { + console.error('播放音频时发生错误:', error) + message.error('音频播放失败,请检查网络连接') + isPlaying.value = false + currentAudio.value = null + return false + } finally { + loading.value = false + } + } + + /** + * 停止播放 + */ + const stopSound = () => { + stopCurrentAudio() + } + + return { + isPlaying, + loading, + playSound, + stopSound + } +} \ No newline at end of file diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue index 1c2ea72..7e478aa 100644 --- a/frontend/src/views/Home.vue +++ b/frontend/src/views/Home.vue @@ -240,6 +240,7 @@ import { message } from 'ant-design-vue' import { ClockCircleOutlined, UserOutlined, BellOutlined } from '@ant-design/icons-vue' import { Service } from '@/api/services/Service' import NoticeModal from '@/components/NoticeModal.vue' +import { useAudioPlayer } from '@/composables/useAudioPlayer' import dayjs from 'dayjs' import { API_ENDPOINTS } from '@/config/mirrors.ts' @@ -296,6 +297,9 @@ const noticeVisible = ref(false) const noticeData = ref>({}) const noticeLoading = ref(false) +// 音频播放器 +const { playSound } = useAudioPlayer() + // 获取当前活动信息 const currentActivity = computed(() => { if (!activityData.value.length) return null @@ -435,6 +439,8 @@ const fetchNoticeData = async () => { if (response.if_need_show && response.data && Object.keys(response.data).length > 0) { noticeData.value = response.data noticeVisible.value = true + // 播放公告展示音频 + await playSound('simple/announcement_display') } } else { console.warn('获取公告失败:', response.message) @@ -461,6 +467,8 @@ const showNotice = async () => { if (response.data && Object.keys(response.data).length > 0) { noticeData.value = response.data noticeVisible.value = true + // 手动查看公告时也播放音频 + await playSound('simple/announcement_display') } else { message.info('暂无公告信息') }