增加登陆验证码,24小时流量统计

This commit is contained in:
qaq
2025-08-14 15:07:53 +08:00
parent 24dcd58d80
commit 6b65a7dd2d
60 changed files with 1108 additions and 148 deletions
+115
View File
@@ -3,6 +3,7 @@ import { Button } from "@heroui/button";
import { Modal, ModalContent, ModalHeader, ModalBody } from "@heroui/modal";
import { useState, useEffect } from "react";
import toast from 'react-hot-toast';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
import AdminLayout from "@/layouts/admin";
import { getUserPackageInfo } from "@/api";
@@ -48,11 +49,20 @@ interface AddressItem {
copying: boolean;
}
interface StatisticsFlow {
id: number;
userId: number;
flow: number;
totalFlow: number;
time: string;
}
export default function DashboardPage() {
const [loading, setLoading] = useState(true);
const [userInfo, setUserInfo] = useState<UserInfo>({} as UserInfo);
const [userTunnels, setUserTunnels] = useState<UserTunnel[]>([]);
const [forwardList, setForwardList] = useState<Forward[]>([]);
const [statisticsFlows, setStatisticsFlows] = useState<StatisticsFlow[]>([]);
const [addressModalOpen, setAddressModalOpen] = useState(false);
const [addressModalTitle, setAddressModalTitle] = useState('');
@@ -154,6 +164,7 @@ export default function DashboardPage() {
setUserInfo({} as UserInfo);
setUserTunnels([]);
setForwardList([]);
setStatisticsFlows([]);
loadPackageData();
localStorage.setItem('e', '/dashboard');
@@ -168,6 +179,7 @@ export default function DashboardPage() {
setUserInfo(data.userInfo || {});
setUserTunnels(data.tunnelPermissions || []);
setForwardList(data.forwards || []);
setStatisticsFlows(data.statisticsFlows || []);
// 检查有效期并显示通知
checkExpirationNotifications(data.userInfo, data.tunnelPermissions || []);
@@ -207,6 +219,33 @@ export default function DashboardPage() {
return value.toString();
};
// 处理24小时流量统计数据
const processFlowChartData = () => {
// 生成最近24小时的时间数组(从当前小时往前推24小时)
const now = new Date();
const hours: string[] = [];
for (let i = 23; i >= 0; i--) {
const time = new Date(now.getTime() - i * 60 * 60 * 1000);
const hourString = time.getHours().toString().padStart(2, '0') + ':00';
hours.push(hourString);
}
// 创建数据映射
const flowMap = new Map<string, number>();
statisticsFlows.forEach(item => {
flowMap.set(item.time, item.flow || 0);
});
// 生成图表数据,没有数据的小时显示为0
return hours.map(hour => ({
time: hour,
flow: flowMap.get(hour) || 0,
// 格式化显示用的流量值
formattedFlow: formatFlow(flowMap.get(hour) || 0)
}));
};
const getExpStatus = (expTime?: string) => {
if (!expTime) return {
color: 'text-green-600 dark:text-green-400',
@@ -636,6 +675,82 @@ export default function DashboardPage() {
</Card>
</div>
{/* 24小时流量统计图表 */}
<Card className="mb-6 lg:mb-8 border border-gray-200 dark:border-default-200 shadow-md">
<CardHeader className="pb-3">
<div className="flex items-center gap-2">
<svg className="w-5 h-5 text-primary" fill="currentColor" viewBox="0 0 20 20">
<path d="M2 10a8 8 0 018-8v8h8a8 8 0 11-16 0z" />
<path d="M12 2.252A8.014 8.014 0 0117.748 8H12V2.252z" />
</svg>
<h2 className="text-lg lg:text-xl font-semibold text-foreground">24小时流量统计</h2>
</div>
</CardHeader>
<CardBody className="pt-0">
{statisticsFlows.length === 0 ? (
<div className="text-center py-12">
<svg className="w-12 h-12 text-default-400 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
<p className="text-default-500">暂无流量统计数据</p>
</div>
) : (
<div className="space-y-4">
{/* 流量趋势图 */}
<div className="h-64 lg:h-80 w-full">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={processFlowChartData()}>
<CartesianGrid strokeDasharray="3 3" className="opacity-30" />
<XAxis
dataKey="time"
tick={{ fontSize: 12 }}
tickLine={false}
axisLine={{ stroke: '#e5e7eb', strokeWidth: 1 }}
/>
<YAxis
tick={{ fontSize: 12 }}
tickLine={false}
axisLine={{ stroke: '#e5e7eb', strokeWidth: 1 }}
tickFormatter={(value) => {
if (value === 0) return '0';
if (value < 1024) return `${value}B`;
if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)}K`;
if (value < 1024 * 1024 * 1024) return `${(value / (1024 * 1024)).toFixed(1)}M`;
return `${(value / (1024 * 1024 * 1024)).toFixed(1)}G`;
}}
/>
<Tooltip
content={({ active, payload, label }) => {
if (active && payload && payload.length) {
return (
<div className="bg-white dark:bg-default-100 border border-default-200 rounded-lg shadow-lg p-3">
<p className="font-medium text-foreground">{`时间: ${label}`}</p>
<p className="text-primary">
{`流量: ${formatFlow(payload[0]?.value as number || 0)}`}
</p>
</div>
);
}
return null;
}}
/>
<Line
type="monotone"
dataKey="flow"
stroke="#8b5cf6"
strokeWidth={3}
dot={false}
activeDot={{ r: 4, stroke: '#8b5cf6', strokeWidth: 2, fill: '#fff' }}
/>
</LineChart>
</ResponsiveContainer>
</div>
</div>
)}
</CardBody>
</Card>
{/* 隧道权限 */}
<Card className="mb-6 lg:mb-8 border border-gray-200 dark:border-default-200 shadow-md">
<CardHeader className="pb-3">