Files
frontend-miniapp/src/repositories/ExplainRepository.ts
lyf cf6ad02318
Some checks failed
CI / verify (push) Has been cancelled
讲解对象列表接入展厅分页接口
2026-07-17 18:27:24 +08:00

232 lines
7.8 KiB
TypeScript

import type {
ExplainBusinessUnit,
ExplainGuideStop,
ExplainGuideStopPage,
ExplainTrack,
MuseumExhibit,
MuseumHall,
SearchIndexItem
} from '@/domain/museum'
import {
mediaRepository,
type MediaRepository
} from '@/repositories/MediaRepository'
import {
museumContentRepository,
type MuseumContentRepository
} from '@/repositories/MuseumContentRepository'
import {
explainContentProvider,
type ExplainContentProvider
} from '@/data/providers/staticMuseumContentProvider'
export interface ExplainRepository {
listExplainExhibits(): Promise<MuseumExhibit[]>
listExplainExhibitsByHall(hallId: string): Promise<MuseumExhibit[]>
listExhibits(): Promise<MuseumExhibit[]>
getExhibitById(id: string): Promise<MuseumExhibit | null>
listHalls(): Promise<MuseumHall[]>
getHallById(id: string): Promise<MuseumHall | null>
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[]>
getTrackByExhibitId(exhibitId: string): Promise<ExplainTrack | null>
searchExplain(keyword?: string): Promise<SearchIndexItem[]>
}
const trackTitleFor = (exhibit: MuseumExhibit) => `${exhibit.name}讲解`
export class DefaultExplainRepository implements ExplainRepository {
constructor(
private readonly content: MuseumContentRepository = museumContentRepository,
private readonly media: MediaRepository = mediaRepository,
private readonly explainContent: ExplainContentProvider = explainContentProvider
) {}
listExplainExhibits() {
return this.explainContent.listExplainExhibits()
}
async listExplainExhibitsByHall(hallId: string) {
if (this.explainContent.listExplainExhibitsByHall) {
return this.explainContent.listExplainExhibitsByHall(hallId)
}
const exhibits = await this.listExplainExhibits()
return exhibits.filter((exhibit) => exhibit.hallId === hallId)
}
listExhibits() {
return this.content.listExhibits()
}
getExhibitById(id: string) {
return this.explainContent.getExhibitById?.(id) || this.content.getExhibitById(id)
}
listHalls() {
return this.explainContent.listHalls()
}
async getHallById(id: string) {
const halls = await this.explainContent.listHalls()
return halls.find((hall) => hall.id === id || hall.poiId === id) || null
}
async listGuideStopsByHall(hallId: string) {
const pageSize = 100
const items: ExplainGuideStop[] = []
const seen = new Set<string>()
for (let pageNo = 1; ; pageNo += 1) {
const page = await this.listGuideStopsPageByHall(hallId, pageNo, pageSize)
page.items.forEach((item) => {
if (!seen.has(item.id)) {
seen.add(item.id)
items.push(item)
}
})
if (!page.hasMore || items.length >= page.total) return items
}
}
async listGuideStopsPageByHall(hallId: string, pageNo: number, pageSize: number) {
if (this.explainContent.listGuideStopsPageByHall) {
return this.explainContent.listGuideStopsPageByHall(hallId, pageNo, pageSize)
}
const items = await this.explainContent.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: items.slice(offset, offset + normalizedPageSize),
total: items.length,
pageNo: normalizedPageNo,
pageSize: normalizedPageSize,
hasMore: offset + normalizedPageSize < items.length
}
}
async listGuideStopsByBusinessUnit(hallId: string, unitId: string) {
if (this.explainContent.listGuideStopsByBusinessUnit) {
return this.explainContent.listGuideStopsByBusinessUnit(hallId, unitId)
}
const units = await this.listTemporaryBusinessUnitsByHall(hallId)
return units.find((unit) => unit.id === unitId)?.stops || []
}
async listTemporaryBusinessUnitsByHall(hallId: string) {
if (this.explainContent.listTemporaryBusinessUnitsByHall) {
return this.explainContent.listTemporaryBusinessUnitsByHall(hallId)
}
return []
}
async listTracks() {
const tracks = await this.explainContent.listTracks()
if (tracks.length) return tracks
const exhibits = await this.content.listExhibits()
return Promise.all(exhibits.map(async (exhibit): Promise<ExplainTrack> => {
const media = await this.media.getMediaForExplainTrack(exhibit.id)
return {
id: `track-${exhibit.id}`,
exhibitId: exhibit.id,
hallId: exhibit.hallId,
title: trackTitleFor(exhibit),
summary: exhibit.description,
mediaId: media?.id,
coverImage: exhibit.image,
poiId: exhibit.poiId,
floorId: exhibit.floorId,
available: media?.available === true
}
}))
}
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(),
this.explainContent.listHalls()
])
const exhibitItems = exhibits
.filter((exhibit) => !normalizedKeyword || [
exhibit.name,
exhibit.hallName,
exhibit.floorLabel,
...(exhibit.tags || [])
].filter(Boolean).join(' ').toLowerCase().includes(normalizedKeyword))
.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 hallItems = halls
.filter((hall) => !normalizedKeyword || [
hall.name,
hall.floorLabel,
hall.description
].filter(Boolean).join(' ').toLowerCase().includes(normalizedKeyword))
.map<SearchIndexItem>((hall) => ({
id: hall.id,
name: hall.name,
desc: `${hall.floorLabel || ''} · ${hall.exhibitCount || 0}个讲解内容`.trim(),
type: 'hall',
hallId: hall.id,
poiId: hall.poiId,
floorId: hall.floorId
}))
return [...exhibitItems, ...hallItems]
}
}
export const explainRepository = new DefaultExplainRepository()