跳转至

附录 A:工具函数与通用技巧

本附录是「iScript 场景指南」的**工具箱速查**——前面各章节按业务目标展开的场景中反复出现的「跨场景通用动作」(缓存、JSON、去重、token、名称↔ID、扩展库、日期计算)集中在这里,按**「问题 → 片段」**式给出。

与正文章节的区别:

  • 正文用 8 字段场景模板;本附录每个主题只给「一段说明 + 可运行代码片段」,不展开业务上下文。
  • 代码均为 **GraalVM JS 合法**写法;写之前如有疑问,先回看 GraalVM 差异
  • 函数签名/参数细节请点链接到 iscript-guid/functions / iscript-guid/api;本附录不复述签名。

末尾另设一节 「其它低频场景」 用表格点名带过(大屏、KMS、数据模块事件脚本等),不在本附录展开。


跨脚本/跨表单缓存(用户私有空间)

问题:在用户会话期间,需要把上一步算出的中间值、跨表单的传递数据、或避免重复计算的结果暂时存起来——但又不想落库。

片段:使用平台 MemoryCacheUtil 写入/读取当前用户的**私有空间**,键值对存储,会话级有效。

(function () {
  // 1. 写缓存
  var cacheUtil = new Packages.cn.myapps.common.util.cache.MemoryCacheUtil();
  cacheUtil.putToPrivateSpace("myKey", "myValue", getWebUser());

  // 2. 读缓存(不存在返回 null)
  var val = cacheUtil.getFromPrivateSpace("myKey", getWebUser());
  // val === "myValue"
})()
// 典型用法:表单 A 写、表单 B 读
// 表单 A 的操作后置
(function () {
  var doc = getCurrentDocument();
  var cacheUtil = new Packages.cn.myapps.common.util.cache.MemoryCacheUtil();
  cacheUtil.putToPrivateSpace("formA_field1", doc.getItemValueAsString("字段1"), getWebUser());
})()

// 表单 B 的值脚本
(function () {
  var cacheUtil = new Packages.cn.myapps.common.util.cache.MemoryCacheUtil();
  var v = cacheUtil.getFromPrivateSpace("formA_field1", getWebUser());
  return v != null ? v : "";
})()

说明

  • getWebUser()iscript-guid/api → curruser;缓存键值对与当前用户绑定,不同用户互不干扰。
  • 数据存内存、会话级有效;服务重启或会话结束后自动失效,**不要**用来存持久化数据。
  • 清空缓存 = 写入 nullcacheUtil.putToPrivateSpace("myKey", null, getWebUser())
  • 键名建议带应用/表单前缀(如 formA_field1),避免与其它脚本冲突。

JSON 创建与解析

问题:与外部接口交换数据、把结构化字段存进单行文本字段、或将文档字段序列化为字符串。

片段:JavaScript 原生 JSON.stringify / JSON.parse 优先;需要与 Java 侧互通时用 org.json.JSONObject / JSONArray

// 创建:JS 对象 → JSON 字符串
(function () {
  var obj = {
    name: "张三",
    age: 30,
    hobbies: ["读书", "游泳"]
  };
  return JSON.stringify(obj);
  // {"name":"张三","age":30,"hobbies":["读书","游泳"]}
})()

// 解析:JSON 字符串 → JS 对象
(function () {
  var jsonStr = '{"name":"张三","age":30}';
  var obj = JSON.parse(jsonStr);
  return obj.name; // 张三
})()
// 与 Java 侧互通(org.json)
(function () {
  var JSONArray = new Packages.org.json.JSONArray();
  JSONArray.put("值1");
  JSONArray.put("值2");
  return JSONArray.toString(); // ["值1","值2"]
})()

// 解析 Java 风格 JSON 数组
(function () {
  var jsonStr = '["值1","值2","值3"]';
  var arr = new Packages.org.json.JSONArray(jsonStr);
  var out = [];
  for (var i = 0; i < arr.length(); i++) {   // ← 真实 Java 对象,用 .length() 方法
    out.push(arr.getString(i));
  }
  return out.join(",");
})()

说明

  • JS 字符串/数组操作直接用 JSON.* 与原生方法即可;如需 splitText/joinText 等平台封装函数,见 iscript-guid/functions → string-functions
  • GraalVM 注意Packages.org.json.JSONArray 是真实 Java 对象,**保留 .length() 方法**不要改成属性;JS 原生字符串则必须用 .length 属性。详见 GraalVM 差异 「字符串长度:用 .length 属性,禁用 `.le」。
  • 解析不可信 JSON 字符串前用 try { ... } catch (e) { ... } 包裹,避免脚本中断。

字符串/ID 列表去重

问题:从字段或参数取到的 "ID1;ID2;ID1;ID3" 需要去重,且要**保留首次出现的顺序**。

片段:用 JS 对象当哈希表去重,保留顺序;分号拼接的多值字段最常见。

(function () {
  var raw = "值1;值2;值1;值3;值2";
  var arr = raw.split(";");
  var seen = {};
  var unique = [];

  for (var i = 0; i < arr.length; i++) {
    var item = arr[i].trim();          // ← trim() 是 Java String 方法,JS 兼容
    if (item.length > 0 && !seen[item]) {  // ← JS 字符串 .length 属性
      seen[item] = true;
      unique.push(item);
    }
  }
  return unique.join(";"); // 值1;值2;值3
})()
// 从文档字段去重(带空值保护)
(function () {
  var doc = getCurrentDocument();
  var str = doc.getItemValueAsString("用户列表");
  if (str == null || str.trim().length === 0) {
    return "";
  }
  var arr = str.split(";");
  var seen = {};
  var unique = [];
  for (var i = 0; i < arr.length; i++) {
    var id = arr[i].trim();
    if (id.length > 0 && !seen[id]) {
      seen[id] = true;
      unique.push(id);
    }
  }
  return unique.join(";");
})()
// 必须用 Java 集合时:LinkedHashSet 保持插入顺序
(function () {
  var LinkedHashSet = Java.type("java.util.LinkedHashSet");
  var set = new LinkedHashSet();
  var arr = "值1;值2;值1".split(";");
  for (var i = 0; i < arr.length; i++) {
    var item = arr[i].trim();
    if (item.length > 0) {
      set.add(item);
    }
  }
  var out = [];
  var it = set.iterator();        // ← Java Collection 用 .iterator() 遍历
  while (it.hasNext()) {
    out.push(it.next());
  }
  return out.join(";");
})()

说明

  • getItemValueAsStringiscript-guid/functions → doc-functions
  • 推荐用 JS 对象去重(最轻量);仅在需要与 Java 侧互传时才用 LinkedHashSet
  • GraalVM 注意item.length 是 JS 属性,**不要**写成 item.length()set.iterator() / it.hasNext() 是 Java 方法,保持原样。详见 GraalVM 差异 「Java List .size() vs JS 数组」。

取用户访问 token

问题:拼装免登 URL(外部系统单点登录、文件下载、分享链接)时,需要当前用户的 accessToken。

片段:通过平台 Security 工具类按用户 ID 取 token。

(function () {
  var userId = getWebUser().getId();

  var Security = new Packages.cn.myapps.common.util.Security();
  var token = Security.getToken(userId);

  if (token == null || token.trim().length === 0) {
    return ""; // 取不到时不要把 null 拼进 URL
  }
  return token;
})()
// 拼装带 token 的免登 URL(结合当前请求信息)
(function () {
  var userId = getWebUser().getId();
  var token = new Packages.cn.myapps.common.util.Security().getToken(userId);

  var req = getParamsTable().getHttpRequest();
  var base = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort();

  // 例:跳转到指定表单
  var appId = getApplication();
  var formId = "__________"; // 表单ID
  return base + "/static/portal/vue/index.html#/open?appId=" + appId
       + "&linkType=00&actionContent=" + formId
       + "&accessToken=" + token;
})()

说明

  • getWebUser().getId()iscript-guid/api → currusergetApplication()iscript-guid/functions → system-functions
  • getParamsTable().getHttpRequest() 拿到原生 HttpServletRequest,可取 scheme/serverName/serverPort
  • Packages.cn.myapps.common.util.Security 是平台内部 Java 工具类(兼容保留写法);token 是敏感信息,避免写入日志或落库明文字段。

表单名 ↔ ID、视图名 ↔ ID 互转

问题:硬编码 ID 不便维护;按业务名("客户表单"、"待办视图")动态取 ID 更直观,反向查询也常需要。

片段:通过 getFormProcess() / getViewProcess()doViewByName 类方法互转。

// 表单名 → 表单ID
(function () {
  var form = getFormProcess().doViewByFormName("Task", getApplication());
  if (form == null) {
    return ""; // 找不到不要 .getId(),会空指针
  }
  return form.getId();
})()

// 表单ID → 表单名
(function () {
  var formId = "__________"; // 已知表单ID
  var form = getFormProcess().findById(getApplication(), formId);
  return form != null ? form.getName() : "";
})()
// 视图名 → 视图ID
(function () {
  var view = getViewProcess().doViewByViewName("视图名称", getApplication());
  return view != null ? view.getId() : "";
})()

// 表单ID + 视图名 → 视图ID(同表单多视图,名称重复时用此法定位)
(function () {
  var form = getFormProcess().doViewByFormName("表单名称", getApplication());
  if (form == null) return "";

  var sql = "SELECT id FROM t_view WHERE name = '"
          + "视图名称' AND formid = '" + form.getId() + "'";
  var row = findBySQL(sql);
  return row != null ? row.getId() : "";
})()

说明


#include 扩展函数库

问题:自定义函数库(如「数据字典」「格式化函数」「业务函数」)想在不复制代码的前提下,被多个脚本复用。

片段:脚本顶部用 #include 引入函数库,库里定义的函数即可直接调用。

#include "mylib"; // 顶部声明:引入名为 mylib 的函数库

(function () {
  // 调用库中定义的函数(无需前缀)
  var rtn = myFunction(arg1, arg2);
  return rtn;
})()
// 引入「数据字典」库,按字典值取名称
#include "数据字典";

(function () {
  var dictValue = "DICT001";
  var dictName = getDictNameByCode(dictValue); // 库中提供的函数
  return dictName;
})()

说明

  • 自定义函数库的创建与 #include 语法见 iscript-guid/basic → function-usage
  • 函数库名称必须与平台中已注册的库**完全一致**(区分大小写);未注册时脚本会找不到函数。
  • 不同库可能存在同名函数;多个 #include 时的覆盖顺序以平台加载规则为准,建议函数命名加业务前缀避免冲突。
  • 库内代码也需遵守 GraalVM 差异.length 属性、=== 等)。

日期加减 / 差值 / 工作日 / 月末

问题:到期日计算、日期范围查询、报表周期、工作日统计——这些是表单值脚本和定时任务里最高频的日期动作。

片段:JS 原生 Date 配合平台 format(date, pattern) 即可覆盖绝大多数场景;复杂日历计算可借 Java Calendar

// 日期加减(加 7 天 / 减 7 天 / 加 1 月)
(function () {
  var d1 = new Date();
  d1.setDate(d1.getDate() + 7);

  var d2 = new Date();
  d2.setDate(d2.getDate() - 7);

  var d3 = new Date();
  d3.setMonth(d3.getMonth() + 1);

  return format(d1, "yyyy-MM-dd") + " | "
       + format(d2, "yyyy-MM-dd") + " | "
       + format(d3, "yyyy-MM-dd");
})()
// 两日期的天数差
(function () {
  var d1 = new Date("2024-01-01");
  var d2 = new Date("2024-12-31");
  var diffMs = Math.abs(d2.getTime() - d1.getTime());
  return Math.ceil(diffMs / (1000 * 60 * 60 * 24)); // 366
})()
// 计算工作日(排除周末;不含节假日调休)
(function () {
  var start = new Date("2024-01-01");
  var end = new Date("2024-01-31");
  var workDays = 0;
  var cur = new Date(start);

  while (cur <= end) {
    var dow = cur.getDay(); // 0=周日, 6=周六
    if (dow !== 0 && dow !== 6) {
      workDays++;
    }
    cur.setDate(cur.getDate() + 1);
  }
  return workDays;
})()
// 月末:本月最后一天(含日期数)
(function () {
  var d = new Date();
  // 关键技巧:下一月的「第 0 天」即为本月最后一天
  var lastDay = new Date(d.getFullYear(), d.getMonth() + 1, 0);
  return format(lastDay, "yyyy-MM-dd") + "(本月共 " + lastDay.getDate() + " 天)";
})()
// 本月第一天 / 最后一天(报表日期范围常用)
(function () {
  var now = new Date();
  var firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
  var lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0);
  return JSON.stringify({
    start: format(firstDay, "yyyy-MM-dd"),
    end: format(lastDay, "yyyy-MM-dd")
  });
})()
// 复杂日历计算用 Java Calendar(如取本月最大天数)
(function () {
  var Calendar = Java.type("java.util.Calendar");
  var SimpleDateFormat = Java.type("java.text.SimpleDateFormat");

  var cal = Calendar.getInstance();
  cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));

  var sdf = new SimpleDateFormat("yyyy-MM-dd");
  return sdf.format(cal.getTime());
})()

说明

  • format(date, pattern)iscript-guid/functions → date-functionsparseDate(str, format) 用于反向转换。
  • JS Date 月份从 0 开始(0=1月,11=12月);构造 new Date(year, month, 0) 中的 0 表示「上个月最后一天」,这是计算月末的惯用写法。
  • Math.abs / Math.ceil / Math.floor 是 JS 原生,可直接使用。
  • 工作日算法**不包含法定节假日调休**;如需精确,请在平台维护一份节假日表,再叠加此算法。
  • Java CalendarJava.type("java.util.Calendar") 引入;**不要**裸写 java.util.Calendar。详见 GraalVM 差异 「取 Java 类:用 Java.type(...),」。

其它低频场景

以下场景在日常表单/视图开发中较少出现,且其脚本属性、返回值契约**以运行模块的产品文档为准**,本 cookbook 不展开。这里只「点名」让读者知道有这些挂载点,需要时去对应模块查文档。对应设计 spec §11 中「低频场景点名带过」的处理倾向。

域 / 模块 挂载点 / Label 触发时机 返回值契约 备注
大屏(数据看板) BIGSCREEN_SCRIPT 大屏渲染时 依大屏产品约定 脚本写在 .bigscreen 资源中;非表单主路径
KMS(知识管理) SCRIPT(KMS 域) KMS 功能点 依 KMS 模块约定 与表单 .form 解耦,挂载点单独维护
数据模块·创建 DATAMODULE:CREATEEVENT_SCRIPT 数据记录创建时 依数据模块实现 与表单事件脚本不同,副作用为主
数据模块·更新 DATAMODULE:UPDATEEVENT_SCRIPT 数据记录更新时 依数据模块实现 同上
数据模块·查询 DATAMODULE:SEARCHEVENT_SCRIPT 数据模块查询时 依数据模块实现(可影响查询结果集) 同上
数据模块·删除 DATAMODULE:DELETEEVENT_SCRIPT 数据记录删除时 依数据模块实现 同上
脚本库·应用级 LIB:APP #include 引用时 库内定义的函数 库资源,非业务字段脚本
脚本库·基础 LIB:BASE #include 引用时 库内定义的函数 同上
脚本库·扩展 LIB:EXTEND #include 引用时 库内定义的函数 同上

写在前面

  • 大屏 / KMS / 数据模块事件脚本 不在常规 .form / .view / .flow 文件中;它们的属性名、触发链路由对应运行模块定义,请勿在表单/视图落点中臆造。
  • LIB:* 是**脚本库资源标签**,配合本附录「#include 扩展函数库」的 #include 使用;本身不是某个字段/控件的脚本属性。
  • 如需扩展这些场景的细节,建议在对应模块(大屏 / KMS / 数据模块)的专题文档中展开,而非挤入本 iScript 场景指南。

本附录的所有代码片段均经过 GraalVM 合规自查(=== / !==.length 属性、Java.type 引入 Java 类、Java List 用 .size()/.iterator()Packages... 仅在源素材已使用处保留并加注)。如发现写法有疑,回看 前言 → GraalVM 差异