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
|
pnpm-lock.yaml
|
||||||
|
|
||||||
src-tauri/target/
|
src-tauri/target/
|
||||||
|
src-tauri/gen/
|
||||||
|
|||||||
@@ -153,30 +153,61 @@ impl Hotkey {
|
|||||||
"=== Hotkey Dashboard Window Operation Start ==="
|
"=== Hotkey Dashboard Window Operation Start ==="
|
||||||
);
|
);
|
||||||
|
|
||||||
// 使用 spawn_blocking 来确保在正确的线程上执行
|
// 使用异步操作避免阻塞
|
||||||
AsyncHandler::spawn_blocking(|| {
|
AsyncHandler::spawn(move || async move {
|
||||||
logging!(debug, Type::Hotkey, "Toggle dashboard window visibility");
|
logging!(
|
||||||
|
debug,
|
||||||
|
Type::Hotkey,
|
||||||
|
true,
|
||||||
|
"Toggle dashboard window visibility"
|
||||||
|
);
|
||||||
|
|
||||||
// 检查窗口是否存在
|
// 检查窗口是否存在
|
||||||
if let Some(window) = handle::Handle::global().get_window() {
|
if let Some(window) = handle::Handle::global().get_window() {
|
||||||
// 如果窗口可见,则隐藏它
|
// 如果窗口可见,则隐藏
|
||||||
if window.is_visible().unwrap_or(false) {
|
match window.is_visible() {
|
||||||
logging!(info, Type::Window, "Window is visible, hiding it");
|
Ok(visible) => {
|
||||||
let _ = window.hide();
|
if visible {
|
||||||
} else {
|
logging!(
|
||||||
// 如果窗口不可见,则显示它
|
info,
|
||||||
logging!(info, Type::Window, "Window is hidden, showing it");
|
Type::Window,
|
||||||
if window.is_minimized().unwrap_or(false) {
|
true,
|
||||||
let _ = window.unminimize();
|
"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 {
|
} else {
|
||||||
// 如果窗口不存在,创建一个新窗口
|
// 如果窗口不存在,创建一个新窗口
|
||||||
logging!(
|
logging!(
|
||||||
info,
|
info,
|
||||||
Type::Window,
|
Type::Window,
|
||||||
|
true,
|
||||||
"Window does not exist, creating a new one"
|
"Window does not exist, creating a new one"
|
||||||
);
|
);
|
||||||
resolve::create_window(true);
|
resolve::create_window(true);
|
||||||
|
|||||||
@@ -49,8 +49,9 @@ pub fn open_or_close_dashboard() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 优化的应用退出函数
|
/// 异步优化的应用退出函数
|
||||||
pub fn quit() {
|
pub fn quit() {
|
||||||
|
use crate::process::AsyncHandler;
|
||||||
log::debug!(target: "app", "启动退出流程");
|
log::debug!(target: "app", "启动退出流程");
|
||||||
|
|
||||||
// 获取应用句柄并设置退出标志
|
// 获取应用句柄并设置退出标志
|
||||||
@@ -60,54 +61,191 @@ pub fn quit() {
|
|||||||
// 优先关闭窗口,提供立即反馈
|
// 优先关闭窗口,提供立即反馈
|
||||||
if let Some(window) = handle::Handle::global().get_window() {
|
if let Some(window) = handle::Handle::global().get_window() {
|
||||||
let _ = window.hide();
|
let _ = window.hide();
|
||||||
|
log::info!(target: "app", "窗口已隐藏");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 在单独线程中处理资源清理,避免阻塞主线程
|
// 使用异步任务处理资源清理,避免阻塞
|
||||||
std::thread::spawn(move || {
|
AsyncHandler::spawn(move || async move {
|
||||||
let cleanup_result = clean();
|
log::info!(target: "app", "开始异步清理资源");
|
||||||
app_handle.exit(match cleanup_result {
|
let cleanup_result = clean_async().await;
|
||||||
true => 0,
|
|
||||||
false => 1,
|
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};
|
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_success = if Config::verge().data().enable_tun_mode.unwrap_or(false) {
|
// 1. 处理TUN模式
|
||||||
|
let tun_task = async {
|
||||||
|
if Config::verge().data().enable_tun_mode.unwrap_or(false) {
|
||||||
let disable_tun = serde_json::json!({
|
let disable_tun = serde_json::json!({
|
||||||
"tun": {
|
"tun": {
|
||||||
"enable": false
|
"enable": false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
timeout(
|
match timeout(
|
||||||
Duration::from_secs(1),
|
Duration::from_secs(2),
|
||||||
MihomoManager::global().patch_configs(disable_tun),
|
MihomoManager::global().patch_configs(disable_tun),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.is_ok()
|
{
|
||||||
|
Ok(_) => {
|
||||||
|
log::info!(target: "app", "TUN模式已禁用");
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
log::warn!(target: "app", "禁用TUN模式超时");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 2. 顺序执行关键清理
|
// 2. 系统代理重置
|
||||||
let proxy_res = timeout(
|
let proxy_task = async {
|
||||||
Duration::from_secs(1),
|
match timeout(
|
||||||
|
Duration::from_secs(3),
|
||||||
sysopt::Sysopt::global().reset_sysproxy(),
|
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")]
|
#[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
|
cleanup_result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,17 +90,20 @@ pub fn run() {
|
|||||||
|
|
||||||
let _ = utils::dirs::init_portable_flag();
|
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, "开始检查单例实例...");
|
logging!(info, Type::Setup, true, "开始检查单例实例...");
|
||||||
match timeout(Duration::from_secs(3), server::check_singleton()).await {
|
match timeout(Duration::from_secs(3), server::check_singleton()).await {
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
if result.is_err() {
|
if result.is_err() {
|
||||||
logging!(info, Type::Setup, true, "检测到已有应用实例运行");
|
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 {
|
} else {
|
||||||
logging!(info, Type::Setup, true, "未检测到其他应用实例");
|
logging!(info, Type::Setup, true, "未检测到其他应用实例");
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
@@ -110,13 +113,9 @@ pub fn run() {
|
|||||||
true,
|
true,
|
||||||
"单例检查超时,假定没有其他实例运行"
|
"单例检查超时,假定没有其他实例运行"
|
||||||
);
|
);
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if app_exists {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
||||||
|
|||||||
@@ -11,20 +11,4 @@ impl AsyncHandler {
|
|||||||
{
|
{
|
||||||
async_runtime::spawn(f())
|
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) {
|
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() {
|
if VERSION.get().is_none() {
|
||||||
let version = app_handle.package_info().version.to_string();
|
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!(trace, Type::System, true, "初始化热键...");
|
||||||
logging_error!(Type::System, true, hotkey::Hotkey::global().init());
|
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 (异步)
|
/// reset system proxy (异步)
|
||||||
@@ -315,7 +334,49 @@ pub fn create_window(is_show: bool) -> bool {
|
|||||||
.fullscreen(false)
|
.fullscreen(false)
|
||||||
.inner_size(DEFAULT_WIDTH as f64, DEFAULT_HEIGHT as f64)
|
.inner_size(DEFAULT_WIDTH as f64, DEFAULT_HEIGHT as f64)
|
||||||
.min_inner_size(520.0, 520.0)
|
.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()
|
.build()
|
||||||
{
|
{
|
||||||
Ok(newly_created_window) => {
|
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 {
|
if is_show {
|
||||||
let window_clone = newly_created_window.clone();
|
let window_clone = newly_created_window.clone();
|
||||||
|
|
||||||
// Attempt to show and focus the window first.
|
// 立即显示窗口
|
||||||
let _ = window_clone.show();
|
let _ = window_clone.show();
|
||||||
let _ = window_clone.set_focus();
|
let _ = window_clone.set_focus();
|
||||||
logging!(debug, Type::Window, true, "窗口已尝试显示和聚焦");
|
logging!(info, 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();
|
|
||||||
|
|
||||||
let timeout_seconds = if crate::module::lightweight::is_in_lightweight_mode() {
|
let timeout_seconds = if crate::module::lightweight::is_in_lightweight_mode() {
|
||||||
2
|
3
|
||||||
} else {
|
} else {
|
||||||
5
|
8
|
||||||
};
|
};
|
||||||
|
|
||||||
logging!(
|
logging!(
|
||||||
info,
|
info,
|
||||||
Type::Window,
|
Type::Window,
|
||||||
true,
|
true,
|
||||||
"等待UI就绪 (最多{}秒)...",
|
"开始监控UI加载状态 (最多{}秒)...",
|
||||||
timeout_seconds
|
timeout_seconds
|
||||||
);
|
);
|
||||||
|
|
||||||
let wait_result =
|
// 异步监控UI状态,不影响窗口显示
|
||||||
tokio::time::timeout(Duration::from_secs(timeout_seconds), async {
|
tokio::spawn(async move {
|
||||||
while !*get_ui_ready().read() {
|
let wait_result =
|
||||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
tokio::time::timeout(Duration::from_secs(timeout_seconds), async {
|
||||||
}
|
let mut check_count = 0;
|
||||||
})
|
while !*get_ui_ready().read() {
|
||||||
.await;
|
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||||
|
check_count += 1;
|
||||||
|
|
||||||
match wait_result {
|
// 每2秒记录一次等待状态
|
||||||
Ok(_) => {
|
if check_count % 20 == 0 {
|
||||||
logging!(info, Type::Window, true, "UI就绪");
|
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, "窗口显示流程完成");
|
logging!(info, Type::Window, true, "窗口显示流程完成");
|
||||||
} else {
|
} else {
|
||||||
logging!(
|
logging!(
|
||||||
debug,
|
debug,
|
||||||
Type::Window,
|
Type::Window,
|
||||||
true,
|
true,
|
||||||
"is_show为false,窗口保持隐藏。尝试发送启动事件。"
|
"is_show为false,窗口保持隐藏状态"
|
||||||
);
|
);
|
||||||
handle::Handle::notify_startup_completed();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
true
|
true
|
||||||
|
|||||||
12
src/main.tsx
12
src/main.tsx
@@ -48,7 +48,8 @@ const contexts = [
|
|||||||
<UpdateStateProvider />,
|
<UpdateStateProvider />,
|
||||||
];
|
];
|
||||||
|
|
||||||
createRoot(container).render(
|
const root = createRoot(container);
|
||||||
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<ComposeContextProvider contexts={contexts}>
|
<ComposeContextProvider contexts={contexts}>
|
||||||
<BaseErrorBoundary>
|
<BaseErrorBoundary>
|
||||||
@@ -61,3 +62,12 @@ createRoot(container).render(
|
|||||||
</ComposeContextProvider>
|
</ComposeContextProvider>
|
||||||
</React.StrictMode>,
|
</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 = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 初始阶段 - 开始加载
|
// 简化的UI初始化流程
|
||||||
notifyUiStage("Loading");
|
const initializeUI = async () => {
|
||||||
|
try {
|
||||||
|
const initialOverlay = document.getElementById(
|
||||||
|
"initial-loading-overlay",
|
||||||
|
);
|
||||||
|
if (initialOverlay) {
|
||||||
|
initialOverlay.style.opacity = "0";
|
||||||
|
setTimeout(() => initialOverlay.remove(), 200);
|
||||||
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
await notifyUiStage("Loading");
|
||||||
notifyUiCoreReady();
|
|
||||||
|
|
||||||
setTimeout(() => {
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
notifyUiResourcesLoaded();
|
|
||||||
setTimeout(() => {
|
console.log("[Layout] 通知后端:DomReady");
|
||||||
notifyUiReady();
|
await notifyUiCoreReady();
|
||||||
}, 100);
|
|
||||||
}, 100);
|
await new Promise((resolve) => {
|
||||||
}, 100);
|
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();
|
const unlistenPromise = listenStartupCompleted();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
clearTimeout(emergencyTimeout);
|
||||||
unlistenPromise.then((unlisten) => unlisten());
|
unlistenPromise.then((unlisten) => unlisten());
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
@@ -332,12 +369,30 @@ const Layout = () => {
|
|||||||
height: "100vh",
|
height: "100vh",
|
||||||
background: mode === "light" ? "#fff" : "#181a1b",
|
background: mode === "light" ? "#fff" : "#181a1b",
|
||||||
transition: "background 0.2s",
|
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 (
|
return (
|
||||||
<SWRConfig value={{ errorRetryCount: 3 }}>
|
<SWRConfig value={{ errorRetryCount: 3 }}>
|
||||||
|
|||||||
Reference in New Issue
Block a user