跳转至

表单数据填充、联动与动态选项

本章覆盖表单打开、刷新、保存提交时最常见的"把数据填进去"类需求:字段默认值、流水编号、字段联动、字段清空、下拉动态选项、调查问卷、表单水印。所有脚本均为 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=01calculateonrefresh;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 .lengthcountBySQL 返回数字;若你换用 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

触发时机与上下文:表单打开时。可用 WebUserCurrentDocumentRelateDocumentParentDocument

返回值契约:以下三种形式平台都接受(详见下一节 选项返回格式速查): - Options 对象(createOptions() 返回,调 .add(name, value) 添加); - 字符串 "值:文本;值:文本"; - 字符串 "文本;文本"(值与文本相同)。

示例代码

从数据源 MY_DSt_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 数组用 .lengtharr[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-functionscreateOptions() 文档为准,使用前用 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

触发时机与上下文:表单打开时。可用 WebUserCurrentDocumentRelateDocumentParentDocument

返回值契约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

触发时机与上下文:表单打开(且开启了水印功能)时触发。可用 WebUserCurrentDocumentRelateDocumentParentDocument

返回值契约String —— 水印文案(如当前用户姓名、部门、时间)。

示例代码

// 用当前用户姓名作为表单水印
(function () {
  var webUserName = getWebUser().getName();
  return webUserName;
})()

代码说明: - getWebUser() —— 取当前登录用户(→ curruser API)。 - .getName() —— 取姓名;若需登录名用 .getLoginno()

变体与扩展: - 拼上时间/IPreturn 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 差异)。