mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-04-13 05:20:28 +08:00
Compare commits
3 Commits
b8fbabae04
...
437fef1c30
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
437fef1c30 | ||
|
|
ec82b69786 | ||
|
|
04ce3d1772 |
@ -1,8 +1,6 @@
|
||||
use super::CmdResult;
|
||||
use crate::core::{autostart, handle};
|
||||
use crate::utils::resolve::ui::{self, UiReadyStage};
|
||||
use crate::core::autostart;
|
||||
use crate::{cmd::StringifyErr as _, feat, utils::dirs};
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use smartstring::alias::String;
|
||||
use tauri::{AppHandle, Manager as _};
|
||||
|
||||
@ -109,22 +107,3 @@ pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String>
|
||||
pub async fn copy_icon_file(path: String, icon_info: feat::IconInfo) -> CmdResult<String> {
|
||||
feat::copy_icon_file(path, icon_info).await
|
||||
}
|
||||
|
||||
/// 通知UI已准备就绪
|
||||
#[tauri::command]
|
||||
pub async fn notify_ui_ready() {
|
||||
logging!(info, Type::Cmd, "前端UI已准备就绪");
|
||||
ui::mark_ui_ready();
|
||||
|
||||
handle::Handle::refresh_clash();
|
||||
let delayed_refresh_delay = std::time::Duration::from_millis(1500);
|
||||
tokio::time::sleep(delayed_refresh_delay).await;
|
||||
handle::Handle::refresh_clash();
|
||||
}
|
||||
|
||||
/// UI加载阶段
|
||||
#[tauri::command]
|
||||
pub fn update_ui_stage(stage: UiReadyStage) {
|
||||
logging!(info, Type::Cmd, "UI加载阶段更新: {:?}", &stage);
|
||||
ui::update_ui_ready_stage(stage);
|
||||
}
|
||||
|
||||
@ -149,8 +149,6 @@ mod app_init {
|
||||
cmd::start_core,
|
||||
cmd::stop_core,
|
||||
cmd::restart_core,
|
||||
cmd::notify_ui_ready,
|
||||
cmd::update_ui_stage,
|
||||
cmd::get_running_mode,
|
||||
cmd::get_auto_launch_status,
|
||||
cmd::entry_lightweight_mode,
|
||||
|
||||
@ -6,6 +6,7 @@ use crate::{
|
||||
config::Config,
|
||||
core::{
|
||||
CoreManager, Timer,
|
||||
handle::Handle,
|
||||
hotkey::Hotkey,
|
||||
logger::Logger,
|
||||
service::{SERVICE_MANAGER, ServiceManager, is_service_ipc_path_exists},
|
||||
@ -22,7 +23,6 @@ use clash_verge_signal;
|
||||
|
||||
pub mod dns;
|
||||
pub mod scheme;
|
||||
pub mod ui;
|
||||
pub mod window;
|
||||
pub mod window_script;
|
||||
|
||||
@ -77,6 +77,7 @@ pub fn resolve_setup_async() {
|
||||
init_silent_updater(),
|
||||
);
|
||||
|
||||
Handle::refresh_clash();
|
||||
refresh_tray_menu().await;
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,57 +0,0 @@
|
||||
use once_cell::sync::OnceCell;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, AtomicU8, Ordering},
|
||||
};
|
||||
use tokio::sync::Notify;
|
||||
|
||||
use clash_verge_logging::{Type, logging};
|
||||
|
||||
// 获取 UI 是否准备就绪的全局状态
|
||||
static UI_READY: AtomicBool = AtomicBool::new(false);
|
||||
// 获取UI就绪状态细节
|
||||
static UI_READY_STATE: AtomicU8 = AtomicU8::new(0);
|
||||
// 添加通知机制,用于事件驱动的 UI 就绪检测
|
||||
static UI_READY_NOTIFY: OnceCell<Arc<Notify>> = OnceCell::new();
|
||||
|
||||
// UI就绪阶段状态枚举
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub enum UiReadyStage {
|
||||
NotStarted = 0,
|
||||
Loading,
|
||||
DomReady,
|
||||
ResourcesLoaded,
|
||||
Ready,
|
||||
}
|
||||
|
||||
pub fn get_ui_ready() -> &'static AtomicBool {
|
||||
&UI_READY
|
||||
}
|
||||
|
||||
fn get_ui_ready_state() -> &'static AtomicU8 {
|
||||
&UI_READY_STATE
|
||||
}
|
||||
|
||||
fn get_ui_ready_notify() -> &'static Arc<Notify> {
|
||||
UI_READY_NOTIFY.get_or_init(|| Arc::new(Notify::new()))
|
||||
}
|
||||
|
||||
// 更新UI准备阶段
|
||||
pub fn update_ui_ready_stage(stage: UiReadyStage) {
|
||||
get_ui_ready_state().store(stage as u8, Ordering::Release);
|
||||
// 如果是最终阶段,标记UI完全就绪
|
||||
if stage == UiReadyStage::Ready {
|
||||
mark_ui_ready();
|
||||
}
|
||||
}
|
||||
|
||||
// 标记UI已准备就绪
|
||||
pub fn mark_ui_ready() {
|
||||
get_ui_ready().store(true, Ordering::Release);
|
||||
logging!(info, Type::Window, "UI已标记为完全就绪");
|
||||
|
||||
// 通知所有等待的任务
|
||||
get_ui_ready_notify().notify_waiters();
|
||||
}
|
||||
@ -2,11 +2,7 @@ use dark_light::{Mode as SystemTheme, detect as detect_system_theme};
|
||||
use tauri::utils::config::Color;
|
||||
use tauri::{Theme, WebviewWindow};
|
||||
|
||||
use crate::{
|
||||
config::Config,
|
||||
core::handle,
|
||||
utils::resolve::window_script::{INITIAL_LOADING_OVERLAY, build_window_initial_script},
|
||||
};
|
||||
use crate::{config::Config, core::handle, utils::resolve::window_script::build_window_initial_script};
|
||||
use clash_verge_logging::{Type, logging_error};
|
||||
|
||||
const DARK_BACKGROUND_COLOR: Color = Color(46, 48, 61, 255); // #2E303D
|
||||
@ -82,7 +78,6 @@ pub async fn build_new_window() -> Result<WebviewWindow, String> {
|
||||
match builder.build() {
|
||||
Ok(window) => {
|
||||
logging_error!(Type::Window, window.set_background_color(Some(background_color)));
|
||||
logging_error!(Type::Window, window.eval(INITIAL_LOADING_OVERLAY));
|
||||
Ok(window)
|
||||
}
|
||||
Err(e) => Err(e.to_string()),
|
||||
|
||||
@ -91,11 +91,3 @@ pub const WINDOW_INITIAL_SCRIPT: &str = r##"
|
||||
|
||||
console.log('[Tauri] 窗口初始化脚本执行完成');
|
||||
"##;
|
||||
|
||||
pub const INITIAL_LOADING_OVERLAY: &str = r"
|
||||
const overlay = document.getElementById('initial-loading-overlay');
|
||||
if (overlay) {
|
||||
overlay.style.opacity = '0';
|
||||
setTimeout(() => overlay.remove(), 300);
|
||||
}
|
||||
";
|
||||
|
||||
@ -285,6 +285,7 @@ impl WindowManager {
|
||||
}
|
||||
|
||||
/// 创建新窗口,防抖避免重复调用
|
||||
/// 窗口创建后保持隐藏,由前端 index.html 在 overlay 渲染后调用 show,避免主题闪烁
|
||||
pub fn create_window(is_show: bool) -> Pin<Box<dyn Future<Output = bool> + Send>> {
|
||||
Box::pin(async move {
|
||||
logging!(info, Type::Window, "开始创建/显示主窗口, is_show={}", is_show);
|
||||
@ -293,23 +294,22 @@ impl WindowManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
let window = match build_new_window().await {
|
||||
Ok(window) => {
|
||||
logging!(info, Type::Window, "新窗口创建成功");
|
||||
window
|
||||
match build_new_window().await {
|
||||
Ok(_) => {
|
||||
logging!(info, Type::Window, "新窗口创建成功,等待前端渲染后显示");
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
handle::Handle::global().set_activation_policy_regular();
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
logging!(error, Type::Window, "新窗口创建失败: {}", e);
|
||||
return false;
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
// 直接激活刚创建的窗口,避免因防抖导致首次显示被跳过
|
||||
if WindowOperationResult::Failed == Self::activate_window(&window) {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -41,7 +41,7 @@ const MODE_META: Record<
|
||||
export const ClashModeCard = () => {
|
||||
const { t } = useTranslation()
|
||||
const { verge } = useVerge()
|
||||
const { clashConfig, refreshClashConfig } = useAppData()
|
||||
const { clashConfig, isCoreDataPending, refreshClashConfig } = useAppData()
|
||||
|
||||
// 支持的模式列表
|
||||
const modeList = CLASH_MODES
|
||||
@ -57,8 +57,11 @@ export const ClashModeCard = () => {
|
||||
if (currentModeKey) {
|
||||
return t(MODE_META[currentModeKey].description)
|
||||
}
|
||||
if (isCoreDataPending) {
|
||||
return '\u00A0'
|
||||
}
|
||||
return t('home.components.clashMode.errors.communication')
|
||||
}, [currentModeKey, t])
|
||||
}, [currentModeKey, isCoreDataPending, t])
|
||||
|
||||
// 模式图标映射
|
||||
const modeIcons = useMemo(
|
||||
|
||||
@ -105,7 +105,8 @@ export const CurrentProxyCard = () => {
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const theme = useTheme()
|
||||
const { proxies, clashConfig, refreshProxy, rules } = useAppData()
|
||||
const { proxies, clashConfig, isCoreDataPending, refreshProxy, rules } =
|
||||
useAppData()
|
||||
const { verge } = useVerge()
|
||||
const { current: currentProfile } = useProfiles()
|
||||
const autoDelayEnabled = verge?.enable_auto_delay_detection ?? false
|
||||
@ -911,7 +912,9 @@ export const CurrentProxyCard = () => {
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
{currentProxy ? (
|
||||
{isCoreDataPending ? (
|
||||
<Box sx={{ py: 4 }} />
|
||||
) : currentProxy ? (
|
||||
<Box>
|
||||
{/* 代理节点信息显示 */}
|
||||
<Box
|
||||
|
||||
@ -11,27 +11,15 @@
|
||||
<title>Clash Verge</title>
|
||||
<style>
|
||||
:root {
|
||||
--initial-bg: #f5f5f5;
|
||||
--initial-text: #333;
|
||||
--initial-spinner-track: #e3e3e3;
|
||||
--initial-spinner-top: #3498db;
|
||||
--bg-color: var(--initial-bg);
|
||||
--text-color: var(--initial-text);
|
||||
--spinner-track: var(--initial-spinner-track);
|
||||
--spinner-top: var(--initial-spinner-top);
|
||||
--bg-color: #f5f5f5;
|
||||
--text-color: #333;
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--initial-bg: #2e303d;
|
||||
--initial-text: #ffffff;
|
||||
--initial-spinner-track: #3a3a3a;
|
||||
--initial-spinner-top: #0a84ff;
|
||||
--bg-color: var(--initial-bg);
|
||||
--text-color: var(--initial-text);
|
||||
--spinner-track: var(--initial-spinner-track);
|
||||
--spinner-top: var(--initial-spinner-top);
|
||||
--bg-color: #2e303d;
|
||||
--text-color: #ffffff;
|
||||
color-scheme: dark;
|
||||
}
|
||||
}
|
||||
@ -53,48 +41,28 @@
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
transition: opacity 0.3s ease;
|
||||
transition: opacity 0.2s ease-out;
|
||||
}
|
||||
|
||||
#initial-loading-overlay[data-hidden='true'] {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.initial-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid var(--spinner-track);
|
||||
border-top: 3px solid var(--spinner-top);
|
||||
border-radius: 50%;
|
||||
animation: initial-spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes initial-spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="initial-loading-overlay">
|
||||
<div class="initial-spinner"></div>
|
||||
<div style="font-size: 14px; opacity: 0.7; margin-top: 20px">
|
||||
Loading Clash Verge...
|
||||
</div>
|
||||
</div>
|
||||
<div id="initial-loading-overlay"></div>
|
||||
<script>
|
||||
if (window.__TAURI_INTERNALS__) {
|
||||
window.__TAURI_INTERNALS__
|
||||
.invoke('plugin:window|show', { label: 'main' })
|
||||
.catch(function () {});
|
||||
window.__TAURI_INTERNALS__
|
||||
.invoke('plugin:window|set_focus', { label: 'main' })
|
||||
.catch(function () {});
|
||||
}
|
||||
</script>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="./main.tsx"></script>
|
||||
</body>
|
||||
|
||||
@ -44,7 +44,6 @@ import { useThemeMode } from '@/services/states'
|
||||
import getSystem from '@/utils/get-system'
|
||||
|
||||
import {
|
||||
useAppInitialization,
|
||||
useCustomTheme,
|
||||
useLayoutEvents,
|
||||
useLoadingOverlay,
|
||||
@ -217,7 +216,6 @@ const Layout = () => {
|
||||
)
|
||||
|
||||
useLoadingOverlay(themeReady)
|
||||
useAppInitialization()
|
||||
|
||||
const handleNotice = useCallback(
|
||||
(payload: [string, string]) => {
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
export { useAppInitialization } from './use-app-initialization'
|
||||
export { useLayoutEvents } from './use-layout-events'
|
||||
export { useLoadingOverlay } from './use-loading-overlay'
|
||||
export { useNavMenuOrder } from './use-nav-menu-order'
|
||||
|
||||
@ -1,112 +0,0 @@
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { useEffect, useRef } from 'react'
|
||||
|
||||
import { hideInitialOverlay } from '../utils'
|
||||
|
||||
export const useAppInitialization = () => {
|
||||
const initRef = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (initRef.current) return
|
||||
initRef.current = true
|
||||
|
||||
let isInitialized = false
|
||||
let isCancelled = false
|
||||
const timers = new Set<number>()
|
||||
|
||||
const scheduleTimeout = (handler: () => void, delay: number) => {
|
||||
if (isCancelled) return -1
|
||||
const id = window.setTimeout(() => {
|
||||
if (!isCancelled) {
|
||||
handler()
|
||||
}
|
||||
timers.delete(id)
|
||||
}, delay)
|
||||
timers.add(id)
|
||||
return id
|
||||
}
|
||||
|
||||
const notifyBackend = async (stage?: string) => {
|
||||
if (isCancelled) return
|
||||
|
||||
try {
|
||||
if (stage) {
|
||||
await invoke('update_ui_stage', { stage })
|
||||
} else {
|
||||
await invoke('notify_ui_ready')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`[Initialization] Failed to notify backend:`, err)
|
||||
}
|
||||
}
|
||||
|
||||
const removeLoadingOverlay = () => {
|
||||
hideInitialOverlay({ schedule: scheduleTimeout })
|
||||
}
|
||||
|
||||
const performInitialization = async () => {
|
||||
if (isCancelled || isInitialized) return
|
||||
isInitialized = true
|
||||
|
||||
try {
|
||||
removeLoadingOverlay()
|
||||
await notifyBackend('Loading')
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
const check = () => {
|
||||
const root = document.getElementById('root')
|
||||
if (root && root.children.length > 0) {
|
||||
resolve()
|
||||
} else {
|
||||
scheduleTimeout(check, 50)
|
||||
}
|
||||
}
|
||||
check()
|
||||
scheduleTimeout(resolve, 2000)
|
||||
})
|
||||
|
||||
await notifyBackend('DomReady')
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve))
|
||||
await notifyBackend('ResourcesLoaded')
|
||||
await notifyBackend()
|
||||
} catch (error) {
|
||||
if (!isCancelled) {
|
||||
console.error('[Initialization] Failed:', error)
|
||||
removeLoadingOverlay()
|
||||
notifyBackend().catch(console.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const checkBackendReady = async () => {
|
||||
try {
|
||||
if (isCancelled) return
|
||||
|
||||
await invoke('update_ui_stage', { stage: 'Loading' })
|
||||
performInitialization()
|
||||
} catch {
|
||||
scheduleTimeout(performInitialization, 1500)
|
||||
}
|
||||
}
|
||||
|
||||
scheduleTimeout(checkBackendReady, 100)
|
||||
scheduleTimeout(() => {
|
||||
if (!isInitialized) {
|
||||
removeLoadingOverlay()
|
||||
notifyBackend().catch(console.error)
|
||||
}
|
||||
}, 5000)
|
||||
|
||||
return () => {
|
||||
isCancelled = true
|
||||
timers.forEach((id) => {
|
||||
try {
|
||||
window.clearTimeout(id)
|
||||
} catch (error) {
|
||||
console.warn('[Initialization] Failed to clear timer:', error)
|
||||
}
|
||||
})
|
||||
timers.clear()
|
||||
}
|
||||
}, [])
|
||||
}
|
||||
@ -3,46 +3,15 @@ import { useEffect, useRef } from 'react'
|
||||
import { hideInitialOverlay } from '../utils'
|
||||
|
||||
export const useLoadingOverlay = (themeReady: boolean) => {
|
||||
const overlayRemovedRef = useRef(false)
|
||||
const doneRef = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!themeReady || overlayRemovedRef.current) return
|
||||
|
||||
let removalTimer: number | undefined
|
||||
let retryTimer: number | undefined
|
||||
let attempts = 0
|
||||
const maxAttempts = 50
|
||||
let stopped = false
|
||||
|
||||
const tryRemoveOverlay = () => {
|
||||
if (stopped || overlayRemovedRef.current) return
|
||||
|
||||
const { removed, removalTimer: timerId } = hideInitialOverlay({
|
||||
assumeMissingAsRemoved: true,
|
||||
})
|
||||
if (typeof timerId === 'number') {
|
||||
removalTimer = timerId
|
||||
}
|
||||
|
||||
if (removed) {
|
||||
overlayRemovedRef.current = true
|
||||
return
|
||||
}
|
||||
|
||||
if (attempts < maxAttempts) {
|
||||
attempts += 1
|
||||
retryTimer = window.setTimeout(tryRemoveOverlay, 100)
|
||||
} else {
|
||||
console.warn('[Loading Overlay] Element not found')
|
||||
}
|
||||
}
|
||||
|
||||
tryRemoveOverlay()
|
||||
if (!themeReady || doneRef.current) return
|
||||
doneRef.current = true
|
||||
|
||||
const timer = hideInitialOverlay()
|
||||
return () => {
|
||||
stopped = true
|
||||
if (typeof removalTimer === 'number') window.clearTimeout(removalTimer)
|
||||
if (typeof retryTimer === 'number') window.clearTimeout(retryTimer)
|
||||
if (timer !== undefined) window.clearTimeout(timer)
|
||||
}
|
||||
}, [themeReady])
|
||||
}
|
||||
|
||||
@ -1,45 +1,17 @@
|
||||
const OVERLAY_ID = 'initial-loading-overlay'
|
||||
const REMOVE_DELAY = 300
|
||||
let removed = false
|
||||
|
||||
let overlayRemoved = false
|
||||
export const hideInitialOverlay = (): number | undefined => {
|
||||
if (removed) return undefined
|
||||
|
||||
type HideOverlayOptions = {
|
||||
schedule?: (handler: () => void, delay: number) => number
|
||||
assumeMissingAsRemoved?: boolean
|
||||
}
|
||||
|
||||
type HideOverlayResult = {
|
||||
removed: boolean
|
||||
removalTimer?: number
|
||||
}
|
||||
|
||||
export const hideInitialOverlay = (
|
||||
options: HideOverlayOptions = {},
|
||||
): HideOverlayResult => {
|
||||
if (overlayRemoved) {
|
||||
return { removed: true }
|
||||
}
|
||||
|
||||
const overlay = document.getElementById(OVERLAY_ID)
|
||||
const overlay = document.getElementById('initial-loading-overlay')
|
||||
if (!overlay) {
|
||||
if (options.assumeMissingAsRemoved) {
|
||||
overlayRemoved = true
|
||||
return { removed: true }
|
||||
}
|
||||
return { removed: false }
|
||||
removed = true
|
||||
return undefined
|
||||
}
|
||||
|
||||
overlayRemoved = true
|
||||
removed = true
|
||||
overlay.dataset.hidden = 'true'
|
||||
|
||||
const schedule = options.schedule ?? window.setTimeout
|
||||
const removalTimer = schedule(() => {
|
||||
try {
|
||||
overlay.remove()
|
||||
} catch (error) {
|
||||
console.warn('[Loading Overlay] Removal failed:', error)
|
||||
}
|
||||
}, REMOVE_DELAY)
|
||||
|
||||
return { removed: true, removalTimer }
|
||||
const timer = window.setTimeout(() => overlay.remove(), 200)
|
||||
return timer
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ export interface AppDataContextType {
|
||||
proxyProviders: Record<string, ProxyProvider>
|
||||
ruleProviders: Record<string, RuleProvider>
|
||||
systemProxyAddress: string
|
||||
isCoreDataPending: boolean
|
||||
|
||||
refreshProxy: () => Promise<any>
|
||||
refreshClashConfig: () => Promise<any>
|
||||
|
||||
@ -23,7 +23,7 @@ const TQ_MIHOMO = {
|
||||
refetchOnReconnect: false,
|
||||
staleTime: 1500,
|
||||
retry: 3,
|
||||
retryDelay: 2000,
|
||||
retryDelay: (attempt: number) => Math.min(200 * 2 ** attempt, 3000),
|
||||
} as const
|
||||
|
||||
const TQ_DEFAULTS = {
|
||||
@ -41,13 +41,21 @@ export const AppDataProvider = ({
|
||||
}) => {
|
||||
const { verge } = useVerge()
|
||||
|
||||
const { data: proxiesData, refetch: refreshProxy } = useQuery({
|
||||
const {
|
||||
data: proxiesData,
|
||||
isPending: isProxiesPending,
|
||||
refetch: refreshProxy,
|
||||
} = useQuery({
|
||||
queryKey: ['getProxies'],
|
||||
queryFn: calcuProxies,
|
||||
...TQ_MIHOMO,
|
||||
})
|
||||
|
||||
const { data: clashConfig, refetch: refreshClashConfig } = useQuery({
|
||||
const {
|
||||
data: clashConfig,
|
||||
isPending: isClashConfigPending,
|
||||
refetch: refreshClashConfig,
|
||||
} = useQuery({
|
||||
queryKey: ['getClashConfig'],
|
||||
queryFn: getBaseConfig,
|
||||
...TQ_MIHOMO,
|
||||
@ -323,6 +331,9 @@ export const AppDataProvider = ({
|
||||
|
||||
systemProxyAddress: calculateSystemProxyAddress(),
|
||||
|
||||
// core 数据加载状态
|
||||
isCoreDataPending: isProxiesPending || isClashConfigPending,
|
||||
|
||||
// 刷新方法
|
||||
refreshProxy,
|
||||
refreshClashConfig,
|
||||
@ -335,6 +346,8 @@ export const AppDataProvider = ({
|
||||
}, [
|
||||
proxiesData,
|
||||
clashConfig,
|
||||
isProxiesPending,
|
||||
isClashConfigPending,
|
||||
rulesData,
|
||||
sysproxy,
|
||||
runningMode,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user