From 1220f5ca98b10b735a47c37a81fbfc554b01e2fe Mon Sep 17 00:00:00 2001
From: liriming <1343021927@qq.com>
Date: 星期一, 20 一月 2025 14:41:35 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'

---
 src/views/infra/apiErrorLog/index.vue                                                              |   14 
 src/components/bpmnProcessDesigner/package/penal/listeners/UserTaskListeners.vue                   |    5 
 src/views/bpm/model/index_old.vue                                                                  |  404 +
 src/views/system/tenant/index.vue                                                                  |    2 
 .env.test                                                                                          |    7 
 src/components/DiyEditor/components/mobile/MenuSwiper/property.vue                                 |   14 
 src/components/SimpleProcessDesignerV2/src/nodes/EndEventNode.vue                                  |  102 
 src/components/DiyEditor/components/mobile/FloatingActionButton/index.vue                          |    2 
 src/components/SimpleProcessDesignerV2/src/SimpleProcessModel.vue                                  |  148 
 src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss                          |  755 ++
 src/components/FormCreate/src/config/useDictSelectRule.ts                                          |    2 
 src/layout/components/useRenderLayout.tsx                                                          |   38 
 src/router/modules/remaining.ts                                                                    |   44 
 src/views/model/mpk/project/index.vue                                                              |   14 
 src/views/bpm/definition/index.vue                                                                 |   22 
 src/components/DiyEditor/components/mobile/Carousel/config.ts                                      |    4 
 src/components/bpmnProcessDesigner/package/penal/task/data.ts                                      |   36 
 src/components/DiyEditor/components/mobile/SearchBar/property.vue                                  |    8 
 src/components/SimpleProcessDesignerV2/src/nodes-config/StartUserNodeConfig.vue                    |  163 
 src/components/SimpleProcessDesignerV2/src/index.ts                                                |    5 
 src/views/system/app/AppForm.vue                                                                   |   34 
 .env.dev                                                                                           |   12 
 src/components/DiyEditor/components/mobile/PromotionSeckill/index.vue                              |  244 
 src/components/DiyEditor/components/ComponentContainer.vue                                         |    1 
 src/components/DiyEditor/components/mobile/PromotionSeckill/config.ts                              |   42 
 src/layout/components/Message/src/Message.vue                                                      |    8 
 src/views/bpm/processInstance/detail/ProcessInstanceSimpleViewer.vue                               |  168 
 src/views/system/role/index.vue                                                                    |   10 
 src/components/UserSelectForm/index.vue                                                            |  171 
 src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue                               |  138 
 src/views/bpm/form/index.vue                                                                       |    4 
 src/components/DiyEditor/components/mobile/PromotionPoint/property.vue                             |  154 
 src/layout/components/Logo/src/Logo.vue                                                            |   28 
 src/views/data/ind/item/AtomIndDefineForm.vue                                                      |   44 
 src/components/DiyEditor/components/mobile/Popover/property.vue                                    |    4 
 src/components/SimpleProcessDesignerV2/src/NodeHandler.vue                                         |  231 
 src/api/model/mpk/project.ts                                                                       |    7 
 vite.config.ts                                                                                     |   28 
 src/assets/svgs/bpm/copy.svg                                                                       |    1 
 src/assets/imgs/logo.png                                                                           |    0 
 src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue                                   |  292 
 src/views/bpm/simple/SimpleModelDesign.vue                                                         |  155 
 src/components/DictTag/src/DictTag.vue                                                             |   90 
 src/components/bpmnProcessDesigner/package/penal/custom-config/components/UserTaskCustomConfig.vue |  623 +
 src/components/DiyEditor/components/mobile/NavigationBar/property.vue                              |   12 
 src/components/bpmnProcessDesigner/package/penal/properties/ElementProperties.vue                  |    5 
 src/views/bpm/model/form/index.vue                                                                 |  439 +
 src/views/model/mpk/file/index.vue                                                                 |   18 
 src/components/bpmnProcessDesigner/package/penal/form/ElementForm.vue                              |    6 
 src/views/data/channel/http/api/tag/index.vue                                                      |    7 
 src/views/system/menu/MenuForm.vue                                                                 |    6 
 src/assets/svgs/bpm/cancel.svg                                                                     |    1 
 src/views/model/pre/item/index.vue                                                                 |   62 
 src/assets/svgs/bpm/parallel.svg                                                                   |    1 
 src/api/data/da/point/index.ts                                                                     |    9 
 src/components/DiyEditor/components/mobile/ProductCard/property.vue                                |   10 
 src/store/modules/user.ts                                                                          |   14 
 src/main.ts                                                                                        |   17 
 src/components/DiyEditor/components/mobile/PromotionCombination/property.vue                       |   86 
 src/views/model/pre/type/index.vue                                                                 |    1 
 src/components/DiyEditor/components/mobile/MenuGrid/property.vue                                   |    4 
 src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue                            |  989 ++
 src/views/model/sche/model/index.vue                                                               |    8 
 src/views/bpm/processListener/ProcessListenerForm.vue                                              |    2 
 src/components/bpmnProcessDesigner/package/penal/custom-config/components/BoundaryEventTimer.vue   |  252 
 src/views/model/sche/model/ScheduleModelForm.vue                                                   |  307 
 src/api/bpm/task/index.ts                                                                          |   55 
 src/components/FormCreate/src/components/useApiSelect.tsx                                          |    5 
 src/components/ShortcutDateRangePicker/index.vue                                                   |    6 
 src/components/UploadFile/src/useUpload.ts                                                         |   33 
 src/views/data/ind/item/CalIndDefineForm.vue                                                       |   35 
 src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue                            |   87 
 src/components/Icon/src/IconSelect.vue                                                             |   18 
 src/views/data/point/index.vue                                                                     |   39 
 src/assets/svgs/bpm/running.svg                                                                    |    1 
 src/views/bpm/simpleWorkflow/index.vue                                                             |   13 
 src/views/model/mpk/file/MpkRun.vue                                                                |   70 
 src/views/model/mpk/project/ProjectPackage.vue                                                     |    5 
 src/views/bpm/category/CategoryForm.vue                                                            |    7 
 src/components/SimpleProcessDesignerV2/src/nodes/CopyTaskNode.vue                                  |   97 
 src/components/DiyEditor/components/mobile/ProductList/index.vue                                   |    3 
 src/views/model/mpk/pack/index.vue                                                                 |    1 
 index.html                                                                                         |    4 
 src/components/DiyEditor/components/mobile/NoticeBar/config.ts                                     |    2 
 src/components/bpmnProcessDesigner/package/penal/task/task-components/CallActivity.vue             |  280 
 src/views/bpm/processInstance/manager/index.vue                                                    |    6 
 src/api/login/index.ts                                                                             |    8 
 src/utils/permission.ts                                                                            |    6 
 src/views/system/appmenu/index.vue                                                                 |    6 
 src/directives/index.ts                                                                            |   11 
 src/utils/constants.ts                                                                             |   20 
 src/api/model/pre/item/index.ts                                                                    |    2 
 src/components/SimpleProcessDesignerV2/theme/iconfont.ttf                                          |    0 
 src/components/DiyEditor/components/mobile/TitleBar/property.vue                                   |   10 
 src/layout/components/UserInfo/src/UserInfo.vue                                                    |    6 
 src/components/bpmnProcessDesigner/package/penal/listeners/utilSelf.ts                             |    2 
 src/components/bpmnProcessDesigner/package/penal/listeners/ElementListeners.vue                    |    4 
 tsconfig.json                                                                                      |   10 
 src/assets/svgs/bpm/simple-process-bg.svg                                                          |    1 
 src/utils/dict.ts                                                                                  |    4 
 stylelint.config.js                                                                                |   10 
 web-types.json                                                                                     |   19 
 src/views/bpm/form/editor/index.vue                                                                |   63 
 src/components/SimpleProcessDesignerV2/src/SimpleProcessViewer.vue                                 |   48 
 src/components/UploadFile/src/UploadFile.vue                                                       |   24 
 src/config/axios/service.ts                                                                        |   54 
 src/components/bpmnProcessDesigner/package/theme/process-designer.scss                             |   18 
 src/views/data/plan/category/CategoryForm.vue                                                      |    6 
 src/components/bpmnProcessDesigner/package/designer/plugins/palette/CustomPalette.js               |   12 
 src/components/DiyEditor/components/mobile/UserCard/index.vue                                      |    2 
 src/views/report/drag/index.vue                                                                    |   13 
 src/components/DiyEditor/components/ComponentContainerProperty.vue                                 |    4 
 src/components/bpmnProcessDesigner/package/penal/multi-instance/ElementMultiInstance.vue           |  142 
 src/components/SimpleProcessDesignerV2/src/nodes/StartUserNode.vue                                 |  154 
 src/views/model/pre/item/MmPredictItemChart.vue                                                    |   55 
 src/views/bpm/processInstance/detail/index.vue                                                     |  597 
 src/views/bpm/model/form/FormDesign.vue                                                            |  137 
 src/views/model/mpk/menu/index.vue                                                                 |    1 
 .env.prod                                                                                          |   20 
 src/views/bpm/model/form/ProcessDesign.vue                                                         |  235 
 src/components/ContentWrap/src/ContentWrap.vue                                                     |    2 
 src/views/bpm/model/form/BasicInfo.vue                                                             |  301 
 src/api/bpm/simple/index.ts                                                                        |   15 
 src/components/DiyEditor/components/mobile/PromotionCombination/index.vue                          |  244 
 src/components/Echart/src/Echart.vue                                                               |    8 
 src/views/bpm/task/todo/index.vue                                                                  |   97 
 src/views/infra/dataSourceConfig/index.vue                                                         |    8 
 src/views/system/appmenu/AppMenuForm.vue                                                           |    6 
 src/views/bpm/task/copy/index.vue                                                                  |   27 
 src/views/system/app/index.vue                                                                     |    4 
 src/views/model/sche/scheme/record/index.vue                                                       |  153 
 src/api/system/tenantPackage/index.ts                                                              |    3 
 src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue                                     |  125 
 src/views/bpm/processInstance/create/index.vue                                                     |  423 
 package.json                                                                                       |   37 
 src/components/bpmnProcessDesigner/package/designer/plugins/palette/paletteProvider.js             |    6 
 src/api/model/mpk/mpk.ts                                                                           |    4 
 src/components/bpmnProcessDesigner/package/penal/custom-config/data.ts                             |   13 
 src/api/bpm/category/index.ts                                                                      |   10 
 src/views/bpm/processInstance/create/index_old.vue                                                 |  266 
 src/directives/permission/hasPermi.ts                                                              |   20 
 src/views/system/tenantPackage/TenantPackageForm.vue                                               |   24 
 src/views/model/sche/scheme/index.vue                                                              |   81 
 src/components/DiyEditor/components/mobile/Carousel/property.vue                                   |   12 
 src/utils/routerHelper.ts                                                                          |    8 
 .env.local                                                                                         |    6 
 src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue                    |  429 +
 src/components/bpmnProcessDesigner/package/designer/plugins/translate/zh.js                        |    2 
 src/views/system/operatelog/index.vue                                                              |    4 
 src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue                 |  235 
 src/store/modules/permission.ts                                                                    |   12 
 src/components/DiyEditor/components/mobile/ProductList/property.vue                                |    6 
 src/plugins/formCreate/index.ts                                                                    |   75 
 src/views/system/tenantPackage/index.vue                                                           |  235 
 src/components/SimpleProcessDesignerV2/src/nodes-config/DelayTimerNodeConfig.vue                   |  189 
 src/api/model/sche/record/index.ts                                                                 |   15 
 src/components/bpmnProcessDesigner/package/theme/element-variables.scss                            |    2 
 src/components/DiyEditor/components/mobile/NavigationBar/components/CellProperty.vue               |    6 
 src/components/SimpleProcessDesignerV2/src/nodes/InclusiveNode.vue                                 |  233 
 src/components/SimpleProcessDesignerV2/src/consts.ts                                               |  606 +
 src/views/bpm/model/CategoryDraggableModel.vue                                                     |  511 +
 src/views/bpm/processInstance/index.vue                                                            |  137 
 src/components/DiyEditor/components/mobile/PromotionPoint/config.ts                                |   96 
 src/components/bpmnProcessDesigner/package/theme/index.scss                                        |  119 
 src/views/model/sche/scheme/ScheduleSchemeForm.vue                                                 |  265 
 src/components/DiyEditor/components/mobile/PromotionPoint/index.vue                                |  202 
 src/utils/tree.ts                                                                                  |    3 
 uno.config.ts                                                                                      |    2 
 src/components/bpmnProcessDesigner/package/designer/plugins/descriptor/flowableDescriptor.json     |  227 
 src/components/SimpleProcessDesignerV2/src/nodes-config/UserTaskNodeConfig.vue                     |  913 ++
 src/assets/svgs/bpm/add-user.svg                                                                   |    1 
 src/components/DiyEditor/components/mobile/FloatingActionButton/property.vue                       |    4 
 src/assets/svgs/bpm/finish.svg                                                                     |    1 
 src/api/model/sche/model/index.ts                                                                  |   48 
 src/views/system/area/index.vue                                                                    |   22 
 src/components/SimpleProcessDesignerV2/src/node.ts                                                 |  510 +
 src/components/AppLinkInput/data.ts                                                                |    8 
 src/views/model/mpk/icon/index.vue                                                                 |    1 
 src/components/bpmnProcessDesigner/package/penal/custom-config/ElementCustomConfig.vue             |   39 
 types/env.d.ts                                                                                     |    2 
 src/views/bpm/model/index.vue                                                                      |  515 
 src/components/SimpleProcessDesignerV2/src/nodes/ExclusiveNode.vue                                 |  229 
 src/store/modules/bpm/simpleWorkflow.ts                                                            |   55 
 src/views/bpm/processInstance/detail/ProcessInstanceTaskList.vue                                   |  194 
 src/views/infra/apiAccessLog/index.vue                                                             |   14 
 src/components/DiyEditor/components/mobile/PromotionCombination/config.ts                          |   40 
 src/views/system/menu/index.vue                                                                    |  201 
 src/views/report/jmreport/index.vue                                                                |    4 
 src/utils/formCreate.ts                                                                            |    1 
 src/styles/index.scss                                                                              |    7 
 src/components/Editor/src/Editor.vue                                                               |    5 
 src/assets/svgs/bpm/condition.svg                                                                  |    1 
 src/components/Crontab/src/Crontab.vue                                                             |   66 
 src/components/bpmnProcessDesigner/package/designer/ProcessViewer.vue                              |  959 -
 src/layout/components/Setting/src/Setting.vue                                                      |    5 
 src/views/system/loginlog/index.vue                                                                |   14 
 src/store/modules/tagsView.ts                                                                      |   38 
 src/views/report/goview/index.vue                                                                  |    6 
 src/views/data/ind/item/DerIndDefineForm.vue                                                       |   60 
 src/views/Home/Index.vue                                                                           |  113 
 src/views/model/mpk/project/ProjectForm.vue                                                        |    2 
 src/components/bpmnProcessDesigner/package/penal/task/ElementTask.vue                              |   33 
 src/views/bpm/model/editor/index.vue                                                               |  262 
 src/views/model/pre/dm/index.vue                                                                   |    1 
 src/components/SimpleProcessDesignerV2/src/utils.ts                                                |   41 
 src/views/bpm/processInstance/create/ProcessDefinitionDetail.vue                                   |  298 
 src/views/model/mpk/file/MpkForm.vue                                                               |    5 
 src/styles/var.css                                                                                 |    8 
 src/views/bpm/task/manager/index.vue                                                               |    1 
 src/components/DiyEditor/components/mobile/CouponCard/property.vue                                 |    6 
 src/assets/svgs/bpm/delay.svg                                                                      |    1 
 src/components/DiyEditor/components/mobile/PromotionSeckill/property.vue                           |   86 
 src/views/bpm/task/done/index.vue                                                                  |  116 
 src/store/modules/app.ts                                                                           |   10 
 src/api/model/sche/scheme/index.ts                                                                 |   14 
 src/api/bpm/processInstance/index.ts                                                               |   46 
 src/assets/svgs/bpm/starter.svg                                                                    |    1 
 src/components/RouterSearch/index.vue                                                              |    8 
 src/components/DiyEditor/components/mobile/TabBar/property.vue                                     |    9 
 src/components/Draggable/index.vue                                                                 |    4 
 src/router/index.ts                                                                                |    2 
 src/components/SimpleProcessDesignerV2/src/nodes/ParallelNode.vue                                  |  184 
 src/components/SimpleProcessDesignerV2/theme/iconfont.woff                                         |    0 
 src/layout/components/TagsView/src/TagsView.vue                                                    |  146 
 src/components/DiyEditor/components/mobile/ProductCard/index.vue                                   |    7 
 src/hooks/web/useCache.ts                                                                          |   16 
 src/views/data/ind/category/CategoryForm.vue                                                       |    6 
 src/components/SimpleProcessDesignerV2/src/nodes/DelayTimerNode.vue                                |   98 
 src/hooks/web/useMessage.ts                                                                        |   24 
 src/components/UploadFile/src/UploadImgs.vue                                                       |   23 
 src/components/IFrame/src/IFrame.vue                                                               |   33 
 src/styles/global.module.scss                                                                      |    2 
 src/views/infra/config/index.vue                                                                   |   14 
 src/layout/components/UserInfo/src/components/LockPage.vue                                         |    2 
 src/layout/components/Menu/src/Menu.vue                                                            |   15 
 src/assets/svgs/bpm/approve.svg                                                                    |    1 
 src/layout/components/TabMenu/src/TabMenu.vue                                                      |    6 
 src/components/SimpleProcessDesignerV2/src/nodes-config/CopyTaskNodeConfig.vue                     |  374 +
 src/views/data/point/DaPointForm.vue                                                               |   68 
 src/views/bpm/group/UserGroupForm.vue                                                              |    2 
 src/views/bpm/model/ModelForm.vue                                                                  |  381 
 src/assets/svgs/bpm/auditor.svg                                                                    |    1 
 src/components/SimpleProcessDesignerV2/src/SimpleProcessDesigner.vue                               |  306 
 src/components/bpmnProcessDesigner/package/penal/task/task-components/ServiceTask.vue              |   91 
 types/router.d.ts                                                                                  |    3 
 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/layout/components/Footer/src/Footer.vue                                                        |    8 
 src/layout/components/UserInfo/src/components/LockDialog.vue                                       |    2 
 src/components/DiyEditor/components/mobile/Divider/property.vue                                    |    6 
 src/views/bpm/processExpression/ProcessExpressionForm.vue                                          |    2 
 src/views/infra/file/index.vue                                                                     |   10 
 src/components/SimpleProcessDesignerV2/theme/iconfont.woff2                                        |    0 
 src/views/micro/index.vue                                                                          |   26 
 src/assets/svgs/bpm/reject.svg                                                                     |    1 
 src/views/model/pre/item/MmPredictItemForm.vue                                                     |  171 
 /dev/null                                                                                          |   89 
 src/views/data/ind/data/DataSetForm.vue                                                            |    3 
 src/components/DiyEditor/components/mobile/TabBar/config.ts                                        |   16 
 src/components/SimpleProcessDesignerV2/src/nodes/UserTaskNode.vue                                  |  174 
 src/directives/permission/hasRole.ts                                                               |    5 
 src/components/FormCreate/src/utils/index.ts                                                       |   43 
 src/views/data/point/DaPointChart.vue                                                              |    1 
 266 files changed, 18,517 insertions(+), 3,510 deletions(-)

diff --git a/.env.dev b/.env.dev
index 82ec13b..89a15bd 100644
--- a/.env.dev
+++ b/.env.dev
@@ -1,15 +1,13 @@
 # 开发环境:本地只启动前端项目,依赖开发环境(后端、APP)
-NODE_ENV=production
+NODE_ENV=development
 
 VITE_DEV=true
 
 # 请求路径
-VITE_BASE_URL='http://localhost:48080'
+VITE_BASE_URL='http://localhost'
 
 # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
 VITE_UPLOAD_TYPE=server
-# 上传路径
-VITE_UPLOAD_URL='http://localhost:48080/admin-api/infra/file/upload'
 
 # 接口地址
 VITE_API_URL=/admin-api
@@ -24,13 +22,13 @@
 VITE_SOURCEMAP=true
 
 # 打包路径
-VITE_BASE_PATH=/plat
+VITE_BASE_PATH=/plat/
 
 # 输出路径
 VITE_OUT_DIR=dist
 
 # 公共静态文件路径
-VITE_STATIC_DIR=/
+VITE_STATIC_DIR=/plat/
 
 # 商城H5会员端域名iai
 VITE_MALL_H5_DOMAIN='http://'
@@ -39,4 +37,4 @@
 VITE_APP_CAPTCHA_ENABLE=false
 
 # MDK模型上传路径
-MDK_UPLOAD_URL='http://localhost:48080/admin-api/model//pre/item/upload-model'
+MDK_UPLOAD_URL='http://localhost/admin-api/model/pre/item/upload-model'
diff --git a/.env.local b/.env.local
index b28e8e2..9a61d8d 100644
--- a/.env.local
+++ b/.env.local
@@ -8,8 +8,6 @@
 
 # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务
 VITE_UPLOAD_TYPE=server
-# 上传路径
-VITE_UPLOAD_URL='http://localhost:48080/admin-api/infra/file/upload'
 
 # 接口地址
 VITE_API_URL=/admin-api
@@ -24,10 +22,10 @@
 VITE_SOURCEMAP=false
 
 # 打包路径
-VITE_BASE_PATH=/plat
+VITE_BASE_PATH=/plat/
 
 # 公共静态文件路径
-VITE_STATIC_DIR=/
+VITE_STATIC_DIR=/plat/
 
 # 商城H5会员端域名
 VITE_MALL_H5_DOMAIN='http://localhost:3000'
diff --git a/.env.prod b/.env.prod
index 50729b1..3dfd239 100644
--- a/.env.prod
+++ b/.env.prod
@@ -1,15 +1,13 @@
-# 生产环境:只在打包时使用
+# 测试环境:只在打包时使用
 NODE_ENV=production
 
 VITE_DEV=false
 
 # 请求路径
-VITE_BASE_URL='http://localhost:48080'
+VITE_BASE_URL='http://10.88.4.131'
 
 # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
 VITE_UPLOAD_TYPE=server
-# 上传路径
-VITE_UPLOAD_URL='http://localhost:48080/admin-api/infra/file/upload'
 
 # 接口地址
 VITE_API_URL=/admin-api
@@ -24,10 +22,16 @@
 VITE_SOURCEMAP=false
 
 # 打包路径
-VITE_BASE_PATH=/
+VITE_BASE_PATH=/plat
+
+# 数据采集服务所在服务器,映射截图图片用
+VITE_VIDEO_CAMERA_DOMAIN='10.88.4.131'
 
 # 输出路径
-VITE_OUT_DIR=dist-prod
+VITE_OUT_DIR=dist
 
-# 商城H5会员端域名
-VITE_MALL_H5_DOMAIN='http://'
+# 公共静态文件路径
+VITE_STATIC_DIR=/plat/
+
+# 验证码的开关
+VITE_APP_CAPTCHA_ENABLE=false
diff --git a/.env.test b/.env.test
index 65af8e8..6478933 100644
--- a/.env.test
+++ b/.env.test
@@ -4,12 +4,10 @@
 VITE_DEV=false
 
 # 请求路径
-VITE_BASE_URL='http://172.16.8.100:48080'
+VITE_BASE_URL='http://172.16.8.100'
 
 # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
 VITE_UPLOAD_TYPE=server
-# 上传路径
-VITE_UPLOAD_URL='http://172.16.8.100:48080/admin-api/infra/file/upload'
 
 # 接口地址
 VITE_API_URL=/admin-api
@@ -34,9 +32,6 @@
 
 # 公共静态文件路径
 VITE_STATIC_DIR=/plat/
-
-# 商城H5会员端域名
-VITE_MALL_H5_DOMAIN='http://'
 
 # 验证码的开关
 VITE_APP_CAPTCHA_ENABLE=false
diff --git a/index.html b/index.html
index 0d9deba..c3d9097 100644
--- a/index.html
+++ b/index.html
@@ -2,7 +2,7 @@
 <html lang="en">
   <head>
     <meta charset="UTF-8" />
-    <link rel="icon" href="/favicon.ico" />
+    <link rel="icon" href="/src/assets/imgs/logo.png" />
     <meta http-equiv="X-UA-Compatible" content="IE=edge" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <meta
@@ -136,7 +136,7 @@
       <div class="app-loading">
         <div class="app-loading-wrap">
           <div class="app-loading-title">
-            <img src="/logo.gif" class="app-loading-logo" alt="Logo" />
+            <img src="/src/assets/imgs/logo.png" class="app-loading-logo" alt="Logo" />
             <div class="app-loading-title">%VITE_APP_TITLE%</div>
           </div>
           <div class="app-loading-item">
diff --git a/package.json b/package.json
index 435c717..f904721 100644
--- a/package.json
+++ b/package.json
@@ -4,17 +4,16 @@
   "description": "基于vue3、vite4、element-plus、typesScript",
   "author": "iailab",
   "private": false,
-  "main": "dist/iailab-plat-ui.min.js",
   "scripts": {
     "i": "pnpm install",
     "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 +26,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",
@@ -39,7 +38,7 @@
     "animate.css": "^4.1.1",
     "axios": "^1.6.8",
     "benz-amr-recorder": "^1.1.5",
-    "bpmn-js-token-simulation": "^0.10.0",
+    "bpmn-js-token-simulation": "^0.36.0",
     "camunda-bpmn-moddle": "^7.0.1",
     "cropperjs": "^1.6.1",
     "crypto-js": "^4.2.0",
@@ -48,7 +47,7 @@
     "driver.js": "^1.3.1",
     "echarts": "^5.5.0",
     "echarts-wordcloud": "^2.1.0",
-    "element-plus": "2.7.0",
+    "element-plus": "2.9.1",
     "fast-xml-parser": "^4.3.2",
     "highlight.js": "^11.9.0",
     "jsencrypt": "^3.3.2",
@@ -65,18 +64,19 @@
     "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",
-    "wujie-vue3": "^1.0.22",
-    "xml-js": "^1.6.11"
+    "xml-js": "^1.6.11",
+    "wujie-vue3": "^1.0.22"
   },
   "devDependencies": {
     "@commitlint/cli": "^19.0.1",
@@ -97,8 +97,8 @@
     "@vitejs/plugin-vue": "^5.0.4",
     "@vitejs/plugin-vue-jsx": "^3.1.0",
     "autoprefixer": "^10.4.17",
-    "bpmn-js": "8.9.0",
-    "bpmn-js-properties-panel": "0.46.0",
+    "bpmn-js": "^17.9.2",
+    "bpmn-js-properties-panel": "5.23.0",
     "consola": "^3.2.3",
     "eslint": "^8.57.0",
     "eslint-config-prettier": "^9.1.0",
@@ -121,7 +121,7 @@
     "stylelint-order": "^6.0.4",
     "terser": "^5.28.1",
     "typescript": "5.3.3",
-    "unocss": "^0.58.9",
+    "unocss": "^0.58.5",
     "unplugin-auto-import": "^0.16.7",
     "unplugin-element-plus": "^0.8.0",
     "unplugin-vue-components": "^0.25.2",
@@ -132,7 +132,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"
   },
@@ -145,6 +145,7 @@
     "url": "https://xxxx"
   },
   "homepage": "https://xxxx",
+  "web-types": "./web-types.json",
   "engines": {
     "node": ">= 16.0.0",
     "pnpm": ">=8.6.0"
diff --git a/public/favicon.ico b/public/favicon.ico
deleted file mode 100644
index 5a7de08..0000000
--- a/public/favicon.ico
+++ /dev/null
Binary files differ
diff --git a/public/logo.gif b/public/logo.gif
deleted file mode 100644
index fdbd32c..0000000
--- a/public/logo.gif
+++ /dev/null
Binary files differ
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/api/data/da/point/index.ts b/src/api/data/da/point/index.ts
index 366f331..b64a001 100644
--- a/src/api/data/da/point/index.ts
+++ b/src/api/data/da/point/index.ts
@@ -20,7 +20,9 @@
 
 export interface DaPointPageReqVO extends PageParam {
   pointNo?: string,
-  pointName?: string
+  pointName?: string,
+  tagNo?: string,
+  collectQuality?: string,
 }
 
 
@@ -34,6 +36,11 @@
   return request.get({ url: '/data/da/point/list', params })
 }
 
+// 查询DaPoint simpleList
+export const getPointSimpleList = (params: DaPointPageReqVO) => {
+  return request.get({ url: '/data/da/point/simple-list', params })
+}
+
 // 查询DaPoint详情
 export const getDaPoint = (id: number) => {
   return request.get({ url: `/data/da/point/info/${id}`})
diff --git a/src/api/login/index.ts b/src/api/login/index.ts
index e6d9f52..4b82a93 100644
--- a/src/api/login/index.ts
+++ b/src/api/login/index.ts
@@ -42,10 +42,10 @@
   return request.get({ url: '/system/auth/get-permission-info' })
 }
 
-// 获取用应用户权限信息
-export const getUserAppInfo = (id: number) => {
-  return request.get({ url: '/system/auth/get-app-permission-info?id=' + id })
-}
+// // 获取用应用户权限信息
+// export const getUserAppInfo = (id: number) => {
+//   return request.get({ url: '/system/auth/get-app-permission-info?id=' + id })
+// }
 
 //获取登录验证码
 export const sendSmsCode = (data: SmsCodeVO) => {
diff --git a/src/api/model/mpk/mpk.ts b/src/api/model/mpk/mpk.ts
index 653cf8b..dc2782f 100644
--- a/src/api/model/mpk/mpk.ts
+++ b/src/api/model/mpk/mpk.ts
@@ -42,8 +42,8 @@
   return request.post({ url: '/model/mpk/api/test', data: params })
 }
 
-export const list = () => {
-  return request.get({ url: '/model/mpk/file/list'})
+export const list = (params) => {
+  return request.get({ url: '/model/mpk/file/list',  params})
 }
 
 export const publish = (params) => {
diff --git a/src/api/model/mpk/project.ts b/src/api/model/mpk/project.ts
index 514b05b..33f1cf6 100644
--- a/src/api/model/mpk/project.ts
+++ b/src/api/model/mpk/project.ts
@@ -1,6 +1,6 @@
 import request from '@/config/axios'
 
-export const getPage = async (params: PageParam) => {
+export const getPage = async (params) => {
   return await request.get({ url: '/model/mpk/project/page', params })
 }
 
@@ -21,13 +21,14 @@
 }
 
 export const packageProject = (params) => {
-  return request.download({ url: '/model/mpk/file/packageModel', params })
+  // 超时时间两分钟
+  return request.download({ url: '/model/mpk/file/packageModel', params, timeout: 2 * 60 * 1000 })
 }
 
 export const list = () => {
   return request.get({ url: '/model/mpk/project/list'})
 }
 
-export const getProjectModel = async (params: PageParam) => {
+export const getProjectModel = async (params) => {
   return await request.get({ url: '/model/mpk/project/getProjectModel', params })
 }
diff --git a/src/api/model/pre/item/index.ts b/src/api/model/pre/item/index.ts
index a4e2065..73a7683 100644
--- a/src/api/model/pre/item/index.ts
+++ b/src/api/model/pre/item/index.ts
@@ -69,6 +69,8 @@
 export interface MmPredictItemPageReqVO extends PageParam {
   itemno?: string,
   itemname?: string,
+  itemtypeid?: string,
+  modulename?: string,
 }
 
 // 查询MmPredictItem列表
diff --git a/src/api/model/sche/model/index.ts b/src/api/model/sche/model/index.ts
index 8863d31..0ac2b05 100644
--- a/src/api/model/sche/model/index.ts
+++ b/src/api/model/sche/model/index.ts
@@ -20,6 +20,7 @@
   status: number,
   paramList: null,
   settingList: null
+  modelOut:null
 }
 
 export interface ModelParamVO {
@@ -70,7 +71,7 @@
 }
 
 // 查询模型参数列表
-export const getModelParamList = async () => {
+export const getModelParamList = async (id) => {
 
   const dataPointList = ref([] as DataPointApi.DaPointVO)
   dataPointList.value = await DataPointApi.getPointList({})
@@ -80,7 +81,8 @@
       pointList.push(
         {
           id: item.id,
-          name: item.pointName
+          name: item.pointName,
+          itemNo : item.pointNo
         }
       )
     })
@@ -88,16 +90,25 @@
 
   const predictItemList = ref([] as PredictItemApi.MmPredictItemVO)
   predictItemList.value = await PredictItemApi.getMmPredictItemList({
-    status: CommonEnabled.ENABLE,
-    itemtypename: 'NormalItem'
+    status: CommonEnabled.ENABLE
   })
-  const itemList = []
-  if (predictItemList.value) {
-    predictItemList.value.forEach(item => {
-      itemList.push(
+  const normalItemList = []
+  const predictNormalItemList = predictItemList.value.filter(e => e.itemtypename === 'NormalItem' && e.outPuts && e.outPuts.length > 0);
+  if (predictNormalItemList && predictNormalItemList.length > 0) {
+    // 过滤掉本身
+    predictNormalItemList.filter(e => e.id !== id).forEach(item => {
+      normalItemList.push(
         {
-          id: item.id,
-          name:  item.itemname
+          value: item.id,
+          label:  item.itemname,
+          predictlength: item.predictlength,
+          moduleid: item.moduleid,
+          children: item.outPuts?.map(e => {
+            return {
+              value: e.id,
+              label: e.resultName
+            }
+          })
         }
       )
     })
@@ -118,9 +129,24 @@
     })
   }
 
+  const predictMergeItemList = predictItemList.value.filter(e => e.itemtypename === 'MergeItem' && e.outPuts && e.outPuts.length > 0);
+  const mergeItemList = []
+  if (predictMergeItemList && predictMergeItemList.length > 0) {
+    // 过滤掉本身
+    predictMergeItemList.filter(e => e.id !== id).forEach(item => {
+      mergeItemList.push(
+          {
+            id: item.outPuts[0].id,
+            name: item.itemname
+          }
+      )
+    })
+  }
+
   return {
     'DATAPOINT':pointList,
-    'PREDICTITEM': itemList,
+    'NormalItem': normalItemList,
+    'MergeItem': mergeItemList,
     'PLAN': planList,
   }
 }
diff --git a/src/api/model/sche/record/index.ts b/src/api/model/sche/record/index.ts
new file mode 100644
index 0000000..f712041
--- /dev/null
+++ b/src/api/model/sche/record/index.ts
@@ -0,0 +1,15 @@
+import request from '@/config/axios'
+
+export interface StScheduleRecordPageReqVO extends PageParam {
+  schemeId?: string
+}
+
+// 查询ScheduleRecord列表
+export const getScheduleRecordPage = (params: StScheduleRecordPageReqVO) => {
+  return request.get({ url: '/model/sche/record/page', params })
+}
+
+// 查询ScheduleRecord详情
+export const getScheduleRecord = (id: string) => {
+  return request.get({ url: '/model/sche/record/get?id=' + id})
+}
diff --git a/src/api/model/sche/scheme/index.ts b/src/api/model/sche/scheme/index.ts
index 3d3971f..0376394 100644
--- a/src/api/model/sche/scheme/index.ts
+++ b/src/api/model/sche/scheme/index.ts
@@ -13,6 +13,7 @@
   scheduleTime: string
   remark: string
   status: number
+  mpkprojectid: string
 }
 
 export interface ScheduleSchemePageReqVO extends PageParam {
@@ -44,3 +45,16 @@
 export const deleteScheduleScheme = (id: number) => {
   return request.delete({ url: '/model/sche/scheme/delete?id=' + id })
 }
+
+// 启用
+export const enable = (ids) => {
+  const data = ids
+  return request.put({ url: '/model/sche/scheme/enable', data })
+}
+
+// 禁用
+export const disable = (ids) => {
+  const data = ids
+  return request.put({ url: '/model/sche/scheme/disable', data })
+}
+
diff --git a/src/api/system/tenantPackage/index.ts b/src/api/system/tenantPackage/index.ts
index e01375a..fdf9a5b 100644
--- a/src/api/system/tenantPackage/index.ts
+++ b/src/api/system/tenantPackage/index.ts
@@ -4,6 +4,9 @@
   id: number
   name: string
   status: number
+  icon: string
+  labels: string
+  description: string
   remark: string
   creator: string
   updater: string
diff --git a/src/assets/imgs/logo.png b/src/assets/imgs/logo.png
index 4017d80..f55aafb 100644
--- a/src/assets/imgs/logo.png
+++ b/src/assets/imgs/logo.png
Binary files differ
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/delay.svg b/src/assets/svgs/bpm/delay.svg
new file mode 100644
index 0000000..cbc31df
--- /dev/null
+++ b/src/assets/svgs/bpm/delay.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="1735905505218" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4277" width="200" height="200" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M561.778 454.929h198.117c0.549 0 0.994 0.444 0.994 1.001v97.553a0.998 0.998 0 0 1-0.994 1.001H463.224a1.005 1.005 0 0 1-1.002-1V207.04c0-0.552 0.444-1 1.002-1h97.552c0.553 0 1.002 0.455 1.002 1v247.89zM512 952.706c-247.424 0-448-200.576-448-448 0-247.423 200.576-448 448-448s448 200.577 448 448c0 247.424-200.576 448-448 448z m0-99.555c192.44 0 348.444-156.004 348.444-348.445 0-192.44-156.003-348.444-348.444-348.444-192.44 0-348.444 156.004-348.444 348.444 0 192.441 156.003 348.445 348.444 348.445z" fill="#3296FA" p-id="4278"></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/assets/svgs/pay/icon/alipay_app.svg b/src/assets/svgs/pay/icon/alipay_app.svg
deleted file mode 100644
index ebf1188..0000000
--- a/src/assets/svgs/pay/icon/alipay_app.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg t="1627279997305" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11904" width="40" height="40"><path d="M938.7008 669.525333L938.7008 249.412267c0-90.555733-73.5232-164.078933-164.1472-164.078933L249.378133 85.333333c-90.555733 0-164.078933 73.48906699-164.078933 164.078933l0 525.2096c0 90.555733 73.454933 164.078933 164.07893301 164.078933l525.20959999 0c80.725333 0 147.8656-58.368 161.553067-135.099733-43.52-18.8416-232.106667-100.283733-330.376533-147.182933-74.786133 90.589867-153.088 144.930133-271.121067 144.930133s-196.81279999-72.704-187.357867-161.655467c6.2464-58.402133 46.2848-153.9072 220.296533-137.5232 91.682133 8.6016 133.666133 25.736533 208.418133 50.414933 19.3536-35.4304 35.4304-74.513067 47.616-116.0192L292.0448 436.565333l0-32.8704 164.0448 0 0-58.9824L256 344.712533l1e-8-36.181333 200.12373299 0L456.123733 223.3344c0 0 1.809067-13.312 16.520533-13.31200001l82.056533 1e-8 0 98.474667 213.333333 0 0 36.181333-213.333333 1e-8 0 58.98239999 174.045867 0c-16.00853301 65.1264-40.277333 124.962133-70.690133 177.220267C708.608 599.176533 938.7008 669.525333 938.7008 669.525333L938.7008 669.525333 938.7008 669.525333 938.7008 669.525333zM321.57013299 744.994133c-124.7232 0-144.452267-78.7456-137.83039999-111.65013299 6.5536-32.733867 42.666667-75.502933 112.0256-75.50293301 79.6672 0 151.04 20.445867 236.714667 62.088533C472.302933 698.333867 398.370133 744.994133 321.57013299 744.994133L321.57013299 744.994133 321.57013299 744.994133zM321.57013299 744.994133" fill="#1296db" p-id="11905"></path></svg>
\ No newline at end of file
diff --git a/src/assets/svgs/pay/icon/alipay_bar.svg b/src/assets/svgs/pay/icon/alipay_bar.svg
deleted file mode 100644
index eb1e1e8..0000000
--- a/src/assets/svgs/pay/icon/alipay_bar.svg
+++ /dev/null
@@ -1,2 +0,0 @@
-<?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="1627279586085" class="icon" viewBox="0 0 1036 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6737" xmlns:xlink="http://www.w3.org/1999/xlink" width="40.46875" height="40"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.eot?#iefix") format("embedded-opentype"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.woff2") format("woff2"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.woff") format("woff"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.ttf") format("truetype"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.svg#iconfont") format("svg"); }
-</style></defs><path d="M27.587124 336.619083h69.148134a13.978733 13.978733 0 0 0 13.79235-13.978733V13.989916A13.978733 13.978733 0 0 0 96.735258 0.011183H27.587124a13.978733 13.978733 0 0 0-13.792351 13.978733v308.650434a13.978733 13.978733 0 0 0 13.792351 13.978733z m165.880969 0h27.584701a13.978733 13.978733 0 0 0 13.79235-13.978733V13.989916a13.978733 13.978733 0 0 0-13.79235-13.978733h-27.584701a13.978733 13.978733 0 0 0-13.79235 13.978733v308.650434a13.978733 13.978733 0 0 0 13.79235 13.978733z m138.109886 322.629167h-110.525185a27.771084 27.771084 0 0 0-27.584701 28.14385v111.829867a27.771084 27.771084 0 0 0 27.584701 28.14385h110.525185a27.957467 27.957467 0 0 0 27.584701-28.14385v-111.829867a27.957467 27.957467 0 0 0-27.584701-28.14385z m484.596091-322.629167h27.584701a13.978733 13.978733 0 0 0 13.79235-13.978733V13.989916a13.978733 13.978733 0 0 0-14.537883-13.978733h-27.5847a13.978733 13.978733 0 0 0-13.978734 13.978733v308.650434a13.978733 13.978733 0 0 0 13.978734 13.978733z m-469.871825 0H428.68358a13.978733 13.978733 0 0 0 13.792351-13.978733V13.989916A13.978733 13.978733 0 0 0 428.68358 0.011183h-83.126867a13.978733 13.978733 0 0 0-13.792351 13.978733v308.650434a13.978733 13.978733 0 0 0 13.792351 13.978733z m594.189361 0h69.148134a13.978733 13.978733 0 0 0 13.792351-13.978733V13.989916a13.978733 13.978733 0 0 0-14.537883-13.978733h-69.148135a13.978733 13.978733 0 0 0-13.79235 13.978733v308.650434a13.978733 13.978733 0 0 0 13.79235 13.978733z m-412.279444 126.181367H66.91396A67.470687 67.470687 0 0 0 0.002423 530.830286v425.139878a67.470687 67.470687 0 0 0 66.911537 68.029836h418.802853a67.470687 67.470687 0 0 0 66.911537-68.029836V487.775787a24.788954 24.788954 0 0 0-24.416188-24.975337z m-58.337914 433.899885a42.681733 42.681733 0 0 1-42.495349 43.054498H125.438257a42.681733 42.681733 0 0 1-42.495349-43.054498V590.100115a42.681733 42.681733 0 0 1 42.495349-43.054498h301.940642a42.681733 42.681733 0 0 1 42.495349 43.054498z m525.22761-433.899885a41.749817 41.749817 0 0 0-41.377051 42.122583v55.914934a41.377051 41.377051 0 1 0 82.940485 0v-55.914934a41.749817 41.749817 0 0 0-41.563434-42.122583z m0 223.659734a41.749817 41.749817 0 0 0-41.377051 42.122584V894.65012a45.477479 45.477479 0 0 1-45.291096 45.850246h-159.730327a43.240882 43.240882 0 0 0-43.613649 37.276622A41.9362 41.9362 0 0 0 745.534871 1024h233.538039a57.778765 57.778765 0 0 0 57.405999-58.337914V729.3283a41.749817 41.749817 0 0 0-41.377051-41.9362zM732.488053 322.64035V13.989916a13.978733 13.978733 0 0 0-13.79235-13.978733h-82.940485a13.978733 13.978733 0 0 0-13.79235 13.978733v308.650434a13.978733 13.978733 0 0 0 13.79235 13.978733h82.940485a13.978733 13.978733 0 0 0 13.79235-13.978733zM532.126208 0.011183c-11.36937 0-20.688525 6.337026-20.688526 13.978733v308.650434c0 7.828091 9.319156 13.978733 20.688526 13.978733s20.688525-6.337026 20.688525-13.978733V13.989916c0-7.641708-9.319156-13.978733-20.688525-13.978733z" p-id="6738" fill="#1977FD"></path><path d="M745.534871 462.80045a41.749817 41.749817 0 0 0-41.377051 42.122583v252.549117a41.377051 41.377051 0 1 0 82.940485 0V504.923033A41.749817 41.749817 0 0 0 745.534871 462.80045" p-id="6739" fill="#1977FD"></path></svg>
diff --git a/src/assets/svgs/pay/icon/alipay_pc.svg b/src/assets/svgs/pay/icon/alipay_pc.svg
deleted file mode 100644
index 2a75277..0000000
--- a/src/assets/svgs/pay/icon/alipay_pc.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg t="1627279878333" class="icon" viewBox="0 0 1285 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8535" width="40" height="40"><path d="M1141.76 855.04h-286.72c0 40.96 30.72 71.68 71.68 71.68h107.52c20.48 0 35.84 15.36 35.84 35.84s-15.36 35.84-35.84 35.84h-783.36c-20.48 0-35.84-15.36-35.84-35.84s15.36-35.84 35.84-35.84h107.52c40.96 0 71.68-30.72 71.68-71.68h-286.72c-76.8 0-143.36-61.44-143.36-143.36v-568.32c0-76.8 61.44-143.36 143.36-143.36h993.28c76.8 0 143.36 61.44 143.36 143.36v568.32c5.12 76.8-56.32 143.36-138.24 143.36z m71.68-711.68c0-40.96-30.72-71.68-71.68-71.68h-993.28c-40.96 0-71.68 30.72-71.68 71.68v568.32c0 40.96 30.72 71.68 71.68 71.68h993.28c40.96 0 71.68-30.72 71.68-71.68v-568.32z m-143.36 568.32h-855.04c-40.96 0-71.68-30.72-71.68-71.68v-424.96c0-40.96 30.72-71.68 71.68-71.68h855.04c40.96 0 71.68 30.72 71.68 71.68v424.96c0 40.96-30.72 71.68-71.68 71.68z" p-id="8536" fill="#1977FD"></path></svg>
\ No newline at end of file
diff --git a/src/assets/svgs/pay/icon/alipay_qr.svg b/src/assets/svgs/pay/icon/alipay_qr.svg
deleted file mode 100644
index 4833750..0000000
--- a/src/assets/svgs/pay/icon/alipay_qr.svg
+++ /dev/null
@@ -1,2 +0,0 @@
-<?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="1627279238245" class="icon" viewBox="0 0 1115 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4112" width="43.5546875" height="40" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.eot?#iefix") format("embedded-opentype"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.woff2") format("woff2"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.woff") format("woff"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.ttf") format("truetype"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.svg#iconfont") format("svg"); }
-</style></defs><path d="M751.388 68.267a34.133 34.133 0 0 1 0-68.267h227.556a91.022 91.022 0 0 1 91.022 91.022v227.556a34.133 34.133 0 1 1-68.266 0V91.022a22.756 22.756 0 0 0-22.756-22.755H751.388M1001.7 705.422a34.133 34.133 0 0 1 68.266 0v227.556A91.022 91.022 0 0 1 978.944 1024H748.885a34.133 34.133 0 0 1 0-68.267H978.49a22.756 22.756 0 0 0 22.755-22.755V705.422M364.09 955.733a34.133 34.133 0 1 1 0 68.267H136.533a91.022 91.022 0 0 1-91.022-91.022V705.422a34.133 34.133 0 0 1 68.267 0v227.556a22.756 22.756 0 0 0 22.755 22.755H364.09M113.778 318.578a34.133 34.133 0 1 1-68.267 0V91.022A91.022 91.022 0 0 1 136.533 0H364.09a34.133 34.133 0 0 1 0 68.267H136.533a22.756 22.756 0 0 0-22.755 22.755v227.556M34.133 477.867a34.133 34.133 0 0 0 0 68.266h168.619v-68.266z m1046.756 0H912.27v68.266h168.619a34.133 34.133 0 0 0 0-68.266zM202.752 157.24h709.746v320.627H202.752z m0 388.893h709.746V866.76H202.752z" fill="#1977FD" p-id="4113"></path></svg>
\ No newline at end of file
diff --git a/src/assets/svgs/pay/icon/alipay_wap.svg b/src/assets/svgs/pay/icon/alipay_wap.svg
deleted file mode 100644
index 87075db..0000000
--- a/src/assets/svgs/pay/icon/alipay_wap.svg
+++ /dev/null
@@ -1 +0,0 @@
-<?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="1645964864184" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8460" xmlns:xlink="http://www.w3.org/1999/xlink" width="40" height="40"><defs><style type="text/css"></style></defs><path d="M768.3 0 255.7 0c-70.8 0-128.1 57.4-128.1 128.1l0 767.8c0 70.8 57.4 128.1 128.1 128.1L512 1024l256.3 0c70.8 0 128.1-57.4 128.1-128.1L896.4 128.1C896.4 57.3 839 0 768.3 0zM383.9 96.1c0-17.7 14.3-32 32-32l192.2 0c17.7 0 32 14.3 32 32l0 0c0 17.7-14.3 32-32 32L415.9 128.1C398.2 128.1 383.9 113.8 383.9 96.1L383.9 96.1zM512 959.9 512 959.9 512 959.9c-35.4 0-64.1-28.8-64.1-64.1 0-35.4 28.7-64.1 64.1-64.1l0 0 0 0c35.4 0 64.1 28.7 64.1 64.1C576.1 931.1 547.4 959.9 512 959.9zM832.3 755.6c0 6.7-5.4 12.2-12.2 12.2L203.9 767.8c-6.7 0-12.2-5.4-12.2-12.2L191.7 204.3c0-6.7 5.4-12.2 12.2-12.2l616.3 0c6.7 0 12.2 5.4 12.2 12.2L832.4 755.6z" p-id="8461" fill="#1977FD"></path></svg>
\ No newline at end of file
diff --git a/src/assets/svgs/pay/icon/mock.svg b/src/assets/svgs/pay/icon/mock.svg
deleted file mode 100644
index 27b09ea..0000000
--- a/src/assets/svgs/pay/icon/mock.svg
+++ /dev/null
@@ -1 +0,0 @@
-<?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="1676209854312" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3033" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M173.077333 362.666667l91.114667-214.677334a65.6 65.6 0 0 1 86.016-34.773333c11.584 4.906667 24.96 10.282667 40.896 16.448 8.277333 3.2 16.789333 6.464 27.904 10.666667 28.202667 10.709333 39.296 14.933333 46.144 17.642666l51.477333-51.669333c28.181333-28.16 74.112-27.946667 102.570667 0.533333l195.925333 195.925334c16.426667 16.426667 23.445333 38.634667 21.056 59.904H896a42.666667 42.666667 0 0 1 42.666667 42.666666v490.666667a42.666667 42.666667 0 0 1-42.666667 42.666667H128a42.666667 42.666667 0 0 1-42.666667-42.666667V405.333333a42.666667 42.666667 0 0 1 42.666667-42.666666h45.077333z m48.96 0h39.104l169.194667-169.770667-27.328-10.389333c-11.2-4.245333-19.818667-7.530667-28.224-10.794667a1459.2 1459.2 0 0 1-42.197333-17.002667 20.522667 20.522667 0 0 0-26.901334 10.88L222.037333 362.666667z m108.842667 0h454.954667a23.509333 23.509333 0 0 0-5.290667-25.322667l-195.925333-195.925333a23.36 23.36 0 0 0-33.024-0.213334L330.88 362.666667zM128 405.333333v490.666667h768V405.333333H128z m597.333333 320a85.333333 85.333333 0 1 1 0-170.666666 85.333333 85.333333 0 0 1 0 170.666666z m0-42.666666a42.666667 42.666667 0 1 0 0-85.333334 42.666667 42.666667 0 0 0 0 85.333334z" fill="#4296d5" p-id="3034"></path></svg>
\ No newline at end of file
diff --git a/src/assets/svgs/pay/icon/wx_app.svg b/src/assets/svgs/pay/icon/wx_app.svg
deleted file mode 100644
index ad40b2a..0000000
--- a/src/assets/svgs/pay/icon/wx_app.svg
+++ /dev/null
@@ -1,2 +0,0 @@
-<?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="1627279375144" class="icon" viewBox="0 0 1115 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4399" width="43.5546875" height="40" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.eot?#iefix") format("embedded-opentype"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.woff2") format("woff2"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.woff") format("woff"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.ttf") format("truetype"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.svg#iconfont") format("svg"); }
-</style></defs><path d="M751.388 68.267a34.133 34.133 0 0 1 0-68.267h227.556a91.022 91.022 0 0 1 91.022 91.022v227.556a34.133 34.133 0 1 1-68.266 0V91.022a22.756 22.756 0 0 0-22.756-22.755H751.388M1001.7 705.422a34.133 34.133 0 0 1 68.266 0v227.556A91.022 91.022 0 0 1 978.944 1024H748.885a34.133 34.133 0 0 1 0-68.267H978.49a22.756 22.756 0 0 0 22.755-22.755V705.422M364.09 955.733a34.133 34.133 0 1 1 0 68.267H136.533a91.022 91.022 0 0 1-91.022-91.022V705.422a34.133 34.133 0 0 1 68.267 0v227.556a22.756 22.756 0 0 0 22.755 22.755H364.09M113.778 318.578a34.133 34.133 0 1 1-68.267 0V91.022A91.022 91.022 0 0 1 136.533 0H364.09a34.133 34.133 0 0 1 0 68.267H136.533a22.756 22.756 0 0 0-22.755 22.755v227.556M34.133 477.867a34.133 34.133 0 0 0 0 68.266h168.619v-68.266z m1046.756 0H912.27v68.266h168.619a34.133 34.133 0 0 0 0-68.266zM202.752 157.24h709.746v320.627H202.752z m0 388.893h709.746V866.76H202.752z" fill="#04C361" p-id="4400"></path></svg>
\ No newline at end of file
diff --git a/src/assets/svgs/pay/icon/wx_bar.svg b/src/assets/svgs/pay/icon/wx_bar.svg
deleted file mode 100644
index 11292e6..0000000
--- a/src/assets/svgs/pay/icon/wx_bar.svg
+++ /dev/null
@@ -1 +0,0 @@
-<?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="1627279586085" class="icon" viewBox="0 0 1036 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6737" xmlns:xlink="http://www.w3.org/1999/xlink" width="40.46875" height="40"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url(&quot;//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.eot?#iefix&quot;) format(&quot;embedded-opentype&quot;), url(&quot;//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.woff2&quot;) format(&quot;woff2&quot;), url(&quot;//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.woff&quot;) format(&quot;woff&quot;), url(&quot;//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.ttf&quot;) format(&quot;truetype&quot;), url(&quot;//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.svg#iconfont&quot;) format(&quot;svg&quot;); }</style></defs><path d="M27.587124 336.619083h69.148134a13.978733 13.978733 0 0 0 13.79235-13.978733V13.989916A13.978733 13.978733 0 0 0 96.735258 0.011183H27.587124a13.978733 13.978733 0 0 0-13.792351 13.978733v308.650434a13.978733 13.978733 0 0 0 13.792351 13.978733z m165.880969 0h27.584701a13.978733 13.978733 0 0 0 13.79235-13.978733V13.989916a13.978733 13.978733 0 0 0-13.79235-13.978733h-27.584701a13.978733 13.978733 0 0 0-13.79235 13.978733v308.650434a13.978733 13.978733 0 0 0 13.79235 13.978733z m138.109886 322.629167h-110.525185a27.771084 27.771084 0 0 0-27.584701 28.14385v111.829867a27.771084 27.771084 0 0 0 27.584701 28.14385h110.525185a27.957467 27.957467 0 0 0 27.584701-28.14385v-111.829867a27.957467 27.957467 0 0 0-27.584701-28.14385z m484.596091-322.629167h27.584701a13.978733 13.978733 0 0 0 13.79235-13.978733V13.989916a13.978733 13.978733 0 0 0-14.537883-13.978733h-27.5847a13.978733 13.978733 0 0 0-13.978734 13.978733v308.650434a13.978733 13.978733 0 0 0 13.978734 13.978733z m-469.871825 0H428.68358a13.978733 13.978733 0 0 0 13.792351-13.978733V13.989916A13.978733 13.978733 0 0 0 428.68358 0.011183h-83.126867a13.978733 13.978733 0 0 0-13.792351 13.978733v308.650434a13.978733 13.978733 0 0 0 13.792351 13.978733z m594.189361 0h69.148134a13.978733 13.978733 0 0 0 13.792351-13.978733V13.989916a13.978733 13.978733 0 0 0-14.537883-13.978733h-69.148135a13.978733 13.978733 0 0 0-13.79235 13.978733v308.650434a13.978733 13.978733 0 0 0 13.79235 13.978733z m-412.279444 126.181367H66.91396A67.470687 67.470687 0 0 0 0.002423 530.830286v425.139878a67.470687 67.470687 0 0 0 66.911537 68.029836h418.802853a67.470687 67.470687 0 0 0 66.911537-68.029836V487.775787a24.788954 24.788954 0 0 0-24.416188-24.975337z m-58.337914 433.899885a42.681733 42.681733 0 0 1-42.495349 43.054498H125.438257a42.681733 42.681733 0 0 1-42.495349-43.054498V590.100115a42.681733 42.681733 0 0 1 42.495349-43.054498h301.940642a42.681733 42.681733 0 0 1 42.495349 43.054498z m525.22761-433.899885a41.749817 41.749817 0 0 0-41.377051 42.122583v55.914934a41.377051 41.377051 0 1 0 82.940485 0v-55.914934a41.749817 41.749817 0 0 0-41.563434-42.122583z m0 223.659734a41.749817 41.749817 0 0 0-41.377051 42.122584V894.65012a45.477479 45.477479 0 0 1-45.291096 45.850246h-159.730327a43.240882 43.240882 0 0 0-43.613649 37.276622A41.9362 41.9362 0 0 0 745.534871 1024h233.538039a57.778765 57.778765 0 0 0 57.405999-58.337914V729.3283a41.749817 41.749817 0 0 0-41.377051-41.9362zM732.488053 322.64035V13.989916a13.978733 13.978733 0 0 0-13.79235-13.978733h-82.940485a13.978733 13.978733 0 0 0-13.79235 13.978733v308.650434a13.978733 13.978733 0 0 0 13.79235 13.978733h82.940485a13.978733 13.978733 0 0 0 13.79235-13.978733zM532.126208 0.011183c-11.36937 0-20.688525 6.337026-20.688526 13.978733v308.650434c0 7.828091 9.319156 13.978733 20.688526 13.978733s20.688525-6.337026 20.688525-13.978733V13.989916c0-7.641708-9.319156-13.978733-20.688525-13.978733z" p-id="6738" fill="#04C361"/><path d="M745.534871 462.80045a41.749817 41.749817 0 0 0-41.377051 42.122583v252.549117a41.377051 41.377051 0 1 0 82.940485 0V504.923033A41.749817 41.749817 0 0 0 745.534871 462.80045" p-id="6739" fill="#04C361"/></svg>
\ No newline at end of file
diff --git a/src/assets/svgs/pay/icon/wx_lite.svg b/src/assets/svgs/pay/icon/wx_lite.svg
deleted file mode 100644
index 0c925cf..0000000
--- a/src/assets/svgs/pay/icon/wx_lite.svg
+++ /dev/null
@@ -1 +0,0 @@
-<?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="1676209433089" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2990" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M608.6 290.3c67.1 0 121.7 50.5 121.7 112.9 0 19.4-5.6 38.4-15.7 55.5-15.3 25-39.8 43.5-69.4 52.3-7.9 2.3-13.9 3.2-19.4 3.2-13 0-23.1-10.2-23.1-23.1 0-13 10.2-23.1 23.1-23.1 0.9 0 2.8 0 5.1-0.9 19.9-5.6 35.6-17.1 44.4-32.4 6-9.7 8.8-20.4 8.8-31.5 0-36.6-33.8-66.6-75-66.6-14.4 0-28.2 3.7-40.7 10.6-21.8 12.5-34.7 33.3-34.7 56v193.9c0 39.3-21.8 75.4-57.9 95.8-19.4 11.1-41.2 16.7-63.4 16.7-67.1 0-121.7-50.5-121.7-112.9 0-19.4 5.6-38.4 15.7-55.5 15.3-25 39.8-43.5 69.4-52.3 8.3-2.3 13.9-3.2 19.4-3.2 13 0 23.1 10.2 23.1 23.1 0 13-10.2 23.1-23.1 23.1-0.9 0-2.8 0-5.1 0.9-19.9 6-35.6 17.6-44.4 32.4-6 9.7-8.8 20.4-8.8 31.5 0 36.6 33.8 66.6 75.4 66.6 14.4 0 28.2-3.7 40.7-10.6 21.8-12.5 34.7-33.3 34.7-56V403.3c0-39.3 21.8-75.4 57.9-95.8 19-11.6 40.7-17.2 63-17.2zM510.8 929c231.1 0 418.4-187.3 418.4-418.4S741.9 92.1 510.8 92.1 92.4 279.5 92.4 510.6 279.7 929 510.8 929z m0 22C267.5 951 70.3 753.8 70.3 510.6S267.5 70.1 510.8 70.1s440.5 197.2 440.5 440.5S754.1 951 510.8 951z" p-id="2991" fill="#58bf6b"></path></svg>
\ No newline at end of file
diff --git a/src/assets/svgs/pay/icon/wx_native.svg b/src/assets/svgs/pay/icon/wx_native.svg
deleted file mode 100644
index bf3ba2b..0000000
--- a/src/assets/svgs/pay/icon/wx_native.svg
+++ /dev/null
@@ -1 +0,0 @@
-<?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="1627279375144" class="icon" viewBox="0 0 1115 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4399" width="43.5546875" height="40" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url(&quot;//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.eot?#iefix&quot;) format(&quot;embedded-opentype&quot;), url(&quot;//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.woff2&quot;) format(&quot;woff2&quot;), url(&quot;//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.woff&quot;) format(&quot;woff&quot;), url(&quot;//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.ttf&quot;) format(&quot;truetype&quot;), url(&quot;//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.svg#iconfont&quot;) format(&quot;svg&quot;); }</style></defs><path d="M751.388 68.267a34.133 34.133 0 0 1 0-68.267h227.556a91.022 91.022 0 0 1 91.022 91.022v227.556a34.133 34.133 0 1 1-68.266 0V91.022a22.756 22.756 0 0 0-22.756-22.755H751.388M1001.7 705.422a34.133 34.133 0 0 1 68.266 0v227.556A91.022 91.022 0 0 1 978.944 1024H748.885a34.133 34.133 0 0 1 0-68.267H978.49a22.756 22.756 0 0 0 22.755-22.755V705.422M364.09 955.733a34.133 34.133 0 1 1 0 68.267H136.533a91.022 91.022 0 0 1-91.022-91.022V705.422a34.133 34.133 0 0 1 68.267 0v227.556a22.756 22.756 0 0 0 22.755 22.755H364.09M113.778 318.578a34.133 34.133 0 1 1-68.267 0V91.022A91.022 91.022 0 0 1 136.533 0H364.09a34.133 34.133 0 0 1 0 68.267H136.533a22.756 22.756 0 0 0-22.755 22.755v227.556M34.133 477.867a34.133 34.133 0 0 0 0 68.266h168.619v-68.266z m1046.756 0H912.27v68.266h168.619a34.133 34.133 0 0 0 0-68.266zM202.752 157.24h709.746v320.627H202.752z m0 388.893h709.746V866.76H202.752z" fill="#04C361" p-id="4400"/></svg>
\ No newline at end of file
diff --git a/src/assets/svgs/pay/icon/wx_pub.svg b/src/assets/svgs/pay/icon/wx_pub.svg
deleted file mode 100644
index 3a6d15b..0000000
--- a/src/assets/svgs/pay/icon/wx_pub.svg
+++ /dev/null
@@ -1,2 +0,0 @@
-<?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="1627279797174" class="icon" viewBox="0 0 1260 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7665" xmlns:xlink="http://www.w3.org/1999/xlink" width="49.21875" height="40"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.eot?#iefix") format("embedded-opentype"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.woff2") format("woff2"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.woff") format("woff"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.ttf") format("truetype"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.svg#iconfont") format("svg"); }
-</style></defs><path d="M797.14798 481.753a269.194 269.194 0 0 0 102.892-211.929C900.03998 120.99 779.02998 0 630.15698 0 481.28298 0 360.27398 120.99 360.27398 269.824c0 85.878 40.33 162.462 102.912 211.929A450.974 450.974 0 0 0 309.84198 582.774c-85.543 85.524-132.608 199.208-132.608 320.236 0 25.01 0 51.712 0.197 76.367a44.898 44.898 0 0 0 44.82 44.623h816.01a44.8 44.8 0 0 0 44.82-44.623V903.01c0-121.009-47.066-234.732-132.609-320.236a451.072 451.072 0 0 0-153.344-101.021z" p-id="7666" fill="#04C361"></path><path d="M1186.18898 580.391A378.644 378.644 0 0 0 1061.81198 473.03a223.783 223.783 0 0 0 64.237-157.657c0-49.742-15.872-96.67-45.746-136.074A225.34 225.34 0 0 0 964.70998 99.9a37.297 37.297 0 0 0-46.14 25.718c-5.592 19.89 5.79 40.724 25.6 46.356 63.114 18.196 107.363 77.135 107.363 143.4a148.913 148.913 0 0 1-81.23 133.06 38.065 38.065 0 0 0-20.363 36.608c1.32 15.203 11.58 28.16 25.975 32.65 125.479 39.601 209.703 155.038 209.703 287.173v63.074c0 20.638 16.62 37.534 37.16 37.711h0.196a37.396 37.396 0 0 0 37.337-37.336V805.06c-0.197-81.644-25.777-159.35-74.142-224.69z m-901.77-62.503a36.982 36.982 0 0 0 25.955-32.65 37.455 37.455 0 0 0-20.362-36.628 148.913 148.913 0 0 1-81.231-133.06c0-66.245 44.071-125.184 107.382-143.4a37.612 37.612 0 0 0 25.58-46.356 37.376 37.376 0 0 0-46.139-25.718 225.32 225.32 0 0 0-115.593 79.4 223.252 223.252 0 0 0-45.746 136.074c0 60.258 23.533 116.381 64.237 157.676A380.475 380.475 0 0 0 74.14498 580.569 373.839 373.839 0 0 0 0.00198 805.258v63.232c0 20.657 16.798 37.356 37.356 37.356h0.197a37.317 37.317 0 0 0 37.14-37.73V805.06c0-132.332 84.401-247.769 209.723-287.173z" p-id="7667" fill="#04C361"></path></svg>
\ No newline at end of file
diff --git a/src/components/AppLinkInput/data.ts b/src/components/AppLinkInput/data.ts
index 1916e08..c9e3678 100644
--- a/src/components/AppLinkInput/data.ts
+++ b/src/components/AppLinkInput/data.ts
@@ -5,6 +5,7 @@
   // 链接列表
   links: AppLink[]
 }
+
 // APP 链接
 export interface AppLink {
   // 链接名称
@@ -21,6 +22,8 @@
   ACTIVITY_COMBINATION,
   // 秒杀活动
   ACTIVITY_SECKILL,
+  // 积分商城活动
+  ACTIVITY_POINT,
   // 文章详情
   ARTICLE_DETAIL,
   // 优惠券详情
@@ -131,6 +134,11 @@
         type: APP_LINK_TYPE_ENUM.ACTIVITY_SECKILL
       },
       {
+        name: '积分商城活动',
+        path: '/pages/activity/point/list',
+        type: APP_LINK_TYPE_ENUM.ACTIVITY_POINT
+      },
+      {
         name: '签到中心',
         path: '/pages/app/sign'
       },
diff --git a/src/components/ContentWrap/src/ContentWrap.vue b/src/components/ContentWrap/src/ContentWrap.vue
index c75e4b7..e603596 100644
--- a/src/components/ContentWrap/src/ContentWrap.vue
+++ b/src/components/ContentWrap/src/ContentWrap.vue
@@ -11,7 +11,7 @@
 defineProps({
   title: propTypes.string.def(''),
   message: propTypes.string.def(''),
-  bodyStyle: propTypes.object.def({ padding: '20px' })
+  bodyStyle: propTypes.object.def({ padding: '10px' })
 })
 </script>
 
diff --git a/src/components/Crontab/src/Crontab.vue b/src/components/Crontab/src/Crontab.vue
index e61fef8..0914bb7 100644
--- a/src/components/Crontab/src/Crontab.vue
+++ b/src/components/Crontab/src/Crontab.vue
@@ -548,10 +548,10 @@
           <el-form>
             <el-form-item label="类型">
               <el-radio-group v-model="cronValue.second.type">
-                <el-radio-button label="0">任意值</el-radio-button>
-                <el-radio-button label="1">范围</el-radio-button>
-                <el-radio-button label="2">间隔</el-radio-button>
-                <el-radio-button label="3">指定</el-radio-button>
+                <el-radio-button value="0">任意值</el-radio-button>
+                <el-radio-button value="1">范围</el-radio-button>
+                <el-radio-button value="2">间隔</el-radio-button>
+                <el-radio-button value="3">指定</el-radio-button>
               </el-radio-group>
             </el-form-item>
             <el-form-item v-if="cronValue.second.type == '1'" label="范围">
@@ -607,10 +607,10 @@
           <el-form>
             <el-form-item label="类型">
               <el-radio-group v-model="cronValue.minute.type">
-                <el-radio-button label="0">任意值</el-radio-button>
-                <el-radio-button label="1">范围</el-radio-button>
-                <el-radio-button label="2">间隔</el-radio-button>
-                <el-radio-button label="3">指定</el-radio-button>
+                <el-radio-button value="0">任意值</el-radio-button>
+                <el-radio-button value="1">范围</el-radio-button>
+                <el-radio-button value="2">间隔</el-radio-button>
+                <el-radio-button value="3">指定</el-radio-button>
               </el-radio-group>
             </el-form-item>
             <el-form-item v-if="cronValue.minute.type == '1'" label="范围">
@@ -666,10 +666,10 @@
           <el-form>
             <el-form-item label="类型">
               <el-radio-group v-model="cronValue.hour.type">
-                <el-radio-button label="0">任意值</el-radio-button>
-                <el-radio-button label="1">范围</el-radio-button>
-                <el-radio-button label="2">间隔</el-radio-button>
-                <el-radio-button label="3">指定</el-radio-button>
+                <el-radio-button value="0">任意值</el-radio-button>
+                <el-radio-button value="1">范围</el-radio-button>
+                <el-radio-button value="2">间隔</el-radio-button>
+                <el-radio-button value="3">指定</el-radio-button>
               </el-radio-group>
             </el-form-item>
             <el-form-item v-if="cronValue.hour.type == '1'" label="范围">
@@ -725,12 +725,12 @@
           <el-form>
             <el-form-item label="类型">
               <el-radio-group v-model="cronValue.day.type">
-                <el-radio-button label="0">任意值</el-radio-button>
-                <el-radio-button label="1">范围</el-radio-button>
-                <el-radio-button label="2">间隔</el-radio-button>
-                <el-radio-button label="3">指定</el-radio-button>
-                <el-radio-button label="4">本月最后一天</el-radio-button>
-                <el-radio-button label="5">不指定</el-radio-button>
+                <el-radio-button value="0">任意值</el-radio-button>
+                <el-radio-button value="1">范围</el-radio-button>
+                <el-radio-button value="2">间隔</el-radio-button>
+                <el-radio-button value="3">指定</el-radio-button>
+                <el-radio-button value="4">本月最后一天</el-radio-button>
+                <el-radio-button value="5">不指定</el-radio-button>
               </el-radio-group>
             </el-form-item>
             <el-form-item v-if="cronValue.day.type == '1'" label="范围">
@@ -786,10 +786,10 @@
           <el-form>
             <el-form-item label="类型">
               <el-radio-group v-model="cronValue.month.type">
-                <el-radio-button label="0">任意值</el-radio-button>
-                <el-radio-button label="1">范围</el-radio-button>
-                <el-radio-button label="2">间隔</el-radio-button>
-                <el-radio-button label="3">指定</el-radio-button>
+                <el-radio-button value="0">任意值</el-radio-button>
+                <el-radio-button value="1">范围</el-radio-button>
+                <el-radio-button value="2">间隔</el-radio-button>
+                <el-radio-button value="3">指定</el-radio-button>
               </el-radio-group>
             </el-form-item>
             <el-form-item v-if="cronValue.month.type == '1'" label="范围">
@@ -846,12 +846,12 @@
             <el-form>
               <el-form-item label="类型">
                 <el-radio-group v-model="cronValue.week.type">
-                  <el-radio-button label="0">任意值</el-radio-button>
-                  <el-radio-button label="1">范围</el-radio-button>
-                  <el-radio-button label="2">间隔</el-radio-button>
-                  <el-radio-button label="3">指定</el-radio-button>
-                  <el-radio-button label="4">本月最后一周</el-radio-button>
-                  <el-radio-button label="5">不指定</el-radio-button>
+                  <el-radio-button value="0">任意值</el-radio-button>
+                  <el-radio-button value="1">范围</el-radio-button>
+                  <el-radio-button value="2">间隔</el-radio-button>
+                  <el-radio-button value="3">指定</el-radio-button>
+                  <el-radio-button value="4">本月最后一周</el-radio-button>
+                  <el-radio-button value="5">不指定</el-radio-button>
                 </el-radio-group>
               </el-form-item>
               <el-form-item v-if="cronValue.week.type == '1'" label="范围">
@@ -925,11 +925,11 @@
           <el-form>
             <el-form-item label="类型">
               <el-radio-group v-model="cronValue.year.type">
-                <el-radio-button label="-1">忽略</el-radio-button>
-                <el-radio-button label="0">任意值</el-radio-button>
-                <el-radio-button label="1">范围</el-radio-button>
-                <el-radio-button label="2">间隔</el-radio-button>
-                <el-radio-button label="3">指定</el-radio-button>
+                <el-radio-button value="-1">忽略</el-radio-button>
+                <el-radio-button value="0">任意值</el-radio-button>
+                <el-radio-button value="1">范围</el-radio-button>
+                <el-radio-button value="2">间隔</el-radio-button>
+                <el-radio-button value="3">指定</el-radio-button>
               </el-radio-group>
             </el-form-item>
             <el-form-item v-if="cronValue.year.type == '1'" label="范围">
diff --git a/src/components/DictTag/src/DictTag.vue b/src/components/DictTag/src/DictTag.vue
index 1d075a1..6414eaa 100644
--- a/src/components/DictTag/src/DictTag.vue
+++ b/src/components/DictTag/src/DictTag.vue
@@ -1,8 +1,9 @@
 <script lang="tsx">
-import { defineComponent, PropType, ref } from 'vue'
+import { computed, defineComponent, PropType } from 'vue'
 import { isHexColor } from '@/utils/color'
 import { ElTag } from 'element-plus'
 import { DictDataType, getDictOptions } from '@/utils/dict'
+import { isArray, isBoolean, isNumber, isString } from '@/utils/is'
 
 export default defineComponent({
   name: 'DictTag',
@@ -12,49 +13,78 @@
       required: true
     },
     value: {
-      type: [String, Number, Boolean] as PropType<string | number | boolean>,
+      type: [String, Number, Boolean, Array],
       required: true
+    },
+    // 字符串分隔符 只有当 props.value 传入值为字符串时有效
+    separator: {
+      type: String as PropType<string>,
+      default: ','
+    },
+    // 每个 tag 之间的间隔,默认为 5px,参考的 el-row 的 gutter
+    gutter: {
+      type: String as PropType<string>,
+      default: '5px'
     }
   },
   setup(props) {
-    const dictData = ref<DictDataType>()
-    const getDictObj = (dictType: string, value: string) => {
-      const dictOptions = getDictOptions(dictType)
-      dictOptions.forEach((dict: DictDataType) => {
-        if (dict.value === value) {
-          if (dict.colorType + '' === 'default') {
-            dict.colorType = 'info'
-          }
-          dictData.value = dict
-        }
-      })
-    }
-    const rederDictTag = () => {
+    const valueArr: any = computed(() => {
+      // 1. 是 Number 类型和 Boolean 类型的情况
+      if (isNumber(props.value) || isBoolean(props.value)) {
+        return [String(props.value)]
+      }
+      // 2. 是字符串(进一步判断是否有包含分隔符号 -> props.sepSymbol )
+      else if (isString(props.value)) {
+        return props.value.split(props.separator)
+      }
+      // 3. 数组
+      else if (isArray(props.value)) {
+        return props.value.map(String)
+      }
+      return []
+    })
+    const renderDictTag = () => {
       if (!props.type) {
         return null
       }
       // 解决自定义字典标签值为零时标签不渲染的问题
-      if (props.value === undefined || props.value === null) {
+      if (props.value === undefined || props.value === null || props.value === '') {
         return null
       }
-      getDictObj(props.type, props.value.toString())
-      // 添加标签的文字颜色为白色,解决自定义背景颜色时标签文字看不清的问题
+      const dictOptions = getDictOptions(props.type)
+
       return (
-        <ElTag
-          style={dictData.value?.cssClass ? 'color: #fff' : ''}
-          type={dictData.value?.colorType}
-          color={
-            dictData.value?.cssClass && isHexColor(dictData.value?.cssClass)
-              ? dictData.value?.cssClass
-              : ''
-          }
-          disableTransitions={true}
+        <div
+          class="dict-tag"
+          style={{
+            display: 'inline-flex',
+            gap: props.gutter,
+            justifyContent: 'center',
+            alignItems: 'center'
+          }}
         >
-          {dictData.value?.label}
-        </ElTag>
+          {dictOptions.map((dict: DictDataType) => {
+            if (valueArr.value.includes(dict.value)) {
+              if (dict.colorType + '' === 'primary' || dict.colorType + '' === 'default') {
+                dict.colorType = ''
+              }
+              return (
+                // 添加标签的文字颜色为白色,解决自定义背景颜色时标签文字看不清的问题
+                <ElTag
+                  style={dict?.cssClass ? 'color: #fff' : ''}
+                  type={dict?.colorType || null}
+                  color={dict?.cssClass && isHexColor(dict?.cssClass) ? dict?.cssClass : ''}
+                  disableTransitions={true}
+                >
+                  {dict?.label}
+                </ElTag>
+              )
+            }
+          })}
+        </div>
       )
     }
-    return () => rederDictTag()
+    return () => renderDictTag()
   }
 })
 </script>
diff --git a/src/components/DiyEditor/components/ComponentContainer.vue b/src/components/DiyEditor/components/ComponentContainer.vue
index 0137278..4856722 100644
--- a/src/components/DiyEditor/components/ComponentContainer.vue
+++ b/src/components/DiyEditor/components/ComponentContainer.vue
@@ -165,6 +165,7 @@
       width: 80px;
       height: 25px;
       font-size: 12px;
+      color: #6a6a6a;
       line-height: 25px;
       text-align: center;
       background: #fff;
diff --git a/src/components/DiyEditor/components/ComponentContainerProperty.vue b/src/components/DiyEditor/components/ComponentContainerProperty.vue
index 9d0750d..25119a5 100644
--- a/src/components/DiyEditor/components/ComponentContainerProperty.vue
+++ b/src/components/DiyEditor/components/ComponentContainerProperty.vue
@@ -11,8 +11,8 @@
         <el-form :model="formData" label-width="80px">
           <el-form-item label="组件背景" prop="bgType">
             <el-radio-group v-model="formData.bgType">
-              <el-radio label="color">纯色</el-radio>
-              <el-radio label="img">图片</el-radio>
+              <el-radio value="color">纯色</el-radio>
+              <el-radio value="img">图片</el-radio>
             </el-radio-group>
           </el-form-item>
           <el-form-item label="选择颜色" prop="bgColor" v-if="formData.bgType === 'color'">
diff --git a/src/components/DiyEditor/components/mobile/Carousel/config.ts b/src/components/DiyEditor/components/mobile/Carousel/config.ts
index 9cee5c2..3e74a51 100644
--- a/src/components/DiyEditor/components/mobile/Carousel/config.ts
+++ b/src/components/DiyEditor/components/mobile/Carousel/config.ts
@@ -38,8 +38,8 @@
     autoplay: false,
     interval: 3,
     items: [
-      { type: 'img', imgUrl: 'https://xxxx/banner-01.jpg', videoUrl: '' },
-      { type: 'img', imgUrl: 'https://xxxx/banner-02.jpg', videoUrl: '' }
+      { type: 'img', imgUrl: 'https://static.iocoder.cn/mall/banner-01.jpg', videoUrl: '' },
+      { type: 'img', imgUrl: 'https://static.iocoder.cn/mall/banner-02.jpg', videoUrl: '' }
     ] as CarouselItemProperty[],
     style: {
       bgType: 'color',
diff --git a/src/components/DiyEditor/components/mobile/Carousel/property.vue b/src/components/DiyEditor/components/mobile/Carousel/property.vue
index c3a5154..e11b032 100644
--- a/src/components/DiyEditor/components/mobile/Carousel/property.vue
+++ b/src/components/DiyEditor/components/mobile/Carousel/property.vue
@@ -5,12 +5,12 @@
         <el-form-item label="样式" prop="type">
           <el-radio-group v-model="formData.type">
             <el-tooltip class="item" content="默认" placement="bottom">
-              <el-radio-button label="default">
+              <el-radio-button value="default">
                 <Icon icon="system-uicons:carousel" />
               </el-radio-button>
             </el-tooltip>
             <el-tooltip class="item" content="卡片" placement="bottom">
-              <el-radio-button label="card">
+              <el-radio-button value="card">
                 <Icon icon="ic:round-view-carousel" />
               </el-radio-button>
             </el-tooltip>
@@ -18,8 +18,8 @@
         </el-form-item>
         <el-form-item label="指示器" prop="indicator">
           <el-radio-group v-model="formData.indicator">
-            <el-radio label="dot">小圆点</el-radio>
-            <el-radio label="number">数字</el-radio>
+            <el-radio value="dot">小圆点</el-radio>
+            <el-radio value="number">数字</el-radio>
           </el-radio-group>
         </el-form-item>
         <el-form-item label="是否轮播" prop="autoplay">
@@ -43,8 +43,8 @@
           <template #default="{ element }">
             <el-form-item label="类型" prop="type" class="m-b-8px!" label-width="40px">
               <el-radio-group v-model="element.type">
-                <el-radio label="img">图片</el-radio>
-                <el-radio label="video">视频</el-radio>
+                <el-radio value="img">图片</el-radio>
+                <el-radio value="video">视频</el-radio>
               </el-radio-group>
             </el-form-item>
             <el-form-item
diff --git a/src/components/DiyEditor/components/mobile/CouponCard/property.vue b/src/components/DiyEditor/components/mobile/CouponCard/property.vue
index 4f32c21..4f69000 100644
--- a/src/components/DiyEditor/components/mobile/CouponCard/property.vue
+++ b/src/components/DiyEditor/components/mobile/CouponCard/property.vue
@@ -26,17 +26,17 @@
         <el-form-item label="列数" prop="type">
           <el-radio-group v-model="formData.columns">
             <el-tooltip class="item" content="一列" placement="bottom">
-              <el-radio-button :label="1">
+              <el-radio-button :value="1">
                 <Icon icon="fluent:text-column-one-24-filled" />
               </el-radio-button>
             </el-tooltip>
             <el-tooltip class="item" content="二列" placement="bottom">
-              <el-radio-button :label="2">
+              <el-radio-button :value="2">
                 <Icon icon="fluent:text-column-two-24-filled" />
               </el-radio-button>
             </el-tooltip>
             <el-tooltip class="item" content="三列" placement="bottom">
-              <el-radio-button :label="3">
+              <el-radio-button :value="3">
                 <Icon icon="fluent:text-column-three-24-filled" />
               </el-radio-button>
             </el-tooltip>
diff --git a/src/components/DiyEditor/components/mobile/Divider/property.vue b/src/components/DiyEditor/components/mobile/Divider/property.vue
index 3d7be26..0c3cb0e 100644
--- a/src/components/DiyEditor/components/mobile/Divider/property.vue
+++ b/src/components/DiyEditor/components/mobile/Divider/property.vue
@@ -11,7 +11,7 @@
           :key="index"
           :content="item.text"
         >
-          <el-radio-button :label="item.type">
+          <el-radio-button :value="item.type">
             <Icon :icon="item.icon" />
           </el-radio-button>
         </el-tooltip>
@@ -24,12 +24,12 @@
       <el-form-item label="左右边距" prop="paddingType">
         <el-radio-group v-model="formData!.paddingType">
           <el-tooltip content="无边距" placement="top">
-            <el-radio-button label="none">
+            <el-radio-button value="none">
               <Icon icon="tabler:box-padding" />
             </el-radio-button>
           </el-tooltip>
           <el-tooltip content="左右留边" placement="top">
-            <el-radio-button label="horizontal">
+            <el-radio-button value="horizontal">
               <Icon icon="vaadin:padding" />
             </el-radio-button>
           </el-tooltip>
diff --git a/src/components/DiyEditor/components/mobile/FloatingActionButton/index.vue b/src/components/DiyEditor/components/mobile/FloatingActionButton/index.vue
index 19e42cb..c2b9926 100644
--- a/src/components/DiyEditor/components/mobile/FloatingActionButton/index.vue
+++ b/src/components/DiyEditor/components/mobile/FloatingActionButton/index.vue
@@ -44,7 +44,7 @@
 defineProps<{ property: FloatingActionButtonProperty }>()
 
 // 是否展开
-const expanded = ref(true)
+const expanded = ref(false)
 // 处理展开/折叠
 const handleToggleFab = () => {
   expanded.value = !expanded.value
diff --git a/src/components/DiyEditor/components/mobile/FloatingActionButton/property.vue b/src/components/DiyEditor/components/mobile/FloatingActionButton/property.vue
index 5db08d0..df459ff 100644
--- a/src/components/DiyEditor/components/mobile/FloatingActionButton/property.vue
+++ b/src/components/DiyEditor/components/mobile/FloatingActionButton/property.vue
@@ -3,8 +3,8 @@
     <el-card header="按钮配置" class="property-group" shadow="never">
       <el-form-item label="展开方向" prop="direction">
         <el-radio-group v-model="formData.direction">
-          <el-radio label="vertical">垂直</el-radio>
-          <el-radio label="horizontal">水平</el-radio>
+          <el-radio value="vertical">垂直</el-radio>
+          <el-radio value="horizontal">水平</el-radio>
         </el-radio-group>
       </el-form-item>
       <el-form-item label="显示文字" prop="showText">
diff --git a/src/components/DiyEditor/components/mobile/MenuGrid/property.vue b/src/components/DiyEditor/components/mobile/MenuGrid/property.vue
index 7940fd0..bb944c9 100644
--- a/src/components/DiyEditor/components/mobile/MenuGrid/property.vue
+++ b/src/components/DiyEditor/components/mobile/MenuGrid/property.vue
@@ -4,8 +4,8 @@
     <el-form label-width="80px" :model="formData" class="m-t-8px">
       <el-form-item label="每行数量" prop="column">
         <el-radio-group v-model="formData.column">
-          <el-radio :label="3">3个</el-radio>
-          <el-radio :label="4">4个</el-radio>
+          <el-radio :value="3">3个</el-radio>
+          <el-radio :value="4">4个</el-radio>
         </el-radio-group>
       </el-form-item>
 
diff --git a/src/components/DiyEditor/components/mobile/MenuSwiper/property.vue b/src/components/DiyEditor/components/mobile/MenuSwiper/property.vue
index 81266bc..fbae83c 100644
--- a/src/components/DiyEditor/components/mobile/MenuSwiper/property.vue
+++ b/src/components/DiyEditor/components/mobile/MenuSwiper/property.vue
@@ -4,21 +4,21 @@
     <el-form label-width="80px" :model="formData" class="m-t-8px">
       <el-form-item label="布局" prop="layout">
         <el-radio-group v-model="formData.layout">
-          <el-radio label="iconText">图标+文字</el-radio>
-          <el-radio label="icon">仅图标</el-radio>
+          <el-radio value="iconText">图标+文字</el-radio>
+          <el-radio value="icon">仅图标</el-radio>
         </el-radio-group>
       </el-form-item>
       <el-form-item label="行数" prop="row">
         <el-radio-group v-model="formData.row">
-          <el-radio :label="1">1行</el-radio>
-          <el-radio :label="2">2行</el-radio>
+          <el-radio :value="1">1行</el-radio>
+          <el-radio :value="2">2行</el-radio>
         </el-radio-group>
       </el-form-item>
       <el-form-item label="列数" prop="column">
         <el-radio-group v-model="formData.column">
-          <el-radio :label="3">3列</el-radio>
-          <el-radio :label="4">4列</el-radio>
-          <el-radio :label="5">5列</el-radio>
+          <el-radio :value="3">3列</el-radio>
+          <el-radio :value="4">4列</el-radio>
+          <el-radio :value="5">5列</el-radio>
         </el-radio-group>
       </el-form-item>
 
diff --git a/src/components/DiyEditor/components/mobile/NavigationBar/components/CellProperty.vue b/src/components/DiyEditor/components/mobile/NavigationBar/components/CellProperty.vue
index edc85f1..2c3bd54 100644
--- a/src/components/DiyEditor/components/mobile/NavigationBar/components/CellProperty.vue
+++ b/src/components/DiyEditor/components/mobile/NavigationBar/components/CellProperty.vue
@@ -14,9 +14,9 @@
     <template v-if="selectedHotAreaIndex === cellIndex">
       <el-form-item label="类型" :prop="`cell[${cellIndex}].type`">
         <el-radio-group v-model="cell.type">
-          <el-radio label="text">文字</el-radio>
-          <el-radio label="image">图片</el-radio>
-          <el-radio label="search">搜索框</el-radio>
+          <el-radio value="text">文字</el-radio>
+          <el-radio value="image">图片</el-radio>
+          <el-radio value="search">搜索框</el-radio>
         </el-radio-group>
       </el-form-item>
       <!-- 1. 文字 -->
diff --git a/src/components/DiyEditor/components/mobile/NavigationBar/property.vue b/src/components/DiyEditor/components/mobile/NavigationBar/property.vue
index b2bc8c1..5b06772 100644
--- a/src/components/DiyEditor/components/mobile/NavigationBar/property.vue
+++ b/src/components/DiyEditor/components/mobile/NavigationBar/property.vue
@@ -2,27 +2,27 @@
   <el-form label-width="80px" :model="formData" :rules="rules">
     <el-form-item label="样式" prop="styleType">
       <el-radio-group v-model="formData!.styleType">
-        <el-radio label="normal">标准</el-radio>
+        <el-radio value="normal">标准</el-radio>
         <el-tooltip
           content="沉侵式头部仅支持微信小程序、APP,建议页面第一个组件为图片展示类组件"
           placement="top"
         >
-          <el-radio label="inner">沉浸式</el-radio>
+          <el-radio value="inner">沉浸式</el-radio>
         </el-tooltip>
       </el-radio-group>
     </el-form-item>
     <el-form-item label="常驻显示" prop="alwaysShow" v-if="formData.styleType === 'inner'">
       <el-radio-group v-model="formData!.alwaysShow">
-        <el-radio :label="false">关闭</el-radio>
+        <el-radio :value="false">关闭</el-radio>
         <el-tooltip content="常驻显示关闭后,头部小组件将在页面滑动时淡入" placement="top">
-          <el-radio :label="true">开启</el-radio>
+          <el-radio :value="true">开启</el-radio>
         </el-tooltip>
       </el-radio-group>
     </el-form-item>
     <el-form-item label="背景类型" prop="bgType">
       <el-radio-group v-model="formData.bgType">
-        <el-radio label="color">纯色</el-radio>
-        <el-radio label="img">图片</el-radio>
+        <el-radio value="color">纯色</el-radio>
+        <el-radio value="img">图片</el-radio>
       </el-radio-group>
     </el-form-item>
     <el-form-item label="背景颜色" prop="bgColor" v-if="formData.bgType === 'color'">
diff --git a/src/components/DiyEditor/components/mobile/NoticeBar/config.ts b/src/components/DiyEditor/components/mobile/NoticeBar/config.ts
index 3a2ef53..b6b0860 100644
--- a/src/components/DiyEditor/components/mobile/NoticeBar/config.ts
+++ b/src/components/DiyEditor/components/mobile/NoticeBar/config.ts
@@ -28,7 +28,7 @@
   name: '公告栏',
   icon: 'ep:bell',
   property: {
-    iconUrl: 'http://xxxx/static/images/xinjian.png',
+    iconUrl: 'http://mall.yudao.iocoder.cn/static/images/xinjian.png',
     contents: [
       {
         text: '',
diff --git a/src/components/DiyEditor/components/mobile/Popover/property.vue b/src/components/DiyEditor/components/mobile/Popover/property.vue
index 6535e3b..2dd4351 100644
--- a/src/components/DiyEditor/components/mobile/Popover/property.vue
+++ b/src/components/DiyEditor/components/mobile/Popover/property.vue
@@ -11,10 +11,10 @@
         <el-form-item label="显示次数" :prop="`list[${index}].showType`">
           <el-radio-group v-model="element.showType">
             <el-tooltip content="只显示一次,下次打开时不显示" placement="bottom">
-              <el-radio label="once">一次</el-radio>
+              <el-radio value="once">一次</el-radio>
             </el-tooltip>
             <el-tooltip content="每次打开时都会显示" placement="bottom">
-              <el-radio label="always">不限</el-radio>
+              <el-radio value="always">不限</el-radio>
             </el-tooltip>
           </el-radio-group>
         </el-form-item>
diff --git a/src/components/DiyEditor/components/mobile/ProductCard/index.vue b/src/components/DiyEditor/components/mobile/ProductCard/index.vue
index f05d4fa..25f8cb8 100644
--- a/src/components/DiyEditor/components/mobile/ProductCard/index.vue
+++ b/src/components/DiyEditor/components/mobile/ProductCard/index.vue
@@ -67,15 +67,15 @@
             class="text-16px"
             :style="{ color: property.fields.price.color }"
           >
-            ¥{{ spu.price }}
+            ¥{{ fenToYuan(spu.price as any) }}
           </span>
           <!-- 市场价 -->
           <span
             v-if="property.fields.marketPrice.show && spu.marketPrice"
             class="ml-4px text-10px line-through"
             :style="{ color: property.fields.marketPrice.color }"
-            >¥{{ spu.marketPrice }}</span
-          >
+            >¥{{ fenToYuan(spu.marketPrice) }}
+          </span>
         </div>
         <div class="text-12px">
           <!-- 销量 -->
@@ -117,6 +117,7 @@
 <script setup lang="ts">
 import { ProductCardProperty } from './config'
 import * as ProductSpuApi from '@/api/mall/product/spu'
+import { fenToYuan } from '../../../../../utils'
 
 /** 商品卡片 */
 defineOptions({ name: 'ProductCard' })
diff --git a/src/components/DiyEditor/components/mobile/ProductCard/property.vue b/src/components/DiyEditor/components/mobile/ProductCard/property.vue
index cfa5008..110c8be 100644
--- a/src/components/DiyEditor/components/mobile/ProductCard/property.vue
+++ b/src/components/DiyEditor/components/mobile/ProductCard/property.vue
@@ -8,17 +8,17 @@
         <el-form-item label="布局" prop="type">
           <el-radio-group v-model="formData.layoutType">
             <el-tooltip class="item" content="单列大图" placement="bottom">
-              <el-radio-button label="oneColBigImg">
+              <el-radio-button value="oneColBigImg">
                 <Icon icon="fluent:text-column-one-24-filled" />
               </el-radio-button>
             </el-tooltip>
             <el-tooltip class="item" content="单列小图" placement="bottom">
-              <el-radio-button label="oneColSmallImg">
+              <el-radio-button value="oneColSmallImg">
                 <Icon icon="fluent:text-column-two-left-24-filled" />
               </el-radio-button>
             </el-tooltip>
             <el-tooltip class="item" content="双列" placement="bottom">
-              <el-radio-button label="twoCol">
+              <el-radio-button value="twoCol">
                 <Icon icon="fluent:text-column-two-24-filled" />
               </el-radio-button>
             </el-tooltip>
@@ -74,8 +74,8 @@
       <el-card header="按钮" class="property-group" shadow="never">
         <el-form-item label="按钮类型" prop="btnBuy.type">
           <el-radio-group v-model="formData.btnBuy.type">
-            <el-radio-button label="text">文字</el-radio-button>
-            <el-radio-button label="img">图片</el-radio-button>
+            <el-radio-button value="text">文字</el-radio-button>
+            <el-radio-button value="img">图片</el-radio-button>
           </el-radio-group>
         </el-form-item>
         <template v-if="formData.btnBuy.type === 'text'">
diff --git a/src/components/DiyEditor/components/mobile/ProductList/index.vue b/src/components/DiyEditor/components/mobile/ProductList/index.vue
index 3ba6367..a51fc07 100644
--- a/src/components/DiyEditor/components/mobile/ProductList/index.vue
+++ b/src/components/DiyEditor/components/mobile/ProductList/index.vue
@@ -54,7 +54,7 @@
               class="text-12px"
               :style="{ color: property.fields.price.color }"
             >
-              ¥{{ spu.price }}
+              ¥{{ fenToYuan(spu.price) }}
             </span>
           </div>
         </div>
@@ -65,6 +65,7 @@
 <script setup lang="ts">
 import { ProductListProperty } from './config'
 import * as ProductSpuApi from '@/api/mall/product/spu'
+import { fenToYuan } from '@/utils'
 
 /** 商品栏 */
 defineOptions({ name: 'ProductList' })
diff --git a/src/components/DiyEditor/components/mobile/ProductList/property.vue b/src/components/DiyEditor/components/mobile/ProductList/property.vue
index e9cf7c0..894687c 100644
--- a/src/components/DiyEditor/components/mobile/ProductList/property.vue
+++ b/src/components/DiyEditor/components/mobile/ProductList/property.vue
@@ -8,17 +8,17 @@
         <el-form-item label="布局" prop="type">
           <el-radio-group v-model="formData.layoutType">
             <el-tooltip class="item" content="双列" placement="bottom">
-              <el-radio-button label="twoCol">
+              <el-radio-button value="twoCol">
                 <Icon icon="fluent:text-column-two-24-filled" />
               </el-radio-button>
             </el-tooltip>
             <el-tooltip class="item" content="三列" placement="bottom">
-              <el-radio-button label="threeCol">
+              <el-radio-button value="threeCol">
                 <Icon icon="fluent:text-column-three-24-filled" />
               </el-radio-button>
             </el-tooltip>
             <el-tooltip class="item" content="水平滑动" placement="bottom">
-              <el-radio-button label="horizSwiper">
+              <el-radio-button value="horizSwiper">
                 <Icon icon="system-uicons:carousel" />
               </el-radio-button>
             </el-tooltip>
diff --git a/src/components/DiyEditor/components/mobile/PromotionCombination/config.ts b/src/components/DiyEditor/components/mobile/PromotionCombination/config.ts
index 0c7e9ff..f4fdf6e 100644
--- a/src/components/DiyEditor/components/mobile/PromotionCombination/config.ts
+++ b/src/components/DiyEditor/components/mobile/PromotionCombination/config.ts
@@ -3,19 +3,40 @@
 /** 拼团属性 */
 export interface PromotionCombinationProperty {
   // 布局类型:单列 | 三列
-  layoutType: 'oneCol' | 'threeCol'
+  layoutType: 'oneColBigImg' | 'oneColSmallImg' | 'twoCol'
   // 商品字段
   fields: {
     // 商品名称
     name: PromotionCombinationFieldProperty
+    // 商品简介
+    introduction: PromotionCombinationFieldProperty
     // 商品价格
     price: PromotionCombinationFieldProperty
+    // 市场价
+    marketPrice: PromotionCombinationFieldProperty
+    // 商品销量
+    salesCount: PromotionCombinationFieldProperty
+    // 商品库存
+    stock: PromotionCombinationFieldProperty
   }
   // 角标
   badge: {
     // 是否显示
     show: boolean
     // 角标图片
+    imgUrl: string
+  }
+  // 按钮
+  btnBuy: {
+    // 类型:文字 | 图片
+    type: 'text' | 'img'
+    // 文字
+    text: string
+    // 文字按钮:背景渐变起始颜色
+    bgBeginColor: string
+    // 文字按钮:背景渐变结束颜色
+    bgEndColor: string
+    // 图片按钮:图片地址
     imgUrl: string
   }
   // 上圆角
@@ -25,7 +46,7 @@
   // 间距
   space: number
   // 拼团活动编号
-  activityId: number
+  activityIds: number[]
   // 组件样式
   style: ComponentStyle
 }
@@ -44,12 +65,23 @@
   name: '拼团',
   icon: 'mdi:account-group',
   property: {
-    layoutType: 'oneCol',
+    layoutType: 'oneColBigImg',
     fields: {
       name: { show: true, color: '#000' },
-      price: { show: true, color: '#ff3000' }
+      introduction: { show: true, color: '#999' },
+      price: { show: true, color: '#ff3000' },
+      marketPrice: { show: true, color: '#c4c4c4' },
+      salesCount: { show: true, color: '#c4c4c4' },
+      stock: { show: false, color: '#c4c4c4' }
     },
     badge: { show: false, imgUrl: '' },
+    btnBuy: {
+      type: 'text',
+      text: '去拼团',
+      bgBeginColor: '#FF6000',
+      bgEndColor: '#FE832A',
+      imgUrl: ''
+    },
     borderRadiusTop: 8,
     borderRadiusBottom: 8,
     space: 8,
diff --git a/src/components/DiyEditor/components/mobile/PromotionCombination/index.vue b/src/components/DiyEditor/components/mobile/PromotionCombination/index.vue
index fe6f3a8..d41bf1c 100644
--- a/src/components/DiyEditor/components/mobile/PromotionCombination/index.vue
+++ b/src/components/DiyEditor/components/mobile/PromotionCombination/index.vue
@@ -1,125 +1,201 @@
 <template>
-  <el-scrollbar class="z-1 min-h-30px" wrap-class="w-full" ref="containerRef">
-    <!-- 商品网格 -->
+  <div :class="`box-content min-h-30px w-full flex flex-row flex-wrap`" ref="containerRef">
     <div
-      class="grid overflow-x-auto"
+      class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
       :style="{
-        gridGap: `${property.space}px`,
-        gridTemplateColumns,
-        width: scrollbarWidth
+        ...calculateSpace(index),
+        ...calculateWidth(),
+        borderTopLeftRadius: `${property.borderRadiusTop}px`,
+        borderTopRightRadius: `${property.borderRadiusTop}px`,
+        borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
+        borderBottomRightRadius: `${property.borderRadiusBottom}px`
       }"
+      v-for="(spu, index) in spuList"
+      :key="index"
     >
-      <!-- 商品 -->
+      <!-- 角标 -->
+      <div v-if="property.badge.show" class="absolute left-0 top-0 z-1 items-center justify-center">
+        <el-image fit="cover" :src="property.badge.imgUrl" class="h-26px w-38px" />
+      </div>
+      <!-- 商品封面图 -->
       <div
-        class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
-        :style="{
-          borderTopLeftRadius: `${property.borderRadiusTop}px`,
-          borderTopRightRadius: `${property.borderRadiusTop}px`,
-          borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
-          borderBottomRightRadius: `${property.borderRadiusBottom}px`
-        }"
-        v-for="(spu, index) in spuList"
-        :key="index"
+        :class="[
+          'h-140px',
+          {
+            'w-full': property.layoutType !== 'oneColSmallImg',
+            'w-140px': property.layoutType === 'oneColSmallImg'
+          }
+        ]"
       >
-        <!-- 角标 -->
+        <el-image fit="cover" class="h-full w-full" :src="spu.picUrl" />
+      </div>
+      <div
+        :class="[
+          ' flex flex-col gap-8px p-8px box-border',
+          {
+            'w-full': property.layoutType !== 'oneColSmallImg',
+            'w-[calc(100%-140px-16px)]': property.layoutType === 'oneColSmallImg'
+          }
+        ]"
+      >
+        <!-- 商品名称 -->
         <div
-          v-if="property.badge.show"
-          class="absolute left-0 top-0 z-1 items-center justify-center"
-        >
-          <el-image fit="cover" :src="property.badge.imgUrl" class="h-26px w-38px" />
-        </div>
-        <!-- 商品封面图 -->
-        <el-image fit="cover" :src="spu.picUrl" :style="{ width: imageSize, height: imageSize }" />
-        <div
+          v-if="property.fields.name.show"
           :class="[
-            'flex flex-col gap-8px p-8px box-border',
+            'text-14px ',
             {
-              'w-[calc(100%-64px)]': columns === 2,
-              'w-full': columns === 3
+              truncate: property.layoutType !== 'oneColSmallImg',
+              'overflow-ellipsis line-clamp-2': property.layoutType === 'oneColSmallImg'
             }
           ]"
+          :style="{ color: property.fields.name.color }"
         >
-          <!-- 商品名称 -->
-          <div
-            v-if="property.fields.name.show"
-            class="truncate text-12px"
-            :style="{ color: property.fields.name.color }"
+          {{ spu.name }}
+        </div>
+        <!-- 商品简介 -->
+        <div
+          v-if="property.fields.introduction.show"
+          class="truncate text-12px"
+          :style="{ color: property.fields.introduction.color }"
+        >
+          {{ spu.introduction }}
+        </div>
+        <div>
+          <!-- 价格 -->
+          <span
+            v-if="property.fields.price.show"
+            class="text-16px"
+            :style="{ color: property.fields.price.color }"
           >
-            {{ spu.name }}
-          </div>
-          <div>
-            <!-- 商品价格 -->
-            <span
-              v-if="property.fields.price.show"
-              class="text-12px"
-              :style="{ color: property.fields.price.color }"
-            >
-              ¥{{ spu.price }}
-            </span>
-          </div>
+            ¥{{ fenToYuan(spu.price || Infinity) }}
+          </span>
+          <!-- 市场价 -->
+          <span
+            v-if="property.fields.marketPrice.show && spu.marketPrice"
+            class="ml-4px text-10px line-through"
+            :style="{ color: property.fields.marketPrice.color }"
+            >¥{{ fenToYuan(spu.marketPrice) }}</span
+          >
+        </div>
+        <div class="text-12px">
+          <!-- 销量 -->
+          <span
+            v-if="property.fields.salesCount.show"
+            :style="{ color: property.fields.salesCount.color }"
+          >
+            已售{{ (spu.salesCount || 0) + (spu.virtualSalesCount || 0) }}件
+          </span>
+          <!-- 库存 -->
+          <span v-if="property.fields.stock.show" :style="{ color: property.fields.stock.color }">
+            库存{{ spu.stock || 0 }}
+          </span>
         </div>
       </div>
+      <!-- 购买按钮 -->
+      <div class="absolute bottom-8px right-8px">
+        <!-- 文字按钮 -->
+        <span
+          v-if="property.btnBuy.type === 'text'"
+          class="rounded-full p-x-12px p-y-4px text-12px text-white"
+          :style="{
+            background: `linear-gradient(to right, ${property.btnBuy.bgBeginColor}, ${property.btnBuy.bgEndColor}`
+          }"
+        >
+          {{ property.btnBuy.text }}
+        </span>
+        <!-- 图片按钮 -->
+        <el-image
+          v-else
+          class="h-28px w-28px rounded-full"
+          fit="cover"
+          :src="property.btnBuy.imgUrl"
+        />
+      </div>
     </div>
-  </el-scrollbar>
+  </div>
 </template>
 <script setup lang="ts">
 import { PromotionCombinationProperty } from './config'
 import * as ProductSpuApi from '@/api/mall/product/spu'
 import * as CombinationActivityApi from '@/api/mall/promotion/combination/combinationActivity'
+import { fenToYuan } from '@/utils'
 
-/** 拼团 */
+/** 拼团卡片 */
 defineOptions({ name: 'PromotionCombination' })
 // 定义属性
 const props = defineProps<{ property: PromotionCombinationProperty }>()
 // 商品列表
 const spuList = ref<ProductSpuApi.Spu[]>([])
+const spuIdList = ref<number[]>([])
+const combinationActivityList = ref<CombinationActivityApi.CombinationActivityVO[]>([])
+
 watch(
-  () => props.property.activityId,
+  () => props.property.activityIds,
   async () => {
-    if (!props.property.activityId) return
-    const activity = await CombinationActivityApi.getCombinationActivity(props.property.activityId)
-    if (!activity?.spuId) return
-    spuList.value = [await ProductSpuApi.getSpu(activity.spuId)]
+    try {
+      // 新添加的拼团组件,是没有活动ID的
+      const activityIds = props.property.activityIds
+      // 检查活动ID的有效性
+      if (Array.isArray(activityIds) && activityIds.length > 0) {
+        // 获取拼团活动详情列表
+        combinationActivityList.value =
+          await CombinationActivityApi.getCombinationActivityListByIds(activityIds)
+
+        // 获取拼团活动的 SPU 详情列表
+        spuList.value = []
+        spuIdList.value = combinationActivityList.value
+          .map((activity) => activity.spuId)
+          .filter((spuId): spuId is number => typeof spuId === 'number')
+        if (spuIdList.value.length > 0) {
+          spuList.value = await ProductSpuApi.getSpuDetailList(spuIdList.value)
+        }
+
+        // 更新 SPU 的最低价格
+        combinationActivityList.value.forEach((activity) => {
+          // 匹配spuId
+          const spu = spuList.value.find((spu) => spu.id === activity.spuId)
+          if (spu) {
+            // 赋值活动价格,哪个最便宜就赋值哪个
+            spu.price = Math.min(activity.combinationPrice || Infinity, spu.price || Infinity)
+          }
+        })
+      }
+    } catch (error) {
+      console.error('获取拼团活动细节或 SPU 细节时出错:', error)
+    }
   },
   {
     immediate: true,
     deep: true
   }
 )
-// 手机宽度
-const phoneWidth = ref(375)
+
+/**
+ * 计算商品的间距
+ * @param index 商品索引
+ */
+const calculateSpace = (index: number) => {
+  // 商品的列数
+  const columns = props.property.layoutType === 'twoCol' ? 2 : 1
+  // 第一列没有左边距
+  const marginLeft = index % columns === 0 ? '0' : props.property.space + 'px'
+  // 第一行没有上边距
+  const marginTop = index < columns ? '0' : props.property.space + 'px'
+
+  return { marginLeft, marginTop }
+}
+
 // 容器
 const containerRef = ref()
-// 商品的列数
-const columns = ref(2)
-// 滚动条宽度
-const scrollbarWidth = ref('100%')
-// 商品图大小
-const imageSize = ref('0')
-// 商品网络列数
-const gridTemplateColumns = ref('')
-// 计算布局参数
-watch(
-  () => [props.property, phoneWidth, spuList.value.length],
-  () => {
-    // 计算列数
-    columns.value = props.property.layoutType === 'oneCol' ? 1 : 3
-    // 每列的宽度为:(总宽度 - 间距 * (列数 - 1))/ 列数
-    const productWidth =
-      (phoneWidth.value - props.property.space * (columns.value - 1)) / columns.value
-    // 商品图布局:2列时,左右布局 3列时,上下布局
-    imageSize.value = columns.value === 2 ? '64px' : `${productWidth}px`
-    // 指定列数
-    gridTemplateColumns.value = `repeat(${columns.value}, auto)`
-    // 不滚动
-    scrollbarWidth.value = '100%'
-  },
-  { immediate: true, deep: true }
-)
-onMounted(() => {
-  // 提取手机宽度
-  phoneWidth.value = containerRef.value?.wrapRef?.offsetWidth || 375
-})
+// 计算商品的宽度
+const calculateWidth = () => {
+  let width = '100%'
+  // 双列时每列的宽度为:(总宽度 - 间距)/ 2
+  if (props.property.layoutType === 'twoCol') {
+    width = `${(containerRef.value.offsetWidth - props.property.space) / 2}px`
+  }
+  return { width }
+}
 </script>
 
 <style scoped lang="scss"></style>
diff --git a/src/components/DiyEditor/components/mobile/PromotionCombination/property.vue b/src/components/DiyEditor/components/mobile/PromotionCombination/property.vue
index ec09dc4..ea901a0 100644
--- a/src/components/DiyEditor/components/mobile/PromotionCombination/property.vue
+++ b/src/components/DiyEditor/components/mobile/PromotionCombination/property.vue
@@ -2,30 +2,31 @@
   <ComponentContainerProperty v-model="formData.style">
     <el-form label-width="80px" :model="formData">
       <el-card header="拼团活动" class="property-group" shadow="never">
-        <el-form-item label="拼团活动" prop="activityId">
-          <el-select v-model="formData.activityId">
-            <el-option
-              v-for="activity in activityList"
-              :key="activity.id"
-              :label="activity.name"
-              :value="activity.id"
-            />
-          </el-select>
-        </el-form-item>
+        <CombinationShowcase v-model="formData.activityIds" />
       </el-card>
       <el-card header="商品样式" class="property-group" shadow="never">
         <el-form-item label="布局" prop="type">
           <el-radio-group v-model="formData.layoutType">
-            <el-tooltip class="item" content="单列" placement="bottom">
-              <el-radio-button label="oneCol">
+            <el-tooltip class="item" content="单列大图" placement="bottom">
+              <el-radio-button value="oneColBigImg">
                 <Icon icon="fluent:text-column-one-24-filled" />
               </el-radio-button>
             </el-tooltip>
-            <el-tooltip class="item" content="三列" placement="bottom">
-              <el-radio-button label="threeCol">
-                <Icon icon="fluent:text-column-three-24-filled" />
+            <el-tooltip class="item" content="单列小图" placement="bottom">
+              <el-radio-button value="oneColSmallImg">
+                <Icon icon="fluent:text-column-two-left-24-filled" />
               </el-radio-button>
             </el-tooltip>
+            <el-tooltip class="item" content="双列" placement="bottom">
+              <el-radio-button value="twoCol">
+                <Icon icon="fluent:text-column-two-24-filled" />
+              </el-radio-button>
+            </el-tooltip>
+            <!--<el-tooltip class="item" content="三列" placement="bottom">
+              <el-radio-button value="threeCol">
+                <Icon icon="fluent:text-column-three-24-filled" />
+              </el-radio-button>
+            </el-tooltip>-->
           </el-radio-group>
         </el-form-item>
         <el-form-item label="商品名称" prop="fields.name.show">
@@ -34,10 +35,34 @@
             <el-checkbox v-model="formData.fields.name.show" />
           </div>
         </el-form-item>
+        <el-form-item label="商品简介" prop="fields.introduction.show">
+          <div class="flex gap-8px">
+            <ColorInput v-model="formData.fields.introduction.color" />
+            <el-checkbox v-model="formData.fields.introduction.show" />
+          </div>
+        </el-form-item>
         <el-form-item label="商品价格" prop="fields.price.show">
           <div class="flex gap-8px">
             <ColorInput v-model="formData.fields.price.color" />
             <el-checkbox v-model="formData.fields.price.show" />
+          </div>
+        </el-form-item>
+        <el-form-item label="市场价" prop="fields.marketPrice.show">
+          <div class="flex gap-8px">
+            <ColorInput v-model="formData.fields.marketPrice.color" />
+            <el-checkbox v-model="formData.fields.marketPrice.show" />
+          </div>
+        </el-form-item>
+        <el-form-item label="商品销量" prop="fields.salesCount.show">
+          <div class="flex gap-8px">
+            <ColorInput v-model="formData.fields.salesCount.color" />
+            <el-checkbox v-model="formData.fields.salesCount.show" />
+          </div>
+        </el-form-item>
+        <el-form-item label="商品库存" prop="fields.stock.show">
+          <div class="flex gap-8px">
+            <ColorInput v-model="formData.fields.stock.color" />
+            <el-checkbox v-model="formData.fields.stock.show" />
           </div>
         </el-form-item>
       </el-card>
@@ -47,9 +72,35 @@
         </el-form-item>
         <el-form-item label="角标" prop="badge.imgUrl" v-if="formData.badge.show">
           <UploadImg v-model="formData.badge.imgUrl" height="44px" width="72px">
-            <template #tip> 建议尺寸:36 * 22 </template>
+            <template #tip> 建议尺寸:36 * 22</template>
           </UploadImg>
         </el-form-item>
+      </el-card>
+      <el-card header="按钮" class="property-group" shadow="never">
+        <el-form-item label="按钮类型" prop="btnBuy.type">
+          <el-radio-group v-model="formData.btnBuy.type">
+            <el-radio-button value="text">文字</el-radio-button>
+            <el-radio-button value="img">图片</el-radio-button>
+          </el-radio-group>
+        </el-form-item>
+        <template v-if="formData.btnBuy.type === 'text'">
+          <el-form-item label="按钮文字" prop="btnBuy.text">
+            <el-input v-model="formData.btnBuy.text" />
+          </el-form-item>
+          <el-form-item label="左侧背景" prop="btnBuy.bgBeginColor">
+            <ColorInput v-model="formData.btnBuy.bgBeginColor" />
+          </el-form-item>
+          <el-form-item label="右侧背景" prop="btnBuy.bgEndColor">
+            <ColorInput v-model="formData.btnBuy.bgEndColor" />
+          </el-form-item>
+        </template>
+        <template v-else>
+          <el-form-item label="图片" prop="btnBuy.imgUrl">
+            <UploadImg v-model="formData.btnBuy.imgUrl" height="56px" width="56px">
+              <template #tip> 建议尺寸:56 * 56</template>
+            </UploadImg>
+          </el-form-item>
+        </template>
       </el-card>
       <el-card header="商品样式" class="property-group" shadow="never">
         <el-form-item label="上圆角" prop="borderRadiusTop">
@@ -92,6 +143,7 @@
 import { usePropertyForm } from '@/components/DiyEditor/util'
 import * as CombinationActivityApi from '@/api/mall/promotion/combination/combinationActivity'
 import { CommonStatusEnum } from '@/utils/constants'
+import CombinationShowcase from '@/views/mall/promotion/combination/components/CombinationShowcase.vue'
 
 // 拼团属性面板
 defineOptions({ name: 'PromotionCombinationProperty' })
@@ -100,7 +152,7 @@
 const emit = defineEmits(['update:modelValue'])
 const { formData } = usePropertyForm(props.modelValue, emit)
 // 活动列表
-const activityList = ref<CombinationActivityApi.CombinationActivityVO>([])
+const activityList = ref<CombinationActivityApi.CombinationActivityVO[]>([])
 onMounted(async () => {
   const { list } = await CombinationActivityApi.getCombinationActivityPage({
     status: CommonStatusEnum.ENABLE
diff --git a/src/components/DiyEditor/components/mobile/PromotionPoint/config.ts b/src/components/DiyEditor/components/mobile/PromotionPoint/config.ts
new file mode 100644
index 0000000..75aa0ff
--- /dev/null
+++ b/src/components/DiyEditor/components/mobile/PromotionPoint/config.ts
@@ -0,0 +1,96 @@
+import {ComponentStyle, DiyComponent} from '@/components/DiyEditor/util'
+
+/** 积分商城属性 */
+export interface PromotionPointProperty {
+  // 布局类型:单列 | 三列
+  layoutType: 'oneColBigImg' | 'oneColSmallImg' | 'twoCol'
+  // 商品字段
+  fields: {
+    // 商品名称
+    name: PromotionPointFieldProperty
+    // 商品简介
+    introduction: PromotionPointFieldProperty
+    // 商品价格
+    price: PromotionPointFieldProperty
+    // 市场价
+    marketPrice: PromotionPointFieldProperty
+    // 商品销量
+    salesCount: PromotionPointFieldProperty
+    // 商品库存
+    stock: PromotionPointFieldProperty
+  }
+  // 角标
+  badge: {
+    // 是否显示
+    show: boolean
+    // 角标图片
+    imgUrl: string
+  }
+  // 按钮
+  btnBuy: {
+    // 类型:文字 | 图片
+    type: 'text' | 'img'
+    // 文字
+    text: string
+    // 文字按钮:背景渐变起始颜色
+    bgBeginColor: string
+    // 文字按钮:背景渐变结束颜色
+    bgEndColor: string
+    // 图片按钮:图片地址
+    imgUrl: string
+  }
+  // 上圆角
+  borderRadiusTop: number
+  // 下圆角
+  borderRadiusBottom: number
+  // 间距
+  space: number
+  // 秒杀活动编号
+  activityIds: number[]
+  // 组件样式
+  style: ComponentStyle
+}
+
+// 商品字段
+export interface PromotionPointFieldProperty {
+  // 是否显示
+  show: boolean
+  // 颜色
+  color: string
+}
+
+// 定义组件
+export const component = {
+  id: 'PromotionPoint',
+  name: '积分商城',
+  icon: 'ep:present',
+  property: {
+    layoutType: 'oneColBigImg',
+    fields: {
+      name: { show: true, color: '#000' },
+      introduction: { show: true, color: '#999' },
+      price: { show: true, color: '#ff3000' },
+      marketPrice: { show: true, color: '#c4c4c4' },
+      salesCount: { show: true, color: '#c4c4c4' },
+      stock: { show: false, color: '#c4c4c4' }
+    },
+    badge: { show: false, imgUrl: '' },
+    btnBuy: {
+      type: 'text',
+      text: '立即兑换',
+      bgBeginColor: '#FF6000',
+      bgEndColor: '#FE832A',
+      imgUrl: ''
+    },
+    borderRadiusTop: 8,
+    borderRadiusBottom: 8,
+    space: 8,
+    style: {
+      bgType: 'color',
+      bgColor: '',
+      marginLeft: 8,
+      marginRight: 8,
+      marginBottom: 8
+    } as ComponentStyle
+  }
+} as DiyComponent<PromotionPointProperty>
diff --git a/src/components/DiyEditor/components/mobile/PromotionPoint/index.vue b/src/components/DiyEditor/components/mobile/PromotionPoint/index.vue
new file mode 100644
index 0000000..4acd93f
--- /dev/null
+++ b/src/components/DiyEditor/components/mobile/PromotionPoint/index.vue
@@ -0,0 +1,202 @@
+<template>
+  <div ref="containerRef" :class="`box-content min-h-30px w-full flex flex-row flex-wrap`">
+    <div
+      v-for="(spu, index) in spuList"
+      :key="index"
+      :style="{
+        ...calculateSpace(index),
+        ...calculateWidth(),
+        borderTopLeftRadius: `${property.borderRadiusTop}px`,
+        borderTopRightRadius: `${property.borderRadiusTop}px`,
+        borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
+        borderBottomRightRadius: `${property.borderRadiusBottom}px`
+      }"
+      class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
+    >
+      <!-- 角标 -->
+      <div v-if="property.badge.show" class="absolute left-0 top-0 z-1 items-center justify-center">
+        <el-image :src="property.badge.imgUrl" class="h-26px w-38px" fit="cover" />
+      </div>
+      <!-- 商品封面图 -->
+      <div
+        :class="[
+          'h-140px',
+          {
+            'w-full': property.layoutType !== 'oneColSmallImg',
+            'w-140px': property.layoutType === 'oneColSmallImg'
+          }
+        ]"
+      >
+        <el-image :src="spu.picUrl" class="h-full w-full" fit="cover" />
+      </div>
+      <div
+        :class="[
+          ' flex flex-col gap-8px p-8px box-border',
+          {
+            'w-full': property.layoutType !== 'oneColSmallImg',
+            'w-[calc(100%-140px-16px)]': property.layoutType === 'oneColSmallImg'
+          }
+        ]"
+      >
+        <!-- 商品名称 -->
+        <div
+          v-if="property.fields.name.show"
+          :class="[
+            'text-14px ',
+            {
+              truncate: property.layoutType !== 'oneColSmallImg',
+              'overflow-ellipsis line-clamp-2': property.layoutType === 'oneColSmallImg'
+            }
+          ]"
+          :style="{ color: property.fields.name.color }"
+        >
+          {{ spu.name }}
+        </div>
+        <!-- 商品简介 -->
+        <div
+          v-if="property.fields.introduction.show"
+          :style="{ color: property.fields.introduction.color }"
+          class="truncate text-12px"
+        >
+          {{ spu.introduction }}
+        </div>
+        <div>
+          <!-- 积分 -->
+          <span
+            v-if="property.fields.price.show"
+            :style="{ color: property.fields.price.color }"
+            class="text-16px"
+          >
+            {{ spu.point }}积分
+            {{ !spu.pointPrice || spu.pointPrice === 0 ? '' : `+${fenToYuan(spu.pointPrice)}元` }}
+          </span>
+          <!-- 市场价 -->
+          <span
+            v-if="property.fields.marketPrice.show && spu.marketPrice"
+            :style="{ color: property.fields.marketPrice.color }"
+            class="ml-4px text-10px line-through"
+          >
+            ¥{{ fenToYuan(spu.marketPrice) }}
+          </span>
+        </div>
+        <div class="text-12px">
+          <!-- 销量 -->
+          <span
+            v-if="property.fields.salesCount.show"
+            :style="{ color: property.fields.salesCount.color }"
+          >
+            已兑{{ (spu.pointTotalStock || 0) - (spu.pointStock || 0) }}件
+          </span>
+          <!-- 库存 -->
+          <span v-if="property.fields.stock.show" :style="{ color: property.fields.stock.color }">
+            库存{{ spu.pointTotalStock || 0 }}
+          </span>
+        </div>
+      </div>
+      <!-- 购买按钮 -->
+      <div class="absolute bottom-8px right-8px">
+        <!-- 文字按钮 -->
+        <span
+          v-if="property.btnBuy.type === 'text'"
+          :style="{
+            background: `linear-gradient(to right, ${property.btnBuy.bgBeginColor}, ${property.btnBuy.bgEndColor}`
+          }"
+          class="rounded-full p-x-12px p-y-4px text-12px text-white"
+        >
+          {{ property.btnBuy.text }}
+        </span>
+        <!-- 图片按钮 -->
+        <el-image
+          v-else
+          :src="property.btnBuy.imgUrl"
+          class="h-28px w-28px rounded-full"
+          fit="cover"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+<script lang="ts" setup>
+import { PromotionPointProperty } from './config'
+import * as ProductSpuApi from '@/api/mall/product/spu'
+import { PointActivityApi, PointActivityVO, SpuExtension0 } from '@/api/mall/promotion/point'
+import { fenToYuan } from '@/utils'
+
+/** 积分商城卡片 */
+defineOptions({ name: 'PromotionPoint' })
+// 定义属性
+const props = defineProps<{ property: PromotionPointProperty }>()
+// 商品列表
+const spuList = ref<SpuExtension0[]>([])
+const spuIdList = ref<number[]>([])
+const pointActivityList = ref<PointActivityVO[]>([])
+
+watch(
+  () => props.property.activityIds,
+  async () => {
+    try {
+      // 新添加的积分商城组件,是没有活动ID的
+      const activityIds = props.property.activityIds
+      // 检查活动ID的有效性
+      if (Array.isArray(activityIds) && activityIds.length > 0) {
+        // 获取积分商城活动详情列表
+        pointActivityList.value = await PointActivityApi.getPointActivityListByIds(activityIds)
+
+        // 获取积分商城活动的 SPU 详情列表
+        spuList.value = []
+        spuIdList.value = pointActivityList.value.map((activity) => activity.spuId)
+        if (spuIdList.value.length > 0) {
+          spuList.value = await ProductSpuApi.getSpuDetailList(spuIdList.value)
+        }
+
+        // 更新 SPU 的最低兑换积分和所需兑换金额
+        pointActivityList.value.forEach((activity) => {
+          // 匹配spuId
+          const spu = spuList.value.find((spu) => spu.id === activity.spuId)
+          if (spu) {
+            spu.pointStock = activity.stock
+            spu.pointTotalStock = activity.totalStock
+            spu.point = activity.point
+            spu.pointPrice = activity.price
+          }
+        })
+      }
+    } catch (error) {
+      console.error('获取积分商城活动细节或 SPU 细节时出错:', error)
+    }
+  },
+  {
+    immediate: true,
+    deep: true
+  }
+)
+
+/**
+ * 计算商品的间距
+ * @param index 商品索引
+ */
+const calculateSpace = (index: number) => {
+  // 商品的列数
+  const columns = props.property.layoutType === 'twoCol' ? 2 : 1
+  // 第一列没有左边距
+  const marginLeft = index % columns === 0 ? '0' : props.property.space + 'px'
+  // 第一行没有上边距
+  const marginTop = index < columns ? '0' : props.property.space + 'px'
+
+  return { marginLeft, marginTop }
+}
+
+// 容器
+const containerRef = ref()
+// 计算商品的宽度
+const calculateWidth = () => {
+  let width = '100%'
+  // 双列时每列的宽度为:(总宽度 - 间距)/ 2
+  if (props.property.layoutType === 'twoCol') {
+    width = `${(containerRef.value.offsetWidth - props.property.space) / 2}px`
+  }
+  return { width }
+}
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/components/DiyEditor/components/mobile/PromotionPoint/property.vue b/src/components/DiyEditor/components/mobile/PromotionPoint/property.vue
new file mode 100644
index 0000000..84a429b
--- /dev/null
+++ b/src/components/DiyEditor/components/mobile/PromotionPoint/property.vue
@@ -0,0 +1,154 @@
+<template>
+  <ComponentContainerProperty v-model="formData.style">
+    <el-form :model="formData" label-width="80px">
+      <el-card class="property-group" header="积分商城活动" shadow="never">
+        <PointShowcase v-model="formData.activityIds" />
+      </el-card>
+      <el-card class="property-group" header="商品样式" shadow="never">
+        <el-form-item label="布局" prop="type">
+          <el-radio-group v-model="formData.layoutType">
+            <el-tooltip class="item" content="单列大图" placement="bottom">
+              <el-radio-button value="oneColBigImg">
+                <Icon icon="fluent:text-column-one-24-filled" />
+              </el-radio-button>
+            </el-tooltip>
+            <el-tooltip class="item" content="单列小图" placement="bottom">
+              <el-radio-button value="oneColSmallImg">
+                <Icon icon="fluent:text-column-two-left-24-filled" />
+              </el-radio-button>
+            </el-tooltip>
+            <el-tooltip class="item" content="双列" placement="bottom">
+              <el-radio-button value="twoCol">
+                <Icon icon="fluent:text-column-two-24-filled" />
+              </el-radio-button>
+            </el-tooltip>
+            <!--<el-tooltip class="item" content="三列" placement="bottom">
+              <el-radio-button value="threeCol">
+                <Icon icon="fluent:text-column-three-24-filled" />
+              </el-radio-button>
+            </el-tooltip>-->
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="商品名称" prop="fields.name.show">
+          <div class="flex gap-8px">
+            <ColorInput v-model="formData.fields.name.color" />
+            <el-checkbox v-model="formData.fields.name.show" />
+          </div>
+        </el-form-item>
+        <el-form-item label="商品简介" prop="fields.introduction.show">
+          <div class="flex gap-8px">
+            <ColorInput v-model="formData.fields.introduction.color" />
+            <el-checkbox v-model="formData.fields.introduction.show" />
+          </div>
+        </el-form-item>
+        <el-form-item label="商品价格" prop="fields.price.show">
+          <div class="flex gap-8px">
+            <ColorInput v-model="formData.fields.price.color" />
+            <el-checkbox v-model="formData.fields.price.show" />
+          </div>
+        </el-form-item>
+        <el-form-item label="市场价" prop="fields.marketPrice.show">
+          <div class="flex gap-8px">
+            <ColorInput v-model="formData.fields.marketPrice.color" />
+            <el-checkbox v-model="formData.fields.marketPrice.show" />
+          </div>
+        </el-form-item>
+        <el-form-item label="商品销量" prop="fields.salesCount.show">
+          <div class="flex gap-8px">
+            <ColorInput v-model="formData.fields.salesCount.color" />
+            <el-checkbox v-model="formData.fields.salesCount.show" />
+          </div>
+        </el-form-item>
+        <el-form-item label="商品库存" prop="fields.stock.show">
+          <div class="flex gap-8px">
+            <ColorInput v-model="formData.fields.stock.color" />
+            <el-checkbox v-model="formData.fields.stock.show" />
+          </div>
+        </el-form-item>
+      </el-card>
+      <el-card class="property-group" header="角标" shadow="never">
+        <el-form-item label="角标" prop="badge.show">
+          <el-switch v-model="formData.badge.show" />
+        </el-form-item>
+        <el-form-item v-if="formData.badge.show" label="角标" prop="badge.imgUrl">
+          <UploadImg v-model="formData.badge.imgUrl" height="44px" width="72px">
+            <template #tip> 建议尺寸:36 * 22</template>
+          </UploadImg>
+        </el-form-item>
+      </el-card>
+      <el-card class="property-group" header="按钮" shadow="never">
+        <el-form-item label="按钮类型" prop="btnBuy.type">
+          <el-radio-group v-model="formData.btnBuy.type">
+            <el-radio-button value="text">文字</el-radio-button>
+            <el-radio-button value="img">图片</el-radio-button>
+          </el-radio-group>
+        </el-form-item>
+        <template v-if="formData.btnBuy.type === 'text'">
+          <el-form-item label="按钮文字" prop="btnBuy.text">
+            <el-input v-model="formData.btnBuy.text" />
+          </el-form-item>
+          <el-form-item label="左侧背景" prop="btnBuy.bgBeginColor">
+            <ColorInput v-model="formData.btnBuy.bgBeginColor" />
+          </el-form-item>
+          <el-form-item label="右侧背景" prop="btnBuy.bgEndColor">
+            <ColorInput v-model="formData.btnBuy.bgEndColor" />
+          </el-form-item>
+        </template>
+        <template v-else>
+          <el-form-item label="图片" prop="btnBuy.imgUrl">
+            <UploadImg v-model="formData.btnBuy.imgUrl" height="56px" width="56px">
+              <template #tip> 建议尺寸:56 * 56</template>
+            </UploadImg>
+          </el-form-item>
+        </template>
+      </el-card>
+      <el-card class="property-group" header="商品样式" shadow="never">
+        <el-form-item label="上圆角" prop="borderRadiusTop">
+          <el-slider
+            v-model="formData.borderRadiusTop"
+            :max="100"
+            :min="0"
+            :show-input-controls="false"
+            input-size="small"
+            show-input
+          />
+        </el-form-item>
+        <el-form-item label="下圆角" prop="borderRadiusBottom">
+          <el-slider
+            v-model="formData.borderRadiusBottom"
+            :max="100"
+            :min="0"
+            :show-input-controls="false"
+            input-size="small"
+            show-input
+          />
+        </el-form-item>
+        <el-form-item label="间隔" prop="space">
+          <el-slider
+            v-model="formData.space"
+            :max="100"
+            :min="0"
+            :show-input-controls="false"
+            input-size="small"
+            show-input
+          />
+        </el-form-item>
+      </el-card>
+    </el-form>
+  </ComponentContainerProperty>
+</template>
+
+<script lang="ts" setup>
+import { PromotionPointProperty } from './config'
+import { usePropertyForm } from '@/components/DiyEditor/util'
+import PointShowcase from '@/views/mall/promotion/point/components/PointShowcase.vue'
+
+// 秒杀属性面板
+defineOptions({ name: 'PromotionPointProperty' })
+
+const props = defineProps<{ modelValue: PromotionPointProperty }>()
+const emit = defineEmits(['update:modelValue'])
+const { formData } = usePropertyForm(props.modelValue, emit)
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/components/DiyEditor/components/mobile/PromotionSeckill/config.ts b/src/components/DiyEditor/components/mobile/PromotionSeckill/config.ts
index 800398b..022be92 100644
--- a/src/components/DiyEditor/components/mobile/PromotionSeckill/config.ts
+++ b/src/components/DiyEditor/components/mobile/PromotionSeckill/config.ts
@@ -3,19 +3,40 @@
 /** 秒杀属性 */
 export interface PromotionSeckillProperty {
   // 布局类型:单列 | 三列
-  layoutType: 'oneCol' | 'threeCol'
+  layoutType: 'oneColBigImg' | 'oneColSmallImg' | 'twoCol'
   // 商品字段
   fields: {
     // 商品名称
     name: PromotionSeckillFieldProperty
+    // 商品简介
+    introduction: PromotionSeckillFieldProperty
     // 商品价格
     price: PromotionSeckillFieldProperty
+    // 市场价
+    marketPrice: PromotionSeckillFieldProperty
+    // 商品销量
+    salesCount: PromotionSeckillFieldProperty
+    // 商品库存
+    stock: PromotionSeckillFieldProperty
   }
   // 角标
   badge: {
     // 是否显示
     show: boolean
     // 角标图片
+    imgUrl: string
+  }
+  // 按钮
+  btnBuy: {
+    // 类型:文字 | 图片
+    type: 'text' | 'img'
+    // 文字
+    text: string
+    // 文字按钮:背景渐变起始颜色
+    bgBeginColor: string
+    // 文字按钮:背景渐变结束颜色
+    bgEndColor: string
+    // 图片按钮:图片地址
     imgUrl: string
   }
   // 上圆角
@@ -25,10 +46,11 @@
   // 间距
   space: number
   // 秒杀活动编号
-  activityId: number
+  activityIds: number[]
   // 组件样式
   style: ComponentStyle
 }
+
 // 商品字段
 export interface PromotionSeckillFieldProperty {
   // 是否显示
@@ -43,13 +65,23 @@
   name: '秒杀',
   icon: 'mdi:calendar-time',
   property: {
-    activityId: undefined,
-    layoutType: 'oneCol',
+    layoutType: 'oneColBigImg',
     fields: {
       name: { show: true, color: '#000' },
-      price: { show: true, color: '#ff3000' }
+      introduction: { show: true, color: '#999' },
+      price: { show: true, color: '#ff3000' },
+      marketPrice: { show: true, color: '#c4c4c4' },
+      salesCount: { show: true, color: '#c4c4c4' },
+      stock: { show: false, color: '#c4c4c4' }
     },
     badge: { show: false, imgUrl: '' },
+    btnBuy: {
+      type: 'text',
+      text: '立即秒杀',
+      bgBeginColor: '#FF6000',
+      bgEndColor: '#FE832A',
+      imgUrl: ''
+    },
     borderRadiusTop: 8,
     borderRadiusBottom: 8,
     space: 8,
diff --git a/src/components/DiyEditor/components/mobile/PromotionSeckill/index.vue b/src/components/DiyEditor/components/mobile/PromotionSeckill/index.vue
index 1b4113b..3d34a3d 100644
--- a/src/components/DiyEditor/components/mobile/PromotionSeckill/index.vue
+++ b/src/components/DiyEditor/components/mobile/PromotionSeckill/index.vue
@@ -1,125 +1,201 @@
 <template>
-  <el-scrollbar class="z-1 min-h-30px" wrap-class="w-full" ref="containerRef">
-    <!-- 商品网格 -->
+  <div :class="`box-content min-h-30px w-full flex flex-row flex-wrap`" ref="containerRef">
     <div
-      class="grid overflow-x-auto"
+      class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
       :style="{
-        gridGap: `${property.space}px`,
-        gridTemplateColumns,
-        width: scrollbarWidth
+        ...calculateSpace(index),
+        ...calculateWidth(),
+        borderTopLeftRadius: `${property.borderRadiusTop}px`,
+        borderTopRightRadius: `${property.borderRadiusTop}px`,
+        borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
+        borderBottomRightRadius: `${property.borderRadiusBottom}px`
       }"
+      v-for="(spu, index) in spuList"
+      :key="index"
     >
-      <!-- 商品 -->
+      <!-- 角标 -->
+      <div v-if="property.badge.show" class="absolute left-0 top-0 z-1 items-center justify-center">
+        <el-image fit="cover" :src="property.badge.imgUrl" class="h-26px w-38px" />
+      </div>
+      <!-- 商品封面图 -->
       <div
-        class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
-        :style="{
-          borderTopLeftRadius: `${property.borderRadiusTop}px`,
-          borderTopRightRadius: `${property.borderRadiusTop}px`,
-          borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
-          borderBottomRightRadius: `${property.borderRadiusBottom}px`
-        }"
-        v-for="(spu, index) in spuList"
-        :key="index"
+        :class="[
+          'h-140px',
+          {
+            'w-full': property.layoutType !== 'oneColSmallImg',
+            'w-140px': property.layoutType === 'oneColSmallImg'
+          }
+        ]"
       >
-        <!-- 角标 -->
+        <el-image fit="cover" class="h-full w-full" :src="spu.picUrl" />
+      </div>
+      <div
+        :class="[
+          ' flex flex-col gap-8px p-8px box-border',
+          {
+            'w-full': property.layoutType !== 'oneColSmallImg',
+            'w-[calc(100%-140px-16px)]': property.layoutType === 'oneColSmallImg'
+          }
+        ]"
+      >
+        <!-- 商品名称 -->
         <div
-          v-if="property.badge.show"
-          class="absolute left-0 top-0 z-1 items-center justify-center"
-        >
-          <el-image fit="cover" :src="property.badge.imgUrl" class="h-26px w-38px" />
-        </div>
-        <!-- 商品封面图 -->
-        <el-image fit="cover" :src="spu.picUrl" :style="{ width: imageSize, height: imageSize }" />
-        <div
+          v-if="property.fields.name.show"
           :class="[
-            'flex flex-col gap-8px p-8px box-border',
+            'text-14px ',
             {
-              'w-[calc(100%-64px)]': columns === 2,
-              'w-full': columns === 3
+              truncate: property.layoutType !== 'oneColSmallImg',
+              'overflow-ellipsis line-clamp-2': property.layoutType === 'oneColSmallImg'
             }
           ]"
+          :style="{ color: property.fields.name.color }"
         >
-          <!-- 商品名称 -->
-          <div
-            v-if="property.fields.name.show"
-            class="truncate text-12px"
-            :style="{ color: property.fields.name.color }"
+          {{ spu.name }}
+        </div>
+        <!-- 商品简介 -->
+        <div
+          v-if="property.fields.introduction.show"
+          class="truncate text-12px"
+          :style="{ color: property.fields.introduction.color }"
+        >
+          {{ spu.introduction }}
+        </div>
+        <div>
+          <!-- 价格 -->
+          <span
+            v-if="property.fields.price.show"
+            class="text-16px"
+            :style="{ color: property.fields.price.color }"
           >
-            {{ spu.name }}
-          </div>
-          <div>
-            <!-- 商品价格 -->
-            <span
-              v-if="property.fields.price.show"
-              class="text-12px"
-              :style="{ color: property.fields.price.color }"
-            >
-              ¥{{ spu.price }}
-            </span>
-          </div>
+            ¥{{ fenToYuan(spu.price || Infinity) }}
+          </span>
+          <!-- 市场价 -->
+          <span
+            v-if="property.fields.marketPrice.show && spu.marketPrice"
+            class="ml-4px text-10px line-through"
+            :style="{ color: property.fields.marketPrice.color }"
+            >¥{{ fenToYuan(spu.marketPrice) }}</span
+          >
+        </div>
+        <div class="text-12px">
+          <!-- 销量 -->
+          <span
+            v-if="property.fields.salesCount.show"
+            :style="{ color: property.fields.salesCount.color }"
+          >
+            已售{{ (spu.salesCount || 0) + (spu.virtualSalesCount || 0) }}件
+          </span>
+          <!-- 库存 -->
+          <span v-if="property.fields.stock.show" :style="{ color: property.fields.stock.color }">
+            库存{{ spu.stock || 0 }}
+          </span>
         </div>
       </div>
+      <!-- 购买按钮 -->
+      <div class="absolute bottom-8px right-8px">
+        <!-- 文字按钮 -->
+        <span
+          v-if="property.btnBuy.type === 'text'"
+          class="rounded-full p-x-12px p-y-4px text-12px text-white"
+          :style="{
+            background: `linear-gradient(to right, ${property.btnBuy.bgBeginColor}, ${property.btnBuy.bgEndColor}`
+          }"
+        >
+          {{ property.btnBuy.text }}
+        </span>
+        <!-- 图片按钮 -->
+        <el-image
+          v-else
+          class="h-28px w-28px rounded-full"
+          fit="cover"
+          :src="property.btnBuy.imgUrl"
+        />
+      </div>
     </div>
-  </el-scrollbar>
+  </div>
 </template>
 <script setup lang="ts">
 import { PromotionSeckillProperty } from './config'
 import * as ProductSpuApi from '@/api/mall/product/spu'
 import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
+import { fenToYuan } from '@/utils'
 
-/** 秒杀 */
+/** 秒杀卡片 */
 defineOptions({ name: 'PromotionSeckill' })
 // 定义属性
 const props = defineProps<{ property: PromotionSeckillProperty }>()
 // 商品列表
 const spuList = ref<ProductSpuApi.Spu[]>([])
+const spuIdList = ref<number[]>([])
+const seckillActivityList = ref<SeckillActivityApi.SeckillActivityVO[]>([])
+
 watch(
-  () => props.property.activityId,
+  () => props.property.activityIds,
   async () => {
-    if (!props.property.activityId) return
-    const activity = await SeckillActivityApi.getSeckillActivity(props.property.activityId)
-    if (!activity?.spuId) return
-    spuList.value = [await ProductSpuApi.getSpu(activity.spuId)]
+    try {
+      // 新添加的秒杀组件,是没有活动ID的
+      const activityIds = props.property.activityIds
+      // 检查活动ID的有效性
+      if (Array.isArray(activityIds) && activityIds.length > 0) {
+        // 获取秒杀活动详情列表
+        seckillActivityList.value =
+          await SeckillActivityApi.getSeckillActivityListByIds(activityIds)
+
+        // 获取秒杀活动的 SPU 详情列表
+        spuList.value = []
+        spuIdList.value = seckillActivityList.value
+          .map((activity) => activity.spuId)
+          .filter((spuId): spuId is number => typeof spuId === 'number')
+        if (spuIdList.value.length > 0) {
+          spuList.value = await ProductSpuApi.getSpuDetailList(spuIdList.value)
+        }
+
+        // 更新 SPU 的最低价格
+        seckillActivityList.value.forEach((activity) => {
+          // 匹配spuId
+          const spu = spuList.value.find((spu) => spu.id === activity.spuId)
+          if (spu) {
+            // 赋值活动价格,哪个最便宜就赋值哪个
+            spu.price = Math.min(activity.seckillPrice || Infinity, spu.price || Infinity)
+          }
+        })
+      }
+    } catch (error) {
+      console.error('获取秒杀活动细节或 SPU 细节时出错:', error)
+    }
   },
   {
     immediate: true,
     deep: true
   }
 )
-// 手机宽度
-const phoneWidth = ref(375)
+
+/**
+ * 计算商品的间距
+ * @param index 商品索引
+ */
+const calculateSpace = (index: number) => {
+  // 商品的列数
+  const columns = props.property.layoutType === 'twoCol' ? 2 : 1
+  // 第一列没有左边距
+  const marginLeft = index % columns === 0 ? '0' : props.property.space + 'px'
+  // 第一行没有上边距
+  const marginTop = index < columns ? '0' : props.property.space + 'px'
+
+  return { marginLeft, marginTop }
+}
+
 // 容器
 const containerRef = ref()
-// 商品的列数
-const columns = ref(2)
-// 滚动条宽度
-const scrollbarWidth = ref('100%')
-// 商品图大小
-const imageSize = ref('0')
-// 商品网络列数
-const gridTemplateColumns = ref('')
-// 计算布局参数
-watch(
-  () => [props.property, phoneWidth, spuList.value.length],
-  () => {
-    // 计算列数
-    columns.value = props.property.layoutType === 'oneCol' ? 1 : 3
-    // 每列的宽度为:(总宽度 - 间距 * (列数 - 1))/ 列数
-    const productWidth =
-      (phoneWidth.value - props.property.space * (columns.value - 1)) / columns.value
-    // 商品图布局:2列时,左右布局 3列时,上下布局
-    imageSize.value = columns.value === 2 ? '64px' : `${productWidth}px`
-    // 指定列数
-    gridTemplateColumns.value = `repeat(${columns.value}, auto)`
-    // 不滚动
-    scrollbarWidth.value = '100%'
-  },
-  { immediate: true, deep: true }
-)
-onMounted(() => {
-  // 提取手机宽度
-  phoneWidth.value = containerRef.value?.wrapRef?.offsetWidth || 375
-})
+// 计算商品的宽度
+const calculateWidth = () => {
+  let width = '100%'
+  // 双列时每列的宽度为:(总宽度 - 间距)/ 2
+  if (props.property.layoutType === 'twoCol') {
+    width = `${(containerRef.value.offsetWidth - props.property.space) / 2}px`
+  }
+  return { width }
+}
 </script>
 
 <style scoped lang="scss"></style>
diff --git a/src/components/DiyEditor/components/mobile/PromotionSeckill/property.vue b/src/components/DiyEditor/components/mobile/PromotionSeckill/property.vue
index 8753782..6128759 100644
--- a/src/components/DiyEditor/components/mobile/PromotionSeckill/property.vue
+++ b/src/components/DiyEditor/components/mobile/PromotionSeckill/property.vue
@@ -2,30 +2,31 @@
   <ComponentContainerProperty v-model="formData.style">
     <el-form label-width="80px" :model="formData">
       <el-card header="秒杀活动" class="property-group" shadow="never">
-        <el-form-item label="秒杀活动" prop="activityId">
-          <el-select v-model="formData.activityId">
-            <el-option
-              v-for="activity in activityList"
-              :key="activity.id"
-              :label="activity.name"
-              :value="activity.id"
-            />
-          </el-select>
-        </el-form-item>
+        <SeckillShowcase v-model="formData.activityIds" />
       </el-card>
       <el-card header="商品样式" class="property-group" shadow="never">
         <el-form-item label="布局" prop="type">
           <el-radio-group v-model="formData.layoutType">
-            <el-tooltip class="item" content="单列" placement="bottom">
-              <el-radio-button label="oneCol">
+            <el-tooltip class="item" content="单列大图" placement="bottom">
+              <el-radio-button value="oneColBigImg">
                 <Icon icon="fluent:text-column-one-24-filled" />
               </el-radio-button>
             </el-tooltip>
-            <el-tooltip class="item" content="三列" placement="bottom">
-              <el-radio-button label="threeCol">
-                <Icon icon="fluent:text-column-three-24-filled" />
+            <el-tooltip class="item" content="单列小图" placement="bottom">
+              <el-radio-button value="oneColSmallImg">
+                <Icon icon="fluent:text-column-two-left-24-filled" />
               </el-radio-button>
             </el-tooltip>
+            <el-tooltip class="item" content="双列" placement="bottom">
+              <el-radio-button value="twoCol">
+                <Icon icon="fluent:text-column-two-24-filled" />
+              </el-radio-button>
+            </el-tooltip>
+            <!--<el-tooltip class="item" content="三列" placement="bottom">
+              <el-radio-button value="threeCol">
+                <Icon icon="fluent:text-column-three-24-filled" />
+              </el-radio-button>
+            </el-tooltip>-->
           </el-radio-group>
         </el-form-item>
         <el-form-item label="商品名称" prop="fields.name.show">
@@ -34,10 +35,34 @@
             <el-checkbox v-model="formData.fields.name.show" />
           </div>
         </el-form-item>
+        <el-form-item label="商品简介" prop="fields.introduction.show">
+          <div class="flex gap-8px">
+            <ColorInput v-model="formData.fields.introduction.color" />
+            <el-checkbox v-model="formData.fields.introduction.show" />
+          </div>
+        </el-form-item>
         <el-form-item label="商品价格" prop="fields.price.show">
           <div class="flex gap-8px">
             <ColorInput v-model="formData.fields.price.color" />
             <el-checkbox v-model="formData.fields.price.show" />
+          </div>
+        </el-form-item>
+        <el-form-item label="市场价" prop="fields.marketPrice.show">
+          <div class="flex gap-8px">
+            <ColorInput v-model="formData.fields.marketPrice.color" />
+            <el-checkbox v-model="formData.fields.marketPrice.show" />
+          </div>
+        </el-form-item>
+        <el-form-item label="商品销量" prop="fields.salesCount.show">
+          <div class="flex gap-8px">
+            <ColorInput v-model="formData.fields.salesCount.color" />
+            <el-checkbox v-model="formData.fields.salesCount.show" />
+          </div>
+        </el-form-item>
+        <el-form-item label="商品库存" prop="fields.stock.show">
+          <div class="flex gap-8px">
+            <ColorInput v-model="formData.fields.stock.color" />
+            <el-checkbox v-model="formData.fields.stock.show" />
           </div>
         </el-form-item>
       </el-card>
@@ -47,9 +72,35 @@
         </el-form-item>
         <el-form-item label="角标" prop="badge.imgUrl" v-if="formData.badge.show">
           <UploadImg v-model="formData.badge.imgUrl" height="44px" width="72px">
-            <template #tip> 建议尺寸:36 * 22 </template>
+            <template #tip> 建议尺寸:36 * 22</template>
           </UploadImg>
         </el-form-item>
+      </el-card>
+      <el-card header="按钮" class="property-group" shadow="never">
+        <el-form-item label="按钮类型" prop="btnBuy.type">
+          <el-radio-group v-model="formData.btnBuy.type">
+            <el-radio-button value="text">文字</el-radio-button>
+            <el-radio-button value="img">图片</el-radio-button>
+          </el-radio-group>
+        </el-form-item>
+        <template v-if="formData.btnBuy.type === 'text'">
+          <el-form-item label="按钮文字" prop="btnBuy.text">
+            <el-input v-model="formData.btnBuy.text" />
+          </el-form-item>
+          <el-form-item label="左侧背景" prop="btnBuy.bgBeginColor">
+            <ColorInput v-model="formData.btnBuy.bgBeginColor" />
+          </el-form-item>
+          <el-form-item label="右侧背景" prop="btnBuy.bgEndColor">
+            <ColorInput v-model="formData.btnBuy.bgEndColor" />
+          </el-form-item>
+        </template>
+        <template v-else>
+          <el-form-item label="图片" prop="btnBuy.imgUrl">
+            <UploadImg v-model="formData.btnBuy.imgUrl" height="56px" width="56px">
+              <template #tip> 建议尺寸:56 * 56</template>
+            </UploadImg>
+          </el-form-item>
+        </template>
       </el-card>
       <el-card header="商品样式" class="property-group" shadow="never">
         <el-form-item label="上圆角" prop="borderRadiusTop">
@@ -92,6 +143,7 @@
 import { usePropertyForm } from '@/components/DiyEditor/util'
 import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
 import { CommonStatusEnum } from '@/utils/constants'
+import SeckillShowcase from '@/views/mall/promotion/seckill/components/SeckillShowcase.vue'
 
 // 秒杀属性面板
 defineOptions({ name: 'PromotionSeckillProperty' })
@@ -100,7 +152,7 @@
 const emit = defineEmits(['update:modelValue'])
 const { formData } = usePropertyForm(props.modelValue, emit)
 // 活动列表
-const activityList = ref<SeckillActivityApi.SeckillActivityVO>([])
+const activityList = ref<SeckillActivityApi.SeckillActivityVO[]>([])
 onMounted(async () => {
   const { list } = await SeckillActivityApi.getSeckillActivityPage({
     status: CommonStatusEnum.ENABLE
diff --git a/src/components/DiyEditor/components/mobile/SearchBar/property.vue b/src/components/DiyEditor/components/mobile/SearchBar/property.vue
index 9002702..71f9493 100644
--- a/src/components/DiyEditor/components/mobile/SearchBar/property.vue
+++ b/src/components/DiyEditor/components/mobile/SearchBar/property.vue
@@ -13,12 +13,12 @@
         <el-form-item label="框体样式">
           <el-radio-group v-model="formData!.borderRadius">
             <el-tooltip content="方形" placement="top">
-              <el-radio-button :label="0">
+              <el-radio-button :value="0">
                 <Icon icon="tabler:input-search" />
               </el-radio-button>
             </el-tooltip>
             <el-tooltip content="圆形" placement="top">
-              <el-radio-button :label="10">
+              <el-radio-button :value="10">
                 <Icon icon="iconoir:input-search" />
               </el-radio-button>
             </el-tooltip>
@@ -30,12 +30,12 @@
         <el-form-item label="文本位置" prop="placeholderPosition">
           <el-radio-group v-model="formData!.placeholderPosition">
             <el-tooltip content="居左" placement="top">
-              <el-radio-button label="left">
+              <el-radio-button value="left">
                 <Icon icon="ant-design:align-left-outlined" />
               </el-radio-button>
             </el-tooltip>
             <el-tooltip content="居中" placement="top">
-              <el-radio-button label="center">
+              <el-radio-button value="center">
                 <Icon icon="ant-design:align-center-outlined" />
               </el-radio-button>
             </el-tooltip>
diff --git a/src/components/DiyEditor/components/mobile/TabBar/config.ts b/src/components/DiyEditor/components/mobile/TabBar/config.ts
index 7e52666..88d706f 100644
--- a/src/components/DiyEditor/components/mobile/TabBar/config.ts
+++ b/src/components/DiyEditor/components/mobile/TabBar/config.ts
@@ -53,26 +53,26 @@
       {
         text: '首页',
         url: '/pages/index/index',
-        iconUrl: 'http://xxxx/static/images/1-001.png',
-        activeIconUrl: 'http://xxxx/static/images/1-002.png'
+        iconUrl: 'http://mall.yudao.iocoder.cn/static/images/1-001.png',
+        activeIconUrl: 'http://mall.yudao.iocoder.cn/static/images/1-002.png'
       },
       {
         text: '分类',
         url: '/pages/index/category?id=3',
-        iconUrl: 'http://xxxx/static/images/2-001.png',
-        activeIconUrl: 'http://xxxx/static/images/2-002.png'
+        iconUrl: 'http://mall.yudao.iocoder.cn/static/images/2-001.png',
+        activeIconUrl: 'http://mall.yudao.iocoder.cn/static/images/2-002.png'
       },
       {
         text: '购物车',
         url: '/pages/index/cart',
-        iconUrl: 'http://xxxx/static/images/3-001.png',
-        activeIconUrl: 'http://xxxx/static/images/3-002.png'
+        iconUrl: 'http://mall.yudao.iocoder.cn/static/images/3-001.png',
+        activeIconUrl: 'http://mall.yudao.iocoder.cn/static/images/3-002.png'
       },
       {
         text: '我的',
         url: '/pages/index/user',
-        iconUrl: 'http://xxxx/static/images/4-001.png',
-        activeIconUrl: 'http://xxxx/static/images/4-002.png'
+        iconUrl: 'http://mall.yudao.iocoder.cn/static/images/4-001.png',
+        activeIconUrl: 'http://mall.yudao.iocoder.cn/static/images/4-002.png'
       }
     ]
   }
diff --git a/src/components/DiyEditor/components/mobile/TabBar/property.vue b/src/components/DiyEditor/components/mobile/TabBar/property.vue
index 6ace5af..d1da142 100644
--- a/src/components/DiyEditor/components/mobile/TabBar/property.vue
+++ b/src/components/DiyEditor/components/mobile/TabBar/property.vue
@@ -27,8 +27,8 @@
       </el-form-item>
       <el-form-item label="导航背景">
         <el-radio-group v-model="formData!.style.bgType">
-          <el-radio-button label="color">纯色</el-radio-button>
-          <el-radio-button label="img">图片</el-radio-button>
+          <el-radio-button value="color">纯色</el-radio-button>
+          <el-radio-button value="img">图片</el-radio-button>
         </el-radio-group>
       </el-form-item>
       <el-form-item label="选择颜色" v-if="formData!.style.bgType === 'color'">
@@ -79,7 +79,7 @@
 </template>
 
 <script setup lang="ts">
-import { TabBarProperty, THEME_LIST } from './config'
+import { TabBarProperty, component, THEME_LIST } from './config'
 import { usePropertyForm } from '@/components/DiyEditor/util'
 // 底部导航栏
 defineOptions({ name: 'TabBarProperty' })
@@ -88,6 +88,9 @@
 const emit = defineEmits(['update:modelValue'])
 const { formData } = usePropertyForm(props.modelValue, emit)
 
+// 将数据库的值更新到右侧属性栏
+component.property.items = formData.value.items
+
 // 要的主题
 const handleThemeChange = () => {
   const theme = THEME_LIST.find((theme) => theme.id === formData.value.theme)
diff --git a/src/components/DiyEditor/components/mobile/TitleBar/property.vue b/src/components/DiyEditor/components/mobile/TitleBar/property.vue
index 4eb3259..44d6bb6 100644
--- a/src/components/DiyEditor/components/mobile/TitleBar/property.vue
+++ b/src/components/DiyEditor/components/mobile/TitleBar/property.vue
@@ -10,12 +10,12 @@
         <el-form-item label="标题位置" prop="textAlign">
           <el-radio-group v-model="formData!.textAlign">
             <el-tooltip content="居左" placement="top">
-              <el-radio-button label="left">
+              <el-radio-button value="left">
                 <Icon icon="ant-design:align-left-outlined" />
               </el-radio-button>
             </el-tooltip>
             <el-tooltip content="居中" placement="top">
-              <el-radio-button label="center">
+              <el-radio-button value="center">
                 <Icon icon="ant-design:align-center-outlined" />
               </el-radio-button>
             </el-tooltip>
@@ -88,9 +88,9 @@
         <template v-if="formData.more.show">
           <el-form-item label="样式" prop="more.type">
             <el-radio-group v-model="formData.more.type">
-              <el-radio label="text">文字</el-radio>
-              <el-radio label="icon">图标</el-radio>
-              <el-radio label="all">文字+图标</el-radio>
+              <el-radio value="text">文字</el-radio>
+              <el-radio value="icon">图标</el-radio>
+              <el-radio value="all">文字+图标</el-radio>
             </el-radio-group>
           </el-form-item>
           <el-form-item label="更多文字" prop="more.text" v-show="formData.more.type !== 'icon'">
diff --git a/src/components/DiyEditor/components/mobile/UserCard/index.vue b/src/components/DiyEditor/components/mobile/UserCard/index.vue
index 7005b97..14b447c 100644
--- a/src/components/DiyEditor/components/mobile/UserCard/index.vue
+++ b/src/components/DiyEditor/components/mobile/UserCard/index.vue
@@ -5,7 +5,7 @@
         <el-avatar :size="60">
           <Icon icon="ep:avatar" :size="60" />
         </el-avatar>
-        <span class="text-18px font-bold">工业互联网平台</span>
+        <span class="text-18px font-bold">芋道源码</span>
       </div>
       <Icon icon="tdesign:qrcode" :size="20" />
     </div>
diff --git a/src/components/Draggable/index.vue b/src/components/Draggable/index.vue
index 2175946..3d7906b 100644
--- a/src/components/Draggable/index.vue
+++ b/src/components/Draggable/index.vue
@@ -13,9 +13,9 @@
         class="mb-4px flex flex-col gap-4px border border-gray-2 border-rounded rounded border-solid p-8px"
       >
         <!-- 操作按钮区 -->
-        <div class="m--8px m-b-4px flex flex-row items-center justify-between bg-gray-1 p-8px">
+        <div class="m--8px m-b-4px flex flex-row items-center justify-between p-8px" style="background-color: var(--app-content-bg-color);">
           <el-tooltip content="拖动排序">
-            <Icon icon="ic:round-drag-indicator" class="drag-icon cursor-move" />
+            <Icon icon="ic:round-drag-indicator" class="drag-icon cursor-move" style="color: #8a909c;" />
           </el-tooltip>
           <el-tooltip content="删除">
             <Icon
diff --git a/src/components/Echart/src/Echart.vue b/src/components/Echart/src/Echart.vue
index fd3342d..02738ca 100644
--- a/src/components/Echart/src/Echart.vue
+++ b/src/components/Echart/src/Echart.vue
@@ -9,6 +9,10 @@
 import { isString } from '@/utils/is'
 import { useDesign } from '@/hooks/web/useDesign'
 
+import 'echarts/lib/component/markPoint'
+import 'echarts/lib/component/markLine'
+import 'echarts/lib/component/markArea'
+
 defineOptions({ name: 'EChart' })
 
 const { getPrefixCls, variables } = useDesign()
@@ -94,13 +98,13 @@
 
   contentEl.value = document.getElementsByClassName(`${variables.namespace}-layout-content`)[0]
   unref(contentEl) &&
-    (unref(contentEl) as Element).addEventListener('transitionend', contentResizeHandler)
+  (unref(contentEl) as Element).addEventListener('transitionend', contentResizeHandler)
 })
 
 onBeforeUnmount(() => {
   window.removeEventListener('resize', resizeHandler)
   unref(contentEl) &&
-    (unref(contentEl) as Element).removeEventListener('transitionend', contentResizeHandler)
+  (unref(contentEl) as Element).removeEventListener('transitionend', contentResizeHandler)
 })
 
 onActivated(() => {
diff --git a/src/components/Editor/src/Editor.vue b/src/components/Editor/src/Editor.vue
index 8dd0645..e16776c 100644
--- a/src/components/Editor/src/Editor.vue
+++ b/src/components/Editor/src/Editor.vue
@@ -7,6 +7,7 @@
 import { ElMessage } from 'element-plus'
 import { useLocaleStore } from '@/store/modules/locale'
 import { getAccessToken, getTenantId } from '@/utils/auth'
+import { getUploadUrl } from '@/components/UploadFile/src/useUpload'
 
 defineOptions({ name: 'Editor' })
 
@@ -88,7 +89,7 @@
       scroll: true,
       MENU_CONF: {
         ['uploadImage']: {
-          server: import.meta.env.VITE_UPLOAD_URL,
+          server: getUploadUrl(),
           // 单个文件的最大体积限制,默认为 2M
           maxFileSize: 5 * 1024 * 1024,
           // 最多可上传几个文件,默认为 100
@@ -136,7 +137,7 @@
           }
         },
         ['uploadVideo']: {
-          server: import.meta.env.VITE_UPLOAD_URL,
+          server: getUploadUrl(),
           // 单个文件的最大体积限制,默认为 10M
           maxFileSize: 10 * 1024 * 1024,
           // 最多可上传几个文件,默认为 100
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/IFrame/src/IFrame.vue b/src/components/IFrame/src/IFrame.vue
index 19de51a..64ffc0e 100644
--- a/src/components/IFrame/src/IFrame.vue
+++ b/src/components/IFrame/src/IFrame.vue
@@ -7,26 +7,41 @@
   src: propTypes.string.def('')
 })
 const loading = ref(true)
-const height = ref('')
 const frameRef = ref<HTMLElement | null>(null)
 const init = () => {
-  height.value = document.documentElement.clientHeight - 94.5 + 'px'
-  loading.value = false
+  nextTick(() => {
+    loading.value = true
+    if (!frameRef.value) return
+    frameRef.value.onload = () => {
+      loading.value = false
+    }
+  })
 }
 onMounted(() => {
-  setTimeout(() => {
-    init()
-  }, 300)
+  init()
 })
+watch(
+  () => props.src,
+  () => {
+    init()
+  }
+)
 </script>
 <template>
-  <div v-loading="loading" :style="'height:' + height">
+  <div
+    v-loading="loading"
+    class="w-full h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-2px)]"
+  >
     <iframe
       ref="frameRef"
       :src="props.src"
-      frameborder="no"
+      frameborder="0"
       scrolling="auto"
-      style="width: 100%; height: 100%"
+      height="100%"
+      width="100%"
+      allowfullscreen="true"
+      webkitallowfullscreen="true"
+      mozallowfullscreen="true"
     ></iframe>
   </div>
 </template>
diff --git a/src/components/Icon/src/IconSelect.vue b/src/components/Icon/src/IconSelect.vue
index d4a5b07..76cc6d5 100644
--- a/src/components/Icon/src/IconSelect.vue
+++ b/src/components/Icon/src/IconSelect.vue
@@ -11,6 +11,10 @@
   modelValue: {
     require: false,
     type: String
+  },
+  clearable: {
+    require: false,
+    type: Boolean
   }
 })
 const emit = defineEmits<{ (e: 'update:modelValue', v: string) }>()
@@ -92,6 +96,12 @@
   currentPage.value = page
 }
 
+function clearIcon() {
+  icon.value = ''
+  emit('update:modelValue', '')
+  visible.value = false
+}
+
 watch(
   () => {
     return props.modelValue
@@ -115,14 +125,14 @@
 
 <template>
   <div class="selector">
-    <ElInput v-model="inputValue" @click="visible = !visible">
+    <ElInput v-model="inputValue" @click="visible = !visible" :clearable="props.clearable" @clear="clearIcon">
       <template #append>
         <ElPopover
           :popper-options="{
             placement: 'auto'
           }"
           :visible="visible"
-          :width="350"
+          :width="355"
           popper-class="pure-popper"
           trigger="click"
         >
@@ -147,7 +157,7 @@
             >
               <ElDivider border-style="dashed" class="tab-divider" />
               <ElScrollbar height="220px">
-                <ul class="ml-2 flex flex-wrap px-2">
+                <ul class="ml-2 flex flex-wrap">
                   <li
                     v-for="(item, key) in pageList"
                     :key="key"
@@ -171,7 +181,7 @@
             background
             class="h-10 flex items-center justify-center"
             layout="prev, pager, next"
-            small
+            size="small"
             @current-change="onCurrentChange"
           />
         </ElPopover>
diff --git a/src/components/RouterSearch/index.vue b/src/components/RouterSearch/index.vue
index c035242..42a4174 100644
--- a/src/components/RouterSearch/index.vue
+++ b/src/components/RouterSearch/index.vue
@@ -20,6 +20,7 @@
   <div v-else class="custom-hover" @click.stop="showTopSearch = !showTopSearch">
     <Icon icon="ep:search" />
     <el-select
+      @click.stop
       filterable
       :reserve-keyword="false"
       remote
@@ -78,7 +79,12 @@
 
 function handleChange(path) {
   router.push({ path })
+  hiddenSearch()
   hiddenTopSearch()
+}
+
+function hiddenSearch() {
+  showSearch.value = false
 }
 
 function hiddenTopSearch() {
@@ -98,6 +104,8 @@
 // 监听 ctrl + k
 function listenKey(event) {
   if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
+    // 阻止触发浏览器默认事件
+    event.preventDefault()
     showSearch.value = !showSearch.value
     // 这里可以执行相应的操作(例如打开搜索框等)
   }
diff --git a/src/components/ShortcutDateRangePicker/index.vue b/src/components/ShortcutDateRangePicker/index.vue
index 117c079..78c5130 100644
--- a/src/components/ShortcutDateRangePicker/index.vue
+++ b/src/components/ShortcutDateRangePicker/index.vue
@@ -1,9 +1,9 @@
 <template>
   <div class="flex flex-row items-center gap-2">
     <el-radio-group v-model="shortcutDays" @change="handleShortcutDaysChange">
-      <el-radio-button :label="1">昨天</el-radio-button>
-      <el-radio-button :label="7">最近7天</el-radio-button>
-      <el-radio-button :label="30">最近30天</el-radio-button>
+      <el-radio-button :value="1">昨天</el-radio-button>
+      <el-radio-button :value="7">最近7天</el-radio-button>
+      <el-radio-button :value="30">最近30天</el-radio-button>
     </el-radio-group>
     <el-date-picker
       v-model="times"
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..4dfd51a
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/NodeHandler.vue
@@ -0,0 +1,231 @@
+<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 class="handler-item" @click="addNode(NodeType.DELAY_TIMER_NODE)">
+            <!-- TODO @芋艿 需要更换一下iconfont的图标 -->
+            <div class="handler-item-icon copy">
+              <span class="iconfont icon-size icon-copy"></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)
+  }
+  if (type === NodeType.DELAY_TIMER_NODE) {
+    const data: SimpleFlowNode = {
+      id: 'Activity_' + generateUUID(),
+      name: NODE_DEFAULT_NAME.get(NodeType.DELAY_TIMER_NODE) as string,
+      showText: '',
+      type: NodeType.DELAY_TIMER_NODE,
+      childNode: props.childNode
+    }
+    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..419501a
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue
@@ -0,0 +1,125 @@
+<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"
+  />
+  <!-- 延迟器节点 -->
+  <DelayTimerNode
+    v-if="currentNode && currentNode.type === NodeType.DELAY_TIMER_NODE"
+    :flow-node="currentNode"
+    @update:flow-node="handleModelValueUpdate"
+  />
+  <!-- 递归显示孩子节点  -->
+  <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 DelayTimerNode from './nodes/DelayTimerNode.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..22e6073
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/SimpleProcessDesigner.vue
@@ -0,0 +1,306 @@
+<template>
+  <div v-loading="loading" class="overflow-auto">
+    <SimpleProcessModel
+      ref="simpleProcessModelRef"
+      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', 'init-finished']) // 保存成功事件
+
+const props = defineProps({
+  modelId: {
+    type: String,
+    required: false
+  },
+  modelKey: {
+    type: String,
+    required: false
+  },
+  modelName: {
+    type: String,
+    required: false
+  },
+  // 可发起流程的人员编号
+  startUserIds : {
+    type: Array,
+    required: false
+  },
+  value: {
+    type: [String, Object],
+    required: false
+  }
+})
+
+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[]>([]) // 用户组列表
+
+// 添加当前值的引用
+const currentValue = ref<SimpleFlowNode | undefined>()
+
+provide('formFields', formFields)
+provide('formType', formType)
+provide('roleList', roleOptions)
+provide('postList', postOptions)
+provide('userList', userOptions)
+provide('deptList', deptOptions)
+provide('userGroupList', userGroupOptions)
+provide('deptTree', deptTreeOptions)
+provide('startUserIds', props.startUserIds)
+
+const message = useMessage() // 国际化
+const processNodeTree = ref<SimpleFlowNode | undefined>()
+const errorDialogVisible = ref(false)
+let errorNodes: SimpleFlowNode[] = []
+
+// 添加更新模型的方法
+const updateModel = () => {
+  if (!processNodeTree.value) {
+    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
+      }
+    }
+    // 初始化时也触发一次保存
+    saveSimpleFlowModel(processNodeTree.value)
+  }
+}
+
+// 加载流程数据
+const loadProcessData = async (data: any) => {
+  try {
+    if (data) {
+      const parsedData = typeof data === 'string' ? JSON.parse(data) : data
+      processNodeTree.value = parsedData
+      currentValue.value = parsedData
+      // 确保数据加载后刷新视图
+      await nextTick()
+      if (simpleProcessModelRef.value?.refresh) {
+        await simpleProcessModelRef.value.refresh()
+      }
+    }
+  } catch (error) {
+    console.error('加载流程数据失败:', error)
+  }
+}
+
+// 监听属性变化
+watch(
+  () => props.value,
+  async (newValue, oldValue) => {
+    if (newValue && newValue !== oldValue) {
+      await loadProcessData(newValue)
+    }
+  },
+  { immediate: true, deep: true }
+)
+
+// 监听流程节点树变化,自动保存
+watch(
+  () => processNodeTree.value,
+  async (newValue, oldValue) => {
+    if (newValue && oldValue && JSON.stringify(newValue) !== JSON.stringify(oldValue)) {
+      await saveSimpleFlowModel(newValue)
+    }
+  },
+  { deep: true }
+)
+
+const saveSimpleFlowModel = async (simpleModelNode: SimpleFlowNode) => {
+  if (!simpleModelNode) {
+    return
+  }
+
+  // 校验节点
+  errorNodes = []
+  validateNode(simpleModelNode, errorNodes)
+  if (errorNodes.length > 0) {
+    errorDialogVisible.value = true
+    return
+  }
+
+  try {
+    if (props.modelId) {
+      // 编辑模式
+      const data = {
+        id: props.modelId,
+        simpleModel: simpleModelNode
+      }
+      await updateBpmSimpleModel(data)
+    }
+    // 无论是编辑还是新建模式,都更新当前值并触发事件
+    currentValue.value = simpleModelNode
+    emits('success', simpleModelNode)
+  } catch (error) {
+    console.error('保存失败:', error)
+  }
+}
+
+// 校验节点设置。 暂时以 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
+    // 获取表单字段
+    if (props.modelId) {
+      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()
+
+    // 加载流程数据
+    if (props.modelId) {
+      // 获取 SIMPLE 设计器模型
+      const result = await getBpmSimpleModel(props.modelId)
+      if (result) {
+        await loadProcessData(result)
+      } else {
+        updateModel()
+      }
+    } else if (props.value) {
+      await loadProcessData(props.value)
+    } else {
+      updateModel()
+    }
+  } finally {
+    loading.value = false
+    emits('init-finished')
+  }
+})
+
+const simpleProcessModelRef = ref()
+
+/** 获取当前流程数据 */
+const getCurrentFlowData = async () => {
+  try {
+    if (simpleProcessModelRef.value) {
+      const data = await simpleProcessModelRef.value.getCurrentFlowData()
+      if (data) {
+        currentValue.value = data
+        return data
+      }
+    }
+    return currentValue.value
+  } catch (error) {
+    console.error('获取流程数据失败:', error)
+    return currentValue.value
+  }
+}
+
+// 刷新方法
+const refresh = async () => {
+  try {
+    if (currentValue.value) {
+      await loadProcessData(currentValue.value)
+    }
+  } catch (error) {
+    console.error('刷新失败:', error)
+  }
+}
+
+defineExpose({
+  getCurrentFlowData,
+  updateModel,
+  loadProcessData,
+  refresh
+})
+</script>
diff --git a/src/components/SimpleProcessDesignerV2/src/SimpleProcessModel.vue b/src/components/SimpleProcessDesignerV2/src/SimpleProcessModel.vue
new file mode 100644
index 0000000..ccd1f10
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/SimpleProcessModel.vue
@@ -0,0 +1,148 @@
+<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-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 { 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[] = []
+
+// 校验节点设置。 暂时以 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)
+    }
+  }
+}
+
+/** 获取当前流程数据 */
+const getCurrentFlowData = async () => {
+  try {
+    errorNodes = []
+    validateNode(processNodeTree.value, errorNodes)
+    if (errorNodes.length > 0) {
+      errorDialogVisible.value = true
+      return undefined
+    }
+    return processNodeTree.value
+  } catch (error) {
+    console.error('获取流程数据失败:', error)
+    return undefined
+  }
+}
+
+defineExpose({
+  getCurrentFlowData
+})
+</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..10d8a21
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/consts.ts
@@ -0,0 +1,606 @@
+// @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,
+
+  /**
+   * 延迟器节点
+   */
+  DELAY_TIMER_NODE = 14,
+
+  /**
+   * 条件节点
+   */
+  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
+  // 延迟设置
+  delaySetting?: DelaySetting
+}
+// 候选人策略枚举 ( 用于审批节点。抄送节点 )
+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, '请设置发起人')
+NODE_DEFAULT_TEXT.set(NodeType.DELAY_TIMER_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, '发起人')
+NODE_DEFAULT_NAME.set(NodeType.DELAY_TIMER_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'
+}
+
+/**
+ * 延迟设置
+ */
+export type DelaySetting = {
+  // 延迟类型
+  delayType: number
+  // 延迟时间表达式
+  delayTime: string
+}
+/**
+ * 延迟类型
+ */
+export enum DelayTypeEnum {
+  /**
+   * 固定时长
+   */
+  FIXED_TIME_DURATION = 1,
+  /**
+   * 固定日期时间
+   */
+  FIXED_DATE_TIME = 2
+}
+export const DELAY_TYPE = [
+  { label: '固定时长', value: DelayTypeEnum.FIXED_TIME_DURATION },
+  { label: '固定日期', value: DelayTypeEnum.FIXED_DATE_TIME }
+]
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..282e81b
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/node.ts
@@ -0,0 +1,510 @@
+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
+} 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)
+    })
+  }
+  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)
+    if (!nodeFormFields || nodeFormFields.length === 0) {
+      fieldsPermissionConfig.value = getDefaultFieldsPermission(unref(formFields))
+    } else {
+      fieldsPermissionConfig.value = mergeFieldsPermission(nodeFormFields, unref(formFields))
+    }
+  }
+  // 合并已经设置的表单字段权限,当前流程表单字段 (可能新增,或删除了字段)
+  const mergeFieldsPermission = (
+    formFieldsPermisson: Array<Record<string, string>>,
+    formFields?: string[]
+  ) => {
+    let mergedFieldsPermission: Array<Record<string, any>> = []
+    if (formFields) {
+      mergedFieldsPermission = parseFormCreateFields(formFields).map((item) => {
+        const found = formFieldsPermisson.find(
+          (fieldPermission) => fieldPermission.field == item.field
+        )
+        return {
+          field: item.field,
+          title: item.title,
+          permission: found ? found.permission : defaultPermission
+        }
+      })
+    }
+    return mergedFieldsPermission
+  }
+
+  // 默认的表单权限: 获取表单的所有字段,设置字段默认权限为只读
+  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..ae93172
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue
@@ -0,0 +1,429 @@
+<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 fieldOptions"
+                        :key="index"
+                        :label="item.title"
+                        :value="item.field"
+                        :disabled="!item.required"
+                      />
+                    </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,
+  ProcessVariableEnum
+} from '../consts'
+import { getDefaultConditionNodeName } from '../utils'
+import { useFormFields } from '../node'
+import { BpmModelFormType } from '@/utils/constants'
+const message = useMessage() // 消息弹窗
+defineOptions({
+  name: 'ConditionNodeConfig'
+})
+const formType = inject<Ref<number>>('formType') // 表单类型
+const conditionConfigTypes = computed(() => {
+  return CONDITION_CONFIG_TYPES.filter((item) => {
+    // 业务表单暂时去掉条件规则选项
+    if (formType?.value === BpmModelFormType.CUSTOM && item.value === ConditionType.RULE) {
+      return false
+    } else {
+      return true
+    }
+  })
+})
+
+const 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 fieldOptions = computed(() => {
+  const fieldsCopy = fieldsInfo.slice()
+  // 固定添加发起人 ID 字段
+  fieldsCopy.unshift({
+    field: ProcessVariableEnum.START_USER_ID,
+    title: '发起人',
+    required: true
+  })
+  return fieldsCopy
+})
+
+/** 获取字段名称 */
+const getFieldTitle = (field: string) => {
+  const item = fieldOptions.value.find((item) => item.field === field)
+  return item?.title
+}
+
+/** 获取操作符名称 */
+const getOpName = (opCode: string): string => {
+  const opName = COMPARISON_OPERATORS.find((item: any) => item.value === opCode)
+  return opName?.label
+}
+</script>
+
+<style lang="scss" scoped>
+.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/DelayTimerNodeConfig.vue b/src/components/SimpleProcessDesignerV2/src/nodes-config/DelayTimerNodeConfig.vue
new file mode 100644
index 0000000..27a351b
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/nodes-config/DelayTimerNodeConfig.vue
@@ -0,0 +1,189 @@
+<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>
+    <div>
+      <el-form ref="formRef" :model="configForm" label-position="top" :rules="formRules">
+        <el-form-item label="延迟时间" prop="delayType">
+          <el-radio-group v-model="configForm.delayType">
+            <el-radio-button
+              v-for="item in DELAY_TYPE"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item v-if="configForm.delayType === DelayTypeEnum.FIXED_TIME_DURATION">
+          <el-form-item prop="timeDuration">
+            <el-input-number
+              class="mr-2"
+              :style="{ width: '100px' }"
+              v-model="configForm.timeDuration"
+              :min="1"
+              controls-position="right"
+            />
+          </el-form-item>
+          <el-select v-model="configForm.timeUnit" class="mr-2" :style="{ width: '100px' }">
+            <el-option
+              v-for="item in TIME_UNIT_TYPES"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+          <el-text>后进入下一节点</el-text>
+        </el-form-item>
+        <el-form-item v-if="configForm.delayType === DelayTypeEnum.FIXED_DATE_TIME" prop="dateTime">
+          <el-date-picker
+            class="mr-2"
+            v-model="configForm.dateTime"
+            type="datetime"
+            placeholder="请选择日期和时间"
+            value-format="YYYY-MM-DDTHH:mm:ss"
+          />
+          <el-text>后进入下一节点</el-text>
+        </el-form-item>
+      </el-form>
+    </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,
+  NodeType,
+  TIME_UNIT_TYPES,
+  TimeUnitType,
+  DelayTypeEnum,
+  DELAY_TYPE
+} from '../consts'
+import { useWatchNode, useDrawer, useNodeName } from '../node'
+import { convertTimeUnit } from '../utils'
+defineOptions({
+  name: 'DelayTimerNodeConfig'
+})
+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.DELAY_TIMER_NODE)
+// 抄送人表单配置
+const formRef = ref() // 表单 Ref
+// 表单校验规则
+const formRules = reactive({
+  delayType: [{ required: true, message: '延迟时间不能为空', trigger: 'change' }],
+  timeDuration: [{ required: true, message: '延迟时间不能为空', trigger: 'change' }],
+  dateTime: [{ required: true, message: '延迟时间不能为空', trigger: 'change' }]
+})
+// 配置表单数据
+const configForm = ref({
+  delayType: DelayTypeEnum.FIXED_TIME_DURATION,
+  timeDuration: 1,
+  timeUnit: TimeUnitType.HOUR,
+  dateTime: ''
+})
+// 保存配置
+const saveConfig = async () => {
+  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 (configForm.value.delayType === DelayTypeEnum.FIXED_TIME_DURATION) {
+    currentNode.value.delaySetting = {
+      delayType: configForm.value.delayType,
+      delayTime: getIsoTimeDuration()
+    }
+  }
+  if (configForm.value.delayType === DelayTypeEnum.FIXED_DATE_TIME) {
+    currentNode.value.delaySetting = {
+      delayType: configForm.value.delayType,
+      delayTime: configForm.value.dateTime
+    }
+  }
+  settingVisible.value = false
+  return true
+}
+const getShowText = (): string => {
+  let showText = ''
+  if (configForm.value.delayType === DelayTypeEnum.FIXED_TIME_DURATION) {
+    showText = `延迟${configForm.value.timeDuration}${TIME_UNIT_TYPES.find((item) => item.value === configForm.value.timeUnit).label}`
+  }
+  if (configForm.value.delayType === DelayTypeEnum.FIXED_DATE_TIME) {
+    showText = `延迟至${configForm.value.dateTime.replace('T', ' ')}`
+  }
+  return showText
+}
+const getIsoTimeDuration = () => {
+  let strTimeDuration = 'PT'
+  if (configForm.value.timeUnit === TimeUnitType.MINUTE) {
+    strTimeDuration += configForm.value.timeDuration + 'M'
+  }
+  if (configForm.value.timeUnit === TimeUnitType.HOUR) {
+    strTimeDuration += configForm.value.timeDuration + 'H'
+  }
+  if (configForm.value.timeUnit === TimeUnitType.DAY) {
+    strTimeDuration += configForm.value.timeDuration + 'D'
+  }
+  return strTimeDuration
+}
+// 显示延迟器节点配置, 由父组件传过来
+const showDelayTimerNodeConfig = (node: SimpleFlowNode) => {
+  nodeName.value = node.name
+  if (node.delaySetting) {
+    configForm.value.delayType = node.delaySetting.delayType
+    // 固定时长
+    if (configForm.value.delayType === DelayTypeEnum.FIXED_TIME_DURATION) {
+      const strTimeDuration = node.delaySetting.delayTime
+      let parseTime = strTimeDuration.slice(2, strTimeDuration.length - 1)
+      let parseTimeUnit = strTimeDuration.slice(strTimeDuration.length - 1)
+      configForm.value.timeDuration = parseInt(parseTime)
+      configForm.value.timeUnit = convertTimeUnit(parseTimeUnit)
+    }
+    // 固定日期时间
+    if (configForm.value.delayType === DelayTypeEnum.FIXED_DATE_TIME) {
+      configForm.value.dateTime = node.delaySetting.delayTime
+    }
+  }
+}
+
+defineExpose({ openDrawer, showDelayTimerNodeConfig }) // 暴露方法给父组件
+</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..26c8e13
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/nodes-config/StartUserNodeConfig.vue
@@ -0,0 +1,163 @@
+<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">
+        <el-text v-if="!startUserIds || startUserIds.length === 0"> 全部成员可以发起流程 </el-text>
+        <el-text v-else-if="startUserIds.length == 1">
+          {{ getUserNicknames(startUserIds) }} 可发起流程
+        </el-text>
+        <el-text v-else>
+          <el-tooltip
+            class="box-item"
+            effect="dark"
+            placement="top"
+            :content="getUserNicknames(startUserIds)"
+          >
+            {{ getUserNicknames(startUserIds.slice(0,2)) }} 等 {{ startUserIds.length }} 人可发起流程
+          </el-tooltip>
+        </el-text>
+      </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'
+import * as UserApi from '@/api/system/user'
+defineOptions({
+  name: 'StartUserNodeConfig'
+})
+const props = defineProps({
+  flowNode: {
+    type: Object as () => SimpleFlowNode,
+    required: true
+  }
+})
+// 可发起流程的用户编号
+const startUserIds = inject<Ref<any[]>>('startUserIds')
+// 用户列表
+const userOptions = inject<Ref<UserApi.UserVO[]>>('userList')
+// 抽屉配置
+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 getUserNicknames = (userIds: number[]): string => {
+  if (!userIds || userIds.length === 0) {
+    return ''
+  }
+  const nicknames: string[] = []
+  userIds.forEach((userId) => {
+    const found = userOptions?.value.find((item) => item.id === userId)
+    if (found && found.nickname) {
+      nicknames.push(found.nickname)
+    }
+  })
+  return nicknames.join(',')
+}
+// 保存配置
+const saveConfig = async () => {
+  activeTabName.value = 'user'
+  currentNode.value.name = nodeName.value!
+  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..fb5e780
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/nodes-config/UserTaskNodeConfig.vue
@@ -0,0 +1,913 @@
+<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,
+  ProcessVariableEnum
+} 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(() => {
+  // 固定添加发起人 ID 字段
+  formFieldOptions.unshift({
+    field: ProcessVariableEnum.START_USER_ID,
+    title: '发起人',
+    type: 'UserSelect',
+    required: true
+  })
+  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/DelayTimerNode.vue b/src/components/SimpleProcessDesignerV2/src/nodes/DelayTimerNode.vue
new file mode 100644
index 0000000..94f9c41
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/nodes/DelayTimerNode.vue
@@ -0,0 +1,98 @@
+<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">
+          <!-- TODO @芋艿 需要更换图标 -->
+          <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.DELAY_TIMER_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>
+    <DelayTimerNodeConfig
+      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 DelayTimerNodeConfig from '../nodes-config/DelayTimerNodeConfig.vue'
+defineOptions({
+  name: 'DelayTimerNode'
+})
+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.DELAY_TIMER_NODE)
+
+const nodeSetting = ref()
+// 打开节点配置
+const openNodeConfig = () => {
+  if (readonly) {
+    return
+  }
+  nodeSetting.value.showDelayTimerNodeConfig(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..8cf2681
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss
@@ -0,0 +1,755 @@
+// 配置节点头部
+.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;
+  overflow-x: auto;
+  width: 100%;
+
+  .simple-process-model {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    transform-origin: 50% 0 0;
+    min-width: fit-content;
+    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;
+        min-width: fit-content;
+
+        &::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;
+          flex-shrink: 0;
+
+          &::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/UploadFile/src/UploadFile.vue b/src/components/UploadFile/src/UploadFile.vue
index 3beb377..9d0a904 100644
--- a/src/components/UploadFile/src/UploadFile.vue
+++ b/src/components/UploadFile/src/UploadFile.vue
@@ -1,5 +1,5 @@
 <template>
-  <div class="upload-file">
+  <div v-if="!disabled" class="upload-file">
     <el-upload
       ref="uploadRef"
       v-model:file-list="fileList"
@@ -20,11 +20,11 @@
       class="upload-file-uploader"
       name="file"
     >
-      <el-button v-if="!disabled" type="primary">
+      <el-button type="primary">
         <Icon icon="ep:upload-filled" />
         选取文件
       </el-button>
-      <template v-if="isShowTip && !disabled" #tip>
+      <template v-if="isShowTip" #tip>
         <div style="font-size: 8px">
           大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
         </div>
@@ -32,7 +32,6 @@
           格式为 <b style="color: #f56c6c">{{ fileType.join('/') }}</b> 的文件
         </div>
       </template>
-      <!-- TODO @puhui999:1)表单展示的时候,位置会偏掉,已发微信;2)disable 的时候,应该把【删除】按钮也隐藏掉? -->
       <template #file="row">
         <div class="flex items-center">
           <span>{{ row.file.name }}</span>
@@ -53,6 +52,18 @@
         </div>
       </template>
     </el-upload>
+  </div>
+
+  <!-- 上传操作禁用时 -->
+  <div v-if="disabled" class="upload-file">
+    <div v-for="(file, index) in fileList" :key="index" class="flex items-center file-list-item">
+      <span>{{ file.name }}</span>
+      <div class="ml-10px">
+        <el-link :href="file.url" :underline="false" download target="_blank" type="primary">
+          下载
+        </el-link>
+      </div>
+    </div>
   </div>
 </template>
 <script lang="ts" setup>
@@ -211,4 +222,9 @@
 :deep(.ele-upload-list__item-content-action .el-link) {
   margin-right: 10px;
 }
+
+.file-list-item {
+  border: 1px dashed var(--el-border-color-darker);
+  border-radius: 8px;
+}
 </style>
diff --git a/src/components/UploadFile/src/UploadImgs.vue b/src/components/UploadFile/src/UploadImgs.vue
index 85da64c..59857a9 100644
--- a/src/components/UploadFile/src/UploadImgs.vue
+++ b/src/components/UploadFile/src/UploadImgs.vue
@@ -25,7 +25,7 @@
       <template #file="{ file }">
         <img :src="file.url" class="upload-image" />
         <div class="upload-handle" @click.stop>
-          <div class="handle-icon" @click="handlePictureCardPreview(file)">
+          <div class="handle-icon" @click="imagePreview(file.url!)">
             <Icon icon="ep:zoom-in" />
             <span>查看</span>
           </div>
@@ -39,16 +39,12 @@
     <div class="el-upload__tip">
       <slot name="tip"></slot>
     </div>
-    <el-image-viewer
-      v-if="imgViewVisible"
-      :url-list="[viewImageUrl]"
-      @close="imgViewVisible = false"
-    />
   </div>
 </template>
 <script lang="ts" setup>
 import type { UploadFile, UploadProps, UploadUserFile } from 'element-plus'
 import { ElNotification } from 'element-plus'
+import { createImageViewer } from '@/components/ImageViewer'
 
 import { propTypes } from '@/utils/propTypes'
 import { useUpload } from '@/components/UploadFile/src/useUpload'
@@ -56,6 +52,13 @@
 defineOptions({ name: 'UploadImgs' })
 
 const message = useMessage() // 消息弹窗
+// 查看图片
+const imagePreview = (imgUrl: string) => {
+  createImageViewer({
+    zIndex: 9999999,
+    urlList: [imgUrl]
+  })
+}
 
 type FileTypes =
   | 'image/apng'
@@ -177,14 +180,6 @@
     message: `当前最多只能上传 ${props.limit} 张图片,请移除后上传!`,
     type: 'warning'
   })
-}
-
-// 图片预览
-const viewImageUrl = ref('')
-const imgViewVisible = ref(false)
-const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
-  viewImageUrl.value = uploadFile.url!
-  imgViewVisible.value = true
 }
 </script>
 
diff --git a/src/components/UploadFile/src/useUpload.ts b/src/components/UploadFile/src/useUpload.ts
index c0465a2..c846acb 100644
--- a/src/components/UploadFile/src/useUpload.ts
+++ b/src/components/UploadFile/src/useUpload.ts
@@ -3,9 +3,16 @@
 import { UploadRawFile, UploadRequestOptions } from 'element-plus/es/components/upload/src/upload'
 import axios from 'axios'
 
+/**
+ * 获得上传 URL
+ */
+export const getUploadUrl = (): string => {
+  return import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/infra/file/upload'
+}
+
 export const useUpload = () => {
   // 后端上传地址
-  const uploadUrl = import.meta.env.VITE_UPLOAD_URL
+  const uploadUrl = getUploadUrl()
   // 是否使用前端直连上传
   const isClientUpload = UPLOAD_TYPE.CLIENT === import.meta.env.VITE_UPLOAD_TYPE
   // 重写ElUpload上传方法
@@ -17,16 +24,18 @@
       // 1.2 获取文件预签名地址
       const presignedInfo = await FileApi.getFilePresignedUrl(fileName)
       // 1.3 上传文件(不能使用 ElUpload 的 ajaxUpload 方法的原因:其使用的是 FormData 上传,Minio 不支持)
-      return axios.put(presignedInfo.uploadUrl, options.file, {
-        headers: {
-          'Content-Type': options.file.type,
-        }
-      }).then(() => {
-        // 1.4. 记录文件信息到后端(异步)
-        createFile(presignedInfo, fileName, options.file)
-        // 通知成功,数据格式保持与后端上传的返回结果一致
-        return { data: presignedInfo.url }
-      })
+      return axios
+        .put(presignedInfo.uploadUrl, options.file, {
+          headers: {
+            'Content-Type': options.file.type
+          }
+        })
+        .then(() => {
+          // 1.4. 记录文件信息到后端(异步)
+          createFile(presignedInfo, fileName, options.file)
+          // 通知成功,数据格式保持与后端上传的返回结果一致
+          return { data: presignedInfo.url }
+        })
     } else {
       // 模式二:后端上传
       // 重写 el-upload httpRequest 文件上传成功会走成功的钩子,失败走失败的钩子
@@ -92,6 +101,4 @@
 enum UPLOAD_TYPE {
   // 客户端直接上传(只支持S3服务)
   CLIENT = 'client',
-  // 客户端发送到后端上传
-  SERVER = 'server'
 }
diff --git a/src/components/UserSelectForm/index.vue b/src/components/UserSelectForm/index.vue
new file mode 100644
index 0000000..5ed99f8
--- /dev/null
+++ b/src/components/UserSelectForm/index.vue
@@ -0,0 +1,171 @@
+<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, 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 deptList = ref<any[]>([]) // 保存扁平化的部门列表数据
+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()
+
+  // 加载部门、用户列表
+  const deptData = await DeptApi.getSimpleDeptList()
+  deptList.value = deptData // 保存扁平结构的部门数据
+  deptTree.value = handleTree(deptData) // 转换成树形结构
+  userList.value = await UserApi.getSimpleUserList()
+
+  // 初始状态下,过滤列表等于所有用户列表
+  filteredUserList.value = [...userList.value]
+  selectedUserIdList.value = selectedList?.map((item: any) => item.id) || []
+  dialogVisible.value = true
+}
+
+/** 获取指定部门及其所有子部门的ID列表 */
+const getChildDeptIds = (deptId: number, deptList: any[]): number[] => {
+  const ids = [deptId]
+  const children = deptList.filter((dept) => dept.parentId === deptId)
+  children.forEach((child) => {
+    ids.push(...getChildDeptIds(child.id, deptList))
+  })
+  return ids
+}
+
+/** 获取部门过滤后的用户列表 */
+const filterUserList = async (deptId?: number) => {
+  formLoading.value = true
+  try {
+    if (!deptId) {
+      // 如果没有选择部门,显示所有用户
+      filteredUserList.value = [...userList.value]
+      return
+    }
+
+    // 直接使用已保存的部门列表数据进行过滤
+    const deptIds = getChildDeptIds(deptId, deptList.value)
+
+    // 过滤出这些部门下的用户
+    filteredUserList.value = userList.value.filter((user) => deptIds.includes(user.deptId))
+  } 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 = []
+  deptList.value = []
+  userList.value = []
+  filteredUserList.value = []
+  selectedUserIdList.value = []
+}
+
+/** 处理部门被点击 */
+const handleNodeClick = (row: { [key: string]: any }) => {
+  filterUserList(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..9d2fa5b 100644
--- a/src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue
+++ b/src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue
@@ -160,13 +160,6 @@
             <XButton preIcon="ep:refresh" @click="processRestart()" />
           </el-tooltip>
         </ElButtonGroup>
-        <XButton
-          preIcon="ep:plus"
-          title="保存模型"
-          @click="processSave"
-          :type="props.headerButtonType"
-          :disabled="simulationStatus"
-        />
       </template>
       <!-- 用于打开本地文件-->
       <input
@@ -314,6 +307,28 @@
       ['default', 'primary', 'success', 'warning', 'danger', 'info'].indexOf(value) !== -1
   }
 })
+
+// 监听value变化,重新加载流程图
+watch(
+  () => props.value,
+  (newValue) => {
+    if (newValue && bpmnModeler) {
+      createNewDiagram(newValue)
+    }
+  },
+  { immediate: true }
+)
+
+// 监听processId和processName变化
+watch(
+  [() => props.processId, () => props.processName],
+  ([newId, newName]) => {
+    if (newId && newName && !props.value) {
+      createNewDiagram(null)
+    }
+  },
+  { immediate: true }
+)
 
 provide('configGlobal', props)
 let bpmnModeler: any = null
@@ -592,16 +607,6 @@
   defaultZoom.value = newZoom
   bpmnModeler.get('canvas').zoom(defaultZoom.value)
 }
-// const processZoomTo = (newZoom = 1) => {
-//   if (newZoom < 0.2) {
-//     throw new Error('[Process Designer Warn ]: The zoom ratio cannot be less than 0.2')
-//   }
-//   if (newZoom > 4) {
-//     throw new Error('[Process Designer Warn ]: The zoom ratio cannot be greater than 4')
-//   }
-//   defaultZoom = newZoom
-//   bpmnModeler.get('canvas').zoom(newZoom)
-// }
 const processReZoom = () => {
   defaultZoom.value = 1
   bpmnModeler.get('canvas').zoom('fit-viewport', 'auto')
@@ -640,63 +645,19 @@
 }
 const previewProcessJson = () => {
   bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
-    // console.log(xml, 'xml')
-
-    // const rootNode = parseXmlString(xml)
-    // console.log(rootNode, 'rootNoderootNode')
     const rootNodes = new XmlNode(XmlNodeType.Root, parseXmlString(xml))
-    // console.log(rootNodes, 'rootNodesrootNodesrootNodes')
-    // console.log(rootNodes.parent.toJsObject(), 'rootNodes.toJSON()')
-    // console.log(JSON.stringify(rootNodes.parent.toJsObject()), 'rootNodes.toJSON()')
-    // console.log(JSON.stringify(rootNodes.parent.toJSON()), 'rootNodes.toJSON()')
-
-    // const parser = new xml2js.XMLParser()
-    // let jObj = parser.parse(xml)
-    // console.log(jObj, 'jObjjObjjObjjObjjObj')
-    // const builder = new xml2js.XMLBuilder(xml)
-    // const xmlContent = builder
-    // console.log(xmlContent, 'xmlContent')
-    // console.log(xml2js, 'convertconvertconvert')
     previewResult.value = rootNodes.parent?.toJSON() as unknown as string
-    // previewResult.value = jObj
-    // previewResult.value = convert.xml2json(xml,  {explicitArray : false},{ spaces: 2 })
     previewType.value = 'json'
     previewModelVisible.value = true
   })
 }
-/* ------------------------------------------------ 工业互联网平台 methods ------------------------------------------------------ */
-const processSave = async () => {
-  // console.log(bpmnModeler, 'bpmnModelerbpmnModelerbpmnModelerbpmnModeler')
-  const { err, xml } = await bpmnModeler.saveXML()
-  // console.log(err, 'errerrerrerrerr')
-  // console.log(xml, 'xmlxmlxmlxmlxml')
-  // 读取异常时抛出异常
-  if (err) {
-    // this.$modal.msgError('保存模型失败,请重试!')
-    alert('保存模型失败,请重试!')
-    return
-  }
-  // 触发 save 事件
-  emit('save', xml)
-}
-/** 高亮显示 */
-// const highlightedCode = (previewType, previewResult) => {
-//   console.log(previewType, 'previewType, previewResult')
-//   console.log(previewResult, 'previewType, previewResult')
-//   console.log(hljs.highlight, 'hljs.highlight')
-//   const result = hljs.highlight(previewType, previewResult.value || '', true)
-//   return result.value || '&nbsp;'
-// }
-onBeforeMount(() => {
-  console.log(props, 'propspropspropsprops')
-})
+
+/* ------------------------------------------------ 芋道源码 methods ------------------------------------------------------ */
 onMounted(() => {
   initBpmnModeler()
   createNewDiagram(props.value)
 })
 onBeforeUnmount(() => {
-  // this.$once('hook:beforeDestroy', () => {
-  // })
   if (bpmnModeler) bpmnModeler.destroy()
   emit('destroy', bpmnModeler)
   bpmnModeler = null
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..7fe1fa7 100644
--- a/src/components/bpmnProcessDesigner/package/designer/plugins/descriptor/flowableDescriptor.json
+++ b/src/components/bpmnProcessDesigner/package/designer/plugins/descriptor/flowableDescriptor.json
@@ -406,6 +406,31 @@
           "name": "variableMappingDelegateExpression",
           "isAttr": true,
           "type": "String"
+        },
+        {
+          "name": "calledElementType",
+          "isAttr": true,
+          "type": "String"
+        },
+        {
+          "name": "processInstanceName",
+          "isAttr": true,
+          "type": "String"
+        },
+        {
+          "name": "inheritBusinessKey",
+          "isAttr": true,
+          "type": "Boolean"
+        },
+        {
+          "name": "businessKey",
+          "isAttr": true,
+          "type": "String"
+        },
+        {
+          "name": "inheritVariables",
+          "isAttr": true,
+          "type": "Boolean"
         }
       ]
     },
@@ -1211,6 +1236,208 @@
           "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
+        }
+      ]
+    },
+    {
+      "name": "ButtonsSetting",
+      "superClass": ["Element"],
+      "meta": {
+        "allowedIn": ["bpmn:UserTask"]
+      },
+      "properties": [
+        {
+          "name": "flowable:id",
+          "type": "Integer",
+          "isAttr": true
+        },
+        {
+          "name": "flowable:enable",
+          "type": "Boolean",
+          "isAttr": true
+        },
+        {
+          "name": "flowable:displayName",
+          "type": "String",
+          "isAttr": true
+        }
+      ]
+    },
+    {
+      "name": "FieldsPermission",
+      "superClass": ["Element"],
+      "meta": {
+        "allowedIn": ["bpmn:UserTask"]
+      },
+      "properties": [
+        {
+          "name": "flowable:field",
+          "type": "String",
+          "isAttr": true
+        },
+        {
+          "name": "flowable:title",
+          "type": "String",
+          "isAttr": true
+        },
+        {
+          "name": "flowable:permission",
+          "type": "String",
+          "isAttr": true
+        }
+      ]
+    },
+    {
+      "name": "BoundaryEventType",
+      "superClass": ["Element"],
+      "meta": {
+        "allowedIn": ["bpmn:BoundaryEvent"]
+      },
+      "properties": [
+        {
+          "name": "value",
+          "type": "Integer",
+          "isBody": true
+        }
+      ]
+    },
+    {
+      "name": "TimeoutHandlerType",
+      "superClass": ["Element"],
+      "meta": {
+        "allowedIn": ["bpmn:BoundaryEvent"]
+      },
+      "properties": [
+        {
+          "name": "value",
+          "type": "Integer",
+          "isBody": true
+        }
+      ]
+    },
+    {
+      "name": "ApproveType",
+      "superClass": ["Element"],
+      "meta": {
+        "allowedIn": ["bpmn:UserTask"]
+      },
+      "properties": [
+        {
+          "name": "value",
+          "type": "Integer",
+          "isBody": true
+        }
+      ]
+    },
+    {
+      "name": "ApproveMethod",
+      "superClass": ["Element"],
+      "meta": {
+        "allowedIn": ["bpmn:UserTask"]
+      },
+      "properties": [
+        {
+          "name": "value",
+          "type": "Integer",
+          "isBody": true
+        }
+      ]
+    },
+    {
+      "name": "CandidateStrategy",
+      "superClass": ["Element"],
+      "meta": {
+        "allowedIn": ["bpmn:UserTask"]
+      },
+      "properties": [
+        {
+          "name": "value",
+          "type": "Integer",
+          "isBody": true
+        }
+      ]
+    },
+    {
+      "name": "CandidateParam",
+      "superClass": ["Element"],
+      "meta": {
+        "allowedIn": ["bpmn:UserTask"]
+      },
+      "properties": [
+        {
+          "name": "value",
+          "type": "String",
+          "isBody": true
+        }
+      ]
     }
   ],
   "emumerations": []
diff --git a/src/components/bpmnProcessDesigner/package/designer/plugins/palette/CustomPalette.js b/src/components/bpmnProcessDesigner/package/designer/plugins/palette/CustomPalette.js
index 5e2803b..788e4d1 100644
--- a/src/components/bpmnProcessDesigner/package/designer/plugins/palette/CustomPalette.js
+++ b/src/components/bpmnProcessDesigner/package/designer/plugins/palette/CustomPalette.js
@@ -165,6 +165,18 @@
       'bpmn-icon-user-task',
       translate('Create User Task')
     ),
+    'create.call-activity': createAction(
+      'bpmn:CallActivity',
+      'activity',
+      'bpmn-icon-call-activity',
+      translate('Create Call Activity')
+    ),
+    'create.service-task': createAction(
+      'bpmn:ServiceTask',
+      'activity',
+      'bpmn-icon-service',
+      translate('Create Service Task')
+    ),
     'create.data-object': createAction(
       'bpmn:DataObjectReference',
       'data-object',
diff --git a/src/components/bpmnProcessDesigner/package/designer/plugins/palette/paletteProvider.js b/src/components/bpmnProcessDesigner/package/designer/plugins/palette/paletteProvider.js
index 7098981..304875c 100644
--- a/src/components/bpmnProcessDesigner/package/designer/plugins/palette/paletteProvider.js
+++ b/src/components/bpmnProcessDesigner/package/designer/plugins/palette/paletteProvider.js
@@ -171,6 +171,12 @@
       'bpmn-icon-user-task',
       translate('Create User Task')
     ),
+    'create.service-task': createAction(
+      'bpmn:ServiceTask',
+      'activity',
+      'bpmn-icon-service',
+      translate('Create Service Task')
+    ),
     'create.data-object': createAction(
       'bpmn:DataObjectReference',
       'data-object',
diff --git a/src/components/bpmnProcessDesigner/package/designer/plugins/translate/zh.js b/src/components/bpmnProcessDesigner/package/designer/plugins/translate/zh.js
index 777db3e..cb92041 100644
--- a/src/components/bpmnProcessDesigner/package/designer/plugins/translate/zh.js
+++ b/src/components/bpmnProcessDesigner/package/designer/plugins/translate/zh.js
@@ -56,6 +56,8 @@
   'Create EndEvent': '创建结束事件',
   'Create Task': '创建任务',
   'Create User Task': '创建用户任务',
+  'Create Call Activity': '创建调用活动',
+  'Create Service Task': '创建服务任务',
   'Create Gateway': '创建网关',
   'Create DataObjectReference': '创建数据对象',
   'Create DataStoreReference': '创建数据存储',
diff --git a/src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue b/src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue
index 86a1cf7..e426eb6 100644
--- a/src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue
+++ b/src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue
@@ -1,6 +1,6 @@
 <template>
   <div class="process-panel__container" :style="{ width: `${width}px` }">
-    <el-collapse v-model="activeTab">
+    <el-collapse v-model="activeTab" v-if="isReady">
       <el-collapse-item name="base">
         <!-- class="panel-tab__title" -->
         <template #title>
@@ -26,8 +26,10 @@
         <template #title><Icon icon="ep:list" />表单</template>
         <element-form :id="elementId" :type="elementType" />
       </el-collapse-item>
-      <el-collapse-item name="task" v-if="elementType.indexOf('Task') !== -1" key="task">
-        <template #title><Icon icon="ep:checked" />任务(审批人)</template>
+      <el-collapse-item name="task" v-if="isTaskCollapseItemShow(elementType)" key="task">
+        <template #title
+        ><Icon icon="ep:checked" />{{ getTaskCollapseItemName(elementType) }}</template
+        >
         <element-task :id="elementId" :type="elementType" />
       </el-collapse-item>
       <el-collapse-item
@@ -35,8 +37,12 @@
         v-if="elementType.indexOf('Task') !== -1"
         key="multiInstance"
       >
-        <template #title><Icon icon="ep:help-filled" />多实例(会签配置)</template>
-        <element-multi-instance :business-object="elementBusinessObject" :type="elementType" />
+        <template #title><Icon icon="ep:help-filled" />多人审批方式</template>
+        <element-multi-instance
+          :id="elementId"
+          :business-object="elementBusinessObject"
+          :type="elementType"
+        />
       </el-collapse-item>
       <el-collapse-item name="listeners" key="listeners">
         <template #title><Icon icon="ep:bell-filled" />执行监听器</template>
@@ -54,6 +60,14 @@
         <template #title><Icon icon="ep:promotion" />其他</template>
         <element-other-config :id="elementId" />
       </el-collapse-item>
+      <el-collapse-item name="customConfig" key="customConfig">
+        <template #title><Icon icon="ep:tools" />自定义配置</template>
+        <element-custom-config
+          :id="elementId"
+          :type="elementType"
+          :business-object="elementBusinessObject"
+        />
+      </el-collapse-item>
     </el-collapse>
   </div>
 </template>
@@ -68,6 +82,7 @@
 import ElementProperties from './properties/ElementProperties.vue'
 // import ElementForm from './form/ElementForm.vue'
 import UserTaskListeners from './listeners/UserTaskListeners.vue'
+import { getTaskCollapseItemName, isTaskCollapseItemShow } from './task/data'
 
 defineOptions({ name: 'MyPropertiesPanel' })
 
@@ -104,24 +119,16 @@
 const conditionFormVisible = ref(false) // 流转条件设置
 const formVisible = ref(false) // 表单配置
 const bpmnElement = ref()
+const isReady = ref(false)
 
 provide('prefix', props.prefix)
 provide('width', props.width)
-const bpmnInstances = () => (window as any)?.bpmnInstances
 
-// 监听 props.bpmnModeler 然后 initModels
-const unwatchBpmn = watch(
-  () => props.bpmnModeler,
-  () => {
-    // 避免加载时 流程图 并未加载完成
-    if (!props.bpmnModeler) {
-      console.log('缺少props.bpmnModeler')
-      return
-    }
-
-    console.log('props.bpmnModeler 有值了!!!')
-    const w = window as any
-    w.bpmnInstances = {
+// 初始化 bpmnInstances
+const initBpmnInstances = () => {
+  if (!props.bpmnModeler) return false
+  try {
+    const instances = {
       modeler: props.bpmnModeler,
       modeling: props.bpmnModeler.get('modeling'),
       moddle: props.bpmnModeler.get('moddle'),
@@ -133,9 +140,45 @@
       selection: props.bpmnModeler.get('selection')
     }
 
-    console.log(bpmnInstances(), 'window.bpmnInstances')
-    getActiveElement()
-    unwatchBpmn()
+    // 检查所有实例是否都存在
+    const allInstancesExist = Object.values(instances).every(instance => instance)
+    if (allInstancesExist) {
+      const w = window as any
+      w.bpmnInstances = instances
+      return true
+    }
+    return false
+  } catch (error) {
+    console.error('初始化 bpmnInstances 失败:', error)
+    return false
+  }
+}
+
+const bpmnInstances = () => (window as any)?.bpmnInstances
+
+// 监听 props.bpmnModeler 然后 initModels
+const unwatchBpmn = watch(
+  () => props.bpmnModeler,
+  async () => {
+    // 避免加载时 流程图 并未加载完成
+    if (!props.bpmnModeler) {
+      console.log('缺少props.bpmnModeler')
+      return
+    }
+
+    try {
+      // 等待 modeler 初始化完成
+      await nextTick()
+      if (initBpmnInstances()) {
+        isReady.value = true
+        await nextTick()
+        getActiveElement()
+      } else {
+        console.error('modeler 实例未完全初始化')
+      }
+    } catch (error) {
+      console.error('初始化失败:', error)
+    }
   },
   {
     immediate: true
@@ -143,6 +186,8 @@
 )
 
 const getActiveElement = () => {
+  if (!isReady.value || !props.bpmnModeler) return
+
   // 初始第一个选中元素 bpmn:Process
   initFormOnChanged(null)
   props.bpmnModeler.on('import.done', (e) => {
@@ -160,8 +205,11 @@
     }
   })
 }
+
 // 初始化数据
 const initFormOnChanged = (element) => {
+  if (!isReady.value || !bpmnInstances()) return
+
   let activatedElement = element
   if (!activatedElement) {
     activatedElement =
@@ -169,32 +217,36 @@
       bpmnInstances().elementRegistry.find((el) => el.type === 'bpmn:Collaboration')
   }
   if (!activatedElement) return
-  console.log(`
-              ----------
-      select element changed:
-                id:  ${activatedElement.id}
-              type:  ${activatedElement.businessObject.$type}
-              ----------
-              `)
-  console.log('businessObject: ', activatedElement.businessObject)
-  bpmnInstances().bpmnElement = activatedElement
-  bpmnElement.value = activatedElement
-  elementId.value = activatedElement.id
-  elementType.value = activatedElement.type.split(':')[1] || ''
-  elementBusinessObject.value = JSON.parse(JSON.stringify(activatedElement.businessObject))
-  conditionFormVisible.value = !!(
-    elementType.value === 'SequenceFlow' &&
-    activatedElement.source &&
-    activatedElement.source.type.indexOf('StartEvent') === -1
-  )
-  formVisible.value = elementType.value === 'UserTask' || elementType.value === 'StartEvent'
+
+  try {
+    console.log(`
+                ----------
+        select element changed:
+                  id:  ${activatedElement.id}
+                type:  ${activatedElement.businessObject.$type}
+                ----------
+                `)
+    console.log('businessObject: ', activatedElement.businessObject)
+    bpmnInstances().bpmnElement = activatedElement
+    bpmnElement.value = activatedElement
+    elementId.value = activatedElement.id
+    elementType.value = activatedElement.type.split(':')[1] || ''
+    elementBusinessObject.value = JSON.parse(JSON.stringify(activatedElement.businessObject))
+    conditionFormVisible.value = !!(
+      elementType.value === 'SequenceFlow' &&
+      activatedElement.source &&
+      activatedElement.source.type.indexOf('StartEvent') === -1
+    )
+    formVisible.value = elementType.value === 'UserTask' || elementType.value === 'StartEvent'
+  } catch (error) {
+    console.error('初始化表单数据失败:', error)
+  }
 }
 
 onBeforeUnmount(() => {
   const w = window as any
   w.bpmnInstances = null
-  console.log(props, 'props1')
-  console.log(props.bpmnModeler, 'props.bpmnModeler1')
+  isReady.value = false
 })
 
 watch(
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..f9cb9ac
--- /dev/null
+++ b/src/components/bpmnProcessDesigner/package/penal/custom-config/ElementCustomConfig.vue
@@ -0,0 +1,39 @@
+<template>
+  <div class="panel-tab__content">
+    <component :is="customConfigComponent" v-bind="$props" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { CustomConfigMap } from './data'
+
+defineOptions({ name: 'ElementCustomConfig' })
+
+const props = defineProps({
+  id: String,
+  type: String,
+  businessObject: {
+    type: Object,
+    default: () => {}
+  }
+})
+
+const bpmnInstances = () => (window as any)?.bpmnInstances
+const customConfigComponent = ref<any>(null)
+
+watch(
+  () => props.businessObject,
+  () => {
+    if (props.type && props.businessObject) {
+      let val = props.type
+      if (props.businessObject.eventDefinitions) {
+        val += props.businessObject.eventDefinitions[0]?.$type.split(':')[1] || ''
+      }
+      customConfigComponent.value = CustomConfigMap[val]?.componet
+    }
+  },
+  { immediate: true }
+)
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/components/bpmnProcessDesigner/package/penal/custom-config/components/BoundaryEventTimer.vue b/src/components/bpmnProcessDesigner/package/penal/custom-config/components/BoundaryEventTimer.vue
new file mode 100644
index 0000000..ca46b27
--- /dev/null
+++ b/src/components/bpmnProcessDesigner/package/penal/custom-config/components/BoundaryEventTimer.vue
@@ -0,0 +1,252 @@
+<template>
+  <div>
+    <el-divider content-position="left">审批人超时未处理时</el-divider>
+    <el-form-item label="启用开关" prop="timeoutHandlerEnable">
+      <el-switch
+        v-model="timeoutHandlerEnable"
+        active-text="开启"
+        inactive-text="关闭"
+        @change="timeoutHandlerChange"
+      />
+    </el-form-item>
+    <el-form-item label="执行动作" prop="timeoutHandlerType" v-if="timeoutHandlerEnable">
+      <el-radio-group v-model="timeoutHandlerType.value" @change="onTimeoutHandlerTypeChanged">
+        <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="timeoutHandlerEnable">
+      <span class="mr-2">当超过</span>
+      <el-form-item prop="timeDuration">
+        <el-input-number
+          class="mr-2"
+          :style="{ width: '100px' }"
+          v-model="timeDuration"
+          :min="1"
+          controls-position="right"
+          @change="() => updateTimeModdle()"
+        />
+      </el-form-item>
+      <el-select
+        v-model="timeUnit"
+        class="mr-2"
+        :style="{ width: '100px' }"
+        @change="onTimeUnitChange"
+      >
+        <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="timeoutHandlerEnable && timeoutHandlerType.value === 1"
+    >
+      <el-input-number
+        v-model="maxRemindCount"
+        :min="1"
+        :max="10"
+        @change="() => updateTimeModdle()"
+      />
+    </el-form-item>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import {
+  TimeUnitType,
+  TIME_UNIT_TYPES,
+  TIMEOUT_HANDLER_TYPES,
+} from '@/components/SimpleProcessDesignerV2/src/consts'
+import { convertTimeUnit } from '@/components/SimpleProcessDesignerV2/src/utils'
+
+defineOptions({ name: 'ElementCustomConfig4BoundaryEventTimer' })
+const props = defineProps({
+  id: String,
+  type: String
+})
+const prefix = inject('prefix')
+
+const bpmnElement = ref()
+const bpmnInstances = () => (window as any)?.bpmnInstances
+
+const timeoutHandlerEnable = ref(false)
+const boundaryEventType = ref()
+const timeoutHandlerType = ref({
+  value: undefined
+})
+const timeModdle = ref()
+const timeDuration = ref(6)
+const timeUnit = ref(TimeUnitType.HOUR)
+const maxRemindCount = ref(1)
+
+const elExtensionElements = ref()
+const otherExtensions = ref()
+const configExtensions = ref([])
+const eventDefinition = ref()
+
+const resetElement = () => {
+  bpmnElement.value = bpmnInstances().bpmnElement
+  eventDefinition.value = bpmnElement.value.businessObject.eventDefinitions[0]
+
+  // 获取元素扩展属性 或者 创建扩展属性
+  elExtensionElements.value =
+    bpmnElement.value.businessObject?.extensionElements ??
+    bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] })
+
+  // 是否开启自定义用户任务超时处理
+  boundaryEventType.value = elExtensionElements.value.values?.filter(
+    (ex) => ex.$type === `${prefix}:BoundaryEventType`
+  )?.[0]
+  if (boundaryEventType.value && boundaryEventType.value.value === 1) {
+    timeoutHandlerEnable.value = true
+    configExtensions.value.push(boundaryEventType.value)
+  }
+
+  // 执行动作
+  timeoutHandlerType.value = elExtensionElements.value.values?.filter(
+    (ex) => ex.$type === `${prefix}:TimeoutHandlerType`
+  )?.[0]
+  if (timeoutHandlerType.value) {
+    configExtensions.value.push(timeoutHandlerType.value)
+    if (eventDefinition.value.timeCycle) {
+      const timeStr = eventDefinition.value.timeCycle.body
+      const maxRemindCountStr = timeStr.split('/')[0]
+      const timeDurationStr = timeStr.split('/')[1]
+      console.log(maxRemindCountStr)
+      maxRemindCount.value = parseInt(maxRemindCountStr.slice(1))
+      timeDuration.value = parseInt(timeDurationStr.slice(2, timeDurationStr.length - 1))
+      timeUnit.value = convertTimeUnit(timeDurationStr.slice(timeDurationStr.length - 1))
+      timeModdle.value = eventDefinition.value.timeCycle
+    }
+    if (eventDefinition.value.timeDuration) {
+      const timeDurationStr = eventDefinition.value.timeDuration.body
+      timeDuration.value = parseInt(timeDurationStr.slice(2, timeDurationStr.length - 1))
+      timeUnit.value = convertTimeUnit(timeDurationStr.slice(timeDurationStr.length - 1))
+      timeModdle.value = eventDefinition.value.timeDuration
+    }
+  }
+
+  // 保留剩余扩展元素,便于后面更新该元素对应属性
+  otherExtensions.value =
+    elExtensionElements.value.values?.filter(
+      (ex) =>
+        ex.$type !== `${prefix}:BoundaryEventType` && ex.$type !== `${prefix}:TimeoutHandlerType`
+    ) ?? []
+}
+
+const timeoutHandlerChange = (val) => {
+  timeoutHandlerEnable.value = val
+  if (val) {
+    // 启用自定义用户任务超时处理
+    // 边界事件类型 --- 超时
+    boundaryEventType.value = bpmnInstances().moddle.create(`${prefix}:BoundaryEventType`, {
+      value: 1
+    })
+    configExtensions.value.push(boundaryEventType.value)
+    // 超时处理类型
+    timeoutHandlerType.value = bpmnInstances().moddle.create(`${prefix}:TimeoutHandlerType`, {
+      value: 1
+    })
+    configExtensions.value.push(timeoutHandlerType.value)
+    // 超时时间表达式
+    timeDuration.value = 6
+    timeUnit.value = 2
+    maxRemindCount.value = 1
+    timeModdle.value = bpmnInstances().moddle.create(`bpmn:Expression`, {
+      body: 'PT6H'
+    })
+    eventDefinition.value.timeDuration = timeModdle.value
+  } else {
+    // 关闭自定义用户任务超时处理
+    configExtensions.value = []
+    delete eventDefinition.value.timeDuration
+    delete eventDefinition.value.timeCycle
+  }
+  updateElementExtensions()
+}
+
+const onTimeoutHandlerTypeChanged = () => {
+  maxRemindCount.value = 1
+  updateElementExtensions()
+  updateTimeModdle()
+}
+
+const onTimeUnitChange = () => {
+  // 分钟,默认是 60 分钟
+  if (timeUnit.value === TimeUnitType.MINUTE) {
+    timeDuration.value = 60
+  }
+  // 小时,默认是 6 个小时
+  if (timeUnit.value === TimeUnitType.HOUR) {
+    timeDuration.value = 6
+  }
+  // 天, 默认 1天
+  if (timeUnit.value === TimeUnitType.DAY) {
+    timeDuration.value = 1
+  }
+  updateTimeModdle()
+}
+
+const updateTimeModdle = () => {
+  if (maxRemindCount.value > 1) {
+    timeModdle.value.body = 'R' + maxRemindCount.value + '/' + isoTimeDuration()
+    if (!eventDefinition.value.timeCycle) {
+      delete eventDefinition.value.timeDuration
+      eventDefinition.value.timeCycle = timeModdle.value
+    }
+  } else {
+    timeModdle.value.body = isoTimeDuration()
+    if (!eventDefinition.value.timeDuration) {
+      delete eventDefinition.value.timeCycle
+      eventDefinition.value.timeDuration = timeModdle.value
+    }
+  }
+}
+
+const isoTimeDuration = () => {
+  let strTimeDuration = 'PT'
+  if (timeUnit.value === TimeUnitType.MINUTE) {
+    strTimeDuration += timeDuration.value + 'M'
+  }
+  if (timeUnit.value === TimeUnitType.HOUR) {
+    strTimeDuration += timeDuration.value + 'H'
+  }
+  if (timeUnit.value === TimeUnitType.DAY) {
+    strTimeDuration += timeDuration.value + 'D'
+  }
+  return strTimeDuration
+}
+
+const updateElementExtensions = () => {
+  const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
+    values: [...otherExtensions.value, ...configExtensions.value]
+  })
+  bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
+    extensionElements: extensions
+  })
+}
+
+watch(
+  () => props.id,
+  (val) => {
+    val &&
+      val.length &&
+      nextTick(() => {
+        resetElement()
+      })
+  },
+  { immediate: true }
+)
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/components/bpmnProcessDesigner/package/penal/custom-config/components/UserTaskCustomConfig.vue b/src/components/bpmnProcessDesigner/package/penal/custom-config/components/UserTaskCustomConfig.vue
new file mode 100644
index 0000000..ba38514
--- /dev/null
+++ b/src/components/bpmnProcessDesigner/package/penal/custom-config/components/UserTaskCustomConfig.vue
@@ -0,0 +1,623 @@
+<!-- UserTask 自定义配置:
+     1. 审批人与提交人为同一人时
+     2. 审批人拒绝时
+     3. 审批人为空时
+     4. 操作按钮
+     5. 字段权限
+     6. 审批类型
+-->
+<template>
+  <div>
+    <el-divider content-position="left">审批类型</el-divider>
+    <el-form-item prop="approveType">
+      <el-radio-group v-model="approveType.value">
+        <el-radio
+          v-for="(item, index) in APPROVE_TYPE"
+          :key="index"
+          :value="item.value"
+          :label="item.value"
+        >
+          {{ item.label }}
+        </el-radio>
+      </el-radio-group>
+    </el-form-item>
+
+    <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>
+
+    <el-divider content-position="left">操作按钮</el-divider>
+    <div class="button-setting-pane">
+      <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 buttonsSettingEl" :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-divider content-position="left">字段权限</el-divider>
+    <div class="field-setting-pane" v-if="formType === 10">
+      <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 fieldsPermissionEl" :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>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import {
+  ASSIGN_START_USER_HANDLER_TYPES,
+  RejectHandlerType,
+  REJECT_HANDLER_TYPES,
+  ASSIGN_EMPTY_HANDLER_TYPES,
+  AssignEmptyHandlerType,
+  OPERATION_BUTTON_NAME,
+  DEFAULT_BUTTON_SETTING,
+  FieldPermissionType,
+  APPROVE_TYPE,
+  ApproveType,
+  ButtonSetting
+} from '@/components/SimpleProcessDesignerV2/src/consts'
+import * as UserApi from '@/api/system/user'
+import { useFormFieldsPermission } from '@/components/SimpleProcessDesignerV2/src/node'
+
+defineOptions({ name: 'ElementCustomConfig4UserTask' })
+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 buttonsSettingEl = ref()
+const { btnDisplayNameEdit, changeBtnDisplayName, btnDisplayNameBlurEvent } = useButtonsSetting()
+
+// 字段权限
+const fieldsPermissionEl = ref([])
+const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFieldsPermission(
+  FieldPermissionType.READ
+)
+
+// 审批类型
+const approveType = ref({ value: ApproveType.USER })
+
+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: [] })
+
+  // 审批类型
+  approveType.value =
+    elExtensionElements.value.values?.filter((ex) => ex.$type === `${prefix}:ApproveType`)?.[0] ||
+    bpmnInstances().moddle.create(`${prefix}:ApproveType`, { value: ApproveType.USER })
+
+  // 审批人与提交人为同一人时
+  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
+  })
+
+  // 操作按钮
+  buttonsSettingEl.value = elExtensionElements.value.values?.filter(
+    (ex) => ex.$type === `${prefix}:ButtonsSetting`
+  )
+  if (buttonsSettingEl.value.length === 0) {
+    DEFAULT_BUTTON_SETTING.forEach((item) => {
+      buttonsSettingEl.value.push(
+        bpmnInstances().moddle.create(`${prefix}:ButtonsSetting`, {
+          'flowable:id': item.id,
+          'flowable:displayName': item.displayName,
+          'flowable:enable': item.enable
+        })
+      )
+    })
+  }
+
+  // 字段权限
+  if (formType.value === 10) {
+    const fieldsPermissionList = elExtensionElements.value.values?.filter(
+      (ex) => ex.$type === `${prefix}:FieldsPermission`
+    )
+    fieldsPermissionEl.value = []
+    getNodeConfigFormFields()
+    // 由于默认添加了发起人元素,这里需要删掉
+    fieldsPermissionConfig.value = fieldsPermissionConfig.value.slice(1)
+    fieldsPermissionConfig.value.forEach((element) => {
+      element.permission =
+        fieldsPermissionList?.find((obj) => obj.field === element.field)?.permission ?? '1'
+      fieldsPermissionEl.value.push(
+        bpmnInstances().moddle.create(`${prefix}:FieldsPermission`, element)
+      )
+    })
+  }
+
+  // 保留剩余扩展元素,便于后面更新该元素对应属性
+  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` &&
+        ex.$type !== `${prefix}:ButtonsSetting` &&
+        ex.$type !== `${prefix}:FieldsPermission` &&
+        ex.$type !== `${prefix}:ApproveType`
+    ) ?? []
+
+  // 更新元素扩展属性,避免后续报错
+  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,
+      approveType.value,
+      ...buttonsSettingEl.value,
+      ...fieldsPermissionEl.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 来避免重复节点
+  const visited = new Set() // 用于记录已访问的节点
+
+  // 检查是否是开始事件节点
+  function isStartEvent(element) {
+    return element.type === 'bpmn:StartEvent'
+  }
+
+  function findPredecessorsRecursively(element) {
+    // 如果该节点已经访问过,直接返回,避免循环
+    if (visited.has(element)) {
+      return
+    }
+
+    // 标记当前节点为已访问
+    visited.add(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) // 返回前置节点数组
+}
+
+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
+  }
+}
+
+const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
+onMounted(async () => {
+  // 获得用户列表
+  userOptions.value = await UserApi.getSimpleUserList()
+})
+</script>
+
+<style lang="scss" scoped>
+.button-setting-pane {
+  display: flex;
+  flex-direction: column;
+  font-size: 14px;
+  margin-top: 8px;
+
+  .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%);
+      }
+    }
+  }
+}
+
+.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: 100px;
+      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: 100px;
+      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: 100px;
+        text-align: center;
+      }
+    }
+  }
+}
+</style>
diff --git a/src/components/bpmnProcessDesigner/package/penal/custom-config/data.ts b/src/components/bpmnProcessDesigner/package/penal/custom-config/data.ts
new file mode 100644
index 0000000..a45355e
--- /dev/null
+++ b/src/components/bpmnProcessDesigner/package/penal/custom-config/data.ts
@@ -0,0 +1,13 @@
+import UserTaskCustomConfig from './components/UserTaskCustomConfig.vue'
+import BoundaryEventTimer from './components/BoundaryEventTimer.vue'
+
+export const CustomConfigMap = {
+  UserTask: {
+    name: '用户任务',
+    componet: UserTaskCustomConfig
+  },
+  BoundaryEventTimerEventDefinition: {
+    name: '定时边界事件(非中断)',
+    componet: BoundaryEventTimer
+  }
+}
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..7c5cd1f 100644
--- a/src/components/bpmnProcessDesigner/package/penal/listeners/ElementListeners.vue
+++ b/src/components/bpmnProcessDesigner/package/penal/listeners/ElementListeners.vue
@@ -302,7 +302,7 @@
 }
 // 打开 监听器详情 侧边栏
 const openListenerForm = (listener, index?) => {
-  // debugger
+  console.log(listener)
   if (listener) {
     listenerForm.value = initListenerForm(listener)
     editingListenerIndex.value = index
@@ -370,7 +370,7 @@
 }
 // 移除监听器
 const removeListener = (index) => {
-  // debugger
+  debugger
   ElMessageBox.confirm('确认移除该监听器吗?', '提示', {
     confirmButtonText: '确 认',
     cancelButtonText: '取 消'
diff --git a/src/components/bpmnProcessDesigner/package/penal/listeners/UserTaskListeners.vue b/src/components/bpmnProcessDesigner/package/penal/listeners/UserTaskListeners.vue
index 76e0c80..4486698 100644
--- a/src/components/bpmnProcessDesigner/package/penal/listeners/UserTaskListeners.vue
+++ b/src/components/bpmnProcessDesigner/package/penal/listeners/UserTaskListeners.vue
@@ -337,16 +337,13 @@
 const bpmnInstances = () => (window as any)?.bpmnInstances
 
 const resetListenersList = () => {
-  console.log(
-    bpmnInstances().bpmnElement,
-    'window.bpmnInstances.bpmnElementwindow.bpmnInstances.bpmnElementwindow.bpmnInstances.bpmnElementwindow.bpmnInstances.bpmnElementwindow.bpmnInstances.bpmnElementwindow.bpmnInstances.bpmnElement'
-  )
   bpmnElement.value = bpmnInstances().bpmnElement
   otherExtensionList.value = []
   bpmnElementListeners.value =
     bpmnElement.value.businessObject?.extensionElements?.values.filter(
       (ex) => ex.$type === `${prefix}:TaskListener`
     ) ?? []
+  console.log(bpmnElementListeners.value.map)
   elementListenersList.value = bpmnElementListeners.value.map((listener) =>
     initListenerType(listener)
   )
diff --git a/src/components/bpmnProcessDesigner/package/penal/listeners/utilSelf.ts b/src/components/bpmnProcessDesigner/package/penal/listeners/utilSelf.ts
index b4eb1d2..6ca7eb6 100644
--- a/src/components/bpmnProcessDesigner/package/penal/listeners/utilSelf.ts
+++ b/src/components/bpmnProcessDesigner/package/penal/listeners/utilSelf.ts
@@ -1,5 +1,6 @@
 // 初始化表单数据
 export function initListenerForm(listener) {
+  console.log(listener)
   let self = {
     ...listener
   }
@@ -28,6 +29,7 @@
 }
 
 export function initListenerType(listener) {
+  listener.id = listener.$attrs.id
   let listenerType
   if (listener.class) listenerType = 'classListener'
   if (listener.expression) listenerType = 'expressionListener'
diff --git a/src/components/bpmnProcessDesigner/package/penal/multi-instance/ElementMultiInstance.vue b/src/components/bpmnProcessDesigner/package/penal/multi-instance/ElementMultiInstance.vue
index c0ec1ca..de2fb0d 100644
--- a/src/components/bpmnProcessDesigner/package/penal/multi-instance/ElementMultiInstance.vue
+++ b/src/components/bpmnProcessDesigner/package/penal/multi-instance/ElementMultiInstance.vue
@@ -1,6 +1,30 @@
 <template>
   <div class="panel-tab__content">
-    <el-form label-width="90px">
+    <el-radio-group v-model="approveMethod" @change="onApproveMethodChange">
+      <div class="flex-col">
+        <div v-for="(item, index) in APPROVE_METHODS" :key="index">
+          <el-radio :value="item.value" :label="item.value">
+            {{ item.label }}
+          </el-radio>
+          <el-form-item prop="approveRatio">
+            <el-input-number
+              v-model="approveRatio"
+              :min="10"
+              :max="100"
+              :step="10"
+              size="small"
+              v-if="
+                item.value === ApproveMethodType.APPROVE_BY_RATIO &&
+                approveMethod === ApproveMethodType.APPROVE_BY_RATIO
+              "
+              @change="onApproveRatioChange"
+            />
+          </el-form-item>
+        </div>
+      </div>
+    </el-radio-group>
+    <!-- 与Simple设计器配置合并,保留以前的代码 -->
+    <el-form label-width="90px" style="display: none">
       <el-form-item label="快捷配置">
         <el-button size="small" @click="changeConfig('依次审批')">依次审批</el-button>
         <el-button size="small" @click="changeConfig('会签')">会签</el-button>
@@ -45,17 +69,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>
@@ -73,11 +100,14 @@
 </template>
 
 <script lang="ts" setup>
+import { ApproveMethodType, APPROVE_METHODS } from '@/components/SimpleProcessDesignerV2/src/consts'
+
 defineOptions({ name: 'ElementMultiInstance' })
 
 const props = defineProps({
   businessObject: Object,
-  type: String
+  type: String,
+  id: String
 })
 const prefix = inject('prefix')
 const loopCharacteristics = ref('')
@@ -264,16 +294,118 @@
   }
 }
 
+/**
+ * -----新版本多实例-----
+ */
+const approveMethod = ref()
+const approveRatio = ref(100)
+const otherExtensions = ref()
+const getElementLoopNew = () => {
+  const extensionElements =
+    bpmnElement.value.businessObject?.extensionElements ??
+    bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] })
+  approveMethod.value = extensionElements.values.filter(
+    (ex) => ex.$type === `${prefix}:ApproveMethod`
+  )?.[0]?.value
+
+  otherExtensions.value =
+    extensionElements.values.filter((ex) => ex.$type !== `${prefix}:ApproveMethod`) ?? []
+
+  if (!approveMethod.value) {
+    approveMethod.value = ApproveMethodType.SEQUENTIAL_APPROVE
+    updateLoopCharacteristics()
+  }
+}
+const onApproveMethodChange = () => {
+  approveRatio.value = 100
+  updateLoopCharacteristics()
+}
+const onApproveRatioChange = () => {
+  updateLoopCharacteristics()
+}
+const updateLoopCharacteristics = () => {
+  // 根据ApproveMethod生成multiInstanceLoopCharacteristics节点
+  if (approveMethod.value === ApproveMethodType.RANDOM_SELECT_ONE_APPROVE) {
+    bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
+      loopCharacteristics: null
+    })
+  } else {
+    if (approveMethod.value === ApproveMethodType.APPROVE_BY_RATIO) {
+      multiLoopInstance.value = bpmnInstances().moddle.create(
+        'bpmn:MultiInstanceLoopCharacteristics',
+        { isSequential: false, collection: '${coll_userList}' }
+      )
+      multiLoopInstance.value.completionCondition = bpmnInstances().moddle.create(
+        'bpmn:FormalExpression',
+        {
+          body: '${ nrOfCompletedInstances/nrOfInstances >= ' + approveRatio.value / 100 + '}'
+        }
+      )
+    }
+    if (approveMethod.value === ApproveMethodType.ANY_APPROVE) {
+      multiLoopInstance.value = bpmnInstances().moddle.create(
+        'bpmn:MultiInstanceLoopCharacteristics',
+        { isSequential: false, collection: '${coll_userList}' }
+      )
+      multiLoopInstance.value.completionCondition = bpmnInstances().moddle.create(
+        'bpmn:FormalExpression',
+        {
+          body: '${ nrOfCompletedInstances > 0 }'
+        }
+      )
+    }
+    if (approveMethod.value === ApproveMethodType.SEQUENTIAL_APPROVE) {
+      multiLoopInstance.value = bpmnInstances().moddle.create(
+        'bpmn:MultiInstanceLoopCharacteristics',
+        { isSequential: true, collection: '${coll_userList}' }
+      )
+      multiLoopInstance.value.loopCardinality = bpmnInstances().moddle.create(
+        'bpmn:FormalExpression',
+        {
+          body: '1'
+        }
+      )
+      multiLoopInstance.value.completionCondition = bpmnInstances().moddle.create(
+        'bpmn:FormalExpression',
+        {
+          body: '${ nrOfCompletedInstances >= nrOfInstances }'
+        }
+      )
+    }
+    bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
+      loopCharacteristics: toRaw(multiLoopInstance.value)
+    })
+  }
+
+  // 添加ApproveMethod到ExtensionElements
+  const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
+    values: [
+      ...otherExtensions.value,
+      bpmnInstances().moddle.create(`${prefix}:ApproveMethod`, {
+        value: approveMethod.value
+      })
+    ]
+  })
+  bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
+    extensionElements: extensions
+  })
+}
+
 onBeforeUnmount(() => {
   multiLoopInstance.value = null
   bpmnElement.value = null
 })
 
 watch(
-  () => props.businessObject,
+  () => props.id,
   (val) => {
-    bpmnElement.value = bpmnInstances().bpmnElement
-    getElementLoop(val)
+    if (val) {
+      nextTick(() => {
+        bpmnElement.value = bpmnInstances().bpmnElement
+        // getElementLoop(val)
+        getElementLoopNew()
+      })
+    }
   },
   { immediate: true }
 )
diff --git a/src/components/bpmnProcessDesigner/package/penal/properties/ElementProperties.vue b/src/components/bpmnProcessDesigner/package/penal/properties/ElementProperties.vue
index 494b3d9..7bf4f0e 100644
--- a/src/components/bpmnProcessDesigner/package/penal/properties/ElementProperties.vue
+++ b/src/components/bpmnProcessDesigner/package/penal/properties/ElementProperties.vue
@@ -75,17 +75,16 @@
 const bpmnInstances = () => (window as any)?.bpmnInstances
 
 const resetAttributesList = () => {
-  console.log(window, 'windowwindowwindowwindowwindowwindowwindow')
   bpmnElement.value = bpmnInstances().bpmnElement
   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)
       }
       return ex.$type === `${prefix}:Properties`
-    }) ?? []
+    }) ?? [];
 
   // 保存所有的 扩展属性字段
   bpmnElementPropertyList.value = bpmnElementProperties.value.reduce(
diff --git a/src/components/bpmnProcessDesigner/package/penal/task/ElementTask.vue b/src/components/bpmnProcessDesigner/package/penal/task/ElementTask.vue
index e808af3..3a71b4c 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>
@@ -22,9 +29,7 @@
 </template>
 
 <script lang="ts" setup>
-import UserTask from './task-components/UserTask.vue'
-import ScriptTask from './task-components/ScriptTask.vue'
-import ReceiveTask from './task-components/ReceiveTask.vue'
+import { installedComponent } from './data'
 
 defineOptions({ name: 'ElementTaskConfig' })
 
@@ -38,14 +43,7 @@
   exclusive: false
 })
 const witchTaskComponent = ref()
-const installedComponent = ref({
-  // 手工任务与普通任务一致,不需要其他配置
-  // 接收消息任务,需要在全局下插入新的消息实例,并在该节点下的 messageRef 属性绑定该实例
-  // 发送任务、服务任务、业务规则任务共用一个相同配置
-  UserTask: 'UserTask', // 用户任务配置
-  ScriptTask: 'ScriptTask', // 脚本任务配置
-  ReceiveTask: 'ReceiveTask' // 消息接收任务
-})
+
 const bpmnElement = ref()
 
 const bpmnInstances = () => (window as any).bpmnInstances
@@ -71,15 +69,8 @@
 watch(
   () => props.type,
   () => {
-    // witchTaskComponent.value = installedComponent.value[props.type]
-    if (props.type == installedComponent.value.UserTask) {
-      witchTaskComponent.value = UserTask
-    }
-    if (props.type == installedComponent.value.ScriptTask) {
-      witchTaskComponent.value = ScriptTask
-    }
-    if (props.type == installedComponent.value.ReceiveTask) {
-      witchTaskComponent.value = ReceiveTask
+    if (props.type) {
+      witchTaskComponent.value = installedComponent[props.type].component
     }
   },
   { immediate: true }
diff --git a/src/components/bpmnProcessDesigner/package/penal/task/data.ts b/src/components/bpmnProcessDesigner/package/penal/task/data.ts
new file mode 100644
index 0000000..805c9ac
--- /dev/null
+++ b/src/components/bpmnProcessDesigner/package/penal/task/data.ts
@@ -0,0 +1,36 @@
+import UserTask from './task-components/UserTask.vue'
+import ServiceTask from './task-components/ServiceTask.vue'
+import ScriptTask from './task-components/ScriptTask.vue'
+import ReceiveTask from './task-components/ReceiveTask.vue'
+import CallActivity from './task-components/CallActivity.vue'
+
+export const installedComponent = {
+  UserTask: {
+    name: '用户任务',
+    component: UserTask
+  },
+  ServiceTask: {
+    name: '服务任务',
+    component: ServiceTask
+  },
+  ScriptTask: {
+    name: '脚本任务',
+    component: ScriptTask
+  },
+  ReceiveTask: {
+    name: '接收任务',
+    component: ReceiveTask
+  },
+  CallActivity: {
+    name: '调用活动',
+    component: CallActivity
+  }
+}
+
+export const getTaskCollapseItemName = (elementType) => {
+  return installedComponent[elementType].name
+}
+
+export const isTaskCollapseItemShow = (elementType) => {
+  return installedComponent[elementType]
+}
diff --git a/src/components/bpmnProcessDesigner/package/penal/task/task-components/CallActivity.vue b/src/components/bpmnProcessDesigner/package/penal/task/task-components/CallActivity.vue
new file mode 100644
index 0000000..6d8268b
--- /dev/null
+++ b/src/components/bpmnProcessDesigner/package/penal/task/task-components/CallActivity.vue
@@ -0,0 +1,280 @@
+<template>
+  <div>
+    <el-form label-width="100px">
+      <el-form-item label="实例名称" prop="processInstanceName">
+        <el-input
+          v-model="formData.processInstanceName"
+          clearable
+          placeholder="请输入实例名称"
+          @change="updateCallActivityAttr('processInstanceName')"
+        />
+      </el-form-item>
+
+      <!-- TODO 需要可选择已存在的流程 -->
+      <el-form-item label="被调用流程" prop="calledElement">
+        <el-input
+          v-model="formData.calledElement"
+          clearable
+          placeholder="请输入被调用流程"
+          @change="updateCallActivityAttr('calledElement')"
+        />
+      </el-form-item>
+
+      <el-form-item label="继承变量" prop="inheritVariables">
+        <el-switch
+          v-model="formData.inheritVariables"
+          @change="updateCallActivityAttr('inheritVariables')"
+        />
+      </el-form-item>
+
+      <el-form-item label="继承业务键" prop="inheritBusinessKey">
+        <el-switch
+          v-model="formData.inheritBusinessKey"
+          @change="updateCallActivityAttr('inheritBusinessKey')"
+        />
+      </el-form-item>
+
+      <el-form-item v-if="!formData.inheritBusinessKey" label="业务键表达式" prop="businessKey">
+        <el-input
+          v-model="formData.businessKey"
+          clearable
+          placeholder="请输入业务键表达式"
+          @change="updateCallActivityAttr('businessKey')"
+        />
+      </el-form-item>
+
+      <el-divider />
+      <div>
+        <div class="flex mb-10px">
+          <el-text>输入参数</el-text>
+          <XButton
+            class="ml-auto"
+            type="primary"
+            preIcon="ep:plus"
+            title="添加参数"
+            size="small"
+            @click="openVariableForm('in', null, -1)"
+          />
+        </div>
+        <el-table :data="inVariableList" max-height="240" fit border>
+          <el-table-column label="源" prop="source" min-width="100px" show-overflow-tooltip />
+          <el-table-column label="目标" prop="target" min-width="100px" show-overflow-tooltip />
+          <el-table-column label="操作" width="110px">
+            <template #default="scope">
+              <el-button link @click="openVariableForm('in', scope.row, scope.$index)" size="small">
+                编辑
+              </el-button>
+              <el-divider direction="vertical" />
+              <el-button
+                link
+                size="small"
+                style="color: #ff4d4f"
+                @click="removeVariable('in', scope.$index)"
+              >
+                移除
+              </el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+
+      <el-divider />
+      <div>
+        <div class="flex mb-10px">
+          <el-text>输出参数</el-text>
+          <XButton
+            class="ml-auto"
+            type="primary"
+            preIcon="ep:plus"
+            title="添加参数"
+            size="small"
+            @click="openVariableForm('out', null, -1)"
+          />
+        </div>
+        <el-table :data="outVariableList" max-height="240" fit border>
+          <el-table-column label="源" prop="source" min-width="100px" show-overflow-tooltip />
+          <el-table-column label="目标" prop="target" min-width="100px" show-overflow-tooltip />
+          <el-table-column label="操作" width="110px">
+            <template #default="scope">
+              <el-button
+                link
+                @click="openVariableForm('out', scope.row, scope.$index)"
+                size="small"
+              >
+                编辑
+              </el-button>
+              <el-divider direction="vertical" />
+              <el-button
+                link
+                size="small"
+                style="color: #ff4d4f"
+                @click="removeVariable('out', scope.$index)"
+              >
+                移除
+              </el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+    </el-form>
+
+    <!-- 添加或修改参数 -->
+    <el-dialog
+      v-model="variableDialogVisible"
+      title="参数配置"
+      width="600px"
+      append-to-body
+      destroy-on-close
+    >
+      <el-form :model="varialbeFormData" label-width="80px" ref="varialbeFormRef">
+        <el-form-item label="源:" prop="source">
+          <el-input v-model="varialbeFormData.source" clearable />
+        </el-form-item>
+        <el-form-item label="目标:" prop="target">
+          <el-input v-model="varialbeFormData.target" clearable />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="variableDialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="saveVariable">确 定</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup>
+defineOptions({ name: 'CallActivity' })
+const props = defineProps({
+  id: String,
+  type: String
+})
+const prefix = inject('prefix')
+const message = useMessage()
+
+const formData = ref({
+  processInstanceName: '',
+  calledElement: '',
+  inheritVariables: false,
+  businessKey: '',
+  inheritBusinessKey: false,
+  calledElementType: 'key'
+})
+const inVariableList = ref()
+const outVariableList = ref()
+const variableType = ref() // 参数类型
+const editingVariableIndex = ref(-1) // 编辑参数下标
+const variableDialogVisible = ref(false)
+const varialbeFormRef = ref()
+const varialbeFormData = ref({
+  source: '',
+  target: ''
+})
+
+const bpmnInstances = () => (window as any)?.bpmnInstances
+const bpmnElement = ref()
+const otherExtensionList = ref()
+
+const initCallActivity = () => {
+  bpmnElement.value = bpmnInstances().bpmnElement
+  console.log(bpmnElement.value.businessObject, 'callActivity')
+
+  // 初始化所有配置项
+  Object.keys(formData.value).forEach((key) => {
+    formData.value[key] = bpmnElement.value.businessObject[key] ?? formData.value[key]
+  })
+
+  otherExtensionList.value = [] // 其他扩展配置
+  inVariableList.value = []
+  outVariableList.value = []
+  // 初始化输入参数
+  bpmnElement.value.businessObject?.extensionElements?.values?.forEach((ex) => {
+    if (ex.$type === `${prefix}:In`) {
+      inVariableList.value.push(ex)
+    } else if (ex.$type === `${prefix}:Out`) {
+      outVariableList.value.push(ex)
+    } else {
+      otherExtensionList.value.push(ex)
+    }
+  })
+
+  // 默认添加
+  // bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
+  //   calledElementType: 'key'
+  // })
+}
+
+const updateCallActivityAttr = (attr) => {
+  bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
+    [attr]: formData.value[attr]
+  })
+}
+
+const openVariableForm = (type, data, index) => {
+  editingVariableIndex.value = index
+  variableType.value = type
+  varialbeFormData.value = index === -1 ? {} : { ...data }
+  variableDialogVisible.value = true
+}
+
+const removeVariable = async (type, index) => {
+  try {
+    await message.delConfirm()
+    if (type === 'in') {
+      inVariableList.value.splice(index, 1)
+    }
+    if (type === 'out') {
+      outVariableList.value.splice(index, 1)
+    }
+    updateElementExtensions()
+  } catch {}
+}
+
+const saveVariable = () => {
+  if (editingVariableIndex.value === -1) {
+    if (variableType.value === 'in') {
+      inVariableList.value.push(
+        bpmnInstances().moddle.create(`${prefix}:In`, { ...varialbeFormData.value })
+      )
+    }
+    if (variableType.value === 'out') {
+      outVariableList.value.push(
+        bpmnInstances().moddle.create(`${prefix}:Out`, { ...varialbeFormData.value })
+      )
+    }
+    updateElementExtensions()
+  } else {
+    if (variableType.value === 'in') {
+      inVariableList.value[editingVariableIndex.value].source = varialbeFormData.value.source
+      inVariableList.value[editingVariableIndex.value].target = varialbeFormData.value.target
+    }
+    if (variableType.value === 'out') {
+      outVariableList.value[editingVariableIndex.value].source = varialbeFormData.value.source
+      outVariableList.value[editingVariableIndex.value].target = varialbeFormData.value.target
+    }
+  }
+  variableDialogVisible.value = false
+}
+
+const updateElementExtensions = () => {
+  const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
+    values: [...inVariableList.value, ...outVariableList.value, ...otherExtensionList.value]
+  })
+  bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
+    extensionElements: extensions
+  })
+}
+
+watch(
+  () => props.id,
+  (val) => {
+    val &&
+      val.length &&
+      nextTick(() => {
+        initCallActivity()
+      })
+  },
+  { immediate: true }
+)
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/components/bpmnProcessDesigner/package/penal/task/task-components/ServiceTask.vue b/src/components/bpmnProcessDesigner/package/penal/task/task-components/ServiceTask.vue
new file mode 100644
index 0000000..2f9c535
--- /dev/null
+++ b/src/components/bpmnProcessDesigner/package/penal/task/task-components/ServiceTask.vue
@@ -0,0 +1,91 @@
+<template>
+  <div>
+    <el-form-item label="执行类型" key="executeType">
+      <el-select v-model="serviceTaskForm.executeType">
+        <el-option label="Java类" value="class" />
+        <el-option label="表达式" value="expression" />
+        <el-option label="代理表达式" value="delegateExpression" />
+      </el-select>
+    </el-form-item>
+    <el-form-item
+      v-if="serviceTaskForm.executeType === 'class'"
+      label="Java类"
+      prop="class"
+      key="execute-class"
+    >
+      <el-input v-model="serviceTaskForm.class" clearable @change="updateElementTask" />
+    </el-form-item>
+    <el-form-item
+      v-if="serviceTaskForm.executeType === 'expression'"
+      label="表达式"
+      prop="expression"
+      key="execute-expression"
+    >
+      <el-input v-model="serviceTaskForm.expression" clearable @change="updateElementTask" />
+    </el-form-item>
+    <el-form-item
+      v-if="serviceTaskForm.executeType === 'delegateExpression'"
+      label="代理表达式"
+      prop="delegateExpression"
+      key="execute-delegate"
+    >
+      <el-input v-model="serviceTaskForm.delegateExpression" clearable @change="updateElementTask" />
+    </el-form-item>
+  </div>
+</template>
+
+<script lang="ts" setup>
+defineOptions({ name: 'ServiceTask' })
+const props = defineProps({
+  id: String,
+  type: String
+})
+
+const defaultTaskForm = ref({
+  executeType: '',
+  class: '',
+  expression: '',
+  delegateExpression: ''
+})
+
+const serviceTaskForm = ref<any>({})
+const bpmnElement = ref()
+
+const bpmnInstances = () => (window as any)?.bpmnInstances
+
+const resetTaskForm = () => {
+  for (let key in defaultTaskForm.value) {
+    let value = bpmnElement.value?.businessObject[key] || defaultTaskForm.value[key]
+    serviceTaskForm.value[key] = value
+    if (value) {
+      serviceTaskForm.value.executeType = key
+    }
+  }
+}
+
+const updateElementTask = () => {
+  let taskAttr = Object.create(null);
+  const type = serviceTaskForm.value.executeType;
+  for (let key in serviceTaskForm.value) {
+    if (key !== 'executeType' && key !== type) taskAttr[key] = null;
+  }
+  taskAttr[type] = serviceTaskForm.value[type] || "";
+  bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), taskAttr)
+}
+
+onBeforeUnmount(() => {
+  bpmnElement.value = null
+})
+
+watch(
+  () => props.id,
+  () => {
+    bpmnElement.value = bpmnInstances().bpmnElement
+    nextTick(() => {
+      resetTaskForm()
+    })
+  },
+  { immediate: true }
+)
+
+</script>
diff --git a/src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue b/src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue
index f404ef7..e563bfd 100644
--- a/src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue
+++ b/src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue
@@ -1,5 +1,5 @@
 <template>
-  <el-form label-width="100px">
+  <el-form label-width="120px">
     <el-form-item label="规则类型" prop="candidateStrategy">
       <el-select
         v-model="userTaskForm.candidateStrategy"
@@ -8,15 +8,15 @@
         @change="changeCandidateStrategy"
       >
         <el-option
-          v-for="dict in getIntDictOptions(DICT_TYPE.BPM_TASK_CANDIDATE_STRATEGY)"
-          :key="dict.value"
+          v-for="(dict, index) in CANDIDATE_STRATEGY"
+          :key="index"
           :label="dict.label"
           :value="dict.value"
         />
       </el-select>
     </el-form-item>
     <el-form-item
-      v-if="userTaskForm.candidateStrategy == 10"
+      v-if="userTaskForm.candidateStrategy == CandidateStrategy.ROLE"
       label="指定角色"
       prop="candidateParam"
     >
@@ -31,7 +31,11 @@
       </el-select>
     </el-form-item>
     <el-form-item
-      v-if="userTaskForm.candidateStrategy == 20 || userTaskForm.candidateStrategy == 21"
+      v-if="
+        userTaskForm.candidateStrategy == CandidateStrategy.DEPT_MEMBER ||
+        userTaskForm.candidateStrategy == CandidateStrategy.DEPT_LEADER ||
+        userTaskForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER
+      "
       label="指定部门"
       prop="candidateParam"
       span="24"
@@ -49,7 +53,7 @@
       />
     </el-form-item>
     <el-form-item
-      v-if="userTaskForm.candidateStrategy == 22"
+      v-if="userTaskForm.candidateStrategy == CandidateStrategy.POST"
       label="指定岗位"
       prop="candidateParam"
       span="24"
@@ -65,7 +69,7 @@
       </el-select>
     </el-form-item>
     <el-form-item
-      v-if="userTaskForm.candidateStrategy == 30"
+      v-if="userTaskForm.candidateStrategy == CandidateStrategy.USER"
       label="指定用户"
       prop="candidateParam"
       span="24"
@@ -86,7 +90,7 @@
       </el-select>
     </el-form-item>
     <el-form-item
-      v-if="userTaskForm.candidateStrategy === 40"
+      v-if="userTaskForm.candidateStrategy === CandidateStrategy.USER_GROUP"
       label="指定用户组"
       prop="candidateParam"
     >
@@ -106,7 +110,67 @@
       </el-select>
     </el-form-item>
     <el-form-item
-      v-if="userTaskForm.candidateStrategy === 60"
+      v-if="userTaskForm.candidateStrategy === CandidateStrategy.FORM_USER"
+      label="表单内用户字段"
+      prop="formUser"
+    >
+      <el-select
+        v-model="userTaskForm.candidateParam"
+        clearable
+        style="width: 100%"
+        @change="handleFormUserChange"
+      >
+        <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="userTaskForm.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER"
+      label="表单内部门字段"
+      prop="formDept"
+    >
+      <el-select
+        v-model="userTaskForm.candidateParam"
+        clearable
+        style="width: 100%"
+        @change="updateElementTask"
+      >
+        <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="
+        userTaskForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
+        userTaskForm.candidateStrategy == CandidateStrategy.START_USER_DEPT_LEADER ||
+        userTaskForm.candidateStrategy == CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER ||
+        userTaskForm.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER
+      "
+      :label="deptLevelLabel!"
+      prop="deptLevel"
+      span="24"
+    >
+      <el-select v-model="deptLevel" clearable @change="updateElementTask">
+        <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="userTaskForm.candidateStrategy === CandidateStrategy.EXPRESSION"
       label="流程表达式"
       prop="candidateParam"
     >
@@ -114,12 +178,17 @@
         type="textarea"
         v-model="userTaskForm.candidateParam[0]"
         clearable
-        style="width: 72%"
+        style="width: 100%"
         @change="updateElementTask"
       />
-      <el-button class="ml-5px" size="small" type="success" @click="openProcessExpressionDialog"
-        >选择表达式</el-button
-      >
+      <XButton
+        class="!w-1/1 mt-5px"
+        type="success"
+        preIcon="ep:select"
+        title="选择表达式"
+        size="small"
+        @click="openProcessExpressionDialog"
+      />
       <!-- 选择弹窗 -->
       <ProcessExpressionDialog ref="processExpressionDialogRef" @select="selectProcessExpression" />
     </el-form-item>
@@ -127,7 +196,12 @@
 </template>
 
 <script lang="ts" setup>
-import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import {
+  CANDIDATE_STRATEGY,
+  CandidateStrategy,
+  FieldPermissionType,
+  MULTI_LEVEL_DEPT
+} from '@/components/SimpleProcessDesignerV2/src/consts'
 import { defaultProps, handleTree } from '@/utils/tree'
 import * as RoleApi from '@/api/system/role'
 import * as DeptApi from '@/api/system/dept'
@@ -136,12 +210,14 @@
 import * as UserGroupApi from '@/api/bpm/userGroup'
 import ProcessExpressionDialog from './ProcessExpressionDialog.vue'
 import { ProcessExpressionVO } from '@/api/bpm/processExpression'
+import { useFormFieldsPermission } from '@/components/SimpleProcessDesignerV2/src/node'
 
 defineOptions({ name: 'UserTask' })
 const props = defineProps({
   id: String,
   type: String
 })
+const prefix = inject('prefix')
 const userTaskForm = ref({
   candidateStrategy: undefined, // 分配规则
   candidateParam: [] // 分配选项
@@ -155,11 +231,88 @@
 const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
 const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 用户组列表
 
+const { formFieldOptions } = useFormFieldsPermission(FieldPermissionType.READ)
+// 表单内用户字段选项, 必须是必填和用户选择器
+const userFieldOnFormOptions = computed(() => {
+  return formFieldOptions.filter((item) => item.type === 'UserSelect')
+})
+// 表单内部门字段选项, 必须是必填和部门选择器
+const deptFieldOnFormOptions = computed(() => {
+  return formFieldOptions.filter((item) => item.type === 'DeptSelect')
+})
+
+const deptLevel = ref(1)
+const deptLevelLabel = computed(() => {
+  let label = '部门负责人来源'
+  if (userTaskForm.value.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER) {
+    label = label + '(指定部门向上)'
+  } else if (userTaskForm.value.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER) {
+    label = label + '(表单内部门向上)'
+  } else {
+    label = label + '(发起人部门向上)'
+  }
+  return label
+})
+
+const otherExtensions = ref()
+
 const resetTaskForm = () => {
   const businessObject = bpmnElement.value.businessObject
   if (!businessObject) {
     return
   }
+
+  const extensionElements =
+    businessObject?.extensionElements ??
+    bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] })
+  userTaskForm.value.candidateStrategy = extensionElements.values?.filter(
+    (ex) => ex.$type === `${prefix}:CandidateStrategy`
+  )?.[0]?.value
+  const candidateParamStr = extensionElements.values?.filter(
+    (ex) => ex.$type === `${prefix}:CandidateParam`
+  )?.[0]?.value
+  if (candidateParamStr && candidateParamStr.length > 0) {
+    if (userTaskForm.value.candidateStrategy === CandidateStrategy.EXPRESSION) {
+      // 特殊:流程表达式,只有一个 input 输入框
+      userTaskForm.value.candidateParam = [candidateParamStr]
+    } else if (userTaskForm.value.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER) {
+      // 特殊:多级不部门负责人,需要通过'|'分割
+      userTaskForm.value.candidateParam = candidateParamStr
+        .split('|')[0]
+        .split(',')
+        .map((item) => {
+          // 如果数字超出了最大安全整数范围,则将其作为字符串处理
+          let num = Number(item)
+          return num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER ? item : num
+        })
+      deptLevel.value = +candidateParamStr.split('|')[1]
+    } else if (
+      userTaskForm.value.candidateStrategy == CandidateStrategy.START_USER_DEPT_LEADER ||
+      userTaskForm.value.candidateStrategy == CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER
+    ) {
+      userTaskForm.value.candidateParam = +candidateParamStr
+      deptLevel.value = +candidateParamStr
+    } else if (userTaskForm.value.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER) {
+      userTaskForm.value.candidateParam = candidateParamStr.split('|')[0]
+      deptLevel.value = +candidateParamStr.split('|')[1]
+    } else {
+      userTaskForm.value.candidateParam = candidateParamStr.split(',').map((item) => {
+        // 如果数字超出了最大安全整数范围,则将其作为字符串处理
+        let num = Number(item)
+        return num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER ? item : num
+      })
+    }
+  } else {
+    userTaskForm.value.candidateParam = []
+  }
+
+  otherExtensions.value =
+    extensionElements.values?.filter(
+      (ex) => ex.$type !== `${prefix}:CandidateStrategy` && ex.$type !== `${prefix}:CandidateParam`
+    ) ?? []
+
+  // 改用通过extensionElements来存储数据
+  return
   if (businessObject.candidateStrategy != undefined) {
     userTaskForm.value.candidateStrategy = parseInt(businessObject.candidateStrategy) as any
   } else {
@@ -172,7 +325,7 @@
     } else {
       userTaskForm.value.candidateParam = businessObject.candidateParam
         .split(',')
-        .map((item) => +item)
+        .map((item) => item)
     }
   } else {
     userTaskForm.value.candidateParam = []
@@ -182,11 +335,55 @@
 /** 更新 candidateStrategy 字段时,需要清空 candidateParam,并触发 bpmn 图更新 */
 const changeCandidateStrategy = () => {
   userTaskForm.value.candidateParam = []
+  deptLevel.value = 1
+  if (userTaskForm.value.candidateStrategy === CandidateStrategy.FORM_USER) {
+    // 特殊处理表单内用户字段,当只有发起人选项时应选中发起人
+    if (!userFieldOnFormOptions.value || userFieldOnFormOptions.value.length <= 1) {
+      userTaskForm.value.candidateStrategy = CandidateStrategy.START_USER
+    }
+  }
   updateElementTask()
 }
 
 /** 选中某个 options 时候,更新 bpmn 图  */
 const updateElementTask = () => {
+  let candidateParam =
+    userTaskForm.value.candidateParam instanceof Array
+      ? userTaskForm.value.candidateParam.join(',')
+      : userTaskForm.value.candidateParam
+
+  // 特殊处理多级部门情况
+  if (
+    userTaskForm.value.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
+    userTaskForm.value.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER
+  ) {
+    candidateParam += '|' + deptLevel.value
+  }
+  // 特殊处理发起人部门负责人、发起人连续部门负责人
+  if (
+    userTaskForm.value.candidateStrategy == CandidateStrategy.START_USER_DEPT_LEADER ||
+    userTaskForm.value.candidateStrategy == CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER
+  ) {
+    candidateParam = deptLevel.value + ''
+  }
+
+  const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
+    values: [
+      ...otherExtensions.value,
+      bpmnInstances().moddle.create(`${prefix}:CandidateStrategy`, {
+        value: userTaskForm.value.candidateStrategy
+      }),
+      bpmnInstances().moddle.create(`${prefix}:CandidateParam`, {
+        value: candidateParam
+      })
+    ]
+  })
+  bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
+    extensionElements: extensions
+  })
+
+  // 改用通过extensionElements来存储数据
+  return
   bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
     candidateStrategy: userTaskForm.value.candidateStrategy,
     candidateParam: userTaskForm.value.candidateParam.join(',')
@@ -203,6 +400,14 @@
   updateElementTask()
 }
 
+const handleFormUserChange = (e) => {
+  if (e === 'PROCESS_START_USER_ID') {
+    userTaskForm.value.candidateParam = []
+    userTaskForm.value.candidateStrategy = CandidateStrategy.START_USER
+  }
+  updateElementTask()
+}
+
 watch(
   () => props.id,
   () => {
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..ac2976b 100644
--- a/src/components/bpmnProcessDesigner/package/theme/process-designer.scss
+++ b/src/components/bpmnProcessDesigner/package/theme/process-designer.scss
@@ -1,6 +1,4 @@
-@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';
 
 // 边框被 token-simulation 样式覆盖了
 .djs-palette {
@@ -83,7 +81,7 @@
       height: 100%;
       position: relative;
       background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+')
-        repeat !important;
+      repeat !important;
       div.toggle-mode {
         display: none;
       }
@@ -97,12 +95,12 @@
         box-sizing: border-box;
       }
     }
-    svg {
-      width: 100%;
-      height: 100%;
-      min-height: 100%;
-      overflow: hidden;
-    }
+    // svg {
+    //   width: 100%;
+    //   height: 100%;
+    //   min-height: 100%;
+    //   overflow: hidden;
+    // }
   }
 }
 
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/config/axios/service.ts b/src/config/axios/service.ts
index cf5ca1c..2053b1c 100644
--- a/src/config/axios/service.ts
+++ b/src/config/axios/service.ts
@@ -1,10 +1,4 @@
-import axios, {
-  AxiosError,
-  AxiosInstance,
-  AxiosRequestHeaders,
-  AxiosResponse,
-  InternalAxiosRequestConfig
-} from 'axios'
+import axios, { AxiosError, AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
 
 import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
 import qs from 'qs'
@@ -37,7 +31,11 @@
 const service: AxiosInstance = axios.create({
   baseURL: base_url, // api 的 base_url
   timeout: request_timeout, // 请求超时时间
-  withCredentials: false // 禁用 Cookie 等信息
+  withCredentials: false, // 禁用 Cookie 等信息
+  // 自定义参数序列化函数
+  paramsSerializer: (params) => {
+    return qs.stringify(params, { allowDots: true })
+  }
 })
 
 // request拦截器
@@ -46,34 +44,31 @@
     // 是否需要设置 token
     let isToken = (config!.headers || {}).isToken === false
     whiteList.some((v) => {
-      if (config.url) {
-        config.url.indexOf(v) > -1
+      if (config.url && config.url.indexOf(v) > -1) {
         return (isToken = false)
       }
     })
     if (getAccessToken() && !isToken) {
-      ;(config as Recordable).headers.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token
+      config.headers.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token
     }
     // 设置租户
     if (tenantEnable && tenantEnable === 'true') {
       const tenantId = getTenantId()
-      if (tenantId) (config as Recordable).headers['tenant-id'] = tenantId
+      if (tenantId) config.headers['tenant-id'] = tenantId
     }
-    const params = config.params || {}
-    const data = config.data || false
-    if (
-      config.method?.toUpperCase() === 'POST' &&
-      (config.headers as AxiosRequestHeaders)['Content-Type'] ===
-        'application/x-www-form-urlencoded'
-    ) {
-      config.data = qs.stringify(data)
+    const method = config.method?.toUpperCase()
+    // 防止 GET 请求缓存
+    if (method === 'GET') {
+      config.headers['Cache-Control'] = 'no-cache'
+      config.headers['Pragma'] = 'no-cache'
     }
-    // get参数编码
-    if (config.method?.toUpperCase() === 'GET' && params) {
-      config.params = {}
-      const paramsStr = qs.stringify(params, { allowDots: true })
-      if (paramsStr) {
-        config.url = config.url + '?' + paramsStr
+    // 自定义参数序列化函数
+    else if (method === 'POST') {
+      const contentType = config.headers['Content-Type'] || config.headers['content-type']
+      if (contentType === 'application/x-www-form-urlencoded') {
+        if (config.data && typeof config.data !== 'string') {
+          config.data = qs.stringify(config.data)
+        }
       }
     }
     return config
@@ -165,7 +160,7 @@
           t('sys.api.errMsg901') +
           '</div>' +
           '<div> &nbsp; </div>' +
-          '<div>参考 https://xxxx/ 教程</div>' +
+          '<div>参考 https://doc.iailab.cn/ 教程</div>' +
           '<div> &nbsp; </div>' +
           '<div>5 分钟搭建本地环境</div>'
       })
@@ -206,15 +201,12 @@
 const handleAuthorized = () => {
   const { t } = useI18n()
   if (!isRelogin.show) {
-    // 如果已经到重新登录页面则不进行弹窗提示
-    if (window.location.href.includes('login?redirect=')) {
-      return
-    }
     isRelogin.show = true
     ElMessageBox.confirm(t('sys.api.timeoutMessage'), t('common.confirmTitle'), {
       showCancelButton: false,
       closeOnClickModal: false,
       showClose: false,
+      closeOnPressEscape: false,
       confirmButtonText: t('login.relogin'),
       type: 'warning'
     }).then(() => {
diff --git a/src/directives/index.ts b/src/directives/index.ts
index 89cc8ba..1b99988 100644
--- a/src/directives/index.ts
+++ b/src/directives/index.ts
@@ -11,3 +11,14 @@
   hasRole(app)
   hasPermi(app)
 }
+
+/**
+ * 导出指令:v-mountedFocus
+ */
+export const setupMountedFocus = (app: App<Element>) => {
+  app.directive('mountedFocus', {
+    mounted(el) {
+      el.focus()
+    }
+  })
+}
diff --git a/src/directives/permission/hasPermi.ts b/src/directives/permission/hasPermi.ts
index d86d2f5..0ef3c50 100644
--- a/src/directives/permission/hasPermi.ts
+++ b/src/directives/permission/hasPermi.ts
@@ -5,17 +5,10 @@
 
 export function hasPermi(app: App<Element>) {
   app.directive('hasPermi', (el, binding) => {
-    const { wsCache } = useCache()
     const { value } = binding
-    const all_permission = '*:*:*'
-    const permissions = wsCache.get(CACHE_KEY.USER).permissions
 
     if (value && value instanceof Array && value.length > 0) {
-      const permissionFlag = value
-
-      const hasPermissions = permissions.some((permission: string) => {
-        return all_permission === permission || permissionFlag.includes(permission)
-      })
+      const hasPermissions = hasPermission(value)
 
       if (!hasPermissions) {
         el.parentNode && el.parentNode.removeChild(el)
@@ -25,3 +18,14 @@
     }
   })
 }
+
+export const hasPermission = (permission: string[]) => {
+  const { wsCache } = useCache()
+  const all_permission = '*:*:*'
+  const userInfo = wsCache.get(CACHE_KEY.USER)
+  const permissions = userInfo?.permissions || []
+
+  return permissions.some((p: string) => {
+    return all_permission === p || permission.includes(p)
+  })
+}
diff --git a/src/directives/permission/hasRole.ts b/src/directives/permission/hasRole.ts
index 31a352a..a512811 100644
--- a/src/directives/permission/hasRole.ts
+++ b/src/directives/permission/hasRole.ts
@@ -7,8 +7,9 @@
   app.directive('hasRole', (el, binding) => {
     const { wsCache } = useCache()
     const { value } = binding
-    const super_admin = 'admin'
-    const roles = wsCache.get(CACHE_KEY.USER).roles
+    const super_admin = 'super_admin'
+    const userInfo = wsCache.get(CACHE_KEY.USER)
+    const roles = userInfo?.roles || []
 
     if (value && value instanceof Array && value.length > 0) {
       const roleFlag = value
diff --git a/src/hooks/web/useCache.ts b/src/hooks/web/useCache.ts
index 4f39f30..f6b2bd1 100644
--- a/src/hooks/web/useCache.ts
+++ b/src/hooks/web/useCache.ts
@@ -37,3 +37,19 @@
   wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
   // 注意,不要清理 LoginForm 登录表单
 }
+
+export const useSessionCache = (type: CacheType = 'sessionStorage') => {
+  const wsSessionCache: WebStorageCache = new WebStorageCache({
+    storage: type
+  })
+
+  return {
+    wsSessionCache
+  }
+}
+
+export const deleteUserSessionCache = () => {
+  const { wsSessionCache } = useSessionCache()
+  wsSessionCache.delete(CACHE_KEY.ROLE_ROUTERS)
+  // 注意,不要清理 用户和 LoginForm 登录表单
+}
diff --git a/src/hooks/web/useMessage.ts b/src/hooks/web/useMessage.ts
index 2bbf5cb..ac2b552 100644
--- a/src/hooks/web/useMessage.ts
+++ b/src/hooks/web/useMessage.ts
@@ -90,30 +90,6 @@
         cancelButtonText: t('common.cancel'),
         type: 'warning'
       })
-    },
-    // 启用窗体
-    enableConfirm(ids, content?: string, tip?: string) {
-      return ElMessageBox.confirm(
-        content ? content : t('确定启用选中的'+ ids.length +'项数据?'),
-        tip ? tip : t('common.confirmTitle'),
-        {
-          confirmButtonText: t('common.ok'),
-          cancelButtonText: t('common.cancel'),
-          type: 'warning'
-        }
-      )
-    },
-    // 禁用窗体
-    disableConfirm(ids, content?: string, tip?: string) {
-      return ElMessageBox.confirm(
-        content ? content : t('确定禁用选中的'+ ids.length +'项数据?'),
-        tip ? tip : t('common.confirmTitle'),
-        {
-          confirmButtonText: t('common.ok'),
-          cancelButtonText: t('common.cancel'),
-          type: 'warning'
-        }
-      )
     }
   }
 }
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..c5a1d1a 100644
--- a/src/layout/components/Footer/src/Footer.vue
+++ b/src/layout/components/Footer/src/Footer.vue
@@ -12,13 +12,17 @@
 const appStore = useAppStore()
 
 const title = computed(() => appStore.getTitle)
+
+
+// 添加当前年份计算属性
+const currentYear = computed(() => new Date().getFullYear())
 </script>
 
 <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 ©{{ currentYear }} {{ title }}</span>
   </div>
 </template>
diff --git a/src/layout/components/Logo/src/Logo.vue b/src/layout/components/Logo/src/Logo.vue
index 2d1cfb6..ef80370 100644
--- a/src/layout/components/Logo/src/Logo.vue
+++ b/src/layout/components/Logo/src/Logo.vue
@@ -1,8 +1,17 @@
 <script lang="ts" setup>
 import { computed, onMounted, ref, unref, watch } from 'vue'
 import { useAppStore } from '@/store/modules/app'
+import { useUserStoreWithOut } from '@/store/modules/user'
+import { usePermissionStoreWithOut } from '@/store/modules/permission'
 import { useDesign } from '@/hooks/web/useDesign'
-import * as authUtil from "@/utils/auth";
+import {isRelogin} from "@/config/axios/service";
+import router from "@/router";
+import type {RouteRecordRaw} from "vue-router";
+import {CACHE_KEY, useCache, useSessionCache} from "@/hooks/web/useCache";
+import {getAccessToken} from "@/utils/auth";
+import {getInfo} from "@/api/login";
+const { wsCache } = useCache()
+const { wsSessionCache } = useSessionCache()
 
 defineOptions({ name: 'Logo' })
 
@@ -59,6 +68,22 @@
     }
   }
 )
+
+/** 刷新所有菜单权限 */
+const gotoHome = async () => {
+  const permissionStore = usePermissionStoreWithOut()
+  isRelogin.show = true
+  let userInfo = await getInfo()
+  wsCache.set(CACHE_KEY.USER, userInfo)
+  wsSessionCache.set(CACHE_KEY.ROLE_ROUTERS, userInfo.menus)
+  isRelogin.show = false
+  // 后端过滤菜单
+  await permissionStore.generateRoutes()
+  permissionStore.getAddRouters.forEach((route) => {
+    router.addRoute(route as unknown as RouteRecordRaw) // 动态添加可访问路由表
+  })
+}
+
 </script>
 
 <template>
@@ -69,6 +94,7 @@
         layout !== 'classic' ? `${prefixCls}__Top` : '',
         'flex !h-[var(--logo-height)] items-center cursor-pointer pl-8px relative decoration-none overflow-hidden'
       ]"
+      @click="gotoHome"
       :to="homePath"
     >
       <img
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..714a088 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')
 
 // 锁定屏幕
@@ -43,14 +43,14 @@
     })
     await userStore.loginOut()
     tagsViewStore.delAllViews()
-    replace('/login?redirect=/index')
+    await replace('/login?redirect=/index')
   } catch {}
 }
 const toProfile = async () => {
   push('/user/profile')
 }
 const toDocument = () => {
-  window.open('https://xxxx/')
+  window.open('https://doc.iailab.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/main.ts b/src/main.ts
index cc30f17..ed098e4 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -4,6 +4,19 @@
 // 导入全局的svg图标
 import '@/plugins/svgIcon'
 
+import Iconify from '@iconify/iconify'
+import epJson from '@iconify/json/json/ep.json'
+import faJson from '@iconify/json/json/fa.json'
+import faSolidJson from '@iconify/json/json/fa-solid.json'
+
+Iconify.addCollection(epJson)
+Iconify.addCollection(faJson)
+Iconify.addCollection(faSolidJson)
+
+export * from '@iconify/iconify'
+
+export default Iconify
+
 // 初始化多语言
 import { setupI18n } from '@/plugins/vueI18n'
 
@@ -29,7 +42,7 @@
 import router, { setupRouter } from '@/router'
 
 // 权限
-import { setupAuth } from '@/directives'
+import { setupAuth, setupMountedFocus } from '@/directives'
 
 import { createApp } from 'vue'
 
@@ -74,6 +87,8 @@
 
   setupAuth(app)
 
+  setupMountedFocus(app)
+
   await router.isReady()
 
   app.use(VueDOMPurifyHTML)
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/index.ts b/src/router/index.ts
index 6af4c26..b818421 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -5,7 +5,7 @@
 
 // 创建路由实例
 const router = createRouter({
-  history: createWebHistory('/plat'), // createWebHashHistory URL带#,createWebHistory URL不带#
+  history: createWebHistory(import.meta.env.VITE_BASE_PATH), // createWebHashHistory URL带#,createWebHistory URL不带#
   strict: true,
   routes: remainingRouter as RouteRecordRaw[],
   scrollBehavior: () => ({ left: 0, top: 0 })
diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts
index 5de3d74..f603ca5 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',
@@ -333,6 +351,30 @@
           title: '查看 OA 请假',
           activeMenu: '/bpm/oa/leave'
         }
+      },
+      {
+        path: 'manager/model/create',
+        component: () => import('@/views/bpm/model/form/index.vue'),
+        name: 'BpmModelCreate',
+        meta: {
+          noCache: true,
+          hidden: true,
+          canTo: true,
+          title: '创建流程',
+          activeMenu: '/bpm/manager/model'
+        }
+      },
+      {
+        path: 'manager/model/update/:id',
+        component: () => import('@/views/bpm/model/form/index.vue'),
+        name: 'BpmModelUpdate',
+        meta: {
+          noCache: true,
+          hidden: true,
+          canTo: true,
+          title: '修改流程',
+          activeMenu: '/bpm/manager/model'
+        }
       }
     ]
   },
diff --git a/src/store/modules/app.ts b/src/store/modules/app.ts
index 8733618..e3d6a56 100644
--- a/src/store/modules/app.ts
+++ b/src/store/modules/app.ts
@@ -1,6 +1,6 @@
 import { defineStore } from 'pinia'
 import { store } from '../index'
-import { setCssVar, humpToUnderline } from '@/utils'
+import { humpToUnderline, setCssVar } from '@/utils'
 import { ElMessage } from 'element-plus'
 import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
 import { ElementPlusSize } from '@/types/elementPlus'
@@ -21,6 +21,7 @@
   locale: boolean
   message: boolean
   tagsView: boolean
+  tagsViewImmerse: boolean
   tagsViewIcon: boolean
   logo: boolean
   fixedHeader: boolean
@@ -58,6 +59,7 @@
       locale: true, // 多语言图标
       message: true, // 消息图标
       tagsView: true, // 标签页
+      tagsViewImmerse: false, // 标签页沉浸
       tagsViewIcon: true, // 是否显示标签图标
       logo: true, // logo
       fixedHeader: true, // 固定toolheader
@@ -130,6 +132,9 @@
     },
     getTagsView(): boolean {
       return this.tagsView
+    },
+    getTagsViewImmerse(): boolean {
+      return this.tagsViewImmerse
     },
     getTagsViewIcon(): boolean {
       return this.tagsViewIcon
@@ -208,6 +213,9 @@
     setTagsView(tagsView: boolean) {
       this.tagsView = tagsView
     },
+    setTagsViewImmerse(tagsViewImmerse: boolean) {
+      this.tagsViewImmerse = tagsViewImmerse
+    },
     setTagsViewIcon(tagsViewIcon: boolean) {
       this.tagsViewIcon = tagsViewIcon
     },
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 5e3287a..f32facc 100644
--- a/src/store/modules/permission.ts
+++ b/src/store/modules/permission.ts
@@ -3,9 +3,9 @@
 import { cloneDeep } from 'lodash-es'
 import remainingRouter from '@/router/modules/remaining'
 import { flatMultiLevelRoutes, generateRoute } from '@/utils/routerHelper'
-import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
+import {CACHE_KEY, useSessionCache} from '@/hooks/web/useCache'
 
-const { wsCache } = useCache()
+const { wsSessionCache } = useSessionCache()
 
 export interface PermissionState {
   routers: AppRouteRecordRaw[]
@@ -35,15 +35,17 @@
       return new Promise<void>(async (resolve) => {
         // 获得菜单列表,它在登录的时候,setUserInfoAction 方法中已经进行获取
         let res: AppCustomRouteRecordRaw[] = []
-        if (wsCache.get(CACHE_KEY.ROLE_ROUTERS)) {
-          res = wsCache.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',
+            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/store/modules/user.ts b/src/store/modules/user.ts
index b386180..e4c45fd 100644
--- a/src/store/modules/user.ts
+++ b/src/store/modules/user.ts
@@ -1,10 +1,17 @@
 import { store } from '@/store'
 import { defineStore } from 'pinia'
 import { getAccessToken, removeToken } from '@/utils/auth'
-import { CACHE_KEY, useCache, deleteUserCache } from '@/hooks/web/useCache'
+import {
+  CACHE_KEY,
+  useCache,
+  deleteUserCache,
+  useSessionCache,
+  deleteUserSessionCache
+} from '@/hooks/web/useCache'
 import { getInfo, loginOut } from '@/api/login'
 
 const { wsCache } = useCache()
+const { wsSessionCache } = useSessionCache()
 
 interface UserVO {
   id: number
@@ -62,7 +69,9 @@
       this.user = userInfo.user
       this.isSetUser = true
       wsCache.set(CACHE_KEY.USER, userInfo)
-      wsCache.set(CACHE_KEY.ROLE_ROUTERS, userInfo.menus)
+      if(!wsSessionCache.get(CACHE_KEY.ROLE_ROUTERS)) {
+        wsSessionCache.set(CACHE_KEY.ROLE_ROUTERS, userInfo.menus)
+      }
     },
     async setUserAvatarAction(avatar: string) {
       const userInfo = wsCache.get(CACHE_KEY.USER)
@@ -82,6 +91,7 @@
       await loginOut()
       removeToken()
       deleteUserCache() // 删除用户缓存
+      deleteUserSessionCache() //删除路由缓存
       this.resetState()
     },
     resetState() {
diff --git a/src/styles/global.module.scss b/src/styles/global.module.scss
index 8448a92..af793f0 100644
--- a/src/styles/global.module.scss
+++ b/src/styles/global.module.scss
@@ -1,4 +1,4 @@
-@import './variables.scss';
+@use './variables.scss' as *;
 // 导出变量
 :export {
   namespace: $namespace;
diff --git a/src/styles/index.scss b/src/styles/index.scss
index fbe76f2..7607941 100644
--- a/src/styles/index.scss
+++ b/src/styles/index.scss
@@ -1,6 +1,7 @@
-@import './var.css';
-@import './FormCreate/index.scss';
-@import 'element-plus/theme-chalk/dark/css-vars.css';
+@use './var.css';
+@use './FormCreate/index.scss';
+@use './theme.scss';
+@use 'element-plus/theme-chalk/dark/css-vars.css';
 
 .reset-margin [class*='el-icon'] + span {
   margin-left: 2px !important;
diff --git a/src/styles/var.css b/src/styles/var.css
index 63459ba..44f9405 100644
--- a/src/styles/var.css
+++ b/src/styles/var.css
@@ -64,3 +64,11 @@
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
 }
+
+*,
+:after,
+:before {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+}
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..0795004 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',
@@ -164,7 +165,7 @@
   MODEL_METHOD_SETTING_VALUE_TYPE = 'model_method_setting_value_type',
   PRED_GRANULARITY = 'pred_granularity',
   ITEM_RUN_STATUS = 'item_run_status',
-
+  RESULT_TYPE = 'result_type',
   // ========== DATA - 数据平台模块  ==========
   DATA_FIELD_TYPE = 'data_field_type',
   TAG_DATA_TYPE = 'tag_data_type',
@@ -186,4 +187,5 @@
   CAMERA_BRAND = 'camera_brand',
   CAPTURE_TYPE = 'capture_type',
   MODEL_RESULT_TYPE = 'model_result_type',
+  DATA_QUALITY = 'data_quality'
 }
diff --git a/src/utils/formCreate.ts b/src/utils/formCreate.ts
index 850df8c..a93d9cd 100644
--- a/src/utils/formCreate.ts
+++ b/src/utils/formCreate.ts
@@ -44,6 +44,7 @@
   value?: object
 ) => {
   if (isRef(detailPreview)) {
+    // @ts-ignore
     detailPreview = detailPreview.value
   }
   // @ts-ignore
diff --git a/src/utils/permission.ts b/src/utils/permission.ts
index a63ee62..43d7f95 100644
--- a/src/utils/permission.ts
+++ b/src/utils/permission.ts
@@ -12,7 +12,8 @@
     const { wsCache } = useCache()
     const permissionDatas = value
     const all_permission = '*:*:*'
-    const permissions = wsCache.get(CACHE_KEY.USER).permissions
+    const userInfo = wsCache.get(CACHE_KEY.USER)
+    const permissions = userInfo?.permissions || []
     const hasPermission = permissions.some((permission) => {
       return all_permission === permission || permissionDatas.includes(permission)
     })
@@ -33,7 +34,8 @@
     const { wsCache } = useCache()
     const permissionRoles = value
     const super_admin = 'admin'
-    const roles = wsCache.get(CACHE_KEY.USER).roles
+    const userInfo = wsCache.get(CACHE_KEY.USER)
+    const roles = userInfo?.roles || []
     const hasRole = roles.some((role) => {
       return super_admin === role || permissionRoles.includes(role)
     })
diff --git a/src/utils/routerHelper.ts b/src/utils/routerHelper.ts
index e9c8c64..a4b2295 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) => {
@@ -74,7 +73,7 @@
       noCache: !route.keepAlive,
       alwaysShow:
         route.children &&
-        route.children.length === 1 &&
+        route.children.length > 0 &&
         (route.alwaysShow !== undefined ? route.alwaysShow : true)
     } as any
     // 特殊逻辑:如果后端配置的 MenuDO.component 包含 ?,则表示需要传递参数
@@ -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/utils/tree.ts b/src/utils/tree.ts
index 91059ef..e5db503 100644
--- a/src/utils/tree.ts
+++ b/src/utils/tree.ts
@@ -376,6 +376,9 @@
   let str = ''
 
   function performAThoroughValidation(arr) {
+    if (typeof arr === 'undefined' || !Array.isArray(arr) || arr.length === 0) {
+      return false
+    }
     for (const item of arr) {
       if (item.id === nodeId) {
         str += ` / ${item.name}`
diff --git a/src/views/Home/Index.vue b/src/views/Home/Index.vue
index 822f800..1558788 100644
--- a/src/views/Home/Index.vue
+++ b/src/views/Home/Index.vue
@@ -1,13 +1,20 @@
 <template>
-  <div>
-    <h1>IAILAB 平台主页</h1>
+  <div id="title">
+    <span>工业互联网平台</span>
   </div>
   <el-skeleton :loading="loading" animated>
-    <div id="app" v-for="(item, index) in appList" :key="`dynamics-${index}`">
-      <div class="card" @click="gotoApp(item)">
-        <img :src="item.icon" style="width: 100px; height: 100px"/>
+    <div id="app">
+      <div class="card" v-for="(item, index) in appList" :key="`dynamics-${index}`">
         <div>
-          {{ item.appName }}
+          <img class="card-left" :src="item.icon"/>
+          <div class="card-right">
+            <div class="app-title">
+              {{ item.appName }}
+            </div>
+            <div class="goto-app" @click="gotoApp(item)">
+              <div>进入应用</div>
+            </div>
+          </div>
         </div>
       </div>
     </div>
@@ -18,12 +25,13 @@
 
 import * as AppApi from '@/api/system/app'
 import {Apps} from "@/views/Home/types";
-import {CACHE_KEY, useCache} from "@/hooks/web/useCache";
+import {CACHE_KEY, useCache, useSessionCache} from "@/hooks/web/useCache";
 
 
 defineOptions({name: 'Home'})
 
 const {wsCache} = useCache()
+const {wsSessionCache} = useSessionCache()
 
 const loading = ref(true)
 
@@ -37,10 +45,10 @@
 const getAppMenuList = async (id, appCode) => {
   const data = await AppApi.getAppMenuList(id)
   let userInfo = wsCache.get(CACHE_KEY.USER)
-  userInfo.menus = data
+  // userInfo.menus = data
   wsCache.set(CACHE_KEY.USER, userInfo)
-  wsCache.set(CACHE_KEY.ROLE_ROUTERS, data)
-  window.location.href = '/plat/index'
+  wsSessionCache.set(CACHE_KEY.ROLE_ROUTERS, data)
+  window.location.href = import.meta.env.VITE_BASE_PATH + 'index'
 }
 
 const getAllApi = async () => {
@@ -56,18 +64,18 @@
 const gotoApp = async (item) => {
   let path = window.location.pathname
   let appName = path.split("/")[0]
-  console.log(appName)
   let id = item.id
   let type = item.type
   let appCode = item.appCode
   if (type === 0) {
     await getAppMenuList(id, appCode)
   } else {
-    const data = await AppApi.getAppMenuList(id)
-    let userInfo = wsCache.get(CACHE_KEY.USER)
-    userInfo.menus = data
-    wsCache.set(CACHE_KEY.USER, userInfo)
-    wsCache.set(CACHE_KEY.ROLE_ROUTERS, data)
+    // const data = await AppApi.getAppMenuList(id)
+    // let userInfo = wsCache.get(CACHE_KEY.USER)
+    // userInfo.menus = data
+    // wsCache.set(CACHE_KEY.USER, userInfo)
+    // wsSessionCache.set(CACHE_KEY.ROLE_ROUTERS, data)
+    localStorage.setItem(appCode, id)
     window.open(item.appDomain + '/index', '_blank')
     // window.open('/plat/shasteel', '_blank')
     // window.location.href = '/plat/shasteel'
@@ -78,24 +86,67 @@
 </script>
 
 <style lang="scss" scoped>
+#title {
+  width: 280px;
+  height: 51px;
+  margin: 30px auto;
+  font-family: Microsoft YaHei UI, Microsoft YaHei UI;
+  font-weight: bold;
+  font-size: 40px;
+  color: #282F3D;
+}
+
 #app {
-  width: 300px;
-  height: 200px;
-  display: inline-block;
-  background: transparent;
+  margin: 0 96px;
+  width: 100%;
 }
 
 .card {
-  border: thin dashed gainsboro;
-  width: 150px;
-  height: 120px;
-  padding: 30px;
-  text-align: center;
-  justify-content: center;
-  font-size: 15px;
-  font-weight: bolder;
-  color: blue;
-  background: aliceblue;
-  border-radius: 10px;
+  width: 354px;
+  height: 200px;
+  margin: 0 24px 24px 0;
+  background: linear-gradient(180deg, #E9F0FA 0%, #FFFFFF 100%);
+  border-radius: 12px 12px 12px 12px;
+  border: 2px solid;
+  border-image: linear-gradient(180deg, rgba(255, 255, 255, 1), rgba(255, 255, 255, 1)) 2 2;
+  display: inline-block;
+}
+
+.card-left {
+  height: 100px;
+  width: 100px;
+  float: left;
+  margin: 50px 30px;
+}
+
+.card-right {
+  float: right;
+  margin: 61px 10px;
+}
+
+.app-title {
+  width: 162px;
+  font-family: Microsoft YaHei, Microsoft YaHei;
+  font-weight: bold;
+  font-size: 24px;
+  color: #282F3D;
+}
+
+.goto-app {
+  width: 96px;
+  height: 35px;
+  margin-top: 5px;
+  background: #3A99FD;
+  border-radius: 80px 80px 80px 80px;
+  cursor: pointer;
+}
+
+.goto-app > div {
+  padding: 6px;
+  margin-left: 5px;
+  font-family: Microsoft YaHei UI, Microsoft YaHei UI;
+  font-weight: 400;
+  font-size: 18px;
+  color: #FFFFFF;
 }
 </style>
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..65699c4 100644
--- a/src/views/bpm/form/index.vue
+++ b/src/views/bpm/form/index.vue
@@ -1,5 +1,4 @@
 <template>
-
   <ContentWrap>
     <!-- 搜索工作栏 -->
     <el-form
@@ -142,8 +141,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..f3b5a42
--- /dev/null
+++ b/src/views/bpm/model/CategoryDraggableModel.vue
@@ -0,0 +1,511 @@
+<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="150">
+          <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="150">
+          <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="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 } 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 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) => {
+  if (type === 'create') {
+    push({ name: 'BpmModelCreate' })
+  } else {
+    push({
+      name: 'BpmModelUpdate',
+      params: { 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..095b0ac 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,120 +45,223 @@
           />
         </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-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"
+          >
+            {{ 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"
+          >
+            {{ 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="startUserType">
+        <el-select
+          v-model="formData.startUserType"
+          placeholder="请选择谁可以发起"
+          @change="handleStartUserTypeChange"
+        >
+          <el-option label="全员" :value="0" />
+          <el-option label="指定人员" :value="1" />
+          <el-option label="均不可提交" :value="2" />
+        </el-select>
+        <div v-if="formData.startUserType === 1" class="mt-2 flex flex-wrap gap-2">
+          <div
+            v-for="user in selectedStartUsers"
+            :key="user.id"
+            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
+              icon="ep:close"
+              class="ml-2 cursor-pointer hover:text-red-500"
+              @click="handleRemoveStartUser(user)"
             />
-          </el-select>
-        </el-form-item>
-        <el-form-item
-          v-if="formData.formType === 20"
-          label="表单提交路由"
-          prop="formCustomCreatePath"
+          </div>
+          <el-button type="primary" link @click="openStartUserSelect">
+            <Icon icon="ep:plus" />选择人员
+          </el-button>
+        </div>
+      </el-form-item>
+      <el-form-item label="流程管理员" prop="managerUserType">
+        <el-select
+          v-model="formData.managerUserType"
+          placeholder="请选择流程管理员"
+          @change="handleManagerUserTypeChange"
         >
-          <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-option label="全员" :value="0" />
+          <el-option label="指定人员" :value="1" />
+          <el-option label="均不可提交" :value="2" />
+        </el-select>
+        <div v-if="formData.managerUserType === 1" class="mt-2 flex flex-wrap gap-2">
+          <div
+            v-for="user in selectedManagerUsers"
+            :key="user.id"
+            class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
           >
-            <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"
-          >
-            <i class="el-icon-question" style="padding-left: 5px"></i>
-          </el-tooltip>
-        </el-form-item>
-      </div>
+            <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
+              icon="ep:close"
+              class="ml-2 cursor-pointer hover:text-red-500"
+              @click="handleRemoveManagerUser(user)"
+            />
+          </div>
+          <el-button type="primary" link @click="openManagerUserSelect">
+            <Icon icon="ep:plus" />选择人员
+          </el-button>
+        </div>
+      </el-form-item>
     </el-form>
     <template #footer>
       <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
       <el-button @click="dialogVisible = false">取 消</el-button>
     </template>
   </Dialog>
+  <UserSelectForm ref="userSelectFormRef" @confirm="handleUserSelectConfirm" />
 </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 { CategoryApi, CategoryVO } 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'
+import { FormVO } from '@/api/bpm/form'
 
 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,
+const formData: any = ref({
+  id: undefined,
   name: '',
+  key: '',
   category: undefined,
   icon: undefined,
   description: '',
+  type: BpmModelType.BPMN,
+  formType: BpmModelFormType.NORMAL,
   formId: '',
   formCustomCreatePath: '',
-  formCustomViewPath: ''
+  formCustomViewPath: '',
+  visible: true,
+  startUserType: undefined,
+  managerUserType: undefined,
+  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 formList = ref<FormVO[]>([]) // 流程表单的下拉框的数据
+const categoryList = ref<CategoryVO[]>([]) // 流程分类列表
+const userList = ref<UserVO[]>([]) // 用户列表
+const selectedStartUsers = ref<UserVO[]>([]) // 已选择的发起人列表
+const selectedManagerUsers = ref<UserVO[]>([]) // 已选择的管理员列表
+const userSelectFormRef = ref() // 用户选择弹窗 ref
+const currentSelectType = ref<'start' | 'manager'>('start') // 当前选择的是发起人还是管理员
 
 /** 打开弹窗 */
-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 +274,31 @@
     } finally {
       formLoading.value = false
     }
+    // 加载数据时,根据已有的用户ID列表初始化已选用户
+    if (formData.value.startUserIds?.length) {
+      formData.value.startUserType = 1
+      selectedStartUsers.value = userList.value.filter((user) =>
+        formData.value.startUserIds.includes(user.id)
+      )
+    }
+    if (formData.value.managerUserIds?.length) {
+      formData.value.managerUserType = 1
+      selectedManagerUsers.value = userList.value.filter((user) =>
+        formData.value.managerUserIds.includes(user.id)
+      )
+    }
+  } 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,11 +317,10 @@
       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>' +
+        '另外,每次流程修改后,都需要点击【发布流程】按钮,才能正式生效!!!',
         '重要提示',
         {
           dangerouslyUseHTMLString: true,
@@ -225,15 +342,99 @@
 /** 重置表单 */
 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,
+    startUserType: undefined,
+    managerUserType: undefined,
+    startUserIds: [],
+    managerUserIds: []
   }
   formRef.value?.resetFields()
+  selectedStartUsers.value = []
+  selectedManagerUsers.value = []
+}
+
+/** 处理发起人类型变化 */
+const handleStartUserTypeChange = (value: number) => {
+  if (value !== 1) {
+    selectedStartUsers.value = []
+    formData.value.startUserIds = []
+  }
+}
+
+/** 处理管理员类型变化 */
+const handleManagerUserTypeChange = (value: number) => {
+  if (value !== 1) {
+    selectedManagerUsers.value = []
+    formData.value.managerUserIds = []
+  }
+}
+
+/** 打开发起人选择 */
+const openStartUserSelect = () => {
+  currentSelectType.value = 'start'
+  userSelectFormRef.value.open(0, selectedStartUsers.value)
+}
+
+/** 打开管理员选择 */
+const openManagerUserSelect = () => {
+  currentSelectType.value = 'manager'
+  userSelectFormRef.value.open(0, selectedManagerUsers.value)
+}
+
+/** 处理用户选择确认 */
+const handleUserSelectConfirm = (_, users: UserVO[]) => {
+  if (currentSelectType.value === 'start') {
+    selectedStartUsers.value = users
+    formData.value.startUserIds = users.map((u) => u.id)
+  } else {
+    selectedManagerUsers.value = users
+    formData.value.managerUserIds = users.map((u) => u.id)
+  }
+}
+
+/** 移除发起人 */
+const handleRemoveStartUser = (user: UserVO) => {
+  selectedStartUsers.value = selectedStartUsers.value.filter((u) => u.id !== user.id)
+  formData.value.startUserIds = formData.value.startUserIds.filter((id: number) => id !== user.id)
+}
+
+/** 移除管理员 */
+const handleRemoveManagerUser = (user: UserVO) => {
+  selectedManagerUsers.value = selectedManagerUsers.value.filter((u) => u.id !== user.id)
+  formData.value.managerUserIds = formData.value.managerUserIds.filter(
+    (id: number) => id !== user.id
+  )
 }
 </script>
+
+<style lang="scss" scoped>
+.bg-gray-100 {
+  background-color: #f5f7fa;
+  transition: all 0.3s;
+
+  &:hover {
+    background-color: #e6e8eb;
+  }
+
+  .ep-close {
+    font-size: 14px;
+    color: #909399;
+    transition: color 0.3s;
+
+    &:hover {
+      color: #f56c6c;
+    }
+  }
+}
+</style>
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..37eff73 100644
--- a/src/views/bpm/model/editor/index.vue
+++ b/src/views/bpm/model/editor/index.vue
@@ -3,7 +3,6 @@
     <!-- 流程设计器,负责绘制流程等 -->
     <MyProcessDesigner
       key="designer"
-      v-if="xmlString !== undefined"
       v-model="xmlString"
       :value="xmlString"
       v-bind="controlForm"
@@ -11,12 +10,14 @@
       ref="processDesigner"
       @init-finished="initModeler"
       :additionalModel="controlForm.additionalModel"
+      :model="model"
       @save="save"
     />
     <!-- 流程属性器,负责编辑每个流程节点的属性 -->
     <MyProcessPenal
+      v-if="isModelerReady && modeler"
       key="penal"
-      :bpmnModeler="modeler as any"
+      :bpmnModeler="modeler"
       :prefix="controlForm.prefix"
       class="process-panel"
       :model="model"
@@ -34,12 +35,26 @@
 
 defineOptions({ name: 'BpmModelEditor' })
 
-const router = useRouter() // 路由
-const { query } = useRoute() // 路由的查询
+const props = defineProps<{
+  modelId?: string
+  modelKey?: string
+  modelName?: string
+  value?: string
+}>()
+
+const emit = defineEmits(['success', 'init-finished'])
 const message = useMessage() // 国际化
 
-const xmlString = ref(undefined) // BPMN XML
-const modeler = ref(null) // BPMN Modeler
+// 表单信息
+const formFields = ref<string[]>([])
+const formType = ref(20)
+provide('formFields', formFields)
+provide('formType', formType)
+
+const xmlString = ref<string>('') // BPMN XML
+const modeler = shallowRef() // BPMN Modeler
+const processDesigner = ref()
+const isModelerReady = ref(false)
 const controlForm = ref({
   simulation: true,
   labelEditing: false,
@@ -50,66 +65,215 @@
 })
 const model = ref<ModelApi.ModelVO>() // 流程模型的信息
 
+// 初始化 bpmnInstances
+const initBpmnInstances = () => {
+  if (!modeler.value) return false
+  try {
+    const instances = {
+      modeler: modeler.value,
+      modeling: modeler.value.get('modeling'),
+      moddle: modeler.value.get('moddle'),
+      eventBus: modeler.value.get('eventBus'),
+      bpmnFactory: modeler.value.get('bpmnFactory'),
+      elementFactory: modeler.value.get('elementFactory'),
+      elementRegistry: modeler.value.get('elementRegistry'),
+      replace: modeler.value.get('replace'),
+      selection: modeler.value.get('selection')
+    }
+
+    // 检查所有实例是否都存在
+    return Object.values(instances).every((instance) => instance)
+  } catch (error) {
+    console.error('初始化 bpmnInstances 失败:', error)
+    return false
+  }
+}
+
 /** 初始化 modeler */
-const initModeler = (item) => {
-  setTimeout(() => {
+const initModeler = async (item) => {
+  try {
     modeler.value = item
-  }, 10)
+    // 等待 modeler 初始化完成
+    await nextTick()
+
+    // 确保 modeler 的所有实例都已经准备好
+    if (initBpmnInstances()) {
+      isModelerReady.value = true
+      emit('init-finished')
+
+      // 初始化完成后,设置初始值
+      if (props.modelId) {
+        // 编辑模式
+        const data = await ModelApi.getModel(props.modelId)
+        model.value = {
+          ...data,
+          bpmnXml: undefined // 清空 bpmnXml 属性
+        }
+        xmlString.value = data.bpmnXml || getDefaultBpmnXml(data.key, data.name)
+      } else if (props.modelKey && props.modelName) {
+        // 新建模式
+        xmlString.value = props.value || getDefaultBpmnXml(props.modelKey, props.modelName)
+        model.value = {
+          key: props.modelKey,
+          name: props.modelName
+        } as ModelApi.ModelVO
+      }
+
+      // 导入XML并刷新视图
+      await nextTick()
+      try {
+        await modeler.value.importXML(xmlString.value)
+        if (processDesigner.value?.refresh) {
+          processDesigner.value.refresh()
+        }
+      } catch (error) {
+        console.error('导入XML失败:', error)
+      }
+    } else {
+      console.error('modeler 实例未完全初始化')
+    }
+  } catch (error) {
+    console.error('初始化 modeler 失败:', error)
+  }
+}
+
+/** 获取默认的BPMN XML */
+const getDefaultBpmnXml = (key: string, name: string) => {
+  return `<?xml version="1.0" encoding="UTF-8"?>
+<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.activiti.org/processdef">
+  <process id="${key}" name="${name}" isExecutable="true" />
+  <bpmndi:BPMNDiagram id="BPMNDiagram">
+    <bpmndi:BPMNPlane id="${key}_di" bpmnElement="${key}" />
+  </bpmndi:BPMNDiagram>
+</definitions>`
 }
 
 /** 添加/修改模型 */
-const save = async (bpmnXml) => {
-  const data = {
-    ...model.value,
-    bpmnXml: bpmnXml // bpmnXml 只是初始化流程图,后续修改无法通过它获得
-  } as unknown as ModelApi.ModelVO
-  // 提交
-  if (data.id) {
-    await ModelApi.updateModel(data)
-    message.success('修改成功')
-  } else {
-    await ModelApi.createModel(data)
-    message.success('新增成功')
+const save = async (bpmnXml: string) => {
+  try {
+    xmlString.value = bpmnXml
+    if (props.modelId) {
+      // 编辑模式
+      const data = {
+        ...model.value,
+        bpmnXml: bpmnXml
+      } as unknown as ModelApi.ModelVO
+      await ModelApi.updateModelBpmn(data)
+      emit('success')
+    } else {
+      // 新建模式,直接返回XML
+      emit('success', bpmnXml)
+    }
+  } catch (error) {
+    console.error('保存失败:', error)
+    message.error('保存失败')
   }
-  // 跳转回去
-  close()
 }
 
-/** 关闭按钮 */
-const close = () => {
-  router.push({ path: '/bpm/manager/model' })
+// 监听 key、name 和 value 的变化
+watch(
+  [() => props.modelKey, () => props.modelName, () => props.value],
+  async ([newKey, newName, newValue]) => {
+    if (!props.modelId && isModelerReady.value) {
+      let shouldRefresh = false
+
+      if (newKey && newName) {
+        const newXml = newValue || getDefaultBpmnXml(newKey, newName)
+        if (newXml !== xmlString.value) {
+          xmlString.value = newXml
+          shouldRefresh = true
+        }
+        model.value = {
+          ...model.value,
+          key: newKey,
+          name: newName
+        } as ModelApi.ModelVO
+      } else if (newValue && newValue !== xmlString.value) {
+        xmlString.value = newValue
+        shouldRefresh = true
+      }
+
+      if (shouldRefresh) {
+        // 确保更新后重新渲染
+        await nextTick()
+        if (processDesigner.value?.refresh) {
+          try {
+            await modeler.value?.importXML(xmlString.value)
+            processDesigner.value.refresh()
+          } catch (error) {
+            console.error('导入XML失败:', error)
+          }
+        }
+      }
+    }
+  },
+  { deep: true }
+)
+
+// 在组件卸载时清理
+onBeforeUnmount(() => {
+  isModelerReady.value = false
+  modeler.value = null
+  // 清理全局实例
+  const w = window as any
+  if (w.bpmnInstances) {
+    w.bpmnInstances = null
+  }
+})
+
+/** 获取 XML 字符串 */
+const saveXML = async () => {
+  if (!modeler.value) {
+    return { xml: xmlString.value }
+  }
+  try {
+    const result = await modeler.value.saveXML({ format: true })
+    xmlString.value = result.xml
+    return result
+  } catch (error) {
+    console.error('获取XML失败:', error)
+    return { xml: xmlString.value }
+  }
 }
 
-/** 初始化 */
-onMounted(async () => {
-  const modelId = query.modelId as unknown as number
-  if (!modelId) {
-    message.error('缺少模型 modelId 编号')
-    return
+/** 获取SVG字符串 */
+const saveSVG = async () => {
+  if (!modeler.value) {
+    return { svg: undefined }
   }
-  // 查询模型
-  const data = await ModelApi.getModel(modelId)
-  if (!data.bpmnXml) {
-    // 首次创建的 Model 模型,它是没有 bpmnXml,此时需要给它一个默认的
-    data.bpmnXml = ` <?xml version="1.0" encoding="UTF-8"?>
-<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.activiti.org/processdef">
-  <process id="${data.key}" name="${data.name}" isExecutable="true" />
-  <bpmndi:BPMNDiagram id="BPMNDiagram">
-    <bpmndi:BPMNPlane id="${data.key}_di" bpmnElement="${data.key}" />
-  </bpmndi:BPMNDiagram>
-</definitions>`
+  try {
+    return await modeler.value.saveSVG()
+  } catch (error) {
+    console.error('获取SVG失败:', error)
+    return { svg: undefined }
   }
-  model.value = {
-    ...data,
-    bpmnXml: undefined // 清空 bpmnXml 属性
+}
+
+/** 刷新视图 */
+const refresh = async () => {
+  if (processDesigner.value?.refresh && modeler.value) {
+    try {
+      await modeler.value.importXML(xmlString.value)
+      processDesigner.value.refresh()
+    } catch (error) {
+      console.error('刷新视图失败:', error)
+    }
   }
-  xmlString.value = data.bpmnXml
+}
+
+// 暴露必要的属性和方法给父组件
+defineExpose({
+  modeler,
+  isModelerReady,
+  saveXML,
+  saveSVG,
+  refresh
 })
 </script>
 <style lang="scss">
 .process-panel__container {
   position: absolute;
-  top: 90px;
-  right: 60px;
+  top: 172px;
+  right: 70px;
 }
 </style>
diff --git a/src/views/bpm/model/form/BasicInfo.vue b/src/views/bpm/model/form/BasicInfo.vue
new file mode 100644
index 0000000..0359ea8
--- /dev/null
+++ b/src/views/bpm/model/form/BasicInfo.vue
@@ -0,0 +1,301 @@
+<template>
+  <el-form ref="formRef" :model="modelData" :rules="rules" label-width="120px" class="mt-20px">
+    <el-form-item label="流程标识" prop="key" class="mb-20px">
+      <div class="flex items-center">
+        <el-input
+          class="!w-440px"
+          v-model="modelData.key"
+          :disabled="!!modelData.id"
+          placeholder="请输入流标标识"
+        />
+        <el-tooltip
+          class="item"
+          :content="modelData.id ? '流程标识不可修改!' : '新建后,流程标识不可修改!'"
+          effect="light"
+          placement="top"
+        >
+          <Icon icon="ep:question-filled" class="ml-5px" />
+        </el-tooltip>
+      </div>
+    </el-form-item>
+    <el-form-item label="流程名称" prop="name" class="mb-20px">
+      <el-input
+        v-model="modelData.name"
+        :disabled="!!modelData.id"
+        clearable
+        placeholder="请输入流程名称"
+      />
+    </el-form-item>
+    <el-form-item label="流程分类" prop="category" class="mb-20px">
+      <el-select
+        class="!w-full"
+        v-model="modelData.category"
+        clearable
+        placeholder="请选择流程分类"
+      >
+        <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="流程图标" prop="icon" class="mb-20px">
+      <UploadImg v-model="modelData.icon" :limit="1" height="64px" width="64px" />
+    </el-form-item>
+    <el-form-item label="流程描述" prop="description" class="mb-20px">
+      <el-input v-model="modelData.description" clearable type="textarea" />
+    </el-form-item>
+    <el-form-item label="流程类型" prop="type" class="mb-20px">
+      <el-radio-group v-model="modelData.type">
+        <el-radio
+          v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_TYPE)"
+          :key="dict.value"
+          :value="dict.value"
+        >
+          {{ dict.label }}
+        </el-radio>
+      </el-radio-group>
+    </el-form-item>
+    <el-form-item label="是否可见" prop="visible" class="mb-20px">
+      <el-radio-group v-model="modelData.visible">
+        <el-radio
+          v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
+          :key="dict.value"
+          :value="dict.value"
+        >
+          {{ dict.label }}
+        </el-radio>
+      </el-radio-group>
+    </el-form-item>
+    <el-form-item label="谁可以发起" prop="startUserType" class="mb-20px">
+      <el-select
+        v-model="modelData.startUserType"
+        placeholder="请选择谁可以发起"
+        @change="handleStartUserTypeChange"
+      >
+        <el-option label="全员" :value="0" />
+        <el-option label="指定人员" :value="1" />
+        <el-option label="均不可提交" :value="2" />
+      </el-select>
+      <div v-if="modelData.startUserType === 1" class="mt-2 flex flex-wrap gap-2">
+        <div
+          v-for="user in selectedStartUsers"
+          :key="user.id"
+          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
+            icon="ep:close"
+            class="ml-2 cursor-pointer hover:text-red-500"
+            @click="handleRemoveStartUser(user)"
+          />
+        </div>
+        <el-button type="primary" link @click="openStartUserSelect">
+          <Icon icon="ep:plus" />选择人员
+        </el-button>
+      </div>
+    </el-form-item>
+    <el-form-item label="流程管理员" prop="managerUserType" class="mb-20px">
+      <el-select
+        v-model="modelData.managerUserType"
+        placeholder="请选择流程管理员"
+        @change="handleManagerUserTypeChange"
+      >
+        <el-option label="全员" :value="0" />
+        <el-option label="指定人员" :value="1" />
+        <el-option label="均不可提交" :value="2" />
+      </el-select>
+      <div v-if="modelData.managerUserType === 1" class="mt-2 flex flex-wrap gap-2">
+        <div
+          v-for="user in selectedManagerUsers"
+          :key="user.id"
+          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
+            icon="ep:close"
+            class="ml-2 cursor-pointer hover:text-red-500"
+            @click="handleRemoveManagerUser(user)"
+          />
+        </div>
+        <el-button type="primary" link @click="openManagerUserSelect">
+          <Icon icon="ep:plus" />选择人员
+        </el-button>
+      </div>
+    </el-form-item>
+  </el-form>
+
+  <!-- 用户选择弹窗 -->
+  <UserSelectForm ref="userSelectFormRef" @confirm="handleUserSelectConfirm" />
+</template>
+
+<script lang="ts" setup>
+import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict'
+import { UserVO } from '@/api/system/user'
+
+const props = defineProps({
+  modelValue: {
+    type: Object,
+    required: true
+  },
+  categoryList: {
+    type: Array,
+    required: true
+  },
+  userList: {
+    type: Array,
+    required: true
+  }
+})
+
+const emit = defineEmits(['update:modelValue'])
+
+const formRef = ref()
+const selectedStartUsers = ref<UserVO[]>([])
+const selectedManagerUsers = ref<UserVO[]>([])
+const userSelectFormRef = ref()
+const currentSelectType = ref<'start' | 'manager'>('start')
+
+const rules = {
+  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' }],
+  visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
+  managerUserIds: [{ required: true, message: '流程管理员不能为空', trigger: 'blur' }]
+}
+
+// 创建本地数据副本
+const modelData = computed({
+  get: () => props.modelValue,
+  set: (val) => emit('update:modelValue', val)
+})
+
+// 初始化选中的用户
+watch(
+  () => props.modelValue,
+  (newVal) => {
+    if (newVal.startUserIds?.length) {
+      selectedStartUsers.value = props.userList.filter((user: UserVO) =>
+        newVal.startUserIds.includes(user.id)
+      ) as UserVO[]
+    }
+    if (newVal.managerUserIds?.length) {
+      selectedManagerUsers.value = props.userList.filter((user: UserVO) =>
+        newVal.managerUserIds.includes(user.id)
+      ) as UserVO[]
+    }
+  },
+  { immediate: true }
+)
+
+/** 打开发起人选择 */
+const openStartUserSelect = () => {
+  currentSelectType.value = 'start'
+  userSelectFormRef.value.open(0, selectedStartUsers.value)
+}
+
+/** 打开管理员选择 */
+const openManagerUserSelect = () => {
+  currentSelectType.value = 'manager'
+  userSelectFormRef.value.open(0, selectedManagerUsers.value)
+}
+
+/** 处理用户选择确认 */
+const handleUserSelectConfirm = (_, users: UserVO[]) => {
+  if (currentSelectType.value === 'start') {
+    selectedStartUsers.value = users
+    emit('update:modelValue', {
+      ...modelData.value,
+      startUserIds: users.map((u) => u.id)
+    })
+  } else {
+    selectedManagerUsers.value = users
+    emit('update:modelValue', {
+      ...modelData.value,
+      managerUserIds: users.map((u) => u.id)
+    })
+  }
+}
+
+/** 处理发起人类型变化 */
+const handleStartUserTypeChange = (value: number) => {
+  if (value !== 1) {
+    selectedStartUsers.value = []
+    emit('update:modelValue', {
+      ...modelData.value,
+      startUserIds: []
+    })
+  }
+}
+
+/** 处理管理员类型变化 */
+const handleManagerUserTypeChange = (value: number) => {
+  if (value !== 1) {
+    selectedManagerUsers.value = []
+    emit('update:modelValue', {
+      ...modelData.value,
+      managerUserIds: []
+    })
+  }
+}
+
+/** 移除发起人 */
+const handleRemoveStartUser = (user: UserVO) => {
+  selectedStartUsers.value = selectedStartUsers.value.filter((u) => u.id !== user.id)
+  emit('update:modelValue', {
+    ...modelData.value,
+    startUserIds: modelData.value.startUserIds.filter((id: number) => id !== user.id)
+  })
+}
+
+/** 移除管理员 */
+const handleRemoveManagerUser = (user: UserVO) => {
+  selectedManagerUsers.value = selectedManagerUsers.value.filter((u) => u.id !== user.id)
+  emit('update:modelValue', {
+    ...modelData.value,
+    managerUserIds: modelData.value.managerUserIds.filter((id: number) => id !== user.id)
+  })
+}
+
+/** 表单校验 */
+const validate = async () => {
+  await formRef.value?.validate()
+}
+
+defineExpose({
+  validate
+})
+</script>
+
+<style lang="scss" scoped>
+.bg-gray-100 {
+  background-color: #f5f7fa;
+  transition: all 0.3s;
+
+  &:hover {
+    background-color: #e6e8eb;
+  }
+
+  .ep-close {
+    font-size: 14px;
+    color: #909399;
+    transition: color 0.3s;
+
+    &:hover {
+      color: #f56c6c;
+    }
+  }
+}
+</style>
diff --git a/src/views/bpm/model/form/FormDesign.vue b/src/views/bpm/model/form/FormDesign.vue
new file mode 100644
index 0000000..98aee6d
--- /dev/null
+++ b/src/views/bpm/model/form/FormDesign.vue
@@ -0,0 +1,137 @@
+<template>
+  <el-form ref="formRef" :model="modelData" :rules="rules" label-width="120px" class="mt-20px">
+    <el-form-item label="表单类型" prop="formType" class="mb-20px">
+      <el-radio-group v-model="modelData.formType">
+        <el-radio
+          v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_FORM_TYPE)"
+          :key="dict.value"
+          :value="dict.value"
+        >
+          {{ dict.label }}
+        </el-radio>
+      </el-radio-group>
+    </el-form-item>
+    <el-form-item v-if="modelData.formType === 10" label="流程表单" prop="formId">
+      <el-select v-model="modelData.formId" clearable style="width: 100%">
+        <el-option v-for="form in formList" :key="form.id" :label="form.name" :value="form.id" />
+      </el-select>
+    </el-form-item>
+    <el-form-item v-if="modelData.formType === 20" label="表单提交路由" prop="formCustomCreatePath">
+      <el-input
+        v-model="modelData.formCustomCreatePath"
+        placeholder="请输入表单提交路由"
+        style="width: 330px"
+      />
+      <el-tooltip
+        class="item"
+        content="自定义表单的提交路径,使用 Vue 的路由地址,例如说:bpm/oa/leave/create.vue"
+        effect="light"
+        placement="top"
+      >
+        <Icon icon="ep:question" class="ml-5px" />
+      </el-tooltip>
+    </el-form-item>
+    <el-form-item v-if="modelData.formType === 20" label="表单查看地址" prop="formCustomViewPath">
+      <el-input
+        v-model="modelData.formCustomViewPath"
+        placeholder="请输入表单查看的组件地址"
+        style="width: 330px"
+      />
+      <el-tooltip
+        class="item"
+        content="自定义表单的查看组件地址,使用 Vue 的组件地址,例如说:bpm/oa/leave/detail.vue"
+        effect="light"
+        placement="top"
+      >
+        <Icon icon="ep:question" class="ml-5px" />
+      </el-tooltip>
+    </el-form-item>
+    <!-- 表单预览 -->
+    <div
+      v-if="modelData.formType === 10 && modelData.formId && formPreview.rule.length > 0"
+      class="mt-20px"
+    >
+      <div class="flex items-center mb-15px">
+        <div class="h-15px w-4px bg-[#1890ff] mr-10px"></div>
+        <span class="text-15px font-bold">表单预览</span>
+      </div>
+      <form-create
+        v-model="formPreview.formData"
+        :rule="formPreview.rule"
+        :option="formPreview.option"
+      />
+    </div>
+  </el-form>
+</template>
+
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import * as FormApi from '@/api/bpm/form'
+import { setConfAndFields2 } from '@/utils/formCreate'
+
+const props = defineProps({
+  modelValue: {
+    type: Object,
+    required: true
+  },
+  formList: {
+    type: Array,
+    required: true
+  }
+})
+
+const emit = defineEmits(['update:modelValue'])
+
+const formRef = ref()
+
+// 创建本地数据副本
+const modelData = computed({
+  get: () => props.modelValue,
+  set: (val) => emit('update:modelValue', val)
+})
+
+// 表单预览数据
+const formPreview = ref({
+  formData: {},
+  rule: [],
+  option: {
+    submitBtn: false,
+    resetBtn: false,
+    formData: {}
+  }
+})
+
+// 监听表单ID变化,加载表单数据
+watch(
+  () => modelData.value.formId,
+  async (newFormId) => {
+    if (newFormId && modelData.value.formType === 10) {
+      const data = await FormApi.getForm(newFormId)
+      setConfAndFields2(formPreview.value, data.conf, data.fields)
+      // 设置只读
+      formPreview.value.rule.forEach((item: any) => {
+        item.props = { ...item.props, disabled: true }
+      })
+    } else {
+      formPreview.value.rule = []
+    }
+  },
+  { immediate: true }
+)
+
+const rules = {
+  formType: [{ required: true, message: '表单类型不能为空', trigger: 'blur' }],
+  formId: [{ required: true, message: '流程表单不能为空', trigger: 'blur' }],
+  formCustomCreatePath: [{ required: true, message: '表单提交路由不能为空', trigger: 'blur' }],
+  formCustomViewPath: [{ required: true, message: '表单查看地址不能为空', trigger: 'blur' }]
+}
+
+/** 表单校验 */
+const validate = async () => {
+  await formRef.value?.validate()
+}
+
+defineExpose({
+  validate
+})
+</script>
diff --git a/src/views/bpm/model/form/ProcessDesign.vue b/src/views/bpm/model/form/ProcessDesign.vue
new file mode 100644
index 0000000..40d35ab
--- /dev/null
+++ b/src/views/bpm/model/form/ProcessDesign.vue
@@ -0,0 +1,235 @@
+<template>
+  <!-- BPMN设计器 -->
+  <template v-if="modelData.type === BpmModelType.BPMN">
+    <BpmModelEditor
+      v-if="showDesigner"
+      :model-id="modelData.id"
+      :model-key="modelData.key"
+      :model-name="modelData.name"
+      :value="currentBpmnXml"
+      ref="bpmnEditorRef"
+      @success="handleDesignSuccess"
+      @init-finished="handleEditorInit"
+    />
+  </template>
+
+  <!-- Simple设计器 -->
+  <template v-else>
+    <SimpleModelDesign
+      v-if="showDesigner"
+      :model-id="modelData.id"
+      :model-key="modelData.key"
+      :model-name="modelData.name"
+      :start-user-ids="modelData.startUserIds"
+      :value="currentSimpleModel"
+      ref="simpleEditorRef"
+      @success="handleDesignSuccess"
+      @init-finished="handleEditorInit"
+    />
+  </template>
+</template>
+
+<script lang="ts" setup>
+import { BpmModelType } from '@/utils/constants'
+import BpmModelEditor from '../editor/index.vue'
+import SimpleModelDesign from '../../simple/SimpleModelDesign.vue'
+
+const props = defineProps({
+  modelValue: {
+    type: Object,
+    required: true
+  }
+})
+
+const emit = defineEmits(['update:modelValue', 'success'])
+
+const bpmnEditorRef = ref()
+const simpleEditorRef = ref()
+const isEditorInitialized = ref(false)
+
+// 创建本地数据副本
+const modelData = computed({
+  get: () => props.modelValue,
+  set: (val) => emit('update:modelValue', val)
+})
+
+// 保存当前的流程XML或数据
+const currentBpmnXml = ref('')
+const currentSimpleModel = ref('')
+
+// 初始化或更新当前的XML数据
+const initOrUpdateXmlData = () => {
+  if (modelData.value) {
+    if (modelData.value.type === BpmModelType.BPMN) {
+      currentBpmnXml.value = modelData.value.bpmnXml || ''
+    } else {
+      currentSimpleModel.value = modelData.value.simpleModel || ''
+    }
+  }
+}
+
+// 监听modelValue的变化,更新数据
+watch(
+  () => props.modelValue,
+  (newVal) => {
+    if (newVal) {
+      if (newVal.type === BpmModelType.BPMN) {
+        if (newVal.bpmnXml && newVal.bpmnXml !== currentBpmnXml.value) {
+          currentBpmnXml.value = newVal.bpmnXml
+          // 如果编辑器已经初始化,刷新视图
+          if (isEditorInitialized.value && bpmnEditorRef.value?.refresh) {
+            nextTick(() => {
+              bpmnEditorRef.value.refresh()
+            })
+          }
+        }
+      } else {
+        if (newVal.simpleModel && newVal.simpleModel !== currentSimpleModel.value) {
+          currentSimpleModel.value = newVal.simpleModel
+          // 如果编辑器已经初始化,刷新视图
+          if (isEditorInitialized.value && simpleEditorRef.value?.refresh) {
+            nextTick(() => {
+              simpleEditorRef.value.refresh()
+            })
+          }
+        }
+      }
+    }
+  },
+  { immediate: true, deep: true }
+)
+
+/** 编辑器初始化完成的回调 */
+const handleEditorInit = async () => {
+  isEditorInitialized.value = true
+
+  // 等待下一个tick,确保编辑器已经准备好
+  await nextTick()
+
+  // 初始化完成后,设置初始值
+  if (modelData.value.type === BpmModelType.BPMN) {
+    if (modelData.value.bpmnXml) {
+      currentBpmnXml.value = modelData.value.bpmnXml
+      if (bpmnEditorRef.value?.refresh) {
+        await nextTick()
+        bpmnEditorRef.value.refresh()
+      }
+    }
+  } else {
+    if (modelData.value.simpleModel) {
+      currentSimpleModel.value = modelData.value.simpleModel
+      if (simpleEditorRef.value?.refresh) {
+        await nextTick()
+        simpleEditorRef.value.refresh()
+      }
+    }
+  }
+}
+
+/** 获取当前流程数据 */
+const getProcessData = async () => {
+  try {
+    if (modelData.value.type === BpmModelType.BPMN) {
+      if (!bpmnEditorRef.value || !isEditorInitialized.value) {
+        return currentBpmnXml.value || undefined
+      }
+      const { xml } = await bpmnEditorRef.value.saveXML()
+      if (xml) {
+        currentBpmnXml.value = xml
+        return xml
+      }
+    } else {
+      if (!simpleEditorRef.value || !isEditorInitialized.value) {
+        return currentSimpleModel.value || undefined
+      }
+      const flowData = await simpleEditorRef.value.getCurrentFlowData()
+      if (flowData) {
+        currentSimpleModel.value = flowData
+        return flowData
+      }
+    }
+    return modelData.value.type === BpmModelType.BPMN
+      ? currentBpmnXml.value
+      : currentSimpleModel.value
+  } catch (error) {
+    console.error('获取流程数据失败:', error)
+    return modelData.value.type === BpmModelType.BPMN
+      ? currentBpmnXml.value
+      : currentSimpleModel.value
+  }
+}
+
+/** 表单校验 */
+const validate = async () => {
+  try {
+    // 获取最新的流程数据
+    const processData = await getProcessData()
+    if (!processData) {
+      throw new Error('请设计流程')
+    }
+    return true
+  } catch (error) {
+    throw error
+  }
+}
+
+/** 处理设计器保存成功 */
+const handleDesignSuccess = async (data?: any) => {
+  if (data) {
+    if (modelData.value.type === BpmModelType.BPMN) {
+      currentBpmnXml.value = data
+    } else {
+      currentSimpleModel.value = data
+    }
+
+    // 创建新的对象以触发响应式更新
+    const newModelData = {
+      ...modelData.value,
+      bpmnXml: modelData.value.type === BpmModelType.BPMN ? data : null,
+      simpleModel: modelData.value.type === BpmModelType.BPMN ? null : data
+    }
+
+    // 使用emit更新父组件的数据
+    await nextTick()
+    emit('update:modelValue', newModelData)
+    emit('success', data)
+  }
+}
+
+/** 是否显示设计器 */
+const showDesigner = computed(() => {
+  return Boolean(modelData.value?.key && modelData.value?.name)
+})
+
+// 组件创建时初始化数据
+onMounted(() => {
+  initOrUpdateXmlData()
+})
+
+// 组件卸载前保存数据
+onBeforeUnmount(async () => {
+  try {
+    // 获取并保存最新的流程数据
+    const data = await getProcessData()
+    if (data) {
+      // 创建新的对象以触发响应式更新
+      const newModelData = {
+        ...modelData.value,
+        bpmnXml: modelData.value.type === BpmModelType.BPMN ? data : null,
+        simpleModel: modelData.value.type === BpmModelType.BPMN ? null : data
+      }
+
+      // 使用emit更新父组件的数据
+      await nextTick()
+      emit('update:modelValue', newModelData)
+    }
+  } catch (error) {
+    console.error('保存数据失败:', error)
+  }
+})
+
+defineExpose({
+  validate,
+  getProcessData
+})
+</script>
diff --git a/src/views/bpm/model/form/index.vue b/src/views/bpm/model/form/index.vue
new file mode 100644
index 0000000..4585fc6
--- /dev/null
+++ b/src/views/bpm/model/form/index.vue
@@ -0,0 +1,439 @@
+<template>
+  <ContentWrap>
+    <div class="mx-auto">
+      <!-- 头部导航栏 -->
+      <div
+        class="absolute top-0 left-0 right-0 h-50px bg-white border-bottom z-10 flex items-center px-20px"
+      >
+        <!-- 左侧标题 -->
+        <div class="w-200px flex items-center overflow-hidden">
+          <Icon icon="ep:arrow-left" class="cursor-pointer flex-shrink-0" @click="handleBack" />
+          <span class="ml-10px text-16px truncate" :title="formData.name || '创建流程'">
+            {{ formData.name || '创建流程' }}
+          </span>
+        </div>
+
+        <!-- 步骤条 -->
+        <div class="flex-1 flex items-center justify-center h-full">
+          <div class="w-400px flex items-center justify-between h-full">
+            <div
+              v-for="(step, index) in steps"
+              :key="index"
+              class="flex items-center cursor-pointer mx-15px relative h-full"
+              :class="[
+                currentStep === index
+                  ? 'text-[#3473ff] border-[#3473ff] border-b-2 border-b-solid'
+                  : 'text-gray-500'
+              ]"
+              @click="handleStepClick(index)"
+            >
+              <div
+                class="w-28px h-28px rounded-full flex items-center justify-center mr-8px border-2 border-solid text-15px"
+                :class="[
+                  currentStep === index
+                    ? 'bg-[#3473ff] text-white border-[#3473ff]'
+                    : 'border-gray-300 bg-white text-gray-500'
+                ]"
+              >
+                {{ index + 1 }}
+              </div>
+              <span class="text-16px font-bold whitespace-nowrap">{{ step.title }}</span>
+            </div>
+          </div>
+        </div>
+
+        <!-- 右侧按钮 -->
+        <div class="w-200px flex items-center justify-end gap-2">
+          <el-button v-if="route.params.id" type="success" @click="handleDeploy">发 布</el-button>
+          <el-button type="primary" @click="handleSave">保 存</el-button>
+        </div>
+      </div>
+
+      <!-- 主体内容 -->
+      <div class="mt-50px">
+        <!-- 第一步:基本信息 -->
+        <div v-if="currentStep === 0" class="mx-auto w-560px">
+          <BasicInfo
+            v-model="formData"
+            :categoryList="categoryList"
+            :userList="userList"
+            ref="basicInfoRef"
+          />
+        </div>
+
+        <!-- 第二步:表单设计 -->
+        <div v-if="currentStep === 1" class="mx-auto w-560px">
+          <FormDesign v-model="formData" :formList="formList" ref="formDesignRef" />
+        </div>
+
+        <!-- 第三步:流程设计 -->
+        <ProcessDesign
+          v-if="currentStep === 2"
+          v-model="formData"
+          ref="processDesignRef"
+          @success="handleDesignSuccess"
+        />
+      </div>
+    </div>
+  </ContentWrap>
+</template>
+
+<script lang="ts" setup>
+import { useRoute, useRouter } from 'vue-router'
+import { useMessage } from '@/hooks/web/useMessage'
+import * as ModelApi from '@/api/bpm/model'
+import * as FormApi from '@/api/bpm/form'
+import { CategoryApi } from '@/api/bpm/category'
+import * as UserApi from '@/api/system/user'
+import { useUserStoreWithOut } from '@/store/modules/user'
+import { BpmModelFormType, BpmModelType } from '@/utils/constants'
+import BasicInfo from './BasicInfo.vue'
+import FormDesign from './FormDesign.vue'
+import ProcessDesign from './ProcessDesign.vue'
+import { useTagsViewStore } from '@/store/modules/tagsView'
+
+const router = useRouter()
+const { delView } = useTagsViewStore() // 视图操作
+const route = useRoute()
+const message = useMessage()
+const userStore = useUserStoreWithOut()
+
+// 组件引用
+const basicInfoRef = ref()
+const formDesignRef = ref()
+const processDesignRef = ref()
+
+/** 步骤校验函数 */
+const validateBasic = async () => {
+  await basicInfoRef.value?.validate()
+}
+
+/** 表单设计校验 */
+const validateForm = async () => {
+  await formDesignRef.value?.validate()
+}
+
+/** 流程设计校验 */
+const validateProcess = async () => {
+  await processDesignRef.value?.validate()
+}
+
+const currentStep = ref(0) // 步骤控制
+const steps = [
+  { title: '基本信息', validator: validateBasic },
+  { title: '表单设计', validator: validateForm },
+  { title: '流程设计', validator: validateProcess }
+]
+
+// 表单数据
+const formData: any = ref({
+  id: undefined,
+  name: '',
+  key: '',
+  category: undefined,
+  icon: undefined,
+  description: '',
+  type: BpmModelType.BPMN,
+  formType: BpmModelFormType.NORMAL,
+  formId: '',
+  formCustomCreatePath: '',
+  formCustomViewPath: '',
+  visible: true,
+  startUserType: undefined,
+  managerUserType: undefined,
+  startUserIds: [],
+  managerUserIds: []
+})
+
+// 数据列表
+const formList = ref([])
+const categoryList = ref([])
+const userList = ref<UserApi.UserVO[]>([])
+
+/** 初始化数据 */
+const initData = async () => {
+  const modelId = route.params.id as string
+  if (modelId) {
+    // 修改场景
+    formData.value = await ModelApi.getModel(modelId)
+  } else {
+    // 新增场景
+    formData.value.managerUserIds.push(userStore.getUser.id)
+  }
+
+  // 获取表单列表
+  formList.value = await FormApi.getFormSimpleList()
+  // 获取分类列表
+  categoryList.value = await CategoryApi.getCategorySimpleList()
+  // 获取用户列表
+  userList.value = await UserApi.getSimpleUserList()
+}
+
+/** 校验所有步骤数据是否完整 */
+const validateAllSteps = async () => {
+  try {
+    // 基本信息校验
+    await basicInfoRef.value?.validate()
+    if (!formData.value.key || !formData.value.name || !formData.value.category) {
+      currentStep.value = 0
+      throw new Error('请完善基本信息')
+    }
+
+    // 表单设计校验
+    await formDesignRef.value?.validate()
+    if (formData.value.formType === 10 && !formData.value.formId) {
+      currentStep.value = 1
+      throw new Error('请选择流程表单')
+    }
+    if (
+      formData.value.formType === 20 &&
+      (!formData.value.formCustomCreatePath || !formData.value.formCustomViewPath)
+    ) {
+      currentStep.value = 1
+      throw new Error('请完善自定义表单信息')
+    }
+
+    // 流程设计校验
+    // 如果已经有流程数据,则不需要重新校验
+    if (!formData.value.bpmnXml && !formData.value.simpleModel) {
+      // 如果当前不在第三步,需要先保存当前步骤数据
+      if (currentStep.value !== 2) {
+        await steps[currentStep.value].validator()
+        // 切换到第三步
+        currentStep.value = 2
+        // 等待组件渲染完成
+        await nextTick()
+      }
+
+      // 校验流程设计
+      await processDesignRef.value?.validate()
+      const processData = await processDesignRef.value?.getProcessData()
+      if (!processData) {
+        throw new Error('请设计流程')
+      }
+
+      // 保存流程数据
+      if (formData.value.type === BpmModelType.BPMN) {
+        formData.value.bpmnXml = processData
+        formData.value.simpleModel = null
+      } else {
+        formData.value.bpmnXml = null
+        formData.value.simpleModel = processData
+      }
+    }
+
+    return true
+  } catch (error) {
+    throw error
+  }
+}
+
+/** 保存操作 */
+const handleSave = async () => {
+  try {
+    // 保存前校验所有步骤的数据
+    await validateAllSteps()
+
+    // 更新表单数据
+    const modelData = {
+      ...formData.value
+    }
+
+    // 如果当前在第三步,获取最新的流程设计数据
+    if (currentStep.value === 2) {
+      const processData = await processDesignRef.value?.getProcessData()
+      if (processData) {
+        if (formData.value.type === BpmModelType.BPMN) {
+          modelData.bpmnXml = processData
+          modelData.simpleModel = null
+        } else {
+          modelData.bpmnXml = null
+          modelData.simpleModel = processData
+        }
+      }
+    }
+
+    if (formData.value.id) {
+      // 修改场景
+      await ModelApi.updateModel(modelData)
+      // 询问是否发布流程
+      try {
+        await message.confirm('修改流程成功,是否发布流程?')
+        // 用户点击确认,执行发布
+        await handleDeploy()
+      } catch {
+        // 用户点击取消,停留在当前页面
+      }
+    } else {
+      // 新增场景
+      formData.value.id = await ModelApi.createModel(modelData)
+      message.success('新增成功')
+      try {
+        await message.confirm('创建流程成功,是否继续编辑?')
+        // 用户点击继续编辑,跳转到编辑页面
+        await nextTick()
+        // 先删除当前页签
+        delView(unref(router.currentRoute))
+        // 跳转到编辑页面
+        await router.push({
+          name: 'BpmModelUpdate',
+          params: { id: formData.value.id }
+        })
+      } catch {
+        // 先删除当前页签
+        delView(unref(router.currentRoute))
+        // 用户点击返回列表
+        await router.push({ name: 'BpmModel' })
+      }
+    }
+  } catch (error: any) {
+    console.error('保存失败:', error)
+    message.warning(error.message || '请完善所有步骤的必填信息')
+  }
+}
+
+/** 发布操作 */
+const handleDeploy = async () => {
+  try {
+    // 修改场景下直接发布,新增场景下需要先确认
+    if (!formData.value.id) {
+      await message.confirm('是否确认发布该流程?')
+    }
+
+    // 校验所有步骤
+    await validateAllSteps()
+
+    // 更新表单数据
+    const modelData = {
+      ...formData.value
+    }
+
+    // 如果当前在第三步,获取最新的流程设计数据
+    if (currentStep.value === 2) {
+      const processData = await processDesignRef.value?.getProcessData()
+      if (processData) {
+        if (formData.value.type === BpmModelType.BPMN) {
+          modelData.bpmnXml = processData
+          modelData.simpleModel = null
+        } else {
+          modelData.bpmnXml = null
+          modelData.simpleModel = processData
+        }
+      }
+    }
+
+    // 先保存所有数据
+    if (formData.value.id) {
+      await ModelApi.updateModel(modelData)
+    } else {
+      const result = await ModelApi.createModel(modelData)
+      formData.value.id = result.id
+    }
+
+    // 发布
+    await ModelApi.deployModel(formData.value.id)
+    message.success('发布成功')
+    // 返回列表页
+    await router.push({ name: 'BpmModel' })
+  } catch (error: any) {
+    console.error('发布失败:', error)
+    message.warning(error.message || '发布失败')
+  }
+}
+
+/** 步骤切换处理 */
+const handleStepClick = async (index: number) => {
+  try {
+    // 如果是切换到第三步(流程设计),需要校验key和name
+    if (index === 2) {
+      if (!formData.value.key || !formData.value.name) {
+        message.warning('请先填写流程标识和流程名称')
+        return
+      }
+    }
+
+    // 保存当前步骤的数据
+    if (currentStep.value === 2) {
+      const processData = await processDesignRef.value?.getProcessData()
+      if (processData) {
+        if (formData.value.type === BpmModelType.BPMN) {
+          formData.value.bpmnXml = processData
+          formData.value.simpleModel = null
+        } else {
+          formData.value.bpmnXml = null
+          formData.value.simpleModel = processData
+        }
+      }
+    } else {
+      // 只有在向后切换时才进行校验
+      if (index > currentStep.value) {
+        if (typeof steps[currentStep.value].validator === 'function') {
+          await steps[currentStep.value].validator()
+        }
+      }
+    }
+
+    // 切换步骤
+    currentStep.value = index
+
+    // 如果切换到流程设计步骤,等待组件渲染完成后刷新设计器
+    if (index === 2) {
+      await nextTick()
+      // 等待更长时间确保组件完全初始化
+      await new Promise(resolve => setTimeout(resolve, 200))
+      if (processDesignRef.value?.refresh) {
+        await processDesignRef.value.refresh()
+      }
+    }
+  } catch (error) {
+    console.error('步骤切换失败:', error)
+    message.warning('请先完善当前步骤必填信息')
+  }
+}
+
+/** 处理设计器保存成功 */
+const handleDesignSuccess = (bpmnXml?: string) => {
+  if (bpmnXml) {
+    formData.value.bpmnXml = bpmnXml
+  }
+}
+
+/** 返回列表页 */
+const handleBack = () => {
+  // 先删除当前页签
+  delView(unref(router.currentRoute))
+  // 跳转到列表页
+  router.push({ name: 'BpmModel' })
+}
+
+/** 初始化 */
+onMounted(async () => {
+  await initData()
+})
+
+// 添加组件卸载前的清理代码
+onBeforeUnmount(() => {
+  // 清理所有的引用
+  basicInfoRef.value = null
+  formDesignRef.value = null
+  processDesignRef.value = null
+})
+</script>
+
+<style lang="scss" scoped>
+.border-bottom {
+  border-bottom: 1px solid #dcdfe6;
+}
+
+.text-primary {
+  color: #3473ff;
+}
+
+.bg-primary {
+  background-color: #3473ff;
+}
+
+.border-primary {
+  border-color: #3473ff;
+}
+</style>
diff --git a/src/views/bpm/model/index.vue b/src/views/bpm/model/index.vue
index a20ea4e..c7d9417 100644
--- a/src/views/bpm/model/index.vue
+++ b/src/views/bpm/model/index.vue
@@ -1,352 +1,138 @@
 <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 { push } = useRouter()
 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()
 }
 
 /** 添加/修改操作 */
 const formRef = ref()
 const openForm = (type: string, id?: number) => {
-  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
+  if (type === 'create') {
+    push({ name: 'BpmModelCreate' })
+  } else {
+    push({
+      name: 'BpmModelUpdate',
+      params: { id }
+    })
   }
-}
-
-/** 设计流程 */
-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
-    }
-  })
 }
 
 /** 流程表单的详情按钮操作 */
@@ -355,36 +141,89 @@
   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..7eaf0f4
--- /dev/null
+++ b/src/views/bpm/processInstance/create/ProcessDefinitionDetail.vue
@@ -0,0 +1,298 @@
+<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" v-loading="processInstanceStartLoading">
+              <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,
+  NodeId,
+  FieldPermissionType
+} 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 processInstanceStartLoading = ref(false) // 流程实例发起中
+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 {
+    // TODO 获取审批详情,设置 activityId 为发起人节点(为了获取字段权限。暂时只对 Simple 设计器有效)
+    const data = await ProcessInstanceApi.getApprovalDetail({
+      processDefinitionId: row.id,
+      activityId: NodeId.START_USER_NODE_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
+    // 获取表单字段权限
+    const formFieldsPermission = data.formFieldsPermission
+    // 设置表单字段权限
+    if (formFieldsPermission) {
+      Object.keys(formFieldsPermission).forEach((item) => {
+        setFieldPermission(item, formFieldsPermission[item])
+      })
+    }
+  } finally {
+  }
+}
+
+/**
+ * 设置表单权限
+ */
+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)
+  }
+}
+
+/** 提交按钮 */
+const submitForm = async () => {
+  if (!fApi.value || !props.selectProcessDefinition) {
+    return
+  }
+  // 流程表单校验
+  await fApi.value.validate()
+  // 如果有指定审批人,需要校验
+  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}的候选人`)
+    }
+  }
+
+  // 提交请求
+  processInstanceStartLoading.value = 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 {
+    processInstanceStartLoading.value = 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..1b3ebc5
--- /dev/null
+++ b/src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue
@@ -0,0 +1,989 @@
+<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="approveFormRef"
+          :model="approveReasonForm"
+          :rules="approveReasonRule"
+          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="approveReasonForm.reason"
+              placeholder="请输入审批意见"
+              type="textarea"
+              :rows="4"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button :disabled="formLoading" type="success" @click="handleAudit(true, approveFormRef)">
+              {{ getButtonDisplayName(OperationButtonType.APPROVE) }}
+            </el-button>
+            <el-button @click="closePropover('approve', approveFormRef)"> 取消 </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="rejectFormRef"
+          :model="rejectReasonForm"
+          :rules="rejectReasonRule"
+          label-width="100px"
+        >
+          <el-form-item label="审批意见" prop="reason">
+            <el-input
+              v-model="rejectReasonForm.reason"
+              placeholder="请输入审批意见"
+              type="textarea"
+              :rows="4"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button :disabled="formLoading" type="danger" @click="handleAudit(false,rejectFormRef)">
+              {{ getButtonDisplayName(OperationButtonType.REJECT) }}
+            </el-button>
+            <el-button @click="closePropover('reject', rejectFormRef)"> 取消 </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="copyFormRef"
+          :model="copyForm"
+          :rules="copyFormRule"
+          label-width="100px"
+        >
+          <el-form-item label="抄送人" prop="copyUserIds">
+            <el-select
+              v-model="copyForm.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="copyForm.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="closePropover('copy', copyFormRef)"> 取消 </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="transferFormRef"
+          :model="transferForm"
+          :rules="transferFormRule"
+          label-width="100px"
+        >
+          <el-form-item label="新审批人" prop="assigneeUserId">
+            <el-select v-model="transferForm.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="transferForm.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="closePropover('transfer', transferFormRef)"> 取消 </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="delegateFormRef"
+          :model="delegateForm"
+          :rules="delegateFormRule"
+          label-width="100px"
+        >
+          <el-form-item label="接收人" prop="delegateUserId">
+            <el-select v-model="delegateForm.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="delegateForm.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="closePropover('delegate', delegateFormRef)"> 取消 </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="addSignFormRef"
+          :model="addSignForm"
+          :rules="addSignFormRule"
+          label-width="100px"
+        >
+          <el-form-item label="加签处理人" prop="addSignUserIds">
+            <el-select v-model="addSignForm.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="addSignForm.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="closePropover('addSign', addSignFormRef)"> 取消 </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="deleteSignFormRef"
+          :model="deleteSignForm"
+          :rules="deleteSignFormRule"
+          label-width="100px"
+        >
+          <el-form-item label="减签人员" prop="deleteSignTaskId">
+            <el-select v-model="deleteSignForm.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="deleteSignForm.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="closePropover('deleteSign', deleteSignFormRef)"> 取消 </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="openPopover('return')" 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="returnFormRef"
+          :model="returnForm"
+          :rules="returnFormRule"
+          label-width="100px"
+        >
+          <el-form-item label="退回节点" prop="targetTaskDefinitionKey">
+            <el-select v-model="returnForm.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="returnForm.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="closePropover('return', returnFormRef)"> 取消 </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="cancelFormRef"
+          :model="cancelForm"
+          :rules="cancelFormRule"
+          label-width="100px"
+        >
+          <el-form-item label="取消理由" prop="cancelReason">
+            <span class="text-#878c93 text-12px">&nbsp; 取消后,该审批流程将自动结束</span>
+            <el-input
+              v-model="cancelForm.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="closePropover('cancel', cancelFormRef)"> 取消 </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 * as UserApi from '@/api/system/user'
+import {
+  OperationButtonType,
+  OPERATION_BUTTON_NAME
+} from '@/components/SimpleProcessDesignerV2/src/consts'
+import { BpmProcessInstanceStatus, BpmModelFormType } from '@/utils/constants'
+import type { FormInstance, FormRules } from 'element-plus'
+defineOptions({ name: 'ProcessInstanceBtnContainer' })
+
+const router = useRouter() // 路由
+const message = useMessage() // 消息弹窗
+
+const userId = useUserStoreWithOut().getUser.id // 当前登录的编号
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+
+const props = defineProps< {
+  processInstance: any,  // 流程实例信息
+  processDefinition: any,  // 流程定义信息
+  userOptions: UserApi.UserVO[],
+  normalForm: any, // 流程表单 formCreate
+  normalFormApi: any, // 流程表单 formCreate Api
+  writableFields: string[] // 流程表单可以编辑的字段
+}>()
+
+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 approveForm = ref<any>({}) // 审批通过时,额外的补充信息
+const approveFormFApi = ref<any>({}) // approveForms 的 fAPi
+
+// 审批通过意见表单
+const approveFormRef = ref<FormInstance>()
+const approveReasonForm = reactive({
+  reason: ''
+})
+const approveReasonRule = reactive<FormRules<typeof approveReasonForm>>({
+  reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
+})
+// 拒绝表单
+const rejectFormRef = ref<FormInstance>()
+const rejectReasonForm = reactive({
+  reason: ''
+})
+const rejectReasonRule = reactive<FormRules<typeof rejectReasonForm>>({
+  reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
+})
+
+// 抄送表单
+const copyFormRef = ref<FormInstance>()
+const copyForm = reactive({
+  copyUserIds: [],
+  copyReason: ''
+})
+const copyFormRule = reactive<FormRules<typeof copyForm>>({
+  copyUserIds: [{ required: true, message: '抄送人不能为空', trigger: 'change' }]
+})
+
+// 转办表单
+const transferFormRef = ref<FormInstance>()
+const transferForm = reactive({
+  assigneeUserId: undefined,
+  reason: ''
+})
+const transferFormRule = reactive<FormRules<typeof transferForm>>({
+  assigneeUserId: [{ required: true, message: '新审批人不能为空', trigger: 'change' }],
+  reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
+})
+
+// 委派表单
+const delegateFormRef = ref<FormInstance>()
+const delegateForm = reactive({
+  delegateUserId: undefined,
+  reason: ''
+})
+const delegateFormRule = reactive<FormRules<typeof delegateForm>>({
+  delegateUserId: [{ required: true, message: '接收人不能为空', trigger: 'change' }],
+  reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
+})
+
+// 加签表单
+const addSignFormRef = ref<FormInstance>()
+const addSignForm = reactive({
+  addSignUserIds: undefined,
+  reason: ''
+})
+const addSignFormRule = reactive<FormRules<typeof addSignForm>>({
+  addSignUserIds: [{ required: true, message: '加签处理人不能为空', trigger: 'change' }],
+  reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
+})
+
+// 减签表单
+const deleteSignFormRef = ref<FormInstance>()
+const deleteSignForm = reactive({
+  deleteSignTaskId: undefined,
+  reason: ''
+})
+const deleteSignFormRule = reactive<FormRules<typeof deleteSignForm>>({
+  deleteSignTaskId: [{ required: true, message: '减签人员不能为空', trigger: 'change' }],
+  reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
+})
+
+// 退回表单
+const returnFormRef = ref<FormInstance>()
+const returnForm = reactive({
+  targetTaskDefinitionKey: undefined,
+  returnReason: ''
+})
+const returnFormRule = reactive<FormRules<typeof returnForm>>({
+  targetTaskDefinitionKey: [{ required: true, message: '退回节点不能为空', trigger: 'change' }],
+  returnReason: [{ required: true, message: '退回理由不能为空', trigger: 'blur' }]
+})
+
+// 取消表单
+const cancelFormRef = ref<FormInstance>()
+const cancelForm = reactive({
+  cancelReason: ''
+})
+const cancelFormRule = reactive<FormRules<typeof cancelForm>>({
+  cancelReason: [{ required: true, message: '取消理由不能为空', trigger: 'blur' }],
+})
+
+/** 监听 approveFormFApis,实现它对应的 form-create 初始化后,隐藏掉对应的表单提交按钮 */
+watch(
+  () => approveFormFApi.value,
+  (val) => {
+    val?.btn?.show(false)
+    val?.resetBtn?.show(false)
+  },
+  {
+    deep: true
+  }
+)
+
+/** 弹出气泡卡 */
+const openPopover = async (type: string) => {
+  if (type === 'approve') {
+    // 校验流程表单
+    const valid = await validateNormalForm();
+    if (!valid) {
+      message.warning('表单校验不通过,请先完善表单!!')
+      return;
+    }
+  }
+  if (type === 'return') {
+    // 获取退回节点
+    returnList.value = await TaskApi.getTaskListByReturn(runningTask.value.id)
+    if (returnList.value.length === 0) {
+      message.warning('当前没有可退回的节点')
+      return
+    }
+  }
+  Object.keys(popOverVisible.value).forEach((item) => {
+    popOverVisible.value[item] = item === type
+  })
+  // await nextTick()
+  // formRef.value.resetFields()
+}
+
+/** 关闭气泡卡 */
+const closePropover = (type: string, formRef: FormInstance | undefined) => {
+  if (formRef) {
+    formRef.resetFields()
+  }
+  popOverVisible.value[type] = false
+}
+
+/** 处理审批通过和不通过的操作 */
+const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) => {
+  formLoading.value = true
+  try {
+    // 校验表单
+    if (!formRef) return
+    await formRef.validate()
+    if (pass) {
+      // 获取修改的流程变量, 暂时只支持流程表单
+      const variables = getUpdatedProcessInstanceVaiables();
+      // 审批通过数据
+      const data = {
+        id: runningTask.value.id,
+        reason: approveReasonForm.reason,
+        variables // 审批通过, 把修改的字段值赋于流程实例变量
+      }
+      // 多表单处理,并且有额外的 approveForm 表单,需要校验 + 拼接到 data 表单里提交
+      // TODO 芋艿 任务有多表单这里要如何处理,会和可编辑的字段冲突
+      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 {
+      // 审批不通过数据
+      const data = {
+        id: runningTask.value.id,
+        reason: rejectReasonForm.reason,
+      }
+      await TaskApi.rejectTask(data)
+      popOverVisible.value.reject = false
+      message.success('审批不通过成功')
+    }
+    // 重置表单
+    formRef.resetFields()
+    // 加载最新数据
+    reload()
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 处理抄送 */
+const handleCopy = async () => {
+  formLoading.value = true
+  try {
+    // 1. 校验表单
+    if (!copyFormRef.value) return
+    await copyFormRef.value.validate()
+    // 2. 提交抄送
+    const data = {
+      id: runningTask.value.id,
+      reason: copyForm.copyReason,
+      copyUserIds:copyForm.copyUserIds
+    }
+    await TaskApi.copyTask(data)
+    copyFormRef.value.resetFields()
+    popOverVisible.value.copy = false
+    message.success('操作成功')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 处理转交 */
+const handleTransfer = async () => {
+  formLoading.value = true
+  try {
+    // 1.1 校验表单
+    if (!transferFormRef.value) return
+    await transferFormRef.value.validate()
+    // 1.2 提交转交
+    const data = {
+      id: runningTask.value.id,
+      reason: transferForm.reason,
+      assigneeUserId: transferForm.assigneeUserId
+    }
+    await TaskApi.transferTask(data)
+    transferFormRef.value.resetFields()
+    popOverVisible.value.transfer = false
+    message.success('操作成功')
+    // 2. 加载最新数据
+    reload()
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 处理委派 */
+const handleDelegate = async () => {
+  formLoading.value = true
+  try {
+
+    // 1.1 校验表单
+    if (!delegateFormRef.value) return
+    await delegateFormRef.value.validate()
+    // 1.2 处理委派
+    const data = {
+      id: runningTask.value.id,
+      reason: delegateForm.reason,
+      delegateUserId: delegateForm.delegateUserId
+    }
+
+    await TaskApi.delegateTask(data)
+    popOverVisible.value.delegate = false
+    delegateFormRef.value.resetFields()
+    message.success('操作成功')
+    // 2. 加载最新数据
+    reload()
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 处理加签 */
+const handlerAddSign = async (type: string) => {
+  formLoading.value = true
+  try {
+    // 1.1 校验表单
+    if (!addSignFormRef.value) return
+    await addSignFormRef.value.validate()
+    // 1.2 提交加签
+    const data = {
+      id: runningTask.value.id,
+      type,
+      reason: addSignForm.reason,
+      userIds: addSignForm.addSignUserIds
+    }
+    await TaskApi.signCreateTask(data)
+    message.success('操作成功')
+    addSignFormRef.value.resetFields()
+    popOverVisible.value.addSign = false
+    // 2 加载最新数据
+    reload()
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 处理退回 */
+const handleReturn = async () => {
+  formLoading.value = true
+  try {
+    // 1.1 校验表单
+    if (!returnFormRef.value) return
+    await returnFormRef.value.validate()
+    // 1.2 提交退回
+    const data = {
+      id: runningTask.value.id,
+      reason: returnForm.returnReason,
+      targetTaskDefinitionKey: returnForm.targetTaskDefinitionKey
+    }
+
+    await TaskApi.returnTask(data)
+    popOverVisible.value.return = false
+    returnFormRef.value.resetFields()
+    message.success('操作成功')
+    // 2 重新加载数据
+    reload()
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 处理取消 */
+const handleCancel = async () => {
+  formLoading.value = true
+  try {
+    // 1.1 校验表单
+    if (!cancelFormRef.value) return
+    await cancelFormRef.value.validate()
+    // 1.2 提交取消
+    await ProcessInstanceApi.cancelProcessInstanceByStartUser(
+      props.processInstance.id,
+      cancelForm.cancelReason
+    )
+    popOverVisible.value.return = false
+    message.success('操作成功')
+    cancelFormRef.value.resetFields()
+    // 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 {
+    // 1.1 校验表单
+    if (!deleteSignFormRef.value) return
+    await deleteSignFormRef.value.validate()
+    // 1.2 提交减签
+    const data = {
+      id: deleteSignForm.deleteSignTaskId,
+      reason: deleteSignForm.reason
+    }
+    await TaskApi.signDeleteTask(data)
+    message.success('减签成功')
+    deleteSignFormRef.value.resetFields()
+    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) => {
+  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 = {} // 占位,避免为空
+  }
+}
+
+/** 校验流程表单 */
+const validateNormalForm = async () => {
+  if (props.processDefinition?.formType === BpmModelFormType.NORMAL) {
+    let valid = true
+    try {
+      await props.normalFormApi?.validate()
+    } catch {
+      valid = false;
+    }
+    return valid;
+  } else {
+    return true;
+  }
+}
+/** 从可以编辑的流程表单字段,获取需要修改的流程实例的变量 */
+const getUpdatedProcessInstanceVaiables = ()=> {
+  const variables = {}
+  props.writableFields.forEach( (field) => {
+    const fieldValue = props.normalFormApi.getValue(field)
+    variables[field] = fieldValue;
+  })
+  return variables
+}
+
+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..e24316c
--- /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}-${index}`">
+        <!-- 第一行:节点名称、时间 -->
+        <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}-${index}`">
+              <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/TaskDelegateForm.vue b/src/views/bpm/processInstance/detail/dialog/TaskDelegateForm.vue
deleted file mode 100644
index 178b1b9..0000000
--- a/src/views/bpm/processInstance/detail/dialog/TaskDelegateForm.vue
+++ /dev/null
@@ -1,89 +0,0 @@
-<template>
-  <Dialog v-model="dialogVisible" title="委派任务" width="500">
-    <el-form
-      ref="formRef"
-      v-loading="formLoading"
-      :model="formData"
-      :rules="formRules"
-      label-width="110px"
-    >
-      <el-form-item label="接收人" prop="delegateUserId">
-        <el-select v-model="formData.delegateUserId" clearable style="width: 100%">
-          <el-option
-            v-for="item in userList"
-            :key="item.id"
-            :label="item.nickname"
-            :value="item.id"
-          />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="委派理由" prop="reason">
-        <el-input v-model="formData.reason" clearable placeholder="请输入委派理由" />
-      </el-form-item>
-    </el-form>
-    <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 * as TaskApi from '@/api/bpm/task'
-import * as UserApi from '@/api/system/user'
-
-defineOptions({ name: 'BpmTaskDelegateForm' })
-
-const dialogVisible = ref(false) // 弹窗的是否展示
-const formLoading = ref(false) // 表单的加载中
-const formData = ref({
-  id: '',
-  delegateUserId: undefined,
-  reason: ''
-})
-const formRules = ref({
-  delegateUserId: [{ required: true, message: '接收人不能为空', trigger: 'change' }],
-  reason: [{ required: true, message: '委派理由不能为空', trigger: 'blur' }]
-})
-
-const formRef = ref() // 表单 Ref
-const userList = ref<any[]>([]) // 用户列表
-
-/** 打开弹窗 */
-const open = async (id: string) => {
-  dialogVisible.value = true
-  resetForm()
-  formData.value.id = id
-  // 获得用户列表
-  userList.value = await UserApi.getSimpleUserList()
-}
-defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
-
-/** 提交表单 */
-const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
-const submitForm = async () => {
-  // 校验表单
-  if (!formRef) return
-  const valid = await formRef.value.validate()
-  if (!valid) return
-  // 提交请求
-  formLoading.value = true
-  try {
-    await TaskApi.delegateTask(formData.value)
-    dialogVisible.value = false
-    // 发送操作成功的事件
-    emit('success')
-  } finally {
-    formLoading.value = false
-  }
-}
-
-/** 重置表单 */
-const resetForm = () => {
-  formData.value = {
-    id: '',
-    delegateUserId: undefined,
-    reason: ''
-  }
-  formRef.value?.resetFields()
-}
-</script>
diff --git a/src/views/bpm/processInstance/detail/dialog/TaskReturnForm.vue b/src/views/bpm/processInstance/detail/dialog/TaskReturnForm.vue
deleted file mode 100644
index a139169..0000000
--- a/src/views/bpm/processInstance/detail/dialog/TaskReturnForm.vue
+++ /dev/null
@@ -1,90 +0,0 @@
-<template>
-  <Dialog v-model="dialogVisible" title="回退任务" width="500">
-    <el-form
-      ref="formRef"
-      v-loading="formLoading"
-      :model="formData"
-      :rules="formRules"
-      label-width="110px"
-    >
-      <el-form-item label="退回节点" prop="targetTaskDefinitionKey">
-        <el-select v-model="formData.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="reason">
-        <el-input v-model="formData.reason" clearable placeholder="请输入回退理由" />
-      </el-form-item>
-    </el-form>
-    <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" name="TaskRollbackDialogForm" setup>
-import * as TaskApi from '@/api/bpm/task'
-
-const message = useMessage() // 消息弹窗
-const dialogVisible = ref(false) // 弹窗的是否展示
-const formLoading = ref(false) // 表单的加载中
-const formData = ref({
-  id: '',
-  targetTaskDefinitionKey: undefined,
-  reason: ''
-})
-const formRules = ref({
-  targetTaskDefinitionKey: [{ required: true, message: '必须选择回退节点', trigger: 'change' }],
-  reason: [{ required: true, message: '回退理由不能为空', trigger: 'blur' }]
-})
-
-const formRef = ref() // 表单 Ref
-const returnList = ref([] as any)
-/** 打开弹窗 */
-const open = async (id: string) => {
-  returnList.value = await TaskApi.getTaskListByReturn(id)
-  if (returnList.value.length === 0) {
-    message.warning('当前没有可回退的节点')
-    return false
-  }
-  dialogVisible.value = true
-  resetForm()
-  formData.value.id = id
-}
-defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
-
-/** 提交表单 */
-const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
-const submitForm = async () => {
-  // 校验表单
-  if (!formRef) return
-  const valid = await formRef.value.validate()
-  if (!valid) return
-  // 提交请求
-  formLoading.value = true
-  try {
-    await TaskApi.returnTask(formData.value)
-    message.success('回退成功')
-    dialogVisible.value = false
-    // 发送操作成功的事件
-    emit('success')
-  } finally {
-    formLoading.value = false
-  }
-}
-
-/** 重置表单 */
-const resetForm = () => {
-  formData.value = {
-    id: '',
-    targetTaskDefinitionKey: undefined,
-    reason: ''
-  }
-  formRef.value?.resetFields()
-}
-</script>
diff --git a/src/views/bpm/processInstance/detail/dialog/TaskSignCreateForm.vue b/src/views/bpm/processInstance/detail/dialog/TaskSignCreateForm.vue
deleted file mode 100644
index 9e4998c..0000000
--- a/src/views/bpm/processInstance/detail/dialog/TaskSignCreateForm.vue
+++ /dev/null
@@ -1,99 +0,0 @@
-<template>
-  <Dialog v-model="dialogVisible" title="加签" width="500">
-    <el-form
-      ref="formRef"
-      v-loading="formLoading"
-      :model="formData"
-      :rules="formRules"
-      label-width="110px"
-    >
-      <el-form-item label="加签处理人" prop="userIds">
-        <el-select v-model="formData.userIds" multiple clearable style="width: 100%">
-          <el-option
-            v-for="item in userList"
-            :key="item.id"
-            :label="item.nickname"
-            :value="item.id"
-          />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="加签理由" prop="reason">
-        <el-input v-model="formData.reason" clearable placeholder="请输入加签理由" />
-      </el-form-item>
-    </el-form>
-    <template #footer>
-      <el-button :disabled="formLoading" type="primary" @click="submitForm('before')">
-        向前加签
-      </el-button>
-      <el-button :disabled="formLoading" type="primary" @click="submitForm('after')">
-        向后加签
-      </el-button>
-      <el-button @click="dialogVisible = false">取 消</el-button>
-    </template>
-  </Dialog>
-</template>
-<script lang="ts" setup>
-import * as TaskApi from '@/api/bpm/task'
-import * as UserApi from '@/api/system/user'
-
-defineOptions({ name: 'TaskSignCreateForm' })
-
-const message = useMessage() // 消息弹窗
-const dialogVisible = ref(false) // 弹窗的是否展示
-const formLoading = ref(false) // 表单的加载中
-const formData = ref({
-  id: '',
-  userIds: [],
-  type: '',
-  reason: ''
-})
-const formRules = ref({
-  userIds: [{ required: true, message: '加签处理人不能为空', trigger: 'change' }],
-  reason: [{ required: true, message: '加签理由不能为空', trigger: 'change' }]
-})
-
-const formRef = ref() // 表单 Ref
-const userList = ref<any[]>([]) // 用户列表
-
-/** 打开弹窗 */
-const open = async (id: string) => {
-  dialogVisible.value = true
-  resetForm()
-  formData.value.id = id
-  // 获得用户列表
-  userList.value = await UserApi.getSimpleUserList()
-}
-defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
-
-/** 提交表单 */
-const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
-const submitForm = async (type: string) => {
-  // 校验表单
-  if (!formRef) return
-  const valid = await formRef.value.validate()
-  if (!valid) return
-  // 提交请求
-  formLoading.value = true
-  formData.value.type = type
-  try {
-    await TaskApi.signCreateTask(formData.value)
-    message.success('加签成功')
-    dialogVisible.value = false
-    // 发送操作成功的事件
-    emit('success')
-  } finally {
-    formLoading.value = false
-  }
-}
-
-/** 重置表单 */
-const resetForm = () => {
-  formData.value = {
-    id: '',
-    userIds: [],
-    type: '',
-    reason: ''
-  }
-  formRef.value?.resetFields()
-}
-</script>
diff --git a/src/views/bpm/processInstance/detail/dialog/TaskSignDeleteForm.vue b/src/views/bpm/processInstance/detail/dialog/TaskSignDeleteForm.vue
deleted file mode 100644
index 19bb2dc..0000000
--- a/src/views/bpm/processInstance/detail/dialog/TaskSignDeleteForm.vue
+++ /dev/null
@@ -1,89 +0,0 @@
-<template>
-  <Dialog v-model="dialogVisible" title="减签" width="500">
-    <el-form
-      ref="formRef"
-      v-loading="formLoading"
-      :model="formData"
-      :rules="formRules"
-      label-width="110px"
-    >
-      <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">
-            {{ item.name }}
-            ({{ item.assigneeUser?.deptName || item.ownerUser?.deptName }} -
-            {{ item.assigneeUser?.nickname || item.ownerUser?.nickname }})
-          </el-radio-button>
-        </el-radio-group>
-      </el-form-item>
-      <el-form-item label="减签理由" prop="reason">
-        <el-input v-model="formData.reason" clearable placeholder="请输入减签理由" />
-      </el-form-item>
-    </el-form>
-    <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 * as TaskApi from '@/api/bpm/task'
-import { isEmpty } from '@/utils/is'
-
-defineOptions({ name: 'TaskSignDeleteForm' })
-
-const message = useMessage() // 消息弹窗
-const dialogVisible = ref(false) // 弹窗的是否展示
-const formLoading = ref(false) // 表单的加载中
-const formData = ref({
-  id: '',
-  reason: ''
-})
-const formRules = ref({
-  id: [{ required: true, message: '必须选择减签任务', trigger: 'change' }],
-  reason: [{ required: true, message: '减签理由不能为空', trigger: 'blur' }]
-})
-
-const formRef = ref() // 表单 Ref
-const childrenTaskList = ref([])
-/** 打开弹窗 */
-const open = async (id: string) => {
-  childrenTaskList.value = await TaskApi.getChildrenTaskList(id)
-  if (isEmpty(childrenTaskList.value)) {
-    message.warning('当前没有可减签的任务')
-    return false
-  }
-  dialogVisible.value = true
-  resetForm()
-}
-defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
-
-/** 提交表单 */
-const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
-const submitForm = async () => {
-  // 校验表单
-  if (!formRef) return
-  const valid = await formRef.value.validate()
-  if (!valid) return
-  // 提交请求
-  formLoading.value = true
-  try {
-    await TaskApi.signDeleteTask(formData.value)
-    message.success('减签成功')
-    dialogVisible.value = false
-    // 发送操作成功的事件
-    emit('success')
-  } finally {
-    formLoading.value = false
-  }
-}
-
-/** 重置表单 */
-const resetForm = () => {
-  formData.value = {
-    id: '',
-    reason: ''
-  }
-  formRef.value?.resetFields()
-}
-</script>
diff --git a/src/views/bpm/processInstance/detail/dialog/TaskSignList.vue b/src/views/bpm/processInstance/detail/dialog/TaskSignList.vue
deleted file mode 100644
index 648e86b..0000000
--- a/src/views/bpm/processInstance/detail/dialog/TaskSignList.vue
+++ /dev/null
@@ -1,106 +0,0 @@
-<template>
-  <el-drawer v-model="drawerVisible" title="子任务" size="880px">
-    <!-- 当前任务 -->
-    <template #header>
-      <h4>【{{ parentTask.name }} 】审批人:{{ parentTask?.assigneeUser?.nickname }}</h4>
-      <el-button
-        style="margin-left: 5px"
-        v-if="isSignDeleteButtonVisible(parentTask)"
-        type="danger"
-        plain
-        @click="handleSignDelete(parentTask)"
-      >
-        <Icon icon="ep:remove" /> 减签
-      </el-button>
-    </template>
-    <!-- 子任务列表 -->
-    <el-table :data="parentTask.children" style="width: 100%" row-key="id" border>
-      <el-table-column prop="assigneeUser.nickname" label="审批人" min-width="100">
-        <template #default="scope">
-          {{ scope.row.assigneeUser?.nickname || scope.row.ownerUser?.nickname }}
-        </template>
-      </el-table-column>
-      <el-table-column prop="assigneeUser.deptName" label="所在部门" min-width="100">
-        <template #default="scope">
-          {{ scope.row.assigneeUser?.deptName || scope.row.ownerUser?.deptName }}
-        </template>
-      </el-table-column>
-      <el-table-column label="审批状态" prop="status" width="120">
-        <template #default="scope">
-          <dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="scope.row.status" />
-        </template>
-      </el-table-column>
-      <el-table-column
-        label="提交时间"
-        align="center"
-        prop="createTime"
-        width="180"
-        :formatter="dateFormatter"
-      />
-      <el-table-column
-        label="结束时间"
-        align="center"
-        prop="endTime"
-        width="180"
-        :formatter="dateFormatter"
-      />
-      <el-table-column label="操作" prop="operation" width="90">
-        <template #default="scope">
-          <el-button
-            v-if="isSignDeleteButtonVisible(scope.row)"
-            type="danger"
-            plain
-            size="small"
-            @click="handleSignDelete(scope.row)"
-          >
-            <Icon icon="ep:remove" /> 减签
-          </el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-
-    <!-- 减签 -->
-    <TaskSignDeleteForm ref="taskSignDeleteFormRef" @success="handleSignDeleteSuccess" />
-  </el-drawer>
-</template>
-<script lang="ts" setup>
-import { isEmpty } from '@/utils/is'
-import { DICT_TYPE } from '@/utils/dict'
-import { dateFormatter } from '@/utils/formatTime'
-import TaskSignDeleteForm from './TaskSignDeleteForm.vue'
-
-defineOptions({ name: 'TaskSignList' })
-
-const message = useMessage() // 消息弹窗
-const drawerVisible = ref(false) // 抽屉的是否展示
-const parentTask = ref({} as any)
-
-/** 打开弹窗 */
-const open = async (task: any) => {
-  if (isEmpty(task.children)) {
-    message.warning('该任务没有子任务')
-    return
-  }
-  parentTask.value = task
-  // 展开抽屉
-  drawerVisible.value = true
-}
-defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
-
-/** 发起减签 */
-const taskSignDeleteFormRef = ref()
-const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
-const handleSignDelete = (item: any) => {
-  taskSignDeleteFormRef.value.open(item.id)
-}
-const handleSignDeleteSuccess = () => {
-  emit('success')
-  // 关闭抽屉
-  drawerVisible.value = false
-}
-
-/** 是否显示减签按钮 */
-const isSignDeleteButtonVisible = (task: any) => {
-  return task && task.children && !isEmpty(task.children)
-}
-</script>
diff --git a/src/views/bpm/processInstance/detail/dialog/TaskTransferForm.vue b/src/views/bpm/processInstance/detail/dialog/TaskTransferForm.vue
deleted file mode 100644
index c1012ac..0000000
--- a/src/views/bpm/processInstance/detail/dialog/TaskTransferForm.vue
+++ /dev/null
@@ -1,89 +0,0 @@
-<template>
-  <Dialog v-model="dialogVisible" title="转派任务" width="500">
-    <el-form
-      ref="formRef"
-      v-loading="formLoading"
-      :model="formData"
-      :rules="formRules"
-      label-width="110px"
-    >
-      <el-form-item label="新审批人" prop="assigneeUserId">
-        <el-select v-model="formData.assigneeUserId" clearable style="width: 100%">
-          <el-option
-            v-for="item in userList"
-            :key="item.id"
-            :label="item.nickname"
-            :value="item.id"
-          />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="转派理由" prop="reason">
-        <el-input v-model="formData.reason" clearable placeholder="请输入转派理由" />
-      </el-form-item>
-    </el-form>
-    <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 * as TaskApi from '@/api/bpm/task'
-import * as UserApi from '@/api/system/user'
-
-defineOptions({ name: 'TaskTransferForm' })
-
-const dialogVisible = ref(false) // 弹窗的是否展示
-const formLoading = ref(false) // 表单的加载中
-const formData = ref({
-  id: '',
-  assigneeUserId: undefined,
-  reason: ''
-})
-const formRules = ref({
-  assigneeUserId: [{ required: true, message: '新审批人不能为空', trigger: 'change' }],
-  reason: [{ required: true, message: '转派理由不能为空', trigger: 'blur' }]
-})
-
-const formRef = ref() // 表单 Ref
-const userList = ref<any[]>([]) // 用户列表
-
-/** 打开弹窗 */
-const open = async (id: string) => {
-  dialogVisible.value = true
-  resetForm()
-  formData.value.id = id
-  // 获得用户列表
-  userList.value = await UserApi.getSimpleUserList()
-}
-defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
-
-/** 提交表单 */
-const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
-const submitForm = async () => {
-  // 校验表单
-  if (!formRef) return
-  const valid = await formRef.value.validate()
-  if (!valid) return
-  // 提交请求
-  formLoading.value = true
-  try {
-    await TaskApi.transferTask(formData.value)
-    dialogVisible.value = false
-    // 发送操作成功的事件
-    emit('success')
-  } finally {
-    formLoading.value = false
-  }
-}
-
-/** 重置表单 */
-const resetForm = () => {
-  formData.value = {
-    id: '',
-    assigneeUserId: undefined,
-    reason: ''
-  }
-  formRef.value?.resetFields()
-}
-</script>
diff --git a/src/views/bpm/processInstance/detail/index.vue b/src/views/bpm/processInstance/detail/index.vue
index da54769..a6ed3b5 100644
--- a/src/views/bpm/processInstance/detail/index.vue
+++ b/src/views/bpm/processInstance/detail/index.vue
@@ -1,174 +1,170 @@
 <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 === BpmModelFormType.NORMAL">
+                        <form-create
+                          v-model="detailForm.value"
+                          v-model:api="fApi"
+                          :option="detailForm.option"
+                          :rule="detailForm.rule"
+                        />
+                      </el-col>
+                      <!-- 情况二:业务表单 -->
+                      <div v-if="processDefinition?.formType === BpmModelFormType.CUSTOM">
+                        <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"
+            :normal-form="detailForm"
+            :normal-form-api="fApi"
+            :writable-fields="writableFields"
+            @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, BpmModelFormType } 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 +174,128 @@
   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 writableFields: Array<string> = [] // 表单可以编辑的字段
 
 /** 获得详情 */
 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 === BpmModelFormType.NORMAL) {
+      // 获取表单字段权限
+      const formFieldsPermission = data.formFieldsPermission
+      // 清空可编辑字段为空
+      writableFields.splice(0)
+      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)
+    // 加入可以编辑的字段
+    writableFields.push(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 +305,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 {
+    display: flex;
+    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;
+    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/index.vue b/src/views/bpm/processInstance/index.vue
index 3951a83..2ffb162 100644
--- a/src/views/bpm/processInstance/index.vue
+++ b/src/views/bpm/processInstance/index.vue
@@ -1,5 +1,4 @@
 <template>
-
   <ContentWrap>
     <!-- 搜索工作栏 -->
     <el-form
@@ -9,7 +8,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 +17,20 @@
           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: '300px' }">
+        <!-- TODO @tuituji:应该选择好分类,就触发搜索啦。 RE:done & to check-->
         <el-select
           v-model="queryParams.category"
           placeholder="请选择流程分类"
           clearable
-          class="!w-240px"
+          class="!w-155px"
+          @change="handleQuery"
         >
           <el-option
             v-for="category in categoryList"
@@ -42,12 +40,14 @@
           />
         </el-select>
       </el-form-item>
-      <el-form-item label="流程状态" prop="status">
+
+      <el-form-item label="" prop="status" :style="{ position: 'absolute', right: '130px' }">
         <el-select
           v-model="queryParams.status"
           placeholder="请选择流程状态"
           clearable
-          class="!w-240px"
+          class="!w-155px"
+          @change="handleQuery"
         >
           <el-option
             v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
@@ -57,28 +57,69 @@
           />
         </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)"
+
+      <!-- 高级筛选 -->
+      <!-- TODO @ tuituji:style 可以使用 unocss -->
+      <el-form-item :style="{ position: 'absolute', right: '0px' }">
+        <el-popover
+          :visible="showPopover"
+          persistent
+          :width="400"
+          :show-arrow="false"
+          placement="bottom-end"
         >
-          <Icon icon="ep:plus" class="mr-5px" /> 发起流程
-        </el-button>
+          <template #reference>
+            <el-button @click="showPopover = !showPopover">
+              <Icon icon="ep:plus" class="mr-5px" />高级筛选
+            </el-button>
+          </template>
+          <el-form-item label="流程发起人" class="bold-label" label-position="top" prop="category">
+            <el-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="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>
+          <!-- TODO tuituiji:参考钉钉,1)按照清空、取消、确认排序。2)右对齐。3)确认增加 primary -->
+          <el-form-item class="bold-label" label-position="top">
+            <el-button @click="handleQuery"> 确认</el-button>
+            <el-button @click="showPopover = false"> 取消</el-button>
+            <el-button @click="resetQuery"> 清空</el-button>
+          </el-form-item>
+        </el-popover>
       </el-form-item>
     </el-form>
   </ContentWrap>
@@ -94,6 +135,8 @@
         min-width="100"
         fixed="left"
       />
+      <!-- TODO @芋艿:摘要 -->
+      <!-- TODO tuituiji:参考钉钉;1)审批中时,展示审批任务;2)非审批中,展示状态 -->
       <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 +156,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 +168,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 +204,12 @@
   </ContentWrap>
 </template>
 <script lang="ts" setup>
+// TODO @tuituji:List 改成 <Icon icon="ep:plus" class="mr-5px" /> 类似这种组件哈。 RE:done & to check
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
-import { dateFormatter, 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 +226,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 () => {
@@ -201,6 +245,8 @@
     loading.value = false
   }
 }
+
+const showPopover = ref(false)
 
 /** 搜索按钮操作 */
 const handleQuery = () => {
@@ -234,7 +280,7 @@
 }
 
 /** 查看详情 */
-const handleDetail = (row) => {
+const handleDetail = (row: ProcessInstanceVO) => {
   router.push({
     name: 'BpmProcessInstanceDetail',
     query: {
@@ -244,7 +290,7 @@
 }
 
 /** 取消按钮操作 */
-const handleCancel = async (row) => {
+const handleCancel = async (row: ProcessInstanceVO) => {
   // 二次确认
   const { value } = await ElMessageBox.prompt('请输入取消原因', '取消流程', {
     confirmButtonText: t('common.ok'),
@@ -270,3 +316,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..eed0099
--- /dev/null
+++ b/src/views/bpm/simple/SimpleModelDesign.vue
@@ -0,0 +1,155 @@
+<template>
+  <ContentWrap :bodyStyle="{ padding: '20px 16px' }">
+    <SimpleProcessDesigner
+      :model-id="modelId"
+      :model-key="modelKey"
+      :model-name="modelName"
+      :value="currentValue"
+      @success="handleSuccess"
+      @init-finished="handleInit"
+      :start-user-ids="startUserIds"
+      ref="designerRef"
+    />
+  </ContentWrap>
+</template>
+<script setup lang="ts">
+import { SimpleProcessDesigner } from '@/components/SimpleProcessDesignerV2/src/'
+
+defineOptions({
+  name: 'SimpleModelDesign'
+})
+
+const props = defineProps<{
+  modelId?: string
+  modelKey?: string
+  modelName?: string
+  value?: string
+  startUserIds?: number[]
+}>()
+
+const emit = defineEmits(['success', 'init-finished'])
+const designerRef = ref()
+const isInitialized = ref(false)
+const currentValue = ref('')
+
+// 初始化或更新当前值
+const initOrUpdateValue = async () => {
+  console.log('initOrUpdateValue', props.value)
+  if (props.value) {
+    currentValue.value = props.value
+    // 如果设计器已经初始化,立即加载数据
+    if (isInitialized.value && designerRef.value) {
+      try {
+        await designerRef.value.loadProcessData(props.value)
+        await nextTick()
+        if (designerRef.value.refresh) {
+          await designerRef.value.refresh()
+        }
+      } catch (error) {
+        console.error('加载流程数据失败:', error)
+      }
+    }
+  }
+}
+
+// 监听属性变化
+watch(
+  [() => props.modelKey, () => props.modelName, () => props.value],
+  async ([newKey, newName, newValue], [oldKey, oldName, oldValue]) => {
+    if (designerRef.value && isInitialized.value) {
+      try {
+        if (newKey && newName && (newKey !== oldKey || newName !== oldName)) {
+          await designerRef.value.updateModel(newKey, newName)
+        }
+        if (newValue && newValue !== oldValue) {
+          currentValue.value = newValue
+          await designerRef.value.loadProcessData(newValue)
+          await nextTick()
+          if (designerRef.value.refresh) {
+            await designerRef.value.refresh()
+          }
+        }
+      } catch (error) {
+        console.error('更新流程数据失败:', error)
+      }
+    }
+  },
+  { deep: true, immediate: true }
+)
+
+// 初始化完成回调
+const handleInit = async () => {
+  try {
+    isInitialized.value = true
+    emit('init-finished')
+
+    // 等待下一个tick,确保设计器已经准备好
+    await nextTick()
+
+    // 初始化完成后,设置初始值
+    if (props.modelKey && props.modelName) {
+      await designerRef.value.updateModel(props.modelKey, props.modelName)
+    }
+    if (props.value) {
+      currentValue.value = props.value
+      await designerRef.value.loadProcessData(props.value)
+      // 再次刷新确保数据正确加载
+      await nextTick()
+      if (designerRef.value.refresh) {
+        await designerRef.value.refresh()
+      }
+    }
+  } catch (error) {
+    console.error('初始化流程数据失败:', error)
+  }
+}
+
+// 修改成功回调
+const handleSuccess = (data?: any) => {
+  console.warn('handleSuccess', data)
+  if (data && data !== currentValue.value) {
+    currentValue.value = data
+    emit('success', data)
+  }
+}
+
+/** 获取当前流程数据 */
+const getCurrentFlowData = async () => {
+  try {
+    if (designerRef.value) {
+      const data = await designerRef.value.getCurrentFlowData()
+      if (data) {
+        currentValue.value = data
+      }
+      return data
+    }
+    return currentValue.value || undefined
+  } catch (error) {
+    console.error('获取流程数据失败:', error)
+    return currentValue.value || undefined
+  }
+}
+
+// 组件创建时初始化数据
+onMounted(() => {
+  initOrUpdateValue()
+})
+
+// 组件卸载前保存数据
+onBeforeUnmount(async () => {
+  try {
+    const data = await getCurrentFlowData()
+    if (data) {
+      emit('success', data)
+    }
+  } catch (error) {
+    console.error('保存数据失败:', error)
+  }
+})
+
+defineExpose({
+  getCurrentFlowData,
+  refresh: () => designerRef.value?.refresh?.()
+})
+</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..e83e9ed 100644
--- a/src/views/bpm/task/done/index.vue
+++ b/src/views/bpm/task/done/index.vue
@@ -8,7 +8,7 @@
       class="-mb-15px"
       label-width="68px"
     >
-      <el-form-item label="任务名称" prop="name">
+      <el-form-item label="" prop="name">
         <el-input
           v-model="queryParams.name"
           class="!w-240px"
@@ -17,27 +17,96 @@
           @keyup.enter="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="创建时间" prop="createTime">
-        <el-date-picker
-          v-model="queryParams.createTime"
-          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
-          class="!w-240px"
-          end-placeholder="结束日期"
-          start-placeholder="开始日期"
-          type="daterange"
-          value-format="YYYY-MM-DD HH:mm:ss"
-        />
-      </el-form-item>
       <el-form-item>
         <el-button @click="handleQuery">
           <Icon class="mr-5px" icon="ep:search" />
           搜索
         </el-button>
-        <el-button @click="resetQuery">
-          <Icon class="mr-5px" icon="ep:refresh" />
-          重置
-        </el-button>
       </el-form-item>
+
+      <el-form-item label="" prop="category" :style="{ position: 'absolute', right: '300px' }">
+        <el-select
+          v-model="queryParams.category"
+          placeholder="请选择流程分类"
+          clearable
+          class="!w-155px"
+          @change="handleQuery"
+        >
+          <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="" prop="status" :style="{ position: 'absolute', right: '130px' }">
+        <el-select
+          v-model="queryParams.status"
+          placeholder="请选择流程状态"
+          clearable
+          class="!w-155px"
+          @change="handleQuery"
+        >
+          <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 :style="{ position: 'absolute', right: '0px' }">
+        <el-popover
+          :visible="showPopover"
+          persistent
+          :width="400"
+          :show-arrow="false"
+          placement="bottom-end"
+        >
+          <template #reference>
+            <el-button @click="showPopover = !showPopover" >
+              <Icon icon="ep:plus" class="mr-5px" />高级筛选
+            </el-button>
+
+          </template>
+          <el-form-item label="流程发起人" class="bold-label" label-position="top" prop="category">
+            <el-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="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 class="bold-label" label-position="top">
+            <el-button @click="handleQuery"> 确认</el-button>
+            <el-button @click="showPopover = false"> 取消</el-button>
+            <el-button @click="resetQuery"> 清空</el-button>
+          </el-form-item>
+        </el-popover>
+      </el-form-item>
+
     </el-form>
   </ContentWrap>
 
@@ -102,9 +171,10 @@
   </ContentWrap>
 </template>
 <script lang="ts" setup>
-import { DICT_TYPE } from '@/utils/dict'
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { dateFormatter, formatPast2 } from '@/utils/formatTime'
 import * as TaskApi from '@/api/bpm/task'
+import { CategoryApi, CategoryVO } from '@/api/bpm/category'
 
 defineOptions({ name: 'BpmTodoTask' })
 
@@ -117,9 +187,13 @@
   pageNo: 1,
   pageSize: 10,
   name: '',
+  category: undefined,
+  status: undefined,
   createTime: []
 })
 const queryFormRef = ref() // 搜索的表单
+const categoryList = ref<CategoryVO[]>([]) // 流程分类列表
+const showPopover = ref(false)
 
 /** 查询任务列表 */
 const getList = async () => {
@@ -150,13 +224,15 @@
   push({
     name: 'BpmProcessInstanceDetail',
     query: {
-      id: row.processInstance.id
+      id: row.processInstance.id,
+      taskId: row.id
     }
   })
 }
 
 /** 初始化 **/
-onMounted(() => {
-  getList()
+onMounted(async () => {
+  await getList()
+  categoryList.value = await CategoryApi.getCategorySimpleList()
 })
 </script>
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..e1449b1 100644
--- a/src/views/bpm/task/todo/index.vue
+++ b/src/views/bpm/task/todo/index.vue
@@ -8,7 +8,7 @@
       class="-mb-15px"
       label-width="68px"
     >
-      <el-form-item label="任务名称" prop="name">
+      <el-form-item label="" prop="name">
         <el-input
           v-model="queryParams.name"
           class="!w-240px"
@@ -17,27 +17,79 @@
           @keyup.enter="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="创建时间" prop="createTime">
-        <el-date-picker
-          v-model="queryParams.createTime"
-          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
-          class="!w-240px"
-          end-placeholder="结束日期"
-          start-placeholder="开始日期"
-          type="daterange"
-          value-format="YYYY-MM-DD HH:mm:ss"
-        />
-      </el-form-item>
       <el-form-item>
         <el-button @click="handleQuery">
           <Icon class="mr-5px" icon="ep:search" />
           搜索
         </el-button>
-        <el-button @click="resetQuery">
-          <Icon class="mr-5px" icon="ep:refresh" />
-          重置
-        </el-button>
       </el-form-item>
+
+      <el-form-item label="" prop="category" :style="{ position: 'absolute', right: '130px' }">
+        <el-select
+          v-model="queryParams.category"
+          placeholder="请选择流程分类"
+          clearable
+          class="!w-155px"
+          @change="handleQuery"
+        >
+          <el-option
+            v-for="category in categoryList"
+            :key="category.code"
+            :label="category.name"
+            :value="category.code"
+          />
+        </el-select>
+      </el-form-item>
+
+      <!-- 高级筛选 -->
+      <el-form-item :style="{ position: 'absolute', right: '0px' }">
+        <el-popover
+          :visible="showPopover"
+          persistent
+          :width="400"
+          :show-arrow="false"
+          placement="bottom-end"
+        >
+          <template #reference>
+            <el-button @click="showPopover = !showPopover" >
+              <Icon icon="ep:plus" class="mr-5px" />高级筛选
+            </el-button>
+
+          </template>
+          <el-form-item label="流程发起人" class="bold-label" label-position="top" prop="category">
+            <el-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="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 class="bold-label" label-position="top">
+            <el-button @click="handleQuery"> 确认</el-button>
+            <el-button @click="showPopover = false"> 取消</el-button>
+            <el-button @click="resetQuery"> 清空</el-button>
+        </el-form-item>
+        </el-popover>
+      </el-form-item>
+
     </el-form>
   </ContentWrap>
 
@@ -87,6 +139,7 @@
 <script lang="ts" setup>
 import { dateFormatter } from '@/utils/formatTime'
 import * as TaskApi from '@/api/bpm/task'
+import { CategoryApi, CategoryVO } from '@/api/bpm/category'
 
 defineOptions({ name: 'BpmTodoTask' })
 
@@ -99,9 +152,11 @@
   pageNo: 1,
   pageSize: 10,
   name: '',
+  category: undefined,
   createTime: []
 })
 const queryFormRef = ref() // 搜索的表单
+const categoryList = ref<CategoryVO[]>([]) // 流程分类列表
 
 /** 查询任务列表 */
 const getList = async () => {
@@ -114,6 +169,8 @@
     loading.value = false
   }
 }
+
+const showPopover = ref(false)
 
 /** 搜索按钮操作 */
 const handleQuery = () => {
@@ -132,13 +189,15 @@
   push({
     name: 'BpmProcessInstanceDetail',
     query: {
-      id: row.processInstance.id
+      id: row.processInstance.id,
+      taskId: row.id
     }
   })
 }
 
 /** 初始化 **/
-onMounted(() => {
-  getList()
+onMounted(async () => {
+  await getList()
+  categoryList.value = await CategoryApi.getCategorySimpleList()
 })
 </script>
diff --git a/src/views/data/channel/http/api/tag/index.vue b/src/views/data/channel/http/api/tag/index.vue
index 9a38a69..1976db9 100644
--- a/src/views/data/channel/http/api/tag/index.vue
+++ b/src/views/data/channel/http/api/tag/index.vue
@@ -122,7 +122,12 @@
           label="数据质量"
           header-align="center"
           align="center"
-        />
+        >
+          <template #default="scope">
+            <el-tag v-if="scope.row.dataQuality === 'Good'" size="small" type="success">{{scope.row.dataQuality}}</el-tag>
+            <el-tag v-if="scope.row.dataQuality === 'Bad'" size="small" type="danger">{{scope.row.dataQuality}}</el-tag>
+          </template>
+        </el-table-column>
         <el-table-column label="操作" align="center" min-width="110" fixed="right">
           <template #default="scope">
             <el-button
diff --git a/src/views/data/ind/category/CategoryForm.vue b/src/views/data/ind/category/CategoryForm.vue
index d4bd86c..40cd2fb 100644
--- a/src/views/data/ind/category/CategoryForm.vue
+++ b/src/views/data/ind/category/CategoryForm.vue
@@ -33,13 +33,13 @@
 <script lang="ts" setup>
   import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
   import * as CategoryApi from '@/api/data/ind/category'
-  import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
+  import {CACHE_KEY, useSessionCache} from '@/hooks/web/useCache'
   import { CommonStatusEnum, SystemMenuTypeEnum } from '@/utils/constants'
   import { defaultProps, handleTree } from '@/utils/tree'
 
   defineOptions({ name: 'IndItemCategoryForm' })
 
-  const { wsCache } = useCache()
+  const { wsSessionCache } = useSessionCache()
   const { t } = useI18n() // 国际化
   const message = useMessage() // 消息弹窗
 
@@ -114,7 +114,7 @@
     } finally {
       formLoading.value = false
       // 清空,从而触发刷新
-      wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
+      wsSessionCache.delete(CACHE_KEY.ROLE_ROUTERS)
     }
   }
 
diff --git a/src/views/data/ind/data/DataSetForm.vue b/src/views/data/ind/data/DataSetForm.vue
index 132e25c..4b03f66 100644
--- a/src/views/data/ind/data/DataSetForm.vue
+++ b/src/views/data/ind/data/DataSetForm.vue
@@ -21,7 +21,8 @@
         </el-select>
       </el-form-item>
       <el-form-item label="查询语句" prop="querySql">
-        <el-input v-model="formData.querySql" placeholder="请输入内容" type="textarea" maxlength="200"
+        <el-input v-model="formData.querySql" placeholder="请输入内容" type="textarea" maxlength="500"
+                  :rows="6"
                   show-word-limit spellcheck="false"/>
       </el-form-item>
       <el-form-item label="备注" prop="remark">
diff --git a/src/views/data/ind/item/AtomIndDefineForm.vue b/src/views/data/ind/item/AtomIndDefineForm.vue
index 00ab2f5..225d5c1 100644
--- a/src/views/data/ind/item/AtomIndDefineForm.vue
+++ b/src/views/data/ind/item/AtomIndDefineForm.vue
@@ -20,19 +20,19 @@
       <el-row>
         <el-col :span="12">
           <el-form-item label="指标分类" prop="itemCategory">
-            <el-select v-model="formData.itemCategory" clearable placeholder="请选择指标分类">
-              <el-option
-                v-for="item in dataCategoryList"
-                :key="item.id"
-                :label="item.label"
-                :value="item.id + ''"
-              />
-            </el-select>
+            <el-tree-select
+              v-model="formData.itemCategory"
+              :data="dataCategoryList"
+              :default-expanded-keys="[0]"
+              :props="defaultProps"
+              check-strictly
+              node-key="id"
+            />
           </el-form-item>
         </el-col>
         <el-col :span="12">
           <el-form-item label="时间粒度" prop="timeGranularity">
-            <el-select v-model="formData.timeGranularity" placeholder="请选择">
+            <el-select v-model="formData.timeGranularity" clearable placeholder="请选择">
               <el-option
                 v-for="dict in getStrDictOptions(DICT_TYPE.TIME_GRANULARITY)"
                 :key="dict.value"
@@ -61,9 +61,10 @@
         </el-col>
       </el-row>
       <el-row>
-        <el-col :span="6">
+        <el-col :span="12">
           <el-form-item label="数据集" prop="atomItem.dataSet">
-            <el-select v-model="formData.atomItem.dataSet" clearable placeholder="请选择数据集" @change="handleDataSetChange($event)">
+            <el-select v-model="formData.atomItem.dataSet" filterable
+                       allow-create clearable placeholder="请选择数据集" @change="handleDataSetChange($event)">
               <el-option
                 v-for="item in dataSetList"
                 :key="item.id"
@@ -75,7 +76,8 @@
         </el-col>
         <el-col :span="6">
           <el-form-item label="使用字段" prop="atomItem.usingField">
-            <el-select v-model="formData.atomItem.usingField" clearable placeholder="请选择字段">
+            <el-select v-model="formData.atomItem.usingField" filterable
+                       allow-create clearable placeholder="请选择字段">
               <el-option
                 v-for="item in dataSetFieldList"
                 :key="item.id"
@@ -87,7 +89,8 @@
         </el-col>
         <el-col :span="6">
           <el-form-item label="统计方式" prop="statFunc">
-            <el-select v-model="formData.atomItem.statFunc" clearable placeholder="请选择">
+            <el-select v-model="formData.atomItem.statFunc" filterable
+                       allow-create clearable placeholder="请选择">
               <el-option
                 v-for="dict in getStrDictOptions(DICT_TYPE.DATA_STAT_FUNC)"
                 :key="dict.value"
@@ -118,7 +121,7 @@
   import * as DataSetApi from '@/api/data/ind/data/data.set'
   import * as DataSetFieldApi from '@/api/data/ind/data/data.field'
   import * as CategoryApi from '@/api/data/ind/category/index'
-
+  import {handleTree} from "@/utils/tree";
 
   defineOptions({name: 'IndDataSetForm'})
 
@@ -167,7 +170,16 @@
   const formRef = ref() // 表单 Ref
   const dataSetList = ref([] as DataSetApi.DataSetVO[])
   const dataSetFieldList = ref([] as DataSetFieldApi.DataSetFieldVO[])
-  const dataCategoryList = ref([])
+
+  const dataCategoryList = ref<Tree[]>([])
+
+  const getCategoryTree = async () => {
+    dataCategoryList.value = []
+    const res = await CategoryApi.getCategoryListAllSimple()
+    let category: Tree = {id: 0, label: '主类目', children: []}
+    category.children = handleTree(res, 'id', 'pid')
+    dataCategoryList.value.push(category)
+  }
   /** 打开弹窗 */
   const open = async (type: string, id?: string) => {
     dialogVisible.value = true
@@ -176,7 +188,7 @@
     resetForm()
     // 加载数据源列表
     dataSetList.value = await DataSetApi.getDataSetList()
-    dataCategoryList.value = await CategoryApi.getCategoryListAllSimple()
+    await getCategoryTree()
     // 修改时,设置数据
     if (id) {
       formLoading.value = true
diff --git a/src/views/data/ind/item/CalIndDefineForm.vue b/src/views/data/ind/item/CalIndDefineForm.vue
index 0d5fc81..52d78cc 100644
--- a/src/views/data/ind/item/CalIndDefineForm.vue
+++ b/src/views/data/ind/item/CalIndDefineForm.vue
@@ -20,14 +20,14 @@
       <el-row>
         <el-col :span="12">
           <el-form-item label="指标分类" prop="itemCategory">
-            <el-select v-model="formData.itemCategory" clearable placeholder="请选择指标分类">
-              <el-option
-                v-for="item in dataCategoryList"
-                :key="item.id"
-                :label="item.label"
-                :value="item.id + ''"
-              />
-            </el-select>
+            <el-tree-select
+              v-model="formData.itemCategory"
+              :data="dataCategoryList"
+              :default-expanded-keys="[0]"
+              :props="defaultProps"
+              check-strictly
+              node-key="id"
+            />
           </el-form-item>
         </el-col>
         <el-col :span="12">
@@ -139,6 +139,7 @@
   import * as ItemApi from '@/api/data/ind/item/item'
   import { ElMessage } from 'element-plus'
   import * as CategoryApi from '@/api/data/ind/category/index'
+  import {handleTree} from "@/utils/tree";
 
   defineOptions({name: 'IndDataSetForm'})
 
@@ -183,14 +184,22 @@
   const operatorList = ref(['+', '-', '*', '/', '&', '|', '!', '>', '<'])
   const formRules = reactive({
     itemName: [{required: true, message: '指标名称不能为空', trigger: 'blur'}],
-    itemCategory: [{required: true, message: '指标类型不能为空', trigger: 'blur'}],
-    precision: [{validator: validateAsNumber, trigger: 'blur' }],
-    coefficient: [{validator: validateAsNumber, trigger: 'blur' }],
+    itemCategory: [{required: true, message: '指标类型不能为空', trigger: 'blur'}]
+    // precision: [{validator: validateAsNumber, trigger: 'blur' }],
+    // coefficient: [{validator: validateAsNumber, trigger: 'blur' }],
   })
   const formRef = ref() // 表单 Ref
   const dataSourceList = ref([] as DataSourceConfigApi.DataSourceConfigVO[])
   const queryParams = reactive({})
-  const dataCategoryList = ref([] as CategoryApi.IndItemCategoryVO[])
+
+  const dataCategoryList = ref<Tree[]>([])
+  const getCategoryTree = async () => {
+    dataCategoryList.value = []
+    const res = await CategoryApi.getCategoryListAllSimple()
+    let category: Tree = {id: 0, label: '主类目', children: []}
+    category.children = handleTree(res, 'id', 'pid')
+    dataCategoryList.value.push(category)
+  }
   /** 打开弹窗 */
   const open = async (type: string, id?: number) => {
     dialogVisible.value = true
@@ -199,7 +208,7 @@
     resetForm()
 
     // 加载数据源列表
-    dataCategoryList.value = await CategoryApi.getCategoryListAllSimple()
+    await getCategoryTree()
     itemList.value = await ItemApi.getItemList(queryParams)
     // 修改时,设置数据
     if (id) {
diff --git a/src/views/data/ind/item/DerIndDefineForm.vue b/src/views/data/ind/item/DerIndDefineForm.vue
index 3822e86..dc35975 100644
--- a/src/views/data/ind/item/DerIndDefineForm.vue
+++ b/src/views/data/ind/item/DerIndDefineForm.vue
@@ -7,8 +7,21 @@
       :rules="formRules" label-width="100px">
       <el-row>
         <el-col :span="12">
+          <el-form-item label="指标编码" prop="itemNo">
+            <el-input v-model="formData.itemNo" disabled/>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="指标名称" prop="itemName">
+            <el-input v-model="formData.itemName"/>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="12">
           <el-form-item label="原子指标" prop="atomItem.itemId">
-            <el-select v-model="formData.atomItem.itemId" clearable placeholder="请选择原子指标"
+            <el-select v-model="formData.atomItem.itemId" filterable
+                       allow-create clearable placeholder="请选择原子指标"
                        @change="handleChange($event)">
               <el-option
                 v-for="item in atomItemList"
@@ -27,27 +40,15 @@
       </el-row>
       <el-row>
         <el-col :span="12">
-          <el-form-item label="指标编码" prop="itemNo">
-            <el-input v-model="formData.itemNo" disabled/>
-          </el-form-item>
-        </el-col>
-        <el-col :span="12">
-          <el-form-item label="指标名称" prop="itemName">
-            <el-input v-model="formData.itemName"/>
-          </el-form-item>
-        </el-col>
-      </el-row>
-      <el-row>
-        <el-col :span="12">
           <el-form-item label="指标分类" prop="itemCategory">
-            <el-select v-model="formData.itemCategory" clearable placeholder="请选择指标分类">
-              <el-option
-                v-for="item in dataCategoryList"
-                :key="item.id"
-                :label="item.label"
-                :value="item.id + ''"
-              />
-            </el-select>
+            <el-tree-select
+              v-model="formData.itemCategory"
+              :data="dataCategoryList"
+              :default-expanded-keys="[0]"
+              :props="defaultProps"
+              check-strictly
+              node-key="id"
+            />
           </el-form-item>
         </el-col>
         <el-col :span="12">
@@ -83,7 +84,7 @@
       <el-row>
         <el-col :span="6">
           <el-form-item label="时间标识" prop="timeLabel">
-            <el-select v-model="formData.derItem.timeLabel" clearable placeholder="请选择时间标识">
+            <el-select v-model="formData.derItem.timeLabel" allow-create filterable clearable placeholder="请选择时间标识">
               <el-option
                 v-for="item in dataSetFieldList"
                 :key="item.id"
@@ -130,7 +131,8 @@
       <el-row>
         <el-col :span="24">
           <el-form-item label="分析维度" prop="dimension">
-            <el-select v-model="formData.derItem.dimension" clearable placeholder="请选择分析维度" multiple>
+            <el-select v-model="formData.derItem.dimension" filterable
+                       allow-create clearable placeholder="请选择分析维度" multiple>
               <el-option
                 v-for="item in dataSetFieldList"
                 :key="item.id"
@@ -164,6 +166,7 @@
   import {PageParam} from "@/api/data/ind/item/item";
   import * as CategoryApi from "@/api/data/ind/category";
   import * as DataSetFieldApi from "@/api/data/ind/data/data.field";
+  import {handleTree} from "@/utils/tree";
 
   defineOptions({name: 'IndDataSetForm'})
 
@@ -218,9 +221,16 @@
   const formRef = ref() // 表单 Ref
   const atomItemList = ref([] as ItemApi.ItemVO[])
   const showTimeChange = ref(false)
-  const dataCategoryList = ref([] as CategoryApi.IndItemCategoryVO[])
   const dataSetFieldList = ref([] as DataSetFieldApi.DataSetFieldVO[])
+  const dataCategoryList = ref<Tree[]>([])
 
+  const getCategoryTree = async () => {
+    dataCategoryList.value = []
+    const res = await CategoryApi.getCategoryListAllSimple()
+    let category: Tree = {id: 0, label: '主类目', children: []}
+    category.children = handleTree(res, 'id', 'pid')
+    dataCategoryList.value.push(category)
+  }
   /** 打开弹窗 */
   const open = async (type: string, id?: string) => {
     dialogVisible.value = true
@@ -228,7 +238,7 @@
     formType.value = type
     resetForm()
     // 加载数据源列表
-    dataCategoryList.value = await CategoryApi.getCategoryListAllSimple()
+    await getCategoryTree()
     const queryParams = reactive({
       itemType: 'ATOM'
     })
diff --git a/src/views/data/plan/category/CategoryForm.vue b/src/views/data/plan/category/CategoryForm.vue
index 5bbbaad..8fa7de0 100644
--- a/src/views/data/plan/category/CategoryForm.vue
+++ b/src/views/data/plan/category/CategoryForm.vue
@@ -33,13 +33,13 @@
 <script lang="ts" setup>
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import * as CategoryApi from '@/api/data/plan/category'
-import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
+import {CACHE_KEY, useCache, useSessionCache} from '@/hooks/web/useCache'
 import { CommonStatusEnum, SystemMenuTypeEnum } from '@/utils/constants'
 import { defaultProps, handleTree } from '@/utils/tree'
 
 defineOptions({ name: 'PlanItemCategoryForm' })
 
-const { wsCache } = useCache()
+const { wsSessionCache } = useSessionCache()
 const { t } = useI18n() // 国际化
 const message = useMessage() // 消息弹窗
 
@@ -114,7 +114,7 @@
   } finally {
     formLoading.value = false
     // 清空,从而触发刷新
-    wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
+    wsSessionCache.delete(CACHE_KEY.ROLE_ROUTERS)
   }
 }
 
diff --git a/src/views/data/point/DaPointChart.vue b/src/views/data/point/DaPointChart.vue
index 8713861..caaa271 100644
--- a/src/views/data/point/DaPointChart.vue
+++ b/src/views/data/point/DaPointChart.vue
@@ -87,6 +87,7 @@
 
 defineExpose({open}) // 提供 open 方法,用于打开弹窗
 
+  const loading = ref(false)
   async function getDataList() {
     visible.value = true;
     loading.value = true
diff --git a/src/views/data/point/DaPointForm.vue b/src/views/data/point/DaPointForm.vue
index 0279054..5a1682b 100644
--- a/src/views/data/point/DaPointForm.vue
+++ b/src/views/data/point/DaPointForm.vue
@@ -283,6 +283,38 @@
           </el-form-item>
         </el-col>
       </el-row>
+      <!--累计点-->
+      <el-row :gutter="20" v-if="formData.pointType === 'CUMULATE'">
+        <el-col :span="24">
+          <el-form-item label="瞬时测点" prop="cumulatePoint.momentPoint">
+            <el-select
+              v-model="formData.cumulatePoint.momentPoint"
+              filterable
+              placeholder="请选择">
+              <el-option
+                v-for="(item, index) in pointList2"
+                :key="index"
+                :label="item.pointName"
+                :value="item.pointNo"/>
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="累计长度" prop="cumulatePoint.length">
+            <el-input-number v-model="formData.cumulatePoint.length" style="width: 100%"
+                             :min="1" :max="3000"
+                             :controls="false"/>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="除数" prop="cumulatePoint.divisor">
+            <el-input-number v-model="formData.cumulatePoint.divisor" style="width: 100%"
+                             :min="1" :max="3000"
+                             :controls="false"/>
+          </el-form-item>
+        </el-col>
+
+      </el-row>
     </el-form>
     <template #footer>
       <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
@@ -294,6 +326,7 @@
 import * as DaPoint from '@/api/data/da/point'
 import * as TagApi from '@/api/data/channel/tag'
 import {DICT_TYPE, getDictOptions, getIntDictOptions} from "@/utils/dict";
+import {getPointSimpleList} from "@/api/data/da/point";
 
 defineOptions({name: 'DataDaPointForm'})
 
@@ -311,6 +344,13 @@
 }])
 const queryParams = reactive({
   pointTypes: "MEASURE,CONSTANT",
+})
+const pointList2 = ref([{
+  pointName: '',
+  pointNo: ''
+}])
+const queryParams2 = reactive({
+  pointTypes: "MEASURE,CONSTANT,CALCULATE",
 })
 const operatorList = ref(['+', '-', '*', '/', '&', '|', '!', '>', '<'])
 const formData = ref({
@@ -343,6 +383,13 @@
     tagNo: '',
     dimension: '',
     valueType: '',
+  },
+  cumulatePoint: {
+    id: '',
+    pointId: '',
+    momentPoint: '',
+    length: '',
+    divisor: ''
   }
 })
 const formRules = reactive({
@@ -350,8 +397,11 @@
   pointType: [{required: true, message: '测点类型不能为空', trigger: 'blur'}],
   dataType: [{required: true, message: '数据类型不能为空', trigger: 'blur'}],
   minfreqid: [{required: true, message: '采集频率不能为空', trigger: 'blur'}],
-  "measurePoint.valueType": [{required: true, message: '采集频率不能为空', trigger: 'blur'}],
-  "measurePoint.dimension": [{required: true, message: '采集频率不能为空', trigger: 'blur'}],
+  "measurePoint.valueType": [{required: true, message: '值类型不能为空', trigger: 'blur'}],
+  "measurePoint.dimension": [{required: true, message: '平滑尺度不能为空', trigger: 'blur'}],
+  "cumulatePoint.momentPoint": [{required: true, message: '累计测点不能为空', trigger: 'blur'}],
+  "cumulatePoint.length": [{required: true, message: '累计长度不能为空', trigger: 'blur'}],
+  "cumulatePoint.divisor": [{required: true, message: '除数不能为空', trigger: 'blur'}],
 })
 const formRef = ref() // 表单 Ref
 
@@ -363,6 +413,7 @@
   resetForm()
   getSourceOption()
   getPointList()
+  getPointList2()
   // 修改时,设置数据
   if (id) {
     formLoading.value = true
@@ -465,6 +516,13 @@
       tagNo: '',
       dimension: '1',
       valueType: 'SIMULATE',
+    },
+    cumulatePoint: {
+      id: '',
+      pointId: '',
+      momentPoint: '',
+      length: 60,
+      divisor: 60
     }
   }
   formRef.value?.resetFields()
@@ -525,7 +583,11 @@
 }
 
 const getPointList = async () => {
-  pointList.value = await DaPoint.getPointList(queryParams)
+  pointList.value = await DaPoint.getPointSimpleList(queryParams)
+}
+
+const getPointList2 = async () => {
+  pointList2.value = await DaPoint.getPointSimpleList(queryParams2)
 }
 
 const getInfo = async (id) => {
diff --git a/src/views/data/point/index.vue b/src/views/data/point/index.vue
index 5d63b34..b11294c 100644
--- a/src/views/data/point/index.vue
+++ b/src/views/data/point/index.vue
@@ -35,6 +35,21 @@
           class="!w-200px"
         />
       </el-form-item>
+      <el-form-item label="采集质量" prop="collectQuality">
+        <el-select
+          v-model="queryParams.collectQuality"
+          placeholder="请选择"
+          clearable
+          class="!w-240px"
+        >
+          <el-option
+            v-for="dict in getStrDictOptions(DICT_TYPE.DATA_QUALITY)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
       <el-form-item>
         <el-button @click="handleQuery">
           <Icon icon="ep:search" class="mr-5px" />
@@ -91,9 +106,9 @@
   <!-- 列表 -->
   <ContentWrap>
     <el-table border stripe v-loading="loading" :data="list" @selection-change="selectionChangeHandle">
-      <el-table-column type="selection" header-align="center" align="center" width="50"/>
+      <el-table-column type="selection" header-align="center" align="center" fixed="left" width="50"/>
       <el-table-column fixed label="测点编码" header-align="center" align="left" min-width="130" prop="pointNo" />
-      <el-table-column label="测点名称" header-align="center" align="left" min-width="220" prop="pointName" />
+      <el-table-column fixed label="测点名称" header-align="center" align="left" min-width="240" prop="pointName" />
       <el-table-column label="测点类型" align="center" prop="pointType" width="100">
         <template #default="scope">
           <dict-tag :type="DICT_TYPE.DATA_POINT_TYPE" :value="scope.row.pointType" />
@@ -116,6 +131,13 @@
       <el-table-column label="数据源类型" align="center" prop="sourceType" min-width="100"/>
       <el-table-column label="数据源名称" align="center" prop="sourceName" min-width="100"/>
       <el-table-column label="测点Tag" header-align="center" align="left" prop="tagNo" min-width="150"/>
+      <el-table-column label="采集质量" header-align="center" align="center" prop="collectQuality" width="100">
+        <template #default="scope">
+          <el-tag v-if="scope.row.collectQuality === 'Good'" size="small" type="success">{{scope.row.collectQuality}}</el-tag>
+          <el-tag v-if="scope.row.collectQuality === 'Bad'" size="small" type="danger">{{scope.row.collectQuality}}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="采集时间" header-align="center" align="center" prop="collectTime" min-width="150"/>
       <el-table-column label="是否启用" align="center" prop="isEnable" width="85">
         <template #default="scope">
           <el-tag v-if="scope.row.isEnable === 1" size="small">是</el-tag>
@@ -168,7 +190,7 @@
 import * as DaPoint from '@/api/data/da/point'
 import {ref, reactive} from "vue";
 import download from "@/utils/download";
-import {DICT_TYPE, getDictOptions} from "@/utils/dict";
+import {DICT_TYPE, getDictOptions, getIntDictOptions, getStrDictOptions} from "@/utils/dict";
 import DaPointForm from './DaPointForm.vue'
 import DaPointChart from './DaPointChart.vue'
 import * as UserApi from "@/api/system/user";
@@ -188,6 +210,7 @@
     pointNo: undefined,
     pointName: undefined,
     tagNo: undefined,
+    collectQuality: undefined,
   })
   const queryFormRef = ref() // 搜索的表单
 
@@ -272,9 +295,8 @@
     let ids = dataListSelections.map(item => {
       return item.id
     })
-    // 启用的二次确认
-    await message.enableConfirm(ids)
-
+    // 二次确认
+    await message.confirm('确认要开启所选测点?')
     await DaPoint.enable(ids)
     message.success(t('common.enableSuccess'))
     await getList()
@@ -284,9 +306,8 @@
     let ids = dataListSelections.map(item => {
       return item.id
     })
-    // 启用的二次确认
-    await message.disableConfirm(ids,)
-
+    // 二次确认
+    await message.confirm('确认要禁用所选测点?')
     await DaPoint.disable(ids)
     message.success(t('common.disableSuccess'))
     await getList()
diff --git a/src/views/infra/apiAccessLog/index.vue b/src/views/infra/apiAccessLog/index.vue
index e3a6a7c..7e3a070 100644
--- a/src/views/infra/apiAccessLog/index.vue
+++ b/src/views/infra/apiAccessLog/index.vue
@@ -90,31 +90,31 @@
   <ContentWrap>
     <el-table v-loading="loading" :data="list">
       <el-table-column label="日志编号" align="center" prop="id" width="100" fix="right" />
-      <el-table-column label="用户编号" align="center" prop="userId" />
-      <el-table-column label="用户类型" align="center" prop="userType">
+      <el-table-column label="用户编号" align="center" prop="userId" width="80"/>
+      <el-table-column label="用户类型" align="center" prop="userType" width="100">
         <template #default="scope">
           <dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType" />
         </template>
       </el-table-column>
-      <el-table-column label="应用名" align="center" prop="applicationName" width="150" />
+      <el-table-column label="应用名" align="center" prop="applicationName" width="120" />
       <el-table-column label="请求方法" align="center" prop="requestMethod" width="80" />
-      <el-table-column label="请求地址" align="center" prop="requestUrl" width="500" />
+      <el-table-column label="请求地址" align="center" prop="requestUrl" />
       <el-table-column label="请求时间" align="center" prop="beginTime" width="180">
         <template #default="scope">
           <span>{{ formatDate(scope.row.beginTime) }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="执行时长" align="center" prop="duration" width="180">
+      <el-table-column label="执行时长" align="center" prop="duration" width="100">
         <template #default="scope"> {{ scope.row.duration }} ms </template>
       </el-table-column>
-      <el-table-column label="操作结果" align="center" prop="status">
+      <el-table-column label="操作结果" align="center" prop="status" width="120">
         <template #default="scope">
           {{ scope.row.resultCode === 0 ? '成功' : '失败(' + scope.row.resultMsg + ')' }}
         </template>
       </el-table-column>
       <el-table-column label="操作模块" align="center" prop="operateModule" width="180" />
       <el-table-column label="操作名" align="center" prop="operateName" width="180" />
-      <el-table-column label="操作类型" align="center" prop="operateType">
+      <el-table-column label="操作类型" align="center" prop="operateType" width="80">
         <template #default="scope">
           <dict-tag :type="DICT_TYPE.INFRA_OPERATE_TYPE" :value="scope.row.operateType" />
         </template>
diff --git a/src/views/infra/apiErrorLog/index.vue b/src/views/infra/apiErrorLog/index.vue
index 22f3116..c337684 100644
--- a/src/views/infra/apiErrorLog/index.vue
+++ b/src/views/infra/apiErrorLog/index.vue
@@ -86,16 +86,16 @@
   <!-- 列表 -->
   <ContentWrap>
     <el-table v-loading="loading" :data="list">
-      <el-table-column label="日志编号" align="center" prop="id" />
-      <el-table-column label="用户编号" align="center" prop="userId" />
-      <el-table-column label="用户类型" align="center" prop="userType">
+      <el-table-column label="日志编号" align="center" prop="id" width="100"/>
+      <el-table-column label="用户编号" align="center" prop="userId" width="80"/>
+      <el-table-column label="用户类型" align="center" prop="userType" width="100">
         <template #default="scope">
           <dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType" />
         </template>
       </el-table-column>
-      <el-table-column label="应用名" align="center" prop="applicationName" width="200" />
+      <el-table-column label="应用名" align="center" prop="applicationName" width="120" />
       <el-table-column label="请求方法" align="center" prop="requestMethod" width="80" />
-      <el-table-column label="请求地址" align="center" prop="requestUrl" width="180" />
+      <el-table-column label="请求地址" align="center" prop="requestUrl" />
       <el-table-column
         label="异常发生时间"
         align="center"
@@ -103,8 +103,8 @@
         width="180"
         :formatter="dateFormatter"
       />
-      <el-table-column label="异常名" align="center" prop="exceptionName" width="180" />
-      <el-table-column label="处理状态" align="center" prop="processStatus">
+      <el-table-column label="异常名" align="center" prop="exceptionName" />
+      <el-table-column label="处理状态" align="center" prop="processStatus" width="100">
         <template #default="scope">
           <dict-tag
             :type="DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS"
diff --git a/src/views/infra/config/index.vue b/src/views/infra/config/index.vue
index e12e86d..0f48ede 100644
--- a/src/views/infra/config/index.vue
+++ b/src/views/infra/config/index.vue
@@ -79,17 +79,17 @@
   <!-- 列表 -->
   <ContentWrap>
     <el-table v-loading="loading" :data="list">
-      <el-table-column label="参数主键" align="center" prop="id" />
-      <el-table-column label="参数分类" align="center" prop="category" />
+      <el-table-column label="参数主键" align="center" prop="id" width="80"/>
+      <el-table-column label="参数分类" align="center" prop="category" width="80"/>
       <el-table-column label="参数名称" align="center" prop="name" :show-overflow-tooltip="true" />
-      <el-table-column label="参数键名" align="center" prop="key" :show-overflow-tooltip="true" />
-      <el-table-column label="参数键值" align="center" prop="value" />
-      <el-table-column label="是否可见" align="center" prop="visible">
+      <el-table-column label="参数键名" align="center" prop="key" :show-overflow-tooltip="true"/>
+      <el-table-column label="参数键值" align="center" prop="value" width="350"/>
+      <el-table-column label="是否可见" align="center" prop="visible" width="80">
         <template #default="scope">
           <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.visible" />
         </template>
       </el-table-column>
-      <el-table-column label="系统内置" align="center" prop="type">
+      <el-table-column label="系统内置" align="center" prop="type" width="80">
         <template #default="scope">
           <dict-tag :type="DICT_TYPE.INFRA_CONFIG_TYPE" :value="scope.row.type" />
         </template>
@@ -102,7 +102,7 @@
         width="180"
         :formatter="dateFormatter"
       />
-      <el-table-column label="操作" align="center">
+      <el-table-column label="操作" align="center" width="120">
         <template #default="scope">
           <el-button
             link
diff --git a/src/views/infra/dataSourceConfig/index.vue b/src/views/infra/dataSourceConfig/index.vue
index 92bd301..3c1cdf6 100644
--- a/src/views/infra/dataSourceConfig/index.vue
+++ b/src/views/infra/dataSourceConfig/index.vue
@@ -18,10 +18,10 @@
   <!-- 列表 -->
   <ContentWrap>
     <el-table v-loading="loading" :data="list">
-      <el-table-column label="主键编号" align="center" prop="id" />
-      <el-table-column label="数据源名称" align="center" prop="name" />
+      <el-table-column label="主键编号" align="center" prop="id" width="100"/>
+      <el-table-column label="数据源名称" align="center" prop="name" width="100"/>
       <el-table-column label="数据源连接" align="center" prop="url" :show-overflow-tooltip="true" />
-      <el-table-column label="用户名" align="center" prop="username" />
+      <el-table-column label="用户名" align="center" prop="username" width="100"/>
       <el-table-column
         label="创建时间"
         align="center"
@@ -29,7 +29,7 @@
         width="180"
         :formatter="dateFormatter"
       />
-      <el-table-column label="操作" align="center">
+      <el-table-column label="操作" align="center" width="120">
         <template #default="scope">
           <el-button
             link
diff --git a/src/views/infra/file/index.vue b/src/views/infra/file/index.vue
index e1d2ffd..3598bfa 100644
--- a/src/views/infra/file/index.vue
+++ b/src/views/infra/file/index.vue
@@ -91,6 +91,9 @@
       />
       <el-table-column label="操作" align="center">
         <template #default="scope">
+          <el-button link type="primary" @click="copyToClipboard(scope.row.url)">
+            复制链接
+          </el-button>
           <el-button
             link
             type="danger"
@@ -168,6 +171,13 @@
   formRef.value.open()
 }
 
+/** 复制到剪贴板方法 */
+const copyToClipboard = (text: string) => {
+  navigator.clipboard.writeText(text).then(() => {
+    message.success('复制成功')
+  })
+}
+
 /** 删除按钮操作 */
 const handleDelete = async (id: number) => {
   try {
diff --git a/src/views/micro/index.vue b/src/views/micro/index.vue
index 1a4669c..92b9626 100644
--- a/src/views/micro/index.vue
+++ b/src/views/micro/index.vue
@@ -1,22 +1,20 @@
 <template>
   <div class="sub-app">
-    <WujieVue width="100%" height="100%" :name="name" :url="url" :alive="true" sync />
+    <WujieVue width="100%" height="100%" v-bind="computedOptions" :degrade="true" :alive="true" sync />
   </div>
 </template>
 <script lang="ts" setup>
-import hostMap from "@/utils/hostMap";
-import wujieVue from "wujie-vue3";
-const route = useRoute()
-const url = hostMap("//localhost:90/") + route.params.path
-const name = 'fast'
-watch(() => "$route.params.path",
-  () => {
-    wujieVue.bus.$emit("vue3-router-change", `/${route.params.path}`);
-  },
-  {
-    immediate: true
-  }
-)
+  import WujieVue from "wujie-vue3";
+  import { useRoute } from 'vue-router'
+  import { computed } from 'vue'
+
+  const route = useRoute()
+  const computedOptions = computed(() => {
+    return {
+      name: route.query.key,
+      url: route.query.url,
+    }
+  })
 </script>
 <style scoped lang="scss">
 .sub-app {
diff --git a/src/views/model/mpk/file/MpkForm.vue b/src/views/model/mpk/file/MpkForm.vue
index da3c881..8dfbae9 100644
--- a/src/views/model/mpk/file/MpkForm.vue
+++ b/src/views/model/mpk/file/MpkForm.vue
@@ -333,6 +333,9 @@
     menuAndGroup: [
       {required: true, message: '所属目录不能为空', trigger: 'blur'}
     ],
+    icon: [
+      {required: true, message: 'icon不能为空', trigger: 'blur'}
+    ],
   })
 
   const formRef = ref() // 表单 Ref
@@ -451,7 +454,9 @@
     if (id) {
       formLoading.value = true
       try {
+        debugger
         formData.value = await MpkApi.getMpk(id)
+        debugger
       } finally {
         formLoading.value = false
       }
diff --git a/src/views/model/mpk/file/MpkRun.vue b/src/views/model/mpk/file/MpkRun.vue
index 104377d..2b9039c 100644
--- a/src/views/model/mpk/file/MpkRun.vue
+++ b/src/views/model/mpk/file/MpkRun.vue
@@ -34,28 +34,24 @@
         </el-col>
       </el-row>
       <el-divider content-position="left">模型参数信息</el-divider>
-      <el-row :gutter="20">
-        <el-col :span="2" style="margin-bottom: 10px;margin-left: 20px">
-          <el-button tag="a" :href="staticDir + '/template/模型参数导入模板.xlsx'" download="模型参数导入模板.xlsx" style="text-decoration: none;" type="primary" size="small" link>模板下载</el-button>
-        </el-col>
-        <el-col :span="2" style="margin-bottom: 10px;">
-          <el-upload
-            ref="uploadRef"
-            v-model:file-list="fileList"
-            :show-file-list="false"
-            :action="importUrl"
-            :auto-upload="true"
-            :disabled="formLoading"
-            :before-upload="beforeUpload"
-            :headers="uploadHeaders"
-            :on-error="submitFormError"
-            :on-success="submitFormSuccess"
-            accept=".xlsx"
-          >
-            <el-button type="primary" size="small" link>参数导入</el-button>
-          </el-upload>
-        </el-col>
-      </el-row>
+      <div style="display:flex;flex-direction: row;align-items: center;margin-bottom: 6px">
+        <el-button tag="a" :href="staticDir + '/template/模型参数导入模板.xlsx'" download="模型参数导入模板.xlsx" style="text-decoration: none;" type="primary" size="small" link>模板下载</el-button>
+        <el-upload
+          ref="uploadRef"
+          v-model:file-list="fileList"
+          :show-file-list="false"
+          :action="importUrl"
+          :auto-upload="true"
+          :disabled="formLoading"
+          :before-upload="beforeUpload"
+          :headers="uploadHeaders"
+          :on-error="submitFormError"
+          :on-success="submitFormSuccess"
+          accept=".xlsx"
+        >
+          <el-button type="primary" size="small" link>参数导入</el-button>
+        </el-upload>
+      </div>
       <el-row v-for="(item,index) in datas" :key="index" :gutter="20">
         <el-col :span="24">
           <el-form-item :label="'参数_' + (index)" required style="width: 100%">
@@ -87,23 +83,31 @@
         </el-table-column>
         <el-table-column
           prop=""
+          label="参数名称"
+          align="center">
+          <template #default="scope">
+            <el-input size="small" v-model="scope.row.name" :disabled="true" maxlength="50" clearable />
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop=""
           label="参数value"
           align="center">
           <template #default="scope">
             <el-input size="small" v-model="scope.row.settingValue" :disabled="scope.row.settingKey === 'pyFile'" maxlength="50" clearable />
           </template>
         </el-table-column>
-        <el-table-column label="操作" fixed="right" header-align="center" align="center" width="100">
-          <template #default="scope">
-            <el-button
-              @click="deleteRow(scope.$index)"
-              key="danger"
-              type="danger"
-              :disabled="scope.row.settingKey === 'pyFile'"
-              link
-            >删除</el-button>
-          </template>
-        </el-table-column>
+<!--        <el-table-column label="操作" fixed="right" header-align="center" align="center" width="100">-->
+<!--          <template #default="scope">-->
+<!--            <el-button-->
+<!--              @click="deleteRow(scope.$index)"-->
+<!--              key="danger"-->
+<!--              type="danger"-->
+<!--              :disabled="scope.row.settingKey === 'pyFile'"-->
+<!--              link-->
+<!--            >删除</el-button>-->
+<!--          </template>-->
+<!--        </el-table-column>-->
       </el-table>
       <el-divider content-position="left">模型运行结果</el-divider>
       <el-input v-model="modelRunResult" placeholder="" rows="4" type="textarea" />
diff --git a/src/views/model/mpk/file/index.vue b/src/views/model/mpk/file/index.vue
index 8e98f7f..a3d2bde 100644
--- a/src/views/model/mpk/file/index.vue
+++ b/src/views/model/mpk/file/index.vue
@@ -22,11 +22,20 @@
           ref="queryFormRef"
           :inline="true"
           label-width="68px"
+          @submit.prevent
         >
-          <el-form-item label="模型名称" prop="pyName">
+          <el-form-item label="模型名称" prop="pyChineseName">
+            <el-input
+              v-model="queryParams.pyChineseName"
+              placeholder="请输入模型名称"
+              clearable
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item label="模型文件" prop="pyName">
             <el-input
               v-model="queryParams.pyName"
-              placeholder="请输入模型名称"
+              placeholder="请输入模型文件名称"
               clearable
               class="!w-240px"
             />
@@ -58,8 +67,8 @@
           :data="list"
           row-key="id"
         >
-          <el-table-column prop="pyChineseName" label="模型名称" header-align="center" align="center" min-width="100" />
-          <el-table-column prop="pyName" label="模型文件" header-align="center" align="center" min-width="300"/>
+          <el-table-column prop="pyChineseName" label="模型名称" header-align="center" align="left" min-width="100" />
+          <el-table-column prop="pyName" label="模型文件" header-align="center" align="left" min-width="300"/>
           <el-table-column prop="pyType" label="模型类型" :formatter="(r,c,v) => getDictLabel(DICT_TYPE.MODEL_TYPE,v)"/>
           <el-table-column prop="menuName" label="所属菜单" min-width="120px"/>
           <el-table-column prop="groupName" label="所属组" min-width="120px"/>
@@ -147,6 +156,7 @@
   const queryParams = reactive({
     page: 1,
     limit: 10,
+    pyChineseName: '',
     pyName: '',
     label: ''
   })
diff --git a/src/views/model/mpk/icon/index.vue b/src/views/model/mpk/icon/index.vue
index 2f97330..e9c3654 100644
--- a/src/views/model/mpk/icon/index.vue
+++ b/src/views/model/mpk/icon/index.vue
@@ -7,6 +7,7 @@
       ref="queryFormRef"
       :inline="true"
       label-width="68px"
+      @submit.prevent
     >
       <el-form-item label="模型名称" prop="iconName">
         <el-input
diff --git a/src/views/model/mpk/menu/index.vue b/src/views/model/mpk/menu/index.vue
index 0578c7e..6ba4c18 100644
--- a/src/views/model/mpk/menu/index.vue
+++ b/src/views/model/mpk/menu/index.vue
@@ -7,6 +7,7 @@
       ref="queryFormRef"
       :inline="true"
       label-width="68px"
+      @submit.prevent
     >
       <el-form-item label="菜单名称" prop="name">
         <el-input
diff --git a/src/views/model/mpk/pack/index.vue b/src/views/model/mpk/pack/index.vue
index f3e4a4c..da368c7 100644
--- a/src/views/model/mpk/pack/index.vue
+++ b/src/views/model/mpk/pack/index.vue
@@ -7,6 +7,7 @@
       ref="queryFormRef"
       :inline="true"
       label-width="68px"
+      @submit.prevent
     >
       <el-form-item label="名称" prop="packName">
         <el-input
diff --git a/src/views/model/mpk/project/ProjectForm.vue b/src/views/model/mpk/project/ProjectForm.vue
index d6937f7..585271e 100644
--- a/src/views/model/mpk/project/ProjectForm.vue
+++ b/src/views/model/mpk/project/ProjectForm.vue
@@ -140,7 +140,7 @@
   // 所有模型列表
   const modelList = ref([])
   const getModelList = async () => {
-    modelList.value = await MpkApi.list()
+    modelList.value = await MpkApi.list({})
   }
 
   // 模型筛选
diff --git a/src/views/model/mpk/project/ProjectPackage.vue b/src/views/model/mpk/project/ProjectPackage.vue
index 3034300..8ba4c25 100644
--- a/src/views/model/mpk/project/ProjectPackage.vue
+++ b/src/views/model/mpk/project/ProjectPackage.vue
@@ -3,6 +3,7 @@
     <el-form
       ref="formRef"
       v-loading="formLoading"
+      element-loading-text="打包时间较长,请耐心等待"
       :model="formData"
       :rules="formRules"
       label-width="80px"
@@ -57,17 +58,15 @@
     projectId: undefined,
     projectName: undefined,
     projectCode: undefined,
-    ids: undefined,
     version: undefined,
   })
 
   /** 打开弹窗 */
-  const open = async (projectId,projectName,projectCode,ids) => {
+  const open = async (projectId,projectName,projectCode) => {
     dialogVisible.value = true
     formData.projectId = projectId
     formData.projectName = projectName
     formData.projectCode = projectCode
-    formData.ids = ids
     formData.log = undefined
     formData.version = 'V'
   }
diff --git a/src/views/model/mpk/project/index.vue b/src/views/model/mpk/project/index.vue
index e7d90ba..7bc116d 100644
--- a/src/views/model/mpk/project/index.vue
+++ b/src/views/model/mpk/project/index.vue
@@ -133,6 +133,7 @@
   import ProjectForm from './ProjectForm.vue'
   import ProjectPackage from './ProjectPackage.vue'
   import RelevanceModel from './ProjectPackageModelDialog.vue'
+  import * as projectApi from "@/api/model/mpk/project";
 
   defineOptions({name: 'MpkProject'})
 
@@ -165,7 +166,7 @@
   const handleCommand = (command: string, row) => {
     switch (command) {
       case 'packageModel':
-        packageModel(row.id, row.projectName, row.projectCode, row.models)
+        packageModel(row.id, row.projectName, row.projectCode)
         break
       default:
         break
@@ -174,13 +175,14 @@
 
   //打包
   const projectPackageRef = ref();
-  const packageModel = (projectId, projectName, projectCode, models) => {
-    let ids = models.map(e => e.id);
-    if (ids && ids.length > 0) {
-      projectPackageRef.value.open(projectId, projectName, projectCode, ids.join(","));
-    } else {
+  const packageModel = async (projectId, projectName, projectCode) => {
+    //校验是否关联模型
+    const data = await projectApi.getProjectModel({page: 1, pageSize: 1, projectId: projectId})
+    if (data.total === 0) {
       message.error("请先为项目添加模型!")
+      return
     }
+    projectPackageRef.value.open(projectId, projectName, projectCode);
   }
 
   /** 搜索按钮操作 */
diff --git a/src/views/model/pre/dm/index.vue b/src/views/model/pre/dm/index.vue
index 77ebac9..27df07a 100644
--- a/src/views/model/pre/dm/index.vue
+++ b/src/views/model/pre/dm/index.vue
@@ -7,6 +7,7 @@
       ref="queryFormRef"
       :inline="true"
       label-width="68px"
+      @submit.prevent
     >
       <el-form-item label="名称" prop="modulename">
         <el-input
diff --git a/src/views/model/pre/item/MmPredictItemChart.vue b/src/views/model/pre/item/MmPredictItemChart.vue
index a0f867a..4d24c06 100644
--- a/src/views/model/pre/item/MmPredictItemChart.vue
+++ b/src/views/model/pre/item/MmPredictItemChart.vue
@@ -3,6 +3,7 @@
     title="预测数据"
     :close-on-click-modal="false"
     width="50%"
+    @close="dialogClose"
     v-model="visible"
   >
     <el-form
@@ -12,7 +13,6 @@
     >
       <el-form-item label="开始时间">
         <el-date-picker
-          size="mini"
           v-model="dataForm.startTime"
           format="YYYY-MM-DD HH:mm:00"
           value-format="YYYY-MM-DD HH:mm:00"
@@ -22,7 +22,6 @@
       </el-form-item>
       <el-form-item label="结束时间">
         <el-date-picker
-          size="mini"
           v-model="dataForm.endTime"
           format="YYYY-MM-DD HH:mm:00"
           value-format="YYYY-MM-DD HH:mm:00"
@@ -58,8 +57,8 @@
 
 const message = useMessage() // 消息弹窗
 const visible = ref(false);
-const chartDomPre = ref(null);
-let myChart = null;
+const chartDomPre = ref();
+let myChart = undefined;
 const chartParams = reactive({
   itemId: undefined,
   startTime: undefined,
@@ -79,7 +78,16 @@
   dataForm.value.id = row.id;
   dataForm.value.itemName = row.itemname;
   if (row.id) {
+    nextTick(() => {
+      myChart = echarts.init(chartDomPre.value);
+    });
     getDataList();
+  }
+}
+
+const dialogClose = () => {
+  if (myChart) {
+    myChart.dispose(); // 组件卸载时销毁实例
   }
 }
 
@@ -94,12 +102,12 @@
       chartParams.endTime = dataForm.value.endTime;
       const data = await McsApi.getPreDataItemChart(chartParams)
       let legendData = []
-      if (data.legend && data.legend.length > 0) {
-        data.legend.forEach(item => {
-          legendData.push(item + ":" + '真实值')
-          legendData.push(item + ":" + '预测值')
-        })
-      }
+      // if (data.legend && data.legend.length > 0) {
+      //   data.legend.forEach(item => {
+      //     legendData.push(item + ":" + '真实值')
+      //     legendData.push(item + ":" + '预测值')
+      //   })
+      // }
 
       let seriesData = []
       if (data.predictTime) {
@@ -131,18 +139,22 @@
       if (data.viewMap) {
         Object.keys(data.viewMap).forEach(key => {
           let viewData = data.viewMap[key]
-          seriesData.push({
-            name: key + ":" + '真实值',
-            type: "line",
-            data: viewData.realData,
-            showSymbol: false,
-            smooth: false,
-            lineStyle: {
-              normal: {
-                width: 1,
+          if(viewData.realData) {
+            legendData.push(key + ":" + '真实值')
+            seriesData.push({
+              name: key + ":" + '真实值',
+              type: "line",
+              data: viewData.realData,
+              showSymbol: false,
+              smooth: false,
+              lineStyle: {
+                normal: {
+                  width: 1,
+                },
               },
-            },
-          })
+            })
+          }
+          legendData.push(key + ":" + '预测值')
           seriesData.push({
             name: key + ":" + '预测值',
             type: "line",
@@ -158,7 +170,6 @@
         })
       }
 
-      myChart = echarts.init(chartDomPre.value);
       const option = {
         title: {
           text: dataForm.value.itemName,
diff --git a/src/views/model/pre/item/MmPredictItemForm.vue b/src/views/model/pre/item/MmPredictItemForm.vue
index 2ee0b2e..c8eee15 100644
--- a/src/views/model/pre/item/MmPredictItemForm.vue
+++ b/src/views/model/pre/item/MmPredictItemForm.vue
@@ -78,7 +78,7 @@
       <el-row>
         <el-col :span="12">
           <el-form-item label="管网" prop="dmModuleItem.moduleid">
-            <el-select v-model="dataForm.dmModuleItem.moduleid" placeholder="请选择">
+            <el-select v-model="dataForm.dmModuleItem.moduleid" placeholder="请选择" @change="clearExpressionList">
               <el-option
                 v-for="item in moduleList"
                 :key="item.id"
@@ -103,10 +103,26 @@
       </el-row>
       <el-row v-if="dataForm.itemtypename === 'MergeItem'">
         <el-col :span="12">
-          <el-form-item label="预测长度">
+          <el-form-item label="预测长度" prop="mmPredictItem.predictlength">
             <el-input
+              @change="clearExpressionList"
               v-model="dataForm.mmPredictItem.predictlength" placeholder="预测长度"
               maxlength="5"/>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="真实数据点">
+            <el-select
+              v-model="dataForm.pointId"
+              filterable
+              clearable
+              placeholder="请选择">
+              <el-option
+                v-for="item in pointList"
+                :key="item.id"
+                :label="item.pointName"
+                :value="item.id"/>
+            </el-select>
           </el-form-item>
         </el-col>
       </el-row>
@@ -135,9 +151,7 @@
               <Icon icon="ep:upload"/>
               上传模型
             </el-button>
-            <el-button
-              size="small" type="primary" @click="setReplaceModelOnly(true)"
-              v-if="formType.value === 'update'">
+            <el-button type="primary" plain @click="setReplaceModelOnly(true)">
               <Icon icon="ep:upload"/>
               更新模型
             </el-button>
@@ -146,7 +160,7 @@
       </el-row>
       <el-row v-if="dataForm.itemtypename === 'NormalItem'">
         <el-col :span="12">
-          <el-form-item label="关联项目">
+          <el-form-item label="关联项目" prop="mmPredictModel.mpkprojectid">
             <el-select v-model="dataForm.mmPredictModel.mpkprojectid" placeholder="请选择">
               <el-option
                 v-for="item in mpkProjectList"
@@ -201,7 +215,8 @@
       <el-divider content-position="left" v-if="dataForm.itemtypename === 'NormalItem'">模型输出
       </el-divider>
       <el-button
-        @click="addItemOutput(dataForm.mmItemOutputList)"
+        v-if="dataForm.itemtypename === 'NormalItem'"
+        @click="addItemOutput()"
         type="primary"
         size="small">
         添加
@@ -249,6 +264,7 @@
             <el-select
               v-model="scope.row.pointid"
               filterable
+              clearable
               @change="(value) => changeOutputPoint(value,scope.row)"
               placeholder="请选择">
               <el-option
@@ -282,7 +298,8 @@
         <el-table-column prop="valuetype" label="类型" align="center" min-width="150"/>
         <el-table-column prop="" label="值" align="center" min-width="200">
           <template #default="scope">
-            <el-input size="mini" v-model="scope.row.value" maxlength="256"
+            <el-input v-model="scope.row.value" maxlength="1000"
+                      :disabled="scope.row.key === 'pyFile'"
                       style="width:100%;height:100%"/>
           </template>
         </el-table-column>
@@ -312,7 +329,26 @@
         </el-table-column>
         <el-table-column prop="" label="参数名称" align="center">
           <template #default="scope">
-            <el-select
+            <el-select v-if="scope.row.modelparamtype === 'NormalItem'"
+              v-model="scope.row.modelparamid"
+              placeholder="请选择"
+              filterable
+              @change="changeModelparam(scope.row)"
+              style="width: 100%">
+              <el-option-group
+                v-for="group in modelparamListMap['NormalItem']"
+                :key="group.value"
+                :label="group.label"
+              >
+                <el-option
+                  v-for="item in group.children"
+                  :key="item.value"
+                  :label="item.label"
+                  :value="item.value"
+                />
+              </el-option-group>
+            </el-select>
+            <el-select v-else
               v-model="scope.row.modelparamid"
               filterable
               @change="changeModelparam(scope.row)"
@@ -336,14 +372,12 @@
           <template #default="scope">
             <el-button
               @click="addRow(scope.$index, dataForm.mmModelParamList)"
-              type="text"
-              size="mini">
+              type="text">
               添加
             </el-button>
             <el-button
-              @click="deleteRow(scope.$index, dataForm.mmModelParamList)"
-              type="text"
-              size="mini">
+              @click="deleteRow(scope.$index, scope.row, dataForm.mmModelParamList)"
+              type="text">
               删除
             </el-button>
           </template>
@@ -358,18 +392,28 @@
         style="width: 100%; margin-top: 5px;">
         <el-table-column
           prop=""
-          label="预测项"
+          label="预测项(NormalItem)"
           align="center">
           <template #default="scope">
             <el-select
               v-model="scope.row.point"
+              placeholder="请选择"
               filterable
-              placeholder="请选择">
-              <el-option
-                v-for="(item, index) in predictItemList"
-                :key="index"
-                :label="item.itemname"
-                :value="item.itemno"/>
+              :no-data-text="'无数据(预测长度:' + dataForm.mmPredictItem.predictlength + ';管网:' + moduleList.find(e => e.id === dataForm.dmModuleItem.moduleid)?.modulename + ')'"
+              @change="changeNormalItemSelect"
+              style="width: 100%">
+              <el-option-group
+                v-for="group in modelparamListMap['NormalItem'].filter(e => e.predictlength == dataForm.mmPredictItem.predictlength && e.moduleid === dataForm.dmModuleItem.moduleid)"
+                :key="group.value"
+                :label="group.label"
+              >
+                <el-option
+                  v-for="item in group.children"
+                  :key="item.value"
+                  :label="item.label"
+                  :value="item.value"
+                />
+              </el-option-group>
             </el-select>
           </template>
         </el-table-column>
@@ -446,7 +490,6 @@
 const pointNoList = ref([])
 const pointList = ref([])
 const pointMap = ref({})
-const predictItemList = ref([])
 const modelparamListMap = ref({})
 const modelparamMap = ref({})
 const expressionList = ref([])
@@ -514,7 +557,8 @@
     num: undefined
   },
   mmModelArithSettingsList: [],
-  mmModelParamList: []
+  mmModelParamList: [],
+  pointId: undefined
 })
 const formRules = reactive({
   'mmPredictItem.itemname': [{required: true, message: '预测项名不能为空', trigger: 'blur'}],
@@ -532,6 +576,8 @@
   'mmPredictItem.status': [{required: true, message: '是否启用不能为空', trigger: 'blur'}],
   'dmModuleItem.moduleid': [{required: true, message: '管网不能为空', trigger: 'blur'}],
   'dmModuleItem.itemorder': [{required: true, message: '排序不能为空', trigger: 'blur'}],
+  'mmPredictItem.predictlength': [{required: true, message: '预测长度不能为空', trigger: 'blur'}],
+  'mmPredictModel.mpkprojectid': [{required: true, message: '关联项目不能为空', trigger: 'blur'}],
 
 })
 const formRef = ref() // 表单 Ref
@@ -547,7 +593,7 @@
   setDefaultFields()
 
   // 加载参数列表
-  modelparamListMap.value = await ScheduleModelApi.getModelParamList()
+  modelparamListMap.value = await ScheduleModelApi.getModelParamList(id)
 
   // 获取预测项类型列表
   itemTypeList.value = await MmItemType.getItemTypeList()
@@ -563,11 +609,6 @@
 
   // 获取mpk项目列表
   mpkProjectList.value = await ProjectApi.list()
-
-  // 获取normal列表
-  predictItemList.value = await MmPredictItem.getMmPredictItemList({
-    itemtypename: 'NormalItem'
-  })
 
   // 获取数据点列表
   pointNoList.value = await DaPoint.getPointList(queryParams)
@@ -599,21 +640,40 @@
   if (!formRef) return
   const valid = await formRef.value.validate()
   if (!valid) return
-
   //校验模型输出
-  if (dataForm.value.mmItemOutputList == undefined || dataForm.value.mmItemOutputList.length <= 0) {
-    message.error("模型输出不为空")
-    return
+  if (dataForm.value.itemtypename === 'NormalItem') {
+    if (dataForm.value.mmItemOutputList == undefined || dataForm.value.mmItemOutputList.length <= 0) {
+      message.error("模型输出不为空")
+      return
+    }
+
+    let flag = false
+    dataForm.value.mmItemOutputList.forEach(e => {
+      if (e.resultstr == undefined || e.resultstr === '' || e.resultType == undefined || e.resultType === '' || (e.resultType === 2 && (e.resultIndex == undefined || e.resultIndex === ''))) {
+        message.error("模型输出数据异常")
+        flag = true
+        return
+      }
+    })
+    if (flag) return
+  }
+  if (dataForm.value.itemtypename === 'MergeItem') {
+    if (expressionList.value == undefined || expressionList.value.length <= 1) {
+      message.error("表达式长度低于2")
+      return
+    }
+
+    let flag = false
+    expressionList.value.forEach((e,index) => {
+      if (e.point == undefined || e.point === '' || ((e.operator == undefined || e.operator === '') && index != expressionList.value.length - 1)) {
+        message.error("表达式数据异常")
+        flag = true
+        return
+      }
+    })
+    if (flag) return
   }
 
-  let flag = false
-  dataForm.value.mmItemOutputList.forEach(e => {
-    if (e.resultstr == undefined || e.resultstr === '' || e.resultType == undefined || e.resultType === '' || e.pointid == undefined || e.pointid === '' || (e.resultType === 2 && (e.resultIndex == undefined || e.resultIndex === ''))) {
-      message.error("模型输出数据异常")
-      flag = true
-    }
-  })
-  if (flag) return
 
   // 提交请求
   formLoading.value = true
@@ -631,7 +691,7 @@
     }
     if (dataForm.value.mmModelArithSettingsList) {
       for (let item of dataForm.value.mmModelArithSettingsList) {
-        if (item.key === 'lenpredict') {
+        if (item.key === 'predictLength') {
           dataForm.value.mmPredictItem.predictlength = item.value
         }
       }
@@ -679,7 +739,7 @@
         let endIndex = (indexSub == -1 || (indexPlus < indexSub && indexPlus !== -1)) ? indexPlus : indexSub
         expressionList.value.push({
           point: expression.substring(0, endIndex),
-          operator: expression.substring(endIndex, 1)
+          operator: expression.substring(endIndex, endIndex + 1)
         })
         expression = expression.substring(endIndex + 1)
       } else {
@@ -719,7 +779,8 @@
     dataForm.value.mmPredictModel.modelparamstructure = ''
     if (response.data.loadFieldSetList && response.data.loadFieldSetList[0].propertyList) {
       response.data.loadFieldSetList[0].propertyList.forEach(function (value) {
-        if (value.key !== 'data1') {
+        //匹配  data数字
+        if (!/^data\d+$/.test(value.key)) {
           dataForm.value.mmModelArithSettingsList.push({
             key: value.key,
             name: value.name,
@@ -788,9 +849,9 @@
   rows.splice(index, 0, row)
 }
 
-function deleteRow(index: string, rows) {
-  if (!rows || rows.length === 1) {
-    message.error('不能全部删除!')
+function deleteRow(index, row, rows) {
+  if (!rows || rows.length === 1 || rows.filter(e => e.modelparamportorder === row.modelparamportorder).length === 1) {
+    message.error('不可删除!')
     return
   }
   rows.splice(index, 1)
@@ -803,9 +864,12 @@
   orderRow(rows)
 }
 
-function addItemOutput(list) {
-  list.push({})
-  orderItemOutput(list)
+function addItemOutput() {
+  if (!dataForm.value.mmItemOutputList) {
+    dataForm.value.mmItemOutputList = []
+  }
+  dataForm.value.mmItemOutputList.push({})
+  orderItemOutput(dataForm.value.mmItemOutputList)
 }
 
 function deleteItemOutput(index: string, rows) {
@@ -866,6 +930,13 @@
   fileList.value = []
 }
 
+const clearExpressionList = (value) => {
+  expressionList.value = [{
+    point: '',
+    operator: ''
+  }]
+}
+
 /** 重置表单 */
 const resetForm = () => {
   dataForm.value = {
diff --git a/src/views/model/pre/item/index.vue b/src/views/model/pre/item/index.vue
index 2158644..8bf49b6 100644
--- a/src/views/model/pre/item/index.vue
+++ b/src/views/model/pre/item/index.vue
@@ -7,6 +7,7 @@
       ref="queryFormRef"
       :inline="true"
       label-width="68px"
+      @submit.prevent
     >
       <el-form-item label="编号" prop="itemno">
         <el-input
@@ -25,6 +26,42 @@
           @keyup.enter="handleQuery"
           class="!w-240px"
         />
+      </el-form-item>
+      <el-form-item label="类型" prop="itemtypeid">
+        <el-select
+          v-model="queryParams.itemtypeid"
+          placeholder="请选择"
+          clearable
+          class="!w-240px">
+          <el-option
+            v-for="item in itemTypeList"
+            :key="item.id"
+            :label="item.itemtypename"
+            :value="item.id"/>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="管网名称" prop="modulename">
+        <el-input
+          v-model="queryParams.modulename"
+          placeholder="请输入管网名称"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="运行状态" prop="runStatus">
+        <el-select
+          v-model="queryParams.runStatus"
+          placeholder="请选择"
+          clearable
+          class="!w-240px">
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.ITEM_RUN_STATUS)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
       </el-form-item>
       <el-form-item>
         <el-button @click="handleQuery">
@@ -65,16 +102,7 @@
           <dict-tag :type="DICT_TYPE.PRED_GRANULARITY" :value="scope.row.granularity" />
         </template>
       </el-table-column>
-      <el-table-column label="是否融合" align="center" prop="isfuse">
-        <template #default="scope">
-          <dict-tag :type="DICT_TYPE.COM_IS_INT" :value="scope.row.isfuse" />
-        </template>
-      </el-table-column>
-      <el-table-column label="是否检查" align="center" prop="workchecked">
-        <template #default="scope">
-          <dict-tag :type="DICT_TYPE.COM_IS_INT" :value="scope.row.workchecked" />
-        </template>
-      </el-table-column>
+      <el-table-column label="管网名称" align="center" prop="modulename" />
       <el-table-column label="是否启用" align="center" prop="status">
         <template #default="scope">
           <dict-tag :type="DICT_TYPE.COM_IS_INT" :value="scope.row.status" />
@@ -83,7 +111,7 @@
       <el-table-column label="运行时间" min-width="150" align="center" prop="lastTime"/>
       <el-table-column label="运行状态" align="center" prop="runStatus">
         <template #default="scope">
-          <dict-tag :type="DICT_TYPE.ITEM_RUN_STATUS" :value="scope.row.runStatus" />
+          <dict-tag :type="DICT_TYPE.ITEM_RUN_STATUS" :value="scope.row.runStatus || 200" />
         </template>
       </el-table-column>
       <el-table-column label="运行耗时(ms)" align="center" prop="duration"/>
@@ -92,17 +120,15 @@
           <el-button
             link
             type="primary"
-            size="mini"
             @click="openForm('update', scope.row.id, scope.row.itemtypename)"
             v-hasPermi="['model:pre-item:update']"
           >
             编辑
           </el-button>
-          <el-button link size="mini" type="primary" @click="chartHandle(scope.row)">数据</el-button>
+          <el-button link type="primary" @click="chartHandle(scope.row)">数据</el-button>
           <el-button
             link
             type="danger"
-            size="mini"
             @click="handleDelete(scope.row.id)"
             v-hasPermi="['model:pre-item:delete']"
           >
@@ -130,14 +156,16 @@
 <script lang="ts" setup>
 import MmPredictItemForm from './MmPredictItemForm.vue'
 import MmPredictItemChart from './MmPredictItemChart.vue'
+import * as MmItemType from '@/api/model/pre/type'
 import * as MmPredictItem from '@/api/model/pre/item'
-import {DICT_TYPE} from "@/utils/dict";
+import {DICT_TYPE, getIntDictOptions} from "@/utils/dict";
 
 defineOptions({name: 'DataMmPredictItem'})
 
 const message = useMessage() // 消息弹窗
 const {t} = useI18n() // 国际化
 
+const itemTypeList = ref([])
 const loading = ref(true) // 列表的加载中
 const total = ref(0) // 列表的总页数
 const list = ref([]) // 列表的数据
@@ -146,6 +174,8 @@
   pageSize: 10,
   itemno: undefined,
   itemname: undefined,
+  itemtypeid: undefined,
+  modulename: undefined,
 })
 const isList = ref([
   {
@@ -212,5 +242,7 @@
 /** 初始化 **/
 onMounted(async () => {
   await getList()
+  // 获取预测项类型列表
+  itemTypeList.value = await MmItemType.getItemTypeList()
 })
 </script>
diff --git a/src/views/model/pre/type/index.vue b/src/views/model/pre/type/index.vue
index 601d7c1..bc1eab1 100644
--- a/src/views/model/pre/type/index.vue
+++ b/src/views/model/pre/type/index.vue
@@ -7,6 +7,7 @@
       ref="queryFormRef"
       :inline="true"
       label-width="68px"
+      @submit.prevent
     >
       <el-form-item label="名称" prop="itemtypename">
         <el-input
diff --git a/src/views/model/sche/model/ScheduleModelForm.vue b/src/views/model/sche/model/ScheduleModelForm.vue
index 84b1f3d..397123b 100644
--- a/src/views/model/sche/model/ScheduleModelForm.vue
+++ b/src/views/model/sche/model/ScheduleModelForm.vue
@@ -7,15 +7,11 @@
       :rules="formRules"
       label-width="120px"
     >
+      <el-divider content-position="left">基本信息</el-divider>
       <el-row>
         <el-col :span="12">
           <el-form-item label="模型编号" prop="modelCode">
             <el-input v-model="formData.modelCode" placeholder="请输入模型编号" />
-          </el-form-item>
-        </el-col>
-        <el-col :span="12">
-          <el-form-item label="模型名称" prop="modelName">
-            <el-input v-model="formData.modelName" placeholder="请输入模型名称" />
           </el-form-item>
         </el-col>
       </el-row>
@@ -45,36 +41,54 @@
           </el-form-item>
         </el-col>
       </el-row>
+      <el-divider content-position="left">模型信息</el-divider>
+      <div style="width: 120px;text-align: right;margin-bottom: 8px">
+        <el-popover placement="right" :width="300" trigger="click" ref="modelPopover" @before-enter="model = undefined">
+          <template #reference>
+            <span style="color: #409eff;cursor: pointer">关联模型信息</span>
+          </template>
+          <template #default>
+            <div style="display:flex;flex-direction: row;align-items: center;">
+              <el-cascader style="width: 100%" v-model="model" placeholder="选择模型" :teleported="false" @change="changeModel" :options="scheduleModelList"/>
+            </div>
+          </template>
+        </el-popover>
+      </div>
       <el-row>
-        <el-col :span="24">
+        <el-col :span="12">
+          <el-form-item label="模型名称" prop="modelName">
+            <el-input v-model="formData.modelName" placeholder="请输入模型名称"/>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
           <el-form-item label="类名" prop="className">
-            <el-input v-model="formData.className" placeholder="请输入类名 " />
+            <el-input v-model="formData.className" placeholder="请输入类名" :disabled="true" />
           </el-form-item>
         </el-col>
       </el-row>
       <el-row>
         <el-col :span="12">
           <el-form-item label="方法名" prop="methodName">
-            <el-input v-model="formData.methodName" placeholder="请输入方法名 " />
+            <el-input v-model="formData.methodName" placeholder="请输入方法名" :disabled="true" />
           </el-form-item>
         </el-col>
         <el-col :span="12">
           <el-form-item label="参数数量" prop="portLength">
-            <el-input-number v-model="formData.portLength" :min="0" controls-position="right" />
+            <el-input-number v-model="formData.portLength" :min="0" controls-position="right" :disabled="true" />
           </el-form-item>
         </el-col>
       </el-row>
       <el-row>
         <el-col :span="24">
           <el-form-item label="参数构造" prop="paramStructure">
-            <el-input v-model="formData.paramStructure" placeholder="请输入参数构造 " />
+            <el-input v-model="formData.paramStructure" placeholder="请输入参数构造" :disabled="true" />
           </el-form-item>
         </el-col>
       </el-row>
       <el-row>
         <el-col :span="24">
           <el-form-item label="模型路径" prop="modelPath">
-            <el-input v-model="formData.modelPath" placeholder="模型路径" />
+            <el-input v-model="formData.modelPath" placeholder="模型路径" :disabled="true" />
           </el-form-item>
         </el-col>
       </el-row>
@@ -89,7 +103,7 @@
           width="100"
           align="center">
           <template #default="scope">
-            <el-input size="mini" v-model="scope.row.modelparamportorder" maxlength="5" clearable
+            <el-input v-model="scope.row.modelparamportorder" maxlength="5" clearable :disabled="true"
                       style="width:100%; hight:100%"/>
           </template>
         </el-table-column>
@@ -99,7 +113,7 @@
           width="100"
           align="center">
           <template #default="scope">
-            <el-input size="mini" v-model="scope.row.modelparamorder" maxlength="5" clearable
+            <el-input v-model="scope.row.modelparamorder" maxlength="5" clearable
                       style="width:100%;hight:100%"/>
           </template>
         </el-table-column>
@@ -109,7 +123,7 @@
           width="150"
           align="center">
           <template #default="scope">
-            <el-select v-model="scope.row.modelparamtype" placeholder="请选择">
+            <el-select v-model="scope.row.modelparamtype" placeholder="请选择" @change="changeModelparamtype(scope.row)">
               <el-option
                 v-for="dict in getStrDictOptions(DICT_TYPE.MODEL_PARAM_TYPE)"
                 :key="dict.value"
@@ -124,8 +138,25 @@
           label="参数名称"
           align="center">
           <template #default="scope">
-            <el-select
-              size="mini"
+            <el-select v-if="scope.row.modelparamtype === 'NormalItem'"
+              v-model="scope.row.modelparamid"
+              placeholder="请选择"
+              filterable
+              style="width: 100%">
+              <el-option-group
+                v-for="group in modelparamListMap['NormalItem']"
+                :key="group.value"
+                :label="group.label"
+              >
+                <el-option
+                  v-for="item in group.children"
+                  :key="item.value"
+                  :label="item.label"
+                  :value="item.value"
+                />
+              </el-option-group>
+            </el-select>
+            <el-select v-else
               v-model="scope.row.modelparamid"
               filterable
               placeholder="请选择">
@@ -162,7 +193,7 @@
             </el-button>
             <el-button
               link
-              @click.prevent="deleteRow(scope.$index, formData.paramList)"
+              @click.prevent="deleteRow(scope.$index, scope.row, formData.paramList)"
               type="primary"
               size="small">
               删除
@@ -179,27 +210,30 @@
         <el-table-column
           prop=""
           label="键"
+          min-width="150"
           align="center">
           <template #default="scope">
-            <el-input size="mini" v-model="scope.row.key" maxlength="20" clearable
+            <el-input v-model="scope.row.key" maxlength="20" clearable :disabled="true"
                       style="width:100%;hight:100%"/>
           </template>
         </el-table-column>
         <el-table-column
           prop=""
           label="名称"
+          min-width="150"
           align="center">
           <template #default="scope">
-            <el-input size="mini" v-model="scope.row.name" maxlength="20" clearable
+            <el-input v-model="scope.row.name" maxlength="20" clearable :disabled="true"
                       style="width:100%;hight:100%"/>
           </template>
         </el-table-column>
         <el-table-column
           prop=""
           label="类型"
+          min-width="100"
           align="center">
           <template #default="scope">
-            <el-select v-model="scope.row.valuetype" placeholder="请选择">
+            <el-select v-model="scope.row.valuetype" placeholder="请选择" :disabled="true">
               <el-option
                 v-for="dict in getStrDictOptions(DICT_TYPE.MODEL_METHOD_SETTING_VALUE_TYPE)"
                 :key="dict.value"
@@ -212,31 +246,96 @@
         <el-table-column
           prop=""
           label="值"
+          min-width="300"
           align="center">
           <template #default="scope">
-            <el-input size="mini" v-model="scope.row.value" maxlength="256" clearable
+            <el-input v-model="scope.row.value" maxlength="256" clearable
+                      :disabled="scope.row.key === 'pyFile_BAK'"
                       style="width:100%;hight:100%"/>
+          </template>
+        </el-table-column>
+      </el-table>
+      <el-divider content-position="left">模型下发配置</el-divider>
+      <el-row :gutter="20">
+        <el-col :span="4">
+          <el-button type="primary" size="small" @click="addRowOut()" >新增</el-button>
+        </el-col>
+      </el-row>
+      <el-table
+        :data="formData.modelOut"
+        border
+        style="width: 100%; margin-top: 5px;">
+        <el-table-column prop="resultKey" label="输出key" align="center" min-width="100">
+          <template #default="scope">
+            <el-input size="mini" v-model="scope.row.resultKey" style="width:100%;height:100%"/>
+          </template>
+        </el-table-column>
+        <el-table-column prop="resultType" label="数据类型" align="center" min-width="150">
+          <template #default="scope">
+            <el-select v-model="scope.row.resultType" placeholder="请选择">
+              <el-option
+                v-for="dict in getStrDictOptions(DICT_TYPE.RESULT_TYPE)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </template>
+        </el-table-column>
+        <el-table-column prop="resultPort" label="角标1" align="center" min-width="100">
+          <template #default="scope">
+            <el-input-number :min="0" clearable controls-position="right" size="mini" v-model="scope.row.resultPort" style="width:100%;height:100%"/>
+          </template>
+        </el-table-column>
+        <el-table-column prop="resultIndex" label="角标2" align="center" min-width="100">
+          <template #default="scope">
+            <el-input-number :min="0" clearable controls-position="right" size="mini" v-model="scope.row.resultIndex" style="width:100%;height:100%"/>
+          </template>
+        </el-table-column>
+        <el-table-column prop="isWrite" label="是否下发" align="center" min-width="100">
+          <template #default="scope">
+            <el-switch size="small" v-model="scope.row.isWrite" :active-value="1"
+                       :inactive-value="0"/>
           </template>
         </el-table-column>
         <el-table-column
           prop=""
-          label="操作"
-          width="100"
-          align="center">
+          label="测点名称"
+          align="center" min-width="200">
+          <template #default="scope">
+            <el-select v-model="scope.row.pointNo"
+                       filterable
+                       placeholder="请选择">
+              <el-option
+                v-for="(item, index) in modelparamListMap['DATAPOINT']"
+                :key="index"
+                :label="item.name"
+                :value="item.itemNo"/>
+            </el-select>
+          </template>
+        </el-table-column>
+        <el-table-column prop="disturbancePointNo’" label="无扰切换点位" align="center" min-width="200">
+          <template #default="scope">
+            <el-select v-model="scope.row.disturbancePointNo"
+                       clearable
+                       filterable
+                       placeholder="请选择">
+              <el-option
+                v-for="(item, index) in modelparamListMap['DATAPOINT']"
+                :key="index"
+                :label="item.name"
+                :value="item.itemNo"/>
+            </el-select>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" fixed="right" header-align="center" align="center" width="100">
           <template #default="scope">
             <el-button
-              @click.prevent="addRow(scope.$index, formData.settingList)"
+              @click="deleteModelOutRow(scope.$index)"
+              key="danger"
+              type="danger"
               link
-              type="primary"
-              size="small">
-              添加
-            </el-button>
-            <el-button
-              @click.prevent="deleteRow(scope.$index, formData.settingList)"
-              link
-              type="primary"
-              size="small">
-              删除
+            >删除
             </el-button>
           </template>
         </el-table-column>
@@ -252,6 +351,8 @@
   import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
   import * as ScheduleModelApi from '@/api/model/sche/model'
   import { CommonStatusEnum } from '@/utils/constants'
+  import * as MpkApi from "@/api/model/mpk/mpk";
+  import {generateUUID} from "@/utils";
 
   defineOptions({ name: 'ScheduleModelForm' })
 
@@ -274,19 +375,9 @@
     resultStrId: undefined,
     invocation: undefined,
     status: CommonStatusEnum.ENABLE,
-    paramList: [{
-      modelparamportorder: '1',
-      modelparamorder: '1',
-      modelparamtype: '',
-      modelparamid: '',
-      datalength: ''
-    }],
-    settingList: [{
-      key: '',
-      value: '',
-      valuetype: '',
-      name: ''
-    }]
+    paramList: [],
+    settingList: [],
+    modelOut: []
   })
   const formRules = reactive({
     modelCode: [{ required: true, message: '模型编号不能为空', trigger: 'blur' }],
@@ -295,6 +386,10 @@
   })
   const formRef = ref() // 表单 Ref
   const modelparamListMap = ref({})
+  // 调度模型列表
+  const scheduleModelList = ref([])
+  const model = ref()
+  const modelPopover = ref()
 
   const addRow = function (index, rows) {
     let row = JSON.parse(JSON.stringify(rows[index]))
@@ -302,9 +397,9 @@
     this.orderRow(rows)
   }
 
-  const deleteRow = function (index, rows) {
-    if (!rows || rows.length === 1) {
-      message.error('不能全部删除!')
+  const deleteRow = function (index, row, rows) {
+    if (!rows || rows.length === 1 || rows.filter(e => e.modelparamportorder === row.modelparamportorder).length === 1) {
+      message.error('不可删除!')
       return
     }
     rows.splice(index, 1)
@@ -341,6 +436,8 @@
     }
     // 加载参数列表
     modelparamListMap.value = await ScheduleModelApi.getModelParamList()
+    // 加载调度模型列表
+    getScheduleModelList()
   }
   defineExpose({ open }) // 提供 open 方法,用于打开弹窗
 
@@ -385,20 +482,102 @@
       resultStrId: undefined,
       invocation: undefined,
       status: CommonStatusEnum.ENABLE,
-      paramList: [{
-        modelparamportorder: '1',
-        modelparamorder: '1',
-        modelparamtype: '',
-        modelparamid: '',
-        datalength: ''
-      }],
-      settingList: [{
-        key: '',
-        value: '',
-        valuetype: '',
-        name: ''
-      }]
+      paramList: [],
+      settingList: [],
+      modelOut: []
     }
     formRef.value?.resetFields()
   }
+
+  const getScheduleModelList = async () => {
+    let list = await MpkApi.list({pyType: 'schedul'})
+    if (list && list.length > 0) {
+      scheduleModelList.value = list.map(e => {
+        return {
+          label: e.pyChineseName,
+          value: e,
+          children: e.modelMethods.map(m => {
+            return {
+              label: m.methodName,
+              value: m
+            }
+          })
+        }
+      })
+    }
+  }
+
+  // 选择调度模型
+  const changeModel = async () => {
+    // 校验
+    if (model.value && model.value.length > 0) {
+      const modelInfo = model.value[0]
+      const methodInfo = model.value[1]
+      formData.value.modelName = modelInfo.pyChineseName
+      formData.value.className = modelInfo.pkgName + '.impl.' + modelInfo.pyName + 'Impl';
+      formData.value.methodName = methodInfo.methodName
+      formData.value.portLength = methodInfo.dataLength
+      // 参数构造
+      let paramStructure = []
+      for (let i = 0; i < methodInfo.dataLength; i++) {
+        paramStructure.push('[[D')
+      }
+      if (methodInfo.model === 1) {
+        paramStructure.push('java.util.HashMap')
+      }
+      paramStructure.push('java.util.HashMap')
+      formData.value.paramStructure = paramStructure.join(',')
+      formData.value.modelPath = modelInfo.pyModule
+      // 输入参数
+      let paramList = []
+      for (let i = 0; i < methodInfo.dataLength; i++) {
+        paramList.push({
+          modelparamportorder: i+1 + '',
+          modelparamorder: '1',
+          modelparamtype: '',
+          modelparamid: '',
+          datalength: 0
+        })
+      }
+
+      formData.value.paramList = paramList
+      // 设置参数
+      let settingList = []
+      methodInfo.methodSettings.forEach(e => {
+        settingList.push({
+          key: e.settingKey,
+          value: e.value,
+          valuetype: e.valueType,
+          name: e.name
+        })
+      })
+      formData.value.settingList = settingList
+      modelPopover.value.hide()
+    }else {
+      message.error("请先选择模型")
+    }
+  }
+
+  function changeModelparamtype(row) {
+    row.modelparamid = ''
+  }
+  const addRowOut= function () {
+    if(formData.value.modelOut===undefined) {
+      formData.value.modelOut = []
+    }
+    formData.value.modelOut.push({
+      id: generateUUID(),
+      resultKey: undefined,
+      resultType: "double[][]",
+      port: 0,
+      index: 0,
+      isWrite: 1,
+      pointNo:undefined,
+      sort:undefined,
+      disturbancePointNo:undefined,
+    })
+  }
+  const deleteModelOutRow = function (index) {
+    formData.value.modelOut.splice(index, 1)
+  }
 </script>
diff --git a/src/views/model/sche/model/index.vue b/src/views/model/sche/model/index.vue
index 87b3cd1..ae84a63 100644
--- a/src/views/model/sche/model/index.vue
+++ b/src/views/model/sche/model/index.vue
@@ -52,8 +52,12 @@
   <ContentWrap>
     <el-table v-loading="loading" :data="list">
       <el-table-column label="模型编号" align="center" prop="modelCode" min-width="100"/>
-      <el-table-column label="模型名称" align="center" prop="modelName" min-width="100"/>
-      <el-table-column label="模型类型" align="center" prop="modelType" min-width="100"/>
+      <el-table-column label="模型名称" header-align="center" align="left" prop="modelName" min-width="100"/>
+      <el-table-column label="模型类型" align="center" prop="modelType" min-width="100">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.SCHE_MODEL_TYPE" :value="scope.row.modelType" />
+        </template>
+      </el-table-column>
       <el-table-column label="类名" header-align="center" align="left" prop="className" min-width="200"/>
       <el-table-column label="方法名" align="center" prop="methodName" min-width="100"/>
       <el-table-column label="参数数量" align="center" prop="portLength" min-width="100"/>
diff --git a/src/views/model/sche/scheme/ScheduleSchemeForm.vue b/src/views/model/sche/scheme/ScheduleSchemeForm.vue
index f7c0b44..9ee1e84 100644
--- a/src/views/model/sche/scheme/ScheduleSchemeForm.vue
+++ b/src/views/model/sche/scheme/ScheduleSchemeForm.vue
@@ -10,12 +10,12 @@
       <el-row>
         <el-col :span="12">
           <el-form-item label="方案编号" prop="code">
-            <el-input v-model="formData.code" placeholder="请输入方案编号" />
+            <el-input v-model="formData.code" placeholder="请输入方案编号"/>
           </el-form-item>
         </el-col>
         <el-col :span="12">
           <el-form-item label="方案名称" prop="name">
-            <el-input v-model="formData.name" placeholder="请输入方案名称" />
+            <el-input v-model="formData.name" placeholder="请输入方案名称"/>
           </el-form-item>
         </el-col>
       </el-row>
@@ -24,7 +24,7 @@
           <el-form-item label="触发方式" prop="triggerMethod">
             <el-select v-model="formData.triggerMethod" placeholder="请选择">
               <el-option
-                v-for="dict in getIntDictOptions(DICT_TYPE.SCHE_TRIGGER_METHOD)"
+                v-for="dict in getDictOptions(DICT_TYPE.SCHE_TRIGGER_METHOD)"
                 :key="dict.value"
                 :label="dict.label"
                 :value="dict.value"
@@ -34,26 +34,26 @@
         </el-col>
         <el-col :span="12">
           <el-form-item label="触发条件" prop="triggerCondition">
-            <el-input v-model="formData.triggerCondition" placeholder="请输入触发条件" />
+            <el-input v-model="formData.triggerCondition" placeholder="请输入触发条件"/>
           </el-form-item>
         </el-col>
       </el-row>
       <el-row>
         <el-col :span="12">
           <el-form-item label="调整对象" prop="scheduleObj">
-            <el-input v-model="formData.scheduleObj" placeholder="请输入调整对象" />
+            <el-input v-model="formData.scheduleObj" placeholder="请输入调整对象"/>
           </el-form-item>
         </el-col>
         <el-col :span="12">
           <el-form-item label="调整类型" prop="scheduleType">
-            <el-input v-model="formData.scheduleType" placeholder="请输入调整类型" />
+            <el-input v-model="formData.scheduleType" placeholder="请输入调整类型"/>
           </el-form-item>
         </el-col>
       </el-row>
       <el-row>
         <el-col :span="12">
           <el-form-item label="调整策略" prop="scheduleStrategy">
-            <el-input v-model="formData.scheduleStrategy" placeholder="请输入调整策略 " />
+            <el-input v-model="formData.scheduleStrategy" placeholder="请输入调整策略 "/>
           </el-form-item>
         </el-col>
         <el-col :span="12">
@@ -71,9 +71,23 @@
         </el-col>
       </el-row>
       <el-row>
+        <el-col :span="12">
+          <el-form-item label="关联项目" prop="mpkprojectid">
+            <el-select v-model="formData.mpkprojectid" placeholder="请选择">
+              <el-option
+                v-for="item in mpkProjectList"
+                :key="item.id"
+                :label="item.projectName"
+                :value="item.id"/>
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
         <el-col :span="24">
           <el-form-item label="备注" prop="remark">
-            <el-input v-model="formData.remark" placeholder="请输入备注"  type="textarea" maxlength="100"
+            <el-input v-model="formData.remark" placeholder="请输入备注" type="textarea"
+                      maxlength="100"
                       show-word-limit/>
           </el-form-item>
         </el-col>
@@ -86,20 +100,127 @@
   </Dialog>
 </template>
 <script lang="ts" setup>
-  import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
-  import * as ScheduleSchemeApi from '@/api/model/sche/scheme'
-  import { CommonStatusEnum } from '@/utils/constants'
-  import * as ScheduleModelApi from "@/api/model/sche/model";
+import {DICT_TYPE, getDictOptions} from '@/utils/dict'
+import * as ScheduleSchemeApi from '@/api/model/sche/scheme'
+import {CommonStatusEnum} from '@/utils/constants'
+import * as ScheduleModelApi from "@/api/model/sche/model";
+import * as ProjectApi from '@/api/model/mpk/project'
 
-  defineOptions({ name: 'ScheduleSchemeForm' })
+defineOptions({name: 'ScheduleSchemeForm'})
 
-  const { t } = useI18n() // 国际化
-  const message = useMessage() // 消息弹窗
-  const dialogVisible = ref(false) // 弹窗的是否展示
-  const dialogTitle = ref('') // 弹窗的标题
-  const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
-  const formType = ref('') // 表单的类型:create - 新增;update - 修改
-  const formData = ref({
+const {t} = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  code: undefined,
+  name: undefined,
+  triggerMethod: undefined,
+  triggerCondition: undefined,
+  scheduleObj: undefined,
+  scheduleType: undefined,
+  scheduleStrategy: undefined,
+  modelId: undefined,
+  scheduleTime: undefined,
+  remark: undefined,
+  status: 0,
+  mpkprojectid: undefined,
+})
+const formRules = reactive({
+  code: [{required: true, message: '编号不能为空', trigger: 'blur'}],
+  name: [{required: true, message: '名称不能为空', trigger: 'blur'}],
+  triggerMethod: [{required: true, message: '触发方式不能为空', trigger: 'blur'}],
+  triggerCondition: [{required: true, message: '触发条件不能为空', trigger: 'blur'}],
+  modelId: [{required: true, message: '调度模型不能为空', trigger: 'blur'}],
+  triggerCondition: [{required: true, message: '触发条件不能为空', trigger: 'blur'}],
+  mpkprojectid: [{required: true, message: '关联项目不能为空', trigger: 'blur'}],
+})
+const formRef = ref() // 表单 Ref
+const scheduleModelList = ref([] as ScheduleModelApi.ScheduleModelVO[])
+const mpkProjectList = ref([])
+const addRow = function (index, rows) {
+  let row = JSON.parse(JSON.stringify(rows[index]))
+  rows.splice(index, 0, row)
+  this.orderRow(rows)
+}
+
+const deleteRow = function (index, rows) {
+  if (!rows || rows.length === 1) {
+    message.error('不能全部删除!')
+    return
+  }
+  rows.splice(index, 1)
+  this.orderRow(rows)
+}
+
+const orderRow = function (rows) {
+  let modelparamorder = 0
+  let modelparamportorder = 0
+  rows.forEach(function (value) {
+    if (value.modelparamportorder !== modelparamportorder) {
+      modelparamportorder = value.modelparamportorder
+      modelparamorder = 1
+    }
+    value.modelparamorder = modelparamorder
+    modelparamorder++
+  })
+}
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await ScheduleSchemeApi.getScheduleScheme(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+  // 加载调度模型列表
+  scheduleModelList.value = await ScheduleModelApi.getScheduleModelList()
+
+  // 获取mpk项目列表
+  mpkProjectList.value = await ProjectApi.list()
+}
+defineExpose({open}) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as ScheduleSchemeApi.ScheduleSchemeVO
+    if (formType.value === 'create') {
+      await ScheduleSchemeApi.createScheduleScheme(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await ScheduleSchemeApi.updateScheduleScheme(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
     id: undefined,
     code: undefined,
     name: undefined,
@@ -111,105 +232,9 @@
     modelId: undefined,
     scheduleTime: undefined,
     remark: undefined,
-    status: 0
-  })
-  const formRules = reactive({
-    code: [{ required: true, message: '编号不能为空', trigger: 'blur' }],
-    name: [{ required: true, message: '名称不能为空', trigger: 'blur' }]
-  })
-  const formRef = ref() // 表单 Ref
-  const scheduleModelList = ref([] as ScheduleModelApi.ScheduleModelVO[])
-
-  const addRow = function (index, rows) {
-    let row = JSON.parse(JSON.stringify(rows[index]))
-    rows.splice(index, 0, row)
-    this.orderRow(rows)
+    status: CommonStatusEnum.ENABLE,
+    mpkprojectid: undefined
   }
-
-  const deleteRow = function (index, rows) {
-    if (!rows || rows.length === 1) {
-      message.error('不能全部删除!')
-      return
-    }
-    rows.splice(index, 1)
-    this.orderRow(rows)
-  }
-
-  const orderRow = function (rows) {
-    let modelparamorder = 0
-    let modelparamportorder = 0
-    rows.forEach(function (value) {
-      if (value.modelparamportorder !== modelparamportorder) {
-        modelparamportorder = value.modelparamportorder
-        modelparamorder = 1
-      }
-      value.modelparamorder = modelparamorder
-      modelparamorder++
-    })
-  }
-
-  /** 打开弹窗 */
-  const open = async (type: string, id?: number) => {
-    dialogVisible.value = true
-    dialogTitle.value = t('action.' + type)
-    formType.value = type
-    resetForm()
-    // 修改时,设置数据
-    if (id) {
-      formLoading.value = true
-      try {
-        formData.value = await ScheduleSchemeApi.getScheduleScheme(id)
-      } finally {
-        formLoading.value = false
-      }
-    }
-    // 加载调度模型列表
-    scheduleModelList.value = await ScheduleModelApi.getScheduleModelList()
-  }
-  defineExpose({ open }) // 提供 open 方法,用于打开弹窗
-
-  /** 提交表单 */
-  const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
-  const submitForm = async () => {
-    // 校验表单
-    if (!formRef) return
-    const valid = await formRef.value.validate()
-    if (!valid) return
-    // 提交请求
-    formLoading.value = true
-    try {
-      const data = formData.value as unknown as ScheduleSchemeApi.ScheduleSchemeVO
-      if (formType.value === 'create') {
-        await ScheduleSchemeApi.createScheduleScheme(data)
-        message.success(t('common.createSuccess'))
-      } else {
-        await ScheduleSchemeApi.updateScheduleScheme(data)
-        message.success(t('common.updateSuccess'))
-      }
-      dialogVisible.value = false
-      // 发送操作成功的事件
-      emit('success')
-    } finally {
-      formLoading.value = false
-    }
-  }
-
-  /** 重置表单 */
-  const resetForm = () => {
-    formData.value = {
-      id: undefined,
-      code: undefined,
-      name: undefined,
-      triggerMethod: undefined,
-      triggerCondition: undefined,
-      scheduleObj: undefined,
-      scheduleType: undefined,
-      scheduleStrategy: undefined,
-      modelId: undefined,
-      scheduleTime: undefined,
-      remark: undefined,
-      status: CommonStatusEnum.ENABLE
-    }
-    formRef.value?.resetFields()
-  }
+  formRef.value?.resetFields()
+}
 </script>
diff --git a/src/views/model/sche/scheme/index.vue b/src/views/model/sche/scheme/index.vue
index 6212a90..1c94533 100644
--- a/src/views/model/sche/scheme/index.vue
+++ b/src/views/model/sche/scheme/index.vue
@@ -36,6 +36,20 @@
           重置
         </el-button>
         <el-button
+          type="success"
+          plain
+          @click="enable"
+          v-hasPermi="['sche:scheme:update']"
+        >启用
+        </el-button>
+        <el-button
+          type="danger"
+          plain
+          @click="disable"
+          v-hasPermi="['sche:scheme:update']"
+        >禁用
+        </el-button>
+        <el-button
           type="primary"
           plain
           @click="openForm('create')"
@@ -50,9 +64,10 @@
 
   <!-- 列表 -->
   <ContentWrap>
-    <el-table v-loading="loading" :data="list">
+    <el-table v-loading="loading" :data="list" @selection-change="selectionChangeHandle">
+      <el-table-column type="selection" header-align="center" align="center" fixed="left" width="50"/>
       <el-table-column label="方案编号" align="center" prop="code" min-width="100"/>
-      <el-table-column label="方案名称" align="center" prop="name" min-width="100"/>
+      <el-table-column label="方案名称" header-align="center" align="left" prop="name" min-width="100"/>
       <el-table-column label="触发方式" align="center" prop="triggerMethod" min-width="100">
         <template #default="scope">
           <dict-tag :type="DICT_TYPE.SCHE_TRIGGER_METHOD" :value="scope.row.triggerMethod" />
@@ -63,13 +78,19 @@
       <el-table-column label="调整类型" align="center" prop="scheduleType" min-width="100"/>
       <el-table-column label=" 调整策略" align="center" prop="scheduleStrategy" min-width="100"/>
       <el-table-column label="调度时间" align="center" prop="scheduleTime" min-width="160" />
-      <el-table-column label="备注" align="center" prop="remark" min-width="100" />
-      <el-table-column label="状态" align="center" prop="status" min-width="100">
+      <el-table-column label="运行状态" align="center" prop="runStatus">
+        <template #default="scope">
+          <el-tag v-if="scope.row.runStatus + '' === '100'" size="small" type="success">{{scope.row.runStatus}}</el-tag>
+          <el-tag v-else size="small" type="danger">{{scope.row.runStatus}}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="备注" header-align="center" align="left" prop="remark" min-width="160" />
+      <el-table-column label="是否启用" align="center" prop="status" min-width="100">
         <template #default="scope">
           <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
         </template>
       </el-table-column>
-      <el-table-column label="操作" align="center" min-width="110" fixed="right">
+      <el-table-column label="操作" align="center" min-width="100" fixed="right">
         <template #default="scope">
           <el-button
             link
@@ -78,6 +99,14 @@
             v-hasPermi="['sche:scheme:update']"
           >
             编辑
+          </el-button>
+          <el-button
+            link
+            type="primary"
+            @click="openRecordList(scope.row.id)"
+            v-hasPermi="['sche:record:query']"
+          >
+            日志
           </el-button>
           <el-button
             link
@@ -102,13 +131,17 @@
   <!-- 表单弹窗:添加/修改 -->
   <ScheduleSchemeForm ref="formRef" @success="getList" />
 
+  <!-- 表单弹窗:添加/修改 -->
+  <RecordList ref="recordRef" />
 </template>
 <script lang="ts" setup>
   import {DICT_TYPE, getIntDictOptions} from '@/utils/dict'
-  import {dateFormatter} from '@/utils/formatTime'
-  import download from '@/utils/download'
   import * as ScheduleSchemeApi from '@/api/model/sche/scheme'
   import ScheduleSchemeForm from './ScheduleSchemeForm.vue'
+  import RecordList from  './record/index.vue'
+  import * as DaPoint from "@/api/data/da/point";
+  import {reactive} from "vue";
+  import {InfraJobStatusEnum} from "@/utils/constants";
 
   defineOptions({name: 'ScheduleScheme'})
 
@@ -171,6 +204,40 @@
     }
   }
 
+  /** 调用日志查看 */
+  const recordRef = ref()
+  const openRecordList = (id?: string) => {
+    recordRef.value.open(id)
+  }
+
+  let dataListSelections = reactive([])
+  // 多选
+  function selectionChangeHandle (val) {
+    dataListSelections = val
+  }
+  // 启用
+  async function enable() {
+    let ids = dataListSelections.map(item => {
+      return item.id
+    })
+    // 二次确认
+    await message.confirm('是否确认要启用所选调度方案?')
+    await ScheduleSchemeApi.enable(ids)
+    message.success(t('common.enableSuccess'))
+    await getList()
+  }
+  // 禁用
+  async function disable(){
+    let ids = dataListSelections.map(item => {
+      return item.id
+    })
+    // 二次确认
+    await message.confirm('确认要禁用所选调度方案?')
+    await ScheduleSchemeApi.disable(ids)
+    message.success(t('common.disableSuccess'))
+    await getList()
+  }
+
   /** 初始化 **/
   onMounted(async () => {
     await getList()
diff --git a/src/views/model/sche/scheme/record/index.vue b/src/views/model/sche/scheme/record/index.vue
new file mode 100644
index 0000000..4d74cda
--- /dev/null
+++ b/src/views/model/sche/scheme/record/index.vue
@@ -0,0 +1,153 @@
+<template>
+  <el-drawer
+    v-model="drawer"
+    size="50%"
+    title="调度日志"
+    :direction="direction"
+    :before-close="handleClose"
+  >
+    <!-- 搜索 -->
+    <ContentWrap>
+      <el-form
+        class="-mb-15px"
+        :model="queryParams"
+        ref="queryFormRef"
+        :inline="true"
+        label-width="68px"
+      >
+        <el-form-item>
+          <el-date-picker
+            v-model="queryParams.startTime"
+            format="YYYY-MM-DD HH:mm:00"
+            value-format="YYYY-MM-DD HH:mm:00"
+            type="datetime"
+            placeholder="选择日期时间"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-date-picker
+            v-model="queryParams.endTime"
+            format="YYYY-MM-DD HH:mm:00"
+            value-format="YYYY-MM-DD HH:mm:00"
+            type="datetime"
+            placeholder="选择日期时间"
+          />
+        </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>
+    <!-- 列表 -->
+    <ContentWrap>
+      <el-table v-loading="loading" :data="list">
+        <el-table-column
+          prop="scheduleTime"
+          label="调度时间"
+          header-align="center"
+          align="left"
+          min-width="150"
+        />
+        <el-table-column
+          prop="resultCode"
+          label="结果状态"
+          header-align="center"
+          align="center"
+        />
+        <el-table-column
+          prop="resultData"
+          label="结果数据"
+          header-align="center"
+          align="center"
+          min-width="400"
+        />
+      </el-table>
+      <!-- 分页 -->
+      <Pagination
+        :total="total"
+        v-model:page="queryParams.pageNo"
+        v-model:limit="queryParams.pageSize"
+        @pagination="getList"
+      />
+    </ContentWrap>
+  </el-drawer>
+</template>
+<script lang="ts" setup>
+import type { DrawerProps } from 'element-plus'
+import * as ScheduleRecordApi from "@/api/model/sche/record";
+import {reactive, ref} from "vue";
+
+defineOptions({name: 'RecordList'})
+
+const message = useMessage() // 消息弹窗
+const {t} = useI18n() // 国际化
+
+const drawer = ref(false)
+const direction = ref<DrawerProps['direction']>('rtl')
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  schemeId: undefined,
+  startTime: undefined,
+  endTime: undefined,
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const page = await ScheduleRecordApi.getScheduleRecordPage(queryParams)
+    list.value = page.list
+    total.value = page.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 打开弹窗 */
+const open = async (id?: string) => {
+  resetForm()
+  drawer.value = true
+  queryParams.schemeId = id
+  if (id) {
+    getList()
+  }
+}
+defineExpose({open}) // 提供 open 方法,用于打开弹窗
+
+/** 重置表单 */
+const resetForm = () => {
+  queryParams.pageNo = 1
+  queryParams.pageSize = 10
+  queryParams.schemeId = ''
+  queryParams.startTime = undefined
+  queryParams.endTime = undefined
+}
+const handleClose = (done: () => void) => {
+  drawer.value = false
+}
+</script>
diff --git a/src/views/report/drag/index.vue b/src/views/report/drag/index.vue
new file mode 100644
index 0000000..82c1117
--- /dev/null
+++ b/src/views/report/drag/index.vue
@@ -0,0 +1,13 @@
+<template>
+  <ContentWrap>
+    <IFrame :src="src" />
+  </ContentWrap>
+</template>
+<script lang="ts" setup>
+import {getAccessToken, getTenantId} from '@/utils/auth'
+
+defineOptions({ name: 'Drag' })
+
+const BASE_URL = import.meta.env.VITE_BASE_URL
+const src = ref(BASE_URL + '/drag/list?token=' + getAccessToken() + "&tenantId=" + getTenantId())
+</script>
diff --git a/src/views/report/goview/index.vue b/src/views/report/goview/index.vue
index 1bac286..8da8e2f 100644
--- a/src/views/report/goview/index.vue
+++ b/src/views/report/goview/index.vue
@@ -4,7 +4,11 @@
   </ContentWrap>
 </template>
 <script lang="ts" setup>
+import {getAccessToken, getTenantId} from "@/utils/auth";
+
 defineOptions({ name: 'GoView' })
 
-const src = 'http://127.0.0.1:3000'
+const BASE_URL = 'http://172.16.8.100'
+const src = ref(BASE_URL + '/bigscreen/project/items?token=' + getAccessToken() + "&tenantId=" + getTenantId())
+
 </script>
diff --git a/src/views/report/jmreport/index.vue b/src/views/report/jmreport/index.vue
index 0bac351..55338e7 100644
--- a/src/views/report/jmreport/index.vue
+++ b/src/views/report/jmreport/index.vue
@@ -4,10 +4,10 @@
   </ContentWrap>
 </template>
 <script lang="ts" setup>
-import { getAccessToken } from '@/utils/auth'
+import {getAccessToken, getTenantId} from '@/utils/auth'
 
 defineOptions({ name: 'JimuReport' })
 
 const BASE_URL = import.meta.env.VITE_BASE_URL
-const src = ref(BASE_URL + '/jmreport/list?token=' + getAccessToken())
+const src = ref(BASE_URL + '/jmreport/list?token=' + getAccessToken() + "&tenantId=" + getTenantId())
 </script>
diff --git a/src/views/system/app/AppForm.vue b/src/views/system/app/AppForm.vue
index 4dd599f..151620e 100644
--- a/src/views/system/app/AppForm.vue
+++ b/src/views/system/app/AppForm.vue
@@ -51,24 +51,24 @@
             <el-input v-model="formData.appDomain" placeholder="请输入应用域名" />
           </el-form-item>
         </el-col>
-        <el-col :span="12">
-          <el-form-item label="接口域名" prop="apiDomain">
-            <el-input v-model="formData.apiDomain" placeholder="请输入接口域名" />
-          </el-form-item>
-        </el-col>
+<!--        <el-col :span="12">-->
+<!--          <el-form-item label="接口域名" prop="apiDomain">-->
+<!--            <el-input v-model="formData.apiDomain" placeholder="请输入接口域名" />-->
+<!--          </el-form-item>-->
+<!--        </el-col>-->
       </el-row>
-      <el-row>
-        <el-col :span="12">
-          <el-form-item label="应用账号" prop="appKey">
-            <el-input v-model="formData.appKey" placeholder="请输入应用账号" />
-          </el-form-item>
-        </el-col>
-        <el-col :span="12">
-          <el-form-item label="应用密码" prop="appSecret">
-            <el-input v-model="formData.appSecret" placeholder="请输入应用密码" />
-          </el-form-item>
-        </el-col>
-      </el-row>
+<!--      <el-row>-->
+<!--        <el-col :span="12">-->
+<!--          <el-form-item label="应用账号" prop="appKey">-->
+<!--            <el-input v-model="formData.appKey" placeholder="请输入应用账号" />-->
+<!--          </el-form-item>-->
+<!--        </el-col>-->
+<!--        <el-col :span="12">-->
+<!--          <el-form-item label="应用密码" prop="appSecret">-->
+<!--            <el-input v-model="formData.appSecret" placeholder="请输入应用密码" />-->
+<!--          </el-form-item>-->
+<!--        </el-col>-->
+<!--      </el-row>-->
       <el-row>
         <el-col :span="24">
           <el-form-item label="应用图标" prop="icon">
diff --git a/src/views/system/app/index.vue b/src/views/system/app/index.vue
index 8e67647..9b29a84 100644
--- a/src/views/system/app/index.vue
+++ b/src/views/system/app/index.vue
@@ -93,8 +93,8 @@
       <el-table-column label="应用编号" align="center" prop="appCode" />
       <el-table-column label="应用名称" align="center" prop="appName" />
       <el-table-column label="应用域名" align="center" prop="appDomain" />
-      <el-table-column label="接口域名" align="center" prop="apiDomain" />
-      <el-table-column label="应用账号" align="center" prop="appKey" />
+<!--      <el-table-column label="接口域名" align="center" prop="apiDomain" />-->
+<!--      <el-table-column label="应用账号" align="center" prop="appKey" />-->
       <el-table-column label="应用图标" align="center" prop="logo">
         <template #default="scope">
           <img width="40px" height="40px" :src="scope.row.icon" />
diff --git a/src/views/system/appmenu/AppMenuForm.vue b/src/views/system/appmenu/AppMenuForm.vue
index 3a7ad72..f8545cf 100644
--- a/src/views/system/appmenu/AppMenuForm.vue
+++ b/src/views/system/appmenu/AppMenuForm.vue
@@ -115,13 +115,13 @@
 <script lang="ts" setup>
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import * as MenuApi from '@/api/system/menu'
-import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
+import {CACHE_KEY, useCache, useSessionCache} from '@/hooks/web/useCache'
 import { CommonStatusEnum, SystemAppMenuTypeEnum } from '@/utils/constants'
 import { defaultProps, handleTree } from '@/utils/tree'
 
 defineOptions({ name: 'SystemAppMenuForm' })
 
-const { wsCache } = useCache()
+const { wsSessionCache } = useSessionCache()
 const { t } = useI18n() // 国际化
 const message = useMessage() // 消息弹窗
 
@@ -214,7 +214,7 @@
   } finally {
     formLoading.value = false
     // 清空,从而触发刷新
-    wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
+    wsSessionCache.delete(CACHE_KEY.ROLE_ROUTERS)
   }
 }
 
diff --git a/src/views/system/appmenu/index.vue b/src/views/system/appmenu/index.vue
index 66b4f35..ea3ef37 100644
--- a/src/views/system/appmenu/index.vue
+++ b/src/views/system/appmenu/index.vue
@@ -116,11 +116,12 @@
 import { handleTree } from '@/utils/tree'
 import * as MenuApi from '@/api/system/menu'
 import AppMenuForm from './AppMenuForm.vue'
-import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
+import {CACHE_KEY, useCache, useSessionCache} from '@/hooks/web/useCache'
 
 defineOptions({ name: 'SystemAppMenu' })
 
 const { wsCache } = useCache()
+const { wsSessionCache } = useSessionCache()
 const { t } = useI18n() // 国际化
 const message = useMessage() // 消息弹窗
 
@@ -190,7 +191,8 @@
     await message.confirm('即将更新缓存刷新浏览器!', '刷新菜单缓存')
     // 清空,从而触发刷新
     wsCache.delete(CACHE_KEY.USER)
-    wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
+    // wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
+    wsSessionCache.delete(CACHE_KEY.ROLE_ROUTERS)
     // 刷新浏览器
     location.reload()
   } catch {}
diff --git a/src/views/system/area/index.vue b/src/views/system/area/index.vue
index f3289a0..339ecef 100644
--- a/src/views/system/area/index.vue
+++ b/src/views/system/area/index.vue
@@ -1,4 +1,6 @@
 <template>
+  <doc-alert title="地区 & IP" url="https://doc.iocoder.cn/area-and-ip/" />
+
   <!-- 操作栏 -->
   <ContentWrap>
     <el-button type="primary" plain @click="openForm()">
@@ -14,6 +16,7 @@
         <template #default="{ height, width }">
           <!-- Virtualized Table 虚拟化表格:高性能,解决表格在大数据量下的卡顿问题 -->
           <el-table-v2
+            v-loading="loading"
             :columns="columns"
             :data="list"
             :width="width"
@@ -29,7 +32,7 @@
   <AreaForm ref="formRef" />
 </template>
 <script setup lang="tsx">
-import type { Column } from 'element-plus'
+import { Column } from 'element-plus'
 import AreaForm from './AreaForm.vue'
 import * as AreaApi from '@/api/system/area'
 
@@ -38,7 +41,7 @@
 // 表格的 column 字段
 const columns: Column[] = [
   {
-    dataKey: 'id', // 需要渲染当前列的数据字段。例如说:{id:9527, name:'Mike'},则填 id
+    dataKey: 'id', // 需要渲染当前列的数据字段
     title: '编号', // 显示在单元格表头的文本
     width: 400, // 当前列的宽度,必须设置
     fixed: true, // 是否固定列
@@ -50,14 +53,17 @@
     width: 200
   }
 ]
-// 表格的数据
-const list = ref([])
+const loading = ref(true) // 列表的加载中
+const list = ref([]) // 表格的数据
 
-/**
- * 获得数据列表
- */
+/** 获得数据列表 */
 const getList = async () => {
-  list.value = await AreaApi.getAreaTree()
+  loading.value = true
+  try {
+    list.value = await AreaApi.getAreaTree()
+  } finally {
+    loading.value = false
+  }
 }
 
 /** 添加/修改操作 */
diff --git a/src/views/system/loginlog/index.vue b/src/views/system/loginlog/index.vue
index 011992b..4c442b8 100644
--- a/src/views/system/loginlog/index.vue
+++ b/src/views/system/loginlog/index.vue
@@ -45,7 +45,7 @@
           plain
           @click="handleExport"
           :loading="exportLoading"
-          v-hasPermi="['infra:login-log:export']"
+          v-hasPermi="['system:login-log:export']"
         >
           <Icon icon="ep:download" class="mr-5px" /> 导出
         </el-button>
@@ -56,16 +56,16 @@
   <!-- 列表 -->
   <ContentWrap>
     <el-table v-loading="loading" :data="list">
-      <el-table-column label="日志编号" align="center" prop="id" />
-      <el-table-column label="操作类型" align="center" prop="logType">
+      <el-table-column label="日志编号" align="center" prop="id" width="100" />
+      <el-table-column label="操作类型" align="center" prop="logType" width="100">
         <template #default="scope">
           <dict-tag :type="DICT_TYPE.SYSTEM_LOGIN_TYPE" :value="scope.row.logType" />
         </template>
       </el-table-column>
       <el-table-column label="用户名称" align="center" prop="username" width="180" />
       <el-table-column label="登录地址" align="center" prop="userIp" width="180" />
-      <el-table-column label="浏览器" align="center" prop="userAgent" />
-      <el-table-column label="登陆结果" align="center" prop="result">
+      <el-table-column label="浏览器" align="center" prop="userAgent" :show-overflow-tooltip="true"/>
+      <el-table-column label="登录结果" align="center" prop="result" width="100">
         <template #default="scope">
           <dict-tag :type="DICT_TYPE.SYSTEM_LOGIN_RESULT" :value="scope.row.result" />
         </template>
@@ -77,13 +77,13 @@
         width="180"
         :formatter="dateFormatter"
       />
-      <el-table-column label="操作" align="center">
+      <el-table-column label="操作" align="center" width="80">
         <template #default="scope">
           <el-button
             link
             type="primary"
             @click="openDetail(scope.row)"
-            v-hasPermi="['infra:login-log:query']"
+            v-hasPermi="['system:login-log:query']"
           >
             详情
           </el-button>
diff --git a/src/views/system/menu/MenuForm.vue b/src/views/system/menu/MenuForm.vue
index 2b4a90d..a5004d0 100644
--- a/src/views/system/menu/MenuForm.vue
+++ b/src/views/system/menu/MenuForm.vue
@@ -115,13 +115,13 @@
 <script lang="ts" setup>
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import * as MenuApi from '@/api/system/menu'
-import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
+import {CACHE_KEY, useCache, useSessionCache} from '@/hooks/web/useCache'
 import { CommonStatusEnum, SystemMenuTypeEnum } from '@/utils/constants'
 import { defaultProps, handleTree } from '@/utils/tree'
 
 defineOptions({ name: 'SystemMenuForm' })
 
-const { wsCache } = useCache()
+const { wsSessionCache } = useSessionCache()
 const { t } = useI18n() // 国际化
 const message = useMessage() // 消息弹窗
 
@@ -214,7 +214,7 @@
   } finally {
     formLoading.value = false
     // 清空,从而触发刷新
-    wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
+    wsSessionCache.delete(CACHE_KEY.ROLE_ROUTERS)
   }
 }
 
diff --git a/src/views/system/menu/index.vue b/src/views/system/menu/index.vue
index 4f280b5..03d352f 100644
--- a/src/views/system/menu/index.vue
+++ b/src/views/system/menu/index.vue
@@ -50,10 +50,6 @@
           <Icon class="mr-5px" icon="ep:plus" />
           新增
         </el-button>
-        <el-button plain type="danger" @click="toggleExpandAll">
-          <Icon class="mr-5px" icon="ep:sort" />
-          展开/折叠
-        </el-button>
         <el-button plain @click="refreshMenu">
           <Icon class="mr-5px" icon="ep:refresh" />
           刷新菜单缓存
@@ -64,57 +60,22 @@
 
   <!-- 列表 -->
   <ContentWrap>
-    <el-table
-      v-if="refreshTable"
-      v-loading="loading"
-      :data="list"
-      :default-expand-all="isExpandAll"
-      row-key="id"
-    >
-      <el-table-column :show-overflow-tooltip="true" label="菜单名称" prop="name" width="250" />
-      <el-table-column align="center" label="图标" prop="icon" width="100">
-        <template #default="scope">
-          <Icon :icon="scope.row.icon" />
+    <div style="height: 700px">
+      <!-- AutoResizer 自动调节大小 -->
+      <el-auto-resizer>
+        <template #default="{ height, width }">
+          <!-- Virtualized Table 虚拟化表格:高性能,解决表格在大数据量下的卡顿问题 -->
+          <el-table-v2
+            v-loading="loading"
+            :columns="columns"
+            :data="list"
+            :width="width"
+            :height="height"
+            expand-column-key="name"
+          />
         </template>
-      </el-table-column>
-      <el-table-column label="排序" prop="sort" width="60" />
-      <el-table-column :show-overflow-tooltip="true" label="权限标识" prop="permission" />
-      <el-table-column :show-overflow-tooltip="true" label="组件路径" prop="component" />
-      <el-table-column :show-overflow-tooltip="true" label="组件名称" prop="componentName" />
-      <el-table-column label="状态" prop="status" width="80">
-        <template #default="scope">
-          <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
-        </template>
-      </el-table-column>
-      <el-table-column align="center" label="操作">
-        <template #default="scope">
-          <el-button
-            v-hasPermi="['system:menu:update']"
-            link
-            type="primary"
-            @click="openForm('update', scope.row.id)"
-          >
-            修改
-          </el-button>
-          <el-button
-            v-hasPermi="['system:menu:create']"
-            link
-            type="primary"
-            @click="openForm('create', undefined, scope.row.id)"
-          >
-            新增
-          </el-button>
-          <el-button
-            v-hasPermi="['system:menu:delete']"
-            link
-            type="danger"
-            @click="handleDelete(scope.row.id)"
-          >
-            删除
-          </el-button>
-        </template>
-      </el-table-column>
-    </el-table>
+      </el-auto-resizer>
+    </div>
   </ContentWrap>
 
   <!-- 表单弹窗:添加/修改 -->
@@ -124,15 +85,117 @@
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { handleTree } from '@/utils/tree'
 import * as MenuApi from '@/api/system/menu'
+import { MenuVO } from '@/api/system/menu'
 import MenuForm from './MenuForm.vue'
-import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
+import { CACHE_KEY, useCache, useSessionCache } from '@/hooks/web/useCache'
+import { h } from 'vue'
+import { Column, ElButton } from 'element-plus'
+import { Icon } from '@/components/Icon'
+import { hasPermission } from '@/directives/permission/hasPermi'
+import { CommonStatusEnum } from '@/utils/constants'
 
 defineOptions({ name: 'SystemMenu' })
 
 const { wsCache } = useCache()
+const { wsSessionCache } = useSessionCache()
 const { t } = useI18n() // 国际化
 const message = useMessage() // 消息弹窗
 
+// 表格的 column 字段
+const columns: Column[] = [
+  {
+    dataKey: 'name',
+    title: '菜单名称',
+    width: 250
+  },
+  {
+    dataKey: 'icon',
+    title: '图标',
+    width: 150,
+    cellRenderer: ({ rowData }) => {
+      return h(Icon, {
+        icon: rowData.icon
+      })
+    }
+  },
+  {
+    dataKey: 'sort',
+    title: '排序',
+    width: 100
+  },
+  {
+    dataKey: 'permission',
+    title: '权限标识',
+    width: 240
+  },
+  {
+    dataKey: 'component',
+    title: '组件路径',
+    width: 240
+  },
+  {
+    dataKey: 'componentName',
+    title: '组件名称',
+    width: 240
+  },
+  {
+    dataKey: 'status',
+    title: '状态',
+    width: 160,
+    cellRenderer: ({ rowData }) => {
+      return h(ElSwitch, {
+        modelValue: rowData.status,
+        activeValue: CommonStatusEnum.ENABLE,
+        inactiveValue: CommonStatusEnum.DISABLE,
+        loading: menuStatusUpdating.value[rowData.id],
+        disabled: !hasPermission(['system:menu:update']),
+        onChange: (val) => handleStatusChanged(rowData, val as number)
+      })
+    }
+  },
+  {
+    dataKey: 'operation',
+    title: '操作',
+    width: 200,
+    cellRenderer: ({ rowData }) => {
+      return h(
+        'div',
+        [
+          hasPermission(['system:menu:update']) &&
+          h(
+            ElButton,
+            {
+              link: true,
+              type: 'primary',
+              onClick: () => openForm('update', rowData.id)
+            },
+            '修改'
+          ),
+          hasPermission(['system:menu:create']) &&
+          h(
+            ElButton,
+            {
+              link: true,
+              type: 'primary',
+              onClick: () => openForm('create', undefined, rowData.id)
+            },
+            '新增'
+          ),
+          hasPermission(['system:menu:delete']) &&
+          h(
+            ElButton,
+            {
+              link: true,
+              type: 'danger',
+              onClick: () => handleDelete(rowData.id)
+            },
+            '删除'
+          )
+        ].filter(Boolean)
+      )
+    }
+  }
+]
 const loading = ref(true) // 列表的加载中
 const list = ref<any>([]) // 列表的数据
 const queryParams = reactive({
@@ -140,8 +203,6 @@
   status: undefined
 })
 const queryFormRef = ref() // 搜索的表单
-const isExpandAll = ref(false) // 是否展开,默认全部折叠
-const refreshTable = ref(true) // 重新渲染表格状态
 
 /** 查询列表 */
 const getList = async () => {
@@ -171,15 +232,6 @@
   formRef.value.open(type, id, parentId)
 }
 
-/** 展开/折叠操作 */
-const toggleExpandAll = () => {
-  refreshTable.value = false
-  isExpandAll.value = !isExpandAll.value
-  nextTick(() => {
-    refreshTable.value = true
-  })
-}
-
 /** 刷新菜单缓存按钮操作 */
 const refreshMenu = async () => {
   try {
@@ -187,6 +239,8 @@
     // 清空,从而触发刷新
     wsCache.delete(CACHE_KEY.USER)
     wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
+    wsSessionCache.delete(CACHE_KEY.USER)
+    wsSessionCache.delete(CACHE_KEY.ROLE_ROUTERS)
     // 刷新浏览器
     location.reload()
   } catch {}
@@ -205,6 +259,21 @@
   } catch {}
 }
 
+/** 开启/关闭菜单的状态 */
+const menuStatusUpdating = ref({}) // 菜单状态更新中的 menu 映射。key:菜单编号,value:是否更新中
+const handleStatusChanged = async (menu: MenuVO, val: number) => {
+  // 1. 标记 menu.id 更新中
+  menuStatusUpdating.value[menu.id] = true
+  try {
+    // 2. 发起更新状态
+    menu.status = val
+    await MenuApi.updateMenu(menu)
+  } finally {
+    // 3. 标记 menu.id 更新完成
+    menuStatusUpdating.value[menu.id] = false
+  }
+}
+
 /** 初始化 **/
 onMounted(() => {
   getList()
diff --git a/src/views/system/operatelog/index.vue b/src/views/system/operatelog/index.vue
index bd67305..dc19764 100644
--- a/src/views/system/operatelog/index.vue
+++ b/src/views/system/operatelog/index.vue
@@ -79,7 +79,7 @@
           plain
           @click="handleExport"
           :loading="exportLoading"
-          v-hasPermi="['infra:operate-log:export']"
+          v-hasPermi="['system:operate-log:export']"
         >
           <Icon icon="ep:download" class="mr-5px" /> 导出
         </el-button>
@@ -110,7 +110,7 @@
             link
             type="primary"
             @click="openDetail(scope.row)"
-            v-hasPermi="['infra:operate-log:query']"
+            v-hasPermi="['system:operate-log:query']"
           >
             详情
           </el-button>
diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue
index 5a8cdb3..f945726 100644
--- a/src/views/system/role/index.vue
+++ b/src/views/system/role/index.vue
@@ -125,6 +125,16 @@
             菜单权限
           </el-button>
           <el-button
+            v-hasPermi="['system:permission:assign-role-data-scope']"
+            link
+            preIcon="ep:coin"
+            title="数据权限"
+            type="primary"
+            @click="openDataPermissionForm(scope.row)"
+          >
+            数据权限
+          </el-button>
+          <el-button
             v-hasPermi="['system:role:delete']"
             link
             type="danger"
diff --git a/src/views/system/tenant/index.vue b/src/views/system/tenant/index.vue
index be849d7..f81c5d3 100644
--- a/src/views/system/tenant/index.vue
+++ b/src/views/system/tenant/index.vue
@@ -99,7 +99,7 @@
     <el-table v-loading="loading" :data="list">
       <el-table-column label="租户编号" align="center" prop="id" />
       <el-table-column label="租户名" align="center" prop="name" />
-      <el-table-column label="租户套餐" align="center" prop="packageId">
+      <el-table-column label="租户套餐" align="center" prop="packageId" width="180" >
         <template #default="scope">
           <el-tag v-if="scope.row.packageId === 0" type="danger">系统租户</el-tag>
           <template v-else v-for="item in packageList">
diff --git a/src/views/system/tenantPackage/TenantPackageForm.vue b/src/views/system/tenantPackage/TenantPackageForm.vue
index 7492889..476a1df 100644
--- a/src/views/system/tenantPackage/TenantPackageForm.vue
+++ b/src/views/system/tenantPackage/TenantPackageForm.vue
@@ -10,6 +10,12 @@
       <el-form-item label="套餐名" prop="name">
         <el-input v-model="formData.name" placeholder="请输入套餐名" />
       </el-form-item>
+      <el-form-item label="套餐介绍" prop="description">
+        <el-input type="textarea" v-model="formData.description" placeholder="请输入套餐介绍" />
+      </el-form-item>
+      <el-form-item label="套餐图标">
+        <UploadImg v-model="formData.icon" :limit="1" />
+      </el-form-item>
       <el-form-item label="菜单权限">
         <el-card class="cardHeight">
           <template #header>
@@ -39,6 +45,18 @@
             show-checkbox
           />
         </el-card>
+      </el-form-item>
+      <el-form-item label="套餐标签" prop="labels">
+        <el-select
+          v-model="formData.labels"
+          filterable
+          multiple
+          allow-create
+          placeholder="请输入套餐标签"
+          style="width: 500px"
+        >
+          <el-option v-for="label in formData.labels" :key="label" :label="label" :value="label" />
+        </el-select>
       </el-form-item>
       <el-form-item label="状态" prop="status">
         <el-radio-group v-model="formData.status">
@@ -81,6 +99,9 @@
 const formData = ref({
   id: null,
   name: null,
+  icon: undefined,
+  labels: [],
+  description: null,
   remark: null,
   menuIds: [],
   status: CommonStatusEnum.ENABLE
@@ -161,6 +182,9 @@
   formData.value = {
     id: null,
     name: null,
+    icon: undefined,
+    labels: [],
+    description: null,
     remark: null,
     menuIds: [],
     status: CommonStatusEnum.ENABLE
diff --git a/src/views/system/tenantPackage/index.vue b/src/views/system/tenantPackage/index.vue
index fc68a4d..56f8f3d 100644
--- a/src/views/system/tenantPackage/index.vue
+++ b/src/views/system/tenantPackage/index.vue
@@ -27,73 +27,83 @@
           />
         </el-select>
       </el-form-item>
-      <el-form-item label="创建时间" prop="createTime">
-        <el-date-picker
-          v-model="queryParams.createTime"
-          type="daterange"
-          value-format="YYYY-MM-DD HH:mm:ss"
-          start-placeholder="开始日期"
-          end-placeholder="结束日期"
-          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 @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="['system:tenant-package:create']"
         >
-          <Icon icon="ep:plus" class="mr-5px" />
+          <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="id" width="120" />
-      <el-table-column label="套餐名" align="center" prop="name" />
-      <el-table-column label="状态" align="center" prop="status" width="100">
-        <template #default="scope">
-          <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
-        </template>
-      </el-table-column>
-      <el-table-column label="备注" align="center" prop="remark" />
-      <el-table-column
-        label="创建时间"
-        align="center"
-        prop="createTime"
-        width="180"
-        :formatter="dateFormatter"
-      />
-      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
-        <template #default="scope">
-          <el-button
-            link
-            type="primary"
-            @click="openForm('update', scope.row.id)"
-            v-hasPermi="['system:tenant-package:update']"
-          >
-            修改
-          </el-button>
-          <el-button
-            link
-            type="danger"
-            @click="handleDelete(scope.row.id)"
-            v-hasPermi="['system:tenant-package:delete']"
-          >
-            删除
-          </el-button>
-        </template>
-      </el-table-column>
-    </el-table>
+    <el-skeleton :loading="loading">
+      <div class="package-card" v-for="(item, index) in packages" :key="`dynamics-${index}`">
+        <div class="card-content">
+          <img class="card-icon" :src="item.icon"/>
+          <div class="card-middle">
+            <div class="tenant-title">{{ item.name }}</div>
+            <div class="tenant-operation">
+              <el-dropdown @command="(command) => handleCommand(command, item)"
+                           v-hasPermi="[
+                      'system:tenant-package:update',
+                      'system:tenant-package:delete'
+                    ]">
+                <el-button type="primary" link>
+                  <Icon icon="ep:more-filled"/>
+                </el-button>
+                <template #dropdown>
+                  <el-dropdown-menu>
+                    <el-dropdown-item
+                      command="handleUpdate"
+                      v-if="checkPermi(['system:tenant-package:update'])"
+                    >
+                      <Icon icon="ep:edit"/>
+                      修改
+                    </el-dropdown-item>
+                    <el-dropdown-item
+                      command="handleDelete"
+                      v-if="checkPermi(['system:tenant-package:delete'])"
+                    >
+                      <Icon icon="ep:delete"/>
+                      删除
+                    </el-dropdown-item>
+                  </el-dropdown-menu>
+                </template>
+              </el-dropdown>
+            </div>
+          </div>
+          <div class="description">{{ item.description }}</div>
+          <div class="label-areas">
+            <el-tag
+              :disable-transitions="true"
+              :key="i"
+              v-for="(label, i) in item.labels"
+              :index="i"
+              class="label"
+            >
+              {{ label }}
+            </el-tag>
+          </div>
+        </div>
+      </div>
+    </el-skeleton>
     <!-- 分页 -->
     <Pagination
+      class="pagination"
       :total="total"
       v-model:page="queryParams.pageNo"
       v-model:limit="queryParams.pageSize"
@@ -102,22 +112,24 @@
   </ContentWrap>
 
   <!-- 表单弹窗:添加/修改 -->
-  <TenantPackageForm ref="formRef" @success="getList" />
+  <TenantPackageForm ref="formRef" @success="getList"/>
 </template>
 <script lang="ts" setup>
-import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
-import { dateFormatter } from '@/utils/formatTime'
+import {DICT_TYPE, getIntDictOptions} from '@/utils/dict'
 import * as TenantPackageApi from '@/api/system/tenantPackage'
 import TenantPackageForm from './TenantPackageForm.vue'
+import {TenantPackageVO} from "@/api/system/tenantPackage";
+import {checkPermi} from "@/utils/permission";
 
-defineOptions({ name: 'SystemTenantPackage' })
+defineOptions({name: 'SystemTenantPackage'})
 
 const message = useMessage() // 消息弹窗
-const { t } = useI18n() // 国际化
+const {t} = useI18n() // 国际化
 
 const loading = ref(true) // 列表的加载中
 const total = ref(0) // 列表的总页数
-const list = ref([]) // 列表的数据
+const packages = ref([])
+
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
@@ -130,10 +142,9 @@
 
 /** 查询列表 */
 const getList = async () => {
-  loading.value = true
   try {
     const data = await TenantPackageApi.getTenantPackagePage(queryParams)
-    list.value = data.list
+    packages.value = data.list
     total.value = data.total
   } finally {
     loading.value = false
@@ -158,6 +169,20 @@
   formRef.value.open(type, id)
 }
 
+/** 操作分发 */
+const handleCommand = (command: string, row: TenantPackageVO) => {
+  switch (command) {
+    case 'handleUpdate':
+      openForm('update', row.id)
+      break
+    case 'handleDelete':
+      handleDelete(row.id)
+      break
+    default:
+      break
+  }
+}
+
 /** 删除按钮操作 */
 const handleDelete = async (id: number) => {
   try {
@@ -168,11 +193,95 @@
     message.success(t('common.delSuccess'))
     // 刷新列表
     await getList()
-  } catch {}
+  } catch {
+  }
+}
+
+const initData = async () => {
+  await Promise.all([
+    getList()
+  ])
 }
 
 /** 初始化 **/
-onMounted(() => {
-  getList()
-})
+initData()
+
 </script>
+<style lang="scss" scoped>
+.package-card {
+  display: inline-block;
+  width: 396px;
+  height: 379px;
+  background: #FFFFFF;
+  border-radius: 4px 4px 4px 4px;
+  border: 1px solid #EBEDF0;
+  margin: 0px 8px 8px 0;
+}
+
+.card-content {
+  margin-left: 10px;
+}
+
+.card-icon {
+  width: 372px;
+  height: 200px;
+  margin: 10px 0 10px 2px;
+}
+
+.card-middle {
+  width: 396px;
+  height: 25px;
+  margin: 0 12px;
+  display: flex;
+}
+
+.tenant-title {
+  width: 340px;
+  height: 25px;
+  font-family: Microsoft YaHei UI, Microsoft YaHei UI;
+  font-weight: bold;
+  font-size: 20px;
+  color: #282F3D;
+}
+
+.tenant-operation {
+  margin-right: 12px;
+}
+
+.description {
+  margin: 8px 12px;
+  width: 372px;
+  height: 36px;
+  font-family: Microsoft YaHei, Microsoft YaHei;
+  font-weight: 400;
+  font-size: 14px;
+  color: #6B7785;
+  line-height: 18px;
+  word-wrap: break-word;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+}
+
+.label-areas {
+  display: flex;
+  flex-wrap: wrap;
+  height: 54px;
+  width: 396px;
+  margin: 0 8px;
+}
+
+.label {
+  width: 80px;
+  height: 20px;
+  margin: 4px;
+  border-radius: 80px 80px 80px 80px;
+  border: 1px solid #417CFF;
+}
+.pagination {
+  margin-right: 30px;
+  margin-top: 400px;
+}
+</style>
diff --git a/stylelint.config.js b/stylelint.config.js
index 890b45b..b336785 100644
--- a/stylelint.config.js
+++ b/stylelint.config.js
@@ -13,19 +13,19 @@
     'at-rule-no-unknown': [
       true,
       {
-        ignoreAtRules: ['function', 'if', 'each', 'include', 'mixin']
+        ignoreAtRules: ['function', 'if', 'each', 'include', 'mixin', 'extend']
       }
     ],
     'media-query-no-invalid': null,
     'function-no-unknown': null,
     'no-empty-source': null,
     'named-grid-areas-no-invalid': null,
-    'unicode-bom': 'never',
+    // 'unicode-bom': 'never',
     'no-descending-specificity': null,
     'font-family-no-missing-generic-family-keyword': null,
-    'declaration-colon-space-after': 'always-single-line',
-    'declaration-colon-space-before': 'never',
-    'declaration-block-trailing-semicolon': null,
+    // 'declaration-colon-space-after': 'always-single-line',
+    // 'declaration-colon-space-before': 'never',
+    // 'declaration-block-trailing-semicolon': null,
     'rule-empty-line-before': [
       'always',
       {
diff --git a/tsconfig.json b/tsconfig.json
index 182852a..38376ef 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -24,11 +24,11 @@
       "@/*": ["src/*"]
     },
     "types": [
-      "@intlify/unplugin-vue-i18n/types",
-      "vite/client",
-      "element-plus/global",
-      "@types/qrcode",
-      "vite-plugin-svg-icons/client"
+      // "@intlify/unplugin-vue-i18n/types",
+      "vite/client"
+      // "element-plus/global",
+      // "@types/qrcode",
+      // "vite-plugin-svg-icons/client"
     ],
     "outDir": "target", // 请保留这个属性,防止tsconfig.json文件报错
     "typeRoots": ["./node_modules/@types/", "./types"]
diff --git a/types/env.d.ts b/types/env.d.ts
index 25cef34..76f4669 100644
--- a/types/env.d.ts
+++ b/types/env.d.ts
@@ -22,9 +22,9 @@
   readonly VITE_APP_DEFAULT_LOGIN_PASSWORD: string
   readonly VITE_APP_DOCALERT_ENABLE: string
   readonly VITE_BASE_URL: string
-  readonly VITE_UPLOAD_URL: string
   readonly VITE_API_URL: string
   readonly VITE_BASE_PATH: string
+  readonly VITE_UPLOAD_TYPE: string
   readonly VITE_VIDEO_CAMERA_DOMAIN: string
   readonly VITE_DROP_DEBUGGER: string
   readonly VITE_DROP_CONSOLE: string
diff --git a/types/router.d.ts b/types/router.d.ts
index 9b08b80..03e91f1 100644
--- a/types/router.d.ts
+++ b/types/router.d.ts
@@ -15,6 +15,8 @@
 
     title: 'title'            设置该路由在侧边栏和面包屑中展示的名字
 
+    titleSuffix: '2'          当 path 和 title 重复时的后缀或备注
+
     icon: 'svg-name'          设置该路由的图标
 
     noCache: true             如果设置为true,则不会被 <keep-alive> 缓存(默认 false)
@@ -37,6 +39,7 @@
     hidden?: boolean
     alwaysShow?: boolean
     title?: string
+    titleSuffix?: string
     icon?: string
     noCache?: boolean
     breadcrumb?: boolean
diff --git a/uno.config.ts b/uno.config.ts
index d146731..e52457a 100644
--- a/uno.config.ts
+++ b/uno.config.ts
@@ -12,7 +12,7 @@
 ${selector} {
   display: flex;
   height: 100%;
-  padding: 1px 10px 0;
+  padding: 0 10px;
   cursor: pointer;
   align-items: center;
   transition: background var(--transition-time-02);
diff --git a/vite.config.ts b/vite.config.ts
index 8cba915..891e5f9 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,8 +43,9 @@
     css: {
       preprocessorOptions: {
         scss: {
-          additionalData: '@import "./src/styles/variables.scss";',
-          javascriptEnabled: true
+          additionalData: '@use "@/styles/variables.scss" as *;',
+          javascriptEnabled: true,
+          silenceDeprecations: ["legacy-js-api"],
         }
       }
     },
@@ -71,8 +72,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}
   }
 }
diff --git a/web-types.json b/web-types.json
new file mode 100644
index 0000000..602f212
--- /dev/null
+++ b/web-types.json
@@ -0,0 +1,19 @@
+{
+  "$schema": "https://json.schemastore.org/web-types",
+  "framework": "vue",
+  "name": "name written in package.json",
+  "version": "version written in package.json",
+  "contributions": {
+    "html": {
+      "types-syntax": "typescript",
+      "attributes": [
+        {
+          "name": "v-hasPermi"
+        },
+        {
+          "name": "v-hasRole"
+        }
+      ]
+    }
+  }
+}

--
Gitblit v1.9.3