跳转至

消息通知与跳转导航

一句话定位:表单/视图操作发生后或流程干预后**通知人**(邮件、站内信、微信待办);以及操作点击或菜单点击时**跳转页面**(表单、视图、图表、报表、自定义 Vue 页、外部 URL)。落点以表单/视图的 操作脚本(ACTIVITY:*菜单脚本(MENU:LINK_CONTENT 为主,外加 o-action 标签这种写在视图列/计算脚本里的声明式跳转。

写在前面——通知函数签名不复述

本书不复述邮件/消息函数的参数签名,请对照查阅:

关于微信待办/企业微信消息:源素材 common-script-collection → 发送微信段待办common-script-collection → 根据角色发送微信及电子邮件 中出现的 sendWechatTodo / sendWechatMessage 等是社区模式函数,不属于 iscript-guid/functions 收录的标准 API;本书在「发微信待办 / 按角色发微信 + 邮件」节以"模式脚本"方式并表呈现,落地时需确认运行环境是否提供同名函数库,或按平台实际企业微信/钉钉集成 API 改写。


本章场景清单

场景 落点 / Label 主要素材
表单按钮执行后发邮件 操作后置 ACTIVITY:AFTER form-controls(邮件示例)、mail-functions
按角色 / 部门群发站内信 操作后置 ACTIVITY:AFTER(亦可操作前置 / 定时) message-functions
流程终止 / 干预后通知经办人 操作前置 / 后置 terminate-flow、intervention-submit-monitor
表单按钮跳转表单 / 视图 / 图表 / 报表 操作跳转 ACTIVITY:DISPATCHERURL form-jump-script
菜单脚本链接外部 URL / 自定义 Vue 页 MENU:LINK_CONTENT menu、menu-script-link-custom-page
发微信待办 / 按角色发微信 + 邮件(模式脚本) 操作后置 / 定时 send-wechat-todo、send-wechat-email-by-role
o-action 标签打开 / 跳转 / 接口按钮(声明式) 视图列 / 计算脚本 / 表单(HTML 标签) o-action-tag

表单按钮执行后发邮件(标杆场景)

业务目标

表单上点击某个操作按钮(如"提交""分发""审批通过")成功后,自动给指定邮箱发一封通知邮件——典型的"操作完成 → 通知相关人"的场景。这是 ACTIVITY:AFTER 最干净的应用:操作已经成功,副作用只做通知,不阻断流程。

写在哪儿

表单 → 表单按钮 → 动作执行后脚本.form 关联的 .activity;属性 afterActionScript;Label ACTIVITY:AFTER)。

参考 iscript-guid/basic → 表单控件 的"表单按钮类控件动作执行后脚本"一节;操作脚本的 Label 体系见 agent-skills-usage → 操作脚本

触发时机与上下文

  • 触发时机:按钮动作**成功执行之后**触发(前置校验、动作本身都已通过)。
  • 可用环境变量WebUser(当前登录用户,getWebUser())、CurrentDocument(当前文档,getCurrentDocument())、RelateDocumentParentDocument

返回值契约

无强制返回值。脚本以副作用方式发邮件,平台忽略返回值。仍建议包成 IIFE 避免变量泄漏,可不 return

示例代码

/* 提交后给固定邮箱发通知邮件(用系统配置的发件人) */
(function () {
  var doc     = getCurrentDocument();
  var subject = "新单据:" + doc.getItemValueAsString("标题");
  var content = "单据编号:" + doc.getId() +
                "\n提交人:" + getWebUser().getName() +
                "\n提交时间:" + format(new Date(), "yyyy-MM-dd HH:mm:ss") +
                "\n请及时查阅。";

  // 用系统配置的发件人发送(无需在脚本里塞 SMTP 配置)
  MAIL.sendEmailBySystemUser("manager@example.com", subject, content);
})()

代码说明

关键步骤 函数 / 字段 文档
当前文档 getCurrentDocument()doc.getId()doc.getItemValueAsString("标题") iscript-guid/api → dociscript-guid/functions → 文档函数
当前用户 getWebUser().getName() iscript-guid/api → curruser
格式化时间 format(new Date(), "yyyy-MM-dd HH:mm:ss") iscript-guid/functions → 日期函数
系统配置发件人发送 MAIL.sendEmailBySystemUser(to, subject, content) iscript-guid/functions → 邮件函数

变体与扩展

  • 收件人来自表单字段:把 "manager@example.com" 换成 doc.getItemValueAsString("通知邮箱"),支持多收件人用 ;, 分隔(按邮件服务器约定)。
  • 自定义 SMTP 发送:需要指定独立邮箱时改用 MAIL.sendMail(from, to, subject, body, host, user, password, bbc, validate);签名见 iscript-guid/functions → 邮件函数
  • 带附件:表单上传的附件路径收集成数组后调 MAIL.sendEmailWithAttachmentsBySystemUser(to, subject, content, attachFiles)
  • 多人群发 + 抄送to 入参用 , 拼接多个邮箱;密送参数 bbcsendMail 支持。
  • 前置确认弹窗:在 ACTIVITY:BEFOREcreateConfirm("确认提交并发送通知?"),先确认再放行——前后置配合形成"先问再做、做完通知"的完整闭环。

常见坑

  • ACTIVITY:AFTER vs ACTIVITY:BEFORE 用反:前者在动作成功后执行,不能阻断(适合通知);后者在动作前执行,返回非空串会阻断动作(适合校验)。把发邮件放错到前置,会导致动作失败时邮件已发出。详见 操作脚本体系
  • SMTP 未配置sendEmailBySystemUser 依赖后台"邮件服务器配置";后台未配时调用静默失败,脚本不会抛异常但邮件不会到达。上线前用测试邮箱跑一次。
  • 多人收件人分隔符:不同邮件服务器对 to 中分隔符的约定不同(,;),用错会被当作一个非法地址整体退回。
  • 字符串拼接用 + 拼多行内容:注意结尾换行符 \n 在邮件正文里有效,但拼到 HTML 邮件里会被忽略——HTML 正文请用 <br/>
  • getName()getLoginno():发通知用姓名更友好;做日志跟踪用登录名更稳定(姓名可能重名)。
  • "" === null:判断空串请用 v === "",不要写 v.equals("")(GraalVM JS 字符串字面量没有 .equals 方法)。详见 GraalVM 差异

按角色 / 部门群发站内信

业务目标

"操作完成后,把消息群发给某个**角色**或某个**部门**下的所有人"——例如投诉单提交后给"客服组"角色下所有人发站内信、报销审批后给"财务部"部门下所有人发站内信。比逐个 sendMessage 简洁,平台直接按组织维度分发。

写在哪儿

表单 → 表单按钮 → 动作执行后脚本.activity;属性 afterActionScript;Label ACTIVITY:AFTER)。亦可挂在视图操作后置、定时任务里(定时任务无 getCurrentDocument(),需用 getDocumentProcess().doView(docId) 取文档)。

触发时机与上下文

  • 触发时机:操作成功后(同「表单按钮执行后发邮件」)。
  • 可用环境变量WebUserCurrentDocument

返回值契约

无强制返回值,副作用调用 MESSAGE.sendMessageByRole / sendMessageByDept

示例代码

/* 提交后给"客服组"角色下所有人发站内信,发件人是当前用户 */
(function () {
  var doc       = getCurrentDocument();
  var senderId  = getWebUser().getId();
  var roleId    = getRoleIdByName("客服组");
  var domainId  = getWebUser().getDomainid();
  var title     = "新投诉单待处理";
  var content   = "标题:" + doc.getItemValueAsString("标题") +
                  "\n提交人:" + getWebUser().getName();

  if (roleId !== null && roleId !== "") {
    MESSAGE.sendMessageByRole(roleId, domainId, title, content);
  } else {
    // 兜底:按部门发
    var deptId = getWebUser().getDepartment() != null
      ? getWebUser().getDepartment().getId()
      : "";
    if (deptId !== "") {
      MESSAGE.sendMessageByDept(deptId, title, content);
    }
  }
})()

代码说明

关键步骤 函数 / 字段 文档
角色名 → 角色 ID getRoleIdByName("客服组") 返回 Stringnull iscript-guid/api → 用户中心
取域 ID getWebUser().getDomainid() iscript-guid/api → curruser
取部门 getWebUser().getDepartment().getId() 同上
按角色群发 MESSAGE.sendMessageByRole(roleid, domainid, title, content) iscript-guid/functions → 信息函数
按部门群发 MESSAGE.sendMessageByDept(departmentid, title, content) 同上
单发(给某用户) MESSAGE.sendMessage(senderid, receiverid, title, content) 同上

变体与扩展

  • 指定发件人单发MESSAGE.sendMessage(senderId, receiverId, title, content);当 senderIdreceiverId 相同时只通知自己。
  • 手机短信sendSMS(docid, title, content, receiver, isReply, isMass) 或定时任务专用 sendSMS4Task(title, content, receiver, isReply, isMass, applicationId, domainId);签名细节见 iscript-guid/functions → 信息函数
  • 遍历角色下用户再单发(个性化内容):getUsersByRoleId(roleid) 返回 Collection<UserVO>,循环里按用户姓名/扩展字段定制内容后 sendMessage
  • 定时任务里群发:定时任务无 WebUser,须用 getApplication()getDomainid()(或显式传入域 ID)配 sendSMS4Task,不能调 sendMessageByRole 的"当前域"捷径。

常见坑

  • sendMessageByRole 必须传 domainId:跨域时角色 ID 重复也分发不到目标用户——务必从 getWebUser().getDomainid() 取,不要硬编码。
  • 角色名找不到返回 nullgetRoleIdByName 是精确匹配,角色改名后旧脚本会失效;上线前到角色管理核对名称拼写。
  • sendMessagesendMessageByRole 参数顺序易混:前者 (senderid, receiverid, ...),后者 (roleid, domainid, ...);签名细节点 信息函数
  • 跨表单取收件人:用 queryBySQLt_user 时返回 Java List,长度 .size()、取元素 .get(i)、遍历 .iterator()——不是 JS 数组的 .length。详见 GraalVM 差异
  • 短信 isMass 与分隔符:群发 receiver 多个手机号用 , 分隔;isMass 须与接收者数量一致,否则部分网关会拒收。

流程终止 / 干预后通知经办人

业务目标

强制终止流程或干预提交(管理员跳过常规审批)后,给原来的审批人、申请人发站内信告知——避免他们继续等一个永远不会到的待办,是流程治理的"善后通知"。挂载点与 流程查询、审批历史与人工干预 的"强制终止"场景同源,这里只聚焦通知部分。

写在哪儿

表单 → 表单按钮 → **操作前置 / 操作后置**脚本(.activity;属性 beforeActionScript / afterActionScript)。终止动作本身(flowProcess.doTerminate)建议放前置,通知放在终止成功之后的同一段脚本末尾(顺序执行)。

触发时机与上下文

  • 触发时机:用户点击表单上的"终止流程""干预提交"按钮(自定义操作按钮)。
  • 可用环境变量WebUserCurrentDocument;通过 getCurrentDocument()doc,再 getFlowProcess().doViewByDocument(doc) 取流程对象。

返回值契约

无强制返回值。脚本以副作用方式终止流程并群发通知;通常 return 一个状态串(如 "流程已终止并通知 N 人")便于调试。

示例代码

/* 终止流程后给当前所有待处理审批人 + 文档作者发站内信 */
(function () {
  var doc       = getCurrentDocument();
  var docId     = doc.getId();
  var reason    = doc.getItemValueAsString("终止原因") || "管理员终止";
  var flowProc  = getFlowProcess();
  var flow      = flowProc.doViewByDocument(doc);

  if (flow === null) {
    return "未找到运行中的流程";
  }

  // 1) 终止前先取出当前待处理审批人 ID(终止后 T_ACTOR 会清空)
  var stateSql = "select domainid,id from t_flowstatert where docid = '" + docId + "'";
  var state    = findBySQL(stateSql);
  var stateId  = state != null ? state.getId() : "";
  var receivers = [];

  if (stateId !== "") {
    var actorSql = "select domainid,actor_id from t_actor " +
                   "where flowstatert_id = '" + stateId + "' and state = 0";
    var actors   = queryBySQL(actorSql);     // Java List
    if (actors != null && actors.size() > 0) {
      for (var iter = actors.iterator(); iter.hasNext();) {
        var actorId = iter.next().getItemValueAsString("actor_id");
        if (actorId !== "") { receivers.push(actorId); }
      }
    }
  }

  // 2) 加入文档作者
  var authorId = doc.getAuthor() != null ? doc.getAuthor() : "";
  if (authorId !== "" && receivers.indexOf(authorId) < 0) {
    receivers.push(authorId);
  }

  // 3) 执行终止
  var params = createParamsTable();
  params.setParameter("_attitude", reason);
  flowProc.doTerminate(flow, params, getWebUser());

  // 4) 群发通知(sendMessage 是单发,循环里逐个调)
  var title   = "流程已终止:" + doc.getItemValueAsString("标题");
  var content = "终止原因:" + reason + "\n操作人:" + getWebUser().getName();
  var sender  = getWebUser().getId();
  for (var i = 0; i < receivers.length; i++) {
    MESSAGE.sendMessage(sender, receivers[i], title, content);
  }

  return "流程已终止,已通知 " + receivers.length + " 人";
})()

代码说明

关键步骤 函数 / 字段 文档
取流程对象 getFlowProcess().doViewByDocument(doc) iscript-guid/api → flow
终止流程 flowProc.doTerminate(flow, params, user) 同上
参数表 createParamsTable() + params.setParameter("_attitude", ...) 平台内置
单条查询 findBySQL(sql) 返回首条或 null iscript-guid/api → database
多条查询 queryBySQL(sql) 返回 java.util.List 同上
单发站内信 MESSAGE.sendMessage(sender, receiver, title, content) iscript-guid/functions → 信息函数

变体与扩展

  • 干预提交后通知目标节点审批人:把 doTerminate 换成 flowProc.doSubmit(flow, targetNode, params, user),通知接收人改为目标节点下的候选审批人(源素材 common-script-collection → 干预提交 示例 3)。
  • 追加邮件/短信:循环里同步调 MAIL.sendEmailBySystemUsersendSMS;短信需 user.getTelephone() 取号。
  • 记录通知日志:循环末尾 insert 一条到自建的 tlk_流程通知日志 表,便于事后审计。
  • 批量终止:视图勾选多条 → 操作前置里 getParameterAsText("_selects") 拆出 docId 列表,外层套 for 循环。

常见坑

  • 顺序错误:必须**先取审批人再终止**。一旦 doTerminate 执行,T_ACTOR 表中 state=0 的记录会被清空/置位,再查就查不到原本的待处理人。
  • doTerminate 不可逆:终止后流程无法恢复,通知只是"善后",不能撤销终止;调试时先用查询脚本确认 receivers 列表正确,再加终止调用。
  • doc.getAuthor() 类型:返回的是用户 ID 字符串(String),不是 UserVO;要姓名需 getUserById(authorId).getName()
  • Java List vs JS 数组actorsqueryBySQL 返回的 Java List,用 .size() / .iterator()receivers 是脚本里 [] 创建的 JS 数组,用 .length / .push / .indexOf——两者别混。详见 GraalVM 差异
  • 群发性能:循环里 sendMessage 是同步调用,上百人时建议改 sendMessageByRole 或异步(操作后置 + 队列);耗时过长会拖慢按钮响应。
  • SQL 拼接注入docId 来自平台上下文较安全,但 reason 含用户输入时需转义单引号。

表单按钮跳转表单 / 视图 / 图表 / 报表

业务目标

点击表单上的某个操作按钮后,跳转到另一个表单(新建/打开指定文档)、视图、统计图、报表或大屏——例如"提交后跳转到列表视图""查看关联明细报表"。跳转目标可以动态计算(条件不同跳不同页)。

写在哪儿

表单 → 表单按钮 → 跳转 URL 脚本.activity;属性 actionDispatcherUrlScript;Label ACTIVITY:DISPATCHERURL)。

参考 agent-skills-usage → 操作脚本 的"跳转 URL"一节;素材片段见 common-script-collection → 表单跳转脚本

触发时机与上下文

  • 触发时机:点击跳转类操作按钮时。
  • 可用环境变量WebUserCurrentDocumentParams(含 _selects 等请求参数);通过 $WEB.getParamsTable().getHttpRequest()getParamsTable().getHttpRequest() 取 HTTP 请求对象。

返回值契约

返回 String URL——平台把返回串作为跳转地址。返回空串或不返回则不跳转。

示例代码

/* 跳转到指定视图(动态拼 appId、视图 ID、服务器地址) */
(function () {
  var appId = getApplication();
  var request = $WEB.getParamsTable().getHttpRequest();
  var base = request.getScheme() + "://" + request.getServerName()
           + ":" + request.getServerPort();

  var viewId = "__DfLvf30doJU6BmaOJMj";   // 目标视图 ID(设计器拷贝)
  // linkType=01 视图 / 00 表单 / 02 统计图 / 09 报表
  var url = base + "/static/portal/vue/index.html#/open"
          + "?appId=" + appId
          + "&linkType=01"
          + "&actionContent=" + viewId;
  return url;
})()

代码说明

关键步骤 函数 / 字段 文档
当前应用 ID getApplication() iscript-guid/api → function
HTTP 请求 $WEB.getParamsTable().getHttpRequest()request.getScheme/getServerName/getServerPort 平台内置
拼跳转 URL /static/portal/vue/index.html#/open?appId=...&linkType=...&actionContent=... 见下方 linkType 表
取 URL 参数 getParameter("_selects") 取勾选记录 ID iscript-guid/functions → 系统函数

linkType 取值与跳转格式

linkType 目标 URL 片段
00 表单(新建/打开文档) linkType=00&actionContent={表单ID}&docid={文档ID}docid 留空=新建)
00 + realformId 模板表单 linkType=00&actionContent={模板表单ID}&realformId={对应表单ID}&docid={文档ID}
01 视图 linkType=01&actionContent={视图ID}
02 统计图(ECharts) linkType=02&actionContent={统计图ID}
09 报表 linkType=09&actionContent={报表ID}
—(不同路径) 大屏 /static/portal/vue/pageIndexHtml/index.html#/?pageId={大屏ID}&appId=...&domainId=...&preview=true&reception=true

变体与扩展

  • 条件跳转:先 var cond = doc.getItemValueAsString("类型");if (cond === "A") { url = ...视图1; } else { url = ...视图2; }
  • 跳转并传参:URL 末尾拼 &docid= + 当前文档 ID、&_selects= + 勾选记录,目标页用 getParameter 接收。
  • 弹出层/对话框打开:拼 &dialogWidth=800px&dialogHeight=600px,并在按钮上设 open-type=open-eject(与 o-action 标签语义一致)。
  • 分享链接(带 Token):跨会话访问时追加 &accessToken= + token;token 用源素材里的 Packages.cn.myapps.common.util.Security 取(旧示例保留 Packages...,新代码可改 Java.type('cn.myapps.common.util.Security'))。

常见坑

  • linkType 取错:表单和模板表单都是 00,但模板表单必须额外带 realformId,缺了会找不到目标表单。
  • ID 拼错或未替换:示例里的 __DfLvf30doJU6BmaOJMj 是占位 ID,复制后必须替换为设计器实际拷贝出来的对象 ID;多个相似 ID(视图 vs 表单)串了会导致跳错。
  • 大屏路径不同:大屏不是 /open 而是 /pageIndexHtml/index.html#/,参数键也从 actionContent 变成 pageId,直接套视图模板会 404。
  • 协议/端口写死:示例用 request.getScheme() 取协议;写死 http:// 时若生产是 HTTPS,浏览器会拦截混合内容。
  • Packages.cn.myapps... vs Java.type:旧示例多用 new Packages.cn.myapps.common.util.Security();GraalVM 下推荐 Java.type('cn.myapps.common.util.Security')new。详见 GraalVM 差异
  • getParamsTable()$WEB.getParamsTable():两种写法在不同上下文都见过,行为一致;若一种报 ReferenceError 请改另一种。

菜单脚本链接外部 URL / 自定义 Vue 页

业务目标

菜单点击时,用脚本动态算出要跳转的 URL——不是写死一个静态链接。典型用途:根据当前用户角色跳到不同首页、带 Token 跳到外部系统、跳到平台内的自定义 Vue 大屏页。

写在哪儿

菜单 → 链接类型 → 脚本链接(PC 菜单 .menu 或移动菜单 .mobilemenu;属性 actionContent;移动菜单脚本链接 type=07;Label MENU:LINK_CONTENT)。

参考 iscript-guid/basic → 菜单 的"通过脚本指定菜单对应的链接 URL";脚本挂载点说明见 agent-skills-usage → 菜单脚本

触发时机与上下文

  • 触发时机:生成菜单 / 用户点击菜单时(运行时亦可先落到 /portal/LinkForScript?_resourceid={菜单id} 再服务端执行)。
  • 可用环境变量WebUser(通过 getWebUser() 取得)。

返回值契约

返回 String URL。平台把返回串作为菜单点击后的跳转地址。

示例代码

/* 根据当前用户角色跳到不同首页 */
(function () {
  var user  = getWebUser();
  var appId = getApplication();
  var domainId = user.getDomainid();

  // 取首個角色名作为分流依据(多角色用户取主角色)
  var roleName = "";
  var roles = user.getRoles();
  if (roles != null && roles.size() > 0) {
    roleName = roles.iterator().next().getName();
  }

  var pageId = roleName === "管理员"
    ? "__adminDashboard__"
    : "__userHome__";

  var request = getParamsTable().getHttpRequest();
  var base = request.getScheme() + "://" + request.getServerName()
           + ":" + request.getServerPort();

  // 跳转到自定义 Vue 页
  var url = base + "/static/portal/vue/pageIndexHtml/index.html#/?pageId=" + pageId
          + "&appId=" + appId
          + "&domainId=" + domainId;
  return url;
})()

代码说明

关键步骤 函数 / 字段 文档
当前用户 getWebUser()user.getRoles() 返回 Collection<RoleVO> iscript-guid/api → curruser
应用 / 域 ID getApplication()user.getDomainid() iscript-guid/api → function
HTTP 请求 getParamsTable().getHttpRequest()request.getScheme/getServerName/getServerPort 平台内置
拼自定义页 URL /static/portal/vue/pageIndexHtml/index.html#/?pageId=...&appId=...&domainId=... common-script-collection → 菜单脚本链接自定义页面

变体与扩展

  • 直接跳外部 URLreturn "https://hao.360.com/";(最简形式,iscript-guid/basic → 菜单 原始示例)。
  • 跳平台内部路径return "/portal/xxx?app=" + getApplication();agent-skills-usage → 菜单脚本 示例)。
  • 带 Token 的分享链接:先 var Security = Java.type('cn.myapps.common.util.Security'); var token = new Security().getToken(user.getId());,URL 末尾拼 &accessToken= + token。
  • 带返回地址:拼 &returnUrl= + encodeURIComponent(request.getRequestURL().toString()),便于目标页关闭后回到原菜单。
  • 按部门分流:把 roleName === "管理员" 换成 user.getDepartment() != null && user.getDepartment().getId() === "__销售部ID__"

常见坑

  • MENU:LINK_CONTENT 返回值必须是字符串 URL:返回对象、数字、undefined 都会被当作非法 URL,菜单点击无反应。
  • 菜单"链接类型"未设为脚本:菜单设计器里有"内部链接/外部链接/脚本链接"等类型;属性 actionContent 只在选了"脚本链接"时才生效。移动端是 type=07(SCRIPT)。
  • user.getRoles() 返回 Java Collection:用 .size() / .iterator()不是 .length。详见 GraalVM 差异
  • 跨域跳转 Cookie 丢失:跳到外部系统时浏览器跨域不会带本平台会话 Cookie;需要免登的话用 Token 参数(变体 3)。
  • pageId 拼写错误:自定义 Vue 页地址键是 pageId(驼峰),跳转视图/表单用的是 actionContent——两种地址格式不可混用。
  • getParamsTable() vs $WEB.getParamsTable():与「表单按钮跳转表单 / 视图 / 图表 / 报表」同,两种写法都可能,看上下文报错改另一种。

发微信待办 / 按角色发微信 + 邮件(模式脚本)

重要前提:源素材 common-script-collection → 发送微信段待办common-script-collection → 根据角色发送微信及电子邮件 中使用的 sendWechatTodo / sendWechatMessage / sendWechatMessageWithLink 等是**社区模式函数**,不属于 iscript-guid/functions 收录的标准 API。下面以"模式脚本"形式给出,落地时需先确认运行环境是否提供同名函数库(如平台的企业微信/钉钉集成模块),否则应替换为:

  • 站内信兜底MESSAGE.sendMessage / sendMessageByRole(见「按角色 / 部门群发站内信」);
  • 邮件兜底MAIL.sendEmailBySystemUser(见「表单按钮执行后发邮件」);
  • 企业微信 OpenAPI:在脚本里通过 HTTP 调用企业微信/钉钉的接口(参考 定时任务、API 与 Widget 的 HTTP 函数用法)。

这两个场景挂载点(操作后置 / 定时任务)、上下文、返回值契约与「表单按钮执行后发邮件」/「按角色 / 部门群发站内信」一致,差异仅在调用模式函数。故合并为一张表:

子场景 模式函数(社区) 标准替代 关键步骤
发微信待办提醒 sendWechatTodo(userId, title, content, todoId) 站内信 MESSAGE.sendMessage + 业务表自建待办 userIdgetWebUser().getId() 或文档字段)、组装 title/content、生成或传入 todoId
发带链接的微信待办 sendWechatTodoWithLink(userId, title, content, todoId, url) 站内信正文里嵌 URL content 末尾追加 \n详情: + 跳转 URL(拼法参考「表单按钮跳转表单 / 视图 / 图表 / 报表」)
按角色群发微信 循环 role.getUsers()sendWechatMessage(userId, message) MESSAGE.sendMessageByRole(roleId, domainId, title, content) getRoleIdByName + getUsersByRoleId,循环里逐个发
按角色 + 邮件并发 循环 users:先 sendWechatMessage,再按 user.getEmail()MAIL.sendEmailBySystemUser 同左 + MESSAGE.sendMessageByRole 兜底 邮箱为空的用户跳过邮件、只发微信
多角色合并去重 收集 allUserIds 数组,发前 indexOf 去重 MESSAGE.sendMessageByRole 串调多次 用 JS 数组 .indexOf / .push不是 Java List 的 .contains

片段:按角色发微信 + 邮件(社区模式)

/* 给"客服组"角色下所有用户发微信 + 邮件(需平台提供 sendWechatMessage) */
(function () {
  var roleName = "客服组";
  var subject  = "新工单待处理";
  var content  = "您有一条新的工单,请及时跟进";

  var role = getRoleByName(roleName);     // 注意:社区脚本用 getRoleByName,返回角色对象
  if (role === null) {
    return "角色不存在:" + roleName;
  }

  var users = role.getUsers();            // Java Collection
  if (users === null || users.size() === 0) {
    return "角色下无用户";
  }

  var wechatOk = 0, wechatFail = 0;
  var mailOk = 0, mailFail = 0;

  for (var iter = users.iterator(); iter.hasNext();) {
    var user = iter.next();
    var userId = user.getId();
    var email  = user.getEmail();

    // 1) 微信
    try {
      sendWechatMessage(userId, content);     // 社区模式函数
      wechatOk++;
    } catch (e) {
      wechatFail++;
    }

    // 2) 邮件(邮箱非空才发)
    if (email != null && email.trim().length > 0) {
      try {
        MAIL.sendEmailBySystemUser(email, subject, content);  // 标准 API
        mailOk++;
      } catch (e2) {
        mailFail++;
      }
    } else {
      mailFail++;
    }
  }

  return "微信 成功 " + wechatOk + " 失败 " + wechatFail +
         ";邮件 成功 " + mailOk + " 失败 " + mailFail;
})()

模式函数 sendWechatTodo / sendWechatMessage 的更多写法(批量、带链接、按条件、带重试)见 common-script-collection → 发送微信段待办common-script-collection → 根据角色发送微信及电子邮件

常见坑

  • sendWechatTodo 等不存在:直接调用会抛 ReferenceError;务必先按平台实际能力替换为标准 MESSAGE.* 或 HTTP 调企业微信 OpenAPI。
  • getRoleByName vs getRoleIdByName:社区脚本里用 getRoleByName(roleName).getUsers() 取角色对象再取用户;标准 API 里更常见 getRoleIdByName + getUsersByRoleId。两种写法都可行,不要混用同一个变量名(role vs roleId)。
  • 用户未绑微信/邮箱:模式函数静默失败或抛异常,需 try/catch 兜底;标准 MESSAGE.sendMessageByRole 则由平台保证投递。
  • 频率限制:循环里同步发上百条会被网关限流;分批或异步(操作后置 + 后台队列)。
  • email.trim().length:JS 字符串 length 是属性,**不要**写成 .length()。详见 GraalVM 差异

o-action 标签打开 / 跳转 / 接口按钮(声明式)

与前面「表单按钮执行后发邮件」–「菜单脚本链接外部 URL / 自定义 Vue 页」的"脚本返回 URL/副作用"不同,o-action 是写在**视图列、计算脚本或表单 HTML 控件**里的声明式标签——平台渲染时把它解析成一个可点击的按钮/链接,点击执行内置动作。不是 iScript 函数调用,但常与脚本配合(如先用脚本算出 docid 再嵌入标签)。落点、上下文、返回值契约不适用 8 字段模板,故以属性同构表呈现。

参考素材:common-script-collection → O-ACTION 标签

标签语法

<o-action action-type="动作类型" open-type="打开方式" exparams="参数">
  按钮文本
</o-action>

action-type 与典型用法

action-type 作用 必填属性 示例
opendocument 打开表单(新建或指定文档) formid / docid / appid <o-action action-type="opendocument" open-type="open-eject" appid="__appA__" formid="__formB__" docid="__docC__">打开</o-action>
openview 打开视图 viewid / appid <o-action action-type="openview" open-type="open-present" appid="__appA__" viewid="__viewD__">列表</o-action>
jumpto 跳转到任意 URL(外部或内部) url <o-action action-type="jumpto" url="https://www.baidu.com/" open-type="open-blank">百度</o-action>
interface 调用后端接口(GET/POST/PUT/DELETE) url / type <o-action action-type="interface" url="/obpm/magic-api/xxx/setStatus" type="post">同步状态</o-action>

open-type 打开方式(4 类通用)

含义
open-present 当前页打开
open-eject 弹出层(可配 dialog-width / dialog-height
open-tab 标签页
open-blank 新窗口
open-below 从下往上滑出(仅表单)
open-side 从右往左滑出(仅表单)

其它常用属性

属性 含义
exparams 业务参数,格式 k1=v1&k2=v2,多参数 & 分隔;目标页用 getParameter 接收
isRefresh true 表示执行后刷新当前视图
isReadonly true 表示以只读模式打开(仅对表单生效
dialog-width / dialog-height 弹窗尺寸(仅 open-type=open-eject),如 1200px / 500px

与脚本配合的常见模式

  • 视图列里嵌入 o-action:列值脚本返回拼好的 HTML 串(含 docid= + 当前记录 ID),实现"每行一个'查看'按钮"。
  • 计算脚本动态生成跳转目标:脚本里按字段值算出目标 formid,再拼成 o-action 标签返回,平台渲染为按钮。
  • 接口按钮action-type="interface"type="post",常用于"一键同步""批量生效"等无跳转的副作用操作;接口返回值若需展示,由前端回调处理。

常见坑

  • 属性大小写敏感action-type / open-type 必须用 kebab-case;写成 actionType 不会被识别。
  • exparams 多参数分隔符:用 & 不是 ;;值含特殊字符须 URL 编码(encodeURIComponent)。
  • isReadonly 仅对表单生效:视图跳转加 isReadonly 无效。
  • dialog-width/height 仅弹出层生效:配 open-present / open-tab 时被忽略。
  • action-type="interface" 的接口地址:内部接口用相对路径(如 /obpm/magic-api/...),外部接口用完整 URL;接口权限按当前用户校验。
  • HTML 注入风险:列值脚本拼接 o-action 时,若 docid 来自不可信输入需转义双引号,避免标签被破坏。

相关场景

  • 流程查询、审批历史与人工干预:终止流程、干预提交、回退的完整脚本——本章「流程终止 / 干预后通知经办人」只覆盖了"终止后通知"这一面。
  • 定时任务、API 与 Widget:定时任务里群发通知(无 WebUser 上下文)、API 响应脚本里调 MESSAGE.* / MAIL.*、Widget 内容脚本里嵌 o-action 标签。
  • 组织架构查询与维护:按角色/部门群发的底层——getUsersByRoleId / getUsersByDptIdAndRoleId / 用户对象属性(getEmailgetTelephone)。
  • 视图过滤与列控制:视图列里嵌入 o-action 跳转按钮的列值脚本写法。
  • 权限与可见性控制:操作按钮的隐藏/只读(ACTIVITY:HIDDEN / READONLY)——决定谁能看到本章涉及的"跳转""通知"按钮。