跳转至

表单与导入校验

一句话定位:校验类脚本的核心契约是**「失败返回提示 String,成功返回 ""(空串)」**——既不是 null,也不是 true/false。本章所有挂载点(表单字段、子表、附件、流程路径、视图操作、Excel 导入列、查重)都遵守这一契约,差异只在「写在哪儿」和「触发时机」。

场景清单

场景 落点 / Label 主要素材
字段非空/格式校验 校验脚本 FORM:FIELD_VALIDATE form-controls
子表(网格视图)非空校验 校验脚本 / 保存前 grid-view-non-empty-check
附件上传数量校验 校验脚本 FORM:FIELD_VALIDATE get-form-attachment-count
流程送出前校验(备注必填) 路径送出校验 WORKFLOW:RELATION_PASS_VALIDATE flow-path
视图操作执行前校验(数量上限) 操作前置 VIEW:COLUMN_OPERATE_BEFORE view-actions
Excel 导入列校验 + 数据清洗 EXCELIMPORT:COLUMN_VALIDATE / COLUMN_VALUE excel-import
记录是否存在 / 查重 校验脚本 FORM:FIELD_VALIDATE check-record-exists-or-count

校验失败统一弹窗:只要返回的是**非空字符串**,平台就会以提示框形式展示并**阻断后续动作**(保存/提交/送出/操作)。返回 "" 视为通过。详见 前言 → 默认包装与返回值契约通则


字段非空/格式校验

本章标杆场景:8 字段完整展开,后续场景沿用同构写法。

业务目标:在保存/提交表单前,校验某字段不为空、或符合指定格式(手机号、邮箱、身份证等),不通过则给出可读提示并阻止保存。

写在哪儿:表单域 → 表单 → 表单控件 → 校验(.form,属性 validateScript / JsonTemplate 校验相关键,Label FORM:FIELD_VALIDATE)。挂载点位置见 iscript-guid/basic → form-controls

触发时机与上下文:保存/提交校验时触发。可用环境变量:WebUserCurrentDocumentRelateDocumentParentDocument

返回值契约String——非空串 = 校验失败的提示文案(平台弹窗显示);""(空串)= 校验通过。不是 null不是 true/false

示例代码

// 校验"职务"字段不能为空
(function () {
  var rt = getItemValueAsString("职务");
  if (rt == null || rt === "") {
    return "'职务'不能为空";
  }
  return "";
})()
// 校验手机号格式(非空 + 11 位数字)
(function () {
  var mobile = getItemValueAsString("手机号");
  if (mobile == null || mobile.trim() === "") {
    return "请填写手机号";
  }
  // JS 正则:11 位数字
  if (!/^1\d{10}$/.test(mobile)) {
    return "手机号格式不正确(应为 11 位数字)";
  }
  return "";
})()

代码说明

  • getItemValueAsString("字段名") —— 按字段名取当前文档的字符串值;空字段返回 null"",需同时判断。
  • getCurrentDocument() —— 取当前文档对象(值/校验脚本上下文里隐式可用,getItemValueAsString 即作用于该文档)。
  • getWebUser() —— 取当前登录用户,常用于"按角色放宽校验"。
  • 字符串相等用 === / !==;判空用 v == null || v === ""== null 同时覆盖 nullundefined)。详见 GraalVM 差异

变体与扩展

  • 数值范围var n = Number(getItemValueAsString("金额")); if (!(n > 0)) return "金额必须大于 0";
  • 日期:用 getItemValueAsDate 取 Date 对象后再比较;勿对字符串直接做日期比较。
  • 条件必填:根据他字段值决定是否必填(如"类型=报销"时"发票号"必填)。
  • 多字段一次性校验:把多个 if 串联,任意一条失败立即 return 提示;不要拼成"字段1、字段2 不能为空"的长串,体验差。

常见坑

  • "".equals(v) 判空——GraalVM 下字符串字面量**没有** .equals 方法,须改 v === ""。详见 GraalVM 差异
  • v.length() 取长度——JS 字符串的 length 是**属性**,不是方法。
  • 成功路径忘了 return "",导致返回 undefined,行为依平台版本而异(旧版可能弹"undefined")。
  • 把校验脚本当值脚本写(返回布尔)——校验脚本只看是否为非空串。

子表(网格视图)非空校验

业务目标:保存前确保子表(网格视图)至少有一条有效明细,常用于报销明细、订单明细、出库清单等场景。

写在哪儿:通常写在保存前/提交前的操作前置脚本(.activity,Label ACTIVITY:BEFORE);也可挂在主表或子表字段的校验脚本(.form,Label FORM:FIELD_VALIDATE)。两者返回值契约一致(非空串=失败提示)。

触发时机与上下文:保存或提交前触发。可用环境变量:WebUserCurrentDocument

返回值契约String——非空串 = 失败提示;"" 或不返回 = 通过。

示例代码

// 校验子表至少有 1 条未删除的明细
(function () {
  var doc = getCurrentDocument();
  var subs = doc.getSubDocuments(); // 取所有子文档(含已删除)

  // 收集未删除行
  var list = createObject("java.util.ArrayList");
  for (var iter = subs.iterator(); iter.hasNext();) {
    var subDoc = iter.next();
    if (!subDoc.isDelete()) {         // true 表示该行被标记删除
      list.add(subDoc);
    }
  }

  if (list.size() === 0) {
    return "单据体没有数据,不允许保存";
  }
  return "";
})()

代码说明

  • getCurrentDocument() —— 取主表文档。
  • doc.getSubDocuments() —— 取所有子文档(网格视图行),包含已标记删除的行,需自行用 subDoc.isDelete() 过滤。
  • createObject("java.util.ArrayList") —— 平台助手函数,创建 Java ArrayList(比 Java.type('java.util.ArrayList') 更短,二者皆可)。
  • 遍历 Java Collection.iterator() + .hasNext() + .next();取长度用 .size()不是 .length。详见 GraalVM 差异

变体与扩展

  • 指定子表:在循环里加 if (subDoc.getFormname().indexOf("子表名") >= 0 && !subDoc.isDelete()) 仅统计某个子表。
  • 行数下限/上限if (list.size() < 2) return "至少 2 行"; if (list.size() > 100) return "超过 100 行";
  • 特定列非空:在循环内对每行 subDoc.getItemValueAsString("关键字段") 再判空,全空才报错。
  • 金额合计校验:在循环内累加某列,校验合计是否超过上限。

常见坑

  • 忘记排除已删除行:用户在前端删了一行又点了保存,getSubDocuments() 仍会返回该行(带删除标记),不过滤就会误判"有数据"。
  • 变量名覆盖:循环里再用 var doc = iter.next() 会遮蔽外层 doc,建议起名 subDoc
  • 把 List 当数组list.lengthlist[i] 都错——Java Listlist.size()list.get(i)
  • == 比较if (list.size() == 0) 在 GraalVM 下能跑(弱相等),但全书约定写 ===

附件上传数量校验

业务目标:保存前校验附件字段上传的文件数量(至少 N 个、不超过 M 个、必须为正整数等),常用于合同、报销单据。

写在哪儿:表单域 → 表单控件 → 校验(.form,属性 validateScript,Label FORM:FIELD_VALIDATE)。

触发时机与上下文:保存/提交前触发。可用环境变量:WebUserCurrentDocumentRelateDocumentParentDocument

返回值契约String——非空串 = 失败提示;"" = 通过。

示例代码

// 校验附件数量:至少 1 个,不超过 5 个
(function () {
  var jsonText = getItemValueAsString("附件");
  var number = 0;

  if (jsonText != null && jsonText.trim().length > 0) {
    // 附件字段存的是 JSON 数组字符串;JSONArray 是真实 Java 对象
    var JSONArray = Java.type('org.json.JSONArray');
    var jsonArray = new JSONArray(jsonText);
    number = jsonArray.length();   // 例外:Java 方法,不是 JS 属性
  }

  if (number === 0) {
    return "请至少上传 1 个附件";
  }
  if (number > 5) {
    return "附件数量不能超过 5 个(当前 " + number + " 个)";
  }
  return "";
})()

代码说明

  • getItemValueAsString("附件") —— 附件字段返回 JSON 数组字符串(如 [{"name":"x.pdf","size":1024,...}]),空时为 null""
  • Java.type('org.json.JSONArray') —— GraalVM 推荐写法。源示例里的 new Packages.org.json.JSONArray(...) 仍可兼容,新代码优先 Java.type
  • jsonArray.length() —— GraalVM 例外JSONArray 是真实 Java 类型,length() 是 Java 方法,不要改成 .length。详见 GraalVM 差异
  • JS 字符串的 jsonText.trim().length 是属性,不带括号——与上面的 Java 方法对照记忆。

变体与扩展

  • 总大小限制:循环 for (var i = 0; i < jsonArray.length(); i++)jsonArray.getJSONObject(i).getLong("size") 累加,超阈值报错。
  • 类型限制:取 fileObj.getString("name") 后看扩展名是否在白名单。
  • 统计字段写回:在值脚本里把 number 写到"附件数量"字段(doc.findItem("附件数量").setValue(number))。

常见坑

  • 空 JSON 解析异常jsonText""null 时直接 new JSONArray(...) 会抛错,必须先判空再解析
  • 混用 .length.length():JS 字符串用属性、JSONArray 用方法,写反任一都会错。
  • Packages.org.json.JSONArray:兼容但属旧写法,新代码用 Java.type('org.json.JSONArray')

流程送出前校验(备注必填)

业务目标:流程提交经过某条路径时,强制要求填写备注/意见/某字段,否则不允许送出。

写在哪儿:流程域 → 流程 → 关联线信息 → 路径送出校验脚本 → 值脚本(.flow 路径属性 validateScript,Label WORKFLOW:RELATION_PASS_VALIDATE)。挂载点见 iscript-guid/basic → flow-path

触发时机与上下文:流程提交前触发(类似表单控件校验,但作用于路径)。可用环境变量:WebUserCurrentDocument

返回值契约String——非空串 = 失败提示;"" = 通过。

示例代码

// 路径送出前校验"备注"必填
(function () {
  var doc = getCurrentDocument();
  var value = doc.getItemValueAsString("备注");

  if (value == null || value === "") {
    return "备注内容不能为空";
  }
  return "";
})()

代码说明

  • getCurrentDocument() —— 流程上下文中的当前文档。
  • doc.getItemValueAsString("字段名") —— iscript-guid/api → currdoc 中 Document 的字段取值方法。
  • 与表单字段校验完全同构,差异仅在挂载点(路径而非字段)和触发时机(送出而非保存)。

变体与扩展

  • 按路径分支不同校验:不同路径挂不同 validateScript,例如驳回路径要求填写驳回理由。
  • 审批意见校验:结合 getParameter("_attitude") 判断当前操作类型再决定是否必填。
  • 金额阈值if (Number(doc.getItemValueAsString("金额")) > 10000 && value === "") return "金额超 1 万必须填备注";

常见坑

  • 与路径条件混淆WORKFLOW:RELATION_CONDITION 返回 Boolean(是否走该路径),RELATION_PASS_VALIDATE 返回 String(失败提示/空串),两个 Label 不要写串
  • 与"路径执行脚本"混淆RELATION_PASS_ACTION 是经过时执行副作用(写状态、改字段),不返回提示;要做"拦截送出"必须用 RELATION_PASS_VALIDATE
  • 源示例里 if (value == null || value == "")==——GraalVM 下能跑,但全书统一写 ===

视图操作执行前校验(数量上限)

业务目标:在视图里点行内操作或顶部按钮时,执行默认动作前对勾选记录做业务校验(数量上下限、字段值范围等),不通过则弹提示并阻止后续动作。

写在哪儿:视图域 → 视图 → 操作(或行内操作列)→ 动作执行前脚本(.view,操作属性 beforeScript,Label VIEW:COLUMN_OPERATE_BEFORE)。挂载点见 iscript-guid/basic → view-actions

触发时机与上下文:点击视图按钮、在执行默认操作前触发。可用环境变量:WebUserCurrentDocumentParams(含 _selects 勾选记录 id 分号串)。

返回值契约String / Message / 空——返回非空串或 Message 对象时,平台以弹窗形式展示并**不执行**后续默认动作与脚本;返回空("" / 不返回)表示放行。

示例代码

// 勾选记录的"数量"字段都不能小于 100,否则阻断
(function () {
  var selectId = getParameterAsText("_selects"); // 形如 "id1;id2;id3"
  if (selectId == null || selectId === "") {
    return "请先勾选记录";
  }

  // 拼成 SQL 的 IN 列表:('id1','id2','id3')
  var sel = "('" + selectId.replace(/;/g, "','") + "')";

  var sql = "select * from tlk_材料信息表 where id in " + sel;
  var datas = queryBySQL(sql);
  if (datas != null && datas.size() > 0) {
    for (var iter = datas.iterator(); iter.hasNext();) {
      var row = iter.next();
      var number = row.getItemValueAsString("数量");
      if (Number(number) < 100) {
        return "勾选数据中存在数量小于 100 的值";
      }
    }
  }
  return "";
})()

代码说明

变体与扩展

  • 勾选数量上下限:用 getParameterAsArray("_selects").length 判断勾了几条。
  • 批量删除前确认return createConfirm("确认删除选中的 " + n + " 条记录?");
  • 按状态过滤:在 SQL 里 and item_状态 = '已审',查不到记录则阻断。

常见坑

  • _selects 形态混淆getParameterAsText 返回分号串(要 .replace 转义),getParameterAsArray 返回 JS 数组(用 .length、下标访问)。不要把数组当字符串处理。
  • SQL 注入:直接把 _selects 拼进 SQL 是源素材的做法,但生产环境若 id 可被前端伪造需注意;高安全场景建议改用 in (?) 参数绑定或先校验 id 形态。
  • selectId.length(属性)不是 .length()——JS 字符串。
  • 判空方向:源示例里 if (selectId != null || selectId.length > 0)|| 应为 &&,复制时注意修正。

Excel 导入列校验 + 数据清洗

一个场景、两个挂载点:导入每行时既可"清洗值"(COLUMN_VALUE),也可"校验值"(COLUMN_VALIDATE)。两者常配合使用:先清洗成规范值,再校验。

业务目标:Excel 导入时对某列做"负数置零""金额上限""空值报错"等处理,避免脏数据进库。

写在哪儿

  • 数据清洗:Excel 导入域 → Excel 导入 → 列信息 → 值脚本(.excelconfig 列属性 valueScript,Label EXCELIMPORT:COLUMN_VALUE;运行/调试时归一到 FORM:FIELD_VALUE)。
  • 列校验:Excel 导入域 → Excel 导入 → 列信息 → 检验脚本(.excelconfig 列属性 validateRule,Label EXCELIMPORT:COLUMN_VALIDATE)。

挂载点见 iscript-guid/basic → excel-import

触发时机与上下文:导入 Excel 时每条记录触发一次。可用环境变量:CurrentDocument(导入行映射成的临时文档)。

返回值契约

  • COLUMN_VALUE:返回写入字段的值(String/Number/Date 等),无强制空串约定。
  • COLUMN_VALIDATEString——非空串 = 错误提示并终止导入该行;"" = 通过。

示例代码

// === COLUMN_VALUE:金额为负或缺失时置 0 ===
(function () {
  var doc = getCurrentDocument();
  var value = doc.getItemValue("金额"); // 原始值(可能是数字/字符串)
  if (value == null || Number(value) < 0) {
    return 0;
  }
  return value;
})()
// === COLUMN_VALIDATE:名称必填,空则终止导入 ===
(function () {
  var doc = getCurrentDocument();
  var value = doc.getItemValueAsString("名称");
  if (value == null || value === "") {
    return "名称不能为空";
  }
  return "";
})()

代码说明

变体与扩展

  • 日期格式归一化:把 "2024/1/1""2024-1-1" 统一转成 "yyyy-MM-dd" 再返回(COLUMN_VALUE)。
  • 字典码 → 名称:导入的是代码(如"01"),查询字典表转成名称"已审"再入库。
  • 金额超阈值告警if (Number(value) > 100000) return "单行金额超过 10 万,请确认";
  • 跨字段校验:在同一文档上取多列值联合判断。

常见坑

  • 返回类型:COLUMN_VALUE 返回的是要**入库的值**,不是提示串;把"提示"错返回到 COLUMN_VALUE 会污染数据。
  • Label 归并:调试时看到 FORM:FIELD_VALUE 是正常的(不是配置错了)。
  • 空串 vs nullvalue === "" 才是真空串;value == null 仅判断 null/undefined。判"未填写"建议两者都判:v == null || v === ""
  • 不要在校验里写副作用(如 doUpdate),导入事务由平台控制。

记录是否存在 / 查重

业务目标:保存前根据业务主键(编码、身份证、合同号等)查重,确保唯一性;或检查关联记录是否存在。

写在哪儿:表单域 → 表单控件 → 校验(.form,属性 validateScript,Label FORM:FIELD_VALIDATE);也可放保存前操作前置。

触发时机与上下文:保存/提交前触发。可用环境变量:WebUserCurrentDocument

返回值契约String——非空串 = 失败提示;"" = 通过。

示例代码

// 新增/编辑时按"合同号"查重,编辑时排除自身
(function () {
  var doc = getCurrentDocument();
  var code = doc.getItemValueAsString("合同号");
  if (code == null || code === "") {
    return ""; // 非空校验交给另一个脚本,这里放行
  }

  // 拼SQL:item_字段名 是平台字段列名约定;tlk_ 是表名前缀
  var selfId = doc.getId();
  var sql = "select id from tlk_合同表 " +
            "where item_合同号 = '" + code + "' " +
            "and id <> '" + selfId + "'";

  var count = countBySQL(sql);
  if (count > 0) {
    return "合同号已存在,请检查后再提交";
  }
  return "";
})()

代码说明

  • countBySQL(sql) —— 按 SQL 统计记录数,比 queryBySQL 更轻;只需判断"有没有"时优先用它。也可用 findBySQL(sql)(返回单条文档或 null)。
  • 平台约定:表单字段在数据库中的列名为 item_字段名;表名为 tlk_表单名;多租户/多域时建议加 domainid 限定(getWebUser().getDomainid())。
  • getCurrentDocument().getId() —— 取当前文档 id,编辑场景下用于排除自身。

变体与扩展

  • 跨表查重select id from tlk_另一张表 where item_某字段 = '...'
  • 多条件查重where item_字段1 = '...' and item_字段2 = '...' and domainid = '...'
  • 限本域and domainid = '" + getWebUser().getDomainid() + "'",避免跨域误判。
  • 统计计数:返回具体数量给前端(值脚本/计算脚本场景):return "已存在 " + count + " 条相同记录";
  • 存在即更新(操作后置场景,不是校验):用 updateByDSName 执行 update 语句。

常见坑

  • 编辑时把自己算成重复:必须 and id <> '<当前文档id>',否则一保存就报"已存在"。
  • 表名前缀/字段列名:写 from 合同表where 合同号 都查不到——平台表名是 tlk_表单名、字段列名是 item_字段名
  • SQL 注入:直接拼用户输入有风险,关键场景应做格式校验(如只允许字母数字)再拼。
  • 跨域数据:不加 domainid 限定时,可能把别的租户/域的同名记录误判为重复。
  • count > 0>countBySQL 返回数值,直接比较即可;不要写 === true
  • 把校验脚本当查询脚本:在 校验脚本里 doUpdate/sendMail 会破坏保存事务,副作用应放操作后置。

本章"返回值契约"速查

场景 Label 失败 成功
字段校验 FORM:FIELD_VALIDATE 非空提示 String ""
子表非空 校验/操作前置 非空提示 String ""
附件数量 FORM:FIELD_VALIDATE 非空提示 String ""
流程送出 WORKFLOW:RELATION_PASS_VALIDATE 非空提示 String ""
视图操作前 VIEW:COLUMN_OPERATE_BEFORE 非空串 / Message "" / 不返回
Excel 校验 EXCELIMPORT:COLUMN_VALIDATE 非空提示 String ""
记录查重 FORM:FIELD_VALIDATE 非空提示 String ""

一句话记住:校验脚本永远 return "" 表示通过;想阻断就 return "可读提示"。其它返回形态(nulltruefalseundefined)都不是契约保证的"通过"语义,不要依赖。

相关章节

  • 字段值/选项/默认值填充:表单数据填充、联动与动态选项
  • 字段隐藏/只读/打印隐藏:权限与可见性控制
  • SQL 查询与批量操作:数据查询、统计与文档批量操作
  • 路径条件/经过动作(与送出校验对比):流程路由条件与子流程