Browse Source

组件优化

kyoyue 7 months ago
parent
commit
a90c3b7d62
50 changed files with 3415 additions and 85 deletions
  1. 3 3
      internal/eslint-config/dist/index.cjs
  2. 2 2
      internal/eslint-config/dist/index.d.ts
  3. 4 4
      internal/eslint-config/dist/index.mjs
  4. 3 3
      internal/eslint-config/dist/strict.cjs
  5. 2 2
      internal/eslint-config/dist/strict.d.ts
  6. 4 4
      internal/eslint-config/dist/strict.mjs
  7. 3 3
      internal/stylelint-config/dist/index.cjs
  8. 2 2
      internal/stylelint-config/dist/index.d.ts
  9. 4 4
      internal/stylelint-config/dist/index.mjs
  10. 3 3
      internal/vite-config/dist/index.cjs
  11. 1 1
      internal/vite-config/dist/index.d.ts
  12. 4 4
      internal/vite-config/dist/index.mjs
  13. 1 0
      package.json
  14. BIN
      src/assets/images/add.png
  15. BIN
      src/assets/images/center.png
  16. BIN
      src/assets/images/home.png
  17. BIN
      src/assets/images/robot.jpeg
  18. BIN
      src/assets/images/top.png
  19. BIN
      src/assets/images/wechatIcon.png
  20. 1 0
      src/assets/images/wechatLogo.svg
  21. BIN
      src/assets/images/wechat_unline.png
  22. 6 1
      src/components/Application/src/AppLogo.vue
  23. 12 1
      src/components/Icon/src/IconPicker.vue
  24. 57 1
      src/components/SimpleMenu/src/SimpleMenu.vue
  25. 1 1
      src/components/SimpleMenu/src/components/SubMenuItem.vue
  26. 1 1
      src/components/Upload/src/components/ImageUpload.vue
  27. 4 0
      src/enums/pageEnum.ts
  28. 36 1
      src/views/sys/role/role.data.ts
  29. 20 7
      src/views/wechat/batch_msg/batchMsg.data.ts
  30. 113 0
      src/views/wechat/batch_msg/components/customIcons.vue
  31. 430 0
      src/views/wechat/batch_msg/components/msgContant.vue
  32. 103 0
      src/views/wechat/batch_msg/components/productContant.vue
  33. 442 0
      src/views/wechat/batch_msg/components/productNote.vue
  34. 166 0
      src/views/wechat/batch_msg/components/timedSending.vue
  35. 30 0
      src/views/wechat/batch_msg/components/tinymce.ts
  36. 749 0
      src/views/wechat/batch_msg/components/userPhasesDrawer.vue
  37. 80 22
      src/views/wechat/batch_msg/index.vue
  38. 157 0
      src/views/wechat/batch_msg/send_record/index.vue
  39. 2 0
      src/views/wechat/contact/contact.data.ts
  40. 96 13
      src/views/wechat/contact/index.vue
  41. 0 2
      src/views/wechat/sop_task/index.vue
  42. 91 0
      src/views/wechat/wechat_main/components/add_robot.vue
  43. 95 0
      src/views/wechat/wechat_main/components/all_answer.vue
  44. 56 0
      src/views/wechat/wechat_main/components/chat_dialog.vue
  45. 189 0
      src/views/wechat/wechat_main/components/home.vue
  46. 88 0
      src/views/wechat/wechat_main/components/processed_answer.vue
  47. 13 0
      src/views/wechat/wechat_main/components/robot_chat.vue
  48. 152 0
      src/views/wechat/wechat_main/components/split_wrapper.vue
  49. 184 0
      src/views/wechat/wechat_main/index.vue
  50. 5 0
      src/views/wechat/wx/wx.data.ts

+ 3 - 3
internal/eslint-config/dist/index.cjs

@@ -1,7 +1,7 @@
-module.exports = require("/Users/songbowen/development/Project/gooki/scrm/wechat-ui/node_modules/.pnpm/jiti@1.21.6/node_modules/jiti/lib/index.js")(null, {
+module.exports = require("/Users/wanganyue/Desktop/wechat-ui/node_modules/.pnpm/jiti@1.21.6/node_modules/jiti/lib/index.js")(null, {
   "esmResolve": true,
   "interopDefault": true,
   "alias": {
-    "@vben/eslint-config": "/Users/songbowen/development/Project/gooki/scrm/wechat-ui/internal/eslint-config"
+    "@vben/eslint-config": "/Users/wanganyue/Desktop/wechat-ui/internal/eslint-config"
   }
-})("/Users/songbowen/development/Project/gooki/scrm/wechat-ui/internal/eslint-config/src/index.ts")
+})("/Users/wanganyue/Desktop/wechat-ui/internal/eslint-config/src/index.ts")

+ 2 - 2
internal/eslint-config/dist/index.d.ts

@@ -1,2 +1,2 @@
-export * from "/Users/songbowen/development/Project/gooki/scrm/wechat-ui/internal/eslint-config/src/index";
-export { default } from "/Users/songbowen/development/Project/gooki/scrm/wechat-ui/internal/eslint-config/src/index";
+export * from "/Users/wanganyue/Desktop/wechat-ui/internal/eslint-config/src/index";
+export { default } from "/Users/wanganyue/Desktop/wechat-ui/internal/eslint-config/src/index";

+ 4 - 4
internal/eslint-config/dist/index.mjs

@@ -1,12 +1,12 @@
-import jiti from "file:///Users/songbowen/development/Project/gooki/scrm/wechat-ui/node_modules/.pnpm/jiti@1.21.6/node_modules/jiti/lib/index.js";
+import jiti from "file:///Users/wanganyue/Desktop/wechat-ui/node_modules/.pnpm/jiti@1.21.6/node_modules/jiti/lib/index.js";
 
-/** @type {import("/Users/songbowen/development/Project/gooki/scrm/wechat-ui/internal/eslint-config/src/index")} */
+/** @type {import("/Users/wanganyue/Desktop/wechat-ui/internal/eslint-config/src/index")} */
 const _module = jiti(null, {
   "esmResolve": true,
   "interopDefault": true,
   "alias": {
-    "@vben/eslint-config": "/Users/songbowen/development/Project/gooki/scrm/wechat-ui/internal/eslint-config"
+    "@vben/eslint-config": "/Users/wanganyue/Desktop/wechat-ui/internal/eslint-config"
   }
-})("/Users/songbowen/development/Project/gooki/scrm/wechat-ui/internal/eslint-config/src/index.ts");
+})("/Users/wanganyue/Desktop/wechat-ui/internal/eslint-config/src/index.ts");
 
 export default _module;

+ 3 - 3
internal/eslint-config/dist/strict.cjs

@@ -1,7 +1,7 @@
-module.exports = require("/Users/songbowen/development/Project/gooki/scrm/wechat-ui/node_modules/.pnpm/jiti@1.21.6/node_modules/jiti/lib/index.js")(null, {
+module.exports = require("/Users/wanganyue/Desktop/wechat-ui/node_modules/.pnpm/jiti@1.21.6/node_modules/jiti/lib/index.js")(null, {
   "esmResolve": true,
   "interopDefault": true,
   "alias": {
-    "@vben/eslint-config": "/Users/songbowen/development/Project/gooki/scrm/wechat-ui/internal/eslint-config"
+    "@vben/eslint-config": "/Users/wanganyue/Desktop/wechat-ui/internal/eslint-config"
   }
-})("/Users/songbowen/development/Project/gooki/scrm/wechat-ui/internal/eslint-config/src/strict.ts")
+})("/Users/wanganyue/Desktop/wechat-ui/internal/eslint-config/src/strict.ts")

+ 2 - 2
internal/eslint-config/dist/strict.d.ts

@@ -1,2 +1,2 @@
-export * from "/Users/songbowen/development/Project/gooki/scrm/wechat-ui/internal/eslint-config/src/strict";
-export { default } from "/Users/songbowen/development/Project/gooki/scrm/wechat-ui/internal/eslint-config/src/strict";
+export * from "/Users/wanganyue/Desktop/wechat-ui/internal/eslint-config/src/strict";
+export { default } from "/Users/wanganyue/Desktop/wechat-ui/internal/eslint-config/src/strict";

+ 4 - 4
internal/eslint-config/dist/strict.mjs

@@ -1,12 +1,12 @@
-import jiti from "file:///Users/songbowen/development/Project/gooki/scrm/wechat-ui/node_modules/.pnpm/jiti@1.21.6/node_modules/jiti/lib/index.js";
+import jiti from "file:///Users/wanganyue/Desktop/wechat-ui/node_modules/.pnpm/jiti@1.21.6/node_modules/jiti/lib/index.js";
 
-/** @type {import("/Users/songbowen/development/Project/gooki/scrm/wechat-ui/internal/eslint-config/src/strict")} */
+/** @type {import("/Users/wanganyue/Desktop/wechat-ui/internal/eslint-config/src/strict")} */
 const _module = jiti(null, {
   "esmResolve": true,
   "interopDefault": true,
   "alias": {
-    "@vben/eslint-config": "/Users/songbowen/development/Project/gooki/scrm/wechat-ui/internal/eslint-config"
+    "@vben/eslint-config": "/Users/wanganyue/Desktop/wechat-ui/internal/eslint-config"
   }
-})("/Users/songbowen/development/Project/gooki/scrm/wechat-ui/internal/eslint-config/src/strict.ts");
+})("/Users/wanganyue/Desktop/wechat-ui/internal/eslint-config/src/strict.ts");
 
 export default _module;

+ 3 - 3
internal/stylelint-config/dist/index.cjs

@@ -1,7 +1,7 @@
-module.exports = require("/Users/songbowen/development/Project/gooki/scrm/wechat-ui/node_modules/.pnpm/jiti@1.21.6/node_modules/jiti/lib/index.js")(null, {
+module.exports = require("/Users/wanganyue/Desktop/wechat-ui/node_modules/.pnpm/jiti@1.21.6/node_modules/jiti/lib/index.js")(null, {
   "esmResolve": true,
   "interopDefault": true,
   "alias": {
-    "@vben/stylelint-config": "/Users/songbowen/development/Project/gooki/scrm/wechat-ui/internal/stylelint-config"
+    "@vben/stylelint-config": "/Users/wanganyue/Desktop/wechat-ui/internal/stylelint-config"
   }
-})("/Users/songbowen/development/Project/gooki/scrm/wechat-ui/internal/stylelint-config/src/index.ts")
+})("/Users/wanganyue/Desktop/wechat-ui/internal/stylelint-config/src/index.ts")

+ 2 - 2
internal/stylelint-config/dist/index.d.ts

@@ -1,2 +1,2 @@
-export * from "/Users/songbowen/development/Project/gooki/scrm/wechat-ui/internal/stylelint-config/src/index";
-export { default } from "/Users/songbowen/development/Project/gooki/scrm/wechat-ui/internal/stylelint-config/src/index";
+export * from "/Users/wanganyue/Desktop/wechat-ui/internal/stylelint-config/src/index";
+export { default } from "/Users/wanganyue/Desktop/wechat-ui/internal/stylelint-config/src/index";

+ 4 - 4
internal/stylelint-config/dist/index.mjs

@@ -1,12 +1,12 @@
-import jiti from "file:///Users/songbowen/development/Project/gooki/scrm/wechat-ui/node_modules/.pnpm/jiti@1.21.6/node_modules/jiti/lib/index.js";
+import jiti from "file:///Users/wanganyue/Desktop/wechat-ui/node_modules/.pnpm/jiti@1.21.6/node_modules/jiti/lib/index.js";
 
-/** @type {import("/Users/songbowen/development/Project/gooki/scrm/wechat-ui/internal/stylelint-config/src/index")} */
+/** @type {import("/Users/wanganyue/Desktop/wechat-ui/internal/stylelint-config/src/index")} */
 const _module = jiti(null, {
   "esmResolve": true,
   "interopDefault": true,
   "alias": {
-    "@vben/stylelint-config": "/Users/songbowen/development/Project/gooki/scrm/wechat-ui/internal/stylelint-config"
+    "@vben/stylelint-config": "/Users/wanganyue/Desktop/wechat-ui/internal/stylelint-config"
   }
-})("/Users/songbowen/development/Project/gooki/scrm/wechat-ui/internal/stylelint-config/src/index.ts");
+})("/Users/wanganyue/Desktop/wechat-ui/internal/stylelint-config/src/index.ts");
 
 export default _module;

+ 3 - 3
internal/vite-config/dist/index.cjs

@@ -1,7 +1,7 @@
-module.exports = require("/Users/songbowen/development/Project/gooki/scrm/wechat-ui/node_modules/.pnpm/jiti@1.21.6/node_modules/jiti/lib/index.js")(null, {
+module.exports = require("/Users/wanganyue/Desktop/wechat-ui/node_modules/.pnpm/jiti@1.21.6/node_modules/jiti/lib/index.js")(null, {
   "esmResolve": true,
   "interopDefault": true,
   "alias": {
-    "@vben/vite-config": "/Users/songbowen/development/Project/gooki/scrm/wechat-ui/internal/vite-config"
+    "@vben/vite-config": "/Users/wanganyue/Desktop/wechat-ui/internal/vite-config"
   }
-})("/Users/songbowen/development/Project/gooki/scrm/wechat-ui/internal/vite-config/src/index.ts")
+})("/Users/wanganyue/Desktop/wechat-ui/internal/vite-config/src/index.ts")

+ 1 - 1
internal/vite-config/dist/index.d.ts

@@ -1 +1 @@
-export * from "/Users/songbowen/development/Project/gooki/scrm/wechat-ui/internal/vite-config/src/index";
+export * from "/Users/wanganyue/Desktop/wechat-ui/internal/vite-config/src/index";

+ 4 - 4
internal/vite-config/dist/index.mjs

@@ -1,13 +1,13 @@
-import jiti from "file:///Users/songbowen/development/Project/gooki/scrm/wechat-ui/node_modules/.pnpm/jiti@1.21.6/node_modules/jiti/lib/index.js";
+import jiti from "file:///Users/wanganyue/Desktop/wechat-ui/node_modules/.pnpm/jiti@1.21.6/node_modules/jiti/lib/index.js";
 
-/** @type {import("/Users/songbowen/development/Project/gooki/scrm/wechat-ui/internal/vite-config/src/index")} */
+/** @type {import("/Users/wanganyue/Desktop/wechat-ui/internal/vite-config/src/index")} */
 const _module = jiti(null, {
   "esmResolve": true,
   "interopDefault": true,
   "alias": {
-    "@vben/vite-config": "/Users/songbowen/development/Project/gooki/scrm/wechat-ui/internal/vite-config"
+    "@vben/vite-config": "/Users/wanganyue/Desktop/wechat-ui/internal/vite-config"
   }
-})("/Users/songbowen/development/Project/gooki/scrm/wechat-ui/internal/vite-config/src/index.ts");
+})("/Users/wanganyue/Desktop/wechat-ui/internal/vite-config/src/index.ts");
 
 export const defineApplicationConfig = _module.defineApplicationConfig;
 export const definePackageConfig = _module.definePackageConfig;

+ 1 - 0
package.json

@@ -103,6 +103,7 @@
     "resize-observer-polyfill": "^1.5.1",
     "showdown": "^2.1.0",
     "sortablejs": "^1.15.2",
+    "split.js": "^1.6.5",
     "tinymce": "^6.8.3",
     "unocss": "^0.58.9",
     "vditor": "^3.10.4",

BIN
src/assets/images/add.png


BIN
src/assets/images/center.png


BIN
src/assets/images/home.png


BIN
src/assets/images/robot.jpeg


BIN
src/assets/images/top.png


BIN
src/assets/images/wechatIcon.png


+ 1 - 0
src/assets/images/wechatLogo.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="178" height="143" viewBox="0 0 178 143"><g><path d="M120.496,43.2605C122.524,43.2734,124.55,43.3959,126.564,43.6273C121.115,18.5962,93.9756,0,63.0023,0C28.3737,0,0,23.281,0,52.8388C0,69.9066,9.43999,83.9174,25.2091,94.7772L18.9158,113.47L40.9373,102.577C48.8231,104.106,55.142,105.695,63.0074,105.695C64.9833,105.695,66.9388,105.601,68.8866,105.441C67.6116,101.219,66.9555,96.835,66.9388,92.4258C66.9388,65.2855,90.5759,43.2604,120.501,43.2604L120.496,43.2605ZM86.6266,26.4143C91.3708,26.4143,94.5124,29.4967,94.5124,34.1712C94.5124,38.8458,91.3708,41.946,86.6266,41.946C81.8823,41.946,77.1687,38.8279,77.1687,34.1712C77.1687,29.5145,81.9027,26.4143,86.6266,26.4143ZM42.5375,41.9536C37.8111,41.9536,33.0464,38.8356,33.0464,34.1789C33.0464,29.5222,37.8111,26.4219,42.5375,26.4219C47.2639,26.4219,50.4029,29.5043,50.4029,34.1789C50.4029,38.8534,47.2562,41.9536,42.5375,41.9536ZM178,91.6922C178,66.8522,152.793,46.6027,124.486,46.6027C94.5124,46.6027,70.9061,66.8497,70.9061,91.6922C70.9061,116.57,94.5124,136.782,124.486,136.782C130.759,136.782,137.088,135.23,143.387,133.669L160.669,143L155.927,127.463C168.578,118.101,177.997,105.695,177.997,91.6846L178,91.6922ZM107.104,83.9098C103.968,83.9098,100.803,80.8325,100.803,77.6915C100.803,74.5964,103.968,71.4834,107.104,71.4834C111.866,71.4834,114.987,74.5964,114.987,77.6915C114.987,80.8325,111.866,83.9098,107.104,83.9098ZM141.753,83.9098C138.642,83.9098,135.498,80.8325,135.498,77.6915C135.498,74.5964,138.642,71.4834,141.753,71.4834C146.48,71.4834,149.639,74.5964,149.639,77.6915C149.639,80.8325,146.48,83.9098,141.753,83.9098Z" fill="#EBEBEB" fill-opacity="1"/></g></svg>

BIN
src/assets/images/wechat_unline.png


+ 6 - 1
src/components/Application/src/AppLogo.vue

@@ -5,7 +5,12 @@
 <template>
   <div class="anticon" :class="getAppLogoClass" @click="goHome">
     <img src="../../../assets/images/gooki.ico" />
-    <div class="ml-2 truncate md:opacity-100" style="color: rgb(38,151,251);" :class="getTitleClass" v-show="showTitle">
+    <div
+      class="ml-2 truncate md:opacity-100"
+      style="color: rgb(38, 151, 251)"
+      :class="getTitleClass"
+      v-show="showTitle"
+    >
       {{ title }}
     </div>
   </div>

+ 12 - 1
src/components/Icon/src/IconPicker.vue

@@ -183,7 +183,7 @@
     currentList.value = icons.filter((item) => item.includes(value));
   }
 </script>
-<style lang="less">
+<style lang="less" >
   @prefix-cls: ~'@{namespace}-icon-picker';
 
   .@{prefix-cls} {
@@ -207,4 +207,15 @@
       }
     }
   }
+   .vben-menu-dark.vben-menu-popup .vben-simple-menu__children{
+    background: #fff;
+    color:#666;
+  }
+   .vben-menu-dark.vben-menu-popup .vben-simple-menu__children:hover{
+    color:#1677ff;
+  }
+  .vben-menu-menu-popover .vben-menu-dark .vben-menu-item-selected{
+    background: #fff !important;
+    color:#1677ff;
+  }
 </style>

+ 57 - 1
src/components/SimpleMenu/src/SimpleMenu.vue

@@ -7,6 +7,13 @@
     :activeSubMenuNames="activeSubMenuNames"
     @select="handleSelect"
   >
+  <div class="switch" >
+    <div class="switch__text">
+      <span class="switch__text__span" :style="{ opacity: isScrm == 'SCRM' ? 0 : 1 }" @click="toggleSwitch('SCRM')">SCRM</span>
+      <span class="switch__text__span" :style="{ opacity: isScrm == 'CHAT' ? 0 : 1, width: '92px' }" @click="toggleSwitch('CHAT')">聚合聊天</span>
+      <div class="switch__slider" :style="{ left: isScrm == 'SCRM' ? '6px' : '92px' }">{{ isScrm == 'SCRM' ? 'SCRM' : '聚合聊天' }}</div>
+    </div>
+  </div>
     <template v-for="item in items" :key="item.path">
       <SimpleSubMenu
         :item="item"
@@ -33,6 +40,8 @@
   import { isFunction, isHttpUrl } from '@/utils/is';
   import { openWindow } from '@/utils';
 
+  import { useGo } from "/@/hooks/web/usePage";
+import { PageEnum } from '@/enums/pageEnum';
   import { useOpenKeys } from './useOpenKeys';
 
   export default defineComponent({
@@ -61,7 +70,8 @@
     setup(props, { attrs, emit }) {
       const currentActiveMenu = ref('');
       const isClickGo = ref(false);
-
+      const isScrm = ref('SCRM');
+      const go = useGo();
       const menuState = reactive<MenuState>({
         activeName: '',
         openNames: [],
@@ -105,6 +115,14 @@
         { flush: 'post' },
       );
 
+      function toggleSwitch(value){
+        isScrm.value = value;
+        if (value == 'SCRM') {
+          go(PageEnum.BASE_HOME);
+        } else {
+          go(PageEnum.WECHAT_MAIN)
+        }
+      };
       listenerRouteChange((route) => {
         if (route.name === REDIRECT_NAME) return;
 
@@ -152,6 +170,8 @@
         getBindValues,
         handleSelect,
         getOpenKeys,
+        toggleSwitch,
+        isScrm,
         ...toRefs(menuState),
       };
     },
@@ -159,4 +179,40 @@
 </script>
 <style lang="less">
   @import url('./index.less');
+  .switch {
+    margin:10px auto;
+    height: 40px;
+    border-radius: 50px;
+    background: #f2f2f2;
+    position: relative;
+    font-size: 14px;
+    color: #666;
+    display: inline-block;
+    padding: 4px 12px;
+    cursor: pointer;
+  }
+  .switch__text {
+    height: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+  }
+  .switch__text__span {
+    transition: all 0.2s ease-in-out;
+    padding: 6px 18px;
+    color: #666;
+  }
+  .switch__slider {
+    position: absolute;
+    top: 50%;
+    left: 6px;
+    transform: translateY(-50%);
+    border-radius: 16px;
+    background: #fff;
+    box-shadow: 0 2px 8px 0 rgba(163, 167, 184, 0.2);
+    font-weight: 600;
+    color: #333;
+    transition: all 0.2s ease-in-out;
+    padding: 9px 18px;
+  }
 </style>

+ 1 - 1
src/components/SimpleMenu/src/components/SubMenuItem.vue

@@ -47,7 +47,7 @@
       <!-- eslint-disable-next-line -->
       <template #content v-show="opened">
         <div v-bind="getEvents(true)">
-          <ul :class="[prefixCls, `${prefixCls}-${getTheme}`, `${prefixCls}-popup`]">
+          <ul :class="[prefixCls, `${prefixCls}-${getTheme}`, `${prefixCls}-popup`]" style="flex-direction: column;">
             <slot></slot>
           </ul>
         </div>

+ 1 - 1
src/components/Upload/src/components/ImageUpload.vue

@@ -17,7 +17,7 @@
         <div style="margin-top: 8px">{{ t('component.upload.upload') }}</div>
       </div>
     </Upload>
-    <Modal :open="previewOpen" :title="previewTitle" :footer="null" @cancel="handleCancel">
+    <Modal v-model:open="previewOpen" :title="previewTitle" :footer="null" @cancel="handleCancel">
       <img alt="" style="width: 100%" :src="previewImage" />
     </Modal>
   </div>

+ 4 - 0
src/enums/pageEnum.ts

@@ -19,6 +19,10 @@ export enum PageEnum {
   PERFORM_TASKS = '/wechat/sop_task/performTasks',
   // 发送任务路径
   SEND_TASKS = '/wechat/sop_task/sendTasks',
+  //群发消息_发送记录
+  SEND_RECORD = '/wechat/batch_msg/sendRecord',
+  //聚合聊天
+  WECHAT_MAIN = '/wechat/wechatMain',
 }
 
 export const PageWrapperFixedHeightKey = 'PageWrapperFixedHeight';

+ 36 - 1
src/views/sys/role/role.data.ts

@@ -83,7 +83,11 @@ export const formSchema: FormSchema[] = [
     label: t('sys.role.roleName'),
     required: true,
     component: 'Input',
-    rules: [{ max: 30 }],
+    componentProps: {
+      maxlength: 20,
+      showCount: true,
+    },
+    rules: [{ max: 20 }],
   },
   {
     field: 'sort',
@@ -124,6 +128,37 @@ export const formSchema: FormSchema[] = [
     component: 'InputTextArea',
     rules: [{ max: 200 }],
   },
+  {
+    label: '角色设定',
+    field: 'roleSetting',
+    component: 'InputTextArea',
+    required: true,
+    componentProps: {
+      maxlength: 1000,
+      showCount: true,
+    },
+    rules: [{ max: 1000 }],
+  },
+  {
+    label: '背景介绍',
+    field: 'bgIntroduction',
+    component: 'InputTextArea',
+    componentProps: {
+      maxlength: 1000,
+      showCount: true,
+    },
+    rules: [{ max: 1000 }],
+  },
+  {
+    label: '对话案例',
+    field: 'dialogueCase',
+    component: 'InputTextArea',
+    componentProps: {
+      maxlength: 5000,
+      showCount: true,
+    },
+    rules: [{ max: 5000 }],
+  },
 ];
 
 /**

+ 20 - 7
src/views/wechat/batch_msg/batchMsg.data.ts

@@ -13,37 +13,50 @@ export const columns: BasicColumn[] = [
   {
     title: t('wechat.batchMsg.batchNo'),
     dataIndex: 'batchNo',
-    width: 200,
+    width: 80,
+  },
+  {
+    title: '任务名称',
+    dataIndex: 'taskName',
+    width: 80,
   },
   {
     title: t('wechat.batchMsg.fromwxid'),
     dataIndex: 'fromwxid',
-    width: 100,
+    width: 80,
   },
   {
     title: t('wechat.batchMsg.msg'),
     dataIndex: 'msg',
-    width: 100,
+    width: 80,
   },
   {
     title: t('wechat.batchMsg.tag'),
     dataIndex: 'tag',
-    width: 100,
+    width: 80,
+  },
+  {
+    title: '计划开始时间',
+    dataIndex: 'planStatTime',
+    width: 70,
+    customRender: ({ record }) => {
+      return formatToDateTime(record.planStatTime);
+    },
   },
   {
     title: t('wechat.batchMsg.total'),
     dataIndex: 'total',
-    width: 100,
+    width: 30,
   },
   {
     title: t('wechat.batchMsg.success'),
     dataIndex: 'success',
-    width: 100,
+    width: 50,
   },
   {
     title: t('wechat.batchMsg.fail'),
     dataIndex: 'fail',
-    width: 100,
+    width: 50,
   },
   {
     title: t('common.status'),

+ 113 - 0
src/views/wechat/batch_msg/components/customIcons.vue

@@ -0,0 +1,113 @@
+<template>
+  <!-- 上移下移删除icon -->
+  <div class="custom-icons">
+    <a-tooltip >
+      <template #title>上移一层</template>
+      <a-button
+        :disabled="iconDisable"
+        @click="moveUp"
+        style="border: none;box-shadow: none;background: none; display: flex; align-item: center"
+      >
+        <VerticalAlignTopOutlined :style="{ fontSize: '18px' }" />
+      </a-button>
+    </a-tooltip>
+    <a-tooltip>
+      <template #title>下移一层</template>
+      <a-button
+        :disabled="iconDisable"
+        @click="moveDown"
+        style="border: none;box-shadow: none;background: none; display: flex; align-item: center"
+      >
+        <VerticalAlignBottomOutlined :style="{ fontSize: '18px' }" />
+      </a-button>
+    </a-tooltip>
+    <a-tooltip>
+      <template #title>删除</template>
+      <a-button
+        :disabled="iconDisable"
+        style="border: none;box-shadow: none;background: none; display: flex; align-item: center"
+        @click="showDeleteConfirm"
+      >
+        <DeleteOutlined style="font-size: 18px; color: red" />
+      </a-button>
+    </a-tooltip>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, inject, props } from 'vue';
+  import { Tooltip, Modal, Button } from 'ant-design-vue';
+  import {
+    DeleteOutlined,
+    VerticalAlignBottomOutlined,
+    VerticalAlignTopOutlined,
+  } from '@ant-design/icons-vue';
+  export default defineComponent({
+    name: 'CustomIcons',
+    components: {
+      'a-tooltip': Tooltip,
+      'a-button': Button,
+      Modal,
+      DeleteOutlined,
+      VerticalAlignBottomOutlined,
+      VerticalAlignTopOutlined,
+    },
+    props: {
+      index: Number,
+      length: Number,
+      canMoveUp: Boolean,
+      canMoveDown: Boolean,
+      iconDisable: Boolean,
+    },
+    emits: ['remove', 'moveUp', 'moveDown'],
+    setup({ index, length, canMoveUp, canMoveDown, iconDisable }, emit) {
+      const removeItem = inject('removeItem');
+      const isFirst = index === 0;
+      const isLast = index === length - 1;
+      const moveItem = inject('moveItem');
+      const moveUp = () => {
+        // emit('moveUp');
+        moveItem(index, 'up');
+      };
+
+      const moveDown = () => {
+        // emit('moveDown');
+        moveItem(index, 'down');
+      };
+      const showDeleteConfirm = () => {
+        Modal.confirm({
+          title: '提示',
+          content: '确定要删除此内容吗?',
+          okText: '确定',
+          okType: 'danger',
+          cancelText: '取消',
+          onOk() {
+            // 确定按钮的回调:执行删除
+            removeItem(index);
+          },
+          onCancel() {
+            // 取消按钮的回调:什么也不做
+            console.log('Cancel');
+          },
+        });
+      };
+      return {
+        removeItem,
+        isFirst,
+        isLast,
+        moveItem,
+        showDeleteConfirm,
+        moveUp,
+        moveDown,
+      };
+    },
+  });
+</script>
+<style scoped lang="less">
+  .custom-icons {
+    width: 80px;
+    display: flex;
+    align-items: center;
+    height: 35px;
+    justify-content: space-between;
+  }
+</style>

+ 430 - 0
src/views/wechat/batch_msg/components/msgContant.vue

@@ -0,0 +1,430 @@
+<!-- 消息内容 -->
+<template>
+  <div>
+    <div v-for="(area, index) in internalValue" :key="index">
+      <!-- 文本内容  -->
+      <a-form-item label="" v-if="area.type === 1" name="content" class="message-content">
+        <div class="custom-placeholder-wrapper">
+          <a-textarea
+            :disabled="msgDisabled"
+            class="custom-textarea"
+            v-model:value="area.content"
+            :rows="8"
+            :maxlength="1000"
+            @focus="handleFocus"
+            @blur="handleBlur"
+          />
+          <!-- <HtmlTextarea v-model="taskForm.messageContent" :rows="4" :maxlength="1000" @focus="handleFocus"
+                    @blur="handleBlur" /> -->
+          <!-- <div v-if="!area.content && !isFocused" class="custom-placeholder">
+            <span>示例:</span>
+            <span class="highlight">xx客户</span>
+            <span class="highlight">性别</span>
+            <span>您好!</span>
+          </div> -->
+          <div class="textarea-bottom">
+            <!-- <a-dropdown :trigger="['click']" @click.stop arrow placement="bottomRight">
+          <a class="ant-dropdown-link" @click.prevent>
+            <PlusCircleOutlined />
+            引用客户信息
+          </a>
+          <template #overlay>
+            <a-menu @click="handleMenuClick">
+              <div class="ant-dropdown-menu-item">
+                <UserOutlined />
+                基本信息
+              </div>
+              <a-menu-item key="wechatNickname">微信昵称</a-menu-item>
+              <a-menu-item key="name">姓名</a-menu-item>
+              <a-menu-item key="title">称呼</a-menu-item>
+              <a-menu-item key="surname">姓氏</a-menu-item>
+              <a-sub-menu key="gender" title="性别">
+                <a-menu-item key="Mr/Ms">先生/女士</a-menu-item>
+                <a-menu-item key="youngMan/youngLady">小哥哥/小姐姐</a-menu-item>
+              </a-sub-menu>
+            </a-menu>
+          </template>
+        </a-dropdown> -->
+            <div class="character-count">{{ area.content.length }}/ 1000</div>
+          </div>
+          <!-- @remove="emit('remove')" @moveUp="emit('moveUp')" @moveDown="emit('moveDown')":length="length" -->
+          <CustomIcons :index="index" :iconDisable="msgDisabled" />
+          <span v-show="area.content == ''" class="warning-style">请输入文本内容</span>
+        </div>
+      </a-form-item>
+      <a-form-item v-else-if="area.type === 2" label="" name="content" class="message-content">
+        <!-- class="upload-contant" -->
+        <div :class="msgDisabled ? 'disabled-upload upload-contant' : 'upload-contant'">
+        <BasicUpload 
+          :maxSize="100"
+          :maxNumber="1"
+          @change="handleChange"
+          :api="uploadApi"
+          v-model:value="area.content"
+          :showPreviewNumber=false
+          emptyHidePreview
+        />
+        <div class="upload-file-style" v-if="area.content">{{String(area.content).split('/').pop()}}</div>
+          <CustomIcons :index="index" :length="length" :iconDisable="msgDisabled" />
+        </div>
+      </a-form-item>
+      <!-- 添加类型按钮 -->
+      <div v-if="index === internalValue.length - 1" class="message-content-btn">
+        <a-dropdown :trigger="['click']" @click.stop arrow placement="bottomRight">
+          <a-button :disabled="msgDisabled" value="large" type="primary" class="create-btn">
+            添加
+          </a-button>
+          <template #overlay>
+            <a-menu @click="handleCreateClick">
+              <a-menu-item key="1">文本</a-menu-item>
+              <a-menu-item key="2">文件</a-menu-item>
+              <!-- <a-menu-item key="productNote">产品笔记</a-menu-item>
+                <a-menu-item key="miniProgram">小程序</a-menu-item> -->
+            </a-menu>
+          </template>
+        </a-dropdown>
+      </div>
+    </div>
+    <div v-if="internalValue.length === 0" class="message-content-btn1">
+      <a-dropdown :trigger="['click']" @click.stop arrow placement="bottomRight">
+        <a-button :disabled="msgDisabled" value="large" type="primary" class="create-btn">
+          添加
+        </a-button>
+        <template #overlay>
+          <a-menu @click="handleCreateClick">
+            <a-menu-item key="1">文本</a-menu-item>
+            <a-menu-item key="2">文件</a-menu-item>
+            <!-- <a-menu-item key="productNote">产品笔记</a-menu-item>
+                <a-menu-item key="miniProgram">小程序</a-menu-item> -->
+          </a-menu>
+        </template>
+      </a-dropdown>
+    </div>
+    <!-- 产品笔记 -->
+    <!-- <ProductContant /> -->
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, reactive, provide, props, watch } from 'vue';
+  import { Form, Button, Dropdown, Menu, message, Input, SubMenu, Upload } from 'ant-design-vue';
+  import {
+    CloudUploadOutlined,
+    PlusCircleOutlined,
+    UserOutlined,
+  } from '@ant-design/icons-vue';
+  import CustomIcons from './customIcons.vue';
+  import ProductNote from './productNote.vue';
+  import { BasicUpload } from '@/components/Upload';
+  import { deleteFile, downloadFile, getFileList, uploadApi } from '@/api/fms/file';
+  import ProductContant from './productContant.vue';
+  import type { UploadChangeParam, UploadProps } from 'ant-design-vue'
+  export default defineComponent({
+    name: 'MsgContant',
+    components: {
+      'a-form': Form,
+      'a-form-item': Form.Item,
+      'a-button': Button,
+      'a-dropdown': Dropdown,
+      'a-menu': Menu,
+      'a-menu-item': Menu.Item,
+      'a-input': Input,
+      'a-input-textarea': Input.TextArea,
+      'a-sub-menu': SubMenu,
+      'a-upload': Upload,
+      'a-upload-dragger': Upload.Dragger,
+      BasicUpload,
+      CloudUploadOutlined,
+      PlusCircleOutlined,
+      UserOutlined,
+      CustomIcons,
+      ProductNote,
+      ProductContant,
+    },
+
+    props: {
+      value: {
+        type: Array,
+        required: true,
+      },
+      msgDisabled: {
+        type: Boolean,
+        default: false,
+      },
+    },
+    emits: ['update:value'],
+    setup(props, { emit }) {
+      // const internalValue = ref([...props.value]);
+      const internalValue = ref(addCanMoveProperties(props.value));
+      const msgDisabled = ref(props.msgDisabled);
+      const taskForm = reactive({
+        fileContent: '',
+      });
+      function handleChange(list: string[]) {
+        if (msgDisabled.value) {
+          return
+        }
+          // createMessage.info(`已上传文件${JSON.stringify(list)}`);
+          console.log(list);
+        };
+      watch(
+        internalValue,
+        (newValue) => {
+          emit('update:value', newValue);
+        },
+        { deep: true },
+      );
+
+      function addCanMoveProperties(items) {
+        return items.map((item, index, array) => ({
+          ...item,
+          canMoveUp: index > 0,
+          canMoveDown: index < array.length - 1,
+        }));
+      }
+      function updateCanMoveProperties() {
+        internalValue.value.forEach((item, index, array) => {
+          item.canMoveUp = index > 0;
+          item.canMoveDown = index < array.length - 1;
+        });
+      }
+      const updateValue = (index: number, event: Event) => {
+        const input = event.target as HTMLInputElement;
+        internalValue.value[index] = input.value;
+        emit('update:value', internalValue.value);
+      };
+      const isFocused = ref(false);
+
+      const fileList = ref([]);
+
+      const handleRemove = () => {
+        fileList.value = [];
+        taskForm.fileContent = '';
+      };
+
+      const handleFocus = () => {
+        isFocused.value = true;
+      };
+
+      const handleBlur = () => {
+        isFocused.value = false;
+      };
+      const handleMenuClick = ({ key }: { key: string }) => {
+        const labelMap: { [key: string]: string } = {
+          wechatNickname: '微信昵称',
+          name: '姓名',
+          title: '称呼',
+          surname: '姓氏',
+          'Mr/Ms': '先生/女士',
+          'youngMan/youngLady': '小哥哥/小姐姐',
+        };
+        const label = labelMap[key] || key;
+        taskForm.content += `${label}`;
+      };
+
+      const addTextArea = () => {
+        const id = Date.now();
+        internalValue.value.push({
+          id,
+          type: 1,
+          content: '',
+          canMoveUp: internalValue.value.length > 0,
+          canMoveDown: false,
+        });
+        updateCanMoveProperties();
+      };
+
+      // const addFileArea = () => {
+      //   const id = Date.now();
+      //   msgContantArry.value.push({ id, type: 'file' });
+      // };
+      const addFileArea = () => {
+        const id = Date.now();
+        internalValue.value.push({
+          id,
+          type: 2,
+          content: '',
+          canMoveUp: internalValue.value.length > 0,
+          canMoveDown: false,
+        });
+
+        // 更新前一个项的 canMoveDown 为 true
+        updateCanMoveProperties();
+      };
+      // 根据传入的类型添加对应的区域
+      const handleCreateClick = (key: string) => {
+        if (key.key === '1') {
+          addTextArea();
+        } else {
+          addFileArea();
+        }
+      };
+
+      const removeItem = (index: number) => {
+        internalValue.value.splice(index, 1); // 根据索引删除项
+        updateCanMoveProperties();
+      };
+
+      provide('removeItem', removeItem);
+
+      const moveItem = (index, direction) => {
+        if (direction === 'up' && index > 0) {
+          [internalValue.value[index], internalValue.value[index - 1]] = [
+            internalValue.value[index - 1],
+            internalValue.value[index],
+          ];
+        } else if (direction === 'down' && index < internalValue.value.length - 1) {
+          [internalValue.value[index], internalValue.value[index + 1]] = [
+            internalValue.value[index + 1],
+            internalValue.value[index],
+          ];
+        }
+        updateCanMoveProperties();
+      };
+
+      const moveItemUp = (index) => {
+        moveItem(index, 'up');
+      };
+
+      const moveItemDown = (index) => {
+        moveItem(index, 'down');
+      };
+
+      provide('moveItem', moveItem);
+
+      return {
+        uploadApi,
+        taskForm,
+        handleCreateClick,
+        addTextArea,
+        addFileArea,
+        removeItem,
+        moveItem,
+        moveItemUp,
+        moveItemDown,
+        updateCanMoveProperties,
+        isFocused,
+        handleFocus,
+        handleBlur,
+        handleMenuClick,
+        fileList,
+        // beforeUpload,
+        handleChange,
+        handleRemove,
+        // customRequest,
+        internalValue,
+        updateValue,
+        addCanMoveProperties,
+      };
+    },
+  });
+</script>
+<style scoped lang="less">
+  .disabled-upload {
+    cursor: not-allowed;
+    pointer-events: none;
+    opacity: 0.5;
+  }
+  .ant-form-item {
+    margin-bottom: 0;
+  }
+  .message-content-btn {
+    width: 800px;
+    background-color: #f9f9f9;
+    padding: 10px 30px 10px 40px;
+    display: flex;
+    justify-content: left;
+  }
+  .message-content-btn1 {
+    width: 800px;
+    background-color: #f9f9f9;
+    padding: 10px 130px 10px 50px;
+    display: flex;
+    justify-content: left;
+  }
+  .create-btn {
+    width: 120px;
+    border-radius: 3px;
+  }
+  .message-content {
+    width: 800px;
+    background-color: #f9f9f9;
+    padding: 10px 30px 10px 30px;
+    display: flex;
+    justify-content: center;
+  }
+  .custom-textarea {
+    width: 100%;
+    border-radius: 4px 4px 0 0;
+    background-color: #f9f8f8;
+  }
+  .warning-style {
+    color: #ff4949;
+    font-size: 12px;
+    margin-left: 2px;
+    position: absolute;
+    bottom: -13px;
+  }
+  .custom-placeholder-wrapper {
+    position: relative;
+    display: flex;
+    flex-direction: column;
+    width: 720px;
+  }
+  .custom-placeholder {
+    position: absolute;
+    top: 10px;
+    left: 10px;
+    color: #a9a9a9;
+    pointer-events: none;
+    user-select: none;
+    font-size: 14px;
+  }
+  .custom-placeholder .highlight {
+    color: #1c53d9;
+    background-color: #e8edfb;
+    padding: 3px 5px;
+    margin: 0 5px;
+    font-size: 12px;
+  }
+  .textarea-bottom {
+    display: flex;
+    background-color: #f5f8fd;
+    justify-content: space-between;
+    padding: 3px 16px;
+    border: 1px solid #c0ccda;
+    border-top: 0 none;
+    border-radius: 0 0 4px 4px;
+  }
+  .ant-dropdown-link {
+    color: rgb(119, 140, 162);
+  }
+  .character-count {
+    text-align: right;
+    color: #a9a9a9;
+    font-size: 12px;
+    margin-top: 5px;
+  }
+  .upload-contant {
+    width: 750px;
+    display: flex;
+    // justify-content: space-around;
+  }
+  ::deep .ant-upload-wrapper .ant-upload-drag {
+    background-color: #fff;
+    padding: 20px 0;
+  }
+  .upload {
+    width: 600px;
+    display: flex;
+  }
+  .upload:hover {
+    border-color: #1890ff;
+  }
+  .upload-file-style{
+    min-width: 400px;
+    padding: 5px;
+    display: inline-block;
+    background: #f6f6fa;
+    cursor: pointer;
+  }
+</style>

+ 103 - 0
src/views/wechat/batch_msg/components/productContant.vue

@@ -0,0 +1,103 @@
+<!-- 产品笔记 -->
+<template>
+    <a-form-item label="" name="messageContent" class="message-content">
+      <div style="display: flex; justify-content: space-around;width: 750px;cursor: pointer; ">
+        <div class="product-note-contant" @click="handleOpenNote">
+          <div class="note-data" v-html="noteContent"></div>
+          <div class="image"></div>
+        </div>
+        <CustomIcons />
+      </div>
+      <ProductNote :visible="isNoteVisible" @close="handleNoteClose" @save-content="handleSaveContent" />
+    </a-form-item>
+</template>
+
+<script lang="ts">
+  import { defineComponent, ref, reactive, } from 'vue';
+  import { Form } from 'ant-design-vue';
+  import CustomIcons from './customIcons.vue';
+  import ProductNote from './productNote.vue';
+  export default defineComponent({
+    name: 'ProductContant',
+    components: {
+      'a-form': Form,
+      'a-form-item': Form.Item,
+      CustomIcons,
+      ProductNote,
+    },
+    setup() {
+      const isNoteVisible = ref(false);
+      const noteContent = ref(null);
+      const taskForm = reactive({
+        taskType: 'sendMessage',
+        sendType: 'immediate',
+        scheduledTime: null,
+        messageContent: '',
+        fileContent:'',
+      });
+
+      const handleSaveContent= (content) => {
+        // 假设有一个名为noteContent的数据属性用于保存接收到的内容
+        noteContent.value = content;
+      };
+
+      const handleOpenNote = () => {
+        // 打开产品笔记
+        // this.$emit('openNote');
+        isNoteVisible.value = true;
+      };
+
+      const handleNoteClose = () => {
+        // 关闭产品笔记
+        // this.$emit('closeNote');
+        isNoteVisible.value = false;
+      };
+      return {
+        taskForm,
+        handleOpenNote,
+        handleNoteClose,
+        isNoteVisible,
+        handleSaveContent,
+        noteContent
+      };
+    },
+  });
+</script>
+
+<style scoped lang="less">
+  .ant-form-item {
+    margin-bottom: 0;
+  }
+  .message-content {
+    background-color: #f9f9f9;
+    padding: 10px 30px 10px 30px;
+    display: flex;
+    justify-content: center;
+  }
+  .product-note-contant {
+    width: 600px;
+    white-space: break-spaces;
+    word-break: break-all;
+    background-color: #f6f6fa;
+    line-height: 1.2;
+    border-radius: 4px;
+    padding: 20px;
+    display: flex;
+    justify-content: space-between;
+  }
+  .note-data {
+    max-width: 80%;
+    color: #000;
+    max-height: 92px;
+    font-weight: 700 !important;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    overflow: hidden;
+  }
+  .image {
+    width: 92px;
+    height: 92px;
+    background-image: url(../static/note.png);
+    background-size: 100% 100%;
+  }
+</style>

+ 442 - 0
src/views/wechat/batch_msg/components/productNote.vue

@@ -0,0 +1,442 @@
+<template>
+  <a-modal v-model:open="visible" title="产品笔记" width="700px" :body-style="{ height: '600px' }" @ok="handleSave"
+    @cancel="handleCancel">
+    <div :class="prefixCls"
+      :style="{ width: '700px', height: '600px',display: 'flex', justifyContent: 'center', }">
+      <!-- <ImgUpload :fullscreen="fullscreen" @uploading="handleImageUploading" @done="handleDone" v-if="showImageUpload"
+      v-show="editorRef" :disabled="disabled" /> -->
+      <textarea :id="tinymceId" ref="elRef" :style="{ visibility: 'hidden', height: '600px', }"
+        v-if="!initOptions.inline"></textarea>
+      <slot v-else></slot>
+    </div>
+  </a-modal>
+
+</template>
+
+<script lang="ts">
+import type { Editor, RawEditorOptions } from 'tinymce';
+import { Modal } from 'ant-design-vue';
+import tinymce from 'tinymce/tinymce';
+import 'tinymce/themes/silver';
+import 'tinymce/icons/default/icons';
+import 'tinymce/plugins/lists';
+import 'tinymce/plugins/advlist';
+import 'tinymce/plugins/autolink';
+import 'tinymce/plugins/link';
+import 'tinymce/plugins/table'
+import 'tinymce/plugins/emoticons';
+import 'tinymce/plugins/image';
+
+import { defineComponent, computed, nextTick, ref, toRefs, unref,watch, watchEffect, onDeactivated, onBeforeUnmount,} from 'vue';
+// import ImgUpload from './ImgUpload.vue';
+import { plugins } from './tinymce';
+import { buildShortUUID } from '@/utils/uuid';
+import { onMountedOrActivated } from '@vben/hooks';
+import { useDesign } from '@/hooks/web/useDesign';
+// import { isNumber } from '@/utils/is';
+import { useLocale } from '@/locales/useLocale';
+import { useAppStore } from '@/store/modules/app';
+
+export default defineComponent({
+  name: 'Tinymce',
+  components: {
+    'a-modal': Modal,
+  },
+  inheritAttrs: false,
+  // props: { tinymceProps, visible: Boolean },
+  props: {
+    visible: Boolean,
+    tinymceProps: {
+      type: Object as PropType<{
+        height: number;
+        width: number;
+        toolbar: string;
+        plugins: string;
+        options: any;
+        modelValue: string;
+        // 在这里定义tinymceProps对象的其他属性
+      }>,
+      default: () => ({
+        // height: 600, // 提供默认值
+        width: 700, // 提供默认值
+        toolbar: 'undo redo | bold italic underline | forecolor | bullist numlist | hr | emoticons',
+        plugins: plugins,
+        options: {},
+        modelValue: '',
+        // 提供其他属性的默认值
+      })
+    }
+  },
+  emits: ['change', 'update:modelValue', 'inited', 'init-error', 'close', 'save-content'],
+  setup(props, { emit, attrs }) {
+    const originalContent = ref(props.tinymceProps.modelValue)//初始内容
+    const { visible } = toRefs(props); // 控制弹窗的显示隐藏
+    const editorRef = ref<Nullable<Editor>>(null);
+    // const fullscreen = ref(false);
+    const tinymceId = ref<string>(buildShortUUID('tiny-vue'));
+    // const elRef = ref<Nullable<HTMLElement>>(null);
+    const elRef = ref<HTMLElement | null>(null);
+
+    watchEffect(() => {
+      if (visible.value) {
+        // 当visible变为true时确保编辑器初始化
+        initEditor();
+      }
+    });
+
+    watch(() => props.tinymceProps.modelValue, (newVal) => {
+      originalContent.value = newVal;
+      if (editorRef.value) {
+        editorRef.value.setContent(newVal);
+      }
+    });
+
+    const handleSave = () => {
+      const currentContent = editorRef.value?.getContent();
+      console.log('内容保存:', editorContent.value, currentContent);
+      // 更新原始内容为当前内容
+      originalContent.value = currentContent || '';
+      emit('save-content', currentContent);
+      emit('close');
+    };
+
+    const handleCancel = () => {
+      Modal.confirm({
+        title: '提示',
+        content: '关闭后所有新建内容将会清空,确定关闭吗?',
+        okText: '确定',
+        cancelText: '取消',
+        onOk: () => {
+          if (editorRef.value) {
+            // 重置编辑器内容为原始内容
+            editorRef.value.setContent(originalContent.value || '');
+          }
+          emit('close');
+        },
+        onCancel() {
+          console.log('取消操作,继续编辑');
+        },
+      });
+      // if (editorRef.value) {
+      //   // 重置编辑器内容为原始内容
+      //   editorRef.value.setContent(originalContent.value);
+      // }
+      // emit('close');
+    };
+
+    const { prefixCls } = useDesign('tinymce-container');
+
+    const appStore = useAppStore();
+
+    const tinymceContent = computed(() => props.tinymceProps.modelValue);
+
+    // const containerWidth = computed(() => {
+    //   const width = props.width;
+    //   if (isNumber(width)) {
+    //     return `${width}px`;
+    //   }
+    //   return width;
+    // });
+
+    const skinName = computed(() => {
+      return appStore.getDarkMode === 'light' ? 'oxide' : 'oxide-dark';
+    });
+
+    const contentCssName = computed(() => {
+      return appStore.getDarkMode === 'light' ? 'default' : 'dark';
+    });
+
+    const langName = computed(() => {
+      const lang = useLocale().getLocale.value;
+      if (lang === 'zh_CN') {
+        return 'zh-Hans';
+      }
+      return lang;
+    });
+
+    const initOptions = computed((): RawEditorOptions => {
+      const { options, toolbar, plugins } = props.tinymceProps;
+      const publicPath = import.meta.env.VITE_PUBLIC_PATH || '/';
+      return {
+        selector: `#${unref(tinymceId)}`,
+        // height,
+        toolbar,
+        menubar: false,
+        plugins,
+        language_url: publicPath + 'resource/tinymce/langs/' + langName.value + '.js',
+        language: langName.value,
+        branding: false,
+        default_link_target: '_blank',
+        link_title: false,
+        object_resizing: false,
+        auto_focus: true,
+        skin: skinName.value,
+        promotion: false,
+        model_url: publicPath + 'resource/tinymce/models/dom/model.min.js',
+        skin_url: publicPath + 'resource/tinymce/skins/ui/' + skinName.value,
+        content_css:
+          publicPath +
+          'resource/tinymce/skins/content/' +
+          contentCssName.value +
+          '/content.min.css',
+        ...options,
+        elementpath: false, // 禁用路径条
+        // setup: (editor: Editor) => {
+        //   editorRef.value = editor;
+        //   editor.on('init', (e) => initSetup(e));
+        // },
+        setup: (editor: Editor) => {
+          editorRef.value = editor;
+          editor.on('init', (e) => initSetup(e));
+
+          // 监听多种可能变更内容的事件
+          editor.on('change keyup undo redo', () => {
+            emit('update:modelValue', editor.getContent());
+          });
+        },
+      };
+    });
+
+    // 监听编辑器内容变化事件
+    // editorRef.value?.on('change', () => {
+    //   const content = editorRef.value?.getContent({ format: attrs.outputFormat });
+    //   console.log('first', editorRef)
+    //   // emit('update:modelValue', content);
+    //   // emit('change', content);
+    // });
+    const editorContent = computed(() => editorRef.value?.getContent());
+    // console.log('first=============', editorRef)
+
+    const disabled = computed(() => {
+      const { options } = props.tinymceProps;
+      const getdDisabled = options && Reflect.get(options, 'readonly');
+      const editor = unref(editorRef);
+      if (editor) {
+        editor.mode.set(getdDisabled ? 'readonly' : 'design');
+      }
+      return getdDisabled ?? false;
+    });
+
+    // watch(
+    //   () => attrs.disabled,
+    //   () => {
+    //     const editor = unref(editorRef);
+    //     if (!editor) {
+    //       return;
+    //     }
+    //     editor.mode.set(attrs.disabled ? 'readonly' : 'design');
+    //   },
+    // );
+
+    onMountedOrActivated(() => {
+      if (!initOptions.value.inline) {
+        tinymceId.value = buildShortUUID('tiny-vue');
+      }
+      nextTick(() => {
+        setTimeout(() => {
+          initEditor();
+        }, 30);
+      });
+    });
+
+    // onBeforeUnmount(() => {
+    //   destory();
+    // });
+    onBeforeUnmount(() => {
+      if (editorRef.value) {
+        editorRef.value.remove();
+        editorRef.value = null;
+      }
+    });
+
+    onDeactivated(() => {
+      destory();
+    });
+
+    function destory() {
+      if (tinymce !== null) {
+        tinymce?.remove?.(unref(initOptions).selector!);
+      }
+    }
+
+    function initEditor() {
+      const el = unref(elRef);
+      if (el) {
+        el.style.visibility = '';
+      }
+      tinymce
+        .init(unref(initOptions))
+        .then((editor) => {
+          emit('inited', editor);
+        })
+        .catch((err) => {
+          emit('init-error', err);
+        });
+    }
+
+    function initSetup(e) {
+      const editor = unref(editorRef);
+      if (!editor) {
+        return;
+      }
+      const value = props.tinymceProps.modelValue || '';
+
+      editor.setContent(value);
+      bindModelHandlers(editor);
+      // bindHandlers(e, attrs, unref(editorRef));
+    }
+
+    function setValue(editor: Recordable, val?: string, prevVal?: string) {
+      if (
+        editor &&
+        typeof val === 'string' &&
+        val !== prevVal &&
+        val !== editor.getContent({ format: attrs.outputFormat })
+      ) {
+        editor.setContent(val);
+      }
+    }
+
+    function bindModelHandlers(editor: any) {
+      const modelEvents = attrs.modelEvents ? attrs.modelEvents : null;
+      const normalizedEvents = Array.isArray(modelEvents) ? modelEvents.join(' ') : modelEvents;
+
+      // watch(
+      //   () => props.modelValue as string,
+      //   (val, prevVal) => {
+      //     setValue(editor, val, prevVal);
+      //   },
+      // );
+
+      // watch(
+      //   () => props.value as string,
+      //   (val, prevVal) => {
+      //     setValue(editor, val, prevVal);
+      //   },
+      //   {
+      //     immediate: true,
+      //   },
+      // );
+
+      editor.on(normalizedEvents ? normalizedEvents : 'change keyup undo redo', () => {
+        const content = editor.getContent({ format: attrs.outputFormat });
+        emit('update:modelValue', content);
+        emit('change', content);
+      });
+
+      // editor.on('FullscreenStateChanged', (e) => {
+      //   fullscreen.value = e.state;
+      // });
+    }
+
+    function handleImageUploading(name: string) {
+      const editor = unref(editorRef);
+      if (!editor) {
+        return;
+      }
+      editor.execCommand('mceInsertContent', false, getUploadingImgName(name));
+      const content = editor?.getContent() ?? '';
+      setValue(editor, content);
+    }
+
+    function handleDone(name: string, url: string) {
+      const editor = unref(editorRef);
+      if (!editor) {
+        return;
+      }
+      const content = editor?.getContent() ?? '';
+      const val = content?.replace(getUploadingImgName(name), `<img src="${url}"/>`) ?? '';
+      setValue(editor, val);
+    }
+
+    function getUploadingImgName(name: string) {
+      return `[uploading:${name}]`;
+    }
+
+    return {
+      prefixCls,
+      // containerWidth,
+      initOptions,
+      tinymceContent,
+      elRef,
+      tinymceId,
+      handleImageUploading,
+      handleDone,
+      editorRef,
+      // fullscreen,
+      disabled,
+      visible,
+      handleSave,
+      handleCancel,
+      editorContent,
+      originalContent
+    };
+  },
+});
+
+// const tinymceProps = {
+//   options: {
+//     type: Object as PropType<Partial<RawEditorOptions>>,
+//     default: () => ({}),
+//   },
+//   value: {
+//     type: String,
+//   },
+
+//   // toolbar: {
+//   //   type: Array as PropType<string[]>,
+//   //   default: toolbar,
+//   // },
+//   toolbar: {
+//     type: Array as PropType<string[]>,
+//     default: () => [
+//       'undo redo | bold italic underline | forecolor | bullist numlist | hr | emoticons'
+//     ],
+//   },
+//   plugins: {
+//     type: Array as PropType<string[]>,
+//     default: plugins,
+//   },
+//   //  plugins: {
+//   //   type: Array as PropType<string[]>,
+//   //   default: () => [
+//   //     'advlist autolink lists link pagebreak',
+//   //   ],
+//   // },
+//   modelValue: {
+//     type: String,
+//   },
+//   height: {
+//     type: [Number, String] as PropType<string | number>,
+//     required: false,
+//     default: 600,
+//   },
+//   width: {
+//     type: [Number, String] as PropType<string | number>,
+//     required: false,
+//     default:700,
+//   },
+//   showImageUpload: {
+//     type: Boolean,
+//     default: false,
+//   },
+// };
+
+
+</script>
+
+<style lang="less" scoped></style>
+
+<style scoped lang="less">
+@prefix-cls: ~'@{namespace}-tinymce-container';
+
+.@{prefix-cls} {
+  position: relative;
+  line-height: normal;
+  height: 600px;
+  textarea {
+    visibility: hidden;
+    z-index: -1;
+  }
+}
+</style>

+ 166 - 0
src/views/wechat/batch_msg/components/timedSending.vue

@@ -0,0 +1,166 @@
+<template>
+  <a-collapse
+    v-model:activeKey="activeKeyTiem"
+    collapsible="header"
+    :bordered="false"
+    class="time-collapse"
+  >
+    <a-collapse-panel key="time1" class="custom-collapse-panel">
+      <template #header>
+        <div @click.stop="toggleTimeoutAction" style="display: flex; align-items: center">
+          <MinusCircleOutlined />
+          &nbsp;&nbsp;
+          <span>进入后, 第</span>
+          &nbsp;&nbsp;
+          <a-input-number id="inputNumber" v-model:value="dayValue" :min="1" :max="10">
+            <template #upIcon>
+              <ArrowUpOutlined />
+            </template>
+            <template #downIcon>
+              <ArrowDownOutlined />
+            </template>
+          </a-input-number>
+          &nbsp;&nbsp;
+          <span>天,</span>
+          &nbsp;&nbsp;
+          <a-time-picker
+            v-model:value="strValue"
+            placeholder="选择开始时间(时分秒)"
+            value-format="HH:mm:ss"
+          />
+          &nbsp;&nbsp;
+          <span>发送</span>
+          &nbsp;&nbsp;
+          <a-tooltip>
+            <template #title>注: 第一天指当天</template>
+            <QuestionCircleOutlined />
+          </a-tooltip>
+        </div>
+      </template>
+      <a-divider style="border-color: #828eaf; width: 750px" dashed />
+      <component :is="MsgContant" />
+    </a-collapse-panel>
+  </a-collapse>
+</template>
+
+<script lang="ts">
+  import { defineComponent, ref, reactive, toRefs, watch } from 'vue';
+  import {
+    Input,
+    InputNumber,
+    Divider,
+    Collapse,
+    Tooltip,
+    DatePicker,
+  } from 'ant-design-vue';
+  import {
+    InfoCircleOutlined,
+    CommentOutlined,
+    TagsOutlined,
+    MinusCircleOutlined,
+    ArrowUpOutlined,
+    ArrowDownOutlined,
+    QuestionCircleOutlined,
+  } from '@ant-design/icons-vue';
+  import type { FormInstance } from 'ant-design-vue';
+  // import HtmlTextarea from './htmlTextarea.vue';
+  import MsgContant from './msgContant.vue';
+  interface SelectOption {
+    label: string;
+    value: string;
+  }
+
+  export default defineComponent({
+    name: 'TimedSending',
+    components: {
+      'a-input': Input,
+      'a-input-number': InputNumber,
+      'a-collapse': Collapse,
+      'a-collapse-panel': Collapse.Panel,
+      'a-tooltip': Tooltip,
+      'a-divider': Divider,
+      'a-time-picker': DatePicker.TimePicker,
+      ArrowUpOutlined,
+      ArrowDownOutlined,
+      MinusCircleOutlined,
+      QuestionCircleOutlined,
+      // HtmlTextarea,
+      MsgContant,
+    },
+
+    setup(props, { emit }) {
+      const dayValue = ref<number>(1);
+      const strValue = ref<string>('09:00:00');
+      // 定义动态组件插槽或具名插槽
+      // const MsgContantSlot = MsgContant;
+      // 定义选项数组
+      
+      // const form = reactive({
+      //   phaseName: '',
+      //   condition: 'tags',
+      //   conditionRelation: 'all',
+      // });
+      // const initialForm = reactive({
+      //   phaseName: '',
+      //   condition: 'tags',
+      //   conditionRelation: 'all',
+      // });
+      const taskForm = reactive({
+        taskType: 'sendMessage',
+        sendType: 'immediate',
+      });
+      // const initialTaskForm = reactive({
+      //   taskType: 'sendMessage',
+      //   sendType: 'immediate',
+      // });
+      const activeKey = ref<number | string>('1');
+      const activeKeyTiem = ref<number | string>('time1');
+      const msgAction = ref(taskForm.taskType === 'sendMessage');
+
+      
+      const toggleTimeoutAction = () => {
+        // 切换折叠面板的动作
+        if (taskForm.taskType === 'scheduled') {
+          msgAction.value = !msgAction.value;
+          if (msgAction.value) {
+            activeKeyTiem.value = ['time1'];
+          } else {
+            activeKeyTiem.value = [];
+          }
+        }
+      };
+      // 切换超时动作
+      // const toggleTimeoutAction = () => {
+      //   activeKeyTiem.value = activeKeyTiem.value ? '' : 'time1';
+      // };
+
+      return {
+        taskForm,
+        activeKey,
+        activeKeyTiem,
+        dayValue,
+        strValue,
+        msgAction,
+        toggleTimeoutAction,
+        MsgContant,
+        // MsgContantSlot,
+      };
+    },
+  });
+</script>
+<style scoped lang="less">
+  .custom-collapse {
+    background-color: #f3faef;
+    color: #333;
+    border-radius: 0px;
+    border: none;
+    margin-top: 20px;
+  }
+  .time-collapse {
+    width: 100%;
+    background-color: #f9f8f8;
+  }
+  .custom-collapse-panel {
+    border: none;
+  }
+</style>

+ 30 - 0
src/views/wechat/batch_msg/components/tinymce.ts

@@ -0,0 +1,30 @@
+export const plugins = [
+  'advlist',
+  'anchor',
+  'autolink',
+  'autosave',
+  'autoresize',
+  'code',
+  'codesample',
+  'directionality',
+  'fullscreen',
+  'insertdatetime',
+  'link',
+  'lists',
+  'media',
+  'nonbreaking',
+  'pagebreak',
+  'preview',
+  'save',
+  'searchreplace',
+  'visualblocks',
+  'visualchars',
+  'wordcount',
+  'image',
+  'emoticons'
+];
+
+export const toolbar = [
+  'fontsizeselect lineheight searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent  blockquote undo redo removeformat subscript superscript code codesample',
+  'hr bullist numlist link  preview anchor pagebreak insertdatetime media image forecolor backcolor fullscreen emoticons',
+];

+ 749 - 0
src/views/wechat/batch_msg/components/userPhasesDrawer.vue

@@ -0,0 +1,749 @@
+<template>
+  <!-- <Spin class="loading-style" :spinning="loading" tip="Loading...">
+    <div v-if="!loading"> -->
+  <Drawer
+    :open="visible"
+    title="群发消息"
+    width="900"
+    class="user-phase-drawer"
+    @close="handleClose"
+  >
+    <Steps :current="currentStep" size="default" class="step-style">
+      <Step class="custom-step" title="设置消息条件"></Step>
+      <Step class="custom-step" title="配置消息内容"></Step>
+    </Steps>
+    <!-- <div v-if="isDataLoaded"> -->
+    <div v-if="currentStep === 0" class="step-content">
+      <Form
+        :model="form"
+        :label-col="{ span: 3 }"
+        :wrapper-col="{ span: 19 }"
+        ref="formRef"
+        :rules="rules"
+      >
+        <FormItem
+          label="任务名称"
+          name="phaseName"
+          :rules="[{ required: true, message: '请输入任务名称' }]"
+        >
+          <Input
+            :disabled="btnDisabled"
+            v-model:value="form.phaseName"
+            placeholder="请输入任务名称"
+            :maxlength="20"
+            show-count
+          />
+        </FormItem>
+        <FormItem label="筛选联系人"></FormItem>
+        <!-- <div class="description">
+              <InfoCircleOutlined />
+              主要用于执行任务的触发的条件,满足条件才可进入群发,转人工的动作
+            </div> -->
+        <!-- <FormItem
+              label="选择条件"
+              name="conditionType"
+              :rules="[{ required: true, message: '请选择条件' }]"
+            >
+              <Radio.Group v-model:value="form.conditionType" :disabled="btnDisabled">
+                <Radio value="1">客户标签</Radio>
+              </Radio.Group>
+            </FormItem> -->
+        <FormItem
+          label="条件关系"
+          name="conditionOperator"
+          :rules="[{ required: true, message: '请选择条件关系' }]"
+        >
+          <Radio.Group v-model:value="form.conditionOperator" :disabled="btnDisabled">
+            <Radio value="1">满足所有条件</Radio>
+            <Radio value="2">满足一个条件即可</Radio>
+          </Radio.Group>
+        </FormItem>
+        <FormItem
+          label="触发条件"
+          name="conditionList"
+          :rules="[{ required: true, message: '请选择触发条件' }]"
+        >
+          <Button
+            :disabled="btnDisabled"
+            @click="addContent"
+            style="
+              box-shadow: none;
+              color: #307ef2;
+              display: inline-block;
+              border: none;
+              background: none;
+            "
+          >
+            <PlusCircleOutlined style="color: #307ef2; display: inline-block" />
+            添加
+          </Button>
+          <div v-if="form.conditionList.length > 0" class="concrete-content-container">
+            <div
+              v-for="(item, index) in form.conditionList"
+              :key="index"
+              class="concrete-content-item"
+            >
+              <span>标签</span>
+              <FormItem :name="'equal-' + index">
+                <Select
+                  :disabled="btnDisabled"
+                  v-model:value="item.equal"
+                  :options="equalList"
+                  :style="{ width: '80px', margin: '0 5px' }"
+                ></Select>
+              </FormItem>
+              <FormItem :name="'labelIdList-' + index" :style="{ display: 'contents' }">
+                <Select
+                  :disabled="btnDisabled"
+                  v-model:value="item.labelIdList"
+                  :options="actionLabel"
+                  allowClear
+                  mode="multiple"
+                  size="middle"
+                  placeholder="请选择"
+                  :style="{ width: '240px', margin: '0 5px' }"
+                  :max-tag-count="1"
+                ></Select>
+              </FormItem>
+              <Button
+                style="
+                  color: #307ef2;
+                  box-shadow: none;
+                  display: inline-block;
+                  border: none;
+                  background: none;
+                "
+                :disabled="btnDisabled"
+                v-if="form.conditionList.length > 1"
+                @click="removeContent(index)"
+              >
+                <CloseCircleOutlined style="color: #d3d2d3" />
+              </Button>
+            </div>
+          </div>
+        </FormItem>
+        <FormItem label="选择发送时间"></FormItem>
+        <FormItem
+          label="发送方式"
+          name="conditionOperator"
+          :rules="[{ required: true, message: '请选择发送方式' }]"
+        >
+          <Radio.Group v-model:value="form.conditionOperator" :disabled="btnDisabled">
+            <Radio value="1">立即发送</Radio>
+            <Radio value="2">定时发送</Radio>
+          </Radio.Group>
+        </FormItem>
+        <FormItem
+          label="发送时间"
+          name="conditionOperator"
+          :rules="[{ required: true, message: '请选择发送时间' }]"
+        >
+          <DatePicker
+            v-model:value="form.dateRange"
+            placeholder="请选择发送时间"
+            format="YYYY-MM-DD HH:mm:ss"
+            :disabled-date="disabledDate"
+            :show-time="{ defaultValue: dayjs('00:00:00', 'HH:mm:ss') }"
+          />
+        </FormItem>
+      </Form>
+    </div>
+    <div v-if="currentStep === 1" class="step-content">
+      <Form
+        :model="taskForm"
+        :label-col="{ span: 3 }"
+        :wrapper-col="{ span: 19 }"
+        ref="taskFormRef"
+      >
+        <FormItem label="执行任务" name="taskType">
+          <Button
+            :disabled="btnDisabled"
+            :class="isMsgAction ? 'action-btn' : 'btn-style'"
+            @click="setTaskType('sendMessage')"
+          >
+            <template #icon>
+              <CommentOutlined />
+            </template>
+            发消息
+          </Button>
+          <!-- <Button
+            :disabled="btnDisabled"
+            :class="isTagAction ? 'action-btn' : 'btn-style'"
+            @click="setTaskType('tag')"
+          >
+            <template #icon>
+              <TagsOutlined />
+            </template>
+            打标签
+          </Button> -->
+        </FormItem>
+        <!-- 发消息 -->
+        <Collapse
+          v-show="isMsgAction"
+          v-model:activeKey="activeKey"
+          collapsible="header"
+          class="custom-collapse"
+        >
+          <Collapse.Panel key="1" class="custom-collapse-panel">
+            <template #header>
+              <div @click.stop="toggleTimeoutAction" style="display: flex; align-items: center">
+                <MessageOutlined style="color: #83b14e" />
+                &nbsp;&nbsp;
+                <span>发消息</span>
+              </div>
+            </template>
+            <div @click.stop>
+              <FormItem label="发送方式" name="sendType">
+                <Radio.Group v-model:value="taskForm.sendType" :disabled="btnDisabled">
+                  <Radio value="immediate">立即发送</Radio>
+                  <!-- <Radio value="scheduled">定时发送</Radio> -->
+                </Radio.Group>
+              </FormItem>
+              <!-- 根据发送方式显示 MsgContant 或在折叠面板中展示 -->
+              <div v-if="taskForm.sendType === 'immediate'">
+                <FormItem
+                  label=""
+                  name="actionMessage"
+                  :rules="[{ required: true, message: '请输入消息内容' }]"
+                >
+                  <MsgContant v-model:value="taskForm.actionMessage" :msgDisabled="btnDisabled" />
+                </FormItem>
+                <!-- <component :is="MsgContant" /> -->
+              </div>
+              <TimedSending v-else />
+            </div>
+          </Collapse.Panel>
+        </Collapse>
+        <!-- 打标签 -->
+        <!-- <Collapse
+          v-show="isTagAction"
+          v-model:activeKey="activeKeyTag"
+          collapsible="header"
+          class="custom-collapse-tag"
+        >
+          <Collapse.Panel key="1" class="tag-collapse-panel">
+            <template #header>
+              <div @click.stop="toggleTimeoutAction" style="display: flex; align-items: center">
+                <TagOutlined style="color: #6f9ce5" />
+                &nbsp;&nbsp;
+                <span>打标签</span>
+              </div>
+            </template>
+            <div @click.stop>
+              <FormItem
+                label="用户进入当前阶段时,可以打上标签"
+                name="tagging"
+                v-if="msgAction"
+                :labelCol="{ span: 8 }"
+              >
+                <Select
+                  :disabled="btnDisabled"
+                  v-model:value="taskForm.tagValue"
+                  :options="actionLabel"
+                  allowClear
+                  mode="multiple"
+                  size="middle"
+                  placeholder="请选择"
+                  :style="{ width: '240px', margin: '0 5px' }"
+                  :max-tag-count="1"
+                ></Select>
+                <span v-show="taskForm.tagValue.length === 0" class="warning-style">
+                  请选择标签
+                </span>
+              </FormItem>
+            </div>
+          </Collapse.Panel>
+        </Collapse> -->
+      </Form>
+    </div>
+    <div class="steps-action">
+      <Button v-if="currentStep > 0" @click="prevStep">上一步</Button>
+      <Button type="primary" v-if="currentStep < 1" @click="nextStep">下一步</Button>
+      <Button type="primary" v-if="currentStep === 1" @click="submitForm">完成</Button>
+      <Button @click="handleClose">取消</Button>
+    </div>
+    <!-- </div> -->
+  </Drawer>
+  <!-- </div>
+  </Spin> -->
+</template>
+
+<script setup lang="ts">
+  import { unref, defineProps, defineEmits, ref, toRefs, watch, reactive, onMounted } from 'vue';
+  import {
+    Drawer,
+    Form,
+    FormItem,
+    Input,
+    Radio,
+    Button,
+    Steps,
+    Step,
+    Collapse,
+    Select,
+    Spin,
+    message,
+    DatePicker,
+  } from 'ant-design-vue';
+  import {
+    InfoCircleOutlined,
+    CommentOutlined,
+    TagsOutlined,
+    PlusCircleOutlined,
+    CloseCircleOutlined,
+    MessageOutlined,
+    TagOutlined,
+  } from '@ant-design/icons-vue';
+  import type { FormInstance } from 'ant-design-vue';
+  import { getLabelSelectList } from '@/api/wechat/label';
+  // import HtmlTextarea from './htmlTextarea.vue';
+  import dayjs, { Dayjs } from 'dayjs';
+  import MsgContant from './msgContant.vue';
+  import TimedSending from './timedSending.vue';
+
+  import {
+    addSopTaskStage,
+    getSopStageDetailById,
+    getSopStageList,
+    editSopTaskStage,
+  } from '@/api/wechat/sopTask';
+  // import { useStore } from '../stores/index';
+  // let store = useStore();
+  // let {
+  //   sopLabelList,
+  //   sopStageId,
+  //   sopTaskId,
+  //   sopTaskDtat,
+  //   setSopStageId,
+  //   setSopStageList,
+  //   setStageDrawer,
+  // } = store;
+
+  // name: 'UserPhasesDrawer',
+  const props = defineProps<{
+    visible: boolean;
+    // stageId: {
+    //   type: Number; // 支持 number 和 ref 类型
+    //   required: true;
+    // };
+    btnDisabled: {
+      type: Boolean;
+      default: false;
+    };
+  }>();
+  const emit = defineEmits<{
+    (event: 'update:open', value: boolean): void;
+    (event: 'update:getStageList', value: { value: boolean; id: number }): void;
+  }>();
+  const { visible, btnDisabled } = toRefs(props);
+  const loading = ref(false);
+  const currentStep = ref(0);
+  const formRef = ref<FormInstance | null>(null);
+  const taskFormRef = ref<FormInstance | null>(null);
+  const dayValue = ref<number>(1);
+  const strValue = ref<string>('09:00:00');
+  // const tagValue = ref<number>();
+  const labelIsShow = ref<boolean>(false);
+  // 定义动态组件插槽或具名插槽
+  // const MsgContantSlot = MsgContant;
+  // 定义选项数组
+  // const actionLabel = sopLabelList;
+  const actionLabel = ref([]);
+  const equalList = [
+    { label: '是', value: 1 },
+    { label: '不是', value: 2 },
+  ];
+  const form = reactive({
+    phaseName: '',
+    conditionType: '1', //1:客户标签2:基本信息
+    conditionOperator: '1',
+    conditionList: [],
+    dateRange: '',
+  });
+  const rules = {
+    phaseName: [{ required: true, message: '请输入阶段名称', trigger: 'blur' }],
+    conditionOperator: [{ required: true, message: '请选择条件关系', trigger: 'change' }],
+    conditionList: [{ required: true, message: '请选择', trigger: 'change' }],
+    // 'conditionList[0].labelIdList': [{ required: true, message: '请输入', trigger: 'blur' }],
+  };
+  const initialForm = reactive({
+    phaseName: '',
+    conditionType: '1',
+    conditionOperator: '1',
+    conditionList: [],
+  });
+  const taskForm = reactive({
+    taskType: 'sendMessage',
+    sendType: 'immediate',
+    actionMessage: [{ type: 1, content: '' }],
+    tagValue: [],
+  });
+  const initialTaskForm = reactive({
+    taskType: 'sendMessage',
+    sendType: 'immediate',
+    actionMessage: [{ type: 1, content: '' }],
+    tagValue: [],
+  });
+  const activeKey = ref<number | string>('1');
+  const activeKeyTiem = ref<number | string>('time1');
+  const activeKeyTag = ref<number | string>('1');
+  // const msgAction = ref<boolean>(false);
+  const msgAction = ref(taskForm.taskType === 'sendMessage');
+  const isMsgAction = ref<boolean>(true);
+  const isTagAction = ref<boolean>(false);
+  // const isDataLoaded = ref(false); // 控制表单的渲染
+  onMounted(async () => {
+    let res = await getLabelSelectList({ page: 1, pageSize: 1000, type: 1 });
+    actionLabel.value = res.data;
+    // if (unref(stageId)) {
+    //   getStageDetail(unref(stageId));
+    // } else {
+    //   console.log('新建----');
+    //   resetForms();
+    // }
+  });
+
+  const disabledDate = (current: Dayjs) => {
+    return current && current < dayjs().endOf('day');
+  };
+
+  function nextStep() {
+    if (currentStep.value === 0) {
+      formRef.value
+        ?.validate()
+        .then((res) => {
+          const allValid = res.conditionList.every((item) => item.labelIdList.length > 0);
+          if (!allValid) {
+            message.warning('请选择完整的标签!');
+            return;
+          } else {
+            currentStep.value += 1;
+          }
+        })
+        .catch((error) => {
+          console.error('校验失败:', error);
+        });
+    }
+  }
+
+  function prevStep() {
+    if (currentStep.value > 0) {
+      currentStep.value -= 1;
+    }
+  }
+
+  function addContent() {
+    labelIsShow.value = true;
+    form.conditionList.push({ equal: 1, labelIdList: [] });
+  }
+
+  function removeContent(index: number) {
+    form.conditionList.splice(index, 1);
+  }
+
+  function resetForms() {
+    Object.assign(form, initialForm);
+    Object.assign(taskForm, initialTaskForm);
+    currentStep.value = 0;
+  }
+
+  async function getStageDetail(id) {
+    // 模拟 API 调用
+    let data = await getSopStageDetailById({ id });
+    // 填充 form 数据
+    Object.assign(form, {
+      phaseName: data.data.name,
+      // conditionType: String(data.data.conditionType),
+      conditionOperator: String(data.data.conditionOperator),
+      conditionList: data.data.conditionList || [],
+    });
+    const taskTypes = [];
+    if (data.data.actionMessage.length > 0) {
+      taskTypes.push('sendMessage');
+    }
+    if (data.data.actionLabel.length > 0) {
+      taskTypes.push('tag');
+    }
+    // 填充 taskForm 数据
+    Object.assign(taskForm, {
+      taskType: taskTypes,
+      sendType: 'immediate',
+      actionMessage: data.data.actionMessage || [{ type: 1, content: '' }],
+      tagValue: data.data.actionLabel || [],
+    });
+    // 根据 taskType 设置 isMsgAction 和 isTagAction
+    isMsgAction.value = taskTypes.includes('sendMessage');
+    isTagAction.value = taskTypes.includes('tag');
+  }
+
+  function processArray(array: any[]) {
+    array.forEach((item) => {
+      if (item.type === 2 && Array.isArray(item.content)) {
+        const contentString = item.content[0]; // 假设content只有一个元素
+        const filename = contentString.split('/').pop(); // 获取最后一个/后的值
+        item.content = contentString; // 将content数组转成字符串
+        item.meta = { filename }; // 添加meta对象
+      }
+    });
+    return array;
+  }
+  async function submitForm() {
+    if (!btnDisabled.value) {
+      // 提交表单逻辑 conditionType,
+      const { phaseName, conditionOperator, conditionList } = form;
+      const { taskType, sendType, actionMessage, tagValue } = taskForm;
+      processArray(actionMessage);
+      const requestData = {
+        name: phaseName,
+        // conditionType: ~~conditionType,
+        conditionType: 1,
+        conditionOperator: ~~conditionOperator,
+        conditionList,
+        taskType,
+        sendType,
+        actionMessage,
+        actionLabel: tagValue,
+      };
+      const allData = actionMessage.every((item) => item.content.trim() !== '');
+      if ((isMsgAction.value && !allData) || (isTagAction.value && tagValue.length === 0)) {
+        return;
+      }
+      if (unref(stageId)) {
+        // 编辑
+        loading.value = true;
+        console.log('编辑阶段');
+        const id = unref(stageId);
+        let response = await editSopTaskStage({ id, ...requestData });
+        if (response && response.code == 0) {
+          loading.value = false;
+          emit('update:getStageList', { value: true, id });
+          console.log('编辑成功');
+          handleClose();
+        } else {
+          console.error(response.code, '编辑失败');
+        }
+      } else {
+        // 新增
+        // console.log('新增阶段', sopTaskId, store.sopTaskId);
+        loading.value = true;
+        let response = await addSopTaskStage({ taskId: 1, ...requestData });
+        if (response && response.code === 0) {
+          console.log('新增成功');
+          loading.value = false;
+          // getStageList()
+          emit('update:getStageList', { value: true, id: response.data });
+          // setSopStageId(response.data); // 更新 sopStageId
+          handleClose();
+        } else {
+          loading.value = false;
+          console.error('新增失败');
+        }
+      }
+    } else {
+      handleClose();
+    }
+  }
+
+  // 打开或关闭抽屉
+  function handleClose() {
+    if (visible.value) {
+      resetForms();
+    }
+    emit('update:open', false);
+  }
+
+  // function resetForms () {
+  //   Object.assign(form, initialForm);
+  //   Object.assign(taskForm, initialTaskForm);
+  //   msgAction.value = '';
+  //   isMsgAction.value = true;
+  //   isTagAction.value = false;
+  //   dayValue.value = 1;
+  //   strValue.value = '09:00:00';
+  //   // tagValue.value = [];
+  // };
+
+  // 在打开抽屉时保存初始状态
+  watch(visible, (newVal) => {
+    if (newVal) {
+      Object.assign(initialForm, { ...form });
+      Object.assign(initialTaskForm, { ...taskForm });
+    }
+  });
+
+  // 设置任务类型
+  function setTaskType(type: string) {
+    taskForm.taskType = type;
+    if (type === 'tag') {
+      isTagAction.value = !isTagAction.value;
+      msgAction.value = type === 'tag';
+      if (!isTagAction.value) {
+        taskForm.taskType = '';
+        taskForm.tagValue = [];
+      }
+    } else if (type === 'sendMessage') {
+      msgAction.value = type === 'sendMessage';
+      isMsgAction.value = !isMsgAction.value;
+      if (!isMsgAction.value) {
+        taskForm.taskType = '';
+        taskForm.actionMessage = [{ type: 1, content: '' }];
+      }
+    }
+    // taskForm.taskType = type;
+    // msgAction.value = type === 'sendMessage';
+    // isMsgAction.value = type === 'sendMessage';
+    // isTagAction.value = type === 'tag';
+  }
+  // const setTaskType = (taskType: string) => {
+  // if(taskType === 'tag'){
+  //   isTagAction.value = !isTagAction.value;
+  // }else if(taskType === 'sendMessage'){
+  //   isMsgAction.value = !isMsgAction.value;
+  // }
+  // if (taskForm.taskType === taskType) {
+  //   // Toggle the state if the taskType is already the same
+  //   msgAction.value = !msgAction.value;
+  //   activeKey.value = msgAction.value ? ['1'] : [];
+  // } else {
+  //   taskForm.taskType = taskType;
+  //   msgAction.value = taskType === 'sendMessage';
+  //   activeKey.value = msgAction.value ? ['1'] : [];
+  // }
+  // };
+
+  function toggleTimeoutAction() {
+    // 切换折叠面板的动作
+    if (taskForm.taskType === 'scheduled') {
+      msgAction.value = !msgAction.value;
+      if (msgAction.value) {
+        activeKeyTiem.value = ['time1'];
+      } else {
+        activeKeyTiem.value = [];
+      }
+    }
+  }
+  // 切换超时动作
+  // const toggleTimeoutAction = () => {
+  //   activeKeyTiem.value = activeKeyTiem.value ? '' : 'time1';
+  // };
+</script>
+<style scoped lang="less">
+  .user-phase-drawer {
+    width: 900px;
+    height: 100vh;
+  }
+  .concrete-content-container {
+    border: 0.5px solid #c0ccda;
+    border-radius: 2px;
+    padding: 16px 16px 16px 16px;
+  }
+  .concrete-content-item {
+    display: flex;
+    align-items: center;
+    margin-bottom: 8px;
+  }
+  .concrete-content-item a-input {
+    flex: 1;
+    margin-right: 8px;
+  }
+  .custom-collapse-tag {
+    background-color: #ecf5fc;
+    color: #333;
+    border-radius: 0px;
+    border: none;
+    margin: 30px 0 90px 0;
+  }
+  .custom-collapse-panel {
+    border: none !important;
+    border-radius: 0px;
+  }
+  .step-content {
+    margin-top: 20px;
+  }
+  .steps-action {
+    margin-top: 20px;
+    display: flex;
+    justify-content: flex-end;
+    gap: 10px;
+  }
+  .step-style {
+    margin: 0 auto;
+    width: 60%;
+  }
+  .custom-step .ant-steps-item-title {
+    font-size: 14px;
+  }
+  .description {
+    width: calc(100% - 90px);
+    margin: 0px 0 30px 20px;
+    font-size: 14px;
+    padding: 10px 15px;
+    color: #666;
+    background-color: #f9f9f9;
+  }
+  .description .anticon {
+    margin-right: 8px;
+  }
+  .btn-style {
+    margin: 0 15px;
+    border-color: #cececd;
+    color: #606266;
+  }
+  .action-btn {
+    margin: 0 15px;
+    color: #4096ff;
+    border-color: #4096ff;
+  }
+  .btn-style:hover {
+    margin: 0 15px;
+    border-color: #cececd;
+    color: #606266;
+  }
+  a-textarea {
+    width: 100%;
+  }
+  .highlighted-text {
+    color: #1c53d9;
+    margin: 0 5px;
+    background-color: #e8edfb;
+    padding: 3px 5px;
+  }
+  .custom-collapse {
+    background-color: #f3faef;
+    color: #333;
+    border-radius: 0px;
+    border: none;
+    margin-top: 20px;
+  }
+  .tag-collapse-panel {
+    border: none;
+  }
+  .loading-style {
+    width: 100%;
+    height: 100%;
+    z-index: 1;
+    position: fixed !important;
+    top: 0;
+    left: 0;
+    background: rgba(255, 255, 255, 0.7);
+    // background: yellow;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+  }
+  .ant-spin-nested-loading > div > .ant-spin {
+    max-height: 100% !important;
+    background: rgba(255, 255, 255, 0.7);
+  }
+  .concrete-content-container .ant-form-item {
+    display: contents !important;
+  }
+  .warning-style {
+    color: #ff4949;
+    font-size: 12px;
+    margin-left: -240px;
+    position: absolute;
+    bottom: -20px;
+  }
+</style>

+ 80 - 22
src/views/wechat/batch_msg/index.vue

@@ -8,33 +8,66 @@
         </a-button>
       </template>
       <template #bodyCell="{ column, record }">
-        <template v-if="column.key === 'action'"></template>
+        <template v-if="column.key === 'action'">
+          <TableAction
+            :actions="[
+              {
+                icon: 'clarity:note-edit-line',
+                onClick: handleStart.bind(null, record),
+                tooltip: '编辑',
+              },
+              // {
+              //   icon: 'ant-design:login-outlined',
+              //   onClick: handleSend.bind(null, record),
+              //   tooltip: '暂停',
+              // },
+              {
+                icon: 'ant-design:send-outlined',
+                onClick: handleSend.bind(null, record),
+                tooltip: '发送记录',
+                ifShow: true,
+              },
+            ]"
+          />
+        </template>
       </template>
     </BasicTable>
-    <BatchMsgDrawer @register="registerDrawer" @success="handleSuccess" />
+    <!-- <BatchMsgDrawer @register="registerDrawer" @success="handleSuccess" /> -->
+    <!--  @update:getStageList="getStageList"
+        :stageId="stageItemId" v-if="!stageLoading" -->
+    <!-- <Spin class="spin-style" :spinning="stageLoading" tip="Loading..."> -->
+      <UserPhasesDrawer
+        :visible="drawerVisible"
+        @update:open="drawerVisible = $event"
+        :btnDisabled="isDisabled"
+      />
+    <!-- </Spin> -->
   </div>
 </template>
-<script lang="ts">
-  import { createVNode, defineComponent, ref } from 'vue';
+<script lang="ts" setup>
+  import { ref } from 'vue';
   import { Modal } from 'ant-design-vue';
-  import { ExclamationCircleOutlined } from '@ant-design/icons-vue/lib/icons';
   import { BasicTable, useTable, TableAction } from '@/components/Table';
   import { Button } from '@/components/Button';
-
+  import { PageEnum } from '@/enums/pageEnum';
+  import { useRouter } from 'vue-router';
   import { useDrawer } from '@/components/Drawer';
   import BatchMsgDrawer from './BatchMsgDrawer.vue';
+  import UserPhasesDrawer from './components/userPhasesDrawer.vue';
   import { useI18n } from 'vue-i18n';
 
   import { columns, searchFormSchema } from './batchMsg.data';
   import { getBatchMsgList, deleteBatchMsg } from '@/api/wechat/batchMsg';
 
-  export default defineComponent({
-    name: 'BatchMsgManagement',
-    components: { BasicTable, BatchMsgDrawer, TableAction, Button },
-    setup() {
+  //   name: 'BatchMsgManagement',
+    // components: { BasicTable, BatchMsgDrawer, UserPhasesDrawe, TableAction, Button },
       const { t } = useI18n();
       const selectedIds = ref<number[] | string[]>();
+      const router = useRouter();
 
+      const stageLoading = ref(false);
+      const drawerVisible = ref(false);
+      const isDisabled = ref(false);
       const [registerDrawer, { openDrawer }] = useDrawer();
       const [registerTable, { reload }] = useTable({
         title: t('wechat.batchMsg.batchMsgList'),
@@ -66,22 +99,47 @@
       });
 
       function handleCreate() {
-        openDrawer(true, {
-          isUpdate: false,
-        });
+        // openDrawer(true, {
+        //   isUpdate: false,
+        // });
+        // stageLoading.value = false;
+        drawerVisible.value = true;
+        console.log(111,drawerVisible.value)
       }
 
       async function handleSuccess() {
         await reload();
       }
 
-      return {
-        t,
-        registerTable,
-        registerDrawer,
-        handleCreate,
-        handleSuccess,
-      };
-    },
-  });
+      //发送记录
+      function handleSend(record: Recordable) {
+        console.log('发送记录');
+        router.push({
+          path: PageEnum.SEND_RECORD,
+          query: { id: record.id },
+        });
+      }
+
+      //开始
+      function handleStart(record: Recordable) {
+        console.log('开始');
+      }
+      //  function handleSend(record: Recordable) {
+      //   console.log('发送记录');
+      // };
 </script>
+<style scoped lang="less">
+  .spin-style {
+    width: 100%;
+    height: 100%;
+    z-index: 1;
+    position: fixed;
+    top: 0;
+    left: 0;
+    background: rgba(255, 255, 255, 0.7);
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+  }
+</style>

+ 157 - 0
src/views/wechat/batch_msg/send_record/index.vue

@@ -0,0 +1,157 @@
+<template>
+  <div class="container">
+    <a-form :model="filters" layout="inline" class="form-style">
+      <!-- <a-form-item label="客户">
+        <a-input v-model:value="filters.customer" placeholder="请输入" />
+      </a-form-item>
+      <a-form-item label="社交账号">
+        <a-select v-model:value="filters.socialAccount" placeholder="请选择" class="select-style">
+          <a-select-option value="account1">账号1</a-select-option>
+          <a-select-option value="account2">账号2</a-select-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item label="类型">
+        <a-select v-model:value="filters.type" placeholder="请选择" class="select-style">
+          <a-select-option value="personal">个人微信</a-select-option>
+          <a-select-option value="enterprise">企业微信</a-select-option>
+        </a-select>
+      </a-form-item> -->
+      <a-form-item label="执行状态">
+        <a-select v-model:value="filters.result" placeholder="请选择" class="select-style">
+          <a-select-option value="notSend">未发送</a-select-option>
+          <a-select-option value="success">成功</a-select-option>
+          <a-select-option value="failure">失败</a-select-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item label="联系人">
+        <a-input v-model:value="filters.customer" placeholder="请输入" />
+      </a-form-item>
+      <a-form-item>
+        <a-button type="primary" @click="onSearch">查询</a-button>
+      </a-form-item>
+      <a-form-item>
+        <a-button @click="onReset">重置</a-button>
+      </a-form-item>
+    </a-form>
+    <a-table :columns="columns" :dataSource="data" :pagination="pagination" class="table-style">
+      <template #action="{ record }">
+        <a @click="viewRecord(record)">发送记录</a>
+      </template>
+    </a-table>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, reactive,onMounted } from 'vue';
+import { Form, Input, Select, Button, Table, Pagination } from 'ant-design-vue';
+import { useRoute } from 'vue-router';
+import { getSopTaskRecordMsg } from '@/api/wechat/sopTask'
+import dayjs from 'dayjs';
+
+export default defineComponent({
+  name: 'SendTasks',
+  components: {
+    'a-form': Form,
+    'a-form-item': Form.Item,
+    'a-input': Input,
+    'a-select': Select,
+    'a-select-option': Select.Option,
+    'a-button': Button,
+    'a-table': Table,
+    'a-pagination': Pagination,
+  },
+  setup() {
+    const filters = reactive({
+      customer: '',
+      socialAccount: '',
+      type: '',
+      result: '',
+    });
+    const route = useRoute();
+    const data = reactive([
+    ]);
+
+    const columns = reactive([
+      { title: '序号', dataIndex: 'index', key: 'index' },
+      { title: '联系人', dataIndex: 'botWxid', key: 'botWxid' },
+      { title: '发送微信', dataIndex: 'contactWxid', key: 'contactWxid' },
+      { title: '执行状态', dataIndex: 'result', key: 'result' },
+      { title: '异常原因', dataIndex: 'errorDetail', key: 'errorDetail' },
+      { title: '执行时间', dataIndex: 'sendTime', key: 'sendTime', customRender: ({ text }) => dayjs(text).format('YY-MM-DD HH:mm:ss')},
+      // { title: '操作', key: 'action', slots: { customRender: 'action' } },
+    ]);
+
+    onMounted(async () => {
+      const sourceId = route.query.sourceId;
+      const sourceType = route.query.sourceType;
+      let res = await getSopTaskRecordMsg({ sourceId: Number(sourceId) ,sourceType: Number(sourceType),page:1,pageSize:50});
+      console.log(res)
+      if (res && Array.isArray(res.data.data)) {
+        res.data.data.forEach((item, index) => {
+          item.index = index + 1;  // 为每条记录添加序号
+        });
+        data.splice(0, data.length, ...res.data.data);  // 更新 data
+        pagination.total = res.data.total;
+      }
+    });
+
+    const pagination = reactive({
+      total: 0,
+      pageSize: 10,
+      current: 1,
+      showTotal: (total) => `共 ${total} 条`,
+    });
+
+    const onSearch = () => {
+      console.log('查询条件', filters);
+      // 在这里添加查询逻辑
+    };
+
+    const onReset = () => {
+      filters.customer = '';
+      filters.socialAccount = '';
+      filters.type = '';
+      filters.result = '';
+    };
+
+    const viewRecord = (record) => {
+      console.log('查看发送记录', record);
+      // 在这里添加你的逻辑,比如跳转到发送记录页面或显示模态框
+    };
+
+    return {
+      filters,
+      data,
+      columns,
+      pagination,
+      onSearch,
+      onReset,
+      viewRecord,
+    };
+  },
+});
+</script>
+
+<style scoped>
+.container{
+  background-color: #fff;
+  margin: 20px;
+  border-radius: 4px;
+  height: calc(100vh - 40px);
+  width: calc(100% - 40px);
+}
+.ant-table-cell {
+  text-align: center;
+}
+.table-style{
+  padding: 16px;
+}
+.form-style{
+  /* width: ; */
+  height: 55px;
+  padding: 16px;
+}
+.select-style{
+  width: 180px;
+}
+</style>

+ 2 - 0
src/views/wechat/contact/contact.data.ts

@@ -18,11 +18,13 @@ export const columns: BasicColumn[] = [
     title: t('wechat.contact.wxid'),
     dataIndex: 'wxid',
     width: 100,
+    align: 'left',
   },
   {
     title: t('wechat.contact.nickname'),
     dataIndex: 'nickname',
     width: 100,
+    align: 'left',
   },
   {
     title: t('wechat.contact.lag'),

+ 96 - 13
src/views/wechat/contact/index.vue

@@ -13,13 +13,19 @@
         </Button>
       </template>
       <template #toolbar>
-<!--        <a-button type="primary" @click="handleCreate">-->
-<!--          {{ t('wechat.contact.addContact') }}-->
-<!--        </a-button>-->
+        <!-- <a-button type="primary" @click="handleCreate">
+         {{ t('wechat.contact.addContact') }}
+       </a-button> -->
+        <a-button @click="handleAddLabel" style="position: absolute; left: 0px">
+          + 增加标签
+        </a-button>
+        <a-button @click="handleDeleteLabel" style="position: absolute; left: 110px">
+          - 移除标签
+        </a-button>
       </template>
-      <template #bodyCell="{ column, record }" >
+      <template #bodyCell="{ column, record }">
         <template v-if="column.key === 'action'">
-          <TableAction 
+          <TableAction
             :actions="[
               {
                 icon: 'ant-design:comment-outlined',
@@ -43,38 +49,82 @@
         </template>
       </template>
     </BasicTable>
+    <Modal
+      width="700px"
+      height="500px"
+      v-model:open="modalVisible"
+      :title="modal_title"
+      @ok="handleOk"
+      @cancel="handleCancel"
+      class="custom-modal"
+    >
+      <Form :model="form" layout="inline" style="height: 300px">
+        <FormItem name="lableName" label="选择标签" style="margin-left: 30px">
+          <Select
+            v-model:value="form.lableName"
+            :options="actionLabel"
+            allowClear
+            mode="multiple"
+            size="middle"
+            placeholder="请选择"
+            :style="{ width: '560px', margin: '0 5px' }"
+          ></Select>
+        </FormItem>
+      </Form>
+    </Modal>
     <GroupLabelDrawer @register="registerGroupLabelDrawer" @success="handleSuccess" />
     <ContactDrawer @register="registerDrawer" @success="handleSuccess" />
-    <SendMsgDrawer  @register="registerDrawerMsg" @success="handleSuccess1"/>
+    <SendMsgDrawer @register="registerDrawerMsg" @success="handleSuccess1" />
   </div>
 </template>
 <script lang="ts">
-import {createVNode, defineComponent, ref} from 'vue';
-  import { Modal } from 'ant-design-vue';
+  import { createVNode, defineComponent, ref, onMounted, reactive } from 'vue';
+  import { Modal, Form, FormItem, Select } from 'ant-design-vue';
   import { ExclamationCircleOutlined } from '@ant-design/icons-vue/lib/icons';
   import { BasicTable, useTable, TableAction } from '@/components/Table';
   import { Button } from '@/components/Button';
 
+  import { getLabelSelectList } from '@/api/wechat/label';
   import { useDrawer } from '@/components/Drawer';
   import GroupLabelDrawer from './GroupLabelDrawer.vue';
   import ContactDrawer from './ContactDrawer.vue';
   import SendMsgDrawer from './SendMsgDrawer.vue';
   import { useI18n } from 'vue-i18n';
 
-import {columns, searchFormSchema} from './contact.data';
+  import { columns, searchFormSchema } from './contact.data';
   import { getContactList, deleteContact } from '@/api/wechat/contact';
 
   export default defineComponent({
     name: 'ContactManagement',
-    components: { BasicTable,SendMsgDrawer, ContactDrawer, GroupLabelDrawer, TableAction, Button },
+    components: {
+      BasicTable,
+      SendMsgDrawer,
+      ContactDrawer,
+      GroupLabelDrawer,
+      TableAction,
+      Button,
+      Modal,
+      Select,
+      Form,
+      FormItem,
+    },
     setup() {
       const { t } = useI18n();
       const selectedIds = ref<number[] | string[]>();
       const showDeleteButton = ref<boolean>(false);
+      const modalVisible = ref(false);
+      const modal_title = ref('新增标签');
+      const form = reactive({
+        lableName: [],
+      });
+      const initialForm = reactive({
+        lableName: [],
+      });
+      const actionLabel = ref([]);
 
       const [registerGroupLabelDrawer, { openGroupLabelDrawer }] = useDrawer();
       const [registerDrawer, { openDrawer }] = useDrawer();
-      const [registerDrawerMsg,{ openDrawerMsg }] = useDrawer();
+      const [registerDrawerMsg, { openDrawerMsg }] = useDrawer();
       const [registerTable, { reload }] = useTable({
         title: t('wechat.contact.contactList'),
         api: getContactList,
@@ -104,11 +154,26 @@ import {columns, searchFormSchema} from './contact.data';
         // },
       });
 
+      onMounted(async () => {
+        let res = await getLabelSelectList({ page: 1, pageSize: 1000, type: 1 });
+        actionLabel.value = res.data;
+      });
       function handleCreate() {
         openDrawer(true, {
           isUpdate: false,
         });
       }
+      function handleAddLabel() {
+        console.log('添加标签');
+        modalVisible.value = true;
+        modal_title.value = '增加标签';
+      }
+
+      function handleDeleteLabel() {
+        console.log('移除标签');
+        modalVisible.value = true;
+        modal_title.value = '移除标签';
+      }
 
       function handleEdit(record: Recordable) {
         if (record.type == 1) {
@@ -124,7 +189,7 @@ import {columns, searchFormSchema} from './contact.data';
         }
       }
       function handleMsg(record: Recordable) {
-         openDrawerMsg(true, {
+        openDrawerMsg(true, {
           record,
         });
       }
@@ -159,7 +224,10 @@ import {columns, searchFormSchema} from './contact.data';
       async function handleSuccess1() {
         await reload();
       }
-
+      async function handleOk() {}
+      async function handleCancel() {
+        Object.assign(form, initialForm);
+      }
       // const labelOptions = ref([]);
       //
       // async function fetchLabelList() {
@@ -191,9 +259,24 @@ import {columns, searchFormSchema} from './contact.data';
         handleSuccess1,
         handleBatchDelete,
         showDeleteButton,
+        handleDeleteLabel,
+        handleAddLabel,
+        handleOk,
+        handleCancel,
+        form,
+        modalVisible,
+        modal_title,
+        actionLabel,
         // labelOptions,
         // getLabelOptions,
       };
     },
   });
 </script>
+<style lang="less" scoped>
+  .custom-modal {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+</style>

+ 0 - 2
src/views/wechat/sop_task/index.vue

@@ -80,7 +80,6 @@ export default defineComponent({
 
     function handleEdit(record: Recordable) {
       console.log('编辑',record.id);
-      // go(PageEnum.ADD_SOP);
       router.push({
         path: PageEnum.ADD_SOP,
         query: { task_id: record.id }
@@ -103,7 +102,6 @@ export default defineComponent({
     //执行任务
     function handleTask(record: Recordable) {
       console.log('执行任务')
-      // go(PageEnum.PERFORM_TASKS);
       router.push({
         path: PageEnum.PERFORM_TASKS,
         query: { task_id: record.id }

+ 91 - 0
src/views/wechat/wechat_main/components/add_robot.vue

@@ -0,0 +1,91 @@
+<template>
+  <div class="container">
+    <Table :columns="columns" :dataSource="data" :pagination="pagination" class="table-style">
+      <!-- 自定义操作列 -->
+      <template #action="{ record }">
+        <a @click="viewRecord(record)">发送记录</a>
+      </template>
+      <template #account="{ record }">
+        <Tooltip placement="top" :title="accountTooltip">
+          <span>{{ record.account }}</span>
+        </Tooltip>
+      </template>
+    </Table>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { reactive ,onMounted} from 'vue';
+import { Table, Pagination ,Tooltip} from 'ant-design-vue';
+import { useRoute ,useRouter} from 'vue-router';
+import { PageEnum } from '@/enums/pageEnum';
+// import {getSopTaskRecordList} from '@/api/wechat/sopTask'
+
+const data = reactive([
+  {
+    index: 1,
+    name: 'DESKTOP-GK01',
+    status: '正常',
+    account: '@微信',
+    belongUser: '冠客小助手 18501211675',
+    sourceId: 'wxid_daaj270crifq...',
+    sourceType: '微信',
+  },
+    ]);
+    const route = useRoute();
+    const router = useRouter();
+    const columns = reactive([
+      { title: '序号', dataIndex: 'index', key: 'index' },
+      { title: '名称', dataIndex: 'name', key: 'name' },
+      { title: '状态', dataIndex: 'status', key: 'status' },
+      { title: '社交账号', dataIndex: 'account', key: 'account' ,slots: { customRender: 'account' }},
+      { title: '归属用户', dataIndex: 'belongUser', key: 'belongUser' },
+      { title: '操作', key: 'action', slots: { customRender: 'action' }},
+    ]);
+  const accountTooltip = "当AI员工未绑定微信时,登录微信则视为自动绑定。如需解绑,请联系管理员处理。";
+
+    const pagination = reactive({
+      total: 0,
+      pageSize: 10,
+      current: 1,
+      showTotal: (total) => `共 ${total} 条`,
+    });
+
+    // onMounted(async () => {
+    //   const task_id = route.query.task_id;
+    //   let res = await getSopTaskRecordList({ id: Number(task_id) });
+    //   if (res && Array.isArray(res.data)) {
+    //     res.data.forEach((item, index) => {
+    //       item.index = index + 1;  // 为每条记录添加序号
+    //     });
+    //     data.splice(0, data.length, ...res.data);  // 更新 data
+    //     pagination.total = res.data.length;
+    //   }
+    // });
+
+    const viewRecord = (record) => {
+      console.log('查看发送记录', record);
+      router.push({
+        path: PageEnum.SEND_TASKS,
+        query: { sourceId: record.sourceId, sourceType: record.sourceType}
+      });
+    };
+</script>
+<style lang="less" scoped>
+.container{
+  background-color: #fff;
+  margin: 20px;
+  border-radius: 4px;
+  height: calc(100vh - 40px);
+  width: calc(100% - 40px);
+  overflow: hidden;
+}
+.ant-table-cell {
+  text-align: center;
+}
+.table-style{
+  width: 100%;
+  height: 100%;
+  
+}
+</style>

+ 95 - 0
src/views/wechat/wechat_main/components/all_answer.vue

@@ -0,0 +1,95 @@
+<template>
+  <div class="content-dialog">
+    <div class="content-dialog_tab">
+      <Tabs v-model:activeKey="activeKey" class="tabs-all">
+        <TabPane key="all">
+          <template #tab>
+            <div class="tab-content">
+              <Badge :dot="true">全部</Badge>
+              <div v-show="activeKey === 'all'" class="boder-line"></div>
+            </div>
+          </template>
+        </TabPane>
+        <TabPane key="unread">
+          <template #tab>
+            <div class="tab-content">
+              <Badge :dot="true">未读</Badge>
+              <div v-show="activeKey === 'unread'" class="boder-line"></div>
+            </div>
+          </template>
+        </TabPane>
+        <TabPane key="aiteme">
+          <template #tab>
+            <div class="tab-content">
+              <Badge :dot="true"> @我</Badge>
+              <div v-show="activeKey === 'aiteme'" class="boder-line"></div>
+            </div>
+          </template>
+        </TabPane>
+      </Tabs>
+    </div>
+    <div class="content-dialog_area">
+      暂无数据
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref } from 'vue';
+import { Tabs, TabPane,Badge } from 'ant-design-vue';
+
+const activeKey = ref('all');
+</script>
+
+<style scoped lang="less">
+.content-dialog {
+  height: 100%;
+  width: 284px;
+  min-width: 285px;
+  border-right: 1px solid #e3e9f4;
+}
+.content-dialog_tab {
+  display: flex;
+  padding: 0 4px;
+  cursor: pointer;
+  position: relative;
+}
+.content-dialog_area {
+  flex-grow: 1;
+  cursor: pointer;
+  height: 80px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: #999;
+    font-size: 15px;
+}
+.tabs-all {
+  /*display: flex;
+  align-items: center;
+  justify-content: center;*/
+  color: #778699;
+  margin: 4px 11px;
+  padding: 4px 0px 7px;
+  width: 100%;
+}
+.tab-content{
+   padding: 3px 0px 8px 0px;
+   position: relative;
+}
+.boder-line{
+  border-bottom: 2px solid #4C84FF;
+  padding: 0px 15px;
+  position: absolute;
+  bottom: 0px;
+}
+::v-deep .ant-tabs .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn {
+  color: #4C84FF !important;
+}
+::v-deep .ant-tabs .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn:hover {
+    color: #4C84FF !important;
+  }
+::v-deep .ant-tabs .ant-tabs-tab .ant-tabs-tab-btn:hover {
+  color: #778699 !important;
+}
+</style>

+ 56 - 0
src/views/wechat/wechat_main/components/chat_dialog.vue

@@ -0,0 +1,56 @@
+<template>
+  <div class="chatMain-container-chatdialog_right">
+    <div v-if="false" class="wechat-no-data">
+      <img class="wechat-img" src="@/assets/images/wechatLogo.svg" alt="" />
+    </div>
+    <div v-if="true" class="chat-roomings room-container">
+      <div class="unline-mode" v-if="true">
+        <img class="unline-img" src="@/assets/images/wechat_unline.png" />
+      </div>
+      <SplitWrapper />
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { ref, computed } from 'vue';
+  import SplitWrapper from './split_wrapper.vue';
+  // import { Tabs, TabPane, InputSearch, Badge } from 'ant-design-vue';
+</script>
+
+<style scoped lang="less">
+  .chatMain-container-chatdialog_right {
+    background: #fcfcfc;
+    position: relative;
+    top: 2px;
+    height: 100%;
+    flex-grow: 1;
+    padding: 2px 0;
+  }
+  .wechat-no-data {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    height: 100%;
+  }
+  .wechat-img {
+    width: 180px;
+    height: 150px;
+  }
+  .unline-mode {
+    z-index: 1000;
+    width: 350px;
+    height: 40px;
+    left: calc(50% - 175px);
+    cursor: pointer;
+    position: absolute;
+    top: 0;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+  .unline-img {
+    width: 350px;
+    height: 40px;
+  }
+</style>

+ 189 - 0
src/views/wechat/wechat_main/components/home.vue

@@ -0,0 +1,189 @@
+<template>
+  <div class="chatMain-container-chatdialog_left">
+    <div v-if="!search_key" class="contacts-container">
+      <div class="header-bar">
+        <div class="header-bar-tab">
+          <Tabs v-model:activeKey="activeKey" class="tabs" tabBarGutter="0">
+            <TabPane key="allAnswer">
+              <template #tab>
+                <div
+                  :class="{ 'tab-content': true, active: activeKey === 'allAnswer' }"
+                  tab="全部对话"
+                >
+                  <Badge :dot="true">全部对话</Badge>
+                </div>
+              </template>
+            </TabPane>
+            <TabPane key="processed">
+              <template #tab>
+                <div
+                  :class="{ 'tab-content': true, active: activeKey === 'processed' }"
+                  tab="待人工处理"
+                >
+                  <Badge :dot="true">待人工处理</Badge>
+                </div>
+              </template>
+            </TabPane>
+          </Tabs>
+        </div>
+        <div class="header-bar-search" @click="handleSearch">
+          <!-- <InputSearch placeholder="搜索" /> -->
+          <SearchOutlined class="search-icon" style="font-size: 16px" />
+        </div>
+      </div>
+      <div class="content">
+        <component :is="currentTabComponent"></component>
+      </div>
+    </div>
+    <div v-if="search_key" class="contacts-container-search">
+      <div class="contacts-container-search_contain">
+        <div class="search-box">
+          <InputSearch
+            v-model:value="value"
+            placeholder="搜索"
+            style="width: 300px"
+            @search="onSearch"
+            @input="onInput"
+            allowClear
+          />
+        </div>
+      </div>
+    </div>
+  </div>
+    <ChatDialog/>
+</template>
+
+<script lang="ts" setup>
+  import { ref, computed } from 'vue';
+  import { Tabs, TabPane, InputSearch, Badge } from 'ant-design-vue';
+  import { SearchOutlined } from '@ant-design/icons-vue';
+  import AllAnswer from './all_answer.vue';
+  import ProcessedAnswer from './processed_answer.vue';
+  import ChatDialog from './chat_dialog.vue';
+
+  const activeKey = ref('allAnswer');
+  const value = ref('');
+  const search_key = ref(false);
+
+  const currentTabComponent = computed(() => {
+    switch (activeKey.value) {
+      case 'allAnswer':
+        return AllAnswer; // 需要替换成你的组件
+      case 'processed':
+        return ProcessedAnswer; // 需要替换成你的组件
+      default:
+        return AllAnswer;
+    }
+  });
+  function handleSearch() {
+    search_key.value = true;
+  }
+  
+  function onInput(event: Event) {
+    const inputValue = (event.target as HTMLInputElement).value;
+    if (inputValue === '') {
+      search_key.value = false;
+    }
+  }
+  const onSearch = (searchValue: string) => {
+    // console.log('use value', searchValue);
+  };
+</script>
+
+<style scoped lang="less">
+  .chatMain-container-chatdialog_left {
+    height: 100%;
+    width: 284px;
+    min-width: 285px;
+    border-right: 1px solid #e3e9f4;
+  }
+
+  .contacts-container {
+    width: 100%;
+    /*height: 100%;*/
+    display: flex;
+    flex-direction: column;
+  }
+
+  .header-bar {
+    display: flex;
+    align-items: center;
+    margin: 0 7px 0 12px;
+    justify-content: space-between;
+    border-bottom: 1px solid #e3e9f4;
+    cursor: pointer;
+  }
+  .tab-content {
+    margin-right: 20px;
+    color: #666;
+  }
+  .tab-content.active {
+    color: #3d3d3d;
+  }
+  ::v-deep .ant-tabs .ant-tabs-tab .ant-tabs-tab-btn:hover {
+    color: #666 !important;
+  }
+  .header-bar-tab {
+    flex-grow: 1;
+    display: flex;
+    padding: 10px 0;
+  }
+  ::v-deep .ant-tabs-top > .ant-tabs-nav {
+    margin: 0;
+  }
+
+  .tabs {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: #666 !important;
+  }
+  .header-bar-search {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 5px;
+    margin-right: 5px;
+    border-radius: 4px;
+    background: #eeeeef;
+  }
+
+  ::v-deep .ant-tabs .ant-tabs-tab {
+    padding: 0 !important;
+  }
+
+  ::v-deep .ant-tabs .ant-tabs-nav::before {
+    border: none !important;
+  }
+
+  /*::v-deep .ant-tabs .ant-tabs-tab-active .ant-tabs-tab-btn {
+                      color: #1890ff !important;
+                    }*/
+
+  ::v-deep .ant-tabs .ant-tabs-ink-bar {
+    display: none !important;
+  }
+
+  .header-bar-search:hover {
+    background: #e7ecfc;
+    .search-icon {
+      color: #5e5fcf;
+    }
+  }
+  .contacts-container-search {
+    font-size: 15px;
+    flex-direction: column;
+    display: flex;
+  }
+  .contacts-container-search_contain {
+    cursor: pointer;
+  }
+  .search-box {
+    display: flex;
+    align-items: center;
+    margin-top: 15px;
+    margin-bottom: 12px;
+    margin-left: 25px;
+    margin-right: 15px;
+  }
+</style>

+ 88 - 0
src/views/wechat/wechat_main/components/processed_answer.vue

@@ -0,0 +1,88 @@
+<template>
+  <div class="content-dialog">
+    <div class="content-dialog_tab">
+      <Tabs v-model:activeKey="activeKey" class="tabs-all">
+        <TabPane key="pending">
+          <template #tab>
+            <div class="tab-content">
+               <Badge :dot="true">待处理</Badge>
+              <div v-show="activeKey === 'pending'" class="boder-line"></div>
+            </div>
+          </template>
+        </TabPane>
+        <TabPane key="processing">
+          <template #tab>
+            <div class="tab-content">
+              <Badge :dot="true">处理中</Badge>
+              <div v-show="activeKey === 'processing'" class="boder-line"></div>
+            </div>
+          </template>
+        </TabPane>
+        
+      </Tabs>
+    </div>
+    <div class="content-dialog_area">
+      暂无数据
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref } from 'vue';
+import { Tabs, TabPane ,Badge} from 'ant-design-vue';
+
+const activeKey = ref('pending');
+</script>
+
+<style scoped lang="less">
+.content-dialog {
+  height: 100%;
+  width: 284px;
+  min-width: 285px;
+  border-right: 1px solid #e3e9f4;
+}
+.content-dialog_tab {
+  display: flex;
+  padding: 0 4px;
+  cursor: pointer;
+  position: relative;
+}
+.content-dialog_area {
+  flex-grow: 1;
+  cursor: pointer;
+    height: 80px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: #999;
+    font-size: 15px;
+}
+.tabs-all {
+  /*display: flex;
+  align-items: center;
+  justify-content: center;*/
+  color: #778699;
+  margin: 4px 11px;
+  padding: 4px 0px 7px;
+  width: 100%;
+}
+.tab-content{
+   padding: 3px 0px 8px 0px;
+   position: relative;
+}
+.boder-line{
+  border-bottom: 2px solid #4C84FF;
+  padding: 0px 21px;
+  position: absolute;
+  bottom: 0px;
+}
+::v-deep .ant-tabs .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn {
+  color: #4C84FF !important;
+}
+::v-deep .ant-tabs .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn:hover {
+    color: #4C84FF !important;
+  }
+::v-deep .ant-tabs .ant-tabs-tab .ant-tabs-tab-btn:hover {
+  color: #778699 !important;
+}
+</style>

+ 13 - 0
src/views/wechat/wechat_main/components/robot_chat.vue

@@ -0,0 +1,13 @@
+<template>
+  <div>
+    <h1>机器人聊天</h1>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+
+export default defineComponent({
+  name: 'Page1'
+});
+</script>

+ 152 - 0
src/views/wechat/wechat_main/components/split_wrapper.vue

@@ -0,0 +1,152 @@
+<template>
+  <div ref="splitContainer" class="split-container">
+    <div class="panel top-panel">
+      <div class="room-container-header">
+        <div class="room-container-header-title">
+          {{ ' 测试群聊 (29)' }}
+        </div>
+        <div class="room-container-header-operate">
+          <span class="item-margin">
+            <div class="room-operate">
+                {{ '房主' }}<Badge dot status="success" class="badge-right-bottom"></Badge>
+              </div>
+          </span>
+          <span class="item-margin">
+            <FileSearchOutlined style="font-size: 20px; color: #666667" />
+          </span>
+          <span class="item-margin">
+            <Switch
+              v-model:checked="state.aiChecked"
+              checked-children="AI"
+              un-checked-children="人工"
+            />
+          </span>
+          <span class="item-margin" style="margin-right:30px;">
+            <EllipsisOutlined style="font-size: 20px; color: #333" />
+          </span>
+        </div>
+      </div>
+    </div>
+    <div class="panel bottom-panel">
+      <p>这是下侧面板的内容。</p>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { onMounted, ref, reactive } from 'vue';
+  import Split from 'split.js';
+  import { Popover, Switch, Badge } from 'ant-design-vue';
+  import { EllipsisOutlined, FileSearchOutlined } from '@ant-design/icons-vue';
+
+  const splitContainer = ref(null);
+  const state = reactive({
+    aiChecked: true,
+  });
+
+  onMounted(() => {
+    Split([splitContainer.value.children[0], splitContainer.value.children[1]], {
+      sizes: [55, 45], // 初始高度百分比
+      minSize: [window.innerHeight * 0.42, window.innerHeight * 0.29], // 每个面板的最小高度
+      gutterSize: 10, // 分割条的大小
+      cursor: 'row-resize', // 改变鼠标光标以适应垂直分割
+      direction: 'vertical', // 设置方向为垂直
+      gutter: (index, direction) => {
+        const gutter = document.createElement('div');
+        gutter.className = `gutter gutter-${direction}`;
+        return gutter;
+      },
+    });
+  });
+</script>
+
+<style scoped lang="less">
+  .split-container {
+    display: flex;
+    flex-direction: column; /* 改为纵向排列 */
+    height: 100vh;
+  }
+
+  .panel {
+    overflow: auto;
+    background-color: #fcfcfc;
+    // padding: 16px;
+  }
+
+  .top-panel {
+    // border-bottom: 1px solid #d9d9d9;
+  }
+
+  .bottom-panel {
+    // border-top: 1px solid #d9d9d9;
+  }
+
+  ::v-deep .gutter {
+    background-color: transparent; /* 分隔条的背景色 */
+    position: relative; /* 使用绝对定位调整内部实线 */
+    z-index: 1; /* 确保分隔条在顶层 */
+  }
+
+  ::v-deep .gutter-vertical {
+    background: transparent !important; /* 设置背景透明 */
+    border: none !important; /* 移除边框 */
+    height: 10px; /* 保持可拖拽区域的高度 */
+    width: 100%; /* 宽度为100% */
+  }
+
+  ::v-deep .gutter-vertical::before {
+    content: '';
+    position: absolute;
+    top: 4.5px; /* 使实线居中 */
+    left: 0;
+    right: 0;
+    height: 1px; /* 实线的高度 */
+    background-color: #e4e9f3; /* 实线的颜色 */
+    z-index: 2; /* 确保实线在分隔条的上层 */
+  }
+  .room-container-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 10px;
+    border-bottom: 1px solid #ccc;
+    flex-basis: 51px;
+    min-height: 51px;
+  }
+  .room-container-header-title {
+    font-weight: 600;
+    font-size: 14px;
+  }
+  .room-container-header-operate {
+    display: flex;
+    align-items: center;
+    cursor: pointer;
+  }
+  ::v-deep .ant-switch {
+    height: 24px;
+  }
+  .item-margin {
+    margin-left: 15px;
+  }
+  .room-operate {
+    border-radius: 50%;
+    width: 32px;
+    height: 32px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background-color: #307ef2;
+    color: #fff;
+    position: relative;
+    font-size: 12px;
+  }
+  .badge-right-bottom {
+  position: absolute; /* 绝对定位 */
+  bottom: -5px; /* 定位到底部 */
+  right: -5px; /* 定位到右侧 */
+}
+::v-deep .ant-badge.ant-badge-status .ant-badge-status-dot{
+  width: 7px;
+  height: 7px;
+}
+</style>

+ 184 - 0
src/views/wechat/wechat_main/index.vue

@@ -0,0 +1,184 @@
+<template>
+  <div class="container">
+    <Tabs v-model:activeKey="activeTab" tabPosition="left" @change="handleChange" class="tabs">
+      <TabPane key="Home">
+        <template #tab>
+          <div class="tab-content">
+            <div v-if="activeTab === 'Home'" class="active_tab"></div>
+            <div class="home_style"></div>
+          </div>
+        </template>
+      </TabPane>
+      <TabPane key="RobotChat">
+        <template #tab>
+          <div class="tab-content">
+            <div v-if="activeTab === 'RobotChat'" class="active_tab_center"></div>
+            <div class="avatar_style">
+              <div class="avatar-overlay">
+                <div class="unline"></div>
+                <div class="wechat_icon"></div>
+              </div>
+            </div>
+          </div>
+        </template>
+      </TabPane>
+      <TabPane key="AddRobot">
+        <template #tab>
+          <div class="tab-content">
+            <div v-if="activeTab === 'AddRobot'" class="active_tab_center"></div>
+            <div class="add_style"></div>
+          </div>
+        </template>
+      </TabPane>
+    </Tabs>
+    <div class="content">
+      <component :is="currentTabComponent"></component>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { ref, computed } from 'vue';
+  import AddRobot from './components/add_robot.vue';
+  import Home from './components/home.vue';
+  import RobotChat from './components/robot_chat.vue';
+  import { Tabs, TabPane } from 'ant-design-vue';
+  const activeTab = ref('Home');
+  const tabs = {
+    Home: Home,
+    RobotChat: RobotChat,
+    AddRobot: AddRobot,
+  };
+
+  const currentTabComponent = computed(() => tabs[activeTab.value]);
+
+  const handleChange = (key: string) => {
+    activeTab.value = key;
+  };
+</script>
+<style lang="less" scoped>
+  .container {
+    display: flex;
+    height: calc(100vh - 10px);
+    padding-left: 10px;
+    overflow: hidden;
+    padding-top: 10px;
+    max-width: 100%;
+  }
+  ::v-deep .ant-tabs-nav::before {
+    border: none !important; /* 去掉边框线 */
+  }
+  ::v-deep .ant-tabs-ink-bar {
+    display: none !important; /* 去掉选中的下划线 */
+  }
+  ::v-deep .ant-tabs-tab-active .ant-tabs-tab-btn {
+    color: inherit !important; /* 去掉默认选中的文本颜色 */
+    background: none !important; /* 去掉默认选中的背景色 */
+    border: none !important; /* 去掉默认选中的边框 */
+    box-shadow: none !important; /* 去掉默认选中的阴影 */
+  }
+  ::v-deep .vben-layout-content .full {
+    background: #f0f1f3;
+  }
+  ::v-deep .ant-tabs-left > .ant-tabs-content-holder {
+    border: none !important;
+    margin: 0;
+    width: 0px;
+    background: #fff;
+  }
+  .tab-content {
+    height: 76px !important;
+    width: 76px !important;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+    position: relative;
+  }
+  .home_style {
+    width: 48px;
+    height: 48px;
+    background-image: url('@/assets/images/home.png');
+    background-size: cover;
+  }
+  .active_tab {
+    width: 76px;
+    height: 106px;
+    background-image: url('../../../assets/images/top.png') !important; /* 选中标签的背景图 */
+    background-size: contain; /* 调整背景图大小 */
+    background-repeat: no-repeat;
+    position: absolute;
+    top: 0;
+    z-index: -1;
+  }
+  .active_tab_center {
+    width: 76px;
+    height: 106px;
+    background-image: url('../../../assets/images/center.png') !important; /* 选中标签的背景图 */
+    background-size: contain; /* 调整背景图大小 */
+    background-repeat: no-repeat;
+    position: absolute;
+    z-index: -1;
+  }
+  .avatar_style {
+    width: 48px;
+    height: 48px;
+    background-image: url('@/assets/images/robot.jpeg');
+    background-size: cover;
+    border-radius: 4px;
+    position: relative;
+  }
+  .avatar-overlay {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    border-radius: 8px;
+    border: 0.5px solid transparent;
+  }
+  .unline {
+    position: absolute;
+    top: -3px;
+    right: -3px;
+    width: 10px;
+    height: 10px;
+    background-color: #dedede;
+    border-radius: 50%;
+  }
+  .wechat_icon {
+    width: 16px;
+    height: 16px;
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    background-image: url('@/assets/images/wechatIcon.png');
+    background-size: cover;
+  }
+  .add_style {
+    width: 48px;
+    height: 48px;
+    background-image: url('@/assets/images/add.png');
+    background-size: cover;
+  }
+  ::v-deep .ant-tabs .ant-tabs-tab {
+    padding: 0 !important;
+  }
+  ::v-deep .ant-tabs-left > .ant-tabs-nav .ant-tabs-tab {
+    padding: 0 !important;
+    margin: 0px;
+  }
+  ::v-deep .ant-tabs .ant-tabs-tab .anticon {
+    margin-right: 0px !important;
+  }
+  ::v-deep .ant-tabs .ant-tabs-tab:hover {
+    color: none;
+  }
+  .content {
+    flex: 1;
+    background: #fff;
+    display: flex;
+
+    /*padding: 16px; */
+  }
+</style>

+ 5 - 0
src/views/wechat/wx/wx.data.ts

@@ -134,4 +134,9 @@ export const formSchema: FormSchema[] = [
     label: t('wechat.wx.callback'),
     component: 'Input',
   },
+  {
+    field: 'mode',
+    label: '模式',
+    component: 'Select',
+  },
 ]