125 lines
3.5 KiB
TypeScript
125 lines
3.5 KiB
TypeScript
import type {
|
|
MuseumExhibit,
|
|
MuseumHall,
|
|
MuseumPoi,
|
|
SearchIndexItem
|
|
} from '@/domain/museum'
|
|
import {
|
|
guideRepository,
|
|
type GuideRepository
|
|
} from '@/repositories/GuideRepository'
|
|
import {
|
|
staticMuseumContentProvider,
|
|
type MuseumContentProvider
|
|
} from '@/data/providers/staticMuseumContentProvider'
|
|
|
|
export interface MuseumContentRepository {
|
|
listFacilities(): Promise<MuseumPoi[]>
|
|
getFacilityById(id: string): Promise<MuseumPoi | null>
|
|
searchFacilities(keyword?: string): Promise<MuseumPoi[]>
|
|
listExhibits(): Promise<MuseumExhibit[]>
|
|
getExhibitById(id: string): Promise<MuseumExhibit | null>
|
|
listHalls(): Promise<MuseumHall[]>
|
|
getHallById(id: string): Promise<MuseumHall | null>
|
|
searchContent(keyword?: string): Promise<SearchIndexItem[]>
|
|
}
|
|
|
|
const normalize = (value: string) => value.trim().toLowerCase()
|
|
const includesKeyword = (values: Array<string | undefined>, keyword: string) => (
|
|
values.filter(Boolean).join(' ').toLowerCase().includes(keyword)
|
|
)
|
|
|
|
export class DefaultMuseumContentRepository implements MuseumContentRepository {
|
|
constructor(
|
|
private readonly contentProvider: MuseumContentProvider = staticMuseumContentProvider,
|
|
private readonly guide: GuideRepository = guideRepository
|
|
) {}
|
|
|
|
listFacilities() {
|
|
return this.guide.listPois()
|
|
}
|
|
|
|
getFacilityById(id: string) {
|
|
return this.guide.getPoiById(id)
|
|
}
|
|
|
|
searchFacilities(keyword = '') {
|
|
return this.guide.searchPois(keyword)
|
|
}
|
|
|
|
listExhibits() {
|
|
return this.contentProvider.listExhibits()
|
|
}
|
|
|
|
async getExhibitById(id: string) {
|
|
const exhibits = await this.contentProvider.listExhibits()
|
|
return exhibits.find((exhibit) => exhibit.id === id) || null
|
|
}
|
|
|
|
listHalls() {
|
|
return this.contentProvider.listHalls()
|
|
}
|
|
|
|
async getHallById(id: string) {
|
|
const halls = await this.contentProvider.listHalls()
|
|
return halls.find((hall) => hall.id === id) || null
|
|
}
|
|
|
|
async searchContent(keyword = '') {
|
|
const normalizedKeyword = normalize(keyword)
|
|
const facilities = await this.searchFacilities(keyword)
|
|
const exhibits = await this.listExhibits()
|
|
const halls = await this.listHalls()
|
|
|
|
const exhibitItems = exhibits
|
|
.filter((exhibit) => !normalizedKeyword || includesKeyword([
|
|
exhibit.name,
|
|
exhibit.hallName,
|
|
exhibit.floorLabel,
|
|
...(exhibit.tags || [])
|
|
], normalizedKeyword))
|
|
.map<SearchIndexItem>((exhibit) => ({
|
|
id: exhibit.id,
|
|
name: exhibit.name,
|
|
desc: `${exhibit.hallName || '展项'} · ${exhibit.floorLabel || ''}`.trim(),
|
|
type: 'exhibit',
|
|
exhibitId: exhibit.id,
|
|
poiId: exhibit.poiId,
|
|
floorId: exhibit.floorId
|
|
}))
|
|
|
|
const hallItems = halls
|
|
.filter((hall) => !normalizedKeyword || includesKeyword([
|
|
hall.name,
|
|
hall.floorLabel,
|
|
hall.description
|
|
], 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
|
|
}))
|
|
|
|
const facilityItems = facilities.map<SearchIndexItem>((poi) => ({
|
|
id: poi.id,
|
|
name: poi.name,
|
|
desc: `${poi.floorLabel} · ${poi.primaryCategory.label}`,
|
|
type: 'facility',
|
|
poiId: poi.id,
|
|
floorId: poi.floorId
|
|
}))
|
|
|
|
return [
|
|
...exhibitItems,
|
|
...hallItems,
|
|
...facilityItems
|
|
]
|
|
}
|
|
}
|
|
|
|
export const museumContentRepository = new DefaultMuseumContentRepository()
|