唯杰地图 logo唯杰地图
VJMAP
EN

SDK 使用(vjvpsdk:嵌入、程序化操作、脚本、自定义组件)

vjvp/references/platform/sdk.md 16.4 KB148 行
原文

包名:对外发布的 npm 包名是 vjvpsdk(CSS 子路径 vjvpsdk/vp-runtime.css)。外部工程一律 import ... from 'vjvpsdk'(例外:编辑器导出的「二次开发工程包」不依赖 vjvpsdk,'@vp/sdk' 由 vite 别名指向随包 vendor/vp-runtime/vp-runtime.mjs,见 deploy-and-integrate.md §3)。

1. 挂载

import { createProject, createVpViewer, type VpProjectBody } from 'vjvpsdk'

const body = createProject({
  name: '实时看板',
  subtitle: 'createProject 构造可挂载的工程 body',
  widgets: [/* VpNode */],
  connections: [/* ... */],
  connectionData: { /* ... */ },
})

const ctx = createVpViewer('#app', body /* 或编辑器导出的 {project,connections,connectionData,dataRules} */, {
  params: { theme: 'dark' },            // 项目参数(千人千面),与 project.params 合并
  transformData(body) { /* 挂载前加工工程数据(返回新 body 或就地改)。编辑器「导出工程」的 .vpproj.json 是精简形态,
                           SDK 不补齐 connectionData 的 tableName/fields → 在此从 connections 补齐,写法见 SKILL.md 骨架 */ },
  widgets: [{ type: 'my-gauge', module }], // 自定义组件(见 §6)
  carousel: false,                       // 多页自动轮播
  onReady(ctx) { /* 就绪,程序化操作 */ },
  onBehavior(event, payload, ctx) {},    // 监听全部行为事件
  onScript(command, ctx) {},             // 接管「执行脚本」动作(不再 POST /execute-script)
  onRequest(req, ctx) { return data },   // 接管「调用接口」动作(加签/代理/Mock;返回解析后数据)。
                                        // 栈式接管:后注册者优先,退订后回落前一个;无人接管回落默认 fetch
})

内部与编辑器预览/独立 viewer 完全同源:注册内置组件 → 解析数据 → 构建工程模型置播放态 → 渲染舞台 → 启动取数/启动窗口/交互。

2. 运行上下文 ctx(VpViewerContext)

分类 API
页面 screens / activeScreen / getScreen(uid?) / setActiveScreen(uid) / addScreen(input) / removeScreen(uid)
窗口 windows / openWindows / getWindow / openWindow(uid, context?) / closeWindow / closeTopWindow / addWindow / removeWindow
组件 getWidget(uid) / findWidgets(pred) / getWidgetsByType(type)(覆盖页面+窗口)/ `addWidget(type
数据 setData(conn, table, rows) 推数即重渲(rows 原样整表替换:键须是字段 uid,不走 sourceField 映射;连接或表不存在时静默无操作。对比:startEventInbound 的 setData、requestApi 写数据源、连接器取数会按 sourceField 映射并丢弃未声明列)/ getData / getConnections()(VpConnectionInfo[],uid 字段)/ getTables(conn)(VpTableInfo[],tableId/tableName/rowCount 字段,无 uid)/ refreshData(conn?) / onDataChange(conn, table, fn)
参数 getParams() / setParams({...})(驱动数据规则/条件样式/状态重算)/ onParamsChange(fn)
事件 on(name?, handler) 行为事件总线——只收组件「发送行为事件」动作派发的事件(sendBehaviorEvent → 宿主;该动作没有 data 选项,宿主收到的 payload 是本条交互的触发器 payload:click 为 undefined、itemClicked 为 {别名:值}、click-row 为行 payload;动作的 element 解析不到时直接返回,宿主也收不到)/ emit(name, data?, targetUid?) 投递进工程驱动「接收二开事件」触发器,不会回 on 总线(方向相反,勿用 emit+on 自测回路)/ startEventInbound(source, opts) 第三方入站 / onScript / onRequest
生命周期 reload(body) / destroy();逃生舱 ctx.project(内核 VpProject,方法面与扩展脚本门面不同名:用 ctx.project.getElementByUID(['w_bar'])(参数是路径数组 [screenUid?, widgetUid, pieceUid?],传裸字符串会被按字符拆开、静默返回 undefined)或 ctx.project.findWidgetByUid('w_bar'),而非 getElement)

⚠️ byId 族只在 ctx.project(内核 VpProject)上,viewer/ctx 上没有:getWindows() / openWindowById(uid) / closeWindowById(uid) / setActiveScreenById(id) 是 VpProject 的方法,在 viewer 上调同名得到 undefined。它们与 viewer 的 openWindow(uid) / setActiveScreen(uid) 并存且参数同为 uid(返回的窗口项 uid 形如 window_xxx,自建窗口用传入 uid)。widget 句柄侧:setEnabled(b) 返回句柄可链式、onClick(fn) 返回退订、setPrivateData/getPrivateData 存取 fields/rows 载荷;getExtensionInstances 属扩展内 this.element(VpExtensionElementApi),不在 widget 句柄。

⚠️ destroy() 拔全局事件通道:宿主行为事件经模块级单例 setBehaviorObserver 派发——createVpViewer 覆盖它、destroy() 置 null(sdk/index.ts)。多 viewer 共存时只有最后创建的实例 on() 能收到;任一实例 destroy 后所有存活 viewer 的 ctx.on 永久失联(sendBehaviorEvent 动作本身仍正常写 status.receiveBehaviorEvent,只是不再回传宿主)。同页多实例/动态增删 viewer 场景须重建或自接 observer。

常见任务:

// 实时推数(绑定该表的所有组件自动刷新)
setInterval(() => ctx.setData('conn-sales', 'monthly', rows), 3000)
// 监听大屏「发送行为事件」动作
ctx.on('open-detail', (event, payload) => { ... })
// 程序化建组件并绑字段
const bar = await ctx.addWidget({ type: 'chart-bar', name: '月度销售', position: [40,120], size: [780,360] })
bar?.addField(['conn-sales', 'monthly', 'month'])   // 自动路由到最合适字段槽
bar?.addField(['conn-sales', 'monthly', 'sales'])

3. 组件句柄 VpWidgetHandle(getWidget 返回,链式)

  • 属性:getOption/setOption(path, v) / setOptions({..}) / getOptions();几何 getPosition() 返回 {left, top} 对象(非数组),setPosition 收 {left,top} 或 [x,y];get/setSize、setRotate。注意 setRotate 只是 setOption('rotate', [x,y,z])——getRotate() 读的是 widget.rotate 字段,对未声明 rotate 选项的组件(如 chart-bar)二者不同步(option 写入但 getRotate 恒 {x:0,y:0,z:0})。
  • path 语义:字符串是字面 key——setOption('title.text', v) 写的是 options['title.text'] 扁平键(VP 选项本就全是扁平连字符名如 'category-field');要真嵌套写须传数组 setOption(['a','b'], v)。setOptions 是逐 key 合并,不会清空其它选项。
  • 显隐命名:isVisible/setVisible/show/hide/setEnabled/setName。注意 setName 写的是组件节点字段、不是 options.name——getOption('name') 读不到它。
  • 数据:getData()(读数 facade)/ getBoundFields(path) / bindField(path, [c,t,f])(显式绑)/ addField([c,t,f])(按推荐类型自动路由)/ setData(rows)(推给首个绑定表,同样原样替换不映射;未绑定则写私有表)/ get/setPrivateData。
  • 选中与值:getValue/getSelectedData/select(index, entry)。
  • 树寻址:getChildren()/addChild(input)/getParent()/getScreen();专用句柄 asMap() / asScada()。
  • builder 子元素(三维/组态/地图 pieces):getPieces() / addPiece({type,uid?,name?,options?}) / removePiece(uid)。引擎已创建时新增会自动 init;scene3d-scene / scene3d-camera / map-body 拒绝删除。不要只改 raw.node.pieces。
  • 事件订阅(都返回退订函数):on(event, fn) / onClick(fn) / onSelect(fn)(点选/取消)/ onValueChange(fn)(控件值)/ watchOption(path, fn);程序化 click() / emit(name, data)。
  • 脚本:getExtensions()/addExtension({name, code})/updateExtension(uid, patch)/removeExtension(uid)。
  • remove() 从工程移除。

页面句柄 VpScreenHandle:getWidgets/getAllWidgets/getWidget/findWidgets/getWidgetsByType/addWidget/removeWidget/activate()/getSize/onAppear/get/setOption。窗口句柄 VpWindowHandle:同页面的组件操作 + isOpen/open(context?)/close()/onOpen。

4. 地图句柄 VpMapHandle(ctx.getMap(uid) / handle.asMap())

  • 图层:listLayers()(uid/type/name/enabled/visible)/ getLayer(uid) → 句柄 setOption/setOptions/setVisible/show/hide;快捷 setLayerOption/setLayerVisible。listLayers() 不含 map-body(本体不是图层),但 getLayer("map-body") 能取到本体句柄;listSources() 每项含 state(ready)与 recordCount。

  • 数据源:listSources() / getSource(uid) → setData(geojson|rows|文本)(运行态注入,不持久化)/refresh()/setVisible;快捷 setSourceData/setSourceVisible/refreshData()。

  • 相机:flyTo({lng,lat,zoom,bearing,pitch}, {duration}) / fitData() / fitWhole() / setZoom(z) / clearSelection()。

  • 交互绘制/图形编辑:canDraw() 判可用(地图异步初始化,建议 map-ready 后再问)→ startDraw(init?) 开会话 → getDraw() 取当前会话。 会话 VpMapDrawSession 包的是引擎 vjmap Draw.Tool,交互都是引擎原生的: 双击/回车结束绘制、点中图形拖顶点、点边中点加顶点、Backspace 删顶点、Esc 取消。

    • init:mode(select/edit/browse/point/line/polygon/rectangle/circle) / features(初始几何,用于编辑既有图形) / toolbar(false=不建引擎按钮,自己做界面;数组=只留这几个按钮) / toolbarPosition / snap / guides / keybindings。
    • 会话:setMode(mode, {featureId?})(edit 需 featureId,缺省取当前选中,没有则降级 select)/ getMode() / set(features) / add(feature) / getAll() / getSelectedIds() / delete(ids) / deleteAll() / trash() / undo() / redo() / setFeatureProperty(id, prop, value) / onChange(fn) / dispose()。
    • onChange 把引擎的 create/update/delete/selectionchange/modechange 归一为一路回调: { kind, features, action?, all, mode },all 是变化后的全量要素(省一次 getAll)。
    • 几何一律是地图渲染坐标(地理底图=经纬度);CAD 底图要源 X/Y 时用 handle.raw.map.fromLngLat 自行换算。
    • 同一地图重复 startDraw 返回同一会话(避免叠出多个控件),要换初始化参数先 dispose()。
  • 原生 vjmap.Map(逃生舱):VpMapHandle 没有单独的 getNativeMap()。handle.raw 是 map-2d 内核实例,就绪后:

    • raw.map → vjmap.Map(未就绪为 null)
    • raw.vjmapModule → 组件已加载的 vjmap 命名空间(用这份写 Marker/Popup/Draw,不要再 import('vjmap') 出第二份运行时)
    • raw.isMapReady → 首帧加载完成 地图是异步 createMap 的,createVpViewer / onReady 时 raw.map 往往还是 null,先等到 isMapReady。(CAD blank 模式下 raw.map 即刻非 null 且 fromLngLat 可调用,但 isMapReady 仍为 false——raw.map 存在 ≠ 地图就绪,坐标换算可用、瓦片/样式操作仍要等 isMapReady。)可写的能力(自定义图层、查询、临时 Marker)用原生 SDK;图层显隐/推数/飞镜/绘制仍走句柄。不要 raw.map.remove() / 整表 setStyle()。
  • 原生 vjmap3d.App:三维没有独立 handle。ctx.getWidget(uid).raw 就绪后:

    • raw.app → vjmap3d.App
    • raw.engineModule / raw.threeModule → 已加载的 vjmap3d / three
    • raw.appReady → App 创建完成 调用引擎 API 前先 engineModule.Store.app = app(多实例会抢全局指针)。不要 app.dispose()。

5. 组态句柄 VpScadaHandle(ctx.getScada(uid) / handle.asScada())

  • 测点:setPoint(key, v) / setPoints({..})(运行层合并写入,覆盖绑定值)/ getPoint(key) / clearPoints() / pointKeys()。
  • 枚举:listNodes() / listLinks()。
  • 改图元选项:setPieceOption(pieceUid, option, value)(运行时、不记历史;典型:sprite-stop-frame 指定图像集第几张、sprite-play 起停。找不到目标件返回 false)。
  • 播放态点击订阅:onNodeClick/onNodeDblClick(fn(payload{nodeUid,shape,dataKey,value})) / onLinkClick(fn) / onBlankClick(fn)——四个均返回退订函数。注意命中检测依赖播放态渲染管线,纯 dispatchEvent 合成点击不一定触发回调(空数组),真实环境以用户交互为准。

另:widget 模型层 raw.getData() 返回 VpDataReader(getRows/getColumns/getPagingRows)——getColumns([])/afterLinkageAndFilterRows([]) 在无绑定时返回 null,getPagingRows({page,size}) 返回 {rows:[],count:0};VpWidget.defineOptions() 静态返回选项蓝图分组数组(basic-text 为 3 组)。

6. 自定义组件

组件模块 VpWidgetModule = TheWidget(模型类,继承 VpWidget,静态 defineOptions() 声明选项蓝图;简单展示件可省) + component(Vue 渲染) + meta(type/name/category/defaultSize/defaultOptions)。注册二选一:挂载选项 widgets: [{type, module}],或挂载前 registerWidgetType(type, module)。注册后该 type 可被工程 JSON 引用、ctx.addWidget(type) 创建、参与导出。⚠️ meta.defaultOptions 只在 ctx.addWidget/编辑器插入时补缺;从工程 JSON 载入的节点只用 defineOptions() 蓝图默认——载入期就要生效的默认值写进蓝图 default。

开发所需 API 均由 vjvpsdk 直接导出(二开工程包里经 @vp/sdk 别名引用,同一套导出):VpWidget 基类、useWidget()/useWidgetSafe()(Vue 组件内取模型实例,读 getOption/getBoundFields/afterLinkageAndFilterRows)、类型 VpOptionGroupDef/VpOptionItemDef。defineOptions() 写法与 field(...) 绑定约束详见官方文档的自定义组件章节。

另:ctx.addWidget / screen.addWidget 的入参支持 pieces(builder 型组件),可程序化创建带完整场景的 scene-3d / scada-2d / map-2d。

7. 二次开发脚本(代码面板 Extension)

随工程持久化(节点 extensions),运行态自动实例化(编辑态不执行)。形态:export default class + 生命周期 created/mounted/ready/update(每帧)/destroy/onEnable/onDisable/onPropertyChanged。API:

this.element.getOption/setOption/show/hide/addEventListener('click',fn)/emit/getData/getChildren/getParent/getExtensionInstance
this.project.getElement(uid)/getElementsByType/getData/setData/setParams/refreshData/emit(name,data,targetUid?)

与交互「执行脚本」动作互补:动作派发命令串由宿主 ctx.onScript 接管(不接管走后端 /execute-script);脚本是常驻逻辑。SDK 侧可用组件句柄增删改脚本(§3)。

8. 事件入站(第三方系统 → 大屏)

ctx.startEventInbound(source, options):内置源工厂 postMessageEventSource / websocketEventSource / pollingEventSource / pocketbaseEventSource(实时订阅)。选项含解析/白名单 allowNames/去重 dedupBy/限流 minIntervalMs,落点默认 emit 行为事件,也可 setData:{connection, table, mode:replace|append|prepend} 写数据源,二者可并存。返回退订函数(destroy 自动停止)。

ctx.emit(name, data?, targetUid?) 定向语义:targetUid 命中组件/页面/窗口则只投该元素的 status.receiveBehaviorEvent;缺省广播全部页面+窗口+组件(同一 payload 引用)。

⚠️ emit 落点语义:入站 emit 走的是 emitBehaviorEvent——把 {name,data} 写进每个组件/页面的 status.receiveBehaviorEvent(驱动组件上配置的「接收二次开发事件」触发器),不是直接派发给 ctx.on(name)。ctx.on 收的是反方向的:组件交互里配「发送行为事件」动作 → 派给宿主。要观察入站落地可直接读 handle.raw.status.receiveBehaviorEvent;postMessage 消息形态 {type:'vp-event', name, data}(parseInboundEvent 也兼容 {name|event|eventName, data|payload})。

9. 版本与产物

  • 推荐:直接 import 'vjvpsdk' 解析编译产物。
  • 业务应用不要给 SDK 配置指向源码的 Vite 别名。
  • 对外产物:pnpm package:sdk / 二开工程包 vendor/vp-runtime/(.mjs+.css+.d.ts),使用时 import '../vendor/vp-runtime/vp-runtime.css'。