wjx 3 semanas atrás
pai
commit
ca1fefeaf8

+ 1 - 0
src/pages/weComTask/API/businessPlan/typings.d.ts

@@ -33,5 +33,6 @@ declare namespace BUSINES_SPLAN_API {
         createTimeMax?: string,
         projectType?: number,
         status?: number
+        projectGroupId?: number
     }
 }

+ 15 - 0
src/pages/weComTask/API/device/index.ts

@@ -0,0 +1,15 @@
+import request from "@/utils/request";
+const { api } = process.env.CONFIG;
+
+/**
+ * 设备 列表
+ * @param data 
+ * @returns 
+ */
+export async function getAllOfUserListApi({ userId, ...params }: { userId: number, corpId?: string, corpUserName?: string }) {
+    return request({
+        url: api + `/corp/corpUser/allOfUserList/${userId}`,
+        method: 'GET',
+        params
+    });
+}

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

@@ -13,5 +13,6 @@ declare namespace GROUP_CHAT_API {
         createTimeMax?: string,
         status?: number,
         corpChatUserIds?: any[]
+        projectGroupId?: number
     }
 }

+ 54 - 2
src/pages/weComTask/API/home/index.ts

@@ -38,7 +38,7 @@ export function getExternalUserRepeatByCorpAtlasApi() {
  * 用户重粉次数统计 主体维度
  * @returns 
  */
-export function getCorpExternalUserRepeatListApi(data: { pageNum: number, pageSize: number, corpName?: string }) {
+export function getCorpExternalUserRepeatListApi(data: { pageNum: number, pageSize: number, corpName?: string, sortType?: string, orderByField?: string }) {
     return request({
         url: api + `/corpOperation/ads/corp/corpExternalUserRepeat`,
         method: 'POST',
@@ -51,10 +51,62 @@ export function getCorpExternalUserRepeatListApi(data: { pageNum: number, pageSi
  * @param data 
  * @returns 
  */
-export function getExternalUserRepeatByCorpListApi(data: { pageNum: number, pageSize: number, corpName?: string }) {
+export function getExternalUserRepeatByCorpListApi(data: { pageNum: number, pageSize: number, corpName?: string, sortType?: string, orderByField?: string }) {
     return request({
         url: api + `/corpOperation/ads/corp/externalUserRepeatByCorp`,
         method: 'POST',
         data
     })
+}
+
+export interface GetExternalUserRepeatByCorpListApiProps {
+    pageNum: number,
+    pageSize: number,
+    corpName?: string,
+    name?: string,
+    minCorpIdCount?: number,
+    maxCorpIdCount?: number,
+    minCorpUserIdCount?: number,
+    maxCorpUserIdCount?: number,
+    sortType?: string,
+    orderByField?: string
+}
+
+/**
+ * Uuid维度统计列表
+ * @param data 
+ * @returns 
+ */
+export function getSelectQcUuidStatisticPageListApi(data: GetExternalUserRepeatByCorpListApiProps) {
+    return request({
+        url: api + `/corpOperation/ads/corp/selectQcUuidStatisticPage`,
+        method: 'POST',
+        data
+    })
+}
+
+/**
+ * 企业id个数 详情
+ * @param params 
+ * @returns 
+ */
+export function getSelectCorpIdsByQcUuidListApi(params: { qcUuid: string }) {
+    return request({
+        url: api + `/corpOperation/ads/corp/selectCorpIdsByQcUuid`,
+        method: 'GET',
+        params
+    })
+}
+
+/**
+ * 客服号id个数详情
+ * @param params 
+ * @returns 
+ */
+export function getSelectCorpUserIdsByQcUuidListApi(params: { qcUuid: string }) {
+    return request({
+        url: api + `/corpOperation/ads/corp/selectCorpUserIdsByQcUuid`,
+        method: 'GET',
+        params
+    })
 }

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

@@ -9,6 +9,7 @@ import Log from './log';
 import { inject, observer } from 'mobx-react';
 import { toJS } from 'mobx';
 import AddToGroup from './components/addToGroup';
+import { getProjectGroupsAllListApi } from '@/pages/weComTask/API/groupManage';
 
 /**
  * 任务列表
@@ -25,7 +26,9 @@ const TaskList: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookL
     const [logOpenData, setLogOpenData] = useState<{ visible: boolean, data: any }>({ visible: false, data: {} })
     const [selectedRows, setselectedRows] = useState<any[]>([])
     const [addToGroupData, setAddToGroupData] = useState<{ visible: boolean, data: any[] }>({ visible: false, data: undefined })
+    const [groupList, setGroupList] = useState<{ label: string, value: number }[]>([])
 
+    const getProjectGroupsAllList = useAjax(() => getProjectGroupsAllListApi())
     const getProjectList = useAjax((params) => getProjectListApi(params))
     const delProject = useAjax((params) => delProjectApi(params))
     const cancelProject = useAjax((params) => cancelProjectApi(params))
@@ -38,6 +41,12 @@ const TaskList: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookL
         }
     }, [])
 
+    useEffect(() => {
+        getProjectGroupsAllList.run().then(res => {
+            setGroupList([{ label: '空项目组', value: 0 }, ...res?.data?.map(item => ({ label: item.name, value: item.id })) || []])
+        })
+    }, [])
+
     useEffect(() => {
         getProjectList.run(queryFormNew)
     }, [queryFormNew])
@@ -93,6 +102,21 @@ const TaskList: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookL
                 <Button>任务名称</Button>
                 <Input placeholder='请输入任务名称' allowClear value={queryForm?.projectName} onChange={(e) => setQueryForm({ ...queryForm, projectName: e.target.value })} />
             </Space.Compact>
+            <Space.Compact>
+                <Button>项目组</Button>
+                <Select
+                    placeholder='请选择项目组'
+                    allowClear
+                    options={groupList}
+                    showSearch
+                    style={{ width: 150 }}
+                    filterOption={(input, option) =>
+                        (option?.label ?? '').toLowerCase().includes(input.toLowerCase())
+                    }
+                    value={queryForm?.projectGroupId}
+                    onChange={(value) => setQueryForm({ ...queryForm, projectGroupId: value })}
+                />
+            </Space.Compact>
             <Space.Compact>
                 <Button>创建时间</Button>
                 <DatePicker.RangePicker

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

@@ -23,7 +23,7 @@ const taskListColumns = (
             title: '操作',
             dataIndex: 'cz',
             key: 'cz',
-            width: 210,
+            width: 230,
             render(_, record) {
                 return <Space>
                     {record?.status === 1 ? <Popconfirm
@@ -57,6 +57,17 @@ const taskListColumns = (
             width: 120,
             ellipsis: true
         },
+        {
+            title: '项目组',
+            dataIndex: 'projectGroupInfo',
+            key: 'projectGroupInfo',
+            width: 120,
+            ellipsis: true,
+            align: 'center',
+            render(value) {
+                return value?.name || '--'
+            },
+        },
         {
             title: '基础信息',
             dataIndex: 'bizType',

+ 1 - 1
src/pages/weComTask/page/chatRecordManage/riskWord/AddModify.tsx

@@ -105,7 +105,7 @@ const AddModify: React.FC<Props> = ({ corpList, initialValues, visible, onClose,
                 {(fields, { add, remove }) => (
                     <>
                         {fields.map(({ key, name, ...restField }, index) => {
-                            const corpId = corpUserDTOList[index]?.corpId
+                            const corpId = corpUserDTOList?.[index]?.corpId
                             const corpUserList = corpList?.find(item => item?.t1?.corpId === corpId)?.t2
                             return <Card
                                 key={key}

+ 7 - 0
src/pages/weComTask/page/corpUserManage/index.less

@@ -140,4 +140,11 @@
         }
 
     }
+}
+
+.notOnline{
+    background-color: rgba(248, 183, 183, 0.2) !important;
+    td{
+        background-color: transparent !important;
+    }
 }

+ 114 - 0
src/pages/weComTask/page/device/index.tsx

@@ -0,0 +1,114 @@
+import React, { useEffect, useRef, useState } from "react";
+import style from '../corpUserManage/index.less'
+import { Button, Card, Input, Select, Table, Tabs } from "antd";
+import { MenuUnfoldOutlined, MenuFoldOutlined, SearchOutlined } from '@ant-design/icons';
+import TeamMembers from "@/components/Team/teamMembers";
+import { getAdAccountAllOfMember, getCorpAllListApi } from "@/API/global";
+import { useAjax } from "@/Hook/useAjax";
+import { getAllOfUserListApi } from "../../API/device";
+import { useSize } from "ahooks";
+import { WeTableConfig } from "./tableConfig";
+import useNewToken from "@/Hook/useNewToken";
+import SearchBox from "../../components/searchBox";
+
+
+const DevicePage: React.FC = () => {
+
+    /**************************************/
+    const { token } = useNewToken();
+    const ref = useRef<HTMLDivElement>(null)
+    const size = useSize(ref)
+    const [activeKey, setActiveKey] = useState<string>('1')
+    const [showLeft, setShowLeft] = useState<boolean>(false)
+    const userIdStr = sessionStorage.getItem('userId')
+    const [userId, setUserId] = useState<number>(userIdStr ? Number(userIdStr) : undefined);
+    const [queryForm, setQueryForm] = useState<{ corpId?: string, corpUserName?: string }>({})
+    const [queryFormNew, setQueryFormNew] = useState<{ corpId?: string, corpUserName?: string }>({})
+
+    const allOfMember = useAjax(() => getAdAccountAllOfMember())
+    const getAllOfUserList = useAjax((params) => getAllOfUserListApi(params))
+    const getCorpAllList = useAjax((params) => getCorpAllListApi(params))
+    /**************************************/
+
+    useEffect(() => {
+        allOfMember.run()
+        getCorpAllList.run({})
+    }, [])
+
+    useEffect(() => {
+        if (userId) {
+            getAllOfUserList.run({ userId, ...queryForm })
+        }
+    }, [userId, queryForm])
+
+    return <div className={style.corpUserManage}>
+        <Tabs
+            tabBarStyle={{ marginBottom: 1 }}
+            activeKey={activeKey}
+            type="card"
+            onChange={(activeKey) => {
+                if (activeKey !== 'contract') {
+                    setUserId(userIdStr ? Number(userIdStr) : undefined)
+                    setActiveKey(activeKey)
+                } else {
+                    setShowLeft(!showLeft)
+                }
+            }}
+            items={[{ label: '我的', key: '1' }, { label: '组员', key: '2' }, { label: showLeft ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />, key: 'contract' }]}
+        />
+
+        <div className={style.corpUserManage_bottom}>
+            {!showLeft && activeKey === '2' && <TeamMembers
+                allOfMember={allOfMember}
+                onChange={(putUserId) => {
+                    setUserId(putUserId)
+                }}
+                value={userId}
+            />}
+
+            <Card className={style.corpUserList} styles={{ body: { padding: 0, display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' } }}>
+                <SearchBox
+                    bodyPadding={`16px 16px 12px`}
+                    buttons={<>
+                        <Button type="primary" onClick={() => {
+                            setQueryForm({ ...queryFormNew })
+                        }} loading={getAllOfUserList.loading} icon={<SearchOutlined />}>搜索</Button>
+                    </>}
+                >
+                    <>
+                        <Select
+                            value={queryFormNew?.corpId}
+                            onChange={(e) => setQueryFormNew({ ...queryFormNew, corpId: e })}
+                            showSearch
+                            style={{ width: 150 }}
+                            maxTagCount={1}
+                            placeholder="主体"
+                            filterOption={(input, option) =>
+                                ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                            }
+                            allowClear
+                            options={getCorpAllList?.data?.data?.map((item: any) => ({ label: item.corpName, value: item.corpId }))}
+                        />
+                        <Input onChange={(e) => setQueryFormNew({ ...queryFormNew, corpUserName: e.target.value as any })} value={queryFormNew?.corpUserName} placeholder="企微号名称" allowClear />
+                    </>
+                </SearchBox>
+                <div className={style.corpUserList_table} ref={ref}>
+                    <Table
+                        dataSource={getAllOfUserList?.data?.data}
+                        columns={WeTableConfig()}
+                        bordered
+                        pagination={{
+                            pageSize: 20
+                        }}
+                        rowClassName={(record) => record?.phoneInfo?.online && record?.phoneInfo?.accessibility != false ? '' : style.notOnline}
+                        rowKey={(row) => userId + '_' + row.corpUserId + '_' + row.corpId}
+                        loading={getAllOfUserList?.loading}
+                        scroll={{ y: size?.height && ref.current ? size?.height - ref.current.querySelector('.ant-table-thead').clientHeight - 42 : 300 }}
+                    />
+                </div>
+            </Card>
+        </div>
+    </div>
+}
+
+export default DevicePage

+ 251 - 0
src/pages/weComTask/page/device/tableConfig.tsx

@@ -0,0 +1,251 @@
+import { copy } from "@/utils/utils"
+import { Badge, Space, Switch, Tag, Tooltip } from "antd"
+import { ColumnsType } from "antd/es/table"
+import { ApiOutlined, QuestionCircleOutlined, ThunderboltOutlined } from "@ant-design/icons"
+
+export function WeTableConfig(): ColumnsType<any> {
+
+    const arr: ColumnsType<any> = [
+        {
+            title: '企微号ID',
+            dataIndex: 'corpUserId',
+            key: 'corpUserId',
+            align: 'center',
+            width: 100,
+            ellipsis: true,
+            fixed: 'left',
+            render: (a: string) => {
+                return <a onClick={() => copy(a)}>{a}</a>
+            }
+        },
+        {
+            title: '企微号',
+            dataIndex: 'name',
+            key: 'name',
+            align: 'center',
+            width: 100,
+            ellipsis: true,
+        },
+        {
+            title: '所属企业',
+            dataIndex: 'corp',
+            key: 'corp',
+            align: 'center',
+            width: 120,
+            ellipsis: true,
+            render(value) {
+                return value?.corpName || '--'
+            },
+        },
+        {
+            title: '企业ID',
+            dataIndex: 'corpId',
+            key: 'corpId',
+            align: 'center',
+            width: 120,
+            ellipsis: true,
+            render: (a: string) => {
+                return <a onClick={() => copy(a)}>{a}</a>
+            }
+        },
+        {
+            title: '投手',
+            dataIndex: 'putUser',
+            key: 'putUser',
+            align: 'center',
+            width: 80,
+            render: (a: { nickName: string }) => {
+                return <span>{a?.nickName || '--'}</span>
+            }
+        },
+        {
+            title: '运营',
+            dataIndex: 'operUser',
+            key: 'operUser',
+            align: 'center',
+            width: 80,
+            render: (a: { nickName: string }) => {
+                return <span>{a?.nickName || '--'}</span>
+            }
+        },
+        {
+            title: '绑定手机UUID',
+            dataIndex: 'phoneUuid',
+            key: 'phoneUuid',
+            align: 'center',
+            width: 120,
+            ellipsis: true,
+            render: (a: any) => {
+                return a ? <a onClick={() => copy(a)}>{a}</a> : '--'
+            }
+        },
+        {
+            title: <>关联手机企微分身<Tooltip title="摩托罗拉手机存在多个企微分身时需要设置的配置,红米,华为手机忽略"> <QuestionCircleOutlined /></Tooltip></>,
+            dataIndex: 'remark',
+            key: 'remark',
+            align: 'center',
+            width: 120,
+            render: (a: any, b: any) => {
+                return a ? <Space>{"企业微信" + a?.replace("企业微信", "")}</Space> : '--'
+            }
+
+        },
+        {
+            title: '绑定电脑UUID',
+            dataIndex: 'uuid',
+            key: 'uuid',
+            align: 'center',
+            width: 120,
+            ellipsis: true,
+            render: (a: any) => {
+                return a ? <a onClick={() => copy(a)}>{a}</a> : '--'
+            }
+        },
+        {
+            title: '手机编号',
+            dataIndex: 'phoneInfo',
+            key: 'phoneInfo',
+            align: 'center',
+            width: 80,
+            render: (a: any) => {
+                return a?.phoneNumber || '--'
+            }
+        },
+        {
+            title: '手机品牌',
+            dataIndex: 'phoneInfo',
+            key: 'phoneInfo',
+            align: 'center',
+            width: 80,
+            render: (b: any) => {
+                let model = b?.model
+                try {
+                    model = JSON.parse(model)
+                } catch (error) {
+                    model = b?.model
+                }
+                return model ? <Tooltip title={typeof model === "string" ? <>设备型号:{model}</> : <Space direction="vertical"><span>设备型号:{model?.model}</span><span>系统版本:{model?.sdkVersion}</span><span>SDK版本:{model?.sdkInt}</span><span>系统:{model?.displayName}</span></Space>}><a>{b?.brand || '--'}</a></Tooltip> : <span>{b?.brand || "--"}</span>
+            }
+        },
+        {
+            title: '脚本版本',
+            dataIndex: 'phoneInfo',
+            key: 'phoneInfo',
+            align: 'center',
+            width: 80,
+            render: (a: any) => {
+                return a?.sysVersion || '--'
+            }
+        },
+        {
+            title: '手机持有者',
+            dataIndex: 'phoneInfo',
+            key: 'phoneInfo',
+            align: 'center',
+            width: 70,
+            render: (a: any) => {
+                return a?.holder || '--'
+            }
+        },
+        {
+            title: '是否停用',
+            dataIndex: 'stopUse',
+            key: 'stopUse',
+            align: 'center',
+            width: 55,
+            fixed: 'right',
+            render: (a, b) => {
+                return <Switch checked={a} disabled size="small" />
+            }
+        },
+        {
+            title: <Tooltip title="此状态代表设备是否能正常接收指令,不在线时无法正常使用,请确保设备有电,并且秘密基地APP正常运行!">手机在线?</Tooltip>,
+            dataIndex: 'phoneInfo',
+            key: 'phoneInfo',
+            align: 'center',
+            width: 55,
+            fixed: 'right',
+            render: (a: any) => {
+                return a ? a?.online ? <Tag color="success">是</Tag> : <Tag color="error">否</Tag> : ''
+            }
+        },
+        {
+            title: '电脑可用',
+            dataIndex: 'pcInfo',
+            key: 'pcInfo',
+            align: 'center',
+            width: 50,
+            fixed: 'right',
+            render: (a: any) => {
+                return a ? a?.enabled ? <Badge status="success" text="是" /> : <Badge status="error" text="否" /> : '--'
+            }
+        },
+        {
+            title: <Tooltip title="无障碍服务是否正常,华为手机请重启手机,直到下次任务成功,才会刷新此状态为正常状态!">无障碍?</Tooltip>,
+            dataIndex: 'accessibility',
+            key: 'accessibility',
+            align: 'center',
+            width: 50,
+            fixed: 'right',
+            render: (a: any, b: any) => {
+                return b?.phoneInfo?.accessibility != null ? b?.phoneInfo?.accessibility == true ? <Badge status="success" text="有效" /> : <Badge status="error" text="无效" /> : '--'
+            }
+        },
+        // {
+        //     title: <Tooltip title="为否时无法云控手机,此状态证明APP与云控服务器的连接状态是否正常">支持云控?</Tooltip>,
+        //     dataIndex: 'on_line',
+        //     key: 'on_line',
+        //     align: 'center',
+        //     width: 50,
+        //     fixed: 'right',
+        //     render: (a: any, b: any) => {
+        //         return (b?.phoneInfo?.sysVersion?.includes("mmjdSw") && b?.phoneInfo?.sysVersion >= "mmjdSw1.6") ? <Badge status="success" text="是" />
+        //             : <Badge status="error" text="否" />
+        //     }
+        // },
+        {
+            title: '应用状态',
+            dataIndex: 'configStatus',
+            key: 'configStatus',
+            align: 'center',
+            width: 60,
+            fixed: 'right',
+            render: (a, b) => {
+                return <Badge text={a ? '已配置' : '未配置'} status={a ? 'success' : 'default'} />
+            }
+        },
+        {
+            title: '手机电量',
+            dataIndex: 'phone_cell',
+            key: 'phone_cell',
+            align: 'center',
+            width: 60,
+            fixed: 'right',
+            render: (a, b) => {
+                let dl = a ? a : b?.phoneInfo?.phoneCell
+                return b?.phoneInfo?.assistUuid ? <div>
+                    <div>
+                        <a style={dl >= 20 && dl <= 50 ? { color: '#e38b07' } : dl > 50 ? { color: '#4CAF50' } : { color: 'red' }}>{dl}</a>
+                        <a style={b?.phoneInfo?.phoneIsCharging ? { color: '#4CAF50' } : { color: 'red' }}>{b?.phoneInfo?.phoneIsCharging ? <ThunderboltOutlined /> : <ApiOutlined />}</a>
+                    </div>
+                </div> : <a style={dl >= 20 && dl <= 50 ? { color: '#e38b07' } : dl > 50 ? { color: '#4CAF50' } : { color: 'red' }}>{dl}</a>
+            }
+        },
+        {
+            title: <Tooltip
+                title='群发补发限制:补发是在手机长时间掉线,上线后对过时的任务补发,开启补发限制会限制补发条数.不建议每日群发多的账号。此类账号开启后一旦漏发的群发过多不会补发全部。失败的任务不会补发,只有手动在手机执行日志点击失败任务重试!'
+            >
+                开启群发补发限制 <QuestionCircleOutlined />
+            </Tooltip>,
+            dataIndex: 'closeReSend',
+            key: 'closeReSend',
+            align: 'center',
+            fixed: 'right',
+            width: 60,
+            render: (a, b) => {
+                return <Switch checkedChildren="开启" unCheckedChildren="关闭" checked={a} disabled size="small" />
+            }
+        }
+    ]
+    return arr
+}

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

@@ -10,6 +10,7 @@ import { inject, observer } from 'mobx-react';
 import Details from './details';
 import AddToGroup from '../../businessPlan/taskList/components/addToGroup';
 import SelectGroupLeader from '../../groupLeaderManage/selectGroupLeader';
+import { getProjectGroupsAllListApi } from '@/pages/weComTask/API/groupManage';
 
 const TaskList: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookListProps[], bookPlatForm: TASK_CREATE.BookPlatFormProps[] } } }> = ({ weComTaskStore }) => {
 
@@ -23,7 +24,9 @@ const TaskList: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookL
     const [selectedRows, setselectedRows] = useState<any[]>([])
     const [logOpenData, setLogOpenData] = useState<{ visible: boolean, data: any }>({ visible: false, data: {} })
     const [addToGroupData, setAddToGroupData] = useState<{ visible: boolean, data: any[] }>({ visible: false, data: undefined })
+    const [groupList, setGroupList] = useState<{ label: string, value: number }[]>([])
 
+    const getProjectGroupsAllList = useAjax(() => getProjectGroupsAllListApi())
     const getProjectList = useAjax((params) => getProjectListApi(params))
     const delProject = useAjax((params) => delProjectApi(params))
     const cancelProject = useAjax((params) => cancelProjectApi(params))
@@ -36,6 +39,12 @@ const TaskList: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookL
         }
     }, [])
 
+    useEffect(() => {
+        getProjectGroupsAllList.run().then(res => {
+            setGroupList([{ label: '空项目组', value: 0 }, ...res?.data?.map(item => ({ label: item.name, value: item.id })) || []])
+        })
+    }, [])
+
     useEffect(() => {
         const { corpChatUserIds, ...p } = queryFormNew
         let params: any = { ...p }
@@ -97,6 +106,21 @@ const TaskList: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookL
                 <Button>任务名称</Button>
                 <Input placeholder='请输入任务名称' allowClear value={queryForm?.projectName} onChange={(e) => setQueryForm({ ...queryForm, projectName: e.target.value })} />
             </Space.Compact>
+            <Space.Compact>
+                <Button>项目组</Button>
+                <Select
+                    placeholder='请选择项目组'
+                    allowClear
+                    options={groupList}
+                    showSearch
+                    style={{ width: 150 }}
+                    filterOption={(input, option) =>
+                        (option?.label ?? '').toLowerCase().includes(input.toLowerCase())
+                    }
+                    value={queryForm?.projectGroupId}
+                    onChange={(value) => setQueryForm({ ...queryForm, projectGroupId: value })}
+                />
+            </Space.Compact>
             <Space.Compact>
                 <Button>创建时间</Button>
                 <DatePicker.RangePicker

+ 12 - 1
src/pages/weComTask/page/groupChat/taskList/tableConfig.tsx

@@ -21,7 +21,7 @@ const taskListColumns = (
             title: '操作',
             dataIndex: 'cz',
             key: 'cz',
-            width: 160,
+            width: 200,
             render(_, record) {
                 return <Space>
                     {record?.status === 1 ? <Popconfirm
@@ -54,6 +54,17 @@ const taskListColumns = (
             width: 120,
             ellipsis: true
         },
+        {
+            title: '项目组',
+            dataIndex: 'projectGroupInfo',
+            key: 'projectGroupInfo',
+            width: 120,
+            ellipsis: true,
+            align: 'center',
+            render(value) {
+                return value?.name || '--'
+            },
+        },
         {
             title: '基础信息',
             dataIndex: 'bizType',

+ 24 - 0
src/pages/weComTask/page/groupChatSend/official/taskList/index.tsx

@@ -12,6 +12,7 @@ import { getBindMpListApi } from '@/pages/weComTask/API/corpUserAssign';
 import { getCorpAllListApi } from '@/API/global';
 import Select, { DefaultOptionType } from 'antd/es/select';
 import AddToGroup from '../../../businessPlan/taskList/components/addToGroup';
+import { getProjectGroupsAllListApi } from '@/pages/weComTask/API/groupManage';
 
 /**
  * 任务列表
@@ -30,7 +31,9 @@ const TaskList: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookL
     const [mpList, setMpList] = useState<DefaultOptionType[]>([])
     const [corpList, setCorpList] = useState<DefaultOptionType[]>([])
     const [addToGroupData, setAddToGroupData] = useState<{ visible: boolean, data: any[] }>({ visible: false, data: undefined })
+    const [groupList, setGroupList] = useState<{ label: string, value: number }[]>([])
 
+    const getProjectGroupsAllList = useAjax(() => getProjectGroupsAllListApi())
     const getProjectList = useAjax((params) => getProjectListApi(params))
     const delProject = useAjax((params) => delProjectApi(params))
     const cancelProject = useAjax((params) => cancelProjectApi(params))
@@ -56,6 +59,12 @@ const TaskList: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookL
         }
     }, [])
 
+    useEffect(() => {
+        getProjectGroupsAllList.run().then(res => {
+            setGroupList([{ label: '空项目组', value: 0 }, ...res?.data?.map(item => ({ label: item.name, value: item.id })) || []])
+        })
+    }, [])
+
     useEffect(() => {
         getProjectList.run(queryFormNew)
     }, [queryFormNew])
@@ -111,6 +120,21 @@ const TaskList: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookL
                 <Button>任务名称</Button>
                 <Input placeholder='请输入任务名称' allowClear value={queryForm?.projectName} onChange={(e) => setQueryForm({ ...queryForm, projectName: e.target.value })} />
             </Space.Compact>
+            <Space.Compact>
+                <Button>项目组</Button>
+                <Select
+                    placeholder='请选择项目组'
+                    allowClear
+                    options={groupList}
+                    showSearch
+                    style={{ width: 150 }}
+                    filterOption={(input, option) =>
+                        (option?.label ?? '').toLowerCase().includes(input.toLowerCase())
+                    }
+                    value={queryForm?.projectGroupId}
+                    onChange={(value) => setQueryForm({ ...queryForm, projectGroupId: value })}
+                />
+            </Space.Compact>
             <Space.Compact>
                 <Button>创建时间</Button>
                 <DatePicker.RangePicker

+ 12 - 1
src/pages/weComTask/page/groupChatSend/official/taskList/tableConfig.tsx

@@ -22,7 +22,7 @@ const taskListColumns = (
             title: '操作',
             dataIndex: 'cz',
             key: 'cz',
-            width: 160,
+            width: 230,
             render(_, record) {
                 return <Space>
                    {record?.status === 1 ? <Popconfirm
@@ -56,6 +56,17 @@ const taskListColumns = (
             width: 120,
             ellipsis: true
         },
+        {
+            title: '项目组',
+            dataIndex: 'projectGroupInfo',
+            key: 'projectGroupInfo',
+            width: 120,
+            ellipsis: true,
+            align: 'center',
+            render(value) {
+                return value?.name || '--'
+            },
+        },
         {
             title: '基础信息',
             dataIndex: 'bizType',

+ 24 - 0
src/pages/weComTask/page/groupChatSend/robot/taskList/index.tsx

@@ -12,6 +12,7 @@ import { getBindMpListApi } from '@/pages/weComTask/API/corpUserAssign';
 import { getCorpAllListApi } from '@/API/global';
 import Select, { DefaultOptionType } from 'antd/es/select';
 import AddToGroup from '../../../businessPlan/taskList/components/addToGroup';
+import { getProjectGroupsAllListApi } from '@/pages/weComTask/API/groupManage';
 
 /**
  * 任务列表
@@ -30,7 +31,9 @@ const TaskList: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookL
     const [mpList, setMpList] = useState<DefaultOptionType[]>([])
     const [corpList, setCorpList] = useState<DefaultOptionType[]>([])
     const [addToGroupData, setAddToGroupData] = useState<{ visible: boolean, data: any[] }>({ visible: false, data: undefined })
+    const [groupList, setGroupList] = useState<{ label: string, value: number }[]>([])
 
+    const getProjectGroupsAllList = useAjax(() => getProjectGroupsAllListApi())
     const getProjectList = useAjax((params) => getProjectListApi(params))
     const delProject = useAjax((params) => delProjectApi(params))
     const cancelProject = useAjax((params) => cancelProjectApi(params))
@@ -56,6 +59,12 @@ const TaskList: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookL
         }
     }, [])
 
+    useEffect(() => {
+        getProjectGroupsAllList.run().then(res => {
+            setGroupList([{ label: '空项目组', value: 0 }, ...res?.data?.map(item => ({ label: item.name, value: item.id })) || []])
+        })
+    }, [])
+
     useEffect(() => {
         getProjectList.run(queryFormNew)
     }, [queryFormNew])
@@ -111,6 +120,21 @@ const TaskList: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookL
                 <Button>任务名称</Button>
                 <Input placeholder='请输入任务名称' allowClear value={queryForm?.projectName} onChange={(e) => setQueryForm({ ...queryForm, projectName: e.target.value })} />
             </Space.Compact>
+            <Space.Compact>
+                <Button>项目组</Button>
+                <Select
+                    placeholder='请选择项目组'
+                    allowClear
+                    options={groupList}
+                    showSearch
+                    style={{ width: 150 }}
+                    filterOption={(input, option) =>
+                        (option?.label ?? '').toLowerCase().includes(input.toLowerCase())
+                    }
+                    value={queryForm?.projectGroupId}
+                    onChange={(value) => setQueryForm({ ...queryForm, projectGroupId: value })}
+                />
+            </Space.Compact>
             <Space.Compact>
                 <Button>创建时间</Button>
                 <DatePicker.RangePicker

+ 12 - 1
src/pages/weComTask/page/groupChatSend/robot/taskList/tableConfig.tsx

@@ -22,7 +22,7 @@ const taskListColumns = (
             title: '操作',
             dataIndex: 'cz',
             key: 'cz',
-            width: 160,
+            width: 230,
             render(_, record) {
                 return <Space>
                     {record?.status === 1 ? <Popconfirm
@@ -56,6 +56,17 @@ const taskListColumns = (
             width: 120,
             ellipsis: true
         },
+        {
+            title: '项目组',
+            dataIndex: 'projectGroupInfo',
+            key: 'projectGroupInfo',
+            width: 120,
+            ellipsis: true,
+            align: 'center',
+            render(value) {
+                return value?.name || '--'
+            },
+        },
         {
             title: '基础信息',
             dataIndex: 'bizType',

+ 90 - 0
src/pages/weComTask/page/home/corpDetails.tsx

@@ -0,0 +1,90 @@
+import { Avatar, Modal, Space, Table, Typography } from "antd"
+import React, { useEffect, useState } from "react"
+import { EyeOutlined, UserOutlined } from "@ant-design/icons"
+import { useAjax } from "@/Hook/useAjax"
+import { getSelectCorpIdsByQcUuidListApi } from "../../API/home"
+const { Text } = Typography;
+
+const CorpDetails: React.FC<{ name: string, avatar: string, corpIdCount: number, qcUuid: string }> = ({ name, avatar, corpIdCount, qcUuid }) => {
+
+    /*******************************************/
+    const [visible, setVisible] = useState<boolean>(false)
+
+    const getSelectCorpIdsByQcUuidList = useAjax((params) => getSelectCorpIdsByQcUuidListApi(params))
+    /*******************************************/
+
+    useEffect(() => {
+        if (visible && qcUuid) {
+            getSelectCorpIdsByQcUuidList.run({ qcUuid })
+        }
+    }, [qcUuid, visible])
+
+    return <>
+        <Space>
+            <span>{corpIdCount}</span>
+            <a onClick={() => setVisible(true)}><EyeOutlined /></a>
+        </Space>
+
+        {visible && <Modal
+            title={<strong>所在企业详情</strong>}
+            open={visible}
+            onCancel={() => setVisible(false)}
+            width={900}
+            footer={null}
+        >
+            <Table
+                columns={[
+                    {
+                        title: '企业名称',
+                        dataIndex: 'corpName',
+                        key: 'corpName',
+                        width: 180,
+                        ellipsis: true,
+                    },
+                    {
+                        title: '企业ID',
+                        dataIndex: 'corpId',
+                        key: 'corpId',
+                        width: 250,
+                        ellipsis: true,
+                        render: (text: string) => {
+                            return <Text copyable>{text}</Text>
+                        }
+                    },
+                    {
+                        title: '客户名称',
+                        dataIndex: 'name',
+                        key: 'name',
+                        align: 'center',
+                        width: 130,
+                        render: () => {
+                            return <Space>
+                                <Avatar shape="square" size={20} icon={<UserOutlined />} src={avatar} />
+                                <Text>{name}</Text>
+                            </Space>
+                        }
+                    },
+                    {
+                        title: '客户ID',
+                        dataIndex: 'externalUserId',
+                        key: 'externalUserId',
+                        ellipsis: true,
+                        render: (text: string) => {
+                            return <Text copyable>{text}</Text>
+                        }
+                    }
+                ]}
+                scroll={{ y: 460 }}
+                bordered
+                pagination={{
+                    defaultPageSize: 20
+                }}
+                dataSource={getSelectCorpIdsByQcUuidList.data?.data}
+                loading={getSelectCorpIdsByQcUuidList.loading}
+                rowKey="corpId"
+            />
+        </Modal>}
+    </>
+}
+
+export default React.memo(CorpDetails)

+ 100 - 0
src/pages/weComTask/page/home/corpUserDetails.tsx

@@ -0,0 +1,100 @@
+import { Avatar, Modal, Space, Table, Typography } from "antd"
+import React, { useEffect, useState } from "react"
+import { EyeOutlined, UserOutlined } from "@ant-design/icons"
+import { useAjax } from "@/Hook/useAjax"
+import { getSelectCorpUserIdsByQcUuidListApi } from "../../API/home"
+const { Text } = Typography;
+
+const CorpUserDetails: React.FC<{ name: string, avatar: string, corpUserIdCount: number, qcUuid: string }> = ({ name, avatar, corpUserIdCount, qcUuid }) => {
+
+    /*******************************************/
+    const [visible, setVisible] = useState<boolean>(false)
+
+    const getSelectCorpUserIdsByQcUuidList = useAjax((params) => getSelectCorpUserIdsByQcUuidListApi(params))
+    /*******************************************/
+
+    useEffect(() => {
+        if (visible && qcUuid) {
+            getSelectCorpUserIdsByQcUuidList.run({ qcUuid })
+        }
+    }, [qcUuid, visible])
+
+    return <>
+        <Space>
+            <span>{corpUserIdCount}</span>
+            <a onClick={() => setVisible(true)}><EyeOutlined /></a>
+        </Space>
+
+        {visible && <Modal
+            title={<strong>所在客服下详情</strong>}
+            open={visible}
+            onCancel={() => setVisible(false)}
+            width={900}
+            footer={null}
+        >
+            <Table
+                columns={[
+                    {
+                        title: '企业名称',
+                        dataIndex: 'corpName',
+                        key: 'corpName',
+                        width: 180,
+                        ellipsis: true,
+                        render: (text: string) => text || '--'
+                    },
+                    {
+                        title: '企业ID',
+                        dataIndex: 'corpId',
+                        key: 'corpId',
+                        width: 300,
+                        ellipsis: true,
+                        render: (text: string) => {
+                            return text ? <Text copyable>{text}</Text> : '--'
+                        }
+                    },
+                    {
+                        title: '客户名称',
+                        dataIndex: 'name',
+                        key: 'name',
+                        width: 130,
+                        align: 'center',
+                        render: () => {
+                            return <Space>
+                                <Avatar shape="square" size={20} icon={<UserOutlined />} src={avatar} />
+                                <Text>{name}</Text>
+                            </Space>
+                        }
+                    },
+                    {
+                        title: '客服号名称',
+                        dataIndex: 'corpUserName',
+                        key: 'corpUserName',
+                        ellipsis: true,
+                        width: 150,
+                        render: (text: string) => text || '--'
+                    },
+                    {
+                        title: '客服号ID',
+                        dataIndex: 'corpUserId',
+                        key: 'corpUserId',
+                        ellipsis: true,
+                        width: 250,
+                        render: (text: string) => {
+                            return text ? <Text copyable>{text}</Text> : '--'
+                        }
+                    }
+                ]}
+                scroll={{ y: 460 }}
+                bordered
+                pagination={{
+                    defaultPageSize: 20
+                }}
+                dataSource={getSelectCorpUserIdsByQcUuidList.data?.data}
+                loading={getSelectCorpUserIdsByQcUuidList.loading}
+                rowKey="corpUserId"
+            />
+        </Modal>}
+    </>
+}
+
+export default React.memo(CorpUserDetails)

+ 138 - 156
src/pages/weComTask/page/home/index.tsx

@@ -7,6 +7,7 @@ import useEcharts from '@/Hook/useEcharts';
 const { Title } = Typography;
 import style from './index.less'
 import { CorpExternalUserColumns, ExternalUserColumns } from './tableConfig';
+import UuidTem from './uuidTem';
 
 const Home: React.FC = () => {
 
@@ -18,7 +19,7 @@ const Home: React.FC = () => {
     const [corpUserRepeat, setCorpUserRepeat] = useState<{ [x: string]: any }>({})
     const [barCorpData, setBarCorpData] = useState<Record<string, any>[]>([])
     const [barCorpUserData, setBarCorpUserData] = useState<Record<string, any>[]>([])
-    const [activeKey, setActiveKey] = useState<string>('1')
+    const [activeKey, setActiveKey] = useState<string>('3')
     const [userData, setUserData] = useState<Record<string, any>[]>([])
     const [overflowData, setOverflowData] = useState<{
         avgCorpRepeatUserRate: number,
@@ -45,59 +46,64 @@ const Home: React.FC = () => {
     }, [queryParmas])
 
     useEffect(() => {
-        getCorpExternalUserRepeatList.run(queryParmasZt)
-    }, [queryParmasZt])
+        if (activeKey === '1')
+            getCorpExternalUserRepeatList.run(queryParmasZt)
+    }, [queryParmasZt, activeKey])
 
     useEffect(() => {
-        getExternalUserRepeatCorp.run().then(res => {
-            if (res?.data) {
-                const cr = res.data
-                setCorpRepeat(cr)
-                setBarCorpData([
-                    { name: '仅添加1个主体', '人数': cr?.oneRepeatCount },
-                    { name: '添加2个主体', '人数': cr?.twoRepeatCount },
-                    { name: '添加3个主体', '人数': cr?.threeRepeatCount },
-                    { name: '添加4个主体', '人数': cr?.fourRepeatCount },
-                    { name: '添加5个主体', '人数': cr?.fiveRepeatCount },
-                    { name: '添加5个主体以上', '人数': cr?.gtFiveRepeatCount }
-                ])
-            } else {
-                setCorpRepeat({})
-                setBarCorpData([])
-            }
+        if (activeKey === '2') {
+            getExternalUserRepeatCorp.run().then(res => {
+                if (res?.data) {
+                    const cr = res.data
+                    setCorpRepeat(cr)
+                    setBarCorpData([
+                        { name: '仅添加1个主体', '人数': cr?.oneRepeatCount },
+                        { name: '添加2个主体', '人数': cr?.twoRepeatCount },
+                        { name: '添加3个主体', '人数': cr?.threeRepeatCount },
+                        { name: '添加4个主体', '人数': cr?.fourRepeatCount },
+                        { name: '添加5个主体', '人数': cr?.fiveRepeatCount },
+                        { name: '添加5个主体以上', '人数': cr?.gtFiveRepeatCount }
+                    ])
+                } else {
+                    setCorpRepeat({})
+                    setBarCorpData([])
+                }
 
-        })
-        getExternalUserRepeatCorpUser.run().then(res => {
-            if (res?.data) {
-                const cur = res.data
-                setCorpUserRepeat(cur)
-                setOverflowData({
-                    avgCorpRepeatUserRate: cur?.avgCorpRepeatUserRate || 0,
-                    repeatUserRate: cur?.repeatUserRate || 0,
-                    userCount: cur?.userCount || 0,
-                    qcUuidCount: cur?.qcUuidCount || 0,
-                    qcUuidCountRate: cur?.qcUuidCountRate || 0,
-                    qcUuidNullCount: cur?.qcUuidNullCount || 0,
-                    qcUuidNullCountRate: cur?.qcUuidNullCountRate || 0,
-                    deletedUserCount: cur?.deletedUserCount || 0,
-                    deletedUserCountRate: cur?.deletedUserCountRate || 0,
-                    qcUuidUserCount: cur?.qcUuidUserCount || 0,
-                })
-                setBarCorpUserData([
-                    { name: '仅添加1名客服', '人数': cur?.oneRepeatCount },
-                    { name: '添加2名客服', '人数': cur?.twoRepeatCount },
-                    { name: '添加3名客服', '人数': cur?.threeRepeatCount },
-                    { name: '添加4名客服', '人数': cur?.fourRepeatCount },
-                    { name: '添加5名客服', '人数': cur?.fiveRepeatCount },
-                    { name: '添加5名客服以上', '人数': cur?.gtFiveRepeatCount }
-                ])
-            } else {
-                setCorpUserRepeat({})
-                setOverflowData({ avgCorpRepeatUserRate: 0, repeatUserRate: 0, userCount: 0, qcUuidCount: 0, qcUuidCountRate: 0, qcUuidNullCount: 0, qcUuidNullCountRate: 0, deletedUserCount: 0, deletedUserCountRate: 0, qcUuidUserCount: 0 })
-                setBarCorpUserData([])
-            }
+            })
+        } else if (activeKey === '3') {
+            getExternalUserRepeatCorpUser.run().then(res => {
+                if (res?.data) {
+                    const cur = res.data
+                    setCorpUserRepeat(cur)
+                    setOverflowData({
+                        avgCorpRepeatUserRate: cur?.avgCorpRepeatUserRate || 0,
+                        repeatUserRate: cur?.repeatUserRate || 0,
+                        userCount: cur?.userCount || 0,
+                        qcUuidCount: cur?.qcUuidCount || 0,
+                        qcUuidCountRate: cur?.qcUuidCountRate || 0,
+                        qcUuidNullCount: cur?.qcUuidNullCount || 0,
+                        qcUuidNullCountRate: cur?.qcUuidNullCountRate || 0,
+                        deletedUserCount: cur?.deletedUserCount || 0,
+                        deletedUserCountRate: cur?.deletedUserCountRate || 0,
+                        qcUuidUserCount: cur?.qcUuidUserCount || 0,
+                    })
+                    setBarCorpUserData([
+                        { name: '仅添加1名客服', '人数': cur?.oneRepeatCount },
+                        { name: '添加2名客服', '人数': cur?.twoRepeatCount },
+                        { name: '添加3名客服', '人数': cur?.threeRepeatCount },
+                        { name: '添加4名客服', '人数': cur?.fourRepeatCount },
+                        { name: '添加5名客服', '人数': cur?.fiveRepeatCount },
+                        { name: '添加5名客服以上', '人数': cur?.gtFiveRepeatCount }
+                    ])
+                } else {
+                    setCorpUserRepeat({})
+                    setOverflowData({ avgCorpRepeatUserRate: 0, repeatUserRate: 0, userCount: 0, qcUuidCount: 0, qcUuidCountRate: 0, qcUuidNullCount: 0, qcUuidNullCountRate: 0, deletedUserCount: 0, deletedUserCountRate: 0, qcUuidUserCount: 0 })
+                    setBarCorpUserData([])
+                }
+
+            })
+        }
 
-        })
         getExternalUserRepeatByCorpAtlas.run().then(res => {
             if (res?.data) {
                 setUserData(res?.data?.map(item => {
@@ -107,7 +113,7 @@ const Home: React.FC = () => {
                 setUserData([])
             }
         })
-    }, [])
+    }, [activeKey])
 
     return <div>
         <Spin spinning={getExternalUserRepeatCorpUser.loading}>
@@ -125,12 +131,7 @@ const Home: React.FC = () => {
                 <Card variant="borderless" style={{ width: '20%' }}>
                     <Flex justify='space-between'>
                         <Statistic
-                            title={<Space>
-                                <strong style={{ fontSize: 14 }}>集团跨主体去重企微用户数</strong>
-                                <Tooltip title="集团跨主体去重企微用户数=未识别人数+添加1个客服人数+添加多个(>1)客服人数">
-                                    <QuestionCircleOutlined />
-                                </Tooltip>
-                            </Space>}
+                            title={<strong style={{ fontSize: 14 }}>集团跨主体去重企微用户数</strong>}
                             style={{ flex: 1 }}
                             value={overflowData.qcUuidUserCount}
                         />
@@ -146,7 +147,7 @@ const Home: React.FC = () => {
                             suffix={<div style={{ display: 'flex' }}>
                                 (<Statistic
                                     value={overflowData.qcUuidCountRate ? overflowData.qcUuidCountRate * 100 : 0}
-                                    precision={4}
+                                    precision={2}
                                     suffix="%"
                                     valueStyle={overflowData?.qcUuidCountRate < 0.8 ? { color: '#cf1322' } : { color: '#3f8600' }}
                                 />)
@@ -164,7 +165,7 @@ const Home: React.FC = () => {
                             suffix={<div style={{ display: 'flex' }}>
                                 (<Statistic
                                     value={overflowData.qcUuidNullCountRate ? overflowData.qcUuidNullCountRate * 100 : 0}
-                                    precision={4}
+                                    precision={2}
                                     suffix="%"
                                     valueStyle={overflowData?.qcUuidNullCountRate > 0.05 ? { color: '#cf1322' } : { color: '#3f8600' }}
                                 />)
@@ -182,7 +183,7 @@ const Home: React.FC = () => {
                             suffix={<div style={{ display: 'flex' }}>
                                 (<Statistic
                                     value={overflowData.deletedUserCountRate ? overflowData.deletedUserCountRate * 100 : 0}
-                                    precision={4}
+                                    precision={2}
                                     suffix="%"
                                     valueStyle={overflowData?.deletedUserCountRate > 0.4 ? { color: '#cf1322' } : { color: '#3f8600' }}
                                 />)
@@ -196,13 +197,13 @@ const Home: React.FC = () => {
                         <Statistic
                             title={<Space>
                                 <strong style={{ fontSize: 14 }}>集团重粉率</strong>
-                                <Tooltip title="所有主体的重复粉丝去重数量/集团粉丝数">
+                                <Tooltip title="所有主体的重复粉丝去重数量/集团跨主体去重企微用户数">
                                     <QuestionCircleOutlined />
                                 </Tooltip>
                             </Space>}
                             style={{ flex: 1 }}
                             value={overflowData.repeatUserRate ? overflowData.repeatUserRate * 100 : 0}
-                            precision={4}
+                            precision={2}
                             suffix="%"
                         />
                         <Avatar style={{ backgroundColor: '#F3E8FF', color: '#9333ea' }} size={40}><GlobalOutlined /></Avatar>
@@ -222,14 +223,14 @@ const Home: React.FC = () => {
             />
         </Flex>
         <Row gutter={16}>
-            <Col span={12}>
+            <Col span={10}>
                 <Spin spinning={getExternalUserRepeatByCorpAtlas.loading}>
                     <Card style={{ height: '100%' }}>
                         <Bar data={userData} title="粉丝前20单主体总用户数, 主体内重粉数" />
                     </Card>
                 </Spin>
             </Col>
-            <Col span={12}>
+            <Col span={14}>
                 <Card style={{ height: '100%' }}>
                     <Table
                         columns={ExternalUserColumns()}
@@ -274,37 +275,18 @@ const Home: React.FC = () => {
                 />}
                 items={[
                     {
-                        key: '1',
-                        label: '主体内用户分布',
-                        children: <Card>
-                            <Table
-                                columns={CorpExternalUserColumns()}
-                                scroll={{ y: 300, x: 1000 }}
-                                bordered
-                                dataSource={getCorpExternalUserRepeatList.data?.data?.records}
-                                loading={getCorpExternalUserRepeatList.loading}
-                                rowKey="corpId"
-                                pagination={{
-                                    total: getCorpExternalUserRepeatList.data?.data?.total,
-                                    current: getCorpExternalUserRepeatList?.data?.data?.current || 1,
-                                    pageSize: getCorpExternalUserRepeatList?.data?.data?.size || 20,
-                                }}
-                                onChange={(pagination: any, _: any, sortData: any) => {
-                                    let { current, pageSize } = pagination
-                                    let newQueryForm = JSON.parse(JSON.stringify(queryParmasZt))
-                                    if (sortData && sortData?.order) {
-                                        newQueryForm['sortType'] = sortData?.order === 'ascend' ? 'ASC' : 'DESC'
-                                        newQueryForm['orderByField'] = sortData?.field
-                                    } else {
-                                        delete newQueryForm['sortType']
-                                        delete newQueryForm['orderByField']
-                                    }
-                                    newQueryForm.pageNum = current || newQueryForm.pageNum
-                                    newQueryForm.pageSize = pageSize || newQueryForm.pageSize
-                                    setQueryParmasZt({ ...newQueryForm })
-                                }}
-                            />
-                        </Card>
+                        key: '3',
+                        label: '用户添加客服号分布',
+                        children: <Row gutter={16}>
+                            <Col span={12}>
+                                <Card style={{ height: '100%' }}>
+                                    <Bar data={barCorpUserData} title="用户添加客服号分布" horizontal />
+                                </Card>
+                            </Col>
+                            <Col span={12}>
+                                <DetailsTemplate data={corpUserRepeat} title='用户添加客服号详细数据' />
+                            </Col>
+                        </Row>
                     },
                     {
                         key: '2',
@@ -320,12 +302,7 @@ const Home: React.FC = () => {
                                     <Title level={3} style={{ marginTop: 0, textAlign: 'center', fontSize: 18, color: '#313131' }}>用户添加主体分布重粉详细数据</Title>
                                     <Flex vertical gap={7}>
                                         <div className={style.item}>
-                                            <Space>
-                                                <span>粉丝总数</span>
-                                                <Tooltip title="未识别+添加1个+添加多个(>1)">
-                                                    <QuestionCircleOutlined />
-                                                </Tooltip>
-                                            </Space>
+                                            <span>粉丝总数</span>
                                             <div className={style.num}>
                                                 <Statistic value={corpRepeat?.userCount || 0} valueStyle={{ fontSize: 14 }} />
                                             </div>
@@ -333,7 +310,7 @@ const Home: React.FC = () => {
                                         <div className={style.item}>
                                             <Space>
                                                 <span>集团跨主体去重企微用户数</span>
-                                                <Tooltip title="集团跨主体去重企微用户数=未识别人数+添加1个客服人数+添加多个(>1)客服人数">
+                                                <Tooltip title="集团跨主体去重企微用户数=未识别人数+添加1个主体人数+添加多个(>1)主体人数">
                                                     <QuestionCircleOutlined />
                                                 </Tooltip>
                                             </Space>
@@ -443,24 +420,46 @@ const Home: React.FC = () => {
                         </Row>
                     },
                     {
-                        key: '3',
-                        label: '用户添加客服号分布',
-                        children: <Row gutter={16}>
-                            <Col span={12}>
-                                <Card style={{ height: '100%' }}>
-                                    <Bar data={barCorpUserData} title="用户添加客服号分布" horizontal />
-                                </Card>
-                            </Col>
-                            <Col span={12}>
-                                <DetailsTemplate data={corpUserRepeat} title='用户添加客服号详细数据' />
-                            </Col>
-                        </Row>
-                    },
+                        key: '1',
+                        label: '主体内用户分布',
+                        children: <Card>
+                            <Table
+                                columns={CorpExternalUserColumns()}
+                                scroll={{ y: 300, x: 1000 }}
+                                bordered
+                                dataSource={getCorpExternalUserRepeatList.data?.data?.records}
+                                loading={getCorpExternalUserRepeatList.loading}
+                                rowKey="corpId"
+                                pagination={{
+                                    total: getCorpExternalUserRepeatList.data?.data?.total,
+                                    current: getCorpExternalUserRepeatList?.data?.data?.current || 1,
+                                    pageSize: getCorpExternalUserRepeatList?.data?.data?.size || 20,
+                                }}
+                                onChange={(pagination: any, _: any, sortData: any) => {
+                                    let { current, pageSize } = pagination
+                                    let newQueryForm = JSON.parse(JSON.stringify(queryParmasZt))
+                                    if (sortData && sortData?.order) {
+                                        newQueryForm['sortType'] = sortData?.order === 'ascend' ? 'ASC' : 'DESC'
+                                        newQueryForm['orderByField'] = sortData?.field
+                                    } else {
+                                        delete newQueryForm['sortType']
+                                        delete newQueryForm['orderByField']
+                                    }
+                                    newQueryForm.pageNum = current || newQueryForm.pageNum
+                                    newQueryForm.pageSize = pageSize || newQueryForm.pageSize
+                                    setQueryParmasZt({ ...newQueryForm })
+                                }}
+                            />
+                        </Card>
+                    }
                 ]}
                 onChange={(e) => { setActiveKey(e) }}
                 activeKey={activeKey}
             />
         </Spin>
+
+        {/* uuid */}
+        <UuidTem />
     </div>
 };
 
@@ -471,39 +470,11 @@ const DetailsTemplate: React.FC<{ data: { [x: string]: any }, title: string }> =
         <Title level={3} style={{ marginTop: 0, textAlign: 'center', fontSize: 18, color: '#313131' }}>{title || '详细数据'}</Title>
         <Flex vertical gap={7}>
             <div className={style.item}>
-                <Space>
-                    <span>粉丝总数</span>
-                    <Tooltip title="未识别+添加1名+添加多名(>1)">
-                        <QuestionCircleOutlined />
-                    </Tooltip>
-                </Space>
+                <span>粉丝总数</span>
                 <div className={style.num}>
                     <Statistic value={data?.userCount || 0} valueStyle={{ fontSize: 14 }} />
                 </div>
             </div>
-            <div className={style.item}>
-                <Space>
-                    <span>集团跨主体去重企微用户数</span>
-                    <Tooltip title="集团跨主体去重企微用户数=未识别人数+添加1个客服人数+添加多个(>1)客服人数">
-                        <QuestionCircleOutlined />
-                    </Tooltip>
-                </Space>
-                <div className={style.num}>
-                    <Statistic value={data?.qcUuidUserCount || 0} valueStyle={{ fontSize: 14 }} />
-                </div>
-            </div>
-            <div className={style.item}>
-                <span>未识别用户数</span>
-                <div className={style.num}>
-                    <Statistic value={data?.qcUuidNullCount || 0} valueStyle={{ fontSize: 14 }} />
-                    (<Statistic
-                        value={data?.qcUuidNullCountRate ? data?.qcUuidNullCountRate * 100 : 0}
-                        valueStyle={data?.qcUuidNullCountRate > 0.1 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
-                        suffix="%"
-                        precision={4}
-                    />)
-                </div>
-            </div>
             <div className={style.item}>
                 <span>已激活集团客服号数</span>
                 <div className={style.num}>
@@ -516,18 +487,6 @@ const DetailsTemplate: React.FC<{ data: { [x: string]: any }, title: string }> =
                     />)
                 </div>
             </div>
-            <div className={style.item}>
-                <span>已禁用集团客服号数</span>
-                <div className={style.num}>
-                    <Statistic value={data?.disabledCorpUserCount || 0} valueStyle={{ fontSize: 14 }} />
-                    (<Statistic
-                        value={data?.disabledCorpUserCountRate ? data?.disabledCorpUserCountRate * 100 : 0}
-                        valueStyle={data?.disabledCorpUserCountRate > 0.1 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
-                        suffix="%"
-                        precision={4}
-                    />)
-                </div>
-            </div>
             <div className={style.item}>
                 <span>退出集团客服号数</span>
                 <div className={style.num}>
@@ -552,6 +511,29 @@ const DetailsTemplate: React.FC<{ data: { [x: string]: any }, title: string }> =
                     />)
                 </div>
             </div>
+            <div className={style.item}>
+                <Space>
+                    <span>集团跨主体去重企微用户数</span>
+                    <Tooltip title="集团跨主体去重企微用户数=未识别人数+添加1个客服人数+添加多个(>1)客服人数">
+                        <QuestionCircleOutlined />
+                    </Tooltip>
+                </Space>
+                <div className={style.num}>
+                    <Statistic value={data?.qcUuidUserCount || 0} valueStyle={{ fontSize: 14 }} />
+                </div>
+            </div>
+            <div className={style.item}>
+                <span>未识别用户数</span>
+                <div className={style.num}>
+                    <Statistic value={data?.qcUuidNullCount || 0} valueStyle={{ fontSize: 14 }} />
+                    (<Statistic
+                        value={data?.qcUuidNullCountRate ? data?.qcUuidNullCountRate * 100 : 0}
+                        valueStyle={data?.qcUuidNullCountRate > 0.1 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
+                        suffix="%"
+                        precision={4}
+                    />)
+                </div>
+            </div>
             <div className={style.item}>
                 <span>{`用户添加>1客服号人数`}</span>
                 <div className={style.num}>

+ 124 - 0
src/pages/weComTask/page/home/uuidTem.tsx

@@ -0,0 +1,124 @@
+import { useAjax } from "@/Hook/useAjax"
+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 SearchBox from "../../components/searchBox";
+import CorpDetails from "./corpDetails";
+import CorpUserDetails from "./corpUserDetails";
+const { Title, Text } = Typography;
+
+const UuidTem: React.FC = () => {
+
+    /*****************************************/
+    const [queryParmas, setQueryParmas] = React.useState<GetExternalUserRepeatByCorpListApiProps>({ pageNum: 1, pageSize: 20 })
+    const [queryParmasNew, setQueryParmasNew] = React.useState<GetExternalUserRepeatByCorpListApiProps>({ pageNum: 1, pageSize: 20 })
+
+    const getSelectQcUuidStatisticPageList = useAjax((params) => getSelectQcUuidStatisticPageListApi(params))
+    /*****************************************/
+
+    useEffect(() => {
+        getSelectQcUuidStatisticPageList.run(queryParmas)
+    }, [queryParmas])
+
+    return <>
+        <Title level={3}><AreaChartOutlined style={{ color: '#ff85c0' }} /> Uuid维度统计列表</Title>
+        <SearchBox
+            bodyPadding={`16px 0 12px`}
+            buttons={<>
+                <Button onClick={() => {
+                    setQueryParmas({ pageNum: 1, pageSize: queryParmas.pageSize })
+                    setQueryParmasNew({ pageNum: 1, pageSize: queryParmas.pageSize })
+                }}>重置</Button>
+                <Button type="primary" onClick={() => {
+                    setQueryParmas({ ...queryParmasNew, pageNum: 1, pageSize: queryParmas.pageSize })
+                }} loading={getSelectQcUuidStatisticPageList.loading} icon={<SearchOutlined />}>搜索</Button>
+            </>}
+        >
+            <>
+                <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 })} />
+                <InputNumber placeholder="最小客服号id个数" style={{ width: 125 }} value={queryParmasNew?.minCorpUserIdCount} onChange={(e) => setQueryParmasNew({ ...queryParmasNew, minCorpUserIdCount: e })} />
+                <InputNumber placeholder="最大客服号id个数" style={{ width: 125 }} value={queryParmasNew?.maxCorpUserIdCount} onChange={(e) => setQueryParmasNew({ ...queryParmasNew, maxCorpUserIdCount: e })} />
+            </>
+        </SearchBox>
+        <Card>
+            <Table
+                columns={[
+                    {
+                        title: '客户名称',
+                        dataIndex: 'name',
+                        key: 'name',
+                        width: 180,
+                        render: (text: any, record: any) => {
+                            return <Space>
+                                <Avatar shape="square" size={20} icon={<UserOutlined />} src={record?.avatar} />
+                                <Text>{text}</Text>
+                            </Space>
+                        }
+                    },
+                    {
+                        title: '企业ID个数',
+                        dataIndex: 'corpIdCount',
+                        key: 'corpIdCount',
+                        width: 120,
+                        align: 'center',
+                        sorter: true,
+                        render: (text: number, record: any) => {
+                            return <CorpDetails name={record?.name} avatar={record?.avatar} corpIdCount={text} qcUuid={record?.qcUuid} />
+                        }
+                    },
+                    {
+                        title: '客服号ID个数',
+                        dataIndex: 'corpUserIdCount',
+                        key: 'corpUserIdCount',
+                        width: 120,
+                        align: 'center',
+                        sorter: true,
+                        render: (text: number, record: any) => {
+                            return <CorpUserDetails name={record?.name} avatar={record?.avatar} corpUserIdCount={text} qcUuid={record?.qcUuid} />
+                        }
+                    },
+                    {
+                        title: 'qcUUID',
+                        dataIndex: 'qcUuid',
+                        key: 'qcUuid',
+                        ellipsis: true,
+                        render: (text: string) => {
+                            return <Text copyable>{text}</Text>
+                        }
+                    }
+                ]}
+                scroll={{ y: 300, x: 1000 }}
+                bordered
+                dataSource={getSelectQcUuidStatisticPageList.data?.data?.records}
+                loading={getSelectQcUuidStatisticPageList.loading}
+                rowKey="qcUuid"
+                pagination={{
+                    total: getSelectQcUuidStatisticPageList.data?.data?.total,
+                    current: getSelectQcUuidStatisticPageList?.data?.data?.current || 1,
+                    pageSize: getSelectQcUuidStatisticPageList?.data?.data?.size || 20,
+                }}
+                onChange={(pagination: any, _: any, sortData: any) => {
+                    let { current, pageSize } = pagination
+                    let newQueryForm = JSON.parse(JSON.stringify(queryParmas))
+                    if (sortData && sortData?.order) {
+                        newQueryForm['sortType'] = sortData?.order === 'ascend' ? 'ASC' : 'DESC'
+                        newQueryForm['orderByField'] = sortData?.field
+                    } else {
+                        delete newQueryForm['sortType']
+                        delete newQueryForm['orderByField']
+                    }
+                    newQueryForm.pageNum = current || newQueryForm.pageNum
+                    newQueryForm.pageSize = pageSize || newQueryForm.pageSize
+                    setQueryParmas({ ...newQueryForm })
+                }}
+            />
+        </Card>
+    </>
+}
+
+export default React.memo(UuidTem)

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

@@ -1,6 +1,6 @@
 import { useAjax } from '@/Hook/useAjax';
 import { cancelProjectApi, delProjectApi, getProjectListApi } from '@/pages/weComTask/API/businessPlan/create';
-import { App, Button, Card, DatePicker, Input, Popconfirm, Space, Table } from 'antd';
+import { App, Button, Card, DatePicker, Input, Popconfirm, Select, Space, Table } from 'antd';
 import React, { useEffect, useState } from 'react';
 import dayjs from 'dayjs';
 import { SearchOutlined, DeleteOutlined, PauseCircleOutlined, PlusCircleOutlined } from '@ant-design/icons';
@@ -9,6 +9,7 @@ import { toJS } from 'mobx';
 import AddToGroup from '../../businessPlan/taskList/components/addToGroup';
 import taskListColumns from './tableConfig';
 import Log from './log';
+import { getProjectGroupsAllListApi } from '@/pages/weComTask/API/groupManage';
 
 /**
  * 任务列表
@@ -25,7 +26,9 @@ const TaskList: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookL
     const [logOpenData, setLogOpenData] = useState<{ visible: boolean, data: any }>({ visible: false, data: {} })
     const [selectedRows, setselectedRows] = useState<any[]>([])
     const [addToGroupData, setAddToGroupData] = useState<{ visible: boolean, data: any[] }>({ visible: false, data: undefined })
+    const [groupList, setGroupList] = useState<{ label: string, value: number }[]>([])
 
+    const getProjectGroupsAllList = useAjax(() => getProjectGroupsAllListApi())
     const getProjectList = useAjax((params) => getProjectListApi(params))
     const delProject = useAjax((params) => delProjectApi(params))
     const cancelProject = useAjax((params) => cancelProjectApi(params))
@@ -38,6 +41,12 @@ const TaskList: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookL
         }
     }, [])
 
+    useEffect(() => {
+        getProjectGroupsAllList.run().then(res => {
+            setGroupList([{ label: '空项目组', value: 0 }, ...res?.data?.map(item => ({ label: item.name, value: item.id })) || []])
+        })
+    }, [])
+
     useEffect(() => {
         getProjectList.run(queryFormNew)
     }, [queryFormNew])
@@ -93,6 +102,21 @@ const TaskList: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookL
                 <Button>任务名称</Button>
                 <Input placeholder='请输入任务名称' allowClear value={queryForm?.projectName} onChange={(e) => setQueryForm({ ...queryForm, projectName: e.target.value })} />
             </Space.Compact>
+            <Space.Compact>
+                <Button>项目组</Button>
+                <Select
+                    placeholder='请选择项目组'
+                    allowClear
+                    style={{ width: 150 }}
+                    options={groupList}
+                    showSearch
+                    filterOption={(input, option) =>
+                        (option?.label ?? '').toLowerCase().includes(input.toLowerCase())
+                    }
+                    value={queryForm?.projectGroupId}
+                    onChange={(value) => setQueryForm({ ...queryForm, projectGroupId: value })}
+                />
+            </Space.Compact>
             <Space.Compact>
                 <Button>创建时间</Button>
                 <DatePicker.RangePicker

+ 12 - 1
src/pages/weComTask/page/moments/taskList/tableConfig.tsx

@@ -18,7 +18,7 @@ const taskListColumns = (
             title: '操作',
             dataIndex: 'cz',
             key: 'cz',
-            width: 160,
+            width: 230,
             render(_, record) {
                 return <Space>
                     {record?.status === 1 ? <Popconfirm
@@ -52,6 +52,17 @@ const taskListColumns = (
             width: 120,
             ellipsis: true
         },
+        {
+            title: '项目组',
+            dataIndex: 'projectGroupInfo',
+            key: 'projectGroupInfo',
+            width: 120,
+            ellipsis: true,
+            align: 'center',
+            render(value) {
+                return value?.name || '--'
+            },
+        },
         {
             title: '基础信息',
             dataIndex: 'bizType',