数据子系统:connections / connectionData / dataRules
工程的数据分三块(均为 body 顶层):
connections[]:数据源定义(连接 + 表 + 字段,不含行数据)。connectionData{}:每个连接的行数据(connUid → 表桶[])。dataRules[]:可复用的数据规则(联动/筛选/状态切换用)。
组件通过三元组 [connUid, tableUid, fieldUid] 绑定到某连接某表某字段:字段元信息(别名/类型)从 connections[].tables[].fields 取,字段值从 connectionData 的 rows 取。
精简交换格式(仅限编辑器导入;手写给 SDK 挂载默认用完整写法):connections[].tables[] 是表名/字段的单一真源;connectionData 可只写 tableId + rows,其 tableName/fields 可省略——导入编辑器时会自动从 connections 对应表补齐为完整内部结构。仅当某表的数据字段确需不同于连接定义(如单独改过 revisedType/别名)时,才在 connectionData 显式重复写 fields。tableId 与 rows 永远要写。
⚠️ 精简写法只对「编辑器导入」安全:补齐发生在编辑器导入路径;SDK
createVpViewer/ctx.reload直挂不补齐——VpElement.getFieldMeta读connData的fields.find(源码缺?.),精简写法会让挂载同步抛Cannot read properties of undefined (reading 'find')。SDK 场景必须写全tableName + fields,或照抄sdk-demo的mergeFieldSchema先合并再挂载(见 sdk-app/scaffold.md §3)。编辑器「导出工程」的.vpproj.json本身就是精简形态(compactProjectBody;二次开发工程包内的src/data/project.json才是完整 body),拿它喂 SDK 须在createVpViewer的transformData里从connections补齐(SKILL.md 骨架已带)。
1. connection 对象
interface VpConnection {
uid: string // conn_xxx
name: string
type: string // 旧字段:static / file / api ...(必填)
category?: 'platform' | 'http' | 'local' | 'file' | 'db'
connectorType?: string // 具体连接器:mock/json/csv/excel/api/websocket/webhook/postMessage
config?: Record<string, unknown>
refreshInterval?: number // 秒,0=不刷新
tables?: VpTable[] // 表(含字段,无行)
}
interface VpTable { uid: string; name: string; fields: VpField[]; eventName?: string; eventParams?: string; source?: string /* 外部源标识;pocketbase=集合物理名 vpd_xxx */ }
interface VpField { uid: string; alias: string; type?: string; revisedType?: string; sourceField?: string }
uid/name/type必填;tables要有(否则无法绑定)。typevsconnectorType:判别一律看connectorType;type是旧兼容字段(仅当connectorType缺失时用于动态源判定)。编辑器写出的值:文件类type:"file"、接口类type=connectorType(如api)、pocketbasetype:"pocketbase"、mocktype:"mock"。手写静态数据对齐编辑器写type:"file", connectorType:"json"。别写"static"——它不在内置连接器清单,编辑器会当未知类型(static仅由「冻结导出」内部生成)。- 不变式:
connection.tables[i].uid === connectionData[connUid][i].tableId。
2. 连接器类型与 config
内置清单:
| connectorType | category | config 关键键 | 说明 |
|---|---|---|---|
pocketbase |
platform | { realtime, limit, sort, url, relations } |
平台数据:本系统 PocketBase 数据表(后端动态集合 vpd_xxx),实时订阅(SSE)或按 refreshInterval 轮询 |
mock |
local | { mock: { rowCount, fields } } + refreshInterval |
模拟数据:按字段规则在前端生成,可定时刷新,适合实时监控/演示大屏 |
json |
file | { fileName, copyToRoot } |
上传/粘贴 JSON。手写静态数据首选(字段 uid=列名) |
csv |
file | { fileName, copyToRoot } |
CSV |
excel |
file | { fileName, copyToRoot } |
Excel |
api |
http | { method, url, headers, queryParams, body, bodyType, auth, variables, dataPath, script } |
HTTP 取数;dataPath 定位响应里的数组 |
websocket |
http | { url, protocol, mode, dataPath, script } |
长连接推送 |
webhook |
http | { url, responseMode, dataPath, script } |
回调推送;responseMode:"实时更新" 且未写 refreshInterval 时按 2s 轮询——写了 refreshInterval(含 0)一律以它为准(0=不轮询) |
postMessage |
http | { linkId, responseMode, dataPath, script } |
父窗口 postMessage 通信,按 linkId 匹配 |
mysql/sqlserver/pgsql/mongodb |
db | — | 当前未启用,勿用 |
⚠️ 没有
static连接器(不在VP_CONNECTORS清单)。内联/静态数据一律用connectorType:"json"。写"static"会让编辑器把它当未知类型(无类型图标、无法替换)。接口类(api/websocket/webhook/postMessage)运行时只更新该连接的第一张表
connectionData[conn][0](kP固定取[0].tableId);mock的定时重生成同样只写首表。
文件/JSON 类连接器运行时不取数 —— 数据就是 connectionData 里的内联 rows。
mock 连接器会在预览/播放态按 refreshInterval(秒)定时重新生成首表 rows;refreshInterval:0 表示不自动刷新,但仍可被「刷新数据」动作或菜单手动触发。
⚠️ 启动即有一次取数/生成:运行时对每个连接器先立即执行一次、再按 refreshInterval 挂 setInterval——即使 refreshInterval:0/缺省,mock 连接也会在启动时把首表 rows 重生成一遍(种子行被替换),api/webhook 连接同样启动即拉取一次。
⚠️ VpConnection 类型上声明了 refreshIntervals?: Record<string, number>(意图按表覆盖),但运行时 connectionRuntime 只读连接级 refreshInterval——当前版本设置 refreshIntervals 会被静默忽略。
SDK 侧对应
viewer.refreshData(connUid?):传 uid 定向刷新单连接,无参刷新全部可重取连接(Bie全量);均只写各连接首表。
2.0 pocketbase 平台数据连接(仅在部署了后端时可用)
数据表在编辑器「平台数据」里创建(后端动态建 PocketBase 集合);工程 JSON 侧的形态:
- 连接:
type:"pocketbase", connectorType:"pocketbase", category:"platform";config键都可省略走默认(realtime:true实时订阅、limit:500行数上限、sort:"created"、relations关联展平映射)。 - 表:
tables[].source= 集合物理名(vpd_xxx);字段sourceField= 字段物理名,行按sourceField → uid物化。 - 鉴权 token 由编辑器登录后注入,不落入工程 JSON;匿名查看端只能读「公开读取」的表。
- 手写生成的工程一般不要用它(依赖目标环境有同名数据表);演示/交付优先
json/mock。
2.1 在编辑器里查看 / 修改数据源(重要)
「连接数据」面板的编辑能力按连接器类型区分:
- 模拟/接口/流式类(mock / api / websocket / webhook / postMessage) 显示「✎ 编辑数据源」铅笔按钮,点开可改连接配置。
- JSON / CSV 文件类 也显示铅笔按钮,点开后用 JSON 编辑器回填当前行数据;保存时就地更新首表数据,并保留
connUid/tableUid/fieldUid,已绑定字段不会失效。 - Excel 和数据库类 暂不支持就地编辑;需要删除后重新上传/创建。
- 任意类型点开表后,右侧「数据结构」可改字段别名 / 类型;「数据明细」是只读预览(≤100 行)。
改 JSON/CSV 源数据的两种方式:
- 编辑器内:在「连接数据」面板点该数据源的铅笔 → 修改 JSON 行数组 → 保存。
- 直接改工程文件里的
connectionData[...].rows(连同connections[].tables[].fields保持一致)→ 重新导入工程。
3. field 与 uid 约定(关键)
| 数据来源 | 字段 uid 如何取 |
sourceField |
|---|---|---|
| Mock(模拟数据) | uid = 字段规则 name(= rows 键) | 无 |
| JSON/CSV/Excel(静态/文件) | uid = 列名(= alias) | 无 |
| API 自动解析 | uid = 列名 | 无 |
| API 手动 JSONPath | uid = alias | sourceField = JSONPath |
| WebSocket/Webhook/PostMessage | uid = f_xxx |
sourceField = 外部键名 |
| PocketBase 平台数据 | uid = f_xxx(自动生成;编辑时按 sourceField 复用旧 uid 保住绑定) |
sourceField = 字段物理名,alias = 显示名(表另有 source=集合物理名) |
字段 type:string / number / array(无一等公民 date)。
行对象的键 = 字段 uid:rows[i][fieldUid] 直接取值。所以:
- 静态/文件类:
rows: [{ "月份":"一月", "销售额":120 }],字段 uid 就是"月份"/"销售额"。 - 流式:
rows: [{ "f_month":"一月", "f_sales":120 }],字段 uid 是f_month等,且字段定义里sourceField指向外部键(如"month")。 - ⚠️ 一个组件同次取数的字段必须来自同一张表(如 chart-bar 的
category-field+metric-fields):混表时DataBucket.getBucket不出桶、getRows返[]并写status.error的multi-filed-error,组件静默无数据。跨表数据先在宿主/接口侧合成一张表。
4. connectionData 对象
type VpConnectionData = Record<string /*connUid*/, VpDataTable[]>
interface VpDataTable {
tableId: string // == connection.tables[i].uid(必写)
tableName?: string // 注意是 tableName,不是 name;可省略=继承 connections 表名
fields?: VpField[] // 可省略=继承 connections 表 fields;仅在确需覆盖时写
rows: Array<Record<string, unknown>> // 键为字段 uid(必写)
}
- 静态/文件类:必须带 rows,否则组件空数据。
- 接口类:可先
rows: [],运行时拉取/推送填充(手写时给几行示例更直观)。 - 整个
connectionData省略 →{},静态工程会没数据。 - 精简(仅编辑器导入):只写
tableId + rows;tableName/fields留给导入器从connections补齐。只有当数据字段与连接定义不同才显式写fields(此时该份为准)。
5. 一个完整连接示例(静态 JSON)
{
"connections": [{
"uid": "conn_sales", "name": "销售数据",
"type": "file", "connectorType": "json", "category": "file",
"config": { "copyToRoot": true, "fileName": "" },
"tables": [{
"uid": "tbl_sales", "name": "销售数据",
"fields": [
{ "uid": "月份", "alias": "月份", "type": "string" },
{ "uid": "销售额", "alias": "销售额", "type": "number" },
{ "uid": "利润", "alias": "利润", "type": "number" }
]
}]
}],
"connectionData": {
"conn_sales": [{
"tableId": "tbl_sales",
"rows": [
{ "月份": "一月", "销售额": 120, "利润": 60 },
{ "月份": "二月", "销售额": 200, "利润": 95 },
{ "月份": "三月", "销售额": 150, "利润": 70 }
]
}]
}
}
- 上面
connectionData用的是精简写法:只写tableId + rows,tableName/fields由connections的tbl_sales补齐。 - 等价的完整写法(与导入后内部结构一致,仍合法)会在该表里再带一份
"tableName": "销售数据"和与connections相同的fields。两种都能导入编辑器;SDK 直挂只认完整写法(见文首 ⚠️),给 SDK 用就写完整或挂载前transformData补齐。 - 组件绑定示例:
"category-field": [["conn_sales","tbl_sales","月份"]],"metric-fields": [["conn_sales","tbl_sales","销售额"],["conn_sales","tbl_sales","利润"]]。
6. mock 模拟数据连接
mock 适合做无后端、会随时间变化的实时监控示例。字段规则写在 config.mock.fields,导入后可在「连接数据」面板点铅笔用 UI 修改字段、行数与刷新间隔。
{
"uid": "conn_kpi",
"name": "实时KPI(Mock)",
"type": "mock",
"connectorType": "mock",
"refreshInterval": 4,
"config": {
"mock": {
"rowCount": 1,
"fields": [
{ "name": "在线用户", "alias": "在线用户", "kind": "wave", "base": 1300, "amplitude": 380, "periodSec": 18, "jitter": 60, "decimals": 0 },
{ "name": "CPU", "alias": "CPU", "kind": "wave", "base": 67, "amplitude": 24, "periodSec": 12, "jitter": 4, "decimals": 0 }
]
}
},
"tables": [{
"uid": "tbl_kpi",
"name": "实时KPI",
"fields": [
{ "uid": "在线用户", "alias": "在线用户", "type": "number" },
{ "uid": "CPU", "alias": "CPU", "type": "number" }
]
}]
}
全部 kind(mockData.ts 的 VpMockFieldKind):
int:整数区间随机,支持min/max(取整,不用decimals)。float:小数区间随机,支持min/max/decimals。wave:随时间正弦波动,支持base/amplitude/periodSec/jitter/decimals(多行时按行号加相位偏移)。walk:基于上一帧随机游走,支持start/step/min/max/decimals。sequence:按行号生成等差序列start + rowIndex*step,支持start/step/decimals。enum:从options数组随机取一。bool:按trueRatio(默认 0.5)概率为 true。const:固定value。timestamp:当前时间,format=ms(默认,毫秒数)/iso/time/datetime。row-const/row-walk/row-wave:按行号分别取values[rowIndex]/starts[rowIndex]作初值游走 /rows[rowIndex]的 wave 参数——长表多测点(每行一条曲线)用这三个。
connectionData 仍建议放一帧初始 rows,便于编辑态预览;播放/预览态启动后会由 mock 运行时按规则替换。
7. postMessage 连接(与示例文件一致)
宿主用 window.postMessage({ linkId, payload }, '*') 推数据。字段用 f_xxx + sourceField:
{
"uid": "conn_pm", "name": "PostMessage数据源",
"type": "postMessage", "connectorType": "postMessage",
"config": { "linkId": "link_demo", "responseMode": "实时更新" },
"tables": [{
"eventName": "data", "eventParams": "", "name": "data", "uid": "tbl_pm",
"fields": [
{ "uid": "f_month", "sourceField": "month", "alias": "month", "type": "string" },
{ "uid": "f_sales", "sourceField": "sales", "alias": "sales", "type": "number" }
]
}]
}
connectionData 里 rows 用字段 uid 作键:{ "f_month":"一月", "f_sales":16000 }。
边界:配置了 linkId 才按 {linkId, payload} 匹配并取 payload;未配置 linkId 时任意 window message 都直接当数据写入该连接——多连接器或宿主页有其他 postMessage 流量时务必配 linkId 隔离。取到数据后仍走 dataPath/script 提取行。
8. dataRules(数据规则)
interface VpDataRule { uid: string; name: string; rules: VpDataRuleRule[][] }
interface VpDataRuleRule {
connection?: string; table?: string; field: string
func: string // = > < != >= <= ⊃ !⊃ ∅ !∅
args?: unknown
method?: 'some' | 'every' | 'first' | 'last' | 'custom'
order?: number // custom 时第几行
source?: 'field' | 'param' // 旧兼容
}
rules二维:组间 OR,组内 AND。多个条件之间也是 OR。func全集:= > < >= <= != ⊃(含) !⊃(不含) ∅(为空) !∅(非空);=/!=宽松比较("1"==1);∅/!∅不需要args。method:some(任一行,默认)/every/first/last/custom(+order,1-based 取第 N 行)。field可为字段 uid,或位置字段row(行号)/reverseRow(倒序行号)/count(条数)。- 用项目参数:
connection/table填"__vp_project_params__",field填参数名。 - 不需要数据规则就给
[]。
8.1 项目参数(千人千面)
project.params 是工程级键值变量(如 { "theme": "dark", "region": "华东" }),让同一份工程按用户/角色/场景呈现不同内容。消费:①数据规则按参数判断(上面 __vp_project_params__ 写法)→ 驱动条件样式/多状态/conditionMeet 触发器;②交互动作公式 {:参数名} 插值。修改:交互动作 editProjectParams / SDK ctx.setParams 或挂载 params / 二开脚本 this.project.setParams。参数变化响应式重算重渲。生成静态演示工程时通常留 {}。
示例:
"dataRules": [{
"uid": "dc_high", "name": "销售额偏高",
"rules": [
[
{ "connection": "conn_sales", "table": "tbl_sales", "field": "销售额", "func": ">", "args": 150, "method": "some" }
]
]
}]
9. connection.tables 与 connectionData 的分工
| 关注点 | connections[].tables | connectionData[conn] |
|---|---|---|
| 表 id | tables[].uid |
tableId(须相等,必写) |
| 表名 | tables[].name(真源) |
tableName?(可省略=继承) |
| 字段 | fields[](真源) |
fields?(可省略=继承;覆盖时才写) |
| 行数据 | 无 | rows[](必写) |
| 事件名/参数 | eventName/eventParams(流式) |
无 |
字段元信息(别名/类型/树展示)以 connections[].tables[].fields 为单一真源;行值取自 connectionData。
- 精简仅限编辑器导入:
connectionData只写tableId + rows,导入时自动补齐tableName/fields;SDK 挂载用完整写法。 - 例外(有意覆盖):当某表数据字段确需不同(如改过
revisedType/别名)才在connectionData写fields,此时以connectionData.fields为准。 - 不变式不变:
connections[].tables[i].uid === connectionData[connUid][i].tableId;组件绑定的fieldUid至少能在connections(或显式的connectionData.fields)里解析到。