using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using System.Windows; using Prism.Commands; using Prism.Mvvm; using XP.Common.GeneralForm.Views; using XP.Common.Logging.Interfaces; using XP.Common.PdfViewer.Interfaces; using XP.ReportEngine.Configs; using XP.ReportEngine.Interfaces; using XP.ReportEngine.Models; using XP.ReportEngine.Services; namespace XP.ReportEngine.ViewModels { /// /// 报告生成演示窗口 ViewModel | Report generation demo window ViewModel /// 演示如何使用 IReportService 生成 PDF 报告 /// Demonstrates how to use IReportService to generate PDF reports /// public class ReportDemoViewModel : BindableBase { private readonly IReportService _reportService; private readonly IPdfViewerService _pdfViewerService; private readonly IPdfPrintService _pdfPrintService; private readonly ILoggerService _logger; private readonly ReportIdGenerator _reportIdGenerator; private readonly ReportConfig _reportConfig; private string _productName = "PCB-TEST-001"; private string _operatorName = "戚明轩 mingxuan.qi@hexagon.com"; private string _description = "BGA 焊球气泡率检测"; private string _cncProgram = "Prog001"; private string _productCode = "PCBA-X100"; private string _workpieceSN = "SN20250001"; private string _deviceId = "XP-CT-001"; private string _machineId = "MC01"; private string _statusMessage = "就绪"; private string _lastOutputPath; private bool _isGenerating; #region 属性 | Properties /// /// 产品名称 | Product name /// public string ProductName { get => _productName; set => SetProperty(ref _productName, value); } /// /// 操作员 | Operator /// public string OperatorName { get => _operatorName; set => SetProperty(ref _operatorName, value); } /// /// 描述 | Description /// public string Description { get => _description; set => SetProperty(ref _description, value); } /// /// CNC 程序名称 | CNC program name /// public string CncProgram { get => _cncProgram; set => SetProperty(ref _cncProgram, value); } /// /// 产品类型码 | Product type code /// public string ProductCode { get => _productCode; set => SetProperty(ref _productCode, value); } /// /// 工件 SN 码 | Workpiece serial number /// public string WorkpieceSN { get => _workpieceSN; set => SetProperty(ref _workpieceSN, value); } /// /// 检测设备编号 | Inspection device ID /// public string DeviceId { get => _deviceId; set => SetProperty(ref _deviceId, value); } /// /// 生产机台号 | Production machine ID /// public string MachineId { get => _machineId; set => SetProperty(ref _machineId, value); } /// /// 状态信息 | Status message /// public string StatusMessage { get => _statusMessage; set => SetProperty(ref _statusMessage, value); } /// /// 是否正在生成 | Whether generating /// public bool IsGenerating { get => _isGenerating; set { if (SetProperty(ref _isGenerating, value)) { GenerateReportCommand.RaiseCanExecuteChanged(); OpenViewerCommand.RaiseCanExecuteChanged(); PrintReportCommand.RaiseCanExecuteChanged(); } } } #endregion #region 命令 | Commands /// /// 生成报告命令 | Generate report command /// public DelegateCommand GenerateReportCommand { get; } /// /// 打开 PDF 阅读器命令 | Open PDF viewer command /// public DelegateCommand OpenViewerCommand { get; } /// /// 打印报告命令 | Print report command /// public DelegateCommand PrintReportCommand { get; } #endregion /// /// 构造函数 | Constructor /// public ReportDemoViewModel( IReportService reportService, IPdfViewerService pdfViewerService, IPdfPrintService pdfPrintService, ILoggerService logger, ReportIdGenerator reportIdGenerator, ReportConfig reportConfig) { _reportService = reportService ?? throw new ArgumentNullException(nameof(reportService)); _pdfViewerService = pdfViewerService ?? throw new ArgumentNullException(nameof(pdfViewerService)); _pdfPrintService = pdfPrintService ?? throw new ArgumentNullException(nameof(pdfPrintService)); _logger = logger?.ForModule() ?? throw new ArgumentNullException(nameof(logger)); _reportIdGenerator = reportIdGenerator ?? throw new ArgumentNullException(nameof(reportIdGenerator)); _reportConfig = reportConfig ?? throw new ArgumentNullException(nameof(reportConfig)); GenerateReportCommand = new DelegateCommand(async () => await GenerateReportAsync(), () => !IsGenerating); OpenViewerCommand = new DelegateCommand(OpenViewer, () => !IsGenerating && !string.IsNullOrEmpty(_lastOutputPath)); PrintReportCommand = new DelegateCommand(PrintReport, () => !IsGenerating && !string.IsNullOrEmpty(_lastOutputPath)); } /// /// 生成报告(带进度条)| Generate report (with progress window) /// private async Task GenerateReportAsync() { IsGenerating = true; StatusMessage = "正在生成报告..."; try { // 构建文件名占位符参数 | Build file name placeholder parameters var fileNameParams = new Dictionary { ["ReportId"] = _reportIdGenerator.GenerateNext(), ["ProductName"] = ProductName, ["CncProgram"] = CncProgram, ["ProductCode"] = ProductCode, ["WorkpieceSN"] = WorkpieceSN, ["DeviceId"] = DeviceId, ["MachineId"] = MachineId, ["Result"] = "Pass" }; // 确定输出路径:提示用户是否使用默认位置 | Determine output path: ask user whether to use default location var defaultOutputPath = _reportConfig.ResolveOutputFilePath(fileNameParams); var defaultFileName = System.IO.Path.GetFileName(defaultOutputPath); var result = MessageBox.Show( $"是否将报告输出到默认位置?\r\n{defaultOutputPath}", "输出位置确认", MessageBoxButton.YesNoCancel, MessageBoxImage.Question); if (result == MessageBoxResult.Cancel) { StatusMessage = "已取消生成"; IsGenerating = false; return; } string outputPath; if (result == MessageBoxResult.No) { // 用户选择自定义位置 | User chooses custom location var saveDialog = new Microsoft.Win32.SaveFileDialog { Title = "选择报告保存位置", Filter = "PDF 文件 (*.pdf)|*.pdf", FileName = defaultFileName, DefaultExt = ".pdf", InitialDirectory = System.IO.Path.GetDirectoryName(defaultOutputPath) }; if (saveDialog.ShowDialog() != true) { StatusMessage = "已取消生成"; IsGenerating = false; return; } outputPath = saveDialog.FileName; } else { // 使用默认路径 | Use default path outputPath = defaultOutputPath; } // 创建进度条窗口 | Create progress window var progressWindow = new ProgressWindow( title: "报告生成中", message: "正在准备数据...", isCancelable: false, logger: _logger); progressWindow.Owner = Application.Current.MainWindow; progressWindow.Show(); try { // 步骤 1:准备模拟数据 | Step 1: Prepare mock data progressWindow.UpdateProgress("正在准备检测数据...", 10); await Task.Delay(300); // 模拟耗时 | Simulate delay var processorOutputs = CreateMockProcessorOutputs(); // 步骤 2:构建报告请求 | Step 2: Build report request progressWindow.UpdateProgress("正在组装报告数据...", 30); await Task.Delay(200); var request = new ReportRequest { ProcessorOutputs = processorOutputs, Metadata = new ReportMetadata { ReportId = fileNameParams["ReportId"], InspectionDate = DateTime.Now, SampleName = ProductName, OperatorName = OperatorName, Description = Description }, OutputFilePath = outputPath, FileNameParameters = fileNameParams }; // 注入工件整体图片 | Inject workpiece overview image var workpieceImagePath = System.IO.Path.Combine(MockImageDirectory, "OverView.png"); if (File.Exists(workpieceImagePath)) { request.AdditionalImages["workpieceImage"] = new ImageData { SourceType = ImageSourceType.FilePath, FilePath = workpieceImagePath }; } // 步骤 3:调用报告服务生成(在后台线程执行,避免阻塞 UI) // Step 3: Call report service (on background thread to avoid blocking UI) progressWindow.UpdateProgress("正在生成 PDF...", 60); await Task.Delay(200); var genResult = await Task.Run(() => _reportService.GenerateAsync(request)); // 步骤 4:处理结果 | Step 4: Handle result progressWindow.UpdateProgress("正在完成...", 95); await Task.Delay(200); if (genResult.IsSuccess) { _lastOutputPath = genResult.OutputFilePath; StatusMessage = $"报告生成成功:{genResult.OutputFilePath}"; _logger.Info("报告生成成功:{Path} | Report generated successfully: {Path}", genResult.OutputFilePath); progressWindow.UpdateProgress("报告生成完成!", 100); await Task.Delay(500); // 根据配置自动打开 PDF 阅读器 | Auto-open PDF viewer based on config if (_reportConfig.AutoOpenAfterGenerate) { try { _pdfViewerService.OpenViewer(genResult.OutputFilePath); } catch (Exception viewerEx) { _logger.Warn("自动打开 PDF 失败 | Auto-open PDF failed: {Message}", viewerEx.Message); } } } else { StatusMessage = $"报告生成失败:{genResult.ErrorMessage}"; _logger.Error(null, "报告生成失败:{Message} | Report generation failed: {Message}", genResult.ErrorMessage); MessageBox.Show( $"报告生成失败:\n{genResult.ErrorMessage}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } finally { progressWindow.Close(); } } catch (Exception ex) { StatusMessage = $"报告生成异常:{ex.Message}"; _logger.Error(ex, "报告生成异常 | Report generation exception: {Message}", ex.Message); MessageBox.Show( $"报告生成过程中发生异常:\n{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } finally { IsGenerating = false; OpenViewerCommand.RaiseCanExecuteChanged(); PrintReportCommand.RaiseCanExecuteChanged(); } } /// /// 打开 PDF 阅读器 | Open PDF viewer /// private void OpenViewer() { if (string.IsNullOrEmpty(_lastOutputPath) || !File.Exists(_lastOutputPath)) { MessageBox.Show("PDF 文件不存在,请先生成报告。", "提示", MessageBoxButton.OK, MessageBoxImage.Warning); return; } try { // 使用 XP.Common PDF 阅读器打开 | Open with XP.Common PDF viewer _pdfViewerService.OpenViewer(_lastOutputPath); _logger.Info("使用内置 PDF 阅读器打开:{Path} | Opened with built-in PDF viewer: {Path}", _lastOutputPath); } catch (Exception ex) { _logger.Error(ex, "打开 PDF 失败 | Failed to open PDF: {Message}", ex.Message); MessageBox.Show($"打开 PDF 失败:\n{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } /// /// 打印报告 | Print report /// private void PrintReport() { if (string.IsNullOrEmpty(_lastOutputPath) || !File.Exists(_lastOutputPath)) { MessageBox.Show("PDF 文件不存在,请先生成报告。", "提示", MessageBoxButton.OK, MessageBoxImage.Warning); return; } try { var confirmed = _pdfPrintService.PrintWithDialog(_lastOutputPath); if (confirmed) { _logger.Info("报告已发送到打印机:{Path} | Report sent to printer: {Path}", _lastOutputPath); StatusMessage = "报告已发送到打印机"; } } catch (Exception ex) { _logger.Error(ex, "打印报告失败 | Failed to print report: {Message}", ex.Message); MessageBox.Show($"打印失败:\n{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } #region 模拟数据 | Mock Data /// /// 模拟图像目录路径 | Mock image directory path /// private const string MockImageDirectory = @"D:\XPData\DetectorImages"; /// /// 创建模拟处理器输出数据(演示用,覆盖所有检测类型) /// Create mock processor outputs (for demo, covers all inspection types) /// private List CreateMockProcessorOutputs() { return new List { CreateLineMeasurementOutput(), CreateBgaVoidRateOutput(), CreateVoidMeasurementOutput(), CreateFillRateOutput() }; } /// /// 创建线测量处理器模拟数据 | Create line measurement processor mock data /// private ProcessorOutput CreateLineMeasurementOutput() { return new ProcessorOutput { ProcessorType = "LineMeasurementProcessor", OutputData = new Dictionary { ["MeasurementType"] = "TwoPointDistance", ["Point1"] = "X=125.32, Y=80.15", ["Point2"] = "X=340.78, Y=80.15", ["PixelDistance"] = 215.46, ["ActualDistance"] = 3.256, ["Unit"] = "mm", ["Angle"] = 0.0 }, AnnotatedImage = LoadMockImage("BGA.png") }; } /// /// 创建 BGA 气泡率处理器模拟数据 | Create BGA void rate processor mock data /// private ProcessorOutput CreateBgaVoidRateOutput() { // 生成 64 组 BGA 球模拟数据 | Generate 64 BGA ball mock data var bgaBalls = new List>(); var random = new Random(42); // 固定种子确保可重复 | Fixed seed for reproducibility var voidLimit = 0.05; for (int i = 1; i <= 64; i++) { // 大部分合格,少数不合格 | Most pass, a few fail double voidRate; if (i == 12 || i == 27 || i == 41 || i == 58) { // 这几个球不合格 | These balls fail voidRate = Math.Round(0.05 + random.NextDouble() * 0.03, 4); } else { voidRate = Math.Round(random.NextDouble() * 0.045, 4); } var area = Math.Round(190.0 + random.NextDouble() * 15.0, 1); var classification = voidRate > voidLimit ? "Fail" : "Pass"; bgaBalls.Add(new Dictionary { ["Index"] = i, ["VoidRate"] = voidRate, ["Area"] = area, ["Classification"] = classification }); } return new ProcessorOutput { ProcessorType = "BgaVoidRateProcessor", OutputData = new Dictionary { ["BgaCount"] = 64, ["VoidRate"] = 0.028, ["FillRate"] = 0.972, ["TotalBgaArea"] = 12500.5, ["TotalVoidArea"] = 350.2, ["Classification"] = "Pass", ["VoidLimit"] = 0.05, ["BgaBalls"] = bgaBalls }, AnnotatedImage = LoadMockImage("BGA.png") }; } /// /// 创建空隙测量处理器模拟数据 | Create void measurement processor mock data /// private ProcessorOutput CreateVoidMeasurementOutput() { return new ProcessorOutput { ProcessorType = "VoidMeasurementProcessor", OutputData = new Dictionary { ["RoiArea"] = 5000.0, ["TotalVoidArea"] = 125.8, ["VoidRate"] = 0.025, ["VoidLimit"] = 0.05, ["VoidCount"] = 5, ["MaxVoidArea"] = 65.2, ["Classification"] = "Pass", ["Voids"] = new List> { new() { ["Index"] = 1, ["Area"] = 65.2, ["AreaPercent"] = 1.30, ["CenterX"] = 120.5, ["CenterY"] = 85.3 }, new() { ["Index"] = 2, ["Area"] = 38.4, ["AreaPercent"] = 0.77, ["CenterX"] = 200.1, ["CenterY"] = 150.8 }, new() { ["Index"] = 3, ["Area"] = 22.2, ["AreaPercent"] = 0.44, ["CenterX"] = 80.0, ["CenterY"] = 220.5 }, new() { ["Index"] = 4, ["Area"] = 15.6, ["AreaPercent"] = 0.31, ["CenterX"] = 310.2, ["CenterY"] = 95.7 }, new() { ["Index"] = 5, ["Area"] = 8.9, ["AreaPercent"] = 0.18, ["CenterX"] = 155.8, ["CenterY"] = 280.1 } } }, AnnotatedImage = LoadMockImage("Void.png") }; } /// /// 创建通孔填锡率处理器模拟数据 | Create via fill rate processor mock data /// private ProcessorOutput CreateFillRateOutput() { return new ProcessorOutput { ProcessorType = "FillRateProcessor", OutputData = new Dictionary { ["FillRate"] = 0.85, ["VoidRate"] = 0.15, ["FullDistance"] = 1.60, ["FillDistance"] = 1.36, ["THTLimit"] = 0.75, ["Classification"] = "Pass", ["E1"] = new Dictionary { ["CenterX"] = 256.0, ["CenterY"] = 256.0, ["SemiAxisA"] = 120.5, ["SemiAxisB"] = 118.2, ["Angle"] = 2.3 }, ["E2"] = new Dictionary { ["CenterX"] = 256.0, ["CenterY"] = 256.0, ["SemiAxisA"] = 95.8, ["SemiAxisB"] = 93.1, ["Angle"] = 2.3 }, ["E3"] = new Dictionary { ["CenterX"] = 256.0, ["CenterY"] = 256.0, ["SemiAxisA"] = 70.2, ["SemiAxisB"] = 68.5, ["Angle"] = 1.8 }, ["E4"] = new Dictionary { ["CenterX"] = 256.0, ["CenterY"] = 256.0, ["SemiAxisA"] = 45.0, ["SemiAxisB"] = 43.7, ["Angle"] = 1.5 } }, AnnotatedImage = LoadMockImage("Void.png") }; } /// /// 加载模拟图像文件 | Load mock image file /// 从指定目录加载图像,文件不存在时返回 null /// Loads image from specified directory, returns null if file not found /// /// 图像文件名 | Image file name /// 图像数据对象或 null | ImageData object or null private ImageData LoadMockImage(string fileName) { var filePath = System.IO.Path.Combine(MockImageDirectory, fileName); if (!File.Exists(filePath)) { _logger.Warn("模拟图像文件不存在:{Path} | Mock image file not found: {Path}", filePath); return null; } return new ImageData { SourceType = ImageSourceType.FilePath, FilePath = filePath }; } #endregion } }