change - 添加控制台日志输出

dev
WenJY 16 hours ago
parent ab609394e4
commit 15edaebf44

@ -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|<Border CornerRadius=\"8\" Padding=\"22\" BorderBrush=\"#1E3550\" BorderThickness=\"1\">|<Border CornerRadius=\"8\" Padding=\"22\" BorderBrush=\"#1E3550\" BorderThickness=\"1\"><Border.Background><LinearGradientBrush StartPoint=\"0%,0%\" EndPoint=\"0%,100%\"><GradientStop Color=\"#121C29\" Offset=\"0\"/><GradientStop Color=\"#0F1620\" Offset=\"1\"/></LinearGradientBrush></Border.Background>|g' HomePageView.axaml)"
"Bash(sed -i '' 's|<Border CornerRadius=\"8\" Padding=\"22\" BorderBrush=\"#1E3550\" BorderThickness=\"1\">|<Border CornerRadius=\"8\" Padding=\"22\" BorderBrush=\"#1E3550\" BorderThickness=\"1\"><Border.Background><LinearGradientBrush StartPoint=\"0%,0%\" EndPoint=\"0%,100%\"><GradientStop Color=\"#121C29\" Offset=\"0\"/><GradientStop Color=\"#0F1620\" Offset=\"1\"/></LinearGradientBrush></Border.Background>|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)"
]
}
}

@ -80,6 +80,7 @@ public partial class App : Application
// 5. 注册 ViewModel
services.AddSingleton<ViewModels.NavigationViewModel>();
services.AddSingleton<ViewModels.HomePageViewModel>();
services.AddSingleton<ViewModels.SystemMonitorViewModel>();
services.AddTransient<ViewModels.Device.DeviceHostViewModel>();
services.AddTransient<ViewModels.Device.DeviceInfoViewModel>();
services.AddTransient<ViewModels.Device.DeviceParamViewModel>();

@ -16,6 +16,7 @@ public interface ICrudPageViewModel
{
void Load();
Avalonia.Controls.Control CreateView();
string PageTitle { get; }
}
public abstract partial class CrudPageViewModel<T> : ObservableObject, ICrudPageViewModel where T : class, new()
@ -56,29 +57,23 @@ public abstract partial class CrudPageViewModel<T> : ObservableObject, ICrudPage
[RelayCommand]
public void Load()
{
Console.WriteLine($"[CRUD] Load 调用, T={typeof(T).Name}, SearchText='{SearchText}'");
try
{
List<T> 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<T>(list);
StatusText = $"共 {list.Count} 条记录";
Console.WriteLine($"[CRUD] Items 已设置, Count={Items.Count}");
}
catch (Exception ex)
{
Console.WriteLine($"[CRUD] Load 异常: {ex}");
StatusText = $"加载失败: {ex.Message}";
}
}

@ -11,7 +11,6 @@ public class DeviceHostViewModel : CrudPageViewModel<BaseDeviceHost>
public DeviceHostViewModel(IBaseDeviceHostService service) : base(service)
{
PageTitle = "设备主机管理";
System.Console.WriteLine("[VM] DeviceHostViewModel 构造, service 类型: " + service.GetType().FullName);
}
public override List<FieldConfig> FieldConfigs => new()

@ -21,6 +21,9 @@ public partial class NavigationViewModel : ObservableObject
public ObservableCollection<TopMenuItem> TopMenuItems { get; } = new();
[ObservableProperty]
private string _currentPageTitle = "首页";
public event Action<Control?>? 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<SubMenuItem>
{
new("库位信息", () => NavigateTo<LocationInfoViewModel>()),
new("物料信息", () => NavigateTo<MaterialInfoViewModel>()),
new("仓库信息", () => NavigateTo<StoreInfoViewModel>()),
new("库位信息", () => NavigateTo<LocationInfoViewModel>("基础数据")),
new("物料信息", () => NavigateTo<MaterialInfoViewModel>("基础数据")),
new("仓库信息", () => NavigateTo<StoreInfoViewModel>("基础数据")),
}));
TopMenuItems.Add(new TopMenuItem("设备管理", new List<SubMenuItem>
{
new("设备主机", () => NavigateTo<DeviceHostViewModel>()),
new("设备信息", () => NavigateTo<DeviceInfoViewModel>()),
new("设备参数", () => NavigateTo<DeviceParamViewModel>()),
new("设备主机", () => NavigateTo<DeviceHostViewModel>("设备管理")),
new("设备信息", () => NavigateTo<DeviceInfoViewModel>("设备管理")),
new("设备参数", () => NavigateTo<DeviceParamViewModel>("设备管理")),
}));
TopMenuItems.Add(new TopMenuItem("路径管理", new List<SubMenuItem>
{
new("路径信息", () => NavigateTo<PathInfoViewModel>()),
new("路径明细", () => NavigateTo<PathDetailsViewModel>()),
new("路径信息", () => NavigateTo<PathInfoViewModel>("路径管理")),
new("路径明细", () => NavigateTo<PathDetailsViewModel>("路径管理")),
}));
TopMenuItems.Add(new TopMenuItem("任务管理", new List<SubMenuItem>
{
new("任务队列", () => NavigateTo<TaskQueueViewModel>()),
new("任务明细", () => NavigateTo<TaskDetailViewModel>()),
new("任务队列", () => NavigateTo<TaskQueueViewModel>("任务管理")),
new("任务明细", () => NavigateTo<TaskDetailViewModel>("任务管理")),
}));
}
private string? _currentModule;
private void ShowHome()
{
if (_homeView == null)
@ -66,10 +72,17 @@ public partial class NavigationViewModel : ObservableObject
var vm = _sp.GetRequiredService<HomePageViewModel>();
_homeView = new HomePageView { DataContext = vm };
}
CurrentPageTitle = "首页";
PageChanged?.Invoke(_homeView);
}
private void NavigateTo<T>() where T : ICrudPageViewModel
private void ShowMonitor()
{
CurrentPageTitle = "系统监控";
PageChanged?.Invoke(new SystemMonitorView(_sp.GetRequiredService<SystemMonitorViewModel>()));
}
private void NavigateTo<T>(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();
}

@ -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<LogEntry> _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";
}
/// <summary>
/// 拦截 Console.WriteLine 输出,同时写入原始输出和 ObservableCollection
/// </summary>
internal class LogTextWriter : TextWriter
{
private readonly TextWriter _original;
private readonly Action<LogEntry> _onWrite;
private readonly StringBuilder _buffer = new();
public LogTextWriter(TextWriter original, Action<LogEntry> 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" });
}
}
}

@ -56,7 +56,13 @@
<!-- Menu Bar -->
<Border Grid.Row="1" Padding="16,0" Background="#0C1622" BorderBrush="#1A2F4A" BorderThickness="0,1,0,1">
<StackPanel x:Name="MenuContainer" Orientation="Horizontal" Spacing="0" />
<Grid ColumnDefinitions="Auto,*">
<StackPanel Grid.Column="0" x:Name="MenuContainer" Orientation="Horizontal" Spacing="0" />
<!-- 当前页面路径 -->
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="16,0,0,0">
<TextBlock Text="{Binding CurrentPageTitle}" FontSize="12" Foreground="#4FC3F7" VerticalAlignment="Center" />
</StackPanel>
</Grid>
</Border>
<!-- Content Area -->

@ -0,0 +1,42 @@
<UserControl x:CompileBindings="False" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Sln.Wcs.UI.Views.SystemMonitorView">
<Grid RowDefinitions="Auto,*" Margin="20,14,20,14" VerticalAlignment="Stretch">
<!-- Toolbar -->
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto,Auto" Margin="0,0,0,8">
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="6">
<Ellipse Width="8" Height="8" Fill="#00E676" VerticalAlignment="Center" />
<TextBlock Text="系统监控" FontSize="15" FontWeight="SemiBold" Foreground="#DDE4F0" VerticalAlignment="Center" />
</StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="6" Margin="0,0,10,0">
<TextBlock Text="{Binding LogCount, StringFormat='共 {0} 条'}" FontSize="12" Foreground="#6B8CB5" VerticalAlignment="Center" />
</StackPanel>
<Button Grid.Column="3" Content="清空" Command="{Binding ClearCommand}"
Background="#0F1F38" Foreground="#8B9BB5" Padding="12,5" FontSize="12" Margin="0,0,6,0" />
<Button Grid.Column="4" Content="{Binding AutoScroll, StringFormat={}{0} auto?}" Command="{Binding ToggleAutoScrollCommand}"
Background="#0F1F38" Foreground="#8B9BB5" Padding="12,5" FontSize="12" />
</Grid>
<!-- Log List -->
<Border Grid.Row="1" Background="#0C1622" CornerRadius="6" BorderBrush="#1A2F4A" BorderThickness="1">
<ListBox x:Name="LogList" ItemsSource="{Binding Logs}"
Background="Transparent" Foreground="#BCC8D6" BorderThickness="0"
>
<ListBox.ItemTemplate>
<DataTemplate>
<Border Padding="10,3" BorderBrush="#1A2F4A" BorderThickness="0,0,0,1">
<StackPanel Orientation="Horizontal" Spacing="10">
<TextBlock Text="{Binding TimeText}"
FontSize="11" Foreground="#4B5E7A" FontFamily="Menlo,Consolas,monospace"
VerticalAlignment="Center" Width="100" />
<TextBlock Text="{Binding Message}"
FontSize="11" Foreground="#8B9BB5" FontFamily="Menlo,Consolas,monospace"
TextWrapping="Wrap" VerticalAlignment="Center" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Border>
</Grid>
</UserControl>

@ -0,0 +1,17 @@
using Avalonia.Controls;
using Sln.Wcs.UI.ViewModels;
namespace Sln.Wcs.UI.Views;
public partial class SystemMonitorView : UserControl
{
public SystemMonitorView()
{
InitializeComponent();
}
public SystemMonitorView(SystemMonitorViewModel vm) : this()
{
DataContext = vm;
}
}
Loading…
Cancel
Save