wjx преди 2 седмици
родител
ревизия
ece912fad4
променени са 65 файла, в които са добавени 3621 реда и са изтрити 313 реда
  1. 111 0
      src/API/global.ts
  2. 1 1
      src/Hook/useNewToken.tsx
  3. 2 2
      src/layout/AvatarDropdown.tsx
  4. 2 3
      src/layout/index.tsx
  5. 1 1
      src/pages/login/entry/index.tsx
  6. 0 3
      src/pages/login/login.tsx
  7. 36 0
      src/pages/weComTask/API/businessPlan/create.ts
  8. 1 1
      src/pages/weComTask/components/filterUser/filterUserText.ts
  9. 1 1
      src/pages/weComTask/components/previewTime/index.tsx
  10. 153 0
      src/pages/weComTask/components/selectExternalAccount/index.less
  11. 220 0
      src/pages/weComTask/components/selectExternalAccount/index.tsx
  12. 8 0
      src/pages/weComTask/components/selectExternalAccount/typings.d.ts
  13. 4 3
      src/pages/weComTask/entry/index.tsx
  14. 225 0
      src/pages/weComTask/page/businessPlan/create/components/highMassSending/content.tsx
  15. 0 0
      src/pages/weComTask/page/businessPlan/create/components/highMassSending/index.less
  16. 503 0
      src/pages/weComTask/page/businessPlan/create/components/highMassSending/index.tsx
  17. 0 0
      src/pages/weComTask/page/businessPlan/create/components/highMassSending/materialNoTextMould.tsx
  18. 79 0
      src/pages/weComTask/page/businessPlan/create/components/highMassSending/settingsHighMassSending.tsx
  19. 88 0
      src/pages/weComTask/page/businessPlan/create/components/highMassSending/showContentTable.tsx
  20. 278 0
      src/pages/weComTask/page/businessPlan/create/components/highMassSending/strategy.tsx
  21. 2 2
      src/pages/weComTask/page/businessPlan/create/components/massSending/content.tsx
  22. 30 0
      src/pages/weComTask/page/businessPlan/create/components/massSending/index.less
  23. 101 99
      src/pages/weComTask/page/businessPlan/create/components/massSending/index.tsx
  24. 143 0
      src/pages/weComTask/page/businessPlan/create/components/massSending/materialNoTextMould.tsx
  25. 5 5
      src/pages/weComTask/page/businessPlan/create/components/massSending/previewMassSendingStrategy.tsx
  26. 1 3
      src/pages/weComTask/page/businessPlan/create/components/massSending/settingsMassSending.tsx
  27. 4 3
      src/pages/weComTask/page/businessPlan/create/components/massSending/showContent.tsx
  28. 0 0
      src/pages/weComTask/page/businessPlan/create/components/massSending/showContentTable.tsx
  29. 1 1
      src/pages/weComTask/page/businessPlan/create/components/massSending/strategy.tsx
  30. 62 58
      src/pages/weComTask/page/businessPlan/create/components/userInherit/index.tsx
  31. 2 2
      src/pages/weComTask/page/businessPlan/create/components/userInherit/previewUserlnherit.tsx
  32. 1 1
      src/pages/weComTask/page/businessPlan/create/components/userInherit/settingsUserlnherit.tsx
  33. 0 0
      src/pages/weComTask/page/businessPlan/create/components/welcome/const.ts
  34. 0 0
      src/pages/weComTask/page/businessPlan/create/components/welcome/index.less
  35. 65 60
      src/pages/weComTask/page/businessPlan/create/components/welcome/index.tsx
  36. 1 1
      src/pages/weComTask/page/businessPlan/create/components/welcome/previewWelcome.tsx
  37. 2 2
      src/pages/weComTask/page/businessPlan/create/components/welcome/settingsWelcome.tsx
  38. 0 0
      src/pages/weComTask/page/businessPlan/create/components/welcome/welcomeShowTable.tsx
  39. 112 2
      src/pages/weComTask/page/businessPlan/create/const.tsx
  40. 31 10
      src/pages/weComTask/page/businessPlan/create/index.less
  41. 231 11
      src/pages/weComTask/page/businessPlan/create/index.tsx
  42. 128 2
      src/pages/weComTask/page/businessPlan/create/tableConfig.tsx
  43. 23 1
      src/pages/weComTask/page/businessPlan/create/typings.d.ts
  44. 3 3
      src/pages/weComTask/page/businessPlan/taskList/components/externalUserTransferTask/index.tsx
  45. 0 0
      src/pages/weComTask/page/businessPlan/taskList/components/externalUserTransferTask/log.tsx
  46. 0 0
      src/pages/weComTask/page/businessPlan/taskList/components/externalUserTransferTask/tableConfig.tsx
  47. 0 0
      src/pages/weComTask/page/businessPlan/taskList/components/groupTask/groupErrorCountList.tsx
  48. 0 0
      src/pages/weComTask/page/businessPlan/taskList/components/groupTask/groupReSend.tsx
  49. 1 1
      src/pages/weComTask/page/businessPlan/taskList/components/groupTask/groupTableConfig.tsx
  50. 0 0
      src/pages/weComTask/page/businessPlan/taskList/components/groupTask/groupTaskNotes.tsx
  51. 0 0
      src/pages/weComTask/page/businessPlan/taskList/components/groupTask/groupXfCorpTabls.tsx
  52. 7 7
      src/pages/weComTask/page/businessPlan/taskList/components/groupTask/index.tsx
  53. 51 0
      src/pages/weComTask/page/businessPlan/taskList/components/highGroupTask/groupErrorCountList.tsx
  54. 35 0
      src/pages/weComTask/page/businessPlan/taskList/components/highGroupTask/groupReSend.tsx
  55. 309 0
      src/pages/weComTask/page/businessPlan/taskList/components/highGroupTask/highGroupTableConfig.tsx
  56. 77 0
      src/pages/weComTask/page/businessPlan/taskList/components/highGroupTask/highGroupTaskNotes.tsx
  57. 121 0
      src/pages/weComTask/page/businessPlan/taskList/components/highGroupTask/highGroupXfCorpTabls.tsx
  58. 278 0
      src/pages/weComTask/page/businessPlan/taskList/components/highGroupTask/index.tsx
  59. 2 2
      src/pages/weComTask/page/businessPlan/taskList/components/welcomeTask/index.tsx
  60. 0 0
      src/pages/weComTask/page/businessPlan/taskList/components/welcomeTask/welcomeTableConfig.tsx
  61. 7 4
      src/pages/weComTask/page/businessPlan/taskList/log.tsx
  62. 48 4
      src/pages/weComTask/page/businessPlan/taskList/tableConfig.tsx
  63. 1 1
      src/pages/weComTask/page/corpUserAssign/index.tsx
  64. 22 12
      src/pages/weComTask/page/corpUserAssign/tableConfig.tsx
  65. 1 1
      src/pages/weComTask/page/corpUserManage/tableConfig.tsx

+ 111 - 0
src/API/global.ts

@@ -60,6 +60,117 @@ export async function api_get_external_tag_group_list(params: { corpId: string }
     })
 }
 
+/**
+ * 所有企业
+ * @param params 
+ * @returns 
+ */
+export async function getCorpAllListApi(params: { corpName?: string }) {
+    return request({
+        url: '/corp/listAll',
+        method: 'POST',
+        params
+    })
+}
+
+export interface GetCorpExternalUserProps {
+    pageSize: number,
+    pageNum: number,
+    corpId: string,
+    noExternalTag?:boolean,//无标签用户
+    externalTagRelation?: "AND" | "OR",
+    sysExternalTagRelation?: "AND" | "OR",
+    /** 企微号ID */
+    corpUserIds?: string,
+    // 客户ID
+    externalUserId?: string,
+    // 关注时间
+    beginTime?: string,
+    // 关注结束时间
+    endTime?: string,
+    // 客户名称
+    name?: string,
+    // 性别 
+    genders?: number
+    // 是否已删除 
+    deleted?: boolean
+    externalTagIds?: number[],
+    sysExternalTagIds?: number[],
+    // 排除客户标签
+    externalExcludeTagIds?:number[],
+    externalExcludeTagRelation?: "AND" | "OR",
+    sysExternalExcludeTagIds?:number[],
+    sysExternalExcludeTagRelation?: "AND" | "OR",
+    // 书城标识
+    platformKey?: string
+    //书城用户ID
+    platformUserId?: string
+    // 最低充值次数
+    rechargeCountMin?: number
+    // 最大充值次数
+    rechargeCountMax?: number
+    // 最低充值金额
+    rechargeMoneyMin?: number
+    // 最大充值金额
+    rechargeMoneyMax?: number
+    // 充值小说
+    rechargeBook?: string
+    // 最少充值次数
+    lastRechargeTimeMin?: number
+    // 最大充值次数
+    lastRechargeTimeMax?: number
+    // 最低首充金额
+    firstRechargeMoneyMin?: number
+    firstRechargeMoneyMax?: number
+    // 最低单笔最大充值金额
+    rechargeMoneyMaxMin?: number
+    rechargeMoneyMaxMax?: number
+    // 系统 手机系统:0:未知、1:安卓、2:IOS
+    operList?: number
+    remarkMobiles?: string
+    registerBeginTime?: string
+    registerEndTime?: string
+    firstRechargeBeginTime?: string
+    firstRechargeEndTime?: string,
+    subHourMin?: number
+    subHourMax?: number
+    subDayMin?: number
+    subDayMax?: number
+    deleteBeginTime?:string// "删除时间-最小(deleted = 1时出现)")
+    deleteEndTime?:string//"删除时间-最大(deleted = 1时出现)")
+    gameId?:number//游戏id
+    roleName?:string//角色名称
+    roleCreateTimeBegin?:string//角色创建时间-最小
+    roleCreateTimeEnd?:string//角色创建时间-最大
+    roleVipLevelMin?:number//角色VIP等级-最小
+    roleVipLevelMax?:number//角色VIP等级-最大
+    userRegTimeBegin?:string//角色对应玩家注册时间-最小
+    userRegTimeEnd?:string//角色对应玩家注册时间-最大
+    roleTodayRechargeMoneyMin?:number//角色当天充值金额-最小
+    roleTodayRechargeMoneyMax?:number//角色当天充值金额-最大
+    roleTotalRechargeMoneyMin?:number//角色累计充值金额-最小
+    roleTotalRechargeMoneyMax?:number//角色累计充值金额-最大
+    roleLastRechargeTimeBegin?:string//角色最近充值时间-最小
+    roleLastRechargeTimeEnd?:string//角色最近充值时间-最大
+    roleLastActiveTimeBegin?:string//角色最近活跃时间-最小
+    roleLastActiveTimeEnd?:string//角色最近活跃时间-最大
+    singleDeleteBeginTime?: number
+    singleDeleteEndTime?: number
+
+}
+/**
+ * 获取客服列表
+ * @param data 
+ * @returns 
+ */
+export async function getCorpExternalUserListApi(data: GetCorpExternalUserProps) {
+    return request({
+        url: '/corpExternalUser/listOfPage',
+        method: 'POST',
+        data
+    });
+}
+
 /**获取背景图ID列表*/
 export async function api_get_img_typeList() {
     return request({

+ 1 - 1
src/Hook/useNewToken.tsx

@@ -23,7 +23,7 @@ let themeConfig = {
 	"acLayout": "rgba(12,37,61,1)",
 	"acContainer": "rgba(247,247,247,1)",
 	"dfcolorBorder": "rgba(134,140,148,0.5)",
-	"dfcolorBorderSecondary": "rgba(152,196,245,0)",
+	"dfcolorBorderSecondary": "rgb(228, 228, 228)", //"rgba(152,196,245,0)",
 	"dfcolorTextQuaternary": "rgba(0, 0, 0, 0.25)",
 	"dfContentBgColor": "rgba(255,255,255,1)",
 	"dfcolorLink": "#1677ff",

+ 2 - 2
src/layout/AvatarDropdown.tsx

@@ -1,5 +1,5 @@
 import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
-import { CloseOutlined, LogoutOutlined, SwapOutlined, SwapRightOutlined } from '@ant-design/icons';
+import { CloseOutlined, LogoutOutlined, SwapOutlined, SwapRightOutlined, UserOutlined } from '@ant-design/icons';
 import { Avatar, MenuProps } from 'antd';
 import globaStore from '../store/index'
 import HeaderDropdown from '../components/HeaderDropdown';
@@ -83,7 +83,7 @@ const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ config }) => {
         setDopen(open)
       }} open={dOpen} >
         <span className={`${styles.action} ${styles.account}`} ref={ref1}>
-          <Avatar className={styles.avatar} style={{ backgroundColor: '#fde3cf', color: '#f56a00' }} src={userInfo?.dingTalkUserList?.[0]?.avatar || config?.myBgUrl || config?.bgUrl[config.acPrimary] || 'https://api.dicebear.com/7.x/miniavs/svg?seed=1'} alt="avatar" />
+          <Avatar className={styles.avatar} style={{ backgroundColor: '#fde3cf', color: '#f56a00' }} icon={<UserOutlined />} alt="avatar" />
           <span className={`${styles.name} anticon`} style={localStorage.getItem("showGx") != '1' ? { color: '#fff' } : { color: '#fff' }}>{userInfo?.nickname}</span>
         </span>
       </HeaderDropdown>

+ 2 - 3
src/layout/index.tsx

@@ -149,7 +149,6 @@ function MainLayout(props: Props) {
         let oldPath = sessionStorage.getItem('oldPath')
         let pathNum = oldPath?.split('/').length
         let thatName = name
-        //console.log(clickName, data, data[clickName as string])
         // 当前菜单刷新
         if (jump) {
             setTimeout(() => {
@@ -221,7 +220,7 @@ function MainLayout(props: Props) {
     useUpdateEffect(() => {
         const newKey = location.hash.replace('#', '').split('?')?.[0]
         if (newKey !== letKey?.[0]) {
-            sessionStorage.setItem('oldPath', newKey);
+            // sessionStorage.setItem('oldPath', newKey);
             setLetKey([newKey])
             let pnum = newKey.split('/').length
             if (pnum && pnum > 3) {//三级菜单
@@ -311,7 +310,7 @@ function MainLayout(props: Props) {
                     ...config.default.components.Card,
                     colorBorderSecondary: config.cardColorBorder,//内部边框色
                     colorBgContainer: config.cardColorBgContainer,//背景色
-                },
+                }
             },
             algorithm: [...config.default.algorithm, ...config.algorithm]
         }}

+ 1 - 1
src/pages/login/entry/index.tsx

@@ -8,7 +8,7 @@ import Login from "../login";
 ReactDOM.createRoot(
     document.getElementById('root') as HTMLElement
 ).render( <Provider store={store}>
-    <HashRouter>
+    <HashRouter future={{ v7_relativeSplatPath: true, v7_startTransition: true }}>
         <Routes>
             <Route path='/' element={<Login />}></Route>
             <Route path='/login' element={<Login />}></Route>

+ 0 - 3
src/pages/login/login.tsx

@@ -210,9 +210,6 @@ const Login: React.FC<{}> = () => {
           })
           let menus: any = Object.values(obj).sort((a: any, b: any) => { return a[0]?.orderNum - b[0]?.orderNum })
           let path = menus[0][0].path
-          if (path === '/imChat') {
-            path = '/imChat#/imChat'
-          }
           window.location.href = window.location.origin + path
         })
       }

+ 36 - 0
src/pages/weComTask/API/businessPlan/create.ts

@@ -105,6 +105,29 @@ export async function getSendTaskGroupuserListApi(data: GetSendTaskGroupuserList
     });
 }
 
+/**
+ * 获取高级群发下发企微号列表
+ * @param data 
+ * @returns 
+ */
+export async function getMessageSendTaskuserListApi(data: GetSendTaskGroupuserListProps) {
+    return request({
+        url: `/corp/message/send/task/user/list`,
+        method: 'POST',
+        data
+    });
+}
+
+/**
+ * 高级群发消息重试
+ * @returns 
+ */
+export async function putMessageUpdateApi(id:any) {
+    return request({
+        url: `/corp/message/send/taskLog/status/update/${id}`,
+        method: 'PUT',
+    });
+}
 
 
 /**
@@ -205,6 +228,19 @@ export async function getSendTaskGroupMsgListApi(data: GetSendTaskGroupMsgListPr
     });
 }
 
+/**
+ * 获取高级群发记录列表
+ * @param data 
+ * @returns 
+ */
+export async function getMessageSendTaskMsgListApi(data: GetSendTaskGroupMsgListProps) {
+    return request({
+        url: `/corp/message/send/task/msg/list`,
+        method: 'POST',
+        data
+    });
+}
+
 /**
  * 继承日志
  * @param data 

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

@@ -177,7 +177,7 @@ const FilterUserText = ({ data, configName, configType = 'USER_GROUP', bookCityL
     (content?.subHour ? `关注距今时间:${content?.subHour?.subHourMin ? content?.subHour?.subHourMin + '小时' : '不限'} - ${content?.subHour?.subHourMax ? content?.subHour?.subHourMax + '小时' : '不限'}\n` : '') +
     (content?.subDay ? `关注距今天:${content?.subDay?.subDayMin ? content?.subDay?.subDayMin + '天' : '不限'} - ${content?.subDay?.subDayMax ? content?.subDay?.subDayMax + '天' : '不限'}\n` : '') +
     (content?.opers?.length > 0 ? `手机系统:${content?.opers.map(g => ({ '0': '未知', '1': '安卓', '2': 'IOS' } as any)[g]).join('、')}\n` : '') +
-    (content?.groupChatNames && `群名称列表:${content?.groupChatNames?.toString()}`) +
+    (content?.groupChatNames ? `群名称列表:${content?.groupChatNames?.toString()}` : '') +
     (content?.groupChatTime ? `群创建时间:${content?.groupChatTime?.groupChatBeginTime ? content?.groupChatTime?.groupChatBeginTime + '小时' : '不限'} - ${content?.groupChatTime?.groupChatEndTime ? content?.groupChatTime?.groupChatEndTime + '小时' : '不限'}\n` : '') +
     (content?.groupChatHour ? `群创建时长:${content?.groupChatHour?.groupChatHourMin ? content?.groupChatHour?.groupChatHourMin : '不限'} - ${content?.groupChatHour?.groupChatHourMax ? content?.groupChatHour?.groupChatHourMax : '不限'}\n` : '') +
     (content?.groupChatUser ? `群人数:${content?.groupChatUser?.groupChatUserMin ? content?.groupChatUser?.groupChatUserMin : '不限'} - ${content?.groupChatUser?.groupChatUserMax ? content?.groupChatUser?.groupChatUserMax : '不限'}\n` : '') +

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

@@ -38,7 +38,7 @@ const PreviewTime: React.FC<Props> = ({ timeRepeatType, repeatArray, sendDay, se
         case 'TIME_TYPE_SINGLE_PLACE': // 定时发送
             return <div>
                 <Title level={5} style={{ marginTop: 0 }}>{TIME_TYPE[timeRepeatType]}</Title>
-                <Paragraph style={{ margin: 0 }}>执行时间:<span style={{ color: '#1890ff' }}>{sendDay}</span></Paragraph>
+                <Paragraph style={{ margin: 0 }}>执行时间:<span style={{ color: '#1890ff' }}>{sendDay} {sendTime}</span></Paragraph>
             </div>
     }
 

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

@@ -0,0 +1,153 @@
+.SelectPackage {
+    .topContent {
+        padding: 20px 10px 4px;
+
+        .title {
+            width: 135px;
+            display: inline-block;
+        }
+    }
+
+    .pageType {
+        border-bottom: 1px solid #f0f0f0;
+        padding: 16px 4px;
+    }
+
+    .content {
+        width: 100%;
+        display: flex;
+        justify-content: flex-start;
+        min-height: 200px;
+
+        .left {
+            width: 200px;
+            padding-top: 10px;
+            border-right: 1px solid #f0f0f0;
+            box-sizing: border-box;
+            overflow: hidden;
+
+            .title {
+                margin: 0 0 5px;
+                font-weight: 700;
+                padding-left: 10px;
+                box-sizing: border-box;
+            }
+
+            .accountIdList {
+                max-height: 510px;
+                overflow: hidden;
+                overflow-y: auto;
+
+                &::-webkit-scrollbar {
+                    width: 1px;
+                    height: 1px;
+                }
+            }
+
+            .accItem {
+                height: 32px;
+                line-height: 32px;
+                cursor: pointer;
+                padding-left: 10px;
+                padding-right: 10px;
+                box-sizing: border-box;
+                margin-bottom: 1px;
+                box-sizing: border-box;
+                display: flex;
+                justify-content: space-between;
+                align-items: center;
+                gap: 10px;
+
+                >div {
+                    flex: 1 0;
+                    overflow: hidden;
+                    display: flex;
+                    align-items: center;
+                }
+
+                &:hover {
+                    background-color: #e6f7ff;
+                    color: #1890ff;
+
+                    >div>span {
+                        color: #1890ff;
+                    }
+                }
+            }
+
+            .select {
+                background-color: #e6f7ff;
+
+                border-right: 2px solid #1890ff;
+
+                &>div>span {
+                    color: #1890ff;
+                }
+            }
+
+            .alreadySelect {
+                background-color: #d1f0ff;
+            }
+        }
+
+        .right {
+            padding: 10px;
+            box-sizing: border-box;
+            // width: calc(100% - 150px);
+            flex: 1;
+            overflow: hidden;
+        }
+
+        .center {
+            width: 150px;
+            border-left: 1px solid #e8e8e8;
+            padding: 5px;
+            box-sizing: border-box;
+
+            >h5 {
+                margin: 0 0 5px;
+            }
+
+            .select_content {
+                height: calc(500 - 28px);
+                overflow: hidden;
+                overflow-y: auto;
+
+                >div {
+                    padding: 3px 4px;
+                    background-color: #ebebeb;
+                    margin-bottom: 2px;
+                    border-radius: 4px;
+                    display: flex;
+                    align-items: center;
+                }
+            }
+
+            .marketingAssetName {
+                flex: 1;
+                overflow: hidden;
+                font-size: 12px;
+            }
+
+            .close {
+                width: 20px;
+                cursor: pointer;
+                color: red;
+            }
+        }
+    }
+
+    .refresh {
+        display: flex;
+        justify-content: flex-start;
+        align-items: center;
+
+        .tips {
+            margin-left: 20px;
+
+            div {
+                font-size: 12px;
+            }
+        }
+    }
+}

+ 220 - 0
src/pages/weComTask/components/selectExternalAccount/index.tsx

@@ -0,0 +1,220 @@
+import { Avatar, Button, Input, message, Modal, Select, Space, Table, Tag, Typography } from "antd";
+import React, { useEffect, useState } from "react";
+import style from './index.less'
+import { CheckOutlined, UserOutlined, CloseOutlined } from '@ant-design/icons'
+import { useAjax } from "@/Hook/useAjax";
+import { getCorpAllListApi, getCorpExternalUserListApi } from "@/API/global";
+import { copy } from "@/utils/utils";
+const { Text, Title } = Typography;
+
+
+/**
+ * 高级群发外部客户选择
+ * @param param0 
+ * @returns 
+ */
+const SelectExternalAccount: React.FC<EXTERNAL_ACCOUNT_PROPS.SelectExternalAccountProps> = ({ corpUsers, visible, onClose, onChange }) => {
+
+    /***************************************/
+    const [data, setData] = useState<TASK_CREATE.corpUsersProps[]>(corpUsers)
+    const [selectAdz, setSelectAdz] = useState<number>(1)   // 选择广告主
+    const [queryForm, setQueryForm] = useState<any>({ pageNum: 1, pageSize: 20, corpId: '', deleted: false })
+
+    const getCorpAllList = useAjax((params) => getCorpAllListApi(params))
+    const getCorpExternalUserList = useAjax((params) => getCorpExternalUserListApi(params))
+    /***************************************/
+
+    useEffect(() => {
+        getCorpAllList.run({}).then(res => {
+            if (res?.data?.length) {
+                setQueryForm(data => ({ ...data, corpId: res.data[0].corpId }))
+            }
+        })
+    }, [])
+
+    useEffect(() => {
+        if (queryForm?.corpId) {
+            getCorpExternalUserList.run(queryForm)
+        }
+    }, [queryForm])
+
+    const handleOk = () => {
+        if (data?.every(item => item?.externalUserList?.length)) {
+            onChange?.(data)
+        } else {
+            message.error('请选择外部联系人')
+        }
+    }
+
+    const handleSelectAdz = (value: number) => {
+        if (value === selectAdz) {
+            return
+        }
+        setSelectAdz(value)
+    }
+
+    return <Modal
+        title={<strong>高级群发外部客户选择</strong>}
+        open={visible}
+        onCancel={onClose}
+        onOk={handleOk}
+        width={1000}
+        className={`${style.SelectPackage}`}
+        styles={{
+            body: {
+                padding: '0 10px 0 10px'
+            }
+        }}
+    >
+        <div className={style.content}>
+            <div className={style.left}>
+                <h4 className={style.title}>客服号</h4>
+                <div className={style.accountIdList}>
+                    {corpUsers?.map((item, index) => {
+                        const externalUserList = data[index]?.externalUserList || []
+                        return <div key={index} onClick={() => { handleSelectAdz(index + 1) }} className={`${style.accItem} ${selectAdz === index + 1 && style.select} `}>
+                            <div><Text ellipsis={{ tooltip: true }}>{item?.name}({item.corpName})</Text></div>
+                            {externalUserList?.length > 0 && <CheckOutlined style={{ color: '#1890ff' }} />}
+                        </div>
+                    })}
+                </div>
+            </div>
+            <div className={style.right}>
+                <Space style={{ marginBottom: 10 }} align="end" size={5}>
+                    <Space.Compact>
+                        <Button>外部企业</Button>
+                        <Select
+                            showSearch
+                            style={{ width: 150 }}
+                            placeholder="请选择外部企业"
+                            filterOption={(input, option) =>
+                                ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                            }
+                            value={queryForm?.corpId}
+                            onChange={(e) => {
+                                setQueryForm({ ...queryForm, corpId: e, pageNum: 1 })
+                            }}
+                            options={getCorpAllList?.data?.data?.map(item => ({ label: item.corpName, value: item.corpId }))}
+                        />
+                    </Space.Compact>
+                    <Input placeholder="客户名称" style={{ width: 125 }} allowClear value={queryForm?.name} onChange={(e) => setQueryForm({ ...queryForm, name: e.target.value, pageNum: 1 })} />
+                    <Input placeholder="客户ID" style={{ width: 125 }} allowClear value={queryForm?.externalUserId} onChange={(e) => setQueryForm({ ...queryForm, externalUserId: e.target.value, pageNum: 1 })} />
+                </Space>
+
+                <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: 400 }}
+                    rowKey={'externalUserId'}
+                    size='small'
+                    rowSelection={{
+                        selectedRowKeys: data[selectAdz - 1]?.externalUserList?.map((item: any) => item?.externalUserId),
+                        getCheckboxProps: (record: any) => ({
+                            name: record.name,
+                        }),
+                        onSelect: (record: { externalUserId: string }, selected: boolean) => {
+                            let newData = JSON.parse(JSON.stringify(data))
+                            let dataIten = newData[selectAdz - 1]?.externalUserList || []
+                            if (selected) {
+                                dataIten.push({ ...record })
+                            } else {
+                                dataIten = dataIten.filter((item: { externalUserId: string }) => item.externalUserId !== record.externalUserId)
+                            }
+                            newData[selectAdz - 1].externalUserList = dataIten
+                            setData(newData)
+                        },
+                        onSelectAll: (selected: boolean, selectedRowss: { externalUserId: string }[], changeRows: { externalUserId: string }[]) => {
+                            let newData = JSON.parse(JSON.stringify(data))
+                            let dataIten = newData[selectAdz - 1]?.externalUserList || []
+                            if (selected) {
+                                let newSelectAccData = [...dataIten]
+                                changeRows.forEach((item: { externalUserId: string }) => {
+                                    let index = newSelectAccData.findIndex((ite: { externalUserId: string }) => ite.externalUserId === item.externalUserId)
+                                    if (index === -1) {
+                                        let data: any = { ...item }
+                                        newSelectAccData.push(data)
+                                    }
+                                })
+                                newData[selectAdz - 1].externalUserList = newSelectAccData
+                            } else {
+                                let newSelectAccData = dataIten.filter((item: { externalUserId: string }) => {
+                                    let index = changeRows.findIndex((ite: { externalUserId: string }) => ite.externalUserId === item.externalUserId)
+                                    if (index !== -1) {
+                                        return false
+                                    } else {
+                                        return true
+                                    }
+                                })
+                                newData[selectAdz - 1].externalUserList = newSelectAccData
+                            }
+                            setData(newData)
+                        }
+                    }}
+                    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) => {
+                            setQueryForm({ ...queryForm, pageNum: page, pageSize })
+                        }
+                    }}
+                />
+            </div>
+            <div className={style.center}>
+                <Title level={5}>已选:{data[selectAdz - 1]?.externalUserList?.length || 0}</Title>
+                <div className={style.select_content}>
+                    {data[selectAdz - 1]?.externalUserList?.map(item => <div key={item.externalUserId}>
+                        <Text ellipsis={{ tooltip: true }} className={style.marketingAssetName}>{item.name}({item?.corpName})</Text>
+                        <CloseOutlined className={style.close} onClick={() => {
+                            let newData = JSON.parse(JSON.stringify(data))
+                            newData[selectAdz - 1].externalUserList = newData[selectAdz - 1]?.externalUserList?.filter((i: any) => i?.externalUserId !== item.externalUserId)
+                            setData(newData)
+                        }} />
+                    </div>)}
+                </div>
+            </div>
+        </div>
+    </Modal>
+}
+
+export default React.memo(SelectExternalAccount);

+ 8 - 0
src/pages/weComTask/components/selectExternalAccount/typings.d.ts

@@ -0,0 +1,8 @@
+declare namespace EXTERNAL_ACCOUNT_PROPS {
+    interface SelectExternalAccountProps {
+        corpUsers: TASK_CREATE.corpUsersProps[]; // 企微号列表
+        visible?: boolean;
+        onClose?: () => void;
+        onChange?: (value: TASK_CREATE.corpUsersProps[]) => void;
+    }
+}

+ 4 - 3
src/pages/weComTask/entry/index.tsx

@@ -17,15 +17,16 @@ const Main = observer(() => {
     useEffect(() => {
         store.globaStore.userInit()//初始化用户信息
     }, [])
-
+    
+    const menus = toJS(store.globaStore.data.menus)
     return <Provider store={store}>
         <HashRouter
             future={{ v7_relativeSplatPath: true, v7_startTransition: true }}
         >
             <Suspense fallback={<div style={{ width: '100%', height: '100vh', display: 'flex', justifyContent: 'center', alignItems: 'center' }}><Spin indicator={<LoadingOutlined spin style={{ fontSize: 48 }} />} size="large" /></div>}>
-                <MainLayout data={toJS(store.globaStore.data.menus)} name='weComTask'>
+                <MainLayout data={menus} name='weComTask'>
                     <Routes>
-                        {routerConfig?.length > 0 && <Route path='/' element={<Navigate to={routerConfig[0].path} replace />}></Route>}
+                        {menus?.weComTask?.[0]?.children?.[0]?.path && <Route path='/' element={<Navigate to={menus?.weComTask?.[0]?.children?.[0]?.path} replace />}></Route>}
                         {routerConfig?.map(route => {
                             return <Route path={route.path} element={route.element} key={route.path} ></Route>
                         })}

+ 225 - 0
src/pages/weComTask/page/businessPlan/create/components/highMassSending/content.tsx

@@ -0,0 +1,225 @@
+import NewSteps from '@/pages/weComTask/components/newSteps';
+import { App, Button, Card, Form, Select } from 'antd';
+import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
+import '../../global.less';
+import { welcomeContentData } from '../../const';
+import { PlusOutlined, MinusOutlined } from '@ant-design/icons'
+import MaterialNoTextMould from './materialNoTextMould';
+import MaterialMould from '@/pages/weComTask/components/materialMould';
+
+/**
+ * 群发内容
+ * @param param0 
+ * @returns 
+ */
+const Content = forwardRef(({ massSendingStrategy, value, onChange }: TASK_CREATE.ContentProps, ref: React.ForwardedRef<{ handleOk: (type: string) => void }>) => {
+
+    /****************************************/
+    const { message } = App.useApp()
+    const ref1 = useRef<HTMLDivElement>(null)
+    const [form] = Form.useForm();
+
+    const [stepsList, setStepsList] = useState<any>([])
+    /****************************************/
+
+    useImperativeHandle(ref, () => ({
+        handleOk(type) {
+            handleOk(type)
+        }
+    }));
+
+    // 回填
+    useEffect(() => {
+        if (!massSendingStrategy?.strategySettings) {
+            message.error('请先设置群发策略')
+            return
+        }
+
+        if (value && Object.keys(value).length) {
+            filedUpdateChange(value)
+            form.setFieldsValue(value)
+        } else {
+            const data = {
+                massSendingContentDTO: massSendingStrategy.strategySettings.map(item => {
+                    return {
+                        sendContentDto: item.sendData.map(() => ({ contentDTO: [undefined], sendMode: undefined }))
+                    }
+                })
+            }
+            filedUpdateChange(data)
+            form.setFieldsValue(data)
+        }
+    }, [value, massSendingStrategy])
+
+    const handleOk = (type: string) => {
+        form.validateFields().then((values) => {
+            console.log(values)
+            onChange(values, type)
+        }).catch(() => {
+            form.submit()
+        });
+    };
+
+    const filedUpdateChange = ({ massSendingContentDTO }: { massSendingContentDTO: { sendContentDto: any }[] }) => {
+        const stepsData = massSendingContentDTO.map((item, index) => {
+
+            const children = item.sendContentDto.map((ci, c_index) => {
+
+                const content = ci?.contentDTO?.map((i, i_index) => {
+                    return { title: `内容${i_index + 1}`, checked: i?.length > 0, id: 'clientId' + '_' + index + '_' + c_index + 'contentDTO' }
+                })
+
+                const contentChildren = [
+                    { title: '发送模式', checked: ci?.sendMode || ci?.sendMode === 0, id: 'clientId' + '_' + index + '_' + c_index + 'sendMode' },
+                    ...content
+                ]
+
+                return {
+                    title: `发送对象 ${c_index + 1}`,
+                    id: 'clientId' + '_' + index + '_' + c_index,
+                    children: contentChildren,
+                    checked: (ci?.sendMode || ci?.sendMode === 0) && contentChildren?.every(item => item.checked)
+                }
+            })
+
+            return {
+                title: '策略' + (index + 1),
+                id: 'clientId' + '_' + index,
+                children: [
+                    ...children,
+                    { title: '完成', checked: children?.every(item => item.checked) }
+                ],
+                checked: children?.every(item => item.checked)
+            }
+        })
+
+        setStepsList([
+            ...stepsData,
+            { title: '完成', checked: stepsData?.every(item => item.checked) }
+        ])
+    }
+
+    return <>
+        <div className={`body_steps`}>
+            <NewSteps
+                items={stepsList}
+                onChange={(e) => {
+                    if (e?.id)
+                        ref1.current?.querySelector('#' + e?.id)?.scrollIntoView({ behavior: 'smooth' })
+                }}
+            />
+        </div>
+        <div className={`body_content`} ref={ref1}>
+            <Form
+                form={form}
+                name="gjnewContent"
+                labelAlign='left'
+                labelCol={{ span: 5 }}
+                colon={false}
+                scrollToFirstError={{
+                    behavior: 'smooth',
+                    block: 'center'
+                }}
+                onFinishFailed={({ errorFields }) => {
+                    message.error(errorFields?.[0]?.errors?.[0])
+                }}
+                onFinish={handleOk}
+                initialValues={{ massSendingContentDTO: [undefined] }}
+                onFieldsChange={() => {
+                    console.log(form.getFieldsValue())
+                    filedUpdateChange(form.getFieldsValue())
+                }}
+                preserve={true}
+            >
+                <Form.List name='massSendingContentDTO'>
+                    {(fields) => (
+                        <>
+                            {fields.map(({ key, name, ...restField }, index) => {
+                                return <Card key={index} title={<strong>策略 {index + 1}</strong>} style={{ background: '#fff', marginBottom: 10 }} id={'clientId' + '_' + index}>
+                                    <Form.List {...restField} name={[name, 'sendContentDto']}>
+                                        {(fields) => (
+                                            <>
+                                                {fields.map(({ key, name, ...restField }, i) => {
+                                                    return <Card
+                                                        key={i}
+                                                        title={<strong>发送对象{i + 1} 内容配置</strong>}
+                                                        style={{ background: '#fff', marginBottom: 10 }}
+                                                        id={'clientId' + '_' + index + '_' + i}
+                                                    >
+                                                        <div id={'clientId' + '_' + index + '_' + i + 'sendMode'}>
+                                                            <Form.Item
+                                                                {...restField}
+                                                                label={<strong>内容组发送模式</strong>}
+                                                                name={[name, 'sendMode']}
+                                                                rules={[{ required: true, message: '请选择内容组发送模式!' }]}
+                                                            >
+                                                                <Select
+                                                                    showSearch
+                                                                    style={{ width: 358 }}
+                                                                    allowClear
+                                                                    placeholder="请选择内容组发送模式"
+                                                                    filterOption={(input, option) =>
+                                                                        ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                                                                    }
+                                                                    options={welcomeContentData}
+                                                                />
+                                                            </Form.Item>
+                                                        </div>
+                                                        <div id={'clientId' + '_' + index + '_' + i + 'contentDTO'}>
+                                                            <Form.List {...restField} name={[name, 'contentDTO']} >
+                                                                {(fields, { add, remove }) => {
+                                                                    return <>
+                                                                        {fields.map(({ key, name, ...restField }, index) => {
+                                                                            return <div key={index}>
+                                                                                <h3 style={{ display: 'flex', justifyContent: 'space-between', margin: 0 }}>内容{index + 1}
+                                                                                    {fields?.length > 1 && <Button
+                                                                                        type="dashed"
+                                                                                        danger
+                                                                                        onClick={() => remove(index)}
+                                                                                        icon={<MinusOutlined />}
+                                                                                        size='small'
+                                                                                    >
+                                                                                        移除内容
+                                                                                    </Button>}
+                                                                                </h3>
+                                                                                <Form.Item
+                                                                                    {...restField}
+                                                                                    name={name}
+                                                                                    rules={[{
+                                                                                        validator: (_, value) => {
+                                                                                            if (!value || value?.length === 0) {
+                                                                                                return Promise.reject('请填写内容或上传附件');
+                                                                                            }
+                                                                                            return Promise.resolve();
+                                                                                        }
+                                                                                    }]}
+                                                                                >
+                                                                                    <MaterialMould dataNum={1} />
+                                                                                </Form.Item>
+                                                                            </div>
+                                                                        })}
+                                                                        <Form.Item noStyle>
+                                                                            <Button type="dashed" onClick={() => add()} style={{ width: '100%' }} icon={<PlusOutlined />}>
+                                                                                新增内容
+                                                                            </Button>
+                                                                        </Form.Item>
+                                                                    </>
+                                                                }}
+                                                            </Form.List>
+                                                        </div>
+                                                    </Card>
+                                                })}
+                                            </>
+                                        )}
+                                    </Form.List>
+                                </Card>
+                            })}
+                        </>
+                    )}
+                </Form.List>
+            </Form>
+        </div>
+    </>
+});
+
+export default React.memo(Content);

+ 0 - 0
src/pages/weComTask/page/businessPlan/create/massSending/index.less → src/pages/weComTask/page/businessPlan/create/components/highMassSending/index.less


+ 503 - 0
src/pages/weComTask/page/businessPlan/create/components/highMassSending/index.tsx

@@ -0,0 +1,503 @@
+import React, { useContext, useState } from 'react';
+import style from '../../index.less';
+import { App, Button, Empty, Space, Upload } from 'antd';
+import { DispatchTaskCreate } from '../..';
+import dayjs from 'dayjs';
+import '../../global.less'
+import useNewToken from '@/Hook/useNewToken';
+import { getHighGroupData, headerJsMustStyle, headerJsStyle, TIME_TYPE, welcomeContentData } from '../../const';
+import ExcelJS from 'exceljs';
+import FilterUserText from '@/pages/weComTask/components/filterUser/filterUserText';
+import { RcFile } from 'antd/es/upload';
+import { groupBy, readFileAsBuffer } from '@/utils/utils';
+import SettingsHighMassSending from './settingsHighMassSending';
+import { saveAs } from 'file-saver';
+import PreviewMassSendingStrategy from '../massSending/previewMassSendingStrategy';
+import ShowContent from '../massSending/showContent';
+
+/**
+ * 高级群发
+ * @returns 
+ */
+const HighMassSending: React.FC = () => {
+
+    /***************************************************/
+    const { token } = useNewToken()
+    const { message } = App.useApp()
+    const { setSettings, settings, bookPlatForm, bookList, onPreviewReset } = useContext(DispatchTaskCreate)!;
+
+    const [newVisible, setNewVisible] = React.useState(false);
+    const [createType, setCreateType] = useState<'STRATEGY' | 'CONTENT'>()
+    const [downloadLoading, setDownloadLoading] = useState<boolean>(false)
+    /***************************************************/
+
+    const exportExcel = async () => {
+        setDownloadLoading(true)
+        const workbook = new ExcelJS.Workbook();  // 创建空工作簿
+        const worksheet = workbook.addWorksheet('Sheet1');  // 添加工作表
+        const headerRowIndex = 1; // 表头行索引
+
+        // 表头冻结
+        worksheet.views = [
+            {
+                state: 'frozen',
+                ySplit: 1,          // 冻结行数
+                topLeftCell: 'A2'   // 冻结后可见区域的起始单元格
+            }
+        ];
+
+        // 设置全局禁止
+        worksheet.protect('yourPwd', {
+            deleteColumns: false,       // 禁止删除列
+            sort: false,                // 禁止排序
+            autoFilter: false           // 禁止自动筛选
+        });
+
+        worksheet.columns = [
+            { key: 'A1', width: 20, header: '账号' },
+            { key: 'A2', width: 20, header: '高级群发标题' },
+            { key: 'A3', width: 30, header: '策略信息' },
+            { key: 'A4', width: 40, header: '发送对象' },
+            { key: 'A5', width: 45, header: '群发外部联系人' },
+            { key: 'A6', width: 70, header: '群发内容' },
+            { key: 'A7', width: 30, header: '图文链接' },
+            { key: 'A8', width: 30, header: '小程序APPID' },
+            { key: 'A9', width: 30, header: '小程序路径' },
+        ];
+
+        // 表头设置样式
+        worksheet.getRow(headerRowIndex).height = 30;
+        Array(9).fill(0).forEach((_, index) => {
+            const col = index + 1; // 从第1列开始
+            // 设置表头字体样式
+            worksheet.getCell(headerRowIndex, col).style = index >= 6 ? headerJsMustStyle as any : headerJsStyle as any;
+        })
+
+
+        const data = getHighGroupData(settings)
+        console.log('数据', data)
+        let rowLength = 0
+        // 合并单元集合
+        const mergeCells = [];
+        // 解放填写限制区域
+        const unLockArea = [];
+        let dataRow = 1
+        // 数据内容
+        settings?.corpUsers?.forEach((item, i) => {
+            const startRow = 2 + rowLength
+            let strategyIndex = 0
+            data.forEach((dataItem) => {
+                const externalUserListLength = item.externalUserList.length
+                const startRow = rowLength + 2
+                if (strategyIndex !== dataItem.strategyIndex) {
+                    mergeCells.push({
+                        startRow,
+                        startColumn: 3,
+                        endRow: startRow + (dataItem.strategyDataCount * externalUserListLength) - 1,
+                        endColumn: 3
+                    })
+                }
+                if (dataItem.sendDataRowSpan) {
+                    mergeCells.push({
+                        startRow,
+                        startColumn: 4,
+                        endRow: startRow + (dataItem.sendDataRowSpan * externalUserListLength) - 1,
+                        endColumn: 4,
+                    })
+                }
+
+                const mediaItem = JSON.parse(JSON.stringify(dataItem?.content || []))
+                let linkPlaceholder = '<空>'
+                let miniprogramPlaceholder = '<空>'
+                const contentRichText = mediaItem.map(item => {
+                    switch (item.mediaType) {
+                        case 'link':
+                            linkPlaceholder = '请输入'
+                            return { text: `链接:${item.linkTitle}_${item.linkDesc}\n`, font: { color: { argb: "FF0000" } } }
+                        case 'miniprogram':
+                            miniprogramPlaceholder = '请输入'
+                            return { text: `小程序:${item.miniprogramTitle}\n`, font: { color: { argb: "FF0000" } } }
+                        case 'file':
+                            return { text: `文件:${item.fileUrl}\n` }
+                        case 'video':
+                            return { text: `视频:${item.videoUrl}\n` }
+                        case 'image':
+                            return { text: `图片:${item.imageUrl}\n` }
+                        case 'text':
+                            return { text: `文本:${item.textContent}\n` }
+                        default:
+                            return { text: `该类型没有请联系管理员` }
+                    }
+                })
+
+                const richText = {
+                    richText: [
+                        { text: `(内容${dataItem?.contentIndex})\n`, font: { bold: true } },
+                        ...contentRichText
+                    ]
+                }
+                
+                item.externalUserList.forEach((user) => {
+                    rowLength += 1;
+                    if (linkPlaceholder !== '<空>') {
+                        unLockArea.push(`${dataRow}_7`)
+                    }
+                    if (miniprogramPlaceholder !== '<空>') {
+                        unLockArea.push(`${dataRow}_8`, `${dataRow}_9`)
+                    }
+
+                    worksheet.addRow([
+                        // 账号
+                        `${item.name}(ID:${item.corpUserId})`,
+                        // 群发标题
+                        `${dataItem?.groupSendName}`,
+                        // 策略信息
+                        {
+                            richText: [
+                                { text: `(策略${dataItem?.strategyIndex})\n`, font: { bold: true } },
+                                { text: `名称:${dataItem?.strategyData?.strategyName || '<空>'}\n` },
+                                {
+                                    text: (`执行类型:${TIME_TYPE[dataItem?.strategyData?.timeRepeatType]}\n`) +
+                                        (dataItem?.strategyData?.sendDay ? `执行时间:${dataItem?.strategyData?.sendDay}\n` : '') +
+                                        (dataItem?.strategyData?.startTime ? `执行日期:${dataItem?.strategyData?.startTime}~${dataItem?.strategyData?.endTime ? dataItem?.strategyData?.endTime : '长期执行'}\n` : '') +
+                                        (dataItem?.strategyData?.sendTime ? '执行时间:' + dataItem?.strategyData?.sendTime + '\n' : '') +
+                                        (dataItem?.strategyData?.repeatArray ? `执行天数:${dataItem?.strategyData?.repeatArray.join('、')}\n` : '')
+                                }
+                            ]
+                        },
+                        // 发送对象
+                        {
+                            richText: [
+                                { text: `(发送对象${dataItem?.sendDataIndex})\n`, font: { bold: true } },
+                                { text: `发送模式:${welcomeContentData?.find(i => i.value === dataItem?.sendMode)?.label}\n` },
+                                {
+                                    text: `类型:${dataItem?.sendData?.externalUserType === 'all' ? '全部' : '指定'}\n` +
+                                        (dataItem?.sendData?.externalUserType === 'specify' && dataItem?.sendData?.externalUserFilter ? FilterUserText({
+                                            data: dataItem?.sendData?.externalUserFilter?.configContent,
+                                            configName: dataItem?.sendData?.externalUserFilter?.configName,
+                                            bookCityList: bookPlatForm?.map(item => ({ label: item.platformName, value: item.platformKey })),
+                                            bookPlatForm,
+                                            bookList,
+                                            configType: 'GROUP_GROUP'
+                                        }) : '')
+                                }
+                            ]
+                        },
+                        {
+                            richText: [
+                                { text: `消息发送号外部联系人\n`, font: { bold: true } },
+                                { text: `${user?.externalUserId}(${user.name})\n` },
+                                { text: `群聊所在主体\n`, font: { bold: true } },
+                                { text: `${user?.corpId}(${user.corpName})` }
+                            ]
+                        },
+                        // 群发内容
+                        richText,
+                        // 图文链接
+                        linkPlaceholder,
+                        // 小程序APPID
+                        miniprogramPlaceholder,
+                        // 小程序路径
+                        miniprogramPlaceholder
+                    ])
+
+                    dataRow++;
+                    worksheet.getRow(dataRow).height = 65; // 设置行高
+                    worksheet.getRow(dataRow).alignment = {
+                        vertical: 'middle',
+                        wrapText: true
+                    }
+                    if (i % 2) {
+                        worksheet.getRow(dataRow).eachCell(function (cell, rowNumber) {
+                            if (rowNumber <= 9) {
+                                cell.border = {
+                                    top: { style: 'thin', color: { argb: '525252' } },
+                                    bottom: { style: 'thin', color: { argb: '525252' } },
+                                    left: { style: 'thin', color: { argb: '525252' } },
+                                    right: { style: 'thin', color: { argb: '525252' } }
+                                }
+                                cell.fill = {
+                                    type: 'pattern',
+                                    pattern: 'solid',
+                                    fgColor: { argb: 'f0f0f0' }
+                                }
+                            }
+                        })
+                    } else {
+                        worksheet.getRow(dataRow).eachCell(function (cell, rowNumber) {
+                            if (rowNumber <= 9) {
+                                cell.border = {
+                                    top: { style: 'thin', color: { argb: '525252' } },
+                                    bottom: { style: 'thin', color: { argb: '525252' } },
+                                    left: { style: 'thin', color: { argb: '525252' } },
+                                    right: { style: 'thin', color: { argb: '525252' } }
+                                }
+                            }
+                        })
+                    }
+                })
+
+                strategyIndex = dataItem.strategyIndex
+            })
+
+            // 1、客服号 群发标题
+            const endRow = 2 + rowLength - 1
+            mergeCells.push({
+                startRow,
+                startColumn: 1,
+                endRow,
+                endColumn: 1,
+            })
+            mergeCells.push({
+                startRow,
+                startColumn: 2,
+                endRow,
+                endColumn: 2,
+            })
+        })
+        // 合并单元格
+        mergeCells.forEach(({ startRow, startColumn, endRow, endColumn }) => {
+            worksheet.mergeCells(startRow, startColumn, endRow, endColumn); // 合并单元格
+        })
+
+        // 设置可填写区域
+        for (let rowNum = 1; rowNum <= rowLength; rowNum++) {
+            for (let colNum = 1; colNum <= 3; colNum++) {
+                if (unLockArea.includes(`${rowNum}_${colNum + 6}`)) {
+                    const cell = worksheet.getCell(rowNum + 1, colNum + 6);
+                    cell.protection = { locked: false };
+                    cell.fill = {
+                        type: 'pattern',
+                        pattern: 'solid',
+                        fgColor: { argb: 'ffadd2' }
+                    }
+                }
+            }
+        }
+        // 生成文件内容
+        workbook.xlsx.writeBuffer().then(buffer => {
+            // 通过 Blob 下载
+            const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
+            console.log('33333333333333333333333', blob)
+            saveAs(blob, `高级群发内容配置_${dayjs().format('YYYYMMDDHHmmss')}.xlsx`);
+            setTimeout(() => setDownloadLoading(false), 1000)
+        });
+    }
+
+    // 读取Excel
+    const readExcelContent = async (file: RcFile) => {
+        const buffer: any = await readFileAsBuffer(file);
+        const workbook = new ExcelJS.Workbook();
+        await workbook.xlsx.load(buffer);  // 加载 Excel 内容
+
+        const worksheet = workbook.worksheets[0];  // 获取第一个工作表
+        const data = [];
+
+        worksheet.eachRow((row) => {
+            const rowData = {};
+            row.eachCell((cell, colNumber) => {
+                if (cell.isHyperlink) {
+                    rowData[`col${colNumber}`] = cell.text || '';
+                } else {
+                    rowData[`col${colNumber}`] = cell.value || '';
+                }
+            });
+            data.push(rowData);
+        });
+
+        data.shift();  // 删除表头
+        const NULLDATA = ['请输入', '']
+        if (data?.some(item => (NULLDATA.includes(item.col9) || !item?.col9 || NULLDATA.includes(item.col7) || !item?.col7 || NULLDATA.includes(item.col8) || !item?.col8))) {
+            message.error('请正确填写Excel内容!')
+            return
+        }
+
+        const msgData = getHighGroupData(settings)
+        console.log('读取的Excel数据', data, msgData)
+
+        if (settings?.corpUsers?.reduce((pre, cur) => pre + (cur?.externalUserList?.length * msgData.length), 0) !== data?.length) {
+            message.error('数量对不上,请正确填写Excel内容!')
+            return
+        }
+
+        const groupData = groupBy(data, (item) => [item['col1']]).reduce((acc, cur) => {
+            const id = cur[0]['col1'].split('ID:')[1].replace(/\)$/, '');
+            acc[id] = cur
+            return acc;
+        }, {});
+
+        const corpUsers = settings?.corpUsers?.map(item => {
+            const highGroupMsgContent = []
+            groupData[item.corpUserId]?.forEach((i: any) => {
+                const strategyIndex = i.col3.richText[0].text.split('(策略')[1]?.split(')')[0]
+                
+                const sendDataIndex = i.col4.richText[0].text.split('(发送对象')[1]?.split(')')[0]
+                const externalUserId = i.col5.richText[1].text.split('(')[0]
+                const corpId = i.col5.richText[3].text.split('(')[0]
+                const contentIndex = i.col6.richText[0].text.split('(内容')[1]?.split(')')[0]
+                const layer1 = strategyIndex - 1;
+                if (!highGroupMsgContent[layer1]) highGroupMsgContent[layer1] = [];              // 初始化第二层
+                const layer2 = sendDataIndex - 1;
+                const layer3 = corpId + '-' + externalUserId
+                if (!highGroupMsgContent[layer1][layer2]) highGroupMsgContent[layer1][layer2] = {};
+                if (!highGroupMsgContent[layer1][layer2][layer3]) highGroupMsgContent[layer1][layer2][layer3] = [];
+
+
+                highGroupMsgContent[strategyIndex - 1][sendDataIndex - 1][layer3][contentIndex - 1] = {
+                    linkUrl: i.col7 === '<空>' ? undefined : i.col7?.toString()?.trim(),
+                    miniprogramAppid: i.col8 === '<空>' ? undefined : i.col8?.toString()?.trim(),
+                    miniprogramPage: i.col9 === '<空>' ? undefined : i.col9?.toString()?.trim()
+                }
+            })
+            return {
+                ...item,
+                highGroupMsgContent
+            }
+        })
+        console.log('corpUsers', corpUsers)
+        setSettings({
+            ...settings,
+            corpUsers
+        })
+    }
+
+    return <>
+        <div className={`${style.settingsBody_content_row}`}>
+            <div className={`${style.settingsBody_content_col}`}>
+                <div className={style.title}>
+                    <span>高级群发</span>
+                </div>
+                <div className={style.detail}>
+                    <div className={style.detail_title}>高级群发策略配置</div>
+                    <div className={style.detail_body}>
+                        {settings?.highMassSendingStrategy && Object.keys(settings?.highMassSendingStrategy).length > 0 ? <>
+                            <PreviewMassSendingStrategy massSendingStrategy={settings?.highMassSendingStrategy} configType='GROUP_GROUP' />
+                        </> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
+                    </div>
+                </div>
+                <div className={style.detail_footer}>
+                    <Button type="link" style={{ padding: 0, fontSize: 12, color: token.colorPrimary }} size='small' onClick={() => { setNewVisible(true); setCreateType('STRATEGY') }}>编辑</Button>
+                </div>
+            </div>
+            <div className={`${style.settingsBody_content_col}`}>
+                <div className={style.title}>
+                    <span></span>
+                    <Space>
+                        {settings?.highMassSendingContent?.massSendingContentDTO?.some(item => item?.sendContentDto?.some(si => si?.contentDTO?.some(i => i?.some(a => ["miniprogram", 'link'].includes(a?.mediaType))))) && <>
+                            {settings?.corpUsers?.every(item => item?.externalUserList?.length) && <>
+                                <Button
+                                    type="link"
+                                    style={{ padding: 0, fontSize: 12 }}
+                                    loading={downloadLoading}
+                                    onClick={() => { exportExcel() }}
+                                >下载</Button>
+                                <Upload
+                                    accept={'.xlsx'}
+                                    action="#"
+                                    showUploadList={false}
+                                    customRequest={() => { }}
+                                    beforeUpload={async (file: RcFile): Promise<any> => {
+                                        readExcelContent(file)
+                                        onPreviewReset()
+                                    }}
+                                >
+                                    <Button
+                                        type="link"
+                                        style={{ padding: 0, fontSize: 12 }}
+                                    >{settings?.corpUsers?.every(item => item?.highGroupMsgContent?.length) ? 'Excel内容已上传,重新上传' : '上传Excel'}</Button>
+                                </Upload>
+                            </>}
+                        </>}
+
+                        {((settings?.highMassSendingContent && Object.keys(settings?.highMassSendingContent).length > 0) || (settings?.highMassSendingStrategy && Object.keys(settings?.highMassSendingStrategy).length > 0)) && <Button
+                            type="link"
+                            danger
+                            style={{ padding: 0, fontSize: 12 }}
+                            onClick={() => {
+                                const corpUsers = settings?.corpUsers?.map(item => {
+                                    delete item?.highGroupMsgContent
+                                    delete item?.externalUserList
+                                    return item
+                                })
+                                setSettings({
+                                    ...settings,
+                                    corpUsers,
+                                    highMassSendingStrategy: undefined,
+                                    highMassSendingContent: undefined
+                                })
+                                onPreviewReset()
+                            }}
+                        >清空</Button>}
+                    </Space>
+                </div>
+                <div className={style.detail}>
+                    <div className={style.detail_title}>高级群发内容配置</div>
+                    <div className={style.detail_body} style={{ padding: 0 }}>
+                        {settings?.highMassSendingContent && Object.keys(settings?.highMassSendingContent) ? <>
+                            <ShowContent
+                                strategySettings={settings?.highMassSendingStrategy?.strategySettings}
+                                massSendingContent={settings?.highMassSendingContent}
+                                type="highQf"
+                            />
+                        </> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
+                    </div>
+                </div>
+                <div className={style.detail_footer}>
+                    <Button
+                        type="link"
+                        style={{ padding: 0, fontSize: 12, color: token.colorPrimary }}
+                        size='small'
+                        onClick={() => {
+                            if (!(settings?.highMassSendingStrategy && Object.keys(settings?.highMassSendingStrategy))) {
+                                message.error('请先配置策略')
+                                return
+                            }
+                            setNewVisible(true); setCreateType('CONTENT')
+                        }}
+                    >编辑</Button>
+                </div>
+            </div>
+        </div>
+
+        {/* 模板配置 */}
+        {newVisible && <SettingsHighMassSending
+            visible={newVisible}
+            createType={createType}
+            value={{
+                highMassSendingContent: settings?.highMassSendingContent,
+                highMassSendingStrategy: settings?.highMassSendingStrategy
+            }}
+            onClose={() => {
+                setNewVisible(false)
+            }}
+            onChange={(values, type) => {
+                console.log(values, type)
+                const corpUsers = settings?.corpUsers?.map(item => {
+                    delete item?.highGroupMsgContent
+                    return item
+                })
+                if (!values?.highMassSendingContent && settings?.corpUsers?.some(item => item?.highGroupMsgContent?.length)) {
+
+                    setSettings({
+                        ...settings,
+                        ...values,
+                        corpUsers
+                    })
+                } else {
+                    setSettings({
+                        ...settings,
+                        ...values,
+                        corpUsers
+                    })
+                }
+
+                if (type === 'OK') {
+                    setNewVisible(false)
+                }
+                onPreviewReset()
+            }}
+        />}
+    </>
+};
+
+export default React.memo(HighMassSending);

+ 0 - 0
src/pages/weComTask/page/businessPlan/create/massSending/materialNoTextMould.tsx → src/pages/weComTask/page/businessPlan/create/components/highMassSending/materialNoTextMould.tsx


+ 79 - 0
src/pages/weComTask/page/businessPlan/create/components/highMassSending/settingsHighMassSending.tsx

@@ -0,0 +1,79 @@
+import { App, Button, Modal, Space } from 'antd';
+import React, { useRef, useState } from 'react';
+import '../../global.less';
+import Strategy from './strategy';
+import Content from './content';
+
+/**
+ * 高级群发配置
+ * @param param0 
+ * @returns 
+ */
+const SettingsHighMassSending: React.FC<TASK_CREATE.HighMassSendingProps<any>> = ({ value: { highMassSendingContent, highMassSendingStrategy }, createType, onChange, visible, onClose }) => {
+
+    /****************************************/
+    const { message } = App.useApp()
+    const ref = useRef(null)
+    const ref1 = useRef(null)
+    const [type, setType] = useState<'STRATEGY' | 'CONTENT'>(createType)
+    /****************************************/
+
+    const handleOk = (p: string) => {
+        if (type === 'STRATEGY') {
+            ref?.current?.handleOk(p)
+        } else {
+            ref1?.current?.handleOk(p)
+        }
+    };
+
+    return <Modal
+        title={<strong>{type === 'STRATEGY' ? '高级群发模板配置' : '高级群发内容配置'}</strong>}
+        open={visible}
+        onCancel={onClose}
+        width={850}
+        className={`settingsModal`}
+        footer={<Space>
+            <Button onClick={onClose}>取消</Button>
+            {type === 'STRATEGY' && <>
+                <Button type="primary" onClick={() => handleOk('CREATE_CONTENT')}>编辑高级群发内容</Button>
+            </>}
+            <Button type="primary" onClick={() => handleOk('OK')}>确定</Button>
+        </Space>}
+    >
+        {/* 策略 */}
+        {type === 'STRATEGY' ? <Strategy
+            ref={ref}
+            value={highMassSendingStrategy}
+            onChange={(value, type) => {
+                console.log(value, type)
+                if (type === 'CREATE_CONTENT') {
+                    setType('CONTENT')
+                }
+                if (highMassSendingContent && (value?.strategySettings?.length !== highMassSendingContent?.massSendingContentDTO?.length || value?.strategySettings?.some((item, index) => highMassSendingContent?.massSendingContentDTO?.[index]?.sendContentDto?.length !== item?.sendData?.length))) {
+                    message.error('策略配置和内容配置不一致,内容已重置,请重新配置!')
+                    onChange({
+                        highMassSendingContent: undefined,
+                        highMassSendingStrategy: value
+                    }, type)
+                    return
+                }
+
+                onChange({
+                    highMassSendingStrategy: value
+                }, type)
+            }}
+        /> : <Content
+            ref={ref1}
+            massSendingStrategy={highMassSendingStrategy}
+            value={highMassSendingContent}
+            onChange={(value, type) => {
+                onChange({
+                    highMassSendingContent: value
+                }, type)
+            }}
+        />}
+
+    </Modal>;
+};
+
+export default SettingsHighMassSending;

+ 88 - 0
src/pages/weComTask/page/businessPlan/create/components/highMassSending/showContentTable.tsx

@@ -0,0 +1,88 @@
+import useNewToken from '@/Hook/useNewToken';
+import LookMsg from '@/pages/weComTask/components/previewMsg/lookMsg';
+import { emoList } from '@/pages/weComTask/components/textEditor/Expression';
+import { Popover, Typography } from 'antd';
+import React from 'react';
+
+interface Props {
+    data: any
+    name?: React.ReactNode
+}
+const ShowContentTable: React.FC<Props> = ({ name, data }) => {
+
+    /*************************************/
+    const { token } = useNewToken()
+    /*************************************/
+
+    const getMsgData = (item: any) => {
+        let msgData: any = []
+        if (item?.text?.content) {
+            let newValue = item?.text?.content
+            const newEmo = emoList.reduce((prev, cur) => {
+                return [...prev, ...cur]
+            }, [])
+            const emos = newValue.match(/\[[\u4e00-\u9fa5]+\]/g)
+            if (emos && emos?.length) {
+                emos.forEach((emo: any) => {
+                    const emoData = newEmo.find(o => `[${o.name}]` === emo)
+                    if (emoData) {
+                        newValue = newValue.replace(emo, `<img src="${emoData.url}" alt="${emoData.name}" style="width:20px;height:20px"/>`)
+                    }
+                })
+            }
+            if (newValue.includes('\n')) {
+                newValue = newValue.replace('\n', `<br/>`)
+            }
+            const names = newValue.match(/%[\u4e00-\u9fa5]+%/g)
+            if (names && names?.length) {
+                newValue = newValue.replace('%昵称%', ` <span style="display: inline-block;margin:0 1px;position: relative; border: 1px solid ${token.colorBorder}; padding: ${token.paddingXS}px; border-radius: ${token.borderRadius}px;color: ${token.colorTextBase}; background: ${token.colorSuccess};color:${token.colorTextLightSolid}" contenteditable="false">昵称<strong data-name="昵称" style="padding: 0 6px;cursor:pointer" onclick="let html =document.querySelector('[data-name=昵称').parentElement.parentElement.innerHTML;let span = ' '+document.querySelector('[data-name=昵称]').parentElement.outerHTML+' ';console.log('=',html,'=');console.log('=',span,'=');document.execCommand('selectAll');document.execCommand('delete'); document.execCommand('insertHTML', true, html.replace(span,''));">X</strong></span> `)
+            }
+            msgData.push({
+                textContent: newValue,
+                mediaType: 'text'
+            })
+        }
+        if (item?.attachmentList && item?.attachmentList?.length > 0) {
+            const newAttachmentList = item?.attachmentList.map((item: any) => {
+                switch (item.msgType) {
+                    case 'TASK_CONTENT_IMAGE':
+                        return { mediaType: 'image', imageUrl: item?.image?.picUrl }
+                    case 'TASK_CONTENT_LINK':
+                        return { mediaType: 'link', linkDesc: item?.link?.desc, linkPicurl: item?.link?.picUrl, linkTitle: item?.link?.title, linkUrl: item?.link?.url }
+                    case 'TASK_STATUS_FILE':
+                        return { mediaType: 'file', fileUrl: item?.file?.fileUrl }
+                    case 'TASK_STATUS_VIDEO':
+                        return { mediaType: 'video', videoUrl: item?.video?.videoUrl }
+                    case 'TASK_STATUS_MINIPROGRAM':
+                        return {
+                            mediaType: 'miniprogram',
+                            miniprogramAppid: item.miniprogram?.appId,
+                            miniprogramPage: item?.miniprogram?.page,
+                            miniprogramPicurl: item?.miniprogram?.picUrl,
+                            miniprogramTitle: item?.miniprogram?.title
+                        }
+                }
+            })
+            msgData = msgData.concat(newAttachmentList)
+        }
+        return msgData
+    }
+
+    return <Popover
+        placement="left"
+        content={<LookMsg data={getMsgData(data)} />}
+        styles={{ body: { width: 300, overflow: 'hidden', overflowY: 'auto', maxHeight: 480 } }}
+    >
+        <div
+            style={{
+                backgroundColor: 'rgba(0,0,0,0.1)',
+                padding: '4px 8px',
+                borderRadius: 8,
+                overflow: 'hidden',
+                cursor: 'help'
+            }}
+        >{name || '群发内容'}</div>
+    </Popover>
+};
+
+export default ShowContentTable;

+ 278 - 0
src/pages/weComTask/page/businessPlan/create/components/highMassSending/strategy.tsx

@@ -0,0 +1,278 @@
+import NewSteps, { NewStepsItem } from '@/pages/weComTask/components/newSteps';
+import { App, Button, Card, Form, Input, Radio } from 'antd';
+import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
+import '../../global.less';
+import { PlusOutlined, DeleteOutlined } from '@ant-design/icons'
+import SendTimeSet from '@/pages/weComTask/components/sendTimeSet';
+import style from './index.less';
+import dayjs from 'dayjs';
+import FilterUser from '@/pages/weComTask/components/filterUser';
+
+/**
+ * 群发策略
+ * @param param0 
+ * @returns 
+ */
+const Strategy = forwardRef(({ value, onChange }: TASK_CREATE.StrategyProps, ref: React.ForwardedRef<{ handleOk: (type: string) => void }>) => {
+
+    /****************************************/
+    const { message } = App.useApp()
+    const ref1 = useRef<HTMLDivElement>(null)
+    const [form] = Form.useForm();
+    const strategySettings = Form.useWatch('strategySettings', form);
+
+    const [stepsList, setStepsList] = useState<NewStepsItem[]>([
+        { title: '群发配置', description: '群发类型、群发标题、适用产品', id: 'basicInfo' },
+        {
+            title: '群发策略', children: [{ title: `策略1`, id: 'strategy_0', children: [{ title: `发送时间`, id: `strategy_0_strategyName` }, { title: `发送对象1`, id: `strategy_0_sendData` }] }, { title: `完成` }]
+        },
+        { title: '完成' }
+    ])
+    /****************************************/
+
+    useImperativeHandle(ref, () => ({
+        handleOk(type) {
+            handleOk(type)
+        }
+    }));
+
+    // 回填
+    useEffect(() => {
+        if (value && Object.keys(value).length) {
+            const data = {
+                ...value, strategySettings: value?.strategySettings?.map(item => {
+                    const { sendTime, startTime, endTime, sendDay, timeRepeatType } = item
+                    if (timeRepeatType === 'TIME_TYPE_SINGLE_PLACE') {
+                        return {
+                            ...item,
+                            timeRepeatType,
+                            sendDay: sendDay ? dayjs(sendDay + ' ' + sendTime) : undefined
+                        }
+                    }
+                    return {
+                        ...item,
+                        timeRepeatType,
+                        sendTime: sendTime ? dayjs('2025-04-25 ' + sendTime) : undefined,
+                        startTime: startTime ? dayjs(startTime) : undefined,
+                        endTime: endTime ? dayjs(endTime) : undefined,
+                        sendDay: sendDay ? dayjs(sendDay) : undefined
+                    }
+                })
+            }
+            filedUpdateChange(data)
+            form.setFieldsValue(data)
+        }
+    }, [value])
+
+    const handleOk = (type: string) => {
+        form.validateFields().then((values) => {
+            const data = {
+                ...values,
+                strategySettings: values?.strategySettings?.map(item => {
+                    const { startTime, endTime, sendDay, sendTime, timeRepeatType, repeatArray, ...rest } = item
+                    const data = { ...rest, timeRepeatType }
+                    if (timeRepeatType === 'TIME_TYPE_SINGLE_PLACE') {
+                        // 定时发送
+                        data.sendDay = dayjs(sendDay).format('YYYY-MM-DD')
+                        data.sendTime = dayjs(sendDay).format('HH:mm:ss')
+                    } else if (timeRepeatType === 'TIME_TYPE_REPEAT_DAY') {
+                        // 每日循环
+                        data.startTime = dayjs(startTime).format('YYYY-MM-DD')
+                        if (endTime) {
+                            data.endTime = dayjs(endTime).format('YYYY-MM-DD')
+                        }
+                        data.sendTime = dayjs(sendTime).format('HH:mm:ss')
+                    } else if (timeRepeatType === 'TIME_TYPE_REPEAT_WEEK' || timeRepeatType === 'TIME_TYPE_REPEAT_MONTH') {
+                        // 每周循环、每月循环
+                        data.startTime = dayjs(startTime).format('YYYY-MM-DD')
+                        data.sendTime = dayjs(sendTime).format('HH:mm:ss')
+                        if (endTime) {
+                            data.endTime = dayjs(endTime).format('YYYY-MM-DD')
+                        }
+                        data.repeatArray = repeatArray
+                    }
+                    return data
+                })
+            }
+            onChange?.(data, type)
+        }).catch((err) => {
+            console.log('err', err)
+            form.submit()
+        });
+    };
+
+    const filedUpdateChange = ({ groupSendName, strategySettings }: any) => {
+        const isChecked = (content: NewStepsItem[]) => {
+            return content.every(item => {
+                if (item.children) {
+                    return isChecked(item.children)
+                }
+                return item.checked
+            })
+        }
+
+        const content = strategySettings?.map((item, index) => {
+            const { timeRepeatType, sendDay, startTime, sendTime, repeatArray } = item
+            const sendTimeChecked =
+                timeRepeatType === "TIME_TYPE_SINGLE_TIMELY" ||
+                (timeRepeatType === "TIME_TYPE_SINGLE_PLACE" && sendDay) ||
+                (timeRepeatType === "TIME_TYPE_REPEAT_DAY" && startTime && sendTime) ||
+                ((timeRepeatType === "TIME_TYPE_REPEAT_WEEK" || timeRepeatType === "TIME_TYPE_REPEAT_MONTH") && startTime && sendTime && repeatArray)
+
+            const children = [
+                {
+                    title: `发送时间`, checked: sendTimeChecked, id: `strategy_${index}_strategyName`
+                },
+                ...item.sendData.map((i, n) => {
+                    return { title: `发送对象${n + 1}`, description: '对象筛选,人群包', id: `strategy_${index}_${n}_sendData`, checked: i?.externalUserType ? i?.externalUserType === 'all' ? true : i?.externalUserFilter && Object.values(i.externalUserFilter).some(item => item) : false }
+                })
+            ]
+
+            return {
+                title: `策略${index + 1}`,
+                id: `strategy_${index}`,
+                children: children,
+                checked: isChecked(children)
+            }
+        })
+
+        const isChecked1 = !!groupSendName
+        const isChecked2 = isChecked(content)
+        const stepsData = [
+            { title: '群发配置', description: '群发标题', checked: isChecked1, id: 'basicInfo' },
+            {
+                title: '群发策略', checked: isChecked2, children: [...content, { title: `完成`, checked: isChecked2 }]
+            },
+            { title: '完成', checked: isChecked1 && isChecked2 }
+        ]
+        setStepsList(stepsData)
+    }
+
+    return <>
+        <div className={`body_steps`}>
+            <NewSteps
+                items={stepsList}
+                onChange={(e) => {
+                    if (e?.id)
+                        ref1.current?.querySelector('#' + e?.id)?.scrollIntoView({ behavior: 'smooth' })
+                }}
+            />
+        </div>
+        <div className={`body_content`} ref={ref1}>
+            <Form
+                form={form}
+                name="gjnewMassSending"
+                labelAlign='left'
+                labelCol={{ span: 5 }}
+                colon={false}
+                scrollToFirstError={{
+                    behavior: 'smooth',
+                    block: 'center'
+                }}
+                onFinishFailed={({ errorFields }) => {
+                    message.error(errorFields?.[0]?.errors?.[0])
+                }}
+                initialValues={{
+                    taskType: 'novel',
+                    strategySettings: [{ sendData: [{ externalUserType: 'all' }] }],
+                }}
+                onFieldsChange={() => {
+                    filedUpdateChange(form.getFieldsValue())
+                }}
+                preserve={true}
+            >
+                <Card title={<strong>基础信息配置</strong>} style={{ background: '#fff', marginBottom: 10 }} hoverable id='basicInfo'>
+                    <Form.Item label={<strong>高级群发标题</strong>} name="groupSendName" rules={[{ required: true, message: '请输入标题!' }]}>
+                        <Input placeholder="请输入标题" style={{ width: 358 }} allowClear />
+                    </Form.Item>
+                </Card>
+
+                <Form.List name="strategySettings">
+                    {(fields, { add, remove }) => (
+                        <>
+                            {fields.map(({ key, name, ...restField }, index) => {
+                                const timeRepeatType = strategySettings?.[index]?.timeRepeatType
+                                const sendData = strategySettings?.[index]?.sendData
+
+                                return <Card
+                                    key={key}
+                                    title={<strong>策略{index + 1}配置</strong>}
+                                    style={{ background: '#fff', marginBottom: 10 }}
+                                    hoverable
+                                    id={`strategy_${index}`}
+                                    extra={strategySettings?.length > 1 ? <Button icon={<DeleteOutlined />} type='link' style={{ color: 'red' }} onClick={() => remove(name)}></Button> : null}
+                                >
+                                    <div className={style.strategy_item} id={`strategy_${index}_strategyName`}>
+                                        <Form.Item
+                                            {...restField}
+                                            label={<strong>策略名称</strong>}
+                                            name={[name, 'strategyName']}
+                                            rules={[{ required: false, message: '请输入策略名称!' }]}
+                                        >
+                                            <Input placeholder="请输入标题" style={{ width: 358 }} allowClear />
+                                        </Form.Item>
+                                        <SendTimeSet active='all' form={form} restField={restField} name={name} timeRepeatType={timeRepeatType} />
+
+                                        <Form.List name={[name, 'sendData']}>
+                                            {(fields, { add, remove }) => (
+                                                <>
+                                                    {fields.map(({ key, name, ...restField }, i) => {
+                                                        return <Card
+                                                            key={i}
+                                                            title={<strong>策略{index + 1} 发送对象{i + 1}</strong>}
+                                                            style={{ background: '#fff', marginBottom: 10 }}
+                                                            extra={sendData?.length > 1 ? <Button icon={<DeleteOutlined />} type='link' style={{ color: 'red' }} onClick={() => remove(name)}></Button> : null}
+                                                            id={`strategy_${index}_${i}_sendData`}
+                                                        >
+                                                            <Form.Item
+                                                                label={<strong>发送对象配置</strong>}
+                                                                required
+                                                            >
+                                                                <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
+                                                                    <Form.Item
+                                                                        {...restField}
+                                                                        name={[name, 'externalUserType']}
+                                                                        rules={[{ required: true, message: '请选择转移对象!' }]}
+                                                                        noStyle
+                                                                    >
+                                                                        <Radio.Group options={[{ label: '全部', value: 'all' }, { label: '指定', value: 'specify' }]} />
+                                                                    </Form.Item>
+                                                                    {sendData?.[i]?.externalUserType === 'specify' && <div style={{ marginTop: 8, width: '100%' }}>
+                                                                        <Form.Item
+                                                                            {...restField}
+                                                                            name={[name, 'externalUserFilter']}
+                                                                            rules={[{ required: true, message: '请选择人群包!' }]}
+                                                                            noStyle
+                                                                        >
+                                                                            <FilterUser configType={'GROUP_GROUP'} />
+                                                                        </Form.Item>
+                                                                    </div>}
+                                                                </div>
+                                                            </Form.Item>
+                                                        </Card>
+                                                    })}
+                                                    <Form.Item>
+                                                        <Button type="dashed" onClick={() => add({ externalUserType: 'all' })} block icon={<PlusOutlined />}>
+                                                            新增发送对象
+                                                        </Button>
+                                                    </Form.Item>
+                                                </>
+                                            )}
+                                        </Form.List>
+                                    </div>
+                                </Card>
+                            })}
+                            <Form.Item>
+                                <Button type="primary" onClick={() => add({ sendData: [{ externalUserType: 'all' }] })} block icon={<PlusOutlined />}>
+                                    新增策略组
+                                </Button>
+                            </Form.Item>
+                        </>
+                    )}
+                </Form.List>
+            </Form>
+        </div>
+    </>
+});
+
+export default React.memo(Strategy);

+ 2 - 2
src/pages/weComTask/page/businessPlan/create/massSending/content.tsx → src/pages/weComTask/page/businessPlan/create/components/massSending/content.tsx

@@ -1,8 +1,8 @@
 import NewSteps from '@/pages/weComTask/components/newSteps';
 import { App, Button, Card, Form, Select } from 'antd';
 import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
-import '../global.less';
-import { welcomeContentData } from '../const';
+import '../../global.less';
+import { welcomeContentData } from '../../const';
 import { PlusOutlined, MinusOutlined } from '@ant-design/icons'
 import MaterialNoTextMould from './materialNoTextMould';
 

+ 30 - 0
src/pages/weComTask/page/businessPlan/create/components/massSending/index.less

@@ -0,0 +1,30 @@
+.strategy_item {
+    position: relative;
+
+    .strategy_clear {
+        position: absolute;
+        bottom: 15px;
+        right: 20px;
+        color: red;
+        cursor: pointer;
+    }
+}
+
+
+.showContent {
+    width: 100%;
+    height: 100%;
+    overflow: hidden;
+    padding-left: 10px;
+    padding-bottom: 10px;
+
+    .showContent_content {
+        height: 305px;
+        overflow: auto;
+        overflow-y: auto;
+        padding-right: 4px;
+    }
+}
+.popupClassName {
+    background-color: #FFF !important;
+}

+ 101 - 99
src/pages/weComTask/page/businessPlan/create/massSending/index.tsx → src/pages/weComTask/page/businessPlan/create/components/massSending/index.tsx

@@ -1,18 +1,18 @@
 import React, { useContext, useState } from 'react';
-import style from '../index.less';
+import style from '../../index.less';
 import { App, Button, Empty, Space, Upload } from 'antd';
-import { DispatchTaskCreate } from '..';
+import { DispatchTaskCreate } from '../..';
 import SettingsMassSending from './settingsMassSending';
 import dayjs from 'dayjs';
-import '../global.less'
+import '../../global.less'
 import useNewToken from '@/Hook/useNewToken';
 import ShowContent from './showContent';
-import { getGroupData, headerJsMustStyle, headerJsStyle, TIME_TYPE, welcomeContentData } from '../const';
+import { getGroupData, headerJsMustStyle, headerJsStyle, TIME_TYPE, welcomeContentData } from '../../const';
 import ExcelJS from 'exceljs';
 import FilterUserText from '@/pages/weComTask/components/filterUser/filterUserText';
 import { RcFile } from 'antd/es/upload';
 import { groupBy, readFileAsBuffer } from '@/utils/utils';
-import streamSaver from 'streamsaver'
+import { saveAs } from 'file-saver';
 import PreviewMassSendingStrategy from './previewMassSendingStrategy';
 
 /**
@@ -273,27 +273,25 @@ const MassSending: React.FC = () => {
                 }
             }
         }
-        console.log('2222222222222222222222222')
         // 生成文件内容
-        // workbook.xlsx.writeBuffer().then(buffer => {
-        //     // 通过 Blob 下载
-        //     const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
-        //     console.log('33333333333333333333333', blob)
-        //     saveAs(blob, `群发内容配置_${dayjs().format('YYYYMMDDHHmmss')}.xlsx`);
-        //     setTimeout(() => setDownloadLoading(false), 1000)
-        // });
-        // 替换原有 saveAs 代码
-        const fileStream = streamSaver.createWriteStream(
-            `群发内容配置_${dayjs().format('YYYYMMDDHHmmss')}.xlsx`
-        );
-        const writer = fileStream.getWriter();
-
-        // 流式写入
-        workbook.xlsx.write(writer).then(() => {
-            writer.close();
-            console.log('33333333333333333333333')
-            setDownloadLoading(false); // 立即关闭loading
+        workbook.xlsx.writeBuffer().then(buffer => {
+            // 通过 Blob 下载
+            const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
+            saveAs(blob, `群发内容配置_${dayjs().format('YYYYMMDDHHmmss')}.xlsx`);
+            setTimeout(() => setDownloadLoading(false), 1000)
         });
+        // 替换原有 saveAs 代码
+        // const fileStream = streamSaver.createWriteStream(
+        //     `群发内容配置_${dayjs().format('YYYYMMDDHHmmss')}.xlsx`
+        // );
+        // const writer = fileStream.getWriter();
+
+        // // 流式写入
+        // workbook.xlsx.write(writer).then(() => {
+        //     writer.close();
+        //     console.log('33333333333333333333333')
+        //     setDownloadLoading(false); // 立即关闭loading
+        // });
     }
 
     // 读取Excel
@@ -368,90 +366,96 @@ const MassSending: React.FC = () => {
     }
 
     return <>
-        <div className={`${style.settingsBody_content_row} ${style.row1}`}>
-            <div className={style.title}>
-                <span>群发策略</span>
-            </div>
-            <div className={style.detail}>
-                <div className={style.detail_body}>
-                    {settings?.massSendingStrategy && Object.keys(settings?.massSendingStrategy).length > 0 ? <>
-                        <PreviewMassSendingStrategy massSendingStrategy={settings?.massSendingStrategy} />
-                    </> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
+        <div className={`${style.settingsBody_content_row}`}>
+            <div className={`${style.settingsBody_content_col}`}>
+                <div className={style.title}>
+                    <span>群发</span>
+                </div>
+                <div className={style.detail}>
+                    <div className={style.detail_title}>群发策略配置</div>
+                    <div className={style.detail_body}>
+                        {settings?.massSendingStrategy && Object.keys(settings?.massSendingStrategy).length > 0 ? <>
+                            <PreviewMassSendingStrategy massSendingStrategy={settings?.massSendingStrategy} />
+                        </> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
+                    </div>
+                </div>
+                <div className={style.detail_footer}>
+                    <Button type="link" style={{ padding: 0, fontSize: 12, color: token.colorPrimary }} size='small' onClick={() => { setNewVisible(true); setCreateType('STRATEGY') }}>编辑</Button>
                 </div>
             </div>
-            <div className={style.detail_footer}>
-                <Button type="link" style={{ padding: 0, fontSize: 12, color: token.colorPrimary }} size='small' onClick={() => { setNewVisible(true); setCreateType('STRATEGY') }}>编辑</Button>
-            </div>
-        </div>
-        <div className={`${style.settingsBody_content_row} ${style.row1}`}>
-            <div className={style.title}>
-                <span>群发内容</span>
-                {settings?.massSendingContent?.massSendingContentDTO?.some(item => item?.sendContentDto?.some(si => si?.contentDTO?.some(i => i?.attachmentList?.some(a => ['TASK_CONTENT_LINK', 'TASK_STATUS_MINIPROGRAM'].includes(a?.msgType))))) && <Space>
-                    <Button
-                        type="link"
-                        style={{ padding: 0, fontSize: 12 }}
-                        loading={downloadLoading}
-                        onClick={() => { exportExcel() }}
-                    >下载</Button>
-                    <Upload
-                        accept={'.xlsx'}
-                        action="#"
-                        showUploadList={false}
-                        customRequest={() => { }}
-                        beforeUpload={async (file: RcFile): Promise<any> => {
-                            readExcelContent(file)
-                            onPreviewReset()
-                        }}
-                    >
+            <div className={`${style.settingsBody_content_col}`}>
+                <div className={style.title}>
+                    <span></span>
+                    {(settings?.massSendingStrategy && Object.keys(settings?.massSendingStrategy).length > 0) && <Space>
+                        {settings?.massSendingContent?.massSendingContentDTO?.some(item => item?.sendContentDto?.some(si => si?.contentDTO?.some(i => i?.attachmentList?.some(a => ['TASK_CONTENT_LINK', 'TASK_STATUS_MINIPROGRAM'].includes(a?.msgType))))) && <>
+                            <Button
+                                type="link"
+                                style={{ padding: 0, fontSize: 12 }}
+                                loading={downloadLoading}
+                                onClick={() => { exportExcel() }}
+                            >下载</Button>
+                            <Upload
+                                accept={'.xlsx'}
+                                action="#"
+                                showUploadList={false}
+                                customRequest={() => { }}
+                                beforeUpload={async (file: RcFile): Promise<any> => {
+                                    readExcelContent(file)
+                                    onPreviewReset()
+                                }}
+                            >
+                                <Button
+                                    type="link"
+                                    style={{ padding: 0, fontSize: 12 }}
+                                >{settings?.corpUsers?.every(item => item?.groupMsgContent?.length) ? 'Excel内容已上传,重新上传' : '上传Excel'}</Button>
+                            </Upload>
+                        </>}
                         <Button
                             type="link"
+                            danger
                             style={{ padding: 0, fontSize: 12 }}
-                        >{settings?.corpUsers?.every(item => item?.groupMsgContent?.length) ? 'Excel内容已上传,重新上传' : '上传Excel'}</Button>
-                    </Upload>
+                            onClick={() => {
+                                const corpUsers = settings?.corpUsers?.map(item => {
+                                    delete item?.groupMsgContent
+                                    return item
+                                })
+                                setSettings({
+                                    ...settings,
+                                    corpUsers,
+                                    massSendingStrategy: undefined,
+                                    massSendingContent: undefined
+                                })
+                                onPreviewReset()
+                            }}
+                        >清空</Button>
+                    </Space>}
+                </div>
+                <div className={style.detail}>
+                    <div className={style.detail_title}>群发内容配置</div>
+                    <div className={style.detail_body} style={{ padding: 0 }}>
+                        {settings?.massSendingContent && Object.keys(settings?.massSendingContent) ? <>
+                            <ShowContent
+                                strategySettings={settings?.massSendingStrategy?.strategySettings}
+                                massSendingContent={settings?.massSendingContent}
+                            />
+                        </> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
+                    </div>
+                </div>
+                <div className={style.detail_footer}>
                     <Button
                         type="link"
-                        danger
-                        style={{ padding: 0, fontSize: 12 }}
+                        style={{ padding: 0, fontSize: 12, color: token.colorPrimary }}
+                        size='small'
                         onClick={() => {
-                            const corpUsers = settings?.corpUsers?.map(item => {
-                                delete item?.groupMsgContent
-                                return item
-                            })
-                            setSettings({
-                                ...settings,
-                                corpUsers,
-                                massSendingStrategy: undefined,
-                                massSendingContent: undefined
-                            })
-                            onPreviewReset()
+                            if (!(settings?.massSendingStrategy && Object.keys(settings?.massSendingStrategy))) {
+                                message.error('请先配置策略')
+                                return
+                            }
+                            setNewVisible(true); setCreateType('CONTENT')
                         }}
-                    >清空</Button>
-                </Space>}
-            </div>
-            <div className={style.detail}>
-                <div className={style.detail_body} style={{ padding: 0 }}>
-                    {settings?.massSendingContent && Object.keys(settings?.massSendingContent) ? <>
-                        <ShowContent
-                            strategySettings={settings?.massSendingStrategy?.strategySettings}
-                            massSendingContent={settings?.massSendingContent}
-                        />
-                    </> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
+                    >编辑</Button>
                 </div>
             </div>
-            <div className={style.detail_footer}>
-                <Button
-                    type="link"
-                    style={{ padding: 0, fontSize: 12, color: token.colorPrimary }}
-                    size='small'
-                    onClick={() => {
-                        if (!(settings?.massSendingStrategy && Object.keys(settings?.massSendingStrategy))) {
-                            message.error('请先配置策略')
-                            return
-                        }
-                        setNewVisible(true); setCreateType('CONTENT')
-                    }}
-                >编辑</Button>
-            </div>
         </div>
 
         {/* 模板配置 */}
@@ -466,13 +470,11 @@ const MassSending: React.FC = () => {
                 setNewVisible(false)
             }}
             onChange={(values, type) => {
-                console.log(values, type)
                 const corpUsers = settings?.corpUsers?.map(item => {
                     delete item?.groupMsgContent
                     return item
                 })
                 if (!values?.massSendingContent && settings?.corpUsers?.some(item => item?.groupMsgContent?.length)) {
-
                     setSettings({
                         ...settings,
                         ...values,

+ 143 - 0
src/pages/weComTask/page/businessPlan/create/components/massSending/materialNoTextMould.tsx

@@ -0,0 +1,143 @@
+import { Space } from "antd"
+import React, { useEffect, useState } from "react"
+import { TextEditor } from "@/pages/weComTask/components/textEditor"
+import MaterialMould from "@/pages/weComTask/components/materialMould"
+
+interface Props {
+    value?: TASK_CREATE.ContentDTOProps
+    onChange?: (value?: TASK_CREATE.ContentDTOProps) => void
+}
+/**
+ * 群发文本设置
+ * @returns 
+ */
+const MaterialNoTextMould: React.FC<Props> = ({ onChange, value }) => {
+
+    /***************************************/
+    const [textContent, setTextContent] = useState<string>()
+    const [attachmentList, setAttachmentList] = useState<any>()
+    /***************************************/
+
+    /** 回填 */
+    useEffect(() => {
+        if (value) {
+            if (value?.text?.content && !textContent) {
+                const newValue = value?.text?.content
+                setTextContent(newValue)
+            }
+            if (value?.attachmentList && value?.attachmentList?.length > 0 && !attachmentList) {
+                const newAttachmentList = value?.attachmentList.map(item => {
+                    switch (item.msgType) {
+                        case 'TASK_CONTENT_IMAGE':
+                            return { mediaType: 'image', imageUrl: item?.image?.picUrl }
+                        case 'TASK_CONTENT_LINK':
+                            return { mediaType: 'link', linkDesc: item?.link?.desc, linkPicurl: item?.link?.picUrl, linkTitle: item?.link?.title, linkUrl: item?.link?.url }
+                        case 'TASK_STATUS_FILE':
+                            return { mediaType: 'file', fileUrl: item?.file?.fileUrl }
+                        case 'TASK_STATUS_VIDEO':
+                            return { mediaType: 'video', videoUrl: item?.video?.videoUrl }
+                        case 'TASK_STATUS_MINIPROGRAM':
+                            return {
+                                mediaType: 'miniprogram',
+                                miniprogramAppid: item.miniprogram?.appId,
+                                miniprogramPage: item?.miniprogram?.page,
+                                miniprogramPicurl: item?.miniprogram?.picUrl,
+                                miniprogramTitle: item?.miniprogram?.title
+                            }
+                    }
+                })
+                setAttachmentList(newAttachmentList)
+            }
+        }
+    }, [value, textContent, attachmentList])
+
+    /** 返回 */
+    const handleChange = (textContent: any, attachmentList?: any) => {
+        let newAttachmentList: TASK_CREATE.Attachment[] = []
+        let text: any = {}
+        if (textContent) {
+            text = {
+                content: textContent
+            }
+        }
+        if (attachmentList && attachmentList?.length > 0) {
+            newAttachmentList = attachmentList.map((item: TASK_CREATE.MediaContentProps) => {
+                switch (item.mediaType) {
+                    case 'image':
+                        return { image: { picUrl: item.imageUrl }, msgType: 'TASK_CONTENT_IMAGE' }
+                    case 'video':
+                        return { video: { videoUrl: item.videoUrl }, msgType: 'TASK_STATUS_VIDEO' }
+                    case 'file':
+                        return { file: { fileUrl: item.fileUrl }, msgType: 'TASK_STATUS_FILE' }
+                    case 'link':
+                        return { link: { desc: item.linkDesc, picUrl: item.linkPicurl, title: item.linkTitle, url: item.linkUrl }, msgType: 'TASK_CONTENT_LINK' }
+                    case 'miniprogram':
+                        return {
+                            miniprogram: {
+                                appId: item.miniprogramAppid,
+                                page: item.miniprogramPage,
+                                picUrl: item.miniprogramPicurl,
+                                title: item.miniprogramTitle
+                            },
+                            msgType: 'TASK_STATUS_MINIPROGRAM'
+                        }
+                }
+            })
+        }
+        if (text?.content) {
+            text.content = text.content.replace(/\n$/, '')
+        }
+        onChange?.({
+            attachmentList: newAttachmentList as any,
+            text
+        })
+    }
+
+    return <Space style={{ width: '100%' }} direction='vertical'>
+        <TextEditor
+            maxStr={2000}
+            value={textContent || ''}
+            onChange={(value) => {
+                setTextContent(value)
+                handleChange(value, attachmentList)
+            }}
+        />
+        <MaterialMould
+            posiType="QF"
+            noShowType={['voice']}
+            textCount={1}
+            value={attachmentList}
+            onChange={(value) => {
+                if (value?.length > 0) {
+                    let newText: TASK_CREATE.MediaContentProps[] = []
+                    let newAttachmentList: TASK_CREATE.MediaContentProps[] = []
+                    value?.forEach((cur: TASK_CREATE.MediaContentProps) => {
+                        if (cur.mediaType === 'text') {
+                            newText.push(cur)
+                        } else {
+                            newAttachmentList.push(cur)
+                        }
+                    })
+                    if (newText.length > 0) {
+                        if (textContent) {
+                            setTextContent('')
+                            setAttachmentList(newAttachmentList)
+                            handleChange(newText[0].textContent, newAttachmentList)
+                        } else {
+                            setTextContent('')
+                            setAttachmentList(newAttachmentList)
+                            handleChange(newText[0].textContent, newAttachmentList)
+                        }
+                    } else {
+                        setAttachmentList(value)
+                        handleChange(textContent, value)
+                    }
+                } else {
+                    setAttachmentList([])
+                    handleChange(textContent, [])
+                }
+            }} />
+    </Space>
+}
+
+export default React.memo(MaterialNoTextMould)

+ 5 - 5
src/pages/weComTask/page/businessPlan/create/massSending/previewMassSendingStrategy.tsx → src/pages/weComTask/page/businessPlan/create/components/massSending/previewMassSendingStrategy.tsx

@@ -1,17 +1,17 @@
 import { Card, Form, Input, Radio } from 'antd';
 import React, { useEffect } from 'react';
-import style from '../index.less';
+import style from '../../index.less';
 import SendTimeSet from '@/pages/weComTask/components/sendTimeSet';
 import FilterUser from '@/pages/weComTask/components/filterUser';
 import dayjs from 'dayjs';
-import '../global.less'
+import '../../global.less'
 
 /**
  * 群发预览
  * @param param0 
  * @returns 
  */
-const PreviewMassSendingStrategy: React.FC<{ massSendingStrategy: { [x: string]: any } }> = ({ massSendingStrategy }) => {
+const PreviewMassSendingStrategy: React.FC<{ massSendingStrategy: { [x: string]: any }, configType?: BUSINES_SPLAN_API.ConfigTypeProps }> = ({ massSendingStrategy, configType = 'USER_GROUP' }) => {
 
     /************************************************/
     const [form] = Form.useForm();
@@ -45,7 +45,7 @@ const PreviewMassSendingStrategy: React.FC<{ massSendingStrategy: { [x: string]:
 
     return <Form
         form={form}
-        name="newMassSendingShow"
+        name={'newMassSendingShowzs' + configType}
         labelAlign='left'
         labelCol={{ span: 5 }}
         colon={false}
@@ -111,7 +111,7 @@ const PreviewMassSendingStrategy: React.FC<{ massSendingStrategy: { [x: string]:
                                                                     rules={[{ required: true, message: '请选择人群包!' }]}
                                                                     noStyle
                                                                 >
-                                                                    <FilterUser isSHow configType='USER_GROUP' />
+                                                                    <FilterUser isSHow configType={configType} />
                                                                 </Form.Item>
                                                             </div>}
                                                         </div>

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

@@ -1,6 +1,6 @@
 import { App, Button, Modal, Space } from 'antd';
 import React, { useRef, useState } from 'react';
-import '../global.less';
+import '../../global.less';
 import Strategy from './strategy';
 import Content from './content';
 
@@ -59,7 +59,6 @@ const SettingsMassSending: React.FC<TASK_CREATE.MassSendingProps<any>> = ({ valu
                 }
 
                 onChange({
-                    ...value,
                     massSendingStrategy: value
                 }, type)
             }}
@@ -69,7 +68,6 @@ const SettingsMassSending: React.FC<TASK_CREATE.MassSendingProps<any>> = ({ valu
             value={massSendingContent}
             onChange={(value, type) => {
                 onChange({
-                    ...value,
                     massSendingContent: value
                 }, type)
             }}

+ 4 - 3
src/pages/weComTask/page/businessPlan/create/massSending/showContent.tsx → src/pages/weComTask/page/businessPlan/create/components/massSending/showContent.tsx

@@ -3,7 +3,7 @@ import style from './index.less'
 import { Tabs } from 'antd';
 import { emoList } from '@/pages/weComTask/components/textEditor/Expression';
 import useNewToken, { NewGlobalToken } from '@/Hook/useNewToken';
-import { welcomeContentData } from '../const';
+import { welcomeContentData } from '../../const';
 import LookMsg from '@/pages/weComTask/components/previewMsg/lookMsg';
 
 
@@ -66,9 +66,10 @@ interface ShowContentProps {
     massSendingContent: {
         massSendingContentDTO: any;
     }
+    type?: 'qf' | 'highQf'
 }
 
-const ShowContent: React.FC<ShowContentProps> = ({ strategySettings, massSendingContent }) => {
+const ShowContent: React.FC<ShowContentProps> = ({ strategySettings, massSendingContent, type = 'qf' }) => {
 
     /*************************************/
     const { token } = useNewToken()
@@ -100,7 +101,7 @@ const ShowContent: React.FC<ShowContentProps> = ({ strategySettings, massSending
                             {si?.contentDTO?.map((item, index) => {
                                 return <div key={index} style={{ marginTop: 5, backgroundColor: '#f1f1f1', borderRadius: 6, padding: '5px 10px 16px' }}>
                                     <h3 style={{ textAlign: 'center' }}>内容{index + 1}</h3>
-                                    <LookMsg data={getMsgData(item, token)} />
+                                    <LookMsg data={type === 'highQf' ? item : getMsgData(item, token)} />
                                 </div>
                             })}
                         </div>

+ 0 - 0
src/pages/weComTask/page/businessPlan/create/massSending/showContentTable.tsx → src/pages/weComTask/page/businessPlan/create/components/massSending/showContentTable.tsx


+ 1 - 1
src/pages/weComTask/page/businessPlan/create/massSending/strategy.tsx → src/pages/weComTask/page/businessPlan/create/components/massSending/strategy.tsx

@@ -1,7 +1,7 @@
 import NewSteps, { NewStepsItem } from '@/pages/weComTask/components/newSteps';
 import { App, Button, Card, Form, Input, Radio } from 'antd';
 import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
-import '../global.less';
+import '../../global.less';
 import { PlusOutlined, DeleteOutlined } from '@ant-design/icons'
 import SendTimeSet from '@/pages/weComTask/components/sendTimeSet';
 import style from './index.less';

+ 62 - 58
src/pages/weComTask/page/businessPlan/create/userInherit/index.tsx → src/pages/weComTask/page/businessPlan/create/components/userInherit/index.tsx

@@ -1,17 +1,17 @@
 import React, { useContext, useState } from 'react';
-import style from '../index.less';
-import { App, Button, Empty, Form, Space, Upload } from 'antd';
+import style from '../../index.less';
+import { App, Button, Empty, Space, Upload } from 'antd';
 import useNewToken from '@/Hook/useNewToken';
 import SettingsUserInherit from './settingsUserlnherit';
-import { DispatchTaskCreate } from '..';
 import dayjs from 'dayjs';
-import { getUserInDataData, headerJsMustStyle, headerJsStyle, TIME_TYPE } from '../const';
+import { getUserInDataData, headerJsMustStyle, headerJsStyle, TIME_TYPE } from '../../const';
 import ExcelJS from 'exceljs';
 import { saveAs } from 'file-saver';
 import FilterUserText from '@/pages/weComTask/components/filterUser/filterUserText';
 import { RcFile } from 'antd/es/upload';
 import { groupBy, readFileAsBuffer } from '@/utils/utils';
 import PreviewUserInherit from './previewUserlnherit';
+import { DispatchTaskCreate } from '../..';
 
 /**
  * 客户继承
@@ -257,61 +257,65 @@ const UserInherit: React.FC = () => {
         })
     }
 
-    return <div className={`${style.settingsBody_content_row} ${style.row1}`}>
-        <div className={style.title}>
-            <span>客户继承</span>
-            {(settings?.userInherit && Object.keys(settings?.userInherit)?.length) && <Space>
-                <Button
-                    type="link"
-                    style={{ padding: 0, fontSize: 12 }}
-                    loading={downloadLoading}
-                    onClick={() => { exportExcel() }}
-                >下载</Button>
-                <Upload
-                    accept={'.xlsx'}
-                    action="#"
-                    showUploadList={false}
-                    customRequest={() => { }}
-                    beforeUpload={async (file: RcFile): Promise<any> => {
-                        readExcelContent(file)
-                        onPreviewReset()
-                    }}
-                >
-                    <Button
-                        type="link"
-                        style={{ padding: 0, fontSize: 12 }}
-                    >{settings?.corpUsers?.every(item => item?.externalUserTransferContent?.length) ? 'Excel内容已上传,重新上传' : '上传Excel'}</Button>
-                </Upload>
-                <Button
-                    type="link"
-                    danger
-                    style={{ padding: 0, fontSize: 12 }}
-                    onClick={() => {
-                        const corpUsers = settings?.corpUsers?.map(item => {
-                            delete item?.externalUserTransferContent
-                            return item
-                        })
-                        setSettings({
-                            ...settings,
-                            corpUsers,
-                            userInherit: undefined
-                        })
-                        onPreviewReset()
-                    }}
-                >清空</Button>
-            </Space>}
-        </div>
-        <div className={style.detail}>
-            <div className={style.detail_body}>
-                {settings?.userInherit && Object.keys(settings?.userInherit).length > 0 ? <>
-                    <PreviewUserInherit userInherit={settings?.userInherit} />
-                </> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
+    return <>
+        <div className={`${style.settingsBody_content_row}`}>
+            <div className={`${style.settingsBody_content_col}`}>
+                <div className={style.title}>
+                    <span>客户继承</span>
+                    {(settings?.userInherit && Object.keys(settings?.userInherit)?.length) && <Space>
+                        <Button
+                            type="link"
+                            style={{ padding: 0, fontSize: 12 }}
+                            loading={downloadLoading}
+                            onClick={() => { exportExcel() }}
+                        >下载</Button>
+                        <Upload
+                            accept={'.xlsx'}
+                            action="#"
+                            showUploadList={false}
+                            customRequest={() => { }}
+                            beforeUpload={async (file: RcFile): Promise<any> => {
+                                readExcelContent(file)
+                                onPreviewReset()
+                            }}
+                        >
+                            <Button
+                                type="link"
+                                style={{ padding: 0, fontSize: 12 }}
+                            >{settings?.corpUsers?.every(item => item?.externalUserTransferContent?.length) ? 'Excel内容已上传,重新上传' : '上传Excel'}</Button>
+                        </Upload>
+                        <Button
+                            type="link"
+                            danger
+                            style={{ padding: 0, fontSize: 12 }}
+                            onClick={() => {
+                                const corpUsers = settings?.corpUsers?.map(item => {
+                                    delete item?.externalUserTransferContent
+                                    return item
+                                })
+                                setSettings({
+                                    ...settings,
+                                    corpUsers,
+                                    userInherit: undefined
+                                })
+                                onPreviewReset()
+                            }}
+                        >清空</Button>
+                    </Space>}
+                </div>
+                <div className={style.detail}>
+                    <div className={style.detail_title}>客户继承配置</div>
+                    <div className={style.detail_body}>
+                        {settings?.userInherit && Object.keys(settings?.userInherit).length > 0 ? <>
+                            <PreviewUserInherit userInherit={settings?.userInherit} />
+                        </> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
+                    </div>
+                </div>
+                <div className={style.detail_footer}>
+                    <Button type="link" style={{ padding: 0, fontSize: 12, color: token.colorPrimary }} onClick={() => setNewVisible(true)}>编辑</Button>
+                </div>
             </div>
         </div>
-        <div className={style.detail_footer}>
-            <Button type="link" style={{ padding: 0, fontSize: 12, color: token.colorPrimary }} onClick={() => setNewVisible(true)}>编辑</Button>
-        </div>
-
         {/* 新增客户继承模板 */}
         {newVisible && <SettingsUserInherit
             visible={newVisible}
@@ -328,7 +332,7 @@ const UserInherit: React.FC = () => {
                 setNewVisible(false)
             }}
         />}
-    </div>
+    </>
 };
 
 export default React.memo(UserInherit);

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

@@ -1,10 +1,10 @@
 import { Card, Form, Input, Radio } from 'antd';
 import React, { useEffect } from 'react';
-import style from '../index.less';
+import style from '../../index.less';
 import SendTimeSet from '@/pages/weComTask/components/sendTimeSet';
 import FilterUser from '@/pages/weComTask/components/filterUser';
 import dayjs from 'dayjs';
-import '../global.less'
+import '../../global.less'
 
 /**
  * 客户继承预览

+ 1 - 1
src/pages/weComTask/page/businessPlan/create/userInherit/settingsUserlnherit.tsx → src/pages/weComTask/page/businessPlan/create/components/userInherit/settingsUserlnherit.tsx

@@ -1,7 +1,7 @@
 import NewSteps from '@/pages/weComTask/components/newSteps';
 import { App, Button, Card, Form, Input, Modal, Radio } from 'antd';
 import React, { useEffect, useRef, useState } from 'react';
-import '../global.less';
+import '../../global.less';
 import SendTimeSet from '@/pages/weComTask/components/sendTimeSet';
 import { PlusOutlined, DeleteOutlined } from '@ant-design/icons';
 import style from '../massSending/index.less'

+ 0 - 0
src/pages/weComTask/page/businessPlan/create/welcome/const.ts → src/pages/weComTask/page/businessPlan/create/components/welcome/const.ts


+ 0 - 0
src/pages/weComTask/page/businessPlan/create/welcome/index.less → src/pages/weComTask/page/businessPlan/create/components/welcome/index.less


+ 65 - 60
src/pages/weComTask/page/businessPlan/create/welcome/index.tsx → src/pages/weComTask/page/businessPlan/create/components/welcome/index.tsx

@@ -1,9 +1,8 @@
 import React, { useContext, useState } from 'react';
-import style from '../index.less';
+import style from '../../index.less';
 import { App, Button, Empty, Space, Upload } from 'antd';
 import SettingsWelcome from './settingsWelcome';
-import { DispatchTaskCreate } from '..';
-import { businessPlanData, headerJsMustStyle, headerJsStyle, welcomeContentData } from '../const';
+import { businessPlanData, headerJsMustStyle, headerJsStyle, welcomeContentData } from '../../const';
 import useNewToken from '@/Hook/useNewToken';
 import { saveAs } from 'file-saver';
 import ExcelJS from 'exceljs';
@@ -11,6 +10,7 @@ import dayjs from 'dayjs';
 import { RcFile } from 'antd/es/upload';
 import { groupBy, readFileAsBuffer } from '@/utils/utils';
 import PreviewWelcome from './previewWelcome';
+import { DispatchTaskCreate } from '../..';
 
 /**
  * 欢迎语
@@ -255,7 +255,7 @@ const Welcome: React.FC = () => {
             message.error('请正确填写Excel内容!')
             return
         }
-        
+
         const groupData = groupBy(data, (item) => [item['col1']]).reduce((acc, cur) => {
             const id = cur[0]['col1'].split('ID:')[1].replace(/\)$/, '');
             acc[id] = cur
@@ -282,66 +282,71 @@ const Welcome: React.FC = () => {
         })
     }
 
-    return <div className={`${style.settingsBody_content_row} ${style.row1}`}>
-        <div className={style.title}>
-            <span>欢迎语</span>
+    return <>
+        <div className={`${style.settingsBody_content_row}`}>
+            <div className={`${style.settingsBody_content_col}`}>
+                <div className={style.title}>
+                    <span>欢迎语</span>
 
-            {(mediaContentList?.some(item => item?.some(i => i?.mediaType === 'link' || i?.mediaType === 'miniprogram')) && settings?.corpUsers?.length) && <Space>
-                <Button
-                    type="link"
-                    style={{ padding: 0, fontSize: 12 }}
-                    loading={downloadLoading}
-                    onClick={() => { exportExcel() }}
-                >下载</Button>
-                <Upload
-                    accept={'.xlsx'}
-                    action="#"
-                    showUploadList={false}
-                    customRequest={() => { }}
-                    beforeUpload={async (file: RcFile): Promise<any> => {
-                        readExcelContent(file)
-                        onPreviewReset()
-                    }}
-                >
-                    <Button
-                        type="link"
-                        style={{ padding: 0, fontSize: 12 }}
-                    >{settings?.corpUsers?.every(item => item?.welcomeMsgContent && Object.keys(item.welcomeMsgContent).length) ? 'Excel内容已上传,重新上传' : '上传Excel'}</Button>
-                </Upload>
+                    {(mediaContentList?.some(item => item?.some(i => i?.mediaType === 'link' || i?.mediaType === 'miniprogram')) && settings?.corpUsers?.length) && <Space>
+                        <Button
+                            type="link"
+                            style={{ padding: 0, fontSize: 12 }}
+                            loading={downloadLoading}
+                            onClick={() => { exportExcel() }}
+                        >下载</Button>
+                        <Upload
+                            accept={'.xlsx'}
+                            action="#"
+                            showUploadList={false}
+                            customRequest={() => { }}
+                            beforeUpload={async (file: RcFile): Promise<any> => {
+                                readExcelContent(file)
+                                onPreviewReset()
+                            }}
+                        >
+                            <Button
+                                type="link"
+                                style={{ padding: 0, fontSize: 12 }}
+                            >{settings?.corpUsers?.every(item => item?.welcomeMsgContent && Object.keys(item.welcomeMsgContent).length) ? 'Excel内容已上传,重新上传' : '上传Excel'}</Button>
+                        </Upload>
 
-                <Button
-                    type="link"
-                    danger
-                    style={{ padding: 0, fontSize: 12 }}
-                    onClick={() => {
-                        const corpUsers = settings?.corpUsers?.map(item => {
-                            delete item?.welcomeMsgContent
-                            return item
-                        })
-                        setSettings({
-                            ...settings,
-                            corpUsers,
-                            welcomeMsgTemplateDTO: undefined
-                        })
-                        onPreviewReset()
-                    }}
-                >清空</Button>
-            </Space>}
+                        <Button
+                            type="link"
+                            danger
+                            style={{ padding: 0, fontSize: 12 }}
+                            onClick={() => {
+                                const corpUsers = settings?.corpUsers?.map(item => {
+                                    delete item?.welcomeMsgContent
+                                    return item
+                                })
+                                setSettings({
+                                    ...settings,
+                                    corpUsers,
+                                    welcomeMsgTemplateDTO: undefined
+                                })
+                                onPreviewReset()
+                            }}
+                        >清空</Button>
+                    </Space>}
 
-        </div>
-        <div className={style.detail}>
-            <div className={style.detail_body}>
-                {settings?.welcomeMsgTemplateDTO && Object.keys(settings?.welcomeMsgTemplateDTO).length > 0 ? <PreviewWelcome 
-                    welcomeMsgTemplateDTO={settings?.welcomeMsgTemplateDTO}
-                    bookList={bookList}
-                    bookPlatForm={bookPlatForm}
-                /> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
+                </div>
+                <div className={style.detail}>
+                    <div className={style.detail_title}>欢迎语配置</div>
+                    <div className={style.detail_body}>
+                        {settings?.welcomeMsgTemplateDTO && Object.keys(settings?.welcomeMsgTemplateDTO).length > 0 ? <PreviewWelcome
+                            welcomeMsgTemplateDTO={settings?.welcomeMsgTemplateDTO}
+                            bookList={bookList}
+                            bookPlatForm={bookPlatForm}
+                        /> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
+                    </div>
+                </div>
+                <div className={style.detail_footer}>
+                    <Button type="link" style={{ padding: 0, fontSize: 12, color: token.colorPrimary }} size="small" onClick={() => setNewVisible(true)}>编辑</Button>
+                </div>
             </div>
         </div>
-        <div className={style.detail_footer}>
-            <Button type="link" style={{ padding: 0, fontSize: 12, color: token.colorPrimary }} size="small" onClick={() => setNewVisible(true)}>编辑</Button>
-        </div>
-
+        
         {/* 配置欢迎语 */}
         {newVisible && <SettingsWelcome
             visible={newVisible}
@@ -363,7 +368,7 @@ const Welcome: React.FC = () => {
                 setNewVisible(false)
             }}
         />}
-    </div>
+    </>
 };
 
 export default React.memo(Welcome);

+ 1 - 1
src/pages/weComTask/page/businessPlan/create/welcome/previewWelcome.tsx → src/pages/weComTask/page/businessPlan/create/components/welcome/previewWelcome.tsx

@@ -1,5 +1,5 @@
 import React from 'react';
-import { businessPlanData, welcomeContentData } from '../const';
+import { businessPlanData, welcomeContentData } from '../../const';
 import LookMsg from '@/pages/weComTask/components/previewMsg/lookMsg';
 
 interface Props {

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

@@ -1,12 +1,12 @@
 import { App, Button, Card, Form, Input, Modal, Select, Space } from 'antd';
 import React, { useEffect, useState } from 'react';
-import '../global.less';
+import '../../global.less';
 import { PlusOutlined, MinusCircleOutlined } from '@ant-design/icons';
 import MaterialMould from '@/pages/weComTask/components/materialMould';
 import style from './index.less'
 import MindTags from '@/pages/weComTask/components/mindTags';
 import NewSteps from '@/pages/weComTask/components/newSteps';
-import { welcomeContentData } from '../const';
+import { welcomeContentData } from '../../const';
 
 /**
  * 欢迎语配置

+ 0 - 0
src/pages/weComTask/page/businessPlan/create/welcome/welcomeShowTable.tsx → src/pages/weComTask/page/businessPlan/create/components/welcome/welcomeShowTable.tsx


+ 112 - 2
src/pages/weComTask/page/businessPlan/create/const.tsx

@@ -185,6 +185,66 @@ export const getGroupData = (settings: TASK_CREATE.SettingsProps): {
     return msgData
 }
 
+/**
+ * 返回处理过的高级群发数据
+ * @param settings 
+ * @returns 
+ */
+export const getHighGroupData = (settings: TASK_CREATE.SettingsProps): {
+    groupSendName: string,
+    strategyData: { [x: string]: any },
+    sendData: { [x: string]: any },
+    content: { [x: string]: any },
+    strategyIndex: number,
+    sendDataIndex: number,
+    contentIndex: number,
+    sendMode: 0 | 1,
+    strategyDataCount: number,
+    sendDataRowSpan: number,
+    strategyItemSendDataCount: number
+}[] => {
+    const msgData = []
+    settings?.highMassSendingContent?.massSendingContentDTO?.forEach((mediaItem, index) => {
+        // 2、策略信息 发送 发送时间
+        const strategyData = settings?.highMassSendingStrategy?.strategySettings?.[index]
+        // 下面总共多少发送对象
+        const strategyDataCount = mediaItem?.sendContentDto?.reduce((pre, cur) => {
+            return pre + cur.contentDTO.length
+        }, 0)
+        // console.log('策略信息' + (index + 1), strategyData)
+        mediaItem?.sendContentDto?.forEach((contentItem, contentindex) => {
+            // 发送对象
+            const sendData = strategyData?.sendData?.[contentindex]
+            const strategyItemSendDataCount = strategyData?.sendData.length
+            const sendDataCount = contentItem.contentDTO.length
+            // console.log('发送对象' + (contentindex + 1), sendData)
+            // 4、群发内容
+            contentItem?.contentDTO.forEach((item, i) => {
+                // console.log('发送内容' + (i + 1), item)
+                const { sendData: aa, ...sd } = strategyData
+                msgData.push({
+                    groupSendName: settings?.highMassSendingStrategy?.groupSendName,
+                    // 策略信息
+                    strategyData: sd,
+                    // 发送模式
+                    sendMode: contentItem?.sendMode,
+                    // 发送对象
+                    sendData,
+                    // 群发内容
+                    content: item,
+                    strategyIndex: index + 1,
+                    sendDataIndex: contentindex + 1,
+                    contentIndex: i + 1,
+                    strategyDataCount,
+                    strategyItemSendDataCount,
+                    sendDataRowSpan: i === 0 ? sendDataCount : 0
+                })
+            })
+        })
+    })
+    return msgData
+}
+
 /**
  * 重置客户继承数据成可编辑数据
  * @param externalUserTransferTasksDTO 
@@ -227,7 +287,8 @@ export const restoreUserInheritData = (externalUserTransferTasksDTO: { [x: strin
  * 重置群发数据为可编辑数据
  * @param groupSendTaskAddDTO 
  */
-export const restoreGroupData = (groupSendTaskAddDTO: { [x: string]: any }) => {
+export const restoreGroupData = (groupSendTaskAddDTO: { [x: string]: any }, configType?: BUSINES_SPLAN_API.ConfigTypeProps) => {
+    const type = configType || 'USER_GROUP'
     const { groupSendName, strategyList } = groupSendTaskAddDTO
     const massSendingContentDTO: { [x: string]: any }[] = []
     const strategySettings = strategyList.map(item => {
@@ -238,7 +299,56 @@ export const restoreGroupData = (groupSendTaskAddDTO: { [x: string]: any }) => {
             const { sendMode, contentDTO, externalUserFilter } = item1
             sendContentDto.push({
                 sendMode,
-                contentDTO
+                contentDTO: type === 'USER_GROUP' ? contentDTO : contentDTO.map(item2 => {
+                    const { msgType, ...content } = item2
+                    if (msgType === 'TASK_STATUS_MINIPROGRAM') {
+                        const { miniprogram: { appId, page, title, picUrl } } = content
+                        return [{
+                            miniprogramAppid: appId,
+                            miniprogramPage: page,
+                            miniprogramTitle: title,
+                            miniprogramPicurl: picUrl,
+                            mediaType: 'miniprogram'
+                        }]
+                    } else if (msgType === 'TASK_CONTENT_TEXT') {
+                        const { text } = content
+                        return [{
+                            textContent: text?.content,
+                            mediaType: 'text'
+                        }]
+                    } else if (msgType === 'TASK_CONTENT_LINK') {
+                        const { link: { desc, picUrl, title, url } } = content
+                        return [{
+                            linkDesc: desc,
+                            linkPicurl: picUrl,
+                            linkTitle: title,
+                            linkUrl: url,
+                            mediaType: 'link'
+                        }]
+                    } else if (msgType === 'TASK_CONTENT_IMAGE') {
+                        const { image: { picUrl } } = content
+                        return [{
+                            imageUrl: picUrl,
+                            mediaType: 'image'
+                        }]
+                    } else if (msgType === 'TASK_STATUS_VIDEO') {
+                        const { video: { videoUrl } } = content
+                        return [{
+                            videoUrl: videoUrl,
+                            mediaType: 'video'
+                        }]
+                    } else if (msgType === 'TASK_STATUS_FILE') { 
+                        const { file: { fileUrl } } = content
+                        return [{
+                            fileUrl: fileUrl,
+                            mediaType: 'file'
+                        }]
+                    }
+                    return {
+                        msgType,
+                        content
+                    }
+                })
             })
             if (externalUserFilter) {
                 const { configName, ...configContent } = externalUserFilter

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

@@ -10,27 +10,20 @@
     }
 }
 
-.config {
-
-    .ant-card-body {}
-}
-
 
 .settingsBody {
     user-select: none;
     margin-top: 16px;
     width: 100%;
     overflow: hidden;
-    overflow-x: auto;
     --w5: 216px;
     --w100: 100%;
 
     .settingsBody_content {
-        min-width: 1400px;
+        width: 100%;
         overflow: hidden;
-        overflow-y: auto;
         border: 1px solid #f0f0f0;
-        border-left: none;
+        border-radius: 6px;
         display: flex;
         box-sizing: border-box;
 
@@ -45,12 +38,14 @@
     .settingsBody_content_right {
         width: 100%;
         min-width: 238px;
+        max-width: 100%;
+        overflow-x: auto;
     }
 
     .settingsBody_content_row {
         border-left: 1px solid #f0f0f0;
         box-sizing: border-box;
-        width: calc(var(--w100) / 4);
+        display: flex;
 
         .title {
             height: 50px;
@@ -75,10 +70,29 @@
             }
         }
 
+        &:first-child {
+            border-left: none;
+        }
+    }
+
+    .settingsBody_content_col {
+        width: 350px;
+        max-width: 350px;
+        min-width: 350px;
+
+        
+
         .detail {
             position: relative;
         }
 
+        .detail_title {
+            font-weight: bold;
+            color: #000;
+            font-size: 12px;
+            padding: 6px 10px;
+        }
+
         .ad_config {
             position: absolute;
             top: 0;
@@ -110,6 +124,13 @@
         &:hover .detail_footer {
             box-shadow: 0 0 4px 1px rgba(0, 0, 0, .08);
         }
+
+        &:not(:last-child) {
+            .detail,
+            .detail_footer {
+                border-right: 1px solid #ebeef5;
+            }
+        }
     }
 }
 

+ 231 - 11
src/pages/weComTask/page/businessPlan/create/index.tsx

@@ -2,19 +2,21 @@ import { inject, observer } from 'mobx-react';
 import React, { useEffect, useState } from 'react';
 import style from './index.less'
 import { App, Button, Card, Empty, Input, Popconfirm, Select, Space, Spin, Table, Tabs } from 'antd';
-import Welcome from './welcome';
-import MassSending from './massSending';
-import UserInherit from './userInherit';
+import MassSending from './components/massSending';
 import { useAjax } from '@/Hook/useAjax';
-import { welcomeMsgJobTypeApi } from '@/pages/weComTask/API/weMaterial/weMaterial';
+import { MediaContentProps, welcomeMsgJobTypeApi } from '@/pages/weComTask/API/weMaterial/weMaterial';
 import { getAllBookApi, getBookPlatInfoAllApi } from '@/pages/weComTask/API/global';
 import SelectCorpUser from '../../corpUserManage/selectCorpUser';
-import { SearchOutlined, PlusOutlined, RedoOutlined, SaveOutlined } from '@ant-design/icons';
-import { getGroupData, getUserInDataData } from './const';
-import { massSendingColumns, userInheritColumns, welcomeColumns } from './tableConfig';
+import { SearchOutlined, PlusOutlined, RedoOutlined, SaveOutlined, CheckOutlined } from '@ant-design/icons';
+import { getGroupData, getHighGroupData, getUserInDataData } from './const';
+import { highMassSendingColumns, massSendingColumns, userInheritColumns, welcomeColumns } from './tableConfig';
 import SubmitModal from './submitModal';
 import { addTaskApi } from '@/pages/weComTask/API/businessPlan/create';
 import { useNavigate } from 'react-router-dom';
+import SelectExternalAccount from '@/pages/weComTask/components/selectExternalAccount';
+import Welcome from './components/welcome';
+import UserInherit from './components/userInherit';
+import HighMassSending from './components/highMassSending';
 
 export const DispatchTaskCreate = React.createContext<TASK_CREATE.DispatchTaskCreate | null>(null);
 /**
@@ -36,6 +38,7 @@ const Create: React.FC<any> = (props: { weGlobaStore: any }) => {
     const [msgJobTypeList, setMsgJobTypeList] = useState<{ value: string, label: string }[]>([])
     const [previewData, setPreviewData] = useState<TASK_CREATE.previewDataProps>({})
     const [subVisible, setSubVisible] = useState<boolean>(false) // 选择设置名称弹窗控制
+    const [eaVisible, setEaVisible] = useState<boolean>(false)
 
     const getAllBook = useAjax(() => getAllBookApi()) //书
     const welcomeMsgJobType = useAjax(() => welcomeMsgJobTypeApi())//获取欢迎语类型
@@ -76,6 +79,7 @@ const Create: React.FC<any> = (props: { weGlobaStore: any }) => {
         }
 
         const corpUsersLength = settings?.corpUsers?.length
+        // 欢迎语
         if (settings?.welcomeMsgTemplateDTO && Object.keys(settings?.welcomeMsgTemplateDTO).length) {
             if (settings?.welcomeMsgTemplateDTO?.mediaContentList?.some(item => item?.some(i => i?.mediaType === 'link' || i?.mediaType === 'miniprogram')) && settings?.corpUsers?.every(item => !item?.welcomeMsgContent?.length)) {
                 message.error('请先上传欢迎语Excel内容')
@@ -131,9 +135,9 @@ const Create: React.FC<any> = (props: { weGlobaStore: any }) => {
         }
 
         // 群发
-        if ((settings?.massSendingContent && Object.keys(settings?.massSendingContent)) && (settings?.massSendingStrategy && Object.keys(settings?.massSendingStrategy))) {
+        if ((settings?.massSendingContent && Object.keys(settings?.massSendingContent).length) && (settings?.massSendingStrategy && Object.keys(settings?.massSendingStrategy).length)) {
             if (settings?.massSendingContent?.massSendingContentDTO?.some(item => item?.sendContentDto?.some(si => si?.contentDTO?.some(i => i?.attachmentList?.some(a => ['TASK_CONTENT_LINK', 'TASK_STATUS_MINIPROGRAM'].includes(a?.msgType))))) && settings?.corpUsers?.every(item => !item?.groupMsgContent?.length)) {
-                message.error('请上传配置好的群发Excel文件')
+                message.error('需要配置小程序、链接,请上传配置好的群发Excel文件')
                 return
             }
             const massSendingData = getGroupData(settings)
@@ -237,6 +241,78 @@ const Create: React.FC<any> = (props: { weGlobaStore: any }) => {
             newPreviewData.userInherit = userInherit
         }
 
+        // 是否有高级群发
+        if ((settings?.highMassSendingContent && Object.keys(settings?.highMassSendingContent).length) && (settings?.highMassSendingStrategy && Object.keys(settings?.highMassSendingStrategy).length)) {
+            if (settings?.highMassSendingContent?.massSendingContentDTO?.some(item => item?.sendContentDto?.some(si => si?.contentDTO?.some(i => i?.some(a => ["miniprogram", 'link'].includes(a?.mediaType))))) && settings?.corpUsers?.every(item => !item?.highGroupMsgContent?.length)) {
+                message.error('需要配置小程序、链接,请上传配置好的高级群发Excel文件')
+                return
+            }
+            const massSendingData = getHighGroupData(settings)
+            console.log('massSendingData---->', massSendingData)
+            const highMassSending = []
+            let rowLength = 0
+            if (!settings?.corpUsers?.every((item, i) => {
+
+                let strategyIndex = 0
+                let corpUserCount = 0
+                return massSendingData.every((dataItem, li, row) => {
+
+                    const mediaItem = JSON.parse(JSON.stringify(dataItem?.content || []))
+                    const contentReactNode = mediaItem.map(item => {
+                        switch (item.mediaType) {
+                            case 'link':
+                                return `<span style="color: red">链接</span>`
+                            case 'miniprogram':
+                                return `<span style="color: red">小程序</span>`
+                            case 'file':
+                                return `<span>文件</span>`
+                            case 'video':
+                                return `<span>视频</span>`
+                            case 'image':
+                                return `<span>图片</span>`
+                            case 'text':
+                                return `<span>文本</span>`
+                            default:
+                                return `<span style="color: red">请联系管理员</span>`
+                        }
+                    })
+                    const externalUserListLength = item.externalUserList.length
+                    return item.externalUserList?.every((externalUser, ii) => {
+                        const layer3 = externalUser.corpId + '-' + externalUser.externalUserId
+                        const externalUserMsg = item.highGroupMsgContent?.[dataItem.strategyIndex - 1]?.[dataItem.sendDataIndex - 1]?.[layer3]?.[dataItem.contentIndex - 1]
+                        if (mediaItem?.some(media => media?.mediaType === 'link' ? !externalUserMsg?.linkUrl : media?.mediaType === 'miniprogram' ? (!externalUserMsg?.miniprogramAppid || !externalUserMsg?.miniprogramPage) : false)) {
+                            message.error('高级群发配置错误,请检查')
+                            return false
+                        }
+                        rowLength++;
+                        highMassSending.push({
+                            ...dataItem,
+                            groupMsgContent: externalUserMsg,
+                            contentReactNode,
+                            externalUser,
+                            corpUserId: item.corpUserId,
+                            corpUserName: item.name,
+                            bizType: settings?.bizType,
+                            channel: settings?.channel,
+                            platform: settings?.platform,
+                            templateProductId: settings?.templateProductId,
+                            id: rowLength,
+                            userRowSpan: corpUserCount === 0 ? (massSendingData.length * externalUserListLength) : 0,
+                            strategyRowSpan: strategyIndex !== dataItem.strategyIndex ? dataItem.sendDataRowSpan * externalUserListLength * dataItem.strategyItemSendDataCount : 0,
+                            sendDataRowSpan: ii === 0 ? externalUserListLength * dataItem.sendDataRowSpan : 0
+                        })
+                        corpUserCount++;
+                        strategyIndex = dataItem.strategyIndex
+
+                        return true
+                    })
+                })
+            })) {
+                return
+            }
+            newPreviewData.highMassSending = highMassSending
+        }
+
         if (newPreviewData && Object.keys(newPreviewData).length) {
             setPreviewData(newPreviewData)
         } else {
@@ -245,7 +321,7 @@ const Create: React.FC<any> = (props: { weGlobaStore: any }) => {
     }
 
     const onSubmit = (values: any) => {
-        const { bizType, platform, templateProductId, corpUsers, welcomeMsgTemplateDTO, massSendingContent, massSendingStrategy, userInherit } = settings
+        const { bizType, platform, templateProductId, corpUsers, welcomeMsgTemplateDTO, massSendingContent, massSendingStrategy, highMassSendingContent, highMassSendingStrategy, userInherit } = settings
         const params = {
             ...values,
             bizType,
@@ -264,9 +340,22 @@ const Create: React.FC<any> = (props: { weGlobaStore: any }) => {
                 if (item?.groupMsgContent) {
                     params.groupMsgContent = item?.groupMsgContent
                 }
+                // 继承
                 if (item?.externalUserTransferContent) {
                     params.takeoverUserIds = item?.externalUserTransferContent
                 }
+                // 高级群发
+                if (item?.highGroupMsgContent) {
+                    params.messageSendContent = item?.highGroupMsgContent
+                    // 外部成员
+                    if (item?.externalUserList) {
+                        params.corpList = item?.externalUserList.map(item => ({
+                            externalUserId: item.externalUserId,
+                            corpId: item.corpId
+                        }))
+                    }
+                }
+
                 return params
             })
         }
@@ -307,6 +396,101 @@ const Create: React.FC<any> = (props: { weGlobaStore: any }) => {
                 })
             }
         }
+        // 高级群发
+        if (highMassSendingStrategy && Object.keys(highMassSendingStrategy).length) {
+            params.messageSendTaskAddDTO = {
+                groupSendName: highMassSendingStrategy.groupSendName,
+                strategyList: highMassSendingStrategy?.strategySettings?.map((settingsItem, settingsIndex) => {
+                    const {
+                        // 发送对象
+                        sendData,
+                        // 策略
+                        ...strategy
+                    } = settingsItem
+                    return {
+                        ...strategy,
+                        taskDetail: sendData.map((sendItem, sendIndex) => {
+                            // 发送内容
+                            const { contentDTO, sendMode } = highMassSendingContent.massSendingContentDTO[settingsIndex]['sendContentDto'][sendIndex]
+                            const detail: { [x: string]: any } = {
+                                sendMode,
+                                contentDTO: contentDTO.map(item => {
+
+                                    let newContentDTO: { [x: string]: any } = {}
+                                    item.forEach((item: MediaContentProps) => {
+                                        switch (item.mediaType) {
+                                            case "text":
+                                                newContentDTO = {
+                                                    text: {
+                                                        content: item?.textContent
+                                                    },
+                                                    msgType: 'TASK_CONTENT_TEXT'
+                                                }
+                                                break
+                                            case "miniprogram":
+                                                newContentDTO = {
+                                                    miniprogram: {
+                                                        appId: item?.miniprogramAppid,
+                                                        page: item?.miniprogramPage,
+                                                        title: item?.miniprogramTitle,
+                                                        picUrl: item?.miniprogramPicurl
+                                                    },
+                                                    msgType: 'TASK_STATUS_MINIPROGRAM'
+                                                }
+                                                break
+                                            case "link":
+                                                newContentDTO = {
+                                                    link: {
+                                                        desc: item?.linkDesc,
+                                                        picUrl: item?.linkPicurl,
+                                                        title: item?.linkTitle,
+                                                        url: item?.linkUrl
+                                                    },
+                                                    msgType: 'TASK_CONTENT_LINK'
+                                                }
+                                                break
+                                            case "image":
+                                                newContentDTO = {
+                                                    image: {
+                                                        picUrl: item?.imageUrl
+                                                    },
+                                                    msgType: 'TASK_CONTENT_IMAGE'
+                                                }
+                                                break
+                                            case "video":
+                                                newContentDTO = {
+                                                    video: {
+                                                        videoUrl: item?.videoUrl
+                                                    },
+                                                    msgType: 'TASK_STATUS_VIDEO'
+                                                }
+                                                break
+                                            case "file":
+                                                newContentDTO = {
+                                                    file: {
+                                                        fileUrl: item?.fileUrl
+                                                    },
+                                                    msgType: 'TASK_STATUS_FILE'
+                                                }
+                                                break
+                                        }
+                                    })
+
+                                    return newContentDTO
+                                })
+                            }
+                            if (sendItem.externalUserType === 'specify') {
+                                detail.externalUserFilter = {
+                                    configName: sendItem.externalUserFilter.configName,
+                                    ...sendItem.externalUserFilter.configContent
+                                }
+                            }
+                            return detail
+                        })
+                    }
+                })
+            }
+        }
         if (userInherit && Object.keys(userInherit).length) {
             params.externalUserTransferTasksDTO = {
                 taskName: userInherit.taskName,
@@ -448,6 +632,15 @@ const Create: React.FC<any> = (props: { weGlobaStore: any }) => {
                         />
                     </Space.Compact> : undefined}
 
+                    {(settings?.highMassSendingStrategy && Object.keys(settings?.highMassSendingStrategy).length > 0) && <Button
+                        onClick={() => setEaVisible(true)}
+                        type={settings?.corpUsers?.every(item => item?.externalUserList?.length) ? 'primary' : 'default'}
+                    >
+                        {settings?.corpUsers?.every(item => item?.externalUserList?.length) ? <>
+                            重选高级群发外部客户
+                            <CheckOutlined style={{ marginLeft: 5 }} />
+                        </> : '高级群发外部客户选择'}
+                    </Button>}
                 </Space>
 
                 <div className={style.settingsBody}>
@@ -466,6 +659,8 @@ const Create: React.FC<any> = (props: { weGlobaStore: any }) => {
                                 <Welcome />
                                 {/* 群发设置 */}
                                 <MassSending />
+                                {/* 高级群发 */}
+                                <HighMassSending />
                                 {/* 客户继承 */}
                                 <UserInherit />
                             </div>
@@ -495,7 +690,7 @@ const Create: React.FC<any> = (props: { weGlobaStore: any }) => {
                     defaultActiveKey="1"
                     items={Object.keys(previewData).map(key => ({
                         key: key,
-                        label: { 'userInherit': '客户继承', 'massSending': '客户群发', 'welcome': '欢迎语' }[key],
+                        label: { 'userInherit': '客户继承', 'massSending': '客户群发', 'welcome': '欢迎语', 'highMassSending': '高级群发' }[key],
                         children: key === 'userInherit' ? <>
                             <Table
                                 dataSource={previewData[key]}
@@ -522,6 +717,15 @@ const Create: React.FC<any> = (props: { weGlobaStore: any }) => {
                                 bordered={true}
                                 scroll={{ y: 550 }}
                             />
+                        </> : key === 'highMassSending' ? <>
+                            <Table
+                                dataSource={previewData[key]}
+                                columns={highMassSendingColumns()}
+                                rowKey={'id'}
+                                bordered={true}
+                                scroll={{ y: 550 }}
+                                pagination={false}
+                            />
                         </> : undefined
                     }))}
                     tabBarExtraContent={<Space size={10}>
@@ -547,6 +751,22 @@ const Create: React.FC<any> = (props: { weGlobaStore: any }) => {
                 setSubVisible(false)
             }}
         />}
+
+        {/* 选择外部客户 */}
+        {eaVisible && <SelectExternalAccount
+            corpUsers={settings?.corpUsers || []}
+            visible={eaVisible}
+            onClose={() => {
+                setEaVisible(false)
+            }}
+            onChange={(value) => {
+                setEaVisible(false)
+                setSettings({
+                    ...settings,
+                    corpUsers: value
+                })
+            }}
+        />}
     </div>
 };
 

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

@@ -3,8 +3,8 @@ import { AnyObject } from "antd/es/_util/type"
 import { ColumnsType } from "antd/es/table"
 import { businessPlanData, TIME_TYPE, welcomeContentData } from "./const";
 import FilterUser from "@/pages/weComTask/components/filterUser";
-import ShowContentTable from "./massSending/showContentTable";
-import WelcomeShowTable from "./welcome/welcomeShowTable";
+import ShowContentTable from "./components/massSending/showContentTable";
+import WelcomeShowTable from "./components/welcome/welcomeShowTable";
 const { Title, Text, Paragraph } = Typography;
 
 export const userInheritColumns = (): ColumnsType<AnyObject> => {
@@ -208,6 +208,132 @@ export const massSendingColumns = (): ColumnsType<AnyObject> => {
 }
 
 
+
+export const highMassSendingColumns = (): ColumnsType<AnyObject> => {
+    return [
+        {
+            title: '账号',
+            dataIndex: 'corpUserName',
+            key: 'corpUserName',
+            width: 150,
+            render(value, record) {
+                return <Text>{value}({record.corpUserId})</Text>
+            },
+            onCell: (record) => {
+                return { rowSpan: record.userRowSpan }
+            }
+        },
+        {
+            title: '群发信息',
+            dataIndex: 'groupSendName',
+            key: 'groupSendName',
+            width: 130,
+            render(value, record) {
+                return <>
+                    <Title level={5} style={{ margin: 0 }}>标题:{value}</Title>
+                    {record?.bizType && <Paragraph style={{ margin: 0 }}>业务类型:{record?.bizType || '<空>'}</Paragraph>}
+                    {record?.platform && <Paragraph style={{ margin: 0 }}>书城:{record?.platform || '<空>'}</Paragraph>}
+                    {record?.templateProductId && <Paragraph style={{ margin: 0 }}>适用产品:{record?.templateProductId || '<空>'}</Paragraph>}
+                </>
+            },
+            onCell: (record) => {
+                return { rowSpan: record.userRowSpan }
+            }
+        },
+        {
+            title: '策略信息',
+            dataIndex: 'strategyData',
+            key: 'strategyData',
+            width: 200,
+            render(value, record) {
+                return <>
+                    <Title level={5} style={{ margin: 0 }}>策略{record.strategyIndex}</Title>
+                    <Paragraph style={{ margin: 0 }}>名称:{value?.strategyName || '<空>'}</Paragraph>
+                    <Paragraph style={{ margin: 0 }}>执行类型:{TIME_TYPE[value?.timeRepeatType]}</Paragraph>
+                    {value?.sendDay && <Paragraph style={{ margin: 0 }}>执行时间:{value?.sendDay}</Paragraph>}
+                    {value?.startTime && <Paragraph style={{ margin: 0 }}>执行日期:{value?.startTime}~{value?.endTime ? value?.endTime : '长期执行'}</Paragraph>}
+                    {value?.sendTime && <Paragraph style={{ margin: 0 }}>执行时间:{value?.sendTime}</Paragraph>}
+                    {value?.repeatArray && <Paragraph style={{ margin: 0 }}>执行天数:{value?.repeatArray.join('、')}</Paragraph>}
+                </>
+            },
+            onCell: (record) => {
+                return { rowSpan: record.strategyRowSpan }
+            }
+        },
+        {
+            title: '发送对象',
+            dataIndex: 'sendData',
+            key: 'sendData',
+            width: 200,
+            render(value, record) {
+                return <>
+                    <Title level={5} style={{ margin: 0 }}>发送对象{record.sendDataIndex}</Title>
+                    <Paragraph style={{ margin: 0 }}>内容发送模式:{welcomeContentData?.find(i => i.value === record?.sendMode)?.label}</Paragraph>
+                    <Paragraph style={{ margin: 0 }}>类型:{value?.externalUserType === 'all' ? '全部' : '指定'}</Paragraph>
+                    {value?.externalUserType === 'specify' && <FilterUser isSHow value={value?.externalUserFilter} configType='GROUP_GROUP' />}
+                </>
+            },
+            onCell: (record) => {
+                return { rowSpan: record.sendDataRowSpan }
+            }
+        },
+        {
+            title: '群发外部联系人',
+            dataIndex: 'externalUser',
+            key: 'externalUser',
+            width: 200,
+            render(value) {
+                return <>
+                    <Title ellipsis level={5} style={{ margin: 0 }}>消息发送号外部联系人</Title>
+                    <Paragraph ellipsis={{ tooltip: true }} style={{ margin: 0 }}>{value?.externalUserId}({value?.name})</Paragraph>
+                    <Title ellipsis level={5} style={{ margin: 0 }}>群聊所在主体</Title>
+                    <Paragraph ellipsis={{ tooltip: true }} style={{ margin: 0 }}>{value?.corpId}({value?.corpName})</Paragraph>
+                </>
+            },
+        },
+        {
+            title: '发送内容',
+            dataIndex: 'content',
+            key: 'content',
+            width: 200,
+            render(_, record) {
+                return <WelcomeShowTable
+                    name={<><span>内容{record.contentIndex}:</span><span dangerouslySetInnerHTML={{ __html: record.contentReactNode?.map((item) => item).join('、') }}></span></>}
+                    data={record?.content}
+                />
+            },
+        },
+        {
+            title: '图文链接',
+            dataIndex: 'linkUrl',
+            key: 'linkUrl',
+            width: 200,
+            render(_, record) {
+                return <Paragraph style={{ margin: 0, color: '#000', fontWeight: !!record?.groupMsgContent?.linkUrl ? 'bold' : 'normal' }}>{record?.groupMsgContent?.linkUrl || '--'}</Paragraph>
+            },
+        },
+        {
+            title: '小程序APPID',
+            dataIndex: 'miniprogramAppid',
+            key: 'miniprogramAppid',
+            width: 200,
+            render(_, record) {
+                return <Paragraph style={{ margin: 0, color: '#000', fontWeight: !!record?.groupMsgContent?.miniprogramAppid ? 'bold' : 'normal' }}>{record?.groupMsgContent?.miniprogramAppid || '--'}</Paragraph>
+            },
+        },
+        {
+            title: '小程序路径',
+            dataIndex: 'miniprogramPage',
+            key: 'miniprogramPage',
+            width: 200,
+            render(_, record) {
+                return <Paragraph style={{ margin: 0, color: '#000', fontWeight: !!record?.groupMsgContent?.miniprogramPage ? 'bold' : 'normal' }}>{record?.groupMsgContent?.miniprogramPage || '--'}</Paragraph>
+            },
+        }
+    ]
+}
+
+
 export const welcomeColumns = (bookPlatForm: { platformName: string, id: number, platformKey: string }[], bookList: { id: number, bookName: string, platformId: number }[]): ColumnsType<AnyObject> => {
     return [
         {

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

@@ -30,6 +30,7 @@ declare namespace TASK_CREATE {
         welcome?: any[];
         userInherit?: any[];
         massSending?: any[];
+        highMassSending?: any[];
     }
     interface WelcomeProps<T> extends DefaultProps, DefaultChangeProps<T> {
         value?: { [x: string]: any };
@@ -42,12 +43,31 @@ declare namespace TASK_CREATE {
         onChange?: (value?: { [x: string]: any }, type?: string) => void;
         createType?: 'STRATEGY' | 'CONTENT'
     }
+    interface HighMassSendingProps<T> extends DefaultProps, DefaultChangeProps<T> {
+        value?: {
+            highMassSendingStrategy?: { [x: string]: any };
+            highMassSendingContent?: { massSendingContentDTO: any };
+        };
+        onChange?: (value?: { [x: string]: any }, type?: string) => void;
+        createType?: 'STRATEGY' | 'CONTENT'
+    }
     interface UserInheritProps<T> extends DefaultProps, DefaultChangeProps<T> {
         value?: { [x: string]: any };
     }
+    interface corpUsersProps { 
+        corpUserId: string, 
+        name: string, 
+        corpName: string, 
+        corpId: string, 
+        welcomeMsgContent?: any[], 
+        groupMsgContent?: any[], 
+        highGroupMsgContent?: any[], 
+        externalUserTransferContent?: any[],
+        externalUserList?: any[]
+    }
     // 配置保存字段
     interface SettingsProps {
-        corpUsers?: { corpUserId: string, name: string, corpName: string, corpId: string, welcomeMsgContent?: any[], groupMsgContent?: any[], externalUserTransferContent?: any[] }[]; // 企微号列表
+        corpUsers?: corpUsersProps[]; // 企微号列表
         bizType?: string; // 业务类型
         platform?: string; // 书城
         channel?: string;   // 渠道
@@ -55,6 +75,8 @@ declare namespace TASK_CREATE {
         welcomeMsgTemplateDTO?: { [x: string]: any };
         massSendingStrategy?: { [x: string]: any };
         massSendingContent?: { massSendingContentDTO: any };
+        highMassSendingStrategy?: { [x: string]: any };
+        highMassSendingContent?: { massSendingContentDTO: any };
         userInherit?: { [x: string]: any };
     }
     // 群发策略

+ 3 - 3
src/pages/weComTask/page/businessPlan/taskList/externalUserTransferTask/index.tsx → src/pages/weComTask/page/businessPlan/taskList/components/externalUserTransferTask/index.tsx

@@ -1,11 +1,11 @@
 import { Button, Popover, Space, Table, Tag, Typography } from 'antd';
 import React, { useContext, useState } from 'react';
-import { EUTTaskStatus, TIME_TYPE_ZJ } from '../../create/const';
+import { EUTTaskStatus, TIME_TYPE_ZJ } from '../../../create/const';
 import PreviewTime from '@/pages/weComTask/components/previewTime';
 import { QuestionCircleFilled } from '@ant-design/icons';
-import style from '../index.less'
+import style from '../../index.less'
 import FilterUserTooltip from '@/pages/weComTask/components/filterUser/filterUserTooltip';
-import { DispatchTaskDetails } from '../log';
+import { DispatchTaskDetails } from '../../log';
 import Log from './log';
 const { Text } = Typography;
 

+ 0 - 0
src/pages/weComTask/page/businessPlan/taskList/externalUserTransferTask/log.tsx → src/pages/weComTask/page/businessPlan/taskList/components/externalUserTransferTask/log.tsx


+ 0 - 0
src/pages/weComTask/page/businessPlan/taskList/externalUserTransferTask/tableConfig.tsx → src/pages/weComTask/page/businessPlan/taskList/components/externalUserTransferTask/tableConfig.tsx


+ 0 - 0
src/pages/weComTask/page/businessPlan/taskList/groupTask/groupErrorCountList.tsx → src/pages/weComTask/page/businessPlan/taskList/components/groupTask/groupErrorCountList.tsx


+ 0 - 0
src/pages/weComTask/page/businessPlan/taskList/groupTask/groupReSend.tsx → src/pages/weComTask/page/businessPlan/taskList/components/groupTask/groupReSend.tsx


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

@@ -5,7 +5,7 @@ import { copy } from "@/utils/utils"
 import { Avatar, Badge, Button, Space, Tooltip, Typography } from "antd"
 import { ColumnsType } from "antd/es/table"
 import { InfoCircleOutlined, QuestionCircleOutlined, UserOutlined } from "@ant-design/icons"
-import { GENDER_TYPE } from "../../create/const"
+import { GENDER_TYPE } from "../../../create/const"
 import GroupErrorCountList from "./groupErrorCountList"
 import GroupReSend from "./groupReSend"
 

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


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


+ 7 - 7
src/pages/weComTask/page/businessPlan/taskList/groupTask/index.tsx → src/pages/weComTask/page/businessPlan/taskList/components/groupTask/index.tsx

@@ -1,7 +1,7 @@
 import { Card, Popover, Table, Tabs, Tag, Typography } from 'antd';
 import React, { useContext, useState } from 'react';
-import { STATUS_ZJ, TIME_TYPE_ZJ } from '../../create/const';
-import style from '../index.less'
+import { STATUS_ZJ, TIME_TYPE_ZJ } from '../../../create/const';
+import style from '../../index.less'
 import { QuestionCircleFilled } from '@ant-design/icons';
 import PreviewTime from '@/pages/weComTask/components/previewTime';
 import FilterUserTooltip from '@/pages/weComTask/components/filterUser/filterUserTooltip';
@@ -9,8 +9,8 @@ import LookMsg from '@/pages/weComTask/components/previewMsg/lookMsg';
 import useNewToken from '@/Hook/useNewToken';
 import GroupXfCorpTabls from './groupXfCorpTabls';
 import GroupTaskNotes from './groupTaskNotes';
-import { DispatchTaskDetails } from '../log';
-import { getMsgData } from '../../create/massSending/showContent';
+import { getMsgData } from '../../../create/components/massSending/showContent';
+import { DispatchTaskDetails } from '../../log';
 const { Text } = Typography;
 
 
@@ -26,7 +26,7 @@ interface Props {
 const GroupTask: React.FC<Props> = ({ groupSendTaskVOList }) => {
 
     const { bookPlatForm } = useContext(DispatchTaskDetails)!;
-    
+
     return <Table
         dataSource={groupSendTaskVOList}
         columns={[
@@ -162,7 +162,7 @@ export const ExpandedRow: React.FC<{ record: any }> = ({ record }) => {
     const { corpList } = useContext(DispatchTaskDetails)!;
 
     return <div style={{ display: 'flex', gap: 10, maxHeight: 450 }}>
-        <div style={{ width: 320, height: '100%', overflow: 'hidden', overflowY: 'auto' }}>
+        <div style={{ width: 320, maxHeight: 450, overflow: 'hidden', overflowY: 'auto' }}>
             <h2>群发内容</h2>
             <div style={{ marginTop: 5, backgroundColor: '#d9d9d9', borderRadius: 6, padding: '10px 10px 16px' }}>
                 <LookMsg data={getMsgData(contentDTO?.[0], token)} />
@@ -182,7 +182,7 @@ export const ExpandedRow: React.FC<{ record: any }> = ({ record }) => {
                         key: '2',
                         label: '下发企微号',
                         children: <Card styles={{ body: { maxHeight: 400 } }}>
-                            {activeKey == '2' && <GroupXfCorpTabls corpId={corpId} taskId={id} weList={corpList?.find(item => item.corpId == corpId).weList} />}
+                            {activeKey == '2' && <GroupXfCorpTabls corpId={corpId} taskId={id} weList={corpList?.find(item => item.corpId == corpId)?.weList} />}
                         </Card>
                     },
                     {

+ 51 - 0
src/pages/weComTask/page/businessPlan/taskList/components/highGroupTask/groupErrorCountList.tsx

@@ -0,0 +1,51 @@
+import { useAjax } from "@/Hook/useAjax"
+import { Popover, Table } from "antd"
+import React from "react"
+import { groupErrorCountTableConfig } from "./highGroupTableConfig"
+import { getGroupFailUserListApi } from "@/pages/weComTask/API/businessPlan/create"
+
+
+
+
+interface Props {
+    failCount: any,
+    corpId: string,
+    msgId: string,
+    taskId: string
+}
+
+/**
+ * 获取群发记录中失败客户列表
+ * @returns 
+ */
+const GroupErrorCountList: React.FC<Props> = ({ failCount, corpId, msgId, taskId }) => {
+
+    /***********************************/
+    const getFailUserList = useAjax((params) => getGroupFailUserListApi(params))//获取朋友圈任务列表
+    /***********************************/
+
+    const getList = () => {
+        getFailUserList.run({ corpId, msgId, taskId })
+    }
+
+    return <>
+        {failCount ? <Popover
+            trigger={['click']}
+            styles={{ body: { width: 500 } }}
+            content={<Table
+                dataSource={getFailUserList?.data?.data?.map((item: any, index: number) => ({ ...item, id: item?.externalUserId + index }))}
+                rowKey={'id'}
+                scroll={{ y: 200 }}
+                loading={getFailUserList.loading}
+                columns={groupErrorCountTableConfig()}
+            />}
+            onOpenChange={(open) => {
+                if (open) getList()
+            }}
+        >
+            <a>{failCount}</a>
+        </Popover> : '--'}
+    </>
+}
+
+export default React.memo(GroupErrorCountList)

+ 35 - 0
src/pages/weComTask/page/businessPlan/taskList/components/highGroupTask/groupReSend.tsx

@@ -0,0 +1,35 @@
+import { useAjax } from "@/Hook/useAjax"
+import { reSendGroupTaskApi } from "@/pages/weComTask/API/businessPlan/create"
+import { App, Button } from "antd"
+import React from "react"
+
+
+interface Props {
+    id: number
+    corpUserId?: string
+    onChange?: () => void
+}
+/**
+ * 重新发送
+ * @returns 
+ */
+const GroupReSend: React.FC<Props> = ({ onChange, ...props }) => {
+
+    /*************************/
+    const { message } = App.useApp()
+    const reSendTask = useAjax((params) => reSendGroupTaskApi(params))
+    /*************************/
+
+    const sendHandle = () => {
+        reSendTask.run(props).then(res => {
+            if (res.data) {
+                message.success('发送成功')
+                onChange?.()
+            }
+        })
+    }
+
+    return <Button type="link" loading={reSendTask.loading} onClick={sendHandle} style={{ padding: 0 }}>重新发送</Button>
+}
+
+export default React.memo(GroupReSend)

+ 309 - 0
src/pages/weComTask/page/businessPlan/taskList/components/highGroupTask/highGroupTableConfig.tsx

@@ -0,0 +1,309 @@
+import useNewToken from "@/Hook/useNewToken"
+import PreviewMsg from "@/pages/weComTask/components/previewMsg"
+import { emoList } from "@/pages/weComTask/components/textEditor/Expression"
+import { copy } from "@/utils/utils"
+import { Avatar, Badge, Button, Popconfirm, Space, Tooltip, Typography } from "antd"
+import { ColumnsType } from "antd/es/table"
+import { InfoCircleOutlined, QuestionCircleOutlined, UserOutlined } from "@ant-design/icons"
+import { GENDER_TYPE } from "../../../create/const"
+import GroupErrorCountList from "./groupErrorCountList"
+import GroupReSend from "./groupReSend"
+
+export function highGroupMsgCorpUserListConfig(reload: (id: any) => void): ColumnsType<any> {
+
+    let arr: ColumnsType<any> = [
+        {
+            title: '所属企业',
+            dataIndex: 'corpName',
+            key: 'corpName',
+            align: 'center',
+            width: 120,
+            fixed: 'left'
+        },
+        {
+            title: '企微号',
+            dataIndex: 'userName',
+            key: 'userName',
+            align: 'center',
+            width: 120,
+            ellipsis: true,
+            fixed: 'left',
+            render: (a: string, b: any) => {
+                return a + `(${b?.userId})`
+            }
+        },
+        {
+            title: '发送时间',
+            dataIndex: 'sendTime',
+            key: 'sendTime',
+            align: 'center',
+            width: 120,
+            ellipsis: true,
+        },
+        {
+            title: '预计发送时间',
+            dataIndex: 'createTime',
+            key: 'createTime',
+            align: 'center',
+            width: 120,
+            ellipsis: true,
+        },
+        {
+            title: '发送状态',
+            dataIndex: 'status',
+            key: 'status',
+            align: 'center',
+            width: 100,
+            render: (a: number) => {
+                return <span>{{ '1': <Badge status="error" text='未发送' />, '2': <Badge status="success" text='已发送' /> }[a]}</span>
+            }
+        },
+        {
+            title: '发送数',
+            dataIndex: 'userCount',
+            key: 'userCount',
+            align: 'center',
+            width: 100,
+            render: (a: number, b: any) => {
+                return b?.status == "1" ? 0 : a
+            }
+        },
+        {
+            title: '失败用户/群',
+            dataIndex: 'errChatNameList',
+            key: 'errChatNameList',
+            align: 'center',
+            width: 200,
+            ellipsis: true,
+            render: (a: any) => {
+                return a
+            }
+        },
+        {
+            title: '失败原因',
+            dataIndex: "errorMsg",
+            align: 'center',
+            width: 200,
+            ellipsis: true,
+            render: (a: string, b: any) => {
+                return b?.status == "1" ? a : ""
+            }
+        },
+        {
+            title: '操作',
+            dataIndex: 'action',
+            key: 'action',
+            align: 'center',
+            width: 50,
+            fixed: 'right',
+            render: (a: any, b: any) => {
+                return <Space>
+                    {
+                        b?.status == "1" ? <Popconfirm title='确定重新发送?' onConfirm={() => {
+                            reload(b?.id)
+                        }}>
+                            <Button type="link" danger style={{ padding: 0 }} >{'重新发送'}</Button>
+                        </Popconfirm> : <></>
+                    }
+                </Space>
+            }
+        }
+    ]
+    return arr
+}
+
+
+export function groupTaskNotesTableConfig(refresh: () => void): ColumnsType<any> {
+    const { token } = useNewToken()
+    // 处理群发内容
+    function groupBody(item) {
+        let msgData = []
+        if (item?.text?.content) {
+            let newValue = item?.text?.content
+            let newEmo = emoList.reduce((prev, cur) => {
+                return [...prev, ...cur]
+            }, [])
+            let emos = newValue.match(/\[[\u4e00-\u9fa5]+\]/g)
+            if (emos && emos?.length) {
+                emos.forEach((emo: any) => {
+                    let emoData = newEmo.find(o => `[${o.name}]` === emo)
+                    if (emoData) {
+                        newValue = newValue.replace(emo, `<img src="${emoData.url}" alt="${emoData.name}" style="width:20px;height:20px"/>`)
+                    }
+                })
+            }
+            if (newValue.includes('\n')) {
+                newValue = newValue.replace('\n', `<br/>`)
+            }
+            let names = newValue.match(/%[\u4e00-\u9fa5]+%/g)
+            if (names && names?.length) {
+                newValue = newValue.replace('%昵称%', ` <span style="display: inline-block;margin:0 1px;position: relative; border: 1px solid ${token.colorBorder}; padding: ${token.paddingXS}px; border-radius: ${token.borderRadius}px;color: ${token.colorTextBase}; background: ${token.colorSuccess};color:${token.colorTextLightSolid}" contenteditable="false">昵称<strong data-name="昵称" style="padding: 0 6px;cursor:pointer" onclick="let html =document.querySelector('[data-name=昵称').parentElement.parentElement.innerHTML;let span = ' '+document.querySelector('[data-name=昵称]').parentElement.outerHTML+' ';console.log('=',html,'=');console.log('=',span,'=');document.execCommand('selectAll');document.execCommand('delete'); document.execCommand('insertHTML', true, html.replace(span,''));">X</strong></span> `)
+            }
+            msgData.push({
+                textContent: newValue,
+                mediaType: 'text'
+            })
+        }
+        if (item?.attachmentList && item?.attachmentList?.length > 0) {
+            let newAttachmentList = item?.attachmentList.map((item: any) => {
+                switch (item.msgType) {
+                    case 'TASK_CONTENT_IMAGE':
+                        return { mediaType: 'image', imageUrl: item?.image?.picUrl }
+                    case 'TASK_CONTENT_LINK':
+                        return { mediaType: 'link', linkDesc: item?.link?.desc, linkPicurl: item?.link?.picUrl, linkTitle: item?.link?.title, linkUrl: item?.link?.url }
+                    case 'TASK_STATUS_FILE':
+                        return { mediaType: 'file', fileUrl: item?.file?.fileUrl }
+                    case 'TASK_STATUS_VIDEO':
+                        return { mediaType: 'video', videoUrl: item?.video?.videoUrl }
+                    case 'TASK_STATUS_MINIPROGRAM':
+                        return {
+                            mediaType: 'miniprogram',
+                            miniprogramAppid: item.miniprogram?.appId,
+                            miniprogramPage: item?.miniprogram?.page,
+                            miniprogramPicurl: item?.miniprogram?.picUrl,
+                            miniprogramTitle: item?.miniprogram?.title
+                        }
+                }
+            })
+            msgData = msgData.concat(newAttachmentList)
+        }
+        return msgData
+    }
+    return [
+        {
+            title: '任务ID',
+            dataIndex: 'taskId',
+            key: 'taskId',
+            align: 'center',
+            width: 65
+        },
+        {
+            title: '客服号名称',
+            dataIndex: 'corpUserName',
+            key: 'corpUserName',
+            align: 'center',
+            width: 150,
+            ellipsis: true
+        },
+        {
+            title: '消息ID',
+            dataIndex: 'msgId',
+            key: 'msgId',
+            align: 'center',
+            width: 250,
+            ellipsis: true,
+            render: (a: string) => {
+                return <a onClick={() => copy(a)}>{a}</a>
+            }
+        },
+        {
+            title: <div>发送内容<Tooltip title="2025-01-10前的群发无法查看内容"><InfoCircleOutlined /></Tooltip></div>,
+            dataIndex: 'contentDTO',
+            key: 'contentDTO',
+            align: 'center',
+            width: 90,
+            ellipsis: true,
+            render: (a, b) => {
+                let data = []
+                if (a) {
+                    data = groupBody(a)
+                }
+
+                return data?.length > 0 ? <Tooltip color={"#fff"} title={<PreviewMsg type="MSG" minWidth={"200px"} height={'calc(100vh - 400px)'} content={{ data }} />}>
+                    <a>查看内容</a>
+                </Tooltip> : "无内容"
+            }
+        },
+        {
+            title: '发送时间',
+            dataIndex: 'createTime',
+            key: 'createTime',
+            ellipsis: true,
+            align: 'center',
+            width: 130
+        },
+        {
+            title: <>执行结果 <Tooltip title={<span>此结果为任务在后台创建后是否成功提交到腾讯。腾讯下发至手机和手机的最终执行结果请查看下发企微号。</span>}><QuestionCircleOutlined style={{ color: 'red' }} /></Tooltip></>,
+            dataIndex: 'errCode',
+            key: 'errCode',
+            width: 60,
+            align: 'center',
+            ellipsis: true,
+            render: (a: number) => {
+                return { 0: '成功', 1: '失败' }[a]
+            }
+        },
+        {
+            title: '失败人数',
+            dataIndex: 'failCount',
+            key: 'failCount',
+            align: 'center',
+            ellipsis: true,
+            width: 60,
+            render: (a: any, b: any) => {
+                return <GroupErrorCountList failCount={a} corpId={b?.corpId} msgId={b?.msgId} taskId={b?.taskId} />
+            }
+        },
+        {
+            title: '错误信息',
+            dataIndex: 'errMsg',
+            key: 'errMsg',
+            ellipsis: true,
+            width: 150
+        },
+        {
+            title: '操作',
+            dataIndex: 'cz',
+            key: 'cz',
+            width: 70,
+            fixed: 'right',
+            align: 'center',
+            render(_, record) {
+                if (record.errCode === 1) {
+                    return <GroupReSend onChange={refresh} id={record.taskId} corpUserId={record.corpUserId} />
+                }
+                return '--'
+            },
+        },
+    ]
+}
+
+
+
+export function groupErrorCountTableConfig(): ColumnsType<any> {
+    return [
+        {
+            title: '客户名称',
+            dataIndex: 'name',
+            key: 'name',
+            align: 'center',
+            width: 100,
+            render: (a: string, b: any) => {
+                return <Space>
+                    <Avatar shape="square" size="small" icon={<UserOutlined />} src={b?.avatar} />
+                    <Typography.Paragraph ellipsis={{ rows: 1, tooltip: true }} style={{ width: 62, marginBottom: 0 }}>{a}</Typography.Paragraph>
+                </Space>
+            }
+        },
+        {
+            title: '用户ID',
+            dataIndex: 'externalUserId',
+            key: 'externalUserId',
+            align: 'center',
+            ellipsis: true,
+            render: (a: string) => {
+                return <a onClick={() => copy(a)}>{a}</a>
+            }
+        },
+        {
+            title: '性别',
+            dataIndex: 'gender',
+            key: 'gender',
+            align: 'center',
+            width: 80,
+            render: (a: number) => {
+                return <span>{GENDER_TYPE[a]}</span>
+            }
+        },
+    ]
+}

+ 77 - 0
src/pages/weComTask/page/businessPlan/taskList/components/highGroupTask/highGroupTaskNotes.tsx

@@ -0,0 +1,77 @@
+import { useAjax } from "@/Hook/useAjax"
+import { useUpdateEffect } from "ahooks"
+import { Button, DatePicker, Space, Table, Tag } from "antd"
+import React, { useEffect, useState } from "react"
+import { SearchOutlined } from '@ant-design/icons';
+import dayjs from "dayjs"
+import { groupTaskNotesTableConfig } from "./highGroupTableConfig"
+import { getMessageSendTaskMsgListApi, getSendTaskGroupMsgListApi, GetSendTaskGroupMsgListProps } from "@/pages/weComTask/API/businessPlan/create"
+import SearchBox from "@/pages/weComTask/components/searchBox";
+
+interface Props {
+    corpId: string
+    taskId: number
+}
+/**
+ * 群发记录
+ * @returns 
+ */
+const HighGroupTaskNotes: React.FC<Props> = ({ corpId, taskId }) => {
+
+    /*************************************/
+    const [queryForms, setQueryForms] = useState<GetSendTaskGroupMsgListProps>({ pageNum: 1, pageSize: 20, corpId, sendTimeStartTime: dayjs().format('YYYY-MM-DD'), sendTimeEndTime: dayjs().format('YYYY-MM-DD'), taskId })
+
+    const getMessageSendTaskMsgList = useAjax((params) => getMessageSendTaskMsgListApi(params))
+    /*************************************/
+
+
+    useEffect(() => {
+        setQueryForms({ ...queryForms, corpId, taskId })
+    }, [corpId, taskId])
+
+    useUpdateEffect(() => {
+        if (queryForms.corpId && queryForms.taskId) {
+            getMessageSendTaskMsgList.run(queryForms)
+        }
+    }, [queryForms])
+
+    return <Space direction="vertical" style={{ width: '100%' }}>
+        <SearchBox
+            bodyPadding={1}
+            buttons={<Button onClick={() => getMessageSendTaskMsgList.refresh()} loading={getMessageSendTaskMsgList.loading} icon={<SearchOutlined />}>搜索</Button>}
+        >
+            <>
+                <DatePicker placeholder="发送开始时间" value={queryForms?.sendTimeStartTime ? dayjs(queryForms.sendTimeStartTime) : undefined} onChange={(e, date: any) => { setQueryForms({ ...queryForms, sendTimeStartTime: date }) }} />
+                <DatePicker placeholder="发送结束时间" value={queryForms?.sendTimeEndTime ? dayjs(queryForms.sendTimeEndTime) : undefined} onChange={(e, date: any) => { setQueryForms({ ...queryForms, sendTimeEndTime: date }) }} />
+            </>
+        </SearchBox>
+        {/* 表 */}
+        <Table
+            style={{ marginBottom: 1 }}
+            dataSource={getMessageSendTaskMsgList?.data?.data?.records}
+            loading={getMessageSendTaskMsgList?.loading}
+            columns={groupTaskNotesTableConfig(() => getMessageSendTaskMsgList.refresh())}
+            scroll={{ x: 400, y: 350 }}
+            rowKey={(s) => {
+                return s.id
+            }}
+            size='small'
+            rowClassName={(record) => record?.errCode === 1 ? 'errorClassName' : ''}
+            pagination={{
+                total: getMessageSendTaskMsgList?.data?.data?.total,
+                showTotal: (total) => <Tag color="cyan">总共{total}数据</Tag>,
+                showSizeChanger: true,
+                showLessItems: true,
+                defaultCurrent: 1,
+                defaultPageSize: 200,//默认初始的每页条数
+                current: getMessageSendTaskMsgList?.data?.data?.current || 1,
+                pageSize: getMessageSendTaskMsgList?.data?.data?.size || 20,
+                onChange: (page, pageSize) => {
+                    setQueryForms({ ...queryForms, pageNum: page, pageSize })
+                }
+            }}
+        />
+    </Space>
+}
+
+export default React.memo(HighGroupTaskNotes)

+ 121 - 0
src/pages/weComTask/page/businessPlan/taskList/components/highGroupTask/highGroupXfCorpTabls.tsx

@@ -0,0 +1,121 @@
+import { useAjax } from "@/Hook/useAjax"
+import useNewToken from "@/Hook/useNewToken"
+import { useUpdateEffect } from "ahooks"
+import { App, Badge, Button, DatePicker, Input, Select, Space, Table, Tag } from "antd"
+import React, { useEffect, useState } from "react"
+import { SearchOutlined } from '@ant-design/icons';
+import { highGroupMsgCorpUserListConfig } from "./highGroupTableConfig"
+import { getMessageSendTaskuserListApi, GetSendTaskGroupuserListProps, putMessageUpdateApi } from "@/pages/weComTask/API/businessPlan/create"
+
+
+interface Props {
+    corpId: string
+    taskId: number,
+    weList: any[]
+}
+/**
+ * 群发下发企微号列表
+ * @param param0 
+ * @returns 
+ */
+const HighGroupXfCorpTabls: React.FC<Props> = ({ corpId, taskId, weList = [] }) => {
+
+    /******************************/
+    const { message } = App.useApp()
+    const { token } = useNewToken()
+    const [queryForms, setQueryForms] = useState<GetSendTaskGroupuserListProps>({ pageNum: 1, pageSize: 20, corpId, taskId })
+
+    const getMessageSendTaskuserList = useAjax((params) => getMessageSendTaskuserListApi(params))
+    const putMessageUpdate = useAjax((params) => putMessageUpdateApi(params))
+    /******************************/
+
+    useEffect(() => {
+        setQueryForms({ ...queryForms, corpId, taskId })
+    }, [corpId, taskId])
+
+    useUpdateEffect(() => {
+        if (queryForms.corpId && queryForms.taskId) {
+            getMessageSendTaskuserList.run(queryForms)
+        }
+    }, [queryForms])
+
+    const reload=(id)=>{
+        putMessageUpdate.run(id).then(res=>{
+            console.log(res);
+            getMessageSendTaskuserList.refresh()
+
+        })
+    }
+
+    return <div>
+        <Space style={{ width: '100%' }} wrap>
+            <Input placeholder="消息ID" style={{ width: 120 }} allowClear onChange={(e) => setQueryForms({ ...queryForms, msgId: e.target.value })} />
+            <Select
+                showSearch
+                style={{ minWidth: 100 }}
+                allowClear
+                onChange={(value) => {
+                    setQueryForms({ ...queryForms, status: value })
+                }}
+                value={queryForms?.status}
+                placeholder="发送状态"
+                filterOption={(input, option) =>
+                    ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                }
+                options={[{ label: <Badge status="error" text='未发送' />, value: 0 }, { label: <Badge status="success" text='已发送' />, value: 2 }]}
+            />
+            <Select
+                showSearch
+                style={{ minWidth: 120 }}
+                allowClear
+                value={queryForms.userId}
+                onChange={(valuse) => {
+                    setQueryForms({ ...queryForms, userId: valuse })
+                }}
+                placeholder="企微号"
+                filterOption={(input, option) =>
+                    ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                }
+                options={weList?.map((item: any) => ({ label: item.name, value: item.corpUserId }))}
+
+            />
+            <DatePicker placeholder="发送开始时间" onChange={(e, date) => { setQueryForms({ ...queryForms, sendTimeStartTime: date as string }) }} />
+            <DatePicker placeholder="发送结束时间" onChange={(e, date) => { setQueryForms({ ...queryForms, sendTimeEndTime: date as string }) }} />
+            <DatePicker.RangePicker placeholder={['预计送达时间(起始)', '预计送达时间(结束)']} onChange={(e, date) => { setQueryForms({ ...queryForms, createStartDate: date[0], createEndDate: date[1] }) }} />
+            <Button onClick={() => getMessageSendTaskuserList.refresh()} loading={getMessageSendTaskuserList.loading} icon={<SearchOutlined />}>搜索</Button>
+        </Space>
+
+        {/* 表 */}
+        <Table
+            style={{ marginTop: 10 }}
+            dataSource={getMessageSendTaskuserList?.data?.data?.records}
+            loading={getMessageSendTaskuserList?.loading}
+            columns={highGroupMsgCorpUserListConfig(reload)}
+            scroll={{ x: 400, y: 350 }}
+            rowKey={(s) => {
+                return s.id
+            }}
+            size='small'
+            onRow={(row) => {
+                return !row.status ? {
+                    style: { background: token.colorPrimaryBgHover }
+                } : {}
+            }}
+            pagination={{
+                total: getMessageSendTaskuserList?.data?.data?.total,
+                showTotal: (total) => <Tag color="cyan">总共{total}数据</Tag>,
+                showSizeChanger: true,
+                showLessItems: true,
+                defaultCurrent: 1,
+                defaultPageSize: 200,//默认初始的每页条数
+                current: getMessageSendTaskuserList?.data?.data?.current || 1,
+                pageSize: getMessageSendTaskuserList?.data?.data?.size || 20,
+                onChange: (page, pageSize) => {
+                    setQueryForms({ ...queryForms, pageNum: page, pageSize })
+                }
+            }}
+        />
+    </div>
+}
+
+export default React.memo(HighGroupXfCorpTabls)

+ 278 - 0
src/pages/weComTask/page/businessPlan/taskList/components/highGroupTask/index.tsx

@@ -0,0 +1,278 @@
+import { Card, Popover, Table, Tabs, Tag, Typography } from 'antd';
+import React, { useContext, useState } from 'react';
+import { STATUS_ZJ, TIME_TYPE_ZJ } from '../../../create/const';
+import style from '../../index.less'
+import { QuestionCircleFilled } from '@ant-design/icons';
+import PreviewTime from '@/pages/weComTask/components/previewTime';
+import FilterUserTooltip from '@/pages/weComTask/components/filterUser/filterUserTooltip';
+import LookMsg from '@/pages/weComTask/components/previewMsg/lookMsg';
+import useNewToken from '@/Hook/useNewToken';
+import GroupXfCorpTabls from './highGroupXfCorpTabls';
+import GroupTaskNotes from './highGroupTaskNotes';
+import { getMsgData } from '../../../create/components/massSending/showContent';
+import { DispatchTaskDetails } from '../../log';
+const { Text } = Typography;
+
+
+
+interface Props {
+    groupSendTaskVOList: { [x: string]: any }[]
+}
+
+/**
+ * 高级群发任务详情
+ * @returns 
+ */
+const HighGroupTask: React.FC<Props> = ({ groupSendTaskVOList }) => {
+
+    const { bookPlatForm } = useContext(DispatchTaskDetails)!;
+
+    return <Table
+        dataSource={groupSendTaskVOList}
+        columns={[
+            {
+                title: '任务名称',
+                dataIndex: 'taskName',
+                key: 'taskName',
+                width: 100,
+                ellipsis: true,
+                align: 'center'
+            },
+            {
+                title: '企业ID',
+                dataIndex: 'corpId',
+                key: 'corpId',
+                width: 100,
+                ellipsis: true,
+                align: 'center'
+            },
+            {
+                title: '客服号',
+                dataIndex: 'corpUserMap',
+                key: 'corpUserMap',
+                width: 100,
+                ellipsis: true,
+                align: 'center',
+                render(value) {
+                    return value ? Object.keys(value).map(key => value[key]).join(',') : ''
+                },
+            },
+            {
+                title: '群聊所在主体',
+                dataIndex: 'corpList',
+                key: 'corpListCorpId',
+                width: 120,
+                ellipsis: true,
+                align: 'center',
+                render(value) {
+                    return value?.map(item => item.corpName + `(${item.corpId})`).join(',')
+                },
+            },
+            {
+                title: '消息发送号外部联系人',
+                dataIndex: 'corpList',
+                key: 'corpListExternalUserId',
+                width: 120,
+                ellipsis: true,
+                align: 'center',
+                render(value) {
+                    return value?.map(item => item.name + `(${item.externalUserId})`).join(',')
+                },
+            },
+            {
+                title: '状态',
+                dataIndex: 'status',
+                key: 'status',
+                width: 100,
+                ellipsis: true,
+                align: 'center',
+                render(value) {
+                    return STATUS_ZJ[value] || '--'
+                },
+            },
+            {
+                title: '执行时间',
+                dataIndex: 'timeRepeatType',
+                key: 'timeRepeatType',
+                width: 100,
+                ellipsis: true,
+                align: 'center',
+                render(value, records: any) {
+                    return <>
+                        {TIME_TYPE_ZJ[value] || '--'}
+                        {value !== 'TIME_TYPE_SINGLE_TIMELY' && <Popover
+                            placement="left"
+                            content={<div>
+                                <PreviewTime
+                                    {...records}
+                                />
+                            </div>}
+                            styles={{ body: { width: 300, overflow: 'hidden', overflowY: 'auto', maxHeight: 400 } }}
+                        >
+                            <a style={{ color: '#000' }}><QuestionCircleFilled /></a>
+                        </Popover>}
+                    </>
+                },
+            },
+            {
+                title: '发送对象',
+                dataIndex: 'externalUserFilter',
+                key: 'externalUserFilter',
+                width: 100,
+                align: 'center',
+                render(value) {
+                    return value ? <div className={style.nameBox}>
+                        <div>
+                            <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}
+                            />}
+                        >
+                            <a style={{ color: '#000' }}><QuestionCircleFilled /></a>
+                        </Popover>
+                    </div> : '全部'
+                },
+            },
+            {
+                title: '投手',
+                dataIndex: 'putUserName',
+                key: 'putUserName',
+                width: 100,
+                ellipsis: true,
+                align: 'center'
+            },
+            {
+                title: '创建时间',
+                dataIndex: 'createTime',
+                key: 'createTime',
+                width: 135,
+                ellipsis: true,
+                align: 'center'
+            },
+        ]}
+        scroll={{ x: 1000, y: 700 }}
+        rowKey={'id'}
+        size='small'
+        bordered
+        pagination={{
+            total: groupSendTaskVOList?.length || 0,
+            showTotal: (total) => <Tag color="cyan">总共{total}数据</Tag>,
+            showSizeChanger: true,
+            showLessItems: true,
+            defaultPageSize: 20
+        }}
+        expandable={{
+            fixed: 'left',
+            expandRowByClick: true,
+            expandedRowRender: (record) => <ExpandedRow record={record} />
+        }}
+    />
+};
+
+export const ExpandedRow: React.FC<{ record: any }> = ({ record }) => {
+
+    const { contentDTO, id, corpId } = record
+    const [activeKey, setActiveKey] = useState<string>('2')
+    const { corpList } = useContext(DispatchTaskDetails)!;
+
+    const data = contentDTO.map(item2 => {
+        const { msgType, ...content } = item2
+        if (msgType === 'TASK_STATUS_MINIPROGRAM') {
+            const { miniprogram: { appId, page, title, picUrl } } = content
+            return [{
+                miniprogramAppid: appId,
+                miniprogramPage: page,
+                miniprogramTitle: title,
+                miniprogramPicurl: picUrl,
+                mediaType: 'miniprogram'
+            }]
+        } else if (msgType === 'TASK_CONTENT_TEXT') {
+            const { text } = content
+            return [{
+                textContent: text?.content,
+                mediaType: 'text'
+            }]
+        } else if (msgType === 'TASK_CONTENT_LINK') {
+            const { link: { desc, picUrl, title, url } } = content
+            return [{
+                linkDesc: desc,
+                linkPicurl: picUrl,
+                linkTitle: title,
+                linkUrl: url,
+                mediaType: 'link'
+            }]
+        } else if (msgType === 'TASK_CONTENT_IMAGE') {
+            const { image: { picUrl } } = content
+            return [{
+                imageUrl: picUrl,
+                mediaType: 'image'
+            }]
+        } else if (msgType === 'TASK_STATUS_VIDEO') {
+            const { video: { videoUrl } } = content
+            return [{
+                videoUrl: videoUrl,
+                mediaType: 'video'
+            }]
+        } else if (msgType === 'TASK_STATUS_FILE') {
+            const { file: { fileUrl } } = content
+            return [{
+                fileUrl: fileUrl,
+                mediaType: 'file'
+            }]
+        }
+        return {
+            msgType,
+            content
+        }
+    })
+
+    return <div style={{ display: 'flex', gap: 10, maxHeight: 450 }}>
+        <div style={{ width: 320, maxHeight: 450, overflow: 'hidden', overflowY: 'auto' }}>
+            <PreviewTime
+                {...record}
+            />
+            <h2>群发内容</h2>
+            {data.map((item, key) => <div key={key} style={{ marginTop: 5, backgroundColor: '#d9d9d9', borderRadius: 6, padding: '10px 10px 16px' }}>
+                <h3 style={{ margin: '0 0 5px 0', textAlign: 'center' }}>内容{key + 1}</h3>
+                <LookMsg data={item} />
+            </div>)}
+        </div>
+        <div style={{ flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column', gap: 10, height: '100%' }}>
+            <Tabs
+                destroyInactiveTabPane={false}
+                onChange={(key) => {
+                    setActiveKey(key)
+                }}
+                activeKey={activeKey}
+                type="card"
+                className="aaa"
+                items={[
+                    {
+                        key: '2',
+                        label: '下发企微号',
+                        children: <Card styles={{ body: { maxHeight: 400 } }}>
+                            {activeKey == '2' && <GroupXfCorpTabls corpId={corpId} taskId={id} weList={corpList?.find(item => item.corpId == corpId)?.weList} />}
+                        </Card>
+                    },
+                    {
+                        key: '4',
+                        label: '群发记录',
+                        children: <Card styles={{ body: { minHeight: 185 } }}>
+                            {activeKey == '4' && <GroupTaskNotes corpId={corpId} taskId={id} />}
+                        </Card>
+                    }
+                ]}
+            />
+        </div>
+    </div>
+}
+
+
+export default React.memo(HighGroupTask);

+ 2 - 2
src/pages/weComTask/page/businessPlan/taskList/welcomeTask/index.tsx → src/pages/weComTask/page/businessPlan/taskList/components/welcomeTask/index.tsx

@@ -1,10 +1,10 @@
 import { Card, Descriptions, Table, Tabs, Tag, Typography } from 'antd';
 import React, { useContext, useEffect, useState } from 'react';
-import { businessPlanData } from '../../create/const';
+import { businessPlanData } from '../../../create/const';
 import LookMsg from '@/pages/weComTask/components/previewMsg/lookMsg';
 import { api_post_welcomeMsg_corpExternalUserList, api_post_welcomeMsg_welcomeMsgCorpUserList, WelcomeMsgCorpUserList } from '@/pages/weComTask/API/businessPlan/create';
 import { useAjax } from '@/Hook/useAjax';
-import { DispatchTaskDetails } from '../log';
+import { DispatchTaskDetails } from '../../log';
 import { corpExternalUserListConfig, WelcomeMsgCorpUserListConfig } from './welcomeTableConfig';
 
 

+ 0 - 0
src/pages/weComTask/page/businessPlan/taskList/welcomeTask/welcomeTableConfig.tsx → src/pages/weComTask/page/businessPlan/taskList/components/welcomeTask/welcomeTableConfig.tsx


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

@@ -2,9 +2,10 @@ import { useAjax } from '@/Hook/useAjax';
 import { getProjectLogListApi } from '@/pages/weComTask/API/businessPlan/create';
 import { Drawer, Spin, Tabs } from 'antd';
 import React, { useEffect, useState } from 'react';
-import WelcomeTask from './welcomeTask';
-import GroupTask from './groupTask';
-import ExternalUserTransferTask from './externalUserTransferTask';
+import WelcomeTask from './components/welcomeTask';
+import ExternalUserTransferTask from './components/externalUserTransferTask';
+import GroupTask from './components/groupTask';
+import HighGroupTask from './components/highGroupTask';
 
 
 interface Props {
@@ -53,13 +54,15 @@ const Log: React.FC<Props> = ({ data, bookPlatForm, bookList, corpList, visible,
                 <Tabs
                     items={Object.keys(previewData).filter(key => key === 'welcomeMsgTemplateVO' ? previewData[key] : previewData[key]?.length).map(key => ({
                         key: key,
-                        label: { 'externalUserTransferTasksVOList': '客户继承', 'groupSendTaskVOList': '客户群发', 'welcomeMsgTemplateVO': '欢迎语' }[key],
+                        label: { 'externalUserTransferTasksVOList': '客户继承', 'groupSendTaskVOList': '客户群发', 'welcomeMsgTemplateVO': '欢迎语', 'messageSendTaskVOS': '高级群发' }[key],
                         children: key === 'externalUserTransferTasksVOList' ? <>
                             <ExternalUserTransferTask externalUserTransferTasksVOList={previewData[key]} />
                         </> : key === 'groupSendTaskVOList' ? <>
                             <GroupTask groupSendTaskVOList={previewData[key]} />
                         </> : key === 'welcomeMsgTemplateVO' ? <>
                             <WelcomeTask welcomeMsgTemplateVO={previewData[key]} />
+                        </> : key === 'messageSendTaskVOS' ? <>
+                            <HighGroupTask groupSendTaskVOList={previewData[key]} />
                         </> : undefined
                     }))}
                 />

+ 48 - 4
src/pages/weComTask/page/businessPlan/taskList/tableConfig.tsx

@@ -3,11 +3,11 @@ import { AnyObject } from "antd/es/_util/type";
 import { ColumnsType } from "antd/es/table";
 import style from './index.less'
 import { QuestionCircleFilled } from '@ant-design/icons';
-import PreviewWelcome from "../create/welcome/previewWelcome";
 import { restoreGroupData, restoreUserInheritData } from "../create/const";
-import PreviewUserInherit from "../create/userInherit/previewUserlnherit";
-import PreviewMassSendingStrategy from "../create/massSending/previewMassSendingStrategy";
-import ShowContent from "../create/massSending/showContent";
+import PreviewUserInherit from "../create/components/userInherit/previewUserlnherit";
+import PreviewMassSendingStrategy from "../create/components/massSending/previewMassSendingStrategy";
+import ShowContent from "../create/components/massSending/showContent";
+import PreviewWelcome from "../create/components/welcome/previewWelcome";
 
 const { Text, Paragraph } = Typography;
 const taskListColumns = (
@@ -115,6 +115,50 @@ const taskListColumns = (
                 return <Text type="danger">当前没有群发配置</Text>
             }
         },
+        {
+            title: '高级群发配置',
+            dataIndex: 'messageSendTaskAddDTO',
+            key: 'messageSendTaskAddDTO',
+            width: 170,
+            ellipsis: true,
+            render: (value, record) => {
+                if (value && Object.keys(value)?.length > 0) {
+                    const data = restoreGroupData(value, 'GROUP_GROUP')
+                    console.log('data', record.projectName, data)
+                    return <div className={style.nameBox}>
+                        <div>
+                            <Text ellipsis>{value?.groupSendName || '<空>'}</Text>
+                        </div>
+                        <Popover
+                            placement="left"
+                            content={<div>
+                                <PreviewMassSendingStrategy
+                                    massSendingStrategy={data.massSendingStrategy}
+                                    configType='GROUP_GROUP'
+                                />
+                            </div>}
+                            styles={{ body: { width: 360, overflow: 'hidden', overflowY: 'auto', maxHeight: 400 } }}
+                        >
+                            <a><QuestionCircleFilled /></a>
+                        </Popover>
+                        <Popover
+                            placement="left"
+                            content={<div>
+                                <ShowContent
+                                    strategySettings={data?.massSendingStrategy?.strategySettings}
+                                    massSendingContent={data?.massSendingContent}
+                                    type="highQf"
+                                />
+                            </div>}
+                            styles={{ body: { width: 360, overflow: 'hidden', overflowY: 'auto', maxHeight: 400 } }}
+                        >
+                            <a><QuestionCircleFilled /></a>
+                        </Popover>
+                    </div>
+                }
+                return <Text type="danger">当前没有群发配置</Text>
+            }
+        },
         {
             title: '客户继承配置',
             dataIndex: 'externalUserTransferTasksDTO',

+ 1 - 1
src/pages/weComTask/page/corpUserAssign/index.tsx

@@ -125,7 +125,7 @@ const CorpUserAssign: React.FC = () => {
                 return s.corpId
             }}
             loading={getCorpList.loading}
-            size='small'
+            bordered
             pagination={{
                 total: getCorpList.data?.data?.total,
                 showTotal: (total) => <Tag color="cyan">总共{total}数据</Tag>,

+ 22 - 12
src/pages/weComTask/page/corpUserAssign/tableConfig.tsx

@@ -10,8 +10,7 @@ export function TableConfig(handle: (data: any, type: string) => void): ColumnsT
             title: '企微名称',
             dataIndex: 'corpName',
             key: 'corpName',
-            align: 'center',
-            width: 80,
+            width: 150,
             ellipsis: true,
             fixed: 'left'
         },
@@ -34,6 +33,9 @@ export function TableConfig(handle: (data: any, type: string) => void): ColumnsT
             align: 'center',
             width: 125,
             ellipsis: true,
+            render(value) {
+                return value || '--'
+            },
         },
         {
             title: '用户规模',
@@ -41,15 +43,18 @@ export function TableConfig(handle: (data: any, type: string) => void): ColumnsT
             key: 'corpUserMax',
             align: 'center',
             width: 100,
+            render(value) {
+                return value && value === 0 ? value : '--'
+            },
         },
         {
             title: '授权方企业类型',
             dataIndex: 'corpType',
             key: 'corpType',
             align: 'center',
-            width: 80,
+            width: 60,
             render: (a: string) => {
-                return <span>{{ verified: '认证号', unverified: '注册号' }[a]}</span>
+                return <span>{{ verified: '认证号', unverified: '注册号' }[a] || '--'}</span>
             }
         },
         {
@@ -57,14 +62,17 @@ export function TableConfig(handle: (data: any, type: string) => void): ColumnsT
             dataIndex: 'verifiedEndTime',
             key: 'verifiedEndTime',
             align: 'center',
-            width: 120
+            width: 120,
+            render(value) {
+                return value || '--'
+            },
         },
         {
             title: '授权状态',
             dataIndex: 'authStatus',
             key: 'authStatus',
             align: 'center',
-            width: 120,
+            width: 80,
             render: (a: any) => {
                 return <span>{a === 1 ? <Badge status="success" text='成功授权' /> : <Badge status="error" text='取消授权' />}</span>
             }
@@ -86,7 +94,7 @@ export function TableConfig(handle: (data: any, type: string) => void): ColumnsT
             width: 150,
             ellipsis: true,
             render: (a: any) => {
-                return <a onClick={() => copy(a)}>{a}</a>
+                return <a onClick={() => copy(a)}>{a || '--'}</a>
             }
         },
         {
@@ -96,7 +104,7 @@ export function TableConfig(handle: (data: any, type: string) => void): ColumnsT
             width: 150,
             ellipsis: true,
             render: (a: any) => {
-                return <a onClick={() => copy(a)}>{a}</a>
+                return <a onClick={() => copy(a)}>{a || '--'}</a>
             }
         },
         {
@@ -106,7 +114,7 @@ export function TableConfig(handle: (data: any, type: string) => void): ColumnsT
             width: 150,
             ellipsis: true,
             render: (a: any) => {
-                return <a onClick={() => copy(a)}>{a}</a>
+                return <a onClick={() => copy(a)}>{a || '--'}</a>
             }
         },
         {
@@ -121,16 +129,18 @@ export function TableConfig(handle: (data: any, type: string) => void): ColumnsT
             title: '备注',
             dataIndex: 'remark',
             key: 'remark',
-            align: 'center',
-            width: 150,
+            width: 200,
             ellipsis: true,
+            render(value) {
+                return value || '--'
+            },
         },
         {
             title: '操作',
             dataIndex: 'cz',
             key: 'cz',
             align: 'center',
-            width: 150,
+            width: 100,
             fixed: 'right',
             render: (a, b) => {
                 return <Space wrap size={'small'}>

+ 1 - 1
src/pages/weComTask/page/corpUserManage/tableConfig.tsx

@@ -18,7 +18,7 @@ export function WeTableConfig(
             dataIndex: 'corpUserId',
             key: 'corpUserId',
             align: 'center',
-            width: 80,
+            width: 150,
             ellipsis: true,
             fixed: 'left',
             render: (a: string) => {