import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Card, Grid } from 'semantic-ui-react'; import { Bar, BarChart, CartesianGrid, Legend, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis, } from 'recharts'; import axios from 'axios'; import './Dashboard.css'; // 在 Dashboard 组件内添加自定义配置 const chartConfig = { lineChart: { style: { background: '#fff', borderRadius: '8px', }, line: { strokeWidth: 2, dot: false, activeDot: { r: 4 }, }, grid: { vertical: false, horizontal: true, opacity: 0.1, }, }, colors: { requests: '#4318FF', quota: '#00B5D8', tokens: '#6C63FF', }, barColors: [ '#4318FF', // 深紫色 '#00B5D8', // 青色 '#6C63FF', // 紫色 '#05CD99', // 绿色 '#FFB547', // 橙色 '#FF5E7D', // 粉色 '#41B883', // 翠绿 '#7983FF', // 淡紫 '#FF8F6B', // 珊瑚色 '#49BEFF', // 天蓝 ], }; const Dashboard = () => { const { t } = useTranslation(); const [data, setData] = useState([]); const [summaryData, setSummaryData] = useState({ todayRequests: 0, todayQuota: 0, todayTokens: 0, }); useEffect(() => { fetchDashboardData(); }, []); const fetchDashboardData = async () => { try { const response = await axios.get('/api/user/dashboard'); if (response.data.success) { const dashboardData = response.data.data || []; setData(dashboardData); calculateSummary(dashboardData); } } catch (error) { console.error('Failed to fetch dashboard data:', error); setData([]); calculateSummary([]); } }; const calculateSummary = (dashboardData) => { if (!Array.isArray(dashboardData) || dashboardData.length === 0) { setSummaryData({ todayRequests: 0, todayQuota: 0, todayTokens: 0, }); return; } const today = new Date().toISOString().split('T')[0]; const todayData = dashboardData.filter((item) => item.Day === today); const summary = { todayRequests: todayData.reduce( (sum, item) => sum + item.RequestCount, 0 ), todayQuota: todayData.reduce((sum, item) => sum + item.Quota, 0) / 1000000, todayTokens: todayData.reduce( (sum, item) => sum + item.PromptTokens + item.CompletionTokens, 0 ), }; setSummaryData(summary); }; // 处理数据以供折线图使用,补充缺失的日期 const processTimeSeriesData = () => { const dailyData = {}; // 获取日期范围 const dates = data.map((item) => item.Day); const maxDate = new Date(); // 总是使用今天作为最后一天 let minDate = dates.length > 0 ? new Date(Math.min(...dates.map((d) => new Date(d)))) : new Date(); // 确保至少显示5天的数据 const fiveDaysAgo = new Date(); fiveDaysAgo.setDate(fiveDaysAgo.getDate() - 4); // -4是因为包含今天 if (minDate > fiveDaysAgo) { minDate = fiveDaysAgo; } // 生成所有日期 for (let d = new Date(minDate); d <= maxDate; d.setDate(d.getDate() + 1)) { const dateStr = d.toISOString().split('T')[0]; dailyData[dateStr] = { date: dateStr, requests: 0, quota: 0, tokens: 0, }; } // 填充实际数据 data.forEach((item) => { dailyData[item.Day].requests += item.RequestCount; dailyData[item.Day].quota += item.Quota / 1000000; dailyData[item.Day].tokens += item.PromptTokens + item.CompletionTokens; }); return Object.values(dailyData).sort((a, b) => a.date.localeCompare(b.date) ); }; // 处理数据以供堆叠柱状图使用 const processModelData = () => { const timeData = {}; // 获取日期范围 const dates = data.map((item) => item.Day); const maxDate = new Date(); // 总是使用今天作为最后一天 let minDate = dates.length > 0 ? new Date(Math.min(...dates.map((d) => new Date(d)))) : new Date(); // 确保至少显示5天的数据 const fiveDaysAgo = new Date(); fiveDaysAgo.setDate(fiveDaysAgo.getDate() - 4); // -4是因为包含今天 if (minDate > fiveDaysAgo) { minDate = fiveDaysAgo; } // 生成所有日期 for (let d = new Date(minDate); d <= maxDate; d.setDate(d.getDate() + 1)) { const dateStr = d.toISOString().split('T')[0]; timeData[dateStr] = { date: dateStr, }; // 初始化所有模型的数据为0 const models = [...new Set(data.map((item) => item.ModelName))]; models.forEach((model) => { timeData[dateStr][model] = 0; }); } // 填充实际数据 data.forEach((item) => { timeData[item.Day][item.ModelName] = item.PromptTokens + item.CompletionTokens; }); return Object.values(timeData).sort((a, b) => a.date.localeCompare(b.date)); }; // 获取所有唯一的模型名称 const getUniqueModels = () => { return [...new Set(data.map((item) => item.ModelName))]; }; const timeSeriesData = processTimeSeriesData(); const modelData = processModelData(); const models = getUniqueModels(); // 生成随机颜色 const getRandomColor = (index) => { return chartConfig.barColors[index % chartConfig.barColors.length]; }; // 添加一个日期格式化函数 const formatDate = (dateStr) => { const date = new Date(dateStr); return date.toLocaleDateString('zh-CN', { month: 'numeric', day: 'numeric', }); }; // 修改所有 XAxis 配置 const xAxisConfig = { dataKey: 'date', axisLine: false, tickLine: false, tick: { fontSize: 12, fill: '#A3AED0', textAnchor: 'middle', // 文本居中对齐 }, tickFormatter: formatDate, interval: 0, minTickGap: 5, padding: { left: 30, right: 30 }, // 增加两侧的内边距,确保首尾标签完整显示 }; return (