跳转至

公式定义字段

状态:设计稿(字段类型尚未实现,依赖的前端组件与后端 QLExpress 能力已就绪) 日期:2026-09-14

定义

公式定义字段**是一种动态表单字段类型,供**填单人(运行时用户)**在填写表单时,调用**公式定义组件,通过可视化公式编辑器定义一条 QLExpress 表达式,并将表达式原文随单据保存。

  • 定位与单行文本、多行文本字段类似:是一个**存值字段**,只是存的值是「公式表达式」而非普通文本;
  • 输入方式不是手敲文本,而是点击「编辑表达式」按钮,弹出公式定义组件进行可视化编辑;
  • 字段本身**只负责定义与存储,不负责执行**。公式何时、由谁执行,由消费方(流程、报表、iScript 等)决定。

关键设计决策

决策点 结论 说明
使用主体 填单人(运行时用户) 公式在表单运行时定义,随单据保存;表单设计者仅放置字段
执行语义 仅存表达式,不执行 表单保存/加载时均不计算公式;消费方按需用 executeQLExpress 取用
变量范围 当前表单的存值字段 公式编辑器字段列表 = 当前表单全部 ValueStoreField 字段,字段名(enCode)作为 QLExpress 变量名

背景与依赖

依赖 状态 位置
公式定义组件 obpm-designer-formula ✅ 已就绪 web/obpm-designer-formula(Vue 3 + Pinia + CodeMirror 6 ESM 组件库,可视化编辑输出 QLExpress)
后端 QLExpress 执行能力 ✅ 已就绪 obpm-core 引入 qlexpress4:4.1.2;QLExpressHelper + baselib.js 的 executeQLExpress(expr[, contextOrDoc])(2026-09-14 合入,ecf54373)
后端字段类 FormulaField ⬜ 待实现 cn.myapps.core.runtime.dynaform.form.ejb 下新增
表单设计器字段面板 ⬜ 待实现 增加「公式定义」字段类型
运行时前端集成 ⬜ 待实现 表单运行时渲染 <o-formula> 控件并接入公式定义组件

字段类型的设计遵循 [[../core-component/dyna-form-field|动态表单字段]] 的既有约定(FormField 继承体系、ValueStoreField 存值标记、TLK_ 表 ITEM_ 列)。

前端设计(PC 运行时)

展示形态

多行文本框(只读)+ 「编辑表达式」按钮:

  • 文本框内展示已定义公式的 QLExpress 原文(如 grossSalary - taxThreshold;),不可直接手敲,避免绕过可视化编辑器造成语法错误;
  • 未定义公式时文本框为空;已有公式时可重新编辑或清空。

编辑流程

  1. 填单人点击「编辑表达式」,弹出公式定义组件(FormulaDesigner)对话框;
  2. 宿主(表单运行时)调用 setFieldList(list) 注入变量列表:
  3. fullName = 字段显示名(别名或名称);
  4. enCode = 字段名(即 QLExpress 变量名,与 TLK_ 表 ITEM_ 列名、doc Item 名一致);
  5. value = 字段值类型(number / text / date,用于编辑器类型提示);
  6. 若字段已有值,setQLExpress(字段当前值) 载入既有表达式(组件可由 QLExpress 反解析重建可视化态);
  7. 填单人在组件内编辑、试算,点击确认后组件 confirm 事件返回 { script, warnings, formulaConf };
  8. 宿主取 script(QLExpress 表达式原文)写入字段值;warnings 非空时提示但不阻断(组件侧已尽量保证语法合法)。

组件能力(已实现,直接复用)

  • 可视化公式编辑(CodeMirror 6)、公式模板(TemplateModal)、自定义字段(FieldFormModal)、QLExpress 预览与试算(QlPreview / TrialPanel);
  • 内置函数映射:IF、SUM、MAX、MIN、ROUND、ABS(functionMap.js);
  • QLExpress ⇄ 可视化公式文本互转(toQLExpress.js / fromQLExpress.js),因此**落库只需存 script 原文**,重新打开编辑器时即可还原,无需额外存 formulaConf。

权限与校验

  • 复用 FormField 既有权限模型:hiddenScript / readonlyScript 照常生效;只读态下「编辑表达式」按钮禁用;
  • 表达式合法性由组件前端校验,后端不做二次语法校验(字段不执行,语法错误的代价推迟到消费方执行时暴露)。

后端设计(Java)

字段类

新增 FormulaField,与 TextareaField 同级:

FormField
└── FormulaField(新增,implements ValueStoreField)
  • 包路径:cn.myapps.core.runtime.dynaform.form.ejb.FormulaField;
  • fieldtype 固定为 VALUE_TYPE_TEXT(表达式是任意长度文本);
  • 模板标签:toHtmlTemplate() 输出 <o-formula>(前端运行时据此渲染多行文本框 + 按钮);
  • 无特有脚本钩子,公共钩子(hiddenScript、readonlyScript、validateRule 等)按 FormField 默认行为继承。

运行时数据存储

TLK_<表单名> 建表时,为公式定义字段创建对应的 **TEXT 类型**数据库列(ITEM_<字段名>),落库时将定义的 QLExpress 表达式**原文**存储到该字段:

  • 不存计算结果(本字段不执行);
  • 不存可视化编辑器的中间结构(formulaConf 的 text/marks),需要时可由 script 反解析重建;
  • 列类型按各数据库 TEXT/CLOB 映射,与 TextareaField(VALUE_TYPE_TEXT)一致。

运行时求值(iScript)

公式定义字段本身不执行,消费方通过 baselib 全局函数 executeQLExpress 按需求值。在值脚本 / 操作脚本等处,可对已保存的表达式求值:

(function () {
  var doc = getCurrentDocument();
  var expr = doc.getItemValueAsString("公式字段"); // 存的是 QLExpress 文本
  return executeQLExpress(expr, doc); // 字段名作变量
})()

也支持字面量上下文:

(function () {
  return executeQLExpress("a+b*c", {a: 1, b: 2, c: 3});
})()

典型消费场景:

场景 用法
流程节点脚本 节点执行脚本中求值,结果用于流转条件或回写其他字段
定时任务 / 后处理 批量遍历单据,对历史公式重新求值(如税率调整后重算个税)
报表 报表数据集中结合公式字段做二次计算
前端联动 运行时页面脚本求值后刷新展示(可选增强,不属于本字段职责)

求值语义、上下文构建(doc.getItems() 展开、同名覆盖、异常上抛)与安全策略(QLExpress4 isolation,不开放任意 Java 反射)见实现规格(java 仓):java/docs/superpowers/specs/2026-09-14-iscript-qlexpress-design.md

非目标

  • 字段值落库时**不**触发公式计算,也不存储计算结果;
  • 不支持跨表单 / 视图数据作为公式变量(后续如有需要再扩展变量源);
  • 不为公式定义字段单独做移动端定制(移动端首版可降级为只读展示表达式原文,编辑引导回 PC 端);
  • 不在本字段内做后端表达式语法校验与版本管理。

实施清单

  1. ⬜ Java:新增 FormulaField 字段类(VALUE_TYPE_TEXT、<o-formula> 标签、ValueStoreField);
  2. ⬜ 表单设计器:字段面板增加「公式定义」类型,配置项对齐多行文本字段;
  3. ⬜ PC 运行时:渲染 <o-formula> 控件(多行文本框 + 按钮),集成 obpm-designer-formula 弹窗,变量列表取当前表单存值字段;
  4. ⬜ 建表 / 存储链路:确认 VALUE_TYPE_TEXT 的列类型映射覆盖公式字段;
  5. ⬜ 移动端降级展示(表达式原文只读);
  6. ✅ 前置依赖:公式定义组件、QLExpressHelper / executeQLExpress 已就绪。