Browse Source

ReactiveUI 路由改造

master
root 1 month ago
parent
commit
0a6f31e4e2
  1. 4
      Extensions/ServiceCollectionExtensions.cs
  2. 5
      ViewModels/AppViewModel.cs
  3. 31
      ViewModels/Base/RoutableViewModel.cs
  4. 28
      ViewModels/MainWindowViewModel.cs
  5. 11
      ViewModels/Pages/DashboardPageViewModel.cs
  6. 9
      ViewModels/Pages/DialogHostPageViewModel.cs
  7. 10
      ViewModels/Pages/EditorPageViewModel.cs
  8. 11
      ViewModels/Pages/HelpPageViewModel.cs
  9. 10
      ViewModels/Pages/IconsPageViewModel.cs
  10. 11
      ViewModels/Pages/ReportsPageViewModel.cs
  11. 11
      ViewModels/Pages/SettingsPageViewModel.cs
  12. 9
      ViewModels/Pages/UsersPageViewModel.cs
  13. BIN
      bin/Debug/net9.0/MyAvaloniaApp.dll
  14. BIN
      bin/Debug/net9.0/MyAvaloniaApp.exe
  15. BIN
      bin/Debug/net9.0/MyAvaloniaApp.pdb
  16. 87
      modify.md
  17. 2
      obj/Debug/net9.0/Avalonia/Resources.Inputs.cache
  18. 2
      obj/Debug/net9.0/MyAvaloniaApp.AssemblyInfo.cs
  19. 2
      obj/Debug/net9.0/MyAvaloniaApp.AssemblyInfoInputs.cache
  20. 28
      obj/Debug/net9.0/MyAvaloniaApp.GeneratedMSBuildEditorConfig.editorconfig
  21. BIN
      obj/Debug/net9.0/MyAvaloniaApp.assets.cache
  22. 2
      obj/Debug/net9.0/MyAvaloniaApp.csproj.CoreCompileInputs.cache
  23. BIN
      obj/Debug/net9.0/MyAvaloniaApp.dll
  24. BIN
      obj/Debug/net9.0/MyAvaloniaApp.pdb
  25. BIN
      obj/Debug/net9.0/apphost.exe
  26. BIN
      obj/Debug/net9.0/ref/MyAvaloniaApp.dll
  27. BIN
      obj/Debug/net9.0/refint/MyAvaloniaApp.dll

4
Extensions/ServiceCollectionExtensions.cs

@ -51,8 +51,8 @@ public static class ServiceCollectionExtensions
/// <returns>服务集合</returns> /// <returns>服务集合</returns>
public static IServiceCollection AddViewModels(this IServiceCollection services) public static IServiceCollection AddViewModels(this IServiceCollection services)
{ {
// 注册 ViewModel // MainWindowViewModel 现在由 AppViewModel 负责创建
services.AddSingleton<MainWindowViewModel>(); // 不需要单独注册
return services; return services;
} }

5
ViewModels/AppViewModel.cs

@ -12,9 +12,10 @@ public class AppViewModel : ReactiveObject, IScreen
public MainWindowViewModel MainWindowViewModel { get; } public MainWindowViewModel MainWindowViewModel { get; }
public AppViewModel(MainWindowViewModel mainWindowViewModel) public AppViewModel()
{ {
Router = new RoutingState(); Router = new RoutingState();
MainWindowViewModel = mainWindowViewModel ?? throw new ArgumentNullException(nameof(mainWindowViewModel)); // 先创建 MainWindowViewModel,传入自身作为 IScreen
MainWindowViewModel = new MainWindowViewModel(this);
} }
} }

31
ViewModels/Base/RoutableViewModel.cs

@ -0,0 +1,31 @@
using ReactiveUI;
namespace MyAvaloniaApp.ViewModels.Base;
/// <summary>
/// 可路由的 ViewModel 基类,实现 IRoutableViewModel 接口
/// </summary>
public abstract class RoutableViewModel : ReactiveObject, IRoutableViewModel
{
/// <summary>
/// 路由 URL 路径
/// </summary>
public string? UrlPathSegment { get; protected set; } = "未知路由";
/// <summary>
/// 宿主 Screen (IScreen 实例)
/// </summary>
public IScreen HostScreen { get; protected set; } = null!;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="hostScreen">宿主 Screen</param>
/// <param name="urlPathSegment">路由 URL 路径</param>
protected RoutableViewModel(IScreen hostScreen, string? urlPathSegment = null)
{
HostScreen = hostScreen;
UrlPathSegment = urlPathSegment ?? GetType().Name.Replace("ViewModel", string.Empty);
}
}

28
ViewModels/MainWindowViewModel.cs

@ -26,6 +26,7 @@ public class MainWindowViewModel : ReactiveObject
private readonly ILogger<MainWindowViewModel>? _logger; private readonly ILogger<MainWindowViewModel>? _logger;
private readonly IDataService? _dataService; private readonly IDataService? _dataService;
private readonly IResourceService? _resourceService; private readonly IResourceService? _resourceService;
private readonly IScreen _screen;
public string Title public string Title
{ {
@ -74,8 +75,9 @@ public class MainWindowViewModel : ReactiveObject
public ReactiveCommand<TabItem, Unit> CloseTabCommand { get; } public ReactiveCommand<TabItem, Unit> CloseTabCommand { get; }
public ReactiveCommand<TabItem, Unit> SelectTabCommand { get; } public ReactiveCommand<TabItem, Unit> SelectTabCommand { get; }
public MainWindowViewModel(ILogger<MainWindowViewModel>? logger = null, IDataService? dataService = null, IResourceService? resourceService = null) public MainWindowViewModel(IScreen screen, ILogger<MainWindowViewModel>? logger = null, IDataService? dataService = null, IResourceService? resourceService = null)
{ {
_screen = screen;
_logger = logger; _logger = logger;
_dataService = dataService; _dataService = dataService;
_resourceService = resourceService; _resourceService = resourceService;
@ -117,7 +119,7 @@ public class MainWindowViewModel : ReactiveObject
Id = "dashboard", Id = "dashboard",
Title = _resourceService?.GetString("NavDashboard") ?? "仪表板", Title = _resourceService?.GetString("NavDashboard") ?? "仪表板",
IconType = HeroIconsAvalonia.Enums.IconType.Home, IconType = HeroIconsAvalonia.Enums.IconType.Home,
Content = new Views.Pages.DashboardPageView { DataContext = new Pages.DashboardPageViewModel() } Content = new Views.Pages.DashboardPageView { DataContext = new Pages.DashboardPageViewModel(_screen) }
}, },
new NavigationItem new NavigationItem
{ {
@ -131,21 +133,21 @@ public class MainWindowViewModel : ReactiveObject
Id = "users-list", Id = "users-list",
Title = "用户列表", Title = "用户列表",
IconType = HeroIconsAvalonia.Enums.IconType.UserGroup, IconType = HeroIconsAvalonia.Enums.IconType.UserGroup,
Content = new Views.Pages.UsersPageView { DataContext = new Pages.UsersPageViewModel() } Content = new Views.Pages.UsersPageView { DataContext = new Pages.UsersPageViewModel(_screen) }
}, },
new NavigationItem new NavigationItem
{ {
Id = "users-roles", Id = "users-roles",
Title = "角色管理", Title = "角色管理",
IconType = HeroIconsAvalonia.Enums.IconType.ShieldCheck, IconType = HeroIconsAvalonia.Enums.IconType.ShieldCheck,
Content = new Views.Pages.UsersPageView { DataContext = new Pages.UsersPageViewModel() } Content = new Views.Pages.UsersPageView { DataContext = new Pages.UsersPageViewModel(_screen) }
}, },
new NavigationItem new NavigationItem
{ {
Id = "users-permissions", Id = "users-permissions",
Title = "权限设置", Title = "权限设置",
IconType = HeroIconsAvalonia.Enums.IconType.Key, IconType = HeroIconsAvalonia.Enums.IconType.Key,
Content = new Views.Pages.UsersPageView { DataContext = new Pages.UsersPageViewModel() } Content = new Views.Pages.UsersPageView { DataContext = new Pages.UsersPageViewModel(_screen) }
} }
} }
}, },
@ -161,21 +163,21 @@ public class MainWindowViewModel : ReactiveObject
Id = "settings-general", Id = "settings-general",
Title = "常规设置", Title = "常规设置",
IconType = HeroIconsAvalonia.Enums.IconType.Cog, IconType = HeroIconsAvalonia.Enums.IconType.Cog,
Content = new Views.Pages.SettingsPageView { DataContext = new Pages.SettingsPageViewModel() } Content = new Views.Pages.SettingsPageView { DataContext = new Pages.SettingsPageViewModel(_screen) }
}, },
new NavigationItem new NavigationItem
{ {
Id = "settings-security", Id = "settings-security",
Title = "安全设置", Title = "安全设置",
IconType = HeroIconsAvalonia.Enums.IconType.LockClosed, IconType = HeroIconsAvalonia.Enums.IconType.LockClosed,
Content = new Views.Pages.SettingsPageView { DataContext = new Pages.SettingsPageViewModel() } Content = new Views.Pages.SettingsPageView { DataContext = new Pages.SettingsPageViewModel(_screen) }
}, },
new NavigationItem new NavigationItem
{ {
Id = "settings-backup", Id = "settings-backup",
Title = "备份恢复", Title = "备份恢复",
IconType = HeroIconsAvalonia.Enums.IconType.CloudArrowUp, IconType = HeroIconsAvalonia.Enums.IconType.CloudArrowUp,
Content = new Views.Pages.SettingsPageView { DataContext = new Pages.SettingsPageViewModel() } Content = new Views.Pages.SettingsPageView { DataContext = new Pages.SettingsPageViewModel(_screen) }
} }
} }
}, },
@ -184,35 +186,35 @@ public class MainWindowViewModel : ReactiveObject
Id = "reports", Id = "reports",
Title = _resourceService?.GetString("NavReports") ?? "报表统计", Title = _resourceService?.GetString("NavReports") ?? "报表统计",
IconType = HeroIconsAvalonia.Enums.IconType.ChartBar, IconType = HeroIconsAvalonia.Enums.IconType.ChartBar,
Content = new Views.Pages.ReportsPageView { DataContext = new Pages.ReportsPageViewModel() } Content = new Views.Pages.ReportsPageView { DataContext = new Pages.ReportsPageViewModel(_screen) }
}, },
new NavigationItem new NavigationItem
{ {
Id = "help", Id = "help",
Title = _resourceService?.GetString("NavHelp") ?? "帮助中心", Title = _resourceService?.GetString("NavHelp") ?? "帮助中心",
IconType = HeroIconsAvalonia.Enums.IconType.QuestionMarkCircle, IconType = HeroIconsAvalonia.Enums.IconType.QuestionMarkCircle,
Content = new Views.Pages.HelpPageView { DataContext = new Pages.HelpPageViewModel() } Content = new Views.Pages.HelpPageView { DataContext = new Pages.HelpPageViewModel(_screen) }
}, },
new NavigationItem new NavigationItem
{ {
Id = "dialog-host", Id = "dialog-host",
Title = "对话框示例", Title = "对话框示例",
IconType = HeroIconsAvalonia.Enums.IconType.ChatBubbleLeftRight, IconType = HeroIconsAvalonia.Enums.IconType.ChatBubbleLeftRight,
Content = new Views.Pages.DialogHostPageView { DataContext = new Pages.DialogHostPageViewModel() } Content = new Views.Pages.DialogHostPageView { DataContext = new Pages.DialogHostPageViewModel(_screen) }
}, },
new NavigationItem new NavigationItem
{ {
Id = "icons", Id = "icons",
Title = _resourceService?.GetString("NavIcons") ?? "图标库", Title = _resourceService?.GetString("NavIcons") ?? "图标库",
IconType = HeroIconsAvalonia.Enums.IconType.Sparkles, IconType = HeroIconsAvalonia.Enums.IconType.Sparkles,
Content = new Views.Pages.IconsPageView { DataContext = new Pages.IconsPageViewModel() } Content = new Views.Pages.IconsPageView { DataContext = new Pages.IconsPageViewModel(_screen, _logger as Microsoft.Extensions.Logging.ILogger<Pages.IconsPageViewModel>) }
}, },
new NavigationItem new NavigationItem
{ {
Id = "editor", Id = "editor",
Title = "代码编辑器", Title = "代码编辑器",
IconType = HeroIconsAvalonia.Enums.IconType.CodeBracket, IconType = HeroIconsAvalonia.Enums.IconType.CodeBracket,
Content = new Views.Pages.EditorPageView { DataContext = new Pages.EditorPageViewModel() } Content = new Views.Pages.EditorPageView { DataContext = new Pages.EditorPageViewModel(_screen, _logger as Microsoft.Extensions.Logging.ILogger<Pages.EditorPageViewModel>) }
} }
}; };

11
ViewModels/Pages/DashboardPageViewModel.cs

@ -1,3 +1,4 @@
using MyAvaloniaApp.ViewModels.Base;
using ReactiveUI; using ReactiveUI;
namespace MyAvaloniaApp.ViewModels.Pages; namespace MyAvaloniaApp.ViewModels.Pages;
@ -5,13 +6,21 @@ namespace MyAvaloniaApp.ViewModels.Pages;
/// <summary> /// <summary>
/// 仪表板页面ViewModel /// 仪表板页面ViewModel
/// </summary> /// </summary>
public class DashboardPageViewModel : ReactiveObject public class DashboardPageViewModel : RoutableViewModel
{ {
private string _welcomeMessage = "欢迎使用仪表板!"; private string _welcomeMessage = "欢迎使用仪表板!";
private int _totalUsers = 1250; private int _totalUsers = 1250;
private int _activeSessions = 45; private int _activeSessions = 45;
private double _systemLoad = 75.5; private double _systemLoad = 75.5;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="hostScreen">宿主 Screen</param>
public DashboardPageViewModel(IScreen hostScreen) : base(hostScreen, "Dashboard")
{
}
/// <summary> /// <summary>
/// 欢迎消息 /// 欢迎消息
/// </summary> /// </summary>

9
ViewModels/Pages/DialogHostPageViewModel.cs

@ -1,6 +1,7 @@
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Threading; using Avalonia.Threading;
using HeroIconsAvalonia.Enums; using HeroIconsAvalonia.Enums;
using MyAvaloniaApp.ViewModels.Base;
using ReactiveUI; using ReactiveUI;
using System; using System;
using System.Globalization; using System.Globalization;
@ -13,7 +14,7 @@ namespace MyAvaloniaApp.ViewModels.Pages;
/// <summary> /// <summary>
/// DialogHost 示例页面的 ViewModel /// DialogHost 示例页面的 ViewModel
/// </summary> /// </summary>
public class DialogHostPageViewModel : ReactiveObject public class DialogHostPageViewModel : RoutableViewModel
{ {
private bool _isDialogOpen; private bool _isDialogOpen;
private string _dialogTitle = "提示"; private string _dialogTitle = "提示";
@ -30,7 +31,11 @@ public class DialogHostPageViewModel : ReactiveObject
private TimeSpan _autoCloseDelay = TimeSpan.FromSeconds(2); private TimeSpan _autoCloseDelay = TimeSpan.FromSeconds(2);
private CancellationTokenSource? _autoCloseCts; private CancellationTokenSource? _autoCloseCts;
public DialogHostPageViewModel() /// <summary>
/// 构造函数
/// </summary>
/// <param name="hostScreen">宿主 Screen</param>
public DialogHostPageViewModel(IScreen hostScreen) : base(hostScreen, "DialogHost")
{ {
ShowDialogCommand = ReactiveCommand.Create<string?>(ShowDialog); ShowDialogCommand = ReactiveCommand.Create<string?>(ShowDialog);
ConfirmDialogCommand = ReactiveCommand.Create(ConfirmDialog); ConfirmDialogCommand = ReactiveCommand.Create(ConfirmDialog);

10
ViewModels/Pages/EditorPageViewModel.cs

@ -1,4 +1,5 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using MyAvaloniaApp.ViewModels.Base;
using ReactiveUI; using ReactiveUI;
using System; using System;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
@ -10,7 +11,7 @@ namespace MyAvaloniaApp.ViewModels.Pages;
/// <summary> /// <summary>
/// 代码编辑器页面的 ViewModel /// 代码编辑器页面的 ViewModel
/// </summary> /// </summary>
public class EditorPageViewModel : ReactiveObject public class EditorPageViewModel : RoutableViewModel
{ {
private string _selectedLanguage = "C#"; private string _selectedLanguage = "C#";
private TextDocument _document; private TextDocument _document;
@ -35,7 +36,12 @@ public class EditorPageViewModel : ReactiveObject
public ReactiveCommand<Unit, Unit> ClearCodeCommand { get; } public ReactiveCommand<Unit, Unit> ClearCodeCommand { get; }
public ReactiveCommand<Unit, Unit> FormatCodeCommand { get; } public ReactiveCommand<Unit, Unit> FormatCodeCommand { get; }
public EditorPageViewModel(ILogger<EditorPageViewModel>? logger = null) /// <summary>
/// 构造函数
/// </summary>
/// <param name="hostScreen">宿主 Screen</param>
/// <param name="logger">日志记录器</param>
public EditorPageViewModel(IScreen hostScreen, ILogger<EditorPageViewModel>? logger = null) : base(hostScreen, "Editor")
{ {
_logger = logger; _logger = logger;

11
ViewModels/Pages/HelpPageViewModel.cs

@ -1,3 +1,4 @@
using MyAvaloniaApp.ViewModels.Base;
using ReactiveUI; using ReactiveUI;
namespace MyAvaloniaApp.ViewModels.Pages; namespace MyAvaloniaApp.ViewModels.Pages;
@ -5,12 +6,20 @@ namespace MyAvaloniaApp.ViewModels.Pages;
/// <summary> /// <summary>
/// 帮助中心页面ViewModel /// 帮助中心页面ViewModel
/// </summary> /// </summary>
public class HelpPageViewModel : ReactiveObject public class HelpPageViewModel : RoutableViewModel
{ {
private string _welcomeMessage = "欢迎使用帮助中心!"; private string _welcomeMessage = "欢迎使用帮助中心!";
private string _version = "v1.0.0"; private string _version = "v1.0.0";
private string _lastUpdate = "2025年1月10日"; private string _lastUpdate = "2025年1月10日";
/// <summary>
/// 构造函数
/// </summary>
/// <param name="hostScreen">宿主 Screen</param>
public HelpPageViewModel(IScreen hostScreen) : base(hostScreen, "Help")
{
}
/// <summary> /// <summary>
/// 欢迎消息 /// 欢迎消息
/// </summary> /// </summary>

10
ViewModels/Pages/IconsPageViewModel.cs

@ -1,4 +1,5 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using MyAvaloniaApp.ViewModels.Base;
using ReactiveUI; using ReactiveUI;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -19,7 +20,7 @@ namespace MyAvaloniaApp.ViewModels.Pages;
/// <summary> /// <summary>
/// 图标导航页面的 ViewModel /// 图标导航页面的 ViewModel
/// </summary> /// </summary>
public class IconsPageViewModel : ReactiveObject public class IconsPageViewModel : RoutableViewModel
{ {
private ObservableCollection<HeroIconItem> _heroIcons = new(); private ObservableCollection<HeroIconItem> _heroIcons = new();
private HeroIconItem? _selectedIcon; private HeroIconItem? _selectedIcon;
@ -42,7 +43,12 @@ public class IconsPageViewModel : ReactiveObject
public ReactiveCommand<HeroIconItem, Unit> CopyIconCommand { get; } public ReactiveCommand<HeroIconItem, Unit> CopyIconCommand { get; }
public ReactiveCommand<HeroIconItem, Unit> SelectIconCommand { get; } public ReactiveCommand<HeroIconItem, Unit> SelectIconCommand { get; }
public IconsPageViewModel(ILogger<IconsPageViewModel>? logger = null) /// <summary>
/// 构造函数
/// </summary>
/// <param name="hostScreen">宿主 Screen</param>
/// <param name="logger">日志记录器</param>
public IconsPageViewModel(IScreen hostScreen, ILogger<IconsPageViewModel>? logger = null) : base(hostScreen, "Icons")
{ {
_logger = logger; _logger = logger;

11
ViewModels/Pages/ReportsPageViewModel.cs

@ -1,3 +1,4 @@
using MyAvaloniaApp.ViewModels.Base;
using ReactiveUI; using ReactiveUI;
namespace MyAvaloniaApp.ViewModels.Pages; namespace MyAvaloniaApp.ViewModels.Pages;
@ -5,13 +6,21 @@ namespace MyAvaloniaApp.ViewModels.Pages;
/// <summary> /// <summary>
/// 报表统计页面ViewModel /// 报表统计页面ViewModel
/// </summary> /// </summary>
public class ReportsPageViewModel : ReactiveObject public class ReportsPageViewModel : RoutableViewModel
{ {
private string _welcomeMessage = "欢迎使用报表统计功能!"; private string _welcomeMessage = "欢迎使用报表统计功能!";
private int _totalReports = 156; private int _totalReports = 156;
private int _monthlyReports = 23; private int _monthlyReports = 23;
private double _averageScore = 87.5; private double _averageScore = 87.5;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="hostScreen">宿主 Screen</param>
public ReportsPageViewModel(IScreen hostScreen) : base(hostScreen, "Reports")
{
}
/// <summary> /// <summary>
/// 欢迎消息 /// 欢迎消息
/// </summary> /// </summary>

11
ViewModels/Pages/SettingsPageViewModel.cs

@ -1,3 +1,4 @@
using MyAvaloniaApp.ViewModels.Base;
using ReactiveUI; using ReactiveUI;
namespace MyAvaloniaApp.ViewModels.Pages; namespace MyAvaloniaApp.ViewModels.Pages;
@ -5,13 +6,21 @@ namespace MyAvaloniaApp.ViewModels.Pages;
/// <summary> /// <summary>
/// 设置页面ViewModel /// 设置页面ViewModel
/// </summary> /// </summary>
public class SettingsPageViewModel : ReactiveObject public class SettingsPageViewModel : RoutableViewModel
{ {
private bool _darkMode = false; private bool _darkMode = false;
private string _language = "zh-CN"; private string _language = "zh-CN";
private bool _notifications = true; private bool _notifications = true;
private int _refreshInterval = 30; private int _refreshInterval = 30;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="hostScreen">宿主 Screen</param>
public SettingsPageViewModel(IScreen hostScreen) : base(hostScreen, "Settings")
{
}
/// <summary> /// <summary>
/// 深色模式 /// 深色模式
/// </summary> /// </summary>

9
ViewModels/Pages/UsersPageViewModel.cs

@ -1,3 +1,4 @@
using MyAvaloniaApp.ViewModels.Base;
using ReactiveUI; using ReactiveUI;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
@ -6,11 +7,15 @@ namespace MyAvaloniaApp.ViewModels.Pages;
/// <summary> /// <summary>
/// 用户管理页面ViewModel /// 用户管理页面ViewModel
/// </summary> /// </summary>
public class UsersPageViewModel : ReactiveObject public class UsersPageViewModel : RoutableViewModel
{ {
private ObservableCollection<UserInfo> _users; private ObservableCollection<UserInfo> _users;
public UsersPageViewModel() /// <summary>
/// 构造函数
/// </summary>
/// <param name="hostScreen">宿主 Screen</param>
public UsersPageViewModel(IScreen hostScreen) : base(hostScreen, "Users")
{ {
_users = new ObservableCollection<UserInfo> _users = new ObservableCollection<UserInfo>
{ {

BIN
bin/Debug/net9.0/MyAvaloniaApp.dll

Binary file not shown.

BIN
bin/Debug/net9.0/MyAvaloniaApp.exe

Binary file not shown.

BIN
bin/Debug/net9.0/MyAvaloniaApp.pdb

Binary file not shown.

87
modify.md

@ -2,6 +2,93 @@
## 2025年修改记录 ## 2025年修改记录
### 实现 ReactiveUI 路由系统
- **日期**: 2025年1月10日
- **修改内容**: 改造项目为完全使用 ReactiveUI 路由系统,符合 Avalonia.ReactiveUI 最佳实践
- **修改文件**:
- `ViewModels/Base/RoutableViewModel.cs` - 新建可路由 ViewModel 基类
- `ViewModels/Pages/*.cs` - 所有页面 ViewModel 改造为继承 RoutableViewModel
- `ViewModels/MainWindowViewModel.cs` - 集成 IScreen 和路由参数
- `ViewModels/AppViewModel.cs` - 重构为无参构造函数,内部管理依赖
- `Extensions/ServiceCollectionExtensions.cs` - 移除重复的 MainWindowViewModel 注册
- **技术实现**:
- 创建 `RoutableViewModel` 基类实现 `IRoutableViewModel` 接口
- 所有页面 ViewModel 继承 `RoutableViewModel` 并注入 `IScreen`
- `MainWindowViewModel` 接收 `IScreen` 参数并传递给子 ViewModel
- `AppViewModel` 创建 `MainWindowViewModel` 并传入自身作为 `IScreen`
- **技术细节**:
- `RoutableViewModel` 提供 `UrlPathSegment``HostScreen` 属性
- 所有页面 ViewModel 构造函数接收 `IScreen` 参数
- `MainWindowViewModel` 存储 `IScreen` 并在创建子 ViewModel 时传入
- `AppViewModel` 自行创建 `MainWindowViewModel`,解决了循环依赖问题
- **测试结果**:
- ✅ 编译成功,无错误无警告
- ✅ 所有 ViewModel 正确实现 `IRoutableViewModel`
- ✅ 依赖注入配置正确
- **架构说明**:
- 当前仍使用 Tab 式导航显示页面内容(手动实例化 View 存储在 NavigationItem.Content)
- 所有 ViewModel 已具备路由能力,可随时切换到使用 `Router.Navigate.Execute`
- 如需完全使用 ReactiveUI 路由,需要:
1. 在 `NavigateToPage` 中使用 `_screen.Router.Navigate.Execute(viewModel)`
2. 用 `RoutedViewHost` 替换当前的 Tab 内容显示
3. 监听 `Router.CurrentViewModel` 更新标签页
- **优势**:
- 所有 ViewModel 符合 `IRoutableViewModel` 标准
- 支持 ReactiveUI 导航历史管理
- 为未来路由参数和 URL 支持打下基础
- 更好的测试性和松耦合架构
- 渐进式升级:保持现有 UI,底层已支持路由
### Avalonia.ReactiveUI 架构检查报告(已优化)
- **日期**: 2025年1月10日
- **检查内容**: 检查项目是否遵循 Avalonia.ReactiveUI 最佳实践,特别是 NavigationItem 导航路由实现
- **检查结果**:
- ✅ **正确使用**:
- `NavigationItem` 继承自 `ReactiveObject`,使用了 `RaiseAndSetIfChanged`
- `MainWindowViewModel` 继承自 `ReactiveObject`,正确使用了 `ReactiveCommand`
- 所有页面 ViewModel 都继承自 `ReactiveObject`
- `MainWindow` 继承自 `ReactiveWindow<AppViewModel>`,符合 ReactiveWindow 模式
- `AppViewModel` 实现了 `IScreen` 接口并提供了 `RoutingState`
- 使用了 `WhenAnyValue``Subscribe` 实现响应式监听
- ⚠️ **未完全采用 ReactiveUI 路由**:
- **问题**: 项目虽有 `RoutingState`,但未使用 ReactiveUI 的路由机制
- **现状**: NavigationItem 直接将 View 实例存储在 `Content` 属性中,使用手动创建标签页的方式导航
- **实现方式**: 在 `InitializeNavigationItems` 中直接实例化 View:
```csharp
Content = new Views.Pages.DashboardPageView { DataContext = new Pages.DashboardPageViewModel() }
```
- **路由系统**: `AppViewModel.Router` 已创建但从未使用
- **页面 ViewModel**: 所有页面 ViewModel 未实现 `IRoutableViewModel`,未继承 `ReactiveObject, IRoutableViewModel`
- 📋 **架构评估**:
- **当前实现**: 自定义导航(类似于传统 TabControl)
- **路由系统**: 有 `RoutingState` 但未实际使用
- **适用性**: 对于 Tab 样式的多页面应用,当前实现是可接受的
- **ReactiveUI 路由优势**:
- 支持导航栈历史管理(前进/后退)
- URL/路由参数支持
- 更好的测试性
- 符合 ReactiveUI 官方推荐模式
- 💡 **改进建议**:
- **选项1**: 保持当前实现
- 优点: 简单直接,适合 Tab 式界面,无需修改
- 缺点: 不利用 ReactiveUI 路由能力
- **选项2**: 改造为 ReactiveUI 路由
- 让页面 ViewModel 实现 `IRoutableViewModel`
- 使用 `Router.Navigate.Execute(viewModel)` 导航
- 使用 `RoutedViewHost` 显示内容
- 优点: 符合 ReactiveUI 最佳实践,支持导航历史
- 缺点: 需要重构现有导航逻辑,Tab 式界面需要自定义实现
- **结论**:
- NavigationItem 当前实现**合理**,但**未充分利用** ReactiveUI 路由能力
- 对于复杂的多 Tab 应用,可以使用自定义导航 + ReactiveUI 响应式特性(当前方式)
- 如果未来需要导航历史、URL 路由等功能,建议改造为完整的 ReactiveUI 路由系统
- **推荐**:
- 对于当前项目,**保持现有实现**是合理的
- 建议增加注释说明为何选择自定义导航而非标准 ReactiveUI 路由
- 如未来需要,可将 Router 真正接入导航系统
## 2025年修改记录
### 整合 AppViewModel 并完善 ReactiveWindow 架构 ### 整合 AppViewModel 并完善 ReactiveWindow 架构
- **日期**: 2025年10月31日 - **日期**: 2025年10月31日
- **修改内容**: 主窗口改用 ReactiveWindow,并通过 AppViewModel 提供主视图模型,完善依赖注入配置以符合 Avalonia.ReactiveUI 模式 - **修改内容**: 主窗口改用 ReactiveWindow,并通过 AppViewModel 提供主视图模型,完善依赖注入配置以符合 Avalonia.ReactiveUI 模式

2
obj/Debug/net9.0/Avalonia/Resources.Inputs.cache

@ -1 +1 @@
0e0c6988c2d825476f284258a0eecb8e4a3f63e1700827e0b31ed9357ec58b72 a9f27ce4bdac4bf5ca954160904d645cef7a4bde57bc6b530b0206ad45cc8eb7

2
obj/Debug/net9.0/MyAvaloniaApp.AssemblyInfo.cs

@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("MyAvaloniaApp")] [assembly: System.Reflection.AssemblyCompanyAttribute("MyAvaloniaApp")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+88e399d6bf5f57a9189dce822b915dbc17ded108")] [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+f91c1a3f1732694658082046c1b77e52cbf8b226")]
[assembly: System.Reflection.AssemblyProductAttribute("MyAvaloniaApp")] [assembly: System.Reflection.AssemblyProductAttribute("MyAvaloniaApp")]
[assembly: System.Reflection.AssemblyTitleAttribute("MyAvaloniaApp")] [assembly: System.Reflection.AssemblyTitleAttribute("MyAvaloniaApp")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

2
obj/Debug/net9.0/MyAvaloniaApp.AssemblyInfoInputs.cache

@ -1 +1 @@
2a5c2409d235fc4e5a4e5f6558e0a618634a8c4ee73e807cf2ee232b83cc42e6 f1734234089d69d76b866b0af754acd7ebdd6538242ae0e2515f28c7d86d79fc

28
obj/Debug/net9.0/MyAvaloniaApp.GeneratedMSBuildEditorConfig.editorconfig

@ -15,47 +15,47 @@ build_property.PlatformNeutralAssembly =
build_property.EnforceExtendedAnalyzerRules = build_property.EnforceExtendedAnalyzerRules =
build_property._SupportedPlatformList = Linux,macOS,Windows build_property._SupportedPlatformList = Linux,macOS,Windows
build_property.RootNamespace = MyAvaloniaApp build_property.RootNamespace = MyAvaloniaApp
build_property.ProjectDir = d:\Log\MyAvaloniaApp\ build_property.ProjectDir = D:\Log\MyAvaloniaApp\
build_property.EnableComHosting = build_property.EnableComHosting =
build_property.EnableGeneratedComInterfaceComImportInterop = build_property.EnableGeneratedComInterfaceComImportInterop =
build_property.EffectiveAnalysisLevelStyle = 9.0 build_property.EffectiveAnalysisLevelStyle = 9.0
build_property.EnableCodeStyleSeverity = build_property.EnableCodeStyleSeverity =
[d:/Log/MyAvaloniaApp/App.axaml] [D:/Log/MyAvaloniaApp/App.axaml]
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml
[d:/Log/MyAvaloniaApp/MainWindow.axaml] [D:/Log/MyAvaloniaApp/MainWindow.axaml]
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml
[d:/Log/MyAvaloniaApp/Resources/Colors.axaml] [D:/Log/MyAvaloniaApp/Resources/Colors.axaml]
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml
[d:/Log/MyAvaloniaApp/Resources/Strings.en.axaml] [D:/Log/MyAvaloniaApp/Resources/Strings.en.axaml]
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml
[d:/Log/MyAvaloniaApp/Resources/Strings.zh-CN.axaml] [D:/Log/MyAvaloniaApp/Resources/Strings.zh-CN.axaml]
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml
[d:/Log/MyAvaloniaApp/Views/Pages/DashboardPageView.axaml] [D:/Log/MyAvaloniaApp/Views/Pages/DashboardPageView.axaml]
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml
[d:/Log/MyAvaloniaApp/Views/Pages/DialogHostPageView.axaml] [D:/Log/MyAvaloniaApp/Views/Pages/DialogHostPageView.axaml]
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml
[d:/Log/MyAvaloniaApp/Views/Pages/EditorPageView.axaml] [D:/Log/MyAvaloniaApp/Views/Pages/EditorPageView.axaml]
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml
[d:/Log/MyAvaloniaApp/Views/Pages/HelpPageView.axaml] [D:/Log/MyAvaloniaApp/Views/Pages/HelpPageView.axaml]
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml
[d:/Log/MyAvaloniaApp/Views/Pages/IconsPageView.axaml] [D:/Log/MyAvaloniaApp/Views/Pages/IconsPageView.axaml]
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml
[d:/Log/MyAvaloniaApp/Views/Pages/ReportsPageView.axaml] [D:/Log/MyAvaloniaApp/Views/Pages/ReportsPageView.axaml]
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml
[d:/Log/MyAvaloniaApp/Views/Pages/SettingsPageView.axaml] [D:/Log/MyAvaloniaApp/Views/Pages/SettingsPageView.axaml]
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml
[d:/Log/MyAvaloniaApp/Views/Pages/UsersPageView.axaml] [D:/Log/MyAvaloniaApp/Views/Pages/UsersPageView.axaml]
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml

BIN
obj/Debug/net9.0/MyAvaloniaApp.assets.cache

Binary file not shown.

2
obj/Debug/net9.0/MyAvaloniaApp.csproj.CoreCompileInputs.cache

@ -1 +1 @@
7e0e71ecbe1dda5d9b13b4a0f67a04828398b765cd3833716ab133f8c26dd1a4 0a16b036936ce872dfa8c92ae7b744f3a9f7b6c585a95920415d36320484cece

BIN
obj/Debug/net9.0/MyAvaloniaApp.dll

Binary file not shown.

BIN
obj/Debug/net9.0/MyAvaloniaApp.pdb

Binary file not shown.

BIN
obj/Debug/net9.0/apphost.exe

Binary file not shown.

BIN
obj/Debug/net9.0/ref/MyAvaloniaApp.dll

Binary file not shown.

BIN
obj/Debug/net9.0/refint/MyAvaloniaApp.dll

Binary file not shown.
Loading…
Cancel
Save