视图过滤与列控制¶
面向已经会把表单脚本挂在控件上的开发者,把"打开视图时让平台按我的意思查数据 / 显示列 / 控按钮"这一类需求集中讲清。本章重点有三:
- 三种过滤模式的返回串形态完全不同——DQL 返回带
$前缀系统字段的 DQL 串、SQL 返回带DOMAINID的标准 SQL、存储过程返回call xxx。 - **列值脚本、列标签、列隐藏**是三个不同挂载点,不要混淆。
- **视图按钮(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),属性 filterScript,editMode=01,Label VIEW:DQL_FILTER。挂载点说明见 ../basic/where-to-use/view/view-data.md。
触发时机与上下文:每次打开视图、翻页或刷新时触发。可用环境变量:WebUser(getWebUser())、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 > 0(GraalVM 差异:.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),属性 sqlFilterScript,editMode=02,Label VIEW:SQL_FILTER。
触发时机与上下文:每次打开视图、翻页或刷新时触发。可用环境变量同「用 DQL 过滤视图数据」:WebUser、getCurrentDocument()(查询表单临时文档)、getParameter(...)。
返回值契约:String,一条完整的 select SQL。硬约定:
- select 列表里**必须包含 domainid 字段**——平台靠它做数据隔离,缺失会导致跨域数据泄漏或查询失败。
- 表单表名前缀 tlk_,业务字段前缀 item_(如 tlk_LeaveRequest、item_申请人)。
- 系统表(t_user、t_department、t_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 在值脚本或操作后置中查回结果再处理。
常见坑:
- 漏 domainid:select 列里少了 domainid,平台会抛错或数据串域。这是 SQL 模式最常见的坑。
- SQL 注入:用户输入直接拼到 SQL,恶意输入可绕过过滤。生产环境应对查询表单输入做转义或白名单校验。
- 表名/字段名混淆:业务表是 tlk_xxx + item_xxx;系统表是 t_xxx(如 t_user、t_flowstatert),系统表字段**没有** item_ 前缀。
- 网格视图:必须是设计模式先转 SQL 模式,不能直接对设计模式视图写 SQL 脚本。
- 字符串比较用 ===、长度用 .length(GraalVM 差异)。
用存储过程过滤视图数据¶
业务目标:业务侧已用存储过程封装了复杂查询(多表、临时表、递归 CTE),视图直接调用,避免在脚本里重写一遍。
写在哪儿:视图 → 数据 → 代码(存储过程),属性 procedureFilterScript,editMode=03,Label VIEW:PROCEDURE_FILTER。
触发时机与上下文:打开/刷新视图时触发。可用环境变量同「用 DQL 过滤视图数据」。
返回值契约:String,形如 call 过程名 或 call 过程名(参数1, 参数2, ...)。存储过程须返回结果集(即执行后能像 select 那样产出多行多列),且结果集中**必须包含 domainid 列**(同 SQL 模式约定)。
示例代码:
带参数的形态:
// 按当前用户 ID 调用存储过程
(function () {
var userId = getWebUser().getId();
return "call query_user_leaves('" + userId + "')";
})()
代码说明:
- 与 DQL/SQL 模式不同,返回的是一条 call 语句,不是 DQL 表达式,也**不是** select SQL。
- 平台把 call 结果当作与 SQL 等价的结果集处理,因此存储过程定义里需要 select 出 domainid 等平台必需字段。
变体与扩展:可与 getParameter(...) 结合,把 URL 参数透传给存储过程。
常见坑:
- 存储过程没 select domainid:视图渲染报错或权限隔离失效。
- 误把 SQL 模式的 select 写进 procedureFilterScript——平台不会按 select 解析,必须 call。
- 在 Oracle 等数据库下,存储过程返回结果集需要显式 OUT 游标,与 MySQL 写法不同,需 DBA 配合。
按流程状态筛选视图数据¶
业务目标:只显示运行中 / 已完成 / 当前用户待办 / 已终止的流程实例对应的数据行;这是 SQL 过滤模式的典型变体,需 join 流程状态运行时表。
写在哪儿:视图 → 数据 → 代码(SQL),属性 sqlFilterScript,editMode=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 关联业务文档 id,state/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 "售价";
})()
按用户隐藏列示例:
说明:
- 布尔契约统一: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 () {
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 () {
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(...)。
本章相关场景¶
- 想控制表单字段(而非视图列)的隐藏/只读——见 权限与可见性控制。
- 想在按钮后置里发邮件/消息/跳转——见 消息通知与跳转导航。
- SQL/存储过程模式涉及跨数据源、复杂查询——见 数据查询、统计与文档批量操作。
- 函数细节:
getCurrentDocument、queryBySQL、getWebUser、getDomainid/getParameter、format、isNotNull。