32 Commits d5ac32465b ... 40101b832d

Tác giả SHA1 Thông báo Ngày
  wjx 40101b832d fix 1 tuần trước cách đây
  wjx 46a0767552 fix 2 tuần trước cách đây
  wjx 719923429a fix 2 tuần trước cách đây
  wjx 003eb3fa87 fix 2 tuần trước cách đây
  wjx db1ce2be40 fix 2 tuần trước cách đây
  wjx ef4ff035ff fix 2 tuần trước cách đây
  wjx d21c00dd8d fix 3 tuần trước cách đây
  wjx f13785de54 fix 3 tuần trước cách đây
  wjx 7450fc9a75 fix 4 tuần trước cách đây
  wjx 99f96867b7 fix 4 tuần trước cách đây
  wjx e5cd59ba24 fix 1 tháng trước cách đây
  wjx 52f20550fa fix 1 tháng trước cách đây
  wjx 9e8da04fdb fix 1 tháng trước cách đây
  wjx 6774f87b5e fix 1 tháng trước cách đây
  wjx 65c7df8581 fix 1 tháng trước cách đây
  wjx aad5483267 fix 1 tháng trước cách đây
  wjx b2a754ae23 fix 1 tháng trước cách đây
  wjx 43e0b79c06 fix 1 tháng trước cách đây
  wjx 34acd0f098 fix 1 tháng trước cách đây
  wjx 8a8ddd3c71 fix 1 tháng trước cách đây
  wjx b4b5db47a0 fix 2 tháng trước cách đây
  wjx facbb1e077 fix 2 tháng trước cách đây
  wjx 6e0b76b8fe fix 2 tháng trước cách đây
  wjx 4b151ed8f2 fix 2 tháng trước cách đây
  wjx 398aba7f55 fix 2 tháng trước cách đây
  wjx 62f183119e xxxx 2 tháng trước cách đây
  wjx 681c382fc6 fix 2 tháng trước cách đây
  wjx a0a5fac3d6 ffff 2 tháng trước cách đây
  wjx f02bea628b fix 2 tháng trước cách đây
  wjx d79487d26b 改版001 2 tháng trước cách đây
  wjx bd89238700 Merge branch 'master' of http://git.zanxiangnet.com/wjx/qc-EnterpriseWeChat-Pro 3 tháng trước cách đây
  wjx dd44a8a497 fix 3 tháng trước cách đây
100 tập tin đã thay đổi với 9668 bổ sung1290 xóa
  1. 3 6
      src/Hook/useNewToken.tsx
  2. 7 367
      src/global.less
  3. 1 1
      src/layout/index.less
  4. 27 85
      src/layout/index.tsx
  5. 100 0
      src/pages/weComTask/API/SelectGroupChat/index.tsx
  6. 99 0
      src/pages/weComTask/API/bookLink/index.ts
  7. 111 9
      src/pages/weComTask/API/businessPlan/create.ts
  8. 3 1
      src/pages/weComTask/API/businessPlan/typings.d.ts
  9. 11 0
      src/pages/weComTask/API/corpUserAssign/index.ts
  10. 1 0
      src/pages/weComTask/API/corpUserManage/typings.d.ts
  11. 144 2
      src/pages/weComTask/API/global.ts
  12. 143 0
      src/pages/weComTask/API/groupChat/index.ts
  13. 15 0
      src/pages/weComTask/API/groupChat/typings.d.ts
  14. 148 0
      src/pages/weComTask/API/groupLeaderManage/index.ts
  15. 96 0
      src/pages/weComTask/API/groupManage/index.ts
  16. 136 0
      src/pages/weComTask/components/SelectBook/index.tsx
  17. 139 0
      src/pages/weComTask/components/SelectChapter/index.tsx
  18. 35 1
      src/pages/weComTask/components/filterUser/filterUserText.ts
  19. 45 3
      src/pages/weComTask/components/filterUser/filterUserTooltip.tsx
  20. 70 18
      src/pages/weComTask/components/filterUser/newFiterUser.tsx
  21. 90 0
      src/pages/weComTask/components/groupChatGroups/index.tsx
  22. 1 1
      src/pages/weComTask/components/newSteps/index.tsx
  23. 2 1
      src/pages/weComTask/components/newsMould/index.less
  24. 6 0
      src/pages/weComTask/components/selectExternalAccount/index.less
  25. 1 1
      src/pages/weComTask/components/sendTimeSet/index.tsx
  26. 40 0
      src/pages/weComTask/page/bookLink/SelectBookLinkButton.tsx
  27. 41 0
      src/pages/weComTask/page/bookLink/components/fanqie/bookPromoLinkCreateDTO.tsx
  28. 73 0
      src/pages/weComTask/page/bookLink/components/fanqie/consumeActivityLinkDTO.tsx
  29. 84 0
      src/pages/weComTask/page/bookLink/components/fanqie/giftActivityLinkDTO.tsx
  30. 33 0
      src/pages/weComTask/page/bookLink/components/fanqie/pagePromoLinkCreateDTO.tsx
  31. 72 0
      src/pages/weComTask/page/bookLink/components/fanqie/rechargeActivityLinkDTO.tsx
  32. 83 0
      src/pages/weComTask/page/bookLink/components/huasheng/bookPromoLinkCreateDTO.tsx
  33. 135 0
      src/pages/weComTask/page/bookLink/components/huasheng/giftActivityLinkDTO.tsx
  34. 151 0
      src/pages/weComTask/page/bookLink/components/huasheng/rechargeActivityLinkDTO.tsx
  35. 96 0
      src/pages/weComTask/page/bookLink/components/wending/bookPromoLinkCreateDTO.tsx
  36. 85 0
      src/pages/weComTask/page/bookLink/components/wending/rechargeActivityLinkDTO.tsx
  37. 248 0
      src/pages/weComTask/page/bookLink/components/wending/setConfig.tsx
  38. 105 0
      src/pages/weComTask/page/bookLink/components/yuewen/bookPromoLinkCreateDTO.tsx
  39. 88 0
      src/pages/weComTask/page/bookLink/components/yuewen/consumeActivityLinkDTO.tsx
  40. 79 0
      src/pages/weComTask/page/bookLink/components/yuewen/giftActivityLinkDTO.tsx
  41. 40 0
      src/pages/weComTask/page/bookLink/components/yuewen/pagePromoLinkCreateDTO.tsx
  42. 125 0
      src/pages/weComTask/page/bookLink/components/yuewen/rechargeActivityLinkDTO.tsx
  43. 163 0
      src/pages/weComTask/page/bookLink/const.tsx
  44. 213 0
      src/pages/weComTask/page/bookLink/details.tsx
  45. 13 0
      src/pages/weComTask/page/bookLink/index.less
  46. 382 0
      src/pages/weComTask/page/bookLink/index.tsx
  47. 662 0
      src/pages/weComTask/page/bookLink/modalBooklink.tsx
  48. 122 0
      src/pages/weComTask/page/bookLink/selectBook.tsx
  49. 229 0
      src/pages/weComTask/page/bookLink/selectBookLink.tsx
  50. 146 0
      src/pages/weComTask/page/bookLink/tableConfig.tsx
  51. 12 0
      src/pages/weComTask/page/bookLink/typings.d.ts
  52. 3 1
      src/pages/weComTask/page/businessPlan/create/components/friends/previewFriendsStrategy.tsx
  53. 4 2
      src/pages/weComTask/page/businessPlan/create/components/friends/strategy.tsx
  54. 6 4
      src/pages/weComTask/page/businessPlan/create/components/highMassSending/strategy.tsx
  55. 39 8
      src/pages/weComTask/page/businessPlan/create/components/massSending/content.tsx
  56. 15 18
      src/pages/weComTask/page/businessPlan/create/components/massSending/index.tsx
  57. 12 2
      src/pages/weComTask/page/businessPlan/create/components/massSending/previewMassSendingStrategy.tsx
  58. 136 0
      src/pages/weComTask/page/businessPlan/create/components/massSending/settingsContent.tsx
  59. 24 3
      src/pages/weComTask/page/businessPlan/create/components/massSending/settingsMassSending.tsx
  60. 16 12
      src/pages/weComTask/page/businessPlan/create/components/massSending/showContent.tsx
  61. 14 4
      src/pages/weComTask/page/businessPlan/create/components/massSending/strategy.tsx
  62. 2 2
      src/pages/weComTask/page/businessPlan/create/components/userInherit/index.tsx
  63. 3 1
      src/pages/weComTask/page/businessPlan/create/components/userInherit/previewUserlnherit.tsx
  64. 6 4
      src/pages/weComTask/page/businessPlan/create/components/userInherit/settingsUserlnherit.tsx
  65. 4 26
      src/pages/weComTask/page/businessPlan/create/components/welcome/index.tsx
  66. 1 2
      src/pages/weComTask/page/businessPlan/create/components/welcome/settingsWelcome.tsx
  67. 21 11
      src/pages/weComTask/page/businessPlan/create/const.tsx
  68. 1 0
      src/pages/weComTask/page/businessPlan/create/global.less
  69. 10 4
      src/pages/weComTask/page/businessPlan/create/index.less
  70. 333 411
      src/pages/weComTask/page/businessPlan/create/index.tsx
  71. 30 7
      src/pages/weComTask/page/businessPlan/create/submitModal.tsx
  72. 236 39
      src/pages/weComTask/page/businessPlan/create/tableConfig.tsx
  73. 16 1
      src/pages/weComTask/page/businessPlan/create/typings.d.ts
  74. 1 1
      src/pages/weComTask/page/businessPlan/taskList/components/groupTask/groupTaskNotes.tsx
  75. 1 1
      src/pages/weComTask/page/businessPlan/taskList/components/groupTask/groupXfCorpTabls.tsx
  76. 5 4
      src/pages/weComTask/page/businessPlan/taskList/components/groupTask/index.tsx
  77. 104 4
      src/pages/weComTask/page/businessPlan/taskList/index.tsx
  78. 1 1
      src/pages/weComTask/page/businessPlan/taskList/log.tsx
  79. 37 89
      src/pages/weComTask/page/businessPlan/taskList/tableConfig.tsx
  80. 3 3
      src/pages/weComTask/page/corpUserManage/global.less
  81. 68 3
      src/pages/weComTask/page/corpUserManage/index.tsx
  82. 131 126
      src/pages/weComTask/page/corpUserManage/selectCorpUser.tsx
  83. 92 0
      src/pages/weComTask/page/corpUserManage/selectCorpUserGroup.tsx
  84. 288 0
      src/pages/weComTask/page/corpUserManage/selectCorpUserGroupModal.tsx
  85. 73 0
      src/pages/weComTask/page/corpUserManage/settingsGroup.tsx
  86. 41 0
      src/pages/weComTask/page/corpUserManage/tableConfig.tsx
  87. 493 0
      src/pages/weComTask/page/groupChat/create/components/groupUser/addGroupObject.tsx
  88. 86 0
      src/pages/weComTask/page/groupChat/create/components/groupUser/index.tsx
  89. 48 0
      src/pages/weComTask/page/groupChat/create/components/groupUser/previewGroupUser.tsx
  90. 189 0
      src/pages/weComTask/page/groupChat/create/components/groupUser/settingsGroupUser.tsx
  91. 69 0
      src/pages/weComTask/page/groupChat/create/components/strategy/index.tsx
  92. 88 0
      src/pages/weComTask/page/groupChat/create/components/strategy/previewStrategy.tsx
  93. 193 0
      src/pages/weComTask/page/groupChat/create/components/strategy/settingsStrategy.tsx
  94. 40 0
      src/pages/weComTask/page/groupChat/create/const.ts
  95. 572 0
      src/pages/weComTask/page/groupChat/create/index.tsx
  96. 225 0
      src/pages/weComTask/page/groupChat/create/tableConfig.tsx
  97. 56 0
      src/pages/weComTask/page/groupChat/create/typings.d.ts
  98. 519 0
      src/pages/weComTask/page/groupChat/taskList/details.tsx
  99. 203 0
      src/pages/weComTask/page/groupChat/taskList/index.tsx
  100. 156 0
      src/pages/weComTask/page/groupChat/taskList/tableConfig.tsx

+ 3 - 6
src/Hook/useNewToken.tsx

@@ -30,7 +30,7 @@ let themeConfig = {
 	"cardColorBorder": "#d5d5d5",
 	"cardColorBorderSecondary": "rgba(155,155,155,0.1)",
 	"cardColorBgContainer": "rgba(247,247,247,1)",
-	"tableColorBgContainer": "rgba(247,247,247,1)",
+	"tableColorBgContainer": "rgb(255, 255, 255)",
 	"tablePaddingXS": 4,
 	"colorBgElevated": "rgba(255,255,255,1)",
 	"colorBgMask": "rgba(0,0,0,0.45)",
@@ -109,7 +109,8 @@ let themeConfig = {
         token: {
             'colorTextWhite': '#FFFFFF',
             colorPrimary: '#1677FF',
-            colorBgContainer: 'rgba(247, 247, 247, 0.6)',//容器背景
+            // colorBgContainer: 'rgba(247, 247, 247, 0.6)',//容器背景
+            colorBgContainer: '#FFFFFF',//容器背景
             colorBgLayout: 'rgba(247, 247, 247, 0.5)',//路由背景
             colorTextBase: '#000',//基础文字色
             "wireframe": true,
@@ -122,7 +123,6 @@ let themeConfig = {
         },
         components: {
             Table: {
-                colorBgContainer: 'rgba(247, 247, 247, 0)',
                 paddingXS: 4,
             },
             Aside: {
@@ -131,9 +131,6 @@ let themeConfig = {
             Layout: {
                 borderRadiusSM: 0,
             },
-            Menu: {
-                borderRadiusSM: 0,
-            },
             Card: {
 
             },

+ 7 - 367
src/global.less

@@ -1,87 +1,13 @@
-// // @import '~antd/es/style/themes/default.less';
-// // @import '~antd/dist/antd.dark.less';
-// @root-entry-name: default;
-// html,
-// body,
-// #root {
-//   height: 100%;
-// }
-// body #root .ranking .ant-table.ant-table-small .ant-table-tbody > tr > td.ant-table-cell{
-//   padding: 3px !important;
-// }
-// body #root .ranking .ant-table.ant-table-small .ant-table-tbody > tr > td.ant-table-cell  >div div {
-//   height: 100%;
-// }
-// body #root .ranking .ant-table.ant-table-small .ant-table-tbody > tr > td.ant-table-cell .ant-progress-inner{
-//   border-radius: 0 !important;
-//   height: 100%;
-//   background-color: #fff;
-// }
-// body #root .ranking .ant-table.ant-table-small .ant-table-tbody > tr > td.ant-table-cell .ant-progress-inner .ant-progress-bg{
-//   border-radius: 0 !important;
-//   height: 100% !important;
-// }
-// .colorWeak {
-//   filter: invert(80%);
-// }
 body {
   padding: 0;
   margin: 0;
 }
 
-// // .ant-layout {
-// //   min-height: 100vh;
-// // }
-
-// canvas {
-//   display: block;
-// }
-
-// body {
-//   text-rendering: optimizeLegibility;
-//   -webkit-font-smoothing: antialiased;
-//   -moz-osx-font-smoothing: grayscale;
-// }
-
 ul,
 ol {
   list-style: none;
 }
 
-// input:-webkit-autofill,
-// textarea:-webkit-autofill,
-// select:-webkit-autofill {
-//   background-color: transparent !important;
-//   background-image: none !important;
-//   -webkit-box-shadow: 0 0 0 1000px white inset !important;
-//   -moz-box-shadow: 0 0 0 1000px white inset !important;
-//   -o-box-shadow: 0 0 0 1000px white inset !important;
-//   -ms-box-shadow: 0 0 0 1000px white inset !important;
-// }
-// // @media (max-width: @screen-xs) {
-// //   .ant-table {
-// //     width: 100%;
-// //     overflow-x: auto;
-// //     &-thead > tr,
-// //     &-tbody > tr {
-// //       > th,
-// //       > td {
-// //         white-space: pre;
-// //         > span {
-// //           display: block;
-// //         }
-// //       }
-// //     }
-// //   }
-// // }
-
-// // 兼容IE11
-// @media screen and(-ms-high-contrast: active), (-ms-high-contrast: none) {
-//   body .ant-design-pro > .ant-layout {
-//     min-height: 100vh;
-//   }
-// }
-
 ::-webkit-scrollbar {
   // display: none; /* Chrome Safari */
   width: 2px;
@@ -100,299 +26,6 @@ ol {
   -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.2);
 }
 
-// #notificationPop {
-//   width: 100vw;
-//   height: 100vh;
-//   background: rgba(0, 0, 0, 0.6);
-//   z-index: 10001;
-//   position: fixed;
-//   top: 0;
-//   > div {
-//     position: relative;
-//     > div {
-//       position: absolute !important;
-//       right: 50% !important;
-//       transform: translateX(50%);
-//     }
-//   }
-// }
-// #logo {
-//   a {
-//     display: flex;
-//     align-items: center;
-//   }
-// }
-// .ant-statistic {
-//   .ant-statistic-content {
-//     color: #000;
-//     font-size: 13px;
-//     word-break: break-word;
-//   }
-// }
-// body {
-//   #root {
-//     .ant-table.ant-table-small .ant-table-title,
-//     .ant-table.ant-table-middle .ant-table-title,
-//     .ant-table.ant-table-small .ant-table-footer,
-//     .ant-table.ant-table-middle .ant-table-footer,
-//     .ant-table.ant-table-small .ant-table-thead > tr > th,
-//     .ant-table.ant-table-middle .ant-table-thead > tr > th,
-//     .ant-table.ant-table-small .ant-table-tbody > tr > td,
-//     .ant-table.ant-table-middle .ant-table-tbody > tr > td,
-//     .ant-table.ant-table-small tfoot > tr > th,
-//     .ant-table.ant-table-middle tfoot > tr > th,
-//     .ant-table.ant-table-small tfoot > tr > td .ant-table.ant-table-middle tfoot > tr > td {
-//       padding: 5px;
-//     }
-//     .ant-table.ant-table-small .ant-table-thead .ant-table-column-sorters {
-//       padding: 0;
-//     }
-//     // .ant-table.ant-table-small .ant-table-tbody > tr > td.ant-table-cell {
-//     //   padding-top: 8px;
-//     // }
-//   }
-//   .ant-table-thead > tr > th {
-//     font-weight: 600 !important;
-//   }
-//   .total_table {
-//     .ant-table-thead > tr > th,
-//     .ant-table-tbody > tr > td {
-//       // border-bottom: 1px solid #e0e0e0;
-//     }
-//     .ant-table.ant-table-bordered
-//       > .ant-table-container
-//       > .ant-table-content
-//       > table
-//       > thead
-//       > tr
-//       > th,
-//     .ant-table.ant-table-bordered
-//       > .ant-table-container
-//       > .ant-table-header
-//       > table
-//       > thead
-//       > tr
-//       > th,
-//     .ant-table.ant-table-bordered
-//       > .ant-table-container
-//       > .ant-table-body
-//       > table
-//       > thead
-//       > tr
-//       > th,
-//     .ant-table.ant-table-bordered
-//       > .ant-table-container
-//       > .ant-table-content
-//       > table
-//       > tbody
-//       > tr
-//       > td,
-//     .ant-table.ant-table-bordered
-//       > .ant-table-container
-//       > .ant-table-header
-//       > table
-//       > tbody
-//       > tr
-//       > td,
-//     .ant-table.ant-table-bordered
-//       > .ant-table-container
-//       > .ant-table-body
-//       > table
-//       > tbody
-//       > tr
-//       > td,
-//     .ant-table.ant-table-bordered
-//       > .ant-table-container
-//       > .ant-table-content
-//       > table
-//       > tfoot
-//       > tr
-//       > th,
-//     .ant-table.ant-table-bordered
-//       > .ant-table-container
-//       > .ant-table-header
-//       > table
-//       > tfoot
-//       > tr
-//       > th,
-//     .ant-table.ant-table-bordered
-//       > .ant-table-container
-//       > .ant-table-body
-//       > table
-//       > tfoot
-//       > tr
-//       > th,
-//     .ant-table.ant-table-bordered
-//       > .ant-table-container
-//       > .ant-table-content
-//       > table
-//       > tfoot
-//       > tr
-//       > td,
-//     .ant-table.ant-table-bordered
-//       > .ant-table-container
-//       > .ant-table-header
-//       > table
-//       > tfoot
-//       > tr
-//       > td,
-//     .ant-table.ant-table-bordered
-//       > .ant-table-container
-//       > .ant-table-body
-//       > table
-//       > tfoot
-//       > tr
-//       > td {
-//       // border-right: 1px solid #e0e0e0;
-//     }
-//     .ant-table.ant-table-bordered > .ant-table-container {
-//       // border: 1px solid #e0e0e0;
-//     }
-//     .ant-table-header {
-//       .ant-table-thead {
-//         tr {
-//           th {
-//             // background-color: #fbdedb;
-//           }
-//         }
-//       }
-//     }
-//   }
-//   .all_table {
-//     .ant-table-thead > tr > th,
-//     .ant-table-tbody > tr > td {
-//       // border-bottom: 1px solid #e0e0e0;
-//     }
-//     .ant-table.ant-table-bordered
-//       > .ant-table-container
-//       > .ant-table-content
-//       > table
-//       > thead
-//       > tr
-//       > th,
-//     .ant-table.ant-table-bordered
-//       > .ant-table-container
-//       > .ant-table-header
-//       > table
-//       > thead
-//       > tr
-//       > th,
-//     .ant-table.ant-table-bordered
-//       > .ant-table-container
-//       > .ant-table-body
-//       > table
-//       > thead
-//       > tr
-//       > th,
-//     .ant-table.ant-table-bordered
-//       > .ant-table-container
-//       > .ant-table-content
-//       > table
-//       > tbody
-//       > tr
-//       > td,
-//     .ant-table.ant-table-bordered
-//       > .ant-table-container
-//       > .ant-table-header
-//       > table
-//       > tbody
-//       > tr
-//       > td,
-//     .ant-table.ant-table-bordered
-//       > .ant-table-container
-//       > .ant-table-body
-//       > table
-//       > tbody
-//       > tr
-//       > td,
-//     .ant-table.ant-table-bordered
-//       > .ant-table-container
-//       > .ant-table-content
-//       > table
-//       > tfoot
-//       > tr
-//       > th,
-//     .ant-table.ant-table-bordered
-//       > .ant-table-container
-//       > .ant-table-header
-//       > table
-//       > tfoot
-//       > tr
-//       > th,
-//     .ant-table.ant-table-bordered
-//       > .ant-table-container
-//       > .ant-table-body
-//       > table
-//       > tfoot
-//       > tr
-//       > th,
-//     .ant-table.ant-table-bordered
-//       > .ant-table-container
-//       > .ant-table-content
-//       > table
-//       > tfoot
-//       > tr
-//       > td,
-//     .ant-table.ant-table-bordered
-//       > .ant-table-container
-//       > .ant-table-header
-//       > table
-//       > tfoot
-//       > tr
-//       > td,
-//     .ant-table.ant-table-bordered
-//       > .ant-table-container
-//       > .ant-table-body
-//       > table
-//       > tfoot
-//       > tr
-//       > td {
-//       // border-right: 1px solid #e0e0e0;
-//     }
-//     .ant-table.ant-table-bordered > .ant-table-container {
-//       // border: 1px solid #e0e0e0;
-//     }
-//     .ant-table-header {
-//       .ant-table-thead {
-//         tr {
-//           th {
-//             // background-color: #f5f5f5;
-//           }
-//         }
-//       }
-//     }
-//   }
-// }
-// .table_row_red {
-//   background-color: #e91e1e80;
-//   &:hover {
-//     > td {
-//       background-color:#e91e1e80!important;
-//     }
-//   }
-// }
-// .table_row_yellow {
-//   background-color: #ffeb3b6b;
-//   &:hover {
-//     > td {
-//       background-color: #ffeb3b6b !important;
-//     }
-//   }
-// }
-
-// .ant-select:not(.ant-select-customize-input) .ant-select-selector, 
-// .ant-input-affix-wrapper,
-// .ant-picker {
-//   border-radius: 4px !important;
-// }
-
-// .ellipsisOne {
-//   width: 100%;
-//   overflow: hidden;
-//   text-overflow:ellipsis;
-//   white-space: nowrap;
-// }
-
 .myForm {
 
   .ant-form-item-control-input-content,
@@ -427,4 +60,11 @@ ol {
 
 .errorClassName {
   background-color: #ffccc9 !important;
+}
+
+.ant-space-compact {
+  .selectCorpUser {
+    border-start-start-radius: 0 !important;
+    border-end-start-radius: 0 !important;
+  }
 }

+ 1 - 1
src/layout/index.less

@@ -73,7 +73,7 @@
   width: 168px;
   display: flex;
   align-items: center;
-  justify-content: center;
+  padding-left: 18px;
   text-overflow:ellipsis;
   white-space: nowrap;
 

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 27 - 85
src/layout/index.tsx


+ 100 - 0
src/pages/weComTask/API/SelectGroupChat/index.tsx

@@ -0,0 +1,100 @@
+import { useAjax } from '@/Hook/useAjax';
+import React, { useEffect, useState } from 'react';
+import { ApiParamsChatListProps, getGroupChatListApi } from '../global';
+import { App, Button, Flex, Input, Modal, Table, Typography } from 'antd';
+const { Text } = Typography;
+
+interface SelectGroupChatProps {
+    corpId: string;
+    value?: { chatId: string; chatName: string, corpName: string, corpId: string };
+    onChange?: (value?: { chatId: string; chatName: string, corpName: string, corpId: string }) => void;
+}
+
+/**
+ * 选择群
+ * @param param0 
+ * @returns 
+ */
+const SelectGroupChat: React.FC<SelectGroupChatProps> = ({ corpId, value, onChange }) => {
+
+    /****************************************/
+    const { message } = App.useApp();
+    const [queryParams, setQueryParams] = useState<ApiParamsChatListProps>({
+        corpId,
+        pageNum: 1,
+        pageSize: 20,
+    });
+    const [visible, setVisible] = useState<boolean>(false);
+    const [selectedRows, setselectedRows] = useState<any[]>([])
+
+    const getGroupChatList = useAjax((params) => getGroupChatListApi(params))
+    /****************************************/
+
+    useEffect(() => {
+        if (value) {
+            setselectedRows([{ chatId: value.chatId, chatName: value.chatName, corpId: value.corpId, corpName: value.corpName }]);
+        }
+    }, [value])
+
+    useEffect(() => {
+        if (visible) {
+            getGroupChatList.run({ ...queryParams, corpId })
+        }
+    }, [visible, queryParams]);
+
+    const handleOk = () => {
+        if (selectedRows?.length) {
+            onChange?.({ chatId: selectedRows[0]?.chatId, chatName: selectedRows[0]?.chatName, corpId: selectedRows[0]?.corpId, corpName: selectedRows[0]?.corpName });
+            setVisible(false);
+        } else {
+            message.error('请选择群聊');
+        }
+    };
+
+    return <Flex gap={10} align='center'>
+        {value?.chatId && <div style={{ maxWidth: 'calc(100% - 90px)', overflow: 'hidden' }}><Text ellipsis>{value?.chatName || value?.chatId}</Text></div>}
+        <Button type="primary" onClick={() => setVisible(true)}>选择素材群</Button>
+        {visible && <Modal
+            title={<strong>选择素材群</strong>}
+            open={visible}
+            onCancel={() => setVisible(false)}
+            onOk={handleOk}
+        >
+            <Input.Search placeholder="请输入群名称" onSearch={(value) => { setQueryParams({ ...queryParams, chatName: value, pageNum: 1 }); }} allowClear style={{ marginBottom: 10, width: 250 }} />
+
+            <Table
+                columns={[
+                    {
+                        title: '群名称',
+                        dataIndex: 'chatName',
+                        key: 'chatName',
+                        render: (text: string, record: any) => <span>{text || record?.chatId || '--'}</span>,
+                    }
+                ]}
+                scroll={{ y: 400 }}
+                bordered
+                dataSource={getGroupChatList.data?.data?.records}
+                rowKey="chatId"
+                pagination={{
+                    total: getGroupChatList.data?.data?.total,
+                    current: getGroupChatList?.data?.data?.current || 1,
+                    pageSize: getGroupChatList?.data?.data?.size || 20,
+                    onChange: (page: number, pageSize: number) => {
+                        setQueryParams({ ...queryParams, pageNum: page, pageSize })
+                    }
+                }}
+                rowSelection={{
+                    selectedRowKeys: selectedRows?.map((item: any) => item?.chatId),
+                    type: 'radio',
+                    onChange(_, selectedRows) {
+                        console.log(selectedRows)
+                        setselectedRows(selectedRows)
+                    },
+                }}
+            />
+
+        </Modal>}
+    </Flex>;
+};
+
+export default SelectGroupChat;

+ 99 - 0
src/pages/weComTask/API/bookLink/index.ts

@@ -0,0 +1,99 @@
+
+import request from "@/utils/request";
+const { api } = process.env.CONFIG;
+
+export interface GetCorpAutoLinkListProps {
+    pageNum: number,
+    pageSize: number,
+    platform?: string,
+    mpAccountId?: number
+    linkType?: number
+    channelName?: string
+    channelType?: number
+    createTimeStart?: string
+    createTimeEnd?: string
+}
+
+/**
+ * 书城链接列表
+ * @param data 
+ * @returns 
+ */
+export function getCorpAutoLinkListApi(data: GetCorpAutoLinkListProps) {
+    return request({
+        url: api + `/corpOperation/corp/autoLink/listOfPage`,
+        method: 'POST',
+        data
+    })
+}
+
+export interface AddCorpAutoLinkProps {
+    mpAccountIds: number[],
+    platform: string,
+    yueWenCreateLinkDTOList: { [x: string]: any }
+}
+
+/**
+ * 新增书城链接生成
+ * @param data 
+ * @returns 
+ */
+export function addCorpAutoLinkApi(data: AddCorpAutoLinkProps) {
+    return request({
+        url: api + `/corpOperation/corp/autoLink/add`,
+        method: 'POST',
+        data
+    })
+}
+
+interface GetFreeChapterListProps {
+    bookId: number
+    mpAccountIds: number[]
+    platform: string
+}
+/**
+ * 获取免费章节
+ * @param data 
+ * @returns 
+ */
+export function getFreeChapterListApi(data: GetFreeChapterListProps) {
+    return request({
+        url: api + `/corpOperation/corp/autoLink/freeChapterList`,
+        method: 'POST',
+        data
+    })
+}
+
+export interface GetAutoLinkBookListProps {
+    pageNum: number
+    pageSize: number
+    bookName?: string,
+    bookId?: string,
+    platformKey?: string
+}
+
+/**
+ * 书籍列表
+ * @param data 
+ * @returns 
+ */
+export function getAutoLinkBookListApi(data: GetAutoLinkBookListProps) {
+    return request({
+        url: api + `/corpOperation/corp/autoLink/bookList`,
+        method: 'POST',
+        data
+    })
+}
+
+/**
+ * 删除链接
+ * @param data 
+ * @returns 
+ */
+export function delAutoLinkApi(data: { linkIds: number[] }) {
+    return request({
+        url: api + `/corpOperation/corp/autoLink/batchDelByIds`,
+        method: 'POST',
+        data: data.linkIds
+    })
+}

+ 111 - 9
src/pages/weComTask/API/businessPlan/create.ts

@@ -9,8 +9,21 @@ const { api } = process.env.CONFIG;
 export async function addTaskApi(data: BUSINES_SPLAN_API.AddTaskProps) {
     return request({
         url: api + `/corpOperation/corp/create/project/task/add`,
-        method: 'POST', 
-        data 
+        method: 'POST',
+        data
+    });
+}
+
+/**
+ * 修改
+ * @param data 
+ * @returns 
+ */
+export async function updateTaskApi({ projectId, ...data }: BUSINES_SPLAN_API.AddTaskProps) {
+    return request({
+        url: api + `/corpOperation/corp/create/project/task/edit/${projectId}`,
+        method: 'POST',
+        data
     });
 }
 
@@ -22,8 +35,8 @@ export async function addTaskApi(data: BUSINES_SPLAN_API.AddTaskProps) {
 export async function getProjectListApi(data: BUSINES_SPLAN_API.GetProjectListProps) {
     return request({
         url: api + `/corpOperation/corp/create/project/listOfPage`,
-        method: 'POST', 
-        data 
+        method: 'POST',
+        data
     });
 }
 
@@ -34,8 +47,47 @@ export async function getProjectListApi(data: BUSINES_SPLAN_API.GetProjectListPr
  */
 export async function getProjectDetailsApi(projectId: string) {
     return request({
-        url: api + `/corpOperation/corp/create/project/getDetail/${projectId}}`,
-        method: 'GET', 
+        url: api + `/corpOperation/corp/create/project/getDetail/${projectId}`,
+        method: 'GET',
+    });
+}
+
+/**
+ * 获取计划详情
+ * @param projectId 
+ * @returns 
+ */
+export async function getCreateDetailsApi(projectId: string) {
+    return request({
+        url: api + `/corpOperation/corp/create/project/selectById/${projectId}`,
+        method: 'POST',
+    });
+}
+
+
+/**
+ * 删除计划
+ * @param params 
+ * @returns 
+ */
+export async function delProjectApi(data: { projectIds: number[] }) {
+    return request({
+        url: api + `/corpOperation/corp/create/project/batchDelByIds`,
+        method: 'POST',
+        data: data.projectIds
+    });
+}
+
+/**
+ * 停止计划
+ * @param data 
+ * @returns 
+ */
+export async function cancelProjectApi(data: { projectIds: number[],  pause: boolean }) {
+    return request({
+        url: api + `/corpOperation/corp/create/project/cancel/${data.pause}`,
+        method: 'POST',
+        data: data.projectIds
     });
 }
 
@@ -80,9 +132,9 @@ export async function api_post_welcomeMsg_welcomeMsgCorpUserList(data: WelcomeMs
 
 
 export interface GetSendTaskGroupuserListProps {
-    corpId: string,
     pageNum: number,
     pageSize: number,
+    corpId?: string,
     msgId?: string,
     sendTimeEndTime?: string
     sendTimeStartTime?: string
@@ -105,6 +157,19 @@ export async function getSendTaskGroupuserListApi(data: GetSendTaskGroupuserList
     });
 }
 
+/**
+ * 群群发获取下发企微号列表
+ * @param data 
+ * @returns 
+ */
+export async function getSendGroupTaskGroupuserListApi(data: GetSendTaskGroupuserListProps) {
+    return request({
+        url: api + `/corpOperation/corp/group/send/task/user/list`,
+        method: 'POST',
+        data
+    });
+}
+
 /**
  * 获取高级群发下发企微号列表
  * @param data 
@@ -122,7 +187,7 @@ export async function getMessageSendTaskuserListApi(data: GetSendTaskGroupuserLi
  * 高级群发消息重试
  * @returns 
  */
-export async function putMessageUpdateApi(id:any) {
+export async function putMessageUpdateApi(id: any) {
     return request({
         url: `/corp/message/send/taskLog/status/update/${id}`,
         method: 'PUT',
@@ -208,7 +273,7 @@ export async function reSendGroupTaskApi(data: { id: number, corpUserId?: string
 }
 
 export interface GetSendTaskGroupMsgListProps {
-    corpId: string,
+    corpId?: string,
     pageNum: number,
     pageSize: number,
     taskId: number,
@@ -228,6 +293,43 @@ export async function getSendTaskGroupMsgListApi(data: GetSendTaskGroupMsgListPr
     });
 }
 
+/**
+ * 获取群群发记录列表
+ * @param data 
+ * @returns 
+ */
+export async function getSendGroupTaskGroupMsgListApi(data: GetSendTaskGroupMsgListProps) {
+    return request({
+        url: api + `/corpOperation/corp/group/send/task/msg/list`,
+        method: 'POST',
+        data
+    });
+}
+
+/**
+ * 转发记录
+ * @param data 
+ * @returns 
+ */
+export async function getSendGroupTaskRobotListApi(data: GetSendTaskGroupuserListProps) {
+    return request({
+        url: api + `/corpOperation/corp/group/send/task/robot/list`,
+        method: 'POST',
+        data
+    });
+}
+
+/**
+ * 转发消息重试
+ * @returns 
+ */
+export async function putMessageUpdateReloadApi(taskLogId: any) {
+    return request({
+        url: `/corp/group/send/taskLog/status/update/${taskLogId}`,
+        method: 'PUT',
+    });
+}
+
 /**
  * 获取高级群发记录列表
  * @param data 

+ 3 - 1
src/pages/weComTask/API/businessPlan/typings.d.ts

@@ -22,6 +22,7 @@ declare namespace BUSINES_SPLAN_API {
         welcomeMsgTemplateDTO?: { [x: string]: any }
         groupSendTaskAddDTO?: { [x: string]: any }
         externalUserTransferTasksDTO?: { [x: string]: any }
+        projectId?: number
     }
 
     interface GetProjectListProps {
@@ -29,6 +30,7 @@ declare namespace BUSINES_SPLAN_API {
         pageSize: number,
         projectName?: string
         createTimeMin?: string,
-        createTimeMax?: string
+        createTimeMax?: string,
+        projectType?: number
     }
 }

+ 11 - 0
src/pages/weComTask/API/corpUserAssign/index.ts

@@ -154,6 +154,17 @@ export function getAllWxListApi() {
         method: 'GET',
     })
 }
+
+/**
+ * 获取已绑定书城公众号列表
+ * @returns 
+ */
+export function getBindMpListApi() {
+    return request({
+        url:api+ `/corpOperation/corpUser/getBindMpList`,
+        method: 'GET',
+    })
+}
 /**
  * 获取公众号变更记录
  * */ 

+ 1 - 0
src/pages/weComTask/API/corpUserManage/typings.d.ts

@@ -13,6 +13,7 @@ declare namespace CORP_USER_ASSIGN_API {
         stopUse?: boolean,
         sysUserId?: number
         corpUserName?: string
+        mpAccountId?: number
     }
 
     interface PutCorpUserTOProps {

+ 144 - 2
src/pages/weComTask/API/global.ts

@@ -73,6 +73,129 @@ export async function getTagGroupListApi(data: any) {
     });
 }
 
+export interface ApiParamsChatListProps {
+    pageNum: number,
+    pageSize: number,
+    corpId?: string,//企业ID
+    corpUserId?: string[],//企微客服id
+    chatName?: string,//群名称
+    createTimeEnd?: string,//群创建结束时间
+    createTimeStart?: string,//群创建开始时间
+    managerName?: string,//群管理名称
+    ownerName?: string,//群主名称
+    userCountMax?: string | number,//群人数最大值
+    userCountMin?: string | number,//群人数最小值
+    chatIdList?: number[],
+    remark?: string
+}
+
+/**
+ * 企业微信群列表
+ * @param data 
+ * @returns 
+ */
+export async function getGroupChatListApi(data: ApiParamsChatListProps) {
+    return request({
+        url: '/corp/group/chat/list',
+        method: 'POST',
+        data
+    })
+}
+
+/**
+ * 总计
+ * @param data 
+ * @returns 
+ */
+export async function getGroupChatCountApi(data: ApiParamsChatListProps) {
+    return request({
+        url: '/corp/group/chat/list/count',
+        method: 'POST',
+        data
+    })
+}
+
+/**企业微信群成员列表*/
+export async function getGroupChatUserListApi(data: ApiParamsChatListProps) {
+    return request({
+        url: '/corp/group/chat/user/list',
+        method: 'POST',
+        data
+    })
+}
+
+/**
+ * 设置备注
+ * @param data 
+ * @returns 
+ */
+export async function setRemarkApi(data: ApiParamsChatListProps) {
+    return request({
+        url: '/corp/group/chat/remark',
+        method: 'POST',
+        data,
+    })
+}
+
+/**
+ * 指派公众号
+ * @param data 
+ * @returns 
+ */
+export async function setQLMpApi(data: { chatIds: string[], corpId: string, mpAccountId: number }) {
+    return request({
+        url: '/corp/group/chat/configCorpGroupChats',
+        method: 'PUT',
+        data,
+    })
+}
+
+/**
+ * 获取群聊变更记录
+ * @param param0 
+ * @returns 
+ */
+export async function getMpChangeRecordListApi({ corpId, chatId }: { corpId: string, chatId: string }) {
+    return request({
+        url: `/corp/group/chat/getMpChangeRecord/${corpId}/${chatId}`,
+        method: 'GET'
+    })
+}
+
+/**
+ * 解散群聊
+ * @param data 
+ * @returns 
+ */
+export async function disbandChatApi(data: { corpId: string, chatIdList: string[] }) {
+    return request({
+        url: '/corp/group/chat/disband',
+        method: 'POST',
+        data
+    })
+}
+
+
+export interface getDisbandChatLogListProps {
+    pageNum: number,
+    pageSize: number,
+    corpId: string,
+    chatName?: string
+    sysUserId?: number
+}
+/**
+ * 解散群聊日志列表
+ * @param data 
+ * @returns 
+ */
+export async function getDisbandChatLogListApi(data: getDisbandChatLogListProps) {
+    return request({
+        url: '/corp/group/chat/disband/list',
+        method: 'POST',
+        data
+    })
+}
+
 /**获取背景图ID列表*/
 export async function api_get_img_typeList() {
     return request({
@@ -141,7 +264,26 @@ export async function getAllBookApi() {
 export async function getGameListNewApi(data: { sourceSystem: string }) {
     return request({
         url: api + `/gameData/choice/game/list`,
-        method: 'POST', 
-        data 
+        method: 'POST',
+        data
+    });
+}
+
+
+// 根据书城获取书籍
+export async function getBookListApi(data: { pageNum: number, pageSize: number, platformKey: string, bookName?: string  }) {
+    return request({
+        url: `/corp/externalUser/platform/bookList`,
+        method: 'POST',
+        data
+    });
+}
+
+// 根据书城书籍获取章节
+export async function getBookChapterListListApi(data: { pageNum: number, pageSize: number, platformKey: string, bookName: string, chapterName?: string  }) {
+    return request({
+        url: `/corp/externalUser/platform/chapterList`,
+        method: 'POST',
+        data
     });
 }

+ 143 - 0
src/pages/weComTask/API/groupChat/index.ts

@@ -0,0 +1,143 @@
+import request from "@/utils/request";
+const { api } = process.env.CONFIG;
+
+/**
+ * 群聊创建
+ * @param data 
+ * @returns 
+ */
+export async function addPullGroupTaskApi(data: GROUP_CHAT_API.AddPullGroupTaskProps) {
+    return request({
+        url: api + `/corpOperation/corp/pullGroup/project/task/add`,
+        method: 'POST',
+        data
+    });
+}
+
+/**
+ * 计划修改
+ * @param param0 
+ * @returns 
+ */
+export async function updateGroupTaskApi({ projectId, ...data }: GROUP_CHAT_API.AddPullGroupTaskProps) {
+    return request({
+        url: api + `/corpOperation/corp/pullGroup/project/task/edit/${projectId}`,
+        method: 'POST',
+        data
+    });
+}
+
+/**
+ * 详情
+ * @param data 
+ * @returns 
+ */
+export async function getCreateDetailsApi(projectId: number) {
+    return request({
+        url: api + `/corpOperation/corp/pullGroup/project/selectById/${projectId}`,
+        method: 'POST'
+    });
+}
+
+
+/**
+ * 计划列表
+ * @param data 
+ * @returns 
+ */
+export async function getProjectListApi(data: GROUP_CHAT_API.GetProjectListProps) {
+    return request({
+        url: api + `/corpOperation/corp/pullGroup/project/listOfPage`,
+        method: 'POST',
+        data
+    });
+}
+
+/**
+ * 计划列表
+ * @param projectId 
+ * @returns 
+ */
+export async function getProjectLogListApi(projectId: number) {
+    return request({
+        url: api + `/corpOperation/corp/pullGroup/project/getDetail/${projectId}`,
+        method: 'GET'
+    });
+}
+
+
+/**
+ * 任务日志列表
+ * @param data 
+ * @returns 
+ */
+export async function getProjectTaskLogListApi(data: { pageNum: number, pageSize: number, taskId: number }) {
+    return request({
+        url: api + `/corpOperation/corp/pullGroup/project/logListOfPage`,
+        method: 'POST',
+        data
+    });
+}
+
+/**
+ * 总数
+ * @param taskId 
+ * @returns 
+ */
+export async function getProjectTaskLogCountApi(taskId: number) {
+    return request({
+        url: api + `/corpOperation/corp/pullGroup/project/count/${taskId}`,
+        method: 'POST'
+    });
+}
+
+/**
+ * 任务日志详情列表
+ * @param data 
+ * @returns 
+ */
+export async function getProjectLogDetailsListApi(taskLogId: number) {
+    return request({
+        url: api + `/corpOperation/corp/pullGroup/project/logChatList/${taskLogId}`,
+        method: 'GET'
+    });
+}
+
+
+/**
+ * 删除计划
+ * @param params 
+ * @returns 
+ */
+export async function delProjectApi(data: { projectIds: number[] }) {
+    return request({
+        url: api + `/corpOperation/corp/pullGroup/project/batchDelByIds`,
+        method: 'POST',
+        data: data.projectIds
+    });
+}
+
+/**
+ * 停止计划
+ * @param data 
+ * @returns 
+ */
+export async function cancelProjectApi(data: { projectIds: number[], pause: boolean }) {
+    return request({
+        url: api + `/corpOperation/corp/pullGroup/project/cancel/${data.pause}`,
+        method: 'POST',
+        data: data.projectIds
+    });
+}
+
+/**
+ * 获取计划详情
+ * @param projectId
+ * @returns
+ */
+// export async function getCreateDetailsApi(projectId: string) {
+//     return request({
+//         url: api + `/corpOperation/corp/create/project/selectById/${projectId}`,
+//         method: 'POST',
+//     });
+// }

+ 15 - 0
src/pages/weComTask/API/groupChat/typings.d.ts

@@ -0,0 +1,15 @@
+declare namespace GROUP_CHAT_API {
+    interface AddPullGroupTaskProps {
+        groupSendName: string; // 任务名称
+        corpUsers: { [x: string]: any }[];
+        strategyList: { [x: string]: any }[];
+        projectId?: number;
+    }
+    interface GetProjectListProps {
+        pageNum: number,
+        pageSize: number,
+        projectName?: string
+        createTimeMin?: string,
+        createTimeMax?: string
+    }
+}

+ 148 - 0
src/pages/weComTask/API/groupLeaderManage/index.ts

@@ -0,0 +1,148 @@
+import request from "@/utils/request";
+const { api } = process.env.CONFIG;
+
+export interface getCorpUserChatListProps {
+    pageNum: number,
+    pageSize: number,
+    putUserId?: number,
+    operUserId?: number,
+    corpUserName?: string,
+    corpIds?: string[],
+    corpUserIds?: string[],
+    status?: number
+}
+
+/**
+ * 群主号列表
+ * @param data 
+ * @returns 
+ */
+export function getCorpUserChatListApi(data: getCorpUserChatListProps) {
+    return request({
+        url: api + `/corpOperation/corpUserChat/list`,
+        method: 'POST',
+        data
+    })
+}
+
+/**
+ * 删除群主号
+ * @param data 
+ * @returns 
+ */
+export function delCorpUserChatApi(data: number[]) {
+    return request({
+        url: api + `/corpOperation/corpUserChat/deleteBatch`,
+        method: 'DELETE',
+        data
+    })
+}
+
+
+interface AddCorpUserChatProps {
+    corpUserList: {
+        corpId: string,
+        corpUserId: string
+    }[]
+}
+
+/**
+ * 新增群主号
+ * @param data 
+ * @returns 
+ */
+export function addCorpUserChatApi(data: AddCorpUserChatProps) {
+    return request({
+        url: api + `/corpOperation/corpUserChat/add`,
+        method: 'POST',
+        data
+    })
+}
+
+
+/**
+ * 查询群主号绑定的客户号或者机器人号详情
+ * @param param0 
+ * @returns 
+ */
+export function getBindDetailListApi({ corpUserChatId, bindType }: { corpUserChatId: number, bindType: number }) {
+    return request({
+        url: api + `/corpOperation/corpUserChat/corpUserChatRelation/listBindDetail/${corpUserChatId}/${bindType}`,
+        method: 'GET'
+    })
+}
+
+interface BindCorpUserChatProps {
+    bindType: 1 | 2,
+    corpUserChatId: number,
+    corpUserList: {
+        corpId: string,
+        corpUserId: string
+        transferExternalUserId: string
+    }[]
+}
+
+/**
+ * 绑定客户号或者机器人号
+ * @param data 
+ * @returns 
+ */
+export function bindCorpUserChatApi(data: BindCorpUserChatProps) {
+    return request({
+        url: api + `/corpOperation/corpUserChat/bind`,
+        method: 'POST',
+        data
+    })
+}
+
+/**
+ * 解绑客户号或者机器人号
+ * @param data 
+ * @returns 
+ */
+export function unbindCorpUserChatApi(data: BindCorpUserChatProps) {
+    return request({
+        url: api + `/corpOperation/corpUserChat/unbind`,
+        method: 'POST',
+        data
+    })
+}
+
+/**
+ * 同步当前群主号下的所有群聊的机器人
+ * @param data 
+ * @returns 
+ */
+export function syncCorpChatRobotApi(data: { corpId: string, corpUserId: string, robotIdList: string[] }) {
+    return request({
+        url: `/corp/group/chat/syncCorpChatRobot`,
+        method: 'PUT',
+        data
+    })
+}
+
+/**
+ * 所有群主号
+ * @param params 
+ * @returns 
+ */
+export function getCorpUserChatAllListApi(params: { corpUserName?: string }) {
+    return request({
+        url: api + `/corpOperation/corpUserChat/listAll`,
+        method: 'POST',
+        params
+    })
+}
+
+/**
+ * 查询群主号下客服号 机器人号
+ * @param params 
+ * @returns 
+ */
+export function getCorpUserChatCorpUserListApi(data: { bindType: 1 | 2, corpUserChatIds: number[] }) {
+    return request({
+        url: api + `/corpOperation/corpUserChat/corpUserChatRelation/listBindDetails`,
+        method: 'POST',
+        data
+    })
+}

+ 96 - 0
src/pages/weComTask/API/groupManage/index.ts

@@ -0,0 +1,96 @@
+import request from "@/utils/request";
+const { api } = process.env.CONFIG;
+
+export interface GetProjectGroupsListProps {
+    pageNum: number,
+    pageSize: number,
+    createTimeEnd?: string,
+    createTimeStart?: string,
+    name?: string
+}
+
+/**
+ * 项目组列表
+ * @param data 
+ * @returns 
+ */
+export function getProjectGroupsListApi(data: GetProjectGroupsListProps) {
+    return request({
+        url: api + `/corpOperation/projectGroups/list`,
+        method: 'POST',
+        data
+    })
+}
+
+/**
+ * 全部项目组列表
+ * @returns 
+ */
+export function getProjectGroupsAllListApi() {
+    return request({
+        url: api + `/corpOperation/projectGroups/listAll`,
+        method: 'POST'
+    })
+}
+
+export interface AddProjectGroupsProps {
+    name: string,
+    description?: string
+    groupId?: number
+}
+
+/**
+ * 新增项目组
+ * @param data 
+ * @returns 
+ */
+export function addProjectGroupsApi(data: AddProjectGroupsProps) {
+    return request({
+        url: api + `/corpOperation/projectGroups/create`,
+        method: 'POST',
+        data
+    })
+}
+
+/**
+ * 修改项目组
+ * @param param0 
+ * @returns 
+ */
+export function editProjectGroupsApi({ groupId, ...data }: AddProjectGroupsProps) {
+    return request({
+        url: api + `/corpOperation/projectGroups/edit/${groupId}`,
+        method: 'POST',
+        data
+    })
+}
+
+
+/**
+ * 删除项目组
+ * @param param0 
+ * @returns 
+ */
+export function delProjectGroupsApi(groupId: number) {
+    return request({
+        url: api + `/corpOperation/projectGroups/deleteGroup/${groupId}`,
+        method: 'DELETE'
+    })
+}
+
+export interface UpdateMembersProps {
+    groupId: number,
+    userIds: number[]
+}
+/**
+ * 更新成员
+ * @param param0 
+ * @returns 
+ */
+export function updateMembersApi({ groupId, userIds }: UpdateMembersProps) {
+    return request({
+        url: api + `/corpOperation/projectGroups/updateMembers/${groupId}`,
+        method: 'POST',
+        data: userIds
+    })
+}

+ 136 - 0
src/pages/weComTask/components/SelectBook/index.tsx

@@ -0,0 +1,136 @@
+import { useAjax } from "@/Hook/useAjax"
+import { Button, Col, Input, Modal, Row, Space, Table, Tag, Typography } from "antd"
+import React, { useEffect, useState } from "react"
+import { getBookListApi } from "../../API/global"
+
+interface Props {
+    platformKey?: string
+    value?: string[],
+    onChange?: (value?: string[]) => void
+}
+
+/**
+ * 选择书籍
+ * @returns 
+ */
+const SelectBook: React.FC<Props> = ({ platformKey, value, onChange }) => {
+
+    /**************************************/
+    const [queryForm, setQueryForm] = useState<{ pageNum: number, pageSize: number, bookName?: string }>({ pageNum: 1, pageSize: 20 })
+    const [queryFormNew, setQueryFormNew] = useState<{ pageNum: number, pageSize: number, bookName?: string }>({ pageNum: 1, pageSize: 20 })
+    const [visible, setVisible] = useState<boolean>(false)
+    const [selectedRow, setSelectedRow] = useState<any[]>([])//选择下发企微号
+
+    const getBookList = useAjax((params) => getBookListApi(params))
+    /**************************************/
+
+    useEffect(() => {
+        if (value && value?.length > 0) {
+            setSelectedRow(value.map(item => ({ bookName: item })))
+        } else {
+            setSelectedRow([])
+        }
+    }, [value])
+
+    useEffect(() => {
+        if (platformKey) {
+            getBookList.run({ ...queryForm, platformKey })
+        }
+    }, [queryForm, platformKey])
+
+    const handleOk = () => {
+        onChange?.(selectedRow?.length > 0 ? selectedRow.map(item => item.bookName) : undefined)
+        setVisible(false)
+    }
+
+    return <div style={{ width: '100%', display: 'flex', alignItems: 'center' }}>
+        {(value && value?.length > 0) && <div style={{ maxWidth: 'calc(100% - 72px)', marginRight: 8 }}><Typography.Text ellipsis={{ tooltip: true }}>{value?.toString()}</Typography.Text></div>}
+        <Button type="primary" disabled={!platformKey} onClick={() => { setQueryForm({ ...queryForm, pageNum: 1 }); setVisible(true) }}>{(value && value?.length > 0) ? '修改书籍' : '选择书籍'}</Button>
+
+        {visible && <Modal
+            title="选择书籍"
+            open={visible}
+            onCancel={() => setVisible(false)}
+            onOk={handleOk}
+        >
+            <Space direction="vertical" style={{ width: '100%' }}>
+                <Space>
+                    <Input placeholder="请输入书名" allowClear value={queryFormNew?.bookName} onChange={(e) => setQueryFormNew({ ...queryFormNew, bookName: e.target.value, pageNum: 1 })} />
+                    <Button type="primary" onClick={() => setQueryForm({ ...queryFormNew })}>搜索</Button>
+                </Space>
+                <Row>
+                    <Col span={17}>
+                        <Table
+                            style={{ marginBottom: 1 }}
+                            dataSource={getBookList?.data?.data?.records?.map((item: any, index: number) => ({ bookName: item, id: item + '_' + index }))}
+                            columns={[{
+                                title: '书名',
+                                dataIndex: 'bookName',
+                                key: 'bookName',
+                            }]}
+                            scroll={{ y: 400 }}
+                            rowKey={'bookName'}
+                            size='small'
+                            loading={getBookList.loading}
+                            bordered
+                            rowSelection={{
+                                type: 'checkbox',
+                                onSelect: (rocord, selected, rows) => {
+                                    if (selected) {
+                                        setSelectedRow([...selectedRow, rocord])
+                                    } else {
+                                        setSelectedRow(selectedRow?.filter(item => item.bookName !== rocord?.bookName))
+                                    }
+                                },
+                                onSelectAll: (selected, _, changeRows) => {
+                                    if (selected) {
+                                        setSelectedRow([...selectedRow, ...changeRows])
+                                    } else {
+                                        let arr = selectedRow?.filter(item => changeRows?.every(i => i.bookName !== item.bookName))
+                                        setSelectedRow(arr)
+                                    }
+                                },
+                                selectedRowKeys: selectedRow?.map(item => item?.bookName),
+                                getCheckboxProps: (record: any) => ({
+                                    name: record?.bookName
+                                }),
+                            }}
+                            pagination={{
+                                total: getBookList?.data?.data?.total,
+                                showTotal: (total) => <Tag color="cyan">总共{total}数据</Tag>,
+                                showSizeChanger: true,
+                                showLessItems: true,
+                                defaultCurrent: 1,
+                                defaultPageSize: 200,//默认初始的每页条数
+                                current: getBookList?.data?.data?.current || 1,
+                                pageSize: getBookList?.data?.data?.size || 20,
+                                onChange: (page, pageSize) => {
+                                    setQueryForm({ ...queryForm, pageNum: page, pageSize })
+                                }
+                            }}
+                        />
+                    </Col>
+                    <Col span={7} style={{ paddingLeft: 6 }}>
+                        <Space style={{ width: '100%' }} direction="vertical">
+                            <div style={{ display: 'flex', justifyContent: 'space-between' }}><Typography.Text>已选:({selectedRow.length || 0})</Typography.Text><a onClick={() => { setSelectedRow([]) }}>清空选择</a></div>
+                            <div style={{ maxHeight: 430, overflow: 'hidden', overflowY: 'auto' }}>
+                                {selectedRow?.map(item => {
+                                    return <div key={item?.bookName} style={{ marginBottom: 5 }}>
+                                        <Tag closable onClose={(e) => {
+                                            e.preventDefault();
+                                            setSelectedRow([...selectedRow.filter(s => s?.bookName !== item?.bookName)])
+                                        }} style={{ padding: 5 }}>
+                                            <div style={{ maxWidth: 110, display: 'inline-block' }}><Typography.Text ellipsis={{ tooltip: true }}>{item?.bookName}</Typography.Text></div>
+                                        </Tag>
+                                    </div>
+                                })}
+                            </div>
+                        </Space>
+                    </Col>
+                </Row>
+            </Space>
+        </Modal>}
+    </div>
+}
+
+export default React.memo(SelectBook)

+ 139 - 0
src/pages/weComTask/components/SelectChapter/index.tsx

@@ -0,0 +1,139 @@
+import { useAjax } from "@/Hook/useAjax"
+import { Button, Col, Input, Modal, Row, Space, Table, Tag, Typography } from "antd"
+import React, { useEffect, useState } from "react"
+import { getBookChapterListListApi } from "../../API/global"
+
+interface Props {
+    bookName?: string
+    platformKey?: string
+    value?: string[],
+    onChange?: (value?: string[]) => void
+}
+
+/**
+ * 选择书籍
+ * @returns 
+ */
+const SelectChapter: React.FC<Props> = ({ bookName, platformKey, value, onChange }) => {
+
+    /**************************************/
+    const [queryForm, setQueryForm] = useState<{ pageNum: number, pageSize: number, chapterName?: string }>({ pageNum: 1, pageSize: 20 })
+    const [queryFormNew, setQueryFormNew] = useState<{ pageNum: number, pageSize: number, chapterName?: string }>({ pageNum: 1, pageSize: 20 })
+    const [visible, setVisible] = useState<boolean>(false)
+    const [selectedRow, setSelectedRow] = useState<any[]>([])//选择下发企微号
+
+    const getBookChapterListList = useAjax((params) => getBookChapterListListApi(params))
+    /**************************************/
+
+    useEffect(() => {
+        if (value && value?.length > 0) {
+            setSelectedRow(value.map(item => ({ chapterName: item })))
+        } else {
+            setSelectedRow([])
+        }
+    }, [value])
+
+    useEffect(() => {
+        if (bookName) {
+            getBookChapterListList.run({ ...queryForm, platformKey, bookName })
+        } else if (getBookChapterListList?.data) {
+            getBookChapterListList.mutate({ data: { records: [], size: 20, total: 0, pages: 0, current: 1 } })
+        }
+    }, [queryForm, platformKey, bookName])
+
+    const handleOk = () => {
+        onChange?.(selectedRow?.length > 0 ? selectedRow.map(item => item.chapterName) : undefined)
+        setVisible(false)
+    }
+
+    return <div style={{ width: '100%', display: 'flex', alignItems: 'center' }}>
+        {(value && value?.length > 0) && <div style={{ maxWidth: 'calc(100% - 72px)', marginRight: 8 }}><Typography.Text ellipsis={{ tooltip: true }}>{value?.toString()}</Typography.Text></div>}
+        <Button type="primary" disabled={!platformKey} onClick={() => { setQueryForm({ ...queryForm, pageNum: 1 }); setVisible(true) }}>{(value && value?.length > 0) ? '修改章节' : '选择章节'}</Button>
+
+        {visible && <Modal
+            title="选择章节"
+            open={visible}
+            onCancel={() => setVisible(false)}
+            onOk={handleOk}
+        >
+            <Space direction="vertical" style={{ width: '100%' }}>
+                <Space>
+                    <Input placeholder="请输入章节名" allowClear value={queryFormNew?.chapterName} onChange={(e) => setQueryFormNew({ ...queryFormNew, chapterName: e.target.value, pageNum: 1 })} />
+                    <Button type="primary" onClick={() => setQueryForm({ ...queryFormNew })}>搜索</Button>
+                </Space>
+                <Row>
+                    <Col span={17}>
+                        <Table
+                            style={{ marginBottom: 1 }}
+                            dataSource={getBookChapterListList?.data?.data?.records?.map((item: any, index: number) => ({ chapterName: item, id: item + '_' + index }))}
+                            columns={[{
+                                title: '章节',
+                                dataIndex: 'chapterName',
+                                key: 'chapterName',
+                            }]}
+                            scroll={{ y: 400 }}
+                            rowKey={'chapterName'}
+                            size='small'
+                            loading={getBookChapterListList.loading}
+                            bordered
+                            rowSelection={{
+                                type: 'checkbox',
+                                onSelect: (rocord, selected, rows) => {
+                                    if (selected) {
+                                        setSelectedRow([...selectedRow, rocord])
+                                    } else {
+                                        setSelectedRow(selectedRow?.filter(item => item.chapterName !== rocord?.chapterName))
+                                    }
+                                },
+                                onSelectAll: (selected, _, changeRows) => {
+                                    if (selected) {
+                                        setSelectedRow([...selectedRow, ...changeRows])
+                                    } else {
+                                        let arr = selectedRow?.filter(item => changeRows?.every(i => i.chapterName !== item.chapterName))
+                                        setSelectedRow(arr)
+                                    }
+                                },
+                                selectedRowKeys: selectedRow?.map(item => item?.chapterName),
+                                getCheckboxProps: (record: any) => ({
+                                    name: record?.chapterName
+                                }),
+                            }}
+                            pagination={{
+                                total: getBookChapterListList?.data?.data?.total,
+                                showTotal: (total) => <Tag color="cyan">总共{total}数据</Tag>,
+                                showSizeChanger: true,
+                                showLessItems: true,
+                                defaultCurrent: 1,
+                                defaultPageSize: 200,//默认初始的每页条数
+                                current: getBookChapterListList?.data?.data?.current || 1,
+                                pageSize: getBookChapterListList?.data?.data?.size || 20,
+                                onChange: (page, pageSize) => {
+                                    setQueryForm({ ...queryForm, pageNum: page, pageSize })
+                                }
+                            }}
+                        />
+                    </Col>
+                    <Col span={7} style={{ paddingLeft: 6 }}>
+                        <Space style={{ width: '100%' }} direction="vertical">
+                            <div style={{ display: 'flex', justifyContent: 'space-between' }}><Typography.Text>已选:({selectedRow.length || 0})</Typography.Text><a onClick={() => { setSelectedRow([]) }}>清空选择</a></div>
+                            <div style={{ maxHeight: 430, overflow: 'hidden', overflowY: 'auto' }}>
+                                {selectedRow?.map(item => {
+                                    return <div key={item?.chapterName} style={{ marginBottom: 5 }}>
+                                        <Tag closable onClose={(e) => {
+                                            e.preventDefault();
+                                            setSelectedRow([...selectedRow.filter(s => s?.chapterName !== item?.chapterName)])
+                                        }} style={{ padding: 5 }}>
+                                            <div style={{ maxWidth: 110, display: 'inline-block' }}><Typography.Text ellipsis={{ tooltip: true }}>{item?.chapterName}</Typography.Text></div>
+                                        </Tag>
+                                    </div>
+                                })}
+                            </div>
+                        </Space>
+                    </Col>
+                </Row>
+            </Space>
+        </Modal>}
+    </div>
+}
+
+export default React.memo(SelectChapter)

+ 35 - 1
src/pages/weComTask/components/filterUser/filterUserText.ts

@@ -5,6 +5,8 @@ const targetingData = [
     { key: 'msgTagIds', name: '智能标签' },
     { key: 'msgExcludeTagIds', name: '排除智能标签' },
     { key: 'platformKey', name: '书城' },
+    { key: 'lastBookNameList', name: '最近阅读书籍' },
+    { key: 'lastChapterNameList', name: '最近阅读章节' },
     { key: 'platformUserId', name: '书城用户ID' },
     { key: 'rechargeBook', name: '充值小说' },
     { key: 'begin', name: '关注时间' },
@@ -16,6 +18,9 @@ const targetingData = [
     { key: 'rechargeMoneyMax', name: '单笔最大充值金额' },
     { key: 'subHour', name: '关注距今时间' },
     { key: 'subDay', name: '关注距今天' },
+    { key: 'lastRechargeHour', name: '最近充值时间距今(小时)' },
+    { key: 'lastReadTime', name: '最近阅读时间' },
+    { key: 'lastReadHour', name: '最近阅读至今(小时)' },
     { key: 'opers', name: '手机系统' }
 ]
 
@@ -31,6 +36,8 @@ interface ContentProps {
     msgTagIds?: { [x: string]: string }[], // 智能标签
     msgExcludeTagIds?: { [x: string]: string }[], // 排除智能标签
     platformKey?: string,   // 书城
+    lastBookNameList?: string[],
+    lastChapterNameList?: string[],
     platformUserId?: string,// 书城用户ID
     rechargeBook?: string,  // 充值小说
     begin?: {               // 关注时间
@@ -66,6 +73,18 @@ interface ContentProps {
         subDayMin?: number
         subDayMax?: number
     },
+    lastRechargeHour?: {          // 充值距今
+        lastRechargeHourMin?: number
+        lastRechargeHourMax?: number
+    },
+    lastReadTime?: {        // 最近阅读时间
+        lastReadTimeMin?: string,
+        lastReadTimeMax?: string
+    },
+    lastReadHour?: {          // 充值距今
+        lastReadHourMin?: number
+        lastReadHourMax?: number
+    },
     opers?: string[]   // 手机系统
     // 群
     groupChatNames?: string,
@@ -88,7 +107,8 @@ export const resetFilterUserData = (data: { [x: string]: any }): ContentProps =>
     const {
         beginTime, endTime, rechargeCountMin, rechargeCountMax, rechargeMoneyMin, rechargeMoneyMax, lastRechargeTimeMin, lastRechargeTimeMax,
         firstRechargeMoneyMin, firstRechargeMoneyMax, rechargeMoneyMaxMin, rechargeMoneyMaxMax, subHourMin, subHourMax, subDayMin, subDayMax, 
-        groupChatNames, groupChatBeginTime, groupChatEndTime, groupChatHourMin, groupChatHourMax, groupChatUserMin, groupChatUserMax,
+        lastRechargeHourMin, lastRechargeHourMax, lastReadHourMin, lastReadHourMax, groupChatNames, groupChatBeginTime, groupChatEndTime, groupChatHourMin, groupChatHourMax, 
+        groupChatUserMin, groupChatUserMax, lastReadTimeMin, lastReadTimeMax,
         ...params
     } = data
 
@@ -105,6 +125,9 @@ export const resetFilterUserData = (data: { [x: string]: any }): ContentProps =>
     if (lastRechargeTimeMin || lastRechargeTimeMax) {
         newData.lastRechargeTime = { lastRechargeTimeMin, lastRechargeTimeMax }
     }
+    if (lastReadTimeMin || lastReadTimeMax) {
+        newData.lastReadTime = { lastReadTimeMin, lastReadTimeMax }
+    }
     if (firstRechargeMoneyMin || firstRechargeMoneyMax) {
         newData.firstRechargeMoney = { firstRechargeMoneyMin, firstRechargeMoneyMax }
     }
@@ -117,6 +140,12 @@ export const resetFilterUserData = (data: { [x: string]: any }): ContentProps =>
     if (subDayMin || subDayMax) {
         newData.subDay = { subDayMin, subDayMax }
     }
+    if (lastRechargeHourMin || lastRechargeHourMax) {
+        newData.lastRechargeHour = { lastRechargeHourMin, lastRechargeHourMax }
+    }
+    if (lastReadHourMin || lastReadHourMax) {
+        newData.lastReadHour = { lastReadHourMin, lastReadHourMax }
+    }
     if (groupChatNames?.length) {
         newData.groupChatNames = groupChatNames.join('、')
     }
@@ -165,6 +194,8 @@ const FilterUserText = ({ data, configName, configType = 'USER_GROUP', bookCityL
     (content?.msgTagIds ? `智能标签:${content?.msgTagIds?.['business'] ? `业务:${businessPlanData.find(i => i.value === content?.msgTagIds['business'])?.label}` : ''}--${content?.msgTagIds?.['bookCity'] ? `书城:${bookPlatForm.find(i => i.id === content?.msgTagIds['bookCity'])?.platformName}` : ''}--${content?.msgTagIds?.['product'] ? `产品:${bookList.find(i => i.id === content?.msgTagIds['product'])?.bookName}\n` : ''}` : '') + 
     (content?.msgExcludeTagIds ? `排除智能标签:${content?.msgExcludeTagIds?.['business'] ? `业务:${businessPlanData.find(i => i.value === content?.msgExcludeTagIds['business'])?.label}` : ''}--${content?.msgExcludeTagIds?.['bookCity'] ? `书城:${bookPlatForm.find(i => i.id === content?.msgExcludeTagIds['bookCity'])?.platformName}` : ''}--${content?.msgExcludeTagIds?.['product'] ? `产品:${bookList.find(i => i.id === content?.msgExcludeTagIds['product'])?.bookName}\n` : ''}` : '') + 
     (content?.platformKey ? `书城:${bookCityList?.find(item => item.value === content?.platformKey)?.label || '--'}\n` : '') +
+    (content?.lastBookNameList ? `最近阅读书籍:${content?.lastBookNameList?.join('、') || '--'}\n` : '') +
+    (content?.lastChapterNameList ? `最近阅读章节:${content?.lastChapterNameList?.join('、') || '--'}\n` : '') +
     (content?.platformUserId ? `书城用户ID:${content?.platformUserId}\n` : '') + 
     (content?.rechargeBook ? `充值小说:${content?.rechargeBook}\n` : '') +
     (content?.begin ? `关注时间:${content?.begin?.beginTime ? content?.begin?.beginTime : '不限'} - ${content?.begin?.endTime ? content?.begin?.endTime : '不限'}\n` : '') + 
@@ -176,6 +207,9 @@ const FilterUserText = ({ data, configName, configType = 'USER_GROUP', bookCityL
     (content?.rechargeMoneyMax ? `单笔最大充值金额:${content?.rechargeMoneyMax?.rechargeMoneyMaxMin ? content?.rechargeMoneyMax?.rechargeMoneyMaxMin : '不限'} - ${content?.rechargeMoneyMax?.rechargeMoneyMaxMax ? content?.rechargeMoneyMax?.rechargeMoneyMaxMax : '不限'}\n` : '') +
     (content?.subHour ? `关注距今时间:${content?.subHour?.subHourMin ? content?.subHour?.subHourMin + '小时' : '不限'} - ${content?.subHour?.subHourMax ? content?.subHour?.subHourMax + '小时' : '不限'}\n` : '') +
     (content?.subDay ? `关注距今天:${content?.subDay?.subDayMin ? content?.subDay?.subDayMin + '天' : '不限'} - ${content?.subDay?.subDayMax ? content?.subDay?.subDayMax + '天' : '不限'}\n` : '') +
+    (content?.lastRechargeHour ? `最近一次充值距今:${content?.lastRechargeHour?.lastRechargeHourMin ? content?.lastRechargeHour?.lastRechargeHourMin + '小时' : '不限'} - ${content?.lastRechargeHour?.lastRechargeHourMax ? content?.lastRechargeHour?.lastRechargeHourMax + '小时' : '不限'}\n` : '') +
+    (content?.lastReadTime ? `最近阅读时间:${content?.lastReadTime?.lastReadTimeMin ? content?.lastReadTime?.lastReadTimeMin : '不限'} - ${content?.lastReadTime?.lastReadTimeMax ? content?.lastReadTime?.lastReadTimeMax : '不限'}\n` : '') +
+    (content?.lastRechargeHour ? `最近阅读至今:${content?.lastReadHour?.lastReadHourMin ? content?.lastReadHour?.lastReadHourMin + '小时' : '不限'} - ${content?.lastReadHour?.lastReadHourMax ? content?.lastReadHour?.lastReadHourMax + '小时' : '不限'}\n` : '') +
     (content?.opers?.length > 0 ? `手机系统:${content?.opers.map(g => ({ '0': '未知', '1': '安卓', '2': 'IOS' } as any)[g]).join('、')}\n` : '') +
     (content?.groupChatNames ? `群名称列表:${content?.groupChatNames?.toString()}` : '') +
     (content?.groupChatTime ? `群创建时间:${content?.groupChatTime?.groupChatBeginTime ? content?.groupChatTime?.groupChatBeginTime + '小时' : '不限'} - ${content?.groupChatTime?.groupChatEndTime ? content?.groupChatTime?.groupChatEndTime + '小时' : '不限'}\n` : '') +

+ 45 - 3
src/pages/weComTask/components/filterUser/filterUserTooltip.tsx

@@ -1,8 +1,6 @@
 import React, { useEffect, useState } from "react"
 import './global.less'
 import { businessPlanData, GENDER_TYPE } from "../../page/businessPlan/create/const"
-import { useAjax } from "@/Hook/useAjax"
-import { getAllBookApi, getBookPlatInfoAllApi } from "../../API/global"
 import weComTaskStore from '@/pages/weComTask/store'
 import { toJS } from 'mobx';
 
@@ -10,6 +8,8 @@ const targetingData = [
     { key: 'msgTagIds', name: '智能标签' },
     { key: 'msgExcludeTagIds', name: '排除智能标签' },
     { key: 'platformKey', name: '书城' },
+    { key: 'lastBookNameList', name: '最近阅读书籍' },
+    { key: 'lastChapterNameList', name: '最近阅读章节' },
     { key: 'platformUserId', name: '书城用户ID' },
     { key: 'rechargeBook', name: '充值小说' },
     { key: 'begin', name: '关注时间' },
@@ -17,10 +17,13 @@ const targetingData = [
     { key: 'rechargeCount', name: '充值次数' },
     { key: 'rechargeMoney', name: '充值金额' },
     { key: 'lastRechargeTime', name: '最后充值时间' },
+    { key: 'lastReadTime', name: '最近阅读时间' },
     { key: 'firstRechargeMoney', name: '首充金额' },
     { key: 'rechargeMoneyMax', name: '单笔最大充值金额' },
     { key: 'subHour', name: '关注距今时间' },
     { key: 'subDay', name: '关注距今天' },
+    { key: 'lastRechargeHour', name: '最近充值时间距今(小时)' },
+    { key: 'lastReadHour', name: '最近阅读至今(小时)' },
     { key: 'opers', name: '手机系统' }
 ]
 
@@ -41,6 +44,8 @@ interface ContentProps {
     msgTagIds?: { [x: string]: string }[], // 智能标签
     msgExcludeTagIds?: { [x: string]: string }[], // 排除智能标签
     platformKey?: string,   // 书城
+    lastBookNameList?: string[],
+    lastChapterNameList?: string[],
     platformUserId?: string,// 书城用户ID
     rechargeBook?: string,  // 充值小说
     begin?: {               // 关注时间
@@ -60,6 +65,10 @@ interface ContentProps {
         lastRechargeTimeMin?: string,
         lastRechargeTimeMax?: string
     },
+    lastReadTime?: {        // 最近阅读时间
+        lastReadTimeMin?: string,
+        lastReadTimeMax?: string
+    },
     firstRechargeMoney?: { // 首充金额
         firstRechargeMoneyMin?: number,
         firstRechargeMoneyMax?: number
@@ -76,6 +85,14 @@ interface ContentProps {
         subDayMin?: number
         subDayMax?: number
     },
+    lastRechargeHour?: {  // 最近一次充值距今
+        lastRechargeHourMin?: number,
+        lastRechargeHourMax?: number
+    },
+    lastReadHour?: {  // 最近阅读至今
+        lastReadHourMin?: number,
+        lastReadHourMax?: number
+    },
     opers?: string[]   // 手机系统
     // 群
     groupChatNames?: string,
@@ -98,7 +115,8 @@ export const resetFilterUserData = (data: { [x: string]: any }): ContentProps =>
     const {
         beginTime, endTime, rechargeCountMin, rechargeCountMax, rechargeMoneyMin, rechargeMoneyMax, lastRechargeTimeMin, lastRechargeTimeMax,
         firstRechargeMoneyMin, firstRechargeMoneyMax, rechargeMoneyMaxMin, rechargeMoneyMaxMax, subHourMin, subHourMax, subDayMin, subDayMax,
-        groupChatNames, groupChatBeginTime, groupChatEndTime, groupChatHourMin, groupChatHourMax, groupChatUserMin, groupChatUserMax,
+        lastRechargeHourMin, lastRechargeHourMax, groupChatNames, groupChatBeginTime, groupChatEndTime, groupChatHourMin, groupChatHourMax, 
+        groupChatUserMin, groupChatUserMax, lastReadHourMin, lastReadHourMax, lastReadTimeMin, lastReadTimeMax,
         ...params
     } = data
 
@@ -115,6 +133,9 @@ export const resetFilterUserData = (data: { [x: string]: any }): ContentProps =>
     if (lastRechargeTimeMin || lastRechargeTimeMax) {
         newData.lastRechargeTime = { lastRechargeTimeMin, lastRechargeTimeMax }
     }
+    if (lastReadTimeMin || lastReadTimeMax) {
+        newData.lastReadTime = { lastReadTimeMin, lastReadTimeMax }
+    }
     if (firstRechargeMoneyMin || firstRechargeMoneyMax) {
         newData.firstRechargeMoney = { firstRechargeMoneyMin, firstRechargeMoneyMax }
     }
@@ -127,6 +148,12 @@ export const resetFilterUserData = (data: { [x: string]: any }): ContentProps =>
     if (subDayMin || subDayMax) {
         newData.subDay = { subDayMin, subDayMax }
     }
+    if (lastRechargeHourMin || lastRechargeHourMax) {
+        newData.lastRechargeHour = { lastRechargeHourMin, lastRechargeHourMax }
+    }
+    if (lastReadHourMin || lastReadHourMax) {
+        newData.lastReadHour = { lastReadHourMin, lastReadHourMax }
+    }
     if (groupChatNames?.length) {
         newData.groupChatNames = groupChatNames.join('、')
     }
@@ -195,6 +222,12 @@ const FilterUserTooltip: React.FC<Props> = (props) => {
         {content?.platformKey && <div>
             <strong>书城:</strong><span>{bookCityList?.find(item => item.value === content?.platformKey)?.label || '--'}</span>
         </div>}
+        {content?.lastBookNameList && <div>
+            <strong>最近阅读书籍:</strong><span>{content?.lastBookNameList?.join('、') || '--'}</span>
+        </div>}
+        {content?.lastChapterNameList && <div>
+            <strong>最近阅读章节:</strong><span>{content?.lastChapterNameList?.join('、') || '--'}</span>
+        </div>}
         {content?.platformUserId && <div>
             <strong>书城用户ID:</strong><span>{content?.platformUserId}</span>
         </div>}
@@ -228,6 +261,15 @@ const FilterUserTooltip: React.FC<Props> = (props) => {
         {content?.subDay && <div>
             <strong>关注距今天:</strong><span>{content?.subDay?.subDayMin ? content?.subDay?.subDayMin + '天' : '不限'} - {content?.subDay?.subDayMax ? content?.subDay?.subDayMax + '天' : '不限'}</span>
         </div>}
+        {content?.lastRechargeHour && <div>
+            <strong>最近一次充值距今(小时):</strong><span>{content?.lastRechargeHour?.lastRechargeHourMin ? content?.lastRechargeHour?.lastRechargeHourMin + '小时' : '不限'} - {content?.lastRechargeHour?.lastRechargeHourMax ? content?.lastRechargeHour?.lastRechargeHourMax + '小时' : '不限'}</span>
+        </div>}
+        {content?.lastReadTime && <div>
+            <strong>最近阅读时间:</strong><span>{content?.lastReadTime?.lastReadTimeMin ? content?.lastReadTime?.lastReadTimeMin : '不限'} - {content?.lastReadTime?.lastReadTimeMax ? content?.lastReadTime?.lastReadTimeMax : '不限'}</span>
+        </div>}
+        {content?.lastReadHour && <div>
+            <strong>最近阅读至今(小时):</strong><span>{content?.lastReadHour?.lastReadHourMin ? content?.lastReadHour?.lastReadHourMin + '小时' : '不限'} - {content?.lastReadHour?.lastReadHourMax ? content?.lastReadHour?.lastReadHourMax + '小时' : '不限'}</span>
+        </div>}
         {content?.opers?.length > 0 && <div>
             <strong>手机系统:</strong><span>{content?.opers.map(g => ({ '0': '未知', '1': '安卓', '2': 'IOS' } as any)[g]).toString()}</span>
         </div>}

+ 70 - 18
src/pages/weComTask/components/filterUser/newFiterUser.tsx

@@ -6,6 +6,8 @@ import { GENDER_TYPE } from '../../page/businessPlan/create/const';
 import { addSiftPopulationConfigApi, updateSiftPopulationConfigApi } from '../../API/businessPlan/filterUser';
 import dayjs from 'dayjs';
 import MindTags from '../mindTags';
+import SelectBook from '../SelectBook';
+import SelectChapter from '../SelectChapter';
 
 interface NewFilterUserProps {
     configType: BUSINES_SPLAN_API.ConfigTypeProps
@@ -27,6 +29,8 @@ const NewFilterUser: React.FC<NewFilterUserProps> = ({ bookCityList, configType
     /***********************************/
     const { message } = App.useApp()
     const [form] = Form.useForm();
+    const platformKey = Form.useWatch('platformKey', form)
+    const lastBookNameList = Form.useWatch('lastBookNameList', form)
     const [isSave, setIsSave] = useState<boolean>(false)
 
     const addSiftPopulationConfig = useAjax((params) => addSiftPopulationConfigApi(params))
@@ -35,7 +39,7 @@ const NewFilterUser: React.FC<NewFilterUserProps> = ({ bookCityList, configType
 
     useEffect(() => {
         if (value && Object.keys(value).length > 0) {
-            const { beginTime, endTime, lastRechargeTimeMin, lastRechargeTimeMax, groupChatNames, groupChatBeginTime, groupChatEndTime, ...params } = value
+            const { beginTime, endTime, lastRechargeTimeMin, lastRechargeTimeMax, groupChatNames, groupChatBeginTime, groupChatEndTime, lastReadTimeMin, lastReadTimeMax, ...params } = value
             if (beginTime) {
                 params.beginTime = dayjs(beginTime)
             }
@@ -45,6 +49,9 @@ const NewFilterUser: React.FC<NewFilterUserProps> = ({ bookCityList, configType
             if (lastRechargeTimeMin && lastRechargeTimeMax) {
                 params.lastRechargeTime = [dayjs(lastRechargeTimeMin), dayjs(lastRechargeTimeMax)]
             }
+            if (lastReadTimeMin && lastReadTimeMax) {
+                params.lastReadTime = [dayjs(lastReadTimeMin), dayjs(lastReadTimeMax)]
+            }
             if (groupChatBeginTime && groupChatEndTime) {
                 params.groupChatTime = [dayjs(groupChatBeginTime), dayjs(groupChatEndTime)]
             }
@@ -60,7 +67,7 @@ const NewFilterUser: React.FC<NewFilterUserProps> = ({ bookCityList, configType
             console.log(values)
             const { configName, ...configContent } = values
             if (configContent && Object.keys(configContent).length > 0 && Object.keys(configContent).some(key => configContent[key])) {
-                const { beginTime, endTime, lastRechargeTime, groupChatNames, groupChatTime, ...params } = configContent
+                const { beginTime, endTime, lastRechargeTime, lastReadTime, groupChatNames, groupChatTime, ...params } = configContent
                 const dta = {
                     ...params
                 }
@@ -74,6 +81,10 @@ const NewFilterUser: React.FC<NewFilterUserProps> = ({ bookCityList, configType
                     dta.lastRechargeTimeMin = dayjs(lastRechargeTime[0]).format('YYYY-MM-DD');
                     dta.lastRechargeTimeMax = dayjs(lastRechargeTime[1]).format('YYYY-MM-DD');
                 }
+                if (lastReadTime?.length > 1) {
+                    dta.lastReadTimeMin = dayjs(lastReadTime[0]).format('YYYY-MM-DD');
+                    dta.lastReadTimeMax = dayjs(lastReadTime[1]).format('YYYY-MM-DD');
+                }
                 if (groupChatNames) {
                     dta.groupChatNames = groupChatNames.split(/[,,\n\s]+/ig).filter((item: any) => item)
                 }
@@ -197,11 +208,11 @@ const NewFilterUser: React.FC<NewFilterUserProps> = ({ bookCityList, configType
                     <Form.Item label={<strong>群创建时长</strong>} style={{ marginBottom: 0 }}>
                         <Space>
                             <Form.Item name="groupChatHourMin" noStyle>
-                                <InputNumber placeholder="最小值小时" style={{ width: 150 }} />
+                                <InputNumber placeholder="最小值小时" style={{ width: 200 }} />
                             </Form.Item>
                             <span>-</span>
                             <Form.Item name="groupChatHourMax" noStyle>
-                                <InputNumber placeholder="最大值小时" style={{ width: 150 }} />
+                                <InputNumber placeholder="最大值小时" style={{ width: 200 }} />
                             </Form.Item>
                         </Space>
                     </Form.Item>
@@ -210,11 +221,11 @@ const NewFilterUser: React.FC<NewFilterUserProps> = ({ bookCityList, configType
                     <Form.Item label={<strong>群人数</strong>} style={{ marginBottom: 0 }}>
                         <Space>
                             <Form.Item name="groupChatUserMin" noStyle>
-                                <InputNumber placeholder="最小值" style={{ width: 150 }} />
+                                <InputNumber placeholder="最小值" style={{ width: 200 }} />
                             </Form.Item>
                             <span>-</span>
                             <Form.Item name="groupChatUserMax" noStyle>
-                                <InputNumber placeholder="最大值" style={{ width: 150 }} />
+                                <InputNumber placeholder="最大值" style={{ width: 200 }} />
                             </Form.Item>
                         </Space>
                     </Form.Item>
@@ -259,6 +270,16 @@ const NewFilterUser: React.FC<NewFilterUserProps> = ({ bookCityList, configType
                         />
                     </Form.Item>
                 </div>
+                <div className={style.newSpace}>
+                    <Form.Item name="lastBookNameList" label={<strong>最近阅读书籍</strong>} style={{ marginBottom: 0 }}>
+                        <SelectBook onChange={() => form.setFieldsValue({ lastChapterNameList: undefined })} platformKey={platformKey} />
+                    </Form.Item>
+                </div>
+                {lastBookNameList?.length === 1 && <div className={style.newSpace}>
+                    <Form.Item label={<strong>最近阅读章节</strong>} name='lastChapterNameList' tooltip="选择单本书才可选择章节">
+                        <SelectChapter platformKey={platformKey} bookName={lastBookNameList?.[0]} />
+                    </Form.Item>
+                </div>}
                 <div className={style.newSpace}>
                     <Form.Item name="platformUserId" label={<strong>书城用户ID</strong>} style={{ marginBottom: 0 }}>
                         <Input placeholder="书城用户ID" allowClear />
@@ -298,11 +319,11 @@ const NewFilterUser: React.FC<NewFilterUserProps> = ({ bookCityList, configType
                     <Form.Item label={<strong>充值次数</strong>} style={{ marginBottom: 0 }}>
                         <Space>
                             <Form.Item name="rechargeCountMin" noStyle>
-                                <InputNumber placeholder="最小充值次数" style={{ width: 150 }} />
+                                <InputNumber placeholder="最小充值次数" style={{ width: 200 }} />
                             </Form.Item>
                             <span>-</span>
                             <Form.Item name="rechargeCountMax" noStyle>
-                                <InputNumber placeholder="最大充值次数" style={{ width: 150 }} />
+                                <InputNumber placeholder="最大充值次数" style={{ width: 200 }} />
                             </Form.Item>
                         </Space>
                     </Form.Item>
@@ -311,11 +332,11 @@ const NewFilterUser: React.FC<NewFilterUserProps> = ({ bookCityList, configType
                     <Form.Item label={<strong>充值金额</strong>} style={{ marginBottom: 0 }}>
                         <Space>
                             <Form.Item name="rechargeMoneyMin" noStyle>
-                                <InputNumber placeholder="最低充值金额" style={{ width: 150 }} />
+                                <InputNumber placeholder="最低充值金额" style={{ width: 200 }} />
                             </Form.Item>
                             <span>-</span>
                             <Form.Item name="rechargeMoneyMax" noStyle>
-                                <InputNumber placeholder="最大充值金额" style={{ width: 150 }} />
+                                <InputNumber placeholder="最大充值金额" style={{ width: 200 }} />
                             </Form.Item>
                         </Space>
                     </Form.Item>
@@ -329,11 +350,11 @@ const NewFilterUser: React.FC<NewFilterUserProps> = ({ bookCityList, configType
                     <Form.Item label={<strong>首充金额</strong>} style={{ marginBottom: 0 }}>
                         <Space>
                             <Form.Item name="firstRechargeMoneyMin" noStyle>
-                                <InputNumber placeholder="最低首充金额" style={{ width: 150 }} />
+                                <InputNumber placeholder="最低首充金额" style={{ width: 200 }} />
                             </Form.Item>
                             <span>-</span>
                             <Form.Item name="firstRechargeMoneyMax" noStyle>
-                                <InputNumber placeholder="最大首充金额" style={{ width: 150 }} />
+                                <InputNumber placeholder="最大首充金额" style={{ width: 200 }} />
                             </Form.Item>
                         </Space>
                     </Form.Item>
@@ -342,11 +363,11 @@ const NewFilterUser: React.FC<NewFilterUserProps> = ({ bookCityList, configType
                     <Form.Item label={<strong>单笔最大充值金额</strong>} style={{ marginBottom: 0 }}>
                         <Space>
                             <Form.Item name="rechargeMoneyMaxMin" noStyle>
-                                <InputNumber placeholder="最低单笔最大充值金额" style={{ width: 150 }} />
+                                <InputNumber placeholder="最低单笔最大充值金额" style={{ width: 200 }} />
                             </Form.Item>
                             <span>-</span>
                             <Form.Item name="rechargeMoneyMaxMax" noStyle>
-                                <InputNumber placeholder="最大单笔最大充值金额" style={{ width: 150 }} />
+                                <InputNumber placeholder="最大单笔最大充值金额" style={{ width: 200 }} />
                             </Form.Item>
                         </Space>
                     </Form.Item>
@@ -355,11 +376,11 @@ const NewFilterUser: React.FC<NewFilterUserProps> = ({ bookCityList, configType
                     <Form.Item label={<strong>关注距今时间</strong>} style={{ marginBottom: 0 }}>
                         <Space>
                             <Form.Item name="subHourMin" noStyle>
-                                <InputNumber placeholder="最小关注时间" style={{ width: 150 }} />
+                                <InputNumber placeholder="最小关注时间" style={{ width: 200 }} />
                             </Form.Item>
                             <span>-</span>
                             <Form.Item name="subHourMax" noStyle>
-                                <InputNumber placeholder="最大关注时间" style={{ width: 150 }} />
+                                <InputNumber placeholder="最大关注时间" style={{ width: 200 }} />
                             </Form.Item>
                         </Space>
                     </Form.Item>
@@ -368,11 +389,42 @@ const NewFilterUser: React.FC<NewFilterUserProps> = ({ bookCityList, configType
                     <Form.Item label={<strong>关注距今天</strong>} style={{ marginBottom: 0 }}>
                         <Space>
                             <Form.Item name="subDayMin" noStyle>
-                                <InputNumber placeholder="最小关注天" style={{ width: 150 }} />
+                                <InputNumber placeholder="最小关注天" style={{ width: 200 }} />
                             </Form.Item>
                             <span>-</span>
                             <Form.Item name="subDayMax" noStyle>
-                                <InputNumber placeholder="最大关注天" style={{ width: 150 }} />
+                                <InputNumber placeholder="最大关注天" style={{ width: 200 }} />
+                            </Form.Item>
+                        </Space>
+                    </Form.Item>
+                </div>
+                <div className={style.newSpace}>
+                    <Form.Item label={<strong>最近一次充值距今(小时)</strong>} style={{ marginBottom: 0 }}>
+                        <Space>
+                            <Form.Item name="lastRechargeHourMin" noStyle>
+                                <InputNumber placeholder="最小充值距今" style={{ width: 200 }} />
+                            </Form.Item>
+                            <span>-</span>
+                            <Form.Item name="lastRechargeHourMax" noStyle>
+                                <InputNumber placeholder="最大充值距今" style={{ width: 200 }} />
+                            </Form.Item>
+                        </Space>
+                    </Form.Item>
+                </div>
+                <div className={style.newSpace}>
+                    <Form.Item name="lastReadTime" label={<strong>最近阅读时间</strong>} style={{ marginBottom: 0 }}>
+                        <DatePicker.RangePicker placeholder={["最近阅读时间开始", "最近阅读时间结束"]} />
+                    </Form.Item>
+                </div>
+                <div className={style.newSpace}>
+                    <Form.Item label={<strong>最近阅读至今(小时)</strong>} style={{ marginBottom: 0 }}>
+                        <Space>
+                            <Form.Item name="lastReadHourMin" noStyle>
+                                <InputNumber placeholder="最近阅读至今小时差区间-最小" style={{ width: 200 }} />
+                            </Form.Item>
+                            <span>-</span>
+                            <Form.Item name="lastReadHourMax" noStyle>
+                                <InputNumber placeholder="最近阅读至今小时差区间-最大" style={{ width: 200 }} />
                             </Form.Item>
                         </Space>
                     </Form.Item>

+ 90 - 0
src/pages/weComTask/components/groupChatGroups/index.tsx

@@ -0,0 +1,90 @@
+import React, { useState } from 'react';
+import '../../page/corpUserManage/global.less'
+import useNewToken from '@/Hook/useNewToken';
+import { DefaultOptionType } from 'antd/es/select';
+import SelectMpCorp from '../../page/groupChatSend/official/create/components/SelectMpCorp';
+import { Tag, Tooltip } from 'antd';
+
+interface GroupChatGroupsProps {
+    corpList: DefaultOptionType[]
+    mpList: DefaultOptionType[]
+    value?: OFFICIAL_CHAT_CREATE.GroupsProps[];
+    onChange?: (value?: OFFICIAL_CHAT_CREATE.GroupsProps[]) => void;
+    placeholder?: React.ReactNode
+}
+
+/**
+ * 群发组配置
+ * @param param0 
+ * @returns 
+ */
+const GroupChatGroups: React.FC<GroupChatGroupsProps> = ({ corpList, mpList, value, onChange, placeholder }) => {
+
+    /************************************************************/
+    const { token } = useNewToken()
+    const [visible, setVisible] = useState<boolean>(false);
+    /************************************************************/
+
+    return <>
+        <div
+            className='selectCorpUser'
+            style={{
+                border: `1px solid ${token.colorBorder}`,
+                padding: `0px ${token.paddingXS}px`,
+                borderRadius: token.borderRadius,
+                height: token.controlHeight,
+            }}
+            onClick={() => {
+                setVisible(true)
+            }}
+        >
+            <div className='selectCorpUserContent'>
+                {(value && value?.length > 0) ? <>
+                    <Tag
+                        closable
+                        onClose={(e) => {
+                            e.preventDefault();
+                            const newData = value?.filter((_, i) => i !== 0)
+                            onChange(newData)
+                        }}
+                    >
+                        {value?.[0]?.mpAccount?.label || '群发组1'}
+                    </Tag>
+                    {value?.length > 1 && <Tooltip
+                        color="#FFF"
+                        title={<span style={{ color: '#000' }}>
+                            {value?.filter((_, index) => index !== 0)?.map((item, index) => <Tag
+                                key={index}
+                                closable
+                                onClose={(e) => {
+                                    e.stopPropagation()
+                                    const newData = value?.filter((_, i) => i !== index + 1)
+                                    onChange(newData)
+                                }}
+                            >{item?.mpAccount?.label || `群发组${index + 2}`}</Tag>)}</span>
+                        }
+                    >
+                        <Tag>+{value.length - 1} ...</Tag>
+                    </Tooltip>}
+                </> : <span style={{ color: token.colorTextDisabled }}>{placeholder || '请选择主体'}</span>}
+            </div>
+        </div>
+
+        {/* 选择关联主体 */}
+        {visible && <SelectMpCorp
+            corpList={corpList}
+            mpList={mpList}
+            visible={visible}
+            group={value}
+            onChange={(group) => {
+                onChange?.(group)
+                setVisible(false)
+            }}
+            onClose={() => {
+                setVisible(false)
+            }}
+        />}
+    </>;
+};
+
+export default GroupChatGroups;

+ 1 - 1
src/pages/weComTask/components/newSteps/index.tsx

@@ -53,7 +53,7 @@ const NewSteps: React.FC<NewStepsProps> = ({ items = [], onChange, isLeaf }) =>
                     {i?.description && <div className={style['newSteps-item-description']}>{i.description}</div>}
                 </div>
                 {i.children && <div style={{ marginTop: 20 }}>
-                    <NewSteps items={i.children} onChange={(e, f) => onChange(e, f)} isLeaf={true} />
+                    <NewSteps items={i.children} onChange={(e, f) => onChange?.(e, f)} isLeaf={true} />
                 </div>}
             </div>
         </div>)}

+ 2 - 1
src/pages/weComTask/components/newsMould/index.less

@@ -45,7 +45,8 @@
 }
 
 .miniprogramNews {
-    max-width: 220px;
+    width: 80%;
+    max-width: 220px !important;
     box-sizing: border-box;
 
     &>.imgBack {

+ 6 - 0
src/pages/weComTask/components/selectExternalAccount/index.less

@@ -26,6 +26,12 @@
             box-sizing: border-box;
             overflow: hidden;
 
+            .leftTitle {
+                display: flex;
+                justify-content: space-between;
+                padding-right: 10px;
+            }
+
             .title {
                 margin: 0 0 5px;
                 font-weight: 700;

+ 1 - 1
src/pages/weComTask/components/sendTimeSet/index.tsx

@@ -198,7 +198,7 @@ const SendTimeSet: React.FC<Props> = ({ active, form, timeRepeatType, disabled,
                 mode="multiple"
                 placeholder="每月执行天数"
                 maxLength={8}
-                style={{ width: 358 }}
+                style={{ width: isShow ? '100%' : 358 }}
                 options={Array(31).fill('').map((item, index) => ({ label: index + 1 + '号', value: index + 1 }))}
             />
         </Form.Item>}

+ 40 - 0
src/pages/weComTask/page/bookLink/SelectBookLinkButton.tsx

@@ -0,0 +1,40 @@
+import React, { useState } from 'react';
+import { Button } from 'antd';
+import SelectBookLink from './selectBookLink';
+
+interface SelectBookLinkButtonProps {
+    children?: React.ReactNode;
+    mpAccountId: number | null
+    bookPlatForm: TASK_CREATE.BookPlatFormProps[]
+    linkData: { [x: string]: any }[]
+    onChange: (linkStr: string, miniprogramAppid?: string, miniprogramPage?: string) => void
+    platform: string
+}
+
+const SelectBookLinkButton: React.FC<SelectBookLinkButtonProps> = ({ mpAccountId, bookPlatForm, linkData, onChange, children = '选择', platform }) => {
+
+    /********************************/
+    const [visible, setVisible] = useState<boolean>(false)
+    /********************************/
+
+    return <>
+        <Button type="link" style={{ padding: 0 }} onClick={() => setVisible(true)}>
+            {children}
+        </Button>
+        {visible && <SelectBookLink
+            mpAccountId={mpAccountId}
+            platform={platform}
+            bookPlatForm={bookPlatForm}
+            visible={visible}
+            linkData={linkData}
+            onClose={() => setVisible(false)}
+            onChange={(linkStr, miniprogramAppid, miniprogramPage) => {
+                console.log('linkStr', linkStr)
+                onChange(linkStr, miniprogramAppid, miniprogramPage)
+                setVisible(false)
+            }}
+        />}
+    </>
+};
+
+export default SelectBookLinkButton;

+ 41 - 0
src/pages/weComTask/page/bookLink/components/fanqie/bookPromoLinkCreateDTO.tsx

@@ -0,0 +1,41 @@
+import { Form, InputNumber } from "antd";
+import React from "react";
+import SelectBook from "../../selectBook";
+
+interface Props extends BOOKLINK.BookLinkChildProps {
+    platform: string,
+}
+/**
+ * 作品链接
+ * @returns 
+ */
+const BookPromoLinkCreateDTO: React.FC<Props> = ({ restField, name, platform }) => {
+
+    /**********************************/
+    /**********************************/
+
+    return <>
+        <Form.Item
+            {...restField}
+            name={[name, 'bookPromoLinkCreateDTO', 'bookId']}
+            rules={[{ required: true, message: '请输入作品(书籍)ID!' }]}
+            label={<strong>作品(书籍)ID</strong>}
+        >
+            <SelectBook
+                platformKey={platform}
+                isSearchChapter={false}
+            />
+        </Form.Item>
+
+        <Form.Item
+            {...restField}
+            name={[name, 'bookPromoLinkCreateDTO', 'chapterId']}
+            label={<strong>章节</strong>}
+            rules={[{ required: true, message: '请输入章节(1~19)!' }]}
+        >
+            <InputNumber style={{ width: '100%' }} min={1} max={19} placeholder="请输入章节(1~19)" />
+        </Form.Item>
+    </>
+}
+
+export default React.memo(BookPromoLinkCreateDTO);

+ 73 - 0
src/pages/weComTask/page/bookLink/components/fanqie/consumeActivityLinkDTO.tsx

@@ -0,0 +1,73 @@
+import { DatePicker, Form, Input, InputNumber, Select } from "antd";
+import React from "react";
+
+
+/**
+ * 免费赠币活动链接
+ * @returns 
+ */
+const ConsumeActivityLinkDTO: React.FC<BOOKLINK.BookLinkChildProps> = ({ restField, name }) => {
+
+
+    return <>
+        <Form.Item
+            {...restField}
+            name={[name, 'consumeActivityLinkDTO', 'activityName']}
+            label={<strong>活动名称</strong>}
+            rules={[{ required: true, message: '请输入活动名称!' }]}
+        >
+            <Input placeholder="请输入活动名称" style={{ width: '100%' }} allowClear />
+        </Form.Item>
+
+        <Form.Item
+            {...restField}
+            name={[name, 'consumeActivityLinkDTO', 'activityTime']}
+            label={<strong>活动时间</strong>}
+            rules={[{ required: true, message: '请选择活动时间!' }]}
+        >
+            <DatePicker.RangePicker />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'consumeActivityLinkDTO', 'giftAmount']}
+            rules={[{ required: true, message: '请输入赠送书币!' }]}
+            label={<strong>赠送书币</strong>}
+        >
+            <InputNumber suffix="个" min={1} max={5000} placeholder="请输入(1~5000)" style={{ width: '100%' }} />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'consumeActivityLinkDTO', 'rechargeCount']}
+            label={<strong>赠送总次数</strong>}
+            rules={[{ required: true, message: '请选择赠送总次数!' }]}
+        >
+            <Select
+                showSearch
+                placeholder="请选择赠送总次数"
+                filterOption={(input, option) =>
+                    ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                }
+                allowClear
+                options={[{ label: '1次', value: 1 }, { label: '2次', value: 2 }, { label: '3次', value: 3 }]}
+            />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'consumeActivityLinkDTO', 'activityFrequencies', 'activityFrequenciesEnum']}
+            label={<strong>频率限制</strong>}
+            rules={[{ required: true, message: '请选择频率限制!' }]}
+        >
+            <Select
+                showSearch
+                placeholder="请选择频率限制"
+                filterOption={(input, option) =>
+                    ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                }
+                allowClear
+                options={[{ label: '每天一次', value: 2 }, { label: '每周一次', value: 3 }]}
+            />
+        </Form.Item>
+    </>
+}
+
+export default React.memo(ConsumeActivityLinkDTO);

+ 84 - 0
src/pages/weComTask/page/bookLink/components/fanqie/giftActivityLinkDTO.tsx

@@ -0,0 +1,84 @@
+import { DatePicker, Form, Input, InputNumber, Select } from "antd";
+import React from "react";
+
+
+interface Props extends BOOKLINK.BookLinkChildProps {
+    vipDuration?: number;
+}
+/**
+ * 年卡活动链接
+ * @returns 
+ */
+const GiftActivityLinkDTO: React.FC<Props> = ({ restField, name, vipDuration = 1 }) => {
+    console.log('-------->', vipDuration)
+
+    return <>
+        <Form.Item
+            {...restField}
+            name={[name, 'giftActivityLinkDTO', 'activityName']}
+            label={<strong>活动名称</strong>}
+            rules={[{ required: true, message: '请输入活动名称!' }]}
+        >
+            <Input placeholder="请输入活动名称" style={{ width: '100%' }} allowClear />
+        </Form.Item>
+
+        <Form.Item
+            {...restField}
+            name={[name, 'giftActivityLinkDTO', 'activityTime']}
+            label={<strong>活动时间</strong>}
+            rules={[{ required: true, message: '请选择活动时间!' }]}
+        >
+            <DatePicker.RangePicker />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'giftActivityLinkDTO', 'vipDuration']}
+            label={<strong>会员时长</strong>}
+            rules={[{ required: true, message: '请选择会员时长!' }]}
+        >
+            <Select
+                showSearch
+                placeholder="请选择会员时长"
+                filterOption={(input, option) =>
+                    ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                }
+                allowClear
+                options={[1, 2, 3, 4, 5].map(item => ({ label: item + '年', value: item }))}
+            />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'giftActivityLinkDTO', 'activityPrice']}
+            rules={[{ required: true, message: `请输入活动价格(${300 * vipDuration}~5000)!` }]}
+            label={<strong>活动价格</strong>}
+        >
+            <InputNumber suffix="元" min={300 * vipDuration} max={5000} placeholder={`请输入活动价格(${300 * vipDuration}~5000)!`} style={{ width: '100%' }} />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'giftActivityLinkDTO', 'originalPrice']}
+            rules={[{ required: true, message: '请输入原价!' }]}
+            label={<strong>原价</strong>}
+        >
+            <InputNumber suffix="元" placeholder="请输入原价" style={{ width: '100%' }} />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'giftActivityLinkDTO', 'rechargeCount']}
+            label={<strong>充值次数</strong>}
+            rules={[{ required: true, message: '请选择充值次数!' }]}
+        >
+            <Select
+                showSearch
+                placeholder="请选择充值次数"
+                filterOption={(input, option) =>
+                    ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                }
+                allowClear
+                options={[{ label: '1次', value: 1 }, { label: '2次', value: 2 }, { label: '3次', value: 3 }]}
+            />
+        </Form.Item>
+    </>
+}
+
+export default React.memo(GiftActivityLinkDTO);

+ 33 - 0
src/pages/weComTask/page/bookLink/components/fanqie/pagePromoLinkCreateDTO.tsx

@@ -0,0 +1,33 @@
+import { Form, Input, InputNumber, Select } from "antd";
+import React from "react";
+import { FANQIEPAGETYPE, PAGETYPE } from "../../const";
+
+
+/**
+ * 页面链接
+ * @returns 
+ */
+const PagePromoLinkCreateDTO: React.FC<BOOKLINK.BookLinkChildProps> = ({ restField, name }) => {
+
+
+    return <>
+        <Form.Item
+            {...restField}
+            name={[name, 'pagePromoLinkCreateDTO', 'pageType']}
+            label={<strong>页面类型</strong>}
+            rules={[{ required: true, message: '请选择页面类型!' }]}
+        >
+            <Select
+                showSearch
+                placeholder="页面类型"
+                filterOption={(input, option) =>
+                    ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                }
+                allowClear
+                options={Object.keys(FANQIEPAGETYPE).map(key => ({ label: FANQIEPAGETYPE[key], value: key }))}
+            />
+        </Form.Item>
+    </>
+}
+
+export default React.memo(PagePromoLinkCreateDTO);

+ 72 - 0
src/pages/weComTask/page/bookLink/components/fanqie/rechargeActivityLinkDTO.tsx

@@ -0,0 +1,72 @@
+import { DatePicker, Form, Input, InputNumber, Select, Space } from "antd";
+import React from "react";
+
+
+/**
+ * 模板充值活动
+ * @returns 
+ */
+const RechargeActivityLinkDTO: React.FC<BOOKLINK.BookLinkChildProps> = ({ restField, name }) => {
+
+    return <>
+        <Form.Item
+            {...restField}
+            name={[name, 'rechargeActivityLinkDTO', 'activityName']}
+            label={<strong>活动名称</strong>}
+            rules={[{ required: true, message: '请输入活动名称!' }]}
+        >
+            <Input placeholder="请输入活动名称" style={{ width: '100%' }} allowClear />
+        </Form.Item>
+
+        <Form.Item
+            {...restField}
+            name={[name, 'rechargeActivityLinkDTO', 'activityTime']}
+            label={<strong>活动时间</strong>}
+            rules={[{ required: true, message: '请选择活动时间!' }]}
+        >
+            <DatePicker.RangePicker />
+        </Form.Item>
+
+        <Form.Item
+            label={<strong>充值档位</strong>}
+            required
+        >
+            <Space>
+                <Form.Item
+                    {...restField}
+                    name={[name, 'rechargeActivityLinkDTO', 'rechargeAmount']}
+                    rules={[{ required: true, message: '请输入充值金额(0.1~300)!' }]}
+                    noStyle
+                >
+                    <InputNumber addonBefore="价位" suffix="元" min={0.1} max={300} placeholder="请输入" style={{ width: '100%' }} />
+                </Form.Item>
+                <Form.Item
+                    {...restField}
+                    name={[name, 'rechargeActivityLinkDTO', 'giftAmount']}
+                    rules={[{ required: true, message: '请输入赠送金额!' }]}
+                    noStyle
+                >
+                    <InputNumber addonBefore="赠送" suffix="元" placeholder="请输入" style={{ width: '100%' }} />
+                </Form.Item>
+            </Space>
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'rechargeActivityLinkDTO', 'rechargeCount']}
+            label={<strong>充值次数</strong>}
+            rules={[{ required: true, message: '请选择充值次数!' }]}
+        >
+            <Select
+                showSearch
+                placeholder="请选择充值次数"
+                filterOption={(input, option) =>
+                    ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                }
+                allowClear
+                options={[{label: '不限次', value: -1}, {label: '1次', value: 1}, {label: '2次', value: 2}, {label: '3次', value: 3}]}
+            />
+        </Form.Item>
+    </>
+}
+
+export default React.memo(RechargeActivityLinkDTO);

+ 83 - 0
src/pages/weComTask/page/bookLink/components/huasheng/bookPromoLinkCreateDTO.tsx

@@ -0,0 +1,83 @@
+import { App, Form, InputNumber, Select } from "antd";
+import React from "react";
+import SelectBook from "../../selectBook";
+import { useAjax } from "@/Hook/useAjax";
+import { getFreeChapterListApi } from "@/pages/weComTask/API/bookLink";
+
+interface Props extends BOOKLINK.BookLinkChildProps {
+    huaShengCreateLinkDTO: { [x: string]: any }
+    platform: string,
+    mpAccountIds: number[]
+}
+/**
+ * 花生作品链接
+ * @returns 
+ */
+const BookPromoLinkCreateDTO: React.FC<Props> = ({ restField, name, huaShengCreateLinkDTO, platform, mpAccountIds }) => {
+
+    /**********************************/
+    const { message } = App.useApp()
+    const getFreeChapterList = useAjax((params) => getFreeChapterListApi(params))
+    /**********************************/
+
+    const getChapterList = () => {
+        if (platform && mpAccountIds?.length && huaShengCreateLinkDTO?.bookPromoLinkCreateDTO?.bookId) {
+            getFreeChapterList.run({ platform, mpAccountIds, bookId: huaShengCreateLinkDTO.bookPromoLinkCreateDTO.bookId })
+        } else {
+            message.error('请先选择书城、公众号')
+        }
+    }
+
+    return <>
+        <Form.Item
+            {...restField}
+            name={[name, 'bookPromoLinkCreateDTO', 'bookId']}
+            rules={[{ required: true, message: '请输入作品(书籍)ID!' }]}
+            label={<strong>作品(书籍)ID</strong>}
+        >
+            <SelectBook
+                platformKey={platform}
+                searchChapterDisabled={!huaShengCreateLinkDTO?.bookPromoLinkCreateDTO?.bookId}
+                searchChapterLoading={getFreeChapterList.loading}
+                hanldeSearchChapter={getChapterList}
+            />
+        </Form.Item>
+
+        <Form.Item
+            {...restField}
+            name={[name, 'bookPromoLinkCreateDTO', 'chapterId']}
+            label={<strong>章节</strong>}
+            rules={[{ required: true, message: '请选择章节!' }]}
+        >
+            <Select
+                showSearch
+                placeholder="请选择章节"
+                filterOption={(input, option) =>
+                    ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                }
+                allowClear
+                options={getFreeChapterList?.data?.data?.chapterList?.map(item => ({ value: item.chapterId, label: item.chapterName }))}
+            />
+        </Form.Item>
+
+        <Form.Item
+            {...restField}
+            name={[name, 'bookPromoLinkCreateDTO', 'forceChapter']}
+            label={<strong>强关章节序号</strong>}
+            rules={[{ required: true, message: '请输入强关章节序号!' }]}
+        >
+            <InputNumber placeholder="请输入强关章节序号" style={{ width: '100%' }} />
+        </Form.Item>
+
+        <Form.Item
+            {...restField}
+            name={[name, 'bookPromoLinkCreateDTO', 'cost']}
+            label={<strong>推广成本</strong>}
+            rules={[{ required: true, message: '请输入推广成本!' }]}
+        >
+            <InputNumber placeholder="请输入推广成本" min={0} style={{ width: '100%' }} />
+        </Form.Item>
+    </>
+}
+
+export default React.memo(BookPromoLinkCreateDTO);

+ 135 - 0
src/pages/weComTask/page/bookLink/components/huasheng/giftActivityLinkDTO.tsx

@@ -0,0 +1,135 @@
+import { Checkbox, DatePicker, Form, Input, InputNumber, Radio } from "antd";
+import React from "react";
+import { DISPLAYTYPEHUASHENG } from "../../const";
+import { RangePickerProps } from "antd/es/date-picker";
+import dayjs from "dayjs";
+
+/**
+ * 免费赠币活动
+ * @returns 
+ */
+const GiftActivityLinkDTO: React.FC<BOOKLINK.BookLinkChildProps> = ({ restField, name, itemData }) => {
+
+    const disabledDate: RangePickerProps['disabledDate'] = current => {
+        return current && current < dayjs().endOf('day');
+    };
+
+    return <>
+        <Form.Item
+            {...restField}
+            name={[name, 'giftActivityLinkDTO', 'activityName']}
+            label={<strong>活动名称</strong>}
+            rules={[{ required: true, message: '请输入活动名称!' }]}
+        >
+            <Input placeholder="请输入活动名称" style={{ width: '100%' }} allowClear />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'giftActivityLinkDTO', 'freeAmount']}
+            label={<strong>活动挡位</strong>}
+            rules={[{ required: true, message: '请选择活动挡位!' }]}
+        >
+            <Radio.Group
+                buttonStyle="solid"
+                options={[50, 100, 200, 300, 500, 800, 1000].map(i => ({ label: i + '书币', value: i }))}
+            />
+        </Form.Item>
+
+        <Form.Item
+            {...restField}
+            name={[name, 'giftActivityLinkDTO', 'activityTime']}
+            label={<strong>活动时间</strong>}
+            rules={[
+                { required: true, message: '请选择活动时间!' },
+                {
+                    validator: (_: any, value: [dayjs.Dayjs, dayjs.Dayjs]) => {
+                        if (!value || value.length < 2) {
+                            return Promise.reject('请选择完整日期范围');
+                        }
+                        if (value[0].isSame(value[1], 'day')) {
+                            return Promise.reject('开始和结束日期不能相同');
+                        }
+                        return Promise.resolve();
+                    }
+                }
+            ]}
+        >
+            <DatePicker.RangePicker disabledDate={disabledDate} />
+        </Form.Item>
+
+        <Form.Item
+            {...restField}
+            name={[name, 'giftActivityLinkDTO', 'status']}
+            label={<strong>活动状态</strong>}
+            rules={[{ required: true, message: '请选择活动状态!' }]}
+        >
+            <Radio.Group
+                optionType='button'
+                buttonStyle="solid"
+                options={[
+                    { value: 'normal', label: '上架' },
+                    { value: 'del', label: '下架' }
+                ]}
+            />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'giftActivityLinkDTO', 'display']}
+            label={<strong>活动展示位</strong>}
+        >
+            <Checkbox.Group options={Object.keys(DISPLAYTYPEHUASHENG).map(key => ({ label: DISPLAYTYPEHUASHENG[key], value: key }))} />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'giftActivityLinkDTO', 'userType']}
+            label={<strong>展示粉丝</strong>}
+            rules={[{ required: true, message: '请选择展示粉丝!' }]}
+        >
+            <Radio.Group
+                optionType='button'
+                buttonStyle="solid"
+                options={[
+                    { value: 1, label: '全部粉丝' },
+                    { value: 2, label: '条件粉丝' }
+                ]}
+            />
+        </Form.Item>
+        {itemData?.giftActivityLinkDTO?.userType === 2 && <>
+            <Form.Item
+                {...restField}
+                name={[name, 'giftActivityLinkDTO', 'charge']}
+                label={<strong>充值状态</strong>}
+                rules={[{ required: true, message: '请选择充值状态!' }]}
+            >
+                <Radio.Group
+                    optionType='button'
+                    buttonStyle="solid"
+                    options={[
+                        { value: 0, label: '不限' },
+                        { value: 1, label: '已充值' },
+                        { value: 2, label: '未充值' }
+                    ]}
+                />
+            </Form.Item>
+            <Form.Item
+                {...restField}
+                name={[name, 'giftActivityLinkDTO', 'commonType']}
+                label={<strong>关注时间</strong>}
+                rules={[{ required: true, message: '请选择关注时间!' }]}
+            >
+                <Radio.Group
+                    optionType='button'
+                    buttonStyle="solid"
+                    options={[
+                        { value: 0, label: '不限' },
+                        { value: 1, label: '24小时以上' },
+                        { value: 2, label: '48小时以上' },
+                        { value: 3, label: '72小时以上' }
+                    ]}
+                />
+            </Form.Item>
+        </>}
+    </>
+}
+
+export default React.memo(GiftActivityLinkDTO);

+ 151 - 0
src/pages/weComTask/page/bookLink/components/huasheng/rechargeActivityLinkDTO.tsx

@@ -0,0 +1,151 @@
+import { Checkbox, DatePicker, Form, Input, InputNumber, Radio, Space } from "antd";
+import React from "react";
+import { DISPLAYTYPEHUASHENG } from "../../const";
+
+
+/**
+ * 花生充值活动
+ * @returns 
+ */
+const RechargeActivityLinkDTO: React.FC<BOOKLINK.BookLinkChildProps> = ({ restField, name, itemData }) => {
+
+    return <>
+        <Form.Item
+            {...restField}
+            name={[name, 'rechargeActivityLinkDTO', 'activityName']}
+            label={<strong>活动名称</strong>}
+            rules={[{ required: true, message: '请输入活动名称!' }]}
+        >
+            <Input placeholder="请输入活动名称" style={{ width: '100%' }} allowClear />
+        </Form.Item>
+        <Form.Item
+            label={<strong>充值档位</strong>}
+            required
+        >
+            <Space>
+                <Form.Item
+                    {...restField}
+                    name={[name, 'rechargeActivityLinkDTO', 'rechargeAmount']}
+                    rules={[{ required: true, message: '请输入价位!' }]}
+                    noStyle
+                >
+                    <InputNumber addonBefore="价位" suffix="元" placeholder="请输入" style={{ width: '100%' }} />
+                </Form.Item>
+                <Form.Item
+                    {...restField}
+                    name={[name, 'rechargeActivityLinkDTO', 'giftAmount']}
+                    rules={[{ required: true, message: '请输入赠送金额!' }]}
+                    noStyle
+                >
+                    <InputNumber addonBefore="赠送" suffix="元" placeholder="请输入" style={{ width: '100%' }} />
+                </Form.Item>
+            </Space>
+        </Form.Item>
+
+        <Form.Item
+            {...restField}
+            name={[name, 'rechargeActivityLinkDTO', 'activityTime']}
+            label={<strong>活动时间</strong>}
+            rules={[{ required: true, message: '请选择活动时间!' }]}
+        >
+            <DatePicker.RangePicker />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'rechargeActivityLinkDTO', 'status']}
+            label={<strong>活动状态</strong>}
+            rules={[{ required: true, message: '请选择活动状态!' }]}
+        >
+            <Radio.Group
+                optionType='button'
+                buttonStyle="solid"
+                options={[
+                    { value: 'normal', label: '上架' },
+                    { value: 'del', label: '下架' }
+                ]}
+            />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'rechargeActivityLinkDTO', 'chargeType']}
+            label={<strong>限购类型</strong>}
+            rules={[{ required: true, message: '请选择限购类型!' }]}
+        >
+            <Radio.Group
+                optionType='button'
+                buttonStyle="solid"
+                options={[
+                    { value: 0, label: '整体限购' },
+                    { value: 1, label: '每天限购' }
+                ]}
+            />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'rechargeActivityLinkDTO', 'display']}
+            label={<strong>活动展示位</strong>}
+        >
+            <Checkbox.Group options={Object.keys(DISPLAYTYPEHUASHENG).map(key => ({ label: DISPLAYTYPEHUASHENG[key], value: key }))} />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'rechargeActivityLinkDTO', 'chargeCount']}
+            label={<strong>限购次数</strong>}
+            rules={[{ required: true, message: '请选择充值次数!' }]}
+        >
+            <InputNumber placeholder="请输入" min={0} style={{ width: '100%' }} />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'rechargeActivityLinkDTO', 'userType']}
+            label={<strong>展示粉丝</strong>}
+            rules={[{ required: true, message: '请选择展示粉丝!' }]}
+        >
+            <Radio.Group
+                optionType='button'
+                buttonStyle="solid"
+                options={[
+                    { value: 1, label: '全部粉丝' },
+                    { value: 2, label: '条件粉丝' }
+                ]}
+            />
+        </Form.Item>
+        {itemData?.rechargeActivityLinkDTO?.userType === 2 && <>
+            <Form.Item
+                {...restField}
+                name={[name, 'rechargeActivityLinkDTO', 'charge']}
+                label={<strong>充值状态</strong>}
+                rules={[{ required: true, message: '请选择充值状态!' }]}
+            >
+                <Radio.Group
+                    optionType='button'
+                    buttonStyle="solid"
+                    options={[
+                        { value: 0, label: '不限' },
+                        { value: 1, label: '已充值' },
+                        { value: 2, label: '未充值' }
+                    ]}
+                />
+            </Form.Item>
+            <Form.Item
+                {...restField}
+                name={[name, 'rechargeActivityLinkDTO', 'commonType']}
+                label={<strong>关注时间</strong>}
+                rules={[{ required: true, message: '请选择关注时间!' }]}
+            >
+                <Radio.Group
+                    optionType='button'
+                    buttonStyle="solid"
+                    options={[
+                        { value: 0, label: '不限' },
+                        { value: 1, label: '24小时以上' },
+                        { value: 2, label: '48小时以上' },
+                        { value: 3, label: '72小时以上' }
+                    ]}
+                />
+            </Form.Item>
+        </>}
+    </>
+}
+
+export default React.memo(RechargeActivityLinkDTO);

+ 96 - 0
src/pages/weComTask/page/bookLink/components/wending/bookPromoLinkCreateDTO.tsx

@@ -0,0 +1,96 @@
+import { useAjax } from "@/Hook/useAjax";
+import { getFreeChapterListApi } from "@/pages/weComTask/API/bookLink";
+import { App, Form, InputNumber, Radio, Select, Switch } from "antd";
+import React from "react";
+import SelectBook from "../../selectBook";
+import { LINKCLASSIFY } from "../../const";
+
+interface Props extends BOOKLINK.BookLinkChildProps {
+    wenDingCreateLinkDTO: { [x: string]: any }
+    platform: string,
+    mpAccountIds: number[]
+}
+/**
+ * 文鼎作品链接
+ * @returns 
+ */
+const BookPromoLinkCreateDTO: React.FC<Props> = ({ restField, name, wenDingCreateLinkDTO, platform, mpAccountIds }) => {
+
+    /**********************************/
+    const { message } = App.useApp()
+    const getFreeChapterList = useAjax((params) => getFreeChapterListApi(params))
+    /**********************************/
+
+    const getChapterList = () => {
+        if (platform && mpAccountIds?.length && wenDingCreateLinkDTO?.bookPromoLinkCreateDTO?.bookId) {
+            getFreeChapterList.run({ platform, mpAccountIds, bookId: wenDingCreateLinkDTO.bookPromoLinkCreateDTO.bookId })
+        } else {
+            message.error('请先选择书城、公众号')
+        }
+    }
+
+    return <>
+        <Form.Item
+            {...restField}
+            name={[name, 'bookPromoLinkCreateDTO', 'bookId']}
+            rules={[{ required: true, message: '请输入作品(书籍)ID!' }]}
+            label={<strong>作品(书籍)ID</strong>}
+        >
+            <SelectBook
+                platformKey={platform}
+                searchChapterDisabled={!wenDingCreateLinkDTO?.bookPromoLinkCreateDTO?.bookId}
+                searchChapterLoading={getFreeChapterList.loading}
+                hanldeSearchChapter={getChapterList}
+
+            />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'bookPromoLinkCreateDTO', 'chapterId']}
+            label={<strong>章节</strong>}
+            rules={[{ required: true, message: '请选择章节!' }]}
+        >
+            <Select
+                showSearch
+                placeholder="请选择章节"
+                filterOption={(input, option) =>
+                    ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                }
+                allowClear
+                options={getFreeChapterList?.data?.data?.chapterList?.map(item => ({ value: item.chapterId, label: item.chapterName }))}
+            />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'bookPromoLinkCreateDTO', 'qrcodeOpenStatus']}
+            label={<strong>二维码展示</strong>}
+            rules={[{ required: true, message: '请选择二维码展示!' }]}
+        >
+            <Radio.Group
+                buttonStyle="solid"
+                options={[
+                    { value: 0, label: '关闭' },
+                    { value: 1, label: '开启' },
+                ]}
+            />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'bookPromoLinkCreateDTO', 'linkClassify']}
+            label={<strong>关注类型</strong>}
+            rules={[{ required: true, message: '请选择关注类型!' }]}
+        >
+            <Select
+                showSearch
+                placeholder="请选择关注类型"
+                filterOption={(input, option) =>
+                    ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                }
+                allowClear
+                options={Object.keys(LINKCLASSIFY)?.map(key => ({ value: key, label: LINKCLASSIFY[key] }))}
+            />
+        </Form.Item>
+    </>
+}
+
+export default React.memo(BookPromoLinkCreateDTO);

+ 85 - 0
src/pages/weComTask/page/bookLink/components/wending/rechargeActivityLinkDTO.tsx

@@ -0,0 +1,85 @@
+import { DatePicker, Form, Input, Radio } from "antd";
+import React from "react";
+import SetConfig from "./setConfig";
+import { RangePickerProps } from "antd/es/date-picker";
+import dayjs from 'dayjs';
+
+
+/**
+ * 文鼎充值活动
+ * @returns 
+ */
+const RechargeActivityLinkDTO: React.FC<BOOKLINK.BookLinkChildProps> = ({ restField, name }) => {
+
+    const disabledDate: RangePickerProps['disabledDate'] = current => {
+        // Can not select days before today and today
+        return current && current < dayjs().endOf('day');
+    };
+
+    return <>
+        <Form.Item
+            {...restField}
+            name={[name, 'rechargeActivityLinkDTO', 'activityName']}
+            label={<strong>活动名称</strong>}
+            rules={[{ required: true, message: '请输入活动名称!' }]}
+        >
+            <Input placeholder="请输入活动名称" style={{ width: '100%' }} allowClear />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'rechargeActivityLinkDTO', 'viewTitle']}
+            label={<strong>活动页标题</strong>}
+            rules={[{ required: true, message: '请输入活动页标题!' }]}
+        >
+            <Input placeholder="请输入活动页标题" style={{ width: '100%' }} allowClear />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'rechargeActivityLinkDTO', 'configs']}
+            label={<strong>活动充值项</strong>}
+            rules={[{ required: true, message: '请添加活动充值项!' }]}
+        >
+            <SetConfig />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'rechargeActivityLinkDTO', 'activityTime']}
+            label={<strong>活动时间</strong>}
+            rules={[{ required: true, message: '请选择活动时间!' }]}
+        >
+            <DatePicker.RangePicker disabledDate={disabledDate} />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'rechargeActivityLinkDTO', 'hideCountDown']}
+            label={<strong>活动倒计时</strong>}
+            rules={[{ required: true, message: '请选择是否显示活动倒计时!' }]}
+        >
+            <Radio.Group
+                optionType='button'
+                buttonStyle="solid"
+                options={[
+                    { value: 1, label: '隐藏' },
+                    { value: 0, label: '显示' }
+                ]}
+            />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'rechargeActivityLinkDTO', 'userGroup']}
+            label={<strong>目标用户</strong>}
+            rules={[{ required: true, message: '请选择目标用户!' }]}
+        >
+            <Radio.Group
+                optionType='button'
+                buttonStyle="solid"
+                options={[
+                    { value: 0, label: '全部用户' },
+                    { value: 10, label: '标签用户' }
+                ]}
+            />
+        </Form.Item>
+    </>
+}
+
+export default React.memo(RechargeActivityLinkDTO);

+ 248 - 0
src/pages/weComTask/page/bookLink/components/wending/setConfig.tsx

@@ -0,0 +1,248 @@
+import { App, Button, Form, InputNumber, Modal, Popconfirm, Radio, Select, Table } from 'antd';
+import React, { useState } from 'react';
+import { RECHARGETYPE } from '../../const';
+
+interface valueProps {
+    hongbao: number;
+    price: number;
+    rechargeFrequency: 0 | 1;
+    rechargeTimes: number;
+    rechargeType: number;
+    vipConfigId: string
+}
+
+interface SetConfigProps {
+    value?: valueProps[]
+    onChange?: (value: valueProps[]) => void
+}
+
+const SetConfig: React.FC<SetConfigProps> = ({ value = [], onChange }) => {
+
+    /*************************************/
+    const [visible, setVisible] = useState<boolean>(false)
+    /*************************************/
+
+    return <div>
+        <Button type="primary" onClick={() => setVisible(true)}>添加充值项</Button>
+        {value?.length > 0 && <Table
+            style={{ marginTop: 10 }}
+            dataSource={value.map((item, index) => ({ ...item, id: index + 1 }))}
+            columns={[
+                {
+                    title: '充值类型',
+                    dataIndex: 'rechargeType',
+                    key: 'rechargeType',
+                    align: 'center',
+                    render(value) {
+                        return RECHARGETYPE[value]
+                    },
+                },
+                {
+                    title: '充值金额',
+                    dataIndex: 'price',
+                    key: 'price',
+                    render(value) {
+                        return (value || value === 0) ? value + '元' : '--'
+                    },
+                },
+                {
+                    title: '赠送金额',
+                    dataIndex: 'hongbao',
+                    key: 'hongbao',
+                    render(value) {
+                        return (value || value === 0) ? value + '元' : '--'
+                    },
+                },
+                {
+                    title: '限购次数',
+                    dataIndex: 'rechargeFrequency',
+                    key: 'rechargeFrequency',
+                    render(value, record) {
+                        return value === 0 ? record.rechargeTimes === -1 ? '不限' : (record.rechargeTimes + '次') : record.rechargeTimes === -1 ? '不限' : ('每天' + record.rechargeTimes + '次')
+                    },
+                },
+                {
+                    title: '操作',
+                    dataIndex: 'cz',
+                    key: 'cz',
+                    align: 'center',
+                    render(_, record) {
+                        return <Popconfirm
+                            title="确定删除该项?"
+                            onConfirm={() => {
+                                onChange?.(value?.filter((_, index) => record.id !== (index + 1)))
+                            }}
+                        >
+                            <a>删除</a>
+                        </Popconfirm>
+                    },
+                },
+            ]}
+            rowKey={'id'}
+            size='small'
+            bordered
+            pagination={false}
+        />}
+        {visible && <SetConfigModal
+            visible={visible}
+            onClose={() => setVisible(false)}
+            onChange={(newValue) => {
+                setVisible(false)
+                const oldValue = JSON.parse(JSON.stringify(value || []))
+                oldValue.push(newValue)
+                onChange?.(oldValue)
+            }}
+        />}
+    </div>
+};
+
+interface SetConfigModalProps {
+    onChange?: (value: valueProps) => void
+    visible?: boolean;
+    onClose?: () => void;
+}
+const SetConfigModal: React.FC<SetConfigModalProps> = ({ visible, onChange, onClose }) => {
+
+    /***********************************/
+    const { message } = App.useApp()
+    const [form] = Form.useForm();
+    const rechargeType = Form.useWatch('rechargeType', form)
+    const price = Form.useWatch('price', form)
+    /***********************************/
+
+    const handleOk = () => {
+        form.validateFields().then((values) => {
+            console.log(values)
+            onChange?.(values)
+        })
+    }
+
+    return <Modal
+        title={<strong>添加充值项</strong>}
+        open={visible}
+        onCancel={onClose}
+        width={600}
+        onOk={handleOk}
+    >
+        <Form
+            form={form}
+            name="newConfig"
+            labelAlign='left'
+            labelCol={{ span: 5 }}
+            colon={false}
+            scrollToFirstError={{
+                behavior: 'smooth',
+                block: 'center'
+            }}
+            onFinishFailed={({ errorFields }) => {
+                message.error(errorFields?.[0]?.errors?.[0])
+            }}
+            onFinish={handleOk}
+            initialValues={{ rechargeType: 0, rechargeFrequency: 0 }}
+        >
+            <Form.Item label={<strong>充值类型</strong>} name="rechargeType" rules={[{ required: true, message: '请选择充值类型!' }]}>
+                <Select
+                    showSearch
+                    allowClear
+                    placeholder="请选择充值类型"
+                    filterOption={(input, option) =>
+                        ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                    }
+                    options={Object.keys(RECHARGETYPE).map(key => ({ label: RECHARGETYPE[key], value: Number(key) }))}
+                />
+            </Form.Item>
+            {rechargeType === 0 ? <>
+                <Form.Item
+                    label={<strong>充值金额</strong>}
+                    name="price"
+                    rules={[
+                        { required: true, message: '请输入充值金额!' },
+                        {
+                            validator: (_: any, value: number) => {
+                                if (value && value.toString().split('.')[1]?.length > 2) {
+                                    return Promise.reject('最多输入两位小数');
+                                }
+                                return Promise.resolve();
+                            }
+                        }
+                    ]}
+                >
+                    <InputNumber suffix="元" placeholder="请输入" min={1} max={300} precision={2} style={{ width: '100%' }} />
+                </Form.Item>
+                <Form.Item
+                    label={<strong>赠送金额</strong>}
+                    name="hongbao"
+                    rules={[
+                        { required: true, message: '请输入赠送金额!' },
+                        {
+                            validator: (_: any, value: number) => {
+                                if (!value) return Promise.resolve();
+                                // 小数位数校验
+                                if (value.toString().split('.')[1]?.length > 2) {
+                                    return Promise.reject('最多输入两位小数');
+                                }
+                                // 动态倍数校验
+                                const maxRatio = price <= 9 ? 2.5 : 1.5;
+                                const maxAmount = Math.min(price * maxRatio, 20);
+                                if (value > maxAmount) {
+                                    return Promise.reject(
+                                        price <= 9
+                                            ? `不能超过${maxRatio}倍(最大20元)`
+                                            : `不能超过${maxRatio}倍`
+                                    );
+                                }
+                                return Promise.resolve();
+                            }
+                        }
+                    ]}
+                >
+                    <InputNumber suffix="元" placeholder="请输入" precision={2} style={{ width: '100%' }} />
+                </Form.Item>
+            </> : <>
+                <Form.Item label={<strong>充值项</strong>} name="vipConfigId" rules={[{ required: true, message: '请选择充值项!' }]}>
+                    <Select
+                        showSearch
+                        allowClear
+                        placeholder="请选择充值项"
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                        }
+                        options={[
+                            { label: '365元包一年', value: '365元包一年' },
+                            { label: '399元包一年', value: '399元包一年' },
+                            { label: '666元包一年', value: '666元包一年' },
+                            { label: '499元包一年', value: '499元包一年' },
+                            { label: '499元买一年送一年', value: '499元买一年送一年' },
+                            { label: '666买一年送一年', value: '666买一年送一年' },
+                            { label: '888元包一年', value: '888元包一年' },
+                            { label: '288元包一年', value: '288元包一年' },
+                            { label: '199元包一个月', value: '199元包一个月' }
+                        ]}
+                    />
+                </Form.Item>
+            </>}
+            <Form.Item label={<strong>限购频次</strong>} name="rechargeFrequency" rules={[{ required: true, message: '请选择限购频次!' }]}>
+                <Radio.Group
+                    buttonStyle="solid"
+                    options={[
+                        { value: 0, label: '按次数' },
+                        { value: 1, label: '每天' },
+                    ]}
+                />
+            </Form.Item>
+            <Form.Item label={<strong>限购次数</strong>} name="rechargeTimes" rules={[{ required: true, message: '请选择限购次数!' }]}>
+                <Select
+                    showSearch
+                    allowClear
+                    placeholder="请选择限购次数"
+                    filterOption={(input, option) =>
+                        ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                    }
+                    options={[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(item => ({ label: item === -1 ? '不限' : item + '次', value: item }))}
+                />
+            </Form.Item>
+        </Form>
+    </Modal>
+};
+
+export default React.memo(SetConfig);

+ 105 - 0
src/pages/weComTask/page/bookLink/components/yuewen/bookPromoLinkCreateDTO.tsx

@@ -0,0 +1,105 @@
+import { useAjax } from "@/Hook/useAjax";
+import { getFreeChapterListApi } from "@/pages/weComTask/API/bookLink";
+import { App, Button, Form, Input, InputNumber, Radio, Select, Space, Switch } from "antd";
+import React from "react";
+import SelectBook from "../../selectBook";
+
+interface Props extends BOOKLINK.BookLinkChildProps {
+    yueWenCreateLinkDTO: { [x: string]: any }
+    platform: string,
+    mpAccountIds: number[]
+}
+/**
+ * 作品链接
+ * @returns 
+ */
+const BookPromoLinkCreateDTO: React.FC<Props> = ({ restField, name, yueWenCreateLinkDTO, platform, mpAccountIds }) => {
+
+    /**********************************/
+    const { message } = App.useApp()
+    const getFreeChapterList = useAjax((params) => getFreeChapterListApi(params))
+    /**********************************/
+
+    const getChapterList = () => {
+        if (platform && mpAccountIds?.length && yueWenCreateLinkDTO?.bookPromoLinkCreateDTO?.bookId) {
+            getFreeChapterList.run({ platform, mpAccountIds, bookId: yueWenCreateLinkDTO.bookPromoLinkCreateDTO.bookId })
+        } else {
+            message.error('请先选择书城、公众号')
+        }
+    }
+
+    return <>
+        <Form.Item
+            {...restField}
+            name={[name, 'bookPromoLinkCreateDTO', 'bookId']}
+            rules={[{ required: true, message: '请输入作品(书籍)ID!' }]}
+            label={<strong>作品(书籍)ID</strong>}
+        >
+            <SelectBook 
+                platformKey={platform}
+                searchChapterDisabled={!yueWenCreateLinkDTO?.bookPromoLinkCreateDTO?.bookId}
+                searchChapterLoading={getFreeChapterList.loading}
+                hanldeSearchChapter={getChapterList}
+
+            />
+        </Form.Item>
+
+        <Form.Item
+            {...restField}
+            name={[name, 'bookPromoLinkCreateDTO', 'chapterId']}
+            label={<strong>章节</strong>}
+            rules={[{ required: true, message: '请选择章节!' }]}
+        >
+            <Select
+                showSearch
+                placeholder="请选择章节"
+                filterOption={(input, option) =>
+                    ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                }
+                allowClear
+                options={getFreeChapterList?.data?.data?.chapterList?.map(item => ({ value: item.chapterId, label: item.chapterName }))}
+            />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'bookPromoLinkCreateDTO', 'forceStyle']}
+            label={<strong>强关设置</strong>}
+            rules={[{ required: true, message: '请选择强关设置!' }]}
+        >
+            <Radio.Group
+                optionType='button'
+                buttonStyle="solid"
+                options={[
+                    { value: 1, label: '不设置强关' },
+                    { value: 2, label: '主动关注' },
+                    { value: 3, label: '强制关注' },
+                ]}
+            />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'bookPromoLinkCreateDTO', 'forceChapter']}
+            label={<strong>强关章节序号</strong>}
+            rules={[{ required: true, message: '请输入强关章节序号!' }]}
+        >
+            <InputNumber placeholder="请输入强关章节序号" style={{ width: '100%' }} />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'bookPromoLinkCreateDTO', 'isOpenBottom']}
+            label={<strong>是否添加底部关注引导</strong>}
+            valuePropName="checked"
+        >
+            <Switch checkedChildren="开启" unCheckedChildren="关闭" />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'bookPromoLinkCreateDTO', 'cost']}
+            label={<strong>推广成本</strong>}
+        >
+            <InputNumber placeholder="请输入推广成本" style={{ width: '100%' }} />
+        </Form.Item>
+    </>
+}
+
+export default React.memo(BookPromoLinkCreateDTO);

+ 88 - 0
src/pages/weComTask/page/bookLink/components/yuewen/consumeActivityLinkDTO.tsx

@@ -0,0 +1,88 @@
+import { Checkbox, DatePicker, Form, Input, InputNumber, Select } from "antd";
+import React from "react";
+import { ACTICITYTHEMEXH, DISPLAYTYPE } from "../../const";
+
+
+/**
+ * 模板消耗活动
+ * @returns 
+ */
+const ConsumeActivityLinkDTO: React.FC<BOOKLINK.BookLinkChildProps> = ({ restField, name }) => {
+
+
+    return <>
+        <Form.Item
+            {...restField}
+            name={[name, 'consumeActivityLinkDTO', 'activityName']}
+            label={<strong>活动名称</strong>}
+            rules={[{ required: true, message: '请输入活动名称!' }]}
+        >
+            <Input placeholder="请输入活动名称" style={{ width: '100%' }} allowClear />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'consumeActivityLinkDTO', 'activityTheme']}
+            label={<strong>活动主题</strong>}
+            rules={[{ required: true, message: '请选择活动主题!' }]}
+        >
+            <Select
+                showSearch
+                placeholder="请选择活动主题"
+                filterOption={(input, option) =>
+                    ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                }
+                allowClear
+                options={Object.keys(ACTICITYTHEMEXH).map(key => ({ label: ACTICITYTHEMEXH[key], value: key }))}
+            />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'consumeActivityLinkDTO', 'unlockChapter']}
+            label={<strong>购买章节数量</strong>}
+            rules={[{ required: true, message: '请输入购买章节数量!' }]}
+        >
+            <InputNumber placeholder="请输入购买章节数量1-200" min={1} max={200} style={{ width: '100%' }} />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'consumeActivityLinkDTO', 'bookId']}
+            label={<strong>书籍ID</strong>}
+        >
+            <Input placeholder="请输入书籍ID,不输入默认任意书籍" style={{ width: '100%' }} allowClear />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'consumeActivityLinkDTO', 'giftAmount']}
+            label={<strong>赠送书券数量</strong>}
+            rules={[{ required: true, message: '请输入赠送书券数量!' }]}
+        >
+            <InputNumber placeholder="请输入赠送书券数量1-2000" min={1} max={2000} style={{ width: '100%' }} />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'consumeActivityLinkDTO', 'totalGift']}
+            label={<strong>赠送名额</strong>}
+            rules={[{ required: true, message: '请输入赠送名额!' }]}
+        >
+            <InputNumber placeholder="请输入赠送名额1-10000" min={1} max={10000} style={{ width: '100%' }} />
+        </Form.Item>
+
+        <Form.Item
+            {...restField}
+            name={[name, 'consumeActivityLinkDTO', 'activityTime']}
+            label={<strong>活动时间</strong>}
+            rules={[{ required: true, message: '请选择活动时间!' }]}
+        >
+            <DatePicker.RangePicker />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'consumeActivityLinkDTO', 'display']}
+            label={<strong>活动展示位</strong>}
+        >
+            <Checkbox.Group options={Object.keys(DISPLAYTYPE).map(key => ({ label: DISPLAYTYPE[key], value: key }))} />
+        </Form.Item>
+    </>
+}
+
+export default React.memo(ConsumeActivityLinkDTO);

+ 79 - 0
src/pages/weComTask/page/bookLink/components/yuewen/giftActivityLinkDTO.tsx

@@ -0,0 +1,79 @@
+import { Checkbox, DatePicker, Form, Input, InputNumber, Select } from "antd";
+import React from "react";
+import { DISPLAYTYPE, RESOURCETTYPE } from "../../const";
+
+/**
+ * 模板直赠活动
+ * @returns 
+ */
+const GiftActivityLinkDTO: React.FC<BOOKLINK.BookLinkChildProps> = ({ restField, name }) => {
+
+
+    return <>
+        <Form.Item
+            {...restField}
+            name={[name, 'giftActivityLinkDTO', 'activityName']}
+            label={<strong>活动名称</strong>}
+            rules={[{ required: true, message: '请输入活动名称!' }]}
+        >
+            <Input placeholder="请输入活动名称" style={{ width: '100%' }} allowClear />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'giftActivityLinkDTO', 'resourceType']}
+            label={<strong>赠送道具</strong>}
+            rules={[{ required: true, message: '请选择赠送道具!' }]}
+        >
+            <Select
+                showSearch
+                placeholder="请选择赠送道具"
+                filterOption={(input, option) =>
+                    ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                }
+                allowClear
+                options={Object.keys(RESOURCETTYPE).map(key => ({ label: RESOURCETTYPE[key], value: key }))}
+            />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'giftActivityLinkDTO', 'freeAmount']}
+            label={<strong>赠送书券量</strong>}
+            rules={[{ required: true, message: '请输入赠送书券量!' }]}
+        >
+            <InputNumber placeholder="请输入" min={1} style={{ width: '100%' }} />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'giftActivityLinkDTO', 'expire']}
+            label={<strong>书券有效期</strong>}
+            rules={[{ required: true, message: '请输入书券有效期1-7天!' }]}
+        >
+            <InputNumber placeholder="请输入书券有效期1-7天" suffix="天" precision={0} max={7} min={1} style={{ width: '100%' }} />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'giftActivityLinkDTO', 'totalGift']}
+            label={<strong>赠送名额</strong>}
+            rules={[{ required: true, message: '请输入赠送名额!' }]}
+        >
+            <InputNumber placeholder="请输入" min={0} style={{ width: '100%' }} />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'giftActivityLinkDTO', 'activityTime']}
+            label={<strong>活动时间</strong>}
+            rules={[{ required: true, message: '请选择活动时间!' }]}
+        >
+            <DatePicker.RangePicker />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'giftActivityLinkDTO', 'display']}
+            label={<strong>活动展示位</strong>}
+        >
+            <Checkbox.Group options={Object.keys(DISPLAYTYPE).filter(key => key === '1').map(key => ({ label: DISPLAYTYPE[key], value: key }))} />
+        </Form.Item>
+    </>
+}
+
+export default React.memo(GiftActivityLinkDTO);

+ 40 - 0
src/pages/weComTask/page/bookLink/components/yuewen/pagePromoLinkCreateDTO.tsx

@@ -0,0 +1,40 @@
+import { Form, Input, InputNumber, Select } from "antd";
+import React from "react";
+import { PAGETYPE } from "../../const";
+
+
+/**
+ * 页面链接
+ * @returns 
+ */
+const PagePromoLinkCreateDTO: React.FC<BOOKLINK.BookLinkChildProps> = ({ restField, name }) => {
+
+
+    return <>
+        <Form.Item
+            {...restField}
+            name={[name, 'pagePromoLinkCreateDTO', 'pageType']}
+            label={<strong>页面类型</strong>}
+            rules={[{ required: true, message: '请选择页面类型!' }]}
+        >
+            <Select
+                showSearch
+                placeholder="页面类型"
+                filterOption={(input, option) =>
+                    ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                }
+                allowClear
+                options={Object.keys(PAGETYPE).map(key => ({ label: PAGETYPE[key], value: key }))}
+            />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'pagePromoLinkCreateDTO', 'cost']}
+            label={<strong>推广成本</strong>}
+        >
+            <InputNumber placeholder="请输入推广成本" style={{ width: '100%' }} />
+        </Form.Item>
+    </>
+}
+
+export default React.memo(PagePromoLinkCreateDTO);

+ 125 - 0
src/pages/weComTask/page/bookLink/components/yuewen/rechargeActivityLinkDTO.tsx

@@ -0,0 +1,125 @@
+import { Checkbox, DatePicker, Form, Input, InputNumber, Radio, Select, Space } from "antd";
+import React from "react";
+import { ACTICITYTHEME, DISPLAYTYPE } from "../../const";
+
+
+/**
+ * 模板充值活动
+ * @returns 
+ */
+const RechargeActivityLinkDTO: React.FC<BOOKLINK.BookLinkChildProps> = ({ restField, name, itemData }) => {
+
+    return <>
+        <Form.Item
+            {...restField}
+            name={[name, 'rechargeActivityLinkDTO', 'activityName']}
+            label={<strong>活动名称</strong>}
+            rules={[{ required: true, message: '请输入活动名称!' }]}
+        >
+            <Input placeholder="请输入活动名称" style={{ width: '100%' }} allowClear />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'rechargeActivityLinkDTO', 'activityTheme']}
+            label={<strong>活动主题</strong>}
+            rules={[{ required: true, message: '请选择活动主题!' }]}
+        >
+            <Select
+                showSearch
+                placeholder="请选择活动主题"
+                filterOption={(input, option) =>
+                    ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                }
+                allowClear
+                options={Object.keys(ACTICITYTHEME).map(key => ({ label: ACTICITYTHEME[key], value: key }))}
+            />
+        </Form.Item>
+        <Form.Item
+            label={<strong>充值档位</strong>}
+            required
+        >
+            <Space>
+                <Form.Item
+                    {...restField}
+                    name={[name, 'rechargeActivityLinkDTO', 'rechargeAmount']}
+                    rules={[{ required: true, message: '请输入价位!' }]}
+                    noStyle
+                >
+                    <InputNumber addonBefore="价位" suffix="元" placeholder="请输入" style={{ width: '100%' }} />
+                </Form.Item>
+                <Form.Item
+                    {...restField}
+                    name={[name, 'rechargeActivityLinkDTO', 'giftAmount']}
+                    rules={[{ required: true, message: '请输入送券数量!' }]}
+                    noStyle
+                >
+                    <InputNumber addonBefore="赠送" suffix="书卷" placeholder="请输入" style={{ width: '100%' }} />
+                </Form.Item>
+            </Space>
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'rechargeActivityLinkDTO', 'rechargeCount']}
+            label={<strong>充值次数</strong>}
+            rules={[{ required: true, message: '请选择充值次数!' }]}
+        >
+            <Select
+                showSearch
+                placeholder="请选择充值次数"
+                filterOption={(input, option) =>
+                    ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                }
+                allowClear
+                options={[1, 2, 3].map(key => ({ label: key + '次', value: key }))}
+            />
+        </Form.Item>
+        {itemData?.rechargeActivityLinkDTO?.rechargeCount === 1 && <Form.Item
+            {...restField}
+            name={[name, 'rechargeActivityLinkDTO', 'isDayRepeat']}
+            label={<strong>是否每日充值活动</strong>}
+            rules={[{ required: true, message: '请选择是否每日充值活动!' }]}
+        >
+            <Radio.Group
+                optionType='button'
+                buttonStyle="solid"
+                options={[
+                    { value: 1, label: '是' },
+                    { value: 0, label: '否' }
+                ]}
+            />
+        </Form.Item>}
+        <Form.Item
+            {...restField}
+            name={[name, 'rechargeActivityLinkDTO', 'activityTime']}
+            label={<strong>活动时间</strong>}
+            rules={[{ required: true, message: '请选择活动时间!' }]}
+        >
+            <DatePicker.RangePicker />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'rechargeActivityLinkDTO', 'timeIsShow']}
+            label={<strong>活动时间状态</strong>}
+            rules={[{ required: true, message: '请选择活动时间状态!' }]}
+        >
+            <Radio.Group
+                optionType='button'
+                buttonStyle="solid"
+                options={[
+                    { value: 1, label: '开启' },
+                    { value: 0, label: '关闭' }
+                ]}
+            />
+        </Form.Item>
+        <Form.Item
+            {...restField}
+            name={[name, 'rechargeActivityLinkDTO', 'display']}
+            label={<strong>活动展示位</strong>}
+        >
+            <Checkbox.Group options={Object.keys(DISPLAYTYPE).map(key => ({ label: DISPLAYTYPE[key], value: key }))} />
+        </Form.Item>
+
+    </>
+}
+
+export default React.memo(RechargeActivityLinkDTO);

+ 163 - 0
src/pages/weComTask/page/bookLink/const.tsx

@@ -0,0 +1,163 @@
+import { Tag } from "antd"
+
+/** 阅文  链接类型 */
+export const LINKTYPE = {
+    1: '页面链接',
+    2: '作品链接',
+    3: '模板充值活动链接',
+    4: '模版直赠活动链接',
+    5: '模板消耗活动链接'
+}
+/** 番茄 链接类型 */
+export const FANQIELINKTYPE = {
+    1: '页面链接',
+    2: '作品链接',
+    3: '充值活动链接',
+    4: '年卡活动链接',
+    5: '免费赠币活动链接'
+}
+/** 花生 链接类型 */
+export const HUASHENGLINKTYPE = {
+    1: '作品链接',
+    2: '充值活动链接',
+    3: '免费赠币活动链接'
+}
+/** 文鼎 链接类型 */
+export const WENDINGLINKTYPE = {
+    1: '作品链接',
+    2: '活动链接',
+}
+
+export const LINKTYPEEle = {
+    1: <Tag color="magenta">页面链接</Tag>,
+    2: <Tag color="red">作品链接</Tag>,
+    3: <Tag color="volcano">模板充值活动链接</Tag>,
+    4: <Tag color="orange">模版直赠活动链接</Tag>,
+    5: <Tag color="gold">模板消耗活动链接</Tag>
+}
+
+// 番茄
+export const FANQIELINKTYPEEle = {
+    1: <Tag color="magenta">页面链接</Tag>,
+    2: <Tag color="red">作品链接</Tag>,
+    3: <Tag color="volcano">充值活动链接</Tag>,
+    4: <Tag color="orange">年卡活动链接</Tag>,
+    5: <Tag color="gold">免费赠币活动链接</Tag>
+}
+
+// 花生
+export const HUASHENGLINKTYPEEle = {
+    1: <Tag color="red">作品链接</Tag>,
+    2: <Tag color="volcano">充值活动链接</Tag>,
+    3: <Tag color="gold">免费赠币活动链接</Tag>
+}
+
+// 文鼎
+export const WENDINGLINKTYPEELE = {
+    1: <Tag color="red">作品链接</Tag>,
+    2: <Tag color="volcano">活动链接</Tag>,
+}
+
+// 1=书城首页2=排行榜(自动)3=排行榜(男生)4=排行榜(女生)5=充值页6=最近阅读(列表)7=限免专区8=签到页面9=个人中心10=置顶公众号引导11=新用户充值活动页12=最近阅读(最近阅读作品)13=意见反馈
+export const PAGETYPE = {
+    1: '书城首页',
+    2: '排行榜(自动)',
+    3: '排行榜(男生)',
+    4: '排行榜(女生)',
+    5: '充值页',
+    6: '最近阅读(列表)',
+    7: '限免专区',
+    8: '签到页面',
+    9: '个人中心',
+    10: '置顶公众号引导',
+    11: '新用户充值活动页',
+    12: '最近阅读(最近阅读作品)',
+    13: '意见反馈'
+}
+
+// 1=继续阅读,2=书城主页,3=签到活动,4=个人中心,5=充值记录,6=阅读历史,7=充值页面
+export const FANQIEPAGETYPE = {
+    1: '继续阅读',
+    2: '书城主页',
+    3: '签到活动',
+    4: '个人中心',
+    5: '充值记录',
+    6: '阅读历史',
+    7: '充值页面'
+}
+
+// 1 默认风格,2 男生风格,3 女生风格,4 教师节,5 中秋节,6 周末风格,7 国庆节,8 重阳节,9 万圣节,10 双十一,11 双十二,12 圣诞节,13 元旦,16 春节,
+// 17 元宵节,18 情人节,19 春季踏青,20 劳动节,21 端午节,23 女神节,24 七夕
+/**活动主题 */
+export const ACTICITYTHEME = {
+    1: '默认风格',
+    2: '男生风格',
+    3: '女生风格',
+    4: '教师节',
+    5: '中秋节',
+    6: '周末风格',
+    7: '国庆节',
+    8: '重阳节',
+    9: '万圣节',
+    10: '双十一',
+    11: '双十二',
+    12: '圣诞节',
+    13: '元旦',
+    16: '春节',
+    17: '元宵节',
+    18: '情人节',
+    19: '春季踏青',
+    20: '劳动节',
+    21: '端午节',
+    23: '女神节',
+    24: '七夕'
+}
+
+/** 模板消耗主题 */
+export const ACTICITYTHEMEXH = {
+    25: '七夕风格',
+    26: '默认风格',
+    29: '国庆风格',
+    30: '中秋风格',
+    33: '圣诞风格',
+    34: '春节风格',
+    35: '元旦风格'
+}
+
+// 活动展示位(可选,多个用逗号分隔):1=阅读页 banner2=客服消息3=首页 banner4=活动中心
+export const DISPLAYTYPE = {
+    '1': '阅读页banner',
+    '2': '客服消息',
+    '3': '首页banner',
+    '4': '活动中心'
+}
+
+// 花生活动展示位
+export const DISPLAYTYPEHUASHENG = {
+    'readlog': '书架弹窗',
+    'home': '首页弹窗',
+    'stack': '分类弹窗',
+    'mine': '个人中心弹窗',
+    'read_footer_text': '阅读页底部文字',
+    'read_footer_banner': '阅读页底部banner'
+}
+
+// 赠送道具类型:1 书券
+export const RESOURCETTYPE = {
+    1: '书卷'
+}
+
+// 关注类型
+// 1-常规二维码2-非强制二维码3-强制二维码,默认3,仅当二维码展示开启时生效
+export const LINKCLASSIFY = {
+    1: '常规二维码',
+    2: '非强制二维码',
+    3: '强制二维码'
+}
+
+
+// 文鼎充值类型
+export const RECHARGETYPE= {
+    0: '普通充值',
+    1: 'VIP会员'
+}

+ 213 - 0
src/pages/weComTask/page/bookLink/details.tsx

@@ -0,0 +1,213 @@
+import React from "react";
+import { ACTICITYTHEME, ACTICITYTHEMEXH, DISPLAYTYPE, DISPLAYTYPEHUASHENG, FANQIELINKTYPEEle, FANQIEPAGETYPE, HUASHENGLINKTYPEEle, LINKCLASSIFY, LINKTYPE, LINKTYPEEle, PAGETYPE, RECHARGETYPE, RESOURCETTYPE, WENDINGLINKTYPEELE } from "./const";
+import { Table } from "antd";
+
+interface Props {
+    linkType: keyof typeof LINKTYPE,
+    platform: string,
+    linkContent: { [x: string]: any }
+}
+
+const Details: React.FC<Props> = ({ linkType, linkContent, platform }) => {
+
+    if (platform === 'YUE_WEN') {
+        if (linkType === 1) {
+            return <div>
+                <div>链接类型:{LINKTYPEEle[linkType]}</div>
+                <div>页面类型:<span style={{ color: '#1890ff' }}>{PAGETYPE[linkContent?.pageType] || linkContent?.pageType}</span></div>
+                {linkContent?.cost && <div>消耗:<span style={{ color: '#1890ff' }}>{linkContent?.cost}</span></div>}
+            </div>
+        } else if (linkType === 2) {
+            return <div>
+                <div>链接类型:{LINKTYPEEle[linkType]}</div>
+                <div>作品(书籍)ID:<span style={{ color: '#1890ff' }}>{linkContent?.bookId}</span></div>
+                <div>章节ID:<span style={{ color: '#1890ff' }}>{linkContent?.chapterId}</span></div>
+                <div>强关设置:<span style={{ color: '#1890ff' }}>{linkContent?.forceStyle === '1' ? '不设置强关' : linkContent?.forceStyle === '2' ? '主动强关' : '强制关注'}</span></div>
+                {linkContent?.forceChapter && <div>强关章节序号:<span style={{ color: '#1890ff' }}>{linkContent?.forceChapter}</span></div>}
+                <div>是否添加底部关注引导:<span style={{ color: '#1890ff' }}>{linkContent?.isOpenBottom ? '是' : '否'}</span></div>
+                {linkContent?.cost && <div>消耗:<span style={{ color: '#1890ff' }}>{linkContent?.cost}</span></div>}
+            </div>
+        } else if (linkType === 3) {
+            return <div>
+                <div>链接类型:{LINKTYPEEle[linkType]}</div>
+                <div>活动名称:<span style={{ color: '#1890ff' }}>{linkContent?.activityName}</span></div>
+                <div>活动主题:<span style={{ color: '#1890ff' }}>{ACTICITYTHEME[linkContent?.activityTheme]}</span></div>
+                <div>充值挡位:<span style={{ color: '#1890ff' }}>充值{linkContent?.rechargeAmount}元,送{linkContent?.giftAmount}书卷</span></div>
+                <div>活动次数:<span style={{ color: '#1890ff' }}>{linkContent?.rechargeCount}</span></div>
+                <div>是否每日充值活动:<span style={{ color: '#1890ff' }}>{linkContent?.isDayRepeat ? '是' : '否'}</span></div>
+                <div>活动时间:<span style={{ color: '#1890ff' }}>{linkContent?.startTime}~{linkContent?.endTime}</span></div>
+                <div>活动时间状态:<span style={{ color: '#1890ff' }}>{linkContent?.timeIsShow ? '开启' : '关闭'}</span></div>
+                {linkContent?.display && <div>活动展示位:<span style={{ color: '#1890ff' }}>{linkContent.display.split(',').map(item => DISPLAYTYPE[item]).join('、')}</span></div>}
+            </div>
+        } else if (linkType === 4) {
+            return <div>
+                <div>链接类型:{LINKTYPEEle[linkType]}</div>
+                <div>活动名称:<span style={{ color: '#1890ff' }}>{linkContent?.activityName}</span></div>
+                <div>赠送道具:<span style={{ color: '#1890ff' }}>{RESOURCETTYPE[linkContent?.resourceType]}</span></div>
+                <div>赠送书卷量:<span style={{ color: '#1890ff' }}>{linkContent?.freeAmount}</span></div>
+                <div>书卷有效期:<span style={{ color: '#1890ff' }}>{linkContent?.expire}天</span></div>
+                <div>赠送名额:<span style={{ color: '#1890ff' }}>{linkContent?.totalGift}</span></div>
+                <div>活动时间:<span style={{ color: '#1890ff' }}>{linkContent?.startTime}~{linkContent?.endTime}</span></div>
+                {linkContent?.display && <div>活动展示位:<span style={{ color: '#1890ff' }}>{linkContent.display.split(',').map(item => DISPLAYTYPE[item]).join('、')}</span></div>}
+            </div>
+        } else if (linkType === 5) {
+            return <div>
+                <div>链接类型:{LINKTYPEEle[linkType]}</div>
+                <div>活动名称:<span style={{ color: '#1890ff' }}>{linkContent?.activityName}</span></div>
+                <div>活动主题:<span style={{ color: '#1890ff' }}>{ACTICITYTHEMEXH[linkContent?.activityTheme]}</span></div>
+                <div>购买章节数量:<span style={{ color: '#1890ff' }}>{linkContent?.unlockChapter}</span></div>
+                <div>生效书籍:<span style={{ color: '#1890ff' }}>{linkContent?.bookId || '全部'}</span></div>
+                <div>赠送书卷数量:<span style={{ color: '#1890ff' }}>{linkContent?.giftAmount}</span></div>
+                <div>赠送名额:<span style={{ color: '#1890ff' }}>{linkContent?.totalGift}</span></div>
+                <div>活动时间:<span style={{ color: '#1890ff' }}>{linkContent?.startTime}~{linkContent?.endTime}</span></div>
+                {linkContent?.display && <div>活动展示位:<span style={{ color: '#1890ff' }}>{linkContent.display.split(',').map(item => DISPLAYTYPE[item]).join('、')}</span></div>}
+            </div>
+        }
+    } else if (platform === 'FAN_QIE') { // f番茄
+        if (linkType === 1) {
+            return <div>
+                <div>链接类型:{FANQIELINKTYPEEle[linkType]}</div>
+                <div>页面类型:<span style={{ color: '#1890ff' }}>{FANQIEPAGETYPE[linkContent?.pageType] || linkContent?.pageType}</span></div>
+            </div>
+        } else if (linkType === 2) {
+            return <div>
+                <div>链接类型:{FANQIELINKTYPEEle[linkType]}</div>
+                <div>作品(书籍)ID:<span style={{ color: '#1890ff' }}>{linkContent?.bookId}</span></div>
+                <div>章节ID:<span style={{ color: '#1890ff' }}>{linkContent?.chapterId}</span></div>
+            </div>
+        } else if (linkType === 3) {
+            return <div>
+                <div>链接类型:{FANQIELINKTYPEEle[linkType]}</div>
+                <div>活动名称:<span style={{ color: '#1890ff' }}>{linkContent?.activityName}</span></div>
+                <div>活动时间:<span style={{ color: '#1890ff' }}>{linkContent?.startTime}~{linkContent?.endTime}</span></div>
+                <div>充值挡位:<span style={{ color: '#1890ff' }}>充值{linkContent?.rechargeAmount}元,送{linkContent?.giftAmount}元</span></div>
+                <div>充值次数:<span style={{ color: '#1890ff' }}>{linkContent?.rechargeCount === -1 ? '不限次' : linkContent?.rechargeCount + '次'}</span></div>
+            </div>
+        } else if (linkType === 4) {
+            return <div>
+                <div>链接类型:{FANQIELINKTYPEEle[linkType]}</div>
+                <div>活动名称:<span style={{ color: '#1890ff' }}>{linkContent?.activityName}</span></div>
+                <div>活动时间:<span style={{ color: '#1890ff' }}>{linkContent?.startTime}~{linkContent?.endTime}</span></div>
+                <div>会员时长:<span style={{ color: '#1890ff' }}>{linkContent?.vipDuration}年</span></div>
+                <div>活动价格:<span style={{ color: '#1890ff' }}>{linkContent?.activityPrice}元</span></div>
+                <div>原价:<span style={{ color: '#1890ff' }}>{linkContent?.originalPrice}元</span></div>
+                <div>充值次数:<span style={{ color: '#1890ff' }}>{linkContent?.rechargeCount}次</span></div>
+            </div>
+        } else if (linkType === 5) {
+            return <div>
+                <div>链接类型:{FANQIELINKTYPEEle[linkType]}</div>
+                <div>活动名称:<span style={{ color: '#1890ff' }}>{linkContent?.activityName}</span></div>
+                <div>活动时间:<span style={{ color: '#1890ff' }}>{linkContent?.startTime}~{linkContent?.endTime}</span></div>
+                <div>赠送书币:<span style={{ color: '#1890ff' }}>{linkContent?.giftAmount}个</span></div>
+                <div>赠送总次数:<span style={{ color: '#1890ff' }}>{linkContent?.rechargeCount}次</span></div>
+                <div>频率限制:<span style={{ color: '#1890ff' }}>{linkContent?.activityFrequencies?.activityFrequenciesEnum == 2 ? '每天一次' : '每周一次'}</span></div>
+            </div>
+        }
+    } else if (platform === 'HUA_SHENG') {
+        if (linkType === 1) {
+            return <div>
+                <div>链接类型:{HUASHENGLINKTYPEEle[linkType]}</div>
+                <div>作品(书籍)ID:<span style={{ color: '#1890ff' }}>{linkContent?.bookId}</span></div>
+                <div>章节ID:<span style={{ color: '#1890ff' }}>{linkContent?.chapterId}</span></div>
+                <div>强关章节序号:<span style={{ color: '#1890ff' }}>{linkContent?.forceChapter}</span></div>
+                <div>消耗:<span style={{ color: '#1890ff' }}>{linkContent?.cost}</span></div>
+            </div>
+        } else if (linkType === 2) {
+            return <div>
+                <div>链接类型:{HUASHENGLINKTYPEEle[linkType]}</div>
+                <div>活动名称:<span style={{ color: '#1890ff' }}>{linkContent?.activityName}</span></div>
+                <div>充值挡位:<span style={{ color: '#1890ff' }}>{linkContent?.productName}</span></div>
+                <div>活动时间:<span style={{ color: '#1890ff' }}>{linkContent?.startTime}~{linkContent?.endTime}</span></div>
+                <div>活动状态:<span style={{ color: '#1890ff' }}>{linkContent?.status === 'normal' ? '上架' : '下架'}</span></div>
+                <div>限购类型:<span style={{ color: '#1890ff' }}>{linkContent?.chargeType === 0 ? '整体限购' : '每天限购'}</span></div>
+                {linkContent?.display && <div>活动展示位:<span style={{ color: '#1890ff' }}>{linkContent.display.split(',').map(item => DISPLAYTYPEHUASHENG[item]).join('、')}</span></div>}
+                <div>限购次数:<span style={{ color: '#1890ff' }}>{linkContent?.chargeCount}次</span></div>
+                <div>展示粉丝:<span style={{ color: '#1890ff' }}>{linkContent?.userType === 1 ? '全部粉丝' : '条件粉丝'}</span></div>
+                {linkContent?.userType === 2 && <>
+                    <div>充值状态:<span style={{ color: '#1890ff' }}>{linkContent?.charge === 0 ? '不限' : linkContent?.charge === 1 ? '已充值' : '未充值'}</span></div>
+                    <div>关注时间:<span style={{ color: '#1890ff' }}>{linkContent?.commonType === 0 ? '不限' : linkContent?.charge === 1 ? '24小时以上' : linkContent?.charge === 1 ? '48小时以上' : '72小时以上'}</span></div>
+                </>}
+            </div>
+        } else if (linkType === 3) {
+            return <div>
+                <div>链接类型:{HUASHENGLINKTYPEEle[linkType]}</div>
+                <div>活动名称:<span style={{ color: '#1890ff' }}>{linkContent?.activityName}</span></div>
+                <div>活动挡位:<span style={{ color: '#1890ff' }}>{linkContent?.freeAmount}书币</span></div>
+                <div>活动时间:<span style={{ color: '#1890ff' }}>{linkContent?.startTime}~{linkContent?.endTime}</span></div>
+                <div>活动状态:<span style={{ color: '#1890ff' }}>{linkContent?.status === 'normal' ? '上架' : '下架'}</span></div>
+                {linkContent?.display && <div>活动展示位:<span style={{ color: '#1890ff' }}>{linkContent.display.split(',').map(item => DISPLAYTYPEHUASHENG[item]).join('、')}</span></div>}
+                <div>展示粉丝:<span style={{ color: '#1890ff' }}>{linkContent?.userType === 1 ? '全部粉丝' : '条件粉丝'}</span></div>
+                {linkContent?.userType === 2 && <>
+                    <div>充值状态:<span style={{ color: '#1890ff' }}>{linkContent?.charge === 0 ? '不限' : linkContent?.charge === 1 ? '已充值' : '未充值'}</span></div>
+                    <div>关注时间:<span style={{ color: '#1890ff' }}>{linkContent?.commonType === 0 ? '不限' : linkContent?.charge === 1 ? '24小时以上' : linkContent?.charge === 1 ? '48小时以上' : '72小时以上'}</span></div>
+                </>}
+            </div>
+        }
+    } else if (platform === 'WEN_DING') {
+        if (linkType === 1) {
+            return <div>
+                <div>链接类型:{WENDINGLINKTYPEELE[linkType]}</div>
+                <div>作品(书籍)ID:<span style={{ color: '#1890ff' }}>{linkContent?.bookId}</span></div>
+                <div>章节ID:<span style={{ color: '#1890ff' }}>{linkContent?.chapterId}</span></div>
+                <div>二维码展示:<span style={{ color: '#1890ff' }}>{linkContent?.qrcodeOpenStatus === 1 ? '开启' : '关闭'}</span></div>
+                <div>关注类型:<span style={{ color: '#1890ff' }}>{LINKCLASSIFY[linkContent?.linkClassify]}</span></div>
+            </div>
+        } else if (linkType === 2) {
+            return <div>
+                <div>链接类型:{WENDINGLINKTYPEELE[linkType]}</div>
+                <div>活动名称:<span style={{ color: '#1890ff' }}>{linkContent?.activityName}</span></div>
+                <div>活动页标题:<span style={{ color: '#1890ff' }}>{linkContent?.viewTitle}</span></div>
+                <div>活动充值项<Table
+                    style={{ marginTop: 10 }}
+                    dataSource={linkContent?.configs.map((item, index) => ({ ...item, id: index + 1 }))}
+                    columns={[
+                        {
+                            title: '充值类型',
+                            dataIndex: 'rechargeType',
+                            key: 'rechargeType',
+                            align: 'center',
+                            render(value) {
+                                return RECHARGETYPE[value]
+                            },
+                        },
+                        {
+                            title: '充值金额',
+                            dataIndex: 'price',
+                            key: 'price',
+                            render(value) {
+                                return (value || value === 0) ? value + '元' : '--'
+                            },
+                        },
+                        {
+                            title: '赠送金额',
+                            dataIndex: 'hongbao',
+                            key: 'hongbao',
+                            render(value) {
+                                return (value || value === 0) ? value + '元' : '--'
+                            },
+                        },
+                        {
+                            title: '限购次数',
+                            dataIndex: 'rechargeFrequency',
+                            key: 'rechargeFrequency',
+                            render(value, record: any) {
+                                return value === 0 ? record.rechargeTimes === -1 ? '不限' : (record.rechargeTimes + '次') : record.rechargeTimes === -1 ? '不限' : ('每天' + record.rechargeTimes + '次')
+                            },
+                        }
+                    ]}
+                    rowKey={'id'}
+                    size='small'
+                    bordered
+                    pagination={false}
+                /></div>
+                <div>活动时间:<span style={{ color: '#1890ff' }}>{linkContent?.startTime}~{linkContent?.endTime}</span></div>
+                <div>活动倒计时:<span style={{ color: '#1890ff' }}>{linkContent?.hideCountDown === 1 ? '隐藏' : '显示'}</span></div>
+                <div>目标用户:<span style={{ color: '#1890ff' }}>{linkContent?.userGroup === 10 ? '标签用户' : '全部用户'}</span></div>
+            </div>
+        }
+    }
+
+    return null
+}
+
+export default React.memo(Details);

+ 13 - 0
src/pages/weComTask/page/bookLink/index.less

@@ -0,0 +1,13 @@
+.bookLinkTable {
+    flex: 1 0;
+    overflow: hidden;
+}
+
+.bookLinkPagination {
+    padding: 10px 16px;
+    border-top: 1px solid #d5d5d5;
+}
+
+.content_col {
+    
+}

+ 382 - 0
src/pages/weComTask/page/bookLink/index.tsx

@@ -0,0 +1,382 @@
+import { App, Button, DatePicker, Input, Pagination, Popconfirm, Select, Table } from 'antd';
+import Card from 'antd/es/card/Card';
+import React, { useEffect, useRef, useState } from 'react';
+import { delAutoLinkApi, getCorpAutoLinkListApi, GetCorpAutoLinkListProps } from '../../API/bookLink';
+import style from './index.less'
+import { useAjax } from '@/Hook/useAjax';
+import SearchBox from '../../components/searchBox';
+import { SearchOutlined, PlusOutlined, DeleteOutlined } from '@ant-design/icons';
+import { getBindMpListApi } from '../../API/corpUserAssign';
+import { inject, observer } from 'mobx-react';
+import { toJS } from 'mobx';
+import { LINKTYPE } from './const';
+import ModalBookLink from './modalBooklink';
+import { useSize } from 'ahooks';
+import { bookLinkTableConfig } from './tableConfig';
+import dayJs from 'dayjs';
+import '../groupLeaderManage/global.less'
+
+/**
+ * 书城自动链接管理
+ * @returns 
+ */
+const BookLink: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookListProps[], bookPlatForm: TASK_CREATE.BookPlatFormProps[] } } }> = ({ weComTaskStore }) => {
+
+    /******************************************/
+    const { message } = App.useApp()
+    const { bookPlatForm } = toJS(weComTaskStore.data)
+    const ref = useRef<HTMLDivElement>(null)
+    const size = useSize(ref)
+
+    const [queryParams, setQueryParams] = useState<GetCorpAutoLinkListProps>({ pageNum: 1, pageSize: 20 })
+    const [queryParamsNew, setQueryParamsNew] = useState<GetCorpAutoLinkListProps>({ pageNum: 1, pageSize: 20 })
+    const [mpList, setMplist] = useState<{ label: string, value: number }[]>([])
+    const [visible, setVisible] = useState<boolean>(false)
+    const [initialValues, setInitialValues] = useState<any>()
+    const [selectedRows, setselectedRows] = useState<any[]>([])
+
+    const getCorpAutoLinkList = useAjax((params) => getCorpAutoLinkListApi(params))
+    const delAutoLink = useAjax((params) => delAutoLinkApi(params))
+    const getBindMpList = useAjax(() => getBindMpListApi())
+    /******************************************/
+
+    useEffect(() => {
+        getCorpAutoLinkList.run(queryParamsNew)
+    }, [queryParamsNew])
+
+    useEffect(() => {
+        getBindMpList.run().then(res => {
+            setMplist(res?.data?.map((item: any) => ({ label: item.name, value: item.id })))
+        })
+    }, [])
+
+    const handleCopy = (value: any) => {
+        const { linkType, linkContext, channelName, channelType, platform, mpAccountId } = value
+        if (platform === 'YUE_WEN') {  // 阅文
+            const yueWenCreateLinkDTO: { [x: string]: any } = {
+                linkType: linkType + '',
+                channelName,
+                channelType: channelType + ''
+            }
+            switch (linkType) {
+                case 1:
+                    yueWenCreateLinkDTO.pagePromoLinkCreateDTO = {
+                        ...linkContext,
+                        pageType: linkContext.pageType + ''
+                    }
+                    break
+                case 2:
+                    yueWenCreateLinkDTO.bookPromoLinkCreateDTO = { ...linkContext, forceStyle: Number(linkContext.forceStyle) }
+                    break
+                case 3:
+                    yueWenCreateLinkDTO.rechargeActivityLinkDTO = {
+                        ...linkContext,
+                        activityTheme: linkContext.activityTheme + '',
+                        activityTime: [dayJs(linkContext.startTime), dayJs(linkContext.endTime)],
+                        display: linkContext?.display ? linkContext?.display?.split(',') : []
+                    }
+                    break
+                case 4:
+                    yueWenCreateLinkDTO.giftActivityLinkDTO = {
+                        ...linkContext,
+                        // activityTheme: Number(linkContext.activityTheme), 
+                        activityTime: [dayJs(linkContext.startTime), dayJs(linkContext.endTime)],
+                        display: linkContext?.display ? linkContext?.display?.split(',') : [],
+                        resourceType: linkContext.resourceType + ''
+                    }
+                    break
+                case 5:
+                    yueWenCreateLinkDTO.consumeActivityLinkDTO = {
+                        ...linkContext,
+                        activityTheme: linkContext.activityTheme + '',
+                        activityTime: [dayJs(linkContext.startTime), dayJs(linkContext.endTime)],
+                        display: linkContext?.display ? linkContext?.display?.split(',') : [],
+                    }
+                    break
+            }
+            setInitialValues({
+                platform,
+                mpAccountIds: [mpAccountId],
+                yueWenCreateLinkDTOList: [yueWenCreateLinkDTO]
+            })
+        } else if (platform === 'FAN_QIE') { // 番茄
+            const fanQieCreateLinkDTO: { [x: string]: any } = {
+                linkType: linkType + '',
+                channelName
+            }
+            switch (linkType) {
+                case 1:
+                    fanQieCreateLinkDTO.pagePromoLinkCreateDTO = {
+                        ...linkContext,
+                        pageType: linkContext.pageType + ''
+                    }
+                    break
+                case 2:
+                    fanQieCreateLinkDTO.bookPromoLinkCreateDTO = { ...linkContext }
+                    break
+                case 3:
+                    fanQieCreateLinkDTO.rechargeActivityLinkDTO = {
+                        ...linkContext,
+                        activityTime: [dayJs(linkContext.startTime), dayJs(linkContext.endTime)]
+                    }
+                    break
+                case 4:
+                    fanQieCreateLinkDTO.giftActivityLinkDTO = {
+                        ...linkContext,
+                        activityTime: [dayJs(linkContext.startTime), dayJs(linkContext.endTime)]
+                    }
+                    break
+                case 5:
+                    fanQieCreateLinkDTO.consumeActivityLinkDTO = {
+                        ...linkContext,
+                        activityTime: [dayJs(linkContext.startTime), dayJs(linkContext.endTime)],
+                    }
+                    break
+            }
+            setInitialValues({
+                platform,
+                mpAccountIds: [mpAccountId],
+                fanQieCreateLinkDTOList: [fanQieCreateLinkDTO]
+            })
+        } else if (platform === 'HUA_SHENG') { // 花生
+            const huaShengCreateLinkDTO: { [x: string]: any } = {
+                linkType: linkType + '',
+                channelName,
+                channelType: channelType + ''
+            }
+
+            switch (linkType) {
+                case 1:
+                    huaShengCreateLinkDTO.bookPromoLinkCreateDTO = { ...linkContext }
+                    break;
+                case 2:
+                    const [rechargeAmount, giftAmount] = linkContext.productName.split('-')
+                    huaShengCreateLinkDTO.rechargeActivityLinkDTO = {
+                        ...linkContext,
+                        activityTime: [dayJs(linkContext.startTime), dayJs(linkContext.endTime)],
+                        display: linkContext?.display ? linkContext?.display?.split(',') : [],
+                        rechargeAmount,
+                        giftAmount
+                    }
+                    break;
+                case 3:
+                    huaShengCreateLinkDTO.giftActivityLinkDTO = {
+                        ...linkContext,
+                        activityTime: [dayJs(linkContext.startTime), dayJs(linkContext.endTime)],
+                        display: linkContext?.display ? linkContext?.display?.split(',') : []
+                    }
+                    break
+            }
+            setInitialValues({
+                platform,
+                mpAccountIds: [mpAccountId],
+                huaShengCreateLinkDTOList: [huaShengCreateLinkDTO]
+            })
+        } else if (platform === 'WEN_DING') { // 文鼎
+            const wenDingCreateLinkDTO: { [x: string]: any } = {
+                linkType: linkType + '',
+                channelName,
+                channelType: channelType
+            }
+
+            switch (linkType) {
+                case 1:
+                    wenDingCreateLinkDTO.bookPromoLinkCreateDTO = { ...linkContext, linkClassify: linkContext.linkClassify + '' }
+                    break;
+                case 2:
+                    wenDingCreateLinkDTO.rechargeActivityLinkDTO = {
+                        ...linkContext,
+                        activityTime: [dayJs(linkContext.startTime), dayJs(linkContext.endTime)],
+                    }
+                    break;
+            }
+            console.log('wenDingCreateLinkDTO--->', wenDingCreateLinkDTO)
+            setInitialValues({
+                platform,
+                mpAccountIds: [mpAccountId],
+                wenDingCreateLinkDTOList: [wenDingCreateLinkDTO]
+            })
+        }
+
+        setVisible(true)
+    }
+
+    const handleDel = (value: { linkIds: number[] }) => {
+        const hide = message.loading('正在删除...', 0)
+        delAutoLink.run(value).then(res => {
+            hide()
+            setselectedRows([])
+            if (res?.data) {
+                message.success('删除成功')
+                getCorpAutoLinkList.refresh()
+            } else {
+                message.error('删除失败')
+            }
+        }).catch(() => hide())
+    }
+
+    return <Card
+        styles={{ body: { padding: 0, display: 'flex', flexDirection: 'column', height: 'calc(100vh - 74px)', overflow: 'hidden' } }}
+    >
+        <div>
+            <SearchBox
+                bodyPadding={`10px 16px 4px`}
+                buttons={<>
+                    <Button type="primary" onClick={() => {
+                        setQueryParamsNew({ ...queryParams, pageNum: 1 })
+                    }} loading={getCorpAutoLinkList.loading} icon={<SearchOutlined />}>搜索</Button>
+                    <Button type="primary" icon={<PlusOutlined />} onClick={() => setVisible(true)}>链接生成</Button>
+                    <Popconfirm
+                        title="确定删除?"
+                        onConfirm={() => { handleDel({ linkIds: selectedRows.map(i => i.id) }) }}
+                        disabled={selectedRows.length === 0}
+                    >
+                        <Button type='primary' danger icon={<DeleteOutlined />} loading={delAutoLink.loading} disabled={selectedRows.length === 0}>删除</Button>
+                    </Popconfirm>
+                </>}
+            >
+                <>
+                    <Select
+                        value={queryParams?.platform}
+                        onChange={(e) => setQueryParams({ ...queryParams, platform: e })}
+                        showSearch
+                        style={{ width: 110 }}
+                        placeholder="书城"
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                        }
+                        allowClear
+                        options={bookPlatForm.map(item => ({ value: item.platformKey, label: item.platformName }))}
+                    />
+                    <Select
+                        value={queryParams?.mpAccountId}
+                        onChange={(e) => setQueryParams({ ...queryParams, mpAccountId: e })}
+                        showSearch
+                        style={{ width: 110 }}
+                        placeholder="公众号"
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                        }
+                        allowClear
+                        options={mpList}
+                    />
+                    <Select
+                        value={queryParams?.linkType}
+                        onChange={(e) => setQueryParams({ ...queryParams, linkType: e })}
+                        showSearch
+                        style={{ width: 110 }}
+                        placeholder="链接类型"
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                        }
+                        allowClear
+                        options={Object.keys(LINKTYPE).map(key => ({ label: LINKTYPE[key], value: key }))}
+                    />
+                    <Input placeholder='请输入渠道名称' style={{ width: 150 }} allowClear value={queryParams?.channelName} onChange={(e) => setQueryParams({ ...queryParams, channelName: e.target.value })} />
+                    <Select
+                        style={{ width: 110 }}
+                        showSearch
+                        placeholder="推广类型"
+                        value={queryParams?.channelType}
+                        onChange={(value) => setQueryParams({ ...queryParams, channelType: value })}
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                        }
+                        allowClear
+                        options={[{ label: '外部', value: '1' }, { label: '内部', value: '2' }]}
+                    />
+                    <DatePicker.RangePicker
+                        placeholder={['创建时间开始', '创建时间结束']}
+                        allowClear
+                        value={queryParams?.createTimeStart ? [dayJs(queryParams?.createTimeStart), dayJs(queryParams?.createTimeEnd)] : undefined}
+                        onChange={(_, options) => setQueryParams({ ...queryParams, createTimeStart: options?.[0], createTimeEnd: options?.[1] })}
+                    />
+                </>
+            </SearchBox>
+        </div>
+        <div className={style.bookLinkTable} ref={ref}>
+            <Table
+                dataSource={getCorpAutoLinkList?.data?.data?.records}
+                columns={bookLinkTableConfig(false, handleCopy, handleDel)}
+                bordered
+                pagination={false}
+                rowKey={'id'}
+                size='small'
+                className='resetTable'
+                loading={getCorpAutoLinkList?.loading}
+                scroll={{ y: size?.height && ref.current ? size?.height - ref.current.querySelector('.ant-table-thead').clientHeight : 300 }}
+                rowSelection={{
+                    selectedRowKeys: selectedRows?.map((item: any) => item?.id),
+                    onSelect: (record: { id: string }, selected: boolean) => {
+                        let newData = JSON.parse(JSON.stringify(selectedRows))
+                        if (selected) {
+                            newData.push({ ...record })
+                        } else {
+                            newData = newData.filter((item: { id: string }) => item.id !== record.id)
+                        }
+                        setselectedRows(newData)
+                    },
+                    onSelectAll: (selected: boolean, _: { id: string }[], changeRows: { id: string }[]) => {
+                        let newData = JSON.parse(JSON.stringify(selectedRows || '[]'))
+                        if (selected) {
+                            changeRows.forEach((item: { id: string }) => {
+                                let index = newData.findIndex((ite: { id: string }) => ite.id === item.id)
+                                if (index === -1) {
+                                    newData.push(item)
+                                }
+                            })
+                        } else {
+                            let newSelectAccData = newData.filter((item: { id: string }) => {
+                                let index = changeRows.findIndex((ite: { id: string }) => ite.id === item.id)
+                                if (index !== -1) {
+                                    return false
+                                } else {
+                                    return true
+                                }
+                            })
+                            newData = newSelectAccData
+                        }
+                        setselectedRows(newData)
+                    }
+                }}
+            />
+        </div>
+        <div className={style.bookLinkPagination}>
+            <Pagination
+                size="small"
+                total={getCorpAutoLinkList?.data?.data?.total || 0}
+                showSizeChanger
+                showQuickJumper
+                pageSize={getCorpAutoLinkList?.data?.data?.size || 20}
+                current={getCorpAutoLinkList?.data?.data?.current || 1}
+                onChange={(page: number, pageSize: number) => {
+                    // ref.current?.scrollTo({ top: 0 })
+                    setTimeout(() => {
+                        setQueryParams({ ...queryParams, pageNum: page, pageSize })
+                        setQueryParamsNew({ ...queryParamsNew, pageNum: page, pageSize })
+                    }, 50)
+                }}
+                showTotal={(total: number) => <span style={{ fontSize: 12 }}>共 {total} 条</span>}
+            />
+        </div>
+
+        {/* 链接生成 模态框 */}
+        {visible && <ModalBookLink
+            mpList={mpList}
+            bookPlatForm={bookPlatForm}
+            visible={visible}
+            initialValues={initialValues}
+            onChange={() => {
+                setInitialValues(undefined)
+                setVisible(false)
+                getCorpAutoLinkList.refresh()
+            }}
+            onClose={() => {
+                setInitialValues(undefined)
+                setVisible(false)
+            }}
+        />}
+    </Card>
+};
+
+export default inject('store')(observer((props: any) => BookLink(props.store)));

+ 662 - 0
src/pages/weComTask/page/bookLink/modalBooklink.tsx

@@ -0,0 +1,662 @@
+import { App, Button, Card, Form, Input, Modal, Select } from "antd";
+import React from "react";
+import { FANQIELINKTYPE, HUASHENGLINKTYPE, LINKTYPE, WENDINGLINKTYPE } from "./const";
+import { PlusOutlined, MinusCircleOutlined } from "@ant-design/icons";
+import { useAjax } from "@/Hook/useAjax";
+import { addCorpAutoLinkApi } from "../../API/bookLink";
+import PagePromoLinkCreateDTO from "./components/yuewen/pagePromoLinkCreateDTO";
+import BookPromoLinkCreateDTO from "./components/yuewen/bookPromoLinkCreateDTO";
+import RechargeActivityLinkDTO from "./components/yuewen/rechargeActivityLinkDTO";
+import GiftActivityLinkDTO from "./components/yuewen/giftActivityLinkDTO";
+import ConsumeActivityLinkDTO from "./components/yuewen/consumeActivityLinkDTO";
+import PagePromoLinkCreateDTOFanQie from "./components/fanqie/pagePromoLinkCreateDTO";
+import BookPromoLinkCreateDTOFanqie from "./components/fanqie/bookPromoLinkCreateDTO";
+import RechargeActivityLinkDTOFanqie from "./components/fanqie/rechargeActivityLinkDTO";
+import GiftActivityLinkDTOFanQie from "./components/fanqie/giftActivityLinkDTO";
+import ConsumeActivityLinkDTOFanQie from "./components/fanqie/consumeActivityLinkDTO";
+import BookPromoLinkCreateDTOHuaSheng from './components/huasheng/bookPromoLinkCreateDTO'
+import RechargeActivityLinkDTOHuaSheng from './components/huasheng/rechargeActivityLinkDTO'
+import GiftActivityLinkDTOHuaSheng from './components/huasheng/giftActivityLinkDTO'
+import BookPromoLinkCreateDTOWenDing from './components/wending/bookPromoLinkCreateDTO'
+import RechargeActivityLinkDTOWenDing from './components/wending/rechargeActivityLinkDTO'
+import dayJs from "dayjs";
+
+interface Props {
+    mpList: { label: string, value: number }[]
+    bookPlatForm: TASK_CREATE.BookPlatFormProps[]
+    visible?: boolean;
+    onChange?: () => void
+    onClose?: () => void
+    initialValues?: any
+}
+
+export const DispatchBookLink = React.createContext<BOOKLINK.DispatchBookLink | null>(null);
+
+/**
+ * 书城链接生成
+ * @param param0 
+ * @returns 
+ */
+const ModalBookLink: React.FC<Props> = ({ mpList, bookPlatForm, visible, onChange, onClose, initialValues = { yueWenCreateLinkDTOList: [{}] } }) => {
+
+    /********************************************/
+    const { message } = App.useApp()
+    const [form] = Form.useForm();
+    const yueWenCreateLinkDTOList = Form.useWatch('yueWenCreateLinkDTOList', form)
+    const fanQieCreateLinkDTOList = Form.useWatch('fanQieCreateLinkDTOList', form)
+    const huaShengCreateLinkDTOList = Form.useWatch('huaShengCreateLinkDTOList', form)
+    const wenDingCreateLinkDTOList = Form.useWatch('wenDingCreateLinkDTOList', form)
+    const platform = Form.useWatch('platform', form)
+    const mpAccountIds = Form.useWatch('mpAccountIds', form)
+
+    const addCorpAutoLink = useAjax((params) => addCorpAutoLinkApi(params))
+    /********************************************/
+
+    const handleOk = () => {
+        form.validateFields().then((values) => {
+            console.log(values)
+            let params: { [x: string]: any } = {}
+            if (values?.platform === 'YUE_WEN') {
+                params = {
+                    mpAccountIds: values.mpAccountIds,
+                    platform: values.platform,
+                    yueWenCreateLinkDTOList: values.yueWenCreateLinkDTOList.map(item => {
+                        const { linkType, pagePromoLinkCreateDTO, bookPromoLinkCreateDTO, rechargeActivityLinkDTO, consumeActivityLinkDTO, giftActivityLinkDTO, ...rest } = item
+                        if (linkType === '1') {
+                            return { ...rest, pagePromoLinkCreateDTO, linkType }
+                        } else if (linkType === '2') {
+                            return { ...rest, bookPromoLinkCreateDTO, linkType }
+                        } else if (linkType === '3') {
+                            const { activityTime, display, isDayRepeat, rechargeCount, ...ralDto } = rechargeActivityLinkDTO
+                            return { ...rest, rechargeActivityLinkDTO: { ...ralDto, rechargeCount, isDayRepeat: rechargeCount == 1 ? isDayRepeat : false, startTime: dayJs(activityTime[0]).format('YYYY-MM-DD'), endTime: dayJs(activityTime[1]).format('YYYY-MM-DD'), display: display?.join(',') }, linkType }
+                        } else if (linkType === '4') {
+                            const { activityTime, display, ...calDto } = giftActivityLinkDTO
+                            return { ...rest, giftActivityLinkDTO: { ...calDto, startTime: dayJs(activityTime[0]).format('YYYY-MM-DD'), endTime: dayJs(activityTime[1]).format('YYYY-MM-DD'), display: display?.join(',') }, linkType }
+                        } else if (linkType === '5') {
+                            const { activityTime, display, ...galDto } = consumeActivityLinkDTO
+                            return { ...rest, consumeActivityLinkDTO: { ...galDto, startTime: dayJs(activityTime[0]).format('YYYY-MM-DD'), endTime: dayJs(activityTime[1]).format('YYYY-MM-DD'), display: display?.join(',') }, linkType }
+                        }
+                    })
+                }
+            } else if (values?.platform === 'FAN_QIE') {
+                params = {
+                    mpAccountIds: values.mpAccountIds,
+                    platform: values.platform,
+                    fanQieCreateLinkDTOList: values.fanQieCreateLinkDTOList.map(item => {
+                        const { linkType, pagePromoLinkCreateDTO, bookPromoLinkCreateDTO, rechargeActivityLinkDTO, consumeActivityLinkDTO, giftActivityLinkDTO, ...rest } = item
+                        if (linkType === '1') {
+                            return { ...rest, pagePromoLinkCreateDTO, linkType }
+                        } else if (linkType === '2') {
+                            return { ...rest, bookPromoLinkCreateDTO, linkType }
+                        } else if (linkType === '3') {
+                            const { activityTime, ...ralDto } = rechargeActivityLinkDTO
+                            return { ...rest, rechargeActivityLinkDTO: { ...ralDto, startTime: dayJs(activityTime[0]).format('YYYY-MM-DD'), endTime: dayJs(activityTime[1]).format('YYYY-MM-DD') }, linkType }
+                        } else if (linkType === '4') {
+                            const { activityTime, ...calDto } = giftActivityLinkDTO
+                            return { ...rest, giftActivityLinkDTO: { ...calDto, startTime: dayJs(activityTime[0]).format('YYYY-MM-DD'), endTime: dayJs(activityTime[1]).format('YYYY-MM-DD') }, linkType }
+                        } else if (linkType === '5') {
+                            const { activityTime, ...galDto } = consumeActivityLinkDTO
+                            return { ...rest, consumeActivityLinkDTO: { ...galDto, startTime: dayJs(activityTime[0]).format('YYYY-MM-DD'), endTime: dayJs(activityTime[1]).format('YYYY-MM-DD') }, linkType }
+                        }
+                    })
+                }
+            } else if (values?.platform === 'HUA_SHENG') {
+                params = {
+                    mpAccountIds: values.mpAccountIds,
+                    platform: values.platform,
+                    huaShengCreateLinkDTOList: values.huaShengCreateLinkDTOList.map(item => {
+                        const { linkType, bookPromoLinkCreateDTO, rechargeActivityLinkDTO, giftActivityLinkDTO, ...rest } = item
+                        if (linkType === '1') {
+                            return { ...rest, bookPromoLinkCreateDTO, linkType }
+                        } else if (linkType === '2') {
+                            const { activityTime, display, rechargeAmount, giftAmount, ...ralDto } = rechargeActivityLinkDTO
+                            return { ...rest, rechargeActivityLinkDTO: { ...ralDto, productName: rechargeAmount + '-' + giftAmount, startTime: dayJs(activityTime[0]).format('YYYY-MM-DD'), endTime: dayJs(activityTime[1]).format('YYYY-MM-DD'), display: display?.join(',') }, linkType }
+                        } else if (linkType === '3') {
+                            const { activityTime, display, rechargeAmount, giftAmount, ...ralDto } = giftActivityLinkDTO
+                            return { ...rest, giftActivityLinkDTO: { ...ralDto, startTime: dayJs(activityTime[0]).format('YYYY-MM-DD'), endTime: dayJs(activityTime[1]).format('YYYY-MM-DD'), display: display?.join(',') }, linkType }
+                        }
+                    })
+                }
+            } else if (values?.platform === 'WEN_DING') {
+                params = {
+                    mpAccountIds: values.mpAccountIds,
+                    platform: values.platform,
+                    wenDingCreateLinkDTOList: values.wenDingCreateLinkDTOList.map(item => {
+                        const { linkType, bookPromoLinkCreateDTO, rechargeActivityLinkDTO, giftActivityLinkDTO, ...rest } = item
+                        if (linkType === '1') {
+                            return { ...rest, bookPromoLinkCreateDTO, linkType }
+                        } else if (linkType === '2') {
+                            const { activityTime, ...ralDto } = rechargeActivityLinkDTO
+                            return { ...rest, rechargeActivityLinkDTO: { ...ralDto, startTime: dayJs(activityTime[0]).format('YYYY-MM-DD'), endTime: dayJs(activityTime[1]).format('YYYY-MM-DD') }, linkType }
+                        }
+                    })
+                }
+            } else {
+                message.error('当前书城占不支持')
+                return
+            }
+            addCorpAutoLink.run(params).then(res => {
+                if (res?.data) {
+                    message.success('添加成功')
+                    onChange?.()
+                }
+            })
+        }).catch(() => {
+            form.submit()
+        });
+    }
+
+    return <Modal
+        title={<strong>书城链接生成</strong>}
+        open={visible}
+        onCancel={onClose}
+        onOk={handleOk}
+        width={650}
+        confirmLoading={addCorpAutoLink.loading}
+    >
+        <Form
+            form={form}
+            name="newBookLink"
+            labelAlign='left'
+            labelCol={{ span: 5 }}
+            colon={false}
+            scrollToFirstError={{
+                behavior: 'smooth',
+                block: 'center'
+            }}
+            onFinishFailed={({ errorFields }) => {
+                message.error(errorFields?.[0]?.errors?.[0])
+            }}
+            onFinish={handleOk}
+            initialValues={initialValues}
+        >
+            <Card title={<strong>基础信息配置</strong>} style={{ background: '#fff', marginBottom: 10 }}>
+                <Form.Item label={<strong>书城</strong>} name="platform" rules={[{ required: true, message: '请选择书城!' }]}>
+                    <Select
+                        showSearch
+                        placeholder="书城"
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                        }
+                        allowClear
+                        options={bookPlatForm.map(item => ({ value: item.platformKey, label: item.platformName }))}
+                        onChange={(e) => {
+                            if (e === 'YUE_WEN') {
+                                form.setFieldsValue({ yueWenCreateLinkDTOList: [{}] })
+                            } else if (e === 'FAN_QIE') {
+                                form.setFieldsValue({ fanQieCreateLinkDTOList: [{}] })
+                            } else if (e === 'HUA_SHENG') {
+                                form.setFieldsValue({ huaShengCreateLinkDTOList: [{}] })
+                            } else if (e === 'WEN_DING') {
+                                form.setFieldsValue({ wenDingCreateLinkDTOList: [{}] })
+                            }
+                        }}
+                    />
+                </Form.Item>
+                <Form.Item label={<strong>公众号</strong>} name="mpAccountIds" rules={[{ required: true, message: '请选择公众号!' }]}>
+                    <Select
+                        showSearch
+                        mode="multiple"
+                        allowClear
+                        placeholder="公众号"
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                        }
+                        options={mpList}
+                    />
+                </Form.Item>
+            </Card>
+            <DispatchBookLink.Provider value={{ form }}>
+                {platform === 'YUE_WEN' ? <Form.List name="yueWenCreateLinkDTOList">
+                    {(fields, { add, remove }) => (
+                        <>
+                            {fields.map(({ key, name, ...restField }, index) => {
+
+                                const yueWenCreateLinkDTO = yueWenCreateLinkDTOList?.[index]
+
+                                return <Card
+                                    title={<strong>链接信息配置{index + 1}</strong>}
+                                    style={{ background: '#fff', marginBottom: 10 }}
+                                    key={key}
+                                    extra={yueWenCreateLinkDTOList?.length > 1 ? <div style={{ color: 'red' }} onClick={() => remove(name)}>
+                                        <MinusCircleOutlined />
+                                    </div> : undefined}
+                                >
+                                    <Form.Item
+                                        {...restField}
+                                        name={[name, 'linkType']}
+                                        label={<strong>链接类型</strong>}
+                                        rules={[{ required: true, message: '请选择链接类型!' }]}
+                                    >
+                                        <Select
+                                            showSearch
+                                            placeholder="链接类型"
+                                            filterOption={(input, option) =>
+                                                ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                                            }
+                                            allowClear
+                                            options={Object.keys(LINKTYPE).map(key => ({ label: LINKTYPE[key], value: key }))}
+                                            onChange={(e) => {
+                                                if (e === '1') {
+                                                    setTimeout(() => {
+                                                        form.setFieldsValue({
+                                                            yueWenCreateLinkDTOList: yueWenCreateLinkDTOList.map((item, i) => {
+                                                                if (index === i) {
+                                                                    const { channelName, channelType } = item
+                                                                    return { channelName, channelType, linkType: '1', pagePromoLinkCreateDTO: {} }
+                                                                }
+                                                                return item
+                                                            })
+                                                        })
+                                                    }, 0)
+                                                } else if (e === '2') {
+                                                    setTimeout(() => {
+                                                        form.setFieldsValue({
+                                                            yueWenCreateLinkDTOList: yueWenCreateLinkDTOList.map((item, i) => {
+                                                                if (index === i) {
+                                                                    const { channelName, channelType } = item
+                                                                    return { channelName, channelType, linkType: '2', bookPromoLinkCreateDTO: { forceStyle: 1, isOpenBottom: false, forceChapter: 0 } }
+                                                                }
+                                                                return item
+                                                            })
+                                                        })
+                                                    }, 0)
+                                                } else if (e === '3') {
+                                                    setTimeout(() => {
+                                                        form.setFieldsValue({
+                                                            yueWenCreateLinkDTOList: yueWenCreateLinkDTOList.map((item, i) => {
+                                                                if (index === i) {
+                                                                    const { channelName, channelType } = item
+                                                                    return { channelName, channelType, linkType: '3', rechargeActivityLinkDTO: { rechargeCount: 1, timeIsShow: 1, isDayRepeat: 0 } }
+                                                                }
+                                                                return item
+                                                            })
+                                                        })
+                                                    }, 0)
+                                                } else if (e === '4') {
+                                                    setTimeout(() => {
+                                                        form.setFieldsValue({
+                                                            yueWenCreateLinkDTOList: yueWenCreateLinkDTOList.map((item, i) => {
+                                                                if (index === i) {
+                                                                    const { channelName, channelType } = item
+                                                                    return { channelName, channelType, linkType: '4', giftActivityLinkDTO: { expire: 7, resourceType: '1' } }
+                                                                }
+                                                                return item
+                                                            })
+                                                        })
+                                                    }, 0)
+                                                } else if (e === '5') {
+                                                    setTimeout(() => {
+                                                        form.setFieldsValue({
+                                                            yueWenCreateLinkDTOList: yueWenCreateLinkDTOList.map((item, i) => {
+                                                                if (index === i) {
+                                                                    const { channelName, channelType } = item
+                                                                    return { channelName, channelType, linkType: '5', consumeActivityLinkDTO: {} }
+                                                                }
+                                                                return item
+                                                            })
+                                                        })
+                                                    }, 0)
+                                                }
+                                            }}
+                                        />
+                                    </Form.Item>
+                                    <Form.Item
+                                        {...restField}
+                                        name={[name, 'channelName']}
+                                        label={<strong>渠道名称</strong>}
+                                        rules={[{ required: true, message: '请输入渠道名称!' }]}
+                                    >
+                                        <Input placeholder="请输入渠道名称" allowClear />
+                                    </Form.Item>
+                                    <Form.Item
+                                        {...restField}
+                                        name={[name, 'channelType']}
+                                        label={<strong>推广类型</strong>}
+                                        rules={[{ required: true, message: '请选择推广类型!' }]}
+                                    >
+                                        <Select
+                                            showSearch
+                                            placeholder="推广类型"
+                                            filterOption={(input, option) =>
+                                                ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                                            }
+                                            allowClear
+                                            options={[{ label: '外部', value: '1' }, { label: '内部', value: '2' }]}
+                                        />
+                                    </Form.Item>
+                                    {yueWenCreateLinkDTO?.linkType === '1' ? <PagePromoLinkCreateDTO restField={restField} name={name} /> :
+                                        yueWenCreateLinkDTO?.linkType === '2' ? <BookPromoLinkCreateDTO restField={restField} name={name} platform={platform} mpAccountIds={mpAccountIds} yueWenCreateLinkDTO={yueWenCreateLinkDTO} /> :
+                                            yueWenCreateLinkDTO?.linkType === '3' ? <RechargeActivityLinkDTO restField={restField} name={name} itemData={yueWenCreateLinkDTO} /> :
+                                                yueWenCreateLinkDTO?.linkType === '4' ? <GiftActivityLinkDTO restField={restField} name={name} /> :
+                                                    yueWenCreateLinkDTO?.linkType === '5' ? <ConsumeActivityLinkDTO restField={restField} name={name} /> :
+                                                        undefined}
+                                </Card>
+                            })}
+                            <Form.Item>
+                                <Button type="dashed" onClick={() => add({})} block icon={<PlusOutlined />}>
+                                    新增链接信息配置
+                                </Button>
+                            </Form.Item>
+                        </>
+                    )}
+                </Form.List> : platform === "FAN_QIE" ? <Form.List name="fanQieCreateLinkDTOList">
+                    {(fields, { add, remove }) => (
+                        <>
+                            {fields.map(({ key, name, ...restField }, index) => {
+                                const fanQieCreateLinkDTO = fanQieCreateLinkDTOList?.[index]
+                                return <Card
+                                    title={<strong>链接信息配置{index + 1}</strong>}
+                                    style={{ background: '#fff', marginBottom: 10 }}
+                                    key={key}
+                                    extra={fanQieCreateLinkDTOList?.length > 1 ? <div style={{ color: 'red' }} onClick={() => remove(name)}>
+                                        <MinusCircleOutlined />
+                                    </div> : undefined}
+                                >
+                                    <Form.Item
+                                        {...restField}
+                                        name={[name, 'linkType']}
+                                        label={<strong>链接类型</strong>}
+                                        rules={[{ required: true, message: '请选择链接类型!' }]}
+                                    >
+                                        <Select
+                                            showSearch
+                                            placeholder="链接类型"
+                                            filterOption={(input, option) =>
+                                                ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                                            }
+                                            allowClear
+                                            options={Object.keys(FANQIELINKTYPE).map(key => ({ label: FANQIELINKTYPE[key], value: key }))}
+                                            onChange={(e) => {
+                                                if (e === '1') {
+                                                    setTimeout(() => {
+                                                        form.setFieldsValue({
+                                                            fanQieCreateLinkDTOList: fanQieCreateLinkDTOList.map((item, i) => {
+                                                                if (index === i) {
+                                                                    const { channelName } = item
+                                                                    return { channelName, linkType: '1', pagePromoLinkCreateDTO: {} }
+                                                                }
+                                                                return item
+                                                            })
+                                                        })
+                                                    }, 0)
+                                                } else if (e === '2') {
+                                                    setTimeout(() => {
+                                                        form.setFieldsValue({
+                                                            fanQieCreateLinkDTOList: fanQieCreateLinkDTOList.map((item, i) => {
+                                                                if (index === i) {
+                                                                    const { channelName } = item
+                                                                    return { channelName, linkType: '2', bookPromoLinkCreateDTO: {} }
+                                                                }
+                                                                return item
+                                                            })
+                                                        })
+                                                    }, 0)
+                                                } else if (e === '3') {
+                                                    setTimeout(() => {
+                                                        form.setFieldsValue({
+                                                            fanQieCreateLinkDTOList: fanQieCreateLinkDTOList.map((item, i) => {
+                                                                if (index === i) {
+                                                                    const { channelName } = item
+                                                                    return { channelName, linkType: '3', rechargeActivityLinkDTO: { rechargeCount: -1 } }
+                                                                }
+                                                                return item
+                                                            })
+                                                        })
+                                                    }, 0)
+                                                } else if (e === '4') {
+                                                    setTimeout(() => {
+                                                        form.setFieldsValue({
+                                                            fanQieCreateLinkDTOList: fanQieCreateLinkDTOList.map((item, i) => {
+                                                                if (index === i) {
+                                                                    const { channelName } = item
+                                                                    return { channelName, linkType: '4', giftActivityLinkDTO: { rechargeCount: 1 } }
+                                                                }
+                                                                return item
+                                                            })
+                                                        })
+                                                    }, 0)
+                                                } else if (e === '5') {
+                                                    setTimeout(() => {
+                                                        form.setFieldsValue({
+                                                            fanQieCreateLinkDTOList: fanQieCreateLinkDTOList.map((item, i) => {
+                                                                if (index === i) {
+                                                                    const { channelName } = item
+                                                                    return { channelName, linkType: '5', consumeActivityLinkDTO: { rechargeCount: 1, activityFrequencies: 2 } }
+                                                                }
+                                                                return item
+                                                            })
+                                                        })
+                                                    }, 0)
+                                                }
+                                            }}
+                                        />
+                                    </Form.Item>
+                                    <Form.Item
+                                        {...restField}
+                                        name={[name, 'channelName']}
+                                        label={<strong>渠道名称</strong>}
+                                        rules={[{ required: true, message: '请输入渠道名称!' }]}
+                                    >
+                                        <Input placeholder="请输入渠道名称" allowClear />
+                                    </Form.Item>
+                                    {fanQieCreateLinkDTO?.linkType === '1' ? <PagePromoLinkCreateDTOFanQie restField={restField} name={name} /> :
+                                        fanQieCreateLinkDTO?.linkType === '2' ? <BookPromoLinkCreateDTOFanqie restField={restField} name={name} platform={platform} /> :
+                                            fanQieCreateLinkDTO?.linkType === '3' ? <RechargeActivityLinkDTOFanqie restField={restField} name={name} /> :
+                                                fanQieCreateLinkDTO?.linkType === '4' ? <GiftActivityLinkDTOFanQie restField={restField} name={name} vipDuration={fanQieCreateLinkDTO?.giftActivityLinkDTO?.vipDuration} /> :
+                                                    fanQieCreateLinkDTO?.linkType === '5' ? <ConsumeActivityLinkDTOFanQie restField={restField} name={name} /> :
+                                                        undefined}
+                                </Card>
+                            })}
+                            <Form.Item>
+                                <Button type="dashed" onClick={() => add({})} block icon={<PlusOutlined />}>
+                                    新增链接信息配置
+                                </Button>
+                            </Form.Item>
+                        </>
+                    )}
+                </Form.List> : platform === 'HUA_SHENG' ? <Form.List name="huaShengCreateLinkDTOList">
+                    {(fields, { add, remove }) => (
+                        <>
+                            {fields.map(({ key, name, ...restField }, index) => {
+                                const huaShengCreateLinkDTO = huaShengCreateLinkDTOList?.[index]
+                                return <Card
+                                    title={<strong>链接信息配置{index + 1}</strong>}
+                                    style={{ background: '#fff', marginBottom: 10 }}
+                                    key={key}
+                                    extra={huaShengCreateLinkDTOList?.length > 1 ? <div style={{ color: 'red' }} onClick={() => remove(name)}>
+                                        <MinusCircleOutlined />
+                                    </div> : undefined}
+                                >
+                                    <Form.Item
+                                        {...restField}
+                                        name={[name, 'linkType']}
+                                        label={<strong>链接类型</strong>}
+                                        rules={[{ required: true, message: '请选择链接类型!' }]}
+                                    >
+                                        <Select
+                                            showSearch
+                                            placeholder="链接类型"
+                                            filterOption={(input, option) =>
+                                                ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                                            }
+                                            allowClear
+                                            options={Object.keys(HUASHENGLINKTYPE).map(key => ({ label: HUASHENGLINKTYPE[key], value: key }))}
+                                            onChange={(e) => {
+                                                if (e === '1') {
+                                                    setTimeout(() => {
+                                                        form.setFieldsValue({
+                                                            huaShengCreateLinkDTOList: huaShengCreateLinkDTOList.map((item, i) => {
+                                                                if (index === i) {
+                                                                    const { channelName, channelType } = item
+                                                                    return { channelName, channelType, linkType: '1', bookPromoLinkCreateDTO: { cost: 0 } }
+                                                                }
+                                                                return item
+                                                            })
+                                                        })
+                                                    }, 0)
+                                                } else if (e === '2') {
+                                                    setTimeout(() => {
+                                                        form.setFieldsValue({
+                                                            huaShengCreateLinkDTOList: huaShengCreateLinkDTOList.map((item, i) => {
+                                                                if (index === i) {
+                                                                    const { channelName, channelType } = item
+                                                                    return { channelName, channelType, linkType: '2', rechargeActivityLinkDTO: { status: 'normal', chargeType: 0, userType: 1, charge: 0, commonType: 0, display: ['stack', 'read_footer_text'] } }
+                                                                }
+                                                                return item
+                                                            })
+                                                        })
+                                                    }, 0)
+                                                } else if (e === '3') {
+                                                    setTimeout(() => {
+                                                        form.setFieldsValue({
+                                                            huaShengCreateLinkDTOList: huaShengCreateLinkDTOList.map((item, i) => {
+                                                                if (index === i) {
+                                                                    const { channelName } = item
+                                                                    return { channelName, linkType: '3', giftActivityLinkDTO: { status: 'normal', freeAmount: 50, userType: 1, charge: 0, commonType: 0, display: ['stack', 'read_footer_text'] } }
+                                                                }
+                                                                return item
+                                                            })
+                                                        })
+                                                    }, 0)
+                                                }
+                                            }}
+                                        />
+                                    </Form.Item>
+                                    <Form.Item
+                                        {...restField}
+                                        name={[name, 'channelName']}
+                                        label={<strong>渠道名称</strong>}
+                                        rules={[{ required: true, message: '请输入渠道名称!' }]}
+                                    >
+                                        <Input placeholder="请输入渠道名称" allowClear />
+                                    </Form.Item>
+                                    <Form.Item
+                                        {...restField}
+                                        name={[name, 'channelType']}
+                                        label={<strong>推广类型</strong>}
+                                        rules={[{ required: huaShengCreateLinkDTO?.linkType == 1 ? true : false, message: '请选择推广类型!' }]}
+                                    >
+                                        <Select
+                                            showSearch
+                                            placeholder="推广类型"
+                                            filterOption={(input, option) =>
+                                                ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                                            }
+                                            allowClear
+                                            options={[{ label: '外部', value: '1' }, { label: '内部', value: '2' }]}
+                                        />
+                                    </Form.Item>
+                                    {huaShengCreateLinkDTO?.linkType === '1' ? <BookPromoLinkCreateDTOHuaSheng restField={restField} name={name} platform={platform} mpAccountIds={mpAccountIds} huaShengCreateLinkDTO={huaShengCreateLinkDTO} /> :
+                                        huaShengCreateLinkDTO?.linkType === '2' ? <RechargeActivityLinkDTOHuaSheng restField={restField} name={name} itemData={huaShengCreateLinkDTO} /> :
+                                            huaShengCreateLinkDTO?.linkType === '3' ? <GiftActivityLinkDTOHuaSheng restField={restField} name={name} itemData={huaShengCreateLinkDTO} /> :
+                                                undefined}
+                                </Card>
+                            })}
+                            <Form.Item>
+                                <Button type="dashed" onClick={() => add({})} block icon={<PlusOutlined />}>
+                                    新增链接信息配置
+                                </Button>
+                            </Form.Item>
+                        </>
+                    )}
+                </Form.List> : platform === 'WEN_DING' ? <Form.List name="wenDingCreateLinkDTOList">
+                    {(fields, { add, remove }) => (
+                        <>
+                            {fields.map(({ key, name, ...restField }, index) => {
+                                const wenDingCreateLinkDTO = wenDingCreateLinkDTOList?.[index]
+                                return <Card
+                                    title={<strong>链接信息配置{index + 1}</strong>}
+                                    style={{ background: '#fff', marginBottom: 10 }}
+                                    key={key}
+                                    extra={wenDingCreateLinkDTOList?.length > 1 ? <div style={{ color: 'red' }} onClick={() => remove(name)}>
+                                        <MinusCircleOutlined />
+                                    </div> : undefined}
+                                >
+                                    <Form.Item
+                                        {...restField}
+                                        name={[name, 'linkType']}
+                                        label={<strong>链接类型</strong>}
+                                        rules={[{ required: true, message: '请选择链接类型!' }]}
+                                    >
+                                        <Select
+                                            showSearch
+                                            placeholder="链接类型"
+                                            filterOption={(input, option) =>
+                                                ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                                            }
+                                            allowClear
+                                            options={Object.keys(WENDINGLINKTYPE).map(key => ({ label: WENDINGLINKTYPE[key], value: key }))}
+                                            onChange={(e) => {
+                                                if (e === '1') {
+                                                    setTimeout(() => {
+                                                        form.setFieldsValue({
+                                                            wenDingCreateLinkDTOList: wenDingCreateLinkDTOList.map((item, i) => {
+                                                                if (index === i) {
+                                                                    const { channelName, channelType } = item
+                                                                    return { channelName, channelType, linkType: '1', bookPromoLinkCreateDTO: { qrcodeOpenStatus: 0, linkClassify: '3' } }
+                                                                }
+                                                                return item
+                                                            })
+                                                        })
+                                                    }, 0)
+                                                } else if (e === '2') {
+                                                    setTimeout(() => {
+                                                        form.setFieldsValue({
+                                                            wenDingCreateLinkDTOList: wenDingCreateLinkDTOList.map((item, i) => {
+                                                                if (index === i) {
+                                                                    const { channelName, channelType } = item
+                                                                    return { channelName, channelType, linkType: '2', rechargeActivityLinkDTO: { userGroup: 0, hideCountDown: 0 } }
+                                                                }
+                                                                return item
+                                                            })
+                                                        })
+                                                    }, 0)
+                                                }
+                                            }}
+                                        />
+                                    </Form.Item>
+                                    <Form.Item
+                                        {...restField}
+                                        name={[name, 'channelName']}
+                                        label={<strong>渠道名称</strong>}
+                                        rules={[{ required: true, message: '请输入渠道名称!' }]}
+                                    >
+                                        <Input placeholder="请输入渠道名称" allowClear />
+                                    </Form.Item>
+                                    <Form.Item
+                                        {...restField}
+                                        name={[name, 'channelType']}
+                                        label={<strong>推广类型</strong>}
+                                        rules={[{ required: true, message: '请选择推广类型!' }]}
+                                    >
+                                        <Select
+                                            showSearch
+                                            placeholder="推广类型"
+                                            filterOption={(input, option) =>
+                                                ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                                            }
+                                            allowClear
+                                            options={[{ label: '内推', value: 0 }, { label: '派单', value: 1 }]}
+                                        />
+                                    </Form.Item>
+                                    {wenDingCreateLinkDTO?.linkType === '1' ? <BookPromoLinkCreateDTOWenDing restField={restField} name={name} platform={platform} mpAccountIds={mpAccountIds} wenDingCreateLinkDTO={wenDingCreateLinkDTO} /> :
+                                        wenDingCreateLinkDTO?.linkType === '2' ? <RechargeActivityLinkDTOWenDing restField={restField} name={name} /> :
+                                            undefined}
+                                </Card>
+                            })}
+                            <Form.Item>
+                                <Button type="dashed" onClick={() => add({})} block icon={<PlusOutlined />}>
+                                    新增链接信息配置
+                                </Button>
+                            </Form.Item>
+                        </>
+                    )}
+                </Form.List> : undefined}
+
+            </DispatchBookLink.Provider>
+        </Form>
+    </Modal>
+};
+
+export default React.memo(ModalBookLink);

+ 122 - 0
src/pages/weComTask/page/bookLink/selectBook.tsx

@@ -0,0 +1,122 @@
+import { useAjax } from '@/Hook/useAjax';
+import { App, Button, Flex, Input, InputProps, Modal, Space, Table, Tag } from 'antd';
+import React, { useCallback, useEffect, useState } from 'react';
+import { getAutoLinkBookListApi, GetAutoLinkBookListProps } from '../../API/bookLink';
+import { SearchOutlined } from '@ant-design/icons';
+
+interface SelectBookProps extends InputProps {
+    platformKey: string,
+    hanldeSearchChapter?: () => void;
+    searchChapterLoading?: boolean;
+    searchChapterDisabled?: boolean;
+    isSearchChapter?: boolean;
+}
+
+/**
+ * 书籍选择
+ * @param param0 
+ * @returns 
+ */
+const SelectBook: React.FC<SelectBookProps> = ({ platformKey, value, onChange, hanldeSearchChapter, searchChapterLoading, searchChapterDisabled, isSearchChapter = true, ...rest }) => {
+
+    /*******************************************/
+    const { message } = App.useApp()
+    const [visible, setVisible] = useState<boolean>(false)
+    const [queryForm, setQueryForm] = useState<GetAutoLinkBookListProps>({ pageNum: 1, pageSize: 20 })
+    const [queryFormNew, setQueryFormNew] = useState<GetAutoLinkBookListProps>({ pageNum: 1, pageSize: 20 })
+    const [selectedRows, setSelectedRows] = useState<any[]>([])
+
+    const getAutoLinkBookList = useAjax((params) => getAutoLinkBookListApi(params))
+    /*******************************************/
+
+    useEffect(() => {
+        if (visible && platformKey) {
+            getAutoLinkBookList.run({ ...queryFormNew, platformKey })
+        }
+    }, [visible, queryFormNew, platformKey])
+
+    const handleOk = useCallback(() => {
+        if (selectedRows.length) {
+            onChange?.(selectedRows[0])
+            setVisible(false)
+            setSelectedRows([])
+        } else {
+            message.warning('请选择书籍')
+        }
+    }, [selectedRows])
+
+    return <>
+        <Flex gap={10}>
+            <Space.Compact style={{ width: '100%' }}>
+                <Input
+                    placeholder="请输入作品(书籍)ID"
+                    style={{ width: '100%' }}
+                    {...rest}
+                    value={value}
+                    onChange={onChange}
+                />
+                <Button type="primary" disabled={!platformKey} onClick={() => setVisible(true)}>选择书</Button>
+            </Space.Compact>
+            {isSearchChapter && <Button type="primary" loading={searchChapterLoading} disabled={searchChapterDisabled} onClick={() => hanldeSearchChapter?.()}>查询章节列表</Button>}
+        </Flex>
+        {visible && <Modal
+            title={<strong>选择书籍</strong>}
+            open={visible}
+            onCancel={() => {
+                setVisible(false)
+                setSelectedRows([]);
+            }}
+            onOk={handleOk}
+            width={600}
+        >
+            <Flex gap={6} style={{ marginBottom: 10 }}>
+                <Input placeholder='书籍名称' allowClear value={queryForm.bookName} onChange={(e) => setQueryForm({ ...queryForm, bookName: e.target.value, pageNum: 1 })} />
+                <Input placeholder='书籍ID' allowClear value={queryForm.bookId} onChange={(e) => setQueryForm({ ...queryForm, bookId: e.target.value, pageNum: 1 })} />
+                <Button type="primary" onClick={() => {
+                    setQueryFormNew({ ...queryForm })
+                }} loading={getAutoLinkBookList.loading} icon={<SearchOutlined />}>搜索</Button>
+            </Flex>
+            <Table
+                dataSource={getAutoLinkBookList?.data?.data?.records}
+                columns={[
+                    {
+                        title: '书籍',
+                        dataIndex: 'bookName',
+                        key: 'bookName',
+                        render: (value, records: any) => {
+                            return <span style={{ fontSize: 12 }}>{value}({records.bookId})</span>
+                        }
+                    },
+                ]}
+                bordered
+                pagination={{
+                    total: getAutoLinkBookList.data?.data?.total,
+                    showTotal: (total) => <Tag color="cyan">总共{total}数据</Tag>,
+                    showSizeChanger: true,
+                    showLessItems: true,
+                    defaultCurrent: 1,
+                    defaultPageSize: 20,//默认初始的每页条数
+                    current: getAutoLinkBookList?.data?.data?.current || 1,
+                    pageSize: getAutoLinkBookList?.data?.data?.size || 20,
+                    onChange: (page, pageSize) => {
+                        setQueryForm({ ...queryForm, pageNum: page, pageSize })
+                        setQueryFormNew({ ...queryFormNew, pageNum: page, pageSize })
+                    }
+                }}
+                rowKey={'bookId'}
+                size='small'
+                loading={getAutoLinkBookList?.loading}
+                scroll={{ y: 400 }}
+                rowSelection={{
+                    selectedRowKeys: selectedRows,
+                    type: 'radio',
+                    onChange(selectedRowKeys) {
+                        setSelectedRows(selectedRowKeys)
+                    },
+                }}
+            />
+        </Modal>}
+    </>
+};
+
+export default React.memo(SelectBook);

+ 229 - 0
src/pages/weComTask/page/bookLink/selectBookLink.tsx

@@ -0,0 +1,229 @@
+import { App, Button, DatePicker, Input, Modal, Select, Space, Table, Tag, Typography } from "antd";
+import style from '../../components/selectExternalAccount/index.less'
+import { useEffect, useState } from "react";
+import { getCorpAutoLinkListApi, GetCorpAutoLinkListProps } from "../../API/bookLink";
+import { useAjax } from "@/Hook/useAjax";
+import { getBindMpListApi } from "../../API/corpUserAssign";
+import { LINKTYPE } from "./const";
+const { Text, Title } = Typography;
+import dayJs from 'dayjs';
+import { SearchOutlined, CloseOutlined, CheckOutlined } from '@ant-design/icons'
+import { bookLinkTableConfig } from "./tableConfig";
+
+interface Props {
+    bookPlatForm: TASK_CREATE.BookPlatFormProps[]
+    linkData: { [x: string]: any }[]
+    mpAccountId: number | null
+    platform: string
+    visible?: boolean;
+    onClose?: () => void;
+    onChange?: (linkStr: string, miniprogramAppid?: string, miniprogramPage?: string) => void;
+}
+
+/**
+ * 选择链接
+ * @param param0 
+ * @returns 
+ */
+const SelectBookLink: React.FC<Props> = ({ bookPlatForm, linkData, mpAccountId, visible, onClose, onChange, platform }) => {
+
+    /*******************************************/
+    const { message } = App.useApp()
+    const [data, setData] = useState<{ [x: string]: any }[]>(linkData)
+    const [selectAdz, setSelectAdz] = useState<number>(1)   // 选择广告主
+    const [mpList, setMplist] = useState<{ label: string, value: number }[]>([])
+    const [queryParams, setQueryParams] = useState<GetCorpAutoLinkListProps>({ pageNum: 1, pageSize: 20, mpAccountId, platform })
+    const [queryParamsNew, setQueryParamsNew] = useState<GetCorpAutoLinkListProps>({ pageNum: 1, pageSize: 20, mpAccountId, platform })
+
+    const getCorpAutoLinkList = useAjax((params) => getCorpAutoLinkListApi(params))
+    const getBindMpList = useAjax(() => getBindMpListApi())
+    /*******************************************/
+
+    useEffect(() => {
+        getCorpAutoLinkList.run({ ...queryParamsNew, mpAccountId })
+    }, [queryParamsNew, mpAccountId, platform])
+
+    useEffect(() => {
+        getBindMpList.run().then(res => {
+            setMplist(res?.data?.map((item: any) => ({ label: item.name, value: item.id })))
+        })
+    }, [])
+
+    const handleOk = () => {
+        if (data?.every(item => item?.bookLink?.length)) {
+            const miniprogramAppid: string[] = [];
+            const miniprogramPage: string[] = [];
+            const linkStr = data.map(item => {
+                if (item?.bookLink?.[0]?.wxAppid) {
+                    miniprogramAppid.push(item?.bookLink?.[0]?.wxAppid)
+                    miniprogramPage.push(item?.bookLink?.[0]?.miniappPagePath)
+                }
+                return item.bookLink?.[0].link
+            }).join(',')
+            onChange?.(linkStr, miniprogramAppid?.join(','), miniprogramPage?.join(','))
+        } else {
+            message.error('请选择链接')
+        }
+    }
+
+    const handleSelectAdz = (value: number) => {
+        if (value === selectAdz) {
+            return
+        }
+        setSelectAdz(value)
+    }
+
+    return <Modal
+        title={<strong>选择链接</strong>}
+        open={visible}
+        onCancel={onClose}
+        onOk={handleOk}
+        width={1200}
+        className={`${style.SelectPackage}`}
+        styles={{
+            body: {
+                padding: '0 10px 0 10px'
+            }
+        }}
+    >
+        <div className={style.content}>
+            <div className={style.left}>
+                <h4 className={style.title}>链接</h4>
+                <div className={style.accountIdList}>
+                    {data?.map((item, index) => {
+                        const bookLink = item?.bookLink
+                        return <div key={index} onClick={() => { handleSelectAdz(index + 1) }} className={`${style.accItem} ${selectAdz === index + 1 && style.select} `}>
+                            <div><Text ellipsis={{ tooltip: true }}>{item?.link?.title}{item?.link?.desc ? `(${item?.link?.desc})` : ''}</Text></div>
+                            {bookLink?.length > 0 && <CheckOutlined style={{ color: '#1890ff' }} />}
+                        </div>
+                    })}
+                </div>
+            </div>
+            <div className={style.right}>
+                <Space style={{ marginBottom: 10 }} align="end" size={5} wrap>
+                    <Select
+                        value={queryParams?.platform}
+                        onChange={(e) => setQueryParams({ ...queryParams, platform: e })}
+                        showSearch
+                        style={{ width: 110 }}
+                        disabled={!!platform}
+                        placeholder="书城"
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                        }
+                        allowClear
+                        options={bookPlatForm.map(item => ({ value: item.platformKey, label: item.platformName }))}
+                    />
+                    <Select
+                        value={queryParams?.mpAccountId}
+                        onChange={(e) => setQueryParams({ ...queryParams, mpAccountId: e })}
+                        showSearch
+                        style={{ width: 110 }}
+                        placeholder="公众号"
+                        disabled={!!mpAccountId}
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                        }
+                        allowClear
+                        options={mpList}
+                    />
+                    <Select
+                        value={queryParams?.linkType}
+                        onChange={(e) => setQueryParams({ ...queryParams, linkType: e })}
+                        showSearch
+                        style={{ width: 110 }}
+                        placeholder="链接类型"
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                        }
+                        allowClear
+                        options={Object.keys(LINKTYPE).map(key => ({ label: LINKTYPE[key], value: key }))}
+                    />
+                    <DatePicker.RangePicker
+                        placeholder={['创建时间开始', '创建时间结束']}
+                        allowClear
+                        value={queryParams?.createTimeStart ? [dayJs(queryParams?.createTimeStart), dayJs(queryParams?.createTimeEnd)] : undefined}
+                        onChange={(_, options) => setQueryParams({ ...queryParams, createTimeStart: options?.[0], createTimeEnd: options?.[1] })}
+                    />
+                    <Input placeholder='请输入渠道名称' style={{ width: 150 }} allowClear value={queryParams?.channelName} onChange={(e) => setQueryParams({ ...queryParams, channelName: e.target.value })} />
+                    <Select
+                        style={{ width: 110 }}
+                        showSearch
+                        placeholder="推广类型"
+                        value={queryParams?.channelType}
+                        onChange={(value) => setQueryParams({ ...queryParams, channelType: value })}
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                        }
+                        allowClear
+                        options={[{ label: '外部', value: '1' }, { label: '内部', value: '2' }]}
+                    />
+                    <Button type="primary" onClick={() => {
+                        setQueryParamsNew({ ...queryParams, pageNum: 1 })
+                    }} loading={getCorpAutoLinkList.loading} icon={<SearchOutlined />}>搜索</Button>
+                </Space>
+
+                <Table
+                    tableLayout='fixed'
+                    dataSource={getCorpAutoLinkList?.data?.data?.records}
+                    columns={bookLinkTableConfig(true)}
+                    loading={getCorpAutoLinkList.loading}
+                    scroll={{ y: 400 }}
+                    rowKey={'id'}
+                    size='small'
+                    bordered
+                    rowSelection={{
+                        selectedRowKeys: data[selectAdz - 1]?.bookLink?.map((item: any) => item?.id),
+                        type: 'radio',
+                        getCheckboxProps: (record: any) => ({
+                            name: record.name,
+                        }),
+                        onChange(_, selectedRows) {
+                            const newData = JSON.parse(JSON.stringify(data))
+                            newData[selectAdz - 1].bookLink = selectedRows
+                            setData(newData)
+                        },
+                    }}
+                    pagination={{
+                        total: getCorpAutoLinkList.data?.data?.total,
+                        showTotal: (total) => <Tag color="cyan">总共{total}数据</Tag>,
+                        showSizeChanger: true,
+                        showLessItems: true,
+                        defaultCurrent: 1,
+                        defaultPageSize: 20,//默认初始的每页条数
+                        current: getCorpAutoLinkList?.data?.data?.current || 1,
+                        pageSize: getCorpAutoLinkList?.data?.data?.size || 20,
+                        onChange: (page, pageSize) => {
+                            setQueryParams({ ...queryParams, pageNum: page, pageSize })
+                            setQueryParamsNew({ ...queryParamsNew, pageNum: page, pageSize })
+                        }
+                    }}
+                    onRow={(record) => ({
+                        onClick: () => {
+                            console.log(record)
+                            const newData = JSON.parse(JSON.stringify(data))
+                            newData[selectAdz - 1]['bookLink'] = [record];
+                            setData(newData)
+                        },
+                    })}
+                />
+            </div>
+            <div className={style.center}>
+                <Title level={5}>已选:{data[selectAdz - 1]?.bookLink?.length || 0}</Title>
+                <div className={style.select_content}>
+                    {data[selectAdz - 1]?.bookLink?.map(item => <div key={item.id}>
+                        <Text ellipsis={{ tooltip: true }} className={style.marketingAssetName}>{item.channelName}</Text>
+                        <CloseOutlined className={style.close} onClick={() => {
+                            let newData = JSON.parse(JSON.stringify(data))
+                            newData[selectAdz - 1].bookLink = newData[selectAdz - 1]?.bookLink?.filter((i: any) => i?.id !== item.id)
+                            setData(newData)
+                        }} />
+                    </div>)}
+                </div>
+            </div>
+        </div>
+    </Modal>
+}
+
+
+export default SelectBookLink;

+ 146 - 0
src/pages/weComTask/page/bookLink/tableConfig.tsx

@@ -0,0 +1,146 @@
+import { copy } from "@/utils/utils"
+import { ColumnsType } from "antd/es/table"
+import { FANQIELINKTYPEEle, HUASHENGLINKTYPEEle, LINKTYPEEle, WENDINGLINKTYPEELE } from "./const"
+import { Button, Flex, Popconfirm, Popover, Tag } from "antd"
+import Details from "./details"
+
+
+export function bookLinkTableConfig(isSelect: boolean, handleCopy?: (value: any) => void, handleDel?: (data: { linkIds: number[] }) => void): ColumnsType<any> {
+
+    const arr: ColumnsType<any> = [
+        {
+            title: '书城',
+            dataIndex: 'platformName',
+            key: 'platformName',
+            align: 'center',
+            width: 70,
+            ellipsis: true,
+            fixed: 'left',
+            render: (value, records) => {
+                return <Tag color={records.platform === 'YUE_WEN' ? '#f50' : records.platform === 'FAN_QIE' ? '#2db7f5' : records.platform === 'HUA_SHENG' ? '#87d068' : '#108ee9'}><span style={{ fontSize: 12 }}>{value}</span></Tag>
+            }
+        },
+        {
+            title: '公众号',
+            dataIndex: 'mpAccountName',
+            key: 'mpAccountName',
+            align: 'center',
+            width: 80,
+            fixed: 'left',
+            render: (value) => {
+                return <span style={{ fontSize: 12 }}>{value}</span>
+            }
+        },
+        {
+            title: '渠道名称',
+            dataIndex: 'channelName',
+            key: 'channelName',
+            width: 300,
+            ellipsis: true,
+            render: (value) => {
+                return <span style={{ fontSize: 12 }}>{value || '--'}</span>
+            }
+        },
+        {
+            title: '推广类型',
+            dataIndex: 'channelType',
+            key: 'channelType',
+            align: 'center',
+            width: 80,
+            render: (value, records) => {
+                return records.platform === 'WEN_DING' ? <span style={{ fontSize: 12 }}>{value === 0 ? '内推' : value === 1 ? '派单' : '--'}</span> : <span style={{ fontSize: 12 }}>{value === 1 ? '外部' : value === 2 ? '内部' : '--'}</span>
+            }
+        },
+        {
+            title: '链接类型',
+            dataIndex: 'linkType',
+            key: 'linkType',
+            align: 'center',
+            width: 120,
+            render: (value, records) => {
+                return records.platform === 'YUE_WEN' ? LINKTYPEEle[value] :
+                    records.platform === 'FAN_QIE' ? FANQIELINKTYPEEle[value] :
+                        records.platform === 'HUA_SHENG' ? HUASHENGLINKTYPEEle[value] :
+                            records.platform === 'WEN_DING' ? WENDINGLINKTYPEELE[value] :
+                                '--'
+            }
+        },
+        {
+            title: '生成链接',
+            dataIndex: 'link',
+            key: 'link',
+            width: 200,
+            ellipsis: true,
+            render: (value) => {
+                return value ? <a style={{ fontSize: 12 }} onClick={() => copy(value)}>{value}</a> : '--'
+            }
+        },
+        {
+            title: '小程序Appid',
+            dataIndex: 'wxAppid',
+            key: 'wxAppid',
+            width: 80,
+            ellipsis: true,
+            render: (value) => {
+                return value ? <a style={{ fontSize: 12 }} onClick={() => copy(value)}>{value}</a> : '--'
+            }
+        },
+        {
+            title: '小程序链接',
+            dataIndex: 'miniappPagePath',
+            key: 'miniappPagePath',
+            width: 200,
+            ellipsis: true,
+            render: (value) => {
+                return value ? <a style={{ fontSize: 12 }} onClick={() => copy(value)}>{value}</a> : '--'
+            }
+        },
+        {
+            title: '创建人',
+            dataIndex: 'createByInfo',
+            key: 'createByInfo',
+            align: 'center',
+            width: 70,
+            render: (value) => {
+                return <span style={{ fontSize: 12 }}>{value?.nickname}</span>
+            }
+        },
+        {
+            title: '创建时间',
+            dataIndex: 'createTime',
+            key: 'createTime',
+            align: 'center',
+            width: 140,
+            render: (value) => {
+                return <span style={{ fontSize: 12 }}>{value}</span>
+            }
+        },
+        {
+            title: '操作',
+            dataIndex: 'cz',
+            key: 'cz',
+            align: 'center',
+            width: 100,
+            fixed: 'right',
+            render: (_, records) => {
+                return <Flex gap={4} justify='center'>
+                    <Popover
+                        placement="left"
+                        content={<Details linkType={records.linkType} platform={records.platform} linkContent={records.linkContext} />}
+                        styles={{ body: { width: 300, overflow: 'hidden', overflowY: 'auto', maxHeight: 400 } }}
+                    >
+                        <Button type="link" style={{ padding: 0, height: 'auto' }}>详情</Button>
+                    </Popover>
+                    {!isSelect && <Button type="link" style={{ padding: 0, height: 'auto' }} onClick={() => handleCopy(records)}>复制</Button>}
+                    <Popconfirm
+                        title="确定删除?"
+                        onConfirm={() => { handleDel?.({ linkIds: [records.id] }) }}
+                    >
+                        <a style={{ color: 'red' }}>删除</a>
+                    </Popconfirm>
+                </Flex>
+            }
+        },
+    ]
+    return arr
+}

+ 12 - 0
src/pages/weComTask/page/bookLink/typings.d.ts

@@ -0,0 +1,12 @@
+declare namespace BOOKLINK {
+    interface DispatchBookLink {
+        form: FormInstance<any>
+    }
+    interface BookLinkChildProps {
+        restField: {
+            fieldKey?: number;
+        }
+        name: number,
+        itemData?: any
+    }
+}

+ 3 - 1
src/pages/weComTask/page/businessPlan/create/components/friends/previewFriendsStrategy.tsx

@@ -24,11 +24,13 @@ const PreviewFriendsStrategy: React.FC<{ friendsStrategy: { [x: string]: any },
                 ...friendsStrategy, strategySettings: friendsStrategy?.strategySettings?.map(item => {
                     const { sendTime, startTime, endTime, sendDay, timeRepeatType } = item
                     if (timeRepeatType === 'TIME_TYPE_SINGLE_PLACE') {
-                        return {
+                        const data = {
                             ...item,
                             timeRepeatType,
                             sendDay: sendDay ? dayjs(sendDay + ' ' + sendTime) : undefined
                         }
+                        delete data?.sendTime
+                        return data
                     }
                     return {
                         ...item,

+ 4 - 2
src/pages/weComTask/page/businessPlan/create/components/friends/strategy.tsx

@@ -43,11 +43,13 @@ const Strategy = forwardRef(({ value, onChange }: TASK_CREATE.StrategyProps, ref
                 ...value, strategySettings: value?.strategySettings?.map(item => {
                     const { sendTime, startTime, endTime, sendDay, timeRepeatType } = item
                     if (timeRepeatType === 'TIME_TYPE_SINGLE_PLACE') {
-                        return {
+                        const data = {
                             ...item,
                             timeRepeatType,
                             sendDay: sendDay ? dayjs(sendDay + ' ' + sendTime) : undefined
                         }
+                        delete data?.sendTime
+                        return data
                     }
                     return {
                         ...item,
@@ -164,7 +166,7 @@ const Strategy = forwardRef(({ value, onChange }: TASK_CREATE.StrategyProps, ref
                 }}
                 initialValues={{
                     taskType: 'novel',
-                    strategySettings: [{ }],
+                    strategySettings: [{}],
                 }}
                 onFieldsChange={() => {
                     filedUpdateChange(form.getFieldsValue())

+ 6 - 4
src/pages/weComTask/page/businessPlan/create/components/highMassSending/strategy.tsx

@@ -43,11 +43,13 @@ const Strategy = forwardRef(({ value, onChange }: TASK_CREATE.StrategyProps, ref
                 ...value, strategySettings: value?.strategySettings?.map(item => {
                     const { sendTime, startTime, endTime, sendDay, timeRepeatType } = item
                     if (timeRepeatType === 'TIME_TYPE_SINGLE_PLACE') {
-                        return {
+                        const data = {
                             ...item,
                             timeRepeatType,
                             sendDay: sendDay ? dayjs(sendDay + ' ' + sendTime) : undefined
                         }
+                        delete data?.sendTime
+                        return data
                     }
                     return {
                         ...item,
@@ -174,7 +176,7 @@ const Strategy = forwardRef(({ value, onChange }: TASK_CREATE.StrategyProps, ref
                 }}
                 initialValues={{
                     taskType: 'novel',
-                    strategySettings: [{ sendData: [{ externalUserType: 'all' }] }],
+                    strategySettings: [{ sendData: [{ externalUserType: 'specify' }] }],
                 }}
                 onFieldsChange={() => {
                     filedUpdateChange(form.getFieldsValue())
@@ -252,7 +254,7 @@ const Strategy = forwardRef(({ value, onChange }: TASK_CREATE.StrategyProps, ref
                                                         </Card>
                                                     })}
                                                     <Form.Item>
-                                                        <Button type="dashed" onClick={() => add({ externalUserType: 'all' })} block icon={<PlusOutlined />}>
+                                                        <Button type="dashed" onClick={() => add({ externalUserType: 'specify' })} block icon={<PlusOutlined />}>
                                                             新增发送对象
                                                         </Button>
                                                     </Form.Item>
@@ -263,7 +265,7 @@ const Strategy = forwardRef(({ value, onChange }: TASK_CREATE.StrategyProps, ref
                                 </Card>
                             })}
                             <Form.Item>
-                                <Button type="primary" onClick={() => add({ sendData: [{ externalUserType: 'all' }] })} block icon={<PlusOutlined />}>
+                                <Button type="primary" onClick={() => add({ sendData: [{ externalUserType: 'specify' }] })} block icon={<PlusOutlined />}>
                                     新增策略组
                                 </Button>
                             </Form.Item>

+ 39 - 8
src/pages/weComTask/page/businessPlan/create/components/massSending/content.tsx

@@ -1,17 +1,19 @@
 import NewSteps from '@/pages/weComTask/components/newSteps';
-import { App, Button, Card, Form, Select } from 'antd';
+import { App, Button, Card, Form, Popover, Select } from 'antd';
 import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
 import '../../global.less';
 import { welcomeContentData } from '../../const';
-import { PlusOutlined, MinusOutlined } from '@ant-design/icons'
+import { PlusOutlined, MinusOutlined, QuestionCircleFilled } from '@ant-design/icons'
 import MaterialNoTextMould from './materialNoTextMould';
+import PreviewTime from '@/pages/weComTask/components/previewTime';
+import FilterUserTooltip from '@/pages/weComTask/components/filterUser/filterUserTooltip';
 
 /**
  * 群发内容
  * @param param0 
  * @returns 
  */
-const Content = forwardRef(({ massSendingStrategy, value, onChange }: TASK_CREATE.ContentProps, ref: React.ForwardedRef<{ handleOk: (type: string) => void }>) => {
+const Content = forwardRef(({ massSendingStrategy, value, onChange, bookCityList }: TASK_CREATE.ContentProps, ref: React.ForwardedRef<{ handleOk: (type: string) => void }>) => {
 
     /****************************************/
     const { message } = App.useApp()
@@ -33,7 +35,6 @@ const Content = forwardRef(({ massSendingStrategy, value, onChange }: TASK_CREAT
             message.error('请先设置群发策略')
             return
         }
-
         if (value && Object.keys(value).length) {
             filedUpdateChange(value)
             form.setFieldsValue(value)
@@ -41,7 +42,8 @@ const Content = forwardRef(({ massSendingStrategy, value, onChange }: TASK_CREAT
             const data = {
                 massSendingContentDTO: massSendingStrategy.strategySettings.map(item => {
                     return {
-                        sendContentDto: item.sendData.map(() => ({ contentDTO: [undefined], sendMode: undefined }))
+                        sendContentDto: item.sendData.map((i) => ({ contentDTO: [undefined], sendMode: undefined, id: i?.id })),
+                        id: item?.id
                     }
                 })
             }
@@ -125,7 +127,6 @@ const Content = forwardRef(({ massSendingStrategy, value, onChange }: TASK_CREAT
                 onFinish={handleOk}
                 initialValues={{ massSendingContentDTO: [undefined] }}
                 onFieldsChange={() => {
-                    console.log(form.getFieldsValue())
                     filedUpdateChange(form.getFieldsValue())
                 }}
                 preserve={true}
@@ -134,15 +135,45 @@ const Content = forwardRef(({ massSendingStrategy, value, onChange }: TASK_CREAT
                     {(fields) => (
                         <>
                             {fields.map(({ key, name, ...restField }, index) => {
-                                return <Card key={index} title={<strong>策略名称:{massSendingStrategy?.strategySettings?.[index]?.strategyName || `<空>`}</strong>} style={{ background: '#fff', marginBottom: 10 }} id={'clientId' + '_' + index}>
+                                const { strategyName, sendData, ...strategyItem } = massSendingStrategy?.strategySettings?.[index]
+                                return <Card
+                                    key={index}
+                                    title={<strong>策略名称:{strategyName || `<空>`}</strong>}
+                                    style={{ background: '#fff', marginBottom: 10 }}
+                                    extra={<Popover
+                                        placement="right"
+                                        content={<div>
+                                            <PreviewTime
+                                                {...strategyItem}
+                                            />
+                                        </div>}
+                                        styles={{ body: { width: 300, overflow: 'hidden', overflowY: 'auto', maxHeight: 400 } }}
+                                    >
+                                        <a style={{ color: '#000' }}><QuestionCircleFilled /></a>
+                                    </Popover>}
+                                    id={'clientId' + '_' + index}
+                                >
                                     <Form.List {...restField} name={[name, 'sendContentDto']}>
                                         {(fields) => (
                                             <>
                                                 {fields.map(({ key, name, ...restField }, i) => {
+                                                    const sendDataItem = sendData?.[i];
                                                     return <Card
                                                         key={i}
-                                                        title={<strong>发送对象{i + 1} 内容配置</strong>}
+                                                        title={<strong>发送对象{i + 1}: {sendDataItem?.externalUserFilterName} 内容配置</strong>}
                                                         style={{ background: '#fff', marginBottom: 10 }}
+                                                        extra={sendDataItem?.externalUserType === "specify" ? <Popover
+                                                            placement="right"
+                                                            styles={{ body: { maxWidth: 350, maxHeight: 350, overflow: 'hidden', overflowY: 'auto' } }}
+                                                            mouseEnterDelay={0.5}
+                                                            content={<FilterUserTooltip
+                                                                bookCityList={bookCityList}
+                                                                configName={sendDataItem?.externalUserFilter?.configName}
+                                                                data={sendDataItem?.externalUserFilter?.configContent}
+                                                            />}
+                                                        >
+                                                            <a style={{ color: '#000' }}>指定 <QuestionCircleFilled /></a>
+                                                        </Popover> : '全部'}
                                                         id={'clientId' + '_' + index + '_' + i}
                                                     >
                                                         <div id={'clientId' + '_' + index + '_' + i + 'sendMode'}>

+ 15 - 18
src/pages/weComTask/page/businessPlan/create/components/massSending/index.tsx

@@ -280,18 +280,6 @@ const MassSending: React.FC = () => {
             saveAs(blob, `群发内容配置_${dayjs().format('YYYYMMDDHHmmss')}.xlsx`);
             setTimeout(() => setDownloadLoading(false), 1000)
         });
-        // 替换原有 saveAs 代码
-        // const fileStream = streamSaver.createWriteStream(
-        //     `群发内容配置_${dayjs().format('YYYYMMDDHHmmss')}.xlsx`
-        // );
-        // const writer = fileStream.getWriter();
-
-        // // 流式写入
-        // workbook.xlsx.write(writer).then(() => {
-        //     writer.close();
-        //     console.log('33333333333333333333333')
-        //     setDownloadLoading(false); // 立即关闭loading
-        // });
     }
 
     // 读取Excel
@@ -366,8 +354,8 @@ const MassSending: React.FC = () => {
     }
 
     return <>
-        <div className={`${style.settingsBody_content_row}`}>
-            <div className={`${style.settingsBody_content_col}`}>
+        <div className={`${style.settingsBody_content_row}`} style={{ width: 'calc(100% / 2)' }}>
+            <div className={`${style.settingsBody_content_col}`} style={{ width: '50%' }}>
                 <div className={style.title}>
                     <span>群发</span>
                 </div>
@@ -380,14 +368,22 @@ const MassSending: React.FC = () => {
                     </div>
                 </div>
                 <div className={style.detail_footer}>
-                    <Button type="link" style={{ padding: 0, fontSize: 12, color: token.colorPrimary }} size='small' onClick={() => { setNewVisible(true); setCreateType('STRATEGY') }}>编辑</Button>
+                    <Button
+                        type="link"
+                        style={{ padding: 0, fontSize: 12, color: token.colorPrimary }}
+                        size='small'
+                        onClick={() => {
+                            setNewVisible(true);
+                            setCreateType('STRATEGY')
+                        }}
+                    >编辑</Button>
                 </div>
             </div>
-            <div className={`${style.settingsBody_content_col}`}>
+            <div className={`${style.settingsBody_content_col}`} style={{ width: '50%' }}>
                 <div className={style.title}>
                     <span></span>
                     {(settings?.massSendingStrategy && Object.keys(settings?.massSendingStrategy).length > 0) && <Space>
-                        {settings?.massSendingContent?.massSendingContentDTO?.some(item => item?.sendContentDto?.some(si => si?.contentDTO?.some(i => i?.attachmentList?.some(a => ['TASK_CONTENT_LINK', 'TASK_STATUS_MINIPROGRAM'].includes(a?.msgType))))) && <>
+                        {/* {settings?.massSendingContent?.massSendingContentDTO?.some(item => item?.sendContentDto?.some(si => si?.contentDTO?.some(i => i?.attachmentList?.some(a => ['TASK_CONTENT_LINK', 'TASK_STATUS_MINIPROGRAM'].includes(a?.msgType))))) && <>
                             <Button
                                 type="link"
                                 style={{ padding: 0, fontSize: 12 }}
@@ -409,7 +405,7 @@ const MassSending: React.FC = () => {
                                     style={{ padding: 0, fontSize: 12 }}
                                 >{settings?.corpUsers?.every(item => item?.groupMsgContent?.length) ? 'Excel内容已上传,重新上传' : '上传Excel'}</Button>
                             </Upload>
-                        </>}
+                        </>} */}
                         <Button
                             type="link"
                             danger
@@ -462,6 +458,7 @@ const MassSending: React.FC = () => {
         {newVisible && <SettingsMassSending
             visible={newVisible}
             createType={createType}
+            bookCityList={bookPlatForm?.map(item => ({ label: item.platformName, value: item.platformKey, id: item.id }))}
             value={{
                 massSendingContent: settings?.massSendingContent,
                 massSendingStrategy: settings?.massSendingStrategy

+ 12 - 2
src/pages/weComTask/page/businessPlan/create/components/massSending/previewMassSendingStrategy.tsx

@@ -24,11 +24,13 @@ const PreviewMassSendingStrategy: React.FC<{ massSendingStrategy: { [x: string]:
                 ...massSendingStrategy, strategySettings: massSendingStrategy?.strategySettings?.map(item => {
                     const { sendTime, startTime, endTime, sendDay, timeRepeatType } = item
                     if (timeRepeatType === 'TIME_TYPE_SINGLE_PLACE') {
-                        return {
+                        const data = {
                             ...item,
                             timeRepeatType,
                             sendDay: sendDay ? dayjs(sendDay + ' ' + sendTime) : undefined
                         }
+                        delete data?.sendTime
+                        return data
                     }
                     return {
                         ...item,
@@ -95,7 +97,15 @@ const PreviewMassSendingStrategy: React.FC<{ massSendingStrategy: { [x: string]:
                                                         title={<strong>策略{index + 1} 发送对象{i + 1}</strong>}
                                                         style={{ background: '#fff', marginBottom: 10 }}
                                                     >
-                                                        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
+                                                        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', width: '100%' }}>
+                                                            <Form.Item
+                                                                {...restField}
+                                                                label={<strong>名称</strong>}
+                                                                name={[name, 'externalUserFilterName']}
+                                                                rules={[{ required: true, message: '请输入发送对象名称!' }]}
+                                                            >
+                                                                <Input placeholder="请输入发送对象名称" allowClear />
+                                                            </Form.Item>
                                                             <Form.Item
                                                                 {...restField}
                                                                 name={[name, 'externalUserType']}

+ 136 - 0
src/pages/weComTask/page/businessPlan/create/components/massSending/settingsContent.tsx

@@ -0,0 +1,136 @@
+import { Button, Input, Modal, Typography } from 'antd';
+import React, { useEffect, useState } from 'react';
+import style from '../../../../../components/selectExternalAccount/index.less'
+import { CheckOutlined } from '@ant-design/icons';
+import SelectBookLinkButton from '@/pages/weComTask/page/bookLink/SelectBookLinkButton';
+const { Paragraph, Title } = Typography;
+
+
+interface Props {
+    corpUserGroups: TASK_CREATE.CorpUserGroupProps[]
+    content: Array<{ msgType: "TASK_CONTENT_LINK", link: { title: string, picUrl: string, desc: string } } | { msgType: "TASK_STATUS_MINIPROGRAM", miniprogram: { title: string, picUrl: string } }>
+    bookPlatForm: TASK_CREATE.BookPlatFormProps[]
+    value?: any[];
+    onChange?: (value?: any) => void;
+}
+const SettingsContent: React.FC<Props> = ({ value, onChange, content, bookPlatForm, corpUserGroups }) => {
+
+    /*******************************************/
+    const [visible, setVisible] = useState<boolean>(false)
+    const [data, setData] = useState<any>([])
+    /*******************************************/
+
+    useEffect(() => {
+        if (visible) {
+            setData(value || corpUserGroups.map(() => {
+                return {
+                    content
+                }
+            }))
+        }
+    }, [visible, corpUserGroups, value])
+
+
+
+    return <>
+        <Button onClick={() => setVisible(true)}>设置链接、小程序</Button>
+        {visible && <SettingsContentModal
+            corpUserGroups={corpUserGroups}
+            visible={visible}
+            value={data}
+            bookPlatForm={bookPlatForm}
+            onChange={(value) => {
+                setData(value)
+                onChange(value)
+            }}
+            onClose={() => {
+                setVisible(false)
+            }}
+        />}
+    </>
+};
+
+const SettingsContentModal: React.FC<{
+    value: any[],
+    onChange: (value: any) => void
+    onClose: () => void
+    corpUserGroups: TASK_CREATE.CorpUserGroupProps[]
+    bookPlatForm: TASK_CREATE.BookPlatFormProps[]
+    visible?: boolean
+}> = ({ visible, onClose, onChange, value, corpUserGroups, bookPlatForm }) => {
+
+    /***************************************/
+    const [data, setData] = useState<any>([])
+    const [selectAdz, setSelectAdz] = useState<number>(1)
+    /***************************************/
+
+    useEffect(() => {
+        if (visible) {
+            setData(value)
+        }
+    }, [visible, value])
+
+    const handleSelectAdz = (value: number) => {
+        if (value === selectAdz) {
+            return
+        }
+        setSelectAdz(value)
+    }
+
+    return <Modal
+        title={<strong>群发内容链接、小程序配置</strong>}
+        open={visible}
+        onCancel={onClose}
+        width={800}
+        className={`${style.SelectPackage}`}
+        styles={{
+            body: {
+                padding: '0 10px 0 10px'
+            }
+        }}
+    >
+        <div className={style.content}>
+            <div className={style.left}>
+                <h4 className={style.title}>客服组</h4>
+                <div className={style.accountIdList}>
+                    {data?.map((item, index) => {
+                        // const bookLink = item?.bookLink
+                        return <div style={{ height: 'auto', lineHeight: 'normal', }} key={index} onClick={() => { handleSelectAdz(index + 1) }} className={`${style.accItem} ${selectAdz === index + 1 && style.select} `}>
+                            <div style={{ flexDirection: 'column', alignItems: 'flex-start', padding: '2px 0', flex: 1, overflow: 'hidden' }}>
+                                <Title ellipsis={{ tooltip: true }} level={5} style={{ margin: 0 }}>客服组{index + 1}</Title>
+                                <Paragraph ellipsis={{ tooltip: true }} style={{ margin: 0, width: '100%' }}>{corpUserGroups[index]?.corpUsers?.map(i => i.name).join('、')}</Paragraph>
+                            </div>
+                            {/* {bookLink?.length > 0 && <CheckOutlined style={{ color: '#1890ff' }} />} */}
+                            <CheckOutlined style={{ color: '#1890ff' }} />
+                        </div>
+                    })}
+                </div>
+            </div>
+            <div className={style.right}>
+                {data?.[selectAdz - 1]?.content?.map((item, index) => {
+                    return <div key={index}>
+                        {item.msgType === 'TASK_CONTENT_LINK' ? <div style={{ marginBottom: 10 }}>
+                            <Title level={5} style={{ margin: 0 }}>链接:{item?.link?.title || ''}</Title>
+                            <div style={{ display: 'flex', gap: 4, alignItems: 'flex-end' }}>
+                                <Input.TextArea placeholder="请输入链接" />
+                                {/* <SelectBookLinkButton
+                                    bookPlatForm={bookPlatForm}
+                                    mpAccountId={corpUserGroups[index]?.corpUsers?.[0]?.mpAccountId}
+                                    linkData={[item]}
+                                    onChange={(linkStr, miniprogramAppid, miniprogramPage) => {
+                                        
+                                    }}
+                                /> */}
+                            </div>
+                        </div> : <>
+                            <Title level={5} style={{ margin: 0 }}>所有小程序</Title>
+                            <Input.TextArea placeholder="请输入链接" />
+                        </>}
+                    </div>
+                })}
+            </div>
+        </div>
+    </Modal>
+}
+
+export default SettingsContent;

+ 24 - 3
src/pages/weComTask/page/businessPlan/create/components/massSending/settingsMassSending.tsx

@@ -9,7 +9,7 @@ import Content from './content';
  * @param param0 
  * @returns 
  */
-const SettingsMassSending: React.FC<TASK_CREATE.MassSendingProps<any>> = ({ value: { massSendingContent, massSendingStrategy }, createType, onChange, visible, onClose }) => {
+const SettingsMassSending: React.FC<TASK_CREATE.MassSendingProps<any>> = ({ bookCityList, value: { massSendingContent, massSendingStrategy }, createType, onChange, visible, onClose }) => {
 
     /****************************************/
     const { message } = App.useApp()
@@ -50,9 +50,29 @@ const SettingsMassSending: React.FC<TASK_CREATE.MassSendingProps<any>> = ({ valu
                     setType('CONTENT')
                 }
                 if (massSendingContent && (value?.strategySettings?.length !== massSendingContent?.massSendingContentDTO?.length || value?.strategySettings?.some((item, index) => massSendingContent?.massSendingContentDTO?.[index]?.sendContentDto?.length !== item?.sendData?.length))) {
-                    message.error('策略配置和内容配置不一致,内容已重置,请重新配置!')
+                    message.warning('策略配置和内容配置不一致,内容已更新,请查看!')
                     onChange({
-                        massSendingContent: undefined,
+                        massSendingContent: {
+                            massSendingContentDTO: value?.strategySettings?.map(item => {
+                                const mc = massSendingContent.massSendingContentDTO.find(mc => mc.id === item.id)
+                                if (mc) {
+                                    return {
+                                        sendContentDto: item.sendData.map((i) => {
+                                            const sendContent = mc.sendContentDto.find(s => s.id === i.id)
+                                            if (sendContent) {
+                                                return sendContent
+                                            }
+                                            return { contentDTO: [undefined], sendMode: undefined, id: i?.id }
+                                        }),
+                                        id: item?.id
+                                    }
+                                }
+                                return {
+                                    sendContentDto: item.sendData.map((i) => ({ contentDTO: [undefined], sendMode: undefined, id: i?.id })),
+                                    id: item?.id
+                                }
+                            })
+                        },
                         massSendingStrategy: value
                     }, type)
                     return
@@ -64,6 +84,7 @@ const SettingsMassSending: React.FC<TASK_CREATE.MassSendingProps<any>> = ({ valu
             }}
         /> : <Content
             ref={ref1}
+            bookCityList={bookCityList}
             massSendingStrategy={massSendingStrategy}
             value={massSendingContent}
             onChange={(value, type) => {

+ 16 - 12
src/pages/weComTask/page/businessPlan/create/components/massSending/showContent.tsx

@@ -75,8 +75,6 @@ const ShowContent: React.FC<ShowContentProps> = ({ strategySettings, massSending
     const { token } = useNewToken()
     /*************************************/
 
-
-
     return <div className={style.showContent}>
         <Tabs
             tabPosition='left'
@@ -85,7 +83,7 @@ const ShowContent: React.FC<ShowContentProps> = ({ strategySettings, massSending
             size='small'
             popupClassName={style.popupClassName}
             items={strategySettings?.map((item, index) => ({
-                label: '策略' + (index + 1),
+                label: <span style={massSendingContent?.massSendingContentDTO?.[index]?.sendContentDto?.every(sc => sc?.sendMode || sc?.sendMode === 0) ? {} : { color: 'red' }}>{'策略' + (index + 1)}</span>,
                 key: `${index}`,
                 children: <div className={style.showContent_content}>
                     <div style={{ backgroundColor: token.colorPrimary, padding: 4, color: '#FFF', fontSize: 12, marginBottom: 5 }}>
@@ -93,17 +91,23 @@ const ShowContent: React.FC<ShowContentProps> = ({ strategySettings, massSending
                     </div>
                     {massSendingContent?.massSendingContentDTO?.[index]?.sendContentDto?.map((si: any, sn: number) => {
                         return <div key={sn}>
-                            <div style={{ backgroundColor: token.colorPrimary, padding: 4, color: '#FFF', fontSize: 12, marginTop: sn > 0 ? 16 : 0 }}>
-                                <div>
-                                    发送对象组{sn + 1} - 发送模式:{welcomeContentData?.find(i => i.value === si?.sendMode)?.label}
+                            {(si?.sendMode || si?.sendMode == 0) ? <>
+                                <div style={{ backgroundColor: token.colorPrimary, padding: 4, color: '#FFF', fontSize: 12, marginTop: sn > 0 ? 16 : 0 }}>
+                                    <div>
+                                        发送对象组{sn + 1} - 发送模式:{welcomeContentData?.find(i => i.value === si?.sendMode)?.label}
+                                    </div>
                                 </div>
-                            </div>
-                            {si?.contentDTO?.map((item, index) => {
-                                return <div key={index} style={{ marginTop: 5, backgroundColor: '#f1f1f1', borderRadius: 6, padding: '5px 10px 16px' }}>
-                                    <h3 style={{ textAlign: 'center' }}>内容{index + 1}</h3>
-                                    <LookMsg data={type === 'highQf' ? item : getMsgData(item, token)} />
+                                {si?.contentDTO?.map((item, index) => {
+                                    return <div key={index} style={{ marginTop: 5, backgroundColor: '#f1f1f1', borderRadius: 6, padding: '5px 10px 16px' }}>
+                                        <h3 style={{ textAlign: 'center' }}>内容{index + 1}</h3>
+                                        <LookMsg data={type === 'highQf' ? item : getMsgData(item, token)} />
+                                    </div>
+                                })}
+                            </> : <div style={{ backgroundColor: token.colorError, padding: 4, color: '#FFF', fontSize: 12, marginTop: sn > 0 ? 16 : 0 }}>
+                                <div>
+                                    发送对象组{sn + 1} - 内容未配置
                                 </div>
-                            })}
+                            </div>}
                         </div>
                     })}
                 </div>

+ 14 - 4
src/pages/weComTask/page/businessPlan/create/components/massSending/strategy.tsx

@@ -43,11 +43,13 @@ const Strategy = forwardRef(({ value, onChange }: TASK_CREATE.StrategyProps, ref
                 ...value, strategySettings: value?.strategySettings?.map(item => {
                     const { sendTime, startTime, endTime, sendDay, timeRepeatType } = item
                     if (timeRepeatType === 'TIME_TYPE_SINGLE_PLACE') {
-                        return {
+                        const data = {
                             ...item,
                             timeRepeatType,
                             sendDay: sendDay ? dayjs(sendDay + ' ' + sendTime) : undefined
                         }
+                        delete data?.sendTime
+                        return data
                     }
                     return {
                         ...item,
@@ -174,7 +176,7 @@ const Strategy = forwardRef(({ value, onChange }: TASK_CREATE.StrategyProps, ref
                 }}
                 initialValues={{
                     taskType: 'novel',
-                    strategySettings: [{ sendData: [{ externalUserType: 'all' }] }],
+                    strategySettings: [{ sendData: [{ externalUserType: 'specify', id: Date.now() }], id: Date.now() }],
                 }}
                 onFieldsChange={() => {
                     filedUpdateChange(form.getFieldsValue())
@@ -225,6 +227,14 @@ const Strategy = forwardRef(({ value, onChange }: TASK_CREATE.StrategyProps, ref
                                                             extra={sendData?.length > 1 ? <Button icon={<DeleteOutlined />} type='link' style={{ color: 'red' }} onClick={() => remove(name)}></Button> : null}
                                                             id={`strategy_${index}_${i}_sendData`}
                                                         >
+                                                            <Form.Item
+                                                                {...restField}
+                                                                label={<strong>发送对象名称</strong>}
+                                                                name={[name, 'externalUserFilterName']}
+                                                                rules={[{ required: true, message: '请输入发送对象名称!' }]}
+                                                            >
+                                                                <Input placeholder="请输入发送对象名称" allowClear />
+                                                            </Form.Item>
                                                             <Form.Item
                                                                 label={<strong>发送对象配置</strong>}
                                                                 required
@@ -253,7 +263,7 @@ const Strategy = forwardRef(({ value, onChange }: TASK_CREATE.StrategyProps, ref
                                                         </Card>
                                                     })}
                                                     <Form.Item>
-                                                        <Button type="dashed" onClick={() => add({ externalUserType: 'all' })} block icon={<PlusOutlined />}>
+                                                        <Button type="dashed" onClick={() => add({ externalUserType: 'specify', id: Date.now() })} block icon={<PlusOutlined />}>
                                                             新增发送对象
                                                         </Button>
                                                     </Form.Item>
@@ -264,7 +274,7 @@ const Strategy = forwardRef(({ value, onChange }: TASK_CREATE.StrategyProps, ref
                                 </Card>
                             })}
                             <Form.Item>
-                                <Button type="primary" onClick={() => add({ sendData: [{ externalUserType: 'all' }] })} block icon={<PlusOutlined />}>
+                                <Button type="primary" onClick={() => add({ sendData: [{ externalUserType: 'specify', id: Date.now() }], id: Date.now() })} block icon={<PlusOutlined />}>
                                     新增策略组
                                 </Button>
                             </Form.Item>

+ 2 - 2
src/pages/weComTask/page/businessPlan/create/components/userInherit/index.tsx

@@ -263,7 +263,7 @@ const UserInherit: React.FC = () => {
                 <div className={style.title}>
                     <span>客户继承</span>
                     {(settings?.userInherit && Object.keys(settings?.userInherit)?.length) && <Space>
-                        <Button
+                        {/* <Button
                             type="link"
                             style={{ padding: 0, fontSize: 12 }}
                             loading={downloadLoading}
@@ -283,7 +283,7 @@ const UserInherit: React.FC = () => {
                                 type="link"
                                 style={{ padding: 0, fontSize: 12 }}
                             >{settings?.corpUsers?.every(item => item?.externalUserTransferContent?.length) ? 'Excel内容已上传,重新上传' : '上传Excel'}</Button>
-                        </Upload>
+                        </Upload> */}
                         <Button
                             type="link"
                             danger

+ 3 - 1
src/pages/weComTask/page/businessPlan/create/components/userInherit/previewUserlnherit.tsx

@@ -23,11 +23,13 @@ const PreviewUserInherit: React.FC<{ userInherit: any }> = ({ userInherit }) =>
                 ...userInherit, schedulingStrategyDTO: userInherit?.schedulingStrategyDTO?.map(item => {
                     const { sendTime, startTime, endTime, sendDay, timeRepeatType } = item
                     if (timeRepeatType === 'TIME_TYPE_SINGLE_PLACE') {
-                        return {
+                        const data = {
                             ...item,
                             timeRepeatType,
                             sendDay: sendDay ? dayjs(sendDay + ' ' + sendTime) : undefined
                         }
+                        delete data?.sendTime
+                        return data
                     }
                     return {
                         ...item,

+ 6 - 4
src/pages/weComTask/page/businessPlan/create/components/userInherit/settingsUserlnherit.tsx

@@ -45,11 +45,13 @@ const SettingsUserInherit: React.FC<TASK_CREATE.UserInheritProps<any>> = ({ valu
                 ...value, schedulingStrategyDTO: value?.schedulingStrategyDTO?.map(item => {
                     const { sendTime, startTime, endTime, sendDay, timeRepeatType } = item
                     if (timeRepeatType === 'TIME_TYPE_SINGLE_PLACE') {
-                        return {
+                        const data = {
                             ...item,
                             timeRepeatType,
                             sendDay: sendDay ? dayjs(sendDay + ' ' + sendTime) : undefined
                         }
+                        delete data?.sendTime
+                        return data
                     }
                     return {
                         ...item,
@@ -176,7 +178,7 @@ const SettingsUserInherit: React.FC<TASK_CREATE.UserInheritProps<any>> = ({ valu
                 }}
                 onFinish={handleOk}
                 initialValues={{
-                    schedulingStrategyDTO: [{ inheritDto: [{ transferType: 'all' }] }],
+                    schedulingStrategyDTO: [{ inheritDto: [{ transferType: 'specify' }] }],
                 }}
                 onFieldsChange={() => {
                     filedUpdateChange(form.getFieldsValue())
@@ -261,7 +263,7 @@ const SettingsUserInherit: React.FC<TASK_CREATE.UserInheritProps<any>> = ({ valu
                                                         </Card>
                                                     })}
                                                     <Form.Item>
-                                                        <Button type="dashed" onClick={() => add({ transferType: 'all' })} block icon={<PlusOutlined />}>
+                                                        <Button type="dashed" onClick={() => add({ transferType: 'specify' })} block icon={<PlusOutlined />}>
                                                             新增继承转移
                                                         </Button>
                                                     </Form.Item>
@@ -272,7 +274,7 @@ const SettingsUserInherit: React.FC<TASK_CREATE.UserInheritProps<any>> = ({ valu
                                 </Card>
                             })}
                             <Form.Item>
-                                <Button type="primary" onClick={() => add({ inheritDto: [{ transferType: 'all' }] })} block icon={<PlusOutlined />}>
+                                <Button type="primary" onClick={() => add({ inheritDto: [{ transferType: 'specify' }] })} block icon={<PlusOutlined />}>
                                     新增策略组
                                 </Button>
                             </Form.Item>

+ 4 - 26
src/pages/weComTask/page/businessPlan/create/components/welcome/index.tsx

@@ -23,7 +23,7 @@ const Welcome: React.FC = () => {
     const { token } = useNewToken()
     const { setSettings, settings, bookPlatForm, bookList, onPreviewReset } = useContext(DispatchTaskCreate)!;
     const { welcomeName, welcomeMsgTagDTO, sendMode, mediaContentList } = settings?.welcomeMsgTemplateDTO || {}
-    const [newVisible, setNewVisible] = React.useState(false);
+    const [newVisible, setNewVisible] = useState<boolean>(false);
     const [downloadLoading, setDownloadLoading] = useState<boolean>(false)
     /***************************************************/
 
@@ -284,33 +284,11 @@ const Welcome: React.FC = () => {
 
     return <>
         <div className={`${style.settingsBody_content_row}`}>
-            <div className={`${style.settingsBody_content_col}`}>
+            <div className={`${style.settingsBody_content_col}`} style={{ width: '100%' }}>
                 <div className={style.title}>
                     <span>欢迎语</span>
 
-                    {(mediaContentList?.some(item => item?.some(i => i?.mediaType === 'link' || i?.mediaType === 'miniprogram')) && settings?.corpUsers?.length) && <Space>
-                        <Button
-                            type="link"
-                            style={{ padding: 0, fontSize: 12 }}
-                            loading={downloadLoading}
-                            onClick={() => { exportExcel() }}
-                        >下载</Button>
-                        <Upload
-                            accept={'.xlsx'}
-                            action="#"
-                            showUploadList={false}
-                            customRequest={() => { }}
-                            beforeUpload={async (file: RcFile): Promise<any> => {
-                                readExcelContent(file)
-                                onPreviewReset()
-                            }}
-                        >
-                            <Button
-                                type="link"
-                                style={{ padding: 0, fontSize: 12 }}
-                            >{settings?.corpUsers?.every(item => item?.welcomeMsgContent && Object.keys(item.welcomeMsgContent).length) ? 'Excel内容已上传,重新上传' : '上传Excel'}</Button>
-                        </Upload>
-
+                    {(settings?.welcomeMsgTemplateDTO && Object.keys(settings?.welcomeMsgTemplateDTO).length > 0) && <Space>
                         <Button
                             type="link"
                             danger
@@ -346,7 +324,7 @@ const Welcome: React.FC = () => {
                 </div>
             </div>
         </div>
-        
+
         {/* 配置欢迎语 */}
         {newVisible && <SettingsWelcome
             visible={newVisible}

+ 1 - 2
src/pages/weComTask/page/businessPlan/create/components/welcome/settingsWelcome.tsx

@@ -43,7 +43,7 @@ const SettingsWelcome: React.FC<TASK_CREATE.WelcomeProps<any>> = ({ visible, onC
                 },
                 { title: '完成', checked: true }
             ])
-            setStep(1)
+            // setStep(1)
             form.setFieldsValue(value)
         }
     }, [value])
@@ -124,7 +124,6 @@ const SettingsWelcome: React.FC<TASK_CREATE.WelcomeProps<any>> = ({ visible, onC
                 }}
                 onFinish={handleOk}
                 initialValues={{
-                    welcomeType: 'novel',
                     mediaContentList: [undefined]
                 }}
                 onFieldsChange={() => {

+ 21 - 11
src/pages/weComTask/page/businessPlan/create/const.tsx

@@ -100,6 +100,8 @@ export const GENDER_TYPE: { [x: string]: string } = {
 }
 
 export const EUTTaskStatus = { 0: <Badge status="default" text="创建中" />, 1: <Badge status="processing" text='执行中' />, 2: <Badge status="success" text="执行完成" />, 3: <Badge status="warning" text="暂停" /> }
+// 拉群
+export const LQTaskStatus = { 1: <Badge status="processing" text='执行中' />, 2: <Badge status="success" text="执行完成" />, 3: <Badge status="warning" text="暂停" />, '-1': <Badge status="error" text="执行失败" /> }
 
 /**
  * 返回处理过的客户继承数据
@@ -112,7 +114,7 @@ export const getUserInDataData = (schedulingStrategyDTO: any[], taskName?: strin
     schedulingStrategyDTO?.forEach((item, index) => {
         // 1、策略 名称 、时间、周期
         const { inheritDto, ...strategyData } = JSON.parse(JSON.stringify(item))
-        inheritDto?.forEach((item1, index1) => {
+        inheritDto?.forEach((item1, index1, row) => {
             // 2、转移对象、文本
 
             userInData.push({
@@ -120,7 +122,8 @@ export const getUserInDataData = (schedulingStrategyDTO: any[], taskName?: strin
                 strategyData,
                 inheritData: item1,
                 strategyIndex: index + 1,
-                inheritIndex: index1 + 1
+                inheritIndex: index1 + 1,
+                strategyRowSpan: index1 === 0 ? row.length : 0
             })
         })
     })
@@ -291,12 +294,12 @@ export const restoreGroupData = (groupSendTaskAddDTO: { [x: string]: any }, conf
     const type = configType || 'USER_GROUP'
     const { groupSendName, strategyList } = groupSendTaskAddDTO
     const massSendingContentDTO: { [x: string]: any }[] = []
-    const strategySettings = strategyList.map(item => {
+    const nowTime = Date.now()
+    const strategySettings = strategyList.map((item, index) => {
         const { taskDetail, ...strategy } = item
-
         const sendContentDto = []
-        const sendData = taskDetail.map(item1 => {
-            const { sendMode, contentDTO, externalUserFilter } = item1
+        const sendData = taskDetail.map((item1, index1) => {
+            const { sendMode, externalUserFilterName, contentDTO, externalUserFilter } = item1
             sendContentDto.push({
                 sendMode,
                 contentDTO: type === 'USER_GROUP' ? contentDTO : contentDTO.map(item2 => {
@@ -348,7 +351,8 @@ export const restoreGroupData = (groupSendTaskAddDTO: { [x: string]: any }, conf
                         msgType,
                         content
                     }
-                })
+                }),
+                id: nowTime + (index * 1000) + (index1 * 100)
             })
             if (externalUserFilter) {
                 const { configName, ...configContent } = externalUserFilter
@@ -357,21 +361,27 @@ export const restoreGroupData = (groupSendTaskAddDTO: { [x: string]: any }, conf
                     externalUserFilter: {
                         configName,
                         configContent
-                    }
+                    },
+                    externalUserFilterName,
+                    id: nowTime + (index * 1000) + (index1 * 100)
                 }
             }
             return {
-                externalUserType: 'all'
+                externalUserType: 'all',
+                externalUserFilterName,
+                id: nowTime + (index * 1000) + (index1 * 100)
             }
         })
 
         massSendingContentDTO.push({
-            sendContentDto
+            sendContentDto,
+            id: nowTime + (index * 1000)
         })
 
         return {
             ...strategy,
-            sendData
+            sendData,
+            id: nowTime + (index * 1000)
         }
     })
 

+ 1 - 0
src/pages/weComTask/page/businessPlan/create/global.less

@@ -2,6 +2,7 @@
     .ant-modal-body {
         display: flex;
         padding: 0;
+        background-color: #f5f8ff;
     }
 
     .body_steps {

+ 10 - 4
src/pages/weComTask/page/businessPlan/create/index.less

@@ -16,6 +16,7 @@
     margin-top: 16px;
     width: 100%;
     overflow: hidden;
+    overflow-x: auto;
     --w5: 216px;
     --w100: 100%;
 
@@ -26,6 +27,7 @@
         border-radius: 6px;
         display: flex;
         box-sizing: border-box;
+        min-width: 1400px;
 
         >div {
             display: flex;
@@ -46,6 +48,7 @@
         border-left: 1px solid #f0f0f0;
         box-sizing: border-box;
         display: flex;
+        width: calc(100% / 4);
 
         .title {
             height: 50px;
@@ -76,10 +79,13 @@
     }
 
     .settingsBody_content_col {
-        width: 350px;
-        max-width: 350px;
-        min-width: 350px;
-
+        // width: 350px;
+        // max-width: 350px;
+        // min-width: 350px;
+        // width: var(--w100) / 4;
+        // max-width: calc(var(--w100) / 4);
+        // min-width: var(--w100) / 4;
+        width: 100%;
         
 
         .detail {

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 333 - 411
src/pages/weComTask/page/businessPlan/create/index.tsx


+ 30 - 7
src/pages/weComTask/page/businessPlan/create/submitModal.tsx

@@ -1,7 +1,9 @@
-import { Form, Input, message, Modal } from "antd"
-import React from "react"
+import { Form, Input, message, Modal, Select } from "antd"
+import React, { useEffect, useState } from "react"
 import dayjs from "dayjs"
 import { randomString } from "@/utils/utils"
+import { useAjax } from "@/Hook/useAjax"
+import { getProjectGroupsAllListApi } from "@/pages/weComTask/API/groupManage"
 
 /**
  * 设置名称
@@ -11,15 +13,25 @@ interface Props {
     visible?: boolean,
     onClose?: () => void,
     onChange?: (data: any) => void
-    ajax?: any
+    loading?: boolean
+    projectName?: string
 }
 const SubmitModal: React.FC<Props> = (props) => {
 
     /********************/
-    const { visible, onClose, onChange, ajax } = props
+    const { visible, onClose, onChange, loading, projectName } = props
     const [form] = Form.useForm()
+    const [groupList, setGroupList] = useState<{ label: string, value: number }[]>([])
+
+    const getProjectGroupsAllList = useAjax(() => getProjectGroupsAllListApi())
     /********************/
 
+    useEffect(() => {
+        getProjectGroupsAllList.run().then(res => {
+            setGroupList(res?.data?.map(item => ({ label: item.name, value: item.id })) || [])
+        })
+    }, [])
+
     const handleOk = () => {
         form.validateFields().then((values) => {
             const params = JSON.parse(JSON.stringify(values))
@@ -31,9 +43,9 @@ const SubmitModal: React.FC<Props> = (props) => {
 
 
     return <Modal
-        title={<strong style={{ fontSize: 20 }}>提交</strong>}
+        title={<strong style={{ fontSize: 20 }}>{projectName ? projectName + '任务修改' : ''}提交</strong>}
         open={visible}
-        confirmLoading={ajax?.loading}
+        confirmLoading={loading}
         onCancel={onClose}
         maskClosable={false}
         onOk={handleOk}
@@ -53,12 +65,23 @@ const SubmitModal: React.FC<Props> = (props) => {
             colon={false}
             labelAlign="left"
             initialValues={{
-                projectName: '任务' + dayjs().format('MMDDHHmmss') + '_' + randomString(true, 3, 5)
+                projectName: projectName || '任务' + dayjs().format('MMDDHHmmss') + '_' + randomString(true, 3, 5)
             }}
         >
             <Form.Item label={<strong>任务名称</strong>} name="projectName" rules={[{ required: true, message: '请输入任务名称!' }]}>
                 <Input placeholder="请输入任务名称" />
             </Form.Item>
+            <Form.Item label={<strong>项目组</strong>} name="projectGroupId">
+                <Select
+                    placeholder='请选择项目组'
+                    allowClear
+                    options={groupList}
+                    showSearch
+                    filterOption={(input, option) =>
+                        (option?.label ?? '').toLowerCase().includes(input.toLowerCase())
+                    }
+                />
+            </Form.Item>
         </Form>
     </Modal>
 }

+ 236 - 39
src/pages/weComTask/page/businessPlan/create/tableConfig.tsx

@@ -1,4 +1,4 @@
-import { Typography } from "antd";
+import { Input, Typography } from "antd";
 import { AnyObject } from "antd/es/_util/type"
 import { ColumnsType } from "antd/es/table"
 import { businessPlanData, TIME_TYPE, welcomeContentData } from "./const";
@@ -6,19 +6,26 @@ import FilterUser from "@/pages/weComTask/components/filterUser";
 import ShowContentTable from "./components/massSending/showContentTable";
 import WelcomeShowTable from "./components/welcome/welcomeShowTable";
 import FriendsShowTable from "./components/friends/friendsShowTable";
+import SelectBookLinkButton from "../../bookLink/SelectBookLinkButton";
 const { Title, Text, Paragraph } = Typography;
 
-export const userInheritColumns = (): ColumnsType<AnyObject> => {
+export const userInheritColumns = (setContent: (data: TASK_CREATE.SetContentProps) => void): ColumnsType<AnyObject> => {
 
     return [
         {
-            title: '账号(原跟进企微号)',
-            dataIndex: 'corpUserName',
-            key: 'corpUserName',
+            title: '账号',
+            dataIndex: 'corpUserGroupName',
+            key: 'corpUserGroupName',
             width: 150,
             render(value, record) {
-                return <Text>{value}({record.corpUserId})</Text>
+                return <>
+                    <Title level={5} style={{ margin: 0 }}>{value}({record?.corpUserList?.[0]?.corpName})</Title>
+                    <Paragraph style={{ margin: 0 }}>客服号:{record?.corpUserList?.map(i => i.name).join('、')}</Paragraph>
+                </>
             },
+            onCell: (record) => {
+                return { rowSpan: record.userRowSpan }
+            }
         },
         {
             title: '继承信息',
@@ -33,6 +40,9 @@ export const userInheritColumns = (): ColumnsType<AnyObject> => {
                     {record?.templateProductId && <Paragraph style={{ margin: 0 }}>适用产品:{record?.templateProductId || '<空>'}</Paragraph>}
                 </>
             },
+            onCell: (record) => {
+                return { rowSpan: record.userRowSpan }
+            }
         },
         {
             title: '策略信息',
@@ -49,6 +59,9 @@ export const userInheritColumns = (): ColumnsType<AnyObject> => {
                     {value?.sendTime && <Paragraph style={{ margin: 0 }}>执行时间:{value?.sendTime}</Paragraph>}
                     {value?.repeatArray && <Paragraph style={{ margin: 0 }}>执行天数:{value?.repeatArray.join('、')}</Paragraph>}
                 </>
+            },
+            onCell: (record) => {
+                return { rowSpan: record.strategyRowSpan }
             }
         },
         {
@@ -78,8 +91,16 @@ export const userInheritColumns = (): ColumnsType<AnyObject> => {
             dataIndex: 'externalUser',
             key: 'externalUserCorpUserName',
             width: 100,
-            render(value) {
-                return <Text style={{ fontWeight: 'bold', color: '#000' }}>{value?.corpUserName || '<空>'}</Text>
+            render(_, record) {
+                return <Input.TextArea
+                    placeholder="请输入接替客户企微号"
+                    allowClear
+                    onChange={e => setContent({
+                        msg: { corpUserName: e.target.value },
+                        index: [record.corpUsergroupIndex, record.strategyIndex - 1, record.inheritIndex - 1],
+                        type: 'userInherit'
+                    })}
+                />
             },
         },
         {
@@ -87,24 +108,36 @@ export const userInheritColumns = (): ColumnsType<AnyObject> => {
             dataIndex: 'externalUser',
             key: 'externalUserCorpUserId',
             width: 100,
-            render(value) {
-                return <Text style={{ fontWeight: 'bold', color: '#000' }}>{value?.corpUserId || '<空>'}</Text>
+            render(_, record) {
+                return <Input.TextArea
+                    placeholder="请输入接替客户企微号ID"
+                    allowClear
+                    onChange={e => setContent({
+                        msg: { corpUserId: e.target.value },
+                        index: [record.corpUsergroupIndex, record.strategyIndex - 1, record.inheritIndex - 1],
+                        type: 'userInherit'
+                    })}
+                />
             },
         }
     ]
 
 }
 
-
-export const massSendingColumns = (): ColumnsType<AnyObject> => {
+/** 群发 */
+export const massSendingColumns = (setContent: (data: TASK_CREATE.SetContentProps) => void, bookPlatForm: TASK_CREATE.BookPlatFormProps[], bookList: TASK_CREATE.BookListProps[], previewContent: { groupMsgContent?: any[], welcomeMsgContent?: any[], externalUserTransferContent?: any[] }, platform: string): ColumnsType<AnyObject> => {
     return [
         {
             title: '账号',
-            dataIndex: 'corpUserName',
-            key: 'corpUserName',
+            dataIndex: 'corpUserGroupName',
+            key: 'corpUserGroupName',
             width: 150,
             render(value, record) {
-                return <Text>{value}({record.corpUserId})</Text>
+                return <>
+                    <Title level={5} style={{ margin: 0 }}>{value}</Title>
+                    <Paragraph style={{ margin: 0 }}>公众号:{record?.corpUserList?.[0]?.mpAccountName || '--'}</Paragraph>
+                    <Paragraph style={{ margin: 0 }}>客服号:{record?.corpUserList?.map(i => i.name).join('、')}</Paragraph>
+                </>
             },
             onCell: (record) => {
                 return { rowSpan: record.userRowSpan }
@@ -118,9 +151,9 @@ export const massSendingColumns = (): ColumnsType<AnyObject> => {
             render(value, record) {
                 return <>
                     <Title level={5} style={{ margin: 0 }}>标题:{value}</Title>
-                    {record?.bizType && <Paragraph style={{ margin: 0 }}>业务类型:{record?.bizType || '<空>'}</Paragraph>}
-                    {record?.platform && <Paragraph style={{ margin: 0 }}>书城:{record?.platform || '<空>'}</Paragraph>}
-                    {record?.templateProductId && <Paragraph style={{ margin: 0 }}>适用产品:{record?.templateProductId || '<空>'}</Paragraph>}
+                    {record?.bizType && <Paragraph style={{ margin: 0 }}>业务类型:{record?.bizType === 'novel' ? '小说' : record?.bizType === 'game' ? '游戏' : '<空>'}</Paragraph>}
+                    {record?.platform && <Paragraph style={{ margin: 0 }}>书城:{record?.platform ? bookPlatForm?.find(item => item.id === record?.platform)?.platformName : '<空>'}</Paragraph>}
+                    {record?.templateProductId && <Paragraph style={{ margin: 0 }}>适用产品:{record?.templateProductId ? bookList?.find(item => item.id === record?.templateProductId)?.bookName : '<空>'}</Paragraph>}
                 </>
             },
             onCell: (record) => {
@@ -155,6 +188,7 @@ export const massSendingColumns = (): ColumnsType<AnyObject> => {
             render(value, record) {
                 return <>
                     <Title level={5} style={{ margin: 0 }}>发送对象{record.sendDataIndex}</Title>
+                    <Paragraph style={{ margin: 0 }} ellipsis>名称:{value?.externalUserFilterName}</Paragraph>
                     <Paragraph style={{ margin: 0 }}>内容发送模式:{welcomeContentData?.find(i => i.value === record?.sendMode)?.label}</Paragraph>
                     <Paragraph style={{ margin: 0 }}>类型:{value?.externalUserType === 'all' ? '全部' : '指定'}</Paragraph>
                     {value?.externalUserType === 'specify' && <FilterUser isSHow value={value?.externalUserFilter} configType='USER_GROUP' />}
@@ -173,7 +207,7 @@ export const massSendingColumns = (): ColumnsType<AnyObject> => {
                 return <>
                     <ShowContentTable
                         data={record?.content}
-                        name={<><span>内容{record.contentIndex}:</span><span dangerouslySetInnerHTML={{ __html: record.contentReactNode?.map((item) => item).join('、') }}></span></>}
+                        name={<><span>内容{record.contentIndex}:</span><span dangerouslySetInnerHTML={{ __html: record.contentReactNode?.map((item) => item).join('、') }}></span></>}
                     />
                 </>
             },
@@ -182,9 +216,40 @@ export const massSendingColumns = (): ColumnsType<AnyObject> => {
             title: '图文链接',
             dataIndex: 'linkUrl',
             key: 'linkUrl',
-            width: 200,
+            width: 250,
             render(_, record) {
-                return <Paragraph style={{ margin: 0, color: '#000', fontWeight: !!record?.groupMsgContent?.linkUrl ? 'bold' : 'normal' }}>{record?.groupMsgContent?.linkUrl || '--'}</Paragraph>
+                return record?.linkData?.length > 0 ? <div style={{ display: 'flex', gap: 4, alignItems: 'flex-end' }}>
+                    <Input.TextArea
+                        placeholder="请输入链接"
+                        allowClear
+                        value={previewContent?.groupMsgContent?.[record.corpUsergroupIndex]?.[record.strategyIndex - 1]?.[record.sendDataIndex - 1]?.[record.contentIndex - 1]?.linkUrl}
+                        onChange={e => {
+                            setContent({
+                                msg: { linkUrl: e.target.value },
+                                index: [record.corpUsergroupIndex, record.strategyIndex - 1, record.sendDataIndex - 1, record.contentIndex - 1],
+                                type: 'massSending'
+                            })
+                        }}
+                    />
+                    <SelectBookLinkButton
+                        bookPlatForm={bookPlatForm}
+                        platform={platform}
+                        mpAccountId={record.corpUserList?.[0]?.mpAccountId}
+                        linkData={record.linkData}
+                        onChange={(linkStr, miniprogramAppid, miniprogramPage) => {
+                            const msg: { [x: string]: string } = { linkUrl: linkStr }
+                            if (miniprogramAppid && miniprogramPage && record?.miniProgramData?.length > 0 && !previewContent?.groupMsgContent?.[record.corpUsergroupIndex]?.[record.strategyIndex - 1]?.[record.sendDataIndex - 1]?.[record.contentIndex - 1]?.miniprogramAppid && !previewContent?.groupMsgContent?.[record.corpUsergroupIndex]?.[record.strategyIndex - 1]?.[record.sendDataIndex - 1]?.[record.contentIndex - 1]?.miniprogramPage) {
+                                msg.miniprogramAppid = miniprogramAppid
+                                msg.miniprogramPage = miniprogramPage
+                            }
+                            setContent({
+                                msg,
+                                index: [record.corpUsergroupIndex, record.strategyIndex - 1, record.sendDataIndex - 1, record.contentIndex - 1],
+                                type: 'massSending'
+                            })
+                        }}
+                    />
+                </div> : '--'
             },
         },
         {
@@ -193,17 +258,62 @@ export const massSendingColumns = (): ColumnsType<AnyObject> => {
             key: 'miniprogramAppid',
             width: 200,
             render(_, record) {
-                return <Paragraph style={{ margin: 0, color: '#000', fontWeight: !!record?.groupMsgContent?.miniprogramAppid ? 'bold' : 'normal' }}>{record?.groupMsgContent?.miniprogramAppid || '--'}</Paragraph>
+                return record?.miniProgramData?.length > 0 ? <Input.TextArea
+                    placeholder="请输入小程序APPID"
+                    allowClear
+                    value={previewContent?.groupMsgContent?.[record.corpUsergroupIndex]?.[record.strategyIndex - 1]?.[record.sendDataIndex - 1]?.[record.contentIndex - 1]?.miniprogramAppid}
+                    onChange={e => setContent({
+                        msg: { miniprogramAppid: e.target.value },
+                        index: [record.corpUsergroupIndex, record.strategyIndex - 1, record.sendDataIndex - 1, record.contentIndex - 1],
+                        type: 'massSending'
+                    })}
+                /> : '--'
             },
         },
         {
             title: '小程序路径',
             dataIndex: 'miniprogramPage',
             key: 'miniprogramPage',
-            width: 200,
+            width: 250,
             render(_, record) {
-                return <Paragraph style={{ margin: 0, color: '#000', fontWeight: !!record?.groupMsgContent?.miniprogramPage ? 'bold' : 'normal' }}>{record?.groupMsgContent?.miniprogramPage || '--'}</Paragraph>
-            },
+                return record?.miniProgramData?.length > 0 ? <div style={{ display: 'flex', gap: 4, alignItems: 'flex-end' }}>
+                    <Input.TextArea
+                        placeholder="请输入小程序路径"
+                        allowClear
+                        value={previewContent?.groupMsgContent?.[record.corpUsergroupIndex]?.[record.strategyIndex - 1]?.[record.sendDataIndex - 1]?.[record.contentIndex - 1]?.miniprogramPage}
+                        onChange={e => setContent({
+                            msg: { miniprogramPage: e.target.value },
+                            index: [record.corpUsergroupIndex, record.strategyIndex - 1, record.sendDataIndex - 1, record.contentIndex - 1],
+                            type: 'massSending'
+                        })}
+                    />
+                    <SelectBookLinkButton
+                        bookPlatForm={bookPlatForm}
+                        platform={platform}
+                        mpAccountId={record.corpUserList?.[0]?.mpAccountId}
+                        linkData={record.miniProgramData.map(item => ({
+                            msgType: "TASK_CONTENT_LINK",
+                            link: {
+                                title: item?.miniprogram?.title,
+                                picUrl: item?.miniprogram?.picUrl,
+                                desc: ''
+                            }
+                        }))}
+                        onChange={(_, miniprogramAppid, miniprogramPage) => {
+                            const msg: { [x: string]: string } = {}
+                            if (miniprogramAppid && miniprogramPage) {
+                                msg.miniprogramAppid = miniprogramAppid
+                                msg.miniprogramPage = miniprogramPage
+                                setContent({
+                                    msg,
+                                    index: [record.corpUsergroupIndex, record.strategyIndex - 1, record.sendDataIndex - 1, record.contentIndex - 1],
+                                    type: 'massSending'
+                                })
+                            }
+                        }}
+                    />
+                </div> : '--'
+            }
         }
     ]
 }
@@ -335,15 +445,19 @@ export const highMassSendingColumns = (): ColumnsType<AnyObject> => {
 }
 
 
-export const welcomeColumns = (bookPlatForm: { platformName: string, id: number, platformKey: string }[], bookList: { id: number, bookName: string, platformId: number }[]): ColumnsType<AnyObject> => {
+export const welcomeColumns = (bookPlatForm: TASK_CREATE.BookPlatFormProps[], bookList: TASK_CREATE.BookListProps[], setContent: (data: TASK_CREATE.SetContentProps) => void, previewContent: { groupMsgContent?: any[], welcomeMsgContent?: any[], externalUserTransferContent?: any[] }, platform: string): ColumnsType<AnyObject> => {
     return [
         {
             title: '账号',
-            dataIndex: 'corpUserName',
-            key: 'corpUserName',
+            dataIndex: 'corpUserGroupName',
+            key: 'corpUserGroupName',
             width: 150,
             render(value, record) {
-                return <Text>{value}({record.corpUserId})</Text>
+                return <>
+                    <Title level={5} style={{ margin: 0 }}>{value}({record?.corpUserList?.[0]?.corpName})</Title>
+                    <Paragraph style={{ margin: 0 }}>公众号:{record?.corpUserList?.[0]?.mpAccountName || '--'}</Paragraph>
+                    <Paragraph style={{ margin: 0 }}>客服号:{record?.corpUserList?.map(i => i.name).join('、')}</Paragraph>
+                </>
             },
             onCell: (record) => {
                 return { rowSpan: record.rowSpan }
@@ -357,9 +471,9 @@ export const welcomeColumns = (bookPlatForm: { platformName: string, id: number,
             render(value, record) {
                 return <>
                     <Title level={5} style={{ margin: 0 }}>标题:{value}</Title>
-                    {record?.bizType && <Paragraph style={{ margin: 0 }}>业务类型:{record?.bizType || '<空>'}</Paragraph>}
-                    {record?.platform && <Paragraph style={{ margin: 0 }}>书城:{record?.platform || '<空>'}</Paragraph>}
-                    {record?.templateProductId && <Paragraph style={{ margin: 0 }}>适用产品:{record?.templateProductId || '<空>'}</Paragraph>}
+                    {record?.bizType && <Paragraph style={{ margin: 0 }}>业务类型:{record?.bizType === 'noval' ? '小说' : record?.bizType === 'game' ? '游戏' : '<空>'}</Paragraph>}
+                    {record?.platform && <Paragraph style={{ margin: 0 }}>书城:{record?.platform ? bookPlatForm?.find(item => item.id === record?.platform)?.platformName : '<空>'}</Paragraph>}
+                    {record?.templateProductId && <Paragraph style={{ margin: 0 }}>适用产品:{record?.templateProductId ? bookList?.find(item => item.id === record?.templateProductId)?.bookName : '<空>'}</Paragraph>}
                 </>
             },
             onCell: (record) => {
@@ -411,28 +525,111 @@ export const welcomeColumns = (bookPlatForm: { platformName: string, id: number,
             title: '图文链接',
             dataIndex: 'linkUrl',
             key: 'linkUrl',
-            width: 150,
+            width: 250,
             render(_, record) {
-                return <Paragraph style={{ margin: 0, color: '#000', fontWeight: !!record?.welcomeMsgContent?.linkUrl ? 'bold' : 'normal' }}>{record?.welcomeMsgContent?.linkUrl || '--'}</Paragraph>
+                return record?.linkData?.length > 0 ? <div style={{ display: 'flex', gap: 4, alignItems: 'flex-end' }}>
+                    <Input.TextArea
+                        placeholder="请输入链接"
+                        allowClear
+                        value={previewContent?.welcomeMsgContent?.[record.corpUsergroupIndex]?.[record.contentIndex - 1]?.linkUrl}
+                        onChange={e => {
+                            setContent({
+                                msg: { linkUrl: e.target.value },
+                                index: [record.corpUsergroupIndex, record.contentIndex - 1],
+                                type: 'welcome'
+                            })
+                        }}
+                    />
+                    <SelectBookLinkButton
+                        bookPlatForm={bookPlatForm}
+                        platform={platform}
+                        mpAccountId={record.corpUserList?.[0]?.mpAccountId}
+                        linkData={record.linkData.map(item => ({
+                            msgType: "TASK_CONTENT_LINK",
+                            link: {
+                                title: item.linkTitle,
+                                picUrl: item.linkPicurl,
+                                desc: item.linkDesc
+                            }
+                        }))}
+                        onChange={(linkStr, miniprogramAppid, miniprogramPage) => {
+                            const msg: { [x: string]: string } = { linkUrl: linkStr }
+                            if (miniprogramAppid && miniprogramPage && record?.miniProgramData?.length > 0 && !previewContent?.welcomeMsgContent?.[record.corpUsergroupIndex]?.[record.contentIndex - 1]?.miniprogramAppid && !previewContent?.welcomeMsgContent?.[record.corpUsergroupIndex]?.[record.contentIndex - 1]?.miniprogramPage) {
+                                msg.miniprogramAppid = miniprogramAppid
+                                msg.miniprogramPage = miniprogramPage
+                            }
+                            setContent({
+                                msg,
+                                index: [record.corpUsergroupIndex, record.contentIndex - 1],
+                                type: 'welcome'
+                            })
+                        }}
+                    />
+                </div> : '--'
             },
         },
         {
             title: '小程序APPID',
             dataIndex: 'miniprogramAppid',
             key: 'miniprogramAppid',
-            width: 150,
+            width: 200,
             render(_, record) {
-                return <Paragraph style={{ margin: 0, color: '#000', fontWeight: !!record?.welcomeMsgContent?.miniprogramAppid ? 'bold' : 'normal' }}>{record?.welcomeMsgContent?.miniprogramAppid || '--'}</Paragraph>
+                return record?.miniProgramData?.length > 0 ? <Input.TextArea
+                    placeholder="请输入小程序APPID"
+                    allowClear
+                    value={previewContent?.welcomeMsgContent?.[record.corpUsergroupIndex]?.[record.contentIndex - 1]?.miniprogramAppid}
+                    onChange={e => setContent({
+                        msg: { miniprogramAppid: e.target.value },
+                        index: [record.corpUsergroupIndex, record.contentIndex - 1],
+                        type: 'welcome'
+                    })}
+                /> : '--'
             },
         },
         {
             title: '小程序路径',
             dataIndex: 'miniprogramPage',
             key: 'miniprogramPage',
-            width: 150,
+            width: 250,
             render(_, record) {
-                return <Paragraph style={{ margin: 0, color: '#000', fontWeight: !!record?.welcomeMsgContent?.miniprogramPage ? 'bold' : 'normal' }}>{record?.welcomeMsgContent?.miniprogramPage || '--'}</Paragraph>
-            },
+                return record?.miniProgramData?.length > 0 ? <div style={{ display: 'flex', gap: 4, alignItems: 'flex-end' }}>
+                    <Input.TextArea
+                        placeholder="请输入小程序路径"
+                        allowClear
+                        value={previewContent?.welcomeMsgContent?.[record.corpUsergroupIndex]?.[record.contentIndex - 1]?.miniprogramPage}
+                        onChange={e => setContent({
+                            msg: { miniprogramPage: e.target.value },
+                            index: [record.corpUsergroupIndex, record.contentIndex - 1],
+                            type: 'welcome'
+                        })}
+                    />
+                    <SelectBookLinkButton
+                        bookPlatForm={bookPlatForm}
+                        platform={platform}
+                        mpAccountId={record.corpUserList?.[0]?.mpAccountId}
+                        linkData={record.miniProgramData.map(item => ({
+                            msgType: "TASK_CONTENT_LINK",
+                            link: {
+                                title: item.miniprogramTitle,
+                                picUrl: item.miniprogramPicurl,
+                                desc: ''
+                            }
+                        }))}
+                        onChange={(_, miniprogramAppid, miniprogramPage) => {
+                            const msg: { [x: string]: string } = {}
+                            if (miniprogramAppid && miniprogramPage) {
+                                msg.miniprogramAppid = miniprogramAppid
+                                msg.miniprogramPage = miniprogramPage
+                                setContent({
+                                    msg,
+                                    index: [record.corpUsergroupIndex, record.contentIndex - 1],
+                                    type: 'welcome'
+                                })
+                            }
+                        }}
+                    />
+                </div> : '--'
+            }
         }
     ]
 }

+ 16 - 1
src/pages/weComTask/page/businessPlan/create/typings.d.ts

@@ -31,12 +31,13 @@ declare namespace TASK_CREATE {
         value?: { [x: string]: any };
     }
     interface MassSendingProps<T> extends DefaultProps, DefaultChangeProps<T> {
+        bookCityList: { label: string, value: string }[]
         value?: {
             massSendingStrategy?: { [x: string]: any };
             massSendingContent?: { massSendingContentDTO: any };
         };
         onChange?: (value?: { [x: string]: any }, type?: string) => void;
-        createType?: 'STRATEGY' | 'CONTENT'
+        createType?: 'STRATEGY' | 'CONTENT';
     }
     interface HighMassSendingProps<T> extends DefaultProps, DefaultChangeProps<T> {
         value?: {
@@ -70,9 +71,17 @@ declare namespace TASK_CREATE {
         friendsTagContent?: any[],
         friendsMsgContent?: any[],
     }
+    type CorpUserProps = { corpUserId: string, name: string, corpName: string, corpId: string, id: string, mpAccountId?: number, mpAccountName?: string }
+    type CorpUserGroupProps = { 
+        corpUsers: CorpUserProps[],
+        welcomeMsgContent?: any[],
+        groupMsgContent?: any[],
+        externalUserTransferContent?: any[]
+    }
     // 配置保存字段
     interface SettingsProps {
         corpUsers?: corpUsersProps[]; // 企微号列表
+        corpUserGroups?: CorpUserGroupProps[];
         bizType?: string; // 业务类型
         platform?: string; // 书城
         channel?: string;   // 渠道
@@ -94,6 +103,7 @@ declare namespace TASK_CREATE {
     // 群发内容
     interface ContentProps {
         massSendingStrategy: { [x: string]: any };
+        bookCityList?: { label: string, value: string }[]
         onChange?: (value?: { [x: string]: any }, type: string) => void;
         value?: { massSendingContentDTO: any };
     }
@@ -192,4 +202,9 @@ declare namespace TASK_CREATE {
         bookPlatForm: BookPlatFormProps[]
         bookList: BookListProps[]
     }
+    interface SetContentProps {
+        msg: { [x: string]: string },
+        index: number[],
+        type: 'welcome' | 'massSending' | 'userInherit'
+    }
 }

+ 1 - 1
src/pages/weComTask/page/businessPlan/taskList/components/groupTask/groupTaskNotes.tsx

@@ -51,7 +51,7 @@ const GroupTaskNotes: React.FC<Props> = ({ corpId, taskId }) => {
             dataSource={getSendTaskMsgList?.data?.data?.records}
             loading={getSendTaskMsgList?.loading}
             columns={groupTaskNotesTableConfig(() => getSendTaskMsgList.refresh())}
-            scroll={{ x: 400, y: 350 }}
+            scroll={{ x: 400, y: 320 }}
             rowKey={(s) => {
                 return s.id
             }}

+ 1 - 1
src/pages/weComTask/page/businessPlan/taskList/components/groupTask/groupXfCorpTabls.tsx

@@ -116,7 +116,7 @@ const GroupXfCorpTabls: React.FC<Props> = ({ corpId, taskId }) => {
             dataSource={getSendTaskuserList?.data?.data?.records}
             loading={getSendTaskuserList?.loading}
             columns={groupMsgCorpUserListConfig(changeHandle)}
-            scroll={{ x: 400, y: 350 }}
+            scroll={{ x: 400, y: 320 }}
             rowKey={(s) => {
                 return s.id
             }}

+ 5 - 4
src/pages/weComTask/page/businessPlan/taskList/components/groupTask/index.tsx

@@ -166,9 +166,10 @@ export const ExpandedRow: React.FC<{ record: any }> = ({ record }) => {
                 {...record}
             />
             <h2>群发内容</h2>
-            <div style={{ marginTop: 5, backgroundColor: '#d9d9d9', borderRadius: 6, padding: '10px 10px 16px' }}>
-                <LookMsg data={getMsgData(contentDTO?.[0], token)} />
-            </div>
+            {contentDTO?.map((item, index) => <div key={index} style={{ marginTop: 5, backgroundColor: '#d9d9d9', borderRadius: 6, padding: '10px 10px 16px' }}>
+                <h3 style={{ marginTop: 0, textAlign: 'center' }}>内容{index + 1}</h3>
+                <LookMsg data={getMsgData(item, token)} />
+            </div>)}
         </div>
         <div style={{ flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column', gap: 10, height: '100%' }}>
             <Tabs
@@ -190,7 +191,7 @@ export const ExpandedRow: React.FC<{ record: any }> = ({ record }) => {
                     {
                         key: '4',
                         label: '群发记录',
-                        children: <Card styles={{ body: { minHeight: 185 } }}>
+                        children: <Card styles={{ body: { minHeight: 400 } }}>
                             {activeKey == '4' && <GroupTaskNotes corpId={corpId} taskId={id} />}
                         </Card>
                     }

+ 104 - 4
src/pages/weComTask/page/businessPlan/taskList/index.tsx

@@ -1,10 +1,10 @@
 import { useAjax } from '@/Hook/useAjax';
-import { getProjectListApi } from '@/pages/weComTask/API/businessPlan/create';
-import { Button, Card, DatePicker, Input, Space, Table } from 'antd';
+import { cancelProjectApi, delProjectApi, getProjectListApi } from '@/pages/weComTask/API/businessPlan/create';
+import { App, Button, Card, DatePicker, Input, Popconfirm, Space, Table } from 'antd';
 import React, { useEffect, useState } from 'react';
 import taskListColumns from './tableConfig';
 import dayjs from 'dayjs';
-import { SearchOutlined } from '@ant-design/icons';
+import { SearchOutlined, DeleteOutlined, PauseCircleOutlined } from '@ant-design/icons';
 import Log from './log';
 import { inject, observer } from 'mobx-react';
 import { toJS } from 'mobx';
@@ -17,12 +17,16 @@ const TaskList: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookL
 
     /***********************************************/
     const projectName = sessionStorage.getItem('CAMPCORP')
+    const { message } = App.useApp();
     const { bookList, bookPlatForm } = toJS(weComTaskStore.data)
     const [queryForm, setQueryForm] = useState<BUSINES_SPLAN_API.GetProjectListProps>({ pageNum: 1, pageSize: 20, projectName })
     const [queryFormNew, setQueryFormNew] = useState<BUSINES_SPLAN_API.GetProjectListProps>({ pageNum: 1, pageSize: 20, projectName })
     const [logOpenData, setLogOpenData] = useState<{ visible: boolean, data: any }>({ visible: false, data: {} })
+    const [selectedRows, setselectedRows] = useState<any[]>([])
 
     const getProjectList = useAjax((params) => getProjectListApi(params))
+    const delProject = useAjax((params) => delProjectApi(params))
+    const cancelProject = useAjax((params) => cancelProjectApi(params))
     /***********************************************/
 
     useEffect(() => {
@@ -36,10 +40,51 @@ const TaskList: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookL
         getProjectList.run(queryFormNew)
     }, [queryFormNew])
 
+    // 日志
     const handleLog = (data: any) => {
         setLogOpenData({ visible: true, data })
     }
 
+    // 复制
+    const handleCopy = (data: any, isCopy: boolean) => {
+        sessionStorage.setItem('OFFICIALTASKID', JSON.stringify({ id: data.id, isCopy }))
+        sessionStorage.setItem('oldPath', '/weComTask/businessPlan/create')
+        window.location.href = '/weComTask#/weComTask/businessPlan/create'
+    }
+
+    // 删除
+    const handleDel = (data: { projectIds: number[] }, type: 'del' | 'cancel' | 'open') => {
+        const hide = message.loading(type === 'del' ? '正在删除...' : '正在取消...', 0)
+        switch (type) {
+            case 'del':
+                delProject.run(data).then(res => {
+                    hide()
+                    setselectedRows([])
+                    if (res?.data) {
+                        message.success('删除成功')
+                        getProjectList.refresh()
+                    } else {
+                        message.error('删除失败')
+                    }
+                }).catch(() => hide())
+                break
+            case 'cancel':
+            case 'open':
+                cancelProject.run({ ...data, pause: type === 'cancel' ? false : true }).then(res => {
+                    hide()
+                    setselectedRows([])
+                    if (res?.data) {
+                        message.success(type === 'cancel' ? '取消成功' : '启用成功')
+                        getProjectList.refresh()
+                    } else {
+                        message.error(type === 'cancel' ? '取消失败' : '启用失败')
+                    }
+                }).catch(() => hide())
+                break
+        }
+
+    }
+
     return <Card>
         <div style={{ display: 'flex', gap: 10, marginBottom: 10 }}>
             <Space.Compact>
@@ -71,10 +116,31 @@ const TaskList: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookL
                 }}
                 icon={<SearchOutlined />}
             >搜索</Button>
+            <Popconfirm
+                title="确定删除?"
+                onConfirm={() => { handleDel({ projectIds: selectedRows.map(i => i.id) }, 'del') }}
+                disabled={selectedRows.length === 0}
+            >
+                <Button type='primary' danger icon={<DeleteOutlined />} loading={delProject.loading} disabled={selectedRows.length === 0}>删除</Button>
+            </Popconfirm>
+            <Popconfirm
+                title="确定暂停?"
+                onConfirm={() => { handleDel({ projectIds: selectedRows.map(i => i.id) }, 'cancel') }}
+                disabled={selectedRows.length === 0}
+            >
+                <Button type='primary' style={{ backgroundColor: 'orange', borderColor: 'orange' }} loading={cancelProject.loading} icon={<PauseCircleOutlined />} disabled={selectedRows.length === 0}>暂停任务</Button>
+            </Popconfirm>
+            <Popconfirm
+                title="确定启用?"
+                onConfirm={() => { handleDel({ projectIds: selectedRows.map(i => i.id) }, 'open') }}
+                disabled={selectedRows.length === 0}
+            >
+                <Button type='primary' style={{ backgroundColor: '#87d068', borderColor: '#87d068' }} loading={cancelProject.loading} icon={<PauseCircleOutlined />} disabled={selectedRows.length === 0}>启用任务</Button>
+            </Popconfirm>
         </div>
         <Table
             dataSource={getProjectList?.data?.data?.records}
-            columns={taskListColumns(bookPlatForm, bookList, handleLog)}
+            columns={taskListColumns(bookPlatForm, bookList, handleLog, handleCopy, handleDel)}
             rowKey={'id'}
             bordered={true}
             size='small'
@@ -89,6 +155,40 @@ const TaskList: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookL
                     setQueryForm({ ...queryForm, pageNum: page, pageSize })
                 }
             }}
+            rowSelection={{
+                selectedRowKeys: selectedRows?.map((item: any) => item?.id),
+                onSelect: (record: { id: string }, selected: boolean) => {
+                    let newData = JSON.parse(JSON.stringify(selectedRows))
+                    if (selected) {
+                        newData.push({ ...record })
+                    } else {
+                        newData = newData.filter((item: { id: string }) => item.id !== record.id)
+                    }
+                    setselectedRows(newData)
+                },
+                onSelectAll: (selected: boolean, _: { id: string }[], changeRows: { id: string }[]) => {
+                    let newData = JSON.parse(JSON.stringify(selectedRows || '[]'))
+                    if (selected) {
+                        changeRows.forEach((item: { id: string }) => {
+                            let index = newData.findIndex((ite: { id: string }) => ite.id === item.id)
+                            if (index === -1) {
+                                newData.push(item)
+                            }
+                        })
+                    } else {
+                        let newSelectAccData = newData.filter((item: { id: string }) => {
+                            let index = changeRows.findIndex((ite: { id: string }) => ite.id === item.id)
+                            if (index !== -1) {
+                                return false
+                            } else {
+                                return true
+                            }
+                        })
+                        newData = newSelectAccData
+                    }
+                    setselectedRows(newData)
+                }
+            }}
         />
 
         {/* 日志 */}

+ 1 - 1
src/pages/weComTask/page/businessPlan/taskList/log.tsx

@@ -43,7 +43,7 @@ const Log: React.FC<Props> = ({ data, bookPlatForm, bookList, visible, onClose }
         title={<strong>{data.projectName} 任务详情</strong>}
         onClose={onClose}
         open={visible}
-        width={1200}
+        width={1400}
         styles={{ body: { paddingTop: 5 } }}
     >
 

+ 37 - 89
src/pages/weComTask/page/businessPlan/taskList/tableConfig.tsx

@@ -1,33 +1,51 @@
-import { Popover, Space, Typography } from "antd";
+import { Flex, Popconfirm, Popover, Space, Typography } from "antd";
 import { AnyObject } from "antd/es/_util/type";
 import { ColumnsType } from "antd/es/table";
 import style from './index.less'
 import { QuestionCircleFilled } from '@ant-design/icons';
-import { restoreGroupData, restoreMomentData, restoreUserInheritData } from "../create/const";
+import { restoreGroupData, restoreUserInheritData } from "../create/const";
 import PreviewUserInherit from "../create/components/userInherit/previewUserlnherit";
 import PreviewMassSendingStrategy from "../create/components/massSending/previewMassSendingStrategy";
 import ShowContent from "../create/components/massSending/showContent";
 import PreviewWelcome from "../create/components/welcome/previewWelcome";
-import PreviewFriendsStrategy from "../create/components/friends/previewFriendsStrategy";
-import ShowFriendsContent from "../create/components/friends/showFriendsContent";
 
 const { Text, Paragraph } = Typography;
 const taskListColumns = (
     bookPlatForm: any[],
     bookList: any[],
     handleLog: (data: any) => void,
+    handleCopy: (data: any, isCopy: boolean) => void,
+    handleDel: (data: any, type: 'del' | 'cancel' | 'open') => void,
 ): ColumnsType<AnyObject> => {
 
-
     return [
         {
             title: '操作',
             dataIndex: 'cz',
             key: 'cz',
-            width: 120,
+            width: 160,
             render(_, record) {
                 return <Space>
+                    {record?.status === 1 ? <Popconfirm
+                        title="确定暂停?"
+                        onConfirm={() => { handleDel({ projectIds: [record.id] }, 'cancel') }}
+                    >
+                        <a style={{ color: 'orange' }}>暂停任务</a>
+                    </Popconfirm> : record?.status === 3 ? <Popconfirm
+                        title="确定启用?"
+                        onConfirm={() => { handleDel({ projectIds: [record.id] }, 'open') }}
+                    >
+                        <a style={{ color: '#87d068' }}>启用任务</a>
+                    </Popconfirm> : undefined}
+                    <a onClick={() => handleCopy(record, true)}>复制</a>
+                    <a onClick={() => handleCopy(record, false)}>编辑</a>
                     <a onClick={() => handleLog(record)}>详情</a>
+                    <Popconfirm
+                        title="确定删除?"
+                        onConfirm={() => { handleDel({ projectIds: [record.id] }, 'del') }}
+                    >
+                        <a style={{ color: 'red' }}>删除</a>
+                    </Popconfirm>
                 </Space>
             },
         },
@@ -76,45 +94,6 @@ const taskListColumns = (
                 </div> : <Text type="danger">当前没有欢迎语配置</Text>
             }
         },
-        {
-            title: '朋友圈配置',
-            dataIndex: 'momentCreateDTO',
-            key: 'momentCreateDTO',
-            width: 150,
-            ellipsis: true,
-            render: (value) => {
-                if (value && Object.keys(value)?.length > 0) {
-                    const data = restoreMomentData(value)
-                    return <div className={style.nameBox}>
-                        <div>
-                            <Text ellipsis>{value?.momentSendName || '<空>'}</Text>
-                        </div>
-                        <Popover
-                            placement="left"
-                            content={<div>
-                                <PreviewFriendsStrategy friendsStrategy={data?.friendsStrategy} />
-                            </div>}
-                            styles={{ body: { width: 360, overflow: 'hidden', overflowY: 'auto', maxHeight: 400 } }}
-                        >
-                            <a><QuestionCircleFilled /></a>
-                        </Popover>
-                        <Popover
-                            placement="left"
-                            content={<div>
-                                <ShowFriendsContent
-                                    strategySettings={data?.friendsStrategy?.strategySettings}
-                                    friendsContent={data?.friendsContent}
-                                />
-                            </div>}
-                            styles={{ body: { width: 360, overflow: 'hidden', overflowY: 'auto', maxHeight: 400 } }}
-                        >
-                            <a><QuestionCircleFilled /></a>
-                        </Popover>
-                    </div>
-                }
-                return <Text type="danger">当前没有朋友圈配置</Text>
-            }
-        },
         {
             title: '群发配置',
             dataIndex: 'groupSendTaskAddDTO',
@@ -156,50 +135,6 @@ const taskListColumns = (
                 return <Text type="danger">当前没有群发配置</Text>
             }
         },
-        {
-            title: '高级群发配置',
-            dataIndex: 'messageSendTaskAddDTO',
-            key: 'messageSendTaskAddDTO',
-            width: 170,
-            ellipsis: true,
-            render: (value, record) => {
-                if (value && Object.keys(value)?.length > 0) {
-                    const data = restoreGroupData(value, 'GROUP_GROUP')
-                    console.log('data', record.projectName, data)
-                    return <div className={style.nameBox}>
-                        <div>
-                            <Text ellipsis>{value?.groupSendName || '<空>'}</Text>
-                        </div>
-                        <Popover
-                            placement="left"
-                            content={<div>
-                                <PreviewMassSendingStrategy
-                                    massSendingStrategy={data.massSendingStrategy}
-                                    configType='GROUP_GROUP'
-                                />
-                            </div>}
-                            styles={{ body: { width: 360, overflow: 'hidden', overflowY: 'auto', maxHeight: 400 } }}
-                        >
-                            <a><QuestionCircleFilled /></a>
-                        </Popover>
-                        <Popover
-                            placement="left"
-                            content={<div>
-                                <ShowContent
-                                    strategySettings={data?.massSendingStrategy?.strategySettings}
-                                    massSendingContent={data?.massSendingContent}
-                                    type="highQf"
-                                />
-                            </div>}
-                            styles={{ body: { width: 360, overflow: 'hidden', overflowY: 'auto', maxHeight: 400 } }}
-                        >
-                            <a><QuestionCircleFilled /></a>
-                        </Popover>
-                    </div>
-                }
-                return <Text type="danger">当前没有群发配置</Text>
-            }
-        },
         {
             title: '客户继承配置',
             dataIndex: 'externalUserTransferTasksDTO',
@@ -237,6 +172,19 @@ const taskListColumns = (
             width: 125,
             ellipsis: true
         },
+        {
+            title: '任务数量',
+            dataIndex: 'taskCount',
+            key: 'taskCount',
+            width: 260,
+            render(value, record) {
+                return <Flex gap={6}>
+                    <Text type={record.welcomeMsgCount > 0 ? 'success' : 'danger'}>欢迎语:{record.welcomeMsgCount}</Text>
+                    <Text type={record.groupSendTaskCount > 0 ? 'success' : 'danger'}>群发:{record.groupSendTaskCount}</Text>
+                    <Text type={record.externalUserTransferTaskCount > 0 ? 'success' : 'danger'}>客户继承:{record.externalUserTransferTaskCount}</Text>
+                </Flex>
+            },
+        },
     ]
 }
 

+ 3 - 3
src/pages/weComTask/page/corpUserManage/global.less

@@ -24,8 +24,8 @@
     display: flex;
     align-items: center;
     box-sizing: border-box;
-    border-start-start-radius: 0 !important;
-    border-end-start-radius: 0 !important;
+    // border-start-start-radius: 0 !important;
+    // border-end-start-radius: 0 !important;
 
     .selectCorpUserContent {
         flex: 1;
@@ -45,7 +45,7 @@
         cursor: pointer;
     }
 
-    &:hover  .selectCorpUserIconClear{
+    &:hover .selectCorpUserIconClear {
         display: inline-block;
     }
 }

+ 68 - 3
src/pages/weComTask/page/corpUserManage/index.tsx

@@ -1,6 +1,6 @@
 import { App, Button, Card, Input, Pagination, Select, Table, Tabs } from 'antd';
 import React, { useEffect, useRef, useState } from 'react';
-import { MenuUnfoldOutlined, MenuFoldOutlined, PlusOutlined, SearchOutlined } from '@ant-design/icons';
+import { MenuUnfoldOutlined, MenuFoldOutlined, PlusOutlined, SearchOutlined, UsergroupAddOutlined } from '@ant-design/icons';
 import style from './index.less'
 import GroupLeft from './groupLeft';
 import TeamMembers from '@/components/Team/teamMembers';
@@ -10,6 +10,8 @@ import { delAccountToGroupApi, getCorpUserApi } from '../../API/corpUserManage';
 import SearchBox from '../../components/searchBox';
 import { WeTableConfig } from './tableConfig';
 import { useSize } from 'ahooks';
+import { getBindMpListApi } from '../../API/corpUserAssign';
+import SettingsGroup from './settingsGroup';
 
 /**
  * 企微号管理
@@ -25,14 +27,18 @@ const CorpUserManage: React.FC = () => {
     const [queryFormNew, setQueryFormNew] = useState<CORP_USER_ASSIGN_API.GetCorpUserProps>({ pageNum: 1, pageSize: 20 })
     const [activeKey, setActiveKey] = useState<string>('1')
     const [showLeft, setShowLeft] = useState<boolean>(false)
+    const [selectedRows, setselectedRows] = useState<any[]>([])
+    const [settingsGroupData, setSettingsGroupData] = useState<{ visible?: boolean, type: 'add' | 'del', data: any[] }>();
 
     const allOfMember = useAjax(() => getAdAccountAllOfMember())
+    const getBindMpList = useAjax(() => getBindMpListApi())
     const getCorpUser = useAjax((params) => getCorpUserApi(params))
     const delAccountToGroup = useAjax((params) => delAccountToGroupApi(params))
     /***************************************************/
 
     useEffect(() => {
         allOfMember.run()
+        getBindMpList.run()
     }, [])
 
     useEffect(() => {
@@ -96,6 +102,8 @@ const CorpUserManage: React.FC = () => {
                         <Button type="primary" onClick={() => {
                             setQueryForm({ ...queryFormNew, pageNum: 1 })
                         }} loading={getCorpUser.loading} icon={<SearchOutlined />}>搜索</Button>
+                        <Button type="primary" disabled={selectedRows?.length === 0} onClick={() => { setSettingsGroupData({ visible: true, data: selectedRows, type: 'add' }) }} icon={<UsergroupAddOutlined />}>添加到分组</Button>
+                        <Button type="primary" danger disabled={selectedRows?.length === 0} onClick={() => { setSettingsGroupData({ visible: true, data: selectedRows, type: 'del' }) }} icon={<UsergroupAddOutlined />}>移除分组</Button>
                     </>}
                 >
                     <>
@@ -106,7 +114,7 @@ const CorpUserManage: React.FC = () => {
                             value={queryFormNew?.operUserId}
                             onChange={(e) => setQueryFormNew({ ...queryFormNew, operUserId: e })}
                             showSearch
-                            style={{ width: 120 }}
+                            style={{ width: 110 }}
                             placeholder="运营"
                             filterOption={(input, option) =>
                                 ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
@@ -114,6 +122,18 @@ const CorpUserManage: React.FC = () => {
                             allowClear
                             options={allOfMember?.data?.data?.map((item: any) => ({ label: item.nickname, value: item.userId }))}
                         />
+                        <Select
+                            value={queryFormNew?.mpAccountId}
+                            onChange={(e) => setQueryFormNew({ ...queryFormNew, mpAccountId: e })}
+                            showSearch
+                            style={{ width: 110 }}
+                            placeholder="公众号"
+                            filterOption={(input, option) =>
+                                ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                            }
+                            allowClear
+                            options={getBindMpList?.data?.data?.map((item: any) => ({ label: item.name, value: item.id }))}
+                        />
                         <Select
                             value={queryFormNew?.groupOwner}
                             onChange={(e) => setQueryFormNew({ ...queryFormNew, groupOwner: e })}
@@ -171,9 +191,43 @@ const CorpUserManage: React.FC = () => {
                         columns={WeTableConfig(activeKey, queryForm?.groupId, () => getCorpUser.refresh(), delHandle)}
                         bordered
                         pagination={false}
-                        rowKey={'corpUserId'}
+                        rowKey={'id'}
                         loading={getCorpUser?.loading}
                         scroll={{ y: size?.height && ref.current ? size?.height - ref.current.querySelector('.ant-table-thead').clientHeight : 300 }}
+                        rowSelection={{
+                            selectedRowKeys: selectedRows?.map((item: any) => item?.id),
+                            onSelect: (record: { id: string }, selected: boolean) => {
+                                let newData = JSON.parse(JSON.stringify(selectedRows))
+                                if (selected) {
+                                    newData.push({ ...record })
+                                } else {
+                                    newData = newData.filter((item: { id: string }) => item.id !== record.id)
+                                }
+                                setselectedRows(newData)
+                            },
+                            onSelectAll: (selected: boolean, _: { id: string }[], changeRows: { id: string }[]) => {
+                                let newData = JSON.parse(JSON.stringify(selectedRows || '[]'))
+                                if (selected) {
+                                    changeRows.forEach((item: { id: string }) => {
+                                        const index = newData.findIndex((ite: { id: string }) => ite.id === item.id)
+                                        if (index === -1) {
+                                            newData.push(item)
+                                        }
+                                    })
+                                } else {
+                                    const newSelectAccData = newData.filter((item: { id: string }) => {
+                                        const index = changeRows.findIndex((ite: { id: string }) => ite.id === item.id)
+                                        if (index !== -1) {
+                                            return false
+                                        } else {
+                                            return true
+                                        }
+                                    })
+                                    newData = newSelectAccData
+                                }
+                                setselectedRows(newData)
+                            }
+                        }}
                     />
                 </div>
 
@@ -197,6 +251,17 @@ const CorpUserManage: React.FC = () => {
                 </div>
             </Card>
         </div>
+
+        {/* 设置分组 */}
+        {settingsGroupData?.visible && <SettingsGroup
+            {...settingsGroupData}
+            onClose={() => setSettingsGroupData(undefined)}
+            onChange={() => {
+                getCorpUser.refresh();
+                setSettingsGroupData(undefined)
+                setselectedRows([])
+            }}
+        />}
     </div>
 };
 

+ 131 - 126
src/pages/weComTask/page/corpUserManage/selectCorpUser.tsx

@@ -1,5 +1,5 @@
 import useNewToken from '@/Hook/useNewToken';
-import { App, Button, Input, Popover, Select, Table, Tag, Tooltip } from 'antd';
+import { App, Button, Input, Modal, Select, Table, Tag, Tooltip } from 'antd';
 import React, { useEffect, useState } from 'react';
 import './global.less'
 import { CloseCircleFilled, SearchOutlined } from '@ant-design/icons';
@@ -7,11 +7,15 @@ import { useAjax } from '@/Hook/useAjax';
 import { getAdAccountAllOfMember } from '../../API/global';
 import { getCorpUserApi, getGroupListApi } from '../../API/corpUserManage';
 import { WeTableSelectConfig } from './tableConfig';
+import { RowSelectionType } from 'antd/es/table/interface';
 
 interface Props {
+    type?: RowSelectionType,
+    corpId?: string,
     value?: { corpUserId: string, name: string, corpName: string, corpId: string }[];
     onChange?: (value: { corpUserId: string, name: string, corpName: string, corpId: string }[]) => void;
     placeholder?: React.ReactNode
+    width?: number | string
 }
 
 /**
@@ -19,29 +23,111 @@ interface Props {
  * @param param0 
  * @returns 
  */
-const SelectCorpUser: React.FC<Props> = ({ value = [], onChange, placeholder }) => {
+const SelectCorpUser: React.FC<Props> = ({ value = [], onChange, placeholder, width, ...itr }) => {
 
     /************************************************************/
     const { token } = useNewToken()
-    const { message } = App.useApp()
     const [open, setOpen] = useState<boolean>(false);
-    const [editSelectedRow, setEditSelectedRow] = useState<any[]>([])
-    const [openNew, setOpenNew] = useState<number>(0);
+    /************************************************************/
+
+    return <>
+        <div
+            className='selectCorpUser'
+            style={{
+                border: `1px solid ${token.colorBorder}`,
+                padding: `0px ${token.paddingXS}px`,
+                borderRadius: token.borderRadius,
+                height: token.controlHeight,
+                width: width || '100%',
+            }}
+            onClick={() => setOpen(true)}
+        >
+            <div className='selectCorpUserContent'>
+                {(value && value?.length > 0) ? <>
+                    <Tag
+                        closable
+                        onClick={(e) => e.stopPropagation()}
+                        onClose={(e) => {
+                            e.preventDefault();
+                            onChange(value?.filter(item => item.corpUserId !== value?.[0]?.corpUserId))
+                        }}
+                    >
+                        {value?.[0]?.name || value?.[0]?.corpUserId}
+                    </Tag>
+                    {value?.length > 1 && <Tooltip
+                        color="#FFF"
+                        title={<span style={{ color: '#000' }}>
+                            {value?.filter((_, index) => index !== 0)?.map((item) => <Tag
+                                key={item.corpUserId}
+                                closable
+                                onClick={(e) => e.stopPropagation()}
+                                onClose={(e) => {
+                                    e.stopPropagation()
+                                    onChange(value?.filter(item1 => item1.corpUserId !== item.corpUserId))
+                                }}
+                            >{item.name || item?.corpUserId}</Tag>)}</span>
+                        }
+                    >
+                        <Tag>+{value.length - 1} ...</Tag>
+                    </Tooltip>}
+                </> : <span style={{ color: token.colorTextDisabled }}>{placeholder || '请选择客服号'}</span>}
+            </div>
+            {(value && value?.length > 0) && <div className='selectCorpUserIcon'>
+                <CloseCircleFilled
+                    className='selectCorpUserIconClear'
+                    style={{ fontSize: token.fontSizeIcon, color: token.colorTextSecondary }}
+                    onClick={(e) => {
+                        e.stopPropagation()
+                        onChange?.([])
+                    }}
+                />
+            </div>}
+        </div>
 
+        {open && <SelectCorpUserModal
+            {...itr}
+            value={value}
+            open={open}
+            placeholder={placeholder}
+            onClose={() => setOpen(false)}
+            onChange={(values) => {
+                onChange?.(values)
+                setOpen(false)
+            }}
+        />}
+    </>
+};
+
+interface SelectCorpUserModalProps extends Props {
+    open?: boolean
+    onClose?: () => void
+}
+
+const SelectCorpUserModal: React.FC<SelectCorpUserModalProps> = React.memo(({ open, type = 'checkbox', value, corpId, onClose, onChange, placeholder }) => {
+
+    /*******************************************/
     const [queryForm, setQueryForm] = useState<CORP_USER_ASSIGN_API.GetCorpUserProps>({ pageNum: 1, pageSize: 20, stopUse: false, status: 1 })
     const [queryFormNew, setQueryFormNew] = useState<CORP_USER_ASSIGN_API.GetCorpUserProps>({ pageNum: 1, pageSize: 20, stopUse: false, status: 1 })
+    const [editSelectedRow, setEditSelectedRow] = useState<any[]>([])
+    const { message } = App.useApp()
+
     const allOfMember = useAjax(() => getAdAccountAllOfMember())
     const getCorpUser = useAjax((params) => getCorpUserApi(params))
     const getGroupList = useAjax(() => getGroupListApi())
-    /************************************************************/
+    /*******************************************/
+
+    useEffect(() => {
+        if (corpId) {
+            setQueryFormNew({ ...queryFormNew, corpIds: corpId as any })
+            setQueryForm({ ...queryForm, corpIds: corpId as any })
+        }
+    }, [corpId])
 
     useEffect(() => {
-        if (open && !openNew) {
-            setOpenNew(1)
-            console.log('editSelectedRow')
+        if (open) {
             setEditSelectedRow(value?.length ? [...value] : [])
         }
-    }, [open, value, openNew])
+    }, [open, value])
 
     useEffect(() => {
         if (open) {
@@ -51,21 +137,23 @@ const SelectCorpUser: React.FC<Props> = ({ value = [], onChange, placeholder })
     }, [open])
 
     useEffect(() => {
-        if (open) {
-            const params = { ...queryForm }
-            if (params?.corpIds) {
-                params.corpIds = (params.corpIds as any).split(/[\s,,\n]/).filter((item: string) => item)
-            } else {
-                delete params?.corpIds
-            }
-            if (params?.corpUserIds) {
-                params.corpUserIds = (params.corpUserIds as any).split(/[\s,,\n]/).filter((item: string) => item)
-            } else {
-                delete params?.corpUserIds
-            }
-            getCorpUser.run(params)
+        const params = { ...queryForm }
+        if (params?.corpIds) {
+            params.corpIds = (params.corpIds as any).split(/[\s,,\n]/).filter((item: string) => item)
+        } else {
+            delete params?.corpIds
+        }
+        if (params?.corpUserIds) {
+            params.corpUserIds = (params.corpUserIds as any).split(/[\s,,\n]/).filter((item: string) => item)
+        } else {
+            delete params?.corpUserIds
         }
-    }, [queryForm, open])
+        if (corpId && !(queryForm?.corpIds?.length > 0)) {
+            return
+        }
+        getCorpUser.run(params)
+
+    }, [queryForm, corpId])
 
     const handleOk = () => {
         if (editSelectedRow.length) {
@@ -73,14 +161,18 @@ const SelectCorpUser: React.FC<Props> = ({ value = [], onChange, placeholder })
         } else {
             message.error('请至少选择一条数据')
         }
-        setOpen(false)
     }
 
-    return <Popover
-        placement="bottomLeft"
-        content={<div className='selectCorpUserBody'>
+    return <Modal
+        title={<strong>{placeholder}</strong>}
+        open={open}
+        width={1050}
+        onCancel={onClose}
+        onOk={handleOk}
+    >
+        <div className='selectCorpUserBody' style={{ width: '100%' }}>
             <div className='selectCorpUserBody_search'>
-                <Input style={{ width: 150 }} onChange={(e) => setQueryFormNew({ ...queryFormNew, corpIds: e.target.value as any })} value={queryFormNew?.corpIds} placeholder="企微ID(多个,,空格换行)" allowClear />
+                <Input style={{ width: 150 }} disabled={!!corpId} onChange={(e) => setQueryFormNew({ ...queryFormNew, corpIds: e.target.value as any })} value={queryFormNew?.corpIds} placeholder="企微ID(多个,,空格换行)" allowClear />
                 <Input style={{ width: 150 }} onChange={(e) => setQueryFormNew({ ...queryFormNew, corpUserName: e.target.value as any })} value={queryFormNew?.corpUserName} placeholder="客服号名称" allowClear />
                 <Input style={{ width: 150 }} onChange={(e) => setQueryFormNew({ ...queryFormNew, corpUserIds: e.target.value as any })} value={queryFormNew?.corpUserIds} placeholder="客服ID(多个,,空格换行)" allowClear />
                 <Select
@@ -140,7 +232,7 @@ const SelectCorpUser: React.FC<Props> = ({ value = [], onChange, placeholder })
                     columns={WeTableSelectConfig()}
                     dataSource={getCorpUser?.data?.data?.records}
                     size='small'
-                    scroll={{ y: 300 }}
+                    scroll={{ y: 250 }}
                     rowKey={'corpUserId'}
                     loading={getCorpUser.loading}
                     pagination={{
@@ -150,7 +242,7 @@ const SelectCorpUser: React.FC<Props> = ({ value = [], onChange, placeholder })
                         showTotal: (total) => `共 ${total} 条数据`,
                         showSizeChanger: true
                     }}
-                    rowSelection={{
+                    rowSelection={type === 'checkbox' ? {
                         type: 'checkbox',
                         selectedRowKeys: editSelectedRow?.map(item => item.corpUserId),
                         onSelect: (record: { corpUserId: any; }, selected: any) => {
@@ -168,107 +260,20 @@ const SelectCorpUser: React.FC<Props> = ({ value = [], onChange, placeholder })
                                 setEditSelectedRow(newArr)
                             }
                         }
+                    } : {
+                        type: 'radio',
+                        selectedRowKeys: editSelectedRow?.map(item => item.corpUserId),
+                        onChange(_, selectedRows) {
+                            setEditSelectedRow(selectedRows)
+                        },
                     }}
                     onChange={(pagination) => {
                         setQueryForm({ ...queryForm, pageNum: pagination.current || 1, pageSize: pagination.pageSize || 20 })
                     }}
                 />
             </div>
-            <div className='selectCorpUserBody_footer'>
-                <Button onClick={() => { setOpen(false) }}>取消</Button>
-                <Button type='primary' onClick={handleOk}>确定</Button>
-            </div>
-        </div>}
-        arrow={false}
-        open={open}
-        trigger={'click'}
-        onOpenChange={(e) => {
-            setOpen(e)
-            setOpenNew(0)
-        }}
-    >
-        <div
-            className='selectCorpUser'
-            style={{
-                border: `1px solid ${token.colorBorder}`,
-                padding: `0px ${token.paddingXS}px`,
-                borderRadius: token.borderRadius,
-                height: token.controlHeight,
-            }}
-        >
-            <div className='selectCorpUserContent'>
-                {open ? <>
-                    {(editSelectedRow && editSelectedRow?.length > 0) ? <>
-                        <Tag
-                            closable
-                            onClose={(e) => {
-                                e.preventDefault();
-                                setEditSelectedRow(editSelectedRow?.filter(item => item.corpUserId !== editSelectedRow?.[0]?.corpUserId))
-                            }}
-                        >
-                            {editSelectedRow?.[0]?.name || editSelectedRow?.[0]?.corpUserId}
-                        </Tag>
-                        {editSelectedRow?.length > 1 && <Tooltip
-                            color="#FFF"
-                            title={<span style={{ color: '#000' }}>
-                                {editSelectedRow?.filter((_, index) => index !== 0)?.map((item) => <Tag
-                                    key={item.corpUserId}
-                                    closable
-                                    onClose={(e) => {
-                                        e.stopPropagation()
-                                        setEditSelectedRow(editSelectedRow?.filter(item1 => item1.corpUserId !== item.corpUserId))
-                                    }}
-                                >{item.name || item?.corpUserId}</Tag>)}</span>
-                            }
-                        >
-                            <Tag>+{editSelectedRow.length - 1} ...</Tag>
-                        </Tooltip>}
-                    </> : <span style={{ color: token.colorTextDisabled }}>{placeholder || '请选择客服号'}</span>}
-                </> : <>
-                    {(value && value?.length > 0) ? <>
-                        <Tag
-                            closable
-                            onClose={(e) => {
-                                e.preventDefault();
-                                onChange(value?.filter(item => item.corpUserId !== value?.[0]?.corpUserId))
-                            }}
-                        >
-                            {value?.[0]?.name || value?.[0]?.corpUserId}
-                        </Tag>
-                        {value?.length > 1 && <Tooltip
-                            color="#FFF"
-                            title={<span style={{ color: '#000' }}>
-                                {value?.filter((_, index) => index !== 0)?.map((item) => <Tag
-                                    key={item.corpUserId}
-                                    closable
-                                    onClose={(e) => {
-                                        e.stopPropagation()
-                                        onChange(value?.filter(item1 => item1.corpUserId !== item.corpUserId))
-                                    }}
-                                >{item.name || item?.corpUserId}</Tag>)}</span>
-                            }
-                        >
-                            <Tag>+{value.length - 1} ...</Tag>
-                        </Tooltip>}
-                    </> : <span style={{ color: token.colorTextDisabled }}>{placeholder || '请选择客服号'}</span>}
-                </>}
-            </div>
-            {((value && value?.length > 0) || (editSelectedRow && editSelectedRow?.length > 0)) && <div className='selectCorpUserIcon'>
-                <CloseCircleFilled
-                    className='selectCorpUserIconClear'
-                    style={{ fontSize: token.fontSizeIcon, color: token.colorTextSecondary }}
-                    onClick={(e) => {
-                        e.stopPropagation()
-                        if (open) {
-                            setEditSelectedRow([])
-                        } else {
-                            onChange?.([])
-                        }
-                    }}
-                />
-            </div>}
         </div>
-    </Popover>;
-};
+    </Modal>
+});
 
 export default React.memo(SelectCorpUser);

+ 92 - 0
src/pages/weComTask/page/corpUserManage/selectCorpUserGroup.tsx

@@ -0,0 +1,92 @@
+import useNewToken from '@/Hook/useNewToken';
+import React, { useEffect, useState } from 'react';
+import './global.less'
+import SelectCorpUserGroupModal from './selectCorpUserGroupModal';
+import { Tag, Tooltip } from 'antd';
+
+
+export type CorpUserProps = { corpUserId: string, name: string, corpName: string, corpId: string }
+interface SelectCorpUserGroupProps {
+    value?: TASK_CREATE.CorpUserGroupProps[];
+    onChange?: (value?: TASK_CREATE.CorpUserGroupProps[]) => void;
+    placeholder?: React.ReactNode
+}
+
+/**
+ * 分组选择企微账号
+ * @param param0 
+ * @returns 
+ */
+const SelectCorpUserGroup: React.FC<SelectCorpUserGroupProps> = ({ placeholder, value = [{ corpUsers: [] }], onChange }) => {
+
+    /************************************************************/
+    const { token } = useNewToken()
+    const [visible, setVisible] = useState<boolean>(false);
+    const [corpUserList, setCorpUserList] = useState<TASK_CREATE.CorpUserProps[]>([])
+    /************************************************************/
+
+    useEffect(() => {
+        const newCorpUserList = value?.map(item => item.corpUsers).flat(1)
+        if (JSON.stringify(newCorpUserList) !== JSON.stringify(corpUserList)) {
+            setCorpUserList(newCorpUserList)
+        }
+    }, [value, corpUserList])
+
+    return <>
+        <div
+            className='selectCorpUser'
+            style={{
+                border: `1px solid ${token.colorBorder}`,
+                padding: `0px ${token.paddingXS}px`,
+                borderRadius: token.borderRadius,
+                height: token.controlHeight,
+            }}
+            onClick={() => {
+                setVisible(true)
+            }}
+        >
+            <div className='selectCorpUserContent'>
+                {(corpUserList && corpUserList?.length > 0) ? <>
+                    <Tag
+                        closable
+                        onClose={(e) => {
+                            e.preventDefault();
+                            const newData = value?.map(item => ({ ...item, corpUsers: item.corpUsers?.filter(item => item.corpUserId !== corpUserList?.[0]?.corpUserId) }))?.filter(item => item?.corpUsers?.length > 0)
+                            onChange(newData)
+                        }}
+                    >
+                        {corpUserList?.[0]?.name || corpUserList?.[0]?.corpUserId}
+                    </Tag>
+                    {corpUserList?.length > 1 && <Tooltip
+                        color="#FFF"
+                        title={<span style={{ color: '#000' }}>
+                            {corpUserList?.filter((_, index) => index !== 0)?.map((item) => <Tag
+                                key={item.corpUserId}
+                                closable
+                                onClose={(e) => {
+                                    e.stopPropagation()
+                                    const newData = value?.map(ii => ({ ...ii, corpUsers: ii.corpUsers?.filter(i => i.corpUserId !== item.corpUserId) }))?.filter(item => item?.corpUsers?.length > 0)
+                                    onChange(newData)
+                                }}
+                            >{item.name || item?.corpUserId}</Tag>)}</span>
+                        }
+                    >
+                        <Tag>+{corpUserList.length - 1} ...</Tag>
+                    </Tooltip>}
+                </> : <span style={{ color: token.colorTextDisabled }}>{placeholder || '请选择客服号'}</span>}
+            </div>
+        </div>
+
+        {visible && <SelectCorpUserGroupModal
+            visible={visible}
+            value={value}
+            onClose={() => setVisible(false)}
+            onChange={(value) => {
+                setVisible(false)
+                onChange?.(value)
+            }}
+        />}
+    </>
+};
+
+export default React.memo(SelectCorpUserGroup);

+ 288 - 0
src/pages/weComTask/page/corpUserManage/selectCorpUserGroupModal.tsx

@@ -0,0 +1,288 @@
+import { App, Button, Input, Modal, Select, Space, Table, Typography } from "antd";
+import { CorpUserProps } from "./selectCorpUserGroup";
+import { useEffect, useState } from "react";
+import style from '../../components/selectExternalAccount/index.less';
+import { CheckOutlined, SearchOutlined, CloseOutlined } from '@ant-design/icons';
+import React from "react";
+import { useAjax } from "@/Hook/useAjax";
+import { getAdAccountAllOfMember } from "@/API/global";
+import { getCorpUserApi, getGroupListApi } from "../../API/corpUserManage";
+import { WeTableSelectConfig } from "./tableConfig";
+import { getBindMpListApi } from "../../API/corpUserAssign";
+const { Text, Title } = Typography;
+
+
+interface Props {
+    visible?: boolean;
+    onClose?: () => void;
+    value?: TASK_CREATE.CorpUserGroupProps[];
+    onChange?: (value?: TASK_CREATE.CorpUserGroupProps[]) => void;
+}
+const SelectCorpUserGroupModal: React.FC<Props> = ({ visible, onClose, value, onChange }) => {
+
+    /*****************************************/
+    const { message } = App.useApp()
+    const [data, setData] = useState<TASK_CREATE.CorpUserGroupProps[]>(value)
+    const [selectAdz, setSelectAdz] = useState<number>(1)   // 选择广告主
+
+    const [queryForm, setQueryForm] = useState<CORP_USER_ASSIGN_API.GetCorpUserProps>({ pageNum: 1, pageSize: 20, stopUse: false, status: 1, corpIds: value?.[0]?.corpUsers?.[0]?.corpId as any })
+    const [queryFormNew, setQueryFormNew] = useState<CORP_USER_ASSIGN_API.GetCorpUserProps>({ pageNum: 1, pageSize: 20, stopUse: false, status: 1, corpIds: value?.[0]?.corpUsers?.[0]?.corpId as any })
+
+    const allOfMember = useAjax(() => getAdAccountAllOfMember())
+    const getCorpUser = useAjax((params) => getCorpUserApi(params))
+    const getGroupList = useAjax(() => getGroupListApi())
+    const getBindMpList = useAjax(() => getBindMpListApi())
+    /*****************************************/
+
+    useEffect(() => {
+        allOfMember.run()
+        getGroupList.run()
+        getBindMpList.run()
+    }, [])
+
+    useEffect(() => {
+        const params = { ...queryForm }
+        if (params?.corpIds) {
+            params.corpIds = (params.corpIds as any).split(/[\s,,\n]/).filter((item: string) => item)
+        } else {
+            delete params?.corpIds
+        }
+        if (params?.corpUserIds) {
+            params.corpUserIds = (params.corpUserIds as any).split(/[\s,,\n]/).filter((item: string) => item)
+        } else {
+            delete params?.corpUserIds
+        }
+        getCorpUser.run(params)
+    }, [queryForm])
+
+    const handleOk = () => {
+        if (data?.every(item => item?.corpUsers?.length)) {
+            onChange?.(data)
+        } else {
+            message.error('请选择客服号')
+        }
+    }
+
+    const handleSelectAdz = (value: number, data: TASK_CREATE.CorpUserGroupProps[], isSd: boolean) => {
+        if (value === selectAdz && isSd) {
+            return
+        }
+        const nowCorpUsers = data[value - 1]?.corpUsers
+        const newQueryForm: any = { ...queryForm }
+        if (nowCorpUsers?.length > 0) {
+            newQueryForm.corpIds = nowCorpUsers[0]?.corpId
+            if (nowCorpUsers?.[0]?.mpAccountId) {
+                newQueryForm.mpAccountId = nowCorpUsers[0]?.mpAccountId
+            } else {
+                delete newQueryForm?.mpAccountId
+            }
+        } else {
+            delete newQueryForm?.corpIds
+            delete newQueryForm?.mpAccountId
+        }
+        setQueryForm(newQueryForm)
+        setQueryFormNew(newQueryForm)
+        setSelectAdz(value)
+    }
+
+    return <Modal
+        title={<strong>选择客服号</strong>}
+        open={visible}
+        onCancel={onClose}
+        onOk={handleOk}
+        width={1100}
+        className={`${style.SelectPackage}`}
+        styles={{
+            body: {
+                padding: '0 10px 0 10px'
+            }
+        }}
+    >
+        <div className={style.content}>
+            <div className={style.left} style={{ width: 180 }}>
+                <div className={style.leftTitle}>
+                    <h4 className={style.title}>客服组</h4>
+                    <a onClick={() => {
+                        setData(d => ([...d, { corpUsers: [] }]))
+                    }}>新增组</a>
+                </div>
+                <div className={style.accountIdList}>
+                    {data?.map((item, index) => {
+                        const corpUserList = data[index]?.corpUsers || []
+                        return <div key={index} onClick={() => { handleSelectAdz(index + 1, data, true) }} className={`${style.accItem} ${selectAdz === index + 1 && style.select} `}>
+                            <div><Text ellipsis={{ tooltip: true }}>客服组{index + 1}</Text></div>
+                            {corpUserList?.length > 0 && <CheckOutlined style={{ color: '#1890ff' }} />}
+                        </div>
+                    })}
+                </div>
+            </div>
+            <div className={style.right}>
+                <Space style={{ marginBottom: 10 }} align="end" size={5} wrap>
+                    <Input style={{ width: 150 }} onChange={(e) => setQueryFormNew({ ...queryFormNew, corpIds: e.target.value as any })} value={queryFormNew?.corpIds} placeholder="企微ID(多个,,空格换行)" allowClear />
+                    <Input style={{ width: 150 }} onChange={(e) => setQueryFormNew({ ...queryFormNew, corpUserName: e.target.value as any })} value={queryFormNew?.corpUserName} placeholder="客服号名称" allowClear />
+                    <Input style={{ width: 150 }} onChange={(e) => setQueryFormNew({ ...queryFormNew, corpUserIds: e.target.value as any })} value={queryFormNew?.corpUserIds} placeholder="客服ID(多个,,空格换行)" allowClear />
+                    <Select
+                        value={queryFormNew?.groupId}
+                        onChange={(e) => setQueryFormNew({ ...queryFormNew, groupId: e })}
+                        showSearch
+                        style={{ width: 100 }}
+                        placeholder="分组"
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                        }
+                        allowClear
+                        options={getGroupList?.data?.data?.map((item: any) => ({ label: item.groupName, value: item.groupId }))}
+                    />
+                    <Select
+                        value={queryFormNew?.operUserId}
+                        onChange={(e) => setQueryFormNew({ ...queryFormNew, operUserId: e })}
+                        showSearch
+                        style={{ width: 100 }}
+                        placeholder="运营"
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                        }
+                        allowClear
+                        options={allOfMember?.data?.data?.map((item: any) => ({ label: item.nickname, value: item.userId }))}
+                    />
+                    <Select
+                        value={queryFormNew?.mpAccountId}
+                        onChange={(e) => setQueryFormNew({ ...queryFormNew, mpAccountId: e })}
+                        showSearch
+                        style={{ width: 100 }}
+                        placeholder="公众号"
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                        }
+                        allowClear
+                        options={getBindMpList?.data?.data?.map((item: any) => ({ label: item.name, value: item.id }))}
+                    />
+                    <Select
+                        value={queryFormNew?.groupOwner}
+                        onChange={(e) => setQueryFormNew({ ...queryFormNew, groupOwner: e })}
+                        showSearch
+                        style={{ width: 100 }}
+                        placeholder="是否群主号"
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                        }
+                        allowClear
+                        options={[{ label: '是', value: 1 }, { label: '否', value: 0 }]}
+                    />
+                    <Select
+                        value={queryFormNew?.machine}
+                        onChange={(e) => setQueryFormNew({ ...queryFormNew, machine: e })}
+                        showSearch
+                        style={{ width: 100 }}
+                        placeholder="是否机器号"
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                        }
+                        allowClear
+                        options={[{ label: '是', value: 1 }, { label: '否', value: 0 }]}
+                    />
+                    <Button type="primary" onClick={() => {
+                        setQueryForm({ ...queryFormNew, pageNum: 1 })
+                    }} loading={getCorpUser.loading} icon={<SearchOutlined />}>搜索</Button>
+                </Space>
+
+                <Table
+                    columns={WeTableSelectConfig()}
+                    dataSource={getCorpUser?.data?.data?.records}
+                    size='small'
+                    scroll={{ y: 300 }}
+                    rowKey={'id'}
+                    loading={getCorpUser.loading}
+                    pagination={{
+                        current: getCorpUser?.data?.data?.current || 1,
+                        pageSize: getCorpUser?.data?.data?.size || 20,
+                        total: getCorpUser?.data?.data?.total,
+                        showTotal: (total) => `共 ${total} 条数据`,
+                        showSizeChanger: true
+                    }}
+                    rowSelection={{
+                        type: 'checkbox',
+                        selectedRowKeys: data?.[selectAdz - 1]?.corpUsers?.map(item => item.id),
+                        getCheckboxProps: (record: any) => ({
+                            disabled: data.filter((_, i) => i !== selectAdz - 1)?.map(item => item.corpUsers).flat(1)?.map(item => item.id).includes(record.id) ||
+                                (data?.[selectAdz - 1]?.corpUsers?.length > 0 ? data?.[selectAdz - 1]?.corpUsers?.[0]?.corpId !== record.corpId || (data?.[selectAdz - 1]?.corpUsers?.[0]?.mpAccountId ? data?.[selectAdz - 1]?.corpUsers?.[0]?.mpAccountId !== record.mpAccountId : record.mpAccountId) : false)
+                        }),
+                        onSelect: (record: { id: string }, selected: boolean) => {
+                            const newData = JSON.parse(JSON.stringify(data))
+                            let dataIten = newData[selectAdz - 1]?.corpUsers || []
+                            if (selected) {
+                                dataIten.push({ ...record })
+                            } else {
+                                dataIten = dataIten.filter((item: { id: string }) => item.id !== record.id)
+                            }
+                            newData[selectAdz - 1].corpUsers = dataIten
+                            setData(newData)
+                        },
+                        onSelectAll: (selected: boolean, _, changeRows: { id: string, corpId: string, mpAccountId?: string }[]) => {
+                            const newData = JSON.parse(JSON.stringify(data))
+                            const dataIten = newData[selectAdz - 1]?.corpUsers || []
+                            if (selected) {
+                                const newSelectAccData = [...dataIten]
+                                if (newSelectAccData?.length === 0) {
+                                    const { mpAccountId, corpId } = changeRows[0]
+                                    changeRows = changeRows.filter(item => item.corpId === corpId && item?.mpAccountId === mpAccountId)
+                                }
+                                changeRows.forEach((item: { id: string }) => {
+                                    const index = newSelectAccData.findIndex((ite: { id: string }) => {
+                                        return ite.id === item.id
+                                    })
+                                    if (index === -1) {
+                                        const data: any = { ...item }
+                                        newSelectAccData.push(data)
+                                    }
+                                })
+                                newData[selectAdz - 1].corpUsers = newSelectAccData
+                            } else {
+                                const newSelectAccData = dataIten.filter((item: { id: string }) => {
+                                    const index = changeRows.findIndex((ite: { id: string }) => ite.id === item.id)
+                                    if (index !== -1) {
+                                        return false
+                                    } else {
+                                        return true
+                                    }
+                                })
+                                newData[selectAdz - 1].corpUsers = newSelectAccData
+                            }
+                            setData(newData)
+                        }
+                    }}
+                    onChange={(pagination) => {
+                        setQueryForm({ ...queryForm, pageNum: pagination.current || 1, pageSize: pagination.pageSize || 20 })
+                    }}
+                />
+            </div>
+
+            <div className={style.center}>
+                <div style={{ margin: '0 0 5px', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
+                    <Title level={5} style={{ margin: 0 }}>已选:{data[selectAdz - 1]?.corpUsers?.length || 0}</Title>
+                    {data.length > 1 && <a style={{ color: 'red' }} onClick={() => {
+                        const newData = data.filter((_, index) => index !== selectAdz - 1)
+                        setData(newData)
+                        if (selectAdz > 1) {
+                            handleSelectAdz(selectAdz - 1, newData, false)
+                        } else {
+                            handleSelectAdz(1, newData, false)
+                        }
+                    }}>删除组</a>}
+                </div>
+                <div className={style.select_content}>
+                    {data[selectAdz - 1]?.corpUsers?.map(item => <div key={item.corpUserId}>
+                        <Text ellipsis={{ tooltip: true }} className={style.marketingAssetName}>{item.name}({item?.corpName})</Text>
+                        <CloseOutlined className={style.close} onClick={() => {
+                            let newData = JSON.parse(JSON.stringify(data))
+                            newData[selectAdz - 1].corpUsers = newData[selectAdz - 1]?.corpUsers?.filter((i: any) => i?.corpUserId !== item.corpUserId)
+                            setData(newData)
+                        }} />
+                    </div>)}
+                </div>
+            </div>
+        </div>
+    </Modal>
+}
+
+export default React.memo(SelectCorpUserGroupModal);

+ 73 - 0
src/pages/weComTask/page/corpUserManage/settingsGroup.tsx

@@ -0,0 +1,73 @@
+import { App, Modal, Select } from 'antd';
+import React, { useEffect, useState } from 'react';
+import { addAccountToGroupApi, delAccountToGroupApi, getGroupListApi } from '../../API/corpUserManage';
+import { useAjax } from '@/Hook/useAjax';
+
+interface SettingsGroupProps {
+    data: any[],
+    type: 'add' | 'del',
+    visible?: boolean,
+    onClose?: () => void,
+    onChange?: () => void
+}
+
+/**
+ * 批量设置分组
+ * @returns 
+ */
+const SettingsGroup: React.FC<SettingsGroupProps> = ({ data, type, visible, onClose, onChange }) => {
+
+    /*******************************************/
+    const { message } = App.useApp()
+    const [groupId, setGroupId] = useState<number>();
+    const getGroupList = useAjax(() => getGroupListApi())
+    const addAccountToGroup = useAjax((params) => addAccountToGroupApi(params))
+    const delAccountToGroup = useAjax((params) => delAccountToGroupApi(params))
+    /*******************************************/
+
+    useEffect(() => {
+        getGroupList.run()
+    }, [])
+
+    const handleOk = () => {
+        if (groupId) {
+            if (type === 'add') {
+                addAccountToGroup.run({ currGroupId: groupId, corpUserList: data.map(item => ({ corpId: item.corpId, corpUserId: item.corpUserId })) }).then(res => {
+                    onChange?.();
+                    message.success('设置分组成功')
+                })
+            } else {
+                delAccountToGroup.run({ currGroupId: groupId, corpUserList: data.map(item => ({ corpId: item.corpId, corpUserId: item.corpUserId })) }).then(res => {
+                    onChange?.();
+                    message.success('移除分组成功')
+                })
+            }
+
+        } else {
+            message.error('请选择分组')
+        }
+    }
+
+    return <Modal
+        title={<strong>{type === 'add' ? '添加到分组' : '移除分组'}</strong>}
+        open={visible}
+        onCancel={onClose}
+        onOk={handleOk}
+        confirmLoading={addAccountToGroup.loading || delAccountToGroup.loading}
+    >
+        <Select
+            value={groupId}
+            onChange={(e) => setGroupId(e)}
+            showSearch
+            placeholder="请选择分组"
+            filterOption={(input, option) =>
+                ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+            }
+            style={{ width: '100%' }}
+            allowClear
+            options={getGroupList?.data?.data?.map((item: { groupName: string, groupId: number }) => ({ label: item.groupName, value: item.groupId }))}
+        />
+    </Modal>
+};
+
+export default SettingsGroup;

+ 41 - 0
src/pages/weComTask/page/corpUserManage/tableConfig.tsx

@@ -71,6 +71,26 @@ export function WeTableConfig(
                 return <span>{a?.nickName || '--'}</span>
             }
         },
+        {
+            title: '公众号',
+            dataIndex: 'name',
+            key: 'name',
+            align: 'center',
+            width: 80,
+            render: (_, record) => {
+                return <span>{record?.mpAccountInfo?.name || '--'}</span>
+            }
+        },
+        {
+            title: '书城',
+            dataIndex: 'platformName',
+            key: 'platformName',
+            align: 'center',
+            width: 80,
+            render: (_, record) => {
+                return <span>{record?.mpAccountInfo?.platformName || '--'}</span>
+            }
+        },
         {
             title: '操作',
             dataIndex: 'cz',
@@ -122,6 +142,7 @@ export function WeTableSelectConfig(): ColumnsType<any> {
             key: 'name',
             align: 'center',
             width: 80,
+            fixed: 'left'
         },
         {
             title: '所属企业',
@@ -161,6 +182,26 @@ export function WeTableSelectConfig(): ColumnsType<any> {
             render: (a: { nickName: string }) => {
                 return <span>{a?.nickName || '--'}</span>
             }
+        },
+        {
+            title: '公众号',
+            dataIndex: 'name',
+            key: 'name',
+            align: 'center',
+            width: 80,
+            render: (_, record) => {
+                return <span>{record?.mpAccountInfo?.name || '--'}</span>
+            }
+        },
+        {
+            title: '书城',
+            dataIndex: 'platformName',
+            key: 'platformName',
+            align: 'center',
+            width: 80,
+            render: (_, record) => {
+                return <span>{record?.mpAccountInfo?.platformName || '--'}</span>
+            }
         }
     ]
     return tableDfixed(arr)

+ 493 - 0
src/pages/weComTask/page/groupChat/create/components/groupUser/addGroupObject.tsx

@@ -0,0 +1,493 @@
+import { App, Badge, Button, Card, Empty, Form, Input, InputNumber, Modal, Popover, Radio, Select, Space, Table, Typography } from 'antd';
+import React, { useEffect, useState } from 'react';
+import { PlusOutlined, QuestionCircleFilled } from '@ant-design/icons';
+import FilterUser from '@/pages/weComTask/components/filterUser';
+import MindTags from '@/pages/weComTask/components/mindTags';
+import { getBindMpListApi } from '@/pages/weComTask/API/corpUserAssign';
+import { useAjax } from '@/Hook/useAjax';
+import FilterUserTooltip from '@/pages/weComTask/components/filterUser/filterUserTooltip';
+import { businessPlanData } from '@/pages/weComTask/page/businessPlan/create/const';
+import { ColumnsType } from 'antd/es/table';
+const { Text, Paragraph } = Typography;
+
+/**
+ * 进群对象新建组件
+ * @param param0 
+ * @returns 
+ */
+const AddGroupObject: React.FC<GROUP_CHAT_CREATE.AddGroupObjectProps> = ({ value = [], onChange, onCopy, bookPlatForm, index, strategyList, bookList }) => {
+
+    /******************************************/
+    const { message } = App.useApp();
+    const [visible, setVisible] = useState<boolean>(false)
+    const [initialValues, setInitialValues] = useState<any>()
+    const [copyData, setCopyData] = useState<{ visible?: boolean, data?: any }>()
+    const [copyIndex, setCopyIndex] = useState<number>()
+    /******************************************/
+
+    return <>
+
+        {value?.length > 0 ? <ShowGroupUserTable
+            value={value}
+            bookList={bookList}
+            bookPlatForm={bookPlatForm}
+            handleDelete={(id) => {
+                onChange?.(value.filter(item => item.id !== id))
+            }}
+            handleEdit={(record) => {
+                setInitialValues(record)
+                setVisible(true)
+            }}
+            handleCopy={(record) => {
+                const newValue = { ...record, id: Date.now(), groupObjectName: `copy策略${index + 1}_` + record.groupObjectName }
+                setCopyData({ visible: true, data: newValue })
+            }}
+        /> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description='暂无群配置' />}
+        <Button type="dashed" style={{ width: '100%' }} block icon={<PlusOutlined />} onClick={() => setVisible(true)}>新建群配置</Button>
+        {visible && <AddGroupObjectModal
+            visible={visible}
+            initialValues={initialValues}
+            onChange={(values) => {
+                let newValue = JSON.parse(JSON.stringify(value))
+                if (initialValues?.id) {
+                    newValue = newValue.map(item => {
+                        if (item.id === initialValues.id)
+                            return values
+                        return item
+                    })
+                } else {
+                    newValue.push(values)
+                }
+                onChange?.(newValue)
+                setVisible(false)
+                setInitialValues(undefined)
+            }}
+            onClose={() => {
+                setVisible(false)
+                setInitialValues(undefined)
+            }}
+        />}
+        {copyData?.visible && <Modal
+            title={<strong>复制至</strong>}
+            open={copyData?.visible}
+            onCancel={() => setCopyData(undefined)}
+            onOk={() => {
+                if (copyIndex) {
+                    onCopy?.(copyData.data, copyIndex)
+                    setCopyData(undefined)
+                    setCopyIndex(undefined)
+                    setVisible(false)
+                } else {
+                    message.error('清选择复制到哪')
+                }
+            }}
+        >
+            <Select
+                style={{ width: '100%' }}
+                onChange={(e) => {
+                    setCopyIndex(e)
+                }}
+                placeholder="请选择复制到哪"
+                value={copyIndex}
+                options={strategyList.map((item, index) => ({ value: index + 1, label: item.strategyName }))}
+            />
+        </Modal>}
+    </>
+};
+
+export const ShowGroupUserTable: React.FC<GROUP_CHAT_CREATE.ShowGroupUserTableProps> = React.memo(({ bookList, bookPlatForm, value, handleEdit, handleDelete, handleCopy, isPreview }) => {
+
+    const columns: ColumnsType<any> = [
+        {
+            title: '群名称',
+            dataIndex: 'groupObjectName',
+            key: 'groupObjectName',
+            width: 120,
+            ellipsis: true,
+            fixed: 'left'
+        },
+        {
+            title: '是否开启已有旧群聊补缺',
+            dataIndex: 'isRepair',
+            key: 'isRepair',
+            width: 150,
+            ellipsis: true,
+            render(value, record, index) {
+                return value ? <Space>
+                    <Badge status="success" text="开启" />
+                    <span>近{record.repairTimes}天</span>
+                </Space> : <Badge status="error" text="关闭" />
+            },
+        },
+        {
+            title: '进群对象',
+            dataIndex: 'externalUserFilter',
+            key: 'externalUserFilter',
+            width: 90,
+            align: 'center',
+            render(value) {
+                return value ? <div style={{ display: 'flex', alignItems: 'center' }}>
+                    <div style={{ flex: '1 0', overflow: 'hidden' }}>
+                        <Text ellipsis>指定</Text>
+                    </div>
+                    <Popover
+                        placement="right"
+                        styles={{ body: { maxWidth: 350, maxHeight: 350, overflow: 'hidden', overflowY: 'auto' } }}
+                        mouseEnterDelay={0.5}
+                        content={<FilterUserTooltip
+                            bookCityList={bookPlatForm?.map(item => ({ label: item.platformName, value: item.platformKey }))}
+                            configName={value?.configName}
+                            data={value?.configContent}
+                        />}
+                    >
+                        <a style={{ color: '#000' }}><QuestionCircleFilled /></a>
+                    </Popover>
+                </div> : '全部'
+            },
+        },
+        {
+            title: '群递增起始编号',
+            dataIndex: 'groupIndex',
+            key: 'groupIndex',
+            width: 60,
+            align: 'center'
+        },
+        {
+            title: '群固定人数',
+            dataIndex: 'groupUserCount',
+            key: 'groupUserCount',
+            width: 50,
+            align: 'center'
+        },
+        {
+            title: '邀请客户进群完毕后客服号是否退群',
+            dataIndex: 'autoOutGroup',
+            key: 'autoOutGroup',
+            width: 85,
+            align: 'center',
+            render(value) {
+                return value ? <span style={{ color: '#1890ff' }}>是</span> : <span style={{ color: 'red' }}>否</span>
+            },
+        },
+        {
+            title: '是否排除已在群的客户',
+            dataIndex: 'excludeInGroup',
+            key: 'excludeInGroup',
+            width: 60,
+            align: 'center',
+            render(value) {
+                return value ? <span style={{ color: '#1890ff' }}>是</span> : <span style={{ color: 'red' }}>否</span>
+            },
+        },
+        {
+            title: '拉群完成后自动删除拉群标签',
+            dataIndex: 'excludeInGroup',
+            key: 'excludeInGroup',
+            width: 70,
+            align: 'center',
+            render(value) {
+                return value ? <span style={{ color: '#1890ff' }}>是</span> : <span style={{ color: 'red' }}>否</span>
+            },
+        },
+        {
+            title: '是否开启失败人群重试',
+            dataIndex: 'isRetry',
+            key: 'isRetry',
+            width: 70,
+            align: 'center',
+            render(value) {
+                return value ? <span style={{ color: '#1890ff' }}>是</span> : <span style={{ color: 'red' }}>否</span>
+            },
+        },
+        {
+            title: '群聊关联公众号',
+            dataIndex: 'weChatAppid',
+            key: 'weChatAppid',
+            width: 100,
+            align: 'center',
+            ellipsis: true,
+            render(value) {
+                return value || '--'
+            },
+        },
+        {
+            title: '拉群完成后群聊备注',
+            dataIndex: 'remark',
+            key: 'remark',
+            width: 140,
+            ellipsis: true,
+            render(value) {
+                return value || '--'
+            },
+        },
+        {
+            title: '拉群完成后群聊智能标签',
+            dataIndex: 'tagDTO',
+            key: 'tagDTO',
+            width: 200,
+            ellipsis: true,
+            render(value) {
+                return value && Object.keys(value)?.length > 0 ?
+                    <Paragraph ellipsis style={{ margin: 0 }}>{Object.keys(value).map(key => {
+                        if (key === 'business' && value[key]) {
+                            return `业务(来源渠道):${businessPlanData.find(i => i.value === value.business)?.label || '<空>'}`
+                        } else if (key === 'bookCity' && value[key]) {
+                            return `书城(来源渠道):${bookPlatForm.find(i => i.id === value.bookCity)?.platformName || '<空>'}`
+                        } else if (key === 'product' && value[key]) {
+                            return `产品(来源渠道):${bookList.find(i => i.id === value.product)?.bookName || '<空>'}`
+                        }
+                        return ''
+                    }).join('、')}</Paragraph> : '--'
+            },
+        },
+        {
+            title: '客服消息',
+            dataIndex: 'groupSendMsg',
+            key: 'groupSendMsg',
+            width: 140,
+            ellipsis: true,
+            render(value) {
+                return value ? value : '--'
+            }
+        },
+        {
+            title: '操作',
+            dataIndex: 'cz',
+            key: 'cz',
+            width: 100,
+            fixed: 'right',
+            align: 'center',
+            render(_, record) {
+                return <Space>
+                    <a onClick={() => {
+                        handleCopy?.(record)
+                    }}>复制</a>
+                    <a onClick={() => {
+                        handleEdit?.(record)
+                    }}>修改</a>
+                    <a style={{ color: 'red' }} onClick={() => {
+                        handleDelete?.(record.id)
+                    }}>删除</a>
+                </Space>
+            },
+        }
+    ]
+    if (isPreview) {
+        columns.pop()
+    }
+
+    return <Table
+        size='small'
+        bordered
+        rowKey={'id'}
+        dataSource={value}
+        scroll={{ y: 1000 }}
+        columns={columns}
+    />
+})
+
+const AddGroupObjectModal: React.FC<GROUP_CHAT_CREATE.AddGroupObjectModalProps> = React.memo(({ visible, onChange, onClose, initialValues }) => {
+
+    /******************************************/
+    const { message } = App.useApp();
+    const [form] = Form.useForm();
+    const externalUserType = Form.useWatch('externalUserType', form);
+    const isRepair = Form.useWatch('isRepair', form);
+
+    const getBindMpList = useAjax(() => getBindMpListApi())
+    /******************************************/
+
+    useEffect(() => {
+        getBindMpList.run()
+    }, [])
+
+    const handleOk = () => {
+        form.validateFields().then((values) => {
+            if (initialValues?.id) {
+                values.id = initialValues.id
+            } else {
+                values.id = Date.now();
+            }
+            console.log(values)
+            onChange?.(values)
+        }).catch(() => {
+            form.submit()
+        });
+    }
+
+    return <Modal
+        title={<strong>{(initialValues && Object.keys(initialValues).length > 0) ? '修改' : '新建'}群配置</strong>}
+        open={visible}
+        onCancel={onClose}
+        width={600}
+        onOk={handleOk}
+        styles={{ body: { maxHeight: 550, overflowY: 'auto' } }}
+    >
+        <Form
+            form={form}
+            name="addGroupUser"
+            labelAlign='left'
+            labelCol={{ span: 9 }}
+            colon={false}
+            labelWrap
+            scrollToFirstError={{
+                behavior: 'smooth',
+                block: 'center'
+            }}
+            onFinishFailed={({ errorFields }) => {
+                message.error(errorFields?.[0]?.errors?.[0])
+            }}
+            onFinish={handleOk}
+            initialValues={(initialValues && Object.keys(initialValues).length > 0) ? initialValues : { externalUserType: 'specify', isRepair: false, groupIndex: 1, groupUserCount: 37, autoOutGroup: false, excludeInGroup: true, deleteGroupTag: true, isRetry: false }}
+            preserve={true}
+        >
+            <Form.Item
+                name={'groupObjectName'}
+                label={<strong>群名称</strong>}
+                rules={[{ required: true, message: '请输入群名称!' }]}
+            >
+                <Input placeholder='请输入群名称' allowClear />
+            </Form.Item>
+            <Form.Item
+                name={'isRepair'}
+                label={<strong>是否开启已有旧群聊补缺</strong>}
+                tooltip={{ title: '优先将客户补充进已有旧群聊的空缺位置,待旧群满员后再创建新群容纳剩余客户。', placement: 'top' }}
+                rules={[{ required: true, message: '请选择是否开启群补人!' }]}
+            >
+                <Radio.Group options={[{ label: '开启', value: true }, { label: '关闭', value: false }]} />
+            </Form.Item>
+            {isRepair && <>
+                <Form.Item
+                    name={'repairTimes'}
+                    label={<strong>补缺群聊范围</strong>}
+                    rules={[{ required: true, message: '请选择补缺群聊范围!' }]}
+                    tooltip={{ title: '可补缺的群聊范围:在本建群策略内创建的群聊', placement: 'top' }}
+                >
+                    <Select
+                        showSearch
+                        allowClear
+                        placeholder="请选择补缺群聊范围"
+                        filterOption={(input, option) =>
+                            (option?.label as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                        }
+                        options={Array(30).fill('').map((_, index) => ({ label: `近${index + 1}天`, value: index + 1 }))}
+                    />
+                </Form.Item>
+                <Form.Item
+                    name={'groupSendMsg'}
+                    label={<strong>客服消息</strong>}
+                >
+                    <Input.TextArea placeholder="请输入建群成功发送内容" />
+                </Form.Item>
+            </>}
+            <Form.Item
+                label={<strong>进群对象配置</strong>}
+                required
+            >
+                <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
+                    <Form.Item
+                        name={'externalUserType'}
+                        rules={[{ required: true, message: '请选择转移对象!' }]}
+                        noStyle
+                    >
+                        <Radio.Group options={[{ label: '全部', value: 'all' }, { label: '指定', value: 'specify' }]} />
+                    </Form.Item>
+                    {externalUserType === 'specify' && <div style={{ marginTop: 8, width: '100%' }}>
+                        <Form.Item
+                            name={'externalUserFilter'}
+                            rules={[{ required: true, message: '请选择人群包!' }]}
+                            noStyle
+                        >
+                            <FilterUser configType={'USER_GROUP'} />
+                        </Form.Item>
+                    </div>}
+                </div>
+            </Form.Item>
+            <Form.Item
+                name={'groupIndex'}
+                tooltip={{ title: `例如:起始编号为'1'群名为'测试群',再建群后群名为'测试群1'超出群人数的用户将分配到'测试群2'依次递增`, placement: 'top' }}
+                label={<strong>群递增起始编号</strong>}
+                rules={[{ required: true, message: '请输入群递增起始编号!' }]}
+            >
+                <InputNumber placeholder='请输入群递增起始编号' min={1} style={{ width: '100%' }} />
+            </Form.Item>
+            <Form.Item
+                name={'groupUserCount'}
+                tooltip={{ title: `不包括建群人和固定企微号,最大上限数量38`, placement: 'top' }}
+                label={<strong>群固定人数</strong>}
+                rules={[{ required: true, message: '请输入群固定人数!' }]}
+            >
+                <InputNumber placeholder='请输入群固定人数' max={38} min={1} style={{ width: '100%' }} />
+            </Form.Item>
+            <Form.Item
+                name={'autoOutGroup'}
+                label={<strong>邀请客户进群完毕后客服号是否退群</strong>}
+                rules={[{ required: true, message: '请选择是否退群!' }]}
+            >
+                <Radio.Group>
+                    <Radio value={true}>是</Radio>
+                    <Radio value={false}>否</Radio>
+                </Radio.Group>
+            </Form.Item>
+            <Form.Item
+                name={'excludeInGroup'}
+                label={<strong>是否排除已在群的客户</strong>}
+                rules={[{ required: true, message: '请选择是否排除已在群的客户!' }]}
+            >
+                <Radio.Group>
+                    <Radio value={true}>是</Radio>
+                </Radio.Group>
+            </Form.Item>
+            <Form.Item
+                name={'deleteGroupTag'}
+                label={<strong>拉群完成后自动删除拉群标签</strong>}
+                rules={[{ required: true, message: '请选择拉群完成后自动删除拉群标签!' }]}
+            >
+                <Radio.Group>
+                    <Radio value={true}>是</Radio>
+                </Radio.Group>
+            </Form.Item>
+            <Form.Item
+                name={'isRetry'}
+                label={<strong>是否开启失败人群重试</strong>}
+                rules={[{ required: true, message: '请选择是否开启失败人群重试!' }]}
+            >
+                <Radio.Group>
+                    <Radio value={true}>是</Radio>
+                    <Radio value={false}>否</Radio>
+                </Radio.Group>
+            </Form.Item>
+            <Form.Item
+                name={'weChatAppid'}
+                label={<strong>群聊关联公众号</strong>}
+            >
+                <Select
+                    showSearch
+                    allowClear
+                    placeholder="选择公众号"
+                    filterOption={(input, option) =>
+                        (option?.label as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                    }
+                    options={getBindMpList?.data?.data?.map(item => ({ label: item.name, value: item.name + '_' + item.id }))}
+                />
+            </Form.Item>
+            <Form.Item
+                name={'remark'}
+                label={<strong>拉群完成后群聊备注</strong>}
+            >
+                <Input.TextArea placeholder="请输入群聊备注" />
+            </Form.Item>
+
+            <Card title={<strong>拉群完成后群聊智能标签</strong>} style={{ background: '#fff', marginBottom: 10 }} styles={{ body: { padding: '6px 0 6px 16px' } }}>
+                <Form.Item
+                    name={'tagDTO'}
+                    style={{ marginBottom: 0 }}
+                >
+                    <MindTags />
+                </Form.Item>
+            </Card>
+        </Form>
+    </Modal>
+})
+
+export default React.memo(AddGroupObject);

+ 86 - 0
src/pages/weComTask/page/groupChat/create/components/groupUser/index.tsx

@@ -0,0 +1,86 @@
+import React, { useContext, useState } from 'react';
+import style from '../../../../businessPlan/create/index.less'
+import useNewToken from '@/Hook/useNewToken';
+import { App, Button, Empty, Popconfirm } from 'antd';
+import { DispatchGroupChatCreate } from '../..';
+import SettingsGroupUser from './settingsGroupUser';
+import PreviewGroupUser from './previewGroupUser';
+
+
+/**
+ * 进群对象
+ * @returns 
+ */
+const GroupUser: React.FC = () => {
+
+    /*********************************/
+    const { message } = App.useApp()
+    const { token } = useNewToken()
+    const { setSettings, settings, onPreviewReset, bookPlatForm, bookList } = useContext(DispatchGroupChatCreate)!;
+    const [newVisible, setNewVisible] = useState<boolean>(false);
+    /*********************************/
+
+
+    return <>
+        <div className={`${style.settingsBody_content_row}`} style={{ width: '65%' }}>
+            <div className={`${style.settingsBody_content_col}`} style={{ width: '100%' }}>
+                <div className={style.title}>
+                    <span>群</span>
+                    {settings?.strategyDTO?.strategyList?.every(item => item?.groupObjectList?.length > 0) && <Popconfirm
+                        title="确定清空?"
+                        onConfirm={() => {
+                            setSettings({
+                                ...settings,
+                                strategyDTO: {
+                                    ...settings.strategyDTO,
+                                    strategyList: settings.strategyDTO.strategyList.map(item => ({
+                                        ...item,
+                                        groupObjectList: []
+                                    }))
+                                }
+                            });
+                            onPreviewReset();
+                        }}
+                    >
+                        <a style={{ color: 'red' }}>清空</a>
+                    </Popconfirm>}
+                </div>
+                <div className={style.detail}>
+                    <div className={style.detail_title}>群配置</div>
+                    <div className={style.detail_body}>
+                        {settings?.strategyDTO && Object.keys(settings?.strategyDTO).length > 0 ? <PreviewGroupUser
+                            strategyList={settings?.strategyDTO?.strategyList || []}
+                            bookList={bookList}
+                            bookPlatForm={bookPlatForm}
+                        /> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
+                    </div>
+                </div>
+                <div className={style.detail_footer}>
+                    {settings?.strategyDTO && Object.keys(settings?.strategyDTO).length > 0 ?
+                        <Button type="link" style={{ padding: 0, fontSize: 12, color: token.colorPrimary }} size="small" onClick={() => setNewVisible(true)}>编辑</Button> :
+                        <Button type="link" style={{ padding: 0, fontSize: 12, color: token.colorPrimary }} size="small" onClick={() => message.error('请先编辑群聊创建策略')}>编辑</Button>}
+                </div>
+            </div>
+        </div>
+
+        {/* 配置群聊创建进群对象 */}
+        {newVisible && <SettingsGroupUser
+            visible={newVisible}
+            onClose={() => setNewVisible(false)}
+            onChange={(values) => {
+                setSettings({
+                    ...settings,
+                    strategyDTO: {
+                        ...settings.strategyDTO,
+                        ...values
+                    }
+                });
+                onPreviewReset();
+                setNewVisible(false);
+            }}
+            value={settings?.strategyDTO}
+        />}
+    </>
+};
+
+export default React.memo(GroupUser);

+ 48 - 0
src/pages/weComTask/page/groupChat/create/components/groupUser/previewGroupUser.tsx

@@ -0,0 +1,48 @@
+import { Tabs, Typography } from 'antd';
+import React, { useState } from 'react';
+import { ShowGroupUserTable } from './addGroupObject';
+const { Text } = Typography;
+
+interface PreviewGroupUserProps {
+    strategyList: { [x: string]: any }[];
+    bookPlatForm: TASK_CREATE.BookPlatFormProps[]
+    bookList: TASK_CREATE.BookListProps[]
+}
+
+/**
+ * 群配置预览
+ * @param param0 
+ * @returns 
+ */
+const PreviewGroupUser: React.FC<PreviewGroupUserProps> = ({ strategyList, bookList, bookPlatForm }) => {
+
+    /****************************************/
+    const [activeKey, setActiveKey] = useState<string>('1')
+    /****************************************/
+
+    return <Tabs
+        activeKey={activeKey}
+        tabPosition='left'
+        style={{
+            height: '100%',
+            width: '100%'
+        }}
+        onChange={(key) => { setActiveKey(key) }}
+        items={strategyList.map((item, index) => {
+            return {
+                label: <div style={{ maxWidth: 100 }}><Text>{item.strategyName}</Text></div>,
+                key: `${index + 1}`,
+                children: <div style={{ width: '100%', height: 282, overflow: 'hidden', overflowY: 'auto' }}>
+                    <ShowGroupUserTable
+                        value={item.groupObjectList}
+                        bookList={bookList}
+                        bookPlatForm={bookPlatForm}
+                        isPreview={true}
+                    />
+                </div>
+            }
+        })}
+    />;
+};
+
+export default PreviewGroupUser;

+ 189 - 0
src/pages/weComTask/page/groupChat/create/components/groupUser/settingsGroupUser.tsx

@@ -0,0 +1,189 @@
+import { App, Card, Form, Modal, Popover, Space } from "antd";
+import React, { useContext, useEffect, useRef, useState } from "react";
+import '../../../../businessPlan/create/global.less'
+import { QuestionCircleFilled, RightOutlined, DownOutlined } from '@ant-design/icons';
+import PreviewTime from "@/pages/weComTask/components/previewTime";
+import { getAllWxListApi } from "@/pages/weComTask/API/corpUserAssign";
+import { useAjax } from "@/Hook/useAjax";
+import AddGroupObject from "./addGroupObject";
+import { DispatchGroupChatCreate } from "../..";
+import NewSteps from "@/pages/weComTask/components/newSteps";
+
+/**
+ * 群聊创建进群对象配置
+ * @param param0 
+ * @returns 
+ */
+const SettingsGroupUser: React.FC<GROUP_CHAT_CREATE.FoundationProps<any>> = ({ visible, onClose, value, onChange }) => {
+
+
+    /***************************************/
+    const { bookPlatForm, bookList } = useContext(DispatchGroupChatCreate)!;
+    const ref1 = useRef<HTMLDivElement>(null)
+
+    const { message } = App.useApp();
+    const [form] = Form.useForm();
+    const strategyList = Form.useWatch('strategyList', form)
+    const [activeKey, setActiveKey] = useState<number[]>([])
+    const [stepsList, setStepsList] = useState<any>([])
+
+    const getAccountList = useAjax(() => getAllWxListApi())
+    /***************************************/
+
+    useEffect(() => {
+        getAccountList.run()
+    }, [])
+
+    useEffect(() => {
+        const strategyList = value?.strategyList?.map(item => {
+            return {
+                ...item,
+                groupObjectList: item?.groupObjectList || []
+            }
+        })
+        form.setFieldsValue({
+            strategyList
+        })
+        filedUpdateChange({ strategyList })
+    }, [value])
+
+    const handleOk = () => {
+        form.validateFields().then((values) => {
+            console.log(values)
+            onChange?.(values)
+        }).catch(() => {
+            form.submit()
+        });
+    };
+
+    const filedUpdateChange = ({ strategyList }: any) => {
+        const stepsData = strategyList?.map((item, index) => {
+            const { strategyName } = item
+            return { title: `策略${index + 1}: ${strategyName}`, description: `进群对象名称、进群对象配置、群递增起始编号、群固定人数...`, id: `strategy_${index + 1}`, checked: item?.groupObjectList?.length > 0 }
+        })
+        setStepsList(stepsData)
+    }
+
+    return <Modal
+        title={<strong>群聊创建群配置</strong>}
+        open={visible}
+        onCancel={onClose}
+        width={1000}
+        onOk={handleOk}
+        className={`settingsModal`}
+    >
+        <div className={`body_steps`}>
+            <NewSteps
+                items={stepsList}
+                onChange={(e) => {
+                    if (e?.id)
+                        ref1.current?.querySelector('#' + e?.id)?.scrollIntoView({ behavior: 'smooth' })
+                }}
+            />
+        </div>
+        <div className={`body_content`} ref={ref1}>
+            <Form
+                form={form}
+                name="newGroupUser"
+                labelAlign='left'
+                labelCol={{ span: 6 }}
+                colon={false}
+                labelWrap
+                scrollToFirstError={{
+                    behavior: 'smooth',
+                    block: 'center'
+                }}
+                onFinishFailed={({ errorFields }) => {
+                    console.log('errorFields--->', errorFields)
+                    setActiveKey(data => data?.filter(item => item != errorFields?.[0]?.name?.[1]))
+                    message.error(errorFields?.[0]?.errors?.[0])
+                }}
+                onFinish={handleOk}
+                initialValues={{
+                    // strategyList: [{ id: Date.now() }]
+                }}
+                onFieldsChange={() => {
+                    filedUpdateChange(form.getFieldsValue())
+                }}
+                preserve={true}
+            >
+                <Form.List name="strategyList">
+                    {(fields) => (
+                        <>
+                            {fields.map(({ key, name, ...restField }, index) => {
+                                const strategyItem = strategyList[index]
+                                return <Card
+                                    key={key}
+                                    title={<Space>
+                                        <strong>{strategyItem?.strategyName}</strong>
+                                        <Popover
+                                            placement="right"
+                                            content={<div>
+                                                <PreviewTime
+                                                    {...strategyItem}
+                                                />
+                                            </div>}
+                                            styles={{ body: { width: 300, overflow: 'hidden', overflowY: 'auto', maxHeight: 400 } }}
+                                        >
+                                            <a style={{ color: '#000' }}><QuestionCircleFilled /></a>
+                                        </Popover>
+                                    </Space>}
+                                    style={{ background: '#fff', marginBottom: 10 }}
+                                    hoverable
+                                    id={`strategy_${index + 1}`}
+                                    extra={<a
+                                        style={{ padding: 2 }}
+                                        onClick={() => setActiveKey(data => {
+                                            if (data.includes(index)) {
+                                                return data.filter(item => item !== index)
+                                            }
+                                            return [...new Set([...data, index])]
+                                        })}
+                                    >{activeKey.includes(index) ? <RightOutlined /> : <DownOutlined />}</a>}
+                                    styles={{
+                                        body: {
+                                            transition: 'height 0.3s ease-in-out',
+                                            overflow: 'hidden',
+                                            height: !activeKey.includes(index) ? 'auto' : 0,
+                                            padding: !activeKey.includes(index) ? '16px' : 0
+                                        }
+                                    }}
+                                >
+                                    <Form.Item
+                                        {...restField}
+                                        name={[name, 'groupObjectList']}
+                                        rules={[{ required: true, message: '请新增群配置!' }]}
+                                    >
+                                        <AddGroupObject
+                                            bookPlatForm={bookPlatForm}
+                                            bookList={bookList}
+                                            strategyList={strategyList}
+                                            index={index}
+                                            onCopy={(data, index) => {
+                                                form.setFieldsValue({
+                                                    strategyList: strategyList.map((item, i) => {
+                                                        if (i === index - 1) {
+                                                            const groupObjectList = item?.groupObjectList || []
+                                                            groupObjectList.push(data)
+                                                            return {
+                                                                ...item,
+                                                                groupObjectList
+                                                            }
+                                                        }
+                                                        return item
+                                                    })
+                                                })
+                                            }}
+                                        />
+                                    </Form.Item>
+                                </Card>
+                            })}
+                        </>
+                    )}
+                </Form.List>
+            </Form>
+        </div>
+    </Modal>
+}
+
+export default React.memo(SettingsGroupUser);

+ 69 - 0
src/pages/weComTask/page/groupChat/create/components/strategy/index.tsx

@@ -0,0 +1,69 @@
+import React, { useContext, useState } from 'react';
+import style from '../../../../businessPlan/create/index.less'
+import useNewToken from '@/Hook/useNewToken';
+import { App, Button, Empty, Popconfirm } from 'antd';
+import { DispatchGroupChatCreate } from '../..';
+import SettingsStrategy from './settingsStrategy';
+import PreviewStrategy from './previewStrategy';
+
+
+/** 基础执行时间配置 */
+const Strategy: React.FC = () => {
+
+    /*********************************/
+    const { message } = App.useApp()
+    const { token } = useNewToken()
+    const { setSettings, settings, onPreviewReset } = useContext(DispatchGroupChatCreate)!;
+    const [newVisible, setNewVisible] = useState<boolean>(false);
+    /*********************************/
+
+
+    return <>
+        <div className={`${style.settingsBody_content_row}`} style={{ width: '35%' }}>
+            <div className={`${style.settingsBody_content_col}`} style={{ width: '100%' }}>
+                <div className={style.title}>
+                    <span>策略</span>
+                    {settings?.strategyDTO && Object.keys(settings?.strategyDTO).length > 0 && <Popconfirm
+                        title="确定清空?"
+                        onConfirm={() => {
+                            setSettings(undefined)
+                            onPreviewReset();
+                        }}
+                    >
+                        <a style={{ color: 'red' }}>清空</a>
+                    </Popconfirm>}
+                </div>
+                <div className={style.detail}>
+                    <div className={style.detail_title}>群聊创建策略配置</div>
+                    <div className={style.detail_body}>
+                        {settings?.strategyDTO && Object.keys(settings?.strategyDTO).length > 0 ? <>
+                            <PreviewStrategy strategyDTO={settings?.strategyDTO} />
+                        </> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
+                    </div>
+                </div>
+                <div className={style.detail_footer}>
+                    <Button type="link" style={{ padding: 0, fontSize: 12, color: token.colorPrimary }} size="small" onClick={() => setNewVisible(true)}>编辑</Button>
+                </div>
+            </div>
+        </div>
+
+        {/* 配置基础信息 */}
+        {newVisible && <SettingsStrategy
+            visible={newVisible}
+            value={settings?.strategyDTO}
+            onClose={() => {
+                setNewVisible(false);
+            }}
+            onChange={(values) => {
+                setSettings({
+                    ...settings,
+                    strategyDTO: values
+                });
+                onPreviewReset();
+                setNewVisible(false);
+            }}
+        />}
+    </>
+};
+
+export default React.memo(Strategy);

+ 88 - 0
src/pages/weComTask/page/groupChat/create/components/strategy/previewStrategy.tsx

@@ -0,0 +1,88 @@
+import React, { useEffect } from 'react';
+import dayjs from 'dayjs';
+import { Card, Form, Input } from 'antd';
+import SendTimeSet from '@/pages/weComTask/components/sendTimeSet';
+import '../../../../businessPlan/create/global.less'
+
+interface Props {
+    strategyDTO: { [x: string]: any }
+}
+
+const PreviewStrategy: React.FC<Props> = ({ strategyDTO }) => {
+
+    /**************************************/
+    const [form] = Form.useForm();
+    const strategyList = Form.useWatch('strategyList', form)
+    /**************************************/
+
+    useEffect(() => {
+        if (strategyDTO && Object.keys(strategyDTO).length) {
+            const data = {
+                ...strategyDTO, strategyList: strategyDTO?.strategyList?.map(item => {
+                    const { sendTime, startTime, endTime, sendDay, timeRepeatType } = item
+                    if (timeRepeatType === 'TIME_TYPE_SINGLE_PLACE') {
+                        const data = {
+                            ...item,
+                            timeRepeatType,
+                            sendDay: sendDay ? dayjs(sendDay + ' ' + sendTime) : undefined
+                        }
+                        delete data?.sendTime
+                        return data
+                    }
+                    return {
+                        ...item,
+                        timeRepeatType,
+                        sendTime: sendTime ? dayjs('2025-04-25 ' + sendTime) : undefined,
+                        startTime: startTime ? dayjs(startTime) : undefined,
+                        endTime: endTime ? dayjs(endTime) : undefined,
+                        sendDay: sendDay ? dayjs(sendDay) : undefined
+                    }
+                })
+            }
+            form.setFieldsValue(data)
+        }
+    }, [strategyDTO])
+
+    return <Form
+        form={form}
+        name="newGroupChatStrategyShow"
+        labelAlign='left'
+        labelCol={{ span: 5 }}
+        colon={false}
+        preserve={true}
+    >
+        {/* <Card title={<strong>基础信息配置</strong>} style={{ background: '#fff', marginBottom: 10 }} id='basicInfo'>
+            <Form.Item label={<strong>任务名称</strong>} name="taskName">
+                <Input placeholder="请输入任务名称" />
+            </Form.Item>
+        </Card> */}
+        <Form.List name="strategyList">
+            {(fields) => (
+                <>
+                    {fields.map(({ key, name, ...restField }, index) => {
+                        const timeRepeatType = strategyList?.[index]?.timeRepeatType
+                        return <Card
+                            key={key}
+                            title={<strong>策略{index + 1} 配置</strong>}
+                            style={{ background: '#fff', marginBottom: 10 }}
+                            id={`strategy_${index + 1}`}
+                        >
+                            <div className='block_tm'>
+                                <Form.Item
+                                    {...restField}
+                                    name={[name, 'strategyName']}
+                                    label={<strong>策略名称</strong>}
+                                >
+                                    <Input placeholder='请输入策略名称' allowClear />
+                                </Form.Item>
+                                <SendTimeSet isShow active='all' form={form} restField={restField} name={name} timeRepeatType={timeRepeatType} />
+                            </div>
+                        </Card>
+                    })}
+                </>
+            )}
+        </Form.List>
+    </Form>
+};
+
+export default React.memo(PreviewStrategy);

+ 193 - 0
src/pages/weComTask/page/groupChat/create/components/strategy/settingsStrategy.tsx

@@ -0,0 +1,193 @@
+import { App, Button, Card, Form, Input, Modal } from 'antd';
+import React, { useEffect, useRef, useState } from 'react';
+import '../../../../businessPlan/create/global.less'
+import NewSteps from '@/pages/weComTask/components/newSteps';
+import { PlusOutlined, MinusCircleOutlined } from '@ant-design/icons';
+import SendTimeSet from '@/pages/weComTask/components/sendTimeSet';
+import dayjs from 'dayjs';
+
+/**
+ * 群聊创建策略配置
+ * @param param0 
+ * @returns 
+ */
+const SettingsStrategy: React.FC<GROUP_CHAT_CREATE.FoundationProps<any>> = ({ visible, onClose, value, onChange }) => {
+
+    /************************************/
+    const { message } = App.useApp()
+    const ref1 = useRef<HTMLDivElement>(null)
+    const [form] = Form.useForm();
+    const strategyList = Form.useWatch('strategyList', form)
+
+    const [stepsList, setStepsList] = useState<any>([
+        // { title: '群聊创建配置', description: '任务名称', id: 'basicInfo' },
+        { title: '策略配置', children: [{ title: `策略${1}`, id: 'strategy_1' }] },
+        { title: '完成' }
+    ])
+    /************************************/
+
+    // 回填
+    useEffect(() => {
+        if (value && Object.keys(value).length) {
+            const data = {
+                ...value, strategyList: value?.strategyList?.map(item => {
+                    const { sendTime, startTime, endTime, sendDay, timeRepeatType } = item
+                    if (timeRepeatType === 'TIME_TYPE_SINGLE_PLACE') {
+                        const data = {
+                            ...item,
+                            timeRepeatType,
+                            sendDay: sendDay ? dayjs(sendDay + ' ' + sendTime) : undefined
+                        }
+                        delete data?.sendTime
+                        return data
+                    }
+                    return {
+                        ...item,
+                        timeRepeatType,
+                        sendTime: sendTime ? dayjs('2025-04-25 ' + sendTime) : undefined,
+                        startTime: startTime ? dayjs(startTime) : undefined,
+                        endTime: endTime ? dayjs(endTime) : undefined,
+                        sendDay: sendDay ? dayjs(sendDay) : undefined
+                    }
+                })
+            }
+            console.log('=================>', data)
+            filedUpdateChange(data)
+            form.setFieldsValue(data)
+        }
+    }, [value])
+
+    const handleOk = () => {
+        form.validateFields().then((values) => {
+            const data = {
+                ...values,
+                strategyList: values?.strategyList?.map(item => {
+                    const { startTime, endTime, sendDay, sendTime, timeRepeatType, repeatArray, ...rest } = item
+                    const data = { ...rest, timeRepeatType }
+                    if (timeRepeatType === 'TIME_TYPE_SINGLE_PLACE') {
+                        // 定时发送
+                        data.sendDay = dayjs(sendDay).format('YYYY-MM-DD')
+                        data.sendTime = dayjs(sendDay).format('HH:mm:ss')
+                    } else if (timeRepeatType === 'TIME_TYPE_REPEAT_DAY') {
+                        // 每日循环
+                        data.startTime = dayjs(startTime).format('YYYY-MM-DD')
+                        if (endTime) {
+                            data.endTime = dayjs(endTime).format('YYYY-MM-DD')
+                        }
+                        data.sendTime = dayjs(sendTime).format('HH:mm:ss')
+                    } else if (timeRepeatType === 'TIME_TYPE_REPEAT_WEEK' || timeRepeatType === 'TIME_TYPE_REPEAT_MONTH') {
+                        // 每周循环、每月循环
+                        data.startTime = dayjs(startTime).format('YYYY-MM-DD')
+                        data.sendTime = dayjs(sendTime).format('HH:mm:ss')
+                        if (endTime) {
+                            data.endTime = dayjs(endTime).format('YYYY-MM-DD')
+                        }
+                        data.repeatArray = repeatArray
+                    }
+                    return data
+                })
+            }
+            onChange(data)
+        }).catch(() => {
+            form.submit()
+        });
+    };
+
+    const filedUpdateChange = ({ strategyList }: any) => {
+        const strategyChildren = strategyList?.map((item, index) => {
+            const { strategyName, timeRepeatType, sendDay, startTime, sendTime, repeatArray } = item
+            const sendTimeChecked =
+                timeRepeatType === "TIME_TYPE_SINGLE_TIMELY" ||
+                (timeRepeatType === "TIME_TYPE_SINGLE_PLACE" && sendDay) ||
+                (timeRepeatType === "TIME_TYPE_REPEAT_DAY" && startTime && sendTime) ||
+                ((timeRepeatType === "TIME_TYPE_REPEAT_WEEK" || timeRepeatType === "TIME_TYPE_REPEAT_MONTH") && startTime && sendTime && repeatArray)
+            return { title: `策略${index + 1}`, description: `策略名称、执行时间`, id: `strategy_${index + 1}`, checked: sendTimeChecked && strategyName }
+        })
+        const stepsData = [
+            { title: '策略配置', children: strategyChildren, checked: strategyChildren.some(item => item.checked) },
+            {
+                title: '完成', checked: strategyChildren.some(item => item.checked)
+            }
+        ]
+        setStepsList(stepsData)
+    }
+
+    return <Modal
+        title={<strong>群聊创建策略配置  &nbsp;&nbsp;&nbsp;<span style={{ color: 'red' }}>对于执行时间冲突的策略,按照策略的排序执行</span></strong>}
+        open={visible}
+        onCancel={onClose}
+        width={850}
+        onOk={handleOk}
+        className={`settingsModal`}
+    >
+        <div className={`body_steps`}>
+            <NewSteps
+                items={stepsList}
+                onChange={(e) => {
+                    if (e?.id)
+                        ref1.current?.querySelector('#' + e?.id)?.scrollIntoView({ behavior: 'smooth' })
+                }}
+            />
+        </div>
+        <div className={`body_content`} ref={ref1}>
+            <Form
+                form={form}
+                name="newGroupChatStrategy"
+                labelAlign='left'
+                labelCol={{ span: 5 }}
+                colon={false}
+                scrollToFirstError={{
+                    behavior: 'smooth',
+                    block: 'center'
+                }}
+                onFinishFailed={({ errorFields }) => {
+                    message.error(errorFields?.[0]?.errors?.[0])
+                }}
+                onFinish={handleOk}
+                initialValues={{
+                    strategyList: [{ id: Date.now() }]
+                }}
+                onFieldsChange={() => {
+                    filedUpdateChange(form.getFieldsValue())
+                }}
+                preserve={true}
+            >
+                <Form.List name="strategyList">
+                    {(fields, { add, remove }) => (
+                        <>
+                            {fields.map(({ key, name, ...restField }, index) => {
+                                const timeRepeatType = strategyList?.[index]?.timeRepeatType
+                                return <Card
+                                    key={key}
+                                    title={<strong>策略{index + 1} 配置</strong>}
+                                    style={{ background: '#fff', marginBottom: 10 }}
+                                    extra={strategyList?.length > 1 && <div style={{ color: 'red', cursor: 'pointer' }} onClick={() => remove(name)}>
+                                        <MinusCircleOutlined />
+                                    </div>}
+                                    id={`strategy_${index + 1}`}
+                                >
+                                    <Form.Item
+                                        {...restField}
+                                        name={[name, 'strategyName']}
+                                        label={<strong>策略名称</strong>}
+                                        rules={[{ required: true, message: '请输入策略名称!' }]}
+                                    >
+                                        <Input placeholder='请输入策略名称' allowClear style={{ width: 358 }} />
+                                    </Form.Item>
+                                    <SendTimeSet active='all' form={form} restField={restField} name={name} timeRepeatType={timeRepeatType} />
+                                </Card>
+                            })}
+                            <Form.Item>
+                                <Button type="dashed" onClick={() => add({ id: Date.now() })} block icon={<PlusOutlined />}>
+                                    新增策略
+                                </Button>
+                            </Form.Item>
+                        </>
+                    )}
+                </Form.List>
+            </Form>
+        </div>
+    </Modal>
+};
+
+export default React.memo(SettingsStrategy);

+ 40 - 0
src/pages/weComTask/page/groupChat/create/const.ts

@@ -0,0 +1,40 @@
+
+/**
+ * 策略数据还原
+ * @param strategyList 
+ */
+export const getPullGroupData = (strategyList: any[]) => {
+    const nowTime = Date.now()
+    return {
+        strategyList: strategyList.map(({ taskDetail, ...item }, index) => {
+            const id = item.id || nowTime + (index * 1000)
+            const nowTime_inherit = Date.now()
+            return {
+                ...item,
+                id,
+                groupObjectList: taskDetail.map(({ externalUserFilter, weChatAppid, mpAccountInfo, msgTagDTO, groupName, isRetry, ...go }, i) => {
+                    const id = go.id || nowTime_inherit + (index * 1000) + (i * 100)
+                    const inherit: { [x: string]: any } = {
+                        ...go,
+                        isRetry: isRetry || false,
+                        id,
+                        weChatAppid: mpAccountInfo?.name ? mpAccountInfo?.name + '_' + mpAccountInfo?.id : undefined,
+                        tagDTO: msgTagDTO,
+                        groupObjectName: groupName
+                    }
+                    if (externalUserFilter) {
+                        inherit.externalUserType = 'specify'
+                        const { configName, ...configContent } = externalUserFilter
+                        inherit.externalUserFilter = {
+                            configName,
+                            configContent
+                        }
+                    } else {
+                        inherit.externalUserType = 'all'
+                    }
+                    return inherit
+                })
+            }
+        })
+    }
+}

+ 572 - 0
src/pages/weComTask/page/groupChat/create/index.tsx

@@ -0,0 +1,572 @@
+import React, { useCallback, useEffect, useState } from 'react';
+import style from '../../businessPlan/create/index.less'
+import { App, Button, Card, Empty, Form, Input, Popconfirm, Select, Space, Spin, Table } from 'antd';
+import { welcomeMsgJobTypeApi } from '@/pages/weComTask/API/weMaterial/weMaterial';
+import { useAjax } from '@/Hook/useAjax';
+import { inject, observer } from 'mobx-react';
+import { toJS } from 'mobx';
+import Strategy from './components/strategy';
+import GroupUser from './components/groupUser';
+import { SaveOutlined, RedoOutlined, SearchOutlined, PlusOutlined } from '@ant-design/icons';
+import { getCorpUserChatAllListApi } from '@/pages/weComTask/API/groupLeaderManage';
+import SelectCorpUserChatUser from '../../groupLeaderManage/selectCorpUserChatUser';
+import { PreviewColumns } from './tableConfig';
+import SelectCorpUser from '../../corpUserManage/selectCorpUser';
+import { getBindMpListApi } from '@/pages/weComTask/API/corpUserAssign';
+import { getCorpAllListApi } from '@/API/global';
+import { removeEmptyValues } from '@/utils/utils';
+import SubmitModal from '../../businessPlan/create/submitModal';
+import { addPullGroupTaskApi, getCreateDetailsApi, updateGroupTaskApi } from '@/pages/weComTask/API/groupChat';
+import { useNavigate } from 'react-router-dom';
+import { getPullGroupData } from './const';
+import SelectGroupLeader from '../../groupLeaderManage/selectGroupLeader';
+
+export const DispatchGroupChatCreate = React.createContext<GROUP_CHAT_CREATE.DispatchGroupChatCreate | null>(null);
+
+/**
+ * 群聊任务创建组件
+ * @returns 
+ */
+const GroupChatCreate: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookListProps[], bookPlatForm: TASK_CREATE.BookPlatFormProps[] } } }> = ({ weComTaskStore }) => {
+
+    /***********************************************/
+    const navigate = useNavigate();
+    const { bookList, bookPlatForm } = toJS(weComTaskStore.data)
+    const { message, modal } = App.useApp()
+
+    const [settings, setSettings] = useState<GROUP_CHAT_CREATE.SettingsProps>();
+    const [msgJobTypeList, setMsgJobTypeList] = useState<{ value: string, label: string }[]>([])
+    const [previewData, setPreviewData] = useState<any[]>([])
+    const [previewDataOld, setPreviewDataOld] = useState<any[]>([])
+    const [mpList, setMplist] = useState<{ label: string, value: string }[]>([])
+    const [subVisible, setSubVisible] = useState<boolean>(false) // 选择设置名称弹窗控制
+    const [projectId, setProjectId] = useState<number>()
+
+
+    const welcomeMsgJobType = useAjax(() => welcomeMsgJobTypeApi())//获取业务类型
+    const getBindMpList = useAjax(() => getBindMpListApi())
+    const getCorpAllList = useAjax((params) => getCorpAllListApi(params))
+    const addPullGroupTask = useAjax((params) => addPullGroupTaskApi(params))
+    const updateGroupTask = useAjax((params) => updateGroupTaskApi(params))
+    const getCreateDetails = useAjax((params) => getCreateDetailsApi(params))
+    /***********************************************/
+    console.log('settings--->', settings)
+
+    useEffect(() => {
+        const project = sessionStorage.getItem('PG_OFFICIALTASKID')
+        if (project) {
+            const { id, isCopy } = JSON.parse(project)
+            if (!isCopy) {
+                setProjectId(id)
+            }
+            getCreateDetails.run(id).then(res => {
+                sessionStorage.removeItem('PG_OFFICIALTASKID')
+                if (res?.data) {
+                    const { bizType, platform, templateProductId, corpUsers, corpChatUserList, corpRobots, strategyList } = res.data
+                    const data = getPullGroupData(strategyList || [])
+                    console.log('data-->', data)
+                    let newSettings: GROUP_CHAT_CREATE.SettingsProps = {
+                        bizType,
+                        platform: Number(platform) as any,
+                        templateProductId,
+                        corpUserChat: corpChatUserList.map(item => {
+                            return {
+                                label: `${item.name}(${item.corpName})`,
+                                value: item.id,
+                                name: item.name,
+                                corpName: item.corpName,
+                                corpId: item.corpId,
+                                corpUserId: item.corpUserId
+                            }
+                        }),
+                        corpUsers: corpUsers?.map(item => {
+                            return {
+                                name: item.corpUserName,
+                                corpUserId: item.corpUserId,
+                                corpName: item.corpName,
+                                corpId: item.corpId
+                            }
+                        }),
+                        robotCorpUsers: corpRobots?.map(item => {
+                            return {
+                                name: item.corpUserName,
+                                corpUserId: item.corpUserId,
+                                corpName: item.corpName,
+                                corpId: item.corpId
+                            }
+                        }),
+                        strategyDTO: {
+                            ...data
+                        }
+                    }
+                    console.log('------------->', newSettings)
+                    setSettings(newSettings)
+                }
+            })
+        } else {
+            const task = localStorage.getItem('TASK_GROUP_CHAT_CREATE')
+            if (task) {
+                setSettings(JSON.parse(task).settings)
+            }
+        }
+    }, [])
+
+    useEffect(() => {
+        welcomeMsgJobType.run().then(res => {
+            if (res?.data) {
+                setMsgJobTypeList(Object.keys(res.data).map(key => ({ value: key, label: res.data[key] })))
+            }
+        })
+        getBindMpList.run().then(res => {
+            setMplist(res?.data?.map((item: any) => ({ label: item.name, value: item.id + '' })))
+        })
+        getCorpAllList.run({})
+    }, [])
+
+
+    const severBd = () => {
+        localStorage.setItem('TASK_GROUP_CHAT_CREATE', JSON.stringify({ settings }))
+        message.success('存储成功')
+    }
+
+    const preview = () => {
+        const { corpUserChat, corpUsers, robotCorpUsers, bizType, platform, templateProductId } = settings
+        if (!corpUserChat || corpUserChat?.length === 0) {
+            message.error('请先选择群主号')
+            return
+        }
+        if (!corpUsers || corpUsers?.length === 0) {
+            message.error('请先选择客服号')
+            return
+        }
+        // if (!robotCorpUsers || robotCorpUsers?.length === 0) {
+        //     message.error('请先选择机器人号')
+        //     return
+        // }
+        if (!bizType) {
+            message.error('请选择业务类型')
+            return
+        }
+        if (!platform) {
+            message.error('请选择书城')
+            return
+        }
+        if (!(settings?.strategyDTO && Object.keys(settings?.strategyDTO).length > 0)) {
+            message.error('请先设置策略')
+            return
+        }
+        if (!settings?.strategyDTO?.strategyList?.every(item => item?.groupObjectList?.length > 0)) {
+            message.error('请先设置群配置')
+            return
+        }
+        const dto = settings.strategyDTO.strategyList.reduce((pre, cur, strategyIndex) => {
+            const { groupObjectList, ...its } = cur
+            groupObjectList.forEach((item, index) => {
+                pre.push({
+                    ...item,
+                    ...its,
+                    strategyIndex,
+                    goIndex: index,
+                    taskName: settings.strategyDTO.taskName
+                })
+            })
+            return pre
+        }, [])
+
+        let id = 1
+        const list = corpUsers.reduce((pre, corpUser) => {
+            return pre.concat(dto.map(item => {
+                return {
+                    ...item,
+                    corpUser,
+                    corpUserChat,
+                    robotCorpUsers,
+                    bizType,
+                    platform,
+                    templateProductId,
+                    id: id++
+                }
+            }))
+        }, [])
+        console.log('==================>', list)
+        setPreviewData(list)
+        setPreviewDataOld(list)
+    }
+
+    // 重置表格
+    const onPreviewReset = () => {
+        setPreviewData([])
+        setPreviewDataOld([])
+    }
+
+    const tableSearch = useCallback((values) => {
+        console.log(values)
+        const obj = removeEmptyValues(values)
+        if (Object.keys(obj).length) {
+            const newPreviewData = previewDataOld.filter(item => {
+                const weChatAppid = item?.weChatAppid?.split('_')?.[1]
+                return (obj?.mpAccountIds?.length > 0 ? obj.mpAccountIds.includes(weChatAppid) : true) &&
+                    (obj?.corpId ? item?.corpUser?.corpId === obj?.corpId : true) &&
+                    (obj?.corpUserIds?.length > 0 ? obj?.corpUserIds?.map(item => item.corpUserId)?.includes(item?.corpUser?.corpUserId) : true) &&
+                    (obj?.groupCorpId ? item?.corpUserChat?.map(item => item.corpId)?.includes(obj?.groupCorpId) : true) &&
+                    (obj?.robotCorpId ? item?.robotCorpUsers?.[0]?.corpId === obj?.robotCorpId : true) &&
+                    (obj?.groupObjectName ? item?.groupObjectName?.includes(obj?.groupObjectName) : true)
+            })
+            setPreviewData(newPreviewData)
+        } else {
+            setPreviewData(previewDataOld)
+        }
+    }, [previewDataOld, previewData])
+
+    const onSubmit = (values: any) => {
+        const { bizType, platform, templateProductId, corpUsers, corpUserChat, robotCorpUsers, strategyDTO } = settings
+        console.log('--->', values)
+        const params: { [x: string]: any } = {
+            ...values,
+            taskName: values.projectName,
+            bizType,
+            platform,
+            templateProductId,
+            corpChatUserIds: corpUserChat.map(item => item.value),
+            corpRobots: robotCorpUsers?.map(item => ({
+                corpId: item.corpId,
+                corpUserId: item.corpUserId,
+                corpUserName: item.name,
+                corpName: item.corpName
+            })),
+            corpUsers: corpUsers.map(item => ({
+                corpId: item.corpId,
+                corpUserId: item.corpUserId,
+                corpUserName: item.name,
+                corpName: item.corpName
+            })),
+            strategyList: strategyDTO.strategyList.map(({ groupObjectList, ...item }) => {
+                return {
+                    ...item,
+                    taskDetail: groupObjectList.map(go => {
+
+                        const { externalUserType, externalUserFilter, groupObjectName, tagDTO, weChatAppid, ...itgo } = go
+                        const detail = {
+                            ...itgo,
+                            groupName: groupObjectName,
+                            msgTagDTO: tagDTO
+                        }
+                        if (externalUserType === 'specify') {
+                            detail.externalUserFilter = {
+                                configName: externalUserFilter.configName,
+                                ...externalUserFilter.configContent
+                            }
+                        }
+                        if (weChatAppid) {
+                            detail.mpAccountId = weChatAppid.split('_')[1]
+                        }
+                        return detail
+                    })
+                }
+            })
+        }
+        delete params?.projectName
+        console.log('提交参数--->', params)
+        if (projectId) {
+            params.projectId = projectId
+            updateGroupTask.run(params).then(res => {
+                console.log(res)
+                if (res?.data) {
+                    message.success('修改提交成功')
+                    setProjectId(undefined)
+                    sessionStorage.setItem('CAMPCORP_PG', values?.projectName)
+                    navigate('/weComTask/groupChat/taskList')
+                }
+            })
+        } else {
+            addPullGroupTask.run(params).then(res => {
+                console.log(res)
+                if (res?.data) {
+                    modal.success({
+                        content: '任务提交成功',
+                        styles: { body: { fontWeight: 700 } },
+                        okText: '跳转任务列表',
+                        closable: true,
+                        onOk: () => {
+                            sessionStorage.setItem('CAMPCORP_PG', values?.projectName)
+                            navigate('/weComTask/groupChat/taskList')
+                        },
+                        onCancel: () => {
+                            setSubVisible(false)
+                        }
+                    })
+                }
+            })
+        }
+    }
+
+    return <div className={style.create}>
+        <Spin spinning={getCreateDetails.loading}>
+            <Card title={<strong>{projectId ? getCreateDetails?.data?.data?.taskName + '任务编辑' : ''}配置区</strong>} className={`${style.card} ${style.config}`}>
+                <Space wrap>
+                    <Space.Compact>
+                        <Button>群主号</Button>
+                        <SelectGroupLeader 
+                            value={settings?.corpUserChat}
+                            onChange={(corpUserChat) => {
+                                setSettings({ ...settings, corpUserChat: corpUserChat, corpUsers: undefined, robotCorpUsers: undefined })
+                                onPreviewReset()
+                            }}
+                        />
+                    </Space.Compact>
+
+                    {settings?.corpUserChat?.length > 0 && <>
+                        <Space.Compact>
+                            <Button>客服号</Button>
+                            <SelectCorpUserChatUser
+                                placeholder="请选择客服号"
+                                bindType={1}
+                                corpUserChatIds={settings?.corpUserChat?.map(item => item.value)}
+                                value={settings?.corpUsers}
+                                onChange={(value) => {
+                                    setSettings({
+                                        ...settings,
+                                        corpUsers: value,
+                                    })
+                                    onPreviewReset()
+                                }}
+                            />
+                        </Space.Compact>
+                        <Space.Compact>
+                            <Button>机器人号</Button>
+                            <SelectCorpUserChatUser
+                                placeholder="请选择机器人号"
+                                bindType={2}
+                                corpUserChatIds={settings?.corpUserChat?.map(item => item.value)}
+                                value={settings?.robotCorpUsers}
+                                onChange={(value) => {
+                                    setSettings({
+                                        ...settings,
+                                        robotCorpUsers: value,
+                                    })
+                                    onPreviewReset()
+                                }}
+                            />
+                        </Space.Compact>
+                    </>}
+
+                    <Space.Compact>
+                        <Button>业务类型</Button>
+                        <Select
+                            showSearch
+                            style={{ width: 120 }}
+                            allowClear
+                            placeholder="请选择类型"
+                            filterOption={(input, option) =>
+                                ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                            }
+                            value={settings?.bizType}
+                            onChange={(e) => {
+                                setSettings({ ...settings, bizType: e })
+                                onPreviewReset()
+                            }}
+                            options={msgJobTypeList.filter(item => item.value === 'novel')}
+                        />
+                    </Space.Compact>
+
+                    {settings?.bizType === 'novel' ? <>
+                        <Space.Compact>
+                            <Button>书城</Button>
+                            <Select
+                                showSearch
+                                allowClear
+                                placeholder="请选择书城"
+                                style={{ width: 120 }}
+                                filterOption={(input, option) =>
+                                    ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                                }
+                                value={settings?.platform}
+                                onChange={(e) => {
+                                    setSettings({ ...settings, platform: e })
+                                    onPreviewReset()
+                                }}
+                                options={bookPlatForm.map(item => ({ value: item.id, label: item.platformName }))}
+                            />
+                        </Space.Compact>
+                        <Space.Compact>
+                            <Button>适用产品</Button>
+                            <Select
+                                showSearch
+                                style={{ width: 150 }}
+                                allowClear
+                                placeholder="请选择模板适用产品"
+                                filterOption={(input, option) =>
+                                    ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                                }
+                                value={settings?.templateProductId}
+                                onChange={(e) => {
+                                    setSettings({ ...settings, templateProductId: e })
+                                    onPreviewReset()
+                                }}
+                                options={bookList.map(item => ({ value: item.id, label: item.bookName }))}
+                            />
+                        </Space.Compact>
+                    </> : settings?.bizType === 'game' ? <Space.Compact>
+                        <Button>游戏渠道</Button>
+                        <Input
+                            style={{ width: 200 }}
+                            allowClear
+                            placeholder="请输入游戏渠道"
+                            value={settings.channel}
+                            onChange={(e) => {
+                                setSettings({ ...settings, channel: e.target.value })
+                                onPreviewReset()
+                            }}
+                        />
+                    </Space.Compact> : undefined}
+                </Space>
+
+                <div className={style.settingsBody}>
+                    <div className={style.settingsBody_content}>
+                        <DispatchGroupChatCreate.Provider
+                            value={{
+                                settings, setSettings,
+                                onPreviewReset,
+                                bookPlatForm, bookList
+                            }}
+                        >
+                            {/* 策略配置 */}
+                            <Strategy />
+                            {/* 进群对象 */}
+                            <GroupUser />
+                        </DispatchGroupChatCreate.Provider>
+                    </div>
+                </div>
+
+                <Space className={style.bts} wrap>
+                    <Button icon={<SaveOutlined />} onClick={severBd}>存为预设</Button>
+                    <Popconfirm
+                        title="确定清空?"
+                        onConfirm={() => {
+                            setSettings(undefined)
+                            onPreviewReset()
+                            localStorage.removeItem('TASK_GROUP_CHAT_CREATE')
+                        }}
+                    >
+                        <Button icon={<RedoOutlined />} danger>清空配置/预设</Button>
+                    </Popconfirm>
+                    <Button type='primary' onClick={preview}><SearchOutlined />预览任务/配置内容</Button>
+                </Space>
+            </Card>
+        </Spin>
+        <Card
+            className={style.card}
+            style={{ marginTop: 10, marginBottom: 10 }}
+            extra={previewDataOld?.length > 0 ? <Button type='primary' icon={<PlusOutlined />} onClick={() => {
+                setSubVisible(true)
+            }}>提交</Button> : undefined}
+        >
+            {previewDataOld?.length > 0 ? <div style={{ minHeight: 300 }}>
+                <Form
+                    layout={'inline'}
+                    onFinish={tableSearch}
+                >
+                    <Form.Item label={<strong>公众号</strong>} style={{ marginBottom: 10 }} name="mpAccountIds">
+                        <Select
+                            showSearch
+                            style={{ minWidth: 160 }}
+                            maxTagCount={1}
+                            placeholder="公众号"
+                            filterOption={(input, option) =>
+                                ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                            }
+                            allowClear
+                            mode='multiple'
+                            options={mpList}
+                        />
+                    </Form.Item>
+                    <Form.Item label={<strong>客服号主体</strong>} style={{ marginBottom: 10 }} name="corpId">
+                        <Select
+                            showSearch
+                            style={{ minWidth: 110 }}
+                            maxTagCount={1}
+                            placeholder="请选择主体"
+                            filterOption={(input, option) =>
+                                ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                            }
+                            allowClear
+                            options={getCorpAllList?.data?.data?.map((item: any) => ({ label: item.corpName, value: item.corpId }))}
+                        />
+                    </Form.Item>
+
+                    <Space.Compact>
+                        <Button>客服号</Button>
+                        <Form.Item name="corpUserIds" style={{ marginBottom: 10 }}>
+                            <SelectCorpUser placeholder="请选择客服号" />
+                        </Form.Item>
+                    </Space.Compact>
+                    <Form.Item label={<strong>群主号主体</strong>} style={{ marginBottom: 10 }} name="groupCorpId">
+                        <Select
+                            showSearch
+                            style={{ minWidth: 110 }}
+                            maxTagCount={1}
+                            placeholder="请选择主体"
+                            filterOption={(input, option) =>
+                                ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                            }
+                            allowClear
+                            options={getCorpAllList?.data?.data?.map((item: any) => ({ label: item.corpName, value: item.corpId }))}
+                        />
+                    </Form.Item>
+                    <Form.Item label={<strong>机器人号主体</strong>} style={{ marginBottom: 10 }} name="robotCorpId">
+                        <Select
+                            showSearch
+                            style={{ minWidth: 110 }}
+                            maxTagCount={1}
+                            placeholder="请选择主体"
+                            filterOption={(input, option) =>
+                                ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                            }
+                            allowClear
+                            options={getCorpAllList?.data?.data?.map((item: any) => ({ label: item.corpName, value: item.corpId }))}
+                        />
+                    </Form.Item>
+                    <Form.Item label={<strong>群配置名称</strong>} style={{ marginBottom: 10 }} name="groupObjectName">
+                        <Input placeholder="请输入群配置名称" allowClear />
+                    </Form.Item>
+                    <Form.Item style={{ marginBottom: 10 }}>
+                        <Space>
+                            <Button htmlType="reset">重置</Button>
+                            <Button type="primary" htmlType='submit'>搜索</Button>
+                        </Space>
+                    </Form.Item>
+                </Form>
+                <Table
+                    dataSource={previewData}
+                    columns={PreviewColumns(bookPlatForm, bookList)}
+                    rowKey={'id'}
+                    bordered={true}
+                    scroll={{ y: 550 }}
+                    pagination={{
+                        showTotal(total, range) {
+                            return `共 ${total} 条记录 第 ${range[0]}-${range[1]} 条`
+                        },
+                    }}
+                />
+            </div> : <div style={{ minHeight: 400, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
+                <Empty description="请先完成模块配置后,再预览" />
+            </div>}
+        </Card>
+
+
+        {/* 提交配置 */}
+        {subVisible && <SubmitModal
+            visible={subVisible}
+            loading={addPullGroupTask.loading}
+            projectName={projectId ? getCreateDetails?.data?.data?.taskName : undefined}
+            onChange={(values) => {
+                onSubmit(values)
+            }}
+            onClose={() => {
+                setSubVisible(false)
+            }}
+        />}
+    </div>
+};
+
+export default inject('store')(observer((props: any) => GroupChatCreate(props.store)))

+ 225 - 0
src/pages/weComTask/page/groupChat/create/tableConfig.tsx

@@ -0,0 +1,225 @@
+import { Badge, Popover, Space, Typography } from "antd";
+import { AnyObject } from "antd/es/_util/type"
+import { ColumnsType } from "antd/es/table"
+import { businessPlanData, TIME_TYPE } from "../../businessPlan/create/const";
+import FilterUserTooltip from "@/pages/weComTask/components/filterUser/filterUserTooltip";
+import { QuestionCircleFilled } from "@ant-design/icons";
+const { Paragraph, Title, Text } = Typography;
+
+
+export const PreviewColumns = (bookPlatForm: TASK_CREATE.BookPlatFormProps[], bookList: TASK_CREATE.BookListProps[]): ColumnsType<AnyObject> => {
+
+    return [
+        {
+            title: '企微号信息',
+            dataIndex: 'corpUser',
+            key: 'corpName',
+            width: 150,
+            fixed: 'left',
+            render(value) {
+                return <>
+                    <Title level={5} style={{ margin: 0 }}>{value?.corpName || '--'}</Title>
+                    <Paragraph style={{ margin: 0 }}>{value?.name || '--'}</Paragraph>
+                </>
+            }
+        },
+        {
+            title: '基础信息',
+            dataIndex: 'taskName',
+            key: 'taskName',
+            width: 130,
+            render(value, record) {
+                return <>
+                    <Title level={5} style={{ margin: 0 }}>标题:{value}</Title>
+                    {record?.bizType && <Paragraph style={{ margin: 0 }}>业务类型:{record?.bizType === 'novel' ? '小说' : record?.bizType === 'game' ? '游戏' : '<空>'}</Paragraph>}
+                    {record?.platform && <Paragraph style={{ margin: 0 }}>书城:{record?.platform ? bookPlatForm?.find(item => item.id === record?.platform)?.platformName : '<空>'}</Paragraph>}
+                    {record?.templateProductId && <Paragraph style={{ margin: 0 }}>适用产品:{record?.templateProductId ? bookList?.find(item => item.id === record?.templateProductId)?.bookName : '<空>'}</Paragraph>}
+                </>
+            }
+        },
+        {
+            title: '群主号',
+            dataIndex: 'corpUserChat',
+            key: 'corpUserChat',
+            width: 130,
+            render(value) {
+                return value.map((item, index) => <Paragraph style={{ margin: 0 }} key={index}>{item.label}</Paragraph>)
+            }
+        },
+        {
+            title: '机器人客服号',
+            dataIndex: 'robotCorpUsers',
+            key: 'robotCorpUsers',
+            width: 130,
+            render(value) {
+                return value?.length > 0 ? value?.map((item, index) => <Paragraph style={{ margin: 0 }} key={index}>{item.name}({item.corpName})</Paragraph>) : '--'
+            }
+        },
+        {
+            title: '策略信息',
+            dataIndex: 'strategyData',
+            key: 'strategyData',
+            width: 200,
+            render(_, record) {
+                return <>
+                    <Title level={5} style={{ margin: 0 }}>策略{record.strategyIndex + 1}</Title>
+                    <Paragraph style={{ margin: 0 }}>名称:{record?.strategyName || '<空>'}</Paragraph>
+                    <Paragraph style={{ margin: 0 }}>执行类型:{TIME_TYPE[record?.timeRepeatType]}</Paragraph>
+                    {record?.sendDay && <Paragraph style={{ margin: 0 }}>执行时间:{record?.sendDay}</Paragraph>}
+                    {record?.startTime && <Paragraph style={{ margin: 0 }}>执行日期:{record?.startTime}~{record?.endTime ? record?.endTime : '长期执行'}</Paragraph>}
+                    {record?.sendTime && <Paragraph style={{ margin: 0 }}>执行时间:{record?.sendTime}</Paragraph>}
+                    {record?.repeatArray && <Paragraph style={{ margin: 0 }}>执行天数:{record?.repeatArray.join('、')}</Paragraph>}
+                </>
+            }
+        },
+        {
+            title: '群名称',
+            dataIndex: 'groupObjectName',
+            key: 'groupObjectName',
+            width: 120,
+            ellipsis: true,
+            fixed: 'left'
+        },
+        {
+            title: '是否开启已有旧群聊补缺',
+            dataIndex: 'isRepair',
+            key: 'isRepair',
+            width: 150,
+            ellipsis: true,
+            render(value, record, index) {
+                return value ? <Space>
+                    <Badge status="success" text="开启" />
+                    <span>近{record.repairTimes}天</span>
+                </Space> : <Badge status="error" text="关闭" />
+            },
+        },
+        {
+            title: '进群对象',
+            dataIndex: 'externalUserFilter',
+            key: 'externalUserFilter',
+            width: 90,
+            align: 'center',
+            render(value) {
+                return value ? <div style={{ display: 'flex', alignItems: 'center' }}>
+                    <div style={{ flex: '1 0', overflow: 'hidden' }}>
+                        <Text ellipsis>指定</Text>
+                    </div>
+                    <Popover
+                        placement="right"
+                        styles={{ body: { maxWidth: 350, maxHeight: 350, overflow: 'hidden', overflowY: 'auto' } }}
+                        mouseEnterDelay={0.5}
+                        content={<FilterUserTooltip
+                            bookCityList={bookPlatForm?.map(item => ({ label: item.platformName, value: item.platformKey }))}
+                            configName={value?.configName}
+                            data={value?.configContent}
+                        />}
+                    >
+                        <a style={{ color: '#000' }}><QuestionCircleFilled /></a>
+                    </Popover>
+                </div> : '全部'
+            },
+        },
+        {
+            title: '群递增起始编号',
+            dataIndex: 'groupIndex',
+            key: 'groupIndex',
+            width: 60,
+            align: 'center'
+        },
+        {
+            title: '群固定人数',
+            dataIndex: 'groupUserCount',
+            key: 'groupUserCount',
+            width: 50,
+            align: 'center'
+        },
+        {
+            title: '邀请客户进群完毕后客服号是否退群',
+            dataIndex: 'autoOutGroup',
+            key: 'autoOutGroup',
+            width: 85,
+            align: 'center',
+            render(value) {
+                return value ? <span style={{ color: '#1890ff' }}>是</span> : <span style={{ color: 'red' }}>否</span>
+            },
+        },
+        {
+            title: '是否排除已在群的客户',
+            dataIndex: 'excludeInGroup',
+            key: 'excludeInGroup',
+            width: 60,
+            align: 'center',
+            render(value) {
+                return value ? <span style={{ color: '#1890ff' }}>是</span> : <span style={{ color: 'red' }}>否</span>
+            },
+        },
+        {
+            title: '拉群完成后自动删除拉群标签',
+            dataIndex: 'excludeInGroup',
+            key: 'excludeInGroup',
+            width: 70,
+            align: 'center',
+            render(value) {
+                return value ? <span style={{ color: '#1890ff' }}>是</span> : <span style={{ color: 'red' }}>否</span>
+            },
+        },
+        {
+            title: '是否开启失败人群重试',
+            dataIndex: 'isRetry',
+            key: 'isRetry',
+            width: 70,
+            align: 'center',
+            render(value) {
+                return value ? <span style={{ color: '#1890ff' }}>是</span> : <span style={{ color: 'red' }}>否</span>
+            },
+        },
+        {
+            title: '群聊关联公众号',
+            dataIndex: 'weChatAppid',
+            key: 'weChatAppid',
+            width: 100,
+            align: 'center',
+            render(value) {
+                return value || '--'
+            },
+        },
+        {
+            title: '拉群完成后群聊备注',
+            dataIndex: 'remark',
+            key: 'remark',
+            width: 140,
+            render(value) {
+                return value || '--'
+            },
+        },
+        {
+            title: '拉群完成后群聊智能标签',
+            dataIndex: 'tagDTO',
+            key: 'tagDTO',
+            width: 200,
+            render(value) {
+                return value && Object.keys(value)?.length > 0 ?
+                    <Paragraph ellipsis style={{ margin: 0 }}>{Object.keys(value).map(key => {
+                        if (key === 'business' && value[key]) {
+                            return `业务(来源渠道):${businessPlanData.find(i => i.value === value.business)?.label || '<空>'}`
+                        } else if (key === 'bookCity' && value[key]) {
+                            return `书城(来源渠道):${bookPlatForm.find(i => i.id === value.bookCity)?.platformName || '<空>'}`
+                        } else if (key === 'product' && value[key]) {
+                            return `产品(来源渠道):${bookList.find(i => i.id === value.product)?.bookName || '<空>'}`
+                        }
+                        return ''
+                    }).join('、')}</Paragraph> : '--'
+            },
+        },
+        {
+            title: '客服消息',
+            dataIndex: 'groupSendMsg',
+            key: 'groupSendMsg',
+            width: 140,
+            render(value) {
+                return value ? value : '--'
+            },
+        },
+    ]
+
+}

+ 56 - 0
src/pages/weComTask/page/groupChat/create/typings.d.ts

@@ -0,0 +1,56 @@
+declare namespace GROUP_CHAT_CREATE {
+    interface corpUsersProps {
+        corpUserId: string,
+        name: string,
+        corpName: string,
+        corpId: string,
+    }
+    interface CorpUserChatProps extends corpUsersProps {
+        value: number
+        label: string
+    }
+    interface SettingsProps {
+        corpUserChat: CorpUserChatProps[]
+        corpUsers?: corpUsersProps[]; // 企微号列表
+        robotCorpUsers?: corpUsersProps[]; // 机器人企微号列表
+        bizType?: string; // 业务类型
+        platform?: string; // 书城
+        channel?: string;   // 渠道
+        templateProductId?: string; // 适用产品
+        strategyDTO?: { [x: string]: any };
+    }
+    interface DispatchGroupChatCreate {
+        settings: SettingsProps
+        setSettings: React.Dispatch<React.SetStateAction<SettingsProps>>
+        onPreviewReset: () => void
+        bookPlatForm: TASK_CREATE.BookPlatFormProps[]
+        bookList: TASK_CREATE.BookListProps[]
+    }
+    interface FoundationProps<T> extends TASK_CREATE.DefaultProps, TASK_CREATE.DefaultChangeProps<T> {
+        value?: { [x: string]: any };
+    }
+    interface AddGroupObjectProps {
+        bookPlatForm: TASK_CREATE.BookPlatFormProps[]
+        bookList: TASK_CREATE.BookListProps[]
+        strategyList: any
+        index: number
+        value?: { [x: string]: any }[];
+        onChange?: (value: { [x: string]: any }[]) => void;
+        onCopy?: (data: any, copyIndex: number) => void
+    }
+    interface ShowGroupUserTableProps {
+        bookPlatForm: TASK_CREATE.BookPlatFormProps[]
+        bookList: TASK_CREATE.BookListProps[]
+        value: { [x: string]: any }[];
+        handleEdit?: (data: { [x: string]: any }) => void
+        handleDelete?: (id: number) => void
+        isPreview?: boolean
+        handleCopy?: (data: any) => void
+    }
+    interface AddGroupObjectModalProps {
+        initialValues?: any;
+        onChange?: (value: { [x: string]: any }[]) => void;
+        visible?: boolean;
+        onClose?: () => void;
+    }
+}

+ 519 - 0
src/pages/weComTask/page/groupChat/taskList/details.tsx

@@ -0,0 +1,519 @@
+import { useAjax } from "@/Hook/useAjax"
+import { getProjectLogDetailsListApi, getProjectLogListApi, getProjectTaskLogCountApi, getProjectTaskLogListApi } from "@/pages/weComTask/API/groupChat"
+import FilterUserTooltip from "@/pages/weComTask/components/filterUser/filterUserTooltip";
+import { Badge, Button, Drawer, Flex, Modal, Popover, Space, Table, Typography } from "antd"
+import React, { useEffect, useState } from "react"
+import { QuestionCircleFilled } from "@ant-design/icons"
+import { businessPlanData, LQTaskStatus, TIME_TYPE_ZJ } from "../../businessPlan/create/const";
+import PreviewTime from "@/pages/weComTask/components/previewTime";
+const { Text, Paragraph, Title } = Typography;
+
+interface Props {
+    data: any,
+    bookPlatForm: TASK_CREATE.BookPlatFormProps[]
+    bookList: TASK_CREATE.BookListProps[]
+    visible?: boolean,
+    onClose?: () => void,
+}
+
+const Details: React.FC<Props> = ({ data, bookPlatForm, bookList, visible, onClose }) => {
+
+    /*******************************************/
+    const getProjectLogList = useAjax((params) => getProjectLogListApi(params))
+    /*******************************************/
+
+    useEffect(() => {
+        getProjectLogList.run(data.id)
+    }, [])
+
+    return <Drawer
+        title={<Space>
+            <strong>{data.taskName} 运营计划</strong>
+            <Button type="link" onClick={() => getProjectLogList.refresh()}>刷新</Button>
+        </Space>}
+        onClose={onClose}
+        open={visible}
+        width={1400}
+        styles={{ body: { paddingTop: 5 } }}
+    >
+
+        <Table
+            size='small'
+            bordered
+            rowKey={'id'}
+            dataSource={getProjectLogList?.data?.data || []}
+            scroll={{ y: 1000 }}
+            columns={[
+                {
+                    title: '任务名称',
+                    dataIndex: 'taskName',
+                    key: 'taskName',
+                    width: 180,
+                    ellipsis: true,
+                    fixed: 'left'
+                },
+                {
+                    title: '群名称',
+                    dataIndex: 'groupName',
+                    key: 'groupName',
+                    width: 120,
+                    ellipsis: true,
+                    fixed: 'left'
+                },
+                {
+                    title: '计划状态',
+                    dataIndex: 'taskStatus',
+                    key: 'taskStatus',
+                    width: 90,
+                    align: 'center',
+                    fixed: 'left',
+                    render(value) {
+                        return LQTaskStatus[value] || '--'
+                    },
+                },
+                {
+                    title: '执行时间',
+                    dataIndex: 'timeRepeatType',
+                    key: 'timeRepeatType',
+                    width: 100,
+                    ellipsis: true,
+                    align: 'center',
+                    render(value, records: any) {
+                        return <>
+                            {TIME_TYPE_ZJ[value] || '--'}
+                            {value !== 'TIME_TYPE_SINGLE_TIMELY' && <Popover
+                                placement="left"
+                                content={<div>
+                                    <PreviewTime
+                                        {...records}
+                                        sendTime={records?.sendTime || records?.repeatSendTime}
+                                    />
+                                </div>}
+                                styles={{ body: { width: 300, overflow: 'hidden', overflowY: 'auto', maxHeight: 400 } }}
+                            >
+                                <a style={{ color: '#000' }}><QuestionCircleFilled /></a>
+                            </Popover>}
+                        </>
+                    },
+                },
+                {
+                    title: '是否开启已有旧群聊补缺',
+                    dataIndex: 'isRepair',
+                    key: 'isRepair',
+                    width: 150,
+                    ellipsis: true,
+                    render(value, record: any) {
+                        return value ? <Space>
+                            <Badge status="success" text="开启" />
+                            <span>近{record.repairTimes}天</span>
+                        </Space> : <Badge status="error" text="关闭" />
+                    },
+                },
+                {
+                    title: '进群对象',
+                    dataIndex: 'corpCondition',
+                    key: 'corpCondition',
+                    width: 90,
+                    align: 'center',
+                    render(value) {
+                        return !value?.[0]?.allCorpUser ? <div style={{ display: 'flex', alignItems: 'center' }}>
+                            <div style={{ flex: '1 0', overflow: 'hidden' }}>
+                                <Text ellipsis>指定</Text>
+                            </div>
+                            <Popover
+                                placement="right"
+                                styles={{ body: { maxWidth: 350, maxHeight: 350, overflow: 'hidden', overflowY: 'auto' } }}
+                                mouseEnterDelay={0.5}
+                                content={<FilterUserTooltip
+                                    bookCityList={bookPlatForm?.map(item => ({ label: item.platformName, value: item.platformKey }))}
+                                    configName={'进群对象'}
+                                    data={value?.[0]}
+                                />}
+                            >
+                                <a style={{ color: '#000' }}><QuestionCircleFilled /></a>
+                            </Popover>
+                        </div> : '全部'
+                    },
+                },
+                {
+                    title: '群递增起始编号',
+                    dataIndex: 'groupIndex',
+                    key: 'groupIndex',
+                    width: 60,
+                    align: 'center'
+                },
+                {
+                    title: '群固定人数',
+                    dataIndex: 'groupUserCount',
+                    key: 'groupUserCount',
+                    width: 50,
+                    align: 'center'
+                },
+                {
+                    title: '邀请客户进群完毕后客服号是否退群',
+                    dataIndex: 'autoOutGroup',
+                    key: 'autoOutGroup',
+                    width: 85,
+                    align: 'center',
+                    render(value) {
+                        return value ? <span style={{ color: '#1890ff' }}>是</span> : <span style={{ color: 'red' }}>否</span>
+                    },
+                },
+                {
+                    title: '是否排除已在群的客户',
+                    dataIndex: 'excludeInGroup',
+                    key: 'excludeInGroup',
+                    width: 60,
+                    align: 'center',
+                    render(value) {
+                        return value ? <span style={{ color: '#1890ff' }}>是</span> : <span style={{ color: 'red' }}>否</span>
+                    },
+                },
+                {
+                    title: '是否开启失败人群重试',
+                    dataIndex: 'isRetry',
+                    key: 'isRetry',
+                    width: 70,
+                    align: 'center',
+                    render(value) {
+                        return value ? <span style={{ color: '#1890ff' }}>是</span> : <span style={{ color: 'red' }}>否</span>
+                    },
+                },
+                {
+                    title: '拉群完成后自动删除拉群标签',
+                    dataIndex: 'excludeInGroup',
+                    key: 'excludeInGroup',
+                    width: 70,
+                    align: 'center',
+                    render(value) {
+                        return value ? <span style={{ color: '#1890ff' }}>是</span> : <span style={{ color: 'red' }}>否</span>
+                    },
+                },
+                {
+                    title: '群聊关联公众号',
+                    dataIndex: 'mpAccountInfo',
+                    key: 'mpAccountInfo',
+                    width: 100,
+                    align: 'center',
+                    ellipsis: true,
+                    render(value) {
+                        return value?.name || '--'
+                    },
+                },
+                {
+                    title: '拉群完成后群聊备注',
+                    dataIndex: 'remark',
+                    key: 'remark',
+                    width: 140,
+                    ellipsis: true,
+                    render(value) {
+                        return value || '--'
+                    },
+                },
+                {
+                    title: '拉群完成后群聊智能标签',
+                    dataIndex: 'msgTagDTO',
+                    key: 'msgTagDTO',
+                    width: 200,
+                    ellipsis: true,
+                    render(value) {
+                        return value && Object.keys(value)?.length > 0 ?
+                            <Paragraph ellipsis style={{ margin: 0 }}>{Object.keys(value).map(key => {
+                                if (key === 'business' && value[key]) {
+                                    return `业务(来源渠道):${businessPlanData.find(i => i.value === value.business)?.label || '<空>'}`
+                                } else if (key === 'bookCity' && value[key]) {
+                                    return `书城(来源渠道):${bookPlatForm.find(i => i.id === value.bookCity)?.platformName || '<空>'}`
+                                } else if (key === 'product' && value[key]) {
+                                    return `产品(来源渠道):${bookList.find(i => i.id === value.product)?.bookName || '<空>'}`
+                                }
+                                return ''
+                            }).join('、')}</Paragraph> : '--'
+                    },
+                },
+                {
+                    title: '客服消息',
+                    dataIndex: 'groupSendMsg',
+                    key: 'groupSendMsg',
+                    width: 140,
+                    ellipsis: true,
+                    render(value) {
+                        return value ? value : '--'
+                    },
+                },
+                {
+                    title: '创建人',
+                    dataIndex: 'createBy',
+                    key: 'createBy',
+                    width: 80,
+                    align: 'center',
+                    ellipsis: true,
+                    render(value) {
+                        return value?.nickname || '--'
+                    }
+                }
+            ]}
+            expandable={{
+                fixed: 'left',
+                expandRowByClick: true,
+                expandedRowRender: (record) => <ExpandedRow record={record} />
+            }}
+        />
+    </Drawer>
+}
+
+const ExpandedRow: React.FC<{ record: any }> = ({ record }) => {
+
+    /*****************************************/
+    const [detalisData, setDetalisData] = useState<{ visible?: boolean, taskLogId?: number }>()
+    const [queryForm, setQueryForm] = useState<{ pageNum: number, pageSize: number }>({ pageNum: 1, pageSize: 20 })
+    const [failedUserDetails, setFailedUserDetails] = useState<{ visible?: boolean, list: any[] }>({ visible: false, list: [] })
+    const [total, setTotal] = useState<{ pullGroupCount?: number, pullGroupUserCount?: number, pullGroupUserSuccessCount?: number, pullSuccessCount?: number }>({})
+
+    const getProjectTaskLogList = useAjax((params) => getProjectTaskLogListApi(params))
+    const getProjectTaskLogCount = useAjax((params) => getProjectTaskLogCountApi(params))
+    /*****************************************/
+
+    useEffect(() => {
+        getProjectTaskLogList.run({ ...queryForm, taskId: record.id })
+    }, [record.id, queryForm])
+
+    useEffect(() => {
+        getProjectTaskLogCount.run(record.id).then(res => {
+            setTotal(res?.data || {})
+        })
+    }, [record.id, queryForm])
+
+    return <div style={{ maxHeight: 450 }}>
+        <Flex gap={20} align="center" style={{ marginBottom: 10, marginTop: 5 }}>
+            <Title level={4} style={{ margin: 0 }}>{record?.taskName} 任务列表</Title>
+            <Button type="link" onClick={() => getProjectTaskLogList.refresh()}>刷新</Button>
+            <Text strong>预计拉群数:{total?.pullGroupCount || 0}</Text>
+            <Text strong>预计拉群客户数:{total?.pullGroupUserCount || 0}</Text>
+            <Text strong>拉群成功总客户数:{total?.pullGroupUserSuccessCount || 0}</Text>
+            <Text strong>拉群成功总数数:{total?.pullSuccessCount || 0}</Text>
+        </Flex>
+        <Table
+            size='small'
+            bordered
+            rowKey={'id'}
+            dataSource={getProjectTaskLogList?.data?.data?.records}
+            loading={getProjectTaskLogList.loading}
+            scroll={{ x: 400, y: 350 }}
+            columns={[
+                {
+                    title: '操作',
+                    dataIndex: 'cz',
+                    key: 'cz',
+                    width: 80,
+                    align: 'center',
+                    render(_, record: any) {
+                        return <a onClick={() => setDetalisData({ visible: true, taskLogId: record.id })}>详情</a>
+                    }
+                },
+                {
+                    title: '预计拉群数',
+                    dataIndex: 'pullGroupCount',
+                    key: 'pullGroupCount',
+                    width: 120
+                },
+                {
+                    title: '预计拉群客户数',
+                    dataIndex: 'pullGroupUserCount',
+                    key: 'pullGroupUserCount',
+                    width: 120
+                },
+                {
+                    title: '上次失败补拉数量',
+                    dataIndex: 'lastFailedRetryCount',
+                    key: 'lastFailedRetryCount',
+                    width: 120
+                },
+                {
+                    title: '实际拉群客户数',
+                    dataIndex: 'pullSuccessUserCount',
+                    key: 'pullSuccessUserCount',
+                    width: 120
+                },
+                {
+                    title: '实际拉群数',
+                    dataIndex: 'pullSuccessCount',
+                    key: 'pullSuccessCount',
+                    width: 120
+                },
+                {
+                    title: '失败客户数',
+                    dataIndex: 'pullFailedUserCount',
+                    key: 'pullFailedUserCount',
+                    width: 120,
+                    render: (a, b) => {
+                        return <a onClick={() => setFailedUserDetails({ visible: true, list: b?.pullFailedUserDetail || [] })}>{a}</a>
+                    }
+                },
+                {
+                    title: '执行时间',
+                    dataIndex: 'createTime',
+                    key: 'createTime'
+                }
+            ]}
+            pagination={{
+                total: getProjectTaskLogList?.data?.data?.total || 0,
+                current: queryForm.pageNum,
+                pageSize: queryForm.pageSize,
+                showTotal: (total) => `共 ${total} 条数据`,
+                onChange: (pageNum, pageSize) => setQueryForm({ pageNum, pageSize })
+            }}
+        />
+
+        {/* 详情 */}
+        {detalisData?.visible && <TaskDetails
+            {...detalisData}
+            onClose={() => setDetalisData(undefined)}
+        />}
+
+        {/* 失败客户详情 */}
+        {failedUserDetails?.visible && <FailedUserDetails
+            {...failedUserDetails}
+            onClose={() => setFailedUserDetails({ visible: false, list: [] })}
+        />}
+    </div>
+}
+
+
+const TaskDetails: React.FC<{
+    visible?: boolean,
+    taskLogId?: number,
+    onClose?: () => void
+}> = ({ visible, taskLogId, onClose }) => {
+
+    /*******************************************/
+    const getProjectLogDetailsList = useAjax((params) => getProjectLogDetailsListApi(params))
+    /*******************************************/
+
+    useEffect(() => {
+        getProjectLogDetailsList.run(taskLogId)
+    }, [taskLogId])
+
+    return <Modal
+        title={<strong>任务详情</strong>}
+        open={visible}
+        onCancel={onClose}
+        width={900}
+        footer={null}
+    >
+        <Table
+            size='small'
+            bordered
+            rowKey={'id'}
+            dataSource={getProjectLogDetailsList?.data?.data || []}
+            scroll={{ y: 1000 }}
+            columns={[
+                {
+                    title: '群名称',
+                    dataIndex: 'chatName',
+                    key: 'chatName',
+                    width: 120,
+                    ellipsis: true,
+                },
+                {
+                    title: '群主号名称',
+                    dataIndex: 'corpUserName',
+                    key: 'corpUserName',
+                    width: 120,
+                    ellipsis: true,
+                },
+                {
+                    title: '预计拉群客户数',
+                    dataIndex: 'pullGroupUserCount',
+                    key: 'pullGroupUserCount',
+                    width: 80,
+                    align: 'center'
+                },
+                {
+                    title: '实际拉群客户数',
+                    dataIndex: 'actualPullGroupUserCount',
+                    key: 'actualPullGroupUserCount',
+                    width: 80,
+                    align: 'center'
+                },
+                {
+                    title: '删除或拉黑客户数',
+                    dataIndex: 'deleteOrBlockUserCount',
+                    key: 'deleteOrBlockUserCount',
+                    width: 80,
+                    align: 'center'
+                }
+            ]}
+        />
+    </Modal>
+}
+
+
+const FailedUserDetails: React.FC<{
+    visible?: boolean,
+    list?: any[],
+    onClose?: () => void
+}> = ({ visible, list, onClose }) => {
+
+
+    return <Modal
+        title={<strong>任务详情</strong>}
+        open={visible}
+        onCancel={onClose}
+        width={900}
+        footer={null}
+    >
+        <Table
+            size='small'
+            bordered
+            rowKey={'externalUserId'}
+            dataSource={list || []}
+            scroll={{ y: 1000 }}
+            columns={[
+                {
+                    title: '企业名称',
+                    dataIndex: 'corpName',
+                    key: 'corpName',
+                    width: 120,
+                    ellipsis: true,
+                },
+                {
+                    title: '企业ID',
+                    dataIndex: 'corpId',
+                    key: 'corpId',
+                    width: 120,
+                    ellipsis: true,
+                },
+                {
+                    title: '客服名称',
+                    dataIndex: 'corpUserName',
+                    key: 'corpUserName',
+                    width: 120,
+                    ellipsis: true,
+                },
+                {
+                    title: '客服ID',
+                    dataIndex: 'corpUserId',
+                    key: 'corpUserId',
+                    width: 120,
+                    ellipsis: true,
+                },
+                {
+                    title: '客服名称',
+                    dataIndex: 'name',
+                    key: 'name',
+                    width: 120,
+                    ellipsis: true,
+                },
+                {
+                    title: '客户ID',
+                    dataIndex: 'externalUserId',
+                    key: 'externalUserId',
+                    width: 120,
+                    ellipsis: true,
+                }
+            ]}
+        />
+    </Modal>
+}
+
+export default React.memo(Details)

+ 203 - 0
src/pages/weComTask/page/groupChat/taskList/index.tsx

@@ -0,0 +1,203 @@
+import { useAjax } from '@/Hook/useAjax';
+import { cancelProjectApi, delProjectApi, getProjectListApi } from '@/pages/weComTask/API/groupChat';
+import { App, Button, Card, DatePicker, Input, Popconfirm, Space, Table } from 'antd';
+import React, { useEffect, useState } from 'react';
+import { DeleteOutlined, PauseCircleOutlined, SearchOutlined } from '@ant-design/icons';
+import dayjs from 'dayjs';
+import taskListColumns from './tableConfig';
+import { toJS } from 'mobx';
+import { inject, observer } from 'mobx-react';
+import Details from './details';
+
+const TaskList: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookListProps[], bookPlatForm: TASK_CREATE.BookPlatFormProps[] } } }> = ({ weComTaskStore }) => {
+
+    /**********************************************/
+    const { bookList, bookPlatForm } = toJS(weComTaskStore.data)
+    const { message } = App.useApp();
+
+    const projectName = sessionStorage.getItem('CAMPCORP_PG')
+    const [queryForm, setQueryForm] = useState<GROUP_CHAT_API.GetProjectListProps>({ pageNum: 1, pageSize: 20, projectName })
+    const [queryFormNew, setQueryFormNew] = useState<GROUP_CHAT_API.GetProjectListProps>({ pageNum: 1, pageSize: 20, projectName })
+    const [selectedRows, setselectedRows] = useState<any[]>([])
+    const [logOpenData, setLogOpenData] = useState<{ visible: boolean, data: any }>({ visible: false, data: {} })
+
+    const getProjectList = useAjax((params) => getProjectListApi(params))
+    const delProject = useAjax((params) => delProjectApi(params))
+    const cancelProject = useAjax((params) => cancelProjectApi(params))
+    /**********************************************/
+
+    useEffect(() => {
+        const projectName = sessionStorage.getItem('CAMPCORP_PG')
+        if (projectName) {
+            sessionStorage.removeItem('CAMPCORP_PG')
+        }
+    }, [])
+
+    useEffect(() => {
+        getProjectList.run(queryFormNew)
+    }, [queryFormNew])
+
+    // 复制
+    const handleCopy = (data: any, isCopy: boolean) => {
+        sessionStorage.setItem('PG_OFFICIALTASKID', JSON.stringify({ id: data.id, isCopy }))
+        sessionStorage.setItem('oldPath', '/weComTask/groupChat/create')
+        window.location.href = '/weComTask#/weComTask/groupChat/create'
+    }
+
+    // 日志
+    const handleLog = (data: any) => {
+        setLogOpenData({ visible: true, data })
+    }
+    // 删除
+    const handleDel = (data: { projectIds: number[] }, type: 'del' | 'cancel' | 'open') => {
+        const hide = message.loading(type === 'del' ? '正在删除...' : '正在取消...', 0)
+        switch (type) {
+            case 'del':
+                delProject.run(data).then(res => {
+                    hide()
+                    setselectedRows([])
+                    if (res?.data) {
+                        message.success('删除成功')
+                        getProjectList.refresh()
+                    } else {
+                        message.error('删除失败')
+                    }
+                }).catch(() => hide())
+                break
+            case 'cancel':
+            case 'open':
+                cancelProject.run({...data, pause: type === 'cancel' ? false : true}).then(res => {
+                    hide()
+                    setselectedRows([])
+                    if (res?.data) {
+                        message.success(type === 'cancel' ? '取消成功' : '启用成功')
+                        getProjectList.refresh()
+                    } else {
+                        message.error(type === 'cancel' ? '取消失败' : '启用失败')
+                    }
+                }).catch(() => hide())
+                break
+        }
+
+    }
+
+    return <Card>
+        <div style={{ display: 'flex', gap: 10, marginBottom: 10 }}>
+            <Space.Compact>
+                <Button>任务名称</Button>
+                <Input placeholder='请输入任务名称' allowClear value={queryForm?.projectName} onChange={(e) => setQueryForm({ ...queryForm, projectName: e.target.value })} />
+            </Space.Compact>
+            <Space.Compact>
+                <Button>创建时间</Button>
+                <DatePicker.RangePicker
+                    placeholder={["创建时间开始", "创建时间结束"]}
+                    value={queryForm?.createTimeMin ? [dayjs(queryForm?.createTimeMin), dayjs(queryForm?.createTimeMax)] : undefined}
+                    onChange={(_, options) => {
+                        const newQueryForm = { ...queryForm }
+                        if (options?.[0]) {
+                            newQueryForm.createTimeMin = options?.[0] + ' 00:00:00'
+                            newQueryForm.createTimeMax = options?.[1] + ' 23:59:59'
+                        } else {
+                            delete newQueryForm?.createTimeMin
+                            delete newQueryForm?.createTimeMax
+                        }
+                        setQueryForm(newQueryForm)
+                    }}
+                />
+            </Space.Compact>
+            <Button
+                type='primary'
+                onClick={() => {
+                    setQueryFormNew({ ...queryForm, pageNum: 1 })
+                }}
+                icon={<SearchOutlined />}
+            >搜索</Button>
+            <Popconfirm
+                title="确定删除?"
+                onConfirm={() => { handleDel({ projectIds: selectedRows.map(i => i.id) }, 'del') }}
+                disabled={selectedRows.length === 0}
+            >
+                <Button type='primary' danger icon={<DeleteOutlined />} loading={delProject.loading} disabled={selectedRows.length === 0}>删除</Button>
+            </Popconfirm>
+            <Popconfirm
+                title="确定暂停?"
+                onConfirm={() => { handleDel({ projectIds: selectedRows.map(i => i.id) }, 'cancel') }}
+                disabled={selectedRows.length === 0}
+            >
+                <Button type='primary' style={{ backgroundColor: 'orange', borderColor: 'orange' }} loading={cancelProject.loading} icon={<PauseCircleOutlined />} disabled={selectedRows.length === 0}>暂停任务</Button>
+            </Popconfirm>
+            <Popconfirm
+                title="确定启用?"
+                onConfirm={() => { handleDel({ projectIds: selectedRows.map(i => i.id) }, 'open') }}
+                disabled={selectedRows.length === 0}
+            >
+                <Button type='primary' style={{ backgroundColor: '#87d068', borderColor: '#87d068' }} loading={cancelProject.loading} icon={<PauseCircleOutlined />} disabled={selectedRows.length === 0}>启用任务</Button>
+            </Popconfirm>
+        </div>
+
+        <Table
+            dataSource={getProjectList?.data?.data?.records}
+            columns={taskListColumns(bookPlatForm, bookList, handleLog, handleCopy, handleDel)}
+            rowKey={'id'}
+            bordered={true}
+            size='small'
+            scroll={{ y: 600, x: 1200 }}
+            pagination={{
+                current: getProjectList?.data?.data?.current,
+                pageSize: getProjectList?.data?.data?.size,
+                total: getProjectList?.data?.data?.total,
+                showSizeChanger: true,
+                onChange: (page, pageSize) => {
+                    setQueryFormNew({ ...queryFormNew, pageNum: page, pageSize })
+                    setQueryForm({ ...queryForm, pageNum: page, pageSize })
+                }
+            }}
+            rowSelection={{
+                selectedRowKeys: selectedRows?.map((item: any) => item?.id),
+                onSelect: (record: { id: string }, selected: boolean) => {
+                    let newData = JSON.parse(JSON.stringify(selectedRows))
+                    if (selected) {
+                        newData.push({ ...record })
+                    } else {
+                        newData = newData.filter((item: { id: string }) => item.id !== record.id)
+                    }
+                    setselectedRows(newData)
+                },
+                onSelectAll: (selected: boolean, _: { id: string }[], changeRows: { id: string }[]) => {
+                    let newData = JSON.parse(JSON.stringify(selectedRows || '[]'))
+                    if (selected) {
+                        changeRows.forEach((item: { id: string }) => {
+                            let index = newData.findIndex((ite: { id: string }) => ite.id === item.id)
+                            if (index === -1) {
+                                newData.push(item)
+                            }
+                        })
+                    } else {
+                        let newSelectAccData = newData.filter((item: { id: string }) => {
+                            let index = changeRows.findIndex((ite: { id: string }) => ite.id === item.id)
+                            if (index !== -1) {
+                                return false
+                            } else {
+                                return true
+                            }
+                        })
+                        newData = newSelectAccData
+                    }
+                    setselectedRows(newData)
+                }
+            }}
+        />
+
+        {/* 日志 */}
+        {logOpenData.visible && <Details
+            {...logOpenData}
+            bookPlatForm={bookPlatForm}
+            bookList={bookList}
+            onClose={() => {
+                setLogOpenData({ visible: false, data: undefined })
+            }}
+        />}
+    </Card>
+};
+
+export default inject('store')(observer((props: any) => TaskList(props.store)));

+ 156 - 0
src/pages/weComTask/page/groupChat/taskList/tableConfig.tsx

@@ -0,0 +1,156 @@
+import { Popconfirm, Popover, Space, Typography } from "antd";
+import { AnyObject } from "antd/es/_util/type";
+import { ColumnsType } from "antd/es/table";
+import style from '../../businessPlan/taskList/index.less'
+import { QuestionCircleFilled } from '@ant-design/icons';
+import PreviewStrategy from "../create/components/strategy/previewStrategy";
+import PreviewGroupUser from "../create/components/groupUser/previewGroupUser";
+import { getPullGroupData } from "../create/const";
+
+const { Text, Paragraph } = Typography;
+const taskListColumns = (
+    bookPlatForm: TASK_CREATE.BookPlatFormProps[],
+    bookList: TASK_CREATE.BookListProps[],
+    handleLog: (data: any) => void,
+    handleCopy: (data: any, isCopy: boolean) => void,
+    handleDel: (data: any, type: 'del' | 'cancel' | 'open') => void,
+): ColumnsType<AnyObject> => {
+
+    return [
+        {
+            title: '操作',
+            dataIndex: 'cz',
+            key: 'cz',
+            width: 160,
+            render(_, record) {
+                return <Space>
+                    {record?.status === 1 ? <Popconfirm
+                        title="确定暂停?"
+                        onConfirm={() => { handleDel({ projectIds: [record.id] }, 'cancel') }}
+                    >
+                        <a style={{ color: 'orange' }}>暂停任务</a>
+                    </Popconfirm> : record?.status === 3 ? <Popconfirm
+                        title="确定启用?"
+                        onConfirm={() => { handleDel({ projectIds: [record.id] }, 'open') }}
+                    >
+                        <a style={{ color: '#87d068' }}>启用任务</a>
+                    </Popconfirm> : undefined}
+                    <a onClick={() => handleCopy(record, true)}>复制</a>
+                    <a onClick={() => handleCopy(record, false)}>编辑</a>
+                    <a onClick={() => handleLog(record)}>详情</a>
+                    <Popconfirm
+                        title="确定删除?"
+                        onConfirm={() => { handleDel({ projectIds: [record.id] }, 'del') }}
+                    >
+                        <a style={{ color: 'red' }}>删除</a>
+                    </Popconfirm>
+                </Space>
+            },
+        },
+        {
+            title: '任务名称',
+            dataIndex: 'taskName',
+            key: 'taskName',
+            width: 120,
+            ellipsis: true
+        },
+        {
+            title: '基础信息',
+            dataIndex: 'bizType',
+            key: 'bizType',
+            width: 180,
+            render: (_, record) => {
+                return <Paragraph style={{ margin: 0 }} ellipsis={{ tooltip: true }}>
+                    业务类型:{record?.bizType === 'novel' ? '小说' : '<空>'}-书城:{record?.platformName || '<空>'}-适用产品:{record?.templateProductName || '<空>'}
+                </Paragraph>
+            }
+        },
+        {
+            title: '群主号',
+            dataIndex: 'corpChatUserList',
+            key: 'corpChatUserList',
+            width: 130,
+            render(value) {
+                return <Paragraph style={{ margin: 0 }} ellipsis={{ tooltip: true }}>{value.map((item) => item.name + `(${item.corpName})`).join('、')}</Paragraph>
+            }
+        },
+        {
+            title: '机器人客服号',
+            dataIndex: 'corpRobots',
+            key: 'corpRobots',
+            width: 130,
+            render(value) {
+                return value?.length > 0 ? <Paragraph style={{ margin: 0 }} ellipsis={{ tooltip: true }}>{value.map((item) => item.corpUserName + `(${item.corpName})`).join('、')}</Paragraph> : '--'
+            }
+        },
+        {
+            title: '客服号',
+            dataIndex: 'corpUsers',
+            key: 'corpUsers',
+            width: 150,
+            render(value) {
+                return <Paragraph style={{ margin: 0 }} ellipsis={{ tooltip: true }}>{value.map((item) => item.corpUserName + `(${item.corpName})`).join('、')}</Paragraph>
+            }
+        },
+        {
+            title: '群聊创建配置预览',
+            dataIndex: 'strategyList',
+            key: 'strategyList',
+            width: 150,
+            ellipsis: true,
+            render: (_, record) => {
+                if (record?.strategyList?.length > 0) {
+                    const data = getPullGroupData(record?.strategyList || [])
+                    return <div className={style.nameBox}>
+                        <Popover
+                            placement="left"
+                            content={<div>
+                                <PreviewStrategy strategyDTO={{
+                                    strategyList: record?.strategyList
+                                }} />
+                            </div>}
+                            styles={{ body: { width: 300, overflow: 'hidden', overflowY: 'auto', maxHeight: 400 } }}
+                        >
+                            <a>策略 <QuestionCircleFilled /></a>
+                        </Popover>
+                        <Popover
+                            placement="left"
+                            content={<div>
+                                <PreviewGroupUser
+                                    strategyList={data?.strategyList || []}
+                                    bookList={bookList}
+                                    bookPlatForm={bookPlatForm}
+                                />
+                            </div>}
+                            styles={{ body: { width: 700, overflow: 'hidden', overflowY: 'auto', maxHeight: 400 } }}
+                        >
+                            <a>群配置 <QuestionCircleFilled /></a>
+                        </Popover>
+                    </div>
+                }
+                return <Text type="danger">当前没有欢迎语配置</Text>
+            }
+        },
+        {
+            title: '创建时间',
+            dataIndex: 'createTime',
+            key: 'createTime',
+            align: 'center',
+            width: 125,
+            ellipsis: true
+        },
+        {
+            title: '拉群任务数量',
+            dataIndex: 'corpPullGroupTaskCount',
+            key: 'corpPullGroupTaskCount',
+            width: 100,
+            align: 'center',
+            render(value) {
+                return value
+            },
+        },
+    ]
+}
+
+
+export default taskListColumns

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác