公式定义字段¶
状态:设计稿(字段类型尚未实现,依赖的前端组件与后端 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;),不可直接手敲,避免绕过可视化编辑器造成语法错误; - 未定义公式时文本框为空;已有公式时可重新编辑或清空。
编辑流程¶
- 填单人点击「编辑表达式」,弹出公式定义组件(
FormulaDesigner)对话框; - 宿主(表单运行时)调用
setFieldList(list)注入变量列表: fullName= 字段显示名(别名或名称);enCode= 字段名(即 QLExpress 变量名,与TLK_表ITEM_列名、docItem 名一致);value= 字段值类型(number/text/date,用于编辑器类型提示);- 若字段已有值,
setQLExpress(字段当前值)载入既有表达式(组件可由 QLExpress 反解析重建可视化态); - 填单人在组件内编辑、试算,点击确认后组件
confirm事件返回{ script, warnings, formulaConf }; - 宿主取
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 同级:
- 包路径:
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); // 字段名作变量
})()
也支持字面量上下文:
典型消费场景:
| 场景 | 用法 |
|---|---|
| 流程节点脚本 | 节点执行脚本中求值,结果用于流转条件或回写其他字段 |
| 定时任务 / 后处理 | 批量遍历单据,对历史公式重新求值(如税率调整后重算个税) |
| 报表 | 报表数据集中结合公式字段做二次计算 |
| 前端联动 | 运行时页面脚本求值后刷新展示(可选增强,不属于本字段职责) |
求值语义、上下文构建(
doc.getItems()展开、同名覆盖、异常上抛)与安全策略(QLExpress4 isolation,不开放任意 Java 反射)见实现规格(java 仓):java/docs/superpowers/specs/2026-09-14-iscript-qlexpress-design.md
非目标¶
- 字段值落库时**不**触发公式计算,也不存储计算结果;
- 不支持跨表单 / 视图数据作为公式变量(后续如有需要再扩展变量源);
- 不为公式定义字段单独做移动端定制(移动端首版可降级为只读展示表达式原文,编辑引导回 PC 端);
- 不在本字段内做后端表达式语法校验与版本管理。
实施清单¶
- ⬜ Java:新增
FormulaField字段类(VALUE_TYPE_TEXT、<o-formula>标签、ValueStoreField); - ⬜ 表单设计器:字段面板增加「公式定义」类型,配置项对齐多行文本字段;
- ⬜ PC 运行时:渲染
<o-formula>控件(多行文本框 + 按钮),集成obpm-designer-formula弹窗,变量列表取当前表单存值字段; - ⬜ 建表 / 存储链路:确认
VALUE_TYPE_TEXT的列类型映射覆盖公式字段; - ⬜ 移动端降级展示(表达式原文只读);
- ✅ 前置依赖:公式定义组件、
QLExpressHelper/executeQLExpress已就绪。