diff --git a/LanMountainDesktop.Launcher/Services/LauncherFlowCoordinator.cs b/LanMountainDesktop.Launcher/Services/LauncherFlowCoordinator.cs
index 11e2822..784b03e 100644
--- a/LanMountainDesktop.Launcher/Services/LauncherFlowCoordinator.cs
+++ b/LanMountainDesktop.Launcher/Services/LauncherFlowCoordinator.cs
@@ -63,9 +63,23 @@ internal sealed class LauncherFlowCoordinator
var reporter = (ISplashStageReporter)splashWindow;
+ // 创建加载详情窗口(可选,用于显示详细加载状态)
+ LoadingDetailsWindow? loadingDetailsWindow = null;
+ if (_context.IsDebugMode || _context.GetOption("show-loading-details") == "true")
+ {
+ await Dispatcher.UIThread.InvokeAsync(() =>
+ {
+ loadingDetailsWindow = new LoadingDetailsWindow();
+ loadingDetailsWindow.Show();
+ });
+ }
+
// 跟踪主程序是否已就绪,就绪后自动关闭 Splash 窗口
var hostReadyTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ // 加载状态管理
+ var loadingState = new LoadingStateMessage();
+
// 启动 IPC 服务端监听主程序进度
using var ipcServer = new LauncherIpcServer(msg =>
{
@@ -73,12 +87,29 @@ internal sealed class LauncherFlowCoordinator
{
try
{
+ // 更新加载状态
+ loadingState = loadingState with
+ {
+ Stage = msg.Stage,
+ OverallProgressPercent = msg.ProgressPercent,
+ Message = msg.Message,
+ Timestamp = DateTimeOffset.UtcNow
+ };
+
+ // 报告到 Splash 窗口
reporter.Report(msg.Stage.ToString().ToLower(), msg.Message ?? "");
- // 主程序报告就绪后,关闭 Splash 窗口
- if (msg.Stage == StartupStage.Ready && splashWindow.IsVisible && splashWindow.IsLoaded)
+ // 更新加载详情窗口
+ loadingDetailsWindow?.UpdateLoadingState(loadingState);
+
+ // 主程序报告就绪后,关闭 Splash 窗口和加载详情窗口
+ if (msg.Stage == StartupStage.Ready)
{
- splashWindow.Close();
+ if (splashWindow.IsVisible && splashWindow.IsLoaded)
+ {
+ splashWindow.Close();
+ }
+ loadingDetailsWindow?.Close();
hostReadyTcs.TrySetResult();
}
}
@@ -133,20 +164,52 @@ internal sealed class LauncherFlowCoordinator
// 维持 IPC 管道服务端供主程序报告启动进度。
if (hostProcess is not null)
{
- // 等待主程序就绪或进程退出(取先发生者)
- // 如果主程序在 60 秒内未报告 Ready,也关闭 Splash 窗口作为超时保护
- var readyOrTimeout = Task.WhenAny(
- hostReadyTcs.Task,
- Task.Delay(TimeSpan.FromSeconds(60)));
-
var processExitTask = hostProcess.WaitForExitAsync();
- // 先等待就绪/超时,然后等待进程退出
- await readyOrTimeout;
+ // 等待主程序就绪或进程退出(取先发生者)
+ // 延长超时到 120 秒,给主程序足够的加载时间
+ var readyOrTimeoutOrExit = Task.WhenAny(
+ hostReadyTcs.Task,
+ processExitTask,
+ Task.Delay(TimeSpan.FromSeconds(120)));
+
+ var completedTask = await readyOrTimeoutOrExit;
+
+ // 检查是否是进程先退出(异常情况)
+ if (completedTask == processExitTask)
+ {
+ var exitCode = hostProcess.ExitCode;
+ Console.Error.WriteLine($"[LauncherFlowCoordinator] Host process exited unexpectedly with code: {exitCode}");
+
+ // 关闭 Splash 窗口
+ await Dispatcher.UIThread.InvokeAsync(() =>
+ {
+ try
+ {
+ if (splashWindow.IsVisible && splashWindow.IsLoaded)
+ {
+ splashWindow.Close();
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.Error.WriteLine($"[LauncherFlowCoordinator] Error closing splash window: {ex.Message}");
+ }
+ });
+
+ return new LauncherResult
+ {
+ Success = false,
+ Stage = "launch",
+ Code = "host_crashed",
+ Message = $"主程序异常退出,退出代码: {exitCode}"
+ };
+ }
// 如果 Splash 窗口仍然打开(超时情况),关闭它
if (splashWindow.IsVisible)
{
+ Console.WriteLine("[LauncherFlowCoordinator] Timeout waiting for Ready signal, closing splash window...");
await Dispatcher.UIThread.InvokeAsync(() =>
{
try
@@ -163,7 +226,11 @@ internal sealed class LauncherFlowCoordinator
});
}
- await processExitTask;
+ // 继续等待主程序进程退出(如果它还在运行)
+ if (!hostProcess.HasExited)
+ {
+ await processExitTask;
+ }
}
else
{
diff --git a/LanMountainDesktop.Launcher/Views/LoadingDetailsWindow.axaml b/LanMountainDesktop.Launcher/Views/LoadingDetailsWindow.axaml
new file mode 100644
index 0000000..13ea986
--- /dev/null
+++ b/LanMountainDesktop.Launcher/Views/LoadingDetailsWindow.axaml
@@ -0,0 +1,250 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LanMountainDesktop.Launcher/Views/LoadingDetailsWindow.axaml.cs b/LanMountainDesktop.Launcher/Views/LoadingDetailsWindow.axaml.cs
new file mode 100644
index 0000000..23d45df
--- /dev/null
+++ b/LanMountainDesktop.Launcher/Views/LoadingDetailsWindow.axaml.cs
@@ -0,0 +1,396 @@
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media;
+using Avalonia.Threading;
+using LanMountainDesktop.Launcher.Services;
+using LanMountainDesktop.Shared.Contracts.Launcher;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+
+namespace LanMountainDesktop.Launcher.Views;
+
+///
+/// 加载详情窗口 - 显示详细的加载状态和进度
+///
+public partial class LoadingDetailsWindow : Window
+{
+ private readonly ObservableCollection _items = new();
+ private readonly DispatcherTimer _updateTimer;
+ private DateTimeOffset _startTime;
+
+ public LoadingDetailsWindow()
+ {
+ AvaloniaXamlLoader.Load(this);
+
+ // 初始化列表
+ var itemsList = this.FindControl("LoadingItemsList");
+ if (itemsList != null)
+ {
+ itemsList.ItemsSource = _items;
+ }
+
+ // 创建更新定时器
+ _updateTimer = new DispatcherTimer
+ {
+ Interval = TimeSpan.FromMilliseconds(100)
+ };
+ _updateTimer.Tick += OnUpdateTimerTick;
+
+ _startTime = DateTimeOffset.UtcNow;
+ }
+
+ ///
+ /// 窗口加载完成
+ ///
+ protected override void OnLoaded(RoutedEventArgs e)
+ {
+ base.OnLoaded(e);
+ _updateTimer.Start();
+ }
+
+ ///
+ /// 窗口关闭
+ ///
+ protected override void OnClosing(WindowClosingEventArgs e)
+ {
+ _updateTimer.Stop();
+ base.OnClosing(e);
+ }
+
+ ///
+ /// 更新加载状态
+ ///
+ public void UpdateLoadingState(LoadingStateMessage state)
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ try
+ {
+ // 更新标题和副标题
+ UpdateHeader(state);
+
+ // 更新整体进度
+ UpdateOverallProgress(state);
+
+ // 更新当前活动项
+ UpdateCurrentItem(state);
+
+ // 更新列表
+ UpdateItemsList(state);
+
+ // 更新错误信息
+ UpdateErrorPanel(state);
+
+ // 更新完成计数
+ UpdateCompletedCount(state);
+ }
+ catch (Exception ex)
+ {
+ Console.Error.WriteLine($"[LoadingDetailsWindow] Error updating state: {ex.Message}");
+ }
+ });
+ }
+
+ ///
+ /// 更新标题
+ ///
+ private void UpdateHeader(LoadingStateMessage state)
+ {
+ var subtitleText = this.FindControl("SubtitleText");
+ if (subtitleText != null)
+ {
+ subtitleText.Text = GetStageDescription(state.Stage);
+ }
+ }
+
+ ///
+ /// 更新整体进度
+ ///
+ private void UpdateOverallProgress(LoadingStateMessage state)
+ {
+ var progressBar = this.FindControl("OverallProgressBar");
+ var percentText = this.FindControl("PercentText");
+
+ if (progressBar != null)
+ {
+ progressBar.Value = state.OverallProgressPercent;
+ }
+
+ if (percentText != null)
+ {
+ percentText.Text = $"{state.OverallProgressPercent}%";
+ }
+ }
+
+ ///
+ /// 更新当前活动项
+ ///
+ private void UpdateCurrentItem(LoadingStateMessage state)
+ {
+ var currentItem = state.ActiveItems.FirstOrDefault();
+ if (currentItem == null) return;
+
+ var nameText = this.FindControl("CurrentItemName");
+ var descText = this.FindControl("CurrentItemDescription");
+ var progressBar = this.FindControl("CurrentItemProgress");
+ var iconText = this.FindControl("CurrentItemIcon");
+
+ if (nameText != null)
+ {
+ nameText.Text = currentItem.Name;
+ }
+
+ if (descText != null)
+ {
+ descText.Text = currentItem.Message ?? GetItemDescription(currentItem);
+ }
+
+ if (progressBar != null)
+ {
+ progressBar.Value = currentItem.ProgressPercent;
+ }
+
+ if (iconText != null)
+ {
+ iconText.Text = GetItemIcon(currentItem.Type);
+ }
+ }
+
+ ///
+ /// 更新列表
+ ///
+ private void UpdateItemsList(LoadingStateMessage state)
+ {
+ // 同步列表项
+ foreach (var item in state.ActiveItems)
+ {
+ var existing = _items.FirstOrDefault(i => i.Id == item.Id);
+ if (existing != null)
+ {
+ existing.UpdateFrom(item);
+ }
+ else
+ {
+ _items.Add(new LoadingItemViewModel(item));
+ }
+ }
+
+ // 移除已完成的项(保留最近完成的5个)
+ var completedItems = _items.Where(i => i.State == LoadingState.Completed).ToList();
+ if (completedItems.Count > 5)
+ {
+ var itemsToRemove = completedItems.OrderBy(i => i.CompletedTime).Take(completedItems.Count - 5);
+ foreach (var item in itemsToRemove)
+ {
+ _items.Remove(item);
+ }
+ }
+
+ // 按状态排序:进行中 -> 等待中 -> 已完成 -> 失败
+ var sortedItems = _items.OrderBy(i => GetStatePriority(i.State)).ToList();
+ _items.Clear();
+ foreach (var item in sortedItems)
+ {
+ _items.Add(item);
+ }
+ }
+
+ ///
+ /// 更新错误面板
+ ///
+ private void UpdateErrorPanel(LoadingStateMessage state)
+ {
+ var errorPanel = this.FindControl("ErrorPanel");
+ var errorText = this.FindControl("ErrorText");
+
+ if (errorPanel != null)
+ {
+ errorPanel.IsVisible = state.HasErrors;
+ }
+
+ if (errorText != null && state.ErrorMessages?.Any() == true)
+ {
+ errorText.Text = string.Join("\n", state.ErrorMessages.Take(3));
+ }
+ }
+
+ ///
+ /// 更新完成计数
+ ///
+ private void UpdateCompletedCount(LoadingStateMessage state)
+ {
+ var countText = this.FindControl("CompletedCountText");
+ if (countText != null)
+ {
+ countText.Text = state.CompletedCount.ToString();
+ }
+ }
+
+ ///
+ /// 定时更新
+ ///
+ private void OnUpdateTimerTick(object? sender, EventArgs e)
+ {
+ // 可以在这里添加时间显示等实时更新
+ }
+
+ ///
+ /// 获取阶段描述
+ ///
+ private static string GetStageDescription(StartupStage stage) => stage switch
+ {
+ StartupStage.Initializing => "正在初始化系统...",
+ StartupStage.LoadingSettings => "正在加载设置...",
+ StartupStage.LoadingPlugins => "正在加载插件...",
+ StartupStage.InitializingUI => "正在初始化界面...",
+ StartupStage.Ready => "加载完成",
+ _ => "正在加载..."
+ };
+
+ ///
+ /// 获取项描述
+ ///
+ private static string GetItemDescription(LoadingItem item)
+ {
+ if (!string.IsNullOrEmpty(item.Description))
+ return item.Description;
+
+ return item.Type switch
+ {
+ LoadingItemType.Plugin => "正在加载插件...",
+ LoadingItemType.Component => "正在加载组件...",
+ LoadingItemType.Resource => "正在加载资源...",
+ LoadingItemType.Data => "正在加载数据...",
+ LoadingItemType.Network => "正在下载...",
+ _ => "正在处理..."
+ };
+ }
+
+ ///
+ /// 获取项图标
+ ///
+ private static string GetItemIcon(LoadingItemType type) => type switch
+ {
+ LoadingItemType.Plugin => "\uE768",
+ LoadingItemType.Component => "\uE7C4",
+ LoadingItemType.Resource => "\uE7C5",
+ LoadingItemType.Data => "\uE7C6",
+ LoadingItemType.Network => "\uE774",
+ LoadingItemType.Settings => "\uE713",
+ LoadingItemType.System => "\uE7C7",
+ _ => "\uE768"
+ };
+
+ ///
+ /// 获取状态优先级
+ ///
+ private static int GetStatePriority(LoadingState state) => state switch
+ {
+ LoadingState.InProgress => 0,
+ LoadingState.Pending => 1,
+ LoadingState.Completed => 2,
+ LoadingState.Failed => 3,
+ LoadingState.Timeout => 4,
+ LoadingState.Cancelled => 5,
+ _ => 6
+ };
+}
+
+///
+/// 加载项视图模型
+///
+public class LoadingItemViewModel : INotifyPropertyChanged
+{
+ public string Id { get; }
+ public string Name { get; private set; }
+ public LoadingItemType Type { get; private set; }
+ public LoadingState State { get; private set; }
+ public int ProgressPercent { get; private set; }
+ public DateTimeOffset? CompletedTime { get; private set; }
+
+ public string StatusIcon => GetStatusIcon(State);
+ public IBrush StatusColor => GetStatusColor(State);
+ public string ProgressText => State == LoadingState.Completed ? "完成" : $"{ProgressPercent}%";
+ public string TypeLabel => GetTypeLabel(Type);
+ public IBrush TypeBackground => GetTypeBackground(Type);
+ public IBrush TypeForeground => GetTypeForeground(Type);
+ public double Opacity => State == LoadingState.Completed ? 0.6 : 1.0;
+
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ public LoadingItemViewModel(LoadingItem item)
+ {
+ Id = item.Id;
+ UpdateFrom(item);
+ }
+
+ public void UpdateFrom(LoadingItem item)
+ {
+ Name = item.Name;
+ Type = item.Type;
+ State = item.State;
+ ProgressPercent = item.ProgressPercent;
+
+ if (State == LoadingState.Completed && !CompletedTime.HasValue)
+ {
+ CompletedTime = DateTimeOffset.UtcNow;
+ }
+
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(string.Empty));
+ }
+
+ private static string GetStatusIcon(LoadingState state) => state switch
+ {
+ LoadingState.Pending => "\uE7C3",
+ LoadingState.InProgress => "\uE768",
+ LoadingState.Completed => "\uE73E",
+ LoadingState.Failed => "\uE783",
+ LoadingState.Timeout => "\uE71A",
+ LoadingState.Cancelled => "\uE711",
+ _ => "\uE7C3"
+ };
+
+ private static IBrush GetStatusColor(LoadingState state) => state switch
+ {
+ LoadingState.Pending => new SolidColorBrush(Colors.Gray),
+ LoadingState.InProgress => new SolidColorBrush(Colors.DodgerBlue),
+ LoadingState.Completed => new SolidColorBrush(Colors.Green),
+ LoadingState.Failed => new SolidColorBrush(Colors.Red),
+ LoadingState.Timeout => new SolidColorBrush(Colors.Orange),
+ LoadingState.Cancelled => new SolidColorBrush(Colors.Gray),
+ _ => new SolidColorBrush(Colors.Gray)
+ };
+
+ private static string GetTypeLabel(LoadingItemType type) => type switch
+ {
+ LoadingItemType.Plugin => "插件",
+ LoadingItemType.Component => "组件",
+ LoadingItemType.Resource => "资源",
+ LoadingItemType.Data => "数据",
+ LoadingItemType.Network => "网络",
+ LoadingItemType.Settings => "设置",
+ LoadingItemType.System => "系统",
+ _ => "其他"
+ };
+
+ private static IBrush GetTypeBackground(LoadingItemType type) => type switch
+ {
+ LoadingItemType.Plugin => new SolidColorBrush(Color.Parse("#E3F2FD")),
+ LoadingItemType.Component => new SolidColorBrush(Color.Parse("#F3E5F5")),
+ LoadingItemType.Resource => new SolidColorBrush(Color.Parse("#E8F5E9")),
+ LoadingItemType.Data => new SolidColorBrush(Color.Parse("#FFF3E0")),
+ LoadingItemType.Network => new SolidColorBrush(Color.Parse("#E0F7FA")),
+ _ => new SolidColorBrush(Color.Parse("#F5F5F5"))
+ };
+
+ private static IBrush GetTypeForeground(LoadingItemType type) => type switch
+ {
+ LoadingItemType.Plugin => new SolidColorBrush(Color.Parse("#1976D2")),
+ LoadingItemType.Component => new SolidColorBrush(Color.Parse("#7B1FA2")),
+ LoadingItemType.Resource => new SolidColorBrush(Color.Parse("#388E3C")),
+ LoadingItemType.Data => new SolidColorBrush(Color.Parse("#F57C00")),
+ LoadingItemType.Network => new SolidColorBrush(Color.Parse("#0097A7")),
+ _ => new SolidColorBrush(Color.Parse("#616161"))
+ };
+}
diff --git a/LanMountainDesktop.Shared.Contracts/Launcher/LoadingState.cs b/LanMountainDesktop.Shared.Contracts/Launcher/LoadingState.cs
new file mode 100644
index 0000000..a4f1ea8
--- /dev/null
+++ b/LanMountainDesktop.Shared.Contracts/Launcher/LoadingState.cs
@@ -0,0 +1,231 @@
+namespace LanMountainDesktop.Shared.Contracts.Launcher;
+
+///
+/// 加载项类型
+///
+public enum LoadingItemType
+{
+ ///
+ /// 系统初始化
+ ///
+ System,
+
+ ///
+ /// 设置加载
+ ///
+ Settings,
+
+ ///
+ /// 插件
+ ///
+ Plugin,
+
+ ///
+ /// 组件
+ ///
+ Component,
+
+ ///
+ /// 资源
+ ///
+ Resource,
+
+ ///
+ /// 数据
+ ///
+ Data,
+
+ ///
+ /// 网络请求
+ ///
+ Network,
+
+ ///
+ /// 其他
+ ///
+ Other
+}
+
+///
+/// 加载状态
+///
+public enum LoadingState
+{
+ ///
+ /// 等待中
+ ///
+ Pending,
+
+ ///
+ /// 进行中
+ ///
+ InProgress,
+
+ ///
+ /// 已完成
+ ///
+ Completed,
+
+ ///
+ /// 失败
+ ///
+ Failed,
+
+ ///
+ /// 已取消
+ ///
+ Cancelled,
+
+ ///
+ /// 超时
+ ///
+ Timeout
+}
+
+///
+/// 加载项信息
+///
+public record LoadingItem
+{
+ ///
+ /// 加载项唯一标识
+ ///
+ public required string Id { get; init; }
+
+ ///
+ /// 加载项类型
+ ///
+ public LoadingItemType Type { get; init; }
+
+ ///
+ /// 加载项名称
+ ///
+ public required string Name { get; init; }
+
+ ///
+ /// 加载项描述
+ ///
+ public string? Description { get; init; }
+
+ ///
+ /// 当前状态
+ ///
+ public LoadingState State { get; init; }
+
+ ///
+ /// 进度百分比 (0-100)
+ ///
+ public int ProgressPercent { get; init; }
+
+ ///
+ /// 状态消息
+ ///
+ public string? Message { get; init; }
+
+ ///
+ /// 错误信息(当 State 为 Failed 时)
+ ///
+ public string? ErrorMessage { get; init; }
+
+ ///
+ /// 开始时间
+ ///
+ public DateTimeOffset? StartTime { get; init; }
+
+ ///
+ /// 结束时间
+ ///
+ public DateTimeOffset? EndTime { get; init; }
+
+ ///
+ /// 预计剩余时间(秒)
+ ///
+ public int? EstimatedRemainingSeconds { get; init; }
+
+ ///
+ /// 子加载项
+ ///
+ public List? Children { get; init; }
+
+ ///
+ /// 额外数据
+ ///
+ public Dictionary? Metadata { get; init; }
+
+ ///
+ /// 时间戳
+ ///
+ public DateTimeOffset Timestamp { get; init; } = DateTimeOffset.UtcNow;
+}
+
+///
+/// 加载状态更新消息
+///
+public record LoadingStateMessage
+{
+ ///
+ /// 当前启动阶段
+ ///
+ public StartupStage Stage { get; init; }
+
+ ///
+ /// 整体进度百分比 (0-100)
+ ///
+ public int OverallProgressPercent { get; init; }
+
+ ///
+ /// 当前活动的加载项
+ ///
+ public List ActiveItems { get; init; } = new();
+
+ ///
+ /// 已完成的加载项数量
+ ///
+ public int CompletedCount { get; init; }
+
+ ///
+ /// 总加载项数量
+ ///
+ public int TotalCount { get; init; }
+
+ ///
+ /// 状态消息
+ ///
+ public string? Message { get; init; }
+
+ ///
+ /// 是否有错误
+ ///
+ public bool HasErrors { get; init; }
+
+ ///
+ /// 错误消息列表
+ ///
+ public List? ErrorMessages { get; init; }
+
+ ///
+ /// 时间戳
+ ///
+ public DateTimeOffset Timestamp { get; init; } = DateTimeOffset.UtcNow;
+}
+
+///
+/// 详细的加载进度消息(用于实时更新)
+///
+public record DetailedProgressMessage : StartupProgressMessage
+{
+ ///
+ /// 当前加载项
+ ///
+ public LoadingItem? CurrentItem { get; init; }
+
+ ///
+ /// 所有加载项
+ ///
+ public List? AllItems { get; init; }
+
+ ///
+ /// 是否为主要更新
+ ///
+ public bool IsMajorUpdate { get; init; }
+}
diff --git a/LanMountainDesktop/App.axaml.cs b/LanMountainDesktop/App.axaml.cs
index 03d303c..206b754 100644
--- a/LanMountainDesktop/App.axaml.cs
+++ b/LanMountainDesktop/App.axaml.cs
@@ -20,6 +20,7 @@ using LanMountainDesktop.Models;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Launcher;
+using LanMountainDesktop.Services.Loading;
using LanMountainDesktop.Services.Settings;
using LanMountainDesktop.Shared.Contracts.Launcher;
using LanMountainDesktop.Theme;
@@ -74,6 +75,8 @@ public partial class App : Application
private bool _uiUnhandledExceptionHooked;
private DesktopShellHost? _desktopShellHost;
private LauncherIpcClient? _launcherIpcClient;
+ private LoadingStateManager? _loadingStateManager;
+ private LoadingStateReporter? _loadingStateReporter;
internal static SingleInstanceService? CurrentSingleInstanceService { get; set; }
internal static IHostApplicationLifecycle? CurrentHostApplicationLifecycle =>
@@ -178,6 +181,16 @@ public partial class App : Application
if (connected)
{
AppLogger.Info("LauncherIpc", "Connected to Launcher IPC server.");
+
+ // 初始化加载状态管理器
+ _loadingStateManager = new LoadingStateManager();
+ _loadingStateReporter = new LoadingStateReporter(_loadingStateManager, _launcherIpcClient);
+ _loadingStateReporter.Start();
+
+ // 注册系统初始化加载项
+ _loadingStateManager.RegisterItem("system.init", LoadingItemType.System, "系统初始化", "初始化系统核心组件");
+ _loadingStateManager.StartItem("system.init", "正在连接启动器...");
+
ReportStartupProgress(StartupStage.Initializing, 10, "正在初始化...");
}
}
@@ -213,6 +226,41 @@ public partial class App : Application
});
}
+ ///
+ /// 同步向 Launcher 报告启动进度,确保关键消息可靠送达
+ /// 用于 Ready 等关键状态报告
+ ///
+ private void ReportStartupProgressSync(StartupStage stage, int percent, string message)
+ {
+ if (_launcherIpcClient is null)
+ return;
+
+ try
+ {
+ // 使用同步等待确保消息发送完成
+ var task = _launcherIpcClient.ReportProgressAsync(new StartupProgressMessage
+ {
+ Stage = stage,
+ ProgressPercent = percent,
+ Message = message
+ });
+
+ // 等待最多 5 秒,确保消息发送成功
+ if (!task.Wait(TimeSpan.FromSeconds(5)))
+ {
+ AppLogger.Warn("LauncherIpc", "Report progress timeout after 5 seconds");
+ }
+ else
+ {
+ AppLogger.Info("LauncherIpc", $"Successfully reported stage: {stage}");
+ }
+ }
+ catch (Exception ex)
+ {
+ AppLogger.Warn("LauncherIpc", $"Failed to report progress synchronously: {ex.Message}");
+ }
+ }
+
private void ApplyDesignTimeTheme()
{
RequestedThemeVariant = ThemeVariant.Light;
@@ -927,10 +975,36 @@ public partial class App : Application
AppLogger.Info("App", $"Main window created. Reason='{reason}'. LogFile={AppLogger.LogFilePath}");
LogBrowserStartupDiagnostics();
SetDesktopShellState(DesktopShellState.ForegroundDesktop, $"MainWindowCreated:{reason}");
- ReportStartupProgress(StartupStage.Ready, 100, "就绪");
+
+ // 延迟报告 Ready 直到窗口实际打开并可见
+ // 使用 Opened 事件确保所有资源已加载完毕
+ mainWindow.Opened += OnMainWindowOpened;
+
return mainWindow;
}
+ ///
+ /// 主窗口打开完成事件 - 此时所有组件、资源及功能模块均已完全加载
+ ///
+ private void OnMainWindowOpened(object? sender, EventArgs e)
+ {
+ if (sender is MainWindow mainWindow)
+ {
+ mainWindow.Opened -= OnMainWindowOpened;
+
+ AppLogger.Info("App", "Main window opened and ready. Reporting Ready to Launcher...");
+
+ // 完成系统初始化加载项
+ _loadingStateManager?.CompleteItem("system.init", "系统初始化完成");
+
+ // 报告 Ready 状态,启动器可以安全关闭 Splash 窗口
+ ReportStartupProgressSync(StartupStage.Ready, 100, "就绪");
+
+ // 停止加载状态上报
+ _loadingStateReporter?.Stop();
+ }
+ }
+
private MainWindow GetOrCreateMainWindow(
IClassicDesktopStyleApplicationLifetime desktop,
string reason)
diff --git a/LanMountainDesktop/Services/Loading/LoadingStateManager.cs b/LanMountainDesktop/Services/Loading/LoadingStateManager.cs
new file mode 100644
index 0000000..55234b0
--- /dev/null
+++ b/LanMountainDesktop/Services/Loading/LoadingStateManager.cs
@@ -0,0 +1,380 @@
+using System.Collections.Concurrent;
+using LanMountainDesktop.Shared.Contracts.Launcher;
+
+namespace LanMountainDesktop.Services.Loading;
+
+///
+/// 加载状态管理器 - 管理所有加载项的状态
+///
+public class LoadingStateManager : IDisposable
+{
+ private readonly ConcurrentDictionary _items = new();
+ private readonly ConcurrentDictionary _startTimes = new();
+ private readonly object _lock = new();
+ private readonly CancellationTokenSource _cts = new();
+
+ ///
+ /// 状态变更事件
+ ///
+ public event EventHandler? StateChanged;
+
+ ///
+ /// 整体进度变更事件
+ ///
+ public event EventHandler? OverallProgressChanged;
+
+ ///
+ /// 当前启动阶段
+ ///
+ public StartupStage CurrentStage { get; private set; } = StartupStage.Initializing;
+
+ ///
+ /// 整体进度百分比
+ ///
+ public int OverallProgressPercent { get; private set; }
+
+ ///
+ /// 是否正在加载
+ ///
+ public bool IsLoading => _items.Values.Any(i => i.State == LoadingState.InProgress);
+
+ ///
+ /// 是否有错误
+ ///
+ public bool HasErrors => _items.Values.Any(i => i.State == LoadingState.Failed);
+
+ ///
+ /// 获取所有加载项
+ ///
+ public IReadOnlyCollection GetAllItems() => _items.Values.ToList();
+
+ ///
+ /// 获取活动的加载项
+ ///
+ public IReadOnlyCollection GetActiveItems() =>
+ _items.Values.Where(i => i.State is LoadingState.InProgress or LoadingState.Pending).ToList();
+
+ ///
+ /// 注册加载项
+ ///
+ public LoadingItem RegisterItem(
+ string id,
+ LoadingItemType type,
+ string name,
+ string? description = null,
+ Dictionary? metadata = null)
+ {
+ var item = new LoadingItem
+ {
+ Id = id,
+ Type = type,
+ Name = name,
+ Description = description,
+ State = LoadingState.Pending,
+ ProgressPercent = 0,
+ Metadata = metadata,
+ Timestamp = DateTimeOffset.UtcNow
+ };
+
+ _items[id] = item;
+
+ StateChanged?.Invoke(this, new LoadingStateChangedEventArgs
+ {
+ Item = item,
+ PreviousState = null,
+ CurrentState = item.State
+ });
+
+ return item;
+ }
+
+ ///
+ /// 开始加载
+ ///
+ public void StartItem(string id, string? message = null)
+ {
+ if (!_items.TryGetValue(id, out var item))
+ return;
+
+ var previousState = item.State;
+ var startTime = DateTimeOffset.UtcNow;
+
+ _startTimes[id] = startTime;
+
+ var updatedItem = item with
+ {
+ State = LoadingState.InProgress,
+ StartTime = startTime,
+ Message = message ?? $"正在加载 {item.Name}...",
+ Timestamp = DateTimeOffset.UtcNow
+ };
+
+ _items[id] = updatedItem;
+
+ StateChanged?.Invoke(this, new LoadingStateChangedEventArgs
+ {
+ Item = updatedItem,
+ PreviousState = previousState,
+ CurrentState = updatedItem.State
+ });
+
+ UpdateOverallProgress();
+ }
+
+ ///
+ /// 更新进度
+ ///
+ public void UpdateProgress(string id, int percent, string? message = null, int? estimatedRemainingSeconds = null)
+ {
+ if (!_items.TryGetValue(id, out var item))
+ return;
+
+ var updatedItem = item with
+ {
+ ProgressPercent = Math.Clamp(percent, 0, 100),
+ Message = message ?? item.Message,
+ EstimatedRemainingSeconds = estimatedRemainingSeconds,
+ Timestamp = DateTimeOffset.UtcNow
+ };
+
+ _items[id] = updatedItem;
+
+ StateChanged?.Invoke(this, new LoadingStateChangedEventArgs
+ {
+ Item = updatedItem,
+ PreviousState = item.State,
+ CurrentState = updatedItem.State,
+ IsProgressUpdate = true
+ });
+
+ UpdateOverallProgress();
+ }
+
+ ///
+ /// 完成加载
+ ///
+ public void CompleteItem(string id, string? message = null)
+ {
+ if (!_items.TryGetValue(id, out var item))
+ return;
+
+ var previousState = item.State;
+ var endTime = DateTimeOffset.UtcNow;
+
+ _startTimes.TryRemove(id, out _);
+
+ var updatedItem = item with
+ {
+ State = LoadingState.Completed,
+ ProgressPercent = 100,
+ EndTime = endTime,
+ Message = message ?? $"{item.Name} 加载完成",
+ Timestamp = DateTimeOffset.UtcNow
+ };
+
+ _items[id] = updatedItem;
+
+ StateChanged?.Invoke(this, new LoadingStateChangedEventArgs
+ {
+ Item = updatedItem,
+ PreviousState = previousState,
+ CurrentState = updatedItem.State
+ });
+
+ UpdateOverallProgress();
+ }
+
+ ///
+ /// 标记失败
+ ///
+ public void FailItem(string id, string errorMessage, string? details = null)
+ {
+ if (!_items.TryGetValue(id, out var item))
+ return;
+
+ var previousState = item.State;
+ var endTime = DateTimeOffset.UtcNow;
+
+ _startTimes.TryRemove(id, out _);
+
+ var fullErrorMessage = string.IsNullOrEmpty(details)
+ ? errorMessage
+ : $"{errorMessage}: {details}";
+
+ var updatedItem = item with
+ {
+ State = LoadingState.Failed,
+ ErrorMessage = fullErrorMessage,
+ EndTime = endTime,
+ Message = $"{item.Name} 加载失败",
+ Timestamp = DateTimeOffset.UtcNow
+ };
+
+ _items[id] = updatedItem;
+
+ StateChanged?.Invoke(this, new LoadingStateChangedEventArgs
+ {
+ Item = updatedItem,
+ PreviousState = previousState,
+ CurrentState = updatedItem.State
+ });
+
+ UpdateOverallProgress();
+ }
+
+ ///
+ /// 标记超时
+ ///
+ public void TimeoutItem(string id, string? message = null)
+ {
+ if (!_items.TryGetValue(id, out var item))
+ return;
+
+ var previousState = item.State;
+ var endTime = DateTimeOffset.UtcNow;
+
+ _startTimes.TryRemove(id, out _);
+
+ var updatedItem = item with
+ {
+ State = LoadingState.Timeout,
+ EndTime = endTime,
+ Message = message ?? $"{item.Name} 加载超时",
+ Timestamp = DateTimeOffset.UtcNow
+ };
+
+ _items[id] = updatedItem;
+
+ StateChanged?.Invoke(this, new LoadingStateChangedEventArgs
+ {
+ Item = updatedItem,
+ PreviousState = previousState,
+ CurrentState = updatedItem.State
+ });
+
+ UpdateOverallProgress();
+ }
+
+ ///
+ /// 设置当前启动阶段
+ ///
+ public void SetStage(StartupStage stage, string? message = null)
+ {
+ CurrentStage = stage;
+
+ OverallProgressChanged?.Invoke(this, new OverallProgressChangedEventArgs
+ {
+ Stage = stage,
+ OverallProgressPercent = OverallProgressPercent,
+ Message = message
+ });
+ }
+
+ ///
+ /// 更新整体进度
+ ///
+ private void UpdateOverallProgress()
+ {
+ lock (_lock)
+ {
+ var items = _items.Values.ToList();
+ if (items.Count == 0)
+ {
+ OverallProgressPercent = 0;
+ return;
+ }
+
+ // 计算加权进度
+ var totalWeight = items.Count;
+ var completedWeight = items.Count(i => i.State == LoadingState.Completed);
+ var inProgressWeight = items
+ .Where(i => i.State == LoadingState.InProgress)
+ .Sum(i => i.ProgressPercent / 100.0);
+
+ var progress = (int)((completedWeight + inProgressWeight) / totalWeight * 100);
+ OverallProgressPercent = Math.Clamp(progress, 0, 100);
+
+ OverallProgressChanged?.Invoke(this, new OverallProgressChangedEventArgs
+ {
+ Stage = CurrentStage,
+ OverallProgressPercent = OverallProgressPercent
+ });
+ }
+ }
+
+ ///
+ /// 获取加载状态消息
+ ///
+ public LoadingStateMessage GetLoadingStateMessage()
+ {
+ var items = _items.Values.ToList();
+ var activeItems = items.Where(i => i.State is LoadingState.InProgress or LoadingState.Pending).ToList();
+ var errorItems = items.Where(i => i.State == LoadingState.Failed).ToList();
+
+ return new LoadingStateMessage
+ {
+ Stage = CurrentStage,
+ OverallProgressPercent = OverallProgressPercent,
+ ActiveItems = activeItems,
+ CompletedCount = items.Count(i => i.State == LoadingState.Completed),
+ TotalCount = items.Count,
+ HasErrors = errorItems.Any(),
+ ErrorMessages = errorItems.Select(i => $"{i.Name}: {i.ErrorMessage}").ToList()
+ };
+ }
+
+ ///
+ /// 清理所有加载项
+ ///
+ public void Clear()
+ {
+ _items.Clear();
+ _startTimes.Clear();
+ OverallProgressPercent = 0;
+ }
+
+ ///
+ /// 检查超时项
+ ///
+ public void CheckTimeouts(TimeSpan timeout)
+ {
+ var now = DateTimeOffset.UtcNow;
+ var timeoutItems = _items.Values
+ .Where(i => i.State == LoadingState.InProgress && i.StartTime.HasValue)
+ .Where(i => now - i.StartTime.Value > timeout)
+ .ToList();
+
+ foreach (var item in timeoutItems)
+ {
+ TimeoutItem(item.Id, $"{item.Name} 加载超时(超过 {timeout.TotalSeconds} 秒)");
+ }
+ }
+
+ public void Dispose()
+ {
+ _cts.Cancel();
+ _items.Clear();
+ _startTimes.Clear();
+ }
+}
+
+///
+/// 加载状态变更事件参数
+///
+public class LoadingStateChangedEventArgs : EventArgs
+{
+ public required LoadingItem Item { get; init; }
+ public LoadingState? PreviousState { get; init; }
+ public required LoadingState CurrentState { get; init; }
+ public bool IsProgressUpdate { get; init; }
+}
+
+///
+/// 整体进度变更事件参数
+///
+public class OverallProgressChangedEventArgs : EventArgs
+{
+ public StartupStage Stage { get; init; }
+ public int OverallProgressPercent { get; init; }
+ public string? Message { get; init; }
+}
diff --git a/LanMountainDesktop/Services/Loading/LoadingStateReporter.cs b/LanMountainDesktop/Services/Loading/LoadingStateReporter.cs
new file mode 100644
index 0000000..d8f817e
--- /dev/null
+++ b/LanMountainDesktop/Services/Loading/LoadingStateReporter.cs
@@ -0,0 +1,360 @@
+using System.Timers;
+using LanMountainDesktop.Services.Launcher;
+using LanMountainDesktop.Shared.Contracts.Launcher;
+
+namespace LanMountainDesktop.Services.Loading;
+
+///
+/// 加载状态上报器 - 将加载状态实时上报给 Launcher
+///
+public class LoadingStateReporter : IDisposable
+{
+ private readonly LoadingStateManager _manager;
+ private readonly LauncherIpcClient? _ipcClient;
+ private readonly System.Timers.Timer _reportTimer;
+ private readonly object _lock = new();
+ private bool _isDisposed;
+
+ ///
+ /// 上报间隔(毫秒)
+ ///
+ public int ReportIntervalMs { get; set; } = 100;
+
+ ///
+ /// 是否启用批量上报优化
+ ///
+ public bool EnableBatching { get; set; } = true;
+
+ ///
+ /// 最小上报间隔(毫秒),用于限制高频更新
+ ///
+ public int MinReportIntervalMs { get; set; } = 50;
+
+ private DateTimeOffset _lastReportTime = DateTimeOffset.MinValue;
+ private DetailedProgressMessage? _pendingMessage;
+ private bool _hasPendingMessage;
+
+ public LoadingStateReporter(
+ LoadingStateManager manager,
+ LauncherIpcClient? ipcClient = null)
+ {
+ _manager = manager ?? throw new ArgumentNullException(nameof(manager));
+ _ipcClient = ipcClient;
+
+ // 创建定时上报定时器
+ _reportTimer = new System.Timers.Timer(ReportIntervalMs);
+ _reportTimer.Elapsed += OnReportTimerElapsed;
+ _reportTimer.AutoReset = true;
+
+ // 订阅状态变更事件
+ _manager.StateChanged += OnStateChanged;
+ _manager.OverallProgressChanged += OnOverallProgressChanged;
+ }
+
+ ///
+ /// 启动上报
+ ///
+ public void Start()
+ {
+ if (_isDisposed) return;
+
+ _reportTimer.Start();
+ AppLogger.Info("LoadingStateReporter", "Loading state reporter started");
+ }
+
+ ///
+ /// 停止上报
+ ///
+ public void Stop()
+ {
+ _reportTimer.Stop();
+
+ // 发送任何待处理的消息
+ FlushPendingMessage();
+
+ AppLogger.Info("LoadingStateReporter", "Loading state reporter stopped");
+ }
+
+ ///
+ /// 立即上报当前状态
+ ///
+ public async Task ReportImmediatelyAsync()
+ {
+ if (_isDisposed || _ipcClient == null) return;
+
+ var message = CreateDetailedProgressMessage();
+ await SendMessageAsync(message);
+ }
+
+ ///
+ /// 上报单个加载项的进度
+ ///
+ public async Task ReportItemProgressAsync(string itemId, int percent, string? message = null)
+ {
+ if (_isDisposed || _ipcClient == null) return;
+
+ var item = _manager.GetAllItems().FirstOrDefault(i => i.Id == itemId);
+ if (item == null) return;
+
+ var updatedItem = item with
+ {
+ ProgressPercent = percent,
+ Message = message ?? item.Message,
+ Timestamp = DateTimeOffset.UtcNow
+ };
+
+ var progressMessage = new DetailedProgressMessage
+ {
+ Stage = _manager.CurrentStage,
+ ProgressPercent = _manager.OverallProgressPercent,
+ CurrentItem = updatedItem,
+ AllItems = _manager.GetAllItems().ToList(),
+ Message = message,
+ IsMajorUpdate = false
+ };
+
+ await SendMessageAsync(progressMessage);
+ }
+
+ ///
+ /// 上报阶段变更
+ ///
+ public async Task ReportStageChangeAsync(StartupStage stage, string? message = null)
+ {
+ if (_isDisposed || _ipcClient == null) return;
+
+ var progressMessage = new DetailedProgressMessage
+ {
+ Stage = stage,
+ ProgressPercent = _manager.OverallProgressPercent,
+ AllItems = _manager.GetAllItems().ToList(),
+ Message = message ?? $"进入阶段: {stage}",
+ IsMajorUpdate = true
+ };
+
+ await SendMessageAsync(progressMessage);
+ }
+
+ ///
+ /// 上报错误
+ ///
+ public async Task ReportErrorAsync(string errorMessage, string? details = null)
+ {
+ if (_isDisposed || _ipcClient == null) return;
+
+ var fullMessage = string.IsNullOrEmpty(details)
+ ? errorMessage
+ : $"{errorMessage}: {details}";
+
+ var progressMessage = new DetailedProgressMessage
+ {
+ Stage = _manager.CurrentStage,
+ ProgressPercent = _manager.OverallProgressPercent,
+ AllItems = _manager.GetAllItems().ToList(),
+ Message = fullMessage,
+ IsMajorUpdate = true
+ };
+
+ await SendMessageAsync(progressMessage);
+ }
+
+ ///
+ /// 状态变更事件处理
+ ///
+ private void OnStateChanged(object? sender, LoadingStateChangedEventArgs e)
+ {
+ if (_isDisposed) return;
+
+ // 重要状态变更立即上报
+ if (e.CurrentState is LoadingState.Completed or LoadingState.Failed or LoadingState.Timeout)
+ {
+ _ = Task.Run(async () =>
+ {
+ try
+ {
+ await ReportImmediatelyAsync();
+ }
+ catch (Exception ex)
+ {
+ AppLogger.Warn("LoadingStateReporter", $"Failed to report state change: {ex.Message}");
+ }
+ });
+ }
+ else
+ {
+ // 其他状态变更标记为待处理
+ QueueMessage(CreateDetailedProgressMessage());
+ }
+ }
+
+ ///
+ /// 整体进度变更事件处理
+ ///
+ private void OnOverallProgressChanged(object? sender, OverallProgressChangedEventArgs e)
+ {
+ if (_isDisposed) return;
+
+ QueueMessage(CreateDetailedProgressMessage(e.Message));
+ }
+
+ ///
+ /// 定时上报处理
+ ///
+ private void OnReportTimerElapsed(object? sender, ElapsedEventArgs e)
+ {
+ FlushPendingMessage();
+ }
+
+ ///
+ /// 将消息加入待处理队列
+ ///
+ private void QueueMessage(DetailedProgressMessage message)
+ {
+ if (!EnableBatching)
+ {
+ // 如果不启用批量,立即发送
+ _ = Task.Run(async () => await SendMessageAsync(message));
+ return;
+ }
+
+ lock (_lock)
+ {
+ _pendingMessage = message;
+ _hasPendingMessage = true;
+ }
+ }
+
+ ///
+ /// 刷新待处理消息
+ ///
+ private void FlushPendingMessage()
+ {
+ DetailedProgressMessage? message;
+
+ lock (_lock)
+ {
+ if (!_hasPendingMessage) return;
+
+ message = _pendingMessage;
+ _pendingMessage = null;
+ _hasPendingMessage = false;
+ }
+
+ if (message != null)
+ {
+ _ = Task.Run(async () =>
+ {
+ try
+ {
+ await SendMessageAsync(message);
+ }
+ catch (Exception ex)
+ {
+ AppLogger.Warn("LoadingStateReporter", $"Failed to flush pending message: {ex.Message}");
+ }
+ });
+ }
+ }
+
+ ///
+ /// 创建详细的进度消息
+ ///
+ private DetailedProgressMessage CreateDetailedProgressMessage(string? message = null)
+ {
+ var activeItems = _manager.GetActiveItems().ToList();
+ var currentItem = activeItems.FirstOrDefault();
+
+ return new DetailedProgressMessage
+ {
+ Stage = _manager.CurrentStage,
+ ProgressPercent = _manager.OverallProgressPercent,
+ CurrentItem = currentItem,
+ AllItems = _manager.GetAllItems().ToList(),
+ Message = message ?? currentItem?.Message,
+ IsMajorUpdate = false
+ };
+ }
+
+ ///
+ /// 发送消息
+ ///
+ private async Task SendMessageAsync(DetailedProgressMessage message)
+ {
+ if (_ipcClient == null) return;
+
+ // 检查最小上报间隔
+ var now = DateTimeOffset.UtcNow;
+ var elapsed = now - _lastReportTime;
+ if (elapsed.TotalMilliseconds < MinReportIntervalMs)
+ {
+ await Task.Delay(MinReportIntervalMs - (int)elapsed.TotalMilliseconds);
+ }
+
+ try
+ {
+ // 转换为 StartupProgressMessage 以保持兼容性
+ var baseMessage = new StartupProgressMessage
+ {
+ Stage = message.Stage,
+ ProgressPercent = message.ProgressPercent,
+ Message = FormatMessage(message),
+ Timestamp = DateTimeOffset.UtcNow
+ };
+
+ await _ipcClient.ReportProgressAsync(baseMessage);
+ _lastReportTime = DateTimeOffset.UtcNow;
+ }
+ catch (Exception ex)
+ {
+ AppLogger.Warn("LoadingStateReporter", $"Failed to send message: {ex.Message}");
+ }
+ }
+
+ ///
+ /// 格式化消息
+ ///
+ private string FormatMessage(DetailedProgressMessage message)
+ {
+ var parts = new List();
+
+ if (message.CurrentItem != null)
+ {
+ parts.Add($"[{message.CurrentItem.Type}] {message.CurrentItem.Name}");
+
+ if (message.CurrentItem.ProgressPercent > 0)
+ {
+ parts.Add($"{message.CurrentItem.ProgressPercent}%");
+ }
+ }
+
+ if (!string.IsNullOrEmpty(message.Message))
+ {
+ parts.Add(message.Message);
+ }
+
+ var completedCount = message.AllItems?.Count(i => i.State == LoadingState.Completed) ?? 0;
+ var totalCount = message.AllItems?.Count ?? 0;
+
+ if (totalCount > 0)
+ {
+ parts.Add($"({completedCount}/{totalCount})");
+ }
+
+ return string.Join(" - ", parts);
+ }
+
+ public void Dispose()
+ {
+ if (_isDisposed) return;
+
+ _isDisposed = true;
+
+ Stop();
+
+ _reportTimer.Elapsed -= OnReportTimerElapsed;
+ _reportTimer.Dispose();
+
+ _manager.StateChanged -= OnStateChanged;
+ _manager.OverallProgressChanged -= OnOverallProgressChanged;
+ }
+}
diff --git a/LanMountainDesktop/Services/Loading/LoadingStateUsageExample.cs b/LanMountainDesktop/Services/Loading/LoadingStateUsageExample.cs
new file mode 100644
index 0000000..905684e
--- /dev/null
+++ b/LanMountainDesktop/Services/Loading/LoadingStateUsageExample.cs
@@ -0,0 +1,201 @@
+using LanMountainDesktop.Shared.Contracts.Launcher;
+
+namespace LanMountainDesktop.Services.Loading;
+
+///
+/// 加载状态管理使用示例
+///
+public static class LoadingStateUsageExample
+{
+ ///
+ /// 示例:插件加载
+ ///
+ public static async Task LoadPluginsExample(LoadingStateManager manager)
+ {
+ // 注册插件加载项
+ var pluginItem = manager.RegisterItem(
+ "plugins.core",
+ LoadingItemType.Plugin,
+ "核心插件",
+ "加载系统核心插件",
+ new Dictionary { { "version", "1.0.0" } });
+
+ // 开始加载
+ manager.StartItem("plugins.core", "正在下载插件...");
+
+ try
+ {
+ // 模拟下载进度
+ for (int i = 0; i <= 100; i += 10)
+ {
+ manager.UpdateProgress(
+ "plugins.core",
+ i,
+ $"正在下载... {i}%",
+ estimatedRemainingSeconds: (100 - i) / 10);
+
+ await Task.Delay(100);
+ }
+
+ // 完成加载
+ manager.CompleteItem("plugins.core", "核心插件加载完成");
+ }
+ catch (Exception ex)
+ {
+ // 标记失败
+ manager.FailItem("plugins.core", "插件加载失败", ex.Message);
+ }
+ }
+
+ ///
+ /// 示例:组件加载
+ ///
+ public static async Task LoadComponentsExample(LoadingStateManager manager)
+ {
+ var components = new[]
+ {
+ ("comp.weather", "天气组件"),
+ ("comp.clock", "时钟组件"),
+ ("comp.calendar", "日历组件")
+ };
+
+ foreach (var (id, name) in components)
+ {
+ // 注册组件
+ manager.RegisterItem(id, LoadingItemType.Component, name);
+
+ // 开始加载
+ manager.StartItem(id, $"正在加载 {name}...");
+
+ // 模拟加载过程
+ for (int i = 0; i <= 100; i += 20)
+ {
+ manager.UpdateProgress(id, i);
+ await Task.Delay(50);
+ }
+
+ // 完成
+ manager.CompleteItem(id, $"{name} 加载完成");
+ }
+ }
+
+ ///
+ /// 示例:网络资源加载
+ ///
+ public static async Task LoadNetworkResourcesExample(LoadingStateManager manager)
+ {
+ // 注册网络加载项
+ manager.RegisterItem(
+ "network.config",
+ LoadingItemType.Network,
+ "配置数据",
+ "从服务器获取最新配置");
+
+ manager.StartItem("network.config", "正在连接服务器...");
+
+ try
+ {
+ // 模拟网络请求
+ await Task.Delay(1000);
+
+ manager.UpdateProgress("network.config", 50, "正在下载数据...");
+
+ await Task.Delay(1000);
+
+ manager.CompleteItem("network.config", "配置数据已更新");
+ }
+ catch (Exception ex)
+ {
+ manager.FailItem("network.config", "网络请求失败", ex.Message);
+ }
+ }
+
+ ///
+ /// 示例:带超时的加载
+ ///
+ public static async Task LoadWithTimeoutExample(
+ LoadingStateManager manager,
+ LoadingTimeoutHandler timeoutHandler)
+ {
+ // 设置超时时间为 10 秒
+ timeoutHandler.SetItemTimeout("data.heavy", TimeSpan.FromSeconds(10));
+
+ // 注册加载项
+ manager.RegisterItem(
+ "data.heavy",
+ LoadingItemType.Data,
+ "大数据处理",
+ "处理大量数据,可能需要较长时间");
+
+ // 订阅超时事件
+ timeoutHandler.ItemTimeout += (s, e) =>
+ {
+ Console.WriteLine($"加载项 '{e.ItemName}' 超时!");
+ };
+
+ timeoutHandler.ItemRetry += (s, e) =>
+ {
+ Console.WriteLine($"正在重试 '{e.ItemName}' ({e.RetryCount}/{e.MaxRetryCount})...");
+ };
+
+ // 开始加载
+ manager.StartItem("data.heavy", "正在处理数据...");
+
+ // 模拟长时间操作
+ await Task.Delay(15000);
+
+ // 完成
+ manager.CompleteItem("data.heavy", "数据处理完成");
+ }
+
+ ///
+ /// 示例:完整启动流程
+ ///
+ public static async Task FullStartupExample(
+ LoadingStateManager manager,
+ LoadingStateReporter reporter,
+ LoadingTimeoutHandler timeoutHandler)
+ {
+ // 启动超时处理器
+ timeoutHandler.Start();
+
+ // 设置阶段
+ manager.SetStage(StartupStage.Initializing, "开始初始化...");
+
+ // 1. 系统初始化
+ manager.RegisterItem("system.init", LoadingItemType.System, "系统初始化");
+ manager.StartItem("system.init");
+ await Task.Delay(500);
+ manager.CompleteItem("system.init");
+
+ // 2. 加载设置
+ manager.SetStage(StartupStage.LoadingSettings, "正在加载设置...");
+ manager.RegisterItem("settings.load", LoadingItemType.Settings, "用户设置");
+ manager.StartItem("settings.load");
+ await Task.Delay(800);
+ manager.CompleteItem("settings.load");
+
+ // 3. 加载插件
+ manager.SetStage(StartupStage.LoadingPlugins, "正在加载插件...");
+ await LoadPluginsExample(manager);
+
+ // 4. 加载组件
+ await LoadComponentsExample(manager);
+
+ // 5. 加载网络资源
+ await LoadNetworkResourcesExample(manager);
+
+ // 6. 初始化界面
+ manager.SetStage(StartupStage.InitializingUI, "正在初始化界面...");
+ manager.RegisterItem("ui.init", LoadingItemType.System, "界面初始化");
+ manager.StartItem("ui.init");
+ await Task.Delay(600);
+ manager.CompleteItem("ui.init");
+
+ // 完成
+ manager.SetStage(StartupStage.Ready, "加载完成");
+
+ // 停止超时处理器
+ timeoutHandler.Stop();
+ }
+}
diff --git a/LanMountainDesktop/Services/Loading/LoadingTimeoutHandler.cs b/LanMountainDesktop/Services/Loading/LoadingTimeoutHandler.cs
new file mode 100644
index 0000000..a163c62
--- /dev/null
+++ b/LanMountainDesktop/Services/Loading/LoadingTimeoutHandler.cs
@@ -0,0 +1,274 @@
+using System.Timers;
+
+namespace LanMountainDesktop.Services.Loading;
+
+///
+/// 加载超时处理器 - 监控加载项超时并执行相应处理
+///
+public class LoadingTimeoutHandler : IDisposable
+{
+ private readonly LoadingStateManager _manager;
+ private readonly System.Timers.Timer _checkTimer;
+ private readonly Dictionary _itemTimeouts = new();
+ private readonly Dictionary _retryCounts = new();
+ private readonly object _lock = new();
+ private bool _isDisposed;
+
+ ///
+ /// 默认超时时间
+ ///
+ public TimeSpan DefaultTimeout { get; set; } = TimeSpan.FromSeconds(30);
+
+ ///
+ /// 最大重试次数
+ ///
+ public int MaxRetryCount { get; set; } = 3;
+
+ ///
+ /// 检查间隔
+ ///
+ public TimeSpan CheckInterval { get; set; } = TimeSpan.FromSeconds(5);
+
+ ///
+ /// 超时事件
+ ///
+ public event EventHandler? ItemTimeout;
+
+ ///
+ /// 重试事件
+ ///
+ public event EventHandler? ItemRetry;
+
+ ///
+ /// 最终失败事件(超过最大重试次数)
+ ///
+ public event EventHandler? ItemFailed;
+
+ public LoadingTimeoutHandler(LoadingStateManager manager)
+ {
+ _manager = manager ?? throw new ArgumentNullException(nameof(manager));
+
+ _checkTimer = new System.Timers.Timer(CheckInterval.TotalMilliseconds);
+ _checkTimer.Elapsed += OnCheckTimerElapsed;
+ _checkTimer.AutoReset = true;
+
+ // 订阅状态变更事件
+ _manager.StateChanged += OnStateChanged;
+ }
+
+ ///
+ /// 启动监控
+ ///
+ public void Start()
+ {
+ if (_isDisposed) return;
+ _checkTimer.Start();
+ AppLogger.Info("LoadingTimeoutHandler", "Timeout handler started");
+ }
+
+ ///
+ /// 停止监控
+ ///
+ public void Stop()
+ {
+ _checkTimer.Stop();
+ AppLogger.Info("LoadingTimeoutHandler", "Timeout handler stopped");
+ }
+
+ ///
+ /// 为特定加载项设置超时
+ ///
+ public void SetItemTimeout(string itemId, TimeSpan timeout)
+ {
+ lock (_lock)
+ {
+ _itemTimeouts[itemId] = timeout;
+ }
+ }
+
+ ///
+ /// 获取加载项的超时时间
+ ///
+ public TimeSpan GetItemTimeout(string itemId)
+ {
+ lock (_lock)
+ {
+ return _itemTimeouts.TryGetValue(itemId, out var timeout) ? timeout : DefaultTimeout;
+ }
+ }
+
+ ///
+ /// 重置重试计数
+ ///
+ public void ResetRetryCount(string itemId)
+ {
+ lock (_lock)
+ {
+ _retryCounts[itemId] = 0;
+ }
+ }
+
+ ///
+ /// 定时检查超时
+ ///
+ private void OnCheckTimerElapsed(object? sender, ElapsedEventArgs e)
+ {
+ if (_isDisposed) return;
+
+ try
+ {
+ var activeItems = _manager.GetActiveItems().ToList();
+ var now = DateTimeOffset.UtcNow;
+
+ foreach (var item in activeItems)
+ {
+ if (!item.StartTime.HasValue) continue;
+
+ var timeout = GetItemTimeout(item.Id);
+ var elapsed = now - item.StartTime.Value;
+
+ if (elapsed > timeout)
+ {
+ HandleTimeout(item.Id, elapsed);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ AppLogger.Warn("LoadingTimeoutHandler", $"Error checking timeouts: {ex.Message}");
+ }
+ }
+
+ ///
+ /// 处理超时
+ ///
+ private void HandleTimeout(string itemId, TimeSpan elapsed)
+ {
+ lock (_lock)
+ {
+ var retryCount = _retryCounts.GetValueOrDefault(itemId, 0);
+
+ if (retryCount < MaxRetryCount)
+ {
+ // 重试
+ _retryCounts[itemId] = retryCount + 1;
+
+ var item = _manager.GetAllItems().FirstOrDefault(i => i.Id == itemId);
+ if (item != null)
+ {
+ AppLogger.Warn("LoadingTimeoutHandler",
+ $"Item '{item.Name}' timed out after {elapsed.TotalSeconds}s, retrying ({retryCount + 1}/{MaxRetryCount})...");
+
+ ItemRetry?.Invoke(this, new LoadingRetryEventArgs
+ {
+ ItemId = itemId,
+ ItemName = item.Name,
+ RetryCount = retryCount + 1,
+ MaxRetryCount = MaxRetryCount,
+ ElapsedTime = elapsed
+ });
+
+ // 重新启动该项
+ _manager.StartItem(itemId, $"第 {retryCount + 1} 次重试...");
+ }
+ }
+ else
+ {
+ // 最终失败
+ _retryCounts.Remove(itemId);
+
+ var item = _manager.GetAllItems().FirstOrDefault(i => i.Id == itemId);
+ if (item != null)
+ {
+ AppLogger.Error("LoadingTimeoutHandler",
+ $"Item '{item.Name}' failed after {MaxRetryCount} retries ({elapsed.TotalSeconds}s)");
+
+ var args = new LoadingTimeoutEventArgs
+ {
+ ItemId = itemId,
+ ItemName = item.Name,
+ ElapsedTime = elapsed,
+ RetryCount = MaxRetryCount,
+ IsFinalFailure = true
+ };
+
+ ItemTimeout?.Invoke(this, args);
+ ItemFailed?.Invoke(this, args);
+
+ // 标记为失败
+ _manager.FailItem(itemId,
+ $"加载超时(超过 {elapsed.TotalSeconds:F0} 秒)",
+ $"已重试 {MaxRetryCount} 次但仍失败");
+ }
+ }
+ }
+ }
+
+ ///
+ /// 状态变更事件处理
+ ///
+ private void OnStateChanged(object? sender, LoadingStateChangedEventArgs e)
+ {
+ // 当项完成或失败时,清除重试计数
+ if (e.CurrentState is LoadingState.Completed or LoadingState.Failed or LoadingState.Cancelled)
+ {
+ lock (_lock)
+ {
+ _retryCounts.Remove(e.Item.Id);
+ }
+ }
+
+ // 当项开始时,如果是第一次开始,初始化重试计数
+ if (e.CurrentState == LoadingState.InProgress &&
+ (e.PreviousState == null || e.PreviousState == LoadingState.Pending))
+ {
+ lock (_lock)
+ {
+ if (!_retryCounts.ContainsKey(e.Item.Id))
+ {
+ _retryCounts[e.Item.Id] = 0;
+ }
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_isDisposed) return;
+ _isDisposed = true;
+
+ Stop();
+
+ _checkTimer.Elapsed -= OnCheckTimerElapsed;
+ _checkTimer.Dispose();
+
+ _manager.StateChanged -= OnStateChanged;
+
+ _itemTimeouts.Clear();
+ _retryCounts.Clear();
+ }
+}
+
+///
+/// 加载超时事件参数
+///
+public class LoadingTimeoutEventArgs : EventArgs
+{
+ public required string ItemId { get; init; }
+ public required string ItemName { get; init; }
+ public required TimeSpan ElapsedTime { get; init; }
+ public int RetryCount { get; init; }
+ public bool IsFinalFailure { get; init; }
+}
+
+///
+/// 加载重试事件参数
+///
+public class LoadingRetryEventArgs : EventArgs
+{
+ public required string ItemId { get; init; }
+ public required string ItemName { get; init; }
+ public required int RetryCount { get; init; }
+ public required int MaxRetryCount { get; init; }
+ public required TimeSpan ElapsedTime { get; init; }
+}