From e473b6a2a592587039aea16cbd2d9081dea099e3 Mon Sep 17 00:00:00 2001 From: lyf <2514544224@qq.com> Date: Mon, 13 Jul 2026 11:00:04 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8D=87=E7=BA=A7=20SGS=20=E5=9C=B0=E5=9B=BE?= =?UTF-8?q?=20SDK=20=E8=87=B3=202.4.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.development | 2 +- .env.example | 2 +- .env.production | 2 +- .env.test | 2 +- README.md | 2 +- src/config/dataSource.ts | 2 +- src/services/sgs/SgsMapService.ts | 11 +- src/types/sgs-map-sdk.ts | 95 +- static/sgs-map-sdk/CHANGELOG.md | 24 + static/sgs-map-sdk/index.d.mts | 1200 ++++++++++++++++++++++++ static/sgs-map-sdk/index.d.ts | 239 ++++- static/sgs-map-sdk/index.global.js | 2 +- static/sgs-map-sdk/index.global.js.map | 2 +- static/sgs-map-sdk/index.mjs | 2 + static/sgs-map-sdk/index.mjs.map | 1 + static/sgs-map-sdk/package.json | 5 +- static/sgs-map-sdk/sdk-quickstart.md | 9 +- tests/unit/SgsMapService.spec.ts | 78 ++ 18 files changed, 1655 insertions(+), 25 deletions(-) create mode 100644 static/sgs-map-sdk/index.d.mts create mode 100644 static/sgs-map-sdk/index.mjs create mode 100644 static/sgs-map-sdk/index.mjs.map create mode 100644 tests/unit/SgsMapService.spec.ts diff --git a/.env.development b/.env.development index 09def4b..93d0f37 100644 --- a/.env.development +++ b/.env.development @@ -15,7 +15,7 @@ VITE_AUDIO_API_BASE_URL=/app-api VITE_AUDIO_LANGUAGE=zh-CN VITE_SGS_API_BASE_URL=/app-api VITE_SGS_MAP_ID=1 -VITE_SGS_SDK_SCRIPT_URL=/static/sgs-map-sdk/index.global.js +VITE_SGS_SDK_SCRIPT_URL=/static/sgs-map-sdk/index.global.js?v=2.4.1 VITE_SGS_H5_ENGINE_URL=/h5-sdk VITE_SGS_SDK_ORIGIN= VITE_SGS_SDK_TIMEOUT_MS=15000 diff --git a/.env.example b/.env.example index 99265df..9313fae 100644 --- a/.env.example +++ b/.env.example @@ -14,7 +14,7 @@ VITE_AUDIO_API_BASE_URL=/app-api VITE_AUDIO_LANGUAGE=zh-CN VITE_SGS_API_BASE_URL=/app-api VITE_SGS_MAP_ID=1 -VITE_SGS_SDK_SCRIPT_URL=/static/sgs-map-sdk/index.global.js +VITE_SGS_SDK_SCRIPT_URL=/static/sgs-map-sdk/index.global.js?v=2.4.1 VITE_SGS_H5_ENGINE_URL=/h5-sdk VITE_SGS_SDK_ORIGIN= VITE_SGS_SDK_TIMEOUT_MS=15000 diff --git a/.env.production b/.env.production index dfc08bc..bba8945 100644 --- a/.env.production +++ b/.env.production @@ -14,7 +14,7 @@ VITE_AUDIO_API_BASE_URL=/app-api VITE_AUDIO_LANGUAGE=zh-CN VITE_SGS_API_BASE_URL=/app-api VITE_SGS_MAP_ID=1 -VITE_SGS_SDK_SCRIPT_URL=/static/sgs-map-sdk/index.global.js +VITE_SGS_SDK_SCRIPT_URL=/static/sgs-map-sdk/index.global.js?v=2.4.1 VITE_SGS_H5_ENGINE_URL=/h5-sdk VITE_SGS_SDK_ORIGIN=https://guide.sznhmuseum.org.cn VITE_SGS_SDK_TIMEOUT_MS=15000 diff --git a/.env.test b/.env.test index 247ad75..1f2ccf1 100644 --- a/.env.test +++ b/.env.test @@ -14,7 +14,7 @@ VITE_AUDIO_API_BASE_URL=/app-api VITE_AUDIO_LANGUAGE=zh-CN VITE_SGS_API_BASE_URL=/app-api VITE_SGS_MAP_ID=1 -VITE_SGS_SDK_SCRIPT_URL=/static/sgs-map-sdk/index.global.js +VITE_SGS_SDK_SCRIPT_URL=/static/sgs-map-sdk/index.global.js?v=2.4.1 VITE_SGS_H5_ENGINE_URL=/h5-sdk VITE_SGS_SDK_ORIGIN=https://guide.whaoyue.com VITE_SGS_SDK_TIMEOUT_MS=15000 diff --git a/README.md b/README.md index 2988b24..be5f10b 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ VITE_AUDIO_LANGUAGE=zh-CN # SGS SDK/H5 地图基座配置;当前代码尚未把 SDK renderer 接入页面渲染 VITE_SGS_MAP_ID=1 -VITE_SGS_SDK_SCRIPT_URL=/static/sgs-map-sdk/index.global.js +VITE_SGS_SDK_SCRIPT_URL=/static/sgs-map-sdk/index.global.js?v=2.4.1 VITE_SGS_H5_ENGINE_URL=/h5-sdk VITE_SGS_SDK_ORIGIN= VITE_SGS_SDK_TIMEOUT_MS=5000 diff --git a/src/config/dataSource.ts b/src/config/dataSource.ts index 011d081..f1bcc87 100644 --- a/src/config/dataSource.ts +++ b/src/config/dataSource.ts @@ -3,7 +3,7 @@ export type GuideContentDataSourceMode = 'static' | 'remote' | 'mock' const allowedModes = new Set(['static', 'api', 'sdk']) const allowedGuideContentModes = new Set(['static', 'remote', 'mock']) -const defaultSdkScriptUrl = '/static/sgs-map-sdk/index.global.js' +const defaultSdkScriptUrl = '/static/sgs-map-sdk/index.global.js?v=2.4.1' const defaultSgsEngineUrl = '/h5-sdk' const defaultSdkTimeoutMs = 5000 const defaultApiBaseUrl = '/app-api' diff --git a/src/services/sgs/SgsMapService.ts b/src/services/sgs/SgsMapService.ts index 0700fe5..0a61dbd 100644 --- a/src/services/sgs/SgsMapService.ts +++ b/src/services/sgs/SgsMapService.ts @@ -3,6 +3,7 @@ import type { SgsMapSDKInstance, SgsMarkerConfig, SgsMapNode, + SgsRouteOptions, SgsRouteResult } from '@/types/sgs-map-sdk' import { @@ -116,9 +117,9 @@ export class SgsMapService { return this.sdk } - async changeFloor(floorId: string | number) { + async changeFloor(floorId: string | number, options?: { forceReload?: boolean }) { const sdk = await this.getReadySdk() - return sdk.changeFloor(floorId, this.options.timeout) + return sdk.changeFloor(floorId, this.options.timeout, options) } async focusTo(node: SgsMapNode | string) { @@ -138,15 +139,15 @@ export class SgsMapService { async resetView() { const sdk = await this.getReadySdk() - sdk.resetView() + return sdk.resetView(this.options.timeout) } async setViewMode(mode: '2d' | '3d') { const sdk = await this.getReadySdk() - sdk.setViewMode(mode) + return sdk.setViewMode(mode, this.options.timeout) } - async planRoute(from: SgsMapNode | string, to: SgsMapNode | string, options?: unknown): Promise { + async planRoute(from: SgsMapNode | string, to: SgsMapNode | string, options?: SgsRouteOptions): Promise { const sdk = await this.getReadySdk() return sdk.planRoute(from, to, options, this.options.timeout) } diff --git a/src/types/sgs-map-sdk.ts b/src/types/sgs-map-sdk.ts index 0235120..994028a 100644 --- a/src/types/sgs-map-sdk.ts +++ b/src/types/sgs-map-sdk.ts @@ -24,6 +24,20 @@ export interface SgsRouteResult { segments?: SgsRouteSegment[] } +export interface SgsRouteOptions { + mode?: 'walk' | 'accessible' + wheelchair?: boolean + preferElevator?: boolean +} + +export interface SgsSdkRoutePoint { + poiId?: string | number + floorId?: string | number + x?: number + y?: number + z?: number +} + export interface SgsMapSDKOptions { container: string | HTMLElement sdkUrl?: string @@ -37,6 +51,13 @@ export interface SgsMapSDKOptions { export interface SgsMapEvents { ready: { engineVersion: string; protocolVersion: number } poiClick: { id: string | number; poiId?: string | number; name: string; type: string; floorId?: string | number; description?: string } + poiDetail: SgsPoi & Record + poiNavigate: SgsPoi & Record + poiClose: Record + spaceClick: SgsSpaceArea & Record + spaceDetail: SgsSpaceArea & Record + spaceNavigate: SgsSpaceArea & Record + spaceClose: Record floorChanged: { floorId: string | number } error: { code: string; message: string } loadError: { type: string; detail: string } @@ -57,6 +78,8 @@ export interface SgsMapEvents { modelError: { floorId: string | number; error: string; fallbackUsed: boolean } floorBundleReady: { floorId: string | number; dataVersion: string } routeReady: { routeId: string; distance: number; duration: number; segmentCount?: number; hasTransfer?: boolean } + roamingProgress: { progress: number } + roamingDone: { success: boolean } } export type SgsRouteTransferType = 'ELEVATOR' | 'STAIRS' | 'ESCALATOR' @@ -95,14 +118,20 @@ export interface SgsMapSDKInstance { whenReady(): Promise ready(): Promise focusTo(node: SgsMapNode | string): void - changeFloor(floorId: string | number, timeout?: number): Promise<{ success: boolean; floorId: string | number }> + changeFloor(floorId: string | number, timeout?: number, options?: { forceReload?: boolean }): Promise<{ success: boolean; floorId: string | number }> addMarker(config: SgsMarkerConfig): void removeMarker(markerId: string): void clearMarkers(): void highlightPolygon(payload: string | object): void - planRoute(from: SgsMapNode | string, to: SgsMapNode | string, options?: unknown, timeout?: number): Promise - planRouteV2(from: SgsMapNode, to: SgsMapNode, options?: unknown, timeout?: number): Promise + planRoute(from: SgsMapNode | string, to: SgsMapNode | string, options?: SgsRouteOptions, timeout?: number): Promise + planRouteV2(from: SgsMapNode, to: SgsMapNode, options?: SgsRouteOptions, timeout?: number): Promise clearRoute(): void + renderRoutePoints(pathPoints: Array<{ x: number; y: number; z: number }>, options?: unknown, timeout?: number): Promise + startRoaming(pathPoints?: Array<{ x: number; y: number; z: number }>, options?: SgsRouteRoamingOptions, timeout?: number): Promise<{ success: boolean; mode?: string }> + stopRoaming(timeout?: number): Promise<{ success: boolean }> + toggleRoamingPause(timeout?: number): Promise<{ success: boolean; paused?: boolean }> + planRouteToPoi(from: string | number | SgsSdkRoutePoint, toPoiId: string | number, options?: SgsRouteOptions, timeout?: number): Promise + planRouteBetweenPois(fromPoiId: string | number, toPoiId: string | number, options?: SgsRouteOptions, timeout?: number): Promise getState(): Promise resetView(timeout?: number): Promise<{ success: boolean }> setViewMode(mode: '2d' | '3d', timeout?: number): Promise<{ success: boolean; mode: string }> @@ -126,6 +155,7 @@ export interface SgsMapSDKInstance { queryPois(params: SgsPoiQueryParams, timeout?: number): Promise getFeaturedRoutes(mapId?: string | number, options?: { limit?: number }, timeout?: number): Promise getFeaturedRoute(routeId: string | number, timeout?: number): Promise + planFeaturedRoutePreview(payload: SgsFeaturedRoutePreviewPayload, timeout?: number): Promise showFlowline(routeId: string | number, options?: SgsFlowlineOptions, timeout?: number): Promise<{ success: boolean; routeId: string | number }> clearFlowline(): void destroy(): void @@ -162,10 +192,15 @@ export interface SgsPoi { status: 'ACTIVE' | 'INACTIVE' anchorNodeName?: string | null description?: string + descriptionEn?: string + extParams?: string | Record iconUrl?: string poiGroup?: SgsPoiGroup businessType?: SgsBusinessPoiType coverImageUrl?: string | null + images?: string | string[] | null + externalUrl?: string | null + videoUrl?: string | null phone?: string | null businessHours?: string | null sortOrder?: number | null @@ -182,7 +217,22 @@ export interface SgsSdkManifest { updatedAt: string coordinateSystem: string floors: SgsSdkFloorSummary[] - capabilities: Record + routeAssets?: SgsRouteAsset[] + capabilities: Record & { sameGroundCompositeRoute?: boolean } +} + +export interface SgsRouteAsset { + id: string | number + mapId?: string | number + floorId?: string | number | null + floorCode?: string | null + assetRole: string + modelUrl: string + sourceFileName?: string | null + sourceNodeName?: string | null + modelVersion?: string | null + coverageFloorCodes?: string[] + sortOrder?: number } export interface SgsSdkFloorSummary { @@ -222,13 +272,26 @@ export interface SgsFloorBundle { export interface SgsSpaceArea { id: string | number name: string + displayName?: string type: string + typeName?: string floorId: string | number + floorCode?: string boundaryWkt: string - center: { x: number; y: number; z: number } + center: { x: number; y?: number; z: number; radius?: number } sourceNodeName?: string status: 'ACTIVE' | 'INACTIVE' colorHex?: string + description?: string | null + descriptionEn?: string | null + coverImageUrl?: string | null + images?: string | string[] | null + galleryUrls?: string | string[] | null + videoUrl?: string | null + externalUrl?: string | null + openTime?: string | null + exhibitCount?: number | null + areaSqm?: number | null } export interface SgsGuideStop { @@ -268,6 +331,11 @@ export interface SgsNavigablePlace { x: number z: number nodeId?: string | number + routeNodeId?: string | number + sourceType?: 'POI' | 'SPACE' | 'FLOOR' | 'CUSTOM' | string | null + sourceId?: string | number | null + anchorId?: string | number | null + anchorType?: string | null ownerName?: string typeCode?: string typeName?: string @@ -305,6 +373,8 @@ export interface SgsPerformanceStats { routePlanMs?: number dracoUsed?: boolean fallbackUsed?: boolean + bundleCacheHit?: boolean + currentModelUrl?: string currentFloorId?: string | number } @@ -394,6 +464,21 @@ export interface SgsFeaturedRouteDetail extends SgsFeaturedRouteSummary { waypoints: SgsFeaturedRouteWaypoint[] } +export interface SgsFeaturedRoutePreviewPayload { + routeId?: string | number + routeName?: string + color?: string | null + routeColor?: string | null + startEntrance?: SgsRouteEndpoint | null + endEntrance?: SgsRouteEndpoint | null + waypoints?: SgsFeaturedRouteWaypoint[] +} + +export interface SgsRouteRoamingOptions { + mode?: 'third-person' | 'top' + speed?: number +} + export interface SgsFlowlineOptions { autoFit?: boolean showLabels?: boolean diff --git a/static/sgs-map-sdk/CHANGELOG.md b/static/sgs-map-sdk/CHANGELOG.md index 8fd6928..51db54f 100644 --- a/static/sgs-map-sdk/CHANGELOG.md +++ b/static/sgs-map-sdk/CHANGELOG.md @@ -1,5 +1,29 @@ # SGS Map SDK Changelog +## [2.4.1] - 2026-07-01 + +### Phase 2: 高层导航API (2026-07-01) + +#### 新增功能 +- **POI间导航API**:新增 `planRouteToPoi` 和 `planRouteBetweenPois` 方法,支持直接通过POI ID完成导航 +- **后端能力扩展**:新增 `/routes/plan-to-poi` 和 `/routes/plan-between-pois` 接口,自动查询POI坐标 +- **Manifest能力声明**:新增 `routeToPoi`、`routeDiagnostics`、`sdkRuntimeCache` 能力字段 +- **错误码标准化**:新增路线规划专用错误码(1009012000段),提供友好的错误提示 + +### Phase 1: 导航稳定性修复 + +#### Bug Fixes +- P0-1: Fixed same-floor route not rendering in Handoff mode (currentFloorPointsArray was dead code) +- P0-2: Fixed CLEAR_ROUTE incomplete state cleanup (10 states now fully reset) +- Fixed route overlay when planning consecutive routes + +#### Improvements +- Added ON_ROUTE_READY event dispatch after successful PLAN_ROUTE +- Added preferElevator option to RouteOptions +- SDK version bumped to 2.4.1 + +--- + ## [2.4.0] - 2026-06-27 ### 🚀 新特性 (New Features) diff --git a/static/sgs-map-sdk/index.d.mts b/static/sgs-map-sdk/index.d.mts new file mode 100644 index 0000000..a4e748f --- /dev/null +++ b/static/sgs-map-sdk/index.d.mts @@ -0,0 +1,1200 @@ +/** + * 轻量级类型安全事件发射器 + * + *

SDK 内部事件系统基类,提供 on/off/once/emit/removeAllListeners/listenerCount。 + */ +declare class EventEmitter> { + private events; + constructor(); + /** 注册事件监听器 */ + on(event: K, listener: (payload: Events[K]) => void): void; + /** 移除指定事件的指定监听器 */ + off(event: K, listener: (payload: Events[K]) => void): void; + /** 注册一次性监听器,触发后自动移除 */ + once(event: K, listener: (payload: Events[K]) => void): void; + /** 触发事件,通知所有已注册的监听器 */ + emit(event: K, data: Events[K]): void; + /** 移除指定事件的全部监听器;不传参则移除所有事件的所有监听器 */ + removeAllListeners(event?: K): void; + /** 返回指定事件的监听器数量 */ + listenerCount(event: K): number; +} + +/** 统一位置描述符 */ +type SgsMapNode = { + type: 'coord'; + floorId: string | number; + x: number; + z: number; + y?: number; +} | { + type: 'poiId'; + value: string | number; + floorId?: string | number; +} | { + type: 'nodeName'; + value: string; + floorId?: string | number; +}; +/** 单点吸附详情 */ +interface SnapDetail { + original: { + x: number; + z: number; + }; + nearest: { + x: number; + z: number; + }; + distance: number; +} +/** 寻路结果 */ +type SgsRouteTransferType = 'ELEVATOR' | 'STAIRS' | 'ESCALATOR'; +type SgsRouteSegmentType = 'WALK' | 'TRANSFER'; +interface RouteResult { + success: boolean; + distance: number; + duration: number; + pathPoints: Array<{ + x: number; + y: number; + z: number; + }>; + startSnap?: SnapDetail; + endSnap?: SnapDetail; + computeTimeMs?: number; + polyCount?: number; + warnings: string[]; + errorCode?: string; + message?: string; + segments?: SgsRouteSegment[]; +} +/** 寻路选项 */ +interface RouteOptions { + mode?: 'walk' | 'accessible'; + wheelchair?: boolean; + /** v2.4.1: 跨楼层时优先选择电梯 */ + preferElevator?: boolean; +} +/** SDK 2.4.1: 路线起点/终点定义 */ +interface SdkRoutePoint { + /** POI ID(POI模式) */ + poiId?: string | number; + /** 楼层 ID(坐标模式) */ + floorId?: string | number; + /** X坐标(GLB米,坐标模式) */ + x?: number; + /** Y坐标(GLB米,坐标模式) */ + y?: number; + /** Z坐标(GLB米,可选) */ + z?: number; +} +/** SDK 初始化配置 */ +interface SGSMapSDKOptions { + container: string | HTMLElement; + sdkUrl?: string; + targetOrigin?: string; + /** 初始楼层 ID(支持雪花 ID 字符串或数字) */ + floorId?: string | number; + /** mapId,默认 "1" */ + mapId?: string | number; + /** 通用命令超时(毫秒),默认 5000 */ + timeout?: number; + /** ready 超时(毫秒),默认 15000。超过此时间 ENGINE_READY 仍未到达则 reject */ + readyTimeout?: number; +} +/** SDK 事件映射 */ +interface SGSMapEvents { + ready: { + engineVersion: string; + protocolVersion: number; + }; + poiClick: { + id: string | number; + name: string; + type: string; + floorId?: string | number; + description?: string; + }; + poiDetail: SgsPoi & Record; + poiNavigate: SgsPoi & Record; + poiClose: Record; + spaceClick: SgsSpaceArea & Record; + spaceDetail: SgsSpaceArea & Record; + spaceNavigate: SgsSpaceArea & Record; + spaceClose: Record; + floorChanged: { + floorId: string | number; + }; + error: { + code: string; + message: string; + }; + loadError: { + type: string; + detail: string; + }; + routeTransfer: { + currentFloorId: string | number; + targetFloorId: string | number; + fromFloorId: string | number; + fromNodeName: string; + toNodeName: string; + transferType: SgsRouteTransferType; + connectorName?: string; + segmentIndex: number; + totalSegments: number; + routeId?: string; + }; + /** 模型开始加载 */ + modelLoading: { + floorId: string | number; + modelUrl: string; + }; + /** 模型解析完成 */ + modelReady: { + floorId: string | number; + parseTimeMs: number; + }; + /** 模型加载失败(含是否使用了 fallback) */ + modelError: { + floorId: string | number; + error: string; + fallbackUsed: boolean; + }; + /** 楼层 Bundle 数据加载完成 */ + floorBundleReady: { + floorId: string | number; + dataVersion: string; + }; + /** 路径规划完成 */ + routeReady: { + routeId: string; + distance: number; + duration: number; + segmentCount?: number; + hasTransfer?: boolean; + }; + /** 路径漫游进度,progress 范围 0-1 */ + roamingProgress: { + progress: number; + }; + /** 路径漫游结束 */ + roamingDone: { + success: boolean; + }; +} +/** SDK 错误码 */ +type SGSErrorCode = 'ERR_TIMEOUT' | 'ERR_NO_PATH' | 'ERR_FLOOR_NOT_FOUND' | 'ERR_NOT_READY' | 'ERR_POI_NOT_FOUND' | 'ERR_NETWORK' | 'ERR_IFRAME_DEAD' | 'ERR_UNAUTHORIZED' | 'ERR_UNSUPPORTED' | 'ERR_FAILED' | 'ERR_DESTROYED' | 'ERR_IFRAME_LOAD' | 'ERR_READY_TIMEOUT'; +/** 图钉配置参数 */ +interface MarkerConfig { + id: string; + name?: string; + iconUrl?: string; + anchorNodeName?: string; + position?: [number, number, number] | { + x: number; + y: number; + z: number; + }; +} +/** 地图发布清单 — 对应 GET /app-api/gis/sdk/maps/{mapId}/manifest */ +interface SgsMapManifest { + /** 地图 ID(数据库主键,number) */ + mapId: number; + /** 地图业务标识码 */ + mapCode: string; + /** 发布 ID,全局唯一 */ + releaseId: string; + /** 语义化版本号 */ + version: string; + /** 发布时间 ISO8601 */ + releasedAt: string; + /** 建筑名称 */ + buildingName: string; + /** 坐标系标识,固定 "GLB_METER" */ + coordinateSystem: string; + /** 建筑外观模型资源 */ + exterior: SgsModelResource; + /** 楼层资源列表(按 sortOrder 升序) */ + floors: SgsFloorResource[]; + /** 楼层总数 */ + totalFloors: number; + /** 所有资源总大小(字节) */ + totalSizeBytes: number; +} +/** 模型资源描述 — 对应 GET /app-api/gis/sdk/maps/{mapId}/resources 中的单个资源 */ +interface SgsModelResource { + /** 楼层代码(如 "L1"/"EXTERIOR") */ + floorCode: string; + /** 楼层名称 */ + floorName?: string; + /** 模型分组(通常与 floorCode 相同) */ + modelGroup: string; + /** 模型文件 URL(相对路径) */ + modelUrl: string; + /** 文件 SHA256 哈希(MVP 阶段为 mock-sha256-{filename}) */ + hash: string; + /** 文件大小(字节) */ + sizeBytes: number; + /** 3D 节点数量 */ + nodeCount?: number; + /** 材质数量 */ + materialCount?: number; + /** 贴图数量 */ + imageCount?: number; + /** 排序序号 */ + sortOrder?: number; +} +/** 楼层资源(继承 SgsModelResource,必填 floorName + sortOrder) */ +interface SgsFloorResource extends SgsModelResource { + floorName: string; + sortOrder: number; +} +/** 图层树节点 — 对应 GET /app-api/gis/sdk/maps/{mapId}/layer-tree */ +interface SgsLayerTreeNode { + /** 节点 ID */ + id: number | string; + /** 节点名称 */ + name: string; + /** 节点类型 */ + nodeType: 'BUILDING' | 'FLOOR' | 'SPACE' | 'ZONE' | 'POI_GROUP'; + /** 楼层代码(FLOOR 节点必填) */ + floorCode?: string; + /** 空间功能分类 */ + spaceCategory?: string; + /** 子节点 */ + children?: SgsLayerTreeNode[]; + /** 是否可见 */ + visible?: boolean; + /** 包含的 3D 节点数量 */ + nodeCount?: number; +} +type SgsPoiGroup = "SERVICE" | "BUSINESS" | "OTHER"; +type SgsBusinessPoiType = "shop" | "restaurant" | "cafe" | "vending" | "bookstore" | "cultural_shop" | "photo_spot"; +interface SgsPoiQueryParams { + mapId?: string | number; + floorId?: string | number; + group?: SgsPoiGroup; + types?: string[]; + keyword?: string; + limit?: number; +} +/** SDK 消费的 POI — 对应 GET /app-api/gis/sdk/floors/{floorId}/pois */ +interface SgsPoi { + /** POI ID(雪花 ID,字符串或数字均可) */ + id: string | number; + /** POI 名称 */ + name: string; + /** POI 类型代码(如 TOILET/ELEVATOR/STAIRS/ENTRANCE) */ + type: string; + /** POI 类型中文名 */ + typeName: string; + /** 所在楼层代码 */ + floorCode: string; + /** GLB 坐标(米),水平面 x/z,高度 y */ + position: { + x: number; + y: number; + z: number; + }; + /** 状态 */ + status: 'ACTIVE' | 'INACTIVE'; + /** 锚定的 GLB 3D 节点名(可选,用于精准定位) */ + anchorNodeName?: string | null; + /** 描述 */ + description?: string; + /** 英文描述 */ + descriptionEn?: string; + /** 扩展参数 JSON */ + extParams?: string | Record; + /** 图标 URL */ + iconUrl?: string; + floorId?: string | number; + poiGroup?: SgsPoiGroup; + businessType?: SgsBusinessPoiType | string; + coverImageUrl?: string | null; + images?: string | string[] | null; + externalUrl?: string | null; + videoUrl?: string | null; + phone?: string | null; + businessHours?: string | null; + sortOrder?: number | null; + spatialAreaId?: string | number | null; + spatialAreaName?: string | null; +} +/** 路径规划结果 — 对应 POST /app-api/gis/sdk/routes/plan */ +interface SgsRoutePlanV2 { + /** 是否成功 */ + success: boolean; + /** 总距离(米) */ + totalDistance: number; + /** 总时长(秒) */ + totalDuration: number; + /** 路线分段列表 */ + segments: SgsRouteSegment[]; + /** 警告信息 */ + warnings: string[]; +} +/** 路线分段 — WALK 段在单楼层内,TRANSFER 段通过 ELEVATOR/STAIRS/ESCALATOR 换层 */ +interface SgsRouteSegment { + /** 分段索引(从 0 开始) */ + segmentIndex: number; + /** 所在楼层 ID(TRANSFER 段可能为空或记录起始楼层) */ + floorId?: string | number | null; + /** 所在楼层代码(换乘段为 "TRANSFER") */ + floorCode: string; + /** 楼层名称 */ + floorName: string; + /** 标准分段类型 */ + segmentType?: SgsRouteSegmentType; + /** 分段类型 */ + type: 'WALK' | SgsRouteTransferType; + /** 换乘类型,WALK 段为 WALK,TRANSFER 段为 ELEVATOR/STAIRS/ESCALATOR */ + transferType?: 'WALK' | SgsRouteTransferType; + /** 距离(米) */ + distance: number; + /** 时长(秒) */ + duration: number; + /** 路径点序列 */ + pathPoints: Array<{ + x: number; + y: number; + z: number; + }>; + /** 路径点序列兼容字段 */ + points?: Array<{ + x: number; + y: number; + z: number; + }>; + /** 起始节点名 */ + fromNodeName?: string | null; + /** 终止节点名 */ + toNodeName?: string | null; + /** 起始节点名兼容字段 */ + startNodeName?: string | null; + /** 终止节点名兼容字段 */ + endNodeName?: string | null; + /** 跨楼层通道名称 */ + connectorName?: string | null; +} +/** v2.2.0 Manifest — GET /app-api/gis/sdk/maps/{mapId}/manifest */ +interface SgsSdkManifest { + /** 地图 ID */ + mapId: string | number; + /** 地图名称 */ + mapName: string; + /** SDK 协议版本 */ + sdkVersion: string; + /** 协议版本号 */ + protocolVersion: number; + /** 数据版本(ISO8601 或递增序号) */ + dataVersion: string; + /** 最后更新时间 ISO8601 */ + updatedAt: string; + /** 坐标系标识,固定 "GLB_METER" */ + coordinateSystem: string; + /** 楼层摘要列表(按 sortOrder 升序) */ + floors: SgsSdkFloorSummary[]; + /** 路线显示资产列表,如 EXTERIOR + L1 同地面组合模型 */ + routeAssets?: SgsRouteAsset[]; + /** 能力声明 */ + capabilities: { + /** 是否支持 Draco 压缩模型 */ + dracoCompression: boolean; + /** 是否支持路径规划 */ + routePlanning: boolean; + /** 是否支持空间面查询 */ + spatialQuery: boolean; + /** 是否支持讲解点 */ + guideStops: boolean; + /** 是否支持地图加载 */ + mapLoading?: boolean; + /** 是否支持楼层切换 */ + floorSwitching?: boolean; + /** 是否支持 POI 查询 */ + poiQuery?: boolean; + /** 是否支持空间查询 */ + spaceQuery?: boolean; + /** 是否支持可导航目的地查询 */ + navigablePlaces?: boolean; + /** 是否支持跨楼层路径规划 */ + crossFloorRoute?: boolean; + /** 是否支持无障碍路径 */ + accessibleRoute?: boolean; + /** 是否支持高亮功能 */ + highlight?: boolean; + /** 是否支持诊断接口 */ + diagnostics?: boolean; + businessPoi?: boolean; + poiSearch?: boolean; + featuredRoutes?: boolean; + flowlinePreview?: boolean; + sameGroundCompositeRoute?: boolean; + }; +} +/** v2.4.1 路线显示资产 */ +interface SgsRouteAsset { + /** 资产 ID */ + id: string | number; + /** 地图 ID */ + mapId?: string | number; + /** 资产所属楼层 ID;全局资产可为空 */ + floorId?: string | number | null; + /** 资产楼层编码或组合编码 */ + floorCode?: string | null; + /** 资产角色,如 SAME_GROUND_COMPOSITE */ + assetRole: string; + /** 模型 URL */ + modelUrl: string; + /** 源文件名 */ + sourceFileName?: string | null; + /** 源节点名 */ + sourceNodeName?: string | null; + /** 模型版本标签 */ + modelVersion?: string | null; + /** 组合资产覆盖的楼层编码,如 ["EXTERIOR", "L1"] */ + coverageFloorCodes?: string[]; + /** 排序 */ + sortOrder?: number; +} +/** v2.2.0 楼层摘要(manifest 内嵌) */ +interface SgsSdkFloorSummary { + /** 楼层 ID */ + floorId: string | number; + /** 楼层代码(如 L1、L-2) */ + floorCode: string; + /** 楼层名称 */ + floorName: string; + /** 排序序号 */ + sortOrder: number; + /** 模型文件大小(字节) */ + modelSizeBytes: number; + /** 该楼层 POI 数量 */ + poiCount: number; + /** 该楼层空间面数量 */ + spaceCount: number; +} +/** v2.2.0 楼层 Bundle — GET /app-api/gis/sdk/floors/{floorId}/bundle */ +interface SgsFloorBundle { + /** 楼层基础信息 */ + floor: { + floorId: string | number; + floorCode: string; + floorName: string; + sortOrder: number; + }; + /** 模型资源信息 */ + model: { + /** Draco 压缩模型 URL(浏览器可直接访问) */ + modelUrl: string; + /** + * 原始 GLB URL(未压缩版),压缩失败时自动回退到此 URL。 + * 保障发布和 H5 SDK 可用性不受 Draco 压缩状态影响。 + */ + fallbackModelUrl: string; + /** 压缩类型:'draco' | 'none' */ + compressionType: 'draco' | 'none'; + /** 模型版本号 */ + modelVersion: string; + /** 3D 节点数量 */ + nodeCount: number; + /** 压缩后文件大小(字节) */ + sizeBytes: number; + /** 原始文件大小(字节) */ + originSizeBytes: number; + }; + /** 该楼层的 POI 列表 */ + pois: SgsPoi[]; + /** 该楼层的空间面列表 */ + spaces: SgsSpaceArea[]; + /** 该楼层的讲解点列表 */ + guideStops: SgsGuideStop[]; + /** 路网概览 */ + routeSummary: { + /** 是否有路网数据 */ + hasRouteNetwork: boolean; + /** 路网节点数 */ + nodeCount: number; + /** 路网边数 */ + edgeCount: number; + }; + /** 需要在前端隐藏的 3D 场景节点名列表 */ + hiddenSceneNodeNames: string[]; + /** 数据版本 */ + dataVersion: string; + /** 最后更新时间 ISO8601 */ + updatedAt: string; +} +/** v2.2.0 空间面 — GET /app-api/gis/sdk/floors/{floorId}/spaces */ +interface SgsSpaceArea { + /** 空间面 ID */ + id: string | number; + /** 显示名称 */ + name: string; + /** 展示名称(兼容 SDK 内部空间标签) */ + displayName?: string; + /** 空间类型(如 exhibition_hall / corridor / office) */ + type: string; + /** 空间类型中文名 */ + typeName?: string; + /** 所属楼层 ID */ + floorId: string | number; + /** 所属楼层编码 */ + floorCode?: string; + /** WKT 边界多边形 */ + boundaryWkt: string; + /** 中心点 GLB 坐标(米) */ + center: { + x: number; + y?: number; + z: number; + radius?: number; + }; + /** 关联的 3D 场景节点名(可选) */ + sourceNodeName?: string; + /** 状态 */ + status: 'ACTIVE' | 'INACTIVE'; + /** 颜色(十六进制,如 #0097A9) */ + colorHex?: string; + /** 简介 */ + description?: string | null; + /** 英文简介 */ + descriptionEn?: string | null; + /** 封面图 */ + coverImageUrl?: string | null; + /** 图集 */ + images?: string | string[] | null; + /** 兼容展厅表图集字段 */ + galleryUrls?: string | string[] | null; + /** 视频 */ + videoUrl?: string | null; + /** 外部详情链接 */ + externalUrl?: string | null; + /** 开放时间 */ + openTime?: string | null; + /** 展品数量 */ + exhibitCount?: number | null; + /** 面积 */ + areaSqm?: number | null; +} +/** v2.2.0 讲解点 — 已标定、可在地图定位的导览讲解点 */ +interface SgsGuideStop { + /** 讲解点 ID */ + id: string | number; + /** 讲解点名称 */ + name: string; + /** 讲解点类型 */ + type: string; + /** 讲解点类型名称 */ + typeName?: string; + /** 所属楼层 ID */ + floorId: string | number; + /** GLB 坐标(米) */ + position: { + x: number; + y: number; + z: number; + }; + /** 目标类型(如 EXHIBIT / SCENE / INTERACTIVE) */ + targetType: string; + /** 关联的目标 ID(如展品 ID) */ + targetId?: string | number; + /** 语音导览音频 URL */ + audioUrl?: string; + /** 封面图片 URL */ + coverImageUrl?: string; + /** 描述 */ + description?: string; + /** 状态 */ + status: 'ACTIVE' | 'INACTIVE'; + /** 是否已完成地图标定 */ + located?: boolean; + /** 是否有可播放音频 */ + hasAudio?: boolean; + /** 关联 POI ID */ + poiId?: string | number; + /** 关联大纲 ID */ + outlineId?: string | number; + /** 关联大纲名称 */ + outlineName?: string; + /** 所属展厅 ID */ + hallId?: string | number; + /** 所属展厅名称 */ + hallName?: string; + /** 讲解点排序 */ + sort?: number; + /** 所属路线 ID */ + routeId?: string | number; + /** 所属路线名称 */ + routeName?: string; + /** 路线中的顺序 */ + seqOrder?: number; + /** 建议停留时长(分钟) */ + stayMinutes?: number; +} +/** v2.2.0 SDK 错误响应 */ +interface SgsSdkError { + /** 错误码(如 ERR_TIMEOUT / ERR_NETWORK) */ + code: string; + /** 人类可读的错误消息 */ + message: string; + /** 额外错误详情(可选) */ + details?: Record; +} +/** v2.2.0 性能统计 */ +interface SgsPerformanceStats { + /** SDK 实例创建时间戳(绝对时间 ms) */ + sdkCreatedAt?: number; + /** iframe DOM 加载完成耗时(从 SDK 创建起算 ms) */ + iframeDomReadyMs?: number; + /** 地图引擎就绪耗时(从 SDK 创建起算 ms) */ + engineReadyMs?: number; + /** Manifest 加载耗时(ms) */ + manifestLoadMs?: number; + /** Bundle 加载耗时(ms) */ + bundleLoadMs?: number; + /** 模型文件下载耗时(ms) */ + modelDownloadMs?: number; + /** 模型文件下载与解析总耗时(ms) */ + modelDownloadAndParseMs?: number; + /** 模型解析耗时(ms) */ + modelParseMs?: number; + /** 首帧渲染耗时(ms) */ + firstRenderMs?: number; + /** 路径规划耗时(ms) */ + routePlanMs?: number; + /** 使用了 Draco 压缩 */ + dracoUsed?: boolean; + /** 是否使用了 fallback 模型 */ + fallbackUsed?: boolean; + /** Bundle 是否命中前端缓存 */ + bundleCacheHit?: boolean; + /** 当前加载的模型 URL */ + currentModelUrl?: string; + /** 当前楼层 ID */ + currentFloorId?: string | number; +} +interface SgsNavigablePlace { + id: string | number; + name: string; + category: "door" | "poi" | "space" | "guide_stop"; + floorId: string | number; + floorCode: string; + floorName?: string; + x: number; + z: number; + nodeId?: string | number; + routeNodeId?: string | number; + sourceType?: "POI" | "SPACE" | "FLOOR" | "CUSTOM" | string | null; + sourceId?: string | number | null; + anchorId?: string | number | null; + anchorType?: string | null; + ownerName?: string; + typeCode?: string; + typeName?: string; +} +/** v2.3.0 地图级诊断响应 — GET /app-api/gis/sdk/maps/{mapId}/diagnostics */ +interface SgsMapDiagnostics { + /** 地图 ID */ + mapId: string | number; + /** 地图名称 */ + mapName: string; + /** SDK 版本号 */ + sdkVersion: string; + /** 整体状态 */ + status: 'OK' | 'WARN' | 'ERROR'; + /** 楼层诊断摘要列表 */ + floors: SgsFloorDiagnosticsSummary[]; + /** 汇总统计 */ + summary: { + /** 楼层总数 */ + floorCount: number; + /** 模型就绪楼层数 */ + modelReadyFloorCount: number; + /** POI 总数 */ + poiCount: number; + /** 空间面总数 */ + spaceCount: number; + /** 讲解点总数 */ + guideStopCount: number; + /** 可导航目的地总数 */ + navigablePlaceCount: number; + /** 路网节点总数 */ + routeNodeCount: number; + /** 路网边总数 */ + routeEdgeCount: number; + }; + /** 警告信息列表 */ + warnings: string[]; +} +/** v2.3.0 楼层诊断摘要(地图级诊断内嵌) */ +interface SgsFloorDiagnosticsSummary { + /** 楼层 ID */ + floorId: string | number; + /** 楼层代码 */ + floorCode: string; + /** 楼层名称 */ + floorName: string; + /** 楼层状态 */ + status: 'OK' | 'WARN' | 'ERROR'; + /** 模型是否就绪 */ + modelReady: boolean; + /** 路径规划是否就绪 */ + routePlanningReady: boolean; + /** 可导航目的地数量 */ + navigablePlaceCount: number; +} +/** v2.3.0 楼层级诊断响应 — GET /app-api/gis/sdk/floors/{floorId}/diagnostics */ +interface SgsFloorDiagnostics { + /** 楼层 ID */ + floorId: string | number; + /** 楼层代码 */ + floorCode: string; + /** 楼层名称 */ + floorName: string; + /** 楼层状态 */ + status: 'OK' | 'WARN' | 'ERROR'; + /** 模型是否就绪 */ + modelReady: boolean; + /** POI 数量 */ + poiCount: number; + /** 空间面数量 */ + spaceCount: number; + /** 讲解点数量 */ + guideStopCount: number; + /** 可导航目的地数量 */ + navigablePlaceCount: number; + /** 路网节点数量 */ + routeNodeCount: number; + /** 路网边数量 */ + routeEdgeCount: number; + /** 路径规划是否就绪 */ + routePlanningReady: boolean; + /** 警告信息列表 */ + warnings: string[]; +} +interface SgsFeaturedRouteSummary { + id: string | number; + mapId: string | number; + routeName: string; + routeNameEn?: string | null; + description?: string | null; + descriptionEn?: string | null; + coverImageUrl?: string | null; + routeColor?: string | null; + estimatedMinutes?: number | null; + displayOrder?: number | null; + waypointCount: number; + startEntranceId?: string | number | null; + endEntranceId?: string | number | null; + updatedAt?: string | null; +} +interface SgsRouteEndpoint { + id: string | number; + name: string; + floorId: string | number; + floorCode?: string | null; + position?: { + x: number; + y: number; + z: number; + } | null; +} +interface SgsFeaturedRouteWaypoint { + order: number; + spaceId: string | number; + spaceName?: string | null; + floorId?: string | number | null; + floorCode?: string | null; + position: { + x: number; + y: number; + z: number; + }; + title?: string | null; + subtitle?: string | null; + description?: string | null; + imageUrl?: string | null; + audioUrl?: string | null; + staySeconds?: number | null; + tags?: string[]; + highlight?: boolean; +} +interface SgsFeaturedRouteDetail extends SgsFeaturedRouteSummary { + startEntrance?: SgsRouteEndpoint | null; + endEntrance?: SgsRouteEndpoint | null; + waypoints: SgsFeaturedRouteWaypoint[]; +} +interface SgsFeaturedRoutePreviewPayload { + routeId?: string | number; + routeName?: string; + color?: string | null; + routeColor?: string | null; + startEntrance?: SgsRouteEndpoint | null; + endEntrance?: SgsRouteEndpoint | null; + waypoints?: SgsFeaturedRouteWaypoint[]; +} +interface SgsRouteRoamingOptions { + mode?: 'third-person' | 'top'; + speed?: number; +} +interface SgsGuideNavigationGraphResp { + floorId: string | number; + floorCode?: string | null; + status?: string | null; + graphJson?: string | null; + updatedAt?: string | null; +} +interface SgsFlowlineOptions { + autoFit?: boolean; + showLabels?: boolean; + animate?: boolean; +} +/** POI 标签项(单个或聚合) */ +interface SgsPoiLabelItem { + id: string; + labelKind: 'SINGLE' | 'CLUSTER'; + lod: 'FAR' | 'MID' | 'NEAR'; + floorId: string | number; + floorCode?: string; + name: string; + type?: string; + typeName?: string; + category?: string; + categoryName?: string; + poiGroup?: string; + position?: { + x: number; + y: number; + z: number; + }; + labelPosition?: { + x: number; + y: number; + z: number; + }; + positionSource?: string; + labelSource?: string; + priority?: number; + childCount?: number; + childPoiIds?: string[]; + defaultVisible?: boolean; + sortOrder?: number; +} +/** POI 标签楼层数据 */ +interface SgsPoiLabelFloor { + floorId: string | number; + floorCode: string; + floorName?: string; + sortOrder?: number; + farLabels: SgsPoiLabelItem[]; + midLabels: SgsPoiLabelItem[]; + nearLabels: SgsPoiLabelItem[]; +} +/** POI 标签摘要统计 */ +interface SgsPoiLabelSummary { + floorCount: number; + farLabelCount: number; + midLabelCount: number; + nearLabelCount: number; + sourcePoiCount: number; + clusterCount: number; + singleCount: number; +} +/** POI Label LOD Bundle 响应 */ +interface SgsPoiLabelLodBundle { + mapId: string | number; + dataVersion?: string; + floors: SgsPoiLabelFloor[]; + summary: SgsPoiLabelSummary; + updatedAt?: string; +} + +/** + * 过滤 EXTERIOR 楼层,检查楼层是否是室内可导航楼层 + */ +declare function isIndoorNavigableFloor(floorCode: string): boolean; +/** + * 物理楼层顺序由低到高排序 + */ +declare function sortIndoorFloors(floors: T[]): T[]; + +declare const utils_isIndoorNavigableFloor: typeof isIndoorNavigableFloor; +declare const utils_sortIndoorFloors: typeof sortIndoorFloors; +declare namespace utils { + export { utils_isIndoorNavigableFloor as isIndoorNavigableFloor, utils_sortIndoorFloors as sortIndoorFloors }; +} + +/** + * SGS 3D 地图 H5 SDK 入口类 + * + *

通过 iframe 嵌入 /h5-sdk 底座页面,以 postMessage 进行双向通信。 + *

所有需要后端数据的操作均通过 postMessage 指令由底座代理, + * Demo / 宿主不允许直连后端 API。 + * + * @version 2.4.1 + */ +declare class SGSMapSDK extends EventEmitter { + static utils: typeof utils; + private container; + private sdkUrl; + private floorId; + private mapId; + private targetOrigin; + private iframe; + private _isReady; + private _isDestroyed; + /** ready 失败终态:iframe 加载失败或 ready 超时后置 true,后续命令直接 reject */ + private _readyFailed; + /** ready 失败时的错误对象,用于后续命令的立即 reject */ + private _readyFailedError; + private defaultTimeout; + private readyTimeoutMs; + /** 已 ready 后正在等待响应的命令队列 */ + private promiseQueue; + /** ready 前暂存的异步命令队列 */ + private _pendingCommands; + /** ready 前暂存的 fire-and-forget 命令队列 */ + private _pendingFireAndForget; + private _messageListener; + /** ready 超时计时器 */ + private _readyTimer; + /** 🔧 P0: HELLO 重试定时器 — 解决 iframe onload 与 React useEffect 时序竞争 */ + private _helloInterval; + /** ENGINE_READY 返回的 payload(供外部读取) */ + readyPayload: any; + /** ready Promise 的 resolve/reject,保证 whenReady() 返回同一个 Promise */ + private _readyResolve; + private _readyReject; + private _readyPromise; + /** 性能追踪字段 */ + private _sdkCreatedAt; + private _iframeDomReadyMs; + private _engineReadyMs; + constructor(options: SGSMapSDKOptions); + private _initIframeBridge; + /** + * ENGINE_READY 到达时的处理 + */ + private _onEngineReady; + /** + * reject ready Promise 并清空所有挂起命令,设置失败终态 + */ + private _rejectReady; + /** + * 排空 ready 前暂存的命令队列 — 逐个以 _postCommandAsync 发送 + */ + private _flushPendingCommands; + /** + * reject 所有挂起的 Promise 和命令队列 + */ + private _rejectAllPending; + /** + * 返回 ready Promise(全局唯一,多次调用返回同一个)。 + * 别名:ready() + * + * @example + * await sdk.whenReady(); + * sdk.changeFloor(floorId); + */ + whenReady(): Promise; + /** + * whenReady 的别名 + */ + ready(): Promise; + /** 获取 ready 状态 */ + getIsReady(): boolean; + /** 获取当前楼层 ID */ + getCurrentFloor(): string | number; + /** 获取当前 mapId */ + getMapId(): string | number; + /** 获取 SDK 版本号 */ + getVersion(): string; + private _generateRequestId; + private _postCommand; + /** + * 异步命令发送(外部入口)— 支持命令队列 + * + *

状态机优先级:destroyed > readyFailed > !ready(入队) > ready(发送) + */ + private _postCommandAsync; + /** + * fire-and-forget 命令发送(void 方法统一入口) + * + *

受 destroyed/readyFailed/!ready 状态保护: + * - destroyed/readyFailed:静默丢弃 + console.warn + * - !ready:暂存到 _pendingFireAndForget,ready 后自动发送 + * - ready:立即发送 + */ + private _postCommandSafe; + /** + * 异步命令发送(内部实现)— 已确认 ready + */ + private _postCommandAsyncInner; + /** 聚焦到指定要素节点 */ + focusTo(node: SgsMapNode | string): void; + /** 切换楼层 */ + changeFloor(floorId: string | number, timeout?: number, options?: { + forceReload?: boolean; + }): Promise<{ + success: boolean; + floorId: string | number; + }>; + /** 添加业务图钉 */ + addMarker(config: MarkerConfig): void; + /** 移除指定图钉 */ + removeMarker(markerId: string): void; + /** 清空所有业务图钉 */ + clearMarkers(): void; + /** 高亮多边形轮廓 */ + highlightPolygon(payload: string | object): void; + /** 寻路规划(v1 兼容) */ + planRoute(from: SgsMapNode | string, to: SgsMapNode | string, options?: any, timeout?: number): Promise; + /** 寻路规划 V2(结构化参数) */ + planRouteV2(from: SgsMapNode, to: SgsMapNode, options?: any, timeout?: number): Promise; + /** 清除当前路线 */ + clearRoute(): void; + /** 渲染已有路线点,供外部宿主直接展示自定义路线 */ + renderRoutePoints(pathPoints: Array<{ + x: number; + y: number; + z: number; + }>, options?: any, timeout?: number): Promise; + /** 启动路线漫游;pathPoints 为空时,基座会优先使用当前已渲染路线 */ + startRoaming(pathPoints?: Array<{ + x: number; + y: number; + z: number; + }>, options?: SgsRouteRoamingOptions, timeout?: number): Promise<{ + success: boolean; + mode?: string; + }>; + /** 停止路线漫游 */ + stopRoaming(timeout?: number): Promise<{ + success: boolean; + }>; + /** 暂停或继续路线漫游 */ + toggleRoamingPause(timeout?: number): Promise<{ + success: boolean; + paused?: boolean; + }>; + /** + * 导航至POI(SDK 2.4.1) + * + * 支持从坐标或POI导航到目标POI,后端自动查询POI坐标。 + * + * @param from 起点(POI ID字符串/数字 或 坐标对象) + * @param toPoiId 目标POI ID(建议使用string避免Snowflake精度丢失) + * @param options 路径选项(wheelchair, preferElevator) + * @param timeout 超时时间(毫秒) + */ + planRouteToPoi(from: string | number | SdkRoutePoint, toPoiId: string | number, options?: RouteOptions, timeout?: number): Promise; + /** + * POI间导航(SDK 2.4.1) + * + * 两个POI之间的导航,后端自动查询POI坐标。 + * + * @param fromPoiId 起点POI ID + * @param toPoiId 终点POI ID + * @param options 路径选项(wheelchair, preferElevator) + * @param timeout 超时时间(毫秒) + */ + planRouteBetweenPois(fromPoiId: string | number, toPoiId: string | number, options?: RouteOptions, timeout?: number): Promise; + getState(): Promise; + /** 重置视角 */ + resetView(timeout?: number): Promise<{ + success: boolean; + }>; + /** 设置 2D/3D 视图模式 */ + setViewMode(mode: '2d' | '3d', timeout?: number): Promise<{ + success: boolean; + mode: string; + }>; + /** 切换图层可见性 */ + toggleLayer(types: string[], visible: boolean, timeout?: number): Promise<{ + success: boolean; + visibleTypes: string[]; + }>; + /** 设置可见图层类型列表 */ + setVisibleLayers(types: string[], timeout?: number): Promise<{ + success: boolean; + visibleTypes: string[]; + }>; + /** 设置路线展示视图是否活跃 */ + setRouteViewActive(active: boolean, timeout?: number): Promise<{ + success: boolean; + active: boolean; + }>; + /** 获取地图 Manifest 清单 */ + getManifest(timeout?: number): Promise; + /** 加载指定楼层的完整 Bundle 数据(含 POI/空间面/讲解点/路网) */ + loadFloorBundle(floorId: string | number, timeout?: number): Promise; + /** 获取指定楼层的 POI 列表 */ + getFloorPois(floorId: string | number, timeout?: number): Promise; + /** 获取指定楼层的起终点候选目的地列表(门点/POI/空间等吸附后目的地) */ + getNavigablePlaces(floorId: string | number, timeout?: number): Promise; + /** 获取当前楼层的 POI 列表(根据 floorCode 参数) */ + getPois(floorCode: string, timeout?: number): Promise; + /** 获取指定楼层的空间面列表 */ + getSpaces(floorId: string | number, timeout?: number): Promise; + /** 获取指定楼层的已标定讲解点列表(地图渲染入口) */ + getGuideStops(floorId: string | number, timeout?: number): Promise; + /** 获取指定展厅的已标定讲解点列表(业务导览入口) */ + getGuideStopsByHall(hallId: string | number, timeout?: number): Promise; + /** 预加载指定楼层的模型资源(不渲染) */ + preloadFloor(floorId: string | number, timeout?: number): Promise<{ + cached: boolean; + }>; + /** 获取性能统计信息 */ + getPerformanceStats(timeout?: number): Promise; + /** 获取地图级诊断信息 */ + getDiagnostics(timeout?: number): Promise; + /** 获取楼层级诊断信息 */ + getFloorDiagnostics(floorId: string | number, timeout?: number): Promise; + /** 清空客户端模型缓存 */ + clearModelCache(): void; + /** 获取指定楼层的业务 POI 列表 */ + getBusinessPois(floorId: string | number, options?: { + types?: string[]; + keyword?: string; + limit?: number; + }, timeout?: number): Promise; + /** 查询 POI 列表 */ + queryPois(params: SgsPoiQueryParams, timeout?: number): Promise; + /** 获取特色路线列表 */ + getFeaturedRoutes(mapId?: string | number, options?: { + limit?: number; + }, timeout?: number): Promise; + /** 获取特色路线详情 */ + getFeaturedRoute(routeId: string | number, timeout?: number): Promise; + /** 生成并展示精品路线预览 */ + planFeaturedRoutePreview(payload: SgsFeaturedRoutePreviewPayload, timeout?: number): Promise; + /** 显示动线 */ + showFlowline(routeId: string | number, options?: SgsFlowlineOptions, timeout?: number): Promise<{ + success: boolean; + routeId: string | number; + }>; + /** 清除动线 */ + clearFlowline(): void; + /** @deprecated 使用 getManifest() 替代 */ + getReleaseManifest(timeout?: number): Promise; + /** @deprecated 已废弃,无替代方法 */ + getModelResources(timeout?: number): Promise; + /** @deprecated 已废弃,无替代方法 */ + getLayerTree(timeout?: number): Promise; + /** @deprecated 已废弃,无替代方法 */ + loadMockReleaseRoute(timeout?: number): Promise; + /** + * 销毁 SDK 实例 + * + *

清理 iframe、移除事件监听器、reject 所有挂起的 Promise。 + *

销毁后调用任何命令方法都会 reject ERR_DESTROYED。 + */ + destroy(): void; +} + +export { type MarkerConfig, type RouteOptions, type RouteResult, type SGSErrorCode, type SGSMapEvents, type SGSMapSDKOptions, type SdkRoutePoint, type SgsBusinessPoiType, type SgsFeaturedRouteDetail, type SgsFeaturedRoutePreviewPayload, type SgsFeaturedRouteSummary, type SgsFeaturedRouteWaypoint, type SgsFloorBundle, type SgsFloorDiagnostics, type SgsFloorDiagnosticsSummary, type SgsFloorResource, type SgsFlowlineOptions, type SgsGuideNavigationGraphResp, type SgsGuideStop, type SgsLayerTreeNode, type SgsMapDiagnostics, type SgsMapManifest, type SgsMapNode, type SgsModelResource, type SgsNavigablePlace, type SgsPerformanceStats, type SgsPoi, type SgsPoiGroup, type SgsPoiLabelFloor, type SgsPoiLabelItem, type SgsPoiLabelLodBundle, type SgsPoiLabelSummary, type SgsPoiQueryParams, type SgsRouteAsset, type SgsRouteEndpoint, type SgsRoutePlanV2, type SgsRouteRoamingOptions, type SgsRouteSegment, type SgsRouteSegmentType, type SgsRouteTransferType, type SgsSdkError, type SgsSdkFloorSummary, type SgsSdkManifest, type SgsSpaceArea, type SnapDetail, SGSMapSDK as default }; diff --git a/static/sgs-map-sdk/index.d.ts b/static/sgs-map-sdk/index.d.ts index ca17961..a4e748f 100644 --- a/static/sgs-map-sdk/index.d.ts +++ b/static/sgs-map-sdk/index.d.ts @@ -73,6 +73,21 @@ interface RouteResult { interface RouteOptions { mode?: 'walk' | 'accessible'; wheelchair?: boolean; + /** v2.4.1: 跨楼层时优先选择电梯 */ + preferElevator?: boolean; +} +/** SDK 2.4.1: 路线起点/终点定义 */ +interface SdkRoutePoint { + /** POI ID(POI模式) */ + poiId?: string | number; + /** 楼层 ID(坐标模式) */ + floorId?: string | number; + /** X坐标(GLB米,坐标模式) */ + x?: number; + /** Y坐标(GLB米,坐标模式) */ + y?: number; + /** Z坐标(GLB米,可选) */ + z?: number; } /** SDK 初始化配置 */ interface SGSMapSDKOptions { @@ -101,6 +116,13 @@ interface SGSMapEvents { floorId?: string | number; description?: string; }; + poiDetail: SgsPoi & Record; + poiNavigate: SgsPoi & Record; + poiClose: Record; + spaceClick: SgsSpaceArea & Record; + spaceDetail: SgsSpaceArea & Record; + spaceNavigate: SgsSpaceArea & Record; + spaceClose: Record; floorChanged: { floorId: string | number; }; @@ -153,6 +175,14 @@ interface SGSMapEvents { segmentCount?: number; hasTransfer?: boolean; }; + /** 路径漫游进度,progress 范围 0-1 */ + roamingProgress: { + progress: number; + }; + /** 路径漫游结束 */ + roamingDone: { + success: boolean; + }; } /** SDK 错误码 */ type SGSErrorCode = 'ERR_TIMEOUT' | 'ERR_NO_PATH' | 'ERR_FLOOR_NOT_FOUND' | 'ERR_NOT_READY' | 'ERR_POI_NOT_FOUND' | 'ERR_NETWORK' | 'ERR_IFRAME_DEAD' | 'ERR_UNAUTHORIZED' | 'ERR_UNSUPPORTED' | 'ERR_FAILED' | 'ERR_DESTROYED' | 'ERR_IFRAME_LOAD' | 'ERR_READY_TIMEOUT'; @@ -274,12 +304,19 @@ interface SgsPoi { anchorNodeName?: string | null; /** 描述 */ description?: string; + /** 英文描述 */ + descriptionEn?: string; + /** 扩展参数 JSON */ + extParams?: string | Record; /** 图标 URL */ iconUrl?: string; floorId?: string | number; poiGroup?: SgsPoiGroup; businessType?: SgsBusinessPoiType | string; coverImageUrl?: string | null; + images?: string | string[] | null; + externalUrl?: string | null; + videoUrl?: string | null; phone?: string | null; businessHours?: string | null; sortOrder?: number | null; @@ -360,6 +397,8 @@ interface SgsSdkManifest { coordinateSystem: string; /** 楼层摘要列表(按 sortOrder 升序) */ floors: SgsSdkFloorSummary[]; + /** 路线显示资产列表,如 EXTERIOR + L1 同地面组合模型 */ + routeAssets?: SgsRouteAsset[]; /** 能力声明 */ capabilities: { /** 是否支持 Draco 压缩模型 */ @@ -392,8 +431,34 @@ interface SgsSdkManifest { poiSearch?: boolean; featuredRoutes?: boolean; flowlinePreview?: boolean; + sameGroundCompositeRoute?: boolean; }; } +/** v2.4.1 路线显示资产 */ +interface SgsRouteAsset { + /** 资产 ID */ + id: string | number; + /** 地图 ID */ + mapId?: string | number; + /** 资产所属楼层 ID;全局资产可为空 */ + floorId?: string | number | null; + /** 资产楼层编码或组合编码 */ + floorCode?: string | null; + /** 资产角色,如 SAME_GROUND_COMPOSITE */ + assetRole: string; + /** 模型 URL */ + modelUrl: string; + /** 源文件名 */ + sourceFileName?: string | null; + /** 源节点名 */ + sourceNodeName?: string | null; + /** 模型版本标签 */ + modelVersion?: string | null; + /** 组合资产覆盖的楼层编码,如 ["EXTERIOR", "L1"] */ + coverageFloorCodes?: string[]; + /** 排序 */ + sortOrder?: number; +} /** v2.2.0 楼层摘要(manifest 内嵌) */ interface SgsSdkFloorSummary { /** 楼层 ID */ @@ -468,17 +533,24 @@ interface SgsSpaceArea { id: string | number; /** 显示名称 */ name: string; + /** 展示名称(兼容 SDK 内部空间标签) */ + displayName?: string; /** 空间类型(如 exhibition_hall / corridor / office) */ type: string; + /** 空间类型中文名 */ + typeName?: string; /** 所属楼层 ID */ floorId: string | number; + /** 所属楼层编码 */ + floorCode?: string; /** WKT 边界多边形 */ boundaryWkt: string; /** 中心点 GLB 坐标(米) */ center: { x: number; - y: number; + y?: number; z: number; + radius?: number; }; /** 关联的 3D 场景节点名(可选) */ sourceNodeName?: string; @@ -486,6 +558,26 @@ interface SgsSpaceArea { status: 'ACTIVE' | 'INACTIVE'; /** 颜色(十六进制,如 #0097A9) */ colorHex?: string; + /** 简介 */ + description?: string | null; + /** 英文简介 */ + descriptionEn?: string | null; + /** 封面图 */ + coverImageUrl?: string | null; + /** 图集 */ + images?: string | string[] | null; + /** 兼容展厅表图集字段 */ + galleryUrls?: string | string[] | null; + /** 视频 */ + videoUrl?: string | null; + /** 外部详情链接 */ + externalUrl?: string | null; + /** 开放时间 */ + openTime?: string | null; + /** 展品数量 */ + exhibitCount?: number | null; + /** 面积 */ + areaSqm?: number | null; } /** v2.2.0 讲解点 — 已标定、可在地图定位的导览讲解点 */ interface SgsGuideStop { @@ -577,6 +669,10 @@ interface SgsPerformanceStats { dracoUsed?: boolean; /** 是否使用了 fallback 模型 */ fallbackUsed?: boolean; + /** Bundle 是否命中前端缓存 */ + bundleCacheHit?: boolean; + /** 当前加载的模型 URL */ + currentModelUrl?: string; /** 当前楼层 ID */ currentFloorId?: string | number; } @@ -590,6 +686,11 @@ interface SgsNavigablePlace { x: number; z: number; nodeId?: string | number; + routeNodeId?: string | number; + sourceType?: "POI" | "SPACE" | "FLOOR" | "CUSTOM" | string | null; + sourceId?: string | number | null; + anchorId?: string | number | null; + anchorType?: string | null; ownerName?: string; typeCode?: string; typeName?: string; @@ -726,11 +827,90 @@ interface SgsFeaturedRouteDetail extends SgsFeaturedRouteSummary { endEntrance?: SgsRouteEndpoint | null; waypoints: SgsFeaturedRouteWaypoint[]; } +interface SgsFeaturedRoutePreviewPayload { + routeId?: string | number; + routeName?: string; + color?: string | null; + routeColor?: string | null; + startEntrance?: SgsRouteEndpoint | null; + endEntrance?: SgsRouteEndpoint | null; + waypoints?: SgsFeaturedRouteWaypoint[]; +} +interface SgsRouteRoamingOptions { + mode?: 'third-person' | 'top'; + speed?: number; +} +interface SgsGuideNavigationGraphResp { + floorId: string | number; + floorCode?: string | null; + status?: string | null; + graphJson?: string | null; + updatedAt?: string | null; +} interface SgsFlowlineOptions { autoFit?: boolean; showLabels?: boolean; animate?: boolean; } +/** POI 标签项(单个或聚合) */ +interface SgsPoiLabelItem { + id: string; + labelKind: 'SINGLE' | 'CLUSTER'; + lod: 'FAR' | 'MID' | 'NEAR'; + floorId: string | number; + floorCode?: string; + name: string; + type?: string; + typeName?: string; + category?: string; + categoryName?: string; + poiGroup?: string; + position?: { + x: number; + y: number; + z: number; + }; + labelPosition?: { + x: number; + y: number; + z: number; + }; + positionSource?: string; + labelSource?: string; + priority?: number; + childCount?: number; + childPoiIds?: string[]; + defaultVisible?: boolean; + sortOrder?: number; +} +/** POI 标签楼层数据 */ +interface SgsPoiLabelFloor { + floorId: string | number; + floorCode: string; + floorName?: string; + sortOrder?: number; + farLabels: SgsPoiLabelItem[]; + midLabels: SgsPoiLabelItem[]; + nearLabels: SgsPoiLabelItem[]; +} +/** POI 标签摘要统计 */ +interface SgsPoiLabelSummary { + floorCount: number; + farLabelCount: number; + midLabelCount: number; + nearLabelCount: number; + sourcePoiCount: number; + clusterCount: number; + singleCount: number; +} +/** POI Label LOD Bundle 响应 */ +interface SgsPoiLabelLodBundle { + mapId: string | number; + dataVersion?: string; + floors: SgsPoiLabelFloor[]; + summary: SgsPoiLabelSummary; + updatedAt?: string; +} /** * 过滤 EXTERIOR 楼层,检查楼层是否是室内可导航楼层 @@ -756,7 +936,7 @@ declare namespace utils { *

所有需要后端数据的操作均通过 postMessage 指令由底座代理, * Demo / 宿主不允许直连后端 API。 * - * @version 2.4.0 + * @version 2.4.1 */ declare class SGSMapSDK extends EventEmitter { static utils: typeof utils; @@ -858,7 +1038,9 @@ declare class SGSMapSDK extends EventEmitter { /** 聚焦到指定要素节点 */ focusTo(node: SgsMapNode | string): void; /** 切换楼层 */ - changeFloor(floorId: string | number, timeout?: number): Promise<{ + changeFloor(floorId: string | number, timeout?: number, options?: { + forceReload?: boolean; + }): Promise<{ success: boolean; floorId: string | number; }>; @@ -876,7 +1058,52 @@ declare class SGSMapSDK extends EventEmitter { planRouteV2(from: SgsMapNode, to: SgsMapNode, options?: any, timeout?: number): Promise; /** 清除当前路线 */ clearRoute(): void; - /** 获取引擎状态 */ + /** 渲染已有路线点,供外部宿主直接展示自定义路线 */ + renderRoutePoints(pathPoints: Array<{ + x: number; + y: number; + z: number; + }>, options?: any, timeout?: number): Promise; + /** 启动路线漫游;pathPoints 为空时,基座会优先使用当前已渲染路线 */ + startRoaming(pathPoints?: Array<{ + x: number; + y: number; + z: number; + }>, options?: SgsRouteRoamingOptions, timeout?: number): Promise<{ + success: boolean; + mode?: string; + }>; + /** 停止路线漫游 */ + stopRoaming(timeout?: number): Promise<{ + success: boolean; + }>; + /** 暂停或继续路线漫游 */ + toggleRoamingPause(timeout?: number): Promise<{ + success: boolean; + paused?: boolean; + }>; + /** + * 导航至POI(SDK 2.4.1) + * + * 支持从坐标或POI导航到目标POI,后端自动查询POI坐标。 + * + * @param from 起点(POI ID字符串/数字 或 坐标对象) + * @param toPoiId 目标POI ID(建议使用string避免Snowflake精度丢失) + * @param options 路径选项(wheelchair, preferElevator) + * @param timeout 超时时间(毫秒) + */ + planRouteToPoi(from: string | number | SdkRoutePoint, toPoiId: string | number, options?: RouteOptions, timeout?: number): Promise; + /** + * POI间导航(SDK 2.4.1) + * + * 两个POI之间的导航,后端自动查询POI坐标。 + * + * @param fromPoiId 起点POI ID + * @param toPoiId 终点POI ID + * @param options 路径选项(wheelchair, preferElevator) + * @param timeout 超时时间(毫秒) + */ + planRouteBetweenPois(fromPoiId: string | number, toPoiId: string | number, options?: RouteOptions, timeout?: number): Promise; getState(): Promise; /** 重置视角 */ resetView(timeout?: number): Promise<{ @@ -944,6 +1171,8 @@ declare class SGSMapSDK extends EventEmitter { }, timeout?: number): Promise; /** 获取特色路线详情 */ getFeaturedRoute(routeId: string | number, timeout?: number): Promise; + /** 生成并展示精品路线预览 */ + planFeaturedRoutePreview(payload: SgsFeaturedRoutePreviewPayload, timeout?: number): Promise; /** 显示动线 */ showFlowline(routeId: string | number, options?: SgsFlowlineOptions, timeout?: number): Promise<{ success: boolean; @@ -968,4 +1197,4 @@ declare class SGSMapSDK extends EventEmitter { destroy(): void; } -export { type MarkerConfig, type RouteOptions, type RouteResult, type SGSErrorCode, type SGSMapEvents, type SGSMapSDKOptions, type SgsBusinessPoiType, type SgsFeaturedRouteDetail, type SgsFeaturedRouteSummary, type SgsFeaturedRouteWaypoint, type SgsFloorBundle, type SgsFloorDiagnostics, type SgsFloorDiagnosticsSummary, type SgsFloorResource, type SgsFlowlineOptions, type SgsGuideStop, type SgsLayerTreeNode, type SgsMapDiagnostics, type SgsMapManifest, type SgsMapNode, type SgsModelResource, type SgsNavigablePlace, type SgsPerformanceStats, type SgsPoi, type SgsPoiGroup, type SgsPoiQueryParams, type SgsRouteEndpoint, type SgsRoutePlanV2, type SgsRouteSegment, type SgsRouteSegmentType, type SgsRouteTransferType, type SgsSdkError, type SgsSdkFloorSummary, type SgsSdkManifest, type SgsSpaceArea, type SnapDetail, SGSMapSDK as default }; +export { type MarkerConfig, type RouteOptions, type RouteResult, type SGSErrorCode, type SGSMapEvents, type SGSMapSDKOptions, type SdkRoutePoint, type SgsBusinessPoiType, type SgsFeaturedRouteDetail, type SgsFeaturedRoutePreviewPayload, type SgsFeaturedRouteSummary, type SgsFeaturedRouteWaypoint, type SgsFloorBundle, type SgsFloorDiagnostics, type SgsFloorDiagnosticsSummary, type SgsFloorResource, type SgsFlowlineOptions, type SgsGuideNavigationGraphResp, type SgsGuideStop, type SgsLayerTreeNode, type SgsMapDiagnostics, type SgsMapManifest, type SgsMapNode, type SgsModelResource, type SgsNavigablePlace, type SgsPerformanceStats, type SgsPoi, type SgsPoiGroup, type SgsPoiLabelFloor, type SgsPoiLabelItem, type SgsPoiLabelLodBundle, type SgsPoiLabelSummary, type SgsPoiQueryParams, type SgsRouteAsset, type SgsRouteEndpoint, type SgsRoutePlanV2, type SgsRouteRoamingOptions, type SgsRouteSegment, type SgsRouteSegmentType, type SgsRouteTransferType, type SgsSdkError, type SgsSdkFloorSummary, type SgsSdkManifest, type SgsSpaceArea, type SnapDetail, SGSMapSDK as default }; diff --git a/static/sgs-map-sdk/index.global.js b/static/sgs-map-sdk/index.global.js index bcfcbac..e541f3b 100644 --- a/static/sgs-map-sdk/index.global.js +++ b/static/sgs-map-sdk/index.global.js @@ -1,2 +1,2 @@ -"use strict";(()=>{var m=class{constructor(){this.events={}}on(n,e){this.events[n]||(this.events[n]=[]),this.events[n].push(e)}off(n,e){this.events[n]&&(this.events[n]=this.events[n].filter(s=>s!==e))}once(n,e){let s=t=>{this.off(n,s),e(t)};this.on(n,s)}emit(n,e){this.events[n]&&[...this.events[n]].forEach(t=>{try{t(e)}catch(r){console.error(`[EventEmitter] Error emitting event ${String(n)}:`,r)}})}removeAllListeners(n){n!==void 0?delete this.events[n]:this.events={}}listenerCount(n){return this.events[n]?.length??0}};var g="2.3.0",_=2,p=15e3,y=5e3,l=class extends m{constructor(e){super();this.iframe=null;this._isReady=!1;this._isDestroyed=!1;this._readyFailed=!1;this._readyFailedError=null;this.promiseQueue={};this._pendingCommands=[];this._pendingFireAndForget=[];this._messageListener=null;this._readyTimer=null;this.readyPayload=null;this._readyResolve=null;this._readyReject=null;this._iframeDomReadyMs=null;this._engineReadyMs=null;this._sdkCreatedAt=Date.now();let s=typeof e.container=="string"?document.getElementById(e.container):e.container;if(!s)throw new Error(`[SGSMapSDK] Cannot find container: ${e.container}`);this.container=s,this.sdkUrl=e.sdkUrl||"/h5-sdk",this.floorId=e.floorId||1,this.mapId=e.mapId||1,this.defaultTimeout=e.timeout||y,this.readyTimeoutMs=e.readyTimeout||p,this.targetOrigin=e.targetOrigin,this.targetOrigin||(this.sdkUrl.startsWith("http")?this.targetOrigin=new URL(this.sdkUrl).origin:typeof window<"u"?this.targetOrigin=window.location.origin:this.targetOrigin="*"),this._readyPromise=new Promise((t,r)=>{this._readyResolve=t,this._readyReject=r}),this._initIframeBridge()}_initIframeBridge(){let e=document.createElement("iframe"),s=new URL(this.sdkUrl,typeof window<"u"?window.location.href:"http://localhost");typeof window<"u"&&new URLSearchParams(window.location.search).forEach((r,i)=>{s.searchParams.set(i,r)}),s.searchParams.has("floorId")||s.searchParams.set("floorId",String(this.floorId)),e.src=s.toString(),e.style.cssText="width:100%;height:100%;border:none;background:transparent;",this.container.innerHTML="",this.container.appendChild(e),this.iframe=e,this._messageListener=t=>{if(this.targetOrigin!=="*"&&t.origin!==this.targetOrigin||!this.iframe||t.source!==this.iframe.contentWindow)return;let r=t.data;if(r){if(r.type==="SGS_MAP_EVENT"){let{action:i,payload:o}=r;i==="ON_POI_CLICK"?this.emit("poiClick",o):i==="ENGINE_READY"?this._onEngineReady(o):i==="ON_FLOOR_CHANGED"?this.emit("floorChanged",o):i==="ON_ROUTE_TRANSFER"?this.emit("routeTransfer",o):i==="ON_MODEL_LOADING"?this.emit("modelLoading",o):i==="ON_MODEL_READY"?this.emit("modelReady",o):i==="ON_MODEL_ERROR"?this.emit("modelError",o):i==="ON_FLOOR_BUNDLE_READY"?this.emit("floorBundleReady",o):i==="ON_ROUTE_READY"&&this.emit("routeReady",o)}if(r.type==="SGS_MAP_RESPONSE"){let{action:i,requestId:o,payload:a,error:d}=r,u=this.promiseQueue[o];if(u){if(a&&a.success!==!1)u.resolve(a);else{let c=d||{code:"ERR_FAILED",message:`${i} command failed`};u.reject(c)}delete this.promiseQueue[o]}}}},window.addEventListener("message",this._messageListener),e.onload=()=>{this._iframeDomReadyMs=Date.now()-this._sdkCreatedAt,!this._isReady&&(this._postCommand("HELLO",{protocolVersion:_,sdkVersion:g}),console.log(`[SGSMapSDK] Sent HELLO handshake (v${g}), waiting for ENGINE_READY...`))},e.onerror=t=>{console.error("[SGSMapSDK] Iframe load error",t);let r={code:"ERR_IFRAME_LOAD",message:"Failed to load map engine iframe."};this.emit("loadError",{type:"iframe_error",detail:r.message}),this._rejectReady(r)},this._readyTimer=setTimeout(()=>{if(!this._isReady&&!this._isDestroyed){let t={code:"ERR_READY_TIMEOUT",message:`ENGINE_READY not received within ${this.readyTimeoutMs}ms`};console.error(`[SGSMapSDK] ${t.message}`),this._rejectReady(t)}},this.readyTimeoutMs)}_onEngineReady(e){this._isReady||(this._isReady=!0,this.readyPayload=e,this._engineReadyMs=Date.now()-this._sdkCreatedAt,this._readyTimer&&(clearTimeout(this._readyTimer),this._readyTimer=null),this._readyResolve&&(this._readyResolve(e),this._readyResolve=null,this._readyReject=null),this.emit("ready",e),console.log("[SGSMapSDK] Handshake complete: Engine Ready received \u2705"),this._flushPendingCommands())}_rejectReady(e){this._readyFailed=!0,this._readyFailedError=e,this._readyReject&&(this._readyReject(e),this._readyResolve=null,this._readyReject=null),this._readyTimer&&(clearTimeout(this._readyTimer),this._readyTimer=null),this._rejectAllPending(e.code,e.message),this._pendingFireAndForget.splice(0)}_flushPendingCommands(){let e=this._pendingFireAndForget.splice(0);for(let r of e)this._postCommand(r.action,r.payload);let s=this._pendingCommands.splice(0);for(let r of s)this._postCommandAsyncInner(r.action,r.payload,r.timeout).then(r.resolve).catch(r.reject);let t=e.length+s.length;t>0&&console.log(`[SGSMapSDK] Flushed ${t} pending commands after ready (${e.length} fire-and-forget, ${s.length} async)`)}_rejectAllPending(e,s){Object.keys(this.promiseQueue).forEach(r=>{let i=this.promiseQueue[r];i&&i.reject({code:e,message:s}),delete this.promiseQueue[r]});let t=this._pendingCommands.splice(0);for(let r of t)r.reject({code:e,message:s})}whenReady(){return this._readyPromise}ready(){return this._readyPromise}getIsReady(){return this._isReady}getCurrentFloor(){return this.floorId}getMapId(){return this.mapId}getVersion(){return g}_generateRequestId(){return`req_sdk_${Date.now()}_${Math.floor(Math.random()*1e5)}`}_postCommand(e,s,t){if(!this.iframe||!this.iframe.contentWindow){console.error("[SGSMapSDK] SDK Iframe is not available!");return}this.iframe.contentWindow.postMessage({type:"SGS_MAP_COMMAND",action:e,requestId:t,payload:s},this.targetOrigin)}_postCommandAsync(e,s,t=this.defaultTimeout){return this._isDestroyed?Promise.reject({code:"ERR_DESTROYED",message:"SGSMapSDK has been destroyed. Cannot send commands."}):this._readyFailed?Promise.reject(this._readyFailedError||{code:"ERR_NOT_READY",message:"SDK ready failed."}):this._isReady?this._postCommandAsyncInner(e,s,t):new Promise((r,i)=>{this._pendingCommands.push({action:e,payload:s,timeout:t,resolve:r,reject:i}),console.log(`[SGSMapSDK] Command "${e}" queued (waiting for ready). Queue size: ${this._pendingCommands.length}`)})}_postCommandSafe(e,s){if(this._isDestroyed){console.warn(`[SGSMapSDK] Command "${e}" ignored: SDK destroyed.`);return}if(this._readyFailed){console.warn(`[SGSMapSDK] Command "${e}" ignored: SDK ready failed.`);return}if(!this._isReady){this._pendingFireAndForget.push({action:e,payload:s}),console.log(`[SGSMapSDK] Command "${e}" queued (fire-and-forget, waiting for ready). Queue size: ${this._pendingFireAndForget.length}`);return}this._postCommand(e,s)}_postCommandAsyncInner(e,s,t=this.defaultTimeout){let r=this._generateRequestId();return new Promise((i,o)=>{let a=setTimeout(()=>{this.promiseQueue[r]&&(this.promiseQueue[r].reject({code:"ERR_TIMEOUT",message:`Command "${e}" timed out after ${t}ms`}),delete this.promiseQueue[r])},t);this.promiseQueue[r]={resolve:d=>{clearTimeout(a),i(d)},reject:d=>{clearTimeout(a),o(d)}},this._postCommand(e,s,r)})}focusTo(e){typeof e=="string"?this._postCommandSafe("FOCUS_TO",{nodeName:e}):this._postCommandSafe("FOCUS_TO",e)}changeFloor(e,s){return this._postCommandAsync("CHANGE_FLOOR",{floorId:e},s).then(t=>(this.floorId=e,t))}addMarker(e){this._postCommandSafe("ADD_MARKER",e)}removeMarker(e){this._postCommandSafe("REMOVE_MARKER",{markerId:e})}clearMarkers(){this._postCommandSafe("CLEAR_MARKERS")}highlightPolygon(e){this._postCommandSafe("HIGHLIGHT_POLYGON",{nodeName:e})}planRoute(e,s,t,r=5e3){let i=typeof e=="string"?e:void 0,o=typeof s=="string"?s:void 0,a={};return i?a.startNode=i:a.from=e,o?a.endNode=o:a.to=s,t&&(a.options=t),this._postCommandAsync("PLAN_ROUTE",a,r)}planRouteV2(e,s,t,r=5e3){let i={from:e,to:s,options:t};return this._postCommandAsync("PLAN_ROUTE",i,r)}clearRoute(){this._postCommandSafe("CLEAR_ROUTE")}getState(){return this._postCommandAsync("GET_STATE")}resetView(){this._postCommandSafe("RESET_VIEW")}setViewMode(e){this._postCommandSafe("SET_VIEW_MODE",{mode:e})}toggleLayer(e,s,t){return this._postCommandAsync("TOGGLE_LAYER",{types:e,visible:s},t)}setVisibleLayers(e,s){return this._postCommandAsync("SET_VISIBLE_TYPES",{types:e},s)}getManifest(e){return this._postCommandAsync("GET_MANIFEST",{},e)}loadFloorBundle(e,s){return this._postCommandAsync("LOAD_FLOOR_BUNDLE",{floorId:e},s)}getFloorPois(e,s){return this._postCommandAsync("GET_FLOOR_POIS",{floorId:e},s).then(t=>Array.isArray(t)?t:t?.pois||[])}getNavigablePlaces(e,s){return this._postCommandAsync("GET_NAVIGABLE_PLACES",{floorId:e},s).then(t=>Array.isArray(t)?t:t?.navigablePlaces||t?.places||[])}getPois(e,s){return this._postCommandAsync("GET_POIS",{floorCode:e},s)}getSpaces(e,s){return this._postCommandAsync("GET_SPACES",{floorId:e},s).then(t=>Array.isArray(t)?t:t?.spaces||[])}getGuideStops(e,s){return this._postCommandAsync("GET_GUIDE_STOPS",{floorId:e},s).then(t=>Array.isArray(t)?t:t?.guideStops||[])}getGuideStopsByHall(e,s){return this._postCommandAsync("GET_GUIDE_STOPS_BY_HALL",{hallId:e},s).then(t=>Array.isArray(t)?t:t?.guideStops||[])}preloadFloor(e,s){return this._postCommandAsync("PRELOAD_FLOOR",{floorId:e},s)}getPerformanceStats(e){return this._postCommandAsync("GET_PERFORMANCE_STATS",{},e).then(s=>({sdkCreatedAt:this._sdkCreatedAt,iframeDomReadyMs:this._iframeDomReadyMs??void 0,engineReadyMs:this._engineReadyMs??void 0,...s}))}getDiagnostics(e){return this._postCommandAsync("GET_DIAGNOSTICS",{},e)}getFloorDiagnostics(e,s){return this._postCommandAsync("GET_FLOOR_DIAGNOSTICS",{floorId:e},s)}clearModelCache(){this._postCommandSafe("CLEAR_MODEL_CACHE")}getReleaseManifest(e){return console.warn("[SGSMapSDK] getReleaseManifest() is deprecated. Use getManifest() instead."),Promise.reject({code:"ERR_UNSUPPORTED",message:"getReleaseManifest() is deprecated. Use getManifest() instead."})}getModelResources(e){return console.warn("[SGSMapSDK] getModelResources() is deprecated."),Promise.reject({code:"ERR_UNSUPPORTED",message:"getModelResources() is deprecated."})}getLayerTree(e){return console.warn("[SGSMapSDK] getLayerTree() is deprecated."),Promise.reject({code:"ERR_UNSUPPORTED",message:"getLayerTree() is deprecated."})}loadMockReleaseRoute(e){return console.warn("[SGSMapSDK] loadMockReleaseRoute() is deprecated."),Promise.reject({code:"ERR_UNSUPPORTED",message:"loadMockReleaseRoute() is deprecated."})}destroy(){this._isDestroyed||(this._isDestroyed=!0,this._readyTimer&&(clearTimeout(this._readyTimer),this._readyTimer=null),this._readyReject&&(this._readyReject({code:"ERR_DESTROYED",message:"SGSMapSDK has been destroyed before ready."}),this._readyResolve=null,this._readyReject=null),this._rejectAllPending("ERR_DESTROYED","SGSMapSDK has been destroyed. Pending promises were rejected."),this.iframe&&(this.iframe.parentNode&&this.iframe.parentNode.removeChild(this.iframe),this.iframe=null),this._messageListener&&(window.removeEventListener("message",this._messageListener),this._messageListener=null),this.removeAllListeners(),this._isReady=!1,console.log("[SGSMapSDK] Destroyed successfully. Cleaned up iframe, listeners and pending queues."))}};typeof window<"u"&&(window.SGSMapSDK=l);})(); +"use strict";(()=>{var y=Object.defineProperty;var f=(d,o)=>{for(var e in o)y(d,e,{get:o[e],enumerable:!0})};var u=class{constructor(){this.events={}}on(o,e){this.events[o]||(this.events[o]=[]),this.events[o].push(e)}off(o,e){this.events[o]&&(this.events[o]=this.events[o].filter(s=>s!==e))}once(o,e){let s=t=>{this.off(o,s),e(t)};this.on(o,s)}emit(o,e){this.events[o]&&[...this.events[o]].forEach(t=>{try{t(e)}catch(r){console.error(`[EventEmitter] Error emitting event ${String(o)}:`,r)}})}removeAllListeners(o){o!==void 0?delete this.events[o]:this.events={}}listenerCount(o){return this.events[o]?.length??0}};var h={};f(h,{isIndoorNavigableFloor:()=>_,sortIndoorFloors:()=>R});function _(d){return d?d.toUpperCase()!=="EXTERIOR":!1}function R(d){let o=["L-2","L-1","B2","B1","L1","L1.5","L2","L3","L4","L5"],e=d.filter(t=>_(t.floorCode)),s=t=>{let r=t.trim().toUpperCase();r.startsWith("F")&&(r="L"+r.substring(1));let i=o.findIndex(a=>a.toUpperCase()===r);if(i!==-1)return i;let n=r.match(/-?\d+(\.\d+)?/);if(n){let a=parseFloat(n[0]);return r.startsWith("B")||r.includes("-")?-Math.abs(a)-100:a}return 0};return[...e].sort((t,r)=>s(t.floorCode)-s(r.floorCode))}var g="2.4.1",S=2,E=3e4,P=5e3,l=class extends u{constructor(e){super();this.iframe=null;this._isReady=!1;this._isDestroyed=!1;this._readyFailed=!1;this._readyFailedError=null;this.promiseQueue={};this._pendingCommands=[];this._pendingFireAndForget=[];this._messageListener=null;this._readyTimer=null;this._helloInterval=null;this.readyPayload=null;this._readyResolve=null;this._readyReject=null;this._iframeDomReadyMs=null;this._engineReadyMs=null;this._sdkCreatedAt=Date.now();let s=typeof e.container=="string"?document.getElementById(e.container):e.container;if(!s)throw new Error(`[SGSMapSDK] Cannot find container: ${e.container}`);this.container=s,this.sdkUrl=e.sdkUrl||"/h5-sdk",this.floorId=e.floorId||1,this.mapId=e.mapId||1,this.defaultTimeout=e.timeout||P,this.readyTimeoutMs=e.readyTimeout||E,this.targetOrigin=e.targetOrigin,this.targetOrigin||(this.sdkUrl.startsWith("http")?this.targetOrigin=new URL(this.sdkUrl).origin:typeof window<"u"?this.targetOrigin=window.location.origin:this.targetOrigin="*"),this._readyPromise=new Promise((t,r)=>{this._readyResolve=t,this._readyReject=r}),this._initIframeBridge()}_initIframeBridge(){let e=document.createElement("iframe"),s=new URL(this.sdkUrl,typeof window<"u"?window.location.href:"http://localhost");typeof window<"u"&&new URLSearchParams(window.location.search).forEach((r,i)=>{s.searchParams.set(i,r)}),s.searchParams.has("floorId")||s.searchParams.set("floorId",String(this.floorId)),e.src=s.toString(),e.style.cssText="width:100%;height:100%;border:none;background:transparent;",this.container.innerHTML="",this.container.appendChild(e),this.iframe=e,this._messageListener=t=>{if(this.targetOrigin!=="*"&&t.origin!==this.targetOrigin||!this.iframe||t.source!==this.iframe.contentWindow)return;let r=t.data;if(r){if(r.type==="SGS_MAP_EVENT"){let{action:i,payload:n}=r;i==="ON_POI_CLICK"?this.emit("poiClick",n):i==="ON_POI_DETAIL"?this.emit("poiDetail",n):i==="ON_POI_NAVIGATE"?this.emit("poiNavigate",n):i==="ON_POI_CLOSE"?this.emit("poiClose",n):i==="ON_SPACE_CLICK"?this.emit("spaceClick",n):i==="ON_SPACE_DETAIL"?this.emit("spaceDetail",n):i==="ON_SPACE_NAVIGATE"?this.emit("spaceNavigate",n):i==="ON_SPACE_CLOSE"?this.emit("spaceClose",n):i==="ENGINE_READY"?this._onEngineReady(n):i==="ON_FLOOR_CHANGED"?this.emit("floorChanged",n):i==="ON_ROUTE_TRANSFER"?this.emit("routeTransfer",n):i==="ON_MODEL_LOADING"?this.emit("modelLoading",n):i==="ON_MODEL_READY"?this.emit("modelReady",n):i==="ON_MODEL_ERROR"?this.emit("modelError",n):i==="ON_FLOOR_BUNDLE_READY"?this.emit("floorBundleReady",n):i==="ON_ROUTE_READY"?this.emit("routeReady",n):i==="ON_ROAMING_PROGRESS"?this.emit("roamingProgress",n):i==="ON_ROAMING_DONE"&&this.emit("roamingDone",n)}if(r.type==="SGS_MAP_RESPONSE"){let{action:i,requestId:n,payload:a,error:m}=r,c=this.promiseQueue[n];if(c){if(a&&a.success!==!1)c.resolve(a);else{let p=m||{code:"ERR_FAILED",message:`${i} command failed`};c.reject(p)}delete this.promiseQueue[n]}}}},window.addEventListener("message",this._messageListener),e.onload=()=>{if(this._iframeDomReadyMs=Date.now()-this._sdkCreatedAt,this._isReady)return;let t=()=>{if(this._isReady||this._isDestroyed){this._helloInterval&&(clearInterval(this._helloInterval),this._helloInterval=null);return}this._postCommand("HELLO",{protocolVersion:S,sdkVersion:g})};t(),this._helloInterval=setInterval(t,500),console.log(`[SGSMapSDK] Sent HELLO handshake (v${g}), will retry every 500ms until ENGINE_READY...`)},e.onerror=t=>{console.error("[SGSMapSDK] Iframe load error",t);let r={code:"ERR_IFRAME_LOAD",message:"Failed to load map engine iframe."};this.emit("loadError",{type:"iframe_error",detail:r.message}),this._rejectReady(r)},this._readyTimer=setTimeout(()=>{if(!this._isReady&&!this._isDestroyed){let t={code:"ERR_READY_TIMEOUT",message:`ENGINE_READY not received within ${this.readyTimeoutMs}ms`};console.error(`[SGSMapSDK] ${t.message}`),this._rejectReady(t)}},this.readyTimeoutMs)}_onEngineReady(e){this._isReady||(this._isReady=!0,this.readyPayload=e,this._engineReadyMs=Date.now()-this._sdkCreatedAt,this._helloInterval&&(clearInterval(this._helloInterval),this._helloInterval=null),this._readyTimer&&(clearTimeout(this._readyTimer),this._readyTimer=null),this._readyResolve&&(this._readyResolve(e),this._readyResolve=null,this._readyReject=null),this.emit("ready",e),console.log("[SGSMapSDK] Handshake complete: Engine Ready received \u2705"),this._flushPendingCommands())}_rejectReady(e){this._readyFailed=!0,this._readyFailedError=e,this._readyReject&&(this._readyReject(e),this._readyResolve=null,this._readyReject=null),this._readyTimer&&(clearTimeout(this._readyTimer),this._readyTimer=null),this._rejectAllPending(e.code,e.message),this._pendingFireAndForget.splice(0)}_flushPendingCommands(){let e=this._pendingFireAndForget.splice(0);for(let r of e)this._postCommand(r.action,r.payload);let s=this._pendingCommands.splice(0);for(let r of s)this._postCommandAsyncInner(r.action,r.payload,r.timeout).then(r.resolve).catch(r.reject);let t=e.length+s.length;t>0&&console.log(`[SGSMapSDK] Flushed ${t} pending commands after ready (${e.length} fire-and-forget, ${s.length} async)`)}_rejectAllPending(e,s){Object.keys(this.promiseQueue).forEach(r=>{let i=this.promiseQueue[r];i&&i.reject({code:e,message:s}),delete this.promiseQueue[r]});let t=this._pendingCommands.splice(0);for(let r of t)r.reject({code:e,message:s})}whenReady(){return this._readyPromise}ready(){return this._readyPromise}getIsReady(){return this._isReady}getCurrentFloor(){return this.floorId}getMapId(){return this.mapId}getVersion(){return g}_generateRequestId(){return`req_sdk_${Date.now()}_${Math.floor(Math.random()*1e5)}`}_postCommand(e,s,t){if(!this.iframe||!this.iframe.contentWindow){console.error("[SGSMapSDK] SDK Iframe is not available!");return}this.iframe.contentWindow.postMessage({type:"SGS_MAP_COMMAND",action:e,requestId:t,payload:s},this.targetOrigin)}_postCommandAsync(e,s,t=this.defaultTimeout){return this._isDestroyed?Promise.reject({code:"ERR_DESTROYED",message:"SGSMapSDK has been destroyed. Cannot send commands."}):this._readyFailed?Promise.reject(this._readyFailedError||{code:"ERR_NOT_READY",message:"SDK ready failed."}):this._isReady?this._postCommandAsyncInner(e,s,t):new Promise((r,i)=>{this._pendingCommands.push({action:e,payload:s,timeout:t,resolve:r,reject:i}),console.log(`[SGSMapSDK] Command "${e}" queued (waiting for ready). Queue size: ${this._pendingCommands.length}`)})}_postCommandSafe(e,s){if(this._isDestroyed){console.warn(`[SGSMapSDK] Command "${e}" ignored: SDK destroyed.`);return}if(this._readyFailed){console.warn(`[SGSMapSDK] Command "${e}" ignored: SDK ready failed.`);return}if(!this._isReady){this._pendingFireAndForget.push({action:e,payload:s}),console.log(`[SGSMapSDK] Command "${e}" queued (fire-and-forget, waiting for ready). Queue size: ${this._pendingFireAndForget.length}`);return}this._postCommand(e,s)}_postCommandAsyncInner(e,s,t=this.defaultTimeout){let r=this._generateRequestId();return new Promise((i,n)=>{let a=setTimeout(()=>{this.promiseQueue[r]&&(this.promiseQueue[r].reject({code:"ERR_TIMEOUT",message:`Command "${e}" timed out after ${t}ms`}),delete this.promiseQueue[r])},t);this.promiseQueue[r]={resolve:m=>{clearTimeout(a),i(m)},reject:m=>{clearTimeout(a),n(m)}},this._postCommand(e,s,r)})}focusTo(e){typeof e=="string"?this._postCommandSafe("FOCUS_TO",{nodeName:e}):this._postCommandSafe("FOCUS_TO",e)}changeFloor(e,s,t){return this._postCommandAsync("CHANGE_FLOOR",{floorId:e,...t},s).then(r=>(this.floorId=e,r))}addMarker(e){this._postCommandSafe("ADD_MARKER",e)}removeMarker(e){this._postCommandSafe("REMOVE_MARKER",{markerId:e})}clearMarkers(){this._postCommandSafe("CLEAR_MARKERS")}highlightPolygon(e){typeof e=="string"?this._postCommandSafe("HIGHLIGHT_POLYGON",{nodeName:e}):this._postCommandSafe("HIGHLIGHT_POLYGON",e)}planRoute(e,s,t,r=5e3){let i=typeof e=="string"?e:void 0,n=typeof s=="string"?s:void 0,a={};return i?a.startNode=i:a.from=e,n?a.endNode=n:a.to=s,t&&(a.options=t),this._postCommandAsync("PLAN_ROUTE",a,r)}planRouteV2(e,s,t,r=5e3){let i={from:e,to:s,options:t};return this._postCommandAsync("PLAN_ROUTE",i,r)}clearRoute(){this._postCommandSafe("CLEAR_ROUTE")}renderRoutePoints(e,s,t=12e3){return this._postCommandAsync("RENDER_ROUTE_POINTS",{...s,pathPoints:e},t)}startRoaming(e=[],s,t=12e3){return this._postCommandAsync("START_ROAMING",{...s,pathPoints:e},t)}stopRoaming(e){return this._postCommandAsync("STOP_ROAMING",{},e)}toggleRoamingPause(e){return this._postCommandAsync("TOGGLE_ROAMING_PAUSE",{},e)}planRouteToPoi(e,s,t,r=5e3){let n={from:typeof e=="string"||typeof e=="number"?{poiId:e}:e,toPoiId:s,wheelchair:t?.wheelchair?1:0,preferElevator:t?.preferElevator};return this._postCommandAsync("PLAN_ROUTE_TO_POI",n,r)}planRouteBetweenPois(e,s,t,r=5e3){let i={fromPoiId:e,toPoiId:s,wheelchair:t?.wheelchair?1:0,preferElevator:t?.preferElevator};return this._postCommandAsync("PLAN_ROUTE_BETWEEN_POIS",i,r)}getState(){return this._postCommandAsync("GET_STATE")}resetView(e){return this._postCommandAsync("RESET_VIEW",{},e)}setViewMode(e,s){return this._postCommandAsync("SET_VIEW_MODE",{mode:e},s)}toggleLayer(e,s,t){return this._postCommandAsync("TOGGLE_LAYER",{types:e,visible:s},t)}setVisibleLayers(e,s){return this._postCommandAsync("SET_VISIBLE_TYPES",{types:e},s)}setRouteViewActive(e,s){return this._postCommandAsync("SET_ROUTE_VIEW_ACTIVE",{active:e},s)}getManifest(e){return this._postCommandAsync("GET_MANIFEST",{},e)}loadFloorBundle(e,s){return this._postCommandAsync("LOAD_FLOOR_BUNDLE",{floorId:e},s)}getFloorPois(e,s){return this._postCommandAsync("GET_FLOOR_POIS",{floorId:e},s).then(t=>Array.isArray(t)?t:t?.pois||[])}getNavigablePlaces(e,s){return this._postCommandAsync("GET_NAVIGABLE_PLACES",{floorId:e},s).then(t=>Array.isArray(t)?t:t?.navigablePlaces||t?.places||[])}getPois(e,s){return this._postCommandAsync("GET_POIS",{floorCode:e},s)}getSpaces(e,s){return this._postCommandAsync("GET_SPACES",{floorId:e},s).then(t=>Array.isArray(t)?t:t?.spaces||[])}getGuideStops(e,s){return this._postCommandAsync("GET_GUIDE_STOPS",{floorId:e},s).then(t=>Array.isArray(t)?t:t?.guideStops||[])}getGuideStopsByHall(e,s){return this._postCommandAsync("GET_GUIDE_STOPS_BY_HALL",{hallId:e},s).then(t=>Array.isArray(t)?t:t?.guideStops||[])}preloadFloor(e,s){return this._postCommandAsync("PRELOAD_FLOOR",{floorId:e},s)}getPerformanceStats(e){return this._postCommandAsync("GET_PERFORMANCE_STATS",{},e).then(s=>({sdkCreatedAt:this._sdkCreatedAt,iframeDomReadyMs:this._iframeDomReadyMs??void 0,engineReadyMs:this._engineReadyMs??void 0,...s}))}getDiagnostics(e){return this._postCommandAsync("GET_DIAGNOSTICS",{},e)}getFloorDiagnostics(e,s){return this._postCommandAsync("GET_FLOOR_DIAGNOSTICS",{floorId:e},s)}clearModelCache(){this._postCommandSafe("CLEAR_MODEL_CACHE")}getBusinessPois(e,s,t){return this._postCommandAsync("GET_BUSINESS_POIS",{floorId:e,options:s},t).then(r=>Array.isArray(r)?r:r?.pois||[])}queryPois(e,s){return this._postCommandAsync("QUERY_POIS",e,s).then(t=>Array.isArray(t)?t:t?.pois||[])}getFeaturedRoutes(e,s,t){return this._postCommandAsync("GET_FEATURED_ROUTES",{mapId:e,options:s},t).then(r=>Array.isArray(r)?r:r?.routes||[])}getFeaturedRoute(e,s){return this._postCommandAsync("GET_FEATURED_ROUTE",{routeId:e},s).then(t=>t?.route||t)}planFeaturedRoutePreview(e,s=15e3){return this._postCommandAsync("PLAN_FEATURED_ROUTE_PREVIEW",e,s)}showFlowline(e,s,t){return this._postCommandAsync("SHOW_FLOWLINE",{routeId:e,options:s},t)}clearFlowline(){this._postCommandSafe("CLEAR_FLOWLINE")}getReleaseManifest(e){return console.warn("[SGSMapSDK] getReleaseManifest() is deprecated. Use getManifest() instead."),Promise.reject({code:"ERR_UNSUPPORTED",message:"getReleaseManifest() is deprecated. Use getManifest() instead."})}getModelResources(e){return console.warn("[SGSMapSDK] getModelResources() is deprecated."),Promise.reject({code:"ERR_UNSUPPORTED",message:"getModelResources() is deprecated."})}getLayerTree(e){return console.warn("[SGSMapSDK] getLayerTree() is deprecated."),Promise.reject({code:"ERR_UNSUPPORTED",message:"getLayerTree() is deprecated."})}loadMockReleaseRoute(e){return console.warn("[SGSMapSDK] loadMockReleaseRoute() is deprecated."),Promise.reject({code:"ERR_UNSUPPORTED",message:"loadMockReleaseRoute() is deprecated."})}destroy(){this._isDestroyed||(this._isDestroyed=!0,this._readyTimer&&(clearTimeout(this._readyTimer),this._readyTimer=null),this._helloInterval&&(clearInterval(this._helloInterval),this._helloInterval=null),this._readyReject&&(this._readyReject({code:"ERR_DESTROYED",message:"SGSMapSDK has been destroyed before ready."}),this._readyResolve=null,this._readyReject=null),this._rejectAllPending("ERR_DESTROYED","SGSMapSDK has been destroyed. Pending promises were rejected."),this.iframe&&(this.iframe.parentNode&&this.iframe.parentNode.removeChild(this.iframe),this.iframe=null),this._messageListener&&(window.removeEventListener("message",this._messageListener),this._messageListener=null),this.removeAllListeners(),this._isReady=!1,console.log("[SGSMapSDK] Destroyed successfully. Cleaned up iframe, listeners and pending queues."))}};l.utils=h;typeof window<"u"&&(window.SGSMapSDK=l);})(); //# sourceMappingURL=index.global.js.map \ No newline at end of file diff --git a/static/sgs-map-sdk/index.global.js.map b/static/sgs-map-sdk/index.global.js.map index 1fc895e..7f01be3 100644 --- a/static/sgs-map-sdk/index.global.js.map +++ b/static/sgs-map-sdk/index.global.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../sdk-src/event-emitter.ts","../../sdk-src/index.ts"],"sourcesContent":["/**\r\n * 轻量级类型安全事件发射器\r\n *\r\n *

SDK 内部事件系统基类,提供 on/off/once/emit/removeAllListeners/listenerCount。\r\n */\r\nexport class EventEmitter> {\r\n private events: { [K in keyof Events]?: Array<(payload: Events[K]) => void> };\r\n\r\n constructor() {\r\n this.events = {};\r\n }\r\n\r\n /** 注册事件监听器 */\r\n on(event: K, listener: (payload: Events[K]) => void): void {\r\n if (!this.events[event]) {\r\n this.events[event] = [];\r\n }\r\n this.events[event]!.push(listener);\r\n }\r\n\r\n /** 移除指定事件的指定监听器 */\r\n off(event: K, listener: (payload: Events[K]) => void): void {\r\n if (this.events[event]) {\r\n this.events[event] = this.events[event]!.filter(l => l !== listener);\r\n }\r\n }\r\n\r\n /** 注册一次性监听器,触发后自动移除 */\r\n once(event: K, listener: (payload: Events[K]) => void): void {\r\n const wrapper = (payload: Events[K]) => {\r\n this.off(event, wrapper);\r\n listener(payload);\r\n };\r\n this.on(event, wrapper);\r\n }\r\n\r\n /** 触发事件,通知所有已注册的监听器 */\r\n emit(event: K, data: Events[K]): void {\r\n if (this.events[event]) {\r\n // 浅拷贝防止 once 触发时修改数组导致跳过\r\n const listeners = [...this.events[event]!];\r\n listeners.forEach(l => {\r\n try {\r\n l(data);\r\n } catch (e) {\r\n console.error(`[EventEmitter] Error emitting event ${String(event)}:`, e);\r\n }\r\n });\r\n }\r\n }\r\n\r\n /** 移除指定事件的全部监听器;不传参则移除所有事件的所有监听器 */\r\n removeAllListeners(event?: K): void {\r\n if (event !== undefined) {\r\n delete this.events[event];\r\n } else {\r\n this.events = {};\r\n }\r\n }\r\n\r\n /** 返回指定事件的监听器数量 */\r\n listenerCount(event: K): number {\r\n return this.events[event]?.length ?? 0;\r\n }\r\n}\r\n","import { EventEmitter } from './event-emitter';\r\nimport {\r\n SGSMapSDKOptions,\r\n SGSMapEvents,\r\n RouteResult,\r\n SgsMapNode,\r\n MarkerConfig,\r\n SgsSdkManifest,\r\n SgsFloorBundle,\r\n SgsPoi,\r\n SgsSpaceArea,\r\n SgsGuideStop,\r\n SgsPerformanceStats,\r\n SgsNavigablePlace,\r\n SgsMapDiagnostics,\r\n SgsFloorDiagnostics,\r\n} from './types';\r\n\r\nexport * from './types';\r\n\r\n/** SDK 版本号常量 — 与后端 SdkMapServiceImpl.SDK_VERSION 对齐 */\r\nconst SDK_VERSION = '2.3.0';\r\n/** 协议版本号 */\r\nconst PROTOCOL_VERSION = 2;\r\n/** 默认 ready 超时(ms) */\r\nconst DEFAULT_READY_TIMEOUT = 15000;\r\n/** 默认命令超时(ms) */\r\nconst DEFAULT_CMD_TIMEOUT = 5000;\r\n\r\n/**\r\n * 命令队列条目 — ready 前调用的异步命令暂存于此\r\n */\r\ninterface PendingCommand {\r\n action: string;\r\n payload?: any;\r\n timeout: number;\r\n resolve: (data: any) => void;\r\n reject: (error: any) => void;\r\n}\r\n\r\n/**\r\n * SGS 3D 地图 H5 SDK 入口类\r\n *\r\n *

通过 iframe 嵌入 /h5-sdk 底座页面,以 postMessage 进行双向通信。\r\n *

所有需要后端数据的操作均通过 postMessage 指令由底座代理,\r\n * Demo / 宿主不允许直连后端 API。\r\n *\r\n * @version 2.3.0\r\n */\r\nexport default class SGSMapSDK extends EventEmitter {\r\n private container: HTMLElement;\r\n private sdkUrl: string;\r\n private floorId: string | number;\r\n private mapId: string | number;\r\n private targetOrigin: string;\r\n private iframe: HTMLIFrameElement | null = null;\r\n private _isReady: boolean = false;\r\n private _isDestroyed: boolean = false;\r\n /** ready 失败终态:iframe 加载失败或 ready 超时后置 true,后续命令直接 reject */\r\n private _readyFailed: boolean = false;\r\n /** ready 失败时的错误对象,用于后续命令的立即 reject */\r\n private _readyFailedError: { code: string; message: string } | null = null;\r\n private defaultTimeout: number;\r\n private readyTimeoutMs: number;\r\n /** 已 ready 后正在等待响应的命令队列 */\r\n private promiseQueue: {\r\n [key: string]: {\r\n resolve: (data: any) => void;\r\n reject: (error: any) => void;\r\n };\r\n } = {};\r\n /** ready 前暂存的异步命令队列 */\r\n private _pendingCommands: PendingCommand[] = [];\r\n /** ready 前暂存的 fire-and-forget 命令队列 */\r\n private _pendingFireAndForget: Array<{ action: string; payload?: any }> = [];\r\n private _messageListener: ((event: MessageEvent) => void) | null = null;\r\n /** ready 超时计时器 */\r\n private _readyTimer: ReturnType | null = null;\r\n\r\n /** ENGINE_READY 返回的 payload(供外部读取) */\r\n public readyPayload: any = null;\r\n\r\n /** ready Promise 的 resolve/reject,保证 whenReady() 返回同一个 Promise */\r\n private _readyResolve: ((value: any) => void) | null = null;\r\n private _readyReject: ((reason: any) => void) | null = null;\r\n private _readyPromise: Promise;\r\n\r\n /** 性能追踪字段 */\r\n private _sdkCreatedAt: number;\r\n private _iframeDomReadyMs: number | null = null;\r\n private _engineReadyMs: number | null = null;\r\n\r\n constructor(options: SGSMapSDKOptions) {\r\n super();\r\n this._sdkCreatedAt = Date.now();\r\n\r\n const containerEl =\r\n typeof options.container === 'string'\r\n ? document.getElementById(options.container)\r\n : options.container;\r\n\r\n if (!containerEl) {\r\n throw new Error(`[SGSMapSDK] Cannot find container: ${options.container}`);\r\n }\r\n\r\n this.container = containerEl;\r\n this.sdkUrl = options.sdkUrl || '/h5-sdk';\r\n this.floorId = options.floorId || 1;\r\n this.mapId = options.mapId || 1;\r\n this.defaultTimeout = options.timeout || DEFAULT_CMD_TIMEOUT;\r\n this.readyTimeoutMs = options.readyTimeout || DEFAULT_READY_TIMEOUT;\r\n\r\n this.targetOrigin = options.targetOrigin as string;\r\n if (!this.targetOrigin) {\r\n if (this.sdkUrl.startsWith('http')) {\r\n this.targetOrigin = new URL(this.sdkUrl).origin;\r\n } else if (typeof window !== 'undefined') {\r\n this.targetOrigin = window.location.origin;\r\n } else {\r\n this.targetOrigin = '*';\r\n }\r\n }\r\n\r\n // 初始化 ready Promise(全局唯一,whenReady() 返回同一个)\r\n this._readyPromise = new Promise((resolve, reject) => {\r\n this._readyResolve = resolve;\r\n this._readyReject = reject;\r\n });\r\n\r\n this._initIframeBridge();\r\n }\r\n\r\n // ==================== iframe 桥接初始化 ====================\r\n\r\n private _initIframeBridge() {\r\n const iframe = document.createElement('iframe');\r\n\r\n // 拷贝父级 URL 上的查询参数传递给 iframe 底座\r\n const iframeUrl = new URL(\r\n this.sdkUrl,\r\n typeof window !== 'undefined' ? window.location.href : 'http://localhost'\r\n );\r\n if (typeof window !== 'undefined') {\r\n const parentParams = new URLSearchParams(window.location.search);\r\n parentParams.forEach((value, key) => {\r\n iframeUrl.searchParams.set(key, value);\r\n });\r\n }\r\n // 附带 floorId\r\n if (!iframeUrl.searchParams.has('floorId')) {\r\n iframeUrl.searchParams.set('floorId', String(this.floorId));\r\n }\r\n\r\n iframe.src = iframeUrl.toString();\r\n iframe.style.cssText = 'width:100%;height:100%;border:none;background:transparent;';\r\n\r\n this.container.innerHTML = '';\r\n this.container.appendChild(iframe);\r\n this.iframe = iframe;\r\n\r\n // ── 消息监听 ──\r\n this._messageListener = (event: MessageEvent) => {\r\n if (this.targetOrigin !== '*' && event.origin !== this.targetOrigin) {\r\n return; // 静默忽略不匹配的来源\r\n }\r\n\r\n if (!this.iframe || event.source !== this.iframe.contentWindow) {\r\n return;\r\n }\r\n\r\n const data = event.data;\r\n if (!data) return;\r\n\r\n // ── 事件类型消息 ──\r\n if (data.type === 'SGS_MAP_EVENT') {\r\n const { action, payload } = data;\r\n if (action === 'ON_POI_CLICK') {\r\n this.emit('poiClick', payload);\r\n } else if (action === 'ENGINE_READY') {\r\n this._onEngineReady(payload);\r\n } else if (action === 'ON_FLOOR_CHANGED') {\r\n this.emit('floorChanged', payload);\r\n } else if (action === 'ON_ROUTE_TRANSFER') {\r\n this.emit('routeTransfer', payload);\r\n } else if (action === 'ON_MODEL_LOADING') {\r\n this.emit('modelLoading', payload);\r\n } else if (action === 'ON_MODEL_READY') {\r\n this.emit('modelReady', payload);\r\n } else if (action === 'ON_MODEL_ERROR') {\r\n this.emit('modelError', payload);\r\n } else if (action === 'ON_FLOOR_BUNDLE_READY') {\r\n this.emit('floorBundleReady', payload);\r\n } else if (action === 'ON_ROUTE_READY') {\r\n this.emit('routeReady', payload);\r\n }\r\n }\r\n\r\n // ── 命令响应消息 ──\r\n if (data.type === 'SGS_MAP_RESPONSE') {\r\n const { action, requestId, payload, error } = data;\r\n const resolver = this.promiseQueue[requestId];\r\n if (resolver) {\r\n if (payload && payload.success !== false) {\r\n resolver.resolve(payload);\r\n } else {\r\n const errObj = error || { code: 'ERR_FAILED', message: `${action} command failed` };\r\n resolver.reject(errObj);\r\n }\r\n delete this.promiseQueue[requestId];\r\n }\r\n }\r\n };\r\n\r\n window.addEventListener('message', this._messageListener);\r\n\r\n // ── iframe 加载完成 → 发送 HELLO 握手 ──\r\n iframe.onload = () => {\r\n this._iframeDomReadyMs = Date.now() - this._sdkCreatedAt;\r\n if (this._isReady) return;\r\n this._postCommand('HELLO', { protocolVersion: PROTOCOL_VERSION, sdkVersion: SDK_VERSION });\r\n console.log(`[SGSMapSDK] Sent HELLO handshake (v${SDK_VERSION}), waiting for ENGINE_READY...`);\r\n };\r\n\r\n // ── iframe 加载失败 → reject ready + 清空命令队列 ──\r\n iframe.onerror = (err) => {\r\n console.error('[SGSMapSDK] Iframe load error', err);\r\n const error = { code: 'ERR_IFRAME_LOAD' as const, message: 'Failed to load map engine iframe.' };\r\n this.emit('loadError', { type: 'iframe_error', detail: error.message });\r\n this._rejectReady(error);\r\n };\r\n\r\n // ── ready 超时计时器 ──\r\n this._readyTimer = setTimeout(() => {\r\n if (!this._isReady && !this._isDestroyed) {\r\n const error = {\r\n code: 'ERR_READY_TIMEOUT' as const,\r\n message: `ENGINE_READY not received within ${this.readyTimeoutMs}ms`\r\n };\r\n console.error(`[SGSMapSDK] ${error.message}`);\r\n this._rejectReady(error);\r\n }\r\n }, this.readyTimeoutMs);\r\n }\r\n\r\n // ==================== ready 生命周期管理 ====================\r\n\r\n /**\r\n * ENGINE_READY 到达时的处理\r\n */\r\n private _onEngineReady(payload: any) {\r\n if (this._isReady) return; // 防重入\r\n\r\n this._isReady = true;\r\n this.readyPayload = payload;\r\n this._engineReadyMs = Date.now() - this._sdkCreatedAt;\r\n\r\n // 清除 ready 超时计时器\r\n if (this._readyTimer) {\r\n clearTimeout(this._readyTimer);\r\n this._readyTimer = null;\r\n }\r\n\r\n // resolve ready Promise\r\n if (this._readyResolve) {\r\n this._readyResolve(payload);\r\n this._readyResolve = null;\r\n this._readyReject = null;\r\n }\r\n\r\n // 触发 ready 事件\r\n this.emit('ready', payload);\r\n console.log('[SGSMapSDK] Handshake complete: Engine Ready received ✅');\r\n\r\n // 排空命令队列 — ready 前暂存的命令逐个发送\r\n this._flushPendingCommands();\r\n }\r\n\r\n /**\r\n * reject ready Promise 并清空所有挂起命令,设置失败终态\r\n */\r\n private _rejectReady(error: { code: string; message: string }) {\r\n // 设置失败终态 — 后续所有新命令直接 reject\r\n this._readyFailed = true;\r\n this._readyFailedError = error;\r\n\r\n // reject ready Promise\r\n if (this._readyReject) {\r\n this._readyReject(error);\r\n this._readyResolve = null;\r\n this._readyReject = null;\r\n }\r\n\r\n // 清空 ready 超时计时器\r\n if (this._readyTimer) {\r\n clearTimeout(this._readyTimer);\r\n this._readyTimer = null;\r\n }\r\n\r\n // reject 所有命令队列 + 清空 fire-and-forget 队列\r\n this._rejectAllPending(error.code, error.message);\r\n this._pendingFireAndForget.splice(0);\r\n }\r\n\r\n /**\r\n * 排空 ready 前暂存的命令队列 — 逐个以 _postCommandAsync 发送\r\n */\r\n private _flushPendingCommands() {\r\n // 1. 排空 fire-and-forget 队列\r\n const fireAndForget = this._pendingFireAndForget.splice(0);\r\n for (const cmd of fireAndForget) {\r\n this._postCommand(cmd.action, cmd.payload);\r\n }\r\n\r\n // 2. 排空异步命令队列\r\n const pending = this._pendingCommands.splice(0);\r\n for (const cmd of pending) {\r\n this._postCommandAsyncInner(cmd.action, cmd.payload, cmd.timeout)\r\n .then(cmd.resolve)\r\n .catch(cmd.reject);\r\n }\r\n\r\n const total = fireAndForget.length + pending.length;\r\n if (total > 0) {\r\n console.log(`[SGSMapSDK] Flushed ${total} pending commands after ready (${fireAndForget.length} fire-and-forget, ${pending.length} async)`);\r\n }\r\n }\r\n\r\n /**\r\n * reject 所有挂起的 Promise 和命令队列\r\n */\r\n private _rejectAllPending(code: string, message: string) {\r\n // 1. reject 已发送的 promiseQueue\r\n Object.keys(this.promiseQueue).forEach((requestId) => {\r\n const resolver = this.promiseQueue[requestId];\r\n if (resolver) {\r\n resolver.reject({ code, message });\r\n }\r\n delete this.promiseQueue[requestId];\r\n });\r\n\r\n // 2. reject 命令队列\r\n const pending = this._pendingCommands.splice(0);\r\n for (const cmd of pending) {\r\n cmd.reject({ code, message });\r\n }\r\n }\r\n\r\n // ==================== 公共生命周期方法 ====================\r\n\r\n /**\r\n * 返回 ready Promise(全局唯一,多次调用返回同一个)。\r\n * 别名:ready()\r\n *\r\n * @example\r\n * await sdk.whenReady();\r\n * sdk.changeFloor(floorId);\r\n */\r\n whenReady(): Promise {\r\n return this._readyPromise;\r\n }\r\n\r\n /**\r\n * whenReady 的别名\r\n */\r\n ready(): Promise {\r\n return this._readyPromise;\r\n }\r\n\r\n /** 获取 ready 状态 */\r\n getIsReady(): boolean {\r\n return this._isReady;\r\n }\r\n\r\n /** 获取当前楼层 ID */\r\n getCurrentFloor(): string | number {\r\n return this.floorId;\r\n }\r\n\r\n /** 获取当前 mapId */\r\n getMapId(): string | number {\r\n return this.mapId;\r\n }\r\n\r\n /** 获取 SDK 版本号 */\r\n getVersion(): string {\r\n return SDK_VERSION;\r\n }\r\n\r\n // ==================== 核心通信方法 ====================\r\n\r\n private _generateRequestId(): string {\r\n return `req_sdk_${Date.now()}_${Math.floor(Math.random() * 100000)}`;\r\n }\r\n\r\n private _postCommand(action: string, payload?: any, requestId?: string | null) {\r\n if (!this.iframe || !this.iframe.contentWindow) {\r\n console.error('[SGSMapSDK] SDK Iframe is not available!');\r\n return;\r\n }\r\n\r\n this.iframe.contentWindow.postMessage(\r\n {\r\n type: 'SGS_MAP_COMMAND',\r\n action,\r\n requestId,\r\n payload\r\n },\r\n this.targetOrigin\r\n );\r\n }\r\n\r\n /**\r\n * 异步命令发送(外部入口)— 支持命令队列\r\n *\r\n *

状态机优先级:destroyed > readyFailed > !ready(入队) > ready(发送)\r\n */\r\n private _postCommandAsync(action: string, payload?: any, timeout: number = this.defaultTimeout): Promise {\r\n // destroyed 后直接 reject\r\n if (this._isDestroyed) {\r\n return Promise.reject({\r\n code: 'ERR_DESTROYED',\r\n message: 'SGSMapSDK has been destroyed. Cannot send commands.'\r\n });\r\n }\r\n\r\n // ready 失败终态 — 立即 reject,不再入队\r\n if (this._readyFailed) {\r\n return Promise.reject(\r\n this._readyFailedError || { code: 'ERR_NOT_READY', message: 'SDK ready failed.' }\r\n );\r\n }\r\n\r\n // ready 前入队\r\n if (!this._isReady) {\r\n return new Promise((resolve, reject) => {\r\n this._pendingCommands.push({\r\n action,\r\n payload,\r\n timeout,\r\n resolve,\r\n reject\r\n });\r\n console.log(`[SGSMapSDK] Command \"${action}\" queued (waiting for ready). Queue size: ${this._pendingCommands.length}`);\r\n });\r\n }\r\n\r\n // ready 后直接发送\r\n return this._postCommandAsyncInner(action, payload, timeout);\r\n }\r\n\r\n /**\r\n * fire-and-forget 命令发送(void 方法统一入口)\r\n *\r\n *

受 destroyed/readyFailed/!ready 状态保护:\r\n * - destroyed/readyFailed:静默丢弃 + console.warn\r\n * - !ready:暂存到 _pendingFireAndForget,ready 后自动发送\r\n * - ready:立即发送\r\n */\r\n private _postCommandSafe(action: string, payload?: any): void {\r\n if (this._isDestroyed) {\r\n console.warn(`[SGSMapSDK] Command \"${action}\" ignored: SDK destroyed.`);\r\n return;\r\n }\r\n if (this._readyFailed) {\r\n console.warn(`[SGSMapSDK] Command \"${action}\" ignored: SDK ready failed.`);\r\n return;\r\n }\r\n if (!this._isReady) {\r\n this._pendingFireAndForget.push({ action, payload });\r\n console.log(`[SGSMapSDK] Command \"${action}\" queued (fire-and-forget, waiting for ready). Queue size: ${this._pendingFireAndForget.length}`);\r\n return;\r\n }\r\n this._postCommand(action, payload);\r\n }\r\n\r\n /**\r\n * 异步命令发送(内部实现)— 已确认 ready\r\n */\r\n private _postCommandAsyncInner(action: string, payload?: any, timeout: number = this.defaultTimeout): Promise {\r\n const requestId = this._generateRequestId();\r\n return new Promise((resolve, reject) => {\r\n const timer = setTimeout(() => {\r\n if (this.promiseQueue[requestId]) {\r\n this.promiseQueue[requestId].reject({\r\n code: 'ERR_TIMEOUT',\r\n message: `Command \"${action}\" timed out after ${timeout}ms`\r\n });\r\n delete this.promiseQueue[requestId];\r\n }\r\n }, timeout);\r\n\r\n this.promiseQueue[requestId] = {\r\n resolve: (data) => {\r\n clearTimeout(timer);\r\n resolve(data as T);\r\n },\r\n reject: (error) => {\r\n clearTimeout(timer);\r\n reject(error);\r\n }\r\n };\r\n\r\n this._postCommand(action, payload, requestId);\r\n });\r\n }\r\n\r\n // ==================== v2.0 核心指令 API ====================\r\n\r\n /** 聚焦到指定要素节点 */\r\n focusTo(node: SgsMapNode | string) {\r\n if (typeof node === 'string') {\r\n this._postCommandSafe('FOCUS_TO', { nodeName: node });\r\n } else {\r\n this._postCommandSafe('FOCUS_TO', node);\r\n }\r\n }\r\n\r\n /** 切换楼层 */\r\n changeFloor(floorId: string | number, timeout?: number): Promise<{ success: boolean; floorId: string | number }> {\r\n return this._postCommandAsync<{ success: boolean; floorId: string | number }>('CHANGE_FLOOR', { floorId }, timeout).then((res) => {\r\n this.floorId = floorId;\r\n return res;\r\n });\r\n }\r\n\r\n /** 添加业务图钉 */\r\n addMarker(config: MarkerConfig) {\r\n this._postCommandSafe('ADD_MARKER', config);\r\n }\r\n\r\n /** 移除指定图钉 */\r\n removeMarker(markerId: string) {\r\n this._postCommandSafe('REMOVE_MARKER', { markerId });\r\n }\r\n\r\n /** 清空所有业务图钉 */\r\n clearMarkers() {\r\n this._postCommandSafe('CLEAR_MARKERS');\r\n }\r\n\r\n /** 高亮多边形轮廓 */\r\n highlightPolygon(nodeName: string) {\r\n this._postCommandSafe('HIGHLIGHT_POLYGON', { nodeName });\r\n }\r\n\r\n /** 寻路规划(v1 兼容) */\r\n planRoute(from: SgsMapNode | string, to: SgsMapNode | string, options?: any, timeout: number = 5000): Promise {\r\n const startNode = typeof from === 'string' ? from : undefined;\r\n const endNode = typeof to === 'string' ? to : undefined;\r\n\r\n const payload: any = {};\r\n if (startNode) payload.startNode = startNode;\r\n else payload.from = from;\r\n\r\n if (endNode) payload.endNode = endNode;\r\n else payload.to = to;\r\n\r\n if (options) payload.options = options;\r\n\r\n return this._postCommandAsync('PLAN_ROUTE', payload, timeout);\r\n }\r\n\r\n /** 寻路规划 V2(结构化参数) */\r\n planRouteV2(from: SgsMapNode, to: SgsMapNode, options?: any, timeout: number = 5000): Promise {\r\n const payload = { from, to, options };\r\n return this._postCommandAsync('PLAN_ROUTE', payload, timeout);\r\n }\r\n\r\n /** 清除当前路线 */\r\n clearRoute() {\r\n this._postCommandSafe('CLEAR_ROUTE');\r\n }\r\n\r\n /** 获取引擎状态 */\r\n getState(): Promise {\r\n return this._postCommandAsync('GET_STATE');\r\n }\r\n\r\n /** 重置视角 */\r\n resetView() {\r\n this._postCommandSafe('RESET_VIEW');\r\n }\r\n\r\n /** 设置 2D/3D 视图模式 */\r\n setViewMode(mode: '2d' | '3d') {\r\n this._postCommandSafe('SET_VIEW_MODE', { mode });\r\n }\r\n\r\n /** 切换图层可见性 */\r\n toggleLayer(types: string[], visible: boolean, timeout?: number): Promise<{ success: boolean; visibleTypes: string[] }> {\r\n return this._postCommandAsync<{ success: boolean; visibleTypes: string[] }>('TOGGLE_LAYER', { types, visible }, timeout);\r\n }\r\n\r\n /** 设置可见图层类型列表 */\r\n setVisibleLayers(types: string[], timeout?: number): Promise<{ success: boolean; visibleTypes: string[] }> {\r\n return this._postCommandAsync<{ success: boolean; visibleTypes: string[] }>('SET_VISIBLE_TYPES', { types }, timeout);\r\n }\r\n\r\n // ==================== v2.2.0 / v2.2.0 数据消费 API ====================\r\n\r\n /** 获取地图 Manifest 清单 */\r\n getManifest(timeout?: number): Promise {\r\n return this._postCommandAsync('GET_MANIFEST', {}, timeout);\r\n }\r\n\r\n /** 加载指定楼层的完整 Bundle 数据(含 POI/空间面/讲解点/路网) */\r\n loadFloorBundle(floorId: string | number, timeout?: number): Promise {\r\n return this._postCommandAsync('LOAD_FLOOR_BUNDLE', { floorId }, timeout);\r\n }\r\n\r\n /** 获取指定楼层的 POI 列表 */\r\n getFloorPois(floorId: string | number, timeout?: number): Promise {\r\n return this._postCommandAsync('GET_FLOOR_POIS', { floorId }, timeout)\r\n .then((res) => Array.isArray(res) ? res : (res?.pois || []));\r\n }\r\n\r\n /** 获取指定楼层的起终点候选目的地列表(门点/POI/空间等吸附后目的地) */\r\n getNavigablePlaces(floorId: string | number, timeout?: number): Promise {\r\n return this._postCommandAsync('GET_NAVIGABLE_PLACES', { floorId }, timeout)\r\n .then((res) => Array.isArray(res) ? res : (res?.navigablePlaces || res?.places || []));\r\n }\r\n\r\n /** 获取当前楼层的 POI 列表(根据 floorCode 参数) */\r\n getPois(floorCode: string, timeout?: number): Promise {\r\n return this._postCommandAsync('GET_POIS', { floorCode }, timeout);\r\n }\r\n\r\n /** 获取指定楼层的空间面列表 */\r\n getSpaces(floorId: string | number, timeout?: number): Promise {\r\n return this._postCommandAsync('GET_SPACES', { floorId }, timeout)\r\n .then((res) => Array.isArray(res) ? res : (res?.spaces || []));\r\n }\r\n\r\n /** 获取指定楼层的已标定讲解点列表(地图渲染入口) */\r\n getGuideStops(floorId: string | number, timeout?: number): Promise {\r\n return this._postCommandAsync('GET_GUIDE_STOPS', { floorId }, timeout)\r\n .then((res) => Array.isArray(res) ? res : (res?.guideStops || []));\r\n }\r\n\r\n /** 获取指定展厅的已标定讲解点列表(业务导览入口) */\r\n getGuideStopsByHall(hallId: string | number, timeout?: number): Promise {\r\n return this._postCommandAsync('GET_GUIDE_STOPS_BY_HALL', { hallId }, timeout)\r\n .then((res) => Array.isArray(res) ? res : (res?.guideStops || []));\r\n }\r\n\r\n /** 预加载指定楼层的模型资源(不渲染) */\r\n preloadFloor(floorId: string | number, timeout?: number): Promise<{ cached: boolean }> {\r\n return this._postCommandAsync<{ cached: boolean }>('PRELOAD_FLOOR', { floorId }, timeout);\r\n }\r\n\r\n /** 获取性能统计信息 */\r\n getPerformanceStats(timeout?: number): Promise {\r\n return this._postCommandAsync('GET_PERFORMANCE_STATS', {}, timeout).then(stats => ({\r\n sdkCreatedAt: this._sdkCreatedAt,\r\n iframeDomReadyMs: this._iframeDomReadyMs ?? undefined,\r\n engineReadyMs: this._engineReadyMs ?? undefined,\r\n ...stats\r\n }));\r\n }\r\n\r\n // ==================== v2.3.0 诊断接口 ====================\r\n\r\n /** 获取地图级诊断信息 */\r\n getDiagnostics(timeout?: number): Promise {\r\n return this._postCommandAsync('GET_DIAGNOSTICS', {}, timeout);\r\n }\r\n\r\n /** 获取楼层级诊断信息 */\r\n getFloorDiagnostics(floorId: string | number, timeout?: number): Promise {\r\n return this._postCommandAsync('GET_FLOOR_DIAGNOSTICS', { floorId }, timeout);\r\n }\r\n\r\n /** 清空客户端模型缓存 */\r\n clearModelCache(): void {\r\n this._postCommandSafe('CLEAR_MODEL_CACHE');\r\n }\r\n\r\n // ==================== @deprecated 兼容层 ====================\r\n // 以下方法已废弃,保留 API 签名避免外部接入方断裂。\r\n // 内部返回 ERR_UNSUPPORTED,不进入任何发布路径。\r\n\r\n /** @deprecated 使用 getManifest() 替代 */\r\n getReleaseManifest(timeout?: number): Promise {\r\n console.warn('[SGSMapSDK] getReleaseManifest() is deprecated. Use getManifest() instead.');\r\n return Promise.reject({ code: 'ERR_UNSUPPORTED', message: 'getReleaseManifest() is deprecated. Use getManifest() instead.' });\r\n }\r\n\r\n /** @deprecated 已废弃,无替代方法 */\r\n getModelResources(timeout?: number): Promise {\r\n console.warn('[SGSMapSDK] getModelResources() is deprecated.');\r\n return Promise.reject({ code: 'ERR_UNSUPPORTED', message: 'getModelResources() is deprecated.' });\r\n }\r\n\r\n /** @deprecated 已废弃,无替代方法 */\r\n getLayerTree(timeout?: number): Promise {\r\n console.warn('[SGSMapSDK] getLayerTree() is deprecated.');\r\n return Promise.reject({ code: 'ERR_UNSUPPORTED', message: 'getLayerTree() is deprecated.' });\r\n }\r\n\r\n /** @deprecated 已废弃,无替代方法 */\r\n loadMockReleaseRoute(timeout?: number): Promise {\r\n console.warn('[SGSMapSDK] loadMockReleaseRoute() is deprecated.');\r\n return Promise.reject({ code: 'ERR_UNSUPPORTED', message: 'loadMockReleaseRoute() is deprecated.' });\r\n }\r\n\r\n // ==================== 销毁 ====================\r\n\r\n /**\r\n * 销毁 SDK 实例\r\n *\r\n *

清理 iframe、移除事件监听器、reject 所有挂起的 Promise。\r\n *

销毁后调用任何命令方法都会 reject ERR_DESTROYED。\r\n */\r\n destroy() {\r\n if (this._isDestroyed) return; // 防重复销毁\r\n this._isDestroyed = true;\r\n\r\n // 清除 ready 超时计时器\r\n if (this._readyTimer) {\r\n clearTimeout(this._readyTimer);\r\n this._readyTimer = null;\r\n }\r\n\r\n // reject ready Promise(如果还没 resolve/reject)\r\n if (this._readyReject) {\r\n this._readyReject({\r\n code: 'ERR_DESTROYED',\r\n message: 'SGSMapSDK has been destroyed before ready.'\r\n });\r\n this._readyResolve = null;\r\n this._readyReject = null;\r\n }\r\n\r\n // reject 所有挂起的命令\r\n this._rejectAllPending('ERR_DESTROYED', 'SGSMapSDK has been destroyed. Pending promises were rejected.');\r\n\r\n // 移除 iframe\r\n if (this.iframe) {\r\n if (this.iframe.parentNode) {\r\n this.iframe.parentNode.removeChild(this.iframe);\r\n }\r\n this.iframe = null;\r\n }\r\n\r\n // 移除事件监听器\r\n if (this._messageListener) {\r\n window.removeEventListener('message', this._messageListener);\r\n this._messageListener = null;\r\n }\r\n\r\n // 移除所有 EventEmitter 监听器\r\n this.removeAllListeners();\r\n\r\n this._isReady = false;\r\n console.log('[SGSMapSDK] Destroyed successfully. Cleaned up iframe, listeners and pending queues.');\r\n }\r\n}\r\n\r\n// 全局暴露(IIFE 产物使用)\r\nif (typeof window !== 'undefined') {\r\n (window as any).SGSMapSDK = SGSMapSDK;\r\n}\r\n"],"mappings":"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","names":["EventEmitter","event","listener","l","wrapper","payload","data","e","SDK_VERSION","PROTOCOL_VERSION","DEFAULT_READY_TIMEOUT","DEFAULT_CMD_TIMEOUT","SGSMapSDK","EventEmitter","options","containerEl","resolve","reject","iframe","iframeUrl","value","key","event","data","action","payload","requestId","error","resolver","errObj","err","fireAndForget","cmd","pending","total","code","message","timeout","timer","node","floorId","res","config","markerId","nodeName","from","to","startNode","endNode","mode","types","visible","floorCode","hallId","stats"]} \ No newline at end of file +{"version":3,"sources":["../../sdk-src/event-emitter.ts","../../sdk-src/utils.ts","../../sdk-src/index.ts"],"sourcesContent":["/**\r\n * 轻量级类型安全事件发射器\r\n *\r\n *

SDK 内部事件系统基类,提供 on/off/once/emit/removeAllListeners/listenerCount。\r\n */\r\nexport class EventEmitter> {\r\n private events: { [K in keyof Events]?: Array<(payload: Events[K]) => void> };\r\n\r\n constructor() {\r\n this.events = {};\r\n }\r\n\r\n /** 注册事件监听器 */\r\n on(event: K, listener: (payload: Events[K]) => void): void {\r\n if (!this.events[event]) {\r\n this.events[event] = [];\r\n }\r\n this.events[event]!.push(listener);\r\n }\r\n\r\n /** 移除指定事件的指定监听器 */\r\n off(event: K, listener: (payload: Events[K]) => void): void {\r\n if (this.events[event]) {\r\n this.events[event] = this.events[event]!.filter(l => l !== listener);\r\n }\r\n }\r\n\r\n /** 注册一次性监听器,触发后自动移除 */\r\n once(event: K, listener: (payload: Events[K]) => void): void {\r\n const wrapper = (payload: Events[K]) => {\r\n this.off(event, wrapper);\r\n listener(payload);\r\n };\r\n this.on(event, wrapper);\r\n }\r\n\r\n /** 触发事件,通知所有已注册的监听器 */\r\n emit(event: K, data: Events[K]): void {\r\n if (this.events[event]) {\r\n // 浅拷贝防止 once 触发时修改数组导致跳过\r\n const listeners = [...this.events[event]!];\r\n listeners.forEach(l => {\r\n try {\r\n l(data);\r\n } catch (e) {\r\n console.error(`[EventEmitter] Error emitting event ${String(event)}:`, e);\r\n }\r\n });\r\n }\r\n }\r\n\r\n /** 移除指定事件的全部监听器;不传参则移除所有事件的所有监听器 */\r\n removeAllListeners(event?: K): void {\r\n if (event !== undefined) {\r\n delete this.events[event];\r\n } else {\r\n this.events = {};\r\n }\r\n }\r\n\r\n /** 返回指定事件的监听器数量 */\r\n listenerCount(event: K): number {\r\n return this.events[event]?.length ?? 0;\r\n }\r\n}\r\n","/**\r\n * 过滤 EXTERIOR 楼层,检查楼层是否是室内可导航楼层\r\n */\r\nexport function isIndoorNavigableFloor(floorCode: string): boolean {\r\n if (!floorCode) return false;\r\n return floorCode.toUpperCase() !== 'EXTERIOR';\r\n}\r\n\r\n/**\r\n * 物理楼层顺序由低到高排序\r\n */\r\nexport function sortIndoorFloors(floors: T[]): T[] {\r\n const FLOOR_ORDER = [\"L-2\", \"L-1\", \"B2\", \"B1\", \"L1\", \"L1.5\", \"L2\", \"L3\", \"L4\", \"L5\"];\r\n \r\n const indoorFloors = floors.filter(f => isIndoorNavigableFloor(f.floorCode));\r\n\r\n const getFloorIndex = (floorCode: string): number => {\r\n let code = floorCode.trim().toUpperCase();\r\n if (code.startsWith('F')) {\r\n code = 'L' + code.substring(1);\r\n }\r\n const matchedIndex = FLOOR_ORDER.findIndex(f => f.toUpperCase() === code);\r\n if (matchedIndex !== -1) return matchedIndex;\r\n \r\n const numMatch = code.match(/-?\\d+(\\.\\d+)?/);\r\n if (numMatch) {\r\n const val = parseFloat(numMatch[0]);\r\n if (code.startsWith('B') || code.includes('-')) {\r\n return -Math.abs(val) - 100;\r\n }\r\n return val;\r\n }\r\n return 0;\r\n };\r\n\r\n return [...indoorFloors].sort((a, b) => getFloorIndex(a.floorCode) - getFloorIndex(b.floorCode));\r\n}\r\n","import { EventEmitter } from './event-emitter';\r\nimport {\r\n SGSMapSDKOptions,\r\n SGSMapEvents,\r\n RouteResult,\r\n SgsMapNode,\r\n MarkerConfig,\r\n SgsSdkManifest,\r\n SgsFloorBundle,\r\n SgsPoi,\r\n SgsSpaceArea,\r\n SgsGuideStop,\r\n SgsPerformanceStats,\r\n SgsNavigablePlace,\r\n SgsMapDiagnostics,\r\n SgsFloorDiagnostics,\r\n SgsPoiQueryParams,\r\n SgsFeaturedRouteSummary,\r\n SgsFeaturedRouteDetail,\r\n SgsFeaturedRoutePreviewPayload,\r\n SgsFlowlineOptions,\r\n SgsRouteRoamingOptions,\r\n RouteOptions,\r\n SdkRoutePoint,\r\n} from './types';\r\n\r\nexport * from './types';\r\n\r\nimport * as utils from './utils';\r\n\r\n/** SDK 版本号常量 — 与后端 SdkMapServiceImpl.SDK_VERSION 对齐 */\r\nconst SDK_VERSION = '2.4.1';\r\n/** 协议版本号 */\r\nconst PROTOCOL_VERSION = 2;\r\n/** 默认 ready 超时(ms) */\r\nconst DEFAULT_READY_TIMEOUT = 30000; // 🔧 P0: 从 15s 提升到 30s,配合延迟 ENGINE_READY 策略\r\n/** 默认命令超时(ms) */\r\nconst DEFAULT_CMD_TIMEOUT = 5000;\r\n\r\n/**\r\n * 命令队列条目 — ready 前调用的异步命令暂存于此\r\n */\r\ninterface PendingCommand {\r\n action: string;\r\n payload?: any;\r\n timeout: number;\r\n resolve: (data: any) => void;\r\n reject: (error: any) => void;\r\n}\r\n\r\n/**\r\n * SGS 3D 地图 H5 SDK 入口类\r\n *\r\n *

通过 iframe 嵌入 /h5-sdk 底座页面,以 postMessage 进行双向通信。\r\n *

所有需要后端数据的操作均通过 postMessage 指令由底座代理,\r\n * Demo / 宿主不允许直连后端 API。\r\n *\r\n * @version 2.4.1\r\n */\r\nexport default class SGSMapSDK extends EventEmitter {\r\n public static utils = utils;\r\n private container: HTMLElement;\r\n private sdkUrl: string;\r\n private floorId: string | number;\r\n private mapId: string | number;\r\n private targetOrigin: string;\r\n private iframe: HTMLIFrameElement | null = null;\r\n private _isReady: boolean = false;\r\n private _isDestroyed: boolean = false;\r\n /** ready 失败终态:iframe 加载失败或 ready 超时后置 true,后续命令直接 reject */\r\n private _readyFailed: boolean = false;\r\n /** ready 失败时的错误对象,用于后续命令的立即 reject */\r\n private _readyFailedError: { code: string; message: string } | null = null;\r\n private defaultTimeout: number;\r\n private readyTimeoutMs: number;\r\n /** 已 ready 后正在等待响应的命令队列 */\r\n private promiseQueue: {\r\n [key: string]: {\r\n resolve: (data: any) => void;\r\n reject: (error: any) => void;\r\n };\r\n } = {};\r\n /** ready 前暂存的异步命令队列 */\r\n private _pendingCommands: PendingCommand[] = [];\r\n /** ready 前暂存的 fire-and-forget 命令队列 */\r\n private _pendingFireAndForget: Array<{ action: string; payload?: any }> = [];\r\n private _messageListener: ((event: MessageEvent) => void) | null = null;\r\n /** ready 超时计时器 */\r\n private _readyTimer: ReturnType | null = null;\r\n /** 🔧 P0: HELLO 重试定时器 — 解决 iframe onload 与 React useEffect 时序竞争 */\r\n private _helloInterval: ReturnType | null = null;\r\n\r\n /** ENGINE_READY 返回的 payload(供外部读取) */\r\n public readyPayload: any = null;\r\n\r\n /** ready Promise 的 resolve/reject,保证 whenReady() 返回同一个 Promise */\r\n private _readyResolve: ((value: any) => void) | null = null;\r\n private _readyReject: ((reason: any) => void) | null = null;\r\n private _readyPromise: Promise;\r\n\r\n /** 性能追踪字段 */\r\n private _sdkCreatedAt: number;\r\n private _iframeDomReadyMs: number | null = null;\r\n private _engineReadyMs: number | null = null;\r\n\r\n constructor(options: SGSMapSDKOptions) {\r\n super();\r\n this._sdkCreatedAt = Date.now();\r\n\r\n const containerEl =\r\n typeof options.container === 'string'\r\n ? document.getElementById(options.container)\r\n : options.container;\r\n\r\n if (!containerEl) {\r\n throw new Error(`[SGSMapSDK] Cannot find container: ${options.container}`);\r\n }\r\n\r\n this.container = containerEl;\r\n this.sdkUrl = options.sdkUrl || '/h5-sdk';\r\n this.floorId = options.floorId || 1;\r\n this.mapId = options.mapId || 1;\r\n this.defaultTimeout = options.timeout || DEFAULT_CMD_TIMEOUT;\r\n this.readyTimeoutMs = options.readyTimeout || DEFAULT_READY_TIMEOUT;\r\n\r\n this.targetOrigin = options.targetOrigin as string;\r\n if (!this.targetOrigin) {\r\n if (this.sdkUrl.startsWith('http')) {\r\n this.targetOrigin = new URL(this.sdkUrl).origin;\r\n } else if (typeof window !== 'undefined') {\r\n this.targetOrigin = window.location.origin;\r\n } else {\r\n this.targetOrigin = '*';\r\n }\r\n }\r\n\r\n // 初始化 ready Promise(全局唯一,whenReady() 返回同一个)\r\n this._readyPromise = new Promise((resolve, reject) => {\r\n this._readyResolve = resolve;\r\n this._readyReject = reject;\r\n });\r\n\r\n this._initIframeBridge();\r\n }\r\n\r\n // ==================== iframe 桥接初始化 ====================\r\n\r\n private _initIframeBridge() {\r\n const iframe = document.createElement('iframe');\r\n\r\n // 拷贝父级 URL 上的查询参数传递给 iframe 底座\r\n const iframeUrl = new URL(\r\n this.sdkUrl,\r\n typeof window !== 'undefined' ? window.location.href : 'http://localhost'\r\n );\r\n if (typeof window !== 'undefined') {\r\n const parentParams = new URLSearchParams(window.location.search);\r\n parentParams.forEach((value, key) => {\r\n iframeUrl.searchParams.set(key, value);\r\n });\r\n }\r\n // 附带 floorId\r\n if (!iframeUrl.searchParams.has('floorId')) {\r\n iframeUrl.searchParams.set('floorId', String(this.floorId));\r\n }\r\n\r\n iframe.src = iframeUrl.toString();\r\n iframe.style.cssText = 'width:100%;height:100%;border:none;background:transparent;';\r\n\r\n this.container.innerHTML = '';\r\n this.container.appendChild(iframe);\r\n this.iframe = iframe;\r\n\r\n // ── 消息监听 ──\r\n this._messageListener = (event: MessageEvent) => {\r\n if (this.targetOrigin !== '*' && event.origin !== this.targetOrigin) {\r\n return; // 静默忽略不匹配的来源\r\n }\r\n\r\n if (!this.iframe || event.source !== this.iframe.contentWindow) {\r\n return;\r\n }\r\n\r\n const data = event.data;\r\n if (!data) return;\r\n\r\n // ── 事件类型消息 ──\r\n if (data.type === 'SGS_MAP_EVENT') {\r\n const { action, payload } = data;\r\n if (action === 'ON_POI_CLICK') {\r\n this.emit('poiClick', payload);\r\n } else if (action === 'ON_POI_DETAIL') {\r\n this.emit('poiDetail', payload);\r\n } else if (action === 'ON_POI_NAVIGATE') {\r\n this.emit('poiNavigate', payload);\r\n } else if (action === 'ON_POI_CLOSE') {\r\n this.emit('poiClose', payload);\r\n } else if (action === 'ON_SPACE_CLICK') {\r\n this.emit('spaceClick', payload);\r\n } else if (action === 'ON_SPACE_DETAIL') {\r\n this.emit('spaceDetail', payload);\r\n } else if (action === 'ON_SPACE_NAVIGATE') {\r\n this.emit('spaceNavigate', payload);\r\n } else if (action === 'ON_SPACE_CLOSE') {\r\n this.emit('spaceClose', payload);\r\n } else if (action === 'ENGINE_READY') {\r\n this._onEngineReady(payload);\r\n } else if (action === 'ON_FLOOR_CHANGED') {\r\n this.emit('floorChanged', payload);\r\n } else if (action === 'ON_ROUTE_TRANSFER') {\r\n this.emit('routeTransfer', payload);\r\n } else if (action === 'ON_MODEL_LOADING') {\r\n this.emit('modelLoading', payload);\r\n } else if (action === 'ON_MODEL_READY') {\r\n this.emit('modelReady', payload);\r\n } else if (action === 'ON_MODEL_ERROR') {\r\n this.emit('modelError', payload);\r\n } else if (action === 'ON_FLOOR_BUNDLE_READY') {\r\n this.emit('floorBundleReady', payload);\r\n } else if (action === 'ON_ROUTE_READY') {\r\n this.emit('routeReady', payload);\r\n } else if (action === 'ON_ROAMING_PROGRESS') {\r\n this.emit('roamingProgress', payload);\r\n } else if (action === 'ON_ROAMING_DONE') {\r\n this.emit('roamingDone', payload);\r\n }\r\n }\r\n\r\n // ── 命令响应消息 ──\r\n if (data.type === 'SGS_MAP_RESPONSE') {\r\n const { action, requestId, payload, error } = data;\r\n const resolver = this.promiseQueue[requestId];\r\n if (resolver) {\r\n if (payload && payload.success !== false) {\r\n resolver.resolve(payload);\r\n } else {\r\n const errObj = error || { code: 'ERR_FAILED', message: `${action} command failed` };\r\n resolver.reject(errObj);\r\n }\r\n delete this.promiseQueue[requestId];\r\n }\r\n }\r\n };\r\n\r\n window.addEventListener('message', this._messageListener);\r\n\r\n // ── iframe 加载完成 → 发送 HELLO 握手 ──\r\n iframe.onload = () => {\r\n this._iframeDomReadyMs = Date.now() - this._sdkCreatedAt;\r\n if (this._isReady) return;\r\n\r\n // 🔧 P0 修复:HELLO 重试机制\r\n // iframe onload 触发时 React useEffect 可能尚未挂载 message listener,\r\n // 单次 HELLO 会丢失。改为每 500ms 重试,直到 ENGINE_READY 到达。\r\n const sendHello = () => {\r\n if (this._isReady || this._isDestroyed) {\r\n if (this._helloInterval) { clearInterval(this._helloInterval); this._helloInterval = null; }\r\n return;\r\n }\r\n this._postCommand('HELLO', { protocolVersion: PROTOCOL_VERSION, sdkVersion: SDK_VERSION });\r\n };\r\n sendHello(); // 立即发第一次\r\n this._helloInterval = setInterval(sendHello, 500);\r\n console.log(`[SGSMapSDK] Sent HELLO handshake (v${SDK_VERSION}), will retry every 500ms until ENGINE_READY...`);\r\n };\r\n\r\n // ── iframe 加载失败 → reject ready + 清空命令队列 ──\r\n iframe.onerror = (err) => {\r\n console.error('[SGSMapSDK] Iframe load error', err);\r\n const error = { code: 'ERR_IFRAME_LOAD' as const, message: 'Failed to load map engine iframe.' };\r\n this.emit('loadError', { type: 'iframe_error', detail: error.message });\r\n this._rejectReady(error);\r\n };\r\n\r\n // ── ready 超时计时器 ──\r\n this._readyTimer = setTimeout(() => {\r\n if (!this._isReady && !this._isDestroyed) {\r\n const error = {\r\n code: 'ERR_READY_TIMEOUT' as const,\r\n message: `ENGINE_READY not received within ${this.readyTimeoutMs}ms`\r\n };\r\n console.error(`[SGSMapSDK] ${error.message}`);\r\n this._rejectReady(error);\r\n }\r\n }, this.readyTimeoutMs);\r\n }\r\n\r\n // ==================== ready 生命周期管理 ====================\r\n\r\n /**\r\n * ENGINE_READY 到达时的处理\r\n */\r\n private _onEngineReady(payload: any) {\r\n if (this._isReady) return; // 防重入\r\n\r\n this._isReady = true;\r\n this.readyPayload = payload;\r\n this._engineReadyMs = Date.now() - this._sdkCreatedAt;\r\n\r\n // 🔧 P0: 清除 HELLO 重试定时器\r\n if (this._helloInterval) {\r\n clearInterval(this._helloInterval);\r\n this._helloInterval = null;\r\n }\r\n\r\n // 清除 ready 超时计时器\r\n if (this._readyTimer) {\r\n clearTimeout(this._readyTimer);\r\n this._readyTimer = null;\r\n }\r\n\r\n // resolve ready Promise\r\n if (this._readyResolve) {\r\n this._readyResolve(payload);\r\n this._readyResolve = null;\r\n this._readyReject = null;\r\n }\r\n\r\n // 触发 ready 事件\r\n this.emit('ready', payload);\r\n console.log('[SGSMapSDK] Handshake complete: Engine Ready received ✅');\r\n\r\n // 排空命令队列 — ready 前暂存的命令逐个发送\r\n this._flushPendingCommands();\r\n }\r\n\r\n /**\r\n * reject ready Promise 并清空所有挂起命令,设置失败终态\r\n */\r\n private _rejectReady(error: { code: string; message: string }) {\r\n // 设置失败终态 — 后续所有新命令直接 reject\r\n this._readyFailed = true;\r\n this._readyFailedError = error;\r\n\r\n // reject ready Promise\r\n if (this._readyReject) {\r\n this._readyReject(error);\r\n this._readyResolve = null;\r\n this._readyReject = null;\r\n }\r\n\r\n // 清空 ready 超时计时器\r\n if (this._readyTimer) {\r\n clearTimeout(this._readyTimer);\r\n this._readyTimer = null;\r\n }\r\n\r\n // reject 所有命令队列 + 清空 fire-and-forget 队列\r\n this._rejectAllPending(error.code, error.message);\r\n this._pendingFireAndForget.splice(0);\r\n }\r\n\r\n /**\r\n * 排空 ready 前暂存的命令队列 — 逐个以 _postCommandAsync 发送\r\n */\r\n private _flushPendingCommands() {\r\n // 1. 排空 fire-and-forget 队列\r\n const fireAndForget = this._pendingFireAndForget.splice(0);\r\n for (const cmd of fireAndForget) {\r\n this._postCommand(cmd.action, cmd.payload);\r\n }\r\n\r\n // 2. 排空异步命令队列\r\n const pending = this._pendingCommands.splice(0);\r\n for (const cmd of pending) {\r\n this._postCommandAsyncInner(cmd.action, cmd.payload, cmd.timeout)\r\n .then(cmd.resolve)\r\n .catch(cmd.reject);\r\n }\r\n\r\n const total = fireAndForget.length + pending.length;\r\n if (total > 0) {\r\n console.log(`[SGSMapSDK] Flushed ${total} pending commands after ready (${fireAndForget.length} fire-and-forget, ${pending.length} async)`);\r\n }\r\n }\r\n\r\n /**\r\n * reject 所有挂起的 Promise 和命令队列\r\n */\r\n private _rejectAllPending(code: string, message: string) {\r\n // 1. reject 已发送的 promiseQueue\r\n Object.keys(this.promiseQueue).forEach((requestId) => {\r\n const resolver = this.promiseQueue[requestId];\r\n if (resolver) {\r\n resolver.reject({ code, message });\r\n }\r\n delete this.promiseQueue[requestId];\r\n });\r\n\r\n // 2. reject 命令队列\r\n const pending = this._pendingCommands.splice(0);\r\n for (const cmd of pending) {\r\n cmd.reject({ code, message });\r\n }\r\n }\r\n\r\n // ==================== 公共生命周期方法 ====================\r\n\r\n /**\r\n * 返回 ready Promise(全局唯一,多次调用返回同一个)。\r\n * 别名:ready()\r\n *\r\n * @example\r\n * await sdk.whenReady();\r\n * sdk.changeFloor(floorId);\r\n */\r\n whenReady(): Promise {\r\n return this._readyPromise;\r\n }\r\n\r\n /**\r\n * whenReady 的别名\r\n */\r\n ready(): Promise {\r\n return this._readyPromise;\r\n }\r\n\r\n /** 获取 ready 状态 */\r\n getIsReady(): boolean {\r\n return this._isReady;\r\n }\r\n\r\n /** 获取当前楼层 ID */\r\n getCurrentFloor(): string | number {\r\n return this.floorId;\r\n }\r\n\r\n /** 获取当前 mapId */\r\n getMapId(): string | number {\r\n return this.mapId;\r\n }\r\n\r\n /** 获取 SDK 版本号 */\r\n getVersion(): string {\r\n return SDK_VERSION;\r\n }\r\n\r\n // ==================== 核心通信方法 ====================\r\n\r\n private _generateRequestId(): string {\r\n return `req_sdk_${Date.now()}_${Math.floor(Math.random() * 100000)}`;\r\n }\r\n\r\n private _postCommand(action: string, payload?: any, requestId?: string | null) {\r\n if (!this.iframe || !this.iframe.contentWindow) {\r\n console.error('[SGSMapSDK] SDK Iframe is not available!');\r\n return;\r\n }\r\n\r\n this.iframe.contentWindow.postMessage(\r\n {\r\n type: 'SGS_MAP_COMMAND',\r\n action,\r\n requestId,\r\n payload\r\n },\r\n this.targetOrigin\r\n );\r\n }\r\n\r\n /**\r\n * 异步命令发送(外部入口)— 支持命令队列\r\n *\r\n *

状态机优先级:destroyed > readyFailed > !ready(入队) > ready(发送)\r\n */\r\n private _postCommandAsync(action: string, payload?: any, timeout: number = this.defaultTimeout): Promise {\r\n // destroyed 后直接 reject\r\n if (this._isDestroyed) {\r\n return Promise.reject({\r\n code: 'ERR_DESTROYED',\r\n message: 'SGSMapSDK has been destroyed. Cannot send commands.'\r\n });\r\n }\r\n\r\n // ready 失败终态 — 立即 reject,不再入队\r\n if (this._readyFailed) {\r\n return Promise.reject(\r\n this._readyFailedError || { code: 'ERR_NOT_READY', message: 'SDK ready failed.' }\r\n );\r\n }\r\n\r\n // ready 前入队\r\n if (!this._isReady) {\r\n return new Promise((resolve, reject) => {\r\n this._pendingCommands.push({\r\n action,\r\n payload,\r\n timeout,\r\n resolve,\r\n reject\r\n });\r\n console.log(`[SGSMapSDK] Command \"${action}\" queued (waiting for ready). Queue size: ${this._pendingCommands.length}`);\r\n });\r\n }\r\n\r\n // ready 后直接发送\r\n return this._postCommandAsyncInner(action, payload, timeout);\r\n }\r\n\r\n /**\r\n * fire-and-forget 命令发送(void 方法统一入口)\r\n *\r\n *

受 destroyed/readyFailed/!ready 状态保护:\r\n * - destroyed/readyFailed:静默丢弃 + console.warn\r\n * - !ready:暂存到 _pendingFireAndForget,ready 后自动发送\r\n * - ready:立即发送\r\n */\r\n private _postCommandSafe(action: string, payload?: any): void {\r\n if (this._isDestroyed) {\r\n console.warn(`[SGSMapSDK] Command \"${action}\" ignored: SDK destroyed.`);\r\n return;\r\n }\r\n if (this._readyFailed) {\r\n console.warn(`[SGSMapSDK] Command \"${action}\" ignored: SDK ready failed.`);\r\n return;\r\n }\r\n if (!this._isReady) {\r\n this._pendingFireAndForget.push({ action, payload });\r\n console.log(`[SGSMapSDK] Command \"${action}\" queued (fire-and-forget, waiting for ready). Queue size: ${this._pendingFireAndForget.length}`);\r\n return;\r\n }\r\n this._postCommand(action, payload);\r\n }\r\n\r\n /**\r\n * 异步命令发送(内部实现)— 已确认 ready\r\n */\r\n private _postCommandAsyncInner(action: string, payload?: any, timeout: number = this.defaultTimeout): Promise {\r\n const requestId = this._generateRequestId();\r\n return new Promise((resolve, reject) => {\r\n const timer = setTimeout(() => {\r\n if (this.promiseQueue[requestId]) {\r\n this.promiseQueue[requestId].reject({\r\n code: 'ERR_TIMEOUT',\r\n message: `Command \"${action}\" timed out after ${timeout}ms`\r\n });\r\n delete this.promiseQueue[requestId];\r\n }\r\n }, timeout);\r\n\r\n this.promiseQueue[requestId] = {\r\n resolve: (data) => {\r\n clearTimeout(timer);\r\n resolve(data as T);\r\n },\r\n reject: (error) => {\r\n clearTimeout(timer);\r\n reject(error);\r\n }\r\n };\r\n\r\n this._postCommand(action, payload, requestId);\r\n });\r\n }\r\n\r\n // ==================== v2.0 核心指令 API ====================\r\n\r\n /** 聚焦到指定要素节点 */\r\n focusTo(node: SgsMapNode | string) {\r\n if (typeof node === 'string') {\r\n this._postCommandSafe('FOCUS_TO', { nodeName: node });\r\n } else {\r\n this._postCommandSafe('FOCUS_TO', node);\r\n }\r\n }\r\n\r\n /** 切换楼层 */\r\n changeFloor(floorId: string | number, timeout?: number, options?: { forceReload?: boolean }): Promise<{ success: boolean; floorId: string | number }> {\r\n return this._postCommandAsync<{ success: boolean; floorId: string | number }>('CHANGE_FLOOR', { floorId, ...options }, timeout).then((res) => {\r\n this.floorId = floorId;\r\n return res;\r\n });\r\n }\r\n\r\n /** 添加业务图钉 */\r\n addMarker(config: MarkerConfig) {\r\n this._postCommandSafe('ADD_MARKER', config);\r\n }\r\n\r\n /** 移除指定图钉 */\r\n removeMarker(markerId: string) {\r\n this._postCommandSafe('REMOVE_MARKER', { markerId });\r\n }\r\n\r\n /** 清空所有业务图钉 */\r\n clearMarkers() {\r\n this._postCommandSafe('CLEAR_MARKERS');\r\n }\r\n\r\n /** 高亮多边形轮廓 */\r\n highlightPolygon(payload: string | object) {\r\n if (typeof payload === 'string') {\r\n this._postCommandSafe('HIGHLIGHT_POLYGON', { nodeName: payload });\r\n } else {\r\n this._postCommandSafe('HIGHLIGHT_POLYGON', payload);\r\n }\r\n }\r\n\r\n /** 寻路规划(v1 兼容) */\r\n planRoute(from: SgsMapNode | string, to: SgsMapNode | string, options?: any, timeout: number = 5000): Promise {\r\n const startNode = typeof from === 'string' ? from : undefined;\r\n const endNode = typeof to === 'string' ? to : undefined;\r\n\r\n const payload: any = {};\r\n if (startNode) payload.startNode = startNode;\r\n else payload.from = from;\r\n\r\n if (endNode) payload.endNode = endNode;\r\n else payload.to = to;\r\n\r\n if (options) payload.options = options;\r\n\r\n return this._postCommandAsync('PLAN_ROUTE', payload, timeout);\r\n }\r\n\r\n /** 寻路规划 V2(结构化参数) */\r\n planRouteV2(from: SgsMapNode, to: SgsMapNode, options?: any, timeout: number = 5000): Promise {\r\n const payload = { from, to, options };\r\n return this._postCommandAsync('PLAN_ROUTE', payload, timeout);\r\n }\r\n\r\n /** 清除当前路线 */\r\n clearRoute() {\r\n this._postCommandSafe('CLEAR_ROUTE');\r\n }\r\n\r\n /** 渲染已有路线点,供外部宿主直接展示自定义路线 */\r\n renderRoutePoints(\r\n pathPoints: Array<{ x: number; y: number; z: number }>,\r\n options?: any,\r\n timeout: number = 12000\r\n ): Promise {\r\n return this._postCommandAsync('RENDER_ROUTE_POINTS', { ...options, pathPoints }, timeout);\r\n }\r\n\r\n /** 启动路线漫游;pathPoints 为空时,基座会优先使用当前已渲染路线 */\r\n startRoaming(\r\n pathPoints: Array<{ x: number; y: number; z: number }> = [],\r\n options?: SgsRouteRoamingOptions,\r\n timeout: number = 12000\r\n ): Promise<{ success: boolean; mode?: string }> {\r\n return this._postCommandAsync<{ success: boolean; mode?: string }>('START_ROAMING', { ...options, pathPoints }, timeout);\r\n }\r\n\r\n /** 停止路线漫游 */\r\n stopRoaming(timeout?: number): Promise<{ success: boolean }> {\r\n return this._postCommandAsync<{ success: boolean }>('STOP_ROAMING', {}, timeout);\r\n }\r\n\r\n /** 暂停或继续路线漫游 */\r\n toggleRoamingPause(timeout?: number): Promise<{ success: boolean; paused?: boolean }> {\r\n return this._postCommandAsync<{ success: boolean; paused?: boolean }>('TOGGLE_ROAMING_PAUSE', {}, timeout);\r\n }\r\n\r\n // ==================== SDK 2.4.1 高层导航 API ====================\r\n\r\n /**\r\n * 导航至POI(SDK 2.4.1)\r\n *\r\n * 支持从坐标或POI导航到目标POI,后端自动查询POI坐标。\r\n *\r\n * @param from 起点(POI ID字符串/数字 或 坐标对象)\r\n * @param toPoiId 目标POI ID(建议使用string避免Snowflake精度丢失)\r\n * @param options 路径选项(wheelchair, preferElevator)\r\n * @param timeout 超时时间(毫秒)\r\n */\r\n planRouteToPoi(from: string | number | SdkRoutePoint, toPoiId: string | number, options?: RouteOptions, timeout: number = 5000): Promise {\r\n // 将 string/number 转为 { poiId: from }\r\n const fromPoint: SdkRoutePoint = (typeof from === 'string' || typeof from === 'number')\r\n ? { poiId: from }\r\n : from;\r\n\r\n const payload = {\r\n from: fromPoint,\r\n toPoiId,\r\n wheelchair: options?.wheelchair ? 1 : 0,\r\n preferElevator: options?.preferElevator,\r\n };\r\n return this._postCommandAsync('PLAN_ROUTE_TO_POI', payload, timeout);\r\n }\r\n\r\n /**\r\n * POI间导航(SDK 2.4.1)\r\n *\r\n * 两个POI之间的导航,后端自动查询POI坐标。\r\n *\r\n * @param fromPoiId 起点POI ID\r\n * @param toPoiId 终点POI ID\r\n * @param options 路径选项(wheelchair, preferElevator)\r\n * @param timeout 超时时间(毫秒)\r\n */\r\n planRouteBetweenPois(fromPoiId: string | number, toPoiId: string | number, options?: RouteOptions, timeout: number = 5000): Promise {\r\n const payload = {\r\n fromPoiId,\r\n toPoiId,\r\n wheelchair: options?.wheelchair ? 1 : 0,\r\n preferElevator: options?.preferElevator,\r\n };\r\n return this._postCommandAsync('PLAN_ROUTE_BETWEEN_POIS', payload, timeout);\r\n }\r\n\r\n // ==================== 状态和视图控制 ====================\r\n getState(): Promise {\r\n return this._postCommandAsync('GET_STATE');\r\n }\r\n\r\n /** 重置视角 */\r\n resetView(timeout?: number): Promise<{ success: boolean }> {\r\n return this._postCommandAsync<{ success: boolean }>('RESET_VIEW', {}, timeout);\r\n }\r\n\r\n /** 设置 2D/3D 视图模式 */\r\n setViewMode(mode: '2d' | '3d', timeout?: number): Promise<{ success: boolean; mode: string }> {\r\n return this._postCommandAsync<{ success: boolean; mode: string }>('SET_VIEW_MODE', { mode }, timeout);\r\n }\r\n\r\n /** 切换图层可见性 */\r\n toggleLayer(types: string[], visible: boolean, timeout?: number): Promise<{ success: boolean; visibleTypes: string[] }> {\r\n return this._postCommandAsync<{ success: boolean; visibleTypes: string[] }>('TOGGLE_LAYER', { types, visible }, timeout);\r\n }\r\n\r\n /** 设置可见图层类型列表 */\r\n setVisibleLayers(types: string[], timeout?: number): Promise<{ success: boolean; visibleTypes: string[] }> {\r\n return this._postCommandAsync<{ success: boolean; visibleTypes: string[] }>('SET_VISIBLE_TYPES', { types }, timeout);\r\n }\r\n\r\n /** 设置路线展示视图是否活跃 */\r\n setRouteViewActive(active: boolean, timeout?: number): Promise<{ success: boolean; active: boolean }> {\r\n return this._postCommandAsync<{ success: boolean; active: boolean }>('SET_ROUTE_VIEW_ACTIVE', { active }, timeout);\r\n }\r\n\r\n // ==================== v2.2.0 / v2.2.0 数据消费 API ====================\r\n\r\n /** 获取地图 Manifest 清单 */\r\n getManifest(timeout?: number): Promise {\r\n return this._postCommandAsync('GET_MANIFEST', {}, timeout);\r\n }\r\n\r\n /** 加载指定楼层的完整 Bundle 数据(含 POI/空间面/讲解点/路网) */\r\n loadFloorBundle(floorId: string | number, timeout?: number): Promise {\r\n return this._postCommandAsync('LOAD_FLOOR_BUNDLE', { floorId }, timeout);\r\n }\r\n\r\n /** 获取指定楼层的 POI 列表 */\r\n getFloorPois(floorId: string | number, timeout?: number): Promise {\r\n return this._postCommandAsync('GET_FLOOR_POIS', { floorId }, timeout)\r\n .then((res) => Array.isArray(res) ? res : (res?.pois || []));\r\n }\r\n\r\n /** 获取指定楼层的起终点候选目的地列表(门点/POI/空间等吸附后目的地) */\r\n getNavigablePlaces(floorId: string | number, timeout?: number): Promise {\r\n return this._postCommandAsync('GET_NAVIGABLE_PLACES', { floorId }, timeout)\r\n .then((res) => Array.isArray(res) ? res : (res?.navigablePlaces || res?.places || []));\r\n }\r\n\r\n /** 获取当前楼层的 POI 列表(根据 floorCode 参数) */\r\n getPois(floorCode: string, timeout?: number): Promise {\r\n return this._postCommandAsync('GET_POIS', { floorCode }, timeout);\r\n }\r\n\r\n /** 获取指定楼层的空间面列表 */\r\n getSpaces(floorId: string | number, timeout?: number): Promise {\r\n return this._postCommandAsync('GET_SPACES', { floorId }, timeout)\r\n .then((res) => Array.isArray(res) ? res : (res?.spaces || []));\r\n }\r\n\r\n /** 获取指定楼层的已标定讲解点列表(地图渲染入口) */\r\n getGuideStops(floorId: string | number, timeout?: number): Promise {\r\n return this._postCommandAsync('GET_GUIDE_STOPS', { floorId }, timeout)\r\n .then((res) => Array.isArray(res) ? res : (res?.guideStops || []));\r\n }\r\n\r\n /** 获取指定展厅的已标定讲解点列表(业务导览入口) */\r\n getGuideStopsByHall(hallId: string | number, timeout?: number): Promise {\r\n return this._postCommandAsync('GET_GUIDE_STOPS_BY_HALL', { hallId }, timeout)\r\n .then((res) => Array.isArray(res) ? res : (res?.guideStops || []));\r\n }\r\n\r\n /** 预加载指定楼层的模型资源(不渲染) */\r\n preloadFloor(floorId: string | number, timeout?: number): Promise<{ cached: boolean }> {\r\n return this._postCommandAsync<{ cached: boolean }>('PRELOAD_FLOOR', { floorId }, timeout);\r\n }\r\n\r\n /** 获取性能统计信息 */\r\n getPerformanceStats(timeout?: number): Promise {\r\n return this._postCommandAsync('GET_PERFORMANCE_STATS', {}, timeout).then(stats => ({\r\n sdkCreatedAt: this._sdkCreatedAt,\r\n iframeDomReadyMs: this._iframeDomReadyMs ?? undefined,\r\n engineReadyMs: this._engineReadyMs ?? undefined,\r\n ...stats\r\n }));\r\n }\r\n\r\n // ==================== v2.3.0 诊断接口 ====================\r\n\r\n /** 获取地图级诊断信息 */\r\n getDiagnostics(timeout?: number): Promise {\r\n return this._postCommandAsync('GET_DIAGNOSTICS', {}, timeout);\r\n }\r\n\r\n /** 获取楼层级诊断信息 */\r\n getFloorDiagnostics(floorId: string | number, timeout?: number): Promise {\r\n return this._postCommandAsync('GET_FLOOR_DIAGNOSTICS', { floorId }, timeout);\r\n }\r\n\r\n /** 清空客户端模型缓存 */\r\n clearModelCache(): void {\r\n this._postCommandSafe('CLEAR_MODEL_CACHE');\r\n }\r\n\r\n // ==================== v2.4.0 新增 API ====================\r\n\r\n /** 获取指定楼层的业务 POI 列表 */\r\n getBusinessPois(floorId: string | number, options?: { types?: string[]; keyword?: string; limit?: number }, timeout?: number): Promise {\r\n return this._postCommandAsync('GET_BUSINESS_POIS', { floorId, options }, timeout)\r\n .then((res) => Array.isArray(res) ? res : (res?.pois || []));\r\n }\r\n\r\n /** 查询 POI 列表 */\r\n queryPois(params: SgsPoiQueryParams, timeout?: number): Promise {\r\n return this._postCommandAsync('QUERY_POIS', params, timeout)\r\n .then((res) => Array.isArray(res) ? res : (res?.pois || []));\r\n }\r\n\r\n /** 获取特色路线列表 */\r\n getFeaturedRoutes(mapId?: string | number, options?: { limit?: number }, timeout?: number): Promise {\r\n return this._postCommandAsync('GET_FEATURED_ROUTES', { mapId, options }, timeout)\r\n .then((res) => Array.isArray(res) ? res : (res?.routes || []));\r\n }\r\n\r\n /** 获取特色路线详情 */\r\n getFeaturedRoute(routeId: string | number, timeout?: number): Promise {\r\n return this._postCommandAsync('GET_FEATURED_ROUTE', { routeId }, timeout)\r\n .then((res) => res?.route || res);\r\n }\r\n\r\n /** 生成并展示精品路线预览 */\r\n planFeaturedRoutePreview(\r\n payload: SgsFeaturedRoutePreviewPayload,\r\n timeout: number = 15000\r\n ): Promise {\r\n return this._postCommandAsync('PLAN_FEATURED_ROUTE_PREVIEW', payload, timeout);\r\n }\r\n\r\n /** 显示动线 */\r\n showFlowline(routeId: string | number, options?: SgsFlowlineOptions, timeout?: number): Promise<{ success: boolean; routeId: string | number }> {\r\n return this._postCommandAsync<{ success: boolean; routeId: string | number }>('SHOW_FLOWLINE', { routeId, options }, timeout);\r\n }\r\n\r\n /** 清除动线 */\r\n clearFlowline(): void {\r\n this._postCommandSafe('CLEAR_FLOWLINE');\r\n }\r\n\r\n // ==================== @deprecated 兼容层 ====================\r\n // 以下方法已废弃,保留 API 签名避免外部接入方断裂。\r\n // 内部返回 ERR_UNSUPPORTED,不进入任何发布路径。\r\n\r\n /** @deprecated 使用 getManifest() 替代 */\r\n getReleaseManifest(timeout?: number): Promise {\r\n console.warn('[SGSMapSDK] getReleaseManifest() is deprecated. Use getManifest() instead.');\r\n return Promise.reject({ code: 'ERR_UNSUPPORTED', message: 'getReleaseManifest() is deprecated. Use getManifest() instead.' });\r\n }\r\n\r\n /** @deprecated 已废弃,无替代方法 */\r\n getModelResources(timeout?: number): Promise {\r\n console.warn('[SGSMapSDK] getModelResources() is deprecated.');\r\n return Promise.reject({ code: 'ERR_UNSUPPORTED', message: 'getModelResources() is deprecated.' });\r\n }\r\n\r\n /** @deprecated 已废弃,无替代方法 */\r\n getLayerTree(timeout?: number): Promise {\r\n console.warn('[SGSMapSDK] getLayerTree() is deprecated.');\r\n return Promise.reject({ code: 'ERR_UNSUPPORTED', message: 'getLayerTree() is deprecated.' });\r\n }\r\n\r\n /** @deprecated 已废弃,无替代方法 */\r\n loadMockReleaseRoute(timeout?: number): Promise {\r\n console.warn('[SGSMapSDK] loadMockReleaseRoute() is deprecated.');\r\n return Promise.reject({ code: 'ERR_UNSUPPORTED', message: 'loadMockReleaseRoute() is deprecated.' });\r\n }\r\n\r\n // ==================== 销毁 ====================\r\n\r\n /**\r\n * 销毁 SDK 实例\r\n *\r\n *

清理 iframe、移除事件监听器、reject 所有挂起的 Promise。\r\n *

销毁后调用任何命令方法都会 reject ERR_DESTROYED。\r\n */\r\n destroy() {\r\n if (this._isDestroyed) return; // 防重复销毁\r\n this._isDestroyed = true;\r\n\r\n // 清除 ready 超时计时器\r\n if (this._readyTimer) {\r\n clearTimeout(this._readyTimer);\r\n this._readyTimer = null;\r\n }\r\n\r\n // 🔧 P0: 清除 HELLO 重试定时器\r\n if (this._helloInterval) {\r\n clearInterval(this._helloInterval);\r\n this._helloInterval = null;\r\n }\r\n\r\n // reject ready Promise(如果还没 resolve/reject)\r\n if (this._readyReject) {\r\n this._readyReject({\r\n code: 'ERR_DESTROYED',\r\n message: 'SGSMapSDK has been destroyed before ready.'\r\n });\r\n this._readyResolve = null;\r\n this._readyReject = null;\r\n }\r\n\r\n // reject 所有挂起的命令\r\n this._rejectAllPending('ERR_DESTROYED', 'SGSMapSDK has been destroyed. Pending promises were rejected.');\r\n\r\n // 移除 iframe\r\n if (this.iframe) {\r\n if (this.iframe.parentNode) {\r\n this.iframe.parentNode.removeChild(this.iframe);\r\n }\r\n this.iframe = null;\r\n }\r\n\r\n // 移除事件监听器\r\n if (this._messageListener) {\r\n window.removeEventListener('message', this._messageListener);\r\n this._messageListener = null;\r\n }\r\n\r\n // 移除所有 EventEmitter 监听器\r\n this.removeAllListeners();\r\n\r\n this._isReady = false;\r\n console.log('[SGSMapSDK] Destroyed successfully. Cleaned up iframe, listeners and pending queues.');\r\n }\r\n}\r\n\r\n// 全局暴露(IIFE 产物使用)\r\nif (typeof window !== 'undefined') {\r\n (window as any).SGSMapSDK = SGSMapSDK;\r\n}\r\n"],"mappings":"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","names":["EventEmitter","event","listener","l","wrapper","payload","data","e","utils_exports","__export","isIndoorNavigableFloor","sortIndoorFloors","floorCode","floors","FLOOR_ORDER","indoorFloors","f","getFloorIndex","code","matchedIndex","numMatch","val","a","b","SDK_VERSION","PROTOCOL_VERSION","DEFAULT_READY_TIMEOUT","DEFAULT_CMD_TIMEOUT","SGSMapSDK","EventEmitter","options","containerEl","resolve","reject","iframe","iframeUrl","value","key","event","data","action","payload","requestId","error","resolver","errObj","sendHello","err","fireAndForget","cmd","pending","total","code","message","timeout","timer","node","floorId","res","config","markerId","from","to","startNode","endNode","pathPoints","toPoiId","fromPoiId","mode","types","visible","active","floorCode","hallId","stats","params","mapId","routeId","utils_exports"]} \ No newline at end of file diff --git a/static/sgs-map-sdk/index.mjs b/static/sgs-map-sdk/index.mjs new file mode 100644 index 0000000..983f540 --- /dev/null +++ b/static/sgs-map-sdk/index.mjs @@ -0,0 +1,2 @@ +var y=Object.defineProperty;var f=(d,o)=>{for(var e in o)y(d,e,{get:o[e],enumerable:!0})};var u=class{constructor(){this.events={}}on(o,e){this.events[o]||(this.events[o]=[]),this.events[o].push(e)}off(o,e){this.events[o]&&(this.events[o]=this.events[o].filter(s=>s!==e))}once(o,e){let s=t=>{this.off(o,s),e(t)};this.on(o,s)}emit(o,e){this.events[o]&&[...this.events[o]].forEach(t=>{try{t(e)}catch(r){console.error(`[EventEmitter] Error emitting event ${String(o)}:`,r)}})}removeAllListeners(o){o!==void 0?delete this.events[o]:this.events={}}listenerCount(o){return this.events[o]?.length??0}};var h={};f(h,{isIndoorNavigableFloor:()=>_,sortIndoorFloors:()=>R});function _(d){return d?d.toUpperCase()!=="EXTERIOR":!1}function R(d){let o=["L-2","L-1","B2","B1","L1","L1.5","L2","L3","L4","L5"],e=d.filter(t=>_(t.floorCode)),s=t=>{let r=t.trim().toUpperCase();r.startsWith("F")&&(r="L"+r.substring(1));let i=o.findIndex(a=>a.toUpperCase()===r);if(i!==-1)return i;let n=r.match(/-?\d+(\.\d+)?/);if(n){let a=parseFloat(n[0]);return r.startsWith("B")||r.includes("-")?-Math.abs(a)-100:a}return 0};return[...e].sort((t,r)=>s(t.floorCode)-s(r.floorCode))}var g="2.4.1",S=2,E=3e4,P=5e3,l=class extends u{constructor(e){super();this.iframe=null;this._isReady=!1;this._isDestroyed=!1;this._readyFailed=!1;this._readyFailedError=null;this.promiseQueue={};this._pendingCommands=[];this._pendingFireAndForget=[];this._messageListener=null;this._readyTimer=null;this._helloInterval=null;this.readyPayload=null;this._readyResolve=null;this._readyReject=null;this._iframeDomReadyMs=null;this._engineReadyMs=null;this._sdkCreatedAt=Date.now();let s=typeof e.container=="string"?document.getElementById(e.container):e.container;if(!s)throw new Error(`[SGSMapSDK] Cannot find container: ${e.container}`);this.container=s,this.sdkUrl=e.sdkUrl||"/h5-sdk",this.floorId=e.floorId||1,this.mapId=e.mapId||1,this.defaultTimeout=e.timeout||P,this.readyTimeoutMs=e.readyTimeout||E,this.targetOrigin=e.targetOrigin,this.targetOrigin||(this.sdkUrl.startsWith("http")?this.targetOrigin=new URL(this.sdkUrl).origin:typeof window<"u"?this.targetOrigin=window.location.origin:this.targetOrigin="*"),this._readyPromise=new Promise((t,r)=>{this._readyResolve=t,this._readyReject=r}),this._initIframeBridge()}_initIframeBridge(){let e=document.createElement("iframe"),s=new URL(this.sdkUrl,typeof window<"u"?window.location.href:"http://localhost");typeof window<"u"&&new URLSearchParams(window.location.search).forEach((r,i)=>{s.searchParams.set(i,r)}),s.searchParams.has("floorId")||s.searchParams.set("floorId",String(this.floorId)),e.src=s.toString(),e.style.cssText="width:100%;height:100%;border:none;background:transparent;",this.container.innerHTML="",this.container.appendChild(e),this.iframe=e,this._messageListener=t=>{if(this.targetOrigin!=="*"&&t.origin!==this.targetOrigin||!this.iframe||t.source!==this.iframe.contentWindow)return;let r=t.data;if(r){if(r.type==="SGS_MAP_EVENT"){let{action:i,payload:n}=r;i==="ON_POI_CLICK"?this.emit("poiClick",n):i==="ON_POI_DETAIL"?this.emit("poiDetail",n):i==="ON_POI_NAVIGATE"?this.emit("poiNavigate",n):i==="ON_POI_CLOSE"?this.emit("poiClose",n):i==="ON_SPACE_CLICK"?this.emit("spaceClick",n):i==="ON_SPACE_DETAIL"?this.emit("spaceDetail",n):i==="ON_SPACE_NAVIGATE"?this.emit("spaceNavigate",n):i==="ON_SPACE_CLOSE"?this.emit("spaceClose",n):i==="ENGINE_READY"?this._onEngineReady(n):i==="ON_FLOOR_CHANGED"?this.emit("floorChanged",n):i==="ON_ROUTE_TRANSFER"?this.emit("routeTransfer",n):i==="ON_MODEL_LOADING"?this.emit("modelLoading",n):i==="ON_MODEL_READY"?this.emit("modelReady",n):i==="ON_MODEL_ERROR"?this.emit("modelError",n):i==="ON_FLOOR_BUNDLE_READY"?this.emit("floorBundleReady",n):i==="ON_ROUTE_READY"?this.emit("routeReady",n):i==="ON_ROAMING_PROGRESS"?this.emit("roamingProgress",n):i==="ON_ROAMING_DONE"&&this.emit("roamingDone",n)}if(r.type==="SGS_MAP_RESPONSE"){let{action:i,requestId:n,payload:a,error:m}=r,c=this.promiseQueue[n];if(c){if(a&&a.success!==!1)c.resolve(a);else{let p=m||{code:"ERR_FAILED",message:`${i} command failed`};c.reject(p)}delete this.promiseQueue[n]}}}},window.addEventListener("message",this._messageListener),e.onload=()=>{if(this._iframeDomReadyMs=Date.now()-this._sdkCreatedAt,this._isReady)return;let t=()=>{if(this._isReady||this._isDestroyed){this._helloInterval&&(clearInterval(this._helloInterval),this._helloInterval=null);return}this._postCommand("HELLO",{protocolVersion:S,sdkVersion:g})};t(),this._helloInterval=setInterval(t,500),console.log(`[SGSMapSDK] Sent HELLO handshake (v${g}), will retry every 500ms until ENGINE_READY...`)},e.onerror=t=>{console.error("[SGSMapSDK] Iframe load error",t);let r={code:"ERR_IFRAME_LOAD",message:"Failed to load map engine iframe."};this.emit("loadError",{type:"iframe_error",detail:r.message}),this._rejectReady(r)},this._readyTimer=setTimeout(()=>{if(!this._isReady&&!this._isDestroyed){let t={code:"ERR_READY_TIMEOUT",message:`ENGINE_READY not received within ${this.readyTimeoutMs}ms`};console.error(`[SGSMapSDK] ${t.message}`),this._rejectReady(t)}},this.readyTimeoutMs)}_onEngineReady(e){this._isReady||(this._isReady=!0,this.readyPayload=e,this._engineReadyMs=Date.now()-this._sdkCreatedAt,this._helloInterval&&(clearInterval(this._helloInterval),this._helloInterval=null),this._readyTimer&&(clearTimeout(this._readyTimer),this._readyTimer=null),this._readyResolve&&(this._readyResolve(e),this._readyResolve=null,this._readyReject=null),this.emit("ready",e),console.log("[SGSMapSDK] Handshake complete: Engine Ready received \u2705"),this._flushPendingCommands())}_rejectReady(e){this._readyFailed=!0,this._readyFailedError=e,this._readyReject&&(this._readyReject(e),this._readyResolve=null,this._readyReject=null),this._readyTimer&&(clearTimeout(this._readyTimer),this._readyTimer=null),this._rejectAllPending(e.code,e.message),this._pendingFireAndForget.splice(0)}_flushPendingCommands(){let e=this._pendingFireAndForget.splice(0);for(let r of e)this._postCommand(r.action,r.payload);let s=this._pendingCommands.splice(0);for(let r of s)this._postCommandAsyncInner(r.action,r.payload,r.timeout).then(r.resolve).catch(r.reject);let t=e.length+s.length;t>0&&console.log(`[SGSMapSDK] Flushed ${t} pending commands after ready (${e.length} fire-and-forget, ${s.length} async)`)}_rejectAllPending(e,s){Object.keys(this.promiseQueue).forEach(r=>{let i=this.promiseQueue[r];i&&i.reject({code:e,message:s}),delete this.promiseQueue[r]});let t=this._pendingCommands.splice(0);for(let r of t)r.reject({code:e,message:s})}whenReady(){return this._readyPromise}ready(){return this._readyPromise}getIsReady(){return this._isReady}getCurrentFloor(){return this.floorId}getMapId(){return this.mapId}getVersion(){return g}_generateRequestId(){return`req_sdk_${Date.now()}_${Math.floor(Math.random()*1e5)}`}_postCommand(e,s,t){if(!this.iframe||!this.iframe.contentWindow){console.error("[SGSMapSDK] SDK Iframe is not available!");return}this.iframe.contentWindow.postMessage({type:"SGS_MAP_COMMAND",action:e,requestId:t,payload:s},this.targetOrigin)}_postCommandAsync(e,s,t=this.defaultTimeout){return this._isDestroyed?Promise.reject({code:"ERR_DESTROYED",message:"SGSMapSDK has been destroyed. Cannot send commands."}):this._readyFailed?Promise.reject(this._readyFailedError||{code:"ERR_NOT_READY",message:"SDK ready failed."}):this._isReady?this._postCommandAsyncInner(e,s,t):new Promise((r,i)=>{this._pendingCommands.push({action:e,payload:s,timeout:t,resolve:r,reject:i}),console.log(`[SGSMapSDK] Command "${e}" queued (waiting for ready). Queue size: ${this._pendingCommands.length}`)})}_postCommandSafe(e,s){if(this._isDestroyed){console.warn(`[SGSMapSDK] Command "${e}" ignored: SDK destroyed.`);return}if(this._readyFailed){console.warn(`[SGSMapSDK] Command "${e}" ignored: SDK ready failed.`);return}if(!this._isReady){this._pendingFireAndForget.push({action:e,payload:s}),console.log(`[SGSMapSDK] Command "${e}" queued (fire-and-forget, waiting for ready). Queue size: ${this._pendingFireAndForget.length}`);return}this._postCommand(e,s)}_postCommandAsyncInner(e,s,t=this.defaultTimeout){let r=this._generateRequestId();return new Promise((i,n)=>{let a=setTimeout(()=>{this.promiseQueue[r]&&(this.promiseQueue[r].reject({code:"ERR_TIMEOUT",message:`Command "${e}" timed out after ${t}ms`}),delete this.promiseQueue[r])},t);this.promiseQueue[r]={resolve:m=>{clearTimeout(a),i(m)},reject:m=>{clearTimeout(a),n(m)}},this._postCommand(e,s,r)})}focusTo(e){typeof e=="string"?this._postCommandSafe("FOCUS_TO",{nodeName:e}):this._postCommandSafe("FOCUS_TO",e)}changeFloor(e,s,t){return this._postCommandAsync("CHANGE_FLOOR",{floorId:e,...t},s).then(r=>(this.floorId=e,r))}addMarker(e){this._postCommandSafe("ADD_MARKER",e)}removeMarker(e){this._postCommandSafe("REMOVE_MARKER",{markerId:e})}clearMarkers(){this._postCommandSafe("CLEAR_MARKERS")}highlightPolygon(e){typeof e=="string"?this._postCommandSafe("HIGHLIGHT_POLYGON",{nodeName:e}):this._postCommandSafe("HIGHLIGHT_POLYGON",e)}planRoute(e,s,t,r=5e3){let i=typeof e=="string"?e:void 0,n=typeof s=="string"?s:void 0,a={};return i?a.startNode=i:a.from=e,n?a.endNode=n:a.to=s,t&&(a.options=t),this._postCommandAsync("PLAN_ROUTE",a,r)}planRouteV2(e,s,t,r=5e3){let i={from:e,to:s,options:t};return this._postCommandAsync("PLAN_ROUTE",i,r)}clearRoute(){this._postCommandSafe("CLEAR_ROUTE")}renderRoutePoints(e,s,t=12e3){return this._postCommandAsync("RENDER_ROUTE_POINTS",{...s,pathPoints:e},t)}startRoaming(e=[],s,t=12e3){return this._postCommandAsync("START_ROAMING",{...s,pathPoints:e},t)}stopRoaming(e){return this._postCommandAsync("STOP_ROAMING",{},e)}toggleRoamingPause(e){return this._postCommandAsync("TOGGLE_ROAMING_PAUSE",{},e)}planRouteToPoi(e,s,t,r=5e3){let n={from:typeof e=="string"||typeof e=="number"?{poiId:e}:e,toPoiId:s,wheelchair:t?.wheelchair?1:0,preferElevator:t?.preferElevator};return this._postCommandAsync("PLAN_ROUTE_TO_POI",n,r)}planRouteBetweenPois(e,s,t,r=5e3){let i={fromPoiId:e,toPoiId:s,wheelchair:t?.wheelchair?1:0,preferElevator:t?.preferElevator};return this._postCommandAsync("PLAN_ROUTE_BETWEEN_POIS",i,r)}getState(){return this._postCommandAsync("GET_STATE")}resetView(e){return this._postCommandAsync("RESET_VIEW",{},e)}setViewMode(e,s){return this._postCommandAsync("SET_VIEW_MODE",{mode:e},s)}toggleLayer(e,s,t){return this._postCommandAsync("TOGGLE_LAYER",{types:e,visible:s},t)}setVisibleLayers(e,s){return this._postCommandAsync("SET_VISIBLE_TYPES",{types:e},s)}setRouteViewActive(e,s){return this._postCommandAsync("SET_ROUTE_VIEW_ACTIVE",{active:e},s)}getManifest(e){return this._postCommandAsync("GET_MANIFEST",{},e)}loadFloorBundle(e,s){return this._postCommandAsync("LOAD_FLOOR_BUNDLE",{floorId:e},s)}getFloorPois(e,s){return this._postCommandAsync("GET_FLOOR_POIS",{floorId:e},s).then(t=>Array.isArray(t)?t:t?.pois||[])}getNavigablePlaces(e,s){return this._postCommandAsync("GET_NAVIGABLE_PLACES",{floorId:e},s).then(t=>Array.isArray(t)?t:t?.navigablePlaces||t?.places||[])}getPois(e,s){return this._postCommandAsync("GET_POIS",{floorCode:e},s)}getSpaces(e,s){return this._postCommandAsync("GET_SPACES",{floorId:e},s).then(t=>Array.isArray(t)?t:t?.spaces||[])}getGuideStops(e,s){return this._postCommandAsync("GET_GUIDE_STOPS",{floorId:e},s).then(t=>Array.isArray(t)?t:t?.guideStops||[])}getGuideStopsByHall(e,s){return this._postCommandAsync("GET_GUIDE_STOPS_BY_HALL",{hallId:e},s).then(t=>Array.isArray(t)?t:t?.guideStops||[])}preloadFloor(e,s){return this._postCommandAsync("PRELOAD_FLOOR",{floorId:e},s)}getPerformanceStats(e){return this._postCommandAsync("GET_PERFORMANCE_STATS",{},e).then(s=>({sdkCreatedAt:this._sdkCreatedAt,iframeDomReadyMs:this._iframeDomReadyMs??void 0,engineReadyMs:this._engineReadyMs??void 0,...s}))}getDiagnostics(e){return this._postCommandAsync("GET_DIAGNOSTICS",{},e)}getFloorDiagnostics(e,s){return this._postCommandAsync("GET_FLOOR_DIAGNOSTICS",{floorId:e},s)}clearModelCache(){this._postCommandSafe("CLEAR_MODEL_CACHE")}getBusinessPois(e,s,t){return this._postCommandAsync("GET_BUSINESS_POIS",{floorId:e,options:s},t).then(r=>Array.isArray(r)?r:r?.pois||[])}queryPois(e,s){return this._postCommandAsync("QUERY_POIS",e,s).then(t=>Array.isArray(t)?t:t?.pois||[])}getFeaturedRoutes(e,s,t){return this._postCommandAsync("GET_FEATURED_ROUTES",{mapId:e,options:s},t).then(r=>Array.isArray(r)?r:r?.routes||[])}getFeaturedRoute(e,s){return this._postCommandAsync("GET_FEATURED_ROUTE",{routeId:e},s).then(t=>t?.route||t)}planFeaturedRoutePreview(e,s=15e3){return this._postCommandAsync("PLAN_FEATURED_ROUTE_PREVIEW",e,s)}showFlowline(e,s,t){return this._postCommandAsync("SHOW_FLOWLINE",{routeId:e,options:s},t)}clearFlowline(){this._postCommandSafe("CLEAR_FLOWLINE")}getReleaseManifest(e){return console.warn("[SGSMapSDK] getReleaseManifest() is deprecated. Use getManifest() instead."),Promise.reject({code:"ERR_UNSUPPORTED",message:"getReleaseManifest() is deprecated. Use getManifest() instead."})}getModelResources(e){return console.warn("[SGSMapSDK] getModelResources() is deprecated."),Promise.reject({code:"ERR_UNSUPPORTED",message:"getModelResources() is deprecated."})}getLayerTree(e){return console.warn("[SGSMapSDK] getLayerTree() is deprecated."),Promise.reject({code:"ERR_UNSUPPORTED",message:"getLayerTree() is deprecated."})}loadMockReleaseRoute(e){return console.warn("[SGSMapSDK] loadMockReleaseRoute() is deprecated."),Promise.reject({code:"ERR_UNSUPPORTED",message:"loadMockReleaseRoute() is deprecated."})}destroy(){this._isDestroyed||(this._isDestroyed=!0,this._readyTimer&&(clearTimeout(this._readyTimer),this._readyTimer=null),this._helloInterval&&(clearInterval(this._helloInterval),this._helloInterval=null),this._readyReject&&(this._readyReject({code:"ERR_DESTROYED",message:"SGSMapSDK has been destroyed before ready."}),this._readyResolve=null,this._readyReject=null),this._rejectAllPending("ERR_DESTROYED","SGSMapSDK has been destroyed. Pending promises were rejected."),this.iframe&&(this.iframe.parentNode&&this.iframe.parentNode.removeChild(this.iframe),this.iframe=null),this._messageListener&&(window.removeEventListener("message",this._messageListener),this._messageListener=null),this.removeAllListeners(),this._isReady=!1,console.log("[SGSMapSDK] Destroyed successfully. Cleaned up iframe, listeners and pending queues."))}};l.utils=h;typeof window<"u"&&(window.SGSMapSDK=l);export{l as default}; +//# sourceMappingURL=index.mjs.map \ No newline at end of file diff --git a/static/sgs-map-sdk/index.mjs.map b/static/sgs-map-sdk/index.mjs.map new file mode 100644 index 0000000..784f3ab --- /dev/null +++ b/static/sgs-map-sdk/index.mjs.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../sdk-src/event-emitter.ts","../../sdk-src/utils.ts","../../sdk-src/index.ts"],"sourcesContent":["/**\r\n * 轻量级类型安全事件发射器\r\n *\r\n *

SDK 内部事件系统基类,提供 on/off/once/emit/removeAllListeners/listenerCount。\r\n */\r\nexport class EventEmitter> {\r\n private events: { [K in keyof Events]?: Array<(payload: Events[K]) => void> };\r\n\r\n constructor() {\r\n this.events = {};\r\n }\r\n\r\n /** 注册事件监听器 */\r\n on(event: K, listener: (payload: Events[K]) => void): void {\r\n if (!this.events[event]) {\r\n this.events[event] = [];\r\n }\r\n this.events[event]!.push(listener);\r\n }\r\n\r\n /** 移除指定事件的指定监听器 */\r\n off(event: K, listener: (payload: Events[K]) => void): void {\r\n if (this.events[event]) {\r\n this.events[event] = this.events[event]!.filter(l => l !== listener);\r\n }\r\n }\r\n\r\n /** 注册一次性监听器,触发后自动移除 */\r\n once(event: K, listener: (payload: Events[K]) => void): void {\r\n const wrapper = (payload: Events[K]) => {\r\n this.off(event, wrapper);\r\n listener(payload);\r\n };\r\n this.on(event, wrapper);\r\n }\r\n\r\n /** 触发事件,通知所有已注册的监听器 */\r\n emit(event: K, data: Events[K]): void {\r\n if (this.events[event]) {\r\n // 浅拷贝防止 once 触发时修改数组导致跳过\r\n const listeners = [...this.events[event]!];\r\n listeners.forEach(l => {\r\n try {\r\n l(data);\r\n } catch (e) {\r\n console.error(`[EventEmitter] Error emitting event ${String(event)}:`, e);\r\n }\r\n });\r\n }\r\n }\r\n\r\n /** 移除指定事件的全部监听器;不传参则移除所有事件的所有监听器 */\r\n removeAllListeners(event?: K): void {\r\n if (event !== undefined) {\r\n delete this.events[event];\r\n } else {\r\n this.events = {};\r\n }\r\n }\r\n\r\n /** 返回指定事件的监听器数量 */\r\n listenerCount(event: K): number {\r\n return this.events[event]?.length ?? 0;\r\n }\r\n}\r\n","/**\r\n * 过滤 EXTERIOR 楼层,检查楼层是否是室内可导航楼层\r\n */\r\nexport function isIndoorNavigableFloor(floorCode: string): boolean {\r\n if (!floorCode) return false;\r\n return floorCode.toUpperCase() !== 'EXTERIOR';\r\n}\r\n\r\n/**\r\n * 物理楼层顺序由低到高排序\r\n */\r\nexport function sortIndoorFloors(floors: T[]): T[] {\r\n const FLOOR_ORDER = [\"L-2\", \"L-1\", \"B2\", \"B1\", \"L1\", \"L1.5\", \"L2\", \"L3\", \"L4\", \"L5\"];\r\n \r\n const indoorFloors = floors.filter(f => isIndoorNavigableFloor(f.floorCode));\r\n\r\n const getFloorIndex = (floorCode: string): number => {\r\n let code = floorCode.trim().toUpperCase();\r\n if (code.startsWith('F')) {\r\n code = 'L' + code.substring(1);\r\n }\r\n const matchedIndex = FLOOR_ORDER.findIndex(f => f.toUpperCase() === code);\r\n if (matchedIndex !== -1) return matchedIndex;\r\n \r\n const numMatch = code.match(/-?\\d+(\\.\\d+)?/);\r\n if (numMatch) {\r\n const val = parseFloat(numMatch[0]);\r\n if (code.startsWith('B') || code.includes('-')) {\r\n return -Math.abs(val) - 100;\r\n }\r\n return val;\r\n }\r\n return 0;\r\n };\r\n\r\n return [...indoorFloors].sort((a, b) => getFloorIndex(a.floorCode) - getFloorIndex(b.floorCode));\r\n}\r\n","import { EventEmitter } from './event-emitter';\r\nimport {\r\n SGSMapSDKOptions,\r\n SGSMapEvents,\r\n RouteResult,\r\n SgsMapNode,\r\n MarkerConfig,\r\n SgsSdkManifest,\r\n SgsFloorBundle,\r\n SgsPoi,\r\n SgsSpaceArea,\r\n SgsGuideStop,\r\n SgsPerformanceStats,\r\n SgsNavigablePlace,\r\n SgsMapDiagnostics,\r\n SgsFloorDiagnostics,\r\n SgsPoiQueryParams,\r\n SgsFeaturedRouteSummary,\r\n SgsFeaturedRouteDetail,\r\n SgsFeaturedRoutePreviewPayload,\r\n SgsFlowlineOptions,\r\n SgsRouteRoamingOptions,\r\n RouteOptions,\r\n SdkRoutePoint,\r\n} from './types';\r\n\r\nexport * from './types';\r\n\r\nimport * as utils from './utils';\r\n\r\n/** SDK 版本号常量 — 与后端 SdkMapServiceImpl.SDK_VERSION 对齐 */\r\nconst SDK_VERSION = '2.4.1';\r\n/** 协议版本号 */\r\nconst PROTOCOL_VERSION = 2;\r\n/** 默认 ready 超时(ms) */\r\nconst DEFAULT_READY_TIMEOUT = 30000; // 🔧 P0: 从 15s 提升到 30s,配合延迟 ENGINE_READY 策略\r\n/** 默认命令超时(ms) */\r\nconst DEFAULT_CMD_TIMEOUT = 5000;\r\n\r\n/**\r\n * 命令队列条目 — ready 前调用的异步命令暂存于此\r\n */\r\ninterface PendingCommand {\r\n action: string;\r\n payload?: any;\r\n timeout: number;\r\n resolve: (data: any) => void;\r\n reject: (error: any) => void;\r\n}\r\n\r\n/**\r\n * SGS 3D 地图 H5 SDK 入口类\r\n *\r\n *

通过 iframe 嵌入 /h5-sdk 底座页面,以 postMessage 进行双向通信。\r\n *

所有需要后端数据的操作均通过 postMessage 指令由底座代理,\r\n * Demo / 宿主不允许直连后端 API。\r\n *\r\n * @version 2.4.1\r\n */\r\nexport default class SGSMapSDK extends EventEmitter {\r\n public static utils = utils;\r\n private container: HTMLElement;\r\n private sdkUrl: string;\r\n private floorId: string | number;\r\n private mapId: string | number;\r\n private targetOrigin: string;\r\n private iframe: HTMLIFrameElement | null = null;\r\n private _isReady: boolean = false;\r\n private _isDestroyed: boolean = false;\r\n /** ready 失败终态:iframe 加载失败或 ready 超时后置 true,后续命令直接 reject */\r\n private _readyFailed: boolean = false;\r\n /** ready 失败时的错误对象,用于后续命令的立即 reject */\r\n private _readyFailedError: { code: string; message: string } | null = null;\r\n private defaultTimeout: number;\r\n private readyTimeoutMs: number;\r\n /** 已 ready 后正在等待响应的命令队列 */\r\n private promiseQueue: {\r\n [key: string]: {\r\n resolve: (data: any) => void;\r\n reject: (error: any) => void;\r\n };\r\n } = {};\r\n /** ready 前暂存的异步命令队列 */\r\n private _pendingCommands: PendingCommand[] = [];\r\n /** ready 前暂存的 fire-and-forget 命令队列 */\r\n private _pendingFireAndForget: Array<{ action: string; payload?: any }> = [];\r\n private _messageListener: ((event: MessageEvent) => void) | null = null;\r\n /** ready 超时计时器 */\r\n private _readyTimer: ReturnType | null = null;\r\n /** 🔧 P0: HELLO 重试定时器 — 解决 iframe onload 与 React useEffect 时序竞争 */\r\n private _helloInterval: ReturnType | null = null;\r\n\r\n /** ENGINE_READY 返回的 payload(供外部读取) */\r\n public readyPayload: any = null;\r\n\r\n /** ready Promise 的 resolve/reject,保证 whenReady() 返回同一个 Promise */\r\n private _readyResolve: ((value: any) => void) | null = null;\r\n private _readyReject: ((reason: any) => void) | null = null;\r\n private _readyPromise: Promise;\r\n\r\n /** 性能追踪字段 */\r\n private _sdkCreatedAt: number;\r\n private _iframeDomReadyMs: number | null = null;\r\n private _engineReadyMs: number | null = null;\r\n\r\n constructor(options: SGSMapSDKOptions) {\r\n super();\r\n this._sdkCreatedAt = Date.now();\r\n\r\n const containerEl =\r\n typeof options.container === 'string'\r\n ? document.getElementById(options.container)\r\n : options.container;\r\n\r\n if (!containerEl) {\r\n throw new Error(`[SGSMapSDK] Cannot find container: ${options.container}`);\r\n }\r\n\r\n this.container = containerEl;\r\n this.sdkUrl = options.sdkUrl || '/h5-sdk';\r\n this.floorId = options.floorId || 1;\r\n this.mapId = options.mapId || 1;\r\n this.defaultTimeout = options.timeout || DEFAULT_CMD_TIMEOUT;\r\n this.readyTimeoutMs = options.readyTimeout || DEFAULT_READY_TIMEOUT;\r\n\r\n this.targetOrigin = options.targetOrigin as string;\r\n if (!this.targetOrigin) {\r\n if (this.sdkUrl.startsWith('http')) {\r\n this.targetOrigin = new URL(this.sdkUrl).origin;\r\n } else if (typeof window !== 'undefined') {\r\n this.targetOrigin = window.location.origin;\r\n } else {\r\n this.targetOrigin = '*';\r\n }\r\n }\r\n\r\n // 初始化 ready Promise(全局唯一,whenReady() 返回同一个)\r\n this._readyPromise = new Promise((resolve, reject) => {\r\n this._readyResolve = resolve;\r\n this._readyReject = reject;\r\n });\r\n\r\n this._initIframeBridge();\r\n }\r\n\r\n // ==================== iframe 桥接初始化 ====================\r\n\r\n private _initIframeBridge() {\r\n const iframe = document.createElement('iframe');\r\n\r\n // 拷贝父级 URL 上的查询参数传递给 iframe 底座\r\n const iframeUrl = new URL(\r\n this.sdkUrl,\r\n typeof window !== 'undefined' ? window.location.href : 'http://localhost'\r\n );\r\n if (typeof window !== 'undefined') {\r\n const parentParams = new URLSearchParams(window.location.search);\r\n parentParams.forEach((value, key) => {\r\n iframeUrl.searchParams.set(key, value);\r\n });\r\n }\r\n // 附带 floorId\r\n if (!iframeUrl.searchParams.has('floorId')) {\r\n iframeUrl.searchParams.set('floorId', String(this.floorId));\r\n }\r\n\r\n iframe.src = iframeUrl.toString();\r\n iframe.style.cssText = 'width:100%;height:100%;border:none;background:transparent;';\r\n\r\n this.container.innerHTML = '';\r\n this.container.appendChild(iframe);\r\n this.iframe = iframe;\r\n\r\n // ── 消息监听 ──\r\n this._messageListener = (event: MessageEvent) => {\r\n if (this.targetOrigin !== '*' && event.origin !== this.targetOrigin) {\r\n return; // 静默忽略不匹配的来源\r\n }\r\n\r\n if (!this.iframe || event.source !== this.iframe.contentWindow) {\r\n return;\r\n }\r\n\r\n const data = event.data;\r\n if (!data) return;\r\n\r\n // ── 事件类型消息 ──\r\n if (data.type === 'SGS_MAP_EVENT') {\r\n const { action, payload } = data;\r\n if (action === 'ON_POI_CLICK') {\r\n this.emit('poiClick', payload);\r\n } else if (action === 'ON_POI_DETAIL') {\r\n this.emit('poiDetail', payload);\r\n } else if (action === 'ON_POI_NAVIGATE') {\r\n this.emit('poiNavigate', payload);\r\n } else if (action === 'ON_POI_CLOSE') {\r\n this.emit('poiClose', payload);\r\n } else if (action === 'ON_SPACE_CLICK') {\r\n this.emit('spaceClick', payload);\r\n } else if (action === 'ON_SPACE_DETAIL') {\r\n this.emit('spaceDetail', payload);\r\n } else if (action === 'ON_SPACE_NAVIGATE') {\r\n this.emit('spaceNavigate', payload);\r\n } else if (action === 'ON_SPACE_CLOSE') {\r\n this.emit('spaceClose', payload);\r\n } else if (action === 'ENGINE_READY') {\r\n this._onEngineReady(payload);\r\n } else if (action === 'ON_FLOOR_CHANGED') {\r\n this.emit('floorChanged', payload);\r\n } else if (action === 'ON_ROUTE_TRANSFER') {\r\n this.emit('routeTransfer', payload);\r\n } else if (action === 'ON_MODEL_LOADING') {\r\n this.emit('modelLoading', payload);\r\n } else if (action === 'ON_MODEL_READY') {\r\n this.emit('modelReady', payload);\r\n } else if (action === 'ON_MODEL_ERROR') {\r\n this.emit('modelError', payload);\r\n } else if (action === 'ON_FLOOR_BUNDLE_READY') {\r\n this.emit('floorBundleReady', payload);\r\n } else if (action === 'ON_ROUTE_READY') {\r\n this.emit('routeReady', payload);\r\n } else if (action === 'ON_ROAMING_PROGRESS') {\r\n this.emit('roamingProgress', payload);\r\n } else if (action === 'ON_ROAMING_DONE') {\r\n this.emit('roamingDone', payload);\r\n }\r\n }\r\n\r\n // ── 命令响应消息 ──\r\n if (data.type === 'SGS_MAP_RESPONSE') {\r\n const { action, requestId, payload, error } = data;\r\n const resolver = this.promiseQueue[requestId];\r\n if (resolver) {\r\n if (payload && payload.success !== false) {\r\n resolver.resolve(payload);\r\n } else {\r\n const errObj = error || { code: 'ERR_FAILED', message: `${action} command failed` };\r\n resolver.reject(errObj);\r\n }\r\n delete this.promiseQueue[requestId];\r\n }\r\n }\r\n };\r\n\r\n window.addEventListener('message', this._messageListener);\r\n\r\n // ── iframe 加载完成 → 发送 HELLO 握手 ──\r\n iframe.onload = () => {\r\n this._iframeDomReadyMs = Date.now() - this._sdkCreatedAt;\r\n if (this._isReady) return;\r\n\r\n // 🔧 P0 修复:HELLO 重试机制\r\n // iframe onload 触发时 React useEffect 可能尚未挂载 message listener,\r\n // 单次 HELLO 会丢失。改为每 500ms 重试,直到 ENGINE_READY 到达。\r\n const sendHello = () => {\r\n if (this._isReady || this._isDestroyed) {\r\n if (this._helloInterval) { clearInterval(this._helloInterval); this._helloInterval = null; }\r\n return;\r\n }\r\n this._postCommand('HELLO', { protocolVersion: PROTOCOL_VERSION, sdkVersion: SDK_VERSION });\r\n };\r\n sendHello(); // 立即发第一次\r\n this._helloInterval = setInterval(sendHello, 500);\r\n console.log(`[SGSMapSDK] Sent HELLO handshake (v${SDK_VERSION}), will retry every 500ms until ENGINE_READY...`);\r\n };\r\n\r\n // ── iframe 加载失败 → reject ready + 清空命令队列 ──\r\n iframe.onerror = (err) => {\r\n console.error('[SGSMapSDK] Iframe load error', err);\r\n const error = { code: 'ERR_IFRAME_LOAD' as const, message: 'Failed to load map engine iframe.' };\r\n this.emit('loadError', { type: 'iframe_error', detail: error.message });\r\n this._rejectReady(error);\r\n };\r\n\r\n // ── ready 超时计时器 ──\r\n this._readyTimer = setTimeout(() => {\r\n if (!this._isReady && !this._isDestroyed) {\r\n const error = {\r\n code: 'ERR_READY_TIMEOUT' as const,\r\n message: `ENGINE_READY not received within ${this.readyTimeoutMs}ms`\r\n };\r\n console.error(`[SGSMapSDK] ${error.message}`);\r\n this._rejectReady(error);\r\n }\r\n }, this.readyTimeoutMs);\r\n }\r\n\r\n // ==================== ready 生命周期管理 ====================\r\n\r\n /**\r\n * ENGINE_READY 到达时的处理\r\n */\r\n private _onEngineReady(payload: any) {\r\n if (this._isReady) return; // 防重入\r\n\r\n this._isReady = true;\r\n this.readyPayload = payload;\r\n this._engineReadyMs = Date.now() - this._sdkCreatedAt;\r\n\r\n // 🔧 P0: 清除 HELLO 重试定时器\r\n if (this._helloInterval) {\r\n clearInterval(this._helloInterval);\r\n this._helloInterval = null;\r\n }\r\n\r\n // 清除 ready 超时计时器\r\n if (this._readyTimer) {\r\n clearTimeout(this._readyTimer);\r\n this._readyTimer = null;\r\n }\r\n\r\n // resolve ready Promise\r\n if (this._readyResolve) {\r\n this._readyResolve(payload);\r\n this._readyResolve = null;\r\n this._readyReject = null;\r\n }\r\n\r\n // 触发 ready 事件\r\n this.emit('ready', payload);\r\n console.log('[SGSMapSDK] Handshake complete: Engine Ready received ✅');\r\n\r\n // 排空命令队列 — ready 前暂存的命令逐个发送\r\n this._flushPendingCommands();\r\n }\r\n\r\n /**\r\n * reject ready Promise 并清空所有挂起命令,设置失败终态\r\n */\r\n private _rejectReady(error: { code: string; message: string }) {\r\n // 设置失败终态 — 后续所有新命令直接 reject\r\n this._readyFailed = true;\r\n this._readyFailedError = error;\r\n\r\n // reject ready Promise\r\n if (this._readyReject) {\r\n this._readyReject(error);\r\n this._readyResolve = null;\r\n this._readyReject = null;\r\n }\r\n\r\n // 清空 ready 超时计时器\r\n if (this._readyTimer) {\r\n clearTimeout(this._readyTimer);\r\n this._readyTimer = null;\r\n }\r\n\r\n // reject 所有命令队列 + 清空 fire-and-forget 队列\r\n this._rejectAllPending(error.code, error.message);\r\n this._pendingFireAndForget.splice(0);\r\n }\r\n\r\n /**\r\n * 排空 ready 前暂存的命令队列 — 逐个以 _postCommandAsync 发送\r\n */\r\n private _flushPendingCommands() {\r\n // 1. 排空 fire-and-forget 队列\r\n const fireAndForget = this._pendingFireAndForget.splice(0);\r\n for (const cmd of fireAndForget) {\r\n this._postCommand(cmd.action, cmd.payload);\r\n }\r\n\r\n // 2. 排空异步命令队列\r\n const pending = this._pendingCommands.splice(0);\r\n for (const cmd of pending) {\r\n this._postCommandAsyncInner(cmd.action, cmd.payload, cmd.timeout)\r\n .then(cmd.resolve)\r\n .catch(cmd.reject);\r\n }\r\n\r\n const total = fireAndForget.length + pending.length;\r\n if (total > 0) {\r\n console.log(`[SGSMapSDK] Flushed ${total} pending commands after ready (${fireAndForget.length} fire-and-forget, ${pending.length} async)`);\r\n }\r\n }\r\n\r\n /**\r\n * reject 所有挂起的 Promise 和命令队列\r\n */\r\n private _rejectAllPending(code: string, message: string) {\r\n // 1. reject 已发送的 promiseQueue\r\n Object.keys(this.promiseQueue).forEach((requestId) => {\r\n const resolver = this.promiseQueue[requestId];\r\n if (resolver) {\r\n resolver.reject({ code, message });\r\n }\r\n delete this.promiseQueue[requestId];\r\n });\r\n\r\n // 2. reject 命令队列\r\n const pending = this._pendingCommands.splice(0);\r\n for (const cmd of pending) {\r\n cmd.reject({ code, message });\r\n }\r\n }\r\n\r\n // ==================== 公共生命周期方法 ====================\r\n\r\n /**\r\n * 返回 ready Promise(全局唯一,多次调用返回同一个)。\r\n * 别名:ready()\r\n *\r\n * @example\r\n * await sdk.whenReady();\r\n * sdk.changeFloor(floorId);\r\n */\r\n whenReady(): Promise {\r\n return this._readyPromise;\r\n }\r\n\r\n /**\r\n * whenReady 的别名\r\n */\r\n ready(): Promise {\r\n return this._readyPromise;\r\n }\r\n\r\n /** 获取 ready 状态 */\r\n getIsReady(): boolean {\r\n return this._isReady;\r\n }\r\n\r\n /** 获取当前楼层 ID */\r\n getCurrentFloor(): string | number {\r\n return this.floorId;\r\n }\r\n\r\n /** 获取当前 mapId */\r\n getMapId(): string | number {\r\n return this.mapId;\r\n }\r\n\r\n /** 获取 SDK 版本号 */\r\n getVersion(): string {\r\n return SDK_VERSION;\r\n }\r\n\r\n // ==================== 核心通信方法 ====================\r\n\r\n private _generateRequestId(): string {\r\n return `req_sdk_${Date.now()}_${Math.floor(Math.random() * 100000)}`;\r\n }\r\n\r\n private _postCommand(action: string, payload?: any, requestId?: string | null) {\r\n if (!this.iframe || !this.iframe.contentWindow) {\r\n console.error('[SGSMapSDK] SDK Iframe is not available!');\r\n return;\r\n }\r\n\r\n this.iframe.contentWindow.postMessage(\r\n {\r\n type: 'SGS_MAP_COMMAND',\r\n action,\r\n requestId,\r\n payload\r\n },\r\n this.targetOrigin\r\n );\r\n }\r\n\r\n /**\r\n * 异步命令发送(外部入口)— 支持命令队列\r\n *\r\n *

状态机优先级:destroyed > readyFailed > !ready(入队) > ready(发送)\r\n */\r\n private _postCommandAsync(action: string, payload?: any, timeout: number = this.defaultTimeout): Promise {\r\n // destroyed 后直接 reject\r\n if (this._isDestroyed) {\r\n return Promise.reject({\r\n code: 'ERR_DESTROYED',\r\n message: 'SGSMapSDK has been destroyed. Cannot send commands.'\r\n });\r\n }\r\n\r\n // ready 失败终态 — 立即 reject,不再入队\r\n if (this._readyFailed) {\r\n return Promise.reject(\r\n this._readyFailedError || { code: 'ERR_NOT_READY', message: 'SDK ready failed.' }\r\n );\r\n }\r\n\r\n // ready 前入队\r\n if (!this._isReady) {\r\n return new Promise((resolve, reject) => {\r\n this._pendingCommands.push({\r\n action,\r\n payload,\r\n timeout,\r\n resolve,\r\n reject\r\n });\r\n console.log(`[SGSMapSDK] Command \"${action}\" queued (waiting for ready). Queue size: ${this._pendingCommands.length}`);\r\n });\r\n }\r\n\r\n // ready 后直接发送\r\n return this._postCommandAsyncInner(action, payload, timeout);\r\n }\r\n\r\n /**\r\n * fire-and-forget 命令发送(void 方法统一入口)\r\n *\r\n *

受 destroyed/readyFailed/!ready 状态保护:\r\n * - destroyed/readyFailed:静默丢弃 + console.warn\r\n * - !ready:暂存到 _pendingFireAndForget,ready 后自动发送\r\n * - ready:立即发送\r\n */\r\n private _postCommandSafe(action: string, payload?: any): void {\r\n if (this._isDestroyed) {\r\n console.warn(`[SGSMapSDK] Command \"${action}\" ignored: SDK destroyed.`);\r\n return;\r\n }\r\n if (this._readyFailed) {\r\n console.warn(`[SGSMapSDK] Command \"${action}\" ignored: SDK ready failed.`);\r\n return;\r\n }\r\n if (!this._isReady) {\r\n this._pendingFireAndForget.push({ action, payload });\r\n console.log(`[SGSMapSDK] Command \"${action}\" queued (fire-and-forget, waiting for ready). Queue size: ${this._pendingFireAndForget.length}`);\r\n return;\r\n }\r\n this._postCommand(action, payload);\r\n }\r\n\r\n /**\r\n * 异步命令发送(内部实现)— 已确认 ready\r\n */\r\n private _postCommandAsyncInner(action: string, payload?: any, timeout: number = this.defaultTimeout): Promise {\r\n const requestId = this._generateRequestId();\r\n return new Promise((resolve, reject) => {\r\n const timer = setTimeout(() => {\r\n if (this.promiseQueue[requestId]) {\r\n this.promiseQueue[requestId].reject({\r\n code: 'ERR_TIMEOUT',\r\n message: `Command \"${action}\" timed out after ${timeout}ms`\r\n });\r\n delete this.promiseQueue[requestId];\r\n }\r\n }, timeout);\r\n\r\n this.promiseQueue[requestId] = {\r\n resolve: (data) => {\r\n clearTimeout(timer);\r\n resolve(data as T);\r\n },\r\n reject: (error) => {\r\n clearTimeout(timer);\r\n reject(error);\r\n }\r\n };\r\n\r\n this._postCommand(action, payload, requestId);\r\n });\r\n }\r\n\r\n // ==================== v2.0 核心指令 API ====================\r\n\r\n /** 聚焦到指定要素节点 */\r\n focusTo(node: SgsMapNode | string) {\r\n if (typeof node === 'string') {\r\n this._postCommandSafe('FOCUS_TO', { nodeName: node });\r\n } else {\r\n this._postCommandSafe('FOCUS_TO', node);\r\n }\r\n }\r\n\r\n /** 切换楼层 */\r\n changeFloor(floorId: string | number, timeout?: number, options?: { forceReload?: boolean }): Promise<{ success: boolean; floorId: string | number }> {\r\n return this._postCommandAsync<{ success: boolean; floorId: string | number }>('CHANGE_FLOOR', { floorId, ...options }, timeout).then((res) => {\r\n this.floorId = floorId;\r\n return res;\r\n });\r\n }\r\n\r\n /** 添加业务图钉 */\r\n addMarker(config: MarkerConfig) {\r\n this._postCommandSafe('ADD_MARKER', config);\r\n }\r\n\r\n /** 移除指定图钉 */\r\n removeMarker(markerId: string) {\r\n this._postCommandSafe('REMOVE_MARKER', { markerId });\r\n }\r\n\r\n /** 清空所有业务图钉 */\r\n clearMarkers() {\r\n this._postCommandSafe('CLEAR_MARKERS');\r\n }\r\n\r\n /** 高亮多边形轮廓 */\r\n highlightPolygon(payload: string | object) {\r\n if (typeof payload === 'string') {\r\n this._postCommandSafe('HIGHLIGHT_POLYGON', { nodeName: payload });\r\n } else {\r\n this._postCommandSafe('HIGHLIGHT_POLYGON', payload);\r\n }\r\n }\r\n\r\n /** 寻路规划(v1 兼容) */\r\n planRoute(from: SgsMapNode | string, to: SgsMapNode | string, options?: any, timeout: number = 5000): Promise {\r\n const startNode = typeof from === 'string' ? from : undefined;\r\n const endNode = typeof to === 'string' ? to : undefined;\r\n\r\n const payload: any = {};\r\n if (startNode) payload.startNode = startNode;\r\n else payload.from = from;\r\n\r\n if (endNode) payload.endNode = endNode;\r\n else payload.to = to;\r\n\r\n if (options) payload.options = options;\r\n\r\n return this._postCommandAsync('PLAN_ROUTE', payload, timeout);\r\n }\r\n\r\n /** 寻路规划 V2(结构化参数) */\r\n planRouteV2(from: SgsMapNode, to: SgsMapNode, options?: any, timeout: number = 5000): Promise {\r\n const payload = { from, to, options };\r\n return this._postCommandAsync('PLAN_ROUTE', payload, timeout);\r\n }\r\n\r\n /** 清除当前路线 */\r\n clearRoute() {\r\n this._postCommandSafe('CLEAR_ROUTE');\r\n }\r\n\r\n /** 渲染已有路线点,供外部宿主直接展示自定义路线 */\r\n renderRoutePoints(\r\n pathPoints: Array<{ x: number; y: number; z: number }>,\r\n options?: any,\r\n timeout: number = 12000\r\n ): Promise {\r\n return this._postCommandAsync('RENDER_ROUTE_POINTS', { ...options, pathPoints }, timeout);\r\n }\r\n\r\n /** 启动路线漫游;pathPoints 为空时,基座会优先使用当前已渲染路线 */\r\n startRoaming(\r\n pathPoints: Array<{ x: number; y: number; z: number }> = [],\r\n options?: SgsRouteRoamingOptions,\r\n timeout: number = 12000\r\n ): Promise<{ success: boolean; mode?: string }> {\r\n return this._postCommandAsync<{ success: boolean; mode?: string }>('START_ROAMING', { ...options, pathPoints }, timeout);\r\n }\r\n\r\n /** 停止路线漫游 */\r\n stopRoaming(timeout?: number): Promise<{ success: boolean }> {\r\n return this._postCommandAsync<{ success: boolean }>('STOP_ROAMING', {}, timeout);\r\n }\r\n\r\n /** 暂停或继续路线漫游 */\r\n toggleRoamingPause(timeout?: number): Promise<{ success: boolean; paused?: boolean }> {\r\n return this._postCommandAsync<{ success: boolean; paused?: boolean }>('TOGGLE_ROAMING_PAUSE', {}, timeout);\r\n }\r\n\r\n // ==================== SDK 2.4.1 高层导航 API ====================\r\n\r\n /**\r\n * 导航至POI(SDK 2.4.1)\r\n *\r\n * 支持从坐标或POI导航到目标POI,后端自动查询POI坐标。\r\n *\r\n * @param from 起点(POI ID字符串/数字 或 坐标对象)\r\n * @param toPoiId 目标POI ID(建议使用string避免Snowflake精度丢失)\r\n * @param options 路径选项(wheelchair, preferElevator)\r\n * @param timeout 超时时间(毫秒)\r\n */\r\n planRouteToPoi(from: string | number | SdkRoutePoint, toPoiId: string | number, options?: RouteOptions, timeout: number = 5000): Promise {\r\n // 将 string/number 转为 { poiId: from }\r\n const fromPoint: SdkRoutePoint = (typeof from === 'string' || typeof from === 'number')\r\n ? { poiId: from }\r\n : from;\r\n\r\n const payload = {\r\n from: fromPoint,\r\n toPoiId,\r\n wheelchair: options?.wheelchair ? 1 : 0,\r\n preferElevator: options?.preferElevator,\r\n };\r\n return this._postCommandAsync('PLAN_ROUTE_TO_POI', payload, timeout);\r\n }\r\n\r\n /**\r\n * POI间导航(SDK 2.4.1)\r\n *\r\n * 两个POI之间的导航,后端自动查询POI坐标。\r\n *\r\n * @param fromPoiId 起点POI ID\r\n * @param toPoiId 终点POI ID\r\n * @param options 路径选项(wheelchair, preferElevator)\r\n * @param timeout 超时时间(毫秒)\r\n */\r\n planRouteBetweenPois(fromPoiId: string | number, toPoiId: string | number, options?: RouteOptions, timeout: number = 5000): Promise {\r\n const payload = {\r\n fromPoiId,\r\n toPoiId,\r\n wheelchair: options?.wheelchair ? 1 : 0,\r\n preferElevator: options?.preferElevator,\r\n };\r\n return this._postCommandAsync('PLAN_ROUTE_BETWEEN_POIS', payload, timeout);\r\n }\r\n\r\n // ==================== 状态和视图控制 ====================\r\n getState(): Promise {\r\n return this._postCommandAsync('GET_STATE');\r\n }\r\n\r\n /** 重置视角 */\r\n resetView(timeout?: number): Promise<{ success: boolean }> {\r\n return this._postCommandAsync<{ success: boolean }>('RESET_VIEW', {}, timeout);\r\n }\r\n\r\n /** 设置 2D/3D 视图模式 */\r\n setViewMode(mode: '2d' | '3d', timeout?: number): Promise<{ success: boolean; mode: string }> {\r\n return this._postCommandAsync<{ success: boolean; mode: string }>('SET_VIEW_MODE', { mode }, timeout);\r\n }\r\n\r\n /** 切换图层可见性 */\r\n toggleLayer(types: string[], visible: boolean, timeout?: number): Promise<{ success: boolean; visibleTypes: string[] }> {\r\n return this._postCommandAsync<{ success: boolean; visibleTypes: string[] }>('TOGGLE_LAYER', { types, visible }, timeout);\r\n }\r\n\r\n /** 设置可见图层类型列表 */\r\n setVisibleLayers(types: string[], timeout?: number): Promise<{ success: boolean; visibleTypes: string[] }> {\r\n return this._postCommandAsync<{ success: boolean; visibleTypes: string[] }>('SET_VISIBLE_TYPES', { types }, timeout);\r\n }\r\n\r\n /** 设置路线展示视图是否活跃 */\r\n setRouteViewActive(active: boolean, timeout?: number): Promise<{ success: boolean; active: boolean }> {\r\n return this._postCommandAsync<{ success: boolean; active: boolean }>('SET_ROUTE_VIEW_ACTIVE', { active }, timeout);\r\n }\r\n\r\n // ==================== v2.2.0 / v2.2.0 数据消费 API ====================\r\n\r\n /** 获取地图 Manifest 清单 */\r\n getManifest(timeout?: number): Promise {\r\n return this._postCommandAsync('GET_MANIFEST', {}, timeout);\r\n }\r\n\r\n /** 加载指定楼层的完整 Bundle 数据(含 POI/空间面/讲解点/路网) */\r\n loadFloorBundle(floorId: string | number, timeout?: number): Promise {\r\n return this._postCommandAsync('LOAD_FLOOR_BUNDLE', { floorId }, timeout);\r\n }\r\n\r\n /** 获取指定楼层的 POI 列表 */\r\n getFloorPois(floorId: string | number, timeout?: number): Promise {\r\n return this._postCommandAsync('GET_FLOOR_POIS', { floorId }, timeout)\r\n .then((res) => Array.isArray(res) ? res : (res?.pois || []));\r\n }\r\n\r\n /** 获取指定楼层的起终点候选目的地列表(门点/POI/空间等吸附后目的地) */\r\n getNavigablePlaces(floorId: string | number, timeout?: number): Promise {\r\n return this._postCommandAsync('GET_NAVIGABLE_PLACES', { floorId }, timeout)\r\n .then((res) => Array.isArray(res) ? res : (res?.navigablePlaces || res?.places || []));\r\n }\r\n\r\n /** 获取当前楼层的 POI 列表(根据 floorCode 参数) */\r\n getPois(floorCode: string, timeout?: number): Promise {\r\n return this._postCommandAsync('GET_POIS', { floorCode }, timeout);\r\n }\r\n\r\n /** 获取指定楼层的空间面列表 */\r\n getSpaces(floorId: string | number, timeout?: number): Promise {\r\n return this._postCommandAsync('GET_SPACES', { floorId }, timeout)\r\n .then((res) => Array.isArray(res) ? res : (res?.spaces || []));\r\n }\r\n\r\n /** 获取指定楼层的已标定讲解点列表(地图渲染入口) */\r\n getGuideStops(floorId: string | number, timeout?: number): Promise {\r\n return this._postCommandAsync('GET_GUIDE_STOPS', { floorId }, timeout)\r\n .then((res) => Array.isArray(res) ? res : (res?.guideStops || []));\r\n }\r\n\r\n /** 获取指定展厅的已标定讲解点列表(业务导览入口) */\r\n getGuideStopsByHall(hallId: string | number, timeout?: number): Promise {\r\n return this._postCommandAsync('GET_GUIDE_STOPS_BY_HALL', { hallId }, timeout)\r\n .then((res) => Array.isArray(res) ? res : (res?.guideStops || []));\r\n }\r\n\r\n /** 预加载指定楼层的模型资源(不渲染) */\r\n preloadFloor(floorId: string | number, timeout?: number): Promise<{ cached: boolean }> {\r\n return this._postCommandAsync<{ cached: boolean }>('PRELOAD_FLOOR', { floorId }, timeout);\r\n }\r\n\r\n /** 获取性能统计信息 */\r\n getPerformanceStats(timeout?: number): Promise {\r\n return this._postCommandAsync('GET_PERFORMANCE_STATS', {}, timeout).then(stats => ({\r\n sdkCreatedAt: this._sdkCreatedAt,\r\n iframeDomReadyMs: this._iframeDomReadyMs ?? undefined,\r\n engineReadyMs: this._engineReadyMs ?? undefined,\r\n ...stats\r\n }));\r\n }\r\n\r\n // ==================== v2.3.0 诊断接口 ====================\r\n\r\n /** 获取地图级诊断信息 */\r\n getDiagnostics(timeout?: number): Promise {\r\n return this._postCommandAsync('GET_DIAGNOSTICS', {}, timeout);\r\n }\r\n\r\n /** 获取楼层级诊断信息 */\r\n getFloorDiagnostics(floorId: string | number, timeout?: number): Promise {\r\n return this._postCommandAsync('GET_FLOOR_DIAGNOSTICS', { floorId }, timeout);\r\n }\r\n\r\n /** 清空客户端模型缓存 */\r\n clearModelCache(): void {\r\n this._postCommandSafe('CLEAR_MODEL_CACHE');\r\n }\r\n\r\n // ==================== v2.4.0 新增 API ====================\r\n\r\n /** 获取指定楼层的业务 POI 列表 */\r\n getBusinessPois(floorId: string | number, options?: { types?: string[]; keyword?: string; limit?: number }, timeout?: number): Promise {\r\n return this._postCommandAsync('GET_BUSINESS_POIS', { floorId, options }, timeout)\r\n .then((res) => Array.isArray(res) ? res : (res?.pois || []));\r\n }\r\n\r\n /** 查询 POI 列表 */\r\n queryPois(params: SgsPoiQueryParams, timeout?: number): Promise {\r\n return this._postCommandAsync('QUERY_POIS', params, timeout)\r\n .then((res) => Array.isArray(res) ? res : (res?.pois || []));\r\n }\r\n\r\n /** 获取特色路线列表 */\r\n getFeaturedRoutes(mapId?: string | number, options?: { limit?: number }, timeout?: number): Promise {\r\n return this._postCommandAsync('GET_FEATURED_ROUTES', { mapId, options }, timeout)\r\n .then((res) => Array.isArray(res) ? res : (res?.routes || []));\r\n }\r\n\r\n /** 获取特色路线详情 */\r\n getFeaturedRoute(routeId: string | number, timeout?: number): Promise {\r\n return this._postCommandAsync('GET_FEATURED_ROUTE', { routeId }, timeout)\r\n .then((res) => res?.route || res);\r\n }\r\n\r\n /** 生成并展示精品路线预览 */\r\n planFeaturedRoutePreview(\r\n payload: SgsFeaturedRoutePreviewPayload,\r\n timeout: number = 15000\r\n ): Promise {\r\n return this._postCommandAsync('PLAN_FEATURED_ROUTE_PREVIEW', payload, timeout);\r\n }\r\n\r\n /** 显示动线 */\r\n showFlowline(routeId: string | number, options?: SgsFlowlineOptions, timeout?: number): Promise<{ success: boolean; routeId: string | number }> {\r\n return this._postCommandAsync<{ success: boolean; routeId: string | number }>('SHOW_FLOWLINE', { routeId, options }, timeout);\r\n }\r\n\r\n /** 清除动线 */\r\n clearFlowline(): void {\r\n this._postCommandSafe('CLEAR_FLOWLINE');\r\n }\r\n\r\n // ==================== @deprecated 兼容层 ====================\r\n // 以下方法已废弃,保留 API 签名避免外部接入方断裂。\r\n // 内部返回 ERR_UNSUPPORTED,不进入任何发布路径。\r\n\r\n /** @deprecated 使用 getManifest() 替代 */\r\n getReleaseManifest(timeout?: number): Promise {\r\n console.warn('[SGSMapSDK] getReleaseManifest() is deprecated. Use getManifest() instead.');\r\n return Promise.reject({ code: 'ERR_UNSUPPORTED', message: 'getReleaseManifest() is deprecated. Use getManifest() instead.' });\r\n }\r\n\r\n /** @deprecated 已废弃,无替代方法 */\r\n getModelResources(timeout?: number): Promise {\r\n console.warn('[SGSMapSDK] getModelResources() is deprecated.');\r\n return Promise.reject({ code: 'ERR_UNSUPPORTED', message: 'getModelResources() is deprecated.' });\r\n }\r\n\r\n /** @deprecated 已废弃,无替代方法 */\r\n getLayerTree(timeout?: number): Promise {\r\n console.warn('[SGSMapSDK] getLayerTree() is deprecated.');\r\n return Promise.reject({ code: 'ERR_UNSUPPORTED', message: 'getLayerTree() is deprecated.' });\r\n }\r\n\r\n /** @deprecated 已废弃,无替代方法 */\r\n loadMockReleaseRoute(timeout?: number): Promise {\r\n console.warn('[SGSMapSDK] loadMockReleaseRoute() is deprecated.');\r\n return Promise.reject({ code: 'ERR_UNSUPPORTED', message: 'loadMockReleaseRoute() is deprecated.' });\r\n }\r\n\r\n // ==================== 销毁 ====================\r\n\r\n /**\r\n * 销毁 SDK 实例\r\n *\r\n *

清理 iframe、移除事件监听器、reject 所有挂起的 Promise。\r\n *

销毁后调用任何命令方法都会 reject ERR_DESTROYED。\r\n */\r\n destroy() {\r\n if (this._isDestroyed) return; // 防重复销毁\r\n this._isDestroyed = true;\r\n\r\n // 清除 ready 超时计时器\r\n if (this._readyTimer) {\r\n clearTimeout(this._readyTimer);\r\n this._readyTimer = null;\r\n }\r\n\r\n // 🔧 P0: 清除 HELLO 重试定时器\r\n if (this._helloInterval) {\r\n clearInterval(this._helloInterval);\r\n this._helloInterval = null;\r\n }\r\n\r\n // reject ready Promise(如果还没 resolve/reject)\r\n if (this._readyReject) {\r\n this._readyReject({\r\n code: 'ERR_DESTROYED',\r\n message: 'SGSMapSDK has been destroyed before ready.'\r\n });\r\n this._readyResolve = null;\r\n this._readyReject = null;\r\n }\r\n\r\n // reject 所有挂起的命令\r\n this._rejectAllPending('ERR_DESTROYED', 'SGSMapSDK has been destroyed. Pending promises were rejected.');\r\n\r\n // 移除 iframe\r\n if (this.iframe) {\r\n if (this.iframe.parentNode) {\r\n this.iframe.parentNode.removeChild(this.iframe);\r\n }\r\n this.iframe = null;\r\n }\r\n\r\n // 移除事件监听器\r\n if (this._messageListener) {\r\n window.removeEventListener('message', this._messageListener);\r\n this._messageListener = null;\r\n }\r\n\r\n // 移除所有 EventEmitter 监听器\r\n this.removeAllListeners();\r\n\r\n this._isReady = false;\r\n console.log('[SGSMapSDK] Destroyed successfully. Cleaned up iframe, listeners and pending queues.');\r\n }\r\n}\r\n\r\n// 全局暴露(IIFE 产物使用)\r\nif (typeof window !== 'undefined') {\r\n (window as any).SGSMapSDK = SGSMapSDK;\r\n}\r\n"],"mappings":"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","names":["EventEmitter","event","listener","l","wrapper","payload","data","e","utils_exports","__export","isIndoorNavigableFloor","sortIndoorFloors","floorCode","floors","FLOOR_ORDER","indoorFloors","f","getFloorIndex","code","matchedIndex","numMatch","val","a","b","SDK_VERSION","PROTOCOL_VERSION","DEFAULT_READY_TIMEOUT","DEFAULT_CMD_TIMEOUT","SGSMapSDK","EventEmitter","options","containerEl","resolve","reject","iframe","iframeUrl","value","key","event","data","action","payload","requestId","error","resolver","errObj","sendHello","err","fireAndForget","cmd","pending","total","code","message","timeout","timer","node","floorId","res","config","markerId","from","to","startNode","endNode","pathPoints","toPoiId","fromPoiId","mode","types","visible","active","floorCode","hallId","stats","params","mapId","routeId","utils_exports"]} \ No newline at end of file diff --git a/static/sgs-map-sdk/package.json b/static/sgs-map-sdk/package.json index 0701bc8..685f2bd 100644 --- a/static/sgs-map-sdk/package.json +++ b/static/sgs-map-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@sgs/map-sdk", - "version": "2.4.0", + "version": "2.4.1", "description": "SGS Map SDK API contract copy for frontend-miniapp; iframe runtime is not enabled by this project", "main": "./index.global.js", "types": "./index.d.ts", @@ -14,6 +14,9 @@ "files": [ "index.global.js", "index.global.js.map", + "index.mjs", + "index.mjs.map", + "index.d.mts", "index.d.ts", "README.md", "CHANGELOG.md", diff --git a/static/sgs-map-sdk/sdk-quickstart.md b/static/sgs-map-sdk/sdk-quickstart.md index acef5a6..d43fdea 100644 --- a/static/sgs-map-sdk/sdk-quickstart.md +++ b/static/sgs-map-sdk/sdk-quickstart.md @@ -44,7 +44,14 @@ SDK 通过 `sdkUrl` 连接独立渲染基座。正式接入时请使用地图服 https://map.museum.com/h5-sdk ``` -本 SDK 发布包不附带产品化 Demo。Demo 将作为独立交付物在后续版本中重构,不作为当前 SDK 接入前置条件。 +发布包内附带 `demo/` 目录,是一个完整的 SDK 能力展示页面。运行方式: + +```bash +# 在 sgs-map-sdk-release 根目录下 +npx -y serve . -l 5555 +# 浏览器打开(?server= 指向运行中的地图服务) +# http://localhost:5555/demo/?server=http://localhost:3001 +``` --- diff --git a/tests/unit/SgsMapService.spec.ts b/tests/unit/SgsMapService.spec.ts new file mode 100644 index 0000000..b5b2a3f --- /dev/null +++ b/tests/unit/SgsMapService.spec.ts @@ -0,0 +1,78 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import type { + SgsMapEvents, + SgsMapSDKConstructor, + SgsMapSDKInstance +} from '@/types/sgs-map-sdk' + +const loadSgsMapScript = vi.hoisted(() => vi.fn()) + +vi.mock('@/services/sgs/SgsMapScriptLoader', () => ({ + loadSgsMapScript +})) + +import { SgsMapService } from '@/services/sgs/SgsMapService' + +const createHarness = async () => { + const listeners = new Map void>() + const sdk = { + on: vi.fn((event: keyof SgsMapEvents, listener: (payload: never) => void) => { + listeners.set(event, listener) + }), + off: vi.fn(), + getIsReady: vi.fn(() => true), + changeFloor: vi.fn().mockResolvedValue({ success: true, floorId: 'L2' }), + resetView: vi.fn().mockResolvedValue({ success: true }), + setViewMode: vi.fn().mockResolvedValue({ success: true, mode: '2d' }), + destroy: vi.fn() + } as unknown as SgsMapSDKInstance + const SDKConstructor = vi.fn(() => sdk) as unknown as SgsMapSDKConstructor + + loadSgsMapScript.mockResolvedValue(SDKConstructor) + + const service = new SgsMapService({ + container: {} as HTMLElement, + scriptUrl: '/static/sgs-map-sdk/index.global.js?v=2.4.1', + sdkUrl: '/h5-sdk', + targetOrigin: 'https://map.example.com', + floorId: 'L1', + timeout: 7000 + }) + const ready = service.init() + + await vi.waitFor(() => expect(SDKConstructor).toHaveBeenCalledOnce()) + listeners.get('ready')?.({ + engineVersion: '2.4.1', + protocolVersion: 2 + } as never) + await ready + + return { sdk, service } +} + +beforeEach(() => { + loadSgsMapScript.mockReset() +}) + +describe('SGS Map SDK 2.4.1 service adapter', () => { + it('透传楼层强制刷新选项和统一命令超时', async () => { + const { sdk, service } = await createHarness() + + await expect(service.changeFloor('L2', { forceReload: true })).resolves.toEqual({ + success: true, + floorId: 'L2' + }) + expect(sdk.changeFloor).toHaveBeenCalledWith('L2', 7000, { forceReload: true }) + }) + + it('返回异步视图命令结果并向上传递 SDK 错误', async () => { + const { sdk, service } = await createHarness() + + await expect(service.setViewMode('2d')).resolves.toEqual({ success: true, mode: '2d' }) + expect(sdk.setViewMode).toHaveBeenCalledWith('2d', 7000) + + vi.mocked(sdk.resetView).mockRejectedValueOnce(new Error('RESET_VIEW failed')) + await expect(service.resetView()).rejects.toThrow('RESET_VIEW failed') + expect(sdk.resetView).toHaveBeenCalledWith(7000) + }) +})