更新
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
@@ -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--)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
BIN
ConeCalorimeter/纯POE-EVA-样12603271445.xls
Normal file
BIN
ConeCalorimeter/纯POE-EVA-样12603271445.xls
Normal file
Binary file not shown.
Reference in New Issue
Block a user