Files
FATrace/FATrace.WPLApp/ViewModels/DashboardViewModel.cs
2025-11-26 16:46:48 +08:00

292 lines
10 KiB
C#

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<string>();
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<DashboardRefreshEvent>().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<string> LiveMessages { get; }
#endregion
#region Commands
public DelegateCommand RefreshCommand { get; }
public DelegateCommand ClearLogsCommand { get; }
#endregion
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<RawProUse>()
.Where(a => a.WeightTime >= todayStart && a.WeightTime <= todayEnd)
.Sum(a => a.Weight));
double m = Convert.ToDouble(
_fsql.Select<RawProUse>()
.Where(a => a.WeightTime >= monthStart && a.WeightTime <= monthEnd)
.Sum(a => a.Weight));
double y = Convert.ToDouble(
_fsql.Select<RawProUse>()
.Where(a => a.WeightTime >= yearStart && a.WeightTime <= yearEnd)
.Sum(a => a.Weight));
double all = Convert.ToDouble(_fsql.Select<RawProUse>().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<DashboardRefreshEvent>().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<string> _onLine;
private readonly StringWriter _buffer = new StringWriter();
public ConsoleInterceptWriter(TextWriter inner, Action<string> 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;
}
}