调整点位搜索初始空间点位来源
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
lyf
2026-07-05 23:55:20 +08:00
parent a71994d490
commit b99ae572da
5 changed files with 218 additions and 21 deletions

View File

@@ -117,7 +117,7 @@
<view v-if="!floorResults.length" class="empty-state">
<text class="empty-title">{{ emptyTitle }}</text>
<text class="empty-desc">可切换楼层或搜索服务设施</text>
<text class="empty-desc">{{ emptyDesc }}</text>
</view>
</view>
</view>
@@ -192,6 +192,7 @@ const floors = ref<MuseumFloor[]>([])
const activeFloor = ref('1F')
const pois = ref<MuseumPoi[]>([])
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) => {

View File

@@ -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<string, MuseumCategory> = {
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<string, MuseumCategory & { accessible?: boolean }> = {
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[],

View File

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

View File

@@ -38,6 +38,7 @@ import {
toMuseumFloorFromSgs,
toMuseumHallPoisFromSgs,
toMuseumPoiFromSgs,
toMuseumSpacePointFromSgs,
toRouteReadinessFromSgsDiagnostics
} from '@/data/adapters/sgsSdkGuideAdapter'
@@ -130,6 +131,7 @@ export interface GuideRepository {
getFloors(): Promise<MuseumFloor[]>
normalizeFloorId(labelOrId: string): string
listPois(): Promise<MuseumPoi[]>
listSpacePoints(): Promise<MuseumPoi[]>
getPoiById(id: string): Promise<MuseumPoi | null>
searchPois(keyword?: string): Promise<MuseumPoi[]>
getLocationPreview(poiId: string): Promise<GuideLocationPreview | null>
@@ -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<string, string> | 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
}

View File

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