跳转至

权限与可见性控制

一句话定位:本章解决"谁能在什么时候看到/编辑什么"——通过返回布尔的脚本统一控制字段、页签、按钮、视图列、表单入口甚至 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(当前文档)、RelateDocumentParentDocument。可调用 getCurrentDocument() 取当前文档、getWebUser() 取当前用户。

返回值契约Booleantrue = 只读 / 隐藏;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 时触发。可用环境变量:WebUserCurrentDocumentRelateDocumentParentDocument

返回值契约Booleantrue = 打印时隐藏;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 → usercenterUSERCENTER.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)。

触发时机与上下文:打开表单时触发。可用环境变量:WebUserCurrentDocumentRelateDocumentParentDocument

返回值契约Boolean。⚠️ 方向与隐藏/只读脚本相反

  • FORM:OPENtrue = **允许**打开,false = **禁止**打开(空值≈允许)。
  • FORM:EDITtrue = **允许**编辑,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:OPENtrue 是**允许,结果反而把 smart 放行了。正确写法是直接 return name !== "smart";
  • 返回空(不写 return)≈ 允许打开/编辑,不要依赖"空=禁止"做安全控制——要禁止必须显式 return false;
  • FORM:OPEN 返回 false 时,平台通常直接跳转或弹提示;如果是嵌入式打开(子表/关联视图),可能影响容器渲染,务必测试目标入口。
  • 与字段级 readonlyScript 区别:FORM:EDIT 是整表只读开关;字段级只读只让单个字段变灰。整表只读时字段脚本仍会执行,可用于在只读状态下做差异化提示。

视图列隐藏(按用户)

业务目标:视图(列表)中某列只对特定用户/角色可见,其他人看不到——例如"成本价"列只给财务看。

写在哪儿:视图域 → 视图 → 列编辑 → 选中列 → 隐藏条件(.view,属性 hiddenScript,Label VIEW:COLUMN_HIDDEN)。

触发时机与上下文:打开视图时触发。可用环境变量:CurrentDocument(当前行的文档对象,逐行求值)、WebUser。注意:视图列脚本在**每一行**渲染时都会求值。

返回值契约Booleantrue = 该列隐藏(对所有行生效,不是逐行隐藏);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>"的写法。

触发时机与上下文:控件渲染时触发。可用环境变量:WebUserCurrentDocumentRelateDocumentParentDocument

返回值契约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.userAgentwindow.innerWidth 是前端 JS,与 iScript 的 GraalVM 环境无关。
  • queryBySQL / getCurrentDocument() 等平台 API 仍可在同一脚本里调,把数据塞进 HTML 一起返回(如把查询结果渲染成 PC 端表格)。

变体与扩展

  • 仅移动端显示:把判断条件取反,或媒体查询改为 @media (max-width: 768px) { .m-only { display: block; } }
  • 触摸设备检测:'ontouchstart' in window || navigator.maxTouchPoints > 0
  • 响应式同时给两套布局:HTML 里同时输出 pcContentmobileContent 两个 div,前端按设备切换 display
  • 在 Widget 内容脚本(WIDGET:CONTENT,定时任务、API 与 Widget)里用同一手法,让首页 Widget 按设备呈现不同内容。

常见坑

  • ⚠️ 这里的 isMobileDevice() / isPC() 是**返回到前端的浏览器 JS**,不是 iScript;不要把 GraalVM 规则(如 ===)误套到内嵌 JS 上——前端 JS 用什么语法都行,关键是别把它和 iScript 主逻辑混。
  • iScript 字符串里的 "</script>" 拼接别破坏外层脚本——本场景把 HTML 作为字符串 return,平台会原样塞进页面,不会冲突。
  • User Agent 可被伪造,不能作为安全控制;仅用于体验差异化。涉密内容必须用服务端权限(FORM:OPEN、字段隐藏脚本、角色数据权限)兜底。
  • 字符串拼接超长时建议改用数组 pushjoin(""),提升可读性。

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;
})()

代码说明

变体与扩展

  • 多角色并集:依次取多个角色的用户列表,全部 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) Booleantrue=只读 与字段只读同构;典型用法:用户为 "张三" 时 return false; 放行编辑,否则 return true; 只读
选项卡页签打印时隐藏 表单 → 选项卡 → 页签 → 打印时隐藏条件 FORM:FIELD_PRINT_HIDE(页签变体) hiddenPrintScript Booleantrue=打印时隐藏 整页签在打印时消失;与字段打印隐藏写法一致
操作按钮只读 视图 → 操作 → 编辑按钮 → 只读条件 ACTIVITY:READONLY readonlyScript Booleantrue=只读/不可点 工具栏渲染时求值;典型:return getWebUser().getName() === "张三";
操作按钮隐藏 视图 → 操作 → 编辑按钮 → 隐藏条件 ACTIVITY:HIDDEN hiddenScript Booleantrue=隐藏 同上;与列隐藏、字段隐藏完全同构

通用写法模板:

(function () {
  var userName = getWebUser().getName();
  // 想让"张三"命中(只读/隐藏)就返回 true,否则 false
  return userName === "张三";
})()

详见 iscript-usage → activitywhere-to-use → view-actionswhere-to-use → form-controls 的「选项卡」小节。

相关场景

  • 字段隐藏/只读只是 UI 层;要彻底拒绝访问,配合 FORM:OPEN 与角色数据权限。
  • 视图列控制全套(隐藏/列值/列标签)见 视图过滤与列控制
  • 上传权限脚本族(下载/删除/重命名/预览/在线编辑/版本)返回值也是布尔,与本章同思路,但专用于附件控件,归在文件附件与二维码「文件附件与二维码」。
  • 评论隐藏(FORM:COMMENT_HIDEtrue=隐藏)也是同构契约,按需查阅 iscript-usage → form