SDK 使用(vjvpsdk:嵌入、程序化操作、脚本、自定义组件)
包名:对外发布的 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包的是引擎 vjmapDraw.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.Appraw.engineModule/raw.threeModule→ 已加载的 vjmap3d / threeraw.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'。