Compare commits
2 Commits
eaec80bdc3
...
64ad7acfa8
| Author | SHA1 | Date | |
|---|---|---|---|
| 64ad7acfa8 | |||
| 349f9b03e5 |
@@ -4,10 +4,10 @@
|
|||||||
VITE_APP_PUBLIC_BASE=/
|
VITE_APP_PUBLIC_BASE=/
|
||||||
# 主开关:导览业务与讲解业务分离配置,避免互相切换。
|
# 主开关:导览业务与讲解业务分离配置,避免互相切换。
|
||||||
VITE_GUIDE_DATA_SOURCE_MODE=static
|
VITE_GUIDE_DATA_SOURCE_MODE=static
|
||||||
VITE_EXPLAIN_CONTENT_SOURCE_MODE=static
|
VITE_EXPLAIN_CONTENT_SOURCE_MODE=remote
|
||||||
# 兼容旧变量:新代码优先读取上方显式主开关。
|
# 兼容旧变量:新代码优先读取上方显式主开关。
|
||||||
VITE_DATA_SOURCE_MODE=static
|
VITE_DATA_SOURCE_MODE=static
|
||||||
VITE_GUIDE_CONTENT_SOURCE_MODE=static
|
VITE_GUIDE_CONTENT_SOURCE_MODE=remote
|
||||||
VITE_GUIDE_STATIC_DATA_BASE_URL=/static/guide-data
|
VITE_GUIDE_STATIC_DATA_BASE_URL=/static/guide-data
|
||||||
VITE_API_BASE_URL=/app-api
|
VITE_API_BASE_URL=/app-api
|
||||||
VITE_AUDIO_API_BASE_URL=/app-api
|
VITE_AUDIO_API_BASE_URL=/app-api
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
VITE_APP_PUBLIC_BASE=/
|
VITE_APP_PUBLIC_BASE=/
|
||||||
# 主开关:导览业务与讲解业务分离配置,避免互相切换。
|
# 主开关:导览业务与讲解业务分离配置,避免互相切换。
|
||||||
VITE_GUIDE_DATA_SOURCE_MODE=sdk
|
VITE_GUIDE_DATA_SOURCE_MODE=sdk
|
||||||
VITE_EXPLAIN_CONTENT_SOURCE_MODE=static
|
VITE_EXPLAIN_CONTENT_SOURCE_MODE=remote
|
||||||
# 兼容旧变量:新代码优先读取上方显式主开关。
|
# 兼容旧变量:新代码优先读取上方显式主开关。
|
||||||
VITE_DATA_SOURCE_MODE=sdk
|
VITE_DATA_SOURCE_MODE=sdk
|
||||||
VITE_GUIDE_CONTENT_SOURCE_MODE=static
|
VITE_GUIDE_CONTENT_SOURCE_MODE=remote
|
||||||
VITE_GUIDE_STATIC_DATA_BASE_URL=/static/guide-data
|
VITE_GUIDE_STATIC_DATA_BASE_URL=/static/guide-data
|
||||||
VITE_API_BASE_URL=/app-api
|
VITE_API_BASE_URL=/app-api
|
||||||
VITE_AUDIO_API_BASE_URL=/app-api
|
VITE_AUDIO_API_BASE_URL=/app-api
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
VITE_APP_PUBLIC_BASE=/
|
VITE_APP_PUBLIC_BASE=/
|
||||||
# 主开关:导览业务与讲解业务分离配置,避免互相切换。
|
# 主开关:导览业务与讲解业务分离配置,避免互相切换。
|
||||||
VITE_GUIDE_DATA_SOURCE_MODE=sdk
|
VITE_GUIDE_DATA_SOURCE_MODE=sdk
|
||||||
VITE_EXPLAIN_CONTENT_SOURCE_MODE=static
|
VITE_EXPLAIN_CONTENT_SOURCE_MODE=remote
|
||||||
# 兼容旧变量:新代码优先读取上方显式主开关。
|
# 兼容旧变量:新代码优先读取上方显式主开关。
|
||||||
VITE_DATA_SOURCE_MODE=sdk
|
VITE_DATA_SOURCE_MODE=sdk
|
||||||
VITE_GUIDE_CONTENT_SOURCE_MODE=static
|
VITE_GUIDE_CONTENT_SOURCE_MODE=remote
|
||||||
VITE_GUIDE_STATIC_DATA_BASE_URL=/static/guide-data
|
VITE_GUIDE_STATIC_DATA_BASE_URL=/static/guide-data
|
||||||
VITE_API_BASE_URL=/app-api
|
VITE_API_BASE_URL=/app-api
|
||||||
VITE_AUDIO_API_BASE_URL=/app-api
|
VITE_AUDIO_API_BASE_URL=/app-api
|
||||||
|
|||||||
@@ -168,6 +168,8 @@ export interface ExplainGuideStopSelectItem {
|
|||||||
hasAudio?: boolean
|
hasAudio?: boolean
|
||||||
audioStatus?: string
|
audioStatus?: string
|
||||||
guideLevel?: string
|
guideLevel?: string
|
||||||
|
playTargetType?: 'ITEM' | 'STOP'
|
||||||
|
playTargetId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ExplainSelectStage = 'hall' | 'unit' | 'stop'
|
export type ExplainSelectStage = 'hall' | 'unit' | 'stop'
|
||||||
@@ -195,7 +197,7 @@ const props = withDefaults(defineProps<{
|
|||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
hallClick: [hallId: string]
|
hallClick: [hallId: string]
|
||||||
businessUnitClick: [unitId: string]
|
businessUnitClick: [unitId: string]
|
||||||
guideStopClick: [stopId: string]
|
guideStopClick: [stop: ExplainGuideStopSelectItem]
|
||||||
back: []
|
back: []
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
@@ -272,7 +274,10 @@ const handleBusinessUnitClick = (unitId: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleGuideStopClick = (stopId: string) => {
|
const handleGuideStopClick = (stopId: string) => {
|
||||||
emit('guideStopClick', stopId)
|
const stop = props.guideStops.find((item) => item.id === stopId)
|
||||||
|
if (stop) {
|
||||||
|
emit('guideStopClick', stop)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
|
|||||||
@@ -71,7 +71,16 @@
|
|||||||
|
|
||||||
<view class="content-section">
|
<view class="content-section">
|
||||||
<text class="section-title">讲解内容</text>
|
<text class="section-title">讲解内容</text>
|
||||||
<text class="section-text">{{ exhibit.body || exhibit.summary }}</text>
|
<text class="section-text">{{ detailBodyText }}</text>
|
||||||
|
<view
|
||||||
|
v-if="isTextLoadActionVisible"
|
||||||
|
class="text-load-btn"
|
||||||
|
:class="{ disabled: detailTextLoading }"
|
||||||
|
@tap="handleLoadDetailText"
|
||||||
|
>
|
||||||
|
<text class="text-load-btn-text">{{ detailTextLoading ? '正在加载' : detailTextLoadActionText }}</text>
|
||||||
|
</view>
|
||||||
|
<text v-if="detailTextUnavailableMessage" class="section-hint">{{ detailTextUnavailableMessage }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-if="visibleLinkedExhibits.length" class="content-section">
|
<view v-if="visibleLinkedExhibits.length" class="content-section">
|
||||||
@@ -192,6 +201,9 @@ const resolvedHallId = ref('')
|
|||||||
const retryingAudio = ref(false)
|
const retryingAudio = ref(false)
|
||||||
const selectedAudioLanguage = ref<AudioLanguage>('zh-CN')
|
const selectedAudioLanguage = ref<AudioLanguage>('zh-CN')
|
||||||
const languageLoading = ref(false)
|
const languageLoading = ref(false)
|
||||||
|
const detailTextLoaded = ref(false)
|
||||||
|
const detailTextLoading = ref(false)
|
||||||
|
const detailTextUnavailableMessage = ref('')
|
||||||
const detailEntryRequest = ref<{
|
const detailEntryRequest = ref<{
|
||||||
exhibitId: string
|
exhibitId: string
|
||||||
targetType?: AudioPlayTargetType
|
targetType?: AudioPlayTargetType
|
||||||
@@ -227,6 +239,38 @@ const visibleLanguageOptions = computed(() => (
|
|||||||
languageOptions.filter((option) => supportedDetailLanguages.value.has(option.value))
|
languageOptions.filter((option) => supportedDetailLanguages.value.has(option.value))
|
||||||
))
|
))
|
||||||
const isLanguageSwitchVisible = computed(() => visibleLanguageOptions.value.length > 1)
|
const isLanguageSwitchVisible = computed(() => visibleLanguageOptions.value.length > 1)
|
||||||
|
const hasTextLoadTarget = computed(() => Boolean(
|
||||||
|
exhibit.value.audio.playTargetId || exhibit.value.id
|
||||||
|
))
|
||||||
|
const normalizedDetailSummary = computed(() => exhibit.value.summary.trim())
|
||||||
|
const hasMeaningfulDetailSummary = computed(() => (
|
||||||
|
normalizedDetailSummary.value
|
||||||
|
&& normalizedDetailSummary.value !== defaultDetail.summary
|
||||||
|
))
|
||||||
|
const isDetailSummaryLikelyComplete = computed(() => (
|
||||||
|
hasMeaningfulDetailSummary.value
|
||||||
|
&& normalizedDetailSummary.value.length >= 120
|
||||||
|
))
|
||||||
|
const hasDeferredDetailText = computed(() => (
|
||||||
|
exhibit.value.audio.hasText === true
|
||||||
|
&& !detailTextLoaded.value
|
||||||
|
&& !detailTextUnavailableMessage.value
|
||||||
|
&& hasTextLoadTarget.value
|
||||||
|
&& !isDetailSummaryLikelyComplete.value
|
||||||
|
))
|
||||||
|
const isTextLoadActionVisible = computed(() => hasDeferredDetailText.value)
|
||||||
|
const detailTextLoadActionText = computed(() => (
|
||||||
|
hasMeaningfulDetailSummary.value
|
||||||
|
? '查看完整讲解词'
|
||||||
|
: '加载讲解正文'
|
||||||
|
))
|
||||||
|
const detailBodyText = computed(() => (
|
||||||
|
detailTextLoaded.value
|
||||||
|
? exhibit.value.body || exhibit.value.summary
|
||||||
|
: hasDeferredDetailText.value && !hasMeaningfulDetailSummary.value
|
||||||
|
? '已找到当前语言讲解词,可加载查看完整正文。'
|
||||||
|
: exhibit.value.summary
|
||||||
|
))
|
||||||
|
|
||||||
// 讲解详情页只定位到所属展厅,不再追踪具体展品点位。
|
// 讲解详情页只定位到所属展厅,不再追踪具体展品点位。
|
||||||
const resolveHallGuidePoi = async () => {
|
const resolveHallGuidePoi = async () => {
|
||||||
@@ -258,6 +302,8 @@ const loadExplainDetail = async (
|
|||||||
|
|
||||||
exhibit.value = toExplainDetailPageViewModel(exhibitData)
|
exhibit.value = toExplainDetailPageViewModel(exhibitData)
|
||||||
selectedAudioLanguage.value = lang
|
selectedAudioLanguage.value = lang
|
||||||
|
detailTextLoaded.value = Boolean(exhibitData.audioText)
|
||||||
|
detailTextUnavailableMessage.value = ''
|
||||||
|
|
||||||
if (exhibitData.hallId) {
|
if (exhibitData.hallId) {
|
||||||
resolvedHallId.value = exhibitData.hallId
|
resolvedHallId.value = exhibitData.hallId
|
||||||
@@ -329,6 +375,44 @@ const handleLanguageSwitch = async (lang: AudioLanguage) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleLoadDetailText = async () => {
|
||||||
|
if (detailTextLoading.value || detailTextLoaded.value) return
|
||||||
|
|
||||||
|
detailTextLoading.value = true
|
||||||
|
detailTextUnavailableMessage.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const selection = await explainUseCase.loadExplainDetailText({
|
||||||
|
id: exhibit.value.id,
|
||||||
|
name: exhibit.value.title,
|
||||||
|
hallId: exhibit.value.hallId,
|
||||||
|
hallName: exhibit.value.hallName,
|
||||||
|
floorId: exhibit.value.floorId,
|
||||||
|
floorLabel: exhibit.value.floorLabel,
|
||||||
|
image: heroImage.value,
|
||||||
|
description: exhibit.value.summary,
|
||||||
|
guideText: exhibit.value.summary,
|
||||||
|
audioLanguage: exhibit.value.audio.language,
|
||||||
|
audioHasText: exhibit.value.audio.hasText,
|
||||||
|
playTargetType: exhibit.value.audio.playTargetType,
|
||||||
|
playTargetId: exhibit.value.audio.playTargetId
|
||||||
|
})
|
||||||
|
|
||||||
|
if (selection.available) {
|
||||||
|
exhibit.value = toExplainDetailPageViewModel(selection.exhibit)
|
||||||
|
detailTextLoaded.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
detailTextUnavailableMessage.value = selection.unavailableMessage || '当前语言暂无讲解词'
|
||||||
|
} catch (error) {
|
||||||
|
console.error('讲解正文加载失败:', error)
|
||||||
|
detailTextUnavailableMessage.value = '讲解词服务暂不可用,请稍后重试'
|
||||||
|
} finally {
|
||||||
|
detailTextLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handlePlayAudio = async () => {
|
const handlePlayAudio = async () => {
|
||||||
const selection = exhibit.value.id
|
const selection = exhibit.value.id
|
||||||
? await explainUseCase.selectAudioForExplainDetail({
|
? await explainUseCase.selectAudioForExplainDetail({
|
||||||
@@ -733,6 +817,43 @@ const handleBack = () => {
|
|||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-load-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 44px;
|
||||||
|
margin-top: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: #151713;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #e0df00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-load-btn.disabled {
|
||||||
|
opacity: 0.62;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-load-btn-text {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 21px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: currentColor;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-hint {
|
||||||
|
display: block;
|
||||||
|
margin-top: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
color: #68725d;
|
||||||
|
}
|
||||||
|
|
||||||
.linked-exhibit-list {
|
.linked-exhibit-list {
|
||||||
margin-top: 14px;
|
margin-top: 14px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -114,7 +114,9 @@ const explainGuideStopItems = computed<ExplainGuideStopSelectItem[]>(() => (
|
|||||||
description: stop.description,
|
description: stop.description,
|
||||||
hasAudio: stop.hasAudio,
|
hasAudio: stop.hasAudio,
|
||||||
audioStatus: stop.audioStatus,
|
audioStatus: stop.audioStatus,
|
||||||
guideLevel: stop.guideLevel
|
guideLevel: stop.guideLevel,
|
||||||
|
playTargetType: stop.playTargetType,
|
||||||
|
playTargetId: stop.playTargetId
|
||||||
})) || []
|
})) || []
|
||||||
))
|
))
|
||||||
|
|
||||||
@@ -191,10 +193,15 @@ const handleExplainBusinessUnitClick = (unitId: string) => {
|
|||||||
explainStage.value = 'stop'
|
explainStage.value = 'stop'
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleExplainGuideStopClick = (stopId: string) => {
|
const handleExplainGuideStopClick = (stop: ExplainGuideStopSelectItem) => {
|
||||||
const target = normalizeExplainDetailTargetFromGuideStop({ id: stopId })
|
const target = stop.playTargetType && stop.playTargetId
|
||||||
|
? {
|
||||||
|
targetType: stop.playTargetType,
|
||||||
|
targetId: stop.playTargetId
|
||||||
|
}
|
||||||
|
: normalizeExplainDetailTargetFromGuideStop({ id: stop.id })
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
id: target.targetId,
|
id: stop.id,
|
||||||
tab: 'explain',
|
tab: 'explain',
|
||||||
targetType: target.targetType,
|
targetType: target.targetType,
|
||||||
targetId: target.targetId
|
targetId: target.targetId
|
||||||
|
|||||||
@@ -730,7 +730,9 @@ const explainGuideStopItems = computed<ExplainGuideStopSelectItem[]>(() => (
|
|||||||
description: stop.description,
|
description: stop.description,
|
||||||
hasAudio: stop.hasAudio,
|
hasAudio: stop.hasAudio,
|
||||||
audioStatus: stop.audioStatus,
|
audioStatus: stop.audioStatus,
|
||||||
guideLevel: stop.guideLevel
|
guideLevel: stop.guideLevel,
|
||||||
|
playTargetType: stop.playTargetType,
|
||||||
|
playTargetId: stop.playTargetId
|
||||||
})) || []
|
})) || []
|
||||||
))
|
))
|
||||||
|
|
||||||
@@ -1551,10 +1553,15 @@ const handleExplainBusinessUnitClick = (unitId: string) => {
|
|||||||
explainStage.value = 'stop'
|
explainStage.value = 'stop'
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleExplainGuideStopClick = (stopId: string) => {
|
const handleExplainGuideStopClick = (stop: ExplainGuideStopSelectItem) => {
|
||||||
const target = normalizeExplainDetailTargetFromGuideStop({ id: stopId })
|
const target = stop.playTargetType && stop.playTargetId
|
||||||
|
? {
|
||||||
|
targetType: stop.playTargetType,
|
||||||
|
targetId: stop.playTargetId
|
||||||
|
}
|
||||||
|
: normalizeExplainDetailTargetFromGuideStop({ id: stop.id })
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
id: target.targetId,
|
id: stop.id,
|
||||||
tab: 'explain',
|
tab: 'explain',
|
||||||
targetType: target.targetType,
|
targetType: target.targetType,
|
||||||
targetId: target.targetId
|
targetId: target.targetId
|
||||||
|
|||||||
@@ -119,7 +119,9 @@ const normalizeBaseUrl = (baseUrl: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const resolveAudioApiBaseUrl = () => {
|
const resolveAudioApiBaseUrl = () => {
|
||||||
const baseUrl = normalizeBaseUrl(dataSourceConfig.apiBaseUrl)
|
// Catalog APIs use apiBaseUrl in BackendExplainContentProvider; stop/detail/play/text APIs use audioApiBaseUrl.
|
||||||
|
// They are both /app-api today, but audioApiBaseUrl can be split to a dedicated proxy or host later.
|
||||||
|
const baseUrl = normalizeBaseUrl(dataSourceConfig.audioApiBaseUrl)
|
||||||
return baseUrl.endsWith('/app-api') ? baseUrl : `${baseUrl}/app-api`
|
return baseUrl.endsWith('/app-api') ? baseUrl : `${baseUrl}/app-api`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -394,7 +394,7 @@ export class ExplainUseCase {
|
|||||||
const entryTarget = await this.resolveDetailEntryTarget(request)
|
const entryTarget = await this.resolveDetailEntryTarget(request)
|
||||||
const fallbackExhibit = await this.resolveStaticDetailFallback(request, entryTarget)
|
const fallbackExhibit = await this.resolveStaticDetailFallback(request, entryTarget)
|
||||||
|
|
||||||
if (fallbackExhibit) {
|
if (fallbackExhibit && dataSourceConfig.explainContentMode !== 'remote') {
|
||||||
return this.applyLanguageVariant(fallbackExhibit, entryTarget.lang)
|
return this.applyLanguageVariant(fallbackExhibit, entryTarget.lang)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,7 +403,7 @@ export class ExplainUseCase {
|
|||||||
.catch((error) => ({ stopInfo: null, error }))
|
.catch((error) => ({ stopInfo: null, error }))
|
||||||
|
|
||||||
if (stopInfoResult.stopInfo) {
|
if (stopInfoResult.stopInfo) {
|
||||||
return this.toExhibitFromStopInfo(stopInfoResult.stopInfo, null)
|
return this.toExhibitFromStopInfo(stopInfoResult.stopInfo, fallbackExhibit)
|
||||||
}
|
}
|
||||||
|
|
||||||
throw stopInfoResult.error || new Error('讲解详情加载失败')
|
throw stopInfoResult.error || new Error('讲解详情加载失败')
|
||||||
|
|||||||
Reference in New Issue
Block a user