流程常用开发技巧

启动流程

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 前的最后一个环节");
    }
}

多人顺序或者同时处理时判断是否是最后一个人处理

一个环节在多人顺序或者同时处理时,有些操作是需要最后一个人处理时才执行的,可以在前端页面的流程组件“流程查询成功”或者“流程成功”事件中判断是否是最后一个人处理,代码基本一样,如下:

1722496721518

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 赋值

1722495802163

流程组件的启动时机设置的是“流转”在组件的“启动流转查询前”事件中给 sData2 赋值

1722495818428

1722495828479

赋值的代码都是一样的,通过事件的 event 操作 relations,例如:

event.relations['sData2'] = 'abc'

流转过程中给任务扩展字段赋值

SA_Task 中提供了以 sESField、sEDField、sETField、sEIField、sEBField、sENField 开头,数据类型为 String、DateTime、Text、Integer、Blob、Decimal 的一组用户扩展字段,用于开发者存储业务信息。在流转过程中可以给任务的扩展字段赋值,最终存储到 SA_Task 中

在页面的流程组件的“流程查询成功”或“流转前”事件上,通过 processControl 获取到要生成的任务和通知,从而设置相关的扩展字段,示例代码如下:

1722493992240

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","给通知扩展字段赋值");
    }
}

需要注意的是,如果要给第一个环节的任务设置扩展字段的值,需要在流程组件的“启动成功”事件中操作,示例代码如下:

1722494002842

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);
        })

控制台输出如下图所示

修改或签默认选中的环节

或签多个环节流转时默认选中的环节不是需要的,要调整默认选中的活动环节

1720604878087

如下默认选中的是"活动环节3",要修改为默认选中“活动环节4”

1720604904560

在流转对话框打开前修改 processControl。在页面的流程组件的“打开对话框前”事件中可以获取到流转对话框中需要的数据

1720604966823

修改对应的数据,示例代码如下:

    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"

1720604940633

修改会签显示的环节顺序

会签多个环节流转时默认显示的环节顺序不是需要的,要调整默认的显示顺序

如下流程图:

1720604711512

运行时默认显示的顺序如下图,要调整为部门领导审核在第一个、技术总监审核在第二个、总经理审核在最后

1720604684856

在页面的流程组件的“打开对话框前”事件中,可以获取到流转对话框中需要的数据

1720612181616

修改对应的数据,示例代码如下:

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]];
                }
            }
        }
    }

通过上面的代码就可以实现需要的显示顺序

1720604742570

流转时通知默认不选中(47)

流程和环节上都可以设置通知规则,在不同操作(启动、流转、回退等)时会在流程对话框中显示通知,并且是默认选中的状态,如果要控制默认不选中可以在“流程”组件的“打开对话框前”事件中修改procssControl中的通知信息,如下:

1737454002514

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界面可能会有差异,如果差异不大,可以使用一个页面,通过控制页面元素的展现来体现页面的差异。如果差异大,那可以设计不同的页面,在环节上绑定需要的页面即可。操作步骤如下:

选定目标环节,进入高级设计的更多设置;

image.png

在执行规则中绑定目标页面。

image.png

特别说明

  • “表单地址”中列出的是设置为流程的表单页面,在页面配置中选中流程,表示流程的表单页面
  • 该表单从我的待办打开前,需要先从功能树上打开一次,执行页面的首次编译

results matching ""

    No results matching ""