对齐讲解业务单元静态数据口径
- 刷新讲解静态数据包并保留完整 outline 树 - 按后台顶层业务单元口径归并讲解点统计 - 更新讲解静态数据刷新操作文档 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import type {
|
||||
ExplainBusinessUnit,
|
||||
ExplainGuideStop,
|
||||
ExplainTrack,
|
||||
GuideLocationResolution,
|
||||
GuideLocationResolutionStatus,
|
||||
@@ -17,6 +19,7 @@ import type {
|
||||
GuideStaticContentPayload,
|
||||
GuideStaticDataset,
|
||||
GuideStaticExplainDataset,
|
||||
GuideStaticExplainNavigationDataset,
|
||||
GuideStaticExhibitPayload,
|
||||
GuideStaticHallPayload,
|
||||
GuideStaticOutlinePayload,
|
||||
@@ -32,11 +35,20 @@ export interface GuideContentDataAdapterResult {
|
||||
mediaAssets: MediaAsset[]
|
||||
}
|
||||
|
||||
export interface GuideExplainNavigationAdapterResult {
|
||||
halls: MuseumHall[]
|
||||
guideStops: ExplainGuideStop[]
|
||||
businessUnitsByHallId: Record<string, ExplainBusinessUnit[]>
|
||||
}
|
||||
|
||||
export interface GuideExplainDataAdapterResult {
|
||||
halls: MuseumHall[]
|
||||
exhibits: MuseumExhibit[]
|
||||
tracks: ExplainTrack[]
|
||||
mediaAssets: MediaAsset[]
|
||||
guideStops: ExplainGuideStop[]
|
||||
businessUnitsByHallId: Record<string, ExplainBusinessUnit[]>
|
||||
exhibitByStopId: Record<string, MuseumExhibit>
|
||||
}
|
||||
|
||||
const stringifyId = (value: string | number | null | undefined) => (
|
||||
@@ -239,6 +251,176 @@ const toHallModels = (
|
||||
}
|
||||
})
|
||||
|
||||
const toExplainBusinessUnitsByHallId = (
|
||||
stops: ExplainGuideStop[],
|
||||
topOutlines: Array<{ id: string; name: string; hallId: string; sort?: number }> = []
|
||||
) => {
|
||||
const unitsByHallId: Record<string, ExplainBusinessUnit[]> = {}
|
||||
const stopGroupsByHall = new Map<string, Map<string, ExplainGuideStop[]>>()
|
||||
const unitNameByKey = new Map<string, string>()
|
||||
const unitOrderByKey = new Map<string, number>()
|
||||
|
||||
topOutlines.forEach((outline) => {
|
||||
const hallGroups = stopGroupsByHall.get(outline.hallId) || new Map<string, ExplainGuideStop[]>()
|
||||
if (!hallGroups.has(outline.id)) {
|
||||
hallGroups.set(outline.id, [])
|
||||
}
|
||||
stopGroupsByHall.set(outline.hallId, hallGroups)
|
||||
unitNameByKey.set(`${outline.hallId}:${outline.id}`, outline.name)
|
||||
unitOrderByKey.set(`${outline.hallId}:${outline.id}`, outline.sort ?? 0)
|
||||
})
|
||||
|
||||
stops
|
||||
.slice()
|
||||
.sort((a, b) => (a.sort ?? 0) - (b.sort ?? 0))
|
||||
.forEach((stop) => {
|
||||
const hallId = stop.hallId || ''
|
||||
if (!hallId) return
|
||||
|
||||
const unitId = stop.outlineId || `ungrouped-${hallId}`
|
||||
const unitName = stop.outlineName || '其他讲解'
|
||||
const hallGroups = stopGroupsByHall.get(hallId) || new Map<string, ExplainGuideStop[]>()
|
||||
const groupStops = hallGroups.get(unitId) || []
|
||||
groupStops.push(stop)
|
||||
hallGroups.set(unitId, groupStops)
|
||||
stopGroupsByHall.set(hallId, hallGroups)
|
||||
if (!unitNameByKey.has(`${hallId}:${unitId}`)) {
|
||||
unitNameByKey.set(`${hallId}:${unitId}`, unitName)
|
||||
}
|
||||
if (!unitOrderByKey.has(`${hallId}:${unitId}`)) {
|
||||
unitOrderByKey.set(`${hallId}:${unitId}`, stop.sort ?? 0)
|
||||
}
|
||||
})
|
||||
|
||||
stopGroupsByHall.forEach((groupMap, hallId) => {
|
||||
unitsByHallId[hallId] = Array.from(groupMap.entries())
|
||||
.map(([unitId, groupStops]) => ({
|
||||
id: unitId,
|
||||
name: unitNameByKey.get(`${hallId}:${unitId}`) || '其他讲解',
|
||||
hallId,
|
||||
guideStopCount: groupStops.length,
|
||||
stops: groupStops
|
||||
}))
|
||||
.sort((a, b) => (
|
||||
(unitOrderByKey.get(`${hallId}:${a.id}`) ?? 0) - (unitOrderByKey.get(`${hallId}:${b.id}`) ?? 0)
|
||||
))
|
||||
})
|
||||
|
||||
return unitsByHallId
|
||||
}
|
||||
|
||||
const createExplainNavigationAdapter = (
|
||||
dataset: GuideStaticExplainNavigationDataset,
|
||||
hallGuideCounts = new Map<string, number>(),
|
||||
poiBridge: {
|
||||
hallToNavPoiId?: Record<string, GuideStaticPoiBridgeEntryPayload>
|
||||
sgsPoiToNavPoiId?: Record<string, GuideStaticPoiBridgeEntryPayload>
|
||||
} = {}
|
||||
): GuideExplainNavigationAdapterResult => {
|
||||
const visibleHalls = visibleHallsFrom(dataset.halls)
|
||||
const visibleOutlines = visibleOutlinesFrom(dataset.outlines)
|
||||
const visibleStops = visibleStopsFrom(dataset.guideStops)
|
||||
const hallById = new Map(visibleHalls.map((hall) => [stringifyId(hall.id), hall]))
|
||||
const outlineById = new Map(visibleOutlines.map((outline) => [stringifyId(outline.id), outline]))
|
||||
const hallBridgeById = poiBridge.hallToNavPoiId || {}
|
||||
const sgsPoiBridgeById = poiBridge.sgsPoiToNavPoiId || {}
|
||||
|
||||
const findHallForOutline = (outline: GuideStaticOutlinePayload | undefined) => {
|
||||
let current = outline
|
||||
let guard = 0
|
||||
|
||||
while (current && guard < 16) {
|
||||
const parentId = stringifyId(current.parentId)
|
||||
if (parentId && hallById.has(parentId)) {
|
||||
return hallById.get(parentId)
|
||||
}
|
||||
|
||||
current = parentId ? outlineById.get(parentId) : undefined
|
||||
guard += 1
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
const findHallForStop = (stop: GuideStaticStopPayload | undefined) => (
|
||||
findHallForOutline(outlineById.get(stringifyId(stop?.outlineId)))
|
||||
)
|
||||
|
||||
const findTopOutlineForStop = (stop: GuideStaticStopPayload | undefined) => {
|
||||
let current = outlineById.get(stringifyId(stop?.outlineId))
|
||||
let guard = 0
|
||||
|
||||
while (current && guard < 16) {
|
||||
const parentId = stringifyId(current.parentId)
|
||||
if (parentId && hallById.has(parentId)) {
|
||||
return current
|
||||
}
|
||||
|
||||
current = parentId ? outlineById.get(parentId) : undefined
|
||||
guard += 1
|
||||
}
|
||||
|
||||
return outlineById.get(stringifyId(stop?.outlineId))
|
||||
}
|
||||
|
||||
const topOutlines = visibleOutlines.flatMap((outline) => {
|
||||
const id = stringifyId(outline.id)
|
||||
const parentId = stringifyId(outline.parentId)
|
||||
if (!id || !parentId || !hallById.has(parentId)) return []
|
||||
|
||||
return [{
|
||||
id,
|
||||
name: outline.name || `业务单元 ${id}`,
|
||||
hallId: parentId,
|
||||
sort: typeof outline.sort === 'number' ? outline.sort : undefined
|
||||
}]
|
||||
})
|
||||
|
||||
const resolveHallLocation = (hall: GuideStaticHallPayload | undefined) => {
|
||||
const hallId = stringifyId(hall?.id)
|
||||
const sourcePoiId = stringifyId(hall?.poiId)
|
||||
const bridgedHall = hallId ? toLocationResolution(hallBridgeById[hallId], 'exact', sourcePoiId) : undefined
|
||||
if (bridgedHall) return bridgedHall
|
||||
|
||||
return sourcePoiId ? toLocationResolution(sgsPoiBridgeById[sourcePoiId], 'exact', sourcePoiId) : undefined
|
||||
}
|
||||
|
||||
const halls = toHallModels(visibleHalls, hallGuideCounts, hallBridgeById, sgsPoiBridgeById)
|
||||
const guideStops: ExplainGuideStop[] = visibleStops.map((stop) => {
|
||||
const topOutline = findTopOutlineForStop(stop)
|
||||
const hall = findHallForStop(stop)
|
||||
const location = resolveHallLocation(hall)
|
||||
const floorId = location?.floorId || formatFloorId(hall, undefined) || stop.floorCode || stringifyId(stop.floorId)
|
||||
|
||||
return {
|
||||
id: stringifyId(stop.id) || '',
|
||||
name: firstText(stop.name) || `讲解点 ${stringifyId(stop.id) || ''}`,
|
||||
hallId: stringifyId(hall?.id),
|
||||
hallName: hall?.name || undefined,
|
||||
floorId,
|
||||
targetType: 'STOP',
|
||||
targetId: stringifyId(stop.id) || '',
|
||||
coverImageUrl: resolveImage(stop.coverImageUrl, hall?.coverImageUrl, hall?.galleryUrls),
|
||||
description: firstText(stop.name),
|
||||
hasAudio: Boolean(stop.audioUrl),
|
||||
poiId: location?.poiId,
|
||||
outlineId: stringifyId(topOutline?.id),
|
||||
outlineName: topOutline?.name || undefined,
|
||||
sort: typeof stop.sort === 'number' ? stop.sort : undefined
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
halls,
|
||||
guideStops,
|
||||
businessUnitsByHallId: toExplainBusinessUnitsByHallId(guideStops, topOutlines)
|
||||
}
|
||||
}
|
||||
|
||||
export const createGuideExplainNavigationAdapter = (
|
||||
dataset: GuideStaticExplainNavigationDataset
|
||||
): GuideExplainNavigationAdapterResult => createExplainNavigationAdapter(dataset)
|
||||
|
||||
export const createGuideExplainDataAdapter = (dataset: GuideStaticExplainDataset): GuideExplainDataAdapterResult => {
|
||||
const visibleHalls = visibleHallsFrom(dataset.halls)
|
||||
const visibleOutlines = visibleOutlinesFrom(dataset.outlines)
|
||||
@@ -272,6 +454,36 @@ export const createGuideExplainDataAdapter = (dataset: GuideStaticExplainDataset
|
||||
findHallForOutline(outlineById.get(stringifyId(stop?.outlineId)))
|
||||
)
|
||||
|
||||
const findTopOutlineForStop = (stop: GuideStaticStopPayload | undefined) => {
|
||||
let current = outlineById.get(stringifyId(stop?.outlineId))
|
||||
let guard = 0
|
||||
|
||||
while (current && guard < 16) {
|
||||
const parentId = stringifyId(current.parentId)
|
||||
if (parentId && hallById.has(parentId)) {
|
||||
return current
|
||||
}
|
||||
|
||||
current = parentId ? outlineById.get(parentId) : undefined
|
||||
guard += 1
|
||||
}
|
||||
|
||||
return outlineById.get(stringifyId(stop?.outlineId))
|
||||
}
|
||||
|
||||
const topOutlines = visibleOutlines.flatMap((outline) => {
|
||||
const id = stringifyId(outline.id)
|
||||
const parentId = stringifyId(outline.parentId)
|
||||
if (!id || !parentId || !hallById.has(parentId)) return []
|
||||
|
||||
return [{
|
||||
id,
|
||||
name: outline.name || `业务单元 ${id}`,
|
||||
hallId: parentId,
|
||||
sort: typeof outline.sort === 'number' ? outline.sort : undefined
|
||||
}]
|
||||
})
|
||||
|
||||
const resolveHallLocation = (
|
||||
hall: GuideStaticHallPayload | undefined,
|
||||
status: Exclude<GuideLocationResolutionStatus, 'unavailable'>
|
||||
@@ -313,6 +525,36 @@ export const createGuideExplainDataAdapter = (dataset: GuideStaticExplainDataset
|
||||
|
||||
const halls = toHallModels(visibleHalls, hallGuideCounts, hallBridgeById, sgsPoiBridgeById)
|
||||
|
||||
const guideStops: ExplainGuideStop[] = visibleStops.map((stop) => {
|
||||
const topOutline = findTopOutlineForStop(stop)
|
||||
const hall = findHallForStop(stop)
|
||||
const preciseLocation = resolveSgsPoiLocation([stop.poiId], 'exact')
|
||||
const location = preciseLocation || resolveHallLocation(hall, 'hallFallback')
|
||||
const floorId = location?.floorId || formatFloorId(hall, undefined) || stop.floorCode || stringifyId(stop.floorId)
|
||||
const stopGuide = visibleGuides.find((guide) => (
|
||||
guide.targetType?.toUpperCase() === 'STOP'
|
||||
&& stringifyId(guide.targetId) === stringifyId(stop.id)
|
||||
))
|
||||
|
||||
return {
|
||||
id: stringifyId(stop.id) || '',
|
||||
name: firstText(stop.name, stopGuide?.title) || `讲解点 ${stringifyId(stop.id) || ''}`,
|
||||
hallId: stringifyId(hall?.id),
|
||||
hallName: hall?.name || undefined,
|
||||
floorId,
|
||||
targetType: 'STOP',
|
||||
targetId: stringifyId(stop.id) || '',
|
||||
coverImageUrl: resolveImage(stop.coverImageUrl, stopGuide?.mediaGallery, hall?.coverImageUrl, hall?.galleryUrls),
|
||||
description: firstText(stopGuide?.standardText, stopGuide?.extendedText, stop.name),
|
||||
hasAudio: stopGuide ? Boolean(primaryAudioUrl(stopGuide)) : Boolean(stop.audioUrl),
|
||||
poiId: location?.poiId,
|
||||
outlineId: stringifyId(topOutline?.id),
|
||||
outlineName: topOutline?.name || undefined,
|
||||
sort: typeof stop.sort === 'number' ? stop.sort : undefined
|
||||
}
|
||||
})
|
||||
const businessUnitsByHallId = toExplainBusinessUnitsByHallId(guideStops, topOutlines)
|
||||
|
||||
const mediaAssets: MediaAsset[] = visibleGuides.map((guide) => {
|
||||
const id = `media-${stringifyId(guide.id) || 'unknown'}`
|
||||
const url = primaryAudioUrl(guide)
|
||||
@@ -374,6 +616,13 @@ export const createGuideExplainDataAdapter = (dataset: GuideStaticExplainDataset
|
||||
}
|
||||
})
|
||||
|
||||
const exhibitByStopId = exhibits.reduce<Record<string, MuseumExhibit>>((result, exhibit) => {
|
||||
if (exhibit.playTargetType === 'STOP' && exhibit.playTargetId) {
|
||||
result[exhibit.playTargetId] = exhibit
|
||||
}
|
||||
return result
|
||||
}, {})
|
||||
|
||||
const tracks: ExplainTrack[] = visibleGuides.map((guide) => {
|
||||
const id = stringifyId(guide.id) || ''
|
||||
const exhibitId = stringifyId(guide.exhibitId) || `guide-${id}`
|
||||
@@ -409,7 +658,10 @@ export const createGuideExplainDataAdapter = (dataset: GuideStaticExplainDataset
|
||||
halls,
|
||||
exhibits,
|
||||
tracks,
|
||||
mediaAssets
|
||||
mediaAssets,
|
||||
guideStops,
|
||||
businessUnitsByHallId,
|
||||
exhibitByStopId
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user