import { useState, useEffect, useRef, useCallback, memo, useMemo } from "react"; import { useTranslation } from "react-i18next"; import { Typography, Paper, alpha, useTheme, PaletteColor, Grid, } from "@mui/material"; 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 { 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: ReturnType | null; memory: ReturnType | null; }>({ traffic: null, memory: 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) { try { trafficRef.current?.appendData({ up: data.up, down: data.down, timestamp: now, }); } catch {} return; } lastUpdateRef.current.traffic = now; const safeUp = isNaN(data.up) ? 0 : data.up; const safeDown = isNaN(data.down) ? 0 : data.down; try { setStats((prev) => ({ ...prev, traffic: { up: safeUp, down: safeDown }, })); } catch {} try { trafficRef.current?.appendData({ up: safeUp, down: safeDown, timestamp: now, }); } catch {} } } catch (err) { console.error("[Traffic] 解析数据错误:", err, event.data); } }, []); // 处理内存数据更新 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, event.data); } }, []); // 使用 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(); // 创建新连接 console.log( `[Traffic][${EnhancedTrafficStats.name}] 正在连接: ${server}/traffic`, ); socketRefs.current.traffic = createAuthSockette( `${server}/traffic`, secret, { onmessage: handleTrafficUpdate, onopen: (event) => { console.log( `[Traffic][${EnhancedTrafficStats.name}] WebSocket 连接已建立`, event, ); }, onerror: (event) => { console.error( `[Traffic][${EnhancedTrafficStats.name}] WebSocket 连接错误或达到最大重试次数`, event, ); setStats((prev) => ({ ...prev, traffic: { up: 0, down: 0 } })); }, onclose: (event) => { console.log( `[Traffic][${EnhancedTrafficStats.name}] WebSocket 连接关闭`, event.code, event.reason, ); if (event.code !== 1000 && event.code !== 1001) { console.warn( `[Traffic][${EnhancedTrafficStats.name}] 连接非正常关闭,重置状态`, ); setStats((prev) => ({ ...prev, traffic: { up: 0, down: 0 } })); } }, }, ); console.log( `[Memory][${EnhancedTrafficStats.name}] 正在连接: ${server}/memory`, ); socketRefs.current.memory = createAuthSockette(`${server}/memory`, secret, { onmessage: handleMemoryUpdate, onopen: (event) => { console.log( `[Memory][${EnhancedTrafficStats.name}] WebSocket 连接已建立`, event, ); }, onerror: (event) => { console.error( `[Memory][${EnhancedTrafficStats.name}] WebSocket 连接错误或达到最大重试次数`, event, ); setStats((prev) => ({ ...prev, memory: { inuse: 0, oslimit: undefined }, })); }, onclose: (event) => { console.log( `[Memory][${EnhancedTrafficStats.name}] WebSocket 连接关闭`, event.code, event.reason, ); if (event.code !== 1000 && event.code !== 1001) { console.warn( `[Memory][${EnhancedTrafficStats.name}] 连接非正常关闭,重置状态`, ); setStats((prev) => ({ ...prev, memory: { inuse: 0, oslimit: undefined }, })); } }, }); return cleanupSockets; }, [clashInfo, pageVisible, handleTrafficUpdate, handleMemoryUpdate]); // 组件卸载时清理所有定时器/引用 useEffect(() => { return () => { try { Object.values(socketRefs.current).forEach((socket) => { if (socket) socket.close(); }); socketRefs.current = { traffic: null, memory: null }; } catch {} }; }, []); // 执行垃圾回收 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, connectionsCount: connections.count, }; }, [stats, connections]); // 渲染流量图表 - 使用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: parsedData.connectionsCount, 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, isDebug, handleGarbageCollection], ); return ( {trafficGraph && ( {/* 流量图表区域 */} {trafficGraphComponent} )} {/* 统计卡片区域 */} {statCards.map((card, index) => ( ))} ); };