232 lines
7.8 KiB
TypeScript
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()
|