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;
}
}
}