wjx 6 gün önce
ebeveyn
işleme
ebb5d37829

+ 5 - 0
package.json

@@ -4,6 +4,11 @@
   "private": true,
   "dependencies": {
     "@babel/core": "^7.16.0",
+    "@fortawesome/fontawesome-svg-core": "^7.1.0",
+    "@fortawesome/free-brands-svg-icons": "^7.1.0",
+    "@fortawesome/free-regular-svg-icons": "^7.1.0",
+    "@fortawesome/free-solid-svg-icons": "^7.1.0",
+    "@fortawesome/react-fontawesome": "^3.1.0",
     "@hot-loader/react-dom": "^17.0.2",
     "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3",
     "@svgr/webpack": "^5.5.0",

+ 2 - 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, AlertOutlined } 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, FieldTimeOutlined } 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'
@@ -67,6 +67,7 @@ const IconMap = {
     cloud: <CloudOutlined />,
     link: <PaperClipOutlined />,
     dashboard: <HomeOutlined />,
+    time: <FieldTimeOutlined />,
     launch: <span role="img" aria-label="fund-view" className="anticon anticon-fund-view"><LaunchSvg /></span>,
     adLaunch: <span role="img" aria-label="fund-view" className="anticon anticon-fund-view"><AdLaunchSvg /></span>,
     material: <span role="img" aria-label="fund-view" className="anticon anticon-fund-view"><MaterialSvg /></span>,

+ 1 - 0
src/pages/weComTask/API/home/index.ts

@@ -70,6 +70,7 @@ export interface GetExternalUserRepeatByCorpListApiProps {
     maxCorpUserIdCount?: number,
     sortType?: string,
     orderByField?: string
+    corpIdList?: string[]
 }
 
 /**

+ 12 - 3
src/pages/weComTask/components/filterUser/filterUserText.ts

@@ -33,10 +33,13 @@ const targetingData = [
     { key: 'addGroupCount', name: '进群次数' },
     { key: 'exitGroupCount', name: '退群次数' },
     { key: 'retentionGroupCount', name: '留存群聊数' },
-    { key: 'repeatRangType', name: '重粉范围' },
+    { key: 'repeatRang', name: '重粉范围' },
     { key: 'repeatOperateType', name: '重粉操作类型' },
     { key: 'corpIdCount', name: '添加主体数' },
     { key: 'corpUserIdCount', name: '添加客服数' },
+    { key: 'groupRepeatRang', name: '群重名过滤范围' },
+    { key: 'groupOperateType', name: '群重名操作类型' },
+    { key: 'groupNameList', name: '群名称列表' },
 ]
 
 const targetingGroupData = [
@@ -153,7 +156,7 @@ interface ContentProps {
         retentionGroupCountMin?: string,
         retentionGroupCountMax?: string
     },
-    repeatRangType?: 0 | 1,
+    repeatRang?: Record<string, string>,
     repeatOperateType?: 0 | 1,
     corpIdCount?: {
         minCorpIdCount?: number,
@@ -163,6 +166,9 @@ interface ContentProps {
         minCorpUserIdCount?: number,
         maxCorpUserIdCount?: number
     },
+    groupRepeatRang?: Record<string, string>,
+    groupOperateType?: 0 | 1,
+    groupNameList?: string[],
 }
 
 /** 重置筛选用户数据 */
@@ -325,10 +331,13 @@ const FilterUserText = ({ data, configName, configType = 'USER_GROUP', bookCityL
         (content?.exitGroupCount ? `退群次数:${content?.exitGroupCount?.exitGroupCountMin ? content?.exitGroupCount?.exitGroupCountMin : '不限'} - ${content?.exitGroupCount?.exitGroupCountMax ? content?.exitGroupCount?.exitGroupCountMax : '不限'}\n` : '') +
         (content?.retentionGroupCount ? `留存群聊数:${content?.retentionGroupCount?.retentionGroupCountMin ? content?.retentionGroupCount?.retentionGroupCountMin : '不限'} - ${content?.retentionGroupCount?.retentionGroupCountMax ? content?.retentionGroupCount?.retentionGroupCountMax : '不限'}\n` : '') +
         (configType === 'USER_GROUP' ? `是否查询所有公司:${content?.allCorp ? '是' : '否'}\n` : '') +
-        ((content?.repeatRangType || content?.repeatRangType == 0) ? `重粉范围:${content?.repeatRangType == 0 ? '任务内' : '集团内'}\n` : '') +
+        ((content?.repeatRang) ? `重粉范围:${Object.values(content?.repeatRang).map(corpName => corpName).join('、')}\n` : '') +
         ((content?.repeatOperateType || content?.repeatOperateType == 0) ? `重粉操作类型:${content?.repeatOperateType == 0 ? '排除运营' : '去重运营'}\n` : '') +
         (content?.corpIdCount ? `添加主体数:${content?.corpIdCount?.minCorpIdCount ? content?.corpIdCount?.minCorpIdCount : '不限'} - ${content?.corpIdCount?.maxCorpIdCount ? content?.corpIdCount?.maxCorpIdCount : '不限'}\n` : '') +
         (content?.corpUserIdCount ? `添加客服数:${content?.corpUserIdCount?.minCorpUserIdCount ? content?.corpUserIdCount?.minCorpUserIdCount : '不限'} - ${content?.corpUserIdCount?.maxCorpUserIdCount ? content?.corpUserIdCount?.maxCorpUserIdCount : '不限'}\n` : '') +
+        ((content?.groupRepeatRang) ? `群重名过滤范围:${Object.values(content?.groupRepeatRang).map(corpName => corpName).join('、')}\n` : '') +
+        ((content?.groupOperateType || content?.groupOperateType == 0) ? `群重名操作类型:${content?.groupOperateType == 0 ? '历史群' : '当前所在群'}\n` : '') +
+        (content?.groupNameList?.length > 0 ? `群名称列表${content?.groupNameList.join('、')}\n` : '') +
         (content?.unlimited ? `不限:${content?.unlimited}` : '')
 }
 

+ 24 - 9
src/pages/weComTask/components/filterUser/filterUserTooltip.tsx

@@ -36,10 +36,13 @@ const targetingData = [
     { key: 'addGroupCount', name: '进群次数' },
     { key: 'exitGroupCount', name: '退群次数' },
     { key: 'retentionGroupCount', name: '留存群聊数' },
-    { key: 'repeatRangType', name: '重粉范围' },
+    { key: 'repeatRang', name: '重粉范围' },
     { key: 'repeatOperateType', name: '重粉操作类型' },
     { key: 'corpIdCount', name: '添加主体数' },
-    { key: 'corpUserIdCount', name: '添加客服数' }
+    { key: 'corpUserIdCount', name: '添加客服数' },
+    { key: 'groupRepeatRang', name: '群重名过滤范围' },
+    { key: 'groupOperateType', name: '群重名操作类型' },
+    { key: 'groupNameList', name: '群名称列表' },
 ]
 
 const targetingGroupData = [
@@ -161,7 +164,7 @@ interface ContentProps {
         retentionGroupCountMin?: string,
         retentionGroupCountMax?: string
     },
-    repeatRangType?: 0 | 1,
+    repeatRang?: Record<string, string>,
     repeatOperateType?: 0 | 1,
     corpIdCount?: {
         minCorpIdCount?: number,
@@ -171,6 +174,9 @@ interface ContentProps {
         minCorpUserIdCount?: number,
         maxCorpUserIdCount?: number
     },
+    groupRepeatRang?: Record<string, string>,
+    groupOperateType?: 0 | 1,
+    groupNameList?: string[],
 }
 
 /** 重置筛选用户数据 */
@@ -178,7 +184,7 @@ export const resetFilterUserData = (data: { [x: string]: any }): ContentProps =>
     const {
         beginTime, endTime, rechargeCountMin, rechargeCountMax, rechargeMoneyMin, rechargeMoneyMax, lastRechargeTimeMin, lastRechargeTimeMax,
         firstRechargeMoneyMin, firstRechargeMoneyMax, rechargeMoneyMaxMin, rechargeMoneyMaxMax, subHourMin, subHourMax, subDayMin, subDayMax,
-        lastRechargeHourMin, lastRechargeHourMax, groupChatNames, groupChatBeginTime, groupChatEndTime, groupChatHourMin, groupChatHourMax, 
+        lastRechargeHourMin, lastRechargeHourMax, groupChatNames, groupChatBeginTime, groupChatEndTime, groupChatHourMin, groupChatHourMax,
         groupChatUserMin, groupChatUserMax, lastReadHourMin, lastReadHourMax, lastReadTimeMin, lastReadTimeMax, firstAddGroupTimeMin, firstAddGroupTimeMax,
         lastAddGroupTimeMin, lastAddGroupTimeMax, lastExitTimeMin, lastExitTimeMax, firstAddGroupHourRangeMin, firstAddGroupHourRangeMax,
         lastAddGroupHourRangeMin, lastAddGroupHourRangeMax, lastExitHourRangeMin, lastExitHourRangeMax, addGroupCountMin, addGroupCountMax,
@@ -259,7 +265,7 @@ export const resetFilterUserData = (data: { [x: string]: any }): ContentProps =>
     if (retentionGroupCountMin || retentionGroupCountMax) {
         newData.retentionGroupCount = { retentionGroupCountMin, retentionGroupCountMax }
     }
-     if (minCorpIdCount || maxCorpIdCount) {
+    if (minCorpIdCount || maxCorpIdCount) {
         newData.corpIdCount = { minCorpIdCount, maxCorpIdCount }
     }
     if (minCorpUserIdCount || maxCorpUserIdCount) {
@@ -420,17 +426,26 @@ const FilterUserTooltip: React.FC<Props> = (props) => {
             <strong>是否查询所有公司:</strong><span>{content?.allCorp ? '是' : '否'}</span>
         </div>}
 
-        {(content?.repeatRangType || content?.repeatRangType == 0) && <div>
-            <strong>重粉范围:</strong><span>{content?.repeatRangType == 0 ? '任务内' : '集团内'}</span>
+        {(content?.repeatRang) && <div>
+            <strong>重粉范围:</strong><span>{Object.values(content?.repeatRang).map(corpName => corpName).join('、')}</span>
         </div>}
         {(content?.repeatOperateType || content?.repeatOperateType == 0) && <div>
             <strong>重粉操作类型:</strong><span>{content?.repeatOperateType == 0 ? '排除运营' : '去重运营'}</span>
         </div>}
         {content?.corpIdCount && <div>
-            <strong>添加主体数:</strong><span>{content?.corpIdCount?.minCorpIdCount ? content?.corpIdCount?.minCorpIdCount : '0'} - {content?.corpIdCount?.maxCorpIdCount ? content?.corpIdCount?.maxCorpIdCount : '不限'}</span>
+            <strong>添加主体数:</strong><span>{content?.corpIdCount?.minCorpIdCount ? content?.corpIdCount?.minCorpIdCount : '不限'} - {content?.corpIdCount?.maxCorpIdCount ? content?.corpIdCount?.maxCorpIdCount : '不限'}</span>
         </div>}
         {content?.corpUserIdCount && <div>
-            <strong>添加客服数:</strong><span>{content?.corpUserIdCount?.minCorpUserIdCount ? content?.corpUserIdCount?.minCorpUserIdCount : '0'} - {content?.corpUserIdCount?.maxCorpUserIdCount ? content?.corpUserIdCount?.maxCorpUserIdCount : '不限'}</span>
+            <strong>添加客服数:</strong><span>{content?.corpUserIdCount?.minCorpUserIdCount ? content?.corpUserIdCount?.minCorpUserIdCount : '不限'} - {content?.corpUserIdCount?.maxCorpUserIdCount ? content?.corpUserIdCount?.maxCorpUserIdCount : '不限'}</span>
+        </div>}
+        {(content?.groupRepeatRang) && <div>
+            <strong>群重名过滤范围:</strong><span>{Object.values(content?.groupRepeatRang).map(corpName => corpName).join('、')}</span>
+        </div>}
+        {(content?.groupOperateType || content?.groupOperateType == 0) && <div>
+            <strong>群重名操作类型:</strong><span>{content?.groupOperateType == 0 ? '历史群' : '当前所在群'}</span>
+        </div>}
+        {content?.groupNameList?.length > 0 && <div>
+            <strong>群名称列表:</strong><span>{content.groupNameList.join('、')}</span>
         </div>}
 
         {content?.unlimited && <div>

+ 116 - 21
src/pages/weComTask/components/filterUser/newFiterUser.tsx

@@ -8,6 +8,7 @@ import dayjs from 'dayjs';
 import MindTags from '../mindTags';
 import SelectBook from '../SelectBook';
 import SelectChapter from '../SelectChapter';
+import { getCorpAllListApi } from '@/API/global';
 
 interface NewFilterUserProps {
     configType: BUSINES_SPLAN_API.ConfigTypeProps
@@ -32,15 +33,23 @@ const NewFilterUser: React.FC<NewFilterUserProps> = ({ bookCityList, configType
     const platformKey = Form.useWatch('platformKey', form)
     const lastBookNameList = Form.useWatch('lastBookNameList', form)
     const isRepeat = Form.useWatch('isRepeat', form)
+    const isGroupRepeat = Form.useWatch('isGroupRepeat', form)
     const [isSave, setIsSave] = useState<boolean>(false)
 
     const addSiftPopulationConfig = useAjax((params) => addSiftPopulationConfigApi(params))
     const updateSiftPopulationConfig = useAjax((params) => updateSiftPopulationConfigApi(params))
+    const getCorpAllList = useAjax((params) => getCorpAllListApi(params))
     /***********************************/
 
+    useEffect(() => {
+        if (isRepeat || isGroupRepeat)
+            getCorpAllList.run({})
+    }, [isRepeat, isGroupRepeat])
+
     useEffect(() => {
         if (value && Object.keys(value).length > 0) {
-            const { beginTime, endTime, lastRechargeTimeMin, lastRechargeTimeMax, groupChatNames, groupChatBeginTime, groupChatEndTime, lastReadTimeMin, lastReadTimeMax, ...params } = value
+            console.log('value', value)
+            const { beginTime, endTime, lastRechargeTimeMin, lastRechargeTimeMax, groupChatNames, groupChatBeginTime, groupChatEndTime, lastReadTimeMin, lastReadTimeMax, groupNameList, groupRepeatRang, ...params } = value
             if (beginTime) {
                 params.beginTime = dayjs(beginTime)
             }
@@ -59,14 +68,21 @@ const NewFilterUser: React.FC<NewFilterUserProps> = ({ bookCityList, configType
             if (groupChatNames?.length) {
                 params.groupChatNames = groupChatNames.join(',')
             }
-            const keys = Object.keys(params)
-            if ((keys.includes('repeatRangType') && params?.repeatRangType !== null) ||
-                (keys.includes('repeatOperateType') && params?.repeatOperateType !== null) ||
-                (keys.includes('minCorpIdCount') && params?.minCorpIdCount !== null) ||
-                (keys.includes('minCorpUserIdCount') && params?.minCorpUserIdCount !== null)
-            ) {
+            if ((params?.repeatOperateType !== null)) {
+                if (params.repeatRang) {
+                    params.repeatRang = Object.entries(params.repeatRang || {}).map(([corpId, corpName]: [string, string]) => `${corpId}&&${corpName}`)
+                }
                 params.isRepeat = true
             }
+
+            if (params?.groupOperateType !== null) {
+                params.groupNameList = groupNameList.join(',')
+                params.isGroupRepeat = true
+                console.log('groupNameList', groupRepeatRang)
+                if (!!groupRepeatRang) {
+                    params.groupRepeatRang = Object.entries(groupRepeatRang || {}).map(([corpId, corpName]: [string, string]) => `${corpId}&&${corpName}`)
+                }
+            }
             form.setFieldsValue(params)
         }
     }, [value])
@@ -74,8 +90,8 @@ const NewFilterUser: React.FC<NewFilterUserProps> = ({ bookCityList, configType
     const handleOk = () => {
         form.validateFields().then((values) => {
             console.log(values)
-            const { configName, isRepeat, ...configContent } = values
-            if (configContent && Object.keys(configContent).length > 0 && Object.keys(configContent).some(key => configContent[key])) {
+            const { configName, isRepeat, repeatRang, isGroupRepeat, groupRepeatRang, groupNameList, ...configContent } = values
+            if (configContent && Object.keys(configContent).length > 0 && Object.keys(configContent).some(key => configContent[key] || configContent[key] === 0)) {
                 const {
                     beginTime, endTime, lastRechargeTime, lastReadTime, groupChatNames, groupChatTime, firstAddGroupTime,
                     lastAddGroupTime, lastExitTime,
@@ -117,6 +133,23 @@ const NewFilterUser: React.FC<NewFilterUserProps> = ({ bookCityList, configType
                     dta.lastExitTimeMin = dayjs(lastExitTime[0]).format('YYYY-MM-DD');
                     dta.lastExitTimeMax = dayjs(lastExitTime[1]).format('YYYY-MM-DD');
                 }
+                if (repeatRang?.length) {
+                    dta.repeatRang = repeatRang.reduce((pre, cur) => {
+                        const [corpId, corpName] = cur.split('&&')
+                        pre[corpId] = corpName
+                        return pre
+                    }, {})
+                }
+                if (groupRepeatRang?.length) {
+                    dta.groupRepeatRang = groupRepeatRang.reduce((pre, cur) => {
+                        const [corpId, corpName] = cur.split('&&')
+                        pre[corpId] = corpName
+                        return pre
+                    }, {})
+                }
+                if (groupNameList) {
+                    dta.groupNameList = groupNameList.split(/[,,\n\s]+/ig).filter((item: any) => item)
+                }
 
                 // 直接新增
                 if (isBackVal && !isSave) {
@@ -168,6 +201,23 @@ const NewFilterUser: React.FC<NewFilterUserProps> = ({ bookCityList, configType
         });
     };
 
+    const validateAnyFieldRequired = async (_, value) => {
+        const values = form.getFieldsValue([
+            'minCorpIdCount',
+            'maxCorpIdCount',
+            'minCorpUserIdCount',
+            'maxCorpUserIdCount',
+        ]);
+
+        const hasValue = Object.values(values).some((v) => v !== undefined && v !== null && v !== '');
+
+        if (!hasValue) {
+            return Promise.reject(new Error('至少填写一个添加数值'));
+        }
+
+        return Promise.resolve();
+    };
+
     const title = configType === 'GROUP_GROUP' ? '群人群包' : '用户人群包';
     return <Modal
         title={isBackVal ? <strong style={{ fontSize: 14 }}>
@@ -491,14 +541,18 @@ const NewFilterUser: React.FC<NewFilterUserProps> = ({ bookCityList, configType
                 </div>
                 {isRepeat && <>
                     <div className={style.newSpace}>
-                        <Form.Item name="repeatRangType" label={<strong>重粉范围</strong>} style={{ marginBottom: 0 }} rules={[{ required: true, message: '请选择重粉范围' }]}>
+                        <Form.Item name="repeatRang" label={<strong>重粉范围</strong>} style={{ marginBottom: 0 }}>
                             <Select
-                                placeholder='重粉范围'
+                                showSearch
+                                mode='multiple'
+                                style={{ minWidth: 110 }}
+                                maxTagCount={1}
+                                placeholder="请选择主体"
+                                filterOption={(input, option) =>
+                                    ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                                }
                                 allowClear
-                                options={[
-                                    { value: 0, label: '任务内' },
-                                    { value: 1, label: '集团内' }
-                                ]}
+                                options={getCorpAllList?.data?.data?.map((item: any) => ({ label: item.corpName, value: item.corpId + '&&' + item.corpName }))}
                             />
                         </Form.Item>
                     </div>
@@ -515,26 +569,26 @@ const NewFilterUser: React.FC<NewFilterUserProps> = ({ bookCityList, configType
                         </Form.Item>
                     </div>
                     <div className={style.newSpace}>
-                        <Form.Item label={<strong>添加主体</strong>} style={{ marginBottom: 0 }}>
+                        <Form.Item label={<strong>添加主体</strong>} style={{ marginBottom: 0 }} required>
                             <Space>
-                                <Form.Item name="minCorpIdCount" noStyle rules={[{ required: true, message: '请输入添加主体最小数' }]}>
+                                <Form.Item name="minCorpIdCount" noStyle rules={[{ validator: validateAnyFieldRequired }]}>
                                     <InputNumber placeholder="添加主体最小数" style={{ width: 200 }} />
                                 </Form.Item>
                                 <span>-</span>
-                                <Form.Item name="maxCorpIdCount" noStyle rules={[{ required: true, message: '请输入添加主体最大数' }]}>
+                                <Form.Item name="maxCorpIdCount" noStyle>
                                     <InputNumber placeholder="添加主体最大数" style={{ width: 200 }} />
                                 </Form.Item>
                             </Space>
                         </Form.Item>
                     </div>
                     <div className={style.newSpace}>
-                        <Form.Item label={<strong>添加客服</strong>} style={{ marginBottom: 0 }}>
+                        <Form.Item label={<strong>添加客服</strong>} style={{ marginBottom: 0 }} required>
                             <Space>
-                                <Form.Item name="minCorpUserIdCount" noStyle rules={[{ required: true, message: '请输入添加客服最小数' }]}>
+                                <Form.Item name="minCorpUserIdCount" noStyle>
                                     <InputNumber placeholder="添加客服最小数" style={{ width: 200 }} />
                                 </Form.Item>
                                 <span>-</span>
-                                <Form.Item name="maxCorpUserIdCount" noStyle rules={[{ required: true, message: '请输入添加客服最大数' }]}>
+                                <Form.Item name="maxCorpUserIdCount" noStyle>
                                     <InputNumber placeholder="添加客服最大数" style={{ width: 200 }} />
                                 </Form.Item>
                             </Space>
@@ -640,6 +694,47 @@ const NewFilterUser: React.FC<NewFilterUserProps> = ({ bookCityList, configType
                         <Checkbox>是否查询所有公司</Checkbox>
                     </Form.Item>
                 </div>
+                <Divider orientation="left">群重名过滤</Divider>
+                <div className={style.newSpace}>
+                    <Form.Item name="isGroupRepeat" label={<strong>群重名过滤筛选</strong>} style={{ marginBottom: 0 }} valuePropName='checked'>
+                        <Checkbox>是否开启群重名过滤筛选</Checkbox>
+                    </Form.Item>
+                </div>
+                {isGroupRepeat && <>
+                    <div className={style.newSpace}>
+                        <Form.Item name="groupRepeatRang" label={<strong>群重名过滤范围</strong>} style={{ marginBottom: 0 }}>
+                            <Select
+                                showSearch
+                                mode='multiple'
+                                style={{ minWidth: 110 }}
+                                maxTagCount={1}
+                                placeholder="请选择主体"
+                                filterOption={(input, option) =>
+                                    ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                                }
+                                allowClear
+                                options={getCorpAllList?.data?.data?.map((item: any) => ({ label: item.corpName, value: item.corpId + '&&' + item.corpName }))}
+                            />
+                        </Form.Item>
+                    </div>
+                    <div className={style.newSpace}>
+                        <Form.Item name="groupOperateType" label={<strong>群重名操作类型</strong>} style={{ marginBottom: 0 }} rules={[{ required: true, message: '请选择群重名操作类型' }]}>
+                            <Select
+                                placeholder='群重名操作类型'
+                                allowClear
+                                options={[
+                                    { value: 0, label: '历史群' },
+                                    { value: 1, label: '当前所在群' }
+                                ]}
+                            />
+                        </Form.Item>
+                    </div>
+                    <div className={style.newSpace}>
+                        <Form.Item name="groupNameList" label={<strong>群名称列表</strong>} style={{ marginBottom: 0 }} rules={[{ required: true, message: '请输入群名称列表' }]}>
+                            <Input.TextArea placeholder="请输入群名称列表(多个,分割)" rows={2} />
+                        </Form.Item>
+                    </div>
+                </>}
             </>}
         </Form>
     </Modal>

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

@@ -666,28 +666,7 @@ const Create: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookLis
                             }}
                         />
                     </Space.Compact> : undefined}
-
-                    {(settings?.highMassSendingStrategy && Object.keys(settings?.highMassSendingStrategy).length > 0) && <Button
-                        onClick={() => setEaVisible(true)}
-                        type={settings?.corpUsers?.every(item => item?.externalUserList?.length) ? 'primary' : 'default'}
-                    >
-                        {settings?.corpUsers?.every(item => item?.externalUserList?.length) ? <>
-                            重选高级群发外部客户
-                            <CheckOutlined style={{ marginLeft: 5 }} />
-                        </> : '高级群发外部客户选择'}
-                    </Button>}
-
-                    {(settings?.friendsStrategy && Object.keys(settings?.friendsStrategy).length > 0 && settings?.corpUsers?.length > 0) && <Button
-                        onClick={() => {
-                            setQwVisible(true)
-                        }}
-                        type={settings?.corpUsers?.some(item => item?.friendsTagContent?.length) ? 'primary' : 'default'}
-                    >
-                        {settings?.corpUsers?.some(item => item?.friendsTagContent?.length) ? <>
-                            重选企业标签
-                            <CheckOutlined style={settings?.corpUsers?.every(item => item?.friendsTagContent?.length) ? { marginLeft: 5 } : { marginLeft: 5, color: 'red' }} />
-                        </> : '选择企业标签'}
-                    </Button>}
+                    
                 </Space>
 
                 <div className={style.settingsBody}>

+ 42 - 15
src/pages/weComTask/page/home/index.tsx

@@ -1,20 +1,21 @@
 import { useAjax } from '@/Hook/useAjax';
 import React, { useEffect, useState } from 'react';
 import { getCorpExternalUserRepeatListApi, getExternalUserRepeatByCorpAtlasApi, getExternalUserRepeatByCorpListApi, getExternalUserRepeatCorpApi, getExternalUserRepeatCorpUserApi } from '../../API/home';
-import { Avatar, Card, Col, Flex, Input, Row, Space, Spin, Statistic, Table, Tabs, Tooltip, Typography } from 'antd';
+import { Avatar, Card, Col, Flex, Input, Row, Select, Space, Spin, Statistic, Table, Tabs, Tooltip, Typography } from 'antd';
 import { BarChartOutlined, CheckOutlined, DeleteOutlined, ExclamationOutlined, GlobalOutlined, QuestionCircleOutlined, RetweetOutlined, UserOutlined } from '@ant-design/icons';
 import useEcharts from '@/Hook/useEcharts';
 const { Title } = Typography;
 import style from './index.less'
 import { CorpExternalUserColumns, ExternalUserColumns } from './tableConfig';
 import UuidTem from './uuidTem';
+import { getCorpAllListApi } from '@/API/global';
 
 const Home: React.FC = () => {
 
     /*******************************************/
     const { Bar } = useEcharts()
-    const [queryParmas, setQueryParmas] = useState<{ pageNum: number, pageSize: number, corpName?: string }>({ pageNum: 1, pageSize: 30 })
-    const [queryParmasZt, setQueryParmasZt] = useState<{ pageNum: number, pageSize: number, corpName?: string }>({ pageNum: 1, pageSize: 30 })
+    const [queryParmas, setQueryParmas] = useState<{ pageNum: number, pageSize: number, corpIdList?: string }>({ pageNum: 1, pageSize: 30 })
+    const [queryParmasZt, setQueryParmasZt] = useState<{ pageNum: number, pageSize: number, corpIdList?: string }>({ pageNum: 1, pageSize: 30 })
     const [corpRepeat, setCorpRepeat] = useState<{ [x: string]: any }>({})
     const [corpUserRepeat, setCorpUserRepeat] = useState<{ [x: string]: any }>({})
     const [barCorpData, setBarCorpData] = useState<Record<string, any>[]>([])
@@ -39,8 +40,13 @@ const Home: React.FC = () => {
     const getExternalUserRepeatByCorpAtlas = useAjax(() => getExternalUserRepeatByCorpAtlasApi())
     const getExternalUserRepeatByCorpList = useAjax((params) => getExternalUserRepeatByCorpListApi(params))
     const getCorpExternalUserRepeatList = useAjax((params) => getCorpExternalUserRepeatListApi(params))
+    const getCorpAllList = useAjax((params) => getCorpAllListApi(params))
     /*******************************************/
 
+    useEffect(() => {
+        getCorpAllList.run({})
+    }, [])
+
     useEffect(() => {
         getExternalUserRepeatByCorpList.run(queryParmas)
     }, [queryParmas])
@@ -104,6 +110,10 @@ const Home: React.FC = () => {
             })
         }
 
+
+    }, [activeKey])
+
+    useEffect(() => {
         getExternalUserRepeatByCorpAtlas.run().then(res => {
             if (res?.data) {
                 setUserData(res?.data?.map(item => {
@@ -113,7 +123,7 @@ const Home: React.FC = () => {
                 setUserData([])
             }
         })
-    }, [activeKey])
+    }, [])
 
     return <div>
         <Spin spinning={getExternalUserRepeatCorpUser.loading}>
@@ -215,11 +225,19 @@ const Home: React.FC = () => {
 
         <Flex justify='space-between' style={{ margin: '20px 0 10px' }}>
             <Title level={3} style={{ margin: 0 }}><RetweetOutlined style={{ color: '#1890ff' }} /> 单主体内重粉分布</Title>
-            <Input.Search
-                placeholder="请输入企业名称"
-                onSearch={(e) => { setQueryParmas({ ...queryParmas, corpName: e, pageNum: 1 }); }}
-                style={{ width: 200 }}
+            <Select
+                value={queryParmas?.corpIdList}
+                onChange={(e) => setQueryParmas({ ...queryParmas, corpIdList: e })}
+                showSearch
+                style={{ minWidth: 150 }}
+                maxTagCount={1}
+                mode='multiple'
+                placeholder="选择企业"
+                filterOption={(input, option) =>
+                    ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                }
                 allowClear
+                options={getCorpAllList?.data?.data?.map((item: any) => ({ label: item.corpName, value: item.corpId }))}
             />
         </Flex>
         <Row gutter={16}>
@@ -267,12 +285,21 @@ const Home: React.FC = () => {
         <Spin spinning={getExternalUserRepeatCorp.loading || getExternalUserRepeatCorpUser.loading || getCorpExternalUserRepeatList.loading}>
             <Title level={3}><BarChartOutlined style={{ color: '#22c55e' }} /> 用户在集团内重粉分布</Title>
             <Tabs
-                tabBarExtraContent={activeKey === '1' && <Input.Search
-                    placeholder="请输入企业名称"
-                    onSearch={(e) => { setQueryParmasZt({ ...queryParmasZt, corpName: e, pageNum: 1 }); }}
-                    style={{ width: 200 }}
-                    allowClear
-                />}
+                tabBarExtraContent={activeKey === '1' &&
+                    <Select
+                        value={queryParmasZt?.corpIdList}
+                        onChange={(e) => setQueryParmasZt({ ...queryParmasZt, corpIdList: e, pageNum: 1 })}
+                        showSearch
+                        style={{ minWidth: 200 }}
+                        maxTagCount={1}
+                        mode='multiple'
+                        placeholder="选择企业"
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                        }
+                        allowClear
+                        options={getCorpAllList?.data?.data?.map((item: any) => ({ label: item.corpName, value: item.corpId }))}
+                    />}
                 items={[
                     {
                         key: '3',
@@ -459,7 +486,7 @@ const Home: React.FC = () => {
         </Spin>
 
         {/* uuid */}
-        <UuidTem />
+        <UuidTem getCorpAllList={getCorpAllList}/>
     </div>
 };
 

+ 17 - 3
src/pages/weComTask/page/home/uuidTem.tsx

@@ -3,13 +3,13 @@ import React, { useEffect } from "react"
 import { AreaChartOutlined, SearchOutlined, UserOutlined } from "@ant-design/icons"
 
 import { GetExternalUserRepeatByCorpListApiProps, getSelectQcUuidStatisticPageListApi } from "../../API/home"
-import { Avatar, Button, Card, Input, InputNumber, Space, Table, Typography } from "antd";
+import { Avatar, Button, Card, Input, InputNumber, Select, Space, Table, Typography } from "antd";
 import SearchBox from "../../components/searchBox";
 import CorpDetails from "./corpDetails";
 import CorpUserDetails from "./corpUserDetails";
 const { Title, Text } = Typography;
 
-const UuidTem: React.FC = () => {
+const UuidTem: React.FC<{ getCorpAllList: any }> = ({ getCorpAllList }) => {
 
     /*****************************************/
     const [queryParmas, setQueryParmas] = React.useState<GetExternalUserRepeatByCorpListApiProps>({ pageNum: 1, pageSize: 20 })
@@ -37,7 +37,21 @@ const UuidTem: React.FC = () => {
             </>}
         >
             <>
-                <Input onChange={(e) => setQueryParmasNew({ ...queryParmasNew, corpName: e.target.value })} value={queryParmasNew?.corpName} placeholder="企微名称" allowClear />
+                <Select
+                    value={queryParmasNew?.corpIdList}
+                    onChange={(e) => setQueryParmasNew({ ...queryParmasNew, corpIdList: e })}
+                    showSearch
+                    style={{ minWidth: 150 }}
+                    maxTagCount={1}
+                    mode='multiple'
+                    placeholder="选择企业"
+                    filterOption={(input, option) =>
+                        ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                    }
+                    allowClear
+                    options={getCorpAllList?.data?.data?.map((item: any) => ({ label: item.corpName, value: item.corpId }))}
+                />
+                {/* <Input onChange={(e) => setQueryParmasNew({ ...queryParmasNew, corpName: e.target.value })} value={queryParmasNew?.corpName} placeholder="企微名称" allowClear /> */}
                 <Input onChange={(e) => setQueryParmasNew({ ...queryParmasNew, name: e.target.value })} value={queryParmasNew?.name} placeholder="客户昵称" allowClear />
                 <InputNumber placeholder="最小企业id个数" style={{ width: 125 }} value={queryParmasNew?.minCorpIdCount} onChange={(e) => setQueryParmasNew({ ...queryParmasNew, minCorpIdCount: e })} />
                 <InputNumber placeholder="最大企业id个数" style={{ width: 125 }} value={queryParmasNew?.maxCorpIdCount} onChange={(e) => setQueryParmasNew({ ...queryParmasNew, maxCorpIdCount: e })} />

+ 100 - 0
src/pages/weComTask/page/logs/index.less

@@ -0,0 +1,100 @@
+.logsPage {
+    width: 100%;
+    overflow: hidden;
+    overflow-x: auto;
+}
+
+.container {
+    min-width: 1200px;
+    padding-bottom: 8px;
+}
+
+.dataList {
+    width: calc(100% / 7);
+    background-color: #f8fafc;
+    border-radius: 6px;
+    height: 100%;
+    display: flex;
+    padding-bottom: 10px;
+    flex-direction: column;
+    box-sizing: border-box;
+
+    .dataList_header {
+        padding: 10px;
+        box-sizing: border-box;
+    }
+}
+
+.dataList_content {
+    height: calc(100% - 24px);
+    padding: 0 10px;
+    // overflow: hidden;
+    overflow-y: auto;
+}
+
+.dataList_content_item {
+    background-color: #ececec;
+    color: #6b7280;
+}
+
+.calendar_task {
+    padding: 4px 8px;
+    margin-bottom: 8px;
+    border-radius: 6px;
+    font-size: 14px;
+    cursor: pointer;
+    transition: all 0.2s ease;
+
+    .time {
+        font-size: 12px;
+    }
+
+    &:hover {
+        transform: translateX(3px);
+    }
+}
+
+.summaryList {
+    height: calc(100% - 46px);
+    overflow-y: auto;
+}
+
+.moduleCard {
+    background-color: #f8fafc;
+    border-radius: 6px;
+    padding: 16px;
+    transition: all 0.2s ease;
+    cursor: pointer;
+    margin-bottom: 8px;
+
+    &:hover {
+        transform: translateX(3px);
+    }
+
+    .moduleCard_header {
+        display: flex;
+        gap: 8px;
+        align-items: center;
+        font-size: 16px;
+        color: #333333;
+        font-weight: bold;
+        margin-bottom: 8px;
+    }
+
+    .moduleCard_center {
+        color: #6b7280;
+        margin-bottom: 6px;
+        font-size: 14px;
+    }
+
+    .moduleCard_footer {
+        display: flex;
+        justify-content: space-between;
+        font-size: 12px;
+        color: #16a34a;
+
+        .error {
+            color: #dc2626;
+        }
+    }
+}

+ 269 - 0
src/pages/weComTask/page/logs/index.tsx

@@ -0,0 +1,269 @@
+import { Avatar, Card, Col, DatePicker, Flex, Row, Spin, Statistic } from "antd";
+import { ArrowDownOutlined, ArrowUpOutlined, UnorderedListOutlined } from "@ant-design/icons";
+import useNewToken from "@/Hook/useNewToken";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faListAlt, faPlusCircle, faExclamationTriangle, faChartPie, faUsers, faCommentDots, faUsersCog } from "@fortawesome/free-solid-svg-icons";
+import { useRef, useState } from "react";
+import dayjs from "dayjs";
+import style from "./index.less";
+import { useSize } from "ahooks";
+import { RangePickerProps } from "antd/es/date-picker";
+
+const weekMap = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
+
+const Logs: React.FC = () => {
+
+    /****************************************/
+    const { token } = useNewToken()
+
+    const ref = useRef<HTMLDivElement>(null)
+    const size = useSize(ref);
+
+    const [weekQuery, setWeekQuery] = useState<{ startTime: string, endTime: string }>({
+        startTime: dayjs().subtract(6, 'day').format('YYYY-MM-DD'),
+        endTime: dayjs().format('YYYY-MM-DD')
+    })
+    const [startClickDate, setStartClickDate] = useState<string>()
+    const [endClickDate, setEndClickDate] = useState<string>()
+    const [rangeStart, setRangeStart] = useState<boolean>(false)
+    /****************************************/
+
+    const disabledDate: RangePickerProps['disabledDate'] = (current) => {
+        // console.log(dayjs(current).format('YYYY-MM-DD'))
+        // Can not select days before today and today
+        if (startClickDate) {
+            return current && current >= dayjs(startClickDate).add(7, 'day');
+        }
+        if (endClickDate) {
+            return current && current <= dayjs(endClickDate).subtract(7, 'day');
+        }
+        return false
+    };
+
+    const height = size?.height ? `calc(100vh - ${size?.height}px - 105px)` : 600
+    return <div className={style.logsPage}>
+        <Flex vertical gap={16} className={style.container}>
+            <Spin spinning={false}>
+                <Row gutter={[16, 16]} style={{ marginInline: 0 }} ref={ref}>
+                    <Col xs={24} sm={12} lg={6}>
+                        <Card variant="borderless" style={{ width: '100%' }} hoverable styles={{ body: { position: 'relative' } }}>
+                            <Flex vertical>
+                                <Statistic
+                                    title={<strong style={{ fontSize: 14 }}>总任务数</strong>}
+                                    value={2846}
+                                    style={{ flex: 1 }}
+                                    valueStyle={{ fontSize: 30, fontWeight: 'bold' }}
+                                />
+                                <Flex vertical>
+                                    <Statistic
+                                        value={11.28}
+                                        precision={2}
+                                        valueStyle={{ color: '#3f8600', fontSize: 14 }}
+                                        prefix={<ArrowUpOutlined />}
+                                        suffix="%"
+                                    />
+                                    <span style={{ fontSize: 13, color: token.colorTextDescription }}>较上月变化百分比</span>
+                                </Flex>
+                            </Flex>
+                            <Avatar style={{ backgroundColor: '#DBEAFE', color: '#2563eb', position: 'absolute', right: 15, top: 15 }} size={38}>
+                                <FontAwesomeIcon icon={faListAlt} />
+                            </Avatar>
+                        </Card>
+                    </Col>
+
+                    <Col xs={24} sm={12} lg={6}>
+                        <Card variant="borderless" style={{ width: '100%' }} hoverable styles={{ body: { position: 'relative' } }}>
+                            <Flex vertical>
+                                <Statistic
+                                    title={<strong style={{ fontSize: 14 }}>今日新增任务数</strong>}
+                                    value={128}
+                                    style={{ flex: 1 }}
+                                    valueStyle={{ fontSize: 30, fontWeight: 'bold' }}
+                                />
+                                <Flex vertical>
+                                    <Statistic
+                                        value={8.28}
+                                        precision={2}
+                                        valueStyle={{ color: '#cf1322', fontSize: 14 }}
+                                        prefix={<ArrowDownOutlined />}
+                                        suffix="%"
+                                    />
+                                    <span style={{ fontSize: 13, color: token.colorTextDescription }}>较昨日新增变化百分比</span>
+                                </Flex>
+                            </Flex>
+                            <Avatar style={{ backgroundColor: '#dcfce7', color: '#16a34a', position: 'absolute', right: 15, top: 15 }} size={38}>
+                                <FontAwesomeIcon icon={faPlusCircle} />
+                            </Avatar>
+                        </Card>
+                    </Col>
+
+                    <Col xs={24} sm={12} lg={6}>
+                        <Card variant="borderless" style={{ width: '100%' }} hoverable styles={{ body: { position: 'relative' } }}>
+                            <Flex vertical>
+                                <Statistic
+                                    title={<strong style={{ fontSize: 14 }}>今日异常/失败任务数</strong>}
+                                    value={42}
+                                    style={{ flex: 1 }}
+                                    valueStyle={{ fontSize: 30, fontWeight: 'bold' }}
+                                />
+                                <Flex vertical>
+                                    <Statistic
+                                        value={3.28}
+                                        precision={2}
+                                        valueStyle={{ color: '#3f8600', fontSize: 14 }}
+                                        prefix={<ArrowUpOutlined />}
+                                        suffix="%"
+                                    />
+                                    <span style={{ fontSize: 13, color: token.colorTextDescription }}>较昨日变化百分比</span>
+                                </Flex>
+                            </Flex>
+                            <Avatar style={{ backgroundColor: '#fee2e2', color: '#dc2626', position: 'absolute', right: 15, top: 15 }} size={38}>
+                                <FontAwesomeIcon icon={faExclamationTriangle} />
+                            </Avatar>
+                        </Card>
+                    </Col>
+
+                    <Col xs={24} sm={12} lg={6}>
+                        <Card variant="borderless" style={{ width: '100%' }} hoverable styles={{ body: { position: 'relative' } }}>
+                            <Flex vertical>
+                                <Statistic
+                                    title={<strong style={{ fontSize: 14 }}>今日任务完成率</strong>}
+                                    value={80}
+                                    style={{ flex: 1 }}
+                                    suffix="%"
+                                    precision={2}
+                                    valueStyle={{ fontSize: 30, fontWeight: 'bold' }}
+                                />
+                                <Flex vertical>
+                                    <Statistic
+                                        value={1.28}
+                                        precision={2}
+                                        valueStyle={{ color: '#3f8600', fontSize: 14 }}
+                                        prefix={<ArrowUpOutlined />}
+                                        suffix="%"
+                                    />
+                                    <span style={{ fontSize: 13, color: token.colorTextDescription }}>较昨日变化百分比</span>
+                                </Flex>
+                            </Flex>
+                            <Avatar style={{ backgroundColor: '#f3e8ff', color: '#9333ea', position: 'absolute', right: 15, top: 15 }} size={38}>
+                                <FontAwesomeIcon icon={faChartPie} />
+                            </Avatar>
+                        </Card>
+                    </Col>
+                </Row>
+            </Spin>
+            <Row gutter={[16, 16]} style={{ marginInline: 0 }}>
+                <Col xs={24} xl={18}>
+                    <Card style={{ width: '100%', height }} hoverable styles={{ body: { height: '100%' } }}>
+                        <Flex justify="space-between" gap={8} align="center" style={{ marginBottom: 16, height: 30 }}>
+                            <DatePicker.RangePicker
+                                placeholder={['开始日期', '结束日期']}
+                                variant="filled"
+                                onCalendarChange={(e, dateStrings, info) => {
+                                    if (rangeStart) { // 点过一次
+                                        setWeekQuery({ ...weekQuery, startTime: dateStrings[0], endTime: dateStrings[1] })
+                                    } else {
+                                        if (info.range === 'start') {
+                                            setStartClickDate(dateStrings[0])
+                                            setWeekQuery({ ...weekQuery, startTime: dateStrings[0], endTime: dateStrings[0] })
+                                        } else {
+                                            setEndClickDate(dateStrings[1])
+                                            setWeekQuery({ ...weekQuery, startTime: dateStrings[1], endTime: dateStrings[1] })
+                                        }
+                                        setRangeStart(true)
+                                    }
+                                }}
+                                onOpenChange={(open) => {
+                                    if (!open) {
+                                        setRangeStart(false)
+                                        setStartClickDate(undefined)
+                                        setEndClickDate(undefined)
+                                    }
+                                }}
+                                disabledDate={disabledDate}
+                                value={(weekQuery.startTime && weekQuery.endTime) ? [dayjs(weekQuery.startTime), dayjs(weekQuery.endTime)] : undefined}
+                            />
+                        </Flex>
+                        <Flex gap={8} style={{ height: 'calc(100% - 46px)' }}>
+                            {Array.from({ length: 7 }, (_, i) => {
+                                const date = i === 0 ? dayjs(weekQuery.startTime) : dayjs(weekQuery.startTime).add(i, "day");
+                                return {
+                                    date: date.format("MM/DD"),
+                                    weekday: weekMap[date.day()],
+                                };
+                            }).map(item => <div key={item.date} className={style.dataList}>
+                                <Flex justify="space-between" align="center" className={style.dataList_header}>
+                                    <strong style={{ fontSize: 14 }}>{item.weekday}</strong>
+                                    <span>{item.date}</span>
+                                </Flex>
+                                <div className={style.dataList_content}>
+                                    <div title="待发送:将在指定时间发送" className={`${style.dataList_content_item} ${style.calendar_task}`}>
+                                        <div className={style.title}>官方用户群发</div>
+                                        <div className={style.time}>14:30-15:00</div>
+                                    </div>
+                                </div>
+                            </div>)}
+                        </Flex>
+
+                    </Card>
+                </Col>
+                <Col xs={24} xl={6}>
+                    <Card style={{ width: '100%', height }} hoverable styles={{ body: { height: '100%', paddingInline: 0 } }}>
+                        <Flex justify="space-between" gap={8} align="center" style={{ paddingInline: token.paddingLG, marginBottom: 16, height: 30 }}>
+                            <strong style={{ fontSize: 16 }}>任务列表摘要</strong>
+                        </Flex>
+                        <div className={style.summaryList} style={{ paddingInline: token.paddingLG }}>
+                            <div className={style.moduleCard}>
+                                <div className={style.moduleCard_header}>
+                                    <Avatar style={{ backgroundColor: '#dbeafe', color: '#2563eb' }} size={30}>
+                                        <FontAwesomeIcon icon={faUsers} />
+                                    </Avatar>
+                                    官方用户群发
+                                </div>
+                                <div className={style.moduleCard_center}>
+                                    总任务数:586 | 今日新增:24
+                                </div>
+                                <div className={style.moduleCard_footer}>
+                                    <span>完成率:96.2%</span>
+                                    <span className={style.error}>异常:2</span>
+                                </div>
+                            </div>
+                            <div className={style.moduleCard}>
+                                <div className={style.moduleCard_header}>
+                                    <Avatar style={{ backgroundColor: '#dcfce7', color: '#16a34a' }} size={30}>
+                                        <FontAwesomeIcon icon={faCommentDots} />
+                                    </Avatar>
+                                    朋友圈
+                                </div>
+                                <div className={style.moduleCard_center}>
+                                    总任务数:666 | 今日新增:111
+                                </div>
+                                <div className={style.moduleCard_footer}>
+                                    <span>完成率:96.2%</span>
+                                    <span className={style.error}>异常:2</span>
+                                </div>
+                            </div>
+                            <div className={style.moduleCard}>
+                                <div className={style.moduleCard_header}>
+                                    <Avatar style={{ backgroundColor: '#f3e8ff', color: '#9333ea' }} size={30}>
+                                        <FontAwesomeIcon icon={faUsersCog} />
+                                    </Avatar>
+                                    群聊群发
+                                </div>
+                                <div className={style.moduleCard_center}>
+                                    总任务数:234 | 今日新增:123
+                                </div>
+                                <div className={style.moduleCard_footer}>
+                                    <span>完成率:96.2%</span>
+                                    <span className={style.error}>异常:2</span>
+                                </div>
+                            </div>
+                        </div>
+                    </Card>
+                </Col>
+            </Row>
+        </Flex>
+    </div>
+}
+
+export default Logs;

+ 38 - 0
yarn.lock

@@ -1349,6 +1349,44 @@
     lodash.isundefined "^3.0.1"
     lodash.uniq "^4.5.0"
 
+"@fortawesome/fontawesome-common-types@7.1.0":
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-7.1.0.tgz#a4e0b7e40073d5fdef41182da1bc216a05875659"
+  integrity sha512-l/BQM7fYntsCI//du+6sEnHOP6a74UixFyOYUyz2DLMXKx+6DEhfR3F2NYGE45XH1JJuIamacb4IZs9S0ZOWLA==
+
+"@fortawesome/fontawesome-svg-core@^7.1.0":
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-7.1.0.tgz#b0a45363a4e95ec985130393c0b1b95717ef0760"
+  integrity sha512-fNxRUk1KhjSbnbuBxlWSnBLKLBNun52ZBTcs22H/xEEzM6Ap81ZFTQ4bZBxVQGQgVY0xugKGoRcCbaKjLQ3XZA==
+  dependencies:
+    "@fortawesome/fontawesome-common-types" "7.1.0"
+
+"@fortawesome/free-brands-svg-icons@^7.1.0":
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-7.1.0.tgz#68e08b05a99566782cff8e5a62b1cb69bdec2d35"
+  integrity sha512-9byUd9bgNfthsZAjBl6GxOu1VPHgBuRUP9juI7ZoM98h8xNPTCTagfwUFyYscdZq4Hr7gD1azMfM9s5tIWKZZA==
+  dependencies:
+    "@fortawesome/fontawesome-common-types" "7.1.0"
+
+"@fortawesome/free-regular-svg-icons@^7.1.0":
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-7.1.0.tgz#e57c5eb63e6a30bb4fe2276aba851e3470452680"
+  integrity sha512-0e2fdEyB4AR+e6kU4yxwA/MonnYcw/CsMEP9lH82ORFi9svA6/RhDyhxIv5mlJaldmaHLLYVTb+3iEr+PDSZuQ==
+  dependencies:
+    "@fortawesome/fontawesome-common-types" "7.1.0"
+
+"@fortawesome/free-solid-svg-icons@^7.1.0":
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-7.1.0.tgz#cb9d149cefd3419a7a3fd041106e3a2315463e3e"
+  integrity sha512-Udu3K7SzAo9N013qt7qmm22/wo2hADdheXtBfxFTecp+ogsc0caQNRKEb7pkvvagUGOpG9wJC1ViH6WXs8oXIA==
+  dependencies:
+    "@fortawesome/fontawesome-common-types" "7.1.0"
+
+"@fortawesome/react-fontawesome@^3.1.0":
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-3.1.0.tgz#bb22f49622574a80ee74213ad8c68107f3f47e64"
+  integrity sha512-5OUQH9aDH/xHJwnpD4J7oEdGvFGJgYnGe0UebaPIdMW9UxYC/f5jv2VjVEgnikdJN0HL8yQxp9Nq+7gqGZpIIA==
+
 "@hot-loader/react-dom@^17.0.2":
   version "17.0.2"
   resolved "https://registry.yarnpkg.com/@hot-loader/react-dom/-/react-dom-17.0.2.tgz#0b24e484093e8f97eb5c72bebdda44fc20bc8400"