更新3/26
This commit is contained in:
@@ -2,6 +2,7 @@ using System.Windows;
|
|||||||
using Cardiopulmonarybypasssystems.Services;
|
using Cardiopulmonarybypasssystems.Services;
|
||||||
using Cardiopulmonarybypasssystems.ViewModels;
|
using Cardiopulmonarybypasssystems.ViewModels;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using OfficeOpenXml;
|
||||||
using QuestPDF.Infrastructure;
|
using QuestPDF.Infrastructure;
|
||||||
|
|
||||||
namespace Cardiopulmonarybypasssystems;
|
namespace Cardiopulmonarybypasssystems;
|
||||||
@@ -13,6 +14,7 @@ public partial class App : Application
|
|||||||
protected override void OnStartup(StartupEventArgs e)
|
protected override void OnStartup(StartupEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnStartup(e);
|
base.OnStartup(e);
|
||||||
|
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
|
||||||
QuestPDF.Settings.License = LicenseType.Community;
|
QuestPDF.Settings.License = LicenseType.Community;
|
||||||
|
|
||||||
var services = new ServiceCollection();
|
var services = new ServiceCollection();
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||||
|
<PackageReference Include="EPPlus" Version="7.5.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
||||||
<PackageReference Include="NModbus" Version="3.0.81" />
|
<PackageReference Include="NModbus" Version="3.0.81" />
|
||||||
<PackageReference Include="QuestPDF" Version="2024.12.1" />
|
<PackageReference Include="QuestPDF" Version="2024.12.1" />
|
||||||
|
|||||||
435
Cardiopulmonarybypasssystems/Services/ExcelReportDocument.cs
Normal file
435
Cardiopulmonarybypasssystems/Services/ExcelReportDocument.cs
Normal file
@@ -0,0 +1,435 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Drawing;
|
||||||
|
using Cardiopulmonarybypasssystems.Models;
|
||||||
|
using OfficeOpenXml;
|
||||||
|
using OfficeOpenXml.Style;
|
||||||
|
|
||||||
|
namespace Cardiopulmonarybypasssystems.Services;
|
||||||
|
|
||||||
|
public sealed class ExcelReportDocument(
|
||||||
|
string pageTitle,
|
||||||
|
string batchNumber,
|
||||||
|
string currentStage,
|
||||||
|
string operatorName,
|
||||||
|
string reviewerName,
|
||||||
|
string approverName,
|
||||||
|
string complianceDisplay,
|
||||||
|
string deltaPressureDisplay,
|
||||||
|
string detectionSummary,
|
||||||
|
string configurationSummary,
|
||||||
|
DateTime exportTime,
|
||||||
|
IReadOnlyCollection<InspectionItem> inspectionItems,
|
||||||
|
IReadOnlyCollection<TraceEvent> traceEvents,
|
||||||
|
IReadOnlyCollection<KinkResistancePointEntry> kinkResistanceEntries,
|
||||||
|
string kinkResistanceFlowPointDisplay,
|
||||||
|
string kinkResistanceMandrelDiameterDisplay,
|
||||||
|
IReadOnlyCollection<PressureDropPointEntry> pressureDropEntries,
|
||||||
|
string pressureDropLimitDisplay,
|
||||||
|
string antiCollapseBaselineDisplay,
|
||||||
|
string antiCollapseComparisonDisplay,
|
||||||
|
string antiCollapseCurrentNegativePressure,
|
||||||
|
string antiCollapseCurrentFlowDisplay,
|
||||||
|
string antiCollapseAllowedIncreaseRateDisplay,
|
||||||
|
IReadOnlyCollection<RecirculationPointEntry> recirculationEntries,
|
||||||
|
string recirculationLimitDisplay)
|
||||||
|
{
|
||||||
|
private const int TotalColumns = 10;
|
||||||
|
private static readonly Color BorderColor = ColorTranslator.FromHtml("#A0AEC0");
|
||||||
|
private static readonly Color SectionFillColor = ColorTranslator.FromHtml("#DDEFEA");
|
||||||
|
private static readonly Color HeaderFillColor = ColorTranslator.FromHtml("#CFE8E3");
|
||||||
|
private static readonly Color SummaryFillColor = ColorTranslator.FromHtml("#D9F3EE");
|
||||||
|
private static readonly Color MetaLabelFillColor = ColorTranslator.FromHtml("#F7FAFC");
|
||||||
|
private static readonly Color MetaLabelFontColor = ColorTranslator.FromHtml("#4A5568");
|
||||||
|
|
||||||
|
public void GenerateExcel(string filePath)
|
||||||
|
{
|
||||||
|
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
|
||||||
|
|
||||||
|
using var package = new ExcelPackage();
|
||||||
|
var worksheet = package.Workbook.Worksheets.Add("检查报告");
|
||||||
|
package.Workbook.Properties.Title = pageTitle;
|
||||||
|
|
||||||
|
ConfigureWorksheet(worksheet);
|
||||||
|
|
||||||
|
var row = 1;
|
||||||
|
row = ComposeTitle(worksheet, row);
|
||||||
|
row = ComposeMeta(worksheet, row + 2);
|
||||||
|
row = ComposeSummary(worksheet, row + 2);
|
||||||
|
row = ComposeKinkResistanceSection(worksheet, row + 2);
|
||||||
|
row = ComposePressureDropSection(worksheet, row + 2);
|
||||||
|
row = ComposeAntiCollapseSection(worksheet, row + 2);
|
||||||
|
row = ComposeRecirculationSection(worksheet, row + 2);
|
||||||
|
row = ComposeInspectionTable(worksheet, row + 2);
|
||||||
|
row = ComposeTraceEvents(worksheet, row + 2);
|
||||||
|
ComposeSignatures(worksheet, row + 2);
|
||||||
|
|
||||||
|
package.SaveAs(new FileInfo(filePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureWorksheet(ExcelWorksheet worksheet)
|
||||||
|
{
|
||||||
|
worksheet.View.ShowGridLines = false;
|
||||||
|
worksheet.PrinterSettings.Orientation = eOrientation.Landscape;
|
||||||
|
worksheet.PrinterSettings.PaperSize = ePaperSize.A4;
|
||||||
|
worksheet.PrinterSettings.FitToPage = true;
|
||||||
|
worksheet.PrinterSettings.FitToWidth = 1;
|
||||||
|
worksheet.PrinterSettings.FitToHeight = 0;
|
||||||
|
worksheet.PrinterSettings.LeftMargin = 0.35m;
|
||||||
|
worksheet.PrinterSettings.RightMargin = 0.35m;
|
||||||
|
worksheet.PrinterSettings.TopMargin = 0.4m;
|
||||||
|
worksheet.PrinterSettings.BottomMargin = 0.4m;
|
||||||
|
worksheet.Cells.Style.Font.Name = "Microsoft YaHei";
|
||||||
|
worksheet.Cells.Style.Font.Size = 9;
|
||||||
|
worksheet.Cells.Style.WrapText = true;
|
||||||
|
worksheet.Cells.Style.VerticalAlignment = ExcelVerticalAlignment.Top;
|
||||||
|
|
||||||
|
var widths = new[] { 12d, 14d, 22d, 24d, 20d, 16d, 10d, 12d, 16d, 18d };
|
||||||
|
for (var index = 0; index < widths.Length; index++)
|
||||||
|
{
|
||||||
|
worksheet.Column(index + 1).Width = widths[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int ComposeTitle(ExcelWorksheet worksheet, int row)
|
||||||
|
{
|
||||||
|
var range = worksheet.Cells[row, 1, row, TotalColumns];
|
||||||
|
range.Merge = true;
|
||||||
|
range.Value = ValueOrFallback(pageTitle);
|
||||||
|
range.Style.Font.Size = 18;
|
||||||
|
range.Style.Font.Bold = true;
|
||||||
|
range.Style.Font.Color.SetColor(ColorTranslator.FromHtml("#0F766E"));
|
||||||
|
range.Style.HorizontalAlignment = ExcelHorizontalAlignment.Left;
|
||||||
|
worksheet.Row(row).Height = 26;
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int ComposeMeta(ExcelWorksheet worksheet, int startRow)
|
||||||
|
{
|
||||||
|
var items = new (string Label, string Value, int ColStart, int ColEnd)[]
|
||||||
|
{
|
||||||
|
("批次", batchNumber, 1, 2),
|
||||||
|
("阶段", currentStage, 3, 4),
|
||||||
|
("测试员", operatorName, 5, 6),
|
||||||
|
("复核人", reviewerName, 7, 8),
|
||||||
|
("批准人", approverName, 1, 2),
|
||||||
|
("导出时间", exportTime.ToString("yyyy-MM-dd HH:mm:ss"), 3, 4),
|
||||||
|
("合格率", complianceDisplay, 5, 6),
|
||||||
|
("压差", deltaPressureDisplay, 7, 8)
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var index = 0; index < items.Length; index++)
|
||||||
|
{
|
||||||
|
var rowOffset = index < 4 ? 0 : 2;
|
||||||
|
WriteMetaCell(
|
||||||
|
worksheet,
|
||||||
|
startRow + rowOffset,
|
||||||
|
startRow + rowOffset + 1,
|
||||||
|
items[index].ColStart,
|
||||||
|
items[index].ColEnd,
|
||||||
|
items[index].Label,
|
||||||
|
items[index].Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return startRow + 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int ComposeSummary(ExcelWorksheet worksheet, int startRow)
|
||||||
|
{
|
||||||
|
var titleRange = worksheet.Cells[startRow, 1, startRow, TotalColumns];
|
||||||
|
titleRange.Merge = true;
|
||||||
|
titleRange.Value = "检测结论";
|
||||||
|
titleRange.Style.Font.Bold = true;
|
||||||
|
titleRange.Style.Font.Size = 11;
|
||||||
|
titleRange.Style.Fill.PatternType = ExcelFillStyle.Solid;
|
||||||
|
titleRange.Style.Fill.BackgroundColor.SetColor(SummaryFillColor);
|
||||||
|
ApplyBorder(titleRange);
|
||||||
|
|
||||||
|
var summaryRange = worksheet.Cells[startRow + 1, 1, startRow + 1, TotalColumns];
|
||||||
|
summaryRange.Merge = true;
|
||||||
|
summaryRange.Value = ValueOrFallback(detectionSummary);
|
||||||
|
ApplyBodyCell(summaryRange);
|
||||||
|
|
||||||
|
var configRange = worksheet.Cells[startRow + 2, 1, startRow + 2, TotalColumns];
|
||||||
|
configRange.Merge = true;
|
||||||
|
configRange.Value = $"产品配置:{ValueOrFallback(configurationSummary)}";
|
||||||
|
ApplyBodyCell(configRange);
|
||||||
|
|
||||||
|
return startRow + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int ComposeKinkResistanceSection(ExcelWorksheet worksheet, int startRow)
|
||||||
|
{
|
||||||
|
var row = WriteSectionTitle(worksheet, startRow, "专项记录:抗扭结抗性");
|
||||||
|
row = WriteMergedBodyRow(worksheet, row + 1, kinkResistanceFlowPointDisplay);
|
||||||
|
row = WriteMergedBodyRow(worksheet, row + 1, kinkResistanceMandrelDiameterDisplay);
|
||||||
|
|
||||||
|
var headers = new[] { "流量点", "目标流量", "L0", "L1", "降幅", "L0时间", "L1时间" };
|
||||||
|
var rows = kinkResistanceEntries.Select(entry => new[]
|
||||||
|
{
|
||||||
|
entry.Label,
|
||||||
|
$"{entry.TargetFlow:F2} L/min",
|
||||||
|
entry.HasBaseline ? $"{entry.BaselineFlow:F2} L/min" : "-",
|
||||||
|
entry.HasKinked ? $"{entry.KinkedFlow:F2} L/min" : "-",
|
||||||
|
entry.FlowDropRateText,
|
||||||
|
entry.BaselineCapturedAtText,
|
||||||
|
entry.KinkedCapturedAtText
|
||||||
|
});
|
||||||
|
|
||||||
|
return WriteTable(worksheet, row + 1, headers, rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int ComposePressureDropSection(ExcelWorksheet worksheet, int startRow)
|
||||||
|
{
|
||||||
|
var row = WriteSectionTitle(worksheet, startRow, "专项记录:压力降");
|
||||||
|
row = WriteMergedBodyRow(worksheet, row + 1, $"声明限值:{pressureDropLimitDisplay}");
|
||||||
|
|
||||||
|
var headers = new[] { "流量点", "目标流量", "实际主泵", "近端压力", "远端压力", "ΔP" };
|
||||||
|
var rows = pressureDropEntries
|
||||||
|
.OrderBy(item => item.TargetFlow)
|
||||||
|
.Select(entry => new[]
|
||||||
|
{
|
||||||
|
entry.Label,
|
||||||
|
$"{entry.TargetFlow:F2} L/min",
|
||||||
|
entry.HasSample ? $"{entry.ActualPumpFlow:F2} L/min" : "-",
|
||||||
|
entry.HasSample ? $"{entry.ProximalPressure:F1} mmHg" : "-",
|
||||||
|
entry.HasSample ? $"{entry.DistalPressure:F1} mmHg" : "-",
|
||||||
|
entry.HasSample ? $"{entry.DeltaPressure:F1} mmHg" : "-"
|
||||||
|
});
|
||||||
|
|
||||||
|
return WriteTable(worksheet, row + 1, headers, rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int ComposeAntiCollapseSection(ExcelWorksheet worksheet, int startRow)
|
||||||
|
{
|
||||||
|
var row = WriteSectionTitle(worksheet, startRow, "专项记录:抗塌陷");
|
||||||
|
var entries = new (string Label, string Value)[]
|
||||||
|
{
|
||||||
|
("基线", antiCollapseBaselineDisplay),
|
||||||
|
("允许增幅", antiCollapseAllowedIncreaseRateDisplay),
|
||||||
|
("负压状态", antiCollapseCurrentNegativePressure),
|
||||||
|
("当前流量", antiCollapseCurrentFlowDisplay),
|
||||||
|
("比较结果", antiCollapseComparisonDisplay)
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var entry in entries)
|
||||||
|
{
|
||||||
|
row++;
|
||||||
|
WriteKeyValueRow(worksheet, row, entry.Label, entry.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int ComposeRecirculationSection(ExcelWorksheet worksheet, int startRow)
|
||||||
|
{
|
||||||
|
var row = WriteSectionTitle(worksheet, startRow, "专项记录:再循环");
|
||||||
|
row = WriteMergedBodyRow(worksheet, row + 1, recirculationLimitDisplay);
|
||||||
|
|
||||||
|
var headers = new[] { "流量点", "目标流量", "实际主泵", "C1(D)", "C2(C)", "R%", "在线参考", "时间" };
|
||||||
|
var rows = recirculationEntries
|
||||||
|
.OrderBy(item => item.TargetFlow)
|
||||||
|
.Select(entry => new[]
|
||||||
|
{
|
||||||
|
entry.Label,
|
||||||
|
$"{entry.TargetFlow:F2} L/min",
|
||||||
|
entry.HasSample ? $"{entry.ActualPumpFlow:F2} L/min" : "-",
|
||||||
|
entry.ConcentrationC1?.ToString("F0") ?? "-",
|
||||||
|
entry.ConcentrationC2?.ToString("F0") ?? "-",
|
||||||
|
entry.RecirculationResultText,
|
||||||
|
entry.HasSample ? $"{entry.OnlineEstimate:F1}%" : "-",
|
||||||
|
entry.SampledAtText
|
||||||
|
});
|
||||||
|
|
||||||
|
return WriteTable(worksheet, row + 1, headers, rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int ComposeInspectionTable(ExcelWorksheet worksheet, int startRow)
|
||||||
|
{
|
||||||
|
var row = WriteSectionTitle(worksheet, startRow, "项目记录");
|
||||||
|
var headers = new[] { "类别", "测试内容", "判定要求", "检测方法", "记录要点", "结果", "状态", "记录人", "记录时间", "备注" };
|
||||||
|
var rows = inspectionItems.Select(item => new[]
|
||||||
|
{
|
||||||
|
item.Category,
|
||||||
|
item.Item,
|
||||||
|
item.AcceptanceCriteria,
|
||||||
|
item.TestMethod,
|
||||||
|
item.RecordFocus,
|
||||||
|
item.Measured,
|
||||||
|
item.StatusText,
|
||||||
|
item.RecordedBy,
|
||||||
|
item.RecordedAtText,
|
||||||
|
item.Notes
|
||||||
|
});
|
||||||
|
|
||||||
|
return WriteTable(worksheet, row + 1, headers, rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int ComposeTraceEvents(ExcelWorksheet worksheet, int startRow)
|
||||||
|
{
|
||||||
|
var row = WriteSectionTitle(worksheet, startRow, "追溯事件");
|
||||||
|
var orderedTraceEvents = traceEvents.OrderBy(item => item.Timestamp).ToList();
|
||||||
|
|
||||||
|
if (orderedTraceEvents.Count == 0)
|
||||||
|
{
|
||||||
|
return WriteMergedBodyRow(worksheet, row + 1, "-");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var trace in orderedTraceEvents)
|
||||||
|
{
|
||||||
|
row++;
|
||||||
|
var range = worksheet.Cells[row, 1, row, TotalColumns];
|
||||||
|
range.Merge = true;
|
||||||
|
range.Value = $"{trace.Timestamp:yyyy-MM-dd HH:mm:ss} {ValueOrFallback(trace.Stage)} {ValueOrFallback(trace.Detail)} ({ValueOrFallback(trace.Operator)})";
|
||||||
|
range.Style.Border.Bottom.Style = ExcelBorderStyle.Thin;
|
||||||
|
range.Style.Border.Bottom.Color.SetColor(ColorTranslator.FromHtml("#CBD5E0"));
|
||||||
|
range.Style.HorizontalAlignment = ExcelHorizontalAlignment.Left;
|
||||||
|
range.Style.VerticalAlignment = ExcelVerticalAlignment.Top;
|
||||||
|
}
|
||||||
|
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ComposeSignatures(ExcelWorksheet worksheet, int startRow)
|
||||||
|
{
|
||||||
|
WriteSignatureCell(worksheet, startRow, 1, 3, "测试员", operatorName);
|
||||||
|
WriteSignatureCell(worksheet, startRow, 4, 6, "复核人", reviewerName);
|
||||||
|
WriteSignatureCell(worksheet, startRow, 7, 10, "批准人", approverName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteMetaCell(
|
||||||
|
ExcelWorksheet worksheet,
|
||||||
|
int labelRow,
|
||||||
|
int valueRow,
|
||||||
|
int startColumn,
|
||||||
|
int endColumn,
|
||||||
|
string label,
|
||||||
|
string value)
|
||||||
|
{
|
||||||
|
var labelRange = worksheet.Cells[labelRow, startColumn, labelRow, endColumn];
|
||||||
|
labelRange.Merge = true;
|
||||||
|
labelRange.Value = label;
|
||||||
|
labelRange.Style.Font.Size = 8;
|
||||||
|
labelRange.Style.Font.Color.SetColor(MetaLabelFontColor);
|
||||||
|
labelRange.Style.Fill.PatternType = ExcelFillStyle.Solid;
|
||||||
|
labelRange.Style.Fill.BackgroundColor.SetColor(MetaLabelFillColor);
|
||||||
|
ApplyBorder(labelRange);
|
||||||
|
|
||||||
|
var valueRange = worksheet.Cells[valueRow, startColumn, valueRow, endColumn];
|
||||||
|
valueRange.Merge = true;
|
||||||
|
valueRange.Value = ValueOrFallback(value);
|
||||||
|
valueRange.Style.Font.Bold = true;
|
||||||
|
ApplyBodyCell(valueRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int WriteSectionTitle(ExcelWorksheet worksheet, int row, string title)
|
||||||
|
{
|
||||||
|
var range = worksheet.Cells[row, 1, row, TotalColumns];
|
||||||
|
range.Merge = true;
|
||||||
|
range.Value = title;
|
||||||
|
range.Style.Font.Bold = true;
|
||||||
|
range.Style.Font.Size = 11;
|
||||||
|
range.Style.Fill.PatternType = ExcelFillStyle.Solid;
|
||||||
|
range.Style.Fill.BackgroundColor.SetColor(SectionFillColor);
|
||||||
|
ApplyBorder(range);
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int WriteMergedBodyRow(ExcelWorksheet worksheet, int row, string value)
|
||||||
|
{
|
||||||
|
var range = worksheet.Cells[row, 1, row, TotalColumns];
|
||||||
|
range.Merge = true;
|
||||||
|
range.Value = ValueOrFallback(value);
|
||||||
|
ApplyBodyCell(range);
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteKeyValueRow(ExcelWorksheet worksheet, int row, string label, string value)
|
||||||
|
{
|
||||||
|
var labelRange = worksheet.Cells[row, 1, row, 2];
|
||||||
|
labelRange.Merge = true;
|
||||||
|
labelRange.Value = label;
|
||||||
|
labelRange.Style.Font.Bold = true;
|
||||||
|
labelRange.Style.Fill.PatternType = ExcelFillStyle.Solid;
|
||||||
|
labelRange.Style.Fill.BackgroundColor.SetColor(HeaderFillColor);
|
||||||
|
ApplyBorder(labelRange);
|
||||||
|
|
||||||
|
var valueRange = worksheet.Cells[row, 3, row, TotalColumns];
|
||||||
|
valueRange.Merge = true;
|
||||||
|
valueRange.Value = ValueOrFallback(value);
|
||||||
|
ApplyBodyCell(valueRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int WriteTable(
|
||||||
|
ExcelWorksheet worksheet,
|
||||||
|
int startRow,
|
||||||
|
IReadOnlyList<string> headers,
|
||||||
|
IEnumerable<string[]> rows)
|
||||||
|
{
|
||||||
|
for (var index = 0; index < headers.Count; index++)
|
||||||
|
{
|
||||||
|
var cell = worksheet.Cells[startRow, index + 1];
|
||||||
|
cell.Value = headers[index];
|
||||||
|
cell.Style.Font.Bold = true;
|
||||||
|
cell.Style.Fill.PatternType = ExcelFillStyle.Solid;
|
||||||
|
cell.Style.Fill.BackgroundColor.SetColor(HeaderFillColor);
|
||||||
|
cell.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
|
||||||
|
cell.Style.VerticalAlignment = ExcelVerticalAlignment.Center;
|
||||||
|
ApplyBorder(cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentRow = startRow;
|
||||||
|
foreach (var row in rows)
|
||||||
|
{
|
||||||
|
currentRow++;
|
||||||
|
for (var index = 0; index < headers.Count; index++)
|
||||||
|
{
|
||||||
|
var cell = worksheet.Cells[currentRow, index + 1];
|
||||||
|
cell.Value = ValueOrFallback(row[index]);
|
||||||
|
ApplyBodyCell(cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteSignatureCell(
|
||||||
|
ExcelWorksheet worksheet,
|
||||||
|
int startRow,
|
||||||
|
int startColumn,
|
||||||
|
int endColumn,
|
||||||
|
string label,
|
||||||
|
string value)
|
||||||
|
{
|
||||||
|
var lineRange = worksheet.Cells[startRow, startColumn, startRow, endColumn];
|
||||||
|
lineRange.Merge = true;
|
||||||
|
lineRange.Style.Border.Top.Style = ExcelBorderStyle.Thin;
|
||||||
|
lineRange.Style.Border.Top.Color.SetColor(Color.Black);
|
||||||
|
|
||||||
|
var textRange = worksheet.Cells[startRow + 1, startColumn, startRow + 1, endColumn];
|
||||||
|
textRange.Merge = true;
|
||||||
|
textRange.Value = $"{label}:{ValueOrFallback(value)}";
|
||||||
|
textRange.Style.HorizontalAlignment = ExcelHorizontalAlignment.Left;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplyBodyCell(ExcelRange range)
|
||||||
|
{
|
||||||
|
range.Style.HorizontalAlignment = ExcelHorizontalAlignment.Left;
|
||||||
|
range.Style.VerticalAlignment = ExcelVerticalAlignment.Top;
|
||||||
|
ApplyBorder(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplyBorder(ExcelRange range)
|
||||||
|
{
|
||||||
|
range.Style.Border.Top.Style = ExcelBorderStyle.Thin;
|
||||||
|
range.Style.Border.Bottom.Style = ExcelBorderStyle.Thin;
|
||||||
|
range.Style.Border.Left.Style = ExcelBorderStyle.Thin;
|
||||||
|
range.Style.Border.Right.Style = ExcelBorderStyle.Thin;
|
||||||
|
range.Style.Border.Top.Color.SetColor(BorderColor);
|
||||||
|
range.Style.Border.Bottom.Color.SetColor(BorderColor);
|
||||||
|
range.Style.Border.Left.Color.SetColor(BorderColor);
|
||||||
|
range.Style.Border.Right.Color.SetColor(BorderColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ValueOrFallback(string? value) =>
|
||||||
|
string.IsNullOrWhiteSpace(value) ? "-" : value.Trim();
|
||||||
|
}
|
||||||
@@ -856,6 +856,58 @@ public partial class MainViewModel : ObservableObject, IDisposable
|
|||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void ExportReport()
|
private void ExportReport()
|
||||||
|
{
|
||||||
|
ExportReportExcel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExportReportExcel()
|
||||||
|
{
|
||||||
|
var outputDirectory = ResolveReportOutputDirectory();
|
||||||
|
var timestamp = DateTime.Now.ToString("yyyyMMdd-HHmmss");
|
||||||
|
var batchToken = string.IsNullOrWhiteSpace(BatchNumber) ? "未填写批号" : SanitizeFileNameSegment(BatchNumber.Trim());
|
||||||
|
var excelPath = Path.Combine(outputDirectory, $"检查报告-{batchToken}-{timestamp}.xlsx");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var document = new ExcelReportDocument(
|
||||||
|
pageTitle: PageTitle,
|
||||||
|
batchNumber: BatchNumber,
|
||||||
|
currentStage: CurrentStage,
|
||||||
|
operatorName: OperatorName,
|
||||||
|
reviewerName: ReviewerName,
|
||||||
|
approverName: ApproverName,
|
||||||
|
complianceDisplay: ComplianceDisplay,
|
||||||
|
deltaPressureDisplay: DeltaPressureDisplay,
|
||||||
|
detectionSummary: DetectionSummary,
|
||||||
|
configurationSummary: ConfigurationSummary,
|
||||||
|
exportTime: DateTime.Now,
|
||||||
|
inspectionItems: InspectionItems.ToList(),
|
||||||
|
traceEvents: TraceEvents.ToList(),
|
||||||
|
kinkResistanceEntries: KinkResistanceEntries.ToList(),
|
||||||
|
kinkResistanceFlowPointDisplay: KinkResistanceFlowPointDisplay,
|
||||||
|
kinkResistanceMandrelDiameterDisplay: KinkResistanceMandrelDiameterDisplay,
|
||||||
|
pressureDropEntries: PressureDropEntries.ToList(),
|
||||||
|
pressureDropLimitDisplay: PressureDropLimitDisplay,
|
||||||
|
antiCollapseBaselineDisplay: AntiCollapseBaselineDisplay,
|
||||||
|
antiCollapseComparisonDisplay: AntiCollapseComparisonDisplay,
|
||||||
|
antiCollapseCurrentNegativePressure: NegativeAssistPressureDisplay,
|
||||||
|
antiCollapseCurrentFlowDisplay: PumpFlowDisplay,
|
||||||
|
antiCollapseAllowedIncreaseRateDisplay: $"{AntiCollapseAllowedIncreaseRate:F1}%",
|
||||||
|
recirculationEntries: RecirculationEntries.ToList(),
|
||||||
|
recirculationLimitDisplay: RecirculationLimitDisplay);
|
||||||
|
|
||||||
|
document.GenerateExcel(excelPath);
|
||||||
|
LatestAction = $"已导出检查报告: {excelPath}";
|
||||||
|
TraceEvents.Insert(0, NewTrace("检查报告导出", Path.GetFileName(excelPath)));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LatestAction = $"检查报告导出失败: {ex.Message}";
|
||||||
|
TraceEvents.Insert(0, NewTrace("检查报告导出失败", ex.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExportReportPdf()
|
||||||
{
|
{
|
||||||
var outputDirectory = ResolveReportOutputDirectory();
|
var outputDirectory = ResolveReportOutputDirectory();
|
||||||
var timestamp = DateTime.Now.ToString("yyyyMMdd-HHmmss");
|
var timestamp = DateTime.Now.ToString("yyyyMMdd-HHmmss");
|
||||||
|
|||||||
Reference in New Issue
Block a user