消息通知与跳转导航¶
一句话定位:表单/视图操作发生后或流程干预后**通知人**(邮件、站内信、微信待办);以及操作点击或菜单点击时**跳转页面**(表单、视图、图表、报表、自定义 Vue 页、外部 URL)。落点以表单/视图的 操作脚本(
ACTIVITY:*) 与 菜单脚本(MENU:LINK_CONTENT) 为主,外加o-action标签这种写在视图列/计算脚本里的声明式跳转。
写在前面——通知函数签名不复述
本书不复述邮件/消息函数的参数签名,请对照查阅:
- 邮件:
MAIL.sendMail/MAIL.sendEmailBySystemUser/MAIL.sendMailWithAttachments/MAIL.sendEmailWithAttachmentsBySystemUser→iscript-guid/functions→ 邮件函数 - 站内信/短信:
MESSAGE.sendMessage/sendMessageByDept/sendMessageByRole/sendSMS/sendSMS4Task→iscript-guid/functions→ 信息函数
关于微信待办/企业微信消息:源素材
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())、RelateDocument、ParentDocument。
返回值契约¶
无强制返回值。脚本以副作用方式发邮件,平台忽略返回值。仍建议包成 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 → doc、iscript-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入参用,拼接多个邮箱;密送参数bbc仅sendMail支持。 - 前置确认弹窗:在
ACTIVITY:BEFORE写createConfirm("确认提交并发送通知?"),先确认再放行——前后置配合形成"先问再做、做完通知"的完整闭环。
常见坑¶
ACTIVITY:AFTERvsACTIVITY: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) 取文档)。
触发时机与上下文¶
- 触发时机:操作成功后(同「表单按钮执行后发邮件」)。
- 可用环境变量:
WebUser、CurrentDocument。
返回值契约¶
无强制返回值,副作用调用 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("客服组") 返回 String 或 null |
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);当senderId与receiverId相同时只通知自己。 - 手机短信:
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()取,不要硬编码。- 角色名找不到返回
null:getRoleIdByName是精确匹配,角色改名后旧脚本会失效;上线前到角色管理核对名称拼写。 sendMessage与sendMessageByRole参数顺序易混:前者(senderid, receiverid, ...),后者(roleid, domainid, ...);签名细节点信息函数。- 跨表单取收件人:用
queryBySQL查t_user时返回 Java List,长度.size()、取元素.get(i)、遍历.iterator()——不是 JS 数组的.length。详见 GraalVM 差异。 - 短信
isMass与分隔符:群发receiver多个手机号用,分隔;isMass须与接收者数量一致,否则部分网关会拒收。
流程终止 / 干预后通知经办人¶
业务目标¶
强制终止流程或干预提交(管理员跳过常规审批)后,给原来的审批人、申请人发站内信告知——避免他们继续等一个永远不会到的待办,是流程治理的"善后通知"。挂载点与 流程查询、审批历史与人工干预 的"强制终止"场景同源,这里只聚焦通知部分。
写在哪儿¶
表单 → 表单按钮 → **操作前置 / 操作后置**脚本(.activity;属性 beforeActionScript / afterActionScript)。终止动作本身(flowProcess.doTerminate)建议放前置,通知放在终止成功之后的同一段脚本末尾(顺序执行)。
触发时机与上下文¶
- 触发时机:用户点击表单上的"终止流程""干预提交"按钮(自定义操作按钮)。
- 可用环境变量:
WebUser、CurrentDocument;通过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.sendEmailBySystemUser或sendSMS;短信需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 数组:
actors是queryBySQL返回的 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 → 表单跳转脚本。
触发时机与上下文¶
- 触发时机:点击跳转类操作按钮时。
- 可用环境变量:
WebUser、CurrentDocument、Params(含_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...vsJava.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 → 菜单脚本链接自定义页面 |
变体与扩展¶
- 直接跳外部 URL:
return "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 + 业务表自建待办 |
取 userId(getWebUser().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。getRoleByNamevsgetRoleIdByName:社区脚本里用getRoleByName(roleName).getUsers()取角色对象再取用户;标准 API 里更常见getRoleIdByName+getUsersByRoleId。两种写法都可行,不要混用同一个变量名(rolevsroleId)。- 用户未绑微信/邮箱:模式函数静默失败或抛异常,需
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 标签。
标签语法¶
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/ 用户对象属性(getEmail、getTelephone)。 - 视图过滤与列控制:视图列里嵌入
o-action跳转按钮的列值脚本写法。 - 权限与可见性控制:操作按钮的隐藏/只读(
ACTIVITY:HIDDEN/READONLY)——决定谁能看到本章涉及的"跳转""通知"按钮。