diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 36e6ad9..c1dc819 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -10,7 +10,12 @@ "Bash(dotnet clean *)", "Bash(cat)", "Bash(sed -i '' 's|Background=\"#0F1620\" CornerRadius=\"8\" Padding=\"22\" BorderBrush=\"#1A2F4A\"|CornerRadius=\"8\" Padding=\"22\" BorderBrush=\"#1E3550\"|g' HomePageView.axaml)", - "Bash(sed -i '' 's|||g' HomePageView.axaml)" + "Bash(sed -i '' 's|||g' HomePageView.axaml)", + "Bash(sed -i '' '/Console.WriteLine/d' ViewModels/NavigationViewModel.cs)", + "Bash(sed -i '' '/Console.WriteLine/d' ViewModels/Base/CrudPageViewModel.cs)", + "Bash(sed -i '' '/Console.WriteLine/d' Views/MainWindow.axaml.cs)", + "Bash(sed -i '' '/System.Console.WriteLine/d' ViewModels/Device/DeviceHostViewModel.cs)", + "Bash(sed -i '' '/System.Console.WriteLine/d' Views/Device/DeviceHostListView.axaml.cs)" ] } } diff --git a/Sln.Wcs.UI/App.axaml.cs b/Sln.Wcs.UI/App.axaml.cs index c5133fd..9566c07 100644 --- a/Sln.Wcs.UI/App.axaml.cs +++ b/Sln.Wcs.UI/App.axaml.cs @@ -80,6 +80,7 @@ public partial class App : Application // 5. 注册 ViewModel services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/Sln.Wcs.UI/ViewModels/Base/CrudPageViewModel.cs b/Sln.Wcs.UI/ViewModels/Base/CrudPageViewModel.cs index 23f2dda..69f3aac 100644 --- a/Sln.Wcs.UI/ViewModels/Base/CrudPageViewModel.cs +++ b/Sln.Wcs.UI/ViewModels/Base/CrudPageViewModel.cs @@ -16,6 +16,7 @@ public interface ICrudPageViewModel { void Load(); Avalonia.Controls.Control CreateView(); + string PageTitle { get; } } public abstract partial class CrudPageViewModel : ObservableObject, ICrudPageViewModel where T : class, new() @@ -56,29 +57,23 @@ public abstract partial class CrudPageViewModel : ObservableObject, ICrudPage [RelayCommand] public void Load() { - Console.WriteLine($"[CRUD] Load 调用, T={typeof(T).Name}, SearchText='{SearchText}'"); try { List list; if (string.IsNullOrWhiteSpace(SearchText)) { - Console.WriteLine($"[CRUD] 调用 _service.Query() 无过滤"); list = _service.Query(); } else { - Console.WriteLine($"[CRUD] 调用 _service.Query(expression)"); var exp = BuildSearchExpression(SearchText); list = exp != null ? _service.Query(exp) : _service.Query(); } - Console.WriteLine($"[CRUD] _service.Query() 返回 {list.Count} 条记录"); Items = new ObservableCollection(list); StatusText = $"共 {list.Count} 条记录"; - Console.WriteLine($"[CRUD] Items 已设置, Count={Items.Count}"); } catch (Exception ex) { - Console.WriteLine($"[CRUD] Load 异常: {ex}"); StatusText = $"加载失败: {ex.Message}"; } } diff --git a/Sln.Wcs.UI/ViewModels/Device/DeviceHostViewModel.cs b/Sln.Wcs.UI/ViewModels/Device/DeviceHostViewModel.cs index dac2bd0..95ae597 100644 --- a/Sln.Wcs.UI/ViewModels/Device/DeviceHostViewModel.cs +++ b/Sln.Wcs.UI/ViewModels/Device/DeviceHostViewModel.cs @@ -11,7 +11,6 @@ public class DeviceHostViewModel : CrudPageViewModel public DeviceHostViewModel(IBaseDeviceHostService service) : base(service) { PageTitle = "设备主机管理"; - System.Console.WriteLine("[VM] DeviceHostViewModel 构造, service 类型: " + service.GetType().FullName); } public override List FieldConfigs => new() diff --git a/Sln.Wcs.UI/ViewModels/NavigationViewModel.cs b/Sln.Wcs.UI/ViewModels/NavigationViewModel.cs index 2c4e602..49bf42b 100644 --- a/Sln.Wcs.UI/ViewModels/NavigationViewModel.cs +++ b/Sln.Wcs.UI/ViewModels/NavigationViewModel.cs @@ -21,6 +21,9 @@ public partial class NavigationViewModel : ObservableObject public ObservableCollection TopMenuItems { get; } = new(); + [ObservableProperty] + private string _currentPageTitle = "首页"; + public event Action? PageChanged; public NavigationViewModel(IServiceProvider sp) @@ -35,30 +38,33 @@ public partial class NavigationViewModel : ObservableObject { TopMenuItems.Clear(); TopMenuItems.Add(new TopMenuItem("首页", ShowHome)); + TopMenuItems.Add(new TopMenuItem("系统监控", ShowMonitor)); TopMenuItems.Add(new TopMenuItem("基础数据", new List { - new("库位信息", () => NavigateTo()), - new("物料信息", () => NavigateTo()), - new("仓库信息", () => NavigateTo()), + new("库位信息", () => NavigateTo("基础数据")), + new("物料信息", () => NavigateTo("基础数据")), + new("仓库信息", () => NavigateTo("基础数据")), })); TopMenuItems.Add(new TopMenuItem("设备管理", new List { - new("设备主机", () => NavigateTo()), - new("设备信息", () => NavigateTo()), - new("设备参数", () => NavigateTo()), + new("设备主机", () => NavigateTo("设备管理")), + new("设备信息", () => NavigateTo("设备管理")), + new("设备参数", () => NavigateTo("设备管理")), })); TopMenuItems.Add(new TopMenuItem("路径管理", new List { - new("路径信息", () => NavigateTo()), - new("路径明细", () => NavigateTo()), + new("路径信息", () => NavigateTo("路径管理")), + new("路径明细", () => NavigateTo("路径管理")), })); TopMenuItems.Add(new TopMenuItem("任务管理", new List { - new("任务队列", () => NavigateTo()), - new("任务明细", () => NavigateTo()), + new("任务队列", () => NavigateTo("任务管理")), + new("任务明细", () => NavigateTo("任务管理")), })); } + private string? _currentModule; + private void ShowHome() { if (_homeView == null) @@ -66,10 +72,17 @@ public partial class NavigationViewModel : ObservableObject var vm = _sp.GetRequiredService(); _homeView = new HomePageView { DataContext = vm }; } + CurrentPageTitle = "首页"; PageChanged?.Invoke(_homeView); } - private void NavigateTo() where T : ICrudPageViewModel + private void ShowMonitor() + { + CurrentPageTitle = "系统监控"; + PageChanged?.Invoke(new SystemMonitorView(_sp.GetRequiredService())); + } + + private void NavigateTo(string module) where T : ICrudPageViewModel { var type = typeof(T); if (!_vmCache.TryGetValue(type, out var vm)) @@ -83,6 +96,7 @@ public partial class NavigationViewModel : ObservableObject view.DataContext = vm; _viewCache[type] = view; } + CurrentPageTitle = $"{module} > {vm.PageTitle}"; PageChanged?.Invoke(view); vm.Load(); } diff --git a/Sln.Wcs.UI/ViewModels/SystemMonitorViewModel.cs b/Sln.Wcs.UI/ViewModels/SystemMonitorViewModel.cs new file mode 100644 index 0000000..e2415f7 --- /dev/null +++ b/Sln.Wcs.UI/ViewModels/SystemMonitorViewModel.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.ObjectModel; +using System.IO; +using System.Text; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; + +namespace Sln.Wcs.UI.ViewModels; + +public partial class SystemMonitorViewModel : ObservableObject +{ + [ObservableProperty] + private ObservableCollection _logs = new(); + + [ObservableProperty] + private int _logCount; + + [ObservableProperty] + private string _filterText = string.Empty; + + [ObservableProperty] + private bool _autoScroll = true; + + private const int MaxLogs = 5000; + + public SystemMonitorViewModel() + { + // 安装控制台输出拦截器 + var original = Console.Out; + var writer = new LogTextWriter(original, entry => + { + Avalonia.Threading.Dispatcher.UIThread.Post(() => + { + Logs.Add(entry); + if (Logs.Count > MaxLogs) Logs.RemoveAt(0); + LogCount = Logs.Count; + }); + }); + Console.SetOut(writer); + } + + [RelayCommand] + private void Clear() + { + Logs.Clear(); + LogCount = 0; + } + + [RelayCommand] + private void ToggleAutoScroll() + { + AutoScroll = !AutoScroll; + } +} + +public class LogEntry +{ + public DateTime Time { get; set; } + public string TimeText => Time.ToString("HH:mm:ss.fff"); + public string Message { get; set; } = string.Empty; + public string Level { get; set; } = "INFO"; +} + +/// +/// 拦截 Console.WriteLine 输出,同时写入原始输出和 ObservableCollection +/// +internal class LogTextWriter : TextWriter +{ + private readonly TextWriter _original; + private readonly Action _onWrite; + private readonly StringBuilder _buffer = new(); + + public LogTextWriter(TextWriter original, Action onWrite) + { + _original = original; + _onWrite = onWrite; + } + + public override Encoding Encoding => Encoding.UTF8; + + public override void Write(char value) + { + _original.Write(value); + if (value == '\n') + { + FlushBuffer(); + } + else if (value != '\r') + { + _buffer.Append(value); + } + } + + public override void WriteLine(string? message) + { + _original.WriteLine(message); + if (message != null) + { + _onWrite(new LogEntry { Time = DateTime.Now, Message = message, Level = "INFO" }); + } + } + + private void FlushBuffer() + { + var msg = _buffer.ToString(); + _buffer.Clear(); + if (msg.Length > 0) + { + _onWrite(new LogEntry { Time = DateTime.Now, Message = msg, Level = "INFO" }); + } + } +} diff --git a/Sln.Wcs.UI/Views/MainWindow.axaml b/Sln.Wcs.UI/Views/MainWindow.axaml index 283d836..46d209b 100644 --- a/Sln.Wcs.UI/Views/MainWindow.axaml +++ b/Sln.Wcs.UI/Views/MainWindow.axaml @@ -56,7 +56,13 @@ - + + + + + + + diff --git a/Sln.Wcs.UI/Views/SystemMonitorView.axaml b/Sln.Wcs.UI/Views/SystemMonitorView.axaml new file mode 100644 index 0000000..fe474f2 --- /dev/null +++ b/Sln.Wcs.UI/Views/SystemMonitorView.axaml @@ -0,0 +1,42 @@ + + + + + + + + + + + +