using QuestPDF.Fluent; using QuestPDF.Helpers; using QuestPDF.Infrastructure; using SSPCTester.Logic.Models; namespace SSPCTester.Logic.Report; /// /// 用 QuestPDF 生成测试报告 PDF。按 Project.Report 勾选拼装章节。 /// internal sealed class LegacyReportGenerator { static LegacyReportGenerator() { QuestPDF.Settings.License = LicenseType.Community; } /// 外部传入曲线图 PNG,键 = 通道号。 public Dictionary CurveImages { get; set; } = new(); public void Generate(Project project, string outputPath) { Directory.CreateDirectory(Path.GetDirectoryName(outputPath)!); var sel = project.Report; Document.Create(container => { container.Page(page => { page.Size(PageSizes.A4); page.Margin(2, Unit.Centimetre); page.DefaultTextStyle(t => t.FontFamily("Microsoft YaHei").FontSize(10)); page.Header().Element(h => Header(h, project)); page.Content().Element(c => Content(c, project, sel)); page.Footer().AlignCenter().Text(t => { t.Span("第 "); t.CurrentPageNumber(); t.Span(" / "); t.TotalPages(); t.Span(" 页"); }); }); }).GeneratePdf(outputPath); } private static void Header(IContainer h, Project p) { h.Column(col => { col.Item().Text($"固态功率控制器动态参数检测报告").FontSize(16).Bold(); col.Item().Text($"项目:{p.Name} 生成时间:{DateTime.Now:yyyy-MM-dd HH:mm}").FontSize(9).FontColor(Colors.Grey.Darken2); col.Item().PaddingTop(4).LineHorizontal(0.5f).LineColor(Colors.Grey.Lighten2); }); } private void Content(IContainer c, Project p, ReportSelection sel) { c.Column(col => { col.Spacing(14); if (sel.Cover) { Section(col, "1. 被测件信息"); col.Item().Element(e => DutTable(e, p)); Section(col, "2. 测试信息"); col.Item().Element(e => SiteTable(e, p)); Section(col, "3. 综合判定"); col.Item().Element(e => OverallBadge(e, p.OverallStatus)); } if (sel.Basic && (p.BasicResults.Count > 0 || HasManualNote(p.TestFunction?.Basic))) { Section(col, "4. 基础测试结果(24 路)"); if (p.BasicResults.Count > 0) col.Item().Element(e => BasicTable(e, p)); ManualNote(col, p.TestFunction?.Basic); } if (sel.Curve && (p.CurveResults.Count > 0 || HasManualNote(p.TestFunction?.Curve))) { Section(col, "5. 开启与关断曲线(1.1–1.6)"); if (p.CurveResults.Count > 0) col.Item().Element(e => CurveTable(e, p)); ManualNote(col, p.TestFunction?.Curve); foreach (var (ch, images) in CurveImages.OrderBy(x => x.Key)) { AddCurveImage(col, ch, "ON", images.Rising); AddCurveImage(col, ch, "OFF", images.Falling); } } if (sel.Power && (p.PowerResults.Count > 0 || HasManualNote(p.TestFunction?.Power))) { Section(col, "6. 功率损耗"); if (p.PowerResults.Count > 0) col.Item().Element(e => PowerTable(e, p)); ManualNote(col, p.TestFunction?.Power); } if (sel.Protect && (p.ProtectResults.Count > 0 || HasManualNote(p.TestFunction?.Protect))) { Section(col, "7. 保护功能(框架占位)"); if (p.ProtectResults.Count > 0) col.Item().Element(e => ProtectTable(e, p)); ManualNote(col, p.TestFunction?.Protect); } }); } private static void Section(ColumnDescriptor col, string title) { col.Item().PaddingTop(6).Text(title).FontSize(13).Bold().FontColor(Colors.Blue.Darken2); } private static void AddCurveImage(ColumnDescriptor column, int channel, string phaseLabel, byte[]? png) { if (png == null || png.Length == 0) return; column.Item().PaddingTop(6).Text($"CH{channel:00} {phaseLabel} Curve").Bold(); column.Item().Image(png).FitWidth(); } private static bool HasManualNote(string? text) => !string.IsNullOrWhiteSpace(text); private static void ManualNote(ColumnDescriptor col, string? note) { if (!HasManualNote(note)) return; col.Item().PaddingTop(8).Text("测试说明").FontSize(11).Bold(); col.Item() .Border(1) .BorderColor(Colors.Grey.Lighten2) .Background(Colors.Grey.Lighten4) .Padding(6) .Column(lines => { foreach (var line in NormalizeLines(note!)) lines.Item().Text(string.IsNullOrEmpty(line) ? " " : line).FontSize(9); }); } private static IEnumerable NormalizeLines(string text) => text.Replace("\r\n", "\n").Replace('\r', '\n').Split('\n'); private static void DutTable(IContainer c, Project p) { c.Table(t => { t.ColumnsDefinition(cd => { cd.ConstantColumn(120); cd.RelativeColumn(); }); Row(t, "设备编号", p.Dut.SerialNumber); Row(t, "设备名称", p.Dut.ProductName); Row(t, "设备型号", p.Dut.Model); Row(t, "生产厂家", p.Dut.Manufacturer); }); } private static void SiteTable(IContainer c, Project p) { c.Table(t => { t.ColumnsDefinition(cd => { cd.ConstantColumn(120); cd.RelativeColumn(); }); Row(t, "测试人员", p.Site.Tester); Row(t, "测试日期", p.Site.TestDate.ToString("yyyy-MM-dd")); Row(t, "环境", $"温度 {p.Site.TemperatureC}℃ 湿度 {p.Site.HumidityPct}% 气压 {p.Site.PressureKpa}kPa"); Row(t, "备注", p.Site.Remark); }); } private static void Row(TableDescriptor t, string k, string? v) { t.Cell().Background(Colors.Grey.Lighten4).Padding(4).Text(k).SemiBold(); t.Cell().Padding(4).Text(v ?? ""); } private static void OverallBadge(IContainer c, TestStatus s) { (string text, string color) = s switch { TestStatus.Pass => ("综合判定:合格", Colors.Green.Darken1), TestStatus.Fail => ("综合判定:不合格", Colors.Red.Darken1), TestStatus.Running => ("综合判定:测试中", Colors.Blue.Darken1), TestStatus.Measured => ("综合判定:已测(无性能判据)", Colors.Blue.Darken1), _ => ("综合判定:未测试", Colors.Grey.Darken1), }; c.Padding(6).Background(color).Padding(10).Text(text).FontColor(Colors.White).FontSize(14).Bold(); } private static void BasicTable(IContainer c, Project p) { c.Table(t => { t.ColumnsDefinition(cd => { cd.ConstantColumn(60); cd.RelativeColumn(); cd.RelativeColumn(); cd.RelativeColumn(); cd.ConstantColumn(70); }); HeaderRow(t, "通道", "电压(V)", "电流(A)", "关后电压(V)", "结果"); foreach (var r in p.BasicResults) { t.Cell().Padding(3).Text($"CH{r.Channel}"); t.Cell().Padding(3).Text(r.VoltageOn.ToString("F2")); t.Cell().Padding(3).Text(r.CurrentOn.ToString("F3")); t.Cell().Padding(3).Text(r.VoltageOff.ToString("F2")); t.Cell().Padding(3).Text(StatusText(r.Status)).FontColor(StatusColor(r.Status)); } }); } private static void CurveTable(IContainer c, Project p) { c.Table(t => { t.ColumnsDefinition(cd => { cd.ConstantColumn(50); cd.RelativeColumn(); cd.RelativeColumn(); cd.RelativeColumn(); cd.RelativeColumn(); cd.ConstantColumn(60); }); HeaderRow(t, "通道", "开启(ms)", "上升(ms)", "关断(ms)", "下降(ms)", "结果"); foreach (var r in p.CurveResults) { t.Cell().Padding(3).Text($"CH{r.Channel}"); t.Cell().Padding(3).Text(FormatMs(r.OnTimeMs)); t.Cell().Padding(3).Text(FormatMs(r.RiseTimeMs)); t.Cell().Padding(3).Text(FormatMs(r.OffTimeMs)); t.Cell().Padding(3).Text(FormatMs(r.FallTimeMs)); t.Cell().Padding(3).Text(StatusText(r.Status)).FontColor(StatusColor(r.Status)); } }); } private static void PowerTable(IContainer c, Project p) { c.Table(t => { t.ColumnsDefinition(cd => { cd.ConstantColumn(50); cd.RelativeColumn(); cd.RelativeColumn(); cd.RelativeColumn(); cd.RelativeColumn(); cd.RelativeColumn(); cd.RelativeColumn(); cd.ConstantColumn(60); }); HeaderRow(t, "通道", "Vin(V)", "Iin(A)", "Vout(V)", "Iout(A)", "损耗(W)", "效率(%)", "状态"); foreach (var r in p.PowerResults) { t.Cell().Padding(3).Text($"CH{r.Channel}"); t.Cell().Padding(3).Text(r.Vin.ToString("F3")); t.Cell().Padding(3).Text(r.Iin.ToString("F3")); t.Cell().Padding(3).Text(r.Vout.ToString("F3")); t.Cell().Padding(3).Text(r.Iout.ToString("F3")); t.Cell().Padding(3).Text(r.PowerLossW.ToString("F3")); t.Cell().Padding(3).Text(r.EfficiencyPct.ToString("F3")); t.Cell().Padding(3).Text(StatusText(r.Status)).FontColor(StatusColor(r.Status)); } }); } private static void ProtectTable(IContainer c, Project p) { c.Table(t => { t.ColumnsDefinition(cd => { cd.ConstantColumn(40); cd.RelativeColumn(); cd.RelativeColumn(); cd.RelativeColumn(); cd.ConstantColumn(70); cd.ConstantColumn(60); }); HeaderRow(t, "序号", "保护类型", "触发条件", "实测值", "判别模式", "判别结果"); foreach (var r in p.ProtectResults) { t.Cell().Padding(3).Text(r.Index.ToString()); t.Cell().Padding(3).Text(r.ProtectType); t.Cell().Padding(3).Text(r.Trigger); t.Cell().Padding(3).Text(r.MeasuredValue); t.Cell().Padding(3).Text(r.JudgeMode); t.Cell().Padding(3).Text(StatusText(r.Status)).FontColor(StatusColor(r.Status)); } }); } private static void HeaderRow(TableDescriptor t, params string[] cols) { foreach (var col in cols) t.Cell().Background(Colors.Grey.Lighten3).Padding(4).Text(col).SemiBold(); } private static string FormatMs(double v) => double.IsNaN(v) ? "—" : v.ToString("F3"); private static string StatusText(TestStatus s) => s switch { TestStatus.Pass => "合格", TestStatus.Fail => "不合格", TestStatus.Running => "测试中", TestStatus.Measured => "已测", _ => "未测试" }; private static string StatusColor(TestStatus s) => s switch { TestStatus.Pass => Colors.Green.Darken1, TestStatus.Fail => Colors.Red.Darken1, TestStatus.Running => Colors.Blue.Darken1, TestStatus.Measured => Colors.Blue.Darken1, _ => Colors.Grey.Darken1 }; }