feat: 临时改造讲解页方案 B 链路
接入展厅列表、展厅讲解点分组生成临时业务单元,并迁移讲解详情播放正文到新接口链路。
This commit is contained in:
@@ -54,6 +54,15 @@
|
||||
<view class="content-section">
|
||||
<text class="section-title">讲解内容</text>
|
||||
<text class="section-text">{{ exhibit.body || exhibit.summary }}</text>
|
||||
<view
|
||||
v-if="canExpandText"
|
||||
class="text-expand-btn"
|
||||
:class="{ disabled: textLoading }"
|
||||
@tap="handleExpandText"
|
||||
>
|
||||
<text class="text-expand-label">{{ textLoading ? '正在加载讲解词' : '展开讲解词全文' }}</text>
|
||||
</view>
|
||||
<text v-if="textError" class="text-error">{{ textError }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
@@ -102,6 +111,9 @@ import {
|
||||
toExplainDetailPageViewModel,
|
||||
type ExplainDetailPageViewModel
|
||||
} from '@/view-models/explainViewModels'
|
||||
import type {
|
||||
AudioPlayTargetType
|
||||
} from '@/domain/museum'
|
||||
import {
|
||||
isGuideTopTab,
|
||||
type GuideTopTab
|
||||
@@ -117,7 +129,7 @@ const defaultDetail: ExplainDetailPageViewModel = {
|
||||
body: '该讲解内容待补充。',
|
||||
audio: {
|
||||
status: 'unavailable',
|
||||
unavailableReason: '该讲解暂无已发布音频,当前提供图文讲解。'
|
||||
unavailableReason: '当前语言暂无语音讲解。'
|
||||
},
|
||||
chapters: [],
|
||||
relatedItems: []
|
||||
@@ -135,6 +147,10 @@ const currentAudio = ref<AudioItem | null>(null)
|
||||
const isPlaying = ref(false)
|
||||
const activeTopTab = ref<GuideTopTab>('explain')
|
||||
const resolvedHallId = ref('')
|
||||
const textLoading = ref(false)
|
||||
const textExpanded = ref(false)
|
||||
const textError = ref('')
|
||||
const retryingAudio = ref(false)
|
||||
|
||||
const heroImage = computed(() => exhibit.value.coverImages[0])
|
||||
const detailMeta = computed(() => [
|
||||
@@ -142,6 +158,11 @@ const detailMeta = computed(() => [
|
||||
exhibit.value.floorLabel
|
||||
].filter(Boolean).join(' · '))
|
||||
const hallLocationId = computed(() => exhibit.value.hallId || resolvedHallId.value)
|
||||
const canExpandText = computed(() => (
|
||||
exhibit.value.audio.hasText === true
|
||||
&& !textExpanded.value
|
||||
&& !textLoading.value
|
||||
))
|
||||
|
||||
// 讲解详情页只定位到所属展厅,不再追踪具体展品点位。
|
||||
const resolveHallGuidePoi = async () => {
|
||||
@@ -163,51 +184,44 @@ onLoad(async (options: any = {}) => {
|
||||
const exhibitId = Array.isArray(options.id) ? options.id[0] : options.id
|
||||
if (!exhibitId) return
|
||||
|
||||
const exhibitData = await explainUseCase.getExhibitById(exhibitId)
|
||||
if (exhibitData) {
|
||||
const rawTargetType = Array.isArray(options.targetType) ? options.targetType[0] : options.targetType
|
||||
const targetType: AudioPlayTargetType | undefined = rawTargetType === 'ITEM' || rawTargetType === 'STOP'
|
||||
? rawTargetType
|
||||
: undefined
|
||||
const rawTargetId = Array.isArray(options.targetId) ? options.targetId[0] : options.targetId
|
||||
const targetId = rawTargetId ? String(rawTargetId) : undefined
|
||||
|
||||
try {
|
||||
const exhibitData = await explainUseCase.enterExplainDetail({
|
||||
exhibitId,
|
||||
targetType,
|
||||
targetId
|
||||
})
|
||||
exhibit.value = toExplainDetailPageViewModel(exhibitData)
|
||||
if (exhibitData.hallId) {
|
||||
resolvedHallId.value = exhibitData.hallId
|
||||
}
|
||||
void explainUseCase.enrichExhibitDetailAudio(exhibitId)
|
||||
.then((enrichedExhibit) => {
|
||||
if (!enrichedExhibit || exhibit.value.id !== exhibitId) return
|
||||
|
||||
exhibit.value = toExplainDetailPageViewModel(enrichedExhibit)
|
||||
if (enrichedExhibit.hallId) {
|
||||
resolvedHallId.value = enrichedExhibit.hallId
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('讲解详情音频正文后台补充失败:', error)
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('讲解详情加载失败:', error)
|
||||
uni.showToast({
|
||||
title: '讲解详情加载失败,请稍后重试',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const handlePlayAudio = async () => {
|
||||
if (exhibit.value.audio.url) {
|
||||
const audio: AudioItem = {
|
||||
id: `media-${exhibit.value.id}`,
|
||||
name: exhibit.value.title,
|
||||
audioUrl: exhibit.value.audio.url,
|
||||
image: heroImage.value,
|
||||
duration: undefined
|
||||
}
|
||||
|
||||
const isSameAudio = currentAudio.value?.id === audio.id
|
||||
if (showAudioPlayer.value && isSameAudio && isPlaying.value) {
|
||||
audioPlayerRef.value?.pause()
|
||||
return
|
||||
}
|
||||
|
||||
showAudioPlayer.value = true
|
||||
currentAudio.value = audio
|
||||
audioPlayerRef.value?.play(audio)
|
||||
return
|
||||
}
|
||||
|
||||
const selection = exhibit.value.id
|
||||
? await explainUseCase.selectAudioForExhibit(exhibit.value.id)
|
||||
? await explainUseCase.selectAudioForExplainDetail({
|
||||
id: exhibit.value.id,
|
||||
name: exhibit.value.title,
|
||||
image: heroImage.value,
|
||||
audioStatus: exhibit.value.audio.status === 'playable' ? 'READY' : 'MISSING',
|
||||
audioLanguage: exhibit.value.audio.language,
|
||||
audioUnavailableReason: exhibit.value.audio.unavailableReason,
|
||||
playTargetType: exhibit.value.audio.playTargetType,
|
||||
playTargetId: exhibit.value.audio.playTargetId
|
||||
})
|
||||
: null
|
||||
|
||||
if (!selection?.playable || !selection.media?.url) {
|
||||
@@ -220,9 +234,9 @@ const handlePlayAudio = async () => {
|
||||
|
||||
const audio: AudioItem = {
|
||||
id: selection.media.id,
|
||||
name: selection.track?.title || selection.exhibit.name,
|
||||
name: selection.playInfo?.title || selection.exhibit.name,
|
||||
audioUrl: selection.media.url,
|
||||
image: selection.exhibit.image || heroImage.value,
|
||||
image: heroImage.value,
|
||||
duration: selection.media.duration
|
||||
}
|
||||
|
||||
@@ -235,10 +249,41 @@ const handlePlayAudio = async () => {
|
||||
showAudioPlayer.value = true
|
||||
currentAudio.value = audio
|
||||
await nextTick()
|
||||
uni.showToast({
|
||||
title: '音频已准备好,请点击底部播放按钮',
|
||||
icon: 'none'
|
||||
})
|
||||
audioPlayerRef.value?.play(audio)
|
||||
}
|
||||
|
||||
const handleExpandText = async () => {
|
||||
if (!exhibit.value.id || textLoading.value || textExpanded.value) return
|
||||
|
||||
textLoading.value = true
|
||||
textError.value = ''
|
||||
|
||||
try {
|
||||
const selection = await explainUseCase.loadExplainDetailText({
|
||||
id: exhibit.value.id,
|
||||
name: exhibit.value.title,
|
||||
image: heroImage.value,
|
||||
audioLanguage: exhibit.value.audio.language,
|
||||
audioStatus: exhibit.value.audio.status === 'playable' ? 'READY' : 'MISSING',
|
||||
hallId: exhibit.value.hallId,
|
||||
hallName: exhibit.value.hallName,
|
||||
floorLabel: exhibit.value.floorLabel,
|
||||
location: exhibit.value.location,
|
||||
playTargetType: exhibit.value.audio.playTargetType,
|
||||
playTargetId: exhibit.value.audio.playTargetId,
|
||||
audioHasText: exhibit.value.audio.hasText
|
||||
})
|
||||
|
||||
if (!selection.available) {
|
||||
textError.value = selection.unavailableMessage || '当前语言暂无讲解词'
|
||||
return
|
||||
}
|
||||
|
||||
exhibit.value = toExplainDetailPageViewModel(selection.exhibit)
|
||||
textExpanded.value = true
|
||||
} finally {
|
||||
textLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleAudioVisibleChange = (visible: boolean) => {
|
||||
@@ -250,6 +295,7 @@ const handleAudioVisibleChange = (visible: boolean) => {
|
||||
}
|
||||
|
||||
const handleAudioPlay = () => {
|
||||
retryingAudio.value = false
|
||||
isPlaying.value = true
|
||||
}
|
||||
|
||||
@@ -261,11 +307,43 @@ const handleAudioEnded = () => {
|
||||
isPlaying.value = false
|
||||
showAudioPlayer.value = false
|
||||
currentAudio.value = null
|
||||
retryingAudio.value = false
|
||||
}
|
||||
|
||||
const handleAudioError = (_audio: AudioItem | null, message: string) => {
|
||||
const handleAudioError = async (_audio: AudioItem | null, message: string) => {
|
||||
console.warn('详情音频不可播放:', message)
|
||||
isPlaying.value = false
|
||||
|
||||
if (!retryingAudio.value && exhibit.value.audio.status === 'playable') {
|
||||
retryingAudio.value = true
|
||||
const selection = await explainUseCase.selectAudioForExplainDetail({
|
||||
id: exhibit.value.id,
|
||||
name: exhibit.value.title,
|
||||
image: heroImage.value,
|
||||
audioStatus: 'READY',
|
||||
audioLanguage: exhibit.value.audio.language,
|
||||
playTargetType: exhibit.value.audio.playTargetType,
|
||||
playTargetId: exhibit.value.audio.playTargetId
|
||||
})
|
||||
|
||||
if (selection.playable && selection.media?.url) {
|
||||
const retryAudio: AudioItem = {
|
||||
id: selection.media.id,
|
||||
name: selection.playInfo?.title || selection.exhibit.name,
|
||||
audioUrl: selection.media.url,
|
||||
image: heroImage.value,
|
||||
duration: selection.media.duration
|
||||
}
|
||||
|
||||
currentAudio.value = retryAudio
|
||||
showAudioPlayer.value = true
|
||||
await nextTick()
|
||||
audioPlayerRef.value?.play(retryAudio)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
retryingAudio.value = false
|
||||
showAudioPlayer.value = false
|
||||
currentAudio.value = null
|
||||
exhibit.value = {
|
||||
@@ -485,6 +563,39 @@ const handleBack = () => {
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.text-expand-btn {
|
||||
width: 100%;
|
||||
height: 46px;
|
||||
margin-top: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
background: #151713;
|
||||
border-radius: 8px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.text-expand-btn.disabled {
|
||||
opacity: 0.56;
|
||||
}
|
||||
|
||||
.text-expand-label {
|
||||
display: block;
|
||||
font-size: 15px;
|
||||
line-height: 21px;
|
||||
font-weight: 700;
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
.text-error {
|
||||
display: block;
|
||||
margin-top: 12px;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
color: #8a4a31;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
|
||||
Reference in New Issue
Block a user