mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-29 00:35:38 +08:00
perf: async app startup loading to prevent UI freeze
This commit is contained in:
@@ -5,3 +5,4 @@
|
||||
pnpm-lock.yaml
|
||||
|
||||
src-tauri/target/
|
||||
src-tauri/gen/
|
||||
|
||||
@@ -153,30 +153,61 @@ impl Hotkey {
|
||||
"=== Hotkey Dashboard Window Operation Start ==="
|
||||
);
|
||||
|
||||
// 使用 spawn_blocking 来确保在正确的线程上执行
|
||||
AsyncHandler::spawn_blocking(|| {
|
||||
logging!(debug, Type::Hotkey, "Toggle dashboard window visibility");
|
||||
// 使用异步操作避免阻塞
|
||||
AsyncHandler::spawn(move || async move {
|
||||
logging!(
|
||||
debug,
|
||||
Type::Hotkey,
|
||||
true,
|
||||
"Toggle dashboard window visibility"
|
||||
);
|
||||
|
||||
// 检查窗口是否存在
|
||||
if let Some(window) = handle::Handle::global().get_window() {
|
||||
// 如果窗口可见,则隐藏它
|
||||
if window.is_visible().unwrap_or(false) {
|
||||
logging!(info, Type::Window, "Window is visible, hiding it");
|
||||
let _ = window.hide();
|
||||
} else {
|
||||
// 如果窗口不可见,则显示它
|
||||
logging!(info, Type::Window, "Window is hidden, showing it");
|
||||
if window.is_minimized().unwrap_or(false) {
|
||||
let _ = window.unminimize();
|
||||
// 如果窗口可见,则隐藏
|
||||
match window.is_visible() {
|
||||
Ok(visible) => {
|
||||
if visible {
|
||||
logging!(
|
||||
info,
|
||||
Type::Window,
|
||||
true,
|
||||
"Window is visible, hiding it"
|
||||
);
|
||||
let _ = window.hide();
|
||||
} else {
|
||||
// 如果窗口不可见,则显示
|
||||
logging!(
|
||||
info,
|
||||
Type::Window,
|
||||
true,
|
||||
"Window is hidden, showing it"
|
||||
);
|
||||
if window.is_minimized().unwrap_or(false) {
|
||||
let _ = window.unminimize();
|
||||
}
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Window,
|
||||
true,
|
||||
"Failed to check window visibility: {}",
|
||||
e
|
||||
);
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
} else {
|
||||
// 如果窗口不存在,创建一个新窗口
|
||||
logging!(
|
||||
info,
|
||||
Type::Window,
|
||||
true,
|
||||
"Window does not exist, creating a new one"
|
||||
);
|
||||
resolve::create_window(true);
|
||||
|
||||
@@ -49,8 +49,9 @@ pub fn open_or_close_dashboard() {
|
||||
}
|
||||
}
|
||||
|
||||
/// 优化的应用退出函数
|
||||
/// 异步优化的应用退出函数
|
||||
pub fn quit() {
|
||||
use crate::process::AsyncHandler;
|
||||
log::debug!(target: "app", "启动退出流程");
|
||||
|
||||
// 获取应用句柄并设置退出标志
|
||||
@@ -60,54 +61,191 @@ pub fn quit() {
|
||||
// 优先关闭窗口,提供立即反馈
|
||||
if let Some(window) = handle::Handle::global().get_window() {
|
||||
let _ = window.hide();
|
||||
log::info!(target: "app", "窗口已隐藏");
|
||||
}
|
||||
|
||||
// 在单独线程中处理资源清理,避免阻塞主线程
|
||||
std::thread::spawn(move || {
|
||||
let cleanup_result = clean();
|
||||
app_handle.exit(match cleanup_result {
|
||||
true => 0,
|
||||
false => 1,
|
||||
});
|
||||
// 使用异步任务处理资源清理,避免阻塞
|
||||
AsyncHandler::spawn(move || async move {
|
||||
log::info!(target: "app", "开始异步清理资源");
|
||||
let cleanup_result = clean_async().await;
|
||||
|
||||
log::info!(target: "app", "资源清理完成,退出代码: {}", if cleanup_result { 0 } else { 1 });
|
||||
app_handle.exit(if cleanup_result { 0 } else { 1 });
|
||||
});
|
||||
}
|
||||
|
||||
pub fn clean() -> bool {
|
||||
async fn clean_async() -> bool {
|
||||
use tokio::time::{timeout, Duration};
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let cleanup_result = rt.block_on(async {
|
||||
// 1. 处理TUN模式
|
||||
let tun_success = if Config::verge().data().enable_tun_mode.unwrap_or(false) {
|
||||
|
||||
log::info!(target: "app", "开始执行异步清理操作...");
|
||||
|
||||
// 1. 处理TUN模式
|
||||
let tun_task = async {
|
||||
if Config::verge().data().enable_tun_mode.unwrap_or(false) {
|
||||
let disable_tun = serde_json::json!({
|
||||
"tun": {
|
||||
"enable": false
|
||||
}
|
||||
});
|
||||
timeout(
|
||||
Duration::from_secs(1),
|
||||
match timeout(
|
||||
Duration::from_secs(2),
|
||||
MihomoManager::global().patch_configs(disable_tun),
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
Ok(_) => {
|
||||
log::info!(target: "app", "TUN模式已禁用");
|
||||
true
|
||||
}
|
||||
Err(_) => {
|
||||
log::warn!(target: "app", "禁用TUN模式超时");
|
||||
false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
true
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// 2. 顺序执行关键清理
|
||||
let proxy_res = timeout(
|
||||
Duration::from_secs(1),
|
||||
// 2. 系统代理重置
|
||||
let proxy_task = async {
|
||||
match timeout(
|
||||
Duration::from_secs(3),
|
||||
sysopt::Sysopt::global().reset_sysproxy(),
|
||||
)
|
||||
.await;
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
log::info!(target: "app", "系统代理已重置");
|
||||
true
|
||||
}
|
||||
Err(_) => {
|
||||
log::warn!(target: "app", "重置系统代理超时");
|
||||
false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let core_res = timeout(Duration::from_secs(1), CoreManager::global().stop_core()).await;
|
||||
// 3. 核心服务停止
|
||||
let core_task = async {
|
||||
match timeout(Duration::from_secs(3), CoreManager::global().stop_core()).await {
|
||||
Ok(_) => {
|
||||
log::info!(target: "app", "核心服务已停止");
|
||||
true
|
||||
}
|
||||
Err(_) => {
|
||||
log::warn!(target: "app", "停止核心服务超时");
|
||||
false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 3. 平台特定清理
|
||||
// 4. DNS恢复(仅macOS)
|
||||
#[cfg(target_os = "macos")]
|
||||
let dns_task = async {
|
||||
match timeout(Duration::from_millis(1000), resolve::restore_public_dns()).await {
|
||||
Ok(_) => {
|
||||
log::info!(target: "app", "DNS设置已恢复");
|
||||
true
|
||||
}
|
||||
Err(_) => {
|
||||
log::warn!(target: "app", "恢复DNS设置超时");
|
||||
false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 并行执行所有清理任务
|
||||
let (tun_success, proxy_success, core_success) = tokio::join!(tun_task, proxy_task, core_task);
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let dns_success = dns_task.await;
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let dns_success = true;
|
||||
|
||||
let all_success = tun_success && proxy_success && core_success && dns_success;
|
||||
|
||||
log::info!(
|
||||
target: "app",
|
||||
"异步清理操作完成 - TUN: {}, 代理: {}, 核心: {}, DNS: {}, 总体: {}",
|
||||
tun_success, proxy_success, core_success, dns_success, all_success
|
||||
);
|
||||
|
||||
all_success
|
||||
}
|
||||
|
||||
pub fn clean() -> bool {
|
||||
use tokio::time::{timeout, Duration};
|
||||
|
||||
// 使用异步处理
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let cleanup_result = rt.block_on(async {
|
||||
log::info!(target: "app", "开始执行清理操作...");
|
||||
|
||||
// 1. 处理TUN模式 - 并行执行,减少等待时间
|
||||
let tun_task = async {
|
||||
if Config::verge().data().enable_tun_mode.unwrap_or(false) {
|
||||
let disable_tun = serde_json::json!({
|
||||
"tun": {
|
||||
"enable": false
|
||||
}
|
||||
});
|
||||
timeout(
|
||||
Duration::from_secs(2),
|
||||
MihomoManager::global().patch_configs(disable_tun),
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
} else {
|
||||
true
|
||||
}
|
||||
};
|
||||
|
||||
// 2. 系统代理重置
|
||||
let proxy_task = async {
|
||||
timeout(
|
||||
Duration::from_secs(2),
|
||||
sysopt::Sysopt::global().reset_sysproxy(),
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
};
|
||||
|
||||
// 3. 核心服务停止
|
||||
let core_task = async {
|
||||
timeout(Duration::from_secs(2), CoreManager::global().stop_core())
|
||||
.await
|
||||
.is_ok()
|
||||
};
|
||||
|
||||
// 4. DNS恢复(仅macOS)
|
||||
#[cfg(target_os = "macos")]
|
||||
let _dns_res = timeout(Duration::from_millis(500), resolve::restore_public_dns()).await;
|
||||
let dns_task = async {
|
||||
timeout(Duration::from_millis(800), resolve::restore_public_dns())
|
||||
.await
|
||||
.is_ok()
|
||||
};
|
||||
|
||||
tun_success && proxy_res.is_ok() && core_res.is_ok()
|
||||
// 并行执行所有清理任务,提高效率
|
||||
let (tun_success, proxy_success, core_success) =
|
||||
tokio::join!(tun_task, proxy_task, core_task);
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let dns_success = dns_task.await;
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let dns_success = true;
|
||||
|
||||
let all_success = tun_success && proxy_success && core_success && dns_success;
|
||||
|
||||
log::info!(
|
||||
target: "app",
|
||||
"清理操作完成 - TUN: {}, 代理: {}, 核心: {}, DNS: {}, 总体: {}",
|
||||
tun_success, proxy_success, core_success, dns_success, all_success
|
||||
);
|
||||
|
||||
all_success
|
||||
});
|
||||
|
||||
cleanup_result
|
||||
}
|
||||
|
||||
|
||||
@@ -90,17 +90,20 @@ pub fn run() {
|
||||
|
||||
let _ = utils::dirs::init_portable_flag();
|
||||
|
||||
// 单例检测
|
||||
let app_exists: bool = AsyncHandler::block_on(move || async move {
|
||||
// 异步单例检测
|
||||
AsyncHandler::spawn(move || async move {
|
||||
logging!(info, Type::Setup, true, "开始检查单例实例...");
|
||||
match timeout(Duration::from_secs(3), server::check_singleton()).await {
|
||||
Ok(result) => {
|
||||
if result.is_err() {
|
||||
logging!(info, Type::Setup, true, "检测到已有应用实例运行");
|
||||
true
|
||||
if let Some(app_handle) = AppHandleManager::global().get() {
|
||||
let _ = app_handle.exit(0);
|
||||
} else {
|
||||
std::process::exit(0);
|
||||
}
|
||||
} else {
|
||||
logging!(info, Type::Setup, true, "未检测到其他应用实例");
|
||||
false
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
@@ -110,13 +113,9 @@ pub fn run() {
|
||||
true,
|
||||
"单例检查超时,假定没有其他实例运行"
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
});
|
||||
if app_exists {
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
||||
|
||||
@@ -11,20 +11,4 @@ impl AsyncHandler {
|
||||
{
|
||||
async_runtime::spawn(f())
|
||||
}
|
||||
|
||||
pub fn spawn_blocking<F, R>(f: F) -> JoinHandle<R>
|
||||
where
|
||||
F: FnOnce() -> R + Send + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
async_runtime::spawn_blocking(f)
|
||||
}
|
||||
|
||||
pub fn block_on<F, Fut, R>(f: F) -> R
|
||||
where
|
||||
F: FnOnce() -> Fut,
|
||||
Fut: Future<Output = R>,
|
||||
{
|
||||
async_runtime::block_on(f())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +142,8 @@ pub async fn find_unused_port() -> Result<u16> {
|
||||
|
||||
/// 异步方式处理启动后的额外任务
|
||||
pub async fn resolve_setup_async(app_handle: &AppHandle) {
|
||||
logging!(info, Type::Setup, true, "执行异步设置任务...");
|
||||
let start_time = std::time::Instant::now();
|
||||
logging!(info, Type::Setup, true, "开始执行异步设置任务...");
|
||||
|
||||
if VERSION.get().is_none() {
|
||||
let version = app_handle.package_info().version.to_string();
|
||||
@@ -230,7 +231,25 @@ pub async fn resolve_setup_async(app_handle: &AppHandle) {
|
||||
logging!(trace, Type::System, true, "初始化热键...");
|
||||
logging_error!(Type::System, true, hotkey::Hotkey::global().init());
|
||||
|
||||
logging!(info, Type::Setup, true, "异步设置任务完成");
|
||||
let elapsed = start_time.elapsed();
|
||||
logging!(
|
||||
info,
|
||||
Type::Setup,
|
||||
true,
|
||||
"异步设置任务完成,耗时: {:?}",
|
||||
elapsed
|
||||
);
|
||||
|
||||
// 如果初始化时间过长,记录警告
|
||||
if elapsed.as_secs() > 10 {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Setup,
|
||||
true,
|
||||
"异步设置任务耗时较长({:?})",
|
||||
elapsed
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// reset system proxy (异步)
|
||||
@@ -315,7 +334,49 @@ pub fn create_window(is_show: bool) -> bool {
|
||||
.fullscreen(false)
|
||||
.inner_size(DEFAULT_WIDTH as f64, DEFAULT_HEIGHT as f64)
|
||||
.min_inner_size(520.0, 520.0)
|
||||
.visible(false)
|
||||
.visible(true) // 立即显示窗口,避免用户等待
|
||||
.initialization_script(r#"
|
||||
// 添加非侵入式的加载指示器
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 只有在React应用还没有挂载时才显示加载指示器
|
||||
if (!document.getElementById('root') || !document.getElementById('root').hasChildNodes()) {
|
||||
// 创建加载指示器,但不覆盖整个body
|
||||
const loadingDiv = document.createElement('div');
|
||||
loadingDiv.id = 'initial-loading-overlay';
|
||||
loadingDiv.innerHTML = `
|
||||
<div style="
|
||||
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: var(--bg-color, #f5f5f5); color: var(--text-color, #333);
|
||||
display: flex; flex-direction: column; align-items: center;
|
||||
justify-content: center; z-index: 9998; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
">
|
||||
<div style="margin-bottom: 20px;">
|
||||
<div style="width: 40px; height: 40px; border: 3px solid #e3e3e3; border-top: 3px solid #3498db; border-radius: 50%; animation: spin 1s linear infinite;"></div>
|
||||
</div>
|
||||
<div style="font-size: 14px; opacity: 0.7;">正在加载 Clash Verge...</div>
|
||||
</div>
|
||||
<style>
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root { --bg-color: #1a1a1a; --text-color: #ffffff; }
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
document.body.appendChild(loadingDiv);
|
||||
|
||||
// 设置定时器,如果React应用在5秒内没有挂载,移除加载指示器
|
||||
setTimeout(() => {
|
||||
const overlay = document.getElementById('initial-loading-overlay');
|
||||
if (overlay) {
|
||||
overlay.remove();
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
"#)
|
||||
.build()
|
||||
{
|
||||
Ok(newly_created_window) => {
|
||||
@@ -334,72 +395,98 @@ pub fn create_window(is_show: bool) -> bool {
|
||||
"异步窗口任务开始 (启动已标记完成)"
|
||||
);
|
||||
|
||||
// 先运行轻量模式检测
|
||||
lightweight::run_once_auto_lightweight();
|
||||
|
||||
// 发送启动完成事件,触发前端开始加载
|
||||
logging!(
|
||||
debug,
|
||||
Type::Window,
|
||||
true,
|
||||
"发送 verge://startup-completed 事件"
|
||||
);
|
||||
handle::Handle::notify_startup_completed();
|
||||
|
||||
if is_show {
|
||||
let window_clone = newly_created_window.clone();
|
||||
|
||||
// Attempt to show and focus the window first.
|
||||
// 立即显示窗口
|
||||
let _ = window_clone.show();
|
||||
let _ = window_clone.set_focus();
|
||||
logging!(debug, Type::Window, true, "窗口已尝试显示和聚焦");
|
||||
|
||||
lightweight::run_once_auto_lightweight();
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(100)).await; // Crucial delay
|
||||
|
||||
logging!(
|
||||
debug,
|
||||
Type::Window,
|
||||
true,
|
||||
"延时后,尝试发送 verge://startup-completed 事件"
|
||||
);
|
||||
handle::Handle::notify_startup_completed();
|
||||
logging!(info, Type::Window, true, "窗口已立即显示");
|
||||
|
||||
let timeout_seconds = if crate::module::lightweight::is_in_lightweight_mode() {
|
||||
2
|
||||
3
|
||||
} else {
|
||||
5
|
||||
8
|
||||
};
|
||||
|
||||
logging!(
|
||||
info,
|
||||
Type::Window,
|
||||
true,
|
||||
"等待UI就绪 (最多{}秒)...",
|
||||
"开始监控UI加载状态 (最多{}秒)...",
|
||||
timeout_seconds
|
||||
);
|
||||
|
||||
let wait_result =
|
||||
tokio::time::timeout(Duration::from_secs(timeout_seconds), async {
|
||||
while !*get_ui_ready().read() {
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
})
|
||||
.await;
|
||||
// 异步监控UI状态,不影响窗口显示
|
||||
tokio::spawn(async move {
|
||||
let wait_result =
|
||||
tokio::time::timeout(Duration::from_secs(timeout_seconds), async {
|
||||
let mut check_count = 0;
|
||||
while !*get_ui_ready().read() {
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
check_count += 1;
|
||||
|
||||
match wait_result {
|
||||
Ok(_) => {
|
||||
logging!(info, Type::Window, true, "UI就绪");
|
||||
// 每2秒记录一次等待状态
|
||||
if check_count % 20 == 0 {
|
||||
logging!(
|
||||
debug,
|
||||
Type::Window,
|
||||
true,
|
||||
"UI加载状态检查... ({}秒)",
|
||||
check_count / 10
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
match wait_result {
|
||||
Ok(_) => {
|
||||
logging!(info, Type::Window, true, "UI已完全加载就绪");
|
||||
// 移除初始加载指示器
|
||||
if let Some(window) = handle::Handle::global().get_window() {
|
||||
let _ = window.eval(r#"
|
||||
const overlay = document.getElementById('initial-loading-overlay');
|
||||
if (overlay) {
|
||||
overlay.style.opacity = '0';
|
||||
setTimeout(() => overlay.remove(), 300);
|
||||
}
|
||||
"#);
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Window,
|
||||
true,
|
||||
"UI加载监控超时({}秒),但窗口已正常显示",
|
||||
timeout_seconds
|
||||
);
|
||||
*get_ui_ready().write() = true;
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Window,
|
||||
true,
|
||||
"等待UI就绪超时({}秒),强制标记就绪",
|
||||
timeout_seconds
|
||||
);
|
||||
*get_ui_ready().write() = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
logging!(info, Type::Window, true, "窗口显示流程完成");
|
||||
} else {
|
||||
logging!(
|
||||
debug,
|
||||
Type::Window,
|
||||
true,
|
||||
"is_show为false,窗口保持隐藏。尝试发送启动事件。"
|
||||
"is_show为false,窗口保持隐藏状态"
|
||||
);
|
||||
handle::Handle::notify_startup_completed();
|
||||
}
|
||||
});
|
||||
true
|
||||
|
||||
12
src/main.tsx
12
src/main.tsx
@@ -48,7 +48,8 @@ const contexts = [
|
||||
<UpdateStateProvider />,
|
||||
];
|
||||
|
||||
createRoot(container).render(
|
||||
const root = createRoot(container);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<ComposeContextProvider contexts={contexts}>
|
||||
<BaseErrorBoundary>
|
||||
@@ -61,3 +62,12 @@ createRoot(container).render(
|
||||
</ComposeContextProvider>
|
||||
</React.StrictMode>,
|
||||
);
|
||||
|
||||
// 错误处理
|
||||
window.addEventListener("error", (event) => {
|
||||
console.error("[main.tsx] 全局错误:", event.error);
|
||||
});
|
||||
|
||||
window.addEventListener("unhandledrejection", (event) => {
|
||||
console.error("[main.tsx] 未处理的Promise拒绝:", event.reason);
|
||||
});
|
||||
|
||||
@@ -288,24 +288,61 @@ const Layout = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 初始阶段 - 开始加载
|
||||
notifyUiStage("Loading");
|
||||
// 简化的UI初始化流程
|
||||
const initializeUI = async () => {
|
||||
try {
|
||||
const initialOverlay = document.getElementById(
|
||||
"initial-loading-overlay",
|
||||
);
|
||||
if (initialOverlay) {
|
||||
initialOverlay.style.opacity = "0";
|
||||
setTimeout(() => initialOverlay.remove(), 200);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
notifyUiCoreReady();
|
||||
await notifyUiStage("Loading");
|
||||
|
||||
setTimeout(() => {
|
||||
notifyUiResourcesLoaded();
|
||||
setTimeout(() => {
|
||||
notifyUiReady();
|
||||
}, 100);
|
||||
}, 100);
|
||||
}, 100);
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
console.log("[Layout] 通知后端:DomReady");
|
||||
await notifyUiCoreReady();
|
||||
|
||||
await new Promise((resolve) => {
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(resolve);
|
||||
});
|
||||
});
|
||||
|
||||
console.log("[Layout] 通知后端:ResourcesLoaded");
|
||||
await notifyUiResourcesLoaded();
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
await notifyUiReady();
|
||||
} catch (error) {
|
||||
try {
|
||||
await notifyUiReady();
|
||||
} catch (e) {
|
||||
console.error("[Layout] 通知UI就绪失败:", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setTimeout(initializeUI, 50);
|
||||
|
||||
const emergencyTimeout = setTimeout(() => {
|
||||
const emergencyNotify = async () => {
|
||||
try {
|
||||
await invoke("notify_ui_ready");
|
||||
} catch (error) {}
|
||||
};
|
||||
emergencyNotify();
|
||||
}, 5000);
|
||||
|
||||
// 启动监听器
|
||||
const unlistenPromise = listenStartupCompleted();
|
||||
|
||||
return () => {
|
||||
clearTimeout(emergencyTimeout);
|
||||
unlistenPromise.then((unlisten) => unlisten());
|
||||
};
|
||||
}, []);
|
||||
@@ -332,12 +369,30 @@ const Layout = () => {
|
||||
height: "100vh",
|
||||
background: mode === "light" ? "#fff" : "#181a1b",
|
||||
transition: "background 0.2s",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
color: mode === "light" ? "#333" : "#fff",
|
||||
}}
|
||||
/>
|
||||
></div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!routersEles) return null;
|
||||
if (!routersEles) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: "100vw",
|
||||
height: "100vh",
|
||||
background: mode === "light" ? "#fff" : "#181a1b",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
color: mode === "light" ? "#333" : "#fff",
|
||||
}}
|
||||
></div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SWRConfig value={{ errorRetryCount: 3 }}>
|
||||
|
||||
Reference in New Issue
Block a user