流程常用开发技巧
启动流程
JS 启动流程
流程组件提供启动流程 start 方法
react 页面参考如下:
onJsStartFlowBtnClick = (event) => {
let processData = this.comp("mainData")
let processCode = 'react_daimaqdl';//要启动的流程编码
this.comp("wfmui").start(processCode, "js启动流程", processData.getCurrentRowID(), null, null);//js启动流程是流程标题
}
vue 页面参考如下:
let processData = useData("mainData");
//JS启动流程
let onJsStartFlowBtnClick = (event) => {
let processCode = 'vue_daimaqdlc';//要启动的流程编码
$page.comp("wfmui").start(processCode, "js启动流程", processData.getCurrentRowID(), null, null);//js启动流程是流程标题
message.info("启动成功!");
}
- mainData:要启动流程的业务数据的 id
- wfmui:流程组件的 id
- 流程编码:流程启动时指定,可以实现一个页面对应多个流程
Java 启动流程
通过 com.justep.util.process.ProcessUtil 中提供的方法 startProcess 实现,启动流程方法有不同参数的区分,具体可以参考《流程工具类 ProcessUtil》中的描述。如下是其中的一个方法
JSONObject attr = new JSONObject();
attr.put("sData2", sData2);
JSONObject vars = new JSONObject();
vars.put("test", "bb");//流程变量中定义一个test变量
ProcessResult result = ProcessUtil.startProcess(SpringWebUtil.getRequest(), "/SA/wf/default/liuchengqdcs", sData1, null, attr, vars);
List<Task> tasks = result.getActiveTasks();
- /SA/wf/default/liuchengqdcs:liuchengqdcs 是要启动的流程编码
- sData1:要启动流程的业务数据主键值
- attrs:任务属性,可以在这里设置 sData2、sData3 等任务属性的值
- vars:流程变量的初始值
调用 API 启动流程
流程提供启动流程 API,具体参考《流程动作 API》
流程启动的时候需要流程编码和业务数据的主键值,其它值按需传入。Java 示例代码如下:
public String startFlow(String proesscode,String sData1,String sData2) throws Exception {
HttpServletRequest request = SpringWebUtil.getRequest();
Map<String, String> headers = ServiceUtil.getExtHeaders(request);
String serviceUrl = "http://"+RequestUtil.getServiceGatewayHostname();
serviceUrl = serviceUrl+ "/wf/biz/process/start";
JSONObject attr = new JSONObject();
attr.put("sData1", sData1);
attr.put("sData2", sData2);
JSONObject vars = new JSONObject();
vars.put("test", "test");//流程变量中定义一个test变量
JSONObject params = new JSONObject();
params.put("process", proesscode);
params.put("attributes",attr);
params.put("vars",vars);
JSONArray result = ServiceUtil.post(serviceUrl, params, headers, JSONArray.class);
return reslut.toJSONString();
}
如上在需要的地方调用 startFlow 服务传流程编码、sData1、sData2 参数的值就可以启动流程
流转、回退、终止
JS 方法参考《工作流组件》中的流转、回退、终止等方法,示例代码如下
//终止流程
onEndBtnOnClick(event){
var wfmui = this.comp("wfmui");
wfmui.abortQuery();
}
//回退流程
onBackBtnOnClick(event){
var wfmui = this.comp("wfmui");
wfmui.backQuery();//按流程设置的回退规则进行回退
}
//流转流程
onAdvanceBtnOnClick(event){
var wfmui = this.comp("wfmui");
wfmui.advanceQuery();
}
Java 方法参考《流程工具类 ProcessUtil》中的流转、回退、终止等方法,示例代码如下
//终止流程
public void endFlow(String sData1) throws Exception {
ProcessUtil.abortProcess(SpringWebUtil.getRequest(), sData1, null);
}
//回退流程
public void backFlow(String sData1) throws Exception {
ProcessUtil.backProcess(SpringWebUtil.getRequest(), sData1, null);
}
//流转流程
public void advanceFlow(String sData1) throws Exception {
ProcessUtil.advanceProcess(SpringWebUtil.getRequest(), sData1, null);
}
API 参考《流程动作 API》中的流转、回退、终止等 API
回退到首环节
JS 回退到首环节
工作流组件提供回退查询 backQuery 方法,示例代码如下:
let wfmui = this.comp("wfmui");
let task = this.params.task;//task 参数是要回退的任务 ID
wfmui.backQuery(task, null, {backRange: "start"});
Java 回退到首环节
通过 com.justep.util.process.ProcessUtil 中提供的 backRootProcessQuery、backProcess 实现,示例代码如下:
ProcessControl control = ProcessUtil.backRootProcessQuery(SpringWebUtil.getRequest(), task);//task 参数是要回退的任务 ID
ProcessUtil.backProcess(SpringWebUtil.getRequest(), task, control);
结束任务或者通知
流程提供结束任务 API,具体参考《流程动作 API》。流程发的通知本质也是一条任务,所以结束通知就是结束任务
- JS 示例代码如下
通过 this.request 或者 $page.request 调用 wf/biz/process/task/finish 请求,具体参考如下:
let params = {
task: this.params.task;//task是要结束的任务id
};
try{
await this.request({
url: "/wf/biz/process/task/finish",
method: 'POST',
data: params
})
message.info("任务已结束!");
}catch(err){
message.error("任务结束失败!" + err);
}
- Java 示例代码如下
public void finishTask(String taskid) throws Exception {
JSONObject params = new JSONObject();
params.put("task", taskID);
ServiceUtil.post(SpringWebUtil.getRequest(), "wf", "/biz/process/task/finish", params);
}
强制删除已审批过的流程数据
“工作流管理-任务中心”提供的删除,是只有启动没有进入审批的才可以删除,已经审批过的不能删除
如果要强制删除已经审批过的流程,调用 com.justep.util.process.ProcessUtil 中提供的 deletePI 方法实现
deletePI(HttpServletRequest contextRequest, String task, boolean force)
功能:根据任务标识删除流程实例
参数:
contextRequest:当前请求对象
task:任务标识
force:是否强制删除
返回:
类型:void
参考:
deletePI(SpringWebUtil.getRequest(), "xx", true)
参考如下调用:
ProcessUtil.deletePI(SpringWebUtil.getRequest(),task,true);
流转后获取处理意见
前端页面中可以在工作流组件的流程成功事件中获取,事件提供的有 event 参数,具体的获取如下:
event.processControl.getPostscript();
后端可以在流转后调用服务获取,服务中获取的具体代码如下:
HttpServletRequest request = SpringWebUtil.getRequest();
InputStream is = request.getInputStream();
String taskInfo = IOUtils.toString(is);
JSONObject task = JSON.parseObject(taskInfo);
String content = task.getString("postscript");//获取处理意见
流转后获取下个环节任务 id
在前端工作流组件提供的流转成功事件中,获取的 task 值不是最终写入数据库可以进行流转的,要获取写入数据库可以流转的值,需要在后端流程事件中获取。
定义一个 post 类型的服务,在流程事件中调用服务,服务中从传入的参数中获取。服务 Java 代码说明如下:
需要 import 的类:
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
获取流程事件传入的参数
HttpServletRequest request = SpringWebUtil.getRequest();
InputStream is =request.getInputStream();
String taskInfo = IOUtils.toString(is);
JSONObject task = JSON.parseObject(taskInfo);
根据流程图的不同,task 里面的内容也不同,下面列举4个场景
一个环节一个执行者
JSONArray activeAIs = task.getJSONArray("activeAIs");
String taskID = activeAIs.getJSONObject(0).getString("task");
一个环节多个执行者
一个环节多人执行
- 抢占模式:或签(一个人同意即可),选择任何一个 task 的值进行流转就可以
- 顺序模式:依次顺序处理
- 共同模式:会签(须所有人同意),可以不按顺序执行
JSONArray activeAIs = task.getJSONArray("activeAIs");
List<String> taskIDs = new ArrayList<String>();
JSONArray executorTasks = activeAIs.getJSONObject(0).getJSONArray("executorTasks");
for(int i=0;i<executorTasks.size();i++) {
taskIDs.add(executorTasks.getJSONObject(i).getString("task"));
}
String taskIDsStr = StringUtils.join(taskIDs,","));
并行多个环节每个环节一个执行者
JSONArray activeAIs = task.getJSONArray("activeAIs");
List<String> taskIDs = new ArrayList<String>();
for(int i=0;i<activeAIs.size();i++) {
taskIDs.add(activeAIs.getJSONObject(i).getString("task"));
}
String taskIDsStr = StringUtils.join(taskIDs,","));
并行多个环节,环节中多人处理
JSONArray activeAIs = task.getJSONArray("activeAIs");
List<String> taskIDs = new ArrayList<String>();
for(int i=0;i<activeAIs.size();i++) {
JSONArray executorTasks = activeAIs.getJSONObject(i).getJSONArray("executorTasks");
for(int j=0;j<executorTasks.size();j++) {
taskIDs.add(executorTasks.getJSONObject(j).getString("task"));
}
}
String taskIDsStr = StringUtils.join(taskIDs,","));
前端获取当前环节
react体系:
this.params.activity
vue体系:
$page.params.activity
多个环节会签合并时判断是否是最后一个环节
多个环节会签(and)合并时,有些操作是需要在会签的最后一个环节流转时才执行,可以在前端页面的流程组件“流程查询成功”或者“流程成功”事件中判断是否是最后一个环节。
流程图如下:
活动环节3、活动环节4和活动环节5通过会签(and)合并(多个环节直接输出到一个环节默认就是会签),三个环节除了最后一个流转的环节,其他前面的环节流转时都是如下图的等待流转对话框,最后一个环节流转的时候才会显示下个环节和处理人的流转对话框
要判断是不是最后一个环节可以在页面的流程组件的“流程查询成功”或者“流程成功”事件写代码的方式判断,代码基本一样,如下:
let activity = this.params.activity;
if(activity == "businessActivity3" || activity == "businessActivity4" || activity == "businessActivity5" ){//判断是不是会签的环节
let processControl = event.processControl;//获取processControl
let flowTos = processControl.getToItems();
if(flowTos.length>0){////判断是否有flowTos
message.info("and 前的最后一个环节");
}
}
多人顺序或者同时处理时判断是否是最后一个人处理
一个环节在多人顺序或者同时处理时,有些操作是需要最后一个人处理时才执行的,可以在前端页面的流程组件“流程查询成功”或者“流程成功”事件中判断是否是最后一个人处理,代码基本一样,如下:
let activity = this.params.activity;
if(activity == "businessActivity3"){//判断是不是多人处理的环节
let processControl = event.processControl;
let flowTos = processControl.getToItems();
if(flowTos.length>0){//判断是最后一个人处理
message.info("最后一个人处理");
}
}
流程启动时把业务数据存到 sData2 中
当待办任务在不同的人之间传来传去时,首先要解决的一个问题就是如何让双方的业务表单保持一致。例如人员 A 通过待办任务把一个单号为 001 的申请单发给人员 B 审批,则人员 B 打开待办任务处理的时候看到的业务单据就应该是单号为 001 的申请单。
在待办任务中业务数据的传递是通过 sData 来实现的。sData 只是一个泛指,在待办任务的数据表 SA_Task 中它其实是靠4个字段来描述的,它们分别是sData1、sData2、sData3 和 sData4。当流程启动时,在调用流程启动动作(startProcessAction)的地方要求必须传入一个值为 sData1 的初始值。这个值被记录在 sData1 中在待办任务中一直传着走,在上例中从人员 A 的待办任务传入人员 B 的待办任务。当人员 B 打开待办任务处理时,从 sData1 中把值取出来参与业务表单的过滤。
一般情况下,赋值给 sData 的都是当前业务单据的单号,也就是业务主表的主键。业务主表只有一个主键时,自然 sData1 就够用了。如果业务主表是多主键则就需要把每个主键字段的值分别填写入 sData1、sData2、sData3 和 sData4。也就是说,目前只支持4个字段的联合主键。
默认的流程在启动的时候只会维护 sData1 的值,如果要同时维护 sData2 等的值,可以在页面的流程组件的事件中给 sData2 赋值。
流程组件的启动时机设置的是“保存”,在组件的“启动前”事件中给 sData2 赋值
流程组件的启动时机设置的是“流转”在组件的“启动流转查询前”事件中给 sData2 赋值
赋值的代码都是一样的,通过事件的 event 操作 relations,例如:
event.relations['sData2'] = 'abc'
流转过程中给任务扩展字段赋值
SA_Task 中提供了以 sESField、sEDField、sETField、sEIField、sEBField、sENField 开头,数据类型为 String、DateTime、Text、Integer、Blob、Decimal 的一组用户扩展字段,用于开发者存储业务信息。在流转过程中可以给任务的扩展字段赋值,最终存储到 SA_Task 中
在页面的流程组件的“流程查询成功”或“流转前”事件上,通过 processControl 获取到要生成的任务和通知,从而设置相关的扩展字段,示例代码如下:
let processControl = event.processControl;
let flowTos = processControl.getToItems();
if(flowTos.length>0){
for(let i=0;i<flowTos.length;i++){
flowTos[i].setTaskRelationValue("sESField01","给任务扩展字段赋值");
}
}
let noticeTos = processControl.getNoticeItems();
if(noticeTos.length>0){
for(let i=0;i<noticeTos.length;i++){
noticeTos[i].setTaskRelationValue("sESField01","给通知扩展字段赋值");
}
}
需要注意的是,如果要给第一个环节的任务设置扩展字段的值,需要在流程组件的“启动成功”事件中操作,示例代码如下:
let task = event.task;
let params = {
id: task,
sESField01: "给第一个环节任务扩展字段赋值"
};
this.request({
header: {
"Accept": "application/json"
},
url: "/wf/biz/task",
dataType: 'json',
method: 'POST',
data:params
});
统计当前人每种流程的任务数
计算当前登录者
- 待办任务中每种流程的数量
- 已办任务中每种流程的数量
- 待阅任务中每种流程的数量
- 已阅任务中每种流程的数量
调用流程组件提供的 countTaskByProcess 方法计算。在 JS 代码中引用流程组件的 taskUtil.js,代码如下
import TaskUtils from "$UI/comp/wfmui/components/wfmui/js/taskUtil";
countTaskByProcess 方法提供两个参数 option 和 page。其中 option 是 json 对象,包括查询条件等参数,page 传入 this。option 中包含的参数如下
{
"personFIDs": [],//当前人的fid数组
"kind": "task",//task代表查询任务 notice代表查询通知
"status": "wait",//wait代表查询待办 finish代表查询已办
"filter": "",//过滤条件
"orderBy": "",//排序
"limit": "",//分页数据大小
"offset": ""//偏移量
}
查询当前人每种流程的待办任务数量的代码如下
//使用上下文组件获取当前人的fid数组
let personMembers = this.comp("wxContext0").getAllPersonMembers();
let personFIDs = [];
personMembers.forEach((person) => {
personFIDs.push(person.fid);
});
TaskUtils.countTaskByProcess({
"personFIDs": personFIDs,//当前人的fid数组
"kind": "task",//task任务 notice通知
"status": "wait"//wait待办 finish已办
},this).then(function(res){
console.log(res);
})
控制台输出如下图所示
修改或签默认选中的环节
或签多个环节流转时默认选中的环节不是需要的,要调整默认选中的活动环节
如下默认选中的是"活动环节3",要修改为默认选中“活动环节4”
在流转对话框打开前修改 processControl。在页面的流程组件的“打开对话框前”事件中可以获取到流转对话框中需要的数据
修改对应的数据,示例代码如下:
let processControl = event.processControl;//获取processControl
let activities = processControl.getActivities();
if(activities && activities.length >0){
if(activities[0].isXor()){//判断是不是xor,如果是获取children进行处理
let children = activities[0].getChildren();
let childrens = activities[0].activityData["@@children"];//获取xor下的所有环节的数组
if (children && children.length>0){
for (let i=0; i<children.length; i++){
if(children[i].getName()=="businessActivity4"){//判断是不是要默认选中的环节,如果是就把这环节放到数组的第一个元素位置
[childrens[0],childrens[i]] = [childrens[i],childrens[0]];
}
}
}
}
}
通过上面的代码就可以实现默认选中"活动环节4"
修改会签显示的环节顺序
会签多个环节流转时默认显示的环节顺序不是需要的,要调整默认的显示顺序
如下流程图:
运行时默认显示的顺序如下图,要调整为部门领导审核在第一个、技术总监审核在第二个、总经理审核在最后
在页面的流程组件的“打开对话框前”事件中,可以获取到流转对话框中需要的数据
修改对应的数据,示例代码如下:
let processControl = event.processControl;//获取processControl
let activities = processControl.getActivities();
if(activities && activities.length >0){
if(activities[0].isAnd()){//判断是不是and,如果是获取children进行处理
let children = activities[0].getChildren();
let childrens = activities[0].activityData["@@children"];//获取and下的所有环节的数组,判断是不是需要的顺序,不是就进行调整
if (children && children.length>0){
for (let i=0; i<children.length; i++){
if(i!= 0 && children[i].getLabel()=="部门领导审核"){//判断要设置为第一个的环节不是在第一个就调整为第一个
[childrens[0],childrens[i]] = [childrens[i],childrens[0]];
}else if(i!=1 && children[i].getLabel()=="技术总监审核"){
[childrens[1],childrens[i]] = [childrens[i],childrens[1]]; //判断要设置为第二个的环节不是在第二个就调整为第二个
}else if(i!=children.length-1 && children[i].getLabel()=="总经理审核"){
[childrens[children.length-1],childrens[i]] = [childrens[i],childrens[children.length-1]];
}
}
}
}
通过上面的代码就可以实现需要的显示顺序
流转时通知默认不选中(47)
流程和环节上都可以设置通知规则,在不同操作(启动、流转、回退等)时会在流程对话框中显示通知,并且是默认选中的状态,如果要控制默认不选中可以在“流程”组件的“打开对话框前”事件中修改procssControl中的通知信息,如下:
let processControl = event.processControl;//获取processControl
let noticeItems = processControl.getNoticeItems();
if(noticeItems && noticeItems.length>0){
for(let i=0;i<noticeItems.length;i++){
noticeItems[i].setSelected(false);
}
}
不同环节使用不同页面
同一个流程的不同环节所使用的UI界面可能会有差异,如果差异不大,可以使用一个页面,通过控制页面元素的展现来体现页面的差异。如果差异大,那可以设计不同的页面,在环节上绑定需要的页面即可。操作步骤如下:
选定目标环节,进入高级设计的更多设置;
在执行规则中绑定目标页面。
特别说明
- “表单地址”中列出的是设置为流程的表单页面,在页面配置中选中流程,表示流程的表单页面
- 该表单从我的待办打开前,需要先从功能树上打开一次,执行页面的首次编译