1、什么是工作流
1.1、概述
工作流(Workflow),就是“业务过程的部分或整体在计算机应用环境下的自动化”,它主要解决的是“使在多个参与者之间按照某种预定义的规则传递文档、信息或任务的过程自动进行,从而实现某个预期的业务目标,或者促使此目标的实现”。
1.2、常见工作流
采用工作流有以下优点:
- 提高系统的柔性,适应业务流程的变化
- 实现更好的业务过程控制,提高顾客服务质量
- 降低系统开发和维护成本
- 常见工作流:
Activiti、JBPM、OSWorkflow、ActiveBPEL、YAWL等。本文主要采用 Activiti7 工作流开发。
1.3、流程实例
流程实例(ProcessInstance) 代表流程定义的执行实例,一个流程实例包括了所有的运行节点,我们可以利用这个对象来了解当前流程实例的进度等信息。
例如:用户或者程序安装流程定义的内容发起了一个流程,这个就是一个流程实例
1.4、业务管理
流程定义部署在 Activiti 后,我们就可以在系统中通过Activiti 去管理流程的执行,但是如果我们要将我们的流程实例和业务数据关联,这时我们需要使用到 Activiti 中预留的 BusinessKey(业务标识) 来关联
2、Activiti7 介绍
2.1、概念
Alfresco软件在2010年5月17日宣布Activiti业务流程管理(BPM)开源项目的正式启动。Activiti是一个工作流引擎, Activiti可以将业务系统中复杂的业务流程抽取出来,使用专门的建模语言BPMN2.0进行定义,业务流程按照预先定义的流程进行执行,实现了系统的流程由activiti进行管理,减少业务系统由于流程变更进行系统升级改造的工作量,从而提高系统的健壮性,同时也减少了系统开发维护成本。
2.2、基本术语
- ProcessEngine对象: 这是Activiti工作的核心.负责生成流程运行时的各种实例及数据,监控和管理流程的运行
- BPM (业务流程管理):是一种以规范化的构造端到端的卓越业务流程为中心,以持续的提高组织业务绩效为目的的系统化方法
- BPMN(Business Process Model and Notation): 业务流程建模与标注,描述流程的基本符号,包括这些图元如何组合成一个业务流程图(Business Process Diagram)bpmn文件又可以叫做流程定义文件,它需要遵循BPMN语言规。
流对象(process engine):通过它可以获得我们需要的一切activiti服务,一个业务流程图有三个流对象的核心元素:
- 事件:一个事件用圆圈来描述,表示一个业务流程期间发生的东西。事件影响流程的流动,一般有一个原因(触发器)或一个影响(结果),基于它们对流程的影响,有三种事件:开始事件、中间事件、终止事件。
- 活动:用圆角矩形表示,一个流程由一个活动或多个活动组成。
- 条件:条件用菱形表示,用于控制序列流的分支与合并,可以作为选择,包括路径的分支与合,内部的标记会给出控制流的类型。
2.3、系统服务类
- 结构图
- 核心类
ProcessEngine: 流程引擎的抽象,可以通过此类获取需要的所有服务 - 服务类
通过ProcessEngine获取,Activiti将不同生命周期的服务封装在不同Service中,包括定义、部署、运行。通过服务类可获取相关生命周期中的服务信息:
- RepositoryService:Repository Service提供了对repository的存取服,Activiti中每一个不同版本的业务流程的定义都需要使用一些定义文件,部署文件和支持数据(例如BPMN2.0XML文件,表单定义文件,流程定义图像文件等),这些文件都存储在Activiti内建的Repository中。
- RuntimeService:Runtime Service提供了启动流程,查询流程实例,设置获取流程实例变量等功能。此外它还提供了对流程部署,流程定义和流程实例的存取服务。
- TaskService:Task Service提供了对用户Task和Form相关的操作。它提供了运行时任务查询、领取、完成、删除以及变量设置等功能。
- HistoryService:History Service用于获取正在运行或已经完成的流程实例的信息,与Runtime Service获取的流程信息不同,历史信息包含已经持久化存储的永久信息,并已经被针对查询优化。
FormService:使用Form Service可以存取启动和完成任务所需的表单数据并且根据需要来渲染表单。Activiti中的流程和状态Task均可以关联业务相关的数据。IdentityService:Identity Service提供了对Activiti系统中的用户和组的管理功,Activiti中内置了用户以及组管理的功能,必须使用这些用户和组的信息才能获取到相应的Task。- ManagementService:Management Service提供了对Activiti流程引擎的管理和维护功能,这些功能不在工作流驱动的应用程序中使用。
2.4、数据库表结构
Activiti7 工作流总共包含25张数据表(Activiti6 包含23张表):
所有的表名默认以“ACT_”开头。表名的第二部分用两个字母表明表的用途:
- ACT_GE (GE) :表示 general 全局通用数据及设置,各种情况都使用的数据。
- ACT_HI (HI) :表示 history 历史数据表,包含着程执行的历史相关数据,如结束的流程实例,变量,任务,等等。
- ACT_RE (RE) :表示 repository 存储,包含的是静态信息,如,流程定义,流程的资源(图片,规则等)。
- ACT_RU (RU) :表示 runtime 运行时,运行时的流程变量,用户任务,变量,职责(job)等运行时的数据。
- Activiti 只存储实例执行期间的运行时数据,当流程实例结束时,将删除这些记录。这就保证了这些运行时的表小且快。
表名 | 解释 |
---|---|
act_evt_log | 流程引擎通用日志表 |
act_ge_bytearray | 二进制表,存储通用的流程资源 |
act_ge_property | 系统存储表,存储整个流程引擎数据,默认存储三条数据 |
act_hi_actinst | 历史节点表 |
act_hi_attachment | 历史附件表 |
act_hi_comment | 历史意见表 |
act_hi_detail | 历史详情表 |
act_hi_identitylink | 历史用户信息表 |
act_hi_procinst | 历史流程实例表 |
act_hi_taskinst | 历史任务实例表 |
act_hi_varinst | 历史变量表 |
act_procdef_info | 流程定义的动态变更信息 |
act_re_deployment | 部署信息表 |
act_re_model | 流程设计实体表 |
act_re_procdef | 流程定义数据表 |
act_ru_deadletter_job | 作业失败表,失败次数>重试次数 |
act_ru_event_subscr | 运行时事件表 |
act_ru_execution | 运行时流程执行实例表 |
act_ru_identitylink | 运行时用户信息表 |
act_ru_integration | 运行时综合表 |
act_ru_job | 作业表 |
act_ru_suspended_job | 作业暂停表 |
act_ru_task | 运行时任务信息表 |
act_ru_timer_job | 运行时定时器表 |
act_ru_variable | 运行时变量表 |
3、集成 IDEA
3.1、部分效果
- 流程图
- 列表页
- 请假申请
- 同意申请
- 驳回申请
- 流程结束
3.2、下载插件
进到 IDEA 的插件页面,搜索插件 Activiti BPMN visualizing,点击安装,应用并完成:
3.3、配置文件
<dependencies>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--spring boot test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- web依赖springmvc -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.2</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--activiti 工作流的依赖-->
<!-- <dependency>-->
<!-- <groupId>org.activiti</groupId>-->
<!-- <artifactId>activiti-spring-boot-starter-basic</artifactId>-->
<!-- <version>5.1.17</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.0.0.Beta2</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
<exclusion>
<groupId>javax.el</groupId>
<artifactId>el-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-image-generator</artifactId>
<version>7.0.0.Beta2</version>
</dependency>
<!--svg转png-->
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-transcoder</artifactId>
<version>1.13</version>
</dependency>
<dependency>
<groupId>batik</groupId>
<artifactId>batik-util</artifactId>
<version>1.6-1</version>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-codec</artifactId>
<version>1.7</version>
</dependency>
</dependencies>
server:
port: 9204
spring:
application:
name: llh-activiti # 注册到eureka上面的应用名称
# 数据源
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/activiti?useSSL=false&useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&nullCatalogMeansCurrent=true
username: root
password: root
# 工作流配置
activiti:
database-schema-update: true
check-process-definitions: true
process-definition-location-prefix: classpath:/process/
history-level: full
db-history-used: true
# mybatis plus配置
mybatis-plus:
mapper-locations: classpath*:mapper/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
package com.llh.config;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* Security框架配置
*/
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 关闭csrf防护
.csrf().disable()
.headers().frameOptions().disable()
.and()
//定制url访问权限
.authorizeRequests()
//无限登录即可访问
.antMatchers("/**").permitAll()
//需要特定权限
// .antMatchers("/sysUser/**","/sysAuthority/**").hasAnyAuthority("ROLE_ADMIN","ROLE_SA")
//其他接口登录才能访问
// .anyRequest().authenticated()
.and();
}
}
package com.llh.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
/**
* User: lilinhan
* DateTime: 2023/10/13 11:53
*/
@Configuration
@MapperScan(value = {"com.llh.mapper"})
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
// 分页拦截器
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return mybatisPlusInterceptor;
}
@Bean
public ThreadPoolTaskExecutor threadPoolTaskExecutor(){
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(5);// 核心线程数量
taskExecutor.setMaxPoolSize(10);// 最大线程数量
taskExecutor.setKeepAliveSeconds(2);// 设置时长(秒)
taskExecutor.setQueueCapacity(10);// 设置队列容量
taskExecutor.setThreadNamePrefix("llh-thread:");// 线程名前缀
return taskExecutor;
}
}
package com.llh.utils;
import org.springframework.beans.BeanUtils;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
public class CommonUtils {
/**
* 把指定的复杂对象属性,按照指定的内容,封装到新的map中
*
* @param source 目标对象
* @param ps 需要封装到map中的属性
* @return
*/
public static Map<String, Object> objmap(Object source, String[] ps) {
Map<String, Object> map = new HashMap<>();
if (source == null)
return null;
if (ps == null || ps.length < 1) {
return null;
}
for (String p : ps) {
PropertyDescriptor sourcePd = BeanUtils.getPropertyDescriptor(
source.getClass(), p);
if (sourcePd != null && sourcePd.getReadMethod() != null) {
try {
Method readMethod = sourcePd.getReadMethod();
if (!Modifier.isPublic(readMethod.getDeclaringClass()
.getModifiers())) {
readMethod.setAccessible(true);
}
Object value = readMethod.invoke(source, new Object[0]);
map.put(p, value);
} catch (Exception ex) {
throw new RuntimeException(
"Could not copy properties from source to target",
ex);
}
}
}
return map;
}
}
3.4、创建流程图
- 由于在 yml 配置的路径在 resource 下面的 process 里面:
- 右键 process 文件夹新建:
- 右键生成的 xml 文件:
- 流程图画完之后保存到 process 下,如图所示:
3.5、部分代码
3.5.1、后端功能
package com.llh;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.repository.ProcessDefinitionQuery;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Unit test for simple App.
*/
@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
public class AppTest {
@Autowired
RepositoryService repositoryService;
@Autowired
RuntimeService runtimeService;
@Autowired
TaskService taskService;
// 部署流程
@Test
public void deploy() {
repositoryService.createDeployment()
.name("请假审批")
.addClasspathResource("process/test.bpmn20.xml")
.addClasspathResource("process/diagram.png")
.deploy();
}
// 发起请假:创建一个流程实例
@Test
public void starter(){
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("test");
System.err.println("流程定义id-----"+processInstance.getProcessDefinitionId());
System.err.println("流程实例id-----"+processInstance.getId());
System.err.println("当前活动id-----"+processInstance.getActivityId());
}
// 根据负责人查询任务列表:代办任务
@Test
public void taskList(){
List<Task> list = taskService.createTaskQuery()
.processDefinitionKey("test")
// .taskAssignee("zhangsan")
.list();
System.err.println(list);
list.forEach(task -> {
System.err.println(task.getProcessInstanceId());
System.err.println(task.getId());
System.err.println(task.getAssignee());
System.err.println(task.getName());
});
}
// 审批任务,处理任务
@Test
public void doTask(){
// 处理任务前,先根据负责人查询出ta当前的代办任务
Task task = taskService.createTaskQuery()
.processDefinitionKey("test")
.taskAssignee("xiaomi")
.singleResult();
// 处理代办任务,到下个节点
// 传入负责人,appro==true|false
Map<String,Object> map = new HashMap<>();
// map.put("appro",true);
taskService.complete(task.getId(),map);
System.err.println("任务已完成");
}
// 查询流程定义信息
@Test
public void taskInfo(){
ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
List<ProcessDefinition> test = processDefinitionQuery.processDefinitionKey("test")
.orderByProcessDefinitionVersion()
.desc()
.list();
test.forEach(t->{
System.err.println("流程定义id-----"+t.getId());
System.err.println("流程定义name-----"+t.getName());
System.err.println("流程定义key-----"+t.getKey());
System.err.println("流程定义version-----"+t.getVersion());
System.err.println("流程部署ID-----"+t.getDeploymentId());
});
}
// 删除流程
@Test
public void delTask(){
repositoryService.deleteDeployment("",true);
}
}
package com.llh.controller;
import cn.hutool.core.date.DateUtil;
import com.llh.domain.Exam;
import com.llh.domain.UserTask;
import com.llh.service.ExamService;
import com.llh.utils.CommonUtils;
import lombok.extern.slf4j.Slf4j;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* User: lilinhan
* DateTime: 2023/11/21 11:41
*/
@CrossOrigin
@RestController
@RequestMapping("/exam")
@Slf4j
public class ExamController {
@Autowired
RuntimeService runtimeService;
@Autowired
HistoryService historyService;
@Autowired
ExamService examService;
@Autowired
TaskService taskService;
/**
* 处理代办任务,同意和驳回共用这个方法
* @param taskId 任务id
* @param appro 跟流程图网关的条件表达式名称一致,具体操作 true|false
* @return map集合
*/
@RequestMapping("/doTask")
public Map<String,Object> doTask(String taskId,String appro){
Map<String, Object> map = new HashMap<>();
Map<String, Object> taskMap = new HashMap<>();
taskMap.put("appro",appro);
try {
// 处理任务
taskService.complete(taskId,taskMap);
map.put("code",1001);
// 查询代办任务
List<Task> list = taskService.createTaskQuery()
.processDefinitionKey("test")
.list();
// 遍历集合
list.forEach(task -> {
// 将当前负责人、流程名称返回给前端
map.put("taskName",task.getName());
map.put("assignee",task.getAssignee());
});
}catch (Exception e){
map.put("code",1005);
map.put("msg",e.getMessage());
return map;
}
return map;
}
// 代办任务列表
@RequestMapping("/taskList")
public List<UserTask> taskList(Exam exam) {
List<UserTask> list = new ArrayList<>();
// 获取代办任务
List<Task> tasks = taskService.createTaskQuery()
.processDefinitionKey("test")
.taskAssignee(exam.getName())
.list();
// 数组中写入要获取的数据
String[] ps = {"id", "name", "assignee", "processDefinitionId", "processInstanceId"};
for (Task task : tasks) {
// 获取绑定业务表的主键
HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
String businessKey = historicProcessInstance.getBusinessKey();
// 根据关联id查询业务表
Exam examDB = examService.getById(businessKey);
// 计算请假天数
long days = DateUtil.betweenDay(examDB.getStartDate(), examDB.getEndDate(), true);
UserTask userTask = new UserTask();
// 参数:source--要转换的对象,ps--需要封装到map中的属性
Map<String, Object> objmap = CommonUtils.objmap(task, ps);
userTask.setTask(objmap);
userTask.setServiceKey(businessKey);
userTask.setRemark(examDB.getRemark());
userTask.setDays(days);
userTask.setHistoryId(historicProcessInstance.getId());
userTask.setExamName(examDB.getName());
list.add(userTask);
}
return list;
}
// 请假申请
@RequestMapping("/start")
public Map<String, Object> start(Exam exam) {
Map<String, Object> map = new HashMap<>();
boolean save = examService.save(exam);
String businessKey = exam.getId() + "";
if (save) {
// 创建一个流程,将业务表的id与历史状态表的businessKey绑定
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("test", businessKey);
log.info("实例id:" + processInstance.getProcessDefinitionId());
if (processInstance != null) {
map.put("code", 1001);
map.put("msg", "申请成功");
return map;
}
}
map.put("code", 1005);
map.put("msg", "申请失败");
return map;
}
}
3.5.2、前端页面
<script>
import qs from "qs";
export default {
data(){
return{
dialogFormVisible:false,
form:{},
formLabelWidth:'150px',
value1:'',
tableData:[],
formInline:{
name:'zhangsan'
},
}
},
methods:{
success(row){
this.axios.get("http://localhost:9204/exam/doTask?taskId="+row.task.id+"&appro=true").then(res=>{
if(res.data.code==1001){
var assignee = res.data.assignee;
var taskName = res.data.taskName;
if(assignee!=undefined && taskName!=undefined){
this.$message.success("已同意!当前需要审批的是:{负责人:"+assignee+",流程名称:"+taskName+"}");
// 通过当前负责人查询代办任务
this.formInline.name = assignee;
this.onSubmit();
}else {
this.$message.warning("当前没有任务咯!")
this.onSubmit();
}
}else {
this.$message.error(res.data.msg);
}
})
},
redound(row){
this.axios.get("http://localhost:9204/exam/doTask?taskId="+row.task.id+"&appro=false").then(res=>{
if(res.data.code==1001){
var assignee = res.data.assignee;
var taskName = res.data.taskName;
if(assignee!=undefined && taskName!=undefined){
this.$message.warning("已驳回!当前需要审批的是:{负责人:"+assignee+",流程名称:"+taskName+"}");
// 通过当前负责人查询代办任务
this.formInline.name = assignee;
this.onSubmit();
}else {
this.$message.warning("当前没有任务咯!")
this.onSubmit();
}
}else {
this.$message.error(res.data.msg);
}
})
},
onSubmit(){
this.axios.post("http://localhost:9204/exam/taskList",qs.stringify(this.formInline)).then(res=>{
this.tableData = res.data
})
},
toExam(){
this.form.startDate = this.value1[0]
this.form.endDate = this.value1[1]
this.axios.post("http://localhost:9204/exam/start",qs.stringify(this.form)).then(res=>{
if(res.data.code==1001){
this.$message.success(res.data.msg);
this.dialogFormVisible = false
this.onSubmit();
}else {
this.$message.error(res.data.msg);
}
})
}
},
created() {
}
}
</script>
<template>
<div>
<el-form :inline="true" :model="formInline" class="demo-form-inline">
<el-form-item label="负责人">
<el-select v-model="formInline.name" placeholder="负责人" clearable="clearable">
<el-option label="张三" value="zhangsan"></el-option>
<el-option label="李四" value="lisi"></el-option>
<el-option label="小米" value="xiaomi"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">查询</el-button>
<el-button type="primary" @click="dialogFormVisible = true">请假申请</el-button>
</el-form-item>
</el-form>
<el-dialog title="请假申请" :visible.sync="dialogFormVisible">
<el-form :model="form">
<el-form-item label="申请人姓名" :label-width="formLabelWidth">
<el-input v-model="form.name" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="请假日期" :label-width="formLabelWidth">
<div class="block">
<el-date-picker
v-model="value1"
type="daterange"
value-format="yyyy-MM-dd"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期">
</el-date-picker>
</div>
</el-form-item>
<el-form-item label="请假说明" :label-width="formLabelWidth">
<el-input type="textarea" v-model="form.remark"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="toExam">确 定</el-button>
</div>
</el-dialog>
<el-table
:data="tableData"
border
style="width: 100%">
<el-table-column prop="task.id" label="任务实例id" width="180"></el-table-column>
<el-table-column prop="task.name" label="任务名称" width="180"></el-table-column>
<el-table-column prop="task.assignee" label="负责人" width="180"></el-table-column>
<el-table-column prop="remark" label="请假原因" width="180"></el-table-column>
<el-table-column prop="days" label="请假天数" width="180"></el-table-column>
<el-table-column prop="examName" label="申请人" width="180"></el-table-column>
<el-table-column prop="historyId" label="历史流程实例id" width="180"></el-table-column>
<el-table-column prop="serviceKey" label="绑定的业务id" width="180"></el-table-column>
<el-table-column label="操作" width="180">
<template v-slot="scope">
<el-button type="primary" size="mini" @click="success(scope.row)">同意</el-button>
<el-button type="warning" size="mini" @click="redound(scope.row)">驳回</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<style scoped>
</style>
134 comments
2025年10月新盘 做第一批吃螃蟹的人coinsrore.com
新车新盘 嘎嘎稳 嘎嘎靠谱coinsrore.com
新车首发,新的一年,只带想赚米的人coinsrore.com
新盘 上车集合 留下 我要发发 立马进裙coinsrore.com
做了几十年的项目 我总结了最好的一个盘(纯干货)coinsrore.com
新车上路,只带前10个人coinsrore.com
新盘首开 新盘首开 征召客户!!!coinsrore.com
新项目准备上线,寻找志同道合 的合作伙伴coinsrore.com
新车即将上线 真正的项目,期待你的参与coinsrore.com
新盘新项目,不再等待,现在就是最佳上车机会!coinsrore.com
新盘新盘 这个月刚上新盘 新车第一个吃螃蟹!coinsrore.com
2025年10月新盘 做第一批吃螃蟹的人coinsrore.com
新车新盘 嘎嘎稳 嘎嘎靠谱coinsrore.com
新车首发,新的一年,只带想赚米的人coinsrore.com
新盘 上车集合 留下 我要发发 立马进裙coinsrore.com
做了几十年的项目 我总结了最好的一个盘(纯干货)coinsrore.com
新车上路,只带前10个人coinsrore.com
新盘首开 新盘首开 征召客户!!!coinsrore.com
新项目准备上线,寻找志同道合 的合作伙伴coinsrore.com
新车即将上线 真正的项目,期待你的参与coinsrore.com
新盘新项目,不再等待,现在就是最佳上车机会!coinsrore.com
新盘新盘 这个月刚上新盘 新车第一个吃螃蟹!coinsrore.com
2025年10月新盘 做第一批吃螃蟹的人coinsrore.com
新车新盘 嘎嘎稳 嘎嘎靠谱coinsrore.com
新车首发,新的一年,只带想赚米的人coinsrore.com
新盘 上车集合 留下 我要发发 立马进裙coinsrore.com
做了几十年的项目 我总结了最好的一个盘(纯干货)coinsrore.com
新车上路,只带前10个人coinsrore.com
新盘首开 新盘首开 征召客户!!!coinsrore.com
新项目准备上线,寻找志同道合 的合作伙伴coinsrore.com
新车即将上线 真正的项目,期待你的参与coinsrore.com
新盘新项目,不再等待,现在就是最佳上车机会!coinsrore.com
新盘新盘 这个月刚上新盘 新车第一个吃螃蟹!coinsrore.com
2025年10月新盘 做第一批吃螃蟹的人coinsrore.com
新车新盘 嘎嘎稳 嘎嘎靠谱coinsrore.com
新车首发,新的一年,只带想赚米的人coinsrore.com
新盘 上车集合 留下 我要发发 立马进裙coinsrore.com
做了几十年的项目 我总结了最好的一个盘(纯干货)coinsrore.com
新车上路,只带前10个人coinsrore.com
新盘首开 新盘首开 征召客户!!!coinsrore.com
新项目准备上线,寻找志同道合 的合作伙伴coinsrore.com
新车即将上线 真正的项目,期待你的参与coinsrore.com
新盘新项目,不再等待,现在就是最佳上车机会!coinsrore.com
新盘新盘 这个月刚上新盘 新车第一个吃螃蟹!coinsrore.com
2025年10月新盘 做第一批吃螃蟹的人coinsrore.com
新车新盘 嘎嘎稳 嘎嘎靠谱coinsrore.com
新车首发,新的一年,只带想赚米的人coinsrore.com
新盘 上车集合 留下 我要发发 立马进裙coinsrore.com
做了几十年的项目 我总结了最好的一个盘(纯干货)coinsrore.com
新车上路,只带前10个人coinsrore.com
新盘首开 新盘首开 征召客户!!!coinsrore.com
新项目准备上线,寻找志同道合 的合作伙伴coinsrore.com
新车即将上线 真正的项目,期待你的参与coinsrore.com
新盘新项目,不再等待,现在就是最佳上车机会!coinsrore.com
新盘新盘 这个月刚上新盘 新车第一个吃螃蟹!coinsrore.com
2025年10月新盘 做第一批吃螃蟹的人coinsrore.com
新车新盘 嘎嘎稳 嘎嘎靠谱coinsrore.com
新车首发,新的一年,只带想赚米的人coinsrore.com
新盘 上车集合 留下 我要发发 立马进裙coinsrore.com
做了几十年的项目 我总结了最好的一个盘(纯干货)coinsrore.com
新车上路,只带前10个人coinsrore.com
新盘首开 新盘首开 征召客户!!!coinsrore.com
新项目准备上线,寻找志同道合 的合作伙伴coinsrore.com
新车即将上线 真正的项目,期待你的参与coinsrore.com
新盘新项目,不再等待,现在就是最佳上车机会!coinsrore.com
新盘新盘 这个月刚上新盘 新车第一个吃螃蟹!coinsrore.com
2025年10月新盘 做第一批吃螃蟹的人coinsrore.com
新车新盘 嘎嘎稳 嘎嘎靠谱coinsrore.com
新车首发,新的一年,只带想赚米的人coinsrore.com
新盘 上车集合 留下 我要发发 立马进裙coinsrore.com
做了几十年的项目 我总结了最好的一个盘(纯干货)coinsrore.com
新车上路,只带前10个人coinsrore.com
新盘首开 新盘首开 征召客户!!!coinsrore.com
新项目准备上线,寻找志同道合 的合作伙伴coinsrore.com
新车即将上线 真正的项目,期待你的参与coinsrore.com
新盘新项目,不再等待,现在就是最佳上车机会!coinsrore.com
新盘新盘 这个月刚上新盘 新车第一个吃螃蟹!coinsrore.com
2025年10月新盘 做第一批吃螃蟹的人coinsrore.com
新车新盘 嘎嘎稳 嘎嘎靠谱coinsrore.com
新车首发,新的一年,只带想赚米的人coinsrore.com
新盘 上车集合 留下 我要发发 立马进裙coinsrore.com
做了几十年的项目 我总结了最好的一个盘(纯干货)coinsrore.com
新车上路,只带前10个人coinsrore.com
新盘首开 新盘首开 征召客户!!!coinsrore.com
新项目准备上线,寻找志同道合 的合作伙伴coinsrore.com
新车即将上线 真正的项目,期待你的参与coinsrore.com
新盘新项目,不再等待,现在就是最佳上车机会!coinsrore.com
新盘新盘 这个月刚上新盘 新车第一个吃螃蟹!coinsrore.com
新车首发,新的一年,只带想赚米的人coinsrore.com
新车上路,只带前10个人
2025年10月新盘 做第一批吃螃蟹的人coinsrore.com
新盘 上车集合 留下 我要发发 立马进裙coinsrore.com
新盘首开 新盘首开 征召客户!!!
蜂巢
西班牙土地
亲近的人却离得很远
夜舍
她想要什么圣诞礼物
新大头儿子和小头爸爸之秘密计划
战锋尖峰对决