初期稳定版本260119

This commit is contained in:
2026-01-19 12:40:45 +08:00
parent 82d0bd6b95
commit f65fa21760
32 changed files with 2494 additions and 138 deletions

View File

@@ -13,6 +13,7 @@ using System.Windows;
using System.Text;
using Prism.Events;
using FATrace.WPLApp.Events;
using System.Threading;
namespace FATrace.WPLApp.ViewModels
{
@@ -22,6 +23,9 @@ namespace FATrace.WPLApp.ViewModels
private readonly ILogService _log;
private readonly DataServices _data;
private int _dashboardRefreshRequested;
private int _dashboardRefreshLoopRunning;
private DispatcherTimer _logTimer;
private bool _initialized;
private TextWriter _originalConsoleOut;
@@ -37,6 +41,7 @@ namespace FATrace.WPLApp.ViewModels
_ea = ea;
LiveMessages = new ObservableCollection<string>();
LineTempCodes = new ObservableCollection<LineTempCode>();
RefreshCommand = new DelegateCommand(async () => await RefreshStatsAsync());
ClearLogsCommand = new DelegateCommand(() => LiveMessages.Clear());
@@ -66,7 +71,13 @@ namespace FATrace.WPLApp.ViewModels
};
_data.LineSglModel.BoxSprayCodeReqHandle += (s, e) =>
{
AppendLiveMessage("外箱喷码请求: 已向PLC下发喷码数据");
var code = _data.BoxSprayCode;
if (Application.Current?.Dispatcher?.CheckAccess() == true)
LatestBoxSprayCode = code;
else
Application.Current?.Dispatcher?.BeginInvoke(new Action(() => LatestBoxSprayCode = code));
//AppendLiveMessage("外箱喷码请求: 已向PLC下发喷码数据");
AppendLiveMessage($"外箱喷码请求: 已向PLC下发喷码数据;{code}");
};
_data.LineSglModel.BoxScanCodeReqHandle += (s, e) =>
{
@@ -81,12 +92,89 @@ namespace FATrace.WPLApp.ViewModels
// 订阅外部刷新事件(来自 DataServices 的 BoxScanCode 完成后)
try
{
_dashEventToken = _ea.GetEvent<DashboardRefreshEvent>().Subscribe(_ =>
{
Application.Current?.Dispatcher?.BeginInvoke(new Action(async () => await RefreshStatsAsync()));
});
EnsureDashboardRefreshSubscription();
}
catch (Exception ex)
{
_log?.Warn($"DashboardRefreshEvent 订阅失败: {ex.Message}");
}
}
private void EnsureDashboardRefreshSubscription()
{
if (_dashEventToken != null) return;
// 注意Prism 默认 keepSubscriberReferenceAlive=false弱引用
// 若使用 lambda目标可能是 closure 对象,存在被 GC 导致订阅失效的风险。
// 这里使用具名方法 + keepSubscriberReferenceAlive=true确保订阅稳定。
_dashEventToken = _ea.GetEvent<DashboardRefreshEvent>()
.Subscribe(OnDashboardRefreshEvent, ThreadOption.UIThread, true);
}
private void OnDashboardRefreshEvent(bool _)
{
RequestDashboardRefresh();
}
/// <summary>
/// 触发一次 Dashboard 刷新(合并 + 互斥):短时间多次触发只会串行执行,并在执行结束后最多再补跑一次。
/// </summary>
private void RequestDashboardRefresh()
{
// 标记有刷新请求
Interlocked.Exchange(ref _dashboardRefreshRequested, 1);
// 若刷新循环已在跑,则仅标记请求即可
if (Interlocked.Exchange(ref _dashboardRefreshLoopRunning, 1) == 1)
{
return;
}
var dispatcher = Application.Current?.Dispatcher;
if (dispatcher == null)
{
// 极少数场景(无 UI Dispatcher直接后台跑
_ = DashboardRefreshLoopAsync();
return;
}
dispatcher.BeginInvoke(new Action(async () =>
{
try
{
await DashboardRefreshLoopAsync();
}
finally
{
Interlocked.Exchange(ref _dashboardRefreshLoopRunning, 0);
// 如果在执行过程中又来了刷新请求,则再次启动一次循环(确保不漏刷新)
if (Interlocked.Exchange(ref _dashboardRefreshRequested, 0) == 1)
{
RequestDashboardRefresh();
}
}
}));
}
/// <summary>
/// 在 UI 线程串行执行刷新,且合并多次触发。
/// </summary>
private async Task DashboardRefreshLoopAsync()
{
// 只要存在刷新请求,就执行一次刷新;过程中再来请求,会在下一轮继续跑
while (Interlocked.Exchange(ref _dashboardRefreshRequested, 0) == 1)
{
try
{
await RefreshStatsAsync();
await RefreshLineTempCodesAsync();
}
catch (Exception ex)
{
_log.Error($"DashboardRefreshEvent 刷新失败: {ex}");
}
}
catch { }
}
#region Properties
@@ -108,17 +196,28 @@ namespace FATrace.WPLApp.ViewModels
private string _latestBoxScanCode;
public string LatestBoxScanCode { get => _latestBoxScanCode; set { _latestBoxScanCode = value; RaisePropertyChanged(); } }
private string _latestBoxSprayCode;
/// <summary>
/// 最近一次外箱喷码数据(即下发给 PLC 的源字符串)
/// </summary>
public string LatestBoxSprayCode { get => _latestBoxSprayCode; set { _latestBoxSprayCode = value; RaisePropertyChanged(); } }
private bool _plcConnected;
public bool PlcConnected { get => _plcConnected; set { _plcConnected = value; RaisePropertyChanged(); } }
public ObservableCollection<string> LiveMessages { get; }
/// <summary>
/// 产线临时条码队列(内包扫码入队,外箱扫码确认后出队)
/// </summary>
public ObservableCollection<LineTempCode> LineTempCodes { get; }
#endregion
#region Commands
public DelegateCommand RefreshCommand { get; }
public DelegateCommand ClearLogsCommand { get; }
#endregion
private void AppendLiveMessage(string message)
{
if (string.IsNullOrWhiteSpace(message)) return;
@@ -186,8 +285,63 @@ namespace FATrace.WPLApp.ViewModels
}
}
/// <summary>
/// 从数据库刷新产线临时队列LineTempCode 表)并同步到 UI。
/// </summary>
private async Task RefreshLineTempCodesAsync()
{
try
{
var list = await Task.Run(() =>
{
return _fsql.Select<LineTempCode>()
.OrderBy(a => a.Id)
.Limit(200)
.ToList();
});
void apply()
{
LineTempCodes.Clear();
foreach (var item in list)
{
LineTempCodes.Add(item);
}
}
var dispatcher = Application.Current?.Dispatcher;
if (dispatcher == null)
{
apply();
return;
}
if (dispatcher.CheckAccess())
{
apply();
}
else
{
await dispatcher.InvokeAsync(apply).Task.ConfigureAwait(false);
}
}
catch (Exception ex)
{
_log.Error($"刷新 LineTempCode 队列失败: {ex}");
}
}
public override async void OnNavigatedTo(Prism.Regions.NavigationContext navigationContext)
{
try
{
EnsureDashboardRefreshSubscription();
}
catch (Exception ex)
{
_log?.Warn($"DashboardRefreshEvent OnNavigatedTo 订阅失败: {ex.Message}");
}
if (!_initialized)
{
_initialized = true;
@@ -196,6 +350,8 @@ namespace FATrace.WPLApp.ViewModels
// 初始化展示最近一次扫描值
LatestWeightScanCode = _data.WeightScanCode;
LatestBoxScanCode = _data.BoxScanCode;
LatestBoxSprayCode = _data.BoxSprayCode;
await RefreshLineTempCodesAsync();
TryHookConsole();
}
}
@@ -208,7 +364,18 @@ namespace FATrace.WPLApp.ViewModels
_logTimer = null;
}
UnhookConsole();
try { if (_dashEventToken != null) _ea.GetEvent<DashboardRefreshEvent>().Unsubscribe(_dashEventToken); } catch { }
try
{
if (_dashEventToken != null)
{
_ea.GetEvent<DashboardRefreshEvent>().Unsubscribe(_dashEventToken);
_dashEventToken = null;
}
}
catch (Exception ex)
{
_log?.Warn($"DashboardRefreshEvent 退订失败: {ex.Message}");
}
}
private void TryHookConsole()