chore: freeze guide and explain update

This commit is contained in:
lyf
2026-06-24 18:00:25 +08:00
parent feb7310a46
commit 67c6609ae6
104 changed files with 3203572 additions and 40713 deletions

221
src/pages/explain/list.vue Normal file
View File

@@ -0,0 +1,221 @@
<template>
<GuidePageFrame
active-tab="explain"
variant="static"
show-back
back-label="讲解首页"
@tab-change="handleTopTabChange"
@back="handleBack"
>
<view class="explain-all-page">
<ExplainList
:cards="explainExhibits"
:loading="explainLoading"
:error="explainError"
list-title="全部讲解"
page-mode="all"
:initial-render-limit="48"
:page-size="48"
@exhibit-click="handleExplainExhibitClick"
@featured-click="handleExplainFeaturedClick"
@audio-click="handleAudioClick"
@location-click="handleExplainLocationClick"
@hall-click="handleExplainHallClick"
/>
<AudioPlayer
:visible="showAudioPlayer"
:audio="currentAudio"
:auto-play="true"
@update:visible="handleAudioVisibleChange"
@play="handleAudioPlay"
@pause="handleAudioPause"
@ended="handleAudioEnded"
@error="handleAudioError"
/>
</view>
</GuidePageFrame>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import GuidePageFrame from '@/components/navigation/GuidePageFrame.vue'
import ExplainList, { type Exhibit } from '@/components/explain/ExplainList.vue'
import AudioPlayer, { type AudioItem } from '@/components/audio/AudioPlayer.vue'
import {
explainUseCase
} from '@/usecases/explainUseCase'
import {
guideUseCase
} from '@/usecases/guideUseCase'
import {
toExplainCardViewModel
} from '@/view-models/explainViewModels'
import {
navigateToGuideTopTab,
type GuideTopTab
} from '@/utils/guideTopTabs'
const explainExhibits = ref<Exhibit[]>([])
const explainLoading = ref(false)
const explainError = ref('')
const showAudioPlayer = ref(false)
const isAudioPlaying = ref(false)
const currentAudio = ref<AudioItem | null>(null)
let explainLoadPromise: Promise<void> | null = null
const loadExplainExhibits = async () => {
if (explainExhibits.value.length) return
if (explainLoadPromise) return explainLoadPromise
explainLoading.value = true
explainError.value = ''
explainLoadPromise = (async () => {
try {
const exhibits = await explainUseCase.listExplainExhibits()
explainExhibits.value = exhibits.map(toExplainCardViewModel)
} catch (error) {
console.error('加载全部讲解内容失败:', error)
explainError.value = '讲解内容加载失败,请稍后重试'
explainExhibits.value = []
} finally {
explainLoading.value = false
explainLoadPromise = null
}
})()
return explainLoadPromise
}
onLoad(() => {
void loadExplainExhibits()
})
const handleBack = () => {
uni.navigateBack({
delta: 1,
fail: () => {
uni.reLaunch({
url: '/pages/index/index?tab=explain'
})
}
})
}
const handleTopTabChange = (tab: GuideTopTab) => {
navigateToGuideTopTab(tab)
}
const handleExplainExhibitClick = (exhibit: Exhibit) => {
uni.navigateTo({
url: `/pages/exhibit/detail?id=${exhibit.id}&tab=explain`
})
}
const handleExplainFeaturedClick = async (exhibit: Exhibit) => {
await handleAudioClick(exhibit)
}
const handleExplainHallClick = (hallId: string) => {
uni.navigateTo({
url: `/pages/hall/detail?id=${encodeURIComponent(hallId)}&tab=explain`
})
}
const handleAudioClick = async (exhibit: Exhibit) => {
if (exhibit.audioUrl) {
currentAudio.value = {
id: `media-${exhibit.id}`,
name: exhibit.title,
audioUrl: exhibit.audioUrl,
image: exhibit.coverImage,
duration: exhibit.audioDuration
}
showAudioPlayer.value = true
return
}
const selection = await explainUseCase.selectAudioForExhibit(exhibit.id)
if (!selection?.playable || !selection.media?.url) {
uni.showToast({
title: selection?.unavailableMessage || exhibit.audioStatusText || '该讲解暂无可播放音频',
icon: 'none'
})
return
}
currentAudio.value = {
id: selection.media.id,
name: selection.track?.title || selection.exhibit.name,
audioUrl: selection.media.url,
image: selection.exhibit.image,
duration: selection.media.duration
}
showAudioPlayer.value = true
}
const clearAudioState = () => {
showAudioPlayer.value = false
isAudioPlaying.value = false
currentAudio.value = null
}
const handleAudioVisibleChange = (visible: boolean) => {
showAudioPlayer.value = visible
if (!visible) {
isAudioPlaying.value = false
currentAudio.value = null
}
}
const handleAudioPlay = () => {
isAudioPlaying.value = true
}
const handleAudioPause = () => {
isAudioPlaying.value = false
}
const handleAudioEnded = () => {
clearAudioState()
}
const handleAudioError = (_audio: AudioItem | null, message: string) => {
console.warn('全部讲解音频不可播放:', message)
clearAudioState()
}
const handleExplainLocationClick = async (exhibit: Exhibit) => {
if (!exhibit.poiId) {
uni.showToast({
title: '该讲解暂无三维位置数据',
icon: 'none'
})
return
}
const matchedPoi = await guideUseCase.getPoiById(exhibit.poiId)
if (!matchedPoi) {
uni.showToast({
title: '该讲解位置尚未映射到当前三维导览资源',
icon: 'none'
})
return
}
uni.navigateTo({
url: `/pages/route/detail?facilityId=${encodeURIComponent(exhibit.poiId)}&target=${encodeURIComponent(exhibit.title)}&state=preview`
})
}
</script>
<style scoped lang="scss">
.explain-all-page {
position: absolute;
inset: 0;
background: #edf0ea;
}
</style>