文章

【若依】33、Flowable 项目实践

AskForLeave.bpmn20.xml yueyazhui/ask_for_leave 技术栈:

  1. Spring Boot
  2. Spring Security
  3. Vue
  4. Axios
  5. Flowable
  6. MySQL/MyBatis

    1 绘制流程图

    image.png

    2 选择用户体系

    选择自己系统中的用户体系

  7. 以自己系统中的用户体系为准,然后,根据自己系统的用户体系,为 Flowable 创建对应的视图即可。

    🌰:删除 Flowable 中的用户表ACT_ID_USER,然后根据自己系统中的用户表,创建一个名为ACT_ID_USER的视图,并且视图中的字段和 Flowable 中原本的字段保持一致。这样,在项目中,只需要维护自己的用户体系即可。

ACT_ID_USER 用户
ACT_ID_GROUP
ACT_ID_MEMBERSHIP 用户_组
ACT_ID_PRIV 权限
ACT_ID_PRIV_MAPPING 用户_权限
  1. 利用 Flowable 中的 IdentityService 服务类,去控制 Flowable 中的用户体系,当需要操作自己的用户体系时,顺便也操作一下 Flowable 中的用户体系。

    3 创建项目

    添加依赖: image.png 项目创建完成后,需要手动添加依赖:

1
2
3
4
5
<dependency>
    <groupId>org.flowable</groupId>
    <artifactId>flowable-spring-boot-starter</artifactId>
    <version>6.7.2</version>
</dependency>

然后配置 application.properties:

1
2
3
4
5
6
7
# 数据库连接地址
spring.datasource.url=jdbc:mysql://localhost:3306/ask_for_leave?serverTimezone=Asia/Shanghai&useSSL=false&nullCatalogMeansCurrent=true
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=123456
# 还原 SpringSecurity 默认密码加密策略(Flowable 会修改 SpringSecurity 的默认密码加密策略)
flowable.idm.password-encoder=spring_delegating

👀:

  1. 数据库的连接地址中,需要拼接nullCatalogMeansCurrent=true,用于自动生成 Flowable 所需要的表。
  2. 还原 SpringSecurity 的密码加密策略。

    原本,SpringSecurity 默认密码加密策略是代理类 DelegatingPasswordEncoder,但是,Flowable 将默认加密策略改为 BCryptPasswordEncoder。

image.png

4 创建用户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role`  (
  `id` int NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '角色代码',
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '角色名称',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, 'manager', '经理');
INSERT INTO `sys_role` VALUES (2, 'employee', '员工');

-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`  (
  `id` int NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户名称',
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户密码',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'yue', '{noop}123');
INSERT INTO `sys_user` VALUES (2, 'yueyazhui', '{noop}123');

-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role`  (
  `id` int NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `user_id` int NULL DEFAULT NULL COMMENT '用户ID',
  `role_id` int NULL DEFAULT NULL COMMENT '角色ID',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户_角色' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (1, 1, 1);
INSERT INTO `sys_user_role` VALUES (2, 2, 2);

SET FOREIGN_KEY_CHECKS = 1;

5 系统登录

自定义两个类: 根据用户表创建用户类,并实现 UserDetails 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public class User implements UserDetails {

    private Integer id;
    private String username;
    private String password;
    private List<Role> roleList;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public List<Role> getRoleList() {
        return roleList;
    }

    public void setRoleList(List<Role> roleList) {
        this.roleList = roleList;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return roleList.stream().map(role -> new SimpleGrantedAuthority(role.getCode())).collect(Collectors.toList());
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

创建用户服务类,并实现 UserDetailsService 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
public class UserService implements UserDetailsService {

    @Autowired
    UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userMapper.loadUserByUsername(username);
        if(user == null) {
            throw new UsernameNotFoundException("用户名不存在");
        }
        user.setRoleList(userMapper.getRoleListByUserId(user.getId()));
        return user;
    }
}

6 两个服务类

  1. 请假通过的服务类:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    @Slf4j
    public class ApproveServiceTask implements JavaDelegate {
    
     @Override
     public void execute(DelegateExecution delegateExecution) {
         Map<String, Object> variables = delegateExecution.getVariables();
         // 请假的用户
         Object name = variables.get("applicant");
         // 请假的天数
         Object days = variables.get("days");
         log.info("{} 请假 {} 天的申请审批通过", name, days);
     }
    }
    
  2. 请假拒绝的服务类:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    @Slf4j
    public class RejectServiceTask implements JavaDelegate {
    
     @Override
     public void execute(DelegateExecution delegateExecution) {
         Map<String, Object> variables = delegateExecution.getVariables();
         // 请假的用户
         Object name = variables.get("applicant");
         // 请假的天数
         Object days = variables.get("days");
         log.info("{} 请假 {} 天的申请审批拒绝", name, days);
     }
    }
    

    7 部署流程

    在流程图中添加服务类: image.png 下载流程图,放到项目的 resources/processes 目录下,当 SpringBoot 项目启动时,流程文件会自动部署: image.png 启动项目,如果ACT_RE_DEPLOYMENTACT_RE_PROCDEF以及ACT_GE_BYTEARRAY表中存在数据,表明流程部署成功。

8 开启请假流程

Dto:

1
2
3
4
5
6
7
8
9
10
11
12
@Data
public class AskForLeaveDto {

    private Integer days;
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "Asia/Shanghai")
    private Date startDate;
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "Asia/Shanghai")
    private Date endDate;
    private String reason;
    private String approveUserId;
    private String approveUserName;
}

Controller:

1
2
3
4
5
6
7
8
9
10
11
@RestController
public class AskForLeaveController {

    @Autowired
    AskForLeaveService askForLeaveService;

    @PostMapping("apply")
    public Response apply(@RequestBody AskForLeaveDto askForLeaveDto) {
        return askForLeaveService.apply(askForLeaveDto);
    }
}

Service:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Service
public class AskForLeaveServiceImpl implements AskForLeaveService {

    @Autowired
    RuntimeService runtimeService;

    @Override
    @Transactional
    public Response apply(AskForLeaveDto askForLeaveDto) {
        try {
            String currentUserId = UserUtil.getCurrentUser().getId().toString();
            // 设置流程发起人(默认 UserUtil.getCurrentUser().getUserName())
            Authentication.setAuthenticatedUserId(currentUserId);
            Map<String, Object> vars = new HashMap<>();
            vars.put("applicant", currentUserId);
            vars.put("days", askForLeaveDto.getDays());
            vars.put("startDate", askForLeaveDto.getStartDate());
            vars.put("endDate", askForLeaveDto.getEndDate());
            vars.put("reason", askForLeaveDto.getReason());
            vars.put("approveUserId", askForLeaveDto.getApproveUserId());
            runtimeService.startProcessInstanceByKey("AskForLeave", vars);
            return Response.success("请假申请提交成功");
        } catch (Exception e) {
            e.printStackTrace();
            return Response.error("请假申请提交失败");
        }
    }
}

配置 SpringSecurity,SpringSecurity 默认开启了 CSRF 攻击防御机制,这样使得 POST 请求会比较麻烦,因此,需要禁用 CSRF。

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.authorizeRequests().anyRequest().authenticated()
                .and().formLogin()
                // 禁用 CSRF 攻击防御机制
                .and().csrf().disable();
        return httpSecurity.build();
    }
}

9 开发请假页面

Vue3+ElementPlus:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>请假</title>
    <!-- Import style -->
    <link rel="stylesheet" href="//unpkg.com/element-plus/dist/index.css" />
    <!-- Import Vue 3 -->
    <script src="//unpkg.com/vue@3"></script>
    <!-- Import component library -->
    <script src="//unpkg.com/element-plus"></script>
    <script src="https://unpkg.com/axios@1.1.2/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
    <div>
        <h1>请假申请</h1>
        <table>
            <tr>
                <th>天数:</th>
                <td>
                    <el-input-number v-model="askForLeaveApply.days" :precision="0" :step="1" :min="1" />
                </td>
            </tr>
            <tr>
                <th>日期:</th>
                <td>
                    <el-date-picker v-model="daterange" type="daterange" format="YYYY-MM-DD" value-format="YYYY-MM-DD" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"/>
                </td>
            </tr>
            <tr>
                <th>原因:</th>
                <td>
                    <el-input v-model="askForLeaveApply.reason" :rows="1" type="textarea" placeholder="请输入原因"/>
                </td>
            </tr>
            <tr>
                <th>审批人:</th>
                <td>
                    <el-select v-model="askForLeaveApply.approveUserId" placeholder="请选择审批人">
                        <el-option v-for="item in approveUserList" :key="item.id" :label="item.username" :value="item.id"/>
                    </el-select>
                </td>
            </tr>
        </table>
    </div>
</div>

<script>
    Vue.createApp({
        data() {
            return {
                askForLeaveApply: {
                    days: 1,
                    startDate: null,
                    endDate: null,
                    reason: '',
                    approveUserId: ''
                },
                daterange: [],
                approveUserList: []
            }
        },
        mounted() {
            this.getApproveUserList()
        },
        methods: {
            getApproveUserList() {
                axios.get('/approveUserList').then((res) => {
                    this.approveUserList = res.data
                })
            }
        },
    }).use(ElementPlus).mount('#app')
</script>
</body>
</html>

在 Vue3 中,创建 Vue 实例的方法是Vue.createApp

10 配置审批人

获取审批人列表: Controller:

1
2
3
4
@GetMapping("approveUserList")
public List<User> getApproveUserList() {
    return askForLeaveService.getApproveUserList();
}

Service:

1
2
3
4
@Override
public List<User> getApproveUserList() {
    return userMapper.getApproveUserList(UserUtil.getCurrentUser().getId());
}

Mapper:

1
2
3
4
5
6
7
8
9
10
11
12
<select id="getApproveUserList" resultType="top.yueyazhui.ask_for_leave.entity.User">
  SELECT
  u.id,
  u.username
  FROM
  sys_user u
  LEFT JOIN sys_user_role ur ON ur.user_id = u.id
  LEFT JOIN sys_role r ON r.id = ur.role_id
  WHERE
  r.code = 'manager'
  AND u.id != #{currentUserId};
</select>

前端请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<script>
  Vue.createApp({
      data() {
          return {
              askForLeaveApply: {
                  days: 1,
                  startDate: null,
                  endDate: null,
                  reason: '',
                  approveUserId: ''
              },
              daterange: [],
              approveUserList: []
          }
      },
      mounted() {
          this.getApproveUserList()
      },
      methods: {
          getApproveUserList() {
              axios.get('/approveUserList').then((res) => {
                  this.approveUserList = res.data
              })
          }
      },
  }).use(ElementPlus).mount('#app')
</script>

前端渲染:

1
2
3
4
5
6
7
8
<tr>
    <th>审批人:</th>
    <td>
        <el-select v-model="askForLeaveApply.approveUserId" placeholder="请选择审批人">
            <el-option v-for="item in approveUserList" :key="item.id" :label="item.username" :value="item.id"/>
        </el-select>
    </td>
</tr>

image.png

11 发起请假

发送请求:

1
2
3
4
5
<tr>
    <td>
        <el-button type="primary" @click="handleApply">申请</el-button>
    </td>
</tr>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
handleApply() {
    this.askForLeaveApply.startDate = this.daterange[0]
    this.askForLeaveApply.endDate = this.daterange[1]
    axios.post('/apply', this.askForLeaveApply).then((res) => {
        this.$message.success(res.data.message)
        this.getPendApproveList()
        this.daterange = []
        this.askForLeaveApply = {
            days: 1,
            startDate: null,
            endDate: null,
            reason: '',
            approveUserId: ''
        }
    })
},

12 查看请假流程

1. 查看待审批的流程

Controller:

1
2
3
4
5
6
7
8
/**
 * 当前用户待审批流程列表
 * @return
 */
@GetMapping("pendApproveList")
public List<AskForLeaveDto> getPendApproveList() {
    return askForLeaveService.getPendApproveList();
}

Service:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
 * 当前用户待审批流程列表
 * @return
 */
@Override
public List<AskForLeaveDto> getPendApproveList() {
    List<AskForLeaveDto> askForLeaveDtoList = new ArrayList<>();
    List<Execution> executionList = runtimeService.createExecutionQuery().startedBy(UserUtil.getCurrentUser().getId().toString()).list();
    for (Execution execution : executionList) {
        Integer days = ((Integer) runtimeService.getVariable(execution.getId(), "days"));
        Date startDate = (Date) runtimeService.getVariable(execution.getId(), "startDate");
        Date endDate = (Date) runtimeService.getVariable(execution.getId(), "endDate");
        String reason = (String) runtimeService.getVariable(execution.getId(), "reason");
        String approveUserId = ((String) runtimeService.getVariable(execution.getId(), "approveUserId"));
        AskForLeaveDto askForLeaveDto = new AskForLeaveDto();
        askForLeaveDto.setDays(days);
        askForLeaveDto.setStartDate(startDate);
        askForLeaveDto.setEndDate(endDate);
        askForLeaveDto.setReason(reason);
        askForLeaveDto.setApproveUserId(approveUserId);
        askForLeaveDto.setApproveUserName(userService.getUsernameById(Integer.parseInt(approveUserId)));
        askForLeaveDto.setProcessInstanceId(execution.getProcessInstanceId());
        askForLeaveDtoList.add(askForLeaveDto);
    }
    return askForLeaveDtoList;
}

前端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div>
    <div>
        <h1>我的请假列表(当前)</h1>
        <el-table :data="pendApproveList" stripe border style="width: 100%">
            <el-table-column prop="days" label="天数"></el-table-column>
            <el-table-column prop="date" label="日期">
                <template #default="scope"></template>
            </el-table-column>
            <el-table-column prop="reason" label="原因"></el-table-column>
            <el-table-column prop="approveUserName" label="审批人"></el-table-column>
            <el-table-column label="操作">
                <template #default="scope">
                    <el-button type="primary" text bg @click="showRealTimeProgressImage(scope.row)">查看</el-button>
                </template>
            </el-table-column>
        </el-table>
    </div>
</div>

待审批流程列表的请求:

1
2
3
4
5
getPendApproveList() {
    axios.get('/pendApproveList').then((res) => {
        this.pendApproveList = res.data
    })
},

实时进度图: Controller:

1
2
3
4
5
6
7
8
9
/**
 * 实时进度图
 * @param processInstanceId
 * @throws IOException
 */
@GetMapping("realTimeProgressImage/{processInstanceId}")
public void getRealTimeProgressImage(@PathVariable String processInstanceId) throws IOException {
    askForLeaveService.getRealTimeProgressImage(processInstanceId);
}

Service:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
 * 实时进度图
 * @return
 */
@Override
public void getRealTimeProgressImage(String processInstanceId) throws IOException {
    ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionKey("AskForLeave").latestVersion().singleResult();
    // 绘制流程图的对象
    BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
    // 绘制流程图的生成器
    DefaultProcessDiagramGenerator defaultProcessDiagramGenerator = new DefaultProcessDiagramGenerator();
    // 所有已经执行过的活动 ID,将之保存在这个集合中
    List<String> highLightedActivities = new ArrayList<>();
    // 所有已经执行过的线条 ID,将之保存在这个集合中
    List<String> highLightedFlows  = new ArrayList<>();
    // 缩放因子
    double scaleFactor = 1.0;
    // 绘制连接线时,是否绘制对应的文本
    boolean drawSequenceFlowNameWithNoLabelDI = true;
    // 查询流程实例的所有活动信息
    List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).list();
    for (HistoricActivityInstance historicActivityInstance : historicActivityInstanceList) {
        if (historicActivityInstance.getActivityType().equals("sequenceFlow")) {
            // 如果活动类型是 sequenceFlow,那么就加入到线条的 ID 中
            highLightedFlows.add(historicActivityInstance.getActivityId());
        } else {
            // 否则就加入到高亮活动的 ID 中
            highLightedActivities.add(historicActivityInstance.getActivityId());
        }
    }
    ProcessEngineConfiguration processEngineConfig = processEngine.getProcessEngineConfiguration();
    InputStream is = defaultProcessDiagramGenerator.generateDiagram(bpmnModel, "PNG", highLightedActivities, highLightedFlows, processEngineConfig.getActivityFontName(), processEngineConfig.getLabelFontName(), processEngineConfig.getAnnotationFontName(), processEngineConfig.getClassLoader(), scaleFactor, drawSequenceFlowNameWithNoLabelDI);
    HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getResponse();
    assert ObjectUtil.isNotNull(response);
    response.setContentType(MediaType.IMAGE_PNG_VALUE);
    FileCopyUtils.copy(is, response.getOutputStream());
}

前端渲染:

1
2
3
4
5
<div>
    <el-dialog v-model="realTimeProgressImageDialogVisible" title="实时进度图" width="560" :close-on-click-modal="false" :draggable="true">
        <img :src="realTimeProgressImageUrl" alt="实时进度图" />
    </el-dialog>
</div>

前端设置属性:

1
2
3
4
showRealTimeProgressImage(row) {
    this.realTimeProgressImageUrl =  '/realTimeProgressImage/' + row.processInstanceId
    this.realTimeProgressImageDialogVisible = true
},

2. 查看待审批的任务

每一个用户登录上来之后,可以查看到自己需要审批的任务。 Controller:

1
2
3
4
5
6
7
8
/**
 * 当前用户任务列表
 * @return
 */
@GetMapping("taskList")
public List<TaskDto> getTaskList() {
    return askForLeaveService.getTaskList();
}

Service:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
public List<TaskDto> getTaskList() {
    List<TaskDto> taskDtoList = new ArrayList<>();
    List<Task> taskList = taskService.createTaskQuery().taskAssignee(UserUtil.getCurrentUser().getId().toString()).list();
    for (Task task : taskList) {
        TaskDto taskDto = new TaskDto();
        Map<String, Object> variables = taskService.getVariables(task.getId());
        taskDto.setApplicantId(((String) variables.get("applicantId")));
        taskDto.setApplicantName(userService.getUsernameById(Integer.parseInt(variables.get("applicantId").toString())));
        taskDto.setDays(((Integer) variables.get("days")));
        taskDto.setStartDate(((Date) variables.get("startDate")));
        taskDto.setEndDate(((Date) variables.get("endDate")));
        taskDto.setReason(((String) variables.get("reason")));
        taskDto.setApproveUserId(((String) variables.get("approveUserId")));
        taskDto.setApproveUserName(userService.getUsernameById(Integer.parseInt(variables.get("approveUserId").toString())));
        taskDto.setTaskId(task.getId());
        taskDtoList.add(taskDto);
    }
    return taskDtoList;
}

前端渲染:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<div>
    <div>
        <h1>待我的审批列表</h1>
        <el-table :data="taskList" stripe border style="width: 100%">
            <el-table-column prop="applicantName" label="申请人"></el-table-column>
            <el-table-column prop="days" label="天数"></el-table-column>
            <el-table-column prop="date" label="日期">
                <template #default="scope"></template>
            </el-table-column>
            <el-table-column prop="reason" label="原因"></el-table-column>
            <el-table-column label="操作">
                <template #default="scope">
                    <el-button type="primary" @click="handleApprove(scope.row, true)">批准</el-button>
                    <el-button type="danger" @click="handleApprove(scope.row, false)">拒绝</el-button>
                </template>
            </el-table-column>
        </el-table>
    </div>
</div>

获取数据:

1
2
3
4
5
getTaskList() {
    axios.get('/taskList').then((res) => {
        this.taskList = res.data
    })
},

3. 流程审批

Controller:

1
2
3
4
5
6
7
8
9
/**
 * 审批
 * @param taskDto
 * @return
 */
@PostMapping("approve")
public Response approve(@RequestBody TaskDto taskDto) {
    return askForLeaveService.approve(taskDto);
}

Service:

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public Response approve(TaskDto taskDto) {
    try {
        Map<String, Object> vars = new HashMap<>();
        vars.put("approve", taskDto.getApprove());
        taskService.complete(taskDto.getTaskId(), vars);
        return Response.success("审批成功");
    } catch (Exception e) {
        e.printStackTrace();
        return Response.success("审批失败");
    }
}

前端请求:

1
2
3
4
5
6
7
handleApprove(row, approve) {
    row.approve = approve
    axios.post('/approve', row).then((res) => {
        this.$message.success(res.data.message)
        this.getTaskList()
    })
},

4. 查看历史流程

Controller:

1
2
3
4
5
6
7
8
/**
 * 当前用户历史流程列表
 * @return
 */
@GetMapping("historyList")
public List<AskForLeaveDto> getHistoryList() {
    return askForLeaveService.getHistoryList();
}

Service:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Override
public List<AskForLeaveDto> getHistoryList() {
    List<AskForLeaveDto> askForLeaveDtoList = new ArrayList<>();
    List<HistoricProcessInstance> historicProcessInstanceList = historyService.createHistoricProcessInstanceQuery().startedBy(UserUtil.getCurrentUser().getId().toString()).finished().list();
    for (HistoricProcessInstance historicProcessInstance : historicProcessInstanceList) {
        AskForLeaveDto askForLeaveDto = new AskForLeaveDto();
        List<HistoricVariableInstance> historicVariableInstanceList = historyService.createHistoricVariableInstanceQuery().processInstanceId(historicProcessInstance.getId()).list();
        for (HistoricVariableInstance historicVariableInstance : historicVariableInstanceList) {
            String variableName = historicVariableInstance.getVariableName();
            Object value = historicVariableInstance.getValue();
            switch (variableName) {
                case "days":
                    askForLeaveDto.setDays(((Integer) value));
                    break;
                case "startDate":
                    askForLeaveDto.setStartDate(((Date) value));
                    break;
                case "endDate":
                    askForLeaveDto.setEndDate(((Date) value));
                    break;
                case "reason":
                    askForLeaveDto.setReason(((String) value));
                    break;
                case "approveUserId":
                    askForLeaveDto.setApproveUserId(((String) value));
                    askForLeaveDto.setApproveUserName(userService.getUsernameById(Integer.parseInt(askForLeaveDto.getApproveUserId())));
                    break;
                case "approve":
                    askForLeaveDto.setApprove(((Boolean) value));
                    break;
            }
        }
        askForLeaveDto.setProcessInstanceId(historicProcessInstance.getId());
        askForLeaveDtoList.add(askForLeaveDto);
    }
    return askForLeaveDtoList;
}

前端渲染:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<div>
    <div>
        <h1>我的请假列表(历史)</h1>
        <el-table :data="historyList" stripe border style="width: 100%">
            <el-table-column prop="days" label="天数"></el-table-column>
            <el-table-column prop="date" label="日期">
                <template #default="scope"></template>
            </el-table-column>
            <el-table-column prop="reason" label="原因"></el-table-column>
            <el-table-column prop="approveUserName" label="审批人"></el-table-column>
            <el-table-column prop="approve" label="审批结果">
                <template #default="scope">
                    <el-tag v-if="scope.row.approve" type="success">通过</el-tag>
                    <el-tag v-else type="danger">拒绝</el-tag>
                </template>
            </el-table-column>
            <el-table-column label="操作">
                <template #default="scope">
                    <el-button type="primary" text bg @click="showRealTimeProgressImage(scope.row)">查看</el-button>
                </template>
            </el-table-column>
        </el-table>
    </div>
</div>

前端请求:

1
2
3
4
5
getHistoryList() {
    axios.get('/historyList').then((res) => {
        this.historyList = res.data
    })
}
本文由作者按照 CC BY 4.0 进行授权