chore: freeze guide and explain update

This commit is contained in:
lyf
2026-06-24 18:00:25 +08:00
parent feb7310a46
commit 67c6609ae6
104 changed files with 3203572 additions and 40713 deletions

View File

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