diff --git a/src/components/home/enhanced-traffic-stats.tsx b/src/components/home/enhanced-traffic-stats.tsx
index 69795f38b..7bea75d22 100644
--- a/src/components/home/enhanced-traffic-stats.tsx
+++ b/src/components/home/enhanced-traffic-stats.tsx
@@ -147,18 +147,19 @@ export const EnhancedTrafficStats = () => {
// 是否显示流量图表
const trafficGraph = verge?.traffic_graph ?? true
+ const displayMemory = verge?.enable_memory_usage ?? true
const {
response: { data: traffic },
- } = useTrafficData({ enabled: trafficGraph && pageVisible })
+ } = useTrafficData({ enabled: pageVisible })
const {
response: { data: memory },
- } = useMemoryData()
+ } = useMemoryData({ enabled: displayMemory && pageVisible })
const {
response: { data: connectionSummary },
- } = useConnectionSummaryData()
+ } = useConnectionSummaryData({ enabled: pageVisible })
// Canvas组件现在直接从全局Hook获取数据,无需手动添加数据点
@@ -211,8 +212,8 @@ export const EnhancedTrafficStats = () => {
}, [trafficGraph, pageVisible, theme.palette.divider])
// 使用useMemo计算统计卡片配置
- const statCards = useMemo(
- () => [
+ const statCards = useMemo(() => {
+ const cards: StatCardProps[] = [
{
icon: ,
title: t('home.components.traffic.metrics.uploadSpeed'),
@@ -248,17 +249,20 @@ export const EnhancedTrafficStats = () => {
unit: parsedData.downloadTotalUnit,
color: 'primary' as const,
},
- {
+ ]
+
+ if (displayMemory) {
+ cards.push({
icon: ,
title: t('home.components.traffic.metrics.memoryUsage'),
value: parsedData.inuse,
unit: parsedData.inuseUnit,
color: 'error' as const,
- onClick: undefined,
- },
- ],
- [t, parsedData],
- )
+ })
+ }
+
+ return cards
+ }, [t, parsedData, displayMemory])
return (
{
{/* 统计卡片区域 */}
{statCards.map((card) => (
-
+
))}
diff --git a/src/components/layout/layout-traffic.tsx b/src/components/layout/layout-traffic.tsx
index ce2fa31e8..90869b246 100644
--- a/src/components/layout/layout-traffic.tsx
+++ b/src/components/layout/layout-traffic.tsx
@@ -24,16 +24,17 @@ export const LayoutTraffic = () => {
// whether hide traffic graph
const trafficGraph = verge?.traffic_graph ?? true
+ const displayMemory = verge?.enable_memory_usage ?? true
const trafficRef = useRef(null)
const pageVisible = useVisibility()
const {
response: { data: traffic },
- } = useTrafficData({ enabled: trafficGraph && pageVisible })
+ } = useTrafficData({ enabled: pageVisible })
const {
response: { data: memory },
- } = useMemoryData()
+ } = useMemoryData({ enabled: displayMemory && pageVisible })
// 监听数据变化,为图表添加数据点
useEffect(() => {
@@ -47,9 +48,6 @@ export const LayoutTraffic = () => {
}
}, [traffic])
- // 显示内存使用情况的设置
- const displayMemory = verge?.enable_memory_usage ?? true
-
// 使用parseTraffic统一处理转换,保持与首页一致的显示格式
const [up, upUnit] = parseTraffic(traffic?.up || 0)
const [down, downUnit] = parseTraffic(traffic?.down || 0)
diff --git a/src/hooks/use-connection-data.ts b/src/hooks/use-connection-data.ts
index 2140a5e03..710e158a9 100644
--- a/src/hooks/use-connection-data.ts
+++ b/src/hooks/use-connection-data.ts
@@ -35,7 +35,6 @@ export const initConnSummaryData: ConnectionSummaryData = {
let connectionData: ConnectionMonitorData = initConnData
let connectionSummary: ConnectionSummaryData = initConnSummaryData
let connectionSocket: MihomoWebSocket | null = null
-let connectionStarted = false
let connectionConnecting = false
let reconnectTimer: ReturnType | null = null
let flushTimer: ReturnType | null = null
@@ -53,6 +52,9 @@ const notifySummaryListeners = () => {
summaryListeners.forEach((listener) => listener())
}
+const hasConnectionSubscribers = () =>
+ connectionListeners.size > 0 || summaryListeners.size > 0
+
const sameMetadata = (left: ConnectionMetadata, right: ConnectionMetadata) =>
metadataValue(left.network) === metadataValue(right.network) &&
metadataValue(left.type) === metadataValue(right.type) &&
@@ -215,7 +217,7 @@ const flushPendingMessage = () => {
flushTimer = null
const messageData = pendingMessageData
pendingMessageData = null
- if (!messageData) return
+ if (!messageData || !hasConnectionSubscribers()) return
let payload: IConnections
try {
@@ -270,6 +272,7 @@ const closeConnectionSocket = async () => {
}
const scheduleReconnect = () => {
+ if (!hasConnectionSubscribers()) return
if (reconnectTimer) return
reconnectTimer = window.setTimeout(() => {
reconnectTimer = null
@@ -278,20 +281,27 @@ const scheduleReconnect = () => {
}
async function reconnectConnectionSocket() {
+ if (!hasConnectionSubscribers()) return
await closeConnectionSocket()
scheduleReconnect()
}
async function connectConnectionSocket() {
if (connectionSocket || connectionConnecting) return
+ if (!hasConnectionSubscribers()) return
clearReconnectTimer()
connectionConnecting = true
try {
const socket = await MihomoWebSocket.connect_connections()
+ if (!hasConnectionSubscribers()) {
+ await socket.close()
+ return
+ }
connectionSocket = socket
socket.addListener((message) => {
+ if (connectionSocket !== socket) return
if (message.type !== 'Text') return
if (message.data.startsWith('Websocket error')) {
void reconnectConnectionSocket()
@@ -308,27 +318,39 @@ async function connectConnectionSocket() {
}
const startConnectionMonitor = () => {
- if (connectionStarted) return
- connectionStarted = true
void connectConnectionSocket()
}
+const stopConnectionMonitorIfIdle = () => {
+ if (hasConnectionSubscribers()) return
+
+ clearReconnectTimer()
+ pendingMessageData = null
+ if (flushTimer) {
+ window.clearTimeout(flushTimer)
+ flushTimer = null
+ }
+ void closeConnectionSocket()
+}
+
const getConnectionSnapshot = () => connectionData
const getConnectionSummarySnapshot = () => connectionSummary
const subscribeConnectionData = (listener: ConnectionListener) => {
- startConnectionMonitor()
connectionListeners.add(listener)
+ startConnectionMonitor()
return () => {
connectionListeners.delete(listener)
+ stopConnectionMonitorIfIdle()
}
}
const subscribeConnectionSummary = (listener: ConnectionListener) => {
- startConnectionMonitor()
summaryListeners.add(listener)
+ startConnectionMonitor()
return () => {
summaryListeners.delete(listener)
+ stopConnectionMonitorIfIdle()
}
}
@@ -351,9 +373,15 @@ const clearClosedConnectionData = () => {
notifyConnectionListeners()
}
-export const useConnectionData = () => {
+export const useConnectionData = (options?: { enabled?: boolean }) => {
+ const enabled = options?.enabled ?? true
+ const subscribe = useCallback(
+ (listener: ConnectionListener) =>
+ enabled ? subscribeConnectionData(listener) : () => {},
+ [enabled],
+ )
const data = useSyncExternalStore(
- subscribeConnectionData,
+ subscribe,
getConnectionSnapshot,
getConnectionSnapshot,
)
@@ -372,9 +400,15 @@ export const useConnectionData = () => {
}
}
-export const useConnectionSummaryData = () => {
+export const useConnectionSummaryData = (options?: { enabled?: boolean }) => {
+ const enabled = options?.enabled ?? true
+ const subscribe = useCallback(
+ (listener: ConnectionListener) =>
+ enabled ? subscribeConnectionSummary(listener) : () => {},
+ [enabled],
+ )
const data = useSyncExternalStore(
- subscribeConnectionSummary,
+ subscribe,
getConnectionSummarySnapshot,
getConnectionSummarySnapshot,
)
diff --git a/src/hooks/use-memory-data.ts b/src/hooks/use-memory-data.ts
index e629ce9e4..db6ea6cb9 100644
--- a/src/hooks/use-memory-data.ts
+++ b/src/hooks/use-memory-data.ts
@@ -29,10 +29,12 @@ const shouldSkipDuplicateMemory = (memory: IMemoryUsageItem) => {
return false
}
-export const useMemoryData = () => {
+export const useMemoryData = (options?: { enabled?: boolean }) => {
+ const enabled = options?.enabled ?? true
+
const { response, refresh } = useMihomoWsSubscription({
storageKey: 'mihomo_memory_date',
- buildSubscriptKey: (date) => `getClashMemory-${date}`,
+ buildSubscriptKey: (date) => (enabled ? `getClashMemory-${date}` : null),
fallbackData: FALLBACK_MEMORY_USAGE,
connect: () => MihomoWebSocket.connect_memory(),
throttleMs: 500,
diff --git a/src/hooks/use-traffic-data.ts b/src/hooks/use-traffic-data.ts
index 7ec750dc7..bd48d09a7 100644
--- a/src/hooks/use-traffic-data.ts
+++ b/src/hooks/use-traffic-data.ts
@@ -33,7 +33,7 @@ export const useTrafficData = (options?: { enabled?: boolean }) => {
} = useTrafficMonitorEnhanced({ subscribe: false, enabled })
const { response, refresh } = useMihomoWsSubscription({
storageKey: 'mihomo_traffic_date',
- buildSubscriptKey: (date) => `getClashTraffic-${date}`,
+ buildSubscriptKey: (date) => (enabled ? `getClashTraffic-${date}` : null),
fallbackData: FALLBACK_TRAFFIC,
connect: () => MihomoWebSocket.connect_traffic(),
throttleMs: 200,
diff --git a/src/hooks/use-visibility.ts b/src/hooks/use-visibility.ts
index 31589c862..fc1285bfb 100644
--- a/src/hooks/use-visibility.ts
+++ b/src/hooks/use-visibility.ts
@@ -1,28 +1,68 @@
+import { TauriEvent } from '@tauri-apps/api/event'
+import { getCurrentWindow } from '@tauri-apps/api/window'
import { useEffect, useState } from 'react'
+const isDocumentVisible = () =>
+ typeof document === 'undefined' || document.visibilityState === 'visible'
+
export const useVisibility = () => {
- const [visible, setVisible] = useState(() =>
- typeof document === 'undefined'
- ? true
- : document.visibilityState === 'visible',
- )
+ const [visible, setVisible] = useState(isDocumentVisible)
useEffect(() => {
- const handleVisibilityChange = () => {
- setVisible(document.visibilityState === 'visible')
+ let mounted = true
+ let visibilityTimer: ReturnType | null = null
+ const appWindow = getCurrentWindow()
+
+ const updateVisible = async () => {
+ const windowVisible = await appWindow.isVisible().catch(() => true)
+ if (mounted) {
+ setVisible(isDocumentVisible() && windowVisible)
+ }
+ }
+
+ const updateVisibleSoon = () => {
+ if (visibilityTimer) {
+ window.clearTimeout(visibilityTimer)
+ }
+ visibilityTimer = window.setTimeout(() => {
+ visibilityTimer = null
+ void updateVisible()
+ }, 50)
+ }
+
+ const handleVisibleEvent = () => {
+ void updateVisible()
}
- const handleFocus = () => setVisible(true)
const handlePointerDown = () => setVisible(true)
- document.addEventListener('focus', handleFocus)
+ document.addEventListener('focus', handleVisibleEvent)
document.addEventListener('pointerdown', handlePointerDown)
- document.addEventListener('visibilitychange', handleVisibilityChange)
+ document.addEventListener('visibilitychange', handleVisibleEvent)
+ window.addEventListener('focus', handleVisibleEvent)
+
+ const unlistenFocusChanged = appWindow.onFocusChanged(updateVisibleSoon)
+ const unlistenCloseRequested = appWindow.listen(
+ TauriEvent.WINDOW_CLOSE_REQUESTED,
+ () => {
+ setVisible(false)
+ updateVisibleSoon()
+ },
+ )
+ void updateVisible()
return () => {
- document.removeEventListener('focus', handleFocus)
+ mounted = false
+ if (visibilityTimer) {
+ window.clearTimeout(visibilityTimer)
+ visibilityTimer = null
+ }
+ document.removeEventListener('focus', handleVisibleEvent)
document.removeEventListener('pointerdown', handlePointerDown)
- document.removeEventListener('visibilitychange', handleVisibilityChange)
+ document.removeEventListener('visibilitychange', handleVisibleEvent)
+ window.removeEventListener('focus', handleVisibleEvent)
+ void unlistenFocusChanged.then((unlisten) => unlisten())
+ void unlistenCloseRequested.then((unlisten) => unlisten())
}
}, [])
diff --git a/src/pages/connections.tsx b/src/pages/connections.tsx
index 8dd39e48a..650ee3630 100644
--- a/src/pages/connections.tsx
+++ b/src/pages/connections.tsx
@@ -40,6 +40,7 @@ import { ConnectionTable } from '@/components/connection/connection-table'
import { useConnectionData } from '@/hooks/use-connection-data'
import { useConnectionSetting } from '@/hooks/use-connection-setting'
import { useTrafficData } from '@/hooks/use-traffic-data'
+import { useVisibility } from '@/hooks/use-visibility'
import parseTraffic from '@/utils/parse-traffic'
type OrderFunc = (list: IConnectionsItem[]) => IConnectionsItem[]
@@ -80,6 +81,7 @@ const orderFunctionMap = ORDER_OPTIONS.reduce>(
const EMPTY_CONNECTIONS: IConnectionsItem[] = []
const ConnectionsPage = () => {
const { t } = useTranslation()
+ const pageVisible = useVisibility()
const [match, setMatch] = useState<(input: string) => boolean>(
() => () => true,
)
@@ -92,10 +94,10 @@ const ConnectionsPage = () => {
const {
response: { data: connections },
clearClosedConnections,
- } = useConnectionData()
+ } = useConnectionData({ enabled: pageVisible })
const {
response: { data: traffic },
- } = useTrafficData()
+ } = useTrafficData({ enabled: pageVisible })
const [setting, setSetting] = useConnectionSetting()