附录 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;缓存键值对与当前用户绑定,不同用户互不干扰。- 数据存内存、会话级有效;服务重启或会话结束后自动失效,**不要**用来存持久化数据。
- 清空缓存 = 写入
null:cacheUtil.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(";");
})()
说明:
getItemValueAsString见iscript-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→ curruser;getApplication()见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() : "";
})()
说明:
getFormProcess/getViewProcess见iscript-guid/functions→ java-class-functions(含返回的FormProcess/ViewProcess业务对象方法清单)。findBySQL/queryBySQL见iscript-guid/api→ curdb;返回Document或Collection<Document>,长度用.size()。getApplication()见iscript-guid/functions→ system-functions。- 名称区分大小写,且必须与平台中**完全一致**;找不到返回
null,调用前必须判空。 - 当前文档的表单 ID/名也可直接取:
getCurrentDocument().getFormid()/.getFormname()。
#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-functions;parseDate(str, format)用于反向转换。- JS Date 月份从 0 开始(0=1月,11=12月);构造
new Date(year, month, 0)中的0表示「上个月最后一天」,这是计算月末的惯用写法。 Math.abs/Math.ceil/Math.floor是 JS 原生,可直接使用。- 工作日算法**不包含法定节假日调休**;如需精确,请在平台维护一份节假日表,再叠加此算法。
- Java
Calendar用Java.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 差异。