型号不同参数不同,模板只有一张:JSON 路径绑定与动态行循环的实现细节

简介: 同一张检验记录表,不同型号提交的数据字段集合、条目数各不相同——让它们进同一张模板的关键,是把模板拆成“固定公共区 + 循环差异区”,差异建模为数组。本文按实现顺序拆解:路径解析规则是整套机制的地基;字段契约通过数据源模型推送接口维护,路径同时描述结构与充当组件绑定的键;绑定时常踩的坑是组件名默认为随机编码,必须先绑定路径才能赋值;渲染受 TEMPLATE_LOADED 时序约束,循环区域只认数组;提交后 dataId 务必与业务单据绑定保存。结构级差异出现时应果断拆模板,用复制路由与版本参数控制成本。

一、先看两份数据

同一张检验记录表,两个型号提交上来的数据是这样的:

// 型号 X 的记录
{
   
  "form": {
   
    "orderNo": "QI-2026-11-11-102",
    "model": "X-100",
    "params": [
      {
    "name": "表面质量", "standard": "无可见缺陷", "value": "" },
      {
    "name": "装配间隙", "standard": "≤0.3mm", "value": "" }
    ]
  }
}

// 型号 Y 的记录
{
   
  "form": {
   
    "orderNo": "QI-2026-11-11-103",
    "model": "Y-300",
    "params": [
      {
    "name": "耐压测试", "standard": "1500V / 60s", "value": "" },
      {
    "name": "绝缘电阻", "standard": "≥100MΩ", "value": "" },
      {
    "name": "接地连续性", "standard": "≤0.1Ω", "value": "" }
    ]
  }
}

字段集合不同,条目数也不同,但这两份数据来自同一张模板。模板里没有"耐压测试"这一行,也没有"表面质量"这一行——差异区只有一块循环区域,渲染哪些行完全由 params 数组决定。

这就是"一张模板承接多条产品线"的基本形态:模板承接结构,数据承接差异。剩下的全是实现问题——路径怎么定、字段契约怎么维护、渲染时序怎么排、哪些环节容易出错。下面按实现顺序拆。

二、自研这条路,要先发明什么

假设不用现成组件,从零实现上面这套机制,清单大致五项:

  1. 字段注册表:一份描述业务数据结构的字段定义,供设计期引用;
  2. 路径解析器:把 form.params#name 这类路径翻译成取数、赋值指令;
  3. 绑定协议:模板里的组件与路径如何一一对应;
  4. 循环渲染器:按数组长度克隆、渲染行区域;
  5. 宿主通信:宿主系统与表单页面之间的数据通道。

其中最值得先想清楚的是路径解析器,它决定了后面所有环节的语义。规则其实只有两条连接符:. 访问对象属性,# 进入数组元素。示意实现:

// 将路径解析为取数指令
// '.' 链式访问对象属性;'#' 之后的部分作用于数组的每个元素
function parsePath(path) {
   
  return path.split('#').map(function (seg, i) {
   
    return {
    intoArray: i > 0, props: seg.split('.') };
  });
}

// parsePath('form.params#name') =>
// [
//   { intoArray: false, props: ['form', 'params'] },  // 逐层取到 params 数组
//   { intoArray: true,  props: ['name'] }             // 对数组每个元素取 name
// ]

有了这个语义,"哪个组件显示哪个值"就变成了"组件持有哪条路径"。绑定协议、循环渲染、宿主通信都建立在这上面。

每一块单看都不难,真正的成本在协议的长期维护:字段结构随主数据演进、模板随业务改版、宿主系统随版本升级,三者任何一方变动都可能牵动解析与绑定的兼容性。这也是为什么这类能力适合交给成熟的表单组件承接,而不是每个项目自造一套——后文选型部分再回到这一点。

三、字段契约:推送而非手工配置

字段注册表怎么维护?表单组件侧提供的数据源模型推送接口,直接把契约从主系统推过去:

await fetch('http://flash-table:9150/api/datasourcefield/saveAll', {
   
  method: 'POST',
  headers: {
    'Content-Type': 'application/json' },
  body: JSON.stringify({
   
    dataSourceName: 'lims_inspection_record',
    description: '检验记录单字段模型',
    items: [
      {
    uniqueId: 'p01', fieldName: 'form.orderNo',      fieldComment: '单号',   fieldType: 'string', isNull: 1 },
      {
    uniqueId: 'p02', fieldName: 'form.model',        fieldComment: '型号',   fieldType: 'string', isNull: 0 },
      {
    uniqueId: 'p03', fieldName: 'form.params',       fieldComment: '参数明细', fieldType: 'string', isNull: 0, isArray: 1 },
      {
    uniqueId: 'p04', fieldName: 'form.params#name',  fieldComment: '参数名', fieldType: 'string', isNull: 0, type: 'text' },
      {
    uniqueId: 'p05', fieldName: 'form.params#value', fieldComment: '实测值', fieldType: 'string', isNull: 1, type: 'text' }
    ]
  })
});

三个实践细节值得单独说。

fieldName 就是结构描述。 接口收到这组路径后,会反推出完整的 JSON 结构:form.params 标记 isArray: 1,是数组字段;form.params#name 是数组元素内的普通值。不需要额外再传一份 Schema,契约与结构是同一份东西。

控件类型随字段声明。 type 支持 text、textarea、number、select、checkbox、date、radio、qrcode;需要选项的字段(select/checkbox/radio)通过 options 携带 label/value。这意味着"判定"列用下拉还是单选,在字段模型里声明即可,不占模板版式。

覆盖是整体覆盖,不是增量合并。 dataSourceName 唯一,重复推送同名数据源会整体覆盖旧模型。这让字段模型可以随主数据演进——新增参数维度,推一版新模型即可。但也意味着推送前要想清楚 items 是全量集:只推两个字段上去,线上模型就只剩这两个字段。实践中建议把推送脚本纳入版本管理,每次全量执行。

推送完成后,设计器左侧的数据源面板即可引用这批字段(设计器 URL 支持 leftType=dataSource 参数直接初始化该面板)。

四、绑定:组件名就是路径

接下来是实践中最容易踩的一个坑。

表单组件创建时,每个输入组件的名称默认是一段 8 位随机编码(类似 6e430b55)。宿主系统直接按路径传值是无效的:

// 无效:组件名还是随机编码,路径找不到落点
rpc.SET_DATA({
    'form.model': 'X-100' });

// 正确:先把组件名绑定到字段路径,再按路径赋值
rpc.SET_DATA({
    'form.model': 'X-100' });

两行代码一模一样,区别在设计器里:做模板时必须把组件名称绑定到数据源字段的路径上,SET_DATA / GET_DATA 才能以路径为键读写。"数据源绑定"这个词的实质动作就是这一步——组件名从随机编码换成 form.params#value 这样的语义路径,模板与 JSON 结构从此逐一对齐。

如果发现下发数据后表单纹丝不动,第一个该检查的就是组件名有没有完成路径绑定。

五、渲染时序:TEMPLATE_LOADED 之前什么都别做

宿主侧通过 iframe 嵌入查看器页面,用 RPC 库封装通信。时序上有硬约束:模板加载完成(TEMPLATE_LOADED 事件)之前,任何 SET_DATA 都没有落点。正确的下发顺序:

var rpc = createFlashTableRPC(iframe, 'http://flash-table:9150', {
   
  TEMPLATE_LOADED: function () {
   
    // 模板就绪,此时下发数据才有意义
    var params = masterData.getParams(workOrder.model);  // [{name, standard}, ...]
    rpc.SET_DATA({
   
      'form.orderNo': workOrder.orderNo,
      'form.model': workOrder.model,
      'form.params': params
    });
  },
  TEMPLATE_VALUE_CHANGED: function (changed) {
   
    // 值级联动:changed 携带本次变更的对象
    handleValueChanged(changed);
  }
});

差异区在模板里被定义为行循环区域,绑定到 form.params。渲染规则:

  • 绑定的数据必须为数组或正整数,传其他类型循环区域不会展开;
  • 绑定数组时,渲染 params.length 行——下发 3 条渲染 3 行,下发 2 条渲染 2 行;
  • 填写过程中手动加行,新行继承该区域的校验规则。

第二个常见的坑就藏在第一条里:型号差异如果建模成了字符串或对象(而不是数组),循环区域会静默不渲染,控制台没有任何报错。排查时先 console.log 一下下发的 params 确认类型,比检查模板快得多。

型号之间的差异,在这套机制下就是三份不同的数组。渲染层不关心"这是哪个型号",它只按数组办事。

六、数据的完整旅程

填写完成之后,数据怎么回流:

// 1. 取数:keyParameter 传 true 只取关键参数,false 取全量
rpc.GET_DATA({
    keyParameter: false }).then(function (data) {
   
  // data['form.params'] 即填好的明细数组
});

// 2. 提交:返回数据 ID
rpc.SUBMIT_DATA().then(function (dataId) {
   
  // 关键动作:dataId 必须与业务单据绑定保存
  workOrder.recordDataId = dataId;
});

dataId 的保存不是可选项。它丢了,这份数据就无法从业务侧关联回看,只能到数据库里翻。回看有两条路:查看器 URL 直接带 dataId 参数加载已保存数据,或宿主侧调用 LOAD_DATA({ id: dataId })。

至此一条完整链路闭合:主数据组装 params 数组 → SET_DATA 下发 → 循环区域渲染 → 填写 → GET_DATA 取数 / SUBMIT_DATA 归档 → dataId 挂到业务单据 → 按需回看。全程只有一张模板。

七、什么时候放弃归一

上面的方案隐含一个前提:各产品线的差异区结构同构——都是"参数名 / 标准 / 实测值"这样的行结构,只是条目不同。这个前提不成立时,就要放弃归一:

  • 公共区布局都不同、行列结构完全两样,属于结构级差异,强行塞进一张模板会让字段模型出现大量"仅对某型号有意义"的畸形字段;
  • 此时更稳妥的做法是按结构拆模板。模板的创建不必从零开始,复制路由支持传 oldId 继承已有模板起稿(/design/copy);
  • 混合场景——大部分型号同构、个别型号特殊——可以让通用模板与特殊模板并存,运行时按型号调用 LOAD_TEMPLATE({ id, version }) 切换;
  • 模板改版后,version 参数可指定历史版本,归档数据回看时按归档时记录的版本号渲染。

取舍的口径可以很简单:参数条目层面的不同,放进数据里解决;连版式骨架都不一样,就另起一张模板。

八、技术选型

回到第二节那份自研清单——字段注册表、路径解析器、绑定协议、循环渲染器、宿主通信。这五项的投入是一次性的,但协议兼容性的维护是持续的,自研方案真正的成本在后者。实施团队在检验记录这类多型号表单场景中,选择在表单层引入 FlashTable 作为表单开发工具,对应关系如下:

  • 以文档模板为结构来源,Word/Excel 单据可 1:1 还原成线上表单,线下既有的公共区版式不必重画;
  • 原生支持行循环、列循环、块循环,数组条数即渲染行数,手动加行继承校验;
  • 字段契约通过数据源模型推送接口维护,. / # 两种连接符的路径规则既描述数据结构,也充当组件绑定的键;
  • 内置 postMessage 通信封装(LOAD_DATA/SET_DATA/GET_DATA/SUBMIT_DATA/LOAD_TEMPLATE 等),并提供 Docker 离线镜像包,可部署在 OpenEuler/OpenKylin 等国产操作系统上。

九、复盘

把整条实现线收拢:差异建模成数组,是整个方案的地基——型号不同即数组内容不同;字段契约靠推送维护,路径规则让契约与结构合为一体;绑定的实质是组件名对齐路径,这是实践中最常见的一个坑;渲染受 TEMPLATE_LOADED 时序约束,循环区域只认数组;数据经 SUBMIT_DATA 归档,dataId 是回看的唯一钥匙;结构级差异出现时,果断拆模板,复制路由与版本参数负责控制拆分成本。

多型号的检验记录、过程记录这类单据,表单层适合用 FlashTable 这类表单开发工具来落地——结构画一次、差异用数据表达、型号扩充靠主数据而非模板——比在每个项目里重写一套路径解析与循环渲染要稳妥得多。

相关文章
|
12天前
|
Linux 开发工具 iOS开发
Codex保姆级教程(下载+安装+登录+使用,含所有版本,2026最新)
Codex是OpenAI推出的开源AI编程智能体,本地运行,可理解整个代码库,支持自然语言指令改代码、执行命令、跑测试、修Bug,兼容Win/macOS/Linux。(239字)
4965 33
|
13天前
|
JavaScript API iOS开发
DeepSeek Harness下载、安装、配置一篇搞定(2026整理,送常用插件)
DeepSeek Harness(DSH)是DeepSeek于2026年开源的MIT协议AI Agent框架,支持插件化组装模型、工具、沙箱与UI,兼容主流大模型。本文提供免Node.js的桌面端安装包(Win/macOS),含详细图文教程及热门插件推荐。
4585 52
|
7天前
|
Linux 开发工具 iOS开发
2026最新codex下载安装保姆级使用教程(下载+安装+登录+使用,含所有历史版本)
本文详解OpenAI Codex国内快速上手指南:提供Win/macOS/Linux全平台免安装包、npm/Homebrew等多方式安装、账号/API Key双登录、简体中文切换,覆盖下载、安装、汉化、使用及高频避坑(如微软商店失败、npm超时等),10分钟即可跑通。
1654 3
|
13天前
|
iOS开发 Docker MacOS
【2026实测】Docker Desktop下载、安装、汉化、配置教程(附官网安装包)
Docker Desktop 是 Docker 官方推出的桌面容器管理工具,集成引擎、CLI、图形界面与 Kubernetes,支持 Win/macOS/Linux。2026 最新版 4.88.1 强化 ARM 原生运行、镜像加速及资源可视化,附中文汉化与国内镜像配置指南。
2197 7
|
5天前
|
人工智能 安全 API
DeepSeek Harness下载、安装、配置一篇搞定(2026整理,送常用插件)
DeepSeek Harness(dsh)是DeepSeek AI于2026年开源的轻量级Agent运行框架,基于Cordis内核,践行“一切皆插件”理念。支持Web/桌面双端,开箱即用;内置多模型接入、丰富插件生态(超3.5万),适配编程、办公、创作等场景。(239字)
1100 0
|
7天前
|
云安全 人工智能 安全
|
20小时前
|
云安全 人工智能 安全
|
6天前
|
人工智能 并行计算 调度
本地 AI 一键部署工具 LocModel:Windows 端集成画图、视频生成、Agent 的开源模型运行方案
LocModel 是专为 Windows 设计的一站式本地 AI 工具,支持零配置启动、自动硬件检测、GGUF 模型一键加载、文生图/视频生成及 Agent 自动化任务。适配 8G 显存,内置显存分片调度,轻松实现 AI 漫剧批量创作与离线大模型调用。(239 字)

热门文章

最新文章