修复导览点位可见性与构建门禁

This commit is contained in:
cxk
2026-07-20 23:54:19 +08:00
parent 735c5cd1ee
commit 1f89e8b3e0
14 changed files with 499 additions and 168 deletions

View File

@@ -5,7 +5,11 @@ import {
} from '@/domain/poiCategories'
import type { SgsSdkApiProvider, SgsSdkManifestPayload } from '@/data/providers/sgsSdkApiProvider'
import type { StaticNavAssetsProvider } from '@/data/providers/staticNavAssetsProvider'
import { SgsSdkGuideRepository, StaticGuideRepository } from '@/repositories/GuideRepository'
import {
canonicalizeVisitorSearchPois,
SgsSdkGuideRepository,
StaticGuideRepository
} from '@/repositories/GuideRepository'
import {
SgsSdkGuideModelRepository,
StaticGuideModelRepository
@@ -40,7 +44,17 @@ const floorSpaces = [
{ id: 'space-hall', name: '地球展厅', type: 'exhibition_hall', floorId: 'L1', center: { x: 1, y: 12, z: 1 } },
{ id: 'space-cinema', name: '穹幕影院', type: 'theater', floorId: 'L1', center: { x: 2, y: 12, z: 2 } },
{ id: 'space-dining', name: '餐饮区', type: 'restaurant', floorId: 'L1', center: { x: 3, y: 12, z: 3 } },
{ id: 'space-shopping', name: '文创商店', type: 'commercial', floorId: 'L1', center: { x: 4, y: 12, z: 4 } }
{ id: 'space-shopping', name: '文创商店', type: 'commercial', floorId: 'L1', center: { x: 4, y: 12, z: 4 } },
{ id: 'space-private-tea', name: '茶水间', type: 'service_space', floorId: 'L1', center: { x: 5, y: 12, z: 5 } },
{ id: 'space-private-vip', name: '贵宾接待区', type: 'service_space', floorId: 'L1', center: { x: 6, y: 12, z: 6 } },
{
id: 'space-public-rest',
name: '游客休息区',
type: 'service_space',
floorId: 'L1',
center: { x: 7, y: 12, z: 7 },
visitorVisible: true
}
]
const floorPois = [
@@ -120,17 +134,25 @@ describe('GuideRepository visitor search contracts', () => {
const provider = createSgsProvider()
const repository = new SgsSdkGuideRepository(provider)
const [defaultResults, availability] = await Promise.all([
const [defaultResults, availability, privateKeyword, publicKeyword] = await Promise.all([
repository.listDestinationPois('L1'),
repository.getQuickFindCategoryAvailability('L1')
repository.getQuickFindCategoryAvailability('L1'),
repository.searchPois('茶水间', 'L1'),
repository.searchPois('游客休息区', 'L1')
])
expect(defaultResults.map((poi) => poi.id)).toEqual(expect.arrayContaining([
'hall-space-hall',
'hall-space-cinema',
'space-space-dining',
'space-space-shopping'
'space-space-shopping',
'space-space-public-rest'
]))
for (const privatePoiId of ['space-space-private-tea', 'space-space-private-vip']) {
expect(defaultResults.map((poi) => poi.id)).not.toContain(privatePoiId)
}
expect(privateKeyword).toEqual([])
expect(publicKeyword.map((poi) => poi.id)).toEqual(['space-space-public-rest'])
expect(defaultResults.map((poi) => poi.id)).not.toEqual(expect.arrayContaining([
'restroom',
'elevator',
@@ -160,9 +182,13 @@ describe('GuideRepository visitor search contracts', () => {
// survive model marker deduplication.
expect(renderPoiIds.has('restroom')).toBe(true)
expect(renderPoiIds.has('elevator')).toBe(true)
expect(renderPoiIds.has('space-space-public-rest')).toBe(true)
for (const hiddenPoiId of ['door', 'anchor', 'route-node', 'guide-stop']) {
expect(renderPoiIds.has(hiddenPoiId)).toBe(false)
}
for (const privatePoiId of ['space-space-private-tea', 'space-space-private-vip']) {
expect(renderPoiIds.has(privatePoiId)).toBe(false)
}
const quickResultIds = (await Promise.all(allQuickCategoryIds.map((categoryId) => (
repository.listQuickFindPois(categoryId, 'L1')
)))).flat().map((poi) => poi.id)
@@ -188,6 +214,115 @@ describe('GuideRepository visitor search contracts', () => {
expect(hiddenKeyword).toEqual([])
})
it('does not resolve private direct IDs outside the visitor search pool', async () => {
const repository = new SgsSdkGuideRepository(createSgsProvider({
queryPois: vi.fn().mockResolvedValue([{
id: 'private-direct-space',
name: '茶水间',
type: 'service_space',
floorId: 'L1',
visitorVisible: false,
position: { x: 30, y: 12, z: 30 }
}])
}))
await expect(repository.getPoiById('private-direct-space')).resolves.toBeNull()
})
it('keeps private and structural space duplicates out of search lists and map markers', async () => {
const provider = createSgsProvider({
getFloorPois: vi.fn().mockResolvedValue([
{
id: 'poi-private-dining',
name: '西侧餐饮区',
type: 'restaurant',
floorId: 'L1',
spatialAreaId: 'space-private-dining',
position: { x: 30, y: 12, z: 30 }
},
{
id: 'poi-service-space',
name: '服务空间',
type: 'service_space',
floorId: 'L1',
position: { x: 31, y: 12, z: 31 }
}
]),
getFloorBusinessPois: vi.fn().mockResolvedValue([]),
getFloorSpaces: vi.fn().mockResolvedValue([
{
id: 'space-private-dining',
name: '西侧餐饮区',
type: 'restaurant',
floorId: 'L1',
visitorVisible: false,
center: { x: 30, y: 12, z: 30 }
},
{
id: 'space-private-cinema',
name: '私享影院',
type: 'theater',
floorId: 'L1',
visitorVisible: false,
center: { x: 32, y: 12, z: 32 }
},
{
id: 'space-route-node',
name: '导航区域',
type: 'navigable_place',
floorId: 'L1',
center: { x: 33, y: 12, z: 33 }
}
]),
getNavigablePlaces: vi.fn().mockResolvedValue([])
})
const repository = new SgsSdkGuideRepository(provider)
const modelRepository = new SgsSdkGuideModelRepository(provider, repository)
const [defaultResults, dining, cinema, privateKeyword, serviceKeyword, structuralKeyword, renderPois] = await Promise.all([
repository.listDestinationPois('L1'),
repository.listQuickFindPois('dining', 'L1'),
repository.listQuickFindPois('cinema', 'L1'),
repository.searchPois('西侧餐饮区', 'L1'),
repository.searchPois('服务空间', 'L1'),
repository.searchPois('导航区域', 'L1'),
modelRepository.loadFloorPois('L1')
])
expect(defaultResults).toEqual([])
expect(dining).toEqual([])
expect(cinema).toEqual([])
expect(privateKeyword).toEqual([])
expect(serviceKeyword).toEqual([])
expect(structuralKeyword).toEqual([])
expect(renderPois).toEqual([])
})
it('merges source-place duplicates before returning canonical visitor IDs', () => {
const basePoi = {
floorId: 'L1',
floorLabel: '1F',
primaryCategory: { id: 'space_restaurant', label: '餐饮', iconType: 'restaurant' },
categories: [{ id: 'space_restaurant', label: '餐饮', iconType: 'restaurant' }],
positionGltf: [40, 12, 40] as [number, number, number],
accessible: false,
kind: 'facility' as const,
sourcePlaceId: 'place-cafe'
}
const results = canonicalizeVisitorSearchPois([
{ ...basePoi, id: 'poi-cafe-a', name: '中庭咖啡' },
{
...basePoi,
id: 'poi-cafe-b',
name: '中庭咖啡取餐台',
positionGltf: [41, 12, 41]
}
])
expect(results.map((poi) => poi.id)).toEqual(['poi-cafe-a'])
})
it('deduplicates co-located sources when they do not provide a shared spatial area ID', async () => {
const repository = new SgsSdkGuideRepository(createSgsProvider({
getFloorPois: vi.fn().mockResolvedValue([{
@@ -348,6 +483,24 @@ describe('GuideRepository visitor search contracts', () => {
primaryCategoryZh: '基础服务设施',
iconType: 'restroom',
positionGltf: position(4)
},
{
id: 'static-vip-reception',
name: '贵宾接待区',
floorId: 'L1',
primaryCategory: 'basic_service_facility',
primaryCategoryZh: '基础服务设施',
iconType: 'restroom',
positionGltf: position(5)
},
{
id: 'static-vip-restroom',
name: '贵宾卫生间',
floorId: 'L1',
primaryCategory: 'basic_service_facility',
primaryCategoryZh: '基础服务设施',
iconType: 'restroom',
positionGltf: position(6)
}
])
} as unknown as StaticNavAssetsProvider
@@ -362,6 +515,10 @@ describe('GuideRepository visitor search contracts', () => {
await expect(repository.searchPois('洗手间', 'L1')).resolves.toMatchObject([
{ id: 'static-restroom' }
])
await expect(repository.searchPois('茶水间', 'L1')).resolves.toEqual([])
await expect(repository.searchPois('贵宾', 'L1')).resolves.toEqual([])
await expect(repository.getPoiById('static-tea-room')).resolves.toBeNull()
await expect(repository.getPoiById('static-vip-restroom')).resolves.toBeNull()
await expect(repository.searchPois('服务', 'L1')).resolves.toEqual([
expect.objectContaining({ id: 'static-service-desk' })
])
@@ -397,6 +554,24 @@ describe('GuideRepository visitor search contracts', () => {
primaryCategoryZh: '讲解点',
iconType: 'guide',
positionGltf: position(2)
},
{
id: 'static-private-tea',
name: '茶水间',
floorId: 'L1',
primaryCategory: 'basic_service_facility',
primaryCategoryZh: '基础服务设施',
iconType: 'restroom',
positionGltf: position(3)
},
{
id: 'static-vip-restroom',
name: '贵宾卫生间',
floorId: 'L1',
primaryCategory: 'basic_service_facility',
primaryCategoryZh: '基础服务设施',
iconType: 'restroom',
positionGltf: position(4)
}
])
} as unknown as StaticNavAssetsProvider