index.tsx 37 KB

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