wjx há 2 dias atrás
pai
commit
1d2c066f0a
27 ficheiros alterados com 2978 adições e 357 exclusões
  1. 4 1
      src/layout/index.tsx
  2. 48 0
      src/pages/weComTask/API/alarmGl/index.ts
  3. 29 0
      src/pages/weComTask/API/businessPlan/create.ts
  4. 2 0
      src/pages/weComTask/API/global.ts
  5. 69 0
      src/pages/weComTask/page/alarmGl/details.tsx
  6. 162 0
      src/pages/weComTask/page/alarmGl/index.tsx
  7. 164 0
      src/pages/weComTask/page/alarmGl/tableConfig.tsx
  8. 1 354
      src/pages/weComTask/page/businessPlan/create/components/massSending/index.tsx
  9. 1 1
      src/pages/weComTask/page/businessPlan/create/index.tsx
  10. 1 1
      src/pages/weComTask/page/businessPlan/taskList/index.tsx
  11. 70 0
      src/pages/weComTask/page/moments/create/components/Strategy/index.tsx
  12. 98 0
      src/pages/weComTask/page/moments/create/components/Strategy/previewMomentsStrategy.tsx
  13. 249 0
      src/pages/weComTask/page/moments/create/components/Strategy/settingsStrategy.tsx
  14. 73 0
      src/pages/weComTask/page/moments/create/components/content/index.tsx
  15. 57 0
      src/pages/weComTask/page/moments/create/components/content/previewMomentsContent.tsx
  16. 219 0
      src/pages/weComTask/page/moments/create/components/content/settingsContent.tsx
  17. 523 0
      src/pages/weComTask/page/moments/create/index.tsx
  18. 212 0
      src/pages/weComTask/page/moments/create/tableConfig.tsx
  19. 30 0
      src/pages/weComTask/page/moments/create/typings.d.ts
  20. 229 0
      src/pages/weComTask/page/moments/taskList/index.tsx
  21. 231 0
      src/pages/weComTask/page/moments/taskList/log.tsx
  22. 72 0
      src/pages/weComTask/page/moments/taskList/momentsTaskNotes.tsx
  23. 65 0
      src/pages/weComTask/page/moments/taskList/momentsXfCorpTabls.tsx
  24. 127 0
      src/pages/weComTask/page/moments/taskList/tableConfig.tsx
  25. 213 0
      src/pages/weComTask/page/moments/taskList/weTableConfig.tsx
  26. 29 0
      src/pages/weComTask/page/weAssociation/list/index.tsx
  27. 0 0
      src/public/svg/moments.svg

+ 4 - 1
src/layout/index.tsx

@@ -1,6 +1,6 @@
 import { SetStateAction, useCallback, useEffect, useState } from 'react'
 import { Layout, Menu, Space, ConfigProvider, Watermark, App, message } from 'antd';
-import { DesktopOutlined, MessageOutlined, SendOutlined, CloudOutlined, TeamOutlined, HomeOutlined, PaperClipOutlined, ContainerOutlined, AlipayCircleOutlined, StopOutlined, QrcodeOutlined, DatabaseOutlined, ReadOutlined, MobileOutlined, FundViewOutlined, RadarChartOutlined, BarChartOutlined, WechatOutlined, BookOutlined, FileImageOutlined, EyeOutlined, UserOutlined } from '@ant-design/icons';
+import { DesktopOutlined, MessageOutlined, SendOutlined, CloudOutlined, TeamOutlined, HomeOutlined, PaperClipOutlined, ContainerOutlined, AlipayCircleOutlined, StopOutlined, QrcodeOutlined, DatabaseOutlined, ReadOutlined, MobileOutlined, FundViewOutlined, RadarChartOutlined, BarChartOutlined, WechatOutlined, BookOutlined, FileImageOutlined, EyeOutlined, UserOutlined, AlertOutlined } from '@ant-design/icons';
 import { ReactComponent as LaunchSvg } from '../public/svg/launch.svg'
 import { ReactComponent as AdLaunchSvg } from '../public/svg/adLaunch.svg'
 import { ReactComponent as MaterialSvg } from '../public/svg/material.svg'
@@ -25,6 +25,7 @@ import { ReactComponent as ImageSvg } from '../public/svg/image.svg'
 import { ReactComponent as BooKSvg } from '../public/svg/book.svg'
 import { ReactComponent as PaSvg } from '../public/svg/pa.svg'
 import { ReactComponent as GroupChatSendSvg } from '../public/svg/groupChatSend.svg'
+import { ReactComponent as MomentsSvg } from '../public/svg/moments.svg'
 import Avatar from './AvatarDropdown';
 import styles from './index.less'
 import { useLocation, useNavigate } from 'react-router-dom';
@@ -90,8 +91,10 @@ const IconMap = {
     book: <span role="img" aria-label="fund-view" className="anticon anticon-fund-view"><BooKSvg /></span>,
     pa: <span role="img" aria-label="fund-view" className="anticon anticon-fund-view"><PaSvg /></span>,
     groupChatSend: <span role="img" aria-label="fund-view" className="anticon anticon-fund-view"><GroupChatSendSvg /></span>,
+    moments: <span role="img" aria-label="fund-view" className="anticon anticon-fund-view"><MomentsSvg /></span>,
     interdiction: <StopOutlined />,
     eye: <EyeOutlined />,
+    alert: <AlertOutlined />,
     log: <ContainerOutlined />
 };
 

+ 48 - 0
src/pages/weComTask/API/alarmGl/index.ts

@@ -0,0 +1,48 @@
+import request from "@/utils/request";
+
+export interface GetCorpPhoneMsgAlarmListProps {
+    pageNum: number,
+    pageSize: number,
+    sysUserId?: number,
+    corpUserId?: string,
+    corpId?: number,
+}
+/**
+ * 获取跳转链接列表
+ * @returns 
+ */
+export async function getCorpPhoneMsgAlarmListApi(data: GetCorpPhoneMsgAlarmListProps) {
+    return request({
+        url: `/corpPhoneMsgAlarm/list`,
+        method: 'POST',
+        data
+    })
+}
+
+
+/**
+ * 告警详情
+ * @param data 
+ * @returns 
+ */
+export async function getCorpPhoneMsgAlarmDetailsListApi(data: { pageNum: number, pageSize: number, alarmId: number }) {
+    return request({
+        url: `/corpPhoneMsgAlarm/getInfo`,
+        method: 'POST',
+        data
+    })
+}
+
+
+/**
+ * 处理告警
+ * @param id 
+ * @returns 
+ */
+export async function updateCorpPhoneMsgAlarmApi(ids: number[]) {
+    return request({
+        url: `/corpPhoneMsgAlarm/handle`,
+        method: 'POST',
+        data: ids
+    })
+}

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

@@ -398,4 +398,33 @@ export async function getSendLogApi(data: any) {
         method: 'POST',
         data
     })
+}
+
+
+
+/**
+ * 朋友圈下发企微号列表
+ * @param data 
+ * @returns 
+ */
+export async function getMomentsCorpUserListApi(data: { pageNum: number, pageSize: number, corpMomentId: number }) {
+    return request({
+        url: `/corp/moment/corpUserList`,
+        method: 'POST',
+        data
+    })
+}
+
+
+/**
+ * 朋友圈发送记录
+ * @param data 
+ * @returns 
+ */
+export async function getMomentsSendLogApi(data: { pageNum: number, pageSize: number, corpMomentId: number }) {
+    return request({
+        url: `/corp/moment/jobList`,
+        method: 'POST',
+        data
+    })
 }

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

@@ -87,6 +87,8 @@ export interface ApiParamsChatListProps {
     userCountMin?: string | number,//群人数最小值
     chatIdList?: number[],
     remark?: string
+    mpAccountIdIsNull?: boolean,
+    mpAccountId?: number,
 }
 
 /**

+ 69 - 0
src/pages/weComTask/page/alarmGl/details.tsx

@@ -0,0 +1,69 @@
+import { useAjax } from '@/Hook/useAjax';
+import { Button, Modal, Table, Tag } from 'antd';
+import React, { useEffect, useState } from 'react';
+import { DetailsTableConfig } from './tableConfig';
+import { getCorpPhoneMsgAlarmDetailsListApi } from '../../API/alarmGl';
+
+interface AlarmGlDetailsProps {
+    data: any;
+}
+
+/**
+ * 详情
+ * @param param0 
+ * @returns 
+ */
+const Details: React.FC<AlarmGlDetailsProps> = ({ data }) => {
+
+    /****************************************/
+    const [visible, setVisible] = useState<boolean>(false)
+    const [queryForm, setQueryForm] = useState<{ pageNum: number, pageSize: number }>({ pageNum: 1, pageSize: 20 })
+
+    const getCorpPhoneMsgAlarmDetailsList = useAjax((params) => getCorpPhoneMsgAlarmDetailsListApi(params))
+    /****************************************/
+
+    useEffect(() => {
+        if (visible) {
+            getCorpPhoneMsgAlarmDetailsList.run({ ...queryForm, alarmId: data.id })
+        }
+    }, [visible, data, queryForm])
+
+    return <>
+        <Button type="link" style={{ padding: 0 }} onClick={() => setVisible(true)}>详情</Button>
+
+        {visible && <Modal
+            title="告警详情"
+            open={visible}
+            onCancel={() => setVisible(false)}
+            footer={null}
+            width={600}
+        >
+            <Table
+                style={{ marginBottom: 1 }}
+                dataSource={getCorpPhoneMsgAlarmDetailsList?.data?.data?.records}
+                loading={getCorpPhoneMsgAlarmDetailsList?.loading}
+                columns={DetailsTableConfig()}
+                scroll={{ y: 500 }}
+                rowKey={(s) => {
+                    return s.id
+                }}
+                size='small'
+                pagination={{
+                    total: getCorpPhoneMsgAlarmDetailsList?.data?.data?.total,
+                    showTotal: (total) => <Tag color="cyan">总共{total}数据</Tag>,
+                    showSizeChanger: true,
+                    showLessItems: true,
+                    defaultCurrent: 1,
+                    defaultPageSize: 20,//默认初始的每页条数
+                    current: getCorpPhoneMsgAlarmDetailsList?.data?.data?.current || 1,
+                    pageSize: getCorpPhoneMsgAlarmDetailsList?.data?.data?.size || 20,
+                    onChange: (page, pageSize) => {
+                        setQueryForm({ ...queryForm, pageNum: page, pageSize })
+                    }
+                }}
+            />
+        </Modal>}
+    </>;
+};
+
+export default React.memo(Details);

+ 162 - 0
src/pages/weComTask/page/alarmGl/index.tsx

@@ -0,0 +1,162 @@
+import { api_corpUser_allOfUser, getAdAccountAllOfMember } from '@/API/global';
+import { useAjax } from '@/Hook/useAjax';
+import React, { useEffect, useRef, useState } from 'react';
+import { getCorpPhoneMsgAlarmListApi, GetCorpPhoneMsgAlarmListProps, updateCorpPhoneMsgAlarmApi } from '../../API/alarmGl';
+import { MenuUnfoldOutlined, MenuFoldOutlined, SearchOutlined } from '@ant-design/icons';
+import style from '../corpUserManage/index.less'
+import { App, Button, Card, Pagination, Popconfirm, Select, Table, Tabs, Tag } from 'antd';
+import TeamMembers from '@/components/Team/teamMembers';
+import SearchBox from '../../components/searchBox';
+import { TableConfig } from './tableConfig';
+import { useSize } from 'ahooks';
+
+const AlarmGl: React.FC = () => {
+
+    /**************************************/
+    const { message } = App.useApp()
+    const ref = useRef<HTMLDivElement>(null)
+    const size = useSize(ref)
+    const userIdStr = sessionStorage.getItem('userId');
+    const [userId, setUserId] = useState<number>(userIdStr ? Number(userIdStr) : undefined);
+    const [queryForm, setQueryForm] = useState<GetCorpPhoneMsgAlarmListProps>({ pageNum: 1, pageSize: 20 })
+    const [activeCorp, setActiveCorp] = useState<any>()
+    const [editSelectedRow, setEditSelectedRow] = useState<any[]>([])
+
+    const corpUser_allOfUser = useAjax((params) => api_corpUser_allOfUser(params))
+    const getCorpPhoneMsgAlarmList = useAjax((params) => getCorpPhoneMsgAlarmListApi(params))
+    const allOfMember = useAjax(() => getAdAccountAllOfMember())
+    const updateCorpPhoneMsgAlarm = useAjax((params) => updateCorpPhoneMsgAlarmApi(params))
+    /**************************************/
+
+    useEffect(() => {
+        allOfMember.run()
+    }, [])
+
+    useEffect(() => {
+        if (userId) {
+            corpUser_allOfUser.run(userId)
+        }
+    }, [userId])
+
+    useEffect(() => {
+        getCorpPhoneMsgAlarmList.run({ ...queryForm, sysUserId: userId })
+    }, [queryForm, userId])
+
+    const handleMsg = (ids: number[]) => {
+        updateCorpPhoneMsgAlarm.run(ids).then(res => {
+            if (res) {
+                message.success('处理成功')
+                getCorpPhoneMsgAlarmList.refresh()
+                setEditSelectedRow([])
+            }
+        })
+    }
+
+    return (
+        <div className={style.corpUserManage}>
+            <div className={style.corpUserManage_bottom}>
+                <Card className={style.corpUserList} styles={{ body: { padding: 0, display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' } }}>
+                    <SearchBox
+                        bodyPadding={`16px 16px 12px`}
+                        buttons={<>
+                            <Button type="primary" onClick={() => { }} icon={<SearchOutlined />}>搜索</Button>
+                            <Popconfirm
+                                title="确定处理告警?"
+                                onConfirm={() => { handleMsg(editSelectedRow.map(item => item.id)) }}
+                                disabled={editSelectedRow.length === 0}
+                            >
+                                <Button type='primary' danger disabled={editSelectedRow.length === 0}>处理告警</Button>
+                            </Popconfirm>
+                        </>}
+                    >
+                        <>
+                            <Select
+                                style={{ width: 180 }}
+                                placeholder="选择企业"
+                                value={queryForm?.corpId}
+                                showSearch
+                                maxTagCount={1}
+                                allowClear
+                                onChange={(e, option: any) => {
+                                    setQueryForm({ ...queryForm, corpId: e, pageNum: 1 })
+                                    setActiveCorp(option?.data)
+                                }}
+                                filterOption={(input: string, option?: { label: string; value: string }) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())}
+                                options={corpUser_allOfUser?.data?.data?.map((item: { t1: any }) => ({ label: item.t1?.corpName, value: item.t1?.corpId, data: item }))}
+                            />
+                            {activeCorp && <Select
+                                style={{ minWidth: 125 }}
+                                placeholder='企微号'
+                                allowClear
+                                autoClearSearchValue={false}
+                                onChange={(e) => setQueryForm({ ...queryForm, corpUserId: e, pageNum: 1 })}
+                                value={queryForm?.corpUserId}
+                                filterOption={(input, option) =>
+                                    ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                                }
+                                options={activeCorp?.t2?.map((item: any) => ({ label: item.name, value: item.corpUserId }))}
+                            />}
+                        </>
+                    </SearchBox>
+
+                    <div className={style.corpUserList_table} ref={ref}>
+                        <Table
+                            style={{ marginBottom: 1 }}
+                            dataSource={getCorpPhoneMsgAlarmList?.data?.data?.records}
+                            loading={getCorpPhoneMsgAlarmList?.loading}
+                            columns={TableConfig(handleMsg)}
+                            scroll={{ y: size?.height && ref.current ? size?.height - ref.current.querySelector('.ant-table-thead').clientHeight - 38 : 300 }}
+                            rowKey={(s) => {
+                                return s.id
+                            }}
+                            size='small'
+                            pagination={false}
+                            rowSelection={{
+                                type: 'checkbox',
+                                selectedRowKeys: editSelectedRow?.map(item => item.id),
+                                getCheckboxProps: (record: any) => {
+                                    return {
+                                        disabled: record?.status
+                                    }
+                                },
+                                onSelect: (record: { id: any; }, selected: any) => {
+                                    if (selected) {
+                                        setEditSelectedRow([...editSelectedRow, record])
+                                    } else {
+                                        setEditSelectedRow(editSelectedRow?.filter(item => item.id !== record.id))
+                                    }
+                                },
+                                onSelectAll: (selected: any, rows: any, changeRows: any[]) => {
+                                    if (selected) {
+                                        setEditSelectedRow([...editSelectedRow, ...changeRows])
+                                    } else {
+                                        let newArr = editSelectedRow?.filter(item => changeRows.every(i => i?.id !== item?.id))
+                                        setEditSelectedRow(newArr)
+                                    }
+                                }
+                            }}
+                        />
+                    </div>
+                    <div className={style.corpUserList_footer}>
+                        <Pagination
+                            size="small"
+                            total={getCorpPhoneMsgAlarmList?.data?.data?.total || 0}
+                            showSizeChanger
+                            showQuickJumper
+                            pageSize={getCorpPhoneMsgAlarmList?.data?.data?.size || 20}
+                            current={getCorpPhoneMsgAlarmList?.data?.data?.current || 1}
+                            onChange={(page: number, pageSize: number) => {
+                                ref.current?.scrollTo({ top: 0 })
+                                setQueryForm({ ...queryForm, pageNum: page, pageSize })
+                            }}
+                            showTotal={(total: number) => <span style={{ fontSize: 12 }}>共 {total} 条</span>}
+                        />
+                    </div>
+
+                </Card>
+            </div>
+        </div>
+    );
+};
+
+export default AlarmGl;

+ 164 - 0
src/pages/weComTask/page/alarmGl/tableConfig.tsx

@@ -0,0 +1,164 @@
+import { Badge, Button, Popconfirm, Space, Typography} from "antd";
+import { ColumnsType } from "antd/es/table";
+import { tableDfixed } from "@/utils/tableDfixed";
+import Details from "./details";
+
+let obj: any = {
+    "send_corp_group_send": "群发",
+    "send_corp_moment_send": "朋友圈发送",
+    "send_corp_pull_group": "拉群任务",
+    "send_corp_pull_group_otherA": "转移客户——拉客户",
+    "send_corp_pull_group_otherB": "转移客户——拉群",
+    "send_corp_pull_group_task": "建群任务",
+    "send_corp_pull_group_transfer_task": "进群拉人",
+    "send_delete_friend_task": "删除单向好友",
+    "send_pull_external_user_task": "拉客户",
+    "send_pull_fix_user_task": "拉固定客户",
+    "send_pull_corp_user_task": "拉成员",
+    "send_user_msg": "聚合聊天消息",
+    "send_update": "同步企微信息",
+    "send_disband_group_chat":"群解散",
+    "forward_corp_group_send":"转发消息"
+}
+
+export function TableConfig(handleMsg: (ids: number[]) => void): ColumnsType<any> {
+    let arr: ColumnsType<any> = [
+        {
+            title: '企业名称',
+            dataIndex: 'corpName',
+            key: 'corpName',
+            width: 150,
+            ellipsis: true,
+            render(_, records) {
+                return records?.corpUserVO?.corpName
+            },
+        },
+        {
+            title: '企业ID',
+            dataIndex: 'corpId',
+            key: 'corpId',
+            width: 150,
+            ellipsis: true,
+            render(value) {
+                return value
+            },
+        },
+        {
+            title: '企微号ID',
+            dataIndex: 'name',
+            key: 'name',
+            width: 120,
+            ellipsis: true,
+            render(_, records) {
+                return records?.corpUserVO?.name
+            },
+        },
+        {
+            title: '企微号ID',
+            dataIndex: 'corpUserId',
+            key: 'corpUserId',
+            width: 150,
+            ellipsis: true,
+            render(value) {
+                return <Typography.Text ellipsis copyable>{value}</Typography.Text>
+            },
+        },
+        {
+            title: '设备编号',
+            dataIndex: 'phoneUuid',
+            key: 'phoneUuid',
+            width: 150,
+            ellipsis: true,
+            render(value) {
+                return value
+            },
+        },
+        {
+            title: '状态',
+            dataIndex: 'status',
+            key: 'status',
+            width: 150,
+            render(value) {
+                return value ? <Badge status="success" text="已处理" /> : <Badge status="error" text="未处理" />
+            },
+        },
+        {
+            title: '报警时间',
+            dataIndex: 'alarmTime',
+            key: 'alarmTime',
+            align: 'center',
+            width: 120,
+            ellipsis: true,
+        },
+        {
+            title: '处理人',
+            dataIndex: 'handlerByVO',
+            key: 'handlerByVO',
+            align: 'center',
+            width: 100,
+            ellipsis: true,
+            render(value) {
+                return value?.nickname || '--'
+            }
+        },
+        {
+            title: '操作',
+            dataIndex: 'cz',
+            key: 'cz',
+            align: 'center',
+            fixed: 'right',
+            width: 120,
+            render: (_, b: any) => {
+                return <Space wrap>
+                    {!b?.status && <Popconfirm
+                        title="确定处理告警?"
+                        onConfirm={() => { handleMsg([b.id]) }}
+                    >
+                        <Button type="link" style={{ padding: 0 }} danger>处理告警</Button>
+                    </Popconfirm>}
+                    <Details data={b} />
+                </Space>
+            }
+        },
+    ]
+    return tableDfixed(arr)
+}
+
+
+export function DetailsTableConfig(): ColumnsType<any> {
+    let arr: ColumnsType<any> = [
+        {
+            title: '任务名称',
+            dataIndex: 'taskName',
+            key: 'taskName',
+            width: 150,
+            ellipsis: true
+        },
+        {
+            title: '消息类型',
+            dataIndex: 'msgType',
+            key: 'msgType',
+            width: 120,
+            align: 'center',
+            render(value) {
+                return obj?.[value] || '--'
+            },
+        },
+        {
+            title: '手机消息ID',
+            dataIndex: 'phoneMsgId',
+            key: 'phoneMsgId',
+            width: 120,
+            align: 'center'
+        },
+        {
+            title: '记录时间',
+            dataIndex: 'createTime',
+            key: 'createTime',
+            align: 'center',
+            width: 120,
+            ellipsis: true,
+        }
+    ]
+    return tableDfixed(arr)
+}

+ 1 - 354
src/pages/weComTask/page/businessPlan/create/components/massSending/index.tsx

@@ -1,18 +1,11 @@
 import React, { useContext, useState } from 'react';
 import style from '../../index.less';
-import { App, Button, Empty, Space, Upload } from 'antd';
+import { App, Button, Empty, Space } from 'antd';
 import { DispatchTaskCreate } from '../..';
 import SettingsMassSending from './settingsMassSending';
-import dayjs from 'dayjs';
 import '../../global.less'
 import useNewToken from '@/Hook/useNewToken';
 import ShowContent from './showContent';
-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 { saveAs } from 'file-saver';
 import PreviewMassSendingStrategy from './previewMassSendingStrategy';
 
 /**
@@ -28,331 +21,8 @@ const MassSending: React.FC = () => {
 
     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: 70, header: '群发内容' },
-            { key: 'A6', width: 30, header: '图文链接' },
-            { key: 'A7', width: 30, header: '小程序APPID' },
-            { key: 'A8', width: 30, header: '小程序路径' },
-        ];
-
-        // 表头设置样式
-        worksheet.getRow(headerRowIndex).height = 30;
-        Array(8).fill(0).forEach((_, index) => {
-            const col = index + 1; // 从第1列开始
-            // 设置表头字体样式
-            worksheet.getCell(headerRowIndex, col).style = index >= 5 ? headerJsMustStyle as any : headerJsStyle as any;
-        })
-
-
-        const data = getGroupData(settings)
-        console.log('数据', data)
-
-        const corpUserListLength = settings?.corpUsers?.length || 0;
-        const dataLength = data.length || 0;
-        // 合并单元集合
-        const mergeCells = [];
-        // 解放填写限制区域
-        const unLockArea = [];
-        let dataRow = 1
-        // 数据内容
-        settings?.corpUsers?.forEach((item, i) => {
-            // 1、客服号 群发标题
-
-            const startRow = 2 + (i * dataLength)
-            const endRow = startRow + dataLength - 1
-            mergeCells.push({
-                startRow,
-                startColumn: 1,
-                endRow,
-                endColumn: 1,
-            })
-            mergeCells.push({
-                startRow,
-                startColumn: 2,
-                endRow,
-                endColumn: 2,
-            })
-
-            let strategyIndex = 0
-
-            data.forEach((dataItem, index, row) => {
-                if (strategyIndex !== dataItem.strategyIndex) {
-                    const startRow = i * row.length + (index + 2)
-                    mergeCells.push({
-                        startRow,
-                        startColumn: 3,
-                        endRow: startRow + dataItem.strategyDataCount - 1,
-                        endColumn: 3,
-                    })
-                }
-                if (dataItem.sendDataRowSpan) {
-                    const startRow = i * row.length + (index + 2)
-                    mergeCells.push({
-                        startRow,
-                        startColumn: 4,
-                        endRow: startRow + dataItem.sendDataRowSpan - 1,
-                        endColumn: 4,
-                    })
-                }
-                const mediaItem = JSON.parse(JSON.stringify(dataItem?.content?.attachmentList || []))
-                if (dataItem?.content?.text?.content) {
-                    mediaItem.push({
-                        msgType: 'TASK_CONTENT_TEXT',
-                        textContent: dataItem?.content?.text?.content
-                    })
-                }
-                let linkPlaceholder = '<空>'
-                let miniprogramPlaceholder = '<空>'
-                const contentRichText = mediaItem.map(item => {
-                    // 'TASK_CONTENT_TEXT' | 'TASK_CONTENT_IMAGE' | 'TASK_CONTENT_LINK' | 'TASK_STATUS_MINIPROGRAM' | 'TASK_STATUS_VIDEO' | 'TASK_STATUS_FILE'
-                    switch (item.msgType) {
-                        case 'TASK_CONTENT_LINK':
-                            linkPlaceholder = '请输入'
-                            return { text: `链接:${item.link.title}_${item.link.desc}\n`, font: { color: { argb: "FF0000" } } }
-                        case 'TASK_STATUS_MINIPROGRAM':
-                            miniprogramPlaceholder = '请输入'
-                            return { text: `小程序:${item.miniprogram.title}\n`, font: { color: { argb: "FF0000" } } }
-                        case 'TASK_STATUS_FILE':
-                            return { text: `文件:${item.file.fileUrl}\n` }
-                        case 'TASK_STATUS_VIDEO':
-                            return { text: `视频:${item.video.videoUrl}\n` }
-                        case 'TASK_CONTENT_IMAGE':
-                            return { text: `图片:${item.image.picUrl}\n` }
-                        case 'TASK_CONTENT_TEXT':
-                            return { text: `文本:${item.textContent}\n` }
-                        default:
-                            return { text: `该类型没有请联系管理员` }
-                    }
-                })
-
-                const richText = {
-                    richText: [
-                        { text: `(内容${dataItem?.contentIndex})\n`, font: { bold: true } },
-                        ...contentRichText
-                    ]
-                }
-
-                if (linkPlaceholder !== '<空>') {
-                    unLockArea.push(`${dataRow}_6`)
-                }
-                if (miniprogramPlaceholder !== '<空>') {
-                    unLockArea.push(`${dataRow}_7`, `${dataRow}_8`)
-                }
-                dataRow++;
-                strategyIndex = dataItem.strategyIndex
-                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
-                                    }) : '')
-                            }
-                        ]
-                    },
-                    // 群发内容
-                    richText,
-                    // 图文链接
-                    linkPlaceholder,
-                    // 小程序APPID
-                    miniprogramPlaceholder,
-                    // 小程序路径
-                    miniprogramPlaceholder
-                ])
-            })
-        })
-
-        Array(corpUserListLength * dataLength).fill(0).forEach((_, index) => {
-            worksheet.getRow(index + headerRowIndex + 1).height = 100; // 设置行高
-            worksheet.getRow(index + headerRowIndex + 1).alignment = {
-                vertical: 'middle',
-                wrapText: true
-            }
-            // 分组设置背景色
-            if (index % (dataLength * 2) < dataLength) {
-                worksheet.getRow(index + headerRowIndex + 1).eachCell(function (cell, rowNumber) {
-                    if (rowNumber <= 8) {
-                        cell.border = {
-                            top: { style: 'thin', color: { argb: 'D4D4D4' } },
-                            bottom: { style: 'thin', color: { argb: 'D4D4D4' } },
-                            left: { style: 'thin', color: { argb: 'D4D4D4' } },
-                            right: { style: 'thin', color: { argb: 'D4D4D4' } }
-                        }
-                        cell.fill = {
-                            type: 'pattern',
-                            pattern: 'solid',
-                            fgColor: { argb: 'f0f0f0' }
-                        }
-                    }
-                })
-            } else {
-                worksheet.getRow(index + headerRowIndex + 1).eachCell(function (cell, rowNumber) {
-                    if (rowNumber <= 8) {
-                        cell.border = {
-                            top: { style: 'thin', color: { argb: 'D4D4D4' } },
-                            bottom: { style: 'thin', color: { argb: 'D4D4D4' } },
-                            left: { style: 'thin', color: { argb: 'D4D4D4' } },
-                            right: { style: 'thin', color: { argb: 'D4D4D4' } }
-                        }
-                    }
-                })
-            }
-        })
-
-        // 合并单元格
-        mergeCells.forEach(({ startRow, startColumn, endRow, endColumn }) => {
-            worksheet.mergeCells(startRow, startColumn, endRow, endColumn); // 合并单元格
-        })
-
-        // 设置可填写区域
-        for (let rowNum = 1; rowNum <= corpUserListLength * dataLength; rowNum++) {
-            for (let colNum = 1; colNum <= 3; colNum++) {
-                if (unLockArea.includes(`${rowNum}_${colNum + 5}`)) {
-                    const cell = worksheet.getCell(rowNum + 1, colNum + 5);
-                    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' });
-            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.col6) || !item?.col6 || NULLDATA.includes(item.col7) || !item?.col7 || NULLDATA.includes(item.col8) || !item?.col8))) {
-            message.error('请正确填写Excel内容!')
-            return
-        }
-
-        const msgData = getGroupData(settings)
-        console.log('读取的Excel数据', data, msgData)
-
-        if (msgData.length * settings?.corpUsers?.length !== 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 groupMsgContent = []
-            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 contentIndex = i.col5.richText[0].text.split('(内容')[1]?.split(')')[0]
-                const layer1 = strategyIndex - 1;
-                if (!groupMsgContent[layer1]) groupMsgContent[layer1] = [];              // 初始化第二层
-                const layer2 = sendDataIndex - 1;
-                if (!groupMsgContent[layer1][layer2]) groupMsgContent[layer1][layer2] = [];
-                groupMsgContent[strategyIndex - 1][sendDataIndex - 1][contentIndex - 1] = {
-                    linkUrl: i.col6 === '<空>' ? undefined : i.col6?.toString()?.trim(),
-                    miniprogramAppid: i.col7 === '<空>' ? undefined : i.col7?.toString()?.trim(),
-                    miniprogramPage: i.col8 === '<空>' ? undefined : i.col8?.toString()?.trim()
-                }
-            })
-            console.log('群发内容', groupMsgContent)
-            return {
-                ...item,
-                groupMsgContent
-            }
-        })
-        console.log('corpUsers', corpUsers)
-        setSettings({
-            ...settings,
-            corpUsers
-        })
-    }
-
     return <>
         <div className={`${style.settingsBody_content_row}`} style={{ width: 'calc(100% / 2)' }}>
             <div className={`${style.settingsBody_content_col}`} style={{ width: '50%' }}>
@@ -383,29 +53,6 @@ const MassSending: React.FC = () => {
                 <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

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

@@ -583,7 +583,7 @@ const Create: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookLis
                                         return {
                                             ...item, corpUsers: item.corpUsers.map((item: any) => {
                                                 const { corpUserId, name, corpName, corpId, mpAccountId, mpAccountInfo, id } = item
-                                                return { corpUserId, name, corpName, corpId, mpAccountId, mpAccountName: mpAccountInfo?.name, id }
+                                                return { corpUserId, name, corpName, corpId, mpAccountId, mpAccountName: mpAccountInfo?.name || item?.mpAccountName, id }
                                             })
                                         }
                                     })

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

@@ -7,7 +7,7 @@ import dayjs from 'dayjs';
 import { SearchOutlined, DeleteOutlined, PauseCircleOutlined, PlusCircleOutlined } from '@ant-design/icons';
 import Log from './log';
 import { inject, observer } from 'mobx-react';
-import { set, toJS } from 'mobx';
+import { toJS } from 'mobx';
 import AddToGroup from './components/addToGroup';
 
 /**

+ 70 - 0
src/pages/weComTask/page/moments/create/components/Strategy/index.tsx

@@ -0,0 +1,70 @@
+import useNewToken from '@/Hook/useNewToken';
+import React, { useContext, useState } from 'react';
+import { DispatchMomentsTaskCreate } from '../..';
+import style from '../../../../businessPlan/create/index.less'
+import { Button, Empty, Popconfirm } from 'antd';
+import SettingsStrategy from './settingsStrategy';
+import PreviewMomentsStrategy from './previewMomentsStrategy';
+
+const Strategy: React.FC = () => {
+
+    /*********************************/
+    const { token } = useNewToken()
+    const { setSettings, settings, onPreviewReset } = useContext(DispatchMomentsTaskCreate)!;
+    const [newVisible, setNewVisible] = useState<boolean>(false);
+    /*********************************/
+
+    return <>
+        <div className={`${style.settingsBody_content_row}`} style={{ width: '35%' }}>
+            <div className={`${style.settingsBody_content_col}`} style={{ width: '100%' }}>
+                <div className={style.title}>
+                    <span>策略</span>
+                    {settings?.massSendingStrategy && Object.keys(settings?.massSendingStrategy).length > 0 && <Popconfirm
+                        title="确定清空?"
+                        onConfirm={() => {
+                            setSettings(undefined)
+                            onPreviewReset();
+                        }}
+                    >
+                        <a style={{ color: 'red' }}>清空</a>
+                    </Popconfirm>}
+                </div>
+                <div className={style.detail}>
+                    <div className={style.detail_title}>朋友圈策略配置</div>
+                    <div className={style.detail_body}>
+                        {settings?.massSendingStrategy && Object.keys(settings?.massSendingStrategy).length > 0 ? <>
+                            <PreviewMomentsStrategy 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)}>编辑</Button>
+                </div>
+            </div>
+        </div>
+
+        {/* 配置基础信息 */}
+        {newVisible && <SettingsStrategy
+            visible={newVisible}
+            value={settings?.massSendingStrategy}
+            onClose={() => {
+                setNewVisible(false);
+            }}
+            onChange={(values) => {
+                const corpUsers = settings?.corpUsers?.map(item => {
+                    delete item?.groupMsgContent
+                    return item
+                })
+                setSettings({
+                    ...settings,
+                    corpUsers,
+                    massSendingStrategy: values
+                });
+                onPreviewReset();
+                setNewVisible(false);
+            }}
+        />}
+    </>;
+};
+
+export default Strategy;

+ 98 - 0
src/pages/weComTask/page/moments/create/components/Strategy/previewMomentsStrategy.tsx

@@ -0,0 +1,98 @@
+import { Card, Form, Input } from 'antd';
+import React, { useEffect } from 'react';
+import style from '../../../../businessPlan/create/index.less';
+import SendTimeSet from '@/pages/weComTask/components/sendTimeSet';
+import dayjs from 'dayjs';
+import '../../../../businessPlan/create/global.less'
+
+/**
+ * 朋友圈预览
+ * @param param0 
+ * @returns 
+ */
+const PreviewMomentsStrategy: React.FC<{ massSendingStrategy: { [x: string]: any } }> = ({ massSendingStrategy }) => {
+
+    /************************************************/
+    const [form] = Form.useForm();
+    const strategyList = Form.useWatch('strategyList', form);
+    /************************************************/
+
+    useEffect(() => {
+        if (massSendingStrategy) {
+            form.setFieldsValue({
+                ...massSendingStrategy, strategyList: massSendingStrategy?.strategyList?.map(item => {
+                    const { sendTime, startTime, endTime, sendDay, timeRepeatType } = item
+                    if (timeRepeatType === 'TIME_TYPE_SINGLE_PLACE') {
+                        const data = {
+                            ...item,
+                            timeRepeatType,
+                            sendDay: sendDay ? dayjs(sendDay + ' ' + sendTime) : undefined
+                        }
+                        delete data?.sendTime
+                        return data
+                    }
+                    return {
+                        ...item,
+                        timeRepeatType,
+                        sendTime: sendTime ? dayjs('2025-04-25 ' + sendTime) : undefined,
+                        startTime: startTime ? dayjs(startTime) : undefined,
+                        endTime: endTime ? dayjs(endTime) : undefined,
+                        sendDay: sendDay ? dayjs(sendDay) : undefined
+                    }
+                })
+            })
+        }
+    }, [massSendingStrategy])
+
+    return <Form
+        form={form}
+        name={'newMomentsShowzs'}
+        labelAlign='left'
+        labelCol={{ span: 5 }}
+        colon={false}
+        preserve={true}
+    >
+        <Card
+            title={<strong>基础信息配置</strong>}
+            style={{ background: '#fff', marginBottom: 10 }}
+            id='basicInfo'
+        >
+            <div className='block_tm'>
+                <Form.Item label={<strong>朋友圈标题</strong>} name="momentSendName">
+                    <Input placeholder="请输入标题" />
+                </Form.Item>
+            </div>
+        </Card>
+
+        <Form.List name="strategyList">
+            {(fields) => (
+                <>
+                    {fields.map(({ key, name, ...restField }, index) => {
+                        const timeRepeatType = strategyList?.[index]?.timeRepeatType
+
+                        return <Card
+                            key={key}
+                            title={<strong>策略{index + 1}配置</strong>}
+                            style={{ background: '#fff', marginBottom: 10 }}
+                        >
+                            <div className='block_tm'>
+                                <div className={style.strategy_item}>
+                                    <Form.Item
+                                        {...restField}
+                                        label={<strong>策略名称</strong>}
+                                        name={[name, 'strategyName']}
+                                    >
+                                        <Input placeholder="请输入标题" />
+                                    </Form.Item>
+                                    <SendTimeSet isShow active='all' form={form} restField={restField} name={name} timeRepeatType={timeRepeatType} />
+                                </div>
+                            </div>
+                        </Card>
+                    })}
+                </>
+            )}
+        </Form.List>
+    </Form>
+};
+
+export default React.memo(PreviewMomentsStrategy);

+ 249 - 0
src/pages/weComTask/page/moments/create/components/Strategy/settingsStrategy.tsx

@@ -0,0 +1,249 @@
+import { App, Button, Card, Form, Input, Modal } from "antd";
+import React, { useEffect, useRef, useState } from "react";
+import '../../../../businessPlan/create/global.less'
+import SendTimeSet from "@/pages/weComTask/components/sendTimeSet";
+import { PlusOutlined, DeleteOutlined } from "@ant-design/icons";
+import NewSteps, { NewStepsItem } from "@/pages/weComTask/components/newSteps";
+import dayjs from 'dayjs';
+import style from '../../../../businessPlan/create/components/massSending/index.less'
+
+/**
+ * 朋友圈策略配置
+ * @param param0 
+ * @returns 
+ */
+const SettingsStrategy: React.FC<GROUP_CHAT_CREATE.FoundationProps<any>> = ({ visible, onClose, value, onChange }) => {
+
+    /************************************/
+    const { message } = App.useApp()
+    const ref1 = useRef<HTMLDivElement>(null)
+    const [form] = Form.useForm();
+    const strategyList = Form.useWatch('strategyList', form);
+
+    const [stepsList, setStepsList] = useState<NewStepsItem[]>([
+        { title: '朋友圈配置', description: '朋友圈类型、朋友圈标题、适用产品', id: 'basicInfo' },
+        {
+            title: '朋友圈策略', children: [{ title: `策略1`, id: 'strategy_0', children: [{ title: `发送时间`, id: `strategy_0_strategyName` }] }, { title: `完成` }]
+        },
+        { title: '完成' }
+    ])
+    /************************************/
+
+    // 回填
+    useEffect(() => {
+        if (value && Object.keys(value).length) {
+            const data = {
+                ...value, strategyList: value?.strategyList?.map(item => {
+                    const { sendTime, startTime, endTime, sendDay, timeRepeatType } = item
+                    if (timeRepeatType === 'TIME_TYPE_SINGLE_PLACE') {
+                        const data = {
+                            ...item,
+                            timeRepeatType,
+                            sendDay: sendDay ? dayjs(sendDay + ' ' + sendTime) : undefined
+                        }
+                        delete data?.sendTime
+                        return data
+                    }
+                    return {
+                        ...item,
+                        timeRepeatType,
+                        sendTime: sendTime ? dayjs('2025-04-25 ' + sendTime) : undefined,
+                        startTime: startTime ? dayjs(startTime) : undefined,
+                        endTime: endTime ? dayjs(endTime) : undefined,
+                        sendDay: sendDay ? dayjs(sendDay) : undefined
+                    }
+                })
+            }
+            filedUpdateChange(data)
+            form.setFieldsValue(data)
+        }
+    }, [value])
+
+    const handleOk = () => {
+        form.validateFields().then((values) => {
+            const data = {
+                ...values,
+                strategyList: values?.strategyList?.map(item => {
+                    const { startTime, endTime, sendDay, sendTime, timeRepeatType, repeatArray, ...rest } = item
+                    const data = { ...rest, timeRepeatType }
+                    if (timeRepeatType === 'TIME_TYPE_SINGLE_PLACE') {
+                        // 定时发送
+                        data.sendDay = dayjs(sendDay).format('YYYY-MM-DD')
+                        data.sendTime = dayjs(sendDay).format('HH:mm:ss')
+                    } else if (timeRepeatType === 'TIME_TYPE_REPEAT_DAY') {
+                        // 每日循环
+                        data.startTime = dayjs(startTime).format('YYYY-MM-DD')
+                        if (endTime) {
+                            data.endTime = dayjs(endTime).format('YYYY-MM-DD')
+                        }
+                        data.sendTime = dayjs(sendTime).format('HH:mm:ss')
+                    } else if (timeRepeatType === 'TIME_TYPE_REPEAT_WEEK' || timeRepeatType === 'TIME_TYPE_REPEAT_MONTH') {
+                        // 每周循环、每月循环
+                        data.startTime = dayjs(startTime).format('YYYY-MM-DD')
+                        data.sendTime = dayjs(sendTime).format('HH:mm:ss')
+                        if (endTime) {
+                            data.endTime = dayjs(endTime).format('YYYY-MM-DD')
+                        }
+                        data.repeatArray = timeRepeatType === 'TIME_TYPE_REPEAT_MONTH' ? repeatArray : repeatArray.filter(item => Number(item) < 8)
+                    }
+                    return data
+                })
+            }
+            onChange(data)
+        }).catch(() => {
+            form.submit()
+        });
+    };
+
+
+    const filedUpdateChange = ({ momentSendName, strategyList }: any) => {
+        const isChecked = (content: NewStepsItem[]) => {
+            return content.every(item => {
+                if (item.children) {
+                    return isChecked(item.children)
+                }
+                return item.checked
+            })
+        }
+
+        const content = strategyList?.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`
+                }
+            ]
+
+            return {
+                title: `策略${index + 1}`,
+                id: `strategy_${index}`,
+                children: children,
+                checked: isChecked(children)
+            }
+        })
+
+        const isChecked1 = !!momentSendName
+        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 <Modal
+        title={<strong>群聊朋友圈策略配置  &nbsp;&nbsp;&nbsp;<span style={{ color: 'red' }}>对于执行时间冲突的策略,按照策略的排序执行</span></strong>}
+        open={visible}
+        onCancel={onClose}
+        width={850}
+        onOk={handleOk}
+        className={`settingsModal`}
+    >
+        <div className={`body_steps`}>
+            <NewSteps
+                items={stepsList}
+                onChange={(e) => {
+                    if (e?.id)
+                        ref1.current?.querySelector('#' + e?.id)?.scrollIntoView({ behavior: 'smooth' })
+                }}
+            />
+        </div>
+        <div className={`body_content`} ref={ref1}>
+            <Form
+                form={form}
+                name="newMomentsMassSending"
+                labelAlign='left'
+                labelCol={{ span: 5 }}
+                colon={false}
+                scrollToFirstError={{
+                    behavior: 'smooth',
+                    block: 'center'
+                }}
+                onFinishFailed={({ errorFields }) => {
+                    message.error(errorFields?.[0]?.errors?.[0])
+                }}
+                initialValues={{
+                    strategyList: [{ id: Date.now() }],
+                }}
+                onFieldsChange={() => {
+                    filedUpdateChange(form.getFieldsValue())
+                }}
+                preserve={true}
+            >
+                <Card title={<strong>基础信息配置</strong>} style={{ background: '#fff', marginBottom: 10 }} hoverable id='basicInfo'>
+
+                    <Form.Item label={<strong>朋友圈标题</strong>} name="momentSendName" rules={[{ required: true, message: '请输入标题!' }]}>
+                        <Input placeholder="请输入标题" style={{ width: 358 }} allowClear />
+                    </Form.Item>
+                </Card>
+
+                <Form.List name="strategyList">
+                    {(fields, { add, remove }) => (
+                        <>
+                            {fields.map(({ key, name, ...restField }, index) => {
+                                const timeRepeatType = strategyList?.[index]?.timeRepeatType
+
+                                return <Card
+                                    key={key}
+                                    title={<strong>策略{index + 1}配置</strong>}
+                                    style={{ background: '#fff', marginBottom: 10 }}
+                                    hoverable
+                                    id={`strategy_${index}`}
+                                    extra={strategyList?.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}
+                                            restFieldChange={(value) => {
+                                                form.setFieldsValue({
+                                                    strategyList: strategyList?.map((item, i) => {
+                                                        if (i === index) {
+                                                            return {
+                                                                ...item,
+                                                                timeRepeatType: value,
+                                                                repeatArray: undefined
+                                                            }
+                                                        }
+                                                        return item
+                                                    })
+                                                });
+                                            }}
+                                        />
+                                    </div>
+                                </Card>
+                            })}
+                            <Form.Item>
+                                <Button type="primary" onClick={() => add({ id: Date.now() })} block icon={<PlusOutlined />}>
+                                    新增策略组
+                                </Button>
+                            </Form.Item>
+                        </>
+                    )}
+                </Form.List>
+            </Form>
+        </div>
+    </Modal>
+}
+
+export default React.memo(SettingsStrategy);

+ 73 - 0
src/pages/weComTask/page/moments/create/components/content/index.tsx

@@ -0,0 +1,73 @@
+import useNewToken from '@/Hook/useNewToken';
+import React, { useContext, useState } from 'react';
+import style from '../../../../businessPlan/create/index.less'
+import { DispatchMomentsTaskCreate } from '../..';
+import { App, Button, Empty } from 'antd';
+import SettingsContent from './settingsContent';
+import PreviewMomentsContent from './previewMomentsContent';
+
+const Content: React.FC = () => {
+
+    /*******************************************/
+    const { token } = useNewToken()
+    const { message } = App.useApp()
+    const { setSettings, settings, onPreviewReset } = useContext(DispatchMomentsTaskCreate)!;
+    const [newVisible, setNewVisible] = useState<boolean>(false);
+    /*******************************************/
+
+    return <>
+        <div className={`${style.settingsBody_content_row}`} style={{ width: '65%' }}>
+            <div className={`${style.settingsBody_content_col}`} style={{ width: '100%' }}>
+                <div className={style.title}>
+                    <span>内容</span>
+                </div>
+                <div className={style.detail}>
+                    <div className={style.detail_title}>朋友圈内容配置</div>
+                    <div className={style.detail_body}>
+                        {settings?.massSendingStrategy && Object.keys(settings?.massSendingStrategy).length > 0 ? <>
+                            <PreviewMomentsContent
+                                strategyList={settings?.massSendingStrategy?.strategyList || []}
+                            />
+                        </> : <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?.massSendingStrategy && Object.keys(settings?.massSendingStrategy))) {
+                                message.error('请先配置策略')
+                                return
+                            }
+                            setNewVisible(true);
+                        }}
+                    >编辑</Button>
+                </div>
+            </div>
+        </div>
+
+        {/* 配置朋友圈对象 */}
+        {newVisible && <SettingsContent
+            visible={newVisible}
+            onClose={() => setNewVisible(false)}
+            onChange={(values) => {
+                const corpUsers = settings?.corpUsers?.map(item => {
+                    delete item?.groupMsgContent
+                    return item
+                })
+                setSettings({
+                    ...settings,
+                    corpUsers,
+                    massSendingStrategy: values
+                });
+                onPreviewReset();
+                setNewVisible(false);
+            }}
+            value={settings?.massSendingStrategy}
+        />}
+    </>;
+};
+
+export default Content;

+ 57 - 0
src/pages/weComTask/page/moments/create/components/content/previewMomentsContent.tsx

@@ -0,0 +1,57 @@
+import useNewToken from '@/Hook/useNewToken';
+import LookMsg from '@/pages/weComTask/components/previewMsg/lookMsg';
+import { getMsgData } from '@/pages/weComTask/page/businessPlan/create/components/massSending/showContent';
+import { welcomeContentData } from '@/pages/weComTask/page/businessPlan/create/const';
+import { Empty, Tabs, Typography } from 'antd';
+import React, { useState } from 'react';
+const { Text } = Typography;
+
+interface PreviewGroupUserProps {
+    strategyList: { [x: string]: any }[];
+}
+
+/**
+ * 朋友圈内容预览
+ * @param param0 
+ * @returns 
+ */
+const PreviewMomentsContent: React.FC<PreviewGroupUserProps> = ({ strategyList }) => {
+
+    /****************************************/
+    const [activeKey, setActiveKey] = useState<string>('1')
+    const { token } = useNewToken()
+    /****************************************/
+
+    return <Tabs
+        activeKey={activeKey}
+        tabPosition='left'
+        style={{
+            height: '100%',
+            width: '100%'
+        }}
+        onChange={(key) => { setActiveKey(key) }}
+        items={strategyList.map((item, index) => {
+            return {
+                label: <div style={{ maxWidth: 100 }}><Text>{item.strategyName}</Text></div>,
+                key: `${index + 1}`,
+                children: <div style={{ width: '100%', height: 282, overflow: 'hidden', overflowY: 'auto' }}>
+                    {item?.taskDetail?.contentDTO?.length > 0 ? <>
+                        <div style={{ backgroundColor: token.colorPrimary, padding: 4, color: '#FFF', fontSize: 12 }}>
+                            <div>
+                                发送模式:{welcomeContentData?.find(i => i.value === item?.taskDetail?.sendMode)?.label}
+                            </div>
+                        </div>
+                        {item?.taskDetail?.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)} />
+                            </div>
+                        })}
+                    </> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
+                </div>
+            }
+        })}
+    />;
+};
+
+export default PreviewMomentsContent;

+ 219 - 0
src/pages/weComTask/page/moments/create/components/content/settingsContent.tsx

@@ -0,0 +1,219 @@
+import { App, Button, Card, Form, Modal, Select } from 'antd';
+import React, { useEffect, useRef, useState } from 'react';
+import { MinusOutlined, PlusOutlined } from '@ant-design/icons';
+import MaterialNoTextMould from '@/pages/weComTask/page/businessPlan/create/components/massSending/materialNoTextMould';
+import { welcomeContentData } from '@/pages/weComTask/page/businessPlan/create/const';
+import NewSteps from '@/pages/weComTask/components/newSteps';
+
+/**
+ * 朋友圈内容配置
+ * @param param0 
+ * @returns 
+ */
+const SettingsContent: React.FC<GROUP_CHAT_CREATE.FoundationProps<any>> = ({ visible, onClose, value, onChange }) => {
+
+    /***********************************************/
+    const { message } = App.useApp()
+    const [form] = Form.useForm();
+    const strategyList = Form.useWatch('strategyList', form)
+    const ref1 = useRef<HTMLDivElement>(null)
+
+    const [stepsList, setStepsList] = useState<any>([
+        {
+            title: `策略${1}`,
+            id: 'strategy_1',
+            children: [{
+                title: `内容${1}`,
+                id: 'strategy_1_content_1'
+            }]
+        },
+        { title: '完成' }
+    ])
+    /***********************************************/
+
+    useEffect(() => {
+        if (value) {
+            console.log(value)
+            const valueS = {
+                ...value,
+                strategyList: value?.strategyList?.map(str => {
+                    return {
+                        ...str,
+                        taskDetail: {
+                            ...(str?.taskDetail || {
+                                contentDTO: str?.contentDTO || [{}]
+                            })
+                        }
+                    }
+                })
+            }
+            filedUpdateChange(valueS)
+            form.setFieldsValue(valueS)
+        }
+    }, [value])
+
+    const handleOk = () => {
+        form.validateFields().then((values) => {
+            onChange({
+                ...value,
+                ...values
+            })
+        }).catch(() => {
+            form.submit()
+        });
+    };
+
+    const filedUpdateChange = ({ strategyList }: any) => {
+        const strategyChildren = strategyList?.map((item, index) => {
+
+            const content = item?.taskDetail?.contentDTO?.map((i, i_index) => {
+                return {
+                    title: `内容${i_index + 1}`,
+                    checked: (i?.attachmentList?.length || i?.text?.content),
+                    id: `strategy_${index + 1}_content_${i_index + 1}`
+                }
+            })
+
+            return {
+                title: `策略${index + 1}`,
+                id: `strategy_${index + 1}`,
+                children: [
+                    {
+                        title: `内容发送模式`,
+                        id: `clientId_${index}_sendMode`,
+                        checked: item?.taskDetail?.sendMode == 0 || item?.taskDetail?.sendMode == 1,
+                    },
+                    ...content
+                ],
+                checked: content.every(item => item.checked)
+            }
+        })
+        const stepsData = [
+            ...strategyChildren,
+            {
+                title: '完成', checked: strategyChildren.every(item => item.checked)
+            }
+        ]
+        setStepsList(stepsData)
+    }
+
+    return <Modal
+        title={<strong>朋友圈内容配置</strong>}
+        open={visible}
+        onCancel={onClose}
+        width={850}
+        onOk={handleOk}
+        className={`settingsModal`}
+    >
+        <div className={`body_steps`}>
+            <NewSteps
+                items={stepsList}
+                onChange={(e) => {
+                    if (e?.id)
+                        ref1.current?.querySelector('#' + e?.id)?.scrollIntoView({ behavior: 'smooth' })
+                }}
+            />
+        </div>
+        <div className={`body_content`} ref={ref1}>
+            <Form
+                form={form}
+                name="newMomentsSendCnotent"
+                labelAlign='left'
+                labelCol={{ span: 5 }}
+                colon={false}
+                scrollToFirstError={{
+                    behavior: 'smooth',
+                    block: 'center'
+                }}
+                onFinishFailed={({ errorFields }) => {
+                    message.error(errorFields?.[0]?.errors?.[0])
+                }}
+                onFinish={handleOk}
+                onFieldsChange={() => {
+                    filedUpdateChange(form.getFieldsValue())
+                }}
+                preserve={true}
+            >
+                <Form.List name="strategyList">
+                    {(fields) => (
+                        <>
+                            {fields.map(({ key, name, ...restField }, index) => {
+                                const strategyItem = strategyList[index]
+                                return <Card
+                                    key={key}
+                                    title={<strong>策略 {strategyItem.strategyName} 配置</strong>}
+                                    style={{ background: '#fff', marginBottom: 10 }}
+                                    id={`strategy_${index + 1}`}
+                                >
+                                    <div id={'clientId' + '_' + index + '_' + 'sendMode'}>
+                                        <Form.Item
+                                            {...restField}
+                                            label={<strong>内容组发送模式</strong>}
+                                            name={[name, 'taskDetail', '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 + '_' + 'contentDTO'}>
+                                        <Form.List {...restField} name={[name, 'taskDetail', 'contentDTO']} >
+                                            {(fields, { add, remove }) => {
+                                                return <>
+                                                    {fields.map(({ key, name, ...restField }, c_index) => {
+                                                        return <div key={c_index} id={`strategy_${index + 1}_content_${c_index + 1}`}>
+                                                            <h3 style={{ display: 'flex', justifyContent: 'space-between', margin: 0 }}>内容{c_index + 1}
+                                                                {fields?.length > 1 && <Button
+                                                                    type="dashed"
+                                                                    danger
+                                                                    onClick={() => remove(c_index)}
+                                                                    icon={<MinusOutlined />}
+                                                                    size='small'
+                                                                >
+                                                                    移除内容
+                                                                </Button>}
+                                                            </h3>
+                                                            <Form.Item
+                                                                {...restField}
+                                                                name={name}
+                                                                rules={[{
+                                                                    validator: (_, value) => {
+                                                                        if ((value && !(value?.text?.content || value?.attachmentList?.length)) || !value) {
+                                                                            return Promise.reject('请填写内容或上传附件');
+                                                                        }
+                                                                        return Promise.resolve();
+                                                                    }
+                                                                }]}
+                                                            >
+                                                                <MaterialNoTextMould />
+                                                            </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>
+            </Form>
+        </div>
+    </Modal>;
+};
+
+export default SettingsContent;

+ 523 - 0
src/pages/weComTask/page/moments/create/index.tsx

@@ -0,0 +1,523 @@
+import React, { useCallback, useEffect, useState } from 'react';
+import style from '../../businessPlan/create/index.less'
+import { useAjax } from '@/Hook/useAjax';
+import { welcomeMsgJobTypeApi } from '@/pages/weComTask/API/weMaterial/weMaterial';
+import { addTaskApi, getCreateDetailsApi, updateTaskApi } from '@/pages/weComTask/API/businessPlan/create';
+import { getBindMpListApi } from '@/pages/weComTask/API/corpUserAssign';
+import { App, Button, Card, Empty, Form, Input, Popconfirm, Select, Space, Spin, Table } from 'antd';
+import SelectCorpUserGroup from '../../corpUserManage/selectCorpUserGroup';
+import { useNavigate } from 'react-router-dom';
+import { toJS } from 'mobx';
+import { inject, observer } from 'mobx-react';
+import Strategy from './components/Strategy';
+import Content from './components/content';
+import { SaveOutlined, RedoOutlined, SearchOutlined, PlusOutlined } from '@ant-design/icons';
+import SelectCorpUser from '../../corpUserManage/selectCorpUser';
+import { monmentsColumns } from './tableConfig';
+import SubmitModal from '../../businessPlan/create/submitModal';
+import { groupBy } from '@/utils/utils';
+
+export const DispatchMomentsTaskCreate = React.createContext<TASK_MOMENTS_CREATE.DispatchMomentsTaskCreate | null>(null);
+
+/**
+ * 朋友圈创建
+ * @param param0 
+ * @returns 
+ */
+const MomentsCreatePage: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookListProps[], bookPlatForm: TASK_CREATE.BookPlatFormProps[] } } }> = ({ weComTaskStore }) => {
+
+    /*************************************/
+    const navigate = useNavigate();
+    const { bookList, bookPlatForm } = toJS(weComTaskStore.data)
+    const { message, modal } = App.useApp()
+    const [settings, setSettings] = useState<TASK_MOMENTS_CREATE.SettingsProps>();
+    const [msgJobTypeList, setMsgJobTypeList] = useState<{ value: string, label: string }[]>([])
+    const [previewData, setPreviewData] = useState<TASK_MOMENTS_CREATE.previewDataProps>([])
+    const [previewDataOld, setPreviewDataOld] = useState<TASK_MOMENTS_CREATE.previewDataProps>([])
+    const [mpList, setMplist] = useState<{ label: string, value: number }[]>([])
+    const [previewContent, setPreviewContent] = useState<{ [x: string]: any }>({})
+    const [projectId, setProjectId] = useState<number>()
+    const [subVisible, setSubVisible] = useState<boolean>(false) // 选择设置名称弹窗控制
+
+    const welcomeMsgJobType = useAjax(() => welcomeMsgJobTypeApi())//获取欢迎语类型
+
+    const addTask = useAjax((params) => addTaskApi(params))
+    const updateTask = useAjax((params) => updateTaskApi(params))
+    const getCreateDetails = useAjax((params) => getCreateDetailsApi(params))
+    const getBindMpList = useAjax(() => getBindMpListApi())
+    /*************************************/
+
+    useEffect(() => {
+        const project = sessionStorage.getItem('MOMENTSTASKID')
+        if (project) {
+            const { id, isCopy } = JSON.parse(project)
+            if (!isCopy) {
+                setProjectId(id)
+            }
+            getCreateDetails.run(id).then(res => {
+                sessionStorage.removeItem('MOMENTSTASKID')
+                if (res?.data) {
+                    const { corpUsers, bizType, platform, templateProductId, momentCreateDTO } = res.data
+                    const corpUserGroups = corpUsers.map(item => {
+                        return {
+                            corpUsers: item.corpUserList.map(({ corpUserId, name, corpId, mpAccountId, mpAccountInfo, id }) => {
+                                return { id: id || (corpId + '_' + corpUserId), corpUserId, name, corpName: item.corpName, corpId, mpAccountId, mpAccountName: mpAccountInfo?.name }
+                            })
+                        }
+                    })
+
+                    const newSettings: TASK_MOMENTS_CREATE.SettingsProps = {
+                        bizType,
+                        platform: Number(platform) as any,
+                        templateProductId,
+                        corpUserGroups,
+                        massSendingStrategy: {
+                            momentSendName: momentCreateDTO?.momentSendName || '',
+                            strategyList: momentCreateDTO?.strategyList || []
+                        }
+                    }
+                    setSettings(newSettings)
+
+                    let id = 1;
+                    const newPreviewContent: { [x: string]: any } = {};
+                    corpUsers.forEach((cu) => {
+                        const momentSendContent = cu?.momentSendContent;
+                        (momentCreateDTO?.strategyList || []).forEach((str, s_index) => {
+                            const { taskDetail } = str
+                            taskDetail?.contentDTO?.forEach((_, c_index) => {
+                                const content = momentSendContent?.[s_index]?.[c_index] || {}
+                                newPreviewContent[id] = content
+                                id++;
+                            })
+                        });
+                    })
+                    setPreviewContent(newPreviewContent)
+                }
+            })
+        } else {
+            const task = localStorage.getItem('TASK_MOMENTS_CREATE')
+            if (task) {
+                setSettings(JSON.parse(task).settings)
+            }
+        }
+    }, [])
+
+    useEffect(() => {
+        welcomeMsgJobType.run().then(res => {
+            if (res?.data) {
+                setMsgJobTypeList(Object.keys(res.data).map(key => ({ value: key, label: res.data[key] })))
+            }
+        })
+        getBindMpList.run().then(res => {
+            setMplist(res?.data?.map((item: any) => ({ label: item.name, value: item.id })))
+        })
+    }, [])
+
+    // 预览
+    const preview = () => {
+        const { bizType, platform, templateProductId, channel, massSendingStrategy, corpUserGroups } = settings
+        if (!corpUserGroups || corpUserGroups?.length === 0) {
+            message.error('请先选择客服号')
+            return
+        }
+
+        if (!bizType) {
+            message.error('请选择业务类型')
+            return
+        }
+        if (!platform) {
+            message.error('请选择书城')
+            return
+        }
+
+        const list: any[] = []
+        if (!massSendingStrategy?.strategyList?.every((str, s_index) => {
+            const { taskDetail, ...sdto } = str
+            if (taskDetail?.contentDTO?.length) {
+                return taskDetail?.contentDTO?.every((cd, c_index, row) => {
+                    if (cd?.attachmentList?.length || cd?.text?.content) {
+                        const mediaItem = JSON.parse(JSON.stringify(cd?.attachmentList || []))
+                        if (cd?.text?.content) {
+                            mediaItem.push({
+                                msgType: 'TASK_CONTENT_TEXT',
+                                textContent: cd?.text?.content
+                            })
+                        }
+                        const linkData = []
+                        const miniProgramData = []
+                        const contentReactNode = mediaItem.map(item => {
+                            switch (item.msgType) {
+                                case 'TASK_CONTENT_LINK':
+                                    linkData.push(item)
+                                    return `<span style="color: red">链接</span>`
+                                case 'TASK_STATUS_MINIPROGRAM':
+                                    miniProgramData.push(item)
+                                    return `<span style="color: red">小程序</span>`
+                                case 'TASK_STATUS_FILE':
+                                    return `<span>文件</span>`
+                                case 'TASK_STATUS_VIDEO':
+                                    return `<span>视频</span>`
+                                case 'TASK_CONTENT_IMAGE':
+                                    return `<span>图片</span>`
+                                case 'TASK_CONTENT_TEXT':
+                                    return `<span>文本</span>`
+                                default:
+                                    return `<span style="color: red">请联系管理员</span>`
+                            }
+                        })
+                        list.push({
+                            bizType,
+                            platform,
+                            templateProductId,
+                            channel,
+                            taskName: settings?.massSendingStrategy?.momentSendName,
+                            strategyData: sdto,
+                            contentReactNode,
+                            content: cd,
+                            strategyIndex: s_index,
+                            contentIndex: c_index,
+                            linkData,
+                            miniProgramData,
+                            strategyRowSpan: c_index === 0 ? row.length : 0,
+                        })
+                        return true
+                    } else {
+                        message.error(`策略:${str?.strategyName}请填写发送内容`)
+                        return false
+                    }
+                })
+            } else {
+                message.error(`策略:${str?.strategyName}请填写发送内容`)
+                return false
+            }
+        })) {
+            return
+        }
+
+        let id = 1
+        const newPreviewData = corpUserGroups.reduce((pre, cur, index) => {
+            return pre.concat(...list.map((item, i) => {
+                return {
+                    ...item,
+                    id: id++,
+                    corpUserGroupName: `客服组${index + 1}`,
+                    corpUsers: cur.corpUsers,
+                    mpAccountId: cur.corpUsers?.[0]?.mpAccountId,
+                    mpAccountName: cur.corpUsers?.[0]?.mpAccountName,
+                    corpUserStrList: cur.corpUsers?.map(item => item.corpId + '_' + item.corpUserId),
+                    groupIndex: index,
+                    groupRowSpan: i === 0 ? list.length : 0, // 用于表格合并
+                }
+            }))
+        }, [])
+        console.log(newPreviewData)
+
+        setPreviewData(newPreviewData)
+        setPreviewDataOld(newPreviewData)
+    }
+
+    const setTaskName = () => {
+        if (previewDataOld.every((item, index) => {
+            if ((item?.linkData?.length > 0 ? previewContent?.[index + 1]?.linkUrl : true) && (item?.miniProgramData?.length > 0 ? (previewContent?.[index + 1]?.miniprogramAppid && previewContent?.[index + 1]?.miniprogramPage) : true)) {
+                return true
+            } else {
+                message.error('请补充图文或者小程序内容')
+                return false
+            }
+        })) {
+            setSubVisible(true)
+        }
+    }
+
+    const onSubmit = (values: any) => {
+        const groupData = groupBy(previewDataOld, (item) => item['groupIndex'], true);
+        const { bizType, platform, templateProductId, corpUserGroups, massSendingStrategy } = settings
+        const params = {
+            ...values,
+            bizType,
+            platform,
+            templateProductId,
+            corpUsers: corpUserGroups?.map((item, index) => {
+                const params: { [x: string]: any } = {
+                    corpId: item.corpUsers[0].corpId,
+                    corpUserIds: item.corpUsers.map(item => item.corpUserId)
+                }
+                const data = groupData[index] || []
+                const momentSendContent: any[] = []
+                data.forEach(d => {
+                    const { strategyIndex, contentIndex, id } = d
+                    if (!momentSendContent[strategyIndex]) momentSendContent[strategyIndex] = [];
+                    momentSendContent[strategyIndex][contentIndex] = previewContent?.[id] || {};
+                })
+                params.momentSendContent = momentSendContent
+                return params
+            })
+        }
+
+        params.momentCreateDTO = {
+            momentSendName: massSendingStrategy.momentSendName,
+            strategyList: massSendingStrategy?.strategyList
+        }
+
+        if (projectId) {
+            params.projectId = projectId
+            updateTask.run(params).then(res => {
+                if (res?.data) {
+                    message.success('修改提交成功')
+                    setProjectId(undefined)
+                    sessionStorage.setItem('CAMPCORP', values?.projectName)
+                    navigate('/weComTask/moments/taskList')
+                }
+            })
+        } else {
+            addTask.run(params).then(res => {
+                if (res?.data) {
+                    modal.success({
+                        content: '任务提交成功',
+                        styles: { body: { fontWeight: 700 } },
+                        okText: '跳转任务列表',
+                        closable: true,
+                        onOk: () => {
+                            sessionStorage.setItem('CAMPCORP', values?.projectName)
+                            navigate('/weComTask/moments/taskList')
+                        },
+                        onCancel: () => {
+                            setSubVisible(false)
+                        }
+                    })
+                }
+            })
+        }
+    }
+
+    // 重置表格
+    const onPreviewReset = () => {
+        setPreviewData([])
+        setPreviewDataOld([])
+        setPreviewContent({})
+    }
+
+    const severBd = () => {
+        localStorage.setItem('TASK_MOMENTS_CREATE', JSON.stringify({ settings }))
+        message.success('存储成功')
+    }
+
+    const tableSearch = useCallback((values) => {
+        console.log(values)
+        if (values?.mpAccountIds?.length > 0 || values?.corpUserIds?.length > 0) {
+            let newPreviewData: TASK_MOMENTS_CREATE.previewDataProps = []
+            const corpUserStrList = values?.corpUserIds?.map(item => item.corpId + '_' + item.corpUserId)
+            if (previewDataOld?.length > 0) {
+                newPreviewData = previewDataOld.filter(item => (
+                    (values?.mpAccountIds?.length > 0 ? values.mpAccountIds.includes(item?.mpAccountId) : true)
+                    &&
+                    (corpUserStrList?.length > 0 ? item.corpUserStrList.some(str => corpUserStrList.includes(str)) : true)
+                )).map(item => ({ ...item, userRowSpan: 1, strategyRowSpan: 1, sendDataRowSpan: 1 }))
+            }
+            setPreviewData(newPreviewData)
+        } else {
+            setPreviewData(previewDataOld)
+        }
+    }, [previewDataOld, previewData])
+
+    return <div className={style.create}>
+        <Spin spinning={getCreateDetails.loading}>
+            <Card title={<strong>{projectId ? getCreateDetails?.data?.data?.projectName + '任务编辑' : ''}配置区</strong>} className={`${style.card} ${style.config}`}>
+                <Space wrap>
+                    <Space.Compact>
+                        <Button>客服组</Button>
+                        <SelectCorpUserGroup
+                            value={settings?.corpUserGroups}
+                            onChange={(corpUserGroups) => {
+                                setSettings({
+                                    ...settings, corpUserGroups: corpUserGroups.map(item => {
+                                        return {
+                                            ...item, corpUsers: item.corpUsers.map((item: any) => {
+                                                const { corpUserId, name, corpName, corpId, mpAccountId, mpAccountInfo, id } = item
+                                                return { corpUserId, name, corpName, corpId, mpAccountId, mpAccountName: mpAccountInfo?.name || item?.mpAccountName, id }
+                                            })
+                                        }
+                                    })
+                                })
+                                onPreviewReset()
+                            }}
+                        />
+                    </Space.Compact>
+                    <Space.Compact>
+                        <Button>业务类型</Button>
+                        <Select
+                            showSearch
+                            style={{ width: 120 }}
+                            allowClear
+                            placeholder="请选择类型"
+                            filterOption={(input, option) =>
+                                ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                            }
+                            value={settings?.bizType}
+                            onChange={(e) => {
+                                setSettings({ ...settings, bizType: e })
+                                onPreviewReset()
+                            }}
+                            options={msgJobTypeList.filter(item => item.value === 'novel')}
+                        />
+                    </Space.Compact>
+
+                    {settings?.bizType === 'novel' ? <>
+                        <Space.Compact>
+                            <Button>书城</Button>
+                            <Select
+                                showSearch
+                                allowClear
+                                placeholder="请选择书城"
+                                style={{ width: 120 }}
+                                filterOption={(input, option) =>
+                                    ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                                }
+                                value={settings?.platform}
+                                onChange={(e) => {
+                                    setSettings({ ...settings, platform: e, templateProductId: undefined })
+                                    onPreviewReset()
+                                }}
+                                options={bookPlatForm.map(item => ({ value: item.id, label: item.platformName }))}
+                            />
+                        </Space.Compact>
+                        <Space.Compact>
+                            <Button>适用产品</Button>
+                            <Select
+                                showSearch
+                                style={{ width: 150 }}
+                                allowClear
+                                placeholder="请选择模板适用产品"
+                                filterOption={(input, option) =>
+                                    ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                                }
+                                value={settings?.templateProductId}
+                                onChange={(e) => {
+                                    setSettings({ ...settings, templateProductId: e })
+                                    onPreviewReset()
+                                }}
+                                options={bookList.filter(item => settings?.platform ? item.platformId === settings?.platform : true).map(item => ({ value: item.id, label: item.bookName }))}
+                            />
+                        </Space.Compact>
+                    </> : settings?.bizType === 'game' ? <Space.Compact>
+                        <Button>游戏渠道</Button>
+                        <Input
+                            style={{ width: 200 }}
+                            allowClear
+                            placeholder="请输入游戏渠道"
+                            value={settings.channel}
+                            onChange={(e) => {
+                                setSettings({ ...settings, channel: e.target.value })
+                                onPreviewReset()
+                            }}
+                        />
+                    </Space.Compact> : undefined}
+                </Space>
+
+                <div className={style.settingsBody}>
+                    <div className={style.settingsBody_content}>
+                        <DispatchMomentsTaskCreate.Provider
+                            value={{
+                                settings, setSettings,
+                                bookPlatForm,
+                                bookList,
+                                msgJobTypeList,
+                                onPreviewReset
+                            }}
+                        >
+                            {/* 朋友圈策略 */}
+                            <Strategy />
+                            {/* 朋友圈内容 */}
+                            <Content />
+                        </DispatchMomentsTaskCreate.Provider>
+                    </div>
+                </div>
+
+                <Space className={style.bts} wrap>
+                    <Button icon={<SaveOutlined />} onClick={severBd}>存为预设</Button>
+                    <Popconfirm
+                        title="确定清空?"
+                        onConfirm={() => {
+                            setSettings(undefined)
+                            setPreviewData([])
+                            setPreviewDataOld([])
+                            localStorage.removeItem('TASK_MOMENTS_CREATE')
+                        }}
+                    >
+                        <Button icon={<RedoOutlined />} danger>清空配置/预设</Button>
+                    </Popconfirm>
+                    <Button type='primary' onClick={preview}><SearchOutlined />预览任务/配置内容</Button>
+                </Space>
+            </Card>
+        </Spin>
+
+        <Card
+            className={style.card}
+            style={{ marginTop: 10, marginBottom: 10 }}
+            extra={previewDataOld?.length > 0 ? <Button type='primary' icon={<PlusOutlined />} onClick={() => {
+                setTaskName()
+            }}>提交</Button> : undefined}
+        >
+            {previewDataOld?.length > 0 ? <div style={{ minHeight: 300 }}>
+                <Form
+                    layout={'inline'}
+                    onFinish={tableSearch}
+                    style={{ marginBottom: 10 }}
+                >
+                    <Form.Item label={<strong>公众号</strong>} name="mpAccountIds">
+                        <Select
+                            showSearch
+                            style={{ minWidth: 160 }}
+                            maxTagCount={1}
+                            placeholder="公众号"
+                            filterOption={(input, option) =>
+                                ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                            }
+                            allowClear
+                            mode='multiple'
+                            options={mpList}
+                        />
+                    </Form.Item>
+
+                    <Space.Compact>
+                        <Button>客服组</Button>
+                        <Form.Item name="corpUserIds">
+                            <SelectCorpUser placeholder="请选择客服号" />
+                        </Form.Item>
+                    </Space.Compact>
+                    <Form.Item>
+                        <Space>
+                            <Button htmlType="reset">重置</Button>
+                            <Button type="primary" htmlType='submit'>搜索</Button>
+                        </Space>
+                    </Form.Item>
+                </Form>
+                <Table
+                    dataSource={previewData}
+                    columns={monmentsColumns(bookPlatForm, bookList, bookPlatForm.find(item => item.id === Number(settings?.platform)).platformKey, previewContent, setPreviewContent)}
+                    rowKey={'id'}
+                    bordered={true}
+                    scroll={{ y: 550 }}
+                    pagination={false}
+                />
+            </div> : <div style={{ minHeight: 400, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
+                <Empty description="请先完成模块配置后,再预览" />
+            </div>}
+        </Card>
+
+        {/* 提交配置 */}
+        {subVisible && <SubmitModal
+            visible={subVisible}
+            loading={addTask.loading || updateTask.loading}
+            projectName={projectId ? getCreateDetails?.data?.data?.projectName : undefined}
+            onChange={(values) => {
+                onSubmit(values)
+            }}
+            onClose={() => {
+                setSubVisible(false)
+            }}
+        />}
+    </div>;
+};
+
+export default inject('store')(observer((props: any) => MomentsCreatePage(props.store)))

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

@@ -0,0 +1,212 @@
+import { Input, Typography } from "antd";
+import { TIME_TYPE } from "../../businessPlan/create/const";
+import ShowContentTable from "../../businessPlan/create/components/massSending/showContentTable";
+import SelectBookLinkButton from "../../bookLink/SelectBookLinkButton";
+import { ColumnsType } from "antd/es/table";
+import { AnyObject } from "antd/es/_util/type";
+
+const { Title, Text, Paragraph } = Typography;
+/** 朋友圈 */
+export const monmentsColumns = (
+    bookPlatForm: TASK_CREATE.BookPlatFormProps[],
+    bookList: TASK_CREATE.BookListProps[],
+    platform: string,
+    previewContent: { [x: string]: any },
+    setPreviewContent: React.Dispatch<React.SetStateAction<{
+        [x: string]: any;
+    }>>
+): ColumnsType<AnyObject> => {
+    return [
+        {
+            title: '账号',
+            dataIndex: 'corpUserGroupName',
+            key: 'corpUserGroupName',
+            width: 150,
+            render(value, record) {
+                return <>
+                    <Title level={5} style={{ margin: 0 }}>{value}</Title>
+                    <Paragraph style={{ margin: 0 }}>公众号:{record?.corpUsers?.[0]?.mpAccountName || '--'}</Paragraph>
+                    <Paragraph style={{ margin: 0 }}>客服号:{record?.corpUsers?.map(i => i.name).join('、')}</Paragraph>
+                </>
+            },
+            onCell: (record) => {
+                return { rowSpan: record.groupRowSpan }
+            }
+        },
+        {
+            title: '朋友圈信息',
+            dataIndex: 'taskName',
+            key: 'taskName',
+            width: 130,
+            render(value, record) {
+                return <>
+                    <Title level={5} style={{ margin: 0 }}>标题:{value}</Title>
+                    {record?.bizType && <Paragraph style={{ margin: 0 }}>业务类型:{record?.bizType === 'novel' ? '小说' : record?.bizType === 'game' ? '游戏' : '<空>'}</Paragraph>}
+                    {record?.platform && <Paragraph style={{ margin: 0 }}>书城:{record?.platform ? bookPlatForm?.find(item => item.id === record?.platform)?.platformName : '<空>'}</Paragraph>}
+                    {record?.templateProductId && <Paragraph style={{ margin: 0 }}>适用产品:{record?.templateProductId ? bookList?.find(item => item.id === record?.templateProductId)?.bookName : '<空>'}</Paragraph>}
+                </>
+            },
+            onCell: (record) => {
+                return { rowSpan: record.groupRowSpan }
+            }
+        },
+        {
+            title: '策略信息',
+            dataIndex: 'strategyData',
+            key: 'strategyData',
+            width: 200,
+            render(value, record) {
+                return <>
+                    <Title level={5} style={{ margin: 0 }}>策略{record.strategyIndex + 1}</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: 'content',
+            key: 'content',
+            width: 200,
+            render(_, record) {
+                return <>
+                    <ShowContentTable
+                        data={record?.content}
+                        name={<><span>内容{record.contentIndex + 1}:</span><span dangerouslySetInnerHTML={{ __html: record.contentReactNode?.map((item) => item).join('、') }}></span></>}
+                    />
+                </>
+            },
+        },
+        {
+            title: '图文链接',
+            dataIndex: 'linkUrl',
+            key: 'linkUrl',
+            width: 250,
+            render(_, record) {
+                const linkUrl = previewContent?.[record?.id]?.linkUrl
+                return record?.linkData?.length > 0 ? <div style={{ display: 'flex', gap: 4, alignItems: 'flex-end' }}>
+                    <Input.TextArea
+                        placeholder="请输入链接"
+                        allowClear
+                        value={linkUrl}
+                        onChange={e => {
+                            const newPreviewContent = JSON.parse(JSON.stringify(previewContent))
+                            const c = previewContent?.[record.id] || {}
+                            newPreviewContent[record.id] = { ...c, linkUrl: e.target.value }
+                            setPreviewContent(newPreviewContent)
+                        }}
+                    />
+                    <SelectBookLinkButton
+                        bookPlatForm={bookPlatForm}
+                        platform={platform}
+                        mpAccountId={record?.mpAccountId}
+                        linkData={record.linkData}
+                        linkList={linkUrl ? {
+                            type: 'link',
+                            data: linkUrl.split(',')
+                        } : undefined}
+                        onChange={(linkStr, miniprogramAppid, miniprogramPage) => {
+                            const msg: { [x: string]: string } = { linkUrl: linkStr }
+                            if (miniprogramAppid && miniprogramPage && record?.miniProgramData?.length > 0 && !previewContent?.[record.id]?.miniprogramAppid && !previewContent?.[record.id]?.miniprogramPage) {
+                                msg.miniprogramAppid = miniprogramAppid
+                                msg.miniprogramPage = miniprogramPage
+                            }
+                            const newPreviewContent = JSON.parse(JSON.stringify(previewContent))
+                            const c = previewContent?.[record.id] || {}
+                            newPreviewContent[record.id] = { ...c, ...msg }
+                            setPreviewContent(newPreviewContent)
+                        }}
+                    />
+                </div> : '--'
+            },
+        },
+        {
+            title: '小程序APPID',
+            dataIndex: 'miniprogramAppid',
+            key: 'miniprogramAppid',
+            width: 200,
+            render(_, record) {
+                return record?.miniProgramData?.length > 0 ? <Input.TextArea
+                    placeholder="请输入小程序APPID"
+                    allowClear
+                    value={previewContent?.[record.id]?.miniprogramAppid}
+                    onChange={e => {
+                        const newPreviewContent = JSON.parse(JSON.stringify(previewContent))
+                        const c = previewContent?.[record.id] || {}
+                        newPreviewContent[record.id] = { ...c, miniprogramAppid: e.target.value }
+                        setPreviewContent(newPreviewContent)
+                    }}
+                /> : '--'
+            },
+        },
+        {
+            title: '小程序路径',
+            dataIndex: 'miniprogramPage',
+            key: 'miniprogramPage',
+            width: 250,
+            render(_, record) {
+                const miniprogramAppid = previewContent?.[record.id]?.miniprogramAppid
+                const miniprogramPage = previewContent?.[record.id]?.miniprogramPage
+                let linkList: {
+                    type: 'link' | 'miniprogram',
+                    data: any[]
+                } = undefined
+                if (miniprogramAppid && miniprogramPage) {
+                    linkList = { type: 'miniprogram', data: [] }
+                    const miniprogramPages = miniprogramPage.split(',')
+                    miniprogramAppid.split(',').map((item, index) => {
+                        linkList.data.push({
+                            miniappId: item,
+                            miniappPagePath: miniprogramPages?.[index]
+                        })
+                    })
+                }
+                return record?.miniProgramData?.length > 0 ? <div style={{ display: 'flex', gap: 4, alignItems: 'flex-end' }}>
+                    <Input.TextArea
+                        placeholder="请输入小程序路径"
+                        allowClear
+                        value={previewContent?.[record.id]?.miniprogramPage}
+                        onChange={e => {
+                            const newPreviewContent = JSON.parse(JSON.stringify(previewContent))
+                            const c = previewContent?.[record.id] || {}
+                            newPreviewContent[record.id] = { ...c, miniprogramPage: e.target.value }
+                            setPreviewContent(newPreviewContent)
+                        }}
+                    />
+                    <SelectBookLinkButton
+                        bookPlatForm={bookPlatForm}
+                        platform={platform}
+                        mpAccountId={record?.mpAccountId}
+                        linkList={linkList}
+                        linkData={record.miniProgramData.map(item => ({
+                            msgType: "TASK_CONTENT_LINK",
+                            link: {
+                                title: item?.miniprogram?.title,
+                                picUrl: item?.miniprogram?.picUrl,
+                                desc: ''
+                            }
+                        }))}
+                        onChange={(_, miniprogramAppid, miniprogramPage) => {
+                            const msg: { [x: string]: string } = {}
+                            if (miniprogramAppid && miniprogramPage) {
+                                msg.miniprogramAppid = miniprogramAppid
+                                msg.miniprogramPage = miniprogramPage
+                                const newPreviewContent = JSON.parse(JSON.stringify(previewContent))
+                                const c = previewContent?.[record.id] || {}
+                                newPreviewContent[record.id] = { ...c, ...msg }
+                                setPreviewContent(newPreviewContent)
+                            }
+                        }}
+                    />
+                </div> : '--'
+            }
+        }
+    ]
+}

+ 30 - 0
src/pages/weComTask/page/moments/create/typings.d.ts

@@ -0,0 +1,30 @@
+declare namespace TASK_MOMENTS_CREATE {
+
+    // 配置保存字段
+    interface SettingsProps {
+        corpUsers?: TASK_CREATE.corpUsersProps[]; // 企微号列表
+        corpUserGroups?: TASK_CREATE.CorpUserGroupProps[];
+        bizType?: string; // 业务类型
+        platform?: number; // 书城
+        channel?: string;   // 渠道
+        templateProductId?: string; // 适用产品
+        massSendingStrategy?: {
+            momentSendName: string;
+            strategyList: any[]
+         };
+    }
+
+    type previewDataProps = any[]
+
+    interface DispatchMomentsTaskCreate {
+        settings: SettingsProps
+        setSettings: React.Dispatch<React.SetStateAction<SettingsProps>>
+        bookPlatForm: BookPlatFormProps[]
+        bookList: BookListProps[]
+        msgJobTypeList: {
+            value: string;
+            label: string;
+        }[]
+        onPreviewReset: () => void
+    }
+}

+ 229 - 0
src/pages/weComTask/page/moments/taskList/index.tsx

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

+ 231 - 0
src/pages/weComTask/page/moments/taskList/log.tsx

@@ -0,0 +1,231 @@
+import { useAjax } from '@/Hook/useAjax';
+import { getProjectLogListApi } from '@/pages/weComTask/API/businessPlan/create';
+import { Card, Drawer, Popover, Spin, Table, Tabs, Tag, Typography } from 'antd';
+import React, { useEffect, useState } from 'react';
+import { STATUS_ZJ, TIME_TYPE_ZJ } from '../../businessPlan/create/const';
+import PreviewTime from '@/pages/weComTask/components/previewTime';
+import FilterUserTooltip from '@/pages/weComTask/components/filterUser/filterUserTooltip';
+import { QuestionCircleFilled } from '@ant-design/icons';
+import style from '../../businessPlan/taskList/index.less'
+import useNewToken from '@/Hook/useNewToken';
+import LookMsg from '@/pages/weComTask/components/previewMsg/lookMsg';
+import { getMsgData } from '../../businessPlan/create/components/massSending/showContent';
+import MomentsXfCorpTabls from './momentsXfCorpTabls';
+import MomentsTaskNotes from './momentsTaskNotes';
+const { Text } = Typography;
+
+interface Props {
+    data: any,
+    bookPlatForm: TASK_CREATE.BookPlatFormProps[]
+    bookList: TASK_CREATE.BookListProps[]
+    visible?: boolean,
+    onClose?: () => void,
+}
+
+/**
+ * 任务日志
+ * @param param0 
+ * @returns 
+ */
+const Log: React.FC<Props> = ({ data, bookPlatForm, bookList, visible, onClose }) => {
+
+    /******************************************************/
+    const [previewData, setPreviewData] = useState<{ momentsTaskVOList?: any[] }>({})
+    const getProjectLogList = useAjax((params) => getProjectLogListApi(params))
+    /******************************************************/
+
+    useEffect(() => {
+        console.log(data.id)
+        getProjectLogList.run(data.id).then(res => {
+            if (res?.data) {
+                setPreviewData(res.data)
+            }
+        })
+    }, [])
+
+    return <Drawer
+        title={<strong>{data.projectName} 任务详情</strong>}
+        onClose={onClose}
+        open={visible}
+        width={1400}
+        styles={{ body: { paddingTop: 5 } }}
+    >
+        <Table
+            dataSource={previewData?.momentsTaskVOList || []}
+            columns={[
+                {
+                    title: '任务名称',
+                    dataIndex: 'momentJobTitle',
+                    key: 'momentJobTitle',
+                    width: 100,
+                    ellipsis: true,
+                    align: 'center'
+                },
+                {
+                    title: '企业ID',
+                    dataIndex: 'corpId',
+                    key: 'corpId',
+                    width: 100,
+                    ellipsis: true,
+                    align: 'center'
+                },
+                {
+                    title: '客服号',
+                    dataIndex: 'corpMomentVisibleCorpUserVOList',
+                    key: 'corpMomentVisibleCorpUserVOList',
+                    width: 100,
+                    ellipsis: true,
+                    align: 'center',
+                    render(value) {
+                        return value?.map(item => item?.relationName)?.join('、')
+                    },
+                },
+                {
+                    title: '状态',
+                    dataIndex: 'jobStatus',
+                    key: 'jobStatus',
+                    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: 'createUserName',
+                    key: 'createUserName',
+                    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
+            loading={getProjectLogList.loading}
+            pagination={{
+                total: previewData?.momentsTaskVOList?.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} />
+            }}
+        />
+    </Drawer>
+};
+
+
+export const ExpandedRow: React.FC<{ record: any }> = ({ record }) => {
+
+    const { corpMomentContentList, id, corpId } = record
+    const { token } = useNewToken()
+    const [activeKey, setActiveKey] = useState<string>('2')
+
+    return <div style={{ display: 'flex', gap: 10, maxHeight: 450 }}>
+        <div style={{ width: 320, maxHeight: 450, overflow: 'hidden', overflowY: 'auto' }}>
+            <PreviewTime
+                {...record}
+            />
+            <h2>群发内容</h2>
+            {corpMomentContentList?.map((item, index) => <div key={index} style={{ marginTop: 5, backgroundColor: '#d9d9d9', borderRadius: 6, padding: '10px 10px 16px' }}>
+                <h3 style={{ marginTop: 0, textAlign: 'center' }}>内容{index + 1}</h3>
+                <LookMsg data={getMsgData(item, token)} />
+            </div>)}
+        </div>
+        <div style={{ flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column', gap: 10, height: '100%' }}>
+            <Tabs
+                destroyInactiveTabPane={false}
+                onChange={(key) => {
+                    setActiveKey(key)
+                }}
+                activeKey={activeKey}
+                type="card"
+                className="aaa"
+                items={[
+                    {
+                        key: '2',
+                        label: '下发企微号',
+                        children: <Card styles={{ body: { maxHeight: 400 } }}>
+                            {activeKey == '2' && <MomentsXfCorpTabls data={record} />}
+                        </Card>
+                    },
+                    {
+                        key: '4',
+                        label: '群发记录',
+                        children: <Card styles={{ body: { minHeight: 400 } }}>
+                            {activeKey == '4' && <MomentsTaskNotes data={record} />}
+                        </Card>
+                    }
+                ]}
+            />
+        </div>
+    </div>
+}
+
+
+export default Log;

+ 72 - 0
src/pages/weComTask/page/moments/taskList/momentsTaskNotes.tsx

@@ -0,0 +1,72 @@
+import { useAjax } from '@/Hook/useAjax';
+import useNewToken from '@/Hook/useNewToken';
+import { getMomentsSendLogApi } from '@/pages/weComTask/API/businessPlan/create';
+import { Button, Table, Tag } from 'antd';
+import React, { useEffect, useState } from 'react';
+import { sendLogTableConfig } from './weTableConfig';
+
+interface Props {
+    data: any
+}
+
+/**
+ * 朋友圈发送记录
+ * @returns 
+ */
+const MomentsTaskNotes: React.FC<Props> = ({ data }) => {
+
+    /*************************************/
+    const { token } = useNewToken()
+    const [queryForm, setQueryForm] = useState<{ pageNum: number, pageSize: number, corpMomentId: number }>({
+        pageNum: 1,
+        pageSize: 20,
+        corpMomentId: data.id,
+    });
+
+    const getMomentsSendLog = useAjax((params) => getMomentsSendLogApi(params))
+    /*************************************/
+
+    useEffect(() => {
+        if (queryForm.corpMomentId) {
+            getMomentsSendLog.run(queryForm)
+        }
+    }, [queryForm])
+
+    return (
+        <div>
+            <div style={{ marginBottom: 10 }}>
+                <Button type='link' style={{ padding: 0, height: 'auto', lineHeight: 'normal' }} loading={getMomentsSendLog.loading} onClick={() => getMomentsSendLog.refresh()}>刷新</Button>
+            </div>
+            <Table
+                dataSource={getMomentsSendLog?.data?.data?.records}
+                loading={getMomentsSendLog?.loading}
+                columns={sendLogTableConfig()}
+                scroll={{ x: 400, y: 280 }}
+                rowKey={(s) => {
+                    return s.id
+                }}
+                size='small'
+                onRow={(row) => {
+                    return !row.status ? {
+                        style: { background: token.colorPrimaryBgHover }
+                    } : {}
+                }}
+                pagination={{
+                    total: getMomentsSendLog?.data?.data?.total,
+                    showTotal: (total) => <Tag color="cyan">总共{total}数据</Tag>,
+                    showSizeChanger: true,
+                    showLessItems: true,
+                    defaultCurrent: 1,
+                    defaultPageSize: 200,//默认初始的每页条数
+                    current: getMomentsSendLog?.data?.data?.current || 1,
+                    pageSize: getMomentsSendLog?.data?.data?.size || 20,
+                    onChange: (page, pageSize) => {
+                        setQueryForm({ ...queryForm, pageNum: page, pageSize })
+                    }
+                }}
+            />
+        </div>
+    );
+};
+
+export default MomentsTaskNotes;

+ 65 - 0
src/pages/weComTask/page/moments/taskList/momentsXfCorpTabls.tsx

@@ -0,0 +1,65 @@
+import { useAjax } from '@/Hook/useAjax';
+import useNewToken from '@/Hook/useNewToken';
+import { getMomentsCorpUserListApi } from '@/pages/weComTask/API/businessPlan/create';
+import { Table, Tag } from 'antd';
+import React, { useEffect, useState } from 'react';
+import { WelcomeMsgCorpUserListConfig } from './weTableConfig';
+
+interface Props {
+    data: any
+}
+
+/**
+ * 朋友圈下发企微号
+ * @returns 
+ */
+const MomentsXfCorpTabls: React.FC<Props> = ({ data }) => {
+
+    /*************************************/
+    const { token } = useNewToken()
+    const [queryForm, setQueryForm] = useState<{ pageNum: number, pageSize: number, corpMomentId: number }>({
+        pageNum: 1,
+        pageSize: 20,
+        corpMomentId: data.id,
+    });
+
+    const getMomentsCorpUserList = useAjax((params) => getMomentsCorpUserListApi(params))
+    /*************************************/
+
+    useEffect(() => {
+        if (queryForm.corpMomentId) {
+            getMomentsCorpUserList.run(queryForm)
+        }
+    }, [queryForm])
+
+    return <Table
+        dataSource={getMomentsCorpUserList?.data?.data?.records}
+        loading={getMomentsCorpUserList?.loading}
+        columns={WelcomeMsgCorpUserListConfig()}
+        scroll={{ x: 400, y: 280 }}
+        rowKey={(s) => {
+            return s.id
+        }}
+        size='small'
+        onRow={(row) => {
+            return !row.status ? {
+                style: { background: token.colorPrimaryBgHover }
+            } : {}
+        }}
+        pagination={{
+            total: getMomentsCorpUserList?.data?.data?.total,
+            showTotal: (total) => <Tag color="cyan">总共{total}数据</Tag>,
+            showSizeChanger: true,
+            showLessItems: true,
+            defaultCurrent: 1,
+            defaultPageSize: 200,//默认初始的每页条数
+            current: getMomentsCorpUserList?.data?.data?.current || 1,
+            pageSize: getMomentsCorpUserList?.data?.data?.size || 20,
+            onChange: (page, pageSize) => {
+                setQueryForm({ ...queryForm, pageNum: page, pageSize })
+            }
+        }}
+    />;
+};
+
+export default React.memo(MomentsXfCorpTabls);

+ 127 - 0
src/pages/weComTask/page/moments/taskList/tableConfig.tsx

@@ -0,0 +1,127 @@
+import { Flex, Popconfirm, Popover, Space, Typography } from "antd";
+import { AnyObject } from "antd/es/_util/type";
+import { ColumnsType } from "antd/es/table";
+import style from '../../businessPlan/taskList/index.less'
+import PreviewMomentsContent from "../create/components/content/previewMomentsContent";
+import { QuestionCircleFilled } from "@ant-design/icons";
+import PreviewMomentsStrategy from "../create/components/Strategy/previewMomentsStrategy";
+
+const { Text, Paragraph } = Typography;
+const taskListColumns = (
+    handleLog: (data: any) => void,
+    handleCopy: (data: any, isCopy: boolean) => void,
+    handleDel: (data: any, type: 'del' | 'cancel' | 'open') => void,
+): ColumnsType<AnyObject> => {
+
+    return [
+        {
+            title: '操作',
+            dataIndex: 'cz',
+            key: 'cz',
+            width: 160,
+            render(_, record) {
+                return <Space>
+                    {record?.status === 1 ? <Popconfirm
+                        title="确定暂停?"
+                        onConfirm={() => { handleDel({ projectIds: [record.id] }, 'cancel') }}
+                    >
+                        <a style={{ color: 'orange' }}>暂停任务</a>
+                    </Popconfirm> : record?.status === 3 ? <Popconfirm
+                        title="确定启用?"
+                        onConfirm={() => { handleDel({ projectIds: [record.id] }, 'open') }}
+                    >
+                        <a style={{ color: '#87d068' }}>启用任务</a>
+                    </Popconfirm> : undefined}
+                    <a onClick={() => handleCopy(record, true)}>复制</a>
+                    <a onClick={() => handleCopy(record, false)}>编辑</a>
+                    <a onClick={() => handleLog(record)}>详情</a>
+                    <Popconfirm
+                        title="确定删除?"
+                        onConfirm={() => { handleDel({ projectIds: [record.id] }, 'del') }}
+                    >
+                        <a style={{ color: 'red' }}>删除</a>
+                    </Popconfirm>
+                </Space>
+            },
+        },
+        {
+            title: '任务名称',
+            dataIndex: 'projectName',
+            key: 'projectName',
+            width: 120,
+            ellipsis: true
+        },
+        {
+            title: '基础信息',
+            dataIndex: 'bizType',
+            key: 'bizType',
+            width: 180,
+            render: (_, record) => {
+                return <Paragraph style={{ margin: 0 }} ellipsis={{ tooltip: true }}>
+                    业务类型:{record?.bizType === 'novel' ? '小说' : '<空>'}-书城:{record?.platformName || '<空>'}-适用产品:{record?.templateProductName || '<空>'}
+                </Paragraph>
+            }
+        },
+        {
+            title: '朋友圈配置',
+            dataIndex: 'momentCreateDTO',
+            key: 'momentCreateDTO',
+            width: 170,
+            ellipsis: true,
+            render: (value) => {
+                if (value && Object.keys(value)?.length > 0) {
+                    return <div className={style.nameBox}>
+                        <div>
+                            <Text ellipsis>{value?.momentSendName || '<空>'}</Text>
+                        </div>
+                        <Popover
+                            placement="left"
+                            content={<div>
+                                <PreviewMomentsStrategy
+                                    massSendingStrategy={value}
+                                />
+                            </div>}
+                            styles={{ body: { width: 360, overflow: 'hidden', overflowY: 'auto', maxHeight: 400 } }}
+                        >
+                            <a><QuestionCircleFilled /></a>
+                        </Popover>
+                        <Popover
+                            placement="left"
+                            content={<div>
+                                <PreviewMomentsContent
+                                    strategyList={value.strategyList}
+                                />
+                            </div>}
+                            styles={{ body: { width: 360, overflow: 'hidden', overflowY: 'auto', maxHeight: 400 } }}
+                        >
+                            <a><QuestionCircleFilled /></a>
+                        </Popover>
+                    </div>
+                }
+                return <Text type="danger">当前没有群发配置</Text>
+            }
+        },
+        {
+            title: '创建时间',
+            dataIndex: 'createTime',
+            key: 'createTime',
+            align: 'center',
+            width: 125,
+            ellipsis: true
+        },
+        {
+            title: '任务数量',
+            dataIndex: 'taskCount',
+            key: 'taskCount',
+            width: 260,
+            render(value, record) {
+                return <Flex gap={6}>
+                    <Text type={record.momentCreateCount > 0 ? 'success' : 'danger'}>朋友圈:{record.momentCreateCount}</Text>
+                </Flex>
+            },
+        },
+    ]
+}
+
+
+export default taskListColumns

+ 213 - 0
src/pages/weComTask/page/moments/taskList/weTableConfig.tsx

@@ -0,0 +1,213 @@
+import { Avatar, Badge, Space } from "antd";
+import { ColumnsType } from "antd/es/table";
+import { UserOutlined } from '@ant-design/icons';
+export function WelcomeMsgCorpUserListConfig(): ColumnsType<any> {
+    const arr: ColumnsType<any> = [
+        {
+            title: '企微号',
+            dataIndex: 'corpUserName',
+            key: 'corpUserName',
+            align: 'center',
+            width: 80,
+            ellipsis: true,
+        },
+        {
+            title: '所属企业',
+            dataIndex: 'corpName',
+            key: 'corpName',
+            align: 'center',
+            width: 120,
+            ellipsis: true,
+        },
+        {
+            title: '预计送达数',
+            dataIndex: 'sendCount',
+            key: 'sendCount',
+            align: 'center',
+            width: 90,
+            ellipsis: true,
+            render:(a)=>{
+                return a > 0 ? a : 0
+            }
+        },
+        {
+            title: '消息ID',
+            dataIndex: 'jobId',
+            key: 'jobId',
+            align: 'center',
+            width: 65,
+            ellipsis: true,
+        },
+        {
+            title: '发送成功',
+            dataIndex: 'sendSuccessCount',
+            key: 'sendSuccessCount',
+            align: 'center',
+            width: 80,
+            render:(a)=>{
+                return a > 0 ? a : 0
+            }
+        },
+        {
+            title: '发送失败',
+            dataIndex: 'sendFailedCount',
+            key: 'sendFailedCount',
+            align: 'center',
+            width: 80,
+            render:(a)=>{
+                return a > 0 ? a : 0
+            }
+        },
+        {
+            title: '评论数',
+            dataIndex: 'commentCount',
+            key: 'commentCount',
+            align: 'center',
+            width: 65,
+            ellipsis: true,
+        },
+        {
+            title: '点赞数',
+            dataIndex: 'likeCount',
+            key: 'likeCount',
+            align: 'center',
+            width: 65,
+            ellipsis: true,
+        },
+        {
+            title: '发送时间',
+            dataIndex: 'sendTime',
+            key: 'sendTime',
+            align: 'center',
+            width: 80,
+        },
+        {
+            title: '发送状态',
+            dataIndex: 'sendStatus',
+            key: 'sendStatus',
+            align: 'center',
+            width: 80,
+            render: (a) => {
+                return <Badge text={a === 1 ? '已确认发送' : '未确认发送'} status={a === 1 ? 'success' : 'error'} />
+            }
+        }
+    ]
+    return arr
+}
+export function WeTableConfig(): ColumnsType<any> {
+    return [
+        {
+            title: '企微号',
+            dataIndex: 'corpUserName',
+            key: 'corpUserName',
+            align: 'center',
+            width: 80,
+        },
+        {
+            title: '所属企业',
+            dataIndex: 'corpName',
+            key: 'corpName',
+            align: 'center',
+            width: 120,
+        },
+        {
+            title: '投手',
+            dataIndex: 'putUserName',
+            key: 'putUserName',
+            align: 'center',
+            width: 80,
+        },
+    ]
+}
+
+export function corpExternalUserListConfig(): ColumnsType<any> {
+    return [
+        {
+            title: '客户',
+            dataIndex: 'name',
+            key: 'name',
+            width: 90,
+            render: (a: string, b: any) => {
+                return <Space>
+                    <Avatar shape="square" size="small" icon={<UserOutlined />} src={b?.avatar} />
+                    <span>{a}</span>
+                </Space>
+            }
+        },
+        {
+            title: '性别',
+            dataIndex: 'gender',
+            key: 'gender',
+            align: 'center',
+            width: 80,
+            render: (a) => {
+                return a === 1 ? '男性' : a === 2 ? '女性' : '未知'
+            }
+        },
+        {
+            title: '所属企业',
+            dataIndex: 'corpName',
+            key: 'corpName',
+            align: 'center',
+            width: 120,
+        },
+        {
+            title: '企微号',
+            dataIndex: 'corpUserName',
+            key: 'corpUserName',
+            align: 'center',
+            width: 80,
+        },
+        {
+            title: '状态',
+            dataIndex: 'sendStatus',
+            key: 'sendStatus',
+            align: 'center',
+            width: 80,
+            render: (a, b) => {
+                return <Badge text={a ? '已送达' : '未送达'} status={a ? 'success' : 'default'} />
+            }
+        },
+        {
+            title: '消息ID',
+            dataIndex: 'jobId',
+            key: 'jobId',
+            align: 'center',
+            width: 65,
+            ellipsis: true,
+        },
+    ]
+}
+
+
+export function sendLogTableConfig(): ColumnsType<any> {
+    return [
+        {
+            title: '消息ID',
+            dataIndex: 'jobId',
+            key: 'jobId',
+            width: 70,
+            ellipsis: true
+        },
+        {
+            title: '发送时间',
+            dataIndex: 'createTime',
+            key: 'createTime',
+            width: 110
+        },
+        {
+            title: '任务日志',
+            dataIndex: 'jobLog',
+            key: 'jobLog',
+            width: 250,
+            ellipsis: true
+        },
+        {
+            title: '任务状态',
+            dataIndex: 'jobStatus',
+            key: 'jobStatus',
+            width: 110,
+            ellipsis: true
+        },
+    ]
+}

+ 29 - 0
src/pages/weComTask/page/weAssociation/list/index.tsx

@@ -14,6 +14,7 @@ import RemarkSet from './remarkSet';
 import DisbandChatLog from './disbandChatLog';
 import ZpMp from './zpMp';
 import ChatLog from './chatLog';
+import { getBindMpListApi } from '@/pages/weComTask/API/corpUserAssign';
 
 const WeAssociationList: React.FC = () => {
 
@@ -51,10 +52,12 @@ const WeAssociationList: React.FC = () => {
     const getGroupChatCount = useAjax((params) => getGroupChatCountApi(params))
     const allOfMember = useAjax(() => getAdAccountAllOfMember())
     const disbandChat = useAjax((params) => disbandChatApi(params))
+    const getBindMpList = useAjax(() => getBindMpListApi())
     /************************************/
 
     useEffect(() => {
         allOfMember.run()
+        getBindMpList.run()
     }, [])
 
     useEffect(() => {
@@ -255,6 +258,32 @@ const WeAssociationList: React.FC = () => {
                                 setListData({ ...listData, remark: v })
                             }}
                         />
+                        <Select
+                            style={{ width: 180 }}
+                            placeholder="公众号绑定状态查询"
+                            value={listData?.mpAccountIdIsNull}
+                            showSearch
+                            allowClear
+                            disabled={!!listData?.mpAccountId}
+                            onChange={(e) => {
+                                setListData({ ...listData, mpAccountIdIsNull: e, mpAccountId: undefined, pageNum: 1 })
+                            }}
+                            filterOption={(input: string, option) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())}
+                            options={[{ label: '查询所有群聊', value: false }, { label: '查询没有绑定公众号群聊', value: true }]}
+                        />
+                        <Select
+                            style={{ width: 180 }}
+                            placeholder="公众号"
+                            value={listData?.mpAccountId}
+                            showSearch
+                            allowClear
+                            disabled={(listData?.mpAccountIdIsNull === false || listData?.mpAccountIdIsNull === true)}
+                            onChange={(e) => {
+                                setListData({ ...listData, mpAccountId: e, mpAccountIdIsNull: undefined, pageNum: 1 })
+                            }}
+                            filterOption={(input: string, option: any) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())}
+                            options={getBindMpList?.data?.data?.map((item: any) => ({ label: item.name, value: item.id }))}
+                        />
                     </>
                 </SearchBox>
                 <div className={style.corpUserList_table} ref={ref}>

Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/public/svg/moments.svg


Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff