using FATrace.WPLApp.Core; using Prism.Commands; using System; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; using System.Windows.Threading; using FreeSql; using FATrace.Model; using FATrace.WPLApp.Services; using System.IO; using System.Windows; using System.Text; using Prism.Events; using FATrace.WPLApp.Events; namespace FATrace.WPLApp.ViewModels { public class DashboardViewModel : NavigationViewModel { private readonly IFreeSql _fsql; private readonly ILogService _log; private readonly DataServices _data; private DispatcherTimer _logTimer; private bool _initialized; private TextWriter _originalConsoleOut; private ConsoleInterceptWriter _consoleInterceptor; private readonly IEventAggregator _ea; public DashboardViewModel(IFreeSql fsql, ILogService log, DataServices data, IEventAggregator ea) { _fsql = fsql; _log = log; _data = data; _ea = ea; LiveMessages = new ObservableCollection(); RefreshCommand = new DelegateCommand(async () => await RefreshStatsAsync()); ClearLogsCommand = new DelegateCommand(() => LiveMessages.Clear()); PlcConnected = _data.PlcConnected; _data.PropertyChanged += (s, e) => { if (e.PropertyName == nameof(DataServices.PlcConnected)) { var val = _data.PlcConnected; if (Application.Current?.Dispatcher?.CheckAccess() == true) PlcConnected = val; else Application.Current?.Dispatcher?.BeginInvoke(new Action(() => PlcConnected = val)); } }; // 订阅产线信号事件,生成人类可读的运行消息 _data.LineSglModel.WeightScanCodeHandle += (s, e) => { var code = _data.WeightScanCode; if (Application.Current?.Dispatcher?.CheckAccess() == true) LatestWeightScanCode = code; else Application.Current?.Dispatcher?.BeginInvoke(new Action(() => LatestWeightScanCode = code)); AppendLiveMessage($"称重扫码触发: {code}"); }; _data.LineSglModel.BoxSprayCodeReqHandle += (s, e) => { AppendLiveMessage("外箱喷码请求: 已向PLC下发喷码数据"); }; _data.LineSglModel.BoxScanCodeReqHandle += (s, e) => { var code = _data.BoxScanCode; if (Application.Current?.Dispatcher?.CheckAccess() == true) LatestBoxScanCode = code; else Application.Current?.Dispatcher?.BeginInvoke(new Action(() => LatestBoxScanCode = code)); AppendLiveMessage($"外箱扫码触发: {code}"); }; // 订阅外部刷新事件(来自 DataServices 的 BoxScanCode 完成后) try { _dashEventToken = _ea.GetEvent().Subscribe(_ => { Application.Current?.Dispatcher?.BeginInvoke(new Action(async () => await RefreshStatsAsync())); }); } catch { } } #region Properties private double _todayWeight; public double TodayWeight { get => _todayWeight; set { _todayWeight = value; RaisePropertyChanged(); } } private double _monthWeight; public double MonthWeight { get => _monthWeight; set { _monthWeight = value; RaisePropertyChanged(); } } private double _yearWeight; public double YearWeight { get => _yearWeight; set { _yearWeight = value; RaisePropertyChanged(); } } private double _totalWeight; public double TotalWeight { get => _totalWeight; set { _totalWeight = value; RaisePropertyChanged(); } } private string _latestWeightScanCode; public string LatestWeightScanCode { get => _latestWeightScanCode; set { _latestWeightScanCode = value; RaisePropertyChanged(); } } private string _latestBoxScanCode; public string LatestBoxScanCode { get => _latestBoxScanCode; set { _latestBoxScanCode = value; RaisePropertyChanged(); } } private bool _plcConnected; public bool PlcConnected { get => _plcConnected; set { _plcConnected = value; RaisePropertyChanged(); } } public ObservableCollection LiveMessages { get; } #endregion #region Commands public DelegateCommand RefreshCommand { get; } public DelegateCommand ClearLogsCommand { get; } #endregion private void StartLogTimer() { if (_logTimer != null) return; _logTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(500) }; _logTimer.Tick += (s, e) => { // 从 DataServices 的队列中取出消息并展示 int drain = 0; while (_data.TryDequeueMessage(out var msg)) { if (!string.IsNullOrWhiteSpace(msg)) { AppendLiveMessage(msg); drain++; if (drain >= 50) break; // 防止一次处理过多 } } }; _logTimer.Start(); } private void AppendLiveMessage(string message) { if (string.IsNullOrWhiteSpace(message)) return; var line = $"{DateTime.Now:HH:mm:ss} {message}"; void add() => LiveMessages.Add(line); if (Application.Current?.Dispatcher?.CheckAccess() == true) add(); else Application.Current?.Dispatcher?.BeginInvoke(new Action(add)); // 限制最大条数,避免内存增长 const int max = 500; void trim() { if (LiveMessages.Count > max) { while (LiveMessages.Count > max) LiveMessages.RemoveAt(0); } } if (Application.Current?.Dispatcher?.CheckAccess() == true) trim(); else Application.Current?.Dispatcher?.BeginInvoke(new Action(trim)); } private async Task RefreshStatsAsync() { try { var todayStart = DateTime.Today; var todayEnd = todayStart.AddDays(1).AddTicks(-1); var monthStart = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1); var monthEnd = monthStart.AddMonths(1).AddTicks(-1); var yearStart = new DateTime(DateTime.Today.Year, 1, 1); var yearEnd = yearStart.AddYears(1).AddTicks(-1); var ret = await Task.Run(() => { double t = Convert.ToDouble( _fsql.Select() .Where(a => a.WeightTime >= todayStart && a.WeightTime <= todayEnd) .Sum(a => a.Weight)); double m = Convert.ToDouble( _fsql.Select() .Where(a => a.WeightTime >= monthStart && a.WeightTime <= monthEnd) .Sum(a => a.Weight)); double y = Convert.ToDouble( _fsql.Select() .Where(a => a.WeightTime >= yearStart && a.WeightTime <= yearEnd) .Sum(a => a.Weight)); double all = Convert.ToDouble(_fsql.Select().Sum(a => a.Weight)); return (t, m, y, all); }); TodayWeight = ret.t; MonthWeight = ret.m; YearWeight = ret.y; TotalWeight = ret.all; } catch (Exception ex) { _log.Error($"Dashboard 统计刷新失败: {ex}"); } } public override async void OnNavigatedTo(Prism.Regions.NavigationContext navigationContext) { if (!_initialized) { _initialized = true; //StartLogTimer(); await RefreshStatsAsync(); // 初始化展示最近一次扫描值 LatestWeightScanCode = _data.WeightScanCode; LatestBoxScanCode = _data.BoxScanCode; TryHookConsole(); } } public override void OnNavigatedFrom(Prism.Regions.NavigationContext navigationContext) { if (_logTimer != null) { try { _logTimer.Stop(); } catch { } _logTimer = null; } UnhookConsole(); try { if (_dashEventToken != null) _ea.GetEvent().Unsubscribe(_dashEventToken); } catch { } } private void TryHookConsole() { try { if (_consoleInterceptor != null) return; _originalConsoleOut = Console.Out; _consoleInterceptor = new ConsoleInterceptWriter(_originalConsoleOut, AppendLiveMessage); Console.SetOut(_consoleInterceptor); } catch { } } private void UnhookConsole() { try { if (_consoleInterceptor != null && _originalConsoleOut != null) { Console.SetOut(_originalConsoleOut); } } catch { } finally { _consoleInterceptor = null; _originalConsoleOut = null; } } private class ConsoleInterceptWriter : TextWriter { private readonly TextWriter _inner; private readonly Action _onLine; private readonly StringWriter _buffer = new StringWriter(); public ConsoleInterceptWriter(TextWriter inner, Action onLine) { _inner = inner; _onLine = onLine; } public override Encoding Encoding => _inner.Encoding; public override void Write(char value) { _inner.Write(value); if (value == '\n') { var line = _buffer.ToString(); _buffer.GetStringBuilder().Clear(); if (!string.IsNullOrWhiteSpace(line)) _onLine(line.TrimEnd('\r')); } else { _buffer.Write(value); } } public override void Write(string value) { _inner.Write(value); if (value == null) return; foreach (var ch in value) { Write(ch); } } public override void WriteLine(string value) { _inner.WriteLine(value); if (!string.IsNullOrWhiteSpace(value)) _onLine(value); } } private SubscriptionToken _dashEventToken; } }