From 3e359e4550d018035406a97c190b98f83839ba7f Mon Sep 17 00:00:00 2001
From: houzhongjian <houzhongyi@126.com>
Date: 星期五, 13 十二月 2024 10:41:05 +0800
Subject: [PATCH] 1、工作流重做更新 2、@import修改为@use以应对sass升级弃用@import 3、进入系统后自动redirect到home页

---
 src/api/bpm/category/index.ts                                                                  |   10 
 src/views/bpm/processInstance/create/index_old.vue                                             |  266 +
 src/utils/routerHelper.ts                                                                      |    6 
 src/views/bpm/model/index_old.vue                                                              |  404 +
 src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue                |  419 +
 src/store/modules/permission.ts                                                                |    9 
 src/components/SimpleProcessDesignerV2/src/nodes/EndEventNode.vue                              |  102 
 src/plugins/formCreate/index.ts                                                                |   75 
 src/components/SimpleProcessDesignerV2/src/SimpleProcessModel.vue                              |  140 
 src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss                      |  750 +++
 src/components/FormCreate/src/config/useDictSelectRule.ts                                      |    2 
 src/layout/components/useRenderLayout.tsx                                                      |   38 
 src/components/bpmnProcessDesigner/package/theme/element-variables.scss                        |    2 
 src/router/modules/remaining.ts                                                                |   20 
 src/components/SimpleProcessDesignerV2/src/nodes/InclusiveNode.vue                             |  233 +
 src/views/bpm/definition/index.vue                                                             |   22 
 src/components/SimpleProcessDesignerV2/src/consts.ts                                           |  570 ++
 src/components/SimpleProcessDesignerV2/src/nodes-config/StartUserNodeConfig.vue                |  135 
 src/views/bpm/model/CategoryDraggableModel.vue                                                 |  532 ++
 src/components/SimpleProcessDesignerV2/src/index.ts                                            |    5 
 src/views/bpm/processInstance/index.vue                                                        |  158 
 src/components/bpmnProcessDesigner/package/theme/index.scss                                    |  119 
 src/components/bpmnProcessDesigner/package/designer/plugins/descriptor/flowableDescriptor.json |   70 
 src/components/SimpleProcessDesignerV2/src/nodes-config/UserTaskNodeConfig.vue                 |  905 ++++
 src/assets/svgs/bpm/add-user.svg                                                               |    1 
 src/assets/svgs/bpm/finish.svg                                                                 |    1 
 src/components/SimpleProcessDesignerV2/src/node.ts                                             |  495 ++
 src/layout/components/Message/src/Message.vue                                                  |    8 
 src/views/bpm/processInstance/detail/ProcessInstanceSimpleViewer.vue                           |  168 
 src/components/UserSelectForm/index.vue                                                        |  152 
 src/components/bpmnProcessDesigner/package/penal/custom-config/ElementCustomConfig.vue         |  283 +
 src/views/bpm/model/index.vue                                                                  |  507 -
 src/components/SimpleProcessDesignerV2/src/nodes/ExclusiveNode.vue                             |  229 +
 src/store/modules/bpm/simpleWorkflow.ts                                                        |   55 
 src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue                           |    6 
 src/views/bpm/form/index.vue                                                                   |    3 
 src/views/bpm/processInstance/detail/ProcessInstanceTaskList.vue                               |  194 
 src/views/bpm/processInstance/detail/dialog/TaskSignDeleteForm.vue                             |    2 
 src/components/SimpleProcessDesignerV2/src/NodeHandler.vue                                     |  214 
 vite.config.ts                                                                                 |   25 
 src/assets/svgs/bpm/copy.svg                                                                   |    1 
 src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue                               |  292 +
 src/views/bpm/simple/SimpleModelDesign.vue                                                     |   19 
 src/components/bpmnProcessDesigner/package/penal/properties/ElementProperties.vue              |    2 
 src/components/bpmnProcessDesigner/package/penal/form/ElementForm.vue                          |    6 
 src/assets/svgs/bpm/condition.svg                                                              |    1 
 src/components/bpmnProcessDesigner/package/designer/ProcessViewer.vue                          |  959 +--
 src/assets/svgs/bpm/cancel.svg                                                                 |    1 
 src/layout/components/Setting/src/Setting.vue                                                  |    5 
 src/assets/svgs/bpm/parallel.svg                                                               |    1 
 src/store/modules/tagsView.ts                                                                  |   38 
 src/views/bpm/processInstance/detail/index_new.vue                                             |  319 +
 src/components/bpmnProcessDesigner/package/penal/task/ElementTask.vue                          |    9 
 src/views/bpm/model/editor/index.vue                                                           |    6 
 src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue                        |  889 +++
 src/views/bpm/processListener/ProcessListenerForm.vue                                          |    2 
 src/components/SimpleProcessDesignerV2/src/utils.ts                                            |   41 
 src/views/bpm/processInstance/create/ProcessDefinitionDetail.vue                               |  259 +
 src/views/bpm/task/manager/index.vue                                                           |    1 
 src/api/bpm/task/index.ts                                                                      |   55 
 src/views/bpm/task/done/index.vue                                                              |    4 
 src/components/FormCreate/src/components/useApiSelect.tsx                                      |    5 
 src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue                        |    2 
 src/assets/svgs/bpm/running.svg                                                                |    1 
 src/views/bpm/simpleWorkflow/index.vue                                                         |   13 
 src/api/bpm/processInstance/index.ts                                                           |   46 
 src/assets/svgs/bpm/starter.svg                                                                |    1 
 src/views/bpm/category/CategoryForm.vue                                                        |    7 
 src/components/SimpleProcessDesignerV2/src/nodes/CopyTaskNode.vue                              |   97 
 src/views/bpm/processInstance/manager/index.vue                                                |    6 
 src/components/SimpleProcessDesignerV2/src/nodes/ParallelNode.vue                              |  184 
 src/components/SimpleProcessDesignerV2/theme/iconfont.woff                                     |    0 
 src/layout/components/TagsView/src/TagsView.vue                                                |  146 
 src/utils/constants.ts                                                                         |   20 
 src/components/SimpleProcessDesignerV2/theme/iconfont.ttf                                      |    0 
 src/layout/components/UserInfo/src/UserInfo.vue                                                |    4 
 src/components/bpmnProcessDesigner/package/penal/listeners/ElementListeners.vue                |    2 
 src/assets/svgs/bpm/simple-process-bg.svg                                                      |    1 
 src/layout/components/UserInfo/src/components/LockPage.vue                                     |    2 
 src/utils/dict.ts                                                                              |    1 
 src/layout/components/Menu/src/Menu.vue                                                        |   15 
 src/assets/svgs/bpm/approve.svg                                                                |    1 
 src/views/bpm/form/editor/index.vue                                                            |   63 
 src/layout/components/TabMenu/src/TabMenu.vue                                                  |    6 
 src/components/SimpleProcessDesignerV2/src/SimpleProcessViewer.vue                             |   48 
 src/components/SimpleProcessDesignerV2/src/nodes-config/CopyTaskNodeConfig.vue                 |  374 +
 src/components/bpmnProcessDesigner/package/theme/process-designer.scss                         |    6 
 src/views/bpm/group/UserGroupForm.vue                                                          |    2 
 src/views/bpm/model/ModelForm.vue                                                              |  234 
 src/assets/svgs/bpm/auditor.svg                                                                |    1 
 src/components/bpmnProcessDesigner/package/penal/multi-instance/ElementMultiInstance.vue       |    3 
 src/components/SimpleProcessDesignerV2/src/nodes/StartUserNode.vue                             |  154 
 src/views/bpm/processInstance/detail/index.vue                                                 |  591 +-
 src/components/SimpleProcessDesignerV2/src/SimpleProcessDesigner.vue                           |  179 
 src/layout/components/AppView.vue                                                              |   23 
 src/api/bpm/model/index.ts                                                                     |   20 
 src/components/bpmnProcessDesigner/package/utils.ts                                            |    2 
 src/layout/components/Breadcrumb/src/Breadcrumb.vue                                            |    6 
 src/views/bpm/processInstance/detail/ProcessInstanceBpmnViewer.vue                             |   67 
 src/api/bpm/simple/index.ts                                                                    |   15 
 src/layout/components/Footer/src/Footer.vue                                                    |    4 
 src/layout/components/UserInfo/src/components/LockDialog.vue                                   |    2 
 src/views/bpm/task/todo/index.vue                                                              |    4 
 src/views/bpm/processExpression/ProcessExpressionForm.vue                                      |    2 
 src/views/bpm/task/copy/index.vue                                                              |   27 
 src/components/SimpleProcessDesignerV2/theme/iconfont.woff2                                    |    0 
 src/assets/svgs/bpm/reject.svg                                                                 |    1 
 src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue                                 |  118 
 src/views/bpm/processInstance/create/index.vue                                                 |  423 +
 /dev/null                                                                                      |  141 
 src/components/SimpleProcessDesignerV2/src/nodes/UserTaskNode.vue                              |  174 
 package.json                                                                                   |   25 
 src/components/FormCreate/src/utils/index.ts                                                   |   43 
 113 files changed, 11,427 insertions(+), 2,130 deletions(-)

diff --git a/package.json b/package.json
index 435c717..f118cca 100644
--- a/package.json
+++ b/package.json
@@ -10,11 +10,11 @@
     "dev": "vite --mode env.local",
     "dev-server": "vite --mode dev",
     "ts:check": "vue-tsc --noEmit",
-    "build:local": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build",
-    "build:dev": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode dev",
-    "build:test": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode test",
-    "build:stage": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode stage",
-    "build:prod": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode prod",
+    "build:local": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build",
+    "build:dev": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build --mode dev",
+    "build:test": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build --mode test",
+    "build:stage": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build --mode stage",
+    "build:prod": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build --mode prod",
     "serve:dev": "vite preview --mode dev",
     "serve:prod": "vite preview --mode prod",
     "preview": "pnpm build:local && vite preview",
@@ -27,8 +27,8 @@
   },
   "dependencies": {
     "@element-plus/icons-vue": "^2.1.0",
-    "@form-create/designer": "^3.1.3",
-    "@form-create/element-ui": "^3.1.24",
+    "@form-create/designer": "^3.2.6",
+    "@form-create/element-ui": "^3.2.11",
     "@iconify/iconify": "^3.1.1",
     "@microsoft/fetch-event-source": "^2.0.1",
     "@videojs-player/vue": "^1.0.0",
@@ -48,7 +48,7 @@
     "driver.js": "^1.3.1",
     "echarts": "^5.5.0",
     "echarts-wordcloud": "^2.1.0",
-    "element-plus": "2.7.0",
+    "element-plus": "2.8.4",
     "fast-xml-parser": "^4.3.2",
     "highlight.js": "^11.9.0",
     "jsencrypt": "^3.3.2",
@@ -65,13 +65,14 @@
     "pinia-plugin-persistedstate": "^3.2.1",
     "qrcode": "^1.5.3",
     "qs": "^6.12.0",
+    "sortablejs": "^1.15.3",
     "steady-xml": "^0.1.0",
     "url": "^0.11.3",
     "video.js": "^7.21.5",
-    "vue": "3.4.21",
+    "vue": "3.5.12",
     "vue-dompurify-html": "^4.1.4",
     "vue-i18n": "9.10.2",
-    "vue-router": "^4.3.0",
+    "vue-router": "4.4.5",
     "vue-types": "^5.1.1",
     "vuedraggable": "^4.1.0",
     "web-storage-cache": "^1.1.1",
@@ -97,7 +98,7 @@
     "@vitejs/plugin-vue": "^5.0.4",
     "@vitejs/plugin-vue-jsx": "^3.1.0",
     "autoprefixer": "^10.4.17",
-    "bpmn-js": "8.9.0",
+    "bpmn-js": "8.10.0",
     "bpmn-js-properties-panel": "0.46.0",
     "consola": "^3.2.3",
     "eslint": "^8.57.0",
@@ -132,7 +133,7 @@
     "vite-plugin-progress": "^0.0.7",
     "vite-plugin-purge-icons": "^0.10.0",
     "vite-plugin-svg-icons": "^2.0.1",
-    "vite-plugin-top-level-await": "^1.3.1",
+    "vite-plugin-top-level-await": "^1.4.4",
     "vue-eslint-parser": "^9.3.2",
     "vue-tsc": "^1.8.27"
   },
diff --git a/src/api/bpm/category/index.ts b/src/api/bpm/category/index.ts
index d1e109c..1854f31 100644
--- a/src/api/bpm/category/index.ts
+++ b/src/api/bpm/category/index.ts
@@ -36,6 +36,16 @@
     return await request.put({ url: `/bpm/category/update`, data })
   },
 
+  // 批量修改流程分类的排序
+  updateCategorySortBatch: async (ids: number[]) => {
+    return await request.put({
+      url: `/bpm/category/update-sort-batch`,
+      params: {
+        ids: ids.join(',')
+      }
+    })
+  },
+
   // 删除流程分类
   deleteCategory: async (id: number) => {
     return await request.delete({ url: `/bpm/category/delete?id=` + id })
diff --git a/src/api/bpm/model/index.ts b/src/api/bpm/model/index.ts
index 2b484a6..0c499db 100644
--- a/src/api/bpm/model/index.ts
+++ b/src/api/bpm/model/index.ts
@@ -26,11 +26,11 @@
   bpmnXml: string
 }
 
-export const getModelPage = async (params) => {
-  return await request.get({ url: '/bpm/model/page', params })
+export const getModelList = async (name: string | undefined) => {
+  return await request.get({ url: '/bpm/model/list', params: { name } })
 }
 
-export const getModel = async (id: number) => {
+export const getModel = async (id: string) => {
   return await request.get({ url: '/bpm/model/get?id=' + id })
 }
 
@@ -38,6 +38,20 @@
   return await request.put({ url: '/bpm/model/update', data: data })
 }
 
+// 批量修改流程分类的排序
+export const updateModelSortBatch = async (ids: number[]) => {
+  return await request.put({
+    url: `/bpm/model/update-sort-batch`,
+    params: {
+      ids: ids.join(',')
+    }
+  })
+}
+
+export const updateModelBpmn = async (data: ModelVO) => {
+  return await request.put({ url: '/bpm/model/update-bpmn', data: data })
+}
+
 // 任务状态修改
 export const updateModelState = async (id: number, state: number) => {
   const data = {
diff --git a/src/api/bpm/processInstance/index.ts b/src/api/bpm/processInstance/index.ts
index 9122b2b..f97270f 100644
--- a/src/api/bpm/processInstance/index.ts
+++ b/src/api/bpm/processInstance/index.ts
@@ -1,6 +1,6 @@
 import request from '@/config/axios'
 import { ProcessDefinitionVO } from '@/api/bpm/model'
-
+import { NodeType, CandidateStrategy } from '@/components/SimpleProcessDesignerV2/src/consts'
 export type Task = {
   id: string
   name: string
@@ -20,6 +20,35 @@
   createTime: string
   endTime: string
   processDefinition?: ProcessDefinitionVO
+}
+
+// 用户信息
+export type User = {
+  id: number
+  nickname: string
+  avatar: string
+}
+
+// 审批任务信息
+export type ApprovalTaskInfo = {
+  id: number
+  ownerUser: User
+  assigneeUser: User
+  status: number
+  reason: string
+}
+
+// 审批节点信息
+export type ApprovalNodeInfo = {
+  id: number
+  name: string
+  nodeType: NodeType
+  candidateStrategy?: CandidateStrategy
+  status: number
+  startTime?: Date
+  endTime?: Date
+  candidateUsers?: User[]
+  tasks: ApprovalTaskInfo[]
 }
 
 export const getProcessInstanceMyPage = async (params: any) => {
@@ -57,3 +86,18 @@
 export const getProcessInstanceCopyPage = async (params: any) => {
   return await request.get({ url: '/bpm/process-instance/copy/page', params })
 }
+
+// 获取审批详情
+export const getApprovalDetail = async (params: any) => {
+  return await request.get({ url: 'bpm/process-instance/get-approval-detail' , params })
+}
+
+// 获取表单字段权限
+export const getFormFieldsPermission = async (params: any) => {
+  return await request.get({ url: '/bpm/process-instance/get-form-fields-permission', params })
+}
+
+// 获取流程实例的 BPMN 模型视图
+export const getProcessInstanceBpmnModelView = async (id: string) => {
+  return await request.get({ url: '/bpm/process-instance/get-bpmn-model-view?id=' + id })
+}
diff --git a/src/api/bpm/simple/index.ts b/src/api/bpm/simple/index.ts
new file mode 100644
index 0000000..6e1e995
--- /dev/null
+++ b/src/api/bpm/simple/index.ts
@@ -0,0 +1,15 @@
+import request from '@/config/axios'
+
+
+export const updateBpmSimpleModel = async (data) => {
+  return await request.post({
+    url: '/bpm/model/simple/update',
+    data: data
+  })
+}
+
+export const getBpmSimpleModel = async (id) => {
+  return await request.get({
+    url: '/bpm/model/simple/get?id=' + id
+  })
+}
diff --git a/src/api/bpm/task/index.ts b/src/api/bpm/task/index.ts
index f3cda9f..d4c1038 100644
--- a/src/api/bpm/task/index.ts
+++ b/src/api/bpm/task/index.ts
@@ -1,7 +1,44 @@
 import request from '@/config/axios'
 
-export type TaskVO = {
-  id: number
+/**
+ * 任务状态枚举
+ */
+export enum TaskStatusEnum {
+  /**
+   * 未开始
+   */
+  NOT_START = -1,
+
+  /**
+   * 待审批
+   */
+  WAIT = 0,
+  /**
+   * 审批中
+   */
+  RUNNING = 1,
+  /**
+   * 审批通过
+   */
+  APPROVE = 2,
+
+  /**
+   * 审批不通过
+   */
+  REJECT = 3,
+
+  /**
+   * 已取消
+   */
+  CANCEL = 4,
+  /**
+   * 已退回
+   */
+  RETURN = 5,
+  /**
+   * 审批通过中
+   */
+  APPROVING = 7
 }
 
 export const getTaskTodoPage = async (params: any) => {
@@ -30,12 +67,12 @@
   })
 }
 
-// 获取所有可回退的节点
+// 获取所有可退回的节点
 export const getTaskListByReturn = async (id: string) => {
   return await request.get({ url: '/bpm/task/list-by-return', params: { id } })
 }
 
-// 回退
+// 退回
 export const returnTask = async (data: any) => {
   return await request.put({ url: '/bpm/task/return', data })
 }
@@ -60,6 +97,16 @@
   return await request.delete({ url: '/bpm/task/delete-sign', data })
 }
 
+// 抄送
+export const copyTask = async (data: any) => {
+  return await request.put({ url: '/bpm/task/copy', data })
+}
+
+// 获取我的待办任务
+export const myTodoTask = async (processInstanceId: string) => {
+  return await request.get({ url: '/bpm/task/my-todo?processInstanceId=' + processInstanceId })
+}
+
 // 获取减签任务列表
 export const getChildrenTaskList = async (id: string) => {
   return await request.get({ url: '/bpm/task/list-by-parent-task-id?parentTaskId=' + id })
diff --git a/src/assets/svgs/bpm/add-user.svg b/src/assets/svgs/bpm/add-user.svg
new file mode 100644
index 0000000..bc7bdbf
--- /dev/null
+++ b/src/assets/svgs/bpm/add-user.svg
@@ -0,0 +1 @@
+<svg t="1731390087280" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4297" width="200" height="200"><path d="M639.9 541.7c76.4-44.2 127.9-126.8 127.9-221.5C767.7 179 653.2 64.5 512 64.5S256.3 179 256.3 320.2c0 89.6 46.1 168.4 115.8 214.1C193.5 593 64.5 761.2 64.5 959.5h63.9c0-211.5 172.1-383.6 383.6-383.6 44.9 0 87.8 8.1 127.9 22.4v-56.6zM320.2 320.2c0-105.8 86-191.8 191.8-191.8s191.8 86 191.8 191.8S617.7 512 512 512s-191.8-86-191.8-191.8zM831.6 767.7V639.9h-63.9v127.8H639.9v63.9h127.8v127.9h63.9V831.6h127.9v-63.9z" fill="#5f6266" p-id="4298"></path></svg>
\ No newline at end of file
diff --git a/src/assets/svgs/bpm/approve.svg b/src/assets/svgs/bpm/approve.svg
new file mode 100644
index 0000000..06aa09d
--- /dev/null
+++ b/src/assets/svgs/bpm/approve.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1724316565416" class="icon" viewBox="0 0 1300 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1339" xmlns:xlink="http://www.w3.org/1999/xlink" width="253.90625" height="200"><path d="M784.058182 99.258182l10.938182 18.385454-21.294546-2.56-14.196363 16.058182-4.072728-21.061818-19.781818-8.494545 18.734546-10.472728 2.094545-21.294545 15.709091 14.545454 20.945454-4.654545-9.076363 19.549091zM1067.287273 642.443636l-18.501818 10.821819 2.56-21.294546-16.058182-14.196364 21.061818-4.072727 8.494545-19.665454 10.472728 18.734545 21.294545 1.978182-14.661818 15.709091 4.770909 20.945454-19.432727-8.96z" fill="#13C463" p-id="1340"></path><path d="M1067.287273 642.443636l-18.501818 10.821819 2.56-21.294546-16.058182-14.196364 21.061818-4.072727 8.494545-19.665454 10.472728 18.734545 21.294545 1.978182-14.661818 15.709091 4.770909 20.945454-19.432727-8.96zM571.927273 100.072727l-17.454546-12.567272 20.596364-6.167273 6.516364-20.48 12.218181 17.570909 21.410909-0.116364-12.916363 17.105455 6.749091 20.363636-20.247273-6.981818-17.338182 12.683636 0.465455-21.410909zM991.418182 784.407273l-21.178182 3.490909 10.123636-18.967273-9.774545-18.967273 21.061818 3.723637 15.127273-15.243637 2.909091 21.294546 19.2 9.658182-19.316364 9.309091-3.258182 21.178181-14.894545-15.476363zM427.985455 156.741818L407.272727 151.505455l16.872728-13.265455-1.396364-21.410909 17.803636 11.985454 20.014546-7.912727-5.934546 20.596364 13.730909 16.523636-21.410909 0.814546-11.52 18.152727-7.447272-20.247273zM854.225455 896.465455l-20.712728-5.352728 16.872728-13.265454-1.396364-21.294546 17.803636 11.869091 20.014546-7.912727-5.934546 20.712727 13.730909 16.523637-21.527272 0.814545-11.403637 18.036364-7.447272-20.130909zM562.501818 923.694545l10.821818 18.385455-21.294545-2.56-14.196364 16.058182-4.072727-21.061818-19.665455-8.494546 18.734546-10.356363 1.978182-21.41091 15.709091 14.661819 20.945454-4.770909-8.96 19.54909zM242.734545 420.770909l-18.385454 10.938182 2.56-21.294546-16.058182-14.196363 21.061818-4.189091 8.494546-19.665455 10.356363 18.734546 21.410909 2.094545-14.545454 15.709091 4.654545 20.945455-19.549091-9.076364z" fill="#13C463" p-id="1341"></path><path d="M242.734545 420.770909l-18.385454 10.938182 2.56-21.294546-16.058182-14.196363 21.061818-4.189091 8.494546-19.665455 10.356363 18.734546 21.410909 2.094545-14.545454 15.709091 4.654545 20.945455-19.549091-9.076364zM700.858182 943.941818l-17.454546-12.450909 20.48-6.283636 6.516364-20.48 12.334545 17.687272 21.41091-0.116363-12.916364 17.105454 6.632727 20.363637-20.247273-7.098182-17.221818 12.683636 0.465455-21.410909zM303.592727 278.807273l-21.178182 3.490909 10.123637-18.967273-9.890909-18.967273 21.178182 3.723637 15.010909-15.243637 2.909091 21.294546 19.2 9.541818-19.316364 9.425455-3.258182 21.178181-14.778182-15.476363z" fill="#13C463" p-id="1342"></path><path d="M407.272727 90.647273a486.632727 486.632727 0 0 1 504.552728 11.636363l25.018181-14.429091A512 512 0 0 0 139.636364 546.909091l25.018181-14.429091A486.981818 486.981818 0 0 1 407.272727 90.647273zM893.323636 933.352727a486.749091 486.749091 0 0 1-504.669091-11.636363l-24.901818 14.429091A512 512 0 0 0 1161.192727 477.090909l-24.901818 13.963636a486.981818 486.981818 0 0 1-242.967273 442.298182z" fill="#13C463" p-id="1343"></path><path d="M814.545455 795.927273a327.447273 327.447273 0 0 1-258.21091 29.556363l-29.78909 17.105455A353.163636 353.163636 0 0 0 998.865455 570.181818l-29.789091 17.105455A326.865455 326.865455 0 0 1 814.545455 795.927273zM486.865455 228.072727A327.447273 327.447273 0 0 1 744.727273 198.516364l29.789091-17.105455A353.163636 353.163636 0 0 0 302.545455 453.818182l29.78909-17.105455A326.865455 326.865455 0 0 1 486.865455 228.072727zM1288.378182 374.690909a53.294545 53.294545 0 0 1-14.429091 11.636364L229.469091 989.090909a53.876364 53.876364 0 0 1-73.425455-19.665454L7.214545 710.632727a53.527273 53.527273 0 0 1 19.781819-73.309091L1071.476364 34.909091a53.876364 53.876364 0 0 1 73.425454 19.665454l148.829091 258.327273a53.061818 53.061818 0 0 1 5.352727 40.727273 55.272727 55.272727 0 0 1-10.705454 21.061818zM32.232727 665.716364A28.043636 28.043636 0 0 0 29.323636 698.181818l148.829091 257.978182a28.392727 28.392727 0 0 0 38.516364 10.356364l1044.48-601.949091a28.16 28.16 0 0 0 10.356364-38.516364L1122.676364 67.84a28.276364 28.276364 0 0 0-38.4-10.356364L39.68 659.432727a27.810909 27.810909 0 0 0-7.447273 6.283637z" fill="#13C463" p-id="1344"></path><path d="M356.770909 569.250909l22.341818 38.749091-15.476363 8.727273L349.090909 592.64l-153.483636 88.785455 14.778182 25.483636-15.476364 8.96-23.272727-39.912727L256 627.2c-6.283636-4.887273-11.636364-8.843636-16.174545-11.636364L256 602.647273c3.956364 3.141818 9.774545 8.261818 17.338182 15.127272z m-17.338182 199.447273l-49.221818 28.392727 7.563636 13.149091-15.476363 8.96-62.138182-107.52 64.814545-37.469091-12.8-22.574545 15.941819-9.192728 12.8 22.109091 65.396363-37.701818 61.672728 106.821818-15.476364 8.96-7.214546-12.450909-49.92 28.858182 26.065455 45.032727-16.058182 9.192728z m-46.545454-79.825455L244.363636 717.265455l14.778182 25.6 49.221818-28.509091zM267.636364 756.945455l14.778181 25.6 49.221819-28.509091-14.778182-25.483637z m106.938181-80.523637l-14.778181-25.483636-49.92 28.741818 14.778181 25.483636zM346.996364 744.727273l49.803636-28.741818-14.661818-25.483637-49.92 28.741818zM505.832727 609.978182c-4.654545 6.283636-10.123636 13.265455-16.523636 21.061818l35.84 62.021818a18.967273 18.967273 0 0 1-6.749091 29.672727l-19.316364 11.636364-12.450909-13.847273a170.123636 170.123636 0 0 0 17.803637-8.727272 8.494545 8.494545 0 0 0 2.909091-13.614546L477.090909 645.352727l-9.890909 10.472728-10.007273 10.24-12.683636-13.149091c9.309091-8.261818 17.221818-15.941818 23.272727-23.272728l-31.301818-54.341818-25.018182 14.545455-8.843636-15.36 25.018182-14.429091-23.272728-41.076364 15.476364-8.96 23.272727 41.076364L465.454545 538.763636l8.843637 15.36-22.109091 12.567273 28.509091 49.221818c5.469091-6.516364 10.938182-13.498182 16.407273-21.061818z m9.076364-45.730909L572.043636 663.272727a207.825455 207.825455 0 0 0 23.272728-27.461818l11.636363 13.149091a365.381818 365.381818 0 0 1-41.774545 45.498182l-12.567273-12.567273a11.636364 11.636364 0 0 0 1.745455-13.963636L453.818182 493.963636l15.709091-9.076363 36.887272 63.883636 31.301819-18.152727 8.96 15.592727z m129.745454 83.316363a20.596364 20.596364 0 0 1-31.418181-9.774545l-103.098182-178.618182 15.709091-9.192727 38.632727 67.025454a200.261818 200.261818 0 0 0 28.043636-41.076363l16.872728 7.68a303.243636 303.243636 0 0 1-35.723637 49.338182l53.410909 93.090909a9.192727 9.192727 0 0 0 13.963637 4.072727l10.821818-6.283636a14.312727 14.312727 0 0 0 8.029091-11.636364 103.447273 103.447273 0 0 0-15.243637-39.098182l17.338182-3.84c12.567273 25.134545 18.036364 41.658182 16.290909 49.803636A28.392727 28.392727 0 0 1 663.272727 636.741818zM860.276364 521.774545c-7.563636 4.421818-20.829091 11.636364-39.912728 22.574546a179.432727 179.432727 0 0 1-37.352727 16.174545 58.181818 58.181818 0 0 1-33.047273-1.978181 14.312727 14.312727 0 0 0-11.636363-0.581819c-5.352727 3.025455-8.261818 18.385455-8.727273 45.847273l-18.269091-3.956364c1.047273-25.483636 5.003636-42.821818 11.636364-52.014545l-38.865455-67.374545-31.534545 18.152727-8.378182-14.661818 46.545454-26.647273 47.825455 82.850909a55.505455 55.505455 0 0 1 8.494545 1.861818 59.694545 59.694545 0 0 0 25.367273 4.072727 101.701818 101.701818 0 0 0 33.512727-11.636363L849.454545 508.509091l31.418182-18.734546c11.636364-7.214545 19.898182-12.334545 24.087273-15.127272l5.469091 18.152727zM676.072727 413.207273L671.185455 430.545455a279.272727 279.272727 0 0 0-58.181819-13.265455l4.887273-16.64a307.781818 307.781818 0 0 1 58.181818 12.567273zM754.967273 372.363636a261.818182 261.818182 0 0 0 20.247272-38.516363l-98.443636 56.785454-7.796364-13.498182 119.97091-69.46909 6.632727 11.636363a281.134545 281.134545 0 0 1-25.949091 54.807273l5.236364 0.930909L818.734545 349.090909l57.25091 99.025455a18.385455 18.385455 0 0 1-8.843637 27.927272l-18.385454 10.589091-11.636364-11.636363 17.92-9.425455a7.796364 7.796364 0 0 0 3.607273-11.636364L849.454545 437.410909l-37.236363 21.527273 21.992727 38.050909-14.894545 8.610909-21.992728-38.167273L760.203636 488.727273l22.458182 38.749091-15.127273 8.727272L699.461818 418.909091l55.389091-32a306.269091 306.269091 0 0 0-39.330909-1.047273l4.305455-15.127273c13.265455-0.232727 24.901818 0.465455 35.141818 1.629091z m15.825454 49.454546l-11.636363-20.014546-37.003637 21.410909 11.636364 20.014546z m-29.44 34.909091l11.636364 19.549091 37.003636-21.410909-11.636363-19.549091z m81.454546-64.814546l-11.636364-19.898182-37.236364 21.527273 11.636364 19.898182z m-29.556364 34.909091l11.636364 19.432727 37.236363-21.527272-11.636363-19.432728zM1086.370909 391.214545l-19.898182 11.636364-10.589091 6.167273-10.938181 6.050909a186.181818 186.181818 0 0 1-38.749091 16.989091 60.16 60.16 0 0 1-33.978182-1.978182 14.312727 14.312727 0 0 0-11.636364 0c-5.585455 3.258182-8.610909 18.734545-8.96 46.545455l-18.036363-3.723637c0.814545-26.181818 4.770909-43.752727 11.636363-52.945454l-38.865454-67.141819-31.883637 18.385455-8.727272-15.010909 47.243636-27.345455 47.941818 83.2h4.189091a32.465455 32.465455 0 0 1 4.538182 1.163637 71.68 71.68 0 0 0 26.298182 3.490909 112.872727 112.872727 0 0 0 34.210909-13.265455c16.523636-9.192727 31.767273-17.803636 46.545454-25.949091l14.545455-8.727272 14.196363-8.727273c11.636364-6.865455 18.618182-11.636364 22.574546-14.196364l5.352727 18.385455zM896 286.021818l-4.770909 18.385455a296.378182 296.378182 0 0 0-58.181818-14.661818l4.770909-16.872728a311.156364 311.156364 0 0 1 58.181818 13.149091zM1031.098182 384l-12.334546-13.149091c11.636364-5.934545 21.76-11.636364 30.138182-15.941818a9.658182 9.658182 0 0 0 4.189091-14.661818l-54.341818-94.138182-83.781818 48.290909-9.076364-15.709091 83.781818-48.407273-20.712727-35.84 16.174545-9.425454 20.712728 36.072727 32.814545-18.967273 8.610909 15.243637-32.349091 18.850909 56.552728 97.978182a20.247273 20.247273 0 0 1-8.843637 31.185454z m-23.272727-59.345455L1000.727273 340.48a405.876364 405.876364 0 0 0-58.181818-25.6l7.796363-15.127273a393.890909 393.890909 0 0 1 57.716364 24.436364z" fill="#13C463" p-id="1345"></path></svg>
\ No newline at end of file
diff --git a/src/assets/svgs/bpm/auditor.svg b/src/assets/svgs/bpm/auditor.svg
new file mode 100644
index 0000000..66d2c2c
--- /dev/null
+++ b/src/assets/svgs/bpm/auditor.svg
@@ -0,0 +1 @@
+<svg t="1729561718271" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8640" width="200" height="200"><path d="M908.5952 920.4224H164.7616a31.0784 31.0784 0 0 1-30.976-30.976c0-17.0496 13.9264-30.976 30.976-30.976h743.8336c17.0496 0 31.0272 13.9264 31.0272 30.976a31.0784 31.0784 0 0 1-31.0272 30.976z m0-123.9552H164.7616a31.0784 31.0784 0 0 1-30.976-30.976v-154.9824c0-51.1488 41.8304-92.9792 92.9792-92.9792h198.3488c-6.1952-37.1712-24.7808-72.8064-51.1488-103.8336a216.576 216.576 0 0 1-54.2208-144.128c0-58.88 23.2448-114.688 66.6112-156.4672C429.7728 71.168 485.5296 51.0976 545.9968 52.6848c111.5648 4.608 206.08 100.6592 207.616 212.2752 1.536 55.808-20.1216 110.0288-57.344 151.8592-26.3168 27.904-41.8304 61.952-48.0256 100.7104h198.3488c51.2 0 93.0304 41.8304 93.0304 92.9792v154.9824a31.0784 31.0784 0 0 1-31.0272 30.976z m-712.8064-61.952H877.568v-124.0064a31.0784 31.0784 0 0 0-31.0272-30.976h-232.448a31.0784 31.0784 0 0 1-30.976-31.0272c0-65.024 23.2448-127.0784 66.6624-173.568 27.8528-29.3888 41.8304-68.1472 41.8304-108.4416-1.536-80.5888-68.1984-148.7872-148.7872-151.8592a150.528 150.528 0 0 0-113.152 43.3664 153.6 153.6 0 0 0-48.0256 111.616c0 37.1712 13.9776 74.3424 38.7584 102.2464 44.9536 51.1488 69.7344 113.152 69.7344 176.64a31.0784 31.0784 0 0 1-30.976 31.0272h-232.448a31.0784 31.0784 0 0 0-30.976 30.976v123.9552z" fill="#fff" p-id="8641"></path></svg>
\ No newline at end of file
diff --git a/src/assets/svgs/bpm/cancel.svg b/src/assets/svgs/bpm/cancel.svg
new file mode 100644
index 0000000..ab9b155
--- /dev/null
+++ b/src/assets/svgs/bpm/cancel.svg
@@ -0,0 +1 @@
+<svg t="1729178183592" class="icon" viewBox="0 0 1300 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4332" width="200" height="200"><path d="M784.074702 99.196443l10.927871 18.473304-21.302843-2.56935-14.180213 16.066571-4.130475-21.042655-19.676671-8.521137 18.733492-10.440019 2.016452-21.335366 15.708814 14.603017 20.945085-4.683373-9.041512 19.449008zM1067.22363 642.402668l-18.440781 10.92787 2.56935-21.302842-16.099094-14.180213 21.042655-4.130475 8.521137-19.676671 10.440019 18.733492 21.367889 2.016452-14.603017 15.708814 4.683373 20.945085-19.481531-9.041512z" fill="#8a8a8a" p-id="4333"></path><path d="M1067.22363 642.402668l-18.440781 10.92787 2.56935-21.302842-16.099094-14.180213 21.042655-4.130475 8.521137-19.676671 10.440019 18.733492 21.367889 2.016452-14.603017 15.708814 4.683373 20.945085-19.481531-9.041512zM571.924408 100.009528l-17.400031-12.488994 20.52228-6.211974 6.504685-20.457234 12.261331 17.595172 21.432936-0.09757-12.944323 17.074798 6.732349 20.359663-20.262093-7.02506-17.269938 12.716659 0.422804-21.46546zM991.444053 784.43246l-21.172749 3.480006 10.114785-18.928632-9.822074-19.026203 21.107702 3.772717 15.090868-15.253486 2.927109 21.237796 19.156296 9.626933-19.318914 9.366746-3.219819 21.205273-14.863204-15.48115zM428.008258 156.795426l-20.749945-5.333841 16.879657-13.237034-1.365983-21.400413 17.822836 11.936097 19.936859-7.870669-5.88674 20.619851 13.692361 16.521899-21.432936 0.813086-11.513292 18.083024-7.382817-20.132zM854.260251 896.475655l-20.749945-5.333841 16.879657-13.237034-1.365983-21.400413 17.822836 11.96862 19.936859-7.903192-5.854217 20.619851 13.659838 16.554423-21.432936 0.780562-11.513292 18.115547-7.382817-20.164523zM562.460092 923.665237l10.895347 18.440782-21.302843-2.569351-14.180212 16.099095-4.130475-21.042655-19.676672-8.521137 18.733493-10.440019 2.016452-21.36789 15.708814 14.603018 20.945085-4.683373-9.008989 19.48153zM242.787359 420.788058l-18.473305 10.895347 2.569351-21.302843-16.066572-14.180213 21.042656-4.130474 8.521137-19.676672 10.440019 18.733492 21.335366 2.016453-14.603018 15.708813 4.683374 20.945085-19.449008-9.008988z" fill="#8a8a8a" p-id="4334"></path><path d="M242.787359 420.788058l-18.473305 10.895347 2.569351-21.302843-16.066572-14.180213 21.042656-4.130474 8.521137-19.676672 10.440019 18.733492 21.335366 2.016453-14.603018 15.708813 4.683374 20.945085-19.449008-9.008988zM700.814737 943.959854l-17.400032-12.521518 20.522281-6.211974 6.504685-20.42471 12.26133 17.595172 21.432937-0.130094-12.944323 17.107321 6.732349 20.359663-20.262093-7.025059-17.269938 12.684135 0.422804-21.432936zM303.541115 278.823313l-21.140226 3.480006 10.114785-18.928633-9.854597-19.058726 21.107702 3.772717 15.090868-15.220962 2.927109 21.237796 19.156296 9.626933-19.28639 9.366746-3.252342 21.172749-14.863205-15.448626z" fill="#8a8a8a" p-id="4335"></path><path d="M407.648595 90.642782a486.713038 486.713038 0 0 1 504.568397 11.578339l25.010513-14.407877A512.081309 512.081309 0 0 0 139.850723 547.401747l24.977989-14.407877a486.778085 486.778085 0 0 1 242.819883-442.351088zM893.28836 933.422265a486.810608 486.810608 0 0 1-504.568398-11.610863l-25.010513 14.407877a512.081309 512.081309 0 0 0 797.5394-459.621026l-24.97799 14.505447a486.843132 486.843132 0 0 1-242.982499 442.318565z" fill="#8a8a8a" p-id="4336"></path><path d="M814.061299 795.880705a326.665269 326.665269 0 0 1-258.170939 29.563792l-29.791456 17.172368a353.236906 353.236906 0 0 0 472.793013-272.448721l-29.693886 17.172367a326.762839 326.762839 0 0 1-155.136732 208.540194zM486.875655 228.119295a326.795363 326.795363 0 0 1 258.170939-29.563792l29.791456-17.172368a353.236906 353.236906 0 0 0-472.793013 272.448721l29.82398-17.172367a326.762839 326.762839 0 0 1 155.006638-208.540194zM1288.350389 374.73489a53.923837 53.923837 0 0 1-14.34283 12.001143L229.420232 988.712085A53.793743 53.793743 0 0 1 156.112434 968.937843l-148.924757-258.235985a53.76122 53.76122 0 0 1 19.741718-73.437891L1071.516722 35.352962A53.826266 53.826266 0 0 1 1144.82452 55.062157l148.827187 258.268508a53.793743 53.793743 0 0 1-5.398888 61.404225zM32.19819 665.754486a28.360426 28.360426 0 0 0-5.626553 10.73273 28.067715 28.067715 0 0 0 2.699444 21.432936L178.195839 956.188661a28.165285 28.165285 0 0 0 38.442687 10.342449l1044.587328-601.976052a28.132762 28.132762 0 0 0 10.440019-38.442687l-148.924758-258.268509a28.197808 28.197808 0 0 0-38.442687-10.342449L39.711101 659.444942a28.230332 28.230332 0 0 0-7.512911 6.309544z" fill="#8a8a8a" p-id="4337"></path><path d="M498.941845 597.390249l-138.322121 79.877529 38.637827 66.933207q8.000762 13.854979 21.595554 5.98431l114.254788-65.957504a21.172749 21.172749 0 0 0 9.952167-11.123011q2.634397-9.757027-16.91218-47.321582l18.440781-4.130474q20.489757 43.22363 18.148071 56.167953a36.166047 36.166047 0 0 1-16.261712 19.514054l-123.068636 71.031158q-25.17313 14.603017-40.394092-11.77348L317.103383 639.020232l16.066571-9.269176 18.570875 32.133143 122.027886-70.47826-33.596697-58.249452-150.160648 86.707448-9.041511-15.611243 166.454883-96.106718zM691.903319 563.663459c-3.935334 3.837764-9.757027 9.399269-17.497602 16.619469l23.319295 40.394093-15.611244 9.008988-21.237795-36.816516q-31.027346 27.709957-64.754137 54.314118l-12.814229-13.39965 9.171605-7.382818 9.236653-7.122629-79.714912-138.126982-17.627696 10.179832-8.781324-15.155915L601.683341 414.836271l6.960013 12.06619 86.34969-49.858408 8.488614 14.733111q28.197808 65.82741 30.506972 123.39387a274.660314 274.660314 0 0 0 69.339939 27.612387l-3.642623 18.440781a322.177037 322.177037 0 0 1-65.534699-26.40902 220.899095 220.899095 0 0 1-15.38358 72.819946l-18.14807-6.179451a215.272542 215.272542 0 0 0 15.448626-77.340702 312.940384 312.940384 0 0 1-89.374369-86.739971l-8.748801 5.138701-7.2202-12.488995-17.172368 9.919644 71.876767 124.499667q10.570113-10.017215 17.465079-16.61947z m-134.32174-56.948515l40.166428-23.189202-19.969382-34.702493-40.166429 23.189201z m28.067714 48.785135l40.166429-23.189201-19.514055-33.921931-40.166428 23.189201z m48.557472-8.813847l-40.166428 23.189201 21.888264 37.922312q13.334604-10.92787 35.775766-30.767159z m7.2202-117.832365A289.848753 289.848753 0 0 0 715.515325 503.365031a330.437986 330.437986 0 0 0-26.441544-101.92841zM812.760362 400.460918l-4.813467 17.95293a280.482007 280.482007 0 0 0-56.167953-12.781706l5.073654-17.530125a291.637542 291.637542 0 0 1 55.907766 12.358901z m24.360045 28.78323a925.063745 925.063745 0 0 1 10.017214 101.895887l-18.440781 2.016452a812.792886 812.792886 0 0 0-8.878895-101.375512z m-45.923075-86.25212l-4.813467 18.017977a290.922026 290.922026 0 0 0-58.542163-11.513292l5.073655-17.497602a308.972527 308.972527 0 0 1 58.281975 10.992917z m48.459902-17.562649l-9.334223 13.724885A298.792695 298.792695 0 0 0 783.814515 315.477211l9.757027-14.180212a437.635191 437.635191 0 0 1 46.085692 24.13238zM834.355916 269.944418l16.521899-9.529363 35.157821 60.916373 48.199714-27.840051L1003.282579 413.047483q12.716659 22.115928-8.228426 34.214642l-26.018739 15.058345-13.237034-13.009369 25.238177-13.952549c6.992536-4.065428 8.45609-9.561887 4.423186-16.554423l-12.716659-22.018358-80.527997 46.475973L919.762427 491.1037l-16.066572 9.269176-81.926505-141.899698 47.744387-27.579864z m107.750103 73.763125l-14.830682-25.660981-80.56052 46.508496 14.830681 25.726028z m-72.592282 60.330952l14.700587 25.433317 80.560521-46.508496-14.700587-25.433318z m45.532793-166.064603a222.720407 222.720407 0 0 1-2.406733 56.13543l-16.456853 0.878132a242.722312 242.722312 0 0 0 2.081499-55.647578z" fill="#8a8a8a" p-id="4338"></path></svg>
\ No newline at end of file
diff --git a/src/assets/svgs/bpm/condition.svg b/src/assets/svgs/bpm/condition.svg
new file mode 100644
index 0000000..41ea85d
--- /dev/null
+++ b/src/assets/svgs/bpm/condition.svg
@@ -0,0 +1 @@
+<svg t="1729585232424" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1602" width="200" height="200"><path d="M925.5 898.9H804.9c-19 0-34.5-15.4-34.5-34.4V761.3c0-19 15.4-34.4 34.5-34.4h34.5V572.2c0-19-15.4-34.4-34.5-34.4H529.2V727h34.5c19 0 34.5 15.4 34.5 34.4v103.2c0 19-15.4 34.4-34.5 34.4H443.1c-19 0-34.5-15.4-34.5-34.4V761.3c0-19 15.4-34.4 34.5-34.4h34.5V537.8H219.1c-19 0-34.5 15.4-34.5 34.4V727h34.5c19 0 34.5 15.4 34.5 34.4v103.2c0 19-15.4 34.4-34.5 34.4H98.5c-19 0-34.5-15.4-34.5-34.4V761.3c0-19 15.4-34.4 34.5-34.4H133V555c0-38 30.9-68.8 68.9-68.8h275.7V297.1h-34.5c-19 0-34.5-15.4-34.5-34.4V159.5c0-19 15.4-34.4 34.5-34.4h120.6c19 0 34.5 15.4 34.5 34.4v103.2c0 19-15.4 34.4-34.5 34.4h-34.5v189.2h292.9c38.1 0 68.9 30.8 68.9 68.8v172h34.5c19 0 34.5 15.4 34.5 34.4v103.2c0 18.8-15.4 34.2-34.5 34.2z m0 0" p-id="1603" fill="#fff"></path></svg>
\ No newline at end of file
diff --git a/src/assets/svgs/bpm/copy.svg b/src/assets/svgs/bpm/copy.svg
new file mode 100644
index 0000000..8ff3bba
--- /dev/null
+++ b/src/assets/svgs/bpm/copy.svg
@@ -0,0 +1 @@
+<svg t="1729649333541" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1644" width="200" height="200"><path d="M647.888 893.84L491.904 571.52l393.888-393.888-237.904 716.208zM872.32 123.232L459.872 535.68 134.96 380.88 872.32 123.232z m90.72-68.32a23.968 23.968 0 0 0-24.784-5.568L64.08 354.816a23.984 23.984 0 0 0-2.4 44.32l381.392 181.728 187.36 387.088a24.048 24.048 0 0 0 23.152 13.504 24.032 24.032 0 0 0 21.232-16.4L968.96 79.552c2.88-8.672 0.592-18.24-5.92-24.64z" fill="#fff" p-id="1645"></path></svg>
\ No newline at end of file
diff --git a/src/assets/svgs/bpm/finish.svg b/src/assets/svgs/bpm/finish.svg
new file mode 100644
index 0000000..674c6df
--- /dev/null
+++ b/src/assets/svgs/bpm/finish.svg
@@ -0,0 +1 @@
+<svg t="1730189225011" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2651" id="mx_n_1730189225011" width="200" height="200"><path d="M793.889347 200.380242c27.648573 20.615681 42.196018 32.710677 63.781037 56.119312 25.313864 27.453234 43.242957 48.52047 64.502857 86.507991 44.537416 79.580127 53.527718 136.949077 53.517684 212.063821 0 64.933675-15.452562 130.459388-40.138263 187.311893-22.076044 50.841799-61.545336 104.359483-101.886297 138.933914-45.506755 39.001681-81.214423 60.462941-137.605337 81.826531-55.699867 21.102023-114.070267 28.641326-181.379458 27.791064-68.274516-0.862973-129.364283-11.040029-180.533878-31.80489-46.159002-18.731189-98.338744-46.827973-141.596418-87.541551-43.946046-41.361142-70.369064-75.958317-93.88139-127.198155-26.157437-57.004361-40.094111-129.065922-39.680686-191.781288 0-36.980719 4.033895-70.902234 12.252873-105.241856 8.532726-35.651474 20.069131-69.572989 38.13135-102.35257 18.856956-34.221214 36.754607-62.067803 58.869452-88.973149 23.248751-28.285434 39.2104-46.417894 64.295476-63.475987 18.297696-12.442861 36.879036-9.295353 47.199252-2.306612 4.403836 2.982273 8.919391 6.577992 12.933218 12.933217 9.572307 15.156208-0.334486 29.769212-6.69038 38.465836-7.148625 9.781026-23.130343 26.023643-38.738775 43.218205-38.192895 42.075603-55.133918 65.965228-74.986303 106.965794-30.772668 63.552249-37.495827 115.718611-38.131349 166.573791-0.668971 53.517684 9.995096 99.647251 27.427813 140.483919 33.916163 80.572211 94.807915 144.44289 175.270414 178.615938 41.108271 17.845472 113.812713 37.319888 181.960793 38.13135 56.193568 0.668971 125.919751-11.321666 166.574459-28.096784 45.935566-18.954626 97.223569-56.862539 127.10383-94.324918 23.013273-28.852721 52.179742-70.910931 64.413884-105.694749 14.863868-42.260239 24.806784-87.661297 24.559934-132.458943 0-54.414105-11.53373-108.417461-36.918505-156.856317-20.16747-38.483228-46.480777-74.607665-84.66899-108.048189-13.377414-11.714352-23.822728-20.067124-38.808348-31.619586-10.191774-7.857065-36.059546-25.027545-28.923632-47.326356 4.970455-15.53217 18.303717-25.294464 31.887843-27.205046 19.456354-2.736092 28.565733 2.427027 43.705885 12.041479l6.179955 4.322891zM510.755379 531.65738c-8.696624-0.668971-10.034566-0.446204-20.738102-6.689711-11.031333-6.434832-17.839451-21.183637-16.514219-35.175166V92.220334c0-18.178619 0.386665-22.815926 8.988295-31.685813 5.351768-5.519011 10.963097-11.381873 26.08987-11.539751 16.055305-0.167243 21.407073 3.846584 27.929542 9.700081 9.70677 8.711341 10.703537 17.56049 10.377078 33.525483v397.5715c-0.509756 15.273947 0.326458 22.967114-11.380535 33.502739-3.884046 3.495374-8.027653 7.693167-20.96087 8.362138l-3.791059 0.000669z m4.453341 0.573308" p-id="2652" fill="#ffffff"></path></svg>
\ No newline at end of file
diff --git a/src/assets/svgs/bpm/parallel.svg b/src/assets/svgs/bpm/parallel.svg
new file mode 100644
index 0000000..ba0ac67
--- /dev/null
+++ b/src/assets/svgs/bpm/parallel.svg
@@ -0,0 +1 @@
+<svg t="1729585239190" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1755" width="200" height="200"><path d="M901.489435 536.822664v-0.931601l-1.001722-198.240726c-0.100172-19.162936-9.21584-37.474409-25.043042-50.246361-14.024104-11.349507-32.265456-17.60025-51.348255-17.610268l-618.062295-0.18031c-19.142902 0-37.424323 6.280795-51.478478 17.690405-15.827203 12.842072-24.902802 31.2437-24.892785 50.486775v196.798247A114.987635 114.987635 0 1 0 195.295664 536.922836V338.782282c1.15198-1.252152 4.808264-3.596181 10.768509-3.596181l276.725622 0.090155v199.753326a114.987635 114.987635 0 1 0 65.612772 1.412428V335.326342l275.693849 0.080138c6.01033 0 9.626546 2.344029 10.768508 3.596181l1.001722 195.70637a114.987635 114.987635 0 1 0 65.592737 2.113633zM214.979496 645.910158a56.437001 56.437001 0 1 1-56.437001-56.437001 56.507122 56.507122 0 0 1 56.437001 56.437001z m354.689623 0a56.437001 56.437001 0 1 1-56.437001-56.437001 56.507122 56.507122 0 0 1 56.437001 56.437001z m295.507904 56.437001a56.437001 56.437001 0 1 1 56.437001-56.437001 56.507122 56.507122 0 0 1-56.457035 56.437001z" p-id="1756" fill='#fff'></path></svg>
\ No newline at end of file
diff --git a/src/assets/svgs/bpm/reject.svg b/src/assets/svgs/bpm/reject.svg
new file mode 100644
index 0000000..21fd5f6
--- /dev/null
+++ b/src/assets/svgs/bpm/reject.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1724316570161" class="icon" viewBox="0 0 1185 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1505" xmlns:xlink="http://www.w3.org/1999/xlink" width="231.4453125" height="200"><path d="M414.276535 230.004913l-2.443086-31.647244 26.446614 17.351559 29.437984-11.852598-8.143622 30.31685 20.423559 24.221229-31.623055 1.475527-16.722646 26.801386-11.239811-29.760504-30.663559-7.522772zM581.664252 176.902047l13.884472-28.542992 14.206993 28.220473 31.42148 4.321763-22.350614 22.092599 5.684409 31.123149-28.180157-14.513385-27.897953 14.819779 5.28126-31.066709-22.76989-21.689448zM896.507969 672.735748l17.754708 26.398236-31.494047-2.064126-19.560819 24.705008-7.95011-30.502299-29.575055-11.02211 26.744945-16.771024 1.104629-31.526299 24.414741 20.197795 30.268472-8.619338zM777.030551 801.961323l2.112504 31.647244-26.446614-17.682142-29.413795 11.546205 8.466141-30.308787-20.092976-24.221229 31.606929-1.153008 17.045166-26.793323 10.86085 29.704063 30.647433 7.837229zM609.312252 853.451591l-14.198929 28.518803-14.110236-28.542992-31.405355-4.636221 22.673134-22.084535-5.36189-31.12315 27.833449 14.835906 28.188221-14.803654-5.28126 31.066709 22.76989 22.060346zM298.435528 354.828094l-17.448315-26.390173 31.485984 2.394709 19.875275-24.753386 7.611465 30.865134 29.583118 11.288189-27.011024 16.779087-1.419086 31.526299-24.084158-20.504189-30.518425 8.280693zM962.56 91.53915a43.636913 43.636913 0 0 1 59.375874 15.601889l138.627024 236.753638c12.175118 20.447748 5.12 47.208819-15.609953 59.375874L229.13411 938.185575a43.636913 43.636913 0 0 1-59.375874-15.60189L31.12315 685.773606a43.636913 43.636913 0 0 1 15.601889-59.319433z m25.672567 24.108346a13.594205 13.594205 0 0 0-10.441575 1.548095L61.625449 652.054173a13.586142 13.586142 0 0 0-4.853921 18.83515l138.643149 236.793953a13.586142 13.586142 0 0 0 18.843213 4.837795l915.818834-534.915024a13.957039 13.957039 0 0 0 5.160315-18.778708l-138.602834-236.78589a13.594205 13.594205 0 0 0-8.401638-6.393953z" fill="#F5222D" p-id="1506"></path><path d="M395.981606 172.338394c123.670173-72.349228 271.11811-69.462677 388.394331-5.12l29.623433-17.335433a414.574866 414.574866 0 0 0-112.107842-47.071748 429.991307 429.991307 0 0 0-162.009701-10.498016 412.792945 412.792945 0 0 0-158.80063 54.707401 417.856504 417.856504 0 0 0-125.363402 111.922394A426.282331 426.282331 0 0 0 185.206929 405.004094a417.348535 417.348535 0 0 0-13.529701 120.977134l29.623433-17.335433c1.386835-133.958551 70.688252-263.958173 194.672882-336.307401z m397.666772 679.484472c-123.670173 72.365354-271.110047 69.462677-388.394331 5.128063l-29.623433 17.335433a414.679685 414.679685 0 0 0 112.075591 47.087874 429.991307 429.991307 0 0 0 162.009701 10.498016 412.744567 412.744567 0 0 0 158.808692-54.707402 423.145827 423.145827 0 0 0 209.105638-378.976756l-29.623433 17.335434c-1.072378 133.974677-70.712441 263.95011-194.350362 336.307401h-0.008063z" fill="#F5222D" p-id="1507"></path><path d="M478.377323 313.110173a226.271748 226.271748 0 0 1 109.979212-31.219905l45.668788-26.761071c-58.634079-9.13537-118.316346 2.314079-170.612914 32.735748a258.693039 258.693039 0 0 0-111.91433 132.71685l45.67685-26.761071a230.359685 230.359685 0 0 1 81.097575-80.589606l0.104819-0.120945z m232.568945 397.674835a226.328189 226.328189 0 0 1-109.979213 31.227968l-45.668787 26.753008c58.634079 9.13537 118.316346-2.314079 170.612913-32.735748a258.709165 258.709165 0 0 0 111.914331-132.71685l-45.676851 26.761071a225.215496 225.215496 0 0 1-81.097574 80.597669l-0.104819 0.112882zM188.57726 706.938961l-10.062614-17.424126 109.938897-63.471874 9.578835 16.585574 17.093543-9.869102-18.770645-32.509984-63.689575 36.767244c-4.047622-3.918614-7.804976-7.337323-11.272063-10.24l-16.859717 13.747401c3.249386 2.144756 6.595528 4.458835 9.869103 7.038993l-62.173733 35.896441 19.254426 33.348535 17.093543-9.869102zM317.44 781.142677l-19.060913-33.017953 32.679307-18.867401 4.741039 8.216189 17.093543-9.869103-48.474708-83.959937-49.772851 28.736504-7.933984-13.747401-17.432189 10.062614 7.933984 13.747402-49.264882 28.446236 48.764977 84.459842 17.093543-9.869102-5.031307-8.708032 32.171339-18.585196 19.060913 33.017952 17.432189-10.062614z m-12.505701-97.126803l-32.679307 18.867402-8.321008-14.41663 32.679307-18.867402 8.321008 14.41663z m-50.111496 28.930016l-32.171338 18.577134-8.321008-14.41663 32.171338-18.577134 8.321008 14.41663z m16.932284 29.325102l-32.171339 18.577134-8.127496-14.077984 32.171339-18.577134 8.127496 14.077984z m50.111496-28.930016l-32.679307 18.867402-8.127496-14.077984 32.679307-18.867402 8.127496 14.077984z m95.828661 7.684032c11.062425-6.38589 13.368441-15.537386 6.692284-27.099717l-25.05978-43.411149c3.55578-4.289512 7.014803-8.740283 10.48189-13.199118l-9.482079-16.424315c-3.467087 4.458835-6.92611 8.917669-10.48189 13.199118l-17.803086-30.832882 14.755275-8.51452-9.780409-16.932283-14.747213 8.522582-16.738771-28.994519-17.093544 9.869102 16.738772 28.99452-16.924221 9.772346 9.772347 16.924221 16.932283-9.772347 20.891213 36.202835a299.927181 299.927181 0 0 1-16.690394 15.214866l13.868347 14.344063a572.617575 572.617575 0 0 0 12.497638-12.804031l19.157669 33.179212c2.322142 4.031496 1.475528 7.200252-2.20926 9.328882-3.85411 2.225386-8.167811 4.039559-12.578268 5.692472l13.55389 14.964914 14.247307-8.224252z m111.390236-65.205417c6.369764-3.676724 10.15937-8.329071 11.151118-13.586142 1.225575-5.619906-3.201008-18.706142-13.182992-39.089386l-18.827086 4.160504c7.627591 14.368252 11.368819 23.164976 11.570393 26.615937 0.112882 3.289701-0.959496 5.692472-3.467086 7.143811l-6.539087 3.77348c-3.354205 1.935118-6.095622 1.064315-8.224252-2.628535l-38.702362-67.027654c8.933795-10.07874 17.762772-21.874898 26.390173-35.573921l-18.383622-8.603213a168.443969 168.443969 0 0 1-17.972409 26.914268l-26.801386-46.426709-17.254803 9.965859 77.686929 134.571338c6.966425 12.070299 16.077606 15.077795 27.478677 8.498394l15.077795-8.708031z m-78.501291 45.547842c13.626457-12.779843 25.285543-25.100094 34.783748-37.291339l-12.473449-14.247307a157.808882 157.808882 0 0 1-14.706897 17.875654l-38.412095-66.535811 20.617071-11.900976-9.869102-17.093544-20.617071 11.900977-27.18841-47.087874-17.254803 9.965858 72.94589 126.363212c2.999433 5.192567 2.418898 9.99811-1.564221 14.311811l13.739339 13.739339z m201.663496-113.978457l-65.21348-112.946393c0.137071-7.901732-0.16126-15.771213-0.886929-23.624567l53.78822-31.050583-9.869102-17.093543-144.795213 83.597102 9.869102 17.093543 71.05915-41.024504c1.894803 37.331654-9.45789 76.517795-33.848441 117.856756l20.367118 8.570961c14.860094-26.898142 25.05978-53.344756 30.445859-79.243087l50.990362 88.313953 18.093354-10.449638z m28.728441-76.017889l5.716661-21.850709c-21.157291-7.224441-45.330142-12.707276-72.349228-16.54526l-5.603779 19.318929c29.163843 4.837795 53.385071 11.191433 72.244409 19.07704z m18.738394-105.33493l5.265134-19.13348c-12.739528-4.25726-27.414173-7.627591-43.612725-10.127118l-5.410268 18.101417c17.674079 2.74948 32.380976 6.555213 43.757859 11.159181z m88.934803 67.74526l-15.76315-27.317417 21.786205-12.578268 15.674457 27.148095 16.085669-9.288567-15.674457-27.148095 22.455433-12.965291 4.063748 7.038992c2.031874 3.523528 1.249764 6.426205-2.435023 8.554835l-11.852599 6.176252 12.175118 12.183181 12.570205-7.256693c11.393008-6.579402 13.997354-15.230992 7.998488-25.616126l-42.862866-74.244032-33.848441 19.544693-0.532157-0.145133a202.445606 202.445606 0 0 0 18.738393-38.750741L790.173228 306.87748l-92.676031 53.506016 8.321008 14.41663 31.679496-18.286866-3.85411 13.836094c8.401638-0.16126 16.125984 0.08063 23.261732 0.427339l-37.202646 21.479811 52.538457 90.998929 16.424315-9.482079z m-25.35811-117.856756c-6.724535-0.806299-14.126362-1.233638-21.947465-1.628724l33.517858-19.351181c-3.305827 7.047055-7.143811 13.948976-11.570393 20.979905z m47.571653 16.996788l-22.455433 12.965291-6.095622-10.56252 22.455433-12.965291 6.095622 10.56252z m-38.541102 22.253858l-21.786205 12.578268-6.095622-10.56252 21.786205-12.578268 6.095622 10.56252z m-24.253481 137.570772c-0.330583-19.915591 1.112693-30.582929 4.458835-32.518048 1.846425-1.064315 4.628157-0.886929 8.627402 0.604725 8.304882 2.797858 16.400126 3.265512 24.269606 1.402961 8.006551-2.386646 17.464441-6.506835 28.462362-12.626646 10.812472-6.031118 20.96378-11.66715 30.187843-16.988725l38.379842-22.157102-5.781165-19.673701c-4.329827 2.942992-10.675402 7.055118-19.028662 12.320252-8.708031 5.031307-16.996787 10.038425-25.374236 14.876221-13.07011 7.546961-24.398614 13.868346-34.211275 19.302803-10.07874 5.378016-18.230425 8.296819-24.543748 8.587087-5.28126 0.145134-11.070488-0.983685-17.440252-3.120378l-2.902678-0.774048-36.767244-63.681511-38.379842 22.157102 9.288567 16.085669 22.116787-12.771779 26.511118 45.91874c-4.571717 7.555024-7.014803 20.359055-7.651779 38.605606l19.778519 4.450772z m38.476599-112.938331l-21.786205 12.578268-6.095622-10.56252 21.786205-12.578268 6.095622 10.56252z m38.541102-22.253858l-22.455433 12.965291-6.095622-10.56252 22.455433-12.957228 6.095622 10.56252z m172.241638-43.798173c12.062236-6.966425 14.610142-16.488819 7.740472-28.381733l-39.863433-69.051464 23.302048-13.449071-9.869103-17.093543-23.302047 13.44907-14.513386-25.132346-17.424126 10.062614 14.513386 25.132347-62.681701 36.186708 9.869103 17.093544 62.6817-36.186709 37.34778 64.689386c2.515654 4.354016 1.523906 8.062992-2.838173 10.578645-6.692283 3.870236-14.190866 7.522772-21.955528 11.110804l13.529701 14.537574 23.463307-13.545826z m-130.942992-43.725607l5.386079-20.092976c-12.900787-4.168567-27.389984-7.200252-43.65304-9.433701l-5.321575 18.27074c17.682142 2.74948 32.219717 6.643906 43.596599 11.255937z m80.702488 27.148095l8.466142-17.851465c-10.756031-5.853732-24.825953-12.038047-41.846929-18.302992l-8.740284 16.22274c16.883906 6.789039 30.808693 13.497449 42.121071 19.931717z m-31.219905 99.577952c-0.354772-20.350992 1.064315-31.445669 4.418519-33.380787 2.007685-1.161071 5.128063-1.177197 9.119244 0.32252a42.951559 42.951559 0 0 0 24.938835 1.007874c8.175874-2.483402 18.141732-6.893858 29.639559-13.303937 11.320441-6.321386 21.810394-12.150929 31.365039-17.657953l35.525544-20.520315-5.966614-20.012346c-3.999244 2.74948-10.006173 6.668094-17.859528 11.651023-7.95011 4.805543-15.722835 9.522394-23.439118 13.973166a2406.72252 2406.72252 0 0 1-35.719055 20.181669c-10.586709 5.66022-19.165732 8.603213-25.712882 9.256315-5.28126 0.145134-11.401071-0.790173-17.940158-2.822047l-3.080063-0.685355-36.767244-63.681512-39.041008 22.544126 9.482079 16.424315 22.455433-12.965291 26.511118 45.91874c-4.57978 7.555024-7.256693 20.72189-7.700157 39.299024l19.770457 4.450771z" fill="#F5222D" p-id="1508"></path></svg>
\ No newline at end of file
diff --git a/src/assets/svgs/bpm/running.svg b/src/assets/svgs/bpm/running.svg
new file mode 100644
index 0000000..5908c13
--- /dev/null
+++ b/src/assets/svgs/bpm/running.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1724304256588" class="icon" viewBox="0 0 1300 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1272" xmlns:xlink="http://www.w3.org/1999/xlink" width="253.90625" height="200"><path d="M784.058182 99.258182l10.938182 18.385454-21.294546-2.56-14.196363 16.058182-4.072728-21.061818-19.781818-8.494545 18.734546-10.472728 2.094545-21.294545 15.709091 14.545454 20.945454-4.654545-9.076363 19.549091zM1067.287273 642.443636l-18.501818 10.821819 2.56-21.294546-16.058182-14.196364 21.061818-4.072727 8.494545-19.665454 10.472728 18.734545 21.294545 1.978182-14.661818 15.709091 4.770909 20.945454-19.432727-8.96z" fill="#2196F3" p-id="1273"></path><path d="M1067.287273 642.443636l-18.501818 10.821819 2.56-21.294546-16.058182-14.196364 21.061818-4.072727 8.494545-19.665454 10.472728 18.734545 21.294545 1.978182-14.661818 15.709091 4.770909 20.945454-19.432727-8.96zM571.927273 100.072727l-17.454546-12.567272 20.596364-6.167273 6.516364-20.48 12.218181 17.570909 21.410909-0.116364-12.916363 17.105455 6.749091 20.363636-20.247273-6.981818-17.338182 12.683636 0.465455-21.410909zM991.418182 784.407273l-21.178182 3.490909 10.123636-18.967273-9.774545-18.967273 21.061818 3.723637 15.127273-15.243637 2.909091 21.294546 19.2 9.658182-19.316364 9.309091-3.258182 21.178181-14.894545-15.476363zM427.985455 156.741818L407.272727 151.505455l16.872728-13.265455-1.396364-21.410909 17.803636 11.985454 20.014546-7.912727-5.934546 20.596364 13.730909 16.523636-21.410909 0.814546-11.52 18.152727-7.447272-20.247273zM854.225455 896.465455l-20.712728-5.352728 16.872728-13.265454-1.396364-21.294546 17.803636 11.869091 20.014546-7.912727-5.934546 20.712727 13.730909 16.523637-21.527272 0.814545-11.403637 18.036364-7.447272-20.130909zM562.501818 923.694545l10.821818 18.385455-21.294545-2.56-14.196364 16.058182-4.072727-21.061818-19.665455-8.494546 18.734546-10.356363 1.978182-21.41091 15.709091 14.661819 20.945454-4.770909-8.96 19.54909zM242.734545 420.770909l-18.385454 10.938182 2.56-21.294546-16.058182-14.196363 21.061818-4.189091 8.494546-19.665455 10.356363 18.734546 21.410909 2.094545-14.545454 15.709091 4.654545 20.945455-19.549091-9.076364z" fill="#2196F3" p-id="1274"></path><path d="M242.734545 420.770909l-18.385454 10.938182 2.56-21.294546-16.058182-14.196363 21.061818-4.189091 8.494546-19.665455 10.356363 18.734546 21.410909 2.094545-14.545454 15.709091 4.654545 20.945455-19.549091-9.076364zM700.858182 943.941818l-17.454546-12.450909 20.48-6.283636 6.516364-20.48 12.334545 17.687272 21.41091-0.116363-12.916364 17.105454 6.632727 20.363637-20.247273-7.098182-17.221818 12.683636 0.465455-21.410909zM303.592727 278.807273l-21.178182 3.490909 10.123637-18.967273-9.890909-18.967273 21.178182 3.723637 15.010909-15.243637 2.909091 21.294546 19.2 9.541818-19.316364 9.425455-3.258182 21.178181-14.778182-15.476363z" fill="#2196F3" p-id="1275"></path><path d="M407.272727 90.647273a486.632727 486.632727 0 0 1 504.552728 11.636363l25.018181-14.429091A512 512 0 0 0 139.636364 546.909091l25.018181-14.429091A486.981818 486.981818 0 0 1 407.272727 90.647273zM893.323636 933.352727a486.749091 486.749091 0 0 1-504.669091-11.636363l-24.901818 14.429091A512 512 0 0 0 1161.192727 477.090909l-24.901818 13.963636a486.981818 486.981818 0 0 1-242.967273 442.298182z" fill="#2196F3" p-id="1276"></path><path d="M814.545455 795.927273a327.447273 327.447273 0 0 1-258.21091 29.556363l-29.78909 17.105455A353.163636 353.163636 0 0 0 998.865455 570.181818l-29.789091 17.105455A326.865455 326.865455 0 0 1 814.545455 795.927273zM486.865455 228.072727A327.447273 327.447273 0 0 1 744.727273 198.516364l29.789091-17.105455A353.163636 353.163636 0 0 0 302.545455 453.818182l29.78909-17.105455A326.865455 326.865455 0 0 1 486.865455 228.072727zM1288.378182 374.690909a53.294545 53.294545 0 0 1-14.429091 11.636364L229.469091 989.090909a53.876364 53.876364 0 0 1-73.425455-19.665454L7.214545 710.632727a53.527273 53.527273 0 0 1 19.781819-73.309091L1071.476364 34.909091a53.876364 53.876364 0 0 1 73.425454 19.665454l148.829091 258.327273a53.061818 53.061818 0 0 1 5.352727 40.727273 55.272727 55.272727 0 0 1-10.705454 21.061818zM32.232727 665.716364A28.043636 28.043636 0 0 0 29.323636 698.181818l148.829091 257.978182a28.392727 28.392727 0 0 0 38.516364 10.356364l1044.48-601.949091a28.16 28.16 0 0 0 10.356364-38.516364L1122.676364 67.84a28.276364 28.276364 0 0 0-38.4-10.356364L39.68 659.432727a27.810909 27.810909 0 0 0-7.447273 6.283637z" fill="#2196F3" p-id="1277"></path><path d="M477.090909 500.945455l22.109091 38.283636-15.36 8.843636-13.963636-24.436363-151.272728 87.621818 14.545455 25.134545-15.243636 8.843637-23.272728-39.330909L377.949091 558.545455c-6.050909-4.887273-11.636364-8.843636-15.825455-11.636364l14.894546-12.450909c3.956364 3.141818 9.658182 8.145455 17.105454 14.894545zM459.869091 698.181818l-48.407273 28.043637 7.447273 12.334545-15.36 8.843636-61.207273-106.007272L406.225455 605.090909l-12.683637-21.876364 15.709091-9.076363 12.683636 21.876363L486.4 558.545455l60.509091 104.727272-15.36 8.843637-7.098182-12.218182-49.105454 28.392727L501.294545 733.090909l-15.70909 9.076364z m-45.381818-78.661818l-48.523637 27.461818 14.545455 25.134546 48.523636-28.043637zM388.538182 686.545455l14.545454 25.134545 48.523637-28.043636-14.545455-25.134546z m105.425454-79.476364L479.418182 581.818182 430.545455 609.861818l14.545454 25.134546z m-26.647272 67.490909l49.221818-28.392727-14.545455-25.134546-49.105454 28.392728zM624.058182 541.090909c-4.654545 6.167273-10.123636 13.149091-16.290909 20.829091l34.909091 61.207273a18.734545 18.734545 0 0 1-6.632728 29.207272l-18.734545 10.938182-11.636364-13.614545a174.545455 174.545455 0 0 0 17.454546-8.610909 8.378182 8.378182 0 0 0 2.327272-12.683637l-30.021818-52.363636-9.774545 10.24-9.890909 10.123636-12.450909-12.916363c9.076364-8.145455 16.872727-15.709091 23.272727-22.574546l-30.836364-53.527272-24.785454 14.196363-8.727273-15.010909L546.909091 492.218182l-23.272727-40.378182 15.36-8.843636 23.272727 40.378181 21.643636-12.450909 8.727273 15.127273-21.643636 12.450909L599.156364 546.909091c5.352727-6.4 10.821818-13.381818 16.290909-20.712727z m8.843636-45.032727L689.221818 593.454545a193.745455 193.745455 0 0 0 22.574546-27.112727l11.636363 13.032727a363.985455 363.985455 0 0 1-41.192727 44.8l-12.334545-12.450909a10.821818 10.821818 0 0 0 1.62909-13.730909l-98.90909-171.403636 15.476363-8.96 36.305455 62.952727 30.836363-17.803636 8.029091 15.476363z m128 81.454545a20.130909 20.130909 0 0 1-30.836363-9.541818L628.363636 392.378182l15.36-8.378182 38.050909 66.094545A206.08 206.08 0 0 0 709.818182 409.018182l16.64 7.563636a297.890909 297.890909 0 0 1-34.909091 48.64l52.712727 91.112727a8.843636 8.843636 0 0 0 13.614546 4.072728l10.821818-6.167273a14.429091 14.429091 0 0 0 7.912727-11.636364 102.981818 102.981818 0 0 0-15.010909-38.516363l17.105455-3.723637c12.334545 24.669091 17.687273 41.076364 16.058181 48.989091a28.16 28.16 0 0 1-15.127272 18.152728zM805.236364 288.116364l16.174545-9.309091 23.272727 39.330909 78.429091-45.265455 59.345455 102.749091-16.64 9.076364-7.912727-13.847273L896 407.272727l42.938182 74.472728-16.174546 9.30909-42.938181-74.472727-62.603637 36.072727 8.029091 13.73091-15.825454 9.192727L749.730909 372.363636l78.196364-45.265454z m2.676363 149.061818l62.603637-36.072727-33.745455-58.181819-62.487273 36.072728z m78.778182-45.381818l62.72-36.189091-33.745454-58.181818-62.72 36.072727z" fill="#2196F3" p-id="1278"></path></svg>
\ No newline at end of file
diff --git a/src/assets/svgs/bpm/simple-process-bg.svg b/src/assets/svgs/bpm/simple-process-bg.svg
new file mode 100644
index 0000000..eb23ab5
--- /dev/null
+++ b/src/assets/svgs/bpm/simple-process-bg.svg
@@ -0,0 +1 @@
+<svg width="22" height="22" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path fill="#FAFAFA" d="M0 0h22v22H0z"/><circle fill="#919BAE" cx="1" cy="1" r="1"/></g></svg>
\ No newline at end of file
diff --git a/src/assets/svgs/bpm/starter.svg b/src/assets/svgs/bpm/starter.svg
new file mode 100644
index 0000000..c12c712
--- /dev/null
+++ b/src/assets/svgs/bpm/starter.svg
@@ -0,0 +1 @@
+<svg t="1729561814171" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1359" width="200" height="200"><path d="M674.496 603.456c120.256 0 218.176 90.752 221.44 203.84l0.064 5.888v125.888c0 11.52-9.92 20.928-22.144 20.928h-44.352a21.568 21.568 0 0 1-22.144-20.928v-125.888c0-67.712-56.512-123.264-128-125.76l-4.928-0.064H349.568c-71.488 0-130.176 53.504-132.864 121.152l-0.064 4.672v125.888c0 11.52-9.92 20.928-22.144 20.928h-44.352A21.568 21.568 0 0 1 128 939.072v-125.888c0-113.92 95.872-206.528 215.36-209.664l6.208-0.064h324.928zM497.216 128c122.368 0 221.568 93.888 221.568 209.728s-99.2 209.792-221.568 209.792c-122.304 0-221.44-93.952-221.44-209.728C275.712 221.952 374.848 128 497.152 128z m0 83.904c-73.408 0-132.864 56.32-132.864 125.888 0 69.504 59.52 125.824 132.864 125.824 73.408 0 132.928-56.32 132.928-125.824 0-69.504-59.52-125.888-132.928-125.888z" fill="#fff" p-id="1360"></path></svg>
\ No newline at end of file
diff --git a/src/components/FormCreate/src/components/useApiSelect.tsx b/src/components/FormCreate/src/components/useApiSelect.tsx
index d668cb8..8ff95fb 100644
--- a/src/components/FormCreate/src/components/useApiSelect.tsx
+++ b/src/components/FormCreate/src/components/useApiSelect.tsx
@@ -104,9 +104,9 @@
           parseOptions0(data)
           return
         }
-        // 情况三:不是 iailab-plat 标准返回
+        // 情况三:不是 yudao-vue-pro 标准返回
         console.warn(
-          `接口[${props.url}] 返回结果不是 iailab-plat 标准返回建议采用自定义解析函数处理`
+          `接口[${props.url}] 返回结果不是 yudao-vue-pro 标准返回建议采用自定义解析函数处理`
         )
       }
 
@@ -185,7 +185,6 @@
             </el-select>
           )
         }
-        // debugger
         return (
           <el-select
             class="w-1/1"
diff --git a/src/components/FormCreate/src/config/useDictSelectRule.ts b/src/components/FormCreate/src/config/useDictSelectRule.ts
index 5c5e8ca..f232f48 100644
--- a/src/components/FormCreate/src/config/useDictSelectRule.ts
+++ b/src/components/FormCreate/src/config/useDictSelectRule.ts
@@ -48,7 +48,7 @@
         },
         {
           type: 'select',
-          field: 'dictValueType',
+          field: 'valueType',
           title: '字典值类型',
           value: 'str',
           options: [
diff --git a/src/components/FormCreate/src/utils/index.ts b/src/components/FormCreate/src/utils/index.ts
index 2d4a6fd..a2b3e67 100644
--- a/src/components/FormCreate/src/utils/index.ts
+++ b/src/components/FormCreate/src/utils/index.ts
@@ -16,3 +16,46 @@
     return rule
   })
 }
+
+/**
+ * 解析表单组件的  field, title 等字段(递归,如果组件包含子组件)
+ * 
+ * @param rule  组件的生成规则 https://www.form-create.com/v3/guide/rule
+ * @param fields 解析后表单组件字段
+ * @param parentTitle  如果是子表单,子表单的标题,默认为空
+ */
+export const parseFormFields = (
+  rule: Record<string, any>,
+  fields: Array<Record<string, any>> = [],
+  parentTitle: string = ''
+) => {
+  const { type, field, $required, title: tempTitle, children } = rule
+  if (field && tempTitle) {
+    let title = tempTitle
+    if (parentTitle) {
+      title = `${parentTitle}.${tempTitle}`
+    }
+    let required = false
+    if ($required) {
+      required = true
+    }
+    fields.push({
+      field,
+      title,
+      type,
+      required
+    })
+    // TODO 子表单 需要处理子表单字段
+    // if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) {
+    //   // 解析子表单的字段
+    //   rule.props.rule.forEach((item) => {
+    //     parseFields(item, fieldsPermission, title)
+    //   })
+    // }
+  }
+  if (children && Array.isArray(children)) {
+    children.forEach((rule) => {
+      parseFormFields(rule, fields)
+    })
+  }
+}
diff --git a/src/components/SimpleProcessDesigner/src/addNode.vue b/src/components/SimpleProcessDesigner/src/addNode.vue
deleted file mode 100644
index 6d09ae8..0000000
--- a/src/components/SimpleProcessDesigner/src/addNode.vue
+++ /dev/null
@@ -1,237 +0,0 @@
-/* stylelint-disable order/properties-order */
-<template>
-  <div class="add-node-btn-box">
-    <div class="add-node-btn">
-      <el-popover placement="right-start" v-model="visible" width="auto">
-        <div class="add-node-popover-body">
-          <a class="add-node-popover-item approver" @click="addType(1)">
-            <div class="item-wrapper">
-              <span class="iconfont"></span>
-            </div>
-            <p>审批人</p>
-          </a>
-          <a class="add-node-popover-item notifier" @click="addType(2)">
-            <div class="item-wrapper">
-              <span class="iconfont"></span>
-            </div>
-            <p>抄送人</p>
-          </a>
-          <a class="add-node-popover-item condition" @click="addType(4)">
-            <div class="item-wrapper">
-              <span class="iconfont"></span>
-            </div>
-            <p>条件分支</p>
-          </a>
-        </div>
-        <template #reference>
-          <button class="btn" type="button">
-            <span class="iconfont"></span>
-          </button>
-        </template>
-      </el-popover>
-    </div>
-  </div>
-</template>
-<script setup>
-import { ref } from 'vue'
-let props = defineProps({
-  childNodeP: {
-    type: Object,
-    default: () => ({})
-  }
-})
-let emits = defineEmits(['update:childNodeP'])
-let visible = ref(false)
-const addType = (type) => {
-  visible.value = false
-  if (type != 4) {
-    var data
-    if (type == 1) {
-      data = {
-        nodeName: '审核人',
-        error: true,
-        type: 1,
-        settype: 1,
-        selectMode: 0,
-        selectRange: 0,
-        directorLevel: 1,
-        examineMode: 1,
-        noHanderAction: 1,
-        examineEndDirectorLevel: 0,
-        childNode: props.childNodeP,
-        nodeUserList: []
-      }
-    } else if (type == 2) {
-      data = {
-        nodeName: '抄送人',
-        type: 2,
-        ccSelfSelectFlag: 1,
-        childNode: props.childNodeP,
-        nodeUserList: []
-      }
-    }
-    emits('update:childNodeP', data)
-  } else {
-    emits('update:childNodeP', {
-      nodeName: '路由',
-      type: 4,
-      childNode: null,
-      conditionNodes: [
-        {
-          nodeName: '条件1',
-          error: true,
-          type: 3,
-          priorityLevel: 1,
-          conditionList: [],
-          nodeUserList: [],
-          childNode: props.childNodeP
-        },
-        {
-          nodeName: '条件2',
-          type: 3,
-          priorityLevel: 2,
-          conditionList: [],
-          nodeUserList: [],
-          childNode: null
-        }
-      ]
-    })
-  }
-}
-</script>
-<style scoped lang="scss">
-.add-node-btn-box {
-  width: 240px;
-  display: inline-flex;
-  -ms-flex-negative: 0;
-  flex-shrink: 0;
-  -webkit-box-flex: 1;
-  -ms-flex-positive: 1;
-  position: relative;
-
-  &:before {
-    content: '';
-    position: absolute;
-    top: 0;
-    left: 0;
-    right: 0;
-    bottom: 0;
-    z-index: -1;
-    margin: auto;
-    width: 2px;
-    height: 100%;
-    background-color: #cacaca;
-  }
-
-  .add-node-btn {
-    user-select: none;
-    width: 240px;
-    padding: 20px 0 32px;
-    display: flex;
-    -webkit-box-pack: center;
-    justify-content: center;
-    flex-shrink: 0;
-    -webkit-box-flex: 1;
-    flex-grow: 1;
-
-    .btn {
-      outline: none;
-      box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
-      width: 30px;
-      height: 30px;
-      background: #3296fa;
-      border-radius: 50%;
-      position: relative;
-      border: none;
-      line-height: 30px;
-      -webkit-transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
-      transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
-
-      .iconfont {
-        color: #fff;
-        font-size: 16px;
-      }
-
-      &:hover {
-        transform: scale(1.3);
-        box-shadow: 0 13px 27px 0 rgba(0, 0, 0, 0.1);
-      }
-
-      &:active {
-        transform: none;
-        background: #1e83e9;
-        box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
-      }
-    }
-  }
-}
-
-.add-node-popover-body {
-  display: flex;
-
-  .add-node-popover-item {
-    margin-right: 10px;
-    cursor: pointer;
-    text-align: center;
-    flex: 1;
-    color: #191f25 !important;
-
-    .item-wrapper {
-      user-select: none;
-      display: inline-block;
-      width: 80px;
-      height: 80px;
-      margin-bottom: 5px;
-      background: #fff;
-      border: 1px solid #e2e2e2;
-      border-radius: 50%;
-      transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
-
-      .iconfont {
-        font-size: 35px;
-        line-height: 80px;
-      }
-    }
-
-    &.approver {
-      .item-wrapper {
-        color: #ff943e;
-      }
-    }
-
-    &.notifier {
-      .item-wrapper {
-        color: #3296fa;
-      }
-    }
-
-    &.condition {
-      .item-wrapper {
-        color: #15bc83;
-      }
-    }
-
-    &:hover {
-      .item-wrapper {
-        background: #3296fa;
-        box-shadow: 0 10px 20px 0 rgba(50, 150, 250, 0.4);
-      }
-
-      .iconfont {
-        color: #fff;
-      }
-    }
-
-    &:active {
-      .item-wrapper {
-        box-shadow: none;
-        background: #eaeaea;
-      }
-
-      .iconfont {
-        color: inherit;
-      }
-    }
-  }
-}
-</style>
diff --git a/src/components/SimpleProcessDesigner/src/nodeWrap.vue b/src/components/SimpleProcessDesigner/src/nodeWrap.vue
deleted file mode 100644
index 3c9d5eb..0000000
--- a/src/components/SimpleProcessDesigner/src/nodeWrap.vue
+++ /dev/null
@@ -1,297 +0,0 @@
-<!-- eslint-disable vue/no-mutating-props -->
-<!--
- * @Date: 2022-09-21 14:41:53
- * @LastEditors: StavinLi 495727881@qq.com
- * @LastEditTime: 2023-05-24 15:20:24
- * @FilePath: /Workflow-Vue3/src/components/nodeWrap.vue
--->
-<template>
-     <div class="node-wrap" v-if="nodeConfig.type < 3">
-      <div class="node-wrap-box" :class="(nodeConfig.type == 0 ? 'start-node ' : '') +(isTried && nodeConfig.error ? 'active error' : '')">
-          <div class="title" :style="`background: rgb(${bgColors[nodeConfig.type]});`">
-            <span v-if="nodeConfig.type == 0">{{ nodeConfig.nodeName }}</span>
-            <template v-else>
-              <span class="iconfont">{{nodeConfig.type == 1?'':''}}</span>
-              <input
-                v-if="isInput"
-                type="text"
-                class="ant-input editable-title-input"
-                @blur="blurEvent()"
-                @focus="$event.currentTarget.select()"
-                v-focus
-                v-model="nodeConfig.nodeName"
-                :placeholder="defaultText"
-              />
-              <span v-else class="editable-title" @click="clickEvent()">{{ nodeConfig.nodeName }}</span>
-              <i class="anticon anticon-close close" @click="delNode"></i>
-            </template>
-          </div>
-          <div class="content" @click="setPerson">
-            <div class="text">
-                <span class="placeholder" v-if="!showText">请选择{{defaultText}}</span>
-                {{showText}}
-            </div>
-            <i class="anticon anticon-right arrow"></i>
-          </div>
-          <div class="error_tip" v-if="isTried && nodeConfig.error">
-            <i class="anticon anticon-exclamation-circle"></i>
-          </div>
-      </div>
-      <addNode v-model:childNodeP="nodeConfig.childNode" />
-    </div>
-    <div class="branch-wrap" v-if="nodeConfig.type == 4">
-    <div class="branch-box-wrap">
-      <div class="branch-box">
-        <button class="add-branch" @click="addTerm">添加条件</button>
-        <div class="col-box" v-for="(item, index) in nodeConfig.conditionNodes" :key="index">
-          <div class="condition-node">
-            <div class="condition-node-box">
-              <div class="auto-judge" :class="isTried && item.error ? 'error active' : ''">
-                <div class="sort-left" v-if="index != 0" @click="arrTransfer(index, -1)">&lt;</div>
-                <div class="title-wrapper">
-                  <input
-                    v-if="isInputList[index]"
-                    type="text"
-                    class="ant-input editable-title-input"
-                    @blur="blurEvent(index)"
-                    @focus="$event.currentTarget.select()"
-                    v-model="item.nodeName"
-                  />
-                  <span v-else class="editable-title" @click="clickEvent(index)">{{ item.nodeName }}</span>
-                  <span class="priority-title" @click="setPerson(item.priorityLevel)">优先级{{ item.priorityLevel }}</span>
-                  <i class="anticon anticon-close close" @click="delTerm(index)"></i>
-                </div>
-                <div class="sort-right" v-if="index != nodeConfig.conditionNodes.length - 1" @click="arrTransfer(index)">&gt;</div>
-                <div class="content" @click="setPerson(item.priorityLevel)">{{ conditionStr(nodeConfig, index) }}</div>
-                <div class="error_tip" v-if="isTried && item.error">
-                    <i class="anticon anticon-exclamation-circle"></i>
-                </div>
-              </div>
-              <addNode v-model:childNodeP="item.childNode" />
-            </div>
-          </div>
-          <nodeWrap v-if="item.childNode" v-model:nodeConfig="item.childNode" />
-          <template v-if="index == 0">
-            <div class="top-left-cover-line"></div>
-            <div class="bottom-left-cover-line"></div>
-          </template>
-          <template v-if="index == nodeConfig.conditionNodes.length - 1">
-            <div class="top-right-cover-line"></div>
-            <div class="bottom-right-cover-line"></div>
-          </template>
-        </div>
-      </div>
-      <addNode v-model:childNodeP="nodeConfig.childNode" />
-    </div>
-  </div>
-    <nodeWrap v-if="nodeConfig.childNode" v-model:nodeConfig="nodeConfig.childNode" />
-</template>
-<script  setup>
-import addNode from './addNode.vue'
-import { onMounted, ref, watch, getCurrentInstance, computed } from 'vue'
-import {
-  arrToStr,
-  conditionStr,
-  setApproverStr,
-  copyerStr,
-  bgColors,
-  placeholderList
-} from './util'
-import { useWorkFlowStoreWithOut } from '@/store/modules/simpleWorkflow'
-let _uid = getCurrentInstance().uid
-
-let props = defineProps({
-  nodeConfig: {
-    type: Object,
-    default: () => ({})
-  },
-  flowPermission: {
-    type: Object,
-    // eslint-disable-next-line vue/require-valid-default-prop
-    default: () => []
-  }
-})
-
-let defaultText = computed(() => {
-  return placeholderList[props.nodeConfig.type]
-})
-let showText = computed(() => {
-  if (props.nodeConfig.type == 0) return arrToStr(props.flowPermission) || '所有人'
-  if (props.nodeConfig.type == 1) return setApproverStr(props.nodeConfig)
-  return copyerStr(props.nodeConfig)
-})
-
-let isInputList = ref([])
-let isInput = ref(false)
-const resetConditionNodesErr = () => {
-  for (var i = 0; i < props.nodeConfig.conditionNodes.length; i++) {
-    // eslint-disable-next-line vue/no-mutating-props
-    props.nodeConfig.conditionNodes[i].error =
-      conditionStr(props.nodeConfig, i) == '请设置条件' &&
-      i != props.nodeConfig.conditionNodes.length - 1
-  }
-}
-onMounted(() => {
-  if (props.nodeConfig.type == 1) {
-    // eslint-disable-next-line vue/no-mutating-props
-    props.nodeConfig.error = !setApproverStr(props.nodeConfig)
-  } else if (props.nodeConfig.type == 2) {
-    // eslint-disable-next-line vue/no-mutating-props
-    props.nodeConfig.error = !copyerStr(props.nodeConfig)
-  } else if (props.nodeConfig.type == 4) {
-    resetConditionNodesErr()
-  }
-})
-let emits = defineEmits(['update:flowPermission', 'update:nodeConfig'])
-let store = useWorkFlowStoreWithOut()
-let {
-  setPromoter,
-  setApprover,
-  setCopyer,
-  setCondition,
-  setFlowPermission,
-  setApproverConfig,
-  setCopyerConfig,
-  setConditionsConfig
-} = store
-let isTried = computed(() => store.isTried)
-let flowPermission1 = computed(() => store.flowPermission1)
-let approverConfig1 = computed(() => store.approverConfig1)
-let copyerConfig1 = computed(() => store.copyerConfig1)
-let conditionsConfig1 = computed(() => store.conditionsConfig1)
-watch(flowPermission1, (flow) => {
-  if (flow.flag && flow.id === _uid) {
-    emits('update:flowPermission', flow.value)
-  }
-})
-watch(approverConfig1, (approver) => {
-  if (approver.flag && approver.id === _uid) {
-    emits('update:nodeConfig', approver.value)
-  }
-})
-watch(copyerConfig1, (copyer) => {
-  if (copyer.flag && copyer.id === _uid) {
-    emits('update:nodeConfig', copyer.value)
-  }
-})
-watch(conditionsConfig1, (condition) => {
-  if (condition.flag && condition.id === _uid) {
-    emits('update:nodeConfig', condition.value)
-  }
-})
-
-const clickEvent = (index) => {
-  if (index || index === 0) {
-    isInputList.value[index] = true
-  } else {
-    isInput.value = true
-  }
-}
-const blurEvent = (index) => {
-  if (index || index === 0) {
-    isInputList.value[index] = false
-    // eslint-disable-next-line vue/no-mutating-props
-    props.nodeConfig.conditionNodes[index].nodeName =
-      props.nodeConfig.conditionNodes[index].nodeName || '条件'
-  } else {
-    isInput.value = false
-    // eslint-disable-next-line vue/no-mutating-props
-    props.nodeConfig.nodeName = props.nodeConfig.nodeName || defaultText
-  }
-}
-const delNode = () => {
-  emits('update:nodeConfig', props.nodeConfig.childNode)
-}
-const addTerm = () => {
-  let len = props.nodeConfig.conditionNodes.length + 1
-  // eslint-disable-next-line vue/no-mutating-props
-  props.nodeConfig.conditionNodes.push({
-    nodeName: '条件' + len,
-    type: 3,
-    priorityLevel: len,
-    conditionList: [],
-    nodeUserList: [],
-    childNode: null
-  })
-  resetConditionNodesErr()
-  emits('update:nodeConfig', props.nodeConfig)
-}
-const delTerm = (index) => {
-  // eslint-disable-next-line vue/no-mutating-props
-  props.nodeConfig.conditionNodes.splice(index, 1)
-  props.nodeConfig.conditionNodes.map((item, index) => {
-    item.priorityLevel = index + 1
-    item.nodeName = `条件${index + 1}`
-  })
-  resetConditionNodesErr()
-  emits('update:nodeConfig', props.nodeConfig)
-  if (props.nodeConfig.conditionNodes.length == 1) {
-    if (props.nodeConfig.childNode) {
-      if (props.nodeConfig.conditionNodes[0].childNode) {
-        reData(props.nodeConfig.conditionNodes[0].childNode, props.nodeConfig.childNode)
-      } else {
-        // eslint-disable-next-line vue/no-mutating-props
-        props.nodeConfig.conditionNodes[0].childNode = props.nodeConfig.childNode
-      }
-    }
-    emits('update:nodeConfig', props.nodeConfig.conditionNodes[0].childNode)
-  }
-}
-const reData = (data, addData) => {
-  if (!data.childNode) {
-    data.childNode = addData
-  } else {
-    reData(data.childNode, addData)
-  }
-}
-const setPerson = (priorityLevel) => {
-  var { type } = props.nodeConfig
-  if (type == 0) {
-    setPromoter(true)
-    setFlowPermission({
-      value: props.flowPermission,
-      flag: false,
-      id: _uid
-    })
-  } else if (type == 1) {
-    setApprover(true)
-    setApproverConfig({
-      value: {
-        ...JSON.parse(JSON.stringify(props.nodeConfig)),
-        ...{ settype: props.nodeConfig.settype ? props.nodeConfig.settype : 1 }
-      },
-      flag: false,
-      id: _uid
-    })
-  } else if (type == 2) {
-    setCopyer(true)
-    setCopyerConfig({
-      value: JSON.parse(JSON.stringify(props.nodeConfig)),
-      flag: false,
-      id: _uid
-    })
-  } else {
-    setCondition(true)
-    setConditionsConfig({
-      value: JSON.parse(JSON.stringify(props.nodeConfig)),
-      priorityLevel,
-      flag: false,
-      id: _uid
-    })
-  }
-}
-const arrTransfer = (index, type = 1) => {
-  //向左-1,向右1
-  // eslint-disable-next-line vue/no-mutating-props
-  props.nodeConfig.conditionNodes[index] = props.nodeConfig.conditionNodes.splice(
-    index + type,
-    1,
-    props.nodeConfig.conditionNodes[index]
-  )[0]
-  props.nodeConfig.conditionNodes.map((item, index) => {
-    item.priorityLevel = index + 1
-  })
-  resetConditionNodesErr()
-  emits('update:nodeConfig', props.nodeConfig)
-}
-</script>
diff --git a/src/components/SimpleProcessDesigner/src/util.ts b/src/components/SimpleProcessDesigner/src/util.ts
deleted file mode 100644
index f4acd76..0000000
--- a/src/components/SimpleProcessDesigner/src/util.ts
+++ /dev/null
@@ -1,165 +0,0 @@
-/**
- * todo
- */
-export const arrToStr = (arr?: [{ name: string }]) => {
-  if (arr) {
-    return arr
-      .map((item) => {
-        return item.name
-      })
-      .toString()
-  }
-}
-
-export const setApproverStr = (nodeConfig: any) => {
-  if (nodeConfig.settype == 1) {
-    if (nodeConfig.nodeUserList.length == 1) {
-      return nodeConfig.nodeUserList[0].name
-    } else if (nodeConfig.nodeUserList.length > 1) {
-      if (nodeConfig.examineMode == 1) {
-        return arrToStr(nodeConfig.nodeUserList)
-      } else if (nodeConfig.examineMode == 2) {
-        return nodeConfig.nodeUserList.length + '人会签'
-      }
-    }
-  } else if (nodeConfig.settype == 2) {
-    const level =
-      nodeConfig.directorLevel == 1 ? '直接主管' : '第' + nodeConfig.directorLevel + '级主管'
-    if (nodeConfig.examineMode == 1) {
-      return level
-    } else if (nodeConfig.examineMode == 2) {
-      return level + '会签'
-    }
-  } else if (nodeConfig.settype == 4) {
-    if (nodeConfig.selectRange == 1) {
-      return '发起人自选'
-    } else {
-      if (nodeConfig.nodeUserList.length > 0) {
-        if (nodeConfig.selectRange == 2) {
-          return '发起人自选'
-        } else {
-          return '发起人从' + nodeConfig.nodeUserList[0].name + '中自选'
-        }
-      } else {
-        return ''
-      }
-    }
-  } else if (nodeConfig.settype == 5) {
-    return '发起人自己'
-  } else if (nodeConfig.settype == 7) {
-    return '从直接主管到通讯录中级别最高的第' + nodeConfig.examineEndDirectorLevel + '个层级主管'
-  }
-}
-
-export const copyerStr = (nodeConfig: any) => {
-  if (nodeConfig.nodeUserList.length != 0) {
-    return arrToStr(nodeConfig.nodeUserList)
-  } else {
-    if (nodeConfig.ccSelfSelectFlag == 1) {
-      return '发起人自选'
-    }
-  }
-}
-export const conditionStr = (nodeConfig, index) => {
-  const { conditionList, nodeUserList } = nodeConfig.conditionNodes[index]
-  if (conditionList.length == 0) {
-    return index == nodeConfig.conditionNodes.length - 1 &&
-      nodeConfig.conditionNodes[0].conditionList.length != 0
-      ? '其他条件进入此流程'
-      : '请设置条件'
-  } else {
-    let str = ''
-    for (let i = 0; i < conditionList.length; i++) {
-      const {
-        columnId,
-        columnType,
-        showType,
-        showName,
-        optType,
-        zdy1,
-        opt1,
-        zdy2,
-        opt2,
-        fixedDownBoxValue
-      } = conditionList[i]
-      if (columnId == 0) {
-        if (nodeUserList.length != 0) {
-          str += '发起人属于:'
-          str +=
-            nodeUserList
-              .map((item) => {
-                return item.name
-              })
-              .join('或') + ' 并且 '
-        }
-      }
-      if (columnType == 'String' && showType == '3') {
-        if (zdy1) {
-          str += showName + '属于:' + dealStr(zdy1, JSON.parse(fixedDownBoxValue)) + ' 并且 '
-        }
-      }
-      if (columnType == 'Double') {
-        if (optType != 6 && zdy1) {
-          const optTypeStr = ['', '<', '>', '≤', '=', '≥'][optType]
-          str += `${showName} ${optTypeStr} ${zdy1} 并且 `
-        } else if (optType == 6 && zdy1 && zdy2) {
-          str += `${zdy1} ${opt1} ${showName} ${opt2} ${zdy2} 并且 `
-        }
-      }
-    }
-    return str ? str.substring(0, str.length - 4) : '请设置条件'
-  }
-}
-
-export const dealStr = (str: string, obj) => {
-  const arr = []
-  const list = str.split(',')
-  for (const elem in obj) {
-    list.map((item) => {
-      if (item == elem) {
-        arr.push(obj[elem].value)
-      }
-    })
-  }
-  return arr.join('或')
-}
-
-export const removeEle = (arr, elem, key = 'id') => {
-  let includesIndex
-  arr.map((item, index) => {
-    if (item[key] == elem[key]) {
-      includesIndex = index
-    }
-  })
-  arr.splice(includesIndex, 1)
-}
-
-export const bgColors = ['87, 106, 149', '255, 148, 62', '50, 150, 250']
-export const placeholderList = ['发起人', '审核人', '抄送人']
-export const setTypes = [
-  { value: 1, label: '指定成员' },
-  { value: 2, label: '主管' },
-  { value: 4, label: '发起人自选' },
-  { value: 5, label: '发起人自己' },
-  { value: 7, label: '连续多级主管' }
-]
-
-export const selectModes = [
-  { value: 1, label: '选一个人' },
-  { value: 2, label: '选多个人' }
-]
-
-export const selectRanges = [
-  { value: 1, label: '全公司' },
-  { value: 2, label: '指定成员' },
-  { value: 3, label: '指定角色' }
-]
-
-export const optTypes = [
-  { value: '1', label: '小于' },
-  { value: '2', label: '大于' },
-  { value: '3', label: '小于等于' },
-  { value: '4', label: '等于' },
-  { value: '5', label: '大于等于' },
-  { value: '6', label: '介于两个数之间' }
-]
diff --git a/src/components/SimpleProcessDesigner/theme/workflow.css b/src/components/SimpleProcessDesigner/theme/workflow.css
deleted file mode 100644
index 888b1a8..0000000
--- a/src/components/SimpleProcessDesigner/theme/workflow.css
+++ /dev/null
@@ -1,1292 +0,0 @@
-
-.clearfix {
-    zoom: 1
-}
-
-.clearfix:after,
-.clearfix:before {
-    content: "";
-    display: table
-}
-
-.clearfix:after {
-    clear: both
-}
-
-@font-face {
-    font-family: anticon;
-    font-display: fallback;
-    src: url("https://at.alicdn.com/t/font_148784_v4ggb6wrjmkotj4i.eot");
-    src: url("https://at.alicdn.com/t/font_148784_v4ggb6wrjmkotj4i.woff") format("woff"), url("https://at.alicdn.com/t/font_148784_v4ggb6wrjmkotj4i.ttf") format("truetype"), url("https://at.alicdn.com/t/font_148784_v4ggb6wrjmkotj4i.svg#iconfont") format("svg")
-}
-
-.anticon {
-    display: inline-block;
-    font-style: normal;
-    vertical-align: baseline;
-    text-align: center;
-    text-transform: none;
-    line-height: 1;
-    text-rendering: optimizeLegibility;
-    -webkit-font-smoothing: antialiased;
-    -moz-osx-font-smoothing: grayscale
-}
-
-.anticon:before {
-    display: block;
-    font-family: anticon!important
-}
-.anticon-close:before {
-  content: "\E633"
-}
-.anticon-right:before {
-    content: "\E61F"
-}
-.anticon-exclamation-circle{
-    color: rgb(242, 86, 67)
-}
-.anticon-exclamation-circle:before {
-    content: "\E62C"
-}
-
-.anticon-left:before {
-    content: "\E620"
-}
-
-.anticon-close-circle:before {
-    content: "\E62E"
-}
-  
-.ant-btn {
-    line-height: 1.5;
-    display: inline-block;
-    font-weight: 400;
-    text-align: center;
-    touch-action: manipulation;
-    cursor: pointer;
-    background-image: none;
-    border: 1px solid transparent;
-    white-space: nowrap;
-    padding: 0 15px;
-    font-size: 14px;
-    border-radius: 4px;
-    height: 32px;
-    user-select: none;
-    transition: all .3s cubic-bezier(.645, .045, .355, 1);
-    position: relative;
-    color: rgba(0, 0, 0, .65);
-    background-color: #fff;
-    border-color: #d9d9d9
-}
-
-.ant-btn>.anticon {
-    line-height: 1
-}
-
-.ant-btn,
-.ant-btn:active,
-.ant-btn:focus {
-    outline: 0
-}
-
-.ant-btn>a:only-child {
-    color: currentColor
-}
-
-.ant-btn>a:only-child:after {
-    content: "";
-    position: absolute;
-    top: 0;
-    left: 0;
-    bottom: 0;
-    right: 0;
-    background: transparent
-}
-
-.ant-btn:focus,
-.ant-btn:hover {
-    color: #40a9ff;
-    background-color: #fff;
-    border-color: #40a9ff
-}
-
-.ant-btn:focus>a:only-child,
-.ant-btn:hover>a:only-child {
-    color: currentColor
-}
-
-.ant-btn:focus>a:only-child:after,
-.ant-btn:hover>a:only-child:after {
-    content: "";
-    position: absolute;
-    top: 0;
-    left: 0;
-    bottom: 0;
-    right: 0;
-    background: transparent
-}
-
-.ant-btn.active,
-.ant-btn:active {
-    color: #096dd9;
-    background-color: #fff;
-    border-color: #096dd9
-}
-
-.ant-btn.active>a:only-child,
-.ant-btn:active>a:only-child {
-    color: currentColor
-}
-
-.ant-btn.active>a:only-child:after,
-.ant-btn:active>a:only-child:after {
-    content: "";
-    position: absolute;
-    top: 0;
-    left: 0;
-    bottom: 0;
-    right: 0;
-    background: transparent
-}
-
-.ant-btn.active,
-.ant-btn:active,
-.ant-btn:focus,
-.ant-btn:hover {
-    background: #fff;
-    text-decoration: none
-}
-
-.ant-btn>i,
-.ant-btn>span {
-    pointer-events: none
-}
-
-.ant-btn:before {
-    position: absolute;
-    top: -1px;
-    left: -1px;
-    bottom: -1px;
-    right: -1px;
-    background: #fff;
-    opacity: .35;
-    content: "";
-    border-radius: inherit;
-    z-index: 1;
-    transition: opacity .2s;
-    pointer-events: none;
-    display: none
-}
-
-.ant-btn .anticon {
-    transition: margin-left .3s cubic-bezier(.645, .045, .355, 1)
-}
-
-.ant-btn:active>span,
-.ant-btn:focus>span {
-    position: relative
-}
-
-.ant-btn>.anticon+span,
-.ant-btn>span+.anticon {
-    margin-left: 8px
-}
-
-.ant-input {
-    font-family: Chinese Quote, -apple-system, BlinkMacSystemFont, Segoe UI, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Helvetica Neue, Helvetica, Arial, sans-serif;
-    font-variant: tabular-nums;
-    box-sizing: border-box;
-    margin: 0;
-    padding: 0;
-    list-style: none;
-    position: relative;
-    display: inline-block;
-    padding: 4px 11px;
-    width: 100%;
-    height: 32px;
-    font-size: 14px;
-    line-height: 1.5;
-    color: rgba(0, 0, 0, .65);
-    background-color: #fff;
-    background-image: none;
-    border: 1px solid #d9d9d9;
-    border-radius: 4px;
-    transition: all .3s
-}
-
-.ant-input::-moz-placeholder {
-    color: #bfbfbf;
-    opacity: 1
-}
-
-.ant-input:-ms-input-placeholder {
-    color: #bfbfbf
-}
-
-.ant-input::-webkit-input-placeholder {
-    color: #bfbfbf
-}
-
-.ant-input:focus,
-.ant-input:hover {
-    border-color: #40a9ff;
-    border-right-width: 1px!important
-}
-
-.ant-input:focus {
-    outline: 0;
-    box-shadow: 0 0 0 2px rgba(24, 144, 255, .2)
-}
-
-textarea.ant-input {
-    max-width: 100%;
-    height: auto;
-    vertical-align: bottom;
-    transition: all .3s, height 0s;
-    min-height: 32px
-}
-
-a,
-abbr,
-acronym,
-address,
-applet,
-article,
-aside,
-audio,
-b,
-big,
-blockquote,
-body,
-canvas,
-caption,
-center,
-cite,
-code,
-dd,
-del,
-details,
-dfn,
-div,
-dl,
-dt,
-em,
-fieldset,
-figcaption,
-figure,
-footer,
-form,
-h1,
-h2,
-h3,
-h4,
-h5,
-h6,
-header,
-hgroup,
-html,
-i,
-iframe,
-img,
-ins,
-kbd,
-label,
-legend,
-li,
-mark,
-menu,
-nav,
-object,
-ol,
-p,
-pre,
-q,
-s,
-samp,
-section,
-small,
-span,
-strike,
-strong,
-sub,
-summary,
-sup,
-table,
-tbody,
-td,
-tfoot,
-th,
-thead,
-time,
-tr,
-tt,
-u,
-ul,
-var,
-video {
-    margin: 0;
-    padding: 0;
-    border: 0;
-    outline: 0;
-    font-size: 100%;
-    font: inherit;
-    vertical-align: baseline
-}
-
-*,
-:after,
-:before {
-    -webkit-box-sizing: border-box;
-    -moz-box-sizing: border-box;
-    box-sizing: border-box
-}
-
-html {
-    font-family: sans-serif;
-    -ms-text-size-adjust: 100%;
-    -webkit-text-size-adjust: 100%
-}
-
-body,
-html {
-    font-size: 14px
-}
-
-body {
-    font-family: Microsoft Yahei, Lucida Grande, Lucida Sans Unicode, Helvetica, Arial, Verdana, sans-serif;
-    line-height: 1.6;
-    background-color: #fff;
-    position: static!important;
-    -webkit-tap-highlight-color: rgba(0, 0, 0, 0)
-}
-
-ol,
-ul {
-    list-style-type: none
-}
-
-b,
-strong {
-    font-weight: 700
-}
-
-img {
-    border: 0
-}
-
-button,
-input,
-select,
-textarea {
-    font-family: inherit;
-    font-size: 100%;
-    margin: 0
-}
-
-textarea {
-    overflow: auto;
-    vertical-align: top;
-    -webkit-appearance: none
-}
-
-button,
-input {
-    line-height: normal
-}
-
-button,
-select {
-    text-transform: none
-}
-
-button,
-html input[type=button],
-input[type=reset],
-input[type=submit] {
-    -webkit-appearance: button;
-    cursor: pointer
-}
-
-input[type=search] {
-    -webkit-appearance: textfield;
-    -moz-box-sizing: content-box;
-    -webkit-box-sizing: content-box;
-    box-sizing: content-box
-}
-
-input[type=search]::-webkit-search-cancel-button,
-input[type=search]::-webkit-search-decoration {
-    -webkit-appearance: none
-}
-
-button::-moz-focus-inner,
-input::-moz-focus-inner {
-    border: 0;
-    padding: 0
-}
-
-table {
-    width: 100%;
-    border-spacing: 0;
-    border-collapse: collapse
-}
-
-table,
-td,
-th {
-    border: 0
-}
-
-td,
-th {
-    padding: 0;
-    vertical-align: top
-}
-
-th {
-    font-weight: 700;
-    text-align: left
-}
-
-thead th {
-    white-space: nowrap
-}
-
-a {
-    text-decoration: none;
-    cursor: pointer;
-    color: #3296fa
-}
-
-a:active,
-a:hover {
-    outline: 0;
-    color: #3296fa
-}
-
-small {
-    font-size: 80%
-}
-
-body,
-html {
-    font-size: 12px!important;
-    color: #191f25!important;
-    background: #f6f6f6!important
-}
-
-.wrap {
-    display: -webkit-box;
-    display: -ms-flexbox;
-    display: flex;
-    -webkit-box-orient: vertical;
-    -webkit-box-direction: normal;
-    -ms-flex-direction: column;
-    flex-direction: column;
-    height: 100%
-}
-
-@font-face {
-    font-family: IconFont;
-    src: url("//at.alicdn.com/t/font_135284_ph2thxxbzgf.eot");
-    src: url("//at.alicdn.com/t/font_135284_ph2thxxbzgf.eot?#iefix") format("embedded-opentype"), url("//at.alicdn.com/t/font_135284_ph2thxxbzgf.woff") format("woff"), url("//at.alicdn.com/t/font_135284_ph2thxxbzgf.ttf") format("truetype"), url("//at.alicdn.com/t/font_135284_ph2thxxbzgf.svg#IconFont") format("svg")
-}
-
-.iconfont {
-    font-family: IconFont!important;
-    font-size: 16px;
-    font-style: normal;
-    -webkit-font-smoothing: antialiased;
-    -webkit-text-stroke-width: .2px;
-    -moz-osx-font-smoothing: grayscale
-}
-
-.fd-nav {
-    position: fixed;
-    top: 0;
-    left: 0;
-    right: 0;
-    z-index: 997;
-    width: 100%;
-    height: 60px;
-    font-size: 14px;
-    color: #fff;
-    background: #3296fa;
-    display: flex;
-    align-items: center
-}
-
-.fd-nav>* {
-    flex: 1;
-    width: 100%
-}
-
-.fd-nav .fd-nav-left {
-    display: -webkit-box;
-    display: flex;
-    align-items: center
-}
-
-.fd-nav .fd-nav-center {
-    flex: none;
-    width: 600px;
-    text-align: center
-}
-
-.fd-nav .fd-nav-right {
-    display: flex;
-    align-items: center;
-    justify-content: flex-end;
-    text-align: right
-}
-
-.fd-nav .fd-nav-back {
-    display: inline-block;
-    width: 60px;
-    height: 60px;
-    font-size: 22px;
-    border-right: 1px solid #1583f2;
-    text-align: center;
-    cursor: pointer
-}
-
-.fd-nav .fd-nav-back:hover {
-    background: #5af
-}
-
-.fd-nav .fd-nav-back:active {
-    background: #1583f2
-}
-
-.fd-nav .fd-nav-back .anticon {
-    line-height: 60px
-}
-
-.fd-nav .fd-nav-title {
-    width: 0;
-    flex: 1;
-    overflow: hidden;
-    white-space: nowrap;
-    text-overflow: ellipsis;
-    padding: 0 15px
-}
-
-.fd-nav a {
-    color: #fff;
-    margin-left: 12px
-}
-
-.fd-nav .button-publish {
-    min-width: 80px;
-    margin-left: 4px;
-    margin-right: 15px;
-    color: #3296fa;
-    border-color: #fff
-}
-
-.fd-nav .button-publish.ant-btn:focus,
-.fd-nav .button-publish.ant-btn:hover {
-    color: #3296fa;
-    border-color: #fff;
-    box-shadow: 0 10px 20px 0 rgba(0, 0, 0, .3)
-}
-
-.fd-nav .button-publish.ant-btn:active {
-    color: #3296fa;
-    background: #d6eaff;
-    box-shadow: none
-}
-
-.fd-nav .button-preview {
-    min-width: 80px;
-    margin-left: 16px;
-    margin-right: 4px;
-    color: #fff;
-    border-color: #fff;
-    background: transparent
-}
-
-.fd-nav .button-preview.ant-btn:focus,
-.fd-nav .button-preview.ant-btn:hover {
-    color: #fff;
-    border-color: #fff;
-    background: #59acfc
-}
-
-.fd-nav .button-preview.ant-btn:active {
-    color: #fff;
-    border-color: #fff;
-    background: #2186ef
-}
-
-.fd-nav-content {
-    position: fixed;
-    top: 60px;
-    left: 0;
-    right: 0;
-    bottom: 0;
-    z-index: 1;
-    overflow-x: hidden;
-    overflow-y: auto;
-    padding-bottom: 30px
-}
-
-.error-modal-desc {
-    font-size: 13px;
-    color: rgba(25, 31, 37, .56);
-    line-height: 22px;
-    margin-bottom: 14px
-}
-
-.error-modal-list {
-    height: 200px;
-    overflow-y: auto;
-    margin-right: -25px;
-    padding-right: 25px
-}
-
-.error-modal-item {
-    padding: 10px 20px;
-    line-height: 21px;
-    background: #f6f6f6;
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    margin-bottom: 8px;
-    border-radius: 4px
-}
-
-.error-modal-item-label {
-    flex: none;
-    font-size: 15px;
-    color: rgba(25, 31, 37, .56);
-    padding-right: 10px
-}
-
-.error-modal-item-content {
-    text-align: right;
-    flex: 1;
-    font-size: 13px;
-    color: #191f25
-}
-
-#body.blur {
-    -webkit-filter: blur(3px);
-    filter: blur(3px)
-}
-
-.zoom {
-    display: flex;
-    position: fixed;
-    -webkit-box-align: center;
-    -ms-flex-align: center;
-    align-items: center;
-    -webkit-box-pack: justify;
-    -ms-flex-pack: justify;
-    justify-content: space-between;
-    height: 40px;
-    width: 125px;
-    right: 40px;
-    margin-top: 30px;
-    z-index: 10
-}
-
-.zoom .zoom-in,
-.zoom .zoom-out {
-    width: 30px;
-    height: 30px;
-    background: #fff;
-    color: #c1c1cd;
-    cursor: pointer;
-    background-size: 100%;
-    background-repeat: no-repeat
-}
-
-.zoom .zoom-out {
-    background-image: url(https://gw.alicdn.com/tfs/TB1s0qhBHGYBuNjy0FoXXciBFXa-90-90.png)
-}
-
-.zoom .zoom-out.disabled {
-    opacity: .5
-}
-
-.zoom .zoom-in {
-    background-image: url(https://gw.alicdn.com/tfs/TB1UIgJBTtYBeNjy1XdXXXXyVXa-90-90.png)
-}
-
-.zoom .zoom-in.disabled {
-    opacity: .5
-}
-
-.auto-judge:hover .editable-title,
-.node-wrap-box:hover .editable-title {
-    border-bottom: 1px dashed #fff
-}
-
-.auto-judge:hover .editable-title.editing,
-.node-wrap-box:hover .editable-title.editing {
-    text-decoration: none;
-    border: 1px solid #d9d9d9
-}
-
-.auto-judge:hover .editable-title {
-    border-color: #15bc83
-}
-
-.editable-title {
-    line-height: 15px;
-    overflow: hidden;
-    white-space: nowrap;
-    text-overflow: ellipsis;
-    border-bottom: 1px dashed transparent
-}
-
-.editable-title:before {
-    content: "";
-    position: absolute;
-    top: 0;
-    left: 0;
-    bottom: 0;
-    right: 40px
-}
-
-.editable-title:hover {
-    border-bottom: 1px dashed #fff
-}
-
-.editable-title-input {
-    flex: none;
-    height: 18px;
-    padding-left: 4px;
-    text-indent: 0;
-    font-size: 12px;
-    line-height: 18px;
-    z-index: 1
-}
-
-.editable-title-input:hover {
-    text-decoration: none
-}
-
-.ant-btn {
-    position: relative
-}
-
-.node-wrap-box {
-    display: -webkit-inline-box;
-    display: -ms-inline-flexbox;
-    display: inline-flex;
-    -webkit-box-orient: vertical;
-    -webkit-box-direction: normal;
-    -ms-flex-direction: column;
-    flex-direction: column;
-    position: relative;
-    width: 220px;
-    min-height: 72px;
-    -ms-flex-negative: 0;
-    flex-shrink: 0;
-    background: #fff;
-    border-radius: 4px;
-    cursor: pointer
-}
-
-.node-wrap-box:after {
-    pointer-events: none;
-    content: "";
-    position: absolute;
-    top: 0;
-    bottom: 0;
-    left: 0;
-    right: 0;
-    z-index: 2;
-    border-radius: 4px;
-    border: 1px solid transparent;
-    transition: all .1s cubic-bezier(.645, .045, .355, 1);
-    box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .1)
-}
-
-.node-wrap-box.active:after,
-.node-wrap-box:active:after,
-.node-wrap-box:hover:after {
-    border: 1px solid #3296fa;
-    box-shadow: 0 0 6px 0 rgba(50, 150, 250, .3)
-}
-
-.node-wrap-box.active .close,
-.node-wrap-box:active .close,
-.node-wrap-box:hover .close {
-    display: block
-}
-
-.node-wrap-box.error:after {
-    border: 1px solid #f25643;
-    box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .1)
-}
-
-.node-wrap-box .title {
-    position: relative;
-    display: flex;
-    align-items: center;
-    padding-left: 16px;
-    padding-right: 30px;
-    width: 100%;
-    height: 24px;
-    line-height: 24px;
-    font-size: 12px;
-    color: #fff;
-    text-align: left;
-    background: #576a95;
-    border-radius: 4px 4px 0 0
-}
-
-.node-wrap-box .title .iconfont {
-    font-size: 12px;
-    margin-right: 5px
-}
-
-.node-wrap-box .placeholder {
-    color: #bfbfbf
-}
-
-.node-wrap-box .close {
-    display: none;
-    position: absolute;
-    right: 10px;
-    top: 50%;
-    transform: translateY(-50%);
-    width: 20px;
-    height: 20px;
-    font-size: 14px;
-    color: #fff;
-    border-radius: 50%;
-    text-align: center;
-    line-height: 20px
-}
-
-.node-wrap-box .content {
-    position: relative;
-    font-size: 14px;
-    padding: 16px;
-    padding-right: 30px
-}
-
-.node-wrap-box .content .text {
-    overflow: hidden;
-    text-overflow: ellipsis;
-    display: -webkit-box;
-    -webkit-line-clamp: 3;
-    -webkit-box-orient: vertical
-}
-
-.node-wrap-box .content .arrow {
-    position: absolute;
-    right: 10px;
-    top: 50%;
-    transform: translateY(-50%);
-    width: 20px;
-    height: 14px;
-    font-size: 14px;
-    color: #979797
-}
-
-.start-node.node-wrap-box .content .text {
-    display: block;
-    white-space: nowrap
-}
-
-.node-wrap-box:before {
-    content: "";
-    position: absolute;
-    top: -12px;
-    left: 50%;
-    -webkit-transform: translateX(-50%);
-    transform: translateX(-50%);
-    width: 0;
-    height: 4px;
-    border-style: solid;
-    border-width: 8px 6px 4px;
-    border-color: #cacaca transparent transparent;
-    background: #f5f5f7
-}
-
-.node-wrap-box.start-node:before {
-    content: none
-}
-
-.top-left-cover-line {
-    left: -1px
-}
-
-.top-left-cover-line,
-.top-right-cover-line {
-    position: absolute;
-    height: 8px;
-    width: 50%;
-    background-color: #f5f5f7;
-    top: -4px
-}
-
-.top-right-cover-line {
-    right: -1px
-}
-
-.bottom-left-cover-line {
-    left: -1px
-}
-
-.bottom-left-cover-line,
-.bottom-right-cover-line {
-    position: absolute;
-    height: 8px;
-    width: 50%;
-    background-color: #f5f5f7;
-    bottom: -4px
-}
-
-.bottom-right-cover-line {
-    right: -1px
-}
-
-.dingflow-design {
-    width: 100%;
-    background-color: #f5f5f7;
-    overflow: auto;
-    position: absolute;
-    bottom: 0;
-    left: 0;
-    right: 0;
-    top: 0
-}
-
-.dingflow-design .box-scale {
-    transform: scale(1);
-    display: inline-block;
-    position: relative;
-    width: 100%;
-    padding: 54.5px 0;
-    -webkit-box-align: start;
-    -ms-flex-align: start;
-    align-items: flex-start;
-    -webkit-box-pack: center;
-    -ms-flex-pack: center;
-    justify-content: center;
-    -ms-flex-wrap: wrap;
-    flex-wrap: wrap;
-    min-width: -webkit-min-content;
-    min-width: -moz-min-content;
-    min-width: min-content;
-    background-color: #f5f5f7;
-    transform-origin: 50% 0px 0px;
-}
-
-.dingflow-design .node-wrap {
-    flex-direction: column;
-    -webkit-box-pack: start;
-    -ms-flex-pack: start;
-    justify-content: flex-start;
-    -webkit-box-align: center;
-    -ms-flex-align: center;
-    align-items: center;
-    -ms-flex-wrap: wrap;
-    flex-wrap: wrap;
-    -webkit-box-flex: 1;
-    -ms-flex-positive: 1;
-    padding: 0 50px;
-    position: relative
-}
-
-.dingflow-design .branch-wrap,
-.dingflow-design .node-wrap {
-    display: inline-flex;
-    width: 100%
-}
-
-.dingflow-design .branch-box-wrap {
-    display: flex;
-    -webkit-box-orient: vertical;
-    -webkit-box-direction: normal;
-    -ms-flex-direction: column;
-    flex-direction: column;
-    -ms-flex-wrap: wrap;
-    flex-wrap: wrap;
-    -webkit-box-align: center;
-    -ms-flex-align: center;
-    align-items: center;
-    min-height: 270px;
-    width: 100%;
-    -ms-flex-negative: 0;
-    flex-shrink: 0
-}
-
-.dingflow-design .branch-box {
-    display: flex;
-    overflow: visible;
-    min-height: 180px;
-    height: auto;
-    border-bottom: 2px solid #ccc;
-    border-top: 2px solid #ccc;
-    position: relative;
-    margin-top: 15px
-}
-
-.dingflow-design .branch-box .col-box {
-    background: #f5f5f7
-}
-
-.dingflow-design .branch-box .col-box:before {
-    content: "";
-    position: absolute;
-    top: 0;
-    left: 0;
-    right: 0;
-    bottom: 0;
-    z-index: 0;
-    margin: auto;
-    width: 2px;
-    height: 100%;
-    background-color: #cacaca
-}
-
-.dingflow-design .add-branch {
-    border: none;
-    outline: none;
-    user-select: none;
-    justify-content: center;
-    font-size: 12px;
-    padding: 0 10px;
-    height: 30px;
-    line-height: 30px;
-    border-radius: 15px;
-    color: #3296fa;
-    background: #fff;
-    box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .1);
-    position: absolute;
-    top: -16px;
-    left: 50%;
-    transform: translateX(-50%);
-    transform-origin: center center;
-    cursor: pointer;
-    z-index: 1;
-    display: inline-flex;
-    align-items: center;
-    -webkit-transition: all .3s cubic-bezier(.645, .045, .355, 1);
-    transition: all .3s cubic-bezier(.645, .045, .355, 1)
-}
-
-.dingflow-design .add-branch:hover {
-    transform: translateX(-50%) scale(1.1);
-    box-shadow: 0 8px 16px 0 rgba(0, 0, 0, .1)
-}
-
-.dingflow-design .add-branch:active {
-    transform: translateX(-50%);
-    box-shadow: none
-}
-
-.dingflow-design .col-box {
-    display: inline-flex;
-    -webkit-box-orient: vertical;
-    -webkit-box-direction: normal;
-    flex-direction: column;
-    -webkit-box-align: center;
-    align-items: center;
-    position: relative
-}
-
-.dingflow-design .condition-node {
-    min-height: 220px
-}
-
-.dingflow-design .condition-node,
-.dingflow-design .condition-node-box {
-    display: inline-flex;
-    -webkit-box-orient: vertical;
-    -webkit-box-direction: normal;
-    flex-direction: column;
-    -webkit-box-flex: 1
-}
-
-.dingflow-design .condition-node-box {
-    padding-top: 30px;
-    padding-right: 50px;
-    padding-left: 50px;
-    -webkit-box-pack: center;
-    justify-content: center;
-    -webkit-box-align: center;
-    align-items: center;
-    flex-grow: 1;
-    position: relative
-}
-
-.dingflow-design .condition-node-box:before {
-    content: "";
-    position: absolute;
-    top: 0;
-    left: 0;
-    right: 0;
-    bottom: 0;
-    margin: auto;
-    width: 2px;
-    height: 100%;
-    background-color: #cacaca
-}
-
-.dingflow-design .auto-judge {
-    position: relative;
-    width: 220px;
-    min-height: 72px;
-    background: #fff;
-    border-radius: 4px;
-    padding: 14px 19px;
-    cursor: pointer
-}
-
-.dingflow-design .auto-judge:after {
-    pointer-events: none;
-    content: "";
-    position: absolute;
-    top: 0;
-    bottom: 0;
-    left: 0;
-    right: 0;
-    z-index: 2;
-    border-radius: 4px;
-    border: 1px solid transparent;
-    transition: all .1s cubic-bezier(.645, .045, .355, 1);
-    box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .1)
-}
-
-.dingflow-design .auto-judge.active:after,
-.dingflow-design .auto-judge:active:after,
-.dingflow-design .auto-judge:hover:after {
-    border: 1px solid #3296fa;
-    box-shadow: 0 0 6px 0 rgba(50, 150, 250, .3)
-}
-
-.dingflow-design .auto-judge.active .close,
-.dingflow-design .auto-judge:active .close,
-.dingflow-design .auto-judge:hover .close {
-    display: block
-}
-
-.dingflow-design .auto-judge.error:after {
-    border: 1px solid #f25643;
-    box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .1)
-}
-
-.dingflow-design .auto-judge .title-wrapper {
-    position: relative;
-    font-size: 12px;
-    color: #15bc83;
-    text-align: left;
-    line-height: 16px
-}
-
-.dingflow-design .auto-judge .title-wrapper .editable-title {
-    display: inline-block;
-    max-width: 120px;
-    overflow: hidden;
-    white-space: nowrap;
-    text-overflow: ellipsis
-}
-
-.dingflow-design .auto-judge .title-wrapper .priority-title {
-    display: inline-block;
-    float: right;
-    margin-right: 10px;
-    color: rgba(25, 31, 37, .56)
-}
-
-.dingflow-design .auto-judge .placeholder {
-    color: #bfbfbf
-}
-
-.dingflow-design .auto-judge .close {
-    display: none;
-    position: absolute;
-    right: -10px;
-    top: -10px;
-    width: 20px;
-    height: 20px;
-    font-size: 14px;
-    color: rgba(0, 0, 0, .25);
-    border-radius: 50%;
-    text-align: center;
-    line-height: 20px;
-    z-index: 2
-}
-
-.dingflow-design .auto-judge .content {
-    font-size: 14px;
-    color: #191f25;
-    text-align: left;
-    margin-top: 6px;
-    overflow: hidden;
-    text-overflow: ellipsis;
-    display: -webkit-box;
-    -webkit-line-clamp: 3;
-    -webkit-box-orient: vertical
-}
-
-.dingflow-design .auto-judge .sort-left,
-.dingflow-design .auto-judge .sort-right {
-    position: absolute;
-    top: 0;
-    bottom: 0;
-    display: none;
-    z-index: 1
-}
-
-.dingflow-design .auto-judge .sort-left {
-    left: 0;
-    border-right: 1px solid #f6f6f6
-}
-
-.dingflow-design .auto-judge .sort-right {
-    right: 0;
-    border-left: 1px solid #f6f6f6
-}
-
-.dingflow-design .auto-judge:hover .sort-left,
-.dingflow-design .auto-judge:hover .sort-right {
-    display: flex;
-    align-items: center
-}
-
-.dingflow-design .auto-judge .sort-left:hover,
-.dingflow-design .auto-judge .sort-right:hover {
-    background: #efefef
-}
-
-.dingflow-design .end-node {
-    border-radius: 50%;
-    font-size: 14px;
-    color: rgba(25, 31, 37, .4);
-    text-align: left
-}
-
-.dingflow-design .end-node .end-node-circle {
-    width: 10px;
-    height: 10px;
-    margin: auto;
-    border-radius: 50%;
-    background: #dbdcdc
-}
-
-.dingflow-design .end-node .end-node-text {
-    margin-top: 5px;
-    text-align: center
-}
-
-.approval-setting {
-    border-radius: 2px;
-    margin: 20px 0;
-    position: relative;
-    background: #fff
-}
-
-.ant-btn {
-    position: relative
-}
-
-
diff --git a/src/components/SimpleProcessDesignerV2/src/NodeHandler.vue b/src/components/SimpleProcessDesignerV2/src/NodeHandler.vue
new file mode 100644
index 0000000..853a0aa
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/NodeHandler.vue
@@ -0,0 +1,214 @@
+<template>
+  <div class="node-handler-wrapper">
+    <div class="node-handler">
+      <el-popover
+        trigger="hover"
+        v-model:visible="popoverShow"
+        placement="right-start"
+        width="auto"
+        v-if="!readonly"
+      >
+        <div class="handler-item-wrapper">
+          <div class="handler-item" @click="addNode(NodeType.USER_TASK_NODE)">
+            <div class="approve handler-item-icon">
+              <span class="iconfont icon-approve 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>
+            </div>
+            <div class="handler-item-text">抄送</div>
+          </div>
+          <div class="handler-item" @click="addNode(NodeType.CONDITION_BRANCH_NODE)">
+            <div class="handler-item-icon condition">
+              <span class="iconfont icon-size icon-exclusive"></span>
+            </div>
+            <div class="handler-item-text">条件分支</div>
+          </div>
+          <div class="handler-item" @click="addNode(NodeType.PARALLEL_BRANCH_NODE)">
+            <div class="handler-item-icon parallel">
+              <span class="iconfont icon-size icon-parallel"></span>
+            </div>
+            <div class="handler-item-text">并行分支</div>
+          </div>
+          <div class="handler-item" @click="addNode(NodeType.INCLUSIVE_BRANCH_NODE)">
+            <div class="handler-item-icon inclusive">
+              <span class="iconfont icon-size icon-inclusive"></span>
+            </div>
+            <div class="handler-item-text">包容分支</div>
+          </div>
+        </div>
+        <template #reference>
+          <div class="add-icon"><Icon icon="ep:plus" /></div>
+        </template>
+      </el-popover>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import {
+  ApproveMethodType,
+  AssignEmptyHandlerType,
+  AssignStartUserHandlerType,
+  NODE_DEFAULT_NAME,
+  NodeType,
+  RejectHandlerType,
+  SimpleFlowNode
+} from './consts'
+import { generateUUID } from '@/utils'
+
+defineOptions({
+  name: 'NodeHandler'
+})
+
+const message = useMessage() // 消息弹窗
+
+const popoverShow = ref(false)
+const props = defineProps({
+  childNode: {
+    type: Object as () => SimpleFlowNode,
+    default: null
+  },
+  currentNode: {
+    type: Object as () => SimpleFlowNode,
+    required: true
+  }
+})
+const emits = defineEmits(['update:childNode'])
+
+const readonly = inject<Boolean>('readonly') // 是否只读
+
+const addNode = (type: number) => {
+  // 校验:条件分支、包容分支后面,不允许直接添加并行分支
+  if (
+    type === NodeType.PARALLEL_BRANCH_NODE &&
+    [NodeType.CONDITION_BRANCH_NODE, NodeType.INCLUSIVE_BRANCH_NODE].includes(
+      props.currentNode?.type
+    )
+  ) {
+    message.error('条件分支、包容分支后面,不允许直接添加并行分支')
+    return
+  }
+
+  popoverShow.value = false
+  if (type === NodeType.USER_TASK_NODE) {
+    const id = 'Activity_' + generateUUID()
+    const data: SimpleFlowNode = {
+      id: id,
+      name: NODE_DEFAULT_NAME.get(NodeType.USER_TASK_NODE) as string,
+      showText: '',
+      type: NodeType.USER_TASK_NODE,
+      approveMethod: ApproveMethodType.SEQUENTIAL_APPROVE,
+      // 超时处理
+      rejectHandler: {
+        type: RejectHandlerType.FINISH_PROCESS
+      },
+      timeoutHandler: {
+        enable: false
+      },
+      assignEmptyHandler: {
+        type: AssignEmptyHandlerType.APPROVE
+      },
+      assignStartUserHandlerType: AssignStartUserHandlerType.START_USER_AUDIT,
+      childNode: props.childNode
+    }
+    emits('update:childNode', data)
+  }
+  if (type === NodeType.COPY_TASK_NODE) {
+    const data: SimpleFlowNode = {
+      id: 'Activity_' + generateUUID(),
+      name: NODE_DEFAULT_NAME.get(NodeType.COPY_TASK_NODE) as string,
+      showText: '',
+      type: NodeType.COPY_TASK_NODE,
+      childNode: props.childNode
+    }
+    emits('update:childNode', data)
+  }
+  if (type === NodeType.CONDITION_BRANCH_NODE) {
+    const data: SimpleFlowNode = {
+      name: '条件分支',
+      type: NodeType.CONDITION_BRANCH_NODE,
+      id: 'GateWay_' + generateUUID(),
+      childNode: props.childNode,
+      conditionNodes: [
+        {
+          id: 'Flow_' + generateUUID(),
+          name: '条件1',
+          showText: '',
+          type: NodeType.CONDITION_NODE,
+          childNode: undefined,
+          conditionType: 1,
+          defaultFlow: false
+        },
+        {
+          id: 'Flow_' + generateUUID(),
+          name: '其它情况',
+          showText: '未满足其它条件时,将进入此分支',
+          type: NodeType.CONDITION_NODE,
+          childNode: undefined,
+          conditionType: undefined,
+          defaultFlow: true
+        }
+      ]
+    }
+    emits('update:childNode', data)
+  }
+  if (type === NodeType.PARALLEL_BRANCH_NODE) {
+    const data: SimpleFlowNode = {
+      name: '并行分支',
+      type: NodeType.PARALLEL_BRANCH_NODE,
+      id: 'GateWay_' + generateUUID(),
+      childNode: props.childNode,
+      conditionNodes: [
+        {
+          id: 'Flow_' + generateUUID(),
+          name: '并行1',
+          showText: '无需配置条件同时执行',
+          type: NodeType.CONDITION_NODE,
+          childNode: undefined
+        },
+        {
+          id: 'Flow_' + generateUUID(),
+          name: '并行2',
+          showText: '无需配置条件同时执行',
+          type: NodeType.CONDITION_NODE,
+          childNode: undefined
+        }
+      ]
+    }
+    emits('update:childNode', data)
+  }
+  if (type === NodeType.INCLUSIVE_BRANCH_NODE) {
+    const data: SimpleFlowNode = {
+      name: '包容分支',
+      type: NodeType.INCLUSIVE_BRANCH_NODE,
+      id: 'GateWay_' + generateUUID(),
+      childNode: props.childNode,
+      conditionNodes: [
+        {
+          id: 'Flow_' + generateUUID(),
+          name: '包容条件1',
+          showText: '',
+          type: NodeType.CONDITION_NODE,
+          childNode: undefined,
+          defaultFlow: false
+        },
+        {
+          id: 'Flow_' + generateUUID(),
+          name: '其它情况',
+          showText: '未满足其它条件时,将进入此分支',
+          type: NodeType.CONDITION_NODE,
+          childNode: undefined,
+          defaultFlow: true
+        }
+      ]
+    }
+    emits('update:childNode', data)
+  }
+}
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue b/src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue
new file mode 100644
index 0000000..9b1d65a
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue
@@ -0,0 +1,118 @@
+<template>
+  <!-- 发起人节点 -->
+  <StartUserNode
+    v-if="currentNode && currentNode.type === NodeType.START_USER_NODE"
+    :flow-node="currentNode"
+  />
+  <!-- 审批节点 -->
+  <UserTaskNode
+    v-if="currentNode && currentNode.type === NodeType.USER_TASK_NODE"
+    :flow-node="currentNode"
+    @update:flow-node="handleModelValueUpdate"
+    @find:parent-node="findFromParentNode"
+  />
+  <!-- 抄送节点 -->
+  <CopyTaskNode
+    v-if="currentNode && currentNode.type === NodeType.COPY_TASK_NODE"
+    :flow-node="currentNode"
+    @update:flow-node="handleModelValueUpdate"
+  />
+  <!-- 条件节点 -->
+  <ExclusiveNode
+    v-if="currentNode && currentNode.type === NodeType.CONDITION_BRANCH_NODE"
+    :flow-node="currentNode"
+    @update:model-value="handleModelValueUpdate"
+    @find:parent-node="findFromParentNode"
+  />
+  <!-- 并行节点 -->
+  <ParallelNode
+    v-if="currentNode && currentNode.type === NodeType.PARALLEL_BRANCH_NODE"
+    :flow-node="currentNode"
+    @update:model-value="handleModelValueUpdate"
+    @find:parent-node="findFromParentNode"
+  />
+  <!-- 包容分支节点 -->
+  <InclusiveNode
+    v-if="currentNode && currentNode.type === NodeType.INCLUSIVE_BRANCH_NODE"
+    :flow-node="currentNode"
+    @update:model-value="handleModelValueUpdate"
+    @find:parent-node="findFromParentNode"
+  />
+  <!-- 递归显示孩子节点  -->
+  <ProcessNodeTree
+    v-if="currentNode && currentNode.childNode"
+    v-model:flow-node="currentNode.childNode"
+    :parent-node="currentNode"
+    @find:recursive-find-parent-node="recursiveFindParentNode"
+  />
+
+  <!-- 结束节点 -->
+  <EndEventNode
+    v-if="currentNode && currentNode.type === NodeType.END_EVENT_NODE"
+    :flow-node="currentNode"
+  />
+</template>
+<script setup lang="ts">
+import StartUserNode from './nodes/StartUserNode.vue'
+import EndEventNode from './nodes/EndEventNode.vue'
+import UserTaskNode from './nodes/UserTaskNode.vue'
+import CopyTaskNode from './nodes/CopyTaskNode.vue'
+import ExclusiveNode from './nodes/ExclusiveNode.vue'
+import ParallelNode from './nodes/ParallelNode.vue'
+import InclusiveNode from './nodes/InclusiveNode.vue'
+import { SimpleFlowNode, NodeType } from './consts'
+import { useWatchNode } from './node'
+defineOptions({
+  name: 'ProcessNodeTree'
+})
+const props = defineProps({
+  parentNode: {
+    type: Object as () => SimpleFlowNode,
+    default: () => null
+  },
+  flowNode: {
+    type: Object as () => SimpleFlowNode,
+    default: () => null
+  }
+})
+const emits = defineEmits<{
+  'update:flowNode': [node: SimpleFlowNode | undefined]
+  'find:recursiveFindParentNode': [
+    nodeList: SimpleFlowNode[],
+    curentNode: SimpleFlowNode,
+    nodeType: number
+  ]
+}>()
+
+const currentNode = useWatchNode(props)
+
+// 用于删除节点
+const handleModelValueUpdate = (updateValue) => {
+  emits('update:flowNode', updateValue)
+}
+
+const findFromParentNode = (nodeList: SimpleFlowNode[], nodeType: number) => {
+  emits('find:recursiveFindParentNode', nodeList, props.parentNode, nodeType)
+}
+
+// 递归从父节点中查询匹配的节点
+const recursiveFindParentNode = (
+  nodeList: SimpleFlowNode[],
+  findNode: SimpleFlowNode,
+  nodeType: number
+) => {
+  if (!findNode) {
+    return
+  }
+  if (findNode.type === NodeType.START_USER_NODE) {
+    nodeList.push(findNode)
+    return
+  }
+
+  if (findNode.type === nodeType) {
+    nodeList.push(findNode)
+  }
+  emits('find:recursiveFindParentNode', nodeList, props.parentNode, nodeType)
+}
+</script>
+<style lang="scss" scoped></style>
diff --git a/src/components/SimpleProcessDesignerV2/src/SimpleProcessDesigner.vue b/src/components/SimpleProcessDesignerV2/src/SimpleProcessDesigner.vue
new file mode 100644
index 0000000..6b5ff99
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/SimpleProcessDesigner.vue
@@ -0,0 +1,179 @@
+<template>
+  <div v-loading="loading" class="overflow-auto">
+    <SimpleProcessModel
+      v-if="processNodeTree"
+      :flow-node="processNodeTree"
+      :readonly="false"
+      @save="saveSimpleFlowModel"
+    />
+    <Dialog v-model="errorDialogVisible" title="保存失败" width="400" :fullscreen="false">
+      <div class="mb-2">以下节点内容不完善,请修改后保存</div>
+      <div
+        class="mb-3 b-rounded-1 bg-gray-100 p-2 line-height-normal"
+        v-for="(item, index) in errorNodes"
+        :key="index"
+      >
+        {{ item.name }} : {{ NODE_DEFAULT_TEXT.get(item.type) }}
+      </div>
+      <template #footer>
+        <el-button type="primary" @click="errorDialogVisible = false">知道了</el-button>
+      </template>
+    </Dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import SimpleProcessModel from './SimpleProcessModel.vue'
+import { updateBpmSimpleModel, getBpmSimpleModel } from '@/api/bpm/simple'
+import { SimpleFlowNode, NodeType, NodeId, NODE_DEFAULT_TEXT } from './consts'
+import { getModel } from '@/api/bpm/model'
+import { getForm, FormVO } from '@/api/bpm/form'
+import { handleTree } from '@/utils/tree'
+import * as RoleApi from '@/api/system/role'
+import * as DeptApi from '@/api/system/dept'
+import * as PostApi from '@/api/system/post'
+import * as UserApi from '@/api/system/user'
+import * as UserGroupApi from '@/api/bpm/userGroup'
+
+defineOptions({
+  name: 'SimpleProcessDesigner'
+})
+const emits = defineEmits(['success']) // 保存成功事件
+
+const props = defineProps({
+  modelId: {
+    type: String,
+    required: true
+  }
+})
+
+const loading = ref(false)
+const formFields = ref<string[]>([])
+const formType = ref(20)
+const roleOptions = ref<RoleApi.RoleVO[]>([]) // 角色列表
+const postOptions = ref<PostApi.PostVO[]>([]) // 岗位列表
+const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
+const deptOptions = ref<DeptApi.DeptVO[]>([]) // 部门列表
+const deptTreeOptions = ref()
+const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 用户组列表
+provide('formFields', formFields)
+provide('formType', formType)
+provide('roleList', roleOptions)
+provide('postList', postOptions)
+provide('userList', userOptions)
+provide('deptList', deptOptions)
+provide('userGroupList', userGroupOptions)
+provide('deptTree', deptTreeOptions)
+
+const message = useMessage() // 国际化
+const processNodeTree = ref<SimpleFlowNode | undefined>()
+const errorDialogVisible = ref(false)
+let errorNodes: SimpleFlowNode[] = []
+const saveSimpleFlowModel = async (simpleModelNode: SimpleFlowNode) => {
+  if (!simpleModelNode) {
+    message.error('模型数据为空')
+    return
+  }
+  try {
+    loading.value = true
+    const data = {
+      id: props.modelId,
+      simpleModel: simpleModelNode
+    }
+    const result = await updateBpmSimpleModel(data)
+    if (result) {
+      message.success('修改成功')
+      emits('success')
+    } else {
+      message.alert('修改失败')
+    }
+  } finally {
+    loading.value = false
+  }
+}
+// 校验节点设置。 暂时以 showText 为空 未节点错误配置
+const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNode[]) => {
+  if (node) {
+    const { type, showText, conditionNodes } = node
+    if (type == NodeType.END_EVENT_NODE) {
+      return
+    }
+    if (type == NodeType.START_USER_NODE) {
+      // 发起人节点暂时不用校验,直接校验孩子节点
+      validateNode(node.childNode, errorNodes)
+    }
+
+    if (
+      type === NodeType.USER_TASK_NODE ||
+      type === NodeType.COPY_TASK_NODE ||
+      type === NodeType.CONDITION_NODE
+    ) {
+      if (!showText) {
+        errorNodes.push(node)
+      }
+      validateNode(node.childNode, errorNodes)
+    }
+
+    if (
+      type == NodeType.CONDITION_BRANCH_NODE ||
+      type == NodeType.PARALLEL_BRANCH_NODE ||
+      type == NodeType.INCLUSIVE_BRANCH_NODE
+    ) {
+      // 分支节点
+      // 1. 先校验各个分支
+      conditionNodes?.forEach((item) => {
+        validateNode(item, errorNodes)
+      })
+      // 2. 校验孩子节点
+      validateNode(node.childNode, errorNodes)
+    }
+  }
+}
+
+onMounted(async () => {
+  try {
+    loading.value = true
+    // 获取表单字段
+    const bpmnModel = await getModel(props.modelId)
+    if (bpmnModel) {
+      formType.value = bpmnModel.formType
+      if (formType.value === 10) {
+        const bpmnForm = (await getForm(bpmnModel.formId)) as unknown as FormVO
+        formFields.value = bpmnForm?.fields
+      }
+    }
+    // 获得角色列表
+    roleOptions.value = await RoleApi.getSimpleRoleList()
+    // 获得岗位列表
+    postOptions.value = await PostApi.getSimplePostList()
+    // 获得用户列表
+    userOptions.value = await UserApi.getSimpleUserList()
+    // 获得部门列表
+    deptOptions.value = await DeptApi.getSimpleDeptList()
+
+    deptTreeOptions.value = handleTree(deptOptions.value as DeptApi.DeptVO[], 'id')
+    // 获取用户组列表
+    userGroupOptions.value = await UserGroupApi.getUserGroupSimpleList()
+
+    //获取 SIMPLE 设计器模型
+    const result = await getBpmSimpleModel(props.modelId)
+    if (result) {
+      processNodeTree.value = result
+    } else {
+      // 初始值
+      processNodeTree.value = {
+        name: '发起人',
+        type: NodeType.START_USER_NODE,
+        id: NodeId.START_USER_NODE_ID,
+        childNode: {
+          id: NodeId.END_EVENT_NODE_ID,
+          name: '结束',
+          type: NodeType.END_EVENT_NODE
+        }
+      }
+    }
+  } finally {
+    loading.value = false
+  }
+})
+</script>
diff --git a/src/components/SimpleProcessDesignerV2/src/SimpleProcessModel.vue b/src/components/SimpleProcessDesignerV2/src/SimpleProcessModel.vue
new file mode 100644
index 0000000..3a18227
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/SimpleProcessModel.vue
@@ -0,0 +1,140 @@
+<template>
+  <div class="simple-process-model-container position-relative">
+    <div class="position-absolute top-0px right-0px bg-#fff">
+      <el-row type="flex" justify="end">
+        <el-button-group key="scale-control" size="default">
+          <el-button size="default" :icon="ScaleToOriginal" @click="processReZoom()" />
+          <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-group>
+        <el-button
+          v-if="!readonly"
+          size="default"
+          class="ml-4px"
+          type="primary"
+          :icon="Select"
+          @click="saveSimpleFlowModel"
+          >保存模型</el-button
+        >
+      </el-row>
+    </div>
+    <div class="simple-process-model" :style="`transform: scale(${scaleValue / 100});`">
+      <ProcessNodeTree v-if="processNodeTree" v-model:flow-node="processNodeTree" />
+    </div>
+  </div>
+  <Dialog v-model="errorDialogVisible" title="保存失败" width="400" :fullscreen="false">
+    <div class="mb-2">以下节点内容不完善,请修改后保存</div>
+    <div
+      class="mb-3 b-rounded-1 bg-gray-100 p-2 line-height-normal"
+      v-for="(item, index) in errorNodes"
+      :key="index"
+    >
+      {{ item.name }} : {{ NODE_DEFAULT_TEXT.get(item.type) }}
+    </div>
+    <template #footer>
+      <el-button type="primary" @click="errorDialogVisible = false">知道了</el-button>
+    </template>
+  </Dialog>
+</template>
+
+<script setup lang="ts">
+import ProcessNodeTree from './ProcessNodeTree.vue'
+import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from './consts'
+import { useWatchNode } from './node'
+import { Select, ZoomOut, ZoomIn, ScaleToOriginal } from '@element-plus/icons-vue'
+defineOptions({
+  name: 'SimpleProcessModel'
+})
+
+const props = defineProps({
+  flowNode: {
+    type: Object as () => SimpleFlowNode,
+    required: true
+  },
+  readonly: {
+    type: Boolean,
+    required: false,
+    default: true
+  }
+})
+const emits = defineEmits<{
+  'save': [node: SimpleFlowNode | undefined]
+}>()
+
+const processNodeTree = useWatchNode(props)
+
+provide('readonly', props.readonly)
+let scaleValue = ref(100)
+const MAX_SCALE_VALUE = 200
+const MIN_SCALE_VALUE = 50
+// 放大
+const zoomIn = () => {
+  if (scaleValue.value == MAX_SCALE_VALUE) {
+    return
+  }
+  scaleValue.value += 10
+}
+// 缩小
+const zoomOut = () => {
+  if (scaleValue.value == MIN_SCALE_VALUE) {
+    return
+  }
+  scaleValue.value -= 10
+}
+const processReZoom = () => {
+  scaleValue.value = 100
+}
+
+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
+    if (type == NodeType.END_EVENT_NODE) {
+      return
+    }
+    if (type == NodeType.START_USER_NODE) {
+      // 发起人节点暂时不用校验,直接校验孩子节点
+      validateNode(node.childNode, errorNodes)
+    }
+
+    if (
+      type === NodeType.USER_TASK_NODE ||
+      type === NodeType.COPY_TASK_NODE ||
+      type === NodeType.CONDITION_NODE
+    ) {
+      if (!showText) {
+        errorNodes.push(node)
+      }
+      validateNode(node.childNode, errorNodes)
+    }
+
+    if (
+      type == NodeType.CONDITION_BRANCH_NODE ||
+      type == NodeType.PARALLEL_BRANCH_NODE ||
+      type == NodeType.INCLUSIVE_BRANCH_NODE
+    ) {
+      // 分支节点
+      // 1. 先校验各个分支
+      conditionNodes?.forEach((item) => {
+        validateNode(item, errorNodes)
+      })
+      // 2. 校验孩子节点
+      validateNode(node.childNode, errorNodes)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/components/SimpleProcessDesignerV2/src/SimpleProcessViewer.vue b/src/components/SimpleProcessDesignerV2/src/SimpleProcessViewer.vue
new file mode 100644
index 0000000..abf73b4
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/SimpleProcessViewer.vue
@@ -0,0 +1,48 @@
+<template>
+  <SimpleProcessModel :flow-node="simpleModel" :readonly="true" />
+</template>
+
+<script setup lang="ts">
+import { useWatchNode } from './node'
+import { SimpleFlowNode } from './consts'
+
+defineOptions({
+  name: 'SimpleProcessViewer'
+})
+
+const props = defineProps({
+  flowNode: {
+    type: Object as () => SimpleFlowNode,
+    required: true
+  },
+  // 流程任务
+  tasks: {
+    type: Array,
+    default: () => [] as any[]
+  },
+  // 流程实例
+  processInstance: {
+    type: Object,
+    default: () => undefined
+  }
+})
+const approveTasks = ref<any[]>(props.tasks)
+const currentProcessInstance = ref(props.processInstance)
+const simpleModel = useWatchNode(props)
+watch(
+  () => props.tasks,
+  (newValue) => {
+    approveTasks.value = newValue
+  }
+)
+watch(
+  () => props.processInstance,
+  (newValue) => {
+    currentProcessInstance.value = newValue
+  }
+)
+
+provide('tasks', approveTasks)
+provide('processInstance', currentProcessInstance)
+</script>
+p
diff --git a/src/components/SimpleProcessDesignerV2/src/consts.ts b/src/components/SimpleProcessDesignerV2/src/consts.ts
new file mode 100644
index 0000000..b81aa94
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/consts.ts
@@ -0,0 +1,570 @@
+// @ts-ignore
+import { DictDataVO } from '@/api/system/dict/types'
+import { TaskStatusEnum } from '@/api/bpm/task'
+/**
+ * 节点类型
+ */
+export enum NodeType {
+  /**
+   * 结束节点
+   */
+  END_EVENT_NODE = 1,
+  /**
+   * 发起人节点
+   */
+  START_USER_NODE = 10,
+  /**
+   * 审批人节点
+   */
+  USER_TASK_NODE = 11,
+
+  /**
+   * 抄送人节点
+   */
+  COPY_TASK_NODE = 12,
+
+  /**
+   * 条件节点
+   */
+  CONDITION_NODE = 50,
+  /**
+   * 条件分支节点 (对应排他网关)
+   */
+  CONDITION_BRANCH_NODE = 51,
+  /**
+   * 并行分支节点 (对应并行网关)
+   */
+  PARALLEL_BRANCH_NODE = 52,
+
+  /**
+   * 包容分支节点 (对应包容网关)
+   */
+  INCLUSIVE_BRANCH_NODE = 53
+}
+
+export enum NodeId {
+  /**
+   * 发起人节点 Id
+   */
+  START_USER_NODE_ID = 'StartUserNode',
+
+  /**
+   * 发起人节点 Id
+   */
+  END_EVENT_NODE_ID = 'EndEvent'
+}
+
+/**
+ *  节点结构定义
+ */
+export interface SimpleFlowNode {
+  id: string
+  type: NodeType
+  name: string
+  showText?: string
+  // 孩子节点
+  childNode?: SimpleFlowNode
+  // 条件节点
+  conditionNodes?: SimpleFlowNode[]
+  // 审批类型
+  approveType?: ApproveType
+  // 候选人策略
+  candidateStrategy?: number
+  // 候选人参数
+  candidateParam?: string
+  // 多人审批方式
+  approveMethod?: ApproveMethodType
+  //通过比例
+  approveRatio?: number
+  // 审批按钮设置
+  buttonsSetting?: any[]
+  // 表单权限
+  fieldsPermission?: Array<Record<string, any>>
+  // 审批任务超时处理
+  timeoutHandler?: TimeoutHandler
+  // 审批任务拒绝处理
+  rejectHandler?: RejectHandler
+  // 审批人为空的处理
+  assignEmptyHandler?: AssignEmptyHandler
+  // 审批节点的审批人与发起人相同时,对应的处理类型
+  assignStartUserHandlerType?: number
+  // 条件类型
+  conditionType?: ConditionType
+  // 条件表达式
+  conditionExpression?: string
+  // 条件组
+  conditionGroups?: ConditionGroup
+  // 是否默认的条件
+  defaultFlow?: boolean
+  // 活动的状态,用于前端节点状态展示
+  activityStatus?: TaskStatusEnum
+}
+// 候选人策略枚举 ( 用于审批节点。抄送节点 )
+export enum CandidateStrategy {
+  /**
+   * 指定角色
+   */
+  ROLE = 10,
+  /**
+   * 部门成员
+   */
+  DEPT_MEMBER = 20,
+  /**
+   * 部门的负责人
+   */
+  DEPT_LEADER = 21,
+  /**
+   * 连续多级部门的负责人
+   */
+  MULTI_LEVEL_DEPT_LEADER = 23,
+  /**
+   * 指定岗位
+   */
+  POST = 22,
+  /**
+   * 指定用户
+   */
+  USER = 30,
+  /**
+   * 发起人自选
+   */
+  START_USER_SELECT = 35,
+  /**
+   * 发起人自己
+   */
+  START_USER = 36,
+  /**
+   * 发起人部门负责人
+   */
+  START_USER_DEPT_LEADER = 37,
+  /**
+   * 发起人连续多级部门的负责人
+   */
+  START_USER_MULTI_LEVEL_DEPT_LEADER = 38,
+  /**
+   * 指定用户组
+   */
+  USER_GROUP = 40,
+  /**
+   * 表单内用户字段
+   */
+  FORM_USER = 50,
+  /**
+   * 表单内部门负责人
+   */
+  FORM_DEPT_LEADER = 51,
+  /**
+   * 流程表达式
+   */
+  EXPRESSION = 60
+}
+
+// 多人审批方式类型枚举 ( 用于审批节点 )
+export enum ApproveMethodType {
+  /**
+   * 随机挑选一人审批
+   */
+  RANDOM_SELECT_ONE_APPROVE = 1,
+
+  /**
+   * 多人会签(按通过比例)
+   */
+  APPROVE_BY_RATIO = 2,
+
+  /**
+   * 多人或签(通过只需一人,拒绝只需一人)
+   */
+  ANY_APPROVE = 3,
+  /**
+   * 多人依次审批
+   */
+  SEQUENTIAL_APPROVE = 4
+}
+
+/**
+ * 审批拒绝结构定义
+ */
+export type RejectHandler = {
+  // 审批拒绝类型
+  type: RejectHandlerType
+  // 退回节点 Id
+  returnNodeId?: string
+}
+
+/**
+ * 审批超时结构定义
+ */
+export type TimeoutHandler = {
+  // 是否开启超时处理
+  enable: boolean
+  // 超时执行的动作
+  type?: number
+  // 超时时间设置
+  timeDuration?: string
+  // 执行动作是自动提醒, 最大提醒次数
+  maxRemindCount?: number
+}
+
+/**
+ * 审批人为空的结构定义
+ */
+export type AssignEmptyHandler = {
+  // 审批人为空的处理类型
+  type: AssignEmptyHandlerType
+  // 指定用户的编号数组
+  userIds?: number[]
+}
+
+// 审批拒绝类型枚举
+export enum RejectHandlerType {
+  /**
+   * 结束流程
+   */
+  FINISH_PROCESS = 1,
+  /**
+   * 驳回到指定节点
+   */
+  RETURN_USER_TASK = 2
+}
+// 用户任务超时处理类型枚举
+export enum TimeoutHandlerType {
+  /**
+   * 自动提醒
+   */
+  REMINDER = 1,
+  /**
+   * 自动同意
+   */
+  APPROVE = 2,
+  /**
+   * 自动拒绝
+   */
+  REJECT = 3
+}
+// 用户任务的审批人为空时,处理类型枚举
+export enum AssignEmptyHandlerType {
+  /**
+   * 自动通过
+   */
+  APPROVE = 1,
+  /**
+   * 自动拒绝
+   */
+  REJECT = 2,
+  /**
+   * 指定人员审批
+   */
+  ASSIGN_USER,
+  /**
+   * 转交给流程管理员
+   */
+  ASSIGN_ADMIN = 4
+}
+// 用户任务的审批人与发起人相同时,处理类型枚举
+export enum AssignStartUserHandlerType {
+  /**
+   * 由发起人对自己审批
+   */
+  START_USER_AUDIT = 1,
+  /**
+   * 自动跳过【参考飞书】:1)如果当前节点还有其他审批人,则交由其他审批人进行审批;2)如果当前节点没有其他审批人,则该节点自动通过
+   */
+  SKIP = 2,
+  /**
+   * 转交给部门负责人审批
+   */
+  ASSIGN_DEPT_LEADER = 3
+}
+
+// 用户任务的审批类型。 【参考飞书】
+export enum ApproveType {
+  /**
+   * 人工审批
+   */
+  USER = 1,
+  /**
+   * 自动通过
+   */
+  AUTO_APPROVE = 2,
+  /**
+   * 自动拒绝
+   */
+  AUTO_REJECT = 3
+}
+
+// 时间单位枚举
+export enum TimeUnitType {
+  /**
+   * 分钟
+   */
+  MINUTE = 1,
+  /**
+   * 小时
+   */
+  HOUR = 2,
+  /**
+   * 天
+   */
+  DAY = 3
+}
+
+// 条件配置类型 ( 用于条件节点配置 )
+export enum ConditionType {
+  /**
+   * 条件表达式
+   */
+  EXPRESSION = 1,
+
+  /**
+   * 条件规则
+   */
+  RULE = 2
+}
+/**
+ * 表单权限的枚举
+ */
+export enum FieldPermissionType {
+  /**
+   * 只读
+   */
+  READ = '1',
+  /**
+   * 编辑
+   */
+  WRITE = '2',
+  /**
+   * 隐藏
+   */
+  NONE = '3'
+}
+/**
+ * 操作按钮权限结构定义
+ */
+export type ButtonSetting = {
+  id: OperationButtonType
+  displayName: string
+  enable: boolean
+}
+
+// 操作按钮类型枚举 (用于审批节点)
+export enum OperationButtonType {
+  /**
+   * 通过
+   */
+  APPROVE = 1,
+  /**
+   * 拒绝
+   */
+  REJECT = 2,
+  /**
+   * 转办
+   */
+  TRANSFER = 3,
+  /**
+   * 委派
+   */
+  DELEGATE = 4,
+  /**
+   * 加签
+   */
+  ADD_SIGN = 5,
+  /**
+   * 退回
+   */
+  RETURN = 6,
+  /**
+   * 抄送
+   */
+  COPY = 7
+}
+
+/**
+ * 条件规则结构定义
+ */
+export type ConditionRule = {
+  type: number
+  opName: string
+  opCode: string
+  leftSide: string
+  rightSide: string
+}
+
+/**
+ * 条件组结构定义
+ */
+export type ConditionGroup = {
+  // 条件组的逻辑关系是否为且
+  and: boolean
+  // 条件数组
+  conditions: Condition[]
+}
+
+/**
+ * 条件结构定义
+ */
+export type Condition = {
+  // 条件规则的逻辑关系是否为且
+  and: boolean
+  rules: ConditionRule[]
+}
+
+export const NODE_DEFAULT_TEXT = new Map<number, string>()
+NODE_DEFAULT_TEXT.set(NodeType.USER_TASK_NODE, '请配置审批人')
+NODE_DEFAULT_TEXT.set(NodeType.COPY_TASK_NODE, '请配置抄送人')
+NODE_DEFAULT_TEXT.set(NodeType.CONDITION_NODE, '请设置条件')
+NODE_DEFAULT_TEXT.set(NodeType.START_USER_NODE, '请设置发起人')
+
+export const NODE_DEFAULT_NAME = new Map<number, string>()
+NODE_DEFAULT_NAME.set(NodeType.USER_TASK_NODE, '审批人')
+NODE_DEFAULT_NAME.set(NodeType.COPY_TASK_NODE, '抄送人')
+NODE_DEFAULT_NAME.set(NodeType.CONDITION_NODE, '条件')
+NODE_DEFAULT_NAME.set(NodeType.START_USER_NODE, '发起人')
+
+// 候选人策略。暂时不从字典中取。 后续可能调整。控制显示顺序
+export const CANDIDATE_STRATEGY: DictDataVO[] = [
+  { label: '指定成员', value: CandidateStrategy.USER },
+  { label: '指定角色', value: CandidateStrategy.ROLE },
+  { label: '部门成员', value: CandidateStrategy.DEPT_MEMBER },
+  { label: '部门负责人', value: CandidateStrategy.DEPT_LEADER },
+  { label: '连续多级部门负责人', value: CandidateStrategy.MULTI_LEVEL_DEPT_LEADER },
+  { label: '发起人自选', value: CandidateStrategy.START_USER_SELECT },
+  { label: '发起人本人', value: CandidateStrategy.START_USER },
+  { label: '发起人部门负责人', value: CandidateStrategy.START_USER_DEPT_LEADER },
+  { label: '发起人连续部门负责人', value: CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER },
+  { label: '用户组', value: CandidateStrategy.USER_GROUP },
+  { label: '表单内用户字段', value: CandidateStrategy.FORM_USER },
+  { label: '表单内部门负责人', value: CandidateStrategy.FORM_DEPT_LEADER },
+  { label: '流程表达式', value: CandidateStrategy.EXPRESSION }
+]
+// 审批节点 的审批类型
+export const APPROVE_TYPE: DictDataVO[] = [
+  { label: '人工审批', value: ApproveType.USER },
+  { label: '自动通过', value: ApproveType.AUTO_APPROVE },
+  { label: '自动拒绝', value: ApproveType.AUTO_REJECT }
+]
+
+export const APPROVE_METHODS: DictDataVO[] = [
+  { label: '按顺序依次审批', value: ApproveMethodType.SEQUENTIAL_APPROVE },
+  { label: '会签(可同时审批,至少 % 人必须审批通过)', value: ApproveMethodType.APPROVE_BY_RATIO },
+  { label: '或签(可同时审批,有一人通过即可)', value: ApproveMethodType.ANY_APPROVE },
+  { label: '随机挑选一人审批', value: ApproveMethodType.RANDOM_SELECT_ONE_APPROVE }
+]
+
+export const CONDITION_CONFIG_TYPES: DictDataVO[] = [
+  { label: '条件表达式', value: ConditionType.EXPRESSION },
+  { label: '条件规则', value: ConditionType.RULE }
+]
+
+// 时间单位类型
+export const TIME_UNIT_TYPES: DictDataVO[] = [
+  { label: '分钟', value: TimeUnitType.MINUTE },
+  { label: '小时', value: TimeUnitType.HOUR },
+  { label: '天', value: TimeUnitType.DAY }
+]
+// 超时处理执行动作类型
+export const TIMEOUT_HANDLER_TYPES: DictDataVO[] = [
+  { label: '自动提醒', value: 1 },
+  { label: '自动同意', value: 2 },
+  { label: '自动拒绝', value: 3 }
+]
+export const REJECT_HANDLER_TYPES: DictDataVO[] = [
+  { label: '终止流程', value: RejectHandlerType.FINISH_PROCESS },
+  { label: '驳回到指定节点', value: RejectHandlerType.RETURN_USER_TASK }
+  // { label: '结束任务', value: RejectHandlerType.FINISH_TASK }
+]
+export const ASSIGN_EMPTY_HANDLER_TYPES: DictDataVO[] = [
+  { label: '自动通过', value: 1 },
+  { label: '自动拒绝', value: 2 },
+  { label: '指定成员审批', value: 3 },
+  { label: '转交给流程管理员', value: 4 }
+]
+export const ASSIGN_START_USER_HANDLER_TYPES: DictDataVO[] = [
+  { label: '由发起人对自己审批', value: 1 },
+  { label: '自动跳过', value: 2 },
+  { label: '转交给部门负责人审批', value: 3 }
+]
+
+// 比较运算符
+export const COMPARISON_OPERATORS: DictDataVO = [
+  {
+    value: '==',
+    label: '等于'
+  },
+  {
+    value: '!=',
+    label: '不等于'
+  },
+  {
+    value: '>',
+    label: '大于'
+  },
+  {
+    value: '>=',
+    label: '大于等于'
+  },
+  {
+    value: '<',
+    label: '小于'
+  },
+  {
+    value: '<=',
+    label: '小于等于'
+  }
+]
+// 审批操作按钮名称
+export const OPERATION_BUTTON_NAME = new Map<number, string>()
+OPERATION_BUTTON_NAME.set(OperationButtonType.APPROVE, '通过')
+OPERATION_BUTTON_NAME.set(OperationButtonType.REJECT, '拒绝')
+OPERATION_BUTTON_NAME.set(OperationButtonType.TRANSFER, '转办')
+OPERATION_BUTTON_NAME.set(OperationButtonType.DELEGATE, '委派')
+OPERATION_BUTTON_NAME.set(OperationButtonType.ADD_SIGN, '加签')
+OPERATION_BUTTON_NAME.set(OperationButtonType.RETURN, '退回')
+OPERATION_BUTTON_NAME.set(OperationButtonType.COPY, '抄送')
+
+// 默认的按钮权限设置
+export const DEFAULT_BUTTON_SETTING: ButtonSetting[] = [
+  { id: OperationButtonType.APPROVE, displayName: '通过', enable: true },
+  { id: OperationButtonType.REJECT, displayName: '拒绝', enable: true },
+  { id: OperationButtonType.TRANSFER, displayName: '转办', enable: true },
+  { id: OperationButtonType.DELEGATE, displayName: '委派', enable: true },
+  { id: OperationButtonType.ADD_SIGN, displayName: '加签', enable: true },
+  { id: OperationButtonType.RETURN, displayName: '退回', enable: true }
+]
+
+// 发起人的按钮权限。暂时定死,不可以编辑
+export const START_USER_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 MULTI_LEVEL_DEPT: DictDataVO = [
+  { label: '第 1 级部门', value: 1 },
+  { label: '第 2 级部门', value: 2 },
+  { label: '第 3 级部门', value: 3 },
+  { label: '第 4 级部门', value: 4 },
+  { label: '第 5 级部门', value: 5 },
+  { label: '第 6 级部门', value: 6 },
+  { label: '第 7 级部门', value: 7 },
+  { label: '第 8 级部门', value: 8 },
+  { label: '第 9 级部门', value: 9 },
+  { label: '第 10 级部门', value: 10 },
+  { label: '第 11 级部门', value: 11 },
+  { label: '第 12 级部门', value: 12 },
+  { label: '第 13 级部门', value: 13 },
+  { label: '第 14 级部门', value: 14 },
+  { label: '第 15 级部门', value: 15 }
+]
+
+/**
+ * 流程实例的变量枚举
+ */
+export enum ProcessVariableEnum {
+  /**
+   * 发起用户 ID
+   */
+  START_USER_ID = 'PROCESS_START_USER_ID'
+}
diff --git a/src/components/SimpleProcessDesignerV2/src/index.ts b/src/components/SimpleProcessDesignerV2/src/index.ts
new file mode 100644
index 0000000..88de07f
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/index.ts
@@ -0,0 +1,5 @@
+import SimpleProcessDesigner from './SimpleProcessDesigner.vue'
+import SimpleProcessViewer from './SimpleProcessViewer.vue'
+import '../theme/simple-process-designer.scss'
+
+export { SimpleProcessDesigner, SimpleProcessViewer}
diff --git a/src/components/SimpleProcessDesignerV2/src/node.ts b/src/components/SimpleProcessDesignerV2/src/node.ts
new file mode 100644
index 0000000..0810c1f
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/node.ts
@@ -0,0 +1,495 @@
+import { cloneDeep } from 'lodash-es'
+import { TaskStatusEnum } from '@/api/bpm/task'
+import * as RoleApi from '@/api/system/role'
+import * as DeptApi from '@/api/system/dept'
+import * as PostApi from '@/api/system/post'
+import * as UserApi from '@/api/system/user'
+import * as UserGroupApi from '@/api/bpm/userGroup'
+import {
+  SimpleFlowNode,
+  CandidateStrategy,
+  NodeType,
+  ApproveMethodType,
+  RejectHandlerType,
+  NODE_DEFAULT_NAME,
+  AssignStartUserHandlerType,
+  AssignEmptyHandlerType,
+  FieldPermissionType,
+  ProcessVariableEnum
+} from './consts'
+import { parseFormFields } from '@/components/FormCreate/src/utils/index'
+export function useWatchNode(props: { flowNode: SimpleFlowNode }): Ref<SimpleFlowNode> {
+  const node = ref<SimpleFlowNode>(props.flowNode)
+  watch(
+    () => props.flowNode,
+    (newValue) => {
+      node.value = newValue
+    }
+  )
+  return node
+}
+
+// 解析 formCreate 所有表单字段, 并返回
+const parseFormCreateFields = (formFields?: string[]) => {
+  const result: Array<Record<string, any>> = []
+  if (formFields) {
+    formFields.forEach((fieldStr: string) => {
+      parseFormFields(JSON.parse(fieldStr), result)
+    })
+  }
+  // 固定添加发起人 ID 字段
+  result.unshift({
+    field: ProcessVariableEnum.START_USER_ID,
+    title: '发起人',
+    type: 'UserSelect',
+    required: true
+  })
+  return result
+}
+
+/**
+ * @description 表单数据权限配置,用于发起人节点 、审批节点、抄送节点
+ */
+export function useFormFieldsPermission(defaultPermission: FieldPermissionType) {
+  // 字段权限配置. 需要有 field, title,  permissioin 属性
+  const fieldsPermissionConfig = ref<Array<Record<string, any>>>([])
+
+  const formType = inject<Ref<number>>('formType') // 表单类型
+
+  const formFields = inject<Ref<string[]>>('formFields') // 流程表单字段
+
+  const getNodeConfigFormFields = (nodeFormFields?: Array<Record<string, string>>) => {
+    nodeFormFields = toRaw(nodeFormFields)
+    fieldsPermissionConfig.value =
+      cloneDeep(nodeFormFields) || getDefaultFieldsPermission(unref(formFields))
+  }
+  // 默认的表单权限: 获取表单的所有字段,设置字段默认权限为只读
+  const getDefaultFieldsPermission = (formFields?: string[]) => {
+    let defaultFieldsPermission: Array<Record<string, any>> = []
+    if (formFields) {
+      defaultFieldsPermission = parseFormCreateFields(formFields).map((item) => {
+        return {
+          field: item.field,
+          title: item.title,
+          permission: defaultPermission
+        }
+      })
+    }
+    return defaultFieldsPermission
+  }
+
+  // 获取表单的所有字段,作为下拉框选项
+  const formFieldOptions = parseFormCreateFields(unref(formFields))
+
+  return {
+    formType,
+    fieldsPermissionConfig,
+    formFieldOptions,
+    getNodeConfigFormFields
+  }
+}
+/**
+ * @description 获取表单的字段
+ */
+export function useFormFields() {
+  const formFields = inject<Ref<string[]>>('formFields') // 流程表单字段
+  return parseFormCreateFields(unref(formFields))
+}
+
+export type UserTaskFormType = {
+  //candidateParamArray: any[]
+  candidateStrategy: CandidateStrategy
+  approveMethod: ApproveMethodType
+  roleIds?: number[] // 角色
+  deptIds?: number[] // 部门
+  deptLevel?: number // 部门层级
+  userIds?: number[] // 用户
+  userGroups?: number[] // 用户组
+  postIds?: number[] // 岗位
+  expression?: string // 流程表达式
+  formUser?: string // 表单内用户字段
+  formDept?: string // 表单内部门字段
+  approveRatio?: number
+  rejectHandlerType?: RejectHandlerType
+  returnNodeId?: string
+  timeoutHandlerEnable?: boolean
+  timeoutHandlerType?: number
+  assignEmptyHandlerType?: AssignEmptyHandlerType
+  assignEmptyHandlerUserIds?: number[]
+  assignStartUserHandlerType?: AssignStartUserHandlerType
+  timeDuration?: number
+  maxRemindCount?: number
+  buttonsSetting: any[]
+}
+
+export type CopyTaskFormType = {
+  // candidateParamArray: any[]
+  candidateStrategy: CandidateStrategy
+  roleIds?: number[] // 角色
+  deptIds?: number[] // 部门
+  deptLevel?: number // 部门层级
+  userIds?: number[] // 用户
+  userGroups?: number[] // 用户组
+  postIds?: number[] // 岗位
+  formUser?: string // 表单内用户字段
+  formDept?: string // 表单内部门字段
+  expression?: string // 流程表达式
+}
+
+/**
+ * @description 节点表单数据。 用于审批节点、抄送节点
+ */
+export function useNodeForm(nodeType: NodeType) {
+  const roleOptions = inject<Ref<RoleApi.RoleVO[]>>('roleList') // 角色列表
+  const postOptions = inject<Ref<PostApi.PostVO[]>>('postList') // 岗位列表
+  const userOptions = inject<Ref<UserApi.UserVO[]>>('userList') // 用户列表
+  const deptOptions = inject<Ref<DeptApi.DeptVO[]>>('deptList') // 部门列表
+  const userGroupOptions = inject<Ref<UserGroupApi.UserGroupVO[]>>('userGroupList') // 用户组列表
+  const deptTreeOptions = inject('deptTree') // 部门树
+  const formFields = inject<Ref<string[]>>('formFields') // 流程表单字段
+  const configForm = ref<UserTaskFormType | CopyTaskFormType>()
+  if (nodeType === NodeType.USER_TASK_NODE) {
+    configForm.value = {
+      candidateStrategy: CandidateStrategy.USER,
+      approveMethod: ApproveMethodType.SEQUENTIAL_APPROVE,
+      approveRatio: 100,
+      rejectHandlerType: RejectHandlerType.FINISH_PROCESS,
+      assignStartUserHandlerType: AssignStartUserHandlerType.START_USER_AUDIT,
+      returnNodeId: '',
+      timeoutHandlerEnable: false,
+      timeoutHandlerType: 1,
+      timeDuration: 6, // 默认 6小时
+      maxRemindCount: 1, // 默认 提醒 1次
+      buttonsSetting: []
+    }
+  } else {
+    configForm.value = {
+      candidateStrategy: CandidateStrategy.USER
+    }
+  }
+
+  const getShowText = (): string => {
+    let showText = ''
+    // 指定成员
+    if (configForm.value?.candidateStrategy === CandidateStrategy.USER) {
+      if (configForm.value?.userIds!.length > 0) {
+        const candidateNames: string[] = []
+        userOptions?.value.forEach((item) => {
+          if (configForm.value?.userIds!.includes(item.id)) {
+            candidateNames.push(item.nickname)
+          }
+        })
+        showText = `指定成员:${candidateNames.join(',')}`
+      }
+    }
+    // 指定角色
+    if (configForm.value?.candidateStrategy === CandidateStrategy.ROLE) {
+      if (configForm.value.roleIds!.length > 0) {
+        const candidateNames: string[] = []
+        roleOptions?.value.forEach((item) => {
+          if (configForm.value?.roleIds!.includes(item.id)) {
+            candidateNames.push(item.name)
+          }
+        })
+        showText = `指定角色:${candidateNames.join(',')}`
+      }
+    }
+    // 指定部门
+    if (
+      configForm.value?.candidateStrategy === CandidateStrategy.DEPT_MEMBER ||
+      configForm.value?.candidateStrategy === CandidateStrategy.DEPT_LEADER ||
+      configForm.value?.candidateStrategy === CandidateStrategy.MULTI_LEVEL_DEPT_LEADER
+    ) {
+      if (configForm.value?.deptIds!.length > 0) {
+        const candidateNames: string[] = []
+        deptOptions?.value.forEach((item) => {
+          if (configForm.value?.deptIds!.includes(item.id!)) {
+            candidateNames.push(item.name)
+          }
+        })
+        if (configForm.value.candidateStrategy === CandidateStrategy.DEPT_MEMBER) {
+          showText = `部门成员:${candidateNames.join(',')}`
+        } else if (configForm.value.candidateStrategy === CandidateStrategy.DEPT_LEADER) {
+          showText = `部门的负责人:${candidateNames.join(',')}`
+        } else {
+          showText = `多级部门的负责人:${candidateNames.join(',')}`
+        }
+      }
+    }
+
+    // 指定岗位
+    if (configForm.value?.candidateStrategy === CandidateStrategy.POST) {
+      if (configForm.value.postIds!.length > 0) {
+        const candidateNames: string[] = []
+        postOptions?.value.forEach((item) => {
+          if (configForm.value?.postIds!.includes(item.id!)) {
+            candidateNames.push(item.name)
+          }
+        })
+        showText = `指定岗位: ${candidateNames.join(',')}`
+      }
+    }
+    // 指定用户组
+    if (configForm.value?.candidateStrategy === CandidateStrategy.USER_GROUP) {
+      if (configForm.value?.userGroups!.length > 0) {
+        const candidateNames: string[] = []
+        userGroupOptions?.value.forEach((item) => {
+          if (configForm.value?.userGroups!.includes(item.id)) {
+            candidateNames.push(item.name)
+          }
+        })
+        showText = `指定用户组: ${candidateNames.join(',')}`
+      }
+    }
+
+    // 表单内用户字段
+    if (configForm.value?.candidateStrategy === CandidateStrategy.FORM_USER) {
+      const formFieldOptions = parseFormCreateFields(unref(formFields))
+      const item = formFieldOptions.find((item) => item.field === configForm.value?.formUser)
+      showText = `表单用户:${item?.title}`
+    }
+
+    // 表单内部门负责人
+    if (configForm.value?.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER) {
+      showText = `表单内部门负责人`
+    }
+
+    // 发起人自选
+    if (configForm.value?.candidateStrategy === CandidateStrategy.START_USER_SELECT) {
+      showText = `发起人自选`
+    }
+    // 发起人自己
+    if (configForm.value?.candidateStrategy === CandidateStrategy.START_USER) {
+      showText = `发起人自己`
+    }
+    // 发起人的部门负责人
+    if (configForm.value?.candidateStrategy === CandidateStrategy.START_USER_DEPT_LEADER) {
+      showText = `发起人的部门负责人`
+    }
+    // 发起人的部门负责人
+    if (
+      configForm.value?.candidateStrategy === CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER
+    ) {
+      showText = `发起人连续部门负责人`
+    }
+    // 流程表达式
+    if (configForm.value?.candidateStrategy === CandidateStrategy.EXPRESSION) {
+      showText = `流程表达式:${configForm.value.expression}`
+    }
+    return showText
+  }
+
+  /**
+   *  处理候选人参数的赋值
+   */
+  const handleCandidateParam = () => {
+    let candidateParam: undefined | string = undefined
+    if (!configForm.value) {
+      return candidateParam
+    }
+    switch (configForm.value.candidateStrategy) {
+      case CandidateStrategy.USER:
+        candidateParam = configForm.value.userIds!.join(',')
+        break
+      case CandidateStrategy.ROLE:
+        candidateParam = configForm.value.roleIds!.join(',')
+        break
+      case CandidateStrategy.POST:
+        candidateParam = configForm.value.postIds!.join(',')
+        break
+      case CandidateStrategy.USER_GROUP:
+        candidateParam = configForm.value.userGroups!.join(',')
+        break
+      case CandidateStrategy.FORM_USER:
+        candidateParam = configForm.value.formUser!
+        break
+      case CandidateStrategy.EXPRESSION:
+        candidateParam = configForm.value.expression!
+        break
+      case CandidateStrategy.DEPT_MEMBER:
+      case CandidateStrategy.DEPT_LEADER:
+        candidateParam = configForm.value.deptIds!.join(',')
+        break
+      // 发起人部门负责人
+      case CandidateStrategy.START_USER_DEPT_LEADER:
+      case CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER:
+        candidateParam = configForm.value.deptLevel + ''
+        break
+      // 指定连续多级部门的负责人
+      case CandidateStrategy.MULTI_LEVEL_DEPT_LEADER: {
+        // 候选人参数格式: | 分隔 。左边为部门(多个部门用 , 分隔)。 右边为部门层级
+        const deptIds = configForm.value.deptIds!.join(',')
+        candidateParam = deptIds.concat('|' + configForm.value.deptLevel + '')
+        break
+      }
+      // 表单内部门的负责人
+      case CandidateStrategy.FORM_DEPT_LEADER: {
+        // 候选人参数格式: | 分隔 。左边为表单内部门字段。 右边为部门层级
+        const deptFieldOnForm = configForm.value.formDept!
+        candidateParam = deptFieldOnForm.concat('|' + configForm.value.deptLevel + '')
+        break
+      }
+      default:
+        break
+    }
+    return candidateParam
+  }
+  /**
+   *  解析候选人参数
+   */
+  const parseCandidateParam = (
+    candidateStrategy: CandidateStrategy,
+    candidateParam: string | undefined
+  ) => {
+    if (!configForm.value || !candidateParam) {
+      return
+    }
+    switch (candidateStrategy) {
+      case CandidateStrategy.USER: {
+        configForm.value.userIds = candidateParam.split(',').map((item) => +item)
+        break
+      }
+      case CandidateStrategy.ROLE:
+        configForm.value.roleIds = candidateParam.split(',').map((item) => +item)
+        break
+      case CandidateStrategy.POST:
+        configForm.value.postIds = candidateParam.split(',').map((item) => +item)
+        break
+      case CandidateStrategy.USER_GROUP:
+        configForm.value.userGroups = candidateParam.split(',').map((item) => +item)
+        break
+      case CandidateStrategy.FORM_USER:
+        configForm.value.formUser = candidateParam
+        break
+      case CandidateStrategy.EXPRESSION:
+        configForm.value.expression = candidateParam
+        break
+      case CandidateStrategy.DEPT_MEMBER:
+      case CandidateStrategy.DEPT_LEADER:
+        configForm.value.deptIds = candidateParam.split(',').map((item) => +item)
+        break
+      // 发起人部门负责人
+      case CandidateStrategy.START_USER_DEPT_LEADER:
+      case CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER:
+        configForm.value.deptLevel = +candidateParam
+        break
+      // 指定连续多级部门的负责人
+      case CandidateStrategy.MULTI_LEVEL_DEPT_LEADER: {
+        // 候选人参数格式: | 分隔 。左边为部门(多个部门用 , 分隔)。 右边为部门层级
+        const paramArray = candidateParam.split('|')
+        configForm.value.deptIds = paramArray[0].split(',').map((item) => +item)
+        configForm.value.deptLevel = +paramArray[1]
+        break
+      }
+      // 表单内的部门负责人
+      case CandidateStrategy.FORM_DEPT_LEADER: {
+        // 候选人参数格式: | 分隔 。左边为表单内的部门字段。 右边为部门层级
+        const paramArray = candidateParam.split('|')
+        configForm.value.formDept = paramArray[0]
+        configForm.value.deptLevel = +paramArray[1]
+        break
+      }
+      default:
+        break
+    }
+  }
+  return {
+    configForm,
+    roleOptions,
+    postOptions,
+    userOptions,
+    userGroupOptions,
+    deptTreeOptions,
+    handleCandidateParam,
+    parseCandidateParam,
+    getShowText
+  }
+}
+
+/**
+ * @description 抽屉配置
+ */
+export function useDrawer() {
+  // 抽屉配置是否可见
+  const settingVisible = ref(false)
+  // 关闭配置抽屉
+  const closeDrawer = () => {
+    settingVisible.value = false
+  }
+  // 打开配置抽屉
+  const openDrawer = () => {
+    settingVisible.value = true
+  }
+  return {
+    settingVisible,
+    closeDrawer,
+    openDrawer
+  }
+}
+
+/**
+ * @description 节点名称配置
+ */
+export function useNodeName(nodeType: NodeType) {
+  // 节点名称
+  const nodeName = ref<string>()
+  // 节点名称输入框
+  const showInput = ref(false)
+  // 点击节点名称编辑图标
+  const clickIcon = () => {
+    showInput.value = true
+  }
+  // 节点名称输入框失去焦点
+  const blurEvent = () => {
+    showInput.value = false
+    nodeName.value = nodeName.value || (NODE_DEFAULT_NAME.get(nodeType) as string)
+  }
+  return {
+    nodeName,
+    showInput,
+    clickIcon,
+    blurEvent
+  }
+}
+
+export function useNodeName2(node: Ref<SimpleFlowNode>, nodeType: NodeType) {
+  // 显示节点名称输入框
+  const showInput = ref(false)
+  // 节点名称输入框失去焦点
+  const blurEvent = () => {
+    showInput.value = false
+    node.value.name = node.value.name || (NODE_DEFAULT_NAME.get(nodeType) as string)
+  }
+  // 点击节点标题进行输入
+  const clickTitle = () => {
+    showInput.value = true
+  }
+  return {
+    showInput,
+    clickTitle,
+    blurEvent
+  }
+}
+
+/**
+ * @description 根据节点任务状态,获取节点任务状态样式
+ */
+export function useTaskStatusClass(taskStatus: TaskStatusEnum | undefined): string {
+  if (!taskStatus) {
+    return ''
+  }
+  if (taskStatus === TaskStatusEnum.APPROVE) {
+    return 'status-pass'
+  }
+  if (taskStatus === TaskStatusEnum.RUNNING) {
+    return 'status-running'
+  }
+  if (taskStatus === TaskStatusEnum.REJECT) {
+    return 'status-reject'
+  }
+  if (taskStatus === TaskStatusEnum.CANCEL) {
+    return 'status-cancel'
+  }
+
+  return ''
+}
diff --git a/src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue b/src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue
new file mode 100644
index 0000000..ffbf187
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue
@@ -0,0 +1,419 @@
+<template>
+  <el-drawer
+    :append-to-body="true"
+    v-model="settingVisible"
+    :show-close="false"
+    :size="588"
+    :before-close="handleClose"
+  >
+    <template #header>
+      <div class="config-header">
+        <input
+          v-if="showInput"
+          type="text"
+          class="config-editable-input"
+          @blur="blurEvent()"
+          v-mountedFocus
+          v-model="currentNode.name"
+          :placeholder="currentNode.name"
+        />
+        <div v-else class="node-name"
+          >{{ currentNode.name }}
+          <Icon class="ml-1" icon="ep:edit-pen" :size="16" @click="clickIcon()"
+        /></div>
+
+        <div class="divide-line"></div>
+      </div>
+    </template>
+    <div>
+      <div class="mb-3 font-size-16px" v-if="currentNode.defaultFlow">未满足其它条件时,将进入此分支(该分支不可编辑和删除)</div>
+      <div v-else>
+        <el-form
+          ref="formRef"
+          :model="currentNode"
+          :rules="formRules"
+          label-position="top"
+        >
+          <el-form-item label="配置方式" prop="conditionType">
+            <el-radio-group
+              v-model="currentNode.conditionType"
+              @change="changeConditionType"
+            >
+              <el-radio
+                v-for="(dict, index) in conditionConfigTypes"
+                :key="index"
+                :value="dict.value"
+                :label="dict.value"
+              >
+                {{ dict.label }}
+              </el-radio>
+            </el-radio-group>
+          </el-form-item>
+
+          <el-form-item
+            v-if="currentNode.conditionType === 1"
+            label="条件表达式"
+            prop="conditionExpression"
+          >
+            <el-input
+              type="textarea"
+              v-model="currentNode.conditionExpression"
+              clearable
+              style="width: 100%"
+            />
+          </el-form-item>
+          <el-form-item v-if="currentNode.conditionType === 2" label="条件规则">
+            <div class="condition-group-tool">
+              <div class="flex items-center">
+                <div class="mr-4">条件组关系</div>
+                <el-switch
+                  v-model="conditionGroups.and"
+                  inline-prompt
+                  active-text="且"
+                  inactive-text="或"
+                />
+              </div>
+            </div>
+            <el-space direction="vertical" :spacer="conditionGroups.and ? '且' : '或'">
+              <el-card
+                class="condition-group"
+                style="width: 530px"
+                v-for="(condition, cIdx) in conditionGroups.conditions"
+                :key="cIdx"
+              >
+                <div class="condition-group-delete" v-if="conditionGroups.conditions.length > 1">
+                  <Icon
+                    color="#0089ff"
+                    icon="ep:circle-close-filled"
+                    :size="18"
+                    @click="deleteConditionGroup(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="condition.and"
+                        inline-prompt
+                        active-text="且"
+                        inactive-text="或"
+                      />
+                    </div>
+                  </div>
+                </template>
+
+                <div class="flex pt-2" v-for="(rule, rIdx) in condition.rules" :key="rIdx">
+                  <div class="mr-2">
+                    <el-select style="width: 160px" v-model="rule.leftSide">
+                      <el-option
+                        v-for="(item, index) in fieldsInfo"
+                        :key="index"
+                        :label="item.title"
+                        :value="item.field"
+                      />
+                    </el-select>
+                  </div>
+                  <div class="mr-2">
+                    <el-select v-model="rule.opCode" style="width: 100px">
+                      <el-option
+                        v-for="item in COMPARISON_OPERATORS"
+                        :key="item.value"
+                        :label="item.label"
+                        :value="item.value"
+                      />
+                    </el-select>
+                  </div>
+                  <div class="mr-2">
+                    <el-input v-model="rule.rightSide" style="width: 160px" />
+                  </div>
+                  <div class="mr-1 flex items-center" v-if="condition.rules.length > 1">
+                    <Icon
+                      icon="ep:delete"
+                      :size="18"
+                      @click="deleteConditionRule(condition, rIdx)"
+                    />
+                  </div>
+                  <div class="flex items-center">
+                    <Icon icon="ep:plus" :size="18" @click="addConditionRule(condition, rIdx)" />
+                  </div>
+                </div>
+              </el-card>
+            </el-space>
+            <div title="添加条件组" class="mt-4 cursor-pointer">
+              <Icon color="#0089ff" icon="ep:plus" :size="24" @click="addConditionGroup" />
+            </div>
+          </el-form-item>
+        </el-form>
+      </div>
+    </div>
+    <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 {
+  SimpleFlowNode,
+  CONDITION_CONFIG_TYPES,
+  ConditionType,
+  COMPARISON_OPERATORS,
+  ConditionGroup,
+  Condition,
+  ConditionRule
+} from '../consts'
+import { getDefaultConditionNodeName } from '../utils'
+import { useFormFields } from '../node'
+const message = useMessage() // 消息弹窗
+defineOptions({
+  name: 'ConditionNodeConfig'
+})
+const formType = inject<Ref<number>>('formType') // 表单类型
+const conditionConfigTypes = computed(() => {
+  return CONDITION_CONFIG_TYPES.filter((item) => {
+    // 业务表单暂时去掉条件规则选项
+    if (formType?.value !== 10) {
+      return item.value === ConditionType.RULE
+    } else {
+      return true
+    }
+  })
+})
+
+const props = defineProps({
+  conditionNode: {
+    type: Object as () => SimpleFlowNode,
+    required: true
+  },
+  nodeIndex: {
+    type: Number,
+    required: true
+  }
+})
+const settingVisible = ref(false)
+const open = () => {
+  if (currentNode.value.conditionType === ConditionType.RULE) {
+    if (currentNode.value.conditionGroups) {
+      conditionGroups.value = currentNode.value.conditionGroups
+    }
+  }
+  settingVisible.value = true
+}
+
+watch(
+  () => props.conditionNode,
+  (newValue) => {
+    currentNode.value = newValue
+  }
+)
+// 显示名称输入框
+const showInput = ref(false)
+
+const clickIcon = () => {
+  showInput.value = true
+}
+// 输入框失去焦点
+const blurEvent = () => {
+  showInput.value = false
+  currentNode.value.name =
+    currentNode.value.name ||
+    getDefaultConditionNodeName(props.nodeIndex, currentNode.value?.defaultFlow)
+}
+
+const currentNode = ref<SimpleFlowNode>(props.conditionNode)
+
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+// 关闭
+const closeDrawer = () => {
+  settingVisible.value = false
+}
+
+const handleClose = async (done: (cancel?: boolean) => void) => {
+  const isSuccess = await saveConfig()
+  if (!isSuccess) {
+    done(true) // 传入 true 阻止关闭
+  } else {
+    done()
+  }
+}
+// 表单校验规则
+const formRules = reactive({
+  conditionType: [{ required: true, message: '配置方式不能为空', trigger: 'blur' }],
+  conditionExpression: [{ required: true, message: '条件表达式不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+
+// 保存配置
+const saveConfig = async () => {
+  if (!currentNode.value.defaultFlow) {
+    // 校验表单
+    if (!formRef) return false
+    const valid = await formRef.value.validate()
+    if (!valid) return false
+    const showText = getShowText()
+    if (!showText) {
+      return false
+    }
+    currentNode.value.showText = showText
+    if (currentNode.value.conditionType === ConditionType.EXPRESSION) {
+      currentNode.value.conditionGroups = undefined
+    }
+    if (currentNode.value.conditionType === ConditionType.RULE) {
+      currentNode.value.conditionExpression = undefined
+      currentNode.value.conditionGroups = conditionGroups.value
+    }
+  }
+  settingVisible.value = false
+  return true
+}
+const getShowText = (): string => {
+  let showText = ''
+  if (currentNode.value.conditionType === ConditionType.EXPRESSION) {
+    if (currentNode.value.conditionExpression) {
+      showText = `表达式:${currentNode.value.conditionExpression}`
+    }
+  }
+  if (currentNode.value.conditionType === ConditionType.RULE) {
+    // 条件组是否为与关系
+    const groupAnd = conditionGroups.value.and
+    let warningMesg: undefined | string = undefined
+    const conditionGroup = conditionGroups.value.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 changeConditionType = () => {}
+
+const conditionGroups = ref<ConditionGroup>({
+  and: true,
+  conditions: [
+    {
+      and: true,
+      rules: [
+        {
+          type: 1,
+          opName: '等于',
+          opCode: '==',
+          leftSide: '',
+          rightSide: ''
+        }
+      ]
+    }
+  ]
+})
+// 添加条件组
+const addConditionGroup = () => {
+  const condition = {
+    and: true,
+    rules: [
+      {
+        type: 1,
+        opName: '等于',
+        opCode: '==',
+        leftSide: '',
+        rightSide: ''
+      }
+    ]
+  }
+  conditionGroups.value.conditions.push(condition)
+}
+// 删除条件组
+const deleteConditionGroup = (idx: number) => {
+  conditionGroups.value.conditions.splice(idx, 1)
+}
+
+// 添加条件规则
+const addConditionRule = (condition: Condition, idx: number) => {
+  const rule: ConditionRule = {
+    type: 1,
+    opName: '等于',
+    opCode: '==',
+    leftSide: '',
+    rightSide: ''
+  }
+  condition.rules.splice(idx + 1, 0, rule)
+}
+
+const deleteConditionRule = (condition: Condition, idx: number) => {
+  condition.rules.splice(idx, 1)
+}
+
+const fieldsInfo = useFormFields()
+
+const getFieldTitle = (field: string) => {
+  const item = fieldsInfo.find((item) => item.field === field)
+  return item?.title
+}
+
+const getOpName = (opCode: string): string => {
+  const opName = COMPARISON_OPERATORS.find((item) => item.value === opCode)
+  return opName?.label
+}
+</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>
diff --git a/src/components/SimpleProcessDesignerV2/src/nodes-config/CopyTaskNodeConfig.vue b/src/components/SimpleProcessDesignerV2/src/nodes-config/CopyTaskNodeConfig.vue
new file mode 100644
index 0000000..f83f185
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/nodes-config/CopyTaskNodeConfig.vue
@@ -0,0 +1,374 @@
+<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="user">
+        <div>
+          <el-form ref="formRef" :model="configForm" label-position="top" :rules="formRules">
+            <el-form-item label="抄送人设置" prop="candidateStrategy">
+              <el-radio-group
+                v-model="configForm.candidateStrategy"
+                @change="changeCandidateStrategy"
+              >
+                <el-radio
+                  v-for="(dict, index) in copyUserStrategies"
+                  :key="index"
+                  :value="dict.value"
+                  :label="dict.value"
+                >
+                  {{ dict.label }}
+                </el-radio>
+              </el-radio-group>
+            </el-form-item>
+
+            <el-form-item
+              v-if="configForm.candidateStrategy == CandidateStrategy.ROLE"
+              label="指定角色"
+              prop="roleIds"
+            >
+              <el-select v-model="configForm.roleIds" clearable multiple style="width: 100%">
+                <el-option
+                  v-for="item in roleOptions"
+                  :key="item.id"
+                  :label="item.name"
+                  :value="item.id"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item
+              v-if="
+                configForm.candidateStrategy == CandidateStrategy.DEPT_MEMBER ||
+                configForm.candidateStrategy == CandidateStrategy.DEPT_LEADER ||
+                configForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER
+              "
+              label="指定部门"
+              prop="deptIds"
+              span="24"
+            >
+              <el-tree-select
+                ref="treeRef"
+                v-model="configForm.deptIds"
+                :data="deptTreeOptions"
+                :props="defaultProps"
+                empty-text="加载中,请稍后"
+                multiple
+                node-key="id"
+                style="width: 100%"
+                show-checkbox
+              />
+            </el-form-item>
+            <el-form-item
+              v-if="configForm.candidateStrategy == CandidateStrategy.POST"
+              label="指定岗位"
+              prop="postIds"
+              span="24"
+            >
+              <el-select v-model="configForm.postIds" clearable multiple style="width: 100%">
+                <el-option
+                  v-for="item in postOptions"
+                  :key="item.id"
+                  :label="item.name"
+                  :value="item.id!"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item
+              v-if="configForm.candidateStrategy == CandidateStrategy.USER"
+              label="指定用户"
+              prop="userIds"
+              span="24"
+            >
+              <el-select v-model="configForm.userIds" clearable multiple style="width: 100%">
+                <el-option
+                  v-for="item in userOptions"
+                  :key="item.id"
+                  :label="item.nickname"
+                  :value="item.id"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item
+              v-if="configForm.candidateStrategy === CandidateStrategy.USER_GROUP"
+              label="指定用户组"
+              prop="userGroups"
+            >
+              <el-select v-model="configForm.userGroups" clearable multiple style="width: 100%">
+                <el-option
+                  v-for="item in userGroupOptions"
+                  :key="item.id"
+                  :label="item.name"
+                  :value="item.id"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item
+              v-if="configForm.candidateStrategy === CandidateStrategy.FORM_USER"
+              label="表单内用户字段"
+              prop="formUser"
+            >
+              <el-select v-model="configForm.formUser" clearable style="width: 100%">
+                <el-option
+                  v-for="(item, idx) in userFieldOnFormOptions"
+                  :key="idx"
+                  :label="item.title"
+                  :value="item.field"
+                  :disabled ="!item.required"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item
+              v-if="configForm.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER"
+              label="表单内部门字段"
+              prop="formDept"
+            >
+              <el-select v-model="configForm.formDept" clearable style="width: 100%">
+                <el-option
+                  v-for="(item, idx) in deptFieldOnFormOptions"
+                  :key="idx"
+                  :label="item.title"
+                  :value="item.field"
+                  :disabled ="!item.required"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item
+              v-if="
+                configForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
+                configForm.candidateStrategy == CandidateStrategy.START_USER_DEPT_LEADER ||
+                configForm.candidateStrategy ==
+                  CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER ||
+                configForm.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER
+              "
+              :label="deptLevelLabel!"
+              prop="deptLevel"
+              span="24"
+            >
+              <el-select v-model="configForm.deptLevel" clearable>
+                <el-option
+                  v-for="(item, index) in MULTI_LEVEL_DEPT"
+                  :key="index"
+                  :label="item.label"
+                  :value="item.value"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item
+              v-if="configForm.candidateStrategy === CandidateStrategy.EXPRESSION"
+              label="流程表达式"
+              prop="expression"
+            >
+              <el-input
+                type="textarea"
+                v-model="configForm.expression"
+                clearable
+                style="width: 100%"
+              />
+            </el-form-item>
+          </el-form>
+        </div>
+      </el-tab-pane>
+      <el-tab-pane label="表单字段权限" name="fields" v-if="formType === 10">
+        <div class="field-setting-pane">
+          <div class="field-setting-desc">字段权限</div>
+          <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>
+            </div>
+          </div>
+          <div
+            class="field-setting-item"
+            v-for="(item, index) in fieldsPermissionConfig"
+            :key="index"
+          >
+            <div class="field-setting-item-label"> {{ item.title }} </div>
+            <el-radio-group class="field-setting-item-group" v-model="item.permission">
+              <div class="item-radio-wrap">
+                <el-radio
+                  :value="FieldPermissionType.READ"
+                  size="large"
+                  :label="FieldPermissionType.WRITE"
+                  ><span></span
+                ></el-radio>
+              </div>
+              <div class="item-radio-wrap">
+                <el-radio
+                  :value="FieldPermissionType.WRITE"
+                  size="large"
+                  :label="FieldPermissionType.WRITE"
+                  disabled
+                  ><span></span
+                ></el-radio>
+              </div>
+              <div class="item-radio-wrap">
+                <el-radio
+                  :value="FieldPermissionType.NONE"
+                  size="large"
+                  :label="FieldPermissionType.NONE"
+                  ><span></span
+                ></el-radio>
+              </div>
+            </el-radio-group>
+          </div>
+        </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 {
+  SimpleFlowNode,
+  CandidateStrategy,
+  NodeType,
+  CANDIDATE_STRATEGY,
+  FieldPermissionType,
+  MULTI_LEVEL_DEPT
+} from '../consts'
+import {
+  useWatchNode,
+  useDrawer,
+  useNodeName,
+  useFormFieldsPermission,
+  useNodeForm,
+  CopyTaskFormType
+} from '../node'
+import { defaultProps } from '@/utils/tree'
+defineOptions({
+  name: 'CopyTaskNodeConfig'
+})
+const props = defineProps({
+  flowNode: {
+    type: Object as () => SimpleFlowNode,
+    required: true
+  }
+})
+const deptLevelLabel = computed(() => {
+  let label = '部门负责人来源'
+  if (configForm.value.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER) {
+    label = label + '(指定部门向上)'
+  } else {
+    label = label + '(发起人部门向上)'
+  }
+  return label
+})
+// 抽屉配置
+const { settingVisible, closeDrawer, openDrawer } = useDrawer()
+// 当前节点
+const currentNode = useWatchNode(props)
+// 节点名称
+const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.COPY_TASK_NODE)
+// 激活的 Tab 标签页
+const activeTabName = ref('user')
+// 表单字段权限配置
+const { formType, fieldsPermissionConfig, formFieldOptions, getNodeConfigFormFields } =
+  useFormFieldsPermission(FieldPermissionType.READ)
+// 表单内用户字段选项, 必须是必填和用户选择器
+const userFieldOnFormOptions = computed(() => {
+  return formFieldOptions.filter((item) => item.type === 'UserSelect')
+})
+// 表单内部门字段选项, 必须是必填和部门选择器
+const deptFieldOnFormOptions = computed(() => {
+  return formFieldOptions.filter((item) => item.type === 'DeptSelect')
+})
+// 抄送人表单配置
+const formRef = ref() // 表单 Ref
+// 表单校验规则
+const formRules = reactive({
+  candidateStrategy: [{ required: true, message: '抄送人设置不能为空', trigger: 'change' }],
+  userIds: [{ required: true, message: '用户不能为空', trigger: 'change' }],
+  roleIds: [{ required: true, message: '角色不能为空', trigger: 'change' }],
+  deptIds: [{ required: true, message: '部门不能为空', trigger: 'change' }],
+  userGroups: [{ required: true, message: '用户组不能为空', trigger: 'change' }],
+  postIds: [{ required: true, message: '岗位不能为空', trigger: 'change' }],
+  formUser: [{ required: true, message: '表单内用户字段不能为空', trigger: 'change' }],
+  formDept: [{ required: true, message: '表单内部门字段不能为空', trigger: 'change' }],
+  expression: [{ required: true, message: '流程表达式不能为空', trigger: 'blur' }]
+})
+
+const {
+  configForm: tempConfigForm,
+  roleOptions,
+  postOptions,
+  userOptions,
+  userGroupOptions,
+  deptTreeOptions,
+  getShowText,
+  handleCandidateParam,
+  parseCandidateParam
+} = useNodeForm(NodeType.COPY_TASK_NODE)
+const configForm = tempConfigForm as Ref<CopyTaskFormType>
+// 抄送人策略, 去掉发起人自选 和 发起人自己
+const copyUserStrategies = computed(() => {
+  return CANDIDATE_STRATEGY.filter((item) => item.value !== CandidateStrategy.START_USER)
+})
+// 改变抄送人设置策略
+const changeCandidateStrategy = () => {
+  configForm.value.userIds = []
+  configForm.value.deptIds = []
+  configForm.value.roleIds = []
+  configForm.value.postIds = []
+  configForm.value.userGroups = []
+  configForm.value.deptLevel = 1
+  configForm.value.formUser = ''
+}
+// 保存配置
+const saveConfig = async () => {
+  activeTabName.value = 'user'
+  if (!formRef) return false
+  const valid = await formRef.value.validate()
+  if (!valid) return false
+  const showText = getShowText()
+  if (!showText) return false
+  currentNode.value.name = nodeName.value!
+  currentNode.value.candidateParam = handleCandidateParam()
+  currentNode.value.candidateStrategy = configForm.value.candidateStrategy
+  currentNode.value.showText = showText
+  currentNode.value.fieldsPermission = fieldsPermissionConfig.value
+  settingVisible.value = false
+  return true
+}
+// 显示抄送节点配置, 由父组件传过来
+const showCopyTaskNodeConfig = (node: SimpleFlowNode) => {
+  nodeName.value = node.name
+  // 抄送人设置
+  configForm.value.candidateStrategy = node.candidateStrategy!
+  parseCandidateParam(node.candidateStrategy!, node?.candidateParam)
+  // 表单字段权限
+  getNodeConfigFormFields(node.fieldsPermission)
+}
+
+defineExpose({ openDrawer, showCopyTaskNodeConfig }) // 暴露方法给父组件
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/components/SimpleProcessDesignerV2/src/nodes-config/StartUserNodeConfig.vue b/src/components/SimpleProcessDesignerV2/src/nodes-config/StartUserNodeConfig.vue
new file mode 100644
index 0000000..e43a351
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/nodes-config/StartUserNodeConfig.vue
@@ -0,0 +1,135 @@
+<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="user">
+        <div> 待实现 </div>
+      </el-tab-pane>
+      <el-tab-pane label="表单字段权限" name="fields" v-if="formType === 10">
+        <div class="field-setting-pane">
+          <div class="field-setting-desc">字段权限</div>
+          <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>
+            </div>
+          </div>
+          <div
+            class="field-setting-item"
+            v-for="(item, index) in fieldsPermissionConfig"
+            :key="index"
+          >
+            <div class="field-setting-item-label"> {{ item.title }} </div>
+            <el-radio-group class="field-setting-item-group" v-model="item.permission">
+              <div class="item-radio-wrap">
+                <el-radio
+                  :value="FieldPermissionType.READ"
+                  size="large"
+                  :label="FieldPermissionType.READ"
+                  ><span></span
+                ></el-radio>
+              </div>
+              <div class="item-radio-wrap">
+                <el-radio
+                  :value="FieldPermissionType.WRITE"
+                  size="large"
+                  :label="FieldPermissionType.WRITE"
+                  ><span></span
+                ></el-radio>
+              </div>
+              <div class="item-radio-wrap">
+                <el-radio
+                  :value="FieldPermissionType.NONE"
+                  size="large"
+                  :label="FieldPermissionType.NONE"
+                  ><span></span
+                ></el-radio>
+              </div>
+            </el-radio-group>
+          </div>
+        </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 { SimpleFlowNode, NodeType, FieldPermissionType, START_USER_BUTTON_SETTING } from '../consts'
+import { useWatchNode, useDrawer, useNodeName, useFormFieldsPermission } from '../node'
+
+defineOptions({
+  name: 'StartUserNodeConfig'
+})
+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.COPY_TASK_NODE)
+// 激活的 Tab 标签页
+const activeTabName = ref('user')
+// 表单字段权限配置
+const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFieldsPermission(
+  FieldPermissionType.WRITE
+)
+
+// 保存配置
+const saveConfig = async () => {
+  activeTabName.value = 'user'
+  currentNode.value.name = nodeName.value!
+  // TODO 暂时写死。后续可以显示谁有权限可以发起
+  currentNode.value.showText = '已设置'
+  // 设置表单权限
+  currentNode.value.fieldsPermission = fieldsPermissionConfig.value
+  // 设置发起人的按钮权限
+  currentNode.value.buttonsSetting = START_USER_BUTTON_SETTING
+  settingVisible.value = false
+  return true
+}
+// 显示发起人节点配置, 由父组件传过来
+const showStartUserNodeConfig = (node: SimpleFlowNode) => {
+  nodeName.value = node.name
+  // 表单字段权限
+  getNodeConfigFormFields(node.fieldsPermission)
+}
+
+defineExpose({ openDrawer, showStartUserNodeConfig }) // 暴露方法给父组件
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/components/SimpleProcessDesignerV2/src/nodes-config/UserTaskNodeConfig.vue b/src/components/SimpleProcessDesignerV2/src/nodes-config/UserTaskNodeConfig.vue
new file mode 100644
index 0000000..a088b6d
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/nodes-config/UserTaskNodeConfig.vue
@@ -0,0 +1,905 @@
+<template>
+  <el-drawer
+    :append-to-body="true"
+    v-model="settingVisible"
+    :show-close="false"
+    :size="550"
+    :before-close="saveConfig"
+    class="justify-start"
+  >
+    <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>
+    <div class="flex flex-items-center mb-3">
+      <span class="font-size-16px mr-3">审批类型 :</span>
+      <el-radio-group v-model="approveType">
+        <el-radio
+          v-for="(item, index) in APPROVE_TYPE"
+          :key="index"
+          :value="item.value"
+          :label="item.value"
+        >
+          {{ item.label }}
+        </el-radio>
+      </el-radio-group>
+    </div>
+    <el-tabs type="border-card" v-model="activeTabName" v-if="approveType === ApproveType.USER">
+      <el-tab-pane label="审批人" name="user">
+        <div>
+          <el-form ref="formRef" :model="configForm" label-position="top" :rules="formRules">
+            <el-form-item label="审批人设置" prop="candidateStrategy">
+              <el-radio-group
+                v-model="configForm.candidateStrategy"
+                @change="changeCandidateStrategy"
+              >
+                <el-radio
+                  v-for="(dict, index) in CANDIDATE_STRATEGY"
+                  :key="index"
+                  :value="dict.value"
+                  :label="dict.value"
+                >
+                  {{ dict.label }}
+                </el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item
+              v-if="configForm.candidateStrategy == CandidateStrategy.ROLE"
+              label="指定角色"
+              prop="roleIds"
+            >
+              <el-select v-model="configForm.roleIds" clearable multiple style="width: 100%">
+                <el-option
+                  v-for="item in roleOptions"
+                  :key="item.id"
+                  :label="item.name"
+                  :value="item.id"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item
+              v-if="
+                configForm.candidateStrategy == CandidateStrategy.DEPT_MEMBER ||
+                configForm.candidateStrategy == CandidateStrategy.DEPT_LEADER ||
+                configForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER
+              "
+              label="指定部门"
+              prop="deptIds"
+              span="24"
+            >
+              <el-tree-select
+                ref="treeRef"
+                v-model="configForm.deptIds"
+                :data="deptTreeOptions"
+                :props="defaultProps"
+                empty-text="加载中,请稍后"
+                multiple
+                node-key="id"
+                :check-strictly="true"
+                style="width: 100%"
+                show-checkbox
+              />
+            </el-form-item>
+            <el-form-item
+              v-if="configForm.candidateStrategy == CandidateStrategy.POST"
+              label="指定岗位"
+              prop="postIds"
+              span="24"
+            >
+              <el-select v-model="configForm.postIds" clearable multiple style="width: 100%">
+                <el-option
+                  v-for="item in postOptions"
+                  :key="item.id"
+                  :label="item.name"
+                  :value="item.id!"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item
+              v-if="configForm.candidateStrategy == CandidateStrategy.USER"
+              label="指定用户"
+              prop="userIds"
+              span="24"
+            >
+              <el-select v-model="configForm.userIds" clearable multiple style="width: 100%">
+                <el-option
+                  v-for="item in userOptions"
+                  :key="item.id"
+                  :label="item.nickname"
+                  :value="item.id"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item
+              v-if="configForm.candidateStrategy === CandidateStrategy.USER_GROUP"
+              label="指定用户组"
+              prop="userGroups"
+            >
+              <el-select v-model="configForm.userGroups" clearable multiple style="width: 100%">
+                <el-option
+                  v-for="item in userGroupOptions"
+                  :key="item.id"
+                  :label="item.name"
+                  :value="item.id"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item
+              v-if="configForm.candidateStrategy === CandidateStrategy.FORM_USER"
+              label="表单内用户字段"
+              prop="formUser"
+            >
+              <el-select v-model="configForm.formUser" clearable style="width: 100%">
+                <el-option
+                  v-for="(item, idx) in userFieldOnFormOptions"
+                  :key="idx"
+                  :label="item.title"
+                  :value="item.field"
+                  :disabled ="!item.required"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item
+              v-if="configForm.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER"
+              label="表单内部门字段"
+              prop="formDept"
+            >
+              <el-select v-model="configForm.formDept" clearable style="width: 100%">
+                <el-option
+                  v-for="(item, idx) in deptFieldOnFormOptions"
+                  :key="idx"
+                  :label="item.title"
+                  :value="item.field"
+                  :disabled ="!item.required"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item
+              v-if="
+                configForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
+                configForm.candidateStrategy == CandidateStrategy.START_USER_DEPT_LEADER ||
+                configForm.candidateStrategy ==
+                  CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER ||
+                configForm.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER
+              "
+              :label="deptLevelLabel!"
+              prop="deptLevel"
+              span="24"
+            >
+              <el-select v-model="configForm.deptLevel" clearable>
+                <el-option
+                  v-for="(item, index) in MULTI_LEVEL_DEPT"
+                  :key="index"
+                  :label="item.label"
+                  :value="item.value"
+                />
+              </el-select>
+            </el-form-item>
+            <!-- TODO @jason:后续要支持选择已经存好的表达式 -->
+            <el-form-item
+              v-if="configForm.candidateStrategy === CandidateStrategy.EXPRESSION"
+              label="流程表达式"
+              prop="expression"
+            >
+              <el-input
+                type="textarea"
+                v-model="configForm.expression"
+                clearable
+                style="width: 100%"
+              />
+            </el-form-item>
+            <el-form-item label="多人审批方式" prop="approveMethod">
+              <el-radio-group v-model="configForm.approveMethod" @change="approveMethodChanged">
+                <div class="flex-col">
+                  <div
+                    v-for="(item, index) in APPROVE_METHODS"
+                    :key="index"
+                    class="flex items-center"
+                  >
+                    <el-radio :value="item.value" :label="item.value">
+                      {{ item.label }}
+                    </el-radio>
+                    <el-form-item prop="approveRatio">
+                      <el-input-number
+                        v-model="configForm.approveRatio"
+                        :min="10"
+                        :max="100"
+                        :step="10"
+                        size="small"
+                        v-if="
+                          item.value === ApproveMethodType.APPROVE_BY_RATIO &&
+                          configForm.approveMethod === ApproveMethodType.APPROVE_BY_RATIO
+                        "
+                      />
+                    </el-form-item>
+                  </div>
+                </div>
+              </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>
+                </div>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item
+              v-if="configForm.rejectHandlerType == RejectHandlerType.RETURN_USER_TASK"
+              label="驳回节点"
+              prop="returnNodeId"
+            >
+              <el-select 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-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
+                v-model="timeUnit"
+                class="mr-2"
+                :style="{ width: '100px' }"
+                @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-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-divider content-position="left">审批人为空时</el-divider>
+            <el-form-item prop="assignEmptyHandlerType">
+              <el-radio-group v-model="configForm.assignEmptyHandlerType">
+                <div class="flex-col">
+                  <div v-for="(item, index) in ASSIGN_EMPTY_HANDLER_TYPES" :key="index">
+                    <el-radio :key="item.value" :value="item.value" :label="item.label" />
+                  </div>
+                </div>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item
+              v-if="configForm.assignEmptyHandlerType == AssignEmptyHandlerType.ASSIGN_USER"
+              label="指定用户"
+              prop="assignEmptyHandlerUserIds"
+              span="24"
+            >
+              <el-select
+                v-model="configForm.assignEmptyHandlerUserIds"
+                clearable
+                multiple
+                style="width: 100%"
+              >
+                <el-option
+                  v-for="item in userOptions"
+                  :key="item.id"
+                  :label="item.nickname"
+                  :value="item.id"
+                />
+              </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>
+                </div>
+              </el-radio-group>
+            </el-form-item>
+          </el-form>
+        </div>
+      </el-tab-pane>
+      <el-tab-pane label="操作按钮设置" name="buttons">
+        <div class="button-setting-pane">
+          <div class="button-setting-desc">操作按钮</div>
+          <div class="button-setting-title">
+            <div class="button-title-label">操作按钮</div>
+            <div class="pl-4 button-title-label">显示名称</div>
+            <div class="button-title-label">启用</div>
+          </div>
+          <div class="button-setting-item" v-for="(item, index) in buttonsSetting" :key="index">
+            <div class="button-setting-item-label"> {{ OPERATION_BUTTON_NAME.get(item.id) }} </div>
+            <div class="button-setting-item-label">
+              <input
+                type="text"
+                class="editable-title-input"
+                @blur="btnDisplayNameBlurEvent(index)"
+                v-mountedFocus
+                v-model="item.displayName"
+                :placeholder="item.displayName"
+                v-if="btnDisplayNameEdit[index]"
+              />
+              <el-button v-else text @click="changeBtnDisplayName(index)"
+                >{{ item.displayName }} &nbsp;<Icon icon="ep:edit"
+              /></el-button>
+            </div>
+            <div class="button-setting-item-label">
+              <el-switch v-model="item.enable" />
+            </div>
+          </div>
+        </div>
+      </el-tab-pane>
+      <el-tab-pane label="表单字段权限" name="fields" v-if="formType === 10">
+        <div class="field-setting-pane">
+          <div class="field-setting-desc">字段权限</div>
+          <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>
+            </div>
+          </div>
+          <div
+            class="field-setting-item"
+            v-for="(item, index) in fieldsPermissionConfig"
+            :key="index"
+          >
+            <div class="field-setting-item-label"> {{ item.title }} </div>
+            <el-radio-group class="field-setting-item-group" v-model="item.permission">
+              <div class="item-radio-wrap">
+                <el-radio
+                  :value="FieldPermissionType.READ"
+                  size="large"
+                  :label="FieldPermissionType.READ"
+                  ><span></span
+                ></el-radio>
+              </div>
+              <div class="item-radio-wrap">
+                <el-radio
+                  :value="FieldPermissionType.WRITE"
+                  size="large"
+                  :label="FieldPermissionType.WRITE"
+                  ><span></span
+                ></el-radio>
+              </div>
+              <div class="item-radio-wrap">
+                <el-radio
+                  :value="FieldPermissionType.NONE"
+                  size="large"
+                  :label="FieldPermissionType.NONE"
+                  ><span></span
+                ></el-radio>
+              </div>
+            </el-radio-group>
+          </div>
+        </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 {
+  SimpleFlowNode,
+  APPROVE_TYPE,
+  ApproveType,
+  APPROVE_METHODS,
+  CandidateStrategy,
+  NodeType,
+  ApproveMethodType,
+  TimeUnitType,
+  RejectHandlerType,
+  TIMEOUT_HANDLER_TYPES,
+  TIME_UNIT_TYPES,
+  REJECT_HANDLER_TYPES,
+  DEFAULT_BUTTON_SETTING,
+  OPERATION_BUTTON_NAME,
+  ButtonSetting,
+  MULTI_LEVEL_DEPT,
+  CANDIDATE_STRATEGY,
+  ASSIGN_START_USER_HANDLER_TYPES,
+  TimeoutHandlerType,
+  ASSIGN_EMPTY_HANDLER_TYPES,
+  AssignEmptyHandlerType,
+  FieldPermissionType
+} from '../consts'
+
+import {
+  useWatchNode,
+  useNodeName,
+  useFormFieldsPermission,
+  useNodeForm,
+  UserTaskFormType,
+  useDrawer
+} from '../node'
+import { defaultProps } from '@/utils/tree'
+import { cloneDeep } from 'lodash-es'
+import { convertTimeUnit, getApproveTypeText } from '../utils'
+defineOptions({
+  name: 'UserTaskNodeConfig'
+})
+const props = defineProps({
+  flowNode: {
+    type: Object as () => SimpleFlowNode,
+    required: true
+  }
+})
+const emits = defineEmits<{
+  'find:returnTaskNodes': [nodeList: SimpleFlowNode[]]
+}>()
+const deptLevelLabel = computed(() => {
+  let label = '部门负责人来源'
+  if (configForm.value.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER) {
+    label = label + '(指定部门向上)'
+  } else if (configForm.value.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER) {
+    label = label + '(表单内部门向上)'
+  } else {
+    label = label + '(发起人部门向上)'
+  }
+  return label
+})
+// 监控节点的变化
+const currentNode = useWatchNode(props)
+// 抽屉配置
+const { settingVisible, closeDrawer, openDrawer } = useDrawer()
+// 节点名称配置
+const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.USER_TASK_NODE)
+// 激活的 Tab 标签页
+const activeTabName = ref('user')
+// 表单字段权限设置
+const { formType, fieldsPermissionConfig, formFieldOptions, getNodeConfigFormFields } =
+  useFormFieldsPermission(FieldPermissionType.READ)
+// 表单内用户字段选项, 必须是必填和用户选择器
+const userFieldOnFormOptions = computed(() => {
+  return formFieldOptions.filter((item) => item.type === 'UserSelect')
+})
+// 表单内部门字段选项, 必须是必填和部门选择器
+const deptFieldOnFormOptions = computed(() => {
+  return formFieldOptions.filter((item) => item.type === 'DeptSelect')
+})
+// 操作按钮设置
+const { buttonsSetting, btnDisplayNameEdit, changeBtnDisplayName, btnDisplayNameBlurEvent } =
+  useButtonsSetting()
+const approveType = ref(ApproveType.USER)
+// 审批人表单设置
+const formRef = ref() // 表单 Ref
+// 表单校验规则
+const formRules = reactive({
+  candidateStrategy: [{ required: true, message: '审批人设置不能为空', trigger: 'change' }],
+  userIds: [{ required: true, message: '用户不能为空', trigger: 'change' }],
+  roleIds: [{ required: true, message: '角色不能为空', trigger: 'change' }],
+  deptIds: [{ required: true, message: '部门不能为空', trigger: 'change' }],
+  userGroups: [{ required: true, message: '用户组不能为空', trigger: 'change' }],
+  formUser: [{ required: true, message: '表单内用户字段不能为空', trigger: 'change' }],
+  formDept: [{ required: true, message: '表单内部门字段不能为空', trigger: 'change' }],
+  postIds: [{ required: true, message: '岗位不能为空', trigger: 'change' }],
+  expression: [{ required: true, message: '流程表达式不能为空', trigger: 'blur' }],
+  approveMethod: [{ required: true, message: '多人审批方式不能为空', trigger: 'change' }],
+  approveRatio: [{ required: true, message: '通过比例不能为空', trigger: 'blur' }],
+  returnNodeId: [{ required: true, message: '驳回节点不能为空', trigger: 'change' }],
+  timeoutHandlerEnable: [{ required: true }],
+  timeoutHandlerType: [{ required: true }],
+  timeDuration: [{ required: true, message: '超时时间不能为空', trigger: 'blur' }],
+  maxRemindCount: [{ required: true, message: '提醒次数不能为空', trigger: 'blur' }],
+  assignEmptyHandlerType: [{ required: true }],
+  assignEmptyHandlerUserIds: [{ required: true, message: '用户不能为空', trigger: 'change' }],
+  assignStartUserHandlerType: [{ required: true }]
+})
+
+const {
+  configForm: tempConfigForm,
+  roleOptions,
+  postOptions,
+  userOptions,
+  userGroupOptions,
+  deptTreeOptions,
+  handleCandidateParam,
+  parseCandidateParam,
+  getShowText
+} = useNodeForm(NodeType.USER_TASK_NODE)
+const configForm = tempConfigForm as Ref<UserTaskFormType>
+
+// 改变审批人设置策略
+const changeCandidateStrategy = () => {
+  configForm.value.userIds = []
+  configForm.value.deptIds = []
+  configForm.value.roleIds = []
+  configForm.value.postIds = []
+  configForm.value.userGroups = []
+  configForm.value.deptLevel = 1
+  configForm.value.formUser = ''
+  configForm.value.formDept = ''
+  configForm.value.approveMethod = ApproveMethodType.SEQUENTIAL_APPROVE
+}
+
+// 审批方式改变
+const approveMethodChanged = () => {
+  configForm.value.rejectHandlerType = RejectHandlerType.FINISH_PROCESS
+  if (configForm.value.approveMethod === ApproveMethodType.APPROVE_BY_RATIO) {
+    configForm.value.approveRatio = 100
+  }
+  formRef.value.clearValidate('approveRatio')
+}
+// 审批拒绝 可退回的节点
+const returnTaskList = ref<SimpleFlowNode[]>([])
+// 审批人超时未处理设置
+const {
+  timeoutHandlerChange,
+  cTimeoutType,
+  timeoutHandlerTypeChanged,
+  timeUnit,
+  timeUnitChange,
+  isoTimeDuration,
+  cTimeoutMaxRemindCount
+} = useTimeoutHandler()
+
+// 保存配置
+const saveConfig = async () => {
+  activeTabName.value = 'user'
+  // 设置审批节点名称
+  currentNode.value.name = nodeName.value!
+  // 设置审批类型
+  currentNode.value.approveType = approveType.value
+  // 如果不是人工审批。返回
+  if (approveType.value !== ApproveType.USER) {
+    currentNode.value.showText = getApproveTypeText(approveType.value)
+    settingVisible.value = false
+    return true
+  }
+
+  if (!formRef) return false
+  const valid = await formRef.value.validate()
+  if (!valid) return false
+  const showText = getShowText()
+  if (!showText) return false
+
+  currentNode.value.candidateStrategy = configForm.value.candidateStrategy
+  // 处理 candidateParam 参数
+  currentNode.value.candidateParam = handleCandidateParam()
+  // 设置审批方式
+  currentNode.value.approveMethod = configForm.value.approveMethod
+  if (configForm.value.approveMethod === ApproveMethodType.APPROVE_BY_RATIO) {
+    currentNode.value.approveRatio = configForm.value.approveRatio
+  }
+  // 设置拒绝处理
+  currentNode.value.rejectHandler = {
+    type: configForm.value.rejectHandlerType!,
+    returnNodeId: configForm.value.returnNodeId
+  }
+  // 设置超时处理
+  currentNode.value.timeoutHandler = {
+    enable: configForm.value.timeoutHandlerEnable!,
+    type: cTimeoutType.value,
+    timeDuration: isoTimeDuration.value,
+    maxRemindCount: cTimeoutMaxRemindCount.value
+  }
+  // 设置审批人为空时
+  currentNode.value.assignEmptyHandler = {
+    type: configForm.value.assignEmptyHandlerType!,
+    userIds:
+      configForm.value.assignEmptyHandlerType === AssignEmptyHandlerType.ASSIGN_USER
+        ? configForm.value.assignEmptyHandlerUserIds
+        : undefined
+  }
+  // 设置审批人与发起人相同时
+  currentNode.value.assignStartUserHandlerType = configForm.value.assignStartUserHandlerType
+  // 设置表单权限
+  currentNode.value.fieldsPermission = fieldsPermissionConfig.value
+  // 设置按钮权限
+  currentNode.value.buttonsSetting = buttonsSetting.value
+
+  currentNode.value.showText = showText
+  settingVisible.value = false
+  return true
+}
+
+// 显示审批节点配置, 由父组件传过来
+const showUserTaskNodeConfig = (node: SimpleFlowNode) => {
+  nodeName.value = node.name
+  // 1 审批类型
+  approveType.value = node.approveType ? node.approveType : ApproveType.USER
+  // 如果审批类型不是人工审批返回
+  if (approveType.value !== ApproveType.USER) {
+    return
+  }
+
+  //2.1 审批人设置
+  configForm.value.candidateStrategy = node.candidateStrategy!
+  // 解析候选人参数
+  parseCandidateParam(node.candidateStrategy!, node?.candidateParam)
+  // 2.2 设置审批方式
+  configForm.value.approveMethod = node.approveMethod!
+  if (node.approveMethod == ApproveMethodType.APPROVE_BY_RATIO) {
+    configForm.value.approveRatio = node.approveRatio!
+  }
+  // 2.3 设置审批拒绝处理
+  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
+  if (node.timeoutHandler?.enable && node.timeoutHandler?.timeDuration) {
+    const strTimeDuration = node.timeoutHandler.timeDuration
+    let parseTime = strTimeDuration.slice(2, strTimeDuration.length - 1)
+    let parseTimeUnit = strTimeDuration.slice(strTimeDuration.length - 1)
+    configForm.value.timeDuration = parseInt(parseTime)
+    timeUnit.value = convertTimeUnit(parseTimeUnit)
+  }
+  configForm.value.timeoutHandlerType = node.timeoutHandler?.type
+  configForm.value.maxRemindCount = node.timeoutHandler?.maxRemindCount
+  // 2.5 设置审批人为空时
+  configForm.value.assignEmptyHandlerType = node.assignEmptyHandler?.type
+  configForm.value.assignEmptyHandlerUserIds = node.assignEmptyHandler?.userIds
+  // 2.6 设置用户任务的审批人与发起人相同时
+  configForm.value.assignStartUserHandlerType = node.assignStartUserHandlerType
+  // 3. 操作按钮设置
+  buttonsSetting.value = cloneDeep(node.buttonsSetting) || DEFAULT_BUTTON_SETTING
+  // 4. 表单字段权限配置
+  getNodeConfigFormFields(node.fieldsPermission)
+}
+
+defineExpose({ openDrawer, showUserTaskNodeConfig }) // 暴露方法给父组件
+
+/**
+ * @description 操作按钮设置
+ */
+function useButtonsSetting() {
+  const buttonsSetting = ref<ButtonSetting[]>()
+  // 操作按钮显示名称可编辑
+  const btnDisplayNameEdit = ref<boolean[]>([])
+  const changeBtnDisplayName = (index: number) => {
+    btnDisplayNameEdit.value[index] = true
+  }
+  const btnDisplayNameBlurEvent = (index: number) => {
+    btnDisplayNameEdit.value[index] = false
+    const buttonItem = buttonsSetting.value![index]
+    buttonItem.displayName = buttonItem.displayName || OPERATION_BUTTON_NAME.get(buttonItem.id)!
+  }
+  return {
+    buttonsSetting,
+    btnDisplayNameEdit,
+    changeBtnDisplayName,
+    btnDisplayNameBlurEvent
+  }
+}
+
+/**
+ * @description 审批人超时未处理配置
+ */
+function useTimeoutHandler() {
+  // 时间单位
+  const timeUnit = ref(TimeUnitType.HOUR)
+
+  // 超时开关改变
+  const timeoutHandlerChange = () => {
+    if (configForm.value.timeoutHandlerEnable) {
+      timeUnit.value = 2
+      configForm.value.timeDuration = 6
+      configForm.value.timeoutHandlerType = 1
+      configForm.value.maxRemindCount = 1
+    }
+  }
+  // 超时执行的动作
+  const cTimeoutType = computed(() => {
+    if (!configForm.value.timeoutHandlerEnable) {
+      return undefined
+    }
+    return configForm.value.timeoutHandlerType
+  })
+
+  // 超时处理动作改变
+  const timeoutHandlerTypeChanged = () => {
+    if (configForm.value.timeoutHandlerType === TimeoutHandlerType.REMINDER) {
+      configForm.value.maxRemindCount = 1 // 超时提醒次数,默认为1
+    }
+  }
+
+  // 时间单位改变
+  const timeUnitChange = () => {
+    // 分钟,默认是 60 分钟
+    if (timeUnit.value === TimeUnitType.MINUTE) {
+      configForm.value.timeDuration = 60
+    }
+    // 小时,默认是 6 个小时
+    if (timeUnit.value === TimeUnitType.HOUR) {
+      configForm.value.timeDuration = 6
+    }
+    // 天, 默认 1天
+    if (timeUnit.value === TimeUnitType.DAY) {
+      configForm.value.timeDuration = 1
+    }
+  }
+  // 超时时间的 ISO 表示
+  const isoTimeDuration = computed(() => {
+    if (!configForm.value.timeoutHandlerEnable) {
+      return undefined
+    }
+    let strTimeDuration = 'PT'
+    if (timeUnit.value === TimeUnitType.MINUTE) {
+      strTimeDuration += configForm.value.timeDuration + 'M'
+    }
+    if (timeUnit.value === TimeUnitType.HOUR) {
+      strTimeDuration += configForm.value.timeDuration + 'H'
+    }
+    if (timeUnit.value === TimeUnitType.DAY) {
+      strTimeDuration += configForm.value.timeDuration + 'D'
+    }
+    return strTimeDuration
+  })
+
+  // 超时最大提醒次数
+  const cTimeoutMaxRemindCount = computed(() => {
+    if (!configForm.value.timeoutHandlerEnable) {
+      return undefined
+    }
+    if (configForm.value.timeoutHandlerType !== TimeoutHandlerType.REMINDER) {
+      return undefined
+    }
+    return configForm.value.maxRemindCount
+  })
+
+  return {
+    timeoutHandlerChange,
+    cTimeoutType,
+    timeoutHandlerTypeChanged,
+    timeUnit,
+    timeUnitChange,
+    isoTimeDuration,
+    cTimeoutMaxRemindCount
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.button-setting-pane {
+  display: flex;
+  flex-direction: column;
+  font-size: 14px;
+
+  .button-setting-desc {
+    padding-right: 8px;
+    margin-bottom: 16px;
+    font-size: 16px;
+    font-weight: 700;
+  }
+
+  .button-setting-title {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    height: 45px;
+    padding-left: 12px;
+    background-color: #f8fafc0a;
+    border: 1px solid #1f38581a;
+
+    & > :first-child {
+      width: 100px !important;
+      text-align: left !important;
+    }
+
+    & > :last-child {
+      text-align: center !important;
+    }
+
+    .button-title-label {
+      width: 150px;
+      font-size: 13px;
+      font-weight: 700;
+      color: #000;
+      text-align: left;
+    }
+  }
+
+  .button-setting-item {
+    align-items: center;
+    display: flex;
+    justify-content: space-between;
+    height: 38px;
+    padding-left: 12px;
+    border: 1px solid #1f38581a;
+    border-top: 0;
+
+    & > :first-child {
+      width: 100px !important;
+    }
+
+    & > :last-child {
+      text-align: center !important;
+    }
+
+    .button-setting-item-label {
+      width: 150px;
+      overflow: hidden;
+      text-align: left;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+
+    .editable-title-input {
+      height: 24px;
+      max-width: 130px;
+      margin-left: 4px;
+      line-height: 24px;
+      border: 1px solid #d9d9d9;
+      border-radius: 4px;
+      transition: all 0.3s;
+
+      &:focus {
+        border-color: #40a9ff;
+        outline: 0;
+        box-shadow: 0 0 0 2px rgb(24 144 255 / 20%);
+      }
+    }
+  }
+}
+</style>
diff --git a/src/components/SimpleProcessDesignerV2/src/nodes/CopyTaskNode.vue b/src/components/SimpleProcessDesignerV2/src/nodes/CopyTaskNode.vue
new file mode 100644
index 0000000..8b97ee5
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/nodes/CopyTaskNode.vue
@@ -0,0 +1,97 @@
+<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 copy-task"><span class="iconfont icon-copy"></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.COPY_TASK_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>
+    <CopyTaskNodeConfig
+      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 CopyTaskNodeConfig from '../nodes-config/CopyTaskNodeConfig.vue'
+defineOptions({
+  name: 'CopyTaskNode'
+})
+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.COPY_TASK_NODE)
+
+const nodeSetting = ref()
+// 打开节点配置
+const openNodeConfig = () => {
+  if (readonly) {
+    return
+  }
+  nodeSetting.value.showCopyTaskNodeConfig(currentNode.value)
+  nodeSetting.value.openDrawer()
+}
+
+// 删除节点。更新当前节点为孩子节点
+const deleteNode = () => {
+  emits('update:flowNode', currentNode.value.childNode)
+}
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/components/SimpleProcessDesignerV2/src/nodes/EndEventNode.vue b/src/components/SimpleProcessDesignerV2/src/nodes/EndEventNode.vue
new file mode 100644
index 0000000..63aa24e
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/nodes/EndEventNode.vue
@@ -0,0 +1,102 @@
+<template>
+  <div class="end-node-wrapper">
+    <div class="end-node-box cursor-pointer" :class="`${useTaskStatusClass(currentNode?.activityStatus)}`" @click="nodeClick">
+      <span class="node-fixed-name" title="结束">结束</span>
+    </div>
+  </div>
+  <el-dialog title="审批信息" v-model="dialogVisible" width="1000px" append-to-body>
+      <el-row>
+        <el-table
+          :data="processInstanceInfos"
+          size="small"
+          border
+          header-cell-class-name="table-header-gray"
+        >
+          <el-table-column
+            label="序号"
+            header-align="center"
+            align="center"
+            type="index"
+            width="50"
+          />
+          <el-table-column
+            label="发起人"
+            prop="assigneeUser.nickname"
+            min-width="100"
+            align="center"
+          />
+          <el-table-column label="部门" min-width="100" align="center">
+            <template #default="scope">
+              {{ scope.row.assigneeUser?.deptName || scope.row.ownerUser?.deptName }}
+            </template>
+          </el-table-column>
+          <el-table-column
+            :formatter="dateFormatter"
+            align="center"
+            label="开始时间"
+            prop="createTime"
+            min-width="140"
+          />
+          <el-table-column
+            :formatter="dateFormatter"
+            align="center"
+            label="结束时间"
+            prop="endTime"
+            min-width="140"
+          />
+          <el-table-column align="center" label="审批状态" prop="status" min-width="90">
+            <template #default="scope">
+              <dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
+            </template>
+          </el-table-column>
+         
+          <el-table-column align="center" label="耗时" prop="durationInMillis" width="100">
+            <template #default="scope">
+              {{ formatPast2(scope.row.durationInMillis) }}
+            </template>
+          </el-table-column>
+        </el-table>
+      </el-row>
+    </el-dialog>
+</template>
+<script setup lang="ts">
+import { SimpleFlowNode } from '../consts'
+import { useWatchNode, useTaskStatusClass } from '../node'
+import { dateFormatter, formatPast2 } from '@/utils/formatTime'
+import { DICT_TYPE } from '@/utils/dict'
+defineOptions({
+  name: 'EndEventNode'
+})
+const props = defineProps({
+  flowNode: {
+    type: Object as () => SimpleFlowNode,
+    default: () => null
+  }
+})
+// 监控节点变化
+const currentNode = useWatchNode(props)
+// 是否只读
+const readonly = inject<Boolean>('readonly')
+const processInstance = inject<Ref<any>>('processInstance')
+// 审批信息的弹窗显示,用于只读模式
+const dialogVisible = ref(false) // 弹窗可见性
+const processInstanceInfos = ref<any[]>([]) // 流程的审批信息
+
+const nodeClick = () => {
+  if (readonly) { 
+    if(processInstance && processInstance.value){
+      processInstanceInfos.value = [
+      {
+        assigneeUser: processInstance.value.startUser,
+        createTime: processInstance.value.startTime,
+        endTime: processInstance.value.endTime,
+        status: processInstance.value.status,
+        durationInMillis: processInstance.value.durationInMillis
+      }
+    ]
+      dialogVisible.value = true
+    }
+  }
+}
+</script>
+<style lang="scss" scoped></style>
diff --git a/src/components/SimpleProcessDesignerV2/src/nodes/ExclusiveNode.vue b/src/components/SimpleProcessDesignerV2/src/nodes/ExclusiveNode.vue
new file mode 100644
index 0000000..adeae77
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/nodes/ExclusiveNode.vue
@@ -0,0 +1,229 @@
+<template>
+  <div class="branch-node-wrapper">
+    <div class="branch-node-container">
+      <div
+        v-if="readonly"
+        class="branch-node-readonly"
+        :class="`${useTaskStatusClass(currentNode?.activityStatus)}`"
+      >
+        <span class="iconfont icon-exclusive icon-size condition"></span>
+      </div>
+      <el-button v-else class="branch-node-add" color="#67c23a" @click="addCondition" plain
+        >添加条件</el-button
+      >
+
+      <div
+        class="branch-node-item"
+        v-for="(item, index) in currentNode.conditionNodes"
+        :key="index"
+      >
+        <template v-if="index == 0">
+          <div class="branch-line-first-top"> </div>
+          <div class="branch-line-first-bottom"></div>
+        </template>
+        <template v-if="index + 1 == currentNode.conditionNodes?.length">
+          <div class="branch-line-last-top"></div>
+          <div class="branch-line-last-bottom"></div>
+        </template>
+        <div class="node-wrapper">
+          <div class="node-container">
+            <div
+              class="node-box"
+              :class="[
+                { 'node-config-error': !item.showText },
+                `${useTaskStatusClass(item.activityStatus)}`
+              ]"
+            >
+              <div class="branch-node-title-container">
+                <div v-if="!readonly && showInputs[index]">
+                  <input
+                    type="text"
+                    class="input-max-width editable-title-input"
+                    @blur="blurEvent(index)"
+                    v-mountedFocus
+                    v-model="item.name"
+                  />
+                </div>
+                <div v-else class="branch-title" @click="clickEvent(index)"> {{ item.name }} </div>
+                <div class="branch-priority"> 优先级{{ index + 1 }} </div>
+              </div>
+              <div class="branch-node-content" @click="conditionNodeConfig(item.id)">
+                <div class="branch-node-text" :title="item.showText" v-if="item.showText">
+                  {{ item.showText }}
+                </div>
+                <div class="branch-node-text" v-else>
+                  {{ NODE_DEFAULT_TEXT.get(NodeType.CONDITION_NODE) }}
+                </div>
+              </div>
+              <div
+                class="node-toolbar"
+                v-if="!readonly && index + 1 !== currentNode.conditionNodes?.length"
+              >
+                <div class="toolbar-icon">
+                  <Icon
+                    color="#0089ff"
+                    icon="ep:circle-close-filled"
+                    :size="18"
+                    @click="deleteCondition(index)"
+                  />
+                </div>
+              </div>
+              <div
+                class="branch-node-move move-node-left"
+                v-if="index != 0 && index + 1 !== currentNode.conditionNodes?.length"
+                @click="moveNode(index, -1)"
+              >
+                <Icon icon="ep:arrow-left" />
+              </div>
+
+              <div
+                class="branch-node-move move-node-right"
+                v-if="currentNode.conditionNodes && index < currentNode.conditionNodes.length - 2"
+                @click="moveNode(index, 1)"
+              >
+                <Icon icon="ep:arrow-right" />
+              </div>
+            </div>
+            <NodeHandler v-model:child-node="item.childNode" :current-node="item" />
+          </div>
+        </div>
+        <ConditionNodeConfig :node-index="index" :condition-node="item" :ref="item.id" />
+        <!-- 递归显示子节点  -->
+        <ProcessNodeTree
+          v-if="item && item.childNode"
+          :parent-node="item"
+          v-model:flow-node="item.childNode"
+          @find:recursive-find-parent-node="recursiveFindParentNode"
+        />
+      </div>
+    </div>
+    <NodeHandler
+      v-if="currentNode"
+      v-model:child-node="currentNode.childNode"
+      :current-node="currentNode"
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+import NodeHandler from '../NodeHandler.vue'
+import ProcessNodeTree from '../ProcessNodeTree.vue'
+import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
+import { getDefaultConditionNodeName } from '../utils'
+import { useTaskStatusClass } from '../node'
+import { generateUUID } from '@/utils'
+import ConditionNodeConfig from '../nodes-config/ConditionNodeConfig.vue'
+const { proxy } = getCurrentInstance() as any
+defineOptions({
+  name: 'ExclusiveNode'
+})
+const props = defineProps({
+  flowNode: {
+    type: Object as () => SimpleFlowNode,
+    required: true
+  }
+})
+// 定义事件,更新父组件
+const emits = defineEmits<{
+  'update:modelValue': [node: SimpleFlowNode | undefined]
+  'find:parentNode': [nodeList: SimpleFlowNode[], nodeType: number]
+  'find:recursiveFindParentNode': [
+    nodeList: SimpleFlowNode[],
+    curentNode: SimpleFlowNode,
+    nodeType: number
+  ]
+}>()
+// 是否只读
+const readonly = inject<Boolean>('readonly')
+const currentNode = ref<SimpleFlowNode>(props.flowNode)
+watch(
+  () => props.flowNode,
+  (newValue) => {
+    currentNode.value = newValue
+  }
+)
+
+const showInputs = ref<boolean[]>([])
+// 失去焦点
+const blurEvent = (index: number) => {
+  showInputs.value[index] = false
+  const conditionNode = currentNode.value.conditionNodes?.at(index) as SimpleFlowNode
+  conditionNode.name =
+    conditionNode.name || getDefaultConditionNodeName(index, conditionNode.defaultFlow)
+}
+
+// 点击条件名称
+const clickEvent = (index: number) => {
+  showInputs.value[index] = true
+}
+
+const conditionNodeConfig = (nodeId: string) => {
+  if (readonly) {
+    return
+  }
+  const conditionNode = proxy.$refs[nodeId][0]
+  conditionNode.open()
+}
+
+// 新增条件
+const addCondition = () => {
+  const conditionNodes = currentNode.value.conditionNodes
+  if (conditionNodes) {
+    const len = conditionNodes.length
+    let lastIndex = len - 1
+    const conditionData: SimpleFlowNode = {
+      id: 'Flow_' + generateUUID(),
+      name: '条件' + len,
+      showText: '',
+      type: NodeType.CONDITION_NODE,
+      childNode: undefined,
+      conditionNodes: [],
+      conditionType: 1,
+      defaultFlow: false
+    }
+    conditionNodes.splice(lastIndex, 0, conditionData)
+  }
+}
+
+// 删除条件
+const deleteCondition = (index: number) => {
+  const conditionNodes = currentNode.value.conditionNodes
+  if (conditionNodes) {
+    conditionNodes.splice(index, 1)
+    if (conditionNodes.length == 1) {
+      const childNode = currentNode.value.childNode
+      // 更新此节点为后续孩子节点
+      emits('update:modelValue', childNode)
+    }
+  }
+}
+
+// 移动节点
+const moveNode = (index: number, to: number) => {
+  // -1 :向左  1: 向右
+  if (currentNode.value.conditionNodes) {
+    currentNode.value.conditionNodes[index] = currentNode.value.conditionNodes.splice(
+      index + to,
+      1,
+      currentNode.value.conditionNodes[index]
+    )[0]
+  }
+}
+// 递归从父节点中查询匹配的节点
+const recursiveFindParentNode = (
+  nodeList: SimpleFlowNode[],
+  node: SimpleFlowNode,
+  nodeType: number
+) => {
+  if (!node || node.type === NodeType.START_USER_NODE) {
+    return
+  }
+  if (node.type === nodeType) {
+    nodeList.push(node)
+  }
+  // 条件节点 (NodeType.CONDITION_NODE) 比较特殊。需要调用其父节点条件分支节点(NodeType.EXCLUSIVE_NODE) 继续查找
+  emits('find:parentNode', nodeList, nodeType)
+}
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/components/SimpleProcessDesignerV2/src/nodes/InclusiveNode.vue b/src/components/SimpleProcessDesignerV2/src/nodes/InclusiveNode.vue
new file mode 100644
index 0000000..f1445d8
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/nodes/InclusiveNode.vue
@@ -0,0 +1,233 @@
+<template>
+  <div class="branch-node-wrapper">
+    <div class="branch-node-container">
+      <div
+        v-if="readonly"
+        class="branch-node-readonly"
+        :class="`${useTaskStatusClass(currentNode?.activityStatus)}`"
+      >
+        <span class="iconfont icon-inclusive icon-size inclusive"></span>
+      </div>
+      <el-button v-else class="branch-node-add" color="#345da2" @click="addCondition" plain
+        >添加条件</el-button
+      >
+      <div
+        class="branch-node-item"
+        v-for="(item, index) in currentNode.conditionNodes"
+        :key="index"
+      >
+        <template v-if="index == 0">
+          <div class="branch-line-first-top"> </div>
+          <div class="branch-line-first-bottom"></div>
+        </template>
+        <template v-if="index + 1 == currentNode.conditionNodes?.length">
+          <div class="branch-line-last-top"></div>
+          <div class="branch-line-last-bottom"></div>
+        </template>
+        <div class="node-wrapper">
+          <div class="node-container">
+            <div
+              class="node-box"
+              :class="[
+                { 'node-config-error': !item.showText },
+                `${useTaskStatusClass(item.activityStatus)}`
+              ]"
+            >
+              <div class="branch-node-title-container">
+                <div v-if="showInputs[index]">
+                  <input
+                    type="text"
+                    class="editable-title-input"
+                    @blur="blurEvent(index)"
+                    v-mountedFocus
+                    v-model="item.name"
+                  />
+                </div>
+                <div v-else class="branch-title" @click="clickEvent(index)"> {{ item.name }} </div>
+              </div>
+              <div class="branch-node-content" @click="conditionNodeConfig(item.id)">
+                <div class="branch-node-text" :title="item.showText" v-if="item.showText">
+                  {{ item.showText }}
+                </div>
+                <div class="branch-node-text" v-else>
+                  {{ NODE_DEFAULT_TEXT.get(NodeType.CONDITION_NODE) }}
+                </div>
+              </div>
+              <div
+                class="node-toolbar"
+                v-if="!readonly && index + 1 !== currentNode.conditionNodes?.length"
+              >
+                <div class="toolbar-icon">
+                  <Icon
+                    color="#0089ff"
+                    icon="ep:circle-close-filled"
+                    :size="18"
+                    @click="deleteCondition(index)"
+                  />
+                </div>
+              </div>
+              <div
+                class="branch-node-move move-node-left"
+                v-if="!readonly && index != 0 && index + 1 !== currentNode.conditionNodes?.length"
+                @click="moveNode(index, -1)"
+              >
+                <Icon icon="ep:arrow-left" />
+              </div>
+
+              <div
+                class="branch-node-move move-node-right"
+                v-if="
+                  !readonly &&
+                  currentNode.conditionNodes &&
+                  index < currentNode.conditionNodes.length - 2
+                "
+                @click="moveNode(index, 1)"
+              >
+                <Icon icon="ep:arrow-right" />
+              </div>
+            </div>
+            <NodeHandler v-model:child-node="item.childNode" :current-node="item" />
+          </div>
+        </div>
+        <ConditionNodeConfig :node-index="index" :condition-node="item" :ref="item.id" />
+        <!-- 递归显示子节点  -->
+        <ProcessNodeTree
+          v-if="item && item.childNode"
+          :parent-node="item"
+          v-model:flow-node="item.childNode"
+          @find:recursive-find-parent-node="recursiveFindParentNode"
+        />
+      </div>
+    </div>
+    <NodeHandler
+      v-if="currentNode"
+      v-model:child-node="currentNode.childNode"
+      :current-node="currentNode"
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+import NodeHandler from '../NodeHandler.vue'
+import ProcessNodeTree from '../ProcessNodeTree.vue'
+import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
+import { useTaskStatusClass } from '../node'
+import { getDefaultInclusiveConditionNodeName } from '../utils'
+import { generateUUID } from '@/utils'
+import ConditionNodeConfig from '../nodes-config/ConditionNodeConfig.vue'
+const { proxy } = getCurrentInstance() as any
+defineOptions({
+  name: 'InclusiveNode'
+})
+const props = defineProps({
+  flowNode: {
+    type: Object as () => SimpleFlowNode,
+    required: true
+  }
+})
+// 定义事件,更新父组件
+const emits = defineEmits<{
+  'update:modelValue': [node: SimpleFlowNode | undefined]
+  'find:parentNode': [nodeList: SimpleFlowNode[], nodeType: number]
+  'find:recursiveFindParentNode': [
+    nodeList: SimpleFlowNode[],
+    curentNode: SimpleFlowNode,
+    nodeType: number
+  ]
+}>()
+// 是否只读
+const readonly = inject<Boolean>('readonly')
+
+const currentNode = ref<SimpleFlowNode>(props.flowNode)
+
+watch(
+  () => props.flowNode,
+  (newValue) => {
+    currentNode.value = newValue
+  }
+)
+
+const showInputs = ref<boolean[]>([])
+// 失去焦点
+const blurEvent = (index: number) => {
+  showInputs.value[index] = false
+  const conditionNode = currentNode.value.conditionNodes?.at(index) as SimpleFlowNode
+  conditionNode.name =
+    conditionNode.name || getDefaultInclusiveConditionNodeName(index, conditionNode.defaultFlow)
+}
+
+// 点击条件名称
+const clickEvent = (index: number) => {
+  showInputs.value[index] = true
+}
+
+const conditionNodeConfig = (nodeId: string) => {
+  if (readonly) {
+    return
+  }
+  const conditionNode = proxy.$refs[nodeId][0]
+  conditionNode.open()
+}
+
+// 新增条件
+const addCondition = () => {
+  const conditionNodes = currentNode.value.conditionNodes
+  if (conditionNodes) {
+    const len = conditionNodes.length
+    let lastIndex = len - 1
+    const conditionData: SimpleFlowNode = {
+      id: 'Flow_' + generateUUID(),
+      name: '包容条件' + len,
+      showText: '',
+      type: NodeType.CONDITION_NODE,
+      childNode: undefined,
+      conditionNodes: [],
+      conditionType: 1,
+      defaultFlow: false
+    }
+    conditionNodes.splice(lastIndex, 0, conditionData)
+  }
+}
+
+// 删除条件
+const deleteCondition = (index: number) => {
+  const conditionNodes = currentNode.value.conditionNodes
+  if (conditionNodes) {
+    conditionNodes.splice(index, 1)
+    if (conditionNodes.length == 1) {
+      const childNode = currentNode.value.childNode
+      // 更新此节点为后续孩子节点
+      emits('update:modelValue', childNode)
+    }
+  }
+}
+
+// 移动节点
+const moveNode = (index: number, to: number) => {
+  // -1 :向左  1: 向右
+  if (currentNode.value.conditionNodes) {
+    currentNode.value.conditionNodes[index] = currentNode.value.conditionNodes.splice(
+      index + to,
+      1,
+      currentNode.value.conditionNodes[index]
+    )[0]
+  }
+}
+// 递归从父节点中查询匹配的节点
+const recursiveFindParentNode = (
+  nodeList: SimpleFlowNode[],
+  node: SimpleFlowNode,
+  nodeType: number
+) => {
+  if (!node || node.type === NodeType.START_USER_NODE) {
+    return
+  }
+  if (node.type === nodeType) {
+    nodeList.push(node)
+  }
+  // 条件节点 (NodeType.CONDITION_NODE) 比较特殊。需要调用其父节点条件分支节点(NodeType.INCLUSIVE_BRANCH_NODE) 继续查找
+  emits('find:parentNode', nodeList, nodeType)
+}
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/components/SimpleProcessDesignerV2/src/nodes/ParallelNode.vue b/src/components/SimpleProcessDesignerV2/src/nodes/ParallelNode.vue
new file mode 100644
index 0000000..7aa6793
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/nodes/ParallelNode.vue
@@ -0,0 +1,184 @@
+<template>
+  <div class="branch-node-wrapper">
+    <div class="branch-node-container">
+      <div
+        v-if="readonly"
+        class="branch-node-readonly"
+        :class="`${useTaskStatusClass(currentNode?.activityStatus)}`"
+      >
+        <span class="iconfont icon-parallel icon-size parallel"></span>
+      </div>
+      <el-button v-else class="branch-node-add" color="#626aef" @click="addCondition" plain
+        >添加分支</el-button
+      >
+      <div
+        class="branch-node-item"
+        v-for="(item, index) in currentNode.conditionNodes"
+        :key="index"
+      >
+        <template v-if="index == 0">
+          <div class="branch-line-first-top"></div>
+          <div class="branch-line-first-bottom"></div>
+        </template>
+        <template v-if="index + 1 == currentNode.conditionNodes?.length">
+          <div class="branch-line-last-top"></div>
+          <div class="branch-line-last-bottom"></div>
+        </template>
+        <div class="node-wrapper">
+          <div class="node-container">
+            <div class="node-box" :class="`${useTaskStatusClass(item.activityStatus)}`">
+              <div class="branch-node-title-container">
+                <div v-if="showInputs[index]">
+                  <input
+                    type="text"
+                    class="input-max-width editable-title-input"
+                    @blur="blurEvent(index)"
+                    v-mountedFocus
+                    v-model="item.name"
+                  />
+                </div>
+                <div v-else class="branch-title" @click="clickEvent(index)"> {{ item.name }} </div>
+                <div class="branch-priority">无优先级</div>
+              </div>
+              <div class="branch-node-content" @click="conditionNodeConfig(item.id)">
+                <div class="branch-node-text" :title="item.showText" v-if="item.showText">
+                  {{ item.showText }}
+                </div>
+                <div class="branch-node-text" v-else>
+                  {{ NODE_DEFAULT_TEXT.get(NodeType.CONDITION_NODE) }}
+                </div>
+              </div>
+              <div v-if="!readonly" class="node-toolbar">
+                <div class="toolbar-icon">
+                  <Icon
+                    color="#0089ff"
+                    icon="ep:circle-close-filled"
+                    :size="18"
+                    @click="deleteCondition(index)"
+                  />
+                </div>
+              </div>
+            </div>
+            <NodeHandler v-model:child-node="item.childNode" :current-node="item" />
+          </div>
+        </div>
+        <!-- 递归显示子节点  -->
+        <ProcessNodeTree
+          v-if="item && item.childNode"
+          :parent-node="item"
+          v-model:flow-node="item.childNode"
+          @find:recursive-find-parent-node="recursiveFindParentNode"
+        />
+      </div>
+    </div>
+    <NodeHandler
+      v-if="currentNode"
+      v-model:child-node="currentNode.childNode"
+      :current-node="currentNode"
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+import NodeHandler from '../NodeHandler.vue'
+import ProcessNodeTree from '../ProcessNodeTree.vue'
+import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
+import { useTaskStatusClass } from '../node'
+import { generateUUID } from '@/utils'
+const { proxy } = getCurrentInstance() as any
+defineOptions({
+  name: 'ParallelNode'
+})
+const props = defineProps({
+  flowNode: {
+    type: Object as () => SimpleFlowNode,
+    required: true
+  }
+})
+// 定义事件,更新父组件
+const emits = defineEmits<{
+  'update:modelValue': [node: SimpleFlowNode | undefined]
+  'find:parentNode': [nodeList: SimpleFlowNode[], nodeType: number]
+  'find:recursiveFindParentNode': [
+    nodeList: SimpleFlowNode[],
+    curentNode: SimpleFlowNode,
+    nodeType: number
+  ]
+}>()
+
+const currentNode = ref<SimpleFlowNode>(props.flowNode)
+// 是否只读
+const readonly = inject<Boolean>('readonly')
+
+watch(
+  () => props.flowNode,
+  (newValue) => {
+    currentNode.value = newValue
+  }
+)
+
+const showInputs = ref<boolean[]>([])
+// 失去焦点
+const blurEvent = (index: number) => {
+  showInputs.value[index] = false
+  const conditionNode = currentNode.value.conditionNodes?.at(index) as SimpleFlowNode
+  conditionNode.name = conditionNode.name || `并行${index + 1}`
+}
+
+// 点击条件名称
+const clickEvent = (index: number) => {
+  showInputs.value[index] = true
+}
+
+const conditionNodeConfig = (nodeId: string) => {
+  const conditionNode = proxy.$refs[nodeId][0]
+  conditionNode.open()
+}
+
+// 新增条件
+const addCondition = () => {
+  const conditionNodes = currentNode.value.conditionNodes
+  if (conditionNodes) {
+    const len = conditionNodes.length
+    let lastIndex = len - 1
+    const conditionData: SimpleFlowNode = {
+      id: 'Flow_' + generateUUID(),
+      name: '并行' + len,
+      showText: '无需配置条件同时执行',
+      type: NodeType.CONDITION_NODE,
+      childNode: undefined,
+      conditionNodes: []
+    }
+    conditionNodes.splice(lastIndex, 0, conditionData)
+  }
+}
+
+// 删除条件
+const deleteCondition = (index: number) => {
+  const conditionNodes = currentNode.value.conditionNodes
+  if (conditionNodes) {
+    conditionNodes.splice(index, 1)
+    if (conditionNodes.length == 1) {
+      const childNode = currentNode.value.childNode
+      // 更新此节点为后续孩子节点
+      emits('update:modelValue', childNode)
+    }
+  }
+}
+
+// 递归从父节点中查询匹配的节点
+const recursiveFindParentNode = (
+  nodeList: SimpleFlowNode[],
+  node: SimpleFlowNode,
+  nodeType: number
+) => {
+  if (!node || node.type === NodeType.START_USER_NODE) {
+    return
+  }
+  if (node.type === nodeType) {
+    nodeList.push(node)
+  }
+  // 条件节点 (NodeType.CONDITION_NODE) 比较特殊。需要调用其父节点并行节点(NodeType.PARALLEL_NODE) 继续查找
+  emits('find:parentNode', nodeList, nodeType)
+}
+</script>
diff --git a/src/components/SimpleProcessDesignerV2/src/nodes/StartUserNode.vue b/src/components/SimpleProcessDesignerV2/src/nodes/StartUserNode.vue
new file mode 100644
index 0000000..89a57d0
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/nodes/StartUserNode.vue
@@ -0,0 +1,154 @@
+<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 start-user"
+            ><span class="iconfont icon-start-user"></span
+          ></div>
+          <input
+            v-if="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="nodeClick">
+          <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.START_USER_NODE) }}
+          </div>
+          <Icon icon="ep:arrow-right-bold" v-if="!readonly" />
+        </div>
+      </div>
+      <!-- 传递子节点给添加节点组件。会在子节点前面添加节点 -->
+      <NodeHandler
+        v-if="currentNode"
+        v-model:child-node="currentNode.childNode"
+        :current-node="currentNode"
+      />
+    </div>
+  </div>
+  <StartUserNodeConfig v-if="!readonly && currentNode" ref="nodeSetting" :flow-node="currentNode" />
+  <!-- 审批记录 -->
+  <el-dialog
+    :title="dialogTitle || '审批记录'"
+    v-model="dialogVisible"
+    width="1000px"
+    append-to-body
+  >
+    <el-row>
+      <el-table :data="selectTasks" size="small" border header-cell-class-name="table-header-gray">
+        <el-table-column
+          label="序号"
+          header-align="center"
+          align="center"
+          type="index"
+          width="50"
+        />
+        <el-table-column label="审批人" min-width="100" align="center">
+          <template #default="scope">
+            {{ scope.row.assigneeUser?.nickname || scope.row.ownerUser?.nickname }}
+          </template>
+        </el-table-column>
+
+        <el-table-column label="部门" min-width="100" align="center">
+          <template #default="scope">
+            {{ scope.row.assigneeUser?.deptName || scope.row.ownerUser?.deptName }}
+          </template>
+        </el-table-column>
+        <el-table-column
+          :formatter="dateFormatter"
+          align="center"
+          label="开始时间"
+          prop="createTime"
+          min-width="140"
+        />
+        <el-table-column
+          :formatter="dateFormatter"
+          align="center"
+          label="结束时间"
+          prop="endTime"
+          min-width="140"
+        />
+        <el-table-column align="center" label="审批状态" prop="status" min-width="90">
+          <template #default="scope">
+            <dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="scope.row.status" />
+          </template>
+        </el-table-column>
+        <el-table-column align="center" label="审批建议" prop="reason" min-width="120" />
+        <el-table-column align="center" label="耗时" prop="durationInMillis" width="100">
+          <template #default="scope">
+            {{ formatPast2(scope.row.durationInMillis) }}
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-row>
+  </el-dialog>
+</template>
+<script setup lang="ts">
+import NodeHandler from '../NodeHandler.vue'
+import { useWatchNode, useNodeName2, useTaskStatusClass } from '../node'
+import { SimpleFlowNode, NODE_DEFAULT_TEXT, NodeType } from '../consts'
+import StartUserNodeConfig from '../nodes-config/StartUserNodeConfig.vue'
+import { dateFormatter, formatPast2 } from '@/utils/formatTime'
+import { DICT_TYPE } from '@/utils/dict'
+defineOptions({
+  name: 'StartEventNode'
+})
+const props = defineProps({
+  flowNode: {
+    type: Object as () => SimpleFlowNode,
+    default: () => null
+  }
+})
+const readonly = inject<Boolean>('readonly') // 是否只读
+const tasks = inject<Ref<any[]>>('tasks')
+// 定义事件,更新父组件。
+const emits = defineEmits<{
+  'update:modelValue': [node: SimpleFlowNode | undefined]
+}>()
+// 监控节点变化
+const currentNode = useWatchNode(props)
+// 节点名称编辑
+const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.START_USER_NODE)
+
+const nodeSetting = ref()
+//
+const nodeClick = () => {
+  if (readonly) {
+    // 只读模式,弹窗显示任务信息
+    if (tasks && tasks.value) {
+      dialogTitle.value = currentNode.value.name
+      selectTasks.value = tasks.value.filter(
+        (item: any) => item?.taskDefinitionKey === currentNode.value.id
+      )
+      dialogVisible.value = true
+    }
+  } else {
+    // 编辑模式,打开节点配置、把当前节点传递给配置组件
+    nodeSetting.value.showStartUserNodeConfig(currentNode.value)
+    nodeSetting.value.openDrawer()
+  }
+}
+
+// 任务的弹窗显示,用于只读模式
+const dialogVisible = ref(false) // 弹窗可见性
+const dialogTitle = ref<string | undefined>(undefined) // 弹窗标题
+const selectTasks = ref<any[] | undefined>([]) // 选中的任务数组
+</script>
+<style lang="scss" scoped></style>
diff --git a/src/components/SimpleProcessDesignerV2/src/nodes/UserTaskNode.vue b/src/components/SimpleProcessDesignerV2/src/nodes/UserTaskNode.vue
new file mode 100644
index 0000000..761a674
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/nodes/UserTaskNode.vue
@@ -0,0 +1,174 @@
+<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 user-task"><span class="iconfont icon-approve"></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="nodeClick">
+          <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.USER_TASK_NODE) }}
+          </div>
+          <Icon icon="ep:arrow-right-bold" v-if="!readonly" />
+        </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>
+  </div>
+  <UserTaskNodeConfig
+    v-if="currentNode"
+    ref="nodeSetting"
+    :flow-node="currentNode"
+    @find:return-task-nodes="findReturnTaskNodes"
+  />
+  <!-- 审批记录 -->
+  <el-dialog
+    :title="dialogTitle || '审批记录'"
+    v-model="dialogVisible"
+    width="1000px"
+    append-to-body
+  >
+    <el-row>
+      <el-table :data="selectTasks" size="small" border header-cell-class-name="table-header-gray">
+        <el-table-column
+          label="序号"
+          header-align="center"
+          align="center"
+          type="index"
+          width="50"
+        />
+        <el-table-column label="审批人" min-width="100" align="center">
+          <template #default="scope">
+            {{ scope.row.assigneeUser?.nickname || scope.row.ownerUser?.nickname }}
+          </template>
+        </el-table-column>
+
+        <el-table-column label="部门" min-width="100" align="center">
+          <template #default="scope">
+            {{ scope.row.assigneeUser?.deptName || scope.row.ownerUser?.deptName }}
+          </template>
+        </el-table-column>
+        <el-table-column
+          :formatter="dateFormatter"
+          align="center"
+          label="开始时间"
+          prop="createTime"
+          min-width="140"
+        />
+        <el-table-column
+          :formatter="dateFormatter"
+          align="center"
+          label="结束时间"
+          prop="endTime"
+          min-width="140"
+        />
+        <el-table-column align="center" label="审批状态" prop="status" min-width="90">
+          <template #default="scope">
+            <dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="scope.row.status" />
+          </template>
+        </el-table-column>
+        <el-table-column align="center" label="审批建议" prop="reason" min-width="120" />
+        <el-table-column align="center" label="耗时" prop="durationInMillis" width="100">
+          <template #default="scope">
+            {{ formatPast2(scope.row.durationInMillis) }}
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-row>
+  </el-dialog>
+</template>
+<script setup lang="ts">
+import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
+import { useWatchNode, useNodeName2, useTaskStatusClass } from '../node'
+import NodeHandler from '../NodeHandler.vue'
+import UserTaskNodeConfig from '../nodes-config/UserTaskNodeConfig.vue'
+import { dateFormatter, formatPast2 } from '@/utils/formatTime'
+import { DICT_TYPE } from '@/utils/dict'
+defineOptions({
+  name: 'UserTaskNode'
+})
+const props = defineProps({
+  flowNode: {
+    type: Object as () => SimpleFlowNode,
+    required: true
+  }
+})
+const emits = defineEmits<{
+  'update:flowNode': [node: SimpleFlowNode | undefined]
+  'find:parentNode': [nodeList: SimpleFlowNode[], nodeType: NodeType]
+}>()
+
+// 是否只读
+const readonly = inject<Boolean>('readonly')
+const tasks = inject<Ref<any[]>>('tasks')
+// 监控节点变化
+const currentNode = useWatchNode(props)
+// 节点名称编辑
+const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.START_USER_NODE)
+const nodeSetting = ref()
+
+const nodeClick = () => {
+  if (readonly) {
+    if (tasks && tasks.value) {
+      dialogTitle.value = currentNode.value.name
+      // 只读模式,弹窗显示任务信息
+      selectTasks.value = tasks.value.filter(
+        (item: any) => item?.taskDefinitionKey === currentNode.value.id
+      )
+      dialogVisible.value = true
+    }
+  } else {
+    // 编辑模式,打开节点配置、把当前节点传递给配置组件
+    nodeSetting.value.showUserTaskNodeConfig(currentNode.value)
+    nodeSetting.value.openDrawer()
+  }
+}
+
+const deleteNode = () => {
+  emits('update:flowNode', currentNode.value.childNode)
+}
+// 查找可以驳回用户节点
+const findReturnTaskNodes = (
+  matchNodeList: SimpleFlowNode[] // 匹配的节点
+) => {
+  // 从父节点查找
+  emits('find:parentNode', matchNodeList, NodeType.USER_TASK_NODE)
+}
+
+// 任务的弹窗显示,用于只读模式
+const dialogVisible = ref(false) // 弹窗可见性
+const dialogTitle = ref<string | undefined>(undefined) // 弹窗标题
+const selectTasks = ref<any[] | undefined>([]) // 选中的任务数组
+</script>
+<style lang="scss" scoped></style>
diff --git a/src/components/SimpleProcessDesignerV2/src/utils.ts b/src/components/SimpleProcessDesignerV2/src/utils.ts
new file mode 100644
index 0000000..8e715b4
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/utils.ts
@@ -0,0 +1,41 @@
+import { TimeUnitType, ApproveType, APPROVE_TYPE } from './consts'
+
+// 获取条件节点默认的名称
+export const getDefaultConditionNodeName = (index: number, defaultFlow: boolean | undefined): string => {
+  if (defaultFlow) {
+    return '其它情况'
+  }
+  return '条件' + (index + 1)
+}
+
+// 获取包容分支条件节点默认的名称
+export const getDefaultInclusiveConditionNodeName = (index: number, defaultFlow: boolean | undefined): string => {
+  if (defaultFlow) {
+    return '其它情况'
+  }
+  return '包容条件' + (index + 1)
+}
+
+export const convertTimeUnit = (strTimeUnit: string) => {
+  if (strTimeUnit === 'M') {
+    return TimeUnitType.MINUTE
+  }
+  if (strTimeUnit === 'H') {
+    return TimeUnitType.HOUR
+  }
+  if (strTimeUnit === 'D') {
+    return TimeUnitType.DAY
+  }
+  return TimeUnitType.HOUR
+}
+
+export const getApproveTypeText = (approveType: ApproveType): string => {
+  let approveTypeText = ''
+  APPROVE_TYPE.forEach((item) => {
+    if (item.value === approveType) {
+      approveTypeText = item.label
+      return
+    }
+  })
+  return approveTypeText
+}
diff --git a/src/components/SimpleProcessDesignerV2/theme/iconfont.ttf b/src/components/SimpleProcessDesignerV2/theme/iconfont.ttf
new file mode 100644
index 0000000..bb85b35
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/theme/iconfont.ttf
Binary files differ
diff --git a/src/components/SimpleProcessDesignerV2/theme/iconfont.woff b/src/components/SimpleProcessDesignerV2/theme/iconfont.woff
new file mode 100644
index 0000000..94befbd
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/theme/iconfont.woff
Binary files differ
diff --git a/src/components/SimpleProcessDesignerV2/theme/iconfont.woff2 b/src/components/SimpleProcessDesignerV2/theme/iconfont.woff2
new file mode 100644
index 0000000..e8f95c8
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/theme/iconfont.woff2
Binary files differ
diff --git a/src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss b/src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss
new file mode 100644
index 0000000..516756e
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss
@@ -0,0 +1,750 @@
+// 配置节点头部
+.config-header {
+  display: flex;
+  flex-direction: column;
+
+  .node-name {
+    display: flex;
+    height: 24px;
+    line-height: 24px;
+    font-size: 16px;
+    cursor: pointer;
+    align-items: center;
+  }
+
+  .divide-line {
+    width: 100%;
+    height: 1px;
+    margin-top: 16px;
+    background: #eee;
+  }
+
+  .config-editable-input {
+    height: 24px;
+    max-width: 510px;
+    font-size: 16px;
+    line-height: 24px;
+    border: 1px solid #d9d9d9;
+    border-radius: 4px;
+    transition: all 0.3s;
+
+    &:focus {
+      border-color: #40a9ff;
+      outline: 0;
+      box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
+    }
+  }
+}
+
+// 表单字段权限
+.field-setting-pane {
+  display: flex;
+  flex-direction: column;
+  font-size: 14px;
+
+  .field-setting-desc {
+    padding-right: 8px;
+    margin-bottom: 16px;
+    font-size: 16px;
+    font-weight: 700;
+  }
+
+  .field-permit-title {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    height: 45px;
+    padding-left: 12px;
+    line-height: 45px;
+    background-color: #f8fafc0a;
+    border: 1px solid #1f38581a;
+
+    .first-title {
+      text-align: left !important;
+    }
+
+    .other-titles {
+      display: flex;
+      justify-content: space-between;
+    }
+
+    .setting-title-label {
+      display: inline-block;
+      width: 110px;
+      padding: 5px 0;
+      font-size: 13px;
+      font-weight: 700;
+      color: #000;
+      text-align: center;
+    }
+  }
+
+  .field-setting-item {
+    align-items: center;
+    display: flex;
+    justify-content: space-between;
+    height: 38px;
+    padding-left: 12px;
+    border: 1px solid #1f38581a;
+    border-top: 0;
+
+    .field-setting-item-label {
+      display: inline-block;
+      width: 110px;
+      min-height: 16px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      cursor: text;
+    }
+
+    .field-setting-item-group {
+      display: flex;
+      justify-content: space-between;
+
+      .item-radio-wrap {
+        display: inline-block;
+        width: 110px;
+        text-align: center;
+      }
+    }
+  }
+}
+
+// 节点连线气泡卡片样式
+.handler-item-wrapper {
+  display: flex;
+  cursor: pointer;
+
+  .handler-item {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+  }
+
+  .handler-item-icon {
+    width: 60px;
+    height: 60px;
+    background: #fff;
+    border: 1px solid #e2e2e2;
+    border-radius: 50%;
+    user-select: none;
+    text-align: center;
+
+    &:hover {
+      background: #e2e2e2;
+      box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
+    }
+
+    .icon-size {
+      font-size: 25px;
+      line-height: 60px;
+    }
+  }
+
+  .approve {
+    color: #ff943e;
+  }
+  .copy {
+    color: #3296fa;
+  }
+
+  .condition {
+    color: #67c23a;
+  }
+
+  .parallel {
+    color: #626aef;
+  }
+
+  .inclusive {
+    color: #345da2;
+  }
+
+  .handler-item-text {
+    margin-top: 4px;
+    width: 80px;
+    text-align: center;
+    font-size: 13px;
+  }
+}
+// Simple 流程模型样式
+.simple-process-model-container {
+  height: 100%;
+  padding-top: 32px;
+  background-color: #fafafa;
+  .simple-process-model {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    transform-origin: 50% 0 0;
+    overflow: auto;
+    transform: scale(1);
+    transition: transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
+    background: url(@/assets/svgs/bpm/simple-process-bg.svg) 0 0 repeat;
+    // 节点容器 定义节点宽度
+    .node-container {
+      width: 200px;
+    }
+    // 节点
+    .node-box {
+      position: relative;
+      display: flex;
+      min-height: 70px;
+      padding: 5px 10px 8px;
+      cursor: pointer;
+      background-color: #fff;
+      flex-direction: column;
+      border: 2px solid transparent;
+      border-radius: 8px;
+      box-shadow: 0 1px 4px 0 rgb(10 30 65 / 16%);
+      transition: all 0.1s cubic-bezier(0.645, 0.045, 0.355, 1);
+
+      &.status-pass {
+        background-color: #a9da90;
+        border-color: #67c23a;
+      }
+
+      &.status-pass:hover {
+        border-color: #67c23a;
+      }
+
+      &.status-running {
+        background-color: #e7f0fe;
+        border-color: #5a9cf8;
+      }
+
+      &.status-running:hover {
+        border-color: #5a9cf8;
+      }
+
+      &.status-reject {
+        background-color: #f6e5e5;
+        border-color: #e47470;
+      }
+
+      &.status-reject:hover {
+        border-color: #e47470;
+      }
+
+      &:hover {
+        border-color: #0089ff;
+
+        .node-toolbar {
+          opacity: 1;
+        }
+
+        .branch-node-move {
+          display: flex;
+        }
+      }
+
+      // 普通节点标题
+      .node-title-container {
+        display: flex;
+        padding: 4px;
+        cursor: pointer;
+        border-radius: 4px 4px 0 0;
+        align-items: center;
+
+        .node-title-icon {
+          display: flex;
+          align-items: center;
+
+          &.user-task {
+            color: #ff943e;
+          }
+
+          &.copy-task {
+            color: #3296fa;
+          }
+
+          &.start-user {
+            color: #676565;
+          }
+        }
+
+        .node-title {
+          margin-left: 4px;
+          overflow: hidden;
+          font-size: 14px;
+          font-weight: 600;
+          line-height: 18px;
+          color: #1f1f1f;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+
+          &:hover {
+            border-bottom: 1px dashed #f60;
+          }
+        }
+      }
+
+      // 条件节点标题
+      .branch-node-title-container {
+        display: flex;
+        padding: 4px 0;
+        cursor: pointer;
+        border-radius: 4px 4px 0 0;
+        align-items: center;
+        justify-content: space-between;
+
+        .input-max-width {
+          max-width: 115px !important;
+        }
+
+        .branch-title {
+          overflow: hidden;
+          font-size: 13px;
+          font-weight: 600;
+          color: #f60;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+
+          &:hover {
+            border-bottom: 1px dashed #000;
+          }
+        }
+
+        .branch-priority {
+          min-width: 50px;
+          font-size: 12px;
+        }
+      }
+
+      .node-content {
+        display: flex;
+        min-height: 32px;
+        padding: 4px 8px;
+        margin-top: 4px;
+        line-height: 32px;
+        justify-content: space-between;
+        align-items: center;
+        color: #111f2c;
+        background: rgb(0 0 0 / 3%);
+        border-radius: 4px;
+
+        .node-text {
+          display: -webkit-box;
+          overflow: hidden;
+          font-size: 14px;
+          line-height: 24px;
+          text-overflow: ellipsis;
+          word-break: break-all;
+          -webkit-line-clamp: 2; /* 这将限制文本显示为两行 */
+          -webkit-box-orient: vertical;
+        }
+      }
+
+      //条件节点内容
+      .branch-node-content {
+        display: flex;
+        min-height: 32px;
+        padding: 4px 0;
+        margin-top: 4px;
+        line-height: 32px;
+        align-items: center;
+        color: #111f2c;
+        border-radius: 4px;
+
+        .branch-node-text {
+          overflow: hidden;
+          font-size: 12px;
+          line-height: 24px;
+          text-overflow: ellipsis;
+          word-break: break-all;
+          -webkit-line-clamp: 2; /* 这将限制文本显示为两行 */
+          -webkit-box-orient: vertical;
+        }
+      }
+
+      // 节点操作 :删除
+      .node-toolbar {
+        position: absolute;
+        top: -20px;
+        right: 0;
+        display: flex;
+        opacity: 0;
+
+        .toolbar-icon {
+          text-align: center;
+          vertical-align: middle;
+        }
+      }
+
+      // 条件节点左右移动
+      .branch-node-move {
+        position: absolute;
+        display: none;
+        width: 10px;
+        height: 100%;
+        cursor: pointer;
+        align-items: center;
+        justify-content: center;
+      }
+
+      .move-node-left {
+        top: 0;
+        left: -2px;
+        background: rgb(126 134 142 / 8%);
+        border-bottom-left-radius: 8px;
+        border-top-left-radius: 8px;
+      }
+
+      .move-node-right {
+        top: 0;
+        right: -2px;
+        background: rgb(126 134 142 / 8%);
+        border-top-right-radius: 6px;
+        border-bottom-right-radius: 6px;
+      }
+    }
+
+    .node-config-error {
+      border-color: #ff5219 !important;
+    }
+    // 普通节点包装
+    .node-wrapper {
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      align-items: center;
+    }
+    // 节点连线处理
+    .node-handler-wrapper {
+      position: relative;
+      display: flex;
+      height: 70px;
+      align-items: center;
+      user-select: none;
+      justify-content: center;
+      flex-direction: column;
+
+      &::before {
+        position: absolute;
+        top: 0;
+        z-index: 0;
+        width: 2px;
+        height: 100%;
+        margin: auto;
+        background-color: #dedede;
+        content: '';
+      }
+
+      .node-handler {
+        .add-icon {
+          position: relative;
+          top: -5px;
+          display: flex;
+          width: 25px;
+          height: 25px;
+          color: #fff;
+          cursor: pointer;
+          background-color: #0089ff;
+          border-radius: 50%;
+          align-items: center;
+          justify-content: center;
+
+          &:hover {
+            transform: scale(1.1);
+          }
+        }
+      }
+
+      .node-handler-arrow {
+        position: absolute;
+        bottom: 0;
+        left: 50%;
+        display: flex;
+        transform: translateX(-50%);
+      }
+    }
+
+    // 条件节点包装
+    .branch-node-wrapper {
+      position: relative;
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      align-items: center;
+      margin-top: 16px;
+
+      .branch-node-container {
+        position: relative;
+        display: flex;
+
+        &::before {
+          position: absolute;
+          left: 50%;
+          width: 4px;
+          height: 100%;
+          background-color: #fafafa;
+          content: '';
+          transform: translate(-50%);
+        }
+
+        .branch-node-add {
+          position: absolute;
+          top: -18px;
+          left: 50%;
+          z-index: 1;
+          height: 36px;
+          padding: 0 10px;
+          font-size: 12px;
+          line-height: 36px;
+          border: 2px solid #dedede;
+          border-radius: 18px;
+          transform: translateX(-50%);
+          transform-origin: center center;
+        }
+
+        .branch-node-readonly {
+          position: absolute;
+          top: -18px;
+          left: 50%;
+          z-index: 1;
+          display: flex;
+          width: 36px;
+          height: 36px;
+          background-color: #fff;
+          border: 2px solid #dedede;
+          border-radius: 50%;
+          transform: translateX(-50%);
+          align-items: center;
+          justify-content: center;
+          transform-origin: center center;
+
+          &.status-pass {
+            background-color: #e9f4e2;
+            border-color: #6bb63c;
+          }
+
+          &.status-pass:hover {
+            border-color: #6bb63c;
+          }
+
+          .icon-size {
+            font-size: 22px;
+            &.condition {
+              color: #67c23a;
+            }
+            &.parallel {
+              color: #626aef;
+            }
+            &.inclusive {
+              color: #345da2;
+            }
+          }
+        }
+
+        .branch-node-item {
+          position: relative;
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          min-width: 280px;
+          padding: 40px 40px 0;
+          background: transparent;
+          border-top: 2px solid #dedede;
+          border-bottom: 2px solid #dedede;
+
+          &::before {
+            position: absolute;
+            width: 2px;
+            height: 100%;
+            margin: auto;
+            inset: 0;
+            background-color: #dedede;
+            content: '';
+          }
+        }
+        // 覆盖条件节点第一个节点左上角的线
+        .branch-line-first-top {
+          position: absolute;
+          top: -5px;
+          left: -1px;
+          width: 50%;
+          height: 7px;
+          background-color: #fafafa;
+          content: '';
+        }
+        // 覆盖条件节点第一个节点左下角的线
+        .branch-line-first-bottom {
+          position: absolute;
+          bottom: -5px;
+          left: -1px;
+          width: 50%;
+          height: 7px;
+          background-color: #fafafa;
+          content: '';
+        }
+        // 覆盖条件节点最后一个节点右上角的线
+        .branch-line-last-top {
+          position: absolute;
+          top: -5px;
+          right: -1px;
+          width: 50%;
+          height: 7px;
+          background-color: #fafafa;
+          content: '';
+        }
+        // 覆盖条件节点最后一个节点右下角的线
+        .branch-line-last-bottom {
+          position: absolute;
+          right: -1px;
+          bottom: -5px;
+          width: 50%;
+          height: 7px;
+          background-color: #fafafa;
+          content: '';
+        }
+      }
+    }
+
+    .node-fixed-name {
+      display: inline-block;
+      width: auto;
+      padding: 0 4px;
+      overflow: hidden;
+      text-align: center;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+    // 开始节点包装
+    .start-node-wrapper {
+      position: relative;
+      margin-top: 16px;
+
+      .start-node-container {
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        align-items: center;
+
+        .start-node-box {
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          width: 90px;
+          height: 36px;
+          padding: 3px 4px;
+          color: #212121;
+          cursor: pointer;
+          background: #fafafa;
+          border-radius: 30px;
+          box-shadow: 0 1px 5px 0 rgb(10 30 65 / 8%);
+          box-sizing: border-box;
+        }
+      }
+    }
+
+    // 结束节点包装
+    .end-node-wrapper {
+      margin-bottom: 16px;
+
+      .end-node-box {
+        display: flex;
+        width: 80px;
+        height: 36px;
+        color: #212121;
+        border: 2px solid #fafafa;
+        border-radius: 30px;
+        box-shadow: 0 1px 5px 0 rgb(10 30 65 / 8%);
+        box-sizing: border-box;
+        justify-content: center;
+        align-items: center;
+
+        &.status-pass {
+          background-color: #a9da90;
+          border-color: #6bb63c;
+        }
+
+        &.status-pass:hover {
+          border-color: #6bb63c;
+        }
+
+        &.status-reject {
+          background-color: #f6e5e5;
+          border-color: #e47470;
+        }
+
+        &.status-reject:hover {
+          border-color: #e47470;
+        }
+
+        &.status-cancel {
+          background-color: #eaeaeb;
+          border-color: #919398;
+        }
+
+        &.status-cancel:hover {
+          border-color: #919398;
+        }
+      }
+    }
+
+    // 可编辑的 title 输入框
+    .editable-title-input {
+      height: 20px;
+      max-width: 145px;
+      margin-left: 4px;
+      font-size: 12px;
+      line-height: 20px;
+      border: 1px solid #d9d9d9;
+      border-radius: 4px;
+      transition: all 0.3s;
+
+      &:focus {
+        border-color: #40a9ff;
+        outline: 0;
+        box-shadow: 0 0 0 2px rgb(24 144 255 / 20%);
+      }
+    }
+  }
+}
+
+// iconfont 样式
+@font-face {
+  font-family: 'iconfont'; /* Project id 4495938 */
+  src:
+    url('iconfont.woff2?t=1724339470412') format('woff2'),
+    url('iconfont.woff?t=1724339470412') format('woff'),
+    url('iconfont.ttf?t=1724339470412') format('truetype');
+}
+
+.iconfont {
+  font-family: 'iconfont' !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.icon-start-user:before {
+  content: '\e679';
+}
+
+.icon-inclusive:before {
+  content: '\e602';
+}
+
+.icon-copy:before {
+  content: '\e7eb';
+}
+
+.icon-handle:before {
+  content: '\e61c';
+}
+
+.icon-exclusive:before {
+  content: '\e717';
+}
+
+.icon-approve:before {
+  content: '\e715';
+}
+
+.icon-parallel:before {
+  content: '\e688';
+}
diff --git a/src/components/UserSelectForm/index.vue b/src/components/UserSelectForm/index.vue
new file mode 100644
index 0000000..801489b
--- /dev/null
+++ b/src/components/UserSelectForm/index.vue
@@ -0,0 +1,152 @@
+<template>
+  <Dialog v-model="dialogVisible" title="人员选择" width="800">
+    <el-row class="gap2" v-loading="formLoading">
+      <el-col :span="6">
+        <ContentWrap class="h-1/1">
+          <el-tree
+            ref="treeRef"
+            :data="deptTree"
+            :expand-on-click-node="false"
+            :props="defaultProps"
+            default-expand-all
+            highlight-current
+            node-key="id"
+            @node-click="handleNodeClick"
+          />
+        </ContentWrap>
+      </el-col>
+      <el-col :span="17">
+        <el-transfer
+          v-model="selectedUserIdList"
+          :titles="['未选', '已选']"
+          filterable
+          filter-placeholder="搜索成员"
+          :data="transferUserList"
+          :props="{ label: 'nickname', key: 'id' }"
+        />
+      </el-col>
+    </el-row>
+    <template #footer>
+      <el-button
+        :disabled="formLoading || !selectedUserIdList?.length"
+        type="primary"
+        @click="submitForm"
+      >
+        确 定
+      </el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script lang="ts" setup>
+import { defaultProps, findTreeNode, handleTree } from '@/utils/tree'
+import * as DeptApi from '@/api/system/dept'
+import * as UserApi from '@/api/system/user'
+
+defineOptions({ name: 'UserSelectForm' })
+const emit = defineEmits<{
+  confirm: [id: any, userList: any[]]
+}>()
+const { t } = useI18n() // 国际
+const message = useMessage() // 消息弹窗
+const deptTree = ref<Tree[]>([]) // 部门树形结构化
+const userList = ref<UserApi.UserVO[]>([]) // 所有用户列表
+const filteredUserList = ref<UserApi.UserVO[]>([]) // 当前部门过滤后的用户列表
+const selectedUserIdList: any = ref([]) // 选中的用户列表
+const dialogVisible = ref(false) // 弹窗的是否展示
+const formLoading = ref(false) // 表单的加载中
+const activityId = ref()
+
+/** 计算属性:合并已选择的用户和当前部门过滤后的用户 */
+const transferUserList = computed(() => {
+  // 1.1 获取所有已选择的用户
+  const selectedUsers = userList.value.filter((user: any) =>
+    selectedUserIdList.value.includes(user.id)
+  )
+
+  // 1.2 获取当前部门过滤后的未选择用户
+  const filteredUnselectedUsers = filteredUserList.value.filter(
+    (user: any) => !selectedUserIdList.value.includes(user.id)
+  )
+
+  // 2. 合并并去重
+  return [...selectedUsers, ...filteredUnselectedUsers]
+})
+
+/** 打开弹窗 */
+const open = async (id: number, selectedList?: any[]) => {
+  activityId.value = id
+  resetForm()
+
+  // 加载部门、用户列表
+  deptTree.value = handleTree(await DeptApi.getSimpleDeptList())
+  userList.value = await UserApi.getSimpleUserList()
+
+  // 初始状态下,过滤列表等于所有用户列表
+  filteredUserList.value = [...userList.value]
+  selectedUserIdList.value = selectedList?.map((item: any) => item.id) || []
+  dialogVisible.value = true
+}
+
+/** 获取部门过滤后的用户列表 */
+const getUserList = async (deptId?: number) => {
+  formLoading.value = true
+  try {
+    // @ts-ignore
+    // TODO @芋艿:替换到 simple List 暂不支持 deptId 过滤
+    // TODO @Zqqq:这个,可以使用前端过滤么?通过 deptList 获取到 deptId 子节点,然后去 userList
+    const data = await UserApi.getUserPage({ pageSize: 100, pageNo: 1, deptId })
+    // 更新过滤后的用户列表
+    filteredUserList.value = data.list
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 提交选择 */
+const submitForm = async () => {
+  try {
+    message.success(t('common.updateSuccess'))
+    dialogVisible.value = false
+    // 从所有用户列表中筛选出已选择的用户
+    const emitUserList = userList.value.filter((user: any) =>
+      selectedUserIdList.value.includes(user.id)
+    )
+    // 发送操作成功的事件
+    emit('confirm', activityId.value, emitUserList)
+  } finally {
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  deptTree.value = []
+  userList.value = []
+  filteredUserList.value = []
+  selectedUserIdList.value = []
+}
+
+/** 处理部门被点击 */
+const handleNodeClick = (row: { [key: string]: any }) => {
+  getUserList(row.id)
+}
+
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+</script>
+
+<style lang="scss" scoped>
+:deep() {
+  .el-transfer {
+    display: flex;
+  }
+  .el-transfer__buttons {
+    display: flex !important;
+    flex-direction: column-reverse;
+    justify-content: center;
+    gap: 20px;
+    .el-transfer__button:nth-child(2) {
+      margin: 0;
+    }
+  }
+}
+</style>
diff --git a/src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue b/src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue
index d8f921a..6cbe11f 100644
--- a/src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue
+++ b/src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue
@@ -664,7 +664,7 @@
     previewModelVisible.value = true
   })
 }
-/* ------------------------------------------------ 工业互联网平台 methods ------------------------------------------------------ */
+/* ------------------------------------------------ 芋道源码 methods ------------------------------------------------------ */
 const processSave = async () => {
   // console.log(bpmnModeler, 'bpmnModelerbpmnModelerbpmnModelerbpmnModeler')
   const { err, xml } = await bpmnModeler.saveXML()
diff --git a/src/components/bpmnProcessDesigner/package/designer/ProcessViewer.vue b/src/components/bpmnProcessDesigner/package/designer/ProcessViewer.vue
index 485b979..34a54c8 100644
--- a/src/components/bpmnProcessDesigner/package/designer/ProcessViewer.vue
+++ b/src/components/bpmnProcessDesigner/package/designer/ProcessViewer.vue
@@ -1,664 +1,379 @@
 <template>
-  <div class="my-process-designer">
-    <div class="my-process-designer__container">
-      <div class="my-process-designer__canvas" style="height: 760px" ref="bpmnCanvas"></div>
+  <div class="process-viewer">
+    <div style="height: 100%" ref="processCanvas" v-show="!isLoading"> </div>
+    <!-- 自定义箭头样式,用于已完成状态下流程连线箭头 -->
+    <defs ref="customDefs">
+      <marker
+        id="sequenceflow-end-white-success"
+        viewBox="0 0 20 20"
+        refX="11"
+        refY="10"
+        markerWidth="10"
+        markerHeight="10"
+        orient="auto"
+      >
+        <path
+          class="success-arrow"
+          d="M 1 5 L 11 10 L 1 15 Z"
+          style="stroke-width: 1px; stroke-linecap: round; stroke-dasharray: 10000, 1"
+        />
+      </marker>
+      <marker
+        id="conditional-flow-marker-white-success"
+        viewBox="0 0 20 20"
+        refX="-1"
+        refY="10"
+        markerWidth="10"
+        markerHeight="10"
+        orient="auto"
+      >
+        <path
+          class="success-conditional"
+          d="M 0 10 L 8 6 L 16 10 L 8 14 Z"
+          style="stroke-width: 1px; stroke-linecap: round; stroke-dasharray: 10000, 1"
+        />
+      </marker>
+    </defs>
+
+    <!-- 审批记录 -->
+    <el-dialog :title="dialogTitle || '审批记录'" v-model="dialogVisible" width="1000px">
+      <el-row>
+        <el-table
+          :data="selectTasks"
+          size="small"
+          border
+          header-cell-class-name="table-header-gray"
+        >
+          <el-table-column
+            label="序号"
+            header-align="center"
+            align="center"
+            type="index"
+            width="50"
+          />
+          <el-table-column
+            label="审批人"
+            min-width="100"
+            align="center"
+            v-if="selectActivityType === 'bpmn:UserTask'"
+          >
+            <template #default="scope">
+              {{ scope.row.assigneeUser?.nickname || scope.row.ownerUser?.nickname }}
+            </template>
+          </el-table-column>
+          <el-table-column
+            label="发起人"
+            prop="assigneeUser.nickname"
+            min-width="100"
+            align="center"
+            v-else
+          />
+          <el-table-column label="部门" min-width="100" align="center">
+            <template #default="scope">
+              {{ scope.row.assigneeUser?.deptName || scope.row.ownerUser?.deptName }}
+            </template>
+          </el-table-column>
+          <el-table-column
+            :formatter="dateFormatter"
+            align="center"
+            label="开始时间"
+            prop="createTime"
+            min-width="140"
+          />
+          <el-table-column
+            :formatter="dateFormatter"
+            align="center"
+            label="结束时间"
+            prop="endTime"
+            min-width="140"
+          />
+          <el-table-column align="center" label="审批状态" prop="status" min-width="90">
+            <template #default="scope">
+              <dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="scope.row.status" />
+            </template>
+          </el-table-column>
+          <el-table-column
+            align="center"
+            label="审批建议"
+            prop="reason"
+            min-width="120"
+            v-if="selectActivityType === 'bpmn:UserTask'"
+          />
+          <el-table-column align="center" label="耗时" prop="durationInMillis" width="100">
+            <template #default="scope">
+              {{ formatPast2(scope.row.durationInMillis) }}
+            </template>
+          </el-table-column>
+        </el-table>
+      </el-row>
+    </el-dialog>
+
+    <!-- Zoom:放大、缩小 -->
+    <div style="position: absolute; top: 0; left: 0; width: 100%">
+      <el-row type="flex" justify="end">
+        <el-button-group key="scale-control" size="default">
+          <el-button
+            size="default"
+            :plain="true"
+            :disabled="defaultZoom <= 0.3"
+            :icon="ZoomOut"
+            @click="processZoomOut()"
+          />
+          <el-button size="default" style="width: 90px">
+            {{ Math.floor(defaultZoom * 10 * 10) + '%' }}
+          </el-button>
+          <el-button
+            size="default"
+            :plain="true"
+            :disabled="defaultZoom >= 3.9"
+            :icon="ZoomIn"
+            @click="processZoomIn()"
+          />
+          <el-button size="default" :icon="ScaleToOriginal" @click="processReZoom()" />
+        </el-button-group>
+      </el-row>
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
+import '../theme/index.scss'
 import BpmnViewer from 'bpmn-js/lib/Viewer'
-import DefaultEmptyXML from './plugins/defaultEmpty'
-import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
-import { formatDate } from '@/utils/formatTime'
-import { isEmpty } from '@/utils/is'
-
-defineOptions({ name: 'MyProcessViewer' })
+import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas'
+import { ZoomOut, ZoomIn, ScaleToOriginal } from '@element-plus/icons-vue'
+import { DICT_TYPE } from '@/utils/dict'
+import { dateFormatter, formatPast2 } from '@/utils/formatTime'
+import { BpmProcessInstanceStatus } from '@/utils/constants'
 
 const props = defineProps({
-  value: {
-    // BPMN XML 字符串
+  xml: {
     type: String,
-    default: ''
+    required: true
   },
-  prefix: {
-    // 使用哪个引擎
-    type: String,
-    default: 'camunda'
-  },
-  activityData: {
-    // 活动的数据。传递时,可高亮流程
-    type: Array,
-    default: () => []
-  },
-  processInstanceData: {
-    // 流程实例的数据。传递时,可展示流程发起人等信息
+  view: {
     type: Object,
-    default: () => {}
-  },
-  taskData: {
-    // 任务实例的数据。传递时,可展示 UserTask 审核相关的信息
-    type: Array,
-    default: () => []
+    require: true
   }
 })
 
-provide('configGlobal', props)
+const processCanvas = ref()
+const bpmnViewer = ref<BpmnViewer | null>(null)
+const customDefs = ref()
+const defaultZoom = ref(1) // 默认缩放比例
+const isLoading = ref(false) // 是否加载中
 
-const emit = defineEmits(['destroy'])
+const processInstance = ref<any>({}) // 流程实例
+const tasks = ref([]) // 流程任务
 
-let bpmnModeler
+const dialogVisible = ref(false) // 弹窗可见性
+const dialogTitle = ref<string | undefined>(undefined) // 弹窗标题
+const selectActivityType = ref<string | undefined>(undefined) // 选中 Task 的活动编号
+const selectTasks = ref<any[]>([]) // 选中的任务数组
 
-const xml = ref('')
-const activityLists = ref<any[]>([])
-const processInstance = ref<any>(undefined)
-const taskList = ref<any[]>([])
-const bpmnCanvas = ref()
-// const element = ref()
-const elementOverlayIds = ref<any>(null)
-const overlays = ref<any>(null)
-
-const initBpmnModeler = () => {
-  if (bpmnModeler) return
-  bpmnModeler = new BpmnViewer({
-    container: bpmnCanvas.value,
-    bpmnRenderer: {}
-  })
+/** Zoom:恢复 */
+const processReZoom = () => {
+  defaultZoom.value = 1
+  bpmnViewer.value?.get('canvas').zoom('fit-viewport', 'auto')
 }
 
-/* 创建新的流程图 */
-const createNewDiagram = async (xml) => {
-  // 将字符串转换成图显示出来
-  let newId = `Process_${new Date().getTime()}`
-  let newName = `业务流程_${new Date().getTime()}`
-  let xmlString = xml || DefaultEmptyXML(newId, newName, props.prefix)
-  try {
-    let { warnings } = await bpmnModeler.importXML(xmlString)
-    if (warnings && warnings.length) {
-      warnings.forEach((warn) => console.warn(warn))
-    }
-    // 高亮流程图
-    await highlightDiagram()
-    const canvas = bpmnModeler.get('canvas')
-    canvas.zoom('fit-viewport', 'auto')
-  } catch (e) {
-    console.error(e)
-    // console.error(`[Process Designer Warn]: ${e?.message || e}`);
+/** Zoom:放大 */
+const processZoomIn = (zoomStep = 0.1) => {
+  let newZoom = Math.floor(defaultZoom.value * 100 + zoomStep * 100) / 100
+  if (newZoom > 4) {
+    throw new Error('[Process Designer Warn ]: The zoom ratio cannot be greater than 4')
   }
+  defaultZoom.value = newZoom
+  bpmnViewer.value?.get('canvas').zoom(defaultZoom.value)
 }
 
-/* 高亮流程图 */
-// TODO 芋艿:如果多个 endActivity 的话,目前的逻辑可能有一定的问题。https://www.jdon.com/workflow/multi-events.html
-const highlightDiagram = async () => {
-  const activityList = activityLists.value
-  if (activityList.length === 0) {
+/** Zoom:缩小 */
+const processZoomOut = (zoomStep = 0.1) => {
+  let newZoom = Math.floor(defaultZoom.value * 100 - zoomStep * 100) / 100
+  if (newZoom < 0.2) {
+    throw new Error('[Process Designer Warn ]: The zoom ratio cannot be less than 0.2')
+  }
+  defaultZoom.value = newZoom
+  bpmnViewer.value?.get('canvas').zoom(defaultZoom.value)
+}
+
+/** 流程图预览清空 */
+const clearViewer = () => {
+  if (processCanvas.value) {
+    processCanvas.value.innerHTML = ''
+  }
+  if (bpmnViewer.value) {
+    bpmnViewer.value.destroy()
+  }
+  bpmnViewer.value = null
+}
+
+/** 添加自定义箭头 */
+// TODO 芋艿:自定义箭头不生效,有点奇怪!!!!相关的 marker-end、marker-start 暂时也注释了!!!
+const addCustomDefs = () => {
+  if (!bpmnViewer.value) {
     return
   }
-  // 参考自 https://gitee.com/tony2y/RuoYi-flowable/blob/master/ruoyi-ui/src/components/Process/index.vue#L222 实现
-  // 再次基础上,增加不同审批结果的颜色等等
-  let canvas = bpmnModeler.get('canvas')
-  let todoActivity: any = activityList.find((m: any) => !m.endTime) // 找到待办的任务
-  let endActivity: any = activityList[activityList.length - 1] // 获得最后一个任务
-  let findProcessTask = false //是否已经高亮了进行中的任务
-  //进行中高亮之后的任务 key 集合,用于过滤掉 taskList 进行中后面的任务,避免进行中后面的数据 Hover 还有数据
-  let removeTaskDefinitionKeyList = []
-  // debugger
-  bpmnModeler.getDefinitions().rootElements[0].flowElements?.forEach((n: any) => {
-    let activity: any = activityList.find((m: any) => m.key === n.id) // 找到对应的活动
-    if (!activity) {
-      return
+  const canvas = bpmnViewer.value?.get('canvas')
+  const svg = canvas?._svg
+  svg.appendChild(customDefs.value)
+}
+
+/** 节点选中 */
+const onSelectElement = (element: any) => {
+  // 清空原选中
+  selectActivityType.value = undefined
+  dialogTitle.value = undefined
+  if (!element || !processInstance.value?.id) {
+    return
+  }
+
+  // UserTask 的情况
+  const activityType = element.type
+  selectActivityType.value = activityType
+  if (activityType === 'bpmn:UserTask') {
+    dialogTitle.value = element.businessObject ? element.businessObject.name : undefined
+    selectTasks.value = tasks.value.filter((item: any) => item?.taskDefinitionKey === element.id)
+    dialogVisible.value = true
+  } else if (activityType === 'bpmn:EndEvent' || activityType === 'bpmn:StartEvent') {
+    dialogTitle.value = '审批信息'
+    selectTasks.value = [
+      {
+        assigneeUser: processInstance.value.startUser,
+        createTime: processInstance.value.startTime,
+        endTime: processInstance.value.endTime,
+        status: processInstance.value.status,
+        durationInMillis: processInstance.value.durationInMillis
+      }
+    ]
+    dialogVisible.value = true
+  }
+}
+
+/** 初始化 BPMN 视图 */
+const importXML = async (xml: string) => {
+  // 清空流程图
+  clearViewer()
+
+  // 初始化流程图
+  if (xml != null && xml !== '') {
+    try {
+      bpmnViewer.value = new BpmnViewer({
+        additionalModules: [MoveCanvasModule],
+        container: processCanvas.value
+      })
+      // 增加点击事件
+      bpmnViewer.value.on('element.click', ({ element }) => {
+        onSelectElement(element)
+      })
+
+      // 初始化 BPMN 视图
+      isLoading.value = true
+      await bpmnViewer.value.importXML(xml)
+      // 自定义成功的箭头
+      addCustomDefs()
+    } catch (e) {
+      clearViewer()
+    } finally {
+      isLoading.value = false
+      // 高亮流程
+      setProcessStatus(props.view)
     }
-    if (n.$type === 'bpmn:UserTask') {
-      // 用户任务
-      // 处理用户任务的高亮
-      const task: any = taskList.value.find((m: any) => m.id === activity.taskId) // 找到活动对应的 taskId
-      if (!task) {
-        return
-      }
-      // 进行中的任务已经高亮过了,则不高亮后面的任务了
-      if (findProcessTask) {
-        removeTaskDefinitionKeyList.push(n.id)
-        return
-      }
-      // 高亮任务
-      canvas.addMarker(n.id, getResultCss(task.status))
-      //标记是否高亮了进行中任务
-      if (task.status === 1) {
-        findProcessTask = true
-      }
-      // 如果非通过,就不走后面的线条了
-      if (task.status !== 2) {
-        return
-      }
-      // 处理 outgoing 出线
-      const outgoing = getActivityOutgoing(activity)
-      outgoing?.forEach((nn: any) => {
-        // debugger
-        let targetActivity: any = activityList.find((m: any) => m.key === nn.targetRef.id)
-        // 如果目标活动存在,则根据该活动是否结束,进行【bpmn:SequenceFlow】连线的高亮设置
-        if (targetActivity) {
-          canvas.addMarker(nn.id, targetActivity.endTime ? 'highlight' : 'highlight-todo')
-        } else if (nn.targetRef.$type === 'bpmn:ExclusiveGateway') {
-          // TODO 芋艿:这个流程,暂时没走到过
-          canvas.addMarker(nn.id, activity.endTime ? 'highlight' : 'highlight-todo')
-          canvas.addMarker(nn.targetRef.id, activity.endTime ? 'highlight' : 'highlight-todo')
-        } else if (nn.targetRef.$type === 'bpmn:EndEvent') {
-          // TODO 芋艿:这个流程,暂时没走到过
-          if (!todoActivity && endActivity.key === n.id) {
-            canvas.addMarker(nn.id, 'highlight')
-            canvas.addMarker(nn.targetRef.id, 'highlight')
-          }
-          if (!activity.endTime) {
-            canvas.addMarker(nn.id, 'highlight-todo')
-            canvas.addMarker(nn.targetRef.id, 'highlight-todo')
-          }
+  }
+}
+
+/** 高亮流程 */
+const setProcessStatus = (view: any) => {
+  // 设置相关变量
+  if (!view || !view.processInstance) {
+    return
+  }
+  processInstance.value = view.processInstance
+  tasks.value = view.tasks
+  if (isLoading.value || !bpmnViewer.value) {
+    return
+  }
+  const {
+    unfinishedTaskActivityIds,
+    finishedTaskActivityIds,
+    finishedSequenceFlowActivityIds,
+    rejectedTaskActivityIds
+  } = view
+  const canvas = bpmnViewer.value.get('canvas')
+  const elementRegistry = bpmnViewer.value.get('elementRegistry')
+
+  // 已完成节点
+  if (Array.isArray(finishedSequenceFlowActivityIds)) {
+    finishedSequenceFlowActivityIds.forEach((item: any) => {
+      if (item != null) {
+        canvas.addMarker(item, 'success')
+        const element = elementRegistry.get(item)
+        const conditionExpression = element.businessObject.conditionExpression
+        if (conditionExpression) {
+          canvas.addMarker(item, 'condition-expression')
         }
-      })
-    } else if (n.$type === 'bpmn:ExclusiveGateway') {
-      // 排它网关
-      // 设置【bpmn:ExclusiveGateway】排它网关的高亮
-      canvas.addMarker(n.id, getActivityHighlightCss(activity))
-      // 查找需要高亮的连线
-      let matchNN: any = undefined
-      let matchActivity: any = undefined
-      n.outgoing?.forEach((nn: any) => {
-        let targetActivity = activityList.find((m: any) => m.key === nn.targetRef.id)
-        if (!targetActivity) {
-          return
-        }
-        // 特殊判断 endEvent 类型的原因,ExclusiveGateway 可能后续连有 2 个路径:
-        //  1. 一个是 UserTask => EndEvent
-        //  2. 一个是 EndEvent
-        // 在选择路径 1 时,其实 EndEvent 可能也存在,导致 1 和 2 都高亮,显然是不正确的。
-        // 所以,在 matchActivity 为 EndEvent 时,需要进行覆盖~~
-        if (!matchActivity || matchActivity.type === 'endEvent') {
-          matchNN = nn
-          matchActivity = targetActivity
-        }
-      })
-      if (matchNN && matchActivity) {
-        canvas.addMarker(matchNN.id, getActivityHighlightCss(matchActivity))
       }
-    } else if (n.$type === 'bpmn:ParallelGateway') {
-      // 并行网关
-      // 设置【bpmn:ParallelGateway】并行网关的高亮
-      canvas.addMarker(n.id, getActivityHighlightCss(activity))
-      n.outgoing?.forEach((nn: any) => {
-        // 获得连线是否有指向目标。如果有,则进行高亮
-        const targetActivity = activityList.find((m: any) => m.key === nn.targetRef.id)
-        if (targetActivity) {
-          canvas.addMarker(nn.id, getActivityHighlightCss(targetActivity)) // 高亮【bpmn:SequenceFlow】连线
-          // 高亮【...】目标。其中 ... 可以是 bpm:UserTask、也可以是其它的。当然,如果是 bpm:UserTask 的话,其实不做高亮也没问题,因为上面有逻辑做了这块。
-          canvas.addMarker(nn.targetRef.id, getActivityHighlightCss(targetActivity))
-        }
-      })
-    } else if (n.$type === 'bpmn:StartEvent') {
-      // 开始节点
-      canvas.addMarker(n.id, 'highlight')
-      n.outgoing?.forEach((nn) => {
-        // outgoing 例如说【bpmn:SequenceFlow】连线
-        // 获得连线是否有指向目标。如果有,则进行高亮
-        let targetActivity = activityList.find((m: any) => m.key === nn.targetRef.id)
-        if (targetActivity) {
-          canvas.addMarker(nn.id, 'highlight') // 高亮【bpmn:SequenceFlow】连线
-          canvas.addMarker(n.id, 'highlight') // 高亮【bpmn:StartEvent】开始节点(自己)
-        }
-      })
-    } else if (n.$type === 'bpmn:EndEvent') {
-      // 结束节点
-      if (!processInstance.value || processInstance.value.status === 1) {
-        return
+    })
+  }
+  if (Array.isArray(finishedTaskActivityIds)) {
+    finishedTaskActivityIds.forEach((item: any) => canvas.addMarker(item, 'success'))
+  }
+
+  // 未完成节点
+  if (Array.isArray(unfinishedTaskActivityIds)) {
+    unfinishedTaskActivityIds.forEach((item: any) => canvas.addMarker(item, 'primary'))
+  }
+
+  // 被拒绝节点
+  if (Array.isArray(rejectedTaskActivityIds)) {
+    rejectedTaskActivityIds.forEach((item: any) => {
+      if (item != null) {
+        canvas.addMarker(item, 'danger')
       }
-      canvas.addMarker(n.id, getResultCss(processInstance.value.status))
-    } else if (n.$type === 'bpmn:ServiceTask') {
-      //服务任务
-      if (activity.startTime > 0 && activity.endTime === 0) {
-        //进入执行,标识进行色
-        canvas.addMarker(n.id, getResultCss(1))
-      }
-      if (activity.endTime > 0) {
-        // 执行完成,节点标识完成色, 所有outgoing标识完成色。
-        canvas.addMarker(n.id, getResultCss(2))
-        const outgoing = getActivityOutgoing(activity)
-        outgoing?.forEach((out) => {
-          canvas.addMarker(out.id, getResultCss(2))
-        })
-      }
-    } else if (n.$type === 'bpmn:SequenceFlow') {
-      let targetActivity = activityList.find((m: any) => m.key === n.targetRef.id)
-      if (targetActivity) {
-        canvas.addMarker(n.id, getActivityHighlightCss(targetActivity))
-      }
-    }
-  })
-  if (!isEmpty(removeTaskDefinitionKeyList)) {
-    taskList.value = taskList.value.filter(
-      (item) => !removeTaskDefinitionKeyList.includes(item.taskDefinitionKey)
+    })
+  }
+
+  // 特殊:处理 end 节点的高亮。因为 end 在拒绝、取消时,被后端计算成了 finishedTaskActivityIds 里
+  if (
+    [BpmProcessInstanceStatus.CANCEL, BpmProcessInstanceStatus.REJECT].includes(
+      processInstance.value.status
     )
-  }
-}
-
-const getActivityHighlightCss = (activity) => {
-  return activity.endTime ? 'highlight' : 'highlight-todo'
-}
-
-const getResultCss = (status) => {
-  if (status === 1) {
-    // 审批中
-    return 'highlight-todo'
-  } else if (status === 2) {
-    // 已通过
-    return 'highlight'
-  } else if (status === 3) {
-    // 不通过
-    return 'highlight-reject'
-  } else if (status === 4) {
-    // 已取消
-    return 'highlight-cancel'
-  } else if (status === 5) {
-    // 退回
-    return 'highlight-return'
-  } else if (status === 6) {
-    // 委派
-    return 'highlight-todo'
-  } else if (status === 7) {
-    // 审批通过中
-    return 'highlight-todo'
-  } else if (status === 0) {
-    // 待审批
-    return 'highlight-todo'
-  }
-  return ''
-}
-
-const getActivityOutgoing = (activity) => {
-  // 如果有 outgoing,则直接使用它
-  if (activity.outgoing && activity.outgoing.length > 0) {
-    return activity.outgoing
-  }
-  // 如果没有,则遍历获得起点为它的【bpmn:SequenceFlow】节点们。原因是:bpmn-js 的 UserTask 拿不到 outgoing
-  const flowElements = bpmnModeler.getDefinitions().rootElements[0].flowElements
-  const outgoing: any[] = []
-  flowElements.forEach((item: any) => {
-    if (item.$type !== 'bpmn:SequenceFlow') {
-      return
-    }
-    if (item.sourceRef.id === activity.key) {
-      outgoing.push(item)
-    }
-  })
-  return outgoing
-}
-const initModelListeners = () => {
-  const EventBus = bpmnModeler.get('eventBus')
-  // 注册需要的监听事件
-  EventBus.on('element.hover', function (eventObj) {
-    let element = eventObj ? eventObj.element : null
-    elementHover(element)
-  })
-  EventBus.on('element.out', function (eventObj) {
-    let element = eventObj ? eventObj.element : null
-    elementOut(element)
-  })
-}
-// 流程图的元素被 hover
-const elementHover = (element) => {
-  element.value = element
-  !elementOverlayIds.value && (elementOverlayIds.value = {})
-  !overlays.value && (overlays.value = bpmnModeler.get('overlays'))
-  // 展示信息
-  // console.log(activityLists.value, 'activityLists.value')
-  // console.log(element.value, 'element.value')
-  const activity = activityLists.value.find((m) => m.key === element.value.id)
-  // console.log(activity, 'activityactivityactivityactivity')
-  if (!activity) {
-    return
-  }
-  if (!elementOverlayIds.value[element.value.id] && element.value.type !== 'bpmn:Process') {
-    let html = `<div class="element-overlays">
-            <p>Elemet id: ${element.value.id}</p>
-            <p>Elemet type: ${element.value.type}</p>
-          </div>` // 默认值
-    if (element.value.type === 'bpmn:StartEvent' && processInstance.value) {
-      html = `<p>发起人:${processInstance.value.startUser.nickname}</p>
-                  <p>部门:${processInstance.value.startUser.deptName}</p>
-                  <p>创建时间:${formatDate(processInstance.value.createTime)}`
-    } else if (element.value.type === 'bpmn:UserTask') {
-      let task = taskList.value.find((m) => m.id === activity.taskId) // 找到活动对应的 taskId
-      if (!task) {
-        return
+  ) {
+    const endNodes = elementRegistry.filter((element: any) => element.type === 'bpmn:EndEvent')
+    endNodes.forEach((item: any) => {
+      canvas.removeMarker(item.id, 'success')
+      if (processInstance.value.status === BpmProcessInstanceStatus.CANCEL) {
+        canvas.addMarker(item.id, 'cancel')
+      } else {
+        canvas.addMarker(item.id, 'danger')
       }
-      let optionData = getIntDictOptions(DICT_TYPE.BPM_TASK_STATUS)
-      let dataResult = ''
-      optionData.forEach((element) => {
-        if (element.value == task.status) {
-          dataResult = element.label
-        }
-      })
-      html = `<p>审批人:${task.assigneeUser.nickname}</p>
-                  <p>部门:${task.assigneeUser.deptName}</p>
-                  <p>结果:${dataResult}</p>
-                  <p>创建时间:${formatDate(task.createTime)}</p>`
-      // html = `<p>审批人:${task.assigneeUser.nickname}</p>
-      //             <p>部门:${task.assigneeUser.deptName}</p>
-      //             <p>结果:${getIntDictOptions(
-      //               DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT,
-      //               task.status
-      //             )}</p>
-      //             <p>创建时间:${formatDate(task.createTime)}</p>`
-      if (task.endTime) {
-        html += `<p>结束时间:${formatDate(task.endTime)}</p>`
-      }
-      if (task.reason) {
-        html += `<p>审批建议:${task.reason}</p>`
-      }
-    } else if (element.value.type === 'bpmn:ServiceTask' && processInstance.value) {
-      if (activity.startTime > 0) {
-        html = `<p>创建时间:${formatDate(activity.startTime)}</p>`
-      }
-      if (activity.endTime > 0) {
-        html += `<p>结束时间:${formatDate(activity.endTime)}</p>`
-      }
-      console.log(html)
-    } else if (element.value.type === 'bpmn:EndEvent' && processInstance.value) {
-      let optionData = getIntDictOptions(DICT_TYPE.BPM_TASK_STATUS)
-      let dataResult = ''
-      optionData.forEach((element) => {
-        if (element.value == processInstance.value.status) {
-          dataResult = element.label
-        }
-      })
-      html = `<p>结果:${dataResult}</p>`
-      // html = `<p>结果:${getIntDictOptions(
-      //   DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT,
-      //   processInstance.value.status
-      // )}</p>`
-      if (processInstance.value.endTime) {
-        html += `<p>结束时间:${formatDate(processInstance.value.endTime)}</p>`
-      }
-    }
-    // console.log(html, 'html111111111111111')
-    elementOverlayIds.value[element.value.id] = toRaw(overlays.value)?.add(element.value, {
-      position: { left: 0, bottom: 0 },
-      html: `<div class="element-overlays">${html}</div>`
     })
   }
 }
 
-// 流程图的元素被 out
-const elementOut = (element) => {
-  toRaw(overlays.value).remove({ element })
-  elementOverlayIds.value[element.id] = null
-}
+watch(
+  () => props.xml,
+  (newXml) => {
+    importXML(newXml)
+  },
+  { immediate: true }
+)
 
+watch(
+  () => props.view,
+  (newView) => {
+    setProcessStatus(newView)
+  },
+  { immediate: true }
+)
+
+/** mounted:初始化 */
 onMounted(() => {
-  xml.value = props.value
-  activityLists.value = props.activityData
-  // 初始化
-  initBpmnModeler()
-  createNewDiagram(xml.value)
-  // 初始模型的监听器
-  initModelListeners()
+  importXML(props.xml)
+  setProcessStatus(props.view)
 })
 
+/** unmount:销毁 */
 onBeforeUnmount(() => {
-  // this.$once('hook:beforeDestroy', () => {
-  // })
-  if (bpmnModeler) bpmnModeler.destroy()
-  emit('destroy', bpmnModeler)
-  bpmnModeler = null
+  clearViewer()
 })
-
-watch(
-  () => props.value,
-  (newValue) => {
-    xml.value = newValue
-    createNewDiagram(xml.value)
-  }
-)
-watch(
-  () => props.activityData,
-  (newActivityData) => {
-    activityLists.value = newActivityData
-    createNewDiagram(xml.value)
-  }
-)
-watch(
-  () => props.processInstanceData,
-  (newProcessInstanceData) => {
-    processInstance.value = newProcessInstanceData
-    createNewDiagram(xml.value)
-  }
-)
-watch(
-  () => props.taskData,
-  (newTaskListData) => {
-    taskList.value = newTaskListData
-    createNewDiagram(xml.value)
-  }
-)
 </script>
-
-<style lang="scss">
-/** 处理中 */
-.highlight-todo.djs-connection > .djs-visual > path {
-  stroke: #1890ff !important;
-  stroke-dasharray: 4px !important;
-  fill-opacity: 0.2 !important;
-}
-
-.highlight-todo.djs-shape .djs-visual > :nth-child(1) {
-  fill: #1890ff !important;
-  stroke: #1890ff !important;
-  stroke-dasharray: 4px !important;
-  fill-opacity: 0.2 !important;
-}
-
-:deep(.highlight-todo.djs-connection > .djs-visual > path) {
-  stroke: #1890ff !important;
-  stroke-dasharray: 4px !important;
-  fill-opacity: 0.2 !important;
-  marker-end: url('#sequenceflow-end-_E7DFDF-_E7DFDF-803g1kf6zwzmcig1y2ulm5egr');
-}
-
-:deep(.highlight-todo.djs-shape .djs-visual > :nth-child(1)) {
-  fill: #1890ff !important;
-  stroke: #1890ff !important;
-  stroke-dasharray: 4px !important;
-  fill-opacity: 0.2 !important;
-}
-
-/** 通过 */
-.highlight.djs-shape .djs-visual > :nth-child(1) {
-  fill: green !important;
-  stroke: green !important;
-  fill-opacity: 0.2 !important;
-}
-
-.highlight.djs-shape .djs-visual > :nth-child(2) {
-  fill: green !important;
-}
-
-.highlight.djs-shape .djs-visual > path {
-  fill: green !important;
-  fill-opacity: 0.2 !important;
-  stroke: green !important;
-}
-
-.highlight.djs-connection > .djs-visual > path {
-  stroke: green !important;
-}
-
-.highlight:not(.djs-connection) .djs-visual > :nth-child(1) {
-  fill: green !important; /* color elements as green */
-}
-
-:deep(.highlight.djs-shape .djs-visual > :nth-child(1)) {
-  fill: green !important;
-  stroke: green !important;
-  fill-opacity: 0.2 !important;
-}
-
-:deep(.highlight.djs-shape .djs-visual > :nth-child(2)) {
-  fill: green !important;
-}
-
-:deep(.highlight.djs-shape .djs-visual > path) {
-  fill: green !important;
-  fill-opacity: 0.2 !important;
-  stroke: green !important;
-}
-
-:deep(.highlight.djs-connection > .djs-visual > path) {
-  stroke: green !important;
-}
-
-.djs-element.highlight > .djs-visual > path {
-  stroke: green !important;
-}
-
-/** 不通过 */
-.highlight-reject.djs-shape .djs-visual > :nth-child(1) {
-  fill: red !important;
-  stroke: red !important;
-  fill-opacity: 0.2 !important;
-}
-
-.highlight-reject.djs-shape .djs-visual > :nth-child(2) {
-  fill: red !important;
-}
-
-.highlight-reject.djs-shape .djs-visual > path {
-  fill: red !important;
-  fill-opacity: 0.2 !important;
-  stroke: red !important;
-}
-
-.highlight-reject.djs-connection > .djs-visual > path {
-  stroke: red !important;
-  marker-end: url(#sequenceflow-end-white-success) !important;
-}
-
-.highlight-reject:not(.djs-connection) .djs-visual > :nth-child(1) {
-  fill: red !important; /* color elements as green */
-}
-
-:deep(.highlight-reject.djs-shape .djs-visual > :nth-child(1)) {
-  fill: red !important;
-  stroke: red !important;
-  fill-opacity: 0.2 !important;
-}
-
-:deep(.highlight-reject.djs-shape .djs-visual > :nth-child(2)) {
-  fill: red !important;
-}
-
-:deep(.highlight-reject.djs-shape .djs-visual > path) {
-  fill: red !important;
-  fill-opacity: 0.2 !important;
-  stroke: red !important;
-}
-
-:deep(.highlight-reject.djs-connection > .djs-visual > path) {
-  stroke: red !important;
-}
-
-/** 已取消 */
-.highlight-cancel.djs-shape .djs-visual > :nth-child(1) {
-  fill: grey !important;
-  stroke: grey !important;
-  fill-opacity: 0.2 !important;
-}
-
-.highlight-cancel.djs-shape .djs-visual > :nth-child(2) {
-  fill: grey !important;
-}
-
-.highlight-cancel.djs-shape .djs-visual > path {
-  fill: grey !important;
-  fill-opacity: 0.2 !important;
-  stroke: grey !important;
-}
-
-.highlight-cancel.djs-connection > .djs-visual > path {
-  stroke: grey !important;
-}
-
-.highlight-cancel:not(.djs-connection) .djs-visual > :nth-child(1) {
-  fill: grey !important; /* color elements as green */
-}
-
-:deep(.highlight-cancel.djs-shape .djs-visual > :nth-child(1)) {
-  fill: grey !important;
-  stroke: grey !important;
-  fill-opacity: 0.2 !important;
-}
-
-:deep(.highlight-cancel.djs-shape .djs-visual > :nth-child(2)) {
-  fill: grey !important;
-}
-
-:deep(.highlight-cancel.djs-shape .djs-visual > path) {
-  fill: grey !important;
-  fill-opacity: 0.2 !important;
-  stroke: grey !important;
-}
-
-:deep(.highlight-cancel.djs-connection > .djs-visual > path) {
-  stroke: grey !important;
-}
-
-/** 回退 */
-.highlight-return.djs-shape .djs-visual > :nth-child(1) {
-  fill: #e6a23c !important;
-  stroke: #e6a23c !important;
-  fill-opacity: 0.2 !important;
-}
-
-.highlight-return.djs-shape .djs-visual > :nth-child(2) {
-  fill: #e6a23c !important;
-}
-
-.highlight-return.djs-shape .djs-visual > path {
-  fill: #e6a23c !important;
-  fill-opacity: 0.2 !important;
-  stroke: #e6a23c !important;
-}
-
-.highlight-return.djs-connection > .djs-visual > path {
-  stroke: #e6a23c !important;
-}
-
-.highlight-return:not(.djs-connection) .djs-visual > :nth-child(1) {
-  fill: #e6a23c !important; /* color elements as green */
-}
-
-:deep(.highlight-return.djs-shape .djs-visual > :nth-child(1)) {
-  fill: #e6a23c !important;
-  stroke: #e6a23c !important;
-  fill-opacity: 0.2 !important;
-}
-
-:deep(.highlight-return.djs-shape .djs-visual > :nth-child(2)) {
-  fill: #e6a23c !important;
-}
-
-:deep(.highlight-return.djs-shape .djs-visual > path) {
-  fill: #e6a23c !important;
-  fill-opacity: 0.2 !important;
-  stroke: #e6a23c !important;
-}
-
-:deep(.highlight-return.djs-connection > .djs-visual > path) {
-  stroke: #e6a23c !important;
-}
-
-.element-overlays {
-  width: 200px;
-  padding: 8px;
-  color: #fafafa;
-  background: rgb(0 0 0 / 60%);
-  border-radius: 4px;
-  box-sizing: border-box;
-}
-</style>
diff --git a/src/components/bpmnProcessDesigner/package/designer/plugins/descriptor/flowableDescriptor.json b/src/components/bpmnProcessDesigner/package/designer/plugins/descriptor/flowableDescriptor.json
index 4ea632a..d1ca4a4 100644
--- a/src/components/bpmnProcessDesigner/package/designer/plugins/descriptor/flowableDescriptor.json
+++ b/src/components/bpmnProcessDesigner/package/designer/plugins/descriptor/flowableDescriptor.json
@@ -1211,6 +1211,76 @@
           "isAttr": true
         }
       ]
+    },
+    {
+      "name": "AssignStartUserHandlerType",
+      "superClass": ["Element"],
+      "meta": {
+        "allowedIn": ["bpmn:StartEvent", "bpmn:UserTask"]
+      },
+      "properties": [
+      {
+        "name": "value",
+        "type": "Integer",
+        "isBody": true
+      }
+      ]
+    },
+    {
+      "name": "RejectHandlerType",
+      "superClass": ["Element"],
+      "meta": {
+        "allowedIn": ["bpmn:UserTask"]
+      },
+      "properties": [
+        {
+          "name": "value",
+          "type": "Integer",
+          "isBody": true
+        }
+      ]
+    },
+    {
+      "name": "RejectReturnTaskId",
+      "superClass": ["Element"],
+      "meta": {
+        "allowedIn": ["bpmn:UserTask"]
+      },
+      "properties": [
+        {
+          "name": "value",
+          "type": "String",
+          "isBody": true
+        }
+      ]
+    },
+    {
+      "name": "AssignEmptyHandlerType",
+      "superClass": ["Element"],
+      "meta": {
+        "allowedIn": ["bpmn:UserTask"]
+      },
+      "properties": [
+        {
+          "name": "value",
+          "type": "Integer",
+          "isBody": true
+        }
+      ]
+    },
+    {
+      "name": "AssignEmptyUserIds",
+      "superClass": ["Element"],
+      "meta": {
+        "allowedIn": ["bpmn:UserTask"]
+      },
+      "properties": [
+        {
+          "name": "value",
+          "type": "String",
+          "isBody": true
+        }
+      ]
     }
   ],
   "emumerations": []
diff --git a/src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue b/src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue
index 86a1cf7..96e8b08 100644
--- a/src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue
+++ b/src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue
@@ -1,5 +1,5 @@
 <template>
-  <div class="process-panel__container" :style="{ width: `${width}px` }">
+  <div class="process-panel__container" :style="{ width: `${width}px`,maxHeight: '700px' }">
     <el-collapse v-model="activeTab">
       <el-collapse-item name="base">
         <!-- class="panel-tab__title" -->
@@ -54,6 +54,10 @@
         <template #title><Icon icon="ep:promotion" />其他</template>
         <element-other-config :id="elementId" />
       </el-collapse-item>
+      <el-collapse-item name="customConfig" v-if="elementType.indexOf('Task') !== -1" key="customConfig">
+        <template #title><Icon icon="ep:circle-plus-filled" />自定义配置</template>
+        <element-custom-config :id="elementId" :type="elementType" />
+      </el-collapse-item>
     </el-collapse>
   </div>
 </template>
diff --git a/src/components/bpmnProcessDesigner/package/penal/custom-config/ElementCustomConfig.vue b/src/components/bpmnProcessDesigner/package/penal/custom-config/ElementCustomConfig.vue
new file mode 100644
index 0000000..e5497b0
--- /dev/null
+++ b/src/components/bpmnProcessDesigner/package/penal/custom-config/ElementCustomConfig.vue
@@ -0,0 +1,283 @@
+<!-- UserTask 自定义配置:
+     1. 审批人与提交人为同一人时
+     2. 审批人拒绝时
+     3. 审批人为空时
+-->
+<template>
+  <div class="panel-tab__content">
+    <el-divider content-position="left">审批人拒绝时</el-divider>
+    <el-form-item prop="rejectHandlerType">
+      <el-radio-group
+        v-model="rejectHandlerType"
+        :disabled="returnTaskList.length === 0"
+        @change="updateRejectHandlerType"
+      >
+        <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>
+      </el-radio-group>
+    </el-form-item>
+    <el-form-item
+      v-if="rejectHandlerType == RejectHandlerType.RETURN_USER_TASK"
+      label="驳回节点"
+      prop="returnNodeId"
+    >
+      <el-select v-model="returnNodeId" clearable style="width: 100%" @change="updateReturnNodeId">
+        <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 prop="assignEmptyHandlerType">
+      <el-radio-group v-model="assignEmptyHandlerType" @change="updateAssignEmptyHandlerType">
+        <div class="flex-col">
+          <div v-for="(item, index) in ASSIGN_EMPTY_HANDLER_TYPES" :key="index">
+            <el-radio :key="item.value" :value="item.value" :label="item.label" />
+          </div>
+        </div>
+      </el-radio-group>
+    </el-form-item>
+    <el-form-item
+      v-if="assignEmptyHandlerType == AssignEmptyHandlerType.ASSIGN_USER"
+      label="指定用户"
+      prop="assignEmptyHandlerUserIds"
+      span="24"
+    >
+      <el-select
+        v-model="assignEmptyUserIds"
+        clearable
+        multiple
+        style="width: 100%"
+        @change="updateAssignEmptyUserIds"
+      >
+        <el-option
+          v-for="item in userOptions"
+          :key="item.id"
+          :label="item.nickname"
+          :value="item.id"
+        />
+      </el-select>
+    </el-form-item>
+
+    <el-divider content-position="left">审批人与提交人为同一人时</el-divider>
+    <el-radio-group v-model="assignStartUserHandlerType" @change="updateAssignStartUserHandlerType">
+      <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>
+    </el-radio-group>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import {
+  ASSIGN_START_USER_HANDLER_TYPES,
+  RejectHandlerType,
+  REJECT_HANDLER_TYPES,
+  ASSIGN_EMPTY_HANDLER_TYPES,
+  AssignEmptyHandlerType
+} from '@/components/SimpleProcessDesignerV2/src/consts'
+import * as UserApi from '@/api/system/user'
+
+defineOptions({ name: 'ElementCustomConfig' })
+const props = defineProps({
+  id: String,
+  type: String
+})
+const prefix = inject('prefix')
+
+// 审批人与提交人为同一人时
+const assignStartUserHandlerTypeEl = ref()
+const assignStartUserHandlerType = ref()
+
+// 审批人拒绝时
+const rejectHandlerTypeEl = ref()
+const rejectHandlerType = ref()
+const returnNodeIdEl = ref()
+const returnNodeId = ref()
+const returnTaskList = ref([])
+
+// 审批人为空时
+const assignEmptyHandlerTypeEl = ref()
+const assignEmptyHandlerType = ref()
+const assignEmptyUserIdsEl = ref()
+const assignEmptyUserIds = ref()
+
+const elExtensionElements = ref()
+const otherExtensions = ref()
+const bpmnElement = ref()
+const bpmnInstances = () => (window as any)?.bpmnInstances
+
+const resetCustomConfigList = () => {
+  bpmnElement.value = bpmnInstances().bpmnElement
+
+  // 获取可回退的列表
+  returnTaskList.value = findAllPredecessorsExcludingStart(
+    bpmnElement.value.id,
+    bpmnInstances().modeler
+  )
+
+  // 获取元素扩展属性 或者 创建扩展属性
+  elExtensionElements.value =
+    bpmnElement.value.businessObject?.extensionElements ??
+    bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] })
+
+  // 审批人与提交人为同一人时
+  assignStartUserHandlerTypeEl.value =
+    elExtensionElements.value.values?.filter(
+      (ex) => ex.$type === `${prefix}:AssignStartUserHandlerType`
+    )?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignStartUserHandlerType`, { value: 1 })
+  assignStartUserHandlerType.value = assignStartUserHandlerTypeEl.value.value
+
+  // 审批人拒绝时
+  rejectHandlerTypeEl.value =
+    elExtensionElements.value.values?.filter(
+      (ex) => ex.$type === `${prefix}:RejectHandlerType`
+    )?.[0] || bpmnInstances().moddle.create(`${prefix}:RejectHandlerType`, { value: 1 })
+  rejectHandlerType.value = rejectHandlerTypeEl.value.value
+  returnNodeIdEl.value =
+    elExtensionElements.value.values?.filter(
+      (ex) => ex.$type === `${prefix}:RejectReturnTaskId`
+    )?.[0] || bpmnInstances().moddle.create(`${prefix}:RejectReturnTaskId`, { value: '' })
+  returnNodeId.value = returnNodeIdEl.value.value
+
+  // 审批人为空时
+  assignEmptyHandlerTypeEl.value =
+    elExtensionElements.value.values?.filter(
+      (ex) => ex.$type === `${prefix}:AssignEmptyHandlerType`
+    )?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignEmptyHandlerType`, { value: 1 })
+  assignEmptyHandlerType.value = assignEmptyHandlerTypeEl.value.value
+  assignEmptyUserIdsEl.value =
+    elExtensionElements.value.values?.filter(
+      (ex) => ex.$type === `${prefix}:AssignEmptyUserIds`
+    )?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignEmptyUserIds`, { value: '' })
+  assignEmptyUserIds.value = assignEmptyUserIdsEl.value.value.split(',').map((item) => {
+    // 如果数字超出了最大安全整数范围,则将其作为字符串处理
+    let num = Number(item)
+    return num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER ? item : num
+  })
+
+  // 保留剩余扩展元素,便于后面更新该元素对应属性
+  otherExtensions.value =
+    elExtensionElements.value.values?.filter(
+      (ex) =>
+        ex.$type !== `${prefix}:AssignStartUserHandlerType` &&
+        ex.$type !== `${prefix}:RejectHandlerType` &&
+        ex.$type !== `${prefix}:RejectReturnTaskId` &&
+        ex.$type !== `${prefix}:AssignEmptyHandlerType` &&
+        ex.$type !== `${prefix}:AssignEmptyUserIds`
+    ) ?? []
+
+  // 更新元素扩展属性,避免后续报错
+  updateElementExtensions()
+}
+
+const updateAssignStartUserHandlerType = () => {
+  assignStartUserHandlerTypeEl.value.value = assignStartUserHandlerType.value
+
+  updateElementExtensions()
+}
+
+const updateRejectHandlerType = () => {
+  rejectHandlerTypeEl.value.value = rejectHandlerType.value
+
+  returnNodeId.value = returnTaskList.value[0].id
+  returnNodeIdEl.value.value = returnNodeId.value
+
+  updateElementExtensions()
+}
+
+const updateReturnNodeId = () => {
+  returnNodeIdEl.value.value = returnNodeId.value
+
+  updateElementExtensions()
+}
+
+const updateAssignEmptyHandlerType = () => {
+  assignEmptyHandlerTypeEl.value.value = assignEmptyHandlerType.value
+
+  updateElementExtensions()
+}
+
+const updateAssignEmptyUserIds = () => {
+  assignEmptyUserIdsEl.value.value = assignEmptyUserIds.value.toString()
+
+  updateElementExtensions()
+}
+
+const updateElementExtensions = () => {
+  const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
+    values: [
+      ...otherExtensions.value,
+      assignStartUserHandlerTypeEl.value,
+      rejectHandlerTypeEl.value,
+      returnNodeIdEl.value,
+      assignEmptyHandlerTypeEl.value,
+      assignEmptyUserIdsEl.value
+    ]
+  })
+  bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
+    extensionElements: extensions
+  })
+}
+
+watch(
+  () => props.id,
+  (val) => {
+    val &&
+      val.length &&
+      nextTick(() => {
+        resetCustomConfigList()
+      })
+  },
+  { immediate: true }
+)
+
+function findAllPredecessorsExcludingStart(elementId, modeler) {
+  const elementRegistry = modeler.get('elementRegistry')
+  const allConnections = elementRegistry.filter((element) => element.type === 'bpmn:SequenceFlow')
+  const predecessors = new Set() // 使用 Set 来避免重复节点
+
+  // 检查是否是开始事件节点
+  function isStartEvent(element) {
+    return element.type === 'bpmn:StartEvent'
+  }
+
+  function findPredecessorsRecursively(element) {
+    // 获取与当前节点相连的所有连接
+    const incomingConnections = allConnections.filter((connection) => connection.target === element)
+
+    incomingConnections.forEach((connection) => {
+      const source = connection.source // 获取前置节点
+
+      // 只添加不是开始事件的前置节点
+      if (!isStartEvent(source)) {
+        predecessors.add(source.businessObject)
+        // 递归查找前置节点
+        findPredecessorsRecursively(source)
+      }
+    })
+  }
+
+  const targetElement = elementRegistry.get(elementId)
+  if (targetElement) {
+    findPredecessorsRecursively(targetElement)
+  }
+
+  return Array.from(predecessors) // 返回前置节点数组
+}
+
+const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
+onMounted(async () => {
+  // 获得用户列表
+  userOptions.value = await UserApi.getSimpleUserList()
+})
+</script>
diff --git a/src/components/bpmnProcessDesigner/package/penal/form/ElementForm.vue b/src/components/bpmnProcessDesigner/package/penal/form/ElementForm.vue
index 33f0bc0..3bb7d66 100644
--- a/src/components/bpmnProcessDesigner/package/penal/form/ElementForm.vue
+++ b/src/components/bpmnProcessDesigner/package/penal/form/ElementForm.vue
@@ -268,9 +268,9 @@
 const resetFormList = () => {
   bpmnELement.value = bpmnInstances().bpmnElement
   formKey.value = bpmnELement.value.businessObject.formKey
-  if (formKey.value?.length > 0) {
-    formKey.value = parseInt(formKey.value)
-  }
+  // if (formKey.value?.length > 0) {
+  //   formKey.value = parseInt(formKey.value)
+  // }
   // 获取元素扩展属性 或者 创建扩展属性
   elExtensionElements.value =
     bpmnELement.value.businessObject.get('extensionElements') ||
diff --git a/src/components/bpmnProcessDesigner/package/penal/listeners/ElementListeners.vue b/src/components/bpmnProcessDesigner/package/penal/listeners/ElementListeners.vue
index c557b59..de5445c 100644
--- a/src/components/bpmnProcessDesigner/package/penal/listeners/ElementListeners.vue
+++ b/src/components/bpmnProcessDesigner/package/penal/listeners/ElementListeners.vue
@@ -370,7 +370,7 @@
 }
 // 移除监听器
 const removeListener = (index) => {
-  // debugger
+  debugger
   ElMessageBox.confirm('确认移除该监听器吗?', '提示', {
     confirmButtonText: '确 认',
     cancelButtonText: '取 消'
diff --git a/src/components/bpmnProcessDesigner/package/penal/multi-instance/ElementMultiInstance.vue b/src/components/bpmnProcessDesigner/package/penal/multi-instance/ElementMultiInstance.vue
index c0ec1ca..7cd16f7 100644
--- a/src/components/bpmnProcessDesigner/package/penal/multi-instance/ElementMultiInstance.vue
+++ b/src/components/bpmnProcessDesigner/package/penal/multi-instance/ElementMultiInstance.vue
@@ -45,17 +45,20 @@
           <el-checkbox
             v-model="loopInstanceForm.asyncBefore"
             label="异步前"
+            value="异步前"
             @change="updateLoopAsync('asyncBefore')"
           />
           <el-checkbox
             v-model="loopInstanceForm.asyncAfter"
             label="异步后"
+            value="异步后"
             @change="updateLoopAsync('asyncAfter')"
           />
           <el-checkbox
             v-model="loopInstanceForm.exclusive"
             v-if="loopInstanceForm.asyncAfter || loopInstanceForm.asyncBefore"
             label="排除"
+            value="排除"
             @change="updateLoopAsync('exclusive')"
           />
         </el-form-item>
diff --git a/src/components/bpmnProcessDesigner/package/penal/properties/ElementProperties.vue b/src/components/bpmnProcessDesigner/package/penal/properties/ElementProperties.vue
index 494b3d9..016cdf6 100644
--- a/src/components/bpmnProcessDesigner/package/penal/properties/ElementProperties.vue
+++ b/src/components/bpmnProcessDesigner/package/penal/properties/ElementProperties.vue
@@ -80,7 +80,7 @@
   otherExtensionList.value = [] // 其他扩展配置
   bpmnElementProperties.value =
     // bpmnElement.value.businessObject?.extensionElements?.filter((ex) => {
-    bpmnElement.value.businessObject?.extensionElements?.values.filter((ex) => {
+    bpmnElement.value.businessObject?.extensionElements?.values?.filter((ex) => {
       if (ex.$type !== `${prefix}:Properties`) {
         otherExtensionList.value.push(ex)
       }
diff --git a/src/components/bpmnProcessDesigner/package/penal/task/ElementTask.vue b/src/components/bpmnProcessDesigner/package/penal/task/ElementTask.vue
index e808af3..bbeeb4c 100644
--- a/src/components/bpmnProcessDesigner/package/penal/task/ElementTask.vue
+++ b/src/components/bpmnProcessDesigner/package/penal/task/ElementTask.vue
@@ -6,13 +6,20 @@
         <el-checkbox
           v-model="taskConfigForm.asyncBefore"
           label="异步前"
+          value="异步前"
           @change="changeTaskAsync"
         />
-        <el-checkbox v-model="taskConfigForm.asyncAfter" label="异步后" @change="changeTaskAsync" />
+        <el-checkbox
+          v-model="taskConfigForm.asyncAfter"
+          label="异步后"
+          value="异步后"
+          @change="changeTaskAsync"
+        />
         <el-checkbox
           v-model="taskConfigForm.exclusive"
           v-if="taskConfigForm.asyncAfter || taskConfigForm.asyncBefore"
           label="排除"
+          value="排除"
           @change="changeTaskAsync"
         />
       </el-form-item>
diff --git a/src/components/bpmnProcessDesigner/package/theme/element-variables.scss b/src/components/bpmnProcessDesigner/package/theme/element-variables.scss
index 49bd326..0646f8e 100644
--- a/src/components/bpmnProcessDesigner/package/theme/element-variables.scss
+++ b/src/components/bpmnProcessDesigner/package/theme/element-variables.scss
@@ -5,7 +5,7 @@
 /* 改变 icon 字体路径变量,必需 */
 $--font-path: '~element-ui/lib/theme-chalk/fonts';
 
-@import '~element-ui/packages/theme-chalk/src/index';
+@use '~element-ui/packages/theme-chalk/src/index';
 
 .el-table td,
 .el-table th {
diff --git a/src/components/bpmnProcessDesigner/package/theme/index.scss b/src/components/bpmnProcessDesigner/package/theme/index.scss
index 2e60fad..2404760 100644
--- a/src/components/bpmnProcessDesigner/package/theme/index.scss
+++ b/src/components/bpmnProcessDesigner/package/theme/index.scss
@@ -1,2 +1,117 @@
-@import './process-designer.scss';
-@import './process-panel.scss';
+@use './process-designer.scss';
+@use './process-panel.scss';
+
+$success-color: #4eb819;
+$primary-color: #409EFF;
+$danger-color: #F56C6C;
+$cancel-color: #909399;
+
+.process-viewer {
+  position: relative;
+  border: 1px solid #EFEFEF;
+  background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+') repeat!important;
+
+  .success-arrow {
+    fill: $success-color;
+    stroke: $success-color;
+  }
+
+  .success-conditional {
+    fill: white;
+    stroke: $success-color;
+  }
+
+  .success.djs-connection {
+    .djs-visual path {
+      stroke: $success-color!important;
+      //marker-end: url(#sequenceflow-end-white-success)!important;
+    }
+  }
+
+  .success.djs-connection.condition-expression {
+    .djs-visual path {
+      //marker-start: url(#conditional-flow-marker-white-success)!important;
+    }
+  }
+
+  .success.djs-shape {
+    .djs-visual rect {
+      stroke: $success-color!important;
+      fill: $success-color!important;
+      fill-opacity: 0.15!important;
+    }
+
+    .djs-visual polygon {
+      stroke: $success-color!important;
+    }
+
+    .djs-visual path:nth-child(2) {
+      stroke: $success-color!important;
+      fill: $success-color!important;
+    }
+
+    .djs-visual circle {
+      stroke: $success-color!important;
+      fill: $success-color!important;
+      fill-opacity: 0.15!important;
+    }
+  }
+
+  .primary.djs-shape {
+    .djs-visual rect {
+      stroke: $primary-color!important;
+      fill: $primary-color!important;
+      fill-opacity: 0.15!important;
+    }
+
+    .djs-visual polygon {
+      stroke: $primary-color!important;
+    }
+
+    .djs-visual circle {
+      stroke: $primary-color!important;
+      fill: $primary-color!important;
+      fill-opacity: 0.15!important;
+    }
+  }
+
+  .danger.djs-shape {
+    .djs-visual rect {
+      stroke: $danger-color!important;
+      fill: $danger-color!important;
+      fill-opacity: 0.15!important;
+    }
+
+    .djs-visual polygon {
+      stroke: $danger-color!important;
+    }
+
+    .djs-visual circle {
+      stroke: $danger-color!important;
+      fill: $danger-color!important;
+      fill-opacity: 0.15!important;
+    }
+  }
+
+  .cancel.djs-shape {
+    .djs-visual rect {
+      stroke: $cancel-color!important;
+      fill: $cancel-color!important;
+      fill-opacity: 0.15!important;
+    }
+
+    .djs-visual polygon {
+      stroke: $cancel-color!important;
+    }
+
+    .djs-visual circle {
+      stroke: $cancel-color!important;
+      fill: $cancel-color!important;
+      fill-opacity: 0.15!important;
+    }
+  }
+}
+
+.process-viewer .djs-tooltip-container, .process-viewer .djs-overlay-container, .process-viewer .djs-palette {
+  display: none;
+}
diff --git a/src/components/bpmnProcessDesigner/package/theme/process-designer.scss b/src/components/bpmnProcessDesigner/package/theme/process-designer.scss
index 6af945d..b1ff0af 100644
--- a/src/components/bpmnProcessDesigner/package/theme/process-designer.scss
+++ b/src/components/bpmnProcessDesigner/package/theme/process-designer.scss
@@ -1,6 +1,6 @@
-@import 'bpmn-js-token-simulation/assets/css/bpmn-js-token-simulation.css';
-@import 'bpmn-js-token-simulation/assets/css/font-awesome.min.css';
-@import 'bpmn-js-token-simulation/assets/css/normalize.css';
+@use 'bpmn-js-token-simulation/assets/css/bpmn-js-token-simulation.css';
+@use 'bpmn-js-token-simulation/assets/css/font-awesome.min.css';
+@use 'bpmn-js-token-simulation/assets/css/normalize.css';
 
 // 边框被 token-simulation 样式覆盖了
 .djs-palette {
diff --git a/src/components/bpmnProcessDesigner/package/utils.ts b/src/components/bpmnProcessDesigner/package/utils.ts
index a7de5f0..8996788 100644
--- a/src/components/bpmnProcessDesigner/package/utils.ts
+++ b/src/components/bpmnProcessDesigner/package/utils.ts
@@ -2,7 +2,7 @@
 const bpmnInstances = () => (window as any)?.bpmnInstances
 // 创建监听器实例
 export function createListenerObject(options, isTask, prefix) {
-  // debugger
+  debugger
   const listenerObj = Object.create(null)
   listenerObj.event = options.event
   isTask && (listenerObj.id = options.id) // 任务监听器特有的 id 字段
diff --git a/src/layout/components/AppView.vue b/src/layout/components/AppView.vue
index 4434187..df720a1 100644
--- a/src/layout/components/AppView.vue
+++ b/src/layout/components/AppView.vue
@@ -36,27 +36,10 @@
 <template>
   <section
     :class="[
-      'p-[var(--app-content-padding)] w-[calc(100%-var(--app-content-padding)-var(--app-content-padding))] bg-[var(--app-content-bg-color)] dark:bg-[var(--el-bg-color)]',
+      'p-[var(--app-content-padding)] w-full bg-[var(--app-content-bg-color)] dark:bg-[var(--el-bg-color)]',
       {
-        '!min-h-[calc(100%-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height))]':
-          (fixedHeader &&
-            (layout === 'classic' || layout === 'topLeft' || layout === 'top') &&
-            footer) ||
-          (!tagsView && layout === 'top' && footer),
-        '!min-h-[calc(100%-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height)-var(--tags-view-height))]':
-          tagsView && layout === 'top' && footer,
-
-        '!min-h-[calc(100%-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-var(--top-tool-height)-var(--app-footer-height))]':
-          !fixedHeader && layout === 'classic' && footer,
-
-        '!min-h-[calc(100%-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height))]':
-          !fixedHeader && layout === 'topLeft' && footer,
-
-        '!min-h-[calc(100%-var(--top-tool-height)-var(--app-content-padding)-var(--app-content-padding))]':
-          fixedHeader && layout === 'cutMenu' && footer,
-
-        '!min-h-[calc(100%-var(--top-tool-height)-var(--app-content-padding)-var(--app-content-padding)-var(--tags-view-height))]':
-          !fixedHeader && layout === 'cutMenu' && footer
+        '!min-h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))] pb-0':
+          footer
       }
     ]"
   >
diff --git a/src/layout/components/Breadcrumb/src/Breadcrumb.vue b/src/layout/components/Breadcrumb/src/Breadcrumb.vue
index 4079a06..80770a8 100644
--- a/src/layout/components/Breadcrumb/src/Breadcrumb.vue
+++ b/src/layout/components/Breadcrumb/src/Breadcrumb.vue
@@ -92,7 +92,7 @@
 $prefix-cls: #{$elNamespace}-breadcrumb;
 
 .#{$prefix-cls} {
-  :deep(&__item) {
+  :deep(.#{$prefix-cls}__item) {
     display: flex;
     .#{$prefix-cls}__inner {
       display: flex;
@@ -105,7 +105,7 @@
     }
   }
 
-  :deep(&__item):not(:last-child) {
+  :deep(.#{$prefix-cls}__item):not(:last-child) {
     .#{$prefix-cls}__inner {
       color: var(--top-header-text-color);
 
@@ -115,7 +115,7 @@
     }
   }
 
-  :deep(&__item):last-child {
+  :deep(.#{$prefix-cls}__item):last-child {
     .#{$prefix-cls}__inner {
       display: flex;
       align-items: center;
diff --git a/src/layout/components/Footer/src/Footer.vue b/src/layout/components/Footer/src/Footer.vue
index 5510159..62302fc 100644
--- a/src/layout/components/Footer/src/Footer.vue
+++ b/src/layout/components/Footer/src/Footer.vue
@@ -17,8 +17,8 @@
 <template>
   <div
     :class="prefixCls"
-    class="h-[var(--app-footer-height)] bg-[var(--app-content-bg-color)] text-center leading-[var(--app-footer-height)] text-[var(--el-text-color-placeholder)] dark:bg-[var(--el-bg-color)]"
+    class="h-[var(--app-footer-height)] bg-[var(--app-content-bg-color)] text-center leading-[var(--app-footer-height)] text-[var(--el-text-color-placeholder)] dark:bg-[var(--el-bg-color)] overflow-hidden"
   >
-    <span class="text-14px">Copyright ©2024-{{ title }}</span>
+    <span class="text-14px">Copyright ©2022-{{ title }}</span>
   </div>
 </template>
diff --git a/src/layout/components/Menu/src/Menu.vue b/src/layout/components/Menu/src/Menu.vue
index 466cca5..94a1da4 100644
--- a/src/layout/components/Menu/src/Menu.vue
+++ b/src/layout/components/Menu/src/Menu.vue
@@ -90,6 +90,11 @@
           backgroundColor="var(--left-menu-bg-color)"
           textColor="var(--left-menu-text-color)"
           activeTextColor="var(--left-menu-text-active-color)"
+          popperClass={
+            unref(menuMode) === 'vertical'
+              ? `${prefixCls}-popper--vertical`
+              : `${prefixCls}-popper--horizontal`
+          }
           onSelect={menuSelect}
         >
           {{
@@ -190,6 +195,16 @@
     }
   }
 
+  // 垂直菜单
+  &__vertical {
+    :deep(.#{$elNamespace}-menu--vertical) {
+      &:not(.#{$elNamespace}-menu--collapse) .#{$elNamespace}-sub-menu__title,
+      .#{$elNamespace}-menu-item {
+        padding-right: 0;
+      }
+    }
+  }
+
   // 水平菜单
   &__horizontal {
     height: calc(var(--top-tool-height)) !important;
diff --git a/src/layout/components/Message/src/Message.vue b/src/layout/components/Message/src/Message.vue
index 6bd7724..d769d88 100644
--- a/src/layout/components/Message/src/Message.vue
+++ b/src/layout/components/Message/src/Message.vue
@@ -1,10 +1,12 @@
 <script lang="ts" setup>
 import { formatDate } from '@/utils/formatTime'
 import * as NotifyMessageApi from '@/api/system/notify/message'
+import { useUserStoreWithOut } from '@/store/modules/user'
 
 defineOptions({ name: 'Message' })
 
 const { push } = useRouter()
+const userStore = useUserStoreWithOut()
 const activeName = ref('notice')
 const unreadCount = ref(0) // 未读消息数量
 const list = ref<any[]>([]) // 消息列表
@@ -37,7 +39,11 @@
   // 轮询刷新小红点
   setInterval(
     () => {
-      getUnreadCount()
+      if (userStore.getIsSetUser) {
+        getUnreadCount()
+      } else {
+        unreadCount.value = 0
+      }
     },
     1000 * 60 * 2
   )
diff --git a/src/layout/components/Setting/src/Setting.vue b/src/layout/components/Setting/src/Setting.vue
index e1908b6..2973674 100644
--- a/src/layout/components/Setting/src/Setting.vue
+++ b/src/layout/components/Setting/src/Setting.vue
@@ -126,8 +126,10 @@
       message: ${appStore.getMessage},
       // 标签页
       tagsView: ${appStore.getTagsView},
+      // 标签页
+      tagsViewImmerse: ${appStore.getTagsViewImmerse},
       // 标签页图标
-      getTagsViewIcon: ${appStore.getTagsViewIcon},
+      tagsViewIcon: ${appStore.getTagsViewIcon},
       // logo
       logo: ${appStore.getLogo},
       // 菜单手风琴
@@ -295,5 +297,6 @@
 
 .#{$prefix-cls} {
   border-radius: 6px 0 0 6px;
+  z-index: 1200;/*修正没有z-index会被表格层覆盖,值不要超过4000*/
 }
 </style>
diff --git a/src/layout/components/TabMenu/src/TabMenu.vue b/src/layout/components/TabMenu/src/TabMenu.vue
index b70464c..efad6a6 100644
--- a/src/layout/components/TabMenu/src/TabMenu.vue
+++ b/src/layout/components/TabMenu/src/TabMenu.vue
@@ -139,7 +139,7 @@
         id={`${variables.namespace}-menu`}
         class={[
           prefixCls,
-          'relative bg-[var(--left-menu-bg-color)] top-1px layout-border__right',
+          'relative bg-[var(--left-menu-bg-color)] layout-border__right',
           {
             'w-[var(--tab-menu-max-width)]': !unref(collapse),
             'w-[var(--tab-menu-min-width)]': unref(collapse)
@@ -147,7 +147,7 @@
         ]}
         onMouseleave={mouseleave}
       >
-        <ElScrollbar class="!h-[calc(100%-var(--tab-menu-collapse-height)-1px)]">
+        <ElScrollbar class="!h-[calc(100%-var(--tab-menu-collapse-height))]">
           <div>
             {() => {
               return unref(tabRouters).map((v) => {
@@ -199,7 +199,7 @@
             {
               '!left-[var(--tab-menu-min-width)]': unref(collapse),
               '!left-[var(--tab-menu-max-width)]': !unref(collapse),
-              '!w-[calc(var(--left-menu-max-width)+1px)]': unref(showMenu) || unref(fixedMenu),
+              '!w-[var(--left-menu-max-width)]': unref(showMenu) || unref(fixedMenu),
               '!w-0': !unref(showMenu) && !unref(fixedMenu)
             }
           ]}
diff --git a/src/layout/components/TagsView/src/TagsView.vue b/src/layout/components/TagsView/src/TagsView.vue
index 7db0cf6..dcbb90f 100644
--- a/src/layout/components/TagsView/src/TagsView.vue
+++ b/src/layout/components/TagsView/src/TagsView.vue
@@ -1,7 +1,7 @@
 <script lang="ts" setup>
-import { onMounted, watch, computed, unref, ref, nextTick } from 'vue'
-import { useRouter } from 'vue-router'
+import { computed, nextTick, onMounted, ref, unref, watch } from 'vue'
 import type { RouteLocationNormalizedLoaded, RouterLinkProps } from 'vue-router'
+import { useRouter } from 'vue-router'
 import { usePermissionStore } from '@/store/modules/permission'
 import { useTagsViewStore } from '@/store/modules/tagsView'
 import { useAppStore } from '@/store/modules/app'
@@ -32,6 +32,8 @@
 const affixTagArr = ref<RouteLocationNormalizedLoaded[]>([])
 
 const appStore = useAppStore()
+
+const tagsViewImmerse = computed(() => appStore.getTagsViewImmerse)
 
 const tagsViewIcon = computed(() => appStore.getTagsViewIcon)
 
@@ -125,12 +127,8 @@
 const moveToCurrentTag = async () => {
   await nextTick()
   for (const v of unref(visitedViews)) {
-    if (v.fullPath === unref(currentRoute).path) {
+    if (v.fullPath === unref(currentRoute).fullPath) {
       moveToTarget(v)
-      if (v.fullPath !== unref(currentRoute).fullPath) {
-        tagsViewStore.updateVisitedView(unref(currentRoute))
-      }
-
       break
     }
   }
@@ -205,7 +203,7 @@
 
 // 是否是当前tag
 const isActive = (route: RouteLocationNormalizedLoaded): boolean => {
-  return route.path === unref(currentRoute).path
+  return route.fullPath === unref(currentRoute).fullPath
 }
 
 // 所有右键菜单组件的元素
@@ -266,21 +264,33 @@
     class="relative w-full flex bg-[#fff] dark:bg-[var(--el-bg-color)]"
   >
     <span
-      :class="`${prefixCls}__tool ${prefixCls}__tool--first`"
+      :class="tagsViewImmerse ? '' : `${prefixCls}__tool ${prefixCls}__tool--first`"
       class="h-[var(--tags-view-height)] w-[var(--tags-view-height)] flex cursor-pointer items-center justify-center"
       @click="move(-200)"
     >
       <Icon
-        icon="ep:d-arrow-left"
-        color="var(--el-text-color-placeholder)"
         :hover-color="isDark ? '#fff' : 'var(--el-color-black)'"
+        color="var(--el-text-color-placeholder)"
+        icon="ep:d-arrow-left"
       />
     </span>
     <div class="flex-1 overflow-hidden">
       <ElScrollbar ref="scrollbarRef" class="h-full" @scroll="scroll">
-        <div class="h-full flex">
+        <div class="h-[var(--tags-view-height)] flex">
           <ContextMenu
+            v-for="item in visitedViews"
+            :key="item.fullPath"
             :ref="itemRefs.set"
+            :class="[
+              `${prefixCls}__item`,
+              tagsViewImmerse ? `${prefixCls}__item--immerse` : '',
+              tagsViewIcon ? `${prefixCls}__item--icon` : '',
+              tagsViewImmerse && tagsViewIcon ? `${prefixCls}__item--immerse--icon` : '',
+              item?.meta?.affix ? `${prefixCls}__item--affix` : '',
+              {
+                'is-active': isActive(item)
+              }
+            ]"
             :schema="[
               {
                 icon: 'ep:refresh',
@@ -338,41 +348,36 @@
                 }
               }
             ]"
-            v-for="item in visitedViews"
-            :key="item.fullPath"
             :tag-item="item"
-            :class="[
-              `${prefixCls}__item`,
-              item?.meta?.affix ? `${prefixCls}__item--affix` : '',
-              {
-                'is-active': isActive(item)
-              }
-            ]"
             @visible-change="visibleChange"
           >
             <div>
-              <router-link :ref="tagLinksRefs.set" :to="{ ...item }" custom v-slot="{ navigate }">
+              <router-link :ref="tagLinksRefs.set" v-slot="{ navigate }" :to="{ ...item }" custom>
                 <div
+                  :class="`h-full flex items-center justify-center whitespace-nowrap pl-15px ${prefixCls}__item--label`"
                   @click="navigate"
-                  class="h-full flex items-center justify-center whitespace-nowrap pl-15px"
                 >
                   <Icon
                     v-if="
-                      item?.matched &&
-                      item?.matched[1] &&
-                      item?.matched[1]?.meta?.icon &&
-                      tagsViewIcon
+                      tagsViewIcon &&
+                      (item?.meta?.icon ||
+                        (item?.matched &&
+                          item.matched[0] &&
+                          item.matched[item.matched.length - 1].meta?.icon))
                     "
-                    :icon="item?.matched[1]?.meta?.icon"
+                    :icon="item?.meta?.icon || item.matched[item.matched.length - 1].meta.icon"
                     :size="12"
                     class="mr-5px"
                   />
-                  {{ t(item?.meta?.title as string) }}
+                  {{
+                    t(item?.meta?.title as string) +
+                    (item?.meta?.titleSuffix ? ` (${item?.meta?.titleSuffix})` : '')
+                  }}
                   <Icon
                     :class="`${prefixCls}__item--close`"
+                    :size="12"
                     color="#333"
                     icon="ep:close"
-                    :size="12"
                     @click.prevent.stop="closeSelectedTag(item)"
                   />
                 </div>
@@ -383,29 +388,28 @@
       </ElScrollbar>
     </div>
     <span
-      :class="`${prefixCls}__tool`"
+      :class="tagsViewImmerse ? '' : `${prefixCls}__tool`"
       class="h-[var(--tags-view-height)] w-[var(--tags-view-height)] flex cursor-pointer items-center justify-center"
       @click="move(200)"
     >
       <Icon
-        icon="ep:d-arrow-right"
-        color="var(--el-text-color-placeholder)"
         :hover-color="isDark ? '#fff' : 'var(--el-color-black)'"
+        color="var(--el-text-color-placeholder)"
+        icon="ep:d-arrow-right"
       />
     </span>
     <span
-      :class="`${prefixCls}__tool`"
+      :class="tagsViewImmerse ? '' : `${prefixCls}__tool`"
       class="h-[var(--tags-view-height)] w-[var(--tags-view-height)] flex cursor-pointer items-center justify-center"
       @click="refreshSelectedTag(selectedTag)"
     >
       <Icon
-        icon="ep:refresh-right"
-        color="var(--el-text-color-placeholder)"
         :hover-color="isDark ? '#fff' : 'var(--el-color-black)'"
+        color="var(--el-text-color-placeholder)"
+        icon="ep:refresh-right"
       />
     </span>
     <ContextMenu
-      trigger="click"
       :schema="[
         {
           icon: 'ep:refresh',
@@ -457,15 +461,16 @@
           }
         }
       ]"
+      trigger="click"
     >
       <span
-        :class="`${prefixCls}__tool`"
+        :class="tagsViewImmerse ? '' : `${prefixCls}__tool`"
         class="block h-[var(--tags-view-height)] w-[var(--tags-view-height)] flex cursor-pointer items-center justify-center"
       >
         <Icon
-          icon="ep:menu"
-          color="var(--el-text-color-placeholder)"
           :hover-color="isDark ? '#fff' : 'var(--el-color-black)'"
+          color="var(--el-text-color-placeholder)"
+          icon="ep:menu"
         />
       </span>
     </ContextMenu>
@@ -485,10 +490,10 @@
 
     &::before {
       position: absolute;
-      top: 1px;
+      top: 0;
       left: 0;
       width: 100%;
-      height: calc(100% - 1px);
+      height: 100%;
       border-left: 1px solid var(--el-border-color);
       content: '';
     }
@@ -496,10 +501,10 @@
     &--first {
       &::before {
         position: absolute;
-        top: 1px;
+        top: 0;
         left: 0;
         width: 100%;
-        height: calc(100% - 1px);
+        height: 100%;
         border-right: 1px solid var(--el-border-color);
         border-left: none;
         content: '';
@@ -509,14 +514,15 @@
 
   &__item {
     position: relative;
-    top: 2px;
+    top: 3px;
     height: calc(100% - 6px);
-    padding-right: 25px;
+    padding-right: 15px;
     margin-left: 4px;
     font-size: 12px;
     cursor: pointer;
     border: 1px solid #d9d9d9;
     border-radius: 2px;
+    box-sizing: border-box;
 
     &--close {
       position: absolute;
@@ -525,11 +531,16 @@
       display: none;
       transform: translate(0, -50%);
     }
+
     &:not(.#{$prefix-cls}__item--affix):hover {
       .#{$prefix-cls}__item--close {
         display: block;
       }
     }
+  }
+
+  &__item--icon {
+    padding-right: 20px;
   }
 
   &__item:not(.is-active) {
@@ -542,9 +553,45 @@
     color: var(--el-color-white);
     background-color: var(--el-color-primary);
     border: 1px solid var(--el-color-primary);
+
     .#{$prefix-cls}__item--close {
       :deep(span) {
         color: var(--el-color-white) !important;
+      }
+    }
+  }
+
+  &__item--immerse {
+    top: 2px;
+    height: calc(100% - 3px);
+    padding-right: 35px;
+    margin: 0 -10px;
+    border: none !important;
+    -webkit-mask-box-image: url("data:image/svg+xml,%3Csvg width='68' height='34' viewBox='0 0 68 34' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='m27,0c-7.99582,0 -11.95105,0.00205 -12,12l0,6c0,8.284 -0.48549,16.49691 -8.76949,16.49691l54.37857,-0.11145c-8.284,0 -8.60908,-8.10146 -8.60908,-16.38546l0,-6c0.11145,-12.08445 -4.38441,-12 -12,-12l-13,0z' fill='%23409eff'/%3E%3C/svg%3E")
+      12 27 15;
+
+    .#{$prefix-cls}__item--label {
+      padding-left: 35px;
+    }
+
+    .#{$prefix-cls}__item--close {
+      right: 20px;
+    }
+  }
+
+  &__item--immerse--icon {
+    padding-right: 35px;
+  }
+
+  &__item--immerse:not(.is-active) {
+    &:hover {
+      color: var(--el-color-white);
+      background-color: var(--el-color-primary);
+
+      .#{$prefix-cls}__item--close {
+        :deep(span) {
+          color: var(--el-color-white) !important;
+        }
       }
     }
   }
@@ -574,12 +621,19 @@
       color: var(--el-color-white);
       background-color: var(--el-color-primary);
       border: 1px solid var(--el-color-primary);
+
       .#{$prefix-cls}__item--close {
         :deep(span) {
           color: var(--el-color-white) !important;
         }
       }
     }
+
+    &__item--immerse:not(.is-active) {
+      &:hover {
+        color: var(--el-color-white);
+      }
+    }
   }
 }
 </style>
diff --git a/src/layout/components/UserInfo/src/UserInfo.vue b/src/layout/components/UserInfo/src/UserInfo.vue
index 797fb87..355aabc 100644
--- a/src/layout/components/UserInfo/src/UserInfo.vue
+++ b/src/layout/components/UserInfo/src/UserInfo.vue
@@ -23,7 +23,7 @@
 
 const prefixCls = getPrefixCls('user-info')
 
-const avatar = computed(() => userStore.user.avatar ?? avatarImg)
+const avatar = computed(() => userStore.user.avatar || avatarImg)
 const userName = computed(() => userStore.user.nickname ?? 'Admin')
 
 // 锁定屏幕
@@ -50,7 +50,7 @@
   push('/user/profile')
 }
 const toDocument = () => {
-  window.open('https://xxxx/')
+  window.open('https://doc.iocoder.cn/')
 }
 </script>
 
diff --git a/src/layout/components/UserInfo/src/components/LockDialog.vue b/src/layout/components/UserInfo/src/components/LockDialog.vue
index f4ab7d4..7257be1 100644
--- a/src/layout/components/UserInfo/src/components/LockDialog.vue
+++ b/src/layout/components/UserInfo/src/components/LockDialog.vue
@@ -21,7 +21,7 @@
 })
 
 const userStore = useUserStore()
-const avatar = computed(() => userStore.user.avatar ?? avatarImg)
+const avatar = computed(() => userStore.user.avatar || avatarImg)
 const userName = computed(() => userStore.user.nickname ?? 'Admin')
 
 const emit = defineEmits(['update:modelValue'])
diff --git a/src/layout/components/UserInfo/src/components/LockPage.vue b/src/layout/components/UserInfo/src/components/LockPage.vue
index e53443f..27d0a43 100644
--- a/src/layout/components/UserInfo/src/components/LockPage.vue
+++ b/src/layout/components/UserInfo/src/components/LockPage.vue
@@ -22,7 +22,7 @@
 const { getPrefixCls } = useDesign()
 const prefixCls = getPrefixCls('lock-page')
 
-const avatar = computed(() => userStore.user.avatar ?? avatarImg)
+const avatar = computed(() => userStore.user.avatar || avatarImg)
 const userName = computed(() => userStore.user.nickname ?? 'Admin')
 
 const lockStore = useLockStore()
diff --git a/src/layout/components/useRenderLayout.tsx b/src/layout/components/useRenderLayout.tsx
index 1110cd8..5cae84d 100644
--- a/src/layout/components/useRenderLayout.tsx
+++ b/src/layout/components/useRenderLayout.tsx
@@ -126,7 +126,7 @@
 
           <ToolHeader class="flex-1"></ToolHeader>
         </div>
-        <div class="absolute left-0 top-[var(--logo-height)+1px] h-[calc(100%-1px-var(--logo-height))] w-full flex">
+        <div class="absolute left-0 top-[var(--logo-height)] h-[calc(100%-var(--logo-height))] w-full flex">
           <Menu class="relative layout-border__right !h-full"></Menu>
           <div
             class={[
@@ -157,9 +157,9 @@
                     'layout-border__bottom absolute',
                     {
                       '!fixed top-0 left-0 z-10': fixedHeader.value,
-                      'w-[calc(100%-var(--left-menu-min-width))] !left-[var(--left-menu-min-width)] mt-[calc(var(--logo-height)+1px)]':
+                      'w-[calc(100%-var(--left-menu-min-width))] !left-[var(--left-menu-min-width)] mt-[var(--logo-height)]':
                         collapse.value && fixedHeader.value,
-                      'w-[calc(100%-var(--left-menu-max-width))] !left-[var(--left-menu-max-width)] mt-[calc(var(--logo-height)+1px)]':
+                      'w-[calc(100%-var(--left-menu-max-width))] !left-[var(--left-menu-max-width)] mt-[var(--logo-height)]':
                         !collapse.value && fixedHeader.value
                     }
                   ]}
@@ -190,24 +190,14 @@
           <Menu class="h-[var(--top-tool-height)] flex-1 px-10px"></Menu>
           <ToolHeader></ToolHeader>
         </div>
-        <div
-          class={[
-            `${prefixCls}-content`,
-            'w-full',
-            {
-              'h-[calc(100%-var(--app-footer-height))]': !fixedHeader.value,
-              'h-[calc(100%-var(--tags-view-height)-var(--app-footer-height))]': fixedHeader.value
-            }
-          ]}
-        >
+        <div class={[`${prefixCls}-content`, 'w-full h-[calc(100%-var(--top-tool-height))]']}>
           <ElScrollbar
             v-loading={pageLoading.value}
             class={[
               `${prefixCls}-content-scrollbar`,
               {
-                'mt-[var(--tags-view-height)] !pb-[calc(var(--tags-view-height)+var(--app-footer-height))]':
-                  fixedHeader.value,
-                'pb-[var(--app-footer-height)]': !fixedHeader.value
+                '!h-[calc(100%-var(--tags-view-height))] mt-[calc(var(--tags-view-height))]':
+                  fixedHeader.value
               }
             ]}
           >
@@ -216,7 +206,7 @@
                 class={[
                   'layout-border__bottom layout-border__top relative',
                   {
-                    '!fixed w-full top-[calc(var(--top-tool-height)+1px)] left-0': fixedHeader.value
+                    '!fixed w-full top-[var(--top-tool-height)] left-0': fixedHeader.value
                   }
                 ]}
                 style="transition: width var(--transition-time-02), left var(--transition-time-02);"
@@ -238,7 +228,7 @@
 
           <ToolHeader class="flex-1"></ToolHeader>
         </div>
-        <div class="absolute left-0 top-[var(--logo-height)] h-[calc(100%-var(--logo-height))] w-[calc(100%-2px)] flex">
+        <div class="absolute left-0 top-[var(--logo-height)] h-[calc(100%-var(--logo-height))] w-full flex">
           <TabMenu></TabMenu>
           <div
             class={[
@@ -270,18 +260,16 @@
               {tagsView.value ? (
                 <TagsView
                   class={[
-                    'relative layout-border__bottom layout-border__top',
+                    'relative layout-border__bottom',
                     {
                       '!fixed top-0 left-0 z-10': fixedHeader.value,
                       'w-[calc(100%-var(--tab-menu-min-width))] !left-[var(--tab-menu-min-width)] mt-[var(--logo-height)]':
-                        collapse.value && fixedHeader.value,
+                        collapse.value && fixedHeader.value && !fixedMenu.value,
                       'w-[calc(100%-var(--tab-menu-max-width))] !left-[var(--tab-menu-max-width)] mt-[var(--logo-height)]':
-                        !collapse.value && fixedHeader.value,
-                      '!fixed top-0 !left-[var(--tab-menu-min-width)+var(--left-menu-max-width)] z-10':
-                        fixedHeader.value && fixedMenu.value,
-                      'w-[calc(100%-var(--tab-menu-min-width)-var(--left-menu-max-width))] !left-[var(--tab-menu-min-width)+var(--left-menu-max-width)] mt-[var(--logo-height)]':
+                        !collapse.value && fixedHeader.value && !fixedMenu.value,
+                      'w-[calc(100%-var(--tab-menu-min-width)-var(--left-menu-max-width))] !left-[calc(var(--tab-menu-min-width)+var(--left-menu-max-width))] mt-[var(--logo-height)]':
                         collapse.value && fixedHeader.value && fixedMenu.value,
-                      'w-[calc(100%-var(--tab-menu-max-width)-var(--left-menu-max-width))] !left-[var(--tab-menu-max-width)+var(--left-menu-max-width)] mt-[var(--logo-height)]':
+                      'w-[calc(100%-var(--tab-menu-max-width)-var(--left-menu-max-width))] !left-[calc(var(--tab-menu-max-width)+var(--left-menu-max-width))] mt-[var(--logo-height)]':
                         !collapse.value && fixedHeader.value && fixedMenu.value
                     }
                   ]}
diff --git a/src/plugins/formCreate/index.ts b/src/plugins/formCreate/index.ts
index 44556de..07d2c51 100644
--- a/src/plugins/formCreate/index.ts
+++ b/src/plugins/formCreate/index.ts
@@ -1,7 +1,37 @@
 import type { App } from 'vue'
 // 👇使用 form-create 需额外全局引入 element plus 组件
 import {
+  // ElAutocomplete,
+  // ElButton,
+  // ElCascader,
+  // ElCheckbox,
+  // ElCheckboxButton,
+  // ElCheckboxGroup,
+  // ElCol,
+  // ElColorPicker,
+  // ElDatePicker,
+  // ElDialog,
+  // ElForm,
+  // ElInput,
+  // ElInputNumber,
+  // ElPopover,
+  // ElRadio,
+  // ElRadioButton,
+  // ElRadioGroup,
+  // ElRate,
+  // ElRow,
+  // ElSelect,
+  // ElSlider,
+  // ElSwitch,
+  // ElTimePicker,
+  // ElTooltip,
+  // ElTree,
+  // ElUpload,
+  // ElIcon,
+  // ElProgress,
+  // 以上会由 @form-create/element-ui/auto-import 自动引入
   ElAlert,
+  ElTransfer,
   ElAside,
   ElContainer,
   ElDivider,
@@ -12,7 +42,21 @@
   ElTableColumn,
   ElTabPane,
   ElTabs,
-  ElTransfer
+  ElDropdown,
+  ElDropdownMenu,
+  ElDropdownItem,
+  ElBadge,
+  ElTag,
+  ElText,
+  ElMenu,
+  ElMenuItem,
+  ElFooter,
+  ElMessage,
+  ElCollapse,
+  ElCollapseItem,
+  ElCard,
+  // ElFormItem,
+  // ElOption
 } from 'element-plus'
 import FcDesigner from '@form-create/designer'
 import formCreate from '@form-create/element-ui'
@@ -41,18 +85,30 @@
 })
 
 const components = [
+  ElAlert,
+  ElTransfer,
   ElAside,
-  ElPopconfirm,
-  ElHeader,
-  ElMain,
   ElContainer,
   ElDivider,
-  ElTransfer,
-  ElAlert,
-  ElTabs,
+  ElHeader,
+  ElMain,
+  ElPopconfirm,
   ElTable,
   ElTableColumn,
   ElTabPane,
+  ElTabs,
+  ElDropdown,
+  ElDropdownMenu,
+  ElDropdownItem,
+  ElBadge,
+  ElTag,
+  ElText,
+  ElMenu,
+  ElMenuItem,
+  ElFooter,
+  ElMessage,
+  // ElFormItem,
+  // ElOption,
   UploadImg,
   UploadImgs,
   UploadFile,
@@ -60,7 +116,10 @@
   UserSelect,
   DeptSelect,
   ApiSelect,
-  Editor
+  Editor,
+  ElCollapse,
+  ElCollapseItem,
+  ElCard,
 ]
 
 // 参考 http://www.form-create.com/v3/element-ui/auto-import.html 文档
diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts
index 5de3d74..6b64a95 100644
--- a/src/router/modules/remaining.ts
+++ b/src/router/modules/remaining.ts
@@ -72,6 +72,7 @@
     path: '/',
     component: Layout,
     name: 'Home',
+    redirect: '/index',
     meta: {
       hidden: true,
       noTagsView: true
@@ -287,6 +288,18 @@
         }
       },
       {
+        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'),
         name: 'BpmProcessDefinition',
@@ -308,7 +321,12 @@
           canTo: true,
           title: '流程详情',
           activeMenu: '/bpm/task/my'
-        }
+        },
+        props: (route) => ({
+          id: route.query.id,
+          taskId: route.query.taskId,
+          activityId: route.query.activityId
+        })
       },
       {
         path: 'oa/leave/create',
diff --git a/src/store/modules/bpm/simpleWorkflow.ts b/src/store/modules/bpm/simpleWorkflow.ts
new file mode 100644
index 0000000..2942951
--- /dev/null
+++ b/src/store/modules/bpm/simpleWorkflow.ts
@@ -0,0 +1,55 @@
+import { store } from '../../index'
+import { defineStore } from 'pinia'
+
+export const useWorkFlowStore = defineStore('simpleWorkflow', {
+  state: () => ({
+    tableId: '',
+    isTried: false,
+    promoterDrawer: false,
+    approverDrawer: false,
+    approverConfig1: {},
+    copyerDrawer: false,
+    copyerConfig: {},
+    conditionDrawer: false,
+    conditionsConfig1: {
+      conditionNodes: []
+    },
+    userTaskConfig: {}
+  }),
+  actions: {
+    setTableId(payload) {
+      this.tableId = payload
+    },
+    setIsTried(payload) {
+      this.isTried = payload
+    },
+    setPromoter(payload) {
+      this.promoterDrawer = payload
+    },
+    setApproverDrawer(payload) {
+      this.approverDrawer = payload
+    },
+    setApproverConfig(payload) {
+      this.approverConfig1 = payload
+    },
+    setCopyerDrawer(payload) {
+      this.copyerDrawer = payload
+    },
+    setCopyerConfig(payload) {
+      this.copyerConfig = payload
+    },
+    setCondition(payload) {
+      this.conditionDrawer = payload
+    },
+    setConditionsConfig(payload) {
+      this.conditionsConfig1 = payload
+    },
+    setUserTaskConfig(payload) {
+      this.userTaskConfig = payload
+    }
+  }
+})
+
+export const useWorkFlowStoreWithOut = () => {
+  return useWorkFlowStore(store)
+}
diff --git a/src/store/modules/permission.ts b/src/store/modules/permission.ts
index 12ddb45..2000b33 100644
--- a/src/store/modules/permission.ts
+++ b/src/store/modules/permission.ts
@@ -35,15 +35,18 @@
       return new Promise<void>(async (resolve) => {
         // 获得菜单列表,它在登录的时候,setUserInfoAction 方法中已经进行获取
         let res: AppCustomRouteRecordRaw[] = []
-        if (wsSessionCache.get(CACHE_KEY.ROLE_ROUTERS)) {
-          res = wsSessionCache.get(CACHE_KEY.ROLE_ROUTERS) as AppCustomRouteRecordRaw[]
+        const roleRouters = wsSessionCache.get(CACHE_KEY.ROLE_ROUTERS)
+        if (roleRouters) {
+          res = roleRouters as AppCustomRouteRecordRaw[]
         }
         const routerMap: AppRouteRecordRaw[] = generateRoute(res)
         // 动态路由,404一定要放到最后面
+        // preschooler:vue-router@4以后已支持静态404路由,此处可不再追加
         this.addRouters = routerMap.concat([
           {
             path: '/:path(.*)*',
-            redirect: '/404',
+            // redirect: '/404',
+            component: () => import('@/views/Error/404.vue'),
             name: '404Page',
             meta: {
               hidden: true,
diff --git a/src/store/modules/tagsView.ts b/src/store/modules/tagsView.ts
index 25a3a1f..4368efe 100644
--- a/src/store/modules/tagsView.ts
+++ b/src/store/modules/tagsView.ts
@@ -31,13 +31,27 @@
     },
     // 新增tag
     addVisitedView(view: RouteLocationNormalizedLoaded) {
-      if (this.visitedViews.some((v) => v.path === view.path)) return
+      if (this.visitedViews.some((v) => v.fullPath === view.fullPath)) return
       if (view.meta?.noTagsView) return
-      this.visitedViews.push(
-        Object.assign({}, view, {
-          title: view.meta?.title || 'no-name'
+      const visitedView = Object.assign({}, view, { title: view.meta?.title || 'no-name' })
+
+      if (visitedView.meta) {
+        const titleSuffixList: string[] = []
+        this.visitedViews.forEach((v) => {
+          if (v.path === visitedView.path && v.meta?.title === visitedView.meta?.title) {
+            titleSuffixList.push(v.meta?.titleSuffix || '1')
+          }
         })
-      )
+        if (titleSuffixList.length) {
+          let titleSuffix = 1
+          while (titleSuffixList.includes(`${titleSuffix}`)) {
+            titleSuffix += 1
+          }
+          visitedView.meta.titleSuffix = titleSuffix === 1 ? undefined : `${titleSuffix}`
+        }
+      }
+
+      this.visitedViews.push(visitedView)
     },
     // 新增缓存
     addCachedView() {
@@ -63,7 +77,7 @@
     // 删除tag
     delVisitedView(view: RouteLocationNormalizedLoaded) {
       for (const [i, v] of this.visitedViews.entries()) {
-        if (v.path === view.path) {
+        if (v.fullPath === view.fullPath) {
           this.visitedViews.splice(i, 1)
           break
         }
@@ -95,18 +109,18 @@
     // 删除其他tag
     delOthersVisitedViews(view: RouteLocationNormalizedLoaded) {
       this.visitedViews = this.visitedViews.filter((v) => {
-        return v?.meta?.affix || v.path === view.path
+        return v?.meta?.affix || v.fullPath === view.fullPath
       })
     },
     // 删除左侧
     delLeftViews(view: RouteLocationNormalizedLoaded) {
       const index = findIndex<RouteLocationNormalizedLoaded>(
         this.visitedViews,
-        (v) => v.path === view.path
+        (v) => v.fullPath === view.fullPath
       )
       if (index > -1) {
         this.visitedViews = this.visitedViews.filter((v, i) => {
-          return v?.meta?.affix || v.path === view.path || i > index
+          return v?.meta?.affix || v.fullPath === view.fullPath || i > index
         })
         this.addCachedView()
       }
@@ -115,18 +129,18 @@
     delRightViews(view: RouteLocationNormalizedLoaded) {
       const index = findIndex<RouteLocationNormalizedLoaded>(
         this.visitedViews,
-        (v) => v.path === view.path
+        (v) => v.fullPath === view.fullPath
       )
       if (index > -1) {
         this.visitedViews = this.visitedViews.filter((v, i) => {
-          return v?.meta?.affix || v.path === view.path || i < index
+          return v?.meta?.affix || v.fullPath === view.fullPath || i < index
         })
         this.addCachedView()
       }
     },
     updateVisitedView(view: RouteLocationNormalizedLoaded) {
       for (let v of this.visitedViews) {
-        if (v.path === view.path) {
+        if (v.fullPath === view.fullPath) {
           v = Object.assign(v, view)
           break
         }
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index 870bbfb..a291a0d 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -461,3 +461,23 @@
   TRUE: true, // 启用
   FALSE: false // 禁用
 }
+
+// ========== BPM 模块 ==========
+
+export const BpmModelType = {
+  BPMN: 10, // BPMN 设计器
+  SIMPLE: 20 // 简易设计器
+}
+
+export const BpmModelFormType = {
+  NORMAL: 10, // 流程表单
+  CUSTOM: 20 // 业务表单
+}
+
+export const BpmProcessInstanceStatus = {
+  NOT_START: -1, // 未开始
+  RUNNING: 1, // 审批中
+  APPROVE: 2, // 审批通过
+  REJECT: 3, // 审批不通过
+  CANCEL: 4 // 已取消
+}
diff --git a/src/utils/dict.ts b/src/utils/dict.ts
index 9347dfc..9d424f1 100644
--- a/src/utils/dict.ts
+++ b/src/utils/dict.ts
@@ -145,6 +145,7 @@
   INFRA_OPERATE_TYPE = 'infra_operate_type',
 
   // ========== BPM 模块 ==========
+  BPM_MODEL_TYPE = 'bpm_model_type',
   BPM_MODEL_FORM_TYPE = 'bpm_model_form_type',
   BPM_TASK_CANDIDATE_STRATEGY = 'bpm_task_candidate_strategy',
   BPM_PROCESS_INSTANCE_STATUS = 'bpm_process_instance_status',
diff --git a/src/utils/routerHelper.ts b/src/utils/routerHelper.ts
index e9c8c64..b65f93a 100644
--- a/src/utils/routerHelper.ts
+++ b/src/utils/routerHelper.ts
@@ -21,7 +21,6 @@
 /* Layout */
 export const Layout = () => import('@/layout/Layout.vue')
 
-
 export const getParentLayout = () => {
   return () =>
     new Promise((resolve) => {
@@ -89,7 +88,8 @@
     // 2. 生成 data(AppRouteRecordRaw)
     // 路由地址转首字母大写驼峰,作为路由名称,适配keepAlive
     let data: AppRouteRecordRaw = {
-      path: route.path.indexOf('?') > -1 ? route.path.split('?')[0] : route.path,
+      path:
+        route.path.indexOf('?') > -1 && !isUrl(route.path) ? route.path.split('?')[0] : route.path, // 注意,需要排除 http 这种 url,避免它带 ? 参数被截取掉
       name:
         route.componentName && route.componentName.length > 0
           ? route.componentName
@@ -120,7 +120,7 @@
       data.children = [childrenData]
     } else {
       // 目录
-      if (route.children) {
+      if (route.children?.length) {
         data.component = Layout
         data.redirect = getRedirect(route.path, route.children)
         // 外链
diff --git a/src/views/bpm/category/CategoryForm.vue b/src/views/bpm/category/CategoryForm.vue
index 5b77153..9c24b3e 100644
--- a/src/views/bpm/category/CategoryForm.vue
+++ b/src/views/bpm/category/CategoryForm.vue
@@ -18,7 +18,7 @@
           <el-radio
             v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
             :key="dict.value"
-            :label="dict.value"
+            :value="dict.value"
           >
             {{ dict.label }}
           </el-radio>
@@ -42,6 +42,7 @@
 <script setup lang="ts">
 import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
 import { CategoryApi, CategoryVO } from '@/api/bpm/category'
+import { CommonStatusEnum } from '@/utils/constants'
 
 /** BPM 流程分类 表单 */
 defineOptions({ name: 'CategoryForm' })
@@ -57,7 +58,7 @@
   id: undefined,
   name: undefined,
   code: undefined,
-  status: undefined,
+  status: CommonStatusEnum.ENABLE,
   sort: undefined
 })
 const formRules = reactive({
@@ -116,7 +117,7 @@
     id: undefined,
     name: undefined,
     code: undefined,
-    status: undefined,
+    status: CommonStatusEnum.ENABLE,
     sort: undefined
   }
   formRef.value?.resetFields()
diff --git a/src/views/bpm/definition/index.vue b/src/views/bpm/definition/index.vue
index 03aa475..8d5309d 100644
--- a/src/views/bpm/definition/index.vue
+++ b/src/views/bpm/definition/index.vue
@@ -69,13 +69,7 @@
 
   <!-- 弹窗:流程模型图的预览 -->
   <Dialog title="流程图" v-model="bpmnDetailVisible" width="800">
-    <MyProcessViewer
-      key="designer"
-      v-model="bpmnXml"
-      :value="bpmnXml as any"
-      v-bind="bpmnControlForm"
-      :prefix="bpmnControlForm.prefix"
-    />
+    <MyProcessViewer style="height: 700px" key="designer" :xml="bpmnXml" />
   </Dialog>
 </template>
 
@@ -117,7 +111,7 @@
   rule: [],
   option: {}
 })
-const handleFormDetail = async (row) => {
+const handleFormDetail = async (row: any) => {
   if (row.formType == 10) {
     // 设置表单
     setConfAndFields2(formDetailPreview, row.formConf, row.formFields)
@@ -132,13 +126,13 @@
 
 /** 流程图的详情按钮操作 */
 const bpmnDetailVisible = ref(false)
-const bpmnXml = ref(null)
-const bpmnControlForm = ref({
-  prefix: 'flowable'
-})
-const handleBpmnDetail = async (row) => {
-  bpmnXml.value = (await DefinitionApi.getProcessDefinition(row.id))?.bpmnXml
+const bpmnXml = ref('')
+const handleBpmnDetail = async (row: any) => {
+  // 设置可见
+  bpmnXml.value = ''
   bpmnDetailVisible.value = true
+  // 加载 BPMN XML
+  bpmnXml.value = (await DefinitionApi.getProcessDefinition(row.id))?.bpmnXml
 }
 
 /** 初始化 **/
diff --git a/src/views/bpm/form/editor/index.vue b/src/views/bpm/form/editor/index.vue
index 0d1230c..12945e0 100644
--- a/src/views/bpm/form/editor/index.vue
+++ b/src/views/bpm/form/editor/index.vue
@@ -1,14 +1,18 @@
 <template>
-  <ContentWrap>
+  <ContentWrap :body-style="{ padding: '0px' }" class="!mb-0">
     <!-- 表单设计器 -->
-    <FcDesigner ref="designer" height="780px">
-      <template #handle>
-        <el-button round size="small" type="primary" @click="handleSave">
-          <Icon class="mr-5px" icon="ep:plus" />
-          保存
-        </el-button>
-      </template>
-    </FcDesigner>
+    <div
+      class="h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-2px)]"
+    >
+      <fc-designer class="my-designer" ref="designer" :config="designerConfig">
+        <template #handle>
+          <el-button size="small" type="success" plain @click="handleSave">
+            <Icon class="mr-5px" icon="ep:plus" />
+            保存
+          </el-button>
+        </template>
+      </fc-designer>
+    </div>
   </ContentWrap>
 
   <!-- 表单保存的弹窗 -->
@@ -22,7 +26,7 @@
           <el-radio
             v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
             :key="dict.value"
-            :label="dict.value"
+            :value="dict.value"
           >
             {{ dict.label }}
           </el-radio>
@@ -55,6 +59,35 @@
 const { query } = useRoute() // 路由信息
 const { delView } = useTagsViewStore() // 视图操作
 
+// 表单设计器配置
+const designerConfig = ref({
+  switchType: [], // 是否可以切换组件类型,或者可以相互切换的字段
+  autoActive: true, // 是否自动选中拖入的组件
+  useTemplate: false, // 是否生成vue2语法的模板组件
+  formOptions: {
+    form: {
+      labelWidth: '100px' // 设置默认的 label 宽度为 100px
+    }
+  }, // 定义表单配置默认值
+  fieldReadonly: false, // 配置field是否可以编辑
+  hiddenDragMenu: false, // 隐藏拖拽操作按钮
+  hiddenDragBtn: false, // 隐藏拖拽按钮
+  hiddenMenu: [], // 隐藏部分菜单
+  hiddenItem: [], // 隐藏部分组件
+  hiddenItemConfig: {}, // 隐藏组件的部分配置项
+  disabledItemConfig: {}, // 禁用组件的部分配置项
+  showSaveBtn: false, // 是否显示保存按钮
+  showConfig: true, // 是否显示右侧的配置界面
+  showBaseForm: true, // 是否显示组件的基础配置表单
+  showControl: true, // 是否显示组件联动
+  showPropsForm: true, // 是否显示组件的属性配置表单
+  showEventForm: true, // 是否显示组件的事件配置表单
+  showValidateForm: true, // 是否显示组件的验证配置表单
+  showFormConfig: true, // 是否显示表单配置
+  showInputData: true, // 是否显示录入按钮
+  showDevice: true, // 是否显示多端适配选项
+  appendConfigData: [] // 定义渲染规则所需的formData
+})
 const designer = ref() // 表单设计器
 useFormCreateDesigner(designer) // 表单设计器增强
 const dialogVisible = ref(false) // 弹窗是否展示
@@ -119,3 +152,13 @@
   setConfAndFields(designer, data.conf, data.fields)
 })
 </script>
+
+<style>
+.my-designer {
+  ._fc-l,
+  ._fc-m,
+  ._fc-r {
+    border-top: none;
+  }
+}
+</style>
diff --git a/src/views/bpm/form/index.vue b/src/views/bpm/form/index.vue
index 11c492d..fd55242 100644
--- a/src/views/bpm/form/index.vue
+++ b/src/views/bpm/form/index.vue
@@ -142,8 +142,9 @@
   const toRouter: { name: string; query?: { id: number } } = {
     name: 'BpmFormEditor'
   }
+  console.log(typeof id)
   // 表单新建的时候id传的是event需要排除
-  if (typeof id === 'number') {
+  if (typeof id === 'number' || typeof id === 'string') {
     toRouter.query = {
       id
     }
diff --git a/src/views/bpm/group/UserGroupForm.vue b/src/views/bpm/group/UserGroupForm.vue
index ac0cfcb..3c825eb 100644
--- a/src/views/bpm/group/UserGroupForm.vue
+++ b/src/views/bpm/group/UserGroupForm.vue
@@ -28,7 +28,7 @@
           <el-radio
             v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
             :key="dict.value"
-            :label="dict.value"
+            :value="dict.value"
           >
             {{ dict.label }}
           </el-radio>
diff --git a/src/views/bpm/model/CategoryDraggableModel.vue b/src/views/bpm/model/CategoryDraggableModel.vue
new file mode 100644
index 0000000..3546593
--- /dev/null
+++ b/src/views/bpm/model/CategoryDraggableModel.vue
@@ -0,0 +1,532 @@
+<template>
+  <div class="flex items-center h-50px">
+    <!-- 头部:分类名 -->
+    <div class="flex items-center">
+      <el-tooltip content="拖动排序" v-if="isCategorySorting">
+        <Icon
+          :size="22"
+          icon="ic:round-drag-indicator"
+          class="ml-10px category-drag-icon cursor-move text-#8a909c"
+        />
+      </el-tooltip>
+      <h3 class="ml-20px mr-8px text-18px">{{ categoryInfo.name }}</h3>
+      <div class="color-gray-600 text-16px"> ({{ categoryInfo.modelList?.length || 0 }}) </div>
+    </div>
+    <!-- 头部:操作 -->
+    <div class="flex-1 flex" v-if="!isCategorySorting">
+      <div
+        v-if="categoryInfo.modelList.length > 0"
+        class="ml-20px flex items-center"
+        :class="[
+          'transition-transform duration-300 cursor-pointer',
+          isExpand ? 'rotate-180' : 'rotate-0'
+        ]"
+        @click="isExpand = !isExpand"
+      >
+        <Icon icon="ep:arrow-down-bold" color="#999" />
+      </div>
+      <div class="ml-auto flex items-center" :class="isModelSorting ? 'mr-15px' : 'mr-45px'">
+        <template v-if="!isModelSorting">
+          <el-button
+            v-if="categoryInfo.modelList.length > 0"
+            link
+            type="info"
+            class="mr-20px"
+            @click.stop="handleModelSort"
+          >
+            <Icon icon="fa:sort-amount-desc" class="mr-5px" />
+            排序
+          </el-button>
+          <el-button v-else link type="info" class="mr-20px" @click.stop="openModelForm('create')">
+            <Icon icon="fa:plus" class="mr-5px" />
+            新建
+          </el-button>
+          <el-dropdown
+            @command="(command) => handleCategoryCommand(command, categoryInfo)"
+            placement="bottom"
+          >
+            <el-button link type="info">
+              <Icon icon="ep:setting" class="mr-5px" />
+              分类
+            </el-button>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item command="handleRename"> 重命名 </el-dropdown-item>
+                <el-dropdown-item command="handleDeleteCategory"> 删除该类 </el-dropdown-item>
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
+        </template>
+        <template v-else>
+          <el-button @click.stop="handleModelSortCancel"> 取 消 </el-button>
+          <el-button type="primary" @click.stop="handleModelSortSubmit"> 保存排序 </el-button>
+        </template>
+      </div>
+    </div>
+  </div>
+  <!-- 模型列表 -->
+  <el-collapse-transition>
+    <div v-show="isExpand">
+      <el-table
+        :class="categoryInfo.name"
+        ref="tableRef"
+        :header-cell-style="{ backgroundColor: isDark ? '' : '#edeff0', paddingLeft: '10px' }"
+        :cell-style="{ paddingLeft: '10px' }"
+        :row-style="{ height: '68px' }"
+        :data="modelList"
+        row-key="id"
+      >
+        <el-table-column label="流程名" prop="name" min-width="150">
+          <template #default="scope">
+            <div class="flex items-center">
+              <el-tooltip content="拖动排序" v-if="isModelSorting">
+                <Icon
+                  icon="ic:round-drag-indicator"
+                  class="drag-icon cursor-move text-#8a909c mr-10px"
+                />
+              </el-tooltip>
+              <el-image :src="scope.row.icon" class="h-38px w-38px mr-10px rounded" />
+              {{ scope.row.name }}
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="可见范围" prop="startUserIds" min-width="100">
+          <template #default="scope">
+            <el-text v-if="!scope.row.startUsers || scope.row.startUsers.length === 0">
+              全部可见
+            </el-text>
+            <el-text v-else-if="scope.row.startUsers.length == 1">
+              {{ scope.row.startUsers[0].nickname }}
+            </el-text>
+            <el-text v-else>
+              <el-tooltip
+                class="box-item"
+                effect="dark"
+                placement="top"
+                :content="scope.row.startUsers.map((user: any) => user.nickname).join('、')"
+              >
+                {{ scope.row.startUsers[0].nickname }}等 {{ scope.row.startUsers.length }} 人可见
+              </el-tooltip>
+            </el-text>
+          </template>
+        </el-table-column>
+        <el-table-column label="表单信息" prop="formType" min-width="200">
+          <template #default="scope">
+            <el-button
+              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-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="最后发布" prop="deploymentTime" min-width="250">
+          <template #default="scope">
+            <div class="flex items-center">
+              <span v-if="scope.row.processDefinition" class="w-150px">
+                {{ formatDate(scope.row.processDefinition.deploymentTime) }}
+              </span>
+              <el-tag v-if="scope.row.processDefinition">
+                v{{ scope.row.processDefinition.version }}
+              </el-tag>
+              <el-tag v-else type="warning">未部署</el-tag>
+              <el-tag
+                v-if="scope.row.processDefinition?.suspensionState === 2"
+                type="warning"
+                class="ml-10px"
+              >
+                已停用
+              </el-tag>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="200" fixed="right">
+          <template #default="scope">
+            <el-button
+              link
+              type="primary"
+              @click="openModelForm('update', scope.row.id)"
+              v-hasPermi="['bpm:model:update']"
+              :disabled="!isManagerUser(scope.row)"
+            >
+              修改
+            </el-button>
+            <el-button
+              link
+              class="!ml-5px"
+              type="primary"
+              @click="handleDesign(scope.row)"
+              v-hasPermi="['bpm:model:update']"
+              :disabled="!isManagerUser(scope.row)"
+            >
+              设计
+            </el-button>
+            <el-button
+              link
+              class="!ml-5px"
+              type="primary"
+              @click="handleDeploy(scope.row)"
+              v-hasPermi="['bpm:model:deploy']"
+              :disabled="!isManagerUser(scope.row)"
+            >
+              发布
+            </el-button>
+            <el-dropdown
+              class="!align-middle ml-5px"
+              @command="(command) => handleModelCommand(command, scope.row)"
+              v-hasPermi="['bpm:process-definition:query', 'bpm:model:update', 'bpm:model:delete']"
+            >
+              <el-button type="primary" link>更多</el-button>
+              <template #dropdown>
+                <el-dropdown-menu>
+                  <el-dropdown-item
+                    command="handleDefinitionList"
+                    v-if="checkPermi(['bpm:process-definition:query'])"
+                  >
+                    历史
+                  </el-dropdown-item>
+                  <el-dropdown-item
+                    command="handleChangeState"
+                    v-if="checkPermi(['bpm:model:update']) && scope.row.processDefinition"
+                    :disabled="!isManagerUser(scope.row)"
+                  >
+                    {{ scope.row.processDefinition.suspensionState === 1 ? '停用' : '启用' }}
+                  </el-dropdown-item>
+                  <el-dropdown-item
+                    type="danger"
+                    command="handleDelete"
+                    v-if="checkPermi(['bpm:model:delete'])"
+                    :disabled="!isManagerUser(scope.row)"
+                  >
+                    删除
+                  </el-dropdown-item>
+                </el-dropdown-menu>
+              </template>
+            </el-dropdown>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+  </el-collapse-transition>
+
+  <!-- 弹窗:重命名分类 -->
+  <Dialog :fullscreen="false" class="rename-dialog" v-model="renameCategoryVisible" width="400">
+    <template #title>
+      <div class="pl-10px font-bold text-18px"> 重命名分类 </div>
+    </template>
+    <div class="px-30px">
+      <el-input v-model="renameCategoryForm.name" />
+    </div>
+    <template #footer>
+      <div class="pr-25px pb-25px">
+        <el-button @click="renameCategoryVisible = false">取 消</el-button>
+        <el-button type="primary" @click="handleRenameConfirm">确 定</el-button>
+      </div>
+    </template>
+  </Dialog>
+
+  <!-- 表单弹窗:添加流程模型 -->
+  <ModelForm :categoryId="categoryInfo.code" ref="modelFormRef" @success="emit('success')" />
+</template>
+
+<script lang="ts" setup>
+import ModelForm from './ModelForm.vue'
+import { CategoryApi, CategoryVO } from '@/api/bpm/category'
+import Sortable from 'sortablejs'
+import { propTypes } from '@/utils/propTypes'
+import { formatDate } from '@/utils/formatTime'
+import * as ModelApi from '@/api/bpm/model'
+import * as FormApi from '@/api/bpm/form'
+import { setConfAndFields2 } from '@/utils/formCreate'
+import { BpmModelFormType, BpmModelType } from '@/utils/constants'
+import { checkPermi } from '@/utils/permission'
+import { useUserStoreWithOut } from '@/store/modules/user'
+import { useAppStore } from '@/store/modules/app'
+import { cloneDeep } from 'lodash-es'
+
+defineOptions({ name: 'BpmModel' })
+
+const props = defineProps({
+  categoryInfo: propTypes.object.def([]), // 分类后的数据
+  isCategorySorting: propTypes.bool.def(false) // 是否分类在排序
+})
+const emit = defineEmits(['success'])
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+const { push } = useRouter() // 路由
+const userStore = useUserStoreWithOut() // 用户信息缓存
+const isDark = computed(() => useAppStore().getIsDark) // 是否黑暗模式
+
+const isModelSorting = ref(false) // 是否正处于排序状态
+const originalData: any = ref([]) // 原始数据
+const modelList: any = ref([]) // 模型列表
+const isExpand = ref(false) // 是否处于展开状态
+
+/** '更多'操作按钮 */
+const handleModelCommand = (command: string, row: any) => {
+  switch (command) {
+    case 'handleDefinitionList':
+      handleDefinitionList(row)
+      break
+    case 'handleDelete':
+      handleDelete(row)
+      break
+    case 'handleChangeState':
+      handleChangeState(row)
+      break
+    default:
+      break
+  }
+}
+
+/** '分类'操作按钮 */
+const handleCategoryCommand = async (command: string, row: any) => {
+  switch (command) {
+    case 'handleRename':
+      renameCategoryForm.value = await CategoryApi.getCategory(row.id)
+      renameCategoryVisible.value = true
+      break
+    case 'handleDeleteCategory':
+      await handleDeleteCategory()
+      break
+    default:
+      break
+  }
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (row: any) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await ModelApi.deleteModel(row.id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    emit('success')
+  } catch {}
+}
+
+/** 更新状态操作 */
+const handleChangeState = async (row: any) => {
+  const state = row.processDefinition.suspensionState
+  const newState = state === 1 ? 2 : 1
+  try {
+    // 修改状态的二次确认
+    const id = row.id
+    debugger
+    const statusState = state === 1 ? '停用' : '启用'
+    const content = '是否确认' + statusState + '流程名字为"' + row.name + '"的数据项?'
+    await message.confirm(content)
+    // 发起修改状态
+    await ModelApi.updateModelState(id, newState)
+    message.success(statusState + '成功')
+    // 刷新列表
+    emit('success')
+  } catch {}
+}
+
+/** 设计流程 */
+const handleDesign = (row: any) => {
+  if (row.type == BpmModelType.BPMN) {
+    push({
+      name: 'BpmModelEditor',
+      query: {
+        modelId: row.id
+      }
+    })
+  } else {
+    push({
+      name: 'SimpleModelDesign',
+      query: {
+        modelId: row.id
+      }
+    })
+  }
+}
+
+/** 发布流程 */
+const handleDeploy = async (row: any) => {
+  try {
+    // 删除的二次确认
+    await message.confirm('是否部署该流程!!')
+    // 发起部署
+    await ModelApi.deployModel(row.id)
+    message.success(t('部署成功'))
+    // 刷新列表
+    emit('success')
+  } catch {}
+}
+
+/** 跳转到指定流程定义列表 */
+const handleDefinitionList = (row: any) => {
+  push({
+    name: 'BpmProcessDefinition',
+    query: {
+      key: row.key
+    }
+  })
+}
+
+/** 流程表单的详情按钮操作 */
+const formDetailVisible = ref(false)
+const formDetailPreview = ref({
+  rule: [],
+  option: {}
+})
+const handleFormDetail = async (row: any) => {
+  if (row.formType == 10) {
+    // 设置表单
+    const data = await FormApi.getForm(row.formId)
+    setConfAndFields2(formDetailPreview, data.conf, data.fields)
+    // 弹窗打开
+    formDetailVisible.value = true
+  } else {
+    await push({
+      path: row.formCustomCreatePath
+    })
+  }
+}
+
+/** 判断是否可以操作 */
+const isManagerUser = (row: any) => {
+  const userId = userStore.getUser.id
+  return row.managerUserIds && row.managerUserIds.includes(userId)
+}
+
+/** 处理模型的排序 **/
+const handleModelSort = () => {
+  // 保存初始数据
+  originalData.value = cloneDeep(props.categoryInfo.modelList)
+  isModelSorting.value = true
+  initSort()
+}
+
+/** 处理模型的排序提交 */
+const handleModelSortSubmit = async () => {
+  // 保存排序
+  const ids = modelList.value.map((item: any) => item.id)
+  await ModelApi.updateModelSortBatch(ids)
+  // 刷新列表
+  isModelSorting.value = false
+  message.success('排序模型成功')
+  emit('success')
+}
+
+/** 处理模型的排序取消 */
+const handleModelSortCancel = () => {
+  // 恢复初始数据
+  modelList.value = cloneDeep(originalData.value)
+  isModelSorting.value = false
+}
+
+/** 创建拖拽实例 */
+const tableRef = ref()
+const initSort = () => {
+  const table = document.querySelector(`.${props.categoryInfo.name} .el-table__body-wrapper tbody`)
+  Sortable.create(table, {
+    group: 'shared',
+    animation: 150,
+    draggable: '.el-table__row',
+    handle: '.drag-icon',
+    // 结束拖动事件
+    onEnd: ({ newDraggableIndex, oldDraggableIndex }) => {
+      if (oldDraggableIndex !== newDraggableIndex) {
+        modelList.value.splice(
+          newDraggableIndex,
+          0,
+          modelList.value.splice(oldDraggableIndex, 1)[0]
+        )
+      }
+    }
+  })
+}
+
+/** 更新 modelList 模型列表 */
+const updateModeList = () => {
+  modelList.value = cloneDeep(props.categoryInfo.modelList)
+  if (props.categoryInfo.modelList.length > 0) {
+    isExpand.value = true
+  }
+}
+
+/** 重命名弹窗确定 */
+const renameCategoryVisible = ref(false)
+const renameCategoryForm = ref({
+  name: ''
+})
+const handleRenameConfirm = async () => {
+  if (renameCategoryForm.value?.name.length === 0) {
+    return message.warning('请输入名称')
+  }
+  // 发起修改
+  await CategoryApi.updateCategory(renameCategoryForm.value as CategoryVO)
+  message.success('重命名成功')
+  // 刷新列表
+  renameCategoryVisible.value = false
+  emit('success')
+}
+
+/** 删除分类 */
+const handleDeleteCategory = async () => {
+  try {
+    if (props.categoryInfo.modelList.length > 0) {
+      return message.warning('该分类下仍有流程定义,不允许删除')
+    }
+    await message.confirm('确认删除分类吗?')
+    // 发起删除
+    await CategoryApi.deleteCategory(props.categoryInfo.id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    emit('success')
+  } catch {}
+}
+
+/** 添加流程模型弹窗 */
+const modelFormRef = ref()
+const openModelForm = (type: string, id?: number) => {
+  modelFormRef.value.open(type, id)
+}
+
+watch(() => props.categoryInfo.modelList, updateModeList, { immediate: true })
+watch(
+  () => props.isCategorySorting,
+  (val) => {
+    if (val) isExpand.value = false
+  },
+  { immediate: true }
+)
+</script>
+
+<style lang="scss">
+.rename-dialog.el-dialog {
+  padding: 0 !important;
+
+  .el-dialog__header {
+    border-bottom: none;
+  }
+
+  .el-dialog__footer {
+    border-top: none !important;
+  }
+}
+</style>
+<style lang="scss" scoped>
+:deep() {
+  .el-table__cell {
+    overflow: hidden;
+    border-bottom: none !important;
+  }
+}
+</style>
diff --git a/src/views/bpm/model/ModelForm.vue b/src/views/bpm/model/ModelForm.vue
index ce60edc..16d3c1d 100644
--- a/src/views/bpm/model/ModelForm.vue
+++ b/src/views/bpm/model/ModelForm.vue
@@ -8,12 +8,7 @@
       label-width="110px"
     >
       <el-form-item label="流程标识" prop="key">
-        <el-input
-          v-model="formData.key"
-          :disabled="!!formData.id"
-          placeholder="请输入流标标识"
-          style="width: 330px"
-        />
+        <el-input v-model="formData.key" :disabled="!!formData.id" placeholder="请输入流标标识" />
         <el-tooltip
           v-if="!formData.id"
           class="item"
@@ -35,7 +30,7 @@
           placeholder="请输入流程名称"
         />
       </el-form-item>
-      <el-form-item v-if="formData.id" label="流程分类" prop="category">
+      <el-form-item label="流程分类" prop="category">
         <el-select
           v-model="formData.category"
           clearable
@@ -50,73 +45,108 @@
           />
         </el-select>
       </el-form-item>
-      <el-form-item v-if="formData.id" label="流程图标" prop="icon">
-        <UploadImg v-model="formData.icon" :limit="1" height="128px" width="128px" />
+      <el-form-item label="流程图标" prop="icon">
+        <UploadImg v-model="formData.icon" :limit="1" height="64px" width="64px" />
       </el-form-item>
       <el-form-item label="流程描述" prop="description">
         <el-input v-model="formData.description" clearable type="textarea" />
       </el-form-item>
-      <div v-if="formData.id">
-        <el-form-item label="表单类型" prop="formType">
-          <el-radio-group v-model="formData.formType">
-            <el-radio
-              v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_FORM_TYPE)"
-              :key="dict.value"
-              :label="dict.value"
-            >
-              {{ dict.label }}
-            </el-radio>
-          </el-radio-group>
-        </el-form-item>
-        <el-form-item v-if="formData.formType === 10" label="流程表单" prop="formId">
-          <el-select v-model="formData.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="formData.formType === 20"
-          label="表单提交路由"
-          prop="formCustomCreatePath"
-        >
-          <el-input
-            v-model="formData.formCustomCreatePath"
-            placeholder="请输入表单提交路由"
-            style="width: 330px"
-          />
-          <el-tooltip
-            class="item"
-            content="自定义表单的提交路径,使用 Vue 的路由地址,例如说:bpm/oa/leave/create"
-            effect="light"
-            placement="top"
+      <el-form-item label="流程类型" prop="type">
+        <el-radio-group v-model="formData.type">
+          <el-radio
+            v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_TYPE)"
+            :key="dict.value"
+            :label="dict.value"
           >
-            <i class="el-icon-question" style="padding-left: 5px"></i>
-          </el-tooltip>
-        </el-form-item>
-        <el-form-item
-          v-if="formData.formType === 20"
-          label="表单查看地址"
-          prop="formCustomViewPath"
-        >
-          <el-input
-            v-model="formData.formCustomViewPath"
-            placeholder="请输入表单查看的组件地址"
-            style="width: 330px"
-          />
-          <el-tooltip
-            class="item"
-            content="自定义表单的查看组件地址,使用 Vue 的组件地址,例如说:bpm/oa/leave/detail"
-            effect="light"
-            placement="top"
+            {{ dict.label }}
+          </el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="表单类型" prop="formType">
+        <el-radio-group v-model="formData.formType">
+          <el-radio
+            v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_FORM_TYPE)"
+            :key="dict.value"
+            :label="dict.value"
           >
-            <i class="el-icon-question" style="padding-left: 5px"></i>
-          </el-tooltip>
-        </el-form-item>
-      </div>
+            {{ dict.label }}
+          </el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item v-if="formData.formType === 10" label="流程表单" prop="formId">
+        <el-select v-model="formData.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="formData.formType === 20"
+        label="表单提交路由"
+        prop="formCustomCreatePath"
+      >
+        <el-input
+          v-model="formData.formCustomCreatePath"
+          placeholder="请输入表单提交路由"
+          style="width: 330px"
+        />
+        <el-tooltip
+          class="item"
+          content="自定义表单的提交路径,使用 Vue 的路由地址,例如说:bpm/oa/leave/create.vue"
+          effect="light"
+          placement="top"
+        >
+          <i class="el-icon-question" style="padding-left: 5px"></i>
+        </el-tooltip>
+      </el-form-item>
+      <el-form-item v-if="formData.formType === 20" label="表单查看地址" prop="formCustomViewPath">
+        <el-input
+          v-model="formData.formCustomViewPath"
+          placeholder="请输入表单查看的组件地址"
+          style="width: 330px"
+        />
+        <el-tooltip
+          class="item"
+          content="自定义表单的查看组件地址,使用 Vue 的组件地址,例如说:bpm/oa/leave/detail.vue"
+          effect="light"
+          placement="top"
+        >
+          <i class="el-icon-question" style="padding-left: 5px"></i>
+        </el-tooltip>
+      </el-form-item>
+      <el-form-item label="是否可见" prop="visible">
+        <el-radio-group v-model="formData.visible">
+          <el-radio
+            v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
+            :key="dict.value as string"
+            :label="dict.value"
+          >
+            {{ dict.label }}
+          </el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="谁可以发起" prop="startUserIds">
+        <el-select
+          v-model="formData.startUserIds"
+          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-item label="流程管理员" prop="managerUserIds">
+        <el-select v-model="formData.managerUserIds" 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>
     <template #footer>
       <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
@@ -125,45 +155,65 @@
   </Dialog>
 </template>
 <script lang="ts" setup>
-import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { propTypes } from '@/utils/propTypes'
+import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict'
 import { ElMessageBox } from 'element-plus'
 import * as ModelApi from '@/api/bpm/model'
 import * as FormApi from '@/api/bpm/form'
 import { CategoryApi } from '@/api/bpm/category'
+import { BpmModelFormType, BpmModelType } from '@/utils/constants'
+import { UserVO } from '@/api/system/user'
+import * as UserApi from '@/api/system/user'
+import { useUserStoreWithOut } from '@/store/modules/user'
 
 defineOptions({ name: 'ModelForm' })
 
 const { t } = useI18n() // 国际化
 const message = useMessage() // 消息弹窗
-
+const userStore = useUserStoreWithOut() // 用户信息缓存
+const props = defineProps({
+  categoryId: propTypes.number
+})
 const dialogVisible = ref(false) // 弹窗的是否展示
 const dialogTitle = ref('') // 弹窗的标题
 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
 const formType = ref('') // 表单的类型:create - 新增;update - 修改
 const formData = ref({
-  formType: 10,
+  id: undefined,
   name: '',
+  key: '',
   category: undefined,
   icon: undefined,
   description: '',
+  type: BpmModelType.BPMN,
+  formType: BpmModelFormType.NORMAL,
   formId: '',
   formCustomCreatePath: '',
-  formCustomViewPath: ''
+  formCustomViewPath: '',
+  visible: true,
+  startUserIds: [],
+  managerUserIds: []
 })
 const formRules = reactive({
-  name: [{ required: true, message: '参数名称不能为空', trigger: 'blur' }],
-  key: [{ required: true, message: '参数键名不能为空', trigger: 'blur' }],
-  category: [{ required: true, message: '参数分类不能为空', trigger: 'blur' }],
-  icon: [{ required: true, message: '参数图标不能为空', trigger: 'blur' }],
-  value: [{ required: true, message: '参数键值不能为空', trigger: 'blur' }],
-  visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }]
+  name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }],
+  key: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }],
+  category: [{ required: true, message: '流程分类不能为空', trigger: 'blur' }],
+  icon: [{ required: true, message: '流程图标不能为空', trigger: 'blur' }],
+  type: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
+  formType: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
+  formId: [{ required: true, message: '流程表单不能为空', trigger: 'blur' }],
+  formCustomCreatePath: [{ required: true, message: '表单提交路由不能为空', trigger: 'blur' }],
+  formCustomViewPath: [{ required: true, message: '表单查看地址不能为空', trigger: 'blur' }],
+  visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
+  managerUserIds: [{ required: true, message: '流程管理员不能为空', trigger: 'blur' }]
 })
 const formRef = ref() // 表单 Ref
 const formList = ref([]) // 流程表单的下拉框的数据
 const categoryList = ref([]) // 流程分类列表
+const userList = ref<UserVO[]>([]) // 用户列表
 
 /** 打开弹窗 */
-const open = async (type: string, id?: number) => {
+const open = async (type: string, id?: string) => {
   dialogVisible.value = true
   dialogTitle.value = t('action.' + type)
   formType.value = type
@@ -176,11 +226,18 @@
     } finally {
       formLoading.value = false
     }
+  } else {
+    formData.value.managerUserIds.push(userStore.getUser.id)
   }
   // 获得流程表单的下拉框的数据
   formList.value = await FormApi.getFormSimpleList()
   // 查询流程分类列表
   categoryList.value = await CategoryApi.getCategorySimpleList()
+  // 查询用户列表
+  userList.value = await UserApi.getSimpleUserList()
+  if (props.categoryId) {
+    formData.value.category = props.categoryId
+  }
 }
 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
 
@@ -199,10 +256,9 @@
       await ModelApi.createModel(data)
       // 提示,引导用户做后续的操作
       await ElMessageBox.alert(
-        '<strong>新建模型成功!</strong>后续需要执行如下 3 个步骤:' +
-          '<div>1. 点击【修改流程】按钮,配置流程的分类、表单信息</div>' +
-          '<div>2. 点击【设计流程】按钮,绘制流程图</div>' +
-          '<div>3. 点击【发布流程】按钮,完成流程的最终发布</div>' +
+        '<strong>新建模型成功!</strong>后续需要执行如下 2 个步骤:' +
+          '<div>1. 点击【设计流程】按钮,绘制流程图</div>' +
+          '<div>2. 点击【发布流程】按钮,完成流程的最终发布</div>' +
           '另外,每次流程修改后,都需要点击【发布流程】按钮,才能正式生效!!!',
         '重要提示',
         {
@@ -225,14 +281,20 @@
 /** 重置表单 */
 const resetForm = () => {
   formData.value = {
-    formType: 10,
+    id: undefined,
     name: '',
+    key: '',
     category: undefined,
-    icon: '',
+    icon: undefined,
     description: '',
+    type: BpmModelType.BPMN,
+    formType: BpmModelFormType.NORMAL,
     formId: '',
     formCustomCreatePath: '',
-    formCustomViewPath: ''
+    formCustomViewPath: '',
+    visible: true,
+    startUserIds: [],
+    managerUserIds: []
   }
   formRef.value?.resetFields()
 }
diff --git a/src/views/bpm/model/ModelImportForm.vue b/src/views/bpm/model/ModelImportForm.vue
deleted file mode 100644
index 9a91e1d..0000000
--- a/src/views/bpm/model/ModelImportForm.vue
+++ /dev/null
@@ -1,141 +0,0 @@
-<template>
-  <Dialog v-model="dialogVisible" title="导入流程" width="400">
-    <div>
-      <el-upload
-        ref="uploadRef"
-        v-model:file-list="fileList"
-        :action="importUrl"
-        :auto-upload="false"
-        :data="formData"
-        :disabled="formLoading"
-        :headers="uploadHeaders"
-        :limit="1"
-        :on-error="submitFormError"
-        :on-exceed="handleExceed"
-        :on-success="submitFormSuccess"
-        accept=".bpmn, .xml"
-        drag
-        name="bpmnFile"
-      >
-        <Icon class="el-icon--upload" icon="ep:upload-filled" />
-        <div class="el-upload__text"> 将文件拖到此处,或 <em>点击上传</em></div>
-        <template #tip>
-          <div class="el-upload__tip" style="color: red">
-            提示:仅允许导入“bpm”或“xml”格式文件!
-          </div>
-          <div>
-            <el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px">
-              <el-form-item label="流程标识" prop="key">
-                <el-input
-                  v-model="formData.key"
-                  placeholder="请输入流标标识"
-                  style="width: 250px"
-                />
-              </el-form-item>
-              <el-form-item label="流程名称" prop="name">
-                <el-input v-model="formData.name" clearable placeholder="请输入流程名称" />
-              </el-form-item>
-              <el-form-item label="流程描述" prop="description">
-                <el-input v-model="formData.description" clearable type="textarea" />
-              </el-form-item>
-            </el-form>
-          </div>
-        </template>
-      </el-upload>
-    </div>
-    <template #footer>
-      <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
-      <el-button @click="dialogVisible = false">取 消</el-button>
-    </template>
-  </Dialog>
-</template>
-<script lang="ts" setup>
-import { getAccessToken, getTenantId } from '@/utils/auth'
-
-defineOptions({ name: 'ModelImportForm' })
-
-const message = useMessage() // 消息弹窗
-
-const dialogVisible = ref(false) // 弹窗的是否展示
-const formLoading = ref(false) // 表单的加载中
-const formData = ref({
-  key: '',
-  name: '',
-  description: ''
-})
-const formRules = reactive({
-  key: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }],
-  name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }]
-})
-const formRef = ref() // 表单 Ref
-const uploadRef = ref() // 上传 Ref
-const importUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/bpm/model/import'
-const uploadHeaders = ref() // 上传 Header 头
-const fileList = ref([]) // 文件列表
-
-/** 打开弹窗 */
-const open = async () => {
-  dialogVisible.value = true
-  resetForm()
-}
-defineExpose({ open }) // 提供 open 方法,用于打开弹窗
-
-/** 提交表单 */
-const submitForm = async () => {
-  // 校验表单
-  if (!formRef) return
-  const valid = await formRef.value.validate()
-  if (!valid) return
-  if (fileList.value.length == 0) {
-    message.error('请上传文件')
-    return
-  }
-  // 提交请求
-  uploadHeaders.value = {
-    Authorization: 'Bearer ' + getAccessToken(),
-    'tenant-id': getTenantId()
-  }
-  formLoading.value = true
-  uploadRef.value!.submit()
-}
-
-/** 文件上传成功 */
-const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
-const submitFormSuccess = async (response: any) => {
-  if (response.code !== 0) {
-    message.error(response.msg)
-    formLoading.value = false
-    return
-  }
-  // 提示成功
-  message.success('导入流程成功!请点击【设计流程】按钮,进行编辑保存后,才可以进行【发布流程】')
-  dialogVisible.value = false
-  // 发送操作成功的事件
-  emit('success')
-}
-
-/** 上传错误提示 */
-const submitFormError = (): void => {
-  message.error('导入流程失败,请您重新上传!')
-  formLoading.value = false
-}
-
-/** 重置表单 */
-const resetForm = () => {
-  // 重置上传状态和文件
-  formLoading.value = false
-  uploadRef.value?.clearFiles()
-  // 重置表单
-  formData.value = {
-    key: '',
-    name: '',
-    description: ''
-  }
-  formRef.value?.resetFields()
-}
-
-/** 文件数超出提示 */
-const handleExceed = (): void => {
-  message.error('最多只能上传一个文件!')
-}
-</script>
diff --git a/src/views/bpm/model/editor/index.vue b/src/views/bpm/model/editor/index.vue
index 29bca71..3e77369 100644
--- a/src/views/bpm/model/editor/index.vue
+++ b/src/views/bpm/model/editor/index.vue
@@ -58,17 +58,17 @@
 }
 
 /** 添加/修改模型 */
-const save = async (bpmnXml) => {
+const save = async (bpmnXml: string) => {
   const data = {
     ...model.value,
     bpmnXml: bpmnXml // bpmnXml 只是初始化流程图,后续修改无法通过它获得
   } as unknown as ModelApi.ModelVO
   // 提交
   if (data.id) {
-    await ModelApi.updateModel(data)
+    await ModelApi.updateModelBpmn(data)
     message.success('修改成功')
   } else {
-    await ModelApi.createModel(data)
+    await ModelApi.updateModelBpmn(data)
     message.success('新增成功')
   }
   // 跳转回去
diff --git a/src/views/bpm/model/index.vue b/src/views/bpm/model/index.vue
index a20ea4e..bf43d29 100644
--- a/src/views/bpm/model/index.vue
+++ b/src/views/bpm/model/index.vue
@@ -1,275 +1,124 @@
 <template>
   <ContentWrap>
-    <!-- 搜索工作栏 -->
-    <el-form
-      class="-mb-15px"
-      :model="queryParams"
-      ref="queryFormRef"
-      :inline="true"
-      label-width="68px"
-    >
-      <el-form-item label="流程标识" prop="key">
-        <el-input
-          v-model="queryParams.key"
-          placeholder="请输入流程标识"
-          clearable
-          @keyup.enter="handleQuery"
-          class="!w-240px"
-        />
-      </el-form-item>
-      <el-form-item label="流程名称" prop="name">
-        <el-input
-          v-model="queryParams.name"
-          placeholder="请输入流程名称"
-          clearable
-          @keyup.enter="handleQuery"
-          class="!w-240px"
-        />
-      </el-form-item>
-      <el-form-item label="流程分类" prop="category">
-        <el-select
-          v-model="queryParams.category"
-          placeholder="请选择流程分类"
-          clearable
-          class="!w-240px"
-        >
-          <el-option
-            v-for="category in categoryList"
-            :key="category.code"
-            :label="category.name"
-            :value="category.code"
-          />
-        </el-select>
-      </el-form-item>
-      <el-form-item>
-        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
-        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
-        <el-button
-          type="primary"
-          plain
-          @click="openForm('create')"
-          v-hasPermi="['bpm:model:create']"
-        >
-          <Icon icon="ep:plus" class="mr-5px" /> 新建流程
-        </el-button>
-        <el-button type="success" plain @click="openImportForm" v-hasPermi="['bpm:model:import']">
-          <Icon icon="ep:upload" class="mr-5px" /> 导入流程
-        </el-button>
-      </el-form-item>
-    </el-form>
-  </ContentWrap>
+    <div class="flex justify-between pl-20px items-center">
+      <h3 class="font-extrabold">流程模型</h3>
+      <!-- 搜索工作栏 -->
+      <el-form
+        v-if="!isCategorySorting"
+        class="-mb-15px flex mr-10px"
+        :model="queryParams"
+        ref="queryFormRef"
+        :inline="true"
+        label-width="68px"
+        @submit.prevent
+      >
+        <el-form-item prop="name" class="ml-auto">
+          <el-input
+            v-model="queryParams.name"
+            placeholder="搜索流程"
+            clearable
+            @keyup.enter="handleQuery"
+            class="!w-240px"
+          >
+            <template #prefix>
+              <Icon icon="ep:search" class="mx-10px" />
+            </template>
+          </el-input>
+        </el-form-item>
+        <!-- 右上角:新建模型、更多操作 -->
+        <el-form-item>
+          <el-button type="primary" @click="openForm('create')" v-hasPermi="['bpm:model:create']">
+            <Icon icon="ep:plus" class="mr-5px" /> 新建模型
+          </el-button>
+        </el-form-item>
+        <el-form-item>
+          <el-dropdown @command="(command) => handleCommand(command)" placement="bottom-end">
+            <el-button class="w-30px" plain>
+              <Icon icon="ep:setting" />
+            </el-button>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item command="handleCategoryAdd">
+                  <Icon icon="ep:circle-plus" :size="13" class="mr-5px" />
+                  新建分类
+                </el-dropdown-item>
+                <el-dropdown-item command="handleCategorySort">
+                  <Icon icon="fa:sort-amount-desc" :size="13" class="mr-5px" />
+                  分类排序
+                </el-dropdown-item>
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
+        </el-form-item>
+      </el-form>
+      <div class="mr-20px" v-else>
+        <el-button @click="handleCategorySortCancel"> 取 消 </el-button>
+        <el-button type="primary" @click="handleCategorySortSubmit"> 保存排序 </el-button>
+      </div>
+    </div>
 
-  <!-- 列表 -->
-  <ContentWrap>
-    <el-table v-loading="loading" :data="list">
-      <el-table-column label="流程标识" align="center" prop="key" width="200" />
-      <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>
-        </template>
-      </el-table-column>
-      <el-table-column label="流程图标" align="center" prop="icon" width="100">
-        <template #default="scope">
-          <el-image :src="scope.row.icon" class="w-32px h-32px" />
-        </template>
-      </el-table-column>
-      <el-table-column label="流程分类" align="center" prop="categoryName" width="100" />
-      <el-table-column label="表单信息" align="center" prop="formType" width="200">
-        <template #default="scope">
-          <el-button
-            v-if="scope.row.formType === 10"
-            type="primary"
-            link
-            @click="handleFormDetail(scope.row)"
+    <el-divider />
+
+    <!-- 按照分类,展示其所属的模型列表 -->
+    <div class="px-15px">
+      <draggable
+        :disabled="!isCategorySorting"
+        v-model="categoryGroup"
+        item-key="id"
+        :animation="400"
+      >
+        <template #item="{ element }">
+          <ContentWrap
+            class="rounded-lg transition-all duration-300 ease-in-out hover:shadow-xl"
+            v-loading="loading"
+            :body-style="{ padding: 0 }"
+            :key="element.id"
           >
-            <span>{{ scope.row.formName }}</span>
-          </el-button>
-          <el-button
-            v-else-if="scope.row.formType === 20"
-            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="createTime"
-        width="180"
-        :formatter="dateFormatter"
-      />
-      <el-table-column label="最新部署的流程定义" align="center">
-        <el-table-column
-          label="流程版本"
-          align="center"
-          prop="processDefinition.version"
-          width="100"
-        >
-          <template #default="scope">
-            <el-tag v-if="scope.row.processDefinition">
-              v{{ scope.row.processDefinition.version }}
-            </el-tag>
-            <el-tag v-else type="warning">未部署</el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column
-          label="激活状态"
-          align="center"
-          prop="processDefinition.version"
-          width="85"
-        >
-          <template #default="scope">
-            <el-switch
-              v-if="scope.row.processDefinition"
-              v-model="scope.row.processDefinition.suspensionState"
-              :active-value="1"
-              :inactive-value="2"
-              @change="handleChangeState(scope.row)"
+            <CategoryDraggableModel
+              :isCategorySorting="isCategorySorting"
+              :categoryInfo="element"
+              @success="getList"
             />
-          </template>
-        </el-table-column>
-        <el-table-column label="部署时间" align="center" prop="deploymentTime" width="180">
-          <template #default="scope">
-            <span v-if="scope.row.processDefinition">
-              {{ formatDate(scope.row.processDefinition.deploymentTime) }}
-            </span>
-          </template>
-        </el-table-column>
-      </el-table-column>
-      <el-table-column label="操作" align="center" width="240" fixed="right">
-        <template #default="scope">
-          <el-button
-            link
-            type="primary"
-            @click="openForm('update', scope.row.id)"
-            v-hasPermi="['bpm:model:update']"
-          >
-            修改流程
-          </el-button>
-          <el-button
-            link
-            type="primary"
-            @click="handleDesign(scope.row)"
-            v-hasPermi="['bpm:model:update']"
-          >
-            设计流程
-          </el-button>
-          <el-button
-            link
-            type="primary"
-            @click="handleDeploy(scope.row)"
-            v-hasPermi="['bpm:model:deploy']"
-          >
-            发布流程
-          </el-button>
-          <el-button
-            link
-            type="primary"
-            v-hasPermi="['bpm:process-definition:query']"
-            @click="handleDefinitionList(scope.row)"
-          >
-            流程定义
-          </el-button>
-          <el-button
-            link
-            type="danger"
-            @click="handleDelete(scope.row.id)"
-            v-hasPermi="['bpm:model:delete']"
-          >
-            删除
-          </el-button>
+          </ContentWrap>
         </template>
-      </el-table-column>
-    </el-table>
-    <!-- 分页 -->
-    <Pagination
-      :total="total"
-      v-model:page="queryParams.pageNo"
-      v-model:limit="queryParams.pageSize"
-      @pagination="getList"
-    />
+      </draggable>
+    </div>
   </ContentWrap>
 
   <!-- 表单弹窗:添加/修改流程 -->
   <ModelForm ref="formRef" @success="getList" />
-
-  <!-- 表单弹窗:导入流程 -->
-  <ModelImportForm ref="importFormRef" @success="getList" />
-
+  <!-- 表单弹窗:添加分类 -->
+  <CategoryForm ref="categoryFormRef" @success="getList" />
   <!-- 弹窗:表单详情 -->
   <Dialog title="表单详情" v-model="formDetailVisible" width="800">
     <form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" />
   </Dialog>
-
-  <!-- 弹窗:流程模型图的预览 -->
-  <Dialog title="流程图" v-model="bpmnDetailVisible" width="800">
-    <MyProcessViewer
-      key="designer"
-      v-model="bpmnXML"
-      :value="bpmnXML as any"
-      v-bind="bpmnControlForm"
-      :prefix="bpmnControlForm.prefix"
-    />
-  </Dialog>
 </template>
 
 <script lang="ts" setup>
-import { dateFormatter, formatDate } from '@/utils/formatTime'
-import { MyProcessViewer } from '@/components/bpmnProcessDesigner/package'
-import * as ModelApi from '@/api/bpm/model'
-import * as FormApi from '@/api/bpm/form'
-import ModelForm from './ModelForm.vue'
-import ModelImportForm from '@/views/bpm/model/ModelImportForm.vue'
-import { setConfAndFields2 } from '@/utils/formCreate'
+import draggable from 'vuedraggable'
 import { CategoryApi } from '@/api/bpm/category'
+import * as ModelApi from '@/api/bpm/model'
+import ModelForm from './ModelForm.vue'
+import CategoryForm from '../category/CategoryForm.vue'
+import { cloneDeep } from 'lodash-es'
+import CategoryDraggableModel from './CategoryDraggableModel.vue'
 
 defineOptions({ name: 'BpmModel' })
 
 const message = useMessage() // 消息弹窗
-const { t } = useI18n() // 国际化
-const { push } = useRouter() // 路由
-
 const loading = ref(true) // 列表的加载中
-const total = ref(0) // 列表的总页数
-const list = ref([]) // 列表的数据
+const isCategorySorting = ref(false) // 是否 category 正处于排序状态
 const queryParams = reactive({
-  pageNo: 1,
-  pageSize: 10,
-  key: undefined,
-  name: undefined,
-  category: undefined
+  name: undefined
 })
 const queryFormRef = ref() // 搜索的表单
-const categoryList = ref([]) // 流程分类列表
-
-/** 查询列表 */
-const getList = async () => {
-  loading.value = true
-  try {
-    const data = await ModelApi.getModelPage(queryParams)
-    list.value = data.list
-    total.value = data.total
-  } finally {
-    loading.value = false
-  }
-}
+const categoryGroup: any = ref([]) // 按照 category 分组的数据
+const originalData: any = ref([]) // 原始数据
 
 /** 搜索按钮操作 */
 const handleQuery = () => {
-  queryParams.pageNo = 1
   getList()
-}
-
-/** 重置按钮操作 */
-const resetQuery = () => {
-  queryFormRef.value.resetFields()
-  handleQuery()
 }
 
 /** 添加/修改操作 */
@@ -278,113 +127,95 @@
   formRef.value.open(type, id)
 }
 
-/** 添加/修改操作 */
-const importFormRef = ref()
-const openImportForm = () => {
-  importFormRef.value.open()
-}
-
-/** 删除按钮操作 */
-const handleDelete = async (id: number) => {
-  try {
-    // 删除的二次确认
-    await message.delConfirm()
-    // 发起删除
-    await ModelApi.deleteModel(id)
-    message.success(t('common.delSuccess'))
-    // 刷新列表
-    await getList()
-  } catch {}
-}
-
-/** 更新状态操作 */
-const handleChangeState = async (row) => {
-  const state = row.processDefinition.suspensionState
-  try {
-    // 修改状态的二次确认
-    const id = row.id
-    const statusState = state === 1 ? '激活' : '挂起'
-    const content = '是否确认' + statusState + '流程名字为"' + row.name + '"的数据项?'
-    await message.confirm(content)
-    // 发起修改状态
-    await ModelApi.updateModelState(id, state)
-    // 刷新列表
-    await getList()
-  } catch {
-    // 取消后,进行恢复按钮
-    row.processDefinition.suspensionState = state === 1 ? 2 : 1
-  }
-}
-
-/** 设计流程 */
-const handleDesign = (row) => {
-  push({
-    name: 'BpmModelEditor',
-    query: {
-      modelId: row.id
-    }
-  })
-}
-
-/** 发布流程 */
-const handleDeploy = async (row) => {
-  try {
-    // 删除的二次确认
-    await message.confirm('是否部署该流程!!')
-    // 发起部署
-    await ModelApi.deployModel(row.id)
-    message.success(t('部署成功'))
-    // 刷新列表
-    await getList()
-  } catch {}
-}
-
-/** 跳转到指定流程定义列表 */
-const handleDefinitionList = (row) => {
-  push({
-    name: 'BpmProcessDefinition',
-    query: {
-      key: row.key
-    }
-  })
-}
-
 /** 流程表单的详情按钮操作 */
 const formDetailVisible = ref(false)
 const formDetailPreview = ref({
   rule: [],
   option: {}
 })
-const handleFormDetail = async (row) => {
-  if (row.formType == 10) {
-    // 设置表单
-    const data = await FormApi.getForm(row.formId)
-    setConfAndFields2(formDetailPreview, data.conf, data.fields)
-    // 弹窗打开
-    formDetailVisible.value = true
-  } else {
-    await push({
-      path: row.formCustomCreatePath
-    })
+
+/** 右上角设置按钮 */
+const handleCommand = (command: string) => {
+  switch (command) {
+    case 'handleCategoryAdd':
+      handleCategoryAdd()
+      break
+    case 'handleCategorySort':
+      handleCategorySort()
+      break
+    default:
+      break
   }
 }
 
-/** 流程图的详情按钮操作 */
-const bpmnDetailVisible = ref(false)
-const bpmnXML = ref(null)
-const bpmnControlForm = ref({
-  prefix: 'flowable'
-})
-const handleBpmnDetail = async (row) => {
-  const data = await ModelApi.getModel(row.id)
-  bpmnXML.value = data.bpmnXml || ''
-  bpmnDetailVisible.value = true
+/** 新建分类 */
+const categoryFormRef = ref()
+const handleCategoryAdd = () => {
+  categoryFormRef.value.open('create')
+}
+
+/** 分类排序的提交 */
+const handleCategorySort = () => {
+  // 保存初始数据
+  originalData.value = cloneDeep(categoryGroup.value)
+  isCategorySorting.value = true
+}
+
+/** 分类排序的取消 */
+const handleCategorySortCancel = () => {
+  // 恢复初始数据
+  categoryGroup.value = cloneDeep(originalData.value)
+  isCategorySorting.value = false
+}
+
+/** 分类排序的保存 */
+const handleCategorySortSubmit = async () => {
+  // 保存排序
+  const ids = categoryGroup.value.map((item: any) => item.id)
+  await CategoryApi.updateCategorySortBatch(ids)
+  // 刷新列表
+  isCategorySorting.value = false
+  message.success('排序分类成功')
+  await getList()
+}
+
+/** 加载数据 */
+const getList = async () => {
+  loading.value = true
+  try {
+    // 查询模型 + 分裂的列表
+    const modelList = await ModelApi.getModelList(queryParams.name)
+    const categoryList = await CategoryApi.getCategorySimpleList()
+    // 按照 category 聚合
+    // 注意:必须一次性赋值给 categoryGroup,否则每次操作后,列表会重新渲染,滚动条的位置会偏离!!!
+    categoryGroup.value = categoryList.map((category: any) => ({
+      ...category,
+      modelList: modelList.filter((model: any) => model.categoryName == category.name)
+    }))
+  } finally {
+    loading.value = false
+  }
 }
 
 /** 初始化 **/
-onMounted(async () => {
-  await getList()
-  // 查询流程分类列表
-  categoryList.value = await CategoryApi.getCategorySimpleList()
+onMounted(() => {
+  getList()
 })
 </script>
+
+<style lang="scss" scoped>
+:deep() {
+  .el-table--fit .el-table__inner-wrapper:before {
+    height: 0;
+  }
+  .el-card {
+    border-radius: 8px;
+  }
+  .el-form--inline .el-form-item {
+    margin-right: 10px;
+  }
+  .el-divider--horizontal {
+    margin-top: 6px;
+  }
+}
+</style>
diff --git a/src/views/bpm/model/index_old.vue b/src/views/bpm/model/index_old.vue
new file mode 100644
index 0000000..9cb6420
--- /dev/null
+++ b/src/views/bpm/model/index_old.vue
@@ -0,0 +1,404 @@
+<template>
+  <doc-alert title="流程设计器(BPMN)" url="https://doc.iocoder.cn/bpm/model-designer-dingding/" />
+  <doc-alert
+    title="流程设计器(钉钉、飞书)"
+    url="https://doc.iocoder.cn/bpm/model-designer-bpmn/"
+  />
+  <doc-alert title="选择审批人、发起人自选" url="https://doc.iocoder.cn/bpm/assignee/" />
+  <doc-alert title="会签、或签、依次审批" url="https://doc.iocoder.cn/bpm/multi-instance/" />
+
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="流程标识" prop="key">
+        <el-input
+          v-model="queryParams.key"
+          placeholder="请输入流程标识"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="流程名称" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入流程名称"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="流程分类" prop="category">
+        <el-select
+          v-model="queryParams.category"
+          placeholder="请选择流程分类"
+          clearable
+          class="!w-240px"
+        >
+          <el-option
+            v-for="category in categoryList"
+            :key="category.code"
+            :label="category.name"
+            :value="category.code"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button
+          type="primary"
+          plain
+          @click="openForm('create')"
+          v-hasPermi="['bpm:model:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新建
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list">
+      <el-table-column label="流程名称" align="center" prop="name" min-width="200" />
+      <el-table-column label="流程图标" align="center" prop="icon" min-width="100">
+        <template #default="scope">
+          <el-image :src="scope.row.icon" class="h-32px w-32px" />
+        </template>
+      </el-table-column>
+      <el-table-column label="可见范围" align="center" prop="startUserIds" min-width="100">
+        <template #default="scope">
+          <el-text v-if="!scope.row.startUsers || scope.row.startUsers.length === 0">
+            全部可见
+          </el-text>
+          <el-text v-else-if="scope.row.startUsers.length == 1">
+            {{ scope.row.startUsers[0].nickname }}
+          </el-text>
+          <el-text v-else>
+            <el-tooltip
+              class="box-item"
+              effect="dark"
+              placement="top"
+              :content="scope.row.startUsers.map((user: any) => user.nickname).join('、')"
+            >
+              {{ scope.row.startUsers[0].nickname }}等 {{ scope.row.startUsers.length }} 人可见
+            </el-tooltip>
+          </el-text>
+        </template>
+      </el-table-column>
+      <el-table-column label="流程分类" align="center" prop="categoryName" min-width="100" />
+      <el-table-column label="表单信息" align="center" prop="formType" min-width="200">
+        <template #default="scope">
+          <el-button
+            v-if="scope.row.formType === 10"
+            type="primary"
+            link
+            @click="handleFormDetail(scope.row)"
+          >
+            <span>{{ scope.row.formName }}</span>
+          </el-button>
+          <el-button
+            v-else-if="scope.row.formType === 20"
+            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="deploymentTime" min-width="250">
+        <template #default="scope">
+          <span v-if="scope.row.processDefinition">
+            {{ formatDate(scope.row.processDefinition.deploymentTime) }}
+          </span>
+          <el-tag v-if="scope.row.processDefinition" class="ml-10px">
+            v{{ scope.row.processDefinition.version }}
+          </el-tag>
+          <el-tag v-else type="warning">未部署</el-tag>
+          <el-tag
+            v-if="scope.row.processDefinition?.suspensionState === 2"
+            type="warning"
+            class="ml-10px"
+          >
+            已停用
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" width="200" fixed="right">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+            v-hasPermi="['bpm:model:update']"
+            :disabled="!isManagerUser(scope.row)"
+          >
+            修改
+          </el-button>
+          <el-button
+            link
+            class="!ml-5px"
+            type="primary"
+            @click="handleDesign(scope.row)"
+            v-hasPermi="['bpm:model:update']"
+            :disabled="!isManagerUser(scope.row)"
+          >
+            设计
+          </el-button>
+          <el-button
+            link
+            class="!ml-5px"
+            type="primary"
+            @click="handleDeploy(scope.row)"
+            v-hasPermi="['bpm:model:deploy']"
+            :disabled="!isManagerUser(scope.row)"
+          >
+            发布
+          </el-button>
+          <el-dropdown
+            class="!align-middle ml-5px"
+            @command="(command) => handleCommand(command, scope.row)"
+            v-hasPermi="['bpm:process-definition:query', 'bpm:model:update', 'bpm:model:delete']"
+          >
+            <el-button type="primary" link>更多</el-button>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item
+                  command="handleDefinitionList"
+                  v-if="checkPermi(['bpm:process-definition:query'])"
+                >
+                  历史
+                </el-dropdown-item>
+                <el-dropdown-item
+                  command="handleChangeState"
+                  v-if="checkPermi(['bpm:model:update']) && scope.row.processDefinition"
+                  :disabled="!isManagerUser(scope.row)"
+                >
+                  {{ scope.row.processDefinition.suspensionState === 1 ? '停用' : '启用' }}
+                </el-dropdown-item>
+                <el-dropdown-item
+                  type="danger"
+                  command="handleDelete"
+                  v-if="checkPermi(['bpm:model:delete'])"
+                  :disabled="!isManagerUser(scope.row)"
+                >
+                  删除
+                </el-dropdown-item>
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改流程 -->
+  <ModelForm ref="formRef" @success="getList" />
+
+  <!-- 弹窗:表单详情 -->
+  <Dialog title="表单详情" v-model="formDetailVisible" width="800">
+    <form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" />
+  </Dialog>
+</template>
+
+<script lang="ts" setup>
+import { formatDate } from '@/utils/formatTime'
+import * as ModelApi from '@/api/bpm/model'
+import * as FormApi from '@/api/bpm/form'
+import ModelForm from './ModelForm.vue'
+import { setConfAndFields2 } from '@/utils/formCreate'
+import { CategoryApi } from '@/api/bpm/category'
+import { BpmModelType } from '@/utils/constants'
+import { checkPermi } from '@/utils/permission'
+import { useUserStoreWithOut } from '@/store/modules/user'
+
+defineOptions({ name: 'BpmModel' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+const { push } = useRouter() // 路由
+const userStore = useUserStoreWithOut() // 用户信息缓存
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  key: undefined,
+  name: undefined,
+  category: undefined
+})
+const queryFormRef = ref() // 搜索的表单
+const categoryList = ref([]) // 流程分类列表
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await ModelApi.getModelList(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** '更多'操作按钮 */
+const handleCommand = (command: string, row: any) => {
+  switch (command) {
+    case 'handleDefinitionList':
+      handleDefinitionList(row)
+      break
+    case 'handleDelete':
+      handleDelete(row)
+      break
+    case 'handleChangeState':
+      handleChangeState(row)
+      break
+    default:
+      break
+  }
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (row: any) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await ModelApi.deleteModel(row.id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 更新状态操作 */
+const handleChangeState = async (row: any) => {
+  const state = row.processDefinition.suspensionState
+  const newState = state === 1 ? 2 : 1
+  try {
+    // 修改状态的二次确认
+    const id = row.id
+    debugger
+    const statusState = state === 1 ? '停用' : '启用'
+    const content = '是否确认' + statusState + '流程名字为"' + row.name + '"的数据项?'
+    await message.confirm(content)
+    // 发起修改状态
+    await ModelApi.updateModelState(id, newState)
+    message.success(statusState + '成功')
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 设计流程 */
+const handleDesign = (row: any) => {
+  if (row.type == BpmModelType.BPMN) {
+    push({
+      name: 'BpmModelEditor',
+      query: {
+        modelId: row.id
+      }
+    })
+  } else {
+    push({
+      name: 'SimpleModelDesign',
+      query: {
+        modelId: row.id
+      }
+    })
+  }
+}
+
+/** 发布流程 */
+const handleDeploy = async (row: any) => {
+  try {
+    // 删除的二次确认
+    await message.confirm('是否部署该流程!!')
+    // 发起部署
+    await ModelApi.deployModel(row.id)
+    message.success(t('部署成功'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 跳转到指定流程定义列表 */
+const handleDefinitionList = (row) => {
+  push({
+    name: 'BpmProcessDefinition',
+    query: {
+      key: row.key
+    }
+  })
+}
+
+/** 流程表单的详情按钮操作 */
+const formDetailVisible = ref(false)
+const formDetailPreview = ref({
+  rule: [],
+  option: {}
+})
+const handleFormDetail = async (row: any) => {
+  if (row.formType == 10) {
+    // 设置表单
+    const data = await FormApi.getForm(row.formId)
+    setConfAndFields2(formDetailPreview, data.conf, data.fields)
+    // 弹窗打开
+    formDetailVisible.value = true
+  } else {
+    await push({
+      path: row.formCustomCreatePath
+    })
+  }
+}
+
+/** 判断是否可以操作 */
+const isManagerUser = (row: any) => {
+  const userId = userStore.getUser.id
+  return row.managerUserIds && row.managerUserIds.includes(userId)
+}
+
+/** 初始化 **/
+onMounted(async () => {
+  await getList()
+  // 查询流程分类列表
+  categoryList.value = await CategoryApi.getCategorySimpleList()
+})
+</script>
diff --git a/src/views/bpm/processExpression/ProcessExpressionForm.vue b/src/views/bpm/processExpression/ProcessExpressionForm.vue
index acf0667..2e5ed2e 100644
--- a/src/views/bpm/processExpression/ProcessExpressionForm.vue
+++ b/src/views/bpm/processExpression/ProcessExpressionForm.vue
@@ -15,7 +15,7 @@
           <el-radio
             v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
             :key="dict.value"
-            :label="dict.value"
+            :value="dict.value"
           >
             {{ dict.label }}
           </el-radio>
diff --git a/src/views/bpm/processInstance/create/ProcessDefinitionDetail.vue b/src/views/bpm/processInstance/create/ProcessDefinitionDetail.vue
new file mode 100644
index 0000000..610963e
--- /dev/null
+++ b/src/views/bpm/processInstance/create/ProcessDefinitionDetail.vue
@@ -0,0 +1,259 @@
+<template>
+  <ContentWrap :bodyStyle="{ padding: '10px 20px 0' }">
+    <div class="processInstance-wrap-main">
+      <el-scrollbar>
+        <div class="text-#878c93 h-15px">流程:{{ selectProcessDefinition.name }}</div>
+        <el-divider class="!my-8px" />
+
+        <!-- 中间主要内容 tab 栏 -->
+        <el-tabs v-model="activeTab">
+          <!-- 表单信息 -->
+          <el-tab-pane label="表单填写" name="form">
+            <div class="form-scroll-area">
+              <el-scrollbar>
+                <el-row>
+                  <el-col :span="17">
+                    <form-create
+                      :rule="detailForm.rule"
+                      v-model:api="fApi"
+                      v-model="detailForm.value"
+                      :option="detailForm.option"
+                      @submit="submitForm"
+                    />
+                  </el-col>
+
+                  <el-col :span="6" :offset="1">
+                    <!-- 流程时间线 -->
+                    <ProcessInstanceTimeline
+                      ref="timelineRef"
+                      :activity-nodes="activityNodes"
+                      :show-status-icon="false"
+                      @select-user-confirm="selectUserConfirm"
+                    />
+                  </el-col>
+                </el-row>
+              </el-scrollbar>
+            </div>
+          </el-tab-pane>
+          <!-- 流程图 -->
+          <el-tab-pane label="流程图" name="diagram">
+            <div class="form-scroll-area">
+              <!-- BPMN 流程图预览 -->
+              <ProcessInstanceBpmnViewer
+                :bpmn-xml="bpmnXML"
+                v-if="BpmModelType.BPMN === selectProcessDefinition.modelType"
+              />
+
+              <!-- Simple 流程图预览 -->
+              <ProcessInstanceSimpleViewer
+                :simple-json="simpleJson"
+                v-if="BpmModelType.SIMPLE === selectProcessDefinition.modelType"
+              />
+            </div>
+          </el-tab-pane>
+        </el-tabs>
+
+        <!-- 底部操作栏 -->
+        <div class="b-t-solid border-t-1px border-[var(--el-border-color)]">
+          <!-- 操作栏按钮 -->
+          <div
+            v-if="activeTab === 'form'"
+            class="h-50px bottom-10 text-14px flex items-center color-#32373c dark:color-#fff font-bold btn-container"
+          >
+            <el-button plain type="success" @click="submitForm">
+              <Icon icon="ep:select" />&nbsp; 发起
+            </el-button>
+            <el-button plain type="danger" @click="handleCancel">
+              <Icon icon="ep:close" />&nbsp; 取消
+            </el-button>
+          </div>
+        </div>
+      </el-scrollbar>
+    </div>
+  </ContentWrap>
+</template>
+<script lang="ts" setup>
+import { decodeFields, setConfAndFields2 } from '@/utils/formCreate'
+import { BpmModelType } from '@/utils/constants'
+import { CandidateStrategy } from '@/components/SimpleProcessDesignerV2/src/consts'
+import ProcessInstanceBpmnViewer from '../detail/ProcessInstanceBpmnViewer.vue'
+import ProcessInstanceSimpleViewer from '../detail/ProcessInstanceSimpleViewer.vue'
+import ProcessInstanceTimeline from '../detail/ProcessInstanceTimeline.vue'
+import type { ApiAttrs } from '@form-create/element-ui/types/config'
+import { useTagsViewStore } from '@/store/modules/tagsView'
+import * as ProcessInstanceApi from '@/api/bpm/processInstance'
+import * as DefinitionApi from '@/api/bpm/definition'
+import { ApprovalNodeInfo } from '@/api/bpm/processInstance'
+
+defineOptions({ name: 'ProcessDefinitionDetail' })
+const props = defineProps<{
+  selectProcessDefinition: any
+}>()
+const emit = defineEmits(['cancel'])
+
+const { push, currentRoute } = useRouter() // 路由
+const message = useMessage() // 消息弹窗
+const { delView } = useTagsViewStore() // 视图操作
+
+const detailForm: any = ref({
+  rule: [],
+  option: {},
+  value: {}
+}) // 流程表单详情
+const fApi = ref<ApiAttrs>()
+// 指定审批人
+const startUserSelectTasks: any = ref([]) // 发起人需要选择审批人或抄送人的任务列表
+const startUserSelectAssignees = ref({}) // 发起人选择审批人的数据
+const bpmnXML: any = ref(null) // BPMN 数据
+const simpleJson = ref<string | undefined>() // Simple 设计器数据 json 格式
+
+const activeTab = ref('form') // 当前的 Tab
+const activityNodes = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([]) // 审批节点信息
+
+/** 设置表单信息、获取流程图数据 **/
+const initProcessInfo = async (row: any, formVariables?: any) => {
+  // 重置指定审批人
+  startUserSelectTasks.value = []
+  startUserSelectAssignees.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)
+    await nextTick()
+    fApi.value?.btn.show(false) // 隐藏提交按钮
+    // 获取流程审批信息
+    await getApprovalDetail(row)
+
+    // 加载流程图
+    const processDefinitionDetail = await DefinitionApi.getProcessDefinition(row.id)
+    if (processDefinitionDetail) {
+      bpmnXML.value = processDefinitionDetail.bpmnXml
+      simpleJson.value = processDefinitionDetail.simpleModel
+    }
+    // 情况二:业务表单
+  } else if (row.formCustomCreatePath) {
+    await push({
+      path: row.formCustomCreatePath
+    })
+    // 这里暂时无需加载流程图,因为跳出到另外个 Tab;
+  }
+}
+
+/** 获取审批详情 */
+const getApprovalDetail = async (row: any) => {
+  try {
+    const data = await ProcessInstanceApi.getApprovalDetail({ processDefinitionId: row.id })
+    if (!data) {
+      message.error('查询不到审批详情信息!')
+      return
+    }
+
+    // 获取发起人自选的任务
+    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] = []
+      }
+    }
+
+    // 获取审批节点,显示 Timeline 的数据
+    activityNodes.value = data.activityNodes
+  } finally {
+  }
+}
+
+/** 提交按钮 */
+const submitForm = async () => {
+  if (!fApi.value || !props.selectProcessDefinition) {
+    return
+  }
+  // 如果有指定审批人,需要校验
+  if (startUserSelectTasks.value?.length > 0) {
+    for (const userTask of startUserSelectTasks.value) {
+      if (
+        Array.isArray(startUserSelectAssignees.value[userTask.id]) &&
+        startUserSelectAssignees.value[userTask.id].length === 0
+      )
+        return message.warning(`请选择${userTask.name}的候选人`)
+    }
+  }
+
+  // 提交请求
+  fApi.value.btn.loading(true)
+  try {
+    await ProcessInstanceApi.createProcessInstance({
+      processDefinitionId: props.selectProcessDefinition.id,
+      variables: detailForm.value.value,
+      startUserSelectAssignees: startUserSelectAssignees.value
+    })
+    // 提示
+    message.success('发起流程成功')
+    // 跳转回去
+    delView(unref(currentRoute))
+    await push({
+      name: 'BpmProcessInstanceMy'
+    })
+  } finally {
+    fApi.value.btn.loading(false)
+  }
+}
+
+/** 取消发起审批 */
+const handleCancel = () => {
+  emit('cancel')
+}
+
+/** 选择发起人 */
+const selectUserConfirm = (id: string, userList: any[]) => {
+  startUserSelectAssignees.value[id] = userList?.map((item: any) => item.id)
+}
+
+defineExpose({ initProcessInfo })
+</script>
+
+<style lang="scss" scoped>
+$wrap-padding-height: 20px;
+$wrap-margin-height: 15px;
+$button-height: 51px;
+$process-header-height: 105px;
+
+.processInstance-wrap-main {
+  height: calc(
+    100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px
+  );
+  max-height: calc(
+    100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px
+  );
+  overflow: auto;
+
+  .form-scroll-area {
+    height: calc(
+      100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
+        $process-header-height - 40px
+    );
+    max-height: calc(
+      100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
+        $process-header-height - 40px
+    );
+    overflow: auto;
+  }
+}
+
+.form-box {
+  :deep(.el-card) {
+    border: none;
+  }
+}
+</style>
diff --git a/src/views/bpm/processInstance/create/index.vue b/src/views/bpm/processInstance/create/index.vue
index 49da30a..284cbdb 100644
--- a/src/views/bpm/processInstance/create/index.vue
+++ b/src/views/bpm/processInstance/create/index.vue
@@ -1,132 +1,115 @@
 <template>
-
   <!-- 第一步,通过流程定义的列表,选择对应的流程 -->
-  <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 v-if="!selectProcessDefinition">
+    <el-input
+      v-model="searchName"
+      class="!w-50% mb-15px"
+      placeholder="请输入流程名称"
+      clearable
+      @input="handleQuery"
+      @clear="handleQuery"
+    >
+      <template #prefix>
+        <Icon icon="ep:search" />
+      </template>
+    </el-input>
+    <ContentWrap
+      :class="{ 'process-definition-container': filteredProcessDefinitionList?.length }"
+      class="position-relative pb-20px h-700px"
+      v-loading="loading"
+    >
+      <el-row v-if="filteredProcessDefinitionList?.length" :gutter="20" class="!flex-nowrap">
+        <el-col :span="5">
+          <div class="flex flex-col">
+            <div
+              v-for="category in availableCategories"
+              :key="category.code"
+              class="flex items-center p-10px cursor-pointer text-14px rounded-md"
+              :class="categoryActive.code === category.code ? 'text-#3e7bff bg-#e8eeff' : ''"
+              @click="handleCategoryClick(category)"
             >
-              <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>
+              {{ category.name }}
+            </div>
+          </div>
+        </el-col>
+        <el-col :span="19">
+          <el-scrollbar ref="scrollWrapper" height="700" @scroll="handleScroll">
+            <div
+              class="mb-20px pl-10px"
+              v-for="(definitions, categoryCode) in processDefinitionGroup"
+              :key="categoryCode"
+              :ref="`category-${categoryCode}`"
+            >
+              <h3 class="text-18px font-bold mb-10px mt-5px">
+                {{ getCategoryName(categoryCode as any) }}
+              </h3>
+              <div class="grid grid-cols-3 gap3">
+                <el-tooltip
+                  v-for="definition in definitions"
+                  :key="definition.id"
+                  :content="definition.description"
+                  :disabled="!definition.description || definition.description.trim().length === 0"
+                  placement="top"
+                >
+                  <el-card
+                    shadow="hover"
+                    class="cursor-pointer definition-item-card"
+                    @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-tooltip>
+              </div>
+            </div>
+          </el-scrollbar>
+        </el-col>
+      </el-row>
+      <el-empty class="!py-200px" :image-size="200" description="没有找到搜索结果" v-else />
+    </ContentWrap>
+  </template>
 
   <!-- 第二步,填写表单,进行流程的提交 -->
-  <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>
+  <ProcessDefinitionDetail
+    v-else
+    ref="processDefinitionDetailRef"
+    :selectProcessDefinition="selectProcessDefinition"
+    @cancel="selectProcessDefinition = undefined"
+  />
 </template>
+
 <script lang="ts" setup>
 import * as DefinitionApi from '@/api/bpm/definition'
 import * as ProcessInstanceApi from '@/api/bpm/processInstance'
-import { 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'
+import { CategoryApi, CategoryVO } from '@/api/bpm/category'
+import ProcessDefinitionDetail from './ProcessDefinitionDetail.vue'
+import { groupBy } from 'lodash-es'
 
 defineOptions({ name: 'BpmProcessInstanceCreate' })
 
+const { proxy } = getCurrentInstance() as any
 const route = useRoute() // 路由
-const { push, currentRoute } = useRouter() // 路由
 const message = useMessage() // 消息
-const { delView } = useTagsViewStore() // 视图操作
 
-const processInstanceId = route.query.processInstanceId
+const searchName = ref('') // 当前搜索关键字
+const processInstanceId: any = route.query.processInstanceId // 流程实例编号。场景:重新发起时
 const loading = ref(true) // 加载中
-const categoryList = ref([]) // 分类的列表
-const categoryActive = ref('') // 选中的分类
+const categoryList: any = ref([]) // 分类的列表
+const categoryActive: any = 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
-    })
+    // 所有流程分类数据
+    await getCategoryList()
+    // 所有流程定义数据
+    await getProcessDefinitionList()
 
     // 如果 processInstanceId 非空,说明是重新发起
     if (processInstanceId?.length > 0) {
@@ -136,7 +119,7 @@
         return
       }
       const processDefinition = processDefinitionList.value.find(
-        (item) => item.key == processInstance.processDefinition?.key
+        (item: any) => item.key == processInstance.processDefinition?.key
       )
       if (!processDefinition) {
         message.error('重新发起流程失败,原因:流程定义不存在')
@@ -149,108 +132,168 @@
   }
 }
 
-/** 选中分类对应的流程定义列表 */
-const categoryProcessDefinitionList = computed(() => {
-  return processDefinitionList.value.filter((item) => item.category == categoryActive.value)
+/** 获取所有流程分类数据 */
+const getCategoryList = async () => {
+  try {
+    // 流程分类
+    categoryList.value = await CategoryApi.getCategorySimpleList()
+  } finally {
+  }
+}
+
+/** 获取所有流程定义数据 */
+const getProcessDefinitionList = async () => {
+  try {
+    // 流程定义
+    processDefinitionList.value = await DefinitionApi.getProcessDefinitionList({
+      suspensionState: 1
+    })
+    // 初始化过滤列表为全部流程定义
+    filteredProcessDefinitionList.value = processDefinitionList.value
+
+    // 在获取完所有数据后,设置第一个有效分类为激活状态
+    if (availableCategories.value.length > 0 && !categoryActive.value?.code) {
+      categoryActive.value = availableCategories.value[0]
+    }
+  } finally {
+  }
+}
+
+/** 搜索流程 */
+const filteredProcessDefinitionList = ref([]) // 用于存储搜索过滤后的流程定义
+const handleQuery = () => {
+  if (searchName.value.trim()) {
+    // 如果有搜索关键字,进行过滤
+    filteredProcessDefinitionList.value = processDefinitionList.value.filter(
+      (definition: any) => definition.name.toLowerCase().includes(searchName.value.toLowerCase()) // 假设搜索依据是流程定义的名称
+    )
+  } else {
+    // 如果没有搜索关键字,恢复所有数据
+    filteredProcessDefinitionList.value = processDefinitionList.value
+  }
+}
+
+/** 流程定义的分组 */
+const processDefinitionGroup: any = computed(() => {
+  if (!processDefinitionList.value?.length) {
+    return {}
+  }
+
+  const grouped = groupBy(filteredProcessDefinitionList.value, 'category')
+  // 按照 categoryList 的顺序重新组织数据
+  const orderedGroup = {}
+  categoryList.value.forEach((category: any) => {
+    if (grouped[category.code]) {
+      orderedGroup[category.code] = grouped[category.code]
+    }
+  })
+  return orderedGroup
 })
 
-// ========== 表单相关 ==========
-const fApi = ref<ApiAttrs>()
-const detailForm = ref({
-  rule: [],
-  option: {},
-  value: {}
-}) // 流程表单详情
-const selectProcessDefinition = ref() // 选择的流程定义
+/** 左侧分类切换 */
+const handleCategoryClick = (category: any) => {
+  categoryActive.value = category
+  const categoryRef = proxy.$refs[`category-${category.code}`] // 获取点击分类对应的 DOM 元素
+  if (categoryRef?.length) {
+    const scrollWrapper = proxy.$refs.scrollWrapper // 获取右侧滚动容器
+    const categoryOffsetTop = categoryRef[0].offsetTop
 
-// 指定审批人
-const bpmnXML = ref(null) // BPMN 数据
-const startUserSelectTasks = ref([]) // 发起人需要选择审批人的用户任务列表
-const startUserSelectAssignees = ref({}) // 发起人选择审批人的数据
-const startUserSelectAssigneesFormRef = ref() // 发起人选择审批人的表单 Ref
-const startUserSelectAssigneesFormRules = ref({}) // 发起人选择审批人的表单 Rules
-const userList = ref<any[]>([]) // 用户列表
+    // 滚动到对应位置
+    scrollWrapper.scrollTo({ top: categoryOffsetTop, behavior: 'smooth' })
+  }
+}
+
+/** 通过分类 code 获取对应的名称 */
+const getCategoryName = (categoryCode: string) => {
+  return categoryList.value?.find((ctg: any) => ctg.code === categoryCode)?.name
+}
+
+// ========== 表单相关 ==========
+const selectProcessDefinition = ref() // 选择的流程定义
+const processDefinitionDetailRef = ref()
 
 /** 处理选择流程的按钮操作 **/
-const handleSelect = async (row, formVariables) => {
+const handleSelect = async (row, formVariables?) => {
   // 设置选择的流程
   selectProcessDefinition.value = row
+  // 初始化流程定义详情
+  await nextTick()
+  processDefinitionDetailRef.value?.initProcessInfo(row, formVariables)
+}
 
-  // 重置指定审批人
-  startUserSelectTasks.value = []
-  startUserSelectAssignees.value = {}
-  startUserSelectAssigneesFormRules.value = {}
+/** 处理滚动事件,和左侧分类联动 */
+const handleScroll = (e: any) => {
+  // 直接使用事件对象获取滚动位置
+  const scrollTop = e.scrollTop
 
-  // 情况一:流程表单
-  if (row.formType == 10) {
-    // 设置表单
-    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' }
-          ]
+  // 获取所有分类区域的位置信息
+  const categoryPositions = categoryList.value
+    .map((category: CategoryVO) => {
+      const categoryRef = proxy.$refs[`category-${category.code}`]
+      if (categoryRef?.[0]) {
+        return {
+          code: category.code,
+          offsetTop: categoryRef[0].offsetTop,
+          height: categoryRef[0].offsetHeight
         }
-        // 加载用户列表
-        userList.value = await UserApi.getSimpleUserList()
       }
+      return null
+    })
+    .filter(Boolean)
+
+  // 查找当前滚动位置对应的分类
+  let currentCategory = categoryPositions[0]
+  for (const position of categoryPositions) {
+    // 为了更好的用户体验,可以添加一个缓冲区域(比如 50px)
+    if (scrollTop >= position.offsetTop - 50) {
+      currentCategory = position
+    } else {
+      break
     }
-    // 情况二:业务表单
-  } else if (row.formCustomCreatePath) {
-    await push({
-      path: row.formCustomCreatePath
-    })
-    // 这里暂时无需加载流程图,因为跳出到另外个 Tab;
+  }
+
+  // 更新当前 active 的分类
+  if (currentCategory && categoryActive.value.code !== currentCategory.code) {
+    categoryActive.value = categoryList.value.find(
+      (c: CategoryVO) => c.code === currentCategory.code
+    )
   }
 }
 
-/** 提交按钮 */
-const submitForm = async (formData) => {
-  if (!fApi.value || !selectProcessDefinition.value) {
-    return
-  }
-  // 如果有指定审批人,需要校验
-  if (startUserSelectTasks.value?.length > 0) {
-    await startUserSelectAssigneesFormRef.value.validate()
+/** 过滤出有流程的分类列表。目的:只展示有流程的分类 */
+const availableCategories = computed(() => {
+  if (!categoryList.value?.length || !processDefinitionGroup.value) {
+    return []
   }
 
-  // 提交请求
-  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)
-  }
-}
+  // 获取所有有流程的分类代码
+  const availableCategoryCodes = Object.keys(processDefinitionGroup.value)
+
+  // 过滤出有流程的分类
+  return categoryList.value.filter((category: CategoryVO) =>
+    availableCategoryCodes.includes(category.code)
+  )
+})
 
 /** 初始化 */
 onMounted(() => {
   getList()
 })
 </script>
+
+<style lang="scss" scoped>
+.process-definition-container::before {
+  content: '';
+  border-left: 1px solid #e6e6e6;
+  position: absolute;
+  left: 20.8%;
+  height: 100%;
+}
+:deep() {
+  .definition-item-card {
+    .el-card__body {
+      padding: 14px;
+    }
+  }
+}
+</style>
diff --git a/src/views/bpm/processInstance/create/index_old.vue b/src/views/bpm/processInstance/create/index_old.vue
new file mode 100644
index 0000000..856a289
--- /dev/null
+++ b/src/views/bpm/processInstance/create/index_old.vue
@@ -0,0 +1,266 @@
+<template>
+
+  <!-- 第一步,通过流程定义的列表,选择对应的流程 -->
+  <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>
diff --git a/src/views/bpm/processInstance/detail/ProcessInstanceBpmnViewer.vue b/src/views/bpm/processInstance/detail/ProcessInstanceBpmnViewer.vue
index 8912593..781263d 100644
--- a/src/views/bpm/processInstance/detail/ProcessInstanceBpmnViewer.vue
+++ b/src/views/bpm/processInstance/detail/ProcessInstanceBpmnViewer.vue
@@ -1,54 +1,61 @@
 <template>
   <el-card v-loading="loading" class="box-card">
-    <template #header>
-      <span class="el-icon-picture-outline">流程图</span>
-    </template>
-    <MyProcessViewer
-      key="designer"
-      :activityData="activityList"
-      :prefix="bpmnControlForm.prefix"
-      :processInstanceData="processInstance"
-      :taskData="tasks"
-      :value="bpmnXml"
-      v-bind="bpmnControlForm"
-    />
+    <MyProcessViewer key="designer" :xml="view.bpmnXml" :view="view" class="process-viewer" />
   </el-card>
 </template>
 <script lang="ts" setup>
 import { propTypes } from '@/utils/propTypes'
 import { MyProcessViewer } from '@/components/bpmnProcessDesigner/package'
-import * as ActivityApi from '@/api/bpm/activity'
 
 defineOptions({ name: 'BpmProcessInstanceBpmnViewer' })
 
 const props = defineProps({
-  loading: propTypes.bool, // 是否加载中
-  id: propTypes.string, // 流程实例的编号
-  processInstance: propTypes.any, // 流程实例的信息
-  tasks: propTypes.array, // 流程任务的数组
-  bpmnXml: propTypes.string // BPMN XML
+  loading: propTypes.bool.def(false), // 是否加载中
+  bpmnXml: propTypes.string, // BPMN XML
+  modelView: propTypes.object
 })
 
-const bpmnControlForm = ref({
-  prefix: 'flowable'
-})
-const activityList = ref([]) // 任务列表
+const view = ref({
+  bpmnXml: ''
+}) // BPMN 流程图数据
+
 
 /** 只有 loading 完成时,才去加载流程列表 */
 watch(
-  () => props.loading,
-  async (value) => {
-    if (value && props.id) {
-      activityList.value = await ActivityApi.getActivityList({
-        processInstanceId: props.id
-      })
+  () => props.modelView,
+  async (newModelView) => {
+    // 加载最新
+    if (newModelView) {
+      //@ts-ignore
+      view.value = newModelView
     }
   }
 )
+
+/** 监听 bpmnXml */
+watch(
+  () => props.bpmnXml,
+  (value) => {
+    view.value.bpmnXml = value
+  }
+)
 </script>
-<style>
+<style lang="scss" scoped>
 .box-card {
+  height: 100%;
   width: 100%;
-  margin-bottom: 20px;
+  margin-bottom: 0;
+
+  :deep(.el-card__body) {
+    height: 100%;
+    padding: 0;
+  }
+
+  :deep(.process-viewer) {
+    height: 100% !important;
+    min-height: 100%;
+    width: 100%;
+    overflow: auto;
+  }
 }
 </style>
diff --git a/src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue b/src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue
new file mode 100644
index 0000000..b92be7e
--- /dev/null
+++ b/src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue
@@ -0,0 +1,889 @@
+<template>
+  <div
+    class="h-50px bottom-10 text-14px flex items-center color-#32373c dark:color-#fff font-bold btn-container"
+  >
+    <!-- 【通过】按钮 -->
+    <el-popover
+      :visible="popOverVisible.approve"
+      placement="top-end"
+      :width="420"
+      trigger="click"
+      v-if="runningTask && isHandleTaskStatus() && isShowButton(OperationButtonType.APPROVE)"
+    >
+      <template #reference>
+        <el-button plain type="success" @click="openPopover('approve')">
+          <Icon icon="ep:select" />&nbsp; {{ getButtonDisplayName(OperationButtonType.APPROVE) }}
+        </el-button>
+      </template>
+      <!-- 审批表单 -->
+      <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
+        <el-form
+          label-position="top"
+          class="mb-auto"
+          ref="formRef"
+          :model="genericForm"
+          :rules="genericRule"
+          label-width="100px"
+        >
+          <el-card v-if="runningTask?.formId > 0" class="mb-15px !-mt-10px">
+            <template #header>
+              <span class="el-icon-picture-outline"> 填写表单【{{ runningTask?.formName }}】 </span>
+            </template>
+            <form-create
+              v-model="approveForm.value"
+              v-model:api="approveFormFApi"
+              :option="approveForm.option"
+              :rule="approveForm.rule"
+            />
+          </el-card>
+          <el-form-item label="审批意见" prop="reason">
+            <el-input
+              v-model="genericForm.reason"
+              placeholder="请输入审批意见"
+              type="textarea"
+              :rows="4"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button :disabled="formLoading" type="success" @click="handleAudit(true)">
+              {{ getButtonDisplayName(OperationButtonType.APPROVE) }}
+            </el-button>
+            <el-button @click="popOverVisible.approve = false"> 取消 </el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </el-popover>
+
+    <!-- 【拒绝】按钮 -->
+    <el-popover
+      :visible="popOverVisible.reject"
+      placement="top-end"
+      :width="420"
+      trigger="click"
+      v-if="runningTask && isHandleTaskStatus() && isShowButton(OperationButtonType.REJECT)"
+    >
+      <template #reference>
+        <el-button class="mr-20px" plain type="danger" @click="openPopover('reject')">
+          <Icon icon="ep:close" />&nbsp; {{ getButtonDisplayName(OperationButtonType.REJECT) }}
+        </el-button>
+      </template>
+      <!-- 审批表单 -->
+      <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
+        <el-form
+          label-position="top"
+          class="mb-auto"
+          ref="formRef"
+          :model="genericForm"
+          :rules="genericRule"
+          label-width="100px"
+        >
+          <el-card v-if="runningTask?.formId > 0" class="mb-15px !-mt-10px">
+            <template #header>
+              <span class="el-icon-picture-outline"> 填写表单【{{ runningTask?.formName }}】 </span>
+            </template>
+            <form-create
+              v-model="approveForm.value"
+              v-model:api="approveFormFApi"
+              :option="approveForm.option"
+              :rule="approveForm.rule"
+            />
+          </el-card>
+          <el-form-item label="审批意见" prop="reason">
+            <el-input
+              v-model="genericForm.reason"
+              placeholder="请输入审批意见"
+              type="textarea"
+              :rows="4"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button :disabled="formLoading" type="danger" @click="handleAudit(false)">
+              {{ getButtonDisplayName(OperationButtonType.REJECT) }}
+            </el-button>
+            <el-button @click="popOverVisible.reject = false"> 取消 </el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </el-popover>
+
+    <!-- 【抄送】按钮 -->
+    <el-popover
+      :visible="popOverVisible.copy"
+      placement="top-start"
+      :width="420"
+      trigger="click"
+      v-if="runningTask && isHandleTaskStatus() && isShowButton(OperationButtonType.COPY)"
+    >
+      <template #reference>
+        <div @click="openPopover('copy')" class="hover-bg-gray-100 rounded-xl p-6px">
+          <Icon :size="14" icon="svg-icon:send" />&nbsp;
+          {{ getButtonDisplayName(OperationButtonType.COPY) }}
+        </div>
+      </template>
+      <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
+        <el-form
+          label-position="top"
+          class="mb-auto"
+          ref="formRef"
+          :model="genericForm"
+          :rules="genericRule"
+          label-width="100px"
+        >
+          <el-form-item label="抄送人" prop="copyUserIds">
+            <el-select
+              v-model="genericForm.copyUserIds"
+              clearable
+              style="width: 100%"
+              multiple
+              placeholder="请选择抄送人"
+            >
+              <el-option
+                v-for="item in userOptions"
+                :key="item.id"
+                :label="item.nickname"
+                :value="item.id"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="抄送意见" prop="copyReason">
+            <el-input
+              v-model="genericForm.copyReason"
+              clearable
+              placeholder="请输入抄送意见"
+              type="textarea"
+              :rows="3"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button :disabled="formLoading" type="primary" @click="handleCopy">
+              {{ getButtonDisplayName(OperationButtonType.COPY) }}
+            </el-button>
+            <el-button @click="popOverVisible.copy = false"> 取消 </el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </el-popover>
+
+    <!-- 【转交】按钮 -->
+    <el-popover
+      :visible="popOverVisible.transfer"
+      placement="top-start"
+      :width="420"
+      trigger="click"
+      v-if="runningTask && isHandleTaskStatus() && isShowButton(OperationButtonType.TRANSFER)"
+    >
+      <template #reference>
+        <div @click="openPopover('transfer')" class="hover-bg-gray-100 rounded-xl p-6px">
+          <Icon :size="14" icon="fa:share-square-o" />&nbsp;
+          {{ getButtonDisplayName(OperationButtonType.TRANSFER) }}
+        </div>
+      </template>
+      <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
+        <el-form
+          label-position="top"
+          class="mb-auto"
+          ref="formRef"
+          :model="genericForm"
+          :rules="genericRule"
+          label-width="100px"
+        >
+          <el-form-item label="新审批人" prop="assigneeUserId">
+            <el-select v-model="genericForm.assigneeUserId" clearable style="width: 100%">
+              <el-option
+                v-for="item in userOptions"
+                :key="item.id"
+                :label="item.nickname"
+                :value="item.id"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="审批意见" prop="reason">
+            <el-input
+              v-model="genericForm.reason"
+              clearable
+              placeholder="请输入审批意见"
+              type="textarea"
+              :rows="3"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button :disabled="formLoading" type="primary" @click="handleTransfer()">
+              {{ getButtonDisplayName(OperationButtonType.TRANSFER) }}
+            </el-button>
+            <el-button @click="popOverVisible.transfer = false"> 取消 </el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </el-popover>
+
+    <!-- 【委派】按钮 -->
+    <el-popover
+      :visible="popOverVisible.delegate"
+      placement="top-start"
+      :width="420"
+      trigger="click"
+      v-if="runningTask && isHandleTaskStatus() && isShowButton(OperationButtonType.DELEGATE)"
+    >
+      <template #reference>
+        <div @click="openPopover('delegate')" class="hover-bg-gray-100 rounded-xl p-6px">
+          <Icon :size="14" icon="ep:position" />&nbsp;
+          {{ getButtonDisplayName(OperationButtonType.DELEGATE) }}
+        </div>
+      </template>
+      <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
+        <el-form
+          label-position="top"
+          class="mb-auto"
+          ref="formRef"
+          :model="genericForm"
+          :rules="genericRule"
+          label-width="100px"
+        >
+          <el-form-item label="接收人" prop="delegateUserId">
+            <el-select v-model="genericForm.delegateUserId" clearable style="width: 100%">
+              <el-option
+                v-for="item in userOptions"
+                :key="item.id"
+                :label="item.nickname"
+                :value="item.id"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="审批意见" prop="reason">
+            <el-input
+              v-model="genericForm.reason"
+              clearable
+              placeholder="请输入审批意见"
+              type="textarea"
+              :rows="3"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button :disabled="formLoading" type="primary" @click="handleDelegate()">
+              {{ getButtonDisplayName(OperationButtonType.DELEGATE) }}
+            </el-button>
+            <el-button @click="popOverVisible.delegate = false"> 取消 </el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </el-popover>
+
+    <!-- 【加签】按钮 当前任务审批人为A,向前加签选了一个C,则需要C先审批,然后再是A审批,向后加签B,A审批完,需要B再审批完,才算完成这个任务节点 -->
+    <el-popover
+      :visible="popOverVisible.addSign"
+      placement="top-start"
+      :width="420"
+      trigger="click"
+      v-if="runningTask && isHandleTaskStatus() && isShowButton(OperationButtonType.ADD_SIGN)"
+    >
+      <template #reference>
+        <div @click="openPopover('addSign')" class="hover-bg-gray-100 rounded-xl p-6px">
+          <Icon :size="14" icon="ep:plus" />&nbsp;
+          {{ getButtonDisplayName(OperationButtonType.ADD_SIGN) }}
+        </div>
+      </template>
+      <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
+        <el-form
+          label-position="top"
+          class="mb-auto"
+          ref="formRef"
+          :model="genericForm"
+          :rules="genericRule"
+          label-width="100px"
+        >
+          <el-form-item label="加签处理人" prop="addSignUserIds">
+            <el-select v-model="genericForm.addSignUserIds" multiple clearable style="width: 100%">
+              <el-option
+                v-for="item in userOptions"
+                :key="item.id"
+                :label="item.nickname"
+                :value="item.id"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="审批意见" prop="reason">
+            <el-input
+              v-model="genericForm.reason"
+              clearable
+              placeholder="请输入审批意见"
+              type="textarea"
+              :rows="3"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button :disabled="formLoading" type="primary" @click="handlerAddSign('before')">
+              向前{{ getButtonDisplayName(OperationButtonType.ADD_SIGN) }}
+            </el-button>
+            <el-button :disabled="formLoading" type="primary" @click="handlerAddSign('after')">
+              向后{{ getButtonDisplayName(OperationButtonType.ADD_SIGN) }}
+            </el-button>
+            <el-button @click="popOverVisible.addSign = false"> 取消 </el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </el-popover>
+
+    <!-- 【减签】按钮 -->
+    <el-popover
+      :visible="popOverVisible.deleteSign"
+      placement="top-start"
+      :width="420"
+      trigger="click"
+      v-if="runningTask?.children.length > 0"
+    >
+      <template #reference>
+        <div @click="openPopover('deleteSign')" class="hover-bg-gray-100 rounded-xl p-6px">
+          <Icon :size="14" icon="ep:semi-select" />&nbsp; 减签
+        </div>
+      </template>
+      <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
+        <el-form
+          label-position="top"
+          class="mb-auto"
+          ref="formRef"
+          :model="genericForm"
+          :rules="genericRule"
+          label-width="100px"
+        >
+          <el-form-item label="减签人员" prop="deleteSignTaskId">
+            <el-select v-model="genericForm.deleteSignTaskId" clearable style="width: 100%">
+              <el-option
+                v-for="item in runningTask.children"
+                :key="item.id"
+                :label="getDeleteSignUserLabel(item)"
+                :value="item.id"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="审批意见" prop="reason">
+            <el-input
+              v-model="genericForm.reason"
+              clearable
+              placeholder="请输入审批意见"
+              type="textarea"
+              :rows="3"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button :disabled="formLoading" type="primary" @click="handlerDeleteSign()">
+              减签
+            </el-button>
+            <el-button @click="popOverVisible.deleteSign = false"> 取消 </el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </el-popover>
+
+    <!-- 【退回】按钮 -->
+    <el-popover
+      :visible="popOverVisible.return"
+      placement="top-start"
+      :width="420"
+      trigger="click"
+      v-if="runningTask && isHandleTaskStatus() && isShowButton(OperationButtonType.RETURN)"
+    >
+      <template #reference>
+        <div @click="openReturnPopover" class="hover-bg-gray-100 rounded-xl p-6px">
+          <Icon :size="14" icon="ep:back" />&nbsp;
+          {{ getButtonDisplayName(OperationButtonType.RETURN) }}
+        </div>
+      </template>
+      <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
+        <el-form
+          label-position="top"
+          class="mb-auto"
+          ref="formRef"
+          :model="genericForm"
+          :rules="genericRule"
+          label-width="100px"
+        >
+          <el-form-item label="退回节点" prop="targetTaskDefinitionKey">
+            <el-select v-model="genericForm.targetTaskDefinitionKey" clearable style="width: 100%">
+              <el-option
+                v-for="item in returnList"
+                :key="item.taskDefinitionKey"
+                :label="item.name"
+                :value="item.taskDefinitionKey"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="退回理由" prop="returnReason">
+            <el-input
+              v-model="genericForm.returnReason"
+              clearable
+              placeholder="请输入退回理由"
+              type="textarea"
+              :rows="3"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button :disabled="formLoading" type="primary" @click="handleReturn()">
+              {{ getButtonDisplayName(OperationButtonType.RETURN) }}
+            </el-button>
+            <el-button @click="popOverVisible.return = false"> 取消 </el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </el-popover>
+
+    <!--【取消】按钮 这个对应发起人的取消, 只有发起人可以取消 -->
+    <el-popover
+      :visible="popOverVisible.cancel"
+      placement="top-start"
+      :width="420"
+      trigger="click"
+      v-if="
+        userId === processInstance?.startUser?.id && !isEndProcessStatus(processInstance?.status)
+      "
+    >
+      <template #reference>
+        <div @click="openPopover('cancel')" class="hover-bg-gray-100 rounded-xl p-6px">
+          <Icon :size="14" icon="fa:mail-reply" />&nbsp; 取消
+        </div>
+      </template>
+      <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
+        <el-form
+          label-position="top"
+          class="mb-auto"
+          ref="formRef"
+          :model="genericForm"
+          :rules="genericRule"
+          label-width="100px"
+        >
+          <el-form-item label="取消理由" prop="cancelReason">
+            <span class="text-#878c93 text-12px">&nbsp; 取消后,该审批流程将自动结束</span>
+            <el-input
+              v-model="genericForm.cancelReason"
+              clearable
+              placeholder="请输入取消理由"
+              type="textarea"
+              :rows="3"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button :disabled="formLoading" type="primary" @click="handleCancel()">
+              取消
+            </el-button>
+            <el-button @click="popOverVisible.cancel = false"> 取消 </el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </el-popover>
+    <!-- 【再次提交】 按钮-->
+    <div
+      @click="handleReCreate()"
+      class="hover-bg-gray-100 rounded-xl p-6px"
+      v-if="
+        userId === processInstance?.startUser?.id &&
+        isEndProcessStatus(processInstance?.status) &&
+        processDefinition?.formType === 10
+      "
+    >
+      <Icon :size="14" icon="ep:refresh" />&nbsp; 再次提交
+    </div>
+  </div>
+</template>
+<script lang="ts" setup>
+import { useUserStoreWithOut } from '@/store/modules/user'
+import { setConfAndFields2 } from '@/utils/formCreate'
+import * as TaskApi from '@/api/bpm/task'
+import * as ProcessInstanceApi from '@/api/bpm/processInstance'
+import { propTypes } from '@/utils/propTypes'
+import {
+  OperationButtonType,
+  OPERATION_BUTTON_NAME
+} from '@/components/SimpleProcessDesignerV2/src/consts'
+import { BpmProcessInstanceStatus } from '@/utils/constants'
+
+defineOptions({ name: 'ProcessInstanceBtnContainer' })
+
+const router = useRouter() // 路由
+const message = useMessage() // 消息弹窗
+const { proxy } = getCurrentInstance() as any
+
+const userId = useUserStoreWithOut().getUser.id // 当前登录的编号
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const props = defineProps({
+  processInstance: propTypes.object, // 流程实例信息
+  processDefinition: propTypes.object, // 流程定义信息
+  userOptions: propTypes.any
+})
+
+const formLoading = ref(false) // 表单加载中
+const popOverVisible = ref({
+  approve: false,
+  reject: false,
+  transfer: false,
+  delegate: false,
+  addSign: false,
+  return: false,
+  copy: false,
+  cancel: false,
+  deleteSign: false
+}) // 气泡卡是否展示
+const returnList = ref([] as any) // 退回节点
+
+// ========== 审批信息 ==========
+const runningTask = ref<any>() // 运行中的任务
+const genericForm = ref<any>({}) // 通用表单
+const approveForm = ref<any>({}) // 审批通过时,额外的补充信息
+const approveFormFApi = ref<any>({}) // approveForms 的 fAPi
+const formRef = ref()
+const genericRule = reactive({
+  reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
+  returnReason: [{ required: true, message: '退回理由不能为空', trigger: 'blur' }],
+  cancelReason: [{ required: true, message: '取消理由不能为空', trigger: 'blur' }],
+  copyUserIds: [{ required: true, message: '抄送人不能为空', trigger: 'change' }],
+  assigneeUserId: [{ required: true, message: '新审批人不能为空', trigger: 'change' }],
+  delegateUserId: [{ required: true, message: '接收人不能为空', trigger: 'change' }],
+  addSignUserIds: [{ required: true, message: '加签处理人不能为空', trigger: 'change' }],
+  deleteSignTaskId: [{ required: true, message: '减签人员不能为空', trigger: 'change' }],
+  targetTaskDefinitionKey: [{ required: true, message: '退回节点不能为空', trigger: 'change' }]
+}) // 表单校验规则
+
+/** 监听 approveFormFApis,实现它对应的 form-create 初始化后,隐藏掉对应的表单提交按钮 */
+watch(
+  () => approveFormFApi.value,
+  (val) => {
+    val?.btn?.show(false)
+    val?.resetBtn?.show(false)
+  },
+  {
+    deep: true
+  }
+)
+
+/** 弹出退回气泡卡 */
+const openReturnPopover = async () => {
+  returnList.value = await TaskApi.getTaskListByReturn(runningTask.value.id)
+  if (returnList.value.length === 0) {
+    message.warning('当前没有可退回的节点')
+    return
+  }
+  await openPopover('return')
+}
+
+/** 弹出气泡卡 */
+const openPopover = async (type: string) => {
+  Object.keys(popOverVisible.value).forEach((item) => {
+    popOverVisible.value[item] = item === type
+  })
+  await nextTick()
+  formRef.value.resetFields()
+}
+
+/** 处理审批通过和不通过的操作 */
+const handleAudit = async (pass: boolean) => {
+  formLoading.value = true
+  try {
+    const genericFormRef = proxy.$refs['formRef']
+    // 1.2 校验表单
+    const elForm = unref(genericFormRef)
+    if (!elForm) return
+    const valid = await elForm.validate()
+    if (!valid) return
+
+    // 2.1 提交审批
+    const data = {
+      id: runningTask.value.id,
+      reason: genericForm.value.reason
+    }
+    if (pass) {
+      // 审批通过,并且有额外的 approveForm 表单,需要校验 + 拼接到 data 表单里提交
+      const formCreateApi = approveFormFApi.value
+      if (Object.keys(formCreateApi)?.length > 0) {
+        await formCreateApi.validate()
+        // @ts-ignore
+        data.variables = approveForm.value.value
+      }
+      await TaskApi.approveTask(data)
+      popOverVisible.value.approve = false
+      message.success('审批通过成功')
+    } else {
+      await TaskApi.rejectTask(data)
+      popOverVisible.value.reject = false
+      message.success('审批不通过成功')
+    }
+    // 2.2 加载最新数据
+    reload()
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 处理抄送 */
+const handleCopy = async () => {
+  formLoading.value = true
+  try {
+    const copyFormRef = proxy.$refs['formRef']
+    // 1. 校验表单
+    const elForm = unref(copyFormRef)
+    if (!elForm) return
+    const valid = await elForm.validate()
+    if (!valid) return
+    // 2. 提交抄送
+    const data = {
+      id: runningTask.value.id,
+      reason: genericForm.value.copyReason,
+      copyUserIds: genericForm.value.copyUserIds
+    }
+    await TaskApi.copyTask(data)
+    popOverVisible.value.copy = false
+    message.success('操作成功')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 处理转交 */
+const handleTransfer = async () => {
+  formLoading.value = true
+  try {
+    const transferFormRef = proxy.$refs['formRef']
+    // 1.1 校验表单
+    const elForm = unref(transferFormRef)
+    if (!elForm) return
+    const valid = await elForm.validate()
+    if (!valid) return
+    // 1.2 提交转交
+    const data = {
+      id: runningTask.value.id,
+      reason: genericForm.value.reason,
+      assigneeUserId: genericForm.value.assigneeUserId
+    }
+
+    await TaskApi.transferTask(data)
+    popOverVisible.value.transfer = false
+    message.success('操作成功')
+    // 2. 加载最新数据
+    reload()
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 处理委派 */
+const handleDelegate = async () => {
+  formLoading.value = true
+  try {
+    const deletegateFormRef = proxy.$refs['formRef']
+    // 1.1 校验表单
+    const elForm = unref(deletegateFormRef)
+    if (!elForm) return
+    const valid = await elForm.validate()
+    if (!valid) return
+    // 1.2 处理委派
+    const data = {
+      id: runningTask.value.id,
+      reason: genericForm.value.reason,
+      delegateUserId: genericForm.value.delegateUserId
+    }
+
+    await TaskApi.delegateTask(data)
+    popOverVisible.value.delegate = false
+    message.success('操作成功')
+    // 2. 加载最新数据
+    reload()
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 处理加签 */
+const handlerAddSign = async (type: string) => {
+  formLoading.value = true
+  try {
+    const transferFormRef = proxy.$refs['formRef']
+    // 1.1 校验表单
+    const elForm = unref(transferFormRef)
+    if (!elForm) return
+    const valid = await elForm.validate()
+    if (!valid) return
+    // 1.2 提交加签
+    const data = {
+      id: runningTask.value.id,
+      type,
+      reason: genericForm.value.reason,
+      userIds: genericForm.value.addSignUserIds
+    }
+    await TaskApi.signCreateTask(data)
+    message.success('操作成功')
+    popOverVisible.value.addSign = false
+    // 2 加载最新数据
+    reload()
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 处理退回 */
+const handleReturn = async () => {
+  formLoading.value = true
+  try {
+    const returnFormRef = proxy.$refs['formRef']
+    // 1.1 校验表单
+    const elForm = unref(returnFormRef)
+    if (!elForm) return
+    const valid = await elForm.validate()
+    if (!valid) return
+    // 1.2 提交退回
+    const data = {
+      id: runningTask.value.id,
+      reason: genericForm.value.returnReason,
+      targetTaskDefinitionKey: genericForm.value.targetTaskDefinitionKey
+    }
+
+    await TaskApi.returnTask(data)
+    popOverVisible.value.return = false
+    message.success('操作成功')
+    // 2 重新加载数据
+    reload()
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 处理取消 */
+const handleCancel = async () => {
+  formLoading.value = true
+  try {
+    const cancelFormRef = proxy.$refs['formRef']
+    // 1.1 校验表单
+    const elForm = unref(cancelFormRef)
+    if (!elForm) return
+    const valid = await elForm.validate()
+    if (!valid) return
+    // 1.2 提交取消
+    await ProcessInstanceApi.cancelProcessInstanceByStartUser(
+      props.processInstance.id,
+      genericForm.value.cancelReason
+    )
+    popOverVisible.value.return = false
+    message.success('操作成功')
+    // 2 重新加载数据
+    reload()
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 处理再次提交 */
+const handleReCreate = async () => {
+  // 跳转发起流程界面
+  await router.push({
+    name: 'BpmProcessInstanceCreate',
+    query: { processInstanceId: props.processInstance?.id }
+  })
+}
+
+/** 获取减签人员标签 */
+const getDeleteSignUserLabel = (task: any): string => {
+  const deptName = task?.assigneeUser?.deptName || task?.ownerUser?.deptName
+  const nickname = task?.assigneeUser?.nickname || task?.ownerUser?.nickname
+  return `${nickname} ( 所属部门:${deptName} )`
+}
+/** 处理减签 */
+const handlerDeleteSign = async () => {
+  formLoading.value = true
+  try {
+    const deleteFormRef = proxy.$refs['formRef']
+    // 1.1 校验表单
+    const elForm = unref(deleteFormRef)
+    if (!elForm) return
+    const valid = await elForm.validate()
+    if (!valid) return
+    // 1.2 提交减签
+    const data = {
+      id: genericForm.value.deleteSignTaskId,
+      reason: genericForm.value.reason
+    }
+    await TaskApi.signDeleteTask(data)
+    message.success('减签成功')
+    popOverVisible.value.deleteSign = false
+    // 2 加载最新数据
+    reload()
+  } finally {
+    formLoading.value = false
+  }
+}
+/** 重新加载数据 */
+const reload = () => {
+  emit('success')
+}
+
+/** 任务是否为处理中状态 */
+const isHandleTaskStatus = () => {
+  let canHandle = false
+  if (TaskApi.TaskStatusEnum.RUNNING === runningTask.value?.status) {
+    canHandle = true
+  }
+  return canHandle
+}
+
+/** 流程状态是否为结束状态 */
+const isEndProcessStatus = (status: number) => {
+  let isEndStatus = false
+  if (
+    BpmProcessInstanceStatus.APPROVE === status ||
+    BpmProcessInstanceStatus.REJECT === status ||
+    BpmProcessInstanceStatus.CANCEL === status
+  ) {
+    isEndStatus = true
+  }
+  return isEndStatus
+}
+
+/** 是否显示按钮 */
+const isShowButton = (btnType: OperationButtonType): boolean => {
+  let isShow = true
+  if (runningTask.value?.buttonsSetting && runningTask.value?.buttonsSetting[btnType]) {
+    isShow = runningTask.value.buttonsSetting[btnType].enable
+  }
+  return isShow
+}
+
+/** 获取按钮的显示名称 */
+const getButtonDisplayName = (btnType: OperationButtonType) => {
+  let displayName = OPERATION_BUTTON_NAME.get(btnType)
+  if (runningTask.value?.buttonsSetting && runningTask.value?.buttonsSetting[btnType]) {
+    displayName = runningTask.value.buttonsSetting[btnType].displayName
+  }
+  return displayName
+}
+
+const loadTodoTask = (task: any) => {
+  genericForm.value = {}
+  approveForm.value = {}
+  approveFormFApi.value = {}
+  runningTask.value = task
+  // 处理 approve 表单.
+  if (task && task.formId && task.formConf) {
+    const tempApproveForm = {}
+    setConfAndFields2(tempApproveForm, task.formConf, task.formFields, task.formVariables)
+    approveForm.value = tempApproveForm
+  } else {
+    approveForm.value = {} // 占位,避免为空
+  }
+}
+
+defineExpose({ loadTodoTask })
+</script>
+
+<style lang="scss" scoped>
+:deep(.el-affix--fixed) {
+  background-color: var(--el-bg-color);
+}
+
+.btn-container {
+  > div {
+    display: flex;
+    margin: 0 8px;
+    cursor: pointer;
+    align-items: center;
+
+    &:hover {
+      color: #6db5ff;
+    }
+  }
+}
+</style>
diff --git a/src/views/bpm/processInstance/detail/ProcessInstanceSimpleViewer.vue b/src/views/bpm/processInstance/detail/ProcessInstanceSimpleViewer.vue
new file mode 100644
index 0000000..0808bec
--- /dev/null
+++ b/src/views/bpm/processInstance/detail/ProcessInstanceSimpleViewer.vue
@@ -0,0 +1,168 @@
+<template>
+  <div v-loading="loading" class="process-viewer-container">
+    <SimpleProcessViewer
+      :flow-node="simpleModel"
+      :tasks="tasks"
+      :process-instance="processInstance"
+      class="process-viewer"
+    />
+  </div>
+</template>
+<script lang="ts" setup>
+import { propTypes } from '@/utils/propTypes'
+import { TaskStatusEnum } from '@/api/bpm/task'
+import { SimpleFlowNode, NodeType } from '@/components/SimpleProcessDesignerV2/src/consts'
+import { SimpleProcessViewer } from '@/components/SimpleProcessDesignerV2/src/'
+defineOptions({ name: 'BpmProcessInstanceSimpleViewer' })
+
+const props = defineProps({
+  loading: propTypes.bool.def(false), // 是否加载中
+  modelView: propTypes.object,
+  simpleJson: propTypes.string // Simple 模型结构数据 (json 格式)
+})
+const simpleModel = ref()
+// 用户任务
+const tasks = ref([])
+// 流程实例
+const processInstance = ref()
+
+/** 监控模型视图 包括任务列表、进行中的活动节点编号等 */
+watch(
+  () => props.modelView,
+  async (newModelView) => {
+    if (newModelView) {
+      tasks.value = newModelView.tasks
+      processInstance.value = newModelView.processInstance
+      // 已经拒绝的活动节点编号集合,只包括 UserTask
+      const rejectedTaskActivityIds: string[] = newModelView.rejectedTaskActivityIds
+      // 进行中的活动节点编号集合, 只包括 UserTask
+      const unfinishedTaskActivityIds: string[] = newModelView.unfinishedTaskActivityIds
+      // 已经完成的活动节点编号集合, 包括 UserTask、Gateway 等
+      const finishedActivityIds: string[] = newModelView.finishedTaskActivityIds
+      // 已经完成的连线节点编号集合,只包括 SequenceFlow
+      const finishedSequenceFlowActivityIds: string[] = newModelView.finishedSequenceFlowActivityIds
+      setSimpleModelNodeTaskStatus(
+        newModelView.simpleModel,
+        newModelView.processInstance.status,
+        rejectedTaskActivityIds,
+        unfinishedTaskActivityIds,
+        finishedActivityIds,
+        finishedSequenceFlowActivityIds
+      )
+      simpleModel.value = newModelView.simpleModel
+    }
+  }
+)
+/** 监控模型结构数据 */
+watch(
+  () => props.simpleJson,
+  async (value) => {
+    if (value) {
+      simpleModel.value = JSON.parse(value)
+    }
+  }
+)
+const setSimpleModelNodeTaskStatus = (
+  simpleModel: SimpleFlowNode | undefined,
+  processStatus: number,
+  rejectedTaskActivityIds: string[],
+  unfinishedTaskActivityIds: string[],
+  finishedActivityIds: string[],
+  finishedSequenceFlowActivityIds: string[]
+) => {
+  if (!simpleModel) {
+    return
+  }
+  // 结束节点
+  if (simpleModel.type === NodeType.END_EVENT_NODE) {
+    if (finishedActivityIds.includes(simpleModel.id)) {
+      simpleModel.activityStatus = processStatus
+    } else {
+      simpleModel.activityStatus = TaskStatusEnum.NOT_START
+    }
+    return
+  }
+
+  // 审批节点
+  if (
+    simpleModel.type === NodeType.START_USER_NODE ||
+    simpleModel.type === NodeType.USER_TASK_NODE
+  ) {
+    simpleModel.activityStatus = TaskStatusEnum.NOT_START
+    if (rejectedTaskActivityIds.includes(simpleModel.id)) {
+      simpleModel.activityStatus = TaskStatusEnum.REJECT
+    } else if (unfinishedTaskActivityIds.includes(simpleModel.id)) {
+      simpleModel.activityStatus = TaskStatusEnum.RUNNING
+    } else if (finishedActivityIds.includes(simpleModel.id)) {
+      simpleModel.activityStatus = TaskStatusEnum.APPROVE
+    }
+    // TODO 是不是还缺一个 cancel 的状态
+  }
+
+  // 抄送节点
+  if (simpleModel.type === NodeType.COPY_TASK_NODE) {
+    // 抄送节点 只有通过和未执行状态
+    if (finishedActivityIds.includes(simpleModel.id)) {
+      simpleModel.activityStatus = TaskStatusEnum.APPROVE
+    } else {
+      simpleModel.activityStatus = TaskStatusEnum.NOT_START
+    }
+  }
+  // 条件节点 对应 SequenceFlow
+  if (simpleModel.type === NodeType.CONDITION_NODE) {
+    // 条件节点。只有通过和未执行状态
+    if (finishedSequenceFlowActivityIds.includes(simpleModel.id)) {
+      simpleModel.activityStatus = TaskStatusEnum.APPROVE
+    } else {
+      simpleModel.activityStatus = TaskStatusEnum.NOT_START
+    }
+  }
+
+  // 网关节点
+  if (
+    simpleModel.type === NodeType.CONDITION_BRANCH_NODE ||
+    simpleModel.type === NodeType.PARALLEL_BRANCH_NODE ||
+    simpleModel.type === NodeType.INCLUSIVE_BRANCH_NODE
+  ) {
+    // 网关节点。只有通过和未执行状态
+    if (finishedActivityIds.includes(simpleModel.id)) {
+      simpleModel.activityStatus = TaskStatusEnum.APPROVE
+    } else {
+      simpleModel.activityStatus = TaskStatusEnum.NOT_START
+    }
+    simpleModel.conditionNodes?.forEach((node) => {
+      setSimpleModelNodeTaskStatus(
+        node,
+        processStatus,
+        rejectedTaskActivityIds,
+        unfinishedTaskActivityIds,
+        finishedActivityIds,
+        finishedSequenceFlowActivityIds
+      )
+    })
+  }
+
+  setSimpleModelNodeTaskStatus(
+    simpleModel.childNode,
+    processStatus,
+    rejectedTaskActivityIds,
+    unfinishedTaskActivityIds,
+    finishedActivityIds,
+    finishedSequenceFlowActivityIds
+  )
+}
+</script>
+
+<style lang="scss" scoped>
+.process-viewer-container {
+  height: 100%;
+  width: 100%;
+  
+  :deep(.process-viewer) {
+    height: 100% !important;
+    min-height: 100%;
+    width: 100%;
+    overflow: auto;
+  }
+}
+</style>
diff --git a/src/views/bpm/processInstance/detail/ProcessInstanceTaskList.vue b/src/views/bpm/processInstance/detail/ProcessInstanceTaskList.vue
index f82e800..8690e58 100644
--- a/src/views/bpm/processInstance/detail/ProcessInstanceTaskList.vue
+++ b/src/views/bpm/processInstance/detail/ProcessInstanceTaskList.vue
@@ -1,85 +1,50 @@
 <template>
-  <el-card v-loading="loading" class="box-card">
-    <template #header>
-      <span class="el-icon-picture-outline">审批记录</span>
-    </template>
-    <el-col :offset="3" :span="17">
-      <div class="block">
-        <el-timeline>
-          <el-timeline-item
-            v-if="processInstance.endTime"
-            :type="getProcessInstanceTimelineItemType(processInstance)"
-          >
-            <p style="font-weight: 700">
-              结束流程:在 {{ formatDate(processInstance?.endTime) }} 结束
-              <dict-tag
-                :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS"
-                :value="processInstance.status"
-              />
-            </p>
-          </el-timeline-item>
-          <el-timeline-item
-            v-for="(item, index) in tasks"
-            :key="index"
-            :type="getTaskTimelineItemType(item)"
-          >
-            <p style="font-weight: 700">
-              审批任务:{{ item.name }}
-              <dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="item.status" />
-              <el-button
-                class="ml-10px"
-                v-if="!isEmpty(item.children)"
-                @click="openChildrenTask(item)"
-                size="small"
-              >
-                <Icon icon="ep:memo" /> 子任务
-              </el-button>
-              <el-button
-                class="ml-10px"
-                size="small"
-                v-if="item.formId > 0"
-                @click="handleFormDetail(item)"
-              >
-                <Icon icon="ep:document" /> 查看表单
-              </el-button>
-            </p>
-            <el-card :body-style="{ padding: '10px' }">
-              <label v-if="item.assigneeUser" style="margin-right: 30px; font-weight: normal">
-                审批人:{{ item.assigneeUser.nickname }}
-                <el-tag size="small" type="info">{{ item.assigneeUser.deptName }}</el-tag>
-              </label>
-              <label v-if="item.createTime" style="font-weight: normal">创建时间:</label>
-              <label style="font-weight: normal; color: #8a909c">
-                {{ formatDate(item?.createTime) }}
-              </label>
-              <label v-if="item.endTime" style="margin-left: 30px; font-weight: normal">
-                审批时间:
-              </label>
-              <label v-if="item.endTime" style="font-weight: normal; color: #8a909c">
-                {{ formatDate(item?.endTime) }}
-              </label>
-              <label v-if="item.durationInMillis" style="margin-left: 30px; font-weight: normal">
-                耗时:
-              </label>
-              <label v-if="item.durationInMillis" style="font-weight: normal; color: #8a909c">
-                {{ formatPast2(item?.durationInMillis) }}
-              </label>
-              <p v-if="item.reason"> 审批建议:{{ item.reason }} </p>
-            </el-card>
-          </el-timeline-item>
-          <el-timeline-item type="success">
-            <p style="font-weight: 700">
-              发起流程:【{{ processInstance.startUser?.nickname }}】在
-              {{ formatDate(processInstance?.startTime) }} 发起【 {{ processInstance.name }} 】流程
-            </p>
-          </el-timeline-item>
-        </el-timeline>
-      </div>
-    </el-col>
-  </el-card>
+  <el-table :data="tasks" border header-cell-class-name="table-header-gray">
+    <el-table-column label="审批节点" prop="name" min-width="120" align="center" />
+    <el-table-column label="审批人" min-width="100" align="center">
+      <template #default="scope">
+        {{ scope.row.assigneeUser?.nickname || scope.row.ownerUser?.nickname }}
+      </template>
+    </el-table-column>
+    <el-table-column
+      :formatter="dateFormatter"
+      align="center"
+      label="开始时间"
+      prop="createTime"
+      min-width="140"
+    />
+    <el-table-column
+      :formatter="dateFormatter"
+      align="center"
+      label="结束时间"
+      prop="endTime"
+      min-width="140"
+    />
+    <el-table-column align="center" label="审批状态" prop="status" min-width="90">
+      <template #default="scope">
+        <dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="scope.row.status" />
+      </template>
+    </el-table-column>
+    <el-table-column align="center" label="审批建议" prop="reason" min-width="200">
+      <template #default="scope">
+        {{ scope.row.reason }}
+        <el-button
+          class="ml-10px"
+          size="small"
+          v-if="scope.row.formId > 0"
+          @click="handleFormDetail(scope.row)"
+        >
+          <Icon icon="ep:document" /> 查看表单
+        </el-button>
+      </template>
+    </el-table-column>
+    <el-table-column align="center" label="耗时" prop="durationInMillis" min-width="100">
+      <template #default="scope">
+        {{ formatPast2(scope.row.durationInMillis) }}
+      </template>
+    </el-table-column>
+  </el-table>
 
-  <!-- 弹窗:子任务  -->
-  <TaskSignList ref="taskSignListRef" @success="refresh" />
   <!-- 弹窗:表单 -->
   <Dialog title="表单详情" v-model="taskFormVisible" width="600">
     <form-create
@@ -91,61 +56,20 @@
   </Dialog>
 </template>
 <script lang="ts" setup>
-import { formatDate, formatPast2 } from '@/utils/formatTime'
+import { dateFormatter, formatPast2 } from '@/utils/formatTime'
 import { propTypes } from '@/utils/propTypes'
 import { DICT_TYPE } from '@/utils/dict'
-import { isEmpty } from '@/utils/is'
-import TaskSignList from './dialog/TaskSignList.vue'
 import type { ApiAttrs } from '@form-create/element-ui/types/config'
 import { setConfAndFields2 } from '@/utils/formCreate'
+import * as TaskApi from '@/api/bpm/task'
 
 defineOptions({ name: 'BpmProcessInstanceTaskList' })
 
-defineProps({
-  loading: propTypes.bool, // 是否加载中
-  processInstance: propTypes.object, // 流程实例
-  tasks: propTypes.arrayOf(propTypes.object) // 流程任务的数组
+const props = defineProps({
+  loading: propTypes.bool.def(false), // 是否加载中
+  id: propTypes.string // 流程实例的编号
 })
-
-/** 获得流程实例对应的颜色 */
-const getProcessInstanceTimelineItemType = (item: any) => {
-  if (item.status === 2) {
-    return 'success'
-  }
-  if (item.status === 3) {
-    return 'danger'
-  }
-  if (item.status === 4) {
-    return 'warning'
-  }
-  return ''
-}
-
-/** 获得任务对应的颜色 */
-const getTaskTimelineItemType = (item: any) => {
-  if ([0, 1, 6, 7].includes(item.status)) {
-    return 'primary'
-  }
-  if (item.status === 2) {
-    return 'success'
-  }
-  if (item.status === 3) {
-    return 'danger'
-  }
-  if (item.status === 4) {
-    return 'info'
-  }
-  if (item.status === 5) {
-    return 'warning'
-  }
-  return ''
-}
-
-/** 子任务 */
-const taskSignListRef = ref()
-const openChildrenTask = (item: any) => {
-  taskSignListRef.value.open(item)
-}
+const tasks = ref([]) // 流程任务的数组
 
 /** 查看表单 */
 const fApi = ref<ApiAttrs>() // form-create 的 API 操作类
@@ -155,7 +79,7 @@
   value: {}
 }) // 流程任务的表单详情
 const taskFormVisible = ref(false)
-const handleFormDetail = async (row) => {
+const handleFormDetail = async (row: any) => {
   // 设置表单
   setConfAndFields2(taskForm, row.formConf, row.formFields, row.formVariables)
   // 弹窗打开
@@ -167,9 +91,13 @@
   fApi.value?.fapi?.disabled(true)
 }
 
-/** 刷新数据 */
-const emit = defineEmits(['refresh']) // 定义 success 事件,用于操作成功后的回调
-const refresh = () => {
-  emit('refresh')
-}
+/** 只有 loading 完成时,才去加载流程列表 */
+watch(
+  () => props.loading,
+  async (value) => {
+    if (value) {
+      tasks.value = await TaskApi.getTaskListByProcessInstanceId(props.id)
+    }
+  }
+)
 </script>
diff --git a/src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue b/src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue
new file mode 100644
index 0000000..56b466a
--- /dev/null
+++ b/src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue
@@ -0,0 +1,292 @@
+<!-- 审批详情的右侧:审批流 -->
+<template>
+  <el-timeline class="pt-20px">
+    <!-- 遍历每个审批节点 -->
+    <el-timeline-item
+      v-for="(activity, index) in activityNodes"
+      :key="index"
+      size="large"
+      :icon="getApprovalNodeIcon(activity.status, activity.nodeType)"
+      :color="getApprovalNodeColor(activity.status)"
+    >
+      <template #dot>
+        <div
+          class="position-absolute left--10px top--6px rounded-full border border-solid border-#dedede w-30px h-30px flex justify-center items-center bg-#3f73f7 p-5px"
+        >
+          <img class="w-full h-full" :src="getApprovalNodeImg(activity.nodeType)" alt="" />
+          <div
+            v-if="showStatusIcon"
+            class="position-absolute top-17px left-17px rounded-full flex items-center p-1px border-2 border-white border-solid"
+            :style="{ backgroundColor: getApprovalNodeColor(activity.status) }"
+          >
+            <el-icon :size="11" color="#fff">
+              <component :is="getApprovalNodeIcon(activity.status, activity.nodeType)" />
+            </el-icon>
+          </div>
+        </div>
+      </template>
+      <div class="flex flex-col items-start gap2" :id="`activity-task-${activity.id}`">
+        <!-- 第一行:节点名称、时间 -->
+        <div class="flex w-full">
+          <div class="font-bold"> {{ activity.name }}</div>
+          <!-- 信息:时间 -->
+          <div
+            v-if="activity.status !== TaskStatusEnum.NOT_START"
+            class="text-#a5a5a5 text-13px mt-1 ml-auto"
+          >
+            {{ getApprovalNodeTime(activity) }}
+          </div>
+        </div>
+        <!-- 需要自定义选择审批人 -->
+        <div
+          class="flex flex-wrap gap2 items-center"
+          v-if="
+            isEmpty(activity.tasks) &&
+            isEmpty(activity.candidateUsers) &&
+            CandidateStrategy.START_USER_SELECT === activity.candidateStrategy
+          "
+        >
+          <!--  && activity.nodeType === NodeType.USER_TASK_NODE -->
+
+          <el-tooltip content="添加用户" placement="left">
+            <el-button
+              class="!px-6px"
+              @click="handleSelectUser(activity.id, customApproveUsers[activity.id])"
+            >
+              <img class="w-18px text-#ccc" src="@/assets/svgs/bpm/add-user.svg" alt="" />
+            </el-button>
+          </el-tooltip>
+          <div
+            v-for="(user, idx1) in customApproveUsers[activity.id]"
+            :key="idx1"
+            class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
+          >
+            <el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
+            <el-avatar class="!m-5px" :size="28" v-else>
+              {{ user.nickname.substring(0, 1) }}
+            </el-avatar>
+            {{ user.nickname }}
+          </div>
+        </div>
+        <div v-else class="flex items-center flex-wrap mt-1 gap2">
+          <!-- 情况一:遍历每个审批节点下的【进行中】task 任务 -->
+          <div v-for="(task, idx) in activity.tasks" :key="idx" class="flex flex-col pr-2 gap2">
+            <div
+              class="position-relative flex flex-wrap gap2"
+              v-if="task.assigneeUser || task.ownerUser"
+            >
+              <!-- 信息:头像昵称 -->
+              <div
+                class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
+              >
+                <template v-if="task.assigneeUser?.avatar || task.assigneeUser?.nickname">
+                  <el-avatar
+                    class="!m-5px"
+                    :size="28"
+                    v-if="task.assigneeUser?.avatar"
+                    :src="task.assigneeUser?.avatar"
+                  />
+                  <el-avatar class="!m-5px" :size="28" v-else>
+                    {{ task.assigneeUser?.nickname.substring(0, 1) }}
+                  </el-avatar>
+                  {{ task.assigneeUser?.nickname }}
+                </template>
+                <template v-else-if="task.ownerUser?.avatar || task.ownerUser?.nickname">
+                  <el-avatar
+                    class="!m-5px"
+                    :size="28"
+                    v-if="task.ownerUser?.avatar"
+                    :src="task.ownerUser?.avatar"
+                  />
+                  <el-avatar class="!m-5px" :size="28" v-else>
+                    {{ task.ownerUser?.nickname.substring(0, 1) }}
+                  </el-avatar>
+                  {{ task.ownerUser?.nickname }}
+                </template>
+                <!-- 信息:任务 ICON -->
+                <div
+                  v-if="showStatusIcon && onlyStatusIconShow.includes(task.status)"
+                  class="position-absolute top-19px left-23px rounded-full flex items-center p-1px border-2 border-white border-solid"
+                  :style="{ backgroundColor: statusIconMap2[task.status]?.color }"
+                >
+                  <Icon :size="11" :icon="statusIconMap2[task.status]?.icon" color="#FFFFFF" />
+                </div>
+              </div>
+            </div>
+            <teleport defer :to="`#activity-task-${activity.id}`">
+              <div
+                v-if="
+                  task.reason &&
+                  [NodeType.USER_TASK_NODE, NodeType.END_EVENT_NODE].includes(activity.nodeType)
+                "
+                class="text-#a5a5a5 text-13px mt-1 w-full bg-#f8f8fa p2 rounded-md"
+              >
+                审批意见:{{ task.reason }}
+              </div>
+            </teleport>
+          </div>
+          <!-- 情况二:遍历每个审批节点下的【候选的】task 任务。例如说,1)依次审批,2)未来的审批任务等 -->
+          <div
+            v-for="(user, idx1) in activity.candidateUsers"
+            :key="idx1"
+            class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
+          >
+            <el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
+            <el-avatar class="!m-5px" :size="28" v-else>
+              {{ user.nickname.substring(0, 1) }}
+            </el-avatar>
+            {{ user.nickname }}
+
+            <!-- 信息:任务 ICON -->
+            <div
+              v-if="showStatusIcon"
+              class="position-absolute top-20px left-24px rounded-full flex items-center p-1px border-2 border-white border-solid"
+              :style="{ backgroundColor: statusIconMap2['-1']?.color }"
+            >
+              <Icon :size="11" :icon="statusIconMap2['-1']?.icon" color="#FFFFFF" />
+            </div>
+          </div>
+        </div>
+      </div>
+    </el-timeline-item>
+  </el-timeline>
+
+  <!-- 用户选择弹窗 -->
+  <UserSelectForm ref="userSelectFormRef" @confirm="handleUserSelectConfirm" />
+</template>
+
+<script lang="ts" setup>
+import { formatDate } from '@/utils/formatTime'
+import * as ProcessInstanceApi from '@/api/bpm/processInstance'
+import { TaskStatusEnum } from '@/api/bpm/task'
+import { NodeType, CandidateStrategy } from '@/components/SimpleProcessDesignerV2/src/consts'
+import { isEmpty } from '@/utils/is'
+import { Check, Close, Loading, Clock, Minus, Delete } from '@element-plus/icons-vue'
+import starterSvg from '@/assets/svgs/bpm/starter.svg'
+import auditorSvg from '@/assets/svgs/bpm/auditor.svg'
+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'
+
+defineOptions({ name: 'BpmProcessInstanceTimeline' })
+withDefaults(
+  defineProps<{
+    activityNodes: ProcessInstanceApi.ApprovalNodeInfo[] // 审批节点信息
+    showStatusIcon?: boolean // 是否显示头像右下角状态图标
+  }>(),
+  {
+    showStatusIcon: true // 默认值为 true
+  }
+)
+
+// 审批节点
+const statusIconMap2 = {
+  // 未开始
+  '-1': { color: '#909398', icon: 'ep-clock' },
+  // 待审批
+  '0': { color: '#00b32a', icon: 'ep:loading' },
+  // 审批中
+  '1': { color: '#448ef7', icon: 'ep:loading' },
+  // 审批通过
+  '2': { color: '#00b32a', icon: 'ep:circle-check-filled' },
+  // 审批不通过
+  '3': { color: '#f46b6c', icon: 'fa-solid:times-circle' },
+  // 取消
+  '4': { color: '#cccccc', icon: 'ep:delete-filled' },
+  // 退回
+  '5': { color: '#f46b6c', icon: 'ep:remove-filled' },
+  // 委派中
+  '6': { color: '#448ef7', icon: 'ep:loading' },
+  // 审批通过中
+  '7': { color: '#00b32a', icon: 'ep:circle-check-filled' }
+}
+
+const statusIconMap = {
+  // 审批未开始
+  '-1': { color: '#909398', icon: Clock },
+  '0': { color: '#00b32a', icon: Clock },
+  // 审批中
+  '1': { color: '#448ef7', icon: Loading },
+  // 审批通过
+  '2': { color: '#00b32a', icon: Check },
+  // 审批不通过
+  '3': { color: '#f46b6c', icon: Close },
+  // 已取消
+  '4': { color: '#cccccc', icon: Delete },
+  // 退回
+  '5': { color: '#f46b6c', icon: Minus },
+  // 委派中
+  '6': { color: '#448ef7', icon: Loading },
+  // 审批通过中
+  '7': { color: '#00b32a', icon: Check }
+}
+
+const nodeTypeSvgMap = {
+  // 结束节点
+  [NodeType.END_EVENT_NODE]: { color: '#909398', svg: finishSvg },
+  // 发起人节点
+  [NodeType.START_USER_NODE]: { color: '#909398', svg: starterSvg },
+  // 审批人节点
+  [NodeType.USER_TASK_NODE]: { color: '#ff943e', svg: auditorSvg },
+  // 抄送人节点
+  [NodeType.COPY_TASK_NODE]: { color: '#3296fb', svg: copySvg },
+  // 条件分支节点
+  [NodeType.CONDITION_NODE]: { color: '#14bb83', svg: conditionSvg },
+  // 并行分支节点
+  [NodeType.PARALLEL_BRANCH_NODE]: { color: '#14bb83', svg: parallelSvg }
+}
+
+// 只有只有状态是 -1、0、1 才展示头像右小角状态小icon
+const onlyStatusIconShow = [-1, 0, 1]
+
+// timeline时间线上icon图标
+const getApprovalNodeImg = (nodeType: NodeType) => {
+  return nodeTypeSvgMap[nodeType]?.svg
+}
+
+const getApprovalNodeIcon = (taskStatus: number, nodeType: NodeType) => {
+  if (taskStatus == TaskStatusEnum.NOT_START) {
+    return statusIconMap[taskStatus]?.icon
+  }
+
+  if (
+    nodeType === NodeType.START_USER_NODE ||
+    nodeType === NodeType.USER_TASK_NODE ||
+    nodeType === NodeType.END_EVENT_NODE
+  ) {
+    return statusIconMap[taskStatus]?.icon
+  }
+}
+
+const getApprovalNodeColor = (taskStatus: number) => {
+  return statusIconMap[taskStatus]?.color
+}
+
+const getApprovalNodeTime = (node: ProcessInstanceApi.ApprovalNodeInfo) => {
+  if (node.nodeType === NodeType.START_USER_NODE && node.startTime) {
+    return `${formatDate(node.startTime)}`
+  }
+  if (node.endTime) {
+    return `${formatDate(node.endTime)}`
+  }
+  if (node.startTime) {
+    return `${formatDate(node.startTime)}`
+  }
+}
+
+// 选择自定义审批人
+const userSelectFormRef = ref()
+const handleSelectUser = (activityId, selectedList) => {
+  userSelectFormRef.value.open(activityId, selectedList)
+}
+const emit = defineEmits<{
+  selectUserConfirm: [id: any, userList: any[]]
+}>()
+const customApproveUsers: any = ref({}) // key:activityId,value:用户列表
+// 选择完成
+const handleUserSelectConfirm = (activityId: string, userList: any[]) => {
+  customApproveUsers.value[activityId] = userList || []
+  emit('selectUserConfirm', activityId, userList)
+}
+</script>
diff --git a/src/views/bpm/processInstance/detail/dialog/TaskSignDeleteForm.vue b/src/views/bpm/processInstance/detail/dialog/TaskSignDeleteForm.vue
index 19bb2dc..fc6d6b2 100644
--- a/src/views/bpm/processInstance/detail/dialog/TaskSignDeleteForm.vue
+++ b/src/views/bpm/processInstance/detail/dialog/TaskSignDeleteForm.vue
@@ -9,7 +9,7 @@
     >
       <el-form-item label="减签任务" prop="id">
         <el-radio-group v-model="formData.id">
-          <el-radio-button v-for="item in childrenTaskList" :key="item.id" :label="item.id">
+          <el-radio-button v-for="item in childrenTaskList" :key="item.id" :value="item.id">
             {{ item.name }}
             ({{ item.assigneeUser?.deptName || item.ownerUser?.deptName }} -
             {{ item.assigneeUser?.nickname || item.ownerUser?.nickname }})
diff --git a/src/views/bpm/processInstance/detail/index.vue b/src/views/bpm/processInstance/detail/index.vue
index da54769..0461a20 100644
--- a/src/views/bpm/processInstance/detail/index.vue
+++ b/src/views/bpm/processInstance/detail/index.vue
@@ -1,174 +1,167 @@
 <template>
-  <ContentWrap>
-    <!-- 审批信息 -->
-    <el-card
-      v-for="(item, index) in runningTasks"
-      :key="index"
-      v-loading="processInstanceLoading"
-      class="box-card"
-    >
-      <template #header>
-        <span class="el-icon-picture-outline">审批任务【{{ item.name }}】</span>
-      </template>
-      <el-col :offset="6" :span="16">
-        <el-form
-          :ref="'form' + index"
-          :model="auditForms[index]"
-          :rules="auditRule"
-          label-width="100px"
-        >
-          <el-form-item v-if="processInstance && processInstance.name" label="流程名">
-            {{ processInstance.name }}
-          </el-form-item>
-          <el-form-item v-if="processInstance && processInstance.startUser" label="流程发起人">
-            {{ processInstance?.startUser.nickname }}
-            <el-tag size="small" type="info">{{ processInstance?.startUser.deptName }}</el-tag>
-          </el-form-item>
-          <el-card v-if="runningTasks[index].formId > 0" class="mb-15px !-mt-10px">
-            <template #header>
-              <span class="el-icon-picture-outline">
-                填写表单【{{ runningTasks[index]?.formName }}】
-              </span>
-            </template>
-            <form-create
-              v-model="approveForms[index].value"
-              v-model:api="approveFormFApis[index]"
-              :option="approveForms[index].option"
-              :rule="approveForms[index].rule"
-            />
-          </el-card>
-          <el-form-item label="审批建议" prop="reason">
-            <el-input
-              v-model="auditForms[index].reason"
-              placeholder="请输入审批建议"
-              type="textarea"
-            />
-          </el-form-item>
-          <el-form-item label="抄送人" prop="copyUserIds">
-            <el-select v-model="auditForms[index].copyUserIds" multiple placeholder="请选择抄送人">
-              <el-option
-                v-for="item in userOptions"
-                :key="item.id"
-                :label="item.nickname"
-                :value="item.id"
-              />
-            </el-select>
-          </el-form-item>
-        </el-form>
-        <div style="margin-bottom: 20px; margin-left: 10%; font-size: 14px">
-          <el-button type="success" @click="handleAudit(item, true)">
-            <Icon icon="ep:select" />
-            通过
-          </el-button>
-          <el-button type="danger" @click="handleAudit(item, false)">
-            <Icon icon="ep:close" />
-            不通过
-          </el-button>
-          <el-button type="primary" @click="openTaskUpdateAssigneeForm(item.id)">
-            <Icon icon="ep:edit" />
-            转办
-          </el-button>
-          <el-button type="primary" @click="handleDelegate(item)">
-            <Icon icon="ep:position" />
-            委派
-          </el-button>
-          <el-button type="primary" @click="handleSign(item)">
-            <Icon icon="ep:plus" />
-            加签
-          </el-button>
-          <el-button type="warning" @click="handleBack(item)">
-            <Icon icon="ep:back" />
-            回退
-          </el-button>
-        </div>
-      </el-col>
-    </el-card>
-
-    <!-- 申请信息 -->
-    <el-card v-loading="processInstanceLoading" class="box-card">
-      <template #header>
-        <span class="el-icon-document">申请信息【{{ processInstance.name }}】</span>
-      </template>
-      <!-- 情况一:流程表单 -->
-      <el-col v-if="processInstance?.processDefinition?.formType === 10" :offset="6" :span="16">
-        <form-create
-          v-model="detailForm.value"
-          v-model:api="fApi"
-          :option="detailForm.option"
-          :rule="detailForm.rule"
+  <ContentWrap :bodyStyle="{ padding: '10px 20px 0' }" class="position-relative">
+    <div class="processInstance-wrap-main">
+      <el-scrollbar>
+        <img
+          class="position-absolute right-20px"
+          width="150"
+          :src="auditIconsMap[processInstance.status]"
+          alt=""
         />
-      </el-col>
-      <!-- 情况二:业务表单 -->
-      <div v-if="processInstance?.processDefinition?.formType === 20">
-        <BusinessFormComponent :id="processInstance.businessKey" />
-      </div>
-    </el-card>
+        <div class="text-#878c93 h-15px">编号:{{ id }}</div>
+        <el-divider class="!my-8px" />
+        <div class="flex items-center gap-5 mb-10px h-40px">
+          <div class="text-26px font-bold mb-5px">{{ processInstance.name }}</div>
+          <dict-tag
+            v-if="processInstance.status"
+            :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS"
+            :value="processInstance.status"
+          />
+        </div>
 
-    <!-- 审批记录 -->
-    <ProcessInstanceTaskList
-      :loading="tasksLoad"
-      :process-instance="processInstance"
-      :tasks="tasks"
-      @refresh="getTaskList"
-    />
+        <div class="flex items-center gap-5 mb-10px text-13px h-35px">
+          <div
+            class="bg-gray-100 h-35px rounded-3xl flex items-center p-8px gap-2 dark:color-gray-600"
+          >
+            <el-avatar
+              :size="28"
+              v-if="processInstance?.startUser?.avatar"
+              :src="processInstance?.startUser?.avatar"
+            />
+            <el-avatar :size="28" v-else-if="processInstance?.startUser?.nickname">
+              {{ processInstance?.startUser?.nickname.substring(0, 1) }}
+            </el-avatar>
+            {{ processInstance?.startUser?.nickname }}
+          </div>
+          <div class="text-#878c93"> {{ formatDate(processInstance.startTime) }} 提交 </div>
+        </div>
 
-    <!-- 高亮流程图 -->
-    <ProcessInstanceBpmnViewer
-      :id="`${id}`"
-      :bpmn-xml="bpmnXml"
-      :loading="processInstanceLoading"
-      :process-instance="processInstance"
-      :tasks="tasks"
-    />
+        <el-tabs v-model="activeTab">
+          <!-- 表单信息 -->
+          <el-tab-pane label="审批详情" name="form">
+            <div class="form-scroll-area">
+              <el-scrollbar>
+                <el-row>
+                  <el-col :span="17" class="!flex !flex-col formCol">
+                    <!-- 表单信息 -->
+                    <div
+                      v-loading="processInstanceLoading"
+                      class="form-box flex flex-col mb-30px flex-1"
+                    >
+                      <!-- 情况一:流程表单 -->
+                      <el-col v-if="processDefinition?.formType === 10">
+                        <form-create
+                          v-model="detailForm.value"
+                          v-model:api="fApi"
+                          :option="detailForm.option"
+                          :rule="detailForm.rule"
+                        />
+                      </el-col>
+                      <!-- 情况二:业务表单 -->
+                      <div v-if="processDefinition?.formType === 20">
+                        <BusinessFormComponent :id="processInstance.businessKey" />
+                      </div>
+                    </div>
+                  </el-col>
+                  <el-col :span="7">
+                    <!-- 审批记录时间线 -->
+                    <ProcessInstanceTimeline :activity-nodes="activityNodes" />
+                  </el-col>
+                </el-row>
+              </el-scrollbar>
+            </div>
+          </el-tab-pane>
 
-    <!-- 弹窗:转派审批人 -->
-    <TaskTransferForm ref="taskTransferFormRef" @success="getDetail" />
-    <!-- 弹窗:回退节点 -->
-    <TaskReturnForm ref="taskReturnFormRef" @success="getDetail" />
-    <!-- 弹窗:委派,将任务委派给别人处理,处理完成后,会重新回到原审批人手中-->
-    <TaskDelegateForm ref="taskDelegateForm" @success="getDetail" />
-    <!-- 弹窗:加签,当前任务审批人为A,向前加签选了一个C,则需要C先审批,然后再是A审批,向后加签B,A审批完,需要B再审批完,才算完成这个任务节点 -->
-    <TaskSignCreateForm ref="taskSignCreateFormRef" @success="getDetail" />
+          <!-- 流程图 -->
+          <el-tab-pane label="流程图" name="diagram">
+            <div class="form-scroll-area">
+              <ProcessInstanceSimpleViewer
+                v-show="
+                  processDefinition.modelType && processDefinition.modelType === BpmModelType.SIMPLE
+                "
+                :loading="processInstanceLoading"
+                :model-view="processModelView"
+              />
+              <ProcessInstanceBpmnViewer
+                v-show="
+                  processDefinition.modelType && processDefinition.modelType === BpmModelType.BPMN
+                "
+                :loading="processInstanceLoading"
+                :model-view="processModelView"
+              />
+            </div>
+          </el-tab-pane>
+
+          <!-- 流转记录 -->
+          <el-tab-pane label="流转记录" name="record">
+            <div class="form-scroll-area">
+              <el-scrollbar>
+                <ProcessInstanceTaskList :loading="processInstanceLoading" :id="id" />
+              </el-scrollbar>
+            </div>
+          </el-tab-pane>
+
+          <!-- 流转评论 TODO 待开发 -->
+          <el-tab-pane label="流转评论" name="comment" v-if="false">
+            <div class="form-scroll-area">
+              <el-scrollbar> 流转评论 </el-scrollbar>
+            </div>
+          </el-tab-pane>
+        </el-tabs>
+
+        <div class="b-t-solid border-t-1px border-[var(--el-border-color)]">
+          <!-- 操作栏按钮 -->
+          <ProcessInstanceOperationButton
+            ref="operationButtonRef"
+            :process-instance="processInstance"
+            :process-definition="processDefinition"
+            :userOptions="userOptions"
+            @success="refresh"
+          />
+        </div>
+      </el-scrollbar>
+    </div>
   </ContentWrap>
 </template>
 <script lang="ts" setup>
-import { useUserStore } from '@/store/modules/user'
+import { formatDate } from '@/utils/formatTime'
+import { DICT_TYPE } from '@/utils/dict'
+import { BpmModelType } from '@/utils/constants'
 import { setConfAndFields2 } from '@/utils/formCreate'
-import type { ApiAttrs } from '@form-create/element-ui/types/config'
-import * as DefinitionApi from '@/api/bpm/definition'
-import * as ProcessInstanceApi from '@/api/bpm/processInstance'
-import * as TaskApi from '@/api/bpm/task'
-import ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue'
-import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue'
-import TaskReturnForm from './dialog/TaskReturnForm.vue'
-import TaskDelegateForm from './dialog/TaskDelegateForm.vue'
-import TaskTransferForm from './dialog/TaskTransferForm.vue'
-import TaskSignCreateForm from './dialog/TaskSignCreateForm.vue'
 import { registerComponent } from '@/utils/routerHelper'
-import { isEmpty } from '@/utils/is'
+import type { ApiAttrs } from '@form-create/element-ui/types/config'
+import * as ProcessInstanceApi from '@/api/bpm/processInstance'
 import * as UserApi from '@/api/system/user'
+import ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue'
+import ProcessInstanceSimpleViewer from './ProcessInstanceSimpleViewer.vue'
+import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue'
+import ProcessInstanceOperationButton from './ProcessInstanceOperationButton.vue'
+import ProcessInstanceTimeline from './ProcessInstanceTimeline.vue'
+import { FieldPermissionType } from '@/components/SimpleProcessDesignerV2/src/consts'
+import { TaskStatusEnum } from '@/api/bpm/task'
+import runningSvg from '@/assets/svgs/bpm/running.svg'
+import approveSvg from '@/assets/svgs/bpm/approve.svg'
+import rejectSvg from '@/assets/svgs/bpm/reject.svg'
+import cancelSvg from '@/assets/svgs/bpm/cancel.svg'
 
 defineOptions({ name: 'BpmProcessInstanceDetail' })
-
-const { query } = useRoute() // 查询参数
+const props = defineProps<{
+  id: string // 流程实例的编号
+  taskId?: string // 任务编号
+  activityId?: string //流程活动编号,用于抄送查看
+}>()
 const message = useMessage() // 消息弹窗
-const { proxy } = getCurrentInstance() as any
-
-const userId = useUserStore().getUser.id // 当前登录的编号
-const id = query.id as unknown as string // 流程实例的编号
 const processInstanceLoading = ref(false) // 流程实例的加载中
 const processInstance = ref<any>({}) // 流程实例
-const bpmnXml = ref('') // BPMN XML
-const tasksLoad = ref(true) // 任务的加载中
-const tasks = ref<any[]>([]) // 任务列表
-// ========== 审批信息 ==========
-const runningTasks = ref<any[]>([]) // 运行中的任务
-const auditForms = ref<any[]>([]) // 审批任务的表单
-const auditRule = reactive({
-  reason: [{ required: true, message: '审批建议不能为空', trigger: 'blur' }]
-})
-const approveForms = ref<any[]>([]) // 审批通过时,额外的补充信息
-const approveFormFApis = ref<ApiAttrs[]>([]) // approveForms 的 fAPi
+const processDefinition = ref<any>({}) // 流程定义
+const processModelView = ref<any>({}) // 流程模型视图
+const operationButtonRef = ref() // 操作按钮组件 ref
+const auditIconsMap = {
+  [TaskStatusEnum.RUNNING]: runningSvg,
+  [TaskStatusEnum.APPROVE]: approveSvg,
+  [TaskStatusEnum.REJECT]: rejectSvg,
+  [TaskStatusEnum.CANCEL]: cancelSvg
+}
 
 // ========== 申请信息 ==========
 const fApi = ref<ApiAttrs>() //
@@ -178,198 +171,123 @@
   value: {}
 }) // 流程实例的表单详情
 
-/** 监听 approveFormFApis,实现它对应的 form-create 初始化后,隐藏掉对应的表单提交按钮 */
-watch(
-  () => approveFormFApis.value,
-  (value) => {
-    value?.forEach((api) => {
-      api.btn.show(false)
-      api.resetBtn.show(false)
-    })
-  },
-  {
-    deep: true
-  }
-)
-
-/** 处理审批通过和不通过的操作 */
-const handleAudit = async (task, pass) => {
-  // 1.1 获得对应表单
-  const index = runningTasks.value.indexOf(task)
-  const auditFormRef = proxy.$refs['form' + index][0]
-  // 1.2 校验表单
-  const elForm = unref(auditFormRef)
-  if (!elForm) return
-  const valid = await elForm.validate()
-  if (!valid) return
-
-  // 2.1 提交审批
-  const data = {
-    id: task.id,
-    reason: auditForms.value[index].reason,
-    copyUserIds: auditForms.value[index].copyUserIds
-  }
-  if (pass) {
-    // 审批通过,并且有额外的 approveForm 表单,需要校验 + 拼接到 data 表单里提交
-    const formCreateApi = approveFormFApis.value[index]
-    if (formCreateApi) {
-      await formCreateApi.validate()
-      data.variables = approveForms.value[index].value
-    }
-    await TaskApi.approveTask(data)
-    message.success('审批通过成功')
-  } else {
-    await TaskApi.rejectTask(data)
-    message.success('审批不通过成功')
-  }
-  // 2.2 加载最新数据
-  getDetail()
-}
-
-/** 转派审批人 */
-const taskTransferFormRef = ref()
-const openTaskUpdateAssigneeForm = (id: string) => {
-  taskTransferFormRef.value.open(id)
-}
-
-/** 处理审批退回的操作 */
-const taskDelegateForm = ref()
-const handleDelegate = async (task) => {
-  taskDelegateForm.value.open(task.id)
-}
-
-/** 处理审批退回的操作 */
-const taskReturnFormRef = ref()
-const handleBack = async (task: any) => {
-  taskReturnFormRef.value.open(task.id)
-}
-
-/** 处理审批加签的操作 */
-const taskSignCreateFormRef = ref()
-const handleSign = async (task: any) => {
-  taskSignCreateFormRef.value.open(task.id)
-}
-
 /** 获得详情 */
 const getDetail = () => {
-  // 1. 获得流程实例相关
-  getProcessInstance()
-  // 2. 获得流程任务列表(审批记录)
-  getTaskList()
+  getApprovalDetail()
+
+  getProcessModelView()
 }
 
 /** 加载流程实例 */
-const BusinessFormComponent = ref(null) // 异步组件
-const getProcessInstance = async () => {
+const BusinessFormComponent = ref<any>(null) // 异步组件
+/** 获取审批详情 */
+const getApprovalDetail = async () => {
+  processInstanceLoading.value = true
   try {
-    processInstanceLoading.value = true
-    const data = await ProcessInstanceApi.getProcessInstance(id)
+    const param = {
+      processInstanceId: props.id,
+      activityId: props.activityId,
+      taskId: props.taskId
+    }
+    const data = await ProcessInstanceApi.getApprovalDetail(param)
     if (!data) {
+      message.error('查询不到审批详情信息!')
+      return
+    }
+    if (!data.processDefinition || !data.processInstance) {
       message.error('查询不到流程信息!')
       return
     }
-    processInstance.value = data
+    processInstance.value = data.processInstance
+    processDefinition.value = data.processDefinition
 
     // 设置表单信息
-    const processDefinition = data.processDefinition
-    if (processDefinition.formType === 10) {
-      setConfAndFields2(
-        detailForm,
-        processDefinition.formConf,
-        processDefinition.formFields,
-        data.formVariables
-      )
+    if (processDefinition.value.formType === 10) {
+      // 获取表单字段权限
+      const formFieldsPermission = data.formFieldsPermission
+
+      if (detailForm.value.rule.length > 0) {
+        // 避免刷新 form-create 显示不了
+        detailForm.value.value = processInstance.value.formVariables
+      } else {
+        setConfAndFields2(
+          detailForm,
+          processDefinition.value.formConf,
+          processDefinition.value.formFields,
+          processInstance.value.formVariables
+        )
+      }
       nextTick().then(() => {
         fApi.value?.btn.show(false)
         fApi.value?.resetBtn.show(false)
+        //@ts-ignore
         fApi.value?.disabled(true)
+        // 设置表单字段权限
+        if (formFieldsPermission) {
+          Object.keys(data.formFieldsPermission).forEach((item) => {
+            setFieldPermission(item, formFieldsPermission[item])
+          })
+        }
       })
     } else {
       // 注意:data.processDefinition.formCustomViewPath 是组件的全路径,例如说:/crm/contract/detail/index.vue
       BusinessFormComponent.value = registerComponent(data.processDefinition.formCustomViewPath)
     }
 
-    // 加载流程图
-    bpmnXml.value = (
-      await DefinitionApi.getProcessDefinition(processDefinition.id as number)
-    )?.bpmnXml
+    // 获取审批节点,显示 Timeline 的数据
+    activityNodes.value = data.activityNodes
+
+    // 获取待办任务显示操作按钮
+    operationButtonRef.value?.loadTodoTask(data.todoTask)
   } finally {
     processInstanceLoading.value = false
   }
 }
 
-/** 加载任务列表 */
-const getTaskList = async () => {
-  runningTasks.value = []
-  auditForms.value = []
-  approveForms.value = []
-  approveFormFApis.value = []
-  try {
-    // 获得未取消的任务
-    tasksLoad.value = true
-    const data = await TaskApi.getTaskListByProcessInstanceId(id)
-    tasks.value = []
-    // 1.1 移除已取消的审批
-    data.forEach((task) => {
-      if (task.status !== 4) {
-        tasks.value.push(task)
-      }
-    })
-    // 1.2 排序,将未完成的排在前面,已完成的排在后面;
-    tasks.value.sort((a, b) => {
-      // 有已完成的情况,按照完成时间倒序
-      if (a.endTime && b.endTime) {
-        return b.endTime - a.endTime
-      } else if (a.endTime) {
-        return 1
-      } else if (b.endTime) {
-        return -1
-        // 都是未完成,按照创建时间倒序
-      } else {
-        return b.createTime - a.createTime
-      }
-    })
+/** 获取流程模型视图*/
+const getProcessModelView = async () => {
+  if (BpmModelType.BPMN === processDefinition.value?.modelType) {
+    // 重置,解决 BPMN 流程图刷新不会重新渲染问题
+    processModelView.value = {
+      bpmnXml: ''
+    }
+  }
+  const data = await ProcessInstanceApi.getProcessInstanceBpmnModelView(props.id)
+  if (data) {
+    processModelView.value = data
+  }
+}
 
-    // 获得需要自己审批的任务
-    loadRunningTask(tasks.value)
-  } finally {
-    tasksLoad.value = false
+// 审批节点信息
+const activityNodes = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([])
+/**
+ * 设置表单权限
+ */
+const setFieldPermission = (field: string, permission: string) => {
+  if (permission === FieldPermissionType.READ) {
+    //@ts-ignore
+    fApi.value?.disabled(true, field)
+  }
+  if (permission === FieldPermissionType.WRITE) {
+    //@ts-ignore
+    fApi.value?.disabled(false, field)
+  }
+  if (permission === FieldPermissionType.NONE) {
+    //@ts-ignore
+    fApi.value?.hidden(true, field)
   }
 }
 
 /**
- * 设置 runningTasks 中的任务
+ * 操作成功后刷新
  */
-const loadRunningTask = (tasks) => {
-  tasks.forEach((task) => {
-    if (!isEmpty(task.children)) {
-      loadRunningTask(task.children)
-    }
-    // 2.1 只有待处理才需要
-    if (task.status !== 1 && task.status !== 6) {
-      return
-    }
-    // 2.2 自己不是处理人
-    if (!task.assigneeUser || task.assigneeUser.id !== userId) {
-      return
-    }
-    // 2.3 添加到处理任务
-    runningTasks.value.push({ ...task })
-    auditForms.value.push({
-      reason: '',
-      copyUserIds: []
-    })
-
-    // 2.4 处理 approve 表单
-    if (task.formId && task.formConf) {
-      const approveForm = {}
-      setConfAndFields2(approveForm, task.formConf, task.formFields, task.formVariables)
-      approveForms.value.push(approveForm)
-    } else {
-      approveForms.value.push({}) // 占位,避免为空
-    }
-  })
+const refresh = () => {
+  // 重新获取详情
+  getDetail()
 }
+
+/** 当前的Tab */
+const activeTab = ref('form')
 
 /** 初始化 */
 const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
@@ -379,3 +297,50 @@
   userOptions.value = await UserApi.getSimpleUserList()
 })
 </script>
+
+<style lang="scss" scoped>
+$wrap-padding-height: 20px;
+$wrap-margin-height: 15px;
+$button-height: 51px;
+$process-header-height: 194px;
+
+.processInstance-wrap-main {
+  height: calc(
+    100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px
+  );
+  max-height: calc(
+    100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px
+  );
+  overflow: auto;
+
+  .form-scroll-area {
+    height: calc(
+      100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
+        $process-header-height - 40px
+    );
+    max-height: calc(
+      100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
+        $process-header-height - 40px
+    );
+    overflow: auto;
+    display: flex;
+    flex-direction: column;
+
+    :deep(.box-card) {
+      height: 100%;
+      flex: 1;
+
+      .el-card__body {
+        height: 100%;
+        padding: 0;
+      }
+    }
+  }
+}
+
+.form-box {
+  :deep(.el-card) {
+    border: none;
+  }
+}
+</style>
diff --git a/src/views/bpm/processInstance/detail/index_new.vue b/src/views/bpm/processInstance/detail/index_new.vue
new file mode 100644
index 0000000..ebbcd0b
--- /dev/null
+++ b/src/views/bpm/processInstance/detail/index_new.vue
@@ -0,0 +1,319 @@
+<template>
+  <ContentWrap :bodyStyle="{ padding: '10px 20px' }" class="position-relative">
+    <div class="processInstance-wrap-main">
+      <el-scrollbar>
+        <img
+          class="position-absolute right-20px"
+          width="150"
+          :src="auditIcons[processInstance.status]"
+          alt=""
+        />
+        <div class="text-#878c93 h-15px">编号:{{ id }}</div>
+        <el-divider class="!my-8px" />
+        <div class="flex items-center gap-5 mb-10px h-40px">
+          <div class="text-26px font-bold mb-5px">{{ processInstance.name }}</div>
+          <dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="processInstance.status" />
+        </div>
+
+        <div class="flex items-center gap-5 mb-10px text-13px h-35px">
+          <div
+            class="bg-gray-100 h-35px rounded-3xl flex items-center p-8px gap-2 dark:color-gray-600"
+          >
+            <img class="rounded-full h-28px" src="@/assets/imgs/avatar.jpg" alt="" />
+            {{ processInstance?.startUser?.nickname }}
+          </div>
+          <div class="text-#878c93"> {{ formatDate(processInstance.startTime) }} 提交 </div>
+        </div>
+
+        <el-tabs v-model="activeTab">
+          <!-- 表单信息 -->
+          <el-tab-pane label="审批详情" name="form">
+            <div class="form-scroll-area">
+              <el-scrollbar>
+                <el-row :gutter="10">
+                  <el-col :span="18" class="!flex !flex-col formCol">
+                    <!-- 表单信息 -->
+                    <div
+                      v-loading="processInstanceLoading"
+                      class="form-box flex flex-col mb-30px flex-1"
+                    >
+                      <!-- 情况一:流程表单 -->
+                      <el-col
+                        v-if="processInstance?.processDefinition?.formType === 10"
+                        :offset="6"
+                        :span="16"
+                      >
+                        <form-create
+                          v-model="detailForm.value"
+                          v-model:api="fApi"
+                          :option="detailForm.option"
+                          :rule="detailForm.rule"
+                        />
+                      </el-col>
+                      <!-- 情况二:业务表单 -->
+                      <div v-if="processInstance?.processDefinition?.formType === 20">
+                        <BusinessFormComponent :id="processInstance.businessKey" />
+                      </div>
+                    </div>
+                  </el-col>
+                  <el-col :span="6">
+                    <!-- 审批记录时间线 -->
+                    <ProcessInstanceTimeline ref="timelineRef" :process-instance-id="id" />
+                  </el-col>
+                </el-row>
+              </el-scrollbar>
+            </div>
+          </el-tab-pane>
+          <!-- 流程图 -->
+          <el-tab-pane label="流程图" name="diagram">
+            <ProcessInstanceBpmnViewer
+              :id="`${id}`"
+              :bpmn-xml="bpmnXml"
+              :loading="processInstanceLoading"
+              :process-instance="processInstance"
+              :tasks="tasks"
+            />
+          </el-tab-pane>
+          <!-- 流转记录 -->
+          <el-tab-pane label="流转记录" name="record">
+            <ProcessInstanceTaskList
+              :loading="tasksLoad"
+              :process-instance="processInstance"
+              :tasks="tasks"
+              @refresh="getTaskList"
+            />
+          </el-tab-pane>
+          <!-- 流转评论 TODO 待开发 -->
+          <el-tab-pane label="流转评论" name="comment"> 流转评论 </el-tab-pane>
+        </el-tabs>
+
+        <div
+          class="b-t-solid border-t-1px border-[var(--el-border-color)]"
+          v-if="activeTab === 'form'"
+        >
+          <!-- 操作栏按钮 -->
+          <ProcessInstanceOperationButton
+            ref="operationButtonRef"
+            :processInstance="processInstance"
+            :userOptions="userOptions"
+            @success="refresh"
+          />
+        </div>
+      </el-scrollbar>
+    </div>
+  </ContentWrap>
+</template>
+<script lang="ts" setup>
+import { formatDate } from '@/utils/formatTime'
+import { DICT_TYPE } from '@/utils/dict'
+import { setConfAndFields2 } from '@/utils/formCreate'
+import type { ApiAttrs } from '@form-create/element-ui/types/config'
+import * as DefinitionApi from '@/api/bpm/definition'
+import * as ProcessInstanceApi from '@/api/bpm/processInstance'
+import * as TaskApi from '@/api/bpm/task'
+import ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue'
+import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue'
+import ProcessInstanceOperationButton from './ProcessInstanceOperationButton.vue'
+import ProcessInstanceTimeline from './ProcessInstanceTimeline.vue'
+import { registerComponent } from '@/utils/routerHelper'
+import * as UserApi from '@/api/system/user'
+import { FieldPermissionType } from '@/components/SimpleProcessDesignerV2/src/consts'
+import audit1 from '@/assets/svgs/bpm/audit1.svg'
+import audit2 from '@/assets/svgs/bpm/audit2.svg'
+import audit3 from '@/assets/svgs/bpm/audit3.svg'
+
+defineOptions({ name: 'BpmProcessInstanceDetail' })
+const props = defineProps<{
+  id: string // 流程实例的编号
+  taskId?: string // 任务编号
+  activityId?: string //流程活动编号,用于抄送查看
+}>()
+const message = useMessage() // 消息弹窗
+const processInstanceLoading = ref(false) // 流程实例的加载中
+const processInstance = ref<any>({}) // 流程实例
+const operationButtonRef = ref()
+const timelineRef = ref()
+const bpmnXml = ref('') // BPMN XML
+const tasksLoad = ref(true) // 任务的加载中
+const tasks = ref<any[]>([]) // 任务列表
+const auditIcons = {
+  1: audit1,
+  2: audit2,
+  3: audit3
+}
+
+// ========== 申请信息 ==========
+const fApi = ref<ApiAttrs>() //
+const detailForm = ref({
+  rule: [],
+  option: {},
+  value: {}
+}) // 流程实例的表单详情
+
+/** 获得详情 */
+const getDetail = () => {
+  // 1. 获得流程实例相关
+  getProcessInstance()
+  // 2. 获得流程任务列表(审批记录)
+  getTaskList()
+}
+
+/** 加载流程实例 */
+const BusinessFormComponent = ref<any>(null) // 异步组件
+const getProcessInstance = async () => {
+  try {
+    processInstanceLoading.value = true
+    const data = await ProcessInstanceApi.getProcessInstance(props.id)
+    if (!data) {
+      message.error('查询不到流程信息!')
+      return
+    }
+    processInstance.value = data
+
+    // 设置表单信息
+    const processDefinition = data.processDefinition
+    if (processDefinition.formType === 10) {
+      // 获取表单字段权限
+      let fieldsPermission = undefined
+      if (props.taskId || props.activityId) {
+        fieldsPermission = await ProcessInstanceApi.getFormFieldsPermission({
+          processInstanceId: props.id,
+          taskId: props.taskId,
+          activityId: props.activityId
+        })
+      }
+      setConfAndFields2(
+        detailForm,
+        processDefinition.formConf,
+        processDefinition.formFields,
+        data.formVariables
+      )
+      nextTick().then(() => {
+        fApi.value?.btn.show(false)
+        fApi.value?.resetBtn.show(false)
+        fApi.value?.disabled(true)
+        if (fieldsPermission) {
+          Object.keys(fieldsPermission).forEach((item) => {
+            setFieldPermission(item, fieldsPermission[item])
+          })
+        }
+      })
+    } else {
+      // 注意:data.processDefinition.formCustomViewPath 是组件的全路径,例如说:/crm/contract/detail/index.vue
+      BusinessFormComponent.value = registerComponent(data.processDefinition.formCustomViewPath)
+    }
+
+    // 加载流程图
+    bpmnXml.value = (await DefinitionApi.getProcessDefinition(processDefinition.id))?.bpmnXml
+    console.log(bpmnXml.value)
+  } finally {
+    processInstanceLoading.value = false
+  }
+}
+
+/**
+ * 设置表单权限
+ */
+const setFieldPermission = (field: string, permission: string) => {
+  if (permission === FieldPermissionType.READ) {
+    fApi.value?.disabled(true, field)
+  }
+  if (permission === FieldPermissionType.WRITE) {
+    fApi.value?.disabled(false, field)
+  }
+  if (permission === FieldPermissionType.NONE) {
+    fApi.value?.hidden(true, field)
+  }
+}
+
+/** 加载任务列表 */
+const getTaskList = async () => {
+  try {
+    // 获得未取消的任务
+    tasksLoad.value = true
+    const data = await TaskApi.getTaskListByProcessInstanceId(props.id)
+    tasks.value = []
+    // 1.1 移除已取消的审批
+    data.forEach((task: any) => {
+      if (task.status !== 4) {
+        tasks.value.push(task)
+      }
+    })
+    // 1.2 排序,将未完成的排在前面,已完成的排在后面;
+    tasks.value.sort((a, b) => {
+      // 有已完成的情况,按照完成时间倒序
+      if (a.endTime && b.endTime) {
+        return b.endTime - a.endTime
+      } else if (a.endTime) {
+        return 1
+      } else if (b.endTime) {
+        return -1
+        // 都是未完成,按照创建时间倒序
+      } else {
+        return b.createTime - a.createTime
+      }
+    })
+
+    // 获得需要自己审批的任务
+    operationButtonRef.value?.loadRunningTask(tasks.value)
+  } finally {
+    tasksLoad.value = false
+  }
+}
+
+/**
+ * 操作成功后刷新
+ */
+const refresh = () => {
+  // 重新获取详情
+  getDetail()
+  // 刷新审批详情 Timeline
+  timelineRef.value?.refresh()
+}
+
+/** 当前的Tab */
+const activeTab = ref('form')
+
+/** 初始化 */
+const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
+onMounted(async () => {
+  getDetail()
+  // 获得用户列表
+  userOptions.value = await UserApi.getSimpleUserList()
+})
+</script>
+
+<style lang="scss" scoped>
+$wrap-padding-height: 30px;
+$wrap-margin-height: 15px;
+$button-height: 51px;
+$process-header-height: 194px;
+
+.processInstance-wrap-main {
+  height: calc(
+    100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 45px
+  );
+  max-height: calc(
+    100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 45px
+  );
+  overflow: auto;
+
+  .form-scroll-area {
+    height: calc(
+      100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 45px -
+        $process-header-height - 40px
+    );
+    max-height: calc(
+      100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 45px -
+        $process-header-height - 40px
+    );
+    overflow: auto;
+  }
+}
+
+.form-box {
+  :deep(.el-card) {
+    border: none;
+  }
+}
+</style>
diff --git a/src/views/bpm/processInstance/index.vue b/src/views/bpm/processInstance/index.vue
index 3951a83..f2bb29d 100644
--- a/src/views/bpm/processInstance/index.vue
+++ b/src/views/bpm/processInstance/index.vue
@@ -9,7 +9,7 @@
       :inline="true"
       label-width="68px"
     >
-      <el-form-item label="流程名称" prop="name">
+      <el-form-item label="" prop="name">
         <el-input
           v-model="queryParams.name"
           placeholder="请输入流程名称"
@@ -18,21 +18,19 @@
           class="!w-240px"
         />
       </el-form-item>
-      <el-form-item label="所属流程" prop="processDefinitionId">
-        <el-input
-          v-model="queryParams.processDefinitionId"
-          placeholder="请输入流程定义的编号"
-          clearable
-          @keyup.enter="handleQuery"
-          class="!w-240px"
-        />
+
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
       </el-form-item>
-      <el-form-item label="流程分类" prop="category">
+
+      <!-- TODO @ tuituji:style 可以使用 unocss -->
+      <el-form-item label="" prop="category" :style="{ position: 'absolute', right: '130px' }">
+        <!-- TODO @tuituji:应该选择好分类,就触发搜索啦。 -->
         <el-select
           v-model="queryParams.category"
           placeholder="请选择流程分类"
           clearable
-          class="!w-240px"
+          class="!w-155px"
         >
           <el-option
             v-for="category in categoryList"
@@ -42,43 +40,79 @@
           />
         </el-select>
       </el-form-item>
-      <el-form-item label="流程状态" prop="status">
-        <el-select
-          v-model="queryParams.status"
-          placeholder="请选择流程状态"
-          clearable
-          class="!w-240px"
-        >
-          <el-option
-            v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
-            :key="dict.value"
-            :label="dict.label"
-            :value="dict.value"
-          />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="发起时间" prop="createTime">
-        <el-date-picker
-          v-model="queryParams.createTime"
-          value-format="YYYY-MM-DD HH:mm:ss"
-          type="daterange"
-          start-placeholder="开始日期"
-          end-placeholder="结束日期"
-          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
-          class="!w-240px"
-        />
-      </el-form-item>
-      <el-form-item>
-        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
-        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
-        <el-button
-          type="primary"
-          plain
-          v-hasPermi="['bpm:process-instance:query']"
-          @click="handleCreate(undefined)"
-        >
-          <Icon icon="ep:plus" class="mr-5px" /> 发起流程
+
+      <!-- 高级筛选 -->
+      <!-- TODO @ tuituji:style 可以使用 unocss -->
+      <el-form-item :style="{ position: 'absolute', right: '0px' }">
+        <el-button v-popover="popoverRef" v-click-outside="onClickOutside" :icon="List">
+          高级筛选
         </el-button>
+        <el-popover
+          ref="popoverRef"
+          trigger="click"
+          virtual-triggering
+          persistent
+          :width="400"
+          :show-arrow="false"
+          placement="bottom-end"
+        >
+          <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"
+            label-position="top"
+            prop="processDefinitionKey"
+          >
+            <el-input
+              v-model="queryParams.processDefinitionKey"
+              placeholder="请输入流程定义的标识"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-390px"
+            />
+          </el-form-item>
+          <el-form-item label="流程状态" class="bold-label" label-position="top" prop="status">
+            <el-select
+              v-model="queryParams.status"
+              placeholder="请选择流程状态"
+              clearable
+              class="!w-390px"
+            >
+              <el-option
+                v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="发起时间" class="bold-label" label-position="top" prop="createTime">
+            <el-date-picker
+              v-model="queryParams.createTime"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              type="daterange"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+              :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+              class="!w-240px"
+            />
+          </el-form-item>
+        </el-popover>
+        <!-- TODO @tuituji:这里应该有确认,和取消、清空搜索条件,三个按钮。 -->
       </el-form-item>
     </el-form>
   </ContentWrap>
@@ -94,6 +128,8 @@
         min-width="100"
         fixed="left"
       />
+      <!-- TODO @芋艿:摘要 -->
+      <!-- TODO @tuituji:流程状态。可见需求文档里  -->
       <el-table-column label="流程状态" prop="status" width="120">
         <template #default="scope">
           <dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
@@ -113,7 +149,7 @@
         width="180"
         :formatter="dateFormatter"
       />
-      <el-table-column align="center" label="耗时" prop="durationInMillis" width="160">
+      <!--<el-table-column align="center" label="耗时" prop="durationInMillis" width="160">
         <template #default="scope">
           {{ scope.row.durationInMillis > 0 ? formatPast2(scope.row.durationInMillis) : '-' }}
         </template>
@@ -125,7 +161,7 @@
           </el-button>
         </template>
       </el-table-column>
-      <el-table-column label="流程编号" align="center" prop="id" min-width="320px" />
+      -->
       <el-table-column label="操作" align="center" fixed="right" width="180">
         <template #default="scope">
           <el-button
@@ -161,11 +197,13 @@
   </ContentWrap>
 </template>
 <script lang="ts" setup>
+// TODO @tuituji:List 改成 <Icon icon="ep:plus" class="mr-5px" /> 类似这种组件哈。
+import { List } from '@element-plus/icons-vue'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
-import { dateFormatter, formatPast2 } from '@/utils/formatTime'
+import { dateFormatter } from '@/utils/formatTime'
 import { ElMessageBox } from 'element-plus'
 import * as ProcessInstanceApi from '@/api/bpm/processInstance'
-import { CategoryApi } from '@/api/bpm/category'
+import { CategoryApi, CategoryVO } from '@/api/bpm/category'
 import { ProcessInstanceVO } from '@/api/bpm/processInstance'
 import * as DefinitionApi from '@/api/bpm/definition'
 
@@ -182,13 +220,13 @@
   pageNo: 1,
   pageSize: 10,
   name: '',
-  processDefinitionId: undefined,
+  processDefinitionKey: undefined,
   category: undefined,
   status: undefined,
   createTime: []
 })
 const queryFormRef = ref() // 搜索的表单
-const categoryList = ref([]) // 流程分类列表
+const categoryList = ref<CategoryVO[]>([]) // 流程分类列表
 
 /** 查询列表 */
 const getList = async () => {
@@ -259,6 +297,15 @@
   await getList()
 }
 
+// TODO @tuituji:这个 import 是不是没用哈?
+import { ClickOutside as vClickOutside } from 'element-plus'
+
+// TODO @tuituji:onClickAdvancedSearch。方法名叫这个,会更好一些哇?打开高级搜索。
+const popoverRef = ref()
+const onClickOutside = () => {
+  unref(popoverRef).popperRef?.delayHide?.()
+}
+
 /** 激活时 **/
 onActivated(() => {
   getList()
@@ -270,3 +317,8 @@
   categoryList.value = await CategoryApi.getCategorySimpleList()
 })
 </script>
+<style>
+.bold-label .el-form-item__label {
+  font-weight: bold; /* 将字体加粗 */
+}
+</style>
diff --git a/src/views/bpm/processInstance/manager/index.vue b/src/views/bpm/processInstance/manager/index.vue
index da79456..3b44b19 100644
--- a/src/views/bpm/processInstance/manager/index.vue
+++ b/src/views/bpm/processInstance/manager/index.vue
@@ -75,9 +75,13 @@
           start-placeholder="开始日期"
           end-placeholder="结束日期"
           :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
-          class="!w-220px"
+          class="!w-240px"
         />
       </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+      </el-form-item>
     </el-form>
   </ContentWrap>
 
diff --git a/src/views/bpm/processListener/ProcessListenerForm.vue b/src/views/bpm/processListener/ProcessListenerForm.vue
index 8d4e979..a9684df 100644
--- a/src/views/bpm/processListener/ProcessListenerForm.vue
+++ b/src/views/bpm/processListener/ProcessListenerForm.vue
@@ -15,7 +15,7 @@
           <el-radio
             v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
             :key="dict.value"
-            :label="dict.value"
+            :value="dict.value"
           >
             {{ dict.label }}
           </el-radio>
diff --git a/src/views/bpm/simple/SimpleModelDesign.vue b/src/views/bpm/simple/SimpleModelDesign.vue
new file mode 100644
index 0000000..1740e03
--- /dev/null
+++ b/src/views/bpm/simple/SimpleModelDesign.vue
@@ -0,0 +1,19 @@
+<template>
+  <ContentWrap :bodyStyle="{ padding: '20px 16px' }">
+    <SimpleProcessDesigner :model-id="modelId" @success="close" />
+  </ContentWrap>
+</template>
+<script setup lang="ts">
+import { SimpleProcessDesigner } from '@/components/SimpleProcessDesignerV2/src/'
+
+defineOptions({
+  name: 'SimpleModelDesign'
+})
+const router = useRouter() // 路由
+const { query } = useRoute() // 路由的查询
+const modelId = query.modelId as string
+const close = () => {
+  router.push({ path: '/bpm/manager/model' })
+}
+</script>
+<style lang="scss" scoped></style>
diff --git a/src/views/bpm/simpleWorkflow/index.vue b/src/views/bpm/simpleWorkflow/index.vue
new file mode 100644
index 0000000..691cf2e
--- /dev/null
+++ b/src/views/bpm/simpleWorkflow/index.vue
@@ -0,0 +1,13 @@
+<template>
+  <SimpleProcessDesigner :model-id="modelId" />
+</template>
+<script setup lang="ts">
+import { SimpleProcessDesigner } from '@/components/SimpleProcessDesignerV2/src/'
+
+defineOptions({
+  name: 'SimpleWorkflowDesignEditor'
+})
+const { query } = useRoute() // 路由的查询
+const modelId = query.modelId as string
+</script>
+<style lang="scss" scoped></style>
diff --git a/src/views/bpm/task/copy/index.vue b/src/views/bpm/task/copy/index.vue
index dd41b2e..b64521d 100644
--- a/src/views/bpm/task/copy/index.vue
+++ b/src/views/bpm/task/copy/index.vue
@@ -1,11 +1,13 @@
 <!-- 工作流 - 抄送我的流程 -->
 <template>
+
   <ContentWrap>
     <!-- 搜索工作栏 -->
     <el-form ref="queryFormRef" :inline="true" class="-mb-15px" label-width="68px">
       <el-form-item label="流程名称" prop="name">
         <el-input
           v-model="queryParams.processInstanceName"
+          @keyup.enter="handleQuery"
           class="!w-240px"
           clearable
           placeholder="请输入流程名称"
@@ -39,7 +41,12 @@
   <ContentWrap>
     <el-table v-loading="loading" :data="list">
       <el-table-column align="center" label="流程名" prop="processInstanceName" min-width="180" />
-      <el-table-column align="center" label="流程发起人" prop="startUserName" min-width="100" />
+      <el-table-column
+        align="center"
+        label="流程发起人"
+        prop="startUser.nickname"
+        min-width="100"
+      />
       <el-table-column
         :formatter="dateFormatter"
         align="center"
@@ -47,8 +54,11 @@
         prop="processInstanceStartTime"
         width="180"
       />
-      <el-table-column align="center" label="抄送任务" prop="taskName" min-width="180" />
-      <el-table-column align="center" label="抄送人" prop="creatorName" min-width="100" />
+      <el-table-column align="center" label="抄送节点" prop="activityName" min-width="180" />
+      <el-table-column align="center" label="抄送人" min-width="100">
+        <template #default="scope"> {{ scope.row.createUser?.nickname || '系统' }} </template>
+      </el-table-column>
+      <el-table-column align="center" label="抄送意见" prop="reason" width="150" />
       <el-table-column
         align="center"
         label="抄送时间"
@@ -105,11 +115,16 @@
 
 /** 处理审批按钮 */
 const handleAudit = (row: any) => {
+  const query = {
+    id: row.processInstanceId,
+    activityId: undefined
+  }
+  if (row.activityId) {
+    query.activityId = row.activityId
+  }
   push({
     name: 'BpmProcessInstanceDetail',
-    query: {
-      id: row.processInstanceId
-    }
+    query: query
   })
 }
 
diff --git a/src/views/bpm/task/done/index.vue b/src/views/bpm/task/done/index.vue
index d54ad61..fdcef66 100644
--- a/src/views/bpm/task/done/index.vue
+++ b/src/views/bpm/task/done/index.vue
@@ -1,4 +1,5 @@
 <template>
+
   <ContentWrap>
     <!-- 搜索工作栏 -->
     <el-form
@@ -150,7 +151,8 @@
   push({
     name: 'BpmProcessInstanceDetail',
     query: {
-      id: row.processInstance.id
+      id: row.processInstance.id,
+      taskId: row.id
     }
   })
 }
diff --git a/src/views/bpm/task/manager/index.vue b/src/views/bpm/task/manager/index.vue
index fb903d7..d608869 100644
--- a/src/views/bpm/task/manager/index.vue
+++ b/src/views/bpm/task/manager/index.vue
@@ -1,4 +1,5 @@
 <template>
+
   <ContentWrap>
     <!-- 搜索工作栏 -->
     <el-form
diff --git a/src/views/bpm/task/todo/index.vue b/src/views/bpm/task/todo/index.vue
index 27aae87..60681fc 100644
--- a/src/views/bpm/task/todo/index.vue
+++ b/src/views/bpm/task/todo/index.vue
@@ -1,4 +1,5 @@
 <template>
+
   <ContentWrap>
     <!-- 搜索工作栏 -->
     <el-form
@@ -132,7 +133,8 @@
   push({
     name: 'BpmProcessInstanceDetail',
     query: {
-      id: row.processInstance.id
+      id: row.processInstance.id,
+      taskId: row.id
     }
   })
 }
diff --git a/vite.config.ts b/vite.config.ts
index 8cba915..640fe50 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,8 +1,8 @@
-import { resolve } from 'path'
-import { loadEnv } from 'vite'
-import type { UserConfig, ConfigEnv } from 'vite'
-import { createVitePlugins } from './build/vite'
-import { include, exclude } from "./build/vite/optimize"
+import {resolve} from 'path'
+import type {ConfigEnv, UserConfig} from 'vite'
+import {loadEnv} from 'vite'
+import {createVitePlugins} from './build/vite'
+import {exclude, include} from "./build/vite/optimize"
 // 当前执行node命令时文件夹的地址(工作目录)
 const root = process.cwd()
 
@@ -12,7 +12,7 @@
 }
 
 // https://vitejs.dev/config/
-export default ({ command, mode }: ConfigEnv): UserConfig => {
+export default ({command, mode}: ConfigEnv): UserConfig => {
   let env = {} as any
   const isBuild = command === 'build'
   if (!isBuild) {
@@ -43,7 +43,7 @@
     css: {
       preprocessorOptions: {
         scss: {
-          additionalData: '@import "./src/styles/variables.scss";',
+          additionalData: '@use "@/styles/variables.scss" as *;',
           javascriptEnabled: true
         }
       }
@@ -71,8 +71,15 @@
           drop_debugger: env.VITE_DROP_DEBUGGER === 'true',
           drop_console: env.VITE_DROP_CONSOLE === 'true'
         }
-      }
+      },
+      rollupOptions: {
+        output: {
+          manualChunks: {
+            echarts: ['echarts'] // 将 echarts 单独打包,参考 https://gitee.com/yudaocode/yudao-ui-admin-vue3/issues/IAB1SX 讨论
+          }
+        },
+      },
     },
-    optimizeDeps: { include, exclude }
+    optimizeDeps: {include, exclude}
   }
 }

--
Gitblit v1.9.3