From 9259c2235e31708f954a3578bde3c6a7ab9753e8 Mon Sep 17 00:00:00 2001
From: houzhongjian <houzhongyi@126.com>
Date: 星期一, 30 十二月 2024 15:51:40 +0800
Subject: [PATCH] 1、工作流相关组件更新 2、偶尔出现退出登录时路由报错的bug导致无法回到登录页面 3、全局配置文件修改,移除VITE_UPLOAD_URL配置等

---
 src/directives/permission/hasPermi.ts                                                              |    3 
 src/components/DiyEditor/components/mobile/Carousel/property.vue                                   |   12 
 src/components/bpmnProcessDesigner/package/penal/listeners/UserTaskListeners.vue                   |    5 
 .env.local                                                                                         |    6 
 src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue                    |   44 
 src/components/bpmnProcessDesigner/package/designer/plugins/translate/zh.js                        |    2 
 src/components/DiyEditor/components/mobile/MenuSwiper/property.vue                                 |   14 
 src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue                 |  235 ++
 src/store/modules/permission.ts                                                                    |    1 
 src/components/DiyEditor/components/mobile/ProductList/property.vue                                |    6 
 src/components/DiyEditor/components/mobile/FloatingActionButton/index.vue                          |    2 
 src/components/DiyEditor/components/mobile/NavigationBar/components/CellProperty.vue               |    6 
 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/views/bpm/model/CategoryDraggableModel.vue                                                     |    5 
 src/views/bpm/processInstance/index.vue                                                            |   77 
 src/components/DiyEditor/components/mobile/PromotionPoint/config.ts                                |   96 +
 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     |  157 +
 src/views/system/app/AppForm.vue                                                                   |   34 
 src/components/SimpleProcessDesignerV2/src/nodes-config/UserTaskNodeConfig.vue                     |   10 
 .env.dev                                                                                           |   12 
 src/components/DiyEditor/components/mobile/FloatingActionButton/property.vue                       |    4 
 src/components/DiyEditor/components/mobile/PromotionSeckill/index.vue                              |  244 +-
 src/components/SimpleProcessDesignerV2/src/node.ts                                                 |    8 
 src/components/DiyEditor/components/ComponentContainer.vue                                         |    1 
 src/components/DiyEditor/components/mobile/PromotionSeckill/config.ts                              |   42 
 src/components/AppLinkInput/data.ts                                                                |    8 
 src/views/system/role/index.vue                                                                    |   10 
 src/components/bpmnProcessDesigner/package/penal/custom-config/ElementCustomConfig.vue             |  284 ---
 src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue                               |   27 
 src/views/bpm/form/index.vue                                                                       |    1 
 src/components/DiyEditor/components/mobile/PromotionPoint/property.vue                             |  154 +
 src/components/DiyEditor/components/mobile/Popover/property.vue                                    |    4 
 src/components/DiyEditor/components/mobile/PromotionCombination/config.ts                          |   40 
 vite.config.ts                                                                                     |    3 
 src/utils/formCreate.ts                                                                            |    1 
 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                  |    3 
 src/styles/index.scss                                                                              |    7 
 src/components/Crontab/src/Crontab.vue                                                             |   66 
 src/components/DiyEditor/components/mobile/ProductCard/property.vue                                |   10 
 src/views/Home/Index.vue                                                                           |    3 
 src/main.ts                                                                                        |    4 
 src/components/DiyEditor/components/mobile/PromotionCombination/property.vue                       |   86 
 src/components/bpmnProcessDesigner/package/penal/task/ElementTask.vue                              |   24 
 src/components/DiyEditor/components/mobile/MenuGrid/property.vue                                   |    4 
 src/views/bpm/model/editor/index.vue                                                               |   14 
 src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue                            |  422 +++-
 src/views/bpm/processInstance/create/ProcessDefinitionDetail.vue                                   |   12 
 src/components/bpmnProcessDesigner/package/penal/custom-config/components/BoundaryEventTimer.vue   |  252 +++
 src/styles/var.css                                                                                 |    8 
 src/components/DiyEditor/components/mobile/CouponCard/property.vue                                 |    6 
 src/components/DiyEditor/components/mobile/PromotionSeckill/property.vue                           |   86 
 src/views/bpm/task/done/index.vue                                                                  |  114 +
 src/components/ShortcutDateRangePicker/index.vue                                                   |    6 
 src/components/UploadFile/src/useUpload.ts                                                         |    2 
 src/store/modules/app.ts                                                                           |   10 
 src/components/Icon/src/IconSelect.vue                                                             |   18 
 src/components/Icon/src/Icon.vue                                                                   |    3 
 src/components/DiyEditor/components/mobile/ProductList/index.vue                                   |    3 
 src/components/DiyEditor/components/mobile/NoticeBar/config.ts                                     |    2 
 src/components/bpmnProcessDesigner/package/penal/task/task-components/CallActivity.vue             |  280 +++
 src/components/RouterSearch/index.vue                                                              |    1 
 src/components/DiyEditor/components/mobile/TabBar/property.vue                                     |    9 
 src/components/Draggable/index.vue                                                                 |    4 
 src/utils/permission.ts                                                                            |    6 
 src/components/DiyEditor/components/mobile/ProductCard/index.vue                                   |    7 
 src/directives/index.ts                                                                            |   11 
 src/hooks/web/useMessage.ts                                                                        |   24 
 src/components/UploadFile/src/UploadImgs.vue                                                       |   23 
 src/components/IFrame/src/IFrame.vue                                                               |   33 
 src/components/DiyEditor/components/mobile/TitleBar/property.vue                                   |   10 
 src/layout/components/UserInfo/src/UserInfo.vue                                                    |    4 
 src/components/bpmnProcessDesigner/package/penal/listeners/utilSelf.ts                             |    2 
 src/styles/global.module.scss                                                                      |    2 
 src/views/infra/config/index.vue                                                                   |   14 
 src/components/bpmnProcessDesigner/package/penal/listeners/ElementListeners.vue                    |    2 
 tsconfig.json                                                                                      |   10 
 stylelint.config.js                                                                                |   10 
 web-types.json                                                                                     |   19 
 src/components/UploadFile/src/UploadFile.vue                                                       |   24 
 src/components/bpmnProcessDesigner/package/designer/plugins/palette/CustomPalette.js               |   12 
 src/components/DiyEditor/components/mobile/UserCard/index.vue                                      |    2 
 src/components/DiyEditor/components/ComponentContainerProperty.vue                                 |    4 
 src/components/bpmnProcessDesigner/package/penal/multi-instance/ElementMultiInstance.vue           |  139 +
 src/views/bpm/processInstance/detail/index.vue                                                     |   22 
 src/components/bpmnProcessDesigner/package/penal/task/task-components/ServiceTask.vue              |   91 +
 types/router.d.ts                                                                                  |    3 
 .env.prod                                                                                          |    6 
 src/components/ContentWrap/src/ContentWrap.vue                                                     |    2 
 src/components/DiyEditor/components/mobile/PromotionCombination/index.vue                          |  244 +-
 src/views/bpm/task/todo/index.vue                                                                  |   95 
 src/components/DiyEditor/components/mobile/Divider/property.vue                                    |    6 
 src/views/infra/dataSourceConfig/index.vue                                                         |    8 
 src/views/system/app/index.vue                                                                     |    4 
 /dev/null                                                                                          |   89 -
 src/components/DiyEditor/components/mobile/TabBar/config.ts                                        |   16 
 src/directives/permission/hasRole.ts                                                               |    5 
 package.json                                                                                       |   12 
 src/components/bpmnProcessDesigner/package/designer/plugins/palette/paletteProvider.js             |    6 
 src/components/bpmnProcessDesigner/package/penal/custom-config/data.ts                             |   13 
 107 files changed, 3,800 insertions(+), 1,177 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 567c520..a602754 100644
--- a/.env.prod
+++ b/.env.prod
@@ -4,12 +4,10 @@
 VITE_DEV=false
 
 # 请求路径
-VITE_BASE_URL='http://10.88.4.131'
+VITE_BASE_URL='http://10.50.37.62'
 
 # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
 VITE_UPLOAD_TYPE=server
-# 上传路径
-VITE_UPLOAD_URL='http://10.88.4.131/admin-api/infra/file/upload'
 
 # 接口地址
 VITE_API_URL=/admin-api
@@ -27,7 +25,7 @@
 VITE_BASE_PATH=/plat
 
 # 数据采集服务所在服务器,映射截图图片用
-VITE_VIDEO_CAMERA_DOMAIN='10.88.4.131'
+VITE_VIDEO_CAMERA_DOMAIN='10.50.37.62'
 
 # 输出路径
 VITE_OUT_DIR=dist
diff --git a/package.json b/package.json
index f118cca..a7c562a 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,6 @@
   "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",
@@ -76,8 +75,8 @@
     "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",
@@ -98,8 +97,8 @@
     "@vitejs/plugin-vue": "^5.0.4",
     "@vitejs/plugin-vue-jsx": "^3.1.0",
     "autoprefixer": "^10.4.17",
-    "bpmn-js": "8.10.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",
@@ -122,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",
@@ -146,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/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/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/Icon.vue b/src/components/Icon/src/Icon.vue
index 4a59f68..a90bb37 100644
--- a/src/components/Icon/src/Icon.vue
+++ b/src/components/Icon/src/Icon.vue
@@ -1,7 +1,6 @@
 <script lang="ts" setup>
 import { propTypes } from '@/utils/propTypes'
-// import Iconify from '@purge-icons/generated'
-
+import Iconify from '@purge-icons/generated'
 import { useDesign } from '@/hooks/web/useDesign'
 
 defineOptions({ name: 'Icon' })
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..3fa35f6 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
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/SimpleProcessDesignerV2/src/node.ts b/src/components/SimpleProcessDesignerV2/src/node.ts
index 0810c1f..4cbac6e 100644
--- a/src/components/SimpleProcessDesignerV2/src/node.ts
+++ b/src/components/SimpleProcessDesignerV2/src/node.ts
@@ -15,7 +15,6 @@
   AssignStartUserHandlerType,
   AssignEmptyHandlerType,
   FieldPermissionType,
-  ProcessVariableEnum
 } from './consts'
 import { parseFormFields } from '@/components/FormCreate/src/utils/index'
 export function useWatchNode(props: { flowNode: SimpleFlowNode }): Ref<SimpleFlowNode> {
@@ -37,13 +36,6 @@
       parseFormFields(JSON.parse(fieldStr), result)
     })
   }
-  // 固定添加发起人 ID 字段
-  result.unshift({
-    field: ProcessVariableEnum.START_USER_ID,
-    title: '发起人',
-    type: 'UserSelect',
-    required: true
-  })
   return result
 }
 
diff --git a/src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue b/src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue
index ffbf187..49e5d9f 100644
--- a/src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue
+++ b/src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue
@@ -26,19 +26,13 @@
       </div>
     </template>
     <div>
-      <div class="mb-3 font-size-16px" v-if="currentNode.defaultFlow">未满足其它条件时,将进入此分支(该分支不可编辑和删除)</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 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-group v-model="currentNode.conditionType" @change="changeConditionType">
               <el-radio
                 v-for="(dict, index) in conditionConfigTypes"
                 :key="index"
@@ -108,10 +102,11 @@
                   <div class="mr-2">
                     <el-select style="width: 160px" v-model="rule.leftSide">
                       <el-option
-                        v-for="(item, index) in fieldsInfo"
+                        v-for="(item, index) in fieldOptions"
                         :key="index"
                         :label="item.title"
                         :value="item.field"
+                        :disabled="!item.required"
                       />
                     </el-select>
                   </div>
@@ -165,10 +160,12 @@
   COMPARISON_OPERATORS,
   ConditionGroup,
   Condition,
-  ConditionRule
+  ConditionRule,
+  ProcessVariableEnum
 } from '../consts'
 import { getDefaultConditionNodeName } from '../utils'
 import { useFormFields } from '../node'
+import { BpmModelFormType } from '@/utils/constants'
 const message = useMessage() // 消息弹窗
 defineOptions({
   name: 'ConditionNodeConfig'
@@ -177,8 +174,8 @@
 const conditionConfigTypes = computed(() => {
   return CONDITION_CONFIG_TYPES.filter((item) => {
     // 业务表单暂时去掉条件规则选项
-    if (formType?.value !== 10) {
-      return item.value === ConditionType.RULE
+    if (formType?.value === BpmModelFormType.CUSTOM && item.value === ConditionType.RULE) {
+      return false
     } else {
       return true
     }
@@ -368,16 +365,29 @@
 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 = fieldsInfo.find((item) => item.field === field)
   return item?.title
 }
 
+/** 获取操作符名称 */
 const getOpName = (opCode: string): string => {
-  const opName = COMPARISON_OPERATORS.find((item) => item.value === opCode)
+  const opName = COMPARISON_OPERATORS.find((item: any) => item.value === opCode)
   return opName?.label
 }
 </script>
diff --git a/src/components/SimpleProcessDesignerV2/src/nodes-config/UserTaskNodeConfig.vue b/src/components/SimpleProcessDesignerV2/src/nodes-config/UserTaskNodeConfig.vue
index a088b6d..fb5e780 100644
--- a/src/components/SimpleProcessDesignerV2/src/nodes-config/UserTaskNodeConfig.vue
+++ b/src/components/SimpleProcessDesignerV2/src/nodes-config/UserTaskNodeConfig.vue
@@ -469,7 +469,8 @@
   TimeoutHandlerType,
   ASSIGN_EMPTY_HANDLER_TYPES,
   AssignEmptyHandlerType,
-  FieldPermissionType
+  FieldPermissionType,
+  ProcessVariableEnum
 } from '../consts'
 
 import {
@@ -519,6 +520,13 @@
   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')
 })
 // 表单内部门字段选项, 必须是必填和部门选择器
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 2981e12..c846acb 100644
--- a/src/components/UploadFile/src/useUpload.ts
+++ b/src/components/UploadFile/src/useUpload.ts
@@ -101,6 +101,4 @@
 enum UPLOAD_TYPE {
   // 客户端直接上传(只支持S3服务)
   CLIENT = 'client',
-  // 客户端发送到后端上传
-  SERVER = 'server'
 }
diff --git a/src/components/bpmnProcessDesigner/package/designer/plugins/descriptor/flowableDescriptor.json b/src/components/bpmnProcessDesigner/package/designer/plugins/descriptor/flowableDescriptor.json
index d1ca4a4..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"
         }
       ]
     },
@@ -1281,6 +1306,138 @@
           "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 96e8b08..d2409ee 100644
--- a/src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue
+++ b/src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue
@@ -1,5 +1,5 @@
 <template>
-  <div class="process-panel__container" :style="{ width: `${width}px`,maxHeight: '700px' }">
+  <div class="process-panel__container" :style="{ width: `${width}px`, maxHeight: '700px' }">
     <el-collapse v-model="activeTab">
       <el-collapse-item name="base">
         <!-- class="panel-tab__title" -->
@@ -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,9 +60,13 @@
         <template #title><Icon icon="ep:promotion" />其他</template>
         <element-other-config :id="elementId" />
       </el-collapse-item>
-      <el-collapse-item name="customConfig" v-if="elementType.indexOf('Task') !== -1" key="customConfig">
-        <template #title><Icon icon="ep:circle-plus-filled" />自定义配置</template>
-        <element-custom-config :id="elementId" :type="elementType" />
+      <el-collapse-item 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>
@@ -72,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' })
 
diff --git a/src/components/bpmnProcessDesigner/package/penal/custom-config/ElementCustomConfig.vue b/src/components/bpmnProcessDesigner/package/penal/custom-config/ElementCustomConfig.vue
index e5497b0..f9cb9ac 100644
--- a/src/components/bpmnProcessDesigner/package/penal/custom-config/ElementCustomConfig.vue
+++ b/src/components/bpmnProcessDesigner/package/penal/custom-config/ElementCustomConfig.vue
@@ -1,283 +1,39 @@
-<!-- UserTask 自定义配置:
-     1. 审批人与提交人为同一人时
-     2. 审批人拒绝时
-     3. 审批人为空时
--->
 <template>
   <div class="panel-tab__content">
-    <el-divider content-position="left">审批人拒绝时</el-divider>
-    <el-form-item prop="rejectHandlerType">
-      <el-radio-group
-        v-model="rejectHandlerType"
-        :disabled="returnTaskList.length === 0"
-        @change="updateRejectHandlerType"
-      >
-        <div class="flex-col">
-          <div v-for="(item, index) in REJECT_HANDLER_TYPES" :key="index">
-            <el-radio :key="item.value" :value="item.value" :label="item.label" />
-          </div>
-        </div>
-      </el-radio-group>
-    </el-form-item>
-    <el-form-item
-      v-if="rejectHandlerType == RejectHandlerType.RETURN_USER_TASK"
-      label="驳回节点"
-      prop="returnNodeId"
-    >
-      <el-select v-model="returnNodeId" clearable style="width: 100%" @change="updateReturnNodeId">
-        <el-option
-          v-for="item in returnTaskList"
-          :key="item.id"
-          :label="item.name"
-          :value="item.id"
-        />
-      </el-select>
-    </el-form-item>
-
-    <el-divider content-position="left">审批人为空时</el-divider>
-    <el-form-item prop="assignEmptyHandlerType">
-      <el-radio-group v-model="assignEmptyHandlerType" @change="updateAssignEmptyHandlerType">
-        <div class="flex-col">
-          <div v-for="(item, index) in ASSIGN_EMPTY_HANDLER_TYPES" :key="index">
-            <el-radio :key="item.value" :value="item.value" :label="item.label" />
-          </div>
-        </div>
-      </el-radio-group>
-    </el-form-item>
-    <el-form-item
-      v-if="assignEmptyHandlerType == AssignEmptyHandlerType.ASSIGN_USER"
-      label="指定用户"
-      prop="assignEmptyHandlerUserIds"
-      span="24"
-    >
-      <el-select
-        v-model="assignEmptyUserIds"
-        clearable
-        multiple
-        style="width: 100%"
-        @change="updateAssignEmptyUserIds"
-      >
-        <el-option
-          v-for="item in userOptions"
-          :key="item.id"
-          :label="item.nickname"
-          :value="item.id"
-        />
-      </el-select>
-    </el-form-item>
-
-    <el-divider content-position="left">审批人与提交人为同一人时</el-divider>
-    <el-radio-group v-model="assignStartUserHandlerType" @change="updateAssignStartUserHandlerType">
-      <div class="flex-col">
-        <div v-for="(item, index) in ASSIGN_START_USER_HANDLER_TYPES" :key="index">
-          <el-radio :key="item.value" :value="item.value" :label="item.label" />
-        </div>
-      </div>
-    </el-radio-group>
+    <component :is="customConfigComponent" v-bind="$props" />
   </div>
 </template>
 
 <script lang="ts" setup>
-import {
-  ASSIGN_START_USER_HANDLER_TYPES,
-  RejectHandlerType,
-  REJECT_HANDLER_TYPES,
-  ASSIGN_EMPTY_HANDLER_TYPES,
-  AssignEmptyHandlerType
-} from '@/components/SimpleProcessDesignerV2/src/consts'
-import * as UserApi from '@/api/system/user'
+import { CustomConfigMap } from './data'
 
 defineOptions({ name: 'ElementCustomConfig' })
+
 const props = defineProps({
   id: String,
-  type: String
+  type: String,
+  businessObject: {
+    type: Object,
+    default: () => {}
+  }
 })
-const prefix = inject('prefix')
 
-// 审批人与提交人为同一人时
-const assignStartUserHandlerTypeEl = ref()
-const assignStartUserHandlerType = ref()
-
-// 审批人拒绝时
-const rejectHandlerTypeEl = ref()
-const rejectHandlerType = ref()
-const returnNodeIdEl = ref()
-const returnNodeId = ref()
-const returnTaskList = ref([])
-
-// 审批人为空时
-const assignEmptyHandlerTypeEl = ref()
-const assignEmptyHandlerType = ref()
-const assignEmptyUserIdsEl = ref()
-const assignEmptyUserIds = ref()
-
-const elExtensionElements = ref()
-const otherExtensions = ref()
-const bpmnElement = ref()
 const bpmnInstances = () => (window as any)?.bpmnInstances
-
-const resetCustomConfigList = () => {
-  bpmnElement.value = bpmnInstances().bpmnElement
-
-  // 获取可回退的列表
-  returnTaskList.value = findAllPredecessorsExcludingStart(
-    bpmnElement.value.id,
-    bpmnInstances().modeler
-  )
-
-  // 获取元素扩展属性 或者 创建扩展属性
-  elExtensionElements.value =
-    bpmnElement.value.businessObject?.extensionElements ??
-    bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] })
-
-  // 审批人与提交人为同一人时
-  assignStartUserHandlerTypeEl.value =
-    elExtensionElements.value.values?.filter(
-      (ex) => ex.$type === `${prefix}:AssignStartUserHandlerType`
-    )?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignStartUserHandlerType`, { value: 1 })
-  assignStartUserHandlerType.value = assignStartUserHandlerTypeEl.value.value
-
-  // 审批人拒绝时
-  rejectHandlerTypeEl.value =
-    elExtensionElements.value.values?.filter(
-      (ex) => ex.$type === `${prefix}:RejectHandlerType`
-    )?.[0] || bpmnInstances().moddle.create(`${prefix}:RejectHandlerType`, { value: 1 })
-  rejectHandlerType.value = rejectHandlerTypeEl.value.value
-  returnNodeIdEl.value =
-    elExtensionElements.value.values?.filter(
-      (ex) => ex.$type === `${prefix}:RejectReturnTaskId`
-    )?.[0] || bpmnInstances().moddle.create(`${prefix}:RejectReturnTaskId`, { value: '' })
-  returnNodeId.value = returnNodeIdEl.value.value
-
-  // 审批人为空时
-  assignEmptyHandlerTypeEl.value =
-    elExtensionElements.value.values?.filter(
-      (ex) => ex.$type === `${prefix}:AssignEmptyHandlerType`
-    )?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignEmptyHandlerType`, { value: 1 })
-  assignEmptyHandlerType.value = assignEmptyHandlerTypeEl.value.value
-  assignEmptyUserIdsEl.value =
-    elExtensionElements.value.values?.filter(
-      (ex) => ex.$type === `${prefix}:AssignEmptyUserIds`
-    )?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignEmptyUserIds`, { value: '' })
-  assignEmptyUserIds.value = assignEmptyUserIdsEl.value.value.split(',').map((item) => {
-    // 如果数字超出了最大安全整数范围,则将其作为字符串处理
-    let num = Number(item)
-    return num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER ? item : num
-  })
-
-  // 保留剩余扩展元素,便于后面更新该元素对应属性
-  otherExtensions.value =
-    elExtensionElements.value.values?.filter(
-      (ex) =>
-        ex.$type !== `${prefix}:AssignStartUserHandlerType` &&
-        ex.$type !== `${prefix}:RejectHandlerType` &&
-        ex.$type !== `${prefix}:RejectReturnTaskId` &&
-        ex.$type !== `${prefix}:AssignEmptyHandlerType` &&
-        ex.$type !== `${prefix}:AssignEmptyUserIds`
-    ) ?? []
-
-  // 更新元素扩展属性,避免后续报错
-  updateElementExtensions()
-}
-
-const updateAssignStartUserHandlerType = () => {
-  assignStartUserHandlerTypeEl.value.value = assignStartUserHandlerType.value
-
-  updateElementExtensions()
-}
-
-const updateRejectHandlerType = () => {
-  rejectHandlerTypeEl.value.value = rejectHandlerType.value
-
-  returnNodeId.value = returnTaskList.value[0].id
-  returnNodeIdEl.value.value = returnNodeId.value
-
-  updateElementExtensions()
-}
-
-const updateReturnNodeId = () => {
-  returnNodeIdEl.value.value = returnNodeId.value
-
-  updateElementExtensions()
-}
-
-const updateAssignEmptyHandlerType = () => {
-  assignEmptyHandlerTypeEl.value.value = assignEmptyHandlerType.value
-
-  updateElementExtensions()
-}
-
-const updateAssignEmptyUserIds = () => {
-  assignEmptyUserIdsEl.value.value = assignEmptyUserIds.value.toString()
-
-  updateElementExtensions()
-}
-
-const updateElementExtensions = () => {
-  const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
-    values: [
-      ...otherExtensions.value,
-      assignStartUserHandlerTypeEl.value,
-      rejectHandlerTypeEl.value,
-      returnNodeIdEl.value,
-      assignEmptyHandlerTypeEl.value,
-      assignEmptyUserIdsEl.value
-    ]
-  })
-  bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
-    extensionElements: extensions
-  })
-}
+const customConfigComponent = ref<any>(null)
 
 watch(
-  () => props.id,
-  (val) => {
-    val &&
-      val.length &&
-      nextTick(() => {
-        resetCustomConfigList()
-      })
+  () => 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 }
 )
-
-function findAllPredecessorsExcludingStart(elementId, modeler) {
-  const elementRegistry = modeler.get('elementRegistry')
-  const allConnections = elementRegistry.filter((element) => element.type === 'bpmn:SequenceFlow')
-  const predecessors = new Set() // 使用 Set 来避免重复节点
-
-  // 检查是否是开始事件节点
-  function isStartEvent(element) {
-    return element.type === 'bpmn:StartEvent'
-  }
-
-  function findPredecessorsRecursively(element) {
-    // 获取与当前节点相连的所有连接
-    const incomingConnections = allConnections.filter((connection) => connection.target === element)
-
-    incomingConnections.forEach((connection) => {
-      const source = connection.source // 获取前置节点
-
-      // 只添加不是开始事件的前置节点
-      if (!isStartEvent(source)) {
-        predecessors.add(source.businessObject)
-        // 递归查找前置节点
-        findPredecessorsRecursively(source)
-      }
-    })
-  }
-
-  const targetElement = elementRegistry.get(elementId)
-  if (targetElement) {
-    findPredecessorsRecursively(targetElement)
-  }
-
-  return Array.from(predecessors) // 返回前置节点数组
-}
-
-const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
-onMounted(async () => {
-  // 获得用户列表
-  userOptions.value = await UserApi.getSimpleUserList()
-})
 </script>
+
+<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/listeners/ElementListeners.vue b/src/components/bpmnProcessDesigner/package/penal/listeners/ElementListeners.vue
index de5445c..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
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 7cd16f7..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>
@@ -76,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('')
@@ -267,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 016cdf6..7bf4f0e 100644
--- a/src/components/bpmnProcessDesigner/package/penal/properties/ElementProperties.vue
+++ b/src/components/bpmnProcessDesigner/package/penal/properties/ElementProperties.vue
@@ -75,7 +75,6 @@
 const bpmnInstances = () => (window as any)?.bpmnInstances
 
 const resetAttributesList = () => {
-  console.log(window, 'windowwindowwindowwindowwindowwindowwindow')
   bpmnElement.value = bpmnInstances().bpmnElement
   otherExtensionList.value = [] // 其他扩展配置
   bpmnElementProperties.value =
@@ -85,7 +84,7 @@
         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 bbeeb4c..3a71b4c 100644
--- a/src/components/bpmnProcessDesigner/package/penal/task/ElementTask.vue
+++ b/src/components/bpmnProcessDesigner/package/penal/task/ElementTask.vue
@@ -29,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' })
 
@@ -45,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
@@ -78,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/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..931f44b 100644
--- a/src/directives/permission/hasPermi.ts
+++ b/src/directives/permission/hasPermi.ts
@@ -8,7 +8,8 @@
     const { wsCache } = useCache()
     const { value } = binding
     const all_permission = '*:*:*'
-    const permissions = wsCache.get(CACHE_KEY.USER).permissions
+    const userInfo = wsCache.get(CACHE_KEY.USER)
+    const permissions = userInfo?.permissions || []
 
     if (value && value instanceof Array && value.length > 0) {
       const permissionFlag = value
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/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/UserInfo/src/UserInfo.vue b/src/layout/components/UserInfo/src/UserInfo.vue
index 355aabc..714a088 100644
--- a/src/layout/components/UserInfo/src/UserInfo.vue
+++ b/src/layout/components/UserInfo/src/UserInfo.vue
@@ -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://doc.iocoder.cn/')
+  window.open('https://doc.iailab.cn/')
 }
 </script>
 
diff --git a/src/main.ts b/src/main.ts
index 4694019..ed098e4 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -42,7 +42,7 @@
 import router, { setupRouter } from '@/router'
 
 // 权限
-import { setupAuth } from '@/directives'
+import { setupAuth, setupMountedFocus } from '@/directives'
 
 import { createApp } from 'vue'
 
@@ -87,6 +87,8 @@
 
   setupAuth(app)
 
+  setupMountedFocus(app)
+
   await router.isReady()
 
   app.use(VueDOMPurifyHTML)
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/permission.ts b/src/store/modules/permission.ts
index 2000b33..f32facc 100644
--- a/src/store/modules/permission.ts
+++ b/src/store/modules/permission.ts
@@ -45,7 +45,6 @@
         this.addRouters = routerMap.concat([
           {
             path: '/:path(.*)*',
-            // redirect: '/404',
             component: () => import('@/views/Error/404.vue'),
             name: '404Page',
             meta: {
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/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/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 e931277..1558788 100644
--- a/src/views/Home/Index.vue
+++ b/src/views/Home/Index.vue
@@ -48,7 +48,7 @@
   // userInfo.menus = data
   wsCache.set(CACHE_KEY.USER, userInfo)
   wsSessionCache.set(CACHE_KEY.ROLE_ROUTERS, data)
-  window.location.href = '/plat/index'
+  window.location.href = import.meta.env.VITE_BASE_PATH + 'index'
 }
 
 const getAllApi = async () => {
@@ -64,7 +64,6 @@
 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
diff --git a/src/views/bpm/form/index.vue b/src/views/bpm/form/index.vue
index fd55242..65699c4 100644
--- a/src/views/bpm/form/index.vue
+++ b/src/views/bpm/form/index.vue
@@ -1,5 +1,4 @@
 <template>
-
   <ContentWrap>
     <!-- 搜索工作栏 -->
     <el-form
diff --git a/src/views/bpm/model/CategoryDraggableModel.vue b/src/views/bpm/model/CategoryDraggableModel.vue
index 3546593..7bd58d7 100644
--- a/src/views/bpm/model/CategoryDraggableModel.vue
+++ b/src/views/bpm/model/CategoryDraggableModel.vue
@@ -236,6 +236,11 @@
     </template>
   </Dialog>
 
+  <!-- 弹窗:表单详情 -->
+  <Dialog title="表单详情" v-model="formDetailVisible" width="800">
+    <form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" />
+  </Dialog>
+
   <!-- 表单弹窗:添加流程模型 -->
   <ModelForm :categoryId="categoryInfo.code" ref="modelFormRef" @success="emit('success')" />
 </template>
diff --git a/src/views/bpm/model/editor/index.vue b/src/views/bpm/model/editor/index.vue
index 3e77369..1a41a50 100644
--- a/src/views/bpm/model/editor/index.vue
+++ b/src/views/bpm/model/editor/index.vue
@@ -31,12 +31,19 @@
 // 自定义左侧菜单(修改 默认任务 为 用户任务)
 import CustomPaletteProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/palette'
 import * as ModelApi from '@/api/bpm/model'
+import { getForm, FormVO } from '@/api/bpm/form'
 
 defineOptions({ name: 'BpmModelEditor' })
 
 const router = useRouter() // 路由
 const { query } = useRoute() // 路由的查询
 const message = useMessage() // 国际化
+
+// 表单信息
+const formFields = ref<string[]>([])
+const formType = ref(20)
+provide('formFields', formFields)
+provide('formType', formType)
 
 const xmlString = ref(undefined) // BPMN XML
 const modeler = ref(null) // BPMN Modeler
@@ -99,6 +106,13 @@
   </bpmndi:BPMNDiagram>
 </definitions>`
   }
+
+  formType.value = data.formType
+  if (data.formType === 10) {
+    const bpmnForm = (await getForm(data.formId)) as unknown as FormVO
+    formFields.value = bpmnForm?.fields
+  }
+
   model.value = {
     ...data,
     bpmnXml: undefined // 清空 bpmnXml 属性
diff --git a/src/views/bpm/processInstance/create/ProcessDefinitionDetail.vue b/src/views/bpm/processInstance/create/ProcessDefinitionDetail.vue
index 610963e..3800f19 100644
--- a/src/views/bpm/processInstance/create/ProcessDefinitionDetail.vue
+++ b/src/views/bpm/processInstance/create/ProcessDefinitionDetail.vue
@@ -8,8 +8,8 @@
         <!-- 中间主要内容 tab 栏 -->
         <el-tabs v-model="activeTab">
           <!-- 表单信息 -->
-          <el-tab-pane label="表单填写" name="form">
-            <div class="form-scroll-area">
+          <el-tab-pane label="表单填写" name="form" >
+            <div class="form-scroll-area" v-loading="processInstanceStartLoading">
               <el-scrollbar>
                 <el-row>
                   <el-col :span="17">
@@ -90,7 +90,7 @@
   selectProcessDefinition: any
 }>()
 const emit = defineEmits(['cancel'])
-
+const processInstanceStartLoading = ref(false) // 流程实例发起中
 const { push, currentRoute } = useRouter() // 路由
 const message = useMessage() // 消息弹窗
 const { delView } = useTagsViewStore() // 视图操作
@@ -179,6 +179,8 @@
   if (!fApi.value || !props.selectProcessDefinition) {
     return
   }
+  // 流程表单校验
+  await fApi.value.validate()
   // 如果有指定审批人,需要校验
   if (startUserSelectTasks.value?.length > 0) {
     for (const userTask of startUserSelectTasks.value) {
@@ -191,7 +193,7 @@
   }
 
   // 提交请求
-  fApi.value.btn.loading(true)
+  processInstanceStartLoading.value = true
   try {
     await ProcessInstanceApi.createProcessInstance({
       processDefinitionId: props.selectProcessDefinition.id,
@@ -206,7 +208,7 @@
       name: 'BpmProcessInstanceMy'
     })
   } finally {
-    fApi.value.btn.loading(false)
+    processInstanceStartLoading.value = false
   }
 }
 
diff --git a/src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue b/src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue
index b92be7e..894a5d4 100644
--- a/src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue
+++ b/src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue
@@ -20,9 +20,9 @@
         <el-form
           label-position="top"
           class="mb-auto"
-          ref="formRef"
-          :model="genericForm"
-          :rules="genericRule"
+          ref="approveFormRef"
+          :model="approveReasonForm"
+          :rules="approveReasonRule"
           label-width="100px"
         >
           <el-card v-if="runningTask?.formId > 0" class="mb-15px !-mt-10px">
@@ -38,17 +38,17 @@
           </el-card>
           <el-form-item label="审批意见" prop="reason">
             <el-input
-              v-model="genericForm.reason"
+              v-model="approveReasonForm.reason"
               placeholder="请输入审批意见"
               type="textarea"
               :rows="4"
             />
           </el-form-item>
           <el-form-item>
-            <el-button :disabled="formLoading" type="success" @click="handleAudit(true)">
+            <el-button :disabled="formLoading" type="success" @click="handleAudit(true, approveFormRef)">
               {{ getButtonDisplayName(OperationButtonType.APPROVE) }}
             </el-button>
-            <el-button @click="popOverVisible.approve = false"> 取消 </el-button>
+            <el-button @click="closePropover('approve', approveFormRef)"> 取消 </el-button>
           </el-form-item>
         </el-form>
       </div>
@@ -72,35 +72,24 @@
         <el-form
           label-position="top"
           class="mb-auto"
-          ref="formRef"
-          :model="genericForm"
-          :rules="genericRule"
+          ref="rejectFormRef"
+          :model="rejectReasonForm"
+          :rules="rejectReasonRule"
           label-width="100px"
         >
-          <el-card v-if="runningTask?.formId > 0" class="mb-15px !-mt-10px">
-            <template #header>
-              <span class="el-icon-picture-outline"> 填写表单【{{ runningTask?.formName }}】 </span>
-            </template>
-            <form-create
-              v-model="approveForm.value"
-              v-model:api="approveFormFApi"
-              :option="approveForm.option"
-              :rule="approveForm.rule"
-            />
-          </el-card>
           <el-form-item label="审批意见" prop="reason">
             <el-input
-              v-model="genericForm.reason"
+              v-model="rejectReasonForm.reason"
               placeholder="请输入审批意见"
               type="textarea"
               :rows="4"
             />
           </el-form-item>
           <el-form-item>
-            <el-button :disabled="formLoading" type="danger" @click="handleAudit(false)">
+            <el-button :disabled="formLoading" type="danger" @click="handleAudit(false,rejectFormRef)">
               {{ getButtonDisplayName(OperationButtonType.REJECT) }}
             </el-button>
-            <el-button @click="popOverVisible.reject = false"> 取消 </el-button>
+            <el-button @click="closePropover('reject', rejectFormRef)"> 取消 </el-button>
           </el-form-item>
         </el-form>
       </div>
@@ -124,14 +113,14 @@
         <el-form
           label-position="top"
           class="mb-auto"
-          ref="formRef"
-          :model="genericForm"
-          :rules="genericRule"
+          ref="copyFormRef"
+          :model="copyForm"
+          :rules="copyFormRule"
           label-width="100px"
         >
           <el-form-item label="抄送人" prop="copyUserIds">
             <el-select
-              v-model="genericForm.copyUserIds"
+              v-model="copyForm.copyUserIds"
               clearable
               style="width: 100%"
               multiple
@@ -147,7 +136,7 @@
           </el-form-item>
           <el-form-item label="抄送意见" prop="copyReason">
             <el-input
-              v-model="genericForm.copyReason"
+              v-model="copyForm.copyReason"
               clearable
               placeholder="请输入抄送意见"
               type="textarea"
@@ -158,13 +147,13 @@
             <el-button :disabled="formLoading" type="primary" @click="handleCopy">
               {{ getButtonDisplayName(OperationButtonType.COPY) }}
             </el-button>
-            <el-button @click="popOverVisible.copy = false"> 取消 </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"
@@ -182,13 +171,13 @@
         <el-form
           label-position="top"
           class="mb-auto"
-          ref="formRef"
-          :model="genericForm"
-          :rules="genericRule"
+          ref="transferFormRef"
+          :model="transferForm"
+          :rules="transferFormRule"
           label-width="100px"
         >
           <el-form-item label="新审批人" prop="assigneeUserId">
-            <el-select v-model="genericForm.assigneeUserId" clearable style="width: 100%">
+            <el-select v-model="transferForm.assigneeUserId" clearable style="width: 100%">
               <el-option
                 v-for="item in userOptions"
                 :key="item.id"
@@ -199,7 +188,7 @@
           </el-form-item>
           <el-form-item label="审批意见" prop="reason">
             <el-input
-              v-model="genericForm.reason"
+              v-model="transferForm.reason"
               clearable
               placeholder="请输入审批意见"
               type="textarea"
@@ -210,7 +199,7 @@
             <el-button :disabled="formLoading" type="primary" @click="handleTransfer()">
               {{ getButtonDisplayName(OperationButtonType.TRANSFER) }}
             </el-button>
-            <el-button @click="popOverVisible.transfer = false"> 取消 </el-button>
+            <el-button @click="closePropover('transfer', transferFormRef)"> 取消 </el-button>
           </el-form-item>
         </el-form>
       </div>
@@ -234,13 +223,13 @@
         <el-form
           label-position="top"
           class="mb-auto"
-          ref="formRef"
-          :model="genericForm"
-          :rules="genericRule"
+          ref="delegateFormRef"
+          :model="delegateForm"
+          :rules="delegateFormRule"
           label-width="100px"
         >
           <el-form-item label="接收人" prop="delegateUserId">
-            <el-select v-model="genericForm.delegateUserId" clearable style="width: 100%">
+            <el-select v-model="delegateForm.delegateUserId" clearable style="width: 100%">
               <el-option
                 v-for="item in userOptions"
                 :key="item.id"
@@ -251,7 +240,7 @@
           </el-form-item>
           <el-form-item label="审批意见" prop="reason">
             <el-input
-              v-model="genericForm.reason"
+              v-model="delegateForm.reason"
               clearable
               placeholder="请输入审批意见"
               type="textarea"
@@ -262,7 +251,7 @@
             <el-button :disabled="formLoading" type="primary" @click="handleDelegate()">
               {{ getButtonDisplayName(OperationButtonType.DELEGATE) }}
             </el-button>
-            <el-button @click="popOverVisible.delegate = false"> 取消 </el-button>
+            <el-button @click="closePropover('delegate', delegateFormRef)"> 取消 </el-button>
           </el-form-item>
         </el-form>
       </div>
@@ -286,13 +275,13 @@
         <el-form
           label-position="top"
           class="mb-auto"
-          ref="formRef"
-          :model="genericForm"
-          :rules="genericRule"
+          ref="addSignFormRef"
+          :model="addSignForm"
+          :rules="addSignFormRule"
           label-width="100px"
         >
           <el-form-item label="加签处理人" prop="addSignUserIds">
-            <el-select v-model="genericForm.addSignUserIds" multiple clearable style="width: 100%">
+            <el-select v-model="addSignForm.addSignUserIds" multiple clearable style="width: 100%">
               <el-option
                 v-for="item in userOptions"
                 :key="item.id"
@@ -303,7 +292,7 @@
           </el-form-item>
           <el-form-item label="审批意见" prop="reason">
             <el-input
-              v-model="genericForm.reason"
+              v-model="addSignForm.reason"
               clearable
               placeholder="请输入审批意见"
               type="textarea"
@@ -317,7 +306,7 @@
             <el-button :disabled="formLoading" type="primary" @click="handlerAddSign('after')">
               向后{{ getButtonDisplayName(OperationButtonType.ADD_SIGN) }}
             </el-button>
-            <el-button @click="popOverVisible.addSign = false"> 取消 </el-button>
+            <el-button @click="closePropover('addSign', addSignFormRef)"> 取消 </el-button>
           </el-form-item>
         </el-form>
       </div>
@@ -340,13 +329,13 @@
         <el-form
           label-position="top"
           class="mb-auto"
-          ref="formRef"
-          :model="genericForm"
-          :rules="genericRule"
+          ref="deleteSignFormRef"
+          :model="deleteSignForm"
+          :rules="deleteSignFormRule"
           label-width="100px"
         >
           <el-form-item label="减签人员" prop="deleteSignTaskId">
-            <el-select v-model="genericForm.deleteSignTaskId" clearable style="width: 100%">
+            <el-select v-model="deleteSignForm.deleteSignTaskId" clearable style="width: 100%">
               <el-option
                 v-for="item in runningTask.children"
                 :key="item.id"
@@ -357,7 +346,7 @@
           </el-form-item>
           <el-form-item label="审批意见" prop="reason">
             <el-input
-              v-model="genericForm.reason"
+              v-model="deleteSignForm.reason"
               clearable
               placeholder="请输入审批意见"
               type="textarea"
@@ -368,7 +357,7 @@
             <el-button :disabled="formLoading" type="primary" @click="handlerDeleteSign()">
               减签
             </el-button>
-            <el-button @click="popOverVisible.deleteSign = false"> 取消 </el-button>
+            <el-button @click="closePropover('deleteSign', deleteSignFormRef)"> 取消 </el-button>
           </el-form-item>
         </el-form>
       </div>
@@ -383,7 +372,7 @@
       v-if="runningTask && isHandleTaskStatus() && isShowButton(OperationButtonType.RETURN)"
     >
       <template #reference>
-        <div @click="openReturnPopover" class="hover-bg-gray-100 rounded-xl p-6px">
+        <div @click="openPopover('return')" class="hover-bg-gray-100 rounded-xl p-6px">
           <Icon :size="14" icon="ep:back" />&nbsp;
           {{ getButtonDisplayName(OperationButtonType.RETURN) }}
         </div>
@@ -392,13 +381,13 @@
         <el-form
           label-position="top"
           class="mb-auto"
-          ref="formRef"
-          :model="genericForm"
-          :rules="genericRule"
+          ref="returnFormRef"
+          :model="returnForm"
+          :rules="returnFormRule"
           label-width="100px"
         >
           <el-form-item label="退回节点" prop="targetTaskDefinitionKey">
-            <el-select v-model="genericForm.targetTaskDefinitionKey" clearable style="width: 100%">
+            <el-select v-model="returnForm.targetTaskDefinitionKey" clearable style="width: 100%">
               <el-option
                 v-for="item in returnList"
                 :key="item.taskDefinitionKey"
@@ -409,7 +398,7 @@
           </el-form-item>
           <el-form-item label="退回理由" prop="returnReason">
             <el-input
-              v-model="genericForm.returnReason"
+              v-model="returnForm.returnReason"
               clearable
               placeholder="请输入退回理由"
               type="textarea"
@@ -420,7 +409,7 @@
             <el-button :disabled="formLoading" type="primary" @click="handleReturn()">
               {{ getButtonDisplayName(OperationButtonType.RETURN) }}
             </el-button>
-            <el-button @click="popOverVisible.return = false"> 取消 </el-button>
+            <el-button @click="closePropover('return', returnFormRef)"> 取消 </el-button>
           </el-form-item>
         </el-form>
       </div>
@@ -445,15 +434,15 @@
         <el-form
           label-position="top"
           class="mb-auto"
-          ref="formRef"
-          :model="genericForm"
-          :rules="genericRule"
+          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="genericForm.cancelReason"
+              v-model="cancelForm.cancelReason"
               clearable
               placeholder="请输入取消理由"
               type="textarea"
@@ -462,9 +451,9 @@
           </el-form-item>
           <el-form-item>
             <el-button :disabled="formLoading" type="primary" @click="handleCancel()">
-              取消
+              确认
             </el-button>
-            <el-button @click="popOverVisible.cancel = false"> 取消 </el-button>
+            <el-button @click="closePropover('cancel', cancelFormRef)"> 取消 </el-button>
           </el-form-item>
         </el-form>
       </div>
@@ -488,26 +477,29 @@
 import { setConfAndFields2 } from '@/utils/formCreate'
 import * as TaskApi from '@/api/bpm/task'
 import * as ProcessInstanceApi from '@/api/bpm/processInstance'
-import { propTypes } from '@/utils/propTypes'
+import * as UserApi from '@/api/system/user'
 import {
   OperationButtonType,
   OPERATION_BUTTON_NAME
 } from '@/components/SimpleProcessDesignerV2/src/consts'
-import { BpmProcessInstanceStatus } from '@/utils/constants'
-
+import { BpmProcessInstanceStatus, BpmModelFormType } from '@/utils/constants'
+import type { FormInstance, FormRules } from 'element-plus'
 defineOptions({ name: 'ProcessInstanceBtnContainer' })
 
 const router = useRouter() // 路由
 const message = useMessage() // 消息弹窗
-const { proxy } = getCurrentInstance() as any
 
 const userId = useUserStoreWithOut().getUser.id // 当前登录的编号
 const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
-const props = defineProps({
-  processInstance: propTypes.object, // 流程实例信息
-  processDefinition: propTypes.object, // 流程定义信息
-  userOptions: propTypes.any
-})
+
+const 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({
@@ -525,21 +517,99 @@
 
 // ========== 审批信息 ==========
 const runningTask = ref<any>() // 运行中的任务
-const genericForm = ref<any>({}) // 通用表单
 const approveForm = ref<any>({}) // 审批通过时,额外的补充信息
 const approveFormFApi = ref<any>({}) // approveForms 的 fAPi
-const formRef = ref()
-const genericRule = reactive({
+
+// 审批通过意见表单
+const approveFormRef = ref<FormInstance>()
+const approveReasonForm = reactive({
+  reason: ''
+})
+const approveReasonRule = reactive<FormRules<typeof approveReasonForm>>({
   reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
-  returnReason: [{ required: true, message: '退回理由不能为空', trigger: 'blur' }],
-  cancelReason: [{ required: true, message: '取消理由不能为空', trigger: 'blur' }],
-  copyUserIds: [{ required: true, message: '抄送人不能为空', trigger: 'change' }],
+})
+// 拒绝表单
+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' }],
-  targetTaskDefinitionKey: [{ 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(
@@ -553,43 +623,57 @@
   }
 )
 
-/** 弹出退回气泡卡 */
-const openReturnPopover = async () => {
-  returnList.value = await TaskApi.getTaskListByReturn(runningTask.value.id)
-  if (returnList.value.length === 0) {
-    message.warning('当前没有可退回的节点')
-    return
-  }
-  await openPopover('return')
-}
-
 /** 弹出气泡卡 */
 const openPopover = async (type: string) => {
+  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()
+  // 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) => {
+const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) => {
   formLoading.value = true
   try {
-    const genericFormRef = proxy.$refs['formRef']
-    // 1.2 校验表单
-    const elForm = unref(genericFormRef)
-    if (!elForm) return
-    const valid = await elForm.validate()
-    if (!valid) return
-
-    // 2.1 提交审批
-    const data = {
-      id: runningTask.value.id,
-      reason: genericForm.value.reason
-    }
+    // 校验表单
+    if (!formRef) return
+    await formRef.validate()
     if (pass) {
-      // 审批通过,并且有额外的 approveForm 表单,需要校验 + 拼接到 data 表单里提交
+       // 获取修改的流程变量, 暂时只支持流程表单
+       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()
@@ -600,11 +684,18 @@
       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('审批不通过成功')
     }
-    // 2.2 加载最新数据
+    // 重置表单
+    formRef.resetFields()
+    // 加载最新数据
     reload()
   } finally {
     formLoading.value = false
@@ -615,19 +706,17 @@
 const handleCopy = async () => {
   formLoading.value = true
   try {
-    const copyFormRef = proxy.$refs['formRef']
     // 1. 校验表单
-    const elForm = unref(copyFormRef)
-    if (!elForm) return
-    const valid = await elForm.validate()
-    if (!valid) return
+    if (!copyFormRef.value) return
+    await copyFormRef.value.validate()
     // 2. 提交抄送
     const data = {
       id: runningTask.value.id,
-      reason: genericForm.value.copyReason,
-      copyUserIds: genericForm.value.copyUserIds
+      reason: copyForm.copyReason,
+      copyUserIds:copyForm.copyUserIds
     }
     await TaskApi.copyTask(data)
+    copyFormRef.value.resetFields()
     popOverVisible.value.copy = false
     message.success('操作成功')
   } finally {
@@ -639,20 +728,17 @@
 const handleTransfer = async () => {
   formLoading.value = true
   try {
-    const transferFormRef = proxy.$refs['formRef']
     // 1.1 校验表单
-    const elForm = unref(transferFormRef)
-    if (!elForm) return
-    const valid = await elForm.validate()
-    if (!valid) return
+    if (!transferFormRef.value) return
+    await transferFormRef.value.validate()
     // 1.2 提交转交
     const data = {
       id: runningTask.value.id,
-      reason: genericForm.value.reason,
-      assigneeUserId: genericForm.value.assigneeUserId
+      reason: transferForm.reason,
+      assigneeUserId: transferForm.assigneeUserId
     }
-
     await TaskApi.transferTask(data)
+    transferFormRef.value.resetFields()
     popOverVisible.value.transfer = false
     message.success('操作成功')
     // 2. 加载最新数据
@@ -666,21 +752,20 @@
 const handleDelegate = async () => {
   formLoading.value = true
   try {
-    const deletegateFormRef = proxy.$refs['formRef']
+ 
     // 1.1 校验表单
-    const elForm = unref(deletegateFormRef)
-    if (!elForm) return
-    const valid = await elForm.validate()
-    if (!valid) return
+    if (!delegateFormRef.value) return
+    await delegateFormRef.value.validate()
     // 1.2 处理委派
     const data = {
       id: runningTask.value.id,
-      reason: genericForm.value.reason,
-      delegateUserId: genericForm.value.delegateUserId
+      reason: delegateForm.reason,
+      delegateUserId: delegateForm.delegateUserId
     }
 
     await TaskApi.delegateTask(data)
     popOverVisible.value.delegate = false
+    delegateFormRef.value.resetFields()
     message.success('操作成功')
     // 2. 加载最新数据
     reload()
@@ -693,21 +778,19 @@
 const handlerAddSign = async (type: string) => {
   formLoading.value = true
   try {
-    const transferFormRef = proxy.$refs['formRef']
     // 1.1 校验表单
-    const elForm = unref(transferFormRef)
-    if (!elForm) return
-    const valid = await elForm.validate()
-    if (!valid) return
+    if (!addSignFormRef.value) return
+    await addSignFormRef.value.validate()
     // 1.2 提交加签
     const data = {
       id: runningTask.value.id,
       type,
-      reason: genericForm.value.reason,
-      userIds: genericForm.value.addSignUserIds
+      reason: addSignForm.reason,
+      userIds: addSignForm.addSignUserIds
     }
     await TaskApi.signCreateTask(data)
     message.success('操作成功')
+    addSignFormRef.value.resetFields()
     popOverVisible.value.addSign = false
     // 2 加载最新数据
     reload()
@@ -720,21 +803,19 @@
 const handleReturn = async () => {
   formLoading.value = true
   try {
-    const returnFormRef = proxy.$refs['formRef']
     // 1.1 校验表单
-    const elForm = unref(returnFormRef)
-    if (!elForm) return
-    const valid = await elForm.validate()
-    if (!valid) return
+    if (!returnFormRef.value) return
+    await returnFormRef.value.validate()
     // 1.2 提交退回
     const data = {
       id: runningTask.value.id,
-      reason: genericForm.value.returnReason,
-      targetTaskDefinitionKey: genericForm.value.targetTaskDefinitionKey
+      reason: returnForm.returnReason,
+      targetTaskDefinitionKey: returnForm.targetTaskDefinitionKey
     }
 
     await TaskApi.returnTask(data)
     popOverVisible.value.return = false
+    returnFormRef.value.resetFields()
     message.success('操作成功')
     // 2 重新加载数据
     reload()
@@ -747,19 +828,17 @@
 const handleCancel = async () => {
   formLoading.value = true
   try {
-    const cancelFormRef = proxy.$refs['formRef']
     // 1.1 校验表单
-    const elForm = unref(cancelFormRef)
-    if (!elForm) return
-    const valid = await elForm.validate()
-    if (!valid) return
+    if (!cancelFormRef.value) return
+    await cancelFormRef.value.validate()
     // 1.2 提交取消
     await ProcessInstanceApi.cancelProcessInstanceByStartUser(
       props.processInstance.id,
-      genericForm.value.cancelReason
+      cancelForm.cancelReason
     )
     popOverVisible.value.return = false
     message.success('操作成功')
+    cancelFormRef.value.resetFields()
     // 2 重新加载数据
     reload()
   } finally {
@@ -786,19 +865,17 @@
 const handlerDeleteSign = async () => {
   formLoading.value = true
   try {
-    const deleteFormRef = proxy.$refs['formRef']
     // 1.1 校验表单
-    const elForm = unref(deleteFormRef)
-    if (!elForm) return
-    const valid = await elForm.validate()
-    if (!valid) return
+    if (!deleteSignFormRef.value) return
+    await deleteSignFormRef.value.validate()
     // 1.2 提交减签
     const data = {
-      id: genericForm.value.deleteSignTaskId,
-      reason: genericForm.value.reason
+      id: deleteSignForm.deleteSignTaskId,
+      reason: deleteSignForm.reason
     }
     await TaskApi.signDeleteTask(data)
     message.success('减签成功')
+    deleteSignFormRef.value.resetFields()
     popOverVisible.value.deleteSign = false
     // 2 加载最新数据
     reload()
@@ -852,7 +929,6 @@
 }
 
 const loadTodoTask = (task: any) => {
-  genericForm.value = {}
   approveForm.value = {}
   approveFormFApi.value = {}
   runningTask.value = task
@@ -866,6 +942,30 @@
   }
 }
 
+/** 校验流程表单 */
+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>
 
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 fc6d6b2..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" :value="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 0461a20..9809f7a 100644
--- a/src/views/bpm/processInstance/detail/index.vue
+++ b/src/views/bpm/processInstance/detail/index.vue
@@ -49,7 +49,7 @@
                       class="form-box flex flex-col mb-30px flex-1"
                     >
                       <!-- 情况一:流程表单 -->
-                      <el-col v-if="processDefinition?.formType === 10">
+                      <el-col v-if="processDefinition?.formType === BpmModelFormType.NORMAL">
                         <form-create
                           v-model="detailForm.value"
                           v-model:api="fApi"
@@ -58,7 +58,7 @@
                         />
                       </el-col>
                       <!-- 情况二:业务表单 -->
-                      <div v-if="processDefinition?.formType === 20">
+                      <div v-if="processDefinition?.formType === BpmModelFormType.CUSTOM">
                         <BusinessFormComponent :id="processInstance.businessKey" />
                       </div>
                     </div>
@@ -116,6 +116,9 @@
             :process-instance="processInstance"
             :process-definition="processDefinition"
             :userOptions="userOptions"
+            :normal-form="detailForm"
+            :normal-form-api="fApi"
+            :writable-fields="writableFields"
             @success="refresh"
           />
         </div>
@@ -126,7 +129,7 @@
 <script lang="ts" setup>
 import { formatDate } from '@/utils/formatTime'
 import { DICT_TYPE } from '@/utils/dict'
-import { BpmModelType } from '@/utils/constants'
+import { BpmModelType, BpmModelFormType } from '@/utils/constants'
 import { setConfAndFields2 } from '@/utils/formCreate'
 import { registerComponent } from '@/utils/routerHelper'
 import type { ApiAttrs } from '@form-create/element-ui/types/config'
@@ -171,6 +174,8 @@
   value: {}
 }) // 流程实例的表单详情
 
+const writableFields: Array<string> = [] // 表单可以编辑的字段
+
 /** 获得详情 */
 const getDetail = () => {
   getApprovalDetail()
@@ -202,11 +207,12 @@
     processDefinition.value = data.processDefinition
 
     // 设置表单信息
-    if (processDefinition.value.formType === 10) {
+    if (processDefinition.value.formType === BpmModelFormType.NORMAL) {
       // 获取表单字段权限
       const formFieldsPermission = data.formFieldsPermission
-
-      if (detailForm.value.rule.length > 0) {
+      // 清空可编辑字段为空
+      writableFields.splice(0)
+      if (detailForm.value.rule?.length > 0) {
         // 避免刷新 form-create 显示不了
         detailForm.value.value = processInstance.value.formVariables
       } else {
@@ -271,6 +277,8 @@
   if (permission === FieldPermissionType.WRITE) {
     //@ts-ignore
     fApi.value?.disabled(false, field)
+    // 加入可以编辑的字段
+    writableFields.push(field)
   }
   if (permission === FieldPermissionType.NONE) {
     //@ts-ignore
@@ -314,6 +322,7 @@
   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
@@ -323,7 +332,6 @@
         $process-header-height - 40px
     );
     overflow: auto;
-    display: flex;
     flex-direction: column;
 
     :deep(.box-card) {
diff --git a/src/views/bpm/processInstance/index.vue b/src/views/bpm/processInstance/index.vue
index f2bb29d..2ffb162 100644
--- a/src/views/bpm/processInstance/index.vue
+++ b/src/views/bpm/processInstance/index.vue
@@ -1,5 +1,4 @@
 <template>
-
   <ContentWrap>
     <!-- 搜索工作栏 -->
     <el-form
@@ -24,13 +23,14 @@
       </el-form-item>
 
       <!-- TODO @ tuituji:style 可以使用 unocss -->
-      <el-form-item label="" prop="category" :style="{ position: 'absolute', right: '130px' }">
-        <!-- TODO @tuituji:应该选择好分类,就触发搜索啦。 -->
+      <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-155px"
+          @change="handleQuery"
         >
           <el-option
             v-for="category in categoryList"
@@ -41,21 +41,38 @@
         </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>
+
       <!-- 高级筛选 -->
       <!-- TODO @ tuituji:style 可以使用 unocss -->
       <el-form-item :style="{ position: 'absolute', right: '0px' }">
-        <el-button v-popover="popoverRef" v-click-outside="onClickOutside" :icon="List">
-          高级筛选
-        </el-button>
         <el-popover
-          ref="popoverRef"
-          trigger="click"
-          virtual-triggering
+          :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"
@@ -85,21 +102,6 @@
               class="!w-390px"
             />
           </el-form-item>
-          <el-form-item label="流程状态" class="bold-label" label-position="top" prop="status">
-            <el-select
-              v-model="queryParams.status"
-              placeholder="请选择流程状态"
-              clearable
-              class="!w-390px"
-            >
-              <el-option
-                v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
-                :key="dict.value"
-                :label="dict.label"
-                :value="dict.value"
-              />
-            </el-select>
-          </el-form-item>
           <el-form-item label="发起时间" class="bold-label" label-position="top" prop="createTime">
             <el-date-picker
               v-model="queryParams.createTime"
@@ -111,8 +113,13 @@
               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>
-        <!-- TODO @tuituji:这里应该有确认,和取消、清空搜索条件,三个按钮。 -->
       </el-form-item>
     </el-form>
   </ContentWrap>
@@ -129,7 +136,7 @@
         fixed="left"
       />
       <!-- TODO @芋艿:摘要 -->
-      <!-- TODO @tuituji:流程状态。可见需求文档里  -->
+      <!-- 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" />
@@ -197,8 +204,7 @@
   </ContentWrap>
 </template>
 <script lang="ts" setup>
-// TODO @tuituji:List 改成 <Icon icon="ep:plus" class="mr-5px" /> 类似这种组件哈。
-import { List } from '@element-plus/icons-vue'
+// TODO @tuituji:List 改成 <Icon icon="ep:plus" class="mr-5px" /> 类似这种组件哈。 RE:done & to check
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import { ElMessageBox } from 'element-plus'
@@ -240,6 +246,8 @@
   }
 }
 
+const showPopover = ref(false)
+
 /** 搜索按钮操作 */
 const handleQuery = () => {
   queryParams.pageNo = 1
@@ -272,7 +280,7 @@
 }
 
 /** 查看详情 */
-const handleDetail = (row) => {
+const handleDetail = (row: ProcessInstanceVO) => {
   router.push({
     name: 'BpmProcessInstanceDetail',
     query: {
@@ -282,7 +290,7 @@
 }
 
 /** 取消按钮操作 */
-const handleCancel = async (row) => {
+const handleCancel = async (row: ProcessInstanceVO) => {
   // 二次确认
   const { value } = await ElMessageBox.prompt('请输入取消原因', '取消流程', {
     confirmButtonText: t('common.ok'),
@@ -295,15 +303,6 @@
   message.success('取消成功')
   // 刷新列表
   await getList()
-}
-
-// TODO @tuituji:这个 import 是不是没用哈?
-import { ClickOutside as vClickOutside } from 'element-plus'
-
-// TODO @tuituji:onClickAdvancedSearch。方法名叫这个,会更好一些哇?打开高级搜索。
-const popoverRef = ref()
-const onClickOutside = () => {
-  unref(popoverRef).popperRef?.delayHide?.()
 }
 
 /** 激活时 **/
diff --git a/src/views/bpm/task/done/index.vue b/src/views/bpm/task/done/index.vue
index fdcef66..1365104 100644
--- a/src/views/bpm/task/done/index.vue
+++ b/src/views/bpm/task/done/index.vue
@@ -1,5 +1,4 @@
 <template>
-
   <ContentWrap>
     <!-- 搜索工作栏 -->
     <el-form
@@ -9,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"
@@ -18,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>
 
@@ -103,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' })
 
@@ -118,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 () => {
@@ -158,7 +231,8 @@
 }
 
 /** 初始化 **/
-onMounted(() => {
-  getList()
+onMounted(async () => {
+  await getList()
+  categoryList.value = await CategoryApi.getCategorySimpleList()
 })
 </script>
diff --git a/src/views/bpm/task/todo/index.vue b/src/views/bpm/task/todo/index.vue
index 60681fc..e1449b1 100644
--- a/src/views/bpm/task/todo/index.vue
+++ b/src/views/bpm/task/todo/index.vue
@@ -1,5 +1,4 @@
 <template>
-
   <ContentWrap>
     <!-- 搜索工作栏 -->
     <el-form
@@ -9,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"
@@ -18,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>
 
@@ -88,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' })
 
@@ -100,9 +152,11 @@
   pageNo: 1,
   pageSize: 10,
   name: '',
+  category: undefined,
   createTime: []
 })
 const queryFormRef = ref() // 搜索的表单
+const categoryList = ref<CategoryVO[]>([]) // 流程分类列表
 
 /** 查询任务列表 */
 const getList = async () => {
@@ -115,6 +169,8 @@
     loading.value = false
   }
 }
+
+const showPopover = ref(false)
 
 /** 搜索按钮操作 */
 const handleQuery = () => {
@@ -140,7 +196,8 @@
 }
 
 /** 初始化 **/
-onMounted(() => {
-  getList()
+onMounted(async () => {
+  await getList()
+  categoryList.value = await CategoryApi.getCategorySimpleList()
 })
 </script>
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/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/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/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/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 640fe50..891e5f9 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -44,7 +44,8 @@
       preprocessorOptions: {
         scss: {
           additionalData: '@use "@/styles/variables.scss" as *;',
-          javascriptEnabled: true
+          javascriptEnabled: true,
+          silenceDeprecations: ["legacy-js-api"],
         }
       }
     },
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