using Microsoft.Extensions.Logging; using AuroraDesk.Presentation.ViewModels.Base; using AuroraDesk.Core.Interfaces; using AuroraDesk.Core.Entities; using ReactiveUI; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Reactive; using System.Reactive.Linq; using System.Threading.Tasks; using System.Linq; using Avalonia.Threading; namespace AuroraDesk.Presentation.ViewModels.Pages; /// /// 图标导航页面的 ViewModel /// 优化后:延迟加载数据,避免导航时阻塞主线程 /// public class IconsPageViewModel : RoutableViewModel { private ObservableCollection _heroIcons = new(); private HeroIconItem? _selectedIcon; private bool _isLoading; private bool _isDataLoaded; private bool _isReStreaming; // 标记是否正在重新流式加载 private readonly ILogger? _logger; private readonly IIconService _iconService; public ObservableCollection HeroIcons { get { // 延迟初始化:只在数据为空时才加载 // 关键优化:只要数据已存在,就不重新加载,无论 Tab 是否切换 // 这样 Tab 切换时不会触发不必要的重新加载 if (!_isLoading && !_isReStreaming && _heroIcons.Count == 0) { // 只在数据为空时才加载 _isDataLoaded = true; _ = LoadIconsAsync(); } // 如果数据已存在,直接返回,不重新加载 // 这样可以避免 Tab 切换时的重新加载,提升性能 return _heroIcons; } set => this.RaiseAndSetIfChanged(ref _heroIcons, value); } public HeroIconItem? SelectedIcon { get => _selectedIcon; set => this.RaiseAndSetIfChanged(ref _selectedIcon, value); } /// /// 是否正在加载图标数据 /// public bool IsLoading { get => _isLoading; set => this.RaiseAndSetIfChanged(ref _isLoading, value); } // 响应式命令 public ReactiveCommand CopyIconCommand { get; } public ReactiveCommand SelectIconCommand { get; } /// /// 构造函数 /// /// 宿主 Screen /// 图标服务 /// 日志记录器 public IconsPageViewModel( IScreen hostScreen, IIconService iconService, ILogger? logger = null) : base(hostScreen, "Icons") { _logger = logger; _iconService = iconService ?? throw new ArgumentNullException(nameof(iconService)); _logger?.LogInformation("IconsPageViewModel 已创建"); // 创建命令 CopyIconCommand = ReactiveCommand.Create(CopyIconToClipboard); SelectIconCommand = ReactiveCommand.Create(SelectIcon); // 监听图标选择变化 this.WhenAnyValue(x => x.SelectedIcon) .Where(icon => icon != null) .Subscribe(icon => _logger?.LogInformation("选择图标: {Name}", icon!.DisplayName)); // 延迟加载:不在构造函数中立即加载,避免导航时阻塞主线程 // 数据将在页面显示时(HeroIcons 绑定触发时)加载 } /// /// 异步流式加载图标数据(优化:批量添加,避免阻塞 UI) /// 在后台线程准备数据,然后在 UI 线程批量添加,减少 UI 更新频率 /// private async Task LoadIconsAsync() { try { // 由于 getter 已经确保只在数据为空时才调用此方法,所以这里不需要检查数据是否已存在 IsLoading = true; _logger?.LogInformation("开始加载图标数据"); // 在后台线程准备数据,避免阻塞 UI var iconList = await Task.Run(() => { var icons = _iconService.GetIcons(); return icons.ToList(); }); // 流式渲染策略: // 1. 立即显示第一批(让用户看到内容) // 2. 然后批量添加剩余数据,每次添加一批,减少 UI 更新频率 // 3. 使用后台线程准备数据,UI 线程批量添加,避免阻塞 const int initialBatchSize = 30; // 减少第一批数量,更快响应,减少初始渲染负担 const int incrementalBatchSize = 30; // 增加批次大小,减少 UI 更新频率 // 立即显示第一批(在 UI 线程执行,使用后台优先级) // 关键优化:不要重新创建ObservableCollection,直接使用Add方法 // 这样可以避免触发PropertyChanged,防止tab切换时ItemsControl重新渲染所有项 var initialBatch = iconList.Take(initialBatchSize).ToList(); await Dispatcher.UIThread.InvokeAsync(() => { // 直接添加到现有集合,而不是创建新集合 // 这样可以保持集合引用不变,避免ItemsControl重新绑定和重新渲染 // 关键优化:当tab切换时,如果数据已加载,不会重新创建集合,避免卡顿 var wasEmpty = _heroIcons.Count == 0; foreach (var icon in initialBatch) { _heroIcons.Add(icon); } // 只在第一次加载时(集合从空变为有数据)触发PropertyChanged // 后续流式加载和tab切换都不会触发PropertyChanged,避免重新渲染 if (wasEmpty) { // 第一次加载,触发一次PropertyChanged确保UI绑定 this.RaisePropertyChanged(nameof(HeroIcons)); } IsLoading = false; // 立即显示,用户体验更好 }, DispatcherPriority.Background); _logger?.LogInformation("初始加载 {Count} 个图标(共 {Total} 个),开始流式加载剩余 {Remaining} 个", initialBatch.Count, iconList.Count, iconList.Count - initialBatch.Count); // 如果有更多数据,在后台流式加载 if (iconList.Count > initialBatchSize) { // 使用 Task.Yield 让 UI 线程有机会渲染第一批 await Task.Yield(); // 批量添加剩余数据 for (int i = initialBatchSize; i < iconList.Count; i += incrementalBatchSize) { var batch = iconList.Skip(i).Take(incrementalBatchSize).ToList(); // 在 UI 线程批量添加,避免单个添加导致的频繁 UI 更新 await Dispatcher.UIThread.InvokeAsync(() => { // 批量添加,而不是逐个添加 // 这样可以减少 UI 更新频率,每批只触发一次集合变更通知 foreach (var icon in batch) { _heroIcons.Add(icon); } }, DispatcherPriority.Background); // 使用后台优先级,不阻塞其他 UI 操作 // 每批之间延迟,让 UI 有时间渲染 // 根据剩余数量动态调整延迟:剩余越多,延迟稍长,避免过度消耗资源 if (i + incrementalBatchSize < iconList.Count) { var remainingCount = iconList.Count - (i + incrementalBatchSize); // 动态延迟:剩余数量多时延迟稍长(最多15ms) var delay = remainingCount > 100 ? 15 : 8; await Task.Delay(delay); } } _logger?.LogInformation("流式加载完成,已添加所有 {Total} 个图标", iconList.Count); } // 重置重新流式加载标志 _isReStreaming = false; _isDataLoaded = true; } catch (Exception ex) { _logger?.LogError(ex, "加载图标数据时发生错误"); IsLoading = false; _isReStreaming = false; } } private void CopyIconToClipboard(HeroIconItem icon) { if (icon == null) return; // 这里可以实现复制到剪贴板的逻辑 _logger?.LogInformation("复制图标到剪贴板: {Name}", icon.DisplayName); // 可以添加通知用户复制成功的逻辑 } private void SelectIcon(HeroIconItem icon) { if (icon == null) return; SelectedIcon = icon; _logger?.LogInformation("选择图标: {Name}", icon.DisplayName); } }