优化首页搜索与讲解详情语言切换
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
lyf
2026-07-09 15:01:33 +08:00
parent 68336fb443
commit eac4b74bdc
2 changed files with 82 additions and 43 deletions

View File

@@ -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 {

View File

@@ -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()}`
}