修复 SGS SDK 导览数据源与模型加载
升级 SDK API 数据契约,补齐 guideStops、业务 POI、诊断与路线数据接口。 修复 SDK 模式下 POI/模型/楼层混用静态数据的问题,并为 ThreeMap 模型加载增加短退避重试。
This commit is contained in:
@@ -6,10 +6,12 @@ import type {
|
||||
GuideLocationPreview,
|
||||
GuideMapDiagnostics,
|
||||
GuideRouteReadiness,
|
||||
MuseumCategory,
|
||||
MuseumFloor,
|
||||
MuseumPoi
|
||||
} from '@/domain/museum'
|
||||
MuseumCategory,
|
||||
MuseumFloor,
|
||||
MuseumPoi,
|
||||
ExplainGuideStop,
|
||||
AudioPlayTargetType
|
||||
} from '@/domain/museum'
|
||||
import {
|
||||
NAV_ROUTE_UNAVAILABLE_MESSAGE
|
||||
} from '@/domain/guideReadiness'
|
||||
@@ -17,23 +19,30 @@ import {
|
||||
isIndoorNavigableFloor
|
||||
} from '@/domain/guideFloor'
|
||||
import type {
|
||||
SgsFloorDiagnosticsPayload,
|
||||
SgsMapDiagnosticsPayload,
|
||||
SgsNavigablePlacePayload,
|
||||
SgsPoiPayload,
|
||||
SgsFloorDiagnosticsPayload,
|
||||
SgsGuideStopPayload,
|
||||
SgsMapDiagnosticsPayload,
|
||||
SgsNavigablePlacePayload,
|
||||
SgsPoiPayload,
|
||||
SgsPositionPayload,
|
||||
SgsSdkFloorSummaryPayload,
|
||||
SgsSdkManifestPayload,
|
||||
SgsSpacePayload
|
||||
} from '@/data/providers/sgsSdkApiProvider'
|
||||
|
||||
const defaultCategory: MuseumCategory = {
|
||||
id: 'operation_experience',
|
||||
label: '导览点位',
|
||||
iconType: 'poi'
|
||||
}
|
||||
|
||||
const hallCategory: MuseumCategory = {
|
||||
const defaultCategory: MuseumCategory = {
|
||||
id: 'operation_experience',
|
||||
label: '导览点位',
|
||||
iconType: 'poi'
|
||||
}
|
||||
|
||||
const businessCategory: MuseumCategory = {
|
||||
id: 'business_poi',
|
||||
label: '运营服务',
|
||||
iconType: 'business'
|
||||
}
|
||||
|
||||
const hallCategory: MuseumCategory = {
|
||||
id: 'exhibition_hall',
|
||||
label: '展厅',
|
||||
iconType: 'exhibition_hall'
|
||||
@@ -45,7 +54,7 @@ const hallEntranceCategory: MuseumCategory = {
|
||||
iconType: 'hall_entrance'
|
||||
}
|
||||
|
||||
const categoryBySgsType: Record<string, MuseumCategory & { accessible?: boolean }> = {
|
||||
const categoryBySgsType: Record<string, MuseumCategory & { accessible?: boolean }> = {
|
||||
toilet: {
|
||||
id: 'basic_service_facility',
|
||||
label: '卫生间',
|
||||
@@ -237,6 +246,16 @@ export interface SgsHallPoiDiagnostics {
|
||||
skippedPlaceCount: number
|
||||
}
|
||||
|
||||
const businessTypeLabels: Record<string, string> = {
|
||||
shop: '商店',
|
||||
restaurant: '餐饮',
|
||||
cafe: '咖啡',
|
||||
vending: '售卖机',
|
||||
bookstore: '书店',
|
||||
cultural_shop: '文创商店',
|
||||
photo_spot: '打卡点'
|
||||
}
|
||||
|
||||
interface SgsHallPoiBuildOptions {
|
||||
fallbackY?: number
|
||||
}
|
||||
@@ -381,13 +400,23 @@ const normalizePlacePosition = (
|
||||
fallbackY
|
||||
)
|
||||
|
||||
const categoryFor = (poi: SgsPoiPayload): MuseumCategory & { accessible?: boolean } => {
|
||||
const normalizedType = poi.type?.trim()
|
||||
if (normalizedType && categoryBySgsType[normalizedType]) {
|
||||
return categoryBySgsType[normalizedType]
|
||||
}
|
||||
|
||||
if (poi.typeName) {
|
||||
const categoryFor = (poi: SgsPoiPayload): MuseumCategory & { accessible?: boolean } => {
|
||||
const normalizedType = poi.type?.trim().toLowerCase()
|
||||
const normalizedBusinessType = poi.businessType?.trim().toLowerCase()
|
||||
|
||||
if (String(poi.poiGroup || '').toUpperCase() === 'BUSINESS') {
|
||||
return {
|
||||
...businessCategory,
|
||||
label: normalizedBusinessType ? businessTypeLabels[normalizedBusinessType] || poi.typeName || businessCategory.label : poi.typeName || businessCategory.label,
|
||||
iconType: normalizedBusinessType || businessCategory.iconType
|
||||
}
|
||||
}
|
||||
|
||||
if (normalizedType && categoryBySgsType[normalizedType]) {
|
||||
return categoryBySgsType[normalizedType]
|
||||
}
|
||||
|
||||
if (poi.typeName) {
|
||||
return {
|
||||
...defaultCategory,
|
||||
label: poi.typeName,
|
||||
@@ -395,8 +424,15 @@ const categoryFor = (poi: SgsPoiPayload): MuseumCategory & { accessible?: boolea
|
||||
}
|
||||
}
|
||||
|
||||
return defaultCategory
|
||||
}
|
||||
return defaultCategory
|
||||
}
|
||||
|
||||
const kindForSgsPoi = (poi: SgsPoiPayload, category: MuseumCategory) => {
|
||||
if (category.id === 'business_poi') return 'facility'
|
||||
if (category.id === 'operation_experience') return 'guide'
|
||||
|
||||
return 'facility'
|
||||
}
|
||||
|
||||
export const toMuseumPoiFromSgs = (
|
||||
poi: SgsPoiPayload,
|
||||
@@ -425,12 +461,41 @@ export const toMuseumPoiFromSgs = (
|
||||
],
|
||||
positionGltf: normalizePosition(poi),
|
||||
sourceObjectName: poi.anchorNodeName || undefined,
|
||||
sourceConfidence: 'backend-sgs-sdk',
|
||||
navigationReadiness: '位置预览',
|
||||
accessible: category.accessible === true,
|
||||
kind: category.id === 'operation_experience' ? 'guide' : 'facility'
|
||||
}
|
||||
}
|
||||
sourceConfidence: 'backend-sgs-sdk',
|
||||
navigationReadiness: '位置预览',
|
||||
accessible: category.accessible === true,
|
||||
kind: kindForSgsPoi(poi, category)
|
||||
}
|
||||
}
|
||||
|
||||
const normalizeAudioTargetType = (targetType?: string | null): AudioPlayTargetType | undefined => {
|
||||
const normalized = targetType?.toUpperCase()
|
||||
if (normalized === 'ITEM' || normalized === 'STOP') return normalized
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
export const toExplainGuideStopFromSgs = (stop: SgsGuideStopPayload): ExplainGuideStop | null => {
|
||||
const id = stringifyId(stop.id)
|
||||
if (!id) return null
|
||||
|
||||
return {
|
||||
id,
|
||||
name: normalizedText(stop.name) || `讲解点${id}`,
|
||||
hallId: stringifyId(stop.hallId) || undefined,
|
||||
hallName: normalizedText(stop.hallName) || undefined,
|
||||
floorId: stringifyId(stop.floorId) || undefined,
|
||||
targetType: normalizeAudioTargetType(stop.targetType) || 'STOP',
|
||||
targetId: stringifyId(stop.targetId) || id,
|
||||
coverImageUrl: normalizedText(stop.coverImageUrl) || undefined,
|
||||
description: normalizedText(stop.description) || undefined,
|
||||
hasAudio: stop.hasAudio === true || Boolean(normalizedText(stop.audioUrl)),
|
||||
poiId: stringifyId(stop.poiId) || undefined,
|
||||
outlineId: stringifyId(stop.outlineId || stop.routeId) || undefined,
|
||||
outlineName: normalizedText(stop.outlineName || stop.routeName) || undefined,
|
||||
sort: optionalNumber(stop.sort ?? stop.seqOrder)
|
||||
}
|
||||
}
|
||||
|
||||
export const toLocationPreviewFromPoi = (poi: MuseumPoi): GuideLocationPreview => ({
|
||||
poiId: poi.id,
|
||||
|
||||
@@ -10,17 +10,22 @@ import {
|
||||
import type {
|
||||
ExplainContentProvider
|
||||
} from '@/data/providers/staticMuseumContentProvider'
|
||||
import {
|
||||
defaultSgsSdkApiProvider,
|
||||
type SgsSdkApiProvider
|
||||
} from '@/data/providers/sgsSdkApiProvider'
|
||||
import {
|
||||
toBackendExplainTrack,
|
||||
toBackendGuideStop,
|
||||
toBackendHallFromList,
|
||||
toBackendMediaAsset,
|
||||
toBackendMuseumExhibit,
|
||||
groupGuideStopsByOutline,
|
||||
type BackendExhibit,
|
||||
type BackendGuideStop,
|
||||
type BackendHall
|
||||
} from '@/data/adapters/backendExplainDataAdapter'
|
||||
import {
|
||||
toExplainGuideStopFromSgs
|
||||
} from '@/data/adapters/sgsSdkGuideAdapter'
|
||||
|
||||
interface CommonResult<T> {
|
||||
code: number
|
||||
@@ -28,7 +33,14 @@ interface CommonResult<T> {
|
||||
data?: T
|
||||
}
|
||||
|
||||
const normalizeBaseUrl = (baseUrl: string) => baseUrl.replace(/\/+$/, '')
|
||||
const normalizeBaseUrl = (baseUrl: string) => {
|
||||
const trimmed = baseUrl.trim().replace(/\/+$/, '')
|
||||
if (!trimmed || /^https?:\/\//i.test(trimmed) || trimmed.startsWith('/')) {
|
||||
return trimmed
|
||||
}
|
||||
|
||||
return `/${trimmed}`
|
||||
}
|
||||
|
||||
const resolveAppApiBaseUrl = () => {
|
||||
const baseUrl = normalizeBaseUrl(dataSourceConfig.apiBaseUrl)
|
||||
@@ -77,7 +89,10 @@ export class BackendExplainContentProvider implements ExplainContentProvider {
|
||||
private readonly guideStopCache = new Map<string, ExplainGuideStop[]>()
|
||||
private readonly guideStopInflight = new Map<string, Promise<ExplainGuideStop[]>>()
|
||||
|
||||
constructor(private readonly fallbackProvider: ExplainContentProvider) {}
|
||||
constructor(
|
||||
private readonly fallbackProvider: ExplainContentProvider,
|
||||
private readonly sgsSdkApiProvider: SgsSdkApiProvider = defaultSgsSdkApiProvider
|
||||
) {}
|
||||
|
||||
private async requestStaticExplainExhibits() {
|
||||
const cached = this.searchCache.get('')
|
||||
@@ -223,14 +238,8 @@ export class BackendExplainContentProvider implements ExplainContentProvider {
|
||||
if (inflight) return inflight
|
||||
|
||||
const promise = (async () => {
|
||||
const url = `${resolveAppApiBaseUrl()}/gis/sdk/halls/${encodeURIComponent(normalizedHallId)}/guide-stops`
|
||||
const response = await requestJson<CommonResult<BackendGuideStop[]>>(url)
|
||||
if (response.code !== 0 || !Array.isArray(response.data)) {
|
||||
throw new Error(response.msg || '后端展厅讲解点加载失败')
|
||||
}
|
||||
|
||||
const stops = response.data
|
||||
.map(toBackendGuideStop)
|
||||
const stops = (await this.sgsSdkApiProvider.getGuideStopsByHall(normalizedHallId))
|
||||
.map(toExplainGuideStopFromSgs)
|
||||
.filter(Boolean) as ExplainGuideStop[]
|
||||
|
||||
this.guideStopCache.set(normalizedHallId, stops)
|
||||
|
||||
@@ -26,6 +26,27 @@ export interface SgsSdkManifestPayload {
|
||||
capabilities?: Record<string, boolean | undefined>
|
||||
}
|
||||
|
||||
export type SgsPoiGroupPayload = 'SERVICE' | 'BUSINESS' | 'OTHER'
|
||||
|
||||
export type SgsBusinessPoiTypePayload =
|
||||
| 'shop'
|
||||
| 'restaurant'
|
||||
| 'cafe'
|
||||
| 'vending'
|
||||
| 'bookstore'
|
||||
| 'cultural_shop'
|
||||
| 'photo_spot'
|
||||
| string
|
||||
|
||||
export interface SgsPoiQueryParamsPayload {
|
||||
mapId?: string | number
|
||||
floorId?: string | number
|
||||
group?: SgsPoiGroupPayload
|
||||
types?: string[]
|
||||
keyword?: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface SgsPositionPayload {
|
||||
x?: number | null
|
||||
y?: number | null
|
||||
@@ -47,6 +68,14 @@ export interface SgsPoiPayload {
|
||||
anchorNodeName?: string | null
|
||||
description?: string | null
|
||||
iconUrl?: string | null
|
||||
poiGroup?: SgsPoiGroupPayload | string | null
|
||||
businessType?: SgsBusinessPoiTypePayload | null
|
||||
coverImageUrl?: string | null
|
||||
phone?: string | null
|
||||
businessHours?: string | null
|
||||
sortOrder?: number | null
|
||||
spatialAreaId?: string | number | null
|
||||
spatialAreaName?: string | null
|
||||
}
|
||||
|
||||
export interface SgsSpacePayload {
|
||||
@@ -79,6 +108,36 @@ export interface SgsNavigablePlacePayload {
|
||||
ownerName?: string | null
|
||||
}
|
||||
|
||||
export interface SgsGuideStopPayload {
|
||||
id: string | number
|
||||
name?: string | null
|
||||
type?: string | null
|
||||
typeName?: string | null
|
||||
floorId?: string | number | null
|
||||
position?: SgsPositionPayload | null
|
||||
x?: number | null
|
||||
y?: number | null
|
||||
z?: number | null
|
||||
targetType?: string | null
|
||||
targetId?: string | number | null
|
||||
audioUrl?: string | null
|
||||
coverImageUrl?: string | null
|
||||
description?: string | null
|
||||
status?: string | null
|
||||
located?: boolean | null
|
||||
hasAudio?: boolean | null
|
||||
poiId?: string | number | null
|
||||
outlineId?: string | number | null
|
||||
outlineName?: string | null
|
||||
hallId?: string | number | null
|
||||
hallName?: string | null
|
||||
sort?: number | null
|
||||
routeId?: string | number | null
|
||||
routeName?: string | null
|
||||
seqOrder?: number | null
|
||||
stayMinutes?: number | null
|
||||
}
|
||||
|
||||
export interface SgsFloorDiagnosticsPayload {
|
||||
floorId: string | number
|
||||
floorCode?: string | null
|
||||
@@ -121,7 +180,7 @@ export interface SgsFloorBundlePayload {
|
||||
model?: SgsModelInfoPayload | null
|
||||
pois?: SgsPoiPayload[]
|
||||
spaces?: SgsSpacePayload[]
|
||||
guideStops?: unknown[]
|
||||
guideStops?: SgsGuideStopPayload[]
|
||||
routeSummary?: {
|
||||
hasRouteNetwork?: boolean
|
||||
nodeCount?: number
|
||||
@@ -196,6 +255,54 @@ export interface SgsRoutePlanResponsePayload {
|
||||
segments?: SgsRouteSegmentPayload[]
|
||||
}
|
||||
|
||||
export interface SgsRouteEndpointPayload {
|
||||
id?: string | number | null
|
||||
name?: string | null
|
||||
floorId?: string | number | null
|
||||
floorCode?: string | null
|
||||
floorName?: string | null
|
||||
x?: number | null
|
||||
y?: number | null
|
||||
z?: number | null
|
||||
}
|
||||
|
||||
export interface SgsFeaturedRouteSummaryPayload {
|
||||
id: string | number
|
||||
mapId?: string | number | null
|
||||
routeName?: string | null
|
||||
routeNameEn?: string | null
|
||||
routeType?: string | null
|
||||
theme?: string | null
|
||||
description?: string | null
|
||||
coverImageUrl?: string | null
|
||||
estimatedDuration?: number | null
|
||||
distanceMeters?: number | null
|
||||
waypointCount?: number | null
|
||||
sortOrder?: number | null
|
||||
status?: string | number | null
|
||||
}
|
||||
|
||||
export interface SgsFeaturedRouteWaypointPayload {
|
||||
order?: number | null
|
||||
spaceId?: string | number | null
|
||||
spaceName?: string | null
|
||||
guideStopId?: string | number | null
|
||||
guideStopName?: string | null
|
||||
floorId?: string | number | null
|
||||
floorCode?: string | null
|
||||
floorName?: string | null
|
||||
x?: number | null
|
||||
y?: number | null
|
||||
z?: number | null
|
||||
stayMinutes?: number | null
|
||||
}
|
||||
|
||||
export interface SgsFeaturedRouteDetailPayload extends SgsFeaturedRouteSummaryPayload {
|
||||
startEntrance?: SgsRouteEndpointPayload | null
|
||||
endEntrance?: SgsRouteEndpointPayload | null
|
||||
waypoints?: SgsFeaturedRouteWaypointPayload[]
|
||||
}
|
||||
|
||||
interface CommonResult<T> {
|
||||
code: number
|
||||
msg?: string
|
||||
@@ -209,7 +316,19 @@ export interface SgsSdkApiProvider {
|
||||
getFloorBundle(floorId: string): Promise<SgsFloorBundlePayload>
|
||||
getFloorPois(floorId: string): Promise<SgsPoiPayload[]>
|
||||
getFloorSpaces(floorId: string): Promise<SgsSpacePayload[]>
|
||||
getGuideStops(floorId: string): Promise<SgsGuideStopPayload[]>
|
||||
getGuideStopsByHall(hallId: string): Promise<SgsGuideStopPayload[]>
|
||||
getNavigablePlaces(floorId: string): Promise<SgsNavigablePlacePayload[]>
|
||||
getFloorBusinessPois(
|
||||
floorId: string,
|
||||
options?: { types?: string[]; keyword?: string; limit?: number }
|
||||
): Promise<SgsPoiPayload[]>
|
||||
queryPois(params: SgsPoiQueryParamsPayload): Promise<SgsPoiPayload[]>
|
||||
getFeaturedRoutes(
|
||||
mapId?: string,
|
||||
options?: { limit?: number }
|
||||
): Promise<SgsFeaturedRouteSummaryPayload[]>
|
||||
getFeaturedRoute(routeId: string): Promise<SgsFeaturedRouteDetailPayload>
|
||||
planRoute(payload: SgsRoutePlanRequestPayload): Promise<SgsRoutePlanResponsePayload>
|
||||
}
|
||||
|
||||
@@ -295,6 +414,21 @@ const requestJson = <T>(
|
||||
})
|
||||
})
|
||||
|
||||
const buildQueryString = (params: object) => {
|
||||
const query = new URLSearchParams()
|
||||
|
||||
Object.entries(params as Record<string, unknown>).forEach(([key, value]) => {
|
||||
if (value === null || typeof value === 'undefined' || value === '') return
|
||||
|
||||
query.set(key, Array.isArray(value) ? value.join(',') : String(value))
|
||||
})
|
||||
|
||||
const queryString = query.toString()
|
||||
return queryString ? `?${queryString}` : ''
|
||||
}
|
||||
|
||||
const stableCacheKey = (value: unknown) => JSON.stringify(value)
|
||||
|
||||
export const createSgsSdkApiProvider = (): SgsSdkApiProvider => {
|
||||
const manifestCache = new Map<string, SgsSdkManifestPayload>()
|
||||
const mapDiagnosticsCache = new Map<string, SgsMapDiagnosticsPayload>()
|
||||
@@ -302,10 +436,17 @@ export const createSgsSdkApiProvider = (): SgsSdkApiProvider => {
|
||||
const floorBundleCache = new Map<string, SgsFloorBundlePayload>()
|
||||
const floorPoiCache = new Map<string, SgsPoiPayload[]>()
|
||||
const floorSpaceCache = new Map<string, SgsSpacePayload[]>()
|
||||
const floorGuideStopCache = new Map<string, SgsGuideStopPayload[]>()
|
||||
const hallGuideStopCache = new Map<string, SgsGuideStopPayload[]>()
|
||||
const navigablePlaceCache = new Map<string, SgsNavigablePlacePayload[]>()
|
||||
const floorBusinessPoiCache = new Map<string, SgsPoiPayload[]>()
|
||||
const poiQueryCache = new Map<string, SgsPoiPayload[]>()
|
||||
const featuredRoutesCache = new Map<string, SgsFeaturedRouteSummaryPayload[]>()
|
||||
const featuredRouteDetailCache = new Map<string, SgsFeaturedRouteDetailPayload>()
|
||||
|
||||
const mapIdFor = (mapId?: string) => mapId || dataSourceConfig.sgsMapId
|
||||
const floorIdFor = (floorId: string) => encodeURIComponent(floorId)
|
||||
const hallIdFor = (hallId: string) => encodeURIComponent(hallId)
|
||||
|
||||
return {
|
||||
async getManifest(mapId) {
|
||||
@@ -370,6 +511,26 @@ export const createSgsSdkApiProvider = (): SgsSdkApiProvider => {
|
||||
floorSpaceCache.set(floorId, spaces)
|
||||
return spaces
|
||||
},
|
||||
async getGuideStops(floorId) {
|
||||
const cached = floorGuideStopCache.get(floorId)
|
||||
if (cached) return cached
|
||||
|
||||
const guideStops = await requestJson<SgsGuideStopPayload[]>(
|
||||
`/gis/sdk/floors/${floorIdFor(floorId)}/guide-stops`
|
||||
)
|
||||
floorGuideStopCache.set(floorId, guideStops)
|
||||
return guideStops
|
||||
},
|
||||
async getGuideStopsByHall(hallId) {
|
||||
const cached = hallGuideStopCache.get(hallId)
|
||||
if (cached) return cached
|
||||
|
||||
const guideStops = await requestJson<SgsGuideStopPayload[]>(
|
||||
`/gis/sdk/halls/${hallIdFor(hallId)}/guide-stops`
|
||||
)
|
||||
hallGuideStopCache.set(hallId, guideStops)
|
||||
return guideStops
|
||||
},
|
||||
async getNavigablePlaces(floorId) {
|
||||
const cached = navigablePlaceCache.get(floorId)
|
||||
if (cached) return cached
|
||||
@@ -380,6 +541,50 @@ export const createSgsSdkApiProvider = (): SgsSdkApiProvider => {
|
||||
navigablePlaceCache.set(floorId, places)
|
||||
return places
|
||||
},
|
||||
async getFloorBusinessPois(floorId, options = {}) {
|
||||
const cacheKey = `${floorId}:${stableCacheKey(options)}`
|
||||
const cached = floorBusinessPoiCache.get(cacheKey)
|
||||
if (cached) return cached
|
||||
|
||||
const pois = await requestJson<SgsPoiPayload[]>(
|
||||
`/gis/sdk/floors/${floorIdFor(floorId)}/business-pois${buildQueryString(options)}`
|
||||
)
|
||||
floorBusinessPoiCache.set(cacheKey, pois)
|
||||
return pois
|
||||
},
|
||||
async queryPois(params) {
|
||||
const cacheKey = stableCacheKey(params)
|
||||
const cached = poiQueryCache.get(cacheKey)
|
||||
if (cached) return cached
|
||||
|
||||
const pois = await requestJson<SgsPoiPayload[]>(
|
||||
`/gis/sdk/pois${buildQueryString(params)}`
|
||||
)
|
||||
poiQueryCache.set(cacheKey, pois)
|
||||
return pois
|
||||
},
|
||||
async getFeaturedRoutes(mapId, options = {}) {
|
||||
const normalizedMapId = mapIdFor(mapId)
|
||||
const cacheKey = `${normalizedMapId}:${stableCacheKey(options)}`
|
||||
const cached = featuredRoutesCache.get(cacheKey)
|
||||
if (cached) return cached
|
||||
|
||||
const routes = await requestJson<SgsFeaturedRouteSummaryPayload[]>(
|
||||
`/gis/sdk/maps/${encodeURIComponent(normalizedMapId)}/featured-routes${buildQueryString(options)}`
|
||||
)
|
||||
featuredRoutesCache.set(cacheKey, routes)
|
||||
return routes
|
||||
},
|
||||
async getFeaturedRoute(routeId) {
|
||||
const cached = featuredRouteDetailCache.get(routeId)
|
||||
if (cached) return cached
|
||||
|
||||
const route = await requestJson<SgsFeaturedRouteDetailPayload>(
|
||||
`/gis/sdk/featured-routes/${encodeURIComponent(routeId)}`
|
||||
)
|
||||
featuredRouteDetailCache.set(routeId, route)
|
||||
return route
|
||||
},
|
||||
async planRoute(payload) {
|
||||
return requestJson<SgsRoutePlanResponsePayload>(
|
||||
'/gis/sdk/routes/plan',
|
||||
|
||||
Reference in New Issue
Block a user