接入H5讲解后端数据闭环

This commit is contained in:
lyf
2026-06-30 17:22:20 +08:00
parent 1c2cc788d1
commit e3682ed3ec
13 changed files with 815 additions and 68 deletions

View File

@@ -29,6 +29,7 @@ export interface AudioPlayInfo {
playUrl?: string | null
expiresAt?: string | null
subtitleUrl?: string | null
hasText?: boolean
fallback?: boolean
fallbackReason?: string | null
reason?: string | null
@@ -40,8 +41,34 @@ interface AudioPlayInfoResponse {
data?: AudioPlayInfo
}
export interface AudioTextInfoRequest {
targetType: AudioPlayTargetType
targetId: string
lang?: AudioLanguage
}
export interface AudioTextInfo {
available: boolean
targetType: AudioPlayTargetType
targetId: string | number
lang: AudioLanguage
narrationTier?: 'STANDARD' | 'EXTENDED'
title?: string | null
text?: string | null
textLength?: number | null
textHash?: string | null
reason?: string | null
}
interface AudioTextInfoResponse {
code: number
msg?: string
data?: AudioTextInfo
}
export interface AudioPlayInfoRepository {
getPlayInfo(request: AudioPlayInfoRequest): Promise<AudioPlayInfo>
getTextInfo(request: AudioTextInfoRequest): Promise<AudioTextInfo>
clearCache(lang?: AudioLanguage): void
}
@@ -67,6 +94,7 @@ const requestJson = <T>(url: string): Promise<T> => new Promise((resolve, reject
uni.request({
url,
method: 'GET',
timeout: 8000,
success: (response) => {
const statusCode = Number(response.statusCode || 0)
if (statusCode < 200 || statusCode >= 300) {
@@ -95,6 +123,10 @@ const audioKey = ({ targetType, targetId, lang }: Required<AudioPlayInfoRequest>
`${targetType}:${targetId}:${lang}`
)
const audioTextKey = ({ targetType, targetId, lang }: Required<AudioTextInfoRequest>) => (
`${targetType}:${targetId}:${lang}`
)
const isExpired = (expiresAt?: string | null) => (
Boolean(expiresAt) && new Date(expiresAt as string).getTime() <= Date.now()
)
@@ -105,6 +137,7 @@ export const audioReasonToText = (reason?: string | null) => (
export class DefaultAudioPlayInfoRepository implements AudioPlayInfoRepository {
private readonly cache = new Map<string, AudioPlayInfo>()
private readonly textCache = new Map<string, AudioTextInfo>()
async getPlayInfo(request: AudioPlayInfoRequest): Promise<AudioPlayInfo> {
const normalizedRequest: Required<AudioPlayInfoRequest> = {
@@ -140,9 +173,42 @@ export class DefaultAudioPlayInfoRepository implements AudioPlayInfoRepository {
return response.data
}
async getTextInfo(request: AudioTextInfoRequest): Promise<AudioTextInfo> {
const normalizedRequest: Required<AudioTextInfoRequest> = {
targetType: request.targetType,
targetId: request.targetId,
lang: request.lang || (dataSourceConfig.audioLanguage as AudioLanguage)
}
const key = audioTextKey(normalizedRequest)
const cached = this.textCache.get(key)
if (cached) {
return cached
}
const params = new URLSearchParams({
targetType: normalizedRequest.targetType,
targetId: normalizedRequest.targetId,
lang: normalizedRequest.lang
})
const url = `${resolveAudioApiBaseUrl()}/gis/guide/audio/text-info?${params.toString()}`
const response = await requestJson<AudioTextInfoResponse>(url)
if (response.code !== 0 || !response.data) {
throw new Error(response.msg || '讲解词正文解析失败')
}
if (response.data.available) {
this.textCache.set(key, response.data)
}
return response.data
}
clearCache(lang?: AudioLanguage) {
if (!lang) {
this.cache.clear()
this.textCache.clear()
return
}
@@ -151,6 +217,11 @@ export class DefaultAudioPlayInfoRepository implements AudioPlayInfoRepository {
this.cache.delete(key)
}
})
Array.from(this.textCache.keys()).forEach((key) => {
if (key.endsWith(`:${lang}`)) {
this.textCache.delete(key)
}
})
}
}

View File

@@ -46,7 +46,7 @@ export class DefaultExplainRepository implements ExplainRepository {
}
getExhibitById(id: string) {
return this.content.getExhibitById(id)
return this.explainContent.getExhibitById?.(id) || this.content.getExhibitById(id)
}
listHalls() {
@@ -81,11 +81,43 @@ export class DefaultExplainRepository implements ExplainRepository {
}
async getTrackByExhibitId(exhibitId: string) {
const detail = await this.getExhibitById(exhibitId).catch(() => null)
if (detail) {
return {
id: `track-${detail.guideContentId || detail.id}`,
exhibitId: detail.id,
hallId: detail.hallId,
title: detail.guideTitle || trackTitleFor(detail),
summary: detail.guideText || detail.description,
mediaId: detail.audioUrl ? `media-${detail.guideContentId || detail.id}` : undefined,
coverImage: detail.image,
poiId: detail.poiId,
floorId: detail.floorId,
available: Boolean(detail.audioUrl) || detail.audioAvailable === true,
playTargetType: detail.playTargetType,
playTargetId: detail.playTargetId
}
}
const tracks = await this.listTracks()
return tracks.find((track) => track.exhibitId === exhibitId) || null
}
async searchExplain(keyword = '') {
if (this.explainContent.searchExplainExhibits) {
const exhibits = await this.explainContent.searchExplainExhibits(keyword)
return exhibits.map<SearchIndexItem>((exhibit) => ({
id: exhibit.id,
name: exhibit.name,
desc: `${exhibit.hallName || '展品讲解'} · ${exhibit.floorLabel || ''}`.trim(),
type: 'exhibit',
exhibitId: exhibit.id,
hallId: exhibit.hallId,
poiId: exhibit.poiId,
floorId: exhibit.floorId
}))
}
const normalizedKeyword = keyword.trim().toLowerCase()
const [exhibits, halls] = await Promise.all([
this.explainContent.listExplainExhibits(),

View File

@@ -54,6 +54,10 @@ export class DefaultMuseumContentRepository implements MuseumContentRepository {
}
async getExhibitById(id: string) {
if (this.contentProvider.getExhibitById) {
return this.contentProvider.getExhibitById(id)
}
const exhibits = await this.contentProvider.listExhibits()
return exhibits.find((exhibit) => exhibit.id === id) || null
}