溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

怎么使用SpringBoot+Vue+Flowable模擬實(shí)現(xiàn)請(qǐng)假審批流程

發(fā)布時(shí)間:2022-08-10 14:00:06 來(lái)源:億速云 閱讀:234 作者:iii 欄目:開(kāi)發(fā)技術(shù)

這篇文章主要講解了“怎么使用SpringBoot+Vue+Flowable模擬實(shí)現(xiàn)請(qǐng)假審批流程”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“怎么使用SpringBoot+Vue+Flowable模擬實(shí)現(xiàn)請(qǐng)假審批流程”吧!

1. 效果展示

在正式開(kāi)搞之前,我先來(lái)給小伙伴們看下我們今天要完成的效果。

簡(jiǎn)單起見(jiàn),我這里并沒(méi)有引入用戶、角色等概念,涉及到用戶的地方都是手動(dòng)輸入,在后續(xù)的文章中我會(huì)繼續(xù)結(jié)合 Spring Security 來(lái)和大家展示引入用戶之后的情況。

我們先來(lái)看看請(qǐng)假頁(yè)面:

怎么使用SpringBoot+Vue+Flowable模擬實(shí)現(xiàn)請(qǐng)假審批流程

員工可以在這個(gè)頁(yè)面輸入姓名,請(qǐng)假天數(shù)以及請(qǐng)假理由等,然后點(diǎn)擊按鈕提交一個(gè)請(qǐng)假申請(qǐng)。

當(dāng)員工提交請(qǐng)假申請(qǐng)之后,這個(gè)請(qǐng)假申請(qǐng)默認(rèn)是由經(jīng)理來(lái)處理的,此時(shí)經(jīng)理登錄之后,就可以看到員工提交上來(lái)的請(qǐng)求:

怎么使用SpringBoot+Vue+Flowable模擬實(shí)現(xiàn)請(qǐng)假審批流程

經(jīng)理此時(shí)可以選擇批準(zhǔn)或者拒絕。無(wú)論是批準(zhǔn)還是拒絕,都可以通過(guò)短信或者郵件等告知員工。

對(duì)于員工來(lái)說(shuō),也可以在一個(gè)頁(yè)面查詢(xún)自己請(qǐng)假流程的最終情況:

怎么使用SpringBoot+Vue+Flowable模擬實(shí)現(xiàn)請(qǐng)假審批流程

2. 工程創(chuàng)建

我就直接來(lái)和小伙伴們展示 Spring Boot 中 flowable 的用法了。

首先我們創(chuàng)建一個(gè) Spring Boot 項(xiàng)目,創(chuàng)建的時(shí)候引入 Web 和 MySQL 驅(qū)動(dòng)依賴(lài)即可,項(xiàng)目創(chuàng)建成功之后,再引入 flowable 依賴(lài),最終的依賴(lài)文件如下:

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

項(xiàng)目創(chuàng)建成功之后,首先需要我們?cè)?application.properties 中配置一下數(shù)據(jù)庫(kù)連接信息,如下:

spring.datasource.username=root
spring.datasource.password=123
spring.datasource.url=jdbc:mysql:///flowable02?serverTimezone=Asia/Shanghai&useSSL=false&nullCatalogMeansCurrent=true

配置完成之后,當(dāng) Spring Boot 項(xiàng)目第一次啟動(dòng)的時(shí)候,會(huì)自動(dòng)創(chuàng)建出來(lái)對(duì)應(yīng)的表和需要的數(shù)據(jù)。

同時(shí),Spring Boot 項(xiàng)目也會(huì)自動(dòng)創(chuàng)建并暴露 Flowable 中的 ProcessEngine、CmmnEngine、DmnEngine、FormEngine、ContentEngine 及 IdmEngine 等 Bean。

并且所有的 Flowable 服務(wù)都暴露為 Spring Bean。例如 RuntimeService、TaskService、HistoryService 等等服務(wù),我們都可以在需要使用的時(shí)候,直接注入就可以使用了。

同時(shí):

  • resources/processes 目錄下的任何 BPMN 2.0 流程定義都會(huì)被自動(dòng)部署,所以在 Spring Boot 項(xiàng)目中,我們只需要將自己的流程文件放對(duì)位置即可,剩下的事情就會(huì)自動(dòng)完成。

  • cases 目錄下的任何 CMMN 1.1 事例都會(huì)被自動(dòng)部署。

  • forms 目錄下的任何 Form 定義都會(huì)被自動(dòng)部署。

3. 流程圖分析

今天這個(gè)例子比較簡(jiǎn)單,就是一個(gè)請(qǐng)假流程,我暫時(shí)先不跟小伙伴們?nèi)コ懂?huà)流程圖的事,咱們直接用一個(gè)官網(wǎng)現(xiàn)成的請(qǐng)假流程圖:

怎么使用SpringBoot+Vue+Flowable模擬實(shí)現(xiàn)請(qǐng)假審批流程

我們先來(lái)簡(jiǎn)單分析一下這張圖:

  • 最左側(cè)的圓圈叫做啟動(dòng)事件(start event),這表示一個(gè)流程實(shí)例的起點(diǎn)。

  • 一個(gè)流程啟動(dòng)之后,首先到達(dá)第一個(gè)有用戶圖標(biāo)的矩形中,這個(gè)矩形稱(chēng)為一個(gè) User Task,在這個(gè) User Task 中,經(jīng)理可以選擇批準(zhǔn)亦或者拒絕。

  • UserTask 的下一步是一個(gè)菱形,這個(gè)稱(chēng)作排他網(wǎng)關(guān)(Exclusive Gateway),這個(gè)會(huì)將請(qǐng)求路由到不同的地方。

  • 先說(shuō)批準(zhǔn),如果在第一個(gè)矩形中,經(jīng)理選擇了批準(zhǔn),那么就會(huì)進(jìn)入到一個(gè)帶有齒輪圖標(biāo)的矩形中,在這個(gè)矩形中我們我們可以額外做一些事情,然后又會(huì)調(diào)用到一個(gè) UserTask,最終完成整個(gè)流程。

  • 如果經(jīng)理選擇了拒絕,則會(huì)進(jìn)入到下面的發(fā)郵件的矩形中,在這個(gè)中我們可以給員工發(fā)送一個(gè)通知,告知他請(qǐng)假?zèng)]有通過(guò)。

  • 當(dāng)系統(tǒng)走到最右邊的圓圈之后,就表示這個(gè)流程執(zhí)行結(jié)束了。

這個(gè)流程圖對(duì)應(yīng)的 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>

很多想學(xué)習(xí)流程引擎的小伙伴都會(huì)被這個(gè) XML 文件勸退,但是?。?!

如果你愿意靜下心來(lái)認(rèn)真閱讀這個(gè) XML 文件,你會(huì)發(fā)現(xiàn)流程引擎原來(lái)如此簡(jiǎn)單!

我們來(lái)挨個(gè)看下這里的每一個(gè)節(jié)點(diǎn):

  • process:這表示一個(gè)流程,例如本文和大家分享的請(qǐng)假就是一個(gè)流程。

  • startEvent:這表示流程的開(kāi)始,這就是一個(gè)開(kāi)始事件。

  • userTask:這就是一個(gè)具體的流程節(jié)點(diǎn)了,flowable:candidateGroups 屬性表示這個(gè)節(jié)點(diǎn)該由哪個(gè)用戶組中的用戶來(lái)處理。

  • sequenceFlow:這就是連接各個(gè)流程節(jié)點(diǎn)之間的線條,這個(gè)里邊一般有兩個(gè)屬性,sourceRef 和 targetRef,前者表示線條的起點(diǎn),后者表示線條的終點(diǎn)。

  • exclusiveGateway:表示一個(gè)排他性網(wǎng)關(guān),也就是那個(gè)菱形選擇框。

  • 從排他性網(wǎng)關(guān)出來(lái)的線條有兩個(gè),大家注意看上面的代碼,這兩個(gè)線條中都涉及到一個(gè)變量 approved,如果這個(gè)變量為 true,則 targeRef 就是 externalSystemCall;如果這個(gè)變量為 false,則 targetRef 就是 rejectLeave。

  • serviceTask:這就是我們定義的一個(gè)具體的外部服務(wù),如果在整個(gè)流程執(zhí)行的過(guò)程中,你有一些需要自己完成的事情,那么可以通過(guò) serviceTask 來(lái)實(shí)現(xiàn),這個(gè)節(jié)點(diǎn)會(huì)有一個(gè) flowable:class 屬性,這個(gè)屬性的值就是一個(gè)自定義類(lèi)。

  • 另外,上文中部分節(jié)點(diǎn)中還涉及到變量 ${},這個(gè)變量是在流程執(zhí)行的過(guò)程中傳入進(jìn)來(lái)的。

總而言之,只要小伙伴們靜下心來(lái)認(rèn)真閱讀一下上面的 XML,你會(huì)發(fā)現(xiàn) So Easy!

4. 請(qǐng)假申請(qǐng)

好了,接下來(lái)我們就來(lái)看一個(gè)具體的請(qǐng)假申請(qǐng)。由于請(qǐng)假流程只要放對(duì)位置,就會(huì)自動(dòng)加載,所以我們并不需要手動(dòng)加載請(qǐng)假流程,直接開(kāi)始一個(gè)請(qǐng)假申請(qǐng)流程即可。

4.1 服務(wù)端接口

首先我們需要一個(gè)實(shí)體類(lèi)來(lái)接受前端傳來(lái)的請(qǐng)假參數(shù):用戶名、請(qǐng)假天數(shù)以及請(qǐng)假理由:

public class AskForLeaveVO {
    private String name;
    private Integer days;
    private String reason;
    // 省略 getter/setter
}

再拿出祖?zhèn)鞯?RespBean,以便響應(yīng)數(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
}

接下來(lái)我們提供一個(gè)處理請(qǐng)假申請(qǐng)的接口:

@RestController
public class AskForLeaveController {

    @Autowired
    AskForLeaveService askForLeaveService;

    @PostMapping("/ask_for_leave")
    public RespBean askForLeave(@RequestBody AskForLeaveVO askForLeaveVO) {
        return askForLeaveService.askForLeave(askForLeaveVO);
    }
}

核心邏輯在 AskForLeaveService 中,來(lái)繼續(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("已提交請(qǐng)假申請(qǐng)");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return RespBean.error("提交申請(qǐng)失敗");
    }
}

小伙伴們看一下,在提交請(qǐng)假申請(qǐng)的時(shí)候,分別傳入了 name、days 以及 reason 三個(gè)參數(shù),我們將這三個(gè)參數(shù)放入到一個(gè) Map 中,然后通過(guò) RuntimeService#startProcessInstanceByKey 方法來(lái)開(kāi)啟一個(gè)流程,開(kāi)啟流程的時(shí)候一共傳入了三個(gè)參數(shù):

  • 第一個(gè)參數(shù)表示流程引擎的名字,這就是我們剛才在流程的 XML 文件中定義的名字。

  • 第二個(gè)參數(shù)表示當(dāng)前這個(gè)流程的 key,我用了申請(qǐng)人的名字,將來(lái)我們可以通過(guò)申請(qǐng)人的名字查詢(xún)這個(gè)人曾經(jīng)提交的所有申請(qǐng)流程。

  • 第三個(gè)參數(shù)就是我們的變量了。

好了,這服務(wù)端就寫(xiě)好了。

4.2 前端頁(yè)面

接下來(lái)我們來(lái)開(kāi)發(fā)前端頁(yè)面。

前端我使用 Vue+ElementUI+Axios,咱們這個(gè)案例比較簡(jiǎn)單,就沒(méi)有必要搭建單頁(yè)面了,直接用普通的 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" href="//unpkg.com/element-plus/dist/index.css" rel="external nofollow"  rel="external nofollow"  rel="external nofollow" />
    <script src="https://unpkg.com/vue@3"></script>
    <!-- Import component library -->
    <script src="//unpkg.com/element-plus"></script>
</head>
<body>
<div id="app">
    <h2>開(kāi)始一個(gè)請(qǐng)假流程</h2>
    <table>
        <tr>
            <td>請(qǐng)輸入姓名:</td>
            <td>
                <el-input type="text" v-model="afl.name"/>
            </td>
        </tr>
        <tr>
            <td>請(qǐng)輸入請(qǐng)假天數(shù):</td>
            <td>
                <el-input type="text" v-model="afl.days"/>
            </td>
        </tr>
        <tr>
            <td>請(qǐng)輸入請(qǐng)假理由:</td>
            <td>
                <el-input type="text" v-model="afl.reason"/>
            </td>
        </tr>
    </table>
    <el-button type="primary" @click="submit">提交請(qǐng)假申請(qǐng)</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>

這個(gè)頁(yè)面有幾個(gè)需要注意的點(diǎn):

  • 通過(guò) Vue.createApp 來(lái)創(chuàng)建一個(gè) Vue 實(shí)例,這跟以前 Vue2 中直接 new 一個(gè) Vue 實(shí)例不一樣。

  • 在最下面,通過(guò) use 來(lái)配置 ElementPlus 插件,這個(gè)跟 Vue2 也不一樣。在 Vue2 中,如果我們單純的在 HTML 頁(yè)面中引用 ElementUI 并不需要這個(gè)步驟。

  • 剩下的東西就比較簡(jiǎn)單了,上面先引入 Vue3、Axios 以及 ElementPlus,然后三個(gè)輸入框,點(diǎn)擊按鈕提交請(qǐng)求,參數(shù)就是三個(gè)輸入框中的數(shù)據(jù),提交成功或者失敗,分別彈個(gè)框出來(lái)提示一下就行了。

好啦,這就寫(xiě)好了。

然而,提交完成后,沒(méi)有一個(gè)直觀的展示,雖然前端提示說(shuō)提交成功了,但是究竟成功沒(méi),還得眼見(jiàn)為實(shí)。

5. 任務(wù)展示

好了,接下來(lái)我們要做的事情就是把用戶提交的流程展示出來(lái)。

按理說(shuō),比如經(jīng)理登錄成功之后,系統(tǒng)頁(yè)面就自動(dòng)展示出來(lái)經(jīng)理需要審批的流程,但是我們當(dāng)前這個(gè)例子為了簡(jiǎn)單,就沒(méi)有登錄這個(gè)操作了,需要需要用戶將來(lái)在網(wǎng)頁(yè)上選一下自己的身份,接下來(lái)就會(huì)展示出這個(gè)身份所對(duì)應(yīng)的需要操作的流程。

我們來(lái)看任務(wù)接口:

@GetMapping("/list")
public RespBean leaveList(String identity) {
    return askForLeaveService.leaveList(identity);
}

這個(gè)請(qǐng)求參數(shù) identity 就表示當(dāng)前用戶的身份(本來(lái)應(yīng)該是登錄后自動(dòng)獲取,但是因?yàn)槲覀兡壳皼](méi)有登錄,所以這個(gè)參數(shù)是由前端傳遞過(guò)來(lái))。來(lái)繼續(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 就是流程中要做的每一件事情,我們首先通過(guò) TaskService,查詢(xún)出來(lái)這個(gè)用戶需要處理的任務(wù),例如前端前傳來(lái)的是 managers,那么這里就是查詢(xún)所有需要由 managers 用戶組處理的任務(wù)。

這段代碼要結(jié)合流程圖一起來(lái)理解,小伙伴們回顧下我們流程圖中有如下一句:

<userTask id="approveTask" name="Approve or reject request" flowable:candidateGroups="managers"/>

這意思就是說(shuō)這個(gè) userTask 是由 managers 這個(gè)組中的用戶來(lái)處理,所以上面 Java 代碼中的查詢(xún)就是查詢(xún) managers 這個(gè)組中的用戶需要審批的任務(wù)。

我們將所有需要審批的任務(wù)查詢(xún)出來(lái)后,通過(guò) taskId 可以進(jìn)一步查詢(xún)到這個(gè)任務(wù)中當(dāng)時(shí)傳入的各種變量,我們將這些數(shù)據(jù)封裝成一個(gè)對(duì)象,并最終返回到前端。

最后,我們?cè)賮?lái)看下前端頁(yè)面:

<!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" href="//unpkg.com/element-plus/dist/index.css" rel="external nofollow"  rel="external nofollow"  rel="external nofollow" />
    <script src="https://unpkg.com/vue@3"></script>
    <!-- Import component library -->
    <script src="//unpkg.com/element-plus"></script>
</head>
<body>
<div id="app">
    <div>
        <div>請(qǐng)選擇你的身份:</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="請(qǐng)假天數(shù)"></el-table-column>
        <el-table-column prop="reason" label="請(qǐng)假原因"></el-table-column>
        <el-table-column lable="操作">
            <template #default="scope">
                <el-button type="primary" @click="approveOrReject(scope.row.id,true,scope.row.name)">批準(zhǔn)</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>

大家看到,首先有一個(gè)下拉框,我們?cè)谶@個(gè)下拉框中來(lái)選擇用戶的身份。選擇完成后,觸發(fā) initTasks 方法,然后在這個(gè)方法中,發(fā)起網(wǎng)絡(luò)請(qǐng)求,最終將請(qǐng)求結(jié)果渲染出來(lái)。

最終效果如下:

怎么使用SpringBoot+Vue+Flowable模擬實(shí)現(xiàn)請(qǐng)假審批流程

當(dāng)然用戶也可以點(diǎn)擊刷新按鈕,刷新列表。

這樣,當(dāng)?shù)谖逍」?jié)中,員工提交了一個(gè)請(qǐng)假審批之后,我們?cè)谶@個(gè)列表中就可以查看到員工提交的請(qǐng)假審批了(在流程圖中,我們直接設(shè)置了用戶的請(qǐng)假審批固定提交給 managers,在后續(xù)的文章中,松哥會(huì)教大家如何把這個(gè)提交的目標(biāo)用戶變成一個(gè)動(dòng)態(tài)的)。

6. 請(qǐng)假審批

接下來(lái)經(jīng)理就可以選擇批準(zhǔn)或者是拒絕這請(qǐng)假了。

首先我們封裝一個(gè)實(shí)體類(lèi)用來(lái)接受前端傳來(lái)的請(qǐng)求:

public class ApproveRejectVO {
    private String taskId;
    private Boolean approve;
    private String name;
    // 省略 getter/setter
}

參數(shù)都好理解,approve 為 true 表示申請(qǐng)通過(guò),false 表示申請(qǐng)被拒絕。

接下來(lái)我們來(lái)看接口:

@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("操作失敗");
    }
}

大家注意這個(gè)審批流程:

  • 審批時(shí)需要兩個(gè)參數(shù),approved 和 employee,approved 為 true,就會(huì)自動(dòng)進(jìn)入到審批通過(guò)的流程中,approved 為 false 則會(huì)自動(dòng)進(jìn)入到拒絕流程中。

  • 通過(guò) taskService,結(jié)合 taskId,從流程中查詢(xún)出對(duì)應(yīng)的 task,然后調(diào)用 taskService.complete 方法傳入 taskId 和 變量,以使流程向下走。

  • 小伙伴們?cè)倩仡櫼幌挛覀兦懊娴牧鞒虉D,如果請(qǐng)求被批準(zhǔn)備了,那么在執(zhí)行完自定義的 Approve 邏輯后,就會(huì)進(jìn)入到 Holiday approved 這個(gè) userTask 中,注意此時(shí)并不會(huì)繼續(xù)向下走了(還差一步到結(jié)束事件);如果是請(qǐng)求拒絕,則在執(zhí)行完自定義的 Reject 邏輯后,就進(jìn)入到結(jié)束事件了,這個(gè)流程就結(jié)束了。

  • 針對(duì)第三條,所以代碼中我們還需要額外再加一步,如果是 approved 為 true,那么就再?gòu)漠?dāng)前流程中查詢(xún)出來(lái)需要執(zhí)行的 task,再調(diào)用 complete 繼續(xù)走一步,此時(shí)就到了結(jié)束事件了,這個(gè)流程就結(jié)束了。注意這次的查詢(xún)是根據(jù)當(dāng)前流程的 ID 查詢(xún)的,一個(gè)流程就是一條線,這條線上有很多 Task,我們可以從 Task 中獲取到流程的 ID。

好啦,接口就寫(xiě)好了。

當(dāng)然,這里還涉及到兩個(gè)自定義的邏輯,就是批準(zhǔn)或者拒絕之后的自定義邏輯,這個(gè)其實(shí)很好寫(xiě),如下:

public class Approve implements JavaDelegate {
    @Override
    public void execute(DelegateExecution execution) {
        System.out.println("申請(qǐng)通過(guò):"+execution.getVariables());
    }
}

我們自定義類(lèi)實(shí)現(xiàn) JavaDelegate 接口即可,然后我們?cè)?execute 方法中做自己想要做的事情即可,execution 中有這個(gè)流程中的所有變量。我們可以在這里發(fā)郵件、發(fā)短信等等。Reject 的定義方式也是類(lèi)似的。這些自定義類(lèi)寫(xiě)好之后,將來(lái)配置到流程圖中即可(可查看上文的流程圖)。

最后再來(lái)看看前端提交方法就簡(jiǎn)單了(頁(yè)面源碼上文已經(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);
        });
}

這就一個(gè)普通的 Ajax 請(qǐng)求,批準(zhǔn)的話第二個(gè)參數(shù)就為 true,拒絕的話第二個(gè)參數(shù)就為 false。

7. 結(jié)果查詢(xún)

最后,每個(gè)用戶都可以查看自己曾經(jīng)的申請(qǐng)記錄。本來(lái)這個(gè)登錄之后就可以展示了,但是因?yàn)槲覀儧](méi)有登錄,所以這里也是需要手動(dòng)輸入查詢(xún)的用戶,然后根據(jù)用戶名查詢(xún)這個(gè)用戶的歷史記錄,我們先來(lái)看查詢(xún)接口:

@GetMapping("/search")
public RespBean searchResult(String name) {
    return askForLeaveService.searchResult(name);
}

參數(shù)就是要查詢(xún)的用戶名。具體的查詢(xún)流程如下:

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);
}
  • 我們當(dāng)時(shí)在開(kāi)啟流程的時(shí)候,傳入了一個(gè)參數(shù) key,這里就是再次通過(guò)這個(gè) key,也就是用戶名去查詢(xún)歷史流程,查詢(xún)的時(shí)候還加上了 finished 方法,這個(gè)表示要查詢(xún)的流程必須是執(zhí)行完畢的流程,對(duì)于沒(méi)有執(zhí)行完畢的流程,這里不查詢(xún),查完之后,按照流程最后的處理時(shí)間進(jìn)行排序。

  • 遍歷第一步的查詢(xún)結(jié)果,從 HistoricProcessInstance 中提取出每一個(gè)流程的詳細(xì)信息,并存入到集合中,并最終返回。

  • 這里涉及到兩個(gè)歷史數(shù)據(jù)查詢(xún),createHistoricProcessInstanceQuery 用來(lái)查詢(xún)歷史流程,而 createHistoricVariableInstanceQuery 則主要是用來(lái)查詢(xún)流程變量的。

最后,前端通過(guò)表格展示這個(gè)數(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" href="//unpkg.com/element-plus/dist/index.css" rel="external nofollow"  rel="external nofollow"  rel="external nofollow" />
    <script src="https://unpkg.com/vue@3"></script>
    <!-- Import component library -->
    <script src="//unpkg.com/element-plus"></script>
</head>
<body>
<div id="app">
    <div >
        <el-input v-model="name"  placeholder="請(qǐng)輸入用戶名"></el-input>
        <el-button type="primary" @click="search">查詢(xún)</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="提交時(shí)間"></el-table-column>
            <el-table-column prop="endTime" label="審批時(shí)間"></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">已通過(guò)</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>

這個(gè)都是一些常規(guī)操作,我就不多說(shuō)了,最終展示效果如下:

怎么使用SpringBoot+Vue+Flowable模擬實(shí)現(xiàn)請(qǐng)假審批流程

感謝各位的閱讀,以上就是“怎么使用SpringBoot+Vue+Flowable模擬實(shí)現(xiàn)請(qǐng)假審批流程”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)怎么使用SpringBoot+Vue+Flowable模擬實(shí)現(xiàn)請(qǐng)假審批流程這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI