chore: freeze guide and explain update
This commit is contained in:
@@ -1,7 +1,25 @@
|
||||
import type {
|
||||
ExplainTrack,
|
||||
MediaAsset,
|
||||
MuseumExhibit,
|
||||
MuseumHall
|
||||
} from '@/domain/museum'
|
||||
import {
|
||||
dataSourceConfig,
|
||||
isGuideContentMockMode,
|
||||
isGuideContentRemoteMode,
|
||||
isGuideContentStaticMode
|
||||
} from '@/config/dataSource'
|
||||
import {
|
||||
createGuideExplainDataAdapter,
|
||||
createGuideContentDataAdapter,
|
||||
type GuideContentDataAdapterResult,
|
||||
type GuideExplainDataAdapterResult
|
||||
} from '@/data/adapters/guideDataAdapter'
|
||||
import {
|
||||
staticGuideDataProvider,
|
||||
type StaticGuideDataProvider
|
||||
} from '@/data/providers/staticGuideDataProvider'
|
||||
import {
|
||||
SGS_SCENE_EXPLAIN_DATASET,
|
||||
type SgsSceneExhibitMock,
|
||||
@@ -13,6 +31,13 @@ export interface MuseumContentProvider {
|
||||
listHalls(): Promise<MuseumHall[]>
|
||||
}
|
||||
|
||||
export interface ExplainContentProvider extends MuseumContentProvider {
|
||||
listExplainExhibits(): Promise<MuseumExhibit[]>
|
||||
listTracks(): Promise<ExplainTrack[]>
|
||||
getMediaById(id: string): Promise<MediaAsset | null>
|
||||
getMediaForExplainTrack(trackId: string): Promise<MediaAsset | null>
|
||||
}
|
||||
|
||||
const normalizeContentId = (prefix: string, value: string | number) => `${prefix}-sgs-${value}`
|
||||
|
||||
const categoryLabelMap: Record<SgsSceneSpaceMock['spaceCategory'], string> = {
|
||||
@@ -25,61 +50,24 @@ const categoryLabelMap: Record<SgsSceneSpaceMock['spaceCategory'], string> = {
|
||||
BACK_OFFICE: '后勤空间'
|
||||
}
|
||||
|
||||
const boundaryLabelMap: Record<NonNullable<SgsSceneSpaceMock['boundaryStatus']>, string> = {
|
||||
DEFINED: '已定义空间边界',
|
||||
UNDEFINED: '空间边界待完善'
|
||||
}
|
||||
|
||||
const publicSceneSpaces = SGS_SCENE_EXPLAIN_DATASET.spaces.filter((space) => space.isPublic)
|
||||
const publicSpaceById = new Map(publicSceneSpaces.map((space) => [space.id, space]))
|
||||
|
||||
const formatArea = (area?: number) => (typeof area === 'number' ? `${area}㎡` : undefined)
|
||||
|
||||
const hallDescription = (space: SgsSceneSpaceMock) => {
|
||||
const category = categoryLabelMap[space.spaceCategory]
|
||||
const area = formatArea(space.area)
|
||||
const boundary = space.boundaryStatus ? boundaryLabelMap[space.boundaryStatus] : undefined
|
||||
const facts = [space.floorLabel, category, area, boundary].filter(Boolean).join(' · ')
|
||||
|
||||
return `${space.name}来自 SGS 前端地图项目“场景设置”的空间管理数据。${facts ? `当前空间信息:${facts}。` : ''}讲解业务据此展示展厅/展区内容,并通过稳定空间、展品与 POI ID 关联到位置预览。`
|
||||
}
|
||||
|
||||
const exhibitDescription = (exhibit: SgsSceneExhibitMock, hall: SgsSceneSpaceMock | undefined) => {
|
||||
const facts = [
|
||||
exhibit.category,
|
||||
exhibit.era,
|
||||
exhibit.origin,
|
||||
exhibit.zoneName
|
||||
].filter(Boolean).join(' · ')
|
||||
|
||||
return [
|
||||
exhibit.description,
|
||||
facts ? `展陈信息:${facts}。` : '',
|
||||
hall ? `所属空间:${hall.name}(${hall.floorLabel})。` : ''
|
||||
].filter(Boolean).join('\n\n')
|
||||
}
|
||||
|
||||
const tagsForExhibit = (exhibit: SgsSceneExhibitMock, hall: SgsSceneSpaceMock | undefined) => [
|
||||
'SGS场景设置',
|
||||
exhibit.category,
|
||||
exhibit.zoneName,
|
||||
exhibit.audioUrl ? '源数据含音频地址' : '图文讲解',
|
||||
hall ? categoryLabelMap[hall.spaceCategory] : undefined
|
||||
].filter(Boolean) as string[]
|
||||
|
||||
const toMuseumHall = (space: SgsSceneSpaceMock): MuseumHall => ({
|
||||
const toMockMuseumHall = (space: SgsSceneSpaceMock): MuseumHall => ({
|
||||
id: normalizeContentId('hall', space.id),
|
||||
name: space.name,
|
||||
floorId: space.floorId,
|
||||
floorLabel: space.floorLabel,
|
||||
description: hallDescription(space),
|
||||
description: `${space.name}为显式开发 mock 数据,仅在 VITE_GUIDE_CONTENT_SOURCE_MODE=mock 且开发环境启用。`,
|
||||
image: '',
|
||||
exhibitCount: SGS_SCENE_EXPLAIN_DATASET.exhibits.filter((exhibit) => exhibit.spaceId === space.id).length || space.exhibitCount || 0,
|
||||
area: formatArea(space.area),
|
||||
poiId: space.poiId
|
||||
})
|
||||
|
||||
const toMuseumExhibit = (exhibit: SgsSceneExhibitMock): MuseumExhibit => {
|
||||
const toMockMuseumExhibit = (exhibit: SgsSceneExhibitMock): MuseumExhibit => {
|
||||
const hall = publicSpaceById.get(exhibit.spaceId)
|
||||
|
||||
return {
|
||||
@@ -90,23 +78,168 @@ const toMuseumExhibit = (exhibit: SgsSceneExhibitMock): MuseumExhibit => {
|
||||
floorId: hall?.floorId || SGS_SCENE_EXPLAIN_DATASET.floorId,
|
||||
floorLabel: hall?.floorLabel || SGS_SCENE_EXPLAIN_DATASET.floorLabel,
|
||||
image: '',
|
||||
description: exhibitDescription(exhibit, hall),
|
||||
description: exhibit.description,
|
||||
poiId: exhibit.poiId || hall?.poiId,
|
||||
year: exhibit.era,
|
||||
material: exhibit.origin,
|
||||
size: exhibit.code,
|
||||
tags: tagsForExhibit(exhibit, hall)
|
||||
tags: [
|
||||
'显式开发mock',
|
||||
exhibit.category,
|
||||
exhibit.zoneName,
|
||||
exhibit.audioUrl ? '源数据含音频地址' : '图文讲解',
|
||||
hall ? categoryLabelMap[hall.spaceCategory] : undefined
|
||||
].filter(Boolean) as string[]
|
||||
}
|
||||
}
|
||||
|
||||
export class StaticMuseumContentProvider implements MuseumContentProvider {
|
||||
const unavailableMockAudio = (id: string): MediaAsset => ({
|
||||
id,
|
||||
type: 'audio',
|
||||
available: false,
|
||||
unavailableReason: '显式开发 mock 数据暂无可播放音频。'
|
||||
})
|
||||
|
||||
export class StaticGuideContentProvider implements ExplainContentProvider {
|
||||
private adapterCache: GuideContentDataAdapterResult | null = null
|
||||
private explainAdapterCache: GuideExplainDataAdapterResult | null = null
|
||||
|
||||
constructor(private readonly provider: StaticGuideDataProvider = staticGuideDataProvider) {}
|
||||
|
||||
private async loadExplainAdapter() {
|
||||
if (this.explainAdapterCache) return this.explainAdapterCache
|
||||
if (this.adapterCache) return this.adapterCache
|
||||
|
||||
const dataset = await this.provider.loadExplainDataset()
|
||||
this.explainAdapterCache = createGuideExplainDataAdapter(dataset)
|
||||
return this.explainAdapterCache
|
||||
}
|
||||
|
||||
private async loadAdapter() {
|
||||
if (this.adapterCache) return this.adapterCache
|
||||
|
||||
const dataset = await this.provider.loadDataset()
|
||||
this.adapterCache = createGuideContentDataAdapter(dataset)
|
||||
return this.adapterCache
|
||||
}
|
||||
|
||||
async listExplainExhibits() {
|
||||
const adapter = await this.loadExplainAdapter()
|
||||
return adapter.exhibits
|
||||
}
|
||||
|
||||
async listExhibits() {
|
||||
return SGS_SCENE_EXPLAIN_DATASET.exhibits.map(toMuseumExhibit)
|
||||
const adapter = await this.loadAdapter()
|
||||
return adapter.exhibits
|
||||
}
|
||||
|
||||
async listHalls() {
|
||||
return publicSceneSpaces.map(toMuseumHall)
|
||||
const adapter = await this.loadExplainAdapter()
|
||||
return adapter.halls
|
||||
}
|
||||
|
||||
async listTracks() {
|
||||
const adapter = await this.loadExplainAdapter()
|
||||
return adapter.tracks
|
||||
}
|
||||
|
||||
async getMediaById(id: string) {
|
||||
const explainAdapter = await this.loadExplainAdapter()
|
||||
const explainMedia = explainAdapter.mediaAssets.find((media) => media.id === id)
|
||||
if (explainMedia) return explainMedia
|
||||
|
||||
const adapter = await this.loadAdapter()
|
||||
return adapter.mediaAssets.find((media) => media.id === id) || null
|
||||
}
|
||||
|
||||
async getMediaForExplainTrack(trackId: string) {
|
||||
const normalizedTrackId = trackId.replace(/^track-/, '')
|
||||
return this.getMediaById(`media-${normalizedTrackId}`)
|
||||
}
|
||||
}
|
||||
|
||||
export const staticMuseumContentProvider = new StaticMuseumContentProvider()
|
||||
export class RemoteGuideContentProvider implements ExplainContentProvider {
|
||||
private readonly notReady = () => new Error(
|
||||
`远程讲解数据源尚未接入。当前 VITE_GUIDE_CONTENT_SOURCE_MODE=${dataSourceConfig.guideContentMode},请切回 static 或实现 remote provider。`
|
||||
)
|
||||
|
||||
async listExhibits(): Promise<MuseumExhibit[]> {
|
||||
throw this.notReady()
|
||||
}
|
||||
|
||||
async listExplainExhibits(): Promise<MuseumExhibit[]> {
|
||||
throw this.notReady()
|
||||
}
|
||||
|
||||
async listHalls(): Promise<MuseumHall[]> {
|
||||
throw this.notReady()
|
||||
}
|
||||
|
||||
async listTracks(): Promise<ExplainTrack[]> {
|
||||
throw this.notReady()
|
||||
}
|
||||
|
||||
async getMediaById(): Promise<MediaAsset | null> {
|
||||
throw this.notReady()
|
||||
}
|
||||
|
||||
async getMediaForExplainTrack(): Promise<MediaAsset | null> {
|
||||
throw this.notReady()
|
||||
}
|
||||
}
|
||||
|
||||
export class ExplicitMockMuseumContentProvider implements ExplainContentProvider {
|
||||
listExplainExhibits() {
|
||||
return this.listExhibits()
|
||||
}
|
||||
|
||||
async listExhibits() {
|
||||
return SGS_SCENE_EXPLAIN_DATASET.exhibits.map(toMockMuseumExhibit)
|
||||
}
|
||||
|
||||
async listHalls() {
|
||||
return publicSceneSpaces.map(toMockMuseumHall)
|
||||
}
|
||||
|
||||
async listTracks() {
|
||||
const exhibits = await this.listExhibits()
|
||||
return exhibits.map<ExplainTrack>((exhibit) => ({
|
||||
id: `track-${exhibit.id}`,
|
||||
exhibitId: exhibit.id,
|
||||
hallId: exhibit.hallId,
|
||||
title: `${exhibit.name}讲解`,
|
||||
summary: exhibit.description,
|
||||
mediaId: `media-${exhibit.id}`,
|
||||
coverImage: exhibit.image,
|
||||
poiId: exhibit.poiId,
|
||||
floorId: exhibit.floorId,
|
||||
available: false
|
||||
}))
|
||||
}
|
||||
|
||||
async getMediaById(id: string) {
|
||||
return unavailableMockAudio(id)
|
||||
}
|
||||
|
||||
async getMediaForExplainTrack(trackId: string) {
|
||||
return unavailableMockAudio(`media-${trackId.replace(/^track-/, '')}`)
|
||||
}
|
||||
}
|
||||
|
||||
export const createMuseumContentProvider = (): ExplainContentProvider => {
|
||||
if (isGuideContentStaticMode()) {
|
||||
return new StaticGuideContentProvider()
|
||||
}
|
||||
|
||||
if (isGuideContentRemoteMode()) {
|
||||
return new RemoteGuideContentProvider()
|
||||
}
|
||||
|
||||
if (isGuideContentMockMode()) {
|
||||
return new ExplicitMockMuseumContentProvider()
|
||||
}
|
||||
|
||||
return new StaticGuideContentProvider()
|
||||
}
|
||||
|
||||
export const staticMuseumContentProvider = createMuseumContentProvider()
|
||||
|
||||
Reference in New Issue
Block a user