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

View File

@@ -2,9 +2,11 @@
<GuidePageFrame
:active-tab="activeTopTab"
variant="static"
show-back
@tab-change="handleTopTabChange"
@back="handleBack"
>
<view class="detail-page">
<view class="detail-page" :class="{ 'with-audio-player': showAudioPlayer }">
<scroll-view class="content" scroll-y>
<view class="detail-hero">
<image
@@ -59,7 +61,7 @@
</view>
<view v-if="exhibit.audio.status !== 'playable'" class="audio-note">
<text class="audio-note-title">音频待同步</text>
<text class="audio-note-title">{{ audioNoteTitle }}</text>
<text class="audio-note-desc">
{{ exhibit.audio.unavailableReason || '当前仅提供图文讲解,正式音频接入后将显示播放入口。' }}
</text>
@@ -107,7 +109,7 @@
<view v-if="exhibit.location" class="location-note">
<text class="location-title">位置说明</text>
<text class="location-desc">当前支持三维位置预览馆内路线规划待开放</text>
<text class="location-desc">{{ locationDescription }}</text>
</view>
</view>
</scroll-view>
@@ -143,14 +145,27 @@
<text class="btn-text">分享</text>
</view>
</view>
<AudioPlayer
ref="audioPlayerRef"
: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 { computed, ref } from 'vue'
import { computed, nextTick, ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import GuidePageFrame from '@/components/navigation/GuidePageFrame.vue'
import AudioPlayer, { type AudioItem } from '@/components/audio/AudioPlayer.vue'
import {
explainUseCase
} from '@/usecases/explainUseCase'
@@ -174,17 +189,25 @@ const defaultDetail: ExplainDetailPageViewModel = {
subtitle: '位置待补充',
contentType: 'exhibit',
coverImages: ['/static/exhibit-placeholder.jpg'],
summary: '该讲解内容待 SGS 场景设置或 CMS 内容补充。',
body: '该讲解内容待 SGS 场景设置或 CMS 内容补充。',
summary: '该讲解内容待补充。',
body: '该讲解内容待补充。',
audio: {
status: 'unavailable',
unavailableReason: 'SGS 场景设置音频资源尚未同步到当前 H5 项目'
unavailableReason: '该展项暂无已发布音频,当前提供图文讲解。'
},
chapters: [],
relatedItems: []
}
interface AudioPlayerExpose {
play: (audio: AudioItem) => void
pause: () => void
}
const exhibit = ref<ExplainDetailPageViewModel>(defaultDetail)
const audioPlayerRef = ref<AudioPlayerExpose | null>(null)
const showAudioPlayer = ref(false)
const currentAudio = ref<AudioItem | null>(null)
const isPlaying = ref(false)
const isCollected = ref(false)
const activeTopTab = ref<GuideTopTab>('guide')
@@ -197,9 +220,15 @@ const audioStatusText = computed(() => {
if (exhibit.value.audio.status === 'none') {
return '图文讲解'
}
return exhibit.value.audio.unavailableReason?.includes('包含音频地址') ? '音频待同步' : '图文讲解'
return '图文讲解'
})
const audioNoteTitle = computed(() => (
exhibit.value.audio.status === 'playable'
? '音频讲解'
: '图文讲解'
))
const contentTypeText = computed(() => {
const map: Record<ExplainContentType, string> = {
hall: '空间讲解',
@@ -210,6 +239,10 @@ const contentTypeText = computed(() => {
return map[exhibit.value.contentType]
})
const locationDescription = computed(() => (
exhibit.value.location?.note || '当前支持三维位置预览。'
))
onLoad(async (options: any) => {
if (options.id) {
const exhibitData = await explainUseCase.getExhibitById(options.id)
@@ -231,6 +264,31 @@ const formatChapterTime = (seconds: number) => {
}
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
await nextTick()
if (!isPlaying.value) {
audioPlayerRef.value?.play(audio)
}
return
}
const selection = exhibit.value.id
? await explainUseCase.selectAudioForExhibit(exhibit.value.id)
: null
@@ -243,7 +301,56 @@ const handlePlayAudio = async () => {
return
}
isPlaying.value = !isPlaying.value
const audio: AudioItem = {
id: selection.media.id,
name: selection.track?.title || selection.exhibit.name,
audioUrl: selection.media.url,
image: selection.exhibit.image || heroImage.value,
duration: selection.media.duration
}
const isSameAudio = currentAudio.value?.id === audio.id
if (showAudioPlayer.value && isSameAudio && isPlaying.value) {
audioPlayerRef.value?.pause()
return
}
showAudioPlayer.value = true
currentAudio.value = audio
await nextTick()
if (!isPlaying.value) {
audioPlayerRef.value?.play(audio)
}
}
const handleAudioVisibleChange = (visible: boolean) => {
showAudioPlayer.value = visible
if (!visible) {
isPlaying.value = false
currentAudio.value = null
}
}
const handleAudioPlay = () => {
isPlaying.value = true
}
const handleAudioPause = () => {
isPlaying.value = false
}
const handleAudioEnded = () => {
isPlaying.value = false
showAudioPlayer.value = false
currentAudio.value = null
}
const handleAudioError = (_audio: AudioItem | null, message: string) => {
console.warn('详情音频不可播放:', message)
isPlaying.value = false
showAudioPlayer.value = false
currentAudio.value = null
}
const handleNavigate = async () => {
@@ -284,6 +391,17 @@ const handleShare = () => {
const handleTopTabChange = (tab: GuideTopTab) => {
navigateToGuideTopTab(tab)
}
const handleBack = () => {
uni.navigateBack({
delta: 1,
fail: () => {
uni.reLaunch({
url: '/pages/index/index?tab=explain'
})
}
})
}
</script>
<style scoped lang="scss">
@@ -530,6 +648,10 @@ const handleTopTabChange = (tab: GuideTopTab) => {
border-top: 1px solid #e7e9e0;
}
.detail-page.with-audio-player .action-bar {
margin-bottom: 88px;
}
.action-btn {
height: 46px;
display: flex;