filesend/backend/app/uploads/1147dd69b0644354a77a6b3d76630012_Analytics.js

290 lines
9.0 KiB
JavaScript
Raw Permalink Normal View History

2025-10-10 17:25:29 +08:00
import React, { useState, useEffect } from 'react';
import axios from '../../services/axios'
import { toast } from 'react-toastify';
import { Card, Tabs, Tab } from 'react-bootstrap';
import { Bar, Pie } from 'react-chartjs-2';
import { Chart, registerables } from 'chart.js';
// 注册Chart.js组件
Chart.register(...registerables);
const Analytics = () => {
const [fileStats, setFileStats] = useState(null);
const [userStats, setUserStats] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchAnalyticsData = async () => {
try {
setLoading(true);
// 获取文件统计
const fileRes = await axios.get('/api/admin/analytics/files');
// 获取用户统计
const userRes = await axios.get('/api/admin/analytics/users');
setFileStats(fileRes.data);
setUserStats(userRes.data);
} catch (err) {
toast.error('获取分析数据失败');
console.error(err);
} finally {
setLoading(false);
}
};
fetchAnalyticsData();
}, []);
// 准备文件统计图表数据
const getFileChartData = () => {
if (!fileStats || !fileStats.total_files) return null;
return {
labels: ['总文件数', '已领取', '可领取'],
datasets: [{
label: '文件数量',
data: [
fileStats.total_files || 0,
fileStats.taken_files || 0,
fileStats.available_files || 0
],
backgroundColor: [
'rgba(54, 162, 235, 0.7)',
'rgba(75, 192, 192, 0.7)',
'rgba(255, 99, 132, 0.7)'
],
borderColor: [
'rgba(54, 162, 235, 1)',
'rgba(75, 192, 192, 1)',
'rgba(255, 99, 132, 1)'
],
borderWidth: 1
}]
};
};
// 准备用户统计图表数据
const getUserChartData = () => {
if (!userStats || !userStats.total_users) return null;
return {
labels: ['总用户数', '活跃用户'],
datasets: [{
label: '用户数量',
data: [
userStats.total_users || 0,
userStats.active_users || 0
],
backgroundColor: [
'rgba(153, 102, 255, 0.7)',
'rgba(255, 159, 64, 0.7)'
],
borderColor: [
'rgba(153, 102, 255, 1)',
'rgba(255, 159, 64, 1)'
],
borderWidth: 1
}]
};
};
// 准备趋势图表数据
const getTrendChartData = () => {
if (!fileStats || !userStats) return null;
// 使用upload_stats和take_stats作为数据源
const uploadData = fileStats.upload_stats || {};
const takeData = fileStats.take_stats || {};
// 合并日期并排序
const allDates = new Set([
...Object.keys(uploadData),
...Object.keys(takeData)
]);
const sortedDates = Array.from(allDates).sort();
return {
labels: sortedDates.map(date => new Date(date).toLocaleDateString()),
datasets: [
{
label: '文件上传',
data: sortedDates.map(date => uploadData[date] || 0),
backgroundColor: 'rgba(54, 162, 235, 0.5)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1,
yAxisID: 'y'
},
{
label: '文件领取',
data: sortedDates.map(date => takeData[date] || 0),
backgroundColor: 'rgba(75, 192, 192, 0.5)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1,
yAxisID: 'y1'
}
]
};
};
if (loading) {
return (
<div className="d-flex justify-content-center align-items-center vh-100">
<div className="spinner-border text-primary" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
);
}
return (
<div>
<h1 className="mb-4">数据分析</h1>
<Tabs defaultActiveKey="overview" className="mb-4">
<Tab eventKey="overview" title="概览">
<div className="row">
{/* 文件统计卡片 */}
<div className="col-md-6 mb-4">
<Card>
<Card.Header>
<Card.Title>文件统计</Card.Title>
</Card.Header>
<Card.Body>
<div className="row mb-4">
<div className="col-md-4">
<div className="text-center p-3 border rounded">
<h6>总文件数</h6>
<h3>{fileStats?.total_files || 0}</h3>
</div>
</div>
<div className="col-md-4">
<div className="text-center p-3 border rounded">
<h6>已领取</h6>
<h3>{fileStats?.taken_files || 0}</h3>
</div>
</div>
<div className="col-md-4">
<div className="text-center p-3 border rounded">
<h6>可领取</h6>
<h3>{fileStats?.available_files || 0}</h3>
</div>
</div>
</div>
<div className="chart-container" style={{ height: '300px' }}>
{getFileChartData() && (
<Pie
data={getFileChartData()}
options={{
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom'
}
}
}}
/>
)}
</div>
</Card.Body>
</Card>
</div>
{/* 用户统计卡片 */}
<div className="col-md-6 mb-4">
<Card>
<Card.Header>
<Card.Title>用户统计</Card.Title>
</Card.Header>
<Card.Body>
<div className="row mb-4">
<div className="col-md-6">
<div className="text-center p-3 border rounded">
<h6>总用户数</h6>
<h3>{userStats?.total_users || 0}</h3>
</div>
</div>
<div className="col-md-6">
<div className="text-center p-3 border rounded">
<h6>活跃用户</h6>
<h3>{userStats?.active_users || 0}</h3>
</div>
</div>
</div>
<div className="chart-container" style={{ height: '300px' }}>
{getUserChartData() && (
<Pie
data={getUserChartData()}
options={{
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom'
}
}
}}
/>
)}
</div>
</Card.Body>
</Card>
</div>
</div>
</Tab>
<Tab eventKey="trends" title="趋势分析">
<Card>
<Card.Header>
<Card.Title>近7天文件上传与领取趋势</Card.Title>
</Card.Header>
<Card.Body>
<div className="chart-container" style={{ height: '400px' }}>
{getTrendChartData() && (
<Bar
data={getTrendChartData()}
options={{
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
type: 'linear',
display: true,
position: 'left',
title: {
display: true,
text: '文件数量'
}
},
y1: {
type: 'linear',
display: true,
position: 'right',
grid: {
drawOnChartArea: false,
},
title: {
display: true,
text: '文件数量'
}
},
},
plugins: {
legend: {
position: 'top'
}
}
}}
/>
)}
</div>
</Card.Body>
</Card>
</Tab>
</Tabs>
</div>
);
};
export default Analytics;