fix: 修复导览按钮文案与三维路线显示联动

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
lyf
2026-06-26 04:17:03 +08:00
parent d4b97379bb
commit a7413ee037
5 changed files with 592 additions and 93 deletions

View File

@@ -0,0 +1,226 @@
import type {
GuideRouteReadiness,
GuideRouteResult,
GuideRouteTarget
} from '@/domain/museum'
import {
NAV_ROUTE_READINESS
} from '@/domain/guideReadiness'
import {
GuideRouteError
} from '@/repositories/GuideRouteRepository'
import {
defaultSgsSdkApiProvider,
type SgsNavigablePlacePayload,
type SgsSdkApiProvider
} from '@/data/providers/sgsSdkApiProvider'
import {
toGuideRouteResultFromSgsPlan
} from '@/data/adapters/sgsSdkRouteAdapter'
import { stringifyId, formatSgsFloorLabel } from '@/data/adapters/sgsSdkGuideAdapter'
import { toAppFloorId } from '@/services/sgs/SgsMapEventAdapter'
const createUnavailableReadiness = (message: string): GuideRouteReadiness => ({
ready: false,
message,
requiredData: ['route_graph', 'nav_data']
})
const toNumber = (value: unknown) => (
Number.isFinite(Number(value)) ? Number(value) : undefined
)
const positionFromPlace = (place: SgsNavigablePlacePayload): [number, number, number] | undefined => {
const source = place.position || {
x: place.x,
y: place.y,
z: place.z
}
const x = toNumber(source.x)
const z = toNumber(source.z)
if (typeof x !== 'number' || typeof z !== 'number') return undefined
return [
x,
toNumber(source.y) || 0,
z
]
}
const numericId = (value: unknown) => {
const numberValue = Number(value)
return Number.isFinite(numberValue) ? numberValue : undefined
}
export class SdkGuideRouteRepository {
private targetsCache: GuideRouteTarget[] | null = null
constructor(
private readonly sdkApiProvider: SgsSdkApiProvider = defaultSgsSdkApiProvider
) {}
async getRouteReadiness(): Promise<GuideRouteReadiness> {
try {
const diagnostics = await this.sdkApiProvider.getMapDiagnostics()
const floors = diagnostics.floors || []
if (!floors.length) {
return createUnavailableReadiness('SDK 地图楼层数据为空')
}
// 收集有路线数据的楼层
const readyFloors = floors.filter(
(floor) =>
floor.routePlanningReady &&
floor.routeNodeCount &&
floor.routeNodeCount > 0 &&
floor.routeEdgeCount &&
floor.routeEdgeCount > 0
)
// 没有楼层有路线数据时不可用
if (readyFloors.length === 0) {
return createUnavailableReadiness('SDK 路线规划数据不完整,当前暂不支持正式导航')
}
// 收集未就绪的楼层
const notReadyFloors = floors.filter((floor) => !floor.routePlanningReady)
// 如果有楼层未就绪,给出警告但仍然可用
if (notReadyFloors.length > 0) {
const floorLabels = notReadyFloors
.map((f) => f.floorName || f.floorCode || String(f.floorId))
.join('、')
return {
ready: true,
message: `部分楼层路线数据未就绪:${floorLabels},已就绪楼层可正常使用导览功能`,
requiredData: []
}
}
return {
ready: true,
message: 'SDK 路线数据已就绪',
requiredData: []
}
} catch (error) {
return createUnavailableReadiness(
error instanceof Error ? error.message : 'SDK 路线数据加载失败'
)
}
}
async listRouteTargets(): Promise<GuideRouteTarget[]> {
if (this.targetsCache) return this.targetsCache
const diagnostics = await this.sdkApiProvider.getMapDiagnostics()
const floors = diagnostics.floors || []
const targets: GuideRouteTarget[] = []
for (const floor of floors) {
// 使用原始 floorId 请求 APISDK API 需要原始 ID 如 2065808921272119298
const rawFloorId = stringifyId(floor.floorId)
try {
const places = await this.sdkApiProvider.getNavigablePlaces(rawFloorId)
for (const place of places) {
// 过滤掉没有 nodeId 的点位,这些无法用于路线规划
if (place.nodeId === null || place.nodeId === undefined || place.nodeId === '') {
continue
}
const placeId = stringifyId(place.id)
const placeName = place.name || `目的地 ${placeId}`
const categoryLabel = place.category || place.typeName || place.typeCode || place.type || undefined
const positionGltf = positionFromPlace(place)
// 使用统一的楼层标签格式化函数,确保与 nav-assets 格式一致
const floorLabel = formatSgsFloorLabel(
place.floorCode || undefined,
place.floorName || floor.floorName || undefined
)
targets.push({
poiId: placeId,
name: placeName,
// 展示用标准化的 floorId如 L1
floorId: toAppFloorId(place.floorId ?? rawFloorId),
floorLabel,
categoryLabel,
positionGltf,
// 保存原始 nodeId 用于 API 调用
routeNodeId: stringifyId(place.nodeId)
})
}
} catch {
// 忽略单个楼层的加载错误
}
}
this.targetsCache = targets
return targets
}
async findRoute(startPoiId: string, endPoiId: string): Promise<GuideRouteResult> {
const readiness = await this.getRouteReadiness()
if (!readiness.ready) {
throw new GuideRouteError(
'ROUTE_DATA_UNAVAILABLE',
readiness.message || NAV_ROUTE_READINESS.message
)
}
if (!startPoiId || !endPoiId) {
throw new GuideRouteError('ROUTE_TARGET_MISSING', '请选择起点和终点')
}
if (startPoiId === endPoiId) {
throw new GuideRouteError('ROUTE_TARGET_MISSING', '起点和终点不能相同')
}
const targets = await this.listRouteTargets()
const startTarget = targets.find((target) => target.poiId === startPoiId)
const endTarget = targets.find((target) => target.poiId === endPoiId)
if (!startTarget || !endTarget) {
throw new GuideRouteError('ROUTE_TARGET_MISSING', '起点或终点不在 SDK 可导航目的地列表中')
}
try {
if (!startTarget.positionGltf || !endTarget.positionGltf) {
throw new GuideRouteError('ROUTE_TARGET_UNANCHORED', '起点或终点缺少可规划坐标')
}
// 获取原始 floorId 用于 API 调用
const diagnostics = await this.sdkApiProvider.getMapDiagnostics()
const floorIdMap = new Map<string, string>()
for (const floor of diagnostics.floors || []) {
floorIdMap.set(toAppFloorId(floor.floorId), stringifyId(floor.floorId))
}
const rawStartFloorId = floorIdMap.get(startTarget.floorId) || startTarget.floorId
const rawEndFloorId = floorIdMap.get(endTarget.floorId) || endTarget.floorId
const route = await this.sdkApiProvider.planRoute({
startFloorId: rawStartFloorId,
startX: startTarget.positionGltf[0],
startY: startTarget.positionGltf[2],
startNodeId: numericId(startTarget.routeNodeId) || null,
endFloorId: rawEndFloorId,
endX: endTarget.positionGltf[0],
endY: endTarget.positionGltf[2],
endNodeId: numericId(endTarget.routeNodeId) || null,
wheelchair: 0
})
return toGuideRouteResultFromSgsPlan(route, startTarget, endTarget)
} catch (error) {
if (error instanceof GuideRouteError) throw error
throw new GuideRouteError(
'ROUTE_NOT_FOUND',
error instanceof Error ? error.message : 'SGS 路线接口规划失败'
)
}
}
}