讲解对象列表接入展厅分页接口
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
lyf
2026-07-17 18:27:24 +08:00
parent 7267dfcac1
commit cf6ad02318
18 changed files with 682 additions and 409 deletions

View File

@@ -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(),

View File

@@ -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] || []