返回 AiToEarn
statistics.tsx
根目录 / project / aitoearn-electron / src / views / statistics / statistics.tsx
1 /*
2 * @Author: nevin
3 * @Date: 2025-01-17 20:05:00
4 * @LastEditTime: 2025-02-24 12:15:15
5 * @LastEditors: nevin
6 * @Description: 数据页
7 */
8 import { useEffect, useState, useRef } from 'react';
9 import './statistics.css';
10 import { icpGetAccountDashboard, icpGetAccountStatistics } from '@/icp/account';
11 import { DashboardData, StatisticsInfo } from './comment';
12 import { Button, Card, Layout, Avatar, DatePicker, Spin } from 'antd';
13 import {
14 InfoCircleOutlined,
15 DownloadOutlined,
16 SearchOutlined,
17 ReloadOutlined,
18 QuestionCircleOutlined,
19 } from '@ant-design/icons';
20 import { Dayjs } from 'dayjs';
21 import * as echarts from 'echarts';
22 import dayjs from 'dayjs';
23 import { message } from 'antd';
24 import WebView from '@/components/WebView';
25
26 import douyinIcon from '../../assets/svgs/account/douyin.svg';
27 import xhsIcon from '../../assets/svgs/account/xhs.svg';
28 import wxSphIcon from '../../assets/svgs/account/wx-sph.svg';
29 import ksIcon from '../../assets/svgs/account/ks.svg';
30
31 const { Content } = Layout;
32
33 const Statistics = () => {
34 const [statisticsInfo, setStatisticsInfo] =
35 useState<Partial<StatisticsInfo>>();
36
37 const [dashboardData, setDashboardData] = useState<DashboardData[]>([]);
38 const [dashboardData7, setDashboardData7] = useState<any[]>([]);
39
40 const [selectedDateRange, setSelectedDateRange] = useState<
41 [Dayjs | null, Dayjs | null]
42 >([dayjs().subtract(7, 'day'), dayjs().subtract(1, 'day')]);
43
44 const chartRef = useRef<HTMLDivElement>(null);
45 const chartInstance = useRef<echarts.ECharts>();
46
47 // 添加选中的账户ID列表
48 const [selectedAccounts, setSelectedAccounts] = useState<number[]>([]);
49 // 添加选中的指标类型
50 const [selectedMetric, setSelectedMetric] = useState<string>('fans');
51
52 // 添加状态来控制WebView的显示
53 const [examineVideoData, setExamineVideoData] = useState<{
54 url: string;
55 account: any;
56 open: boolean;
57 }>({
58 url: '',
59 account: null,
60 open: false,
61 });
62
63 const [loading, setLoading] = useState(true);
64 const [loadingMore, setLoadingMore] = useState(false);
65
66 useEffect(() => {
67 getAccountStatistics();
68 console.log('selectedAccounts', selectedAccounts);
69 }, []);
70
71 useEffect(() => {
72 // 获取账户信息后立即调用 getAccountStatistics7
73 console.log('###', 123);
74 getAccountStatistics7();
75 }, [selectedAccounts]);
76
77 // 初始化图表
78 useEffect(() => {
79 if (chartRef.current) {
80 chartInstance.current = echarts.init(chartRef.current);
81 }
82 return () => {
83 chartInstance.current?.dispose();
84 };
85 }, []);
86
87 // 在获取账户统计信息后,默认选中所有账户
88 useEffect(() => {
89 if (statisticsInfo?.list) {
90 setSelectedAccounts(statisticsInfo.list.map((account) => account.id));
91 }
92 }, [statisticsInfo]);
93
94 // 更新图表数据
95 useEffect(() => {
96 if (chartInstance.current) {
97 // 如果没有选中的账户或没有数据,显示空图表
98 if (selectedAccounts.length === 0 || dashboardData7.length === 0) {
99 const emptyOption = {
100 title: {
101 text: '数据趋势',
102 left: 'center',
103 },
104 grid: {
105 left: '3%',
106 right: '4%',
107 bottom: '15%',
108 top: '10%',
109 containLabel: true,
110 },
111 xAxis: {
112 type: 'category',
113 data: [],
114 },
115 yAxis: {
116 type: 'value',
117 },
118 series: [],
119 };
120 chartInstance.current.setOption(emptyOption, true);
121 return;
122 }
123
124 // 获取所有日期
125 const allDates =
126 dashboardData7[0]?.data.map((_: any, index: number) => {
127 const date = selectedDateRange[0]?.clone().add(index, 'day');
128 return date?.format('YYYY-MM-DD');
129 }) || [];
130
131 // 平台类型对应的颜色
132 const platformColors: Record<string, string> = {
133 douyin: '#183641', // 抖音
134 xhs: '#FF2442', // 小红书
135 wxSph: '#FA9A32', // 微信视频号
136 KWAI: '#F64806', // 快手
137 };
138
139 const option = {
140 title: {
141 text: '数据趋势',
142 left: 'center',
143 },
144 tooltip: {
145 trigger: 'axis',
146 axisPointer: {
147 type: 'shadow',
148 },
149 },
150 legend: {
151 data: dashboardData7.map((item) => item.name),
152 bottom: 0,
153 textStyle: {
154 color: '#666',
155 },
156 },
157 grid: {
158 left: '3%',
159 right: '4%',
160 bottom: '15%',
161 top: '10%',
162 containLabel: true,
163 },
164 xAxis: {
165 type: 'category',
166 data: allDates,
167 axisLabel: {
168 interval: 0,
169 rotate: 30,
170 color: '#666',
171 fontSize: 12,
172 },
173 axisTick: {
174 alignWithLabel: true,
175 },
176 },
177 yAxis: {
178 type: 'value',
179 axisLabel: {
180 color: '#666',
181 fontSize: 12,
182 },
183 },
184 series: dashboardData7.map((account) => ({
185 name: account.name,
186 type: 'bar',
187 data: account.data.map(
188 (item: Record<string, any>) => Number(item[selectedMetric]) || 0,
189 ),
190 barMaxWidth: 30,
191 itemStyle: {
192 color: platformColors[account.type] || '#a66ae4', // 根据账户类型设置颜色,默认使用紫色
193 borderRadius: [4, 4, 0, 0],
194 },
195 })),
196 };
197 chartInstance.current.setOption(option, true);
198 }
199 }, [dashboardData7, selectedMetric]);
200
201 // 获取日期数组
202 const getDatesArray = (start: Dayjs | null, end: Dayjs | null) => {
203 if (!start || !end) return [];
204 const dates = [];
205 let curr = start;
206 while (curr <= end) {
207 dates.push(curr.format('YYYY-MM-DD'));
208 curr = curr.add(1, 'day');
209 }
210 return dates;
211 };
212
213 // 获取指标趋势数据
214 const getMetricTrend = (metric: string) => {
215 if (!selectedDateRange?.[0] || !selectedDateRange?.[1]) return [];
216 const dates = getDatesArray(selectedDateRange[0], selectedDateRange[1]);
217 return dates.map((date) => {
218 return dashboardData7.reduce((sum, item: any) => {
219 // 这里需要根据实际数据结构调整
220 return sum + Number(item[metric] || 0);
221 }, 0);
222 });
223 };
224
225 // 获取昨日新增总和
226 const getTotalYesterdayIncrease = (metric: string) => {
227 return dashboardData.reduce((sum, item: any) => {
228 // 这里需要根据实际数据结构调整
229 return Number(sum) + Number(item[`${metric}`] || 0);
230 }, 0);
231 };
232
233 // 获取日期范围内的总计值
234 const getRangeTotalMetricValue = (metric: string) => {
235 if (!dashboardData7.length) return 0;
236 return dashboardData7.reduce((sum, account) => {
237 const accountSum = account.data.reduce(
238 (acc: number, item: Record<string, any>) =>
239 acc + Number(item[metric] || 0),
240 0,
241 );
242 return sum + accountSum;
243 }, 0);
244 };
245
246 // 获取昨日数据总和
247 const getYesterdayTotalValue = (metric: string) => {
248 return dashboardData.reduce(
249 (sum, item: any) => sum + Number(item[metric] || 0),
250 0,
251 );
252 };
253
254 // 导出数据
255 const handleExportData = () => {
256 const data = dashboardData.map((item: any) => ({
257 账户ID: item.id,
258 粉丝数: item.fans,
259 播放数: item.read,
260 评论数: item.comment,
261 点赞数: item.like,
262 分享数: item.collect,
263 主页访问: item.forward,
264 }));
265
266 const csvContent =
267 'data:text/csv;charset=utf-8,' +
268 Object.keys(data[0]).join(',') +
269 '\n' +
270 data.map((row) => Object.values(row).join(',')).join('\n');
271
272 const encodedUri = encodeURI(csvContent);
273 const link = document.createElement('a');
274 link.setAttribute('href', encodedUri);
275 link.setAttribute(
276 'download',
277 `数据统计_${new Date().toLocaleDateString()}.csv`,
278 );
279 document.body.appendChild(link);
280 link.click();
281 document.body.removeChild(link);
282 };
283
284 async function getAccountStatistics() {
285 setLoading(true);
286 setDashboardData([]);
287 const res: StatisticsInfo = await icpGetAccountStatistics();
288 console.log('zhanghu res:', res);
289 const list = res.list.filter((account: any) => account.status === 0);
290 setStatisticsInfo({ ...res, list });
291 // 获取到账号列表后,遍历获取每个账号的看板数据
292 if (res.list && res.list.length > 0) {
293 for (const account of res.list) {
294 if (account.status === 1) continue;
295 const dashboardRes = await getAccountDashboard(account.id);
296 console.log('dashboardRes:', dashboardRes);
297 // const accountInfo = await icpGetAccountInfo(account.type, account.uid);
298 setDashboardData((prev) => [
299 ...prev,
300 { ...dashboardRes[0], id: account.id },
301 ]);
302 }
303 }
304 setLoading(false);
305 }
306
307 async function getAccountStatistics7() {
308 setLoading(true);
309 setDashboardData7([]);
310 const res: any = statisticsInfo;
311 const dataAll = [];
312
313 try {
314 // 获取到账号列表后,遍历获取每个账号的看板数据
315 if (res?.list && res.list.length > 0) {
316 // 只获取选中账户的数据
317 const selectedAccountsList = res.list.filter((account: any) =>
318 selectedAccounts.includes(account.id),
319 );
320
321 for (const account of selectedAccountsList) {
322 const startDate = selectedDateRange[0]?.format('YYYY-MM-DD');
323 const endDate = selectedDateRange[1]?.format('YYYY-MM-DD');
324 const dashboardRes = await icpGetAccountDashboard(account.id, [
325 startDate,
326 endDate,
327 ]);
328
329 const datas = {
330 id: account.id,
331 type: account.type,
332 name: account.nickname,
333 data: dashboardRes,
334 };
335 dataAll.push(datas);
336 }
337
338 console.log('所有数据', dataAll);
339
340 // 所有数据获取完成后再一次性更新
341 setDashboardData7(dataAll);
342 }
343 } catch (error) {
344 console.error('获取数据失败:', error);
345 setLoading(false);
346 } finally {
347 setLoading(false);
348 }
349 }
350
351 /**
352 * 获取账号看板信息
353 * @param id
354 */
355 async function getAccountDashboard(id: number) {
356 const res = await icpGetAccountDashboard(id);
357 return res;
358 }
359
360 // 渲染数据卡片
361 const renderMetricCard = (
362 title: string,
363 value: number | string,
364 icon: string,
365 ) => (
366 <div className="p-4 bg-white rounded-lg shadow-sm">
367 <div className="flex items-center justify-between mb-2">
368 <div className="text-sm text-gray-600">{title}</div>
369 <span className="text-lg text-gray-400">{icon}</span>
370 </div>
371 <div className="text-2xl font-semibold text-[#a66ae4]">
372 {value.toLocaleString()}
373 </div>
374 </div>
375 );
376
377 // 获取一个月前的日期
378 const disabledDate = (current: Dayjs) => {
379 const oneMonthAgo = dayjs().subtract(1, 'month');
380 const today = dayjs();
381 return current && (current < oneMonthAgo || current > today);
382 };
383
384 // 处理账户选择
385 const toggleAccountSelection = async (accountId: number) => {
386 const newSelectedAccounts = selectedAccounts.includes(accountId)
387 ? selectedAccounts.filter((id) => id !== accountId)
388 : [...selectedAccounts, accountId];
389
390 setSelectedAccounts(newSelectedAccounts);
391
392 // 重新获取数据
393 setDashboardData7([]);
394 const res: any = statisticsInfo;
395 const dataAll = [];
396
397 if (res.list && res.list.length > 0) {
398 // 只获取选中账户的数据
399 const selectedAccountsList = res.list.filter((account: any) =>
400 newSelectedAccounts.includes(account.id),
401 );
402
403 for (const account of selectedAccountsList) {
404 const startDate = selectedDateRange[0]?.format('YYYY-MM-DD');
405 const endDate = selectedDateRange[1]?.format('YYYY-MM-DD');
406 const dashboardRes = await icpGetAccountDashboard(account.id, [
407 startDate,
408 endDate,
409 ]);
410
411 const datas = {
412 id: account.id,
413 type: account.type,
414 name: account.nickname,
415 data: dashboardRes,
416 };
417 dataAll.push(datas);
418 }
419 setDashboardData7(dataAll);
420 }
421 };
422
423 // 处理指标类型选择
424 const handleMetricSelect = (metric: string) => {
425 setSelectedMetric(metric);
426 };
427
428 // 刷新账户数据
429 const refreshAccountData = () => {
430 // 清空现有数据
431 setDashboardData([]);
432 // 重新获取所有选中账户的数据
433 getAccountStatistics();
434 message.success('数据已刷新');
435 };
436
437 // 添加获取平台图标的函数
438 const getPlatformIcon = (type: string) => {
439 switch (type) {
440 case 'douyin':
441 return douyinIcon;
442 case 'xhs':
443 return xhsIcon;
444 case 'wxSph':
445 return wxSphIcon;
446 case 'KWAI':
447 return ksIcon;
448 default:
449 return '';
450 }
451 };
452
453 // 修改检查视频的函数
454 const examineVideo = (account: any) => {
455 console.log('检查视频', account);
456 // 根据账户类型确定要打开的URL
457 let url = '';
458 switch (account.type) {
459 case 'douyin':
460 url = `https://creator.douyin.com/`;
461 break;
462 case 'xhs':
463 url = `https://www.xiaohongshu.com/`;
464 break;
465 case 'wxSph':
466 url = `https://channels.weixin.qq.com`;
467 break;
468 case 'KWAI':
469 url = `https://id.kuaishou.com/pass/kuaishou/login/passToken?sid=kuaishou.web.cp.api`;
470 break;
471 default:
472 url = '';
473 }
474
475 setExamineVideoData({
476 url,
477 account,
478 open: true,
479 });
480 };
481
482 // 关闭WebView
483 const closeWebView = () => {
484 setExamineVideoData((prev) => ({ ...prev, open: false }));
485 };
486
487 return (
488 <div className="min-h-screen page-container bg-gray-50">
489 <Spin
490 spinning={loading}
491 tip="数据加载中..."
492 size="large"
493 className="min-h-screen"
494 >
495 {/* WebView组件 */}
496 {examineVideoData.open && examineVideoData.account ? (
497 <div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
498 <div className="relative w-4/5 overflow-hidden bg-white rounded-lg h-4/5">
499 <button
500 className="absolute flex items-center justify-center p-2 transition-all duration-300 bg-white rounded-full shadow-md top-4 right-4 hover:shadow-lg"
501 style={{
502 zIndex: 1000,
503 width: '36px',
504 height: '36px',
505 color: '#a66ae4',
506 border: '1px solid rgba(166, 106, 228, 0.2)',
507 }}
508 onClick={closeWebView}
509 >
510 <svg
511 viewBox="0 0 24 24"
512 width="20"
513 height="20"
514 stroke="currentColor"
515 strokeWidth="2"
516 fill="none"
517 strokeLinecap="round"
518 strokeLinejoin="round"
519 >
520 <line x1="18" y1="6" x2="6" y2="18"></line>
521 <line x1="6" y1="6" x2="18" y2="18"></line>
522 </svg>
523 </button>
524 <WebView
525 url={examineVideoData.url}
526 cookieParams={{
527 cookies: JSON.parse(
528 examineVideoData.account.loginCookie || '{}',
529 ),
530 }}
531 key={examineVideoData.url + examineVideoData.open}
532 />
533 </div>
534 </div>
535 ) : null}
536
537 <div className="px-6 py-6">
538 {/* 顶部标题区域 */}
539 <div className="flex items-center justify-between mb-6">
540 <h1 className="text-2xl font-bold text-gray-900">数据中心</h1>
541 <div className="flex items-center space-x-2 text-gray-600 cursor-pointer hover:text-[#a66ae4]">
542 <InfoCircleOutlined />
543 <span>数据说明</span>
544 </div>
545 </div>
546
547 {/* 总体数据概览和账户列表 */}
548 <div className="flex gap-8 mb-8">
549 {/* 总体数据概览 */}
550 <div className="w-[500px] flex-shrink-0">
551 <h2 className="mb-4 text-lg font-medium">总体数据概览</h2>
552 <div className="bg-white rounded-lg shadow-sm p-6 h-[120px] flex items-center">
553 <div className="grid w-full grid-cols-2 gap-6">
554 <div className="p-4 rounded-lg bg-gray-50">
555 <div className="flex items-center justify-between mb-2">
556 <div className="text-sm text-gray-600">账户总数</div>
557 <span className="text-lg text-gray-400">👥</span>
558 </div>
559 <div className="relative">
560 <div className="text-2xl font-semibold text-[#a66ae4]">
561 {statisticsInfo?.accountTotal?.toLocaleString() || 0}
562 </div>
563 <div className="absolute bottom-0 right-0 text-xs text-[#ccc]">
564 失效:{' '}
565 {!!statisticsInfo
566 ? (statisticsInfo.accountTotal ?? 0) -
567 (statisticsInfo.list?.length ?? 0)
568 : 0}
569 </div>
570 </div>
571 </div>
572 <div className="p-4 rounded-lg bg-gray-50">
573 <div className="flex items-center justify-between mb-2">
574 <div className="text-sm text-gray-600">粉丝总数</div>
575 <span className="text-lg text-gray-400">🌟</span>
576 </div>
577 <div className="text-2xl font-semibold text-[#a66ae4]">
578 {statisticsInfo?.fansCount?.toLocaleString() || 0}
579 </div>
580 </div>
581 </div>
582 </div>
583 </div>
584
585 {/* 账户列表 */}
586 <div className="flex-1">
587 <h2 className="mb-4 text-lg font-medium">账户列表</h2>
588 <div className="bg-white rounded-lg shadow-sm p-6 h-[120px]">
589 {!statisticsInfo?.list || statisticsInfo.list.length === 0 ? (
590 // 无数据状态 - 使用Ant Design图标
591 <div className="flex flex-col items-center justify-center h-full">
592 <QuestionCircleOutlined
593 style={{ fontSize: '32px', color: '#CCCCCC' }}
594 />
595 <div className="mt-2 text-sm text-gray-500">暂无数据</div>
596 </div>
597 ) : (
598 // 有数据状态
599 <div
600 className="flex items-center h-full gap-4 overflow-x-auto custom-scrollbar"
601 style={{ padding: '2px 10px' }}
602 >
603 {statisticsInfo?.list?.map((account) => (
604 <div
605 key={account.id}
606 className={`flex-shrink-0 flex items-center px-6 py-4 space-x-3 transition-all rounded-lg bg-gray-50 hover:shadow-sm cursor-pointer ${
607 selectedAccounts.includes(account.id)
608 ? 'ring-2 ring-[#a66ae4]'
609 : ''
610 }`}
611 onClick={() => toggleAccountSelection(account.id)}
612 >
613 <div style={{ position: 'relative' }}>
614 <img
615 className="w-12 h-12 rounded-full"
616 src={account.avatar}
617 alt=""
618 />
619 {getPlatformIcon(account.type) && (
620 <img
621 src={getPlatformIcon(account.type)}
622 alt={account.type}
623 className="w-4 h-4 ml-2"
624 style={{
625 position: 'absolute',
626 bottom: '0',
627 right: '0',
628 }}
629 />
630 )}
631 </div>
632
633 <div className="space-y-1">
634 <div className="text-base font-medium text-left text-gray-900">
635 {account.nickname}
636 </div>
637
638 <div className="flex text-sm text-left">
639 <span className="text-gray-500">
640 粉丝:{' '}
641 {(account as any).fansCount?.toLocaleString() ||
642 0}
643 </span>
644 </div>
645
646 <div
647 className="text-left text-gray-500"
648 style={{ fontSize: '12px' }}
649 >
650 ID: {account.uid}
651 </div>
652 </div>
653 </div>
654 ))}
655 </div>
656 )}
657 </div>
658 </div>
659 </div>
660
661 {/* 数据明细 */}
662 <div className="mb-8">
663 <div className="flex items-center justify-between mb-4">
664 <h2 className="text-lg font-medium">数据明细</h2>
665 <div className="flex items-center space-x-4">
666 <DatePicker.RangePicker
667 value={selectedDateRange}
668 onChange={(dates) => {
669 if (dates) {
670 setSelectedDateRange([
671 dates[0] as Dayjs,
672 dates[1] as Dayjs,
673 ]);
674 }
675 }}
676 disabledDate={disabledDate}
677 className="w-64"
678 />
679 <Button
680 type="primary"
681 icon={<SearchOutlined />}
682 onClick={() => {
683 // 清空现有数据
684 setDashboardData7([]);
685 // 重新获取所有选中账户的数据
686 getAccountStatistics7();
687 }}
688 className="bg-[#a66ae4] hover:bg-[#9559d1]"
689 >
690 搜索
691 </Button>
692 <Button
693 type="primary"
694 icon={<DownloadOutlined />}
695 onClick={handleExportData}
696 className="bg-[#a66ae4] hover:bg-[#9559d1]"
697 >
698 导出数据
699 </Button>
700 </div>
701 </div>
702
703 {/* 总体数据卡片 */}
704 <div className="grid grid-cols-6 gap-4 mb-6">
705 {[
706 { key: 'fans', title: '总粉丝数', icon: '👥' },
707 { key: 'read', title: '总播放数', icon: '▶️' },
708 { key: 'comment', title: '总评论数', icon: '💬' },
709 { key: 'like', title: '总点赞数', icon: '👍' },
710 { key: 'collect', title: '总分享数', icon: '🔄' },
711 { key: 'forward', title: '总主页访问', icon: '🏠' },
712 ].map((metric) => (
713 <div
714 key={metric.key}
715 className={`p-4 bg-white rounded-lg shadow-sm cursor-pointer transition-all ${
716 selectedMetric === metric.key ? 'ring-2 ring-[#a66ae4]' : ''
717 }`}
718 onClick={() => handleMetricSelect(metric.key)}
719 >
720 <div className="flex items-center justify-between mb-2">
721 <div className="text-sm text-gray-600">{metric.title}</div>
722 <div className="flex items-center space-x-2">
723 <div className="text-xs text-gray-400">
724 昨日:{' '}
725 {getYesterdayTotalValue(metric.key).toLocaleString()}
726 </div>
727 <div className="text-lg text-gray-400">{metric.icon}</div>
728 </div>
729 </div>
730 <div className="text-2xl font-semibold text-[#a66ae4]">
731 {getRangeTotalMetricValue(metric.key).toLocaleString()}
732 </div>
733 </div>
734 ))}
735 </div>
736
737 {/* 趋势图表 */}
738 <div className="p-6 mb-6 bg-white rounded-lg shadow-sm">
739 <div ref={chartRef} style={{ height: '400px' }} />
740 </div>
741 </div>
742
743 {/* 账户数据 */}
744 <div className="mb-8" style={{ paddingBottom: '80px' }}>
745 <div className="flex items-center justify-between mb-4">
746 <h2 className="text-lg font-medium">昨日账户数据</h2>
747 <Button
748 type="text"
749 icon={<ReloadOutlined />}
750 onClick={refreshAccountData}
751 className="flex items-center transition-colors hover:text-blue-500"
752 >
753 刷新
754 </Button>
755 </div>
756
757 {!statisticsInfo?.list || statisticsInfo.list.length === 0 ? (
758 // 无数据状态 - 使用Ant Design图标
759 <div className="flex flex-col items-center justify-center p-12 bg-white rounded-lg shadow-sm">
760 <QuestionCircleOutlined
761 style={{ fontSize: '32px', color: '#CCCCCC' }}
762 />
763 <div className="mt-2 text-sm text-gray-500">暂无数据</div>
764 </div>
765 ) : (
766 // 有数据状态
767 <div className="space-y-4">
768 {statisticsInfo?.list?.map((account) => {
769 const accountData = dashboardData.find(
770 (item: any) => item.id == account.id,
771 );
772 return (
773 <Card
774 key={account.id}
775 className="transition-shadow hover:shadow-md"
776 >
777 <div className="flex items-center">
778 {/* 账户信息 */}
779 <div className="flex items-center flex-shrink-0 w-64 space-x-3">
780 <Avatar size={48} src={account.avatar} />
781 <div>
782 <div className="font-medium">
783 {account.nickname}
784 </div>
785
786 <div className="text-sm text-gray-500">
787 ID: {account.uid}{' '}
788 </div>
789 </div>
790 </div>
791
792 {/* 数据展示 */}
793 <div className="grid flex-1 grid-cols-7 gap-4">
794 <div className="text-center">
795 <div className="text-sm text-gray-500">粉丝数</div>
796 <div className="font-medium text-[#a66ae4]">
797 {(accountData?.fans || 0) > 0 ? '+ ' : '- '}
798 {Math.abs(accountData?.fans || 0)}
799 </div>
800 </div>
801 <div className="text-center">
802 <div className="text-sm text-gray-500">阅读数</div>
803 <div className="font-medium text-[#a66ae4]">
804 {accountData?.read || 0}
805 </div>
806 </div>
807 <div className="text-center">
808 <div className="text-sm text-gray-500">评论数</div>
809 <div className="font-medium text-[#a66ae4]">
810 {accountData?.comment || 0}
811 </div>
812 </div>
813 <div className="text-center">
814 <div className="text-sm text-gray-500">点赞数</div>
815 <div className="font-medium text-[#a66ae4]">
816 {accountData?.like || 0}
817 </div>
818 </div>
819 <div className="text-center">
820 <div className="text-sm text-gray-500">分享数</div>
821 <div className="font-medium text-[#a66ae4]">
822 {accountData?.collect || 0}
823 </div>
824 </div>
825 <div className="text-center">
826 <div className="text-sm text-gray-500">
827 主页访问
828 </div>
829 <div className="font-medium text-[#a66ae4]">
830 {accountData?.forward || 0}
831 </div>
832 </div>
833 <div className="text-center">
834 <div
835 className="text-sm text-gray-500"
836 style={{
837 marginTop: '10px',
838 color: '#a66ae4',
839 cursor: 'pointer',
840 }}
841 onClick={() => examineVideo(account)}
842 >
843 查看详情
844 </div>
845 </div>
846 </div>
847 </div>
848 </Card>
849 );
850 })}
851 </div>
852 )}
853 </div>
854 </div>
855 </Spin>
856 </div>
857 );
858 };
859
860 export default Statistics;
861
861 lines Plain Text