2
0

5 Commity d3487138b6 ... dd80339d7d

Autor SHA1 Správa Dátum
  Yue dd80339d7d Update 字典调整成左右分屏的模式 2 týždňov pred
  Yue 9ec5a9ac80 Update 优化分割面板组件,支持传入是否可以拖拽 2 týždňov pred
  Yue 0441c37464 Update 优化富文本支持图片上传 2 týždňov pred
  Yue 4c7f2e66da Add 添加echarts通用调用组件 2 týždňov pred
  Yue 4f3f0326c3 Update 优化工作流代码实现 2 týždňov pred
56 zmenil súbory, kde vykonal 1821 pridanie a 944 odobranie
  1. 177 143
      SERVER/VberAdminPlusV3/.script/sql/flow.sql
  2. 1 1
      SERVER/VberAdminPlusV3/pom.xml
  3. 2 3
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/dto/StartProcessDTO.java
  4. 8 2
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/service/UserService.java
  5. 2 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/service/WorkflowService.java
  6. 21 22
      SERVER/VberAdminPlusV3/vber-common/vber-common-encrypt/src/main/java/com/vber/common/encrypt/interceptor/MybatisEncryptInterceptor.java
  7. 1 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/helper/DataBaseHelper.java
  8. 3 0
      SERVER/VberAdminPlusV3/vber-modules/vber-generator/src/main/java/com/vber/generator/service/GenTableServiceImpl.java
  9. 15 3
      SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysDictTypeServiceImpl.java
  10. 18 0
      SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysUserServiceImpl.java
  11. 15 21
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/common/constant/FlowConstant.java
  12. 53 0
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/common/enums/TaskOperationEnum.java
  13. 16 0
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/controller/FlwDefinitionController.java
  14. 24 1
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/controller/FlwInstanceController.java
  15. 9 9
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/controller/FlwSpelController.java
  16. 6 4
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/controller/FlwTaskController.java
  17. 1 2
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/FlowSpel.java
  18. 2 2
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/BackProcessBo.java
  19. 2 3
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/FlowCopyBo.java
  20. 1 1
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/FlowInstanceBo.java
  21. 2 1
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/FlowNextNodeBo.java
  22. 3 3
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/FlowSpelBo.java
  23. 15 0
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/FlowTaskBo.java
  24. 4 4
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/StartProcessBo.java
  25. 7 3
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/TaskOperationBo.java
  26. 6 2
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/vo/FlowHisTaskVo.java
  27. 4 5
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/vo/FlowInstanceVo.java
  28. 1 3
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/vo/FlowSpelVo.java
  29. 9 4
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/vo/FlowTaskVo.java
  30. 17 10
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/handler/FlowProcessEventHandler.java
  31. 60 14
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/listener/WorkflowGlobalListener.java
  32. 1 1
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/mapper/FlwSpelMapper.java
  33. 1 1
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/IFlwInstanceService.java
  34. 7 3
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/IFlwNodeExtService.java
  35. 12 12
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/IFlwSpelService.java
  36. 9 6
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwCategoryServiceImpl.java
  37. 28 13
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwCommonServiceImpl.java
  38. 28 15
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwDefinitionServiceImpl.java
  39. 110 118
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwInstanceServiceImpl.java
  40. 107 8
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwNodeExtServiceImpl.java
  41. 12 12
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwSpelServiceImpl.java
  42. 240 177
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwTaskServiceImpl.java
  43. 14 6
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/TestLeaveServiceImpl.java
  44. 15 25
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/WorkflowServiceImpl.java
  45. 7 2
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/resources/mapper/workflow/FlwTaskMapper.xml
  46. 95 8
      UI/VAP_V3.VUE/src/components/editor/VbEditor.vue
  47. 16 3
      UI/VAP_V3.VUE/src/components/process/ProcessMeddle.vue
  48. 22 9
      UI/VAP_V3.VUE/src/components/process/SubmitVerify.vue
  49. 16 4
      UI/VAP_V3.VUE/src/components/split-panel/VbSplitPanel.vue
  50. 109 0
      UI/VAP_V3.VUE/src/core/plugins/echarts.ts
  51. 5 1
      UI/VAP_V3.VUE/src/core/services/RequestService.ts
  52. 174 0
      UI/VAP_V3.VUE/src/core/use/use-echarts.ts
  53. 30 25
      UI/VAP_V3.VUE/src/views/system/dict/_data.vue
  54. 221 0
      UI/VAP_V3.VUE/src/views/system/dict/_type.vue
  55. 27 225
      UI/VAP_V3.VUE/src/views/system/dict/index.vue
  56. 10 2
      UI/VAP_V3.VUE/src/views/workflow/processInstance/index.vue

+ 177 - 143
SERVER/VberAdminPlusV3/.script/sql/flow.sql

@@ -5,46 +5,49 @@ USE
 -- ----------------------------
 CREATE TABLE `flow_definition`
 (
-    `id`              bigint       NOT NULL AUTO_INCREMENT COMMENT '主键id',
-    `flow_code`       varchar(40)  NOT NULL COMMENT '流程编码',
-    `flow_name`       varchar(100) NOT NULL COMMENT '流程名称',
-    `category`        varchar(100) DEFAULT NULL COMMENT '流程类别',
-    `version`         varchar(20)  NOT NULL COMMENT '流程版本',
+    `id`              bigint          NOT NULL COMMENT '主键id',
+    `flow_code`       varchar(40)     NOT NULL COMMENT '流程编码',
+    `flow_name`       varchar(100)    NOT NULL COMMENT '流程名称',
+    `model_value`     varchar(40)     NOT NULL DEFAULT 'CLASSICS' COMMENT '设计器模型(CLASSICS经典模型 MIMIC仿钉钉模型)',
+    `category`        varchar(100)             DEFAULT NULL COMMENT '流程类别',
+    `version`         varchar(20)     NOT NULL COMMENT '流程版本',
     `is_publish`      tinyint(1)      NOT NULL DEFAULT '0' COMMENT '是否发布(0未发布 1已发布 9失效)',
-    `form_custom`     char(1)      DEFAULT 'N' COMMENT '审批表单是否自定义(Y是 N否)',
-    `form_path`       varchar(100) DEFAULT NULL COMMENT '审批表单路径',
+    `form_custom`     char(1)                  DEFAULT 'N' COMMENT '审批表单是否自定义(Y是 N否)',
+    `form_path`       varchar(100)             DEFAULT NULL COMMENT '审批表单路径',
     `activity_status` tinyint(1)      NOT NULL DEFAULT '1' COMMENT '流程激活状态(0挂起 1激活)',
-    `listener_type`   varchar(100) DEFAULT NULL COMMENT '监听器类型',
-    `listener_path`   varchar(400) DEFAULT NULL COMMENT '监听器路径',
-    `ext`             varchar(500) DEFAULT NULL COMMENT '业务详情 存业务表对象json字符串',
-    `create_time`     datetime     DEFAULT NULL COMMENT '创建时间',
-    `update_time`     datetime     DEFAULT NULL COMMENT '更新时间',
-    `del_flag`        char(1)      DEFAULT '0' COMMENT '删除标志',
-    `tenant_id`       varchar(40)  DEFAULT NULL COMMENT '租户id',
+    `listener_type`   varchar(100)             DEFAULT NULL COMMENT '监听器类型',
+    `listener_path`   varchar(400)             DEFAULT NULL COMMENT '监听器路径',
+    `ext`             varchar(500)             DEFAULT NULL COMMENT '业务详情 存业务表对象json字符串',
+    `create_time`     datetime                 DEFAULT NULL COMMENT '创建时间',
+    `create_by`       varchar(64)          DEFAULT '' COMMENT '创建人',
+    `update_time`     datetime                 DEFAULT NULL COMMENT '更新时间',
+    `update_by`       varchar(64)          DEFAULT '' COMMENT '更新人',
+    `del_flag`        char(1)                  DEFAULT '0' COMMENT '删除标志',
+    `tenant_id`       varchar(40)              DEFAULT NULL COMMENT '租户id',
     PRIMARY KEY (`id`) USING BTREE
 ) ENGINE = InnoDB COMMENT ='流程定义表';
 
 CREATE TABLE `flow_node`
 (
-    `id`              bigint       NOT NULL AUTO_INCREMENT COMMENT '主键id',
+    `id`              bigint        NOT NULL COMMENT '主键id',
     `node_type`       tinyint(1)      NOT NULL COMMENT '节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)',
-    `definition_id`   bigint       NOT NULL COMMENT '流程定义id',
-    `node_code`       varchar(100) NOT NULL COMMENT '流程节点编码',
+    `definition_id`   bigint          NOT NULL COMMENT '流程定义id',
+    `node_code`       varchar(100)    NOT NULL COMMENT '流程节点编码',
     `node_name`       varchar(100)  DEFAULT NULL COMMENT '流程节点名称',
     `permission_flag` varchar(200)  DEFAULT NULL COMMENT '权限标识(权限类型:权限标识,可以多个,用@@隔开)',
-    `node_ratio`      decimal(6, 3) DEFAULT NULL COMMENT '流程签署比例值',
+    `node_ratio`      varchar(200)  DEFAULT NULL COMMENT '流程签署比例值',
     `coordinate`      varchar(100)  DEFAULT NULL COMMENT '坐标',
     `any_node_skip`   varchar(100)  DEFAULT NULL COMMENT '任意结点跳转',
     `listener_type`   varchar(100)  DEFAULT NULL COMMENT '监听器类型',
     `listener_path`   varchar(400)  DEFAULT NULL COMMENT '监听器路径',
-    `handler_type`    varchar(100)  DEFAULT NULL COMMENT '处理器类型',
-    `handler_path`    varchar(400)  DEFAULT NULL COMMENT '处理器路径',
     `form_custom`     char(1)       DEFAULT 'N' COMMENT '审批表单是否自定义(Y是 N否)',
     `form_path`       varchar(100)  DEFAULT NULL COMMENT '审批表单路径',
-    `version`         varchar(20)  NOT NULL COMMENT '版本',
+    `version`         varchar(20)     NOT NULL COMMENT '版本',
     `create_time`     datetime      DEFAULT NULL COMMENT '创建时间',
+    `create_by`       varchar(64)          DEFAULT '' COMMENT '创建人',
     `update_time`     datetime      DEFAULT NULL COMMENT '更新时间',
-    `ext`             text COMMENT '扩展属性',
+    `update_by`       varchar(64)          DEFAULT '' COMMENT '更新人',
+    `ext`             text          COMMENT '节点扩展属性',
     `del_flag`        char(1)       DEFAULT '0' COMMENT '删除标志',
     `tenant_id`       varchar(40)   DEFAULT NULL COMMENT '租户id',
     PRIMARY KEY (`id`) USING BTREE
@@ -52,18 +55,20 @@ CREATE TABLE `flow_node`
 
 CREATE TABLE `flow_skip`
 (
-    `id`             bigint       NOT NULL AUTO_INCREMENT COMMENT '主键id',
-    `definition_id`  bigint       NOT NULL COMMENT '流程定义id',
-    `now_node_code`  varchar(100) NOT NULL COMMENT '当前流程节点的编码',
+    `id`             bigint       NOT NULL COMMENT '主键id',
+    `definition_id`  bigint          NOT NULL COMMENT '流程定义id',
+    `now_node_code`  varchar(100)    NOT NULL COMMENT '当前流程节点的编码',
     `now_node_type`  tinyint(1)   DEFAULT NULL COMMENT '当前节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)',
-    `next_node_code` varchar(100) NOT NULL COMMENT '下一个流程节点的编码',
+    `next_node_code` varchar(100)    NOT NULL COMMENT '下一个流程节点的编码',
     `next_node_type` tinyint(1)   DEFAULT NULL COMMENT '下一个节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)',
     `skip_name`      varchar(100) DEFAULT NULL COMMENT '跳转名称',
     `skip_type`      varchar(40)  DEFAULT NULL COMMENT '跳转类型(PASS审批通过 REJECT退回)',
     `skip_condition` varchar(200) DEFAULT NULL COMMENT '跳转条件',
     `coordinate`     varchar(100) DEFAULT NULL COMMENT '坐标',
     `create_time`    datetime     DEFAULT NULL COMMENT '创建时间',
+    `create_by`       varchar(64)          DEFAULT '' COMMENT '创建人',
     `update_time`    datetime     DEFAULT NULL COMMENT '更新时间',
+    `update_by`       varchar(64)          DEFAULT '' COMMENT '更新人',
     `del_flag`       char(1)      DEFAULT '0' COMMENT '删除标志',
     `tenant_id`      varchar(40)  DEFAULT NULL COMMENT '租户id',
     PRIMARY KEY (`id`) USING BTREE
@@ -71,28 +76,29 @@ CREATE TABLE `flow_skip`
 
 CREATE TABLE `flow_instance`
 (
-    `id`              bigint      NOT NULL AUTO_INCREMENT COMMENT '主键id',
+    `id`              bigint      NOT NULL COMMENT '主键id',
     `definition_id`   bigint      NOT NULL COMMENT '对应flow_definition表的id',
     `business_id`     varchar(40) NOT NULL COMMENT '业务id',
     `node_type`       tinyint(1)  NOT NULL COMMENT '节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)',
     `node_code`       varchar(40) NOT NULL COMMENT '流程节点编码',
-    `node_name`       varchar(100) DEFAULT NULL COMMENT '流程节点名称',
+    `node_name`       varchar(100)         DEFAULT NULL COMMENT '流程节点名称',
     `variable`        text COMMENT '任务变量',
     `flow_status`     varchar(20) NOT NULL COMMENT '流程状态(0待提交 1审批中 2审批通过 4终止 5作废 6撤销 8已完成 9已退回 10失效 11拿回)',
     `activity_status` tinyint(1)  NOT NULL DEFAULT '1' COMMENT '流程激活状态(0挂起 1激活)',
     `def_json`        text COMMENT '流程定义json',
-    `create_by`       varchar(64)  DEFAULT '' COMMENT '创建者',
-    `create_time`     datetime     DEFAULT NULL COMMENT '创建时间',
-    `update_time`     datetime     DEFAULT NULL COMMENT '更新时间',
-    `ext`             TEXT         DEFAULT NULL COMMENT '扩展字段,预留给业务系统使用',
-    `del_flag`        char(1)      DEFAULT '0' COMMENT '删除标志',
-    `tenant_id`       varchar(40)  DEFAULT NULL COMMENT '租户id',
+    `create_time`     datetime             DEFAULT NULL COMMENT '创建时间',
+    `create_by`       varchar(64)          DEFAULT '' COMMENT '创建人',
+    `update_time`     datetime             DEFAULT NULL COMMENT '更新时间',
+    `update_by`       varchar(64)          DEFAULT '' COMMENT '更新人',
+    `ext`             varchar(500)         DEFAULT NULL COMMENT '扩展字段,预留给业务系统使用',
+    `del_flag`        char(1)              DEFAULT '0' COMMENT '删除标志',
+    `tenant_id`       varchar(40)          DEFAULT NULL COMMENT '租户id',
     PRIMARY KEY (`id`) USING BTREE
 ) ENGINE = InnoDB COMMENT ='流程实例表';
 
 CREATE TABLE `flow_task`
 (
-    `id`            bigint       NOT NULL AUTO_INCREMENT COMMENT '主键id',
+    `id`            bigint       NOT NULL COMMENT '主键id',
     `definition_id` bigint       NOT NULL COMMENT '对应flow_definition表的id',
     `instance_id`   bigint       NOT NULL COMMENT '对应flow_instance表的id',
     `node_code`     varchar(100) NOT NULL COMMENT '节点编码',
@@ -102,7 +108,9 @@ CREATE TABLE `flow_task`
     `form_custom`   char(1)      DEFAULT 'N' COMMENT '审批表单是否自定义(Y是 N否)',
     `form_path`     varchar(100) DEFAULT NULL COMMENT '审批表单路径',
     `create_time`   datetime     DEFAULT NULL COMMENT '创建时间',
+    `create_by`       varchar(64)          DEFAULT '' COMMENT '创建人',
     `update_time`   datetime     DEFAULT NULL COMMENT '更新时间',
+    `update_by`       varchar(64)          DEFAULT '' COMMENT '更新人',
     `del_flag`      char(1)      DEFAULT '0' COMMENT '删除标志',
     `tenant_id`     varchar(40)  DEFAULT NULL COMMENT '租户id',
     PRIMARY KEY (`id`) USING BTREE
@@ -110,94 +118,48 @@ CREATE TABLE `flow_task`
 
 CREATE TABLE `flow_his_task`
 (
-    `id`               bigint(20)          NOT NULL AUTO_INCREMENT COMMENT '主键id',
-    `definition_id`    bigint(20)          NOT NULL COMMENT '对应flow_definition表的id',
-    `instance_id`      bigint(20)          NOT NULL COMMENT '对应flow_instance表的id',
-    `task_id`          bigint(20)          NOT NULL COMMENT '对应flow_task表的id',
-    `node_code`        varchar(100) DEFAULT NULL COMMENT '开始节点编码',
-    `node_name`        varchar(100) DEFAULT NULL COMMENT '开始节点名称',
+    `id`               bigint(20)                   NOT NULL COMMENT '主键id',
+    `definition_id`    bigint(20)                   NOT NULL COMMENT '对应flow_definition表的id',
+    `instance_id`      bigint(20)                   NOT NULL COMMENT '对应flow_instance表的id',
+    `task_id`          bigint(20)                   NOT NULL COMMENT '对应flow_task表的id',
+    `node_code`        varchar(100)                 DEFAULT NULL COMMENT '开始节点编码',
+    `node_name`        varchar(100)                 DEFAULT NULL COMMENT '开始节点名称',
     `node_type`        tinyint(1)                   DEFAULT NULL COMMENT '开始节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)',
-    `target_node_code` varchar(200) DEFAULT NULL COMMENT '目标节点编码',
-    `target_node_name` varchar(200) DEFAULT NULL COMMENT '结束节点名称',
-    `approver`         varchar(40)  DEFAULT NULL COMMENT '审批者',
-    `cooperate_type`   tinyint(1)          NOT NULL DEFAULT '0' COMMENT '协作方式(1审批 2转办 3委派 4会签 5票签 6加签 7减签)',
-    `collaborator`     varchar(40)  DEFAULT NULL COMMENT '协作人',
-    `skip_type`        varchar(10) NOT NULL COMMENT '流转类型(PASS通过 REJECT退回 NONE无动作)',
-    `flow_status`      varchar(20) NOT NULL COMMENT '流程状态(0待提交 1审批中 2审批通过 4终止 5作废 6撤销 8已完成 9已退回 10失效 11拿回)',
-    `form_custom`      char(1)      DEFAULT 'N' COMMENT '审批表单是否自定义(Y是 N否)',
-    `form_path`        varchar(100) DEFAULT NULL COMMENT '审批表单路径',
-    `message`          varchar(500) DEFAULT NULL COMMENT '审批意见',
-    `variable`         TEXT         DEFAULT NULL COMMENT '任务变量',
-    `ext`              TEXT         DEFAULT NULL COMMENT '业务详情 存业务表对象json字符串',
-    `create_time`      datetime     DEFAULT NULL COMMENT '任务开始时间',
-    `update_time`      datetime     DEFAULT NULL COMMENT '审批完成时间',
-    `del_flag`         char(1)      DEFAULT '0' COMMENT '删除标志',
-    `tenant_id`        varchar(40)  DEFAULT NULL COMMENT '租户id',
+    `target_node_code` varchar(200)                 DEFAULT NULL COMMENT '目标节点编码',
+    `target_node_name` varchar(200)                 DEFAULT NULL COMMENT '结束节点名称',
+    `approver`         varchar(40)                  DEFAULT NULL COMMENT '审批人',
+    `cooperate_type`   tinyint(1)                   NOT NULL DEFAULT '0' COMMENT '协作方式(1审批 2转办 3委派 4会签 5票签 6加签 7减签)',
+    `collaborator`     varchar(500)                 DEFAULT NULL COMMENT '协作人',
+    `skip_type`        varchar(10)                  NOT NULL COMMENT '流转类型(PASS通过 REJECT退回 NONE无动作)',
+    `flow_status`      varchar(20)                  NOT NULL COMMENT '流程状态(0待提交 1审批中 2审批通过 4终止 5作废 6撤销 8已完成 9已退回 10失效 11拿回)',
+    `form_custom`      char(1)                      DEFAULT 'N' COMMENT '审批表单是否自定义(Y是 N否)',
+    `form_path`        varchar(100)                 DEFAULT NULL COMMENT '审批表单路径',
+    `message`          varchar(500)                 DEFAULT NULL COMMENT '审批意见',
+    `variable`         TEXT                         DEFAULT NULL COMMENT '任务变量',
+    `ext`              TEXT                         DEFAULT NULL COMMENT '业务详情 存业务表对象json字符串',
+    `create_time`      datetime                     DEFAULT NULL COMMENT '任务开始时间',
+    `update_time`      datetime                     DEFAULT NULL COMMENT '审批完成时间',
+    `del_flag`         char(1)                      DEFAULT '0' COMMENT '删除标志',
+    `tenant_id`        varchar(40)                  DEFAULT NULL COMMENT '租户id',
     PRIMARY KEY (`id`) USING BTREE
 ) ENGINE = InnoDB COMMENT ='历史任务记录表';
 
-CREATE TABLE `flow_spel`
-(
-    `id`             bigint(20) NOT NULL COMMENT '主键id',
-    `component_name` varchar(255) DEFAULT NULL COMMENT '组件名称',
-    `method_name`    varchar(255) DEFAULT NULL COMMENT '方法名',
-    `method_params`  varchar(255) DEFAULT NULL COMMENT '参数',
-    `view_spel`      varchar(255) DEFAULT NULL COMMENT '预览spel表达式',
-    `remark`         varchar(255) DEFAULT NULL COMMENT '备注',
-    `status`         char(1)      DEFAULT '0' COMMENT '状态(0正常 1停用)',
-    `del_flag`       char(1)      DEFAULT '0' COMMENT '删除标志',
-    `create_org`     bigint(20) DEFAULT NULL COMMENT '创建组织机构',
-    `create_by`      bigint(20) DEFAULT NULL COMMENT '创建者',
-    `create_time`    datetime     DEFAULT NULL COMMENT '创建时间',
-    `update_by`      bigint(20) DEFAULT NULL COMMENT '更新者',
-    `update_time`    datetime     DEFAULT NULL COMMENT '更新时间',
-    PRIMARY KEY (`id`)
-) ENGINE = InnoDB COMMENT='流程spel表达式定义表';
-
-
-INSERT INTO `flow_spel` (`id`, `component_name`, `method_name`, `method_params`, `view_spel`, `remark`, `status`,
-                         `del_flag`, `create_org`, `create_by`, `create_time`, `update_by`, `update_time`)
-VALUES (1, 'spelRuleComponent', 'selectOrgLeaderById', 'initiatorOrgId',
-        '#{@spelRuleComponent.selectOrgLeaderById(#initiatorOrgId)}', '根据组织机构id获取组织机构负责人', '0', '0', 100,
-        1, sysdate(), 1, sysdate()),
-       (2, NULL, NULL, 'initiator', '${initiator}', '流程发起人', '0', '0', 103, 1, sysdate(), 1, sysdate());
-
--- ----------------------------
--- 流程实例业务扩展表
--- ----------------------------
-
-create table flow_instance_biz_ext
-(
-    id             bigint not null comment '主键id',
-    tenant_id      varchar(20) default '000000' null comment '租户编号',
-    create_dept    bigint null comment '创建部门',
-    create_by      bigint null comment '创建者',
-    create_time    datetime null comment '创建时间',
-    update_by      bigint null comment '更新者',
-    update_time    datetime null comment '更新时间',
-    business_code  varchar(255) null comment '业务编码',
-    business_title varchar(1000) null comment '业务标题',
-    del_flag       char        default '0' null comment '删除标志(0代表存在 1代表删除)',
-    instance_id    bigint null comment '流程实例Id',
-    business_id    varchar(255) null comment '业务Id',
-    PRIMARY KEY (id)
-) ENGINE = InnoDB COMMENT '流程实例业务扩展表';
-
 
 CREATE TABLE `flow_user`
 (
-    `id`           bigint  NOT NULL AUTO_INCREMENT COMMENT '主键id',
-    `type`         char(1) NOT NULL COMMENT '人员类型(1待办任务的审批人权限 2待办任务的转办人权限 3待办任务的委托人权限)',
+    `id`           bigint      NOT NULL COMMENT '主键id',
+    `type`         char(1)         NOT NULL COMMENT '人员类型(1待办任务的审批人权限 2待办任务的转办人权限 3待办任务的委托人权限)',
     `processed_by` varchar(80) DEFAULT NULL COMMENT '权限人',
-    `associated`   bigint  NOT NULL COMMENT '任务表id',
+    `associated`   bigint          NOT NULL COMMENT '任务表id',
     `create_time`  datetime    DEFAULT NULL COMMENT '创建时间',
     `create_by`    varchar(80) DEFAULT NULL COMMENT '创建人',
     `update_time`  datetime    DEFAULT NULL COMMENT '更新时间',
+    `update_by`       varchar(64)          DEFAULT '' COMMENT '创建人',
     `del_flag`     char(1)     DEFAULT '0' COMMENT '删除标志',
     `tenant_id`    varchar(40) DEFAULT NULL COMMENT '租户id',
     PRIMARY KEY (`id`) USING BTREE,
-    KEY            `user_processed_type` (`processed_by`, `type`),
-    KEY            `user_associated` (`associated`) USING BTREE
+    KEY `user_processed_type` (`processed_by`, `type`),
+    KEY `user_associated` (`associated`) USING BTREE
 ) ENGINE = InnoDB COMMENT ='流程用户表';
 
 -- ----------------------------
@@ -205,21 +167,88 @@ CREATE TABLE `flow_user`
 -- ----------------------------
 create table flow_category
 (
-    category_id   bigint(20)  not null  AUTO_INCREMENT comment '流程分类ID',
+    category_id   bigint(20)  not null comment '流程分类ID',
     tenant_id     varchar(20)  default '000000' comment '租户编号',
     parent_id     bigint(20)   default 0 comment '父流程分类id',
     ancestors     varchar(500) default '' comment '祖级列表',
     category_name varchar(30) not null comment '流程分类名称',
     order_num     int(4)       default 0 comment '显示顺序',
     del_flag      char(1)      default '0' comment '删除标志(0代表存在 1代表删除)',
-    create_org    bigint(20)  null comment '创建组织机构',
+    create_dept   bigint(20)  null comment '创建部门',
     create_by     bigint(20)  null comment '创建者',
-    create_time   datetime null comment '创建时间',
+    create_time   datetime    null comment '创建时间',
     update_by     bigint(20)  null comment '更新者',
-    update_time   datetime null comment '更新时间',
+    update_time   datetime    null comment '更新时间',
     primary key (category_id)
 ) engine = innodb comment = '流程分类';
 
+-- ----------------------------
+-- 流程spel表达式定义表
+-- ----------------------------
+
+CREATE TABLE flow_spel (
+    id bigint(20) NOT NULL COMMENT '主键id',
+    component_name varchar(255) DEFAULT NULL COMMENT '组件名称',
+    method_name varchar(255) DEFAULT NULL COMMENT '方法名',
+    method_params varchar(255) DEFAULT NULL COMMENT '参数',
+    view_spel varchar(255) DEFAULT NULL COMMENT '预览spel表达式',
+    remark varchar(255) DEFAULT NULL COMMENT '备注',
+    status char(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
+    del_flag char(1) DEFAULT '0' COMMENT '删除标志',
+    create_dept bigint(20) DEFAULT NULL COMMENT '创建部门',
+    create_by bigint(20) DEFAULT NULL COMMENT '创建者',
+    create_time datetime DEFAULT NULL COMMENT '创建时间',
+    update_by bigint(20) DEFAULT NULL COMMENT '更新者',
+    update_time datetime DEFAULT NULL COMMENT '更新时间',
+    PRIMARY KEY (id)
+) ENGINE = InnoDB COMMENT='流程spel表达式定义表';
+
+
+-- ----------------------------
+-- 流程实例业务扩展表
+-- ----------------------------
+
+create table flow_instance_biz_ext (
+    id             bigint                       not null comment '主键id',
+    tenant_id      varchar(20) default '000000' null comment '租户编号',
+    create_dept    bigint                       null comment '创建部门',
+    create_by      bigint                       null comment '创建者',
+    create_time    datetime                     null comment '创建时间',
+    update_by      bigint                       null comment '更新者',
+    update_time    datetime                     null comment '更新时间',
+    business_code  varchar(255)                 null comment '业务编码',
+    business_title varchar(1000)                null comment '业务标题',
+    del_flag       char        default '0'      null comment '删除标志(0代表存在 1代表删除)',
+    instance_id    bigint                       null comment '流程实例Id',
+    business_id    varchar(255)                 null comment '业务Id',
+    PRIMARY KEY (id)
+)  ENGINE = InnoDB COMMENT '流程实例业务扩展表';
+
+-- ----------------------------
+-- 请假单信息
+-- ----------------------------
+
+create table test_leave
+(
+    id          bigint(20)   not null comment 'id',
+    tenant_id   varchar(20)  default '000000' comment '租户编号',
+    apply_code  varchar(50)  not null comment '申请编号',
+    leave_type  varchar(255) not null comment '请假类型',
+    start_date  datetime     not null comment '开始时间',
+    end_date    datetime     not null comment '结束时间',
+    leave_days  int(10)      not null comment '请假天数',
+    remark      varchar(255) null comment '请假原因',
+    status      varchar(255) null comment '状态',
+    create_dept bigint       null comment '创建部门',
+    create_by   bigint       null comment '创建者',
+    create_time datetime     null comment '创建时间',
+    update_by   bigint       null comment '更新者',
+    update_time datetime     null comment '更新时间',
+    PRIMARY KEY (id) USING BTREE
+) ENGINE = InnoDB COMMENT = '请假申请表';
+
+
+
 INSERT INTO flow_category
 values (0, '000000', NULL, '', '根目录', 0, '0', 100, 1, sysdate(), null, null);
 INSERT INTO flow_category
@@ -243,27 +272,15 @@ values (108, '000000', 102, '0,100,102', '转正', 1, '0', 100, 1, sysdate(), nu
 INSERT INTO flow_category
 values (109, '000000', 102, '0,100,102', '离职', 2, '0', 100, 1, sysdate(), null, null);
 
--- ----------------------------
--- 请假单信息
--- ----------------------------
-create table test_leave
-(
-    id          bigint(20)   not null AUTO_INCREMENT comment 'id',
-    tenant_id   varchar(20) default '000000' comment '租户编号',
-    apply_code  varchar(50)  not null comment '申请编号',
-    leave_type  varchar(255) not null comment '请假类型',
-    start_date  datetime     not null comment '开始时间',
-    end_date    datetime     not null comment '结束时间',
-    leave_days  int(10)      not null comment '请假天数',
-    remark      varchar(255) null comment '请假原因',
-    status      varchar(255) null comment '状态',
-    create_org  bigint null comment '创建组织机构',
-    create_by   bigint null comment '创建者',
-    create_time datetime null comment '创建时间',
-    update_by   bigint null comment '更新者',
-    update_time datetime null comment '更新时间',
-    PRIMARY KEY (id) USING BTREE
-) ENGINE = InnoDB COMMENT = '请假申请表';
+
+INSERT INTO `flow_spel` (`id`, `component_name`, `method_name`, `method_params`, `view_spel`, `remark`, `status`,
+                         `del_flag`, `create_org`, `create_by`, `create_time`, `update_by`, `update_time`)
+VALUES (1, 'spelRuleComponent', 'selectOrgLeaderById', 'initiatorOrgId',
+        '#{@spelRuleComponent.selectOrgLeaderById(#initiatorOrgId)}', '根据组织机构id获取组织机构负责人', '0', '0', 100,
+        1, sysdate(), 1, sysdate()),
+       (2, NULL, NULL, 'initiator', '${initiator}', '流程发起人', '0', '0', 103, 1, sysdate(), 1, sysdate());
+
+
 
 -- 工作流菜单
 INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache,
@@ -298,29 +315,46 @@ VALUES (4, '工作流', 0, 4, 'workflow', '', '', '1', '0', 'M', '0', '0', '', '
         'btn btn-light-success', 'handleUpload', 100, 1, SYSDATE(), NULL, NULL, ''),
        (1136, '导出流程', 153, 6, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:definition:export', 'cloud-download',
         'btn btn-light-info', 'handleExport', 100, 1, SYSDATE(), NULL, NULL, ''),
-       (1137, '复制流程', 153, 7, '#', '', '', 1, 0, 'F', '1', '0', 'workflow:definition:copy', 'cloud-download',
+       (1137, '复制流程', 153, 7, '#', '', '', 1, 0, 'F', '1', '1', 'workflow:definition:copy', 'cloud-download',
         'btn btn-light-info', 'handleCopy', 100, 1, SYSDATE(), NULL, NULL, ''),
-       (1138, '发布流程', 153, 8, '#', '', '', 1, 0, 'F', '1', '0', 'workflow:definition:publish', 'cloud-download',
+       (1138, '发布流程/取消发布', 153, 8, '#', '', '', 1, 0, 'F', '1', '0', 'workflow:definition:publish', 'cloud-download',
         'btn btn-light-info', 'handlePublish', 100, 1, SYSDATE(), NULL, NULL, ''),
+       (1139, '激活/挂起流程', 153, 9, '#', '', '', 1, 0, 'F', '1', '0', 'workflow:definition:active', 'cloud-upload',
+        'btn btn-light-success', 'handleUpload', 100, 1, SYSDATE(), NULL, NULL, ''),
 
        (154, '流程监控', 4, 4, 'wf_monitor', '', '', '1', '0', 'M', '0', '0', 'workflow:monitor', 'display', '', '',
         100, 1, SYSDATE(), NULL, NULL, ''),
        (155, '流程实例', 154, 1, 'processInstance', 'workflow/processInstance/index', '', '1', '1', 'C', '0', '0',
         'workflow:monitor:instance', 'database', '', '', 100, 1, SYSDATE(), NULL, NULL, ''),
+       (1141, '流程实例查询', 155, 1, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:instance:query', 'eye', '', '', 100,
+                1, sysdate(), NULL, NULL, ''),
+       (1142, '流程实例激活/挂起', 155, 2, '#', '', '', 1, 0, 'F', '1', '0', 'workflow:instance:active', 'eye', '', '', 100,
+                1, sysdate(), NULL, NULL, ''),
+       (1143, '流程实例删除', 155, 3, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:instance:remove', 'dash-square',
+        'btn btn-light-danger', 'handleDelete@0', 100, 1, SYSDATE(), NULL, NULL, ''),
+       (1144, '流程实例作废', 155, 4, '#', '', '', 1, 0, 'F', '1', '0', 'workflow:instance:invalid', '#',
+        'btn btn-light-danger', '', 100, 1, SYSDATE(), NULL, NULL, ''),
+       (1145, '流程实例撤销', 155, 5, '#', '', '', 1, 0, 'F', '1', '0', 'workflow:instance:cancel', '#',
+        'btn btn-light-danger', '', 100, 1, SYSDATE(), NULL, NULL, ''),
+       (1147, '流程变量查询', 155, 6, '#', '', '', 1, 0, 'F', '1', '0', 'workflow:instance:variableQuery', 'eye', '', '', 100,
+                1, sysdate(), NULL, NULL, ''),
+       (1148, '流程变量修改', 155, 7, '#', '', '', 1, 0, 'F', '1', '0', 'workflow:instance:variableEdit', 'pencil-square',
+        'btn btn-light-success', '', 100, 1, SYSDATE(), NULL, NULL, ''),
+
        (156, '待办任务', 154, 2, 'allTaskWaiting', 'workflow/task/allTaskWaiting', '', '1', '1', 'C', '0', '0',
         'workflow:monitor:task', 'list-task', '', '', 100, 1, SYSDATE(), NULL, NULL, ''),
 
-       (157, '流程spel表达式', 4, 5, 'spel', 'workflow/spel/index', '', 1, 0, 'C', '0', '0', 'workflow:spel', 'input',
-        '', '', 100, 1, sysdate(), 1, sysdate(), '流程spel达式定义菜单'),
-       (1141, '流程spel达式查询', 157, 1, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:spel:query', 'eye', '', '', 100,
+       (157, '流程表达式', 4, 5, 'spel', 'workflow/spel/index', '', 1, 0, 'C', '0', '0', 'workflow:spel', 'input',
+        '', '', 100, 1, sysdate(), 1, sysdate(), '流程spel达式定义菜单'),
+       (1151, '流程spel表达式查询', 157, 1, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:spel:query', 'eye', '', '', 100,
         1, sysdate(), NULL, NULL, ''),
-       (1142, '流程spel达式新增', 157, 2, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:spel:add', 'plus-square',
+       (1152, '流程表达式新增', 157, 2, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:spel:add', 'plus-square',
         'btn btn-light-primary', 'handleCreate', 100, 1, sysdate(), NULL, NULL, ''),
-       (1143, '流程spel达式修改', 157, 3, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:spel:edit', 'pencil-square',
+       (1153, '流程表达式修改', 157, 3, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:spel:edit', 'pencil-square',
         'btn btn-light-success', 'handleUpdate@1', 100, 1, sysdate(), NULL, NULL, ''),
-       (1144, '流程spel达式删除', 157, 4, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:spel:remove', 'dash-square',
+       (1154, '流程表达式删除', 157, 4, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:spel:remove', 'dash-square',
         'btn btn-light-danger', 'handleDelete@0', 100, 1, sysdate(), NULL, NULL, ''),
-       (1145, '流程spel达式导出', 157, 5, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:spel:export', 'cloud-download',
+       (1155, '流程表达式导出', 157, 5, '#', '', '', 1, 0, 'F', '0', '0', 'workflow:spel:export', 'cloud-download',
         'btn btn-light-info', 'handleExport', 100, 1, sysdate(), NULL, NULL, ''),
 
        (5, '我的任务', 0, 5, 'task', '', '', '1', '0', 'M', '1', '0', 'workflow:task', 'award', '', '', 100, 1,

+ 1 - 1
SERVER/VberAdminPlusV3/pom.xml

@@ -57,7 +57,7 @@
         <anyline.version>8.7.3-20251210</anyline.version>
 
         <!-- 工作流配置 -->
-        <warm-flow.version>1.8.1</warm-flow.version>
+        <warm-flow.version>1.8.4</warm-flow.version>
 
         <!-- 插件版本 -->
         <maven-jar-plugin.version>3.4.2</maven-jar-plugin.version>

+ 2 - 3
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/dto/StartProcessDTO.java

@@ -1,6 +1,5 @@
 package com.vber.common.core.domain.dto;
 
-
 import cn.hutool.core.util.ObjectUtil;
 import lombok.Data;
 
@@ -21,7 +20,6 @@ public class StartProcessDTO implements Serializable {
     @Serial
     private static final long serialVersionUID = 1L;
 
-
     /**
      * 业务唯一值id
      */
@@ -49,7 +47,8 @@ public class StartProcessDTO implements Serializable {
 
     public Map<String, Object> getVariables() {
         if (variables == null) {
-            return new HashMap<>(16);
+            variables = new HashMap<>(16);
+            return variables;
         }
         variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
         return variables;

+ 8 - 2
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/service/UserService.java

@@ -31,12 +31,11 @@ public interface UserService {
     /**
      * 通过用户ID查询用户账户
      *
-     * @param userIds 用户ID  多个用逗号隔开
+     * @param userIds 用户ID 多个用逗号隔开
      * @return 用户昵称
      */
     String selectNicknameByIds(String userIds);
 
-
     /**
      * 通过用户ID查询用户列表
      *
@@ -85,5 +84,12 @@ public interface UserService {
      */
     Map<Long, String> selectUserNamesByIds(List<Long> userIds);
 
+    /**
+     * 根据用户 ID 列表查询用户昵称映射关系
+     *
+     * @param userIds 用户 ID 列表
+     * @return Map,其中 key 为用户 ID,value 为对应的用户昵称
+     */
+    Map<Long, String> selectUserNicksByIds(List<Long> userIds);
 
 }

+ 2 - 1
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/service/WorkflowService.java

@@ -13,13 +13,14 @@ import java.util.Map;
  * @author Iwb
  */
 public interface WorkflowService {
+
     /**
      * 运行中的实例 删除程实例,删除历史记录,删除业务与流程关联信息
      *
      * @param businessIds 业务id
      * @return 结果
      */
-    boolean deleteInstance(List<Long> businessIds);
+    boolean deleteInstance(List<String> businessIds);
 
     /**
      * 获取当前流程状态

+ 21 - 22
SERVER/VberAdminPlusV3/vber-common/vber-common-encrypt/src/main/java/com/vber/common/encrypt/interceptor/MybatisEncryptInterceptor.java

@@ -13,10 +13,7 @@ import com.vber.common.encrypt.properties.EncryptorProperties;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.ibatis.executor.parameter.ParameterHandler;
-import org.apache.ibatis.plugin.Interceptor;
-import org.apache.ibatis.plugin.Intercepts;
-import org.apache.ibatis.plugin.Invocation;
-import org.apache.ibatis.plugin.Signature;
+import org.apache.ibatis.plugin.*;
 
 import java.lang.reflect.Field;
 import java.sql.PreparedStatement;
@@ -29,10 +26,7 @@ import java.util.*;
  * @version 4.6.0
  */
 @Slf4j
-@Intercepts({@Signature(
-        type = ParameterHandler.class,
-        method = "setParameters",
-        args = {PreparedStatement.class})
+@Intercepts({ @Signature(type = ParameterHandler.class, method = "setParameters", args = { PreparedStatement.class })
 })
 @AllArgsConstructor
 public class MybatisEncryptInterceptor implements Interceptor {
@@ -42,19 +36,19 @@ public class MybatisEncryptInterceptor implements Interceptor {
 
     @Override
     public Object intercept(Invocation invocation) throws Throwable {
-        return invocation;
-    }
-
-    @Override
-    public Object plugin(Object target) {
+        Object target = invocation.getTarget();
         if (target instanceof ParameterHandler parameterHandler) {
-            // 进行加密操作
             Object parameterObject = parameterHandler.getParameterObject();
             if (ObjectUtil.isNotNull(parameterObject) && !(parameterObject instanceof String)) {
                 this.encryptHandler(parameterObject);
             }
         }
-        return target;
+        return invocation.proceed();
+    }
+
+    @Override
+    public Object plugin(Object target) {
+        return Plugin.wrap(target, this);
     }
 
     /**
@@ -76,7 +70,8 @@ public class MybatisEncryptInterceptor implements Interceptor {
             }
             // 判断第一个元素是否含有注解。如果没有直接返回,提高效率
             Object firstItem = list.get(0);
-            if (ObjectUtil.isNull(firstItem) || CollUtil.isEmpty(encryptorManager.getFieldCache(firstItem.getClass()))) {
+            if (ObjectUtil.isNull(firstItem)
+                    || CollUtil.isEmpty(encryptorManager.getFieldCache(firstItem.getClass()))) {
                 return;
             }
             list.forEach(this::encryptHandler);
@@ -109,15 +104,19 @@ public class MybatisEncryptInterceptor implements Interceptor {
         }
         EncryptField encryptField = field.getAnnotation(EncryptField.class);
         EncryptContext encryptContext = new EncryptContext();
-        encryptContext.setAlgorithm(encryptField.algorithm() == AlgorithmType.DEFAULT ? defaultProperties.getAlgorithm() : encryptField.algorithm());
-        encryptContext.setEncode(encryptField.encode() == EncodeType.DEFAULT ? defaultProperties.getEncode() : encryptField.encode());
-        encryptContext.setPassword(StringUtils.isBlank(encryptField.password()) ? defaultProperties.getPassword() : encryptField.password());
-        encryptContext.setPrivateKey(StringUtils.isBlank(encryptField.privateKey()) ? defaultProperties.getPrivateKey() : encryptField.privateKey());
-        encryptContext.setPublicKey(StringUtils.isBlank(encryptField.publicKey()) ? defaultProperties.getPublicKey() : encryptField.publicKey());
+        encryptContext.setAlgorithm(encryptField.algorithm() == AlgorithmType.DEFAULT ? defaultProperties.getAlgorithm()
+                : encryptField.algorithm());
+        encryptContext.setEncode(
+                encryptField.encode() == EncodeType.DEFAULT ? defaultProperties.getEncode() : encryptField.encode());
+        encryptContext.setPassword(StringUtils.isBlank(encryptField.password()) ? defaultProperties.getPassword()
+                : encryptField.password());
+        encryptContext.setPrivateKey(StringUtils.isBlank(encryptField.privateKey()) ? defaultProperties.getPrivateKey()
+                : encryptField.privateKey());
+        encryptContext.setPublicKey(StringUtils.isBlank(encryptField.publicKey()) ? defaultProperties.getPublicKey()
+                : encryptField.publicKey());
         return this.encryptorManager.encrypt(value, encryptContext);
     }
 
-
     @Override
     public void setProperties(Properties properties) {
     }

+ 1 - 1
SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/helper/DataBaseHelper.java

@@ -42,7 +42,7 @@ public class DataBaseHelper {
             String databaseProductName = metaData.getDatabaseProductName();
             return DataBaseType.find(databaseProductName);
         } catch (SQLException e) {
-            throw new ServiceException(e.getMessage());
+            throw new RuntimeException("获取数据库类型失败", e);
         }
     }
 

+ 3 - 0
SERVER/VberAdminPlusV3/vber-modules/vber-generator/src/main/java/com/vber/generator/service/GenTableServiceImpl.java

@@ -677,6 +677,9 @@ public class GenTableServiceImpl implements IGenTableService {
      * @param table 业务表信息
      */
     public void setPkColumn(GenTable table) {
+        if (CollUtil.isEmpty(table.getColumns())) {
+            throw new ServiceException("表【" + table.getTableName() + "】字段为空,请检查表结构");
+        }
         for (GenTableColumn column : table.getColumns()) {
             if (column.isPk()) {
                 table.setPkColumn(column);

+ 15 - 3
SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysDictTypeServiceImpl.java

@@ -48,7 +48,6 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
     private final SysDictTypeMapper baseMapper;
     private final SysDictDataMapper dictDataMapper;
 
-
     /**
      * 分页查询字典类型列表
      *
@@ -231,6 +230,9 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
     @Override
     public String getDictLabel(String dictType, String dictValue, String separator) {
         List<SysDictDataVo> datas = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
+        if (CollUtil.isEmpty(datas)) {
+            return StringUtils.EMPTY;
+        }
         Map<String, String> map = StreamUtils.toMap(datas, SysDictDataVo::getDictValue, SysDictDataVo::getDictLabel);
         if (StringUtils.containsAny(dictValue, separator)) {
             return Arrays.stream(dictValue.split(separator))
@@ -253,6 +255,9 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
     @Override
     public String getDictValue(String dictType, String dictLabel, String separator) {
         List<SysDictDataVo> datas = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
+        if (CollUtil.isEmpty(datas)) {
+            return StringUtils.EMPTY;
+        }
         Map<String, String> map = StreamUtils.toMap(datas, SysDictDataVo::getDictLabel, SysDictDataVo::getDictValue);
         if (StringUtils.containsAny(dictLabel, separator)) {
             return Arrays.stream(dictLabel.split(separator))
@@ -266,6 +271,9 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
     @Override
     public Map<String, String> getAllDictByDictType(String dictType) {
         List<SysDictDataVo> list = selectDictDataByType(dictType);
+        if (CollUtil.isEmpty(list)) {
+            return new HashMap<>();
+        }
         // 保证顺序
         LinkedHashMap<String, String> map = new LinkedHashMap<>();
         for (SysDictDataVo vo : list) {
@@ -283,10 +291,12 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
     @Override
     public DictTypeDTO getDictType(String dictType) {
         SysDictTypeVo vo = SpringUtils.getAopProxy(this).selectDictTypeByType(dictType);
+        if (ObjectUtil.isNull(vo)) {
+            return null;
+        }
         return BeanUtil.toBean(vo, DictTypeDTO.class);
     }
 
-
     /**
      * 根据字典类型查询字典数据列表
      *
@@ -296,8 +306,10 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
     @Override
     public List<DictDataDTO> getDictData(String dictType) {
         List<SysDictDataVo> list = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
+        if (CollUtil.isEmpty(list)) {
+            return new ArrayList<>();
+        }
         return BeanUtil.copyToList(list, DictDataDTO.class);
     }
 
-
 }

+ 18 - 0
SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysUserServiceImpl.java

@@ -775,4 +775,22 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
         return StreamUtils.toMap(list, SysUser::getUserId, SysUser::getNickName);
     }
 
+    /**
+     * 根据用户 ID 列表查询用户昵称映射关系
+     *
+     * @param userIds 用户 ID 列表
+     * @return Map,其中 key 为用户 ID,value 为对应的用户昵称
+     */
+    @Override
+    public Map<Long, String> selectUserNicksByIds(List<Long> userIds) {
+        if (CollUtil.isEmpty(userIds)) {
+            return Collections.emptyMap();
+        }
+        List<SysUser> list = baseMapper.selectList(
+                new LambdaQueryWrapper<SysUser>()
+                        .select(SysUser::getUserId, SysUser::getNickName)
+                        .in(SysUser::getUserId, userIds));
+        return StreamUtils.toMap(list, SysUser::getUserId, SysUser::getNickName);
+    }
+
 }

+ 15 - 21
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/common/constant/FlowConstant.java

@@ -1,6 +1,5 @@
 package com.vber.workflow.common.constant;
 
-
 /**
  * 工作流常量
  *
@@ -23,26 +22,6 @@ public interface FlowConstant {
      */
     String INITIATOR_ORG_ID = "initiatorDeptId";
 
-    /**
-     * 委托
-     */
-    String DELEGATE_TASK = "delegateTask";
-
-    /**
-     * 转办
-     */
-    String TRANSFER_TASK = "transferTask";
-
-    /**
-     * 加签
-     */
-    String ADD_SIGNATURE = "addSignature";
-
-    /**
-     * 减签
-     */
-    String REDUCTION_SIGNATURE = "reductionSignature";
-
     /**
      * 流程分类Id转名称
      */
@@ -92,4 +71,19 @@ public interface FlowConstant {
      * 业务编码
      */
     String BUSINESS_CODE = "businessCode";
+
+    /**
+     * 忽略-办理权限校验(true:忽略,false:不忽略)
+     */
+    String VAR_IGNORE = "ignore";
+
+    /**
+     * 忽略-委派处理(true:忽略,false:不忽略)
+     */
+    String VAR_IGNORE_DEPUTE = "ignoreDepute";
+
+    /**
+     * 忽略-会签票签处理(true:忽略,false:不忽略)
+     */
+    String VAR_IGNORE_COOPERATE = "ignoreCooperate";
 }

+ 53 - 0
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/common/enums/TaskOperationEnum.java

@@ -0,0 +1,53 @@
+package com.vber.workflow.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 任务操作类型枚举
+ *
+ * @author Iwb
+ */
+@Getter
+@AllArgsConstructor
+public enum TaskOperationEnum {
+
+    /**
+     * 委派
+     */
+    DELEGATE_TASK("delegateTask", "委派"),
+
+    /**
+     * 转办
+     */
+    TRANSFER_TASK("transferTask", "转办"),
+
+    /**
+     * 加签
+     */
+    ADD_SIGNATURE("addSignature", "加签"),
+
+    /**
+     * 减签
+     */
+    REDUCTION_SIGNATURE("reductionSignature", "减签");
+
+    private final String code;
+    private final String desc;
+
+    private static final Map<String, TaskOperationEnum> CODE_MAP = Arrays.stream(values())
+            .collect(Collectors.toConcurrentMap(TaskOperationEnum::getCode, Function.identity()));
+
+    /**
+     * 根据 code 获取枚举
+     */
+    public static TaskOperationEnum getByCode(String code) {
+        return CODE_MAP.get(code);
+    }
+
+}

+ 16 - 0
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/controller/FlwDefinitionController.java

@@ -10,6 +10,8 @@ import com.vber.common.web.core.BaseController;
 import com.vber.workflow.common.ConditionalOnEnable;
 import com.vber.workflow.domain.vo.FlowDefinitionVo;
 import com.vber.workflow.service.IFlwDefinitionService;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
 import jakarta.servlet.http.HttpServletResponse;
 import lombok.RequiredArgsConstructor;
 import org.dromara.warm.flow.core.entity.Definition;
@@ -45,6 +47,7 @@ public class FlwDefinitionController extends BaseController {
      * @param pageQuery      分页
      */
     @GetMapping("/list")
+    @SaCheckPermission("workflow:definition:query")
     public TableDataInfo<FlowDefinitionVo> list(FlowDefinition flowDefinition, PageQuery pageQuery) {
         return flwDefinitionService.queryList(flowDefinition, pageQuery);
     }
@@ -56,6 +59,7 @@ public class FlwDefinitionController extends BaseController {
      * @param pageQuery      分页
      */
     @GetMapping("/unPublishList")
+    @SaCheckPermission("workflow:definition:query")
     public TableDataInfo<FlowDefinitionVo> unPublishList(FlowDefinition flowDefinition, PageQuery pageQuery) {
         return flwDefinitionService.unPublishList(flowDefinition, pageQuery);
     }
@@ -66,6 +70,7 @@ public class FlwDefinitionController extends BaseController {
      * @param id 流程定义id
      */
     @GetMapping(value = "/{id}")
+    @SaCheckPermission("workflow:definition:query")
     public R<Definition> getInfo(@PathVariable Long id) {
         return R.ok(defService.getById(id));
     }
@@ -79,6 +84,7 @@ public class FlwDefinitionController extends BaseController {
     @PostMapping
     @RepeatSubmit()
     @Transactional(rollbackFor = Exception.class)
+    @SaCheckPermission("workflow:definition:add")
     public R<Boolean> add(@RequestBody FlowDefinition flowDefinition) {
         return R.ok(defService.checkAndSave(flowDefinition));
     }
@@ -92,6 +98,7 @@ public class FlwDefinitionController extends BaseController {
     @PutMapping
     @RepeatSubmit()
     @Transactional(rollbackFor = Exception.class)
+    @SaCheckPermission("workflow:definition:edit")
     public R<Boolean> edit(@RequestBody FlowDefinition flowDefinition) {
         return R.ok(defService.updateById(flowDefinition));
     }
@@ -104,6 +111,7 @@ public class FlwDefinitionController extends BaseController {
     @Log(title = "流程定义", businessType = BusinessType.INSERT)
     @PutMapping("/publish/{id}")
     @RepeatSubmit()
+    @SaCheckPermission("workflow:definition:publish")
     public R<Boolean> publish(@PathVariable Long id) {
         return R.ok(flwDefinitionService.publish(id));
     }
@@ -117,6 +125,7 @@ public class FlwDefinitionController extends BaseController {
     @PutMapping("/unPublish/{id}")
     @RepeatSubmit()
     @Transactional(rollbackFor = Exception.class)
+    @SaCheckPermission("workflow:definition:publish")
     public R<Boolean> unPublish(@PathVariable Long id) {
         return R.ok(defService.unPublish(id));
     }
@@ -126,6 +135,7 @@ public class FlwDefinitionController extends BaseController {
      */
     @Log(title = "流程定义", businessType = BusinessType.DELETE)
     @DeleteMapping("/{ids}")
+    @SaCheckPermission("workflow:definition:remove")
     public R<Void> remove(@PathVariable List<Long> ids) {
         return toAjax(flwDefinitionService.removeDef(ids));
     }
@@ -139,6 +149,7 @@ public class FlwDefinitionController extends BaseController {
     @PostMapping("/copy/{id}")
     @RepeatSubmit()
     @Transactional(rollbackFor = Exception.class)
+    @SaCheckPermission("workflow:definition:copy")
     public R<Boolean> copy(@PathVariable Long id) {
         return R.ok(defService.copyDef(id));
     }
@@ -151,6 +162,7 @@ public class FlwDefinitionController extends BaseController {
      */
     @Log(title = "流程定义", businessType = BusinessType.IMPORT)
     @PostMapping("/importDef")
+    @SaCheckPermission("workflow:definition:import")
     public R<Boolean> importDef(MultipartFile file, String category) {
         return R.ok(flwDefinitionService.importJson(file, category));
     }
@@ -164,6 +176,7 @@ public class FlwDefinitionController extends BaseController {
      */
     @Log(title = "流程定义", businessType = BusinessType.EXPORT)
     @PostMapping("/exportDef/{id}")
+    @SaCheckPermission("workflow:definition:export")
     public void exportDef(@PathVariable Long id, HttpServletResponse response) throws IOException {
         flwDefinitionService.exportDef(id, response);
     }
@@ -174,6 +187,7 @@ public class FlwDefinitionController extends BaseController {
      * @param id 流程定义id
      */
     @GetMapping("/xmlString/{id}")
+    @SaCheckPermission("workflow:definition:query")
     public R<String> xmlString(@PathVariable Long id) {
         return R.ok("操作成功", defService.exportJson(id));
     }
@@ -187,6 +201,8 @@ public class FlwDefinitionController extends BaseController {
     @RepeatSubmit()
     @PutMapping("/active/{id}")
     @Transactional(rollbackFor = Exception.class)
+    @Log(title = "流程定义", businessType = BusinessType.UPDATE)
+    @SaCheckPermission("workflow:definition:active")
     public R<Boolean> active(@PathVariable Long id, @RequestParam boolean active) {
         return R.ok(active ? defService.active(id) : defService.unActive(id));
     }

+ 24 - 1
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/controller/FlwInstanceController.java

@@ -1,6 +1,7 @@
 package com.vber.workflow.controller;
 
 import com.vber.common.core.domain.R;
+import com.vber.common.core.utils.StreamUtils;
 import com.vber.common.idempotent.annotation.RepeatSubmit;
 import com.vber.common.log.annotation.Log;
 import com.vber.common.log.enums.BusinessType;
@@ -14,6 +15,9 @@ import com.vber.workflow.domain.bo.FlowInvalidBo;
 import com.vber.workflow.domain.bo.FlowVariableBo;
 import com.vber.workflow.domain.vo.FlowInstanceVo;
 import com.vber.workflow.service.IFlwInstanceService;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.convert.Convert;
 import lombok.RequiredArgsConstructor;
 import org.dromara.warm.flow.core.service.InsService;
 import org.springframework.validation.annotation.Validated;
@@ -44,6 +48,7 @@ public class FlwInstanceController extends BaseController {
      * @param pageQuery      分页
      */
     @GetMapping("/pageByRunning")
+    @SaCheckPermission("workflow:instance:list:query")
     public TableDataInfo<FlowInstanceVo> selectRunningInstanceList(FlowInstanceBo flowInstanceBo, PageQuery pageQuery) {
         return flwInstanceService.selectRunningInstanceList(flowInstanceBo, pageQuery);
     }
@@ -55,6 +60,7 @@ public class FlwInstanceController extends BaseController {
      * @param pageQuery      分页
      */
     @GetMapping("/pageByFinish")
+    @SaCheckPermission("workflow:instance:query")
     public TableDataInfo<FlowInstanceVo> selectFinishInstanceList(FlowInstanceBo flowInstanceBo, PageQuery pageQuery) {
         return flwInstanceService.selectFinishInstanceList(flowInstanceBo, pageQuery);
     }
@@ -65,6 +71,7 @@ public class FlwInstanceController extends BaseController {
      * @param businessId 业务id
      */
     @GetMapping("/getInfo/{businessId}")
+    @SaCheckPermission("workflow:instance:query")
     public R<FlowInstanceVo> getInfo(@PathVariable Long businessId) {
         return R.ok(flwInstanceService.queryByBusinessId(businessId));
     }
@@ -75,8 +82,10 @@ public class FlwInstanceController extends BaseController {
      * @param businessIds 业务id
      */
     @DeleteMapping("/deleteByBusinessIds/{businessIds}")
+    @Log(title = "流程实例管理", businessType = BusinessType.DELETE)
+    @SaCheckPermission("workflow:instance:remove")
     public R<Void> deleteByBusinessIds(@PathVariable List<Long> businessIds) {
-        return toAjax(flwInstanceService.deleteByBusinessIds(businessIds));
+        return toAjax(flwInstanceService.deleteByBusinessIds(StreamUtils.toList(businessIds, Convert::toStr)));
     }
 
     /**
@@ -85,6 +94,8 @@ public class FlwInstanceController extends BaseController {
      * @param instanceIds 实例id
      */
     @DeleteMapping("/deleteByInstanceIds/{instanceIds}")
+    @Log(title = "流程实例管理", businessType = BusinessType.DELETE)
+    @SaCheckPermission("workflow:instance:remove")
     public R<Void> deleteByInstanceIds(@PathVariable List<Long> instanceIds) {
         return toAjax(flwInstanceService.deleteByInstanceIds(instanceIds));
     }
@@ -95,6 +106,8 @@ public class FlwInstanceController extends BaseController {
      * @param instanceIds 实例id
      */
     @DeleteMapping("/deleteHisByInstanceIds/{instanceIds}")
+    @Log(title = "流程实例管理", businessType = BusinessType.DELETE)
+    @SaCheckPermission("workflow:instance:remove")
     public R<Void> deleteHisByInstanceIds(@PathVariable List<Long> instanceIds) {
         return toAjax(flwInstanceService.deleteHisByInstanceIds(instanceIds));
     }
@@ -106,6 +119,8 @@ public class FlwInstanceController extends BaseController {
      */
     @RepeatSubmit()
     @PutMapping("/cancelProcessApply")
+    @Log(title = "流程实例管理", businessType = BusinessType.UPDATE)
+    @SaCheckPermission("workflow:instance:cancel")
     public R<Void> cancelProcessApply(@RequestBody FlowCancelBo bo) {
         return toAjax(flwInstanceService.cancelProcessApply(bo));
     }
@@ -118,6 +133,8 @@ public class FlwInstanceController extends BaseController {
      */
     @RepeatSubmit()
     @PutMapping("/active/{id}")
+    @Log(title = "流程实例管理", businessType = BusinessType.UPDATE)
+    @SaCheckPermission("workflow:instance:active")
     public R<Boolean> active(@PathVariable Long id, @RequestParam boolean active) {
         return R.ok(active ? insService.active(id) : insService.unActive(id));
     }
@@ -129,6 +146,7 @@ public class FlwInstanceController extends BaseController {
      * @param pageQuery      分页
      */
     @GetMapping("/pageByCurrent")
+    @SaCheckPermission("workflow:instance:query")
     public TableDataInfo<FlowInstanceVo> selectCurrentInstanceList(FlowInstanceBo flowInstanceBo, PageQuery pageQuery) {
         return flwInstanceService.selectCurrentInstanceList(flowInstanceBo, pageQuery);
     }
@@ -139,6 +157,7 @@ public class FlwInstanceController extends BaseController {
      * @param businessId 业务id
      */
     @GetMapping("/flowHisTaskList/{businessId}")
+    @SaCheckPermission("workflow:instance:query")
     public R<Map<String, Object>> flowHisTaskList(@PathVariable String businessId) {
         return R.ok(flwInstanceService.flowHisTaskList(businessId));
     }
@@ -149,6 +168,7 @@ public class FlwInstanceController extends BaseController {
      * @param instanceId 流程实例id
      */
     @GetMapping("/instanceVariable/{instanceId}")
+    @SaCheckPermission("workflow:instance:variableQuery")
     public R<Map<String, Object>> instanceVariable(@PathVariable Long instanceId) {
         return R.ok(flwInstanceService.instanceVariable(instanceId));
     }
@@ -160,6 +180,8 @@ public class FlwInstanceController extends BaseController {
      */
     @RepeatSubmit()
     @PutMapping("/updateVariable")
+    @Log(title = "流程实例管理", businessType = BusinessType.UPDATE)
+    @SaCheckPermission("workflow:instance:variableEdit")
     public R<Void> updateVariable(@Validated @RequestBody FlowVariableBo bo) {
         return toAjax(flwInstanceService.updateVariable(bo));
     }
@@ -172,6 +194,7 @@ public class FlwInstanceController extends BaseController {
     @Log(title = "流程实例管理", businessType = BusinessType.INSERT)
     @RepeatSubmit()
     @PostMapping("/invalid")
+    @SaCheckPermission("workflow:instance:invalid")
     public R<Boolean> invalid(@Validated @RequestBody FlowInvalidBo bo) {
         return R.ok(flwInstanceService.processInvalid(bo));
     }

+ 9 - 9
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/controller/FlwSpelController.java

@@ -23,7 +23,7 @@ import org.springframework.web.bind.annotation.*;
 import java.util.List;
 
 /**
- * 流程spel达式定义
+ * 流程spel达式定义
  *
  * @author Iwb
  */
@@ -37,7 +37,7 @@ public class FlwSpelController extends BaseController {
     private final IFlwSpelService flwSpelService;
 
     /**
-     * 查询流程spel达式定义列表
+     * 查询流程spel达式定义列表
      */
     @SaCheckPermission("workflow:spel:query")
     @GetMapping("/list")
@@ -46,7 +46,7 @@ public class FlwSpelController extends BaseController {
     }
 
     /**
-     * 获取流程spel达式定义详细信息
+     * 获取流程spel达式定义详细信息
      *
      * @param id 主键
      */
@@ -57,10 +57,10 @@ public class FlwSpelController extends BaseController {
     }
 
     /**
-     * 新增流程spel达式定义
+     * 新增流程spel达式定义
      */
     @SaCheckPermission("workflow:spel:add")
-    @Log(title = "流程spel达式定义", businessType = BusinessType.INSERT)
+    @Log(title = "流程spel达式定义", businessType = BusinessType.INSERT)
     @RepeatSubmit()
     @PostMapping()
     public R<Void> add(@Validated(AddGroup.class) @RequestBody FlowSpelBo bo) {
@@ -68,10 +68,10 @@ public class FlwSpelController extends BaseController {
     }
 
     /**
-     * 修改流程spel达式定义
+     * 修改流程spel达式定义
      */
     @SaCheckPermission("workflow:spel:edit")
-    @Log(title = "流程spel达式定义", businessType = BusinessType.UPDATE)
+    @Log(title = "流程spel达式定义", businessType = BusinessType.UPDATE)
     @RepeatSubmit()
     @PutMapping()
     public R<Void> edit(@Validated(EditGroup.class) @RequestBody FlowSpelBo bo) {
@@ -79,12 +79,12 @@ public class FlwSpelController extends BaseController {
     }
 
     /**
-     * 删除流程spel达式定义
+     * 删除流程spel达式定义
      *
      * @param ids 主键串
      */
     @SaCheckPermission("workflow:spel:remove")
-    @Log(title = "流程spel达式定义", businessType = BusinessType.DELETE)
+    @Log(title = "流程spel达式定义", businessType = BusinessType.DELETE)
     @DeleteMapping("/{ids}")
     public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
         return toAjax(flwSpelService.deleteWithValidByIds(List.of(ids), true));

+ 6 - 4
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/controller/FlwTaskController.java

@@ -45,7 +45,8 @@ public class FlwTaskController extends BaseController {
     @Log(title = "任务管理", businessType = BusinessType.INSERT)
     @RepeatSubmit()
     @PostMapping("/startWorkFlow")
-    public R<StartProcessReturnDTO> startWorkFlow(@Validated(AddGroup.class) @RequestBody StartProcessBo startProcessBo) {
+    public R<StartProcessReturnDTO> startWorkFlow(
+            @Validated(AddGroup.class) @RequestBody StartProcessBo startProcessBo) {
         StartProcessReturnDTO startProcessReturn = flwTaskService.startWorkFlow(startProcessBo);
         return R.ok("提交成功", startProcessReturn);
     }
@@ -154,7 +155,8 @@ public class FlwTaskController extends BaseController {
      * 任务操作
      *
      * @param bo            参数
-     * @param taskOperation 操作类型,委派 delegateTask、转办 transferTask、加签 addSignature、减签 reductionSignature
+     * @param taskOperation 操作类型,委派 delegateTask、转办 transferTask、加签 addSignature、减签
+     *                      reductionSignature
      */
     @Log(title = "任务管理", businessType = BusinessType.UPDATE)
     @RepeatSubmit
@@ -184,7 +186,7 @@ public class FlwTaskController extends BaseController {
     @Log(title = "任务管理", businessType = BusinessType.INSERT)
     @RepeatSubmit()
     @PostMapping("/backProcess")
-    public R<Void> backProcess(@Validated({AddGroup.class}) @RequestBody BackProcessBo bo) {
+    public R<Void> backProcess(@Validated({ AddGroup.class }) @RequestBody BackProcessBo bo) {
         return toAjax(flwTaskService.backProcess(bo));
     }
 
@@ -216,9 +218,9 @@ public class FlwTaskController extends BaseController {
      * @return 结果
      */
     @PostMapping("/urgeTask")
+    @Log(title = "任务管理", businessType = BusinessType.INSERT)
     public R<Void> urgeTask(@RequestBody FlowUrgeTaskBo bo) {
         return toAjax(flwTaskService.urgeTask(bo));
     }
 
-
 }

+ 1 - 2
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/FlowSpel.java

@@ -10,7 +10,7 @@ import lombok.EqualsAndHashCode;
 import java.io.Serial;
 
 /**
- * 流程spel达式定义对象 flow_spel
+ * 流程spel达式定义对象 flow_spel
  *
  * @author Iwb
  */
@@ -64,5 +64,4 @@ public class FlowSpel extends BaseEntity {
     @TableLogic
     private String delFlag;
 
-
 }

+ 2 - 2
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/BackProcessBo.java

@@ -11,7 +11,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 
-
 /**
  * 驳回参数请求
  *
@@ -61,7 +60,8 @@ public class BackProcessBo implements Serializable {
 
     public Map<String, Object> getVariables() {
         if (variables == null) {
-            return new HashMap<>(16);
+            variables = new HashMap<>(16);
+            return variables;
         }
         variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
         return variables;

+ 2 - 3
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/FlowCopyBo.java

@@ -5,7 +5,6 @@ import lombok.Data;
 import java.io.Serial;
 import java.io.Serializable;
 
-
 /**
  * 抄送
  *
@@ -23,8 +22,8 @@ public class FlowCopyBo implements Serializable {
     private Long userId;
 
     /**
-     * 用户
+     * 用户
      */
-    private String userName;
+    private String nickName;
 
 }

+ 1 - 1
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/FlowInstanceBo.java

@@ -50,6 +50,6 @@ public class FlowInstanceBo implements Serializable {
     /**
      * 申请人Ids
      */
-    private List<Long> createByIds;
+    private List<String> createByIds;
 
 }

+ 2 - 1
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/FlowNextNodeBo.java

@@ -30,7 +30,8 @@ public class FlowNextNodeBo implements Serializable {
 
     public Map<String, Object> getVariables() {
         if (variables == null) {
-            return new HashMap<>(16);
+            variables = new HashMap<>(16);
+            return variables;
         }
         variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
         return variables;

+ 3 - 3
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/FlowSpelBo.java

@@ -10,7 +10,7 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 
 /**
- * 流程spel达式定义业务对象 flow_spel
+ * 流程spel达式定义业务对象 flow_spel
  *
  * @author Iwb
  */
@@ -42,13 +42,13 @@ public class FlowSpelBo extends BaseEntity {
     /**
      * 预览spel值
      */
-    @NotBlank(message = "预览spel值不能为空", groups = {AddGroup.class, EditGroup.class})
+    @NotBlank(message = "预览spel值不能为空", groups = { AddGroup.class, EditGroup.class })
     private String viewSpel;
 
     /**
      * 状态(0正常 1停用)
      */
-    @NotBlank(message = "状态(0正常 1停用)不能为空", groups = {AddGroup.class, EditGroup.class})
+    @NotBlank(message = "状态(0正常 1停用)不能为空", groups = { AddGroup.class, EditGroup.class })
     private String status;
 
     /**

+ 15 - 0
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/FlowTaskBo.java

@@ -4,7 +4,11 @@ import lombok.Data;
 
 import java.io.Serial;
 import java.io.Serializable;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
 
 /**
  * 任务请求对象
@@ -42,6 +46,11 @@ public class FlowTaskBo implements Serializable {
      */
     private Long instanceId;
 
+    /**
+     * 流程状态
+     */
+    private String flowStatus;
+
     /**
      * 权限列表
      */
@@ -52,4 +61,10 @@ public class FlowTaskBo implements Serializable {
      */
     private List<Long> createByIds;
 
+    /**
+     * 请求参数
+     */
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
+    private Map<String, Object> params = new HashMap<>();
+
 }

+ 4 - 4
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/StartProcessBo.java

@@ -1,6 +1,5 @@
 package com.vber.workflow.domain.bo;
 
-
 import cn.hutool.core.util.ObjectUtil;
 import com.vber.common.core.validate.AddGroup;
 import com.vber.workflow.domain.FlowInstanceBizExt;
@@ -27,13 +26,13 @@ public class StartProcessBo implements Serializable {
     /**
      * 业务唯一值id
      */
-    @NotBlank(message = "业务ID不能为空", groups = {AddGroup.class})
+    @NotBlank(message = "业务ID不能为空", groups = { AddGroup.class })
     private String businessId;
 
     /**
      * 流程定义编码
      */
-    @NotBlank(message = "流程定义编码不能为空", groups = {AddGroup.class})
+    @NotBlank(message = "流程定义编码不能为空", groups = { AddGroup.class })
     private String flowCode;
 
     /**
@@ -53,7 +52,8 @@ public class StartProcessBo implements Serializable {
 
     public Map<String, Object> getVariables() {
         if (variables == null) {
-            return new HashMap<>(16);
+            variables = new HashMap<>(16);
+            return variables;
         }
         variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
         return variables;

+ 7 - 3
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/TaskOperationBo.java

@@ -9,7 +9,6 @@ import java.io.Serial;
 import java.io.Serializable;
 import java.util.List;
 
-
 /**
  * 任务操作业务对象,用于描述任务委派、转办、加签等操作的必要参数
  * 包含了用户ID、任务ID、任务相关的消息、以及加签/减签的用户ID
@@ -25,13 +24,13 @@ public class TaskOperationBo implements Serializable {
     /**
      * 委派/转办人的用户ID(必填,准对委派/转办人操作)
      */
-    @NotNull(message = "委派/转办人id不能为空", groups = {AddGroup.class})
+    @NotNull(message = "委派/转办人id不能为空", groups = { AddGroup.class })
     private String userId;
 
     /**
      * 加签/减签人的用户ID列表(必填,针对加签/减签操作)
      */
-    @NotNull(message = "加签/减签id不能为空", groups = {EditGroup.class})
+    @NotNull(message = "加签/减签id不能为空", groups = { EditGroup.class })
     private List<String> userIds;
 
     /**
@@ -40,6 +39,11 @@ public class TaskOperationBo implements Serializable {
     @NotNull(message = "任务id不能为空")
     private Long taskId;
 
+    /**
+     * 消息类型
+     */
+    private List<String> messageType;
+
     /**
      * 意见或备注信息(可选)
      */

+ 6 - 2
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/vo/FlowHisTaskVo.java

@@ -203,7 +203,7 @@ public class FlowHisTaskVo implements Serializable {
      */
     private String runDuration;
 
-    //业务扩展信息开始
+    // 业务扩展信息开始
     /**
      * 业务编码
      */
@@ -213,7 +213,7 @@ public class FlowHisTaskVo implements Serializable {
      * 业务标题
      */
     private String businessTitle;
-    //业务扩展信息结束
+    // 业务扩展信息结束
 
     /**
      * 设置创建时间并计算任务运行时长
@@ -253,4 +253,8 @@ public class FlowHisTaskVo implements Serializable {
         this.cooperateTypeName = CooperateType.getValueByKey(cooperateType);
     }
 
+    public String getCreateTime() {
+        return DateUtils.formatFriendlyTime(createTime);
+    }
+
 }

+ 4 - 5
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/vo/FlowInstanceVo.java

@@ -17,7 +17,6 @@ public class FlowInstanceVo {
 
     private Long id;
 
-
     /**
      * 创建时间
      */
@@ -64,7 +63,7 @@ public class FlowInstanceVo {
     private Integer nodeType;
 
     /**
-     * 流程节点编码   每个流程的nodeCode是唯一的,即definitionId+nodeCode唯一,在数据库层面做了控制
+     * 流程节点编码 每个流程的nodeCode是唯一的,即definitionId+nodeCode唯一,在数据库层面做了控制
      */
     private String nodeCode;
 
@@ -79,7 +78,7 @@ public class FlowInstanceVo {
     private String variable;
 
     /**
-     * 流程状态(0待提交 1审批中 2 审批通过 3自动通过 8已完成 9已退回 10失效)
+     * 流程状态
      */
     private String flowStatus;
 
@@ -135,7 +134,7 @@ public class FlowInstanceVo {
     @Translation(type = FlowConstant.CATEGORY_ID_TO_NAME, mapper = "category")
     private String categoryName;
 
-    //业务扩展信息开始
+    // 业务扩展信息开始
     /**
      * 业务编码
      */
@@ -145,6 +144,6 @@ public class FlowInstanceVo {
      * 业务标题
      */
     private String businessTitle;
-    //业务扩展信息结束
+    // 业务扩展信息结束
 
 }

+ 1 - 3
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/vo/FlowSpelVo.java

@@ -12,9 +12,8 @@ import java.io.Serial;
 import java.io.Serializable;
 import java.util.Date;
 
-
 /**
- * 流程spel达式定义视图对象 flow_spel
+ * 流程spel达式定义视图对象 flow_spel
  *
  * @author Iwb
  * @date 2025-07-04
@@ -27,7 +26,6 @@ public class FlowSpelVo implements Serializable {
     @Serial
     private static final long serialVersionUID = 1L;
 
-
     /**
      * 主键id
      */

+ 9 - 4
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/vo/FlowTaskVo.java

@@ -1,5 +1,6 @@
 package com.vber.workflow.domain.vo;
 
+import com.vber.common.core.utils.DateUtils;
 import com.vber.common.translation.annotation.Translation;
 import com.vber.common.translation.constant.TransConstant;
 import com.vber.workflow.common.constant.FlowConstant;
@@ -8,7 +9,6 @@ import org.dromara.warm.flow.core.entity.User;
 
 import java.io.Serial;
 import java.io.Serializable;
-import java.math.BigDecimal;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
@@ -23,6 +23,7 @@ public class FlowTaskVo implements Serializable {
 
     @Serial
     private static final long serialVersionUID = 1L;
+
     private Long id;
 
     /**
@@ -162,7 +163,7 @@ public class FlowTaskVo implements Serializable {
     /**
      * 流程签署比例值 大于0为票签,会签
      */
-    private BigDecimal nodeRatio;
+    private String nodeRatio;
 
     /**
      * 申请人id
@@ -199,7 +200,7 @@ public class FlowTaskVo implements Serializable {
      */
     private Map<String, String> varList;
 
-    //业务扩展信息开始
+    // 业务扩展信息开始
     /**
      * 业务编码
      */
@@ -209,6 +210,10 @@ public class FlowTaskVo implements Serializable {
      * 业务标题
      */
     private String businessTitle;
-    //业务扩展信息结束
+    // 业务扩展信息结束
+
+    public String getCreateTime() {
+        return DateUtils.formatFriendlyTime(createTime);
+    }
 
 }

+ 17 - 10
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/handler/FlowProcessEventHandler.java

@@ -8,6 +8,7 @@ import com.vber.common.tenant.helper.TenantHelper;
 import com.vber.workflow.common.ConditionalOnEnable;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.warm.flow.core.entity.Instance;
+import org.dromara.warm.flow.core.entity.Task;
 import org.springframework.stereotype.Component;
 
 import java.util.Map;
@@ -24,7 +25,7 @@ import java.util.Map;
 public class FlowProcessEventHandler {
 
     /**
-     * 总体流程监听(例如: 草稿,撤销,退回,作废,终止,已完成,单任务完成等)
+     * 总体流程监听(例如: 草稿,撤销,退回,作废,终止,已完成等)
      *
      * @param flowCode 流程定义编码
      * @param instance 实例数据
@@ -32,13 +33,16 @@ public class FlowProcessEventHandler {
      * @param params   办理参数
      * @param submit   当为true时为申请人节点办理
      */
-    public void processHandler(String flowCode, Instance instance, String status, Map<String, Object> params, boolean submit) {
+    public void processHandler(String flowCode, Instance instance, String status, Map<String, Object> params,
+            boolean submit) {
         String tenantId = TenantHelper.getTenantId();
         log.info("【流程事件发布】租户ID: {}, 流程编码: {}, 业务ID: {}, 流程状态: {}, 节点类型: {}, 节点编码: {}, 节点名称: {}, 是否申请人节点: {}, 参数: {}",
-                tenantId, flowCode, instance.getBusinessId(), status, instance.getNodeType(), instance.getNodeCode(), instance.getNodeName(), submit, params);
+                tenantId, flowCode, instance.getBusinessId(), status, instance.getNodeType(), instance.getNodeCode(),
+                instance.getNodeName(), submit, params);
         ProcessEvent processEvent = new ProcessEvent();
         processEvent.setTenantId(tenantId);
         processEvent.setFlowCode(flowCode);
+        processEvent.setInstanceId(instance.getId());
         processEvent.setBusinessId(instance.getBusinessId());
         processEvent.setNodeType(instance.getNodeType());
         processEvent.setNodeCode(instance.getNodeCode());
@@ -54,20 +58,23 @@ public class FlowProcessEventHandler {
      *
      * @param flowCode 流程定义编码
      * @param instance 实例数据
-     * @param taskId   任务id
+     * @param nextTask 任务
+     * @param params   上一个任务的办理参数
      */
-    public void processTaskHandler(String flowCode, Instance instance, Long taskId, Map<String, Object> params) {
+    public void processTaskHandler(String flowCode, Instance instance, Task nextTask, Map<String, Object> params) {
         String tenantId = TenantHelper.getTenantId();
         log.info("【流程任务事件发布】租户ID: {}, 流程编码: {}, 业务ID: {}, 节点类型: {}, 节点编码: {}, 节点名称: {}, 任务ID: {}",
-                tenantId, flowCode, instance.getBusinessId(), instance.getNodeType(), instance.getNodeCode(), instance.getNodeName(), taskId);
+                tenantId, flowCode, instance.getBusinessId(), nextTask.getNodeType(), nextTask.getNodeCode(),
+                nextTask.getNodeName(), nextTask.getId());
         ProcessTaskEvent processTaskEvent = new ProcessTaskEvent();
         processTaskEvent.setTenantId(tenantId);
         processTaskEvent.setFlowCode(flowCode);
+        processTaskEvent.setInstanceId(instance.getId());
         processTaskEvent.setBusinessId(instance.getBusinessId());
-        processTaskEvent.setNodeType(instance.getNodeType());
-        processTaskEvent.setNodeCode(instance.getNodeCode());
-        processTaskEvent.setNodeName(instance.getNodeName());
-        processTaskEvent.setTaskId(taskId);
+        processTaskEvent.setNodeType(nextTask.getNodeType());
+        processTaskEvent.setNodeCode(nextTask.getNodeCode());
+        processTaskEvent.setNodeName(nextTask.getNodeName());
+        processTaskEvent.setTaskId(nextTask.getId());
         processTaskEvent.setStatus(instance.getFlowStatus());
         processTaskEvent.setParams(params);
         SpringUtils.context().publishEvent(processTaskEvent);

+ 60 - 14
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/listener/WorkflowGlobalListener.java

@@ -5,6 +5,8 @@ import cn.hutool.core.convert.Convert;
 import cn.hutool.core.lang.TypeReference;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+
 import com.vber.common.core.enums.BusinessStatusEnum;
 import com.vber.common.core.service.UserService;
 import com.vber.common.core.utils.StreamUtils;
@@ -72,15 +74,20 @@ public class WorkflowGlobalListener implements GlobalListener {
     public void start(ListenerVariable listenerVariable) {
         String ext = listenerVariable.getNode().getExt();
         if (StringUtils.isNotBlank(ext)) {
-            NodeExtVo nodeExt = nodeExtService.parseNodeExt(ext);
             Map<String, Object> variable = listenerVariable.getVariable();
+            if (CollUtil.isEmpty(variable)) {
+                variable = new HashMap<>();
+            }
+            NodeExtVo nodeExt = nodeExtService.parseNodeExt(ext, variable);
             Set<String> copyList = nodeExt.getCopySettings();
-            if (CollUtil.isEmpty(copyList)) {
+            if (CollUtil.isNotEmpty(copyList)) {
+                List<Long> userIds = StreamUtils.toList(copyList, Convert::toLong);
+                Map<Long, String> nickNameMap = userService.selectUserNicksByIds(userIds);
                 List<FlowCopyBo> list = StreamUtils.toList(copyList, x -> {
                     FlowCopyBo bo = new FlowCopyBo();
                     Long id = Convert.toLong(x);
                     bo.setUserId(id);
-                    bo.setUserName(userService.selectUserNameById(id));
+                    bo.setNickName(nickNameMap.getOrDefault(id, StringUtils.EMPTY));
                     return bo;
                 });
                 variable.put(FlowConstant.FLOW_COPY_LIST, list);
@@ -104,21 +111,61 @@ public class WorkflowGlobalListener implements GlobalListener {
         Definition definition = listenerVariable.getDefinition();
         Instance instance = listenerVariable.getInstance();
         String applyNodeCode = flwCommonService.applyNodeCode(definition.getId());
+        String hisStatus = flowParams != null ? flowParams.getHisStatus() : null;
+
         for (Task flowTask : nextTasks) {
-            // 如果办理或者退回并行存在需要指定办理人,则直接覆盖办理人
-            if (variable.containsKey(flowTask.getNodeCode())
-                    && TaskStatusEnum.isPassOrBack(flowParams.getHisStatus())) {
-                String userIds = variable.get(flowTask.getNodeCode()).toString();
-                flowTask.setPermissionList(List.of(userIds.split(StringUtils.SEPARATOR)));
-                variable.remove(flowTask.getNodeCode());
+            String nodeCode = flowTask.getNodeCode();
+
+            // 处理办理或退回时指定办理人的情况
+            if (TaskStatusEnum.PASS.getStatus().equals(hisStatus)) {
+                processTaskPermission(variable, flowTask, hisStatus);
+            } else if (TaskStatusEnum.BACK.getStatus().equals(hisStatus)) {
+                processTaskPermission(variable, flowTask, hisStatus);
             }
+
             // 如果是申请节点,则把启动人添加到办理人
-            if (flowTask.getNodeCode().equals(applyNodeCode)) {
+            if (nodeCode.equals(applyNodeCode) && StringUtils.isNotBlank(instance.getCreateBy())) {
                 flowTask.setPermissionList(List.of(instance.getCreateBy()));
             }
         }
     }
 
+    /**
+     * 处理任务权限设置
+     *
+     * @param variable   变量集合
+     * @param flowTask   流程任务
+     * @param taskStatus 任务状态
+     */
+    private void processTaskPermission(Map<String, Object> variable, Task flowTask, String taskStatus) {
+        String nodeKey = taskStatus + StrUtil.COLON + flowTask.getNodeCode();
+
+        // 检查是否存在状态相关的变量
+        if (!variable.containsKey(nodeKey)) {
+            return;
+        }
+
+        // 获取用户ID字符串
+        Object userIdsObj = variable.get(nodeKey);
+        if (userIdsObj == null) {
+            return;
+        }
+
+        String userIds = userIdsObj.toString();
+        if (StringUtils.isBlank(userIds)) {
+            return;
+        }
+
+        // 分割用户ID并设置权限列表
+        String[] userIdArray = userIds.split(StringUtils.SEPARATOR);
+        if (userIdArray.length > 0) {
+            flowTask.setPermissionList(List.of(userIdArray));
+            // 移除已处理的状态变量
+            variable.remove(nodeKey);
+            FlowEngine.insService().removeVariables(flowTask.getInstanceId(), nodeKey);
+        }
+    }
+
     /**
      * 完成监听器,当前任务完成后执行
      *
@@ -145,8 +192,8 @@ public class WorkflowGlobalListener implements GlobalListener {
         // 申请人提交事件
         Boolean submit = MapUtil.getBool(variable, FlowConstant.SUBMIT);
         if (submit != null && submit) {
-            flowProcessEventHandler.processHandler(definition.getFlowCode(), instance, instance.getFlowStatus(),
-                    variable, true);
+            String status = determineFlowStatus(instance);
+            flowProcessEventHandler.processHandler(definition.getFlowCode(), instance, status, variable, true);
         } else {
             // 判断流程状态(发布:撤销,退回,作废,终止,已完成事件)
             String status = determineFlowStatus(instance);
@@ -168,8 +215,7 @@ public class WorkflowGlobalListener implements GlobalListener {
         // 发布任务事件
         if (CollUtil.isNotEmpty(nextTasks)) {
             for (Task nextTask : nextTasks) {
-                flowProcessEventHandler.processTaskHandler(definition.getFlowCode(), instance, nextTask.getId(),
-                        params);
+                flowProcessEventHandler.processTaskHandler(definition.getFlowCode(), instance, nextTask, params);
             }
         }
         if (ObjectUtil.isNull(flowParams)) {

+ 1 - 1
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/mapper/FlwSpelMapper.java

@@ -6,7 +6,7 @@ import com.vber.workflow.domain.vo.FlowSpelVo;
 import org.springframework.stereotype.Repository;
 
 /**
- * 流程spel达式定义Mapper接口
+ * 流程spel达式定义Mapper接口
  *
  * @author Iwb
  */

+ 1 - 1
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/IFlwInstanceService.java

@@ -75,7 +75,7 @@ public interface IFlwInstanceService {
      * @param businessIds 业务id
      * @return 结果
      */
-    boolean deleteByBusinessIds(List<Long> businessIds);
+    boolean deleteByBusinessIds(List<String> businessIds);
 
     /**
      * 按照实例id删除流程实例

+ 7 - 3
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/IFlwNodeExtService.java

@@ -1,5 +1,7 @@
 package com.vber.workflow.service;
 
+import java.util.Map;
+
 import com.vber.workflow.domain.vo.NodeExtVo;
 
 /**
@@ -17,16 +19,18 @@ public interface IFlwNodeExtService {
      * 2. CopySettingEnum:解析为抄送对象 ID 集合
      * 3. VariablesEnum:解析为自定义参数 Map
      *
-     * <p>示例 JSON:
+     * <p>
+     * 示例 JSON:
      * [
      * {"code": "ButtonPermissionEnum", "value": "back,termination"},
      * {"code": "CopySettingEnum", "value": "1"},
      * {"code": "VariablesEnum", "value": "key1=value1,key2=value2"}
      * ]
      *
-     * @param ext 扩展属性 JSON 字符串
+     * @param ext      扩展属性 JSON 字符串
+     * @param variable 流程变量
      * @return NodeExtVo 对象,封装按钮权限列表、抄送对象集合和自定义参数 Map
      */
-    NodeExtVo parseNodeExt(String ext);
+    NodeExtVo parseNodeExt(String ext, Map<String, Object> variable);
 
 }

+ 12 - 12
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/IFlwSpelService.java

@@ -12,7 +12,7 @@ import java.util.List;
 import java.util.Map;
 
 /**
- * 流程spel达式定义Service接口
+ * 流程spel达式定义Service接口
  *
  * @author Iwb
  * @date 2025-07-04
@@ -20,48 +20,48 @@ import java.util.Map;
 public interface IFlwSpelService {
 
     /**
-     * 查询流程spel达式定义
+     * 查询流程spel达式定义
      *
      * @param id 主键
-     * @return 流程spel达式定义
+     * @return 流程spel达式定义
      */
     FlowSpelVo queryById(Long id);
 
     /**
-     * 分页查询流程spel达式定义列表
+     * 分页查询流程spel达式定义列表
      *
      * @param bo        查询条件
      * @param pageQuery 分页参数
-     * @return 流程spel达式定义分页列表
+     * @return 流程spel达式定义分页列表
      */
     TableDataInfo<FlowSpelVo> queryPageList(FlowSpelBo bo, PageQuery pageQuery);
 
     /**
-     * 查询符合条件的流程spel达式定义列表
+     * 查询符合条件的流程spel达式定义列表
      *
      * @param bo 查询条件
-     * @return 流程spel达式定义列表
+     * @return 流程spel达式定义列表
      */
     List<FlowSpelVo> queryList(FlowSpelBo bo);
 
     /**
-     * 新增流程spel达式定义
+     * 新增流程spel达式定义
      *
-     * @param bo 流程spel达式定义
+     * @param bo 流程spel达式定义
      * @return 是否新增成功
      */
     Boolean insertByBo(FlowSpelBo bo);
 
     /**
-     * 修改流程spel达式定义
+     * 修改流程spel达式定义
      *
-     * @param bo 流程spel达式定义
+     * @param bo 流程spel达式定义
      * @return 是否修改成功
      */
     Boolean updateByBo(FlowSpelBo bo);
 
     /**
-     * 校验并批量删除流程spel达式定义信息
+     * 校验并批量删除流程spel达式定义信息
      *
      * @param ids     待删除的主键集合
      * @param isValid 是否进行有效性校验

+ 9 - 6
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwCategoryServiceImpl.java

@@ -102,8 +102,7 @@ public class FlwCategoryServiceImpl implements IFlwCategoryService, CategoryServ
                         .setId(Convert.toStr(node.getCategoryId()))
                         .setParentId(Convert.toStr(node.getParentId()))
                         .setName(node.getCategoryName())
-                        .setWeight(node.getOrderNum())
-        );
+                        .setWeight(node.getOrderNum()));
     }
 
     /**
@@ -117,8 +116,7 @@ public class FlwCategoryServiceImpl implements IFlwCategoryService, CategoryServ
         return StreamUtils.toList(list, category -> new org.dromara.warm.flow.core.dto.Tree()
                 .setId(Convert.toStr(category.getCategoryId()))
                 .setName(category.getCategoryName())
-                .setParentId(Convert.toStr(category.getParentId()))
-        );
+                .setParentId(Convert.toStr(category.getParentId())));
     }
 
     /**
@@ -132,7 +130,8 @@ public class FlwCategoryServiceImpl implements IFlwCategoryService, CategoryServ
         boolean exist = baseMapper.exists(new LambdaQueryWrapper<FlowCategory>()
                 .eq(FlowCategory::getCategoryName, category.getCategoryName())
                 .eq(FlowCategory::getParentId, category.getParentId())
-                .ne(ObjectUtil.isNotNull(category.getCategoryId()), FlowCategory::getCategoryId, category.getCategoryId()));
+                .ne(ObjectUtil.isNotNull(category.getCategoryId()), FlowCategory::getCategoryId,
+                        category.getCategoryId()));
         return !exist;
     }
 
@@ -206,10 +205,14 @@ public class FlwCategoryServiceImpl implements IFlwCategoryService, CategoryServ
         if (ObjectUtil.isNull(oldCategory)) {
             throw new ServiceException("流程分类不存在,无法修改");
         }
+        if (oldCategory.getParentId() == 0L && category.getParentId() != 0L) {
+            throw new ServiceException("不允许修改顶级分类的父级节点");
+        }
         if (!oldCategory.getParentId().equals(category.getParentId())) {
             FlowCategory newParentCategory = baseMapper.selectById(category.getParentId());
             if (ObjectUtil.isNotNull(newParentCategory)) {
-                String newAncestors = newParentCategory.getAncestors() + StringUtils.SEPARATOR + newParentCategory.getCategoryId();
+                String newAncestors = newParentCategory.getAncestors() + StringUtils.SEPARATOR
+                        + newParentCategory.getCategoryId();
                 String oldAncestors = oldCategory.getAncestors();
                 category.setAncestors(newAncestors);
                 updateCategoryChildren(category.getCategoryId(), newAncestors, oldAncestors);

+ 28 - 13
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwCommonServiceImpl.java

@@ -3,6 +3,7 @@ package com.vber.workflow.service.impl;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjectUtil;
 import com.vber.common.core.domain.dto.UserDTO;
+import com.vber.common.core.exception.ServiceException;
 import com.vber.common.core.utils.SpringUtils;
 import com.vber.common.core.utils.StreamUtils;
 import com.vber.common.core.utils.StringUtils;
@@ -25,7 +26,6 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Set;
 
-
 /**
  * 工作流工具
  *
@@ -88,23 +88,35 @@ public class FlwCommonServiceImpl implements IFlwCommonService {
             if (ObjectUtil.isEmpty(messageTypeEnum)) {
                 continue;
             }
-            switch (messageTypeEnum) {
-                case SYSTEM_MESSAGE -> {
-                    SseMessageDto dto = new SseMessageDto();
-                    dto.setUserIds(userIds);
-                    dto.setMessage(message);
-                    SseMessageUtils.publishMessage(dto);
-                }
-                case EMAIL_MESSAGE -> MailUtils.sendText(emails, subject, message);
-                case SMS_MESSAGE -> {
-                    //todo 短信发送
+            try {
+                switch (messageTypeEnum) {
+                    case SYSTEM_MESSAGE -> {
+                        SseMessageDto dto = new SseMessageDto();
+                        dto.setUserIds(userIds);
+                        dto.setMessage(message);
+                        SseMessageUtils.publishMessage(dto);
+                    }
+                    case EMAIL_MESSAGE -> MailUtils.sendText(emails, subject, message);
+                    case SMS_MESSAGE -> {
+                        // LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
+                        // // 根据具体短信服务商参数用法传参
+                        // map.put("code", "1234");
+                        // // 自动获取一个短信服务商
+                        // SmsBlend smsBlend = SmsFactory.getSmsBlend();
+                        // // 指定获取一个短信服务商 configKey
+                        // SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
+                        // SmsResponse smsResponse = smsBlend.sendMessage(phones, templateId, map);
+                        log.info("【短信发送 - TODO】用户数量={} 内容={}", userList.size(), message);
+                    }
+                    default -> log.warn("【消息发送】未处理的消息类型:{}", messageTypeEnum);
                 }
-                default -> throw new IllegalStateException("Unexpected value: " + messageTypeEnum);
+            } catch (Exception ex) {
+                // 记录错误但不抛出,确保主逻辑不受影响
+                log.error("【消息发送失败】类型={},原因={}", messageTypeEnum, ex.getMessage(), ex);
             }
         }
     }
 
-
     /**
      * 申请人节点编码
      *
@@ -114,6 +126,9 @@ public class FlwCommonServiceImpl implements IFlwCommonService {
     @Override
     public String applyNodeCode(Long definitionId) {
         List<Node> firstBetweenNode = FlowEngine.nodeService().getFirstBetweenNode(definitionId, new HashMap<>());
+        if (CollUtil.isEmpty(firstBetweenNode)) {
+            throw new ServiceException("流程定义缺少申请人节点,请检查流程定义配置");
+        }
         return firstBetweenNode.get(0).getNodeCode();
     }
 }

+ 28 - 15
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwDefinitionServiceImpl.java

@@ -92,7 +92,8 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
     @Override
     public TableDataInfo<FlowDefinitionVo> unPublishList(FlowDefinition flowDefinition, PageQuery pageQuery) {
         LambdaQueryWrapper<FlowDefinition> wrapper = buildQueryWrapper(flowDefinition);
-        wrapper.in(FlowDefinition::getIsPublish, Arrays.asList(PublishStatus.UNPUBLISHED.getKey(), PublishStatus.EXPIRED.getKey()));
+        wrapper.in(FlowDefinition::getIsPublish,
+                Arrays.asList(PublishStatus.UNPUBLISHED.getKey(), PublishStatus.EXPIRED.getKey()));
         Page<FlowDefinition> page = flowDefinitionMapper.selectPage(pageQuery.build(), wrapper);
         List<FlowDefinitionVo> list = BeanUtil.copyToList(page.getRecords(), FlowDefinitionVo.class);
         return new TableDataInfo<>(list, page.getTotal());
@@ -100,10 +101,13 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
 
     private LambdaQueryWrapper<FlowDefinition> buildQueryWrapper(FlowDefinition flowDefinition) {
         LambdaQueryWrapper<FlowDefinition> wrapper = Wrappers.lambdaQuery();
-        wrapper.like(StringUtils.isNotBlank(flowDefinition.getFlowCode()), FlowDefinition::getFlowCode, flowDefinition.getFlowCode());
-        wrapper.like(StringUtils.isNotBlank(flowDefinition.getFlowName()), FlowDefinition::getFlowName, flowDefinition.getFlowName());
+        wrapper.like(StringUtils.isNotBlank(flowDefinition.getFlowCode()), FlowDefinition::getFlowCode,
+                flowDefinition.getFlowCode());
+        wrapper.like(StringUtils.isNotBlank(flowDefinition.getFlowName()), FlowDefinition::getFlowName,
+                flowDefinition.getFlowName());
         if (StringUtils.isNotBlank(flowDefinition.getCategory())) {
-            List<Long> categoryIds = flwCategoryMapper.selectCategoryIdsByParentId(Convert.toLong(flowDefinition.getCategory()));
+            List<Long> categoryIds = flwCategoryMapper
+                    .selectCategoryIdsByParentId(Convert.toLong(flowDefinition.getCategory()));
             wrapper.in(FlowDefinition::getCategory, StreamUtils.toList(categoryIds, Convert::toStr));
         }
         wrapper.orderByDesc(FlowDefinition::getCreateTime);
@@ -118,12 +122,14 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public boolean publish(Long id) {
-        List<FlowNode> flowNodes = flowNodeMapper.selectList(new LambdaQueryWrapper<FlowNode>().eq(FlowNode::getDefinitionId, id));
+        List<FlowNode> flowNodes = flowNodeMapper
+                .selectList(new LambdaQueryWrapper<FlowNode>().eq(FlowNode::getDefinitionId, id));
         List<String> errorMsg = new ArrayList<>();
         if (CollUtil.isNotEmpty(flowNodes)) {
             String applyNodeCode = flwCommonService.applyNodeCode(id);
             for (FlowNode flowNode : flowNodes) {
-                if (StringUtils.isBlank(flowNode.getPermissionFlag()) && !applyNodeCode.equals(flowNode.getNodeCode()) && NodeType.BETWEEN.getKey().equals(flowNode.getNodeType())) {
+                if (StringUtils.isBlank(flowNode.getPermissionFlag()) && !applyNodeCode.equals(flowNode.getNodeCode())
+                        && NodeType.BETWEEN.getKey().equals(flowNode.getNodeType())) {
                     errorMsg.add(flowNode.getNodeName());
                 }
             }
@@ -184,7 +190,8 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
         wrapper.in(FlowHisTask::getDefinitionId, ids);
         List<FlowHisTask> flowHisTasks = flowHisTaskMapper.selectList(wrapper);
         if (CollUtil.isNotEmpty(flowHisTasks)) {
-            List<FlowDefinition> flowDefinitions = flowDefinitionMapper.selectByIds(StreamUtils.toList(flowHisTasks, FlowHisTask::getDefinitionId));
+            List<FlowDefinition> flowDefinitions = flowDefinitionMapper
+                    .selectByIds(StreamUtils.toList(flowHisTasks, FlowHisTask::getDefinitionId));
             if (CollUtil.isNotEmpty(flowDefinitions)) {
                 String join = StreamUtils.join(flowDefinitions, FlowDefinition::getFlowCode);
                 log.info("流程定义【{}】已被使用不可被删除!", join);
@@ -208,10 +215,6 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void syncDef(String tenantId) {
-        List<FlowDefinition> flowDefinitions = flowDefinitionMapper.selectList(new LambdaQueryWrapper<FlowDefinition>().eq(FlowDefinition::getTenantId, DEFAULT_TENANT_ID));
-        if (CollUtil.isEmpty(flowDefinitions)) {
-            return;
-        }
         FlowCategory flowCategory = flwCategoryMapper.selectOne(new LambdaQueryWrapper<FlowCategory>()
                 .eq(FlowCategory::getTenantId, DEFAULT_TENANT_ID)
                 .eq(FlowCategory::getCategoryId, FlowConstant.FLOW_CATEGORY_ID));
@@ -223,9 +226,17 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
         flowCategory.setUpdateBy(null);
         flowCategory.setUpdateTime(null);
         flwCategoryMapper.insert(flowCategory);
+
+        List<FlowDefinition> flowDefinitions = flowDefinitionMapper.selectList(
+                new LambdaQueryWrapper<FlowDefinition>().eq(FlowDefinition::getTenantId, DEFAULT_TENANT_ID));
+        if (CollUtil.isEmpty(flowDefinitions)) {
+            return;
+        }
         List<Long> defIds = StreamUtils.toList(flowDefinitions, FlowDefinition::getId);
-        List<FlowNode> flowNodes = flowNodeMapper.selectList(new LambdaQueryWrapper<FlowNode>().in(FlowNode::getDefinitionId, defIds));
-        List<FlowSkip> flowSkips = flowSkipMapper.selectList(new LambdaQueryWrapper<FlowSkip>().in(FlowSkip::getDefinitionId, defIds));
+        List<FlowNode> flowNodes = flowNodeMapper
+                .selectList(new LambdaQueryWrapper<FlowNode>().in(FlowNode::getDefinitionId, defIds));
+        List<FlowSkip> flowSkips = flowSkipMapper
+                .selectList(new LambdaQueryWrapper<FlowSkip>().in(FlowSkip::getDefinitionId, defIds));
         for (FlowDefinition definition : flowDefinitions) {
             FlowDefinition flowDefinition = BeanUtil.toBean(definition, FlowDefinition.class);
             flowDefinition.setId(null);
@@ -240,7 +251,8 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
             log.info("同步流程定义【{}】成功!", definition.getFlowCode());
             Long definitionId = flowDefinition.getId();
             if (CollUtil.isNotEmpty(flowNodes)) {
-                List<FlowNode> nodes = StreamUtils.filter(flowNodes, node -> node.getDefinitionId().equals(definition.getId()));
+                List<FlowNode> nodes = StreamUtils.filter(flowNodes,
+                        node -> node.getDefinitionId().equals(definition.getId()));
                 if (CollUtil.isNotEmpty(nodes)) {
                     List<FlowNode> flowNodeList = BeanUtil.copyToList(nodes, FlowNode.class);
                     flowNodeList.forEach(e -> {
@@ -253,7 +265,8 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
                 }
             }
             if (CollUtil.isNotEmpty(flowSkips)) {
-                List<FlowSkip> skips = StreamUtils.filter(flowSkips, skip -> skip.getDefinitionId().equals(definition.getId()));
+                List<FlowSkip> skips = StreamUtils.filter(flowSkips,
+                        skip -> skip.getDefinitionId().equals(definition.getId()));
                 if (CollUtil.isNotEmpty(skips)) {
                     List<FlowSkip> flowSkipList = BeanUtil.copyToList(skips, FlowSkip.class);
                     flowSkipList.forEach(e -> {

+ 110 - 118
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwInstanceServiceImpl.java

@@ -111,8 +111,14 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
     @Override
     public FlowInstanceVo queryByBusinessId(Long businessId) {
         FlowInstance instance = this.selectInstByBusinessId(Convert.toStr(businessId));
+        if (ObjectUtil.isNull(instance)) {
+            throw new ServiceException(ExceptionCons.NOT_FOUNT_INSTANCE);
+        }
         FlowInstanceVo instanceVo = BeanUtil.toBean(instance, FlowInstanceVo.class);
         Definition definition = defService.getById(instanceVo.getDefinitionId());
+        if (ObjectUtil.isNull(definition)) {
+            throw new ServiceException(ExceptionCons.NOT_FOUNT_DEF);
+        }
         instanceVo.setFlowName(definition.getFlowName());
         instanceVo.setFlowCode(definition.getFlowCode());
         instanceVo.setVersion(definition.getVersion());
@@ -130,15 +136,21 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
      */
     private QueryWrapper<FlowInstanceBo> buildQueryWrapper(FlowInstanceBo flowInstanceBo) {
         QueryWrapper<FlowInstanceBo> queryWrapper = Wrappers.query();
-        queryWrapper.like(StringUtils.isNotBlank(flowInstanceBo.getNodeName()), "fi.node_name", flowInstanceBo.getNodeName());
-        queryWrapper.like(StringUtils.isNotBlank(flowInstanceBo.getFlowName()), "fd.flow_name", flowInstanceBo.getFlowName());
-        queryWrapper.like(StringUtils.isNotBlank(flowInstanceBo.getFlowCode()), "fd.flow_code", flowInstanceBo.getFlowCode());
+        queryWrapper.like(StringUtils.isNotBlank(flowInstanceBo.getNodeName()), "fi.node_name",
+                flowInstanceBo.getNodeName());
+        queryWrapper.like(StringUtils.isNotBlank(flowInstanceBo.getFlowName()), "fd.flow_name",
+                flowInstanceBo.getFlowName());
+        queryWrapper.like(StringUtils.isNotBlank(flowInstanceBo.getFlowCode()), "fd.flow_code",
+                flowInstanceBo.getFlowCode());
         if (StringUtils.isNotBlank(flowInstanceBo.getCategory())) {
-            List<Long> categoryIds = flwCategoryMapper.selectCategoryIdsByParentId(Convert.toLong(flowInstanceBo.getCategory()));
+            List<Long> categoryIds = flwCategoryMapper
+                    .selectCategoryIdsByParentId(Convert.toLong(flowInstanceBo.getCategory()));
             queryWrapper.in("fd.category", StreamUtils.toList(categoryIds, Convert::toStr));
         }
-        queryWrapper.eq(StringUtils.isNotBlank(flowInstanceBo.getBusinessId()), "fi.business_id", flowInstanceBo.getBusinessId());
-        queryWrapper.in(CollUtil.isNotEmpty(flowInstanceBo.getCreateByIds()), "fi.create_by", flowInstanceBo.getCreateByIds());
+        queryWrapper.eq(StringUtils.isNotBlank(flowInstanceBo.getBusinessId()), "fi.business_id",
+                flowInstanceBo.getBusinessId());
+        queryWrapper.in(CollUtil.isNotEmpty(flowInstanceBo.getCreateByIds()), "fi.create_by",
+                flowInstanceBo.getCreateByIds());
         queryWrapper.eq("fi.del_flag", "0");
         queryWrapper.orderByDesc("fi.create_time");
         return queryWrapper;
@@ -151,7 +163,8 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
      */
     @Override
     public FlowInstance selectInstByBusinessId(String businessId) {
-        return flowInstanceMapper.selectOne(new LambdaQueryWrapper<FlowInstance>().eq(FlowInstance::getBusinessId, businessId));
+        return flowInstanceMapper
+                .selectOne(new LambdaQueryWrapper<FlowInstance>().eq(FlowInstance::getBusinessId, businessId));
     }
 
     /**
@@ -181,12 +194,15 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
      */
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public boolean deleteByBusinessIds(List<Long> businessIds) {
-        List<FlowInstance> flowInstances = flowInstanceMapper.selectList(new LambdaQueryWrapper<FlowInstance>().in(FlowInstance::getBusinessId, StreamUtils.toList(businessIds, Convert::toStr)));
+    public boolean deleteByBusinessIds(List<String> businessIds) {
+        List<FlowInstance> flowInstances = flowInstanceMapper
+                .selectList(new LambdaQueryWrapper<FlowInstance>().in(FlowInstance::getBusinessId, businessIds));
         if (CollUtil.isEmpty(flowInstances)) {
             log.warn("未找到对应的流程实例信息,无法执行删除操作。");
             return false;
         }
+        // 发送事件
+        processDeleteHandler(flowInstances);
         return insService.remove(StreamUtils.toList(flowInstances, FlowInstance::getId));
     }
 
@@ -199,39 +215,15 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
     @Transactional(rollbackFor = Exception.class)
     public boolean deleteByInstanceIds(List<Long> instanceIds) {
         // 获取实例信息
-        List<Instance> instances = insService.getByIds(instanceIds);
-        if (CollUtil.isEmpty(instances)) {
+        List<FlowInstance> flowInstances = flowInstanceMapper.selectByIds(instanceIds);
+        if (CollUtil.isEmpty(flowInstances)) {
             log.warn("未找到对应的流程实例信息,无法执行删除操作。");
             return false;
         }
-        // 获取定义信息
-        Map<Long, Definition> definitionMap = StreamUtils.toMap(
-                defService.getByIds(StreamUtils.toList(instances, Instance::getDefinitionId)),
-                Definition::getId,
-                Function.identity()
-        );
-
-        try {
-            // 逐一触发删除事件
-            instances.forEach(instance -> {
-                Definition definition = definitionMap.get(instance.getDefinitionId());
-                if (ObjectUtil.isNull(definition)) {
-                    log.warn("实例 ID: {} 对应的流程定义信息未找到,跳过删除事件触发。", instance.getId());
-                    return;
-                }
-                flowProcessEventHandler.processDeleteHandler(definition.getFlowCode(), instance.getBusinessId());
-            });
-            // 删除实例
-            boolean remove = insService.remove(instanceIds);
-            if (!remove) {
-                log.warn("删除流程实例失败!");
-                throw new ServiceException("删除流程实例失败");
-            }
-        } catch (Exception e) {
-            log.warn("操作失败!{}", e.getMessage());
-            throw new ServiceException(e.getMessage());
-        }
-        return true;
+        // 发送事件
+        processDeleteHandler(flowInstances);
+        // 删除实例
+        return insService.remove(instanceIds);
     }
 
     /**
@@ -243,39 +235,48 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
     @Transactional(rollbackFor = Exception.class)
     public boolean deleteHisByInstanceIds(List<Long> instanceIds) {
         // 获取实例信息
-        List<Instance> instances = insService.getByIds(instanceIds);
-        if (CollUtil.isEmpty(instances)) {
+        List<FlowInstance> flowInstances = flowInstanceMapper.selectByIds(instanceIds);
+        if (CollUtil.isEmpty(flowInstances)) {
             log.warn("未找到对应的流程实例信息,无法执行删除操作。");
             return false;
         }
+        // 发送事件
+        processDeleteHandler(flowInstances);
+        List<FlowTask> flowTaskList = flwTaskService.selectByInstIds(instanceIds);
+        if (CollUtil.isNotEmpty(flowTaskList)) {
+            FlowEngine.userService().deleteByTaskIds(StreamUtils.toList(flowTaskList, FlowTask::getId));
+        }
+        FlowEngine.taskService().deleteByInsIds(instanceIds);
+        FlowEngine.hisTaskService().deleteByInsIds(instanceIds);
+        FlowEngine.insService().removeByIds(instanceIds);
+        return true;
+    }
+
+    private void processDeleteHandler(List<FlowInstance> flowInstances) {
+
+        String userId = LoginHelper.getUserIdStr();
+        for (FlowInstance flowInstance : flowInstances) {
+            // 如果创建人与当前登陆人一致或者当前登陆人为管理员才能删除
+            if (LoginHelper.isSuperAdmin() || flowInstance.getCreateBy().equals(userId)) {
+                continue;
+            }
+            throw new ServiceException("权限不足,无法删除流程实例信息!");
+        }
         // 获取定义信息
         Map<Long, Definition> definitionMap = StreamUtils.toMap(
-                defService.getByIds(StreamUtils.toList(instances, Instance::getDefinitionId)),
+                defService.getByIds(StreamUtils.toList(flowInstances, Instance::getDefinitionId)),
                 Definition::getId,
-                Function.identity()
-        );
-        try {
-            // 逐一触发删除事件
-            instances.forEach(instance -> {
-                Definition definition = definitionMap.get(instance.getDefinitionId());
-                if (ObjectUtil.isNull(definition)) {
-                    log.warn("实例 ID: {} 对应的流程定义信息未找到,跳过删除事件触发。", instance.getId());
-                    return;
-                }
-                flowProcessEventHandler.processDeleteHandler(definition.getFlowCode(), instance.getBusinessId());
-            });
-            List<FlowTask> flowTaskList = flwTaskService.selectByInstIds(instanceIds);
-            if (CollUtil.isNotEmpty(flowTaskList)) {
-                FlowEngine.userService().deleteByTaskIds(StreamUtils.toList(flowTaskList, FlowTask::getId));
+                Function.identity());
+
+        // 逐一触发删除事件
+        flowInstances.forEach(instance -> {
+            Definition definition = definitionMap.get(instance.getDefinitionId());
+            if (ObjectUtil.isNull(definition)) {
+                log.warn("实例 ID: {} 对应的流程定义信息未找到,跳过删除事件触发。", instance.getId());
+                return;
             }
-            FlowEngine.taskService().deleteByInsIds(instanceIds);
-            FlowEngine.hisTaskService().deleteByInsIds(instanceIds);
-            FlowEngine.insService().removeByIds(instanceIds);
-        } catch (Exception e) {
-            log.warn("操作失败!{}", e.getMessage());
-            throw new ServiceException(e.getMessage());
-        }
-        return true;
+            flowProcessEventHandler.processDeleteHandler(definition.getFlowCode(), instance.getBusinessId());
+        });
     }
 
     /**
@@ -286,29 +287,27 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public boolean cancelProcessApply(FlowCancelBo bo) {
-        try {
-            Instance instance = selectInstByBusinessId(bo.getBusinessId());
-            if (instance == null) {
-                throw new ServiceException(ExceptionCons.NOT_FOUNT_INSTANCE);
-            }
-            Definition definition = defService.getById(instance.getDefinitionId());
-            if (definition == null) {
-                throw new ServiceException(ExceptionCons.NOT_FOUNT_DEF);
-            }
-            String message = bo.getMessage();
-            String userIdStr = LoginHelper.getUserIdStr();
-            BusinessStatusEnum.checkCancelStatus(instance.getFlowStatus());
-            FlowParams flowParams = FlowParams.build()
-                    .message(message)
-                    .flowStatus(BusinessStatusEnum.CANCEL.getStatus())
-                    .hisStatus(BusinessStatusEnum.CANCEL.getStatus())
-                    .handler(userIdStr)
-                    .ignore(true);
-            taskService.revoke(instance.getId(), flowParams);
-        } catch (Exception e) {
-            log.error("撤销失败: {}", e.getMessage(), e);
-            throw new ServiceException(e.getMessage());
+        Instance instance = selectInstByBusinessId(bo.getBusinessId());
+        if (instance == null) {
+            throw new ServiceException(ExceptionCons.NOT_FOUNT_INSTANCE);
+        }
+        Definition definition = defService.getById(instance.getDefinitionId());
+        if (definition == null) {
+            throw new ServiceException(ExceptionCons.NOT_FOUNT_DEF);
+        }
+        String userIdStr = LoginHelper.getUserIdStr();
+        if (!LoginHelper.isSuperAdmin() && !instance.getCreateBy().equals(userIdStr)) {
+            throw new ServiceException("权限不足,无法撤销流程!");
         }
+        String message = bo.getMessage();
+        BusinessStatusEnum.checkCancelStatus(instance.getFlowStatus());
+        FlowParams flowParams = FlowParams.build()
+                .message(message)
+                .flowStatus(BusinessStatusEnum.CANCEL.getStatus())
+                .hisStatus(BusinessStatusEnum.CANCEL.getStatus())
+                .handler(userIdStr)
+                .ignore(true);
+        taskService.revoke(instance.getId(), flowParams);
         return true;
     }
 
@@ -367,8 +366,7 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
                 new LambdaQueryWrapper<FlowHisTask>()
                         .eq(FlowHisTask::getInstanceId, instanceId)
                         .eq(FlowHisTask::getNodeType, NodeType.BETWEEN.getKey())
-                        .orderByDesc(FlowHisTask::getUpdateTime)
-        );
+                        .orderByDesc(FlowHisTask::getUpdateTime));
         if (CollUtil.isNotEmpty(hisTasks)) {
             hisTaskVos = BeanUtil.copyToList(hisTasks, FlowHisTaskVo.class);
         }
@@ -403,7 +401,11 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
     @Override
     public Map<String, Object> instanceVariable(Long instanceId) {
         FlowInstance flowInstance = flowInstanceMapper.selectById(instanceId);
-        Map<String, Object> variableMap = Optional.ofNullable(flowInstance.getVariableMap()).orElse(Collections.emptyMap());
+        if (ObjectUtil.isNull(flowInstance)) {
+            throw new ServiceException(ExceptionCons.NOT_FOUNT_INSTANCE);
+        }
+        Map<String, Object> variableMap = Optional.ofNullable(flowInstance.getVariableMap())
+                .orElse(Collections.emptyMap());
         List<Map<String, Object>> variableList = variableMap.entrySet().stream()
                 .map(entry -> Map.of("key", entry.getKey(), "value", entry.getValue()))
                 .toList();
@@ -422,20 +424,15 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
         if (flowInstance == null) {
             throw new ServiceException(ExceptionCons.NOT_FOUNT_INSTANCE);
         }
-        try {
-            Map<String, Object> variableMap = new HashMap<>(Optional.ofNullable(flowInstance.getVariableMap()).orElse(Collections.emptyMap()));
-            if (!variableMap.containsKey(bo.getKey())) {
-                log.error("变量不存在: {}", bo.getKey());
-                return false;
-            }
-            variableMap.put(bo.getKey(), bo.getValue());
-            flowInstance.setVariable(FlowEngine.jsonConvert.objToStr(variableMap));
-            flowInstanceMapper.updateById(flowInstance);
-        } catch (Exception e) {
-            log.error("设置流程变量失败: {}", e.getMessage(), e);
-            throw new ServiceException(e.getMessage());
+        Map<String, Object> variableMap = new HashMap<>(
+                Optional.ofNullable(flowInstance.getVariableMap()).orElse(Collections.emptyMap()));
+        if (!variableMap.containsKey(bo.getKey())) {
+            log.error("变量不存在: {}", bo.getKey());
+            return false;
         }
-        return true;
+        variableMap.put(bo.getKey(), bo.getValue());
+        flowInstance.setVariable(FlowEngine.jsonConvert.objToStr(variableMap));
+        return flowInstanceMapper.updateById(flowInstance) > 0;
     }
 
     /**
@@ -480,21 +477,16 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public boolean processInvalid(FlowInvalidBo bo) {
-        try {
-            Instance instance = insService.getById(bo.getId());
-            if (instance != null) {
-                BusinessStatusEnum.checkInvalidStatus(instance.getFlowStatus());
-            }
-            FlowParams flowParams = FlowParams.build()
-                    .message(bo.getComment())
-                    .flowStatus(BusinessStatusEnum.INVALID.getStatus())
-                    .hisStatus(TaskStatusEnum.INVALID.getStatus())
-                    .ignore(true);
-            taskService.terminationByInsId(bo.getId(), flowParams);
-            return true;
-        } catch (Exception e) {
-            log.error(e.getMessage(), e);
-            throw new ServiceException(e.getMessage());
+        Instance instance = insService.getById(bo.getId());
+        if (instance != null) {
+            BusinessStatusEnum.checkInvalidStatus(instance.getFlowStatus());
         }
+        FlowParams flowParams = FlowParams.build()
+                .message(bo.getComment())
+                .flowStatus(BusinessStatusEnum.INVALID.getStatus())
+                .hisStatus(TaskStatusEnum.INVALID.getStatus())
+                .ignore(true);
+        taskService.terminationByInsId(bo.getId(), flowParams);
+        return true;
     }
 }

+ 107 - 8
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwNodeExtServiceImpl.java

@@ -1,5 +1,6 @@
 package com.vber.workflow.service.impl;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.lang.Dict;
 import cn.hutool.core.util.ObjectUtil;
@@ -17,6 +18,9 @@ import com.vber.workflow.domain.vo.NodeExtVo;
 import com.vber.workflow.service.IFlwNodeExtService;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+
+import org.dromara.warm.flow.core.FlowEngine;
+import org.dromara.warm.flow.core.utils.ExpressionUtil;
 import org.dromara.warm.flow.ui.service.NodeExtService;
 import org.dromara.warm.flow.ui.vo.NodeExt;
 import org.springframework.stereotype.Service;
@@ -45,7 +49,7 @@ public class FlwNodeExtServiceImpl implements NodeExtService, IFlwNodeExtService
                 CopySettingEnum.class.getSimpleName(),
                 Map.of(
                         "label", "抄送对象",
-                        "type", 2,
+                        "type", 5,
                         "must", false,
                         "multiple", false,
                         "desc", "设置该节点的抄送办理人"),
@@ -55,7 +59,7 @@ public class FlwNodeExtServiceImpl implements NodeExtService, IFlwNodeExtService
                         "type", 2,
                         "must", false,
                         "multiple", false,
-                        "desc", "节点执行时可以使用的自定义参数"),
+                        "desc", "节点执行时可设置自定义参数,多个参数以逗号分隔,如:key1=value1,key2=value2"),
                 ButtonPermissionEnum.class.getSimpleName(),
                 Map.of(
                         "label", "权限按钮",
@@ -132,7 +136,7 @@ public class FlwNodeExtServiceImpl implements NodeExtService, IFlwNodeExtService
         childNode.setCode(simpleName);
         // label名称
         childNode.setLabel(Convert.toStr(map.get("label")));
-        // 1:输入框 2:文本域 3:下拉框 4:选择框
+        // 1:输入框 2:文本域 3:下拉框 4:选择框 5:用户选择器
         childNode.setType(Convert.toInt(map.get("type"), 1));
         // 是否必填
         childNode.setMust(Convert.toBool(map.get("must"), false));
@@ -163,7 +167,7 @@ public class FlwNodeExtServiceImpl implements NodeExtService, IFlwNodeExtService
         childNode.setCode(dictType);
         // label名称
         childNode.setLabel(dictTypeDTO.getDictName());
-        // 1:输入框 2:文本域 3:下拉框 4:选择框
+        // 1:输入框 2:文本域 3:下拉框 4:选择框 5:用户选择器
         childNode.setType(3);
         // 是否必填
         childNode.setMust(false);
@@ -191,19 +195,24 @@ public class FlwNodeExtServiceImpl implements NodeExtService, IFlwNodeExtService
      * 示例 JSON:
      * [
      * {"code": "ButtonPermissionEnum", "value": "back,termination"},
-     * {"code": "CopySettingEnum", "value": "1"},
+     * {"code": "CopySettingEnum", "value":
+     * "1,3,4,#{@spelRuleComponent.selectDeptLeaderById(#deptId", "#roleId)}"},
      * {"code": "VariablesEnum", "value": "key1=value1,key2=value2"}
      * ]
      *
-     * @param ext 扩展属性 JSON 字符串
+     * @param ext      扩展属性 JSON 字符串
+     * @param variable 流程变量
      * @return NodeExtVo 对象,封装按钮权限列表、抄送对象集合和自定义参数 Map
      */
     @Override
-    public NodeExtVo parseNodeExt(String ext) {
+    public NodeExtVo parseNodeExt(String ext, Map<String, Object> variable) {
         NodeExtVo nodeExtVo = new NodeExtVo();
 
         // 解析 JSON 为 Dict 列表
         List<Dict> nodeExtMap = JsonUtils.parseArrayMap(ext);
+        if (ObjectUtil.isEmpty(nodeExtMap)) {
+            return nodeExtVo;
+        }
 
         for (Dict nodeExt : nodeExtMap) {
             String code = nodeExt.getStr("code");
@@ -228,8 +237,20 @@ public class FlwNodeExtServiceImpl implements NodeExtService, IFlwNodeExtService
                 nodeExtVo.setButtonPermissions(buttonList);
 
             } else if (CopySettingEnum.class.getSimpleName().equals(code)) {
+                List<String> permissions = spelSmartSplit(value).stream()
+                        .map(s -> {
+                            List<String> result = ExpressionUtil.evalVariable(s, variable);
+                            if (CollUtil.isNotEmpty(result)) {
+                                return result;
+                            }
+                            return Collections.singletonList(s);
+                        }).filter(Objects::nonNull)
+                        .flatMap(List::stream)
+                        .distinct()
+                        .collect(Collectors.toList());
+                List<String> copySettings = FlowEngine.permissionHandler().convertPermissions(permissions);
                 // 解析抄送对象 ID 集合
-                nodeExtVo.setCopySettings(StringUtils.str2Set(value, StringUtils.SEPARATOR));
+                nodeExtVo.setCopySettings(new HashSet<>(copySettings));
 
             } else if (VariablesEnum.class.getSimpleName().equals(code)) {
                 // 解析自定义参数
@@ -248,4 +269,82 @@ public class FlwNodeExtServiceImpl implements NodeExtService, IFlwNodeExtService
         return nodeExtVo;
     }
 
+    /**
+     * 按逗号分割字符串,但保留 #{...} 表达式和字符串常量中的逗号
+     */
+    private static List<String> spelSmartSplit(String str) {
+        List<String> result = new ArrayList<>();
+        if (str == null || str.trim().isEmpty()) {
+            return result;
+        }
+
+        StringBuilder token = new StringBuilder();
+        // #{...} 的嵌套深度
+        int depth = 0;
+        // 是否在字符串常量中(" 或 ')
+        boolean inString = false;
+        // 当前字符串引号类型
+        char stringQuote = 0;
+
+        for (int i = 0; i < str.length(); i++) {
+            char c = str.charAt(i);
+
+            // 检测进入 SpEL 表达式 #{...}
+            if (!inString && c == '#' && depth == 0 && checkNext(str, i, '{')) {
+                depth++;
+                token.append("#{");
+                // 跳过 {
+                i++;
+                continue;
+            }
+
+            // 在表达式中遇到 { 或 } 改变嵌套深度
+            if (!inString && depth > 0) {
+                if (c == '{') {
+                    depth++;
+                } else if (c == '}') {
+                    depth--;
+                }
+                token.append(c);
+                continue;
+            }
+
+            // 检测字符串开始/结束
+            if (depth > 0 && (c == '"' || c == '\'')) {
+                if (!inString) {
+                    inString = true;
+                    stringQuote = c;
+                } else if (stringQuote == c) {
+                    inString = false;
+                }
+                token.append(c);
+                continue;
+            }
+
+            // 外层逗号才分割
+            if (c == ',' && depth == 0 && !inString) {
+                String part = token.toString().trim();
+                if (!part.isEmpty()) {
+                    result.add(part);
+                }
+                token.setLength(0);
+                continue;
+            }
+
+            token.append(c);
+        }
+
+        // 添加最后一个
+        String part = token.toString().trim();
+        if (!part.isEmpty()) {
+            result.add(part);
+        }
+
+        return result;
+    }
+
+    private static boolean checkNext(String str, int index, char expected) {
+        return index + 1 < str.length() && str.charAt(index + 1) == expected;
+    }
+
 }

+ 12 - 12
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwSpelServiceImpl.java

@@ -28,7 +28,7 @@ import java.util.List;
 import java.util.Map;
 
 /**
- * 流程spel达式定义Service业务层处理
+ * 流程spel达式定义Service业务层处理
  *
  * @author Iwb
  */
@@ -41,10 +41,10 @@ public class FlwSpelServiceImpl implements IFlwSpelService {
     private final FlwSpelMapper baseMapper;
 
     /**
-     * 查询流程spel达式定义
+     * 查询流程spel达式定义
      *
      * @param id 主键
-     * @return 流程spel达式定义
+     * @return 流程spel达式定义
      */
     @Override
     public FlowSpelVo queryById(Long id) {
@@ -52,11 +52,11 @@ public class FlwSpelServiceImpl implements IFlwSpelService {
     }
 
     /**
-     * 分页查询流程spel达式定义列表
+     * 分页查询流程spel达式定义列表
      *
      * @param bo        查询条件
      * @param pageQuery 分页参数
-     * @return 流程spel达式定义分页列表
+     * @return 流程spel达式定义分页列表
      */
     @Override
     public TableDataInfo<FlowSpelVo> queryPageList(FlowSpelBo bo, PageQuery pageQuery) {
@@ -66,10 +66,10 @@ public class FlwSpelServiceImpl implements IFlwSpelService {
     }
 
     /**
-     * 查询符合条件的流程spel达式定义列表
+     * 查询符合条件的流程spel达式定义列表
      *
      * @param bo 查询条件
-     * @return 流程spel达式定义列表
+     * @return 流程spel达式定义列表
      */
     @Override
     public List<FlowSpelVo> queryList(FlowSpelBo bo) {
@@ -91,9 +91,9 @@ public class FlwSpelServiceImpl implements IFlwSpelService {
     }
 
     /**
-     * 新增流程spel达式定义
+     * 新增流程spel达式定义
      *
-     * @param bo 流程spel达式定义
+     * @param bo 流程spel达式定义
      * @return 是否新增成功
      */
     @Override
@@ -108,9 +108,9 @@ public class FlwSpelServiceImpl implements IFlwSpelService {
     }
 
     /**
-     * 修改流程spel达式定义
+     * 修改流程spel达式定义
      *
-     * @param bo 流程spel达式定义
+     * @param bo 流程spel达式定义
      * @return 是否修改成功
      */
     @Override
@@ -127,7 +127,7 @@ public class FlwSpelServiceImpl implements IFlwSpelService {
     }
 
     /**
-     * 校验并批量删除流程spel达式定义信息
+     * 校验并批量删除流程spel达式定义信息
      *
      * @param ids     待删除的主键集合
      * @param isValid 是否进行有效性校验

+ 240 - 177
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwTaskServiceImpl.java

@@ -5,9 +5,11 @@ import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.lang.Dict;
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+
+import com.baomidou.lock.annotation.Lock4j;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.vber.common.core.domain.dto.StartProcessReturnDTO;
@@ -23,10 +25,11 @@ import com.vber.common.core.validate.EditGroup;
 import com.vber.common.json.utils.JsonUtils;
 import com.vber.common.mybatis.core.page.PageQuery;
 import com.vber.common.mybatis.core.page.TableDataInfo;
+import com.vber.common.mybatis.utils.IdGeneratorUtil;
 import com.vber.common.satoken.utils.LoginHelper;
 import com.vber.workflow.common.ConditionalOnEnable;
-import com.vber.workflow.common.constant.FlowConstant;
 import com.vber.workflow.common.enums.TaskAssigneeType;
+import com.vber.workflow.common.enums.TaskOperationEnum;
 import com.vber.workflow.common.enums.TaskStatusEnum;
 import com.vber.workflow.domain.FlowInstanceBizExt;
 import com.vber.workflow.domain.bo.*;
@@ -46,6 +49,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.dromara.warm.flow.core.FlowEngine;
 import org.dromara.warm.flow.core.dto.FlowParams;
 import org.dromara.warm.flow.core.entity.*;
+import org.dromara.warm.flow.core.enums.CooperateType;
 import org.dromara.warm.flow.core.enums.NodeType;
 import org.dromara.warm.flow.core.enums.SkipType;
 import org.dromara.warm.flow.core.enums.UserType;
@@ -60,7 +64,6 @@ import org.dromara.warm.flow.orm.mapper.FlowTaskMapper;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
-import java.math.BigDecimal;
 import java.util.*;
 
 import static com.vber.workflow.common.constant.FlowConstant.*;
@@ -84,7 +87,6 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
     private final FlowInstanceMapper flowInstanceMapper;
     private final FlowTaskMapper flowTaskMapper;
     private final FlowHisTaskMapper flowHisTaskMapper;
-    private final IdentifierGenerator identifierGenerator;
     private final UserService userService;
     private final FlwTaskMapper flwTaskMapper;
     private final FlwCategoryMapper flwCategoryMapper;
@@ -101,6 +103,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
      */
     @Override
     @Transactional(rollbackFor = Exception.class)
+    @Lock4j(keys = { "#startProcessBo.flowCode + #startProcessBo.businessId" })
     public StartProcessReturnDTO startWorkFlow(StartProcessBo startProcessBo) {
         String businessId = startProcessBo.getBusinessId();
         if (StringUtils.isBlank(businessId)) {
@@ -125,6 +128,9 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
             // 已存在流程
             BusinessStatusEnum.checkStartStatus(flowInstance.getFlowStatus());
             List<Task> taskList = taskService.list(new FlowTask().setInstanceId(flowInstance.getId()));
+            if (CollUtil.isEmpty(taskList)) {
+                throw new ServiceException("流程实例缺少任务,请检查流程定义配置");
+            }
             taskService.mergeVariable(flowInstance, variables);
             insService.updateById(flowInstance);
             StartProcessReturnDTO dto = new StartProcessReturnDTO();
@@ -137,25 +143,26 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
 
         // 将流程定义内的扩展参数设置到变量中
         Definition definition = FlowEngine.defService().getPublishByFlowCode(startProcessBo.getFlowCode());
+        if (ObjectUtil.isNull(definition)) {
+            throw new ServiceException("流程【" + startProcessBo.getFlowCode() + "】未发布,请先在流程设计器中发布流程定义");
+        }
         Dict dict = JsonUtils.parseMap(definition.getExt());
-        boolean autoPass = !ObjectUtil.isNull(dict) && dict.getBool(FlowConstant.AUTO_PASS);
-        variables.put(FlowConstant.AUTO_PASS, autoPass);
-        variables.put(FlowConstant.BUSINESS_CODE, this.generateBusinessCode(bizExt));
+        boolean autoPass = !ObjectUtil.isNull(dict) && dict.getBool(AUTO_PASS);
+        variables.put(AUTO_PASS, autoPass);
+        variables.put(BUSINESS_CODE, this.generateBusinessCode(bizExt));
         FlowParams flowParams = FlowParams.build()
                 .handler(startProcessBo.getHandler())
                 .flowCode(startProcessBo.getFlowCode())
                 .variable(startProcessBo.getVariables())
                 .flowStatus(BusinessStatusEnum.DRAFT.getStatus());
-        Instance instance;
-        try {
-            instance = insService.start(businessId, flowParams);
-        } catch (Exception e) {
-            throw new ServiceException(e.getMessage());
-        }
+        Instance instance = insService.start(businessId, flowParams);
         // 保存流程实例业务信息
         this.buildFlowInstanceBizExt(instance, bizExt);
         // 申请人执行流程
         List<Task> taskList = taskService.list(new FlowTask().setInstanceId(instance.getId()));
+        if (CollUtil.isEmpty(taskList)) {
+            throw new ServiceException("流程启动失败,未生成任务");
+        }
         if (taskList.size() > 1) {
             throw new ServiceException("请检查流程第一个环节是否为申请人!");
         }
@@ -196,52 +203,54 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
      */
     @Override
     @Transactional(rollbackFor = Exception.class)
+    @Lock4j(keys = { "#completeTaskBo.taskId" })
     public boolean completeTask(CompleteTaskBo completeTaskBo) {
-        try {
-            // 获取任务ID并查询对应的流程任务和实例信息
-            Long taskId = completeTaskBo.getTaskId();
-            List<String> messageType = completeTaskBo.getMessageType();
-            String notice = completeTaskBo.getNotice();
-            // 获取抄送人
-            List<FlowCopyBo> flowCopyList = completeTaskBo.getFlowCopyList();
-            // 设置抄送人
-            Map<String, Object> variables = completeTaskBo.getVariables();
-            variables.put(FlowConstant.FLOW_COPY_LIST, flowCopyList);
-            // 消息类型
-            variables.put(FlowConstant.MESSAGE_TYPE, messageType);
-            // 消息通知
-            variables.put(FlowConstant.MESSAGE_NOTICE, notice);
-
-            FlowTask flowTask = flowTaskMapper.selectById(taskId);
-            if (ObjectUtil.isNull(flowTask)) {
-                throw new ServiceException("流程任务不存在或任务已审批!");
-            }
-            Instance ins = insService.getById(flowTask.getInstanceId());
-            // 检查流程状态是否为草稿、已撤销或已退回状态,若是则执行流程提交监听
-            if (BusinessStatusEnum.isDraftOrCancelOrBack(ins.getFlowStatus())) {
-                variables.put(FlowConstant.SUBMIT, true);
-            }
-            // 设置弹窗处理人
-            Map<String, Object> assigneeMap = setPopAssigneeMap(completeTaskBo.getAssigneeMap(), ins.getVariableMap());
-            if (CollUtil.isNotEmpty(assigneeMap)) {
-                variables.putAll(assigneeMap);
-            }
-            // 构建流程参数,包括变量、跳转类型、消息、处理人、权限等信息
-            FlowParams flowParams = FlowParams.build()
-                    .handler(completeTaskBo.getHandler())
-                    .variable(variables)
-                    .skipType(SkipType.PASS.getKey())
-                    .message(completeTaskBo.getMessage())
-                    .flowStatus(BusinessStatusEnum.WAITING.getStatus())
-                    .hisStatus(TaskStatusEnum.PASS.getStatus())
-                    .hisTaskExt(completeTaskBo.getFileId());
-            Boolean autoPass = Convert.toBool(variables.getOrDefault(AUTO_PASS, false));
-            skipTask(taskId, flowParams, flowTask.getInstanceId(), autoPass);
-            return true;
-        } catch (Exception e) {
-            log.error(e.getMessage(), e);
-            throw new ServiceException(e.getMessage());
-        }
+        // 获取任务ID并查询对应的流程任务和实例信息
+        Long taskId = completeTaskBo.getTaskId();
+        List<String> messageType = completeTaskBo.getMessageType();
+        String notice = completeTaskBo.getNotice();
+        // 获取抄送人
+        List<FlowCopyBo> flowCopyList = completeTaskBo.getFlowCopyList();
+        // 设置抄送人
+        Map<String, Object> variables = completeTaskBo.getVariables();
+        variables.put(FLOW_COPY_LIST, flowCopyList);
+        // 消息类型
+        variables.put(MESSAGE_TYPE, messageType);
+        // 消息通知
+        variables.put(MESSAGE_NOTICE, notice);
+
+        FlowTask flowTask = flowTaskMapper.selectById(taskId);
+        if (ObjectUtil.isNull(flowTask)) {
+            throw new ServiceException("流程任务不存在或任务已审批!");
+        }
+        Instance ins = insService.getById(flowTask.getInstanceId());
+        if (ObjectUtil.isNull(ins)) {
+            throw new ServiceException("流程实例不存在");
+        }
+        // 检查流程状态是否为草稿、已撤销或已退回状态,若是则执行流程提交监听
+        if (BusinessStatusEnum.isDraftOrCancelOrBack(ins.getFlowStatus())) {
+            variables.put(SUBMIT, true);
+        }
+        // 设置弹窗处理人
+        Map<String, Object> assigneeMap = setPopAssigneeMap(completeTaskBo.getAssigneeMap(), ins.getVariableMap());
+        if (CollUtil.isNotEmpty(assigneeMap)) {
+            variables.putAll(assigneeMap);
+        }
+        // 构建流程参数,包括变量、跳转类型、消息、处理人、权限等信息
+        FlowParams flowParams = FlowParams.build()
+                .handler(completeTaskBo.getHandler())
+                .variable(variables)
+                .ignore(Convert.toBool(variables.getOrDefault(VAR_IGNORE, false)))
+                .ignoreDepute(Convert.toBool(variables.getOrDefault(VAR_IGNORE_DEPUTE, false)))
+                .ignoreCooperate(Convert.toBool(variables.getOrDefault(VAR_IGNORE_COOPERATE, false)))
+                .skipType(SkipType.PASS.getKey())
+                .message(completeTaskBo.getMessage())
+                .flowStatus(BusinessStatusEnum.WAITING.getStatus())
+                .hisStatus(TaskStatusEnum.PASS.getStatus())
+                .hisTaskExt(completeTaskBo.getFileId());
+        Boolean autoPass = Convert.toBool(variables.getOrDefault(AUTO_PASS, false));
+        skipTask(taskId, flowParams, flowTask.getInstanceId(), autoPass);
+        return true;
     }
 
     /**
@@ -272,9 +281,9 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
                     continue;
                 }
                 flowParams.message("流程引擎自动审批!").variable(Map.of(
-                        FlowConstant.SUBMIT, false,
-                        FlowConstant.FLOW_COPY_LIST, Collections.emptyList(),
-                        FlowConstant.MESSAGE_NOTICE, StringUtils.EMPTY));
+                        SUBMIT, false,
+                        FLOW_COPY_LIST, Collections.emptyList(),
+                        MESSAGE_NOTICE, StringUtils.EMPTY));
                 skipTask(task.getId(), flowParams, instanceId, true);
             }
         }
@@ -302,10 +311,14 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
                     List<String> variableUserIds = Arrays.asList(userIds.split(StringUtils.SEPARATOR));
                     hashSet.addAll(popUserIds);
                     hashSet.addAll(variableUserIds);
-                    map.put(entry.getKey(), StringUtils.joinComma(hashSet));
+                    map.put(TaskStatusEnum.PASS.getStatus() + StrUtil.COLON + entry.getKey(),
+                            StringUtils.joinComma(hashSet));
+                    map.put(TaskStatusEnum.BACK.getStatus() + StrUtil.COLON + entry.getKey(),
+                            StringUtils.joinComma(hashSet));
                 }
             } else {
-                map.put(entry.getKey(), entry.getValue());
+                map.put(TaskStatusEnum.PASS.getStatus() + StrUtil.COLON + entry.getKey(), entry.getValue());
+                map.put(TaskStatusEnum.BACK.getStatus() + StrUtil.COLON + entry.getKey(), entry.getValue());
             }
         }
         return map;
@@ -324,21 +337,21 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
         }
         // 添加抄送人记录
         FlowHisTask flowHisTask = flowHisTaskMapper.selectList(
-                        new LambdaQueryWrapper<>(FlowHisTask.class)
-                                .eq(FlowHisTask::getTaskId, task.getId()))
+                new LambdaQueryWrapper<>(FlowHisTask.class)
+                        .eq(FlowHisTask::getTaskId, task.getId()))
                 .get(0);
         FlowNode flowNode = new FlowNode();
         flowNode.setNodeCode(flowHisTask.getTargetNodeCode());
         flowNode.setNodeName(flowHisTask.getTargetNodeName());
         // 生成新的任务id
-        long taskId = identifierGenerator.nextId(null).longValue();
+        long taskId = IdGeneratorUtil.nextLongId();
         task.setId(taskId);
         task.setNodeName("【抄送】" + task.getNodeName());
         Date updateTime = new Date(flowHisTask.getUpdateTime().getTime() - 1000);
         FlowParams flowParams = FlowParams.build()
                 .skipType(SkipType.NONE.getKey())
                 .hisStatus(TaskStatusEnum.COPY.getStatus())
-                .message("【抄送给】" + StreamUtils.join(flowCopyList, FlowCopyBo::getUserName));
+                .message("【抄送给】" + StreamUtils.join(flowCopyList, FlowCopyBo::getNickName));
         HisTask hisTask = hisTaskService.setSkipHisTask(task, flowNode, flowParams);
         hisTask.setCreateTime(updateTime);
         hisTask.setUpdateTime(updateTime);
@@ -379,7 +392,6 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
         QueryWrapper<FlowTaskBo> queryWrapper = buildQueryWrapper(flowTaskBo);
         queryWrapper.eq("t.node_type", NodeType.BETWEEN.getKey());
         queryWrapper.in("t.approver", LoginHelper.getUserIdStr());
-        queryWrapper.orderByDesc("t.create_time").orderByDesc("t.update_time");
         Page<FlowHisTaskVo> page = flwTaskMapper.getListFinishTask(pageQuery.build(), queryWrapper);
         return TableDataInfo.build(page);
     }
@@ -446,17 +458,21 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
     }
 
     private QueryWrapper<FlowTaskBo> buildQueryWrapper(FlowTaskBo flowTaskBo) {
+        Map<String, Object> params = flowTaskBo.getParams();
         QueryWrapper<FlowTaskBo> wrapper = Wrappers.query();
         wrapper.like(StringUtils.isNotBlank(flowTaskBo.getNodeName()), "t.node_name", flowTaskBo.getNodeName());
         wrapper.like(StringUtils.isNotBlank(flowTaskBo.getFlowName()), "t.flow_name", flowTaskBo.getFlowName());
         wrapper.like(StringUtils.isNotBlank(flowTaskBo.getFlowCode()), "t.flow_code", flowTaskBo.getFlowCode());
+        wrapper.like(StringUtils.isNotBlank(flowTaskBo.getFlowStatus()), "t.flow_status", flowTaskBo.getFlowStatus());
         wrapper.in(CollUtil.isNotEmpty(flowTaskBo.getCreateByIds()), "t.create_by", flowTaskBo.getCreateByIds());
         if (StringUtils.isNotBlank(flowTaskBo.getCategory())) {
             List<Long> categoryIds = flwCategoryMapper
                     .selectCategoryIdsByParentId(Convert.toLong(flowTaskBo.getCategory()));
             wrapper.in("t.category", StreamUtils.toList(categoryIds, Convert::toStr));
         }
-        wrapper.orderByDesc("t.create_time");
+        wrapper.between(params.get("beginTime") != null && params.get("endTime") != null,
+                "t.create_time", params.get("beginTime"), params.get("endTime"));
+        wrapper.orderByDesc("t.create_time").orderByDesc("t.update_time");
         return wrapper;
     }
 
@@ -468,41 +484,39 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public boolean backProcess(BackProcessBo bo) {
-        try {
-            Long taskId = bo.getTaskId();
-            String notice = bo.getNotice();
-            List<String> messageType = bo.getMessageType();
-            String message = bo.getMessage();
-            FlowTask task = flowTaskMapper.selectById(taskId);
-            if (ObjectUtil.isNull(task)) {
-                throw new ServiceException("任务不存在!");
-            }
-            Instance inst = insService.getById(task.getInstanceId());
-            BusinessStatusEnum.checkBackStatus(inst.getFlowStatus());
-            Long definitionId = task.getDefinitionId();
-            String applyNodeCode = flwCommonService.applyNodeCode(definitionId);
-
-            Map<String, Object> variable = new HashMap<>();
-            // 消息类型
-            variable.put(FlowConstant.MESSAGE_TYPE, messageType);
-            // 消息通知
-            variable.put(FlowConstant.MESSAGE_NOTICE, notice);
-
-            FlowParams flowParams = FlowParams.build()
-                    .nodeCode(bo.getNodeCode())
-                    .variable(variable)
-                    .message(message)
-                    .skipType(SkipType.REJECT.getKey())
-                    .flowStatus(applyNodeCode.equals(bo.getNodeCode()) ? TaskStatusEnum.BACK.getStatus()
-                            : TaskStatusEnum.WAITING.getStatus())
-                    .hisStatus(TaskStatusEnum.BACK.getStatus())
-                    .hisTaskExt(bo.getFileId());
-            taskService.skip(task.getId(), flowParams);
-            return true;
-        } catch (Exception e) {
-            log.error(e.getMessage(), e);
-            throw new ServiceException(e.getMessage());
+        Long taskId = bo.getTaskId();
+        String notice = bo.getNotice();
+        List<String> messageType = bo.getMessageType();
+        String message = bo.getMessage();
+        FlowTask task = flowTaskMapper.selectById(taskId);
+        if (ObjectUtil.isNull(task)) {
+            throw new ServiceException("任务不存在!");
         }
+        Instance inst = insService.getById(task.getInstanceId());
+        if (ObjectUtil.isNull(inst)) {
+            throw new ServiceException("流程实例不存在");
+        }
+        BusinessStatusEnum.checkBackStatus(inst.getFlowStatus());
+        Long definitionId = task.getDefinitionId();
+        String applyNodeCode = flwCommonService.applyNodeCode(definitionId);
+
+        Map<String, Object> variable = new HashMap<>();
+        // 消息类型
+        variable.put(MESSAGE_TYPE, messageType);
+        // 消息通知
+        variable.put(MESSAGE_NOTICE, notice);
+
+        FlowParams flowParams = FlowParams.build()
+                .nodeCode(bo.getNodeCode())
+                .variable(variable)
+                .message(message)
+                .skipType(SkipType.REJECT.getKey())
+                .flowStatus(applyNodeCode.equals(bo.getNodeCode()) ? TaskStatusEnum.BACK.getStatus()
+                        : TaskStatusEnum.WAITING.getStatus())
+                .hisStatus(TaskStatusEnum.BACK.getStatus())
+                .hisTaskExt(bo.getFileId());
+        taskService.skip(task.getId(), flowParams);
+        return true;
     }
 
     /**
@@ -514,6 +528,9 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
     @Override
     public List<Node> getBackTaskNode(Long taskId, String nowNodeCode) {
         FlowTask task = flowTaskMapper.selectById(taskId);
+        if (ObjectUtil.isNull(task)) {
+            throw new ServiceException("任务不存在!");
+        }
         List<Node> nodeCodes = nodeService.getByNodeCodes(Collections.singletonList(nowNodeCode),
                 task.getDefinitionId());
         if (!CollUtil.isNotEmpty(nodeCodes)) {
@@ -531,8 +548,22 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
         }
         // 获取可驳回的前置节点
         List<Node> nodes = nodeService.previousNodeList(task.getDefinitionId(), nowNodeCode);
-        if (CollUtil.isNotEmpty(nodes)) {
-            return StreamUtils.filter(nodes, e -> NodeType.BETWEEN.getKey().equals(e.getNodeType()));
+        List<HisTask> hisTaskList = hisTaskService.getByInsId(task.getInstanceId());
+
+        Map<String, Node> nodeMap = StreamUtils.toIdentityMap(nodes, Node::getNodeCode);
+        Set<String> added = new HashSet<>();
+        List<Node> backNodeList = new ArrayList<>();
+        for (HisTask hisTask : hisTaskList) {
+            Node nodeValue = nodeMap.get(hisTask.getNodeCode());
+            if (nodeValue != null
+                    && NodeType.BETWEEN.getKey().equals(nodeValue.getNodeType())
+                    && added.add(nodeValue.getNodeCode())) {
+                backNodeList.add(nodeValue);
+            }
+        }
+        if (CollUtil.isNotEmpty(backNodeList)) {
+            Collections.reverse(backNodeList);
+            return backNodeList;
         }
         return nodes;
     }
@@ -545,26 +576,21 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public boolean terminationTask(FlowTerminationBo bo) {
-        try {
-            Long taskId = bo.getTaskId();
-            Task task = taskService.getById(taskId);
-            if (task == null) {
-                throw new ServiceException("任务不存在!");
-            }
-            Instance instance = insService.getById(task.getInstanceId());
-            if (ObjectUtil.isNotNull(instance)) {
-                BusinessStatusEnum.checkInvalidStatus(instance.getFlowStatus());
-            }
-            FlowParams flowParams = FlowParams.build()
-                    .message(bo.getComment())
-                    .flowStatus(BusinessStatusEnum.TERMINATION.getStatus())
-                    .hisStatus(TaskStatusEnum.TERMINATION.getStatus());
-            taskService.termination(taskId, flowParams);
-            return true;
-        } catch (Exception e) {
-            log.error(e.getMessage(), e);
-            throw new ServiceException(e.getMessage());
+        Long taskId = bo.getTaskId();
+        Task task = taskService.getById(taskId);
+        if (task == null) {
+            throw new ServiceException("任务不存在!");
+        }
+        Instance instance = insService.getById(task.getInstanceId());
+        if (ObjectUtil.isNotNull(instance)) {
+            BusinessStatusEnum.checkInvalidStatus(instance.getFlowStatus());
         }
+        FlowParams flowParams = FlowParams.build()
+                .message(bo.getComment())
+                .flowStatus(BusinessStatusEnum.TERMINATION.getStatus())
+                .hisStatus(TaskStatusEnum.TERMINATION.getStatus());
+        taskService.termination(taskId, flowParams);
+        return true;
     }
 
     /**
@@ -590,7 +616,13 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
         }
         FlowTaskVo flowTaskVo = BeanUtil.toBean(task, FlowTaskVo.class);
         Instance instance = insService.getById(task.getInstanceId());
+        if (ObjectUtil.isNull(instance)) {
+            throw new ServiceException("流程实例不存在");
+        }
         Definition definition = defService.getById(task.getDefinitionId());
+        if (ObjectUtil.isNull(definition)) {
+            throw new ServiceException("流程定义不存在");
+        }
         flowTaskVo.setFlowStatus(instance.getFlowStatus());
         flowTaskVo.setVersion(definition.getVersion());
         flowTaskVo.setFlowCode(definition.getFlowCode());
@@ -600,9 +632,13 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
         if (ObjectUtil.isNull(flowNode)) {
             throw new NullPointerException("当前【" + flowTaskVo.getNodeCode() + "】节点编码不存在");
         }
-        NodeExtVo nodeExtVo = flwNodeExtService.parseNodeExt(flowNode.getExt());
+        NodeExtVo nodeExtVo = flwNodeExtService.parseNodeExt(flowNode.getExt(), instance.getVariableMap());
         // 设置按钮权限
-        flowTaskVo.setButtonList(nodeExtVo.getButtonPermissions());
+        if (CollUtil.isNotEmpty(nodeExtVo.getButtonPermissions())) {
+            flowTaskVo.setButtonList(nodeExtVo.getButtonPermissions());
+        } else {
+            flowTaskVo.setButtonList(new ArrayList<>());
+        }
         if (CollUtil.isNotEmpty(nodeExtVo.getCopySettings())) {
             List<FlowCopyVo> list = StreamUtils.toList(nodeExtVo.getCopySettings(),
                     x -> new FlowCopyVo(Convert.toLong(x)));
@@ -610,7 +646,11 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
         } else {
             flowTaskVo.setCopyList(new ArrayList<>());
         }
-        flowTaskVo.setVarList(nodeExtVo.getVariables());
+        if (CollUtil.isNotEmpty(nodeExtVo.getVariables())) {
+            flowTaskVo.setVarList(nodeExtVo.getVariables());
+        } else {
+            flowTaskVo.setVarList(new HashMap<>());
+        }
         flowTaskVo.setNodeRatio(flowNode.getNodeRatio());
         flowTaskVo.setApplyNode(flowNode.getNodeCode().equals(flwCommonService.applyNodeCode(task.getDefinitionId())));
         return flowTaskVo;
@@ -626,12 +666,24 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
         Long taskId = bo.getTaskId();
         Map<String, Object> variables = bo.getVariables();
         Task task = taskService.getById(taskId);
+        if (ObjectUtil.isNull(task)) {
+            throw new ServiceException("任务不存在!");
+        }
         Instance instance = insService.getById(task.getInstanceId());
+        if (ObjectUtil.isNull(instance)) {
+            throw new ServiceException("流程实例不存在");
+        }
         Definition definition = defService.getById(task.getDefinitionId());
+        if (ObjectUtil.isNull(definition)) {
+            throw new ServiceException("流程定义不存在");
+        }
         Map<String, Object> mergeVariable = MapUtil.mergeAll(instance.getVariableMap(), variables);
         // 获取下一节点列表
         List<Node> nextNodeList = nodeService.getNextNodeList(task.getDefinitionId(), task.getNodeCode(), null,
                 SkipType.PASS.getKey(), mergeVariable);
+        if (CollUtil.isEmpty(nextNodeList)) {
+            return new ArrayList<>();
+        }
         List<FlowNode> nextFlowNodes = BeanUtil.copyToList(nextNodeList, FlowNode.class);
         // 只获取中间节点
         nextFlowNodes = StreamUtils.filter(nextFlowNodes, node -> NodeType.BETWEEN.getKey().equals(node.getNodeType()));
@@ -647,7 +699,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
                             List<UserDTO> users;
                             if (CollUtil.isNotEmpty(first.getPermissionList())
                                     && CollUtil.isNotEmpty(users = flwTaskAssigneeService.fetchUsersByStorageIds(
-                                    StringUtils.joinComma(first.getPermissionList())))) {
+                                            StringUtils.joinComma(first.getPermissionList())))) {
                                 flowNode.setPermissionFlag(StreamUtils.join(users, e -> Convert.toStr(e.getUserId())));
                             }
                         });
@@ -712,13 +764,19 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public boolean taskOperation(TaskOperationBo bo, String taskOperation) {
+        TaskOperationEnum op = TaskOperationEnum.getByCode(taskOperation);
+        if (op == null) {
+            log.error("Invalid operation type:{} ", taskOperation);
+            throw new ServiceException("Invalid operation type " + taskOperation);
+        }
+
         FlowParams flowParams = FlowParams.build().message(bo.getMessage());
         if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) {
             flowParams.ignore(true);
         }
 
         // 根据操作类型构建 FlowParams
-        switch (taskOperation) {
+        switch (op) {
             case DELEGATE_TASK, TRANSFER_TASK -> {
                 ValidatorUtils.validate(bo, AddGroup.class);
                 flowParams.addHandlers(Collections.singletonList(bo.getUserId()));
@@ -731,47 +789,62 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
                 ValidatorUtils.validate(bo, EditGroup.class);
                 flowParams.reductionHandlers(bo.getUserIds());
             }
-            default -> {
-                log.error("Invalid operation type:{} ", taskOperation);
-                throw new ServiceException("Invalid operation type " + taskOperation);
-            }
         }
 
         Long taskId = bo.getTaskId();
         Task task = taskService.getById(taskId);
+        if (ObjectUtil.isNull(task)) {
+            throw new ServiceException("任务不存在!");
+        }
         FlowNode flowNode = getByNodeCode(task.getNodeCode(), task.getDefinitionId());
-        if (ADD_SIGNATURE.equals(taskOperation) || REDUCTION_SIGNATURE.equals(taskOperation)) {
-            if (flowNode.getNodeRatio().compareTo(BigDecimal.ZERO) == 0) {
-                throw new ServiceException(task.getNodeName() + "不是会签节点!");
+        if (ObjectUtil.isNull(flowNode)) {
+            throw new ServiceException("流程节点不存在");
+        }
+        if (op == TaskOperationEnum.ADD_SIGNATURE || op == TaskOperationEnum.REDUCTION_SIGNATURE) {
+            if (CooperateType.isOrSign(flowNode.getNodeRatio())) {
+                throw new ServiceException(task.getNodeName() + "不是会签或票签节点!");
             }
         }
+
         // 设置任务状态并执行对应的任务操作
-        switch (taskOperation) {
-            // 委派任务
+        boolean result = false;
+        switch (op) {
             case DELEGATE_TASK -> {
                 flowParams.hisStatus(TaskStatusEnum.DEPUTE.getStatus());
-                return taskService.depute(taskId, flowParams);
+                result = taskService.depute(taskId, flowParams);
             }
-            // 转办任务
             case TRANSFER_TASK -> {
                 flowParams.hisStatus(TaskStatusEnum.TRANSFER.getStatus());
-                return taskService.transfer(taskId, flowParams);
+                result = taskService.transfer(taskId, flowParams);
             }
-            // 加签,增加办理人
             case ADD_SIGNATURE -> {
                 flowParams.hisStatus(TaskStatusEnum.SIGN.getStatus());
-                return taskService.addSignature(taskId, flowParams);
+                result = taskService.addSignature(taskId, flowParams);
             }
-            // 减签,减少办理人
             case REDUCTION_SIGNATURE -> {
                 flowParams.hisStatus(TaskStatusEnum.SIGN_OFF.getStatus());
-                return taskService.reductionSignature(taskId, flowParams);
+                result = taskService.reductionSignature(taskId, flowParams);
+            }
+        }
+
+        // 操作执行成功后再发送消息
+        if (result && CollUtil.isNotEmpty(bo.getMessageType())) {
+            List<Long> userIdList = new ArrayList<>();
+            if (StrUtil.isNotBlank(bo.getUserId())) {
+                userIdList.add(Convert.toLong(bo.getUserId()));
+            }
+            if (CollUtil.isNotEmpty(bo.getUserIds())) {
+                userIdList.addAll(StreamUtils.toList(bo.getUserIds(), Convert::toLong));
             }
-            default -> {
-                log.error("Invalid operation type:{} ", taskOperation);
-                throw new ServiceException("Invalid operation type " + taskOperation);
+            if (CollUtil.isNotEmpty(userIdList)) {
+                flwCommonService.sendMessage(
+                        bo.getMessageType(),
+                        StringUtils.isNotBlank(bo.getMessage()) ? bo.getMessage() : "单据「" + op.getDesc() + "」通知",
+                        "单据「" + op.getDesc() + "」提醒",
+                        userService.selectListByIds(userIdList));
             }
         }
+        return result;
     }
 
     /**
@@ -786,22 +859,17 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
         if (CollUtil.isEmpty(taskIdList)) {
             return false;
         }
-        try {
-            List<FlowTask> flowTasks = this.selectByIdList(taskIdList);
-            // 批量删除现有任务的办理人记录
-            if (CollUtil.isNotEmpty(flowTasks)) {
-                FlowEngine.userService().deleteByTaskIds(StreamUtils.toList(flowTasks, FlowTask::getId));
-                List<User> userList = StreamUtils.toList(flowTasks, flowTask -> new FlowUser()
-                        .setType(TaskAssigneeType.APPROVER.getCode())
-                        .setProcessedBy(userId)
-                        .setAssociated(flowTask.getId()));
-                if (CollUtil.isNotEmpty(userList)) {
-                    FlowEngine.userService().saveBatch(userList);
-                }
+        List<FlowTask> flowTasks = this.selectByIdList(taskIdList);
+        // 批量删除现有任务的办理人记录
+        if (CollUtil.isNotEmpty(flowTasks)) {
+            FlowEngine.userService().deleteByTaskIds(StreamUtils.toList(flowTasks, FlowTask::getId));
+            List<User> userList = StreamUtils.toList(flowTasks, flowTask -> new FlowUser()
+                    .setType(TaskAssigneeType.APPROVER.getCode())
+                    .setProcessedBy(userId)
+                    .setAssociated(flowTask.getId()));
+            if (CollUtil.isNotEmpty(userList)) {
+                FlowEngine.userService().saveBatch(userList);
             }
-        } catch (Exception e) {
-            log.error(e.getMessage(), e);
-            throw new ServiceException(e.getMessage());
         }
         return true;
     }
@@ -841,21 +909,16 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
      */
     @Override
     public boolean urgeTask(FlowUrgeTaskBo bo) {
-        try {
-            if (CollUtil.isEmpty(bo.getTaskIdList())) {
-                return false;
-            }
-            List<UserDTO> userList = this.currentTaskAllUser(bo.getTaskIdList());
-            if (CollUtil.isEmpty(userList)) {
-                return false;
-            }
-            List<String> messageType = bo.getMessageType();
-            String message = bo.getMessage();
-            flwCommonService.sendMessage(messageType, message, "单据审批提醒", userList);
-        } catch (Exception e) {
-            log.error(e.getMessage(), e);
-            throw new ServiceException(e.getMessage());
+        if (CollUtil.isEmpty(bo.getTaskIdList())) {
+            return false;
+        }
+        List<UserDTO> userList = this.currentTaskAllUser(bo.getTaskIdList());
+        if (CollUtil.isEmpty(userList)) {
+            return false;
         }
+        List<String> messageType = bo.getMessageType();
+        String message = bo.getMessage();
+        flwCommonService.sendMessage(messageType, message, "单据审批提醒", userList);
         return true;
     }
 }

+ 14 - 6
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/TestLeaveServiceImpl.java

@@ -16,6 +16,7 @@ import com.vber.common.core.enums.BusinessStatusEnum;
 import com.vber.common.core.exception.ServiceException;
 import com.vber.common.core.service.WorkflowService;
 import com.vber.common.core.utils.MapstructUtils;
+import com.vber.common.core.utils.StreamUtils;
 import com.vber.common.core.utils.StringUtils;
 import com.vber.common.mybatis.core.domain.BaseEntity;
 import com.vber.common.mybatis.core.page.PageQuery;
@@ -46,6 +47,7 @@ import java.util.Map;
 @Service
 @Slf4j
 public class TestLeaveServiceImpl implements ITestLeaveService {
+
     private final TestLeaveMapper baseMapper;
     private final WorkflowService workflowService;
 
@@ -165,7 +167,7 @@ public class TestLeaveServiceImpl implements ITestLeaveService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Boolean deleteWithValidByIds(List<Long> ids) {
-        workflowService.deleteInstance(ids);
+        workflowService.deleteInstance(StreamUtils.toList(ids, Convert::toStr));
         return baseMapper.deleteByIds(ids) > 0;
     }
 
@@ -185,11 +187,14 @@ public class TestLeaveServiceImpl implements ITestLeaveService {
         Map<String, Object> params = processEvent.getParams();
         if (MapUtil.isNotEmpty(params)) {
             // 历史任务扩展(通常为附件)
-            // String hisTaskExt = Convert.toStr(params.get("hisTaskExt"));
-            // // 办理人
-            // String handler = Convert.toStr(params.get("handler"));
-            // // 办理意见
-            // String message = Convert.toStr(params.get("message"));
+            @SuppressWarnings("unused")
+            String hisTaskExt = Convert.toStr(params.get("hisTaskExt"));
+            // 办理人
+            @SuppressWarnings("unused")
+            String handler = Convert.toStr(params.get("handler"));
+            // 办理意见
+            @SuppressWarnings("unused")
+            String message = Convert.toStr(params.get("message"));
         }
         if (processEvent.getSubmit()) {
             if (StringUtils.isBlank(testLeave.getApplyCode())) {
@@ -197,7 +202,10 @@ public class TestLeaveServiceImpl implements ITestLeaveService {
                 testLeave.setApplyCode(businessCode);
             }
             testLeave.setStatus(BusinessStatusEnum.WAITING.getStatus());
+            log.info("申请人提交");
         }
+        String status = BusinessStatusEnum.findByStatus(processEvent.getStatus());
+        log.info("当前流程状态为{}", status);
         baseMapper.updateById(testLeave);
     }
 

+ 15 - 25
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/WorkflowServiceImpl.java

@@ -5,7 +5,6 @@ import cn.hutool.core.util.ObjectUtil;
 import com.vber.common.core.domain.dto.CompleteTaskDTO;
 import com.vber.common.core.domain.dto.StartProcessDTO;
 import com.vber.common.core.domain.dto.StartProcessReturnDTO;
-import com.vber.common.core.exception.ServiceException;
 import com.vber.common.core.service.WorkflowService;
 import com.vber.common.core.utils.StringUtils;
 import com.vber.workflow.common.ConditionalOnEnable;
@@ -46,7 +45,7 @@ public class WorkflowServiceImpl implements WorkflowService {
      * @return 结果
      */
     @Override
-    public boolean deleteInstance(List<Long> businessIds) {
+    public boolean deleteInstance(List<String> businessIds) {
         return flwInstanceService.deleteByBusinessIds(businessIds);
     }
 
@@ -161,28 +160,19 @@ public class WorkflowServiceImpl implements WorkflowService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public boolean startCompleteTask(StartProcessDTO startProcess) {
-        try {
-            StartProcessBo processBo = new StartProcessBo();
-            processBo.setBusinessId(startProcess.getBusinessId());
-            processBo.setFlowCode(startProcess.getFlowCode());
-            processBo.setVariables(startProcess.getVariables());
-            processBo.setHandler(startProcess.getHandler());
-            processBo.setBizExt(BeanUtil.toBean(startProcess.getBizExt(), FlowInstanceBizExt.class));
-
-            StartProcessReturnDTO result = flwTaskService.startWorkFlow(processBo);
-            CompleteTaskBo taskBo = new CompleteTaskBo();
-            taskBo.setTaskId(result.getTaskId());
-            taskBo.setMessageType(Collections.singletonList(MessageTypeEnum.SYSTEM_MESSAGE.getCode()));
-            taskBo.setVariables(startProcess.getVariables());
-            taskBo.setHandler(startProcess.getHandler());
-
-            boolean flag = flwTaskService.completeTask(taskBo);
-            if (!flag) {
-                throw new ServiceException("流程发起异常");
-            }
-            return true;
-        } catch (Exception e) {
-            throw new ServiceException(e.getMessage());
-        }
+        StartProcessBo processBo = new StartProcessBo();
+        processBo.setBusinessId(startProcess.getBusinessId());
+        processBo.setFlowCode(startProcess.getFlowCode());
+        processBo.setVariables(startProcess.getVariables());
+        processBo.setHandler(startProcess.getHandler());
+        processBo.setBizExt(BeanUtil.toBean(startProcess.getBizExt(), FlowInstanceBizExt.class));
+
+        StartProcessReturnDTO result = flwTaskService.startWorkFlow(processBo);
+        CompleteTaskBo taskBo = new CompleteTaskBo();
+        taskBo.setTaskId(result.getTaskId());
+        taskBo.setMessageType(Collections.singletonList(MessageTypeEnum.SYSTEM_MESSAGE.getCode()));
+        taskBo.setVariables(startProcess.getVariables());
+        taskBo.setHandler(startProcess.getHandler());
+        return flwTaskService.completeTask(taskBo);
     }
 }

+ 7 - 2
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/resources/mapper/workflow/FlwTaskMapper.xml

@@ -26,7 +26,10 @@
                               d.flow_code,
                               d.form_custom,
                               d.category,
-                              COALESCE(t.form_path, d.form_path) as form_path,
+                              COALESCE(
+                                  NULLIF(TRIM(t.form_path), ''),
+                                  NULLIF(TRIM(d.form_path), '')
+                              ) AS form_path,
                               d.version,
                               uu.processed_by,
                               uu.type,
@@ -80,7 +83,9 @@
               where a.del_flag = '0'
                 and b.del_flag = '0'
                 and c.del_flag = '0'
-                and a.node_type in ('1', '3', '4')) t ${ew.getCustomSqlSegment}
+                and a.node_type in ('1', '3', '4')
+                  and a.flow_status <![CDATA[ <> ]]> 'copy'
+              ) t ${ew.getCustomSqlSegment}
     </select>
 
     <select id="getTaskCopyByPage" resultMap="FlowTaskResult">

+ 95 - 8
UI/VAP_V3.VUE/src/components/editor/VbEditor.vue

@@ -1,14 +1,25 @@
 <script lang="ts" setup>
-import { QuillEditor } from "@vueup/vue-quill"
+import { QuillEditor, Quill } from "@vueup/vue-quill"
 import "@vueup/vue-quill/dist/vue-quill.snow.css"
 
-const props = defineProps<{
-	modelValue?: string
-	height?: number
-	minHeight?: number
-	readOnly?: boolean
-}>()
+const props = withDefaults(
+	defineProps<{
+		modelValue?: string
+		height?: number
+		minHeight?: number
+		readOnly?: boolean
+		fileSize?: number
+		type?: "url" | "base64"
+	}>(),
+	{
+		type: "url",
+		fileSize: 5
+	}
+)
 defineEmits<(e: "update:modelValue", v: string) => void>()
+const quillEditorRef = ref()
+const uploadRef = ref()
+
 const options = ref({
 	theme: "snow",
 	bounds: document.body,
@@ -26,11 +37,72 @@ const options = ref({
 			[{ align: [] }], // 对齐方式
 			["clean"], // 清除文本格式
 			["link", "image", "video"] // 链接、图片、视频
-		]
+		],
+		handlers: {
+			image: (value: boolean) => {
+				if (value) {
+					// 调用element图片上传
+					uploadRef.value.click()
+				} else {
+					Quill.format("image", true)
+				}
+			}
+		}
 	},
 	placeholder: "请输入内容",
 	readOnly: props.readOnly
 })
+const upload = reactive({
+	headers: {
+		Authorization: "Bearer " + getToken(),
+		ClientId: import.meta.env.VITE_APP_CLIENT_ID
+	},
+	url: import.meta.env.VITE_APP_BASE_API + "/resource/oss/upload"
+})
+
+// 图片上传成功返回图片地址
+const handleUploadSuccess = (res: any) => {
+	// 如果上传成功
+	if (res.code === 200) {
+		// 获取富文本实例
+		const quill = toRaw(quillEditorRef.value).getQuill()
+		// 获取光标位置
+		const length = quill.selection.savedRange.index
+		// 插入图片,res为服务器返回的图片链接地址
+		quill.insertEmbed(length, "image", res.data.url)
+		// 调整光标到最后
+		quill.setSelection(length + 1)
+	} else {
+		message.msgError("图片插入失败")
+	}
+	message.closeLoading()
+}
+
+// 图片上传前拦截
+const handleBeforeUpload = (file: any) => {
+	const type = ["image/jpeg", "image/jpg", "image/png", "image/svg"]
+	const isJPG = type.includes(file.type)
+	//检验文件格式
+	if (!isJPG) {
+		message.msgError(`图片格式错误!`)
+		return false
+	}
+	// 校检文件大小
+	if (props.fileSize) {
+		const isLt = file.size / 1024 / 1024 < props.fileSize
+		if (!isLt) {
+			message.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`)
+			return false
+		}
+	}
+	message.loading("正在上传文件,请稍候...")
+	return true
+}
+
+// 图片失败拦截
+const handleUploadError = (err: any) => {
+	message.msgError("上传文件失败")
+}
 
 const styles = computed(() => {
 	const style: any = {}
@@ -57,7 +129,22 @@ watch(
 
 <template>
 	<div class="editor">
+		<div>
+			<el-upload
+				v-if="type === 'url'"
+				:action="upload.url"
+				:before-upload="handleBeforeUpload"
+				:on-success="handleUploadSuccess"
+				:on-error="handleUploadError"
+				class="editor-img-uploader"
+				name="file"
+				:show-file-list="false"
+				:headers="upload.headers">
+				<i ref="uploadRef"></i>
+			</el-upload>
+		</div>
 		<quill-editor
+			ref="quillEditorRef"
 			v-model:content="content"
 			contentType="html"
 			@textChange="(e) => $emit('update:modelValue', content)"

+ 16 - 3
UI/VAP_V3.VUE/src/components/process/ProcessMeddle.vue

@@ -52,20 +52,24 @@ function handleOpen(taskId: string) {
 		buttonDisabled.value = false
 	})
 }
+
 function handleClose() {
 	modalRef.value.hide()
 }
 
+// 打开转办
 function handleOpenTransferTask() {
 	transferTaskRef.value.open()
 }
 
+// 转办
 function handleTransferTask(data: any) {
 	if (data && data.length > 0) {
 		const bo = {
 			userId: data[0].userId,
 			taskId: task.value.id,
-			message: ""
+			message: "",
+			messageType: ["1"]
 		}
 		buttonDisabled.value = true
 
@@ -87,6 +91,7 @@ function handleTransferTask(data: any) {
 		message.msgWarning("请选择转交人员")
 	}
 }
+
 const deleteUserColumns = [
 	{
 		title: "任务名称",
@@ -106,15 +111,20 @@ const deleteUserColumns = [
 		align: "center"
 	}
 ] as any[]
+
+// 打开加签
 function handleOpenMultiInstanceUser() {
 	multiInstanceUserRef.value.open()
 }
+
+// 加签
 function handleAddMultiInstanceUser(data: any) {
 	if (data && data.length > 0) {
 		const bo = {
 			userIds: data.map((e) => e.userId),
 			taskId: task.value.id,
-			message: ""
+			message: "",
+			messageType: ["1"]
 		}
 		buttonDisabled.value = true
 		message.confirm(`是否确认加签?`).then(() => {
@@ -134,6 +144,7 @@ function handleAddMultiInstanceUser(data: any) {
 	}
 }
 
+// 打开减签
 function handleDeleteTaskUser() {
 	apis.workflow.taskApi.currentTaskAllUser(task.value.id).then((res: any) => {
 		deleteUserList.value = res.data
@@ -145,12 +156,14 @@ function handleDeleteTaskUser() {
 		deleteUserModalRef.value.show()
 	})
 }
+// 减签
 function deleteMultiInstanceUser(row: any) {
 	if (row) {
 		const bo = {
 			userIds: [row.userId],
 			taskId: task.value.id,
-			message: ""
+			message: "",
+			messageType: ["1"]
 		}
 		buttonDisabled.value = true
 		message.confirm(`是否确认减签?`).then(() => {

+ 22 - 9
UI/VAP_V3.VUE/src/components/process/SubmitVerify.vue

@@ -112,10 +112,11 @@ function handlePopUser(userList) {
 	nickName.value[nodeCode.value] = nickNames.join(",")
 }
 
+// 打开抄送人选择
 function handleOpenUserSelectCopy() {
 	userSelectCopyRef.value.open()
 }
-
+// 抄送人选择关闭标签
 function handleCopyCloseTag(user: any) {
 	const userId = user.userId
 	// 使用split删除用户
@@ -125,12 +126,12 @@ function handleCopyCloseTag(user: any) {
 		selectCopyUserIds.value = selectCopyUserList.value.map((item: any) => item.userId).join(",")
 	}
 }
+
 //办理流程
 function handleCompleteTask() {
 	message.confirm("确认提交审批?", "确认提交").then(() => {
 		form.value.taskId = taskId.value
 		form.value.variables = props.taskVariables
-		let verify = false
 
 		if (buttonObj.value.pop && nextNodeList.value && nextNodeList.value.length > 0) {
 			let flowCopyList: any = []
@@ -160,12 +161,14 @@ function handleCompleteTask() {
 	})
 }
 
+// 加签
 function handleAddMultiInstanceUser(data) {
 	if (data && data.length > 0) {
 		const params = {
 			taskId: taskId.value,
 			userIds: data.map((item: any) => item.userId),
-			message: form.value.message
+			message: form.value.message,
+			messageType: ["1"]
 		}
 		message.confirm("是否确认加签?").then(() => {
 			buttonDisabled.value = true
@@ -184,11 +187,14 @@ function handleAddMultiInstanceUser(data) {
 		message.msgWarning("请选择加签人员")
 	}
 }
+
+// 减签
 function handleDeleteMultiInstanceUser(row: any) {
 	const params = {
 		userIds: [row.userId],
 		taskId: taskId.value,
-		message: form.value.message
+		message: form.value.message,
+		messageType: ["1"]
 	}
 	message.confirm("是否确认减签?").then(() => {
 		buttonDisabled.value = true
@@ -204,10 +210,12 @@ function handleDeleteMultiInstanceUser(row: any) {
 			})
 	})
 }
+// 终止流程
 function handleTerminationTask() {
 	const params = {
 		taskId: taskId.value,
-		comment: form.value.message
+		comment: form.value.message,
+		messageType: ["1"]
 	}
 	message.confirm("是否确认终止流程?", "确认终止").then(() => {
 		buttonDisabled.value = true
@@ -223,6 +231,7 @@ function handleTerminationTask() {
 			})
 	})
 }
+// 驳回流程
 function handleBackProcessOpen() {
 	backForm.value = {}
 	backForm.value.messageType = ["1"]
@@ -275,13 +284,15 @@ function onUserSelectCopy(data: any) {
 function handleOpenTransferTask() {
 	transferTaskRef.value.open()
 }
+// 转办
 function onTransferTask(data: any) {
 	if (data && data.length > 0) {
 		message.confirm("是否确认转办?", "确认转办").then(() => {
 			const params = {
 				taskId: taskId.value,
 				userId: data[0].userId,
-				message: form.value.message
+				message: form.value.message,
+				messageType: ["1"]
 			}
 			buttonDisabled.value = true
 			apis.workflow.taskApi
@@ -303,13 +314,15 @@ function onTransferTask(data: any) {
 function handleOpenDelegateTask() {
 	delegateTaskRef.value.open()
 }
+// 委托
 function onDelegateTask(data: any) {
 	if (data && data.length > 0) {
 		message.confirm("是否确认委托?", "确认委托").then(() => {
 			const params = {
 				taskId: taskId.value,
 				userId: data[0].userId,
-				message: form.value.message
+				message: form.value.message,
+				messageType: ["1"]
 			}
 			buttonDisabled.value = true
 			apis.workflow.taskApi
@@ -385,7 +398,7 @@ defineExpose({
 						<el-checkbox label="3" name="type">短信</el-checkbox>
 					</el-checkbox-group>
 				</el-form-item>
-				<el-form-item label="附件">
+				<el-form-item label="附件" v-if="buttonObj.file">
 					<VbUpload
 						v-model="form.fileId"
 						:fileType="['doc', 'xls', 'ppt', 'txt', 'pdf', 'xlsx', 'docx', 'zip']"
@@ -403,7 +416,7 @@ defineExpose({
 						closable
 						style="margin: 2px"
 						@close="handleCopyCloseTag(user)">
-						{{ user.userName }}
+						{{ user.nickName }}
 					</el-tag>
 				</el-form-item>
 				<el-form-item

+ 16 - 4
UI/VAP_V3.VUE/src/components/split-panel/VbSplitPanel.vue

@@ -1,7 +1,8 @@
 <script setup lang="ts">
 const props = withDefaults(
 	defineProps<{
-		mode?: "h" | "v" | "horizontal" | "vertical" // 拖拽方向
+		mode?: "h" | "v" | "horizontal" | "vertical" // 布局方向
+		isDrag?: boolean // 是否可以拖拽
 		size?: number | string // 初始尺寸,支持像素或百分比(如 '50%')
 		minSize?: number | string // 最小尺寸,支持像素或百分比(如 '10%')
 		maxSize?: number | string // 最大尺寸,支持像素或百分比(如 '80%')
@@ -17,6 +18,7 @@ const props = withDefaults(
 	}>(),
 	{
 		mode: "h",
+		isDrag: true,
 		size: 200,
 		minSize: 100,
 		maxSize: 2000,
@@ -111,9 +113,16 @@ const containerStyle = computed(() => {
 	return {
 		...(props.panelStyle || {}),
 		"--split-handler-bg": props.handlerBg || "#e5e7eb",
-		"--split-handler-hover-bg": props.handlerHoverBg || "#409eff",
+		"--split-handler-hover-bg": props.isDrag
+			? props.handlerHoverBg || "#409eff"
+			: "var(--split-handler-bg)",
 		"--split-handler-width": (props.handlerWidth || 6) + "px",
 		"--split-handler-margin": (props.handlerMargin || 2) + "px",
+		"--split-handler-cursor": props.isDrag
+			? realMode.value === "horizontal"
+				? "col-resize"
+				: "row-resize"
+			: "default",
 		[sizeProp]: finalSize,
 		[crossProp]: "100%",
 		display: "flex",
@@ -148,6 +157,7 @@ const secondPanelStyle = computed(() => {
 
 // 拖拽逻辑
 const startDrag = (e) => {
+	if (!props.isDrag) return
 	e.preventDefault()
 	isDragging.value = true
 	const startPos = realMode.value === "horizontal" ? e.clientX : e.clientY
@@ -227,6 +237,7 @@ watch(
 	--split-handler-hover-bg: #409eff;
 	--split-handler-width: 6px;
 	--split-handler-margin: 2px;
+	--split-handler-cursor: row-resize;
 
 	&.horizontal {
 		flex-direction: row;
@@ -243,16 +254,17 @@ watch(
 		transition: background 0.2s;
 		z-index: 10;
 		flex-shrink: 0;
+
 		&.h-handler {
 			width: var(--split-handler-width);
 			height: 100%;
-			cursor: col-resize;
+			cursor: var(--split-handler-cursor);
 			margin: 0 var(--split-handler-margin);
 		}
 		&.v-handler {
 			height: var(--split-handler-width);
 			width: 100%;
-			cursor: row-resize;
+			cursor: var(--split-handler-cursor);
 			margin: var(--split-handler-margin) 0;
 		}
 		&:hover {

+ 109 - 0
UI/VAP_V3.VUE/src/core/plugins/echarts.ts

@@ -0,0 +1,109 @@
+// 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
+import * as echarts from "echarts/core"
+// 类型,组件后缀都为 charts
+import {
+	LineChart, // 折线图
+	BarChart, // 柱状图
+	PieChart, // 饼图
+	ScatterChart, // 散点图
+	GaugeChart, // 仪表盘
+	HeatmapChart // 热力图
+} from "echarts/charts"
+
+// 引入标题,提示框,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component
+import {
+	GridComponent, // 网格布局
+	TooltipComponent, // 提示框
+	TitleComponent, // 标题
+	LegendComponent, // 图例
+	DataZoomComponent, // 数据缩放
+	VisualMapComponent, // 视觉映射
+	MarkLineComponent, // 标记线
+	MarkPointComponent, // 标记点
+	GraphicComponent, // 图形元素
+	DatasetComponent, // 数据集
+	TransformComponent, // 数据转换
+	PolarComponent // 极坐标
+} from "echarts/components"
+
+// 标签自动布局、全局过渡动画等特性
+import { LabelLayout, UniversalTransition } from "echarts/features"
+
+// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
+import { CanvasRenderer } from "echarts/renderers"
+
+// 注册必须的组件
+echarts.use([
+	// 渲染器
+	CanvasRenderer,
+	LineChart,
+	BarChart,
+	PieChart,
+	ScatterChart,
+	GaugeChart,
+	HeatmapChart,
+	GridComponent,
+	TooltipComponent,
+	TitleComponent,
+	LegendComponent,
+	DataZoomComponent,
+	VisualMapComponent,
+	MarkLineComponent,
+	MarkPointComponent,
+	GraphicComponent,
+	DatasetComponent,
+	TransformComponent,
+	PolarComponent
+])
+
+const buildEchartsOptions = (options: any, themeColors?: string[], color?: string) => {
+	if (!themeColors) themeColors = []
+	if (!color) color = "#666666"
+	const _options: any = {
+		color: themeColors,
+		grid: {
+			left: "3%",
+			right: "4%",
+			bottom: "3%",
+			top: "20%",
+			containLabel: true
+		},
+		xAxis: {
+			type: "category",
+			boundaryGap: ["20%", "20%"],
+			nameTextStyle: {
+				color: color
+			},
+
+			axisTick: {
+				lineStyle: {
+					color: color
+				}
+			},
+			data: []
+		},
+		yAxis: {
+			type: "value",
+			nameTextStyle: {
+				color: color
+			},
+			splitNumber: 3,
+			splitLine: {
+				show: true,
+				lineStyle: {
+					type: [5, 10],
+					color: color
+				}
+			},
+			axisTick: {
+				lineStyle: {
+					color: color
+				}
+			}
+		},
+		series: []
+	}
+	return { ..._options, ...options }
+}
+
+export { echarts, buildEchartsOptions }

+ 5 - 1
UI/VAP_V3.VUE/src/core/services/RequestService.ts

@@ -42,7 +42,11 @@ export const Rs = axios.create({
 	// axios中请求配置有baseURL选项,表示请求URL公共部分
 	baseURL: baseURL,
 	// 超时
-	timeout: 50000
+	timeout: 50000,
+	transitional: {
+		// 超时错误更明确
+		clarifyTimeoutError: true
+	}
 })
 
 // request拦截器

+ 174 - 0
UI/VAP_V3.VUE/src/core/use/use-echarts.ts

@@ -0,0 +1,174 @@
+import { echarts } from "@@/plugins/echarts"
+import { useDebounceFn, useResizeObserver } from "@vueuse/core"
+import type { EChartsCoreOption, EChartsInitOpts, SetOptionOpts } from "echarts"
+
+type NullType<T> = T | null
+
+interface ConfigProps {
+	/**
+	 * init函数基本配置
+	 * @see https://echarts.apache.org/zh/api.html#echarts.init
+	 */
+	echartsInitOpts?: EChartsInitOpts
+	/**
+	 * 是否显示loading
+	 * @default false
+	 */
+	loading?: boolean
+	/**
+	 * Loading配置项
+	 * @see https://echarts.apache.org/zh/api.html#echartsInstance.showLoading
+	 */
+	loadingOption?: object
+	/**
+	 * 是否开启过渡动画
+	 * @default true
+	 */
+	animation?: boolean
+	/**
+	 * 过渡动画持续时间(ms)
+	 * @default 300
+	 */
+	animationDuration?: number
+	/**
+	 * 是否自动调整大小
+	 * @default true
+	 */
+	autoResize?: boolean
+	/**
+	 * 防抖时间(ms)
+	 * @default 300
+	 */
+	resizeDebounceWait?: number
+	/**
+	 * 最大防抖时间(ms)
+	 * @default 500
+	 */
+	maxResizeDebounceWait?: number
+}
+
+const DEFAULT_CONFIG: ConfigProps = {
+	loading: true,
+	loadingOption: {
+		text: "加载中...",
+		color: "#c23531",
+		textColor: "#000",
+		maskColor: "rgba(255, 255, 255, 0.8)",
+		zlevel: 0,
+		// 字体大小。从 `v4.8.0` 开始支持。
+		fontSize: 12,
+		// 是否显示旋转动画(spinner)。从 `v4.8.0` 开始支持。
+		showSpinner: true,
+		// 旋转动画(spinner)的半径。从 `v4.8.0` 开始支持。
+		spinnerRadius: 10,
+		// 旋转动画(spinner)的线宽。从 `v4.8.0` 开始支持。
+		lineWidth: 3,
+		// 字体粗细。从 `v5.0.1` 开始支持。
+		fontWeight: "normal",
+		// 字体风格。从 `v5.0.1` 开始支持。
+		fontStyle: "normal",
+		// 字体系列。从 `v5.0.1` 开始支持。
+		fontFamily: "sans-serif"
+	},
+	animation: true,
+	animationDuration: 300,
+	autoResize: true,
+	resizeDebounceWait: 300,
+	maxResizeDebounceWait: 500
+}
+
+export const useEcharts = (
+	dom: Ref<HTMLDivElement | HTMLCanvasElement | null>,
+	config?: ConfigProps
+) => {
+	const {
+		echartsInitOpts,
+		loading,
+		animation,
+		animationDuration,
+		autoResize,
+		resizeDebounceWait,
+		maxResizeDebounceWait
+	} = Object.assign({}, DEFAULT_CONFIG, config)
+	const loadingOption = Object.assign({}, DEFAULT_CONFIG.loadingOption, config?.loadingOption)
+	/** 图表实例 */
+	let chartInstance: NullType<echarts.ECharts> = null
+
+	/** Loading 状态控制 */
+	const toggleLoading = (show: boolean) => {
+		if (!chartInstance) return
+		if (!loading) return
+		show ? chartInstance.showLoading("default", loadingOption) : chartInstance.hideLoading()
+	}
+	/** 图表初始化 */
+	const initChart = () => {
+		if (!dom.value || echarts.getInstanceByDom(dom.value)) return
+		chartInstance = echarts.init(dom.value, null, echartsInitOpts)
+		toggleLoading(true)
+	}
+
+	/** 获取图表实例 */
+	const getChartInstance = () => chartInstance
+
+	// SetOptionOpts:
+	// (option: Object, opts?: {
+	//     notMerge?: boolean;// 是否不跟之前设置的 `option` 进行合并
+	//     replaceMerge?: string | string[];// 用户可以在这里指定一个或多个组件
+	//     lazyUpdate?: boolean;// 在设置完 `option` 后是否不立即更新图表
+	// })
+	/**
+	 * 图表渲染
+	 * @param options 图表数据集
+	 * @param opts 图表配置项
+	 */
+	const renderChart = (options: EChartsCoreOption, opts: SetOptionOpts = { notMerge: true }) => {
+		if (!chartInstance) {
+			initChart()
+		}
+		const finalOptions = { ...options, backgroundColor: "transparent" }
+		// console.log("renderChart", finalOptions)
+		chartInstance.setOption(finalOptions, opts)
+		toggleLoading(false)
+	}
+
+	/** 图表销毁 */
+	const destroyChart = () => {
+		if (!chartInstance) return
+		chartInstance.dispose()
+		chartInstance = null
+	}
+
+	/** 调整图表尺寸 */
+	const resize = () => {
+		if (!chartInstance) return
+		chartInstance.resize({
+			animation: {
+				duration: animation ? animationDuration : 0
+			}
+		})
+	}
+	/** 防抖处理的resize */
+	const resizeDebounce = useDebounceFn(resize, resizeDebounceWait, {
+		maxWait: maxResizeDebounceWait
+	})
+
+	onMounted(() => {
+		initChart()
+		if (autoResize) {
+			useResizeObserver(dom, resizeDebounce)
+		}
+	})
+
+	// 组件实例被卸载之后
+	onUnmounted(() => {
+		destroyChart()
+	})
+
+	return {
+		echarts,
+		chartInstance,
+		getChartInstance,
+		renderChart,
+		toggleLoading
+	}
+}

+ 30 - 25
UI/VAP_V3.VUE/src/views/system/dict/_data.vue

@@ -2,13 +2,10 @@
 import apis from "@a"
 import message from "@@/utils/message"
 import appStore from "~/src/stores"
-const props = defineProps<{
-	id: string
-}>()
 
-const { sys_normal_disable, sys_yes_no } = useDict("sys_normal_disable", "sys_yes_no")
+const { sys_yes_no } = useDict("sys_yes_no")
 
-const defaultDictType = ref("")
+const dictTypeData = ref<any>()
 
 const tableRef = ref()
 const modalRef = ref()
@@ -18,10 +15,9 @@ const opts = reactive({
 		{ field: "dictLabel", name: "字典标签", visible: true },
 		{ field: "dictValue", name: "字典键值", visible: true },
 		{ field: "dictSort", name: "字典排序", visible: false, width: 85 },
-		{ field: "createTime", name: "创建时间", visible: true, width: 185 },
 		{ field: "remark", name: "备注", visible: true },
-		{ field: "actions", name: `操作`, width: 120 }
-	],
+		{ field: "actions", name: `操作`, width: 100 }
+	] as any,
 	queryParams: {
 		dictType: undefined,
 		dictLabel: "",
@@ -63,7 +59,7 @@ const opts = reactive({
 			key: "handleExport",
 			show: false
 		}
-	],
+	] as any,
 	handleFuns: {},
 	customBtns: [],
 	tableListFun: apis.system.dictApi.listData,
@@ -127,24 +123,29 @@ function submitForm() {
 	}
 }
 
-watch(
-	() => props.id,
-	(val) => {
-		apis.system.dictApi.getType(val).then((res: any) => {
-			emptyFormData.value.dictType = res.data.dictType
-			queryParams.value = {
-				dictType: res.data.dictType,
-				status: "0",
-				dictLabel: ""
-			}
-			defaultDictType.value = res.data.dictType
-		})
+function queryDictData(id: string) {
+	if (dictTypeData.value?.dictId && id == dictTypeData.value?.dictId) {
+		dictTypeData.value = null
+		return
 	}
-)
-defineExpose({ defaultDictType })
+	apis.system.dictApi.getType(id).then((res: any) => {
+		emptyFormData.value.dictType = res.data.dictType
+		tableRef.value?.query({
+			dictType: res.data.dictType,
+			status: "0",
+			dictLabel: ""
+		})
+		dictTypeData.value = res.data
+	})
+}
+
+defineExpose({ queryDictData, dictTypeData })
 </script>
 <template>
-	<div class="app-container">
+	<div v-show="dictTypeData">
+		<h2 class="mt-5 text-primary text-center">
+			{{ dictTypeData?.dictName || "" }} {{ dictTypeData?.dictType || "" }}
+		</h2>
 		<VbDataTable
 			ref="tableRef"
 			:handle-perm="opts.permission"
@@ -160,7 +161,9 @@ defineExpose({ defaultDictType })
 			:reset-form-fun="opts.resetForm"
 			v-model:form-data="form"
 			v-model:query-params="queryParams"
+			:auto-search="false"
 			:check-multiple="true"
+			:show-search-bar="false"
 			:show-right-column-btn="false"
 			:show-right-search-btn="false"
 			:reset-search-form-fun="resetQuery"
@@ -206,7 +209,9 @@ defineExpose({ defaultDictType })
 		<VbModal
 			v-model:modal="modalRef"
 			modal-dialog-class="max-width:850px;width:850px;"
-			:title="opts.modalTitle"
+			:title="
+				opts.modalTitle + '(' + dictTypeData?.dictName + ':' + dictTypeData?.dictType + ')'
+			"
 			:form-data="form"
 			:form-rules="opts.formRules"
 			:label-width="opts.labelWidth"

+ 221 - 0
UI/VAP_V3.VUE/src/views/system/dict/_type.vue

@@ -0,0 +1,221 @@
+<script setup lang="ts" name="Dict">
+import apis from "@a"
+import message from "@@/utils/message"
+import appStore from "@s"
+
+const emits = defineEmits<{
+	(e: "query", id: string): void
+}>()
+const tableRef = ref()
+const modalRef = ref()
+const isEdit = ref(false)
+
+const opts = reactive({
+	columns: [
+		{ field: "dictId", name: "字典编号", width: 100, isSort: true, visible: false },
+		{ field: "dictName", name: "字典名称", visible: true, width: 180 },
+		{ field: "dictType", name: "字典类型", visible: true, width: 150 },
+		{ field: "remark", name: "备注", visible: true, width: "auto" },
+		{ field: "actions", name: `操作`, width: 120 }
+	] as any,
+	queryParams: {
+		dictName: undefined,
+		dictType: undefined,
+		status: undefined,
+		dateRange: []
+	},
+	searchFormItems: [
+		{
+			field: "dictName",
+			label: "字典名称",
+			required: false,
+			class: "w-100",
+			placeholder: "请输入字典名称",
+			listeners: {
+				keyup: (e: KeyboardEvent) => {
+					if (e.code == "Enter") {
+						handleQuery()
+					}
+				}
+			}
+		},
+		{
+			field: "dictType",
+			label: "字典类型",
+			required: false,
+			class: "w-100",
+			placeholder: "请输入字典类型",
+			listeners: {
+				keyup: (e: KeyboardEvent) => {
+					if (e.code == "Enter") {
+						handleQuery()
+					}
+				}
+			}
+		}
+	],
+	permission: "system:dict",
+	handleFuns: {
+		handleUpdate: () => {
+			const row = tableRef.value.getSelected()
+			handleUpdate(row)
+		},
+		handleRefreshCache
+	},
+	customBtns: [],
+	tableListFun: apis.system.dictApi.listType,
+	getEntityFun: apis.system.dictApi.getType,
+	deleteEntityFun: apis.system.dictApi.delType,
+	exportUrl: apis.system.dictApi.typeExportUrl,
+	exportName: "DICT",
+	modalTitle: "字典类型",
+	resetForm: () => {
+		isEdit.value = false
+		form.value = emptyFormData.value
+	},
+	formRules: {
+		dictName: [{ required: true, message: "字典名称不能为空", trigger: "blur" }],
+		dictType: [{ required: true, message: "字典类型不能为空", trigger: "blur" }]
+	},
+	labelWidth: "80px",
+	emptyFormData: {
+		dictId: undefined,
+		dictName: undefined,
+		dictType: undefined,
+		status: "0",
+		remark: undefined
+	}
+})
+const { queryParams, emptyFormData } = toRefs(opts)
+const form = ref(emptyFormData.value)
+
+/** 修改按钮操作 */
+function handleUpdate(row: any) {
+	tableRef.value.defaultHandleFuns.handleUpdate("", row)
+	isEdit.value = true
+}
+/** 删除按钮操作 */
+function handleDelete(rows: any[]) {
+	tableRef.value.defaultHandleFuns.handleDelete("", rows)
+}
+
+/** 搜索按钮操作 */
+function handleQuery(query?: any) {
+	query = query || tableRef.value?.getQueryParams() || queryParams.value
+	addDateRange(query, query.dateRange)
+	tableRef.value?.query(query)
+}
+
+/** 重置按钮操作 */
+function resetQuery(query?: any) {
+	query = query || tableRef.value?.getQueryParams() || queryParams.value
+	query.dateRange = []
+	addDateRange(query, query.dateRange)
+}
+/** 提交按钮 */
+function submitForm() {
+	if (form.value.dictId != null) {
+		apis.system.dictApi.updateType(form.value).then(() => {
+			message.msgSuccess("修改成功")
+			handleQuery()
+		})
+	} else {
+		apis.system.dictApi.addType(form.value).then(() => {
+			message.msgSuccess("新增成功")
+			handleQuery()
+		})
+	}
+}
+
+/** 刷新缓存按钮操作 */
+function handleRefreshCache() {
+	appStore.dictStore.cleanDict().then(() => {
+		message.msgSuccess("刷新成功")
+	})
+}
+function handleRowClick(row: any) {
+	emits("query", row.dictId)
+}
+function onQuerySuccess() {
+	const row = tableRef.value?.getFirstRowData()
+	if (row) {
+		tableRef.value?.setSelected(row, true)
+		handleRowClick(row)
+	}
+}
+</script>
+<template>
+	<div>
+		<VbDataTable
+			ref="tableRef"
+			:handle-perm="opts.permission"
+			:handle-funs="opts.handleFuns"
+			:search-form-items="opts.searchFormItems as any"
+			:init-search="true"
+			:columns="opts.columns"
+			:custom-btns="opts.customBtns"
+			:remote-fun="opts.tableListFun"
+			:get-entity-fun="opts.getEntityFun"
+			:delete-entity-fun="opts.deleteEntityFun"
+			:export-url="opts.exportUrl"
+			:export-name="opts.exportName"
+			:modal="modalRef"
+			:reset-form-fun="opts.resetForm"
+			v-model:form-data="form"
+			v-model:query-params="queryParams"
+			:check-multiple="false"
+			:has-checkbox="true"
+			:row-click="handleRowClick"
+			:reset-search-form-fun="resetQuery"
+			@query-success="onQuerySuccess"
+			:custom-search-fun="handleQuery">
+			<template #status="{ row }">
+				<DictTag type="sys_normal_disable" :value="row.status"></DictTag>
+			</template>
+			<template #actions="{ row }">
+				<vb-tooltip content="修改" placement="top">
+					<el-button
+						link
+						type="primary"
+						@click="handleUpdate(row)"
+						v-hasPermission="'system:dict:edit'">
+						<template #icon>
+							<VbIcon icon-name="notepad-edit" icon-type="duotone" class="fs-3"></VbIcon>
+						</template>
+					</el-button>
+				</vb-tooltip>
+				<vb-tooltip content="删除" placement="top" :delay="500">
+					<el-button
+						link
+						type="primary"
+						@click="handleDelete([row])"
+						v-hasPermission="'system:dict:remove'">
+						<template #icon>
+							<VbIcon icon-name="trash-square" icon-type="duotone" class="fs-3"></VbIcon>
+						</template>
+					</el-button>
+				</vb-tooltip>
+			</template>
+		</VbDataTable>
+		<VbModal
+			v-model:modal="modalRef"
+			:title="opts.modalTitle"
+			:form-data="form"
+			:form-rules="opts.formRules"
+			:label-width="opts.labelWidth"
+			@confirm="submitForm"
+			append-to-body>
+			<template #form>
+				<el-form-item label="字典名称" prop="dictName">
+					<el-input v-model="form.dictName" placeholder="请输入字典名称" />
+				</el-form-item>
+				<el-form-item label="字典类型" prop="dictType">
+					<el-input v-model="form.dictType" placeholder="请输入字典类型" :disabled="isEdit" />
+				</el-form-item>
+				<el-form-item label="备注" prop="remark">
+					<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
+				</el-form-item>
+			</template>
+		</VbModal>
+	</div>
+</template>

+ 27 - 225
UI/VAP_V3.VUE/src/views/system/dict/index.vue

@@ -1,238 +1,40 @@
-<script setup lang="ts" name="Dict">
-import apis from "@a"
-import message from "@@/utils/message"
-import appStore from "@s"
-import Data from "./_data.vue"
+<script setup lang="ts">
+import DataView from "./_data.vue"
+import TypeView from "./_type.vue"
 
-const { sys_normal_disable } = useDict("sys_normal_disable")
+// const typeRef = ref()
+const dataRef = ref()
+const curSize = ref("60%")
 
-const tableRef = ref()
-const modalRef = ref()
-const isEdit = ref(false)
-const opts = reactive({
-	columns: [
-		{ field: "dictId", name: "字典编号", width: 100, isSort: true, visible: false },
-		{ field: "dictName", name: "字典名称", visible: true, width: 200 },
-		{ field: "dictType", name: "字典类型", visible: true, width: 200 },
-		{ field: "remark", name: "备注", visible: true, width: "auto" },
-		{ field: "actions", name: `操作`, width: 150 }
-	],
-	queryParams: {
-		dictName: undefined,
-		dictType: undefined,
-		status: undefined,
-		dateRange: []
-	},
-	searchFormItems: [
-		{
-			field: "dictName",
-			label: "字典名称",
-			required: false,
-			class: "w-100",
-			placeholder: "请输入字典名称",
-			listeners: {
-				keyup: (e: KeyboardEvent) => {
-					if (e.code == "Enter") {
-						handleQuery()
-					}
-				}
-			}
-		},
-		{
-			field: "dictType",
-			label: "字典类型",
-			required: false,
-			class: "w-100",
-			placeholder: "请输入字典类型",
-			listeners: {
-				keyup: (e: KeyboardEvent) => {
-					if (e.code == "Enter") {
-						handleQuery()
-					}
-				}
-			}
-		}
-	],
-	permission: "system:dict",
-	handleFuns: {
-		handleUpdate: () => {
-			const row = tableRef.value.getSelected()
-			handleUpdate(row)
-		},
-		handleRefreshCache
-	},
-	customBtns: [],
-	tableListFun: apis.system.dictApi.listType,
-	getEntityFun: apis.system.dictApi.getType,
-	deleteEntityFun: apis.system.dictApi.delType,
-	exportUrl: apis.system.dictApi.typeExportUrl,
-	exportName: "DICT",
-	modalTitle: "字典类型",
-	resetForm: () => {
-		isEdit.value = false
-		form.value = emptyFormData.value
-	},
-	formRules: {
-		dictName: [{ required: true, message: "字典名称不能为空", trigger: "blur" }],
-		dictType: [{ required: true, message: "字典类型不能为空", trigger: "blur" }]
-	},
-	labelWidth: "80px",
-	emptyFormData: {
-		dictId: undefined,
-		dictName: undefined,
-		dictType: undefined,
-		status: "0",
-		remark: undefined
-	}
+const size = computed(() => {
+	return dataRef.value?.dictTypeData ? curSize.value : "100%"
 })
-const { queryParams, emptyFormData } = toRefs(opts)
-const form = ref(emptyFormData.value)
 
-/** 修改按钮操作 */
-function handleUpdate(row: any) {
-	tableRef.value.defaultHandleFuns.handleUpdate("", row)
-	isEdit.value = true
-}
-/** 删除按钮操作 */
-function handleDelete(rows: any[]) {
-	tableRef.value.defaultHandleFuns.handleDelete("", rows)
-}
-
-/** 搜索按钮操作 */
-function handleQuery(query?: any) {
-	query = query || tableRef.value?.getQueryParams() || queryParams.value
-	addDateRange(query, query.dateRange)
-	tableRef.value?.query(query)
-}
-
-/** 重置按钮操作 */
-function resetQuery(query?: any) {
-	query = query || tableRef.value?.getQueryParams() || queryParams.value
-	query.dateRange = []
-	addDateRange(query, query.dateRange)
-}
-/** 提交按钮 */
-function submitForm() {
-	if (form.value.dictId != null) {
-		apis.system.dictApi.updateType(form.value).then(() => {
-			message.msgSuccess("修改成功")
-			handleQuery()
-		})
-	} else {
-		apis.system.dictApi.addType(form.value).then(() => {
-			message.msgSuccess("新增成功")
-			handleQuery()
-		})
-	}
-}
-
-/** 刷新缓存按钮操作 */
-function handleRefreshCache() {
-	appStore.dictStore.cleanDict().then(() => {
-		message.msgSuccess("刷新成功")
-	})
+function onSizeChange(size) {
+	curSize.value = size
 }
 
-const viewModalRef = ref()
-const dataViewRef = ref()
-const viewDictId = ref()
-const viewModalTitle = computed(() => {
-	return "字典数据:" + dataViewRef.value?.defaultDictType
-})
-function handleView(row: any) {
-	viewDictId.value = row.dictId
-	viewModalRef.value.show()
+function onQueryData(id) {
+	dataRef.value.queryDictData(id)
 }
 </script>
+
 <template>
-	<div>
-		<VbDataTable
-			ref="tableRef"
-			:handle-perm="opts.permission"
-			:handle-funs="opts.handleFuns"
-			:search-form-items="opts.searchFormItems as any"
-			:columns="opts.columns"
-			:custom-btns="opts.customBtns"
-			:remote-fun="opts.tableListFun"
-			:get-entity-fun="opts.getEntityFun"
-			:delete-entity-fun="opts.deleteEntityFun"
-			:export-url="opts.exportUrl"
-			:export-name="opts.exportName"
-			:modal="modalRef"
-			:reset-form-fun="opts.resetForm"
-			v-model:form-data="form"
-			v-model:query-params="queryParams"
-			:check-multiple="true"
-			:reset-search-form-fun="resetQuery"
-			:custom-search-fun="handleQuery">
-			<template #status="{ row }">
-				<DictTag type="sys_normal_disable" :value="row.status"></DictTag>
-			</template>
-			<template #actions="{ row }">
-				<vb-tooltip content="修改" placement="top">
-					<el-button
-						link
-						type="primary"
-						@click="handleUpdate(row)"
-						v-hasPermission="'system:dict:edit'">
-						<template #icon>
-							<VbIcon icon-name="notepad-edit" icon-type="duotone" class="fs-3"></VbIcon>
-						</template>
-					</el-button>
-				</vb-tooltip>
-				<vb-tooltip content="删除" placement="top" :delay="500">
-					<el-button
-						link
-						type="primary"
-						@click="handleDelete([row])"
-						v-hasPermission="'system:dict:remove'">
-						<template #icon>
-							<VbIcon icon-name="trash-square" icon-type="duotone" class="fs-3"></VbIcon>
-						</template>
-					</el-button>
-				</vb-tooltip>
-				<vb-tooltip content="详情" placement="top">
-					<el-button
-						link
-						type="primary"
-						@click="handleView(row)"
-						v-hasPermission="'system:dict:query'">
-						<template #icon>
-							<VbIcon icon-name="eye" icon-type="duotone" class="fs-3"></VbIcon>
-						</template>
-					</el-button>
-				</vb-tooltip>
+	<div class="app-container">
+		<VbSplitPanel :size="size" min-size="30%" :is-drag="false" @drag-complete="onSizeChange">
+			<template #first>
+				<TypeView @query="onQueryData" ref="typeRef" />
 			</template>
-		</VbDataTable>
-		<VbModal
-			v-model:modal="modalRef"
-			:title="opts.modalTitle"
-			:form-data="form"
-			:form-rules="opts.formRules"
-			:label-width="opts.labelWidth"
-			@confirm="submitForm"
-			append-to-body>
-			<template #form>
-				<el-form-item label="字典名称" prop="dictName">
-					<el-input v-model="form.dictName" placeholder="请输入字典名称" />
-				</el-form-item>
-				<el-form-item label="字典类型" prop="dictType">
-					<el-input v-model="form.dictType" placeholder="请输入字典类型" :disabled="isEdit" />
-				</el-form-item>
-				<el-form-item label="备注" prop="remark">
-					<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
-				</el-form-item>
+			<template #second>
+				<DataView ref="dataRef" />
 			</template>
-		</VbModal>
-		<VbModal
-			v-model:modal="viewModalRef"
-			:title="viewModalTitle"
-			modal-dialog-style="width:1000px;max-width:1000px;"
-			modal-body-class="pt-0"
-			append-to-body>
-			<template #body>
-				<Data ref="dataViewRef" :id="viewDictId"></Data>
-			</template>
-		</VbModal>
+		</VbSplitPanel>
 	</div>
 </template>
+
+<style scoped>
+.app-container {
+	width: 100%;
+	height: calc(100vh - 135px);
+}
+</style>

+ 10 - 2
UI/VAP_V3.VUE/src/views/workflow/processInstance/index.vue

@@ -293,6 +293,9 @@ function handleVariable(row: any) {
 		variableModalRef.value.show()
 	})
 }
+const hasVariableEditPermission = computed(() => {
+	return checkPermission("workflow:instance:variableEdit")
+})
 function onVariableSubmit() {
 	form.value.instanceId = instanceId.value
 	message.confirm("是否确认保存变量?", "确认保存").then(() => {
@@ -389,6 +392,7 @@ onMounted(init)
 							</el-button>
 						</vb-tooltip>
 						<el-popover
+							v-hasPermission="'workflow:instance:invalid'"
 							v-if="queryParams.runStatus == '1'"
 							:ref="`popoverRef_${row.id}`"
 							trigger="click"
@@ -418,14 +422,17 @@ onMounted(init)
 								</vb-tooltip> -->
 							</template>
 						</el-popover>
-						<vb-tooltip content="删除" placement="top">
+						<vb-tooltip content="删除" placement="top" v-hasPermission="'workflow:instance:remove'">
 							<el-button link type="danger" @click="handleDelete([row])">
 								<template #icon>
 									<VbIcon icon-name="trash-square" icon-type="duotone" class="fs-3"></VbIcon>
 								</template>
 							</el-button>
 						</vb-tooltip>
-						<vb-tooltip content="变量" placement="top">
+						<vb-tooltip
+							content="变量"
+							placement="top"
+							v-hasPermission="'workflow:instance:variableQuery'">
 							<el-button link type="primary" @click="handleVariable(row)">
 								<template #icon>
 									<VbIcon icon-name="file" icon-type="duotone" class="fs-3"></VbIcon>
@@ -455,6 +462,7 @@ onMounted(init)
 			v-model:modal="variableModalRef"
 			title="流程变量"
 			:save-auto-close="false"
+			:confirm-btn="hasVariableEditPermission"
 			@confirm="onVariableSubmit"
 			append-to-body>
 			<template #body>