接入H5讲解后端数据闭环
This commit is contained in:
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user