import { useState, useEffect, useRef, useCallback, memo, useMemo } from "react"; import { useTranslation } from "react-i18next"; import { Typography, Paper, alpha, useTheme, PaletteColor, } from "@mui/material"; import Grid from "@mui/material/Grid2"; import { ArrowUpwardRounded, ArrowDownwardRounded, MemoryRounded, LinkRounded, CloudUploadRounded, CloudDownloadRounded, } from "@mui/icons-material"; import { EnhancedTrafficGraph, EnhancedTrafficGraphRef, ITrafficItem, } from "./enhanced-traffic-graph"; import { useVisibility } from "@/hooks/use-visibility"; import { useClashInfo } from "@/hooks/use-clash"; import { useVerge } from "@/hooks/use-verge"; import { createAuthSockette } from "@/utils/websocket"; import parseTraffic from "@/utils/parse-traffic"; import { getConnections, isDebugEnabled, gc } from "@/services/api"; import { ReactNode } from "react"; import { useAppData } from "@/providers/app-data-provider"; interface MemoryUsage { inuse: number; oslimit?: number; } interface TrafficStatData { uploadTotal: number; downloadTotal: number; activeConnections: number; } interface StatCardProps { icon: ReactNode; title: string; value: string | number; unit: string; color: "primary" | "secondary" | "error" | "warning" | "info" | "success"; onClick?: () => void; } // 全局变量类型定义 declare global { interface Window { animationFrameId?: number; lastTrafficData?: { up: number; down: number; }; } } // 控制更新频率 const CONNECTIONS_UPDATE_INTERVAL = 5000; // 5秒更新一次连接数据 const THROTTLE_TRAFFIC_UPDATE = 500; // 500ms节流流量数据更新 // 统计卡片组件 - 使用memo优化 const CompactStatCard = memo(({ icon, title, value, unit, color, onClick, }: StatCardProps) => { const theme = useTheme(); // 获取调色板颜色 - 使用useMemo避免重复计算 const colorValue = useMemo(() => { const palette = theme.palette; if ( color in palette && palette[color as keyof typeof palette] && "main" in (palette[color as keyof typeof palette] as PaletteColor) ) { return (palette[color as keyof typeof palette] as PaletteColor).main; } return palette.primary.main; }, [theme.palette, color]); return ( {/* 图标容器 */} {icon} {/* 文本内容 */} {title} {value} {unit} ); }); // 添加显示名称 CompactStatCard.displayName = "CompactStatCard"; export const EnhancedTrafficStats = () => { const { t } = useTranslation(); const theme = useTheme(); const { clashInfo } = useClashInfo(); const { verge } = useVerge(); const trafficRef = useRef(null); const pageVisible = useVisibility(); const [isDebug, setIsDebug] = useState(false); // 使用AppDataProvider const { connections, uptime } = useAppData(); // 使用单一状态对象减少状态更新次数 const [stats, setStats] = useState({ traffic: { up: 0, down: 0 }, memory: { inuse: 0, oslimit: undefined as number | undefined }, }); // 创建一个标记来追踪最后更新时间,用于节流 const lastUpdateRef = useRef({ traffic: 0 }); // 是否显示流量图表 const trafficGraph = verge?.traffic_graph ?? true; // WebSocket引用 const socketRefs = useRef({ traffic: null as ReturnType | null, memory: null as ReturnType | null, }); // 检查是否支持调试 useEffect(() => { isDebugEnabled().then((flag) => setIsDebug(flag)); }, []); // 处理流量数据更新 - 使用节流控制更新频率 const handleTrafficUpdate = useCallback((event: MessageEvent) => { try { const data = JSON.parse(event.data) as ITrafficItem; if ( data && typeof data.up === "number" && typeof data.down === "number" ) { // 使用节流控制更新频率 const now = Date.now(); if (now - lastUpdateRef.current.traffic < THROTTLE_TRAFFIC_UPDATE) { // 如果距离上次更新时间小于阈值,只更新图表不更新状态 if (trafficRef.current) { trafficRef.current.appendData({ up: data.up, down: data.down, timestamp: now, }); } return; } // 更新最后更新时间 lastUpdateRef.current.traffic = now; // 验证数据有效性,防止NaN const safeUp = isNaN(data.up) ? 0 : data.up; const safeDown = isNaN(data.down) ? 0 : data.down; // 批量更新状态 setStats(prev => ({ ...prev, traffic: { up: safeUp, down: safeDown } })); // 更新图表数据 if (trafficRef.current) { trafficRef.current.appendData({ up: safeUp, down: safeDown, timestamp: now, }); } } } catch (err) { console.error("[Traffic] 解析数据错误:", err); } }, []); // 处理内存数据更新 const handleMemoryUpdate = useCallback((event: MessageEvent) => { try { const data = JSON.parse(event.data) as MemoryUsage; if (data && typeof data.inuse === "number") { setStats(prev => ({ ...prev, memory: { inuse: isNaN(data.inuse) ? 0 : data.inuse, oslimit: data.oslimit, } })); } } catch (err) { console.error("[Memory] 解析数据错误:", err); } }, []); // 使用 WebSocket 连接获取数据 - 合并流量和内存连接逻辑 useEffect(() => { if (!clashInfo || !pageVisible) return; const { server, secret = "" } = clashInfo; if (!server) return; // 清理现有连接的函数 const cleanupSockets = () => { Object.values(socketRefs.current).forEach(socket => { if (socket) { socket.close(); } }); socketRefs.current = { traffic: null, memory: null }; }; // 关闭现有连接 cleanupSockets(); // 创建新连接 socketRefs.current.traffic = createAuthSockette(`${server}/traffic`, secret, { onmessage: handleTrafficUpdate, }); socketRefs.current.memory = createAuthSockette(`${server}/memory`, secret, { onmessage: handleMemoryUpdate, }); return cleanupSockets; }, [clashInfo, pageVisible, handleTrafficUpdate, handleMemoryUpdate]); // 执行垃圾回收 const handleGarbageCollection = useCallback(async () => { if (isDebug) { try { await gc(); console.log("[Debug] 垃圾回收已执行"); } catch (err) { console.error("[Debug] 垃圾回收失败:", err); } } }, [isDebug]); // 使用useMemo计算解析后的流量数据 const parsedData = useMemo(() => { const [up, upUnit] = parseTraffic(stats.traffic.up); const [down, downUnit] = parseTraffic(stats.traffic.down); const [inuse, inuseUnit] = parseTraffic(stats.memory.inuse); const [uploadTotal, uploadTotalUnit] = parseTraffic(connections.uploadTotal); const [downloadTotal, downloadTotalUnit] = parseTraffic(connections.downloadTotal); return { up, upUnit, down, downUnit, inuse, inuseUnit, uploadTotal, uploadTotalUnit, downloadTotal, downloadTotalUnit }; }, [stats, connections.uploadTotal, connections.downloadTotal]); // 渲染流量图表 - 使用useMemo缓存渲染结果 const trafficGraphComponent = useMemo(() => { if (!trafficGraph || !pageVisible) return null; return ( trafficRef.current?.toggleStyle()} >
{isDebug && (
DEBUG: {!!trafficRef.current ? "图表已初始化" : "图表未初始化"}
{new Date().toISOString().slice(11, 19)}
)}
); }, [trafficGraph, pageVisible, theme.palette.divider, isDebug]); // 使用useMemo计算统计卡片配置 const statCards = useMemo(() => [ { icon: , title: t("Upload Speed"), value: parsedData.up, unit: `${parsedData.upUnit}/s`, color: "secondary" as const, }, { icon: , title: t("Download Speed"), value: parsedData.down, unit: `${parsedData.downUnit}/s`, color: "primary" as const, }, { icon: , title: t("Active Connections"), value: connections.count, unit: "", color: "success" as const, }, { icon: , title: t("Uploaded"), value: parsedData.uploadTotal, unit: parsedData.uploadTotalUnit, color: "secondary" as const, }, { icon: , title: t("Downloaded"), value: parsedData.downloadTotal, unit: parsedData.downloadTotalUnit, color: "primary" as const, }, { icon: , title: t("Memory Usage"), value: parsedData.inuse, unit: parsedData.inuseUnit, color: "error" as const, onClick: isDebug ? handleGarbageCollection : undefined, }, ], [t, parsedData, connections.count, isDebug, handleGarbageCollection]); return ( {trafficGraph && ( {/* 流量图表区域 */} {trafficGraphComponent} )} {/* 统计卡片区域 */} {statCards.map((card, index) => ( ))} ); };