index.tsx 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
  1. import { useAjax } from '@/Hook/useAjax';
  2. import React, { useEffect, useState } from 'react';
  3. import { getCorpExternalUserRepeatListApi, getExternalUserRepeatByCorpAtlasApi, getExternalUserRepeatByCorpListApi, getExternalUserRepeatCorpApi, getExternalUserRepeatCorpUserApi } from '../../API/home';
  4. import { Avatar, Card, Checkbox, Col, Divider, Flex, Input, Row, Select, Space, Spin, Statistic, Table, Tabs, Tooltip, Typography } from 'antd';
  5. import { BarChartOutlined, CheckOutlined, DeleteOutlined, ExclamationOutlined, GlobalOutlined, QuestionCircleOutlined, RetweetOutlined, UserOutlined } from '@ant-design/icons';
  6. import useEcharts from '@/Hook/useEcharts';
  7. const { Title } = Typography;
  8. import style from './index.less'
  9. import { CorpExternalUserColumns, ExternalUserColumns } from './tableConfig';
  10. import UuidTem from './uuidTem';
  11. import { getCorpAllListApi } from '@/API/global';
  12. const Home: React.FC = () => {
  13. /*******************************************/
  14. const { Bar } = useEcharts()
  15. const [queryParmas, setQueryParmas] = useState<{ pageNum: number, pageSize: number, corpIdList?: string[] }>({ pageNum: 1, pageSize: 30 })
  16. const [queryParmasZt, setQueryParmasZt] = useState<{ pageNum: number, pageSize: number, corpIdList?: string[] }>({ pageNum: 1, pageSize: 30 })
  17. const [corpRepeat, setCorpRepeat] = useState<{ [x: string]: any }>({})
  18. const [corpUserRepeat, setCorpUserRepeat] = useState<{ [x: string]: any }>({})
  19. const [barCorpData, setBarCorpData] = useState<Record<string, any>[]>([])
  20. const [barCorpUserData, setBarCorpUserData] = useState<Record<string, any>[]>([])
  21. const [activeKey, setActiveKey] = useState<string>('3')
  22. const [userData, setUserData] = useState<Record<string, any>[]>([])
  23. const [overflowData, setOverflowData] = useState<{
  24. avgCorpRepeatUserRate: number,
  25. repeatUserRate: number,
  26. userCount: number,
  27. qcUuidCount: number,
  28. qcUuidCountRate: number,
  29. qcUuidNullCount: number,
  30. qcUuidNullCountRate: number,
  31. deletedUserCount: number,
  32. deletedUserCountRate: number
  33. qcUuidUserCount: number
  34. }>({ avgCorpRepeatUserRate: 0, repeatUserRate: 0, userCount: 0, qcUuidCount: 0, qcUuidCountRate: 0, qcUuidNullCount: 0, qcUuidNullCountRate: 0, deletedUserCount: 0, deletedUserCountRate: 0, qcUuidUserCount: 0 })
  35. const [oldFiltered, setOldFiltered] = useState<{ label: string, value: string }[]>([]);
  36. const [filtered, setFiltered] = useState<{ label: string, value: string }[]>([]);
  37. const [filtered1, setFiltered1] = useState<{ label: string, value: string }[]>([]);
  38. const getExternalUserRepeatCorp = useAjax(() => getExternalUserRepeatCorpApi())
  39. const getExternalUserRepeatCorpUser = useAjax(() => getExternalUserRepeatCorpUserApi())
  40. const getExternalUserRepeatByCorpAtlas = useAjax(() => getExternalUserRepeatByCorpAtlasApi())
  41. const getExternalUserRepeatByCorpList = useAjax((params) => getExternalUserRepeatByCorpListApi(params))
  42. const getCorpExternalUserRepeatList = useAjax((params) => getCorpExternalUserRepeatListApi(params))
  43. const getCorpAllList = useAjax((params) => getCorpAllListApi(params))
  44. /*******************************************/
  45. useEffect(() => {
  46. getCorpAllList.run({}).then(res => {
  47. console.log('getCorpAllList', res)
  48. setFiltered(res?.data?.map(item => ({ label: item.corpName, value: item.corpId })))
  49. setFiltered1(res?.data?.map(item => ({ label: item.corpName, value: item.corpId })))
  50. setOldFiltered(res?.data?.map(item => ({ label: item.corpName, value: item.corpId })))
  51. })
  52. }, [])
  53. useEffect(() => {
  54. getExternalUserRepeatByCorpList.run(queryParmas)
  55. }, [queryParmas])
  56. useEffect(() => {
  57. if (activeKey === '1')
  58. getCorpExternalUserRepeatList.run(queryParmasZt)
  59. }, [queryParmasZt, activeKey])
  60. useEffect(() => {
  61. if (activeKey === '2') {
  62. getExternalUserRepeatCorp.run().then(res => {
  63. if (res?.data) {
  64. const cr = res.data
  65. setCorpRepeat(cr)
  66. setBarCorpData([
  67. { name: '仅添加1个主体', '人数': cr?.oneRepeatCount },
  68. { name: '添加2个主体', '人数': cr?.twoRepeatCount },
  69. { name: '添加3个主体', '人数': cr?.threeRepeatCount },
  70. { name: '添加4个主体', '人数': cr?.fourRepeatCount },
  71. { name: '添加5个主体', '人数': cr?.fiveRepeatCount },
  72. { name: '添加5个主体以上', '人数': cr?.gtFiveRepeatCount }
  73. ])
  74. } else {
  75. setCorpRepeat({})
  76. setBarCorpData([])
  77. }
  78. })
  79. } else if (activeKey === '3') {
  80. getExternalUserRepeatCorpUser.run().then(res => {
  81. if (res?.data) {
  82. const cur = res.data
  83. setCorpUserRepeat(cur)
  84. setOverflowData({
  85. avgCorpRepeatUserRate: cur?.avgCorpRepeatUserRate || 0,
  86. repeatUserRate: cur?.repeatUserRate || 0,
  87. userCount: cur?.userCount || 0,
  88. qcUuidCount: cur?.qcUuidCount || 0,
  89. qcUuidCountRate: cur?.qcUuidCountRate || 0,
  90. qcUuidNullCount: cur?.qcUuidNullCount || 0,
  91. qcUuidNullCountRate: cur?.qcUuidNullCountRate || 0,
  92. deletedUserCount: cur?.deletedUserCount || 0,
  93. deletedUserCountRate: cur?.deletedUserCountRate || 0,
  94. qcUuidUserCount: cur?.qcUuidUserCount || 0,
  95. })
  96. setBarCorpUserData([
  97. { name: '仅添加1名客服', '人数': cur?.oneRepeatCount },
  98. { name: '添加2名客服', '人数': cur?.twoRepeatCount },
  99. { name: '添加3名客服', '人数': cur?.threeRepeatCount },
  100. { name: '添加4名客服', '人数': cur?.fourRepeatCount },
  101. { name: '添加5名客服', '人数': cur?.fiveRepeatCount },
  102. { name: '添加5名客服以上', '人数': cur?.gtFiveRepeatCount }
  103. ])
  104. } else {
  105. setCorpUserRepeat({})
  106. setOverflowData({ avgCorpRepeatUserRate: 0, repeatUserRate: 0, userCount: 0, qcUuidCount: 0, qcUuidCountRate: 0, qcUuidNullCount: 0, qcUuidNullCountRate: 0, deletedUserCount: 0, deletedUserCountRate: 0, qcUuidUserCount: 0 })
  107. setBarCorpUserData([])
  108. }
  109. })
  110. }
  111. }, [activeKey])
  112. useEffect(() => {
  113. getExternalUserRepeatByCorpAtlas.run().then(res => {
  114. if (res?.data) {
  115. setUserData(res?.data?.map(item => {
  116. return { name: item.corpName, '主体粉丝总数': item.corpExternalUserCount, '主体内重粉数': item.corpExternalUserRepeatCount }
  117. }))
  118. } else {
  119. setUserData([])
  120. }
  121. })
  122. }, [])
  123. const handleSearch = (val, isOne?: boolean) => {
  124. const f = oldFiltered.filter((item) => {
  125. const inputStr = val.replace(/[,,\s]/g, ',');
  126. if (inputStr && inputStr.includes(',')) {
  127. const inputList = inputStr.split(',').filter((it) => it).map(i => i.toLowerCase());
  128. return inputList.includes(((item?.label ?? '') as string).toLowerCase())
  129. }
  130. return ((item?.label ?? '') as string).toLowerCase().includes(val.toLowerCase())
  131. });
  132. !isOne ? setFiltered(f) : setFiltered1(f);
  133. };
  134. return <div>
  135. <Spin spinning={getExternalUserRepeatCorpUser.loading}>
  136. <Flex gap={16}>
  137. <Card variant="borderless" style={{ width: '20%' }}>
  138. <Flex justify='space-between'>
  139. <Statistic
  140. title={<strong style={{ fontSize: 14 }}>集团总企微用户数</strong>}
  141. value={overflowData.userCount}
  142. style={{ flex: 1 }}
  143. />
  144. <Avatar style={{ backgroundColor: '#DBEAFE', color: '#2563eb' }} size={40}><UserOutlined /></Avatar>
  145. </Flex>
  146. </Card>
  147. <Card variant="borderless" style={{ width: '20%' }}>
  148. <Flex justify='space-between'>
  149. <Statistic
  150. title={<strong style={{ fontSize: 14 }}>集团跨主体去重企微用户数</strong>}
  151. style={{ flex: 1 }}
  152. value={overflowData.qcUuidUserCount}
  153. />
  154. <Avatar style={{ backgroundColor: '#fff0f6', color: '#f759ab' }} size={40}><UserOutlined /></Avatar>
  155. </Flex>
  156. </Card>
  157. <Card variant="borderless" style={{ width: '20%' }}>
  158. <Flex justify='space-between'>
  159. <Statistic
  160. title={<strong style={{ fontSize: 14 }}>已识别用户数</strong>}
  161. value={overflowData.qcUuidCount}
  162. style={{ flex: 1 }}
  163. suffix={<div style={{ display: 'flex' }}>
  164. (<Statistic
  165. value={overflowData.qcUuidCountRate ? overflowData.qcUuidCountRate * 100 : 0}
  166. precision={2}
  167. suffix="%"
  168. valueStyle={overflowData?.qcUuidCountRate < 0.8 ? { color: '#cf1322' } : { color: '#3f8600' }}
  169. />)
  170. </div>}
  171. />
  172. <Avatar style={{ backgroundColor: '#DCFCE7', color: '#16a34a' }} size={40}><CheckOutlined /></Avatar>
  173. </Flex>
  174. </Card>
  175. <Card variant="borderless" style={{ width: '20%' }}>
  176. <Flex justify='space-between'>
  177. <Statistic
  178. title={<strong style={{ fontSize: 14 }}>未识别用户数</strong>}
  179. value={overflowData?.qcUuidNullCount}
  180. style={{ flex: 1 }}
  181. suffix={<div style={{ display: 'flex' }}>
  182. (<Statistic
  183. value={overflowData.qcUuidNullCountRate ? overflowData.qcUuidNullCountRate * 100 : 0}
  184. precision={2}
  185. suffix="%"
  186. valueStyle={overflowData?.qcUuidNullCountRate > 0.05 ? { color: '#cf1322' } : { color: '#3f8600' }}
  187. />)
  188. </div>}
  189. />
  190. <Avatar style={{ backgroundColor: 'rgba(251, 192, 163, 1)', color: '#f50' }} size={40}><ExclamationOutlined /></Avatar>
  191. </Flex>
  192. </Card>
  193. <Card variant="borderless" style={{ width: '20%' }}>
  194. <Flex justify='space-between'>
  195. <Statistic
  196. title={<strong style={{ fontSize: 14 }}>双删用户数</strong>}
  197. value={overflowData?.deletedUserCount}
  198. style={{ flex: 1 }}
  199. suffix={<div style={{ display: 'flex' }}>
  200. (<Statistic
  201. value={overflowData.deletedUserCountRate ? overflowData.deletedUserCountRate * 100 : 0}
  202. precision={2}
  203. suffix="%"
  204. valueStyle={overflowData?.deletedUserCountRate > 0.4 ? { color: '#cf1322' } : { color: '#3f8600' }}
  205. />)
  206. </div>}
  207. />
  208. <Avatar style={{ backgroundColor: '#ffcece', color: '#ff0606' }} size={40}><DeleteOutlined /></Avatar>
  209. </Flex>
  210. </Card>
  211. <Card variant="borderless" style={{ width: '20%' }}>
  212. <Flex justify='space-between'>
  213. <Statistic
  214. title={<Space>
  215. <strong style={{ fontSize: 14 }}>集团重粉率</strong>
  216. <Tooltip title="所有主体的重复粉丝去重数量/集团跨主体去重企微用户数">
  217. <QuestionCircleOutlined />
  218. </Tooltip>
  219. </Space>}
  220. style={{ flex: 1 }}
  221. value={overflowData.repeatUserRate ? overflowData.repeatUserRate * 100 : 0}
  222. precision={2}
  223. suffix="%"
  224. />
  225. <Avatar style={{ backgroundColor: '#F3E8FF', color: '#9333ea' }} size={40}><GlobalOutlined /></Avatar>
  226. </Flex>
  227. </Card>
  228. </Flex>
  229. </Spin>
  230. <Flex justify='space-between' style={{ margin: '20px 0 10px' }}>
  231. <Title level={3} style={{ margin: 0 }}><RetweetOutlined style={{ color: '#1890ff' }} /> 单主体内重粉分布</Title>
  232. <Select
  233. value={queryParmas?.corpIdList}
  234. onChange={(e) => setQueryParmas({ ...queryParmas, corpIdList: e })}
  235. showSearch
  236. style={{ minWidth: 200 }}
  237. maxTagCount={1}
  238. mode='multiple'
  239. placeholder="选择企业"
  240. filterOption={false}
  241. onSearch={handleSearch}
  242. allowClear
  243. options={filtered}
  244. popupRender={(menu) => (
  245. <>
  246. {menu}
  247. <Divider style={{ margin: '8px 0' }} />
  248. <Space style={{ padding: '0 8px 4px' }}>
  249. <Checkbox onChange={(e) => {
  250. if (e.target.checked) {
  251. setQueryParmas({ ...queryParmas, corpIdList: filtered.map(item => item.value) })
  252. } else {
  253. setQueryParmas({ ...queryParmas, corpIdList: [] })
  254. }
  255. }}>全选</Checkbox>
  256. </Space>
  257. </>
  258. )}
  259. />
  260. </Flex>
  261. <Row gutter={16}>
  262. <Col span={10}>
  263. <Spin spinning={getExternalUserRepeatByCorpAtlas.loading}>
  264. <Card style={{ height: '100%' }}>
  265. <Bar data={userData} title="粉丝前20单主体总用户数, 主体内重粉数" />
  266. </Card>
  267. </Spin>
  268. </Col>
  269. <Col span={14}>
  270. <Card style={{ height: '100%' }}>
  271. <Table
  272. columns={ExternalUserColumns()}
  273. scroll={{ y: 300, x: 900 }}
  274. bordered
  275. dataSource={getExternalUserRepeatByCorpList.data?.data?.records}
  276. loading={getExternalUserRepeatByCorpList.loading}
  277. rowKey="corpId"
  278. pagination={{
  279. total: getExternalUserRepeatByCorpList.data?.data?.total,
  280. current: getExternalUserRepeatByCorpList?.data?.data?.current || 1,
  281. pageSize: getExternalUserRepeatByCorpList?.data?.data?.size || 10
  282. }}
  283. onChange={(pagination: any, _: any, sortData: any) => {
  284. let { current, pageSize } = pagination
  285. let newQueryForm = JSON.parse(JSON.stringify(queryParmas))
  286. if (sortData && sortData?.order) {
  287. newQueryForm['sortType'] = sortData?.order === 'ascend' ? 'ASC' : 'DESC'
  288. newQueryForm['orderByField'] = sortData?.field
  289. } else {
  290. delete newQueryForm['sortType']
  291. delete newQueryForm['orderByField']
  292. }
  293. newQueryForm.pageNum = current || newQueryForm.pageNum
  294. newQueryForm.pageSize = pageSize || newQueryForm.pageSize
  295. setQueryParmas({ ...newQueryForm })
  296. }}
  297. />
  298. </Card>
  299. </Col>
  300. </Row>
  301. <Spin spinning={getExternalUserRepeatCorp.loading || getExternalUserRepeatCorpUser.loading || getCorpExternalUserRepeatList.loading}>
  302. <Title level={3}><BarChartOutlined style={{ color: '#22c55e' }} /> 用户在集团内重粉分布</Title>
  303. <Tabs
  304. tabBarExtraContent={activeKey === '1' &&
  305. <Select
  306. value={queryParmasZt?.corpIdList}
  307. onChange={(e) => setQueryParmasZt({ ...queryParmasZt, corpIdList: e, pageNum: 1 })}
  308. showSearch
  309. style={{ minWidth: 200 }}
  310. maxTagCount={1}
  311. mode='multiple'
  312. placeholder="选择企业"
  313. filterOption={false}
  314. onSearch={(val) => handleSearch(val, true)}
  315. allowClear
  316. options={filtered1}
  317. popupRender={(menu) => (
  318. <>
  319. {menu}
  320. <Divider style={{ margin: '8px 0' }} />
  321. <Space style={{ padding: '0 8px 4px' }}>
  322. <Checkbox onChange={(e) => {
  323. if (e.target.checked) {
  324. setQueryParmasZt({ ...queryParmasZt, corpIdList: filtered1.map(item => item.value) })
  325. } else {
  326. setQueryParmasZt({ ...queryParmasZt, corpIdList: [] })
  327. }
  328. }}>全选</Checkbox>
  329. </Space>
  330. </>
  331. )}
  332. />}
  333. items={[
  334. {
  335. key: '3',
  336. label: '用户添加客服号分布',
  337. children: <Row gutter={16}>
  338. <Col span={12}>
  339. <Card style={{ height: '100%' }}>
  340. <Bar data={barCorpUserData} title="用户添加客服号分布" horizontal />
  341. </Card>
  342. </Col>
  343. <Col span={12}>
  344. <DetailsTemplate data={corpUserRepeat} title='用户添加客服号详细数据' />
  345. </Col>
  346. </Row>
  347. },
  348. {
  349. key: '2',
  350. label: '用户添加主体分布',
  351. children: <Row gutter={16}>
  352. <Col span={12}>
  353. <Card style={{ height: '100%' }}>
  354. <Bar data={barCorpData} title="用户添加主体分布" horizontal />
  355. </Card>
  356. </Col>
  357. <Col span={12}>
  358. <Card style={{ height: '100%' }}>
  359. <Title level={3} style={{ marginTop: 0, textAlign: 'center', fontSize: 18, color: '#313131' }}>用户添加主体分布重粉详细数据</Title>
  360. <Flex vertical gap={7}>
  361. <div className={style.item}>
  362. <span>粉丝总数</span>
  363. <div className={style.num}>
  364. <Statistic value={corpRepeat?.userCount || 0} valueStyle={{ fontSize: 14 }} />
  365. </div>
  366. </div>
  367. <div className={style.item}>
  368. <Space>
  369. <span>集团跨主体去重企微用户数</span>
  370. <Tooltip title="集团跨主体去重企微用户数=未识别人数+添加1个主体人数+添加多个(>1)主体人数">
  371. <QuestionCircleOutlined />
  372. </Tooltip>
  373. </Space>
  374. <div className={style.num}>
  375. <Statistic value={corpRepeat?.qcUuidUserCount || 0} valueStyle={{ fontSize: 14 }} />
  376. </div>
  377. </div>
  378. <div className={style.item}>
  379. <span>未识别用户数</span>
  380. <div className={style.num}>
  381. <Statistic value={corpRepeat?.qcUuidNullCount || 0} valueStyle={{ fontSize: 14 }} />
  382. (<Statistic
  383. value={corpRepeat?.qcUuidNullCountRate ? corpRepeat?.qcUuidNullCountRate * 100 : 0}
  384. valueStyle={corpRepeat?.qcUuidNullCountRate > 0.1 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
  385. suffix="%"
  386. precision={4}
  387. />)
  388. </div>
  389. </div>
  390. <div className={style.item}>
  391. <span>{`用户添加>1主体人数`}</span>
  392. <div className={style.num}>
  393. <Statistic value={corpRepeat?.gtOneRepeatCountTotal || 0} valueStyle={{ fontSize: 14 }} />
  394. (<Statistic
  395. value={corpRepeat?.gtOneRepeatCountTotalRate ? corpRepeat?.gtOneRepeatCountTotalRate * 100 : 0}
  396. valueStyle={corpRepeat?.gtOneRepeatCountTotalRate > 0.2 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
  397. suffix="%"
  398. precision={4}
  399. />)
  400. </div>
  401. </div>
  402. <div className={style.item}>
  403. <span>仅添加1个主体人数</span>
  404. <div className={style.num}>
  405. <Statistic value={corpRepeat?.oneRepeatCount || 0} valueStyle={{ fontSize: 14 }} />
  406. (<Statistic
  407. value={corpRepeat?.oneRepeatCountRate ? corpRepeat?.oneRepeatCountRate * 100 : 0}
  408. valueStyle={corpRepeat?.oneRepeatCountRate > 0.5 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
  409. suffix="%"
  410. precision={4}
  411. />)
  412. </div>
  413. </div>
  414. <div className={style.item}>
  415. <span>添加2个主体人数</span>
  416. <div className={style.num}>
  417. <Statistic value={corpRepeat?.twoRepeatCount || 0} valueStyle={{ fontSize: 14 }} />
  418. (<Statistic
  419. value={corpRepeat?.twoRepeatCountRate ? corpRepeat?.twoRepeatCountRate * 100 : 0}
  420. valueStyle={corpRepeat?.twoRepeatCountRate > 0.1 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
  421. suffix="%"
  422. precision={4}
  423. />)
  424. </div>
  425. </div>
  426. <div className={style.item}>
  427. <span>添加3个主体人数</span>
  428. <div className={style.num}>
  429. <Statistic value={corpRepeat?.threeRepeatCount || 0} valueStyle={{ fontSize: 14 }} />
  430. (<Statistic
  431. value={corpRepeat?.threeRepeatCountRate ? corpRepeat?.threeRepeatCountRate * 100 : 0}
  432. valueStyle={corpRepeat?.threeRepeatCountRate > 0.09 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
  433. suffix="%"
  434. precision={4}
  435. />)
  436. </div>
  437. </div>
  438. <div className={style.item}>
  439. <span>添加4个主体人数</span>
  440. <div className={style.num}>
  441. <Statistic value={corpRepeat?.fourRepeatCount || 0} valueStyle={{ fontSize: 14 }} />
  442. (<Statistic
  443. value={corpRepeat?.fourRepeatCountRate ? corpRepeat?.fourRepeatCountRate * 100 : 0}
  444. valueStyle={corpRepeat?.fourRepeatCountRate > 0.08 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
  445. suffix="%"
  446. precision={4}
  447. />)
  448. </div>
  449. </div>
  450. <div className={style.item}>
  451. <span>添加5个主体人数</span>
  452. <div className={style.num}>
  453. <Statistic value={corpRepeat?.fiveRepeatCount || 0} valueStyle={{ fontSize: 14 }} />
  454. (<Statistic
  455. value={corpRepeat?.fiveRepeatCountRate ? corpRepeat?.fiveRepeatCountRate * 100 : 0}
  456. valueStyle={corpRepeat?.fiveRepeatCountRate > 0.07 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
  457. suffix="%"
  458. precision={4}
  459. />)
  460. </div>
  461. </div>
  462. <div className={style.item}>
  463. <span>添加5个主体以上人数</span>
  464. <div className={style.num}>
  465. <Statistic value={corpRepeat?.gtFiveRepeatCount || 0} valueStyle={{ fontSize: 14 }} />
  466. (<Statistic
  467. value={corpRepeat?.gtFiveRepeatCountRate ? corpRepeat?.gtFiveRepeatCountRate * 100 : 0}
  468. valueStyle={corpRepeat?.gtFiveRepeatCountRate > 0.06 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
  469. suffix="%"
  470. precision={4}
  471. />)
  472. </div>
  473. </div>
  474. </Flex>
  475. </Card>
  476. </Col>
  477. </Row>
  478. },
  479. {
  480. key: '1',
  481. label: '主体内用户分布',
  482. children: <Card>
  483. <Table
  484. columns={CorpExternalUserColumns()}
  485. scroll={{ y: 300, x: 1000 }}
  486. bordered
  487. dataSource={getCorpExternalUserRepeatList.data?.data?.records}
  488. loading={getCorpExternalUserRepeatList.loading}
  489. rowKey="corpId"
  490. pagination={{
  491. total: getCorpExternalUserRepeatList.data?.data?.total,
  492. current: getCorpExternalUserRepeatList?.data?.data?.current || 1,
  493. pageSize: getCorpExternalUserRepeatList?.data?.data?.size || 20,
  494. }}
  495. onChange={(pagination: any, _: any, sortData: any) => {
  496. let { current, pageSize } = pagination
  497. let newQueryForm = JSON.parse(JSON.stringify(queryParmasZt))
  498. if (sortData && sortData?.order) {
  499. newQueryForm['sortType'] = sortData?.order === 'ascend' ? 'ASC' : 'DESC'
  500. newQueryForm['orderByField'] = sortData?.field
  501. } else {
  502. delete newQueryForm['sortType']
  503. delete newQueryForm['orderByField']
  504. }
  505. newQueryForm.pageNum = current || newQueryForm.pageNum
  506. newQueryForm.pageSize = pageSize || newQueryForm.pageSize
  507. setQueryParmasZt({ ...newQueryForm })
  508. }}
  509. />
  510. </Card>
  511. }
  512. ]}
  513. onChange={(e) => { setActiveKey(e) }}
  514. activeKey={activeKey}
  515. />
  516. </Spin>
  517. {/* uuid */}
  518. <UuidTem getCorpAllList={oldFiltered} />
  519. </div>
  520. };
  521. const DetailsTemplate: React.FC<{ data: { [x: string]: any }, title: string }> = ({ data, title }) => {
  522. return <Card style={{ height: '100%' }}>
  523. <Title level={3} style={{ marginTop: 0, textAlign: 'center', fontSize: 18, color: '#313131' }}>{title || '详细数据'}</Title>
  524. <Flex vertical gap={7}>
  525. <div className={style.item}>
  526. <span>粉丝总数</span>
  527. <div className={style.num}>
  528. <Statistic value={data?.userCount || 0} valueStyle={{ fontSize: 14 }} />
  529. </div>
  530. </div>
  531. <div className={style.item}>
  532. <span>已激活集团客服号数</span>
  533. <div className={style.num}>
  534. <Statistic value={data?.activatedCorpUserCount || 0} valueStyle={{ fontSize: 14 }} />
  535. (<Statistic
  536. value={data?.activatedCorpUserCountRate ? data?.activatedCorpUserCountRate * 100 : 0}
  537. valueStyle={data?.activatedCorpUserCountRate < 0.6 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
  538. suffix="%"
  539. precision={4}
  540. />)
  541. </div>
  542. </div>
  543. <div className={style.item}>
  544. <span>退出集团客服号数</span>
  545. <div className={style.num}>
  546. <Statistic value={data?.quitCorpUserCount || 0} valueStyle={{ fontSize: 14 }} />
  547. (<Statistic
  548. value={data?.quitCorpUserCountRate ? data?.quitCorpUserCountRate * 100 : 0}
  549. valueStyle={data?.quitCorpUserCountRate > 0.1 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
  550. suffix="%"
  551. precision={4}
  552. />)
  553. </div>
  554. </div>
  555. <div className={style.item}>
  556. <span>未激活集团客服号数</span>
  557. <div className={style.num}>
  558. <Statistic value={data?.unactivatedCorpUserCount || 0} valueStyle={{ fontSize: 14 }} />
  559. (<Statistic
  560. value={data?.unactivatedCorpUserCountRate ? data?.unactivatedCorpUserCountRate * 100 : 0}
  561. valueStyle={data?.unactivatedCorpUserCountRate > 0.1 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
  562. suffix="%"
  563. precision={4}
  564. />)
  565. </div>
  566. </div>
  567. <div className={style.item}>
  568. <Space>
  569. <span>集团跨主体去重企微用户数</span>
  570. <Tooltip title="集团跨主体去重企微用户数=未识别人数+添加1个客服人数+添加多个(>1)客服人数">
  571. <QuestionCircleOutlined />
  572. </Tooltip>
  573. </Space>
  574. <div className={style.num}>
  575. <Statistic value={data?.qcUuidUserCount || 0} valueStyle={{ fontSize: 14 }} />
  576. </div>
  577. </div>
  578. <div className={style.item}>
  579. <span>未识别用户数</span>
  580. <div className={style.num}>
  581. <Statistic value={data?.qcUuidNullCount || 0} valueStyle={{ fontSize: 14 }} />
  582. (<Statistic
  583. value={data?.qcUuidNullCountRate ? data?.qcUuidNullCountRate * 100 : 0}
  584. valueStyle={data?.qcUuidNullCountRate > 0.1 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
  585. suffix="%"
  586. precision={4}
  587. />)
  588. </div>
  589. </div>
  590. <div className={style.item}>
  591. <span>{`用户添加>1客服号人数`}</span>
  592. <div className={style.num}>
  593. <Statistic value={data?.gtOneRepeatCountTotal || 0} valueStyle={{ fontSize: 14 }} />
  594. (<Statistic
  595. value={data?.gtOneRepeatCountTotalRate ? data?.gtOneRepeatCountTotalRate * 100 : 0}
  596. valueStyle={data?.gtOneRepeatCountTotalRate > 0.3 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
  597. suffix="%"
  598. precision={4}
  599. />)
  600. </div>
  601. </div>
  602. <div className={style.item}>
  603. <span>仅添加1名客服人数</span>
  604. <div className={style.num}>
  605. <Statistic value={data?.oneRepeatCount || 0} valueStyle={{ fontSize: 14 }} />
  606. (<Statistic
  607. value={data?.oneRepeatCountRate ? data?.oneRepeatCountRate * 100 : 0}
  608. valueStyle={data?.oneRepeatCountRate > 0.5 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
  609. suffix="%"
  610. precision={4}
  611. />)
  612. </div>
  613. </div>
  614. <div className={style.item}>
  615. <span>添加2名客服人数</span>
  616. <div className={style.num}>
  617. <Statistic value={data?.twoRepeatCount || 0} valueStyle={{ fontSize: 14 }} />
  618. (<Statistic
  619. value={data?.twoRepeatCountRate ? data?.twoRepeatCountRate * 100 : 0}
  620. valueStyle={data?.twoRepeatCountRate > 0.1 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
  621. suffix="%"
  622. precision={4}
  623. />)
  624. </div>
  625. </div>
  626. <div className={style.item}>
  627. <span>添加3名客服人数</span>
  628. <div className={style.num}>
  629. <Statistic value={data?.threeRepeatCount || 0} valueStyle={{ fontSize: 14 }} />
  630. (<Statistic
  631. value={data?.threeRepeatCountRate ? data?.threeRepeatCountRate * 100 : 0}
  632. valueStyle={data?.threeRepeatCountRate > 0.09 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
  633. suffix="%"
  634. precision={4}
  635. />)
  636. </div>
  637. </div>
  638. <div className={style.item}>
  639. <span>添加4名客服人数</span>
  640. <div className={style.num}>
  641. <Statistic value={data?.fourRepeatCount || 0} valueStyle={{ fontSize: 14 }} />
  642. (<Statistic
  643. value={data?.fourRepeatCountRate ? data?.fourRepeatCountRate * 100 : 0}
  644. valueStyle={data?.fourRepeatCountRate > 0.08 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
  645. suffix="%"
  646. precision={4}
  647. />)
  648. </div>
  649. </div>
  650. <div className={style.item}>
  651. <span>添加5名客服人数</span>
  652. <div className={style.num}>
  653. <Statistic value={data?.fiveRepeatCount || 0} valueStyle={{ fontSize: 14 }} />
  654. (<Statistic
  655. value={data?.fiveRepeatCountRate ? data?.fiveRepeatCountRate * 100 : 0}
  656. valueStyle={data?.fiveRepeatCountRate > 0.07 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
  657. suffix="%"
  658. precision={4}
  659. />)
  660. </div>
  661. </div>
  662. <div className={style.item}>
  663. <span>添加5名客服以上人数</span>
  664. <div className={style.num}>
  665. <Statistic value={data?.gtFiveRepeatCount || 0} valueStyle={{ fontSize: 14 }} />
  666. (<Statistic
  667. value={data?.gtFiveRepeatCountRate ? data?.gtFiveRepeatCountRate * 100 : 0}
  668. valueStyle={data?.gtFiveRepeatCountRate > 0.06 ? { color: '#cf1322', fontSize: 14 } : { color: '#3f8600', fontSize: 14 }}
  669. suffix="%"
  670. precision={4}
  671. />)
  672. </div>
  673. </div>
  674. </Flex>
  675. </Card>
  676. };
  677. export default Home;