数据系统
数据系统
数据系统解决一个核心问题:让组件随数据变化。它由「连接」「连接数据」「字段绑定」三部分组成,并在此之上提供数据规则、筛选、联动、选中、条件样式等能力。
连接 → 表 → 字段
数据侧是清晰的三层结构:
工程里数据分成两块,定义与行数据分开存放:
| 存什么 | 组织方式 | |
|---|---|---|
连接 connections | 数据源的定义:类型、有哪些表、每张表有哪些字段(不含行) | 数组 |
连接数据 connectionData | 真正的行数据 | 连接uid → [{ tableId, rows }] |
分开的好处:定义稳定、数据可独立刷新(API / WebSocket / 实时订阅写回时,绑定组件自动重渲)。
字段绑定三元组(最重要的机制)
组件靠三元组 [连接uid, 表uid, 字段uid] 绑定数据。在编辑器里只需在属性面板「数据」页签点选字段,系统自动生成;导出 JSON 中形如:
"metric-fields": [["conn_sales", "tbl_sales", "销售额"]]- 可多选的字段槽(如柱状图数值字段)放多个三元组,一个数值字段 = 一条系列;单选槽也用
[[...]]包裹。 - 行对象的键名就是字段 uid:
rows[i]["销售额"]即该行该字段的值。 - 每种组件能绑哪些字段、数量约束(类目恰 1、数值 ≥1……)见组件手册各页。
绑定失效的常见原因
删除并重建了连接 / 表 / 字段(uid 变了)。别名怎么改都不影响绑定(绑定认 uid)。用「替换连接」功能可整体迁移绑定。
连接器类型
| 连接器 | 类别 | 取数方式 | 用途 |
|---|---|---|---|
json / csv / excel | 文件 | 数据已内联,运行时不取数 | 静态 / 文件数据,手写工程首选 json |
api | HTTP | 定时轮询(refreshInterval) | HTTP 接口,dataPath 定位响应里的数组 |
websocket | HTTP | 长连接推送 | 实时推送 |
webhook | HTTP | 外部回调 + 轮询取回 | 生产环境外部系统推数 |
postMessage | HTTP | 父窗口 postMessage,按 linkId 匹配 | iframe 嵌入时由宿主页面喂数据 |
mock | 本地 | 前端按规则定时生成 | 模拟数据,做无后端的实时监控 / 演示 |
pocketbase | 平台数据 | 实时订阅(SSE) | 平台内置数据库表 |
手写静态数据用 json
内联 / 静态数据一律用 connectorType: "json"(编辑器能识别为「文件 / JSON」类型)。手写新建连接不要写 "static"——它不在内置连接器清单,会被当作未知类型。例外:导出部署文件勾选「动态数据源转静态」时,系统会把动态连接(api / webhook / websocket / postMessage / pocketbase / db)冻结为合法的 connectorType: "static" 并内联当前数据(默认截取前 100 行),这类导出产物不必改回 json。
连接器 config 字段速查
每种连接器的取数配置写在连接的 config 里(类型清单见源码 web/packages/core/src/data/connectors.ts,取数消费见 connectionRuntime.ts):
| connectorType | config 关键键 | 说明 |
|---|---|---|
json / csv / excel | fileName / copyToRoot | 数据内联在 connectionData,运行时不取数 |
api | method / url / headers / queryParams / body / auth / variables / script / dataPath | dataPath 定位响应里的数组;配 refreshInterval(秒)轮询;进阶键见下 |
websocket | url / dataPath(protocol / mode 暂未生效) | 原生 WebSocket 长连接,收到消息解析后写首表;protocol / mode 当前仅存配置、运行时忽略 |
webhook | url / responseMode / dataPath | 外部回调;responseMode 为「实时更新」时约 2s 轮询取回 |
postMessage | linkId / responseMode / dataPath | 父窗口 postMessage,按 linkId 匹配 |
mock | mock.rowCount / mock.fields | 配 refreshInterval 定时重新生成首表 |
pocketbase | realtime / limit / sort | realtime 默认 true(SSE 实时订阅,false 时按 refreshInterval 轮询);limit 行数上限默认 500;sort 为 PocketBase 排序语法,默认 'created' |
注意
接口类(api / websocket / webhook / postMessage)运行时只更新该连接的第一张表 connectionData[conn][0];pocketbase 每张表对应一个集合,可多表更新。数据库类连接器(mysql/pgsql…)当前未启用。
api 连接器进阶
config.script:取数后对响应执行的 JS 片段(函数体,入参response,返回值作为新的取数根;返回undefined则沿用原响应)。websocket / postMessage 收到的消息同样会先过script。config.variables:[{ key, value }]变量表,URL / 查询参数 / 请求头 / 请求体 / auth 里的{{key}}占位统一替换。auth:运行时主要{ type: 'bearer', token }(加Authorization: Bearer ...)与{ type: 'basic', username, password }生效。dataPath未命中时:智能兜底依次尝试响应里的data/rows/list/result/records/items数组。执行顺序:script→dataPath→ 智能兜底。
mock 模拟数据
mock 适合做无后端、会随时间变化的实时监控示例。字段规则写在 config.mock.fields,常用 kind:
| kind | 行为 |
|---|---|
int / float | 区间随机数(min/max/decimals) |
wave | 随时间正弦波动(base/amplitude/periodSec/jitter) |
walk | 基于上一帧随机游走(start/step/min/max) |
sequence | 按行号等差序列(start/step) |
enum / bool / const / timestamp | 枚举 / 布尔 / 固定值 / 当前时间;timestamp 的 format 取 'ms'(默认)/ 'iso' / 'time' / 'datetime' |
row-const | 每行固定值:按行号取 values[] 对应项(长表测点名等) |
row-walk | 每行独立随机游走:按行号取 starts[] 作初值,其余同 walk |
row-wave | 每行独立正弦:按行号取 rows[] 里的 {base, amplitude, periodSec, jitter, decimals} |
预览 / 播放态启动后,mock 运行时会按 refreshInterval(秒)定时重新生成首表行数据。
字段 uid 约定
行对象的键 = 字段 uid,不同数据源的取法不同:
| 数据来源 | 字段 uid | 说明 |
|---|---|---|
| Mock / JSON / CSV / Excel | = 列名(= 别名) | 如 rows: [{ "月份":"一月", "销售额":120 }] |
| API 自动解析 | = 列名 | |
| API 手动 JSONPath | = 别名 | 字段带 sourceField = JSONPath |
| WebSocket / Webhook / PostMessage | = f_xxx | 字段带 sourceField 指向外部键名 |
字段 type 取 string / number / array。
数据规则(dataRules)
数据规则是可复用的「条件」(如 销售额 > 150),一处定义、多处引用:条件样式、多状态、事件触发器「数据规则满足」、数据筛选。
- 结构是二维数组:组间 OR,组内 AND。
- 每条规则
{ connection, table, field, func, args, method },运算符func全集:=><>=<=!=⊃(包含)!⊃(不包含)∅(为空)!∅(非空)。=/!=是宽松比较("1"等于1);∅/!∅不需要args。 method决定按哪行判断:some(任一行,默认)/every/first/last/custom;custom需配order(1-based,默认 1)取第 N 行。field除字段 uid 外还可填位置字段:row(当前行号)/reverseRow(倒序行号)/count(数据条数)。- 用项目参数:
connection/table填"__vp_project_params__",field填参数名(配合千人千面)。
"dataRules": [{
"uid": "dc_high", "name": "销售额偏高",
"rules": [[
{ "connection": "conn_sales", "table": "tbl_sales", "field": "销售额", "func": ">", "args": 150, "method": "some" }
]]
}]项目参数(千人千面)
项目参数是工程级的键值变量,定义在 project.params,让同一份工程按用户 / 角色 / 主题 / 场景呈现不同内容——即「千人千面」。
参数可被多处消费:
- 数据规则:规则里
connection/table填"__vp_project_params__"、field填参数名,即按参数值判断(见上)。 - 条件样式 / 多状态:命中依赖参数的规则时切换样式 / 状态。
- 公式取值:交互动作的公式里用
{:参数名}插值。
三种修改途径:
| 途径 | 用法 |
|---|---|
| 交互动作 | editProjectParams(params-key + <key>-params-value),见事件交互 |
| SDK | 挂载时 params: {...} 注入(与 project.params 合并)、运行时 ctx.setParams({...})、ctx.onParamsChange(...),见 SDK |
| 二开脚本 | this.project.setParams({...}),见二次开发脚本 |
"project": { "params": { "theme": "dark", "region": "华东" } }参数变化是响应式的:绑定它的规则 / 条件样式 / 状态会自动重算重渲。
在线演示
① 选中传递(select-sync):点击饼图扇区 → 表格选中对应行。发起方开 select-sync-emit,接收方开 select-sync-receive + select-sync-fields,页面也开 select-sync-receive:
② 组件级筛选(filter):同一张设备表,右侧只保留「负载 > 60」的行(filter: true + direct-judgment):
数据之上的能力
- 筛选 filter:组件级「先筛后用」,可按数据规则过滤行。
- 联动 cross-filter / 选中 select-sync:一个组件的操作影响其它组件(数据的传播),与事件交互(行为的编排)是两套正交机制。联动广播的键是字段别名,接收侧按「别名 → uid → sourceField」解析回行键;组件取数顺序是先联动过滤、再组件筛选。
- 渲染限流
data-row-limit:组件渲染行数上限(默认 0 = 不限)。只截断渲染取数,不影响条件样式判断与字段统计,大数据量时用它保护性能。 - 条件样式:命中数据规则时切换样式。
- 动态数据项:把某个样式参数绑到字段,数据变样式变。
- 多状态数据驱动:状态跟随字段值 / 数据规则自动切换,见事件交互 · 多状态。
默认联动 / 选中会广播给所有开了接收的组件;要精确控制作用范围,开高级路由,按发送 / 接收两个方向分别设黑 / 白名单:
cross-filter-routing-enabled:开启高级路由(默认false)。cross-filter-routing:按方向分组,键为cross-filter-emit/cross-filter-receive,每组里cross-filter-list-mode取blacklist(黑名单,默认)或whitelist(白名单),cross-filter-widgets是组件名单。- 选中传递同构:
select-sync-routing-enabled+select-sync-routing(内层键为select-sync-list-mode/select-sync-widgets)。
持久化写法(手写 JSON 易错点)
在编辑器里配这些能力不必关心格式;但手写 / 脚本生成工程 JSON 时,下面几处写错会导入即报错:
数据筛选 filter:options.filter: true 开启,再用 row-filter-mode 选模式(默认 no-condition 不筛选),各模式配套键如下——键名容易写错,请照抄:
row-filter-mode | 配套键 | 行为 |
|---|---|---|
data-row | row-filter-row-index(默认 1) | 只保留第 N 行 |
data-rows | row-filter-start-index(默认 1)+ row-filter-row-count(默认 1) | 从第 N 行起取 M 条 |
custom-rules | row-filter-rules | 内联条件组二维数组(组间 OR、组内 AND,与 dataRules[].rules 同构),逐行过滤 |
data-rule | row-row-filter-mode-id | 引用 dataRules[].uid(注意键名就是这么拗口,不是 row-filter-rule) |
direct-judgment | row-filter-source-table(值为 "连接uid,表uid")+ row-filter-source-field + row-filter-operator + row-filter-compare-value | 直接按「表-字段-运算符-值」逐行判断;运算符 ∅ / !∅ 不需要 row-filter-compare-value |
- 动态数据项
dynamic-prop-fields:把某样式项绑到字段。⚠️dynamic-prop-entries[].name与dynamic-prop-fields的键都必须是「选项路径数组」的 JSON 字符串(如JSON.stringify(["opacity"])得到"[\"opacity\"]"),不能裸写"opacity"(运行时会JSON.parse抛错)。 - 条件样式
conditionalStyles:写在节点顶层(与options平级,不在options里)。paths是目标选项路径(字符串数组,直接用,不做 JSON 编码);cases按序求值,ruleId引用dataRules[].uid,也可不引用规则、用inlineRules直接内联条件组(与dataRules[].rules同构的二维数组),fallback: true兜底。
{
"uid": "w_x", "type": "basic-text",
"options": { "text-font": { "size": 48, "color": "#ffffff" } },
"conditionalStyles": [{
"paths": ["text-font"],
"cases": [
{ "ruleId": "dc_alarm", "value": { "size": 48, "color": "#ff4d4f" } },
{ "inlineRules": [[{ "connection": "conn_sales", "table": "tbl_sales", "field": "销售额", "func": ">", "args": 100, "method": "some" }]],
"value": { "size": 48, "color": "#faad14" } },
{ "fallback": true, "value": { "size": 48, "color": "#52c41a" } }
]
}]
}编辑态与运行态的取数差异
| 编辑态(画布) | 预览 / 运行态 | |
|---|---|---|
| 数据 | 已保存的 connectionData 快照 + 手动「刷新数据」 | 启动连接运行时,真实取数 / 订阅 / 轮询 |
| mock / 接口类 | 用快照,不自动刷新 | 按 refreshInterval 定时刷新 / 实时推送 |
connectionData 是响应式的——任何数据源写回(轮询、推送、实时订阅)都会自动触发绑定组件重渲,你不需要手动刷新组件。
第三方事件入站(startEventInbound)
除了连接器拉数,还有一条反向通道:第三方系统把原始消息推给大屏,经解析归一后派发为行为事件(驱动组件的 receiveBehaviorEvent 触发器)或直接写入数据源。入口是 SDK 的 ctx.startEventInbound(source, options)(源码 web/packages/core/src/data/eventInbound.ts),返回退订函数,destroy() 时自动停止。
四种内置传输源(也可自写 VpEventSource——(push) => stop 一个函数即是源):
| 源工厂 | 场景 | 说明 |
|---|---|---|
postMessageEventSource({ origin? }) | iframe 嵌入 | 监听 window.message,仅放行 {type:'vp-event', ...};建议配 origin 校验来源 |
websocketEventSource(url) | 实时推送 | 每条 message 文本作为一条原始消息 |
pollingEventSource(pull, intervalMs) | 只有 HTTP 接口 | 周期调用 pull()(返回数组逐条推、单对象整条推),最小间隔 500ms |
pocketbaseEventSource({ topics, actions? }) | 平台数据库 | 订阅事件集合的记录变更(默认仅 create),record 作为原始消息 |
消息协议(parseInboundEvent 归一,无法识别静默丢弃):标准形态 { name, data?, targetUid? };别名 { event|eventName, payload?, target? };postMessage 约定 { type:'vp-event', name, data };以上任一的 JSON 字符串也可。targetUid 命中则定向派发到该组件,否则广播。
过滤与落点(options):
- 过滤:
allowNames(事件名白名单)、dedupBy(按字段/自定义函数去重)、minIntervalMs(同名事件限流,防风暴)。 - 落点:
emit(默认true,派发行为事件);setData: { connection, table?, dataPath?, mode?, limit? }把事件data经dataPath提行后写入目标表——mode取replace(替换,默认)/append/prepend(流式追加,配limit从另一端截断)。两个落点可同时启用。
import { createVpViewer, websocketEventSource } from '@vp/sdk'
const viewer = await createVpViewer(el, projectBody)
// 告警事件 → 驱动 receiveBehaviorEvent 触发器,同时流式追加进「告警列表」表
const stop = viewer.ctx.startEventInbound(websocketEventSource('wss://iot.example.com/events'), {
allowNames: ['alarm', 'device-online'],
minIntervalMs: 500,
setData: { connection: 'conn_alarms', mode: 'prepend', limit: 200 },
})组件侧配 receiveBehaviorEvent 触发器(事件名填 alarm 等)即可响应,见事件交互。
工程 JSON 速查(静态 JSON 连接)
{
"connections": [{
"uid": "conn_sales", "name": "销售数据",
"type": "json", "connectorType": "json",
"tables": [{
"uid": "tbl_sales", "name": "销售数据",
"fields": [
{ "uid": "月份", "alias": "月份", "type": "string" },
{ "uid": "销售额", "alias": "销售额", "type": "number" }
]
}]
}],
"connectionData": {
"conn_sales": [{
"tableId": "tbl_sales",
"rows": [
{ "月份": "一月", "销售额": 120 },
{ "月份": "二月", "销售额": 200 }
]
}]
}
}- 精简写法:
connectionData默认只写tableId + rows,tableName/fields导入时自动从connections补齐。 - 不变式:
connections[].tables[i].uid === connectionData[connUid][i].tableId。 - 绑定示例:
"category-field": [["conn_sales","tbl_sales","月份"]]、"metric-fields": [["conn_sales","tbl_sales","销售额"]]。
延伸阅读:事件交互(数据规则驱动触发器 / 状态)· SDK(ctx.setData 程序化推数、平台数据实时订阅)· 组件手册(各组件的字段绑定槽)。