refactor: organize cnc view model folders
This commit is contained in:
+690
-690
File diff suppressed because it is too large
Load Diff
+53
-53
@@ -1,54 +1,54 @@
|
||||
// ── CNC 流水线步骤 ViewModel ──
|
||||
// 检测模块流水线中的单一步骤(具体图像处理算子)的轻量封装,
|
||||
// 在树形结构中作为三级节点显示。
|
||||
using Prism.Mvvm;
|
||||
|
||||
namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
/// <summary>
|
||||
/// 检测模块流水线步骤的轻量 ViewModel,用于在 CNC 树形结构中作为三级节点显示。
|
||||
/// Lightweight ViewModel for a pipeline step inside an InspectionModule node,
|
||||
/// displayed as a 3rd-level node in the CNC tree.
|
||||
/// </summary>
|
||||
public class CncPipelineStepViewModel : BindableBase
|
||||
{
|
||||
private string _displayName;
|
||||
private bool _isEnabled;
|
||||
private string _iconPath;
|
||||
|
||||
public CncPipelineStepViewModel(string displayName, bool isEnabled, string iconPath = null)
|
||||
{
|
||||
_displayName = displayName;
|
||||
_isEnabled = isEnabled;
|
||||
_iconPath = iconPath ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>算子显示名称(中文)| Operator display name</summary>
|
||||
public string DisplayName
|
||||
{
|
||||
get => _displayName;
|
||||
set => SetProperty(ref _displayName, value);
|
||||
}
|
||||
|
||||
/// <summary>是否启用 | Whether the step is enabled</summary>
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => _isEnabled;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _isEnabled, value))
|
||||
RaisePropertyChanged(nameof(StateText));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>图标路径 | Icon path</summary>
|
||||
public string IconPath
|
||||
{
|
||||
get => _iconPath;
|
||||
set => SetProperty(ref _iconPath, value);
|
||||
}
|
||||
|
||||
/// <summary>状态文字(已启用 / 已停用)| State text</summary>
|
||||
public string StateText => _isEnabled ? "已启用" : "已停用";
|
||||
}
|
||||
// ── CNC 流水线步骤 ViewModel ──
|
||||
// 检测模块流水线中的单一步骤(具体图像处理算子)的轻量封装,
|
||||
// 在树形结构中作为三级节点显示。
|
||||
using Prism.Mvvm;
|
||||
|
||||
namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
/// <summary>
|
||||
/// 检测模块流水线步骤的轻量 ViewModel,用于在 CNC 树形结构中作为三级节点显示。
|
||||
/// Lightweight ViewModel for a pipeline step inside an InspectionModule node,
|
||||
/// displayed as a 3rd-level node in the CNC tree.
|
||||
/// </summary>
|
||||
public class CncPipelineStepViewModel : BindableBase
|
||||
{
|
||||
private string _displayName;
|
||||
private bool _isEnabled;
|
||||
private string _iconPath;
|
||||
|
||||
public CncPipelineStepViewModel(string displayName, bool isEnabled, string iconPath = null)
|
||||
{
|
||||
_displayName = displayName;
|
||||
_isEnabled = isEnabled;
|
||||
_iconPath = iconPath ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>算子显示名称(中文)| Operator display name</summary>
|
||||
public string DisplayName
|
||||
{
|
||||
get => _displayName;
|
||||
set => SetProperty(ref _displayName, value);
|
||||
}
|
||||
|
||||
/// <summary>是否启用 | Whether the step is enabled</summary>
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => _isEnabled;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _isEnabled, value))
|
||||
RaisePropertyChanged(nameof(StateText));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>图标路径 | Icon path</summary>
|
||||
public string IconPath
|
||||
{
|
||||
get => _iconPath;
|
||||
set => SetProperty(ref _iconPath, value);
|
||||
}
|
||||
|
||||
/// <summary>状态文字(已启用 / 已停用)| State text</summary>
|
||||
public string StateText => _isEnabled ? "已启用" : "已停用";
|
||||
}
|
||||
}
|
||||
+33
-33
@@ -1,34 +1,34 @@
|
||||
// ── CNC 程序树根节点 ViewModel ──
|
||||
// 程序树的顶层根节点,包含子节点列表(CncNodeViewModel),
|
||||
// 负责子节点的添加/移除通知。
|
||||
using Prism.Mvvm;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
public class CncProgramTreeRootViewModel : BindableBase
|
||||
{
|
||||
private string _displayName;
|
||||
private bool _isExpanded = true;
|
||||
|
||||
public CncProgramTreeRootViewModel(string displayName, ObservableCollection<CncNodeViewModel> children)
|
||||
{
|
||||
_displayName = displayName;
|
||||
Children = children;
|
||||
}
|
||||
|
||||
public string DisplayName
|
||||
{
|
||||
get => _displayName;
|
||||
set => SetProperty(ref _displayName, value);
|
||||
}
|
||||
|
||||
public bool IsExpanded
|
||||
{
|
||||
get => _isExpanded;
|
||||
set => SetProperty(ref _isExpanded, value);
|
||||
}
|
||||
|
||||
public ObservableCollection<CncNodeViewModel> Children { get; }
|
||||
}
|
||||
// ── CNC 程序树根节点 ViewModel ──
|
||||
// 程序树的顶层根节点,包含子节点列表(CncNodeViewModel),
|
||||
// 负责子节点的添加/移除通知。
|
||||
using Prism.Mvvm;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
public class CncProgramTreeRootViewModel : BindableBase
|
||||
{
|
||||
private string _displayName;
|
||||
private bool _isExpanded = true;
|
||||
|
||||
public CncProgramTreeRootViewModel(string displayName, ObservableCollection<CncNodeViewModel> children)
|
||||
{
|
||||
_displayName = displayName;
|
||||
Children = children;
|
||||
}
|
||||
|
||||
public string DisplayName
|
||||
{
|
||||
get => _displayName;
|
||||
set => SetProperty(ref _displayName, value);
|
||||
}
|
||||
|
||||
public bool IsExpanded
|
||||
{
|
||||
get => _isExpanded;
|
||||
set => SetProperty(ref _isExpanded, value);
|
||||
}
|
||||
|
||||
public ObservableCollection<CncNodeViewModel> Children { get; }
|
||||
}
|
||||
}
|
||||
+101
-101
@@ -1,102 +1,102 @@
|
||||
// ── 检测指标行 ViewModel ──
|
||||
// 将 InspectionMetricResult 模型封装为可绑定的行数据,
|
||||
// 在检测执行结果列表中显示指标名称、测量值、容差范围等。
|
||||
using Prism.Mvvm;
|
||||
using System.Windows.Media;
|
||||
using XplorePlane.Themes;
|
||||
|
||||
namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
/// <summary>
|
||||
/// 检测指标行 ViewModel,将 InspectionMetricResult 模型封装为可绑定的 WPF ViewModel
|
||||
/// Inspection metric row ViewModel that wraps an InspectionMetricResult model into a bindable WPF ViewModel
|
||||
/// </summary>
|
||||
public class InspectionMetricRowViewModel : BindableBase
|
||||
{
|
||||
private readonly InspectionMetricResult _model;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数,从 InspectionMetricResult 模型初始化 ViewModel
|
||||
/// Constructor that initializes the ViewModel from an InspectionMetricResult model
|
||||
/// </summary>
|
||||
public InspectionMetricRowViewModel(InspectionMetricResult model)
|
||||
{
|
||||
_model = model;
|
||||
}
|
||||
|
||||
/// <summary>底层检测指标模型(只读)| Underlying inspection metric model (read-only)</summary>
|
||||
public InspectionMetricResult Model => _model;
|
||||
|
||||
/// <summary>指标名称 | Metric name</summary>
|
||||
public string MetricName => _model.MetricName;
|
||||
|
||||
/// <summary>实测值 | Measured metric value</summary>
|
||||
public double MetricValue => _model.MetricValue;
|
||||
|
||||
/// <summary>单位 | Unit of measurement</summary>
|
||||
public string Unit => _model.Unit;
|
||||
|
||||
/// <summary>下限 | Lower limit (nullable)</summary>
|
||||
public double? LowerLimit => _model.LowerLimit;
|
||||
|
||||
/// <summary>上限 | Upper limit (nullable)</summary>
|
||||
public double? UpperLimit => _model.UpperLimit;
|
||||
|
||||
/// <summary>单指标判定 | Individual metric pass/fail status</summary>
|
||||
public bool IsPass => _model.IsPass;
|
||||
|
||||
/// <summary>显示顺序 | Display order for sorting</summary>
|
||||
public int DisplayOrder => _model.DisplayOrder;
|
||||
|
||||
/// <summary>
|
||||
/// 下限文本(null 时显示 "—")
|
||||
/// Lower limit text (displays "—" when null)
|
||||
/// </summary>
|
||||
public string LowerLimitText => _model.LowerLimit.HasValue ? _model.LowerLimit.Value.ToString("F2") : "—";
|
||||
|
||||
/// <summary>
|
||||
/// 上限文本(null 时显示 "—")
|
||||
/// Upper limit text (displays "—" when null)
|
||||
/// </summary>
|
||||
public string UpperLimitText => _model.UpperLimit.HasValue ? _model.UpperLimit.Value.ToString("F2") : "—";
|
||||
|
||||
/// <summary>
|
||||
/// 实测值是否超出范围(用于加粗字体触发器)
|
||||
/// Whether the measured value is out of range (used for bold font trigger)
|
||||
/// </summary>
|
||||
public bool IsValueOutOfRange
|
||||
{
|
||||
get
|
||||
{
|
||||
// 如果下限不为空且实测值小于下限,则超出范围
|
||||
// If lower limit is not null and measured value is less than lower limit, it's out of range
|
||||
if (_model.LowerLimit.HasValue && _model.MetricValue < _model.LowerLimit.Value)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果上限不为空且实测值大于上限,则超出范围
|
||||
// If upper limit is not null and measured value is greater than upper limit, it's out of range
|
||||
if (_model.UpperLimit.HasValue && _model.MetricValue > _model.UpperLimit.Value)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 行背景色(IsPass=true → NOVA success 容器色,IsPass=false → NOVA error 容器色)
|
||||
/// Row background color (IsPass=true → NOVA success container, IsPass=false → NOVA error container)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 此前硬编码 #E8F5E9/#FFEBEE,与 Converters.cs 中 PassFailColorConverter
|
||||
/// 等判定色转换器各自维护一套色值。现改为从 Nova 令牌取色,与全应用的
|
||||
/// 通过/失败语义色统一。若资源未加载(极端情况),回退到浅灰,
|
||||
/// 不使用原硬编码色,避免同一处出现两套配色来源。
|
||||
/// </remarks>
|
||||
public Brush RowBackground =>
|
||||
NovaResourceHelper.FindBrush(_model.IsPass ? "NovaSuccessContainerBrush" : "NovaErrorContainerBrush");
|
||||
}
|
||||
// ── 检测指标行 ViewModel ──
|
||||
// 将 InspectionMetricResult 模型封装为可绑定的行数据,
|
||||
// 在检测执行结果列表中显示指标名称、测量值、容差范围等。
|
||||
using Prism.Mvvm;
|
||||
using System.Windows.Media;
|
||||
using XplorePlane.Themes;
|
||||
|
||||
namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
/// <summary>
|
||||
/// 检测指标行 ViewModel,将 InspectionMetricResult 模型封装为可绑定的 WPF ViewModel
|
||||
/// Inspection metric row ViewModel that wraps an InspectionMetricResult model into a bindable WPF ViewModel
|
||||
/// </summary>
|
||||
public class InspectionMetricRowViewModel : BindableBase
|
||||
{
|
||||
private readonly InspectionMetricResult _model;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数,从 InspectionMetricResult 模型初始化 ViewModel
|
||||
/// Constructor that initializes the ViewModel from an InspectionMetricResult model
|
||||
/// </summary>
|
||||
public InspectionMetricRowViewModel(InspectionMetricResult model)
|
||||
{
|
||||
_model = model;
|
||||
}
|
||||
|
||||
/// <summary>底层检测指标模型(只读)| Underlying inspection metric model (read-only)</summary>
|
||||
public InspectionMetricResult Model => _model;
|
||||
|
||||
/// <summary>指标名称 | Metric name</summary>
|
||||
public string MetricName => _model.MetricName;
|
||||
|
||||
/// <summary>实测值 | Measured metric value</summary>
|
||||
public double MetricValue => _model.MetricValue;
|
||||
|
||||
/// <summary>单位 | Unit of measurement</summary>
|
||||
public string Unit => _model.Unit;
|
||||
|
||||
/// <summary>下限 | Lower limit (nullable)</summary>
|
||||
public double? LowerLimit => _model.LowerLimit;
|
||||
|
||||
/// <summary>上限 | Upper limit (nullable)</summary>
|
||||
public double? UpperLimit => _model.UpperLimit;
|
||||
|
||||
/// <summary>单指标判定 | Individual metric pass/fail status</summary>
|
||||
public bool IsPass => _model.IsPass;
|
||||
|
||||
/// <summary>显示顺序 | Display order for sorting</summary>
|
||||
public int DisplayOrder => _model.DisplayOrder;
|
||||
|
||||
/// <summary>
|
||||
/// 下限文本(null 时显示 "—")
|
||||
/// Lower limit text (displays "—" when null)
|
||||
/// </summary>
|
||||
public string LowerLimitText => _model.LowerLimit.HasValue ? _model.LowerLimit.Value.ToString("F2") : "—";
|
||||
|
||||
/// <summary>
|
||||
/// 上限文本(null 时显示 "—")
|
||||
/// Upper limit text (displays "—" when null)
|
||||
/// </summary>
|
||||
public string UpperLimitText => _model.UpperLimit.HasValue ? _model.UpperLimit.Value.ToString("F2") : "—";
|
||||
|
||||
/// <summary>
|
||||
/// 实测值是否超出范围(用于加粗字体触发器)
|
||||
/// Whether the measured value is out of range (used for bold font trigger)
|
||||
/// </summary>
|
||||
public bool IsValueOutOfRange
|
||||
{
|
||||
get
|
||||
{
|
||||
// 如果下限不为空且实测值小于下限,则超出范围
|
||||
// If lower limit is not null and measured value is less than lower limit, it's out of range
|
||||
if (_model.LowerLimit.HasValue && _model.MetricValue < _model.LowerLimit.Value)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果上限不为空且实测值大于上限,则超出范围
|
||||
// If upper limit is not null and measured value is greater than upper limit, it's out of range
|
||||
if (_model.UpperLimit.HasValue && _model.MetricValue > _model.UpperLimit.Value)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 行背景色(IsPass=true → NOVA success 容器色,IsPass=false → NOVA error 容器色)
|
||||
/// Row background color (IsPass=true → NOVA success container, IsPass=false → NOVA error container)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 此前硬编码 #E8F5E9/#FFEBEE,与 Converters.cs 中 PassFailColorConverter
|
||||
/// 等判定色转换器各自维护一套色值。现改为从 Nova 令牌取色,与全应用的
|
||||
/// 通过/失败语义色统一。若资源未加载(极端情况),回退到浅灰,
|
||||
/// 不使用原硬编码色,避免同一处出现两套配色来源。
|
||||
/// </remarks>
|
||||
public Brush RowBackground =>
|
||||
NovaResourceHelper.FindBrush(_model.IsPass ? "NovaSuccessContainerBrush" : "NovaErrorContainerBrush");
|
||||
}
|
||||
}
|
||||
+392
-392
@@ -1,394 +1,394 @@
|
||||
// ── 检测节点卡片 ViewModel ──
|
||||
// CNC 执行时各检测节点运行状态监控的 ViewModel。
|
||||
// 绑定到运行视图中的校验卡片面板,实时显示每个检测子节点的状态/进度/数据。
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Prism.Commands;
|
||||
using Prism.Mvvm;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using XplorePlane.Helpers;
|
||||
using XplorePlane.Services.Storage;
|
||||
|
||||
namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
/// <summary>
|
||||
/// 检测节点卡片 ViewModel,包装单个 InspectionNodeResult 并提供图像切换和快照查看功能
|
||||
/// Inspection node card ViewModel that wraps a single InspectionNodeResult and provides image toggle and snapshot viewing
|
||||
/// </summary>
|
||||
public class InspectionNodeCardViewModel : BindableBase
|
||||
{
|
||||
private readonly InspectionNodeResult _nodeResult;
|
||||
private readonly IXpDataPathService _dataPathService;
|
||||
private readonly string _imageBaseDirectory;
|
||||
private readonly PipelineExecutionSnapshot _snapshot;
|
||||
private readonly InspectionAssetRecord _resultImageAsset;
|
||||
private readonly InspectionAssetRecord _inputImageAsset;
|
||||
private readonly IReadOnlyList<InspectionResultTable> _resultTables;
|
||||
|
||||
private BitmapSource _resultImage;
|
||||
private BitmapSource _inputImage;
|
||||
private BitmapSource _currentImage;
|
||||
private bool _isShowingInputImage;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
/// <param name="nodeResult">节点结果数据 | Node result data</param>
|
||||
/// <param name="metrics">该节点的所有指标 | All metrics for this node</param>
|
||||
/// <param name="assets">该节点的所有资产 | All assets for this node</param>
|
||||
/// <param name="snapshot">Pipeline 快照(可为 null)| Pipeline snapshot (can be null)</param>
|
||||
/// <param name="resultTables">该节点的结构化明细表(可为 null)| Structured result tables for this node (can be null)</param>
|
||||
/// <param name="dataPathService">数据路径服务 | Data path service</param>
|
||||
/// <param name="imageBaseDirectory">图像基目录覆盖(本地导入时使用;为空则用 {DataPath}\InspectionResults)| Image base override (used for local import; defaults to {DataPath}\InspectionResults)</param>
|
||||
public InspectionNodeCardViewModel(
|
||||
InspectionNodeResult nodeResult,
|
||||
IEnumerable<InspectionMetricResult> metrics,
|
||||
IEnumerable<InspectionAssetRecord> assets,
|
||||
PipelineExecutionSnapshot snapshot,
|
||||
IEnumerable<InspectionResultTable> resultTables,
|
||||
IXpDataPathService dataPathService,
|
||||
string imageBaseDirectory = null)
|
||||
{
|
||||
_nodeResult = nodeResult ?? throw new ArgumentNullException(nameof(nodeResult));
|
||||
_dataPathService = dataPathService ?? throw new ArgumentNullException(nameof(dataPathService));
|
||||
_imageBaseDirectory = string.IsNullOrWhiteSpace(imageBaseDirectory)
|
||||
// ── 检测节点卡片 ViewModel ──
|
||||
// CNC 执行时各检测节点运行状态监控的 ViewModel。
|
||||
// 绑定到运行视图中的校验卡片面板,实时显示每个检测子节点的状态/进度/数据。
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Prism.Commands;
|
||||
using Prism.Mvvm;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using XplorePlane.Helpers;
|
||||
using XplorePlane.Services.Storage;
|
||||
|
||||
namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
/// <summary>
|
||||
/// 检测节点卡片 ViewModel,包装单个 InspectionNodeResult 并提供图像切换和快照查看功能
|
||||
/// Inspection node card ViewModel that wraps a single InspectionNodeResult and provides image toggle and snapshot viewing
|
||||
/// </summary>
|
||||
public class InspectionNodeCardViewModel : BindableBase
|
||||
{
|
||||
private readonly InspectionNodeResult _nodeResult;
|
||||
private readonly IXpDataPathService _dataPathService;
|
||||
private readonly string _imageBaseDirectory;
|
||||
private readonly PipelineExecutionSnapshot _snapshot;
|
||||
private readonly InspectionAssetRecord _resultImageAsset;
|
||||
private readonly InspectionAssetRecord _inputImageAsset;
|
||||
private readonly IReadOnlyList<InspectionResultTable> _resultTables;
|
||||
|
||||
private BitmapSource _resultImage;
|
||||
private BitmapSource _inputImage;
|
||||
private BitmapSource _currentImage;
|
||||
private bool _isShowingInputImage;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
/// <param name="nodeResult">节点结果数据 | Node result data</param>
|
||||
/// <param name="metrics">该节点的所有指标 | All metrics for this node</param>
|
||||
/// <param name="assets">该节点的所有资产 | All assets for this node</param>
|
||||
/// <param name="snapshot">Pipeline 快照(可为 null)| Pipeline snapshot (can be null)</param>
|
||||
/// <param name="resultTables">该节点的结构化明细表(可为 null)| Structured result tables for this node (can be null)</param>
|
||||
/// <param name="dataPathService">数据路径服务 | Data path service</param>
|
||||
/// <param name="imageBaseDirectory">图像基目录覆盖(本地导入时使用;为空则用 {DataPath}\InspectionResults)| Image base override (used for local import; defaults to {DataPath}\InspectionResults)</param>
|
||||
public InspectionNodeCardViewModel(
|
||||
InspectionNodeResult nodeResult,
|
||||
IEnumerable<InspectionMetricResult> metrics,
|
||||
IEnumerable<InspectionAssetRecord> assets,
|
||||
PipelineExecutionSnapshot snapshot,
|
||||
IEnumerable<InspectionResultTable> resultTables,
|
||||
IXpDataPathService dataPathService,
|
||||
string imageBaseDirectory = null)
|
||||
{
|
||||
_nodeResult = nodeResult ?? throw new ArgumentNullException(nameof(nodeResult));
|
||||
_dataPathService = dataPathService ?? throw new ArgumentNullException(nameof(dataPathService));
|
||||
_imageBaseDirectory = string.IsNullOrWhiteSpace(imageBaseDirectory)
|
||||
? _dataPathService.DataPath
|
||||
: imageBaseDirectory;
|
||||
_snapshot = snapshot;
|
||||
|
||||
// 查找资产记录
|
||||
// Find asset records
|
||||
var assetList = assets?.ToList() ?? new List<InspectionAssetRecord>();
|
||||
_resultImageAsset = assetList.FirstOrDefault(a => a.NodeId == nodeResult.NodeId && a.AssetType == InspectionAssetType.NodeResultImage);
|
||||
_inputImageAsset = assetList.FirstOrDefault(a => a.NodeId == nodeResult.NodeId && a.AssetType == InspectionAssetType.NodeInputImage);
|
||||
|
||||
// 构建指标列表(按 DisplayOrder 升序,同序按 MetricName 升序)
|
||||
// Build metrics list (sorted by DisplayOrder ascending, then by MetricName ascending)
|
||||
var metricList = metrics?
|
||||
.Where(m => m.NodeId == nodeResult.NodeId)
|
||||
.OrderBy(m => m.DisplayOrder)
|
||||
.ThenBy(m => m.MetricName)
|
||||
.Select(m => new InspectionMetricRowViewModel(m))
|
||||
.ToList() ?? new List<InspectionMetricRowViewModel>();
|
||||
|
||||
Metrics = new ObservableCollection<InspectionMetricRowViewModel>(metricList);
|
||||
|
||||
// 筛选属于本节点的结构化明细表,并按 DisplayOrder/TableName 排序
|
||||
// Filter structured result tables that belong to this node, sorted by DisplayOrder/TableName
|
||||
_resultTables = resultTables?
|
||||
.Where(t => t.NodeId == nodeResult.NodeId)
|
||||
.OrderBy(t => t.DisplayOrder)
|
||||
.ThenBy(t => t.TableName)
|
||||
.ToList()
|
||||
as IReadOnlyList<InspectionResultTable>
|
||||
?? Array.Empty<InspectionResultTable>();
|
||||
|
||||
// 为 UI 绑定构建列信息,按 DisplayOrder 取第一个有数据的表(UI 一次只显示一个明细表)
|
||||
// Build column info for UI binding from the first table with data (UI shows one detail table at a time)
|
||||
if (_resultTables.Count > 0 && _resultTables[0].Columns?.Count > 0)
|
||||
{
|
||||
ResultTableColumns = _resultTables[0].Columns;
|
||||
ResultTableRows = new ObservableCollection<Dictionary<string, object>>(
|
||||
_resultTables[0].Rows ?? Array.Empty<Dictionary<string, object>>());
|
||||
}
|
||||
else
|
||||
{
|
||||
ResultTableColumns = Array.Empty<InspectionResultTableColumn>();
|
||||
ResultTableRows = new ObservableCollection<Dictionary<string, object>>();
|
||||
}
|
||||
|
||||
// 初始化命令
|
||||
// Initialize commands
|
||||
ToggleImageCommand = new DelegateCommand(ExecuteToggleImage, CanExecuteToggleImage);
|
||||
ViewSnapshotCommand = new DelegateCommand(ExecuteViewSnapshot, CanExecuteViewSnapshot);
|
||||
|
||||
// 异步加载图像
|
||||
// Load images asynchronously
|
||||
_ = LoadImagesAsync();
|
||||
}
|
||||
|
||||
// ── 属性 | Properties ──────────────────────────────────────────
|
||||
|
||||
/// <summary>节点序号 | Node index</summary>
|
||||
public int NodeIndex => _nodeResult.NodeIndex;
|
||||
|
||||
/// <summary>节点名称 | Node name</summary>
|
||||
public string NodeName => _nodeResult.NodeName;
|
||||
|
||||
/// <summary>Pipeline 名称 | Pipeline name</summary>
|
||||
public string PipelineName => _nodeResult.PipelineName;
|
||||
|
||||
/// <summary>节点判定 | Node pass status</summary>
|
||||
public bool NodePass => _nodeResult.NodePass;
|
||||
|
||||
/// <summary>节点状态 | Node status</summary>
|
||||
public InspectionNodeStatus Status => _nodeResult.Status;
|
||||
|
||||
/// <summary>耗时(毫秒)| Duration in milliseconds</summary>
|
||||
public long DurationMs => _nodeResult.DurationMs;
|
||||
|
||||
/// <summary>
|
||||
/// 判定标签文字("Pass" / "Fail")
|
||||
/// Pass label text ("Pass" / "Fail")
|
||||
/// </summary>
|
||||
public string PassLabel => NodePass ? "Pass" : "Fail";
|
||||
|
||||
// 注:PassLabelColor 曾在此定义(硬编码 #2E7D32/#C62828),但未被任何
|
||||
// XAML 绑定引用——检测节点卡片实际走的是 PassFailColorConverter
|
||||
// (绑定 NodePass),已改用 Nova 令牌。该死代码属性已删除,避免继续
|
||||
// 维护一套从未生效的重复配色。
|
||||
|
||||
/// <summary>
|
||||
/// 是否有资产缺失警告(Status == AssetMissing)
|
||||
/// Whether there is an asset missing warning (Status == AssetMissing)
|
||||
/// </summary>
|
||||
public bool HasAssetMissingWarning => Status == InspectionNodeStatus.AssetMissing;
|
||||
|
||||
/// <summary>
|
||||
/// Pipeline 版本标识(hash 前 8 位,空则 "--")
|
||||
/// Pipeline version identifier (first 8 chars of hash, or "--" if empty)
|
||||
/// </summary>
|
||||
public string PipelineVersionShort
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_nodeResult.PipelineVersionHash))
|
||||
{
|
||||
return "--";
|
||||
}
|
||||
|
||||
return _nodeResult.PipelineVersionHash.Length >= 8
|
||||
? _nodeResult.PipelineVersionHash.Substring(0, 8)
|
||||
: _nodeResult.PipelineVersionHash;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否有 Pipeline 快照
|
||||
/// Whether pipeline snapshot exists
|
||||
/// </summary>
|
||||
public bool HasPipelineSnapshot => _snapshot != null && !string.IsNullOrWhiteSpace(_snapshot.PipelineDefinitionJson);
|
||||
|
||||
/// <summary>
|
||||
/// 格式化的 Pipeline 定义 JSON(2 空格缩进)
|
||||
/// Formatted pipeline definition JSON (2-space indentation)
|
||||
/// </summary>
|
||||
public string PipelineDefinitionJson
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_snapshot == null || string.IsNullOrWhiteSpace(_snapshot.PipelineDefinitionJson))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 尝试解析并格式化 JSON
|
||||
// Try to parse and format JSON
|
||||
var parsed = JToken.Parse(_snapshot.PipelineDefinitionJson);
|
||||
return parsed.ToString(Formatting.Indented);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 解析失败,返回原始字符串
|
||||
// Parsing failed, return original string
|
||||
return _snapshot.PipelineDefinitionJson;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 节点结果图(可为 null)
|
||||
/// Node result image (can be null)
|
||||
/// </summary>
|
||||
public BitmapSource ResultImage
|
||||
{
|
||||
get => _resultImage;
|
||||
private set => SetProperty(ref _resultImage, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 节点输入图(可为 null)
|
||||
/// Node input image (can be null)
|
||||
/// </summary>
|
||||
public BitmapSource InputImage
|
||||
{
|
||||
get => _inputImage;
|
||||
private set => SetProperty(ref _inputImage, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前显示的图像(默认为结果图)
|
||||
/// Currently displayed image (defaults to result image)
|
||||
/// </summary>
|
||||
public BitmapSource CurrentImage
|
||||
{
|
||||
get => _currentImage;
|
||||
private set => SetProperty(ref _currentImage, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否正在显示输入图
|
||||
/// Whether currently showing input image
|
||||
/// </summary>
|
||||
public bool IsShowingInputImage
|
||||
{
|
||||
get => _isShowingInputImage;
|
||||
private set => SetProperty(ref _isShowingInputImage, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否有输入图
|
||||
/// Whether input image exists
|
||||
/// </summary>
|
||||
public bool HasInputImage => InputImage != null;
|
||||
|
||||
/// <summary>
|
||||
/// 指标列表(按 DisplayOrder 升序,同序按 MetricName 升序)
|
||||
/// Metrics list (sorted by DisplayOrder ascending, then by MetricName ascending)
|
||||
/// </summary>
|
||||
public ObservableCollection<InspectionMetricRowViewModel> Metrics { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否有结构化明细表(如 BGA焊球明细、QFN引脚明细)
|
||||
/// Whether structured result tables exist (e.g. BGA ball details, QFN lead details)
|
||||
/// </summary>
|
||||
public bool HasResultTables => ResultTableRows != null && ResultTableRows.Count > 0;
|
||||
|
||||
/// <summary>
|
||||
/// 当前明细表的列定义(Key为列键,Name为显示名,含Unit)
|
||||
/// Column definitions for the current result table
|
||||
/// </summary>
|
||||
public IReadOnlyList<InspectionResultTableColumn> ResultTableColumns { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前明细表的行数据(每个 Dictionary 代表一行,Key 为列键)
|
||||
/// Row data for the current result table (each Dictionary is a row, Key is column key)
|
||||
/// </summary>
|
||||
public ObservableCollection<Dictionary<string, object>> ResultTableRows { get; }
|
||||
|
||||
// ── 命令 | Commands ────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 切换图像命令(在输入图和结果图之间切换)
|
||||
/// Toggle image command (switch between input and result images)
|
||||
/// </summary>
|
||||
public DelegateCommand ToggleImageCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 查看快照命令(打开 SnapshotViewerWindow)
|
||||
/// View snapshot command (open SnapshotViewerWindow)
|
||||
/// </summary>
|
||||
public DelegateCommand ViewSnapshotCommand { get; }
|
||||
|
||||
// ── 私有方法 | Private Methods ─────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 异步加载图像
|
||||
/// Load images asynchronously
|
||||
/// </summary>
|
||||
private async Task LoadImagesAsync()
|
||||
{
|
||||
// 加载结果图
|
||||
// Load result image
|
||||
if (_resultImageAsset != null && !string.IsNullOrWhiteSpace(_resultImageAsset.RelativePath))
|
||||
{
|
||||
var resultImagePath = Path.Combine(_imageBaseDirectory, _resultImageAsset.RelativePath);
|
||||
ResultImage = await ImageLoader.LoadBitmapSafeAsync(resultImagePath);
|
||||
}
|
||||
|
||||
// 加载输入图
|
||||
// Load input image
|
||||
if (_inputImageAsset != null && !string.IsNullOrWhiteSpace(_inputImageAsset.RelativePath))
|
||||
{
|
||||
var inputImagePath = Path.Combine(_imageBaseDirectory, _inputImageAsset.RelativePath);
|
||||
InputImage = await ImageLoader.LoadBitmapSafeAsync(inputImagePath);
|
||||
}
|
||||
|
||||
// 设置当前图像为结果图
|
||||
// Set current image to result image
|
||||
CurrentImage = ResultImage;
|
||||
IsShowingInputImage = false;
|
||||
|
||||
// 通知命令状态可能已更改
|
||||
// Notify that command state may have changed
|
||||
ToggleImageCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行切换图像命令
|
||||
/// Execute toggle image command
|
||||
/// </summary>
|
||||
private void ExecuteToggleImage()
|
||||
{
|
||||
if (IsShowingInputImage)
|
||||
{
|
||||
// 切换到结果图
|
||||
// Switch to result image
|
||||
CurrentImage = ResultImage;
|
||||
IsShowingInputImage = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 切换到输入图
|
||||
// Switch to input image
|
||||
CurrentImage = InputImage;
|
||||
IsShowingInputImage = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否可以执行切换图像命令
|
||||
/// Determine whether toggle image command can be executed
|
||||
/// </summary>
|
||||
private bool CanExecuteToggleImage()
|
||||
{
|
||||
return InputImage != null && ResultImage != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行查看快照命令
|
||||
/// Execute view snapshot command
|
||||
/// </summary>
|
||||
private void ExecuteViewSnapshot()
|
||||
{
|
||||
if (!HasPipelineSnapshot)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建并显示快照查看器窗口
|
||||
// Create and show snapshot viewer window
|
||||
// 注意:这里需要在 Views/Cnc 中实现 SnapshotViewerWindow
|
||||
// Note: SnapshotViewerWindow needs to be implemented in Views/Cnc
|
||||
try
|
||||
{
|
||||
var window = new Views.Cnc.SnapshotViewerWindow(PipelineDefinitionJson, PipelineName);
|
||||
window.Show();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 如果窗口尚未实现,静默失败
|
||||
// Silently fail if window is not yet implemented
|
||||
System.Diagnostics.Debug.WriteLine($"Failed to open snapshot viewer: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否可以执行查看快照命令
|
||||
/// Determine whether view snapshot command can be executed
|
||||
/// </summary>
|
||||
private bool CanExecuteViewSnapshot()
|
||||
{
|
||||
return HasPipelineSnapshot;
|
||||
}
|
||||
}
|
||||
: imageBaseDirectory;
|
||||
_snapshot = snapshot;
|
||||
|
||||
// 查找资产记录
|
||||
// Find asset records
|
||||
var assetList = assets?.ToList() ?? new List<InspectionAssetRecord>();
|
||||
_resultImageAsset = assetList.FirstOrDefault(a => a.NodeId == nodeResult.NodeId && a.AssetType == InspectionAssetType.NodeResultImage);
|
||||
_inputImageAsset = assetList.FirstOrDefault(a => a.NodeId == nodeResult.NodeId && a.AssetType == InspectionAssetType.NodeInputImage);
|
||||
|
||||
// 构建指标列表(按 DisplayOrder 升序,同序按 MetricName 升序)
|
||||
// Build metrics list (sorted by DisplayOrder ascending, then by MetricName ascending)
|
||||
var metricList = metrics?
|
||||
.Where(m => m.NodeId == nodeResult.NodeId)
|
||||
.OrderBy(m => m.DisplayOrder)
|
||||
.ThenBy(m => m.MetricName)
|
||||
.Select(m => new InspectionMetricRowViewModel(m))
|
||||
.ToList() ?? new List<InspectionMetricRowViewModel>();
|
||||
|
||||
Metrics = new ObservableCollection<InspectionMetricRowViewModel>(metricList);
|
||||
|
||||
// 筛选属于本节点的结构化明细表,并按 DisplayOrder/TableName 排序
|
||||
// Filter structured result tables that belong to this node, sorted by DisplayOrder/TableName
|
||||
_resultTables = resultTables?
|
||||
.Where(t => t.NodeId == nodeResult.NodeId)
|
||||
.OrderBy(t => t.DisplayOrder)
|
||||
.ThenBy(t => t.TableName)
|
||||
.ToList()
|
||||
as IReadOnlyList<InspectionResultTable>
|
||||
?? Array.Empty<InspectionResultTable>();
|
||||
|
||||
// 为 UI 绑定构建列信息,按 DisplayOrder 取第一个有数据的表(UI 一次只显示一个明细表)
|
||||
// Build column info for UI binding from the first table with data (UI shows one detail table at a time)
|
||||
if (_resultTables.Count > 0 && _resultTables[0].Columns?.Count > 0)
|
||||
{
|
||||
ResultTableColumns = _resultTables[0].Columns;
|
||||
ResultTableRows = new ObservableCollection<Dictionary<string, object>>(
|
||||
_resultTables[0].Rows ?? Array.Empty<Dictionary<string, object>>());
|
||||
}
|
||||
else
|
||||
{
|
||||
ResultTableColumns = Array.Empty<InspectionResultTableColumn>();
|
||||
ResultTableRows = new ObservableCollection<Dictionary<string, object>>();
|
||||
}
|
||||
|
||||
// 初始化命令
|
||||
// Initialize commands
|
||||
ToggleImageCommand = new DelegateCommand(ExecuteToggleImage, CanExecuteToggleImage);
|
||||
ViewSnapshotCommand = new DelegateCommand(ExecuteViewSnapshot, CanExecuteViewSnapshot);
|
||||
|
||||
// 异步加载图像
|
||||
// Load images asynchronously
|
||||
_ = LoadImagesAsync();
|
||||
}
|
||||
|
||||
// ── 属性 | Properties ──────────────────────────────────────────
|
||||
|
||||
/// <summary>节点序号 | Node index</summary>
|
||||
public int NodeIndex => _nodeResult.NodeIndex;
|
||||
|
||||
/// <summary>节点名称 | Node name</summary>
|
||||
public string NodeName => _nodeResult.NodeName;
|
||||
|
||||
/// <summary>Pipeline 名称 | Pipeline name</summary>
|
||||
public string PipelineName => _nodeResult.PipelineName;
|
||||
|
||||
/// <summary>节点判定 | Node pass status</summary>
|
||||
public bool NodePass => _nodeResult.NodePass;
|
||||
|
||||
/// <summary>节点状态 | Node status</summary>
|
||||
public InspectionNodeStatus Status => _nodeResult.Status;
|
||||
|
||||
/// <summary>耗时(毫秒)| Duration in milliseconds</summary>
|
||||
public long DurationMs => _nodeResult.DurationMs;
|
||||
|
||||
/// <summary>
|
||||
/// 判定标签文字("Pass" / "Fail")
|
||||
/// Pass label text ("Pass" / "Fail")
|
||||
/// </summary>
|
||||
public string PassLabel => NodePass ? "Pass" : "Fail";
|
||||
|
||||
// 注:PassLabelColor 曾在此定义(硬编码 #2E7D32/#C62828),但未被任何
|
||||
// XAML 绑定引用——检测节点卡片实际走的是 PassFailColorConverter
|
||||
// (绑定 NodePass),已改用 Nova 令牌。该死代码属性已删除,避免继续
|
||||
// 维护一套从未生效的重复配色。
|
||||
|
||||
/// <summary>
|
||||
/// 是否有资产缺失警告(Status == AssetMissing)
|
||||
/// Whether there is an asset missing warning (Status == AssetMissing)
|
||||
/// </summary>
|
||||
public bool HasAssetMissingWarning => Status == InspectionNodeStatus.AssetMissing;
|
||||
|
||||
/// <summary>
|
||||
/// Pipeline 版本标识(hash 前 8 位,空则 "--")
|
||||
/// Pipeline version identifier (first 8 chars of hash, or "--" if empty)
|
||||
/// </summary>
|
||||
public string PipelineVersionShort
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_nodeResult.PipelineVersionHash))
|
||||
{
|
||||
return "--";
|
||||
}
|
||||
|
||||
return _nodeResult.PipelineVersionHash.Length >= 8
|
||||
? _nodeResult.PipelineVersionHash.Substring(0, 8)
|
||||
: _nodeResult.PipelineVersionHash;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否有 Pipeline 快照
|
||||
/// Whether pipeline snapshot exists
|
||||
/// </summary>
|
||||
public bool HasPipelineSnapshot => _snapshot != null && !string.IsNullOrWhiteSpace(_snapshot.PipelineDefinitionJson);
|
||||
|
||||
/// <summary>
|
||||
/// 格式化的 Pipeline 定义 JSON(2 空格缩进)
|
||||
/// Formatted pipeline definition JSON (2-space indentation)
|
||||
/// </summary>
|
||||
public string PipelineDefinitionJson
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_snapshot == null || string.IsNullOrWhiteSpace(_snapshot.PipelineDefinitionJson))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 尝试解析并格式化 JSON
|
||||
// Try to parse and format JSON
|
||||
var parsed = JToken.Parse(_snapshot.PipelineDefinitionJson);
|
||||
return parsed.ToString(Formatting.Indented);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 解析失败,返回原始字符串
|
||||
// Parsing failed, return original string
|
||||
return _snapshot.PipelineDefinitionJson;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 节点结果图(可为 null)
|
||||
/// Node result image (can be null)
|
||||
/// </summary>
|
||||
public BitmapSource ResultImage
|
||||
{
|
||||
get => _resultImage;
|
||||
private set => SetProperty(ref _resultImage, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 节点输入图(可为 null)
|
||||
/// Node input image (can be null)
|
||||
/// </summary>
|
||||
public BitmapSource InputImage
|
||||
{
|
||||
get => _inputImage;
|
||||
private set => SetProperty(ref _inputImage, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前显示的图像(默认为结果图)
|
||||
/// Currently displayed image (defaults to result image)
|
||||
/// </summary>
|
||||
public BitmapSource CurrentImage
|
||||
{
|
||||
get => _currentImage;
|
||||
private set => SetProperty(ref _currentImage, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否正在显示输入图
|
||||
/// Whether currently showing input image
|
||||
/// </summary>
|
||||
public bool IsShowingInputImage
|
||||
{
|
||||
get => _isShowingInputImage;
|
||||
private set => SetProperty(ref _isShowingInputImage, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否有输入图
|
||||
/// Whether input image exists
|
||||
/// </summary>
|
||||
public bool HasInputImage => InputImage != null;
|
||||
|
||||
/// <summary>
|
||||
/// 指标列表(按 DisplayOrder 升序,同序按 MetricName 升序)
|
||||
/// Metrics list (sorted by DisplayOrder ascending, then by MetricName ascending)
|
||||
/// </summary>
|
||||
public ObservableCollection<InspectionMetricRowViewModel> Metrics { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否有结构化明细表(如 BGA焊球明细、QFN引脚明细)
|
||||
/// Whether structured result tables exist (e.g. BGA ball details, QFN lead details)
|
||||
/// </summary>
|
||||
public bool HasResultTables => ResultTableRows != null && ResultTableRows.Count > 0;
|
||||
|
||||
/// <summary>
|
||||
/// 当前明细表的列定义(Key为列键,Name为显示名,含Unit)
|
||||
/// Column definitions for the current result table
|
||||
/// </summary>
|
||||
public IReadOnlyList<InspectionResultTableColumn> ResultTableColumns { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前明细表的行数据(每个 Dictionary 代表一行,Key 为列键)
|
||||
/// Row data for the current result table (each Dictionary is a row, Key is column key)
|
||||
/// </summary>
|
||||
public ObservableCollection<Dictionary<string, object>> ResultTableRows { get; }
|
||||
|
||||
// ── 命令 | Commands ────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 切换图像命令(在输入图和结果图之间切换)
|
||||
/// Toggle image command (switch between input and result images)
|
||||
/// </summary>
|
||||
public DelegateCommand ToggleImageCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 查看快照命令(打开 SnapshotViewerWindow)
|
||||
/// View snapshot command (open SnapshotViewerWindow)
|
||||
/// </summary>
|
||||
public DelegateCommand ViewSnapshotCommand { get; }
|
||||
|
||||
// ── 私有方法 | Private Methods ─────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 异步加载图像
|
||||
/// Load images asynchronously
|
||||
/// </summary>
|
||||
private async Task LoadImagesAsync()
|
||||
{
|
||||
// 加载结果图
|
||||
// Load result image
|
||||
if (_resultImageAsset != null && !string.IsNullOrWhiteSpace(_resultImageAsset.RelativePath))
|
||||
{
|
||||
var resultImagePath = Path.Combine(_imageBaseDirectory, _resultImageAsset.RelativePath);
|
||||
ResultImage = await ImageLoader.LoadBitmapSafeAsync(resultImagePath);
|
||||
}
|
||||
|
||||
// 加载输入图
|
||||
// Load input image
|
||||
if (_inputImageAsset != null && !string.IsNullOrWhiteSpace(_inputImageAsset.RelativePath))
|
||||
{
|
||||
var inputImagePath = Path.Combine(_imageBaseDirectory, _inputImageAsset.RelativePath);
|
||||
InputImage = await ImageLoader.LoadBitmapSafeAsync(inputImagePath);
|
||||
}
|
||||
|
||||
// 设置当前图像为结果图
|
||||
// Set current image to result image
|
||||
CurrentImage = ResultImage;
|
||||
IsShowingInputImage = false;
|
||||
|
||||
// 通知命令状态可能已更改
|
||||
// Notify that command state may have changed
|
||||
ToggleImageCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行切换图像命令
|
||||
/// Execute toggle image command
|
||||
/// </summary>
|
||||
private void ExecuteToggleImage()
|
||||
{
|
||||
if (IsShowingInputImage)
|
||||
{
|
||||
// 切换到结果图
|
||||
// Switch to result image
|
||||
CurrentImage = ResultImage;
|
||||
IsShowingInputImage = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 切换到输入图
|
||||
// Switch to input image
|
||||
CurrentImage = InputImage;
|
||||
IsShowingInputImage = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否可以执行切换图像命令
|
||||
/// Determine whether toggle image command can be executed
|
||||
/// </summary>
|
||||
private bool CanExecuteToggleImage()
|
||||
{
|
||||
return InputImage != null && ResultImage != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行查看快照命令
|
||||
/// Execute view snapshot command
|
||||
/// </summary>
|
||||
private void ExecuteViewSnapshot()
|
||||
{
|
||||
if (!HasPipelineSnapshot)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建并显示快照查看器窗口
|
||||
// Create and show snapshot viewer window
|
||||
// 注意:这里需要在 Views/Cnc 中实现 SnapshotViewerWindow
|
||||
// Note: SnapshotViewerWindow needs to be implemented in Views/Cnc
|
||||
try
|
||||
{
|
||||
var window = new Views.Cnc.SnapshotViewerWindow(PipelineDefinitionJson, PipelineName);
|
||||
window.Show();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 如果窗口尚未实现,静默失败
|
||||
// Silently fail if window is not yet implemented
|
||||
System.Diagnostics.Debug.WriteLine($"Failed to open snapshot viewer: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否可以执行查看快照命令
|
||||
/// Determine whether view snapshot command can be executed
|
||||
/// </summary>
|
||||
private bool CanExecuteViewSnapshot()
|
||||
{
|
||||
return HasPipelineSnapshot;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1062
-1062
File diff suppressed because it is too large
Load Diff
+123
-123
@@ -1,124 +1,124 @@
|
||||
// ── 检测结果审查 ViewModel ──
|
||||
// 窗口右半部分的审查面板,展示已选节点在选中运行中的各 Tile 判定结果。
|
||||
// 按检测子节点分组,每个子节点显示其对应的指标列表。
|
||||
using Prism.Mvvm;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using XplorePlane.Services.Cnc;
|
||||
|
||||
namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
/// <summary>
|
||||
/// CNC 运行时结果复核窗的 ViewModel。
|
||||
/// 提供只读展示数据(明细表 + 汇总 + 整体判定),
|
||||
/// 操作员改判状态与备注由窗体交互收集(见 InspectionReviewWindow)。
|
||||
/// </summary>
|
||||
public sealed class InspectionReviewViewModel : BindableBase
|
||||
{
|
||||
public InspectionReviewViewModel(InspectionReviewRequest request)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
NodeName = request.NodeName ?? string.Empty;
|
||||
OverallPass = request.OverallPass;
|
||||
Tables = request.Tables ?? new List<InspectionResultTable>();
|
||||
Metrics = request.Metrics ?? new List<InspectionMetricResult>();
|
||||
|
||||
BuildSummary();
|
||||
}
|
||||
|
||||
/// <summary>检测模块名称。</summary>
|
||||
public string NodeName { get; }
|
||||
|
||||
/// <summary>算法整体判定是否合格。</summary>
|
||||
public bool OverallPass { get; }
|
||||
|
||||
/// <summary>结构化明细表(供窗体动态建列展示)。</summary>
|
||||
public IReadOnlyList<InspectionResultTable> Tables { get; }
|
||||
|
||||
/// <summary>指标集合。</summary>
|
||||
public IReadOnlyList<InspectionMetricResult> Metrics { get; }
|
||||
|
||||
/// <summary>窗体标题。</summary>
|
||||
public string Title => string.IsNullOrWhiteSpace(NodeName) ? "检测结果复核" : $"检测结果复核 - {NodeName}";
|
||||
|
||||
// ── 汇总 ──
|
||||
|
||||
/// <summary>数量项标签(焊球数 / 引脚数 / 数量)。</summary>
|
||||
public string CountLabel { get; private set; } = "数量";
|
||||
|
||||
/// <summary>项目总数。</summary>
|
||||
public int CountValue { get; private set; }
|
||||
|
||||
/// <summary>不合格项数。</summary>
|
||||
public int FailValue { get; private set; }
|
||||
|
||||
/// <summary>缺失项数(如缺球)。</summary>
|
||||
public int MissingValue { get; private set; }
|
||||
|
||||
/// <summary>整体判定文本(PASS / FAILED)。</summary>
|
||||
public string ResultText => OverallPass ? "PASS" : "FAILED";
|
||||
|
||||
/// <summary>整体判定是否合格(供 XAML 着色)。</summary>
|
||||
public bool IsPass => OverallPass;
|
||||
|
||||
private void BuildSummary()
|
||||
{
|
||||
var primary = Tables.OrderByDescending(t => t.Rows?.Count ?? 0).FirstOrDefault();
|
||||
|
||||
// 数量标签与总数:优先取指标,回退到明细行数
|
||||
if (TryGetMetric("BgaCount", out var bgaCount))
|
||||
{
|
||||
CountLabel = "焊球数";
|
||||
CountValue = (int)Math.Round(bgaCount);
|
||||
}
|
||||
else if (TryGetMetric("QfnLeadCount", out var leadCount))
|
||||
{
|
||||
CountLabel = "引脚数";
|
||||
CountValue = (int)Math.Round(leadCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
CountLabel = "数量";
|
||||
CountValue = primary?.Rows?.Count ?? 0;
|
||||
}
|
||||
|
||||
// 不合格 / 缺失:从明细行的判定列统计
|
||||
int fail = 0, missing = 0;
|
||||
if (primary?.Rows != null)
|
||||
{
|
||||
foreach (var row in primary.Rows)
|
||||
{
|
||||
if (row == null || !row.TryGetValue("Classification", out var clsObj))
|
||||
continue;
|
||||
var cls = Convert.ToString(clsObj, CultureInfo.InvariantCulture)?.Trim().ToUpperInvariant() ?? string.Empty;
|
||||
if (cls == "MISS" || cls == "MISSING")
|
||||
missing++;
|
||||
else if (cls.StartsWith("FAIL", StringComparison.Ordinal))
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
// 若明细无判定列,回退用指标的不合格数
|
||||
if (fail == 0 && TryGetMetric("QfnLeadFailCount", out var failMetric))
|
||||
fail = (int)Math.Round(failMetric);
|
||||
|
||||
FailValue = fail;
|
||||
MissingValue = missing;
|
||||
}
|
||||
|
||||
private bool TryGetMetric(string key, out double value)
|
||||
{
|
||||
var metric = Metrics?.FirstOrDefault(m => string.Equals(m.MetricKey, key, StringComparison.Ordinal));
|
||||
if (metric != null)
|
||||
{
|
||||
value = metric.MetricValue;
|
||||
return true;
|
||||
}
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// ── 检测结果审查 ViewModel ──
|
||||
// 窗口右半部分的审查面板,展示已选节点在选中运行中的各 Tile 判定结果。
|
||||
// 按检测子节点分组,每个子节点显示其对应的指标列表。
|
||||
using Prism.Mvvm;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using XplorePlane.Services.Cnc;
|
||||
|
||||
namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
/// <summary>
|
||||
/// CNC 运行时结果复核窗的 ViewModel。
|
||||
/// 提供只读展示数据(明细表 + 汇总 + 整体判定),
|
||||
/// 操作员改判状态与备注由窗体交互收集(见 InspectionReviewWindow)。
|
||||
/// </summary>
|
||||
public sealed class InspectionReviewViewModel : BindableBase
|
||||
{
|
||||
public InspectionReviewViewModel(InspectionReviewRequest request)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
NodeName = request.NodeName ?? string.Empty;
|
||||
OverallPass = request.OverallPass;
|
||||
Tables = request.Tables ?? new List<InspectionResultTable>();
|
||||
Metrics = request.Metrics ?? new List<InspectionMetricResult>();
|
||||
|
||||
BuildSummary();
|
||||
}
|
||||
|
||||
/// <summary>检测模块名称。</summary>
|
||||
public string NodeName { get; }
|
||||
|
||||
/// <summary>算法整体判定是否合格。</summary>
|
||||
public bool OverallPass { get; }
|
||||
|
||||
/// <summary>结构化明细表(供窗体动态建列展示)。</summary>
|
||||
public IReadOnlyList<InspectionResultTable> Tables { get; }
|
||||
|
||||
/// <summary>指标集合。</summary>
|
||||
public IReadOnlyList<InspectionMetricResult> Metrics { get; }
|
||||
|
||||
/// <summary>窗体标题。</summary>
|
||||
public string Title => string.IsNullOrWhiteSpace(NodeName) ? "检测结果复核" : $"检测结果复核 - {NodeName}";
|
||||
|
||||
// ── 汇总 ──
|
||||
|
||||
/// <summary>数量项标签(焊球数 / 引脚数 / 数量)。</summary>
|
||||
public string CountLabel { get; private set; } = "数量";
|
||||
|
||||
/// <summary>项目总数。</summary>
|
||||
public int CountValue { get; private set; }
|
||||
|
||||
/// <summary>不合格项数。</summary>
|
||||
public int FailValue { get; private set; }
|
||||
|
||||
/// <summary>缺失项数(如缺球)。</summary>
|
||||
public int MissingValue { get; private set; }
|
||||
|
||||
/// <summary>整体判定文本(PASS / FAILED)。</summary>
|
||||
public string ResultText => OverallPass ? "PASS" : "FAILED";
|
||||
|
||||
/// <summary>整体判定是否合格(供 XAML 着色)。</summary>
|
||||
public bool IsPass => OverallPass;
|
||||
|
||||
private void BuildSummary()
|
||||
{
|
||||
var primary = Tables.OrderByDescending(t => t.Rows?.Count ?? 0).FirstOrDefault();
|
||||
|
||||
// 数量标签与总数:优先取指标,回退到明细行数
|
||||
if (TryGetMetric("BgaCount", out var bgaCount))
|
||||
{
|
||||
CountLabel = "焊球数";
|
||||
CountValue = (int)Math.Round(bgaCount);
|
||||
}
|
||||
else if (TryGetMetric("QfnLeadCount", out var leadCount))
|
||||
{
|
||||
CountLabel = "引脚数";
|
||||
CountValue = (int)Math.Round(leadCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
CountLabel = "数量";
|
||||
CountValue = primary?.Rows?.Count ?? 0;
|
||||
}
|
||||
|
||||
// 不合格 / 缺失:从明细行的判定列统计
|
||||
int fail = 0, missing = 0;
|
||||
if (primary?.Rows != null)
|
||||
{
|
||||
foreach (var row in primary.Rows)
|
||||
{
|
||||
if (row == null || !row.TryGetValue("Classification", out var clsObj))
|
||||
continue;
|
||||
var cls = Convert.ToString(clsObj, CultureInfo.InvariantCulture)?.Trim().ToUpperInvariant() ?? string.Empty;
|
||||
if (cls == "MISS" || cls == "MISSING")
|
||||
missing++;
|
||||
else if (cls.StartsWith("FAIL", StringComparison.Ordinal))
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
// 若明细无判定列,回退用指标的不合格数
|
||||
if (fail == 0 && TryGetMetric("QfnLeadFailCount", out var failMetric))
|
||||
fail = (int)Math.Round(failMetric);
|
||||
|
||||
FailValue = fail;
|
||||
MissingValue = missing;
|
||||
}
|
||||
|
||||
private bool TryGetMetric(string key, out double value)
|
||||
{
|
||||
var metric = Metrics?.FirstOrDefault(m => string.Equals(m.MetricKey, key, StringComparison.Ordinal));
|
||||
if (metric != null)
|
||||
{
|
||||
value = metric.MetricValue;
|
||||
return true;
|
||||
}
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+122
-122
@@ -1,123 +1,123 @@
|
||||
// ── 检测运行事件行 ViewModel ──
|
||||
// 运行记录中每条事件/日志的封装,绑定到事件列表表格的行显示。
|
||||
// 包含时间戳、事件类型图标、事件描述等信息。
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Prism.Mvvm;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
/// <summary>
|
||||
/// 运行事件行 ViewModel,包装 InspectionRunEvent 供事件时间线绑定
|
||||
/// Run event row ViewModel that wraps InspectionRunEvent for event timeline binding
|
||||
/// </summary>
|
||||
public class InspectionRunEventRowViewModel : BindableBase
|
||||
{
|
||||
private readonly InspectionRunEvent _event;
|
||||
private readonly Dictionary<Guid, string> _nodeIdToNameMap;
|
||||
private bool _isExpanded;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
/// <param name="eventRecord">运行事件记录 | Run event record</param>
|
||||
/// <param name="nodeIdToNameMap">节点 ID 到节点名称的映射字典 | Dictionary mapping node IDs to node names</param>
|
||||
public InspectionRunEventRowViewModel(InspectionRunEvent eventRecord, Dictionary<Guid, string> nodeIdToNameMap)
|
||||
{
|
||||
_event = eventRecord ?? throw new ArgumentNullException(nameof(eventRecord));
|
||||
_nodeIdToNameMap = nodeIdToNameMap ?? new Dictionary<Guid, string>();
|
||||
}
|
||||
|
||||
// ── 属性 | Properties ──────────────────────────────────────────
|
||||
|
||||
/// <summary>事件类型 | Event type</summary>
|
||||
public InspectionRunEventType EventType => _event.EventType;
|
||||
|
||||
/// <summary>
|
||||
/// 事件时间(本地时区,格式 HH:mm:ss.fff)
|
||||
/// Event time in local timezone (format: HH:mm:ss.fff)
|
||||
/// </summary>
|
||||
public string EventTimeLocal => _event.EventTime.ToLocalTime().ToString("HH:mm:ss.fff");
|
||||
|
||||
/// <summary>
|
||||
/// 关联节点显示名称
|
||||
/// 规则:如果 NodeId 不为空,优先显示节点名称;若无法解析则显示 NodeId 前 8 位;若 NodeId 为空则返回空字符串
|
||||
/// Associated node display name
|
||||
/// Rules: If NodeId is not null, display node name if available; otherwise display first 8 chars of NodeId; if NodeId is null return empty string
|
||||
/// </summary>
|
||||
public string NodeDisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_event.NodeId.HasValue)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// 尝试从映射字典中获取节点名称
|
||||
// Try to get node name from the mapping dictionary
|
||||
if (_nodeIdToNameMap.TryGetValue(_event.NodeId.Value, out string nodeName) && !string.IsNullOrWhiteSpace(nodeName))
|
||||
{
|
||||
return nodeName;
|
||||
}
|
||||
|
||||
// 无法解析节点名称,返回 NodeId 前 8 位
|
||||
// Cannot resolve node name, return first 8 chars of NodeId
|
||||
return _event.NodeId.Value.ToString().Substring(0, 8);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格式化的 Payload JSON(2 空格缩进)
|
||||
/// Formatted payload JSON (2-space indentation)
|
||||
/// </summary>
|
||||
public string PayloadFormatted
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_event.PayloadJson))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 尝试解析并格式化 JSON
|
||||
// Try to parse and format JSON
|
||||
var parsed = JToken.Parse(_event.PayloadJson);
|
||||
return parsed.ToString(Formatting.Indented);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 解析失败,返回原始字符串
|
||||
// Parsing failed, return original string
|
||||
return _event.PayloadJson;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否有 Payload 数据
|
||||
/// Whether payload data exists
|
||||
/// </summary>
|
||||
public bool HasPayload => !string.IsNullOrWhiteSpace(_event.PayloadJson);
|
||||
|
||||
// 注:IconColor 曾在此定义(硬编码 #1565C0/#2E7D32/#C62828/#E65100/
|
||||
// #9E9E9E),但未被任何 XAML 绑定引用——事件时间线实际走的是
|
||||
// EventTypeIconConverter(绑定 EventType),已改用 Nova 令牌。
|
||||
// 该死代码属性已删除,避免继续维护一套从未生效的重复配色。
|
||||
|
||||
/// <summary>
|
||||
/// 是否展开显示 Payload(用于 UI 绑定)
|
||||
/// Whether to expand and display payload (for UI binding)
|
||||
/// </summary>
|
||||
public bool IsExpanded
|
||||
{
|
||||
get => _isExpanded;
|
||||
set => SetProperty(ref _isExpanded, value);
|
||||
}
|
||||
}
|
||||
// ── 检测运行事件行 ViewModel ──
|
||||
// 运行记录中每条事件/日志的封装,绑定到事件列表表格的行显示。
|
||||
// 包含时间戳、事件类型图标、事件描述等信息。
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Prism.Mvvm;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
/// <summary>
|
||||
/// 运行事件行 ViewModel,包装 InspectionRunEvent 供事件时间线绑定
|
||||
/// Run event row ViewModel that wraps InspectionRunEvent for event timeline binding
|
||||
/// </summary>
|
||||
public class InspectionRunEventRowViewModel : BindableBase
|
||||
{
|
||||
private readonly InspectionRunEvent _event;
|
||||
private readonly Dictionary<Guid, string> _nodeIdToNameMap;
|
||||
private bool _isExpanded;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
/// <param name="eventRecord">运行事件记录 | Run event record</param>
|
||||
/// <param name="nodeIdToNameMap">节点 ID 到节点名称的映射字典 | Dictionary mapping node IDs to node names</param>
|
||||
public InspectionRunEventRowViewModel(InspectionRunEvent eventRecord, Dictionary<Guid, string> nodeIdToNameMap)
|
||||
{
|
||||
_event = eventRecord ?? throw new ArgumentNullException(nameof(eventRecord));
|
||||
_nodeIdToNameMap = nodeIdToNameMap ?? new Dictionary<Guid, string>();
|
||||
}
|
||||
|
||||
// ── 属性 | Properties ──────────────────────────────────────────
|
||||
|
||||
/// <summary>事件类型 | Event type</summary>
|
||||
public InspectionRunEventType EventType => _event.EventType;
|
||||
|
||||
/// <summary>
|
||||
/// 事件时间(本地时区,格式 HH:mm:ss.fff)
|
||||
/// Event time in local timezone (format: HH:mm:ss.fff)
|
||||
/// </summary>
|
||||
public string EventTimeLocal => _event.EventTime.ToLocalTime().ToString("HH:mm:ss.fff");
|
||||
|
||||
/// <summary>
|
||||
/// 关联节点显示名称
|
||||
/// 规则:如果 NodeId 不为空,优先显示节点名称;若无法解析则显示 NodeId 前 8 位;若 NodeId 为空则返回空字符串
|
||||
/// Associated node display name
|
||||
/// Rules: If NodeId is not null, display node name if available; otherwise display first 8 chars of NodeId; if NodeId is null return empty string
|
||||
/// </summary>
|
||||
public string NodeDisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_event.NodeId.HasValue)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// 尝试从映射字典中获取节点名称
|
||||
// Try to get node name from the mapping dictionary
|
||||
if (_nodeIdToNameMap.TryGetValue(_event.NodeId.Value, out string nodeName) && !string.IsNullOrWhiteSpace(nodeName))
|
||||
{
|
||||
return nodeName;
|
||||
}
|
||||
|
||||
// 无法解析节点名称,返回 NodeId 前 8 位
|
||||
// Cannot resolve node name, return first 8 chars of NodeId
|
||||
return _event.NodeId.Value.ToString().Substring(0, 8);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格式化的 Payload JSON(2 空格缩进)
|
||||
/// Formatted payload JSON (2-space indentation)
|
||||
/// </summary>
|
||||
public string PayloadFormatted
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_event.PayloadJson))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 尝试解析并格式化 JSON
|
||||
// Try to parse and format JSON
|
||||
var parsed = JToken.Parse(_event.PayloadJson);
|
||||
return parsed.ToString(Formatting.Indented);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 解析失败,返回原始字符串
|
||||
// Parsing failed, return original string
|
||||
return _event.PayloadJson;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否有 Payload 数据
|
||||
/// Whether payload data exists
|
||||
/// </summary>
|
||||
public bool HasPayload => !string.IsNullOrWhiteSpace(_event.PayloadJson);
|
||||
|
||||
// 注:IconColor 曾在此定义(硬编码 #1565C0/#2E7D32/#C62828/#E65100/
|
||||
// #9E9E9E),但未被任何 XAML 绑定引用——事件时间线实际走的是
|
||||
// EventTypeIconConverter(绑定 EventType),已改用 Nova 令牌。
|
||||
// 该死代码属性已删除,避免继续维护一套从未生效的重复配色。
|
||||
|
||||
/// <summary>
|
||||
/// 是否展开显示 Payload(用于 UI 绑定)
|
||||
/// Whether to expand and display payload (for UI binding)
|
||||
/// </summary>
|
||||
public bool IsExpanded
|
||||
{
|
||||
get => _isExpanded;
|
||||
set => SetProperty(ref _isExpanded, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
+64
-64
@@ -1,65 +1,65 @@
|
||||
// ── 检测运行行 ViewModel ──
|
||||
// 运行列表中的单条执行记录封装。绑定到运行历史表格的行显示,
|
||||
// 包含运行时间、判定结果(OK/NG)、操作等列。
|
||||
using Prism.Mvvm;
|
||||
using System;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
/// <summary>
|
||||
/// 检测实例行 ViewModel,包装 InspectionRunRecord 供 RunList 行绑定
|
||||
/// Inspection run row ViewModel that wraps InspectionRunRecord for RunList row binding
|
||||
/// </summary>
|
||||
public class InspectionRunRowViewModel : BindableBase
|
||||
{
|
||||
private readonly InspectionRunRecord _record;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
/// <param name="record">检测实例记录 | Inspection run record</param>
|
||||
public InspectionRunRowViewModel(InspectionRunRecord record)
|
||||
{
|
||||
_record = record ?? throw new ArgumentNullException(nameof(record));
|
||||
}
|
||||
|
||||
// ── 属性 | Properties ──────────────────────────────────────────
|
||||
|
||||
/// <summary>运行 ID | Run ID</summary>
|
||||
public Guid RunId => _record.RunId;
|
||||
|
||||
/// <summary>程序名 | Program name</summary>
|
||||
public string ProgramName => _record.ProgramName;
|
||||
|
||||
/// <summary>工件号 | Workpiece ID</summary>
|
||||
public string WorkpieceId => _record.WorkpieceId;
|
||||
|
||||
/// <summary>序列号 | Serial number</summary>
|
||||
public string SerialNumber => _record.SerialNumber;
|
||||
|
||||
/// <summary>
|
||||
/// 开始时间(本地时区,格式 yyyy-MM-dd HH:mm:ss)
|
||||
/// Start time in local timezone (format: yyyy-MM-dd HH:mm:ss)
|
||||
/// </summary>
|
||||
public string StartedAtLocal => _record.StartedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
/// <summary>整体判定 | Overall pass</summary>
|
||||
public bool OverallPass => _record.OverallPass;
|
||||
|
||||
/// <summary>运行状态 | Run status</summary>
|
||||
public InspectionRunStatus Status => _record.Status;
|
||||
|
||||
/// <summary>
|
||||
/// 判定标签文字("Pass" / "Fail")
|
||||
/// Pass label text ("Pass" / "Fail")
|
||||
/// </summary>
|
||||
public string PassLabel => OverallPass ? "Pass" : "Fail";
|
||||
|
||||
// 注:PassLabelColor / StatusWarningColor 曾在此定义(硬编码 #2E7D32/
|
||||
// #C62828/#E65100),但未被任何 XAML 绑定引用——RunList 实际走的是
|
||||
// PassFailColorConverter(绑定 OverallPass)与 RunStatusColorConverter
|
||||
// (绑定 Status),两者均已改用 Nova 令牌。这两个死代码属性已删除,
|
||||
// 避免继续维护一套从未生效的重复配色。
|
||||
}
|
||||
// ── 检测运行行 ViewModel ──
|
||||
// 运行列表中的单条执行记录封装。绑定到运行历史表格的行显示,
|
||||
// 包含运行时间、判定结果(OK/NG)、操作等列。
|
||||
using Prism.Mvvm;
|
||||
using System;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
/// <summary>
|
||||
/// 检测实例行 ViewModel,包装 InspectionRunRecord 供 RunList 行绑定
|
||||
/// Inspection run row ViewModel that wraps InspectionRunRecord for RunList row binding
|
||||
/// </summary>
|
||||
public class InspectionRunRowViewModel : BindableBase
|
||||
{
|
||||
private readonly InspectionRunRecord _record;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
/// <param name="record">检测实例记录 | Inspection run record</param>
|
||||
public InspectionRunRowViewModel(InspectionRunRecord record)
|
||||
{
|
||||
_record = record ?? throw new ArgumentNullException(nameof(record));
|
||||
}
|
||||
|
||||
// ── 属性 | Properties ──────────────────────────────────────────
|
||||
|
||||
/// <summary>运行 ID | Run ID</summary>
|
||||
public Guid RunId => _record.RunId;
|
||||
|
||||
/// <summary>程序名 | Program name</summary>
|
||||
public string ProgramName => _record.ProgramName;
|
||||
|
||||
/// <summary>工件号 | Workpiece ID</summary>
|
||||
public string WorkpieceId => _record.WorkpieceId;
|
||||
|
||||
/// <summary>序列号 | Serial number</summary>
|
||||
public string SerialNumber => _record.SerialNumber;
|
||||
|
||||
/// <summary>
|
||||
/// 开始时间(本地时区,格式 yyyy-MM-dd HH:mm:ss)
|
||||
/// Start time in local timezone (format: yyyy-MM-dd HH:mm:ss)
|
||||
/// </summary>
|
||||
public string StartedAtLocal => _record.StartedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
/// <summary>整体判定 | Overall pass</summary>
|
||||
public bool OverallPass => _record.OverallPass;
|
||||
|
||||
/// <summary>运行状态 | Run status</summary>
|
||||
public InspectionRunStatus Status => _record.Status;
|
||||
|
||||
/// <summary>
|
||||
/// 判定标签文字("Pass" / "Fail")
|
||||
/// Pass label text ("Pass" / "Fail")
|
||||
/// </summary>
|
||||
public string PassLabel => OverallPass ? "Pass" : "Fail";
|
||||
|
||||
// 注:PassLabelColor / StatusWarningColor 曾在此定义(硬编码 #2E7D32/
|
||||
// #C62828/#E65100),但未被任何 XAML 绑定引用——RunList 实际走的是
|
||||
// PassFailColorConverter(绑定 OverallPass)与 RunStatusColorConverter
|
||||
// (绑定 Status),两者均已改用 Nova 令牌。这两个死代码属性已删除,
|
||||
// 避免继续维护一套从未生效的重复配色。
|
||||
}
|
||||
}
|
||||
+185
-185
@@ -1,186 +1,186 @@
|
||||
// ── 矩阵单元格 ViewModel ──
|
||||
// 将 MatrixCell 模型封装为可绑定 ViewModel,每个单元格对应一个检测位置。
|
||||
// 在矩阵编辑器中显示 Cell 名称/状态/坐标预览等。
|
||||
using Prism.Mvvm;
|
||||
|
||||
namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
/// <summary>
|
||||
/// 矩阵单元格 ViewModel,将 MatrixCell 模型封装为可绑定的 WPF ViewModel
|
||||
/// Matrix cell ViewModel that wraps a MatrixCell model into a bindable WPF ViewModel
|
||||
/// </summary>
|
||||
public class MatrixCellViewModel : BindableBase
|
||||
{
|
||||
private int _row;
|
||||
private int _column;
|
||||
private double _offsetX;
|
||||
private double _offsetY;
|
||||
private bool _isEnabled;
|
||||
private MatrixCellStatus _status;
|
||||
private string _errorMessage;
|
||||
private bool _isSelected;
|
||||
private int _position;
|
||||
private MatrixCellTeachState _teachState;
|
||||
private double _absoluteX;
|
||||
private double _absoluteY;
|
||||
private bool _hasCoordinate;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数,从 MatrixCell 模型初始化 ViewModel
|
||||
/// Constructor that initializes the ViewModel from a MatrixCell model
|
||||
/// </summary>
|
||||
public MatrixCellViewModel(MatrixCell model)
|
||||
{
|
||||
Model = model;
|
||||
_row = model.Row;
|
||||
_column = model.Column;
|
||||
_offsetX = model.OffsetX;
|
||||
_offsetY = model.OffsetY;
|
||||
_isEnabled = model.IsEnabled;
|
||||
_status = model.Status;
|
||||
_errorMessage = model.ErrorMessage;
|
||||
_teachState = MatrixCellTeachState.Auto;
|
||||
}
|
||||
|
||||
/// <summary>底层矩阵单元格模型(只读)| Underlying matrix cell model (read-only)</summary>
|
||||
public MatrixCell Model { get; }
|
||||
|
||||
/// <summary>单元格行索引 | Cell row index</summary>
|
||||
public int Row
|
||||
{
|
||||
get => _row;
|
||||
set => SetProperty(ref _row, value);
|
||||
}
|
||||
|
||||
/// <summary>单元格列索引 | Cell column index</summary>
|
||||
public int Column
|
||||
{
|
||||
get => _column;
|
||||
set => SetProperty(ref _column, value);
|
||||
}
|
||||
|
||||
/// <summary>计算后的 X 偏移量 | Calculated X offset</summary>
|
||||
public double OffsetX
|
||||
{
|
||||
get => _offsetX;
|
||||
set => SetProperty(ref _offsetX, value);
|
||||
}
|
||||
|
||||
/// <summary>计算后的 Y 偏移量 | Calculated Y offset</summary>
|
||||
public double OffsetY
|
||||
{
|
||||
get => _offsetY;
|
||||
set => SetProperty(ref _offsetY, value);
|
||||
}
|
||||
|
||||
/// <summary>单元格是否启用检测 | Whether the cell is enabled for inspection</summary>
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => _isEnabled;
|
||||
set => SetProperty(ref _isEnabled, value);
|
||||
}
|
||||
|
||||
/// <summary>单元格执行状态 | Cell execution status</summary>
|
||||
public MatrixCellStatus Status
|
||||
{
|
||||
get => _status;
|
||||
set => SetProperty(ref _status, value);
|
||||
}
|
||||
|
||||
/// <summary>错误信息 | Error message if any</summary>
|
||||
public string ErrorMessage
|
||||
{
|
||||
get => _errorMessage;
|
||||
set => SetProperty(ref _errorMessage, value);
|
||||
}
|
||||
|
||||
/// <summary>单元格是否被选中 | Whether the cell is currently selected in the UI</summary>
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set => SetProperty(ref _isSelected, value);
|
||||
}
|
||||
|
||||
/// <summary>单元格 1 基位置编号(行优先)| 1-based position number (row-major)</summary>
|
||||
public int Position
|
||||
{
|
||||
get => _position;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _position, value))
|
||||
{
|
||||
RaisePropertyChanged(nameof(PositionLabel));
|
||||
RaisePropertyChanged(nameof(PositionShort));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>位置显示标签,如 "位置1 / Position 1" | Position display label</summary>
|
||||
public string PositionLabel => $"位置{Position} / Position {Position}";
|
||||
|
||||
/// <summary>位置短标签,如 "位置1" | Short position label</summary>
|
||||
public string PositionShort => $"位置{Position}";
|
||||
|
||||
/// <summary>
|
||||
/// 示教来源状态:手动示教 / 当前待示教 / 自动推算
|
||||
/// Teaching source state: Taught / Current / Auto
|
||||
/// </summary>
|
||||
public MatrixCellTeachState TeachState
|
||||
{
|
||||
get => _teachState;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _teachState, value))
|
||||
{
|
||||
RaisePropertyChanged(nameof(TeachStateLabel));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>示教状态显示文本 | Teach state display text</summary>
|
||||
public string TeachStateLabel => _teachState switch
|
||||
{
|
||||
MatrixCellTeachState.Taught => "已示教",
|
||||
MatrixCellTeachState.Current => "当前",
|
||||
_ => "自动生成"
|
||||
};
|
||||
|
||||
/// <summary>该位置的实际机台 X 坐标(mm)| Actual stage X coordinate (mm)</summary>
|
||||
public double AbsoluteX
|
||||
{
|
||||
get => _absoluteX;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _absoluteX, value))
|
||||
RaisePropertyChanged(nameof(CoordinateLabel));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>该位置的实际机台 Y 坐标(mm)| Actual stage Y coordinate (mm)</summary>
|
||||
public double AbsoluteY
|
||||
{
|
||||
get => _absoluteY;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _absoluteY, value))
|
||||
RaisePropertyChanged(nameof(CoordinateLabel));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>是否已具备有效的机台坐标(存在示教参考点后为 true)| Whether a valid stage coordinate is available</summary>
|
||||
public bool HasCoordinate
|
||||
{
|
||||
get => _hasCoordinate;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _hasCoordinate, value))
|
||||
RaisePropertyChanged(nameof(CoordinateLabel));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>实际机台坐标显示文本(mm),无参考点时为 "--" | Actual stage coordinate label (mm)</summary>
|
||||
public string CoordinateLabel => _hasCoordinate
|
||||
? $"X:{_absoluteX:F3} Y:{_absoluteY:F3}"
|
||||
: "--";
|
||||
}
|
||||
// ── 矩阵单元格 ViewModel ──
|
||||
// 将 MatrixCell 模型封装为可绑定 ViewModel,每个单元格对应一个检测位置。
|
||||
// 在矩阵编辑器中显示 Cell 名称/状态/坐标预览等。
|
||||
using Prism.Mvvm;
|
||||
|
||||
namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
/// <summary>
|
||||
/// 矩阵单元格 ViewModel,将 MatrixCell 模型封装为可绑定的 WPF ViewModel
|
||||
/// Matrix cell ViewModel that wraps a MatrixCell model into a bindable WPF ViewModel
|
||||
/// </summary>
|
||||
public class MatrixCellViewModel : BindableBase
|
||||
{
|
||||
private int _row;
|
||||
private int _column;
|
||||
private double _offsetX;
|
||||
private double _offsetY;
|
||||
private bool _isEnabled;
|
||||
private MatrixCellStatus _status;
|
||||
private string _errorMessage;
|
||||
private bool _isSelected;
|
||||
private int _position;
|
||||
private MatrixCellTeachState _teachState;
|
||||
private double _absoluteX;
|
||||
private double _absoluteY;
|
||||
private bool _hasCoordinate;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数,从 MatrixCell 模型初始化 ViewModel
|
||||
/// Constructor that initializes the ViewModel from a MatrixCell model
|
||||
/// </summary>
|
||||
public MatrixCellViewModel(MatrixCell model)
|
||||
{
|
||||
Model = model;
|
||||
_row = model.Row;
|
||||
_column = model.Column;
|
||||
_offsetX = model.OffsetX;
|
||||
_offsetY = model.OffsetY;
|
||||
_isEnabled = model.IsEnabled;
|
||||
_status = model.Status;
|
||||
_errorMessage = model.ErrorMessage;
|
||||
_teachState = MatrixCellTeachState.Auto;
|
||||
}
|
||||
|
||||
/// <summary>底层矩阵单元格模型(只读)| Underlying matrix cell model (read-only)</summary>
|
||||
public MatrixCell Model { get; }
|
||||
|
||||
/// <summary>单元格行索引 | Cell row index</summary>
|
||||
public int Row
|
||||
{
|
||||
get => _row;
|
||||
set => SetProperty(ref _row, value);
|
||||
}
|
||||
|
||||
/// <summary>单元格列索引 | Cell column index</summary>
|
||||
public int Column
|
||||
{
|
||||
get => _column;
|
||||
set => SetProperty(ref _column, value);
|
||||
}
|
||||
|
||||
/// <summary>计算后的 X 偏移量 | Calculated X offset</summary>
|
||||
public double OffsetX
|
||||
{
|
||||
get => _offsetX;
|
||||
set => SetProperty(ref _offsetX, value);
|
||||
}
|
||||
|
||||
/// <summary>计算后的 Y 偏移量 | Calculated Y offset</summary>
|
||||
public double OffsetY
|
||||
{
|
||||
get => _offsetY;
|
||||
set => SetProperty(ref _offsetY, value);
|
||||
}
|
||||
|
||||
/// <summary>单元格是否启用检测 | Whether the cell is enabled for inspection</summary>
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => _isEnabled;
|
||||
set => SetProperty(ref _isEnabled, value);
|
||||
}
|
||||
|
||||
/// <summary>单元格执行状态 | Cell execution status</summary>
|
||||
public MatrixCellStatus Status
|
||||
{
|
||||
get => _status;
|
||||
set => SetProperty(ref _status, value);
|
||||
}
|
||||
|
||||
/// <summary>错误信息 | Error message if any</summary>
|
||||
public string ErrorMessage
|
||||
{
|
||||
get => _errorMessage;
|
||||
set => SetProperty(ref _errorMessage, value);
|
||||
}
|
||||
|
||||
/// <summary>单元格是否被选中 | Whether the cell is currently selected in the UI</summary>
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set => SetProperty(ref _isSelected, value);
|
||||
}
|
||||
|
||||
/// <summary>单元格 1 基位置编号(行优先)| 1-based position number (row-major)</summary>
|
||||
public int Position
|
||||
{
|
||||
get => _position;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _position, value))
|
||||
{
|
||||
RaisePropertyChanged(nameof(PositionLabel));
|
||||
RaisePropertyChanged(nameof(PositionShort));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>位置显示标签,如 "位置1 / Position 1" | Position display label</summary>
|
||||
public string PositionLabel => $"位置{Position} / Position {Position}";
|
||||
|
||||
/// <summary>位置短标签,如 "位置1" | Short position label</summary>
|
||||
public string PositionShort => $"位置{Position}";
|
||||
|
||||
/// <summary>
|
||||
/// 示教来源状态:手动示教 / 当前待示教 / 自动推算
|
||||
/// Teaching source state: Taught / Current / Auto
|
||||
/// </summary>
|
||||
public MatrixCellTeachState TeachState
|
||||
{
|
||||
get => _teachState;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _teachState, value))
|
||||
{
|
||||
RaisePropertyChanged(nameof(TeachStateLabel));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>示教状态显示文本 | Teach state display text</summary>
|
||||
public string TeachStateLabel => _teachState switch
|
||||
{
|
||||
MatrixCellTeachState.Taught => "已示教",
|
||||
MatrixCellTeachState.Current => "当前",
|
||||
_ => "自动生成"
|
||||
};
|
||||
|
||||
/// <summary>该位置的实际机台 X 坐标(mm)| Actual stage X coordinate (mm)</summary>
|
||||
public double AbsoluteX
|
||||
{
|
||||
get => _absoluteX;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _absoluteX, value))
|
||||
RaisePropertyChanged(nameof(CoordinateLabel));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>该位置的实际机台 Y 坐标(mm)| Actual stage Y coordinate (mm)</summary>
|
||||
public double AbsoluteY
|
||||
{
|
||||
get => _absoluteY;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _absoluteY, value))
|
||||
RaisePropertyChanged(nameof(CoordinateLabel));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>是否已具备有效的机台坐标(存在示教参考点后为 true)| Whether a valid stage coordinate is available</summary>
|
||||
public bool HasCoordinate
|
||||
{
|
||||
get => _hasCoordinate;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _hasCoordinate, value))
|
||||
RaisePropertyChanged(nameof(CoordinateLabel));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>实际机台坐标显示文本(mm),无参考点时为 "--" | Actual stage coordinate label (mm)</summary>
|
||||
public string CoordinateLabel => _hasCoordinate
|
||||
? $"X:{_absoluteX:F3} Y:{_absoluteY:F3}"
|
||||
: "--";
|
||||
}
|
||||
}
|
||||
+126
-126
@@ -1,127 +1,127 @@
|
||||
// ── 矩阵示教点 ViewModel ──
|
||||
// 示教点 = 操作员手动移动机台后记录的机械位置(锚点)。
|
||||
// 系统通过示教点插值自动生成其余单元格坐标。
|
||||
using Prism.Mvvm;
|
||||
|
||||
namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
/// <summary>
|
||||
/// 推荐示教点条目 ViewModel。
|
||||
/// 表示矩阵编排流程中操作员需要手动移动机台并记录位置的一个"锚点"。
|
||||
/// 系统仅需示教这些关键点(如 2×2 的 1、4 或 3×3 的 1、3、7),
|
||||
/// 其余位置由 <see cref="MatrixEditorViewModel"/> 自动推算偏移。
|
||||
/// A recommended teach point in the matrix teaching workflow.
|
||||
/// </summary>
|
||||
public class MatrixTeachPointViewModel : BindableBase
|
||||
{
|
||||
private TeachPointStatus _status;
|
||||
private double _recordedX;
|
||||
private double _recordedY;
|
||||
private bool _hasRecorded;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="position">1 基位置编号(行优先)| 1-based position number (row-major)</param>
|
||||
/// <param name="row">0 基行索引 | 0-based row index</param>
|
||||
/// <param name="column">0 基列索引 | 0-based column index</param>
|
||||
public MatrixTeachPointViewModel(int position, int row, int column)
|
||||
{
|
||||
Position = position;
|
||||
Row = row;
|
||||
Column = column;
|
||||
_status = TeachPointStatus.Pending;
|
||||
}
|
||||
|
||||
/// <summary>1 基位置编号 | 1-based position number</summary>
|
||||
public int Position { get; }
|
||||
|
||||
/// <summary>0 基行索引 | 0-based row index</summary>
|
||||
public int Row { get; }
|
||||
|
||||
/// <summary>0 基列索引 | 0-based column index</summary>
|
||||
public int Column { get; }
|
||||
|
||||
/// <summary>位置显示标签,如 "位置1" | Position display label</summary>
|
||||
public string PositionLabel => $"位置{Position}";
|
||||
|
||||
/// <summary>示教点状态:待示教 / 当前 / 已示教 | Teach point status</summary>
|
||||
public TeachPointStatus Status
|
||||
{
|
||||
get => _status;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _status, value))
|
||||
{
|
||||
RaisePropertyChanged(nameof(StatusLabel));
|
||||
RaisePropertyChanged(nameof(IsCurrent));
|
||||
RaisePropertyChanged(nameof(IsTaught));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>状态显示文本 | Status display text</summary>
|
||||
public string StatusLabel => _status switch
|
||||
{
|
||||
TeachPointStatus.Taught => "已示教",
|
||||
TeachPointStatus.Current => "当前",
|
||||
_ => "待示教"
|
||||
};
|
||||
|
||||
/// <summary>是否为当前待记录的示教点 | Whether this is the current teach point</summary>
|
||||
public bool IsCurrent => _status == TeachPointStatus.Current;
|
||||
|
||||
/// <summary>是否已完成示教 | Whether this teach point has been taught</summary>
|
||||
public bool IsTaught => _status == TeachPointStatus.Taught;
|
||||
|
||||
/// <summary>是否已记录实际坐标 | Whether an actual coordinate has been recorded</summary>
|
||||
public bool HasRecorded
|
||||
{
|
||||
get => _hasRecorded;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _hasRecorded, value))
|
||||
RaisePropertyChanged(nameof(CoordinateLabel));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>记录的载物台 X 坐标(mm)| Recorded stage X (mm)</summary>
|
||||
public double RecordedX
|
||||
{
|
||||
get => _recordedX;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _recordedX, value))
|
||||
RaisePropertyChanged(nameof(CoordinateLabel));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>记录的载物台 Y 坐标(mm)| Recorded stage Y (mm)</summary>
|
||||
public double RecordedY
|
||||
{
|
||||
get => _recordedY;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _recordedY, value))
|
||||
RaisePropertyChanged(nameof(CoordinateLabel));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>坐标显示文本(未记录时为 "未记录")| Coordinate display text</summary>
|
||||
public string CoordinateLabel => _hasRecorded
|
||||
? $"X:{_recordedX:F3} Y:{_recordedY:F3} mm"
|
||||
: "未记录";
|
||||
|
||||
/// <summary>
|
||||
/// 记录一次实际位置(单位 mm,保留 3 位小数),并标记为已示教。
|
||||
/// Record an actual position (in mm, rounded to 3 decimal places) and mark as taught.
|
||||
/// </summary>
|
||||
public void Record(double stageXmm, double stageYmm)
|
||||
{
|
||||
RecordedX = System.Math.Round(stageXmm, 3, System.MidpointRounding.AwayFromZero);
|
||||
RecordedY = System.Math.Round(stageYmm, 3, System.MidpointRounding.AwayFromZero);
|
||||
HasRecorded = true;
|
||||
Status = TeachPointStatus.Taught;
|
||||
}
|
||||
}
|
||||
// ── 矩阵示教点 ViewModel ──
|
||||
// 示教点 = 操作员手动移动机台后记录的机械位置(锚点)。
|
||||
// 系统通过示教点插值自动生成其余单元格坐标。
|
||||
using Prism.Mvvm;
|
||||
|
||||
namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
/// <summary>
|
||||
/// 推荐示教点条目 ViewModel。
|
||||
/// 表示矩阵编排流程中操作员需要手动移动机台并记录位置的一个"锚点"。
|
||||
/// 系统仅需示教这些关键点(如 2×2 的 1、4 或 3×3 的 1、3、7),
|
||||
/// 其余位置由 <see cref="MatrixEditorViewModel"/> 自动推算偏移。
|
||||
/// A recommended teach point in the matrix teaching workflow.
|
||||
/// </summary>
|
||||
public class MatrixTeachPointViewModel : BindableBase
|
||||
{
|
||||
private TeachPointStatus _status;
|
||||
private double _recordedX;
|
||||
private double _recordedY;
|
||||
private bool _hasRecorded;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="position">1 基位置编号(行优先)| 1-based position number (row-major)</param>
|
||||
/// <param name="row">0 基行索引 | 0-based row index</param>
|
||||
/// <param name="column">0 基列索引 | 0-based column index</param>
|
||||
public MatrixTeachPointViewModel(int position, int row, int column)
|
||||
{
|
||||
Position = position;
|
||||
Row = row;
|
||||
Column = column;
|
||||
_status = TeachPointStatus.Pending;
|
||||
}
|
||||
|
||||
/// <summary>1 基位置编号 | 1-based position number</summary>
|
||||
public int Position { get; }
|
||||
|
||||
/// <summary>0 基行索引 | 0-based row index</summary>
|
||||
public int Row { get; }
|
||||
|
||||
/// <summary>0 基列索引 | 0-based column index</summary>
|
||||
public int Column { get; }
|
||||
|
||||
/// <summary>位置显示标签,如 "位置1" | Position display label</summary>
|
||||
public string PositionLabel => $"位置{Position}";
|
||||
|
||||
/// <summary>示教点状态:待示教 / 当前 / 已示教 | Teach point status</summary>
|
||||
public TeachPointStatus Status
|
||||
{
|
||||
get => _status;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _status, value))
|
||||
{
|
||||
RaisePropertyChanged(nameof(StatusLabel));
|
||||
RaisePropertyChanged(nameof(IsCurrent));
|
||||
RaisePropertyChanged(nameof(IsTaught));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>状态显示文本 | Status display text</summary>
|
||||
public string StatusLabel => _status switch
|
||||
{
|
||||
TeachPointStatus.Taught => "已示教",
|
||||
TeachPointStatus.Current => "当前",
|
||||
_ => "待示教"
|
||||
};
|
||||
|
||||
/// <summary>是否为当前待记录的示教点 | Whether this is the current teach point</summary>
|
||||
public bool IsCurrent => _status == TeachPointStatus.Current;
|
||||
|
||||
/// <summary>是否已完成示教 | Whether this teach point has been taught</summary>
|
||||
public bool IsTaught => _status == TeachPointStatus.Taught;
|
||||
|
||||
/// <summary>是否已记录实际坐标 | Whether an actual coordinate has been recorded</summary>
|
||||
public bool HasRecorded
|
||||
{
|
||||
get => _hasRecorded;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _hasRecorded, value))
|
||||
RaisePropertyChanged(nameof(CoordinateLabel));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>记录的载物台 X 坐标(mm)| Recorded stage X (mm)</summary>
|
||||
public double RecordedX
|
||||
{
|
||||
get => _recordedX;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _recordedX, value))
|
||||
RaisePropertyChanged(nameof(CoordinateLabel));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>记录的载物台 Y 坐标(mm)| Recorded stage Y (mm)</summary>
|
||||
public double RecordedY
|
||||
{
|
||||
get => _recordedY;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _recordedY, value))
|
||||
RaisePropertyChanged(nameof(CoordinateLabel));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>坐标显示文本(未记录时为 "未记录")| Coordinate display text</summary>
|
||||
public string CoordinateLabel => _hasRecorded
|
||||
? $"X:{_recordedX:F3} Y:{_recordedY:F3} mm"
|
||||
: "未记录";
|
||||
|
||||
/// <summary>
|
||||
/// 记录一次实际位置(单位 mm,保留 3 位小数),并标记为已示教。
|
||||
/// Record an actual position (in mm, rounded to 3 decimal places) and mark as taught.
|
||||
/// </summary>
|
||||
public void Record(double stageXmm, double stageYmm)
|
||||
{
|
||||
RecordedX = System.Math.Round(stageXmm, 3, System.MidpointRounding.AwayFromZero);
|
||||
RecordedY = System.Math.Round(stageYmm, 3, System.MidpointRounding.AwayFromZero);
|
||||
HasRecorded = true;
|
||||
Status = TeachPointStatus.Taught;
|
||||
}
|
||||
}
|
||||
}
|
||||
+131
-131
@@ -1,132 +1,132 @@
|
||||
using Prism.Commands;
|
||||
using Prism.Mvvm;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XplorePlane.Services.Measurement;
|
||||
|
||||
namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
/// <summary>
|
||||
/// 测量统计 ViewModel,显示测量统计数据并支持筛选
|
||||
/// Measurement statistics ViewModel that displays measurement statistics and supports filtering
|
||||
/// </summary>
|
||||
public class MeasurementStatsViewModel : BindableBase
|
||||
{
|
||||
private readonly IMeasurementDataService _measurementDataService;
|
||||
private readonly ILoggerService _logger;
|
||||
|
||||
// ── 统计属性字段 | Statistics property fields ──
|
||||
private int _totalCount;
|
||||
|
||||
private int _passCount;
|
||||
private int _failCount;
|
||||
private double _passRate;
|
||||
|
||||
// ── 筛选属性字段 | Filter property fields ──
|
||||
private string _recipeName;
|
||||
|
||||
private DateTime? _fromDate;
|
||||
private DateTime? _toDate;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
public MeasurementStatsViewModel(
|
||||
IMeasurementDataService measurementDataService,
|
||||
ILoggerService logger)
|
||||
{
|
||||
_measurementDataService = measurementDataService ?? throw new ArgumentNullException(nameof(measurementDataService));
|
||||
_logger = (logger ?? throw new ArgumentNullException(nameof(logger))).ForModule<MeasurementStatsViewModel>();
|
||||
|
||||
// ── 命令初始化 | Command initialization ──
|
||||
RefreshCommand = new DelegateCommand(async () => await ExecuteRefreshAsync());
|
||||
|
||||
_logger.Info("MeasurementStatsViewModel 已初始化 | MeasurementStatsViewModel initialized");
|
||||
}
|
||||
|
||||
// ── 统计属性 | Statistics properties ────────────────────────────
|
||||
|
||||
/// <summary>总测量数 | Total measurement count</summary>
|
||||
public int TotalCount
|
||||
{
|
||||
get => _totalCount;
|
||||
set => SetProperty(ref _totalCount, value);
|
||||
}
|
||||
|
||||
/// <summary>合格数 | Pass count</summary>
|
||||
public int PassCount
|
||||
{
|
||||
get => _passCount;
|
||||
set => SetProperty(ref _passCount, value);
|
||||
}
|
||||
|
||||
/// <summary>不合格数 | Fail count</summary>
|
||||
public int FailCount
|
||||
{
|
||||
get => _failCount;
|
||||
set => SetProperty(ref _failCount, value);
|
||||
}
|
||||
|
||||
/// <summary>合格率(0.0 ~ 1.0)| Pass rate (0.0 to 1.0)</summary>
|
||||
public double PassRate
|
||||
{
|
||||
get => _passRate;
|
||||
set => SetProperty(ref _passRate, value);
|
||||
}
|
||||
|
||||
// ── 筛选属性 | Filter properties ────────────────────────────────
|
||||
|
||||
/// <summary>按配方名称筛选 | Filter by recipe name</summary>
|
||||
public string RecipeName
|
||||
{
|
||||
get => _recipeName;
|
||||
set => SetProperty(ref _recipeName, value);
|
||||
}
|
||||
|
||||
/// <summary>筛选起始日期 | Filter start date</summary>
|
||||
public DateTime? FromDate
|
||||
{
|
||||
get => _fromDate;
|
||||
set => SetProperty(ref _fromDate, value);
|
||||
}
|
||||
|
||||
/// <summary>筛选结束日期 | Filter end date</summary>
|
||||
public DateTime? ToDate
|
||||
{
|
||||
get => _toDate;
|
||||
set => SetProperty(ref _toDate, value);
|
||||
}
|
||||
|
||||
// ── 命令 | Commands ─────────────────────────────────────────────
|
||||
|
||||
/// <summary>刷新统计数据命令 | Refresh statistics command</summary>
|
||||
public DelegateCommand RefreshCommand { get; }
|
||||
|
||||
// ── 命令执行方法 | Command execution methods ────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 从服务获取统计数据并更新属性
|
||||
/// Fetch statistics from service and update properties
|
||||
/// </summary>
|
||||
private async Task ExecuteRefreshAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var stats = await _measurementDataService.GetStatisticsAsync(RecipeName, FromDate, ToDate);
|
||||
|
||||
TotalCount = stats.TotalCount;
|
||||
PassCount = stats.PassCount;
|
||||
FailCount = stats.FailCount;
|
||||
PassRate = stats.PassRate;
|
||||
|
||||
_logger.Info("统计数据已刷新 | Statistics refreshed: Total={TotalCount}, Pass={PassCount}, Fail={FailCount}, Rate={PassRate:P1}",
|
||||
stats.TotalCount, stats.PassCount, stats.FailCount, stats.PassRate);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "刷新统计数据失败 | Failed to refresh statistics");
|
||||
}
|
||||
}
|
||||
}
|
||||
using Prism.Commands;
|
||||
using Prism.Mvvm;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XplorePlane.Services.Measurement;
|
||||
|
||||
namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
/// <summary>
|
||||
/// 测量统计 ViewModel,显示测量统计数据并支持筛选
|
||||
/// Measurement statistics ViewModel that displays measurement statistics and supports filtering
|
||||
/// </summary>
|
||||
public class MeasurementStatsViewModel : BindableBase
|
||||
{
|
||||
private readonly IMeasurementDataService _measurementDataService;
|
||||
private readonly ILoggerService _logger;
|
||||
|
||||
// ── 统计属性字段 | Statistics property fields ──
|
||||
private int _totalCount;
|
||||
|
||||
private int _passCount;
|
||||
private int _failCount;
|
||||
private double _passRate;
|
||||
|
||||
// ── 筛选属性字段 | Filter property fields ──
|
||||
private string _recipeName;
|
||||
|
||||
private DateTime? _fromDate;
|
||||
private DateTime? _toDate;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
public MeasurementStatsViewModel(
|
||||
IMeasurementDataService measurementDataService,
|
||||
ILoggerService logger)
|
||||
{
|
||||
_measurementDataService = measurementDataService ?? throw new ArgumentNullException(nameof(measurementDataService));
|
||||
_logger = (logger ?? throw new ArgumentNullException(nameof(logger))).ForModule<MeasurementStatsViewModel>();
|
||||
|
||||
// ── 命令初始化 | Command initialization ──
|
||||
RefreshCommand = new DelegateCommand(async () => await ExecuteRefreshAsync());
|
||||
|
||||
_logger.Info("MeasurementStatsViewModel 已初始化 | MeasurementStatsViewModel initialized");
|
||||
}
|
||||
|
||||
// ── 统计属性 | Statistics properties ────────────────────────────
|
||||
|
||||
/// <summary>总测量数 | Total measurement count</summary>
|
||||
public int TotalCount
|
||||
{
|
||||
get => _totalCount;
|
||||
set => SetProperty(ref _totalCount, value);
|
||||
}
|
||||
|
||||
/// <summary>合格数 | Pass count</summary>
|
||||
public int PassCount
|
||||
{
|
||||
get => _passCount;
|
||||
set => SetProperty(ref _passCount, value);
|
||||
}
|
||||
|
||||
/// <summary>不合格数 | Fail count</summary>
|
||||
public int FailCount
|
||||
{
|
||||
get => _failCount;
|
||||
set => SetProperty(ref _failCount, value);
|
||||
}
|
||||
|
||||
/// <summary>合格率(0.0 ~ 1.0)| Pass rate (0.0 to 1.0)</summary>
|
||||
public double PassRate
|
||||
{
|
||||
get => _passRate;
|
||||
set => SetProperty(ref _passRate, value);
|
||||
}
|
||||
|
||||
// ── 筛选属性 | Filter properties ────────────────────────────────
|
||||
|
||||
/// <summary>按配方名称筛选 | Filter by recipe name</summary>
|
||||
public string RecipeName
|
||||
{
|
||||
get => _recipeName;
|
||||
set => SetProperty(ref _recipeName, value);
|
||||
}
|
||||
|
||||
/// <summary>筛选起始日期 | Filter start date</summary>
|
||||
public DateTime? FromDate
|
||||
{
|
||||
get => _fromDate;
|
||||
set => SetProperty(ref _fromDate, value);
|
||||
}
|
||||
|
||||
/// <summary>筛选结束日期 | Filter end date</summary>
|
||||
public DateTime? ToDate
|
||||
{
|
||||
get => _toDate;
|
||||
set => SetProperty(ref _toDate, value);
|
||||
}
|
||||
|
||||
// ── 命令 | Commands ─────────────────────────────────────────────
|
||||
|
||||
/// <summary>刷新统计数据命令 | Refresh statistics command</summary>
|
||||
public DelegateCommand RefreshCommand { get; }
|
||||
|
||||
// ── 命令执行方法 | Command execution methods ────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 从服务获取统计数据并更新属性
|
||||
/// Fetch statistics from service and update properties
|
||||
/// </summary>
|
||||
private async Task ExecuteRefreshAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var stats = await _measurementDataService.GetStatisticsAsync(RecipeName, FromDate, ToDate);
|
||||
|
||||
TotalCount = stats.TotalCount;
|
||||
PassCount = stats.PassCount;
|
||||
FailCount = stats.FailCount;
|
||||
PassRate = stats.PassRate;
|
||||
|
||||
_logger.Info("统计数据已刷新 | Statistics refreshed: Total={TotalCount}, Pass={PassCount}, Fail={FailCount}, Rate={PassRate:P1}",
|
||||
stats.TotalCount, stats.PassCount, stats.FailCount, stats.PassRate);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "刷新统计数据失败 | Failed to refresh statistics");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user