文件附件与二维码¶
一句话定位:本章处理"附件字段值"这条隐式 JSON 数据线——上传控件存的是
[{"name":"x.pdf","size":1024,"path":"..."}]形式的 JSON 数组字符串,要先解析才能拿到数量/文件名/大小;再延伸到 ZIP 打包下载、计算脚本图片自适应展示、二维码内容生成与扫码签到回调;最后用一张表收束上传控件的同构权限脚本族(下载/删除/重命名/预览/在线编辑/新版本/版本比较/版本还原/水印)。附件 JSON 用org.json.JSONArray解析,jsonArray.length()是 Java 方法(合法),与 JS 字符串的.length属性是两码事——动手前先扫 GraalVM 差异。
场景清单¶
| 场景 | 落点 / Label | 主要素材 |
|---|---|---|
| 解析附件 JSON(数量/名/大小) | 值脚本 FORM:FIELD_VALUE / 校验 FORM:FIELD_VALIDATE |
get-form-attachment-count |
| 附件打包 ZIP 下载 | 视图操作 / 操作后置 / 计算 | file-download |
| 计算脚本图片自适应高宽 | 计算脚本 FORM:FIELD_VALUE(返回 HTML) |
calc-script-image-display-resize |
| 二维码控件内容 | FORM:FIELD_QR_CONTENT |
form-controls |
| 二维码扫码签到回调 + 重复校验 | 二维码回调脚本 | qrcode-callback |
| 上传权限脚本族速查(下载/删除/重命名/预览/在线编辑/版本/水印) | FORM:FIELD_UPLOAD_* 全族 |
iscript-usage/form |
附件字段 JSON 形态(字段名按业务约定,常见为"附件"/"图片"/"文件"):
[ {"name":"合同.pdf", "path":"/uploads/xxx", "size":102400, "type":"application/pdf"}, {"name":"附件.doc", "path":"/uploads/yyy", "size":204800, "type":"application/msword"} ]注意:字段未上传附件时可能返回
null、""或"[]"——解析前必须判空。
解析附件 JSON(数量/名/大小)¶
本章标杆场景:8 字段完整展开,后续场景沿用同构写法。
业务目标:把"附件"字段的 JSON 字符串解析出来,在表单上展示"共 N 个文件 / 总大小 X MB / 文件清单",或把数量写回另一个数值字段供后续流程使用。校验场景("至少上传 1 个、不超过 5 个")已在 → 附件上传数量校验 讲过,此处聚焦"解析 + 展示/统计"。
写在哪儿:
- 值脚本 / 计算脚本(.form,属性 valueScript / JsonTemplate valuescript,Label FORM:FIELD_VALUE)—— 在派生字段(如"附件数量"、"附件清单"计算文本)里返回数量或拼接好的展示串。
- 校验脚本(.form,Label FORM:FIELD_VALIDATE)—— 校验数量时复用同一段解析逻辑(详见 表单与导入校验)。
挂载点位置见 where-to-use/form/form-controls。
触发时机与上下文:表单打开、刷新计算时。可用环境变量:WebUser(→ curruser API)、CurrentDocument(→ doc API)、RelateDocument、ParentDocument。
返回值契约:值脚本——Number(数量)或 String(拼接展示文案),赋给当前字段。计算脚本(HTML 类型控件)可返回 HTML 串直接渲染。
示例代码:
值脚本——返回附件数量:
(function () {
var jsonText = getItemValueAsString("附件");
var number = 0;
if (jsonText != null && jsonText.trim().length > 0) {
// JSONArray 是真实 Java 对象,length() 是方法(合法),不要改成 .length 属性
var JSONArray = Java.type('org.json.JSONArray');
var jsonArray = new JSONArray(jsonText);
number = jsonArray.length();
}
return number;
})()
值脚本——返回"附件清单 + 总大小"展示串:
(function () {
var jsonText = getItemValueAsString("附件");
if (jsonText == null || jsonText.trim().length === 0) {
return "暂无附件";
}
var JSONArray = Java.type('org.json.JSONArray');
var jsonArray = new JSONArray(jsonText);
var names = [];
var totalBytes = 0;
for (var i = 0; i < jsonArray.length(); i++) { // Java 方法 length()
var fileObj = jsonArray.getJSONObject(i);
names.push(fileObj.getString("name"));
totalBytes += fileObj.optLong("size", 0); // optLong 兜底 0,避免缺字段异常
}
var totalMB = (totalBytes / 1024 / 1024).toFixed(2);
return "共 " + jsonArray.length() + " 个,"
+ totalMB + " MB:" + names.join("、");
})()
操作后置——把数量写回"附件数量"字段(保存后持久化):
(function () {
var doc = getCurrentDocument();
var jsonText = doc.getItemValueAsString("附件");
var number = 0;
if (jsonText != null && jsonText.trim().length > 0) {
var JSONArray = Java.type('org.json.JSONArray');
number = new JSONArray(jsonText).length();
}
doc.findItem("附件数量").setValue(number);
})()
代码说明:
getItemValueAsString("附件")—— 取附件字段的 JSON 字符串值;空时返回null或"",二者都要判。Java.type('org.json.JSONArray')—— GraalVM 推荐的 Java 类引用方式;旧示例里的new Packages.org.json.JSONArray(...)仍可兼容运行(→ GraalVM 差异 → 取 Java 类)。jsonArray.length()—— Java 方法,返回数组长度。这是 GraalVM 下少有的合法.length():因为jsonArray是真实 Java 对象,不是 JS 数组、也不是 JS 字符串。**不要**改成.length属性,详见 GraalVM 差异 → 字符串长度。jsonArray.getJSONObject(i).getString("name")/.optLong("size", 0)—— 标准org.json.JSONObjectAPI;.optXxx(key, default)在字段缺失时返回默认值,比.getXxx(key)安全。doc.findItem("字段名").setValue(value)—— 把派生值写回主表字段。
变体与扩展:
- 过滤特定类型:遍历时按
fileObj.getString("name")的扩展名筛选(如只统计.pdf)。 - 多附件字段汇总:把多个附件字段的 JSON 各自解析后累加。
- HTML 渲染清单:计算脚本里把
names拼成<ul><li>...</li></ul>直接渲染。 - 校验数量上限:复用同一段解析,把"非空串=失败提示"的校验契约套上去(见 → 附件上传数量校验)。
常见坑:
- 混淆
.length与.length():JS 字符串jsonText.trim().length是**属性**(无括号);jsonArray.length()是 Java 方法(带括号)。两者在同一脚本里出现时最易写错,详见 GraalVM 差异。 - 空值未判直接解析:
new JSONArray(null)或new JSONArray("")会抛异常,必须先if (jsonText != null && jsonText.trim().length > 0)。 - 字段名匹配:附件字段名("附件"/"图片"/"文件")须与表单实际字段一致;不同表单上传控件对应的字段名不同。
getLong("size")缺字段抛异常:附件 JSON 中若个别对象缺size键,.getLong("size")直接抛异常,建议用.optLong("size", 0)。
附件打包 ZIP 下载¶
业务目标:把表单中多个附件打包成 ZIP 文件并提供下载链接,常用于"一键下载全部附件"、批量归档、批量导出。
写在哪儿:通常挂在视图操作按钮的后置脚本或表单工具栏按钮的执行后脚本(.activity,属性 afteractionscript,Label ACTIVITY:AFTER);也可放在计算脚本里返回 URL 串供前端跳转。挂载点位置见 where-to-use/view/view-actions。
触发时机与上下文:操作按钮点击后触发。可用 getCurrentDocument()(→ doc API)、getWebUser()、getItemValueAsString("字段名")。视图操作上下文还可用 getParameterAsArray("_selects") 取选中记录。
返回值契约:String——ZIP 文件的相对下载路径(如 /uploads/zipFile/xxx.zip),前端按此路径触发跳转下载。
示例代码:
(function () {
var fileString = getItemValueAsString("附件");
if (fileString == null || fileString.trim().length === 0) {
return ""; // 无附件,交由前端处理
}
// —— Java 工具类:源素材用 Packages.xxx 形式,平台兼容写法,保留并加注 ——
var ZipUtil = new Packages.cn.myapps.util.file.ZipUtil();
var FileUtil = new Packages.cn.myapps.util.file.FileUtil();
var JSONArray = new Packages.net.sf.json.JSONArray();
var fileArr = JSONArray.fromObject(fileString);
// 5.1+ 版本取存储根路径
var rootPath = Packages.cn.myapps.common.Environment.getInstance().getStorageRootPath();
var destFolder = rootPath + "/uploads/zipFile";
var copyFolder = destFolder + "/copyFile";
// 确保临时目录存在
var copyDir = new Packages.java.io.File(copyFolder);
if (!copyDir.exists()) {
copyDir.mkdirs();
}
// 复制文件到临时目录,处理重名
var files = [];
var index = 1;
for (var it = fileArr.iterator(); it.hasNext();) {
var file = it.next();
var path = rootPath + file.getString("path");
var name = file.getString("name");
var fileType = name.substring(name.lastIndexOf("."));
var newPath = copyFolder + "/" + name;
if (new Packages.java.io.File(newPath).exists()) {
newPath = copyFolder + "/" + name.replace(fileType, index + fileType);
index++;
}
FileUtil.copyFile(newPath, path);
files.push(newPath);
}
// 压缩 + 清理临时副本
var zipName = "附件_" + format(new Date(), "yyyyMMddHHmmss");
ZipUtil.compressFiles(zipName, files, destFolder);
for (var i = 0; i < files.length; i++) { // files 是 JS 数组,用 .length
FileUtil.deleteFiles(files[i]);
}
return "/uploads/zipFile/" + zipName + ".zip";
})()
代码说明:
Packages.cn.myapps.util.file.ZipUtil/FileUtil—— 平台内部 Java 工具类,源素材均以Packages.xxx形式引用,按 GraalVM 差异 → 取 Java 类 的"兼容保留"规则沿用;新代码不强制改写为Java.type,因为平台内部类用Packages更直观。JSONArray.fromObject(jsonString)——net.sf.json.JSONArray的解析方法(与第 1 节的org.json.JSONArray构造函数是**两套不同的 JSON 库**,不要混用)。Environment.getInstance().getStorageRootPath()—— 5.1+ 版本取存储根路径;旧版本(4.4 用cn.myapps.util.property.PropertyUtil、5.0 用cn.myapps.common.util.PropertyUtil)按部署版本切换。ZipUtil.compressFiles(zipName, files, destFolder)—— 参数:ZIP 名(不含扩展名)、待压缩文件路径数组(JS 数组即可)、目标目录。format(date, pattern)—— 日期格式化,给 ZIP 名加时间戳避免重名。
变体与扩展:
- 按文档名命名 ZIP:
var zipName = getCurrentDocument().getItemValueAsString("文档名称") || "附件"; - 过滤特定类型再打包:遍历时按扩展名白名单筛选。
- 单文件直链下载:不压缩,直接
return fileArr.getJSONObject(0).getString("path");。
常见坑:
files是 JS 数组用.length,fileArr是 JavaList/JSONArray用.iterator()或.size()——两种长度写法混用是最常见的踩坑点,详见 GraalVM 差异 → Java List.size()vs JS 数组.length。- 根路径版本不匹配:4.⅘.0/5.1 三种
PropertyUtil/Environment写法分别对应不同平台版本,错用会找不到类。 - 目录权限:确保应用运行账号对
destFolder/copyFolder有写权限。 - 磁盘空间:临时副本会翻倍占用空间,压缩完成后必须清理(脚本末尾的
deleteFiles循环不能漏)。 - 大量文件耗时:压缩成百上千个文件可能超时,建议改为后台任务或限制单次打包数量。
计算脚本图片自适应高宽¶
业务目标:在表单的计算脚本控件里展示一张或多张图片,限制最大宽高并保持原始比例,避免图片溢出撑爆布局。
写在哪儿:表单域 → 表单 → 计算脚本控件(控件类型设为 HTML/计算文本)(.form,属性 valueScript / JsonTemplate valuescript,Label FORM:FIELD_VALUE)。挂载点位置见 where-to-use/form/form-controls。
触发时机与上下文:表单打开、刷新计算时。可用 getCurrentDocument()、getWebUser()。
返回值契约:String——HTML 片段,平台按 HTML 渲染。
示例代码:
单张图片 + 限制最大宽高(推荐写法):
(function () {
var doc = getCurrentDocument();
var imageUrl = doc.getItemValueAsString("图片字段");
if (imageUrl == null || imageUrl.trim().length === 0) {
return "<span style='color:#999;'>未设置图片</span>";
}
var maxWidth = 500;
var maxHeight = 400;
return "<img src='" + imageUrl + "' "
+ "style='max-width:" + maxWidth + "px;"
+ " max-height:" + maxHeight + "px;"
+ " width:auto; height:auto; display:block;'"
+ " onerror=\"this.style.display='none'\">";
})()
多张图片(附件字段是 JSON 数组)+ Flex 布局:
(function () {
var doc = getCurrentDocument();
var jsonText = doc.getItemValueAsString("图片");
if (jsonText == null || jsonText.trim().length === 0) {
return "<span style='color:#999;'>暂无图片</span>";
}
var JSONArray = Java.type('org.json.JSONArray');
var jsonArray = new JSONArray(jsonText);
var maxWidth = 300, maxHeight = 300;
var html = "<div style='display:flex; flex-wrap:wrap; gap:10px;'>";
for (var i = 0; i < jsonArray.length(); i++) { // Java 方法
var imgObj = jsonArray.getJSONObject(i);
var url = imgObj.getString("path");
html += "<img src='" + url + "' "
+ "style='max-width:" + maxWidth + "px;"
+ " max-height:" + maxHeight + "px;"
+ " width:auto; height:auto;"
+ " border:1px solid #ccc;'>";
}
html += "</div>";
return html;
})()
代码说明:
- CSS 关键三件套:
max-width+max-height+width:auto; height:auto;—— 限制上限的同时保持原始比例。 object-fit: contain;—— 进一步保证图片完整显示不裁剪。getCurrentDocument().getItemValueAsString("字段名")—— 从当前文档取图片 URL 字段。Java.type('org.json.JSONArray')—— 解析附件 JSON 数组(同第 1 节),遍历用jsonArray.length()(Java 方法)。
变体与扩展:
- 点击放大:外层包
<div onclick='window.open(url, "_blank")'>。 - 响应式:用 CSS 媒体查询
@media (max-width:768px){...}适配移动端。 - 缩略图列表 + 轮播:遍历数组时把第一张
display:block,其余display:none,前端再加切换按钮。 - 懒加载:
<img loading='lazy' ...>。
常见坑:
- 图片 URL 转义:URL 含
&时直接拼到 HTML 里会破坏属性解析,应先imageUrl.replace(/&/g, "&")。 onerror引号嵌套:HTML 里嵌 JS 字符串时引号要交替使用(外双内单或反之)。- 计算脚本不落库:返回的 HTML 只渲染不入库,不能依赖此脚本做数据持久化。
- 附件 JSON 解析方法名:本节用
org.json.JSONArray的构造函数,第 2 节用net.sf.json.JSONArray.fromObject——是两套不同 JSON 库,API 不可混用。
二维码控件内容¶
业务目标:在表单上动态生成二维码内容(URL 或文本),扫码后跳转或显示信息;内容可按当前文档/用户动态变化(如生成"签到码"、"作品编号链接")。
写在哪儿:表单域 → 表单 → 二维码控件 → 内容脚本(.form,属性"二维码内容脚本";Label FORM:FIELD_QR_CONTENT)。挂载点位置见 where-to-use/form/form-controls。
触发时机与上下文:表单打开时。可用环境变量:WebUser、CurrentDocument、RelateDocument、ParentDocument。
返回值契约:String——二维码内容。若是 URL,扫码后浏览器会跳转到此 URL;若是普通文本,扫码后显示该文本。
示例代码:
固定 URL:
按当前文档 ID 拼动态签到链接:
(function () {
var doc = getCurrentDocument();
var docId = doc.getId();
// 拼成带 docId 参数的签到链接,配合下一节"扫码回调"完成签到
return "https://app.example.com/qrcode/checkin?docId=" + docId;
})()
按当前用户 + 时间生成一次性签到码:
(function () {
var user = getWebUser();
var code = user.getId() + "|" + format(new Date(), "yyyyMMddHHmm");
return code;
})()
代码说明:
getCurrentDocument().getId()—— 取当前文档 ID,常用来拼业务参数。getWebUser()—— 取当前登录用户;.getId()取用户 ID。format(date, pattern)—— 日期格式化,给一次性签到码加时间戳。
变体与扩展:
- 生成业务编号二维码:
getItemValueAsString("流水号")直接返回。 - 配扫码回调:内容脚本生成扫码入口、回调脚本(见下一节)处理扫码后的业务逻辑——两者配合实现完整签到闭环。
- 加密签名 URL:对参数做签名(如 MD5)防止用户改 URL 伪造签到。
常见坑:
- 二维码内容为空:脚本忘了
return,或返回undefined,二维码渲染为空。 - URL 未转义:拼到 URL 里的参数含
&/=/中文 时要encodeURIComponent。 - 字符串比较用
===:根据返回值切换二维码内容时不要用.equals(→ GraalVM 差异)。
二维码扫码签到回调 + 重复校验¶
业务目标:用户用手机扫描表单上的二维码后,平台回调本脚本——校验当前用户是否在"签到人员"名单中、是否已签到过,然后写库并返回提示消息。典型用于会议签到、活动签到、培训打卡。
写在哪儿:表单域 → 表单 → 二维码控件 → 回调脚本(.form,属性依产品约定为"回调脚本",Label 平台未公开固定枚举;与 第 4 节 的"内容脚本"成对配置)。挂载点位置见 where-to-use/form/form-controls。
触发时机与上下文:用户扫码并命中回调时触发。可用 getCurrentDocument()(→ doc API)、getWebUser()(→ curruser API)。
返回值契约:JsMessage 对象——平台按消息类型(成功/提示/警告/错误)弹窗显示。也可返回 String,平台按普通提示处理。
示例代码:
(function () {
var user = getWebUser();
var userId = user.getId();
var doc = getCurrentDocument();
var docId = doc.getId();
// 已签到名单(形如 "u1;u2;u3;"),按 ; 拆成 JS 数组
var signedRaw = doc.getItemValueAsString("签到");
var signedList = (signedRaw == null || signedRaw === "") ? [] : signedRaw.split(";");
// 1. 重复校验:当前用户已在"签到"字段中
for (var i = 0; i < signedList.length; i++) { // JS 数组用 .length
if (signedList[i] === userId) {
return new JsMessage(2, "您已签到,无需重复签到!");
}
}
// 2. 资格校验:当前用户是否在"签到人员"名单中
var allowedRaw = doc.getItemValueAsString("签到人员");
var allowedList = (allowedRaw == null || allowedRaw === "") ? [] : allowedRaw.split(";");
var isAllowed = false;
for (var j = 0; j < allowedList.length; j++) {
if (allowedList[j] === userId) {
isAllowed = true;
break;
}
}
if (!isAllowed) {
return new JsMessage(4, "您不在本次签到名单中,签到失败!");
}
// 3. 写库:把当前用户追加到"签到"字段
var newSigned = (signedRaw == null ? "" : signedRaw) + userId + ";";
var sql = "update tlk_会议签到 set item_签到 = '" + newSigned.replace(/'/g, "''")
+ "' where id = '" + docId + "'";
updateByDSName("business", sql);
// 同步内存中的文档对象,避免后续脚本读到旧值
doc.findItem("签到").setValue(newSigned);
return new JsMessage(1, "签到成功!");
})()
代码说明:
JsMessage(type, text)—— 平台消息对象,type:1=成功、2=提示、3=警告、4=错误(与源素材约定一致)。也可改用MSGBOX工厂的createSuccess(text)/createInfo(text)/createWarning(text)/createAlert(text),语义更清晰。String.split(";")—— JS 字符串方法,返回 JS 数组,长度用.length(属性,无括号)。updateByDSName(dsName, sql)—— 跨数据源执行 update/insert/delete;表名前缀tlk_<表单名>,业务字段列名item_<字段名>。doc.findItem("字段名").setValue(value)—— 同步更新内存中的文档字段,避免回调内后续脚本读到旧值。- 字符串相等用
===(→ GraalVM 差异)。
变体与扩展:
- 签到时间落库:在表中加
item_签到时间列,update 时一起写入format(new Date(), "yyyy-MM-dd HH:mm:ss")。 - 签到人数统计:签到成功后
queryBySQL("select count(...) ...")统计并回写到主表"已签到人数"字段。 - 迟到判断:取当前时间与会议开始时间比较,超过则返回
JsMessage(3, "您已迟到")。 - 不写 SQL 改用文档 API:
doc.findItem("签到").setValue(newSigned); getDocumentProcess().doUpdate(doc);替代裸 SQL,避免表名/字段名硬编码。
常见坑:
split返回的是 JS 数组——长度用.length(属性),**不要**写.size()或.length()。- SQL 注入:把字段值原样拼进 SQL 极易被注入,生产环境务必先
replace(/'/g, "''")或改用文档 API(doUpdate)。 - 表前缀与字段列名:平台业务表为
tlk_<表单名>、字段列为item_<字段名>;写错 SQL 静默失败不报错。 - 数据源名大小写:
updateByDSName第一个参数须与平台数据源配置名完全一致。 JsMessage类型枚举:½/¾ 的语义在不同版本/素材中描述略有差异;上线前用println打印一次确认。- 重复签到竞态:高并发下两个请求同时读到未含本用户的"签到"字段,会出现覆盖——可加锁或在 SQL 里
where item_签到 not like '%<userId>;%'兜底。
上传权限脚本族速查¶
上传控件的"下载/删除/重命名/预览/在线编辑/新版本/版本比较/版本还原/水印"9 个挂载点**属性同构**(都是布尔权限或字符串水印)、写法一致,没必要逐条展开 8 字段模板,合并为一张速查表。素材:
agent-skills-usage/skills/iscript-usage/form.md。
业务目标:对上传控件(附件/图片)的每个操作按钮**单独**控制权限——谁能下载、谁能删除、谁能在线编辑、谁能还原旧版本;以及对预览/下载行为打水印。
写在哪儿:表单域 → 表单 → 上传控件 → 各操作脚本(.form,Label FORM:FIELD_UPLOAD_*,属性名见下表)。挂载点位置见 where-to-use/form/form-controls。
触发时机与上下文:用户点击上传控件对应的操作按钮时(或控件渲染时取权限)。可用 getCurrentDocument()、getWebUser()。
返回值契约:前 8 个权限脚本——Boolean:true = 允许该操作、false = 禁用该按钮(隐藏或置灰)。水印脚本——String:水印文案。
速查表:
| Label | 属性名 | 返回 | 典型用法 |
|---|---|---|---|
FORM:FIELD_UPLOAD_DOWNLOAD |
downloadscript |
Boolean |
仅"下载员"角色允许下载 |
FORM:FIELD_UPLOAD_REMOVE |
deletescript |
Boolean |
保存后不可删,按 getCurrentDocument().isFirstNode() 判断 |
FORM:FIELD_UPLOAD_RENAME |
renamescript |
Boolean |
仅上传者本人允许重命名 |
FORM:FIELD_UPLOAD_PREVIEW |
previewscript |
Boolean |
公开链接分享时禁用预览 |
FORM:FIELD_UPLOAD_ONLINEEDIT |
editscript |
Boolean |
仅"编辑员"角色允许在线编辑 |
FORM:FIELD_UPLOAD_VERSION_RENEW |
newversionscript |
Boolean |
修改文档时强制上传新版本 |
FORM:FIELD_UPLOAD_VERSION_COMPARE |
versioncomparescript |
Boolean |
法务审批场景允许版本对比 |
FORM:FIELD_UPLOAD_VERSION_REVERT |
restorescript |
Boolean |
仅管理员允许还原旧版本 |
FORM:FIELD_UPLOAD_WATERMARK |
watermarkscript |
String |
预览/下载时打当前用户姓名水印,溯源防泄密 |
最短示例(前 8 个布尔权限通用模板):
// 仅"管理员"角色允许该操作
(function () {
var roles = getWebUser().getRoles(); // Java Collection
if (roles == null || roles.size() === 0) {
return false;
}
for (var it = roles.iterator(); it.hasNext();) {
var r = it.next();
if (r.getName() === "管理员") {
return true;
}
}
return false;
})()
水印脚本示例(FORM:FIELD_UPLOAD_WATERMARK):
(function () {
// 拼上用户名 + 时间,作为附件预览/下载水印
return getWebUser().getName() + " " + format(new Date(), "yyyy-MM-dd HH:mm");
})()
代码说明:
getWebUser().getRoles()—— 返回 JavaCollection,用.iterator()遍历、.size()取长度(→ GraalVM 差异 → Java List.size()vs JS 数组.length)。format(date, pattern)—— 日期格式化。- 字符串相等用
===,**不要**用r.getName().equals("管理员")(→ GraalVM 差异)。
变体与扩展:
- 按字段状态联动:
var st = getCurrentDocument().getItemValueAsString("状态"); return st === "草稿";—— 仅草稿状态可删/改名。 - 按流程节点:
getCurrentDocument().isFirstNode()—— 仅首节点(起草)允许删/重命名。 - 水印按角色隐藏:管理员返回
""(不打水印),其他角色返回用户名。
常见坑:
getRoles()是 JavaCollection——用.size()取长度,不要写.length;遍历用.iterator(),不要写roles[i]。- 属性名拼写:上传控件属性多为**全小写脚本字段名**(
downloadscript、deletescript),不要写成 camelCase。 - 布尔语义方向:
true= 允许操作(与"隐藏脚本 true=隐藏"的方向**相反**,容易记混)——上传权限族是"允许"语义,隐藏/只读族是"生效"语义。 - 水印脚本返回空串:
""视为不打水印,不要返回null。
相关场景¶
- → 附件上传数量校验 —— 校验脚本里复用本章第 1 节的 JSON 解析模式。
- → 字段只读/隐藏 —— 与本章第 6 节上传权限族同属"权限脚本"大家族,但契约方向不同(隐藏脚本
true=隐藏、上传权限true=允许),切换场景时务必注意。 - → 附录 A 工具函数 —— JSON 创建/解析的更多模式(
createOptions().toJSON()、Packages.net.sf.json.JSONObject等)。