关于java:SpringBootVueFlowable模拟一个请假审批流程

47次阅读

共计 16514 个字符,预计需要花费 42 分钟才能阅读完成。

小伙伴们晓得松哥最近在录 TienChin 我的项目视频,这个我的项目会用到工作流,为了帮忙小伙伴们更好的了解这个我的项目,松哥最近会出几篇文章和大伙聊一聊工作流 flowable 的应用,算是给 TienChin 我的项目的第一个铺垫,当然,在 TienChin 我的项目的系列视频中,我也会和大家具体聊一聊 flowable 流程引擎的应用。

明天我就先写一个简略的销假流程,让小伙伴们对 flowable 先有一个直观的认知。

1. 成果展现

在正式开搞之前,我先来给小伙伴们看下咱们明天要实现的成果。

简略起见,我这里并没有引入用户、角色等概念,波及到用户的中央都是手动输出,在后续的文章中我会持续联合 Spring Security 来和大家展现引入用户之后的状况。

咱们先来看看销假页面:

员工能够在这个页面输出姓名,销假天数以及销假理由等,而后点击按钮提交一个销假申请。

当员工提交销假申请之后,这个销假申请默认是由经理来解决的,此时经理登录之后,就能够看到员工提交上来的申请:

经理此时能够抉择批准或者回绝。无论是批准还是回绝,都能够通过短信或者邮件等告知员工。

对于员工来说,也能够在一个页面查问本人销假流程的最终状况:

可能有小伙伴曾经留神到了,咱们这里所有波及到用户名的中央,都须要手动输出。这是因为我为了让这个案例足够简略,临时没有引入 Spring Security,只是单纯的和大家分享 Flowable 的用法,等小伙伴们通过这篇文章把握了 Flowable 的根本用法之后,下篇文章我会和大家分享如何联合具体的用户来应用。

2. 工程创立

我就间接来和小伙伴们展现 Spring Boot 中 flowable 的用法了。

首先咱们创立一个 Spring Boot 我的项目,创立的时候引入 Web 和 MySQL 驱动依赖即可,我的项目创立胜利之后,再引入 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>

我的项目创立胜利之后,首先须要咱们在 application.properties 中配置一下数据库连贯信息,如下:

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

配置实现之后,当 Spring Boot 我的项目第一次启动的时候,会主动创立进去对应的表和须要的数据。

同时,Spring Boot 我的项目也会主动创立并裸露 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. 流程图剖析

明天这个例子比较简单,就是一个销假流程,我临时先不跟小伙伴们去扯画流程图的事,咱们间接用一个官网现成的销假流程图:

咱们先来简略剖析一下这张图:

  1. 最左侧的圆圈叫做启动事件 (start event),这示意一个流程实例的终点。
  2. 一个流程启动之后,首先达到第一个有用户图标的矩形中,这个矩形称为一个 User Task,在这个 User Task 中,经理能够抉择批准亦或者回绝。
  3. UserTask 的下一步是一个菱形,这个称作排他网关(Exclusive Gateway), 这个会将申请路由到不同的中央。
  4. 先说批准,如果在第一个矩形中,经理抉择了批准,那么就会进入到一个带有齿轮图标的矩形中,在这个矩形中咱们咱们能够额定做一些事件,而后又会调用到一个 UserTask,最终实现整个流程。
  5. 如果经理抉择了回绝,则会进入到上面的发邮件的矩形中,在这个中咱们能够给员工发送一个告诉,告知他销假没有通过。
  6. 当零碎走到最左边的圆圈之后,就示意这个流程执行完结了。

这个流程图对应的 XML 文件位于 src/main/resources/processes/holiday-request.bpmn20.xml 地位,其内容如下:

<?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 文件,你会发现流程引擎原来如此简略!

咱们来挨个看下这里的每一个节点:

  1. process:这示意一个流程,例如本文和大家分享的销假就是一个流程。
  2. startEvent:这示意流程的开始,这就是一个开始事件。
  3. userTask:这就是一个具体的流程节点了,flowable:candidateGroups 属性示意这个节点该由哪个用户组中的用户来解决。
  4. sequenceFlow:这就是连贯各个流程节点之间的线条,这个里边个别有两个属性,sourceRef 和 targetRef,前者示意线条的终点,后者示意线条的起点。
  5. exclusiveGateway:示意一个排他性网关,也就是那个菱形抉择框。
  6. 从排他性网关进去的线条有两个,大家留神看下面的代码,这两个线条中都波及到一个变量 approved,如果这个变量为 true,则 targeRef 就是 externalSystemCall;如果这个变量为 false,则 targetRef 就是 rejectLeave。
  7. serviceTask:这就是咱们定义的一个具体的内部服务,如果在整个流程执行的过程中,你有一些须要本人实现的事件,那么能够通过 serviceTask 来实现,这个节点会有一个 flowable:class 属性,这个属性的值就是一个自定义类。
  8. 另外,上文中局部节点中还波及到变量 ${},这个变量是在流程执行的过程中传入进来的。

总而言之,只有小伙伴们静下心来认真浏览一下下面的 XML,你会发现 So Easy!

4. 销假申请

好了,接下来咱们就来看一个具体的销假申请。因为销假流程只有放对地位,就会主动加载,所以咱们并不需要手动加载销假流程,间接开始一个销假申请流程即可。

4.1 服务端接口

首先咱们须要一个实体类来承受前端传来的销假参数:用户名、销假天数以及销假理由:

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

再拿出祖传的 RespBean,以便响应数据不便一些:

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 中,来持续看:

@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 三个参数,咱们将这三个参数放入到一个 Map 中,而后通过 RuntimeService#startProcessInstanceByKey 办法来开启一个流程,开启流程的时候一共传入了三个参数:

  1. 第一个参数示意流程引擎的名字,这就是咱们方才在流程的 XML 文件中定义的名字。
  2. 第二个参数示意以后这个流程的 key,我用了申请人的名字,未来咱们能够通过申请人的名字查问这个人已经提交的所有申请流程。
  3. 第三个参数就是咱们的变量了。

好了,这服务端就写好了。

4.2 前端页面

接下来咱们来开发前端页面。

前端我应用 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" href="//unpkg.com/element-plus/dist/index.css"/>
    <script src="https://unpkg.com/vue@3"></script>
    <!-- Import component library -->
    <script src="//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> 请输出销假天数:</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>

这个页面有几个须要留神的点:

  1. 通过 Vue.createApp 来创立一个 Vue 实例,这跟以前 Vue2 中间接 new 一个 Vue 实例不一样。
  2. 在最上面,通过 use 来配置 ElementPlus 插件,这个跟 Vue2 也不一样。在 Vue2 中,如果咱们单纯的在 HTML 页面中援用 ElementUI 并不需要这个步骤。
  3. 剩下的货色就比较简单了,下面先引入 Vue3、Axios 以及 ElementPlus,而后三个输入框,点击按钮提交申请,参数就是三个输入框中的数据,提交胜利或者失败,别离弹个框出来提醒一下就行了。

好啦,这就写好了。

然而,提交实现后,没有一个直观的展现,尽管前端提醒说提交胜利了,然而到底胜利没,还得眼见为实。

5. 工作展现

好了,接下来咱们要做的事件就是把用户提交的流程展现进去。

按理说,比方经理登录胜利之后,零碎页面就主动展现进去经理须要审批的流程,然而咱们以后这个例子为了简略,就没有登录这个操作了,须要须要用户未来在网页上选一下本人的身份,接下来就会展现出这个身份所对应的须要操作的流程。

咱们来看工作接口:

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

这个申请参数 identity 就示意以后用户的身份(原本应该是登录后主动获取,然而因为咱们目前没有登录,所以这个参数是由前端传递过去)。来持续看 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 用户组解决的工作。

这段代码要联合流程图一起来了解,小伙伴们回顾下咱们流程图中有如下一句:

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

这意思就是说这个 userTask 是由 managers 这个组中的用户来解决,所以下面 Java 代码中的查问就是查问 managers 这个组中的用户须要审批的工作。

咱们将所有须要审批的工作查问进去后,通过 taskId 能够进一步查问到这个工作中过后传入的各种变量,咱们将这些数据封装成一个对象,并最终返回到前端。

最初,咱们再来看下前端页面:

<!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"/>
    <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> 请抉择你的身份:</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="销假天数"></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>

大家看到,首先有一个下拉框,咱们在这个下拉框中来抉择用户的身份。抉择实现后,触发 initTasks 办法,而后在这个办法中,发动网络申请,最终将申请后果渲染进去。

最终成果如下:

当然用户也能够点击刷新按钮,刷新列表。

这样,当第五大节中,员工提交了一个销假审批之后,咱们在这个列表中就能够查看到员工提交的销假审批了(在流程图中,咱们间接设置了用户的销假审批固定提交给 managers,在后续的文章中,松哥会教大家如何把这个提交的指标用户变成一个动静的)。

6. 销假审批

接下来经理就能够抉择批准或者是回绝这销假了。

首先咱们封装一个实体类用来承受前端传来的申请:

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

参数都好了解,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) {
                // 如果是批准,还须要持续走一步
                Task t = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
                taskService.complete(t.getId());
            }
            return RespBean.ok("操作胜利");
        } catch (Exception e) {e.printStackTrace();
        }
        return RespBean.error("操作失败");
    }
}

大家留神这个审批流程:

  1. 审批时须要两个参数,approved 和 employee,approved 为 true,就会主动进入到审批通过的流程中,approved 为 false 则会主动进入到回绝流程中。
  2. 通过 taskService,联合 taskId,从流程中查问出对应的 task,而后调用 taskService.complete 办法传入 taskId 和 变量,以使流程向下走。
  3. 小伙伴们再回顾一下咱们后面的流程图,如果申请被批筹备了,那么在执行完自定义的 Approve 逻辑后,就会进入到 Holiday approved 这个 userTask 中,留神此时并不会持续向下走了(还差一步到完结事件);如果是申请回绝,则在执行完自定义的 Reject 逻辑后,就进入到完结事件了,这个流程就完结了。
  4. 针对第三条,所以代码中咱们还须要额定再加一步,如果是 approved 为 true,那么就再从以后流程中查问进去须要执行的 task,再调用 complete 持续走一步,此时就到了完结事件了,这个流程就完结了。留神这次的查问是依据以后流程的 ID 查问的,一个流程就是一条线,这条线上有很多 Task,咱们能够从 Task 中获取到流程的 ID。

好啦,接口就写好了。

当然,这里还波及到两个自定义的逻辑,就是批准或者回绝之后的自定义逻辑,这个其实很好写,如下:

public class Approve implements JavaDelegate {
    @Override
    public void execute(DelegateExecution execution) {System.out.println("申请通过:"+execution.getVariables());
    }
}

咱们自定义类实现 JavaDelegate 接口即可,而后咱们在 execute 办法中做本人想要做的事件即可,execution 中有这个流程中的所有变量。咱们能够在这里发邮件(公众号江南一点雨后盾回复 666 有发邮件教程)、发短信等等。Reject 的定义形式也是相似的。这些自定义类写好之后,未来配置到流程图中即可(可查看上文的流程图)。

最初再来看看前端提交办法就简略了(页面源码上文曾经列出):

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 申请,批准的话第二个参数就为 true,回绝的话第二个参数就为 false。

7. 后果查问

最初,每个用户都能够查看本人已经的申请记录。原本这个登录之后就能够展现了,然而因为咱们没有登录,所以这里也是须要手动输出查问的用户,而后依据用户名查问这个用户的历史记录,咱们先来看查问接口:

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

参数就是要查问的用户名。具体的查问流程如下:

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);
}
  1. 咱们过后在开启流程的时候,传入了一个参数 key,这里就是再次通过这个 key,也就是用户名去查问历史流程,查问的时候还加上了 finished 办法,这个示意要查问的流程必须是执行结束的流程,对于没有执行结束的流程,这里不查问,查完之后,依照流程最初的解决工夫进行排序。
  2. 遍历第一步的查问后果,从 HistoricProcessInstance 中提取出每一个流程的详细信息,并存入到汇合中,并最终返回。
  3. 这里波及到两个历史数据查问,createHistoricProcessInstanceQuery 用来查问历史流程,而 createHistoricVariableInstanceQuery 则次要是用来查问流程变量的。

最初,前端通过表格展现这个数据即可:

<!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"/>
    <script src="https://unpkg.com/vue@3"></script>
    <!-- Import component library -->
    <script src="//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="天数"></el-table-column>
            <el-table-column label="状态">
                <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>

这个都是一些惯例操作,我就不多说了,最终展现成果如下:

8. 小结

好啦,一个简略的销假流程,让大家对 Flowable 的玩法有一个根本的认知,下篇文章松哥来和大家持续欠缺本文。Flowable 的视频将会呈现在 TienChin 我的项目中,大家不要错过哦:TienChin 我的项目配套视频来啦。

正文完
 0