This commit is contained in:
@@ -117,7 +117,7 @@
|
|||||||
|
|
||||||
<view v-if="!floorResults.length" class="empty-state">
|
<view v-if="!floorResults.length" class="empty-state">
|
||||||
<text class="empty-title">{{ emptyTitle }}</text>
|
<text class="empty-title">{{ emptyTitle }}</text>
|
||||||
<text class="empty-desc">可切换楼层或搜索服务设施</text>
|
<text class="empty-desc">{{ emptyDesc }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -192,6 +192,7 @@ const floors = ref<MuseumFloor[]>([])
|
|||||||
const activeFloor = ref('1F')
|
const activeFloor = ref('1F')
|
||||||
const pois = ref<MuseumPoi[]>([])
|
const pois = ref<MuseumPoi[]>([])
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
|
const initialSpaceLoadError = ref('')
|
||||||
|
|
||||||
const showSearchContent = computed(() => props.variant === 'page' || homeExpanded.value)
|
const showSearchContent = computed(() => props.variant === 'page' || homeExpanded.value)
|
||||||
const collapseDragThreshold = 48
|
const collapseDragThreshold = 48
|
||||||
@@ -206,7 +207,13 @@ const floorResults = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const emptyTitle = 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'
|
const isVisiblePoi = (poi: MuseumPoi) => poi.primaryCategory.id !== 'touring_poi'
|
||||||
@@ -277,6 +284,7 @@ const loadFloors = async () => {
|
|||||||
|
|
||||||
const loadPois = async (keyword = '') => {
|
const loadPois = async (keyword = '') => {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
|
initialSpaceLoadError.value = ''
|
||||||
try {
|
try {
|
||||||
const result = await guideUseCase.searchPois(keyword)
|
const result = await guideUseCase.searchPois(keyword)
|
||||||
pois.value = result.filter(isVisiblePoi)
|
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 () => {
|
const focusSearchInput = async () => {
|
||||||
searchInputFocused.value = false
|
searchInputFocused.value = false
|
||||||
await nextTick()
|
await nextTick()
|
||||||
@@ -365,7 +393,7 @@ const handleSearchInput = (event: any) => {
|
|||||||
const handleSearchClear = async () => {
|
const handleSearchClear = async () => {
|
||||||
searchDraftKeyword.value = ''
|
searchDraftKeyword.value = ''
|
||||||
searchKeyword.value = ''
|
searchKeyword.value = ''
|
||||||
await loadPois()
|
await loadInitialSpacePoints()
|
||||||
await focusSearchInput()
|
await focusSearchInput()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -443,10 +471,17 @@ const handleResultTap = (poi: MuseumPoi) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const applyInitialKeyword = async (keyword: string) => {
|
const applyInitialKeyword = async (keyword: string) => {
|
||||||
searchKeyword.value = keyword
|
const normalizedKeyword = keyword.trim()
|
||||||
searchDraftKeyword.value = keyword
|
searchKeyword.value = normalizedKeyword
|
||||||
|
searchDraftKeyword.value = normalizedKeyword
|
||||||
homeExpanded.value = props.variant === 'page' || Boolean(keyword)
|
homeExpanded.value = props.variant === 'page' || Boolean(keyword)
|
||||||
await loadPois(keyword)
|
|
||||||
|
if (normalizedKeyword) {
|
||||||
|
await loadPois(normalizedKeyword)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await loadInitialSpacePoints()
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(() => props.initialKeyword, (keyword) => {
|
watch(() => props.initialKeyword, (keyword) => {
|
||||||
|
|||||||
@@ -54,6 +54,61 @@ const hallEntranceCategory: MuseumCategory = {
|
|||||||
iconType: 'hall_entrance'
|
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 }> = {
|
const categoryBySgsType: Record<string, MuseumCategory & { accessible?: boolean }> = {
|
||||||
poi: {
|
poi: {
|
||||||
id: 'poi',
|
id: 'poi',
|
||||||
@@ -279,6 +334,11 @@ const sgsSpaceCenterConfidence = 'backend-sgs-sdk-space-center'
|
|||||||
const sgsSpaceBoundaryCenterConfidence = 'backend-sgs-sdk-space-boundary-center'
|
const sgsSpaceBoundaryCenterConfidence = 'backend-sgs-sdk-space-boundary-center'
|
||||||
const sgsHallEntranceConfidence = 'backend-sgs-sdk-hall-entrance'
|
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) => (
|
const hasExhibitionKeyword = (value?: string | null) => (
|
||||||
exhibitionHallKeywords.some((keyword) => normalizedText(value).includes(keyword))
|
exhibitionHallKeywords.some((keyword) => normalizedText(value).includes(keyword))
|
||||||
)
|
)
|
||||||
@@ -294,7 +354,7 @@ const searchableSpaceText = (space: SgsSpacePayload) => [
|
|||||||
|
|
||||||
const parseBoundaryWktCenter = (
|
const parseBoundaryWktCenter = (
|
||||||
boundaryWkt?: string | null,
|
boundaryWkt?: string | null,
|
||||||
fallbackY?: number
|
fallbackY = 0
|
||||||
): [number, number, number] | undefined => {
|
): [number, number, number] | undefined => {
|
||||||
const matches = [...(boundaryWkt || '').matchAll(/(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)/g)]
|
const matches = [...(boundaryWkt || '').matchAll(/(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)/g)]
|
||||||
const points = matches
|
const points = matches
|
||||||
@@ -586,6 +646,62 @@ const floorLabelForSgs = (
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 = (
|
const createHallEntrance = (
|
||||||
place: SgsNavigablePlacePayload,
|
place: SgsNavigablePlacePayload,
|
||||||
floors: SgsSdkFloorSummaryPayload[],
|
floors: SgsSdkFloorSummaryPayload[],
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export interface MuseumCategory {
|
|||||||
iconType?: string
|
iconType?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MuseumPoiKind = 'facility' | 'hall' | 'hall_entrance' | 'guide'
|
export type MuseumPoiKind = 'facility' | 'hall' | 'hall_entrance' | 'guide' | 'space'
|
||||||
|
|
||||||
export interface MuseumPoiEntrance {
|
export interface MuseumPoiEntrance {
|
||||||
id: string
|
id: string
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import {
|
|||||||
toMuseumFloorFromSgs,
|
toMuseumFloorFromSgs,
|
||||||
toMuseumHallPoisFromSgs,
|
toMuseumHallPoisFromSgs,
|
||||||
toMuseumPoiFromSgs,
|
toMuseumPoiFromSgs,
|
||||||
|
toMuseumSpacePointFromSgs,
|
||||||
toRouteReadinessFromSgsDiagnostics
|
toRouteReadinessFromSgsDiagnostics
|
||||||
} from '@/data/adapters/sgsSdkGuideAdapter'
|
} from '@/data/adapters/sgsSdkGuideAdapter'
|
||||||
|
|
||||||
@@ -130,6 +131,7 @@ export interface GuideRepository {
|
|||||||
getFloors(): Promise<MuseumFloor[]>
|
getFloors(): Promise<MuseumFloor[]>
|
||||||
normalizeFloorId(labelOrId: string): string
|
normalizeFloorId(labelOrId: string): string
|
||||||
listPois(): Promise<MuseumPoi[]>
|
listPois(): Promise<MuseumPoi[]>
|
||||||
|
listSpacePoints(): Promise<MuseumPoi[]>
|
||||||
getPoiById(id: string): Promise<MuseumPoi | null>
|
getPoiById(id: string): Promise<MuseumPoi | null>
|
||||||
searchPois(keyword?: string): Promise<MuseumPoi[]>
|
searchPois(keyword?: string): Promise<MuseumPoi[]>
|
||||||
getLocationPreview(poiId: string): Promise<GuideLocationPreview | null>
|
getLocationPreview(poiId: string): Promise<GuideLocationPreview | null>
|
||||||
@@ -175,6 +177,10 @@ export class StaticGuideRepository implements GuideRepository {
|
|||||||
return this.poiCache
|
return this.poiCache
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async listSpacePoints() {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
async getPoiById(id: string) {
|
async getPoiById(id: string) {
|
||||||
const pois = await this.listPois()
|
const pois = await this.listPois()
|
||||||
return pois.find((poi) => poi.id === id) || findPoiByNavIdNameFallback(pois, id)
|
return pois.find((poi) => poi.id === id) || findPoiByNavIdNameFallback(pois, id)
|
||||||
@@ -254,6 +260,7 @@ export class StaticGuideRepository implements GuideRepository {
|
|||||||
export class SgsSdkGuideRepository implements GuideRepository {
|
export class SgsSdkGuideRepository implements GuideRepository {
|
||||||
private floorsCache: MuseumFloor[] | null = null
|
private floorsCache: MuseumFloor[] | null = null
|
||||||
private poiCache: MuseumPoi[] | null = null
|
private poiCache: MuseumPoi[] | null = null
|
||||||
|
private spacePointCache: MuseumPoi[] | null = null
|
||||||
private floorAliases: Map<string, string> | null = null
|
private floorAliases: Map<string, string> | null = null
|
||||||
|
|
||||||
constructor(private readonly provider: SgsSdkApiProvider = defaultSgsSdkApiProvider) {}
|
constructor(private readonly provider: SgsSdkApiProvider = defaultSgsSdkApiProvider) {}
|
||||||
@@ -326,10 +333,45 @@ export class SgsSdkGuideRepository implements GuideRepository {
|
|||||||
return this.poiCache
|
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) {
|
async getPoiById(id: string) {
|
||||||
const pois = await this.listPois()
|
const pois = await this.listPois()
|
||||||
return pois.find((poi) => poi.id === id)
|
const matchedPoi = pois.find((poi) => poi.id === id)
|
||||||
|| findPoiByNavIdNameFallback(pois, 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
|
|| null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -139,6 +139,10 @@ export class GuideUseCase {
|
|||||||
return this.guide.searchPois(keyword)
|
return this.guide.searchPois(keyword)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getInitialSearchSpacePoints() {
|
||||||
|
return this.guide.listSpacePoints()
|
||||||
|
}
|
||||||
|
|
||||||
getPoiById(id: string) {
|
getPoiById(id: string) {
|
||||||
return this.guide.getPoiById(id)
|
return this.guide.getPoiById(id)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user