适配讲解详情聚合接口
This commit is contained in:
@@ -160,7 +160,8 @@ import {
|
||||
type ExplainDetailPageViewModel
|
||||
} from '@/view-models/explainViewModels'
|
||||
import type {
|
||||
AudioPlayTargetType
|
||||
AudioPlayTargetType,
|
||||
MuseumExhibit
|
||||
} from '@/domain/museum'
|
||||
import type {
|
||||
AudioLanguage
|
||||
@@ -193,6 +194,7 @@ const defaultDetail: ExplainDetailPageViewModel = {
|
||||
}
|
||||
|
||||
const exhibit = ref<ExplainDetailPageViewModel>(defaultDetail)
|
||||
const detailSource = ref<MuseumExhibit | null>(null)
|
||||
const detailState = ref<'idle' | 'loading' | 'ready' | 'missing' | 'error'>('idle')
|
||||
const detailStateMessage = ref('请检查链接后重试。')
|
||||
const globalAudioPlayer = useGlobalAudioPlayer()
|
||||
@@ -201,7 +203,6 @@ const resolvedHallId = ref('')
|
||||
const retryingAudio = ref(false)
|
||||
const languageSwitchLoading = ref(false)
|
||||
const selectedAudioLanguage = ref<AudioLanguage>('zh-CN')
|
||||
let languageSwitchSequence = 0
|
||||
const detailEntryRequest = ref<{
|
||||
exhibitId: string
|
||||
targetType?: AudioPlayTargetType
|
||||
@@ -217,16 +218,6 @@ const detailTextByLanguage = ref<Record<AudioLanguage, string>>({
|
||||
'yue-HK': '当前语言暂无讲解词。',
|
||||
'en-US': 'English narration text is not available yet.'
|
||||
})
|
||||
const detailTextLoadingByLanguage = ref<Record<AudioLanguage, boolean>>({
|
||||
'zh-CN': false,
|
||||
'yue-HK': false,
|
||||
'en-US': false
|
||||
})
|
||||
const detailTextLoadedByLanguage = ref<Record<AudioLanguage, boolean>>({
|
||||
'zh-CN': false,
|
||||
'yue-HK': false,
|
||||
'en-US': false
|
||||
})
|
||||
// 讲解详情页位置入口暂未开放,避免误导用户进入位置预览流程。
|
||||
const isLocationActionVisible = false
|
||||
const shouldUseHostNavigation = computed(() => isEmbeddedInWechatMiniProgram())
|
||||
@@ -416,63 +407,22 @@ const applyDetailText = (lang: AudioLanguage, text?: string) => {
|
||||
...detailTextByLanguage.value,
|
||||
[lang]: normalized || fallbackTextForLanguage(lang)
|
||||
}
|
||||
detailTextLoadedByLanguage.value = {
|
||||
...detailTextLoadedByLanguage.value,
|
||||
[lang]: true
|
||||
}
|
||||
}
|
||||
|
||||
const buildTextSourceFromViewModel = (viewModel: ExplainDetailPageViewModel) => ({
|
||||
id: viewModel.id,
|
||||
name: viewModel.title,
|
||||
hallId: viewModel.hallId,
|
||||
hallName: viewModel.hallName,
|
||||
floorId: viewModel.floorId,
|
||||
floorLabel: viewModel.floorLabel,
|
||||
image: viewModel.coverImages[0],
|
||||
description: viewModel.summary,
|
||||
guideText: viewModel.summary,
|
||||
audioLanguage: viewModel.audio.language,
|
||||
audioHasText: viewModel.audio.hasText,
|
||||
playTargetType: viewModel.audio.playTargetType,
|
||||
playTargetId: viewModel.audio.playTargetId
|
||||
})
|
||||
|
||||
const loadFullDetailTextForLanguage = async (
|
||||
request: NonNullable<typeof detailEntryRequest.value>,
|
||||
lang: AudioLanguage,
|
||||
preferredViewModel?: ExplainDetailPageViewModel
|
||||
) => {
|
||||
detailTextLoadingByLanguage.value = {
|
||||
...detailTextLoadingByLanguage.value,
|
||||
[lang]: true
|
||||
const hydrateDetailTexts = (source: MuseumExhibit) => {
|
||||
const nextTexts: Record<AudioLanguage, string> = {
|
||||
'zh-CN': fallbackTextForLanguage('zh-CN'),
|
||||
'yue-HK': fallbackTextForLanguage('yue-HK'),
|
||||
'en-US': fallbackTextForLanguage('en-US')
|
||||
}
|
||||
|
||||
try {
|
||||
const viewModel = preferredViewModel || toExplainDetailPageViewModel(await explainUseCase.enterExplainDetail({
|
||||
...request,
|
||||
lang
|
||||
}))
|
||||
;(['zh-CN', 'yue-HK', 'en-US'] as AudioLanguage[]).forEach((lang) => {
|
||||
const variant = source.audioVariants?.[lang]
|
||||
const text = variant?.text || (source.audioLanguage === lang ? source.guideText : undefined)
|
||||
nextTexts[lang] = text?.trim() || fallbackTextForLanguage(lang)
|
||||
})
|
||||
|
||||
if (viewModel.audio.hasText) {
|
||||
const selection = await explainUseCase.loadExplainDetailText(buildTextSourceFromViewModel(viewModel))
|
||||
if (selection.available) {
|
||||
const nextViewModel = toExplainDetailPageViewModel(selection.exhibit)
|
||||
applyDetailText(lang, nextViewModel.body || nextViewModel.summary)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
applyDetailText(lang, viewModel.body || viewModel.summary)
|
||||
} catch (error) {
|
||||
console.warn('讲解正文加载失败:', lang, error)
|
||||
applyDetailText(lang)
|
||||
} finally {
|
||||
detailTextLoadingByLanguage.value = {
|
||||
...detailTextLoadingByLanguage.value,
|
||||
[lang]: false
|
||||
}
|
||||
}
|
||||
detailTextByLanguage.value = nextTexts
|
||||
}
|
||||
|
||||
const loadExplainDetail = async (
|
||||
@@ -485,6 +435,8 @@ const loadExplainDetail = async (
|
||||
lang
|
||||
})
|
||||
|
||||
detailSource.value = exhibitData
|
||||
hydrateDetailTexts(exhibitData)
|
||||
exhibit.value = toExplainDetailPageViewModel(exhibitData)
|
||||
const resolvedLanguage = resolveSupportedDetailLanguage(lang)
|
||||
selectedAudioLanguage.value = resolvedLanguage
|
||||
@@ -494,12 +446,13 @@ const loadExplainDetail = async (
|
||||
}
|
||||
|
||||
if (resolvedLanguage !== lang) {
|
||||
const localizedExhibit = explainUseCase.selectExplainDetailLanguage(exhibitData, resolvedLanguage)
|
||||
detailSource.value = localizedExhibit
|
||||
exhibit.value = toExplainDetailPageViewModel(localizedExhibit)
|
||||
selectedAudioLanguage.value = resolvedLanguage
|
||||
replaceDetailRouteLanguage(resolvedLanguage)
|
||||
await loadExplainDetail(request, resolvedLanguage)
|
||||
return
|
||||
}
|
||||
|
||||
void loadFullDetailTextForLanguage(request, resolvedLanguage, exhibit.value)
|
||||
detailState.value = 'ready'
|
||||
}
|
||||
|
||||
@@ -618,9 +571,7 @@ const splitDetailTextIntoParagraphs = (text: string): string[] => {
|
||||
|
||||
const currentDetailText = computed(() => detailTextFor(selectedAudioLanguage.value))
|
||||
const currentDetailParagraphs = computed(() => splitDetailTextIntoParagraphs(currentDetailText.value))
|
||||
const currentDetailTextLoading = computed(() => (
|
||||
languageSwitchLoading.value || detailTextLoadingByLanguage.value[selectedAudioLanguage.value]
|
||||
))
|
||||
const currentDetailTextLoading = computed(() => languageSwitchLoading.value)
|
||||
const detailLoadingMessage = computed(() => (
|
||||
languageSwitchLoading.value
|
||||
? `正在切换到${audioLanguageLabel(selectedAudioLanguage.value)}`
|
||||
@@ -648,62 +599,40 @@ const handleLanguageChange = async (lang: AudioLanguage, options: { syncAudio?:
|
||||
|| (supportedDetailLanguages.value.length > 0 && !supportedDetailLanguages.value.includes(lang))
|
||||
) return
|
||||
|
||||
const source = detailSource.value
|
||||
if (!source) return
|
||||
|
||||
const shouldSyncAudio = options.syncAudio !== false && isCurrentDetailAudioTarget.value
|
||||
const request = detailEntryRequest.value
|
||||
const requestSequence = ++languageSwitchSequence
|
||||
const localizedExhibit = explainUseCase.selectExplainDetailLanguage(source, lang)
|
||||
const localizedViewModel = toExplainDetailPageViewModel(localizedExhibit)
|
||||
selectedAudioLanguage.value = lang
|
||||
detailSource.value = localizedExhibit
|
||||
exhibit.value = localizedViewModel
|
||||
applyDetailText(lang, localizedExhibit.audioText || localizedExhibit.guideText || localizedViewModel.body)
|
||||
replaceDetailRouteLanguage(lang)
|
||||
|
||||
if (localizedExhibit.hallId) {
|
||||
resolvedHallId.value = localizedExhibit.hallId
|
||||
}
|
||||
|
||||
if (!shouldSyncAudio || globalAudioPlayer.currentSource.value?.lang === lang) return
|
||||
|
||||
languageSwitchLoading.value = true
|
||||
|
||||
const audioSwitch = shouldSyncAudio && globalAudioPlayer.currentSource.value?.lang !== lang
|
||||
? globalAudioPlayer.switchLanguage(lang)
|
||||
: Promise.resolve(true)
|
||||
|
||||
try {
|
||||
if (!request) {
|
||||
applyDetailText(lang)
|
||||
await audioSwitch
|
||||
return
|
||||
}
|
||||
|
||||
const exhibitData = await explainUseCase.enterExplainDetail({
|
||||
...request,
|
||||
lang
|
||||
})
|
||||
if (requestSequence !== languageSwitchSequence) return
|
||||
|
||||
const nextViewModel = toExplainDetailPageViewModel(exhibitData)
|
||||
exhibit.value = nextViewModel
|
||||
if (exhibitData.hallId) {
|
||||
resolvedHallId.value = exhibitData.hallId
|
||||
}
|
||||
|
||||
void loadFullDetailTextForLanguage(request, lang, nextViewModel)
|
||||
await audioSwitch
|
||||
} catch (error) {
|
||||
if (requestSequence !== languageSwitchSequence) return
|
||||
|
||||
console.warn('讲解语言切换失败:', lang, error)
|
||||
exhibit.value = {
|
||||
...exhibit.value,
|
||||
audio: {
|
||||
...exhibit.value.audio,
|
||||
status: 'unavailable',
|
||||
url: undefined,
|
||||
duration: undefined,
|
||||
const nextAudio = localizedViewModel.audio.url
|
||||
? {
|
||||
id: `stop-${localizedViewModel.audio.playTargetType || 'ITEM'}-${localizedViewModel.audio.playTargetId || localizedViewModel.id}-${lang}`,
|
||||
name: localizedViewModel.title,
|
||||
audioUrl: localizedViewModel.audio.url,
|
||||
image: heroImage.value,
|
||||
duration: localizedViewModel.audio.duration,
|
||||
language: lang,
|
||||
unavailableReason: '讲解服务暂不可用,请稍后重试'
|
||||
supportedLanguages: supportedDetailLanguages.value
|
||||
}
|
||||
}
|
||||
applyDetailText(lang)
|
||||
uni.showToast({
|
||||
title: '讲解语言切换失败,请稍后重试',
|
||||
icon: 'none'
|
||||
})
|
||||
: null
|
||||
await globalAudioPlayer.switchLanguage(lang, nextAudio)
|
||||
} finally {
|
||||
if (requestSequence === languageSwitchSequence) {
|
||||
languageSwitchLoading.value = false
|
||||
}
|
||||
languageSwitchLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user