This commit is contained in:
@@ -796,11 +796,24 @@ defineExpose({
|
||||
}
|
||||
|
||||
.home-back-icon {
|
||||
font-size: 30px;
|
||||
line-height: 30px;
|
||||
display: flex;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0;
|
||||
line-height: 0;
|
||||
font-weight: 600;
|
||||
color: #151713;
|
||||
transform: translateX(-1px);
|
||||
}
|
||||
|
||||
.home-back-icon::before {
|
||||
content: '';
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-left: 2px solid currentColor;
|
||||
border-bottom: 2px solid currentColor;
|
||||
transform: translateX(2px) rotate(45deg);
|
||||
}
|
||||
|
||||
.home-fullscreen-title {
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
class="content"
|
||||
:class="{ 'with-fixed-player': isDetailAudioDockVisible || showAudioPlayer }"
|
||||
scroll-y
|
||||
scroll-with-animation
|
||||
:scroll-into-view="scrollTargetId"
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<view class="immersive-hero" :class="{ 'has-real-image': heroImage }">
|
||||
@@ -44,8 +42,8 @@
|
||||
v-for="option in contentLanguageOptions"
|
||||
:key="option.value"
|
||||
class="language-anchor-option"
|
||||
:class="{ active: activeContentLanguage === option.value }"
|
||||
@tap="handleAnchorTap(option.value)"
|
||||
:class="{ active: selectedAudioLanguage === option.value }"
|
||||
@tap="handleLanguageChange(option.value)"
|
||||
>
|
||||
<text class="language-anchor-text">{{ option.label }}</text>
|
||||
</view>
|
||||
@@ -54,18 +52,9 @@
|
||||
</view>
|
||||
|
||||
<view class="content-panel">
|
||||
<view
|
||||
v-for="section in contentLanguageOptions"
|
||||
:id="section.anchorId"
|
||||
:key="section.value"
|
||||
class="content-section"
|
||||
>
|
||||
<view class="section-heading">
|
||||
<view class="section-marker" :class="{ english: section.value === 'en-US' }"></view>
|
||||
<text class="section-title">{{ section.heading }}</text>
|
||||
</view>
|
||||
<text class="section-text">{{ detailTextFor(section.value) }}</text>
|
||||
<text v-if="detailTextLoadingByLanguage[section.value]" class="section-hint">正在加载讲解正文</text>
|
||||
<view class="content-section">
|
||||
<text class="section-text">{{ currentDetailText }}</text>
|
||||
<text v-if="currentDetailTextLoading" class="section-hint">正在加载讲解正文</text>
|
||||
</view>
|
||||
|
||||
<view v-if="visibleLinkedExhibits.length" class="content-section">
|
||||
@@ -157,7 +146,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, ref, watch } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import GuidePageFrame from '@/components/navigation/GuidePageFrame.vue'
|
||||
import type { AudioItem } from '@/components/audio/AudioPlayer.vue'
|
||||
@@ -213,8 +202,6 @@ const detailEntryRequest = ref<{
|
||||
targetType?: AudioPlayTargetType
|
||||
targetId?: string
|
||||
} | null>(null)
|
||||
const scrollTargetId = ref('')
|
||||
const activeContentLanguage = ref<AudioLanguage>('zh-CN')
|
||||
const detailTextByLanguage = ref<Record<AudioLanguage, string>>({
|
||||
'zh-CN': defaultDetail.body,
|
||||
'en-US': 'English narration text is not available yet.'
|
||||
@@ -223,26 +210,24 @@ const detailTextLoadingByLanguage = ref<Record<AudioLanguage, boolean>>({
|
||||
'zh-CN': false,
|
||||
'en-US': false
|
||||
})
|
||||
const detailTextLoadedByLanguage = ref<Record<AudioLanguage, boolean>>({
|
||||
'zh-CN': false,
|
||||
'en-US': false
|
||||
})
|
||||
// 讲解详情页位置入口暂未开放,避免误导用户进入位置预览流程。
|
||||
const isLocationActionVisible = false
|
||||
|
||||
const contentLanguageOptions: Array<{
|
||||
value: AudioLanguage
|
||||
label: string
|
||||
heading: string
|
||||
anchorId: string
|
||||
}> = [
|
||||
{
|
||||
value: 'zh-CN',
|
||||
label: '中文',
|
||||
heading: '中文讲解',
|
||||
anchorId: 'zh-section'
|
||||
label: '中文'
|
||||
},
|
||||
{
|
||||
value: 'en-US',
|
||||
label: 'English',
|
||||
heading: 'English',
|
||||
anchorId: 'en-section'
|
||||
label: 'English'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -315,7 +300,17 @@ const detailAudioLanguageOptions = computed(() => (
|
||||
audioLanguageOptions.filter((option) => supportedDetailAudioLanguages.value.includes(option.value))
|
||||
))
|
||||
const isAudioLanguageSwitchVisible = computed(() => detailAudioLanguageOptions.value.length > 1)
|
||||
const isCurrentDetailAudioTarget = computed(() => {
|
||||
const source = globalAudioPlayer.currentSource.value
|
||||
const targetType = exhibit.value.audio.playTargetType || 'ITEM'
|
||||
const targetId = exhibit.value.audio.playTargetId || exhibit.value.id
|
||||
|
||||
return Boolean(
|
||||
globalAudioPlayer.currentAudio.value
|
||||
&& source?.targetType === targetType
|
||||
&& source.targetId === targetId
|
||||
)
|
||||
})
|
||||
// 讲解详情页只定位到所属展厅,不再追踪具体展品点位。
|
||||
const resolveHallGuidePoi = async () => {
|
||||
return guideUseCase.resolveContentLocationPreviewTarget({
|
||||
@@ -339,6 +334,10 @@ const applyDetailText = (lang: AudioLanguage, text?: string) => {
|
||||
...detailTextByLanguage.value,
|
||||
[lang]: normalized || fallbackTextForLanguage(lang)
|
||||
}
|
||||
detailTextLoadedByLanguage.value = {
|
||||
...detailTextLoadedByLanguage.value,
|
||||
[lang]: true
|
||||
}
|
||||
}
|
||||
|
||||
const buildTextSourceFromViewModel = (viewModel: ExplainDetailPageViewModel) => ({
|
||||
@@ -404,6 +403,16 @@ const loadBilingualDetailText = async (
|
||||
void loadFullDetailTextForLanguage(request, nextLang)
|
||||
}
|
||||
|
||||
const ensureDetailTextForLanguage = async (lang: AudioLanguage) => {
|
||||
if (detailTextLoadedByLanguage.value[lang] || detailTextLoadingByLanguage.value[lang]) return
|
||||
if (!detailEntryRequest.value) {
|
||||
applyDetailText(lang)
|
||||
return
|
||||
}
|
||||
|
||||
await loadFullDetailTextForLanguage(detailEntryRequest.value, lang)
|
||||
}
|
||||
|
||||
const loadExplainDetail = async (
|
||||
request: NonNullable<typeof detailEntryRequest.value>,
|
||||
lang: AudioLanguage
|
||||
@@ -461,22 +470,37 @@ onLoad(async (options: any = {}) => {
|
||||
}
|
||||
})
|
||||
|
||||
const handleAnchorTap = async (lang: AudioLanguage) => {
|
||||
activeContentLanguage.value = lang
|
||||
scrollTargetId.value = ''
|
||||
await nextTick()
|
||||
scrollTargetId.value = lang === 'en-US' ? 'en-section' : 'zh-section'
|
||||
}
|
||||
|
||||
const detailTextFor = (lang: AudioLanguage) => {
|
||||
return detailTextByLanguage.value[lang] || fallbackTextForLanguage(lang)
|
||||
}
|
||||
const currentDetailText = computed(() => detailTextFor(selectedAudioLanguage.value))
|
||||
const currentDetailTextLoading = computed(() => detailTextLoadingByLanguage.value[selectedAudioLanguage.value])
|
||||
|
||||
const replaceDetailRouteLanguage = (lang: AudioLanguage) => {
|
||||
if (typeof window === 'undefined') return
|
||||
|
||||
const nextHash = `#${buildDetailRoute(lang)}`
|
||||
if (window.location.hash === nextHash) return
|
||||
|
||||
window.history.replaceState(null, '', `${window.location.pathname}${window.location.search}${nextHash}`)
|
||||
}
|
||||
|
||||
const handleLanguageChange = async (lang: AudioLanguage, options: { syncAudio?: boolean } = {}) => {
|
||||
if (lang === selectedAudioLanguage.value) return
|
||||
|
||||
const shouldSyncAudio = options.syncAudio !== false && isCurrentDetailAudioTarget.value
|
||||
selectedAudioLanguage.value = lang
|
||||
replaceDetailRouteLanguage(lang)
|
||||
void ensureDetailTextForLanguage(lang)
|
||||
|
||||
if (shouldSyncAudio && globalAudioPlayer.currentSource.value?.lang !== lang) {
|
||||
await globalAudioPlayer.switchLanguage(lang)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDetailAudioLanguageChange = (lang: AudioLanguage) => {
|
||||
if (lang === selectedAudioLanguage.value) return
|
||||
if (!supportedDetailAudioLanguages.value.includes(lang)) return
|
||||
|
||||
selectedAudioLanguage.value = lang
|
||||
void handleLanguageChange(lang)
|
||||
}
|
||||
|
||||
watch(
|
||||
@@ -492,11 +516,13 @@ watch(
|
||||
&& source.targetId === targetId
|
||||
) {
|
||||
selectedAudioLanguage.value = lang
|
||||
replaceDetailRouteLanguage(lang)
|
||||
void ensureDetailTextForLanguage(lang)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const buildDetailRoute = () => {
|
||||
function buildDetailRoute(lang: AudioLanguage = selectedAudioLanguage.value) {
|
||||
const request = detailEntryRequest.value
|
||||
const params = new URLSearchParams({
|
||||
id: request?.exhibitId || exhibit.value.id,
|
||||
@@ -508,8 +534,8 @@ const buildDetailRoute = () => {
|
||||
if (currentAudioTarget.value.targetId) {
|
||||
params.set('targetId', currentAudioTarget.value.targetId)
|
||||
}
|
||||
if (selectedAudioLanguage.value) {
|
||||
params.set('lang', selectedAudioLanguage.value)
|
||||
if (lang) {
|
||||
params.set('lang', lang)
|
||||
}
|
||||
return `/pages/exhibit/detail?${params.toString()}`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user