统一首页点位详情目标解析
This commit is contained in:
159
src/usecases/poiDetailUseCase.ts
Normal file
159
src/usecases/poiDetailUseCase.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import type {
|
||||
MuseumCategory,
|
||||
MuseumHall,
|
||||
MuseumPoiEntrance,
|
||||
MuseumPoiKind
|
||||
} from '@/domain/museum'
|
||||
import { explainUseCase } from '@/usecases/explainUseCase'
|
||||
import { explainGuideStopListUrl } from '@/utils/explainNavigation'
|
||||
|
||||
export type PoiDetailType = 'hall' | 'exhibit' | 'facility'
|
||||
|
||||
export type PoiDetailSource = 'map' | 'search'
|
||||
|
||||
export interface PoiDetailSourcePoi {
|
||||
id: string
|
||||
name: string
|
||||
floorId: string
|
||||
floorLabel?: string
|
||||
primaryCategory?: MuseumCategory | string
|
||||
kind?: MuseumPoiKind
|
||||
hallId?: string
|
||||
hallName?: string
|
||||
exhibitId?: string
|
||||
sourcePlaceId?: string
|
||||
entrances?: MuseumPoiEntrance[]
|
||||
}
|
||||
|
||||
export interface PoiDetailTarget {
|
||||
poiId: string
|
||||
floorId: string
|
||||
detailType: PoiDetailType
|
||||
detailId: string
|
||||
hallId?: string
|
||||
exhibitId?: string
|
||||
url: string
|
||||
failureMessage: string
|
||||
}
|
||||
|
||||
const normalizeMatchText = (value?: string) => (value || '')
|
||||
.trim()
|
||||
.replace(/[\s()()【】[\]_-]/g, '')
|
||||
.replace(/^展厅\d*/, '')
|
||||
.toLowerCase()
|
||||
|
||||
const categoryIdOf = (poi: PoiDetailSourcePoi) => (
|
||||
typeof poi.primaryCategory === 'string'
|
||||
? poi.primaryCategory
|
||||
: poi.primaryCategory?.id || ''
|
||||
)
|
||||
|
||||
const isHallNameFallbackAllowed = (poi: PoiDetailSourcePoi) => {
|
||||
const categoryId = categoryIdOf(poi)
|
||||
return (
|
||||
poi.kind === 'hall'
|
||||
|| poi.kind === 'hall_entrance'
|
||||
|| categoryId === 'touring_poi'
|
||||
|| categoryId === 'exhibition_hall'
|
||||
|| categoryId === 'exhibition_hall_entrance'
|
||||
)
|
||||
}
|
||||
|
||||
const appendQuery = (url: string, values: Record<string, string | number>) => {
|
||||
const [path, query = ''] = url.split('?')
|
||||
const params = new URLSearchParams(query)
|
||||
Object.entries(values).forEach(([key, value]) => params.set(key, String(value)))
|
||||
return `${path}?${params.toString()}`
|
||||
}
|
||||
|
||||
const createCanonicalDetailUrl = (url: string, poi: PoiDetailSourcePoi) => (
|
||||
// 详情 URL 只承载稳定目标,搜索筛选等首页状态由调用方单独保存。
|
||||
appendQuery(url, {
|
||||
source: 'guide-poi',
|
||||
poiId: poi.id,
|
||||
floorId: poi.floorId
|
||||
})
|
||||
)
|
||||
|
||||
const resolveHallByStableRelationship = (
|
||||
poi: PoiDetailSourcePoi,
|
||||
halls: MuseumHall[]
|
||||
) => {
|
||||
if (poi.hallId) {
|
||||
const hall = halls.find((item) => item.id === poi.hallId)
|
||||
if (hall) return hall
|
||||
}
|
||||
|
||||
const stablePoiIds = new Set([
|
||||
poi.id,
|
||||
poi.sourcePlaceId,
|
||||
...(poi.entrances || []).flatMap((entrance) => [
|
||||
entrance.id,
|
||||
entrance.sourcePlaceId
|
||||
])
|
||||
].filter((id): id is string => Boolean(id)))
|
||||
|
||||
return halls.find((hall) => (
|
||||
(hall.poiId ? stablePoiIds.has(hall.poiId) : false)
|
||||
|| (hall.location?.poiId ? stablePoiIds.has(hall.location.poiId) : false)
|
||||
)) || null
|
||||
}
|
||||
|
||||
const resolveHallByControlledNameFallback = (
|
||||
poi: PoiDetailSourcePoi,
|
||||
halls: MuseumHall[]
|
||||
) => {
|
||||
// 名称只对明确的展厅语义兜底,避免普通空间误入讲解列表。
|
||||
if (!isHallNameFallbackAllowed(poi)) return null
|
||||
const poiHallName = normalizeMatchText(poi.hallName || poi.name)
|
||||
if (!poiHallName) return null
|
||||
return halls.find((hall) => normalizeMatchText(hall.name) === poiHallName) || null
|
||||
}
|
||||
|
||||
export class PoiDetailUseCase {
|
||||
async resolve(poi: PoiDetailSourcePoi): Promise<PoiDetailTarget> {
|
||||
if (poi.exhibitId) {
|
||||
return {
|
||||
poiId: poi.id,
|
||||
floorId: poi.floorId,
|
||||
detailType: 'exhibit',
|
||||
detailId: poi.exhibitId,
|
||||
exhibitId: poi.exhibitId,
|
||||
url: createCanonicalDetailUrl(`/pages/exhibit/detail?id=${encodeURIComponent(poi.exhibitId)}`, poi),
|
||||
failureMessage: '展品详情打开失败,请重试'
|
||||
}
|
||||
}
|
||||
|
||||
const halls = await explainUseCase.listHalls().catch((error) => {
|
||||
console.warn('展厅关联数据加载失败,点位详情降级为普通设施:', error)
|
||||
return []
|
||||
})
|
||||
const hall = resolveHallByStableRelationship(poi, halls)
|
||||
|| resolveHallByControlledNameFallback(poi, halls)
|
||||
if (hall) {
|
||||
return {
|
||||
poiId: poi.id,
|
||||
floorId: poi.floorId,
|
||||
detailType: 'hall',
|
||||
detailId: hall.id,
|
||||
hallId: hall.id,
|
||||
url: createCanonicalDetailUrl(explainGuideStopListUrl(hall.id, hall.name), poi),
|
||||
failureMessage: '展厅讲解列表打开失败,请重试'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
poiId: poi.id,
|
||||
floorId: poi.floorId,
|
||||
detailType: 'facility',
|
||||
detailId: poi.id,
|
||||
url: createCanonicalDetailUrl(
|
||||
`/pages/facility/detail?id=${encodeURIComponent(poi.id)}&target=${encodeURIComponent(poi.name)}`,
|
||||
poi
|
||||
),
|
||||
failureMessage: '点位详情打开失败,请重试'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const poiDetailUseCase = new PoiDetailUseCase()
|
||||
Reference in New Issue
Block a user