唯杰地图 logo唯杰地图
VJMAP
EN

数据子系统:connections / connectionData / dataRules

vjvp/references/data.md 17.7 KB278 行
原文

工程的数据分三块(均为 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 要有(否则无法绑定)。
  • type vs connectorType:判别一律看 connectorType;type 是旧兼容字段(仅当 connectorType 缺失时用于动态源判定)。编辑器写出的值:文件类 type:"file"、接口类 type=connectorType(如 api)、pocketbase type:"pocketbase"、mock type:"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 源数据的两种方式:

  1. 编辑器内:在「连接数据」面板点该数据源的铅笔 → 修改 JSON 行数组 → 保存。
  2. 直接改工程文件里的 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)里解析到。