wjx 6 天之前
父节点
当前提交
6c1680a478

+ 1 - 1
src/pages/weComTask/API/groupChat/index.ts

@@ -119,7 +119,7 @@ export interface GetLogChatUserInfoListProps {
     id: number,
     pageNum: number,
     pageSize: number,
-    status?: 1 | 3 | 4 | null
+    status?: 1 | 3 | 4 | 5 | null
 }
 
 /**

+ 32 - 6
src/pages/weComTask/API/heavyPowderMAS/index.ts

@@ -1,14 +1,20 @@
 import request from "@/utils/request";
 const { api } = process.env.CONFIG;
 
+
+export interface GetProps {
+    aCorpIds: string[];
+    bCorpIds: string[];
+}
 /**
  * 主体详细指标
  * @returns 
  */
-export function getCorpDuplicateFanOverviewListApi() {
+export function getCorpDuplicateFanOverviewListApi(data: GetProps) {
     return request({
         url: api + `/corpOperation/corpDuplicateFan/overview`,
-        method: 'POST'
+        method: 'POST',
+        data
     })
 }
 
@@ -17,10 +23,11 @@ export function getCorpDuplicateFanOverviewListApi() {
  * 重粉分析
  * @returns 
  */
-export function getCorpDuplicateFanRepeatAnalyzeListApi() {
+export function getCorpDuplicateFanRepeatAnalyzeListApi(data: GetProps) {
     return request({
         url: api + `/corpOperation/corpDuplicateFan/repeatAnalyze`,
-        method: 'POST'
+        method: 'POST',
+        data
     })
 }
 
@@ -30,9 +37,28 @@ export function getCorpDuplicateFanRepeatAnalyzeListApi() {
  * 添加客服数量分布
  * @returns 
  */
-export function getCorpDuplicateFanRepeatOfCorpUserListApi() {
+export function getCorpDuplicateFanRepeatOfCorpUserListApi(data: GetProps) {
     return request({
         url: api + `/corpOperation/corpDuplicateFan/repeatOfCorpUser`,
-        method: 'POST'
+        method: 'POST',
+        data
+    })
+}
+
+export interface GetFansDetailProps extends GetProps {
+    pageNum: number;
+    pageSize: number;
+}
+
+/**
+ * 重复粉丝用户详情
+ * @param data 
+ * @returns 
+ */
+export function getCorpDuplicateFanFansDetailListApi(data: GetFansDetailProps) {
+    return request({
+        url: api + `/corpOperation/corpDuplicateFan/fansDetail`,
+        method: 'POST',
+        data
     })
 }

+ 3 - 4
src/pages/weComTask/page/corpData/heavyPowder/corpDetails.tsx

@@ -29,7 +29,7 @@ const CorpDetails: React.FC<{ name: string, avatar: string, corpIdCount: number,
             title={<strong>所在企业详情</strong>}
             open={visible}
             onCancel={() => setVisible(false)}
-            width={900}
+            width={1000}
             footer={null}
         >
             <Table
@@ -45,7 +45,7 @@ const CorpDetails: React.FC<{ name: string, avatar: string, corpIdCount: number,
                         title: '企业ID',
                         dataIndex: 'corpId',
                         key: 'corpId',
-                        width: 300,
+                        width: 280,
                         ellipsis: true,
                         render: (text: string) => {
                             return <Text copyable>{text}</Text>
@@ -56,7 +56,7 @@ const CorpDetails: React.FC<{ name: string, avatar: string, corpIdCount: number,
                         dataIndex: 'name',
                         key: 'name',
                         align: 'center',
-                        width: 130,
+                        width: 145,
                         render: () => {
                             return <Space>
                                 <Avatar shape="square" size={20} icon={<UserOutlined />} src={avatar} />
@@ -68,7 +68,6 @@ const CorpDetails: React.FC<{ name: string, avatar: string, corpIdCount: number,
                         title: '客户ID',
                         dataIndex: 'externalUserId',
                         key: 'externalUserId',
-                        ellipsis: true,
                         render: (text: string) => {
                             return <Text copyable>{text}</Text>
                         }

+ 3 - 3
src/pages/weComTask/page/corpData/heavyPowder/corpUserDetails.tsx

@@ -29,7 +29,7 @@ const CorpUserDetails: React.FC<{ name: string, avatar: string, corpUserIdCount:
             title={<strong>所在客服下详情</strong>}
             open={visible}
             onCancel={() => setVisible(false)}
-            width={900}
+            width={1000}
             footer={null}
         >
             <Table
@@ -56,7 +56,7 @@ const CorpUserDetails: React.FC<{ name: string, avatar: string, corpUserIdCount:
                         title: '客户名称',
                         dataIndex: 'name',
                         key: 'name',
-                        width: 130,
+                        width: 150,
                         align: 'center',
                         render: () => {
                             return <Space>
@@ -78,7 +78,7 @@ const CorpUserDetails: React.FC<{ name: string, avatar: string, corpUserIdCount:
                         dataIndex: 'corpUserId',
                         key: 'corpUserId',
                         ellipsis: true,
-                        width: 250,
+                        width: 300,
                         render: (text: string) => {
                             return text ? <Text copyable>{text}</Text> : '--'
                         }

+ 64 - 0
src/pages/weComTask/page/corpData/heavyPowderMAS/SelectCorp.tsx

@@ -0,0 +1,64 @@
+import { Checkbox, Divider, Select, Space } from "antd";
+import { DefaultOptionType } from "antd/es/select";
+import React, { useEffect, useState } from "react";
+
+interface Props {
+    corpList: DefaultOptionType[],
+    value?: string[],
+    onChange?: (e: string[]) => void;
+}
+
+const SelectCorp: React.FC<Props> = ({ corpList, value, onChange }) => {
+
+    const [oldFiltered, setOldFiltered] = useState<DefaultOptionType[]>([]);
+    const [filtered, setFiltered] = useState<DefaultOptionType[]>([]);
+
+    useEffect(() => {
+        setOldFiltered(corpList);
+        setFiltered(corpList);
+    }, [corpList]);
+
+    const handleSearch = (val) => {
+        const f = oldFiltered.filter((item) => {
+            const inputStr = val.replace(/[,,\s]/g, ',');
+            if (inputStr && inputStr.includes(',')) {
+                const inputList = inputStr.split(',').filter((it) => it).map(i => i.toLowerCase());
+                return inputList.includes(((item?.label ?? '') as string).toLowerCase())
+            }
+            return ((item?.label ?? '') as string).toLowerCase().includes(val.toLowerCase())
+        });
+        setFiltered(f);
+    };
+
+
+    return <Select
+        value={value}
+        onChange={(e) => onChange?.(e as string[])}
+        showSearch
+        style={{ minWidth: 200 }}
+        maxTagCount={1}
+        mode='multiple'
+        placeholder="选择企业"
+        filterOption={false}
+        onSearch={handleSearch}
+        allowClear
+        options={filtered}
+        popupRender={(menu) => (
+            <>
+                {menu}
+                <Divider style={{ margin: '8px 0' }} />
+                <Space style={{ padding: '0 8px 4px' }}>
+                    <Checkbox onChange={(e) => {
+                        if (e.target.checked) {
+                            onChange?.(filtered.map(item => item.value) as string[])
+                        } else {
+                            onChange?.([])
+                        }
+                    }}>全选</Checkbox>
+                </Space>
+            </>
+        )}
+    />
+}
+
+export default React.memo(SelectCorp);

+ 97 - 11
src/pages/weComTask/page/corpData/heavyPowderMAS/index.tsx

@@ -1,6 +1,15 @@
+import { getCorpAllListApi } from "@/API/global";
 import { useAjax } from "@/Hook/useAjax";
-import { getCorpDuplicateFanOverviewListApi, getCorpDuplicateFanRepeatAnalyzeListApi, getCorpDuplicateFanRepeatOfCorpUserListApi } from "@/pages/weComTask/API/heavyPowderMAS";
-import { useEffect } from "react";
+import { getCorpDuplicateFanOverviewListApi, GetProps } from "@/pages/weComTask/API/heavyPowderMAS";
+import { Card, Col, Flex, Row, Space, Table, Typography } from "antd";
+import { useEffect, useState } from "react";
+import SelectCorp from "./SelectCorp";
+import { Columns1 } from "./tableConfig";
+import RepeatAnalyze from "./repeatAnalyze";
+import RepeatOfCorpUser from "./repeatOfCorpUser";
+import UserDetails from "./userDetails";
+
+const { Title } = Typography;
 
 /**
  * 多主体重粉数据
@@ -9,20 +18,97 @@ import { useEffect } from "react";
 const HeavyPowderMAS: React.FC = () => {
 
     /*********************************************/
-    const getCorpDuplicateFanOverviewList = useAjax(() => getCorpDuplicateFanOverviewListApi())
-    const getCorpDuplicateFanRepeatAnalyzeList = useAjax(() => getCorpDuplicateFanRepeatAnalyzeListApi())
-    const getCorpDuplicateFanRepeatOfCorpUserList = useAjax(() => getCorpDuplicateFanRepeatOfCorpUserListApi())
+    const [queryParmas, setQueryParmas] = useState<GetProps>()
+    const [filtered, setFiltered] = useState<{ label: string, value: string }[]>([]);
+
+    const getCorpDuplicateFanOverviewList = useAjax((params) => getCorpDuplicateFanOverviewListApi(params))
+    const getCorpAllList = useAjax((params) => getCorpAllListApi(params))
     /*********************************************/
 
     useEffect(() => {
-        getCorpDuplicateFanOverviewList.run()
-        getCorpDuplicateFanRepeatAnalyzeList.run()
-        getCorpDuplicateFanRepeatOfCorpUserList.run() 
+        getCorpAllList.run({}).then(res => {
+            const corpList = res?.data?.map((item, index) => {
+                if (index === 0) {
+                    setQueryParmas({ aCorpIds: [item.corpId], bCorpIds: [item.corpId] })
+                }
+                return { label: item.corpName, value: item.corpId }
+            })
+            setFiltered(corpList)
+        })
     }, [])
 
-    return <div>
-        1111111111
-    </div>
+    useEffect(() => {
+        if (queryParmas?.aCorpIds.length > 0 && queryParmas?.bCorpIds.length > 0) {
+            getCorpDuplicateFanOverviewList.run(queryParmas)
+        }
+    }, [queryParmas])
+
+    return <Flex vertical gap={10}>
+        <Card
+            variant="borderless"
+        >
+            <Flex gap={30}>
+                <Space>
+                    <Title level={5} style={{ margin: 0 }}>查询组主体:</Title>
+                    <SelectCorp
+                        corpList={filtered}
+                        value={queryParmas?.aCorpIds}
+                        onChange={(v) => {
+                            setQueryParmas({ ...queryParmas, aCorpIds: v })
+                        }}
+                    />
+                </Space>
+                <Space>
+                    <Title level={5} style={{ margin: 0 }}>对照组主体:</Title>
+                    <SelectCorp
+                        corpList={filtered}
+                        value={queryParmas?.bCorpIds}
+                        onChange={(v) => {
+                            setQueryParmas({ ...queryParmas, bCorpIds: v })
+                        }}
+                    />
+                </Space>
+            </Flex>
+        </Card>
+        <Card
+            variant="borderless"
+            title={<strong>主体详细</strong>}
+        >
+            <Row gutter={16}>
+                <Col span={12}>
+                    <Title level={5} style={{ margin: 0, marginBottom: 10 }}>查询组数据</Title>
+                    <Table
+                        columns={Columns1()}
+                        dataSource={getCorpDuplicateFanOverviewList?.data?.data?.aData}
+                        loading={getCorpDuplicateFanOverviewList.loading}
+                        bordered
+                        scroll={{ y: 300 }}
+                        rowKey={'corp_id'}
+                    />
+                </Col>
+                <Col span={12}>
+                    <Title level={5} style={{ margin: 0, marginBottom: 10 }}>对照组数据</Title>
+                    <Table
+                        columns={Columns1()}
+                        dataSource={getCorpDuplicateFanOverviewList?.data?.data?.bData}
+                        loading={getCorpDuplicateFanOverviewList.loading}
+                        bordered
+                        scroll={{ y: 300 }}
+                        rowKey={'corp_id'}
+                    />
+                </Col>
+            </Row>
+        </Card>
+        <Row gutter={10}>
+            <Col span={12}>
+                <RepeatAnalyze queryParmas={queryParmas} />
+            </Col>
+            <Col span={12}>
+                <RepeatOfCorpUser queryParmas={queryParmas} />
+            </Col>
+        </Row>
+        <UserDetails queryParmas={queryParmas} />
+    </Flex>
 }
 
 export default HeavyPowderMAS;

+ 109 - 0
src/pages/weComTask/page/corpData/heavyPowderMAS/repeatAnalyze.tsx

@@ -0,0 +1,109 @@
+import { useAjax } from "@/Hook/useAjax";
+import { getCorpDuplicateFanRepeatAnalyzeListApi, GetProps } from "@/pages/weComTask/API/heavyPowderMAS";
+import { Card, Flex, Spin, Statistic } from "antd";
+import React, { useState } from "react";
+import { useEffect } from "react";
+import style from '../heavyPowder/index.less';
+
+
+const RepeatAnalyze: React.FC<{ queryParmas: GetProps }> = ({ queryParmas }) => {
+
+    /**************************************/
+    const [data, setData] = useState<Record<string, number>>({});
+    const getCorpDuplicateFanRepeatAnalyzeList = useAjax((params) => getCorpDuplicateFanRepeatAnalyzeListApi(params))
+    /**************************************/
+
+    useEffect(() => {
+        if (queryParmas?.aCorpIds.length > 0 && queryParmas?.bCorpIds.length > 0) {
+            getCorpDuplicateFanRepeatAnalyzeList.run(queryParmas).then(res => {
+                if (res?.data) {
+                    setData(res.data)
+                } else {
+                    setData({})
+                }
+            })
+        }
+    }, [queryParmas])
+
+    return <Card
+        variant="borderless"
+        title={<strong>重粉分析</strong>}
+    >
+        <Spin spinning={getCorpDuplicateFanRepeatAnalyzeList.loading}>
+            <Flex vertical gap={7}>
+                <div className={style.item}>
+                    <span>查询组总用户</span>
+                    <div className={style.num}>
+                        <Statistic value={data?.query_total_num || 0} valueStyle={{ fontSize: 14 }} />
+                    </div>
+                </div>
+                <div className={style.item}>
+                    <span>对照组总用户</span>
+                    <div className={style.num}>
+                        <Statistic value={data?.control_total_num || 0} valueStyle={{ fontSize: 14 }} />
+                    </div>
+                </div>
+                <div className={style.item}>
+                    <span>查询组净值用户数</span>
+                    <div className={style.num}>
+                        <Statistic value={data?.query_only_num || 0} valueStyle={{ fontSize: 14 }} />
+                    </div>
+                </div>
+                <div className={style.item}>
+                    <span>对照组净值用户数</span>
+                    <div className={style.num}>
+                        <Statistic value={data?.control_only_num || 0} valueStyle={{ fontSize: 14 }} />
+                    </div>
+                </div>
+                <div className={style.item}>
+                    <span>查询组未识别用户</span>
+                    <div className={style.num}>
+                        <Statistic value={data?.query_qc_null_num || 0} valueStyle={{ fontSize: 14 }} />
+                    </div>
+                </div>
+                <div className={style.item}>
+                    <span>对照组未识别用户</span>
+                    <div className={style.num}>
+                        <Statistic value={data?.control_qc_null_num || 0} valueStyle={{ fontSize: 14 }} />
+                    </div>
+                </div>
+                <div className={style.item}>
+                    <span>重粉数</span>
+                    <div className={style.num}>
+                        <Statistic value={data?.overlap_num || 0} valueStyle={{ fontSize: 14 }} />
+                    </div>
+                </div>
+                <div className={style.item}>
+                    <span>重粉比</span>
+                    <div className={style.num}>
+                        <Statistic
+                            value={data?.query_overlap_rate ? data?.query_overlap_rate * 100 : 0}
+                            valueStyle={data?.query_overlap_rate > 0.2 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
+                            suffix="%"
+                            precision={4}
+                        />
+                    </div>
+                </div>
+                <div className={style.item}>
+                    <span>交叉重粉数</span>
+                    <div className={style.num}>
+                        <Statistic value={data?.cross_overlap_num || 0} valueStyle={{ fontSize: 14 }} />
+                    </div>
+                </div>
+                <div className={style.item}>
+                    <span>查询组非重粉占比</span>
+                    <div className={style.num}>
+                        <Statistic
+                            value={data?.query_non_overlap_rate ? data?.query_non_overlap_rate * 100 : 0}
+                            valueStyle={data?.query_non_overlap_rate > 0.1 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
+                            suffix="%"
+                            precision={4}
+                        />
+                    </div>
+                </div>
+            </Flex>
+        </Spin>
+    </Card>
+};
+
+export default React.memo(RepeatAnalyze);

+ 146 - 0
src/pages/weComTask/page/corpData/heavyPowderMAS/repeatOfCorpUser.tsx

@@ -0,0 +1,146 @@
+import { useAjax } from "@/Hook/useAjax";
+import { getCorpDuplicateFanRepeatOfCorpUserListApi, GetProps } from "@/pages/weComTask/API/heavyPowderMAS";
+import { Card, Flex, Spin, Statistic } from "antd";
+import React, { useState } from "react";
+import { useEffect } from "react";
+import style from '../heavyPowder/index.less';
+
+/**
+ * 添加客服数量分布
+ * @param param0 
+ * @returns 
+ */
+const RepeatOfCorpUser: React.FC<{ queryParmas: GetProps }> = ({ queryParmas }) => {
+
+    /**************************************/
+    const [data, setData] = useState<Record<string, number>>({});
+
+    const getCorpDuplicateFanRepeatOfCorpUserList = useAjax((params) => getCorpDuplicateFanRepeatOfCorpUserListApi(params))
+    /**************************************/
+
+    useEffect(() => {
+        if (queryParmas?.aCorpIds.length > 0 && queryParmas?.bCorpIds.length > 0) {
+            getCorpDuplicateFanRepeatOfCorpUserList.run(queryParmas).then(res => {
+                if (res?.data) {
+                    setData(res.data)
+                } else {
+                    setData({})
+                }
+            })
+        }
+    }, [queryParmas])
+
+    return <Card
+        variant="borderless"
+        title={<strong>添加客服数量分布</strong>}
+    >
+        <Spin spinning={getCorpDuplicateFanRepeatOfCorpUserList.loading}>
+            <Flex vertical gap={7}>
+                <div className={style.item}>
+                    <span>去重后企微用户总数</span>
+                    <div className={style.num}>
+                        <Statistic value={data?.total_user_num || 0} valueStyle={{ fontSize: 14 }} />
+                    </div>
+                </div>
+                <div className={style.item}>
+                    <span>查询组未识别用户</span>
+                    <div className={style.num}>
+                        <Statistic value={data?.query_qc_null_num || 0} valueStyle={{ fontSize: 14 }} />
+                    </div>
+                </div>
+                <div className={style.item}>
+                    <span>对照组未识别用户</span>
+                    <div className={style.num}>
+                        <Statistic value={data?.control_qc_null_num || 0} valueStyle={{ fontSize: 14 }} />
+                    </div>
+                </div>
+                <div className={style.item}>
+                    <span>{`用户添加>1客服号人数`}</span>
+                    <div className={style.num}>
+                        <Statistic value={data?.add_gt1_cnt || 0} valueStyle={{ fontSize: 14 }} />
+                        (<Statistic
+                            value={data?.add_gt1_rate ? data?.add_gt1_rate * 100 : 0}
+                            valueStyle={data?.add_gt1_rate > 0.3 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
+                            suffix="%"
+                            precision={4}
+                        />)
+                    </div>
+                </div>
+                <div className={style.item}>
+                    <span>仅添加1名客服人数</span>
+                    <div className={style.num}>
+                        <Statistic value={data?.add_1_cnt || 0} valueStyle={{ fontSize: 14 }} />
+                        (<Statistic
+                            value={data?.add_1_rate ? data?.add_1_rate * 100 : 0}
+                            valueStyle={data?.add_1_rate > 0.5 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
+                            suffix="%"
+                            precision={4}
+                        />)
+                    </div>
+                </div>
+                <div className={style.item}>
+                    <span>添加2名客服人数</span>
+                    <div className={style.num}>
+                        <Statistic value={data?.add_2_cnt || 0} valueStyle={{ fontSize: 14 }} />
+                        (<Statistic
+                            value={data?.add_2_rate ? data?.add_2_rate * 100 : 0}
+                            valueStyle={data?.add_2_rate > 0.1 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
+                            suffix="%"
+                            precision={4}
+                        />)
+                    </div>
+                </div>
+                <div className={style.item}>
+                    <span>添加3名客服人数</span>
+                    <div className={style.num}>
+                        <Statistic value={data?.add_3_cnt || 0} valueStyle={{ fontSize: 14 }} />
+                        (<Statistic
+                            value={data?.add_3_rate ? data?.add_3_rate * 100 : 0}
+                            valueStyle={data?.add_3_rate > 0.09 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
+                            suffix="%"
+                            precision={4}
+                        />)
+                    </div>
+                </div>
+                <div className={style.item}>
+                    <span>添加4名客服人数</span>
+                    <div className={style.num}>
+                        <Statistic value={data?.add_4_cnt || 0} valueStyle={{ fontSize: 14 }} />
+                        (<Statistic
+                            value={data?.add_4_rate ? data?.add_4_rate * 100 : 0}
+                            valueStyle={data?.add_4_rate > 0.08 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
+                            suffix="%"
+                            precision={4}
+                        />)
+                    </div>
+                </div>
+                <div className={style.item}>
+                    <span>添加5名客服人数</span>
+                    <div className={style.num}>
+                        <Statistic value={data?.add_5_cnt || 0} valueStyle={{ fontSize: 14 }} />
+                        (<Statistic
+                            value={data?.add_5_rate ? data?.add_5_rate * 100 : 0}
+                            valueStyle={data?.add_5_rate > 0.07 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
+                            suffix="%"
+                            precision={4}
+                        />)
+                    </div>
+                </div>
+                <div className={style.item}>
+                    <span>添加5名客服以上人数</span>
+                    <div className={style.num}>
+                        <Statistic value={data?.add_gt5_cnt || 0} valueStyle={{ fontSize: 14 }} />
+                        (<Statistic
+                            value={data?.add_gt5_rate ? data?.add_gt5_rate * 100 : 0}
+                            valueStyle={data?.add_gt5_rate > 0.06 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
+                            suffix="%"
+                            precision={4}
+                        />)
+                    </div>
+                </div>
+            </Flex>
+        </Spin>
+    </Card>
+};
+
+export default React.memo(RepeatOfCorpUser);

+ 57 - 0
src/pages/weComTask/page/corpData/heavyPowderMAS/tableConfig.tsx

@@ -0,0 +1,57 @@
+import { Statistic } from "antd";
+import { AnyObject } from "antd/es/_util/type"
+import { ColumnsType } from "antd/es/table"
+
+
+export const Columns1 = (): ColumnsType<AnyObject> => {
+
+    return [
+        {
+            title: '企业名称',
+            dataIndex: 'corp_name',
+            key: 'corp_name',
+            ellipsis: true,
+            width: 150,
+        },
+        {
+            title: '企业ID',
+            dataIndex: 'corp_id',
+            key: 'corp_id',
+            ellipsis: true,
+            width: 120
+        },
+        {
+            title: '总粉丝数',
+            dataIndex: 'total_num',
+            key: 'total_num',
+            align: 'center',
+            render: (text: any) => <Statistic value={text || 0} valueStyle={{ fontSize: 12 }} />
+        },
+        {
+            title: '净值粉丝数',
+            dataIndex: 'only_num',
+            key: 'only_num',
+            align: 'center',
+            render: (text: any) => <Statistic value={text || 0} valueStyle={{ fontSize: 12 }} />
+        },
+        {
+            title: '未识别粉丝数',
+            dataIndex: 'qc_null_num',
+            key: 'qc_null_num',
+            align: 'center',
+            render: (text: any) => <Statistic value={text || 0} valueStyle={{ fontSize: 12 }} />
+        },
+        {
+            title: '净值比',
+            dataIndex: 'only_rate',
+            key: 'only_rate',
+            align: 'center',
+            render: (text: any) => <Statistic
+                value={text ? text * 100 : 0}
+                valueStyle={text < 0.8 ? { color: '#cf1322', fontSize: 12 } : { color: '#3f8600', fontSize: 12 }}
+                suffix="%"
+                precision={2}
+            />
+        }
+    ]
+}

+ 127 - 0
src/pages/weComTask/page/corpData/heavyPowderMAS/userDetails.tsx

@@ -0,0 +1,127 @@
+import { useAjax } from "@/Hook/useAjax";
+import { GetProps } from "@/pages/weComTask/API/heavyPowderMAS";
+import { GetExternalUserRepeatByCorpListApiProps, getSelectQcUuidStatisticPageListApi } from "@/pages/weComTask/API/home";
+import SearchBox from "@/pages/weComTask/components/searchBox";
+import { Card, Space, Table, Image, Typography, Button, Input, InputNumber } from "antd";
+import React, { useEffect, useState } from "react";
+import CorpUserDetails from "../heavyPowder/corpUserDetails";
+import CorpDetails from "../heavyPowder/corpDetails";
+import { SearchOutlined } from "@ant-design/icons";
+
+const { Text } = Typography;
+
+const UserDetails: React.FC<{ queryParmas: GetProps }> = ({ queryParmas: q }) => {
+
+    /**************************************/
+    const [queryParmas, setQueryParmas] = useState<GetExternalUserRepeatByCorpListApiProps>({ pageNum: 1, pageSize: 20 })
+    const [queryParmasNew, setQueryParmasNew] = useState<GetExternalUserRepeatByCorpListApiProps>({ pageNum: 1, pageSize: 20 })
+    const getSelectQcUuidStatisticPageList = useAjax((params) => getSelectQcUuidStatisticPageListApi(params))
+    /**************************************/
+
+    useEffect(() => {
+        if (q?.aCorpIds.length > 0 && q?.bCorpIds.length > 0) {
+            getSelectQcUuidStatisticPageList.run({ corpIdList: [...new Set([...q.aCorpIds, ...q.bCorpIds])], ...queryParmas })
+        }
+    }, [queryParmas, q])
+
+    return <Card
+        variant="borderless"
+        title={<strong>重复粉丝用户详情(同时添加查询组、对照组的企微用户详情)</strong>}
+    >
+        <SearchBox
+            bodyPadding={`0 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>
+
+        <Table
+            columns={[
+                {
+                    title: '客户名称',
+                    dataIndex: 'name',
+                    key: 'name',
+                    width: 180,
+                    render: (text: any, record: any) => {
+                        return <Space>
+                            <Image src={record?.avatar} style={{ width: 20, borderRadius: 4 }} />
+                            <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(UserDetails);

+ 18 - 0
src/pages/weComTask/page/groupChat/taskList/details.tsx

@@ -384,6 +384,16 @@ const ExpandedRow: React.FC<{ record: any }> = ({ record }) => {
                         return <a onClick={() => setFailedUserDetails({ visible: true, list: b?.pullFailedUserDetail || [] })}>{a || 0}</a>
                     }
                 },
+                {
+                    title: '退群客户详情',
+                    dataIndex: 'quitUserCount',
+                    key: 'quitUserCount',
+                    width: 120,
+                    align: 'center',
+                    render: (a, b) => {
+                        return <a onClick={() => setFailedUserDetails({ visible: true, list: b?.quitUserDetail || [] })}>{a || 0}</a>
+                    }
+                },
                 {
                     title: '发送邀请客户数',
                     dataIndex: 'sendInviteUserCount',
@@ -511,6 +521,14 @@ const TaskDetails: React.FC<{
                     align: 'center',
                     render: (a, b) => <UserInfo count={a} id={b.id} title="删除或拉黑客户详情" status={3} />
                 },
+                {
+                    title: '退群客户数',
+                    dataIndex: 'quitUserCount',
+                    key: 'quitUserCount',
+                    width: 80,
+                    align: 'center',
+                    render: (a, b) => <UserInfo count={a} id={b.id} title="退群客户详情" status={5} />
+                },
                 {
                     title: '发送邀请客户数',
                     dataIndex: 'sendInviteUserCount',

+ 2 - 2
src/pages/weComTask/page/groupChat/taskList/userInfo.tsx

@@ -9,7 +9,7 @@ import React, { useEffect, useState } from "react";
  * @param param0 
  * @returns 
  */
-const UserInfo: React.FC<{ id: number, count: number, title?: string, status?: 1 | 3 | 4 | null }> = ({ id, count, title, status }) => {
+const UserInfo: React.FC<{ id: number, count: number, title?: string, status?: 1 | 3 | 4 | 5 | null }> = ({ id, count, title, status }) => {
 
     /*********************************************/
     const [queryForm, setQueryForm] = useState<GetLogChatUserInfoListProps>({ id, pageNum: 1, pageSize: 20, status })
@@ -24,7 +24,7 @@ const UserInfo: React.FC<{ id: number, count: number, title?: string, status?: 1
     }, [id, visible, queryForm])
 
     return <>
-        <a onClick={() => setVisible(true)}>{count}</a>
+        <a onClick={() => setVisible(true)}>{count || 0}</a>
         {visible && <Modal
             title={<strong>{title || '客户详情'}</strong>}
             open={visible}