SpringBoot+Vue+Flowable模擬實現(xiàn)請假審批流程
小伙伴們知道松哥最近在錄 TienChin 項目視頻,這個項目會用到工作流,為了幫助小伙伴們更好的理解這個項目,松哥最近會出幾篇文章和大伙聊一聊工作流 flowable 的使用,算是給 TienChin 項目的第一個鋪墊,當然,在 TienChin 項目的系列視頻中,我也會和大家詳細聊一聊 flowable 流程引擎的使用。
今天我就先寫一個簡單的請假流程,讓小伙伴們對 flowable 先有一個直觀的認知。
1. 效果展示
在正式開搞之前,我先來給小伙伴們看下我們今天要完成的效果。
簡單起見,我這里并沒有引入用戶、角色等概念,涉及到用戶的地方都是手動輸入,在后續(xù)的文章中我會繼續(xù)結(jié)合 Spring Security 來和大家展示引入用戶之后的情況。
我們先來看看請假頁面:
員工可以在這個頁面輸入姓名,請假天數(shù)以及請假理由等,然后點擊按鈕提交一個請假申請。
當員工提交請假申請之后,這個請假申請默認是由經(jīng)理來處理的,此時經(jīng)理登錄之后,就可以看到員工提交上來的請求:
經(jīng)理此時可以選擇批準或者拒絕。無論是批準還是拒絕,都可以通過短信或者郵件等告知員工。
對于員工來說,也可以在一個頁面查詢自己請假流程的最終情況:
可能有小伙伴已經(jīng)注意到了,我們這里所有涉及到用戶名的地方,都需要手動輸入。這是因為我為了讓這個案例足夠簡單,暫時沒有引入 Spring Security,只是單純的和大家分享 Flowable 的用法,等小伙伴們通過這篇文章掌握了 Flowable 的基本用法之后,下篇文章我會和大家分享如何結(jié)合具體的用戶來使用。
2. 工程創(chuàng)建
我就直接來和小伙伴們展示 Spring Boot 中 flowable 的用法了。
首先我們創(chuàng)建一個 Spring Boot 項目,創(chuàng)建的時候引入 Web 和 MySQL 驅(qū)動依賴即可,項目創(chuàng)建成功之后,再引入 flowable 依賴,最終的依賴文件如下:
<dependency> ????<groupId>org.springframework.boot</groupId> ????<artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> ????<groupId>org.flowable</groupId> ????<artifactId>flowable-spring-boot-starter</artifactId> ????<version>6.7.2</version> </dependency> <dependency> ????<groupId>mysql</groupId> ????<artifactId>mysql-connector-java</artifactId> ????<scope>runtime</scope> </dependency>
項目創(chuàng)建成功之后,首先需要我們在 application.properties 中配置一下數(shù)據(jù)庫連接信息,如下:
spring.datasource.username=root spring.datasource.password=123 spring.datasource.url=jdbc:mysql:///flowable02?serverTimezone=Asia/Shanghai&useSSL=false&nullCatalogMeansCurrent=true
配置完成之后,當 Spring Boot 項目第一次啟動的時候,會自動創(chuàng)建出來對應的表和需要的數(shù)據(jù)。
同時,Spring Boot 項目也會自動創(chuàng)建并暴露 Flowable 中的 ProcessEngine、CmmnEngine、DmnEngine、FormEngine、ContentEngine 及 IdmEngine 等 Bean。
并且所有的 Flowable 服務都暴露為 Spring Bean。例如 RuntimeService、TaskService、HistoryService 等等服務,我們都可以在需要使用的時候,直接注入就可以使用了。
同時:
- resources/processes 目錄下的任何 BPMN 2.0 流程定義都會被自動部署,所以在 Spring Boot 項目中,我們只需要將自己的流程文件放對位置即可,剩下的事情就會自動完成。
- cases 目錄下的任何 CMMN 1.1 事例都會被自動部署。
- forms 目錄下的任何 Form 定義都會被自動部署。
3. 流程圖分析
今天這個例子比較簡單,就是一個請假流程,我暫時先不跟小伙伴們?nèi)コ懂嬃鞒虉D的事,咱們直接用一個官網(wǎng)現(xiàn)成的請假流程圖:
我們先來簡單分析一下這張圖:
- 最左側(cè)的圓圈叫做啟動事件(start event),這表示一個流程實例的起點。
- 一個流程啟動之后,首先到達第一個有用戶圖標的矩形中,這個矩形稱為一個 User Task,在這個 User Task 中,經(jīng)理可以選擇批準亦或者拒絕。
- UserTask 的下一步是一個菱形,這個稱作排他網(wǎng)關(guān)(Exclusive Gateway),這個會將請求路由到不同的地方。
- 先說批準,如果在第一個矩形中,經(jīng)理選擇了批準,那么就會進入到一個帶有齒輪圖標的矩形中,在這個矩形中我們我們可以額外做一些事情,然后又會調(diào)用到一個 UserTask,最終完成整個流程。
- 如果經(jīng)理選擇了拒絕,則會進入到下面的發(fā)郵件的矩形中,在這個中我們可以給員工發(fā)送一個通知,告知他請假沒有通過。
- 當系統(tǒng)走到最右邊的圓圈之后,就表示這個流程執(zhí)行結(jié)束了。
這個流程圖對應的 XML 文件位于 src/main/resources/processes/holiday-request.bpmn20.xml 位置,其內(nèi)容如下:
<?xml?version="1.0"?encoding="UTF-8"?> <definitions?xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"?xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ?????????????xmlns:flowable="http://flowable.org/bpmn" ?????????????typeLanguage="http://www.w3.org/2001/XMLSchema"?expressionLanguage="http://www.w3.org/1999/XPath" ?????????????targetNamespace="http://www.flowable.org/processdef"> ????<process?id="holidayRequest"?name="Holiday?Request"?isExecutable="true"> ????????<startEvent?id="startEvent"/> ????????<sequenceFlow?sourceRef="startEvent"?targetRef="approveTask"/> ????????<userTask?id="approveTask"?name="Approve?or?reject?request"?flowable:candidateGroups="managers"/> ????????<sequenceFlow?sourceRef="approveTask"?targetRef="decision"/> ????????<exclusiveGateway?id="decision"/> ????????<sequenceFlow?sourceRef="decision"?targetRef="externalSystemCall"> ????????????<conditionExpression?xsi:type="tFormalExpression"> ????????????????<![CDATA[ ??????????${approved} ????????]]> ????????????</conditionExpression> ????????</sequenceFlow> ????????<sequenceFlow??sourceRef="decision"?targetRef="rejectLeave"> ????????????<conditionExpression?xsi:type="tFormalExpression"> ????????????????<![CDATA[ ??????????${!approved} ????????]]> ????????????</conditionExpression> ????????</sequenceFlow> ????????<serviceTask?id="externalSystemCall"?name="Enter?holidays?in?external?system" ?????????????????????flowable:class="org.javaboy.flowable02.flowable.Approve"/> ????????<sequenceFlow?sourceRef="externalSystemCall"?targetRef="holidayApprovedTask"/> ????????<userTask?id="holidayApprovedTask"?flowable:assignee="${employee}"?name="Holiday?approved"/> ????????<sequenceFlow?sourceRef="holidayApprovedTask"?targetRef="approveEnd"/> ????????<serviceTask?id="rejectLeave"?name="Send?out?rejection?email" ?????????????????????flowable:class="org.javaboy.flowable02.flowable.Reject"/> ????????<sequenceFlow?sourceRef="rejectLeave"?targetRef="rejectEnd"/> ????????<endEvent?id="approveEnd"/> ????????<endEvent?id="rejectEnd"/> ????</process> </definitions>
很多想學習流程引擎的小伙伴都會被這個 XML 文件勸退,但是?。?!
如果你愿意靜下心來認真閱讀這個 XML 文件,你會發(fā)現(xiàn)流程引擎原來如此簡單!
我們來挨個看下這里的每一個節(jié)點:
- process:這表示一個流程,例如本文和大家分享的請假就是一個流程。
- startEvent:這表示流程的開始,這就是一個開始事件。
- userTask:這就是一個具體的流程節(jié)點了,flowable:candidateGroups 屬性表示這個節(jié)點該由哪個用戶組中的用戶來處理。
- sequenceFlow:這就是連接各個流程節(jié)點之間的線條,這個里邊一般有兩個屬性,sourceRef 和 targetRef,前者表示線條的起點,后者表示線條的終點。
- exclusiveGateway:表示一個排他性網(wǎng)關(guān),也就是那個菱形選擇框。
- 從排他性網(wǎng)關(guān)出來的線條有兩個,大家注意看上面的代碼,這兩個線條中都涉及到一個變量 approved,如果這個變量為 true,則 targeRef 就是 externalSystemCall;如果這個變量為 false,則 targetRef 就是 rejectLeave。
- serviceTask:這就是我們定義的一個具體的外部服務,如果在整個流程執(zhí)行的過程中,你有一些需要自己完成的事情,那么可以通過 serviceTask 來實現(xiàn),這個節(jié)點會有一個 flowable:class 屬性,這個屬性的值就是一個自定義類。
- 另外,上文中部分節(jié)點中還涉及到變量 ${},這個變量是在流程執(zhí)行的過程中傳入進來的。
總而言之,只要小伙伴們靜下心來認真閱讀一下上面的 XML,你會發(fā)現(xiàn) So Easy!
4. 請假申請
好了,接下來我們就來看一個具體的請假申請。由于請假流程只要放對位置,就會自動加載,所以我們并不需要手動加載請假流程,直接開始一個請假申請流程即可。
4.1 服務端接口
首先我們需要一個實體類來接受前端傳來的請假參數(shù):用戶名、請假天數(shù)以及請假理由:
public?class?AskForLeaveVO?{ ????private?String?name; ????private?Integer?days; ????private?String?reason; ????//?省略?getter/setter }
再拿出祖?zhèn)鞯?RespBean,以便響應數(shù)據(jù)方便一些:
public?class?RespBean?{ ????private?Integer?status; ????private?String?msg; ????private?Object?data; ????public?static?RespBean?ok(String?msg,?Object?data)?{ ????????return?new?RespBean(200,?msg,?data); ????} ????public?static?RespBean?ok(String?msg)?{ ????????return?new?RespBean(200,?msg,?null); ????} ????public?static?RespBean?error(String?msg,?Object?data)?{ ????????return?new?RespBean(500,?msg,?data); ????} ????public?static?RespBean?error(String?msg)?{ ????????return?new?RespBean(500,?msg,?null); ????} ????private?RespBean()?{ ????} ????private?RespBean(Integer?status,?String?msg,?Object?data)?{ ????????this.status?=?status; ????????this.msg?=?msg; ????????this.data?=?data; ????} ????//?省略?getter/setter }
接下來我們提供一個處理請假申請的接口:
@RestController public?class?AskForLeaveController?{ ????@Autowired ????AskForLeaveService?askForLeaveService; ????@PostMapping("/ask_for_leave") ????public?RespBean?askForLeave(@RequestBody?AskForLeaveVO?askForLeaveVO)?{ ????????return?askForLeaveService.askForLeave(askForLeaveVO); ????} }
核心邏輯在 AskForLeaveService 中,來繼續(xù)看:
@Service public?class?AskForLeaveService?{ ????@Autowired ????RuntimeService?runtimeService; ????@Transactional ????public?RespBean?askForLeave(AskForLeaveVO?askForLeaveVO)?{ ????????Map<String,?Object>?variables?=?new?HashMap<>(); ????????variables.put("name",?askForLeaveVO.getName()); ????????variables.put("days",?askForLeaveVO.getDays()); ????????variables.put("reason",?askForLeaveVO.getReason()); ????????try?{ ????????????runtimeService.startProcessInstanceByKey("holidayRequest",?askForLeaveVO.getName(),?variables); ????????????return?RespBean.ok("已提交請假申請"); ????????}?catch?(Exception?e)?{ ????????????e.printStackTrace(); ????????} ????????return?RespBean.error("提交申請失敗"); ????} }
小伙伴們看一下,在提交請假申請的時候,分別傳入了 name、days 以及 reason 三個參數(shù),我們將這三個參數(shù)放入到一個 Map 中,然后通過 RuntimeService#startProcessInstanceByKey 方法來開啟一個流程,開啟流程的時候一共傳入了三個參數(shù):
- 第一個參數(shù)表示流程引擎的名字,這就是我們剛才在流程的 XML 文件中定義的名字。
- 第二個參數(shù)表示當前這個流程的 key,我用了申請人的名字,將來我們可以通過申請人的名字查詢這個人曾經(jīng)提交的所有申請流程。
- 第三個參數(shù)就是我們的變量了。
好了,這服務端就寫好了。
4.2 前端頁面
接下來我們來開發(fā)前端頁面。
前端我使用 Vue+ElementUI+Axios,咱們這個案例比較簡單,就沒有必要搭建單頁面了,直接用普通的 HTML 就行了。另外,Vue 我是用了 Vue3:
<!DOCTYPE?html> <html?lang="en"> <head> ????<meta?charset="UTF-8"> ????<title>Title</title> ????<script?src="https://unpkg.com/axios/dist/axios.min.js"></script> ????<!--?Import?style?--> ????<link?rel="stylesheet"? rel="external nofollow" rel="external nofollow" rel="external nofollow" /> ????<script?src="https://unpkg.com/vue@3"></script> ????<!--?Import?component?library?--> ????<script?src="http://unpkg.com/element-plus"></script> </head> <body> <div?id="app"> ????<h1>開始一個請假流程</h1> ????<table> ????????<tr> ????????????<td>請輸入姓名:</td> ????????????<td> ????????????????<el-input?type="text"?v-model="afl.name"/> ????????????</td> ????????</tr> ????????<tr> ????????????<td>請輸入請假天數(shù):</td> ????????????<td> ????????????????<el-input?type="text"?v-model="afl.days"/> ????????????</td> ????????</tr> ????????<tr> ????????????<td>請輸入請假理由:</td> ????????????<td> ????????????????<el-input?type="text"?v-model="afl.reason"/> ????????????</td> ????????</tr> ????</table> ????<el-button?type="primary"?@click="submit">提交請假申請</el-button> </div> <script> ????Vue.createApp( ????????{ ????????????data()?{ ????????????????return?{ ????????????????????afl:?{ ????????????????????????name:?'javaboy', ????????????????????????days:?3, ????????????????????????reason:?'休息一下' ????????????????????} ????????????????} ????????????}, ????????????methods:?{ ????????????????submit()?{ ????????????????????let?_this?=?this; ????????????????????axios.post('/ask_for_leave',?this.afl) ????????????????????????.then(function?(response)?{ ????????????????????????????if?(response.data.status?==?200)?{ ????????????????????????????????//提交成功 ????????????????????????????????_this.$message.success(response.data.msg); ????????????????????????????}?else?{ ????????????????????????????????//提交失敗 ????????????????????????????????_this.$message.error(response.data.msg); ????????????????????????????} ????????????????????????}) ????????????????????????.catch(function?(error)?{ ????????????????????????????console.log(error); ????????????????????????}); ????????????????} ????????????} ????????} ????).use(ElementPlus).mount('#app') </script> </body> </html>
這個頁面有幾個需要注意的點:
- 通過 Vue.createApp 來創(chuàng)建一個 Vue 實例,這跟以前 Vue2 中直接 new 一個 Vue 實例不一樣。
- 在最下面,通過 use 來配置 ElementPlus 插件,這個跟 Vue2 也不一樣。在 Vue2 中,如果我們單純的在 HTML 頁面中引用 ElementUI 并不需要這個步驟。
- 剩下的東西就比較簡單了,上面先引入 Vue3、Axios 以及 ElementPlus,然后三個輸入框,點擊按鈕提交請求,參數(shù)就是三個輸入框中的數(shù)據(jù),提交成功或者失敗,分別彈個框出來提示一下就行了。
好啦,這就寫好了。
然而,提交完成后,沒有一個直觀的展示,雖然前端提示說提交成功了,但是究竟成功沒,還得眼見為實。
5. 任務展示
好了,接下來我們要做的事情就是把用戶提交的流程展示出來。
按理說,比如經(jīng)理登錄成功之后,系統(tǒng)頁面就自動展示出來經(jīng)理需要審批的流程,但是我們當前這個例子為了簡單,就沒有登錄這個操作了,需要需要用戶將來在網(wǎng)頁上選一下自己的身份,接下來就會展示出這個身份所對應的需要操作的流程。
我們來看任務接口:
@GetMapping("/list") public?RespBean?leaveList(String?identity)?{ ????return?askForLeaveService.leaveList(identity); }
這個請求參數(shù) identity 就表示當前用戶的身份(本來應該是登錄后自動獲取,但是因為我們目前沒有登錄,所以這個參數(shù)是由前端傳遞過來)。來繼續(xù)看 askForLeaveService 中的方法:
@Service public?class?AskForLeaveService?{ ????@Autowired ????TaskService?taskService; ????public?RespBean?leaveList(String?identity)?{ ????????List<Task>?tasks?=?taskService.createTaskQuery().taskCandidateGroup(identity).list(); ????????List<Map<String,?Object>>?list?=?new?ArrayList<>(); ????????for?(int?i?=?0;?i?<?tasks.size();?i++)?{ ????????????Task?task?=?tasks.get(i); ????????????Map<String,?Object>?variables?=?taskService.getVariables(task.getId()); ????????????variables.put("id",?task.getId()); ????????????list.add(variables); ????????} ????????return?RespBean.ok("加載成功",?list); ????} }
Task 就是流程中要做的每一件事情,我們首先通過 TaskService,查詢出來這個用戶需要處理的任務,例如前端前傳來的是 managers,那么這里就是查詢所有需要由 managers 用戶組處理的任務。
這段代碼要結(jié)合流程圖一起來理解,小伙伴們回顧下我們流程圖中有如下一句:
<userTask?id="approveTask"?name="Approve?or?reject?request"?flowable:candidateGroups="managers"/>
這意思就是說這個 userTask 是由 managers 這個組中的用戶來處理,所以上面 Java 代碼中的查詢就是查詢 managers 這個組中的用戶需要審批的任務。
我們將所有需要審批的任務查詢出來后,通過 taskId 可以進一步查詢到這個任務中當時傳入的各種變量,我們將這些數(shù)據(jù)封裝成一個對象,并最終返回到前端。
最后,我們再來看下前端頁面:
<!DOCTYPE?html> <html?lang="en"> <head> ????<meta?charset="UTF-8"> ????<title>Title</title> ????<script?src="https://unpkg.com/axios/dist/axios.min.js"></script> ????<!--?Import?style?--> ????<link?rel="stylesheet"? rel="external nofollow" rel="external nofollow" rel="external nofollow" /> ????<script?src="https://unpkg.com/vue@3"></script> ????<!--?Import?component?library?--> ????<script?src="http://unpkg.com/element-plus"></script> </head> <body> <div?id="app"> ????<div> ????????<div>請選擇你的身份:</div> ????????<div> ????????????<el-select?name=""?id=""?v-model="identity"?@change="initTasks"> ????????????????<el-option?:value="iden"?v-for="(iden,index)?in?identities"?:key="index"?:label="iden"></el-option> ????????????</el-select> ????????????<el-button?type="primary"?@click="initTasks">刷新一下</el-button> ????????</div> ????</div> ????<el-table?border?strip?:data="tasks"> ????????<el-table-column?prop="name"?label="姓名"></el-table-column> ????????<el-table-column?prop="days"?label="請假天數(shù)"></el-table-column> ????????<el-table-column?prop="reason"?label="請假原因"></el-table-column> ????????<el-table-column?lable="操作"> ????????????<template?#default="scope"> ????????????????<el-button?type="primary"?@click="approveOrReject(scope.row.id,true,scope.row.name)">批準</el-button> ????????????????<el-button?type="danger"?@click="approveOrReject(scope.row.id,false,scope.row.name)">拒絕</el-button> ????????????</template> ????????</el-table-column> ????</el-table> </div> <script> ????Vue.createApp( ????????{ ????????????data()?{ ????????????????return?{ ????????????????????tasks:?[], ????????????????????identities:?[ ????????????????????????'managers' ????????????????????], ????????????????????identity:?'' ????????????????} ????????????}, ????????????methods:?{ ????????????????initTasks()?{ ????????????????????let?_this?=?this; ????????????????????axios.get('/list?identity='?+?this.identity) ????????????????????????.then(function?(response)?{ ????????????????????????????_this.tasks?=?response.data.data; ????????????????????????}) ????????????????????????.catch(function?(error)?{ ????????????????????????????console.log(error); ????????????????????????}); ????????????????} ????????????} ????????} ????).use(ElementPlus).mount('#app') </script> </body> </html>
大家看到,首先有一個下拉框,我們在這個下拉框中來選擇用戶的身份。選擇完成后,觸發(fā) initTasks 方法,然后在這個方法中,發(fā)起網(wǎng)絡(luò)請求,最終將請求結(jié)果渲染出來。
最終效果如下:
當然用戶也可以點擊刷新按鈕,刷新列表。
這樣,當?shù)谖逍」?jié)中,員工提交了一個請假審批之后,我們在這個列表中就可以查看到員工提交的請假審批了(在流程圖中,我們直接設(shè)置了用戶的請假審批固定提交給 managers,在后續(xù)的文章中,松哥會教大家如何把這個提交的目標用戶變成一個動態(tài)的)。
6. 請假審批
接下來經(jīng)理就可以選擇批準或者是拒絕這請假了。
首先我們封裝一個實體類用來接受前端傳來的請求:
public?class?ApproveRejectVO?{ ????private?String?taskId; ????private?Boolean?approve; ????private?String?name; ????//?省略?getter/setter }
參數(shù)都好理解,approve 為 true 表示申請通過,false 表示申請被拒絕。
接下來我們來看接口:
@PostMapping("/handler") public?RespBean?askForLeaveHandler(@RequestBody?ApproveRejectVO?approveRejectVO)?{ ????return?askForLeaveService.askForLeaveHandler(approveRejectVO); }
看具體的 askForLeaveHandler 方法:
@Service public?class?AskForLeaveService?{ ????@Autowired ????TaskService?taskService; ????public?RespBean?askForLeaveHandler(ApproveRejectVO?approveRejectVO)?{ ????????try?{ ????????????boolean?approved?=?approveRejectVO.getApprove(); ????????????Map<String,?Object>?variables?=?new?HashMap<String,?Object>(); ????????????variables.put("approved",?approved); ????????????variables.put("employee",?approveRejectVO.getName()); ????????????Task?task?=?taskService.createTaskQuery().taskId(approveRejectVO.getTaskId()).singleResult(); ????????????taskService.complete(task.getId(),?variables); ????????????if?(approved)?{ ????????????????//如果是同意,還需要繼續(xù)走一步 ????????????????Task?t?=?taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).singleResult(); ????????????????taskService.complete(t.getId()); ????????????} ????????????return?RespBean.ok("操作成功"); ????????}?catch?(Exception?e)?{ ????????????e.printStackTrace(); ????????} ????????return?RespBean.error("操作失敗"); ????} }
大家注意這個審批流程:
- 審批時需要兩個參數(shù),approved 和 employee,approved 為 true,就會自動進入到審批通過的流程中,approved 為 false 則會自動進入到拒絕流程中。
- 通過 taskService,結(jié)合 taskId,從流程中查詢出對應的 task,然后調(diào)用 taskService.complete 方法傳入 taskId 和 變量,以使流程向下走。
- 小伙伴們再回顧一下我們前面的流程圖,如果請求被批準備了,那么在執(zhí)行完自定義的 Approve 邏輯后,就會進入到 Holiday approved 這個 userTask 中,注意此時并不會繼續(xù)向下走了(還差一步到結(jié)束事件);如果是請求拒絕,則在執(zhí)行完自定義的 Reject 邏輯后,就進入到結(jié)束事件了,這個流程就結(jié)束了。
- 針對第三條,所以代碼中我們還需要額外再加一步,如果是 approved 為 true,那么就再從當前流程中查詢出來需要執(zhí)行的 task,再調(diào)用 complete 繼續(xù)走一步,此時就到了結(jié)束事件了,這個流程就結(jié)束了。注意這次的查詢是根據(jù)當前流程的 ID 查詢的,一個流程就是一條線,這條線上有很多 Task,我們可以從 Task 中獲取到流程的 ID。
好啦,接口就寫好了。
當然,這里還涉及到兩個自定義的邏輯,就是批準或者拒絕之后的自定義邏輯,這個其實很好寫,如下:
public?class?Approve?implements?JavaDelegate?{ ????@Override ????public?void?execute(DelegateExecution?execution)?{ ????????System.out.println("申請通過:"+execution.getVariables()); ????} }
我們自定義類實現(xiàn) JavaDelegate 接口即可,然后我們在 execute 方法中做自己想要做的事情即可,execution 中有這個流程中的所有變量。我們可以在這里發(fā)郵件、發(fā)短信等等。Reject 的定義方式也是類似的。這些自定義類寫好之后,將來配置到流程圖中即可(可查看上文的流程圖)。
最后再來看看前端提交方法就簡單了(頁面源碼上文已經(jīng)列出):
approveOrReject(taskId,?approve,name)?{ ????let?_this?=?this; ????axios.post('/handler',?{taskId:?taskId,?approve:?approve,name:name}) ????????.then(function?(response)?{ ????????????_this.initTasks(); ????????}) ????????.catch(function?(error)?{ ????????????console.log(error); ????????}); }
這就一個普通的 Ajax 請求,批準的話第二個參數(shù)就為 true,拒絕的話第二個參數(shù)就為 false。
7. 結(jié)果查詢
最后,每個用戶都可以查看自己曾經(jīng)的申請記錄。本來這個登錄之后就可以展示了,但是因為我們沒有登錄,所以這里也是需要手動輸入查詢的用戶,然后根據(jù)用戶名查詢這個用戶的歷史記錄,我們先來看查詢接口:
@GetMapping("/search") public?RespBean?searchResult(String?name)?{ ????return?askForLeaveService.searchResult(name); }
參數(shù)就是要查詢的用戶名。具體的查詢流程如下:
public?RespBean?searchResult(String?name)?{ ????List<HistoryInfo>?historyInfos?=?new?ArrayList<>(); ????List<HistoricProcessInstance>?historicProcessInstances?=?historyService.createHistoricProcessInstanceQuery().processInstanceBusinessKey(name).finished().orderByProcessInstanceEndTime().desc().list(); ????for?(HistoricProcessInstance?historicProcessInstance?:?historicProcessInstances)?{ ????????HistoryInfo?historyInfo?=?new?HistoryInfo(); ????????Date?startTime?=?historicProcessInstance.getStartTime(); ????????Date?endTime?=?historicProcessInstance.getEndTime(); ????????List<HistoricVariableInstance>?historicVariableInstances?=?historyService.createHistoricVariableInstanceQuery() ????????????????.processInstanceId(historicProcessInstance.getId()) ????????????????.list(); ????????for?(HistoricVariableInstance?historicVariableInstance?:?historicVariableInstances)?{ ????????????String?variableName?=?historicVariableInstance.getVariableName(); ????????????Object?value?=?historicVariableInstance.getValue(); ????????????if?("reason".equals(variableName))?{ ????????????????historyInfo.setReason((String)?value); ????????????}?else?if?("days".equals(variableName))?{ ????????????????historyInfo.setDays(Integer.parseInt(value.toString())); ????????????}?else?if?("approved".equals(variableName))?{ ????????????????historyInfo.setStatus((Boolean)?value); ????????????}?else?if?("name".equals(variableName))?{ ????????????????historyInfo.setName((String)?value); ????????????} ????????} ????????historyInfo.setStartTime(startTime); ????????historyInfo.setEndTime(endTime); ????????historyInfos.add(historyInfo); ????} ????return?RespBean.ok("ok",?historyInfos); }
- 我們當時在開啟流程的時候,傳入了一個參數(shù) key,這里就是再次通過這個 key,也就是用戶名去查詢歷史流程,查詢的時候還加上了 finished 方法,這個表示要查詢的流程必須是執(zhí)行完畢的流程,對于沒有執(zhí)行完畢的流程,這里不查詢,查完之后,按照流程最后的處理時間進行排序。
- 遍歷第一步的查詢結(jié)果,從 HistoricProcessInstance 中提取出每一個流程的詳細信息,并存入到集合中,并最終返回。
- 這里涉及到兩個歷史數(shù)據(jù)查詢,createHistoricProcessInstanceQuery 用來查詢歷史流程,而 createHistoricVariableInstanceQuery 則主要是用來查詢流程變量的。
最后,前端通過表格展示這個數(shù)據(jù)即可:
<!DOCTYPE?html> <html?lang="en"> <head> ????<meta?charset="UTF-8"> ????<title>Title</title> ????<script?src="https://unpkg.com/axios/dist/axios.min.js"></script> ????<!--?Import?style?--> ????<link?rel="stylesheet"? rel="external nofollow" rel="external nofollow" rel="external nofollow" /> ????<script?src="https://unpkg.com/vue@3"></script> ????<!--?Import?component?library?--> ????<script?src="http://unpkg.com/element-plus"></script> </head> <body> <div?id="app"> ????<div?style="margin-top:?50px"> ????????<el-input?v-model="name"?style="width:?300px"?placeholder="請輸入用戶名"></el-input> ????????<el-button?type="primary"?@click="search">查詢</el-button> ????</div> ????<div> ????????<el-table?border?strip?:data="historyInfos"> ????????????<el-table-column?prop="name"?label="姓名"></el-table-column> ????????????<el-table-column?prop="startTime"?label="提交時間"></el-table-column> ????????????<el-table-column?prop="endTime"?label="審批時間"></el-table-column> ????????????<el-table-column?prop="reason"?label="事由"></el-table-column> ????????????<el-table-column?prop="days"?label="天數(shù)"></el-table-column> ????????????<el-table-column?label="狀態(tài)"> ????????????????<template?#default="scope"> ????????????????????<el-tag?type="success"?v-if="scope.row.status">已通過</el-tag> ????????????????????<el-tag?type="danger"?v-else>已拒絕</el-tag> ????????????????</template> ????????????</el-table-column> ????????</el-table> ????</div> </div> <script> ????Vue.createApp( ????????{ ????????????data()?{ ????????????????return?{ ????????????????????historyInfos:?[], ????????????????????name:?'zhangsan' ????????????????} ????????????}, ????????????methods:?{ ????????????????search()?{ ????????????????????let?_this?=?this; ????????????????????axios.get('/search?name='?+?this.name) ????????????????????????.then(function?(response)?{ ????????????????????????????if?(response.data.status?==?200)?{ ????????????????????????????????_this.historyInfos=response.data.data; ????????????????????????????}?else?{ ????????????????????????????????_this.$message.error(response.data.msg); ????????????????????????????} ????????????????????????}) ????????????????????????.catch(function?(error)?{ ????????????????????????????console.log(error); ????????????????????????}); ????????????????} ????????} ????).use(ElementPlus).mount('#app') </script> </body> </html>
這個都是一些常規(guī)操作,我就不多說了,最終展示效果如下:
以上就是SpringBoot+Vue+Flowable模擬實現(xiàn)請假審批流程的詳細內(nèi)容,更多關(guān)于SpringBoot Vue Flowable請假審批流程的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
kafka手動調(diào)整分區(qū)副本數(shù)的操作步驟
這篇文章主要介紹了kafka手動調(diào)整分區(qū)副本數(shù),本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-03-03Java實現(xiàn)BP神經(jīng)網(wǎng)絡(luò)MNIST手寫數(shù)字識別的示例詳解
這篇文章主要為大家詳細介紹了Java實現(xiàn)BP神經(jīng)網(wǎng)絡(luò)MNIST手寫數(shù)字識別的相關(guān)方法,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起了解一下2023-01-01springboot整合mybatis-plus基于注解實現(xiàn)一對一(一對多)查詢功能
這篇文章主要介紹了springboot整合mybatis-plus基于純注解實現(xiàn)一對一(一對多)查詢功能,因為本人采用的是spring-boot進行開發(fā),本身springboot就提倡采用不用配置自動配置的方式,所以真心希望mybatis(不是mybatis-plus)這點需要繼續(xù)努力2021-09-09SpringCloud Eureka 服務注冊實現(xiàn)過程
這篇文章主要介紹了SpringCloud Eureka 服務注冊實現(xiàn)過程,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-10-10Spring Data Jpa實現(xiàn)自定義repository轉(zhuǎn)DTO
這篇文章主要介紹了Spring Data Jpa實現(xiàn)自定義repository轉(zhuǎn)DTO,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-08-08