refactor: frontend (#7097)

* refactor: remove unused window show/hide event listeners

* refactor: deduplicate profile-changed event handling

* refactor: remove unused setupCloseListener from use-listen

* refactor: remove unused removeAllListeners from use-listen

* fix(backend): dedupe profile changed notification on import

* fix: avoid duplicate timer update completed event

* refactor(frontend): simplify proxy switch fallback path
This commit is contained in:
Tunglies
2026-05-23 12:52:52 +08:00
committed by GitHub
parent d3bb018ab7
commit c18c23963d
8 changed files with 9 additions and 138 deletions
-8
View File
@@ -94,14 +94,6 @@ pub async fn import_profile(url: std::string::String, option: Option<PrfOption>)
handle::Handle::notify_profile_changed(uid);
}
// 异步保存配置文件并发送全局通知
if let Some(uid) = &item.uid {
// 延迟发送,确保文件已完全写入
tokio::time::sleep(Duration::from_millis(100)).await;
logging!(info, Type::Cmd, "[导入订阅] 发送配置变更通知: {}", uid);
handle::Handle::notify_profile_changed(uid);
}
logging!(info, Type::Cmd, "[导入订阅] 导入完成: {}", help::mask_url(&url));
Ok(())
}
+1 -2
View File
@@ -1,4 +1,4 @@
use crate::{config::Config, core::handle, feat, process::AsyncHandler, singleton, utils::resolve::is_resolve_done};
use crate::{config::Config, feat, process::AsyncHandler, singleton, utils::resolve::is_resolve_done};
use anyhow::Result;
use clash_verge_logging::{Type, logging, logging_error};
use parking_lot::{Mutex, RwLock};
@@ -396,7 +396,6 @@ impl Timer {
uid,
task_start.elapsed().as_millis()
);
handle::Handle::notify_profile_update_completed(uid);
}
Ok(Err(e)) => logging_error!(Type::Timer, "Failed to update profile uid {}: {}", uid, e),
Err(_) => logging_error!(Type::Timer, "Timer task timed out for uid: {}", uid),
-4
View File
@@ -1,5 +1,3 @@
use std::time::Duration;
use anyhow::Result;
use percent_encoding::percent_decode_str;
use smartstring::alias::String;
@@ -123,7 +121,6 @@ async fn fetch_profile_item(url: &str, name: Option<&String>) -> Option<PrfItem>
async fn post_import_updates(uid: &String, had_current_profile: bool) {
handle::Handle::refresh_verge();
handle::Handle::notify_profile_changed(uid);
tokio::time::sleep(Duration::from_millis(100)).await;
let should_update_core = if uid.is_empty() || had_current_profile {
false
@@ -131,7 +128,6 @@ async fn post_import_updates(uid: &String, had_current_profile: bool) {
let profiles = Config::profiles().await;
profiles.latest_arc().is_current_profile_index(uid)
};
handle::Handle::notify_profile_changed(uid);
if should_update_core {
refresh_core_config().await;
+3 -37
View File
@@ -1,49 +1,15 @@
import { event } from '@tauri-apps/api'
import { listen, UnlistenFn, EventCallback } from '@tauri-apps/api/event'
import { useCallback, useRef } from 'react'
import { listen, EventCallback } from '@tauri-apps/api/event'
import { useCallback } from 'react'
export const useListen = () => {
const unlistenFnsRef = useRef<UnlistenFn[]>([])
const addListener = useCallback(
async <T>(eventName: string, handler: EventCallback<T>) => {
const unlisten = await listen(eventName, handler)
unlistenFnsRef.current.push(unlisten)
return unlisten
return await listen(eventName, handler)
},
[],
)
const removeAllListeners = useCallback(() => {
const errors: Error[] = []
unlistenFnsRef.current.forEach((unlisten) => {
try {
unlisten()
} catch (error) {
errors.push(error instanceof Error ? error : new Error(String(error)))
}
})
if (errors.length > 0) {
console.warn(
`[useListen] 清理监听器时发生 ${errors.length} 个错误`,
errors,
)
}
unlistenFnsRef.current.length = 0
}, [])
const setupCloseListener = useCallback(async () => {
await event.once('tauri://close-requested', async () => {
removeAllListeners()
})
}, [removeAllListeners])
return {
addListener,
removeAllListeners,
setupCloseListener,
}
}
+2 -17
View File
@@ -104,29 +104,14 @@ export const useProxySelection = (options: ProxySelectionOptions = {}) => {
config.autoCloseConnection &&
previousProxy
) {
setTimeout(() => cleanupConnections(previousProxy), 0)
void cleanupConnections(previousProxy)
}
} catch (error) {
console.error(
`[ProxySelection] 代理切换失败: ${groupName} -> ${proxyName}`,
error,
)
try {
await selectNodeForGroup(groupName, proxyName)
onSuccess?.()
syncTraySelection()
persistSelection(groupName, proxyName, skipConfigSave)
debugLog(
`[ProxySelection] 代理切换回退成功: ${groupName} -> ${proxyName}`,
)
} catch (fallbackError) {
console.error(
`[ProxySelection] 代理切换回退也失败: ${groupName} -> ${proxyName}`,
fallbackError,
)
onError?.(fallbackError)
}
onError?.(error)
}
},
[config, onError, onSuccess, persistSelection, syncTraySelection],
@@ -1,5 +1,3 @@
import { listen } from '@tauri-apps/api/event'
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
import { useEffect } from 'react'
import { useListen } from '@/hooks/use-listen'
@@ -73,20 +71,6 @@ export const useLayoutEvents = (
),
)
const appWindow = getCurrentWebviewWindow()
register(
(async () => {
const [hideUnlisten, showUnlisten] = await Promise.all([
listen('verge://hide-window', () => appWindow.hide()),
listen('verge://show-window', () => appWindow.show()),
])
return () => {
hideUnlisten()
showUnlisten()
}
})(),
)
return () => {
disposed = true
const errors: Error[] = []
+1 -54
View File
@@ -22,7 +22,7 @@ import {
} from '@mui/icons-material'
import { Box, Button, Divider, Grid, IconButton, Stack } from '@mui/material'
import { useQuery } from '@tanstack/react-query'
import { listen, TauriEvent } from '@tauri-apps/api/event'
import { TauriEvent } from '@tauri-apps/api/event'
import { readText } from '@tauri-apps/plugin-clipboard-manager'
import { readTextFile } from '@tauri-apps/plugin-fs'
import { useLockFn } from 'ahooks'
@@ -742,59 +742,6 @@ const ProfilePage = () => {
? 'rgba(0, 0, 0, 0.06)'
: 'rgba(255, 255, 255, 0.06)'
// 监听后端配置变更
useEffect(() => {
let unlistenPromise: Promise<() => void> | undefined
let lastProfileId: string | null = null
let lastUpdateTime = 0
const debounceDelay = 200
let refreshTimer: number | null = null
const setupListener = async () => {
unlistenPromise = listen<string>('profile-changed', (event) => {
const newProfileId = event.payload
const now = Date.now()
debugLog(`[Profile] 收到配置变更事件: ${newProfileId}`)
if (
lastProfileId === newProfileId &&
now - lastUpdateTime < debounceDelay
) {
debugLog(`[Profile] 重复事件被防抖,跳过`)
return
}
lastProfileId = newProfileId
lastUpdateTime = now
debugLog(`[Profile] 执行配置数据刷新`)
if (refreshTimer !== null) {
window.clearTimeout(refreshTimer)
}
// 使用异步调度避免阻塞事件处理
refreshTimer = window.setTimeout(() => {
mutateProfiles().catch((error) => {
console.error('[Profile] 配置数据刷新失败:', error)
})
refreshTimer = null
}, 0)
})
}
setupListener()
return () => {
if (refreshTimer !== null) {
window.clearTimeout(refreshTimer)
}
unlistenPromise?.then((unlisten) => unlisten()).catch(console.error)
}
}, [mutateProfiles])
// 组件卸载时清理中断控制器
useEffect(() => {
return () => {
+2
View File
@@ -15,6 +15,7 @@ import {
getRunningMode,
getSystemProxy,
} from '@/services/cmds'
import { queryClient } from '@/services/query-client'
import {
ClashConfigContext,
@@ -137,6 +138,7 @@ export const AppDataProvider = ({
}
lastProfileId = newProfileId
lastUpdateTime = now
void queryClient.invalidateQueries({ queryKey: ['getProfiles'] })
refreshRules().catch(() => {})
refreshRuleProviders().catch(() => {})
}