更多nbcio-boot功能請(qǐng)看演示系統(tǒng)
gitee源代碼地址
后端代碼: https://gitee.com/nbacheng/nbcio-boot
前端代碼:https://gitee.com/nbacheng/nbcio-vue.git
在線演示(包括H5) : http://122.227.135.243:9888
? ? ? ?
? ? ? 對(duì)于之前的flowable流程,之前有撤回,拒絕,退回等功能,但都不能滿足發(fā)起人對(duì)于流程收回的功能,發(fā)起人收回后可以重新進(jìn)行流程發(fā)起,同時(shí)能夠支持自定義業(yè)務(wù)的收回功能。
? ? ? 從目前開源項(xiàng)目與全網(wǎng)的資料看都沒有找到相關(guān)資料,所以只能自己來寫相應(yīng)的功能,滿足用戶的需求了。
? ? ? 版權(quán)聲明:大家要是單獨(dú)用我的代碼,請(qǐng)注明作者。
? ? ?1、首先前端功能
? ? ? ?前端比較簡(jiǎn)單,只要在已辦功能里增加收回菜單功能,同時(shí)調(diào)用后端代碼來實(shí)現(xiàn)。
? ? ? ? 增加一個(gè)菜單按鈕
? ? ? 增加一個(gè)收回任務(wù)函數(shù)
? ? ? 完整的代碼如下:
<template>
<a-card :bordered="false">
<!-- 查詢區(qū)域 -->
<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="請(qǐng)輸入流程名稱" 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="請(qǐng)輸入接收日期"/>
</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>
<!-- 查詢區(qū)域-END -->
<!-- 操作按鈕區(qū)域 -->
<div class="table-operator">
<a-button type="primary" icon="download" @click="handleExportXls('待辦任務(wù)')">導(dǎo)出</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區(qū)域-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>項(xiàng)
<a style="margin-left: 24px" @click="onClearSelected">清空</a>
</div>
<a-table
ref="table"
size="middle"
:scroll="{x:true}"
bordered
rowKey="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-button
v-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)">流轉(zhuǎn)記錄</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:'任務(wù)編號(hào)',
align:"center",
dataIndex: 'procInsId',
},
{
title:'流程名稱',
align:"center",
dataIndex: 'procDefName',
},
{
title:'任務(wù)節(jié)點(diǎn)',
align:"center",
dataIndex: 'taskName',
},
{
title:'流程類別',
align:"center",
dataIndex: 'category'
},
{
title:'流程版本',
align:"center",
dataIndex: 'procDefVersion',
scopedSlots: { customRender: 'procDefVersion' }
},
{
title:'業(yè)務(wù)主鍵',
align:"center",
dataIndex: 'businessKey'
},
{
title:'流程發(fā)起人',
align:"center",
dataIndex: 'startUserName',
scopedSlots: { customRender: 'startUserName' }
},
{
title:'接收時(shí)間',
align:"center",
dataIndex: 'createTime'
},
{
title:'審批時(shí)間',
align:"center",
dataIndex: 'finishTime'
},
{
title:'耗時(shí)',
align:"center",
dataIndex: 'duration'
},
{
title: '操作',
dataIndex: 'action',
align:"center",
fixed:"right",
width:147,
scopedSlots: { customRender: 'action' }
}
],
// 查詢參數(shù)
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: [], //表格數(shù)據(jù)源
/* 表格分頁參數(shù) */
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,
// 選中數(shù)組
ids: [],
// 非單個(gè)禁用
single: true,
// 非多個(gè)禁用
multiple: true,
// 顯示搜索條件
showSearch: true,
// 總條數(shù)
total: 0,
// 已辦任務(wù)列表數(shù)據(jù)
finishedList: [],
// 彈出層標(biāo)題
title: "",
// 是否顯示彈出層
open: false,
src: "",
// 查詢參數(shù)
queryParams: {
pageNo: 1,
pageSize: 10,
name: null,
category: null,
key: null,
tenantId: null,
deployTime: null,
derivedFrom: null,
derivedFromRoot: null,
parentDeploymentId: null,
engineVersion: null
},
// 表單參數(shù)
form: {},
// 表單校驗(yàn)
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:'任務(wù)編號(hào)'})
fieldList.push({type:'string',value:'procDefName',text:'流程名稱'})
fieldList.push({type:'string',value:'taskName',text:'任務(wù)節(jié)點(diǎn)'})
fieldList.push({type:'string',value:'category',text:'流程類別'})
fieldList.push({type:'string',value: 'procDefVersion',text:'流程版本'})
fieldList.push({type:'string',value: 'businessKey',text:'業(yè)務(wù)主鍵'})
fieldList.push({type:'string',value:'startUserName',text:'流程發(fā)起人'})
fieldList.push({type:'datetime',value:'createTime',text:'接收時(shí)間'})
fieldList.push({type:'datetime',value:'finishTime',text:'審批時(shí)間'})
fieldList.push({type:'string',value:'duration',text:'耗時(shí)'})
this.superFieldList = fieldList
},
/** 搜索按鈕操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按鈕操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
// 多選框選中數(shù)據(jù)
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length!==1
this.multiple = !selection.length
},
/** 新增按鈕操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加流程定義";
},
/** 流程流轉(zhuǎn)記錄 */
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
}})
},
/** 撤回任務(wù) */
handleRevoke(row){
const params = {
instanceId: row.procInsId,
dataId: row.businessKey
}
revokeProcess(params).then( res => {
this.$message.success(res.message);
this.getList();
});
},
/** 收回任務(wù) */
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('是否確認(rèn)刪除流程定義編號(hào)為"' + ids + '"的數(shù)據(jù)項(xiàng)?', "警告", {
confirmButtonText: "確定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return delDeployment(ids,dataid);
}).then(() => {
this.getList();
this.$message.success("刪除成功");
})
},
/** 導(dǎo)出按鈕操作 */
handleExport() {
const queryParams = this.queryParams;
this.$confirm('是否確認(rèn)導(dǎo)出所有流程定義數(shù)據(jù)項(xiàng)?', "警告", {
confirmButtonText: "確定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return exportDeployment(queryParams);
}).then(response => {
this.download(response.message);
})
}
}
};
</script>
2、后端代碼
? ? 2.1? 增加一個(gè)收回流程功能recallProcess,具體代碼如下:
/**
* 發(fā)起人收回流程
* add by nbacheng
*
* @param FlowTaskVo taskVo
*
* @return
*/
@Override
@Transactional
public Result recallProcess(FlowTaskVo flowTaskVo) {
// 當(dāng)前任務(wù) listtask
List<Task> listtask = taskService.createTaskQuery().processInstanceId(flowTaskVo.getInstanceId()).active().list();
if (listtask == null) {
throw new CustomException("流程未啟動(dòng)或已執(zhí)行完成,無法收回");
}
if (taskService.createTaskQuery().taskId(listtask.get(0).getId()).singleResult().isSuspended()) {
throw new CustomException("任務(wù)處于掛起狀態(tài)");
}
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();
// 獲取所有歷史任務(wù)(按創(chuàng)建時(shí)間升序)
List<HistoricTaskInstance> hisTaskList = historyService.createHistoricTaskInstanceQuery()
.processInstanceId(processInstanceId).orderByTaskCreateTime()
.asc()
.list();
if (CollectionUtil.isEmpty(hisTaskList) || hisTaskList.size() < 2) {
log.error("當(dāng)前流程 【{}】 審批節(jié)點(diǎn) 【{}】正在初始節(jié)點(diǎn)無法收回", processInstanceId, listtask.get(0).getName());
throw new FlowableException(String.format("當(dāng)前流程 【%s】 審批節(jié)點(diǎn)【%s】正在初始節(jié)點(diǎn)無法收回", processInstanceId, listtask.get(0).getName()));
}
// 第一個(gè)任務(wù)
HistoricTaskInstance startTask = hisTaskList.get(0);
//若操作用戶不是發(fā)起人,不能收回
if(!StringUtils.equalsAnyIgnoreCase(loginUser.getUsername(), startTask.getAssignee())) {
throw new CustomException("操作用戶不是發(fā)起人,不能收回");
}
// 當(dāng)前任務(wù)
HistoricTaskInstance currentTask = hisTaskList.get(hisTaskList.size() - 1);
BpmnModel bpmnModel = repositoryService.getBpmnModel(listtask.get(0).getProcessDefinitionId());
// 獲取第一個(gè)活動(dòng)節(jié)點(diǎn)
FlowNode startFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(startTask.getTaskDefinitionKey());
// 獲取當(dāng)前活動(dòng)節(jié)點(diǎn)
FlowNode currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentTask.getTaskDefinitionKey());
// 臨時(shí)保存當(dāng)前活動(dòng)的原始方向
List<SequenceFlow> originalSequenceFlowList = new ArrayList<>(currentFlowNode.getOutgoingFlows());
// 清理活動(dòng)方向
currentFlowNode.getOutgoingFlows().clear();
// 建立新方向
SequenceFlow newSequenceFlow = new SequenceFlow();
newSequenceFlow.setId("newSequenceFlowId");
newSequenceFlow.setSourceFlowElement(currentFlowNode);
newSequenceFlow.setTargetFlowElement(startFlowNode);
List<SequenceFlow> newSequenceFlowList = new ArrayList<>();
newSequenceFlowList.add(newSequenceFlow);
// 當(dāng)前節(jié)點(diǎn)指向新的方向
currentFlowNode.setOutgoingFlows(newSequenceFlowList);
// 完成當(dāng)前任務(wù)
for(Task task : listtask) {
taskService.addComment(task.getId(), listtask.get(0).getProcessInstanceId(),FlowComment.RECALL.getType(), "發(fā)起人收回");
taskService.setAssignee(task.getId(), startTask.getAssignee());
taskService.complete(task.getId());
}
// 重新查詢當(dāng)前任務(wù)
Task nextTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
if (ObjectUtil.isNotNull(nextTask)) {
taskService.setAssignee(nextTask.getId(), startTask.getAssignee());
//taskService.complete(nextTask.getId());;//跳過流程發(fā)起節(jié)點(diǎn)
}
//自定義業(yè)務(wù)處理id
String dataId = flowTaskVo.getDataId();
// 刪除運(yùn)行和歷史的節(jié)點(diǎn)信息
this.deleteActivity(procInsId.get(1).getTaskDefinitionKey(), flowTaskVo.getInstanceId(), dataId);
// 恢復(fù)原始方向
currentFlowNode.setOutgoingFlows(originalSequenceFlowList);
//自定義業(yè)務(wù)處理
if(StrUtil.isNotBlank(flowTaskVo.getDataId()) && !Objects.equals(flowTaskVo.getDataId(), "null")){
//如果保存數(shù)據(jù)前未調(diào)用必調(diào)的FlowCommonService.initActBusiness方法,就會(huì)有問題
FlowMyBusiness business = flowMyBusinessService.getByDataId(dataId);
//刪除自定義業(yè)務(wù)任務(wù)關(guān)聯(lián)表,以便可以重新發(fā)起流程
if (business != null) {
flowMyBusinessService.removeById(business);
}
}
return Result.OK("發(fā)起人收回成功");
}
? ?2.2 調(diào)用?刪除歷史節(jié)點(diǎn)信息deleteActivity
/**
* 刪除跳轉(zhuǎn)的歷史節(jié)點(diǎn)信息
*
* @param disActivityId 跳轉(zhuǎn)的節(jié)點(diǎn)id
* @param processInstanceId 流程實(shí)例id
* @param dataId 自定義業(yè)務(wù)id
*/
protected void deleteActivity(String disActivityId, String processInstanceId, String dataId) {
List<ActivityInstance> disActivities = flowTaskMapper
.queryActivityInstance(disActivityId, processInstanceId, null);
//刪除運(yùn)行時(shí)和歷史節(jié)點(diǎn)信息
if (CollectionUtils.isNotEmpty(disActivities)) {
ActivityInstance activityInstance = disActivities.get(0);
List<ActivityInstance> datas = flowTaskMapper
.queryActivityInstance(disActivityId, processInstanceId, activityInstance.getEndTime());
//datas.remove(0); //保留流程發(fā)起節(jié)點(diǎn)信息
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) {//對(duì)于自定義業(yè)務(wù), 刪除所有相關(guān)流程信息
//flowTaskMapper.deleteAllHisAndRun(processInstanceId);
//根據(jù)流程實(shí)例id 刪除去ACT_RU_*與ACT_HI_*流程實(shí)例數(shù)據(jù)
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
if (null != processInstance) {
runtimeService.deleteProcessInstance(processInstanceId, "流程實(shí)例刪除");
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.* from
act_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、自定義業(yè)務(wù)與其它流程做分別處理
? ? ?其它流程直接刪除相關(guān)用戶任務(wù)歷史信息,保留初始發(fā)送,用戶可以直接進(jìn)行流程重新編輯發(fā)送。文章來源:http://www.zghlxwxcb.cn/news/detail-660721.html
? ? ?而自定義業(yè)務(wù)則刪除所有實(shí)例相關(guān)的任務(wù)歷史信息,不保留任務(wù)相關(guān)信息,同時(shí)刪除自定義業(yè)務(wù)發(fā)起時(shí)候的寫入的關(guān)聯(lián)表,以便用戶可以再次發(fā)起流程。文章來源地址http://www.zghlxwxcb.cn/news/detail-660721.html
到了這里,關(guān)于基于jeecg-boot的flowable流程收回功能實(shí)現(xiàn)(全網(wǎng)首創(chuàng)功能)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!