SSPC-Tester/SSPCTester.Logic/Report/ReportGenerator.cs
2026-07-02 17:43:32 +08:00

305 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using SSPCTester.Logic.Models;
namespace SSPCTester.Logic.Report;
/// <summary>
/// 用 QuestPDF 生成测试报告 PDF。按 Project.Report 勾选拼装章节。
/// </summary>
internal sealed class LegacyReportGenerator
{
static LegacyReportGenerator()
{
QuestPDF.Settings.License = LicenseType.Community;
}
/// <summary>外部传入曲线图 PNG键 = 通道号。</summary>
public Dictionary<int, CurveReportImages> 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.11.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<string> 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
};
}