diff --git a/AuroraDesk.Infrastructure/AuroraDesk.Infrastructure.csproj b/AuroraDesk.Infrastructure/AuroraDesk.Infrastructure.csproj
index 5985e48..8a42f99 100644
--- a/AuroraDesk.Infrastructure/AuroraDesk.Infrastructure.csproj
+++ b/AuroraDesk.Infrastructure/AuroraDesk.Infrastructure.csproj
@@ -11,11 +11,16 @@
-
-
-
+
+
+
+
+
+
+
+
diff --git a/AuroraDesk.Infrastructure/Extensions/HostBuilderExtensions.cs b/AuroraDesk.Infrastructure/Extensions/HostBuilderExtensions.cs
new file mode 100644
index 0000000..d81a5c3
--- /dev/null
+++ b/AuroraDesk.Infrastructure/Extensions/HostBuilderExtensions.cs
@@ -0,0 +1,75 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+using Serilog;
+using Serilog.Events;
+using System;
+using System.IO;
+
+namespace AuroraDesk.Infrastructure.Extensions;
+
+///
+/// HostBuilder 扩展方法(基础设施层)
+/// 用于配置日志记录功能
+///
+public static class HostBuilderExtensions
+{
+ ///
+ /// 配置文件日志记录
+ ///
+ /// HostBuilder
+ /// HostBuilder
+ ///
+ /// 配置优先级说明:
+ /// 1. 代码中的配置(WriteTo.Console、WriteTo.File)优先级最高,会覆盖配置文件中的相同配置
+ /// 2. serilog.json 文件中的 "Serilog" 节点配置次之,会被 ReadFrom.Configuration 读取
+ ///
+ /// 注意:Serilog 配置已提取到独立的 serilog.json 文件中,便于管理和维护
+ ///
+ public static IHostBuilder ConfigureFileLogging(this IHostBuilder hostBuilder)
+ {
+ return hostBuilder.UseSerilog((context, services, configuration) =>
+ {
+ var environment = context.HostingEnvironment;
+
+ // 配置日志目录
+ var logDirectory = Path.Combine(
+ Directory.GetCurrentDirectory(),
+ "logs"
+ );
+
+ // 确保日志目录存在
+ if (!Directory.Exists(logDirectory))
+ {
+ Directory.CreateDirectory(logDirectory);
+ }
+
+ // 配置 Serilog
+ // 注意:ReadFrom.Configuration 会读取 serilog.json 文件中的 "Serilog" 节点
+ // 然后代码中的配置会在此基础上添加或覆盖
+ configuration
+ .ReadFrom.Configuration(context.Configuration) // 读取 serilog.json 中的 "Serilog" 节点
+ .ReadFrom.Services(services)
+ .Enrich.FromLogContext()
+ .Enrich.WithProperty("Environment", environment.EnvironmentName)
+ // 以下代码配置会覆盖配置文件中相同类型的配置(如果存在)
+ .WriteTo.Console(
+ outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message:lj}{NewLine}{Exception}",
+ restrictedToMinimumLevel: LogEventLevel.Information
+ )
+ .WriteTo.File(
+ path: Path.Combine(logDirectory, "auroradesk-.log"),
+ rollingInterval: RollingInterval.Day,
+ retainedFileCountLimit: 30,
+ outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}",
+ restrictedToMinimumLevel: LogEventLevel.Debug
+ )
+ .WriteTo.File(
+ path: Path.Combine(logDirectory, "auroradesk-error-.log"),
+ rollingInterval: RollingInterval.Day,
+ retainedFileCountLimit: 60,
+ outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}",
+ restrictedToMinimumLevel: LogEventLevel.Error
+ );
+ });
+ }
+}
diff --git a/AuroraDesk.Presentation/ViewModels/CloseConfirmViewModel.cs b/AuroraDesk.Presentation/ViewModels/CloseConfirmViewModel.cs
new file mode 100644
index 0000000..2b384d0
--- /dev/null
+++ b/AuroraDesk.Presentation/ViewModels/CloseConfirmViewModel.cs
@@ -0,0 +1,82 @@
+using Avalonia.Media;
+using HeroIconsAvalonia.Enums;
+using ReactiveUI;
+
+namespace AuroraDesk.Presentation.ViewModels;
+
+///
+/// 关闭确认对话框的 ViewModel
+///
+public class CloseConfirmViewModel : ReactiveObject
+{
+ private string _title = "确认关闭";
+ private string _message = "确定要退出程序吗?";
+ private IconType _icon = IconType.QuestionMarkCircle;
+ private IBrush _accentBrush = new SolidColorBrush(Color.FromRgb(59, 130, 246));
+ private string _primaryButtonText = "确认";
+ private string _secondaryButtonText = "取消";
+
+ ///
+ /// 对话框标题
+ ///
+ public string Title
+ {
+ get => _title;
+ set => this.RaiseAndSetIfChanged(ref _title, value);
+ }
+
+ ///
+ /// 对话框消息
+ ///
+ public string Message
+ {
+ get => _message;
+ set => this.RaiseAndSetIfChanged(ref _message, value);
+ }
+
+ ///
+ /// 对话框图标
+ ///
+ public IconType Icon
+ {
+ get => _icon;
+ set => this.RaiseAndSetIfChanged(ref _icon, value);
+ }
+
+ ///
+ /// 对话框强调色
+ ///
+ public IBrush AccentBrush
+ {
+ get => _accentBrush;
+ set => this.RaiseAndSetIfChanged(ref _accentBrush, value);
+ }
+
+ ///
+ /// 主操作按钮文本
+ ///
+ public string PrimaryButtonText
+ {
+ get => _primaryButtonText;
+ set => this.RaiseAndSetIfChanged(ref _primaryButtonText, value);
+ }
+
+ ///
+ /// 次操作按钮文本
+ ///
+ public string SecondaryButtonText
+ {
+ get => _secondaryButtonText;
+ set
+ {
+ this.RaiseAndSetIfChanged(ref _secondaryButtonText, value);
+ this.RaisePropertyChanged(nameof(HasSecondaryButton));
+ }
+ }
+
+ ///
+ /// 是否显示次操作按钮
+ ///
+ public bool HasSecondaryButton => !string.IsNullOrWhiteSpace(_secondaryButtonText);
+}
+
diff --git a/AuroraDesk.Presentation/ViewModels/MainWindowViewModel.cs b/AuroraDesk.Presentation/ViewModels/MainWindowViewModel.cs
index 4140528..56c75e6 100644
--- a/AuroraDesk.Presentation/ViewModels/MainWindowViewModel.cs
+++ b/AuroraDesk.Presentation/ViewModels/MainWindowViewModel.cs
@@ -91,6 +91,20 @@ public class MainWindowViewModel : ReactiveObject
public ReactiveCommand CloseTabCommand { get; }
public ReactiveCommand SelectTabCommand { get; }
+ // 关闭确认对话框 Interaction
+ public Interaction CloseConfirmInteraction { get; }
+
+ ///
+ /// 请求关闭确认
+ ///
+ public async System.Threading.Tasks.Task RequestCloseConfirmAsync()
+ {
+ var viewModel = new CloseConfirmViewModel();
+ var result = await CloseConfirmInteraction.Handle(viewModel);
+ _logger?.LogInformation("用户选择: {Result}", result ? "确认关闭" : "取消关闭");
+ return result;
+ }
+
public MainWindowViewModel(
IScreen screen,
INavigationService navigationService,
@@ -121,6 +135,9 @@ public class MainWindowViewModel : ReactiveObject
// 创建选择标签页命令(委托给 TabManagementService)
SelectTabCommand = ReactiveCommand.Create(tab => SelectTab(tab));
+ // 创建关闭确认对话框 Interaction
+ CloseConfirmInteraction = new Interaction();
+
// 监听导航项选择变化
this.WhenAnyValue(x => x.SelectedNavigationItem)
.Where(item => item != null)
@@ -358,6 +375,7 @@ public class MainWindowViewModel : ReactiveObject
_logger?.LogInformation("关闭标签页: {Title}", tab.Title);
}
+
///
/// 释放资源
///
diff --git a/AuroraDesk.Presentation/Views/Dialogs/CloseConfirmDialog.axaml b/AuroraDesk.Presentation/Views/Dialogs/CloseConfirmDialog.axaml
new file mode 100644
index 0000000..122b6a7
--- /dev/null
+++ b/AuroraDesk.Presentation/Views/Dialogs/CloseConfirmDialog.axaml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AuroraDesk.Presentation/Views/Dialogs/CloseConfirmDialog.axaml.cs b/AuroraDesk.Presentation/Views/Dialogs/CloseConfirmDialog.axaml.cs
new file mode 100644
index 0000000..12ee1dc
--- /dev/null
+++ b/AuroraDesk.Presentation/Views/Dialogs/CloseConfirmDialog.axaml.cs
@@ -0,0 +1,46 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.ReactiveUI;
+using AuroraDesk.Presentation.ViewModels;
+using ReactiveUI;
+
+namespace AuroraDesk.Presentation.Views.Dialogs;
+
+///
+/// 关闭确认对话框
+///
+public partial class CloseConfirmDialog : ReactiveWindow
+{
+ public CloseConfirmDialog()
+ {
+ InitializeComponent();
+ SetupButtons();
+ }
+
+ public CloseConfirmDialog(CloseConfirmViewModel viewModel) : this()
+ {
+ ViewModel = viewModel;
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ private void SetupButtons()
+ {
+ var confirmButton = this.FindControl