Bladeren bron

!735 2.4.2:工作流的更新
Merge pull request !735 from 芋道源码/feature/bpm

芋道源码 10 maanden geleden
bovenliggende
commit
d2b993311f
51 gewijzigde bestanden met toevoegingen van 2922 en 1486 verwijderingen
  1. 55 32
      README.md
  2. 2 1
      build/vite/optimize.ts
  3. 6 0
      src/api/bpm/definition/index.ts
  4. 6 1
      src/api/bpm/processInstance/index.ts
  5. 1 0
      src/assets/svgs/bpm/child-process.svg
  6. 1 0
      src/assets/svgs/bpm/transactor.svg
  7. 42 5
      src/components/SimpleProcessDesignerV2/src/NodeHandler.vue
  8. 14 3
      src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue
  9. 83 16
      src/components/SimpleProcessDesignerV2/src/SimpleProcessModel.vue
  10. 0 1
      src/components/SimpleProcessDesignerV2/src/SimpleProcessViewer.vue
  11. 146 7
      src/components/SimpleProcessDesignerV2/src/consts.ts
  12. 71 3
      src/components/SimpleProcessDesignerV2/src/node.ts
  13. 610 0
      src/components/SimpleProcessDesignerV2/src/nodes-config/ChildProcessNodeConfig.vue
  14. 69 75
      src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue
  15. 23 5
      src/components/SimpleProcessDesignerV2/src/nodes-config/CopyTaskNodeConfig.vue
  16. 22 4
      src/components/SimpleProcessDesignerV2/src/nodes-config/StartUserNodeConfig.vue
  17. 356 166
      src/components/SimpleProcessDesignerV2/src/nodes-config/TriggerNodeConfig.vue
  18. 198 123
      src/components/SimpleProcessDesignerV2/src/nodes-config/UserTaskNodeConfig.vue
  19. 308 0
      src/components/SimpleProcessDesignerV2/src/nodes-config/components/ConditionDialog.vue
  20. 7 3
      src/components/SimpleProcessDesignerV2/src/nodes-config/components/HttpRequestParamSetting.vue
  21. 127 0
      src/components/SimpleProcessDesignerV2/src/nodes-config/components/HttpRequestSetting.vue
  22. 106 0
      src/components/SimpleProcessDesignerV2/src/nodes/ChildProcessNode.vue
  23. 9 2
      src/components/SimpleProcessDesignerV2/src/nodes/UserTaskNode.vue
  24. BIN
      src/components/SimpleProcessDesignerV2/theme/iconfont.ttf
  25. BIN
      src/components/SimpleProcessDesignerV2/theme/iconfont.woff
  26. BIN
      src/components/SimpleProcessDesignerV2/theme/iconfont.woff2
  27. 34 2
      src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss
  28. 16 6
      src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue
  29. 52 18
      src/components/bpmnProcessDesigner/package/penal/custom-config/components/UserTaskCustomConfig.vue
  30. 1 26
      src/router/modules/remaining.ts
  31. 7 7
      src/utils/index.ts
  32. 9 9
      src/views/bpm/model/CategoryDraggableModel.vue
  33. 73 42
      src/views/bpm/definition/index.vue
  34. 77 0
      src/views/bpm/model/form/ExtraSettings.vue
  35. 6 5
      src/views/bpm/model/form/FormDesign.vue
  36. 1 1
      src/views/bpm/model/form/ProcessDesign.vue
  37. 20 3
      src/views/bpm/model/editor/index.vue
  38. 56 39
      src/views/bpm/model/form/index.vue
  39. 42 9
      src/views/bpm/processInstance/create/ProcessDefinitionDetail.vue
  40. 4 4
      src/views/bpm/processInstance/create/index.vue
  41. 0 267
      src/views/bpm/processInstance/create/index_old.vue
  42. 108 20
      src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue
  43. 6 5
      src/views/bpm/processInstance/detail/ProcessInstanceSimpleViewer.vue
  44. 12 2
      src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue
  45. 2 1
      src/views/bpm/processInstance/detail/index.vue
  46. 58 59
      src/views/bpm/processInstance/index.vue
  47. 33 16
      src/views/bpm/task/done/index.vue
  48. 43 27
      src/views/bpm/task/todo/index.vue
  49. 0 151
      src/views/knowledge/dataset-form/form-step1.vue
  50. 0 168
      src/views/knowledge/dataset-form/form-step2.vue
  51. 0 152
      src/views/knowledge/dataset.vue

+ 55 - 32
README.md

@@ -117,54 +117,77 @@
 
 ### 工作流程
 
-|    | 功能    | 描述                                      |
-|----|-------|-----------------------------------------|
-| 🚀 | 流程模型  | 配置工作流的流程模型,支持 BPMN 和仿钉钉/飞书设计器           |
-| 🚀 | 流程表单  | 拖动表单元素生成相应的工作流表单,覆盖 Element UI 所有的表单组件  |
-| 🚀 | 用户分组  | 自定义用户分组,可用于工作流的审批分组                     |
-| 🚀 | 我的流程  | 查看我发起的工作流程,支持新建、取消流程等操作,高亮流程图、审批时间线     |
-| 🚀 | 待办任务  | 查看自己【未】审批的工作任务,支持通过、不通过、转派、委派、退回、加减签等操作 |
-| 🚀 | 已办任务  | 查看自己【已】审批的工作任务,支持流程预测,展示未来审批人信息         |
-| 🚀 | OA 请假 | 作为业务自定义接入工作流的使用示例,只需创建请求对应的工作流程,即可进行审批  |
-
 ![功能图](/.image/common/bpm-feature.png)
 
+基于 Flowable 构建,可支持信创(国产)数据库,满足中国特色流程操作:
+
 | BPMN 设计器                     | 钉钉/飞书设计器                       |
 |------------------------------|--------------------------------|
 | ![](/.image/工作流设计器-bpmn.jpg) | ![](/.image/工作流设计器-simple.jpg) |
 
+> 历经头部企业生产验证,工作流引擎须标配仿钉钉/飞书 + BPMN 双设计器!!!
+>
+> 前者支持轻量配置简单流程,后者实现复杂场景深度编排
+
+| 功能列表       | 功能描述                                                                                | 是否完成 |
+|------------|-------------------------------------------------------------------------------------|------|
+| SIMPLE 设计器 | 仿钉钉/飞书设计器,支持拖拽搭建表单流程,10 分钟快速完成审批流程配置                                                | ✅    |
+| BPMN 设计器   | 基于 BPMN 标准开发,适配复杂业务场景,满足多层级审批及流程自动化需求                                               | ✅    |
+| 会签         | 同一个审批节点设置多个人(如 A、B、C 三人,三人会同时收到待办任务),需全部同意之后,审批才可到下一审批节点                            | ✅    |
+| 或签         | 同一个审批节点设置多个人,任意一个人处理后,就能进入下一个节点                                                     | ✅    |
+| 依次审批       | (顺序会签)同一个审批节点设置多个人(如 A、B、C 三人),三人按顺序依次收到待办,即 A 先审批,A 提交后 B 才能审批,需全部同意之后,审批才可到下一审批节点 | ✅    |
+| 抄送         | 将审批结果通知给抄送人,同一个审批默认排重,不重复抄送给同一人                                                     | ✅    |
+| 驳回         | (退回)将审批重置发送给某节点,重新审批。可驳回至发起人、上一节点、任意节点                                              | ✅    |
+| 转办         | A 转给其 B 审批,B 审批后,进入下一节点                                                             | ✅    |
+| 委派         | A 转给其 B 审批,B 审批后,转给 A,A 继续审批后进入下一节点                                                 | ✅    |
+| 加签         | 允许当前审批人根据需要,自行增加当前节点的审批人,支持向前、向后加签                                                  | ✅    |
+| 减签         | (取消加签)在当前审批人操作之前,减少审批人                                                              | ✅    |
+| 撤销         | (取消流程)流程发起人,可以对流程进行撤销处理                                                             | ✅    |
+| 终止         | 系统管理员,在任意节点终止流程实例                                                                   | ✅    |
+| 表单权限       | 支持拖拉拽配置表单,每个审批节点可配置只读、编辑、隐藏权限                                                       | ✅    |
+| 超时审批       | 配置超时审批时间,超时后自动触发审批通过、不通过、驳回等操作                                                      | ✅    |
+| 自动提醒       | 配置提醒时间,到达时间后自动触发短信、邮箱、站内信等通知提醒,支持自定义重复提醒频次                                          | ✅    |
+| 父子流程       | 主流程设置子流程节点,子流程节点会自动触发子流程。子流程结束后,主流程才会执行(继续往下下执行),支持同步子流程、异步子流程                      | ✅    |
+| 条件分支       | (排它分支)用于在流程中实现决策,即根据条件选择一个分支执行                                                      | ✅    |
+| 并行分支       | 允许将流程分成多条分支,不进行条件判断,所有分支都会执行                                                        | ✅    |
+| 包容分支       | (条件分支 + 并行分支的结合体)允许基于条件选择多条分支执行,但如果没有任何一个分支满足条件,则可以选择默认分支                           | ✅    |
+| 路由分支       | 根据条件选择一个分支执行(重定向到指定配置节点),也可以选择默认分支执行(继续往下执行)                                        | ✅    |
+| 触发节点       | 执行到该节点,触发 HTTP 请求、HTTP 回调、更新数据、删除数据等                                                | ✅    |
+| 延迟节点       | 执行到该节点,审批等待一段时间再执行,支持固定时长、固定日期等                                                     | ✅    |
+| 拓展设置       | 流程前置/后置通知,节点(任务)前置、后置通知,流程报表,自动审批去重,自定流程编号、标题、摘要,流程报表等                              | ✅    |
+
 ### 支付系统
 
 |     | 功能   | 描述                        |
 |-----|------|---------------------------|
-| 🚀  | 商户信息 | 管理商户信息,支持 Saas 场景下的多商户功能  |
 | 🚀  | 应用信息 | 配置商户的应用信息,对接支付宝、微信等多个支付渠道 |
 | 🚀  | 支付订单 | 查看用户发起的支付宝、微信等的【支付】订单     |
 | 🚀  | 退款订单 | 查看用户发起的支付宝、微信等的【退款】订单     |
-
-ps:核心功能已经实现,正在对接微信小程序中...
+| 🚀  | 回调通知 | 查看支付回调业务的【支付】【退款】的通知结果    |
+| 🚀  | 接入示例 | 提供接入支付系统的【支付】【退款】的功能实战    |
 
 ### 基础设施
 
-|    | 功能       | 描述                                           |
-|----|----------|----------------------------------------------|
-| 🚀 | 代码生成     | 前后端代码的生成(Java、Vue、SQL、单元测试),支持 CRUD 下载       |
-| 🚀 | 系统接口     | 基于 Swagger 自动生成相关的 RESTful API 接口文档          |
-| 🚀 | 数据库文档    | 基于 Screw 自动生成数据库文档,支持导出 Word、HTML、MD 格式      |
-|    | 表单构建     | 拖动表单元素生成相应的 HTML 代码,支持导出 JSON、Vue 文件         |
-| 🚀 | 配置管理     | 对系统动态配置常用参数,支持 SpringBoot 加载                 |
-| ⭐️ | 定时任务     | 在线(添加、修改、删除)任务调度包含执行结果日志                     |
-| 🚀 | 文件服务     | 支持将文件存储到 S3(MinIO、阿里云、腾讯云、七牛云)、本地、FTP、数据库等   |
-| 🚀 | API 日志   | 包括 RESTful API 访问日志、异常日志两部分,方便排查 API 相关的问题   |
-|    | MySQL 监控 | 监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈              |
-|    | Redis 监控 | 监控 Redis 数据库的使用情况,使用的 Redis Key 管理           |
-| 🚀 | 消息队列     | 基于 Redis 实现消息队列,Stream 提供集群消费,Pub/Sub 提供广播消费 |
-| 🚀 | Java 监控  | 基于 Spring Boot Admin 实现 Java 应用的监控           |
-| 🚀 | 链路追踪     | 接入 SkyWalking 组件,实现链路追踪                      |
-| 🚀 | 日志中心     | 接入 SkyWalking 组件,实现日志中心                      |
-| 🚀 | 服务保障     | 基于 Redis 实现分布式锁、幂等、限流功能,满足高并发场景              |
-| 🚀 | 日志服务     | 轻量级日志中心,查看远程服务器的日志                           |
-| 🚀 | 单元测试     | 基于 JUnit + Mockito 实现单元测试,保证功能的正确性、代码的质量等    |
+|     | 功能        | 描述                                           |
+|-----|-----------|----------------------------------------------|
+| 🚀  | 代码生成      | 前后端代码的生成(Java、Vue、SQL、单元测试),支持 CRUD 下载       |
+| 🚀  | 系统接口      | 基于 Swagger 自动生成相关的 RESTful API 接口文档          |
+| 🚀  | 数据库文档     | 基于 Screw 自动生成数据库文档,支持导出 Word、HTML、MD 格式      |
+|     | 表单构建      | 拖动表单元素生成相应的 HTML 代码,支持导出 JSON、Vue 文件         |
+| 🚀  | 配置管理      | 对系统动态配置常用参数,支持 SpringBoot 加载                 |
+| ⭐️  | 定时任务      | 在线(添加、修改、删除)任务调度包含执行结果日志                     |
+| 🚀  | 文件服务      | 支持将文件存储到 S3(MinIO、阿里云、腾讯云、七牛云)、本地、FTP、数据库等   | 
+| 🚀  | WebSocket | 提供 WebSocket 接入示例,支持一对一、一对多发送方式              | 
+| 🚀  | API 日志    | 包括 RESTful API 访问日志、异常日志两部分,方便排查 API 相关的问题   |
+|     | MySQL 监控  | 监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈              |
+|     | Redis 监控  | 监控 Redis 数据库的使用情况,使用的 Redis Key 管理           |
+| 🚀  | 消息队列      | 基于 Redis 实现消息队列,Stream 提供集群消费,Pub/Sub 提供广播消费 |
+| 🚀  | Java 监控   | 基于 Spring Boot Admin 实现 Java 应用的监控           |
+| 🚀  | 链路追踪      | 接入 SkyWalking 组件,实现链路追踪                      |
+| 🚀  | 日志中心      | 接入 SkyWalking 组件,实现日志中心                      |
+| 🚀  | 服务保障      | 基于 Redis 实现分布式锁、幂等、限流功能,满足高并发场景              |
+| 🚀  | 日志服务      | 轻量级日志中心,查看远程服务器的日志                           |
+| 🚀  | 单元测试      | 基于 JUnit + Mockito 实现单元测试,保证功能的正确性、代码的质量等    |
 
 ![功能图](/.image/common/infra-feature.png)
 

+ 2 - 1
build/vite/optimize.ts

@@ -114,7 +114,8 @@ const include = [
   'element-plus/es/components/segmented/style/css',
   '@element-plus/icons-vue',
   'element-plus/es/components/footer/style/css',
-  'element-plus/es/components/empty/style/css'
+  'element-plus/es/components/empty/style/css',
+  'element-plus/es/components/mention/style/css'
 ]
 
 const exclude = ['@iconify/json']

+ 6 - 0
src/api/bpm/definition/index.ts

@@ -20,3 +20,9 @@ export const getProcessDefinitionList = async (params) => {
     params
   })
 }
+
+export const getSimpleProcessDefinitionList = async () => {
+  return await request.get({
+    url: '/bpm/process-definition/simple-list'
+  })
+}

+ 6 - 1
src/api/bpm/processInstance/index.ts

@@ -90,7 +90,12 @@ export const getProcessInstanceCopyPage = async (params: any) => {
 
 // 获取审批详情
 export const getApprovalDetail = async (params: any) => {
-  return await request.get({ url: 'bpm/process-instance/get-approval-detail', params })
+  return await request.get({ url: '/bpm/process-instance/get-approval-detail', params })
+}
+
+// 获取下一个执行的流程节点
+export const getNextApprovalNodes = async (params: any) => {
+  return await request.get({ url: '/bpm/process-instance/get-next-approval-nodes', params })
 }
 
 // 获取表单字段权限

+ 1 - 0
src/assets/svgs/bpm/child-process.svg

@@ -0,0 +1 @@
+<svg t="1740116949537" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1153" width="200" height="200"><path d="M440.32 296.96h283.30496v145.92h66.56V230.4H440.32V17.92H17.92v424.96H440.32V296.96zM373.76 376.32H84.48v-291.84H373.76v291.84zM586.24 588.8v143.36512H298.66496V586.24h-66.56v212.48512H586.24V1013.76H1008.64v-424.96h-422.4z m355.84 358.4h-289.28v-291.84H942.08v291.84z" p-id="1154" fill="#ffffff"></path></svg>

File diff suppressed because it is too large
+ 1 - 0
src/assets/svgs/bpm/transactor.svg


+ 42 - 5
src/components/SimpleProcessDesignerV2/src/NodeHandler.vue

@@ -15,6 +15,12 @@
             </div>
             <div class="handler-item-text">审批人</div>
           </div>
+          <div class="handler-item" @click="addNode(NodeType.TRANSACTOR_NODE)">
+            <div class="transactor handler-item-icon">
+              <span class="iconfont icon-transactor icon-size"></span>
+            </div>
+            <div class="handler-item-text">办理人</div>
+          </div>
           <div class="handler-item" @click="addNode(NodeType.COPY_TASK_NODE)">
             <div class="handler-item-icon copy">
               <span class="iconfont icon-size icon-copy"></span>
@@ -57,7 +63,13 @@
             </div>
             <div class="handler-item-text">触发器</div>
           </div>
-        </div> 
+          <div class="handler-item" @click="addNode(NodeType.CHILD_PROCESS_NODE)">
+            <div class="handler-item-icon child-process">
+              <span class="iconfont icon-size icon-child-process"></span>
+            </div>
+            <div class="handler-item-text">子流程</div>
+          </div>
+        </div>
         <template #reference>
           <div class="add-icon"><Icon icon="ep:plus" /></div>
         </template>
@@ -78,7 +90,7 @@ import {
   SimpleFlowNode,
   DEFAULT_CONDITION_GROUP_VALUE
 } from './consts'
-import {generateUUID} from '@/utils'
+import { generateUUID } from '@/utils'
 
 defineOptions({
   name: 'NodeHandler'
@@ -114,13 +126,13 @@ const addNode = (type: number) => {
   }
 
   popoverShow.value = false
-  if (type === NodeType.USER_TASK_NODE) {
+  if (type === NodeType.USER_TASK_NODE || type === NodeType.TRANSACTOR_NODE) {
     const id = 'Activity_' + generateUUID()
     const data: SimpleFlowNode = {
       id: id,
-      name: NODE_DEFAULT_NAME.get(NodeType.USER_TASK_NODE) as string,
+      name: NODE_DEFAULT_NAME.get(type) as string,
       showText: '',
-      type: NodeType.USER_TASK_NODE,
+      type: type,
       approveMethod: ApproveMethodType.SEQUENTIAL_APPROVE,
       // 超时处理
       rejectHandler: {
@@ -277,6 +289,31 @@ const addNode = (type: number) => {
     }
     emits('update:childNode', data)
   }
+  if (type === NodeType.CHILD_PROCESS_NODE) {
+    const data: SimpleFlowNode = {
+      id: 'Activity_' + generateUUID(),
+      name: NODE_DEFAULT_NAME.get(NodeType.CHILD_PROCESS_NODE) as string,
+      showText: '',
+      type: NodeType.CHILD_PROCESS_NODE,
+      childNode: props.childNode,
+      childProcessSetting: {
+        calledProcessDefinitionKey: '',
+        calledProcessDefinitionName: '',
+        async: false,
+        skipStartUserNode: false,
+        startUserSetting: {
+          type: 1
+        },
+        timeoutSetting: {
+          enable: false
+        },
+        multiInstanceSetting: {
+          enable: false
+        }
+      }
+    }
+    emits('update:childNode', data)
+  }
 }
 </script>
 

+ 14 - 3
src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue

@@ -6,7 +6,11 @@
   />
   <!-- 审批节点 -->
   <UserTaskNode
-    v-if="currentNode && currentNode.type === NodeType.USER_TASK_NODE"
+    v-if="
+      currentNode &&
+      (currentNode.type === NodeType.USER_TASK_NODE ||
+        currentNode.type === NodeType.TRANSACTOR_NODE)
+    "
     :flow-node="currentNode"
     @update:flow-node="handleModelValueUpdate"
     @find:parent-node="findFromParentNode"
@@ -50,12 +54,18 @@
     :flow-node="currentNode"
     @update:flow-node="handleModelValueUpdate"
   />
-   <!-- 触发器节点 -->
-   <TriggerNode
+  <!-- 触发器节点 -->
+  <TriggerNode
     v-if="currentNode && currentNode.type === NodeType.TRIGGER_NODE"
     :flow-node="currentNode"
     @update:flow-node="handleModelValueUpdate"
   />
+  <!-- 子流程节点 -->
+  <ChildProcessNode
+    v-if="currentNode && currentNode.type === NodeType.CHILD_PROCESS_NODE"
+    :flow-node="currentNode"
+    @update:flow-node="handleModelValueUpdate"
+  />
   <!-- 递归显示孩子节点  -->
   <ProcessNodeTree
     v-if="currentNode && currentNode.childNode"
@@ -81,6 +91,7 @@ import InclusiveNode from './nodes/InclusiveNode.vue'
 import DelayTimerNode from './nodes/DelayTimerNode.vue'
 import RouterNode from './nodes/RouterNode.vue'
 import TriggerNode from './nodes/TriggerNode.vue'
+import ChildProcessNode from './nodes/ChildProcessNode.vue'
 import { SimpleFlowNode, NodeType } from './consts'
 import { useWatchNode } from './node'
 defineOptions({

+ 83 - 16
src/components/SimpleProcessDesignerV2/src/SimpleProcessModel.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="simple-process-model-container position-relative">
-    <div class="position-absolute top-0px right-0px bg-#fff">
+    <div class="position-absolute top-0px right-0px bg-#fff z-index-button-group">
       <el-row type="flex" justify="end">
         <el-button-group key="scale-control" size="default">
           <el-button v-if="!readonly" size="default" @click="exportJson">
@@ -23,10 +23,19 @@
           <el-button size="default" :plain="true" :icon="ZoomOut" @click="zoomOut()" />
           <el-button size="default" class="w-80px"> {{ scaleValue }}% </el-button>
           <el-button size="default" :plain="true" :icon="ZoomIn" @click="zoomIn()" />
+          <el-button size="default" @click="resetPosition">重置</el-button>
         </el-button-group>
       </el-row>
     </div>
-    <div class="simple-process-model" :style="`transform: scale(${scaleValue / 100});`">
+    <div
+      class="simple-process-model"
+      :style="`transform: translate(${currentX}px, ${currentY}px) scale(${scaleValue / 100});`"
+      @mousedown="startDrag"
+      @mousemove="onDrag"
+      @mouseup="stopDrag"
+      @mouseleave="stopDrag"
+      @mouseenter="setGrabCursor"
+    >
       <ProcessNodeTree v-if="processNodeTree" v-model:flow-node="processNodeTree" />
     </div>
   </div>
@@ -76,11 +85,51 @@ const emits = defineEmits<{
 const processNodeTree = useWatchNode(props)
 
 provide('readonly', props.readonly)
+
+// TODO 可优化:拖拽有点卡顿
+/** 拖拽、放大缩小等操作 */
 let scaleValue = ref(100)
 const MAX_SCALE_VALUE = 200
 const MIN_SCALE_VALUE = 50
+const isDragging = ref(false)
+const startX = ref(0)
+const startY = ref(0)
+const currentX = ref(0)
+const currentY = ref(0)
+const initialX = ref(0)
+const initialY = ref(0)
+
+const setGrabCursor = () => {
+  document.body.style.cursor = 'grab'
+}
+
+const resetCursor = () => {
+  document.body.style.cursor = 'default'
+}
+
+const startDrag = (e: MouseEvent) => {
+  isDragging.value = true
+  startX.value = e.clientX - currentX.value
+  startY.value = e.clientY - currentY.value
+  setGrabCursor() // 设置小手光标
+}
+
+const onDrag = (e: MouseEvent) => {
+  if (!isDragging.value) return
+  e.preventDefault() // 禁用文本选择
+
+  // 使用 requestAnimationFrame 优化性能
+  requestAnimationFrame(() => {
+    currentX.value = e.clientX - startX.value
+    currentY.value = e.clientY - startY.value
+  })
+}
+
+const stopDrag = () => {
+  isDragging.value = false
+  resetCursor() // 重置光标
+}
 
-// 放大
 const zoomIn = () => {
   if (scaleValue.value == MAX_SCALE_VALUE) {
     return
@@ -88,7 +137,6 @@ const zoomIn = () => {
   scaleValue.value += 10
 }
 
-// 缩小
 const zoomOut = () => {
   if (scaleValue.value == MIN_SCALE_VALUE) {
     return
@@ -100,20 +148,15 @@ const processReZoom = () => {
   scaleValue.value = 100
 }
 
+const resetPosition = () => {
+  currentX.value = initialX.value
+  currentY.value = initialY.value
+}
+
+/** 校验节点设置 */
 const errorDialogVisible = ref(false)
 let errorNodes: SimpleFlowNode[] = []
 
-const saveSimpleFlowModel = async () => {
-  errorNodes = []
-  validateNode(processNodeTree.value, errorNodes)
-  if (errorNodes.length > 0) {
-    errorDialogVisible.value = true
-    return
-  }
-  emits('save', processNodeTree.value)
-}
-
-// 校验节点设置。 暂时以 showText 为空 未节点错误配置
 const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNode[]) => {
   if (node) {
     const { type, showText, conditionNodes } = node
@@ -193,6 +236,30 @@ const importLocalFile = () => {
     }
   }
 }
+
+// 在组件初始化时记录初始位置
+onMounted(() => {
+  initialX.value = currentX.value
+  initialY.value = currentY.value
+})
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+.simple-process-model-container {
+  width: 100%;
+  height: 100%;
+  position: relative;
+  overflow: hidden;
+  user-select: none; // 禁用文本选择
+}
+
+.simple-process-model {
+  position: relative; // 确保相对定位
+  min-width: 100%; // 确保宽度为100%
+  min-height: 100%; // 确保高度为100%
+}
+
+.z-index-button-group {
+  z-index: 10;
+}
+</style>

+ 0 - 1
src/components/SimpleProcessDesignerV2/src/SimpleProcessViewer.vue

@@ -45,4 +45,3 @@ watch(
 provide('tasks', approveTasks)
 provide('processInstance', currentProcessInstance)
 </script>
-p

+ 146 - 7
src/components/SimpleProcessDesignerV2/src/consts.ts

@@ -24,6 +24,11 @@ export enum NodeType {
   COPY_TASK_NODE = 12,
 
   /**
+   * 办理人节点
+   */
+  TRANSACTOR_NODE = 13,
+
+  /**
    * 延迟器节点
    */
   DELAY_TIMER_NODE = 14,
@@ -34,6 +39,11 @@ export enum NodeType {
   TRIGGER_NODE = 15,
 
   /**
+   * 子流程节点
+   */
+  CHILD_PROCESS_NODE = 20,
+
+  /**
    * 条件节点
    */
   CONDITION_NODE = 50,
@@ -123,6 +133,8 @@ export interface SimpleFlowNode {
   reasonRequire?: boolean
   // 触发器设置
   triggerSetting?: TriggerSetting
+  // 子流程
+  childProcessSetting?: ChildProcessSetting
 }
 // 候选人策略枚举 ( 用于审批节点。抄送节点 )
 export enum CandidateStrategy {
@@ -151,6 +163,10 @@ export enum CandidateStrategy {
    */
   USER = 30,
   /**
+   * 审批人自选
+   */
+  APPROVE_USER_SELECT = 34,
+  /**
    * 发起人自选
    */
   START_USER_SELECT = 35,
@@ -506,6 +522,8 @@ NODE_DEFAULT_TEXT.set(NodeType.START_USER_NODE, '请设置发起人')
 NODE_DEFAULT_TEXT.set(NodeType.DELAY_TIMER_NODE, '请设置延迟器')
 NODE_DEFAULT_TEXT.set(NodeType.ROUTER_BRANCH_NODE, '请设置路由节点')
 NODE_DEFAULT_TEXT.set(NodeType.TRIGGER_NODE, '请设置触发器')
+NODE_DEFAULT_TEXT.set(NodeType.TRANSACTOR_NODE, '请设置办理人')
+NODE_DEFAULT_TEXT.set(NodeType.CHILD_PROCESS_NODE, '请设置子流程')
 
 export const NODE_DEFAULT_NAME = new Map<number, string>()
 NODE_DEFAULT_NAME.set(NodeType.USER_TASK_NODE, '审批人')
@@ -515,15 +533,20 @@ NODE_DEFAULT_NAME.set(NodeType.START_USER_NODE, '发起人')
 NODE_DEFAULT_NAME.set(NodeType.DELAY_TIMER_NODE, '延迟器')
 NODE_DEFAULT_NAME.set(NodeType.ROUTER_BRANCH_NODE, '路由分支')
 NODE_DEFAULT_NAME.set(NodeType.TRIGGER_NODE, '触发器')
+NODE_DEFAULT_NAME.set(NodeType.TRANSACTOR_NODE, '办理人')
+NODE_DEFAULT_NAME.set(NodeType.CHILD_PROCESS_NODE, '子流程')
 
 // 候选人策略。暂时不从字典中取。 后续可能调整。控制显示顺序
 export const CANDIDATE_STRATEGY: DictDataVO[] = [
   { label: '指定成员', value: CandidateStrategy.USER },
   { label: '指定角色', value: CandidateStrategy.ROLE },
+  { label: '指定岗位', value: CandidateStrategy.POST },
   { label: '部门成员', value: CandidateStrategy.DEPT_MEMBER },
   { label: '部门负责人', value: CandidateStrategy.DEPT_LEADER },
   { label: '连续多级部门负责人', value: CandidateStrategy.MULTI_LEVEL_DEPT_LEADER },
+  { label: '指定岗位', value: CandidateStrategy.MULTI_LEVEL_DEPT_LEADER },
   { label: '发起人自选', value: CandidateStrategy.START_USER_SELECT },
+  { label: '审批人自选', value: CandidateStrategy.APPROVE_USER_SELECT },
   { label: '发起人本人', value: CandidateStrategy.START_USER },
   { label: '发起人部门负责人', value: CandidateStrategy.START_USER_DEPT_LEADER },
   { label: '发起人连续部门负责人', value: CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER },
@@ -627,6 +650,16 @@ export const DEFAULT_BUTTON_SETTING: ButtonSetting[] = [
   { id: OperationButtonType.RETURN, displayName: '退回', enable: true }
 ]
 
+// 办理人默认的按钮权限设置
+export const TRANSACTOR_DEFAULT_BUTTON_SETTING: ButtonSetting[] = [
+  { id: OperationButtonType.APPROVE, displayName: '办理', enable: true },
+  { id: OperationButtonType.REJECT, displayName: '拒绝', enable: false },
+  { id: OperationButtonType.TRANSFER, displayName: '转办', enable: false },
+  { id: OperationButtonType.DELEGATE, displayName: '委派', enable: false },
+  { id: OperationButtonType.ADD_SIGN, displayName: '加签', enable: false },
+  { id: OperationButtonType.RETURN, displayName: '退回', enable: false }
+]
+
 // 发起人的按钮权限。暂时定死,不可以编辑
 export const START_USER_BUTTON_SETTING: ButtonSetting[] = [
   { id: OperationButtonType.APPROVE, displayName: '提交', enable: true },
@@ -717,7 +750,7 @@ export type RouterSetting = {
 export type TriggerSetting = {
   type: TriggerTypeEnum
   httpRequestSetting?: HttpRequestTriggerSetting
-  normalFormSetting?: NormalFormTriggerSetting
+  formSettings?: FormTriggerSetting[]
 }
 
 /**
@@ -729,9 +762,17 @@ export enum TriggerTypeEnum {
    */
   HTTP_REQUEST = 1,
   /**
-   * 更新流程表单触发器
+   * 接收 HTTP 回调请求触发器
    */
-  UPDATE_NORMAL_FORM = 2 // TODO @jason:FORM_UPDATE?
+  HTTP_CALLBACK = 2,
+  /**
+   * 表单数据更新触发器
+   */
+  FORM_UPDATE = 10,
+  /**
+   * 表单数据删除触发器
+   */
+  FORM_DELETE = 11
 }
 
 /**
@@ -751,12 +792,110 @@ export type HttpRequestTriggerSetting = {
 /**
  * 流程表单触发器配置结构定义
  */
-export type NormalFormTriggerSetting = {
-  // 更新表单字段
+export type FormTriggerSetting = {
+  // 条件类型
+  conditionType?: ConditionType
+  // 条件表达式
+  conditionExpression?: string
+  // 条件组
+  conditionGroups?: ConditionGroup
+  // 更新表单字段配置
   updateFormFields?: Record<string, any>
+  // 删除表单字段配置
+  deleteFields?: string[]
 }
 
 export const TRIGGER_TYPES: DictDataVO[] = [
-  { label: 'HTTP 请求', value: TriggerTypeEnum.HTTP_REQUEST },
-  { label: '修改表单数据', value: TriggerTypeEnum.UPDATE_NORMAL_FORM }
+  { label: '发送 HTTP 请求', value: TriggerTypeEnum.HTTP_REQUEST },
+  { label: '接收 HTTP 回调', value: TriggerTypeEnum.HTTP_CALLBACK },
+  { label: '修改表单数据', value: TriggerTypeEnum.FORM_UPDATE },
+  { label: '删除表单数据', value: TriggerTypeEnum.FORM_DELETE }
+]
+
+/**
+ * 子流程节点结构定义
+ */
+export type ChildProcessSetting = {
+  calledProcessDefinitionKey: string
+  calledProcessDefinitionName: string
+  async: boolean
+  inVariables?: IOParameter[]
+  outVariables?: IOParameter[]
+  skipStartUserNode: boolean
+  startUserSetting: StartUserSetting
+  timeoutSetting: TimeoutSetting
+  multiInstanceSetting: MultiInstanceSetting
+}
+export type IOParameter = {
+  source: string
+  target: string
+}
+export type StartUserSetting = {
+  type: ChildProcessStartUserTypeEnum
+  formField?: string
+  emptyType?: ChildProcessStartUserEmptyTypeEnum
+}
+export type TimeoutSetting = {
+  enable: boolean
+  type?: DelayTypeEnum
+  timeExpression?: string
+}
+export type MultiInstanceSetting = {
+  enable: boolean
+  sequential?: boolean
+  approveRatio?: number
+  sourceType?: ChildProcessMultiInstanceSourceTypeEnum
+  source?: string
+}
+export enum ChildProcessStartUserTypeEnum {
+  /**
+   * 同主流程发起人
+   */
+  MAIN_PROCESS_START_USER = 1,
+  /**
+   * 表单
+   */
+  FROM_FORM = 2
+}
+export const CHILD_PROCESS_START_USER_TYPE = [
+  { label: '同主流程发起人', value: ChildProcessStartUserTypeEnum.MAIN_PROCESS_START_USER },
+  { label: '表单', value: ChildProcessStartUserTypeEnum.FROM_FORM }
+]
+export enum ChildProcessStartUserEmptyTypeEnum {
+  /**
+   * 同主流程发起人
+   */
+  MAIN_PROCESS_START_USER = 1,
+  /**
+   * 子流程管理员
+   */
+  CHILD_PROCESS_ADMIN = 2,
+  /**
+   * 主流程管理员
+   */
+  MAIN_PROCESS_ADMIN = 3
+}
+export const CHILD_PROCESS_START_USER_EMPTY_TYPE = [
+  { label: '同主流程发起人', value: ChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_START_USER },
+  { label: '子流程管理员', value: ChildProcessStartUserEmptyTypeEnum.CHILD_PROCESS_ADMIN },
+  { label: '主流程管理员', value: ChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_ADMIN }
+]
+export enum ChildProcessMultiInstanceSourceTypeEnum {
+  /**
+   * 固定数量
+   */
+  FIXED_QUANTITY = 1,
+  /**
+   * 数字表单
+   */
+  NUMBER_FORM = 2,
+  /**
+   * 多选表单
+   */
+  MULTIPLE_FORM = 3
+}
+export const CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE = [
+  { label: '固定数量', value: ChildProcessMultiInstanceSourceTypeEnum.FIXED_QUANTITY },
+  { label: '数字表单', value: ChildProcessMultiInstanceSourceTypeEnum.NUMBER_FORM },
+  { label: '多选表单', value: ChildProcessMultiInstanceSourceTypeEnum.MULTIPLE_FORM }
 ]

+ 71 - 3
src/components/SimpleProcessDesignerV2/src/node.ts

@@ -15,7 +15,10 @@ import {
   AssignEmptyHandlerType,
   FieldPermissionType,
   HttpRequestParam,
-  ProcessVariableEnum
+  ProcessVariableEnum,
+  ConditionType,
+  ConditionGroup,
+  COMPARISON_OPERATORS
 } from './consts'
 import { parseFormFields } from '@/components/FormCreate/src/utils'
 
@@ -201,7 +204,7 @@ export function useNodeForm(nodeType: NodeType) {
   const deptTreeOptions = inject('deptTree', ref()) // 部门树
   const formFields = inject<Ref<string[]>>('formFields', ref([])) // 流程表单字段
   const configForm = ref<UserTaskFormType | CopyTaskFormType>()
-  if (nodeType === NodeType.USER_TASK_NODE) {
+  if (nodeType === NodeType.USER_TASK_NODE || nodeType === NodeType.TRANSACTOR_NODE) {
     configForm.value = {
       candidateStrategy: CandidateStrategy.USER,
       approveMethod: ApproveMethodType.SEQUENTIAL_APPROVE,
@@ -307,6 +310,11 @@ export function useNodeForm(nodeType: NodeType) {
       showText = `表单内部门负责人`
     }
 
+    // 审批人自选
+    if (configForm.value?.candidateStrategy === CandidateStrategy.APPROVE_USER_SELECT) {
+      showText = `审批人自选`
+    }
+
     // 发起人自选
     if (configForm.value?.candidateStrategy === CandidateStrategy.START_USER_SELECT) {
       showText = `发起人自选`
@@ -543,6 +551,66 @@ export function useTaskStatusClass(taskStatus: TaskStatusEnum | undefined): stri
   if (taskStatus === TaskStatusEnum.CANCEL) {
     return 'status-cancel'
   }
-
   return ''
 }
+
+/** 条件组件文字展示 */
+export function getConditionShowText(
+  conditionType: ConditionType | undefined,
+  conditionExpression: string | undefined,
+  conditionGroups: ConditionGroup | undefined,
+  fieldOptions: Array<Record<string, any>>
+) {
+  let showText = ''
+  if (conditionType === ConditionType.EXPRESSION) {
+    if (conditionExpression) {
+      showText = `表达式:${conditionExpression}`
+    }
+  }
+  if (conditionType === ConditionType.RULE) {
+    // 条件组是否为与关系
+    const groupAnd = conditionGroups?.and
+    let warningMessage: undefined | string = undefined
+    const conditionGroup = conditionGroups?.conditions.map((item) => {
+      return (
+        '(' +
+        item.rules
+          .map((rule) => {
+            if (rule.leftSide && rule.rightSide) {
+              return (
+                getFormFieldTitle(fieldOptions, rule.leftSide) +
+                ' ' +
+                getOpName(rule.opCode) +
+                ' ' +
+                rule.rightSide
+              )
+            } else {
+              // 有一条规则不完善。提示错误
+              warningMessage = '请完善条件规则'
+              return ''
+            }
+          })
+          .join(item.and ? ' 且 ' : ' 或 ') +
+        ' ) '
+      )
+    })
+    if (warningMessage) {
+      showText = ''
+    } else {
+      showText = conditionGroup!.join(groupAnd ? ' 且 ' : ' 或 ')
+    }
+  }
+  return showText
+}
+
+/** 获取表单字段名称*/
+const getFormFieldTitle = (fieldOptions: Array<Record<string, any>>, field: string) => {
+  const item = fieldOptions.find((item) => item.field === field)
+  return item?.title
+}
+
+/** 获取操作符名称 */
+const getOpName = (opCode: string): string => {
+  const opName = COMPARISON_OPERATORS.find((item: any) => item.value === opCode)
+  return opName?.label
+}

+ 610 - 0
src/components/SimpleProcessDesignerV2/src/nodes-config/ChildProcessNodeConfig.vue

@@ -0,0 +1,610 @@
+<template>
+  <el-drawer
+    :append-to-body="true"
+    v-model="settingVisible"
+    :show-close="false"
+    :size="550"
+    :before-close="saveConfig"
+  >
+    <template #header>
+      <div class="config-header">
+        <input
+          v-if="showInput"
+          type="text"
+          class="config-editable-input"
+          @blur="blurEvent()"
+          v-mountedFocus
+          v-model="nodeName"
+          :placeholder="nodeName"
+        />
+        <div v-else class="node-name">
+          {{ nodeName }} <Icon class="ml-1" icon="ep:edit-pen" :size="16" @click="clickIcon()" />
+        </div>
+        <div class="divide-line"></div>
+      </div>
+    </template>
+    <el-tabs type="border-card" v-model="activeTabName">
+      <el-tab-pane label="子流程" name="child">
+        <div>
+          <el-form ref="formRef" :model="configForm" label-position="top" :rules="formRules">
+            <el-form-item label="是否异步" prop="async">
+              <el-switch v-model="configForm.async" active-text="异步" inactive-text="不异步" />
+            </el-form-item>
+            <el-form-item label="选择子流程" prop="calledProcessDefinitionKey">
+              <el-select
+                v-model="configForm.calledProcessDefinitionKey"
+                clearable
+                @change="handleCalledElementChange"
+              >
+                <el-option
+                  v-for="(item, index) in childProcessOptions"
+                  :key="index"
+                  :label="item.name"
+                  :value="item.key"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="是否自动跳过子流程发起节点" prop="skipStartUserNode">
+              <el-switch
+                v-model="configForm.skipStartUserNode"
+                active-text="跳过"
+                inactive-text="不跳过"
+              />
+            </el-form-item>
+            <el-form-item label="主→子变量传递" prop="inVariables">
+              <div class="flex pt-2" v-for="(item, index) in configForm.inVariables" :key="index">
+                <div class="mr-2">
+                  <el-form-item
+                    :prop="`inVariables.${index}.source`"
+                    :rules="{
+                      required: true,
+                      message: '变量不能为空',
+                      trigger: 'blur'
+                    }"
+                  >
+                    <el-select class="w-200px!" v-model="item.source">
+                      <el-option
+                        v-for="(field, fIdx) in formFieldOptions"
+                        :key="fIdx"
+                        :label="field.title"
+                        :value="field.field"
+                      />
+                    </el-select>
+                  </el-form-item>
+                </div>
+                <div class="mr-2">
+                  <el-form-item
+                    :prop="`inVariables.${index}.target`"
+                    :rules="{
+                      required: true,
+                      message: '变量不能为空',
+                      trigger: 'blur'
+                    }"
+                  >
+                    <el-select class="w-200px!" v-model="item.target">
+                      <el-option
+                        v-for="(field, fIdx) in childFormFieldOptions"
+                        :key="fIdx"
+                        :label="field.title"
+                        :value="field.field"
+                      />
+                    </el-select>
+                  </el-form-item>
+                </div>
+                <div class="mr-1 flex items-center">
+                  <Icon
+                    icon="ep:delete"
+                    :size="18"
+                    @click="deleteVariable(index, configForm.inVariables)"
+                  />
+                </div>
+              </div>
+              <el-button type="primary" text @click="addVariable(configForm.inVariables)">
+                <Icon icon="ep:plus" class="mr-5px" />添加一行
+              </el-button>
+            </el-form-item>
+            <el-form-item
+              v-if="configForm.async === false"
+              label="子→主变量传递"
+              prop="outVariables"
+            >
+              <div class="flex pt-2" v-for="(item, index) in configForm.outVariables" :key="index">
+                <div class="mr-2">
+                  <el-form-item
+                    :prop="`outVariables.${index}.source`"
+                    :rules="{
+                      required: true,
+                      message: '变量不能为空',
+                      trigger: 'blur'
+                    }"
+                  >
+                    <el-select class="w-200px!" v-model="item.source">
+                      <el-option
+                        v-for="(field, fIdx) in childFormFieldOptions"
+                        :key="fIdx"
+                        :label="field.title"
+                        :value="field.field"
+                      />
+                    </el-select>
+                  </el-form-item>
+                </div>
+                <div class="mr-2">
+                  <el-form-item
+                    :prop="`outVariables.${index}.target`"
+                    :rules="{
+                      required: true,
+                      message: '变量不能为空',
+                      trigger: 'blur'
+                    }"
+                  >
+                    <el-select class="w-200px!" v-model="item.target">
+                      <el-option
+                        v-for="(field, fIdx) in formFieldOptions"
+                        :key="fIdx"
+                        :label="field.title"
+                        :value="field.field"
+                      />
+                    </el-select>
+                  </el-form-item>
+                </div>
+                <div class="mr-1 flex items-center">
+                  <Icon
+                    icon="ep:delete"
+                    :size="18"
+                    @click="deleteVariable(index, configForm.outVariables)"
+                  />
+                </div>
+              </div>
+              <el-button type="primary" text @click="addVariable(configForm.outVariables)">
+                <Icon icon="ep:plus" class="mr-5px" />添加一行
+              </el-button>
+            </el-form-item>
+            <el-form-item label="子流程发起人" prop="startUserType">
+              <el-radio-group v-model="configForm.startUserType">
+                <el-radio
+                  v-for="item in CHILD_PROCESS_START_USER_TYPE"
+                  :key="item.value"
+                  :value="item.value"
+                >
+                  {{ item.label }}
+                </el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item
+              v-if="configForm.startUserType === ChildProcessStartUserTypeEnum.FROM_FORM"
+              label="当子流程发起人为空时"
+              prop="startUserType"
+            >
+              <el-radio-group v-model="configForm.startUserEmptyType">
+                <el-radio
+                  v-for="item in CHILD_PROCESS_START_USER_EMPTY_TYPE"
+                  :key="item.value"
+                  :value="item.value"
+                >
+                  {{ item.label }}
+                </el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item
+              v-if="configForm.startUserType === 2"
+              label="发起人表单"
+              prop="startUserFormField"
+            >
+              <el-select class="w-200px!" v-model="configForm.startUserFormField">
+                <el-option
+                  v-for="(field, fIdx) in formFieldOptions"
+                  :key="fIdx"
+                  :label="field.title"
+                  :value="field.field"
+                />
+              </el-select>
+            </el-form-item>
+
+            <el-divider content-position="left">超时设置</el-divider>
+            <el-form-item label="启用开关" prop="timeoutEnable">
+              <el-switch
+                v-model="configForm.timeoutEnable"
+                active-text="开启"
+                inactive-text="关闭"
+              />
+            </el-form-item>
+            <div v-if="configForm.timeoutEnable">
+              <el-form-item prop="timeoutType">
+                <el-radio-group v-model="configForm.timeoutType">
+                  <el-radio-button
+                    v-for="item in DELAY_TYPE"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
+                  />
+                </el-radio-group>
+              </el-form-item>
+              <el-form-item v-if="configForm.timeoutType === DelayTypeEnum.FIXED_TIME_DURATION">
+                <el-form-item prop="timeDuration">
+                  <el-input-number
+                    class="mr-2"
+                    :style="{ width: '100px' }"
+                    v-model="configForm.timeDuration"
+                    :min="1"
+                    controls-position="right"
+                  />
+                </el-form-item>
+                <el-select v-model="configForm.timeUnit" class="mr-2" :style="{ width: '100px' }">
+                  <el-option
+                    v-for="item in TIME_UNIT_TYPES"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
+                  />
+                </el-select>
+                <el-text>后进入下一节点</el-text>
+              </el-form-item>
+              <el-form-item
+                v-if="configForm.timeoutType === DelayTypeEnum.FIXED_DATE_TIME"
+                prop="dateTime"
+              >
+                <el-date-picker
+                  class="mr-2"
+                  v-model="configForm.dateTime"
+                  type="datetime"
+                  placeholder="请选择日期和时间"
+                  value-format="YYYY-MM-DDTHH:mm:ss"
+                />
+                <el-text>后进入下一节点</el-text>
+              </el-form-item>
+            </div>
+
+            <el-divider content-position="left">多实例设置</el-divider>
+            <el-form-item label="启用开关" prop="multiInstanceEnable">
+              <el-switch
+                v-model="configForm.multiInstanceEnable"
+                active-text="开启"
+                inactive-text="关闭"
+              />
+            </el-form-item>
+            <div v-if="configForm.multiInstanceEnable">
+              <el-form-item prop="sequential">
+                <el-switch
+                  v-model="configForm.sequential"
+                  active-text="串行"
+                  inactive-text="并行"
+                />
+              </el-form-item>
+              <el-form-item prop="approveRatio">
+                <el-text>完成比例(%)</el-text>
+                <el-input-number
+                  class="ml-10px"
+                  v-model="configForm.approveRatio"
+                  :min="10"
+                  :max="100"
+                  :step="10"
+                />
+              </el-form-item>
+              <el-form-item prop="multiInstanceSourceType">
+                <el-text>多实例来源</el-text>
+                <el-select
+                  class="ml-10px w-200px!"
+                  v-model="configForm.multiInstanceSourceType"
+                  @change="handleMultiInstanceSourceTypeChange"
+                >
+                  <el-option
+                    v-for="item in CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
+                  />
+                </el-select>
+              </el-form-item>
+              <el-form-item v-if="configForm.multiInstanceSourceType === ChildProcessMultiInstanceSourceTypeEnum.FIXED_QUANTITY">
+                <el-input-number v-model="configForm.multiInstanceSource" :min="1" />
+              </el-form-item>
+              <el-form-item v-if="configForm.multiInstanceSourceType === ChildProcessMultiInstanceSourceTypeEnum.NUMBER_FORM">
+                <el-select class="w-200px!" v-model="configForm.multiInstanceSource">
+                  <el-option
+                    v-for="(field, fIdx) in digitalFormFieldOptions"
+                    :key="fIdx"
+                    :label="field.title"
+                    :value="field.field"
+                  />
+                </el-select>
+              </el-form-item>
+              <el-form-item v-if="configForm.multiInstanceSourceType === ChildProcessMultiInstanceSourceTypeEnum.MULTIPLE_FORM">
+                <el-select class="w-200px!" v-model="configForm.multiInstanceSource">
+                  <el-option
+                    v-for="(field, fIdx) in multiFormFieldOptions"
+                    :key="fIdx"
+                    :label="field.title"
+                    :value="field.field"
+                  />
+                </el-select>
+              </el-form-item>
+            </div>
+          </el-form>
+        </div>
+      </el-tab-pane>
+    </el-tabs>
+    <template #footer>
+      <el-divider />
+      <div>
+        <el-button type="primary" @click="saveConfig">确 定</el-button>
+        <el-button @click="closeDrawer">取 消</el-button>
+      </div>
+    </template>
+  </el-drawer>
+</template>
+<script setup lang="ts">
+import { getModelList } from '@/api/bpm/model'
+import { getForm } from '@/api/bpm/form'
+import {
+  SimpleFlowNode,
+  NodeType,
+  TIME_UNIT_TYPES,
+  TimeUnitType,
+  DelayTypeEnum,
+  DELAY_TYPE,
+  IOParameter,
+  ChildProcessStartUserTypeEnum,
+  CHILD_PROCESS_START_USER_TYPE,
+  ChildProcessStartUserEmptyTypeEnum,
+  CHILD_PROCESS_START_USER_EMPTY_TYPE,
+  CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE,
+  ChildProcessMultiInstanceSourceTypeEnum
+} from '../consts'
+import { useWatchNode, useDrawer, useNodeName, useFormFieldsAndStartUser } from '../node'
+import { parseFormFields } from '@/components/FormCreate/src/utils'
+import { convertTimeUnit } from '../utils'
+defineOptions({
+  name: 'ChildProcessNodeConfig'
+})
+const props = defineProps({
+  flowNode: {
+    type: Object as () => SimpleFlowNode,
+    required: true
+  }
+})
+// 抽屉配置
+const { settingVisible, closeDrawer, openDrawer } = useDrawer()
+// 当前节点
+const currentNode = useWatchNode(props)
+// 节点名称
+const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.CHILD_PROCESS_NODE)
+// 激活的 Tab 标签页
+const activeTabName = ref('child')
+// 子流程表单配置
+const formRef = ref() // 表单 Ref
+// 表单校验规则
+const formRules = reactive({
+  async: [{ required: true, message: '是否异步不能为空', trigger: 'change' }],
+  calledProcessDefinitionKey: [{ required: true, message: '子流程不能为空', trigger: 'change' }],
+  skipStartUserNode: [
+    { required: true, message: '是否自动跳过子流程发起节点不能为空', trigger: 'change' }
+  ],
+  startUserType: [{ required: true, message: '子流程发起人不能为空', trigger: 'change' }],
+  startUserEmptyType: [
+    { required: true, message: '当子流程发起人为空时不能为空', trigger: 'change' }
+  ],
+  startUserFormField: [{ required: true, message: '发起人表单不能为空', trigger: 'change' }],
+  timeoutEnable: [{ required: true, message: '超时设置是否开启不能为空', trigger: 'change' }],
+  timeoutType: [{ required: true, message: '超时设置时间不能为空', trigger: 'change' }],
+  timeDuration: [{ required: true, message: '超时设置时间不能为空', trigger: 'change' }],
+  dateTime: [{ required: true, message: '超时设置时间不能为空', trigger: 'change' }],
+  multiInstanceEnable: [{ required: true, message: '多实例设置不能为空', trigger: 'change' }]
+})
+type ChildProcessFormType = {
+  async: boolean
+  calledProcessDefinitionKey: string
+  skipStartUserNode: boolean
+  inVariables?: IOParameter[]
+  outVariables?: IOParameter[]
+  startUserType: ChildProcessStartUserTypeEnum
+  startUserEmptyType: ChildProcessStartUserEmptyTypeEnum
+  startUserFormField: string
+  timeoutEnable: boolean
+  timeoutType: DelayTypeEnum
+  timeDuration: number
+  timeUnit: TimeUnitType
+  dateTime: string
+  multiInstanceEnable: boolean
+  sequential: boolean
+  approveRatio: number
+  multiInstanceSourceType: ChildProcessMultiInstanceSourceTypeEnum
+  multiInstanceSource: string
+}
+const configForm = ref<ChildProcessFormType>({
+  async: false,
+  calledProcessDefinitionKey: '',
+  skipStartUserNode: false,
+  inVariables: [],
+  outVariables: [],
+  startUserType: ChildProcessStartUserTypeEnum.MAIN_PROCESS_START_USER,
+  startUserEmptyType: ChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_START_USER,
+  startUserFormField: '',
+  timeoutEnable: false,
+  timeoutType: DelayTypeEnum.FIXED_TIME_DURATION,
+  timeDuration: 1,
+  timeUnit: TimeUnitType.HOUR,
+  dateTime: '',
+  multiInstanceEnable: false,
+  sequential: false,
+  approveRatio: 100,
+  multiInstanceSourceType: ChildProcessMultiInstanceSourceTypeEnum.FIXED_QUANTITY,
+  multiInstanceSource: ''
+})
+const childProcessOptions = ref()
+const formFieldOptions = useFormFieldsAndStartUser()
+const digitalFormFieldOptions = computed(() => {
+  return formFieldOptions.filter((item) => item.type === 'inputNumber')
+})
+const multiFormFieldOptions = computed(() => {
+  return formFieldOptions.filter((item) => item.type === 'select' || item.type === 'checkbox')
+})
+const childFormFieldOptions = ref()
+
+// 保存配置
+const saveConfig = async () => {
+  activeTabName.value = 'child'
+  if (!formRef) return false
+  const valid = await formRef.value.validate()
+  if (!valid) return false
+  const childInfo = childProcessOptions.value.find(
+    (option: any) => option.key === configForm.value.calledProcessDefinitionKey
+  )
+  currentNode.value.name = nodeName.value!
+  if (currentNode.value.childProcessSetting) {
+    // 1. 是否异步
+    currentNode.value.childProcessSetting.async = configForm.value.async
+    // 2. 调用流程
+    currentNode.value.childProcessSetting.calledProcessDefinitionKey = childInfo.key
+    currentNode.value.childProcessSetting.calledProcessDefinitionName = childInfo.name
+    // 3. 是否跳过发起人
+    currentNode.value.childProcessSetting.skipStartUserNode = configForm.value.skipStartUserNode
+    // 4. 主->子变量
+    currentNode.value.childProcessSetting.inVariables = configForm.value.inVariables
+    // 5. 子->主变量
+    currentNode.value.childProcessSetting.outVariables = configForm.value.outVariables
+    // 6. 发起人设置
+    currentNode.value.childProcessSetting.startUserSetting.type = configForm.value.startUserType
+    currentNode.value.childProcessSetting.startUserSetting.emptyType =
+      configForm.value.startUserEmptyType
+    currentNode.value.childProcessSetting.startUserSetting.formField =
+      configForm.value.startUserFormField
+    // 7. 超时设置
+    currentNode.value.childProcessSetting.timeoutSetting = {
+      enable: configForm.value.timeoutEnable
+    }
+    if (configForm.value.timeoutEnable) {
+      currentNode.value.childProcessSetting.timeoutSetting.type = configForm.value.timeoutType
+      if (configForm.value.timeoutType === DelayTypeEnum.FIXED_TIME_DURATION) {
+        currentNode.value.childProcessSetting.timeoutSetting.timeExpression = getIsoTimeDuration()
+      }
+      if (configForm.value.timeoutType === DelayTypeEnum.FIXED_DATE_TIME) {
+        currentNode.value.childProcessSetting.timeoutSetting.timeExpression =
+          configForm.value.dateTime
+      }
+    }
+    // 8. 多实例设置
+    currentNode.value.childProcessSetting.multiInstanceSetting = {
+      enable: configForm.value.multiInstanceEnable
+    }
+    if (configForm.value.multiInstanceEnable) {
+      currentNode.value.childProcessSetting.multiInstanceSetting.sequential =
+        configForm.value.sequential
+      currentNode.value.childProcessSetting.multiInstanceSetting.approveRatio =
+        configForm.value.approveRatio
+      currentNode.value.childProcessSetting.multiInstanceSetting.sourceType =
+        configForm.value.multiInstanceSourceType
+      currentNode.value.childProcessSetting.multiInstanceSetting.source =
+        configForm.value.multiInstanceSource
+    }
+  }
+
+  currentNode.value.showText = `调用子流程:${childInfo.name}`
+  settingVisible.value = false
+  return true
+}
+// 显示子流程节点配置, 由父组件传过来
+const showChildProcessNodeConfig = (node: SimpleFlowNode) => {
+  nodeName.value = node.name
+  if (node.childProcessSetting) {
+    // 1. 是否异步
+    configForm.value.async = node.childProcessSetting.async
+    // 2. 调用流程
+    configForm.value.calledProcessDefinitionKey =
+      node.childProcessSetting?.calledProcessDefinitionKey
+    // 3. 是否跳过发起人
+    configForm.value.skipStartUserNode = node.childProcessSetting.skipStartUserNode
+    // 4. 主->子变量
+    configForm.value.inVariables = node.childProcessSetting.inVariables
+    // 5. 子->主变量
+    configForm.value.outVariables = node.childProcessSetting.outVariables
+    // 6. 发起人设置
+    configForm.value.startUserType = node.childProcessSetting.startUserSetting.type
+    configForm.value.startUserEmptyType = node.childProcessSetting.startUserSetting.emptyType ?? ChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_START_USER
+    configForm.value.startUserFormField = node.childProcessSetting.startUserSetting.formField ?? ''
+    // 7. 超时设置
+    configForm.value.timeoutEnable = node.childProcessSetting.timeoutSetting.enable ?? false
+    if (configForm.value.timeoutEnable) {
+      configForm.value.timeoutType =
+        node.childProcessSetting.timeoutSetting.type ?? DelayTypeEnum.FIXED_TIME_DURATION
+      // 固定时长
+      if (configForm.value.timeoutType === DelayTypeEnum.FIXED_TIME_DURATION) {
+        const strTimeDuration = node.childProcessSetting.timeoutSetting.timeExpression ?? ''
+        let parseTime = strTimeDuration.slice(2, strTimeDuration.length - 1)
+        let parseTimeUnit = strTimeDuration.slice(strTimeDuration.length - 1)
+        configForm.value.timeDuration = parseInt(parseTime)
+        configForm.value.timeUnit = convertTimeUnit(parseTimeUnit)
+      }
+      // 固定日期时间
+      if (configForm.value.timeoutType === DelayTypeEnum.FIXED_DATE_TIME) {
+        configForm.value.dateTime = node.childProcessSetting.timeoutSetting.timeExpression ?? ''
+      }
+    }
+    // 8. 多实例设置
+    configForm.value.multiInstanceEnable =
+      node.childProcessSetting.multiInstanceSetting.enable ?? false
+    if (configForm.value.multiInstanceEnable) {
+      configForm.value.sequential =
+        node.childProcessSetting.multiInstanceSetting.sequential ?? false
+      configForm.value.approveRatio =
+        node.childProcessSetting.multiInstanceSetting.approveRatio ?? 100
+      configForm.value.multiInstanceSourceType =
+        node.childProcessSetting.multiInstanceSetting.sourceType ??
+        ChildProcessMultiInstanceSourceTypeEnum.FIXED_QUANTITY
+      configForm.value.multiInstanceSource =
+        node.childProcessSetting.multiInstanceSetting.source ?? ''
+    }
+  }
+  loadFormInfo()
+}
+
+defineExpose({ openDrawer, showChildProcessNodeConfig }) // 暴露方法给父组件
+
+const addVariable = (arr?: IOParameter[]) => {
+  arr?.push({
+    source: '',
+    target: ''
+  })
+}
+const deleteVariable = (index: number, arr?: IOParameter[]) => {
+  arr?.splice(index, 1)
+}
+const handleCalledElementChange = () => {
+  configForm.value.inVariables = []
+  configForm.value.outVariables = []
+  loadFormInfo()
+}
+const loadFormInfo = async () => {
+  const childInfo = childProcessOptions.value.find(
+    (option) => option.key === configForm.value.calledProcessDefinitionKey
+  )
+  const formInfo = await getForm(childInfo.formId)
+  childFormFieldOptions.value = []
+  if (formInfo.fields) {
+    formInfo.fields.forEach((fieldStr: string) => {
+      parseFormFields(JSON.parse(fieldStr), childFormFieldOptions.value)
+    })
+  }
+}
+const getIsoTimeDuration = () => {
+  let strTimeDuration = 'PT'
+  if (configForm.value.timeUnit === TimeUnitType.MINUTE) {
+    strTimeDuration += configForm.value.timeDuration + 'M'
+  }
+  if (configForm.value.timeUnit === TimeUnitType.HOUR) {
+    strTimeDuration += configForm.value.timeDuration + 'H'
+  }
+  if (configForm.value.timeUnit === TimeUnitType.DAY) {
+    strTimeDuration += configForm.value.timeDuration + 'D'
+  }
+  return strTimeDuration
+}
+const handleMultiInstanceSourceTypeChange = () => {
+  configForm.value.multiInstanceSource = ''
+}
+
+onMounted(async () => {
+  childProcessOptions.value = await getModelList(undefined)
+})
+</script>
+
+<style lang="scss" scoped></style>

+ 69 - 75
src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue

@@ -43,15 +43,12 @@
   </el-drawer>
 </template>
 <script setup lang="ts">
-import {
-  SimpleFlowNode,
-  ConditionType,
-  COMPARISON_OPERATORS,
-} from '../consts'
+import { SimpleFlowNode, ConditionType } from '../consts'
 import { getDefaultConditionNodeName } from '../utils'
-import { useFormFieldsAndStartUser } from '../node'
+import { useFormFieldsAndStartUser, getConditionShowText } from '../node'
 import Condition from './components/Condition.vue'
-const message = useMessage() // 消息弹窗
+import { cloneDeep } from 'lodash-es'
+
 defineOptions({
   name: 'ConditionNodeConfig'
 })
@@ -67,9 +64,51 @@ const props = defineProps({
 })
 const settingVisible = ref(false)
 const currentNode = ref<SimpleFlowNode>(props.conditionNode)
-const condition = ref<any>()
+const condition = ref<any>({
+  conditionType: ConditionType.RULE, // 设置默认值
+  conditionExpression: '',
+  conditionGroups: {
+    and: true,
+    conditions: [
+      {
+        and: true,
+        rules: [
+          {
+            opCode: '==',
+            leftSide: '',
+            rightSide: ''
+          }
+        ]
+      }
+    ]
+  }
+})
 const open = () => {
-  condition.value = currentNode.value.conditionSetting
+  // 如果有已存在的配置则使用,否则使用默认值
+  if (currentNode.value.conditionSetting) {
+    condition.value = cloneDeep(currentNode.value.conditionSetting)
+  } else {
+    // 重置为默认值
+    condition.value = {
+      conditionType: ConditionType.RULE,
+      conditionExpression: '',
+      conditionGroups: {
+        and: true,
+        conditions: [
+          {
+            and: true,
+            rules: [
+              {
+                opCode: '==',
+                leftSide: '',
+                rightSide: ''
+              }
+            ]
+          }
+        ]
+      }
+    }
+  }
   settingVisible.value = true
 }
 
@@ -93,8 +132,6 @@ const blurEvent = () => {
     getDefaultConditionNodeName(props.nodeIndex, currentNode.value?.conditionSetting?.defaultFlow)
 }
 
-
-
 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
 
 // 关闭
@@ -111,84 +148,41 @@ const handleClose = async (done: (cancel?: boolean) => void) => {
   }
 }
 
+/** 保存配置 */
+const fieldOptions = useFormFieldsAndStartUser() // 流程表单字段和发起人字段
 const conditionRef = ref()
-// 保存配置
 const saveConfig = async () => {
   if (!currentNode.value.conditionSetting?.defaultFlow) {
     // 校验表单
     const valid = await conditionRef.value.validate()
     if (!valid) return false
-    const showText = getShowText()
+    const showText = getConditionShowText(
+      condition.value?.conditionType,
+      condition.value?.conditionExpression,
+      condition.value.conditionGroups,
+      fieldOptions
+    )
     if (!showText) {
       return false
     }
     currentNode.value.showText = showText
-    currentNode.value.conditionSetting!.conditionType = condition.value?.conditionType
-    if (currentNode.value.conditionSetting?.conditionType === ConditionType.EXPRESSION) {
-      currentNode.value.conditionSetting.conditionGroups = undefined
-      currentNode.value.conditionSetting.conditionExpression = condition.value?.conditionExpression
-    }
-    if (currentNode.value.conditionSetting!.conditionType === ConditionType.RULE) {
-      currentNode.value.conditionSetting!.conditionExpression = undefined
-      currentNode.value.conditionSetting!.conditionGroups = condition.value?.conditionGroups
-    }
+    // 使用 cloneDeep 进行深拷贝
+    currentNode.value.conditionSetting = cloneDeep({
+      ...currentNode.value.conditionSetting,
+      conditionType: condition.value?.conditionType,
+      conditionExpression:
+        condition.value?.conditionType === ConditionType.EXPRESSION
+          ? condition.value?.conditionExpression
+          : undefined,
+      conditionGroups:
+        condition.value?.conditionType === ConditionType.RULE
+          ? condition.value?.conditionGroups
+          : undefined
+    })
   }
   settingVisible.value = false
   return true
 }
-const getShowText = (): string => {
-  let showText = ''
-  if (condition.value?.conditionType === ConditionType.EXPRESSION) {
-    if (condition.value.conditionExpression) {
-      showText = `表达式:${condition.value.conditionExpression}`
-    }
-  }
-  if (condition.value?.conditionType === ConditionType.RULE) {
-    // 条件组是否为与关系
-    const groupAnd = condition.value.conditionGroups?.and
-    let warningMesg: undefined | string = undefined
-    const conditionGroup = condition.value.conditionGroups?.conditions.map((item) => {
-      return (
-        '(' +
-        item.rules
-          .map((rule) => {
-            if (rule.leftSide && rule.rightSide) {
-              return (
-                getFieldTitle(rule.leftSide) + ' ' + getOpName(rule.opCode) + ' ' + rule.rightSide
-              )
-            } else {
-              // 有一条规则不完善。提示错误
-              warningMesg = '请完善条件规则'
-              return ''
-            }
-          })
-          .join(item.and ? ' 且 ' : ' 或 ') +
-        ' ) '
-      )
-    })
-    if (warningMesg) {
-      message.warning(warningMesg)
-      showText = ''
-    } else {
-      showText = conditionGroup!.join(groupAnd ? ' 且 ' : ' 或 ')
-    }
-  }
-  return showText
-}
-// 流程表单字段和发起人字段
-const fieldOptions = useFormFieldsAndStartUser()
-
-/** 获取字段名称 */
-const getFieldTitle = (field: string) => {
-  const item = fieldOptions.find((item) => item.field === field)
-  return item?.title
-}
-
-/** 获取操作符名称 */
-const getOpName = (opCode: string): string => {
-  const opName = COMPARISON_OPERATORS.find((item: any) => item.value === opCode)
-  return opName?.label
-}
 </script>
 
 <style lang="scss" scoped>

+ 23 - 5
src/components/SimpleProcessDesignerV2/src/nodes-config/CopyTaskNodeConfig.vue

@@ -134,7 +134,7 @@
                   :key="idx"
                   :label="item.title"
                   :value="item.field"
-                  :disabled ="!item.required"
+                  :disabled="!item.required"
                 />
               </el-select>
             </el-form-item>
@@ -149,7 +149,7 @@
                   :key="idx"
                   :label="item.title"
                   :value="item.field"
-                  :disabled ="!item.required"
+                  :disabled="!item.required"
                 />
               </el-select>
             </el-form-item>
@@ -195,9 +195,15 @@
           <div class="field-permit-title">
             <div class="setting-title-label first-title"> 字段名称 </div>
             <div class="other-titles">
-              <span class="setting-title-label">只读</span>
-              <span class="setting-title-label">可编辑</span>
-              <span class="setting-title-label">隐藏</span>
+              <span class="setting-title-label cursor-pointer" @click="updatePermission('READ')">
+                只读
+              </span>
+              <span class="setting-title-label cursor-pointer" @click="updatePermission('WRITE')">
+                可编辑
+              </span>
+              <span class="setting-title-label cursor-pointer" @click="updatePermission('NONE')">
+                隐藏
+              </span>
             </div>
           </div>
           <div
@@ -368,6 +374,18 @@ const showCopyTaskNodeConfig = (node: SimpleFlowNode) => {
   getNodeConfigFormFields(node.fieldsPermission)
 }
 
+/** 批量更新权限 */
+const updatePermission = (type: string) => {
+  fieldsPermissionConfig.value.forEach((field) => {
+    field.permission =
+      type === 'READ'
+        ? FieldPermissionType.READ
+        : type === 'WRITE'
+          ? FieldPermissionType.WRITE
+          : FieldPermissionType.NONE
+  })
+}
+
 defineExpose({ openDrawer, showCopyTaskNodeConfig }) // 暴露方法给父组件
 </script>
 

+ 22 - 4
src/components/SimpleProcessDesignerV2/src/nodes-config/StartUserNodeConfig.vue

@@ -36,7 +36,8 @@
             placement="top"
             :content="getUserNicknames(startUserIds)"
           >
-            {{ getUserNicknames(startUserIds.slice(0,2)) }} 等 {{ startUserIds.length }} 人可发起流程
+            {{ getUserNicknames(startUserIds.slice(0, 2)) }} 等
+            {{ startUserIds.length }} 人可发起流程
           </el-tooltip>
         </el-text>
       </el-tab-pane>
@@ -46,9 +47,15 @@
           <div class="field-permit-title">
             <div class="setting-title-label first-title"> 字段名称 </div>
             <div class="other-titles">
-              <span class="setting-title-label">只读</span>
-              <span class="setting-title-label">可编辑</span>
-              <span class="setting-title-label">隐藏</span>
+              <span class="setting-title-label cursor-pointer" @click="updatePermission('READ')">
+                只读
+              </span>
+              <span class="setting-title-label cursor-pointer" @click="updatePermission('WRITE')">
+                可编辑
+              </span>
+              <span class="setting-title-label cursor-pointer" @click="updatePermission('NONE')">
+                隐藏
+              </span>
             </div>
           </div>
           <div
@@ -157,6 +164,17 @@ const showStartUserNodeConfig = (node: SimpleFlowNode) => {
   getNodeConfigFormFields(node.fieldsPermission)
 }
 
+/** 批量更新权限 */
+const updatePermission = (type: string) => {
+  fieldsPermissionConfig.value.forEach((field) => {
+    field.permission =
+      type === 'READ'
+        ? FieldPermissionType.READ
+        : type === 'WRITE'
+          ? FieldPermissionType.WRITE
+          : FieldPermissionType.NONE
+  })
+}
 defineExpose({ openDrawer, showStartUserNodeConfig }) // 暴露方法给父组件
 </script>
 

+ 356 - 166
src/components/SimpleProcessDesignerV2/src/nodes-config/TriggerNodeConfig.vue

@@ -3,7 +3,7 @@
     :append-to-body="true"
     v-model="settingVisible"
     :show-close="false"
-    :size="550"
+    :size="630"
     :before-close="saveConfig"
   >
     <template #header>
@@ -26,7 +26,7 @@
     <div>
       <el-form ref="formRef" :model="configForm" label-position="top" :rules="formRules">
         <el-form-item label="触发器类型" prop="type">
-          <el-select v-model="configForm.type">
+          <el-select v-model="configForm.type" @change="changeTriggerType">
             <el-option
               v-for="(item, index) in TRIGGER_TYPES"
               :key="index"
@@ -37,146 +37,197 @@
         </el-form-item>
         <!-- HTTP 请求触发器 -->
         <div
-          v-if="configForm.type === TriggerTypeEnum.HTTP_REQUEST && configForm.httpRequestSetting"
+          v-if="
+            [TriggerTypeEnum.HTTP_REQUEST, TriggerTypeEnum.HTTP_CALLBACK].includes(
+              configForm.type
+            ) && configForm.httpRequestSetting
+          "
         >
-          <el-form-item>
-            <el-alert
-              title="仅支持 POST 请求,以请求体方式接收参数"
-              type="warning"
-              show-icon
-              :closable="false"
-            />
-          </el-form-item>
-          <!-- 请求地址-->
-          <el-form-item label="请求地址" prop="httpRequestSetting.url">
-            <el-input v-model="configForm.httpRequestSetting.url" />
-          </el-form-item>
-          <!-- 请求头,请求体设置-->
-          <HttpRequestParamSetting
-            :header="configForm.httpRequestSetting.header"
-            :body="configForm.httpRequestSetting.body"
-            :bind="'httpRequestSetting'"
+          <HttpRequestSetting
+            v-model:setting="configForm.httpRequestSetting"
+            :responseEnable="configForm.type === TriggerTypeEnum.HTTP_REQUEST"
+            :formItemPrefix="'httpRequestSetting'"
           />
-          <!-- 返回值设置-->
-          <el-form-item label="返回值">
-            <el-alert
-              title="通过请求返回值, 可以修改流程表单的值"
-              type="warning"
-              show-icon
-              :closable="false"
-            />
-          </el-form-item>
-          <el-form-item>
-            <div
-              class="flex pt-2"
-              v-for="(item, index) in configForm.httpRequestSetting.response"
-              :key="index"
-            >
-              <div class="mr-2">
-                <el-form-item
-                  :prop="`httpRequestSetting.response.${index}.key`"
-                  :rules="{
-                    required: true,
-                    message: '表单字段不能为空',
-                    trigger: 'blur'
-                  }"
+        </div>
+
+        <!-- 表单数据修改触发器 -->
+        <div v-if="configForm.type === TriggerTypeEnum.FORM_UPDATE">
+          <div v-for="(formSetting, index) in configForm.formSettings" :key="index">
+            <el-card class="w-580px mt-4">
+              <template #header>
+                <div class="flex items-center justify-between">
+                  <div>修改表单设置 {{ index + 1 }}</div>
+                  <el-button
+                    type="primary"
+                    plain
+                    circle
+                    v-if="configForm.formSettings!.length > 1"
+                    @click="deleteFormSetting(index)"
+                  >
+                    <Icon icon="ep:close" />
+                  </el-button>
+                </div>
+              </template>
+
+              <!-- 条件设置 -->
+              <ConditionDialog
+                :ref="`condition-${index}`"
+                @update-condition="(val) => handleConditionUpdate(index, val)"
+              />
+              <div class="cursor-pointer" v-if="formSetting.conditionType">
+                <el-tag
+                  type="success"
+                  effect="light"
+                  closable
+                  @close="deleteFormSettingCondition(formSetting)"
+                  @click="openFormSettingCondition(index, formSetting)"
                 >
-                  <el-select class="w-160px!" v-model="item.key" placeholder="请选择表单字段">
-                    <el-option
-                      v-for="(field, fIdx) in formFields"
-                      :key="fIdx"
-                      :label="field.title"
-                      :value="field.field"
-                      :disabled="!field.required"
+                  {{ showConditionText(formSetting) }}
+                </el-tag>
+              </div>
+              <el-button
+                v-else
+                type="primary"
+                text
+                @click="addFormSettingCondition(index, formSetting)"
+              >
+                <Icon icon="ep:link" class="mr-5px" />添加条件
+              </el-button>
+              <el-divider content-position="left">修改表单字段设置</el-divider>
+              <!-- 表单字段修改设置 -->
+              <div
+                class="flex items-center"
+                v-for="key in Object.keys(formSetting.updateFormFields || {})"
+                :key="key"
+              >
+                <div class="mr-2 flex items-center">
+                  <el-form-item>
+                    <el-select
+                      class="w-160px!"
+                      :model-value="key"
+                      @update:model-value="(newKey) => updateFormFieldKey(formSetting, key, newKey)"
+                      placeholder="请选择表单字段"
+                      :disabled="key !== ''"
+                    >
+                      <el-option
+                        v-for="(field, fIdx) in optionalUpdateFormFields"
+                        :key="fIdx"
+                        :label="field.title"
+                        :value="field.field"
+                        :disabled="field.disabled"
+                      />
+                    </el-select>
+                  </el-form-item>
+                </div>
+                <div class="mx-2"><el-form-item>的值设置为</el-form-item></div>
+                <div class="mr-2">
+                  <el-form-item
+                    :prop="`formSettings.${index}.updateFormFields.${key}`"
+                    :rules="{
+                      required: true,
+                      message: '值不能为空',
+                      trigger: 'blur'
+                    }"
+                  >
+                    <el-input
+                      class="w-160px"
+                      v-model="formSetting.updateFormFields![key]"
+                      placeholder="请输入"
+                      :disabled="!key"
                     />
-                  </el-select>
-                </el-form-item>
+                  </el-form-item>
+                </div>
+                <div class="mr-1 pt-1 cursor-pointer">
+                  <el-form-item>
+                    <Icon
+                      icon="ep:delete"
+                      :size="18"
+                      @click="deleteFormFieldSetting(formSetting, key)"
+                    />
+                  </el-form-item>
+                </div>
               </div>
-              <div class="mr-2">
-                <el-form-item
-                  :prop="`httpRequestSetting.response.${index}.value`"
-                  :rules="{
-                    required: true,
-                    message: '请求返回字段不能为空',
-                    trigger: 'blur'
-                  }"
+
+              <!-- 添加表单字段按钮 -->
+              <el-button type="primary" text @click="addFormFieldSetting(formSetting)">
+                <Icon icon="ep:memo" class="mr-5px" />添加修改字段
+              </el-button>
+            </el-card>
+          </div>
+
+          <!-- 添加新的设置 -->
+          <el-button class="mt-6" type="primary" text @click="addFormSetting">
+            <Icon icon="ep:setting" class="mr-5px" />添加设置
+          </el-button>
+        </div>
+
+        <!-- 表单数据删除触发器 -->
+        <div v-if="configForm.type === TriggerTypeEnum.FORM_DELETE">
+          <div v-for="(formSetting, index) in configForm.formSettings" :key="index">
+            <el-card class="w-580px mt-4">
+              <template #header>
+                <div class="flex items-center justify-between">
+                  <div>删除表单设置 {{ index + 1 }}</div>
+                  <el-button
+                    type="primary"
+                    plain
+                    circle
+                    v-if="configForm.formSettings!.length > 1"
+                    @click="deleteFormSetting(index)"
+                  >
+                    <Icon icon="ep:close" />
+                  </el-button>
+                </div>
+              </template>
+
+              <!-- 条件设置 -->
+              <ConditionDialog
+                :ref="`condition-${index}`"
+                @update-condition="(val) => handleConditionUpdate(index, val)"
+              />
+              <div class="cursor-pointer" v-if="formSetting.conditionType">
+                <el-tag
+                  type="warning"
+                  effect="light"
+                  closable
+                  @close="deleteFormSettingCondition(formSetting)"
+                  @click="openFormSettingCondition(index, formSetting)"
                 >
-                  <el-input class="w-160px" v-model="item.value" placeholder="请求返回字段" />
-                </el-form-item>
-              </div>
-              <div class="mr-1 pt-1 cursor-pointer">
-                <Icon
-                  icon="ep:delete"
-                  :size="18"
-                  @click="deleteHttpResponseSetting(configForm.httpRequestSetting.response!, index)"
-                />
+                  {{ showConditionText(formSetting) }}
+                </el-tag>
               </div>
-            </div>
-            <el-button
-              type="primary"
-              text
-              @click="addHttpResponseSetting(configForm.httpRequestSetting.response!)"
-            >
-              <Icon icon="ep:plus" class="mr-5px" />添加一行
-            </el-button>
-          </el-form-item>
-        </div>
-        <div
-          v-if="
-            configForm.type === TriggerTypeEnum.UPDATE_NORMAL_FORM && configForm.normalFormSetting
-          "
-        >
-          <el-divider content-position="left">修改表单设置</el-divider>
-          <div
-            class="flex items-center"
-            v-for="key in Object.keys(configForm.normalFormSetting.updateFormFields!)"
-            :key="key"
-          >
-            <div class="mr-2 flex items-center">
-              <el-form-item>
+              <el-button
+                v-else
+                type="primary"
+                text
+                @click="addFormSettingCondition(index, formSetting)"
+              >
+                <Icon icon="ep:link" class="mr-5px" />添加条件
+              </el-button>
+
+              <el-divider content-position="left">删除表单字段设置</el-divider>
+              <!-- 表单字段删除设置 -->
+              <div class="flex flex-wrap gap-2">
                 <el-select
-                  class="w-160px!"
-                  :model-value="key"
-                  @update:model-value="(newKey) => updateFormFieldKey(key, newKey)"
-                  placeholder="请选择表单字段"
-                  :disabled="key !== ''"
+                  v-model="formSetting.deleteFields"
+                  multiple
+                  placeholder="请选择要删除的字段"
+                  class="w-full"
                 >
                   <el-option
-                    v-for="(field, fIdx) in optionalUpdateFormFields"
-                    :key="fIdx"
+                    v-for="field in formFields"
+                    :key="field.field"
                     :label="field.title"
                     :value="field.field"
-                    :disabled="field.disabled"
                   />
                 </el-select>
-              </el-form-item>
-            </div>
-            <div class="mx-2"><el-form-item>的值设置为</el-form-item></div>
-            <div class="mr-2">
-              <el-form-item
-                :prop="`normalFormSetting.updateFormFields.${key}`"
-                :rules="{
-                  required: true,
-                  message: '值不能为空',
-                  trigger: 'blur'
-                }"
-              >
-                <el-input
-                  class="w-160px"
-                  v-model="configForm.normalFormSetting.updateFormFields![key]"
-                  placeholder="请输入"
-                  :disabled="!key"
-                />
-              </el-form-item>
-            </div>
-            <div class="mr-1 pt-1 cursor-pointer">
-              <el-form-item>
-                <Icon icon="ep:delete" :size="18" @click="deleteFormFieldSetting(key)" />
-              </el-form-item>
-            </div>
+              </div>
+            </el-card>
           </div>
-          <el-button type="primary" text @click="addFormFieldSetting()">
-            <Icon icon="ep:plus" class="mr-5px" />添加修改字段
+
+          <!-- 添加新的设置 -->
+          <el-button class="mt-6" type="primary" text @click="addFormSetting">
+            <Icon icon="ep:setting" class="mr-5px" />添加设置
           </el-button>
         </div>
       </el-form>
@@ -191,9 +242,19 @@
   </el-drawer>
 </template>
 <script setup lang="ts">
-import { SimpleFlowNode, NodeType, TriggerSetting, TRIGGER_TYPES, TriggerTypeEnum } from '../consts'
-import { useWatchNode, useDrawer, useNodeName, useFormFields } from '../node'
-import HttpRequestParamSetting from './components/HttpRequestParamSetting.vue'
+import {
+  SimpleFlowNode,
+  NodeType,
+  TriggerSetting,
+  TRIGGER_TYPES,
+  TriggerTypeEnum,
+  FormTriggerSetting,
+  DEFAULT_CONDITION_GROUP_VALUE
+} from '../consts'
+import { useWatchNode, useDrawer, useNodeName, useFormFields, getConditionShowText } from '../node'
+import HttpRequestSetting from './components/HttpRequestSetting.vue'
+import ConditionDialog from './components/ConditionDialog.vue'
+const { proxy } = getCurrentInstance() as any
 
 defineOptions({
   name: 'TriggerNodeConfig'
@@ -227,52 +288,153 @@ const configForm = ref<TriggerSetting>({
     body: [],
     response: []
   },
-  normalFormSetting: { updateFormFields: {} }
+  formSettings: [
+    {
+      conditionGroups: DEFAULT_CONDITION_GROUP_VALUE,
+      updateFormFields: {},
+      deleteFields: []
+    }
+  ]
 })
 // 流程表单字段
 const formFields = useFormFields()
 
 // 可选的修改的表单字段
 const optionalUpdateFormFields = computed(() => {
-  const usedFields = Object.keys(configForm.value.normalFormSetting?.updateFormFields || {})
   return formFields.map((field) => ({
     title: field.title,
     field: field.field,
-    disabled: usedFields.includes(field.field)
+    disabled: false
   }))
 })
 
-const updateFormFieldKey = (oldKey: string, newKey: string) => {
-  if (!configForm.value.normalFormSetting?.updateFormFields) return
-  const value = configForm.value.normalFormSetting.updateFormFields[oldKey]
-  delete configForm.value.normalFormSetting.updateFormFields[oldKey]
-  configForm.value.normalFormSetting.updateFormFields[newKey] = value
+let originalSetting: TriggerSetting | undefined
+
+/** 触发器类型改变了 */
+const changeTriggerType = () => {
+  if (configForm.value.type === TriggerTypeEnum.HTTP_REQUEST) {
+    configForm.value.httpRequestSetting =
+      originalSetting?.type === TriggerTypeEnum.HTTP_REQUEST && originalSetting.httpRequestSetting
+        ? originalSetting.httpRequestSetting
+        : {
+            url: '',
+            header: [],
+            body: [],
+            response: []
+          }
+    configForm.value.formSettings = undefined
+    return
+  }
+
+  if (configForm.value.type === TriggerTypeEnum.HTTP_CALLBACK) {
+    configForm.value.httpRequestSetting =
+      originalSetting?.type === TriggerTypeEnum.HTTP_CALLBACK && originalSetting.httpRequestSetting
+        ? originalSetting.httpRequestSetting
+        : {
+            url: '',
+            header: [],
+            body: [],
+            response: []
+          }
+    configForm.value.formSettings = undefined
+    return
+  }
+
+  if (configForm.value.type === TriggerTypeEnum.FORM_UPDATE) {
+    configForm.value.formSettings =
+      originalSetting?.type === TriggerTypeEnum.FORM_UPDATE && originalSetting.formSettings
+        ? originalSetting.formSettings
+        : [
+            {
+              conditionGroups: DEFAULT_CONDITION_GROUP_VALUE,
+              updateFormFields: {},
+              deleteFields: []
+            }
+          ]
+    configForm.value.httpRequestSetting = undefined
+    return
+  }
+
+  if (configForm.value.type === TriggerTypeEnum.FORM_DELETE) {
+    configForm.value.formSettings =
+      originalSetting?.type === TriggerTypeEnum.FORM_DELETE && originalSetting.formSettings
+        ? originalSetting.formSettings
+        : [
+            {
+              conditionGroups: DEFAULT_CONDITION_GROUP_VALUE,
+              updateFormFields: undefined,
+              deleteFields: []
+            }
+          ]
+    configForm.value.httpRequestSetting = undefined
+    return
+  }
 }
 
-/** 添加 HTTP 请求返回值设置项*/
-const addHttpResponseSetting = (responseSetting: Record<string, string>[]) => {
-  responseSetting.push({
-    key: '',
-    value: ''
+/** 添加新的修改表单设置 */
+const addFormSetting = () => {
+  configForm.value.formSettings!.push({
+    conditionGroups: DEFAULT_CONDITION_GROUP_VALUE,
+    updateFormFields: {},
+    deleteFields: []
   })
 }
 
-/** 删除 HTTP 请求返回值设置项 */
-const deleteHttpResponseSetting = (responseSetting: Record<string, string>[], index: number) => {
-  responseSetting.splice(index, 1)
+/** 删除修改表单设置 */
+const deleteFormSetting = (index: number) => {
+  configForm.value.formSettings!.splice(index, 1)
+}
+
+/** 添加条件配置 */
+const addFormSettingCondition = (index: number, formSetting: FormTriggerSetting) => {
+  const conditionDialog = proxy.$refs[`condition-${index}`][0]
+  conditionDialog.open(formSetting)
+}
+/** 删除条件配置 */
+const deleteFormSettingCondition = (formSetting: FormTriggerSetting) => {
+  formSetting.conditionType = undefined
+}
+/** 打开条件配置弹窗 */
+const openFormSettingCondition = (index: number, formSetting: FormTriggerSetting) => {
+  const conditionDialog = proxy.$refs[`condition-${index}`][0]
+  conditionDialog.open(formSetting)
+}
+/** 处理条件配置保存 */
+const handleConditionUpdate = (index: number, condition: any) => {
+  configForm.value.formSettings![index].conditionType = condition.conditionType
+  configForm.value.formSettings![index].conditionExpression = condition.conditionExpression
+  configForm.value.formSettings![index].conditionGroups = condition.conditionGroups
+}
+/** 条件配置展示 */
+const showConditionText = (formSetting: FormTriggerSetting) => {
+  return getConditionShowText(
+    formSetting.conditionType,
+    formSetting.conditionExpression,
+    formSetting.conditionGroups,
+    formFields
+  )
 }
 
-/** 添加修改表单设置项 */
-const addFormFieldSetting = () => {
-  if (configForm.value.normalFormSetting!.updateFormFields === undefined) {
-    configForm.value.normalFormSetting!.updateFormFields = {}
+/** 添加修改字段设置项 */
+const addFormFieldSetting = (formSetting: FormTriggerSetting) => {
+  if (!formSetting) return
+  if (!formSetting.updateFormFields) {
+    formSetting.updateFormFields = {}
   }
-  configForm.value.normalFormSetting!.updateFormFields[''] = undefined
+  formSetting.updateFormFields[''] = undefined
 }
-/** 删除修改表单设置项 */
-const deleteFormFieldSetting = (key: string) => {
-  if (!configForm.value.normalFormSetting?.updateFormFields) return
-  delete configForm.value.normalFormSetting.updateFormFields[key]
+/** 更新字段 KEY */
+const updateFormFieldKey = (formSetting: FormTriggerSetting, oldKey: string, newKey: string) => {
+  if (!formSetting?.updateFormFields) return
+  const value = formSetting.updateFormFields[oldKey]
+  delete formSetting.updateFormFields[oldKey]
+  formSetting.updateFormFields[newKey] = value
+}
+
+/** 删除修改字段设置项 */
+const deleteFormFieldSetting = (formSetting: FormTriggerSetting, key: string) => {
+  if (!formSetting?.updateFormFields) return
+  delete formSetting.updateFormFields[key]
 }
 
 /** 保存配置 */
@@ -285,10 +447,19 @@ const saveConfig = async () => {
   currentNode.value.name = nodeName.value!
   currentNode.value.showText = showText
   if (configForm.value.type === TriggerTypeEnum.HTTP_REQUEST) {
-    configForm.value.normalFormSetting = undefined
-  }
-  if (configForm.value.type === TriggerTypeEnum.UPDATE_NORMAL_FORM) {
+    configForm.value.formSettings = undefined
+  } else if (configForm.value.type === TriggerTypeEnum.FORM_UPDATE) {
     configForm.value.httpRequestSetting = undefined
+    // 清理删除字段相关的数据
+    configForm.value.formSettings?.forEach((setting) => {
+      setting.deleteFields = undefined
+    })
+  } else if (configForm.value.type === TriggerTypeEnum.FORM_DELETE) {
+    configForm.value.httpRequestSetting = undefined
+    // 清理修改字段相关的数据
+    configForm.value.formSettings?.forEach((setting) => {
+      setting.updateFormFields = undefined
+    })
   }
   currentNode.value.triggerSetting = configForm.value
   settingVisible.value = false
@@ -298,15 +469,27 @@ const saveConfig = async () => {
 /** 获取节点展示内容 */
 const getShowText = (): string => {
   let showText = ''
-  if (configForm.value.type === TriggerTypeEnum.HTTP_REQUEST) {
+  if (
+    configForm.value.type === TriggerTypeEnum.HTTP_REQUEST ||
+    configForm.value.type === TriggerTypeEnum.HTTP_CALLBACK
+  ) {
     showText = `${configForm.value.httpRequestSetting?.url}`
-  } else if (configForm.value.type === TriggerTypeEnum.UPDATE_NORMAL_FORM) {
-    const updatefields = Object.keys(configForm.value.normalFormSetting?.updateFormFields || {})
-    if (updatefields.length === 0) {
-      message.warning('请设置修改表单字段')
-    } else {
-      showText = '修改表单数据'
+  } else if (configForm.value.type === TriggerTypeEnum.FORM_UPDATE) {
+    for (const [index, setting] of configForm.value.formSettings!.entries()) {
+      if (!setting.updateFormFields || Object.keys(setting.updateFormFields).length === 0) {
+        message.warning(`请添加表单设置${index + 1}的修改字段`)
+        return ''
+      }
+    }
+    showText = '修改表单数据'
+  } else if (configForm.value.type === TriggerTypeEnum.FORM_DELETE) {
+    for (const [index, setting] of configForm.value.formSettings!.entries()) {
+      if (!setting.deleteFields || setting.deleteFields.length === 0) {
+        message.warning(`请选择表单设置${index + 1}要删除的字段`)
+        return ''
+      }
     }
+    showText = '删除表单数据'
   }
   return showText
 }
@@ -314,6 +497,7 @@ const getShowText = (): string => {
 /** 显示触发器节点配置, 由父组件传过来 */
 const showTriggerNodeConfig = (node: SimpleFlowNode) => {
   nodeName.value = node.name
+  originalSetting = node.triggerSetting ? JSON.parse(JSON.stringify(node.triggerSetting)) : {}
   if (node.triggerSetting) {
     configForm.value = {
       type: node.triggerSetting.type,
@@ -323,7 +507,13 @@ const showTriggerNodeConfig = (node: SimpleFlowNode) => {
         body: [],
         response: []
       },
-      normalFormSetting: node.triggerSetting.normalFormSetting || { updateFormFields: {} }
+      formSettings: node.triggerSetting.formSettings || [
+        {
+          conditionGroups: DEFAULT_CONDITION_GROUP_VALUE,
+          updateFormFields: {},
+          deleteFields: []
+        }
+      ]
     }
   }
 }

+ 198 - 123
src/components/SimpleProcessDesignerV2/src/nodes-config/UserTaskNodeConfig.vue

@@ -25,7 +25,7 @@
         <div class="divide-line"></div>
       </div>
     </template>
-    <div class="flex flex-items-center mb-3">
+    <div v-if="currentNode.type === NodeType.USER_TASK_NODE" class="flex flex-items-center mb-3">
       <span class="font-size-16px mr-3">审批类型 :</span>
       <el-radio-group v-model="approveType">
         <el-radio
@@ -39,10 +39,10 @@
       </el-radio-group>
     </div>
     <el-tabs type="border-card" v-model="activeTabName" v-if="approveType === ApproveType.USER">
-      <el-tab-pane label="审批人" name="user">
+      <el-tab-pane :label="`${nodeTypeName}人`" name="user">
         <div>
           <el-form ref="formRef" :model="configForm" label-position="top" :rules="formRules">
-            <el-form-item label="审批人设置" prop="candidateStrategy">
+            <el-form-item :label="`${nodeTypeName}人设置`" prop="candidateStrategy">
               <el-radio-group
                 v-model="configForm.candidateStrategy"
                 @change="changeCandidateStrategy"
@@ -61,7 +61,13 @@
               label="指定角色"
               prop="roleIds"
             >
-              <el-select filterable v-model="configForm.roleIds" clearable multiple style="width: 100%">
+              <el-select
+                filterable
+                v-model="configForm.roleIds"
+                clearable
+                multiple
+                style="width: 100%"
+              >
                 <el-option
                   v-for="item in roleOptions"
                   :key="item.id"
@@ -99,7 +105,13 @@
               prop="postIds"
               span="24"
             >
-              <el-select filterable v-model="configForm.postIds" clearable multiple style="width: 100%">
+              <el-select
+                filterable
+                v-model="configForm.postIds"
+                clearable
+                multiple
+                style="width: 100%"
+              >
                 <el-option
                   v-for="item in postOptions"
                   :key="item.id"
@@ -114,7 +126,13 @@
               prop="userIds"
               span="24"
             >
-              <el-select filterable v-model="configForm.userIds" clearable multiple style="width: 100%">
+              <el-select
+                filterable
+                v-model="configForm.userIds"
+                clearable
+                multiple
+                style="width: 100%"
+              >
                 <el-option
                   v-for="item in userOptions"
                   :key="item.id"
@@ -128,7 +146,13 @@
               label="指定用户组"
               prop="userGroups"
             >
-              <el-select filterable v-model="configForm.userGroups" clearable multiple style="width: 100%">
+              <el-select
+                filterable
+                v-model="configForm.userGroups"
+                clearable
+                multiple
+                style="width: 100%"
+              >
                 <el-option
                   v-for="item in userGroupOptions"
                   :key="item.id"
@@ -201,7 +225,7 @@
                 style="width: 100%"
               />
             </el-form-item>
-            <el-form-item label="多人审批方式" prop="approveMethod">
+            <el-form-item :label="`多人${nodeTypeName}方式`" prop="approveMethod">
               <el-radio-group v-model="configForm.approveMethod" @change="approveMethodChanged">
                 <div class="flex-col">
                   <div
@@ -230,93 +254,102 @@
               </el-radio-group>
             </el-form-item>
 
-            <el-divider content-position="left">审批人拒绝时</el-divider>
-            <el-form-item prop="rejectHandlerType">
-              <el-radio-group v-model="configForm.rejectHandlerType">
-                <div class="flex-col">
-                  <div v-for="(item, index) in REJECT_HANDLER_TYPES" :key="index">
-                    <el-radio :key="item.value" :value="item.value" :label="item.label" />
+            <div v-if="currentNode.type === NodeType.USER_TASK_NODE">
+              <el-divider content-position="left">审批人拒绝时</el-divider>
+              <el-form-item prop="rejectHandlerType">
+                <el-radio-group v-model="configForm.rejectHandlerType">
+                  <div class="flex-col">
+                    <div v-for="(item, index) in REJECT_HANDLER_TYPES" :key="index">
+                      <el-radio :key="item.value" :value="item.value" :label="item.label" />
+                    </div>
                   </div>
-                </div>
-              </el-radio-group>
-            </el-form-item>
-            <el-form-item
-              v-if="configForm.rejectHandlerType == RejectHandlerType.RETURN_USER_TASK"
-              label="驳回节点"
-              prop="returnNodeId"
-            >
-              <el-select filterable v-model="configForm.returnNodeId" clearable style="width: 100%">
-                <el-option
-                  v-for="item in returnTaskList"
-                  :key="item.id"
-                  :label="item.name"
-                  :value="item.id"
-                />
-              </el-select>
-            </el-form-item>
-
-            <el-divider content-position="left">审批人超时未处理时</el-divider>
-            <el-form-item label="启用开关" prop="timeoutHandlerEnable">
-              <el-switch
-                v-model="configForm.timeoutHandlerEnable"
-                active-text="开启"
-                inactive-text="关闭"
-                @change="timeoutHandlerChange"
-              />
-            </el-form-item>
-            <el-form-item
-              label="执行动作"
-              prop="timeoutHandlerType"
-              v-if="configForm.timeoutHandlerEnable"
-            >
-              <el-radio-group
-                v-model="configForm.timeoutHandlerType"
-                @change="timeoutHandlerTypeChanged"
+                </el-radio-group>
+              </el-form-item>
+              <el-form-item
+                v-if="configForm.rejectHandlerType == RejectHandlerType.RETURN_USER_TASK"
+                label="驳回节点"
+                prop="returnNodeId"
               >
-                <el-radio-button
-                  v-for="item in TIMEOUT_HANDLER_TYPES"
-                  :key="item.value"
-                  :value="item.value"
-                  :label="item.label"
+                <el-select
+                  filterable
+                  v-model="configForm.returnNodeId"
+                  clearable
+                  style="width: 100%"
+                >
+                  <el-option
+                    v-for="item in returnTaskList"
+                    :key="item.id"
+                    :label="item.name"
+                    :value="item.id"
+                  />
+                </el-select>
+              </el-form-item>
+            </div>
+
+            <div v-if="currentNode.type === NodeType.USER_TASK_NODE">
+              <el-divider content-position="left">审批人超时未处理时</el-divider>
+              <el-form-item label="启用开关" prop="timeoutHandlerEnable">
+                <el-switch
+                  v-model="configForm.timeoutHandlerEnable"
+                  active-text="开启"
+                  inactive-text="关闭"
+                  @change="timeoutHandlerChange"
                 />
-              </el-radio-group>
-            </el-form-item>
-            <el-form-item label="超时时间设置" v-if="configForm.timeoutHandlerEnable">
-              <span class="mr-2">当超过</span>
-              <el-form-item prop="timeDuration">
-                <el-input-number
+              </el-form-item>
+              <el-form-item
+                label="执行动作"
+                prop="timeoutHandlerType"
+                v-if="configForm.timeoutHandlerEnable"
+              >
+                <el-radio-group
+                  v-model="configForm.timeoutHandlerType"
+                  @change="timeoutHandlerTypeChanged"
+                >
+                  <el-radio-button
+                    v-for="item in TIMEOUT_HANDLER_TYPES"
+                    :key="item.value"
+                    :value="item.value"
+                    :label="item.label"
+                  />
+                </el-radio-group>
+              </el-form-item>
+              <el-form-item label="超时时间设置" v-if="configForm.timeoutHandlerEnable">
+                <span class="mr-2">当超过</span>
+                <el-form-item prop="timeDuration">
+                  <el-input-number
+                    class="mr-2"
+                    :style="{ width: '100px' }"
+                    v-model="configForm.timeDuration"
+                    :min="1"
+                    controls-position="right"
+                  />
+                </el-form-item>
+                <el-select
+                  filterable
+                  v-model="timeUnit"
                   class="mr-2"
                   :style="{ width: '100px' }"
-                  v-model="configForm.timeDuration"
-                  :min="1"
-                  controls-position="right"
-                />
+                  @change="timeUnitChange"
+                >
+                  <el-option
+                    v-for="item in TIME_UNIT_TYPES"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
+                  />
+                </el-select>
+                未处理
               </el-form-item>
-              <el-select
-                filterable
-                v-model="timeUnit"
-                class="mr-2"
-                :style="{ width: '100px' }"
-                @change="timeUnitChange"
+              <el-form-item
+                label="最大提醒次数"
+                prop="maxRemindCount"
+                v-if="configForm.timeoutHandlerEnable && configForm.timeoutHandlerType === 1"
               >
-                <el-option
-                  v-for="item in TIME_UNIT_TYPES"
-                  :key="item.value"
-                  :label="item.label"
-                  :value="item.value"
-                />
-              </el-select>
-              未处理
-            </el-form-item>
-            <el-form-item
-              label="最大提醒次数"
-              prop="maxRemindCount"
-              v-if="configForm.timeoutHandlerEnable && configForm.timeoutHandlerType === 1"
-            >
-              <el-input-number v-model="configForm.maxRemindCount" :min="1" :max="10" />
-            </el-form-item>
+                <el-input-number v-model="configForm.maxRemindCount" :min="1" :max="10" />
+              </el-form-item>
+            </div>
 
-            <el-divider content-position="left">审批人为空时</el-divider>
+            <el-divider content-position="left">{{ nodeTypeName }}人为空时</el-divider>
             <el-form-item prop="assignEmptyHandlerType">
               <el-radio-group v-model="configForm.assignEmptyHandlerType">
                 <div class="flex-col">
@@ -348,30 +381,44 @@
               </el-select>
             </el-form-item>
 
-            <el-divider content-position="left">审批人与提交人为同一人时</el-divider>
-            <el-form-item prop="assignStartUserHandlerType">
-              <el-radio-group v-model="configForm.assignStartUserHandlerType">
-                <div class="flex-col">
-                  <div v-for="(item, index) in ASSIGN_START_USER_HANDLER_TYPES" :key="index">
-                    <el-radio :key="item.value" :value="item.value" :label="item.label" />
+            <div v-if="currentNode.type === NodeType.USER_TASK_NODE">
+              <el-divider content-position="left">审批人与提交人为同一人时</el-divider>
+              <el-form-item prop="assignStartUserHandlerType">
+                <el-radio-group v-model="configForm.assignStartUserHandlerType">
+                  <div class="flex-col">
+                    <div v-for="(item, index) in ASSIGN_START_USER_HANDLER_TYPES" :key="index">
+                      <el-radio :key="item.value" :value="item.value" :label="item.label" />
+                    </div>
                   </div>
-                </div>
-              </el-radio-group>
-            </el-form-item>
+                </el-radio-group>
+              </el-form-item>
+            </div>
 
-            <el-divider content-position="left">是否需要签名</el-divider>
-            <el-form-item prop="signEnable">
-              <el-switch v-model="configForm.signEnable" active-text="是" inactive-text="否" />
-            </el-form-item>
+            <div v-if="currentNode.type === NodeType.USER_TASK_NODE">
+              <el-divider content-position="left">是否需要签名</el-divider>
+              <el-form-item prop="signEnable">
+                <el-switch v-model="configForm.signEnable" active-text="是" inactive-text="否" />
+              </el-form-item>
+            </div>
 
-            <el-divider content-position="left">审批意见</el-divider>
-            <el-form-item prop="reasonRequire">
-              <el-switch v-model="configForm.reasonRequire" active-text="必填" inactive-text="非必填" />
-            </el-form-item>
+            <div v-if="currentNode.type === NodeType.USER_TASK_NODE">
+              <el-divider content-position="left">审批意见</el-divider>
+              <el-form-item prop="reasonRequire">
+                <el-switch
+                  v-model="configForm.reasonRequire"
+                  active-text="必填"
+                  inactive-text="非必填"
+                />
+              </el-form-item>
+            </div>
           </el-form>
         </div>
       </el-tab-pane>
-      <el-tab-pane label="操作按钮设置" name="buttons">
+      <el-tab-pane
+        label="操作按钮设置"
+        v-if="currentNode.type === NodeType.USER_TASK_NODE"
+        name="buttons"
+      >
         <div class="button-setting-pane">
           <div class="button-setting-desc">操作按钮</div>
           <div class="button-setting-title">
@@ -407,9 +454,15 @@
           <div class="field-permit-title">
             <div class="setting-title-label first-title"> 字段名称 </div>
             <div class="other-titles">
-              <span class="setting-title-label">只读</span>
-              <span class="setting-title-label">可编辑</span>
-              <span class="setting-title-label">隐藏</span>
+              <span class="setting-title-label cursor-pointer" @click="updatePermission('READ')">
+                只读
+              </span>
+              <span class="setting-title-label cursor-pointer" @click="updatePermission('WRITE')">
+                可编辑
+              </span>
+              <span class="setting-title-label cursor-pointer" @click="updatePermission('NONE')">
+                隐藏
+              </span>
             </div>
           </div>
           <div
@@ -448,7 +501,11 @@
         </div>
       </el-tab-pane>
       <el-tab-pane label="监听器" name="listener">
-        <UserTaskListener ref="userTaskListenerRef" v-model="configForm" :form-field-options="formFieldOptions" />
+        <UserTaskListener
+          ref="userTaskListenerRef"
+          v-model="configForm"
+          :form-field-options="formFieldOptions"
+        />
       </el-tab-pane>
     </el-tabs>
     <template #footer>
@@ -485,7 +542,8 @@ import {
   ASSIGN_EMPTY_HANDLER_TYPES,
   AssignEmptyHandlerType,
   FieldPermissionType,
-  ProcessVariableEnum
+  ProcessVariableEnum,
+  TRANSACTOR_DEFAULT_BUTTON_SETTING
 } from '../consts'
 
 import {
@@ -588,7 +646,7 @@ const {
   handleCandidateParam,
   parseCandidateParam,
   getShowText
-} = useNodeForm(NodeType.USER_TASK_NODE)
+} = useNodeForm(currentNode.value.type)
 const configForm = tempConfigForm as Ref<UserTaskFormType>
 
 // 改变审批人设置策略
@@ -627,7 +685,12 @@ const {
 
 const userTaskListenerRef = ref()
 
-// 保存配置
+/** 节点类型名称 */
+const nodeTypeName = computed(() => {
+  return currentNode.value.type === NodeType.TRANSACTOR_NODE ? '办理' : '审批'
+})
+
+/** 保存配置 */
 const saveConfig = async () => {
   // activeTabName.value = 'user'
   // 设置审批节点名称
@@ -713,7 +776,7 @@ const saveConfig = async () => {
   return true
 }
 
-// 显示审批节点配置, 由父组件传过来
+/** 显示审批节点配置, 由父组件传过来 */
 const showUserTaskNodeConfig = (node: SimpleFlowNode) => {
   nodeName.value = node.name
   // 1 审批类型
@@ -733,13 +796,13 @@ const showUserTaskNodeConfig = (node: SimpleFlowNode) => {
     configForm.value.approveRatio = node.approveRatio!
   }
   // 2.3 设置审批拒绝处理
-  configForm.value.rejectHandlerType = node.rejectHandler!.type
+  configForm.value.rejectHandlerType = node.rejectHandler?.type
   configForm.value.returnNodeId = node.rejectHandler?.returnNodeId
   const matchNodeList = []
   emits('find:returnTaskNodes', matchNodeList)
   returnTaskList.value = matchNodeList
   // 2.4 设置审批超时处理
-  configForm.value.timeoutHandlerEnable = node.timeoutHandler!.enable
+  configForm.value.timeoutHandlerEnable = node.timeoutHandler?.enable
   if (node.timeoutHandler?.enable && node.timeoutHandler?.timeDuration) {
     const strTimeDuration = node.timeoutHandler.timeDuration
     let parseTime = strTimeDuration.slice(2, strTimeDuration.length - 1)
@@ -755,7 +818,11 @@ const showUserTaskNodeConfig = (node: SimpleFlowNode) => {
   // 2.6 设置用户任务的审批人与发起人相同时
   configForm.value.assignStartUserHandlerType = node.assignStartUserHandlerType
   // 3. 操作按钮设置
-  buttonsSetting.value = cloneDeep(node.buttonsSetting) || DEFAULT_BUTTON_SETTING
+  buttonsSetting.value =
+    cloneDeep(node.buttonsSetting) ||
+    (node.type === NodeType.TRANSACTOR_NODE
+      ? TRANSACTOR_DEFAULT_BUTTON_SETTING
+      : DEFAULT_BUTTON_SETTING)
   // 4. 表单字段权限配置
   getNodeConfigFormFields(node.fieldsPermission)
   // 5. 监听器
@@ -773,7 +840,7 @@ const showUserTaskNodeConfig = (node: SimpleFlowNode) => {
     header: node.taskAssignListener?.header ?? [],
     body: node.taskAssignListener?.body ?? []
   }
- // 5.3 完成任务
+  // 5.3 完成任务
   configForm.value.taskCompleteListenerEnable = node.taskCompleteListener?.enable
   configForm.value.taskCompleteListenerPath = node.taskCompleteListener?.path
   configForm.value.taskCompleteListener = {
@@ -788,9 +855,7 @@ const showUserTaskNodeConfig = (node: SimpleFlowNode) => {
 
 defineExpose({ openDrawer, showUserTaskNodeConfig }) // 暴露方法给父组件
 
-/**
- * @description 操作按钮设置
- */
+/** 操作按钮设置 */
 function useButtonsSetting() {
   const buttonsSetting = ref<ButtonSetting[]>()
   // 操作按钮显示名称可编辑
@@ -811,9 +876,7 @@ function useButtonsSetting() {
   }
 }
 
-/**
- * @description 审批人超时未处理配置
- */
+/** 审批人超时未处理配置 */
 function useTimeoutHandler() {
   // 时间单位
   const timeUnit = ref(TimeUnitType.HOUR)
@@ -896,6 +959,18 @@ function useTimeoutHandler() {
     cTimeoutMaxRemindCount
   }
 }
+
+/** 批量更新权限 */
+const updatePermission = (type: string) => {
+  fieldsPermissionConfig.value.forEach((field) => {
+    field.permission =
+      type === 'READ'
+        ? FieldPermissionType.READ
+        : type === 'WRITE'
+          ? FieldPermissionType.WRITE
+          : FieldPermissionType.NONE
+  })
+}
 </script>
 
 <style lang="scss" scoped>

+ 308 - 0
src/components/SimpleProcessDesignerV2/src/nodes-config/components/ConditionDialog.vue

@@ -0,0 +1,308 @@
+<!-- TODO @jason:有可能,它里面套 Condition 么?  -->
+<!-- TODO 怕影响其它节点功能,后面看看如何如何复用 Condtion --> 
+<template>
+  <Dialog v-model="dialogVisible" title="条件配置" width="600px" :fullscreen="false">
+    <div class="h-410px">
+      <el-scrollbar wrap-class="h-full">
+        <el-form ref="formRef" :model="condition" :rules="formRules" label-position="top">
+          <el-form-item label="配置方式" prop="conditionType">
+            <el-radio-group v-model="condition.conditionType" @change="changeConditionType">
+              <el-radio
+                v-for="(dict, indexConditionType) in conditionConfigTypes"
+                :key="indexConditionType"
+                :value="dict.value"
+                :label="dict.value"
+              >
+                {{ dict.label }}
+              </el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item
+            v-if="condition.conditionType === ConditionType.RULE && condition.conditionGroups"
+            label="条件规则"
+          >
+            <div class="condition-group-tool">
+              <div class="flex items-center">
+                <div class="mr-4">条件组关系</div>
+                <el-switch
+                  v-model="condition.conditionGroups.and"
+                  inline-prompt
+                  active-text="且"
+                  inactive-text="或"
+                />
+              </div>
+            </div>
+            <el-space direction="vertical" :spacer="condition.conditionGroups.and ? '且' : '或'">
+              <el-card
+                class="condition-group"
+                style="width: 530px"
+                v-for="(equation, cIdx) in condition.conditionGroups.conditions"
+                :key="cIdx"
+              >
+                <div
+                  class="condition-group-delete"
+                  v-if="condition.conditionGroups.conditions.length > 1"
+                >
+                  <Icon
+                    color="#0089ff"
+                    icon="ep:circle-close-filled"
+                    :size="18"
+                    @click="deleteConditionGroup(condition.conditionGroups.conditions, cIdx)"
+                  />
+                </div>
+                <template #header>
+                  <div class="flex items-center justify-between">
+                    <div>条件组</div>
+                    <div class="flex">
+                      <div class="mr-4">规则关系</div>
+                      <el-switch
+                        v-model="equation.and"
+                        inline-prompt
+                        active-text="且"
+                        inactive-text="或"
+                      />
+                    </div>
+                  </div>
+                </template>
+
+                <div class="flex pt-2" v-for="(rule, rIdx) in equation.rules" :key="rIdx">
+                  <div class="mr-2">
+                    <el-form-item
+                      :prop="`conditionGroups.conditions.${cIdx}.rules.${rIdx}.leftSide`"
+                      :rules="{
+                        required: true,
+                        message: '左值不能为空',
+                        trigger: 'change'
+                      }"
+                    >
+                      <el-select style="width: 160px" v-model="rule.leftSide">
+                        <el-option
+                          v-for="(field, fIdx) in fieldOptions"
+                          :key="fIdx"
+                          :label="field.title"
+                          :value="field.field"
+                          :disabled="!field.required"
+                        />
+                      </el-select>
+                    </el-form-item>
+                  </div>
+                  <div class="mr-2">
+                    <el-select v-model="rule.opCode" style="width: 100px">
+                      <el-option
+                        v-for="operator in COMPARISON_OPERATORS"
+                        :key="operator.value"
+                        :label="operator.label"
+                        :value="operator.value"
+                      />
+                    </el-select>
+                  </div>
+                  <div class="mr-2">
+                    <el-form-item
+                      :prop="`conditionGroups.conditions.${cIdx}.rules.${rIdx}.rightSide`"
+                      :rules="{
+                        required: true,
+                        message: '右值不能为空',
+                        trigger: 'blur'
+                      }"
+                    >
+                      <el-input v-model="rule.rightSide" style="width: 160px" />
+                    </el-form-item>
+                  </div>
+                  <div
+                    class="cursor-pointer mr-1 flex items-center"
+                    v-if="equation.rules.length > 1"
+                  >
+                    <Icon
+                      icon="ep:delete"
+                      :size="18"
+                      @click="deleteConditionRule(equation, rIdx)"
+                    />
+                  </div>
+                  <div class="cursor-pointer flex items-center">
+                    <Icon icon="ep:plus" :size="18" @click="addConditionRule(equation, rIdx)" />
+                  </div>
+                </div>
+              </el-card>
+            </el-space>
+            <div title="添加条件组" class="mt-4 cursor-pointer">
+              <Icon
+                color="#0089ff"
+                icon="ep:plus"
+                :size="24"
+                @click="addConditionGroup(condition.conditionGroups?.conditions)"
+              />
+            </div>
+          </el-form-item>
+          <el-form-item
+            v-if="condition.conditionType === ConditionType.EXPRESSION"
+            label="条件表达式"
+            prop="conditionExpression"
+          >
+            <el-input
+              type="textarea"
+              v-model="condition.conditionExpression"
+              clearable
+              style="width: 100%"
+            />
+          </el-form-item>
+        </el-form>
+      </el-scrollbar>
+    </div>
+    <template #footer>
+      <el-button type="primary" @click="submitForm">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+
+<script setup lang="ts">
+import {
+  COMPARISON_OPERATORS,
+  CONDITION_CONFIG_TYPES,
+  ConditionType,
+  ConditionGroup,
+  DEFAULT_CONDITION_GROUP_VALUE
+} from '../../consts'
+import { BpmModelFormType } from '@/utils/constants'
+import { useFormFieldsAndStartUser } from '../../node'
+defineOptions({
+  name: 'ConditionDialog'
+})
+
+const condition = ref<{
+  conditionType: ConditionType
+  conditionExpression?: string
+  conditionGroups?: ConditionGroup
+}>({
+  conditionType: ConditionType.RULE,
+  conditionGroups: DEFAULT_CONDITION_GROUP_VALUE
+})
+
+const emit = defineEmits<{
+  updateCondition: [condition: object]
+}>()
+const message = useMessage() // 消息弹窗
+const dialogVisible = ref(false) // 弹窗的是否展示
+
+const formType = inject<Ref<number>>('formType') // 表单类型
+const conditionConfigTypes = computed(() => {
+  return CONDITION_CONFIG_TYPES.filter((item) => {
+    // 业务表单暂时去掉条件规则选项
+    if (formType?.value === BpmModelFormType.CUSTOM && item.value === ConditionType.RULE) {
+      return false
+    } else {
+      return true
+    }
+  })
+})
+
+/** 条件规则可选择的表单字段 */
+const fieldOptions = useFormFieldsAndStartUser()
+
+// 表单校验规则
+const formRules = reactive({
+  conditionType: [{ required: true, message: '配置方式不能为空', trigger: 'blur' }],
+  conditionExpression: [{ required: true, message: '条件表达式不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+
+/** 切换条件配置方式 */
+const changeConditionType = () => {
+  if (condition.value.conditionType === ConditionType.RULE) {
+    if (!condition.value.conditionGroups) {
+      condition.value.conditionGroups = DEFAULT_CONDITION_GROUP_VALUE
+    }
+  }
+}
+const deleteConditionGroup = (conditions, index) => {
+  conditions.splice(index, 1)
+}
+
+const deleteConditionRule = (condition, index) => {
+  condition.rules.splice(index, 1)
+}
+
+const addConditionRule = (condition, index) => {
+  const rule = {
+    opCode: '==',
+    leftSide: '',
+    rightSide: ''
+  }
+  condition.rules.splice(index + 1, 0, rule)
+}
+
+const addConditionGroup = (conditions) => {
+  const condition = {
+    and: true,
+    rules: [
+      {
+        opCode: '==',
+        leftSide: '',
+        rightSide: ''
+      }
+    ]
+  }
+  conditions.push(condition)
+}
+
+/** 保存条件设置 */
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) {
+    message.warning('请完善条件规则')
+    return
+  }
+  dialogVisible.value = false
+  // 设置完的条件传递给父组件
+  emit('updateCondition', condition.value)
+}
+
+const open = (conditionObj: any | undefined) => {
+  if (conditionObj) {
+    condition.value.conditionType = conditionObj.conditionType
+    condition.value.conditionExpression = conditionObj.conditionExpression
+    condition.value.conditionGroups = conditionObj.conditionGroups
+  }
+  dialogVisible.value = true
+}
+
+defineExpose({ open })
+</script>
+
+<style lang="scss" scoped>
+.condition-group-tool {
+  display: flex;
+  justify-content: space-between;
+  width: 500px;
+  margin-bottom: 20px;
+}
+
+.condition-group {
+  position: relative;
+
+  &:hover {
+    border-color: #0089ff;
+
+    .condition-group-delete {
+      opacity: 1;
+    }
+  }
+
+  .condition-group-delete {
+    position: absolute;
+    top: 0;
+    left: 0;
+    display: flex;
+    cursor: pointer;
+    opacity: 0;
+  }
+}
+
+::v-deep(.el-card__header) {
+  padding: 8px var(--el-card-padding);
+  border-bottom: 1px solid var(--el-card-border-color);
+  box-sizing: border-box;
+}
+</style>

+ 7 - 3
src/components/SimpleProcessDesignerV2/src/nodes-config/components/HttpRequestParamSetting.vue

@@ -1,5 +1,5 @@
 <template>
-  <el-form-item label="请求头">
+  <el-form-item label-position="top" label="请求头">
     <div class="flex pt-2" v-for="(item, index) in props.header" :key="index">
       <div class="mr-2">
         <el-form-item
@@ -69,7 +69,7 @@
       <Icon icon="ep:plus" class="mr-5px" />添加一行
     </el-button>
   </el-form-item>
-  <el-form-item label="请求体">
+  <el-form-item label-position="top" label="请求体">
     <div class="flex pt-2" v-for="(item, index) in props.body" :key="index">
       <div class="mr-2">
         <el-form-item
@@ -141,7 +141,11 @@
   </el-form-item>
 </template>
 <script setup lang="ts">
-import { HttpRequestParam, BPM_HTTP_REQUEST_PARAM_TYPES, BpmHttpRequestParamTypeEnum } from '../../consts'
+import {
+  HttpRequestParam,
+  BPM_HTTP_REQUEST_PARAM_TYPES,
+  BpmHttpRequestParamTypeEnum
+} from '../../consts'
 import { useFormFieldsAndStartUser } from '../../node'
 defineOptions({
   name: 'HttpRequestParamSetting'

+ 127 - 0
src/components/SimpleProcessDesignerV2/src/nodes-config/components/HttpRequestSetting.vue

@@ -0,0 +1,127 @@
+<template>
+  <el-form-item>
+    <el-alert
+      title="仅支持 POST 请求,以请求体方式接收参数"
+      type="warning"
+      show-icon
+      :closable="false"
+    />
+  </el-form-item>
+  <!-- 请求地址-->
+  <el-form-item
+    label-position="top"
+    label="请求地址"
+    :prop="`${formItemPrefix}.url`"
+    :rules="{
+      required: true,
+      message: '请求地址不能为空',
+      trigger: 'blur'
+    }"
+  >
+    <el-input v-model="setting.url" />
+  </el-form-item>
+  <!-- 请求头,请求体设置-->
+  <HttpRequestParamSetting :header="setting.header" :body="setting.body" :bind="formItemPrefix" />
+  <!-- 返回值设置-->
+  <div v-if="responseEnable">
+    <el-form-item label="返回值" label-position="top">
+      <el-alert
+        title="通过请求返回值, 可以修改流程表单的值"
+        type="warning"
+        show-icon
+        :closable="false"
+      />
+    </el-form-item>
+    <el-form-item>
+      <div class="flex pt-2" v-for="(item, index) in setting.response" :key="index">
+        <div class="mr-2">
+          <el-form-item
+            :prop="`${formItemPrefix}.response.${index}.key`"
+            :rules="{
+              required: true,
+              message: '表单字段不能为空',
+              trigger: 'blur'
+            }"
+          >
+            <el-select class="w-160px!" v-model="item.key" placeholder="请选择表单字段">
+              <el-option
+                v-for="(field, fIdx) in formFields"
+                :key="fIdx"
+                :label="field.title"
+                :value="field.field"
+                :disabled="!field.required"
+              />
+            </el-select>
+          </el-form-item>
+        </div>
+        <div class="mr-2">
+          <el-form-item
+            :prop="`${formItemPrefix}.response.${index}.value`"
+            :rules="{
+              required: true,
+              message: '请求返回字段不能为空',
+              trigger: 'blur'
+            }"
+          >
+            <el-input class="w-160px" v-model="item.value" placeholder="请求返回字段" />
+          </el-form-item>
+        </div>
+        <div class="mr-1 pt-1 cursor-pointer">
+          <Icon
+            icon="ep:delete"
+            :size="18"
+            @click="deleteHttpResponseSetting(setting.response!, index)"
+          />
+        </div>
+      </div>
+      <el-button type="primary" text @click="addHttpResponseSetting(setting.response!)">
+        <Icon icon="ep:plus" class="mr-5px" />添加一行
+      </el-button>
+    </el-form-item>
+  </div>
+</template>
+<script setup lang="ts">
+import HttpRequestParamSetting from './HttpRequestParamSetting.vue'
+import { useFormFields } from '../../node'
+
+const props = defineProps({
+  setting: {
+    type: Object,
+    required: true
+  },
+  responseEnable: {
+    type: Boolean,
+    required: true
+  },
+  formItemPrefix: {
+    type: String,
+    required: true
+  }
+})
+const { setting } = toRefs(props)
+const emits = defineEmits(['update:setting'])
+watch(
+  () => setting,
+  (val) => {
+    emits('update:setting', val)
+  }
+)
+
+/** 流程表单字段 */
+const formFields = useFormFields()
+
+/** 添加 HTTP 请求返回值设置项 */
+const addHttpResponseSetting = (responseSetting: Record<string, string>[]) => {
+  responseSetting.push({
+    key: '',
+    value: ''
+  })
+}
+
+/** 删除 HTTP 请求返回值设置项 */
+const deleteHttpResponseSetting = (responseSetting: Record<string, string>[], index: number) => {
+  responseSetting.splice(index, 1)
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 106 - 0
src/components/SimpleProcessDesignerV2/src/nodes/ChildProcessNode.vue

@@ -0,0 +1,106 @@
+<template>
+  <div class="node-wrapper">
+    <div class="node-container">
+      <div
+        class="node-box"
+        :class="[
+          { 'node-config-error': !currentNode.showText },
+          `${useTaskStatusClass(currentNode?.activityStatus)}`
+        ]"
+      >
+        <div class="node-title-container">
+          <div
+            :class="`node-title-icon ${currentNode.childProcessSetting?.async === true ? 'async-child-process' : 'child-process'}`"
+          >
+            <span
+              :class="`iconfont ${currentNode.childProcessSetting?.async === true ? 'icon-async-child-process' : 'icon-child-process'}`"
+            >
+            </span>
+          </div>
+          <input
+            v-if="!readonly && showInput"
+            type="text"
+            class="editable-title-input"
+            @blur="blurEvent()"
+            v-mountedFocus
+            v-model="currentNode.name"
+            :placeholder="currentNode.name"
+          />
+          <div v-else class="node-title" @click="clickTitle">
+            {{ currentNode.name }}
+          </div>
+        </div>
+        <div class="node-content" @click="openNodeConfig">
+          <div class="node-text" :title="currentNode.showText" v-if="currentNode.showText">
+            {{ currentNode.showText }}
+          </div>
+          <div class="node-text" v-else>
+            {{ NODE_DEFAULT_TEXT.get(NodeType.CHILD_PROCESS_NODE) }}
+          </div>
+          <Icon v-if="!readonly" icon="ep:arrow-right-bold" />
+        </div>
+        <div v-if="!readonly" class="node-toolbar">
+          <div class="toolbar-icon"
+            ><Icon color="#0089ff" icon="ep:circle-close-filled" :size="18" @click="deleteNode"
+          /></div>
+        </div>
+      </div>
+
+      <!-- 传递子节点给添加节点组件。会在子节点前面添加节点 -->
+      <NodeHandler
+        v-if="currentNode"
+        v-model:child-node="currentNode.childNode"
+        :current-node="currentNode"
+      />
+    </div>
+    <ChildProcessNodeConfig
+      v-if="!readonly && currentNode"
+      ref="nodeSetting"
+      :flow-node="currentNode"
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
+import NodeHandler from '../NodeHandler.vue'
+import { useNodeName2, useWatchNode, useTaskStatusClass } from '../node'
+import ChildProcessNodeConfig from '../nodes-config/ChildProcessNodeConfig.vue'
+
+defineOptions({
+  name: 'ChildProcessNode'
+})
+const props = defineProps({
+  flowNode: {
+    type: Object as () => SimpleFlowNode,
+    required: true
+  }
+})
+// 定义事件,更新父组件。
+const emits = defineEmits<{
+  'update:flowNode': [node: SimpleFlowNode | undefined]
+}>()
+// 是否只读
+const readonly = inject<Boolean>('readonly')
+// 监控节点的变化
+const currentNode = useWatchNode(props)
+// 节点名称编辑
+const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.CHILD_PROCESS_NODE)
+const nodeSetting = ref()
+
+// 打开节点配置
+const openNodeConfig = () => {
+  if (readonly) {
+    return
+  }
+  nodeSetting.value.showChildProcessNodeConfig(currentNode.value)
+  nodeSetting.value.openDrawer()
+}
+
+// 删除节点。更新当前节点为孩子节点
+const deleteNode = () => {
+  emits('update:flowNode', currentNode.value.childNode)
+}
+</script>
+
+<style scoped></style>

+ 9 - 2
src/components/SimpleProcessDesignerV2/src/nodes/UserTaskNode.vue

@@ -9,7 +9,14 @@
         ]"
       >
         <div class="node-title-container">
-          <div class="node-title-icon user-task"><span class="iconfont icon-approve"></span></div>
+          <div
+            :class="`node-title-icon ${currentNode.type === NodeType.TRANSACTOR_NODE ? 'transactor-task' : 'user-task'}`"
+          >
+            <span
+              :class="`iconfont ${currentNode.type === NodeType.TRANSACTOR_NODE ? 'icon-transactor' : 'icon-approve'}`"
+            >
+            </span>
+          </div>
           <input
             v-if="!readonly && showInput"
             type="text"
@@ -28,7 +35,7 @@
             {{ currentNode.showText }}
           </div>
           <div class="node-text" v-else>
-            {{ NODE_DEFAULT_TEXT.get(NodeType.USER_TASK_NODE) }}
+            {{ NODE_DEFAULT_TEXT.get(currentNode.type) }}
           </div>
           <Icon icon="ep:arrow-right-bold" v-if="!readonly" />
         </div>

BIN
src/components/SimpleProcessDesignerV2/theme/iconfont.ttf


BIN
src/components/SimpleProcessDesignerV2/theme/iconfont.woff


BIN
src/components/SimpleProcessDesignerV2/theme/iconfont.woff2


+ 34 - 2
src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss

@@ -177,6 +177,18 @@
     color: #ca3a31
   }
 
+  .transactor {
+    color: #330099;
+  }
+
+  .child-process {
+    color: #996633;
+  }
+
+  .async-child-process {
+    color: #006666;
+  }
+
   .handler-item-text {
     margin-top: 4px;
     width: 80px;
@@ -290,10 +302,22 @@
           &.trigger-node {
             color: #3373d2;
           }
-          
+
           &.router-node {
             color: #ca3a31
           }
+
+          &.transactor-task {
+            color: #330099;
+          }
+
+          &.child-process {
+            color: #996633;
+          }
+
+          &.async-child-process {
+            color: #006666;
+          }
         }
 
         .node-title {
@@ -777,7 +801,7 @@
   content: "\e7eb";
 }
 
-.icon-handle:before {
+.icon-transactor:before {
   content: "\e61c";
 }
 
@@ -792,3 +816,11 @@
 .icon-parallel:before {
   content: "\e688";
 }
+
+.icon-async-child-process:before {
+  content: "\e6f2";
+}
+
+.icon-child-process:before {
+  content: "\e6c1";
+}

+ 16 - 6
src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue

@@ -188,12 +188,8 @@
       :scroll="true"
       max-height="600px"
     >
-      <!-- append-to-body -->
-      <div v-highlight>
-        <code class="hljs">
-          <!-- 高亮代码块 -->
-          {{ previewResult }}
-        </code>
+      <div>
+        <pre><code v-dompurify-html="highlightedCode(previewResult)" class="hljs"></code></pre>
       </div>
     </Dialog>
   </div>
@@ -237,6 +233,8 @@ import { XmlNode, XmlNodeType, parseXmlString } from 'steady-xml'
 // const eventName = reactive({
 //   name: ''
 // })
+import hljs from 'highlight.js' // 导入代码高亮文件
+import 'highlight.js/styles/github.css' // 导入代码高亮样式
 
 defineOptions({ name: 'MyProcessDesigner' })
 
@@ -308,6 +306,18 @@ const props = defineProps({
   }
 })
 
+/**
+ * 代码高亮
+ */
+const highlightedCode = (code: string) => {
+  // 高亮
+  if (previewType.value === 'json') {
+    code = JSON.stringify(code, null, 2)
+  }
+  const result = hljs.highlight(code, { language: previewType.value, ignoreIllegals: true })
+  return result.value || '&nbsp;'
+}
+
 provide('configGlobal', props)
 let bpmnModeler: any = null
 const defaultZoom = ref(1)

+ 52 - 18
src/components/bpmnProcessDesigner/package/penal/custom-config/components/UserTaskCustomConfig.vue

@@ -123,13 +123,19 @@
     </div>
 
     <el-divider content-position="left">字段权限</el-divider>
-    <div class="field-setting-pane" v-if="formType === 10">
+    <div class="field-setting-pane" v-if="formType === BpmModelFormType.NORMAL">
       <div class="field-permit-title">
         <div class="setting-title-label first-title"> 字段名称 </div>
         <div class="other-titles">
-          <span class="setting-title-label">只读</span>
-          <span class="setting-title-label">可编辑</span>
-          <span class="setting-title-label">隐藏</span>
+          <span class="setting-title-label cursor-pointer" @click="updatePermission('READ')"
+            >只读</span
+          >
+          <span class="setting-title-label cursor-pointer" @click="updatePermission('WRITE')"
+            >可编辑</span
+          >
+          <span class="setting-title-label cursor-pointer" @click="updatePermission('NONE')"
+            >隐藏</span
+          >
         </div>
       </div>
       <div class="field-setting-item" v-for="(item, index) in fieldsPermissionEl" :key="index">
@@ -140,24 +146,30 @@
               :value="FieldPermissionType.READ"
               size="large"
               :label="FieldPermissionType.READ"
-              ><span></span
-            ></el-radio>
+              @change="updateElementExtensions"
+            >
+              <span></span>
+            </el-radio>
           </div>
           <div class="item-radio-wrap">
             <el-radio
               :value="FieldPermissionType.WRITE"
               size="large"
               :label="FieldPermissionType.WRITE"
-              ><span></span
-            ></el-radio>
+              @change="updateElementExtensions"
+            >
+              <span></span>
+            </el-radio>
           </div>
           <div class="item-radio-wrap">
             <el-radio
               :value="FieldPermissionType.NONE"
               size="large"
               :label="FieldPermissionType.NONE"
-              ><span></span
-            ></el-radio>
+              @change="updateElementExtensions"
+            >
+              <span></span>
+            </el-radio>
           </div>
         </el-radio-group>
       </div>
@@ -165,12 +177,22 @@
 
     <el-divider content-position="left">是否需要签名</el-divider>
     <el-form-item prop="signEnable">
-      <el-switch v-model="signEnable.value" active-text="是" inactive-text="否" />
+      <el-switch
+        v-model="signEnable.value"
+        active-text="是"
+        inactive-text="否"
+        @change="updateElementExtensions"
+      />
     </el-form-item>
 
     <el-divider content-position="left">审批意见</el-divider>
     <el-form-item prop="reasonRequire">
-      <el-switch v-model="reasonRequire.value" active-text="必填" inactive-text="非必填" />
+      <el-switch
+        v-model="reasonRequire.value"
+        active-text="必填"
+        inactive-text="非必填"
+        @change="updateElementExtensions"
+      />
     </el-form-item>
   </div>
 </template>
@@ -191,6 +213,7 @@ import {
 } from '@/components/SimpleProcessDesignerV2/src/consts'
 import * as UserApi from '@/api/system/user'
 import { useFormFieldsPermission } from '@/components/SimpleProcessDesignerV2/src/node'
+import { BpmModelFormType } from '@/utils/constants'
 
 defineOptions({ name: 'ElementCustomConfig4UserTask' })
 const props = defineProps({
@@ -248,7 +271,6 @@ const resetCustomConfigList = () => {
     bpmnElement.value.id,
     bpmnInstances().modeler
   )
-
   // 获取元素扩展属性 或者 创建扩展属性
   elExtensionElements.value =
     bpmnElement.value.businessObject?.extensionElements ??
@@ -311,14 +333,13 @@ const resetCustomConfigList = () => {
   }
 
   // 字段权限
-  if (formType.value === 10) {
+  if (formType.value === BpmModelFormType.NORMAL) {
     const fieldsPermissionList = elExtensionElements.value.values?.filter(
       (ex) => ex.$type === `${prefix}:FieldsPermission`
     )
     fieldsPermissionEl.value = []
     getNodeConfigFormFields()
-    // 由于默认添加了发起人元素,这里需要删掉
-    fieldsPermissionConfig.value = fieldsPermissionConfig.value.slice(1)
+    fieldsPermissionConfig.value = fieldsPermissionConfig.value
     fieldsPermissionConfig.value.forEach((element) => {
       element.permission =
         fieldsPermissionList?.find((obj) => obj.field === element.field)?.permission ?? '1'
@@ -487,6 +508,19 @@ function useButtonsSetting() {
   }
 }
 
+/** 批量更新权限 */
+// TODO @lesan:这个页面,有一些 idea 红色报错,咱要不要 fix 下!
+const updatePermission = (type: string) => {
+  fieldsPermissionEl.value.forEach((field) => {
+    field.permission =
+      type === 'READ'
+        ? FieldPermissionType.READ
+        : type === 'WRITE'
+          ? FieldPermissionType.WRITE
+          : FieldPermissionType.NONE
+  })
+}
+
 const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
 onMounted(async () => {
   // 获得用户列表
@@ -497,9 +531,9 @@ onMounted(async () => {
 <style lang="scss" scoped>
 .button-setting-pane {
   display: flex;
-  flex-direction: column;
-  font-size: 14px;
   margin-top: 8px;
+  font-size: 14px;
+  flex-direction: column;
 
   .button-setting-desc {
     padding-right: 8px;

+ 1 - 26
src/router/modules/remaining.ts

@@ -255,32 +255,8 @@ const remainingRouter: AppRouteRecordRaw[] = [
         }
       },
       {
-        path: 'manager/model/edit',
-        component: () => import('@/views/bpm/model/editor/index.vue'),
-        name: 'BpmModelEditor',
-        meta: {
-          noCache: true,
-          hidden: true,
-          canTo: true,
-          title: '设计流程',
-          activeMenu: '/bpm/manager/model'
-        }
-      },
-      {
-        path: 'manager/simple/model',
-        component: () => import('@/views/bpm/simple/SimpleModelDesign.vue'),
-        name: 'SimpleModelDesign',
-        meta: {
-          noCache: true,
-          hidden: true,
-          canTo: true,
-          title: '仿钉钉设计流程',
-          activeMenu: '/bpm/manager/model'
-        }
-      },
-      {
         path: 'manager/definition',
-        component: () => import('@/views/bpm/definition/index.vue'),
+        component: () => import('@/views/bpm/model/definition/index.vue'),
         name: 'BpmProcessDefinition',
         meta: {
           noCache: true,
@@ -356,7 +332,6 @@ const remainingRouter: AppRouteRecordRaw[] = [
         }
       },
       {
-        // TODO @zws:1)建议,在加一个路由。然后标题是“复制流程”,这样体验会好点;2)复制出来的数据,在名字前面,加“副本 ”,和钉钉保持一致!
         path: 'manager/model/:type/:id',
         component: () => import('@/views/bpm/model/form/index.vue'),
         name: 'BpmModelUpdate',

+ 7 - 7
src/utils/index.ts

@@ -511,14 +511,14 @@ export function jsonParse(str: string) {
 /**
  * 截取字符串
  *
- * @param name
- * @param start
- * @param end
+ * @param str 字符串
+ * @param start 开始位置
+ * @param end 结束位置
  */
 
-export const sliceName = (name: string,start: number, end : number) => {
-  if (name.length > end) {
-    return name.slice(start, end)
+export const subString = (str: string, start: number, end: number) => {
+  if (str.length > end) {
+    return str.slice(start, end)
   }
-  return name
+  return str
 }

+ 9 - 9
src/views/bpm/model/CategoryDraggableModel.vue

@@ -89,7 +89,7 @@
               </el-tooltip>
               <el-image v-if="row.icon" :src="row.icon" class="h-38px w-38px mr-10px rounded" />
               <div v-else class="flow-icon">
-                <span style="font-size: 12px; color: #fff">{{ sliceName(row.name,0,2) }}</span>
+                <span style="font-size: 12px; color: #fff">{{ subString(row.name, 0, 2) }}</span>
               </div>
               {{ row.name }}
             </div>
@@ -113,6 +113,11 @@
             </el-text>
           </template>
         </el-table-column>
+        <el-table-column label="流程类型" prop="type" min-width="120">
+          <template #default="{ row }">
+            <dict-tag :value="row.type" :type="DICT_TYPE.BPM_MODEL_TYPE" />
+          </template>
+        </el-table-column>
         <el-table-column label="表单信息" prop="formType" min-width="150">
           <template #default="scope">
             <el-button
@@ -260,6 +265,7 @@
 </template>
 
 <script lang="ts" setup>
+import { DICT_TYPE } from '@/utils/dict'
 import { CategoryApi, CategoryVO } from '@/api/bpm/category'
 import Sortable from 'sortablejs'
 import { formatDate } from '@/utils/formatTime'
@@ -271,9 +277,8 @@ import { checkPermi } from '@/utils/permission'
 import { useUserStoreWithOut } from '@/store/modules/user'
 import { useAppStore } from '@/store/modules/app'
 import { cloneDeep, isEqual } from 'lodash-es'
-import { useTagsView } from '@/hooks/web/useTagsView'
 import { useDebounceFn } from '@vueuse/core'
-import { sliceName } from '@/utils/index'
+import { subString } from '@/utils/index'
 
 defineOptions({ name: 'BpmModel' })
 
@@ -583,8 +588,7 @@ const handleDeleteCategory = async () => {
   } catch {}
 }
 
-/** 添加流程模型弹窗 */
-const tagsView = useTagsView()
+/** 添加/修改/复制流程模型弹窗 */
 const openModelForm = async (type: string, id?: number) => {
   if (type === 'create') {
     await push({ name: 'BpmModelCreate' })
@@ -593,10 +597,6 @@ const openModelForm = async (type: string, id?: number) => {
       name: 'BpmModelUpdate',
       params: { id, type }
     })
-    // 设置标题
-    if (type === 'copy') {
-      tagsView.setTitle('复制流程')
-    }
   }
 }
 

+ 73 - 42
src/views/bpm/definition/index.vue

@@ -3,40 +3,60 @@
 
   <ContentWrap>
     <el-table v-loading="loading" :data="list">
-      <el-table-column label="定义编号" align="center" prop="id" width="400" />
-      <el-table-column label="流程名称" align="center" prop="name" width="200">
-        <template #default="scope">
-          <el-button type="primary" link @click="handleBpmnDetail(scope.row)">
-            <span>{{ scope.row.name }}</span>
-          </el-button>
+      <el-table-column label="定义编号" align="center" prop="id" min-width="250" />
+      <el-table-column label="流程名称" align="center" prop="name" min-width="150" />
+      <el-table-column label="流程图标" align="center" min-width="50">
+        <template #default="{ row }">
+          <el-image v-if="row.icon" :src="row.icon" class="h-24px w-24pxrounded" />
+        </template>
+      </el-table-column>
+      <el-table-column label="可见范围" prop="startUserIds" min-width="100">
+        <template #default="{ row }">
+          <el-text v-if="!row.startUsers?.length"> 全部可见 </el-text>
+          <el-text v-else-if="row.startUsers.length === 1">
+            {{ row.startUsers[0].nickname }}
+          </el-text>
+          <el-text v-else>
+            <el-tooltip
+              class="box-item"
+              effect="dark"
+              placement="top"
+              :content="row.startUsers.map((user: any) => user.nickname).join('、')"
+            >
+              {{ row.startUsers[0].nickname }}等 {{ row.startUsers.length }} 人可见
+            </el-tooltip>
+          </el-text>
         </template>
       </el-table-column>
-      <el-table-column label="定义分类" align="center" prop="categoryName" width="100" />
-      <el-table-column label="表单信息" align="center" prop="formType" width="200">
+      <el-table-column label="流程类型" prop="modelType" min-width="120">
+        <template #default="{ row }">
+          <dict-tag :value="row.modelType" :type="DICT_TYPE.BPM_MODEL_TYPE" />
+        </template>
+      </el-table-column>
+      <el-table-column label="表单信息" prop="formType" min-width="150">
         <template #default="scope">
           <el-button
-            v-if="scope.row.formType === 10"
+            v-if="scope.row.formType === BpmModelFormType.NORMAL"
             type="primary"
             link
             @click="handleFormDetail(scope.row)"
           >
             <span>{{ scope.row.formName }}</span>
           </el-button>
-          <el-button v-else type="primary" link @click="handleFormDetail(scope.row)">
+          <el-button
+            v-else-if="scope.row.formType === BpmModelFormType.CUSTOM"
+            type="primary"
+            link
+            @click="handleFormDetail(scope.row)"
+          >
             <span>{{ scope.row.formCustomCreatePath }}</span>
           </el-button>
+          <label v-else>暂无表单</label>
         </template>
       </el-table-column>
-      <el-table-column label="流程版本" align="center" prop="processDefinition.version" width="80">
+      <el-table-column label="流程版本" align="center" min-width="80">
         <template #default="scope">
-          <el-tag v-if="scope.row">v{{ scope.row.version }}</el-tag>
-          <el-tag type="warning" v-else>未部署</el-tag>
-        </template>
-      </el-table-column>
-      <el-table-column label="状态" align="center" prop="version" width="80">
-        <template #default="scope">
-          <el-tag type="success" v-if="scope.row.suspensionState === 1">激活</el-tag>
-          <el-tag type="warning" v-if="scope.row.suspensionState === 2">挂起</el-tag>
+          <el-tag>v{{ scope.row.version }}</el-tag>
         </template>
       </el-table-column>
       <el-table-column
@@ -46,13 +66,18 @@
         width="180"
         :formatter="dateFormatter"
       />
-      <el-table-column
-        label="定义描述"
-        align="center"
-        prop="description"
-        width="300"
-        show-overflow-tooltip
-      />
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openModelForm(scope.row.id)"
+            v-hasPermi="['bpm:model:update']"
+          >
+            恢复
+          </el-button>
+        </template>
+      </el-table-column>
     </el-table>
     <!-- 分页 -->
     <Pagination
@@ -67,18 +92,14 @@
   <Dialog title="表单详情" v-model="formDetailVisible" width="800">
     <form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" />
   </Dialog>
-
-  <!-- 弹窗:流程模型图的预览 -->
-  <Dialog title="流程图" v-model="bpmnDetailVisible" width="800">
-    <MyProcessViewer style="height: 700px" key="designer" :xml="bpmnXml" />
-  </Dialog>
 </template>
 
 <script lang="ts" setup>
 import { dateFormatter } from '@/utils/formatTime'
-import { MyProcessViewer } from '@/components/bpmnProcessDesigner/package'
 import * as DefinitionApi from '@/api/bpm/definition'
 import { setConfAndFields2 } from '@/utils/formCreate'
+import { DICT_TYPE } from '@/utils/dict'
+import { BpmModelFormType } from '@/utils/constants'
 
 defineOptions({ name: 'BpmProcessDefinition' })
 
@@ -113,7 +134,7 @@ const formDetailPreview = ref({
   option: {}
 })
 const handleFormDetail = async (row: any) => {
-  if (row.formType == 10) {
+  if (row.formType == BpmModelFormType.NORMAL) {
     // 设置表单
     setConfAndFields2(formDetailPreview, row.formConf, row.formFields)
     // 弹窗打开
@@ -125,15 +146,12 @@ const handleFormDetail = async (row: any) => {
   }
 }
 
-/** 流程图的详情按钮操作 */
-const bpmnDetailVisible = ref(false)
-const bpmnXml = ref('')
-const handleBpmnDetail = async (row: any) => {
-  // 设置可见
-  bpmnXml.value = ''
-  bpmnDetailVisible.value = true
-  // 加载 BPMN XML
-  bpmnXml.value = (await DefinitionApi.getProcessDefinition(row.id))?.bpmnXml
+/** 恢复流程模型弹窗 */
+const openModelForm = async (id?: number) => {
+  await push({
+    name: 'BpmModelUpdate',
+    params: { id, type: 'definition' }
+  })
 }
 
 /** 初始化 **/
@@ -141,3 +159,16 @@ onMounted(() => {
   getList()
 })
 </script>
+
+<style lang="scss" scoped>
+.flow-icon {
+  display: flex;
+  width: 38px;
+  height: 38px;
+  margin-right: 10px;
+  background-color: var(--el-color-primary);
+  border-radius: 0.25rem;
+  align-items: center;
+  justify-content: center;
+}
+</style>

+ 77 - 0
src/views/bpm/model/form/ExtraSettings.vue

@@ -140,6 +140,46 @@
         </el-select>
       </div>
     </el-form-item>
+    <el-form-item class="mb-20px">
+      <template #label>
+        <el-text size="large" tag="b">流程前置通知</el-text>
+      </template>
+      <div class="flex flex-col w-100%">
+        <div class="flex">
+          <el-switch
+            v-model="preProcessNotifyEnable"
+            @change="handlePreProcessNotifyEnableChange"
+          />
+          <div class="ml-80px">流程启动后通知</div>
+        </div>
+        <HttpRequestSetting
+          v-if="preProcessNotifyEnable"
+          v-model:setting="modelData.preProcessNotifySetting"
+          :responseEnable="true"
+          :formItemPrefix="'preProcessNotifySetting'"
+        />
+      </div>
+    </el-form-item>
+    <el-form-item class="mb-20px">
+      <template #label>
+        <el-text size="large" tag="b">流程后置通知</el-text>
+      </template>
+      <div class="flex flex-col w-100%">
+        <div class="flex">
+          <el-switch
+            v-model="postProcessNotifyEnable"
+            @change="handlePostProcessNotifyEnableChange"
+          />
+          <div class="ml-80px">流程启动后通知</div>
+        </div>
+        <HttpRequestSetting
+          v-if="postProcessNotifyEnable"
+          v-model:setting="modelData.postProcessNotifySetting"
+          :responseEnable="true"
+          :formItemPrefix="'postProcessNotifySetting'"
+        />
+      </div>
+    </el-form-item>
   </el-form>
 </template>
 
@@ -149,6 +189,7 @@ import { BpmAutoApproveType, BpmModelFormType } from '@/utils/constants'
 import * as FormApi from '@/api/bpm/form'
 import { parseFormFields } from '@/components/FormCreate/src/utils'
 import { ProcessVariableEnum } from '@/components/SimpleProcessDesignerV2/src/consts'
+import HttpRequestSetting from '@/components/SimpleProcessDesignerV2/src/nodes-config/components/HttpRequestSetting.vue'
 
 const modelData = defineModel<any>()
 
@@ -205,6 +246,36 @@ const numberExample = computed(() => {
   }
 })
 
+/** 是否开启流程前置通知 */
+const preProcessNotifyEnable = ref(false)
+const handlePreProcessNotifyEnableChange = (val: boolean | string | number) => {
+  if (val) {
+    modelData.value.preProcessNotifySetting = {
+      url: '',
+      header: [],
+      body: [],
+      response: []
+    }
+  } else {
+    modelData.value.preProcessNotifySetting = null
+  }
+}
+
+/** 是否开启流程后置通知 */
+const postProcessNotifyEnable = ref(false)
+const handlePostProcessNotifyEnableChange = (val: boolean | string | number) => {
+  if (val) {
+    modelData.value.postProcessNotifySetting = {
+      url: '',
+      header: [],
+      body: [],
+      response: []
+    }
+  } else {
+    modelData.value.postProcessNotifySetting = null
+  }
+}
+
 /** 表单选项 */
 const formField = ref<Array<{ field: string; title: string }>>([])
 const formFieldOptions4Title = computed(() => {
@@ -264,6 +335,12 @@ const initData = () => {
       summary: []
     }
   }
+  if (modelData.value.preProcessNotifySetting) {
+    preProcessNotifyEnable.value = true
+  }
+  if (modelData.value.postProcessNotifySetting) {
+    postProcessNotifyEnable.value = true
+  }
 }
 defineExpose({ initData })
 

+ 6 - 5
src/views/bpm/model/form/FormDesign.vue

@@ -11,12 +11,12 @@
         </el-radio>
       </el-radio-group>
     </el-form-item>
-    <el-form-item v-if="modelData.formType === 10" label="流程表单" prop="formId">
+    <el-form-item v-if="modelData.formType === BpmModelFormType.NORMAL" label="流程表单" prop="formId">
       <el-select v-model="modelData.formId" clearable style="width: 100%">
         <el-option v-for="form in formList" :key="form.id" :label="form.name" :value="form.id" />
       </el-select>
     </el-form-item>
-    <el-form-item v-if="modelData.formType === 20" label="表单提交路由" prop="formCustomCreatePath">
+    <el-form-item v-if="modelData.formType === BpmModelFormType.CUSTOM" label="表单提交路由" prop="formCustomCreatePath">
       <el-input
         v-model="modelData.formCustomCreatePath"
         placeholder="请输入表单提交路由"
@@ -31,7 +31,7 @@
         <Icon icon="ep:question" class="ml-5px" />
       </el-tooltip>
     </el-form-item>
-    <el-form-item v-if="modelData.formType === 20" label="表单查看地址" prop="formCustomViewPath">
+    <el-form-item v-if="modelData.formType === BpmModelFormType.CUSTOM" label="表单查看地址" prop="formCustomViewPath">
       <el-input
         v-model="modelData.formCustomViewPath"
         placeholder="请输入表单查看的组件地址"
@@ -48,7 +48,7 @@
     </el-form-item>
     <!-- 表单预览 -->
     <div
-      v-if="modelData.formType === 10 && modelData.formId && formPreview.rule.length > 0"
+      v-if="modelData.formType === BpmModelFormType.NORMAL && modelData.formId && formPreview.rule.length > 0"
       class="mt-20px"
     >
       <div class="flex items-center mb-15px">
@@ -68,6 +68,7 @@
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import * as FormApi from '@/api/bpm/form'
 import { setConfAndFields2 } from '@/utils/formCreate'
+import { BpmModelFormType } from '@/utils/constants'
 
 const props = defineProps({
   formList: {
@@ -96,7 +97,7 @@ const formPreview = ref({
 watch(
   () => modelData.value.formId,
   async (newFormId) => {
-    if (newFormId && modelData.value.formType === 10) {
+    if (newFormId && modelData.value.formType === BpmModelFormType.NORMAL) {
       const data = await FormApi.getForm(newFormId)
       setConfAndFields2(formPreview.value, data.conf, data.fields)
       // 设置只读

+ 1 - 1
src/views/bpm/model/form/ProcessDesign.vue

@@ -25,7 +25,7 @@
 
 <script lang="ts" setup>
 import { BpmModelType } from '@/utils/constants'
-import BpmModelEditor from '../editor/index.vue'
+import BpmModelEditor from './editor/index.vue'
 import SimpleModelDesign from '../../simple/SimpleModelDesign.vue'
 
 // 创建本地数据副本

+ 20 - 3
src/views/bpm/model/editor/index.vue

@@ -34,10 +34,12 @@ import CustomContentPadProvider from '@/components/bpmnProcessDesigner/package/d
 // 自定义左侧菜单(修改 默认任务 为 用户任务)
 import CustomPaletteProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/palette'
 import * as ModelApi from '@/api/bpm/model'
+import { BpmModelFormType } from '@/utils/constants'
+import * as FormApi from '@/api/bpm/form'
 
 defineOptions({ name: 'BpmModelEditor' })
 
-const props = defineProps<{
+defineProps<{
   modelId?: string
   modelKey: string
   modelName: string
@@ -49,7 +51,8 @@ const message = useMessage() // 国际化
 
 // 表单信息
 const formFields = ref<string[]>([])
-const formType = ref(20)
+// 表单类型,暂仅限流程表单
+const formType = ref(BpmModelFormType.NORMAL)
 provide('formFields', formFields)
 provide('formType', formType)
 
@@ -72,7 +75,7 @@ const model = ref<ModelApi.ModelVO>() // 流程模型的信息
 
 /** 初始化 modeler */
 const initModeler = async (item: any) => {
-  //先初始化模型数据
+  // 先初始化模型数据
   model.value = modelData.value
   modeler.value = item
 }
@@ -88,6 +91,20 @@ const save = async (bpmnXml: string) => {
   }
 }
 
+/** 监听表单 ID 变化,加载表单数据 */
+watch(
+  () => modelData.value.formId,
+  async (newFormId) => {
+    if (newFormId && modelData.value.formType === BpmModelFormType.NORMAL) {
+      const data = await FormApi.getForm(newFormId)
+      formFields.value = data.fields
+    } else {
+      formFields.value = []
+    }
+  },
+  { immediate: true }
+)
+
 // 在组件卸载时清理
 onBeforeUnmount(() => {
   modeler.value = null

+ 56 - 39
src/views/bpm/model/form/index.vue

@@ -44,8 +44,13 @@
 
         <!-- 右侧按钮 -->
         <div class="w-200px flex items-center justify-end gap-2">
-          <el-button v-if="route.params.id" type="success" @click="handleDeploy">发 布</el-button>
-          <el-button type="primary" @click="handleSave">保 存</el-button>
+          <el-button v-if="actionType === 'update'" type="success" @click="handleDeploy">
+            发 布
+          </el-button>
+          <el-button type="primary" @click="handleSave">
+            <span v-if="actionType === 'definition'">恢 复</span>
+            <span v-else>保 存</span>
+          </el-button>
         </div>
       </div>
 
@@ -81,20 +86,23 @@
 <script lang="ts" setup>
 import { useRoute, useRouter } from 'vue-router'
 import { useMessage } from '@/hooks/web/useMessage'
+import { useTagsViewStore } from '@/store/modules/tagsView'
+import { useUserStoreWithOut } from '@/store/modules/user'
 import * as ModelApi from '@/api/bpm/model'
 import * as FormApi from '@/api/bpm/form'
 import { CategoryApi, CategoryVO } from '@/api/bpm/category'
 import * as UserApi from '@/api/system/user'
-import { useUserStoreWithOut } from '@/store/modules/user'
+import * as DefinitionApi from '@/api/bpm/definition'
 import { BpmModelFormType, BpmModelType, BpmAutoApproveType } from '@/utils/constants'
 import BasicInfo from './BasicInfo.vue'
 import FormDesign from './FormDesign.vue'
 import ProcessDesign from './ProcessDesign.vue'
-import { useTagsViewStore } from '@/store/modules/tagsView'
 import ExtraSettings from './ExtraSettings.vue'
+import { useTagsView } from '@/hooks/web/useTagsView'
 
 const router = useRouter()
 const { delView } = useTagsViewStore() // 视图操作
+const tagsView = useTagsView()
 const route = useRoute()
 const message = useMessage()
 const userStore = useUserStoreWithOut()
@@ -165,7 +173,7 @@ const formData: any = ref({
   }
 })
 
-//流程数据
+// 流程数据
 const processData = ref<any>()
 
 provide('processData', processData)
@@ -177,20 +185,36 @@ const categoryList = ref<CategoryVO[]>([])
 const userList = ref<UserApi.UserVO[]>([])
 
 /** 初始化数据 */
+const actionType = route.params.type as string
 const initData = async () => {
-  const modelId = route.params.id as string
-  if (modelId) {
-    // 修改场景
+  if (actionType === 'definition') {
+    // 情况一:流程定义场景(恢复)
+    const definitionId = route.params.id as string
+    const data = await DefinitionApi.getProcessDefinition(definitionId)
+    // 将 definition => model,最终赋值
+    data.type = data.modelType
+    delete data.modelType
+    data.id = data.modelId
+    delete data.modelId
+    if (data.simpleModel) {
+      data.simpleModel = JSON.parse(data.simpleModel)
+    }
+    formData.value = data
+    formData.value.startUserType = formData.value.startUserIds?.length > 0 ? 1 : 0
+  } else if (['update', 'copy'].includes(actionType)) {
+    // 情况二:修改场景/复制场景
+    const modelId = route.params.id as string
     formData.value = await ModelApi.getModel(modelId)
     formData.value.startUserType = formData.value.startUserIds?.length > 0 ? 1 : 0
-    // 复制场景
-    if (route.params.type === 'copy') {
+    // 特殊:复制场景
+    if (actionType === 'copy') {
       delete formData.value.id
       formData.value.name += '副本'
       formData.value.key += '_copy'
+      tagsView.setTitle('复制流程')
     }
   } else {
-    // 新增场景
+    // 情况三:新增场景
     formData.value.startUserType = 0 // 全体
     formData.value.managerUserIds.push(userStore.getUser.id)
   }
@@ -271,37 +295,31 @@ const handleSave = async () => {
       ...formData.value
     }
 
-    if (formData.value.id) {
+    if (actionType === 'definition') {
+      // 情况一:流程定义场景(恢复)
+      await ModelApi.updateModel(modelData)
+      // 提示成功
+      message.success('恢复成功,可点击【发布】按钮,进行发布模型')
+    } else if (actionType === 'update') {
       // 修改场景
       await ModelApi.updateModel(modelData)
-      // 询问是否发布流程
-      try {
-        await message.confirm('修改流程成功,是否发布流程?')
-        // 用户点击确认,执行发布
-        await handleDeploy()
-      } catch {
-        // 用户点击取消,停留在当前页面
-      }
+      // 提示成功
+      message.success('修改成功,可点击【发布】按钮,进行发布模型')
+    } else if (actionType === 'copy') {
+      // 情况三:复制场景
+      formData.value.id = await ModelApi.createModel(modelData)
+      // 提示成功
+      message.success('复制成功,可点击【发布】按钮,进行发布模型')
     } else {
-      // 新增场景
+      // 情况四:新增场景
       formData.value.id = await ModelApi.createModel(modelData)
-      try {
-        await message.confirm('流程创建成功,是否继续编辑?')
-        // 用户点击继续编辑,跳转到编辑页面
-        await nextTick()
-        // 先删除当前页签
-        delView(unref(router.currentRoute))
-        // 跳转到编辑页面
-        await router.push({
-          name: 'BpmModelUpdate',
-          params: { id: formData.value.id }
-        })
-      } catch {
-        // 先删除当前页签
-        delView(unref(router.currentRoute))
-        // 用户点击返回列表
-        await router.push({ name: 'BpmModel' })
-      }
+      // 提示成功
+      message.success('新建成功,可点击【发布】按钮,进行发布模型')
+    }
+
+    // 返回列表页(排除更新的情况)
+    if (actionType !== 'update') {
+      await router.push({ name: 'BpmModel' })
     }
   } catch (error: any) {
     console.error('保存失败:', error)
@@ -346,7 +364,6 @@ const handleDeploy = async () => {
 /** 步骤切换处理 */
 const handleStepClick = async (index: number) => {
   try {
-    console.log('index', index)
     if (index !== 0) {
       await validateBasic()
     }

+ 42 - 9
src/views/bpm/processInstance/create/ProcessDefinitionDetail.vue

@@ -74,7 +74,7 @@
 </template>
 <script lang="ts" setup>
 import { decodeFields, setConfAndFields2 } from '@/utils/formCreate'
-import { BpmModelType } from '@/utils/constants'
+import { BpmModelType, BpmModelFormType } from '@/utils/constants'
 import {
   CandidateStrategy,
   NodeId,
@@ -108,6 +108,7 @@ const fApi = ref<ApiAttrs>()
 // 指定审批人
 const startUserSelectTasks: any = ref([]) // 发起人需要选择审批人或抄送人的任务列表
 const startUserSelectAssignees = ref({}) // 发起人选择审批人的数据
+const tempStartUserSelectAssignees = ref({}) // 历史发起人选择审批人的数据,用于每次表单变更时,临时保存
 const bpmnXML: any = ref(null) // BPMN 数据
 const simpleJson = ref<string | undefined>() // Simple 设计器数据 json 格式
 
@@ -121,7 +122,7 @@ const initProcessInfo = async (row: any, formVariables?: any) => {
   startUserSelectAssignees.value = {}
 
   // 情况一:流程表单
-  if (row.formType == 10) {
+  if (row.formType == BpmModelFormType.NORMAL) {
     // 设置表单
     // 注意:需要从 formVariables 中,移除不在 row.formFields 的值。
     // 原因是:后端返回的 formVariables 里面,会有一些非表单的信息。例如说,某个流程节点的审批人。
@@ -137,8 +138,11 @@ const initProcessInfo = async (row: any, formVariables?: any) => {
     await nextTick()
     fApi.value?.btn.show(false) // 隐藏提交按钮
 
-    // 获取流程审批信息
-    await getApprovalDetail(row)
+    // 获取流程审批信息,当再次发起时,流程审批节点要根据原始表单参数预测出来
+    await getApprovalDetail({
+      id: row.id,
+      processVariablesStr: JSON.stringify(formVariables)
+    })
 
     // 加载流程图
     const processDefinitionDetail = await DefinitionApi.getProcessDefinition(row.id)
@@ -155,32 +159,61 @@ const initProcessInfo = async (row: any, formVariables?: any) => {
   }
 }
 
+/** 预测流程节点会因为输入的参数值而产生新的预测结果值,所以需重新预测一次 */
+watch(
+  detailForm.value,
+  (newValue) => {
+    if (newValue && Object.keys(newValue.value).length > 0) {
+      // 记录之前的节点审批人
+      tempStartUserSelectAssignees.value = startUserSelectAssignees.value
+      startUserSelectAssignees.value = {}
+      // 加载最新的审批详情
+      getApprovalDetail({
+        id: props.selectProcessDefinition.id,
+        processVariablesStr: JSON.stringify(newValue.value) // 解决 GET 无法传递对象的问题,后端 String 再转 JSON
+      })
+    }
+  },
+  {
+    immediate: true
+  }
+)
+
 /** 获取审批详情 */
 const getApprovalDetail = async (row: any) => {
   try {
-    // TODO 获取审批详情,设置 activityId 为发起人节点(为了获取字段权限。暂时只对 Simple 设计器有效)
+    // TODO 获取审批详情,设置 activityId 为发起人节点(为了获取字段权限。暂时只对 Simple 设计器有效);@jason:这里可以去掉 activityId 么?
     const data = await ProcessInstanceApi.getApprovalDetail({
       processDefinitionId: row.id,
-      activityId: NodeId.START_USER_NODE_ID
+      activityId: NodeId.START_USER_NODE_ID,
+      processVariablesStr: row.processVariablesStr // 解决 GET 无法传递对象的问题,后端 String 再转 JSON
     })
 
     if (!data) {
       message.error('查询不到审批详情信息!')
       return
     }
+    // 获取审批节点,显示 Timeline 的数据
+    activityNodes.value = data.activityNodes
 
     // 获取发起人自选的任务
     startUserSelectTasks.value = data.activityNodes?.filter(
       (node: ApprovalNodeInfo) => CandidateStrategy.START_USER_SELECT === node.candidateStrategy
     )
+    // 恢复之前的选择审批人
     if (startUserSelectTasks.value?.length > 0) {
       for (const node of startUserSelectTasks.value) {
-        startUserSelectAssignees.value[node.id] = []
+        if (
+          tempStartUserSelectAssignees.value[node.id] &&
+          tempStartUserSelectAssignees.value[node.id].length > 0
+        ) {
+          startUserSelectAssignees.value[node.id] = tempStartUserSelectAssignees.value[node.id]
+        } else {
+          startUserSelectAssignees.value[node.id] = []
+        }
       }
     }
 
-    // 获取审批节点,显示 Timeline 的数据
-    activityNodes.value = data.activityNodes
     // 获取表单字段权限
     const formFieldsPermission = data.formFieldsPermission
     // 设置表单字段权限

+ 4 - 4
src/views/bpm/processInstance/create/index.vue

@@ -64,9 +64,9 @@
                           class="w-32px h-32px"
                         />
                         <div v-else class="flow-icon">
-                          <span style="font-size: 12px; color: #fff">{{
-                            sliceName(definition.name,0,2)
-                          }}</span>
+                          <span style="font-size: 12px; color: #fff">
+                            {{ subString(definition.name, 0, 2) }}
+                          </span>
                         </div>
                         <el-text class="!ml-10px" size="large">{{ definition.name }}</el-text>
                       </div>
@@ -97,7 +97,7 @@ import * as ProcessInstanceApi from '@/api/bpm/processInstance'
 import { CategoryApi, CategoryVO } from '@/api/bpm/category'
 import ProcessDefinitionDetail from './ProcessDefinitionDetail.vue'
 import { groupBy } from 'lodash-es'
-import { sliceName } from '@/utils/index'
+import { subString } from '@/utils/index'
 
 defineOptions({ name: 'BpmProcessInstanceCreate' })
 

+ 0 - 267
src/views/bpm/processInstance/create/index_old.vue

@@ -1,267 +0,0 @@
-<template>
-  <doc-alert title="流程发起、取消、重新发起" url="https://doc.iocoder.cn/bpm/process-instance/" />
-
-  <!-- 第一步,通过流程定义的列表,选择对应的流程 -->
-  <ContentWrap v-if="!selectProcessDefinition" v-loading="loading">
-    <el-tabs tab-position="left" v-model="categoryActive">
-      <el-tab-pane
-        :label="category.name"
-        :name="category.code"
-        :key="category.code"
-        v-for="category in categoryList"
-      >
-        <el-row :gutter="20">
-          <el-col
-            :lg="6"
-            :sm="12"
-            :xs="24"
-            v-for="definition in categoryProcessDefinitionList"
-            :key="definition.id"
-          >
-            <el-card
-              shadow="hover"
-              class="mb-20px cursor-pointer"
-              @click="handleSelect(definition)"
-            >
-              <template #default>
-                <div class="flex">
-                  <el-image :src="definition.icon" class="w-32px h-32px" />
-                  <el-text class="!ml-10px" size="large">{{ definition.name }}</el-text>
-                </div>
-              </template>
-            </el-card>
-          </el-col>
-        </el-row>
-      </el-tab-pane>
-    </el-tabs>
-  </ContentWrap>
-
-  <!-- 第二步,填写表单,进行流程的提交 -->
-  <ContentWrap v-else>
-    <el-card class="box-card">
-      <div class="clearfix">
-        <span class="el-icon-document">申请信息【{{ selectProcessDefinition.name }}】</span>
-        <el-button style="float: right" type="primary" @click="selectProcessDefinition = undefined">
-          <Icon icon="ep:delete" /> 选择其它流程
-        </el-button>
-      </div>
-      <el-col :span="16" :offset="6" style="margin-top: 20px">
-        <form-create
-          :rule="detailForm.rule"
-          v-model:api="fApi"
-          v-model="detailForm.value"
-          :option="detailForm.option"
-          @submit="submitForm"
-        >
-          <template #type-startUserSelect>
-            <el-col :span="24">
-              <el-card class="mb-10px">
-                <template #header>指定审批人</template>
-                <el-form
-                  :model="startUserSelectAssignees"
-                  :rules="startUserSelectAssigneesFormRules"
-                  ref="startUserSelectAssigneesFormRef"
-                >
-                  <el-form-item
-                    v-for="userTask in startUserSelectTasks"
-                    :key="userTask.id"
-                    :label="`任务【${userTask.name}】`"
-                    :prop="userTask.id"
-                  >
-                    <el-select
-                      v-model="startUserSelectAssignees[userTask.id]"
-                      multiple
-                      placeholder="请选择审批人"
-                    >
-                      <el-option
-                        v-for="user in userList"
-                        :key="user.id"
-                        :label="user.nickname"
-                        :value="user.id"
-                      />
-                    </el-select>
-                  </el-form-item>
-                </el-form>
-              </el-card>
-            </el-col>
-          </template>
-        </form-create>
-      </el-col>
-    </el-card>
-    <!-- 流程图预览 -->
-    <ProcessInstanceBpmnViewer :bpmn-xml="bpmnXML as any" />
-  </ContentWrap>
-</template>
-<script lang="ts" setup>
-import * as DefinitionApi from '@/api/bpm/definition'
-import * as ProcessInstanceApi from '@/api/bpm/processInstance'
-import { decodeFields, setConfAndFields2 } from '@/utils/formCreate'
-import type { ApiAttrs } from '@form-create/element-ui/types/config'
-import ProcessInstanceBpmnViewer from '../detail/ProcessInstanceBpmnViewer.vue'
-import { CategoryApi } from '@/api/bpm/category'
-import { useTagsViewStore } from '@/store/modules/tagsView'
-import * as UserApi from '@/api/system/user'
-
-defineOptions({ name: 'BpmProcessInstanceCreate' })
-
-const route = useRoute() // 路由
-const { push, currentRoute } = useRouter() // 路由
-const message = useMessage() // 消息
-const { delView } = useTagsViewStore() // 视图操作
-
-const processInstanceId = route.query.processInstanceId
-const loading = ref(true) // 加载中
-const categoryList = ref([]) // 分类的列表
-const categoryActive = ref('') // 选中的分类
-const processDefinitionList = ref([]) // 流程定义的列表
-
-/** 查询列表 */
-const getList = async () => {
-  loading.value = true
-  try {
-    // 流程分类
-    categoryList.value = await CategoryApi.getCategorySimpleList()
-    if (categoryList.value.length > 0) {
-      categoryActive.value = categoryList.value[0].code
-    }
-    // 流程定义
-    processDefinitionList.value = await DefinitionApi.getProcessDefinitionList({
-      suspensionState: 1
-    })
-
-    // 如果 processInstanceId 非空,说明是重新发起
-    if (processInstanceId?.length > 0) {
-      const processInstance = await ProcessInstanceApi.getProcessInstance(processInstanceId)
-      if (!processInstance) {
-        message.error('重新发起流程失败,原因:流程实例不存在')
-        return
-      }
-      const processDefinition = processDefinitionList.value.find(
-        (item) => item.key == processInstance.processDefinition?.key
-      )
-      if (!processDefinition) {
-        message.error('重新发起流程失败,原因:流程定义不存在')
-        return
-      }
-      await handleSelect(processDefinition, processInstance.formVariables)
-    }
-  } finally {
-    loading.value = false
-  }
-}
-
-/** 选中分类对应的流程定义列表 */
-const categoryProcessDefinitionList = computed(() => {
-  return processDefinitionList.value.filter((item) => item.category == categoryActive.value)
-})
-
-// ========== 表单相关 ==========
-const fApi = ref<ApiAttrs>()
-const detailForm = ref({
-  rule: [],
-  option: {},
-  value: {}
-}) // 流程表单详情
-const selectProcessDefinition = ref() // 选择的流程定义
-
-// 指定审批人
-const bpmnXML = ref(null) // BPMN 数据
-const startUserSelectTasks = ref([]) // 发起人需要选择审批人的用户任务列表
-const startUserSelectAssignees = ref({}) // 发起人选择审批人的数据
-const startUserSelectAssigneesFormRef = ref() // 发起人选择审批人的表单 Ref
-const startUserSelectAssigneesFormRules = ref({}) // 发起人选择审批人的表单 Rules
-const userList = ref<any[]>([]) // 用户列表
-
-/** 处理选择流程的按钮操作 **/
-const handleSelect = async (row, formVariables) => {
-  // 设置选择的流程
-  selectProcessDefinition.value = row
-
-  // 重置指定审批人
-  startUserSelectTasks.value = []
-  startUserSelectAssignees.value = {}
-  startUserSelectAssigneesFormRules.value = {}
-
-  // 情况一:流程表单
-  if (row.formType == 10) {
-    // 设置表单
-    // 注意:需要从 formVariables 中,移除不在 row.formFields 的值。
-    // 原因是:后端返回的 formVariables 里面,会有一些非表单的信息。例如说,某个流程节点的审批人。
-    //        这样,就可能导致一个流程被审批不通过后,重新发起时,会直接后端报错!!!
-    const allowedFields = decodeFields(row.formFields).map((fieldObj: any) => fieldObj.field)
-    for (const key in formVariables) {
-      if (!allowedFields.includes(key)) {
-        delete formVariables[key]
-      }
-    }
-    setConfAndFields2(detailForm, row.formConf, row.formFields, formVariables)
-
-    // 加载流程图
-    const processDefinitionDetail = await DefinitionApi.getProcessDefinition(row.id)
-    if (processDefinitionDetail) {
-      bpmnXML.value = processDefinitionDetail.bpmnXml
-      startUserSelectTasks.value = processDefinitionDetail.startUserSelectTasks
-
-      // 设置指定审批人
-      if (startUserSelectTasks.value?.length > 0) {
-        detailForm.value.rule.push({
-          type: 'startUserSelect',
-          props: {
-            title: '指定审批人'
-          }
-        })
-        // 设置校验规则
-        for (const userTask of startUserSelectTasks.value) {
-          startUserSelectAssignees.value[userTask.id] = []
-          startUserSelectAssigneesFormRules.value[userTask.id] = [
-            { required: true, message: '请选择审批人', trigger: 'blur' }
-          ]
-        }
-        // 加载用户列表
-        userList.value = await UserApi.getSimpleUserList()
-      }
-    }
-    // 情况二:业务表单
-  } else if (row.formCustomCreatePath) {
-    await push({
-      path: row.formCustomCreatePath
-    })
-    // 这里暂时无需加载流程图,因为跳出到另外个 Tab;
-  }
-}
-
-/** 提交按钮 */
-const submitForm = async (formData) => {
-  if (!fApi.value || !selectProcessDefinition.value) {
-    return
-  }
-  // 如果有指定审批人,需要校验
-  if (startUserSelectTasks.value?.length > 0) {
-    await startUserSelectAssigneesFormRef.value.validate()
-  }
-
-  // 提交请求
-  fApi.value.btn.loading(true)
-  try {
-    await ProcessInstanceApi.createProcessInstance({
-      processDefinitionId: selectProcessDefinition.value.id,
-      variables: formData,
-      startUserSelectAssignees: startUserSelectAssignees.value
-    })
-    // 提示
-    message.success('发起流程成功')
-    // 跳转回去
-    delView(unref(currentRoute))
-    await push({
-      name: 'BpmProcessInstanceMy'
-    })
-  } finally {
-    fApi.value.btn.loading(false)
-  }
-}
-
-/** 初始化 */
-onMounted(() => {
-  getList()
-})
-</script>

+ 108 - 20
src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue

@@ -36,15 +36,28 @@
               :rule="approveForm.rule"
             />
           </el-card>
-          <el-form-item label="审批意见" prop="reason">
+          <el-form-item :label="`${nodeTypeName}意见`" prop="reason">
             <el-input
               v-model="approveReasonForm.reason"
-              placeholder="请输入审批意见"
+              :placeholder="`请输入${nodeTypeName}意见`"
               type="textarea"
               :rows="4"
             />
           </el-form-item>
           <el-form-item
+            label="下一个节点的审批人"
+            prop="nextAssignees"
+            v-if="nextAssigneesActivityNode.length > 0"
+          >
+            <div class="ml-10px -mt-15px -mb-35px">
+              <ProcessInstanceTimeline
+                :activity-nodes="nextAssigneesActivityNode"
+                :show-status-icon="false"
+                @select-user-confirm="selectNextAssigneesConfirm"
+              />
+            </div>
+          </el-form-item>
+          <el-form-item
             v-if="runningTask.signEnable"
             label="签名"
             prop="signPicUrl"
@@ -66,7 +79,7 @@
             >
               {{ getButtonDisplayName(OperationButtonType.APPROVE) }}
             </el-button>
-            <el-button @click="closePropover('approve', approveFormRef)"> 取消 </el-button>
+            <el-button @click="closePopover('approve', approveFormRef)"> 取消 </el-button>
           </el-form-item>
         </el-form>
       </div>
@@ -111,7 +124,7 @@
             >
               {{ getButtonDisplayName(OperationButtonType.REJECT) }}
             </el-button>
-            <el-button @click="closePropover('reject', rejectFormRef)"> 取消 </el-button>
+            <el-button @click="closePopover('reject', rejectFormRef)"> 取消 </el-button>
           </el-form-item>
         </el-form>
       </div>
@@ -169,7 +182,7 @@
             <el-button :disabled="formLoading" type="primary" @click="handleCopy">
               {{ getButtonDisplayName(OperationButtonType.COPY) }}
             </el-button>
-            <el-button @click="closePropover('copy', copyFormRef)"> 取消 </el-button>
+            <el-button @click="closePopover('copy', copyFormRef)"> 取消 </el-button>
           </el-form-item>
         </el-form>
       </div>
@@ -221,7 +234,7 @@
             <el-button :disabled="formLoading" type="primary" @click="handleTransfer()">
               {{ getButtonDisplayName(OperationButtonType.TRANSFER) }}
             </el-button>
-            <el-button @click="closePropover('transfer', transferFormRef)"> 取消 </el-button>
+            <el-button @click="closePopover('transfer', transferFormRef)"> 取消 </el-button>
           </el-form-item>
         </el-form>
       </div>
@@ -273,7 +286,7 @@
             <el-button :disabled="formLoading" type="primary" @click="handleDelegate()">
               {{ getButtonDisplayName(OperationButtonType.DELEGATE) }}
             </el-button>
-            <el-button @click="closePropover('delegate', delegateFormRef)"> 取消 </el-button>
+            <el-button @click="closePopover('delegate', delegateFormRef)"> 取消 </el-button>
           </el-form-item>
         </el-form>
       </div>
@@ -328,7 +341,7 @@
             <el-button :disabled="formLoading" type="primary" @click="handlerAddSign('after')">
               向后{{ getButtonDisplayName(OperationButtonType.ADD_SIGN) }}
             </el-button>
-            <el-button @click="closePropover('addSign', addSignFormRef)"> 取消 </el-button>
+            <el-button @click="closePopover('addSign', addSignFormRef)"> 取消 </el-button>
           </el-form-item>
         </el-form>
       </div>
@@ -379,7 +392,7 @@
             <el-button :disabled="formLoading" type="primary" @click="handlerDeleteSign()">
               减签
             </el-button>
-            <el-button @click="closePropover('deleteSign', deleteSignFormRef)"> 取消 </el-button>
+            <el-button @click="closePopover('deleteSign', deleteSignFormRef)"> 取消 </el-button>
           </el-form-item>
         </el-form>
       </div>
@@ -431,7 +444,7 @@
             <el-button :disabled="formLoading" type="primary" @click="handleReturn()">
               {{ getButtonDisplayName(OperationButtonType.RETURN) }}
             </el-button>
-            <el-button @click="closePropover('return', returnFormRef)"> 取消 </el-button>
+            <el-button @click="closePopover('return', returnFormRef)"> 取消 </el-button>
           </el-form-item>
         </el-form>
       </div>
@@ -475,7 +488,7 @@
             <el-button :disabled="formLoading" type="primary" @click="handleCancel()">
               确认
             </el-button>
-            <el-button @click="closePropover('cancel', cancelFormRef)"> 取消 </el-button>
+            <el-button @click="closePopover('cancel', cancelFormRef)"> 取消 </el-button>
           </el-form-item>
         </el-form>
       </div>
@@ -504,12 +517,16 @@ import * as TaskApi from '@/api/bpm/task'
 import * as ProcessInstanceApi from '@/api/bpm/processInstance'
 import * as UserApi from '@/api/system/user'
 import {
+  NodeType,
   OPERATION_BUTTON_NAME,
-  OperationButtonType
+  OperationButtonType,
+  CandidateStrategy
 } from '@/components/SimpleProcessDesignerV2/src/consts'
 import { BpmModelFormType, BpmProcessInstanceStatus } from '@/utils/constants'
 import type { FormInstance, FormRules } from 'element-plus'
 import SignDialog from './SignDialog.vue'
+import ProcessInstanceTimeline from '../detail/ProcessInstanceTimeline.vue'
+import { isEmpty } from '@/utils/is'
 
 defineOptions({ name: 'ProcessInstanceBtnContainer' })
 
@@ -546,22 +563,29 @@ const returnList = ref([] as any) // 退回节点
 const runningTask = ref<any>() // 运行中的任务
 const approveForm = ref<any>({}) // 审批通过时,额外的补充信息
 const approveFormFApi = ref<any>({}) // approveForms 的 fAPi
+const nodeTypeName = ref('审批') // 节点类型名称
 
 // 审批通过意见表单
 const reasonRequire = ref()
 const approveFormRef = ref<FormInstance>()
 const signRef = ref()
 const approveSignFormRef = ref()
+const nextAssigneesActivityNode = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([]) // 下一个审批节点信息
 const approveReasonForm = reactive({
   reason: '',
-  signPicUrl: ''
+  signPicUrl: '',
+  nextAssignees: {}
 })
 const approveReasonRule = computed(() => {
   return {
-    reason: [{ required: reasonRequire.value, message: '审批意见不能为空', trigger: 'blur' }],
-    signPicUrl: [{ required: true, message: '签名不能为空', trigger: 'change' }]
+    reason: [
+      { required: reasonRequire.value, message: nodeTypeName + '意见不能为空', trigger: 'blur' }
+    ],
+    signPicUrl: [{ required: true, message: '签名不能为空', trigger: 'change' }],
+    nextAssignees: [{ required: true, message: '审批人不能为空', trigger: 'blur' }]
   }
 })
+
 // 拒绝表单
 const rejectFormRef = ref<FormInstance>()
 const rejectReasonForm = reactive({
@@ -668,6 +692,7 @@ const openPopover = async (type: string) => {
       message.warning('表单校验不通过,请先完善表单!!')
       return
     }
+    initNextAssigneesFormField()
   }
   if (type === 'return') {
     // 获取退回节点
@@ -685,11 +710,58 @@ const openPopover = async (type: string) => {
 }
 
 /** 关闭气泡卡 */
-const closePropover = (type: string, formRef: FormInstance | undefined) => {
+const closePopover = (type: string, formRef: FormInstance | undefined) => {
   if (formRef) {
     formRef.resetFields()
   }
   popOverVisible.value[type] = false
+  nextAssigneesActivityNode.value = []
+}
+
+/** 流程通过时,根据表单变量查询新的流程节点,判断下一个节点类型是否为自选审批人 */
+const initNextAssigneesFormField = async () => {
+  // 获取修改的流程变量, 暂时只支持流程表单
+  const variables = getUpdatedProcessInstanceVariables()
+  const data = await ProcessInstanceApi.getNextApprovalNodes({
+    processInstanceId: props.processInstance.id,
+    taskId: runningTask.value.id,
+    processVariablesStr: JSON.stringify(variables)
+  })
+  if (data && data.length > 0) {
+    data.forEach((node: any) => {
+      if (
+        // 情况一:当前节点没有审批人,并且是发起人自选
+        (isEmpty(node.tasks) &&
+          isEmpty(node.candidateUsers) &&
+          CandidateStrategy.START_USER_SELECT === node.candidateStrategy) ||
+        // 情况二:当前节点是审批人自选
+        CandidateStrategy.APPROVE_USER_SELECT === node.candidateStrategy
+      ) {
+        nextAssigneesActivityNode.value.push(node)
+      }
+    })
+  }
+}
+
+/** 选择下一个节点的审批人 */
+const selectNextAssigneesConfirm = (id: string, userList: any[]) => {
+  approveReasonForm.nextAssignees[id] = userList?.map((item: any) => item.id)
+}
+/** 审批通过时,校验每个自选审批人的节点是否都已配置了审批人 */
+const validateNextAssignees = () => {
+  // TODO @小北:可以考虑 Object.keys(nextAssigneesActivityNode.value).length === 0) return true;减少括号层级
+  // 如果需要自选审批人,则校验自选审批人
+  if (Object.keys(nextAssigneesActivityNode.value).length > 0) {
+    // 校验每个节点是否都已配置审批人
+    for (const item of nextAssigneesActivityNode.value) {
+      if (isEmpty(approveReasonForm.nextAssignees[item.id])) {
+        // TODO @小北:可以打印下节点名,嘿嘿。
+        message.warning('下一个节点的审批人不能为空!')
+        return false
+      }
+    }
+  }
+  return true
 }
 
 /** 处理审批通过和不通过的操作 */
@@ -699,15 +771,24 @@ const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) =>
     // 校验表单
     if (!formRef) return
     await formRef.validate()
+    // 校验流程表单必填字段
+    const valid = await validateNormalForm()
+    if (!valid) {
+      message.warning('表单校验不通过,请先完善表单!!')
+      return
+    }
+
     if (pass) {
-      // 获取修改的流程变量, 暂时只支持流程表单
+      const nextAssigneesValid = validateNextAssignees()
+      if (!nextAssigneesValid) return
       const variables = getUpdatedProcessInstanceVariables()
       // 审批通过数据
       const data = {
         id: runningTask.value.id,
         reason: approveReasonForm.reason,
-        variables // 审批通过, 把修改的字段值赋于流程实例变量
-      }
+        variables, // 审批通过, 把修改的字段值赋于流程实例变量
+        nextAssignees: approveReasonForm.nextAssignees // 下个自选节点选择的审批人信息
+      } as any
       // 签名
       if (runningTask.value.signEnable) {
         data.signPicUrl = approveReasonForm.signPicUrl
@@ -722,6 +803,7 @@ const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) =>
       }
       await TaskApi.approveTask(data)
       popOverVisible.value.approve = false
+      nextAssigneesActivityNode.value = []
       message.success('审批通过成功')
     } else {
       // 审批不通过数据
@@ -969,9 +1051,10 @@ const getButtonDisplayName = (btnType: OperationButtonType) => {
 
 const loadTodoTask = (task: any) => {
   approveForm.value = {}
-  approveFormFApi.value = {}
   runningTask.value = task
+  approveFormFApi.value = {}
   reasonRequire.value = task?.reasonRequire ?? false
+  nodeTypeName.value = task?.nodeType === NodeType.TRANSACTOR_NODE ? '办理' : '审批'
   // 处理 approve 表单.
   if (task && task.formId && task.formConf) {
     const tempApproveForm = {}
@@ -997,6 +1080,11 @@ const validateNormalForm = async () => {
   }
 }
 
+/**
+ * TODO @小北  TO  @芋道
+ * 问题:这里存在一种场景会出现问题,流程发起后,A节点审批完成,B节点没有可编辑的流程字段且B节点为自选审批人节点,会导致流程审批人为空,
+ * 原因:因为没有可编辑的流程字段时props.writableFields为空,参数variables传递时也为空
+ */
 /** 从可以编辑的流程表单字段,获取需要修改的流程实例的变量 */
 const getUpdatedProcessInstanceVariables = () => {
   const variables = {}

+ 6 - 5
src/views/bpm/processInstance/detail/ProcessInstanceSimpleViewer.vue

@@ -42,13 +42,13 @@ watch(
       const finishedSequenceFlowActivityIds: string[] = newModelView.finishedSequenceFlowActivityIds
       setSimpleModelNodeTaskStatus(
         newModelView.simpleModel,
-        newModelView.processInstance.status,
+        newModelView.processInstance?.status,
         rejectedTaskActivityIds,
         unfinishedTaskActivityIds,
         finishedActivityIds,
         finishedSequenceFlowActivityIds
       )
-      simpleModel.value = newModelView.simpleModel
+      simpleModel.value = newModelView.simpleModel ? newModelView.simpleModel : {}
     }
   }
 )
@@ -84,7 +84,9 @@ const setSimpleModelNodeTaskStatus = (
   // 审批节点
   if (
     simpleModel.type === NodeType.START_USER_NODE ||
-    simpleModel.type === NodeType.USER_TASK_NODE
+    simpleModel.type === NodeType.USER_TASK_NODE ||
+    simpleModel.type === NodeType.TRANSACTOR_NODE ||
+    simpleModel.type === NodeType.CHILD_PROCESS_NODE
   ) {
     simpleModel.activityStatus = TaskStatusEnum.NOT_START
     if (rejectedTaskActivityIds.includes(simpleModel.id)) {
@@ -169,5 +171,4 @@ const setSimpleModelNodeTaskStatus = (
 }
 </script>
 
-<style lang="scss" scoped>
-</style>
+<style lang="scss" scoped></style>

+ 12 - 2
src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue

@@ -43,7 +43,8 @@
           v-if="
             isEmpty(activity.tasks) &&
             isEmpty(activity.candidateUsers) &&
-            CandidateStrategy.START_USER_SELECT === activity.candidateStrategy
+            (CandidateStrategy.START_USER_SELECT === activity.candidateStrategy ||
+              CandidateStrategy.APPROVE_USER_SELECT === activity.candidateStrategy)
           "
         >
           <!--  && activity.nodeType === NodeType.USER_TASK_NODE -->
@@ -121,6 +122,7 @@
                 "
                 class="text-#a5a5a5 text-13px mt-1 w-full bg-#f8f8fa p2 rounded-md"
               >
+                <!-- TODO lesan:这里如果是办理,需要是办理意见 -->
                 审批意见:{{ task.reason }}
               </div>
               <div
@@ -179,6 +181,8 @@ import copySvg from '@/assets/svgs/bpm/copy.svg'
 import conditionSvg from '@/assets/svgs/bpm/condition.svg'
 import parallelSvg from '@/assets/svgs/bpm/parallel.svg'
 import finishSvg from '@/assets/svgs/bpm/finish.svg'
+import transactorSvg from '@/assets/svgs/bpm/transactor.svg'
+import childProcessSvg from '@/assets/svgs/bpm/child-process.svg'
 
 defineOptions({ name: 'BpmProcessInstanceTimeline' })
 withDefaults(
@@ -240,12 +244,16 @@ const nodeTypeSvgMap = {
   [NodeType.START_USER_NODE]: { color: '#909398', svg: starterSvg },
   // 审批人节点
   [NodeType.USER_TASK_NODE]: { color: '#ff943e', svg: auditorSvg },
+  // 办理人节点
+  [NodeType.TRANSACTOR_NODE]: { color: '#ff943e', svg: transactorSvg },
   // 抄送人节点
   [NodeType.COPY_TASK_NODE]: { color: '#3296fb', svg: copySvg },
   // 条件分支节点
   [NodeType.CONDITION_NODE]: { color: '#14bb83', svg: conditionSvg },
   // 并行分支节点
-  [NodeType.PARALLEL_BRANCH_NODE]: { color: '#14bb83', svg: parallelSvg }
+  [NodeType.PARALLEL_BRANCH_NODE]: { color: '#14bb83', svg: parallelSvg },
+  // 子流程节点
+  [NodeType.CHILD_PROCESS_NODE]: { color: '#14bb83', svg: childProcessSvg }
 }
 
 // 只有只有状态是 -1、0、1 才展示头像右小角状态小icon
@@ -264,6 +272,8 @@ const getApprovalNodeIcon = (taskStatus: number, nodeType: NodeType) => {
   if (
     nodeType === NodeType.START_USER_NODE ||
     nodeType === NodeType.USER_TASK_NODE ||
+    nodeType === NodeType.TRANSACTOR_NODE ||
+    nodeType === NodeType.CHILD_PROCESS_NODE ||
     nodeType === NodeType.END_EVENT_NODE
   ) {
     return statusIconMap[taskStatus]?.icon

+ 2 - 1
src/views/bpm/processInstance/detail/index.vue

@@ -178,8 +178,9 @@ const writableFields: Array<string> = [] // 表单可以编辑的字段
 
 /** 获得详情 */
 const getDetail = () => {
+  // 获得审批详情
   getApprovalDetail()
-
+  // 获得流程模型视图
   getProcessModelView()
 }
 

+ 58 - 59
src/views/bpm/processInstance/index.vue

@@ -24,9 +24,7 @@
         <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
       </el-form-item>
 
-      <!-- TODO @ tuituji:style 可以使用 unocss -->
-      <el-form-item label="" prop="category" :style="{ position: 'absolute', right: '300px' }">
-        <!-- TODO @tuituji:应该选择好分类,就触发搜索啦。 RE:done & to check-->
+      <el-form-item label="" prop="category" class="absolute right-[300px]">
         <el-select
           v-model="queryParams.category"
           placeholder="请选择流程分类"
@@ -42,8 +40,7 @@
           />
         </el-select>
       </el-form-item>
-
-      <el-form-item label="" prop="status" :style="{ position: 'absolute', right: '130px' }">
+      <el-form-item label="" prop="status" class="absolute right-[130px]">
         <el-select
           v-model="queryParams.status"
           placeholder="请选择流程状态"
@@ -61,8 +58,7 @@
       </el-form-item>
 
       <!-- 高级筛选 -->
-      <!-- TODO @ tuituji:style 可以使用 unocss -->
-      <el-form-item :style="{ position: 'absolute', right: '0px' }">
+      <el-form-item class="absolute right-0">
         <el-popover
           :visible="showPopover"
           persistent
@@ -75,36 +71,28 @@
               <Icon icon="ep:plus" class="mr-5px" />高级筛选
             </el-button>
           </template>
-          <!-- <el-form-item label="流程发起人" class="bold-label" label-position="top" prop="category">
-            <el-select
-              v-model="queryParams.category"
-              placeholder="请选择流程发起人"
-              clearable
-              class="!w-390px"
-            >
-              <el-option
-                v-for="category in categoryList"
-                :key="category.code"
-                :label="category.name"
-                :value="category.code"
-              />
-            </el-select>
-          </el-form-item> -->
           <el-form-item
             label="所属流程"
-            class="bold-label"
+            class="font-bold"
             label-position="top"
             prop="processDefinitionKey"
           >
-            <el-input
+            <el-select
               v-model="queryParams.processDefinitionKey"
-              placeholder="请输入流程定义的标识"
+              placeholder="请选择流程定义"
               clearable
-              @keyup.enter="handleQuery"
               class="!w-390px"
-            />
+              @change="handleQuery"
+            >
+              <el-option
+                v-for="item in processDefinitionList"
+                :key="item.key"
+                :label="item.name"
+                :value="item.key"
+              />
+            </el-select>
           </el-form-item>
-          <el-form-item label="发起时间" class="bold-label" label-position="top" prop="createTime">
+          <el-form-item label="发起时间" class="font-bold" label-position="top" prop="createTime">
             <el-date-picker
               v-model="queryParams.createTime"
               value-format="YYYY-MM-DD HH:mm:ss"
@@ -115,11 +103,12 @@
               class="!w-240px"
             />
           </el-form-item>
-          <!-- TODO tuituiji:参考钉钉,1)按照清空、取消、确认排序。2)右对齐。3)确认增加 primary -->
-          <el-form-item class="bold-label" label-position="top">
-            <el-button @click="handleQuery"> 确认</el-button>
-            <el-button @click="showPopover = false"> 取消</el-button>
-            <el-button @click="resetQuery"> 清空</el-button>
+          <el-form-item class="font-bold" label-position="top">
+            <div class="flex justify-end w-full">
+              <el-button @click="resetQuery">清空</el-button>
+              <el-button @click="showPopover = false">取消</el-button>
+              <el-button type="primary" @click="handleQuery">确认</el-button>
+            </div>
           </el-form-item>
         </el-popover>
       </el-form-item>
@@ -130,7 +119,7 @@
   <ContentWrap>
     <el-table v-loading="loading" :data="list">
       <el-table-column label="流程名称" align="center" prop="name" min-width="200px" fixed="left" />
-      <el-table-column label="摘要" prop="summary" min-width="180" fixed="left">
+      <el-table-column label="摘要" prop="summary" width="180" fixed="left">
         <template #default="scope">
           <div class="flex flex-col" v-if="scope.row.summary && scope.row.summary.length > 0">
             <div v-for="(item, index) in scope.row.summary" :key="index">
@@ -146,11 +135,37 @@
         min-width="100"
         fixed="left"
       />
-      <!-- TODO @芋艿:摘要 -->
-      <!-- TODO tuituiji:参考钉钉;1)审批中时,展示审批任务;2)非审批中,展示状态 -->
-      <el-table-column label="流程状态" prop="status" width="120">
+      <el-table-column label="流程状态" prop="status" min-width="200">
         <template #default="scope">
-          <dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
+          <!-- 审批中状态 -->
+          <template
+            v-if="
+              scope.row.status === BpmProcessInstanceStatus.RUNNING && scope.row.tasks?.length > 0
+            "
+          >
+            <!-- 单人审批 -->
+            <template v-if="scope.row.tasks.length === 1">
+              <span>
+                <el-button link type="primary" @click="handleDetail(scope.row)">
+                  {{ scope.row.tasks[0].assigneeUser?.nickname }}
+                </el-button>
+                ({{ scope.row.tasks[0].name }}) 审批中
+              </span>
+            </template>
+            <!-- 多人审批 -->
+            <template v-else>
+              <span>
+                <el-button link type="primary" @click="handleDetail(scope.row)">
+                  {{ scope.row.tasks[0].assigneeUser?.nickname }}
+                </el-button>
+                等 {{ scope.row.tasks.length }} 人 ({{ scope.row.tasks[0].name }})审批中
+              </span>
+            </template>
+          </template>
+          <!-- 非审批中状态 -->
+          <template v-else>
+            <dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
+          </template>
         </template>
       </el-table-column>
       <el-table-column
@@ -167,19 +182,6 @@
         width="180"
         :formatter="dateFormatter"
       />
-      <!--<el-table-column align="center" label="耗时" prop="durationInMillis" width="160">
-        <template #default="scope">
-          {{ scope.row.durationInMillis > 0 ? formatPast2(scope.row.durationInMillis) : '-' }}
-        </template>
-      </el-table-column>
-      <el-table-column label="当前审批任务" align="center" prop="tasks" min-width="120px">
-        <template #default="scope">
-          <el-button type="primary" v-for="task in scope.row.tasks" :key="task.id" link>
-            <span>{{ task.name }}</span>
-          </el-button>
-        </template>
-      </el-table-column>
-      -->
       <el-table-column label="操作" align="center" fixed="right" width="180">
         <template #default="scope">
           <el-button
@@ -215,7 +217,6 @@
   </ContentWrap>
 </template>
 <script lang="ts" setup>
-// TODO @tuituji:List 改成 <Icon icon="ep:plus" class="mr-5px" /> 类似这种组件哈。 RE:done & to check
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import { ElMessageBox } from 'element-plus'
@@ -223,6 +224,7 @@ import * as ProcessInstanceApi from '@/api/bpm/processInstance'
 import { CategoryApi, CategoryVO } from '@/api/bpm/category'
 import { ProcessInstanceVO } from '@/api/bpm/processInstance'
 import * as DefinitionApi from '@/api/bpm/definition'
+import { BpmProcessInstanceStatus } from '@/utils/constants'
 
 defineOptions({ name: 'BpmProcessInstanceMy' })
 
@@ -233,6 +235,7 @@ const { t } = useI18n() // 国际化
 const loading = ref(true) // 列表的加载中
 const total = ref(0) // 列表的总页数
 const list = ref([]) // 列表的数据
+const processDefinitionList = ref<any[]>([]) // 流程定义列表
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
@@ -244,6 +247,7 @@ const queryParams = reactive({
 })
 const queryFormRef = ref() // 搜索的表单
 const categoryList = ref<CategoryVO[]>([]) // 流程分类列表
+const showPopover = ref(false) // 高级筛选是否展示
 
 /** 查询列表 */
 const getList = async () => {
@@ -257,8 +261,6 @@ const getList = async () => {
   }
 }
 
-const showPopover = ref(false)
-
 /** 搜索按钮操作 */
 const handleQuery = () => {
   queryParams.pageNo = 1
@@ -325,10 +327,7 @@ onActivated(() => {
 onMounted(async () => {
   await getList()
   categoryList.value = await CategoryApi.getCategorySimpleList()
+  // 获取流程定义列表
+  processDefinitionList.value = await DefinitionApi.getSimpleProcessDefinitionList()
 })
 </script>
-<style>
-.bold-label .el-form-item__label {
-  font-weight: bold; /* 将字体加粗 */
-}
-</style>

+ 33 - 16
src/views/bpm/task/done/index.vue

@@ -76,26 +76,31 @@
           placement="bottom-end"
         >
           <template #reference>
-            <el-button @click="showPopover = !showPopover" >
+            <el-button @click="showPopover = !showPopover">
               <Icon icon="ep:plus" class="mr-5px" />高级筛选
             </el-button>
-
           </template>
-          <!-- <el-form-item label="流程发起人" class="bold-label" label-position="top" prop="category">
+          <el-form-item
+            label="所属流程"
+            class="font-bold"
+            label-position="top"
+            prop="processDefinitionKey"
+          >
             <el-select
-              v-model="queryParams.category"
-              placeholder="请选择流程发起人"
+              v-model="queryParams.processDefinitionKey"
+              placeholder="请选择流程定义"
               clearable
+              @change="handleQuery"
               class="!w-390px"
             >
               <el-option
-                v-for="category in categoryList"
-                :key="category.code"
-                :label="category.name"
-                :value="category.code"
+                v-for="item in processDefinitionList"
+                :key="item.key"
+                :label="item.name"
+                :value="item.key"
               />
             </el-select>
-          </el-form-item> -->
+          </el-form-item>
           <el-form-item label="发起时间" class="bold-label" label-position="top" prop="createTime">
             <el-date-picker
               v-model="queryParams.createTime"
@@ -111,10 +116,9 @@
             <el-button @click="handleQuery"> 确认</el-button>
             <el-button @click="showPopover = false"> 取消</el-button>
             <el-button @click="resetQuery"> 清空</el-button>
-        </el-form-item>
+          </el-form-item>
         </el-popover>
       </el-form-item>
-
     </el-form>
   </ContentWrap>
 
@@ -122,9 +126,12 @@
   <ContentWrap>
     <el-table v-loading="loading" :data="list">
       <el-table-column align="center" label="流程" prop="processInstance.name" width="180" />
-      <el-table-column label="摘要" prop="processInstance.summary" min-width="180">
+      <el-table-column label="摘要" prop="processInstance.summary" width="180">
         <template #default="scope">
-          <div class="flex flex-col" v-if="scope.row.processInstance.summary && scope.row.processInstance.summary.length > 0">
+          <div
+            class="flex flex-col"
+            v-if="scope.row.processInstance.summary && scope.row.processInstance.summary.length > 0"
+          >
             <div v-for="(item, index) in scope.row.processInstance.summary" :key="index">
               <el-text type="info"> {{ item.key }} : {{ item.value }} </el-text>
             </div>
@@ -170,7 +177,12 @@
           {{ formatPast2(scope.row.durationInMillis) }}
         </template>
       </el-table-column>
-      <el-table-column align="center" label="流程编号" prop="processInstanceId" :show-overflow-tooltip="true" />
+      <el-table-column
+        align="center"
+        label="流程编号"
+        prop="processInstanceId"
+        :show-overflow-tooltip="true"
+      />
       <el-table-column align="center" label="任务编号" prop="id" :show-overflow-tooltip="true" />
       <el-table-column align="center" label="操作" fixed="right" width="80">
         <template #default="scope">
@@ -192,6 +204,7 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { dateFormatter, formatPast2 } from '@/utils/formatTime'
 import * as TaskApi from '@/api/bpm/task'
 import { CategoryApi, CategoryVO } from '@/api/bpm/category'
+import * as DefinitionApi from '@/api/bpm/definition'
 
 defineOptions({ name: 'BpmDoneTask' })
 
@@ -200,17 +213,19 @@ const { push } = useRouter() // 路由
 const loading = ref(true) // 列表的加载中
 const total = ref(0) // 列表的总页数
 const list = ref([]) // 列表的数据
+const processDefinitionList = ref<any[]>([]) // 流程定义列表
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
   name: '',
   category: undefined,
   status: undefined,
+  processDefinitionKey: '',
   createTime: []
 })
 const queryFormRef = ref() // 搜索的表单
 const categoryList = ref<CategoryVO[]>([]) // 流程分类列表
-const showPopover = ref(false)
+const showPopover = ref(false) // 高级筛选是否展示
 
 /** 查询任务列表 */
 const getList = async () => {
@@ -251,5 +266,7 @@ const handleAudit = (row: any) => {
 onMounted(async () => {
   await getList()
   categoryList.value = await CategoryApi.getCategorySimpleList()
+  // 获取流程定义列表
+  processDefinitionList.value = await DefinitionApi.getSimpleProcessDefinitionList()
 })
 </script>

+ 43 - 27
src/views/bpm/task/todo/index.vue

@@ -31,8 +31,7 @@
           搜索
         </el-button>
       </el-form-item>
-
-      <el-form-item label="" prop="category" :style="{ position: 'absolute', right: '130px' }">
+      <el-form-item label="" prop="category" class="absolute right-130px">
         <el-select
           v-model="queryParams.category"
           placeholder="请选择流程分类"
@@ -48,9 +47,8 @@
           />
         </el-select>
       </el-form-item>
-
       <!-- 高级筛选 -->
-      <el-form-item :style="{ position: 'absolute', right: '0px' }">
+      <el-form-item class="absolute right-0">
         <el-popover
           :visible="showPopover"
           persistent
@@ -59,27 +57,32 @@
           placement="bottom-end"
         >
           <template #reference>
-            <el-button @click="showPopover = !showPopover" >
+            <el-button @click="showPopover = !showPopover">
               <Icon icon="ep:plus" class="mr-5px" />高级筛选
             </el-button>
-
           </template>
-          <!-- <el-form-item label="流程发起人" class="bold-label" label-position="top" prop="category">
+          <el-form-item
+            label="所属流程"
+            class="font-bold"
+            label-position="top"
+            prop="processDefinitionKey"
+          >
             <el-select
-              v-model="queryParams.category"
-              placeholder="请选择流程发起人"
+              v-model="queryParams.processDefinitionKey"
+              placeholder="请选择流程定义"
               clearable
+              @change="handleQuery"
               class="!w-390px"
             >
               <el-option
-                v-for="category in categoryList"
-                :key="category.code"
-                :label="category.name"
-                :value="category.code"
+                v-for="item in processDefinitionList"
+                :key="item.key"
+                :label="item.name"
+                :value="item.key"
               />
             </el-select>
-          </el-form-item> -->
-          <el-form-item label="发起时间" class="bold-label" label-position="top" prop="createTime">
+          </el-form-item>
+          <el-form-item label="发起时间" class="font-bold" label-position="top" prop="createTime">
             <el-date-picker
               v-model="queryParams.createTime"
               value-format="YYYY-MM-DD HH:mm:ss"
@@ -87,17 +90,18 @@
               start-placeholder="开始日期"
               end-placeholder="结束日期"
               :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
-              class="!w-240px"
+              class="w-240px!"
             />
           </el-form-item>
-          <el-form-item class="bold-label" label-position="top">
-            <el-button @click="handleQuery"> 确认</el-button>
-            <el-button @click="showPopover = false"> 取消</el-button>
-            <el-button @click="resetQuery"> 清空</el-button>
-        </el-form-item>
+          <el-form-item class="font-bold" label-position="top">
+            <div class="flex justify-end w-full">
+              <el-button @click="resetQuery">清空</el-button>
+              <el-button @click="showPopover = false">取消</el-button>
+              <el-button type="primary" @click="handleQuery">确认</el-button>
+            </div>
+          </el-form-item>
         </el-popover>
       </el-form-item>
-
     </el-form>
   </ContentWrap>
 
@@ -105,9 +109,12 @@
   <ContentWrap>
     <el-table v-loading="loading" :data="list">
       <el-table-column align="center" label="流程" prop="processInstance.name" width="180" />
-      <el-table-column label="摘要" prop="processInstance.summary" min-width="180">
+      <el-table-column label="摘要" prop="processInstance.summary" width="180">
         <template #default="scope">
-          <div class="flex flex-col" v-if="scope.row.processInstance.summary && scope.row.processInstance.summary.length > 0">
+          <div
+            class="flex flex-col"
+            v-if="scope.row.processInstance.summary && scope.row.processInstance.summary.length > 0"
+          >
             <div v-for="(item, index) in scope.row.processInstance.summary" :key="index">
               <el-text type="info"> {{ item.key }} : {{ item.value }} </el-text>
             </div>
@@ -135,7 +142,12 @@
         prop="createTime"
         width="180"
       />
-      <el-table-column align="center" label="流程编号" prop="processInstanceId" :show-overflow-tooltip="true" />
+      <el-table-column
+        align="center"
+        label="流程编号"
+        prop="processInstanceId"
+        :show-overflow-tooltip="true"
+      />
       <el-table-column align="center" label="任务编号" prop="id" :show-overflow-tooltip="true" />
       <el-table-column align="center" label="操作" fixed="right" width="80">
         <template #default="scope">
@@ -157,6 +169,7 @@
 import { dateFormatter } from '@/utils/formatTime'
 import * as TaskApi from '@/api/bpm/task'
 import { CategoryApi, CategoryVO } from '@/api/bpm/category'
+import * as DefinitionApi from '@/api/bpm/definition'
 
 defineOptions({ name: 'BpmTodoTask' })
 
@@ -165,15 +178,18 @@ const { push } = useRouter() // 路由
 const loading = ref(true) // 列表的加载中
 const total = ref(0) // 列表的总页数
 const list = ref([]) // 列表的数据
+const processDefinitionList = ref<any[]>([]) // 流程定义列表
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
   name: '',
   category: undefined,
+  processDefinitionKey: '',
   createTime: []
 })
 const queryFormRef = ref() // 搜索的表单
 const categoryList = ref<CategoryVO[]>([]) // 流程分类列表
+const showPopover = ref(false) // 高级筛选是否展示
 
 /** 查询任务列表 */
 const getList = async () => {
@@ -187,8 +203,6 @@ const getList = async () => {
   }
 }
 
-const showPopover = ref(false)
-
 /** 搜索按钮操作 */
 const handleQuery = () => {
   queryParams.pageNo = 1
@@ -216,5 +230,7 @@ const handleAudit = (row: any) => {
 onMounted(async () => {
   await getList()
   categoryList.value = await CategoryApi.getCategorySimpleList()
+  // 获取流程定义列表
+  processDefinitionList.value = await DefinitionApi.getSimpleProcessDefinitionList()
 })
 </script>

+ 0 - 151
src/views/knowledge/dataset-form/form-step1.vue

@@ -1,151 +0,0 @@
-<template>
-  <div class="upload-container">
-    <!-- 标题 -->
-    <div class="title">
-      <div>选择数据源</div>
-    </div>
-
-    <!-- 数据源选择 -->
-    <div class="resource-btn" >导入已有文本</div>
-
-    <!-- 上传文件区域 -->
-    <el-form>
-      <div class="upload-section">
-        <div class="upload-label">上传文本文件</div>
-        <el-upload
-          class="upload-area"
-          action="#"
-          :file-list="fileList"
-          :on-remove="handleRemove"
-          :before-upload="beforeUpload"
-          list-type="text"
-          drag
-        >
-          <i class="el-icon-upload"></i>
-          <div class="el-upload__text">拖拽文件至此,或者 <em>选择文件</em></div>
-          <div class="el-upload__tip">
-            已支持 TXT、MARKDOWN、PDF、HTML、XLSX、XLS、DOCX、CSV、EML、MSG、PPTX、PPT、XML、EPUB,每个文件不超过 15MB。
-          </div>
-        </el-upload>
-      </div>
-
-      <!-- 下一步按钮 -->
-      <div class="next-button">
-        <el-button type="primary" :disabled="!fileList.length">下一步</el-button>
-      </div>
-    </el-form>
-
-    <!-- 知识库创建 -->
-    <div class="create-knowledge">
-      <el-link type="primary" underline>创建一个空知识库</el-link>
-    </div>
-  </div>
-</template>
-
-<script setup>
-import { ref } from 'vue'
-
-const fileList = ref([])
-
-const handleRemove = (file, fileList) => {
-  console.log(file, fileList)
-}
-
-const beforeUpload = (file) => {
-  fileList.value.push(file)
-  return false
-}
-</script>
-
-<style scoped lang="scss">
-.upload-container {
-  width: 600px;
-  margin: 0 auto;
-  padding: 20px;
-  background-color: #fff;
-  border-radius: 8px;
-  border: 1px solid #ebebeb;
-}
-
-.title {
-  font-size: 22px;
-  font-weight: bold;
-}
-
-.resource-btn {
-  margin-top: 20px;
-  border-radius: 10px;
-  cursor: pointer;
-  width: 150px;
-  border: 1.5px solid #528bff;
-  padding: 10px;
-  text-align: center;
-  font-weight: 500;
-  font-size: 14px;
-  line-height: 30px;
-  color: #101828;
-}
-
-.upload-section {
-  margin: 20px 0;
-  padding-top: 10px;
-}
-
-.upload-label {
-  font-size: 16px;
-  font-weight: bold;
-  margin-bottom: 10px;
-  color: #303133;
-}
-
-.upload-area {
-  margin-top: 10px;
-  border: 1px dashed #d9d9d9;
-  padding: 40px;
-  text-align: center;
-  background-color: #f5f7fa;
-  border-radius: 8px;
-}
-
-.el-upload__text em {
-  color: #409eff;
-  cursor: pointer;
-}
-
-.el-upload__tip {
-  margin-top: 10px;
-  font-size: 12px;
-  color: #909399;
-}
-
-.next-button {
-  text-align: left;
-  margin-top: 20px;
-}
-
-.create-knowledge {
-  text-align: left;
-  margin-top: 20px;
-}
-
-.el-form-item {
-  margin-bottom: 0;
-}
-
-.source-radio-group {
-  display: flex;
-  justify-content: space-between;
-}
-
-.el-radio-button {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  font-size: 14px;
-  padding: 10px 20px;
-}
-
-.el-radio-button .el-icon {
-  margin-right: 8px;
-}
-</style>

File diff suppressed because it is too large
+ 0 - 168
src/views/knowledge/dataset-form/form-step2.vue


+ 0 - 152
src/views/knowledge/dataset.vue

@@ -1,152 +0,0 @@
-<template>
-  <div class="knowledge-base-container">
-    <div class="card-container">
-      <el-card class="create-card" shadow="hover">
-        <div class="create-content">
-          <el-icon class="create-icon"><Plus /></el-icon>
-          <span class="create-text">创建知识库</span>
-        </div>
-        <div class="create-footer">
-          导入您自己的文本数据或通过 Webhook 实时写入数据以增强 LLM 的上下文。
-        </div>
-      </el-card>
-
-      <el-card class="document-card" shadow="hover" v-for="index in 4" :key="index">
-        <div class="document-header">
-          <el-icon><Folder /></el-icon>
-          <span>接口鉴权示例代码.md</span>
-        </div>
-        <div class="document-info">
-          <el-tag size="small">1 文档</el-tag>
-          <el-tag size="small" type="info">5 千字符</el-tag>
-          <el-tag size="small" type="warning">0 关联应用</el-tag>
-        </div>
-        <p class="document-description">
-          useful for when you want to answer queries about the 接口鉴权示例代码.md
-        </p>
-      </el-card>
-    </div>
-
-    <div class="pagination-container">
-      <el-pagination
-        v-model:current-page="currentPage"
-        v-model:page-size="pageSize"
-        :page-sizes="[10, 20, 30, 40]"
-        :small="false"
-        :disabled="false"
-        :background="true"
-        layout="total, sizes, prev, pager, next, jumper"
-        :total="total"
-        @size-change="handleSizeChange"
-        @current-change="handleCurrentChange"
-      />
-    </div>
-  </div>
-</template>
-
-<script setup>
-import { ref } from 'vue'
-import { Folder, Plus } from '@element-plus/icons-vue'
-
-const currentPage = ref(1)
-const pageSize = ref(10)
-const total = ref(100) // 假设总共有100条数据
-
-const handleSizeChange = (val) => {
-  console.log(`每页 ${val} 条`)
-}
-
-const handleCurrentChange = (val) => {
-  console.log(`当前页: ${val}`)
-}
-</script>
-
-<style scoped>
-.knowledge-base-container {
-  font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
-  position: absolute;
-  padding: 20px;
-  margin: 0 auto;
-  display: flex;
-  flex-direction: column;
-  top: 0;
-  bottom: 40px;
-  width: 100%;
-}
-
-.card-container {
-  display: flex;
-  flex-wrap: wrap; /* Enable wrapping */
-  gap: 20px;
-  margin-bottom: auto; /* Pushes pagination to the bottom */
-}
-
-.create-card, .document-card {
-  flex: 1 1 360px; /* Allow cards to grow and shrink */
-  min-width: 0;
-  max-width: 400px;
-  border-radius: 10px;
-  cursor: pointer;
-}
-
-.create-card {
-  background-color: rgba(168, 168, 168, 0.22);
-}
-.create-card:hover {
-  background-color: #fff;
-}
-
-.create-content {
-  display: flex;
-  align-items: center;
-  gap: 10px;
-  margin-bottom: 15px;
-}
-
-.create-icon {
-  font-size: 24px;
-  color: #409EFF;
-}
-
-.create-text {
-  font-size: 18px;
-  font-weight: bold;
-  color: #303133;
-}
-
-.create-footer {
-  font-size: 14px;
-  color: #909399;
-  line-height: 1.5;
-}
-
-.document-header {
-  display: flex;
-  align-items: center;
-  gap: 10px;
-  font-size: 16px;
-  font-weight: bold;
-  margin-bottom: 15px;
-}
-
-.document-info {
-  display: flex;
-  gap: 10px;
-  margin-bottom: 15px;
-}
-
-.document-description {
-  color: #606266;
-  font-size: 14px;
-  line-height: 1.5;
-}
-
-.pagination-container {
-  position: absolute;
-  width: 100%;
-  bottom: 0;
-  display: flex;
-  justify-content: center;
-  margin-top: 20px;
-}
-</style>