跳转至

视图过滤与列控制

面向已经会把表单脚本挂在控件上的开发者,把"打开视图时让平台按我的意思查数据 / 显示列 / 控按钮"这一类需求集中讲清。本章重点有三:

  1. 三种过滤模式的返回串形态完全不同——DQL 返回带 $ 前缀系统字段的 DQL 串、SQL 返回带 DOMAINID 的标准 SQL、存储过程返回 call xxx
  2. **列值脚本、列标签、列隐藏**是三个不同挂载点,不要混淆。
  3. **视图按钮(Activity)的前后置脚本**与表单值脚本返回契约不同:前置返回字符串即拦截后续动作。

跨章约定:默认包装为 IIFE (function () { ... return ...; })();字符串比较用 ===/!==;JS 字符串长度用 s.length(属性),Java 集合用 .size()。详见 GraalVM 差异

三种过滤模式速查

视图「数据」节点提供三种互斥的过滤模式,由 editMode 决定走哪一个脚本属性——只有当前模式对应的属性才会被执行

editMode Label 属性 返回串形态 关键约定
01 VIEW:DQL_FILTER filterScript DQL 表达式,系统字段前加 $(如 $formname<$作者$> 用平台 DQL 语法,不是 SQL
02 VIEW:SQL_FILTER sqlFilterScript 完整 select SQL 语句 必须 select domainid, ...,平台表带 DOMAINID 过滤;表单表前缀 tlk_、字段前缀 item_
03 VIEW:PROCEDURE_FILTER procedureFilterScript call 存储过程名(可带参数) 存储过程须返回结果集

模式切换:在视图设计器「数据」页签顶部选择 DQL / SQL / 存储过程其一;写错模式的属性(比如 SQL 模式下写 filterScript)不会被执行。


用 DQL 过滤视图数据

业务目标:打开视图时按当前用户、当前域或查询表单输入动态过滤行;不想写完整 SQL,用平台 DQL 表达式即可。

写在哪儿:视图 → 数据 → 代码(DQL),属性 filterScripteditMode=01,Label VIEW:DQL_FILTER。挂载点说明见 ../basic/where-to-use/view/view-data.md

触发时机与上下文:每次打开视图、翻页或刷新时触发。可用环境变量:WebUsergetWebUser())、getCurrentDocument()(这里指**查询表单**临时文档,doc-functions.md)。

返回值契约:String,一条 DQL 表达式。系统字段前加 $,如 $formname<$创建人$>;业务字段直接写中文角括号 <>item_字段名(视平台版本)。空串 "" 视为不过滤。

示例代码

// 按查询表单输入动态拼接 DQL:未填的字段不参与过滤
(function () {
  var doc = getCurrentDocument(); // 视图的"查询表单"临时文档
  var val1 = doc.getItemValueAsString("val1"); // 查询表单字段1
  var val2 = doc.getItemValueAsString("val2"); // 查询表单字段2

  // DQL 系统字段必须以 $ 开头:$formname 指表单名
  var dql = "$formname = 'tlk_LeaveRequest'";

  // 业务字段用 item_ 前缀(DQL 也会自动识别 <> 中文角括号写法)
  if (val1 !== null && val1.trim().length > 0) {
    dql += " and item_字段1 like '" + val1 + "%'";
  }
  if (val2 !== null && val2.trim().length > 0) {
    dql += " and item_字段2 = '" + val2 + "'";
  }
  return dql;
})()

代码说明: - getCurrentDocument().getItemValueAsString(...):在视图过滤脚本里拿到的是**查询表单**临时文档(用户在查询头输入的值),不是数据行文档。详见 doc-functions.md。 - 字符串判空用 s.trim().length > 0GraalVM 差异.length 是属性,不是方法)。 - 比较用 !==,**不要**写成 .equals(...)

变体与扩展: - 限定当前用户:dql += " and <$创建人$> = '" + getWebUser().getId() + "'"。 - 限定当前域:DQL 默认按域隔离,一般无需手写。 - 按视图参数过滤:用 getParameter("_keyword") 取 URL/请求参数,再拼到 DQL。

常见坑: - 把 SQL 写进 filterScript(DQL 模式)——平台不会执行 select ...,必须用 DQL 语法。两种模式切换见速查表。 - 忘记系统字段前缀 $formname = '...' 不会报错但过滤不生效,正确是 $formname = '...'。 - 字符串比较误用 .equals():GraalVM JS 中 .equals 不是 JS 字符串方法。详见 GraalVM 差异


用 SQL 过滤视图数据(多字段动态拼接)

业务目标:DQL 表达力不够(要 join、要聚合、要子查询),直接写 SQL;按当前用户、URL 参数、查询表单输入动态拼接 where

写在哪儿:视图 → 数据 → 代码(SQL),属性 sqlFilterScripteditMode=02,Label VIEW:SQL_FILTER

触发时机与上下文:每次打开视图、翻页或刷新时触发。可用环境变量同「用 DQL 过滤视图数据」:WebUsergetCurrentDocument()(查询表单临时文档)、getParameter(...)

返回值契约:String,一条完整的 select SQL。硬约定: - select 列表里**必须包含 domainid 字段**——平台靠它做数据隔离,缺失会导致跨域数据泄漏或查询失败。 - 表单表名前缀 tlk_,业务字段前缀 item_(如 tlk_LeaveRequestitem_申请人)。 - 系统表(t_usert_departmentt_flowstatert 等)本身带 DOMAINID,过滤时记得带上。

示例代码

// 按查询表单输入 + 当前用户动态拼接 SQL
(function () {
  var doc = getCurrentDocument();        // 查询表单临时文档
  var number = doc.getItemValueAsString("_number");     // 单号(模糊)
  var start  = doc.getItemValueAsDate("_startDate");    // 起始日期
  var end    = doc.getItemValueAsDate("_endDate");      // 结束日期
  var userId = getWebUser().getId();

  // domainid 必选;item_ 字段为表单业务字段
  var sql = "select domainid, id, item_申请人, item_单号, item_申请日期 "
          + "from tlk_LeaveRequest "
          + "where domainid = '" + getDomainid() + "' "
          + "  and item_申请人 = '" + userId + "'";

  if (number !== null && number.trim().length > 0) {
    sql += " and item_单号 like '%" + number + "%'";
  }
  if (start !== null) {
    sql += " and item_申请日期 >= '" + format(start, "yyyy-MM-dd 00:00:00") + "'";
  }
  if (end !== null) {
    sql += " and item_申请日期 <= '" + format(end, "yyyy-MM-dd 23:59:59") + "'";
  }
  sql += " order by item_申请日期 desc";
  return sql;
})()

代码说明: - getDomainid() 返回当前企业域 ID(system-functions.md),与平台表 DOMAINID 列对照。 - getWebUser().getId() 取当前用户 ID(curruser.md)。 - format(date, pattern) 把日期转为指定格式字符串(date-functions.md);不要直接拼接 Date 对象,不同数据库方言会解析失败。 - 也可用 isNotNull(v) 做非空判断(string-functions.md),等价于 v !== null && v.trim().length > 0

变体与扩展: - 关联查询left join t_user u on d.item_申请人 = u.id 显示用户姓名;关联系统表时记得系统表也按 DOMAINID 过滤。 - 统计聚合select item_分类, count(*) as 数量 group by item_分类——视图中能直接显示聚合列。 - 子查询where id in (select parent from tlk_子表 where item_状态 = '已完成') 用主表 ID 反查子表。 - 跨数据源:在 SQL 中跨库 join 通常不可行,改用 queryByDSName 在值脚本或操作后置中查回结果再处理。

常见坑: - domainidselect 列里少了 domainid,平台会抛错或数据串域。这是 SQL 模式最常见的坑。 - SQL 注入:用户输入直接拼到 SQL,恶意输入可绕过过滤。生产环境应对查询表单输入做转义或白名单校验。 - 表名/字段名混淆:业务表是 tlk_xxx + item_xxx;系统表是 t_xxx(如 t_usert_flowstatert),系统表字段**没有** item_ 前缀。 - 网格视图:必须是设计模式先转 SQL 模式,不能直接对设计模式视图写 SQL 脚本。 - 字符串比较用 ===、长度用 .lengthGraalVM 差异)。


用存储过程过滤视图数据

业务目标:业务侧已用存储过程封装了复杂查询(多表、临时表、递归 CTE),视图直接调用,避免在脚本里重写一遍。

写在哪儿:视图 → 数据 → 代码(存储过程),属性 procedureFilterScripteditMode=03,Label VIEW:PROCEDURE_FILTER

触发时机与上下文:打开/刷新视图时触发。可用环境变量同「用 DQL 过滤视图数据」。

返回值契约:String,形如 call 过程名call 过程名(参数1, 参数2, ...)。存储过程须返回结果集(即执行后能像 select 那样产出多行多列),且结果集中**必须包含 domainid 列**(同 SQL 模式约定)。

示例代码

// 调用名为 test 的存储过程作为视图数据源
(function () {
  return "call test";
})()

带参数的形态:

// 按当前用户 ID 调用存储过程
(function () {
  var userId = getWebUser().getId();
  return "call query_user_leaves('" + userId + "')";
})()

代码说明: - 与 DQL/SQL 模式不同,返回的是一条 call 语句,不是 DQL 表达式,也**不是** select SQL。 - 平台把 call 结果当作与 SQL 等价的结果集处理,因此存储过程定义里需要 selectdomainid 等平台必需字段。

变体与扩展:可与 getParameter(...) 结合,把 URL 参数透传给存储过程。

常见坑: - 存储过程没 select domainid:视图渲染报错或权限隔离失效。 - 误把 SQL 模式的 select 写进 procedureFilterScript——平台不会按 select 解析,必须 call。 - 在 Oracle 等数据库下,存储过程返回结果集需要显式 OUT 游标,与 MySQL 写法不同,需 DBA 配合。


按流程状态筛选视图数据

业务目标:只显示运行中 / 已完成 / 当前用户待办 / 已终止的流程实例对应的数据行;这是 SQL 过滤模式的典型变体,需 join 流程状态运行时表。

写在哪儿:视图 → 数据 → 代码(SQL),属性 sqlFilterScripteditMode=02,Label VIEW:SQL_FILTER

触发时机与上下文:打开/刷新视图时触发。可用 getWebUser()getParameter(...)(可读取视图查询参数如「流程状态」「节点 ID」)。

返回值契约:String,完整 select SQL,约定同「用 SQL 过滤视图数据」;额外要求正确 join t_flowstatert(流程状态运行时表)。

示例代码

// 只列出当前用户的"运行中"流程数据
(function () {
  var userId = getWebUser().getId();
  var sql = "select d.domainid, d.id, d.item_字段1, d.item_字段2, fs.state_label "
          + "from tlk_表单名 d "
          + "inner join t_flowstatert fs on d.id = fs.docid "
          + "inner join t_actor a on fs.id = a.flowstatert_id "
          + "where d.domainid = '" + getDomainid() + "' "
          + "  and a.actor_id = '" + userId + "' "
          + "  and a.state = 0"; // 0 = 未处理
  return sql;
})()

代码说明: - t_flowstatert:流程状态运行时表,docid 关联业务文档 idstate/state_label 反映流程当前状态。 - t_actor:当前待处理执行人表,actor_id = 用户 ID,state=0 表示尚未处理。 - t_actorhis:执行人历史表,查询"已审批过"用 inner join t_actorhis ah on fs.id = ah.flowstatert_id and ah.actor_id = '...'。 - 用 inner join 只显示有流程的文档;想列出全部(含未启动流程的)改 left join

变体与扩展

目标 关键 where 片段
运行中 fs.state = '运行中'
已完成 fs.state = '已完成'
已终止 fs.state = '已终止'
指定节点 fs.current_node_id = '...'
指定流程 fs.flow_id = '...'
当前用户已审批 inner join t_actorhis ah ... where ah.actor_id='...' and ah.attitude is not null

常见坑: - 漏 domainid、漏 d.domainid 过滤——同「用 SQL 过滤视图数据」,是 SQL 模式通病。 - inner join 误用导致没启动流程的数据消失——无流程文档应走 left join。 - 状态字段值("运行中"/"已完成")需与实际系统字典匹配,不同部署可能差异;先用 SQL 客户端 select distinct state from t_flowstatert 校对。


用列值脚本格式化显示

业务目标:视图列原样显示字段值不够友好——空值显示"-"、数值加单位、日期格式化、多字段拼成一个单元格。

写在哪儿:视图 → 列 → 列编辑 → 值脚本,列属性 valueScript(列类型须为 COLUMN_TYPE_SCRIPT,即"脚本列"),Label VIEW:COLUMN_VALUE。挂载点说明见 ../basic/where-to-use/view/view-columns.md

触发时机与上下文:渲染每一行时触发,每行调用一次。可用 getCurrentDocument()(这里指**当前行对应的数据文档**,不是查询表单)、getWebUser()getParameter(...)

返回值契约:String,作为该列该行的显示文本。空串或不返回则单元格为空。

示例代码

// 字段为空时显示 "-"
(function () {
  var val = getCurrentDocument().getItemValueAsString("姓名");
  if (val === null || val === "") {
    val = "-";
  }
  return val;
})()

代码说明: - 注意:同样是 getCurrentDocument()在过滤脚本里指查询表单临时文档,在列值脚本里指当前行数据文档——上下文不同,含义不同。 - 字符串判空推荐 val === null || val === "";用 === 而非 ==,避免弱类型隐式转换。 - 想格式化数值/日期,用 format(date, pattern) 或自己 parseFloat 后 toFixed。

变体与扩展: - 金额加千分位:return "¥" + Number(val).toFixed(2);。 - 状态码翻译:return { 0: "草稿", 1: "提交", 2: "已批" }[val] || "未知";。 - 多字段拼接:return doc.getItemValueAsString("姓") + doc.getItemValueAsString("名");

常见坑: - 列类型不是"脚本列"——valueScript 不会被执行;必须把列类型设为 COLUMN_TYPE_SCRIPT。 - 在列值脚本里 queryBySQL 跑复杂查询:每行都执行一次 SQL,N 行就是 N 次查询,性能灾难。应预聚合或改用 SQL 过滤模式 join。 - 误把 getCurrentDocument() 当查询表单——它在此处指当前行文档。


列控制脚本族(标签 / 隐藏 / 打印隐藏)

三个属性同构(都挂在"视图 → 列 → 列编辑"下,都按当前用户/参数返回字符串或布尔),合并为一张表:

场景 Label 属性 触发 返回值
动态列标题 VIEW:COLUMN_LABEL labelScript 渲染表头时 String,列标题文本
按用户/角色隐藏整列 VIEW:COLUMN_HIDDEN hiddenScript 打开视图时 Boolean,true = 隐藏
打印时隐藏该列 VIEW:COLUMN_PRINT_HIDE 列打印隐藏脚本 打印时 Boolean,true = 隐藏

动态列标题示例

// 按当前用户角色切换列标题
(function () {
  var roles = getWebUser().getRoles();
  if (roles !== null && roles.size() > 0 && roles.get(0).getName() === "管理员") {
    return "成本价";
  }
  return "售价";
})()

按用户隐藏列示例

// 当前用户为"张三"时,不显示该列
(function () {
  return getWebUser().getName() === "张三";
})()

说明: - 布尔契约统一:true = 隐藏/不打印。详见 权限与可见性控制。 - getWebUser().getRoles() 返回 Java List,用 .size().get(i) 遍历(GraalVM 差异:不要写成 .length)。 - 列隐藏是**整列级**的;想按行控制单元格显示,请改用列值脚本返回空串或 -


视图按钮(Activity)脚本族

视图工具栏按钮(Activity)支持名称、动作前/后置、只读、隐藏、跳转等脚本,全部挂在"视图 → 操作 → 编辑操作按钮"下。前置/后置是高频场景,单独展开;其余并表。

按钮动作执行前校验/拦截

业务目标:用户点按钮后、平台执行默认动作(如删除、提交)前先校验选中行;不通过则弹消息中止动作。

写在哪儿:视图 → 操作 → 编辑操作按钮 → 动作执行前脚本,属性 beforeScript,Label ACTIVITY:BEFORE。挂载点见 ../basic/where-to-use/view/view-actions.md

触发时机与上下文:用户点击按钮、平台执行默认动作**前**触发。可用 getParameterAsText("_selects")getParameterAsArray("_selects") 拿到勾选行的文档 ID(分号分隔),getCurrentDocument()getWebUser()

返回值契约:String。返回非空字符串 → 弹出该字符串作为提示,并中止后续默认动作与后置脚本;返回 ""/null/undefined → 放行,继续执行默认动作与后置。这是与校验脚本一致的"失败返串"契约。

示例代码

// 校验勾选行的"数量"必须 >= 100,否则拦截
(function () {
  var selects = getParameterAsText("_selects"); // 形如 "id1;id2;id3"
  if (selects === null || selects === "") {
    return "请先勾选记录";
  }
  // 转成 SQL in 列表
  var inList = "('" + selects.replace(/;/g, "','") + "')";

  var sql = "select item_数量 from tlk_材料信息表 where id in " + inList;
  var datas = queryBySQL(sql);
  if (datas !== null && datas.size() > 0) {
    for (var iter = datas.iterator(); iter.hasNext();) {
      var row = iter.next();
      var qty = parseFloat(row.getItemValueAsString("数量"));
      if (isNaN(qty) || qty < 100) {
        return "勾选数据中存在数量小于 100 的值,已中止操作";
      }
    }
  }
  return ""; // 放行
})()

代码说明: - getParameterAsText("_selects") 返回分号分隔的 ID 串;要数组用 getParameterAsArray("_selects")system-functions.md)。 - queryBySQL(sql) 返回 Java Collection,用 .iterator() 遍历(GraalVM 差异:不要用 JS for...of 直接遍历 Java 集合)。 - row.getItemValueAsString(...):每行是 Document,方法同表单字段读取(doc-functions.md)。

变体与扩展: - 想弹确认对话框而非纯文本:用 createConfirm("确定执行?")system-functions.md)返回 true/false——但注意 createConfirm 返回的是布尔,需配合包装成字符串或直接由平台处理交互。 - 操作后置 ACTIVITY:AFTER 属性 afterScript:在默认动作完成后触发,无返回值约束,常用于发消息、记日志、写缓存。

常见坑: - 误以为前置返回 false 才拦截——契约是**返回非空字符串即拦截**,与布尔隐藏契约相反,容易混。 - 在前置里 queryBySQL 拼接用户输入未转义——SQL 注入风险,同「用 SQL 过滤视图数据」。 - 把 _selects 当数组用:getParameterAsText 返回的是分号串,要先 split 或替换。

其余按钮脚本并表

场景 Label 属性 触发 返回值
按钮动态名称 ACTIVITY:LABEL 名称标签脚本 打开视图时 String,按钮显示文本
动作执行后 ACTIVITY:AFTER afterScript 默认动作完成后 无强制(一般不返回)
按钮只读 ACTIVITY:READONLY readonlyScript 打开视图时 Boolean,true = 只读
按钮隐藏 ACTIVITY:HIDDEN hiddenScript 打开视图时 Boolean,true = 隐藏
跳转 URL ACTIVITY:DISPATCHERURL dispatcher URL 脚本 点击按钮时 String,跳转地址

按钮动态名称示例

(function () {
  return "新建【" + getWebUser().getName() + "】的申请单";
})()

动作执行后示例(统计选中条数并打印到日志):

(function () {
  var selects = getParameterAsArray("_selects");
  println("共选择了 " + selects.length + " 条记录!");
})()

说明: - ACTIVITY:LABEL 返回字符串作为按钮名称。 - ACTIVITY:READONLY / ACTIVITY:HIDDEN 沿用布尔契约:true = 只读 / 隐藏(与权限与可见性控制字段隐藏契约一致)。 - ACTIVITY:AFTER 拦截无效——动作已执行,只能做副作用(消息、缓存、跳转)。 - 跳转相关详细用法见 消息通知与跳转导航


查询头字段默认值

业务目标:打开视图时让查询头自动带上默认值(本月、当前用户、当前部门),用户少点几下就能查到关心的数据。属于查询头字段(本质是表单字段)的值脚本。

写在哪儿:视图 → 查询头字段 → 值脚本(与表单值脚本同构,挂载点同 ../basic/where-to-use/form/form-controls.md 的「值脚本」)。无独立 VIEW: Label——查询头字段就是表单字段,按表单值脚本契约 FORM:FIELD_VALUE

触发时机与上下文:打开视图、渲染查询头时触发。可用 getWebUser()getParameter(...)(URL 参数)、getCurrentDocument()(查询表单临时文档)。

返回值契约:String。日期型字段返回 yyyy-MM-dd 等格式串;用户选择框返回用户 ID(多个用 ; 分隔);部门字段返回部门 ID。

子场景 示例
本月第一天 format(new Date(today.getFullYear(), today.getMonth(), 1), "yyyy-MM-dd")
今天 format(new Date(), "yyyy-MM-dd")
当前用户 getWebUser().getId()
当前部门 getWebUser().getDepartment().getId()
当前用户 + 下级 getWebUser().getId() + ";" + 下级列表 join(";")
从 URL 透传 getParameter("_keyword")(无值时回落到默认值)

示例代码(日期型查询头默认为本月第一天):

(function () {
  var today = new Date();
  var firstDay = new Date(today.getFullYear(), today.getMonth(), 1);
  return format(firstDay, "yyyy-MM-dd");
})()

说明: - format(date, pattern) 详见 date-functions.md。 - 用户/部门选择框必须返回 ID 而非名称——平台按 ID 匹配。 - 与缓存结合:把上一次查询条件写入私有空间,下次打开视图自动还原——见 附录 A 工具箱 的"跨脚本缓存"。

视图导入配置的用户选择框默认值

变体场景:配置视图 Excel 导入时,希望"导入用户"字段自动带当前用户或当前部门。挂载点是视图导入配置中的「用户选择框值脚本」,与查询头默认值同构(返回用户 ID 或 ; 分隔的多个 ID)。

// 默认填当前用户
(function () {
  return getWebUser().getId();
})()

多个用户(如某角色下全部成员):

(function () {
  var users = getUsersByRole("角色名称"); // Java List
  if (users === null || users.size() === 0) {
    return "";
  }
  var ids = [];
  for (var i = 0; i < users.size(); i++) {
    ids.push(users.get(i).getId());
  }
  return ids.join(";");
})()

常见坑: - 用户字段返回了**名称**而非 ID——选择框不识别,导入/查询都失效。 - 多个 ID 用逗号分隔——平台约定是**分号 ;**。 - 在视图导入场景里 getCurrentDocument() 通常为空(导入时尚无业务文档),应改用 getWebUser()getParameter(...)


本章相关场景