This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import type {
|
||||
ExplainBusinessUnit,
|
||||
ExplainGuideStop,
|
||||
ExplainGuideStopPage,
|
||||
MuseumExhibit,
|
||||
MuseumHall
|
||||
} from '@/domain/museum'
|
||||
@@ -84,6 +85,11 @@ interface TimedCacheEntry<T> {
|
||||
expiresAt: number
|
||||
}
|
||||
|
||||
interface BackendCatalogStopPage {
|
||||
list?: BackendCatalogStopItem[]
|
||||
total?: number
|
||||
}
|
||||
|
||||
const requireArrayData = <T>(response: CommonResult<T[]>, message: string) => {
|
||||
if (response.code !== 0) {
|
||||
throw new Error(response.msg || message)
|
||||
@@ -92,6 +98,14 @@ const requireArrayData = <T>(response: CommonResult<T[]>, message: string) => {
|
||||
return Array.isArray(response.data) ? response.data : []
|
||||
}
|
||||
|
||||
const requirePageData = (response: CommonResult<BackendCatalogStopPage>, message: string) => {
|
||||
if (response.code !== 0) throw new Error(response.msg || message)
|
||||
return {
|
||||
list: Array.isArray(response.data?.list) ? response.data.list : [],
|
||||
total: Math.max(0, Number(response.data?.total || 0))
|
||||
}
|
||||
}
|
||||
|
||||
export class BackendExplainContentProvider implements ExplainContentProvider {
|
||||
private readonly explainExhibitCache = new Map<string, TimedCacheEntry<MuseumExhibit[]>>()
|
||||
private readonly explainExhibitInflight = new Map<string, Promise<MuseumExhibit[]>>()
|
||||
@@ -100,6 +114,8 @@ export class BackendExplainContentProvider implements ExplainContentProvider {
|
||||
private readonly hallListInflight = new Map<string, Promise<MuseumHall[]>>()
|
||||
private readonly guideStopCache = new Map<string, TimedCacheEntry<ExplainGuideStop[]>>()
|
||||
private readonly guideStopInflight = new Map<string, Promise<ExplainGuideStop[]>>()
|
||||
private readonly guideStopPageCache = new Map<string, TimedCacheEntry<ExplainGuideStopPage>>()
|
||||
private readonly guideStopPageInflight = new Map<string, Promise<ExplainGuideStopPage>>()
|
||||
private readonly outlineCache = new Map<string, TimedCacheEntry<BackendCatalogOutlineItem[]>>()
|
||||
private readonly outlineInflight = new Map<string, Promise<BackendCatalogOutlineItem[]>>()
|
||||
private readonly businessUnitCache = new Map<string, TimedCacheEntry<ExplainBusinessUnit[]>>()
|
||||
@@ -222,11 +238,51 @@ export class BackendExplainContentProvider implements ExplainContentProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private async requestCatalogStopsPageByHall(hallId: string, pageNo: number, pageSize: number): Promise<ExplainGuideStopPage> {
|
||||
const normalizedHallId = hallId.trim()
|
||||
const normalizedPageNo = Math.max(1, Math.floor(pageNo) || 1)
|
||||
const normalizedPageSize = Math.min(100, Math.max(1, Math.floor(pageSize) || 20))
|
||||
if (!normalizedHallId) return { items: [], total: 0, pageNo: normalizedPageNo, pageSize: normalizedPageSize, hasMore: false }
|
||||
|
||||
const key = cacheKey('hall', normalizedHallId, 'stops', 'page', String(normalizedPageNo), String(normalizedPageSize))
|
||||
const cached = this.getCached(this.guideStopPageCache, key)
|
||||
if (cached) return cached
|
||||
|
||||
const inflight = this.guideStopPageInflight.get(key)
|
||||
if (inflight) return inflight
|
||||
|
||||
const promise = (async () => {
|
||||
const halls = await this.requestCatalogHalls()
|
||||
const hall = halls.find((item) => item.id === normalizedHallId) || null
|
||||
const params = new URLSearchParams({ lang: catalogLang(), pageNo: String(normalizedPageNo), pageSize: String(normalizedPageSize) })
|
||||
const url = `${resolveAppApiBaseUrl()}/gis/guide/catalog/halls/${encodeURIComponent(normalizedHallId)}/stops/page?${params.toString()}`
|
||||
const data = requirePageData(await requestJson<CommonResult<BackendCatalogStopPage>>(url), '讲解对象目录加载失败')
|
||||
const items = data.list
|
||||
.map((item) => toCatalogGuideStop(item, hall, null))
|
||||
.filter(Boolean) as ExplainGuideStop[]
|
||||
const page = {
|
||||
items,
|
||||
total: data.total,
|
||||
pageNo: normalizedPageNo,
|
||||
pageSize: normalizedPageSize,
|
||||
hasMore: normalizedPageNo * normalizedPageSize < data.total
|
||||
}
|
||||
this.setCached(this.guideStopPageCache, key, page)
|
||||
return page
|
||||
})()
|
||||
|
||||
this.guideStopPageInflight.set(key, promise)
|
||||
try {
|
||||
return await promise
|
||||
} finally {
|
||||
this.guideStopPageInflight.delete(key)
|
||||
}
|
||||
}
|
||||
|
||||
private async requestCatalogStopsByHall(hallId: string) {
|
||||
const normalizedHallId = hallId.trim()
|
||||
if (!normalizedHallId) return []
|
||||
|
||||
const key = cacheKey('hall', normalizedHallId, 'stops')
|
||||
const key = cacheKey('hall', normalizedHallId, 'stops', 'all')
|
||||
const cached = this.getCached(this.guideStopCache, key)
|
||||
if (cached) return cached
|
||||
|
||||
@@ -234,28 +290,18 @@ export class BackendExplainContentProvider implements ExplainContentProvider {
|
||||
if (inflight) return inflight
|
||||
|
||||
const promise = (async () => {
|
||||
const [halls, outlines] = await Promise.all([
|
||||
this.requestCatalogHalls(),
|
||||
this.requestCatalogOutlinesByHall(normalizedHallId).catch(() => [])
|
||||
])
|
||||
const hall = halls.find((item) => item.id === normalizedHallId) || null
|
||||
const outlineById = new Map(flattenCatalogOutlines(outlines).map((outline) => [String(outline.id || ''), outline]))
|
||||
|
||||
const params = new URLSearchParams({ lang: catalogLang() })
|
||||
const url = `${resolveAppApiBaseUrl()}/gis/guide/catalog/halls/${encodeURIComponent(normalizedHallId)}/stops?${params.toString()}`
|
||||
const response = await requestJson<CommonResult<BackendCatalogStopItem[]>>(url)
|
||||
const stops = requireArrayData(response, '讲解点目录加载失败')
|
||||
.map((item) => {
|
||||
const outline = outlineById.get(String(item.outlineId || ''))
|
||||
return toCatalogGuideStop(item, hall, outline
|
||||
? {
|
||||
id: String(outline.id || ''),
|
||||
name: String(outline.name || '')
|
||||
}
|
||||
: null)
|
||||
const stops: ExplainGuideStop[] = []
|
||||
const seen = new Set<string>()
|
||||
for (let pageNo = 1; ; pageNo += 1) {
|
||||
const page = await this.requestCatalogStopsPageByHall(normalizedHallId, pageNo, 100)
|
||||
page.items.forEach((stop) => {
|
||||
if (!seen.has(stop.id)) {
|
||||
seen.add(stop.id)
|
||||
stops.push(stop)
|
||||
}
|
||||
})
|
||||
.filter(Boolean) as ExplainGuideStop[]
|
||||
|
||||
if (!page.hasMore || stops.length >= page.total) break
|
||||
}
|
||||
this.setCached(this.guideStopCache, key, stops)
|
||||
return stops
|
||||
})()
|
||||
@@ -416,6 +462,17 @@ export class BackendExplainContentProvider implements ExplainContentProvider {
|
||||
}
|
||||
}
|
||||
|
||||
async listGuideStopsPageByHall(hallId: string, pageNo: number, pageSize: number) {
|
||||
try {
|
||||
return await this.requestCatalogStopsPageByHall(hallId, pageNo, pageSize)
|
||||
} catch (error) {
|
||||
return this.fallbackOrThrow(error, '讲解对象目录加载失败', async () => (
|
||||
this.fallbackProvider?.listGuideStopsPageByHall?.(hallId, pageNo, pageSize)
|
||||
|| { items: [], total: 0, pageNo, pageSize, hasMore: false }
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
async listExplainExhibitsByHall(hallId: string) {
|
||||
const [halls, stops] = await Promise.all([
|
||||
this.listHalls(),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type {
|
||||
ExplainBusinessUnit,
|
||||
ExplainGuideStop,
|
||||
ExplainGuideStopPage,
|
||||
ExplainTrack,
|
||||
MediaAsset,
|
||||
MuseumExhibit,
|
||||
@@ -44,6 +45,7 @@ export interface ExplainContentProvider extends MuseumContentProvider {
|
||||
listExplainExhibitsByHall?(hallId: string): Promise<MuseumExhibit[]>
|
||||
searchExplainExhibits?(keyword?: string): Promise<MuseumExhibit[]>
|
||||
listGuideStopsByHall?(hallId: string): Promise<ExplainGuideStop[]>
|
||||
listGuideStopsPageByHall?(hallId: string, pageNo: number, pageSize: number): Promise<ExplainGuideStopPage>
|
||||
listGuideStopsByBusinessUnit?(hallId: string, unitId: string): Promise<ExplainGuideStop[]>
|
||||
listTemporaryBusinessUnitsByHall?(hallId: string): Promise<ExplainBusinessUnit[]>
|
||||
listTracks(): Promise<ExplainTrack[]>
|
||||
@@ -161,6 +163,20 @@ export class StaticGuideContentProvider implements ExplainContentProvider {
|
||||
return adapter.guideStops.filter((stop) => stop.hallId === hallId)
|
||||
}
|
||||
|
||||
async listGuideStopsPageByHall(hallId: string, pageNo: number, pageSize: number) {
|
||||
const stops = await this.listGuideStopsByHall(hallId)
|
||||
const normalizedPageNo = Math.max(1, Math.floor(pageNo) || 1)
|
||||
const normalizedPageSize = Math.max(1, Math.floor(pageSize) || 20)
|
||||
const offset = (normalizedPageNo - 1) * normalizedPageSize
|
||||
return {
|
||||
items: stops.slice(offset, offset + normalizedPageSize),
|
||||
total: stops.length,
|
||||
pageNo: normalizedPageNo,
|
||||
pageSize: normalizedPageSize,
|
||||
hasMore: offset + normalizedPageSize < stops.length
|
||||
}
|
||||
}
|
||||
|
||||
async listTemporaryBusinessUnitsByHall(hallId: string) {
|
||||
const adapter = await this.loadExplainNavigationAdapter()
|
||||
return adapter.businessUnitsByHallId[hallId] || []
|
||||
|
||||
Reference in New Issue
Block a user