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.Models; using XplorePlane.Services.Storage; namespace XplorePlane.ViewModels.Cnc { /// /// 检测节点卡片 ViewModel,包装单个 InspectionNodeResult 并提供图像切换和快照查看功能 /// Inspection node card ViewModel that wraps a single InspectionNodeResult and provides image toggle and snapshot viewing /// public class InspectionNodeCardViewModel : BindableBase { private readonly InspectionNodeResult _nodeResult; private readonly IXpDataPathService _dataPathService; private readonly PipelineExecutionSnapshot _snapshot; private readonly InspectionAssetRecord _resultImageAsset; private readonly InspectionAssetRecord _inputImageAsset; private BitmapSource _resultImage; private BitmapSource _inputImage; private BitmapSource _currentImage; private bool _isShowingInputImage; /// /// 构造函数 | Constructor /// /// 节点结果数据 | Node result data /// 该节点的所有指标 | All metrics for this node /// 该节点的所有资产 | All assets for this node /// Pipeline 快照(可为 null)| Pipeline snapshot (can be null) /// 数据路径服务 | Data path service public InspectionNodeCardViewModel( InspectionNodeResult nodeResult, IEnumerable metrics, IEnumerable assets, PipelineExecutionSnapshot snapshot, IXpDataPathService dataPathService) { _nodeResult = nodeResult ?? throw new ArgumentNullException(nameof(nodeResult)); _dataPathService = dataPathService ?? throw new ArgumentNullException(nameof(dataPathService)); _snapshot = snapshot; // 查找资产记录 // Find asset records var assetList = assets?.ToList() ?? new List(); _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(); Metrics = new ObservableCollection(metricList); // 初始化命令 // Initialize commands ToggleImageCommand = new DelegateCommand(ExecuteToggleImage, CanExecuteToggleImage); ViewSnapshotCommand = new DelegateCommand(ExecuteViewSnapshot, CanExecuteViewSnapshot); // 异步加载图像 // Load images asynchronously _ = LoadImagesAsync(); } // ── 属性 | Properties ────────────────────────────────────────── /// 节点序号 | Node index public int NodeIndex => _nodeResult.NodeIndex; /// 节点名称 | Node name public string NodeName => _nodeResult.NodeName; /// Pipeline 名称 | Pipeline name public string PipelineName => _nodeResult.PipelineName; /// 节点判定 | Node pass status public bool NodePass => _nodeResult.NodePass; /// 节点状态 | Node status public InspectionNodeStatus Status => _nodeResult.Status; /// 耗时(毫秒)| Duration in milliseconds public long DurationMs => _nodeResult.DurationMs; /// /// 判定标签文字("Pass" / "Fail") /// Pass label text ("Pass" / "Fail") /// public string PassLabel => NodePass ? "Pass" : "Fail"; /// /// 判定标签颜色(绿色 #2E7D32 / 红色 #C62828) /// Pass label color (green #2E7D32 / red #C62828) /// public Brush PassLabelColor => NodePass ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#2E7D32")) : new SolidColorBrush((Color)ColorConverter.ConvertFromString("#C62828")); /// /// 是否有资产缺失警告(Status == AssetMissing) /// Whether there is an asset missing warning (Status == AssetMissing) /// public bool HasAssetMissingWarning => Status == InspectionNodeStatus.AssetMissing; /// /// Pipeline 版本标识(hash 前 8 位,空则 "--") /// Pipeline version identifier (first 8 chars of hash, or "--" if empty) /// public string PipelineVersionShort { get { if (string.IsNullOrWhiteSpace(_nodeResult.PipelineVersionHash)) { return "--"; } return _nodeResult.PipelineVersionHash.Length >= 8 ? _nodeResult.PipelineVersionHash.Substring(0, 8) : _nodeResult.PipelineVersionHash; } } /// /// 是否有 Pipeline 快照 /// Whether pipeline snapshot exists /// public bool HasPipelineSnapshot => _snapshot != null && !string.IsNullOrWhiteSpace(_snapshot.PipelineDefinitionJson); /// /// 格式化的 Pipeline 定义 JSON(2 空格缩进) /// Formatted pipeline definition JSON (2-space indentation) /// 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; } } } /// /// 节点结果图(可为 null) /// Node result image (can be null) /// public BitmapSource ResultImage { get => _resultImage; private set => SetProperty(ref _resultImage, value); } /// /// 节点输入图(可为 null) /// Node input image (can be null) /// public BitmapSource InputImage { get => _inputImage; private set => SetProperty(ref _inputImage, value); } /// /// 当前显示的图像(默认为结果图) /// Currently displayed image (defaults to result image) /// public BitmapSource CurrentImage { get => _currentImage; private set => SetProperty(ref _currentImage, value); } /// /// 是否正在显示输入图 /// Whether currently showing input image /// public bool IsShowingInputImage { get => _isShowingInputImage; private set => SetProperty(ref _isShowingInputImage, value); } /// /// 是否有输入图 /// Whether input image exists /// public bool HasInputImage => InputImage != null; /// /// 指标列表(按 DisplayOrder 升序,同序按 MetricName 升序) /// Metrics list (sorted by DisplayOrder ascending, then by MetricName ascending) /// public ObservableCollection Metrics { get; } // ── 命令 | Commands ──────────────────────────────────────────── /// /// 切换图像命令(在输入图和结果图之间切换) /// Toggle image command (switch between input and result images) /// public DelegateCommand ToggleImageCommand { get; } /// /// 查看快照命令(打开 SnapshotViewerWindow) /// View snapshot command (open SnapshotViewerWindow) /// public DelegateCommand ViewSnapshotCommand { get; } // ── 私有方法 | Private Methods ───────────────────────────────── /// /// 异步加载图像 /// Load images asynchronously /// private async Task LoadImagesAsync() { // 加载结果图 // Load result image if (_resultImageAsset != null && !string.IsNullOrWhiteSpace(_resultImageAsset.RelativePath)) { var resultImagePath = Path.Combine(_dataPathService.DataPath, _resultImageAsset.RelativePath); ResultImage = await ImageLoader.LoadBitmapSafeAsync(resultImagePath); } // 加载输入图 // Load input image if (_inputImageAsset != null && !string.IsNullOrWhiteSpace(_inputImageAsset.RelativePath)) { var inputImagePath = Path.Combine(_dataPathService.DataPath, _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(); } /// /// 执行切换图像命令 /// Execute toggle image command /// private void ExecuteToggleImage() { if (IsShowingInputImage) { // 切换到结果图 // Switch to result image CurrentImage = ResultImage; IsShowingInputImage = false; } else { // 切换到输入图 // Switch to input image CurrentImage = InputImage; IsShowingInputImage = true; } } /// /// 判断是否可以执行切换图像命令 /// Determine whether toggle image command can be executed /// private bool CanExecuteToggleImage() { return InputImage != null && ResultImage != null; } /// /// 执行查看快照命令 /// Execute view snapshot command /// 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}"); } } /// /// 判断是否可以执行查看快照命令 /// Determine whether view snapshot command can be executed /// private bool CanExecuteViewSnapshot() { return HasPipelineSnapshot; } } }