wjx пре 1 дан
родитељ
комит
5a75af71b8

+ 52 - 0
src/pages/weComTask/API/logs/index.tsx

@@ -68,4 +68,56 @@ export function getPullGroupListApi(data: GetPullGroupListProps) {
         method: 'POST',
         data
     })
+}
+
+export interface GetSendUserListProps {
+    pageNum: number,
+    pageSize: number,
+    projectType?: 1 | 2,
+    msgId?: string,
+    status?: 0 | 2,
+    projectName?: string,
+    taskId?: any,
+    expectStartDay?: string,
+    expectEndDay?: string
+    startDay?: string,
+    endDay?: string,
+    projectGroupIds?: string[]
+}
+
+/**
+ * 下发企微号
+ * @param data 
+ * @returns 
+ */
+export function getSendUserListApi(data: GetSendUserListProps) {
+    return request({
+        url: api + `/corpOperation/corp/stats/listOfPage/send/user`,
+        method: 'POST',
+        data
+    })
+}
+
+export interface GetSendUserLogListProps {
+    pageNum: number,
+    pageSize: number,
+    projectType?: 1 | 2,
+    projectName?: string,
+    taskId?: any,
+    startDay?: string,
+    endDay?: string,
+    projectGroupIds?: string[]
+}
+
+/**
+ * 群发记录
+ * @param data 
+ * @returns 
+ */
+export function getSendUserLogListApi(data: GetSendUserLogListProps) {
+    return request({
+        url: api + `/corpOperation/corp/stats/listOfPage/send/user/log`,
+        method: 'POST',
+        data
+    })
 }

+ 724 - 0
src/pages/weComTask/page/home/groupItem.tsx

@@ -0,0 +1,724 @@
+import React, { useEffect, useState } from "react";
+import style from './index.less';
+import { Avatar, Badge, Button, Card, DatePicker, Drawer, Input, Modal, Select, Space, Table, Tabs, Tag, Tooltip } from "antd";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { IconDefinition } from "@fortawesome/free-solid-svg-icons";
+import { getProjectGroupsAllListApi } from "../../API/groupManage";
+import { useAjax } from "@/Hook/useAjax";
+import { getSendUserListApi, GetSendUserListProps, getSendUserLogListApi, GetSendUserLogListProps } from "../../API/logs";
+import SearchBox from "../../components/searchBox";
+import { ExclamationCircleOutlined, InfoCircleOutlined, QuestionCircleOutlined, SearchOutlined } from "@ant-design/icons";
+import dayJs from "dayjs";
+import useNewToken from "@/Hook/useNewToken";
+import { copy } from "@/utils/utils";
+import GroupErrorCountList from "../businessPlan/taskList/components/groupTask/groupErrorCountList";
+import PreviewMsg from "../../components/previewMsg";
+import { emoList } from "../../components/textEditor/Expression";
+
+
+interface IProps {
+    title: string
+    icon: IconDefinition
+    taskTotal: number
+    todayAddTask: number
+    todayFinishTaskRate: number
+    todayFailTask: number
+    projectType?: 1 | 2
+}
+
+const GroupItem: React.FC<IProps> = ({ title, icon, taskTotal, todayAddTask, todayFinishTaskRate, todayFailTask, projectType }) => {
+
+    /***********************************************/
+    const [visible, setVisible] = useState<boolean>(false);
+    const [groupList, setGroupList] = useState<{ label: string, value: number }[]>([]);
+    const [activeKey, setActiveKey] = useState<string>('2')
+
+    const getProjectGroupsAllList = useAjax(() => getProjectGroupsAllListApi())
+    /***********************************************/
+
+    useEffect(() => {
+        getProjectGroupsAllList.run().then(res => {
+            setGroupList([{ label: '空项目组', value: 0 }, ...res?.data?.map(item => ({ label: item.name, value: item.id })) || []])
+        })
+    }, [])
+
+    return <>
+        <div className={style.moduleCard} onClick={() => setVisible(true)}>
+            <div className={style.moduleCard_header}>
+                <Avatar style={projectType === 1 ? { backgroundColor: '#f3e8ff', color: '#9333ea' } : projectType === 2 ? { backgroundColor: '#fff0f6', color: '#f759ab' } : { backgroundColor: '#dbeafe', color: '#2563eb' }} size={30}>
+                    <FontAwesomeIcon icon={icon} />
+                </Avatar>
+                {title}
+            </div>
+            <div className={style.moduleCard_center}>
+                总任务数:{taskTotal || 0} | 今日新增:{todayAddTask || 0}
+            </div>
+            <div className={style.moduleCard_footer}>
+                <span>今日完成率:{((todayFinishTaskRate || 0) * 100).toFixed(2)} %</span>
+                <span className={style.error}>今日异常:{todayFailTask}</span>
+            </div>
+        </div>
+
+        <Drawer
+            title={title}
+            onClose={() => setVisible(false)}
+            open={visible}
+            width={'70%'}
+        >
+            <Tabs
+                destroyOnHidden={false}
+                onChange={(key) => {
+                    setActiveKey(key)
+                }}
+                activeKey={activeKey}
+                type="card"
+                items={[
+                    {
+                        key: '2',
+                        label: '下发企微号',
+                        children: <>
+                            {activeKey == '2' && <GroupXfCorpTabls groupList={groupList} projectType={projectType} />}
+                        </>
+                    },
+                    {
+                        key: '4',
+                        label: '群发记录',
+                        children: <>
+                            {activeKey == '4' && <GroupTaskNotes groupList={groupList} projectType={projectType} />}
+                        </>
+                    }
+                ]}
+            />
+
+        </Drawer>
+    </>
+}
+
+/**
+ * 下发企微号
+ * @param param0 
+ * @returns 
+ */
+const GroupXfCorpTabls: React.FC<{ groupList: { label: string, value: number }[], projectType?: 1 | 2 }> = ({ groupList, projectType }) => {
+
+    /***********************************/
+    const taskType = projectType === 1 ? 'TASK_STAT_GROUP_SEND_CHAT' : projectType === 2 ? 'TASK_STAT_GROUP_ROBOT' : 'TASK_STAT_GROUP_SEND'
+    const [queryParams, setQueryParams] = useState<GetSendUserListProps>({ pageNum: 1, pageSize: 20 })
+    const [oldQueryParams, setOldQueryParams] = useState<GetSendUserListProps>({ pageNum: 1, pageSize: 20 })
+    const { token } = useNewToken()
+    const [modal, contextHolder] = Modal.useModal();
+
+    const getSendUserList = useAjax((params) => getSendUserListApi(params))
+    /***********************************/
+
+    useEffect(() => {
+        getSendUserList.run({ ...queryParams, projectType })
+    }, [queryParams, projectType])
+
+    return <div>
+        {contextHolder}
+        <SearchBox
+            bodyPadding={`0 0 10px`}
+            buttons={<>
+                <Button type="primary" onClick={() => {
+                    setQueryParams({ ...oldQueryParams, pageNum: 1, pageSize: queryParams.pageSize })
+                }} loading={getSendUserList.loading} icon={<SearchOutlined />}>搜索</Button>
+            </>}
+        >
+            <>
+                <Input
+                    placeholder="消息ID"
+                    style={{ width: 120 }}
+                    allowClear
+                    onChange={(e) => setOldQueryParams({ ...oldQueryParams, msgId: e.target.value })}
+                    value={oldQueryParams?.msgId}
+                />
+                <Input
+                    placeholder="任务名称"
+                    style={{ width: 120 }}
+                    allowClear
+                    onChange={(e) => setOldQueryParams({ ...oldQueryParams, projectName: e.target.value })}
+                    value={oldQueryParams?.projectName}
+                />
+                <Input
+                    placeholder="子任务ID"
+                    style={{ width: 120 }}
+                    allowClear
+                    onChange={(e) => setOldQueryParams({ ...oldQueryParams, taskId: e.target.value })}
+                    value={oldQueryParams?.taskId}
+                />
+                <Select
+                    showSearch
+                    style={{ minWidth: 100 }}
+                    allowClear
+                    onChange={(value) => {
+                        setOldQueryParams({ ...oldQueryParams, status: value })
+                    }}
+                    value={oldQueryParams?.status}
+                    placeholder="发送状态"
+                    filterOption={(input, option) =>
+                        ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                    }
+                    options={[{ label: <Badge status="error" text='未发送' />, value: 0 }, { label: <Badge status="success" text='已发送' />, value: 2 }]}
+                />
+                <Select
+                    placeholder='请选择项目组'
+                    allowClear
+                    options={groupList}
+                    showSearch
+                    maxTagCount={1}
+                    style={{ minWidth: 150 }}
+                    filterOption={(input, option) =>
+                        (option?.label ?? '').toLowerCase().includes(input.toLowerCase())
+                    }
+                    mode="multiple"
+                    value={oldQueryParams?.projectGroupIds}
+                    onChange={(value) => setOldQueryParams({ ...oldQueryParams, projectGroupIds: value })}
+                />
+                <DatePicker
+                    placeholder="发送开始时间"
+                    onChange={(e, date) => { setOldQueryParams({ ...oldQueryParams, startDay: date as string }) }}
+                    allowClear
+                    value={oldQueryParams?.startDay ? dayJs(oldQueryParams?.startDay) : undefined}
+                />
+                <DatePicker
+                    placeholder="发送结束时间"
+                    onChange={(e, date) => { setOldQueryParams({ ...oldQueryParams, endDay: date as string }) }}
+                    allowClear
+                    value={oldQueryParams?.endDay ? dayJs(oldQueryParams?.endDay) : undefined}
+                />
+                <DatePicker.RangePicker
+                    placeholder={['预计送达时间(起始)', '预计送达时间(结束)']}
+                    onChange={(e, date) => { setOldQueryParams({ ...oldQueryParams, expectStartDay: date[0], expectEndDay: date[1] }) }}
+                    allowClear
+                    value={oldQueryParams?.expectStartDay ? [dayJs(oldQueryParams?.expectStartDay), dayJs(oldQueryParams?.expectEndDay)] : undefined}
+                />
+            </>
+        </SearchBox>
+
+        {/* 表 */}
+        <Table
+            style={{ marginTop: 10 }}
+            dataSource={getSendUserList?.data?.data?.records}
+            loading={getSendUserList?.loading}
+            bordered
+            columns={[
+                {
+                    title: '操作',
+                    dataIndex: 'cz',
+                    key: 'cz',
+                    width: 100,
+                    align: 'center',
+                    render: (_, record: any) => {
+                        return <Space>
+                            <a onClick={() => {
+                                modal.confirm({
+                                    title: '确认',
+                                    icon: <ExclamationCircleOutlined />,
+                                    content: '是否跳转到日志详情?',
+                                    okText: '确认',
+                                    cancelText: '复制',
+                                    onCancel: () => {
+                                        copy(record.projectName + '任务Id:' + record.taskId)
+                                    },
+                                    onOk: () => {
+                                        localStorage.setItem('OPENTASK', JSON.stringify({
+                                            id: record.projectId,
+                                            projectName: record.projectName,
+                                            taskType,
+                                            taskId: record.taskId,
+                                            msg: '跳转日志详情'
+                                        }))
+                                        switch (taskType) {
+                                            case 'TASK_STAT_GROUP_SEND_CHAT': // 官方群群发
+                                                window.open(`/weComTask#/weComTask/groupChatSend/official/taskList`)
+                                                break
+                                            case 'TASK_STAT_GROUP_ROBOT': // 机器人群发
+                                                window.open(`/weComTask#/weComTask/groupChatSend/robot/taskList`)
+                                                break
+                                            case 'TASK_STAT_GROUP_SEND': // 官方群发
+                                                window.open(`/weComTask#/weComTask/businessPlan/taskList`)
+                                                break
+                                        }
+                                    },
+                                });
+                            }}>任务跳转</a>
+                        </Space>
+                    }
+                },
+                {
+                    title: '任务名称',
+                    dataIndex: 'projectName',
+                    key: 'projectName',
+                    width: 180,
+                    ellipsis: true,
+                    render: (text) => <a onClick={() => copy(text)}>{text}</a>
+                },
+                {
+                    title: '子任务ID',
+                    dataIndex: 'taskId',
+                    key: 'taskId',
+                    width: 70,
+                    align: 'center'
+                },
+                {
+                    title: '项目组名称',
+                    dataIndex: 'projectGroupName',
+                    key: 'projectGroupName',
+                    width: 100,
+                    ellipsis: true,
+                    align: 'center',
+                    render: (text) => text ? <a onClick={() => copy(text)}>{text}</a> : '--'
+                },
+                {
+                    title: '项目组ID',
+                    dataIndex: 'projectGroupId',
+                    key: 'projectGroupId',
+                    width: 70,
+                    align: 'center'
+                },
+                {
+                    title: '所属企业',
+                    dataIndex: 'corpName',
+                    key: 'corpName',
+                    align: 'center',
+                    width: 120,
+                    ellipsis: true
+                },
+                {
+                    title: '企微号',
+                    dataIndex: 'userName',
+                    key: 'userName',
+                    align: 'center',
+                    width: 120,
+                    ellipsis: true,
+                    render: (a: string, b: any) => {
+                        return a + `(${b?.userId})`
+                    }
+                },
+                {
+                    title: '发送时间',
+                    dataIndex: 'sendTime',
+                    key: 'sendTime',
+                    align: 'center',
+                    width: 120,
+                    ellipsis: true,
+                },
+                {
+                    title: '预计发送时间',
+                    dataIndex: 'createTime',
+                    key: 'createTime',
+                    align: 'center',
+                    width: 120,
+                    ellipsis: true,
+                },
+                {
+                    title: '发送状态',
+                    dataIndex: 'status',
+                    key: 'status',
+                    align: 'center',
+                    width: 100,
+                    render: (a: number) => {
+                        return <span>{{ '0': <Badge status="error" text='未发送' />, '2': <Badge status="success" text='已发送' /> }[a]}</span>
+                    }
+                },
+                {
+                    title: '发送对象',
+                    dataIndex: 'userCount',
+                    key: 'userCount',
+                    align: 'center',
+                    width: 100,
+                    render: (a: number) => {
+                        return <span>好友{a > 0 ? a : 0}个</span>
+                    }
+                },
+                {
+                    title: '发送成功',
+                    dataIndex: 'sendSuccessCount',
+                    key: 'sendSuccessCount',
+                    align: 'center',
+                    width: 70,
+                    render: (a) => {
+                        return a > 0 ? a : 0
+                    }
+                },
+                {
+                    title: '发送失败',
+                    dataIndex: 'sendFailCount',
+                    key: 'sendFailCount',
+                    align: 'center',
+                    width: 70,
+                    render: (a) => {
+                        return a > 0 ? a : 0
+                    }
+                },
+                {
+                    title: '因客户不是好友导致发送失败',
+                    dataIndex: 'unFriendCount',
+                    key: 'unFriendCount',
+                    align: 'center',
+                    width: 70,
+                    render: (a) => {
+                        return a > 0 ? a : 0
+                    }
+                },
+                {
+                    title: '因客户已经收到其他群发消息导致发送失败',
+                    dataIndex: 'othersSendCount',
+                    key: 'othersSendCount',
+                    align: 'center',
+                    width: 95,
+                    render: (a) => {
+                        return a > 0 ? a : 0
+                    }
+                },
+                {
+                    title: '消息ID',
+                    dataIndex: 'msgId',
+                    key: 'msgId',
+                    align: 'center',
+                    width: 150,
+                    ellipsis: true,
+                    render: (a: string) => {
+                        return <a onClick={() => copy(a)}>{a}</a>
+                    }
+                }
+            ]}
+            scroll={{ x: 1000 }}
+            rowKey={'id'}
+            size='small'
+            onRow={(row: any) => {
+                return !row?.status ? {
+                    style: { background: token.colorPrimaryBgHover }
+                } : {}
+            }}
+            pagination={{
+                total: getSendUserList?.data?.data?.total,
+                showTotal: (total) => <Tag color="cyan">总共{total}数据</Tag>,
+                showSizeChanger: true,
+                showLessItems: true,
+                defaultCurrent: 1,
+                defaultPageSize: 200,//默认初始的每页条数
+                current: getSendUserList?.data?.data?.current || 1,
+                pageSize: getSendUserList?.data?.data?.size || 20,
+                onChange: (page, pageSize) => {
+                    setQueryParams({ ...queryParams, pageNum: page, pageSize })
+                    setOldQueryParams({ ...oldQueryParams, pageNum: page, pageSize })
+                }
+            }}
+        />
+    </div>
+}
+
+
+const GroupTaskNotes: React.FC<{ groupList: { label: string, value: number }[], projectType?: 1 | 2 }> = ({ groupList, projectType }) => {
+
+    /***********************************/
+    const taskType = projectType === 1 ? 'TASK_STAT_GROUP_SEND_CHAT' : projectType === 2 ? 'TASK_STAT_GROUP_ROBOT' : 'TASK_STAT_GROUP_SEND'
+    const [queryParams, setQueryParams] = useState<GetSendUserLogListProps>({ pageNum: 1, pageSize: 20 })
+    const [oldQueryParams, setOldQueryParams] = useState<GetSendUserLogListProps>({ pageNum: 1, pageSize: 20 })
+    const { token } = useNewToken()
+    const [modal, contextHolder] = Modal.useModal();
+
+    const getSendUserLogList = useAjax((params) => getSendUserLogListApi(params))
+    /***********************************/
+
+    useEffect(() => {
+        getSendUserLogList.run({ ...queryParams, projectType })
+    }, [queryParams, projectType])
+
+    const groupBody = (item) => {
+        let msgData = []
+        if (item?.text?.content) {
+            let newValue = item?.text?.content
+            let newEmo = emoList.reduce((prev, cur) => {
+                return [...prev, ...cur]
+            }, [])
+            let emos = newValue.match(/\[[\u4e00-\u9fa5]+\]/g)
+            if (emos && emos?.length) {
+                emos.forEach((emo: any) => {
+                    let emoData = newEmo.find(o => `[${o.name}]` === emo)
+                    if (emoData) {
+                        newValue = newValue.replace(emo, `<img src="${emoData.url}" alt="${emoData.name}" style="width:20px;height:20px"/>`)
+                    }
+                })
+            }
+            if (newValue.includes('\n')) {
+                newValue = newValue.replace('\n', `<br/>`)
+            }
+            let names = newValue.match(/%[\u4e00-\u9fa5]+%/g)
+            if (names && names?.length) {
+                newValue = newValue.replace('%昵称%', ` <span style="display: inline-block;margin:0 1px;position: relative; border: 1px solid ${token.colorBorder}; padding: ${token.paddingXS}px; border-radius: ${token.borderRadius}px;color: ${token.colorTextBase}; background: ${token.colorSuccess};color:${token.colorTextLightSolid}" contenteditable="false">昵称<strong data-name="昵称" style="padding: 0 6px;cursor:pointer" onclick="let html =document.querySelector('[data-name=昵称').parentElement.parentElement.innerHTML;let span = ' '+document.querySelector('[data-name=昵称]').parentElement.outerHTML+' ';console.log('=',html,'=');console.log('=',span,'=');document.execCommand('selectAll');document.execCommand('delete'); document.execCommand('insertHTML', true, html.replace(span,''));">X</strong></span> `)
+            }
+            msgData.push({
+                textContent: newValue,
+                mediaType: 'text'
+            })
+        }
+        if (item?.attachmentList && item?.attachmentList?.length > 0) {
+            let newAttachmentList = item?.attachmentList.map((item: any) => {
+                switch (item.msgType) {
+                    case 'TASK_CONTENT_IMAGE':
+                        return { mediaType: 'image', imageUrl: item?.image?.picUrl }
+                    case 'TASK_CONTENT_LINK':
+                        return { mediaType: 'link', linkDesc: item?.link?.desc, linkPicurl: item?.link?.picUrl, linkTitle: item?.link?.title, linkUrl: item?.link?.url }
+                    case 'TASK_STATUS_FILE':
+                        return { mediaType: 'file', fileUrl: item?.file?.fileUrl }
+                    case 'TASK_STATUS_VIDEO':
+                        return { mediaType: 'video', videoUrl: item?.video?.videoUrl }
+                    case 'TASK_STATUS_MINIPROGRAM':
+                        return {
+                            mediaType: 'miniprogram',
+                            miniprogramAppid: item.miniprogram?.appId,
+                            miniprogramPage: item?.miniprogram?.page,
+                            miniprogramPicurl: item?.miniprogram?.picUrl,
+                            miniprogramTitle: item?.miniprogram?.title
+                        }
+                }
+            })
+            msgData = msgData.concat(newAttachmentList)
+        }
+        return msgData
+    }
+
+    return <div>
+        {contextHolder}
+        <SearchBox
+            bodyPadding={`0 0 10px`}
+            buttons={<>
+                <Button type="primary" onClick={() => {
+                    setQueryParams({ ...oldQueryParams, pageNum: 1, pageSize: queryParams.pageSize })
+                }} loading={getSendUserLogList.loading} icon={<SearchOutlined />}>搜索</Button>
+            </>}
+        >
+            <>
+                <Input
+                    placeholder="任务名称"
+                    style={{ width: 120 }}
+                    allowClear
+                    onChange={(e) => setOldQueryParams({ ...oldQueryParams, projectName: e.target.value })}
+                    value={oldQueryParams?.projectName}
+                />
+                <Input
+                    placeholder="子任务ID"
+                    style={{ width: 120 }}
+                    allowClear
+                    onChange={(e) => setOldQueryParams({ ...oldQueryParams, taskId: e.target.value })}
+                    value={oldQueryParams?.taskId}
+                />
+                <Select
+                    placeholder='请选择项目组'
+                    allowClear
+                    options={groupList}
+                    showSearch
+                    style={{ minWidth: 150 }}
+                    filterOption={(input, option) =>
+                        (option?.label ?? '').toLowerCase().includes(input.toLowerCase())
+                    }
+                    maxTagCount={1}
+                    mode="multiple"
+                    value={oldQueryParams?.projectGroupIds}
+                    onChange={(value) => setOldQueryParams({ ...oldQueryParams, projectGroupIds: value })}
+                />
+                <DatePicker
+                    placeholder="发送开始时间"
+                    onChange={(e, date) => { setOldQueryParams({ ...oldQueryParams, startDay: date as string }) }}
+                    allowClear
+                    value={oldQueryParams?.startDay ? dayJs(oldQueryParams?.startDay) : undefined}
+                />
+                <DatePicker
+                    placeholder="发送结束时间"
+                    onChange={(e, date) => { setOldQueryParams({ ...oldQueryParams, endDay: date as string }) }}
+                    allowClear
+                    value={oldQueryParams?.endDay ? dayJs(oldQueryParams?.endDay) : undefined}
+                />
+            </>
+        </SearchBox>
+
+        <Table
+            style={{ marginTop: 10 }}
+            dataSource={getSendUserLogList?.data?.data?.records}
+            loading={getSendUserLogList?.loading}
+            bordered
+            columns={[
+                {
+                    title: '操作',
+                    dataIndex: 'cz',
+                    key: 'cz',
+                    width: 100,
+                    align: 'center',
+                    render: (_, record: any) => {
+                        return <Space>
+                            <a onClick={() => {
+                                modal.confirm({
+                                    title: '确认',
+                                    icon: <ExclamationCircleOutlined />,
+                                    content: '是否跳转到日志详情?',
+                                    okText: '确认',
+                                    cancelText: '复制',
+                                    onCancel: () => {
+                                        copy(record.projectName + '任务Id:' + record.taskId)
+                                    },
+                                    onOk: () => {
+                                        localStorage.setItem('OPENTASK', JSON.stringify({
+                                            id: record.projectId,
+                                            projectName: record.projectName,
+                                            taskType,
+                                            taskId: record.taskId,
+                                            msg: '跳转日志详情'
+                                        }))
+                                        switch (taskType) {
+                                            case 'TASK_STAT_GROUP_SEND_CHAT': // 官方群群发
+                                                window.open(`/weComTask#/weComTask/groupChatSend/official/taskList`)
+                                                break
+                                            case 'TASK_STAT_GROUP_ROBOT': // 机器人群发
+                                                window.open(`/weComTask#/weComTask/groupChatSend/robot/taskList`)
+                                                break
+                                            case 'TASK_STAT_GROUP_SEND': // 官方群发
+                                                window.open(`/weComTask#/weComTask/businessPlan/taskList`)
+                                                break
+                                        }
+                                    },
+                                });
+                            }}>任务跳转</a>
+                        </Space>
+                    }
+                },
+                {
+                    title: '任务名称',
+                    dataIndex: 'projectName',
+                    key: 'projectName',
+                    width: 180,
+                    ellipsis: true,
+                    render: (text) => <a onClick={() => copy(text)}>{text}</a>
+                },
+                {
+                    title: '子任务ID',
+                    dataIndex: 'taskId',
+                    key: 'taskId',
+                    width: 70,
+                    align: 'center'
+                },
+                {
+                    title: '项目组名称',
+                    dataIndex: 'projectGroupName',
+                    key: 'projectGroupName',
+                    width: 100,
+                    ellipsis: true,
+                    align: 'center',
+                    render: (text) => text ? <a onClick={() => copy(text)}>{text}</a> : '--'
+                },
+                {
+                    title: '项目组ID',
+                    dataIndex: 'projectGroupId',
+                    key: 'projectGroupId',
+                    width: 70,
+                    align: 'center'
+                },
+                {
+                    title: '任务ID',
+                    dataIndex: 'taskId',
+                    key: 'taskId',
+                    align: 'center',
+                    width: 65
+                },
+                {
+                    title: '客服号名称',
+                    dataIndex: 'corpUserName',
+                    key: 'corpUserName',
+                    align: 'center',
+                    width: 150,
+                    ellipsis: true
+                },
+                {
+                    title: '消息ID',
+                    dataIndex: 'msgId',
+                    key: 'msgId',
+                    align: 'center',
+                    width: 250,
+                    ellipsis: true,
+                    render: (a: string) => {
+                        return <a onClick={() => copy(a)}>{a}</a>
+                    }
+                },
+                {
+                    title: <div>发送内容<Tooltip title="2025-01-10前的群发无法查看内容"><InfoCircleOutlined /></Tooltip></div>,
+                    dataIndex: 'contentDTO',
+                    key: 'contentDTO',
+                    align: 'center',
+                    width: 90,
+                    ellipsis: true,
+                    render: (a, b) => {
+                        let data = []
+                        if (a) {
+                            data = groupBody(a)
+                        }
+
+                        return data?.length > 0 ? <Tooltip color={"#fff"} title={<PreviewMsg type="MSG" minWidth={"200px"} height={'calc(100vh - 400px)'} content={{ data }} />}>
+                            <a>查看内容</a>
+                        </Tooltip> : "无内容"
+                    }
+                },
+                {
+                    title: '发送时间',
+                    dataIndex: 'createTime',
+                    key: 'createTime',
+                    ellipsis: true,
+                    align: 'center',
+                    width: 130
+                },
+                {
+                    title: <>执行结果 <Tooltip title={<span>此结果为任务在后台创建后是否成功提交到腾讯。腾讯下发至手机和手机的最终执行结果请查看下发企微号。</span>}><QuestionCircleOutlined style={{ color: 'red' }} /></Tooltip></>,
+                    dataIndex: 'errCode',
+                    key: 'errCode',
+                    width: 60,
+                    align: 'center',
+                    ellipsis: true,
+                    render: (a: number) => {
+                        return { 0: '成功', 1: '失败' }[a]
+                    }
+                },
+                {
+                    title: '失败人数',
+                    dataIndex: 'failCount',
+                    key: 'failCount',
+                    align: 'center',
+                    ellipsis: true,
+                    width: 60,
+                    render: (a: any, b: any) => {
+                        return <GroupErrorCountList failCount={a} corpId={b?.corpId} msgId={b?.msgId} taskId={b?.taskId} />
+                    }
+                },
+                {
+                    title: '错误信息',
+                    dataIndex: 'errMsg',
+                    key: 'errMsg',
+                    ellipsis: true,
+                    width: 150
+                }
+            ]}
+            scroll={{ x: 1000 }}
+            rowKey={(s) => {
+                return s.id
+            }}
+            size='small'
+            rowClassName={(record) => record?.errCode === 1 ? 'errorClassName' : ''}
+            pagination={{
+                total: getSendUserLogList?.data?.data?.total,
+                showTotal: (total) => <Tag color="cyan">总共{total}数据</Tag>,
+                showSizeChanger: true,
+                showLessItems: true,
+                defaultCurrent: 1,
+                defaultPageSize: 200,//默认初始的每页条数
+                current: getSendUserLogList?.data?.data?.current || 1,
+                pageSize: getSendUserLogList?.data?.data?.size || 20,
+                onChange: (page, pageSize) => {
+                    setQueryParams({ ...queryParams, pageNum: page, pageSize })
+                    setOldQueryParams({ ...oldQueryParams, pageNum: page, pageSize })
+                }
+            }}
+        />
+    </div>
+}
+
+export default React.memo(GroupItem);

+ 32 - 47
src/pages/weComTask/page/home/index.tsx

@@ -12,6 +12,7 @@ import { useAjax } from "@/Hook/useAjax";
 import { getTotalModelApi, getTotalTaslApi } from "../../API/logs";
 import CalendarItem from "./calendarItem";
 import PullGroup from "./pullGroup";
+import GroupItem from "./groupItem";
 
 const weekMap = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
 const taskTypeMap = {
@@ -277,53 +278,37 @@ const Logs: React.FC = () => {
                             {getTotalModel?.data?.data?.map((item: any, index: number) => {
                                 switch (item.taskType) {
                                     case 'TASK_STAT_GROUP_SEND': // 官方用户群发
-                                        return <div className={style.moduleCard} key={index}>
-                                            <div className={style.moduleCard_header}>
-                                                <Avatar style={{ backgroundColor: '#dbeafe', color: '#2563eb' }} size={30}>
-                                                    <FontAwesomeIcon icon={faUsers} />
-                                                </Avatar>
-                                                官方用户群发
-                                            </div>
-                                            <div className={style.moduleCard_center}>
-                                                总任务数:{item?.taskTotal || 0} | 今日新增:{item?.todayAddTask || 0}
-                                            </div>
-                                            <div className={style.moduleCard_footer}>
-                                                <span>今日完成率:{((item?.todayFinishTaskRate || 0) * 100).toFixed(2)} %</span>
-                                                <span className={style.error}>今日异常:{item?.todayFailTask}</span>
-                                            </div>
-                                        </div>
+                                        return <GroupItem
+                                            key={index}
+                                            title="官方用户群发"
+                                            icon={faUsers}
+                                            taskTotal={item?.taskTotal}
+                                            todayAddTask={item?.todayAddTask}
+                                            todayFinishTaskRate={item?.todayFinishTaskRate}
+                                            todayFailTask={item?.todayFailTask}
+                                        />
                                     case 'TASK_STAT_GROUP_SEND_CHAT': // 官方群群发
-                                        return <div className={style.moduleCard} key={index}>
-                                            <div className={style.moduleCard_header}>
-                                                <Avatar style={{ backgroundColor: '#f3e8ff', color: '#9333ea' }} size={30}>
-                                                    <FontAwesomeIcon icon={faUsersCog} />
-                                                </Avatar>
-                                                群聊群发
-                                            </div>
-                                            <div className={style.moduleCard_center}>
-                                                总任务数:{item?.taskTotal || 0} | 今日新增:{item?.todayAddTask || 0}
-                                            </div>
-                                            <div className={style.moduleCard_footer}>
-                                                <span>今日完成率:{((item?.todayFinishTaskRate || 0) * 100).toFixed(2)} %</span>
-                                                <span className={style.error}>今日异常:{item?.todayFailTask}</span>
-                                            </div>
-                                        </div>
+                                        return <GroupItem
+                                            key={index}
+                                            title="群聊群发"
+                                            icon={faUsersCog}
+                                            taskTotal={item?.taskTotal}
+                                            todayAddTask={item?.todayAddTask}
+                                            todayFinishTaskRate={item?.todayFinishTaskRate}
+                                            todayFailTask={item?.todayFailTask}
+                                            projectType={1}
+                                        />
                                     case 'TASK_STAT_GROUP_ROBOT': // 机器人群发
-                                        return <div className={style.moduleCard} key={index}>
-                                            <div className={style.moduleCard_header}>
-                                                <Avatar style={{ backgroundColor: '#fff0f6', color: '#f759ab' }} size={30}>
-                                                    <FontAwesomeIcon icon={faRobot} />
-                                                </Avatar>
-                                                机器人群发
-                                            </div>
-                                            <div className={style.moduleCard_center}>
-                                                总任务数:{item?.taskTotal || 0} | 今日新增:{item?.todayAddTask || 0}
-                                            </div>
-                                            <div className={style.moduleCard_footer}>
-                                                <span>今日完成率:{((item?.todayFinishTaskRate || 0) * 100).toFixed(2)} %</span>
-                                                <span className={style.error}>今日异常:{item?.todayFailTask}</span>
-                                            </div>
-                                        </div>
+                                        return <GroupItem
+                                            key={index}
+                                            title="机器人群发"
+                                            icon={faRobot}
+                                            taskTotal={item?.taskTotal}
+                                            todayAddTask={item?.todayAddTask}
+                                            todayFinishTaskRate={item?.todayFinishTaskRate}
+                                            todayFailTask={item?.todayFailTask}
+                                            projectType={2}
+                                        />
                                     case 'TASK_STAT_MOMENT': // 朋友圈
                                         return <div className={style.moduleCard} key={index}>
                                             <div className={style.moduleCard_header}>
@@ -341,8 +326,8 @@ const Logs: React.FC = () => {
                                             </div>
                                         </div>
                                     case 'TASK_STAT_PULL_GROUP': // 群聊创建 - 拉群
-                                        return <PullGroup 
-                                            key={index} 
+                                        return <PullGroup
+                                            key={index}
                                             pullGroupCount={item?.pullGroupCount}
                                             pullSuccessCount={item?.pullSuccessCount}
                                             pullSuccessRate={item?.pullSuccessRate}

+ 1 - 0
src/pages/weComTask/page/home/pullGroup.tsx

@@ -197,6 +197,7 @@ const PullGroup: React.FC<Props> = ({ pullFailCount, pullGroupCount, pullSuccess
                         key: 'projectGroupName',
                         width: 100,
                         ellipsis: true,
+                        align: 'center',
                         render: (text) => text ? <a onClick={() => copy(text)}>{text}</a> : '--'
                     },
                     {