权限与可见性控制¶
一句话定位:本章解决"谁能在什么时候看到/编辑什么"——通过返回布尔的脚本统一控制字段、页签、按钮、视图列、表单入口甚至 Widget 的可见与可编辑性。
章首契约速记¶
本章绝大多数脚本返回 Boolean,且方向**高度一致**:
| Label | 含义 | true |
false |
|---|---|---|---|
FORM:FIELD_HIDE / FIELD_PRINT_HIDE |
字段隐藏 | 隐藏 | 可见 |
FORM:FIELD_READONLY |
字段只读 | 只读 | 可编辑 |
ACTIVITY:HIDDEN / READONLY |
操作按钮 | 隐藏 / 只读 | 可见 / 可点 |
VIEW:COLUMN_HIDDEN |
视图列 | 隐藏 | 可见 |
FORM:OPEN / FORM:EDIT |
表单入口 | **允许**打开 / 编辑 | **禁止**打开 / 编辑 |
⚠️ 唯一易错点:FORM:OPEN / FORM:EDIT 的方向与上面所有"隐藏/只读"脚本**相反**——它们是"允许"语义:返回 false 才是禁止。详见 § 表单入口开关。
字段只读 / 隐藏(标杆场景)¶
业务目标:根据表单上某个字段的值或当前用户角色,让某个控件在打开表单时变灰只读或直接消失,避免误填误改。
写在哪儿:表单域 → 表单控件 → 选中字段 → 只读条件 / 隐藏条件(.form,属性 readonlyScript / hiddenScript,Label FORM:FIELD_READONLY / FORM:FIELD_HIDE)。
触发时机与上下文:打开表单时触发。可用环境变量:WebUser(当前登录用户)、CurrentDocument(当前文档)、RelateDocument、ParentDocument。可调用 getCurrentDocument() 取当前文档、getWebUser() 取当前用户。
返回值契约:Boolean。true = 只读 / 隐藏;false = 可编辑 / 可见。空值视同 false。
示例代码:
// 只读脚本 / 隐藏脚本共用同一套写法,只是挂的属性不同
// 当"是否启动"字段值为 "02" 时,当前字段只读(或隐藏)
(function () {
var doc = getCurrentDocument();
var status = doc.getItemValueAsString("IsStartup");
// 注意:GraalVM 下用 === 比较,不要写 "02".equals(status)
return status === "02";
})()
代码说明:
getCurrentDocument()取当前打开的文档对象(详见iscript-guid/api→ currdoc)。doc.getItemValueAsString("字段名")按字段名取字符串值(同上,CURDOC 命名空间下)。getWebUser()取当前用户,常用于按角色/姓名判断(详见iscript-guid/api→ curruser)。- 字符串比较必须用
===/!==,不能用.equals()——见 前言 GraalVM 差异。
变体与扩展:
- 按角色判断只读:先
getRoleIdByName("角色名")取角色 id,再遍历getWebUser().getRoles()比对(参考 § 字段打印时隐藏(按角色) 的角色遍历模板)。 - 只读 vs 隐藏的选择:只读保留位置、防误改;隐藏则彻底不渲染。提交后留痕场景适合只读,敏感字段适合隐藏。
- 同一套逻辑同时控制多个字段:在每个字段脚本里调用同一个公共函数(用
#include引入扩展函数库,详见附录 A)。
常见坑:
- 误把"反向逻辑"写成
return status !== "02"来表达"等于 02 时隐藏"——记住契约是true= 隐藏,方向写反会导致字段在该隐藏时反而显示。 - 用
"02".equals(status)旧写法在 GraalVM 下会抛错;改成status === "02"(详见 GraalVM 差异「字符串相等:用===/!==,禁用 `.e」)。 - 只读脚本不阻止后台
setValue/ 操作前后置脚本写值——它只是 UI 约束;要在数据层兜底,请在操作前置或校验脚本里再判一次。 - 隐藏≠权限:前端隐藏的字段,用户通过 API/导入仍可能写入;涉密场景请配合角色数据权限与字段级 ACL。
字段打印时隐藏(按角色)¶
业务目标:屏幕上能看到的字段,打印(导出 PDF / 打印预览)时按角色隐藏——例如普通员工打印时不显示成本价,管理员打印时才显示。
写在哪儿:表单域 → 表单控件 → 选中字段 → 打印时隐藏条件(.form,属性 hiddenPrintScript,Label FORM:FIELD_PRINT_HIDE)。
触发时机与上下文:点击打印按钮、生成打印 HTML 时触发。可用环境变量:WebUser、CurrentDocument、RelateDocument、ParentDocument。
返回值契约:Boolean。true = 打印时隐藏;false = 打印时可见。
示例代码:
// 当前用户不具备"系统管理员"角色时,打印隐藏该字段
(function () {
var adminRoleId = getRoleIdByName("系统管理员"); // 取角色 id
var roles = getWebUser().getRoles(); // 当前用户的角色集合(Java Collection)
var isHidden = true; // 默认隐藏(保守策略)
if (roles != null && roles.size() > 0) {
for (var it = roles.iterator(); it.hasNext();) {
var roleVO = it.next();
if (adminRoleId === roleVO.getId()) {
isHidden = false; // 命中管理员 → 打印可见
break;
}
}
}
return isHidden;
})()
代码说明:
getRoleIdByName("角色名")按名称取角色 id(详见iscript-guid/api→ usercenter 的USERCENTER.getRoleIdByName)。getWebUser().getRoles()返回java.util.Collection,须用.size()取长度、.iterator()遍历(见 GraalVM 差异「Java List.size()vs JS 数组」)。roleVO.getId()取角色 id,与getRoleIdByName返回值比较;用===。- 注意循环里
break提前退出——找到一个匹配即可定论。
变体与扩展:
- 反过来"按角色显示":把默认值改为
false、命中改为true即可(注意语义仍是"是否隐藏")。 - 多角色白名单:把允许的角色名放进数组
["系统管理员", "财务"],遍历用户角色看是否命中其中任一。 - 配合
FORM:TOHTML(打印 HTML 转换脚本)做更复杂的打印版渲染。
常见坑:
getRoles()返回的是 Java 集合,写roles.length会得到undefined——必须用.size()。- 用
getRoleIdByName取的 id 是字符串,与roleVO.getId()比较时两边类型应一致;遇到不一致用String(roleVO.getId()) === adminRoleId兜底。 - 打印隐藏只对平台打印生效;如果用浏览器原生打印(Ctrl+P),需要额外配合 CSS(参考 § 仅 PC / 移动端显示 的媒体查询思路)。
表单是否可打开 / 可编辑(FORM:OPEN / FORM:EDIT)¶
业务目标:根据当前用户/文档状态,决定这份表单能不能被打开、打开后能不能编辑——例如"已归档"单据对所有人只读、特定用户被拉黑后根本打不开。
写在哪儿:表单域 → 表单 → 基本 → 是否可打开脚本 / 是否可编辑脚本(.form,属性 isopenablescript / iseditablescript,Label FORM:OPEN / FORM:EDIT)。
触发时机与上下文:打开表单时触发。可用环境变量:WebUser、CurrentDocument、RelateDocument、ParentDocument。
返回值契约:Boolean。⚠️ 方向与隐藏/只读脚本相反:
FORM:OPEN:true= **允许**打开,false= **禁止**打开(空值≈允许)。FORM:EDIT:true= **允许**编辑,false= **禁止**编辑(即只读)。
记忆口诀:OPEN/EDIT 是"放行"语义——返回
false才拦下;其余 HIDE/READONLY 是"屏蔽"语义——返回true才生效。
示例代码:
// FORM:OPEN —— 用户名为 "smart" 时禁止打开表单
(function () {
var name = getWebUser().getName();
// 等价于:name 不是 "smart" 才放行
return name !== "smart";
})()
// FORM:EDIT —— 文档处于"已归档"状态时禁止编辑(即只读)
(function () {
var status = getCurrentDocument().getItemValueAsString("状态");
// 已归档 → 返回 false → 禁止编辑
return status !== "03";
})()
代码说明:
getWebUser().getName()取当前用户登录名(详见iscript-guid/api→ curruser)。getCurrentDocument().getItemValueAsString("字段名")取文档字段值(详见iscript-guid/api→ currdoc)。- 注意返回的是"放行"布尔,所以条件要写成"非禁止条件"——
name !== "smart"、status !== "03"。
变体与扩展:
- 多条件组合:用
&&/||串联,例如return name !== "smart" && status !== "03";(既不被拉黑、又未归档才允许)。 - 按角色放行:调用
getWebUser().getRoles()遍历,命中任一允许角色返回true。 - OPEN 与 EDIT 配合:OPEN 禁止=直接打不开;EDIT 禁止=能看不能改。需要"能看不能改"用 EDIT,需要"完全拒访"用 OPEN。
- 配合 操作前置脚本 在视图按钮层再做一次判断,避免用户绕过表单入口。
常见坑:
- 🚨 方向写反**是最常见的坑:把"smart 禁止打开"误写成
if (name === "smart") return true;——但FORM:OPEN的true是**允许,结果反而把 smart 放行了。正确写法是直接return name !== "smart";。 - 返回空(不写
return)≈ 允许打开/编辑,不要依赖"空=禁止"做安全控制——要禁止必须显式return false;。 FORM:OPEN返回false时,平台通常直接跳转或弹提示;如果是嵌入式打开(子表/关联视图),可能影响容器渲染,务必测试目标入口。- 与字段级
readonlyScript区别:FORM:EDIT 是整表只读开关;字段级只读只让单个字段变灰。整表只读时字段脚本仍会执行,可用于在只读状态下做差异化提示。
视图列隐藏(按用户)¶
业务目标:视图(列表)中某列只对特定用户/角色可见,其他人看不到——例如"成本价"列只给财务看。
写在哪儿:视图域 → 视图 → 列编辑 → 选中列 → 隐藏条件(.view,属性 hiddenScript,Label VIEW:COLUMN_HIDDEN)。
触发时机与上下文:打开视图时触发。可用环境变量:CurrentDocument(当前行的文档对象,逐行求值)、WebUser。注意:视图列脚本在**每一行**渲染时都会求值。
返回值契约:Boolean。true = 该列隐藏(对所有行生效,不是逐行隐藏);false = 可见。
示例代码:
// 当前用户为 "张三" 时隐藏该列(典型场景:张三是外部协作账号,不应看到此列)
(function () {
var userName = getWebUser().getName();
return userName === "张三";
})()
代码说明:
- 与字段隐藏脚本几乎是同一套写法,只是挂在视图列上。
getWebUser()在视图上下文同样可用(详见iscript-guid/api→ curruser)。- 列隐藏是"整列级别"——一旦返回
true,整列对所有行都不渲染;如需"逐行根据数据决定是否显示某单元格内容",请改用列值脚本(VIEW:COLUMN_VALUE,返回该单元格的显示内容),见 视图过滤与列控制。
变体与扩展:
- 按角色隐藏:遍历
getWebUser().getRoles()命中财务角色才显示(参考 § 字段打印时隐藏(按角色) 的遍历模板)。 - 与
VIEW:SQL_FILTER(见视图过滤与列控制)区别:列隐藏是"不显示该列",过滤是"不显示某些行"——两者正交。 - 配合查询头默认值脚本实现"按查询条件动态显隐列"。
常见坑:
- 视图列脚本逐行求值,不要在脚本里做重查询(如
queryBySQL),否则 N 行数据触发 N 次 SQL,列表慢到不可用;改在加载前算好,或用getCurrentDocument()上的字段值。 - 隐藏列在导出 Excel 时默认也会被隐藏——如需"屏幕隐藏但导出包含",请使用平台导出配置或专用的导出脚本。
- 与角色数据权限冲突时:列隐藏是 UI 层;底层 SQL 仍可能查出该字段。涉密场景需配合数据源权限。
仅 PC / 移动端显示(设备检测)¶
业务目标:在计算脚本/HTML 内容脚本里返回一段 HTML,仅当用户用 PC 浏览器(或反之仅移动端)访问时才显示某块内容——例如"在 PC 端展示一个复杂表格,移动端折叠成卡片"。
写在哪儿:表单域 → 计算脚本 / HTML 内容脚本(.form,属性 calculateScript / HTML 脚本属性);也可挂在视图列值脚本或 Widget 内容脚本里返回 HTML。本场景无独立 Label,本质是"返回 HTML 字符串 + 内嵌 <script>/<style>"的写法。
触发时机与上下文:控件渲染时触发。可用环境变量:WebUser、CurrentDocument、RelateDocument、ParentDocument。
返回值契约:String(一段 HTML,内嵌前端 JS/CSS 由浏览器执行做最终显隐)。
示例代码:
// 方式 1:JS 检测 User Agent —— 仅 PC 端显示
(function () {
var html = "<script>" +
"function isMobileDevice() {" +
" return /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i" +
" .test(navigator.userAgent);" +
"}" +
"if (!isMobileDevice()) {" +
" document.getElementById('pcContent').style.display = 'block';" +
"}" +
"</script>" +
"<div id='pcContent' style='display: none;'>这是仅在 PC 端显示的内容</div>";
return html;
})()
// 方式 2:CSS 媒体查询 —— 屏幕宽度 ≤ 768px 时隐藏(推荐,无需 JS)
(function () {
var html = "<style>" +
"@media (max-width: 768px) { .pc-only { display: none !important; } }" +
"@media (min-width: 769px) { .pc-only { display: block; } }" +
"</style>" +
"<div class='pc-only'>这是仅在 PC 端显示的内容</div>";
return html;
})()
代码说明:
- 平台没有"设备检测"专用 API,靠返回前端 HTML 让浏览器自己判断——本质是"服务端脚本生成视图层 HTML",前端 JS/CSS 完成最终显隐。
- 字符串拼接时,浏览器侧的
navigator.userAgent、window.innerWidth是前端 JS,与 iScript 的 GraalVM 环境无关。 queryBySQL/getCurrentDocument()等平台 API 仍可在同一脚本里调,把数据塞进 HTML 一起返回(如把查询结果渲染成 PC 端表格)。
变体与扩展:
- 仅移动端显示:把判断条件取反,或媒体查询改为
@media (max-width: 768px) { .m-only { display: block; } }。 - 触摸设备检测:
'ontouchstart' in window || navigator.maxTouchPoints > 0。 - 响应式同时给两套布局:HTML 里同时输出
pcContent和mobileContent两个 div,前端按设备切换display。 - 在 Widget 内容脚本(
WIDGET:CONTENT,定时任务、API 与 Widget)里用同一手法,让首页 Widget 按设备呈现不同内容。
常见坑:
- ⚠️ 这里的
isMobileDevice()/isPC()是**返回到前端的浏览器 JS**,不是 iScript;不要把 GraalVM 规则(如===)误套到内嵌 JS 上——前端 JS 用什么语法都行,关键是别把它和 iScript 主逻辑混。 - iScript 字符串里的
"</script>"拼接别破坏外层脚本——本场景把 HTML 作为字符串return,平台会原样塞进页面,不会冲突。 - User Agent 可被伪造,不能作为安全控制;仅用于体验差异化。涉密内容必须用服务端权限(
FORM:OPEN、字段隐藏脚本、角色数据权限)兜底。 - 字符串拼接超长时建议改用数组
push后join(""),提升可读性。
Widget 对谁可见(WIDGET:USERSCRIPT)¶
业务目标:首页 Widget(小组件)按业务规则只对特定人群可见——例如"待办统计 Widget 只给部门经理看"。
写在哪儿:Widget 域 → Widget 属性 → 用户脚本(.widget,属性 userScript,需 authMode=2,Label WIDGET:USERSCRIPT)。
触发时机与上下文:判断 Widget 是否对当前用户可见时触发;脚本返回的集合决定可见用户范围。可用环境变量:WebUser。
返回值契约:用户 id 集合或 UserVO 集合(java.util.Collection)。集合中包含当前用户 = 可见;不包含 = 不可见。
注意:这里不再是布尔,而是"白名单集合"——把允许看到的人塞进去,平台判断当前用户是否在集合中。
示例代码:
// 仅当前用户自己可见(最小可用示例)
(function () {
var ArrayList = Java.type('java.util.ArrayList');
var list = new ArrayList();
list.add(getWebUser().getId());
return list;
})()
// 按"部门经理"角色可见:取角色下所有用户 id 加入集合
(function () {
var ArrayList = Java.type('java.util.ArrayList');
var list = new ArrayList();
var managerRoleId = getRoleIdByName("部门经理");
var users = getUsersByRoleId(managerRoleId); // 返回 Collection<UserVO>
if (users != null && users.size() > 0) {
for (var it = users.iterator(); it.hasNext();) {
var u = it.next();
list.add(u.getId());
}
}
return list;
})()
代码说明:
Java.type('java.util.ArrayList')是 GraalVM 推荐的取 Java 类方式(详见 GraalVM 差异「取 Java 类:用Java.type(...),」),**禁止**裸写new java.util.ArrayList()。getWebUser().getId()取当前用户 id(详见iscript-guid/api→ curruser)。getUsersByRoleId(roleId)按角色取用户集合(详见iscript-guid/api→ usercenter 的USERCENTER.getUsersByRoleId)。- 返回的集合元素既可以是 id 字符串,也可以是
UserVO对象——平台会自动适配。
变体与扩展:
- 多角色并集:依次取多个角色的用户列表,全部
add进同一集合。 - 按部门:用
getUsersByDptId(deptId)替换角色查询。 - 动态从 SQL 取白名单:
queryBySQL("select userid from ...")后遍历结果集收集 id。 - 与 Widget 内容脚本(
WIDGET:CONTENT,定时任务、API 与 Widget)配合:USERSCRIPT 控制对谁显示,CONTENT 控制显示什么。
常见坑:
- 🚨 不要写
new java.util.ArrayList()——GraalVM 下裸java.util.X不可用,必须Java.type('java.util.ArrayList')后new(详见 GraalVM 差异「取 Java 类:用Java.type(...),」)。 authMode必须设为2(脚本模式),USERSCRIPT 才会生效;保持默认值时平台按其它可见范围规则判断。- 返回
null或空集合等于"谁也看不到"——Widget 直接消失;如要"对所有人可见",直接不挂脚本或返回包含全部用户的集合。 - 大用户量场景下,把全公司用户塞进集合性能很差;优先用"角色/部门"维度的内置可见范围,脚本只做精细化补充。
属性同构场景速查(合并表)¶
下面这些场景与上面展开的场景**契约完全一致**(true = 隐藏 / 只读),只是挂在不同的元素上,逐条展开 8 字段会重复——合并为一张速查表:
| 场景 | 域 → 路径 | Label | 属性 | 返回值 | 备注 |
|---|---|---|---|---|---|
| 选项卡页签只读 | 表单 → 选项卡 → 页签 → 只读条件 | FORM:FIELD_READONLY(页签变体) |
readOnlyScript(Tab 子项 camelCase) |
Boolean:true=只读 |
与字段只读同构;典型用法:用户为 "张三" 时 return false; 放行编辑,否则 return true; 只读 |
| 选项卡页签打印时隐藏 | 表单 → 选项卡 → 页签 → 打印时隐藏条件 | FORM:FIELD_PRINT_HIDE(页签变体) |
hiddenPrintScript |
Boolean:true=打印时隐藏 |
整页签在打印时消失;与字段打印隐藏写法一致 |
| 操作按钮只读 | 视图 → 操作 → 编辑按钮 → 只读条件 | ACTIVITY:READONLY |
readonlyScript |
Boolean:true=只读/不可点 |
工具栏渲染时求值;典型:return getWebUser().getName() === "张三"; |
| 操作按钮隐藏 | 视图 → 操作 → 编辑按钮 → 隐藏条件 | ACTIVITY:HIDDEN |
hiddenScript |
Boolean:true=隐藏 |
同上;与列隐藏、字段隐藏完全同构 |
通用写法模板:
(function () {
var userName = getWebUser().getName();
// 想让"张三"命中(只读/隐藏)就返回 true,否则 false
return userName === "张三";
})()
详见 iscript-usage → activity 与 where-to-use → view-actions、where-to-use → form-controls 的「选项卡」小节。
相关场景¶
- 字段隐藏/只读只是 UI 层;要彻底拒绝访问,配合
FORM:OPEN与角色数据权限。 - 视图列控制全套(隐藏/列值/列标签)见 视图过滤与列控制。
- 上传权限脚本族(下载/删除/重命名/预览/在线编辑/版本)返回值也是布尔,与本章同思路,但专用于附件控件,归在文件附件与二维码「文件附件与二维码」。
- 评论隐藏(
FORM:COMMENT_HIDE,true=隐藏)也是同构契约,按需查阅 iscript-usage → form。