305 lines
12 KiB
C#
305 lines
12 KiB
C#
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.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<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
|
||
};
|
||
}
|