This commit is contained in:
GukSang.Jin
2026-05-05 19:13:53 +08:00
parent d11e1d4270
commit 33c8509df6
4 changed files with 120 additions and 24 deletions

View File

@@ -2,7 +2,7 @@ namespace ConeCalorimeter.Models;
public sealed class ReportInput
{
public string LaboratoryName { get; set; } = "广东安拓普检测中心";
public string LaboratoryName { get; set; } = "检测中心";
public string Operator { get; set; } = string.Empty;

View File

@@ -26,31 +26,54 @@ public sealed class NpoiReportExportService : IReportExportService
"热释放KW/m2",
"EHC",
"损失质量",
"试样温度(℃)"
"试样温度(℃)",
"Timestamp",
"点火计时(s)",
"火焰监测",
"孔板流量",
"辐射锥温度 (℃)",
"当前质量",
"初始质量",
"最大热释放",
"热释放速率180",
"热释放速率300",
"C-系数"
];
public void Export(string outputPath, ReportInput input, IReadOnlyList<RealtimeDataRecord> records)
{
var exportRecords = records.Where(IsValidExperimentRecord).ToList();
if (exportRecords.Count == 0)
{
throw new InvalidOperationException("请先点击“测试开始”,产生有效实验数据后再导出报表。");
}
var templatePath = FindTemplatePath();
using var templateStream = File.OpenRead(templatePath);
var workbook = new HSSFWorkbook(templateStream);
FillReportSheet(workbook.GetSheet("Result_ISO"), input, records);
FillReportSheet(workbook.GetSheet("Result_FTP"), input, records);
FillDataSheet(workbook.GetSheet("Data") ?? workbook.CreateSheet("Data"), records);
FillReportSheet(workbook.GetSheet("Result_ISO"), "Result_ISO", input, exportRecords);
FillReportSheet(workbook.GetSheet("Result_FTP"), "Result_FTP", input, exportRecords);
FillDataSheet(workbook.GetSheet("Data") ?? workbook.CreateSheet("Data"), exportRecords);
using var outputStream = File.Create(outputPath);
workbook.Write(outputStream);
}
private static void FillReportSheet(ISheet? sheet, ReportInput input, IReadOnlyList<RealtimeDataRecord> records)
private static void FillReportSheet(
ISheet? sheet,
string sheetName,
ReportInput input,
IReadOnlyList<RealtimeDataRecord> records)
{
if (sheet is null)
{
return;
throw new InvalidOperationException($"报表模板缺少工作表:{sheetName}");
}
var summary = BuildSummary(records);
ValidateSummary(summary);
SetValueBesideLabel(sheet, "实验室名称", input.LaboratoryName);
SetValueBesideLabel(sheet, "实验员", input.Operator);
SetValueBesideLabel(sheet, "文件名", input.FileName);
@@ -59,7 +82,7 @@ public sealed class NpoiReportExportService : IReportExportService
SetValueBesideLabel(sheet, "材料", input.Material);
SetValueBesideLabel(sheet, "样品", input.SampleName);
SetValueBesideLabel(sheet, "厚度", input.Thickness);
SetValueBesideLabel(sheet, "初始质量", FirstNonEmpty(input.InitialMass, summary.InitialMass));
SetRequiredValueBesideLabel(sheet, "初始质量", FirstNonEmpty(input.InitialMass, summary.InitialMass));
SetValueBesideLabel(sheet, "辐射面积", input.IrradiatedArea);
SetValueBesideLabel(sheet, "热辐射值", input.Irradiance);
SetValueBesideLabel(sheet, "辐射距离", input.IrradianceDistance);
@@ -87,7 +110,7 @@ public sealed class NpoiReportExportService : IReportExportService
SetValueBesideLabel(sheet, "结束标准", input.EndCriteria);
SetValueBesideLabel(sheet, "结束时间", FirstNonEmpty(input.EndTime, summary.EndTime));
SetValueBesideLabel(sheet, "E等价热值", input.EquivalentHeatValue);
SetValueBesideLabel(sheet, "C-系数", FirstNonEmpty(input.CFactor, summary.CFactor));
SetRequiredValueBesideLabel(sheet, "C-系数", FirstNonEmpty(input.CFactor, summary.CFactor));
SetValueBesideLabel(sheet, "光程", input.LightPath);
SetValueBesideLabel(sheet, "O2延迟时间", input.O2DelayTime);
SetValueBesideLabel(sheet, "CO2延迟时间", input.CO2DelayTime);
@@ -98,11 +121,11 @@ public sealed class NpoiReportExportService : IReportExportService
SetValueBesideLabel(sheet, "基线C2氧含量", input.BaselineC2Oxygen);
SetValueBesideLabel(sheet, "基线CO2氧含量", input.BaselineCO2Oxygen);
SetValueBesideLabel(sheet, "总热释放", summary.TotalHeatRelease);
SetValueBesideLabel(sheet, "总产烟量", summary.TotalSmoke);
SetValueBesideLabel(sheet, "质量损失", summary.MassLoss);
SetValueBesideLabel(sheet, "热释放(30)最大", summary.PeakHeatReleaseRate);
SetValueBesideLabel(sheet, "产烟率(30)最大", summary.PeakSmokeProduction);
SetRequiredValueBesideLabel(sheet, "总热释放", summary.TotalHeatRelease);
SetRequiredValueBesideLabel(sheet, "总产烟量", summary.TotalSmoke);
SetRequiredValueBesideLabel(sheet, "质量损失", summary.MassLoss);
SetRequiredValueBesideLabel(sheet, "热释放(30)最大", summary.PeakHeatReleaseRate);
SetRequiredValueBesideLabel(sheet, "产烟率(30)最大", summary.PeakSmokeProduction);
}
private static void FillDataSheet(ISheet sheet, IReadOnlyList<RealtimeDataRecord> records)
@@ -119,6 +142,7 @@ public sealed class NpoiReportExportService : IReportExportService
{
var row = sheet.CreateRow(i + 1);
var record = records[i];
// Keep columns A-O fixed; the Excel template charts reference these positions.
SetNumeric(row, 0, record.TestSeconds >= 0 ? record.TestSeconds : double.NaN);
SetNumeric(row, 1, record.Oxygen);
SetNumeric(row, 2, record.CarbonDioxide);
@@ -134,6 +158,17 @@ public sealed class NpoiReportExportService : IReportExportService
SetNumeric(row, 12, record.EffectiveHeatOfCombustion);
SetNumeric(row, 13, record.MassLoss);
SetNumeric(row, 14, record.SampleTemperature);
row.CreateCell(15).SetCellValue(record.Timestamp.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.CurrentCulture));
SetNumeric(row, 16, record.IgnitionSeconds >= 0 ? record.IgnitionSeconds : double.NaN);
SetNumeric(row, 17, record.FlameDetected ? 1 : 0);
SetNumeric(row, 18, record.OrificeFlow);
SetNumeric(row, 19, record.ConeTemperature);
SetNumeric(row, 20, record.CurrentMass);
SetNumeric(row, 21, record.InitialMass);
SetNumeric(row, 22, record.PeakHeatReleaseRate);
SetNumeric(row, 23, record.Qa180);
SetNumeric(row, 24, record.Qa300);
SetNumeric(row, 25, record.CFactor);
}
for (var i = 0; i < DataHeaders.Length; i++)
@@ -142,11 +177,11 @@ public sealed class NpoiReportExportService : IReportExportService
}
}
private static void SetValueBesideLabel(ISheet sheet, string label, string value)
private static bool SetValueBesideLabel(ISheet sheet, string label, string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return;
return false;
}
for (var rowIndex = sheet.FirstRowNum; rowIndex <= sheet.LastRowNum; rowIndex++)
@@ -173,9 +208,24 @@ public sealed class NpoiReportExportService : IReportExportService
var targetColumn = FindTargetColumn(sheet, rowIndex, columnIndex);
var target = row.GetCell(targetColumn) ?? row.CreateCell(targetColumn);
target.SetCellValue(value);
return;
return true;
}
}
return false;
}
private static void SetRequiredValueBesideLabel(ISheet sheet, string label, string value)
{
if (string.IsNullOrWhiteSpace(value))
{
throw new InvalidOperationException($"缺少关键导出数据:{label}");
}
if (!SetValueBesideLabel(sheet, label, value))
{
throw new InvalidOperationException($"报表模板缺少字段:{label}");
}
}
private static int FindTargetColumn(ISheet sheet, int rowIndex, int columnIndex)
@@ -269,8 +319,28 @@ public sealed class NpoiReportExportService : IReportExportService
MassLoss: FormatWithUnit(LastFinite(records, record => record.MassLoss), "g"),
InitialMass: FormatWithUnit(LastFinite(records, record => record.InitialMass), "g"),
CFactor: FormatValue(LastFinite(records, record => record.CFactor)),
IgnitionTime: ignition is null ? "" : $"{ignition.Value} s",
EndTime: $"{last.TestSeconds} s");
IgnitionTime: FormatSeconds(ignition),
EndTime: FormatSeconds(last.TestSeconds));
}
private static void ValidateSummary(ReportSummary summary)
{
RequireSummaryValue("总热释放", summary.TotalHeatRelease);
RequireSummaryValue("总产烟量", summary.TotalSmoke);
RequireSummaryValue("质量损失", summary.MassLoss);
RequireSummaryValue("初始质量", summary.InitialMass);
RequireSummaryValue("C-系数", summary.CFactor);
RequireSummaryValue("热释放(30)最大", summary.PeakHeatReleaseRate);
RequireSummaryValue("产烟率(30)最大", summary.PeakSmokeProduction);
}
private static void RequireSummaryValue(string label, string value)
{
if (string.IsNullOrWhiteSpace(value))
{
throw new InvalidOperationException(
$"缺少关键导出数据:{label}。请确认已点击测试开始并采集到有效实验数据。");
}
}
private static string FormatWithUnit(double? value, string unit)
@@ -283,6 +353,18 @@ public sealed class NpoiReportExportService : IReportExportService
return value is null || !double.IsFinite(value.Value) ? "" : $"{value.Value:0.00}";
}
private static string FormatSeconds(int? value)
{
return value.HasValue && value.Value >= 0 ? $"{value.Value} s" : "";
}
private static bool IsValidExperimentRecord(RealtimeDataRecord record)
{
return record.TestSeconds >= 0
&& double.IsFinite(record.TotalHeatRelease)
&& double.IsFinite(record.TotalSmoke);
}
private static double? LastFinite(IReadOnlyList<RealtimeDataRecord> records, Func<RealtimeDataRecord, double> selector)
{
for (var i = records.Count - 1; i >= 0; i--)

View File

@@ -59,7 +59,7 @@ public sealed class ReportPageViewModel : PageViewModel
[
new ReportSectionViewModel("报告信息",
[
new ReportFieldViewModel("LaboratoryName", "实验室名称", "广东安拓普检测中心"),
new ReportFieldViewModel("LaboratoryName", "实验室名称", "检测中心"),
new ReportFieldViewModel("Operator", "实验员"),
new ReportFieldViewModel("FileName", "文件名"),
new ReportFieldViewModel("ReportName", "报告名")
@@ -143,14 +143,21 @@ public sealed class ReportPageViewModel : PageViewModel
SummaryItems[2].Value = records.Last().Timestamp.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.CurrentCulture);
var last = records.Last();
SummaryItems[3].Value = FormatWithUnit(last.PeakHeatReleaseRate, "kW/㎡");
SummaryItems[4].Value = FormatWithUnit(last.TotalHeatRelease, "MJ/㎡");
SummaryItems[5].Value = FormatWithUnit(last.TotalSmoke, "m²");
SummaryItems[6].Value = FormatWithUnit(last.MassLoss, "g");
SummaryItems[4].Value = FormatWithUnit(LastFinite(records, record => record.TotalHeatRelease), "MJ/㎡");
SummaryItems[5].Value = FormatWithUnit(LastFinite(records, record => record.TotalSmoke), "m²");
SummaryItems[6].Value = FormatWithUnit(LastFinite(records, record => record.MassLoss), "g");
FillCollectedReportFields(records);
}
private void ExportReport()
{
var records = _experimentDataService.Records.ToList();
if (!records.Any(IsValidExperimentRecord))
{
StatusText = "请先点击“测试开始”,产生有效实验数据后再导出报表";
return;
}
var input = BuildInput();
var defaultName = BuildDefaultFileName(input);
var dialog = new SaveFileDialog
@@ -171,7 +178,7 @@ public sealed class ReportPageViewModel : PageViewModel
try
{
_reportExportService.Export(dialog.FileName, input, _experimentDataService.Records.ToList());
_reportExportService.Export(dialog.FileName, input, records);
StatusText = $"已导出:{dialog.FileName}";
}
catch (Exception ex)
@@ -262,4 +269,11 @@ public sealed class ReportPageViewModel : PageViewModel
{
return double.IsFinite(value) ? $"{value:0.00}" : string.Empty;
}
private static bool IsValidExperimentRecord(RealtimeDataRecord record)
{
return record.TestSeconds >= 0
&& double.IsFinite(record.TotalHeatRelease)
&& double.IsFinite(record.TotalSmoke);
}
}

Binary file not shown.