You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
390 lines
15 KiB
390 lines
15 KiB
using Microsoft.Extensions.Logging;
|
|
using AuroraDesk.Core.Interfaces;
|
|
using AuroraDesk.Presentation.ViewModels.Base;
|
|
using AuroraDesk.Core.Entities;
|
|
using ReactiveUI;
|
|
using System;
|
|
using System.Collections.ObjectModel;
|
|
using System.Linq;
|
|
using System.Reactive;
|
|
using System.Reactive.Disposables;
|
|
using System.Reactive.Linq;
|
|
|
|
namespace AuroraDesk.Presentation.ViewModels;
|
|
|
|
/// <summary>
|
|
/// 主窗口的 ViewModel,使用完整的 ReactiveUI 路由系统
|
|
/// 重构后遵循整洁架构原则:
|
|
/// - 职责分离:导航状态管理委托给 INavigationStateService
|
|
/// - 标签页管理委托给 ITabManagementService
|
|
/// - 使用字典缓存优化查找性能(O(1) 时间复杂度)
|
|
/// - 减少不必要的状态重置循环
|
|
/// </summary>
|
|
public class MainWindowViewModel : ReactiveObject
|
|
{
|
|
private string _title = "My Avalonia App - ReactiveUI 路由";
|
|
private string _headerTitle = "系统管理平台";
|
|
private string _headerSubtitle = "欢迎使用现代化管理界面";
|
|
private ObservableCollection<NavigationItem> _navigationItems = new();
|
|
private NavigationItem? _selectedNavigationItem;
|
|
|
|
private readonly ILogger<MainWindowViewModel>? _logger;
|
|
private readonly INavigationService _navigationService;
|
|
private readonly INavigationStateService _navigationStateService;
|
|
private readonly ITabManagementService _tabManagementService;
|
|
private readonly IScreen _screen;
|
|
|
|
// 使用 CompositeDisposable 管理订阅,确保资源释放
|
|
private readonly System.Reactive.Disposables.CompositeDisposable _disposables = new();
|
|
|
|
public string Title
|
|
{
|
|
get => _title;
|
|
set => this.RaiseAndSetIfChanged(ref _title, value);
|
|
}
|
|
|
|
public string HeaderTitle
|
|
{
|
|
get => _headerTitle;
|
|
set => this.RaiseAndSetIfChanged(ref _headerTitle, value);
|
|
}
|
|
|
|
public string HeaderSubtitle
|
|
{
|
|
get => _headerSubtitle;
|
|
set => this.RaiseAndSetIfChanged(ref _headerSubtitle, value);
|
|
}
|
|
|
|
public ObservableCollection<NavigationItem> NavigationItems
|
|
{
|
|
get => _navigationItems;
|
|
set => this.RaiseAndSetIfChanged(ref _navigationItems, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 标签页集合(委托给 TabManagementService)
|
|
/// </summary>
|
|
public ObservableCollection<TabItem> Tabs => _tabManagementService.Tabs;
|
|
|
|
public NavigationItem? SelectedNavigationItem
|
|
{
|
|
get => _selectedNavigationItem;
|
|
set => this.RaiseAndSetIfChanged(ref _selectedNavigationItem, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 当前选中的标签页(委托给 TabManagementService)
|
|
/// </summary>
|
|
public TabItem? SelectedTab
|
|
{
|
|
get => _tabManagementService.SelectedTab;
|
|
set => _tabManagementService.SelectedTab = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 路由状态(暴露给 UI 使用)
|
|
/// </summary>
|
|
public RoutingState Router => _screen.Router;
|
|
|
|
// 响应式命令
|
|
public ReactiveCommand<NavigationItem, Unit> NavigateCommand { get; }
|
|
public ReactiveCommand<TabItem, Unit> CloseTabCommand { get; }
|
|
public ReactiveCommand<TabItem, Unit> SelectTabCommand { get; }
|
|
|
|
// 关闭确认对话框 Interaction
|
|
public Interaction<CloseConfirmViewModel, bool> CloseConfirmInteraction { get; }
|
|
|
|
/// <summary>
|
|
/// 请求关闭确认
|
|
/// </summary>
|
|
public async System.Threading.Tasks.Task<bool> RequestCloseConfirmAsync()
|
|
{
|
|
var viewModel = new CloseConfirmViewModel();
|
|
var result = await CloseConfirmInteraction.Handle(viewModel);
|
|
_logger?.LogInformation("用户选择: {Result}", result ? "确认关闭" : "取消关闭");
|
|
return result;
|
|
}
|
|
|
|
public MainWindowViewModel(
|
|
IScreen screen,
|
|
INavigationService navigationService,
|
|
INavigationStateService navigationStateService,
|
|
ITabManagementService tabManagementService,
|
|
ILogger<MainWindowViewModel>? logger = null)
|
|
{
|
|
_screen = screen;
|
|
_navigationService = navigationService;
|
|
_navigationStateService = navigationStateService;
|
|
_tabManagementService = tabManagementService;
|
|
_logger = logger;
|
|
|
|
_logger?.LogInformation("MainWindowViewModel 已创建(重构版本 - 整洁架构)");
|
|
|
|
// 从导航服务获取导航项
|
|
NavigationItems = _navigationService.GetNavigationItems(_screen);
|
|
|
|
// 初始化导航状态服务(建立字典缓存,优化查找性能)
|
|
_navigationStateService.InitializeStateMap(NavigationItems);
|
|
|
|
// 创建导航命令
|
|
NavigateCommand = ReactiveCommand.Create<NavigationItem>(NavigateToPage);
|
|
|
|
// 创建关闭标签页命令(委托给 TabManagementService,并在关闭后同步导航)
|
|
CloseTabCommand = ReactiveCommand.Create<TabItem>(CloseTab);
|
|
|
|
// 创建选择标签页命令(委托给 TabManagementService)
|
|
SelectTabCommand = ReactiveCommand.Create<TabItem>(tab => SelectTab(tab));
|
|
|
|
// 创建关闭确认对话框 Interaction
|
|
CloseConfirmInteraction = new Interaction<CloseConfirmViewModel, bool>();
|
|
|
|
// 监听导航项选择变化
|
|
_disposables.Add(
|
|
this.WhenAnyValue(x => x.SelectedNavigationItem)
|
|
.Where(item => item != null)
|
|
.Subscribe(item => _logger?.LogInformation("导航到: {Title}", item!.Title)));
|
|
|
|
// 监听标签页选择变化(通过服务的事件流)
|
|
_disposables.Add(
|
|
_tabManagementService.SelectedTabChanged
|
|
.Where(tab => tab != null)
|
|
.Subscribe(tab =>
|
|
{
|
|
this.RaisePropertyChanged(nameof(SelectedTab));
|
|
_logger?.LogInformation("切换到标签页: {Title}", tab!.Title);
|
|
}));
|
|
|
|
// 监听 Router.CurrentViewModel 变化,同步标签页
|
|
_disposables.Add(
|
|
_screen.Router
|
|
.CurrentViewModel
|
|
.Subscribe(viewModel => OnRouterViewModelChanged(viewModel)));
|
|
|
|
// 初始化导航:自动导航到仪表板页面
|
|
_disposables.Add(
|
|
Observable.Start(() =>
|
|
{
|
|
var dashboardItem = NavigationItems.FirstOrDefault(item => item.Id == "dashboard");
|
|
if (dashboardItem != null && dashboardItem.ViewModel != null)
|
|
{
|
|
// 使用状态服务选中导航项
|
|
_navigationStateService.SelectItem(dashboardItem);
|
|
SelectedNavigationItem = dashboardItem;
|
|
|
|
// 导航到仪表板
|
|
_navigationService.NavigateToPage(dashboardItem);
|
|
_logger?.LogInformation("自动导航到仪表板页面");
|
|
}
|
|
}, RxApp.MainThreadScheduler)
|
|
.Subscribe());
|
|
}
|
|
|
|
// 以下状态管理方法已移除,现在委托给 INavigationStateService
|
|
// - ResetAllNavigationItemsState() -> _navigationStateService.ResetAllStates()
|
|
// - ResetAllNavigationItemsSelectionOnly() -> _navigationStateService.ResetSelectionOnly()
|
|
// - ResetOtherNavigationItemsState() -> _navigationStateService.ResetOtherStates()
|
|
// 这样可以分离职责,并且使用字典缓存优化性能(O(1) vs O(n*m))
|
|
|
|
/// <summary>
|
|
/// 导航到指定页面
|
|
/// 重构后:使用 INavigationStateService 管理状态,减少耦合
|
|
/// </summary>
|
|
private void NavigateToPage(NavigationItem navigationItem)
|
|
{
|
|
if (navigationItem == null) return;
|
|
|
|
// 使用状态服务查找父项(使用字典缓存,O(1) 时间复杂度)
|
|
var parentItem = _navigationStateService.FindParentItem(navigationItem);
|
|
bool isChildItem = parentItem != null;
|
|
|
|
if (isChildItem)
|
|
{
|
|
// 子级菜单项:处理导航
|
|
if (navigationItem.ViewModel == null) return;
|
|
|
|
// 如果已经选中,直接返回,避免重复操作
|
|
if (navigationItem.IsSelected && SelectedNavigationItem == navigationItem) return;
|
|
|
|
// 使用状态服务重置选中状态(保持展开状态)
|
|
_navigationStateService.ResetSelectionOnly();
|
|
|
|
// 确保父项保持展开状态
|
|
if (parentItem != null && !parentItem.IsExpanded)
|
|
{
|
|
parentItem.IsExpanded = true;
|
|
}
|
|
|
|
// 使用状态服务选中当前子项
|
|
_navigationStateService.SelectItem(navigationItem);
|
|
SelectedNavigationItem = navigationItem;
|
|
|
|
// 使用导航服务进行导航
|
|
_navigationService.NavigateToPage(navigationItem);
|
|
|
|
_logger?.LogInformation("导航到子页面: {Title}", navigationItem.Title);
|
|
}
|
|
else if (navigationItem.HasChildren)
|
|
{
|
|
// 父级菜单项:处理展开/收起逻辑
|
|
if (navigationItem.IsExpanded)
|
|
{
|
|
// 如果已经展开,则收起
|
|
_navigationStateService.ToggleExpand(navigationItem);
|
|
_navigationStateService.ResetAllStates();
|
|
SelectedNavigationItem = null;
|
|
|
|
_logger?.LogInformation("收起导航项: {Title}", navigationItem.Title);
|
|
}
|
|
else
|
|
{
|
|
// 如果未展开,则展开并选中第一个子项
|
|
_navigationStateService.ResetOtherStates(navigationItem);
|
|
_navigationStateService.ToggleExpand(navigationItem);
|
|
|
|
// 选中第一个子项并导航
|
|
if (navigationItem.Children.Count > 0)
|
|
{
|
|
var firstChild = navigationItem.Children[0];
|
|
if (firstChild.ViewModel != null)
|
|
{
|
|
_navigationStateService.SelectItem(firstChild);
|
|
SelectedNavigationItem = firstChild;
|
|
_navigationService.NavigateToPage(firstChild);
|
|
}
|
|
}
|
|
|
|
_logger?.LogInformation("展开导航项: {Title}", navigationItem.Title);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// 无子项的父级导航项,直接选中并导航
|
|
// 如果已经选中,直接返回,避免重复操作
|
|
if (navigationItem.IsSelected && SelectedNavigationItem == navigationItem) return;
|
|
|
|
// 优化:检查是否已经有对应的 Tab 存在,如果有就直接切换到那个 Tab
|
|
// 使用字典查找,时间复杂度 O(1)
|
|
var existingTab = _tabManagementService.FindTabById(navigationItem.Id);
|
|
if (existingTab != null)
|
|
{
|
|
// 直接切换到已存在的 Tab,避免重新创建和导航
|
|
_navigationStateService.ResetAllStates();
|
|
_navigationStateService.SelectItem(navigationItem);
|
|
SelectedNavigationItem = navigationItem;
|
|
SelectTab(existingTab);
|
|
_logger?.LogInformation("切换到已存在的标签页: {Title}", navigationItem.Title);
|
|
return;
|
|
}
|
|
|
|
// 使用状态服务重置所有状态并选中当前项
|
|
_navigationStateService.ResetAllStates();
|
|
_navigationStateService.SelectItem(navigationItem);
|
|
SelectedNavigationItem = navigationItem;
|
|
|
|
// 使用导航服务进行导航
|
|
if (navigationItem.ViewModel != null)
|
|
{
|
|
_navigationService.NavigateToPage(navigationItem);
|
|
}
|
|
|
|
_logger?.LogInformation("导航到页面: {Title}", navigationItem.Title);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Router.CurrentViewModel 变化时的处理
|
|
/// 重构后:委托给 TabManagementService 管理标签页
|
|
/// </summary>
|
|
private void OnRouterViewModelChanged(IRoutableViewModel? viewModel)
|
|
{
|
|
if (viewModel == null) return;
|
|
|
|
// 使用状态服务查找导航项(使用字典缓存,O(1) 时间复杂度)
|
|
var navigationItem = _navigationStateService.FindNavigationItemByViewModel(viewModel);
|
|
if (navigationItem == null) return;
|
|
|
|
// 委托给 TabManagementService 创建或更新标签页
|
|
_tabManagementService.CreateOrUpdateTab(navigationItem, viewModel);
|
|
|
|
_logger?.LogInformation("Router 导航到: {Title}", navigationItem.Title);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 选择标签页
|
|
/// 重构后:委托给 TabManagementService,并同步 Router
|
|
/// 优化:如果当前已经是目标 ViewModel,跳过导航避免重复渲染
|
|
/// </summary>
|
|
private void SelectTab(TabItem tab)
|
|
{
|
|
if (tab == null) return;
|
|
|
|
// 如果当前 Router 已经显示的是这个 ViewModel,跳过导航避免重复渲染
|
|
if (tab.ViewModel != null && _screen.Router.CurrentViewModel == tab.ViewModel)
|
|
{
|
|
// 只更新标签页状态,不触发 Router 导航
|
|
_tabManagementService.SelectTab(tab);
|
|
_logger?.LogDebug("标签页已处于当前视图,跳过导航: {Title}", tab.Title);
|
|
return;
|
|
}
|
|
|
|
// 委托给 TabManagementService 选择标签页
|
|
_tabManagementService.SelectTab(tab);
|
|
|
|
// 使用 Router 导航到对应的 ViewModel
|
|
if (tab.ViewModel != null)
|
|
{
|
|
_screen.Router.Navigate.Execute(tab.ViewModel);
|
|
}
|
|
|
|
_logger?.LogInformation("选择标签页: {Title}", tab.Title);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 关闭标签页
|
|
/// 重构后:委托给 TabManagementService,并在关闭后同步 Router 导航
|
|
/// </summary>
|
|
private void CloseTab(TabItem tab)
|
|
{
|
|
if (tab == null || !tab.CanClose) return;
|
|
|
|
var wasSelected = tab.IsSelected;
|
|
var tabIndex = _tabManagementService.Tabs.IndexOf(tab);
|
|
|
|
// 委托给 TabManagementService 关闭标签页
|
|
_tabManagementService.CloseTab(tab);
|
|
|
|
// 如果关闭的是当前选中的标签页,且还有其他标签页,需要导航到新选中的标签页
|
|
if (wasSelected && _tabManagementService.Tabs.Count > 0)
|
|
{
|
|
var newSelectedTab = _tabManagementService.SelectedTab;
|
|
if (newSelectedTab?.ViewModel != null)
|
|
{
|
|
_screen.Router.Navigate.Execute(newSelectedTab.ViewModel);
|
|
}
|
|
}
|
|
// 如果没有标签页了,可能需要导航到默认页面(如仪表板)
|
|
else if (_tabManagementService.Tabs.Count == 0)
|
|
{
|
|
var dashboardItem = NavigationItems.FirstOrDefault(item => item.Id == "dashboard");
|
|
if (dashboardItem?.ViewModel != null)
|
|
{
|
|
_screen.Router.Navigate.Execute(dashboardItem.ViewModel);
|
|
}
|
|
}
|
|
|
|
_logger?.LogInformation("关闭标签页: {Title}", tab.Title);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 释放资源
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
_disposables?.Dispose();
|
|
if (_tabManagementService is IDisposable disposable)
|
|
{
|
|
disposable.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|