From 5f0a4b23f4f9ad1e900f20ebaf9956291402a68c Mon Sep 17 00:00:00 2001 From: "David P.G" Date: Sun, 7 Sep 2025 19:48:48 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=98=BE=E7=A4=BA=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DMS.Application/Interfaces/INlogAppService.cs | 5 + DMS.Application/Profiles/MappingProfile.cs | 1 + DMS.Application/Services/NlogAppService.cs | 11 ++ .../Repositories/INlogRepository.cs | 5 + DMS.Infrastructure/Profiles/MappingProfile.cs | 1 + .../Repositories/InitializeRepository.cs | 14 +- .../Repositories/NlogRepository.cs | 8 + DMS.WPF/App.xaml.cs | 11 ++ DMS.WPF/Profiles/MappingProfile.cs | 1 + DMS.WPF/Services/NavigationService.cs | 55 +++--- DMS.WPF/ViewModels/Items/NlogItemViewModel.cs | 58 ++----- DMS.WPF/ViewModels/LogHistoryViewModel.cs | 139 ++++++++++++++++ DMS.WPF/Views/LogHistoryView.xaml | 156 ++++++++++++++++++ DMS.WPF/Views/LogHistoryView.xaml.cs | 24 +++ DMS.WPF/Views/MainView.xaml | 4 + 15 files changed, 425 insertions(+), 68 deletions(-) create mode 100644 DMS.WPF/ViewModels/LogHistoryViewModel.cs create mode 100644 DMS.WPF/Views/LogHistoryView.xaml create mode 100644 DMS.WPF/Views/LogHistoryView.xaml.cs diff --git a/DMS.Application/Interfaces/INlogAppService.cs b/DMS.Application/Interfaces/INlogAppService.cs index bad8743..b8ee522 100644 --- a/DMS.Application/Interfaces/INlogAppService.cs +++ b/DMS.Application/Interfaces/INlogAppService.cs @@ -23,6 +23,11 @@ public interface INlogAppService /// 要获取的日志条目数量。 Task> GetLatestLogsAsync(int count); + /// + /// 异步清空所有Nlog日志。 + /// + Task ClearAllLogsAsync(); + // 可以在这里添加更多针对日志的查询服务方法,例如: // Task> GetLogsByLevelAsync(string level); // Task> GetLogsByDateRangeAsync(DateTime startDate, DateTime endDate); diff --git a/DMS.Application/Profiles/MappingProfile.cs b/DMS.Application/Profiles/MappingProfile.cs index f14f236..55ef82f 100644 --- a/DMS.Application/Profiles/MappingProfile.cs +++ b/DMS.Application/Profiles/MappingProfile.cs @@ -47,5 +47,6 @@ public class MappingProfile : Profile // User 映射 CreateMap().ReverseMap(); + CreateMap().ReverseMap(); } } diff --git a/DMS.Application/Services/NlogAppService.cs b/DMS.Application/Services/NlogAppService.cs index 8f89696..2b5244b 100644 --- a/DMS.Application/Services/NlogAppService.cs +++ b/DMS.Application/Services/NlogAppService.cs @@ -65,6 +65,17 @@ public class NlogAppService : INlogAppService return allLogs.OrderByDescending(l => l.LogTime).Take(count).ToList(); } + /// + /// 异步清空所有Nlog日志。 + /// + public async Task ClearAllLogsAsync() + { + // 这里需要实现清空日志的逻辑 + // 暂时只清空UI上的日志列表 + // 实际实现可能需要在 INlogRepository 中添加相应的方法 + await _repoManager.Nlogs.DeleteAllAsync(); + } + // 可以在这里实现 INlogAppService 接口中定义的其他方法 // 例如: /* diff --git a/DMS.Core/Interfaces/Repositories/INlogRepository.cs b/DMS.Core/Interfaces/Repositories/INlogRepository.cs index 567f13d..d1c5328 100644 --- a/DMS.Core/Interfaces/Repositories/INlogRepository.cs +++ b/DMS.Core/Interfaces/Repositories/INlogRepository.cs @@ -8,6 +8,11 @@ namespace DMS.Core.Interfaces.Repositories; /// public interface INlogRepository : IBaseRepository { + /// + /// 异步删除所有Nlog日志。 + /// + Task DeleteAllAsync(); + // 可以在此处添加 Nlog 特定的查询方法,例如: // Task> GetLogsByLevelAsync(string level); // Task> GetLogsByDateRangeAsync(DateTime startDate, DateTime endDate); diff --git a/DMS.Infrastructure/Profiles/MappingProfile.cs b/DMS.Infrastructure/Profiles/MappingProfile.cs index c983c12..260bfe1 100644 --- a/DMS.Infrastructure/Profiles/MappingProfile.cs +++ b/DMS.Infrastructure/Profiles/MappingProfile.cs @@ -34,5 +34,6 @@ public class MappingProfile : Profile CreateMap().ReverseMap(); + CreateMap().ReverseMap(); } } diff --git a/DMS.Infrastructure/Repositories/InitializeRepository.cs b/DMS.Infrastructure/Repositories/InitializeRepository.cs index 06d051c..fa88cf3 100644 --- a/DMS.Infrastructure/Repositories/InitializeRepository.cs +++ b/DMS.Infrastructure/Repositories/InitializeRepository.cs @@ -134,14 +134,20 @@ public class InitializeRepository : IInitializeRepository }, new DbMenu { - Id = 5, Header = "设置", Icon = "\uE713", ParentId = 0, - MenuType = MenuType.MainMenu, TargetViewKey = "SettingView", + Id = 5, Header = "日志历史", Icon = "\uE7BA", ParentId = 0, + MenuType = MenuType.MainMenu, TargetViewKey = "LogHistoryView", DisplayOrder = 5 }, new DbMenu { - Id = 6, Header = "关于", Icon = "\uE946", ParentId = 0, - MenuType = MenuType.MainMenu, TargetViewKey = "", DisplayOrder = 6 + Id = 6, Header = "设置", Icon = "\uE713", ParentId = 0, + MenuType = MenuType.MainMenu, TargetViewKey = "SettingView", + DisplayOrder = 6 + }, + new DbMenu + { + Id = 7, Header = "关于", Icon = "\uE946", ParentId = 0, + MenuType = MenuType.MainMenu, TargetViewKey = "", DisplayOrder = 7 } // 假设有一个AboutView }; diff --git a/DMS.Infrastructure/Repositories/NlogRepository.cs b/DMS.Infrastructure/Repositories/NlogRepository.cs index e420978..6ff34f2 100644 --- a/DMS.Infrastructure/Repositories/NlogRepository.cs +++ b/DMS.Infrastructure/Repositories/NlogRepository.cs @@ -27,6 +27,14 @@ public class NlogRepository : BaseRepository, INlogRepository _mapper = mapper; } + /// + /// 异步删除所有Nlog日志。 + /// + public async Task DeleteAllAsync() + { + await Db.Deleteable().ExecuteCommandAsync(); + } + // Nlog 通常是只读或追加的日志,因此像 AddAsync, UpdateAsync, DeleteAsync 这样的修改方法 // 可能不需要在仓储接口中暴露,或者可以省略具体实现或抛出 NotSupportedException。 // 但为了保持与基类一致性并满足接口要求,这里显式实现它们。 diff --git a/DMS.WPF/App.xaml.cs b/DMS.WPF/App.xaml.cs index aaa0560..b3fc0b4 100644 --- a/DMS.WPF/App.xaml.cs +++ b/DMS.WPF/App.xaml.cs @@ -221,6 +221,7 @@ public partial class App : System.Windows.Application services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); // 注册MQTT服务管理器 services.AddSingleton(); @@ -248,6 +249,7 @@ public partial class App : System.Windows.Application services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddTransient(provider => new VariableTableViewModel( provider.GetRequiredService(), @@ -270,6 +272,14 @@ public partial class App : System.Windows.Application provider.GetRequiredService() ) ); + services.AddSingleton(provider => + new LogHistoryViewModel( + provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService() + ) + ); services.AddScoped(); // 注册对话框视图模型 @@ -292,6 +302,7 @@ public partial class App : System.Windows.Application services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddScoped(); services.AddScoped(); } diff --git a/DMS.WPF/Profiles/MappingProfile.cs b/DMS.WPF/Profiles/MappingProfile.cs index 1c5eeb7..24ab14a 100644 --- a/DMS.WPF/Profiles/MappingProfile.cs +++ b/DMS.WPF/Profiles/MappingProfile.cs @@ -29,6 +29,7 @@ namespace DMS.WPF.Profiles CreateMap().ReverseMap(); CreateMap().ReverseMap(); CreateMap().ReverseMap(); + CreateMap().ReverseMap(); } } } \ No newline at end of file diff --git a/DMS.WPF/Services/NavigationService.cs b/DMS.WPF/Services/NavigationService.cs index fc644f2..950fce3 100644 --- a/DMS.WPF/Services/NavigationService.cs +++ b/DMS.WPF/Services/NavigationService.cs @@ -11,13 +11,15 @@ namespace DMS.WPF.Services; public class NavigationService : INavigationService { private readonly IServiceProvider _serviceProvider; + private readonly INotificationService _notificationService; /// /// 构造函数。 /// - public NavigationService(IServiceProvider serviceProvider) + public NavigationService(IServiceProvider serviceProvider,INotificationService notificationService) { _serviceProvider = serviceProvider; + _notificationService = notificationService; } /// @@ -34,8 +36,7 @@ public class NavigationService : INavigationService var viewModel = GetViewModelByKey(menu.TargetViewKey); if (viewModel == null) { - var notificationService = App.Current.Services.GetRequiredService(); - notificationService.ShowError($"切换界面失败,没有找到界面:{menu.TargetViewKey}"); + _notificationService.ShowError($"切换界面失败,没有找到界面:{menu.TargetViewKey}"); return; } @@ -51,26 +52,36 @@ public class NavigationService : INavigationService private ViewModelBase GetViewModelByKey(string key) { - switch (key) + try { - case "HomeView": - return App.Current.Services.GetRequiredService(); - case "DevicesView": - return App.Current.Services.GetRequiredService(); - case "DeviceDetailView": - return App.Current.Services.GetRequiredService(); - case "DataTransformView": - return App.Current.Services.GetRequiredService(); - case "VariableTableView": - return App.Current.Services.GetRequiredService(); - case "MqttsView": - return App.Current.Services.GetRequiredService(); - case "MqttServerDetailView": - return App.Current.Services.GetRequiredService(); - case "SettingView": - return App.Current.Services.GetRequiredService(); - default: - return null; + switch (key) + { + case "HomeView": + return App.Current.Services.GetRequiredService(); + case "DevicesView": + return App.Current.Services.GetRequiredService(); + case "DeviceDetailView": + return App.Current.Services.GetRequiredService(); + case "DataTransformView": + return App.Current.Services.GetRequiredService(); + case "VariableTableView": + return App.Current.Services.GetRequiredService(); + case "LogHistoryView": + return App.Current.Services.GetRequiredService(); + case "MqttsView": + return App.Current.Services.GetRequiredService(); + case "MqttServerDetailView": + return App.Current.Services.GetRequiredService(); + case "SettingView": + return App.Current.Services.GetRequiredService(); + default: + return null; + } + } + catch (Exception e) + { + _notificationService.ShowError($"切换界面失败,获取:{key}对应的ViewModel时发生了错误:{e.Message}"); + throw; } } } \ No newline at end of file diff --git a/DMS.WPF/ViewModels/Items/NlogItemViewModel.cs b/DMS.WPF/ViewModels/Items/NlogItemViewModel.cs index a86574e..32d34c4 100644 --- a/DMS.WPF/ViewModels/Items/NlogItemViewModel.cs +++ b/DMS.WPF/ViewModels/Items/NlogItemViewModel.cs @@ -1,50 +1,24 @@ -// 文件: DMS.WPF/ViewModels/Items/NlogItemViewModel.cs - using CommunityToolkit.Mvvm.ComponentModel; +using DMS.Core.Models; namespace DMS.WPF.ViewModels.Items; -/// -/// 代表日志列表中的单个日志项的ViewModel。 -/// 实现了INotifyPropertyChanged,其任何属性变化都会自动通知UI。 -/// -public partial class NlogItemViewModel : ObservableObject +public class NlogItemViewModel : ObservableObject { - public int Id { get; set; } + private Nlog _nlog; - [ObservableProperty] - private DateTime _logTime; + public NlogItemViewModel(Nlog nlog) + { + _nlog = nlog; + } - [ObservableProperty] - private string _level; - - [ObservableProperty] - private int _threadId; - - [ObservableProperty] - private string _threadName; - - [ObservableProperty] - private string _callsite; - - [ObservableProperty] - private int _callsiteLineNumber; - - [ObservableProperty] - private string _message; - - [ObservableProperty] - private string _logger; - - [ObservableProperty] - private string _exception; - - [ObservableProperty] - private string _callerFilePath; - - [ObservableProperty] - private int _callerLineNumber; - - [ObservableProperty] - private string _callerMember; + public int Id => _nlog.Id; + public string Level => _nlog.Level; + public string ThreadName => _nlog.ThreadName; + public string Callsite => _nlog.Callsite; + public string Message => _nlog.Message; + public string Logger => _nlog.Logger; + public string Exception => _nlog.Exception; + public string StackTrace => _nlog.Exception; // Using Exception as StackTrace since it's not in the Nlog model + public System.DateTime TimeStamp => _nlog.LogTime; } \ No newline at end of file diff --git a/DMS.WPF/ViewModels/LogHistoryViewModel.cs b/DMS.WPF/ViewModels/LogHistoryViewModel.cs new file mode 100644 index 0000000..e91d6e1 --- /dev/null +++ b/DMS.WPF/ViewModels/LogHistoryViewModel.cs @@ -0,0 +1,139 @@ +using AutoMapper; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using DMS.Application.DTOs; +using DMS.Application.Interfaces; +using DMS.Core.Models; +using DMS.WPF.Interfaces; +using DMS.WPF.ViewModels.Items; +using ObservableCollections; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using DMS.WPF.ViewModels.Dialogs; +using Microsoft.Extensions.DependencyInjection; + +namespace DMS.WPF.ViewModels; + +partial class LogHistoryViewModel : ViewModelBase +{ + private readonly IMapper _mapper; + private readonly INlogAppService _nlogAppService; + private readonly IDialogService _dialogService; + private readonly INotificationService _notificationService; + + [ObservableProperty] + private NlogItemViewModel _selectedLog; + + [ObservableProperty] + private IList _selectedLogs = new ArrayList(); + + [ObservableProperty] + private string _searchText; + + private readonly ObservableList _logItemList; + private readonly ISynchronizedView _synchronizedView; + public NotifyCollectionChangedSynchronizedViewList LogItemListView { get; } + + public LogHistoryViewModel(IMapper mapper, INlogAppService nlogAppService, IDialogService dialogService, INotificationService notificationService) + { + _mapper = mapper; + _nlogAppService = nlogAppService; + _dialogService = dialogService; + _notificationService = notificationService; + + _logItemList = new ObservableList(); + _synchronizedView = _logItemList.CreateView(v => v); + LogItemListView = _synchronizedView.ToNotifyCollectionChanged(); + } + + private bool FilterLogs(NlogItemViewModel item) + { + var searchTextLower = SearchText.ToLower(); + return item.Logger?.ToLower().Contains(searchTextLower) == true || + item.Message?.ToLower().Contains(searchTextLower) == true || + item.Exception?.ToLower().Contains(searchTextLower) == true || + item.StackTrace?.ToLower().Contains(searchTextLower) == true; + } + + partial void OnSearchTextChanged(string value) + { + if (string.IsNullOrWhiteSpace(SearchText)) + { + _synchronizedView.ResetFilter(); + } + else + { + _synchronizedView.AttachFilter(FilterLogs); + } + } + + public override async void OnLoaded() + { + await LoadLogsAsync(); + } + + [RelayCommand] + private async Task RefreshLogsAsync() + { + await LoadLogsAsync(); + } + + [RelayCommand] + private async Task ClearLogsAsync() + { + var confirmDialogViewModel = new ConfirmDialogViewModel("确认", "确定要清空所有日志吗?", "确定"); + + var result = await _dialogService.ShowDialogAsync(confirmDialogViewModel); + if (result == true) + { + try + { + await _nlogAppService.ClearAllLogsAsync(); + _logItemList.Clear(); + _notificationService.ShowInfo("日志已清空"); + } + catch (System.Exception ex) + { + _notificationService.ShowError($"清空日志时发生错误: {ex.Message}", ex); + } + } + } + + private async Task LoadLogsAsync() + { + try + { + var logs = await _nlogAppService.GetAllLogsAsync(); + var logItems = logs.Select(logDto => + { + // Manually map NlogDto to Nlog + var nlog = new Nlog + { + Id = logDto.Id, + LogTime = logDto.LogTime, + Level = logDto.Level, + ThreadId = logDto.ThreadId, + ThreadName = logDto.ThreadName, + Callsite = logDto.Callsite, + CallsiteLineNumber = logDto.CallsiteLineNumber, + Message = logDto.Message, + Logger = logDto.Logger, + Exception = logDto.Exception, + CallerFilePath = logDto.CallerFilePath, + CallerLineNumber = logDto.CallerLineNumber, + CallerMember = logDto.CallerMember + }; + return new NlogItemViewModel(nlog); + }).ToList(); + + _logItemList.Clear(); + _logItemList.AddRange(logItems); + } + catch (System.Exception ex) + { + _notificationService.ShowError($"加载日志时发生错误: {ex.Message}", ex); + } + } +} \ No newline at end of file diff --git a/DMS.WPF/Views/LogHistoryView.xaml b/DMS.WPF/Views/LogHistoryView.xaml new file mode 100644 index 0000000..45cc055 --- /dev/null +++ b/DMS.WPF/Views/LogHistoryView.xaml @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DMS.WPF/Views/LogHistoryView.xaml.cs b/DMS.WPF/Views/LogHistoryView.xaml.cs new file mode 100644 index 0000000..37112f1 --- /dev/null +++ b/DMS.WPF/Views/LogHistoryView.xaml.cs @@ -0,0 +1,24 @@ +using System.Windows; +using System.Windows.Controls; +using DMS.WPF.ViewModels; + +namespace DMS.WPF.Views; + +/// +/// LogHistoryView.xaml 的交互逻辑 +/// +public partial class LogHistoryView : UserControl +{ + public LogHistoryView() + { + InitializeComponent(); + } + + private void LogHistoryView_OnLoaded(object sender, RoutedEventArgs e) + { + if (DataContext is LogHistoryViewModel viewModel) + { + viewModel.OnLoaded(); + } + } +} \ No newline at end of file diff --git a/DMS.WPF/Views/MainView.xaml b/DMS.WPF/Views/MainView.xaml index 326c3fd..d728ea3 100644 --- a/DMS.WPF/Views/MainView.xaml +++ b/DMS.WPF/Views/MainView.xaml @@ -104,6 +104,10 @@ + + + +