wjx 2 недель назад
Родитель
Сommit
8a8ddd3c71
34 измененных файлов с 2637 добавлено и 580 удалено
  1. 1 1
      src/Hook/useNewToken.tsx
  2. 7 367
      src/global.less
  3. 0 46
      src/layout/index.tsx
  4. 80 0
      src/pages/weComTask/API/groupChat/index.ts
  5. 14 0
      src/pages/weComTask/API/groupChat/typings.d.ts
  6. 122 0
      src/pages/weComTask/API/groupLeaderManage/index.ts
  7. 2 0
      src/pages/weComTask/page/bookLink/index.tsx
  8. 1 0
      src/pages/weComTask/page/businessPlan/create/global.less
  9. 1 1
      src/pages/weComTask/page/businessPlan/create/tableConfig.tsx
  10. 2 1
      src/pages/weComTask/page/businessPlan/taskList/index.tsx
  11. 1 1
      src/pages/weComTask/page/businessPlan/taskList/log.tsx
  12. 2 2
      src/pages/weComTask/page/corpUserManage/global.less
  13. 125 125
      src/pages/weComTask/page/corpUserManage/selectCorpUser.tsx
  14. 49 11
      src/pages/weComTask/page/groupChat/create/components/groupUser/addGroupObject.tsx
  15. 12 6
      src/pages/weComTask/page/groupChat/create/components/groupUser/index.tsx
  16. 11 2
      src/pages/weComTask/page/groupChat/create/components/strategy/index.tsx
  17. 2 2
      src/pages/weComTask/page/groupChat/create/components/strategy/previewStrategy.tsx
  18. 2 2
      src/pages/weComTask/page/groupChat/create/components/strategy/settingsStrategy.tsx
  19. 35 0
      src/pages/weComTask/page/groupChat/create/const.ts
  20. 363 13
      src/pages/weComTask/page/groupChat/create/index.tsx
  21. 212 0
      src/pages/weComTask/page/groupChat/create/tableConfig.tsx
  22. 6 0
      src/pages/weComTask/page/groupChat/create/typings.d.ts
  23. 232 0
      src/pages/weComTask/page/groupChat/taskList/details.tsx
  24. 195 0
      src/pages/weComTask/page/groupChat/taskList/index.tsx
  25. 151 0
      src/pages/weComTask/page/groupChat/taskList/tableConfig.tsx
  26. 68 0
      src/pages/weComTask/page/groupLeaderManage/addGL.tsx
  27. 173 0
      src/pages/weComTask/page/groupLeaderManage/bindDetails.tsx
  28. 113 0
      src/pages/weComTask/page/groupLeaderManage/bindUser.tsx
  29. 12 0
      src/pages/weComTask/page/groupLeaderManage/global.less
  30. 156 0
      src/pages/weComTask/page/groupLeaderManage/index.tsx
  31. 213 0
      src/pages/weComTask/page/groupLeaderManage/selectCorpUserChatUser.tsx
  32. 153 0
      src/pages/weComTask/page/groupLeaderManage/selectGDUser.tsx
  33. 106 0
      src/pages/weComTask/page/groupLeaderManage/tableConfig.tsx
  34. 15 0
      src/utils/utils.ts

+ 1 - 1
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)",

+ 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;
+  }
 }

+ 0 - 46
src/layout/index.tsx

@@ -269,52 +269,6 @@ function MainLayout(props: Props) {
         theme={{
             ...config.default, token: {
                 ...config.default.token,
-                // colorTextBase: config.acTextColor,//基础文字颜色
-                colorBorder: config.dfcolorBorder,//外部边框色
-                colorBorderSecondary: config.dfcolorBorderSecondary,//内部边框色
-                colorTextQuaternary: config.dfcolorTextQuaternary,//输入提示色
-                colorLink: config.dfcolorLink,//链接色
-                colorPrimary: (config.colorPrimary as any)[config.acPrimary] || config.acPrimary,//主题色
-                colorBgElevated: config.colorBgElevated,//弹窗背景色
-                colorBgMask: config.colorBgMask,//弹窗遮罩层颜色
-                colorInfo: config.colorInfo,//用于表示操作信息的 Token 序列,如 Alert 、Tag、 Progress 
-                colorInfoBg: config.colorInfoBg,//信息背景色
-                colorInfoBorder: config.colorInfoBorder,//信息边框色
-                colorError: config.colorError,//失败色
-                colorErrorBg: config.colorErrorBg,//失败色
-                colorErrorBorder: config.colorErrorBorder,//失败边框色
-                colorSuccess: config.colorSuccess,//成功色
-                colorSuccessBg: config.colorSuccessBg,//成功背景色
-                colorSuccessBorder: config.colorSuccessBorder,//成功边框色
-                colorWarning: config.colorWarning,//警戒色
-                colorWarningBg: config.colorWarningBg,//警戒背景色
-                colorWarningBorder: config.colorWarningBorder,//警戒边框色
-                controlItemBgActive: config.controlItemBgActive,//menu和table被选中的背景色
-                controlItemBgActiveHover: config.controlItemBgActiveHover,//table在被选中时hover的背景色
-                colorFillAlter: config.colorFillAlter,//Table控制区hover背景色
-                colorPrimaryBgHover: config.colorPrimaryBgHover,//主题悬浮色
-            },
-            components: {
-                ...config.default.components,
-                Table: {
-                    ...config.default.components.Table,
-                    colorBgContainer: config.tableColorBgContainer,
-                    paddingXS: config.tablePaddingXS,
-                    controlItemBgActive: config.controlItemBgActiveTable,
-                    controlItemBgActiveHover: config.controlItemBgActiveHover,
-                    borderColor: config.cardColorBorder // '#e0e0e0',
-                },
-                Layout: {
-                    ...config.default.components.Layout,
-                },
-                Menu: {
-                    ...config.default.components.Menu,
-                },
-                Card: {
-                    ...config.default.components.Card,
-                    colorBorderSecondary: config.cardColorBorder,//内部边框色
-                    colorBgContainer: config.cardColorBgContainer,//背景色
-                }
             },
             algorithm: [...config.default.algorithm, ...config.algorithm]
         }}

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

@@ -0,0 +1,80 @@
+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 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 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[] }) {
+    return request({
+        url: api + `/corpOperation/corp/pullGroup/project/cancel`,
+        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',
+//     });
+// }

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

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

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

@@ -0,0 +1,122 @@
+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
+    })
+}
+
+
+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 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
+    })
+}

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

@@ -14,6 +14,7 @@ import ModalBookLink from './modalBooklink';
 import { useSize } from 'ahooks';
 import { bookLinkTableConfig } from './tableConfig';
 import dayJs from 'dayjs';
+import '../groupLeaderManage/global.less'
 
 /**
  * 书城自动链接管理
@@ -301,6 +302,7 @@ const BookLink: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookL
                 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={{

+ 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 {

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

@@ -151,7 +151,7 @@ export const massSendingColumns = (setContent: (data: TASK_CREATE.SetContentProp
             render(value, record) {
                 return <>
                     <Title level={5} style={{ margin: 0 }}>标题:{value}</Title>
-                    {record?.bizType && <Paragraph style={{ margin: 0 }}>业务类型:{record?.bizType === 'noval' ? '小说' : record?.bizType === 'game' ? '游戏' : '<空>'}</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>}
                 </>

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

@@ -1,6 +1,6 @@
 import { useAjax } from '@/Hook/useAjax';
 import { cancelProjectApi, delProjectApi, getProjectListApi } from '@/pages/weComTask/API/businessPlan/create';
-import { Button, Card, DatePicker, Input, message, Popconfirm, Space, Table } from 'antd';
+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';
@@ -17,6 +17,7 @@ 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 })

+ 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 } }}
     >
 

+ 2 - 2
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;

+ 125 - 125
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,8 +7,11 @@ 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
@@ -19,29 +22,110 @@ interface Props {
  * @param param0 
  * @returns 
  */
-const SelectCorpUser: React.FC<Props> = ({ value = [], onChange, placeholder }) => {
+const SelectCorpUser: React.FC<Props> = ({ value = [], onChange, placeholder, ...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,
+            }}
+            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 +135,20 @@ 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])
+        getCorpUser.run(params)
+
+    }, [queryForm])
 
     const handleOk = () => {
         if (editSelectedRow.length) {
@@ -73,14 +156,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
@@ -150,7 +237,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 +255,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);

+ 49 - 11
src/pages/weComTask/page/groupChat/create/components/groupUser/addGroupObject.tsx

@@ -1,9 +1,9 @@
-import { App, Button, Card, Empty, Form, Input, InputNumber, Modal, Popover, Radio, Select, Space, Table, Typography } from 'antd';
+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 { getAllWxListApi } from '@/pages/weComTask/API/corpUserAssign';
+import { getAllWxListApi, 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';
@@ -63,13 +63,26 @@ export const ShowGroupUserTable: React.FC<GROUP_CHAT_CREATE.ShowGroupUserTablePr
 
     const columns: ColumnsType<any> = [
         {
-            title: '群配置名称',
+            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',
@@ -227,12 +240,13 @@ const AddGroupObjectModal: React.FC<GROUP_CHAT_CREATE.AddGroupObjectModalProps>
     const { message } = App.useApp();
     const [form] = Form.useForm();
     const externalUserType = Form.useWatch('externalUserType', form);
+    const isRepair = Form.useWatch('isRepair', form);
 
-    const getAccountList = useAjax(() => getAllWxListApi())
+    const getBindMpList = useAjax(() => getBindMpListApi())
     /******************************************/
 
     useEffect(() => {
-        getAccountList.run()
+        getBindMpList.run()
     }, [])
 
     const handleOk = () => {
@@ -256,7 +270,7 @@ const AddGroupObjectModal: React.FC<GROUP_CHAT_CREATE.AddGroupObjectModalProps>
             form={form}
             name="addGroupUser"
             labelAlign='left'
-            labelCol={{ span: 6 }}
+            labelCol={{ span: 9 }}
             colon={false}
             labelWrap
             scrollToFirstError={{
@@ -267,16 +281,40 @@ const AddGroupObjectModal: React.FC<GROUP_CHAT_CREATE.AddGroupObjectModalProps>
                 message.error(errorFields?.[0]?.errors?.[0])
             }}
             onFinish={handleOk}
-            initialValues={(initialValues && Object.keys(initialValues).length > 0) ? initialValues : { externalUserType: 'all', groupIndex: 1, groupUserCount: 37, autoOutGroup: false, excludeInGroup: true, deleteGroupTag: true }}
+            initialValues={(initialValues && Object.keys(initialValues).length > 0) ? initialValues : { externalUserType: 'all', isRepair: false, groupIndex: 1, groupUserCount: 37, autoOutGroup: false, excludeInGroup: true, deleteGroupTag: true }}
             preserve={true}
         >
             <Form.Item
                 name={'groupObjectName'}
-                label={<strong>群配置名称</strong>}
-                rules={[{ required: true, message: '请输入群配置名称!' }]}
+                label={<strong>群名称</strong>}
+                rules={[{ required: true, message: '请输入群名称!' }]}
             >
-                <Input placeholder='请输入群配置名称' allowClear />
+                <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
                 label={<strong>进群对象配置</strong>}
                 required
@@ -355,7 +393,7 @@ const AddGroupObjectModal: React.FC<GROUP_CHAT_CREATE.AddGroupObjectModalProps>
                     filterOption={(input, option) =>
                         (option?.label as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
                     }
-                    options={getAccountList?.data?.data?.map(item => ({ label: item.name, value: item.name + '_' + item.appId }))}
+                    options={getBindMpList?.data?.data?.map(item => ({ label: item.name, value: item.name + '_' + item.id }))}
                 />
             </Form.Item>
             <Form.Item

+ 12 - 6
src/pages/weComTask/page/groupChat/create/components/groupUser/index.tsx

@@ -1,7 +1,7 @@
 import React, { useContext, useState } from 'react';
 import style from '../../../../businessPlan/create/index.less'
 import useNewToken from '@/Hook/useNewToken';
-import { App, Button, Empty } from 'antd';
+import { App, Button, Empty, Popconfirm } from 'antd';
 import { DispatchGroupChatCreate } from '../..';
 import SettingsGroupUser from './settingsGroupUser';
 import PreviewGroupUser from './previewGroupUser';
@@ -26,9 +26,9 @@ const GroupUser: React.FC = () => {
             <div className={`${style.settingsBody_content_col}`} style={{ width: '100%' }}>
                 <div className={style.title}>
                     <span>群</span>
-                    {settings?.strategyDTO?.strategyList?.every(item => item?.groupObjectList?.length > 0) && <a
-                        style={{ color: 'red' }}
-                        onClick={() => {
+                    {settings?.strategyDTO?.strategyList?.every(item => item?.groupObjectList?.length > 0) && <Popconfirm
+                        title="确定清空?"
+                        onConfirm={() => {
                             setSettings({
                                 ...settings,
                                 strategyDTO: {
@@ -39,8 +39,11 @@ const GroupUser: React.FC = () => {
                                     }))
                                 }
                             });
+                            onPreviewReset();
                         }}
-                    >清空</a>}
+                    >
+                        <a style={{ color: 'red' }}>清空</a>
+                    </Popconfirm>}
                 </div>
                 <div className={style.detail}>
                     <div className={style.detail_title}>群配置</div>
@@ -67,7 +70,10 @@ const GroupUser: React.FC = () => {
             onChange={(values) => {
                 setSettings({
                     ...settings,
-                    strategyDTO: values
+                    strategyDTO: {
+                        ...settings.strategyDTO,
+                        ...values
+                    }
                 });
                 onPreviewReset();
                 setNewVisible(false);

+ 11 - 2
src/pages/weComTask/page/groupChat/create/components/strategy/index.tsx

@@ -1,7 +1,7 @@
 import React, { useContext, useState } from 'react';
 import style from '../../../../businessPlan/create/index.less'
 import useNewToken from '@/Hook/useNewToken';
-import { App, Button, Empty } from 'antd';
+import { App, Button, Empty, Popconfirm } from 'antd';
 import { DispatchGroupChatCreate } from '../..';
 import SettingsStrategy from './settingsStrategy';
 import PreviewStrategy from './previewStrategy';
@@ -23,12 +23,21 @@ const Strategy: React.FC = () => {
             <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}/>
+                            <PreviewStrategy strategyDTO={settings?.strategyDTO} />
                         </> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
                     </div>
                 </div>

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

@@ -49,11 +49,11 @@ const PreviewStrategy: React.FC<Props> = ({ strategyDTO }) => {
         colon={false}
         preserve={true}
     >
-        <Card title={<strong>基础信息配置</strong>} style={{ background: '#fff', marginBottom: 10 }} id='basicInfo'>
+        {/* <Card title={<strong>基础信息配置</strong>} style={{ background: '#fff', marginBottom: 10 }} id='basicInfo'>
             <Form.Item label={<strong>任务名称</strong>} name="taskName">
                 <Input placeholder="请输入任务名称" />
             </Form.Item>
-        </Card>
+        </Card> */}
         <Form.List name="strategyList">
             {(fields) => (
                 <>

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

@@ -150,11 +150,11 @@ const SettingsStrategy: React.FC<GROUP_CHAT_CREATE.FoundationProps<any>> = ({ vi
                 }}
                 preserve={true}
             >
-                <Card title={<strong>基础信息配置</strong>} style={{ background: '#fff', marginBottom: 10 }} id='basicInfo'>
+                {/* <Card title={<strong>基础信息配置</strong>} style={{ background: '#fff', marginBottom: 10 }} id='basicInfo'>
                     <Form.Item label={<strong>任务名称</strong>} name="taskName" rules={[{ required: true, message: '请输入任务名称!' }]}>
                         <Input placeholder="请输入任务名称" style={{ width: 358 }} allowClear />
                     </Form.Item>
-                </Card>
+                </Card> */}
                 <Form.List name="strategyList">
                     {(fields, { add, remove }) => (
                         <>

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

@@ -0,0 +1,35 @@
+
+/**
+ * 策略数据还原
+ * @param strategyList 
+ */
+export const getPullGroupData = (strategyList: any[]) => {
+    const nowTime = Date.now()
+    return {
+        strategyList: strategyList.map(({ taskDetail, ...item }, index) => {
+
+            return {
+                ...item,
+                id: nowTime + (index * 1000),
+                groupObjectList: taskDetail.map(({ externalUserFilter, weChatAppid, msgTagDTO, groupName, ...go }) => {
+                    const inherit: { [x: string]: any } = {
+                        ...go,
+                        tagDTO: msgTagDTO,
+                        groupObjectName: groupName
+                    }
+                    if (externalUserFilter) {
+                        inherit.transferType = 'specify'
+                        const { configName, ...configContent } = externalUserFilter
+                        inherit.transferUserDto = {
+                            configName,
+                            configContent
+                        }
+                    } else {
+                        inherit.transferType = 'all'
+                    }
+                    return inherit
+                })
+            }
+        })
+    }
+}

+ 363 - 13
src/pages/weComTask/page/groupChat/create/index.tsx

@@ -1,13 +1,23 @@
-import React, { useEffect, useState } from 'react';
+import React, { useCallback, useEffect, useState } from 'react';
 import style from '../../businessPlan/create/index.less'
-import { App, Button, Card, Input, Popconfirm, Select, Space, Spin } from 'antd';
+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 } from '@ant-design/icons';
+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 } from '@/pages/weComTask/API/groupChat';
+import { useNavigate } from 'react-router-dom';
 
 export const DispatchGroupChatCreate = React.createContext<GROUP_CHAT_CREATE.DispatchGroupChatCreate | null>(null);
 
@@ -18,22 +28,40 @@ export const DispatchGroupChatCreate = React.createContext<GROUP_CHAT_CREATE.Dis
 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 } = App.useApp()
+    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 [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 getCorpUserChatAllList = useAjax((params) => getCorpUserChatAllListApi(params))//获取业务类型
+    const getBindMpList = useAjax(() => getBindMpListApi())
+    const getCorpAllList = useAjax((params) => getCorpAllListApi(params))
+    const addPullGroupTask = useAjax((params) => addPullGroupTaskApi(params))
     /***********************************************/
     console.log('settings--->', settings)
 
     useEffect(() => {
-        const projectId = undefined //sessionStorage.getItem('OFFICIALTASKID')
-        if (projectId) {
-            
+        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('OFFICIALTASKID')
+            //     if (res?.data) {
+
+            //     }
+            // })
         } else {
             const task = localStorage.getItem('TASK_GROUP_CHAT_CREATE')
             if (task) {
@@ -48,6 +76,11 @@ const GroupChatCreate: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREAT
                 setMsgJobTypeList(Object.keys(res.data).map(key => ({ value: key, label: res.data[key] })))
             }
         })
+        getCorpUserChatAllList.run({})
+        getBindMpList.run().then(res => {
+            setMplist(res?.data?.map((item: any) => ({ label: item.name, value: item.id + '' })))
+        })
+        getCorpAllList.run({})
     }, [])
 
 
@@ -56,16 +89,223 @@ const GroupChatCreate: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREAT
         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 } = {
+            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 }) => {
+                delete item?.id // 删除id
+                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
+                    })
+                }
+            })
+        }
+        console.log('提交参数--->', params)
+        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={false}>
             <Card title={<strong>配置区</strong>} className={`${style.card} ${style.config}`}>
                 <Space wrap>
+                    <Space.Compact>
+                        <Button>群主号</Button>
+                        <Select
+                            showSearch
+                            style={{ minWidth: 150 }}
+                            maxTagCount={1}
+                            allowClear
+                            mode='multiple'
+                            placeholder="请选择群主号"
+                            filterOption={(input, option) =>
+                                ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                            }
+                            value={settings?.corpUserChat?.map(item => item.value)}
+                            onChange={(_, option) => {
+                                setSettings({ ...settings, corpUserChat: option as any[], corpUsers: undefined, robotCorpUsers: undefined })
+                                onPreviewReset()
+                            }}
+                            options={getCorpUserChatAllList?.data?.data?.map(item => ({ label: `${item.name}(${item.corpName})`, value: item.id, name: item.name, corpName: item.corpName, corpId: item.corpId, corpUserId: item.corpUserId }))}
+                        />
+                    </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
@@ -160,17 +400,127 @@ const GroupChatCreate: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREAT
                         title="确定清空?"
                         onConfirm={() => {
                             setSettings(undefined)
-                            setPreviewData({})
-                            setPreviewDataOld({})
+                            onPreviewReset()
                             localStorage.removeItem('TASK_GROUP_CHAT_CREATE')
                         }}
                     >
                         <Button icon={<RedoOutlined />} danger>清空配置/预设</Button>
                     </Popconfirm>
-                    {/* <Button type='primary' onClick={preview}><SearchOutlined />预览任务/配置内容</Button> */}
+                    <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}
+            onChange={(values) => {
+                onSubmit(values)
+            }}
+            onClose={() => {
+                setSubVisible(false)
+            }}
+        />}
     </div>
 };
 

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

@@ -0,0 +1,212 @@
+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.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: '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,
+        },
+    ]
+
+}

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

@@ -5,8 +5,14 @@ declare namespace GROUP_CHAT_CREATE {
         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;   // 渠道

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

@@ -0,0 +1,232 @@
+import { useAjax } from "@/Hook/useAjax"
+import { getProjectLogListApi } from "@/pages/weComTask/API/groupChat"
+import FilterUserTooltip from "@/pages/weComTask/components/filterUser/filterUserTooltip";
+import { Badge, Drawer, Popover, Space, Table, Typography } from "antd"
+import React, { useEffect } from "react"
+import { QuestionCircleFilled } from "@ant-design/icons"
+import { businessPlanData, TIME_TYPE_ZJ } from "../../businessPlan/create/const";
+import PreviewTime from "@/pages/weComTask/components/previewTime";
+const { Text, Paragraph } = 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(() => {
+        console.log(data.id)
+        getProjectLogList.run(data.id)
+    }, [])
+
+    return <Drawer
+        title={<strong>{data.taskName} 任务详情</strong>}
+        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: 120,
+                    ellipsis: true,
+                    fixed: 'left'
+                },
+                {
+                    title: '群名称',
+                    dataIndex: 'groupName',
+                    key: 'groupName',
+                    width: 120,
+                    ellipsis: true,
+                    fixed: 'left'
+                },
+                {
+                    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}
+                                    />
+                                </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: '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: '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
+                },
+                {
+                    title: '创建人',
+                    dataIndex: 'createBy',
+                    key: 'createBy',
+                    width: 80,
+                    align: 'center',
+                    ellipsis: true,
+                    render(value) {
+                        return value?.nickname || '--'
+                    }
+                }
+            ]}
+        />
+    </Drawer>
+}
+
+export default React.memo(Details)

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

@@ -0,0 +1,195 @@
+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')
+        if (projectName) {
+            sessionStorage.removeItem('CAMPCORP')
+        }
+    }, [])
+
+    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') => {
+        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':
+                cancelProject.run(data).then(res => {
+                    hide()
+                    setselectedRows([])
+                    if (res?.data) {
+                        message.success('取消成功')
+                        getProjectList.refresh()
+                    } else {
+                        message.error('取消失败')
+                    }
+                }).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>
+        </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)));

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

@@ -0,0 +1,151 @@
+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') => void,
+): ColumnsType<AnyObject> => {
+
+    return [
+        {
+            title: '操作',
+            dataIndex: 'cz',
+            key: 'cz',
+            width: 160,
+            render(_, record) {
+                return <Space>
+                    <Popconfirm
+                        title="确定取消?"
+                        onConfirm={() => { handleDel({ projectIds: [record.id] }, 'cancel') }}
+                    >
+                        <a style={{ color: 'orange' }}>取消任务</a>
+                    </Popconfirm>
+                    <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 <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

+ 68 - 0
src/pages/weComTask/page/groupLeaderManage/addGL.tsx

@@ -0,0 +1,68 @@
+import { App, Form, Modal } from 'antd';
+import React from 'react';
+import SelectCorpUser from '../corpUserManage/selectCorpUser';
+import { useAjax } from '@/Hook/useAjax';
+import { addCorpUserChatApi } from '../../API/groupLeaderManage';
+
+interface AddGLProps {
+    visible?: boolean
+    onChange?: () => void
+    onClose?: () => void
+}
+
+
+const AddGL: React.FC<AddGLProps> = ({ visible, onChange, onClose }) => {
+
+    /****************************************/
+    const { message } = App.useApp()
+    const [form] = Form.useForm();
+    const addCorpUserChat = useAjax((params) => addCorpUserChatApi(params))
+    /****************************************/
+
+    const handleOk = () => {
+        form.validateFields().then((values) => {
+            addCorpUserChat.run({
+                corpUserList: values.corpUserList.map(item => ({ corpId: item.corpId, corpUserId: item.corpUserId }))
+            }).then((res) => {
+                if (res?.data) {
+                    message.success('添加成功')
+                    onChange?.()
+                }
+            })
+        }).catch((err) => {
+            console.log('err', err)
+            form.submit()
+        });
+    }
+
+    return <Modal
+        title={<strong>新增群主号</strong>}
+        open={visible}
+        onCancel={onClose}
+        width={500}
+        onOk={handleOk}
+        confirmLoading={addCorpUserChat.loading}
+    >
+        <Form
+            form={form}
+            name="newGL"
+            labelAlign='left'
+            labelCol={{ span: 5 }}
+            colon={false}
+            scrollToFirstError={{
+                behavior: 'smooth',
+                block: 'center'
+            }}
+            onFinishFailed={({ errorFields }) => {
+                message.error(errorFields?.[0]?.errors?.[0])
+            }}
+            preserve={true}
+        >
+            <Form.Item label={<strong>群主号</strong>} name="corpUserList" rules={[{ required: true, message: '请选择群主号!' }]}>
+                <SelectCorpUser placeholder="请选择群主号" />
+            </Form.Item>
+        </Form>
+    </Modal>
+};
+
+export default React.memo(AddGL);

+ 173 - 0
src/pages/weComTask/page/groupLeaderManage/bindDetails.tsx

@@ -0,0 +1,173 @@
+import React, { useEffect, useState } from 'react';
+import { getBindDetailListApi, unbindCorpUserChatApi } from '../../API/groupLeaderManage';
+import { useAjax } from '@/Hook/useAjax';
+import { App, Button, Flex, Modal, Popconfirm, Table, Typography } from 'antd';
+import { ForkOutlined, PullRequestOutlined } from '@ant-design/icons';
+import BindUser from './bindUser';
+const { Paragraph } = Typography;
+
+interface BindDetailsProps {
+    bindType: 1 | 2  // 1 客服号 2 机器人号
+    data: any
+}
+
+
+/**
+ * 绑定详情
+ * @param param0 
+ * @returns 
+ */
+const BindDetails: React.FC<BindDetailsProps> = ({ bindType, data }) => {
+
+    /***********************************************/
+    const [visible, setVisible] = useState<boolean>(false)
+    /***********************************************/
+
+
+    return <>
+        <a onClick={() => setVisible(true)}>详情</a>
+        {visible && <BindDetailsModal
+            bindType={bindType}
+            data={data}
+            visible={visible}
+            onClose={() => setVisible(false)}
+        />}
+    </>
+};
+
+
+interface BindDetailsModalProps extends BindDetailsProps {
+    visible?: boolean
+    onClose?: () => void
+}
+
+const BindDetailsModal: React.FC<BindDetailsModalProps> = React.memo(({ visible, onClose, bindType, data }) => {
+
+    /*********************************/
+    const { message } = App.useApp()
+    const [bindVisible, setBindVisible] = useState<boolean>(false)
+    const [editSelectedRow, setEditSelectedRow] = useState<any[]>([])
+
+    const getBindDetailList = useAjax((params) => getBindDetailListApi(params))
+    const unbindCorpUserChat = useAjax((params) => unbindCorpUserChatApi(params))
+    /*********************************/
+
+    useEffect(() => {
+        getBindDetailList.run({
+            corpUserChatId: data.id,
+            bindType
+        })
+    }, [])
+
+    const handleUnbind = () => {
+        unbindCorpUserChat.run({
+            bindType,
+            corpUserChatId: data.id,
+            corpUserList: editSelectedRow.map(item => {
+                const { corpId, corpUserId, transferExternalUserId } = item
+                return { corpId, corpUserId, transferExternalUserId }
+            })
+        }).then((res) => {
+            if (res?.data) {
+                message.success('解绑成功')
+                getBindDetailList.refresh()
+                setEditSelectedRow([])
+            }
+        })
+    }
+
+    return <Modal
+        title={<strong>《{data.corpName}》群主《{data.name}》{bindType === 1 ? '客服号' : '机器人号'}详情</strong>}
+        open={visible}
+        onCancel={onClose}
+        width={900}
+        footer={null}
+    >
+        <Flex style={{ marginBottom: 10 }} gap={10}>
+            <Button loading={getBindDetailList.loading} onClick={() => getBindDetailList.refresh()}>刷新</Button>
+            <Button type="primary" icon={<ForkOutlined />} onClick={() => setBindVisible(true)}>绑定{bindType === 1 ? '客服号' : '机器人号'}</Button>
+            <Popconfirm
+                title="确定解绑?"
+                onConfirm={handleUnbind}
+                disabled={editSelectedRow?.length === 0}
+            >
+                <Button type="primary" danger disabled={editSelectedRow?.length === 0} icon={<PullRequestOutlined />}>解绑{bindType === 1 ? '客服号' : '机器人号'}</Button>
+            </Popconfirm>
+        </Flex>
+        <Table
+            columns={[
+                {
+                    title: (bindType === 1 ? '客服号' : '机器人号') + '企业',
+                    dataIndex: 'corpUser',
+                    key: 'corpUser',
+                    width: 150,
+                    render: (value) => {
+                        return <div>
+                            <Paragraph style={{ margin: 0 }} ellipsis>{value?.corpName || '--'}({value?.corpId || '--'})</Paragraph>
+                        </div>
+                    }
+                },
+                {
+                    title: (bindType === 1 ? '客服号' : '机器人号'),
+                    dataIndex: 'corpUser',
+                    key: 'name',
+                    width: 150,
+                    render: (value) => {
+                        return <div>
+                            <Paragraph style={{ margin: 0 }} ellipsis>{value?.name || '--'}({value?.corpUserId || '--'})</Paragraph>
+                        </div>
+                    }
+                },
+                {
+                    title: '外部联系人',
+                    dataIndex: 'transferExternalUser',
+                    key: 'transferExternalUser',
+                    width: 150,
+                    render: (value) => {
+                        return <div>
+                            <Paragraph style={{ margin: 0 }} ellipsis>{value?.remark || '--'}({value?.externalUserId || '--'})</Paragraph>
+                        </div>
+                    }
+                },
+            ]}
+            dataSource={getBindDetailList?.data?.data}
+            rowKey="id"
+            loading={getBindDetailList.loading}
+            scroll={{ y: 400 }}
+            rowSelection={{
+                type: 'checkbox',
+                selectedRowKeys: editSelectedRow?.map(item => item.id),
+                onSelect: (record: { id: any; }, selected: any) => {
+                    if (selected) {
+                        setEditSelectedRow([...editSelectedRow, record])
+                    } else {
+                        setEditSelectedRow(editSelectedRow?.filter(item => item.id !== record.id))
+                    }
+                },
+                onSelectAll: (selected: any, _: any, changeRows: any[]) => {
+                    if (selected) {
+                        setEditSelectedRow([...editSelectedRow, ...changeRows])
+                    } else {
+                        let newArr = editSelectedRow?.filter(item => changeRows.every(i => i?.id !== item?.id))
+                        setEditSelectedRow(newArr)
+                    }
+                }
+            }}
+        />
+        {/* 绑定用户 */}
+        {bindVisible && <BindUser
+            bindType={bindType}
+            data={data}
+            visible={bindVisible}
+            onChange={() => {
+                setBindVisible(false)
+                getBindDetailList.refresh()
+            }}
+            onClose={() => {
+                setBindVisible(false)
+            }}
+        />}
+    </Modal>
+})
+
+export default React.memo(BindDetails);

+ 113 - 0
src/pages/weComTask/page/groupLeaderManage/bindUser.tsx

@@ -0,0 +1,113 @@
+import { App, Button, Card, Form, Modal } from 'antd';
+import React from 'react';
+import SelectCorpUser from '../corpUserManage/selectCorpUser';
+import { useAjax } from '@/Hook/useAjax';
+import { bindCorpUserChatApi } from '../../API/groupLeaderManage';
+import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
+import SelectGDUser from './selectGDUser';
+
+interface AddGLProps {
+    bindType: 1 | 2
+    data: { [x: string]: any }
+    visible?: boolean
+    onChange?: () => void
+    onClose?: () => void
+}
+
+
+const BindUser: React.FC<AddGLProps> = ({ bindType, data, visible, onChange, onClose }) => {
+
+    /****************************************/
+    const { message } = App.useApp()
+    const [form] = Form.useForm();
+    const corpUserList = Form.useWatch('corpUserList', form)
+    const bindCorpUserChat = useAjax((params) => bindCorpUserChatApi(params))
+    /****************************************/
+
+    const handleOk = () => {
+        form.validateFields().then((values) => {
+            bindCorpUserChat.run({
+                bindType,
+                corpUserChatId: data.id,
+                corpUserList: values.corpUserList.map(item => {
+                    const { corpId, corpUserId } = item.corpUser[0]
+                    const { externalUserId } = item.transferExternalUser[0]
+                    return { corpId, corpUserId, transferExternalUserId: externalUserId }
+                })
+            }).then((res) => {
+                if (res?.data) {
+                    message.success('绑定成功')
+                    onChange?.()
+                }
+            })
+        }).catch((err) => {
+            console.log('err', err)
+            form.submit()
+        });
+    }
+
+    return <Modal
+        title={<strong>绑定{bindType === 1 ? '客服号' : '机器人号'}</strong>}
+        open={visible}
+        onCancel={onClose}
+        width={500}
+        onOk={handleOk}
+        confirmLoading={bindCorpUserChat.loading}
+    >
+        <Form
+            form={form}
+            name="newGL"
+            labelAlign='left'
+            labelCol={{ span: 5 }}
+            colon={false}
+            scrollToFirstError={{
+                behavior: 'smooth',
+                block: 'center'
+            }}
+            onFinishFailed={({ errorFields }) => {
+                message.error(errorFields?.[0]?.errors?.[0])
+            }}
+            preserve={true}
+            initialValues={{ corpUserList: [{}] }}
+        >
+            <Form.List name='corpUserList'>
+                {(fields, { add, remove }) => (
+                    <>
+                        {fields.map(({ key, name, ...restField }, i) => {
+                            return <Card
+                                key={i}
+                                title={<strong>{bindType === 1 ? '客服号' : '机器人号'}</strong>}
+                                style={{ background: '#fff', marginBottom: 10 }}
+                                extra={corpUserList?.length > 1 ? <Button icon={<DeleteOutlined />} type='link' style={{ color: 'red' }} onClick={() => remove(name)}></Button> : null}
+                            >
+                                <Form.Item
+                                    {...restField}
+                                    label={<strong>{bindType === 1 ? '客服号' : '机器人号'}</strong>}
+                                    name={[name, 'corpUser']}
+                                    rules={[{ required: true, message: `请选择${bindType === 1 ? '客服号' : '机器人号'}` }]}
+                                >
+                                    <SelectCorpUser type='radio' placeholder={`请选择${bindType === 1 ? '客服号' : '机器人号'}`} />
+                                </Form.Item>
+                                <Form.Item
+                                    {...restField}
+                                    label={<strong>外部联系人</strong>}
+                                    name={[name, 'transferExternalUser']}
+                                    rules={[{ required: true, message: '请选择外部联系人!' }]}
+                                >
+                                    <SelectGDUser corpId={data.corpId} corpUserIds={[data.corpUserId]} />
+                                </Form.Item>
+                            </Card>
+                        })}
+                        <Form.Item>
+                            <Button type="dashed" onClick={() => add({ externalUserType: 'all', id: Date.now() })} block icon={<PlusOutlined />}>
+                                新增{bindType === 1 ? '客服号' : '机器人号'}
+                            </Button>
+                        </Form.Item>
+                    </>
+                )}
+            </Form.List>
+        </Form>
+    </Modal>
+};
+
+export default React.memo(BindUser);

+ 12 - 0
src/pages/weComTask/page/groupLeaderManage/global.less

@@ -0,0 +1,12 @@
+.resetTable {
+    .ant-table {
+        border-radius: 0 !important;
+    }
+
+    .ant-table-container {
+        border-top: none !important;
+        border-inline-start: none !important;
+        border-start-start-radius: 0 !important;
+        border-start-end-radius: 0 !important;
+    }
+}

+ 156 - 0
src/pages/weComTask/page/groupLeaderManage/index.tsx

@@ -0,0 +1,156 @@
+import { Button, Card, Input, Pagination, Select, Table } from 'antd';
+import React, { useEffect, useRef, useState } from 'react';
+import style from '../bookLink/index.less'
+import { useSize } from 'ahooks';
+import { useAjax } from '@/Hook/useAjax';
+import { getCorpUserChatListApi, getCorpUserChatListProps } from '../../API/groupLeaderManage';
+import { GroupLeaderTableConfig } from './tableConfig';
+import SearchBox from '../../components/searchBox';
+import { SearchOutlined, PlusOutlined } from '@ant-design/icons';
+import { getAdAccountAllOfMember, getCorpAllListApi } from '@/API/global';
+import './global.less'
+import AddGL from './addGL';
+
+/**
+ * 群主号管理
+ * @returns 
+ */
+const GroupLeaderManage: React.FC = () => {
+
+    /*******************************************/
+    const ref = useRef<HTMLDivElement>(null)
+    const size = useSize(ref)
+    const [queryParams, setQueryParams] = useState<getCorpUserChatListProps>({ pageNum: 1, pageSize: 20 })
+    const [queryParamsNew, setQueryParamsNew] = useState<getCorpUserChatListProps>({ pageNum: 1, pageSize: 20 })
+    const [visible, setVisible] = useState<boolean>(false)
+
+    const getCorpUserChatList = useAjax((params) => getCorpUserChatListApi(params))
+    const getCorpAllList = useAjax((params) => getCorpAllListApi(params))
+    const allOfMember = useAjax(() => getAdAccountAllOfMember())
+    /*******************************************/
+
+    useEffect(() => {
+        allOfMember.run()
+        getCorpAllList.run({})
+    }, [])
+
+    useEffect(() => {
+        getCorpUserChatList.run(queryParamsNew)
+    }, [queryParamsNew])
+
+    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={getCorpUserChatList.loading} icon={<SearchOutlined />}>搜索</Button>
+                    <Button type="primary" icon={<PlusOutlined />} onClick={() => setVisible(true)}>新增群主号</Button>
+                </>}
+            >
+                <>
+                    <Select
+                        value={queryParams?.corpIds}
+                        onChange={(e) => setQueryParams({ ...queryParams, corpIds: e })}
+                        showSearch
+                        mode='multiple'
+                        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 }))}
+                    />
+                    <Input onChange={(e) => setQueryParams({ ...queryParams, corpUserName: e.target.value as any })} value={queryParams?.corpUserName} placeholder="客服号名称" allowClear />
+                    <Input onChange={(e) => setQueryParams({ ...queryParams, corpUserIds: e.target.value as any })} value={queryParams?.corpUserIds} placeholder="客服ID(多个,,空格换行)" allowClear />
+                    <Select
+                        value={queryParams?.operUserId}
+                        onChange={(e) => setQueryParams({ ...queryParams, operUserId: e })}
+                        showSearch
+                        style={{ width: 110 }}
+                        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={queryParams?.putUserId}
+                        onChange={(e) => setQueryParams({ ...queryParams, putUserId: e })}
+                        showSearch
+                        style={{ width: 110 }}
+                        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
+                        style={{ width: 90 }}
+                        showSearch
+                        placeholder="状态"
+                        value={queryParams?.status}
+                        onChange={(value) => setQueryParams({ ...queryParams, status: value })}
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                        }
+                        allowClear
+                        options={[{ label: '可用', value: 1 }, { label: '禁用', value: 0 }]}
+                    />
+                </>
+            </SearchBox>
+        </div>
+        <div className={style.bookLinkTable} ref={ref}>
+            <Table
+                dataSource={getCorpUserChatList?.data?.data?.records}
+                columns={GroupLeaderTableConfig()}
+                bordered
+                className='resetTable'
+                pagination={false}
+                rowKey={'id'}
+                size='small'
+                loading={getCorpUserChatList?.loading}
+                scroll={{ y: size?.height && ref.current ? size?.height - ref.current.querySelector('.ant-table-thead').clientHeight : 300 }}
+            />
+        </div>
+        <div className={style.bookLinkPagination}>
+            <Pagination
+                size="small"
+                total={getCorpUserChatList?.data?.data?.total || 0}
+                showSizeChanger
+                showQuickJumper
+                pageSize={getCorpUserChatList?.data?.data?.size || 20}
+                current={getCorpUserChatList?.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 && <AddGL
+            visible={visible}
+            onChange={() => {
+                setVisible(false)
+                getCorpUserChatList.refresh()
+            }}
+            onClose={() => {
+                setVisible(false)
+            }}
+        />}
+    </Card>
+};
+
+export default GroupLeaderManage;

+ 213 - 0
src/pages/weComTask/page/groupLeaderManage/selectCorpUserChatUser.tsx

@@ -0,0 +1,213 @@
+import useNewToken from '@/Hook/useNewToken';
+import React, { useEffect, useState } from 'react';
+import { App, Button, Input, Modal, Select, Table, Tag, Tooltip, Typography } from 'antd';
+import { CloseCircleFilled, SearchOutlined } from '@ant-design/icons';
+import '../corpUserManage/global.less'
+import { useAjax } from '@/Hook/useAjax';
+import { getCorpUserChatCorpUserListApi } from '../../API/groupLeaderManage';
+import { RowSelectionType } from 'antd/es/table/interface';
+const { Paragraph } = Typography;
+
+interface SelectCorpUserChatUserProps {
+    // 这里可以添加选择成员组件的属性
+    bindType: 1 | 2
+    corpUserChatIds: number[]
+    value?: { corpUserId: string, name: string, corpName: string, corpId: string }[];
+    onChange?: (value: { corpUserId: string, name: string, corpName: string, corpId: string }[]) => void;
+    placeholder?: React.ReactNode
+    type?: RowSelectionType
+}
+
+/**
+ * 选择群主下客服号 机器人号
+ * @returns 
+ */
+const SelectCorpUserChatUser: React.FC<SelectCorpUserChatUserProps> = ({ value, onChange, placeholder, ...its }) => {
+
+    /************************************************************/
+    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
+                        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>
+
+        {visible && <SelectCorpUserChatUserModal
+            {...its}
+            value={value}
+            visible={visible}
+            placeholder={placeholder}
+            onClose={() => setVisible(false)}
+            onChange={(values) => {
+                onChange?.(values)
+                setVisible(false)
+            }}
+        />}
+    </>
+};
+
+interface SelectCorpUserChatUserModalProps extends SelectCorpUserChatUserProps {
+    visible?: boolean
+    onClose?: () => void
+}
+
+const SelectCorpUserChatUserModal: React.FC<SelectCorpUserChatUserModalProps> = React.memo(({ placeholder, bindType, corpUserChatIds, visible, onClose, onChange, value, type = 'checkbox' }) => {
+
+    /**********************************************/
+    const [editSelectedRow, setEditSelectedRow] = useState<any[]>([])
+    const { message } = App.useApp()
+
+    const getCorpUserChatCorpUserList = useAjax((params) => getCorpUserChatCorpUserListApi(params))
+    /**********************************************/
+
+    useEffect(() => {
+        if (bindType && corpUserChatIds?.length > 0) {
+            getCorpUserChatCorpUserList.run({ bindType, corpUserChatIds })
+        }
+    }, [bindType, corpUserChatIds])
+
+    useEffect(() => {
+        if (visible) {
+            setEditSelectedRow(value?.length ? [...value] : [])
+        }
+    }, [visible, value])
+
+    const handleOk = () => {
+        if (editSelectedRow.length) {
+            onChange?.(editSelectedRow.map(item => ({
+                corpUserId: item.corpUserId,
+                name: item.name,
+                corpName: item.corpName,
+                corpId: item.corpId
+            })))
+        } else {
+            message.error('请至少选择一条数据')
+        }
+    }
+
+    return <Modal
+        title={<strong>{placeholder}</strong>}
+        open={visible}
+        width={800}
+        onCancel={onClose}
+        onOk={handleOk}
+    >
+        <div className='selectCorpUserBody' style={{ width: '100%' }}>
+            <div className='selectCorpUserBody_search'>
+                <Button type="primary" onClick={() => {
+                    getCorpUserChatCorpUserList.refresh()
+                }} loading={getCorpUserChatCorpUserList.loading} icon={<SearchOutlined />}>刷新</Button>
+            </div>
+            <div className='selectCorpUserBody_content'>
+                <Table
+                    columns={[
+                        {
+                            title: (bindType === 1 ? '客服号' : '机器人号'),
+                            dataIndex: 'corpUser',
+                            key: 'name',
+                            width: 150,
+                            render: (value) => {
+                                return <div>
+                                    <Paragraph style={{ margin: 0 }} ellipsis>{value?.name || '--'}({value?.corpUserId || '--'})</Paragraph>
+                                </div>
+                            }
+                        },
+                        {
+                            title: (bindType === 1 ? '客服号' : '机器人号') + '企业',
+                            dataIndex: 'corpUser',
+                            key: 'corpUser',
+                            width: 150,
+                            render: (value) => {
+                                return <div>
+                                    <Paragraph style={{ margin: 0 }} ellipsis>{value?.corpName || '--'}({value?.corpId || '--'})</Paragraph>
+                                </div>
+                            }
+                        }
+                    ]}
+                    dataSource={getCorpUserChatCorpUserList?.data?.data}
+                    size='small'
+                    scroll={{ y: 350 }}
+                    rowKey={'corpUserId'}
+                    loading={getCorpUserChatCorpUserList.loading}
+                    rowSelection={type === 'checkbox' ? {
+                        type: 'checkbox',
+                        selectedRowKeys: editSelectedRow?.map(item => item.corpUserId),
+                        onSelect: (record: { corpUserId: any; corpUser: any }, selected: any) => {
+                            if (selected) {
+                                setEditSelectedRow([...editSelectedRow, { ...record, ...record?.corpUser }])
+                            } else {
+                                setEditSelectedRow(editSelectedRow?.filter(item => item.corpUserId !== record.corpUserId))
+                            }
+                        },
+                        onSelectAll: (selected: any, rows: any, changeRows: any[]) => {
+                            if (selected) {
+                                setEditSelectedRow([...editSelectedRow, ...changeRows.map(item => ({ ...item, ...item?.corpUser }))])
+                            } else {
+                                let newArr = editSelectedRow?.filter(item => changeRows.every(i => i?.corpUserId !== item?.corpUserId))
+                                setEditSelectedRow(newArr)
+                            }
+                        }
+                    } : {
+                        type: 'radio',
+                        selectedRowKeys: editSelectedRow?.map(item => item.corpUserId),
+                        onChange(_, selectedRows) {
+                            setEditSelectedRow(selectedRows)
+                        },
+                    }}
+                />
+            </div>
+        </div>
+    </Modal>
+});
+
+export default SelectCorpUserChatUser;

+ 153 - 0
src/pages/weComTask/page/groupLeaderManage/selectGDUser.tsx

@@ -0,0 +1,153 @@
+import { Avatar, Button, Input, Modal, Space, Table, Tag, Typography } from "antd"
+import React, { useCallback, useEffect, useState } from "react"
+import { UserAddOutlined, UserOutlined } from '@ant-design/icons';
+import { useAjax } from "@/Hook/useAjax";
+import { getCorpExternalUserListApi } from "@/API/global";
+import SearchBox from "../../components/searchBox";
+import { copy } from "@/utils/utils";
+
+
+
+interface Props {
+    corpId: string,
+    corpUserIds?: string[],
+    value?: any
+    onChange?: (value?: any) => void
+}
+const SelectGDUser: React.FC<Props> = ({ corpId, corpUserIds = [], value, onChange }) => {
+
+    /******************************/
+    const [editSelectedRow, setEditSelectedRow] = useState<any[]>([])
+    const [showOpen, set_ShowOpen] = useState(false)
+    const [queryForm2, setQueryForm2] = useState<any>({ pageNum: 1, pageSize: 20, corpId: '', deleted: false })
+    const getCorpExternalUserList = useAjax((params) => getCorpExternalUserListApi(params))
+    /******************************/
+
+    // 查固定客户
+    useEffect(() => {
+        if (corpId && corpUserIds?.length > 0 && showOpen) {
+            getCorpExternalUserList.run({ ...queryForm2, corpId, corpUserIds })
+        }
+    }, [queryForm2, corpId, corpUserIds, showOpen])
+
+    useEffect(() => {
+        setEditSelectedRow(value || [])
+    }, [value])
+
+    const setFieldValue1 = useCallback((e: any, field: string, f?: any, field1?: string) => {
+        let newQueryForm = JSON.parse(JSON.stringify(queryForm2))
+        newQueryForm['pageNum'] = 1
+        newQueryForm['pageSize'] = 20
+        if (field === 'platformKey') {
+            newQueryForm[field] = e
+        } else {
+            newQueryForm[field] = e
+            if (field1) {
+                newQueryForm[field1] = f
+            }
+            newQueryForm['platformKey'] = newQueryForm['platformKey'] || undefined
+        }
+        setQueryForm2(newQueryForm)
+    }, [queryForm2])
+
+    const handleOk = () => {
+        onChange?.(editSelectedRow)
+        set_ShowOpen(false)
+    }
+    return <>
+        <Space style={{ width: '100%' }}>
+            <Space>
+                {editSelectedRow?.map(item => {
+                    return <div key={item.id} style={{ maxWidth: 70 }}><Typography.Text ellipsis={{ tooltip: true }}>{item.name}</Typography.Text></div>
+                })}
+            </Space>
+            <Button type="primary" disabled={!corpId || corpUserIds?.length === 0} onClick={() => { set_ShowOpen(true) }}><UserAddOutlined />{editSelectedRow?.length > 0 ? '重新选择' : '选择客户'}</Button>
+        </Space>
+
+        <Modal
+            width={900}
+            open={showOpen}
+            onCancel={() => { set_ShowOpen(false) }}
+            onOk={handleOk}
+            zIndex={2000}
+        >
+            <SearchBox buttons={<>
+                <Button icon={getCorpExternalUserList.loading} onClick={() => getCorpExternalUserList.refresh()}>刷新</Button>
+            </>}>
+                <>
+                    <Input placeholder="客户名称" style={{ width: 125 }} allowClear value={queryForm2?.name} onChange={(e) => setFieldValue1(e.target.value, 'name')} />
+                    <Input placeholder="客户ID" style={{ width: 125 }} allowClear value={queryForm2?.externalUserId} onChange={(e) => setFieldValue1(e.target.value, 'externalUserId')} />
+                </>
+            </SearchBox>
+            <Table
+                tableLayout='fixed'
+                dataSource={getCorpExternalUserList.data?.data?.records?.map((item: any, index: number) => ({ ...item, id: item.externalUserId + '_' + item.corpUserId }))}
+                columns={[
+                    {
+                        title: '客户ID',
+                        dataIndex: 'externalUserId',
+                        key: 'externalUserId',
+                        align: 'center',
+                        width: 140,
+                        ellipsis: true,
+                        render: (a: string) => {
+                            return <a onClick={() => copy(a)}>{a}</a>
+                        },
+                    },
+                    {
+                        title: '客户',
+                        dataIndex: 'name',
+                        key: 'name',
+                        width: 80,
+                        ellipsis: true,
+                        render: (a: string, b: any) => {
+                            return <Space style={{ width: '100%' }}>
+                                <Avatar shape="square" size="small" icon={<UserOutlined />} src={b?.avatar} />
+                                <Typography.Paragraph style={{ marginBottom: 0, width: 250 }} ellipsis={{ rows: 1, tooltip: true }}>{a}</Typography.Paragraph>
+                            </Space>
+                        }
+                    },
+                    {
+                        title: '通讯录成员名称',
+                        dataIndex: 'corpUserName',
+                        key: 'corpUserName',
+                        align: 'center',
+                        width: 80,
+                        ellipsis: true
+                    },
+                ]}
+                loading={getCorpExternalUserList.loading}
+                scroll={{ y: 'calc(100vh - 17.8125rem)' }}
+                rowKey={(r) => {
+                    return r.id
+                }}
+                size='small'
+                rowSelection={{
+                    type: 'radio',
+                    selectedRowKeys: editSelectedRow?.map(item => item.id),
+                    getCheckboxProps: (record: any) => ({
+                        name: record.name,
+                    }),
+                    onChange(_selectedRowKeys, selectedRows) {
+                        setEditSelectedRow(selectedRows)
+                    }
+                }}
+                pagination={{
+                    total: getCorpExternalUserList.data?.data?.total,
+                    showTotal: (total) => <Tag color="cyan">总共{total}数据</Tag>,
+                    showSizeChanger: true,
+                    showLessItems: true,
+                    defaultCurrent: 1,
+                    defaultPageSize: 20,//默认初始的每页条数
+                    current: getCorpExternalUserList?.data?.data?.current || 1,
+                    pageSize: getCorpExternalUserList?.data?.data?.size || 20,
+                    onChange: (page, pageSize) => {
+                        setQueryForm2({ ...queryForm2, pageNum: page, pageSize })
+                    }
+                }}
+            />
+        </Modal>
+    </>
+}
+
+export default React.memo(SelectGDUser)

+ 106 - 0
src/pages/weComTask/page/groupLeaderManage/tableConfig.tsx

@@ -0,0 +1,106 @@
+import { copy } from "@/utils/utils"
+import { ColumnsType } from "antd/es/table"
+import { Button, Flex, Popconfirm, Popover, Tag } from "antd"
+import BindDetails from "./bindDetails"
+
+
+export function GroupLeaderTableConfig(): ColumnsType<any> {
+
+    const arr: ColumnsType<any> = [
+        {
+            title: '群主主体',
+            dataIndex: 'corpName',
+            key: 'corpName',
+            align: 'center',
+            width: 70,
+            ellipsis: true,
+        },
+        {
+            title: '群主企微主体ID',
+            dataIndex: 'corpId',
+            key: 'corpId',
+            align: 'center',
+            width: 70,
+            ellipsis: true,
+        },
+        {
+            title: '群主企微号',
+            dataIndex: 'name',
+            key: 'name',
+            align: 'center',
+            width: 70,
+            ellipsis: true,
+        },
+        {
+            title: '群主企微号ID',
+            dataIndex: 'corpUserId',
+            key: 'corpUserId',
+            align: 'center',
+            width: 70,
+            ellipsis: true,
+        },
+        {
+            title: '主运营',
+            dataIndex: 'operUser',
+            key: 'operUser',
+            align: 'center',
+            width: 70,
+            ellipsis: true,
+            render: (value) => {
+                return value?.nickname || '--'
+            }
+        },
+        {
+            title: '投手',
+            dataIndex: 'putUser',
+            key: 'putUser',
+            align: 'center',
+            width: 70,
+            ellipsis: true,
+            render: (value) => {
+                return value?.nickname || '--'
+            }
+        },
+        {
+            title: '运营助理',
+            dataIndex: 'userList',
+            key: 'userList',
+            width: 120,
+            ellipsis: true,
+            render: (value) => {
+                return value?.map(item => item.nickname)?.join('、') || '--'
+            }
+        },
+        {
+            title: '状态',
+            dataIndex: 'status',
+            key: 'status',
+            width: 70,
+            align: 'center',
+            render: (value) => {
+                return value ? <Tag color="success">可用</Tag> : <Tag color="error">禁用</Tag>
+            }
+        },
+        {
+            title: '绑定客服号',
+            dataIndex: 'bindCorpUser',
+            key: 'bindCorpUser',
+            width: 70,
+            align: 'center',
+            render: (_, record) => {
+                return <BindDetails data={record} bindType={1}/>
+            }
+        },
+        {
+            title: '绑定机器人客服号',
+            dataIndex: 'bindACorpUser',
+            key: 'bindACorpUser',
+            width: 70,
+            align: 'center',
+            render: (_, record) => {
+                return <BindDetails data={record} bindType={2}/>
+            }
+        },
+    ]
+    return arr
+}

+ 15 - 0
src/utils/utils.ts

@@ -392,4 +392,19 @@ export const cutByBytes = (str: string, bytes: number) => {
     byteCount += charSize;
   }
   return result !== str ? result + '...' : str;
+}
+
+
+/**
+ * 对象去空
+ * @param obj 
+ * @returns 
+ */
+export const removeEmptyValues = (obj: { [x: string]: any }) => {
+  for (const key in obj) {
+    if (obj[key] === null || obj[key] === undefined || obj[key] === '') {
+      delete obj[key];
+    }
+  }
+  return obj;
 }