
表面看是"表单换个地方填",背后是让智能体完整读懂企业的组织关系与内部规则;AI 的聪明从数据与知识中长出——一句话生成流程,一句话转换表单。直接可用的背后,是强大的数据支撑与完整的 agent 平台技术体系。
本文以财务报销单为样本,讲清一件事:传统 IT 里"流程引擎 + 表单"这套资产,怎样被改造成一个UIAgent——既能对话式地生成流程、转换表单,又能对接设计系统全套 UI/UE 组件、读懂组织机构、把组织变成 BPM 审批流、在知识库里完成字段的业务语义设置。前面看到的那一半只是"表单换了地方填";真正的重活在企业组织关系、内部规则与数据知识的沉淀,本文都用本机实测的路径、字段数、探针结果作为证据。
需求很朴素:员工在协同办公平台或第三方财务系统里填报销单,数据要进我们的财务系统,按我们的规则审批、付款、归档;报销单里还带着费用明细 / 开票明细,不能丢。
拆成四件可验收的事:
业务诉求 | 验收口径 | 本文落点 |
|---|---|---|
单据能收 | 第三方字段 → 自有字段,一一对应且可回写 | §3 字段对齐表 |
页面能填 | 生成可编辑表单(含明细子表),字段与第三方同源 | §4 流水线 · §5 明细 |
流程能走 | 提交 → 审批 → 付款 → 归档,节点可编排 | §5 审批流程 |
代码能跑 | 后端 Schema / Payload 生成并通过编译;产物落库可回读 | §4 · §6 |
真正的麻烦不在"生成",而在两家口径不同:同一个"报销总金额",第三方财务系统叫 submitAmount,协同办公平台的表单里可能写作「报销金额 / 总报销金额 / 申请报销 / 合计 / 金额」,落到我们的页面又是另一个组件。手工对齐一次容易,每换一家重来一次不可接受。这就是整条链路要先解决的头号问题。
传统"流程引擎 + 表单"这套资产,在企业里已经跑了十几年。把它们原样搬进新平台,技术上不难;难的是——原样搬,等于把当年的痛点也一起搬了进来。用 UIAgent 重做一遍,不是为了追新,而是传统做法始终没解决两件事,而智能化改造正好补上。
传统模式下,流程"走完了"不等于数据"通了":报销单在组织系统、发票在影像系统、科目在财务系统、台账在另一套库里,靠人工导出、Excel 对账、邮件催办把数据从一段搬到下一段。流程的"微循环"断在最后一公里——单子批了,但数据没真正汇到一处、没自动核对、没沉淀成可复用的资产。
UIAgent 的做法是把组织关系、内部规则、字段语义先读进统一的数据底座,流程每推进一步,数据就自动归集、自动校验、跨系统对账。数据在流程内部闭环,不再靠人肉接棒,最后一公里由系统自己走完。
传统表单是"静态格子"——人填、人传、人追进度,填表体验是"人去适配系统"。
UIAgent 让表单能对话:用一句话描述诉求,表单自动生成并预填;发票上传或拍照,字段自动识别并回填;报销走到哪一步、卡在谁手里,系统自动跟踪并提醒。填表从"人适配系统"变成"系统适配人",流程从"人追着跑"变成"系统推着走"。
这两点合起来,就是 UIAgent 相对传统流程 + 表单的本质增量:把"流程跑通"升级为数据自闭环 + 交互智能化。后面几节讲清这套能力在平台里是怎么一步步落地的。
与其在每个生成器里各写一份映射,不如把它抽成唯一的对齐真相源:
表里每一条字段,同时记录三个视角:第三方财务系统的 API 路径、协同办公平台的别名与组件类型、OODUI 组件与键名,并给出对齐状态。
role=amount 语义=报销总金额
财务系统 path : submitAmount
协同办公 别名 : ["报销金额","总报销金额","申请报销","合计","金额"] 组件 MoneyField
OODUI : ood.UI.Input(字段类型 amount,必填) status=aligned
role=applicant 语义=报销人 / 申请人
财务系统 path : applicant.name / applicant.userId
协同办公 别名 : ["报销人","申请人","填报人","报销人员"] 组件 DDContact / InnerContactField
OODUI : ood.UI.Input(只读) status=aligned
role=expenseType 语义=费用类型
财务系统 path : feeComponentList[].feeType
协同办公 别名 : ["费用类型","费用类别","报销类别"] 组件 DDSelect
OODUI : ood.UI.Select status=aligned_with_gap注意最后一条的 aligned_with_gap:两家都有,但口径不完全一致(财务系统是费用明细数组里的 feeType,协同办公是单选)。对齐表把这种"能对齐但有缝"的情况显式标注,而不是假装完全一致——后面生成时可以据此决定是否需要人工确认。

图 1 字段对齐:一个 role 同时绑定财务系统路径、协同办公别名与 OODUI 组件
改造后的"流程 + 表单"被拆成两条可独立演进的驱动:流程侧由 skillflow(流程定义与编排)驱动,UI 侧由 uiSkills 基于 componentTree(UI 组件树)驱动。本节讲 UI 侧——表单不再手写 HTML,而是转译为一份 componentTree。
componentTree 描述"有哪些字段、什么类型、如何布局"的树:叶子是 Form 的字段组件(输入框、下拉、日期、金额),明细区是 TreeGrid 节点。uiSkills 基于这棵树完成传统表单的全部交互职责,而不依赖静态 HTML:渲染、取字段(extractFieldsFromComponentTree)、自动填(fillFieldsFromComponentTree)、校验(auditComponentTree)。componentTree 替代表单结构承载了用户交互;"填表单"从"人适配静态页面"变成"uiSkills 读组件树、按流程节点动态裁剪可见字段并回填"。
这份组件树有两个入口,收敛到同一份 componentTree:
对齐表确定后,剩下的工作应该是确定性生成,而不是每次靠模型即兴发挥。这条链路已拆成四个可独立跑、可单独验收的工具(_tools/ 下,Node 实现):
环节 | 工具 | 产物 | 实测结果 |
|---|---|---|---|
对齐表 → 可视化表单 | formalign2ouc.mjs | ExpenseClaim.ouc.json(11 字段 / 2 分组:基础信息、收款信息) | 555 行,bindings 11 条 |
表单 → 页面描述 | ouc2cls.mjs | ooder-domain-finance/src/main/resources/view/ExpenseClaim.cls | 绑定随页面走(moduleVar.bindings) |
产物落库 | vfs-publish.mjs | VFS 落库 + 回读校验 | 9/9 全绿,产物 7368 → 读回 8277 字符,字段命中 3/3 |
对齐表 → 后端代码 | formalign2java.mjs | ExpenseClaimFormSchema.java+ExpenseClaimPayloadBuilder.java | 110 / 63 行,javac编译通过 |

图 2 表单生成流水线:对齐表一次投入,页面与后端同时产出
生成的 .cls 不是"死页面":字段的第三方绑定写进组件树的 source / bindings,随页面一起流转——页面在哪,映射关系就在哪,不必再去别处查对齐表;source 还记了生成器与对齐表版本,半年后追问"这页面按哪版映射生成"答案就在文件里。
对齐表路线适合"字段已知、结构标准"的场景。现实中还有另一类需求:业务方手上已经有一份 HTML 单据(或设计师先出 HTML 稿)。这时不必手工建模,走平台的 html2cls:把 HTML 逆向成平台可加载的页面描述 .cls。
按业务各做一张(真实文件,位于 ooder-pro/src/main/resources/static/finance-reimburse/):
业务单据 | 页面 / 类名 | 核心字段与明细 |
|---|---|---|
员工日常报销单 | expense-claim→ExpenseClaim | 申请人 / 部门 / 费用类型 / 报销金额 / 事由 / 单据日期;明细:开票明细(开票抬头、含税金额);收款信息 + 附件 |
差旅报销单 | travel-expense→TravelExpense | 出差人 / 事由 / 起止日期 / 同行人员;明细:行程明细(日期、出发地、目的地、交通方式、金额);交通/住宿/补贴汇总 |
采购付款申请单 | purchase-payment→PurchasePayment | 供应商 / 合同号 / 发票号 / 发票类型;明细:采购明细(物料、数量、单价、金额);付款方式与收款信息 |
借款申请单 | loan-request→LoanRequest | 借款人 / 借款金额 / 类型 / 用途 / 预计归还日期 / 还款方式;收款信息与逾期提示 |

HTML 原版(浏览器直接打开):员工日常报销单 —— 基础信息、开票明细表、收款信息、附件与提交动作。
(图注取自该页面实际渲染文本)

同套做法的另一张业务单据:差旅报销单 —— 行程明细为多行表格,费用分区汇总。
(图注取自该页面实际渲染文本)
转换复用平台既有流程,两步即可接入:
POST /api/html2cls/runtime/register
{"projectName":"finance","packageId":"finance-reimburse"}
→ {"success":true,"projectName":"finance","packageId":"finance-reimburse",
"ok":4,"fail":0,
"pages":[{"page":"expense-claim","clsName":"ExpenseClaim","status":"OK"},
{"page":"travel-expense","clsName":"TravelExpense","status":"OK"},
{"page":"purchase-payment","clsName":"PurchasePayment","status":"OK"},
{"page":"loan-request","clsName":"LoanRequest","status":"OK"}]}产物落到工程 FS 真源 workspace/finance/src/main/resources/view/:ExpenseClaim.cls 5.8KB、TravelExpense.cls 5.5KB、PurchasePayment.cls 5.7KB、LoanRequest.cls 5.0KB。

CLS 运行时环境:左为 HTML 原版、右为 .cls 渲染结果,用于核对还原度。截图时页面下拉已能选到本次新增的四个业务单据,渲染日志为
✅ view.ExpenseClaim 渲染完成(链路:.cls JSON → cls.ftl 风格 JS → ood.Class → showModule 入 #gtContent)。
(图注取自该页面实际渲染文本;截图前先断言"渲染完成且页面是本次的报销单",避免又截到演示包)
展示型页面沉淀的,表单控件语义不在其现有覆盖内。下一步:把fr-field / fr-detail
这类表单结构补进外观目录与组件映射,即可把还原度从区块级推到字段级;字段级产物再与 §3 的对齐表对接,两条入口(对齐表 / HTML)就收敛到同一份.cls。
在 CLS 运行时并排渲染(左 HTML 原版、右 .cls 渲染)做断言式审计,程序化读取两侧 DOM:左侧断言文档是否真含报销单文本,右侧断言渲染区是否含业务字段。审计揪出两个真实缺陷并迭代修复——左侧缺 index.html 被兜底成错误页、右侧样式失效与 430px 移动视口错配。
修复后整体断言(落盘判据):rendered=true · isMine=true · leftHasForm=true · rightFields=true
—— 四项全过才允许覆盖截图(12-cls-runtime-render.png),两侧视觉基准一致。
报销单不是静态表单,它要能被审批。平台里这条流程是 expense-approval-flow,在流程工作台里按泳道组织:

图 3 报销审批流程:提交申请 → 审批 → 出纳付款 → 归档
「开票明细」这类多行数据,最省事的做法是压成一个文本/JSON 大字段——然后查询、校验、统计全部失效。正确做法是把明细建模成子实体 + 一对多关系:

图 4 明细走 1:N 子实体:表单上是可增删的网格,数据层是真正的子表
落到 OODUI 就是 @Ref(ref = RefType.O2M, view = ViewType.GRID):表单上表现为可增删的明细网格,数据层是真正的子表。明细列名也不是拍脑袋——是从第三方表单样本里真实提取的列(如「开票抬头」「含税金额」),金额类按列名语义判为 DECIMAL(18,2)。
状态说明:主子表在生成链上的两处闸门——①View 层 1:N 模板变量(o2mRefs)此前全仓库无生产者,导致@Ref(O2M)静默不产出;②持久层展开只转主实体一张表,子表整体丢失。这两处已修复并接入(子实体随主表展开为tables=2、relations=1),现有证据为单测级(本机 25 个用例全绿),待一次真实生成流程复核。
生成不是终点,产物要在真实工程里跑起来。这一环在 codeAgent 工作台完成:绑定工程(VFS 根 + FS 工程根,后者是编译/打包必需)后,可以编译当前文件、部署到沙箱、运行、看日志,并直接检测沙箱 harness 状态。

codeAgent 工作台(finance 工程):中栏「工程」页签绑定VFS 根 + FS 工程根(已绑定finance | FS根 ooder-biz-finance),可执行编译 / 部署到沙箱 / 晋级 / 运行 / 查看日志;右栏沙箱页签逐项检测——沙箱基址127.0.0.1:8081可达、令牌有(来源:file:.sandbox-token)、工程绑定是、可部署是、harness 就绪:可编译 / 打包 / 部署 / 运行。
面板是运行时状态而非静态声明:令牌没配时它直接报「未就绪」并给出补齐提示,配好后才变
切到「源码」页签:中栏是已绑定工程的真实源码树(工程根studio\ooder-biz-finace,条目 4:src / main / test / pom.xml),右栏沙箱页签同步显示令牌 有 · 工程绑定 是 · 可部署 是 · harness 就绪 是。这张图同时说明两件事:工程是真工程、真路径(不是演示数据),且"能不能编译/部署/运行"是可查询的运行时事实。(图注取自该页面实际渲染文本)实测判据(上游 G48–G50):设计器探针16/16 全绿;落库通道9/9 全绿,产物 7368 字符写库、引擎读回 8277 字符、字段命中 3/3;后端ExpenseClaimFormSchema.java与ExpenseClaimPayloadBuilder.java经javac编译通过。

图 5 业务架构:第三方差异封在 C 层,领域能力在 B 层,平台能力在 A 层复用
层 | 职责 | 变化来源与频率 | 换第三方时改什么 |
|---|---|---|---|
A平台基座 | 工作台、代码生成、沙箱、VFS、流程引擎、知识库 | 平台演进(低频) | 不改 |
B领域能力 | 报销、发票、流水、账龄、税负等业务语义与规则 | 业务规则调整(中频) | 基本不改 |
C第三方适配 | 开放接口客户端、表单 Schema 拉取、字段别名归位 | 第三方接口/表单变更(高频) | 只改这一层 + 对齐表 |
实际效果可以验证:本文的报销单链路里,"协同办公叫报销金额、财务系统叫 submitAmount"这件事只出现在对齐表和 C 层,B 层的报销规则、A 层的生成器都不知道第三方叫什么。再接一家,工作量是"补一列别名 + 一个适配方法",而不是重做页面与代码。
用自然语言提需求最容易踩的坑是:一句话进去,一团不可复现的东西出来。这里的做法是让对话负责决策,让流程负责执行:

图 6 对话 + 确定性流程:对话决策 → 流程编排 → 生成器产出 → 探针验收
一句话概括:对话负责决策,流程负责编排,生成器负责产出,探针负责验收。这样自然语言交互的随机性被关在决策环节,产出与验收环节仍然是确定性的、可回归的。
字段对齐表、语义命名映射不是"一次性配置",而是可版本化的知识资产——它们已经落在知识库目录里:
好处是:下一个报销类场景不需要从零对齐——直接命中已有语义映射,只处理差异项(对齐表里 aligned_with_gap / aligned_no_role 的条目就是需要人工拍板的部分)。平台已具备 kb_get 检索能力,知识库从"文档仓库"变成"生成链路的输入"。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。