应用骨架与模块脚手架
返回 SKILL.md。下文结构提炼自官方参考实现(park 能源站、safety 罐区两套应用,模块结构相同)。
包名:外部工程用发布包名
vjvpsdk("file:./vendor/vjvpsdk"或私有 registry 版本号),拿到包即可用,无需构建 SDK。下文示例的@vp/sdk与vjvpsdk等价互换;CSS 子路径分别为@vp/sdk/vp-runtime.css与vjvpsdk/vp-runtime.css。
1. 应用骨架(npm 消费形态,默认推荐)
四个文件起步:
package.json(依赖 vjvpsdk + vue;vjvpsdk 也可换成私有 registry 版本号):
{
"name": "<name>",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"typecheck": "vue-tsc --noEmit"
},
"dependencies": { "vjvpsdk": "file:./vendor/vjvpsdk", "vue": "^3.5.13" },
"devDependencies": { "@vitejs/plugin-vue": "^5.2.1", "typescript": "^5.6.3", "vite": "^5.4.11", "vue-tsc": "^2.1.10" }
}
vite.config.ts(不配置任何别名——import 'vjvpsdk' 走包入口解析到 dist/vp-runtime.mjs):
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
export default defineConfig({
base: './',
plugins: [vue()],
server: { port: 5181, host: true },
})
index.html:<div id="app"></div> + <script type="module" src="/src/main.ts"></script>(深色底 body 样式防白闪)。
src/main.ts(样式经包子路径引入;自定义组件经挂载选项注册):
import { createVpViewer } from 'vjvpsdk'
import 'vjvpsdk/vp-runtime.css'
import { appInit } from './project'
import { buildAll } from './build'
import { myWidgetModule } from './widgets/myWidget'
createVpViewer('#app', appInit, {
widgets: [{ type: 'my-widget', module: myWidgetModule }],
onReady: (ctx) => void buildAll(ctx),
})
⚠️ 外部工程用 vjvpsdk 包无需构建 SDK。类型解析走包内 dist/vp-runtime.d.ts(发布包 types 字段所指)。
2. 模块目录(配置层 / 代码层)
src/
├── main.ts # 入口:注册自定义组件 + createVpViewer + onReady
├── project/ # ◆配置层:数据源
│ ├── connections.ts # 连接/表/字段定义(uid=alias 中文字段,helper 压缩重复)
│ ├── connectionData.ts # 初始行(测点初值从 sim 单一事实源导入,静态报表手写)
│ └── index.ts # body 外壳(页面级选项 + 首页空壳)+ mergeFieldSchema
├── scenes/ # ◆配置层:重场景 pieces 静态定义
│ ├── scene3dPieces.ts # 三维场景(导出 UID 常量 + pieces() + interactions())
│ ├── scadaPieces.ts # 组态工艺图(node/link 用 helper 生成)
│ └── mapPieces.ts # 2D 地图(map-body + 数据源 + 图层)
├── sim/ # ◆代码层:模拟引擎与业务状态机(纯函数 + 单测)
│ ├── points.ts # 测点/设备/映射表 单一事实源(含 DEVICE_PIECE_MAP 类映射)
│ ├── simulator.ts # 物理模型 tick(纯函数,注入 rng)
│ ├── alarms.ts # 告警状态机(raise/resolve/ack + 派生视图)
│ ├── <domain>.ts # 领域状态机(隐患/作业票/演练…)
│ └── runtime.ts # 接线:setInterval tick → ctx.setData;对页面暴露控制 API
├── pages/ # ◆代码层:每页一个 builder(ctx/screen/sim 注入)
├── widgets/ # ◆代码层:自定义组件(模型类 + 渲染 + 纯函数内核分离)
├── ui.ts # 主题常量 + addPanel/addCartesian/addPie/addTable/addButton/addValueText + buildNav/buildHeader
├── windows.ts # 工程级窗口(趋势/详情/应急……内容组件带 uid 供程序化刷写)
└── build.ts # 总装:启动 sim → addScreen 建页 → 逐页 builder → 窗口 → 跨页联动
3. 数据层模板要点
- connections.ts:用
F(uid)/S(uid)/conn(...)小 helper 压缩;字段uid = alias(中文列名直接当字段 id,绑定与行键一致零转换)。 - connectionData.ts:测点初值 必须 从
sim/points.ts的initialPoints()导入生成(单一事实源,防两处漂移);静态报表(月度/分项)手写;历史窗口可用初始 sim 状态回放 N 拍生成,与实时数据无缝衔接。 - index.ts:内核
VpDataTable要求每张表带fields/tableName(图表getFieldMeta直读),mergeFieldSchema从 connections 合并进 connectionData。 - body 外壳:只带数据层 + 一个空首页(页面级选项
layout-profile: canvas-percent+fullscreen-mode: responsive+ 1920×1080 + 深色渐变背景);其余页面全部ctx.addScreen程序化创建。
4. 页面构建要点
- 全部几何按 1920×1080 设计基准 +
'geometry-unit': 'percent'写,全屏自适应。 - 导航条每页重建(当前页高亮、其余
btn.onClick(() => ctx.setActiveScreen(id)));PAGES清单集中定义。 - 面板套路:
addPanel(group-panel 百分比几何)→addPanelHeader(光条+标题+分隔线)→ 内容组件(图表绑category-field/metric-fields,表格绑table-data-fields,指标文本绑value-field)。 - 程序化刷写文本:创建时给 uid,回调里
ctx.getWidget(uid)?.setOption('text', ...)。 - 表格点行:
table.onSelect((data, index) => ...)——data是选中维度{别名:值}(取联动键 /table-key-fields/select-sync-fields各列,都未配时为被点击列;再点同行取消时为undefined),不是行 payload;要{rowNumber(1基), rowIndex(0基), columnUID, value, row:{别名:值}, …}用table.onValueChange(v => …)或table.getValue()(即currentValue)。业务主键(如_id)要出现在data里,就把它配进table-key-fields;数据行本身另带运行时注入的_index字段(1 基行号,vxekeyField也用它)。