更多nbcio-boot功能请看演示系统
gitee源代码地址
后端代码: https://gitee.com/nbacheng/nbcio-boot
前端代码:https://gitee.com/nbacheng/nbcio-vue.git
在线演示(包括H5) : http://122.227.135.243:9888
对于之前的flowable流程,之前有撤回,拒绝,退回等功能,但都不能满足发起人对于流程收回的功能,发起人收回后可以重新进行流程发起,同时能够支持自定义业务的收回功能。
从目前开源项目与全网的资料看都没有找到相关资料,所以只能自己来写相应的功能,满足用户的需求了。
版权声明:大家要是单独用我的代码,请注明作者。
1、首先前端功能
前端比较简单,只要在已办功能里增加收回菜单功能,同时调用后端代码来实现。
增加一个菜单按钮
增加一个收回任务函数
完整的代码如下:
<template><a-card :bordered="false"><!-- 查询区域 --><div class="table-page-search-wrapper"><a-form layout="inline" @keyup.enter.native="handleQuery"><a-row :gutter="24"><a-col :md="6" :sm="8"><a-form-item label="流程名称"><a-input placeholder="请输入流程名称" v-model="queryParams.procDefName"></a-input></a-form-item></a-col><a-col :md="8" :sm="24"><a-form-item label="接收日期"><a-date-picker v-model="queryParams.createTime" style="width: 100%" placeholder="请输入接收日期"/></a-form-item></a-col><a-col :md="6" :sm="8"><span style="float: left;overflow: hidden;" class="table-page-search-submitButtons"><a-button type="primary" @click="handleQuery" icon="search">查询</a-button><a-button type="primary" @click="searchReset" icon="reload" style="margin-left: 8px">重置</a-button></span></a-col></a-row></a-form></div><!-- 查询区域-END --><!-- 操作按钮区域 --><div class="table-operator"><a-button type="primary" icon="download" @click="handleExportXls('待办任务')">导出</a-button><a-dropdown v-if="selectedRowKeys.length > 0"><a-menu slot="overlay"><a-menu-item key="1" @click="batchDel"><a-icon type="delete"/>删除</a-menu-item></a-menu><a-button style="margin-left: 8px"> 批量操作 <a-icon type="down" /></a-button></a-dropdown></div><!-- table区域-begin --><div><div class="ant-alert ant-alert-info" style="margin-bottom: 16px;"><i class="anticon anticon-info-circle ant-alert-icon"></i> 已选择 <a style="font-weight: 600">{{ selectedRowKeys.length }}</a>项<a style="margin-left: 24px" @click="onClearSelected">清空</a></div><a-tableref="table"size="middle":scroll="{x:true}"borderedrowKey="procInsId":columns="columns":dataSource="dataSource":pagination="ipagination":loading="loading":rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"class="j-table-force-nowrap"@change="handleTableChange"><template slot="procDefVersion" slot-scope="text, record, index"><el-tag size="medium" >V{{ record.procDefVersion }}</el-tag></template><template slot="startUserName" slot-scope="text, record, index"><label>{{record.startUserName}} <el-tag type="info" size="mini">{{record.startDeptName}}</el-tag></label></template><template slot="htmlSlot" slot-scope="text"><div v-html="text"></div> </template><template slot="imgSlot" slot-scope="text"><span v-if="!text" style="font-size: 12px;font-style: italic;">无图片</span><img v-else :src="getImgView(text)" height="25px" alt="" style="max-width:80px;font-size: 12px;font-style: italic;"/></template><template slot="fileSlot" slot-scope="text"><span v-if="!text" style="font-size: 12px;font-style: italic;">无文件</span><a-buttonv-else:ghost="true"type="primary"icon="download"size="small"@click="downloadFile(text)">下载</a-button></template><span slot="action" slot-scope="text, record"><a-dropdown><a class="ant-dropdown-link">更多 <a-icon type="down" /></a><a-menu slot="overlay"><a-menu-item><a @click="handleFlowRecord(record)">流转记录</a></a-menu-item><a-menu-item><a @click="handleRecall(record)"> 收回</a></a-menu-item><a-menu-item><a @click="handleRevoke(record)"> 撤回</a></a-menu-item></a-menu></a-dropdown></span></a-table></div></a-card>
</template><script>import '@/assets/less/TableExpand.less'import { mixinDevice } from '@/utils/mixin'import { JeecgListMixin } from '@/mixins/JeecgListMixin' import { finishedList, finishedListNew, getDeployment, delDeployment, addDeployment, updateDeployment, exportDeployment, revokeProcess, recallProcess } from "@/views/flowable/api/finished";import moment from 'moment';
export default {name: "finishedIndex",mixins:[JeecgListMixin, mixinDevice],components: {},data() {return {// 表头columns: [{title: '#',dataIndex: '',key:'rowIndex',width:60,align:"center",customRender:function (t,r,index) {return parseInt(index)+1;}},{title:'任务编号',align:"center",dataIndex: 'procInsId',},{title:'流程名称',align:"center",dataIndex: 'procDefName',},{title:'任务节点',align:"center",dataIndex: 'taskName',},{title:'流程类别',align:"center",dataIndex: 'category'},{title:'流程版本',align:"center",dataIndex: 'procDefVersion',scopedSlots: { customRender: 'procDefVersion' }},{title:'业务主键',align:"center",dataIndex: 'businessKey'},{title:'流程发起人',align:"center",dataIndex: 'startUserName',scopedSlots: { customRender: 'startUserName' }},{title:'接收时间',align:"center",dataIndex: 'createTime'},{title:'审批时间',align:"center",dataIndex: 'finishTime'},{title:'耗时',align:"center",dataIndex: 'duration'},{title: '操作',dataIndex: 'action',align:"center",fixed:"right",width:147,scopedSlots: { customRender: 'action' }}],// 查询参数queryParams: {pageNo: 1,pageSize: 10,name: null,category: null,key: null,tenantId: null,deployTime: null,derivedFrom: null,derivedFromRoot: null,parentDeploymentId: null,engineVersion: null,procDefName: null,createTime: null},url: {list: "/flowable/task/finishedListNew",deleteBatch: "/flowable/task/deleteBatch",exportXlsUrl: "/flowable/task/finishedExportXls",},dataSource: [], //表格数据源/* 表格分页参数 */ipagination:{current: 1,pageSize: 10,pageSizeOptions: ['10', '20', '30'],showTotal: (total, range) => {return range[0] + "-" + range[1] + " 共" + total + "条"},showQuickJumper: true,showSizeChanger: true,total: 0},// 遮罩层loading: true,// 选中数组ids: [],// 非单个禁用single: true,// 非多个禁用multiple: true,// 显示搜索条件showSearch: true,// 总条数total: 0,// 已办任务列表数据finishedList: [],// 弹出层标题title: "",// 是否显示弹出层open: false,src: "",// 查询参数queryParams: {pageNo: 1,pageSize: 10,name: null,category: null,key: null,tenantId: null,deployTime: null,derivedFrom: null,derivedFromRoot: null,parentDeploymentId: null,engineVersion: null},// 表单参数form: {},// 表单校验rules: {}};},created() {this.getSuperFieldList();//this.getList();},methods: {/** 查询流程定义列表 */getList() {this.loading = true;finishedListNew(this.queryParams).then(response => {if(response.success) {this.dataSource = response.result.records;this.total = response.result.total;this.ipagination.total = response.result.total; this.loading = false;}else {this.$message.error(response.message)this.loading = false;}});},// 取消按钮cancel() {this.open = false;this.reset();},// 表单重置reset() {this.form = {id: null,name: null,category: null,key: null,tenantId: null,deployTime: null,derivedFrom: null,derivedFromRoot: null,parentDeploymentId: null,engineVersion: null};this.resetForm("form");},setIcon(val){if (val){return "el-icon-check";}else {return "el-icon-time";}},setColor(val){if (val){return "#2bc418";}else {return "#b3bdbb";}},initDictConfig(){},getSuperFieldList(){let fieldList=[];fieldList.push({type:'string',value:'procInsId',text:'任务编号'})fieldList.push({type:'string',value:'procDefName',text:'流程名称'})fieldList.push({type:'string',value:'taskName',text:'任务节点'})fieldList.push({type:'string',value:'category',text:'流程类别'})fieldList.push({type:'string',value: 'procDefVersion',text:'流程版本'})fieldList.push({type:'string',value: 'businessKey',text:'业务主键'})fieldList.push({type:'string',value:'startUserName',text:'流程发起人'})fieldList.push({type:'datetime',value:'createTime',text:'接收时间'})fieldList.push({type:'datetime',value:'finishTime',text:'审批时间'})fieldList.push({type:'string',value:'duration',text:'耗时'})this.superFieldList = fieldList},/** 搜索按钮操作 */handleQuery() {this.queryParams.pageNum = 1;this.getList();},/** 重置按钮操作 */resetQuery() {this.resetForm("queryForm");this.handleQuery();},// 多选框选中数据handleSelectionChange(selection) {this.ids = selection.map(item => item.id)this.single = selection.length!==1this.multiple = !selection.length},/** 新增按钮操作 */handleAdd() {this.reset();this.open = true;this.title = "添加流程定义";},/** 流程流转记录 */handleFlowRecord(row){this.$router.push({ path: '/flowable/task/record/index',query: {procInsId: row.procInsId,deployId: row.deployId,taskId: row.taskId,businessKey: row.businessKey,category: row.category,finished: false}})},/** 撤回任务 */handleRevoke(row){const params = {instanceId: row.procInsId,dataId: row.businessKey}revokeProcess(params).then( res => {this.$message.success(res.message);this.getList();});},/** 收回任务 */handleRecall(row){const params = {instanceId: row.procInsId,dataId: row.businessKey}recallProcess(params).then( res => {this.$message.success(res.message);this.getList();});},/** 提交按钮 */submitForm() {this.$refs["form"].validate(valid => {if (valid) {if (this.form.id != null) {updateDeployment(this.form).then(response => {this.$message.success("修改成功");this.open = false;this.getList();});} else {addDeployment(this.form).then(response => {this.$message.success("新增成功");this.open = false;this.getList();});}}});},/** 删除按钮操作 */handleDelete(row) {const ids = row.id || this.ids;const dataid = row.businessKey; this.$confirm('是否确认删除流程定义编号为"' + ids + '"的数据项?', "警告", {confirmButtonText: "确定",cancelButtonText: "取消",type: "warning"}).then(function() {return delDeployment(ids,dataid);}).then(() => {this.getList();this.$message.success("删除成功");})},/** 导出按钮操作 */handleExport() {const queryParams = this.queryParams;this.$confirm('是否确认导出所有流程定义数据项?', "警告", {confirmButtonText: "确定",cancelButtonText: "取消",type: "warning"}).then(function() {return exportDeployment(queryParams);}).then(response => {this.download(response.message);})}}
};
</script>
2、后端代码
2.1 增加一个收回流程功能recallProcess,具体代码如下:
/*** 发起人收回流程* add by nbacheng* * @param FlowTaskVo taskVo* * @return*/@Override@Transactionalpublic Result recallProcess(FlowTaskVo flowTaskVo) {// 当前任务 listtaskList<Task> listtask = taskService.createTaskQuery().processInstanceId(flowTaskVo.getInstanceId()).active().list();if (listtask == null) {throw new CustomException("流程未启动或已执行完成,无法收回");}if (taskService.createTaskQuery().taskId(listtask.get(0).getId()).singleResult().isSuspended()) {throw new CustomException("任务处于挂起状态");}List<Task> procInsId = taskService.createNativeTaskQuery().sql("select * from ACT_HI_TASKINST where PROC_INST_ID_ = #{procInsId} ORDER BY START_TIME_ desc").parameter("procInsId", flowTaskVo.getInstanceId()).list();SysUser loginUser = iFlowThirdService.getLoginUser();String processInstanceId = listtask.get(0).getProcessInstanceId();// 获取所有历史任务(按创建时间升序)List<HistoricTaskInstance> hisTaskList = historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstanceId).orderByTaskCreateTime().asc().list();if (CollectionUtil.isEmpty(hisTaskList) || hisTaskList.size() < 2) {log.error("当前流程 【{}】 审批节点 【{}】正在初始节点无法收回", processInstanceId, listtask.get(0).getName());throw new FlowableException(String.format("当前流程 【%s】 审批节点【%s】正在初始节点无法收回", processInstanceId, listtask.get(0).getName()));}// 第一个任务HistoricTaskInstance startTask = hisTaskList.get(0);//若操作用户不是发起人,不能收回if(!StringUtils.equalsAnyIgnoreCase(loginUser.getUsername(), startTask.getAssignee())) {throw new CustomException("操作用户不是发起人,不能收回");}// 当前任务HistoricTaskInstance currentTask = hisTaskList.get(hisTaskList.size() - 1);BpmnModel bpmnModel = repositoryService.getBpmnModel(listtask.get(0).getProcessDefinitionId());// 获取第一个活动节点FlowNode startFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(startTask.getTaskDefinitionKey());// 获取当前活动节点FlowNode currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentTask.getTaskDefinitionKey());// 临时保存当前活动的原始方向List<SequenceFlow> originalSequenceFlowList = new ArrayList<>(currentFlowNode.getOutgoingFlows());// 清理活动方向currentFlowNode.getOutgoingFlows().clear();// 建立新方向SequenceFlow newSequenceFlow = new SequenceFlow();newSequenceFlow.setId("newSequenceFlowId");newSequenceFlow.setSourceFlowElement(currentFlowNode);newSequenceFlow.setTargetFlowElement(startFlowNode);List<SequenceFlow> newSequenceFlowList = new ArrayList<>();newSequenceFlowList.add(newSequenceFlow);// 当前节点指向新的方向currentFlowNode.setOutgoingFlows(newSequenceFlowList);// 完成当前任务for(Task task : listtask) {taskService.addComment(task.getId(), listtask.get(0).getProcessInstanceId(),FlowComment.RECALL.getType(), "发起人收回");taskService.setAssignee(task.getId(), startTask.getAssignee());taskService.complete(task.getId());}// 重新查询当前任务Task nextTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();if (ObjectUtil.isNotNull(nextTask)) {taskService.setAssignee(nextTask.getId(), startTask.getAssignee());//taskService.complete(nextTask.getId());;//跳过流程发起节点}//自定义业务处理idString dataId = flowTaskVo.getDataId();// 删除运行和历史的节点信息 this.deleteActivity(procInsId.get(1).getTaskDefinitionKey(), flowTaskVo.getInstanceId(), dataId);// 恢复原始方向currentFlowNode.setOutgoingFlows(originalSequenceFlowList);//自定义业务处理if(StrUtil.isNotBlank(flowTaskVo.getDataId()) && !Objects.equals(flowTaskVo.getDataId(), "null")){//如果保存数据前未调用必调的FlowCommonService.initActBusiness方法,就会有问题FlowMyBusiness business = flowMyBusinessService.getByDataId(dataId);//删除自定义业务任务关联表,以便可以重新发起流程if (business != null) {flowMyBusinessService.removeById(business);}}return Result.OK("发起人收回成功");}
2.2 调用 删除历史节点信息deleteActivity
/*** 删除跳转的历史节点信息** @param disActivityId 跳转的节点id* @param processInstanceId 流程实例id* @param dataId 自定义业务id*/protected void deleteActivity(String disActivityId, String processInstanceId, String dataId) {List<ActivityInstance> disActivities = flowTaskMapper.queryActivityInstance(disActivityId, processInstanceId, null);//删除运行时和历史节点信息if (CollectionUtils.isNotEmpty(disActivities)) {ActivityInstance activityInstance = disActivities.get(0);List<ActivityInstance> datas = flowTaskMapper.queryActivityInstance(disActivityId, processInstanceId, activityInstance.getEndTime());//datas.remove(0); //保留流程发起节点信息List<String> runActivityIds = new ArrayList<>();if (CollectionUtils.isNotEmpty(datas)) {datas.forEach(ai -> runActivityIds.add(ai.getId()));flowTaskMapper.deleteRunActinstsByIds(runActivityIds);flowTaskMapper.deleteHisActinstsByIds(runActivityIds);}if(dataId != null) {//对于自定义业务, 删除所有相关流程信息//flowTaskMapper.deleteAllHisAndRun(processInstanceId);//根据流程实例id 删除去ACT_RU_*与ACT_HI_*流程实例数据ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();if (null != processInstance) {runtimeService.deleteProcessInstance(processInstanceId, "流程实例删除");historyService.deleteHistoricProcessInstance(processInstanceId);}}}}
2.3 FlowTaskMapper.xml文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nbcio.modules.flowable.mapper.FlowTaskMapper"><select id="queryActivityInstance" resultType="org.flowable.engine.impl.persistence.entity.ActivityInstanceEntityImpl">select t.* fromact_ru_actinst t<where><if test="processInstanceId !=null and processInstanceId != ''" >t.PROC_INST_ID_=#{processInstanceId} and ACT_TYPE_ = 'userTask' and END_TIME_ is not null </if></where>order by t.END_TIME_ ASC</select><delete id="deleteRunActinstsByIds" parameterType="java.util.List">delete from act_ru_actinst where ID_ in<foreach item="item" index="index" collection="list" open="(" separator="," close=")">#{item}</foreach></delete><delete id="deleteHisActinstsByIds" parameterType="java.util.List">delete from act_hi_actinst where ID_ in<foreach item="item" index="index" collection="list" open="(" separator="," close=")">#{item}</foreach></delete><delete id="deleteAllHisAndRun" parameterType="String">delete from act_ru_actinst where proc_inst_id_ = #{processInstanceId}; delete from act_ru_identitylink where proc_inst_id_ = #{processInstanceId};delete from act_ru_task where proc_inst_id_ = #{processInstanceId};delete from act_ru_variable where proc_inst_id_ = #{processInstanceId};delete from act_ru_execution where proc_inst_id_ = #{processInstanceId};delete from act_hi_actinst where proc_inst_id_ = #{processInstanceId};delete from act_hi_comment where proc_inst_id_ = #{processInstanceId};delete from act_hi_identitylink where proc_inst_id_ = #{processInstanceId};delete from act_hi_procinst where proc_inst_id_ = #{processInstanceId};delete from act_hi_taskinst where proc_inst_id_ = #{processInstanceId};delete from act_hi_varinst where proc_inst_id_ = #{processInstanceId};</delete>
</mapper>
3、自定义业务与其它流程做分别处理
其它流程直接删除相关用户任务历史信息,保留初始发送,用户可以直接进行流程重新编辑发送。
而自定义业务则删除所有实例相关的任务历史信息,不保留任务相关信息,同时删除自定义业务发起时候的写入的关联表,以便用户可以再次发起流程。