修复讲解详情页退出音频清理
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
lyf
2026-07-17 23:08:45 +08:00
parent 72d1f30c89
commit b8ab414bac
2 changed files with 44 additions and 6 deletions

View File

@@ -134,8 +134,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, watch } from 'vue' import { computed, onBeforeUnmount, ref, watch } from 'vue'
import { onLoad, onUnload } from '@dcloudio/uni-app' import { onHide, onLoad, onUnload } from '@dcloudio/uni-app'
import GuidePageFrame from '@/components/navigation/GuidePageFrame.vue' import GuidePageFrame from '@/components/navigation/GuidePageFrame.vue'
import type { AudioItem } from '@/components/audio/AudioPlayer.vue' import type { AudioItem } from '@/components/audio/AudioPlayer.vue'
import { useGlobalAudioPlayer } from '@/composables/useGlobalAudioPlayer' import { useGlobalAudioPlayer } from '@/composables/useGlobalAudioPlayer'
@@ -305,6 +305,14 @@ const isCurrentDetailAudioTarget = computed(() => {
&& source.targetId === targetId && source.targetId === targetId
) )
}) })
let detailAudioClosedOnExit = false
const closeDetailAudioOnExit = () => {
if (detailAudioClosedOnExit || !isCurrentDetailAudioTarget.value) return
detailAudioClosedOnExit = true
globalAudioPlayer.close()
}
const audioDockTitle = computed(() => displayTitle.value || '讲解内容') const audioDockTitle = computed(() => displayTitle.value || '讲解内容')
const formatDetailAudioTime = (seconds?: number) => { const formatDetailAudioTime = (seconds?: number) => {
const normalizedSeconds = Number.isFinite(seconds || 0) const normalizedSeconds = Number.isFinite(seconds || 0)
@@ -482,6 +490,7 @@ const loadExplainDetail = async (
} }
onLoad(async (options: any = {}) => { onLoad(async (options: any = {}) => {
detailAudioClosedOnExit = false
const tab = Array.isArray(options.tab) ? options.tab[0] : options.tab const tab = Array.isArray(options.tab) ? options.tab[0] : options.tab
if (isGuideTopTab(tab)) { if (isGuideTopTab(tab)) {
activeTopTab.value = tab activeTopTab.value = tab
@@ -525,9 +534,10 @@ onLoad(async (options: any = {}) => {
} }
}) })
onUnload(() => { // iOS H5 may hide a uni page before its route is fully unloaded.
globalAudioPlayer.close() onHide(closeDetailAudioOnExit)
}) onUnload(closeDetailAudioOnExit)
onBeforeUnmount(closeDetailAudioOnExit)
const detailTextFor = (lang: AudioLanguage) => { const detailTextFor = (lang: AudioLanguage) => {
return detailTextByLanguage.value[lang] || fallbackTextForLanguage(lang) return detailTextByLanguage.value[lang] || fallbackTextForLanguage(lang)
@@ -908,6 +918,7 @@ const fallbackToExplainObjectList = () => {
} }
const returnToExplainObjectList = () => { const returnToExplainObjectList = () => {
closeDetailAudioOnExit()
const pages = getCurrentPages() const pages = getCurrentPages()
const previousPage = pages[pages.length - 2] as ExplainDetailPageStackEntry | undefined const previousPage = pages[pages.length - 2] as ExplainDetailPageStackEntry | undefined
if (previousPage?.route === 'pages/explain/guide-stop-list') { if (previousPage?.route === 'pages/explain/guide-stop-list') {

View File

@@ -6,6 +6,8 @@ import { flushPromises, mount } from '@vue/test-utils'
const mocks = vi.hoisted(() => ({ const mocks = vi.hoisted(() => ({
onLoadHandler: null as null | ((options?: Record<string, unknown>) => Promise<void>), onLoadHandler: null as null | ((options?: Record<string, unknown>) => Promise<void>),
onHideHandler: null as null | (() => void),
onUnloadHandler: null as null | (() => void),
enterExplainDetail: vi.fn(), enterExplainDetail: vi.fn(),
loadExplainDetailText: vi.fn(), loadExplainDetailText: vi.fn(),
selectAudioForExplainDetail: vi.fn(), selectAudioForExplainDetail: vi.fn(),
@@ -30,7 +32,12 @@ vi.mock('@dcloudio/uni-app', () => ({
onLoad: (handler: (options?: Record<string, unknown>) => Promise<void>) => { onLoad: (handler: (options?: Record<string, unknown>) => Promise<void>) => {
mocks.onLoadHandler = handler mocks.onLoadHandler = handler
}, },
onUnload: () => undefined onHide: (handler: () => void) => {
mocks.onHideHandler = handler
},
onUnload: (handler: () => void) => {
mocks.onUnloadHandler = handler
}
})) }))
vi.mock('@/usecases/explainUseCase', () => ({ vi.mock('@/usecases/explainUseCase', () => ({
@@ -93,6 +100,8 @@ const exhibit = {
describe('讲解详情音频优先布局', () => { describe('讲解详情音频优先布局', () => {
beforeEach(() => { beforeEach(() => {
mocks.onLoadHandler = null mocks.onLoadHandler = null
mocks.onHideHandler = null
mocks.onUnloadHandler = null
audioState.currentAudio.value = null audioState.currentAudio.value = null
audioState.currentSource.value = null audioState.currentSource.value = null
audioState.playing.value = false audioState.playing.value = false
@@ -301,6 +310,24 @@ describe('讲解详情音频优先布局', () => {
expect(uni.redirectTo).not.toHaveBeenCalled() expect(uni.redirectTo).not.toHaveBeenCalled()
}) })
it('离开详情页时立即关闭当前讲解播放器', async () => {
audioState.currentAudio.value = { id: 'audio-1', audioUrl: exhibit.audioUrl }
audioState.currentSource.value = { targetType: 'STOP', targetId: 'stop-1', lang: 'en-US' }
const wrapper = mount(ExhibitDetail, {
global: { stubs: { GuidePageFrame: GuidePageFrameStub } }
})
await mocks.onLoadHandler?.({ id: exhibit.id, lang: 'en-US', targetType: 'STOP', targetId: 'stop-1' })
await flushPromises()
mocks.onHideHandler?.()
mocks.onUnloadHandler?.()
wrapper.getComponent(GuidePageFrameStub).vm.$emit('back')
await flushPromises()
expect(mocks.close).toHaveBeenCalledTimes(1)
})
it('深链详情按展厅上下文回退至讲解对象列表', async () => { it('深链详情按展厅上下文回退至讲解对象列表', async () => {
const wrapper = mount(ExhibitDetail, { const wrapper = mount(ExhibitDetail, {
global: { stubs: { GuidePageFrame: GuidePageFrameStub } } global: { stubs: { GuidePageFrame: GuidePageFrameStub } }