From b99ae572da003281630d18c74c5f35b0340b1efa Mon Sep 17 00:00:00 2001
From: lyf <2514544224@qq.com>
Date: Sun, 5 Jul 2026 23:55:20 +0800
Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4=E7=82=B9=E4=BD=8D=E6=90=9C?=
=?UTF-8?q?=E7=B4=A2=E5=88=9D=E5=A7=8B=E7=A9=BA=E9=97=B4=E7=82=B9=E4=BD=8D?=
=?UTF-8?q?=E6=9D=A5=E6=BA=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/components/search/PoiSearchPanel.vue | 47 +++++++-
src/data/adapters/sgsSdkGuideAdapter.ts | 142 ++++++++++++++++++++---
src/domain/museum.ts | 2 +-
src/repositories/GuideRepository.ts | 44 ++++++-
src/usecases/guideUseCase.ts | 4 +
5 files changed, 218 insertions(+), 21 deletions(-)
diff --git a/src/components/search/PoiSearchPanel.vue b/src/components/search/PoiSearchPanel.vue
index ba16f7d..fab6d56 100644
--- a/src/components/search/PoiSearchPanel.vue
+++ b/src/components/search/PoiSearchPanel.vue
@@ -117,7 +117,7 @@
{{ emptyTitle }}
- 可切换楼层或搜索服务设施
+ {{ emptyDesc }}
@@ -192,6 +192,7 @@ const floors = ref([])
const activeFloor = ref('1F')
const pois = ref([])
const isLoading = ref(false)
+const initialSpaceLoadError = ref('')
const showSearchContent = computed(() => props.variant === 'page' || homeExpanded.value)
const collapseDragThreshold = 48
@@ -206,7 +207,13 @@ const floorResults = computed(() => {
})
const emptyTitle = computed(() => (
- isLoading.value ? '正在读取点位' : '暂无匹配点位'
+ isLoading.value ? '正在读取点位' : initialSpaceLoadError.value || '暂无匹配点位'
+))
+
+const emptyDesc = computed(() => (
+ initialSpaceLoadError.value
+ ? '请检查 SGS 空间点位数据接口后重试'
+ : '可切换楼层或搜索服务设施'
))
const isVisiblePoi = (poi: MuseumPoi) => poi.primaryCategory.id !== 'touring_poi'
@@ -277,6 +284,7 @@ const loadFloors = async () => {
const loadPois = async (keyword = '') => {
isLoading.value = true
+ initialSpaceLoadError.value = ''
try {
const result = await guideUseCase.searchPois(keyword)
pois.value = result.filter(isVisiblePoi)
@@ -292,6 +300,26 @@ const loadPois = async (keyword = '') => {
}
}
+const loadInitialSpacePoints = async () => {
+ isLoading.value = true
+ initialSpaceLoadError.value = ''
+
+ try {
+ const result = await guideUseCase.getInitialSearchSpacePoints()
+ pois.value = result.filter(isVisiblePoi)
+ } catch (error) {
+ console.error('加载 SDK 空间点位失败:', error)
+ pois.value = []
+ initialSpaceLoadError.value = '空间点位读取失败'
+ uni.showToast({
+ title: '空间点位读取失败',
+ icon: 'none'
+ })
+ } finally {
+ isLoading.value = false
+ }
+}
+
const focusSearchInput = async () => {
searchInputFocused.value = false
await nextTick()
@@ -365,7 +393,7 @@ const handleSearchInput = (event: any) => {
const handleSearchClear = async () => {
searchDraftKeyword.value = ''
searchKeyword.value = ''
- await loadPois()
+ await loadInitialSpacePoints()
await focusSearchInput()
}
@@ -443,10 +471,17 @@ const handleResultTap = (poi: MuseumPoi) => {
}
const applyInitialKeyword = async (keyword: string) => {
- searchKeyword.value = keyword
- searchDraftKeyword.value = keyword
+ const normalizedKeyword = keyword.trim()
+ searchKeyword.value = normalizedKeyword
+ searchDraftKeyword.value = normalizedKeyword
homeExpanded.value = props.variant === 'page' || Boolean(keyword)
- await loadPois(keyword)
+
+ if (normalizedKeyword) {
+ await loadPois(normalizedKeyword)
+ return
+ }
+
+ await loadInitialSpacePoints()
}
watch(() => props.initialKeyword, (keyword) => {
diff --git a/src/data/adapters/sgsSdkGuideAdapter.ts b/src/data/adapters/sgsSdkGuideAdapter.ts
index fda5490..64361ca 100644
--- a/src/data/adapters/sgsSdkGuideAdapter.ts
+++ b/src/data/adapters/sgsSdkGuideAdapter.ts
@@ -48,11 +48,66 @@ const hallCategory: MuseumCategory = {
iconType: 'exhibition_hall'
}
-const hallEntranceCategory: MuseumCategory = {
- id: 'exhibition_hall_entrance',
- label: '展厅出入口',
- iconType: 'hall_entrance'
-}
+const hallEntranceCategory: MuseumCategory = {
+ id: 'exhibition_hall_entrance',
+ label: '展厅出入口',
+ iconType: 'hall_entrance'
+}
+
+const spaceCategory: MuseumCategory = {
+ id: 'space_point',
+ label: '空间点位',
+ iconType: 'space'
+}
+
+const spaceCategoryBySgsType: Record = {
+ exhibition_hall: hallCategory,
+ theater: {
+ id: 'space_theater',
+ label: '剧场空间',
+ iconType: 'theater'
+ },
+ education_activity: {
+ id: 'space_education_activity',
+ label: '教育活动空间',
+ iconType: 'education_activity'
+ },
+ ramp: {
+ id: 'space_ramp',
+ label: '空间坡道',
+ iconType: 'ramp'
+ },
+ public_area: {
+ id: 'space_public_area',
+ label: '公共空间',
+ iconType: 'public_area'
+ },
+ service_space: {
+ id: 'space_service',
+ label: '服务空间',
+ iconType: 'service_space'
+ },
+ commercial: {
+ id: 'space_commercial',
+ label: '商业空间',
+ iconType: 'commercial'
+ },
+ parking: {
+ id: 'space_parking',
+ label: '停车空间',
+ iconType: 'parking'
+ },
+ plaza: {
+ id: 'space_plaza',
+ label: '广场空间',
+ iconType: 'plaza'
+ },
+ room: {
+ id: 'space_room',
+ label: '房间空间',
+ iconType: 'room'
+ }
+}
const categoryBySgsType: Record = {
poi: {
@@ -278,6 +333,11 @@ interface SgsHallPoiBuildOptions {
const sgsSpaceCenterConfidence = 'backend-sgs-sdk-space-center'
const sgsSpaceBoundaryCenterConfidence = 'backend-sgs-sdk-space-boundary-center'
const sgsHallEntranceConfidence = 'backend-sgs-sdk-hall-entrance'
+
+const normalizeTypeId = (value?: string | null) => normalizedText(value)
+ .toLowerCase()
+ .replace(/[^a-z0-9_-]+/g, '_')
+ .replace(/^_+|_+$/g, '')
const hasExhibitionKeyword = (value?: string | null) => (
exhibitionHallKeywords.some((keyword) => normalizedText(value).includes(keyword))
@@ -294,7 +354,7 @@ const searchableSpaceText = (space: SgsSpacePayload) => [
const parseBoundaryWktCenter = (
boundaryWkt?: string | null,
- fallbackY?: number
+ fallbackY = 0
): [number, number, number] | undefined => {
const matches = [...(boundaryWkt || '').matchAll(/(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)/g)]
const points = matches
@@ -573,19 +633,75 @@ const findMatchedHallSpace = (
return rankedMatches[0]?.space
}
-const floorLabelForSgs = (
- floorId: string,
- floors: SgsSdkFloorSummaryPayload[],
- floorCode?: string | null,
+const floorLabelForSgs = (
+ floorId: string,
+ floors: SgsSdkFloorSummaryPayload[],
+ floorCode?: string | null,
floorName?: string | null
) => {
const matchedFloor = floors.find((floor) => stringifyId(floor.floorId) === floorId || floor.floorCode === floorCode)
return formatSgsFloorLabel(
matchedFloor?.floorCode || floorCode,
matchedFloor?.floorName || floorName
- )
-}
-
+ )
+}
+
+const categoryForSgsSpace = (space: SgsSpacePayload): MuseumCategory => {
+ const normalizedType = normalizeTypeId(space.type)
+
+ if (normalizedType && spaceCategoryBySgsType[normalizedType]) {
+ return spaceCategoryBySgsType[normalizedType]
+ }
+
+ const typeLabel = normalizedText(space.type)
+ if (typeLabel) {
+ return {
+ id: `space_${normalizedType || 'point'}`,
+ label: typeLabel,
+ iconType: normalizedType || spaceCategory.iconType
+ }
+ }
+
+ return spaceCategory
+}
+
+export const toMuseumSpacePointFromSgs = (
+ space: SgsSpacePayload,
+ floors: SgsSdkFloorSummaryPayload[],
+ fallbackFloorId: string,
+ options: SgsHallPoiBuildOptions = {}
+): MuseumPoi | null => {
+ const spaceId = stringifyId(space.id)
+ const floorId = stringifyId(space.floorId) || fallbackFloorId
+ const name = normalizedText(space.name)
+ const spacePosition = normalizeSpacePosition(space, options.fallbackY)
+
+ if (!spaceId || !name || !spacePosition) return null
+
+ const category = categoryForSgsSpace(space)
+
+ return {
+ id: `space-${spaceId}`,
+ name,
+ floorId,
+ floorLabel: floorLabelForSgs(floorId, floors),
+ primaryCategory: category,
+ categories: [
+ category
+ ],
+ positionGltf: spacePosition.position,
+ sourceObjectName: space.sourceNodeName || undefined,
+ sourceConfidence: spacePosition.sourceConfidence,
+ navigationReadiness: '位置预览',
+ accessible: false,
+ kind: 'space',
+ hallId: category.id === hallCategory.id ? spaceId : undefined,
+ hallName: category.id === hallCategory.id ? name : undefined,
+ spaceId,
+ sourceSpaceId: spaceId
+ }
+}
+
const createHallEntrance = (
place: SgsNavigablePlacePayload,
floors: SgsSdkFloorSummaryPayload[],
diff --git a/src/domain/museum.ts b/src/domain/museum.ts
index eb17af5..7de59c2 100644
--- a/src/domain/museum.ts
+++ b/src/domain/museum.ts
@@ -72,7 +72,7 @@ export interface MuseumCategory {
iconType?: string
}
-export type MuseumPoiKind = 'facility' | 'hall' | 'hall_entrance' | 'guide'
+export type MuseumPoiKind = 'facility' | 'hall' | 'hall_entrance' | 'guide' | 'space'
export interface MuseumPoiEntrance {
id: string
diff --git a/src/repositories/GuideRepository.ts b/src/repositories/GuideRepository.ts
index fcabcf4..201ce14 100644
--- a/src/repositories/GuideRepository.ts
+++ b/src/repositories/GuideRepository.ts
@@ -38,6 +38,7 @@ import {
toMuseumFloorFromSgs,
toMuseumHallPoisFromSgs,
toMuseumPoiFromSgs,
+ toMuseumSpacePointFromSgs,
toRouteReadinessFromSgsDiagnostics
} from '@/data/adapters/sgsSdkGuideAdapter'
@@ -130,6 +131,7 @@ export interface GuideRepository {
getFloors(): Promise
normalizeFloorId(labelOrId: string): string
listPois(): Promise
+ listSpacePoints(): Promise
getPoiById(id: string): Promise
searchPois(keyword?: string): Promise
getLocationPreview(poiId: string): Promise
@@ -175,6 +177,10 @@ export class StaticGuideRepository implements GuideRepository {
return this.poiCache
}
+ async listSpacePoints() {
+ return []
+ }
+
async getPoiById(id: string) {
const pois = await this.listPois()
return pois.find((poi) => poi.id === id) || findPoiByNavIdNameFallback(pois, id)
@@ -254,6 +260,7 @@ export class StaticGuideRepository implements GuideRepository {
export class SgsSdkGuideRepository implements GuideRepository {
private floorsCache: MuseumFloor[] | null = null
private poiCache: MuseumPoi[] | null = null
+ private spacePointCache: MuseumPoi[] | null = null
private floorAliases: Map | null = null
constructor(private readonly provider: SgsSdkApiProvider = defaultSgsSdkApiProvider) {}
@@ -326,10 +333,45 @@ export class SgsSdkGuideRepository implements GuideRepository {
return this.poiCache
}
+ async listSpacePoints() {
+ if (this.spacePointCache) return this.spacePointCache
+
+ const manifest = await this.provider.getManifest()
+ const indoorFloors = manifest.floors.filter(isIndoorNavigableFloor)
+ const indoorFloorIds = new Set(indoorFloors.map((floor) => String(floor.floorId)))
+ this.floorAliases = buildSgsFloorAliases(indoorFloors)
+
+ const floorDataGroups = await Promise.all(
+ indoorFloors.map(async (floor) => {
+ const floorId = String(floor.floorId)
+ const spaces = await this.provider.getFloorSpaces(floorId).catch(() => [])
+
+ return {
+ floorId,
+ spaces
+ }
+ })
+ )
+
+ const spacePoints = floorDataGroups
+ .flatMap((group) => group.spaces.map((space) => toMuseumSpacePointFromSgs(space, manifest.floors, group.floorId)))
+ .filter((poi): poi is MuseumPoi => Boolean(poi))
+ .filter((poi) => indoorFloorIds.has(String(poi.floorId)) || isPoiOnIndoorNavigableFloor(poi))
+
+ this.spacePointCache = dedupePoisById(spacePoints)
+
+ return this.spacePointCache
+ }
+
async getPoiById(id: string) {
const pois = await this.listPois()
- return pois.find((poi) => poi.id === id)
+ const matchedPoi = pois.find((poi) => poi.id === id)
|| findPoiByNavIdNameFallback(pois, id)
+
+ if (matchedPoi) return matchedPoi
+
+ const spacePoints = await this.listSpacePoints()
+ return spacePoints.find((poi) => poi.id === id || poi.spaceId === id || poi.sourceSpaceId === id)
|| null
}
diff --git a/src/usecases/guideUseCase.ts b/src/usecases/guideUseCase.ts
index a51cd04..82a821b 100644
--- a/src/usecases/guideUseCase.ts
+++ b/src/usecases/guideUseCase.ts
@@ -139,6 +139,10 @@ export class GuideUseCase {
return this.guide.searchPois(keyword)
}
+ getInitialSearchSpacePoints() {
+ return this.guide.listSpacePoints()
+ }
+
getPoiById(id: string) {
return this.guide.getPoiById(id)
}