From 07beae95d0a3cfda2b0ecb8a73633f11865afaf8 Mon Sep 17 00:00:00 2001 From: lyf <2514544224@qq.com> Date: Tue, 7 Jul 2026 23:35:03 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E8=AE=B2=E8=A7=A3=E5=85=A8?= =?UTF-8?q?=E5=B1=80=E6=92=AD=E6=94=BE=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/audio/AudioPlayer.vue | 112 +++++-- .../audio/GlobalAudioPlayerHost.vue | 82 +++++ src/components/navigation/GuidePageFrame.vue | 2 + src/composables/useGlobalAudioPlayer.ts | 304 ++++++++++++++++++ src/pages/exhibit/detail.vue | 240 +++++++------- src/pages/explain/list.vue | 9 +- src/pages/hall/detail.vue | 9 +- 7 files changed, 599 insertions(+), 159 deletions(-) create mode 100644 src/components/audio/GlobalAudioPlayerHost.vue create mode 100644 src/composables/useGlobalAudioPlayer.ts diff --git a/src/components/audio/AudioPlayer.vue b/src/components/audio/AudioPlayer.vue index 013987b..cea5a16 100644 --- a/src/components/audio/AudioPlayer.vue +++ b/src/components/audio/AudioPlayer.vue @@ -3,7 +3,7 @@ @@ -11,21 +11,21 @@ - {{ currentAudio?.name || '讲解音频' }} - {{ playerSubtitle }} + {{ displayAudio?.name || '讲解音频' }} + {{ playerSubtitle }} - + @@ -73,6 +73,12 @@ const props = defineProps<{ audio?: AudioItem | null autoPlay?: boolean avoidBottomNav?: boolean + controlled?: boolean + playing?: boolean + loading?: boolean + playbackError?: string + currentTime?: number + durationValue?: number }>() const emit = defineEmits<{ @@ -82,15 +88,19 @@ const emit = defineEmits<{ ended: [audio: AudioItem] timeUpdate: [currentTime: number] error: [audio: AudioItem | null, message: string] + toggle: [] + close: [] + expand: [] + seek: [percent: number] }>() // 状态 const currentAudio = ref(null) const isPlaying = ref(false) -const currentTime = ref(0) +const localCurrentTime = ref(0) const duration = ref(0) const isLoading = ref(false) -const playbackError = ref('') +const localPlaybackError = ref('') const h5AudioElement = ref(null) // 内部音频上下文 @@ -102,22 +112,32 @@ let cleanupH5Audio: (() => void) | null = null const progressPercent = computed(() => { const totalDuration = resolvedDuration.value if (totalDuration === 0) return 0 - return (currentTime.value / totalDuration) * 100 + return (displayCurrentTime.value / totalDuration) * 100 }) const resolvedDuration = computed(() => { + if (props.controlled) { + if (props.durationValue && props.durationValue > 0) return props.durationValue + return props.audio?.duration || 0 + } if (duration.value > 0) return duration.value return currentAudio.value?.duration || 0 }) +const displayAudio = computed(() => props.controlled ? props.audio || null : currentAudio.value) +const displayPlaying = computed(() => props.controlled ? props.playing === true : isPlaying.value) +const displayLoading = computed(() => props.controlled ? props.loading === true : isLoading.value) +const displayPlaybackError = computed(() => props.controlled ? props.playbackError || '' : localPlaybackError.value) +const displayCurrentTime = computed(() => props.controlled ? props.currentTime || 0 : localCurrentTime.value) + const playerSubtitle = computed(() => { - if (playbackError.value) return playbackError.value - if (isLoading.value) return '音频加载中' - if (!currentAudio.value) return '请选择讲解' + if (displayPlaybackError.value) return displayPlaybackError.value + if (displayLoading.value) return '音频加载中' + if (!displayAudio.value) return '请选择讲解' if (resolvedDuration.value > 0) { - return `${formatTime(currentTime.value)} / ${formatTime(resolvedDuration.value)}` + return `${formatTime(displayCurrentTime.value)} / ${formatTime(resolvedDuration.value)}` } - return isPlaying.value ? '正在播放' : '点击播放按钮开始' + return displayPlaying.value ? '正在播放' : '点击播放按钮开始' }) // 格式化时间 @@ -137,7 +157,7 @@ const initAudio = () => { innerAudioContext.onPlay(() => { isPlaying.value = true isLoading.value = false - playbackError.value = '' + localPlaybackError.value = '' if (currentAudio.value) { emit('play', currentAudio.value) } @@ -149,21 +169,21 @@ const initAudio = () => { innerAudioContext.onStop(() => { isPlaying.value = false - currentTime.value = 0 + localCurrentTime.value = 0 }) innerAudioContext.onEnded(() => { isPlaying.value = false - currentTime.value = 0 + localCurrentTime.value = 0 if (currentAudio.value) { emit('ended', currentAudio.value) } }) innerAudioContext.onTimeUpdate(() => { - currentTime.value = innerAudioContext.currentTime + localCurrentTime.value = innerAudioContext.currentTime duration.value = innerAudioContext.duration || 0 - emit('timeUpdate', currentTime.value) + emit('timeUpdate', localCurrentTime.value) }) innerAudioContext.onError((err: any) => { @@ -229,7 +249,7 @@ const showToast = (title: string) => { const handlePlaybackError = (message: string, shouldClose = false) => { isPlaying.value = false isLoading.value = false - playbackError.value = message + localPlaybackError.value = message emit('error', currentAudio.value, message) showToast(message) @@ -241,9 +261,9 @@ const handlePlaybackError = (message: string, shouldClose = false) => { const resetPlaybackState = (clearAudio = true) => { isPlaying.value = false isLoading.value = false - currentTime.value = 0 + localCurrentTime.value = 0 duration.value = 0 - playbackError.value = '' + localPlaybackError.value = '' if (clearAudio) { currentAudio.value = null @@ -260,11 +280,11 @@ const playAudio = async (audio: AudioItem) => { const isNewAudio = currentAudio.value?.id !== audio.id currentAudio.value = audio - playbackError.value = '' + localPlaybackError.value = '' isLoading.value = true if (isNewAudio) { - currentTime.value = 0 + localCurrentTime.value = 0 duration.value = audio.duration || 0 } @@ -284,7 +304,7 @@ const playAudio = async (audio: AudioItem) => { isPlaying.value = false if (isAutoplayBlocked(error)) { - playbackError.value = '点击播放按钮开始播放' + localPlaybackError.value = '点击播放按钮开始播放' return false } @@ -356,6 +376,11 @@ const stopAudio = () => { // 切换播放/暂停 const togglePlay = () => { + if (props.controlled) { + emit('toggle') + return + } + if (isPlaying.value) { pauseAudio() } else { @@ -374,13 +399,13 @@ const seekTo = (percent: number) => { if (h5AudioElement.value) { h5AudioElement.value.currentTime = targetTime - currentTime.value = targetTime + localCurrentTime.value = targetTime return } if (innerAudioContext) { innerAudioContext.seek(targetTime) - currentTime.value = targetTime + localCurrentTime.value = targetTime } } @@ -389,19 +414,30 @@ const handleSeek = (e: any) => { const rect = e.currentTarget.getBoundingClientRect() const x = e.detail.x || (e.touches && e.touches[0]?.clientX - rect.left) || 0 const percent = (x / rect.width) * 100 - seekTo(Math.max(0, Math.min(100, percent))) + const nextPercent = Math.max(0, Math.min(100, percent)) + if (props.controlled) { + emit('seek', nextPercent) + return + } + seekTo(nextPercent) } // 展开播放器 const handleExpand = () => { - // 展开逻辑 (如果需要全屏播放器) - console.log('展开播放器') + emit('expand') } // 关闭播放器 const handleClose = () => { + if (props.controlled) { + emit('close') + emit('update:visible', false) + return + } + stopAudio() emit('update:visible', false) + emit('close') } const handleH5LoadedMetadata = () => { @@ -413,7 +449,7 @@ const handleH5LoadedMetadata = () => { const handleH5Play = () => { isPlaying.value = true isLoading.value = false - playbackError.value = '' + localPlaybackError.value = '' if (currentAudio.value) { emit('play', currentAudio.value) } @@ -429,7 +465,7 @@ const handleH5Pause = () => { const handleH5Ended = () => { isPlaying.value = false isLoading.value = false - currentTime.value = 0 + localCurrentTime.value = 0 if (currentAudio.value) { emit('ended', currentAudio.value) } @@ -438,9 +474,9 @@ const handleH5Ended = () => { const handleH5TimeUpdate = () => { const audio = h5AudioElement.value if (!audio) return - currentTime.value = audio.currentTime || 0 + localCurrentTime.value = audio.currentTime || 0 duration.value = Number.isFinite(audio.duration) ? audio.duration : duration.value - emit('timeUpdate', currentTime.value) + emit('timeUpdate', localCurrentTime.value) } const handleH5Waiting = () => { @@ -458,6 +494,8 @@ const handleH5AudioError = () => { // 监听外部 audio 属性变化 watch(() => props.audio, (newAudio) => { + if (props.controlled) return + if (newAudio && props.visible && props.autoPlay && currentAudio.value?.id !== newAudio.id) { void playAudio(newAudio) } else if (newAudio && currentAudio.value?.id !== newAudio.id) { @@ -468,17 +506,23 @@ watch(() => props.audio, (newAudio) => { // 监听外部 visible 属性变化 watch(() => props.visible, (visible) => { + if (props.controlled) return + if (!visible) { stopAudio() } }) onMounted(() => { + if (props.controlled) return + initH5Audio() initAudio() }) onUnmounted(() => { + if (props.controlled) return + cleanupH5Audio?.() cleanupH5Audio = null @@ -495,7 +539,7 @@ defineExpose({ stop: stopAudio, seekTo: seekTo, isPlaying: () => isPlaying.value, - currentTime: () => currentTime.value, + currentTime: () => localCurrentTime.value, duration: () => duration.value }) diff --git a/src/components/audio/GlobalAudioPlayerHost.vue b/src/components/audio/GlobalAudioPlayerHost.vue new file mode 100644 index 0000000..d374743 --- /dev/null +++ b/src/components/audio/GlobalAudioPlayerHost.vue @@ -0,0 +1,82 @@ + + + diff --git a/src/components/navigation/GuidePageFrame.vue b/src/components/navigation/GuidePageFrame.vue index c5d7793..68a30ff 100644 --- a/src/components/navigation/GuidePageFrame.vue +++ b/src/components/navigation/GuidePageFrame.vue @@ -32,11 +32,13 @@ +