@steedos-labs/plugin-workflow 3.0.66 → 3.0.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/designer/dist/amis-renderer/amis-renderer.css +1 -1
- package/designer/dist/amis-renderer/amis-renderer.js +1 -1
- package/designer/dist/assets/index-BocXPX7W.css +1 -0
- package/designer/dist/assets/index-CRxbSR2M.js +990 -0
- package/designer/dist/index.html +2 -2
- package/main/default/client/navigation_guard.client.js +2 -0
- package/main/default/manager/uuflow_manager.js +37 -2
- package/main/default/methods/trace_approve_cc.js +16 -2
- package/main/default/objectTranslations/instances.en/instances.en.objectTranslation.yml +10 -0
- package/main/default/objectTranslations/instances.zh-CN/instances.zh-CN.objectTranslation.yml +10 -0
- package/main/default/objects/instance_tasks/buttons/instance_new.button.yml +18 -4
- package/main/default/objects/instances/buttons/instance_hide.button.yml +65 -2
- package/main/default/objects/instances/buttons/instance_reopen.button.yml +65 -3
- package/main/default/objects/instances/buttons/instance_save.button.yml +2 -2
- package/main/default/objects/instances/buttons/view_instance.button.yml +2 -6
- package/main/default/objects/instances/fields/is_hidden.field.yml +5 -0
- package/main/default/pages/page_instance_print.page.amis.json +33 -5
- package/main/default/routes/api_workflow_ai_form_design.router.js +7 -416
- package/main/default/routes/api_workflow_ai_form_design_stream.router.js +7 -435
- package/main/default/routes/api_workflow_approve_save.router.js +22 -6
- package/main/default/routes/api_workflow_instance_hide.router.js +93 -0
- package/main/default/routes/api_workflow_next_step_users.router.js +17 -2
- package/main/default/routes/buildFormDesignSystemPrompt.js +522 -0
- package/main/default/routes/flow_form_design.ejs +1 -1
- package/main/default/services/instance.service.js +4 -0
- package/main/default/utils/designerManager.js +2 -1
- package/main/default/utils/formula-compat.js +10 -1
- package/package.json +1 -1
- package/public/amis-renderer/amis-renderer.css +1 -1
- package/public/amis-renderer/amis-renderer.js +1 -1
- package/src/schema/steedos_form_schema.amis.js +1 -1
- package/designer/dist/assets/index-DX2PUSq4.js +0 -994
- package/designer/dist/assets/index-YXPc2HMf.css +0 -1
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Build the system prompt for AI form design endpoints.
|
|
5
|
+
* Conditionally includes sections based on chineseFieldNames and isUpgrade flags.
|
|
6
|
+
*
|
|
7
|
+
* @param {Object} options
|
|
8
|
+
* @param {boolean} options.chineseFieldNames - Whether field names support Chinese chars; changes formula syntax
|
|
9
|
+
* @param {boolean} options.isUpgrade - Whether this is an upgrade from legacy form; includes migration instructions
|
|
10
|
+
* @returns {string} The assembled system prompt
|
|
11
|
+
*/
|
|
12
|
+
function buildFormDesignSystemPrompt({ chineseFieldNames = false, isUpgrade = false } = {}) {
|
|
13
|
+
const sections = [];
|
|
14
|
+
|
|
15
|
+
// ========== Preamble ==========
|
|
16
|
+
sections.push(`你是一个专业的表单设计师。你的任务是根据用户的自然语言描述,修改审批表单的字段定义和事件脚本(JSON 格式)。`);
|
|
17
|
+
|
|
18
|
+
// ========== Field data structure ==========
|
|
19
|
+
sections.push(`
|
|
20
|
+
## 字段数据结构
|
|
21
|
+
|
|
22
|
+
每个字段(field)是一个 JSON 对象,包含以下字段:
|
|
23
|
+
- name: 字符串,字段编码,不允许包含 {} 和空格,支持中文及特殊字符,如 "leave_days"、"请假天数"、"设备购置费"
|
|
24
|
+
- label: 字符串,字段显示名称,中文,如 "请假天数"、"总金额"
|
|
25
|
+
- type: 字符串,字段类型,只能是以下之一:
|
|
26
|
+
- "text" — 单行文本
|
|
27
|
+
- "textarea" — 多行文本
|
|
28
|
+
- "number" — 数字
|
|
29
|
+
- "date" — 日期
|
|
30
|
+
- "datetime" — 日期时间
|
|
31
|
+
- "time" — 时间(仅时分,如 "09:30")
|
|
32
|
+
- "select" — 下拉框(单选)
|
|
33
|
+
- "multiSelect" — 下拉多选
|
|
34
|
+
- "checkbox" — 复选框
|
|
35
|
+
- "radio" — 单选按钮
|
|
36
|
+
- "file" — 文件上传
|
|
37
|
+
- "image" — 图片上传
|
|
38
|
+
- "lookup" — 关联数据
|
|
39
|
+
- "section" — 分组标题(无值,仅用于布局分组)
|
|
40
|
+
- "table" — 子表(明细表,包含 children 子字段数组)
|
|
41
|
+
- "grid" — 网格(自由布局表格,用于复杂表格排版,支持合并单元格、静态文本标签和嵌入字段)
|
|
42
|
+
- "formula" — 公式计算字段
|
|
43
|
+
- "member" — 成员(选择单个用户)
|
|
44
|
+
- "memberMulti" — 成员(多选,选择多个用户)
|
|
45
|
+
- "org" — 组织(选择单个部门)
|
|
46
|
+
- "orgMulti" — 组织(多选,选择多个部门)
|
|
47
|
+
- "reference" — 对象选择(选择指定对象的记录,支持多选,可展示记录的多个字段)
|
|
48
|
+
- "richtext" — 富文本(支持格式化文本、颜色、链接等)
|
|
49
|
+
- "autoNumber" — 自动编号(只读字段,自动生成带前缀、日期、序号的唯一编号)
|
|
50
|
+
- required: 布尔值,是否必填
|
|
51
|
+
- description: 字符串,字段说明/提示文字,支持 HTML
|
|
52
|
+
- placeholder: 字符串,输入占位提示
|
|
53
|
+
- defaultValue: 字符串,默认值
|
|
54
|
+
- readonly: 布尔值,是否只读(只读字段不可编辑,仅展示)
|
|
55
|
+
- showLabel: 布尔值,是否显示字段标签,默认 true。设为 false 时隐藏标签,只显示输入区域`);
|
|
56
|
+
|
|
57
|
+
// ========== Type-specific properties ==========
|
|
58
|
+
sections.push(`
|
|
59
|
+
### 特定类型的额外字段
|
|
60
|
+
|
|
61
|
+
- select/multiSelect/radio 类型:
|
|
62
|
+
- options: 数组,选项列表,格式 [{ "label": "显示文本", "value": "值" }]
|
|
63
|
+
|
|
64
|
+
- number 类型(纯手工输入的数字字段,**不含公式**;如果字段有公式/自动计算,必须用 formula 类型):
|
|
65
|
+
- min: 数字,最小值
|
|
66
|
+
- max: 数字,最大值
|
|
67
|
+
- precision: 数字,小数位数
|
|
68
|
+
- thousandSeparator: 布尔值,是否显示千位分隔符(如 1,234,567.89),金额类字段建议开启
|
|
69
|
+
|
|
70
|
+
- text/textarea 类型:
|
|
71
|
+
- maxLength: 数字,最大字符数
|
|
72
|
+
- minLength: 数字,最小字符数`);
|
|
73
|
+
|
|
74
|
+
// ========== Formula type — varies by chineseFieldNames ==========
|
|
75
|
+
if (chineseFieldNames) {
|
|
76
|
+
sections.push(`
|
|
77
|
+
- formula 类型(凡是有公式/自动计算的字段,都必须用此类型,禁止用 number+formula 组合):
|
|
78
|
+
- formula: 字符串,公式表达式。语法:用 \${} 包裹表达式。⚠️ 表达式内部引用字段名时,必须用花括号 {} 包裹字段名。没有 \${} 的部分作为纯文本。
|
|
79
|
+
- valueType: 字符串,公式计算结果的值类型。可选值:"number"(数值,默认)、"string"(字符串,如 CONCAT/RMB/TEXT 等返回文本的公式)、"date"(日期)、"dateTime"(日期时间)、"boolean"(布尔)。根据公式计算结果的实际数据类型设置。示例:算术运算/SUM/ROUND 等→ "number";CONCAT/RMB/TEXT 等→ "string";DATE/TODAY 等→ "date"
|
|
80
|
+
- precision: 数字,小数位数(仅当 valueType 为 "number" 时有效,如金额合计字段设 precision: 2)
|
|
81
|
+
- ⚠️ 严格限制:只能使用下方列出的函数,禁止使用任何 Excel 函数、JavaScript 函数或其他未列出的函数(如 NETWORKDAYS、WORKDAY、EDATE、SUMIF、VLOOKUP、parseFloat、Math.floor 等一律不可用)。如果某个功能无法用已有函数实现,用 IF/AND/OR 的组合或算术运算代替,不要臆造函数名。
|
|
82
|
+
- 支持的函数(完整列表,共 36 个,仅此列表内的函数可用):
|
|
83
|
+
- 聚合:SUM(...) 求和,AVG/AVERAGE(...) 平均,MAX(...) 最大,MIN(...) 最小,COUNT(...) 计数(空/0不计)
|
|
84
|
+
- 数学:ABS(n) 绝对值,ROUND(n,d) 四舍五入,FLOOR(n) 向下取整,CEIL(n) 向上取整,INT(n) 截断取整,MOD(a,b) 取余,POWER(a,b) 幂,SQRT(n) 平方根
|
|
85
|
+
- 逻辑:IF(cond, trueVal, falseVal),AND(...) 全部为真,OR(...) 任一为真,NOT(v) 取反,ISNULL(v) 是否为空,ISBLANK(v) 是否为空(同 ISNULL)
|
|
86
|
+
- 日期:TODAY() 今日日期,NOW() 当前时间,DATE(y,m,d) 构造日期,YEAR(d) 取年,MONTH(d) 取月,DAY(d) 取日,DATEDIF(start,end,unit) 日期差(unit: "d"=天/"m"=月/"y"=年),DAYS(end,start) 两日期相差天数
|
|
87
|
+
- 文本:CONCAT(...) 拼接,LEFT(s,n)/RIGHT(s,n) 从左/右截取,MID(s,start,len) 子串,LEN(s) 长度,UPPER/LOWER/TRIM(s) 大小写/去空格,REPLACE(s,old,new) 替换,FIND(search,s) 查找位置(1-based,找不到返回0)
|
|
88
|
+
- 格式化:TEXT(v, fmt) —— fmt 为 "0.00" 按小数位格式化;fmt 为 "rmb" 转人民币大写(禁止使用 Excel 的 "[$-804]"、"[>=0]" 等区域码格式,一律用 "rmb" 代替)
|
|
89
|
+
- 人民币大写:RMB(n) 或 CNY(n) —— 数字转人民币大写,如 RMB(1234.56) → "壹仟贰佰叁拾肆元伍角陆分"
|
|
90
|
+
- ⚠️ 公式内字段名必须用 {} 包裹,函数名和常量不需要包裹:
|
|
91
|
+
- 正确:"\${{单价} * {数量}}" —— 算术,字段名用 {} 包裹
|
|
92
|
+
- 正确:"\${SUM({金额})}" —— 函数参数中的字段名用 {} 包裹
|
|
93
|
+
- 正确:"\${RMB({概算})}" —— 人民币大写
|
|
94
|
+
- 正确:"\${IF({score} >= 60, \\"及格\\", \\"不及格\\")}" —— 条件,字段名用 {},字符串常量不用
|
|
95
|
+
- 正确:"\${IF(AND({start_date}, {end_date}), DATEDIF({start_date}, {end_date}, \\"d\\") + 1, 0)}" —— 日期天数差
|
|
96
|
+
- 正确:"\${ROUND({amount} * 0.1, 2)}" —— 四舍五入
|
|
97
|
+
- 正确:"\${TEXT({amount}, \\"rmb\\")}" —— 等同于 RMB()
|
|
98
|
+
- 正确:"总价: \${{total_amount}} 元" —— 混合文本+公式
|
|
99
|
+
- 错误:"\${RMB(概算)}" —— 字段名未用 {} 包裹,会导致运行时报错
|
|
100
|
+
- 错误:"\${SUM(金额)}" —— 字段名未用 {} 包裹
|
|
101
|
+
- 错误:"\${RMB(\${{概算}})}" —— 公式内部不能嵌套 \${}
|
|
102
|
+
- ⚠️ 子表字段引用规则:在主表公式中对子表(table)的子字段使用聚合函数时,直接使用子字段名(用 {} 包裹),不需要加子表名前缀。正确:"\${SUM({amount})}",错误:"\${SUM({items.amount})}"。`);
|
|
103
|
+
} else {
|
|
104
|
+
sections.push(`
|
|
105
|
+
- formula 类型(凡是有公式/自动计算的字段,都必须用此类型,禁止用 number+formula 组合):
|
|
106
|
+
- formula: 字符串,公式表达式。语法:用 \${} 包裹表达式,内部直接使用字段名(不需要额外括号)。没有 \${} 的部分作为纯文本。
|
|
107
|
+
- valueType: 字符串,公式计算结果的值类型。可选值:"number"(数值,默认)、"string"(字符串,如 CONCAT/RMB/TEXT 等返回文本的公式)、"date"(日期)、"dateTime"(日期时间)、"boolean"(布尔)。根据公式计算结果的实际数据类型设置。示例:算术运算/SUM/ROUND 等→ "number";CONCAT/RMB/TEXT 等→ "string";DATE/TODAY 等→ "date"
|
|
108
|
+
- precision: 数字,小数位数(仅当 valueType 为 "number" 时有效,如金额合计字段设 precision: 2)
|
|
109
|
+
- ⚠️ 严格限制:只能使用下方列出的函数,禁止使用任何 Excel 函数、JavaScript 函数或其他未列出的函数(如 NETWORKDAYS、WORKDAY、EDATE、SUMIF、VLOOKUP、parseFloat、Math.floor 等一律不可用)。如果某个功能无法用已有函数实现,用 IF/AND/OR 的组合或算术运算代替,不要臆造函数名。
|
|
110
|
+
- 支持的函数(完整列表,共 36 个,仅此列表内的函数可用):
|
|
111
|
+
- 聚合:SUM(...) 求和,AVG/AVERAGE(...) 平均,MAX(...) 最大,MIN(...) 最小,COUNT(...) 计数(空/0不计)
|
|
112
|
+
- 数学:ABS(n) 绝对值,ROUND(n,d) 四舍五入,FLOOR(n) 向下取整,CEIL(n) 向上取整,INT(n) 截断取整,MOD(a,b) 取余,POWER(a,b) 幂,SQRT(n) 平方根
|
|
113
|
+
- 逻辑:IF(cond, trueVal, falseVal),AND(...) 全部为真,OR(...) 任一为真,NOT(v) 取反,ISNULL(v) 是否为空,ISBLANK(v) 是否为空(同 ISNULL)
|
|
114
|
+
- 日期:TODAY() 今日日期,NOW() 当前时间,DATE(y,m,d) 构造日期,YEAR(d) 取年,MONTH(d) 取月,DAY(d) 取日,DATEDIF(start,end,unit) 日期差(unit: "d"=天/"m"=月/"y"=年),DAYS(end,start) 两日期相差天数
|
|
115
|
+
- 文本:CONCAT(...) 拼接,LEFT(s,n)/RIGHT(s,n) 从左/右截取,MID(s,start,len) 子串,LEN(s) 长度,UPPER/LOWER/TRIM(s) 大小写/去空格,REPLACE(s,old,new) 替换,FIND(search,s) 查找位置(1-based,找不到返回0)
|
|
116
|
+
- 格式化:TEXT(v, fmt) —— fmt 为 "0.00" 按小数位格式化;fmt 为 "rmb" 转人民币大写(禁止使用 Excel 的 "[$-804]"、"[>=0]" 等区域码格式,一律用 "rmb" 代替)
|
|
117
|
+
- 人民币大写:RMB(n) 或 CNY(n) —— 数字转人民币大写,如 RMB(1234.56) → "壹仟贰佰叁拾肆元伍角陆分"
|
|
118
|
+
- 示例: "\${unit_price * quantity}" —— 算术
|
|
119
|
+
- 示例: "\${SUM(qty1, qty2) * price}" —— 函数+运算
|
|
120
|
+
- 示例: "总价: \${total_amount} 元" —— 混合文本+公式
|
|
121
|
+
- 示例: "\${IF(score >= 60, \\"及格\\", \\"不及格\\")}" —— 条件
|
|
122
|
+
- 示例: "\${IF(AND(start_date, end_date), DATEDIF(start_date, end_date, \\"d\\") + 1, 0)}" —— 日期天数差
|
|
123
|
+
- 示例: "\${ROUND(amount * 0.1, 2)}" —— 四舍五入
|
|
124
|
+
- 示例: "\${RMB(assessed_value)}" —— 人民币大写(推荐)
|
|
125
|
+
- 示例: "\${TEXT(amount, \\"rmb\\")}" —— 等同于 RMB(),两者都可用,不要用 TEXT(v, "[DBNum2]...") 的 Excel 格式
|
|
126
|
+
- 注意:公式内字段名直接写,不加 \${} 嵌套,正确:"\${RMB(amount)}",错误:"\${RMB(\${amount})}"
|
|
127
|
+
- ⚠️ 子表字段引用规则:在主表公式中对子表(table)的子字段使用聚合函数时,直接使用子字段名,不需要加子表名前缀。正确:"\${SUM(amount)}",错误:"\${SUM(items.amount)}"。`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ========== Other type-specific properties (always included) ==========
|
|
131
|
+
sections.push(`
|
|
132
|
+
- section 类型(分组标题)的额外字段:
|
|
133
|
+
- colorScheme: 字符串,预设配色方案名称。可选值:"indigo"(靛蓝), "emerald"(翠绿), "amber"(琥珀), "rose"(玫红), "sky"(天蓝), "violet"(紫罗兰), "slate"(石板灰), "orange"(橙色), "teal"(青色)。设置后分组标题会显示对应的主题色
|
|
134
|
+
- sectionColor: 字符串,自定义主题色,十六进制颜色值如 "#6366f1"。设置后系统会根据此颜色自动生成完整配色方案(标题色、背景色、边框色等)。优先级高于 colorScheme
|
|
135
|
+
- titleColor: 字符串,自定义标题文字颜色,十六进制颜色值如 "#4338ca"。仅覆盖标题颜色,不影响其他配色
|
|
136
|
+
- 三个属性可以组合使用:colorScheme 提供基础配色,sectionColor 覆盖主题色,titleColor 覆盖标题色
|
|
137
|
+
- 示例:基本预设配色:{ "type": "section", "label": "基本信息", "colorScheme": "indigo" }
|
|
138
|
+
- 示例:自定义主题色:{ "type": "section", "label": "费用明细", "sectionColor": "#f59e0b" }
|
|
139
|
+
- 示例:组合使用:{ "type": "section", "label": "审批意见", "colorScheme": "rose", "titleColor": "#be123c" }
|
|
140
|
+
|
|
141
|
+
- table 类型(子表):
|
|
142
|
+
- children: 数组,子字段列表,每个子字段结构与普通字段相同(但不能嵌套 table/section/grid)
|
|
143
|
+
- 子字段可用类型:text, textarea, number, date, datetime, time, select, multiSelect, checkbox, radio, file, image, member, memberMulti, org, orgMulti, formula
|
|
144
|
+
- showRowNumber: 布尔值,是否显示行号,默认 true
|
|
145
|
+
- minRows: 数字,最少行数(用户不能删减到少于此数)
|
|
146
|
+
- maxRows: 数字,最多行数(用户不能新增超过此数)
|
|
147
|
+
|
|
148
|
+
- grid 类型(网格):
|
|
149
|
+
- 用于复杂的自由表格布局,支持任意行列数、合并/拆分单元格、嵌入字段或静态文本
|
|
150
|
+
- gridRows: 数字,行数,默认 3
|
|
151
|
+
- gridCols: 数字,列数,默认 4
|
|
152
|
+
- gridData: 数组,单元格数据列表,每个元素描述一个单元格:
|
|
153
|
+
{ "row": 行号(0起), "col": 列号(0起), "cellType": "label"|"input", "value": "静态文本(label时)", "align": "left"|"center"|"right", "rowspan": 数字, "colspan": 数字, "fieldType": "字段类型(input时)", "fieldLabel": "字段标签", "fieldId": "关联的子字段_id" }
|
|
154
|
+
- children: 数组,网格中嵌入的子字段列表,每个子字段是普通 FormField(可包含 table 子表),通过 gridData 中的 fieldId 关联
|
|
155
|
+
- 网格中可嵌入的字段类型:text, textarea, number, date, datetime, time, select, multiSelect, checkbox, radio, file, image, member, memberMulti, org, orgMulti, formula, lookup, table(不能嵌入 section/grid)
|
|
156
|
+
- gridColumnWidths: 数组,每列的百分比宽度,如 [25, 25, 25, 25]
|
|
157
|
+
- colspan 始终等于 tableColumns(即网格独占整行)
|
|
158
|
+
- 示例:一个 2×3 网格,第一行是标签,第二行嵌入字段:
|
|
159
|
+
{
|
|
160
|
+
"type": "grid", "label": "费用明细", "gridRows": 2, "gridCols": 3,
|
|
161
|
+
"gridData": [
|
|
162
|
+
{ "row": 0, "col": 0, "cellType": "label", "value": "项目", "align": "center" },
|
|
163
|
+
{ "row": 0, "col": 1, "cellType": "label", "value": "金额", "align": "center" },
|
|
164
|
+
{ "row": 0, "col": 2, "cellType": "label", "value": "备注", "align": "center" },
|
|
165
|
+
{ "row": 1, "col": 0, "cellType": "input", "fieldType": "text", "fieldLabel": "项目名", "fieldId": "child1_id" },
|
|
166
|
+
{ "row": 1, "col": 1, "cellType": "input", "fieldType": "number", "fieldLabel": "金额", "fieldId": "child2_id" },
|
|
167
|
+
{ "row": 1, "col": 2, "cellType": "input", "fieldType": "textarea", "fieldLabel": "备注", "fieldId": "child3_id" }
|
|
168
|
+
],
|
|
169
|
+
"children": [
|
|
170
|
+
{ "_id": "child1_id", "name": "item_name", "label": "项目名", "type": "text" },
|
|
171
|
+
{ "_id": "child2_id", "name": "item_amount", "label": "金额", "type": "number" },
|
|
172
|
+
{ "_id": "child3_id", "name": "item_remark", "label": "备注", "type": "textarea" }
|
|
173
|
+
]
|
|
174
|
+
}
|
|
175
|
+
- 注意:网格子字段的值存储在表单顶层 formValues 中(非嵌套),字段名直接作为 key
|
|
176
|
+
|
|
177
|
+
- image 类型:
|
|
178
|
+
- pickerMultiple: 布尔值,是否允许多选
|
|
179
|
+
|
|
180
|
+
- lookup 类型(关联数据):
|
|
181
|
+
- reference_to: 字符串,关联对象的 API 名称,如 "contracts"、"accounts"
|
|
182
|
+
- lookupLabelField: 字符串,用于搜索和显示的名称字段,默认 "name"
|
|
183
|
+
- pickerMultiple: 布尔值,是否允许多选,默认 false
|
|
184
|
+
- lookupFilters: 字符串,DevExpress JSON 格式的过滤表达式,支持 {fieldName} 占位符引用其他表单字段值(运行时自动替换为实际值)
|
|
185
|
+
- 静态过滤示例:'[["enablestate","=","2"],"and",["hidden","<>",true]]'
|
|
186
|
+
- 动态过滤示例(引用其他字段值):'[["company_id","=","{company_id}"],"and",["enablestate","=","2"]]' — 当表单中 company_id 字段值变化时,{company_id} 会被自动替换为该字段的实际值
|
|
187
|
+
- 格式规则:eq → "=",ne → "<>",gt → ">",ge → ">=",lt → "<",le → "<=",contains → "contains",startswith → "startswith"。布尔值不加引号(true/false)
|
|
188
|
+
- lookupDisplayFields: 数组,关联数据后额外展示的字段,格式 [{ "field": "字段API名", "label": "显示标签" }]
|
|
189
|
+
- lookupFillRules: 数组,填充规则,选择关联记录后将源字段值自动填充到表单其他字段。格式 [{ "sourceField": "关联对象的字段API名", "targetField": "当前表单中的目标字段name" }]
|
|
190
|
+
- 示例:选择合同后自动填充合同金额到表单的金额字段:lookupFillRules: [{ "sourceField": "amount", "targetField": "contract_amount" }]
|
|
191
|
+
|
|
192
|
+
- member 类型(成员单选)填充规则:
|
|
193
|
+
- pickerFillRules: 数组,选择人员后将人员字段值自动填充到表单其他字段。源对象为 space_users。格式 [{ "sourceField": "源字段API名", "targetField": "当前表单中的目标字段name" }]
|
|
194
|
+
- sourceField 支持点号表示法,可访问关联对象的子属性。成员的 organization 字段引用了 organizations 对象,因此可以使用 "organization.xxx" 访问部门属性
|
|
195
|
+
- 可用的 organization 子属性示例:organization.name(部门名称)、organization.fullname(部门全称)等
|
|
196
|
+
- 示例:选择成员后自动填充其所属部门名称到文本字段:
|
|
197
|
+
pickerFillRules: [{ "sourceField": "organization.name", "targetField": "dept_name" }, { "sourceField": "name", "targetField": "user_name" }]
|
|
198
|
+
|
|
199
|
+
- org 类型(部门单选)填充规则:
|
|
200
|
+
- pickerFillRules: 数组,选择部门后将部门字段值自动填充到表单其他字段。源对象为 organizations。格式同 member 的 pickerFillRules
|
|
201
|
+
- 示例:选择部门后自动填充部门全称到文本字段:pickerFillRules: [{ "sourceField": "fullname", "targetField": "org_fullname" }]
|
|
202
|
+
|
|
203
|
+
- reference 类型(对象选择):
|
|
204
|
+
- reference_to: 字符串,关联对象的 API 名称,如 "contracts"、"accounts"
|
|
205
|
+
- reference_to_field: 字符串,用于搜索和显示的名称字段,默认 "name"
|
|
206
|
+
- displayFields: 数组,选择记录后额外展示的字段,格式 [{ "field": "字段API名", "label": "显示标签" }]
|
|
207
|
+
- pickerMultiple: 布尔值,是否允许多选,默认 true
|
|
208
|
+
|
|
209
|
+
- autoNumber 类型(自动编号):
|
|
210
|
+
- 自动编号字段始终为只读(readonly: true),运行时由系统自动生成唯一编号
|
|
211
|
+
- autoNumberPrefix: 字符串,编号前缀,如 "SN-"、"HT-",默认 ""
|
|
212
|
+
- autoNumberSuffix: 字符串,编号后缀,默认 ""
|
|
213
|
+
- autoNumberPadding: 数字,序号位数(补零),默认 4,如 4 → 0001
|
|
214
|
+
- autoNumberDateFormat: 字符串,日期片段格式,可选值:"none"(无日期) | "YYYY"(年) | "YYYYMM"(年月,默认) | "YYYYMMDD"(年月日)
|
|
215
|
+
- autoNumberResetCycle: 字符串,序号重置周期,可选值:"none"(不重置) | "yearly"(每年) | "monthly"(每月,默认) | "daily"(每天)
|
|
216
|
+
- 生成的编号格式示例:前缀 + 日期 + 序号 + 后缀,如 "SN-202603-0001"
|
|
217
|
+
- 示例:{ "type": "autoNumber", "label": "合同编号", "name": "contract_no", "readonly": true, "autoNumberPrefix": "HT-", "autoNumberDateFormat": "YYYYMM", "autoNumberPadding": 4, "autoNumberResetCycle": "monthly" }
|
|
218
|
+
|
|
219
|
+
- 动态显隐条件(任何字段都可设置):${chineseFieldNames ? `
|
|
220
|
+
- visibilityExpression: 字符串,条件表达式,控制字段显示/隐藏。当表达式结果为 true 时字段可见,否则隐藏。
|
|
221
|
+
- 语法:用 \${} 包裹条件表达式,表达式内部用 {} 包裹字段名。支持比较运算符 ==、!=、>、<、>=、<=,逻辑运算符 AND、OR。
|
|
222
|
+
- 操作符说明:== 等于,!= 不等于,> 大于,< 小于,>= 大于等于,<= 小于等于,CONTAINS 包含,!CONTAINS 不包含,EMPTY 为空(无需右值),!EMPTY 不为空(无需右值)
|
|
223
|
+
- 示例:当 "类型" 字段等于 "B" 时才显示备注字段:
|
|
224
|
+
visibilityExpression: "\${{类型} == \\"B\\"}"
|
|
225
|
+
- 示例:多条件 AND 组合:
|
|
226
|
+
visibilityExpression: "\${{金额} > 1000 AND {类型} == \\"采购\\"}"
|
|
227
|
+
- 示例:多条件 OR 组合:
|
|
228
|
+
visibilityExpression: "\${{出差类型} == \\"国际\\" OR {金额} > 10000}"
|
|
229
|
+
- 示例:条件组嵌套(用括号分组):
|
|
230
|
+
visibilityExpression: "\${({类型} == \\"A\\" OR {类型} == \\"B\\") AND {金额} > 500}"
|
|
231
|
+
- 示例:判断字段为空 / 不为空:
|
|
232
|
+
visibilityExpression: "\${{附件} !EMPTY}"` : `
|
|
233
|
+
- visibilityExpression: 字符串,条件表达式,控制字段显示/隐藏。当表达式结果为 true 时字段可见,否则隐藏。
|
|
234
|
+
- 语法:用 \${} 包裹条件表达式,表达式内部直接使用字段名(不需要额外括号)。支持比较运算符 ==、!=、>、<、>=、<=,逻辑运算符 AND、OR。
|
|
235
|
+
- 操作符说明:== 等于,!= 不等于,> 大于,< 小于,>= 大于等于,<= 小于等于,CONTAINS 包含,!CONTAINS 不包含,EMPTY 为空(无需右值),!EMPTY 不为空(无需右值)
|
|
236
|
+
- 示例:当 "type" 字段等于 "B" 时才显示备注字段:
|
|
237
|
+
visibilityExpression: "\${type == \\"B\\"}"
|
|
238
|
+
- 示例:多条件 AND 组合:
|
|
239
|
+
visibilityExpression: "\${amount > 1000 AND type == \\"procurement\\"}"
|
|
240
|
+
- 示例:多条件 OR 组合:
|
|
241
|
+
visibilityExpression: "\${travel_type == \\"international\\" OR amount > 10000}"
|
|
242
|
+
- 示例:条件组嵌套(用括号分组):
|
|
243
|
+
visibilityExpression: "\${(type == \\"A\\" OR type == \\"B\\") AND amount > 500}"
|
|
244
|
+
- 示例:判断字段为空 / 不为空:
|
|
245
|
+
visibilityExpression: "\${attachment !EMPTY}"`}
|
|
246
|
+
- 不再使用旧版 visibilityRules 数组格式
|
|
247
|
+
|
|
248
|
+
- 动态必填条件(任何非 section 字段都可设置):${chineseFieldNames ? `
|
|
249
|
+
- requiredExpression: 字符串,条件表达式,语法与 visibilityExpression 完全相同。当表达式结果为 true 时字段变为必填(与静态 required:true 取 OR 关系)。
|
|
250
|
+
- 示例:当 "has_attachment" 等于 "yes" 时,附件说明字段变为必填:
|
|
251
|
+
requiredExpression: "\${{has_attachment} == \\"yes\\"}"
|
|
252
|
+
- 示例:多条件组合:
|
|
253
|
+
requiredExpression: "\${{合同类型} == \\"采购\\" AND {金额} > 10000}"` : `
|
|
254
|
+
- requiredExpression: 字符串,条件表达式,语法与 visibilityExpression 完全相同。当表达式结果为 true 时字段变为必填(与静态 required:true 取 OR 关系)。
|
|
255
|
+
- 示例:当 "has_attachment" 等于 "yes" 时,附件说明字段变为必填:
|
|
256
|
+
requiredExpression: "\${has_attachment == \\"yes\\"}"
|
|
257
|
+
- 示例:多条件组合:
|
|
258
|
+
requiredExpression: "\${contract_type == \\"procurement\\" AND amount > 10000}"`}
|
|
259
|
+
- 不再使用旧版 requiredRules 数组格式
|
|
260
|
+
- 注意:requiredExpression 和 required 是独立的,required:false 且 requiredExpression 满足 → 必填;required:true 无论 requiredExpression → 始终必填`);
|
|
261
|
+
|
|
262
|
+
// ========== Event scripts ==========
|
|
263
|
+
sections.push(`
|
|
264
|
+
## 事件脚本
|
|
265
|
+
|
|
266
|
+
events 对象包含三个可选的 JavaScript 脚本:
|
|
267
|
+
- onInit: 表单初始化时执行。可用参数:form (表单实例), fields (字段数据), currentStep (当前审批步骤对象)
|
|
268
|
+
- form.setFieldValue(fieldName, value) — 设置字段值
|
|
269
|
+
- form.getFieldValue(fieldName) — 获取字段值
|
|
270
|
+
- form.setFieldOptions(fieldName, options) — 设置下拉选项
|
|
271
|
+
- form.setFieldHidden(fieldName, hidden) — 显隐字段
|
|
272
|
+
- form.setFieldRequired(fieldName, required) — 设为必填
|
|
273
|
+
- form.currentStep — 当前审批步骤对象,包含 name(步骤名)、step_type(步骤类型: start/submit/sign/counterSign/condition)、_id 等属性
|
|
274
|
+
- onValueChange: 字段值变化时执行。可用参数:field (变化的字段), value (新值), oldValue (旧值), form (表单实例), values (所有字段值), currentStep (当前审批步骤对象)
|
|
275
|
+
- onSubmit: 表单提交前执行。可用参数:form (表单实例), values (提交数据), currentStep (当前审批步骤对象)。返回 false 可阻止提交
|
|
276
|
+
|
|
277
|
+
所有脚本支持 async/await。
|
|
278
|
+
⚠️ 脚本内容直接写代码体,参数(form、field、value 等)由运行时自动注入,**禁止用 function(...){} 包裹**。
|
|
279
|
+
正确:"if (field.name === 'x') { ... }"
|
|
280
|
+
错误:"function(field, value, oldValue, form, values) { ... }"
|
|
281
|
+
|
|
282
|
+
示例:
|
|
283
|
+
\`\`\`javascript
|
|
284
|
+
// onValueChange 示例
|
|
285
|
+
if (field.name === 'quantity' || field.name === 'unit_price') {
|
|
286
|
+
var qty = Number(form.getFieldValue('quantity')) || 0;
|
|
287
|
+
var price = Number(form.getFieldValue('unit_price')) || 0;
|
|
288
|
+
form.setFieldValue('total_amount', qty * price);
|
|
289
|
+
}
|
|
290
|
+
\`\`\``);
|
|
291
|
+
|
|
292
|
+
// ========== Rules ==========
|
|
293
|
+
const formulaRule = chineseFieldNames
|
|
294
|
+
? `4. formula 字段的公式表达式使用 \${} 语法,内部引用字段名时必须用 {} 包裹。正确示例:"\${SUM({金额})}"、"\${RMB({概算})}";错误示例:"\${RMB(概算)}"(字段名未用 {} 包裹)、"\${RMB(\${{概算}})}"(不能嵌套 \${})`
|
|
295
|
+
: `4. formula 字段的公式表达式使用 \${} 语法,内部直接引用字段名,如 "\${unit_price * quantity}";公式内部不能再嵌套 \${},错误示例:"\${RMB(\${amount})}",正确示例:"\${RMB(amount)}"`;
|
|
296
|
+
|
|
297
|
+
let rules = `
|
|
298
|
+
## 规则
|
|
299
|
+
1. 🚫【绝对禁止修改已有字段名】修改或升级时,已有字段的 name 必须原样保留,严禁修改、翻译、重命名或转换为英文。旧版字段的 code 就是新版的 name,不得更改。违反此规则将导致流程权限和同步规则全部失效
|
|
300
|
+
2. 新增字段的 name 不允许包含 {} 和空格,支持中文及特殊字符,要语义清晰
|
|
301
|
+
3. section 分组、table 子表、grid 网格始终独占整行,不需要设置 colspan
|
|
302
|
+
${formulaRule}
|
|
303
|
+
5. formula 字段只能使用上方"支持的函数(完整列表)"中列出的 36 个函数,禁止使用任何 Excel 专有函数(NETWORKDAYS、WORKDAY、EDATE、SUMIF、VLOOKUP、IFERROR、EOMONTH 等)或 JavaScript 方法(Math.floor、parseFloat、toString 等),违反此规则会导致运行时报错
|
|
304
|
+
6. 如果用户只要求修改脚本/事件,则 fields 保持原样不变
|
|
305
|
+
7. 如果用户只要求修改字段,则 events 保持原样不变
|
|
306
|
+
8. 返回完整的 fields 数组和 events 对象
|
|
307
|
+
9. **grid 类型字段必须同时返回 gridRows、gridCols、gridData、children 四个属性**,缺少任何一个都会导致网格渲染为空白。gridData 中 cellType 为 "input" 的单元格必须有 fieldId,且该 fieldId 需在 children 数组中有对应的子字段
|
|
308
|
+
10. 修改已有 grid 字段时,必须保留其完整的 gridData 和 children 数据,不要丢弃未被修改的子字段。如果用户未要求改动 grid 内容,应原样返回 grid 的所有属性
|
|
309
|
+
11. 每个字段的类型特有属性必须完整返回:number 类型的 min/max/precision/thousandSeparator、text/textarea 的 maxLength/minLength、lookup 的 reference_to/lookupLabelField/lookupFillRules、member/org 的 pickerFillRules、reference 的 reference_to/displayFields 等。不要遗漏这些属性
|
|
310
|
+
12. ⚠️ **type 与 formula 属性的严格对应**:只要字段包含公式表达式(formula 属性),其 type 就**必须**是 "formula",绝对不能是 "number"、"text" 或其他类型。"number" 类型仅用于用户手工输入数字的字段(无公式)。错误示例:{ type: "number", formula: "\${a + b}" },正确示例:{ type: "formula", formula: "\${a + b}", valueType: "number", precision: 2 }。grid/table 的 children 子字段同样适用此规则`;
|
|
311
|
+
|
|
312
|
+
if (isUpgrade) {
|
|
313
|
+
rules += `
|
|
314
|
+
13. ⚠️ **升级场景禁止自动设置配色**:当用户提供了 instance_template、form_script、flow_events 等旧版数据进行升级转换时,section(分组)、grid(网格)、table(子表)字段**不要自动添加** colorScheme、sectionColor、titleColor 等配色属性。配色属性只有在用户明确要求设置颜色/配色/主题色时才添加。升级的目标是忠实还原旧版表单的结构和逻辑,不要擅自美化
|
|
315
|
+
14. **原样保留字段的 is_wide 属性**:is_wide 用于控制字段是否占满整行宽度。输出时必须保持每个字段的 is_wide 与输入完全一致:输入为 true 则输出 true,输入为 false 则输出 false,输入中没有该属性则不要添加。禁止擅自修改或统一设置 is_wide 的值`;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
sections.push(rules);
|
|
319
|
+
|
|
320
|
+
// ========== Upgrade-only: migration instructions ==========
|
|
321
|
+
if (isUpgrade) {
|
|
322
|
+
sections.push(`
|
|
323
|
+
## 额外待转换的数据(升级场景)
|
|
324
|
+
|
|
325
|
+
新版表单已**不再支持** instance_template(审批单模板)、form_script(表单脚本)、flow_events(流程事件),这些是旧版的遗留功能。当用户提供这些数据时,你需要:
|
|
326
|
+
|
|
327
|
+
1. **分析其中的业务逻辑**,理解它们实现了什么功能
|
|
328
|
+
2. **⭐ 声明式字段配置优先,脚本兜底**。迁移旧版脚本时,按以下优先级选择实现方式——能用字段属性声明式解决的,绝不写事件脚本:
|
|
329
|
+
- 数值范围校验(如金额上限/下限)→ number 字段的 **min / max** 属性(而非 onSubmit 脚本校验)
|
|
330
|
+
- 字段显隐控制 → 字段的 **visibilityExpression**(而非 onValueChange 中 form.setFieldHidden())
|
|
331
|
+
- 动态必填控制 → 字段的 **requiredExpression**(而非 onValueChange 中 form.setFieldRequired())
|
|
332
|
+
- 字段值自动计算 → **formula** 类型字段(而非 onValueChange 中 form.setFieldValue()),仅当公式语法无法表达时才退化到 onValueChange
|
|
333
|
+
- 跨对象属性引用(如旧公式 {applicant.organization.name})→ 成员字段的 **pickerFillRules**,sourceField 使用点号表示法如 "organization.name",targetField 指向目标文本字段
|
|
334
|
+
- 字段选项设置 → 字段的 **options** 属性(静态选项),仅动态/异步加载选项时才用 events.onInit / events.onValueChange 中 form.setFieldOptions()
|
|
335
|
+
- 初始化赋值/选项加载 → 写入 events.onInit
|
|
336
|
+
- 值联动/级联逻辑(无法用 formula 表达时)→ 写入 events.onValueChange
|
|
337
|
+
- 提交前复杂校验(无法用 min/max/required 等表达时)→ 写入 events.onSubmit
|
|
338
|
+
3. **instance_template 仍可用于推断字段 label、colspan(列宽)和 rowspan(行高)**
|
|
339
|
+
4. **不要在输出中返回** instance_template、form_script、flow_events 字段
|
|
340
|
+
|
|
341
|
+
### 审批单模板 (instance_template)
|
|
342
|
+
- HTML 模板,字段引用格式通常为 {{values.字段名}}
|
|
343
|
+
- **布局还原是升级的核心目标**,instance_template 的 HTML 表格是布局的权威来源,新版表单的显示效果必须尽可能精确还原旧版模板的视觉布局
|
|
344
|
+
- 分析模板时,首先统计 HTML 表格展开所有 colspan 后的**实际最大总列数**(包含标签列和数据列),然后选择布局方案:
|
|
345
|
+
- **⭐ 优先使用方案二(grid 网格字段)**。只有当模板表格极其简单(总列数 ≤ 6 且无 rowspan/colspan)时才考虑方案一。如果你不确定该用哪个,请使用方案二。
|
|
346
|
+
|
|
347
|
+
#### 方案一:简单布局(tableColumns)
|
|
348
|
+
适用条件:模板 HTML 表格展开后总列数 ≤ 6(含标签列),每行最多 3 个输入字段,且无 rowspan/colspan 合并单元格。**如果模板中出现任何 rowspan 或 colspan,则不得使用方案一,必须使用方案二。**
|
|
349
|
+
|
|
350
|
+
1. **确定 tableColumns**(每行字段数):
|
|
351
|
+
- 模板中每行有 1 个输入字段 → tableColumns=1
|
|
352
|
+
- 模板中每行有 2 个输入字段 → tableColumns=2
|
|
353
|
+
- 模板中每行有 3 个输入字段 → tableColumns=3(最大值)
|
|
354
|
+
|
|
355
|
+
2. **字段排布**:每个字段默认占 1 列,按顺序自动流入行中
|
|
356
|
+
|
|
357
|
+
3. **分组布局**:模板中的分类标题应转为 type="section" 字段
|
|
358
|
+
|
|
359
|
+
4. **字段 label**:优先使用模板中该字段旁边的显示文本
|
|
360
|
+
|
|
361
|
+
#### 方案二:复杂表格布局(grid 网格字段)⭐ 默认首选方案
|
|
362
|
+
适用条件:模板 HTML 表格展开后总列数 > 6,或者存在**任何 rowspan 或 colspan**,或者有多级表头(主标题下有子标题),或者表格结构无法用 tableColumns(1-3) 简单表达。**绝大多数审批单模板都应使用此方案。**
|
|
363
|
+
|
|
364
|
+
**必须使用 grid 网格字段来精确还原表格布局。禁止将复杂模板用 tableColumns 简化处理。如果模板有任何 rowspan/colspan 合并单元格,就必须使用 grid。**
|
|
365
|
+
|
|
366
|
+
转换步骤:
|
|
367
|
+
1. **分析 HTML 表格结构**:统计表格的总行数和总列数(考虑 colspan 展开后的最大列数),确定 gridRows 和 gridCols
|
|
368
|
+
2. **映射每个 \`<td>\` 到 gridData 单元格**:
|
|
369
|
+
- 纯文本/标题单元格(如 \`<td class="td-title">\`、表头)→ \`{ cellType: "label", value: "显示文本", align: "center" }\`
|
|
370
|
+
- 包含 \`{{values.字段名}}\` 的字段单元格 → \`{ cellType: "input", fieldType: "对应类型", fieldLabel: "标签", fieldId: "唯一ID" }\`
|
|
371
|
+
- HTML 的 \`rowspan\`/\`colspan\` 属性直接映射到 gridData 单元格的 \`rowspan\`/\`colspan\`
|
|
372
|
+
3. **构建 children 数组**:为每个 input 类型的单元格创建对应的子字段,通过 fieldId 关联
|
|
373
|
+
4. **设置 gridColumnWidths**:根据 HTML 表格各列的实际宽度比例设置百分比数组
|
|
374
|
+
5. **grid 字段始终独占整行**,tableColumns 可设为 1
|
|
375
|
+
|
|
376
|
+
转换示例 — 假设模板有一个 5 列表格(标题+4个数据列):
|
|
377
|
+
HTML: \`<tr><td rowspan="3">工程名称</td><td>材料费</td><td>{{values.clf_bq}}</td><td>{{values.clf_lj}}</td><td>{{values.clf_bz}}</td></tr>\`
|
|
378
|
+
转换为 grid:
|
|
379
|
+
\`\`\`json
|
|
380
|
+
{
|
|
381
|
+
"type": "grid", "label": "费用明细", "name": "cost_grid",
|
|
382
|
+
"gridRows": 10, "gridCols": 5,
|
|
383
|
+
"gridColumnWidths": [15, 15, 25, 25, 20],
|
|
384
|
+
"gridData": [
|
|
385
|
+
{ "row": 0, "col": 0, "cellType": "label", "value": "工程名称", "rowspan": 3, "align": "center" },
|
|
386
|
+
{ "row": 0, "col": 1, "cellType": "label", "value": "材料费", "align": "center" },
|
|
387
|
+
{ "row": 0, "col": 2, "cellType": "input", "fieldType": "number", "fieldLabel": "本期材料费", "fieldId": "f_clf_bq" },
|
|
388
|
+
{ "row": 0, "col": 3, "cellType": "input", "fieldType": "number", "fieldLabel": "累计材料费", "fieldId": "f_clf_lj" },
|
|
389
|
+
{ "row": 0, "col": 4, "cellType": "input", "fieldType": "text", "fieldLabel": "材料费备注", "fieldId": "f_clf_bz" }
|
|
390
|
+
],
|
|
391
|
+
"children": [
|
|
392
|
+
{ "_id": "f_clf_bq", "name": "clf_bq", "label": "本期材料费", "type": "number" },
|
|
393
|
+
{ "_id": "f_clf_lj", "name": "clf_lj", "label": "累计材料费", "type": "number" },
|
|
394
|
+
{ "_id": "f_clf_bz", "name": "clf_bz", "label": "材料费备注", "type": "text" }
|
|
395
|
+
]
|
|
396
|
+
}
|
|
397
|
+
\`\`\`
|
|
398
|
+
|
|
399
|
+
**重要**:grid 网格中的 label 单元格用于还原 HTML 表格中的标题/分类文字(如"工程费用""其他费用""小计""合计"等),这些文字不需要创建独立字段,直接作为 cellType:"label" 的静态文本即可。只有包含 {{values.xxx}} 的单元格才需要创建为 cellType:"input" 并在 children 中添加对应字段。
|
|
400
|
+
|
|
401
|
+
**⚠️ 列数精确匹配**:gridCols 必须等于 HTML 表格展开所有 colspan 后的实际最大列数。禁止合并、删减或精简列。如果模板表头"预算"下方有"合同预算"和"调整预算"两个子列,则必须保留为 2 个独立的 grid 列,不能合并成 1 列。同理,"决算"下方有子列也必须全部保留。遇到多级表头(主标题跨多列、下方有子标题),需正确设置主标题的 colspan 来覆盖子列。
|
|
402
|
+
|
|
403
|
+
**⚠️ 单元格完整覆盖**:gridData 中每一行的每个列位置(row=0..gridRows-1, col=0..gridCols-1)必须被一个单元格"覆盖"——要么该位置有一个 gridData 条目,要么它被另一个单元格的 rowspan/colspan 所覆盖。不允许出现"空洞"(某个位置既没有 gridData 条目也没有被合并覆盖)。
|
|
404
|
+
- 如果某行的某个单元格位于一个多列表头下方但只需要 1 列内容,应设置 colspan 使其跨满父表头的列范围。例如:表头"项目分类"占 col 2-3(colspan=2),则数据行中如果只有一个名称"设备购置"在 col 2,应给它 colspan=2 让它跨到 col 3,而不是留 col 3 为空。
|
|
405
|
+
- 公式:每行所有单元格的 (colspan值之和) + (被其他行的 rowspan 覆盖的列数) = gridCols
|
|
406
|
+
- 模板中的业务逻辑(如条件显隐的 JS 代码)需迁移到 visibilityExpression 或 events 中
|
|
407
|
+
|
|
408
|
+
### 表单脚本 (form_script)
|
|
409
|
+
- 旧版表单使用的 JavaScript 脚本,引用字段名的方式包括:doc.字段名、values.字段名 等
|
|
410
|
+
- 分析脚本中的逻辑,将其迁移到新版的 events(onInit / onValueChange / onSubmit)中
|
|
411
|
+
- 新版事件脚本使用 form.getFieldValue() / form.setFieldValue() / form.setFieldOptions() / form.setFieldHidden() / form.setFieldRequired() 等 API
|
|
412
|
+
|
|
413
|
+
### 流程事件 (flow_events)
|
|
414
|
+
- 旧版流程级别的事件脚本,通常绑定在 jQuery 事件上(如 \`instance-before-submit\`、\`instance-before-save\` 等)
|
|
415
|
+
- 分析其中的业务逻辑,**优先用字段声明式配置实现,无法声明式表达时才写入 events 脚本**
|
|
416
|
+
- 常见迁移模式:
|
|
417
|
+
- \`instance-before-submit\` 中的数值范围校验(如 \`money >= 5000000\` 则阻止提交)→ **直接设置 number 字段的 max 属性**,如 \`"max": 4999999.99\`,无需写 onSubmit 脚本
|
|
418
|
+
- \`instance-before-submit\` 中的必填校验 → 设置字段 \`required: true\` 或 \`requiredExpression\`
|
|
419
|
+
- \`instance-before-submit\` 中的复杂跨字段校验(如"结束日期必须大于开始日期"且无法用单字段属性表达)→ 写入 events.onSubmit,用 \`form.getFieldValue()\` 获取值,返回 \`false\` 阻止提交
|
|
420
|
+
- \`instance-before-save\` 中的自动赋值 → 写入 events.onInit 或 events.onValueChange
|
|
421
|
+
- \`WorkflowManager.getInstance().state\` 等实例状态判断 → 新版 onSubmit 仅在提交时触发,草稿保存不触发,通常可省略状态判断
|
|
422
|
+
- \`e.preventDefault()\` + \`toastr.error(msg)\` → 新版用 \`form.showError(msg); return false;\` 替代
|
|
423
|
+
- 注意:旧版脚本是 JavaScript 语法,其中被注释掉的代码(// 单行注释 或 /* */ 块注释)属于废弃逻辑,不需要识别和迁移,直接忽略即可
|
|
424
|
+
- 迁移示例:
|
|
425
|
+
旧版 flow_events:
|
|
426
|
+
\`\`\`javascript
|
|
427
|
+
var money = parseFloat(formValues['申请增加额度']);
|
|
428
|
+
if (money >= 5000000) { e.preventDefault(); toastr.error('金额不能超过500万'); }
|
|
429
|
+
\`\`\`
|
|
430
|
+
新版迁移方式:**不写脚本**,直接在字段上配置:
|
|
431
|
+
\`\`\`json
|
|
432
|
+
{ "name": "apply_increase_amount", "type": "number", "max": 4999999.99, "precision": 2 }
|
|
433
|
+
\`\`\`
|
|
434
|
+
|
|
435
|
+
### 旧版自动编号字段迁移
|
|
436
|
+
- 旧版表单中的自动编号字段通过 default_value 属性实现,格式为 \`default_value = "auto_number('规则名称')"\`
|
|
437
|
+
- 新版必须将此类字段转换为 \`type: "autoNumber"\` 类型
|
|
438
|
+
- 迁移规则:
|
|
439
|
+
- 遇到字段的 default_value 包含 \`auto_number(...)\` 时,将该字段的 type 改为 "autoNumber",设置 readonly: true
|
|
440
|
+
- 删除原有的 default_value 属性
|
|
441
|
+
- 如果旧版编号规则名称中能推断出前缀、日期格式等信息,相应设置 autoNumberPrefix、autoNumberDateFormat 等属性;否则使用默认值
|
|
442
|
+
- 默认配置:autoNumberPrefix: ""、autoNumberPadding: 4、autoNumberDateFormat: "YYYYMM"、autoNumberResetCycle: "monthly"
|
|
443
|
+
- 迁移示例:
|
|
444
|
+
旧版字段:{ "name": "合同编号", "type": "text", "default_value": "auto_number('contract_no')" }
|
|
445
|
+
新版字段:{ "name": "contract_no", "label": "合同编号", "type": "autoNumber", "readonly": true, "autoNumberPrefix": "", "autoNumberPadding": 4, "autoNumberDateFormat": "YYYYMM", "autoNumberResetCycle": "monthly" }
|
|
446
|
+
- migrationLog 记录格式:"字段 <name>: default_value auto_number(...) 转换为 autoNumber 类型"
|
|
447
|
+
|
|
448
|
+
### 旧版 HTML 字段类型迁移
|
|
449
|
+
- 旧版表单中存在 type 为 "html" 的字段,这是旧版的 HTML 编辑器字段,允许用户输入和编辑 HTML 格式的富文本内容
|
|
450
|
+
- 新版表单中 **不再支持 "html" 类型**,已被 **"richtext"(富文本)** 类型完全替代
|
|
451
|
+
- **迁移规则**:当遇到 type 为 "html" 的字段时,必须将其 type 改为 "richtext",其余属性(name、label、required、colspan、defaultValue 等)保持不变
|
|
452
|
+
- richtext 类型支持格式化文本、颜色、链接等富文本编辑功能,功能上完全覆盖旧版 html 类型
|
|
453
|
+
- 迁移示例:
|
|
454
|
+
旧版字段:{ "name": "content", "label": "内容", "type": "html" }
|
|
455
|
+
新版字段:{ "name": "content", "label": "内容", "type": "richtext" }
|
|
456
|
+
- migrationLog 记录格式:"字段 <name>: type 从 html 转换为 richtext"
|
|
457
|
+
|
|
458
|
+
### 旧公式字段跨对象引用迁移
|
|
459
|
+
- 旧版表单中,公式字段可能通过 \`{applicant.organization.name}\`、\`{applicant.name}\` 等语法引用成员的关联属性。这种跨对象引用在新版中不再通过公式实现,而是通过**成员字段的 pickerFillRules(填充规则)**来替代。
|
|
460
|
+
- ⚠️ **重要:applicant(申请人)是系统内置字段**,name 固定为 \`__applicant\`,不要创建新的 member 字段。在 fields 数组中输出 { "name": "__applicant", "label": "申请人", "type": "member", "pickerFillRules": [...] } 即可覆盖系统默认配置。该字段在拟稿状态下可编辑(用户可更换申请人),pickerFillRules 会在选人后自动触发填充。
|
|
461
|
+
- 迁移方式:
|
|
462
|
+
1. 将引用跨对象属性的公式字段改为**普通文本字段**(type: "text")作为目标字段
|
|
463
|
+
2. 在 \`__applicant\` 字段上配置 **pickerFillRules**,使用点号表示法的 sourceField(如 "organization.name"),targetField 指向该文本字段
|
|
464
|
+
3. 如果 targetField 字段应为只读展示(即旧版是纯展示的公式字段),设置 readonly: true
|
|
465
|
+
- 迁移示例:
|
|
466
|
+
旧版公式字段:\`{applicant.organization.name}\` 用于显示申请人所属部门名称
|
|
467
|
+
新版迁移方式:
|
|
468
|
+
1. 创建文本字段 { "name": "dept_name", "label": "所属部门", "type": "text", "readonly": true }
|
|
469
|
+
2. 输出 __applicant 字段:{ "name": "__applicant", "label": "申请人", "type": "member", "pickerFillRules": [{ "sourceField": "organization.name", "targetField": "dept_name" }] }
|
|
470
|
+
- 常见映射:
|
|
471
|
+
- \`{applicant.organization.name}\` → sourceField: "organization.name"
|
|
472
|
+
- \`{applicant.organization.fullname}\` → sourceField: "organization.fullname"
|
|
473
|
+
- \`{applicant.name}\` → sourceField: "name"
|
|
474
|
+
- \`{applicant.mobile}\` → sourceField: "mobile"`);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// ========== Output format ==========
|
|
478
|
+
sections.push(`
|
|
479
|
+
## 输出格式
|
|
480
|
+
返回一个 JSON 对象,格式如下(不要有任何其他文字、解释、markdown 标记):
|
|
481
|
+
{
|
|
482
|
+
"tableColumns": 数字,每行显示的字段数,默认 2,最大值为 3。根据表单布局需要调整,常见值为 1、2、3,
|
|
483
|
+
"fields": [ ... ],
|
|
484
|
+
"events": {
|
|
485
|
+
"onInit": "脚本代码或空字符串",
|
|
486
|
+
"onValueChange": "脚本代码或空字符串",
|
|
487
|
+
"onSubmit": "脚本代码或空字符串"
|
|
488
|
+
}${isUpgrade ? `,
|
|
489
|
+
"migrationLog": ["迁移记录"]` : ''}
|
|
490
|
+
}`);
|
|
491
|
+
|
|
492
|
+
// ========== migrationLog section (upgrade only) ==========
|
|
493
|
+
if (isUpgrade) {
|
|
494
|
+
sections.push(`
|
|
495
|
+
### migrationLog(迁移记录)
|
|
496
|
+
当用户提供了 flow_events、form_script、instance_template 中包含业务逻辑(如校验、赋值、显隐控制等脚本代码)时,必须在 migrationLog 数组中为每条被迁移的逻辑记录一条说明,格式:
|
|
497
|
+
"<来源>: <原逻辑简述> → <迁移到的具体位置>"
|
|
498
|
+
|
|
499
|
+
迁移目标可以是以下任意一种:
|
|
500
|
+
- 字段属性:如 "字段 apply_increase_amount 的 max:4999999.99"
|
|
501
|
+
- 字段规则:如 "字段 remark 的 visibilityExpression"、"字段 attachment_desc 的 requiredExpression"
|
|
502
|
+
- formula 字段:如 "新增 formula 字段 total_amount"
|
|
503
|
+
- 事件脚本:如 "events.onSubmit"、"events.onValueChange"
|
|
504
|
+
- 已省略(含原因):如 "已省略(旧版 state=='draft' 判断,新版 onSubmit 仅提交时触发,无需判断)"
|
|
505
|
+
|
|
506
|
+
示例:
|
|
507
|
+
"migrationLog": [
|
|
508
|
+
"flow_events: 申请增加额度>=500万阻止提交 → 字段 apply_increase_amount 的 max:4999999.99",
|
|
509
|
+
"flow_events: ins.state!='draft' 状态判断 → 已省略(新版 onSubmit 仅提交时触发)",
|
|
510
|
+
"form_script: 根据类型字段显隐备注 → 字段 remark 的 visibilityExpression",
|
|
511
|
+
"form_script: 选择部门后级联加载人员选项 → events.onValueChange",
|
|
512
|
+
"instance_template: {applicant.organization.name} 跨对象引用 → 成员字段 applicant 的 pickerFillRules (organization.name → dept_name)"
|
|
513
|
+
]
|
|
514
|
+
|
|
515
|
+
⚠️ 如果 flow_events 或 form_script 不为空但 migrationLog 为空数组,视为错误。每条旧版逻辑都必须有对应的迁移记录,不允许静默丢弃。
|
|
516
|
+
如果没有提供 flow_events、form_script,或它们为空,则 migrationLog 可省略或为空数组。`);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return sections.join('\n');
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
module.exports = { buildFormDesignSystemPrompt };
|
|
@@ -280,7 +280,7 @@
|
|
|
280
280
|
* 保持同步。
|
|
281
281
|
*/
|
|
282
282
|
const _getSafeCode = (code) => {
|
|
283
|
-
return code.replace(/(/g, '_').replace(/)/g, '').replace(/\(/g, '_').replace(/\)/g, '').replace(/、/g, '_').replace(/,/g, '_').replace(/%/g, '_').replace(/=/g, '_').replace(/:/g, '_').replace(/\//g, '_').replace(/-/g, '_');
|
|
283
|
+
return code.replace(/(/g, '_').replace(/)/g, '').replace(/\(/g, '_').replace(/\)/g, '').replace(/、/g, '_').replace(/,/g, '_').replace(/%/g, '_').replace(/=/g, '_').replace(/:/g, '_').replace(/\//g, '_').replace(/-/g, '_').replace(/\+/g, '_').replace(/</g, '_').replace(/>/g, '_').replace(/\s+/g, '_').replace(/\[/g, '_').replace(/\]/g, '_').replace(/\./g, '_').replace(/_{2,}/g, '_').replace(/_$/, '');
|
|
284
284
|
};
|
|
285
285
|
|
|
286
286
|
/**
|
|
@@ -296,7 +296,8 @@ async function updateForm(formId, form, forms, flows, currentUserId) {
|
|
|
296
296
|
'help_text': form["help_text"],
|
|
297
297
|
'error_message': form["error_message"],
|
|
298
298
|
'category': form["category"],
|
|
299
|
-
'instance_style': form["instance_style"]
|
|
299
|
+
'instance_style': form["instance_style"],
|
|
300
|
+
'chineseFieldNames': form["chineseFieldNames"] || false
|
|
300
301
|
}
|
|
301
302
|
|
|
302
303
|
await formCollection.updateOne({_id: formId}, formUpdateObj);
|
|
@@ -13,7 +13,16 @@ const getSafeCode = (code) => {
|
|
|
13
13
|
.replace(/=/g, '_')
|
|
14
14
|
.replace(/:/g, '_')
|
|
15
15
|
.replace(/\//g, '_')
|
|
16
|
-
.replace(/-/g, '_')
|
|
16
|
+
.replace(/-/g, '_')
|
|
17
|
+
.replace(/\+/g, '_')
|
|
18
|
+
.replace(/</g, '_')
|
|
19
|
+
.replace(/>/g, '_')
|
|
20
|
+
.replace(/\s+/g, '_')
|
|
21
|
+
.replace(/\[/g, '_')
|
|
22
|
+
.replace(/\]/g, '_')
|
|
23
|
+
.replace(/\./g, '_')
|
|
24
|
+
.replace(/_{2,}/g, '_')
|
|
25
|
+
.replace(/_$/, '');
|
|
17
26
|
};
|
|
18
27
|
|
|
19
28
|
const isContextVariable = (code) => {
|