Browse Source

fate: 升级流程设计器,表单设计器

master
zhuyong 1 year ago
parent
commit
73acc6453c
  1. 1
      pom.xml
  2. 12
      ruoyi-admin/src/main/resources/application-druid.yml
  3. 4
      ruoyi-admin/src/main/resources/application.yml
  4. 2
      ruoyi-flowable/pom.xml
  5. 12
      ruoyi-flowable/src/main/java/com/ruoyi/flowable/common/expand/el/BaseEl.java
  6. 32
      ruoyi-flowable/src/main/java/com/ruoyi/flowable/common/expand/el/FlowEl.java
  7. 11
      ruoyi-flowable/src/main/java/com/ruoyi/flowable/config/FlowableConfig.java
  8. 12
      ruoyi-flowable/src/main/java/com/ruoyi/flowable/controller/FlowDefinitionController.java
  9. 9
      ruoyi-flowable/src/main/java/com/ruoyi/flowable/controller/FlowInstanceController.java
  10. 31
      ruoyi-flowable/src/main/java/com/ruoyi/flowable/controller/FlowTaskController.java
  11. 4
      ruoyi-flowable/src/main/java/com/ruoyi/flowable/controller/SysExpressionController.java
  12. 4
      ruoyi-flowable/src/main/java/com/ruoyi/flowable/controller/SysListenerController.java
  13. 11
      ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/dto/FlowNextDto.java
  14. 3
      ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/dto/FlowTaskDto.java
  15. 26
      ruoyi-flowable/src/main/java/com/ruoyi/flowable/flow/FindNextNodeUtil.java
  16. 180
      ruoyi-flowable/src/main/java/com/ruoyi/flowable/flow/FlowableUtils.java
  17. 3
      ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/IFlowInstanceService.java
  18. 11
      ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/IFlowTaskService.java
  19. 62
      ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/ISysTaskFormService.java
  20. 25
      ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/FlowDefinitionServiceImpl.java
  21. 350
      ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/FlowTaskServiceImpl.java
  22. 93
      ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/SysTaskFormServiceImpl.java
  23. 30
      ruoyi-system/src/main/java/com/ruoyi/system/domain/SysExpression.java
  24. 18
      ruoyi-system/src/main/resources/mapper/system/SysExpressionMapper.xml
  25. 44
      ruoyi-ui/src/api/flowable/expression.js
  26. 9
      ruoyi-ui/src/api/flowable/finished.js
  27. 44
      ruoyi-ui/src/api/flowable/listener.js
  28. 8
      ruoyi-ui/src/api/flowable/process.js
  29. 9
      ruoyi-ui/src/components/Process/.bpmnlintrc
  30. 68
      ruoyi-ui/src/components/Process/BpmData.js
  31. 168
      ruoyi-ui/src/components/Process/PropertyPanel.vue
  32. 181
      ruoyi-ui/src/components/Process/README.md
  33. 120
      ruoyi-ui/src/components/Process/common/bpmnUtils.js
  34. 18
      ruoyi-ui/src/components/Process/common/global.js
  35. 12
      ruoyi-ui/src/components/Process/common/index.js
  36. 24
      ruoyi-ui/src/components/Process/common/mixinExecutionListener.js
  37. 70
      ruoyi-ui/src/components/Process/common/mixinPanel.js
  38. 22
      ruoyi-ui/src/components/Process/common/mixinXcrud.js
  39. 1769
      ruoyi-ui/src/components/Process/common/packed-config.js
  40. 55
      ruoyi-ui/src/components/Process/common/parseElement.js
  41. 24
      ruoyi-ui/src/components/Process/components/custom/customContextPad.vue
  42. 81
      ruoyi-ui/src/components/Process/components/nodePanel/gateway.vue
  43. 113
      ruoyi-ui/src/components/Process/components/nodePanel/process.vue
  44. 217
      ruoyi-ui/src/components/Process/components/nodePanel/property/executionListener.vue
  45. 72
      ruoyi-ui/src/components/Process/components/nodePanel/property/listenerList.vue
  46. 96
      ruoyi-ui/src/components/Process/components/nodePanel/property/listenerParam.vue
  47. 133
      ruoyi-ui/src/components/Process/components/nodePanel/property/multiInstance.vue
  48. 147
      ruoyi-ui/src/components/Process/components/nodePanel/property/signal.vue
  49. 221
      ruoyi-ui/src/components/Process/components/nodePanel/property/taskListener.vue
  50. 105
      ruoyi-ui/src/components/Process/components/nodePanel/sequenceFlow.vue
  51. 102
      ruoyi-ui/src/components/Process/components/nodePanel/startEnd.vue
  52. 631
      ruoyi-ui/src/components/Process/components/nodePanel/task.vue
  53. 3
      ruoyi-ui/src/components/Process/customPanel/CustomContextPad.js
  54. 3
      ruoyi-ui/src/components/Process/customPanel/CustomPalette.js
  55. 0
      ruoyi-ui/src/components/Process/customPanel/customTranslate.js
  56. 0
      ruoyi-ui/src/components/Process/customPanel/index.js
  57. 187
      ruoyi-ui/src/components/Process/designer.vue
  58. 499
      ruoyi-ui/src/components/Process/flowable/flowable.json
  59. 0
      ruoyi-ui/src/components/Process/flowable/init.js
  60. 51
      ruoyi-ui/src/components/Process/flowable/showConfig.js
  61. 5
      ruoyi-ui/src/components/Process/index.js
  62. 309
      ruoyi-ui/src/components/Process/index.vue
  63. 85
      ruoyi-ui/src/components/Process/lang/zh.js
  64. 140
      ruoyi-ui/src/components/Process/panel/ButtonsPanel.vue
  65. 139
      ruoyi-ui/src/components/Process/panel/PropertiesPanel.vue
  66. 87
      ruoyi-ui/src/components/Process/panel/commonPanel.vue
  67. 175
      ruoyi-ui/src/components/Process/panel/conditionPanel.vue
  68. 472
      ruoyi-ui/src/components/Process/panel/executionListener.vue
  69. 82
      ruoyi-ui/src/components/Process/panel/formPanel.vue
  70. 236
      ruoyi-ui/src/components/Process/panel/multiInstance.vue
  71. 65
      ruoyi-ui/src/components/Process/panel/otherPanel.vue
  72. 529
      ruoyi-ui/src/components/Process/panel/taskListener.vue
  73. 424
      ruoyi-ui/src/components/Process/panel/taskPanel.vue
  74. 183
      ruoyi-ui/src/components/Process/style/flow-viewer.scss
  75. 123
      ruoyi-ui/src/components/Process/style/process-panel.scss
  76. 202
      ruoyi-ui/src/components/Process/viewer/index.vue
  77. 191
      ruoyi-ui/src/components/flow/ElInputTag/index.vue
  78. 24
      ruoyi-ui/src/components/flow/Expression/index.vue
  79. 125
      ruoyi-ui/src/components/flow/Listener/index.vue
  80. 10
      ruoyi-ui/src/components/flow/Role/index.vue
  81. 35
      ruoyi-ui/src/components/flow/User/index.vue
  82. 1
      ruoyi-ui/src/components/vform/VFormDesigner.css
  83. 74
      ruoyi-ui/src/components/vform/VFormDesigner.umd.min.js
  84. 11
      ruoyi-ui/src/main.js
  85. 13
      ruoyi-ui/src/router/index.js
  86. 34
      ruoyi-ui/src/views/flowable/definition/index.vue
  87. 47
      ruoyi-ui/src/views/flowable/definition/model.vue
  88. 297
      ruoyi-ui/src/views/flowable/expression/index.vue
  89. 335
      ruoyi-ui/src/views/flowable/listener/index.vue
  90. 24
      ruoyi-ui/src/views/flowable/task/finished/detail/flow.vue
  91. 41
      ruoyi-ui/src/views/flowable/task/finished/detail/index.vue
  92. 0
      ruoyi-ui/src/views/flowable/task/finished/index.vue
  93. 123
      ruoyi-ui/src/views/flowable/task/flowForm/index.vue
  94. 193
      ruoyi-ui/src/views/flowable/task/form/index.vue
  95. 24
      ruoyi-ui/src/views/flowable/task/myProcess/detail/flow.vue
  96. 238
      ruoyi-ui/src/views/flowable/task/myProcess/detail/flowview.vue
  97. 57
      ruoyi-ui/src/views/flowable/task/myProcess/detail/index.vue
  98. 0
      ruoyi-ui/src/views/flowable/task/myProcess/index.vue
  99. 24
      ruoyi-ui/src/views/flowable/task/myProcess/send/flow.vue
  100. 129
      ruoyi-ui/src/views/flowable/task/myProcess/send/flowview.vue

1
pom.xml

@ -188,6 +188,7 @@
<artifactId>ruoyi-flowable</artifactId>
<version>${ruoyi.version}</version>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>

12
ruoyi-admin/src/main/resources/application-druid.yml

@ -6,16 +6,16 @@ spring:
druid:
# 主库数据源
master:
url: jdbc:mysql://localhost:3306/tony-flowable?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true
url: jdbc:mysql://localhost:3306/tony-flowable-24?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true
username: root
password: 123456
# 从库数据源
slave:
# 从数据源开关/默认关闭
enabled: false
url:
username:
password:
url:
username:
password:
# 初始连接数
initialSize: 5
# 最小连接池数量
@ -39,7 +39,7 @@ spring:
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
webStatFilter:
enabled: true
statViewServlet:
enabled: true
@ -58,4 +58,4 @@ spring:
merge-sql: true
wall:
config:
multi-statement-allow: true
multi-statement-allow: true

4
ruoyi-admin/src/main/resources/application.yml

@ -131,6 +131,6 @@ xss:
# flowable相关表
flowable:
# true 会对数据库中所有表进行更新操作。如果表不存在,则自动创建(建议开发时使用)
database-schema-update: false
database-schema-update: true
# 关闭定时任务JOB
async-executor-activate: false
async-executor-activate: false

2
ruoyi-flowable/pom.xml

@ -4,7 +4,7 @@
<parent>
<artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.8.7</version>
<version>3.8.8</version>
</parent>
<modelVersion>4.0.0</modelVersion>

12
ruoyi-flowable/src/main/java/com/ruoyi/flowable/common/expand/el/BaseEl.java

@ -0,0 +1,12 @@
package com.ruoyi.flowable.common.expand.el;
/**
* 扩展表达式
*
* @author Tony
* @date 2023-03-04 09:10
*/
public interface BaseEl {
}

32
ruoyi-flowable/src/main/java/com/ruoyi/flowable/common/expand/el/FlowEl.java

@ -0,0 +1,32 @@
package com.ruoyi.flowable.common.expand.el;
import com.ruoyi.system.service.ISysDeptService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 扩展表达式
*
* @author Tony
* @date 2023-03-04 12:10
*/
@Component
@Slf4j
public class FlowEl implements BaseEl {
@Resource
private ISysDeptService sysDeptService;
public String findDeptLeader(String name){
log.info("开始查询表达式变量值,getName");
return name;
}
public String getName(String name){
log.info("开始查询表达式变量值,getName");
return name;
}
}

11
ruoyi-flowable/src/main/java/com/ruoyi/flowable/config/FlowableConfig.java

@ -3,24 +3,15 @@ package com.ruoyi.flowable.config;
import org.flowable.engine.impl.db.DbIdGenerator;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.AsyncListenableTaskExecutor;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
/**
* 流程id生成处理
* 扩展流程配置
* @author Tony
* @date 2022-12-26 10:24
*/
@Configuration
public class FlowableConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {
@Bean
public AsyncListenableTaskExecutor applicationTaskExecutor() {
return new SimpleAsyncTaskExecutor();
}
@Override
public void configure(SpringProcessEngineConfiguration engineConfiguration) {
engineConfiguration.setActivityFontName("宋体");

12
ruoyi-flowable/src/main/java/com/ruoyi/flowable/controller/FlowDefinitionController.java

@ -1,12 +1,15 @@
package com.ruoyi.flowable.controller;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.domain.FlowProcDefDto;
import com.ruoyi.flowable.domain.dto.FlowSaveXmlVo;
import com.ruoyi.flowable.service.IFlowDefinitionService;
import com.ruoyi.system.domain.FlowProcDefDto;
import com.ruoyi.system.domain.SysExpression;
import com.ruoyi.system.service.ISysExpressionService;
import com.ruoyi.system.service.ISysRoleService;
@ -16,6 +19,7 @@ import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@ -131,6 +135,7 @@ public class FlowDefinitionController extends BaseController {
@ApiOperation(value = "保存流程设计器内的xml文件")
@Log(title = "流程定义", businessType = BusinessType.INSERT)
@PostMapping("/save")
public AjaxResult save(@RequestBody FlowSaveXmlVo vo) {
InputStream in = null;
@ -153,16 +158,16 @@ public class FlowDefinitionController extends BaseController {
return AjaxResult.success("导入成功");
}
@ApiOperation(value = "发起流程")
@Log(title = "发起流程", businessType = BusinessType.INSERT)
@PostMapping("/start/{procDefId}")
public AjaxResult start(@ApiParam(value = "流程定义id") @PathVariable(value = "procDefId") String procDefId,
@ApiParam(value = "变量集合,json对象") @RequestBody Map<String, Object> variables) {
return flowDefinitionService.startProcessInstanceById(procDefId, variables);
}
@ApiOperation(value = "激活或挂起流程定义")
@Log(title = "激活/挂起流程", businessType = BusinessType.UPDATE)
@PutMapping(value = "/updateState")
public AjaxResult updateState(@ApiParam(value = "1:激活,2:挂起", required = true) @RequestParam Integer state,
@ApiParam(value = "流程部署ID", required = true) @RequestParam String deployId) {
@ -171,6 +176,7 @@ public class FlowDefinitionController extends BaseController {
}
@ApiOperation(value = "删除流程")
@Log(title = "删除流程", businessType = BusinessType.DELETE)
@DeleteMapping(value = "/{deployIds}")
public AjaxResult delete(@PathVariable String[] deployIds) {
for (String deployId : deployIds) {

9
ruoyi-flowable/src/main/java/com/ruoyi/flowable/controller/FlowInstanceController.java

@ -1,7 +1,10 @@
package com.ruoyi.flowable.controller;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.flowable.domain.vo.FlowTaskVo;
import com.ruoyi.flowable.service.IFlowInstanceService;
import io.swagger.annotations.Api;
@ -23,7 +26,7 @@ import java.util.Map;
@Api(tags = "工作流流程实例管理")
@RestController
@RequestMapping("/flowable/instance")
public class FlowInstanceController {
public class FlowInstanceController extends BaseController {
@Autowired
private IFlowInstanceService flowInstanceService;
@ -36,7 +39,6 @@ public class FlowInstanceController {
}
@ApiOperation(value = "激活或挂起流程实例")
@PostMapping(value = "/updateState")
public AjaxResult updateState(@ApiParam(value = "1:激活,2:挂起", required = true) @RequestParam Integer state,
@ -53,6 +55,7 @@ public class FlowInstanceController {
}
@ApiOperation(value = "删除流程实例")
@Log(title = "删除任务", businessType = BusinessType.DELETE)
@DeleteMapping(value = "/delete/{instanceIds}")
public AjaxResult delete(@ApiParam(value = "流程实例ID", required = true) @PathVariable String[] instanceIds,
@ApiParam(value = "删除原因") @RequestParam(required = false) String deleteReason) {
@ -61,4 +64,4 @@ public class FlowInstanceController {
}
return AjaxResult.success();
}
}
}

31
ruoyi-flowable/src/main/java/com/ruoyi/flowable/controller/FlowTaskController.java

@ -1,6 +1,9 @@
package com.ruoyi.flowable.controller;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.flowable.domain.dto.FlowTaskDto;
import com.ruoyi.flowable.domain.vo.FlowQueryVo;
import com.ruoyi.flowable.domain.vo.FlowTaskVo;
@ -29,7 +32,7 @@ import java.io.OutputStream;
@Api(tags = "工作流流程任务管理")
@RestController
@RequestMapping("/flowable/task")
public class FlowTaskController {
public class FlowTaskController extends BaseController {
@Autowired
private IFlowTaskService flowTaskService;
@ -41,12 +44,14 @@ public class FlowTaskController {
}
@ApiOperation(value = "取消申请", response = FlowTaskDto.class)
@Log(title = "取消申请", businessType = BusinessType.UPDATE)
@PostMapping(value = "/stopProcess")
public AjaxResult stopProcess(@RequestBody FlowTaskVo flowTaskVo) {
return flowTaskService.stopProcess(flowTaskVo);
}
@ApiOperation(value = "撤回流程", response = FlowTaskDto.class)
@Log(title = "撤回流程", businessType = BusinessType.UPDATE)
@PostMapping(value = "/revokeProcess")
public AjaxResult revokeProcess(@RequestBody FlowTaskVo flowTaskVo) {
return flowTaskService.revokeProcess(flowTaskVo);
@ -71,6 +76,12 @@ public class FlowTaskController {
return flowTaskService.flowRecord(procInsId, deployId);
}
@ApiOperation(value = "根据任务ID查询挂载的表单信息")
@GetMapping(value = "/getTaskForm")
public AjaxResult getTaskForm(String taskId) {
return flowTaskService.getTaskForm(taskId);
}
@ApiOperation(value = "流程初始化表单", response = FlowTaskDto.class)
@GetMapping(value = "/flowFormData")
@ -85,12 +96,14 @@ public class FlowTaskController {
}
@ApiOperation(value = "审批任务")
@Log(title = "审批任务", businessType = BusinessType.UPDATE)
@PostMapping(value = "/complete")
public AjaxResult complete(@RequestBody FlowTaskVo flowTaskVo) {
return flowTaskService.complete(flowTaskVo);
}
@ApiOperation(value = "驳回任务")
@Log(title = "驳回任务", businessType = BusinessType.UPDATE)
@PostMapping(value = "/reject")
public AjaxResult taskReject(@RequestBody FlowTaskVo flowTaskVo) {
flowTaskService.taskReject(flowTaskVo);
@ -98,6 +111,7 @@ public class FlowTaskController {
}
@ApiOperation(value = "退回任务")
@Log(title = "退回任务", businessType = BusinessType.UPDATE)
@PostMapping(value = "/return")
public AjaxResult taskReturn(@RequestBody FlowTaskVo flowTaskVo) {
flowTaskService.taskReturn(flowTaskVo);
@ -111,6 +125,7 @@ public class FlowTaskController {
}
@ApiOperation(value = "删除任务")
@Log(title = "删除任务", businessType = BusinessType.DELETE)
@DeleteMapping(value = "/delete")
public AjaxResult delete(@RequestBody FlowTaskVo flowTaskVo) {
flowTaskService.deleteTask(flowTaskVo);
@ -245,4 +260,18 @@ public class FlowTaskController {
return flowTaskService.flowTaskForm(taskId);
}
/**
* 流程节点信息
*
* @param procInsId 流程实例编号
* @param elementId 流程节点编号
* @return
*/
@GetMapping("/flowTaskInfo")
public AjaxResult flowTaskInfo(@RequestParam(value = "procInsId") String procInsId,
@RequestParam(value = "elementId") String elementId){
return flowTaskService.flowTaskInfo(procInsId,elementId);
}
}

4
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysExpressionController.java → ruoyi-flowable/src/main/java/com/ruoyi/flowable/controller/SysExpressionController.java

@ -1,4 +1,4 @@
package com.ruoyi.web.controller.system;
package com.ruoyi.flowable.controller;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
@ -23,7 +23,7 @@ import com.ruoyi.common.core.page.TableDataInfo;
/**
* 流程达式Controller
*
*
* @author ruoyi
* @date 2022-12-12
*/

4
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysListenerController.java → ruoyi-flowable/src/main/java/com/ruoyi/flowable/controller/SysListenerController.java

@ -1,4 +1,4 @@
package com.ruoyi.web.controller.system;
package com.ruoyi.flowable.controller;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
@ -23,7 +23,7 @@ import com.ruoyi.common.core.page.TableDataInfo;
/**
* 流程监听Controller
*
*
* @author Tony
* @date 2022-12-25
*/

11
ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/dto/FlowNextDto.java

@ -15,8 +15,19 @@ import java.util.List;
@Data
public class FlowNextDto implements Serializable {
/**
* 审批人类型
*/
private String type;
/**
* 是否需要动态指定任务审批人
*/
private String dataType;
/**
* 流程变量
*/
private String vars;
}

3
ruoyi-flowable/src/main/java/com/ruoyi/flowable/domain/dto/FlowTaskDto.java

@ -46,7 +46,6 @@ public class FlowTaskDto implements Serializable {
@ApiModelProperty("任务执行人名称")
private String assigneeName;
@ApiModelProperty("任务执行人部门")
private String assigneeDeptName;;
@ -60,7 +59,7 @@ public class FlowTaskDto implements Serializable {
private String category;
@ApiModelProperty("流程变量信息")
private Object procVars;
private Object variables;
@ApiModelProperty("局部变量信息")
private Object taskLocalVars;

26
ruoyi-flowable/src/main/java/com/ruoyi/flowable/flow/FindNextNodeUtil.java

@ -5,12 +5,13 @@ import com.googlecode.aviator.Expression;
//import com.greenpineyu.fel.FelEngine;
//import com.greenpineyu.fel.FelEngineImpl;
//import com.greenpineyu.fel.context.FelContext;
import org.apache.commons.lang3.StringUtils;
//import org.apache.commons.jexl2.JexlContext;
//import org.apache.commons.jexl2.JexlEngine;
//import org.apache.commons.jexl2.MapContext;
//import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.TaskService;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ProcessDefinition;
import java.util.*;
@ -61,12 +62,17 @@ public class FindNextNodeUtil {
}
}
FlowElement flowElement = bpmnModel.getFlowElement(key);
next(flowElements, flowElement, map, data);
List<SequenceFlow> sequenceFlows = ((StartEvent)flowElement).getOutgoingFlows();
// 获取出口连线, 此时从开始节点往后,只能是一个出口
if (!sequenceFlows.isEmpty()) {
SequenceFlow sequenceFlow = sequenceFlows.get(0);
FlowElement targetFlowElement = sequenceFlow.getTargetFlowElement();
next(flowElements, targetFlowElement, map, data);
}
return data;
}
/**
* 查找下一节点
*
@ -162,13 +168,13 @@ public class FindNextNodeUtil {
/**
* 判断是否是多实例子流程并且需要设置集合类型变量
*/
public static boolean checkSubProcess(String Id, Collection<FlowElement> flowElements, List<UserTask> nextUser) {
public static boolean checkSubProcess(String id, Collection<FlowElement> flowElements, List<UserTask> nextUser) {
for (FlowElement flowElement1 : flowElements) {
if (flowElement1 instanceof SubProcess && flowElement1.getId().equals(Id)) {
if (flowElement1 instanceof SubProcess && flowElement1.getId().equals(id)) {
SubProcess sp = (SubProcess) flowElement1;
if (sp.getLoopCharacteristics() != null) {
String inputDataItem = sp.getLoopCharacteristics().getInputDataItem();
// String inputDataItem = sp.getLoopCharacteristics().getInputDataItem();
UserTask userTask = new UserTask();
userTask.setId(sp.getId());
userTask.setLoopCharacteristics(sp.getLoopCharacteristics());
@ -254,7 +260,7 @@ public class FindNextNodeUtil {
*/
public static boolean expressionResult(Map<String, Object> map, String expression) {
Expression exp = AviatorEvaluator.compile(expression);
final Object execute = exp.execute(map);
return Boolean.parseBoolean(String.valueOf(execute));
return (Boolean) exp.execute(map);
// return true;
}
}

180
ruoyi-flowable/src/main/java/com/ruoyi/flowable/flow/FlowableUtils.java

@ -2,12 +2,14 @@ package com.ruoyi.flowable.flow;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.*;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
import org.flowable.task.api.Task;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.task.api.history.HistoricTaskInstance;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author Tony
@ -18,6 +20,7 @@ public class FlowableUtils {
/**
* 根据节点获取入口连线
*
* @param source
* @return
*/
@ -39,6 +42,7 @@ public class FlowableUtils {
/**
* 根据节点获取出口连线
*
* @param source
* @return
*/
@ -60,6 +64,7 @@ public class FlowableUtils {
/**
* 获取全部节点列表包含子流程节点
*
* @param flowElements
* @param allElements
* @return
@ -79,9 +84,10 @@ public class FlowableUtils {
/**
* 迭代获取父级任务节点列表向前找
* @param source 起始节点
*
* @param source 起始节点
* @param hasSequenceFlow 已经经过的连线的 ID用于判断线路是否重复
* @param userTaskList 已找到的用户任务节点
* @param userTaskList 已找到的用户任务节点
* @return
*/
public static List<UserTask> iteratorFindParentUserTasks(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
@ -98,7 +104,7 @@ public class FlowableUtils {
if (sequenceFlows != null) {
// 循环找到目标元素
for (SequenceFlow sequenceFlow: sequenceFlows) {
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复,说明循环了,跳过这个循环
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
@ -129,10 +135,11 @@ public class FlowableUtils {
/**
* 根据正在运行的任务节点迭代获取子级任务节点列表向后找
* @param source 起始节点(退回节点)
* @param runTaskKeyList 正在运行的任务 Key用于校验任务节点是否是正在运行的节点
*
* @param source 起始节点(退回节点)
* @param runTaskKeyList 正在运行的任务 Key用于校验任务节点是否是正在运行的节点
* @param hasSequenceFlow 已经经过的连线的 ID用于判断线路是否重复
* @param userTaskList 需要撤回的用户任务列表
* @param userTaskList 需要撤回的用户任务列表
* @return
*/
public static List<UserTask> iteratorFindChildUserTasks(FlowElement source, List<String> runTaskKeyList, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
@ -149,7 +156,7 @@ public class FlowableUtils {
if (sequenceFlows != null) {
// 循环找到目标元素
for (SequenceFlow sequenceFlow: sequenceFlows) {
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复,说明循环了,跳过这个循环
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
@ -179,9 +186,10 @@ public class FlowableUtils {
/**
* 迭代获取子流程用户任务节点
* @param source 起始节点
*
* @param source 起始节点
* @param hasSequenceFlow 已经经过的连线的 ID用于判断线路是否重复
* @param userTaskList 需要撤回的用户任务列表
* @param userTaskList 需要撤回的用户任务列表
* @return
*/
public static List<UserTask> findChildProcessUserTasks(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
@ -193,7 +201,7 @@ public class FlowableUtils {
if (sequenceFlows != null) {
// 循环找到目标元素
for (SequenceFlow sequenceFlow: sequenceFlows) {
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复,说明循环了,跳过这个循环
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
@ -223,11 +231,12 @@ public class FlowableUtils {
/**
* 从后向前寻路获取所有脏线路上的点
* @param source 起始节点
* @param passRoads 已经经过的点集合
*
* @param source 起始节点
* @param passRoads 已经经过的点集合
* @param hasSequenceFlow 已经经过的连线的 ID用于判断线路是否重复
* @param targets 目标脏线路终点
* @param dirtyRoads 确定为脏数据的点因为不需要重复因此使用 set 存储
* @param targets 目标脏线路终点
* @param dirtyRoads 确定为脏数据的点因为不需要重复因此使用 set 存储
* @return
*/
public static Set<String> iteratorFindDirtyRoads(FlowElement source, List<String> passRoads, Set<String> hasSequenceFlow, List<String> targets, Set<String> dirtyRoads) {
@ -245,7 +254,7 @@ public class FlowableUtils {
if (sequenceFlows != null) {
// 循环找到目标元素
for (SequenceFlow sequenceFlow: sequenceFlows) {
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复,说明循环了,跳过这个循环
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
@ -279,9 +288,10 @@ public class FlowableUtils {
/**
* 迭代获取子流程脏路线
* 说明假如回退的点就是子流程那么也肯定会回退到子流程最初的用户任务节点因此子流程中的节点全是脏路线
* @param source 起始节点
*
* @param source 起始节点
* @param hasSequenceFlow 已经经过的连线的 ID用于判断线路是否重复
* @param dirtyRoads 确定为脏数据的点因为不需要重复因此使用 set 存储
* @param dirtyRoads 确定为脏数据的点因为不需要重复因此使用 set 存储
* @return
*/
public static Set<String> findChildProcessAllDirtyRoad(FlowElement source, Set<String> hasSequenceFlow, Set<String> dirtyRoads) {
@ -293,7 +303,7 @@ public class FlowableUtils {
if (sequenceFlows != null) {
// 循环找到目标元素
for (SequenceFlow sequenceFlow: sequenceFlows) {
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复,说明循环了,跳过这个循环
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
@ -315,10 +325,11 @@ public class FlowableUtils {
/**
* 判断脏路线结束节点是否在子流程上
* @param source 起始节点
*
* @param source 起始节点
* @param hasSequenceFlow 已经经过的连线的 ID用于判断线路是否重复
* @param targets 判断脏路线节点是否存在子流程上只要存在一个说明脏路线只到子流程为止
* @param inChildProcess 是否存在子流程上true false
* @param targets 判断脏路线节点是否存在子流程上只要存在一个说明脏路线只到子流程为止
* @param inChildProcess 是否存在子流程上true false
* @return
*/
public static Boolean dirtyTargetInChildProcess(FlowElement source, Set<String> hasSequenceFlow, List<String> targets, Boolean inChildProcess) {
@ -330,7 +341,7 @@ public class FlowableUtils {
if (sequenceFlows != null && !inChildProcess) {
// 循环找到目标元素
for (SequenceFlow sequenceFlow: sequenceFlows) {
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复,说明循环了,跳过这个循环
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
@ -356,10 +367,11 @@ public class FlowableUtils {
/**
* 迭代从后向前扫描判断目标节点相对于当前节点是否是串行
* 不存在直接回退到子流程中的情况但存在从子流程出去到父流程情况
* @param source 起始节点
* @param isSequential 是否串行
*
* @param source 起始节点
* @param isSequential 是否串行
* @param hasSequenceFlow 已经经过的连线的 ID用于判断线路是否重复
* @param targetKsy 目标节点
* @param targetKsy 目标节点
* @return
*/
public static Boolean iteratorCheckSequentialReferTarget(FlowElement source, String targetKsy, Set<String> hasSequenceFlow, Boolean isSequential) {
@ -376,7 +388,7 @@ public class FlowableUtils {
if (sequenceFlows != null) {
// 循环找到目标元素
for (SequenceFlow sequenceFlow: sequenceFlows) {
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复,说明循环了,跳过这个循环
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
@ -405,9 +417,10 @@ public class FlowableUtils {
/**
* 从后向前寻路获取到达节点的所有路线
* 不存在直接回退到子流程但是存在回退到父级流程的情况
* @param source 起始节点
*
* @param source 起始节点
* @param passRoads 已经经过的点集合
* @param roads 路线
* @param roads 路线
* @return
*/
public static List<List<UserTask>> findRoad(FlowElement source, List<UserTask> passRoads, Set<String> hasSequenceFlow, List<List<UserTask>> roads) {
@ -424,7 +437,7 @@ public class FlowableUtils {
List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
if (sequenceFlows != null && sequenceFlows.size() != 0) {
for (SequenceFlow sequenceFlow: sequenceFlows) {
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复,说明循环了,跳过这个循环
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
@ -447,7 +460,8 @@ public class FlowableUtils {
/**
* 历史节点数据清洗清洗掉又回滚导致的脏数据
* @param allElements 全部节点信息
*
* @param allElements 全部节点信息
* @param historicTaskInstanceList 历史任务实例信息数据采用开始时间升序
* @return
*/
@ -541,7 +555,7 @@ public class FlowableUtils {
lastHistoricTaskInstanceList.add(stack.peek().getTaskDefinitionKey());
}
// 校验脏线路是否结束
for (int i = 0; i < deleteKeyList.size(); i ++) {
for (int i = 0; i < deleteKeyList.size(); i++) {
// 如果发现脏数据属于会签,记录下下标与对应 Key,以备后续比对,会签脏数据范畴开始
if (multiKey == null && multiTask.contains(stack.peek().getTaskDefinitionKey())
&& deleteKeyList.get(i).contains(stack.peek().getTaskDefinitionKey())) {
@ -551,7 +565,7 @@ public class FlowableUtils {
// 会签脏数据处理,节点退回会签清空
// 如果在会签脏数据范畴中发现 Key改变,说明会签脏数据在上个节点就结束了,可以把会签脏数据删掉
if (multiKey != null && !multiKey.toString().equals(stack.peek().getTaskDefinitionKey())) {
deleteKeyList.set(multiIndex , deleteKeyList.get(multiIndex).replace(stack.peek().getTaskDefinitionKey() + ",", ""));
deleteKeyList.set(multiIndex, deleteKeyList.get(multiIndex).replace(stack.peek().getTaskDefinitionKey() + ",", ""));
multiKey = null;
// 结束进行下校验删除
multiOpera = true;
@ -561,7 +575,7 @@ public class FlowableUtils {
// 脏数据产生的新实例中是否包含这条数据
if (multiKey == null && deleteKeyList.get(i).contains(stack.peek().getTaskDefinitionKey())) {
// 删除匹配到的部分
deleteKeyList.set(i , deleteKeyList.get(i).replace(stack.peek().getTaskDefinitionKey() + ",", ""));
deleteKeyList.set(i, deleteKeyList.get(i).replace(stack.peek().getTaskDefinitionKey() + ",", ""));
}
// 如果每组中的元素都以匹配过,说明脏数据结束
if ("".equals(deleteKeyList.get(i))) {
@ -587,4 +601,102 @@ public class FlowableUtils {
log.info("清洗后的历史节点数据:" + lastHistoricTaskInstanceList);
return lastHistoricTaskInstanceList;
}
/**
* flowElement 获取 指定名称的 拓展元素
*
* @param flowElement 元素
* @param extensionElementName 拓展元素名称
*/
public static ExtensionElement getExtensionElementFromFlowElementByName(FlowElement flowElement, String extensionElementName) {
if (flowElement == null) {
return null;
}
Map<String, List<ExtensionElement>> extensionElements = flowElement.getExtensionElements();
for (Map.Entry<String, List<ExtensionElement>> stringEntry : extensionElements.entrySet()) {
if (stringEntry.getKey().equals(extensionElementName)) {
for (ExtensionElement extensionElement : stringEntry.getValue()) {
if (extensionElement.getName().equals(extensionElementName)) {
return extensionElement;
}
}
}
}
return null;
}
/**
* 获取当前任务节点扩展属性信息
*
* @param repositoryService
* @param task 当前任务
* @return 自定义属性列表
*/
public static List<Object> getPropertyElement(RepositoryService repositoryService, org.flowable.task.api.Task task) {
FlowElement flowElement = getCurrentElement(repositoryService, task);
ExtensionElement extensionElement = FlowableUtils.getExtensionElementFromFlowElementByName(flowElement, "properties");
if (extensionElement == null) {
return Collections.emptyList();
}
return getPropertyExtensionElementByName(extensionElement, "property");
}
/**
* 获取当前任务节点
*
* @param repositoryService
* @param task
* @return
*/
public static FlowElement getCurrentElement(RepositoryService repositoryService, org.flowable.task.api.Task task) {
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(task.getProcessDefinitionId()).singleResult();
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
return bpmnModel.getFlowElement(task.getTaskDefinitionKey());
}
/**
* 根据属性名获取扩展元素中的扩展属性列表
*
* @param extensionElement 扩展元素
* @param attributesName 属性名
* @return 扩展属性列表
*/
public static List<Object> getPropertyExtensionElementByName(ExtensionElement extensionElement, String attributesName) {
try {
// 获取名称为attributesName的子元素
return Optional.ofNullable(extensionElement.getChildElements().get(attributesName))
.orElse(Collections.emptyList()) // 如果子元素不存在则返回空集合,避免null引用
.stream()
.map(element -> {
// 获取子元素的属性
Map<String, List<ExtensionAttribute>> attributes = element.getAttributes();
Object propertyDto = new Object();
// 获取FlowPropertyDto的所有属性
Arrays.stream(propertyDto.getClass().getDeclaredFields())
.forEach(field -> {
field.setAccessible(true);
// 获取属性名称和值
attributes.getOrDefault(field.getName(), Collections.emptyList())
.stream()
.findFirst()
.ifPresent(attribute -> {
try {
// 反射设置属性值
field.set(propertyDto, attribute.getValue());
} catch (IllegalAccessException e) {
e.printStackTrace();
// 如果反射设置失败则忽略该属性
}
});
});
return propertyDto;
}).collect(Collectors.toList());
} catch (Exception e) {
e.printStackTrace();
return Collections.emptyList(); // 如果发生异常则返回空列表
}
}
}

3
ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/IFlowInstanceService.java

@ -3,9 +3,6 @@ package com.ruoyi.flowable.service;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.flowable.domain.vo.FlowTaskVo;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.task.api.Task;
import java.util.List;
import java.util.Map;
/**

11
ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/IFlowTaskService.java

@ -154,7 +154,7 @@ public interface IFlowTaskService {
* @param taskId 任务Id
* @return
*/
Task getTaskForm(String taskId);
AjaxResult getTaskForm(String taskId);
/**
* 获取流程过程图
@ -206,4 +206,13 @@ public interface IFlowTaskService {
* @return
*/
AjaxResult flowTaskForm(String taskId) throws Exception;
/**
* 流程节点信息
* @param procInsId
* @param elementId
* @return
*/
AjaxResult flowTaskInfo(String procInsId, String elementId);
}

62
ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/ISysTaskFormService.java

@ -1,62 +0,0 @@
package com.ruoyi.flowable.service;
import com.ruoyi.system.domain.SysTaskForm;
import java.util.List;
/**
* 流程任务关联单Service接口
*
* @author Tony
* @date 2021-04-03
*/
@Deprecated
public interface ISysTaskFormService {
/**
* 查询流程任务关联单
*
* @param id 流程任务关联单ID
* @return 流程任务关联单
*/
public SysTaskForm selectSysTaskFormById(Long id);
/**
* 查询流程任务关联单列表
*
* @param sysTaskForm 流程任务关联单
* @return 流程任务关联单集合
*/
public List<SysTaskForm> selectSysTaskFormList(SysTaskForm sysTaskForm);
/**
* 新增流程任务关联单
*
* @param sysTaskForm 流程任务关联单
* @return 结果
*/
public int insertSysTaskForm(SysTaskForm sysTaskForm);
/**
* 修改流程任务关联单
*
* @param sysTaskForm 流程任务关联单
* @return 结果
*/
public int updateSysTaskForm(SysTaskForm sysTaskForm);
/**
* 批量删除流程任务关联单
*
* @param ids 需要删除的流程任务关联单ID
* @return 结果
*/
public int deleteSysTaskFormByIds(Long[] ids);
/**
* 删除流程任务关联单信息
*
* @param id 流程任务关联单ID
* @return 结果
*/
public int deleteSysTaskFormById(Long id);
}

25
ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/FlowDefinitionServiceImpl.java

@ -6,8 +6,8 @@ import com.github.pagehelper.PageInfo;
import com.ruoyi.flowable.common.constant.ProcessConstants;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.flowable.common.enums.FlowComment;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.flowable.common.enums.FlowComment;
import com.ruoyi.system.domain.FlowProcDefDto;
import com.ruoyi.flowable.factory.FlowServiceFactory;
import com.ruoyi.flowable.service.IFlowDefinitionService;
@ -55,9 +55,6 @@ public class FlowDefinitionServiceImpl extends FlowServiceFactory implements IFl
@Resource
private ISysDeptService sysDeptService;
@Resource
private ISysPostService postService;
@Resource
private FlowDeployMapper flowDeployMapper;
@ -199,19 +196,15 @@ public class FlowDefinitionServiceImpl extends FlowServiceFactory implements IFl
SysUser sysUser = SecurityUtils.getLoginUser().getUser();
identityService.setAuthenticatedUserId(sysUser.getUserId().toString());
variables.put(ProcessConstants.PROCESS_INITIATOR, sysUser.getUserId());
runtimeService.startProcessInstanceById(procDefId, variables);
// 流程发起时 跳过发起人节点
// SysUser sysUser = SecurityUtils.getLoginUser().getUser();
// identityService.setAuthenticatedUserId(sysUser.getUserId().toString());
// variables.put(ProcessConstants.PROCESS_INITIATOR, "");
// ProcessInstance processInstance = runtimeService.startProcessInstanceById(procDefId, variables);
// // 给第一步申请人节点设置任务执行人和意见
// Task task = taskService.createTaskQuery().processInstanceId(processInstance.getProcessInstanceId()).singleResult();
// if (Objects.nonNull(task)) {
// taskService.addComment(task.getId(), processInstance.getProcessInstanceId(), FlowComment.NORMAL.getType(), sysUser.getNickName() + "发起流程申请");
//// taskService.setAssignee(task.getId(), sysUser.getUserId().toString());
// taskService.complete(task.getId(), variables);
// }
ProcessInstance processInstance = runtimeService.startProcessInstanceById(procDefId, variables);
// 给第一步申请人节点设置任务执行人和意见
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getProcessInstanceId()).singleResult();
if (Objects.nonNull(task)) {
taskService.addComment(task.getId(), processInstance.getProcessInstanceId(), FlowComment.NORMAL.getType(), sysUser.getNickName() + "发起流程申请");
taskService.complete(task.getId(), variables);
}
return AjaxResult.success("流程启动成功");
} catch (Exception e) {
e.printStackTrace();

350
ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/FlowTaskServiceImpl.java

@ -2,11 +2,9 @@ package com.ruoyi.flowable.service.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.flowable.common.constant.ProcessConstants;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysRole;
@ -14,10 +12,7 @@ import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.flowable.common.enums.FlowComment;
import com.ruoyi.common.exception.CustomException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.flowable.domain.dto.FlowCommentDto;
import com.ruoyi.flowable.domain.dto.FlowNextDto;
import com.ruoyi.flowable.domain.dto.FlowTaskDto;
import com.ruoyi.flowable.domain.dto.FlowViewerDto;
import com.ruoyi.flowable.domain.dto.*;
import com.ruoyi.flowable.domain.vo.FlowQueryVo;
import com.ruoyi.flowable.domain.vo.FlowTaskVo;
import com.ruoyi.flowable.factory.FlowServiceFactory;
@ -38,13 +33,11 @@ import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.api.FlowableObjectNotFoundException;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.engine.ProcessEngineConfiguration;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ActivityInstance;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.task.Comment;
@ -321,56 +314,6 @@ public class FlowTaskServiceImpl extends FlowServiceFactory implements IFlowTask
*/
@Override
public AjaxResult findReturnTaskList(FlowTaskVo flowTaskVo) {
// // 当前任务 task
// Task task = taskService.createTaskQuery().taskId(flowTaskVo.getTaskId()).singleResult();
// // 从流程历史任务中获取可退回节点
//// List<HistoricActivityInstance> hisActIns = historyService.createHistoricActivityInstanceQuery()
//// .executionId(task.getExecutionId())
//// .activityType("userTask")
//// .orderByHistoricActivityInstanceStartTime()
//// .finished()
//// .desc()
//// .list();
////
//// // 可回退的节点列表
//// List<ReturnTaskNodeVo> returnTaskNodeList = new ArrayList<>();
//// ReturnTaskNodeVo returnTaskNodeVo;
//// for (HistoricActivityInstance activityInstance : hisActIns) {
//// returnTaskNodeVo = new ReturnTaskNodeVo();
//// returnTaskNodeVo.setId(activityInstance.getActivityId());
//// // 根据流程节点处理时间校验改节点是否已完成
//// returnTaskNodeVo.setName(activityInstance.getActivityName());
//// returnTaskNodeList.add(returnTaskNodeVo);
//// }
// List<UserTask> userTaskList = new ArrayList<>();
// // 获取流程定义信息
// ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(task.getProcessDefinitionId()).singleResult();
// // 获取所有节点信息,暂不考虑子流程情况
// Process process = repositoryService.getBpmnModel(processDefinition.getId()).getProcesses().get(0);
// Collection<FlowElement> flowElements = process.getFlowElements();
// // 获取当前任务节点元素
// UserTask source = null;
// if (flowElements != null) {
// for (FlowElement flowElement : flowElements) {
// // 类型为用户节点
// if (flowElement.getId().equals(task.getTaskDefinitionKey())) {
// source = (UserTask) flowElement;
// }
// }
// }
// // 获取节点的所有路线
// List<List<UserTask>> roads = FlowableUtils.findRoad(source, null, null, null);
//
// for (List<UserTask> road : roads) {
// if (userTaskList.size() == 0) {
// // 还没有可回退节点直接添加
// userTaskList = road;
// } else {
// // 如果已有回退节点,则比对取交集部分
// userTaskList.retainAll(road);
// }
// }
// return AjaxResult.success(userTaskList);
// 当前任务 task
Task task = taskService.createTaskQuery().taskId(flowTaskVo.getTaskId()).singleResult();
// 获取流程定义信息
@ -629,7 +572,9 @@ public class FlowTaskServiceImpl extends FlowServiceFactory implements IFlowTask
*/
@Override
public AjaxResult revokeProcess(FlowTaskVo flowTaskVo) {
Task task = taskService.createTaskQuery().processInstanceId(flowTaskVo.getInstanceId()).singleResult();
Task task = taskService.createTaskQuery()
.processInstanceId(flowTaskVo.getInstanceId())
.singleResult();
if (task == null) {
throw new CustomException("流程未启动或已执行完成,无法撤回");
}
@ -641,41 +586,35 @@ public class FlowTaskServiceImpl extends FlowServiceFactory implements IFlowTask
.asc()
.list();
String myTaskId = null;
HistoricTaskInstance myTask = null;
for (HistoricTaskInstance hti : htiList) {
if (loginUser.getUserId().toString().equals(hti.getAssignee())) {
myTaskId = hti.getId();
myTask = hti;
break;
}
}
if (null == myTaskId) {
throw new CustomException("该任务非当前用户提交,无法撤回");
}
String processDefinitionId = myTask.getProcessDefinitionId();
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
//变量
// Map<String, VariableInstance> variables = runtimeService.getVariableInstances(currentTask.getExecutionId());
String myActivityId = null;
List<HistoricActivityInstance> haiList = historyService.createHistoricActivityInstanceQuery()
.executionId(myTask.getExecutionId()).finished().list();
for (HistoricActivityInstance hai : haiList) {
if (myTaskId.equals(hai.getTaskId())) {
myActivityId = hai.getActivityId();
break;
List<HistoricTaskInstance> historicTaskInstanceList = historyService
.createHistoricTaskInstanceQuery()
.processInstanceId(task.getProcessInstanceId())
.orderByHistoricTaskInstanceStartTime()
.asc()
.list();
Iterator<HistoricTaskInstance> it = historicTaskInstanceList.iterator();
//循环节点,获取当前节点的上一节点的key
String tarKey = "";
while (it.hasNext()) {
HistoricTaskInstance his = it.next();
if (!task.getTaskDefinitionKey().equals(his.getTaskDefinitionKey())) {
tarKey = his.getTaskDefinitionKey();
}
}
FlowNode myFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(myActivityId);
Execution execution = runtimeService.createExecutionQuery().executionId(task.getExecutionId()).singleResult();
String activityId = execution.getActivityId();
FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(activityId);
//记录原活动方向
List<SequenceFlow> oriSequenceFlows = new ArrayList<>(flowNode.getOutgoingFlows());
// 跳转节点
runtimeService.createChangeActivityStateBuilder()
.processInstanceId(flowTaskVo.getInstanceId())
.moveActivityIdTo(task.getTaskDefinitionKey(), tarKey)
.changeState();
return AjaxResult.success();
}
@ -699,9 +638,9 @@ public class FlowTaskServiceImpl extends FlowServiceFactory implements IFlowTask
.orderByTaskCreateTime().desc();
// TODO 传入名称查询不到数据?
// if (StringUtils.isNotBlank(queryVo.getName())){
// taskQuery.processDefinitionNameLike(queryVo.getName());
// }
if (StringUtils.isNotBlank(queryVo.getName())) {
taskQuery.processDefinitionNameLike(queryVo.getName());
}
page.setTotal(taskQuery.count());
List<Task> taskList = taskQuery.listPage(queryVo.getPageSize() * (queryVo.getPageNum() - 1), queryVo.getPageSize());
List<FlowTaskDto> flowList = new ArrayList<>();
@ -786,9 +725,7 @@ public class FlowTaskServiceImpl extends FlowServiceFactory implements IFlowTask
SysUser startUser = sysUserService.selectUserById(Long.parseLong(historicProcessInstance.getStartUserId()));
flowTask.setStartUserId(startUser.getNickName());
flowTask.setStartUserName(startUser.getNickName());
if(Objects.nonNull(startUser.getDept())) {
flowTask.setStartDeptName(startUser.getDept().getDeptName());
}
flowTask.setStartDeptName(Objects.nonNull(startUser.getDept()) ? startUser.getDept().getDeptName() : "");
hisTaskList.add(flowTask);
}
page.setTotal(taskInstanceQuery.count());
@ -898,9 +835,10 @@ public class FlowTaskServiceImpl extends FlowServiceFactory implements IFlowTask
* @return
*/
@Override
public Task getTaskForm(String taskId) {
public AjaxResult getTaskForm(String taskId) {
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
return task;
SysForm sysForm = sysFormService.selectSysFormById(Long.parseLong(task.getFormKey()));
return AjaxResult.success(sysForm.getFormContent());
}
/**
@ -1003,11 +941,6 @@ public class FlowTaskServiceImpl extends FlowServiceFactory implements IFlowTask
*/
@Override
public AjaxResult processVariables(String taskId) {
// HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
// .processInstanceId(task.getProcessInstanceId())
// .singleResult();
// SysUser startUser = sysUserService.selectUserById(Long.parseLong(historicProcessInstance.getStartUserId()));
// 流程变量
HistoricTaskInstance historicTaskInstance = historyService.createHistoricTaskInstanceQuery().includeProcessVariables().finished().taskId(taskId).singleResult();
if (Objects.nonNull(historicTaskInstance)) {
@ -1019,7 +952,7 @@ public class FlowTaskServiceImpl extends FlowServiceFactory implements IFlowTask
}
/**
* 获取下一节点
* 审批任务获取下一节点
*
* @param flowTaskVo 任务
* @return
@ -1028,64 +961,61 @@ public class FlowTaskServiceImpl extends FlowServiceFactory implements IFlowTask
public AjaxResult getNextFlowNode(FlowTaskVo flowTaskVo) {
// Step 1. 获取当前节点并找到下一步节点
Task task = taskService.createTaskQuery().taskId(flowTaskVo.getTaskId()).singleResult();
FlowNextDto flowNextDto = new FlowNextDto();
if (Objects.nonNull(task)) {
// Step 2. 获取当前流程所有流程变量(网关节点时需要校验表达式)
Map<String, Object> variables = taskService.getVariables(task.getId());
List<UserTask> nextUserTask = FindNextNodeUtil.getNextUserTasks(repositoryService, task, variables);
if (CollectionUtils.isNotEmpty(nextUserTask)) {
for (UserTask userTask : nextUserTask) {
MultiInstanceLoopCharacteristics multiInstance = userTask.getLoopCharacteristics();
// 会签节点
if (Objects.nonNull(multiInstance)) {
flowNextDto.setVars(multiInstance.getInputDataItem());
flowNextDto.setType(ProcessConstants.PROCESS_MULTI_INSTANCE);
flowNextDto.setDataType(ProcessConstants.DYNAMIC);
} else {
// 读取自定义节点属性 判断是否是否需要动态指定任务接收人员、组
String dataType = userTask.getAttributeValue(ProcessConstants.NAMASPASE, ProcessConstants.PROCESS_CUSTOM_DATA_TYPE);
String userType = userTask.getAttributeValue(ProcessConstants.NAMASPASE, ProcessConstants.PROCESS_CUSTOM_USER_TYPE);
flowNextDto.setVars(ProcessConstants.PROCESS_APPROVAL);
flowNextDto.setType(userType);
flowNextDto.setDataType(dataType);
}
}
} else {
return AjaxResult.success("流程已完结", null);
}
if (Objects.isNull(task)) {
return AjaxResult.error("任务不存在或已被审批!");
}
return AjaxResult.success(flowNextDto);
// Step 2. 获取当前流程所有流程变量(网关节点时需要校验表达式)
Map<String, Object> variables = taskService.getVariables(task.getId());
List<UserTask> nextUserTask = FindNextNodeUtil.getNextUserTasks(repositoryService, task, variables);
if (CollectionUtils.isEmpty(nextUserTask)) {
return AjaxResult.success("流程已完结!", null);
}
return getFlowAttribute(nextUserTask);
}
/**
* 获取下一节点
* 发起流程获取下一节点
*
* @param flowTaskVo 任务
* @return
*/
@Override
public AjaxResult getNextFlowNodeByStart(FlowTaskVo flowTaskVo) {
// Step 1. 查找流程定义信息
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(flowTaskVo.getDeploymentId()).singleResult();
// Step 1. 获取当前节点并找到下一步节点
FlowNextDto flowNextDto = new FlowNextDto();
// Step 2. 获取当前流程所有流程变量(网关节点时需要校验表达式)
if (Objects.isNull(processDefinition)) {
return AjaxResult.error("流程信息不存在!");
}
// Step 2. 获取下一任务节点(网关节点时需要校验表达式)
List<UserTask> nextUserTask = FindNextNodeUtil.getNextUserTasksByStart(repositoryService, processDefinition, flowTaskVo.getVariables());
if (CollectionUtils.isNotEmpty(nextUserTask)) {
for (UserTask userTask : nextUserTask) {
MultiInstanceLoopCharacteristics multiInstance = userTask.getLoopCharacteristics();
// 会签节点
if (Objects.nonNull(multiInstance)) {
flowNextDto.setVars(multiInstance.getInputDataItem());
flowNextDto.setType(ProcessConstants.PROCESS_MULTI_INSTANCE);
flowNextDto.setDataType(ProcessConstants.DYNAMIC);
} else {
// 读取自定义节点属性 判断是否是否需要动态指定任务接收人员、组
String dataType = userTask.getAttributeValue(ProcessConstants.NAMASPASE, ProcessConstants.PROCESS_CUSTOM_DATA_TYPE);
String userType = userTask.getAttributeValue(ProcessConstants.NAMASPASE, ProcessConstants.PROCESS_CUSTOM_USER_TYPE);
flowNextDto.setVars(ProcessConstants.PROCESS_APPROVAL);
flowNextDto.setType(userType);
flowNextDto.setDataType(dataType);
}
if (CollectionUtils.isEmpty(nextUserTask)) {
return AjaxResult.error("暂未查找到下一任务,请检查流程设计是否正确!");
}
return getFlowAttribute(nextUserTask);
}
/**
* 获取任务节点属性,包含自定义属性等
*
* @param nextUserTask
*/
private AjaxResult getFlowAttribute(List<UserTask> nextUserTask) {
FlowNextDto flowNextDto = new FlowNextDto();
for (UserTask userTask : nextUserTask) {
MultiInstanceLoopCharacteristics multiInstance = userTask.getLoopCharacteristics();
// 会签节点
if (Objects.nonNull(multiInstance)) {
flowNextDto.setVars(multiInstance.getInputDataItem());
flowNextDto.setType(ProcessConstants.PROCESS_MULTI_INSTANCE);
flowNextDto.setDataType(ProcessConstants.DYNAMIC);
} else {
// 读取自定义节点属性 判断是否是否需要动态指定任务接收人员、组
String dataType = userTask.getAttributeValue(ProcessConstants.NAMASPASE, ProcessConstants.PROCESS_CUSTOM_DATA_TYPE);
String userType = userTask.getAttributeValue(ProcessConstants.NAMASPASE, ProcessConstants.PROCESS_CUSTOM_USER_TYPE);
flowNextDto.setVars(ProcessConstants.PROCESS_APPROVAL);
flowNextDto.setType(userType);
flowNextDto.setDataType(dataType);
}
}
return AjaxResult.success(flowNextDto);
@ -1132,7 +1062,10 @@ public class FlowTaskServiceImpl extends FlowServiceFactory implements IFlowTask
FlowViewerDto flowViewerDto = new FlowViewerDto();
flowViewerDto.setKey(s.getActivityId());
flowViewerDto.setCompleted(true);
flowViewerList.add(flowViewerDto);
// 退回节点不进行展示
if (StringUtils.isBlank(s.getDeleteReason())) {
flowViewerList.add(flowViewerDto);
}
});
// 获取代办节点
@ -1143,6 +1076,8 @@ public class FlowTaskServiceImpl extends FlowServiceFactory implements IFlowTask
// 保存需要代办的节点编号
listUnFinished.forEach(s -> {
// 删除已退回节点
flowViewerList.removeIf(task -> task.getKey().equals(s.getActivityId()));
FlowViewerDto flowViewerDto = new FlowViewerDto();
flowViewerDto.setKey(s.getActivityId());
flowViewerDto.setCompleted(false);
@ -1169,11 +1104,7 @@ public class FlowTaskServiceImpl extends FlowServiceFactory implements IFlowTask
*/
@Override
public AjaxResult flowTaskForm(String taskId) throws Exception {
JSONObject result = new JSONObject();
result.put("formKeyExist", false);
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey());
// 流程变量
Map<String, Object> parameters = new HashMap<>();
HistoricTaskInstance historicTaskInstance = historyService.createHistoricTaskInstanceQuery().includeProcessVariables().finished().taskId(taskId).singleResult();
@ -1182,36 +1113,105 @@ public class FlowTaskServiceImpl extends FlowServiceFactory implements IFlowTask
} else {
parameters = taskService.getVariables(taskId);
}
JSONObject oldVariables = JSONObject.parseObject(JSON.toJSONString(parameters.get("formJson")));
List<JSONObject> oldFields = JSON.parseObject(JSON.toJSONString(oldVariables.get("widgetList")), new TypeReference<List<JSONObject>>() {
});
// 设置已填写的表单为禁用状态
for (JSONObject oldField : oldFields) {
JSONObject options = oldField.getJSONObject("options");
options.put("disabled", true);
}
// TODO 暂时只处理用户任务上的表单
if (flowElement instanceof UserTask) {
String formKey = ((UserTask) flowElement).getFormKey();
if (StringUtils.isNotBlank(formKey)) {
SysForm sysForm = sysFormService.selectSysFormById(Long.parseLong(formKey));
JSONObject oldVariables = JSONObject.parseObject(JSON.toJSONString(parameters.get("variables")));
List<JSONObject> oldFields = JSON.parseObject(JSON.toJSONString(oldVariables.get("fields")), new TypeReference<List<JSONObject>>() {
});
oldFields.forEach(obj -> obj.put("disabled", true));
JSONObject data = JSONObject.parseObject(sysForm.getFormContent());
List<JSONObject> newFields = JSON.parseObject(JSON.toJSONString(data.get("fields")), new TypeReference<List<JSONObject>>() {
});
oldFields.addAll(newFields);
oldVariables.put("fields", oldFields);
oldVariables.put("disabled", false);
oldVariables.put("formBtns", true);
result.put("formData", oldVariables);
result.put("formKeyExist", true);
return AjaxResult.success("", result);
} else {
result.put("formData", parameters.get("variables"));
return AjaxResult.success("", result);
if (StringUtils.isNotBlank(task.getFormKey())) {
SysForm sysForm = sysFormService.selectSysFormById(Long.parseLong(task.getFormKey()));
JSONObject data = JSONObject.parseObject(sysForm.getFormContent());
List<JSONObject> newFields = JSON.parseObject(JSON.toJSONString(data.get("widgetList")), new TypeReference<List<JSONObject>>() {
});
// 表单回显时 加入子表单信息到流程变量中
for (JSONObject newField : newFields) {
String key = newField.getString("id");
// 处理图片上传组件回显问题
if ("picture-upload".equals(newField.getString("type"))) {
parameters.put(key, new ArrayList<>());
} else {
parameters.put(key, null);
}
}
} else {
result.put("formData", parameters.get("variables"));
return AjaxResult.success("", result);
oldFields.addAll(newFields);
}
oldVariables.put("widgetList", oldFields);
parameters.put("formJson", oldVariables);
return AjaxResult.success(parameters);
}
/**
* 流程节点信息
*
* @param procInsId
* @param elementId
* @return
*/
@Override
public AjaxResult flowTaskInfo(String procInsId, String elementId) {
List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(procInsId)
.activityId(elementId)
.list();
// 退回任务后有多条数据 只取待办任务进行展示
list.removeIf(task -> StringUtils.isNotBlank(task.getDeleteReason()));
if (CollectionUtils.isEmpty(list)) {
return AjaxResult.success();
}
if (list.size() > 1) {
list.removeIf(task -> Objects.nonNull(task.getEndTime()));
}
HistoricActivityInstance histIns = list.get(0);
FlowTaskDto flowTask = new FlowTaskDto();
flowTask.setTaskId(histIns.getTaskId());
flowTask.setTaskName(histIns.getActivityName());
flowTask.setCreateTime(histIns.getStartTime());
flowTask.setFinishTime(histIns.getEndTime());
if (StringUtils.isNotBlank(histIns.getAssignee())) {
SysUser sysUser = sysUserService.selectUserById(Long.parseLong(histIns.getAssignee()));
flowTask.setAssigneeId(sysUser.getUserId());
flowTask.setAssigneeName(sysUser.getNickName());
flowTask.setDeptName(Objects.nonNull(sysUser.getDept()) ? sysUser.getDept().getDeptName() : "");
}
// 流程变量信息
// HistoricTaskInstance historicTaskInstance = historyService.createHistoricTaskInstanceQuery()
// .includeProcessVariables().finished().taskId(histIns.getTaskId()).singleResult();
// flowTask.setVariables(historicTaskInstance.getProcessVariables());
// 展示审批人员
List<HistoricIdentityLink> linksForTask = historyService.getHistoricIdentityLinksForTask(histIns.getTaskId());
StringBuilder stringBuilder = new StringBuilder();
for (HistoricIdentityLink identityLink : linksForTask) {
// 获选人,候选组/角色(多个)
if ("candidate".equals(identityLink.getType())) {
if (StringUtils.isNotBlank(identityLink.getUserId())) {
SysUser sysUser = sysUserService.selectUserById(Long.parseLong(identityLink.getUserId()));
stringBuilder.append(sysUser.getNickName()).append(",");
}
if (StringUtils.isNotBlank(identityLink.getGroupId())) {
SysRole sysRole = sysRoleService.selectRoleById(Long.parseLong(identityLink.getGroupId()));
stringBuilder.append(sysRole.getRoleName()).append(",");
}
}
}
if (StringUtils.isNotBlank(stringBuilder)) {
flowTask.setCandidate(stringBuilder.substring(0, stringBuilder.length() - 1));
}
flowTask.setDuration(histIns.getDurationInMillis() == null || histIns.getDurationInMillis() == 0 ? null : getDate(histIns.getDurationInMillis()));
// 获取意见评论内容
List<Comment> commentList = taskService.getProcessInstanceComments(histIns.getProcessInstanceId());
commentList.forEach(comment -> {
if (histIns.getTaskId().equals(comment.getTaskId())) {
flowTask.setComment(FlowCommentDto.builder().type(comment.getType()).comment(comment.getFullMessage()).build());
}
});
return AjaxResult.success(flowTask);
}
/**

93
ruoyi-flowable/src/main/java/com/ruoyi/flowable/service/impl/SysTaskFormServiceImpl.java

@ -1,93 +0,0 @@
package com.ruoyi.flowable.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.system.mapper.SysTaskFormMapper;
import com.ruoyi.system.domain.SysTaskForm;
import com.ruoyi.flowable.service.ISysTaskFormService;
/**
* 流程任务关联单Service业务层处理
*
* @author Tony
* @date 2021-04-03
*/
@Service
public class SysTaskFormServiceImpl implements ISysTaskFormService
{
@Autowired
private SysTaskFormMapper sysTaskFormMapper;
/**
* 查询流程任务关联单
*
* @param id 流程任务关联单ID
* @return 流程任务关联单
*/
@Override
public SysTaskForm selectSysTaskFormById(Long id)
{
return sysTaskFormMapper.selectSysTaskFormById(id);
}
/**
* 查询流程任务关联单列表
*
* @param sysTaskForm 流程任务关联单
* @return 流程任务关联单
*/
@Override
public List<SysTaskForm> selectSysTaskFormList(SysTaskForm sysTaskForm)
{
return sysTaskFormMapper.selectSysTaskFormList(sysTaskForm);
}
/**
* 新增流程任务关联单
*
* @param sysTaskForm 流程任务关联单
* @return 结果
*/
@Override
public int insertSysTaskForm(SysTaskForm sysTaskForm)
{
return sysTaskFormMapper.insertSysTaskForm(sysTaskForm);
}
/**
* 修改流程任务关联单
*
* @param sysTaskForm 流程任务关联单
* @return 结果
*/
@Override
public int updateSysTaskForm(SysTaskForm sysTaskForm)
{
return sysTaskFormMapper.updateSysTaskForm(sysTaskForm);
}
/**
* 批量删除流程任务关联单
*
* @param ids 需要删除的流程任务关联单ID
* @return 结果
*/
@Override
public int deleteSysTaskFormByIds(Long[] ids)
{
return sysTaskFormMapper.deleteSysTaskFormByIds(ids);
}
/**
* 删除流程任务关联单信息
*
* @param id 流程任务关联单ID
* @return 结果
*/
@Override
public int deleteSysTaskFormById(Long id)
{
return sysTaskFormMapper.deleteSysTaskFormById(id);
}
}

30
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysExpression.java

@ -7,7 +7,7 @@ import com.ruoyi.common.core.domain.BaseEntity;
/**
* 流程达式对象 sys_expression
*
*
* @author ruoyi
* @date 2022-12-12
*/
@ -25,53 +25,65 @@ public class SysExpression extends BaseEntity
/** 表达式内容 */
@Excel(name = "表达式内容")
private String expression;
/** 表达式类型 */
@Excel(name = "表达式类型")
private String dataType;
/** 状态 */
private Integer status;
public void setId(Long id)
public void setId(Long id)
{
this.id = id;
}
public Long getId()
public Long getId()
{
return id;
}
public void setName(String name)
public void setName(String name)
{
this.name = name;
}
public String getName()
public String getName()
{
return name;
}
public void setExpression(String expression)
public void setExpression(String expression)
{
this.expression = expression;
}
public String getExpression()
public String getExpression()
{
return expression;
}
public void setStatus(Integer status)
public void setStatus(Integer status)
{
this.status = status;
}
public Integer getStatus()
public Integer getStatus()
{
return status;
}
public void setDataType(String dataType) {
this.dataType = dataType;
}
public String getDataType() {
return dataType;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("name", getName())
.append("expression", getExpression())
.append("dataType", getDataType())
.append("createTime", getCreateTime())
.append("updateTime", getUpdateTime())
.append("createBy", getCreateBy())

18
ruoyi-system/src/main/resources/mapper/system/SysExpressionMapper.xml

@ -3,11 +3,12 @@
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.SysExpressionMapper">
<resultMap type="SysExpression" id="SysExpressionResult">
<result property="id" column="id" />
<result property="name" column="name" />
<result property="expression" column="expression" />
<result property="dataType" column="data_type"/>
<result property="createTime" column="create_time" />
<result property="updateTime" column="update_time" />
<result property="createBy" column="create_by" />
@ -17,28 +18,29 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap>
<sql id="selectSysExpressionVo">
select id, name, expression, create_time, update_time, create_by, update_by, status, remark from sys_expression
select id, name, expression, data_type,create_time, update_time, create_by, update_by, status, remark from sys_expression
</sql>
<select id="selectSysExpressionList" parameterType="SysExpression" resultMap="SysExpressionResult">
<include refid="selectSysExpressionVo"/>
<where>
<where>
<if test="name != null and name != ''"> and name like concat('%', #{name}, '%')</if>
<if test="expression != null and expression != ''"> and expression = #{expression}</if>
<if test="status != null "> and status = #{status}</if>
</where>
</select>
<select id="selectSysExpressionById" parameterType="Long" resultMap="SysExpressionResult">
<include refid="selectSysExpressionVo"/>
where id = #{id}
</select>
<insert id="insertSysExpression" parameterType="SysExpression" useGeneratedKeys="true" keyProperty="id">
insert into sys_expression
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="name != null">name,</if>
<if test="expression != null">expression,</if>
<if test="dataType != null">data_type,</if>
<if test="createTime != null">create_time,</if>
<if test="updateTime != null">update_time,</if>
<if test="createBy != null">create_by,</if>
@ -49,6 +51,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="name != null">#{name},</if>
<if test="expression != null">#{expression},</if>
<if test="dataType != null">#{dataType},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="createBy != null">#{createBy},</if>
@ -63,6 +66,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<trim prefix="SET" suffixOverrides=",">
<if test="name != null">name = #{name},</if>
<if test="expression != null">expression = #{expression},</if>
<if test="dataType != null">data_type = #{dataType},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="createBy != null">create_by = #{createBy},</if>
@ -78,9 +82,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</delete>
<delete id="deleteSysExpressionByIds" parameterType="String">
delete from sys_expression where id in
delete from sys_expression where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
</mapper>
</mapper>

44
ruoyi-ui/src/api/flowable/expression.js

@ -0,0 +1,44 @@
import request from '@/utils/request'
// 查询流程达式列表
export function listExpression(query) {
return request({
url: '/system/expression/list',
method: 'get',
params: query
})
}
// 查询流程达式详细
export function getExpression(id) {
return request({
url: '/system/expression/' + id,
method: 'get'
})
}
// 新增流程达式
export function addExpression(data) {
return request({
url: '/system/expression',
method: 'post',
data: data
})
}
// 修改流程达式
export function updateExpression(data) {
return request({
url: '/system/expression',
method: 'put',
data: data
})
}
// 删除流程达式
export function delExpression(id) {
return request({
url: '/system/expression/' + id,
method: 'delete'
})
}

9
ruoyi-ui/src/api/flowable/finished.js

@ -19,15 +19,6 @@ export function flowRecord(query) {
})
}
// 撤回任务
export function revokeProcess(data) {
return request({
url: '/flowable/task/revokeProcess',
method: 'post',
data: data
})
}
// 部署流程实例
export function deployStart(deployId) {
return request({

44
ruoyi-ui/src/api/flowable/listener.js

@ -0,0 +1,44 @@
import request from '@/utils/request'
// 查询流程监听列表
export function listListener(query) {
return request({
url: '/system/listener/list',
method: 'get',
params: query
})
}
// 查询流程监听详细
export function getListener(id) {
return request({
url: '/system/listener/' + id,
method: 'get'
})
}
// 新增流程监听
export function addListener(data) {
return request({
url: '/system/listener',
method: 'post',
data: data
})
}
// 修改流程监听
export function updateListener(data) {
return request({
url: '/system/listener',
method: 'put',
data: data
})
}
// 删除流程监听
export function delListener(id) {
return request({
url: '/system/listener/' + id,
method: 'delete'
})
}

8
ruoyi-ui/src/api/flowable/process.js

@ -18,6 +18,14 @@ export function flowFormData(query) {
})
}
export function flowTaskInfo(query) {
return request({
url: '/flowable/task/flowTaskInfo',
method: 'get',
params: query
})
}
// 完成任务
export function complete(data) {
return request({

9
ruoyi-ui/src/components/Process/.bpmnlintrc

@ -1,9 +0,0 @@
{
"extends": [
"bpmnlint:recommended",
"plugin:local/recommended"
],
"rules": {
"local/no-manual-task": "warn"
}
}

68
ruoyi-ui/src/components/Process/BpmData.js

@ -1,68 +0,0 @@
/**
* 存储流程设计相关参数
*/
export default class BpmData {
constructor() {
this.controls = [] // 设计器控件
this.init()
}
init() {
this.controls = [
{
action: 'create.start-event',
title: '开始'
},
{
action: 'create.intermediate-event',
title: '中间'
},
{
action: 'create.end-event',
title: '结束'
},
{
action: 'create.exclusive-gateway',
title: '网关'
},
{
action: 'create.task',
title: '任务'
},
{
action: 'create.user-task',
title: '用户任务'
},
{
action: 'create.user-sign-task',
title: '会签任务'
},
{
action: 'create.subprocess-expanded',
title: '子流程'
},
{
action: 'create.data-object',
title: '数据对象'
},
{
action: 'create.data-store',
title: '数据存储'
},
{
action: 'create.participant-expanded',
title: '扩展流程'
},
{
action: 'create.group',
title: '分组'
}
]
}
// 获取控件配置信息
getControl(action) {
const result = this.controls.filter(item => item.action === action)
return result[0] || {}
}
}

168
ruoyi-ui/src/components/Process/PropertyPanel.vue

@ -1,168 +0,0 @@
<template>
<div ref="propertyPanel" class="property-panel">
<div v-if="nodeName" class="node-name">{{ nodeName }}</div>
<component
:is="getComponent"
v-if="element"
:element="element"
:modeler="modeler"
:users="users"
:groups="groups"
:exps="exps"
:categorys="categorys"
/>
</div>
</template>
<script>
import taskPanel from './components/nodePanel/task'
import startEndPanel from './components/nodePanel/startEnd'
import processPanel from './components/nodePanel/process'
import sequenceFlowPanel from './components/nodePanel/sequenceFlow'
import gatewayPanel from './components/nodePanel/gateway'
import { NodeName } from './lang/zh'
export default {
name: 'PropertyPanel',
components: { processPanel, taskPanel, startEndPanel, sequenceFlowPanel, gatewayPanel },
props: {
users: {
type: Array,
required: true
},
groups: {
type: Array,
required: true
},
categorys: {
type: Array,
required: true
},
exps: {
type: Array,
default: () => []
},
modeler: {
type: Object,
required: true
}
},
data() {
return {
element: null,
form: {
id: '',
name: '',
color: null
},
}
},
computed: {
getComponent() {
const type = this.element?.type
if (['bpmn:IntermediateThrowEvent', 'bpmn:StartEvent', 'bpmn:EndEvent'].includes(type)) {
return 'startEndPanel'
}
if ([
'bpmn:UserTask',
'bpmn:Task',
'bpmn:SendTask',
'bpmn:ReceiveTask',
'bpmn:ManualTask',
'bpmn:BusinessRuleTask',
'bpmn:ServiceTask',
'bpmn:ScriptTask'
// 'bpmn:CallActivity',
// 'bpmn:SubProcess'
].includes(type)) {
return 'taskPanel'
}
if (type === 'bpmn:SequenceFlow') {
return 'sequenceFlowPanel'
}
if ([
'bpmn:InclusiveGateway',
'bpmn:ExclusiveGateway',
'bpmn:ParallelGateway',
'bpmn:EventBasedGateway'
].includes(type)) {
return 'gatewayPanel'
}
if (type === 'bpmn:Process') {
return 'processPanel'
}
return null
},
nodeName() {
if (this.element) {
const bizObj = this.element.businessObject
const type = bizObj?.eventDefinitions
? bizObj.eventDefinitions[0].$type
: bizObj.$type
return NodeName[type] || type
}
return ''
}
},
mounted() {
this.handleModeler()
},
methods: {
handleModeler() {
this.modeler.on('root.added', e => {
if (e.element.type === 'bpmn:Process') {
this.element = null
this.$nextTick().then(() => {
this.element = e.element
})
}
})
this.modeler.on('element.click', e => {
const { element } = e
if (element.type === 'bpmn:Process'
|| element.type === 'bpmn:SequenceFlow'
|| element.type === 'bpmn:EndEvent' ) {
this.$nextTick().then(() => {
this.element = element
})
}
})
this.modeler.on('selection.changed', e => {
// hack
this.element = null
const element = e.newSelection[0]
if (element) {
this.$nextTick().then(() => {
this.element = element
})
}
})
},
}
}
</script>
<style lang="scss">
.property-panel {
padding: 20px 20px;
// reset element css
.el-form--label-top .el-form-item__label {
padding: 0;
}
//
.el-form-item {
margin-bottom: 6px;
}
.tab-table .el-form-item {
margin-bottom: 16px;
}
.node-name{
border-bottom: 1px solid #ccc;
padding: 0 0 10px 20px;
margin-bottom: 10px;
font-size: 16px;
font-weight: bold;
color: #444;
}
}
</style>

181
ruoyi-ui/src/components/Process/README.md

@ -0,0 +1,181 @@
## 界面布局结构
- palette(工具栏) :提供拖拽工具、框选工具、连线工具、基本图元等
- contextPad(上下文面板):可以理解为快捷面板
- propertiesPanel(属性面板):定义流程图中图形元素属性
- shape(图形) 是所有图形的基类(比如Connection,Root)
## 导入与导出
````
## 导入
// 异步方式(推荐)
let result = await bpmnModeler.importXML(xml)
// 回调方式
bpmnModeler.importXML(xml, (result) => {} )
### 导出xml
// 异步方式
let { xml } = await bpmnModeler.saveXML()
// 回调方式
bpmnModeler.saveXML({ format: false },({ xml }) => {})
// 格式化导出的xml
let { xml } = await bpmnModeler.saveXML({ format: true })
### 导出svg
// 异步方式
let { svg } = await bpmnModeler.saveSVG()
// 回调方式
bpmnModeler.saveXML(( { svg } )=>{ })
## 导入的生命周期事件如下:
import.parse.start (即将从xml读取模型)
import.parse.complete (模型读取完成)
import.render.start (图形导入开始)
import.render.complete (图形导入完成)
import.done (一切都完成)
````
## 内部模块/供应商/服务
- eventBus - 事件总线,管理bpmn实例中所有事件
- canvas - 画布,管理svg元素、连线/图形的添加/删除、缩放等
- commandStack - 命令堆栈,管理bpmn内部所有命令操作,提供撤销、重做功能等
- elementRegistry - 元素注册表,管理bpmn内部所有元素
- moddle - 模型管理,用于管理bpmn的xml结构
- modeling - 建模器,绘图时用到,提供用于更新画布上元素的 API(移动、删除)
````
## 获取一个模块
// 第一个参数为模块名称,第二参数表示是否严格模式
bpmnModeler.get("模块名称",false)
````
## 事件总线 - eventBus
````
## 获取事件总线模块
let eventBus = bpmnModeler.get("eventBus")
## 监听事件
// 监听事件
eventBus.on('element.changed', (ev) => {})
// 监听多个事件
eventBus.on(
['shape.added', 'connection.added', 'shape.removed', 'connection.removed'],
(ev) => {
}
)
// 设置优先级
eventBus.on('element.changed', 100, (ev) => {})
// 传入上下文
eventBus.on('element.changed', (ev) => {}, that)
// 使用所有参数
eventBus.on('事件名称', 优先级(可选), 回调函数, 上下文(可选))
## 只监听一次事件
// 用法同on
eventBus.once('事件名称', 优先级(可选), 回调函数, 上下文(可选))
## 取消监听事件
// 取消监听
eventBus.off('element.changed', callback)
// 取消监听多个事件
eventBus.off(['shape.added', 'connection.added', 'shape.removed', 'connection.removed'], callback)
## 触发事件
eventBus.fire('element.changed', data)
````
## 画布 - canvas
````
## 获取画布模块
let canvas = bpmnModeler.get("canvas")
## 缩放
/**
*
* @param {'fit-viewport' | 'fit-content' | number} lvl
* @param {'auto'|{ x: number, y: number }} center
*/
function zoom(lvl, center) {
let canvas = bpmnModeler.get('canvas')
canvas.zoom(lvl, center)
}
// 适应容器缩放
zoom('fit-canvas','auto')
// 完全显示内容
zoom('fit-content','auto')
## 对齐(选择多个元素使用shift+鼠标左键)
/**
* 获取当前选集并对齐
* @param {'left'|'right'|'top'|'bottom'|'middle'|'center'} mode
*/
function align(mode) {
const align = bpmnModeler.get('alignElements')
const selection = bpmnModeler.get('selection')
const elements = selection.get()
if (!elements || elements.length === 0) {
return
}
align.trigger(elements, mode)
}
````
## 元素注册表 - elementRegistry
````
## 获取元素注册表模块
let elementRegistry = bpmnModeler.get('elementRegistry')
## 遍历所有元素
elementRegistry.forEach((shape, svgElement) => { })
## 获取指定元素
let shape = elementRegistry.get(元素id或者SVGElement)
## 获取过滤后的元素
let shapes = elementRegistry.filter((shape) => shape.type === 'bpmn:Task')
## 更新元素ID
elementRegistry.updateId(shape, "123xxxxsssd")
## 删除一个元素
elementRegistry .remove(传入SVGElement)
## 模型 - moddle
基本上没有用到,具体类型定义见此
## 建模器 - modeling
获取建模器模块
let modeling= bpmnModeler.get('modeling')
## 修改元素显示文本(常用)
modeling.updateLabel(shape, '审核')
## 修改元素属性(常用)
modeling.updateProperties(shape, { 属性名称: 属性值 })
## 对齐元素集合
const selection = bpmnModeler.get('selection')
const elements = selection.get()
modeling.updateProperties(selection, 'left')
````

120
ruoyi-ui/src/components/Process/common/bpmnUtils.js

@ -0,0 +1,120 @@
import { NodeName } from '../lang/zh'
// 创建监听器实例
export function createListenerObject(moddle, options, isTask, prefix) {
const listenerObj = Object.create(null);
listenerObj.event = options.event;
isTask && (listenerObj.id = options.id); // 任务监听器特有的 id 字段
switch (options.listenerType) {
case "scriptListener":
listenerObj.script = createScriptObject(moddle, options, prefix);
break;
case "expressionListener":
listenerObj.expression = options.expression;
break;
case "delegateExpressionListener":
listenerObj.delegateExpression = options.delegateExpression;
break;
default:
listenerObj.class = options.class;
}
// 注入字段
if (options.fields) {
listenerObj.fields = options.fields.map(field => {
return createFieldObject(moddle, field, prefix);
});
}
// 任务监听器的 定时器 设置
if (isTask && options.event === "timeout" && !!options.eventDefinitionType) {
const timeDefinition = moddle.create("bpmn:FormalExpression", {
body: options.eventTimeDefinitions
});
const TimerEventDefinition = moddle.create("bpmn:TimerEventDefinition", {
id: `TimerEventDefinition_${uuid(8)}`,
[`time${options.eventDefinitionType.replace(/^\S/, s => s.toUpperCase())}`]: timeDefinition
});
listenerObj.eventDefinitions = [TimerEventDefinition];
}
return moddle.create(`${prefix}:${isTask ? "TaskListener" : "ExecutionListener"}`, listenerObj);
}
// 处理内置流程监听器
export function createSystemListenerObject(moddle, options, isTask, prefix) {
const listenerObj = Object.create(null);
listenerObj.event = options.eventType;
listenerObj.listenerType = options.valueType;
switch (options.valueType) {
case "scriptListener":
listenerObj.script = createScriptObject(moddle, options, prefix);
break;
case "expressionListener":
listenerObj.expression = options.expression;
break;
case "delegateExpressionListener":
listenerObj.delegateExpression = options.delegateExpression;
break;
default:
listenerObj.class = options.value;
}
return moddle.create(`${prefix}:${isTask ? "TaskListener" : "ExecutionListener"}`, listenerObj);
}
// 转换成字段
export function changeListenerObject(options) {
const listenerObj = Object.create(null);
listenerObj.event = options.eventType;
listenerObj.listenerType = options.valueType;
switch (options.valueType) {
case "scriptListener":
// listenerObj.script = createScriptObject(moddle, options, prefix);
break;
case "expressionListener":
listenerObj.expression = options.expression;
break;
case "delegateExpressionListener":
listenerObj.delegateExpression = options.delegateExpression;
break;
default:
listenerObj.class = options.value;
}
return listenerObj;
}
// 创建 监听器的注入字段 实例
export function createFieldObject(moddle, option, prefix) {
const { name, fieldType, string, expression } = option;
const fieldConfig = fieldType === "string" ? { name, string } : { name, expression };
return moddle.create(`${prefix}:Field`, fieldConfig);
}
// 创建脚本实例
export function createScriptObject(moddle, options, prefix) {
const { scriptType, scriptFormat, value, resource } = options;
const scriptConfig = scriptType === "inlineScript" ? { scriptFormat, value } : { scriptFormat, resource };
return moddle.create(`${prefix}:Script`, scriptConfig);
}
// 更新元素扩展属性
export function updateElementExtensions(moddle, modeling, element, extensionList) {
const extensions = moddle.create("bpmn:ExtensionElements", {
values: extensionList
});
modeling.updateProperties(element, {
extensionElements: extensions
});
}
// 创建一个id
export function uuid(length = 8, chars) {
let result = "";
let charsString = chars || "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
for (let i = length; i > 0; --i) {
result += charsString[Math.floor(Math.random() * charsString.length)];
}
return result;
}
// 转换流程节点名称
export function translateNodeName(node){
return NodeName[node];
}

18
ruoyi-ui/src/components/Process/common/global.js

@ -0,0 +1,18 @@
// 全局流程相关变量
const modelerStore = {
'userList': [],
'roleList': [],
'expList': [],
'modeler': null,
'modeling': null,
'moddle': null,
'canvas': null,
'bpmnFactory': null,
'elRegistry': null,
'element': null,
}
export default
{
modelerStore,
}

12
ruoyi-ui/src/components/Process/common/index.js

@ -0,0 +1,12 @@
import inherits from "inherits";
import Viewer from "bpmn-js/lib/Viewer";
import ZoomScrollModule from "diagram-js/lib/navigation/zoomscroll";
import MoveCanvasModule from "diagram-js/lib/navigation/movecanvas";
function CustomViewer(options) {
Viewer.call(this, options);
}
inherits(CustomViewer, Viewer);
CustomViewer.prototype._modules = [].concat(Viewer.prototype._modules, [ZoomScrollModule, MoveCanvasModule]);
export {
CustomViewer
};

24
ruoyi-ui/src/components/Process/common/mixinExecutionListener.js

@ -1,24 +0,0 @@
import executionListenerDialog from '../components/nodePanel/property/executionListener'
export default {
components: {
executionListenerDialog
},
data() {
return {
executionListenerLength: 0,
dialogName: null
}
},
methods: {
computedExecutionListenerLength() {
this.executionListenerLength = this.element.businessObject.extensionElements?.values?.length ?? 0
},
finishExecutionListener() {
if (this.dialogName === 'executionListenerDialog') {
this.computedExecutionListenerLength()
}
this.dialogName = ''
}
}
}

70
ruoyi-ui/src/components/Process/common/mixinPanel.js

@ -1,70 +0,0 @@
import xcrud from 'xcrud'
import golbalConfig from 'xcrud/package/common/config'
import showConfig from '../flowable/showConfig'
golbalConfig.set({
input: {
// size: 'mini'
},
select: {
// size: 'mini'
},
colorPicker: {
showAlpha: true
},
xform: {
form: {
labelWidth: 'auto'
// size: 'mini'
}
}
})
export default {
components: { xForm: xcrud.xForm },
props: {
modeler: {
type: Object,
required: true
},
element: {
type: Object,
required: true
},
categorys: {
type: Array,
default: () => []
}
},
watch: {
'formData.id': function(val) {
this.updateProperties({ id: val })
},
'formData.name': function(val) {
this.updateProperties({ name: val })
},
'formData.documentation': function(val) {
if (!val) {
this.updateProperties({ documentation: [] })
return
}
const documentationElement = this.modeler.get('moddle').create('bpmn:Documentation', { text: val })
this.updateProperties({ documentation: [documentationElement] })
}
},
methods: {
updateProperties(properties) {
const modeling = this.modeler.get('modeling')
modeling.updateProperties(this.element, properties)
}
},
computed: {
elementType() {
const bizObj = this.element.businessObject
return bizObj.eventDefinitions
? bizObj.eventDefinitions[0].$type
: bizObj.$type
},
showConfig() {
return showConfig[this.elementType] || {}
}
}
}

22
ruoyi-ui/src/components/Process/common/mixinXcrud.js

@ -1,22 +0,0 @@
import xcrud from 'xcrud'
import golbalConfig from 'xcrud/package/common/config'
golbalConfig.set({
input: {
// size: 'mini'
},
select: {
// size: 'mini'
},
colorPicker: {
showAlpha: true
},
xform: {
form: {
labelWidth: 'auto'
// size: 'mini'
}
}
})
export default {
components: { xForm: xcrud.xForm }
}

1769
ruoyi-ui/src/components/Process/common/packed-config.js

File diff suppressed because it is too large

55
ruoyi-ui/src/components/Process/common/parseElement.js

@ -1,55 +0,0 @@
export function commonParse(element) {
const result = {
...element.businessObject,
...element.businessObject.$attrs
}
return formatJsonKeyValue(result)
}
export function formatJsonKeyValue(result) {
// 移除flowable前缀,格式化数组
for (const key in result) {
if (key.indexOf('flowable:') === 0) {
const newKey = key.replace('flowable:', '')
result[newKey] = result[key]
delete result[key]
}
}
result = documentationParse(result)
return result
}
export function documentationParse(obj) {
if ('documentation' in obj) {
let str = ''
obj.documentation.forEach(item => {
str += item.text
})
obj.documentation = str
}
return obj
}
export function conditionExpressionParse(obj) {
if ('conditionExpression' in obj) {
if (obj.conditionExpression) {
obj.conditionExpression = obj.conditionExpression.body
}
}
return obj
}
export function userTaskParse(obj) {
for (const key in obj) {
if (key === 'candidateUsers') {
obj.userType = 'candidateUsers'
obj[key] = obj[key]?.split(',') || []
} else if (key === 'candidateGroups') {
obj.userType = 'candidateGroups'
obj[key] = obj[key]?.split(',') || []
} else if (key === 'assignee') {
obj.userType = 'assignee'
}
}
return obj
}

24
ruoyi-ui/src/components/Process/components/custom/customContextPad.vue

@ -1,24 +0,0 @@
export default class CustomContextPad {
constructor(config, contextPad, create, elementFactory, injector, translate) {
this.create = create;
this.elementFactory = elementFactory;
this.translate = translate;
if (config.autoPlace !== false) {
this.autoPlace = injector.get('autoPlace', false);
}
contextPad.registerProvider(this); // contextPad
}
getContextPadEntries(element) {}
}
CustomContextPad.$inject = [
'config',
'contextPad',
'create',
'elementFactory',
'injector',
'translate'
];

81
ruoyi-ui/src/components/Process/components/nodePanel/gateway.vue

@ -1,81 +0,0 @@
<template>
<div>
<x-form ref="xForm" v-model="formData" :config="formConfig">
<template #executionListener>
<el-badge :value="executionListenerLength">
<el-button size="small" @click="dialogName = 'executionListenerDialog'">编辑</el-button>
</el-badge>
</template>
</x-form>
<executionListenerDialog
v-if="dialogName === 'executionListenerDialog'"
:element="element"
:modeler="modeler"
@close="finishExecutionListener"
/>
</div>
</template>
<script>
import mixinPanel from '../../common/mixinPanel'
import mixinExecutionListener from '../../common/mixinExecutionListener'
import { commonParse } from '../../common/parseElement'
export default {
mixins: [mixinPanel, mixinExecutionListener],
data() {
return {
formData: {}
}
},
computed: {
formConfig() {
return {
inline: false,
item: [
{
xType: 'input',
name: 'id',
label: '节点 id',
rules: [{ required: true, message: 'Id 不能为空' }]
},
{
xType: 'input',
name: 'name',
label: '节点名称'
},
{
xType: 'input',
name: 'documentation',
label: '节点描述'
},
{
xType: 'slot',
name: 'executionListener',
label: '执行监听器'
},
{
xType: 'switch',
name: 'async',
label: '异步',
activeText: '是',
inactiveText: '否'
}
]
}
}
},
watch: {
'formData.async': function(val) {
if (val === '') val = null
this.updateProperties({ 'flowable:async': val })
}
},
created() {
this.formData = commonParse(this.element)
}
}
</script>
<style>
</style>

113
ruoyi-ui/src/components/Process/components/nodePanel/process.vue

@ -1,113 +0,0 @@
<template>
<div>
<x-form ref="xForm" v-model="formData" :config="formConfig">
<template #executionListener>
<el-badge :value="executionListenerLength">
<el-button size="small" @click="dialogName = 'executionListenerDialog'">编辑</el-button>
</el-badge>
</template>
<template #signal>
<el-badge :value="signalLength">
<el-button size="small" @click="dialogName = 'signalDialog'">编辑</el-button>
</el-badge>
</template>
</x-form>
<executionListenerDialog
v-if="dialogName === 'executionListenerDialog'"
:element="element"
:modeler="modeler"
@close="finishExecutionListener"
/>
<signalDialog
v-if="dialogName === 'signalDialog'"
:element="element"
:modeler="modeler"
@close="finishExecutionListener"
/>
</div>
</template>
<script>
import mixinPanel from '../../common/mixinPanel'
import mixinExecutionListener from '../../common/mixinExecutionListener'
import signalDialog from './property/signal'
import { commonParse } from '../../common/parseElement'
export default {
components: {
signalDialog
},
mixins: [mixinPanel, mixinExecutionListener],
data() {
return {
signalLength: 0,
formData: {}
}
},
computed: {
formConfig() {
const _this = this
return {
inline: false,
item: [
{
xType: 'select',
name: 'processCategory',
label: '流程分类',
dic: { data: _this.categorys, label: 'dictLabel', value: 'dictValue' }
},
{
xType: 'input',
name: 'id',
label: '流程标识key',
rules: [{ required: true, message: 'Id 不能为空' }]
},
{
xType: 'input',
name: 'name',
label: '流程名称'
},
{
xType: 'input',
name: 'documentation',
label: '节点描述'
},
{
xType: 'slot',
name: 'executionListener',
label: '执行监听器'
},
{
xType: 'slot',
name: 'signal',
label: '信号定义'
}
]
}
}
},
watch: {
'formData.processCategory': function(val) {
if (val === '') val = null
this.updateProperties({ 'flowable:processCategory': val })
}
},
created() {
this.formData = commonParse(this.element)
},
methods: {
computedSignalLength() {
this.signalLength = this.element.businessObject.extensionElements?.values?.length ?? 0
},
finishSignal() {
if (this.dialogName === 'signalDialog') {
this.computedSignalLength()
}
this.dialogName = ''
}
}
}
</script>
<style>
</style>

217
ruoyi-ui/src/components/Process/components/nodePanel/property/executionListener.vue

@ -1,217 +0,0 @@
<template>
<div>
<el-dialog
title="执行监听器"
:visible.sync="dialogVisible"
width="900px"
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="false"
@closed="$emit('close')"
>
<x-form ref="xForm" v-model="formData" :config="formConfig">
<template #params="scope">
<el-badge :value="scope.row.params ? scope.row.params.length : 0" type="primary">
<el-button size="small" @click="configParam(scope.$index)">配置</el-button>
</el-badge>
</template>
</x-form>
<span slot="footer" class="dialog-footer">
<el-button type="primary" size="medium" @click="closeDialog"> </el-button>
</span>
<listener-list
:visible="listenerDialogVisible"
@close="() => this.listenerDialogVisible = false"
@submit="addListener"
/>
</el-dialog>
<listenerParam v-if="showParamDialog" :value="formData.executionListener[nowIndex].params" @close="finishConfigParam" />
</div>
</template>
<script>
import mixinPanel from '../../../common/mixinPanel'
import listenerParam from './listenerParam'
import FlowListener from '@/components/flow/Listener'
import ListenerList from '@/components/Process/components/nodePanel/property/listenerList'
export default {
components: { ListenerList, listenerParam, FlowListener },
mixins: [mixinPanel],
data() {
return {
dialogVisible: true,
listenerDialogVisible: false,
showParamDialog: false,
nowIndex: null,
formData: {
executionListener: []
}
}
},
computed: {
formConfig() {
// const _this = this
return {
inline: false,
item: [
{
xType: 'tabs',
tabs: [
{
label: '执行监听器',
name: 'executionListener',
column: [
{
label: '事件',
name: 'event',
width: 180,
rules: [{ required: true, message: '请选择', trigger: ['blur', 'change'] }],
xType: 'select',
dic: [
{ label: 'start', value: 'start' },
{ label: 'end', value: 'end' },
{ label: 'take', value: 'take' }
]
},
{
label: '类型',
name: 'type',
width: 180,
rules: [{ required: true, message: '请选择', trigger: ['blur', 'change'] }],
xType: 'select',
dic: [
{ label: '类', value: 'class' },
{ label: '表达式', value: 'expression' },
{ label: '委托表达式', value: 'delegateExpression' }
],
tooltip: `类:示例 com.company.MyCustomListener,自定义类必须实现 org.flowable.engine.delegate.ExecutionListener 接口 <br />
表达式示例 \${myObject.callMethod(task, task.eventName)} <br />
委托表达式示例 \${myListenerSpringBean} springBean 需要实现 org.flowable.engine.delegate.ExecutionListener 接口
`
},
{
label: '值',
name: 'className',
xType: 'input',
rules: [{ required: true, message: '请输入', trigger: ['blur', 'change'] }]
},
{
xType: 'slot',
label: '参数',
width: 120,
slot: true,
name: 'params'
}
]
}
]
}
]
}
}
},
mounted() {
this.$nextTick(() => this.addButton())
this.formData.executionListener = this.element.businessObject.extensionElements?.values
.filter(item => item.$type === 'flowable:ExecutionListener')
.map(item => {
let type
if ('class' in item) type = 'class'
if ('expression' in item) type = 'expression'
if ('delegateExpression' in item) type = 'delegateExpression'
return {
event: item.event,
type: type,
className: item[type],
params: item.fields?.map(field => {
let fieldType
if ('stringValue' in field) fieldType = 'stringValue'
if ('expression' in field) fieldType = 'expression'
return {
name: field.name,
type: fieldType,
value: field[fieldType]
}
}) ?? []
}
}) ?? []
},
methods: {
addButton() {
const button = document.createElement('button')
button.innerText = '内置监听器'
button.setAttribute('type', 'button')
button.setAttribute('class', 'el-button el-button--primary el-button--mini')
button.addEventListener('click', () => this.listenerDialogVisible = true)
const div = document.getElementById('pane-executionListener')
const table = div.getElementsByClassName('el-table')[0]
div.insertBefore(button, table)
},
configParam(index) {
this.nowIndex = index
const nowObj = this.formData.executionListener[index]
if (!nowObj.params) {
nowObj.params = []
}
this.showParamDialog = true
},
finishConfigParam(param) {
this.showParamDialog = false
// hack
const cache = this.formData.executionListener[this.nowIndex]
cache.params = param
this.$set(this.formData.executionListener[this.nowIndex], this.nowIndex, cache)
this.nowIndex = null
},
updateElement() {
if (this.formData.executionListener?.length) {
let extensionElements = this.element.businessObject.get('extensionElements')
if (!extensionElements) {
extensionElements = this.modeler.get('moddle').create('bpmn:ExtensionElements')
}
//
extensionElements.values = extensionElements.values?.filter(item => item.$type !== 'flowable:ExecutionListener') ?? []
this.formData.executionListener.forEach(item => {
const executionListener = this.modeler.get('moddle').create('flowable:ExecutionListener')
executionListener['event'] = item.event
executionListener[item.type] = item.className
if (item.params && item.params.length) {
item.params.forEach(field => {
const fieldElement = this.modeler.get('moddle').create('flowable:Field')
fieldElement['name'] = field.name
fieldElement[field.type] = field.value
// flowable.json stringexpressionStringhack
// const valueElement = this.modeler.get('moddle').create(`flowable:${field.type}`, { body: field.value })
// fieldElement[field.type] = valueElement
executionListener.get('fields').push(fieldElement)
})
}
extensionElements.get('values').push(executionListener)
})
this.updateProperties({ extensionElements: extensionElements })
} else {
const extensionElements = this.element.businessObject[`extensionElements`]
if (extensionElements) {
extensionElements.values = extensionElements.values?.filter(item => item.$type !== 'flowable:ExecutionListener') ?? []
}
}
},
closeDialog() {
console.log(this.formData)
this.$refs.xForm.validate().then(() => {
this.updateElement()
this.dialogVisible = false
}).catch(e => console.error(e))
},
addListener(data) {
this.formData.executionListener = this.formData.executionListener.concat(data)
}
}
}
</script>
<style>
.flow-containers .el-badge__content.is-fixed {
top: 18px;
}
</style>

72
ruoyi-ui/src/components/Process/components/nodePanel/property/listenerList.vue

@ -1,72 +0,0 @@
<template>
<el-dialog title="内置监听器"
width="900px"
:visible.sync="dialogVisible"
append-to-body
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="false"
:before-close="close"
>
<flow-listener @handleSelect="handleSelect"/>
<span slot="footer" class="dialog-footer">
<el-button @click="close"> </el-button>
<el-button type="primary" @click="checkComplete"> </el-button>
</span>
</el-dialog>
</template>
<script>
import FlowListener from '@/components/flow/Listener'
export default {
name: 'ListentList',
components: { FlowListener },
props: {
visible: {
type: Boolean,
default: false
}
},
data() {
return {
dialogVisible: this.visible,
listenerList: []
}
},
watch: {
visible: {
handler(newVal) {
this.dialogVisible = newVal
},
immediate: true,
deep: true
}
},
methods: {
close() {
this.dialogVisible = false
this.$emit('close')
},
checkComplete() {
this.close()
this.$emit('submit', this.listenerList)
},
handleSelect(selection) {
const type = ['class', 'expression', 'delegateExpression']
let list = []
selection.forEach(data => {
const formData = {
event: data.eventType,
type: type[parseInt(data.valueType) - 1],
className: data.value
}
list.push(formData)
})
this.listenerList = list
}
}
}
</script>
<style></style>

96
ruoyi-ui/src/components/Process/components/nodePanel/property/listenerParam.vue

@ -1,96 +0,0 @@
<template>
<div>
<el-dialog
title="监听器参数"
:visible.sync="dialogVisible"
width="700px"
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="false"
@closed="$emit('close', formData.paramList)"
>
<x-form ref="xForm" v-model="formData" :config="formConfig" />
<span slot="footer" class="dialog-footer">
<el-button type="primary" size="medium" @click="closeDialog"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import mixinXcrud from '../../../common/mixinXcrud'
export default {
mixins: [mixinXcrud],
props: {
value: {
type: Array,
default: () => []
}
},
data() {
return {
dialogVisible: true,
formData: {
paramList: this.value
}
}
},
computed: {
formConfig() {
return {
inline: false,
item: [
{
xType: 'tabs',
tabs: [
{
label: '监听器参数',
name: 'paramList',
column: [
{
label: '类型',
name: 'type',
width: 180,
rules: [{ required: true, message: '请选择', trigger: ['blur', 'change'] }],
xType: 'select',
dic: [
{ label: '字符串', value: 'stringValue' },
{ label: '表达式', value: 'expression' }
]
},
{
label: '名称',
name: 'name',
width: 180,
rules: [{ required: true, message: '请选择', trigger: ['blur', 'change'] }],
xType: 'input'
},
{
label: '值',
name: 'value',
xType: 'input',
rules: [{ required: true, message: '请输入', trigger: ['blur', 'change'] }]
}
]
}
]
}
]
}
}
},
methods: {
closeDialog() {
this.$refs.xForm.validate().then(() => {
this.dialogVisible = false
}).catch(e => console.error(e))
}
}
}
</script>
<style>
.flow-containers .el-badge__content.is-fixed {
top: 18px;
}
</style>

133
ruoyi-ui/src/components/Process/components/nodePanel/property/multiInstance.vue

@ -1,133 +0,0 @@
<template>
<div>
<el-dialog
title="多实例配置"
:visible.sync="dialogVisible"
width="600px"
:close-on-click-modal="false"
:close-on-press-escape="false"
class="muti-instance"
@closed="$emit('close')"
>
<el-descriptions :column="1" size="mini" border>
<el-descriptions-item label="使用说明">按照BPMN2.0规范的要求用于为每个实例创建执行的父执行会提供下列变量:</el-descriptions-item>
<el-descriptions-item label="nrOfInstances">实例总数</el-descriptions-item>
<el-descriptions-item label="nrOfActiveInstances">当前活动的即未完成的实例数量对于顺序多实例这个值总为1</el-descriptions-item>
<el-descriptions-item label="nrOfCompletedInstances">已完成的实例数量</el-descriptions-item>
<el-descriptions-item label="loopCounter">给定实例在for-each循环中的index</el-descriptions-item>
</el-descriptions>
<div class="app-container">
<x-form ref="xForm" v-model="formData" :config="formConfig" />
</div>
</el-dialog>
</div>
</template>
<script>
import mixinPanel from '@/components/Process/common/mixinPanel'
import {formatJsonKeyValue} from '@/components/Process/common/parseElement'
export default {
mixins: [mixinPanel],
data() {
return {
dialogVisible: true,
formData: {},
prefix: 'flowable:',
}
},
computed: {
formConfig() {
const _this = this
return {
inline: false,
item: [
{
xType: 'input',
name: 'collection',
label: '集合',
tooltip: 'collection: 属性会作为表达式进行解析。如果表达式解析为字符串而不是一个集合,<br />不论是因为本身配置的就是静态字符串值,还是表达式计算结果为字符串,<br />这个字符串都会被当做变量名,并从流程变量中用于获取实际的集合。',
// rules: [{ required: true, message: '', trigger: ['blur', 'change'] }]
},
{
xType: 'input',
name: 'elementVariable',
label: '元素变量',
tooltip: 'elementVariable: 每创建一个用户任务前,先以该元素变量为label,集合中的一项为value,<br />创建(局部)流程变量,该局部流程变量被用于指派用户任务。<br />一般来说,该字符串应与指定人员变量相同。',
// rules: [{ required: true, message: '', trigger: ['blur', 'change'] }]
},
{
xType: 'select',
name: 'isSequential',
label: '执行方式',
tooltip: '并行会签(parallel)、串行会签(sequential),其中并行会签的意思是 多个人同时执行任务。串行会签是按顺序执行任务。',
dic: [{label: '串行', value: true}, {label: '并行', value: false}],
// rules: [{ required: true, message: '', trigger: ['blur', 'change'] }]
},
{
xType: 'input',
name: 'completionCondition',
label: '完成条件',
tooltip: 'completionCondition: 多实例活动在所有实例都完成时结束,然而也可以指定一个表达式,在每个实例<br />结束时进行计算。当表达式计算为true时,将销毁所有剩余的实例,并结束多实例<br />活动,继续执行流程。例如 ${nrOfCompletedInstances/nrOfInstances >= 0.6 },<br />表示当任务完成60%时,该节点就算完成',
// rules: [{ required: true, message: '', trigger: ['blur', 'change'] }]
}
],
operate: [
{ text: '确定', show: true, click: _this.save },
{ text: '清空', show: true, click: () => { _this.formData = {} } }
]
}
}
},
mounted() {
const cache = JSON.parse(JSON.stringify(this.element.businessObject.loopCharacteristics ?? {}))
cache.completionCondition = cache.completionCondition?.body
//
if (this.element.businessObject.loopCharacteristics) {
Object.assign(cache, this.element.businessObject.loopCharacteristics.$attrs)
}
this.formData = formatJsonKeyValue(cache)
},
methods: {
updateElement() {
if (this.formData.isSequential !== null && this.formData.isSequential !== undefined) {
// const model = this.modeler.get('moddle');
let loopCharacteristics = this.element.businessObject.get('loopCharacteristics')
if (!loopCharacteristics) {
loopCharacteristics = this.modeler.get('moddle').create('bpmn:MultiInstanceLoopCharacteristics')
}
loopCharacteristics['isSequential'] = this.formData.isSequential
loopCharacteristics['collection'] = this.formData.collection
loopCharacteristics['elementVariable'] = this.formData.elementVariable
// let loopCardinality = model.create("bpmn:Expression",{
// body: "4"
// });
// loopCharacteristics['loopCardinality'] = loopCardinality
loopCharacteristics.$attrs['isSequential'] = this.formData.isSequential
loopCharacteristics.$attrs[this.prefix + 'collection'] = this.formData.collection
loopCharacteristics.$attrs[this.prefix + 'elementVariable'] = this.formData.elementVariable
if (this.formData.completionCondition) {
loopCharacteristics['completionCondition'] = this.modeler.get('moddle').create('bpmn:Expression', {body: this.formData.completionCondition})
}
this.updateProperties({loopCharacteristics: loopCharacteristics})
} else {
delete this.element.businessObject.loopCharacteristics
}
},
save() {
this.$refs['xForm'].validate().then(() => {
this.updateElement()
this.dialogVisible = false
}).catch(e => console.error(e));
}
}
}
</script>
<style>
.muti-instance .el-form-item {
margin-bottom: 22px;
}
</style>

147
ruoyi-ui/src/components/Process/components/nodePanel/property/signal.vue

@ -1,147 +0,0 @@
<template>
<div>
<el-dialog
title="信号定义"
:visible.sync="dialogVisible"
width="700px"
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="false"
@closed="$emit('close')"
>
<x-form ref="xForm" v-model="formData" :config="formConfig" />
<span slot="footer" class="dialog-footer">
<el-button type="primary" size="medium" @click="closeDialog"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import mixinPanel from '../../../common/mixinPanel'
export default {
mixins: [mixinPanel],
data() {
return {
dialogVisible: true,
formData: {
signal: []
}
}
},
computed: {
formConfig() {
// const _this = this
return {
inline: false,
item: [
{
xType: 'tabs',
tabs: [
{
label: '信号定义',
name: 'signal',
column: [
{
label: 'scope',
name: 'scope',
width: 180,
rules: [{ required: true, message: '请选择', trigger: ['blur', 'change'] }],
xType: 'select',
dic: [
{ label: '全局', value: 'start' },
{ label: '流程实例', value: 'end' }
]
},
{
label: 'id',
name: 'id',
width: 200,
rules: [{ required: true, message: '请输入', trigger: ['blur', 'change'] }],
xType: 'input'
},
{
label: '名称',
name: 'name',
xType: 'input',
rules: [{ required: true, message: '请输入', trigger: ['blur', 'change'] }]
}
]
}
]
}
]
}
}
},
mounted() {
// this.formData.signal = this.element.businessObject.extensionElements?.values.map(item => {
this.formData.signal = this.element.businessObject.extensionElements?.values
.filter(item => item.$type === 'bpmn:Signal')
.map(item => {
return {
scope: item.scope,
id: item.id,
name: item.name
}
}) ?? []
},
methods: {
updateElement() {
// if (this.formData.signal?.length) {
// let extensionElements = this.element.businessObject.get('extensionElements')
// if (!extensionElements) {
// console.log(this.modeler.get('moddle'),"this.modeler.get('moddle')")
// extensionElements = this.modeler.get('moddle').create('bpmn:Signal')
// }
// extensionElements.values = extensionElements.values?.filter(item => item.$type !== 'bpmn:Signal') ?? []
// console.log(extensionElements,"extensionElements")
// const length = extensionElements.get('values').length
// for (let i = 0; i < length; i++) {
// //
// extensionElements.get('values').pop()
// }
// this.updateProperties({ extensionElements: extensionElements })
// } else {
// const extensionElements = this.element.businessObject[`extensionElements`]
// if (extensionElements) {
// extensionElements.values = extensionElements.values?.filter(item => item.$type !== 'flowable:ExecutionListener')
// }
// }
if (this.formData.signal?.length) {
let extensionElements = this.element.businessObject.get('extensionElements')
if (!extensionElements) {
extensionElements = this.modeler.get('moddle').create('bpmn:ExtensionElements')
}
//
extensionElements.values = extensionElements.values?.filter(item => item.$type !== 'bpmn:Signal') ?? []
this.formData.signal.forEach(item => {
const signal = this.modeler.get('moddle').create('bpmn:Signal')
signal['scope'] = item.scope
signal['id'] = item.id
signal['name'] = item.name
extensionElements.get('values').push(signal)
})
this.updateProperties({ extensionElements: extensionElements })
} else {
const extensionElements = this.element.businessObject[`extensionElements`]
if (extensionElements) {
extensionElements.values = extensionElements.values?.filter(item => item.$type !== 'bpmn:Signal') ?? []
}
}
},
closeDialog() {
this.$refs.xForm.validate().then(() => {
this.updateElement()
this.dialogVisible = false
}).catch(e => console.error(e))
}
}
}
</script>
<style>
.flow-containers .el-badge__content.is-fixed {
top: 18px;
}
</style>

221
ruoyi-ui/src/components/Process/components/nodePanel/property/taskListener.vue

@ -1,221 +0,0 @@
<template>
<div>
<el-dialog
title="任务监听器"
:visible.sync="dialogVisible"
width="900px"
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="false"
@closed="$emit('close')"
>
<x-form ref="xForm" v-model="formData" :config="formConfig">
<template #params="scope">
<el-badge :value="scope.row.params ? scope.row.params.length : 0" type="primary">
<el-button size="small" @click="configParam(scope.$index)">配置</el-button>
</el-badge>
</template>
</x-form>
<span slot="footer" class="dialog-footer">
<el-button type="primary" size="medium" @click="closeDialog"> </el-button>
</span>
<listener-list
:visible="listenerDialogVisible"
@close="() => this.listenerDialogVisible = false"
@submit="addListener"
/>
</el-dialog>
<listenerParam v-if="showParamDialog" :value="formData.taskListener[nowIndex].params" @close="finishConfigParam" />
</div>
</template>
<script>
import mixinPanel from '../../../common/mixinPanel'
import listenerParam from './listenerParam'
import ListenerList from '@/components/Process/components/nodePanel/property/listenerList'
export default {
components: { listenerParam, ListenerList },
mixins: [mixinPanel],
data() {
return {
dialogVisible: true,
listenerDialogVisible: false,
showParamDialog: false,
nowIndex: null,
formData: {
taskListener: []
}
}
},
computed: {
formConfig() {
// const _this = this
return {
inline: false,
item: [
{
xType: 'tabs',
tabs: [
{
label: '任务监听器',
name: 'taskListener',
column: [
{
label: '事件',
name: 'event',
width: 180,
rules: [{ required: true, message: '请选择', trigger: ['blur', 'change'] }],
xType: 'select',
dic: [
{ label: 'create', value: 'create' },
{ label: 'assignment', value: 'assignment' },
{ label: 'complete', value: 'complete' },
{ label: 'delete', value: 'delete' }
],
tooltip: `create(创建):当任务已经创建,并且所有任务参数都已经设置时触发。<br />
assignment指派当任务已经指派给某人时触发请注意当流程执行到达用户任务时在触发create事件之前会首先触发assignment事件<br />
complete完成当任务已经完成从运行时数据中删除前触发<br />
delete删除在任务即将被删除前触发请注意任务由completeTask正常完成时也会触发
`
},
{
label: '类型',
name: 'type',
width: 180,
rules: [{ required: true, message: '请选择', trigger: ['blur', 'change'] }],
xType: 'select',
dic: [
{ label: '类', value: 'class' },
{ label: '表达式', value: 'expression' },
{ label: '委托表达式', value: 'delegateExpression' }
],
tooltip: `类:示例 com.company.MyCustomListener,自定义类必须实现 org.flowable.engine.delegate.TaskListener 接口 <br />
表达式示例 \${myObject.callMethod(task, task.eventName)} <br />
委托表达式示例 \${myListenerSpringBean} springBean 需要实现 org.flowable.engine.delegate.TaskListener 接口
`
},
{
label: '值',
name: 'className',
xType: 'input',
rules: [{ required: true, message: '请输入', trigger: ['blur', 'change'] }]
},
// {
// xType: 'slot',
// label: '',
// width: 120,
// slot: true,
// name: 'params'
// }
]
}
]
}
]
}
}
},
mounted() {
this.$nextTick(() => this.addButton())
this.formData.taskListener = this.element.businessObject.extensionElements?.values
.filter(item => item.$type === 'flowable:TaskListener')
.map(item => {
let type
if ('class' in item) type = 'class'
if ('expression' in item) type = 'expression'
if ('delegateExpression' in item) type = 'delegateExpression'
return {
event: item.event,
type: type,
className: item[type],
params: item.fields?.map(field => {
let fieldType
if ('stringValue' in field) fieldType = 'stringValue'
if ('expression' in field) fieldType = 'expression'
return {
name: field.name,
type: fieldType,
value: field[fieldType]
}
}) ?? []
}
}) ?? []
},
methods: {
addButton() {
const button = document.createElement('button')
button.innerText = '内置监听器'
button.setAttribute('type', 'button')
button.setAttribute('class', 'el-button el-button--primary el-button--mini')
button.addEventListener('click', () => this.listenerDialogVisible = true)
const div = document.getElementById('pane-taskListener')
const table = div.getElementsByClassName('el-table')[0]
div.insertBefore(button, table)
},
configParam(index) {
this.nowIndex = index
const nowObj = this.formData.taskListener[index]
if (!nowObj.params) {
nowObj.params = []
}
this.showParamDialog = true
},
finishConfigParam(param) {
this.showParamDialog = false
// hack
const cache = this.formData.taskListener[this.nowIndex]
cache.params = param
this.$set(this.formData.taskListener[this.nowIndex], this.nowIndex, cache)
this.nowIndex = null
},
updateElement() {
if (this.formData.taskListener?.length) {
let extensionElements = this.element.businessObject.get('extensionElements')
if (!extensionElements) {
extensionElements = this.modeler.get('moddle').create('bpmn:ExtensionElements')
}
//
extensionElements.values = extensionElements.values?.filter(item => item.$type !== 'flowable:TaskListener') ?? []
this.formData.taskListener.forEach(item => {
const taskListener = this.modeler.get('moddle').create('flowable:TaskListener')
taskListener['event'] = item.event
taskListener[item.type] = item.className
if (item.params && item.params.length) {
item.params.forEach(field => {
const fieldElement = this.modeler.get('moddle').create('flowable:Field')
fieldElement['name'] = field.name
fieldElement[field.type] = field.value
// flowable.json stringexpressionStringhack
// const valueElement = this.modeler.get('moddle').create(`flowable:${field.type}`, { body: field.value })
// fieldElement[field.type] = valueElement
taskListener.get('fields').push(fieldElement)
})
}
extensionElements.get('values').push(taskListener)
})
this.updateProperties({ extensionElements: extensionElements })
} else {
const extensionElements = this.element.businessObject[`extensionElements`]
if (extensionElements) {
extensionElements.values = extensionElements.values?.filter(item => item.$type !== 'flowable:TaskListener') ?? []
}
}
},
closeDialog() {
this.$refs.xForm.validate().then(() => {
this.updateElement()
this.dialogVisible = false
}).catch(e => console.error(e))
},
addListener(data) {
this.formData.taskListener = this.formData.taskListener.concat(data)
}
}
}
</script>
<style>
.flow-containers .el-badge__content.is-fixed {
top: 18px;
}
</style>

105
ruoyi-ui/src/components/Process/components/nodePanel/sequenceFlow.vue

@ -1,105 +0,0 @@
<template>
<div>
<x-form ref="xForm" v-model="formData" :config="formConfig">
<template #executionListener>
<el-badge :value="executionListenerLength">
<el-button size="small" @click="dialogName = 'executionListenerDialog'">编辑</el-button>
</el-badge>
</template>
</x-form>
<executionListenerDialog
v-if="dialogName === 'executionListenerDialog'"
:element="element"
:modeler="modeler"
@close="finishExecutionListener"
/>
</div>
</template>
<script>
import mixinPanel from '../../common/mixinPanel'
import {StrUtil} from '@/utils/StrUtil'
import mixinExecutionListener from '../../common/mixinExecutionListener'
import { commonParse, conditionExpressionParse } from '../../common/parseElement'
export default {
mixins: [mixinPanel, mixinExecutionListener],
data() {
return {
formData: {},
executionListenerLength: 0
}
},
computed: {
formConfig() {
return {
inline: false,
item: [
{
xType: 'input',
name: 'id',
label: '节点 id',
rules: [{ required: true, message: 'Id 不能为空' }]
},
{
xType: 'input',
name: 'name',
label: '节点名称'
},
{
xType: 'input',
name: 'documentation',
label: '节点描述'
},
{
xType: 'slot',
name: 'executionListener',
label: '执行监听器'
},
{
xType: 'input',
name: 'conditionExpression',
label: '跳转条件'
},
{
xType: 'input',
name: 'skipExpression',
label: '跳过表达式'
}
]
}
}
},
watch: {
'formData.conditionExpression': function(val) {
if (StrUtil.isNotBlank(val)) {
const newCondition = this.modeler.get('moddle').create('bpmn:FormalExpression', { body: val })
this.updateProperties({ conditionExpression: newCondition })
}
// else {
// this.updateProperties({ conditionExpression: null })
// }
},
'formData.skipExpression': function(val) {
if (StrUtil.isNotBlank(val)) {
this.updateProperties({'flowable:skipExpression': val})
} else {
delete this.element.businessObject.$attrs[`flowable:skipExpression`]
}
}
},
created() {
let cache = commonParse(this.element)
cache = conditionExpressionParse(cache)
this.formData = cache;
this.computedExecutionListenerLength();
},
methods:{
computedExecutionListenerLength() {
this.executionListenerLength = this.element.businessObject.extensionElements?.values
?.filter(item => item.$type === 'flowable:ExecutionListener').length ?? 0
},
}
}
</script>
<style></style>

102
ruoyi-ui/src/components/Process/components/nodePanel/startEnd.vue

@ -1,102 +0,0 @@
<template>
<div>
<x-form ref="xForm" v-model="formData" :config="formConfig">
<template #executionListener>
<el-badge :value="executionListenerLength">
<el-button size="small" @click="dialogName = 'executionListenerDialog'">编辑</el-button>
</el-badge>
</template>
</x-form>
<executionListenerDialog
v-if="dialogName === 'executionListenerDialog'"
:element="element"
:modeler="modeler"
@close="finishExecutionListener"
/>
</div>
</template>
<script>
import mixinPanel from '../../common/mixinPanel'
import mixinExecutionListener from '../../common/mixinExecutionListener'
import { commonParse } from '../../common/parseElement'
export default {
mixins: [mixinPanel, mixinExecutionListener],
data() {
return {
formData: {},
executionListenerLength: 0
}
},
computed: {
formConfig() {
const _this = this
return {
inline: false,
item: [
{
xType: 'input',
name: 'id',
label: '节点 id',
rules: [{ required: true, message: 'Id 不能为空' }]
},
{
xType: 'input',
name: 'name',
label: '节点名称'
},
{
xType: 'input',
name: 'documentation',
label: '节点描述'
},
{
xType: 'slot',
name: 'executionListener',
label: '执行监听器'
},
// {
// xType: 'input',
// name: 'initiator',
// label: '',
// show: !!_this.showConfig.initiator
// },
// {
// xType: 'input',
// name: 'formKey',
// label: 'key',
// show: !!_this.showConfig.formKey
// }
]
}
}
},
watch: {
'formData.initiator': function(val) {
if (val === '') val = null
//
// if (val === '') val = 'INITIATOR'
this.updateProperties({ 'flowable:initiator': val })
},
'formData.formKey': function(val) {
if (val === '') val = null
this.updateProperties({ 'flowable:formKey': val })
}
},
created() {
// this.updateProperties({ 'flowable:initiator': 'INITIATOR' })
this.formData = commonParse(this.element)
this.computedExecutionListenerLength();
},
methods:{
computedExecutionListenerLength() {
this.executionListenerLength = this.element.businessObject.extensionElements?.values
?.filter(item => item.$type === 'flowable:ExecutionListener').length ?? 0
},
}
}
</script>
<style>
</style>

631
ruoyi-ui/src/components/Process/components/nodePanel/task.vue

@ -1,631 +0,0 @@
<template>
<div>
<x-form ref="xForm" v-model="formData" :config="formConfig">
<template #executionListener>
<el-badge :value="executionListenerLength">
<el-button size="small" @click="dialogName = 'executionListenerDialog'">编辑</el-button>
</el-badge>
</template>
<template #taskListener>
<el-badge :value="taskListenerLength">
<el-button size="small" @click="dialogName = 'taskListenerDialog'">编辑</el-button>
</el-badge>
</template>
<template #multiInstance>
<el-badge :is-dot="hasMultiInstance">
<el-button size="small" @click="dialogName = 'multiInstanceDialog'">编辑</el-button>
</el-badge>
</template>
<template #checkSingleUser>
<el-input placeholder="请选择人员" class="input-with-select" v-model="checkValues">
<template slot="append">
<!--指定用户-->
<el-button style="padding-left: 7px" icon="el-icon-user" @click="singleUserCheck"/>
<el-divider direction="vertical"></el-divider>
<!--选择表达式-->
<el-button style="padding-right: 7px" icon="el-icon-postcard" @click="singleExpCheck('assignee')"/>
</template>
</el-input>
</template>
<template #checkMultipleUser>
<el-input placeholder="请选择候选用户" class="input-with-select" v-model="checkValues">
<template slot="append">
<!--候选用户-->
<el-button style="padding-left: 7px" icon="el-icon-user" @click="multipleUserCheck"/>
<el-divider direction="vertical"></el-divider>
<!--选择表达式-->
<el-button style="padding-right: 7px" icon="el-icon-postcard" @click="singleExpCheck('candidateUsers')"/>
</template>
</el-input>
</template>
<template #checkRole>
<el-input placeholder="请选择候选角色" class="input-with-select" v-model="checkValues">
<template slot="append">
<!--候选角色-->
<el-button style="padding-left: 7px" icon="el-icon-user" @click="multipleRoleCheck"/>
<el-divider direction="vertical"></el-divider>
<!--选择表达式-->
<el-button style="padding-right: 7px" icon="el-icon-postcard" @click="singleExpCheck('candidateGroups')"/>
</template>
</el-input>
</template>
</x-form>
<executionListenerDialog
v-if="dialogName === 'executionListenerDialog'"
:element="element"
:modeler="modeler"
@close="finishExecutionListener"
/>
<taskListenerDialog
v-if="dialogName === 'taskListenerDialog'"
:element="element"
:modeler="modeler"
@close="finishTaskListener"
/>
<multiInstanceDialog
v-if="dialogName === 'multiInstanceDialog'"
:element="element"
:modeler="modeler"
@close="finishMultiInstance"
/>
<!--选择人员-->
<el-dialog
title="选择人员"
:visible.sync="userVisible"
width="60%"
:close-on-press-escape="false"
:show-close="false"
>
<flow-user :checkType="checkType" :selectValues="selectValues" @handleUserSelect="handleUserSelect"></flow-user>
<span slot="footer" class="dialog-footer">
<el-button @click="userVisible = false"> </el-button>
<el-button type="primary" @click="checkUserComplete"> </el-button>
</span>
</el-dialog>
<!--选择表达式-->
<el-dialog
title="选择表达式"
:visible.sync="expVisible"
width="60%"
:close-on-press-escape="false"
:show-close="false"
>
<flow-exp :selectValues="selectValues" @handleSingleExpSelect="handleSingleExpSelect"></flow-exp>
<span slot="footer" class="dialog-footer">
<el-button @click="expVisible = false"> </el-button>
<el-button type="primary" @click="checkExpComplete"> </el-button>
</span>
</el-dialog>
<!--选择角色-->
<el-dialog
title="选择候选角色"
:visible.sync="roleVisible"
width="60%"
:close-on-press-escape="false"
:show-close="false"
>
<flow-role :checkType="checkType" :selectValues="selectValues" @handleRoleSelect="handleRoleSelect"></flow-role>
<span slot="footer" class="dialog-footer">
<el-button @click="roleVisible = false"> </el-button>
<el-button type="primary" @click="checkRoleComplete"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import mixinPanel from '../../common/mixinPanel'
import executionListenerDialog from './property/executionListener'
import taskListenerDialog from './property/taskListener'
import multiInstanceDialog from './property/multiInstance'
import { commonParse, userTaskParse } from '../../common/parseElement'
import {StrUtil} from '@/utils/StrUtil'
import FlowUser from '@/components/flow/User'
import FlowRole from '@/components/flow/Role'
import FlowExp from '@/components/flow/Expression'
import { listAllForm } from '@/api/flowable/form'
export default {
components: {
executionListenerDialog,
taskListenerDialog,
multiInstanceDialog,
FlowUser,
FlowRole,
FlowExp,
},
mixins: [mixinPanel],
props: {
users: {
type: Array,
required: true
},
groups: {
type: Array,
required: true
},
exps: {
type: Array,
required: true
}
},
data() {
return {
userTypeOption: [
{ label: '指定人员', value: 'assignee' },
{ label: '候选人员', value: 'candidateUsers' },
{ label: '候选角色', value: 'candidateGroups' }
],
dialogName: '',
executionListenerLength: 0,
taskListenerLength: 0,
hasMultiInstance: false,
userVisible: false,
roleVisible: false,
expVisible: false,
formData: {},
assignee: null,
candidateUsers: null,
candidateGroups: null,
//
checkType: 'single',
//
checkValues: null,
//
selectValues: null,
//
userList: this.users,
//
groupList: this.groups,
//
expList: this.exps,
//
expType: null,
//
formList: [],
}
},
computed: {
formConfig() {
const _this = this
return {
inline: false,
item: [
{
xType: 'input',
name: 'id',
label: '节点 id',
rules: [{ required: true, message: 'Id 不能为空' }]
},
{
xType: 'input',
name: 'name',
label: '节点名称',
rules: [{ required: true, message: '节点名称不能为空' }]
},
{
xType: 'input',
name: 'documentation',
label: '节点描述'
},
{
xType: 'slot',
name: 'executionListener',
label: '执行监听器'
},
{
xType: 'slot',
name: 'taskListener',
label: '任务监听器',
show: !!_this.showConfig.taskListener
},
{
xType: 'select',
name: 'userType',
label: '用户类型',
// clearable: true,
dic: _this.userTypeOption,
// rules: [{ required: true, message: '' }],
show: !!_this.showConfig.userType
},
{
xType: 'slot',
name: 'checkSingleUser',
label: '指定人员',
// rules: [{ required: true, message: '' }],
show: !!_this.showConfig.assignee && _this.formData.userType === 'assignee'
},
{
xType: 'slot',
name: 'checkMultipleUser',
label: '候选人员',
// rules: [{ required: true, message: '' }],
show: !!_this.showConfig.candidateUsers && _this.formData.userType === 'candidateUsers'
},
{
xType: 'slot',
name: 'checkRole',
label: '候选角色',
// rules: [{ required: true, message: '' }],
show: !!_this.showConfig.candidateGroups && _this.formData.userType === 'candidateGroups'
},
{
xType: 'slot',
name: 'multiInstance',
label: '多实例'
},
{
xType: 'switch',
name: 'async',
label: '异步',
activeText: '是',
inactiveText: '否',
show: !!_this.showConfig.async
},
{
xType: 'input',
name: 'priority',
label: '优先级',
show: !!_this.showConfig.priority
},
// {
// xType: 'input',
// name: 'formKey',
// label: 'key',
// show: !!_this.showConfig.formKey
// },
{
xType: 'select',
name: 'formKey',
label: '表单标识key',
clearable: true,
dic: { data: _this.formList, label: 'formName', value: 'formId' },
show: !!_this.showConfig.formKey
},
{
xType: 'input',
name: 'skipExpression',
label: '跳过表达式',
show: !!_this.showConfig.skipExpression
},
{
xType: 'switch',
name: 'isForCompensation',
label: '是否为补偿',
activeText: '是',
inactiveText: '否',
show: !!_this.showConfig.isForCompensation
},
{
xType: 'switch',
name: 'triggerable',
label: '服务任务可触发',
activeText: '是',
inactiveText: '否',
show: !!_this.showConfig.triggerable
},
{
xType: 'switch',
name: 'autoStoreVariables',
label: '自动存储变量',
activeText: '是',
inactiveText: '否',
show: !!_this.showConfig.autoStoreVariables
},
{
xType: 'input',
name: 'ruleVariablesInput',
label: '输入变量',
show: !!_this.showConfig.ruleVariablesInput
},
{
xType: 'input',
name: 'rules',
label: '规则',
show: !!_this.showConfig.rules
},
{
xType: 'input',
name: 'resultVariable',
label: '结果变量',
show: !!_this.showConfig.resultVariable
},
{
xType: 'switch',
name: 'exclude',
label: '排除',
activeText: '是',
inactiveText: '否',
show: !!_this.showConfig.exclude
},
{
xType: 'input',
name: 'class',
label: '类',
show: !!_this.showConfig.class
},
{
xType: 'datePicker',
type: 'datetime',
valueFormat: 'yyyy-MM-ddTHH:mm:ss',
name: 'dueDate',
label: '到期时间',
show: !!_this.showConfig.dueDate
}
]
}
}
},
watch: {
'formData.userType': function(val, oldVal) {
if (StrUtil.isNotBlank(oldVal)) {
delete this.element.businessObject.$attrs[`flowable:${oldVal}`]
delete this.formData[oldVal]
//
this.checkValues = '';
this.selectValues = null;
// xml
delete this.element.businessObject.$attrs[`flowable:dataType`]
}
// userTypexml
this.updateProperties({'flowable:userType': val})
},
'formData.async': function(val) {
if (StrUtil.isNotBlank(val)) {
this.updateProperties({'flowable:async': val})
}
},
'formData.dueDate': function(val) {
if (StrUtil.isNotBlank(val)) {
this.updateProperties({'flowable:dueDate': val})
}
},
'formData.formKey': function(val) {
if (StrUtil.isNotBlank(val)) {
this.updateProperties({'flowable:formKey': val})
} else {
// xml
delete this.element.businessObject[`formKey`]
}
},
'formData.priority': function(val) {
if (StrUtil.isNotBlank(val)) {
this.updateProperties({'flowable:priority': val})
}
},
'formData.skipExpression': function(val) {
if (StrUtil.isNotBlank(val)) {
this.updateProperties({'flowable:skipExpression': val})
} else {
delete this.element.businessObject.$attrs[`flowable:skipExpression`]
}
},
'formData.isForCompensation': function(val) {
if (StrUtil.isNotBlank(val)) {
this.updateProperties({'isForCompensation': val})
}
},
'formData.triggerable': function(val) {
if (StrUtil.isNotBlank(val)) {
this.updateProperties({'flowable:triggerable': val})
}
},
'formData.class': function(val) {
if (StrUtil.isNotBlank(val)) {
this.updateProperties({'flowable:class': val})
}
},
'formData.autoStoreVariables': function(val) {
if (StrUtil.isNotBlank(val)) {
this.updateProperties({'flowable:autoStoreVariables': val})
}
},
'formData.exclude': function(val) {
if (StrUtil.isNotBlank(val)) {
this.updateProperties({'flowable:exclude': val})
}
},
'formData.ruleVariablesInput': function(val) {
if (StrUtil.isNotBlank(val)) {
this.updateProperties({'flowable:ruleVariablesInput': val})
}
},
'formData.rules': function(val) {
if (StrUtil.isNotBlank(val)) {
this.updateProperties({'flowable:rules': val})
}
},
'formData.resultVariable': function(val) {
if (StrUtil.isNotBlank(val)) {
this.updateProperties({'flowable:resultVariable': val})
}
}
},
created() {
let cache = commonParse(this.element)
cache = userTaskParse(cache)
this.formData = cache
this.computedExecutionListenerLength()
this.computedTaskListenerLength()
this.computedHasMultiInstance()
//
this.checkValuesEcho();
//
this.getListForm();
},
methods: {
computedExecutionListenerLength() {
this.executionListenerLength = this.element.businessObject.extensionElements?.values
?.filter(item => item.$type === 'flowable:ExecutionListener').length ?? 0
},
computedTaskListenerLength() {
this.taskListenerLength = this.element.businessObject.extensionElements?.values
?.filter(item => item.$type === 'flowable:TaskListener').length ?? 0
},
computedHasMultiInstance() {
if (this.element.businessObject.loopCharacteristics) {
this.hasMultiInstance = true
} else {
this.hasMultiInstance = false
}
},
//
getListForm(){
listAllForm().then(res =>{
res.data.forEach(item =>{
item.formId = item.formId.toString();
})
this.formList = res.data;
})
},
//
checkValuesEcho(){
const that = this;
const attrs = that.element.businessObject.$attrs;
const businessObject = that.element.businessObject;
console.log(that.element.businessObject,"this.element.businessObject")
//
if (attrs.hasOwnProperty("flowable:assignee")) {
const val = attrs["flowable:assignee"];
// ()
if (attrs["flowable:dataType"] === "dynamic") {
this.checkValues = that.expList.find(item => item.expression == val).name;
this.selectValues = that.expList.find(item => item.expression == val).id;
} else {
this.checkValues = that.userList.find(item => item.userId == val).nickName;
this.selectValues = that.userList.find(item => item.userId == val).userId;
}
//
} else if (attrs.hasOwnProperty("flowable:candidateUsers")) {
const val = attrs["flowable:candidateUsers"];
if (attrs["flowable:dataType"] === "dynamic") {
this.checkValues = that.expList.find(item => item.expression == val).name;
this.selectValues = that.expList.find(item => item.expression == val).id;
} else {
const newArr = that.userList.filter(i => val.split(',').includes(i.userId))
this.checkValues = newArr.map(item => item.nickName).join(',');
this.selectValues = newArr.map(item => item.userId).join(',');
}
} else if (businessObject.hasOwnProperty("candidateGroups") || attrs.hasOwnProperty("flowable:candidateGroups") ) {
//
const val = businessObject["candidateGroups"] || attrs["flowable:candidateGroups"];
if (attrs["flowable:dataType"] === "dynamic") {
this.checkValues = that.expList.find(item => item.expression == val).name;
this.selectValues = that.expList.find(item => item.expression == val).id;
} else {
const newArr = that.groupList.filter(i => val.split(',').includes(i.roleId))
this.checkValues = newArr.map(item => item.roleName).join(',');
this.selectValues = newArr.map(item => item.roleId).join(',');
}
}
},
finishExecutionListener() {
if (this.dialogName === 'executionListenerDialog') {
this.computedExecutionListenerLength()
}
this.dialogName = ''
},
finishTaskListener() {
if (this.dialogName === 'taskListenerDialog') {
this.computedTaskListenerLength()
}
this.dialogName = ''
},
finishMultiInstance() {
if (this.dialogName === 'multiInstanceDialog') {
this.computedHasMultiInstance()
}
this.dialogName = ''
},
/*单选人员*/
singleUserCheck(){
this.userVisible = true;
this.checkType = "single";
},
/*多选人员*/
multipleUserCheck(){
this.userVisible = true;
this.checkType = "multiple";
},
/*单选角色*/
singleRoleCheck(){
this.roleVisible = true;
this.checkType = "single";
},
/*多选角色*/
multipleRoleCheck(){
this.roleVisible = true;
this.checkType = "multiple";
},
/*单选表达式*/
singleExpCheck(expType){
this.expVisible = true;
this.expType = expType;
},
//
handleSingleExpSelect(selection) {
this.deleteFlowAttar();
this.updateProperties({'flowable:dataType': 'dynamic'})
if ("assignee" === this.expType) {
this.updateProperties({'flowable:assignee': selection.expression});
} else if ("candidateUsers" === this.expType) {
this.updateProperties({'flowable:candidateUsers': selection.expression});
} else if ("candidateGroups" === this.expType) {
this.updateProperties({'flowable:candidateGroups': selection.expression});
}
this.checkValues = selection.name;
this.expType = null;
},
//
handleUserSelect(selection) {
const that = this;
if (selection) {
that.deleteFlowAttar();
that.updateProperties({'flowable:dataType': 'fixed'})
if (selection instanceof Array) {
const userIds = selection.map(item => item.userId);
const nickName = selection.map(item => item.nickName);
that.updateProperties({'flowable:candidateUsers': userIds.join(',')})
that.checkValues = nickName.join(',');
} else {
that.updateProperties({'flowable:assignee': selection.userId})
that.checkValues = selection.nickName;
}
}
},
//
handleRoleSelect(selection, name) {
const that = this;
if (selection && name) {
that.deleteFlowAttar();
that.updateProperties({'flowable:dataType': 'fixed'})
that.updateProperties({'flowable:candidateGroups': selection});
that.checkValues = name;
}
},
/*用户选中赋值*/
checkUserComplete(){
this.userVisible = false;
this.checkType = "";
},
/*候选角色选中赋值*/
checkRoleComplete(){
this.roleVisible = false;
this.checkType = "";
},
/*表达式选中赋值*/
checkExpComplete(){
this.expVisible = false;
},
//
deleteFlowAttar(){
delete this.element.businessObject.$attrs[`flowable:dataType`]
delete this.element.businessObject.$attrs[`flowable:assignee`]
delete this.element.businessObject.$attrs[`flowable:candidateUsers`]
delete this.element.businessObject.$attrs[`flowable:candidateGroups`]
}
}
}
</script>
<style></style>

3
ruoyi-ui/src/components/Process/customPanel/CustomContextPad.js

@ -1,3 +1,6 @@
/**
* 自定义任务弹窗节点
*/
import {
assign,
forEach,

3
ruoyi-ui/src/components/Process/customPanel/CustomPalette.js

@ -1,3 +1,6 @@
/**
* 自定义左侧工具栏
*/
import { assign } from "min-dash";
export default function CustomPalette(

0
ruoyi-ui/src/components/Process/common/customTranslate.js → ruoyi-ui/src/components/Process/customPanel/customTranslate.js

0
ruoyi-ui/src/components/Process/customPanel/index.js

187
ruoyi-ui/src/components/Process/designer.vue

@ -0,0 +1,187 @@
<template>
<div>
<template slot="header">
<div class="card-header">
<span>{{ translateNodeName(elementType) }}</span>
</div>
</template>
<el-collapse v-model="activeName" >
<!-- 常规信息 -->
<el-collapse-item name="common">
<template slot="title"><i class="el-icon-info"></i> 常规信息</template>
<common-panel :id="elementId"/>
</el-collapse-item>
<!-- 任务信息 -->
<el-collapse-item name="Task" v-if="elementType.indexOf('Task') !== -1">
<template slot="title"><i class="el-icon-s-claim"></i> 任务配置</template>
<user-task-panel :id="elementId"/>
</el-collapse-item>
<!-- 表单 -->
<el-collapse-item name="form" v-if="formVisible">
<template slot="title"><i class="el-icon-s-order"></i> 表单配置</template>
<form-panel :id="elementId"/>
</el-collapse-item>
<!-- 执行监听器 -->
<el-collapse-item name="executionListener">
<template slot="title"><i class="el-icon-s-promotion"></i> 执行监听器
<el-badge :value="executionListenerCount" class="item" type="primary"/>
</template>
<execution-listener :id="elementId" @getExecutionListenerCount="getExecutionListenerCount"/>
</el-collapse-item>
<!-- 任务监听器 -->
<el-collapse-item name="taskListener" v-if="elementType === 'UserTask'" >
<template slot="title"><i class="el-icon-s-flag"></i> 任务监听器
<el-badge :value="taskListenerCount" class="item" type="primary"/>
</template>
<task-listener :id="elementId" @getTaskListenerCount="getTaskListenerCount"/>
</el-collapse-item>
<!-- 多实例 -->
<el-collapse-item name="multiInstance" v-if="elementType.indexOf('Task') !== -1" >
<template slot="title"><i class="el-icon-s-grid"></i> 多实例</template>
<multi-instance :id="elementId"/>
</el-collapse-item>
<!-- 流转条件 -->
<el-collapse-item name="condition" v-if="conditionVisible" >
<template slot="title"><i class="el-icon-share"></i> 流转条件</template>
<condition-panel :id="elementId"/>
</el-collapse-item>
<!-- 扩展属性 -->
<el-collapse-item name="properties" >
<template slot="title"><i class="el-icon-circle-plus"></i> 扩展属性</template>
<properties-panel :id="elementId"/>
</el-collapse-item>
</el-collapse>
</div>
</template>
<script>
import ExecutionListener from './panel/executionListener'
import TaskListener from './panel/taskListener'
import MultiInstance from './panel/multiInstance'
import CommonPanel from './panel/commonPanel'
import UserTaskPanel from './panel/taskPanel'
import ConditionPanel from './panel/conditionPanel'
import FormPanel from './panel/formPanel'
import OtherPanel from './panel/otherPanel'
import PropertiesPanel from './panel/PropertiesPanel'
import { translateNodeName } from "./common/bpmnUtils";
import FlowUser from "@/components/flow/User/index.vue";
import FlowRole from "@/components/flow/Role/index.vue";
import FlowExp from "@/components/flow/Expression/index.vue";
export default {
name: "Designer",
components: {
ExecutionListener,
TaskListener,
MultiInstance,
CommonPanel,
UserTaskPanel,
ConditionPanel,
FormPanel,
OtherPanel,
PropertiesPanel,
FlowUser,
FlowRole,
FlowExp,
},
data() {
return {
activeName : 'common',
executionListenerCount: 0,
taskListenerCount:0,
elementId:"",
elementType:"",
conditionVisible:false,//
formVisible:false, //
rules:{
id: [
{ required: true, message: '节点Id 不能为空', trigger: 'blur' },
],
name: [
{ required: true, message: '节点名称不能为空', trigger: 'blur' },
],
},
}
},
/** 传值监听 */
watch: {
elementId: {
handler() {
this.activeName = "common";
}
},
},
created() {
this.initModels();
},
methods: {
//
initModels() {
this.getActiveElement();
},
//
getActiveElement() {
// bpmn:Process
this.initFormOnChanged(null);
this.modelerStore.modeler.on("import.done", e => {
this.initFormOnChanged(null);
});
//
this.modelerStore.modeler.on("selection.changed", ({newSelection}) => {
this.initFormOnChanged(newSelection[0] || null);
});
this.modelerStore.modeler.on("element.changed", ({element}) => {
// ""
if (element && element.id === this.elementId) {
this.initFormOnChanged(element);
}
});
},
//
initFormOnChanged(element) {
let activatedElement = element;
if (!activatedElement) {
activatedElement =
this.modelerStore.elRegistry.find(el => el.type === "bpmn:Process") ??
this.modelerStore.elRegistry.find(el => el.type === "bpmn:Collaboration");
}
if (!activatedElement) return;
this.modelerStore.element = activatedElement;
this.elementId = activatedElement.id;
this.elementType = activatedElement.type.split(":")[1] || "";
this.conditionVisible = !!(
this.elementType === "SequenceFlow" &&
activatedElement.source &&
activatedElement.source.type.indexOf("StartEvent") === -1
);
this.formVisible = this.elementType === "UserTask" || this.elementType === "StartEvent";
},
/** 获取执行监听器数量 */
getExecutionListenerCount(value) {
this.executionListenerCount = value;
},
/** 获取任务监听器数量 */
getTaskListenerCount(value) {
this.taskListenerCount = value;
},
translateNodeName(val){
return translateNodeName(val);
}
}
}
</script>
<style lang="scss">
</style>

499
ruoyi-ui/src/components/Process/flowable/flowable.json

@ -90,56 +90,6 @@
}
]
},
{
"name": "flowable:in",
"superClass": ["Element"],
"properties": [
{
"name": "source",
"type": "string",
"isAttr": true
},
{
"name": "target",
"type": "string",
"isAttr": true
}
]
},
{
"name": "flowable:out",
"superClass": ["Element"],
"properties": [
{
"name": "source",
"type": "string",
"isAttr": true
},
{
"name": "target",
"type": "string",
"isAttr": true
}
]
},
{
"name": "BoundaryEvent",
"superClass": ["CatchEvent"],
"properties": [
{
"name": "cancelActivity",
"default": true,
"isAttr": true,
"type": "Boolean"
},
{
"name": "attachedToRef",
"type": "Activity",
"isAttr": true,
"isReference": true
}
]
},
{
"name": "JobPriorized",
"isAbstract": true,
@ -204,32 +154,6 @@
}
]
},
{
"name": "UserTask",
"isAbstract": true,
"extends": ["bpmn:UserTask"],
"properties": [
{
"name": "timerEventDefinition",
"type": "Expression"
},
{
"name": "multiInstanceLoopCharacteristics",
"type": "MultiInstanceLoopCharacteristics"
}
]
},
{
"name": "StartEvent",
"isAbstract": true,
"extends": ["bpmn:StartEvent"],
"properties": [
{
"name": "timerEventDefinition",
"type": "Expression"
}
]
},
{
"name": "FormSupported",
"isAbstract": true,
@ -244,6 +168,23 @@
"name": "formKey",
"isAttr": true,
"type": "String"
},
{
"name": "formType",
"isAttr": true,
"type": "String"
},
{
"name": "formReadOnly",
"isAttr": true,
"type": "Boolean",
"default": false
},
{
"name": "formInit",
"isAttr": true,
"type": "Boolean",
"default": true
}
]
},
@ -318,6 +259,11 @@
"isAttr": true,
"type": "Boolean",
"default": true
},
{
"name": "processCategory",
"isAttr": true,
"type": "String"
}
]
},
@ -349,6 +295,16 @@
"name": "Assignable",
"extends": ["bpmn:UserTask"],
"properties": [
{
"name": "assignee",
"isAttr": true,
"type": "String"
},
{
"name": "candidateUsers",
"isAttr": true,
"type": "String"
},
{
"name": "candidateGroups",
"isAttr": true,
@ -368,6 +324,40 @@
"name": "priority",
"isAttr": true,
"type": "String"
},
{
"name": "userType",
"isAttr": true,
"type": "String"
},
{
"name": "dataType",
"isAttr": true,
"type": "String"
},
{
"name": "expId",
"isAttr": true,
"type": "String"
}
]
},
{
"name": "Assignee",
"supperClass": "Element",
"meta": {
"allowedIn": ["*"]
},
"properties": [
{
"name": "label",
"type": "String",
"isAttr": true
},
{
"name": "viewId",
"type": "Number",
"isAttr": true
}
]
},
@ -460,18 +450,6 @@
}
]
},
{
"name": "ExclusiveGateway",
"isAbstract": true,
"extends": ["bpmn:ExclusiveGateway"],
"properties": [
{
"name": "serviceClass",
"isAttr": true,
"type": "String"
}
]
},
{
"name": "DmnCapable",
"extends": ["bpmn:BusinessRuleTask"],
@ -567,6 +545,100 @@
}
]
},
{
"name": "Buttons",
"superClass": ["Element"],
"meta": {
"allowedIn": ["*"]
},
"properties": [
{
"name": "values",
"type": "Button",
"isMany": true
}
]
},
{
"name": "Button",
"superClass": ["Element"],
"properties": [
{
"name": "id",
"type": "Integer",
"isAttr": true
},
{
"name": "label",
"type": "String",
"isAttr": true
},
{
"name": "code",
"type": "String",
"isAttr": true
},
{
"name": "icon",
"type": "String",
"isAttr": true
},
{
"name": "type",
"type": "String",
"isAttr": true
},
{
"name": "size",
"type": "String",
"isAttr": true
},
{
"name": "click",
"type": "String",
"isAttr": true
}
]
},
{
"name": "Assignee",
"superClass": ["Element"],
"meta": {
"allowedIn": ["bpmn:UserTask"]
},
"properties": [
{
"name": "id",
"type": "String",
"isAttr": true
},
{
"name": "type",
"type": "String",
"isAttr": true
},
{
"name": "value",
"type": "String",
"isAttr": true
},
{
"name": "condition",
"type": "String",
"isAttr": true
},
{
"name": "operationType",
"type": "String",
"isAttr": true
},
{
"name": "sort",
"type": "Integer",
"isAttr": true
}
]
},
{
"name": "Connector",
"superClass": ["Element"],
@ -724,7 +796,8 @@
"allowedIn": [
"flowable:ServiceTaskLike",
"flowable:ExecutionListener",
"flowable:TaskListener"
"flowable:TaskListener",
"bpmn:ServiceTask"
]
},
"properties": [
@ -735,175 +808,107 @@
},
{
"name": "expression",
"isAttr": true,
"type": "expression"
},
{
"name": "string",
"type": "string"
"type": "String"
},
{
"name": "stringValue",
"isAttr": true,
"type": "String"
}
]
},
{
"name": "string",
"superClass": ["Element"],
"meta": {
"allowedIn": [
"flowable:Field"
]
},
"properties": [
{
"name": "body",
"isBody": true,
"type": "String"
}
]
},
{
"name": "expression",
"superClass": ["Element"],
"meta": {
"allowedIn": [
"flowable:Field"
]
},
"properties": [
{
"name": "body",
"isBody": true,
"type": "String"
}
]
},
{
"name": "InputParameter",
"superClass": ["InputOutputParameter"]
},
{
"name": "OutputParameter",
"superClass": ["InputOutputParameter"]
},
{
"name": "Collectable",
"isAbstract": true,
"extends": ["bpmn:MultiInstanceLoopCharacteristics"],
"superClass": ["flowable:AsyncCapable"],
"properties": [
},
{
"name": "collection",
"isAttr": true,
"name": "string",
"type": "String"
},
{
"name": "elementVariable",
"isAttr": true,
"type": "String"
"name": "htmlVar",
"type": "Expression"
}
]
},
{
"name": "SequenceFlow",
"superClass": ["FlowElement"],
"name": "ChildField",
"superClass": ["Element"],
"properties": [
{
"name": "isImmediate",
"isAttr": true,
"type": "Boolean"
"name": "id",
"type": "String",
"isAttr": true
},
{
"name": "conditionExpression",
"type": "Expression"
"name": "name",
"type": "String",
"isAttr": true
},
{
"name": "sourceRef",
"type": "FlowNode",
"isAttr": true,
"isReference": true
"name": "type",
"type": "String",
"isAttr": true
},
{
"name": "targetRef",
"type": "FlowNode",
"isAttr": true,
"isReference": true
}
]
},
{
"name": "MultiInstanceLoopCharacteristics",
"superClass": ["LoopCharacteristics"],
"properties": [
{
"name": "isSequential",
"default": false,
"isAttr": true,
"type": "Boolean"
"name": "required",
"type": "String",
"isAttr": true
},
{
"name": "behavior",
"type": "MultiInstanceBehavior",
"default": "All",
"name": "readable",
"type": "String",
"isAttr": true
},
{
"name": "loopCardinality",
"type": "Expression",
"xml": {
"serialize": "xsi:type"
}
"name": "writable",
"type": "String",
"isAttr": true
},
{
"name": "loopDataInputRef",
"type": "ItemAwareElement",
"isReference": true
"name": "variable",
"type": "String",
"isAttr": true
},
{
"name": "loopDataOutputRef",
"type": "ItemAwareElement",
"isReference": true
"name": "expression",
"type": "String",
"isAttr": true
},
{
"name": "inputDataItem",
"type": "DataInput",
"xml": {
"serialize": "property"
}
"name": "datePattern",
"type": "String",
"isAttr": true
},
{
"name": "outputDataItem",
"type": "DataOutput",
"xml": {
"serialize": "property"
}
"name": "default",
"type": "String",
"isAttr": true
},
{
"name": "complexBehaviorDefinition",
"type": "ComplexBehaviorDefinition",
"name": "values",
"type": "Value",
"isMany": true
},
{
"name": "completionCondition",
"type": "Expression",
"xml": {
"serialize": "xsi:type"
}
},
}
]
},
{
"name": "InputParameter",
"superClass": ["InputOutputParameter"]
},
{
"name": "OutputParameter",
"superClass": ["InputOutputParameter"]
},
{
"name": "Collectable",
"isAbstract": true,
"extends": ["bpmn:MultiInstanceLoopCharacteristics"],
"superClass": ["flowable:AsyncCapable"],
"properties": [
{
"name": "oneBehaviorEventRef",
"type": "EventDefinition",
"name": "collection",
"isAttr": true,
"isReference": true
"type": "String"
},
{
"name": "noneBehaviorEventRef",
"type": "EventDefinition",
"name": "elementVariable",
"isAttr": true,
"isReference": true
"type": "String"
}
]
},
@ -911,7 +916,10 @@
"name": "FailedJobRetryTimeCycle",
"superClass": ["Element"],
"meta": {
"allowedIn": ["flowable:AsyncCapable", "bpmn:MultiInstanceLoopCharacteristics"]
"allowedIn": [
"flowable:AsyncCapable",
"bpmn:MultiInstanceLoopCharacteristics"
]
},
"properties": [
{
@ -945,8 +953,7 @@
"bpmn:BoundaryEvent",
"bpmn:CallActivity",
"bpmn:SubProcess",
"bpmn:Process",
"bpmn:Signal"
"bpmn:Process"
]
},
"properties": [
@ -960,6 +967,11 @@
"isAttr": true,
"type": "String"
},
{
"name": "listenerType",
"isAttr": true,
"type": "String"
},
{
"name": "delegateExpression",
"isAttr": true,
@ -1080,6 +1092,16 @@
"name": "values",
"type": "Value",
"isMany": true
},
{
"name": "children",
"type": "ChildField",
"isMany": true
},
{
"name": "extensionElements",
"type": "bpmn:ExtensionElements",
"isMany": true
}
]
},
@ -1189,6 +1211,45 @@
"type": "String"
}
]
},
{
"name": "Condition",
"superClass": ["Element"],
"meta": {
"allowedIn": ["bpmn:SequenceFlow"]
},
"properties": [
{
"name": "id",
"type": "String",
"isAttr": true
},
{
"name": "field",
"type": "String",
"isAttr": true
},
{
"name": "compare",
"type": "String",
"isAttr": true
},
{
"name": "value",
"type": "String",
"isAttr": true
},
{
"name": "logic",
"type": "String",
"isAttr": true
},
{
"name": "sort",
"type": "Integer",
"isAttr": true
}
]
}
],
"emumerations": []

0
ruoyi-ui/src/components/Process/flowable/init.js

51
ruoyi-ui/src/components/Process/flowable/showConfig.js

@ -1,51 +0,0 @@
export default {
'bpmn:EndEvent': {},
'bpmn:StartEvent': {
initiator: true,
formKey: true
},
'bpmn:UserTask': {
userType: true,
assignee: true,
candidateUsers: true,
candidateGroups: true,
async: true,
priority: true,
formKey: true,
skipExpression: true,
dueDate: true,
taskListener: true
},
'bpmn:ServiceTask': {
async: true,
skipExpression: true,
isForCompensation: true,
triggerable: true,
class: true
},
'bpmn:ScriptTask': {
async: true,
isForCompensation: true,
autoStoreVariables: true
},
'bpmn:ManualTask': {
async: true,
isForCompensation: true
},
'bpmn:ReceiveTask': {
async: true,
isForCompensation: true
},
'bpmn:SendTask': {
async: true,
isForCompensation: true
},
'bpmn:BusinessRuleTask': {
async: true,
isForCompensation: true,
ruleVariablesInput: true,
rules: true,
resultVariable: true,
exclude: true
}
}

5
ruoyi-ui/src/components/Process/index.js

@ -1,5 +0,0 @@
import workflowBpmnModeler from './index.vue'
workflowBpmnModeler.install = Vue => Vue.component(workflowBpmnModeler.name, workflowBpmnModeler) // 给组件配置install方法
export default workflowBpmnModeler

309
ruoyi-ui/src/components/Process/index.vue

@ -1,9 +1,9 @@
<template>
<div v-loading="isView" class="flow-containers" :class="{ 'view-mode': isView }">
<el-container style="height: 100%">
<el-header style="border-bottom: 1px solid rgb(218 218 218);height: auto;padding-left:0px">
<div style="display: flex; padding: 10px 0px; justify-content: space-between;">
<div>
<el-header style="border-bottom: 1px solid rgb(218 218 218);height: auto;padding-left:0">
<div style="display: flex; padding: 10px 0; justify-content: space-between;">
<el-button-group>
<el-upload action="" :before-upload="openBpmn" style="margin-right: 10px; display:inline-block;">
<el-tooltip effect="dark" content="加载xml" placement="bottom">
<el-button size="mini" icon="el-icon-folder-opened" />
@ -27,23 +27,32 @@
<el-tooltip effect="dark" content="前进" placement="bottom">
<el-button size="mini" icon="el-icon-right" @click="modeler.get('commandStack').redo()" />
</el-tooltip>
</div>
<div>
<!-- <el-button size="mini" icon="el-icon-s-check" @click="verifyXML">校验xml</el-button>-->
<!-- <el-button size="mini" icon="el-icon-share" @click="processSimulation">-->
<!-- {{ this.simulationStatus ? '退出模拟' : '开启模拟' }}-->
<!-- </el-button>-->
<!-- <el-button size="mini" icon="el-icon-first-aid-kit" @click="handlerIntegrityCheck">-->
<!-- {{ this.bpmnlintStatus ? '关闭检查' : '开启检查' }}-->
<!-- </el-button>-->
</el-button-group>
<el-button-group>
<el-button size="mini" icon="el-icon-view" @click="showXML">查看xml</el-button>
<el-button size="mini" icon="el-icon-download" @click="saveXML(true)">下载xml</el-button>
<el-button size="mini" icon="el-icon-picture" @click="saveImg('svg', true)">下载svg</el-button>
<el-button size="mini" type="primary" @click="save">保存模型</el-button>
</div>
<el-button size="mini" type="danger" @click="goBack">关闭</el-button>
</el-button-group>
</div>
</el-header>
<!-- 流程设计页面 -->
<el-container style="align-items: stretch">
<el-main style="padding: 0;">
<el-main>
<div ref="canvas" class="canvas" />
</el-main>
<el-aside style="width: 400px; min-height: 650px; background-color: #f0f2f5">
<panel v-if="modeler" :modeler="modeler" :users="users" :groups="groups" :exps="exps" :categorys="categorys" />
</el-aside>
<!--右侧属性栏-->
<el-card shadow="never" class="normalPanel">
<designer v-if="loadCanvas"></designer>
</el-card>
</el-container>
</el-container>
</div>
@ -51,42 +60,23 @@
<script>
//
import customTranslate from './common/customTranslate'
import lintModule from 'bpmn-js-bpmnlint';
import customTranslate from './customPanel/customTranslate'
import Modeler from 'bpmn-js/lib/Modeler'
// import bpmnlintConfig from './.bpmnlintrc';
import panel from './PropertyPanel'
import Designer from './designer'
import getInitStr from './flowable/init'
import {StrUtil} from '@/utils/StrUtil'
// flowable
import FlowableModule from './flowable/flowable.json'
import customControlsModule from './customPanel'
export default {
name: 'WorkflowBpmnModeler',
components: {
panel
},
props: {
name: "BpmnModel",
components: {Designer},
/** 组件传值 */
props : {
xml: {
type: String,
default: ''
},
users: {
type: Array,
default: () => []
},
groups: {
type: Array,
default: () => []
},
categorys: {
type: Array,
default: () => []
},
exps: {
type: Array,
default: () => []
},
isView: {
type: Boolean,
default: false
@ -95,105 +85,134 @@ export default {
data() {
return {
modeler: null,
zoom: 1
zoom: 1,
loadCanvas: false, // canvas
simulationStatus: false,
bpmnlintStatus: false,
simulation: true,
designer: true,
}
},
/** 传值监听 */
watch: {
xml: function(val) {
if (val) {
this.createNewDiagram(val)
}
}
xml: {
handler(newVal) {
if (StrUtil.isNotBlank(newVal)) {
this.createNewDiagram(newVal)
} else {
this.newDiagram()
}
},
immediate: true, //
},
},
computed: {
additionalModules() {
const Modules = [];
Modules.push(customControlsModule);
Modules.push({ //
translate: ['value', customTranslate]
});
return Modules;
},
},
mounted() {
//
this.modeler = new Modeler({
/** 创建bpmn 实例 */
const modeler = new Modeler({
container: this.$refs.canvas,
additionalModules: [
lintModule,
customControlsModule,
{ //
translate: ['value', customTranslate]
},
],
// ,,package.json "bpmnlint-plugin-local": "file:bpmnlint-plugin-local"
// linting: {
// bpmnlint: bpmnlintConfig
// },
additionalModules: this.additionalModules,
moddleExtensions: {
flowable: FlowableModule
}
},
keyboard: { bindTo: document },
})
//
if (!this.xml) {
this.modeler = modeler;
// modeler
this.modelerStore.modeler = modeler;
this.modelerStore.modeling = modeler.get("modeling");
this.modelerStore.moddle = modeler.get("moddle");
this.modelerStore.canvas = modeler.get("canvas");
this.modelerStore.bpmnFactory = modeler.get("bpmnFactory");
this.modelerStore.elRegistry = modeler.get("elementRegistry");
// ,
if (StrUtil.isBlank(this.xml)) {
this.newDiagram()
} else {
this.createNewDiagram(this.xml)
}
},
methods: {
//
newDiagram() {
this.createNewDiagram(getInitStr())
},
// xml
async createNewDiagram(data) {
//
// data = data.replace(/<!\[CDATA\[(.+?)]]>/g, '&lt;![CDATA[$1]]&gt;')
if (StrUtil.isNotBlank(this.modelerStore.modeler)) {
data = data.replace(/<!\[CDATA\[(.+?)]]>/g, function (match, str) {
return str.replace(/</g, '&lt;')
}
)
try {
await this.modelerStore.modeler.importXML(data)
this.fitViewport()
} catch (err) {
console.error(err.message, err.warnings)
}
}
},
//
fitViewport() {
this.zoom = this.modeler.get('canvas').zoom('fit-viewport')
this.zoom = this.modelerStore.canvas.zoom('fit-viewport')
const bbox = document.querySelector('.flow-containers .viewport').getBBox()
const currentViewbox = this.modeler.get('canvas').viewbox()
const currentViewBox = this.modelerStore.canvas.viewbox()
const elementMid = {
x: bbox.x + bbox.width / 2 - 65,
y: bbox.y + bbox.height / 2
}
this.modeler.get('canvas').viewbox({
x: elementMid.x - currentViewbox.width / 2,
y: elementMid.y - currentViewbox.height / 2,
width: currentViewbox.width,
height: currentViewbox.height
this.modelerStore.canvas.viewbox({
x: elementMid.x - currentViewBox.width / 2,
y: elementMid.y - currentViewBox.height / 2,
width: currentViewBox.width,
height: currentViewBox.height
})
this.zoom = bbox.width / currentViewbox.width * 1.8
this.zoom = bbox.width / currentViewBox.width * 1.8
this.loadCanvas = true;
},
//
zoomViewport(zoomIn = true) {
this.zoom = this.modeler.get('canvas').zoom()
this.zoom = this.modelerStore.canvas.zoom()
this.zoom += (zoomIn ? 0.1 : -0.1)
this.modeler.get('canvas').zoom(this.zoom)
this.modelerStore.canvas.zoom(this.zoom)
},
async createNewDiagram(data) {
//
// data = data.replace(/<!\[CDATA\[(.+?)]]>/g, '&lt;![CDATA[$1]]&gt;')
data = data.replace(/<!\[CDATA\[(.+?)]]>/g, function(match, str) {
return str.replace(/</g, '&lt;')
})
try {
await this.modeler.importXML(data)
// this.adjustPalette()
this.fitViewport()
} catch (err) {
console.error(err.message, err.warnings)
}
},
// api
//
getProcess() {
const element = this.getProcessElement()
return {
id: element.id,
name: element.name,
category: element.$attrs['flowable:processCategory']
category: element.processCategory
}
},
//
getProcessElement() {
const rootElements = this.modeler.getDefinitions().rootElements
const rootElements = this.modelerStore.modeler.getDefinitions().rootElements
for (let i = 0; i < rootElements.length; i++) {
if (rootElements[i].$type === 'bpmn:Process') return rootElements[i]
}
},
async verifyXML(){
const linting = this.modeler.get('linting')
linting.toggle();
},
// xml
async saveXML(download = false) {
try {
const { xml } = await this.modeler.saveXML({ format: true })
const {xml} = await this.modelerStore.modeler.saveXML({format: true})
if (download) {
this.downloadFile(`${this.getProcessElement().name}.bpmn20.xml`, xml, 'application/xml')
}
@ -202,17 +221,21 @@ export default {
console.log(err)
}
},
// 线xml
async showXML() {
try {
const xml = await this.saveXML()
this.$emit('showXML',xml)
const xmlStr = await this.saveXML()
this.$emit('showXML', xmlStr)
} catch (err) {
console.log(err)
}
},
// svg
async saveImg(type = 'svg', download = false) {
try {
const { svg } = await this.modeler.saveSVG({ format: true })
const {svg} = await this.modelerStore.modeler.saveSVG({format: true})
if (download) {
this.downloadFile(this.getProcessElement().name, svg, 'image/svg+xml')
}
@ -221,14 +244,19 @@ export default {
console.log(err)
}
},
//
async save() {
const process = this.getProcess()
const xml = await this.saveXML()
const svg = await this.saveImg()
const result = { process, xml, svg }
const result = {process, xml, svg}
this.$emit('save', result)
window.parent.postMessage(result, '*')
this.goBack();
},
//
openBpmn(file) {
const reader = new FileReader()
reader.readAsText(file, 'utf-8')
@ -237,6 +265,8 @@ export default {
}
return false
},
//
downloadFile(filename, data, type) {
const a = document.createElement('a');
const url = window.URL.createObjectURL(new Blob([data], {type: type}));
@ -245,6 +275,13 @@ export default {
a.click()
window.URL.revokeObjectURL(url)
},
/** 关闭当前标签页并返回上个页面 */
goBack() {
const obj = {path: "/flowable/definition", query: {t: Date.now()}};
this.$tab.closeOpenPage(obj);
this.toggleSideBar();
},
}
}
</script>
@ -267,20 +304,15 @@ export default {
display: none;
}
}
.flow-containers {
// background-color: #ffffff;
width: 100%;
height: 100%;
.canvas {
min-height: 850px;
width: 100%;
height: 100%;
//flex: 1;
//position: relative;
//background: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+")
//repeat !important;
//div.toggle-mode {
// display: none;
//}
background: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+")
}
.panel {
position: absolute;
@ -291,63 +323,30 @@ export default {
.load {
margin-right: 10px;
}
.el-form-item__label{
font-size: 13px;
.normalPanel {
width: 460px;
height: 100%;
padding: 20px 20px;
}
.djs-palette{
left: 0px!important;
top: 0px;
border-top: none;
.el-main {
position: relative;
padding: 0;
}
.djs-container svg {
//min-height: 650px;
.el-main .button-group {
display: flex;
flex-direction: column;
position: absolute;
width: auto;
height: auto;
top: 10px;
right: 10px;
}
.highlight.djs-shape .djs-visual > :nth-child(1) {
fill: green !important;
stroke: green !important;
fill-opacity: 0.2 !important;
}
.highlight.djs-shape .djs-visual > :nth-child(2) {
fill: green !important;
}
.highlight.djs-shape .djs-visual > path {
fill: green !important;
fill-opacity: 0.2 !important;
stroke: green !important;
}
.highlight.djs-connection > .djs-visual > path {
stroke: green !important;
}
// .djs-connection > .djs-visual > path {
// stroke: orange !important;
// stroke-dasharray: 4px !important;
// fill-opacity: 0.2 !important;
// }
// .djs-shape .djs-visual > :nth-child(1) {
// fill: orange !important;
// stroke: orange !important;
// stroke-dasharray: 4px !important;
// fill-opacity: 0.2 !important;
// }
.highlight-todo.djs-connection > .djs-visual > path {
stroke: orange !important;
stroke-dasharray: 4px !important;
fill-opacity: 0.2 !important;
}
.highlight-todo.djs-shape .djs-visual > :nth-child(1) {
fill: orange !important;
stroke: orange !important;
stroke-dasharray: 4px !important;
fill-opacity: 0.2 !important;
}
.overlays-div {
font-size: 10px;
color: red;
width: 100px;
top: -20px !important;
}
.button-group .el-button {
width: 100%;
margin: 0 0 5px;
}
}
</style>

85
ruoyi-ui/src/components/Process/lang/zh.js

@ -1,3 +1,4 @@
// https://github.com/bpmn-io/bpmn-js-i18n/blob/master/translations/zn.js
export default {
// Labels
'Activate the global connect tool': '激活全局连接工具',
@ -107,7 +108,7 @@ export default {
'out of bounds release': 'out of bounds release',
'more than {count} child lanes': '子道大于{count} ',
'element required': '元素不能为空',
'diagram not part of bpmn:Definitions': '流程图不符合bpmn规范',
'diagram not part of Definitions': '流程图不符合bpmn规范',
'no diagram to display': '没有可展示的流程图',
'no process or collaboration to display': '没有可展示的流程/协作',
'element {element} referenced by {referenced}#{property} not yet drawn': '由{referenced}#{property}引用的{element}元素仍未绘制',
@ -207,26 +208,70 @@ export default {
'Priority': '优先级',
'The follow up date as an EL expression (e.g. ${someDate} or an ISO date (e.g. 2015-06-26T09:54:00)': '跟踪日期必须符合EL表达式,如: ${someDate} ,或者一个ISO标准日期,如:2015-06-26T09:54:00',
'The due date as an EL expression (e.g. ${someDate} or an ISO date (e.g. 2015-06-26T09:54:00)': '跟踪日期必须符合EL表达式,如: ${someDate} ,或者一个ISO标准日期,如:2015-06-26T09:54:00',
'Variables': '变量'
'Variables': '变量',
// 流程校验器翻译字段
'{errors} Errors, {warnings} Warnings': '{errors} 错误, {warnings} 警告',
'Element is missing label/name': '元素未设置 label 标签',
'Sequence flow is missing condition': '检查从条件分叉网关或任务节点传出的序列流是否为默认流或具有附加条件',
'is missing end event': '检查每个流程范围内是否存在结束事件',
'Start event is missing event definition': '检查流程中是否具有开始事件',
'Incoming flows do not join': '检查事件或者任务节点是否具有隐式的流转规则,默认事件和任务只能有一个流入条件',
'Element has disallowed type': '元素具有不允许的类型',
'Element is missing bpmndi': '检查可见的元素是否都具有对应的 DI 标签',
'Element is not connected': '检查节点是否正常连接',
'SequenceFlow is a duplicate': 'SequenceFlow重复',
'Duplicate outgoing sequence flows': '重复的传出序列流',
'Duplicate incoming sequence flows': '重复的传入序列流',
'Gateway forks and joins': '检查网关是否同时有多个流入和流出规则',
'Flow splits implicitly': '检查网关或者任务节点是否有多个流出路径且没有配置条件',
'has multiple blank start events': '检查流程内是否有多个开始事件',
'Event has multiple event definitions': '验证事件是否包含有超过一个事件定义的规则',
'is missing start event': '检查流程内是否存在开始事件',
'Process is missing end event': '检查流程内是否存在结束事件',
'Start event must be blank': '检查子流程的开始事件是否具有启动条件',
'Gateway is superfluous. It only has one source and target.': '检查网关是否同时只有一个流入和流出路径,否则需要移除',
}
//
// export const NodeName = {
// 'bpmn:Process': '流程',
// 'bpmn:StartEvent': '开始事件',
// 'bpmn:IntermediateThrowEvent': '中间事件',
// 'bpmn:Task': '任务',
// 'bpmn:SendTask': '发送任务',
// 'bpmn:ReceiveTask': '接收任务',
// 'bpmn:UserTask': '用户任务',
// 'bpmn:ManualTask': '手工任务',
// 'bpmn:BusinessRuleTask': '业务规则任务',
// 'bpmn:ServiceTask': '服务任务',
// 'bpmn:ScriptTask': '脚本任务',
// 'bpmn:EndEvent': '结束事件',
// 'bpmn:SequenceFlow': '流程线',
// 'bpmn:ExclusiveGateway': '互斥网关',
// 'bpmn:ParallelGateway': '并行网关',
// 'bpmn:InclusiveGateway': '相容网关',
// 'bpmn:ComplexGateway': '复杂网关',
// 'bpmn:EventBasedGateway': '事件网关'
// }
export const NodeName = {
'bpmn:Process': '流程',
'bpmn:StartEvent': '开始事件',
'bpmn:IntermediateThrowEvent': '中间事件',
'bpmn:Task': '任务',
'bpmn:SendTask': '发送任务',
'bpmn:ReceiveTask': '接收任务',
'bpmn:UserTask': '用户任务',
'bpmn:ManualTask': '手工任务',
'bpmn:BusinessRuleTask': '业务规则任务',
'bpmn:ServiceTask': '服务任务',
'bpmn:ScriptTask': '脚本任务',
'bpmn:EndEvent': '结束事件',
'bpmn:SequenceFlow': '流程线',
'bpmn:ExclusiveGateway': '互斥网关',
'bpmn:ParallelGateway': '并行网关',
'bpmn:InclusiveGateway': '相容网关',
'bpmn:ComplexGateway': '复杂网关',
'bpmn:EventBasedGateway': '事件网关'
'Process': '流程',
'StartEvent': '开始事件',
'IntermediateThrowEvent': '中间事件',
'Task': '任务',
'SendTask': '发送任务',
'ReceiveTask': '接收任务',
'UserTask': '用户任务',
'ManualTask': '手工任务',
'BusinessRuleTask': '业务规则任务',
'ServiceTask': '服务任务',
'ScriptTask': '脚本任务',
'EndEvent': '结束事件',
'SequenceFlow': '流程线',
'ExclusiveGateway': '互斥网关',
'ParallelGateway': '并行网关',
'InclusiveGateway': '相容网关',
'ComplexGateway': '复杂网关',
'EventBasedGateway': '事件网关'
}

140
ruoyi-ui/src/components/Process/panel/ButtonsPanel.vue

@ -0,0 +1,140 @@
<template>
<div class="panel-tab__content">
<el-divider content-position="center">按钮设置</el-divider>
<el-table :data="elementButtonList" size="mini" max-height="240" border fit>
<el-table-column label="序号" width="50px" type="index" />
<el-table-column label="属性名" prop="name" min-width="100px" show-overflow-tooltip />
<el-table-column label="属性值" prop="value" min-width="100px" show-overflow-tooltip />
<el-table-column label="操作" width="90px">
<template slot-scope="{ row, $index }">
<el-button size="mini" type="text" @click="openAttributesForm(row, $index)">编辑</el-button>
<el-divider direction="vertical" />
<el-button size="mini" type="text" style="color: #ff4d4f" @click="removeAttributes(row, $index)">移除</el-button>
</template>
</el-table-column>
</el-table>
<div class="element-drawer__button_save">
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openAttributesForm(null, -1)">添加按钮</el-button>
</div>
<el-dialog :visible.sync="buttonFormModelVisible" title="按钮配置" width="600px" append-to-body destroy-on-close>
<el-form :model="buttonForm" label-width="80px" size="mini" ref="attributeFormRef" @submit.native.prevent>
<el-form-item label="属性名:" prop="label">
<el-input v-model="buttonForm.label" clearable />
</el-form-item>
<el-form-item label="属性值:" prop="value">
<el-input v-model="buttonForm.value" clearable />
</el-form-item>
</el-form>
<template slot="footer">
<el-button size="mini" @click="buttonFormModelVisible = false"> </el-button>
<el-button size="mini" type="primary" @click="saveAttribute"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import {StrUtil} from "@/utils/StrUtil";
export default {
name: "ButtonsPanel",
props: {
id: {
type: String,
required: true
},
},
data() {
return {
elementButtonList: [],
otherExtensionList: [],
buttonForm: {},
editingPropertyIndex: -1,
buttonFormModelVisible: false
};
},
watch: {
id: {
immediate: true,
handler(val) {
if (StrUtil.isNotBlank(val)) {
this.resetAttributesList();
}
}
}
},
methods: {
resetAttributesList() {
this.bpmnElement = this.modelerStore.element;
this.otherExtensionList = []; //
this.bpmnElementProperties =
this.bpmnElement.businessObject?.extensionElements?.values?.filter(ex => {
if (ex.$type !== `flowable:Buttons`) {
this.otherExtensionList.push(ex);
}
return ex.$type === `flowable:Buttons`;
}) ?? [];
//
this.bpmnElementButtonList = this.bpmnElementProperties.reduce((pre, current) => pre.concat(current.values), []);
//
this.elementButtonList = JSON.parse(JSON.stringify(this.bpmnElementButtonList ?? []));
},
openAttributesForm(attr, index) {
this.editingPropertyIndex = index;
this.buttonForm = index === -1 ? {} : JSON.parse(JSON.stringify(attr));
this.buttonFormModelVisible = true;
this.$nextTick(() => {
if (this.$refs["attributeFormRef"]) this.$refs["attributeFormRef"].clearValidate();
});
},
removeAttributes(attr, index) {
this.$confirm("确认移除该属性吗?", "提示", {
confirmButtonText: "确 认",
cancelButtonText: "取 消"
})
.then(() => {
this.elementButtonList.splice(index, 1);
this.bpmnElementButtonList.splice(index, 1);
//
const propertiesObject = this.modelerStore.moddle.create(`flowable:Properties`, {
values: this.bpmnElementButtonList
});
this.updateElementExtensions(propertiesObject);
this.resetAttributesList();
})
.catch(() => console.info("操作取消"));
},
saveAttribute() {
const { name, value } = this.buttonForm;
console.log(this.bpmnElementButtonList);
if (this.editingPropertyIndex !== -1) {
this.modelerStore.modeling.updateModdleProperties(this.bpmnElement, this.bpmnElementButtonList[this.editingPropertyIndex], {
name,
value
});
} else {
//
const newPropertyObject = this.modelerStore.moddle.create(`flowable:Button`, { name, value });
//
const propertiesObject = this.modelerStore.moddle.create(`flowable:Buttons`, {
values: this.bpmnElementButtonList.concat([newPropertyObject])
});
this.updateElementExtensions(propertiesObject);
}
this.buttonFormModelVisible = false;
this.resetAttributesList();
},
updateElementExtensions(properties) {
const extensions = this.modelerStore.moddle.create("bpmn:ExtensionElements", {
values: this.otherExtensionList.concat([properties])
});
this.modelerStore.modeling.updateProperties(this.bpmnElement, {
extensionElements: extensions
});
}
}
};
</script>

139
ruoyi-ui/src/components/Process/panel/PropertiesPanel.vue

@ -0,0 +1,139 @@
<template>
<div class="panel-tab__content">
<el-table :data="elementPropertyList" size="mini" max-height="240" border fit>
<el-table-column label="序号" width="50px" type="index" />
<el-table-column label="属性名" prop="name" min-width="100px" show-overflow-tooltip />
<el-table-column label="属性值" prop="value" min-width="100px" show-overflow-tooltip />
<el-table-column label="操作" width="90px">
<template slot-scope="{ row, $index }">
<el-button size="mini" type="text" @click="openAttributesForm(row, $index)">编辑</el-button>
<el-divider direction="vertical" />
<el-button size="mini" type="text" style="color: #ff4d4f" @click="removeAttributes(row, $index)">移除</el-button>
</template>
</el-table-column>
</el-table>
<div class="element-drawer__button">
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openAttributesForm(null, -1)">添加属性</el-button>
</div>
<el-dialog :visible.sync="propertyFormModelVisible" title="属性配置" width="600px" append-to-body destroy-on-close>
<el-form :model="propertyForm" label-width="80px" size="mini" ref="attributeFormRef" @submit.native.prevent>
<el-form-item label="属性名:" prop="name">
<el-input v-model="propertyForm.name" clearable />
</el-form-item>
<el-form-item label="属性值:" prop="value">
<el-input v-model="propertyForm.value" clearable />
</el-form-item>
</el-form>
<template slot="footer">
<el-button size="mini" @click="propertyFormModelVisible = false"> </el-button>
<el-button size="mini" type="primary" @click="saveAttribute"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import {StrUtil} from "@/utils/StrUtil";
export default {
name: "PropertiesPanel",
props: {
id: {
type: String,
required: true
},
},
data() {
return {
elementPropertyList: [],
otherExtensionList: [],
propertyForm: {},
editingPropertyIndex: -1,
propertyFormModelVisible: false
};
},
watch: {
id: {
immediate: true,
handler(val) {
if (StrUtil.isNotBlank(val)) {
this.resetAttributesList();
}
}
}
},
methods: {
resetAttributesList() {
this.bpmnElement = this.modelerStore.element;
this.otherExtensionList = []; //
this.bpmnElementProperties =
this.bpmnElement.businessObject?.extensionElements?.values?.filter(ex => {
if (ex.$type !== `flowable:Properties`) {
this.otherExtensionList.push(ex);
}
return ex.$type === `flowable:Properties`;
}) ?? [];
//
this.bpmnElementPropertyList = this.bpmnElementProperties.reduce((pre, current) => pre.concat(current.values), []);
//
this.elementPropertyList = JSON.parse(JSON.stringify(this.bpmnElementPropertyList ?? []));
},
openAttributesForm(attr, index) {
this.editingPropertyIndex = index;
this.propertyForm = index === -1 ? {} : JSON.parse(JSON.stringify(attr));
this.propertyFormModelVisible = true;
this.$nextTick(() => {
if (this.$refs["attributeFormRef"]) this.$refs["attributeFormRef"].clearValidate();
});
},
removeAttributes(attr, index) {
this.$confirm("确认移除该属性吗?", "提示", {
confirmButtonText: "确 认",
cancelButtonText: "取 消"
})
.then(() => {
this.elementPropertyList.splice(index, 1);
this.bpmnElementPropertyList.splice(index, 1);
//
const propertiesObject = this.modelerStore.moddle.create(`flowable:Properties`, {
values: this.bpmnElementPropertyList
});
this.updateElementExtensions(propertiesObject);
this.resetAttributesList();
})
.catch(() => console.info("操作取消"));
},
saveAttribute() {
const { name, value } = this.propertyForm;
console.log(this.bpmnElementPropertyList);
if (this.editingPropertyIndex !== -1) {
this.modelerStore.modeling.updateModdleProperties(this.bpmnElement, this.bpmnElementPropertyList[this.editingPropertyIndex], {
name,
value
});
} else {
//
const newPropertyObject = this.modelerStore.moddle.create(`flowable:Property`, { name, value });
//
const propertiesObject = this.modelerStore.moddle.create(`flowable:Properties`, {
values: this.bpmnElementPropertyList.concat([newPropertyObject])
});
this.updateElementExtensions(propertiesObject);
}
this.propertyFormModelVisible = false;
this.resetAttributesList();
},
updateElementExtensions(properties) {
const extensions = this.modelerStore.moddle.create("bpmn:ExtensionElements", {
values: this.otherExtensionList.concat([properties])
});
this.modelerStore.modeling.updateProperties(this.bpmnElement, {
extensionElements: extensions
});
}
}
};
</script>

87
ruoyi-ui/src/components/Process/panel/commonPanel.vue

@ -0,0 +1,87 @@
<template>
<div>
<el-form :model="bpmnFormData" label-width="80px" :rules="rules" size="small">
<el-form-item :label="bpmnFormData.$type === 'bpmn:Process'? '流程标识': '节点ID'" prop="id" @change="updateElementTask('id')">
<el-input v-model="bpmnFormData.id"/>
</el-form-item>
<el-form-item :label="bpmnFormData.$type === 'bpmn:Process'? '流程名称': '节点名称'" prop="name">
<el-input v-model="bpmnFormData.name" @change="updateElementTask('name')"/>
</el-form-item>
<!--流程的基础属性-->
<template v-if="bpmnFormData.$type === 'bpmn:Process'">
<el-form-item label="流程分类" prop="processCategory">
<el-select v-model="bpmnFormData.processCategory" placeholder="请选择流程分类" @change="updateElementTask('processCategory')">
<el-option
v-for="dict in dict.type.sys_process_category"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</el-form-item>
</template>
<el-form-item v-if="bpmnFormData.$type === 'bpmn:SubProcess'" label="状态">
<el-switch v-model="bpmnFormData.isExpanded" active-text="展开" inactive-text="折叠" @change="updateElementTask('isExpanded')" />
</el-form-item>
<el-form-item label="节点描述">
<el-input :rows="2" type="textarea" v-model="bpmnFormData.documentation" @change="updateElementTask('documentation')"/>
</el-form-item>
</el-form>
</div>
</template>
<script>
import {StrUtil} from '@/utils/StrUtil'
export default {
name: "CommonPanel",
dicts: ['sys_process_category'],
/** 组件传值 */
props : {
id: {
type: String,
required: true
},
},
data() {
return {
rules:{
id: [
{ required: true, message: '节点Id 不能为空', trigger: 'blur' },
],
name: [
{ required: true, message: '节点名称不能为空', trigger: 'blur' },
],
},
bpmnFormData: {}
}
},
/** 传值监听 */
watch: {
id: {
handler(newVal) {
if (StrUtil.isNotBlank(newVal)) {
this.resetTaskForm();
}
},
immediate: true, //
},
},
created() {
},
methods: {
resetTaskForm() {
this.bpmnFormData = JSON.parse(JSON.stringify(this.modelerStore.element.businessObject));
},
updateElementTask(key) {
const taskAttr = Object.create(null);
taskAttr[key] = this.bpmnFormData[key] || null;
this.modelerStore.modeling.updateProperties(this.modelerStore.element, taskAttr);
}
}
}
</script>

175
ruoyi-ui/src/components/Process/panel/conditionPanel.vue

@ -0,0 +1,175 @@
<template>
<div>
<el-form label-width="100px" size="small" @submit.native.prevent>
<el-form-item>
<template slot="label">
<span>
流转类型
<el-tooltip placement="top">
<template slot="content">
<div>
普通流转路径流程执行过程中一个元素被访问后会沿着其所有出口顺序流继续执行
<br />默认流转路径只有当没有其他顺序流可以选择时才会选择默认顺序流作为活动的出口顺序流流程会忽略默认顺序流上的条件
<br />条件流转路径是计算其每个出口顺序流上的条件当条件计算为true时选择该出口顺序流如果该方法选择了多条顺序流则会生成多个执行流程会以并行方式继续
</div>
</template>
<i class="el-icon-question" />
</el-tooltip>
</span>
</template>
<el-select v-model="bpmnFormData.type" @change="updateFlowType">
<el-option label="普通流转路径" value="normal" />
<el-option label="默认流转路径" value="default" />
<el-option label="条件流转路径" value="condition" />
</el-select>
</el-form-item>
<el-form-item label="条件格式" v-if="bpmnFormData.type === 'condition'" key="condition">
<el-select v-model="bpmnFormData.conditionType">
<el-option label="表达式" value="expression" />
<el-option label="脚本" value="script" />
</el-select>
</el-form-item>
<el-form-item label="表达式" v-if="bpmnFormData.conditionType && bpmnFormData.conditionType === 'expression'" key="express">
<el-input v-model="bpmnFormData.body" clearable @change="updateFlowCondition" />
</el-form-item>
<template v-if="bpmnFormData.conditionType && bpmnFormData.conditionType === 'script'">
<el-form-item label="脚本语言" key="language">
<el-input v-model="bpmnFormData.language" clearable @change="updateFlowCondition" />
</el-form-item>
<el-form-item label="脚本类型" key="scriptType">
<el-select v-model="bpmnFormData.scriptType">
<el-option label="内联脚本" value="inlineScript" />
<el-option label="外部脚本" value="externalScript" />
</el-select>
</el-form-item>
<el-form-item label="脚本" v-if="bpmnFormData.scriptType === 'inlineScript'" key="body">
<el-input v-model="bpmnFormData.body" type="textarea" clearable @change="updateFlowCondition" />
</el-form-item>
<el-form-item label="资源地址" v-if="bpmnFormData.scriptType === 'externalScript'" key="resource">
<el-input v-model="bpmnFormData.resource" clearable @change="updateFlowCondition" />
</el-form-item>
</template>
</el-form>
</div>
</template>
<script>
import {StrUtil} from "@/utils/StrUtil";
export default {
name: "BpmnModel",
/** 组件传值 */
props : {
id: {
type: String,
required: true
},
},
data() {
return {
bpmnElementSource: {},
bpmnElementSourceRef: {},
bpmnFormData: {}
}
},
/** 传值监听 */
watch: {
id: {
handler(newVal) {
if (StrUtil.isNotBlank(newVal)) {
this.resetFlowCondition();
}
},
immediate: true, //
},
},
created() {
},
methods: {
//
resetFlowCondition() {
this.bpmnFormData = {
body: null
};
this.bpmnElementSource = this.modelerStore.element.source;
this.bpmnElementSourceRef = this.modelerStore.element.businessObject.sourceRef;
if (this.bpmnElementSourceRef && this.bpmnElementSourceRef.default && this.bpmnElementSourceRef.default.id === this.modelerStore.element.id) {
//
this.$set(this.bpmnFormData, "type", "default");
} else if (!this.modelerStore.element.businessObject.conditionExpression) {
//
this.$set(this.bpmnFormData, "type", "normal");
} else {
//
const conditionExpression = this.modelerStore.element.businessObject.conditionExpression;
this.bpmnFormData = {...conditionExpression, type: "condition"};
// resource
if (this.bpmnFormData.resource) {
this.$set(this.bpmnFormData, "conditionType", "script");
this.$set(this.bpmnFormData, "scriptType", "externalScript");
return;
}
if (conditionExpression.language) {
this.$set(this.bpmnFormData, "conditionType", "script");
this.$set(this.bpmnFormData, "scriptType", "inlineScript");
return;
}
this.$set(this.bpmnFormData, "conditionType", "expression");
}
},
updateFlowType(flowType) {
//
if (flowType === "condition") {
const flowConditionRef = this.modelerStore.moddle.create("bpmn:FormalExpression");
this.modelerStore.modeling.updateProperties(this.modelerStore.element, {
conditionExpression: flowConditionRef
});
return;
}
//
if (flowType === "default") {
this.modelerStore.modeling.updateProperties(this.modelerStore.element, {
conditionExpression: null
});
this.modelerStore.modeling.updateProperties(this.bpmnElementSource, {
default: this.modelerStore.element
});
//
this.bpmnFormData.conditionType = null;
return;
}
//
this.bpmnFormData.conditionType = null;
// 线
if (this.bpmnElementSourceRef.default && this.bpmnElementSourceRef.default.id === this.modelerStore.element.id) {
this.modelerStore.modeling.updateProperties(this.bpmnElementSource, {
default: null
});
}
this.modelerStore.modeling.updateProperties(this.modelerStore.element, {
conditionExpression: null
});
},
updateFlowCondition() {
let {conditionType, scriptType, body, resource, language} = this.bpmnFormData;
let condition;
if (conditionType === "expression") {
condition = this.modelerStore.moddle.create("bpmn:FormalExpression", {body});
} else {
if (scriptType === "inlineScript") {
condition = this.modelerStore.moddle.create("bpmn:FormalExpression", {body, language});
this.$set(this.bpmnFormData, "resource", "");
} else {
this.$set(this.bpmnFormData, "body", "");
condition = this.modelerStore.moddle.create("bpmn:FormalExpression", {resource, language});
}
}
this.modelerStore.modeling.updateProperties(this.modelerStore.element, {conditionExpression: condition});
}
}
}
</script>

472
ruoyi-ui/src/components/Process/panel/executionListener.vue

@ -0,0 +1,472 @@
<template>
<div class="panel-tab__content">
<el-table :data="elementListenersList" size="mini" border>
<el-table-column label="序号" width="50px" type="index" />
<el-table-column label="类型" width="60px" prop="event" />
<el-table-column label="监听类型" width="80px" show-overflow-tooltip :formatter="row => listenerTypeObject[row.listenerType]" />
<el-table-column label="操作">
<template slot-scope="scope">
<el-button size="mini" type="primary" @click="openListenerForm(scope.row, scope.$index)">编辑</el-button>
<el-divider direction="vertical" />
<el-button size="mini" type="danger" @click="removeListener(scope.row, scope.$index)">移除</el-button>
</template>
</el-table-column>
</el-table>
<div class="element-drawer__button_save">
<el-button type="primary" icon="el-icon-plus" size="small" @click="listenerSystemVisible = true">内置监听器</el-button>
<el-button type="primary" icon="el-icon-plus" size="small" @click="openListenerForm(null)" >自定义监听器</el-button>
</div>
<!-- 监听器 编辑/创建 部分 -->
<el-drawer :visible.sync="listenerFormModelVisible" title="执行监听器" size="480px" append-to-body destroy-on-close>
<el-form :model="listenerForm" size="small" label-width="96px" ref="listenerFormRef" @submit.native.prevent>
<el-form-item label="事件类型" prop="event" :rules="{ required: true, trigger: ['blur', 'change'] }">
<el-select v-model="listenerForm.event">
<el-option label="start" value="start" />
<el-option label="end" value="end" />
</el-select>
</el-form-item>
<el-form-item label="监听器类型" prop="listenerType" :rules="{ required: true, trigger: ['blur', 'change'] }">
<el-select v-model="listenerForm.listenerType">
<el-option v-for="i in Object.keys(listenerTypeObject)" :key="i" :label="listenerTypeObject[i]" :value="i" />
</el-select>
</el-form-item>
<el-form-item
v-if="listenerForm.listenerType === 'classListener'"
label="Java类"
prop="class"
key="listener-class"
:rules="{ required: true, trigger: ['blur', 'change'] }"
>
<el-input v-model="listenerForm.class" clearable />
</el-form-item>
<el-form-item
v-if="listenerForm.listenerType === 'expressionListener'"
label="表达式"
prop="expression"
key="listener-expression"
:rules="{ required: true, trigger: ['blur', 'change'] }"
>
<el-input v-model="listenerForm.expression" clearable />
</el-form-item>
<el-form-item
v-if="listenerForm.listenerType === 'delegateExpressionListener'"
label="代理表达式"
prop="delegateExpression"
key="listener-delegate"
:rules="{ required: true, trigger: ['blur', 'change'] }"
>
<el-input v-model="listenerForm.delegateExpression" clearable />
</el-form-item>
<template v-if="listenerForm.listenerType === 'scriptListener'">
<el-form-item
label="脚本格式"
prop="scriptFormat"
key="listener-script-format"
:rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本格式' }"
>
<el-input v-model="listenerForm.scriptFormat" clearable />
</el-form-item>
<el-form-item
label="脚本类型"
prop="scriptType"
key="listener-script-type"
:rules="{ required: true, trigger: ['blur', 'change'], message: '请选择脚本类型' }"
>
<el-select v-model="listenerForm.scriptType">
<el-option label="内联脚本" value="inlineScript" />
<el-option label="外部脚本" value="externalScript" />
</el-select>
</el-form-item>
<el-form-item
v-if="listenerForm.scriptType === 'inlineScript'"
label="脚本内容"
prop="value"
key="listener-script"
:rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本内容' }"
>
<el-input v-model="this.listenerForm" clearable />
</el-form-item>
<el-form-item
v-if="listenerForm.scriptType === 'externalScript'"
label="资源地址"
prop="resource"
key="listener-resource"
:rules="{ required: true, trigger: ['blur', 'change'], message: '请填写资源地址' }"
>
<el-input v-model="listenerForm.resource" clearable />
</el-form-item>
</template>
</el-form>
<el-divider />
<p class="listener-filed__title">
<span><el-icon><BellFilled /></el-icon></span>
<el-button type="primary" size="mini" @click="openListenerFieldForm(null)">添加字段</el-button>
</p>
<el-table :data="fieldsListOfListener" size="mini" max-height="240" border fit style="flex: none">
<el-table-column label="序号" width="50px" type="index" />
<el-table-column label="字段名称" width="80px" prop="name" />
<el-table-column label="字段类型" width="80px" show-overflow-tooltip :formatter="row => fieldTypeObject[row.fieldType]" />
<el-table-column label="值内容" width="80px" show-overflow-tooltip :formatter="row => row.string || row.expression" />
<el-table-column label="操作">
<template slot-scope="scope">
<el-button size="mini" type="primary" @click="openListenerFieldForm(scope.row, scope.$index)">编辑</el-button>
<el-divider direction="vertical" />
<el-button size="mini" type="danger" @click="removeListenerField(scope.row, scope.$index)">移除</el-button>
</template>
</el-table-column>
</el-table>
<div class="element-drawer__button">
<el-button size="small" @click="listenerFormModelVisible = false"> </el-button>
<el-button size="small" type="primary" @click="saveListenerConfig"> </el-button>
</div>
</el-drawer>
<!-- 注入西段 编辑/创建 部分 -->
<el-dialog title="字段配置" :visible.sync="listenerFieldFormModelVisible" width="600px" append-to-body destroy-on-close>
<el-form :model="listenerFieldForm" label-width="96px" ref="listenerFieldFormRef" style="height: 136px" @submit.native.prevent>
<el-form-item label="字段名称:" prop="name" :rules="{ required: true, trigger: ['blur', 'change'] }">
<el-input v-model="listenerFieldForm.name" clearable />
</el-form-item>
<el-form-item label="字段类型:" prop="fieldType" :rules="{ required: true, trigger: ['blur', 'change'] }">
<el-select v-model="listenerFieldForm.fieldType">
<el-option v-for="i in Object.keys(fieldTypeObject)" :key="i" :label="fieldTypeObject[i]" :value="i" />
</el-select>
</el-form-item>
<el-form-item
v-if="listenerFieldForm.fieldType === 'string'"
label="字段值:"
prop="string"
key="field-string"
:rules="{ required: true, trigger: ['blur', 'change'] }"
>
<el-input v-model="listenerFieldForm.string" clearable />
</el-form-item>
<el-form-item
v-if="listenerFieldForm.fieldType === 'expression'"
label="表达式:"
prop="expression"
key="field-expression"
:rules="{ required: true, trigger: ['blur', 'change'] }"
>
<el-input v-model="listenerFieldForm.expression" clearable />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button size="small" @click="listenerFieldFormModelVisible= false"> </el-button>
<el-button size="small" type="primary" @click="saveListenerFiled"> </el-button>
</div>
</el-dialog>
<!-- 内置监听器 -->
<el-drawer :visible.sync="listenerSystemVisible" title="执行监听器" size="580px" append-to-body destroy-on-close>
<el-table v-loading="loading" :data="listenerList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="名称" align="center" prop="name" />
<el-table-column label="类型" align="center" prop="eventType"/>
<el-table-column label="监听类型" align="center" prop="valueType">
<template slot-scope="scope">
<dict-tag :options="dict.type.sys_listener_value_type" :value="scope.row.valueType"/>
</template>
</el-table-column>
<el-table-column label="执行内容" align="center" prop="value" :show-overflow-tooltip="true"/>
</el-table>
<pagination
v-show="total>0"
:total="total"
layout="prev, pager, next"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<div class="element-drawer__button">
<el-button size="small" @click="listenerSystemVisible = false"> </el-button>
<el-button size="small" type="primary" :disabled="listenerSystemChecked" @click="saveSystemListener"> </el-button>
</div>
</el-drawer>
</div>
</template>
<script>
import { listListener } from "@/api/flowable/listener";
import {
changeListenerObject,
createListenerObject,
createSystemListenerObject,
updateElementExtensions
} from "../common/bpmnUtils";
import {StrUtil} from "@/utils/StrUtil";
export default {
name: "ExecutionListener",
//
dicts: ['sys_listener_value_type', 'sys_listener_event_type'],
/** 组件传值 */
props : {
id: {
type: String,
required: true
},
},
data() {
return {
elementListenersList: [], //
listenerForm: {},//
listenerFormModelVisible: false, //
fieldsListOfListener: [],
bpmnElementListeners: [],
otherExtensionList: [],
listenerFieldForm: {}, //
listenerFieldFormModelVisible: false, //
editingListenerIndex: -1, // -1
editingListenerFieldIndex: -1, // -1
listenerList: [],
checkedListenerData: [],
listenerSystemVisible: false,
listenerSystemChecked: true,
loading: true,
total: 0,
listenerTypeObject: {
classListener: "Java 类",
expressionListener: "表达式",
delegateExpressionListener: "代理表达式",
scriptListener: "脚本"
},
eventType: {
create: "创建",
assignment: "指派",
complete: "完成",
delete: "删除",
update: "更新",
timeout: "超时"
},
fieldTypeObject: {
string: "字符串",
expression: "表达式"
},
queryParams: {
pageNum: 1,
pageSize: 10,
type: 2,
},
}
},
/** 传值监听 */
watch: {
id: {
handler(newVal) {
if (StrUtil.isNotBlank(newVal)) {
this.resetListenersList();
}
},
immediate: true, //
},
},
created() {
this.getList();
},
methods: {
resetListenersList() {
this.bpmnElementListeners =
this.modelerStore.element.businessObject?.extensionElements?.values?.filter(ex => ex.$type === `flowable:ExecutionListener`) ?? [];
this.elementListenersList = this.bpmnElementListeners.map(listener => this.initListenerType(listener));
this.$emit('getExecutionListenerCount', this.elementListenersList.length)
},
//
openListenerForm(listener, index) {
if (listener) {
this.listenerForm = this.initListenerForm(listener);
this.editingListenerIndex = index;
} else {
this.listenerForm = {};
this.editingListenerIndex = -1; //
}
if (listener && listener.fields) {
this.fieldsListOfListener = listener.fields.map(field => ({
...field,
fieldType: field.string ? "string" : "expression"
}));
} else {
this.fieldsListOfListener = [];
this.$set(this.listenerForm, "fields", []);
}
//
this.listenerFormModelVisible = true;
this.$nextTick(() => {
if (this.$refs["listenerFormRef"]) this.$refs["listenerFormRef"].clearValidate();
});
},
//
openListenerFieldForm(field, index) {
this.listenerFieldForm = field ? JSON.parse(JSON.stringify(field)) : {};
this.editingListenerFieldIndex = field ? index : -1;
this.listenerFieldFormModelVisible = true;
this.$nextTick(() => {
if (this.$refs["listenerFieldFormRef"]) this.$refs["listenerFieldFormRef"].clearValidate();
});
},
//
async saveListenerFiled() {
let validateStatus = await this.$refs["listenerFieldFormRef"].validate();
if (!validateStatus) return; //
if (this.editingListenerFieldIndex === -1) {
this.fieldsListOfListener.push(this.listenerFieldForm);
this.listenerForm.fields.push(this.listenerFieldForm);
} else {
this.fieldsListOfListener.splice(this.editingListenerFieldIndex, 1, this.listenerFieldForm);
this.listenerForm.fields.splice(this.editingListenerFieldIndex, 1, this.listenerFieldForm);
}
this.listenerFieldFormModelVisible = false;
this.$nextTick(() => (this.listenerFieldForm = {}));
},
//
removeListenerField(field, index) {
this.$confirm("确认移除该字段吗?", "提示", {
confirmButtonText: "确 认",
cancelButtonText: "取 消"
}).then(() => {
this.fieldsListOfListener.splice(index, 1);
this.listenerForm.fields.splice(index, 1);
}).catch(() => console.info("操作取消"));
},
//
removeListener(listener, index) {
this.$confirm("确认移除该监听器吗?", "提示", {
confirmButtonText: "确 认",
cancelButtonText: "取 消"
}).then(() => {
this.bpmnElementListeners.splice(index, 1);
this.elementListenersList.splice(index, 1);
updateElementExtensions(this.modelerStore.moddle, this.modelerStore.modeling, this.modelerStore.element, this.otherExtensionList.concat(this.bpmnElementListeners));
this.$emit('getExecutionListenerCount', this.elementListenersList.length)
}).catch((r) => console.info(r, "操作取消"));
},
//
async saveListenerConfig() {
let validateStatus = await this.$refs["listenerFormRef"].validate();
if (!validateStatus) return; //
const listenerObject = createListenerObject(this.modelerStore.moddle, this.listenerForm, false, "flowable");
if (this.editingListenerIndex === -1) {
this.bpmnElementListeners.push(listenerObject);
this.elementListenersList.push(this.listenerForm);
} else {
this.bpmnElementListeners.splice(this.editingListenerIndex, 1, listenerObject);
this.elementListenersList.splice(this.editingListenerIndex, 1, this.listenerForm);
}
//
this.otherExtensionList = this.modelerStore.element.businessObject?.extensionElements?.values?.filter(ex => ex.$type !== `flowable:ExecutionListener`) ?? [];
updateElementExtensions(this.modelerStore.moddle, this.modelerStore.modeling, this.modelerStore.element, this.otherExtensionList.concat(this.bpmnElementListeners));
this.$emit('getExecutionListenerCount', this.elementListenersList.length)
// 4.
this.listenerFormModelVisible = false;
this.listenerForm = {};
},
initListenerType(listener) {
let listenerType;
if (listener.class) listenerType = "classListener";
if (listener.expression) listenerType = "expressionListener";
if (listener.delegateExpression) listenerType = "delegateExpressionListener";
if (listener.script) listenerType = "scriptListener";
return {
...JSON.parse(JSON.stringify(listener)),
...(listener.script ?? {}),
listenerType: listenerType
};
},
//
initListenerForm(listener) {
let self = {
...listener
};
if (listener.script) {
self = {
...listener,
...listener.script,
scriptType: listener.script.resource ? "externalScript" : "inlineScript"
};
}
if (listener.event === "timeout" && listener.eventDefinitions) {
if (listener.eventDefinitions.length) {
let k = "";
for (let key in listener.eventDefinitions[0]) {
console.log(listener.eventDefinitions, key);
if (key.indexOf("time") !== -1) {
k = key;
self.eventDefinitionType = key.replace("time", "").toLowerCase();
}
}
console.log(k);
self.eventTimeDefinitions = listener.eventDefinitions[0][k].body;
}
}
return self;
},
/** 查询流程达式列表 */
getList() {
this.loading = true;
listListener(this.queryParams).then(response => {
this.listenerList = response.rows;
this.total = response.total;
this.loading = false;
});
},
//
handleSelectionChange(selection) {
// ids.value = selection.map(item => item.id);
// TODO 使 push?
this.checkedListenerData = selection;
this.listenerSystemChecked = selection.length !== 1;
},
//
saveSystemListener() {
if (this.checkedListenerData.length > 0) {
this.checkedListenerData.forEach(value => {
//
const listenerObject = createSystemListenerObject(this.modelerStore.moddle, value, false, "flowable");
this.bpmnElementListeners.push(listenerObject);
this.elementListenersList.push(changeListenerObject(value));
this.otherExtensionList = this.modelerStore.element.businessObject?.extensionElements?.values?.filter(ex => ex.$type !== `flowable:TaskListener`) ?? [];
updateElementExtensions(this.modelerStore.moddle, this.modelerStore.modeling, this.modelerStore.element, this.otherExtensionList.concat(this.bpmnElementListeners));
})
//
this.$emit('getExecutionListenerCount', this.elementListenersList.length)
}
this.checkedListenerData = [];
this.listenerSystemChecked = true;
//
this.listenerSystemVisible = false;
}
}
}
</script>
<style lang="scss">
@import '../style/process-panel';
.flow-containers .el-badge__content.is-fixed {
top: 18px;
}
.dialog-footer button:first-child {
margin-right: 10px;
}
</style>

82
ruoyi-ui/src/components/Process/panel/formPanel.vue

@ -0,0 +1,82 @@
<template>
<div>
<el-form label-width="80px" size="small" @submit.native.prevent>
<el-form-item label="流程表单">
<el-select v-model="bpmnFormData.formKey" clearable class="m-2" placeholder="挂载节点表单" @change="updateElementFormKey">
<el-option
v-for="item in formList"
:key="item.value"
:label="item.formName"
:value="item.formId"
/>
</el-select>
</el-form-item>
</el-form>
</div>
</template>
<script>
import { listAllForm } from '@/api/flowable/form'
import {StrUtil} from "@/utils/StrUtil";
export default {
name: "FormPanel",
/** 组件传值 */
props : {
id: {
type: String,
required: true
},
},
data() {
return {
formList: [], //
bpmnFormData: {}
}
},
/** 传值监听 */
watch: {
id: {
handler(newVal) {
if (StrUtil.isNotBlank(newVal)) {
//
this.getListForm();
this.resetFlowForm();
}
},
immediate: true, //
},
},
created() {
},
methods: {
//
resetFlowForm() {
this.bpmnFormData.formKey = this.modelerStore.element.businessObject.formKey;
},
updateElementFormKey(val) {
if (StrUtil.isBlank(val)) {
delete this.modelerStore.element.businessObject[`formKey`]
} else {
this.modelerStore.modeling.updateProperties(this.modelerStore.element, {'formKey': val});
}
},
//
getListForm() {
listAllForm().then(res => {
res.data.forEach(item => {
item.formId = item.formId.toString();
})
this.formList = res.data;
})
}
}
}
</script>

236
ruoyi-ui/src/components/Process/panel/multiInstance.vue

@ -0,0 +1,236 @@
<template>
<div class="panel-tab__content">
<el-form label-width="70px" @submit.native.prevent size="small">
<el-form-item label="参数说明">
<el-button size="small" type="primary" @click="dialogVisible = true">查看</el-button>
</el-form-item>
<el-form-item label="回路特性">
<el-select v-model="loopCharacteristics" @change="changeLoopCharacteristicsType">
<!--bpmn:MultiInstanceLoopCharacteristics-->
<el-option label="并行多重事件" value="ParallelMultiInstance" />
<el-option label="时序多重事件" value="SequentialMultiInstance" />
<!--bpmn:StandardLoopCharacteristics-->
<el-option label="循环事件" value="StandardLoop" />
<el-option label="无" value="Null" />
</el-select>
</el-form-item>
<template v-if="loopCharacteristics === 'ParallelMultiInstance' || loopCharacteristics === 'SequentialMultiInstance'">
<el-form-item label="循环基数" key="loopCardinality">
<el-input v-model="loopInstanceForm.loopCardinality" clearable @change="updateLoopCardinality" />
</el-form-item>
<el-form-item label="集合" key="collection">
<el-input v-model="loopInstanceForm.collection" clearable @change="updateLoopBase" />
</el-form-item>
<el-form-item label="元素变量" key="elementVariable">
<el-input v-model="loopInstanceForm.elementVariable" clearable @change="updateLoopBase" />
</el-form-item>
<el-form-item label="完成条件" key="completionCondition">
<el-input v-model="loopInstanceForm.completionCondition" clearable @change="updateLoopCondition" />
</el-form-item>
<el-form-item label="异步状态" key="async">
<el-checkbox v-model="loopInstanceForm.asyncBefore" label="异步前" @change="updateLoopAsync('asyncBefore')" />
<el-checkbox v-model="loopInstanceForm.asyncAfter" label="异步后" @change="updateLoopAsync('asyncAfter')" />
<el-checkbox
v-model="loopInstanceForm.exclusive"
v-if="loopInstanceForm.asyncAfter || loopInstanceForm.asyncBefore"
label="排除"
@change="updateLoopAsync('exclusive')"
/>
</el-form-item>
<el-form-item label="重试周期" prop="timeCycle" v-if="loopInstanceForm.asyncAfter || loopInstanceForm.asyncBefore" key="timeCycle">
<el-input v-model="loopInstanceForm.timeCycle" clearable @change="updateLoopTimeCycle" />
</el-form-item>
</template>
</el-form>
<!-- 参数说明 -->
<el-dialog title="多实例参数" :visible.sync="dialogVisible" width="680px" @closed="$emit('close')">
<el-descriptions :column="1" border>
<el-descriptions-item label="使用说明">按照BPMN2.0规范的要求用于为每个实例创建执行的父执行会提供下列变量:</el-descriptions-item>
<el-descriptions-item label="collection(集合变量)">传入List参数, 一般为用户ID集合</el-descriptions-item>
<el-descriptions-item label="elementVariable(元素变量)">List中单个参数的名称</el-descriptions-item>
<el-descriptions-item label="loopCardinality(基数)">List循环次数</el-descriptions-item>
<el-descriptions-item label="isSequential(串并行)">Parallel: 并行多实例Sequential: 串行多实例</el-descriptions-item>
<el-descriptions-item label="completionCondition(完成条件)">任务出口条件</el-descriptions-item>
<el-descriptions-item label="nrOfInstances(实例总数)">实例总数</el-descriptions-item>
<el-descriptions-item label="nrOfActiveInstances(未完成数)">当前活动的即未完成的实例数量对于顺序多实例这个值总为1</el-descriptions-item>
<el-descriptions-item label="nrOfCompletedInstances(已完成数)">已完成的实例数量</el-descriptions-item>
<el-descriptions-item label="loopCounter">给定实例在for-each循环中的index</el-descriptions-item>
</el-descriptions>
</el-dialog>
</div>
</template>
<script>
import {StrUtil} from '@/utils/StrUtil'
export default {
name: "MultiInstance",
/** 组件传值 */
props: {
id: {
type: String,
required: true
},
},
data() {
return {
dialogVisible: false,
loopCharacteristics: "",
loopInstanceForm: {},
multiLoopInstance: {},
defaultLoopInstanceForm: {
completionCondition: "",
loopCardinality: "",
extensionElements: [],
asyncAfter: false,
asyncBefore: false,
exclusive: false
},
}
},
/** 传值监听 */
watch: {
id: {
handler(newVal) {
if (StrUtil.isNotBlank(newVal)) {
this.getElementLoop(this.modelerStore.element.businessObject); }
},
immediate: true, //
},
},
created() {
},
methods: {
//
getElementLoop(businessObject) {
if (!businessObject.loopCharacteristics) {
this.loopCharacteristics = "Null";
this.loopInstanceForm = {};
return;
}
if (businessObject.loopCharacteristics.$type === "bpmn:StandardLoopCharacteristics") {
this.loopCharacteristics = "StandardLoop";
this.loopInstanceForm = {};
return;
}
if (businessObject.loopCharacteristics.isSequential) {
this.loopCharacteristics = "SequentialMultiInstance";
} else {
this.loopCharacteristics = "ParallelMultiInstance";
}
//
this.loopInstanceForm = {
...this.defaultLoopInstanceForm,
...businessObject.loopCharacteristics,
completionCondition: businessObject.loopCharacteristics?.completionCondition?.body ?? "",
loopCardinality: businessObject.loopCharacteristics?.loopCardinality?.body ?? ""
};
// businessObject loopCharacteristics
this.multiLoopInstance = this.modelerStore.element.businessObject.loopCharacteristics;
//
if (
businessObject.loopCharacteristics.extensionElements &&
businessObject.loopCharacteristics.extensionElements.values &&
businessObject.loopCharacteristics.extensionElements.values.length
) {
this.$set(this.loopInstanceForm, "timeCycle", businessObject.loopCharacteristics.extensionElements.values[0].body);
}
},
changeLoopCharacteristicsType(type) {
//
this.loopInstanceForm = {...this.defaultLoopInstanceForm};
//
if (type === "Null") {
this.modelerStore.modeling.updateProperties(this.modelerStore.element, {loopCharacteristics: null});
return;
}
//
if (type === "StandardLoop") {
const loopCharacteristicsObject = this.modelerStore.moddle.create("bpmn:StandardLoopCharacteristics");
this.modelerStore.modeling.updateProperties(this.modelerStore.element, {
loopCharacteristics: loopCharacteristicsObject
});
this.multiLoopInstance = null;
return;
}
//
if (type === "SequentialMultiInstance") {
this.multiLoopInstance = this.modelerStore.moddle.create("bpmn:MultiInstanceLoopCharacteristics", {
isSequential: true
});
} else {
this.multiLoopInstance = this.modelerStore.moddle.create("bpmn:MultiInstanceLoopCharacteristics");
}
this.modelerStore.modeling.updateProperties(this.modelerStore.element, {
loopCharacteristics: this.multiLoopInstance
});
},
//
updateLoopCardinality(cardinality) {
let loopCardinality = null;
if (cardinality && cardinality.length) {
loopCardinality = this.modelerStore.moddle.create("bpmn:FormalExpression", {body: cardinality});
}
this.modelerStore.modeling.updateModdleProperties(this.modelerStore.element, this.multiLoopInstance, {
loopCardinality
});
},
//
updateLoopCondition(condition) {
let completionCondition = null;
if (condition && condition.length) {
completionCondition = this.modelerStore.moddle.create("bpmn:FormalExpression", {body: condition});
}
this.modelerStore.modeling.updateModdleProperties(this.modelerStore.element, this.multiLoopInstance, {
completionCondition
});
},
//
updateLoopTimeCycle(timeCycle) {
const extensionElements = this.modelerStore.moddle.create("bpmn:ExtensionElements", {
values: [
this.modelerStore.moddle.create(`flowable:FailedJobRetryTimeCycle`, {
body: timeCycle
})
]
});
this.modelerStore.modeling.updateModdleProperties(this.modelerStore.element, this.multiLoopInstance, {
extensionElements
});
},
//
updateLoopBase() {
this.modelerStore.modeling.updateModdleProperties(this.modelerStore.element, this.multiLoopInstance, {
collection: this.loopInstanceForm.collection || null,
elementVariable: this.loopInstanceForm.elementVariable || null
});
},
//
updateLoopAsync(key) {
const {asyncBefore, asyncAfter} = this.loopInstanceForm;
let asyncAttr = Object.create(null);
if (!asyncBefore && !asyncAfter) {
this.$set(this.loopInstanceForm, "exclusive", false);
asyncAttr = {asyncBefore: false, asyncAfter: false, exclusive: false, extensionElements: null};
} else {
asyncAttr[key] = this.loopInstanceForm[key];
}
this.modelerStore.modeling.updateModdleProperties(this.modelerStore.element, this.multiLoopInstance, asyncAttr);
}
}
}
</script>
<style lang="scss">
@import '../style/process-panel';
</style>

65
ruoyi-ui/src/components/Process/panel/otherPanel.vue

@ -0,0 +1,65 @@
<template>
<div>
<el-form label-width="80px" size="small" @submit.native.prevent>
<el-form-item label="跳过表达式">
<el-input v-model="bpmnFormData.skipExpression" @change="updateElementTask('skipExpression')"/>
</el-form-item>
<el-form-item label="是否为补偿">
<el-input v-model="bpmnFormData.isForCompensation" @change="updateElementTask('isForCompensation')"/>
</el-form-item>
<el-form-item label="服务任务可触发">
<el-input v-model="bpmnFormData.triggerable" @change="updateElementTask('triggerable')"/>
</el-form-item>
</el-form>
</div>
</template>
<script>
import {StrUtil} from "@/utils/StrUtil";
export default {
name: "OtherPanel",
/** 组件传值 */
props : {
id: {
type: String,
required: true
},
},
data() {
return {
bpmnFormData: {}
}
},
/** 传值监听 */
watch: {
id: {
handler(newVal) {
if (StrUtil.isNotBlank(newVal)) {
this.resetTaskForm();
}
},
immediate: true, //
},
},
created() {
},
methods: {
//
resetFlowForm() {
this.bpmnFormData = JSON.parse(JSON.stringify(this.modelerStore.element.businessObject));
},
updateElementTask(key) {
const taskAttr = Object.create(null);
taskAttr[key] = this.bpmnFormData[key] || null;
this.modelerStore.modeling.updateProperties(this.modelerStore.element, taskAttr);
}
}
}
</script>

529
ruoyi-ui/src/components/Process/panel/taskListener.vue

@ -0,0 +1,529 @@
<template>
<div class="panel-tab__content">
<el-table :data="elementListenersList" border size="mini">
<el-table-column label="序号" width="50px" type="index" />
<el-table-column label="类型" width="60px" show-overflow-tooltip :formatter="row => listenerEventTypeObject[row.event]" />
<!-- <el-table-column label="事件id" min-width="70px" prop="id" show-overflow-tooltip />-->
<el-table-column label="监听类型" width="85px" show-overflow-tooltip :formatter="row => listenerTypeObject[row.listenerType]" />
<el-table-column label="操作" >
<template slot-scope="scope">
<el-button size="mini" type="primary" @click="openListenerForm(scope.row, scope.$index)">编辑</el-button>
<el-divider direction="vertical" />
<el-button size="mini" type="danger" @click="removeListener(scope.row, scope.$index)">移除</el-button>
</template>
</el-table-column>
</el-table>
<div class="element-drawer__button_save">
<el-button type="primary" icon="el-icon-plus" size="small" @click="listenerSystemVisible = true">内置监听器</el-button>
<el-button type="primary" icon="el-icon-plus" size="small" @click="openListenerForm(null)">自定义监听器</el-button>
</div>
<!-- 监听器 编辑/创建 部分 -->
<el-drawer :visible.sync="listenerFormModelVisible" title="任务监听器" size="480px" append-to-body destroy-on-close>
<el-form :model="listenerForm" size="small" label-width="110px" ref="listenerFormRef" @submit.native.prevent>
<el-form-item prop="event" :rules="{ required: true, trigger: ['blur', 'change'] }">
<template slot="label">
<span>
事件类型
<el-tooltip placement="top">
<template slot="content">
<div>
create创建当任务已经创建并且所有任务参数都已经设置时触发
<br />assignment指派当任务已经指派给某人时触发请注意当流程执行到达用户任务时
<br />在触发create事件之前会首先触发assignment事件这顺序看起来不太自然
<br />但是有实际原因的当收到create事件时我们通常希望能看到任务的所有参数包括办理人
<br />complete完成当任务已经完成从运行时数据中删除前触发
<br />delete删除在任务即将被删除前触发请注意任务由completeTask正常完成时也会触发
</div>
</template>
<i class="el-icon-question" />
</el-tooltip>
</span>
</template>
<el-select v-model="listenerForm.event">
<el-option v-for="i in Object.keys(listenerEventTypeObject)" :key="i" :label="listenerEventTypeObject[i]" :value="i" />
</el-select>
</el-form-item>
<!-- <el-form-item label="监听器ID" prop="id" :rules="{ required: true, trigger: ['blur', 'change'] }">-->
<!-- <el-input v-model="listenerForm.id" clearable />-->
<!-- </el-form-item>-->
<el-form-item label="监听器类型" prop="listenerType" :rules="{ required: true, trigger: ['blur', 'change'] }">
<template slot="label">
<span>
监听类型
<el-tooltip placement="top">
<template slot="content">
<div>
class需要调用的委托类这个类必须实现org.flowable.engine.delegate.TaskListener接口
<br />assignment指派当任务已经指派给某人时触发请注意当流程执行到达用户任务时
<br /> 在触发create事件之前会首先触发assignment事件这顺序看起来不太自然
<br /> 但是有实际原因的当收到create事件时我们通常希望能看到任务的所有参数包括办理人
<br />complete完成当任务已经完成从运行时数据中删除前触发
<br />delete删除在任务即将被删除前触发请注意任务由completeTask正常完成时也会触发
</div>
</template>
<i class="el-icon-question" />
</el-tooltip>
</span>
</template>
<el-select v-model="listenerForm.listenerType">
<el-option v-for="i in Object.keys(listenerTypeObject)" :key="i" :label="listenerTypeObject[i]" :value="i" />
</el-select>
</el-form-item>
<el-form-item
v-if="listenerForm.listenerType === 'classListener'"
label="Java类"
prop="class"
key="listener-class"
:rules="{ required: true, trigger: ['blur', 'change'] }"
>
<el-input v-model="listenerForm.class" clearable />
</el-form-item>
<el-form-item
v-if="listenerForm.listenerType === 'expressionListener'"
label="表达式"
prop="expression"
key="listener-expression"
:rules="{ required: true, trigger: ['blur', 'change'] }"
>
<el-input v-model="listenerForm.expression" clearable />
</el-form-item>
<el-form-item
v-if="listenerForm.listenerType === 'delegateExpressionListener'"
label="代理表达式"
prop="delegateExpression"
key="listener-delegate"
:rules="{ required: true, trigger: ['blur', 'change'] }"
>
<el-input v-model="listenerForm.delegateExpression" clearable />
</el-form-item>
<template v-if="listenerForm.listenerType === 'scriptListener'">
<el-form-item
label="脚本格式"
prop="scriptFormat"
key="listener-script-format"
:rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本格式' }"
>
<el-input v-model="listenerForm.scriptFormat" clearable />
</el-form-item>
<el-form-item
label="脚本类型"
prop="scriptType"
key="listener-script-type"
:rules="{ required: true, trigger: ['blur', 'change'], message: '请选择脚本类型' }"
>
<el-select v-model="listenerForm.scriptType">
<el-option label="内联脚本" value="inlineScript" />
<el-option label="外部脚本" value="externalScript" />
</el-select>
</el-form-item>
<el-form-item
v-if="listenerForm.scriptType === 'inlineScript'"
label="脚本内容"
prop="value"
key="listener-script"
:rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本内容' }"
>
<el-input v-model="listenerForm.value" clearable />
</el-form-item>
<el-form-item
v-if="listenerForm.scriptType === 'externalScript'"
label="资源地址"
prop="resource"
key="listener-resource"
:rules="{ required: true, trigger: ['blur', 'change'], message: '请填写资源地址' }"
>
<el-input v-model="listenerForm.resource" clearable />
</el-form-item>
</template>
<template v-if="listenerForm.event === 'timeout'">
<el-form-item label="定时器类型" prop="eventDefinitionType" key="eventDefinitionType">
<el-select v-model="listenerForm.eventDefinitionType">
<el-option label="日期" value="date" />
<el-option label="持续时长" value="duration" />
<el-option label="循环" value="cycle" />
<el-option label="无" value="null" />
</el-select>
</el-form-item>
<el-form-item
v-if="!!listenerForm.eventDefinitionType && listenerForm.eventDefinitionType !== 'null'"
label="定时器"
prop="eventTimeDefinitions"
key="eventTimeDefinitions"
:rules="{ required: true, trigger: ['blur', 'change'], message: '请填写定时器配置' }"
>
<el-input v-model="listenerForm.eventTimeDefinitions" clearable />
</el-form-item>
</template>
</el-form>
<el-divider />
<p class="listener-filed__title">
<span><i class="el-icon-menu"></i>注入字段</span>
<el-button size="small" type="primary" @click="openListenerFieldForm(null)">添加字段</el-button>
</p>
<el-table :data="fieldsListOfListener" size="mini" max-height="240" border fit style="flex: none">
<el-table-column label="序号" width="50px" type="index" />
<el-table-column label="字段名称" width="80px" prop="name" />
<el-table-column label="字段类型" width="80px" show-overflow-tooltip :formatter="row => fieldTypeObject[row.fieldType]" />
<el-table-column label="值内容" width="80px" show-overflow-tooltip :formatter="row => row.string || row.expression" />
<el-table-column label="操作">
<template slot-scope="scope">
<el-button size="mini" type="primary" @click="openListenerFieldForm(scope.row, scope.$index)">编辑</el-button>
<el-divider direction="vertical" />
<el-button size="mini" type="danger" @click="removeListenerField(scope.row, scope.$index)">移除</el-button>
</template>
</el-table-column>
</el-table>
<div class="element-drawer__button">
<el-button size="small" @click="listenerFormModelVisible = false"> </el-button>
<el-button size="small" type="primary" @click="saveListenerConfig"> </el-button>
</div>
</el-drawer>
<!-- 注入西段 编辑/创建 部分 -->
<el-dialog title="字段配置" :visible.sync="listenerFieldFormModelVisible" width="600px" append-to-body destroy-on-close>
<el-form :model="listenerFieldForm" label-width="96px" ref="listenerFieldFormRef" style="height: 136px" @submit.native.prevent>
<el-form-item label="字段名称:" prop="name" :rules="{ required: true, trigger: ['blur', 'change'] }">
<el-input v-model="listenerFieldForm.name" clearable />
</el-form-item>
<el-form-item label="字段类型:" prop="fieldType" :rules="{ required: true, trigger: ['blur', 'change'] }">
<el-select v-model="listenerFieldForm.fieldType">
<el-option v-for="i in Object.keys(fieldTypeObject)" :key="i" :label="fieldTypeObject[i]" :value="i" />
</el-select>
</el-form-item>
<el-form-item
v-if="listenerFieldForm.fieldType === 'string'"
label="字段值:"
prop="string"
key="field-string"
:rules="{ required: true, trigger: ['blur', 'change'] }"
>
<el-input v-model="listenerFieldForm.string" clearable />
</el-form-item>
<el-form-item
v-if="listenerFieldForm.fieldType === 'expression'"
label="表达式:"
prop="expression"
key="field-expression"
:rules="{ required: true, trigger: ['blur', 'change'] }"
>
<el-input v-model="listenerFieldForm.expression" clearable />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button size="small" @click="listenerFieldFormModelVisible= false"> </el-button>
<el-button size="small" type="primary" @click="saveListenerFiled"> </el-button>
</div>
</el-dialog>
<!-- 内置监听器 -->
<el-drawer :visible.sync="listenerSystemVisible" title="任务监听器" size="580px" append-to-body destroy-on-close>
<el-table v-loading="loading" :data="listenerList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="名称" align="center" prop="name" />
<el-table-column label="类型" align="center" prop="eventType"/>
<el-table-column label="监听类型" align="center" prop="valueType">
<template slot-scope="scope">
<dict-tag :options="dict.type.sys_listener_value_type" :value="scope.row.valueType"/>
</template>
</el-table-column>
<el-table-column label="执行内容" align="center" prop="value" :show-overflow-tooltip="true"/>
</el-table>
<pagination
v-show="total>0"
:total="total"
layout="prev, pager, next"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<div class="element-drawer__button">
<el-button size="small" @click="listenerSystemVisible = false"> </el-button>
<el-button size="small" type="primary" :disabled="listenerSystemChecked" @click="saveSystemListener"> </el-button>
</div>
</el-drawer>
</div>
</template>
<script>
import { listListener } from "@/api/flowable/listener";
import {
changeListenerObject,
createListenerObject,
createSystemListenerObject,
updateElementExtensions
} from "../common/bpmnUtils";
import {StrUtil} from "@/utils/StrUtil";
export default {
name: "TaskListener",
//
dicts: ['sys_listener_value_type', 'sys_listener_event_type'],
/** 组件传值 */
props : {
id: {
type: String,
required: true
},
},
data() {
return {
elementListenersList: [], //
listenerForm: {},//
listenerFormModelVisible: false, //
fieldsListOfListener: [],
bpmnElementListeners: [],
otherExtensionList: [],
listenerFieldForm: {}, //
listenerFieldFormModelVisible: false, //
editingListenerIndex: -1, // -1
editingListenerFieldIndex: -1, // -1
listenerList: [],
checkedListenerData: [],
listenerSystemVisible: false,
listenerSystemChecked: true,
loading: true,
total: 0,
listenerTypeObject: {
classListener: "Java 类",
expressionListener: "表达式",
delegateExpressionListener: "代理表达式",
scriptListener: "脚本"
},
listenerEventTypeObject:{
create: "创建",
assignment: "指派",
complete: "完成",
delete: "删除",
// update: "",
// timeout: ""
},
fieldTypeObject:{
string: "字符串",
expression: "表达式"
},
queryParams: {
pageNum: 1,
pageSize: 10,
type: 1,
},
}
},
/** 传值监听 */
watch: {
id: {
handler(newVal) {
if (StrUtil.isNotBlank(newVal)) {
this.resetListenersList();
}
},
immediate: true, //
},
},
created() {
this.getList();
},
methods: {
resetListenersList() {
this.bpmnElementListeners =
this.modelerStore.element.businessObject?.extensionElements?.values?.filter(ex => ex.$type === `flowable:TaskListener`) ?? [];
this.elementListenersList = this.bpmnElementListeners.map(listener => this.initListenerType(listener));
this.$emit('getTaskListenerCount', this.elementListenersList.length)
},
//
openListenerForm(listener, index) {
if (listener) {
this.listenerForm = this.initListenerForm(listener);
this.editingListenerIndex = index;
} else {
this.listenerForm = {};
this.editingListenerIndex = -1; //
}
if (listener && listener.fields) {
this.fieldsListOfListener = listener.fields.map(field => ({
...field,
fieldType: field.string ? "string" : "expression"
}));
} else {
this.fieldsListOfListener = [];
this.$set(this.listenerForm, "fields", []);
}
//
this.listenerFormModelVisible = true;
this.$nextTick(() => {
if (this.$refs["listenerFormRef"]) this.$refs["listenerFormRef"].clearValidate();
});
},
//
openListenerFieldForm(field, index) {
this.listenerFieldForm = field ? JSON.parse(JSON.stringify(field)) : {};
this.editingListenerFieldIndex = field ? index : -1;
this.listenerFieldFormModelVisible = true;
this.$nextTick(() => {
if (this.$refs["listenerFieldFormRef"]) this.$refs["listenerFieldFormRef"].clearValidate();
});
},
//
async saveListenerFiled() {
let validateStatus = await this.$refs["listenerFieldFormRef"].validate();
if (!validateStatus) return; //
if (this.editingListenerFieldIndex === -1) {
this.fieldsListOfListener.push(this.listenerFieldForm);
this.listenerForm.fields.push(this.listenerFieldForm);
} else {
this.fieldsListOfListener.splice(this.editingListenerFieldIndex, 1, this.listenerFieldForm);
this.listenerForm.fields.splice(this.editingListenerFieldIndex, 1, this.listenerFieldForm);
}
this.listenerFieldFormModelVisible = false;
this.$nextTick(() => (this.listenerFieldForm = {}));
},
//
removeListenerField(field, index) {
this.$confirm("确认移除该字段吗?", "提示", {
confirmButtonText: "确 认",
cancelButtonText: "取 消"
}).then(() => {
this.fieldsListOfListener.splice(index, 1);
this.listenerForm.fields.splice(index, 1);
}).catch(() => console.info("操作取消"));
},
//
removeListener(listener, index) {
this.$confirm("确认移除该监听器吗?", "提示", {
confirmButtonText: "确 认",
cancelButtonText: "取 消"
}).then(() => {
this.bpmnElementListeners.splice(index, 1);
this.elementListenersList.splice(index, 1);
updateElementExtensions(this.modelerStore.moddle, this.modelerStore.modeling, this.modelerStore.element, this.otherExtensionList.concat(this.bpmnElementListeners));
this.$emit('getTaskListenerCount', this.elementListenersList.length)
}).catch((r) => console.info(r, "操作取消"));
},
//
async saveListenerConfig() {
let validateStatus = await this.$refs["listenerFormRef"].validate();
if (!validateStatus) return; //
const listenerObject = createListenerObject(this.modelerStore.moddle, this.listenerForm, false, "flowable");
if (this.editingListenerIndex === -1) {
this.bpmnElementListeners.push(listenerObject);
this.elementListenersList.push(this.listenerForm);
} else {
this.bpmnElementListeners.splice(this.editingListenerIndex, 1, listenerObject);
this.elementListenersList.splice(this.editingListenerIndex, 1, this.listenerForm);
}
//
this.otherExtensionList = this.modelerStore.element.businessObject?.extensionElements?.values?.filter(ex => ex.$type !== `flowable:TaskListener`) ?? [];
updateElementExtensions(this.modelerStore.moddle, this.modelerStore.modeling, this.modelerStore.element, this.otherExtensionList.concat(this.bpmnElementListeners));
this.$emit('getTaskListenerCount', this.elementListenersList.length)
// 4.
this.listenerFormModelVisible = false;
this.listenerForm = {};
},
initListenerType(listener) {
let listenerType;
if (listener.class) listenerType = "classListener";
if (listener.expression) listenerType = "expressionListener";
if (listener.delegateExpression) listenerType = "delegateExpressionListener";
if (listener.script) listenerType = "scriptListener";
return {
...JSON.parse(JSON.stringify(listener)),
...(listener.script ?? {}),
listenerType: listenerType
};
},
//
initListenerForm(listener) {
let self = {
...listener
};
if (listener.script) {
self = {
...listener,
...listener.script,
scriptType: listener.script.resource ? "externalScript" : "inlineScript"
};
}
if (listener.event === "timeout" && listener.eventDefinitions) {
if (listener.eventDefinitions.length) {
let k = "";
for (let key in listener.eventDefinitions[0]) {
console.log(listener.eventDefinitions, key);
if (key.indexOf("time") !== -1) {
k = key;
self.eventDefinitionType = key.replace("time", "").toLowerCase();
}
}
console.log(k);
self.eventTimeDefinitions = listener.eventDefinitions[0][k].body;
}
}
return self;
},
/** 查询流程达式列表 */
getList() {
this.loading = true;
listListener(this.queryParams).then(response => {
this.listenerList = response.rows;
this.total = response.total;
this.loading = false;
});
},
//
handleSelectionChange(selection) {
// ids.value = selection.map(item => item.id);
// TODO 使 push?
this.checkedListenerData = selection;
this.listenerSystemChecked = selection.length !== 1;
},
//
saveSystemListener() {
if (this.checkedListenerData.length > 0) {
this.checkedListenerData.forEach(value => {
//
const listenerObject = createSystemListenerObject(this.modelerStore.moddle, value, true, "flowable");
this.bpmnElementListeners.push(listenerObject);
this.elementListenersList.push(changeListenerObject(value));
this.otherExtensionList = this.modelerStore.element.businessObject?.extensionElements?.values?.filter(ex => ex.$type !== `flowable:TaskListener`) ?? [];
updateElementExtensions(this.modelerStore.moddle, this.modelerStore.modeling, this.modelerStore.element, this.otherExtensionList.concat(this.bpmnElementListeners));
})
//
this.$emit('getTaskListenerCount', this.elementListenersList.length)
}
this.checkedListenerData = [];
this.listenerSystemChecked = true;
//
this.listenerSystemVisible = false;
}
}
}
</script>
<style lang="scss">
@import '../style/process-panel';
.flow-containers .el-badge__content.is-fixed {
top: 18px;
}
.dialog-footer button:first-child {
margin-right: 10px;
}
</style>

424
ruoyi-ui/src/components/Process/panel/taskPanel.vue

@ -0,0 +1,424 @@
<template>
<div>
<el-form label-width="80px" size="small">
<el-form-item label="异步">
<el-switch v-model="bpmnFormData.async" active-text="" inactive-text="" @change="updateElementTask('async')"/>
</el-form-item>
<el-form-item label="用户类型">
<el-select v-model="bpmnFormData.userType" placeholder="选择人员" @change="updateUserType">
<el-option
v-for="item in userTypeOption"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="指定人员" v-if="bpmnFormData.userType === 'assignee'">
<el-input-tag v-model="bpmnFormData.assignee" :value="bpmnFormData.assignee"/>
<el-button-group class="ml-4" style="margin-top: 4px">
<!--指定人员-->
<el-tooltip class="box-item" effect="dark" content="指定人员" placement="bottom">
<el-button size="mini" type="primary" icon="el-icon-user" @click="singleUserCheck"/>
</el-tooltip>
<!--选择表达式-->
<el-tooltip class="box-item" effect="dark" content="选择表达式" placement="bottom">
<el-button size="mini" type="warning" icon="el-icon-postcard" @click="singleExpCheck"/>
</el-tooltip>
</el-button-group>
</el-form-item>
<el-form-item label="候选人员" v-else-if="bpmnFormData.userType === 'candidateUsers'">
<el-input-tag v-model="bpmnFormData.candidateUsers" :value="bpmnFormData.candidateUsers"/>
<el-button-group class="ml-4" style="margin-top: 4px">
<!--候选人员-->
<el-tooltip class="box-item" effect="dark" content="候选人员" placement="bottom">
<el-button size="mini" type="primary" icon="el-icon-user" @click="multipleUserCheck"/>
</el-tooltip>
<!--选择表达式-->
<el-tooltip class="box-item" effect="dark" content="选择表达式" placement="bottom">
<el-button size="mini" type="warning" icon="el-icon-postcard" @click="singleExpCheck"/>
</el-tooltip>
</el-button-group>
</el-form-item>
<el-form-item label="候选角色" v-else>
<el-input-tag v-model="bpmnFormData.candidateGroups" :value="bpmnFormData.candidateGroups"/>
<el-button-group class="ml-4" style="margin-top: 4px">
<!--候选角色-->
<el-tooltip class="box-item" effect="dark" content="候选角色" placement="bottom">
<el-button size="mini" type="primary" icon="el-icon-user" @click="multipleRoleCheck"/>
</el-tooltip>
<!--选择表达式-->
<el-tooltip class="box-item" effect="dark" content="选择表达式" placement="bottom">
<el-button size="mini" type="warning" icon="el-icon-postcard" @click="singleExpCheck"/>
</el-tooltip>
</el-button-group>
</el-form-item>
<el-form-item label="优先级">
<el-input v-model="bpmnFormData.priority" @change="updateElementTask('priority')"/>
</el-form-item>
<el-form-item label="到期时间">
<el-input v-model="bpmnFormData.dueDate" @change="updateElementTask('dueDate')"/>
</el-form-item>
</el-form>
<!--选择人员-->
<el-dialog
title="选择人员"
:visible.sync="userVisible"
width="60%"
:close-on-press-escape="false"
:show-close="false"
>
<flow-user v-if="userVisible" :checkType="checkType" :selectValues="selectData.assignee || selectData.candidateUsers" @handleUserSelect="userSelect"></flow-user>
<div slot="footer" class="dialog-footer">
<el-button size="small" @click="userVisible = false"> </el-button>
<el-button size="small" type="primary" @click="checkUserComplete"> </el-button>
</div>
</el-dialog>
<!--选择角色-->
<el-dialog
title="选择候选角色"
:visible.sync="roleVisible"
width="60%"
:close-on-press-escape="false"
:show-close="false"
>
<flow-role v-if="roleVisible" :selectValues="selectData.candidateGroups" @handleRoleSelect="roleSelect"></flow-role>
<div slot="footer" class="dialog-footer">
<el-button size="small" @click="roleVisible = false"> </el-button>
<el-button size="small" type="primary" @click="checkRoleComplete"> </el-button>
</div>
</el-dialog>
<!--选择表达式-->
<el-dialog
title="选择表达式"
:visible.sync="expVisible"
width="60%"
:close-on-press-escape="false"
:show-close="false"
>
<flow-exp v-if="expVisible" :selectValues="selectData.exp" @handleSingleExpSelect="expSelect"></flow-exp>
<div slot="footer" class="dialog-footer">
<el-button size="small" @click="expVisible = false"> </el-button>
<el-button size="small" type="primary" @click="checkExpComplete"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import FlowUser from '@/components/flow/User'
import FlowRole from '@/components/flow/Role'
import FlowExp from '@/components/flow/Expression'
import ElInputTag from '@/components/flow/ElInputTag'
import {StrUtil} from '@/utils/StrUtil'
export default {
name: "TaskPanel",
components: {
FlowUser,
FlowRole,
FlowExp,
ElInputTag
},
/** 组件传值 */
props : {
id: {
type: String,
required: true
},
},
data() {
return {
userVisible: false,
roleVisible: false,
expVisible: false,
isIndeterminate: true,
checkType: 'single', //
userType: '',
userTypeOption: [
{label: '指定人员', value: 'assignee'},
{label: '候选人员', value: 'candidateUsers'},
{label: '候选角色', value: 'candidateGroups'}
],
checkAll: false,
bpmnFormData: {
userType: "",
assignee: "",
candidateUsers: "",
candidateGroups: "",
dueDate: "",
priority: "",
dataType: "",
expId: "",
},
//
selectData: {
assignee: null,
candidateUsers: null,
candidateGroups: null,
exp: null,
},
otherExtensionList:[],
}
},
/** 传值监听 */
watch: {
id: {
handler(newVal) {
if (StrUtil.isNotBlank(newVal)) {
this.resetTaskForm();
}
},
immediate: true, //
},
},
created() {
},
methods: {
//
resetTaskForm() {
//
this.bpmnFormData = {
userType: "",
assignee: "",
candidateUsers: "",
candidateGroups: "",
dueDate: "",
priority: "",
dataType: "",
expId: "",
}
this.selectData = {
assignee: null,
candidateUsers: null,
candidateGroups: null,
exp: null,
}
//
for (let key in this.bpmnFormData) {
const value = this.modelerStore.element?.businessObject[key] || this.bpmnFormData[key];
this.$set(this.bpmnFormData, key, value);
}
//
this.checkValuesEcho(this.bpmnFormData);
},
//
updateElementTask(key) {
const taskAttr = Object.create(null);
taskAttr[key] = this.bpmnFormData[key] || "";
this.modelerStore.modeling.updateProperties(this.modelerStore.element, taskAttr);
},
// /
updateCustomElement(key, value) {
const taskAttr = Object.create(null);
taskAttr[key] = value;
this.modelerStore.modeling.updateProperties(this.modelerStore.element, taskAttr);
},
//
updateUserType(val) {
// xml
this.deleteFlowAttar();
delete this.modelerStore.element.businessObject[`userType`]
//
this.bpmnFormData[val] = null;
this.selectData = {
assignee: null,
candidateUsers: null,
candidateGroups: null,
exp: null,
}
// userTypexml
this.updateCustomElement('userType', val);
},
//
checkValuesEcho(formData) {
if (StrUtil.isNotBlank(formData.expId)) {
this.getExpList(formData.expId, formData.userType);
} else {
if ("candidateGroups" === formData.userType) {
this.getRoleList(formData[formData.userType], formData.userType);
} else {
this.getUserList(formData[formData.userType], formData.userType);
}
}
},
//
getExpList(val, key) {
if (StrUtil.isNotBlank(val)) {
this.bpmnFormData[key] = this.modelerStore.expList?.find(item => item.id.toString() === val).name;
this.selectData.exp = this.modelerStore.expList?.find(item => item.id.toString() === val).id;
}
},
//
getUserList(val, key) {
if (StrUtil.isNotBlank(val)) {
const newArr = this.modelerStore.userList?.filter(i => val.split(',').includes(i.userId.toString()))
this.bpmnFormData[key] = newArr.map(item => item.nickName).join(',');
if ('assignee' === key) {
this.selectData[key] = newArr.find(item => item.userId.toString() === val).userId;
} else {
this.selectData[key] = newArr.map(item => item.userId);
}
}
},
//
getRoleList(val, key) {
if (StrUtil.isNotBlank(val)) {
const newArr = this.modelerStore.roleList?.filter(i => val.split(',').includes(i.roleId.toString()))
this.bpmnFormData[key] = newArr.map(item => item.roleName).join(',');
if ('assignee' === key) {
this.selectData[key] = newArr.find(item => item.roleId.toString() === val).roleId;
} else {
this.selectData[key] = newArr.map(item => item.roleId);
}
}
},
// ------ start---------
/*单选人员*/
singleUserCheck() {
this.userVisible = true;
this.checkType = "single";
},
/*多选人员*/
multipleUserCheck() {
this.userVisible = true;
this.checkType = "multiple";
},
/*单选角色*/
singleRoleCheck() {
this.roleVisible = true;
this.checkType = "single";
},
/*多选角色*/
multipleRoleCheck() {
this.roleVisible = true;
},
/*单选表达式*/
singleExpCheck() {
this.expVisible = true;
},
//
expSelect(selection) {
if (selection) {
this.deleteFlowAttar();
this.bpmnFormData[this.bpmnFormData.userType] = selection.name;
this.updateCustomElement('dataType', selection.dataType);
this.updateCustomElement('expId', selection.id.toString());
this.updateCustomElement(this.bpmnFormData.userType, selection.expression);
this.handleSelectData("exp", selection.id);
}
},
// TODO:
userSelect(selection) {
if (selection) {
this.deleteFlowAttar();
this.updateCustomElement('dataType', 'fixed');
if (selection instanceof Array) {
const userIds = selection.map(item => item.userId);
const nickName = selection.map(item => item.nickName);
// userType = candidateUsers
this.bpmnFormData[this.bpmnFormData.userType] = nickName.join(',');
this.updateCustomElement(this.bpmnFormData.userType, userIds.join(','));
this.handleSelectData(this.bpmnFormData.userType, userIds);
} else {
// userType = assignee
this.bpmnFormData[this.bpmnFormData.userType] = selection.nickName;
this.updateCustomElement(this.bpmnFormData.userType, selection.userId);
this.handleSelectData(this.bpmnFormData.userType, selection.userId);
}
}
},
//
roleSelect(selection, name) {
if (selection && name) {
this.deleteFlowAttar();
this.bpmnFormData[this.bpmnFormData.userType] = name;
this.updateCustomElement('dataType', 'fixed');
// userType = candidateGroups
this.updateCustomElement(this.bpmnFormData.userType, selection);
this.handleSelectData(this.bpmnFormData.userType, selection);
}
},
//
handleSelectData(key, value) {
for (let oldKey in this.selectData) {
if (key !== oldKey) {
this.$set(this.selectData, oldKey, null);
} else {
this.$set(this.selectData, oldKey, value);
}
}
},
/*用户选中赋值*/
checkUserComplete() {
this.userVisible = false;
this.checkType = "";
},
/*候选角色选中赋值*/
checkRoleComplete() {
this.roleVisible = false;
},
/*表达式选中赋值*/
checkExpComplete() {
this.expVisible = false;
},
//
deleteFlowAttar() {
delete this.modelerStore.element.businessObject[`dataType`]
delete this.modelerStore.element.businessObject[`expId`]
delete this.modelerStore.element.businessObject[`assignee`]
delete this.modelerStore.element.businessObject[`candidateUsers`]
delete this.modelerStore.element.businessObject[`candidateGroups`]
},
//
unique(arr, code) {
const res = new Map();
return arr.filter((item) => !res.has(item[code]) && res.set(item[code], 1));
},
//
updateElementExtensions(properties) {
const extensions = this.modelerStore.moddle.create("bpmn:ExtensionElements", {
values: this.otherExtensionList.concat([properties])
});
this.modelerStore.modeling.updateProperties(this.modelerStore.element, {
extensionElements: extensions
});
}
}
}
</script>

183
ruoyi-ui/src/components/Process/style/flow-viewer.scss

@ -0,0 +1,183 @@
.my-header {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.flowMsgPopover {
display: none;
}
.tipBox {
width: 180px;
background: #fff;
border-radius: 4px;
border: 1px solid #ebeef5;
padding: 12px;
p{
line-height: 28px;
margin:0;
padding:0;
}
}
.cell-item {
display: flex;
align-items: center;
}
// bpmn 画布 logo
//.bjs-powered-by {
// display: none;
//}
.view-mode {
.el-header, .el-aside, .djs-palette, .bjs-powered-by {
display: none;
}
.el-loading-mask {
background-color: initial;
}
.el-loading-spinner {
display: none;
}
}
.containers {
width: 100%;
height: 100%;
.canvas {
width: 100%;
height: 100%;
background: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+")
}
.panel {
position: absolute;
right: 0;
top: 50px;
width: 300px;
}
.load {
margin-right: 10px;
}
.djs-palette {
left: 0px !important;
top: 0px;
border-top: none;
}
.djs-container svg {
min-height: 650px;
}
.overlays-div {
font-size: 10px;
color: red;
width: 100px;
top: -20px !important;
}
.flow-viewer {
position: relative;
padding: 0;
}
.flow-viewer .button-group {
display: flex;
position: absolute;
width: auto;
height: auto;
top: 10px;
right: 10px;
}
// 流程线
.highlight.djs-shape .djs-visual > :nth-child(1) {
fill: #56bb56 !important;
stroke: #56bb56 !important;
fill-opacity: 0.2 !important;
}
.highlight.djs-shape .djs-visual > :nth-child(2) {
fill: #56bb56 !important;
}
.highlight.djs-shape .djs-visual > path {
fill: #56bb56 !important;
fill-opacity: 0.2 !important;
stroke: #56bb56 !important;
}
.highlight.djs-connection > .djs-visual > path {
stroke: #56bb56 !important;
}
.highlight-todo.djs-connection > .djs-visual > path {
stroke: #eab24a !important;
stroke-dasharray: 4px !important;
fill-opacity: 0.2 !important;
}
.highlight-todo.djs-shape .djs-visual > :nth-child(1) {
stroke-dasharray: 5, 5;
stroke-dashoffset: 500;
stroke: #eab24a !important;
fill: rgba(252, 211, 127, 0.2) !important;
}
@keyframes draw {
100% {
stroke-dashoffset: 0;
}
}
.process-status {
position: absolute;
width: auto;
height: auto;
display: flex;
float: right;
top: 10px;
left: 10px;
font-size: 12px;
.intro {
color: #303133;
margin-top: 5px;
}
.finish {
background-color: #E8FFEA;
padding: 4px;
border: 1px solid rgba(0, 180, 42, 0.1);
border-radius: 3px;
color: #56bb56;
margin-right: 8px;
}
.processing {
background-color: #fcf5ea;
padding: 4px;
border: 1px solid #fce9c7;
border-radius: 3px;
color: #eab24a;
margin-right: 8px;
}
.todo {
padding: 4px;
background: #ECEDEE;
border: 1px solid rgba(204, 204, 204, 0.1);
border-radius: 3px;
color: #666666;
margin-right: 5px;
}
}
}

123
ruoyi-ui/src/components/Process/style/process-panel.scss

@ -0,0 +1,123 @@
//.process-panel__container {
// box-sizing: border-box;
// padding: 0 8px;
// border-left: 1px solid #eeeeee;
// box-shadow: 0 0 8px #cccccc;
// max-height: 100%;
// overflow-y: scroll;
//}
.panel-tab__title {
font-weight: 600;
padding: 0 8px;
font-size: 1.1em;
line-height: 1.2em;
i {
margin-right: 8px;
font-size: 1.2em;
}
}
.panel-tab__content {
width: 100%;
box-sizing: border-box;
//border-top: 1px solid #eeeeee;
padding: 8px 16px;
.panel-tab__content--title {
display: flex;
justify-content: space-between;
padding-bottom: 8px;
span {
flex: 1;
text-align: left;
}
}
}
.element-property {
width: 100%;
display: flex;
align-items: flex-start;
margin: 8px 0;
.element-property__label {
display: block;
width: 90px;
text-align: right;
overflow: hidden;
padding-right: 12px;
line-height: 32px;
font-size: 14px;
box-sizing: border-box;
}
.element-property__value {
flex: 1;
line-height: 32px;
}
.el-form-item {
width: 100%;
margin-bottom: 0;
padding-bottom: 18px;
}
}
.list-property {
flex-direction: column;
.element-listener-item {
width: 100%;
display: inline-grid;
grid-template-columns: 16px auto 32px 32px;
grid-column-gap: 8px;
}
.element-listener-item + .element-listener-item {
margin-top: 8px;
}
}
.listener-filed__title {
width: 100%;
justify-content: space-between;
align-items: center;
margin-top: 0;
span {
font-size: 14px;
}
i {
margin-right: 8px;
}
}
.element-drawer__button {
margin-top: 8px;
display: inline-flex;
justify-content: space-around;
}
.element-drawer__button > .el-button {
width: 100%;
}
.element-drawer__button_save {
margin-top: 8px;
width: 100%;
display: inline-flex;
justify-content: space-around;
}
.element-drawer__button_save > .el-button {
width: 100%;
}
.el-collapse-item__content {
padding-bottom: 0;
}
.el-input.is-disabled .el-input__inner {
color: #999999;
}
.el-form-item.el-form-item--mini {
margin-bottom: 0;
& + .el-form-item {
margin-top: 16px;
}
}
.el-drawer__header{
margin-bottom: 0;
border-bottom: 1px solid #e8e8e8;
padding: 16px 16px 8px 16px;
font-size: 18px;
color: #303133;
}
.el-drawer__body{
padding: 10px;
}

202
ruoyi-ui/src/views/flowable/task/finished/detail/flowview.vue → ruoyi-ui/src/components/Process/viewer/index.vue

@ -1,84 +1,102 @@
<template>
<div class="containers main-box">
<el-button type="success"
size="small"
icon="el-icon-zoom-in"
@click="zoomViewport(true)">放大</el-button>
<el-button type="warning"
size="small"
icon="el-icon-zoom-out"
@click="zoomViewport(false)">缩小</el-button>
<el-button type="info"
size="small"
icon="el-icon-rank"
@click="fitViewport">适中</el-button>
<div class="canvas" ref="flowCanvas"></div>
<div class="containers">
<el-container style="align-items: stretch">
<el-main class="flow-viewer">
<div class="process-status">
<span class="intro">状态</span>
<div class="finish">已办理</div>
<div class="processing">处理中</div>
<div class="todo">未进行</div>
</div>
<!-- 流程图显示 -->
<div v-loading="loading" class="canvas" ref="flowCanvas"></div>
<!-- 按钮区域 -->
<el-button-group class="button-group">
<el-tooltip effect="dark" content="适中" placement="bottom">
<el-button size="small" icon="el-icon-rank" @click="fitViewport" />
</el-tooltip>
<el-tooltip effect="dark" content="放大" placement="bottom">
<el-button size="small" icon="el-icon-zoom-in" @click="zoomViewport(true)" />
</el-tooltip>
<el-tooltip effect="dark" content="缩小" placement="bottom">
<el-button size="small" icon="el-icon-zoom-out" @click="zoomViewport(false)" />
</el-tooltip>
</el-button-group>
</el-main>
</el-container>
</div>
</template>
<script>
import { CustomViewer as BpmnViewer } from "@/components/customBpmn";
<script>
import { CustomViewer as BpmnViewer } from "@/components/Process/common";
export default {
name: "FlowView",
name: "BpmnViewer",
/** 组件传值 */
props: {
//
flowData: {
type: Object,
default: () => {}
default: () => {
},
required: false
},
procInsId: {
type: String,
default: ''
},
},
data() {
return {
bpmnViewer: null
};
bpmnViewer: null,
flowDetail: {},
loading: true,
}
},
/** 传值监听 */
watch: {
flowData: {
handler(newVal) {
if (Object.keys(newVal).length > 0) {
handler(newValue) {
if (Object.keys(newValue).length > 0) {
//
this.bpmnViewer && this.bpmnViewer.destroy();
this.bpmnViewer = new BpmnViewer({
container: this.$refs.flowCanvas,
height: 'calc(100vh - 200px)',
});
this.loadFlowCanvas(newVal)
this.loadFlowCanvas(newValue);
}
},
immediate: true, //
deep: true //
}
}
},
},
mounted() {
created() {
},
methods: {
//
//
async loadFlowCanvas(flowData) {
const self = this
try {
await self.bpmnViewer.importXML(flowData.xmlData);
self.fitViewport()
if (flowData.nodeData !==undefined && flowData.nodeData.length > 0 ) {
self.fillColor(flowData.nodeData)
await this.bpmnViewer.importXML(flowData.xmlData);
await this.fitViewport();
// 线
if (flowData.nodeData !== undefined && flowData.nodeData.length > 0 && this.procInsId) {
await this.fillColor(flowData.nodeData);
}
} catch (err) {
console.error(err.message, err.warnings)
}
},
// class
setNodeColor(nodeCodes, colorClass, canvas) {
for (let i = 0; i < nodeCodes.length; i++) {
canvas.addMarker(nodeCodes[i], colorClass);
}
},
//
fitViewport() {
this.zoom = this.bpmnViewer.get('canvas').zoom("fit-viewport", "auto")
this.zoom = this.bpmnViewer.get('canvas').zoom("fit-viewport", "auto");
this.loading = false;
},
//
zoomViewport(zoomIn = true) {
this.zoom = this.bpmnViewer.get('canvas').zoom()
this.zoom += (zoomIn ? 0.1 : -0.1)
if(this.zoom >= 0.2) this.bpmnViewer.get('canvas').zoom(this.zoom)
if (this.zoom >= 0.2) this.bpmnViewer.get('canvas').zoom(this.zoom);
},
//
@ -94,10 +112,10 @@ export default {
n.outgoing?.forEach(nn => {
const targetTask = nodeData.find(m => m.key === nn.targetRef.id)
if (targetTask) {
if (todoTask && completeTask.key === todoTask.key && !todoTask.completed){
if (todoTask && completeTask.key === todoTask.key && !todoTask.completed) {
canvas.addMarker(nn.id, todoTask.completed ? 'highlight' : 'highlight-todo')
canvas.addMarker(nn.targetRef.id, todoTask.completed ? 'highlight' : 'highlight-todo')
}else {
} else {
canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
}
@ -116,10 +134,8 @@ export default {
canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
}
})
}
}
//
else if (n.$type === 'bpmn:ParallelGateway') {
@ -133,106 +149,28 @@ export default {
}
})
}
}
else if (n.$type === 'bpmn:StartEvent') {
} else if (n.$type === 'bpmn:StartEvent') {
n.outgoing.forEach(nn => {
const completeTask = nodeData.find(m => m.key === nn.targetRef.id)
if (completeTask) {
canvas.addMarker(nn.id, 'highlight')
canvas.addMarker(n.id, 'highlight')
return
return;
}
})
}
else if (n.$type === 'bpmn:EndEvent') {
} else if (n.$type === 'bpmn:EndEvent') {
if (endTask.key === n.id && endTask.completed) {
canvas.addMarker(n.id, 'highlight')
return
return;
}
}
})
},
}
};
</script>
<style lang="scss">
.bjs-powered-by {
display: none;
}
.view-mode {
.el-header, .el-aside, .djs-palette, .bjs-powered-by {
display: none;
}
.el-loading-mask {
background-color: initial;
}
.el-loading-spinner {
display: none;
}
}
.containers {
// background-color: #ffffff;
width: 100%;
height: 100%;
.canvas {
width: 100%;
height: 100%;
}
.panel {
position: absolute;
right: 0;
top: 50px;
width: 300px;
}
.load {
margin-right: 10px;
}
.el-form-item__label{
font-size: 13px;
}
}
.djs-palette{
left: 0px!important;
top: 0px;
border-top: none;
}
.djs-container svg {
min-height: 650px;
}
</script>
.highlight.djs-shape .djs-visual > :nth-child(1) {
fill: green !important;
stroke: green !important;
fill-opacity: 0.2 !important;
}
.highlight.djs-shape .djs-visual > :nth-child(2) {
fill: green !important;
}
.highlight.djs-shape .djs-visual > path {
fill: green !important;
fill-opacity: 0.2 !important;
stroke: green !important;
}
.highlight.djs-connection > .djs-visual > path {
stroke: green !important;
}
.highlight-todo.djs-connection > .djs-visual > path {
stroke: orange !important;
stroke-dasharray: 4px !important;
fill-opacity: 0.2 !important;
}
.highlight-todo.djs-shape .djs-visual > :nth-child(1) {
fill: orange !important;
stroke: orange !important;
stroke-dasharray: 4px !important;
fill-opacity: 0.2 !important;
}
.overlays-div {
font-size: 10px;
color: red;
width: 100px;
top: -20px !important;
}
}
<style lang="scss">
@import "../style/flow-viewer.scss";
</style>

191
ruoyi-ui/src/components/flow/ElInputTag/index.vue

@ -0,0 +1,191 @@
<template>
<div
class="el-input-tag input-tag-wrapper"
:class="[size ? 'el-input-tag--' + size : '']"
@click="focusTagInput">
<el-tag
v-for="(tag, idx) in innerTags"
v-bind="$attrs"
:key="tag"
:size="size"
effect="dark"
closable
:disable-transitions="false"
@close="remove(idx)">
{{tag}}
</el-tag>
<input
v-if="!readOnly"
class="tag-input"
:placeholder="placeholder"
@input="inputTag"
:value="newTag"
@keydown.delete.stop = "removeLastTag"
/>
<!-- @keydown = "addNew"
@blur = "addNew"-->
</div>
</template>
<script>
import {StrUtil} from '@/utils/StrUtil'
export default {
name: "ElInputTag",
/** 组件传值 */
props : {
value: {
type: String,
default: ""
},
addTagOnKeys: {
type: Array,
default: () => []
},
size: {
type: String,
default: 'small'
},
placeholder: String,
},
data() {
return {
newTag :"",
innerTags :[],
readOnly :true,
}
},
/** 传值监听 */
watch: {
value: {
handler(newVal) {
if (StrUtil.isNotBlank(newVal)) {
this.innerTags = newVal.split(',');
}else {
this.innerTags = [];
}
},
immediate: true, //
},
},
methods: {
focusTagInput() {
if (this.readOnly || !this.$el.querySelector('.tag-input')) {
return
} else {
this.$el.querySelector('.tag-input').focus()
}
},
inputTag(ev) {
this.newTag = ev.target.value
},
addNew(e) {
if (e && (!this.addTagOnKeys.includes(e.keyCode)) && (e.type !== 'blur')) {
return
}
if (e) {
e.stopPropagation()
e.preventDefault()
}
let addSuccess = false
if (this.newTag.includes(',')) {
this.newTag.split(',').forEach(item => {
if (this.addTag(item.trim())) {
addSuccess = true
}
})
} else {
if (this.addTag(this.newTag.trim())) {
addSuccess = true
}
}
if (addSuccess) {
this.tagChange()
this.newTag = ''
}
},
addTag(tag) {
tag = tag.trim()
if (tag && !this.innerTags.includes(tag)) {
this.innerTags.push(tag)
return true
}
return false
},
remove(index) {
this.innerTags.splice(index, 1)
this.tagChange();
},
removeLastTag() {
if (this.newTag) {
return
}
this.innerTags.pop()
this.tagChange()
},
tagChange() {
this.$emit('input', this.innerTags)
}
}
}
</script>
<style scoped>
.el-form-item.is-error .el-input-tag {
border-color: #f56c6c;
}
.input-tag-wrapper {
position: relative;
font-size: 14px;
background-color: #fff;
background-image: none;
border-radius: 4px;
border: 1px solid #dcdfe6;
box-sizing: border-box;
color: #606266;
display: inline-block;
outline: none;
padding: 0 10px 0 5px;
transition: border-color .2s cubic-bezier(.645,.045,.355,1);
width: 100%;
}
.el-tag {
margin-right: 4px;
}
.tag-input {
background: transparent;
border: 0;
font-size: inherit;
outline: none;
padding-left: 0;
width: 100px;
}
.el-input-tag {
min-height: 42px;
}
.el-input-tag--small {
min-height: 32px;
line-height: 32px;
font-size: 12px;
}
.el-input-tag--default {
min-height: 34px;
line-height: 34px;
}
.el-input-tag--large {
min-height: 36px;
line-height: 36px;
}
</style>

24
ruoyi-ui/src/components/flow/Expression/index.vue

@ -9,39 +9,33 @@
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
<el-option
v-for="dict in dict.type.sys_common_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="expressionList" row-key="id" @current-change="handleSingleExpSelect">
<el-table v-loading="loading" :data="expressionList" @current-change="handleSingleExpSelect">
<el-table-column width="55" align="center" >
<template slot-scope="scope">
<!-- 可以手动的修改label的值从而控制选择哪一项 -->
<el-radio v-model="radioSelected" :label="scope.row.id">{{''}}</el-radio>
</template>
</el-table-column>
<el-table-column label="主键" align="center" prop="id" />
<el-table-column label="名称" align="center" prop="name" />
<el-table-column label="表达式内容" align="center" prop="expression" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="表达式类型" align="center" prop="dataType" >
<template slot-scope="scope">
<dict-tag :options="dict.type.exp_data_type" :value="scope.row.dataType"/>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page-sizes="[5,10]"
layout="prev, pager, next"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
@ -50,12 +44,12 @@
</template>
<script>
import { listExpression } from "@/api/system/expression";
import { listExpression } from "@/api/flowable/expression";
import {StrUtil} from "@/utils/StrUtil";
export default {
name: "Expression",
dicts: ['sys_common_status'],
dicts: ['sys_common_status','exp_data_type'],
//
props: {
//

125
ruoyi-ui/src/components/flow/Listener/index.vue

@ -1,125 +0,0 @@
<template>
<div>
<el-row>
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="68px">
<el-form-item label="名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="监听类型" prop="type">
<el-select v-model="queryParams.type" placeholder="请选择监听类型" clearable>
<el-option
v-for="dict in dict.type.sys_listener_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<el-table v-loading="loading" :data="listenerList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center"/>
<el-table-column label="名称" align="center" prop="name"/>
<el-table-column label="监听类型" align="center" prop="type">
<template slot-scope="scope">
<dict-tag :options="dict.type.sys_listener_type" :value="scope.row.type"/>
</template>
</el-table-column>
<el-table-column label="事件类型" align="center" prop="eventType"/>
<el-table-column label="值类型" align="center" prop="valueType">
<template slot-scope="scope">
<dict-tag :options="dict.type.sys_listener_value_type" :value="scope.row.valueType"/>
</template>
</el-table-column>
<el-table-column label="执行内容" align="center" prop="value"/>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page-sizes="[5,10]"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
</div>
</template>
<script>
import { listListener } from '@/api/system/listener'
export default {
name: 'FlowListener',
dicts: ['sys_listener_value_type', 'sys_listener_type', 'common_status', 'sys_listener_event_type'],
props: {
selectValues: {
type: Number | String | Array,
default: null,
required: false
}
},
data() {
return {
/** 遮罩层 */
loading: true,
/** 流程监听表格数据 */
listenerList: [],
/** 总条数 */
total: 0,
/** 查询参数 */
queryParams: {
pageNum: 1,
pageSize: 5,
name: null,
type: null,
eventType: null,
valueType: null,
value: null,
status: null
}
}
},
mounted() {
this.getList()
},
methods: {
/** 查询流程监听列表 */
getList() {
this.loading = true
listListener(this.queryParams).then(response => {
this.listenerList = response.rows
this.total = response.total
this.loading = false
})
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm('queryForm')
this.handleQuery()
},
/** 多选框选中数据 */
handleSelectionChange(selection) {
this.$emit('handleSelect', selection)
}
}
}
</script>
<style scoped>
</style>

10
ruoyi-ui/src/components/flow/Role/index.vue

@ -50,6 +50,7 @@
v-show="total>0"
:total="total"
:page-sizes="[5,10]"
layout="prev, pager, next"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
@ -58,8 +59,7 @@
</template>
<script>
import { listRole, getRole, delRole, addRole, updateRole, dataScope, changeRoleStatus, deptTreeSelect } from "@/api/system/role";
import { treeselect as menuTreeselect, roleMenuTreeselect } from "@/api/system/menu";
import { listRole} from "@/api/system/role";
import {StrUtil} from "@/utils/StrUtil";
export default {
@ -109,7 +109,7 @@ export default {
},
//
form: {},
radioSelected: null, //
radioSelected: 0, //
selectRoleList: [] //
};
},
@ -138,9 +138,7 @@ export default {
});
});
}
},
immediate: true, //
deep: true //
}
}
},
created() {

35
ruoyi-ui/src/components/flow/User/index.vue

@ -39,15 +39,6 @@
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="手机号码" prop="phonenumber">
<el-input
v-model="queryParams.phonenumber"
placeholder="请输入手机号码"
clearable
style="width: 150px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
@ -77,6 +68,7 @@
v-show="total>0"
:total="total"
:page-sizes="[5,10]"
layout="prev, pager, next"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
@ -90,7 +82,7 @@
import { listUser, deptTreeSelect } from "@/api/system/user";
import Treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
import {StrUtil} from "@/utils/StrUtil";
import {StrUtil} from '@/utils/StrUtil'
export default {
name: "FlowUser",
@ -160,7 +152,7 @@ export default {
{ key: 5, label: `状态`, visible: true },
{ key: 6, label: `创建时间`, visible: true }
],
radioSelected: null, //
radioSelected: 0, //
selectUserList: [] //
};
},
@ -183,19 +175,18 @@ export default {
},
userList: {
handler(newVal) {
if (StrUtil.isNotBlank(newVal) && this.selectUserList.length > 0) {
this.$nextTick(() => {
this.$refs.dataTable.clearSelection();
this.selectUserList?.split(',').forEach(key => {
this.$refs.dataTable.toggleRowSelection(newVal.find(
item => key == item.userId
), true)
debugger
if (StrUtil.isNotBlank(newVal) && this.selectUserList.length > 0) {
this.$nextTick(() => {
this.$refs.dataTable.clearSelection();
this.selectUserList?.split(',').forEach(key => {
this.$refs.dataTable.toggleRowSelection(newVal.find(
item => key == item.userId
), true)
});
});
});
}
},
immediate: true, //
deep: true //
}
}
},
created() {

1
ruoyi-ui/src/components/vform/VFormDesigner.css

File diff suppressed because one or more lines are too long

74
ruoyi-ui/src/components/vform/VFormDesigner.umd.min.js

File diff suppressed because one or more lines are too long

11
ruoyi-ui/src/main.js

@ -38,12 +38,15 @@ import VueMeta from 'vue-meta'
// 字典数据组件
import DictData from '@/components/DictData'
import Tinymce from '@/components/tinymce/index.vue'
// vform 表单设计器
import vform from '@/components/vform/VFormDesigner.umd.min.js'
import '@/components/vform/VFormDesigner.css'
import modelerStore from '@/components/Process/common/global'
// 全局方法挂载
Vue.prototype.modelerStore = modelerStore
Vue.prototype.getDicts = getDicts
Vue.prototype.getConfigKey = getConfigKey
Vue.prototype.parseTime = parseTime
@ -62,7 +65,9 @@ Vue.component('Editor', Editor)
Vue.component('FileUpload', FileUpload)
Vue.component('ImageUpload', ImageUpload)
Vue.component('ImagePreview', ImagePreview)
Vue.component('tinymce', Tinymce)
//同时注册了v-form-designer、v-form-render等组件
Vue.use(vform)
Vue.use(directive)
Vue.use(plugins)

13
ruoyi-ui/src/router/index.js

@ -153,6 +153,19 @@ export const constantRoutes = [
}
]
},
{
path: '/flowable',
component: Layout,
hidden: true,
children: [
{
path: 'task/flowForm/index',
component: () => import('@/views/flowable/task/flowForm/index'),
name: 'FlowForm',
meta: { title: '流程表单', icon: '' }
}
]
},
{
path: '/tool',
component: Layout,

34
ruoyi-ui/src/views/flowable/definition/index.vue

@ -174,13 +174,13 @@
<!-- 流程图 -->
<el-dialog :title="readImage.title" :visible.sync="readImage.open" width="70%" append-to-body>
<!-- <el-image :src="readImage.src"></el-image> -->
<flow :flowData="flowData"/>
<bpmn-viewer :flowData="flowData"/>
</el-dialog>
<!--表单配置详情-->
<el-dialog :title="formTitle" :visible.sync="formConfOpen" width="50%" append-to-body>
<div class="test-form">
<parser :key="new Date().getTime()" :form-conf="formConf" />
<v-form-render :form-data="formData" ref="vFormRef"/>
</div>
</el-dialog>
@ -215,8 +215,8 @@
/>
</el-col>
<el-col :span="14" :xs="24">
<div v-if="currentRow">
<parser :key="new Date().getTime()" :form-conf="currentRow" />
<div class="test-form">
<v-form-render :form-data="formData" ref="vFormCurrentRowRef"/>
</div>
</el-col>
</el-row>
@ -248,16 +248,14 @@ import {
} from "@/api/flowable/definition";
import { getToken } from "@/utils/auth";
import { getForm, addDeployForm ,listForm } from "@/api/flowable/form";
import Parser from '@/components/parser/Parser'
import flow from '@/views/flowable/task/myProcess/send/flow'
import BpmnViewer from '@/components/Process/viewer';
import Model from './model';
export default {
name: "Definition",
dicts: ['sys_process_category'],
components: {
Parser,
flow,
BpmnViewer,
Model
},
data() {
@ -287,7 +285,7 @@ export default {
formDeployTitle: "",
formList: [],
formTotal:0,
formConf: {}, //
formData: {}, //
readImage:{
open: false,
src: "",
@ -432,7 +430,14 @@ export default {
getForm(formId).then(res =>{
this.formTitle = "表单详情";
this.formConfOpen = true;
this.formConf = JSON.parse(res.data.formContent)
this.$nextTick(() => {
//
this.$refs.vFormRef.setFormJson(JSON.parse(res.data.formContent))
this.$nextTick(() => {
//
this.$refs.vFormRef.disableForm();
})
})
})
},
/** 启动流程 */
@ -479,7 +484,14 @@ export default {
},
handleCurrentChange(data) {
if (data) {
this.currentRow = JSON.parse(data.formContent);
this.$nextTick(() => {
//
this.$refs.vFormCurrentRowRef.setFormJson(JSON.parse(data.formContent))
this.$nextTick(() => {
//
this.$refs.vFormCurrentRowRef.disableForm();
})
})
}
},
/** 挂起/激活流程 */

47
ruoyi-ui/src/views/flowable/definition/model.vue

@ -1,12 +1,8 @@
<template>
<div>
<bpmn-modeler
ref="refNode"
<bpmn-model
v-if="dataExit"
:xml="xml"
:users="users"
:groups="groups"
:categorys="categorys"
:exps="exps"
:is-view="false"
@save="save"
@showXML="showXML"
@ -22,15 +18,14 @@
</template>
<script>
import {readXml, roleList, saveXml, userList,expList} from "@/api/flowable/definition";
import bpmnModeler from '@/components/Process/index'
import BpmnModel from '@/components/Process'
import vkBeautify from 'vkbeautify'
import hljs from 'highlight.js'
import 'highlight.js/styles/atelier-savanna-dark.css'
export default {
name: "Model",
components: {
bpmnModeler,
BpmnModel,
vkBeautify
},
//
@ -68,14 +63,10 @@ export default {
return {
xml: "", // xml
modeler:"",
dataExit: false,
xmlOpen: false,
xmlTitle: '',
xmlData: '',
users: [],
groups: [],
categorys: [],
exps: [],
};
},
created () {
@ -84,9 +75,6 @@ export default {
if (deployId) {
this.getXmlData(deployId);
}
this.getDicts("sys_process_category").then(res => {
this.categorys = res.data;
});
this.getDataList()
},
methods: {
@ -114,22 +102,15 @@ export default {
},
/** 指定流程办理人员列表 */
getDataList() {
userList().then(res =>{
res.data.forEach(val =>{
val.userId = val.userId.toString();
})
this.users = res.data;
// let arr = {nickName: "", userId: "${INITIATOR}"}
// this.users.push(arr)
});
roleList().then(res =>{
res.data.forEach(val =>{
val.roleId = val.roleId.toString();
})
this.groups = res.data;
});
expList().then(res =>{
this.exps = res.data;
userList().then(res => {
this.modelerStore.userList = res.data;
})
roleList().then(res => {
this.modelerStore.roleList = res.data;
})
expList().then(res => {
this.modelerStore.expList = res.data;
this.dataExit = true;
});
},
/** 展示xml */

297
ruoyi-ui/src/views/flowable/expression/index.vue

@ -0,0 +1,297 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入表达式名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
<el-option
v-for="dict in dict.type.sys_common_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['system:expression:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['system:expression:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['system:expression:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['system:expression:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="expressionList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="主键" align="center" prop="id" />
<el-table-column label="名称" align="center" prop="name" />
<el-table-column label="表达式内容" align="center" prop="expression" />
<el-table-column label="指定类型" align="center" prop="dataType" >
<template slot-scope="scope">
<dict-tag :options="dict.type.exp_data_type" :value="scope.row.dataType"/>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['system:expression:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['system:expression:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改流程达式对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="名称" prop="name">
<el-input v-model="form.name" placeholder="请输入表达式名称" />
</el-form-item>
<el-form-item label="内容" prop="expression">
<el-input v-model="form.expression" placeholder="请输入表达式内容" />
</el-form-item>
<el-form-item label="指定类型" prop="dataType">
<el-radio-group v-model="form.dataType">
<el-radio
v-for="dict in dict.type.exp_data_type"
:key="dict.value"
:label="dict.value"
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio
v-for="dict in dict.type.sys_common_status"
:key="dict.value"
:label="parseInt(dict.value)"
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listExpression, getExpression, delExpression, addExpression, updateExpression } from "@/api/flowable/expression";
export default {
name: "FlowExp",
dicts: ['sys_common_status','exp_data_type'],
data() {
return {
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
expressionList: [],
//
title: "",
//
open: false,
//
queryParams: {
pageNum: 1,
pageSize: 10,
name: null,
expression: null,
status: null,
},
//
form: {
dataType: 'fixed'
},
//
rules: {
}
};
},
created() {
this.getList();
},
methods: {
/** 查询流程达式列表 */
getList() {
this.loading = true;
listExpression(this.queryParams).then(response => {
this.expressionList = response.rows;
this.total = response.total;
this.loading = false;
});
},
//
cancel() {
this.open = false;
this.reset();
},
//
reset() {
this.form = {
id: null,
name: null,
expression: null,
createTime: null,
updateTime: null,
createBy: null,
updateBy: null,
status: null,
remark: null
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length!==1
this.multiple = !selection.length
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加流程达式";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id || this.ids
getExpression(id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改流程达式";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.id != null) {
updateExpression(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
addExpression(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids;
this.$modal.confirm('是否确认删除流程达式编号为"' + ids + '"的数据项?').then(function() {
return delExpression(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 导出按钮操作 */
handleExport() {
this.download('system/expression/export', {
...this.queryParams
}, `expression_${new Date().getTime()}.xlsx`)
}
}
};
</script>

335
ruoyi-ui/src/views/flowable/listener/index.vue

@ -0,0 +1,335 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="监听类型" prop="type">
<el-select v-model="queryParams.type" placeholder="请选择监听类型" clearable>
<el-option
v-for="dict in dict.type.sys_listener_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['system:listener:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['system:listener:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['system:listener:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['system:listener:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="listenerList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="名称" align="center" prop="name" />
<el-table-column label="监听类型" align="center" prop="type">
<template slot-scope="scope">
<dict-tag :options="dict.type.sys_listener_type" :value="scope.row.type"/>
</template>
</el-table-column>
<el-table-column label="事件类型" align="center" prop="eventType"/>
<el-table-column label="值类型" align="center" prop="valueType">
<template slot-scope="scope">
<dict-tag :options="dict.type.sys_listener_value_type" :value="scope.row.valueType"/>
</template>
</el-table-column>
<el-table-column label="执行内容" align="center" prop="value" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['system:listener:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['system:listener:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改流程监听对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="名称" prop="name">
<el-input v-model="form.name" placeholder="请输入名称" />
</el-form-item>
<el-form-item label="监听类型" prop="type">
<el-select v-model="form.type" placeholder="请选择监听类型">
<el-option
v-for="dict in dict.type.sys_listener_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="事件类型" prop="eventType" v-if="form.type === '1'">
<el-select v-model="form.eventType" placeholder="请选择事件类型">
<el-option
v-for="dict in taskListenerEventList"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="事件类型" prop="eventType" v-else>
<el-select v-model="form.eventType" placeholder="请选择事件类型">
<el-option
v-for="dict in executionListenerEventList"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="值类型" prop="valueType">
<el-radio-group v-model="form.valueType">
<el-radio
v-for="dict in dict.type.sys_listener_value_type"
:key="dict.value"
:label="dict.value"
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="执行内容" prop="value">
<el-input v-model="form.value" placeholder="请输入执行内容" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listListener, getListener, delListener, addListener, updateListener } from "@/api/flowable/listener";
export default {
name: "Listener",
dicts: ['sys_listener_value_type', 'sys_listener_type', 'common_status', 'sys_listener_event_type'],
data() {
return {
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
listenerList: [],
//
title: "",
//
open: false,
//
queryParams: {
pageNum: 1,
pageSize: 10,
name: null,
type: null,
eventType: null,
valueType: null,
value: null,
status: null,
},
//
form: {},
//
rules: {
},
taskListenerEventList: [
{label: 'create', value: 'create'},
{label: 'assignment', value: 'assignment'},
{label: 'complete', value: 'complete'},
{label: 'delete', value: 'delete'},
],
executionListenerEventList: [
{label: 'start', value: 'start'},
{label: 'end', value: 'end'},
{label: 'take', value: 'take'},
],
};
},
created() {
this.getList();
},
methods: {
/** 查询流程监听列表 */
getList() {
this.loading = true;
listListener(this.queryParams).then(response => {
this.listenerList = response.rows;
this.total = response.total;
this.loading = false;
});
},
//
cancel() {
this.open = false;
this.reset();
},
//
reset() {
this.form = {
id: null,
name: null,
type: null,
eventType: null,
valueType: null,
value: null,
createTime: null,
updateTime: null,
createBy: null,
updateBy: null,
status: null,
remark: null
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length!==1
this.multiple = !selection.length
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加流程监听";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id || this.ids
getListener(id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改流程监听";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.id != null) {
updateListener(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
addListener(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids;
this.$modal.confirm('是否确认删除流程监听编号为"' + ids + '"的数据项?').then(function() {
return delListener(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 导出按钮操作 */
handleExport() {
this.download('system/listener/export', {
...this.queryParams
}, `listener_${new Date().getTime()}.xlsx`)
}
}
};
</script>

24
ruoyi-ui/src/views/flowable/task/finished/detail/flow.vue

@ -1,24 +0,0 @@
<template>
<div>
<flow-view :flowData="flowData"/>
</div>
</template>
<script>
import FlowView from './flowview'
export default {
name: "Flow",
components: {
FlowView
},
props: {
flowData: {
type: Object,
default: () => {}
}
},
data() {
return {};
}
};
</script>

41
ruoyi-ui/src/views/flowable/task/finished/detail/index.vue

@ -8,10 +8,8 @@
<el-tabs tab-position="top" v-model="activeName" @tab-click="handleClick">
<!--表单信息-->
<el-tab-pane label="表单信息" name="1">
<el-col :span="16" :offset="4" v-if="variableOpen">
<div class="test-form">
<parser :key="new Date().getTime()" :form-conf="variablesData" />
</div>
<el-col :span="16" :offset="4">
<v-form-render ref="vFormRef"/>
</el-col>
</el-tab-pane>
<!--流程流转记录-->
@ -61,7 +59,7 @@
</el-col>
</el-tab-pane>
<el-tab-pane label="流程图" name="3">
<flow :flowData="flowData"/>
<Bpmn-viewer :flowData="flowData" :procInsId="taskForm.procInsId"/>
</el-tab-pane>
</el-tabs>
</el-card>
@ -70,16 +68,14 @@
<script>
import {flowRecord} from "@/api/flowable/finished";
import Parser from '@/components/parser/Parser'
import {getProcessVariables, flowXmlAndNode} from "@/api/flowable/definition";
import flow from '@/views/flowable/task/finished/detail/flow'
import BpmnViewer from '@/components/Process/viewer';
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
export default {
name: "Record",
components: {
Parser,
flow,
BpmnViewer,
},
props: {},
data() {
@ -87,12 +83,6 @@ export default {
// xml
flowData: {},
activeName: '1',
//
userList: null,
defaultProps: {
children: "children",
label: "label"
},
//
queryParams: {
deptId: undefined
@ -100,8 +90,6 @@ export default {
//
loading: true,
flowRecordList: [], //
formConfCopy: {},
src: null,
taskForm:{
multiple: false,
comment:"", //
@ -111,18 +99,13 @@ export default {
taskId: "" ,//
procDefId: "", //
vars: "",
targetKey:""
},
variables: [], //
variablesData: {}, //
variableOpen: false, //
};
},
created() {
this.taskForm.deployId = this.$route.query && this.$route.query.deployId;
this.taskForm.taskId = this.$route.query && this.$route.query.taskId;
this.taskForm.procInsId = this.$route.query && this.$route.query.procInsId;
//
//
if (this.taskForm.taskId){
this.processVariables( this.taskForm.taskId)
@ -166,8 +149,18 @@ export default {
if (taskId) {
//
getProcessVariables(taskId).then(res => {
this.variablesData = res.data.variables;
this.variableOpen = true
//
this.$nextTick(() => {
this.$refs.vFormRef.setFormJson(res.data.formJson);
this.$nextTick(() => {
//
this.$refs.vFormRef.setFormData(res.data);
this.$nextTick(() => {
//
this.$refs.vFormRef.disableForm();
})
})
})
});
}
},

0
ruoyi-ui/src/views/flowable/task/finished/index.vue

123
ruoyi-ui/src/views/flowable/task/flowForm/index.vue

@ -0,0 +1,123 @@
<template>
<div>
<v-form-designer ref="vfDesigner" :designer-config="designerConfig">
<!-- 保存按钮 -->
<template #customSaveButton>
<el-button type="text" @click="saveFormJson"><i class="el-icon-s-promotion" />保存</el-button>
</template>
</v-form-designer>
<!--系统表单信息-->
<el-dialog :title="formTitle" :visible.sync="formOpen" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="表单名称" prop="formName">
<el-input v-model="form.formName" placeholder="请输入表单名称" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {addForm, getForm, updateForm} from "@/api/flowable/form";
import { StrUtil } from '@/utils/StrUtil'
export default {
name: "flowForm",
data() {
return {
formTitle: "",
formOpen: false,
//
rules: {
formName: [
{ required: true, message: "表单名称不能为空", trigger: "blur" }
]
},
//
form: {
formId: null,
formName: null,
formContent: null,
remark: null
},
designerConfig: {
generateSFCButton: false,
exportCodeButton: false, //
toolbarMaxWidth: 320,
toolbarMinWidth: 300, //
formHeader: false,
},
}
},
mounted() {
const formId = this.$route.query && this.$route.query.formId;
if (StrUtil.isNotBlank(formId)) {
getForm(formId).then(res => {
this.$nextTick(() => {
// json
this.$refs.vfDesigner.setFormJson(JSON.parse(res.data.formContent))
})
this.form = res.data;
})
}else {
this.$nextTick(() => {
// json
this.$refs.vfDesigner.setFormJson({"widgetList":[],"formConfig":{"modelName":"formData","refName":"vForm","rulesName":"rules","labelWidth":80,"labelPosition":"left","size":"","labelAlign":"label-left-align","cssCode":"","customClass":"","functions":"","layoutType":"PC","onFormCreated":"","onFormMounted":"","onFormDataChange":"","onFormValidate":""}})
})
}
},
methods:{
//
saveFormJson() {
let formJson = this.$refs.vfDesigner.getFormJson()
this.form.formContent = JSON.stringify(formJson);
this.formOpen = true;
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.formId != null) {
updateForm(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.formOpen = false;
});
} else {
addForm(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.formOpen = false;
});
}
//
const obj = { path: "/flowable/form", query: { t: Date.now()} };
this.$tab.closeOpenPage(obj);
}
});
},
//
cancel() {
this.formOpen = false;
this.reset();
},
}
}
</script>
<style lang="scss" scoped>
body {
margin: 0; /* 如果页面出现垂直滚动条,则加入此行CSS以消除之 */
}
.el-container.main-container{
background: #fff;
margin-left: 0 !important;
}
</style>

193
ruoyi-ui/src/views/flowable/task/form/index.vue

@ -24,20 +24,8 @@
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['flowable:form:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['flowable:form:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
@ -49,16 +37,6 @@
v-hasPermi="['flowable:form:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['flowable:form:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@ -120,10 +98,40 @@
</div>
</el-dialog>
<!--表单配置详情-->
<el-dialog :title="formTitle" :visible.sync="formConfOpen" width="60%" append-to-body>
<div class="test-form">
<parser :key="new Date().getTime()" :form-conf="formConf" />
<!--表单详情-->
<el-dialog :title="formTitle" :visible.sync="formRenderOpen" width="60%" append-to-body>
<v-form-render :form-data="formData" ref="vFormRef"/>
</el-dialog>
<!--表单设计器-->
<el-dialog
custom-class="dialogClass"
:visible.sync="dialogVisible"
:close-on-press-escape="false"
:fullscreen=true
:before-close="handleClose"
append-to-body>
<v-form-designer ref="vfDesigner" :designer-config="designerConfig">
<!-- 自定义按钮插槽演示 -->
<template #customSaveButton>
<el-button type="text" @click="saveFormJson"><i class="el-icon-s-promotion" />保存</el-button>
</template>
</v-form-designer>
</el-dialog>
<!--系统表单信息-->
<el-dialog :title="formTitle" :visible.sync="formOpen" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="表单名称" prop="formName">
<el-input v-model="form.formName" placeholder="请输入表单名称" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
@ -132,17 +140,19 @@
<script>
import { listForm, getForm, delForm, addForm, updateForm, exportForm } from "@/api/flowable/form";
import Editor from '@/components/Editor';
import Parser from '@/components/parser/Parser'
export default {
name: "Form",
components: {
Editor,
Parser
Editor
},
data() {
return {
//
loading: true,
dialogVisible: false,
designerConfig: {
exportCodeButton: false, //
},
//
ids: [],
//
@ -157,9 +167,9 @@ export default {
formList: [],
//
title: "",
formConf: {}, //
formConfOpen: false,
formRenderOpen: false,
formTitle: "",
formOpen: false,
//
open: false,
//
@ -170,15 +180,26 @@ export default {
formContent: null,
},
//
form: {},
form: {
formId: null,
formName: null,
formContent: null,
remark: null
},
//
rules: {
}
rules: {},
formData: {},
};
},
created() {
this.getList();
},
activated() {
const time = this.$route.query.t;
if (time != null) {
this.getList();
}
},
methods: {
/** 查询流程表单列表 */
getList() {
@ -189,11 +210,6 @@ export default {
this.loading = false;
});
},
//
cancel() {
this.open = false;
this.reset();
},
//
reset() {
this.form = {
@ -225,28 +241,57 @@ export default {
this.multiple = !selection.length
},
/** 表单配置信息 */
handleDetail(row){
this.formConfOpen = true;
this.formTitle = "流程表单配置详细";
this.formConf = JSON.parse(row.formContent)
handleDetail(row) {
this.formRenderOpen = true;
this.formTitle = "表单详情";
this.$nextTick(() => {
//
this.$refs.vFormRef.setFormJson(JSON.parse(row.formContent))
this.$nextTick(() => {
//
this.$refs.vFormRef.disableForm();
})
})
},
/** 新增按钮操作 */
handleAdd() {
// this.reset();
// this.open = true;
// this.title = "";
this.$router.push({ path: '/tool/build/index', query: {formId: null }})
// this.dialogVisible = true;
this.$router.push({ path: '/flowable/task/flowForm/index'})
},
//
saveFormJson() {
let formJson = this.$refs.vfDesigner.getFormJson()
this.form.formContent = JSON.stringify(formJson);
this.formOpen = true;
},
//
cancel() {
this.formOpen = false;
this.reset();
},
handleClose(done) {
this.$confirm('确定要关闭吗?关闭未保存的修改都会丢失?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
done();
}).catch(() => {});
},
/** 修改按钮操作 */
handleUpdate(row) {
// this.reset();
// const formId = row.formId || this.ids
// getForm(formId).then(response => {
// this.form = response.data;
// this.open = true;
// this.title = "";
// });
this.$router.push({ path: '/tool/build/index', query: {formId: row.formId }})
// this.form = row;
// this.dialogVisible = true;
// this.$nextTick(() => {
// // json
// this.$refs.vfDesigner.setFormJson(JSON.parse(row.formContent))
// })
this.$router.push({ path: '/flowable/task/flowForm/index', query: {formId: row.formId }})
},
/** 重置表单 */
resetFormData() {
this.$refs.vFormRef.resetForm();
},
/** 提交按钮 */
submitForm() {
@ -255,23 +300,34 @@ export default {
if (this.form.formId != null) {
updateForm(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.formOpen = false;
this.getList();
});
} else {
addForm(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.formOpen = false;
this.getList();
});
}
this.dialogVisible = false;
}
});
},
/** 提交按钮 */
submitFormData() {
this.$refs.vFormRef.getFormData().then(formData => {
// Form Validation OK
console.log(JSON.stringify(formData))
}).catch(error => {
// Form Validation failed
this.$modal.msgError(error)
})
},
/** 删除按钮操作 */
handleDelete(row) {
const formIds = row.formId || this.ids;
this.$confirm('是否确认删除流程表单编号为"' + formIds + '"的数据项?', "警告", {
this.$confirm('是否确认删除表单编号为"' + formIds + '"的数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
@ -282,27 +338,20 @@ export default {
this.$modal.msgSuccess("删除成功");
})
},
/** 导出按钮操作 */
handleExport() {
const queryParams = this.queryParams;
this.$confirm('是否确认导出所有流程表单数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return exportForm(queryParams);
}).then(response => {
this.download(response.msg);
})
}
}
};
</script>
<style lang="scss" scoped>
<style scoped>
.test-form {
margin: 15px auto;
width: 800px;
padding: 15px;
}
/deep/ .dialogClass .el-dialog__header {
padding: 0;
}
/deep/ .dialogClass .el-dialog__body {
padding: 0;
}
</style>

24
ruoyi-ui/src/views/flowable/task/myProcess/detail/flow.vue

@ -1,24 +0,0 @@
<template>
<div>
<flow-view :flowData="flowData"/>
</div>
</template>
<script>
import FlowView from './flowview'
export default {
name: "Flow",
components: {
FlowView
},
props: {
flowData: {
type: Object,
default: () => {}
}
},
data() {
return {};
}
};
</script>

238
ruoyi-ui/src/views/flowable/task/myProcess/detail/flowview.vue

@ -1,238 +0,0 @@
<template>
<div class="containers main-box">
<el-button type="success"
size="small"
icon="el-icon-zoom-in"
@click="zoomViewport(true)">放大</el-button>
<el-button type="warning"
size="small"
icon="el-icon-zoom-out"
@click="zoomViewport(false)">缩小</el-button>
<el-button type="info"
size="small"
icon="el-icon-rank"
@click="fitViewport">适中</el-button>
<div class="canvas" ref="flowCanvas"></div>
</div>
</template>
<script>
import { CustomViewer as BpmnViewer } from "@/components/customBpmn";
export default {
name: "FlowView",
props: {
flowData: {
type: Object,
default: () => {}
},
},
data() {
return {
bpmnViewer: null
};
},
watch: {
flowData: {
handler(newVal) {
if (Object.keys(newVal).length > 0) {
//
this.bpmnViewer && this.bpmnViewer.destroy();
this.bpmnViewer = new BpmnViewer({
container: this.$refs.flowCanvas,
height: 'calc(100vh - 200px)',
});
this.loadFlowCanvas(newVal);
}
},
immediate: true, //
deep: true //
}
},
mounted() {
},
methods: {
//
async loadFlowCanvas(flowData) {
const self = this
try {
await self.bpmnViewer.importXML(flowData.xmlData);
self.fitViewport()
if (flowData.nodeData !==undefined && flowData.nodeData.length > 0 ) {
self.fillColor(flowData.nodeData)
}
} catch (err) {
console.error(err.message, err.warnings)
}
},
// class
setNodeColor(nodeCodes, colorClass, canvas) {
for (let i = 0; i < nodeCodes.length; i++) {
canvas.addMarker(nodeCodes[i], colorClass);
}
},
//
fitViewport() {
this.zoom = this.bpmnViewer.get('canvas').zoom("fit-viewport", "auto")
},
//
zoomViewport(zoomIn = true) {
this.zoom = this.bpmnViewer.get('canvas').zoom()
this.zoom += (zoomIn ? 0.1 : -0.1)
if(this.zoom >= 0.2) this.bpmnViewer.get('canvas').zoom(this.zoom)
},
//
fillColor(nodeData) {
const canvas = this.bpmnViewer.get('canvas')
this.bpmnViewer.getDefinitions().rootElements[0].flowElements.forEach(n => {
const completeTask = nodeData.find(m => m.key === n.id)
const todoTask = nodeData.find(m => !m.completed)
const endTask = nodeData[nodeData.length - 1]
if (n.$type === 'bpmn:UserTask') {
if (completeTask) {
canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
n.outgoing?.forEach(nn => {
const targetTask = nodeData.find(m => m.key === nn.targetRef.id)
if (targetTask) {
if (todoTask && completeTask.key === todoTask.key && !todoTask.completed){
canvas.addMarker(nn.id, todoTask.completed ? 'highlight' : 'highlight-todo')
canvas.addMarker(nn.targetRef.id, todoTask.completed ? 'highlight' : 'highlight-todo')
}else {
canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
}
}
})
}
}
//
else if (n.$type === 'bpmn:ExclusiveGateway') {
if (completeTask) {
canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
n.outgoing?.forEach(nn => {
const targetTask = nodeData.find(m => m.key === nn.targetRef.id)
if (targetTask) {
canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
}
})
}
}
//
else if (n.$type === 'bpmn:ParallelGateway') {
if (completeTask) {
canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
n.outgoing?.forEach(nn => {
const targetTask = nodeData.find(m => m.key === nn.targetRef.id)
if (targetTask) {
canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
}
})
}
}
else if (n.$type === 'bpmn:StartEvent') {
n.outgoing.forEach(nn => {
const completeTask = nodeData.find(m => m.key === nn.targetRef.id)
if (completeTask) {
canvas.addMarker(nn.id, 'highlight')
canvas.addMarker(n.id, 'highlight')
return
}
})
}
else if (n.$type === 'bpmn:EndEvent') {
if (endTask.key === n.id && endTask.completed) {
canvas.addMarker(n.id, 'highlight')
return
}
}
})
},
}
};
</script>
<style lang="scss">
.bjs-powered-by {
display: none;
}
.view-mode {
.el-header, .el-aside, .djs-palette, .bjs-powered-by {
display: none;
}
.el-loading-mask {
background-color: initial;
}
.el-loading-spinner {
display: none;
}
}
.containers {
// background-color: #ffffff;
width: 100%;
height: 100%;
.canvas {
width: 100%;
height: 100%;
}
.panel {
position: absolute;
right: 0;
top: 50px;
width: 300px;
}
.load {
margin-right: 10px;
}
.el-form-item__label{
font-size: 13px;
}
.djs-palette{
left: 0px!important;
top: 0px;
border-top: none;
}
.djs-container svg {
min-height: 650px;
}
.highlight.djs-shape .djs-visual > :nth-child(1) {
fill: green !important;
stroke: green !important;
fill-opacity: 0.2 !important;
}
.highlight.djs-shape .djs-visual > :nth-child(2) {
fill: green !important;
}
.highlight.djs-shape .djs-visual > path {
fill: green !important;
fill-opacity: 0.2 !important;
stroke: green !important;
}
.highlight.djs-connection > .djs-visual > path {
stroke: green !important;
}
.highlight-todo.djs-connection > .djs-visual > path {
stroke: orange !important;
stroke-dasharray: 4px !important;
fill-opacity: 0.2 !important;
}
.highlight-todo.djs-shape .djs-visual > :nth-child(1) {
fill: orange !important;
stroke: orange !important;
stroke-dasharray: 4px !important;
fill-opacity: 0.2 !important;
}
.overlays-div {
font-size: 10px;
color: red;
width: 100px;
top: -20px !important;
}
}
</style>

57
ruoyi-ui/src/views/flowable/task/myProcess/detail/index.vue

@ -9,9 +9,7 @@
<!--表单信息-->
<el-tab-pane label="表单信息" name="1">
<el-col :span="16" :offset="4">
<div class="test-form">
<parser :key="new Date().getTime()" :form-conf="variablesData" />
</div>
<v-form-render ref="vFormRef"/>
</el-col>
</el-tab-pane>
<!--流程流转记录-->
@ -62,7 +60,7 @@
</el-tab-pane>
<!--流程图-->
<el-tab-pane label="流程图" name="3">
<flow :flowData="flowData"/>
<bpmn-viewer :flowData="flowData" :procInsId="taskForm.procInsId"/>
</el-tab-pane>
</el-tabs>
</el-card>
@ -71,16 +69,14 @@
<script>
import {flowRecord} from "@/api/flowable/finished";
import Parser from '@/components/parser/Parser'
import {getProcessVariables, readXml, getFlowViewer, getHighlight, flowXmlAndNode} from "@/api/flowable/definition";
import flow from '@/views/flowable/task/myProcess/detail/flow'
import {getProcessVariables, flowXmlAndNode} from "@/api/flowable/definition";
import BpmnViewer from '@/components/Process/viewer';
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
export default {
name: "Record",
components: {
Parser,
flow
BpmnViewer
},
props: {},
data() {
@ -89,9 +85,7 @@ export default {
flowData: {},
activeName: '1',
//
queryParams: {
deptId: undefined
},
queryParams: {},
//
loading: true,
flowRecordList: [], //
@ -104,14 +98,12 @@ export default {
taskId: "" ,//
procDefId: "", //
},
variablesData: {}, //
};
},
created() {
this.taskForm.deployId = this.$route.query && this.$route.query.deployId;
this.taskForm.taskId = this.$route.query && this.$route.query.taskId;
this.taskForm.procInsId = this.$route.query && this.$route.query.procInsId;
//
//
this.processVariables( this.taskForm.taskId)
this.getFlowRecordList(this.taskForm.procInsId, this.taskForm.deployId);
@ -124,11 +116,6 @@ export default {
})
}
},
getFlowViewer(procInsId,executionId) {
getFlowViewer(procInsId,executionId).then(res => {
this.taskList = res.data
})
},
setIcon(val) {
if (val) {
return "el-icon-check";
@ -153,31 +140,23 @@ export default {
this.goBack();
})
},
fillFormData(form, data) {
form.fields.forEach((item) => {
const vModel = item.__vModel__;
const val = data[item.__vModel__];
// el-upload
if (item.__config__.tag === "el-upload") {
//
item["file-list"] = (val || []).map((url) => ({
name: `${vModel}${i}`,
url,
}));
}
if (val) {
item.__config__.defaultValue = val;
}
});
},
/** 获取流程变量内容 */
processVariables(taskId) {
if (taskId) {
//
getProcessVariables(taskId).then(res => {
this.variablesData = res.data.variables;
this.$nextTick(() => {
//
this.$refs.vFormRef.setFormJson(res.data.formJson);
this.$nextTick(() => {
//
this.$refs.vFormRef.setFormData(res.data);
this.$nextTick(() => {
//
this.$refs.vFormRef.disableForm();
})
})
})
});
}
},

0
ruoyi-ui/src/views/flowable/task/myProcess/index.vue

24
ruoyi-ui/src/views/flowable/task/myProcess/send/flow.vue

@ -1,24 +0,0 @@
<template>
<div>
<flow-view :flowData="flowData"/>
</div>
</template>
<script>
import FlowView from './flowview'
export default {
name: "Flow",
components: {
FlowView
},
props: {
flowData: {
type: Object,
default: () => {}
},
},
data() {
return {};
}
};
</script>

129
ruoyi-ui/src/views/flowable/task/myProcess/send/flowview.vue

@ -1,129 +0,0 @@
<template>
<div class="containers main-box">
<el-button type="success"
size="small"
icon="el-icon-zoom-in"
@click="zoomViewport(true)">放大</el-button>
<el-button type="warning"
size="small"
icon="el-icon-zoom-out"
@click="zoomViewport(false)">缩小</el-button>
<el-button type="info"
size="small"
icon="el-icon-rank"
@click="fitViewport">适中</el-button>
<div class="canvas" ref="flowCanvas"></div>
</div>
</template>
<script>
import { CustomViewer as BpmnViewer } from "@/components/customBpmn";
export default {
name: "FlowView",
props: {
flowData: {
type: Object,
default: () => {}
},
},
data() {
return {
bpmnViewer: null
};
},
watch: {
flowData: {
handler(newVal) {
if (Object.keys(newVal).length > 0) {
//
this.bpmnViewer && this.bpmnViewer.destroy();
this.bpmnViewer = new BpmnViewer({
container: this.$refs.flowCanvas,
height: 'calc(100vh - 200px)',
});
this.loadFlowCanvas(newVal)
}
},
immediate: true, //
deep: true //
}
},
mounted() {},
methods: {
//
async loadFlowCanvas(flowData) {
const self = this
try {
await self.bpmnViewer.importXML(flowData.xmlData);
self.fitViewport()
} catch (err) {
console.error(err.message, err.warnings)
}
},
//
fitViewport() {
this.zoom = this.bpmnViewer.get('canvas').zoom("fit-viewport", "auto")
},
//
zoomViewport(zoomIn = true) {
this.zoom = this.bpmnViewer.get('canvas').zoom()
this.zoom += (zoomIn ? 0.1 : -0.1)
if(this.zoom >= 0.2) this.bpmnViewer.get('canvas').zoom(this.zoom)
},
}
};
</script>
<style lang="scss">
.bjs-powered-by {
display: none;
}
.view-mode {
.el-header, .el-aside, .djs-palette, .bjs-powered-by {
display: none;
}
.el-loading-mask {
background-color: initial;
}
.el-loading-spinner {
display: none;
}
}
.containers {
// background-color: #ffffff;
width: 100%;
height: 100%;
.canvas {
width: 100%;
height: 100%;
}
.panel {
position: absolute;
right: 0;
top: 50px;
width: 300px;
}
.load {
margin-right: 10px;
}
.el-form-item__label{
font-size: 13px;
}
.djs-palette{
left: 0px!important;
top: 0px;
border-top: none;
}
.djs-container svg {
min-height: 650px;
}
.overlays-div {
font-size: 10px;
color: red;
width: 100px;
top: -20px !important;
}
}
</style>

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save