290 lines
9.0 KiB
JavaScript
290 lines
9.0 KiB
JavaScript
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;
|