表单数据填充、联动与动态选项¶
本章覆盖表单打开、刷新、保存提交时最常见的"把数据填进去"类需求:字段默认值、流水编号、字段联动、字段清空、下拉动态选项、调查问卷、表单水印。所有脚本均为 GraalVM JS 写法——动手前建议先扫一遍 GraalVM 差异。
场景清单¶
| 场景 | 落点 / Label | 主要素材 |
|---|---|---|
| 用当前用户填充字段默认值 | 值脚本 FORM:FIELD_VALUE |
form-controls |
| 保存/提交/暂存生成流水编号 | 值脚本 / 操作前置 | save-submit-temp-count |
| 根据他字段或查询结果联动重算 | 值脚本 / 计算脚本 | update-document-field、use-calc-script-form |
| 清空/重置字段(含子表) | 值脚本 | page-value-script-change |
| 下拉动态取值(SQL/组织架构) | 选项脚本 FORM:FIELD_OPTION |
form-controls |
| 选项返回格式速查(Options / 串 / JSON) | 选项脚本 | dropdown-json-format |
| 调查问卷动态构造 | 问卷脚本 FORM:FIELD_QUESTION |
form-controls |
| 表单水印 | 水印脚本 FORM:WATERMARK |
form-basic-properties |
用当前用户填充字段默认值¶
业务目标:新建报销单/申请单时,自动把"报销人/申请人"等字段填成当前登录用户的姓名,省去手填、避免填错。
写在哪儿:表单域 → 表单 → 表单控件 → 值脚本(.form,属性 valueScript / JsonTemplate valuescript,常配 editmode=01 或 calculateonrefresh;Label FORM:FIELD_VALUE)。挂载点位置见 where-to-use/form/form-controls。
触发时机与上下文:表单打开(新建或查看)以及刷新计算时触发。可用的环境变量:
- WebUser —— 即 getWebUser() 返回的当前登录用户对象(→ curruser API)
- CurrentDocument —— 即 getCurrentDocument() 返回的当前文档对象(→ doc API)
- RelateDocument / ParentDocument —— 关联文档/父文档对象(包含元素场景)
返回值契约:String(或其他与控件匹配的类型),赋给当前字段作为显示/存值。
示例代码:
// 用当前用户名填充控件默认值
(function () {
var applicant = getItemValueAsString("报销人");
// 仅在字段为空时兜底,避免覆盖用户已填值或在编辑时被重置
if (applicant == null || applicant === "") {
applicant = getWebUser().getName();
}
return applicant;
})()
代码说明:
- getItemValueAsString("字段名") —— 按字段名取当前文档某字段的字符串值(→ doc-functions)。
- getWebUser() —— 取当前登录用户对象;.getName() 取其姓名(→ curruser API)。
- getCurrentDocument() —— 当需要在值脚本里读其他字段时使用(→ curdoc-functions)。
变体与扩展:
- 填充部门/角色:用 getWebUser().getDepartment() 或遍历 getWebUser().getRoles()(Java Collection,用 .iterator() 遍历,.size() 取长度)。
- 首节点才填:结合 getCurrentDocument().isFirstNode()(→ curdoc-functions),仅首节点新建时填默认值,后续审批节点保留已存值。
- 格式化日期默认值:搭配 format(new Date(), "yyyy-MM-dd")(→ date-functions)填"今天"。
常见坑:
- 必须判空兜底:值脚本在每次刷新都会执行,不判空就会把用户已填的值覆盖掉。
- 字符串比较用 ===:不要写 applicant.equals(""),GraalVM 下会报错或始终 false,详见 GraalVM 差异。
- getWebUser() 可能为空:在定时任务、API public 调用等无用户上下文的场景里 getWebUser() 会返回 null——本场景是表单打开触发,不会有此问题,但若复用代码到其它脚本要重新审视。
保存/提交/暂存生成流水编号¶
业务目标:保存或提交时自动生成形如 DF20260302-0004 的流水编号,同一文档只生成一次、再次保存不重置。
写在哪儿:有两种写法,按业务选其一或组合使用:
- 字段值脚本(.form,属性 valueScript,Label FORM:FIELD_VALUE)—— 显示即编号,常配 calculateonrefresh;
- 操作前置脚本(.form 工具栏/操作按钮,Label 同 FORM:ACTIVITY 语义,属性 beforeactionscript)—— 在保存/暂存/提交动作触发时生成并写回字段。
触发时机与上下文:
- 值脚本:表单打开或刷新计算时。
- 操作前置:点击保存/暂存/提交按钮时。可用 getCurrentDocument()(→ doc API)、getWebUser()。
返回值契约:
- 值脚本:String —— 编号文本。
- 操作前置:通常无返回值(副作用:findItem().setValue(...) 后由平台持久化);若返回非空串可能阻断动作,慎用。
示例代码:
值脚本——优先复用已有编号,仅在为空时生成:
(function () {
var v = getItemValueAsString("流水号");
if (v != null && v.trim().length > 0) {
return v; // 已有编号,直接返回
}
// countNext2: 前缀 DF + 年月日 + 4 位流水,平台自动维护计数器
return countNext2("DF", true, true, true, 4);
})()
操作前置脚本——保存前判断该文档是否已存在编号,不存在则生成并写回:
(function () {
var doc = getCurrentDocument();
var docid = doc.getId();
// 检查文档是否已落库(避免编辑保存时再次覆盖编号)
var sql = "select domainid from tlk_合同 where id = '" + docid + "'";
if (countBySQL(sql) <= 0) {
var dNum = countNext2("JM", true, false, false, 3);
doc.findItem("合同编号").setValue(dNum);
}
})()
代码说明:
- countNext2(headText, isYear, isMonth, isDay, digit) —— 平台计数器,生成带前缀和日期段的唯一编号(→ counter API)。
- countBySQL(sql) / findBySQL(sql) —— 单值计数 / 单文档查询(→ curdb API)。
- doc.findItem("字段名").setValue(value) —— 修改当前文档字段(→ curdoc-functions)。
变体与扩展:
- 自定义计数表:自己维护 t_count_ver,按 findBySQL 取计数后补零拼接(见素材 common-script-collection/save-submit-temp-count.md)。
- 加上部门/项目前缀:取 getWebUser().getDefaultDepartment() 后再拼。
- 回写到流程标题:操作后置中调用 doc.findItem("subject").setValue(...) 同步标题。
常见坑:
- 必须在已存值时早返回:值脚本不判空会无限递增编号。
- SQL 注意表前缀和域名:平台表为 tlk_<表单名>,业务字段列为 item_<字段名>;过滤常需带 domainid。
- .size() vs .length:countBySQL 返回数字;若你换用 queryBySQL 返回的是 Java Collection,长度用 .size(),不要写成 .length(→ GraalVM 差异)。
根据他字段或查询结果联动重算¶
业务目标:当某字段变化(如"合同金额")或查询结果变化时,自动重算"已开票金额"、"剩余金额"、"合计"等派生字段,无需用户手算。
写在哪儿:
- 值脚本(.form,属性 valueScript,Label FORM:FIELD_VALUE)—— 派生字段自身的值脚本;
- 计算脚本(.form,属性同 valuescript,常用于"计算文本"等不落库控件,可返回 HTML)—— 适合做主子表汇总、关联数据表格展示;
- 操作前置/后置(.form 工具栏 .activity,属性 beforeactionscript / afteractionscript)—— 适合保存前批量重算并写回多个字段。
触发时机与上下文:表单打开、刷新计算(依赖字段变化触发);操作前后置在动作执行时。可用 getCurrentDocument()、getWebUser()、queryBySQL(sql) / queryByDSName(dsName, sql)(→ database API)。
返回值契约:
- 值脚本:派生字段的计算结果(String / Number)。
- 计算脚本(HTML 类型):可返回 HTML 字符串直接渲染。
- 操作前后置:通常无返回值,副作用写回字段。
示例代码:
值脚本——按"合同金额 - 已开票金额"算"剩余金额":
(function () {
var total = getItemValueAsDouble("合同金额");
var invoiced = getItemValueAsDouble("已开票金额");
if (Number.isNaN(total)) { total = 0; }
if (Number.isNaN(invoiced)) { invoiced = 0; }
return total - invoiced;
})()
操作前置——按当前文档"签进合同ID"查询关联项目并重算主表字段:
(function () {
var doc = getCurrentDocument();
var projectId = doc.getItemValueAsString("签进合同ID");
if (projectId == null || projectId.trim().length === 0) {
return; // 关键字段为空,跳过
}
var sql = "select sum(合同金额_bak) as item_总合同, sum(到账金额_bak) as item_总到账 "
+ " from tlk_项目 where item_项目编号 in "
+ " (select distinct item_项目编号 from tlk_签进合同明细 "
+ " where parent = '" + projectId + "' and item_项目编号 is not null)";
var row = findBySQL(sql);
if (row != null) {
doc.findItem("合同总额").setValue(row.getItemValueAsDouble("总合同"));
doc.findItem("到账总额").setValue(row.getItemValueAsDouble("总到账"));
}
})()
计算脚本——返回 HTML 表格展示主子表关联(控件类型设为 HTML):
(function () {
var doc = getCurrentDocument();
var sql = "select domainid, item_名称, item_金额 from tlk_明细 "
+ " where parent = '" + doc.getId() + "'";
var query = queryBySQL(sql);
var html = "<table border='1'><tr><th>名称</th><th>金额</th></tr>";
if (query != null) {
for (var it = query.iterator(); it.hasNext();) {
var d = it.next();
html += "<tr><td>" + d.getItemValueAsString("名称") + "</td>"
+ "<td>" + d.getItemValueAsDouble("金额") + "</td></tr>";
}
}
html += "</table>";
return html;
})()
代码说明:
- getItemValueAsDouble("字段名") —— 取数值型字段值(→ doc-functions)。
- findBySQL(sql) / queryBySQL(sql) —— 单文档 / 集合查询(→ curdb API);跨数据源用 queryByDSName(dsName, sql)(→ database API)。
- doc.findItem("字段名").setValue(value) —— 重算后写回(→ curdoc-functions)。
- 计算脚本可 #include "数据字典"; 引入预置扩展函数(详见附录 A:工具函数与通用技巧 附录 A 工具函数与通用技巧,文件 appendix-toolbox.md)。
变体与扩展:
- 多字段批量重算 + 落库:操作后置里 getDocumentProcess().doUpdate(doc) 主动持久化。
- 跨表单关联:用 queryByDSName 跨数据源取数。
- 公式化重算:用 format() 格式化日期/金额显示。
常见坑:
- 数值字段空值:getItemValueAsDouble 在字段为空时可能返回 NaN,必须先判 Number.isNaN 再参与运算。
- 遍历 Java 集合用 .iterator():queryBySQL 返回 java.util.Collection,不要用 for...of 当 JS 数组遍历,也不要写 query.length(→ GraalVM 差异)。
- SQL 注入:拼接用户输入字段时先做转义(replace(/'/g, "''"))。
- 值脚本写其他字段:值脚本里调 findItem().setValue 写别的字段在某些版本不会立即落库——需要写库时改用操作前置/后置。
清空/重置字段(含子表)¶
业务目标:根据某条件清空一组字段(如切换类型时清掉旧明细),或重置整个子表的某列。
写在哪儿:值脚本(.form,属性 valueScript,Label FORM:FIELD_VALUE);也可放操作前置(.form 工具栏,属性 beforeactionscript)做"重置按钮"。
触发时机与上下文:表单打开、刷新计算时;操作前置在按钮点击时。可用 getCurrentDocument()(→ doc API)、getWebUser()。
返回值契约:通常无返回值(纯副作用,调 findItem().setValue("") 清空)。
示例代码:
按条件清空主表字段:
(function () {
var doc = getCurrentDocument();
var t = doc.getItemValueAsString("类型");
if (t === "重置") {
doc.findItem("值1").setValue("");
doc.findItem("值2").setValue("");
doc.findItem("值3").setValue("");
}
})()
清空子表(包含元素)某列所有行:
(function () {
var doc = getCurrentDocument();
var subTable = doc.getSubTable("子表名");
if (subTable != null && subTable.size() > 0) {
for (var i = 0; i < subTable.size(); i++) {
var row = subTable.get(i);
row.findItem("子表字段").setValue("");
}
}
})()
代码说明:
- doc.findItem("字段名").setValue("") —— 清空字段(→ curdoc-functions)。
- doc.getSubTable("子表名") —— 取包含元素对应的子文档列表(Java List,长度 .size(),取元素 .get(i))。
变体与扩展:
- 清空并赋默认值:setValue("") 后接 setValue("默认值")。
- 重置按钮:挂到工具栏按钮的执行前脚本,弹 createConfirm("确认重置?") 确认后再清空。
- 删除整行子表:用 getDocumentProcess() 相关 API,不要直接拿 setValue 删行。
常见坑:
- 子表是 Java List 不是 JS 数组:长度写 subTable.size(),不要写 subTable.length;遍历用下标 for 或 .iterator()(→ GraalVM 差异)。
- 空对象判空:getSubTable 可能返回 null,必须判空再 .size()。
- 清空不立即落库:副作用脚本只在内存改字段,需触发保存/提交才会落库——若在操作前置清空后取消动作,UI 已清空但库未变。
下拉动态取值(SQL/组织架构)¶
业务目标:下拉框(Select/Radio/Checkbox/Suggest/SelectAbout)选项不写死,从数据库表、组织架构、角色用户等动态取。
写在哪儿:表单域 → 表单 → 选项类控件 → 选项脚本(.form,属性 JsonTemplate optionsscript,常配 optionseditmode=01;Label FORM:FIELD_OPTION)。挂载点位置见 where-to-use/form/form-controls。
触发时机与上下文:表单打开时。可用 WebUser、CurrentDocument、RelateDocument、ParentDocument。
返回值契约:以下三种形式平台都接受(详见下一节 选项返回格式速查):
- Options 对象(createOptions() 返回,调 .add(name, value) 添加);
- 字符串 "值:文本;值:文本";
- 字符串 "文本;文本"(值与文本相同)。
示例代码:
从数据源 MY_DS 的 t_user 表取用户作选项:
(function () {
var opts = createOptions();
opts.add("", ""); // 空白行,避免强制选第一项
var sql = "select id, name from t_user";
var datas = queryByDSName("MY_DS", sql);
if (datas != null) {
for (var it = datas.iterator(); it.hasNext();) {
var row = it.next();
opts.add(row.get("name"), row.get("id")); // add(name, value)
}
}
return opts;
})()
按角色取用户作选项(组织架构):
(function () {
var opts = createOptions();
var roleId = "<角色ID>";
var users = getUsersByRoleId(roleId); // 返回 java.util.Collection
if (users != null) {
for (var it = users.iterator(); it.hasNext();) {
var u = it.next();
opts.add(u.getName(), u.getId());
}
}
return opts;
})()
代码说明:
- createOptions() —— 平台选项对象工厂(→ java-class-functions)。
- opts.add(name, value) —— 注意参数顺序是"显示文本, 值"。
- queryByDSName(dsName, sql) —— 跨数据源查询(→ database API);同源也可用 queryBySQL(sql)(→ curdb API)。
- getUsersByRoleId(roleId) —— 按角色取用户集合(→ usercenter API)。
变体与扩展:
- 级联下拉:根据上游字段 getItemValueAsString("省份") 动态拼 SQL 取下游选项。
- 当前文档字段拆分得到选项:把 "A;B;C" 拆成选项——JS 数组用 .length 和 arr[i]。
- 返回字符串简化:选项少且固定时直接 return "01:待审;02:已审"。
常见坑:
- add(name, value) 参数顺序:第一个是显示文本、第二个是值,传反则存库值变成显示文本。
- 遍历 Java Collection:用 .iterator() + .hasNext() + .next(),不要当 JS 数组写 datas[i] 或 datas.length(→ GraalVM 差异)。
- 空选项:不先 opts.add("", "") 时下拉默认选中第一项,可能不是用户想要的。
- SQL 数据源名大小写:queryByDSName 第一个参数必须与平台数据源配置名完全一致。
选项返回格式速查(Options / 串 / JSON)¶
本场景与 第 5 节 共用
FORM:FIELD_OPTION落点(属性同构),仅"返回值形态"不同,故合并速查。素材:common-script-collection/dropdown-json-format。
Options 对象除 .add() 外还提供 JSON 序列化方法;某些前端控件(如 Suggest 自动补全)只吃 JSON。三种返回形态对照:
| 形态 | 写法 | 适用控件 |
|---|---|---|
| Options 对象 | var opts = createOptions(); opts.add(name, value); return opts; |
Select / Radio / Checkbox(默认推荐) |
| 分号串 | return "01:待审;02:已审"; 或 "待审;已审" |
Select / Radio / Checkbox(选项少时简写) |
JSON 对象 toJSON() |
return opts.toJSON(); → {'A':'A','B':'B'} |
需要键值对的前端组件 |
JSON 数组 toJsonSuggest() |
return opts.toJsonSuggest(); → [{"name":"A","id":"A"},...] |
Suggest / 自动补全类 |
示例——把选项序列化成 JSON 数组返回:
(function () {
var opts = createOptions();
opts.add("A", "A");
opts.add("B", "B");
opts.add("C", "C");
// 数组格式:[{"name":"A","id":"A"}, ...]
return opts.toJsonSuggest();
})()
说明:
- add(id, name) 与 add(name, value) 在不同示例中参数顺序描述曾出现差异;以 java-class-functions 的 createOptions() 文档为准,使用前用 println 打印一次验证。
- toJSON() 返回对象格式 {'值':'文本', ...};toJsonSuggest() 返回数组格式 [{name, id}, ...]。
- 常见坑:return 时不要漏——选项脚本必须有返回值;空白行同样要 opts.add("", "") 显式添加;JSON 字段顺序按添加顺序,业务上要排序请先排好再 add。字符串拼接/SQL 拼接注意转义;遍历 Java 集合用 .iterator()(→ GraalVM 差异)。
调查问卷动态构造¶
业务目标:用调查控件(问卷)动态生成题目与选项,比如根据业务类型显示不同的"满意度"问题。
写在哪儿:表单域 → 表单 → 调查控件 → 问卷脚本(.form,属性 questionscript;Label FORM:FIELD_QUESTION)。挂载点位置见 where-to-use/form/form-controls。
触发时机与上下文:表单打开时。可用 WebUser、CurrentDocument、RelateDocument、ParentDocument。
返回值契约:Question 对象的 List(Java ArrayList),平台按列表渲染问卷。
示例代码:
(function () {
var q = createQuestion(1, "你的爱好(多选)");
q.addCheckboxOption("羽毛球", "羽毛球");
q.addCheckboxOption("游泳", "游泳");
q.addCheckboxOption("阅读", "阅读");
// 新建 Java ArrayList 必须用 Java.type,不要裸写 java.util.ArrayList
var ArrayList = Java.type('java.util.ArrayList');
var qs = new ArrayList();
qs.add(q);
return qs;
})()
代码说明:
- createQuestion(type, title) —— 平台问卷对象工厂(type 通常 1=多选;具体枚举见产品约定)。createQuestion / createOptions 同属工具类工厂,参考 java-class-functions。
- q.addCheckboxOption(name, value) —— 给问题添加复选选项。
- Java.type('java.util.ArrayList') —— GraalVM 推荐 Java 类引用方式(→ GraalVM 差异)。
变体与扩展:
- 动态题库:用 queryBySQL 从题库表取题目后循环 createQuestion + addCheckboxOption。
- 题型扩展:单选、文本问答等用对应的 addXXXOption 方法。
- 多题组合:qs.add(q1); qs.add(q2); 把多个问题塞进同一列表。
常见坑:
- 必须返回 List,不是单个 Question:返回单个 Question 不会渲染;至少包一层 ArrayList。
- 不要裸写 new java.util.ArrayList():GraalVM 下应使用 Java.type('java.util.ArrayList'),旧示例里的裸写法在新引擎上可能报错(→ GraalVM 差异)。
- 题目编号唯一:createQuestion 第一参数是题号,重复会导致前端渲染错乱。
表单水印¶
业务目标:在表单背景显示当前用户姓名或自定义文案作为水印,用于防泄密溯源。
写在哪儿:表单域 → 表单 → 基本 → 水印脚本(.form,属性 waterMarkScript;Label FORM:WATERMARK)。挂载点位置见 where-to-use/form/form-basic-properties。
触发时机与上下文:表单打开(且开启了水印功能)时触发。可用 WebUser、CurrentDocument、RelateDocument、ParentDocument。
返回值契约:String —— 水印文案(如当前用户姓名、部门、时间)。
示例代码:
代码说明:
- getWebUser() —— 取当前登录用户(→ curruser API)。
- .getName() —— 取姓名;若需登录名用 .getLoginno()。
变体与扩展:
- 拼上时间/IP:return getWebUser().getName() + " " + format(new Date(), "yyyy-MM-dd HH:mm");(→ date-functions)。
- 按角色切换文案:遍历 getWebUser().getRoles(),管理员返回空串不显示水印。
- 水印相关控件级脚本:上传控件有独立的 FORM:FIELD_UPLOAD_WATERMARK(属性 watermarkscript),见文件附件与二维码(11-attachment-and-qrcode.md)。
常见坑:
- 水印开关未开启:脚本写了但表单基本属性里没勾选"水印"则不显示。
- getWebUser() 为空场景:API public 调用、定时任务无用户上下文——但本场景是表单打开触发,通常安全。
- 字符串比较用 ===:判断角色名时不要用 .equals(→ GraalVM 差异)。