filesend/backend/app/uploads/7ea5876a4e8b452bbbc74dafeb54d7e0_Dashboard.js

211 lines
6.7 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 { Link } from 'react-router-dom';
import { Bar } from 'react-chartjs-2';
import { Chart, registerables } from 'chart.js';
// 注册Chart.js组件
Chart.register(...registerables);
const Dashboard = () => {
const [stats, setStats] = useState({
totalUsers: 0,
activeUsers: 0,
totalFiles: 0,
takenFiles: 0,
availableFiles: 0,
recentUploads: {},
recentDownloads: {}
});
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchDashboardData = async () => {
try {
const [userRes, fileRes] = await Promise.all([
axios.get('/api/admin/analytics/users'),
axios.get('/api/admin/analytics/files')
]);
setStats({
totalUsers: userRes.data.total_users || 0,
activeUsers: userRes.data.active_users || 0,
totalFiles: fileRes.data.total_files || 0,
takenFiles: fileRes.data.taken_files || 0,
availableFiles: fileRes.data.available_files || 0,
recentUploads: fileRes.data.upload_stats || {},
recentDownloads: fileRes.data.take_stats || {}
});
} catch (err) {
console.error('获取统计数据失败:', err);
toast.error('获取统计数据失败');
} finally {
setLoading(false);
}
};
fetchDashboardData();
}, []);
// 准备图表数据
const getChartData = () => {
// 确保数据存在且为对象
const recentUploads = stats.recentUploads || {};
const recentDownloads = stats.recentDownloads || {};
// 确保日期顺序正确
const uploadDates = Object.keys(recentUploads);
const downloadDates = Object.keys(recentDownloads);
const allDates = [...new Set([...uploadDates, ...downloadDates])].sort();
return {
labels: allDates.map(date => new Date(date).toLocaleDateString()),
datasets: [
{
label: '文件上传',
data: allDates.map(date => recentUploads[date] || 0),
backgroundColor: 'rgba(54, 162, 235, 0.5)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1
},
{
label: '文件领取',
data: allDates.map(date => recentDownloads[date] || 0),
backgroundColor: 'rgba(75, 192, 192, 0.5)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1
}
]
};
};
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>
<div className="d-flex justify-content-between align-items-center mb-4">
<h1>仪表盘</h1>
</div>
{/* 统计卡片 */}
<div className="row mb-4">
<div className="col-md-3 mb-4">
<div className="stats-card">
<div className="stats-number">{stats.totalUsers}</div>
<div className="stats-label">总用户数</div>
<Link to="/admin/users" className="btn btn-sm btn-admin-outline mt-3">
查看详情 <i className="bi bi-arrow-right ms-1"></i>
</Link>
</div>
</div>
<div className="col-md-3 mb-4">
<div className="stats-card">
<div className="stats-number">{stats.activeUsers}</div>
<div className="stats-label">活跃用户</div>
<Link to="/admin/users" className="btn btn-sm btn-admin-outline mt-3">
查看详情 <i className="bi bi-arrow-right ms-1"></i>
</Link>
</div>
</div>
<div className="col-md-3 mb-4">
<div className="stats-card">
<div className="stats-number">{stats.totalFiles}</div>
<div className="stats-label">总文件数</div>
<Link to="/admin/files" className="btn btn-sm btn-admin-outline mt-3">
查看详情 <i className="bi bi-arrow-right ms-1"></i>
</Link>
</div>
</div>
<div className="col-md-3 mb-4">
<div className="stats-card">
<div className="stats-number">{stats.availableFiles}</div>
<div className="stats-label">可领取文件</div>
<Link to="/admin/files" className="btn btn-sm btn-admin-outline mt-3">
查看详情 <i className="bi bi-arrow-right ms-1"></i>
</Link>
</div>
</div>
</div>
{/* 图表 */}
<div className="admin-card mb-4">
<div className="card-header">
<h5>近7天数据统计</h5>
</div>
<div className="card-body">
<div className="chart-container" style={{ height: '300px' }}>
<Bar
data={getChartData()}
options={{
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
labels: {
color: '#e8e9ea'
}
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
precision: 0,
color: '#b8bcc8'
},
grid: {
color: 'rgba(100, 255, 218, 0.1)'
}
},
x: {
ticks: {
color: '#b8bcc8'
},
grid: {
color: 'rgba(100, 255, 218, 0.1)'
}
}
}
}}
/>
</div>
</div>
</div>
{/* 快捷操作 */}
<div className="admin-card">
<div className="card-header">
<h5>快捷操作</h5>
</div>
<div className="card-body">
<div className="d-flex flex-wrap gap-3">
<Link to="/admin/users" className="btn btn-admin">
<i className="bi bi-people me-2"></i>
</Link>
<Link to="/admin/files/upload" className="btn btn-admin">
<i className="bi bi-upload me-2"></i>
</Link>
<Link to="/admin/files" className="btn btn-admin">
<i className="bi bi-file-earmark me-2"></i>
</Link>
<Link to="/admin/analytics" className="btn btn-admin">
<i className="bi bi-bar-chart me-2"></i>
</Link>
</div>
</div>
</div>
</div>
);
};
export default Dashboard;