feat: 支持讲解详情页粤语语音切换
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
lyf
2026-07-14 18:25:45 +08:00
parent 04c6ccd40b
commit d813f0b375
12 changed files with 291 additions and 70 deletions

View File

@@ -14,6 +14,10 @@ import {
EXHIBIT_PLACEHOLDER_IMAGE,
HALL_PLACEHOLDER_IMAGE
} from '@/utils/placeholders'
import {
normalizeGuideAudioLanguage,
type GuideAudioLanguage
} from '@/data/adapters/guideStopInfoAdapter'
export interface BackendGuideContent {
id?: string | number | null
@@ -251,8 +255,12 @@ const buildTags = (exhibit: BackendExhibit) => Array.from(new Set([
firstText(exhibit.origin)
].filter(Boolean)))
const normalizeSupportedLanguages = (languages: string[] | null | undefined) => (
Array.isArray(languages) ? languages.map(String).filter(Boolean) : []
const normalizeSupportedLanguages = (languages: string[] | null | undefined): GuideAudioLanguage[] => (
Array.from(new Set((languages || [])
.map(String)
.filter((language) => ['zh', 'zh-cn', 'en', 'en-us', 'yue', 'yue-cn', 'yue-hk']
.includes(language.trim().toLowerCase()))
.map(normalizeGuideAudioLanguage)))
)
const normalizeCatalogAudioStatus = (status?: string | null) => (
@@ -496,7 +504,8 @@ export const toBackendMuseumExhibit = (
const audioUrl = options.includeDetail ? resolveGuideAudioUrl(guide, source) : normalizeSameOriginPublicUrl(firstText(source.audioUrl))
const targetType = normalizeAudioTargetType(guide?.targetType || source.playTargetType)
const targetId = stringifyId(guide?.targetId || source.playTargetId)
const supportedLanguage = source.supportedLanguages?.find((language) => language === 'zh-CN' || language === 'en-US')
const supportedLanguages = normalizeSupportedLanguages(source.supportedLanguages)
const supportedLanguage = supportedLanguages[0]
const metadataSuggestsAudio = source.hasAudio === true && isAudioReady(source.audioStatus)
const audioAvailable = Boolean(audioUrl)
const sourcePoiId = stringifyId(source.poiId)
@@ -528,7 +537,7 @@ export const toBackendMuseumExhibit = (
audioHasText: options.includeDetail ? Boolean(guideText) : undefined,
audioAvailable: audioAvailable || metadataSuggestsAudio,
audioStatus: source.audioStatus || undefined,
supportedLanguages: source.supportedLanguages || undefined,
supportedLanguages: supportedLanguages.length ? supportedLanguages : undefined,
audioUnavailableReason: audioAvailable || metadataSuggestsAudio
? undefined
: '该讲解暂无已发布音频,当前提供图文讲解。',

View File

@@ -5,6 +5,8 @@ import {
normalizeSameOriginPublicUrl
} from '@/utils/publicUrl'
export type GuideAudioLanguage = 'zh-CN' | 'yue-HK' | 'en-US'
export interface BackendGuideStopLinkedExhibit {
id?: string | number | null
name?: string | null
@@ -97,7 +99,7 @@ export interface GuideStopInfo {
floorId?: string
mapX?: number
mapY?: number
lang: 'zh-CN' | 'en-US'
lang: GuideAudioLanguage
title: string
description?: string
coverImageUrl?: string
@@ -109,7 +111,7 @@ export interface GuideStopInfo {
playTargetId: string
hasAudio: boolean
hasText: boolean
supportedLanguages: string[]
supportedLanguages: GuideAudioLanguage[]
audioStatus: 'READY' | 'MISSING' | string
reason?: string
linkedExhibitCount?: number
@@ -120,7 +122,7 @@ export interface GuideAudioPlayInfo {
playable: boolean
targetType: AudioPlayTargetType
targetId: string
lang: 'zh-CN' | 'en-US'
lang: GuideAudioLanguage
narrationTier?: 'STANDARD' | 'EXTENDED'
audioId?: string
title?: string
@@ -139,7 +141,7 @@ export interface GuideAudioTextInfo {
available: boolean
targetType: AudioPlayTargetType
targetId: string
lang: 'zh-CN' | 'en-US'
lang: GuideAudioLanguage
narrationTier?: 'STANDARD' | 'EXTENDED'
title?: string
text?: string
@@ -166,8 +168,25 @@ const normalizeTargetType = (value: string | null | undefined, fallback: AudioPl
return normalized === 'ITEM' || normalized === 'STOP' ? normalized : fallback
}
const normalizeLanguage = (value: string | null | undefined): 'zh-CN' | 'en-US' => (
value === 'en-US' ? 'en-US' : 'zh-CN'
export const normalizeGuideAudioLanguage = (
value: string | null | undefined
): GuideAudioLanguage => {
const normalized = value?.trim().toLowerCase()
if (normalized === 'en' || normalized === 'en-us') return 'en-US'
if (normalized === 'yue' || normalized === 'yue-cn' || normalized === 'yue-hk') return 'yue-HK'
return 'zh-CN'
}
const normalizeSupportedLanguages = (languages: string[] | null | undefined): GuideAudioLanguage[] => (
Array.from(new Set((languages || [])
.map((language) => {
const normalized = language?.trim().toLowerCase()
if (!['zh', 'zh-cn', 'en', 'en-us', 'yue', 'yue-cn', 'yue-hk'].includes(normalized)) {
return null
}
return normalizeGuideAudioLanguage(language)
})
.filter(Boolean))) as GuideAudioLanguage[]
)
const parseGalleryUrls = (value: BackendGuideStopInfo['galleryUrls'] | BackendGuideStopLinkedExhibit['galleryUrls']) => {
@@ -223,7 +242,7 @@ export const toGuideStopInfo = (
fallback: {
targetType: AudioPlayTargetType
targetId: string
lang: 'zh-CN' | 'en-US'
lang: GuideAudioLanguage
}
): GuideStopInfo => {
const targetType = normalizeTargetType(source.targetType, fallback.targetType)
@@ -246,7 +265,7 @@ export const toGuideStopInfo = (
floorId: stringifyId(source.floorId) || undefined,
mapX: normalizeNumber(source.mapX),
mapY: normalizeNumber(source.mapY),
lang: normalizeLanguage(source.lang || fallback.lang),
lang: normalizeGuideAudioLanguage(source.lang || fallback.lang),
title: source.title?.trim() || '讲解内容',
description: source.description?.trim() || undefined,
coverImageUrl,
@@ -258,7 +277,7 @@ export const toGuideStopInfo = (
playTargetId,
hasAudio: source.hasAudio === true,
hasText: source.hasText === true,
supportedLanguages: source.supportedLanguages || [],
supportedLanguages: normalizeSupportedLanguages(source.supportedLanguages),
audioStatus: source.audioStatus || 'MISSING',
reason: source.reason || undefined,
linkedExhibitCount: normalizeNumber(source.linkedExhibitCount),
@@ -271,7 +290,7 @@ export const toGuideAudioPlayInfo = (
fallback: {
targetType: AudioPlayTargetType
targetId: string
lang: 'zh-CN' | 'en-US'
lang: GuideAudioLanguage
}
): GuideAudioPlayInfo => {
const targetType = normalizeTargetType(source.targetType, fallback.targetType)
@@ -281,7 +300,7 @@ export const toGuideAudioPlayInfo = (
playable: source.playable === true,
targetType,
targetId,
lang: normalizeLanguage(source.lang || fallback.lang),
lang: normalizeGuideAudioLanguage(source.lang || fallback.lang),
narrationTier: normalizeNarrationTier(source.narrationTier),
audioId: stringifyId(source.audioId) || undefined,
title: source.title?.trim() || undefined,
@@ -302,7 +321,7 @@ export const toGuideAudioTextInfo = (
fallback: {
targetType: AudioPlayTargetType
targetId: string
lang: 'zh-CN' | 'en-US'
lang: GuideAudioLanguage
}
): GuideAudioTextInfo => {
const targetType = normalizeTargetType(source.targetType, fallback.targetType)
@@ -312,7 +331,7 @@ export const toGuideAudioTextInfo = (
available: source.available === true,
targetType,
targetId,
lang: normalizeLanguage(source.lang || fallback.lang),
lang: normalizeGuideAudioLanguage(source.lang || fallback.lang),
narrationTier: normalizeNarrationTier(source.narrationTier),
title: source.title?.trim() || undefined,
text: source.text || undefined,

View File

@@ -73,9 +73,7 @@ const requestJson = <T>(url: string): Promise<T> => new Promise((resolve, reject
const normalizeKeyword = (keyword = '') => keyword.trim()
const catalogLang = () => (
dataSourceConfig.audioLanguage === 'en-US' ? 'en-US' : 'zh-CN'
)
const catalogLang = () => dataSourceConfig.audioLanguage
const cacheKey = (...parts: string[]) => [catalogLang(), ...parts].join(':')