This commit is contained in:
@@ -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) => {
|
||||
|
||||
@@ -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[],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user