Files
FootwearTest-20260602/Footwear Test methodsfor wholeshoe Slipresistanceperformance/Services/SlipExcelExportService.cs
2026-06-02 18:14:01 +08:00

366 lines
15 KiB
C#

using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Models;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using A = DocumentFormat.OpenXml.Drawing;
using C = DocumentFormat.OpenXml.Drawing.Charts;
using Xdr = DocumentFormat.OpenXml.Drawing.Spreadsheet;
namespace Footwear_Test_methodsfor_wholeshoe_Slipresistanceperformance.Services
{
public sealed class SlipExcelExportService
{
private const string InfoSheetName = "试验信息";
private const string ResultSheetName = "结果汇总";
private const string DataSheetName = "实时数据";
public string Export(SlipReportExport report)
{
var directory = GetExportDirectory();
Directory.CreateDirectory(directory);
var safeNumber = MakeSafeFileName(string.IsNullOrWhiteSpace(report.TestNumber)
? DateTime.Now.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture)
: report.TestNumber);
var path = Path.Combine(directory, $"{safeNumber}_防滑性能测试报告.xlsx");
using var document = SpreadsheetDocument.Create(path, SpreadsheetDocumentType.Workbook);
var workbookPart = document.AddWorkbookPart();
workbookPart.Workbook = new Workbook();
var sheets = workbookPart.Workbook.AppendChild(new Sheets());
AddInfoSheet(workbookPart, sheets, 1, report);
AddResultSheet(workbookPart, sheets, 2, report.Results);
AddDataSheet(workbookPart, sheets, 3, report.Points);
workbookPart.Workbook.Save();
return path;
}
private static string GetExportDirectory()
{
var legacyDirectory = Path.Combine(@"D:\文件保存", DateTime.Now.ToString("yyyyMMdd", CultureInfo.InvariantCulture));
if (Directory.Exists(@"D:\"))
{
return legacyDirectory;
}
return Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
"FootwearSlipResistance",
DateTime.Now.ToString("yyyyMMdd", CultureInfo.InvariantCulture));
}
private static void AddInfoSheet(WorkbookPart workbookPart, Sheets sheets, uint sheetId, SlipReportExport report)
{
var rows = new List<IReadOnlyList<object?>>
{
new object?[] { "字段", "内容" },
new object?[] { "试验编号", report.TestNumber },
new object?[] { "报告名称", report.ReportName },
new object?[] { "操作人员", report.OperatorName },
new object?[] { "测试方法", report.MethodName },
new object?[] { "样品特征", report.SampleFeature },
new object?[] { "鞋码区间/目标负荷", $"{report.ShoeSize} / {report.TargetLoad}" },
new object?[] { "实际负荷", report.ActualLoad },
new object?[] { "润滑介质", report.Lubricant },
new object?[] { "测试模式", report.TestMode },
new object?[] { "地面材质", report.Surface },
new object?[] { "测试速度", report.TestSpeed },
new object?[] { "标准依据", report.StandardReference },
new object?[] { "导出时间", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture) }
};
AddWorksheet(workbookPart, sheets, sheetId, InfoSheetName, rows, "A", "B");
}
private static void AddResultSheet(WorkbookPart workbookPart, Sheets sheets, uint sheetId, IReadOnlyList<TestSample> results)
{
var rows = new List<IReadOnlyList<object?>>
{
new object?[] { "序号", "时间", "静摩擦系数", "动摩擦系数", "判定" }
};
foreach (var result in results)
{
rows.Add(new object?[]
{
result.Index,
result.Time,
result.StaticCoefficientValue,
result.DynamicCoefficientValue,
result.Verdict
});
}
AddWorksheet(workbookPart, sheets, sheetId, ResultSheetName, rows, "A", "E");
}
private static void AddDataSheet(WorkbookPart workbookPart, Sheets sheets, uint sheetId, IReadOnlyList<SlipDataPoint> points)
{
var rows = new List<IReadOnlyList<object?>>
{
new object?[] { "时间(s)", "正压力(N)", "摩擦力(N)", "位移量(mm)", "摩擦系数" }
};
foreach (var point in points)
{
rows.Add(new object?[]
{
point.TimeSeconds,
point.VerticalLoadN,
point.HorizontalFrictionN,
point.DisplacementMm,
point.FrictionCoefficient
});
}
var worksheetPart = AddWorksheet(workbookPart, sheets, sheetId, DataSheetName, rows, "A", "E");
if (points.Count > 0)
{
AddChart(worksheetPart, points.Count + 1);
}
}
private static WorksheetPart AddWorksheet(
WorkbookPart workbookPart,
Sheets sheets,
uint sheetId,
string sheetName,
IReadOnlyList<IReadOnlyList<object?>> rows,
string firstColumn,
string lastColumn)
{
var worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
var sheetData = new SheetData();
var worksheet = new Worksheet();
worksheet.Append(CreateColumns(firstColumn, lastColumn));
worksheet.Append(sheetData);
worksheetPart.Worksheet = worksheet;
for (var rowIndex = 0; rowIndex < rows.Count; rowIndex++)
{
var row = new Row { RowIndex = (uint)(rowIndex + 1) };
for (var columnIndex = 0; columnIndex < rows[rowIndex].Count; columnIndex++)
{
row.Append(CreateCell(columnIndex + 1, rowIndex + 1, rows[rowIndex][columnIndex]));
}
sheetData.Append(row);
}
var relationshipId = workbookPart.GetIdOfPart(worksheetPart);
sheets.Append(new Sheet
{
Id = relationshipId,
SheetId = sheetId,
Name = sheetName
});
worksheetPart.Worksheet.Save();
return worksheetPart;
}
private static Columns CreateColumns(string firstColumn, string lastColumn)
{
var first = ColumnNameToIndex(firstColumn);
var last = ColumnNameToIndex(lastColumn);
var columns = new Columns();
for (var index = first; index <= last; index++)
{
columns.Append(new Column
{
Min = (uint)index,
Max = (uint)index,
Width = index == 1 ? 15 : 18,
CustomWidth = true
});
}
return columns;
}
private static Cell CreateCell(int columnIndex, int rowIndex, object? value)
{
var cell = new Cell { CellReference = $"{GetColumnName(columnIndex)}{rowIndex}" };
switch (value)
{
case null:
cell.DataType = CellValues.String;
cell.CellValue = new CellValue(string.Empty);
break;
case int intValue:
cell.CellValue = new CellValue(intValue);
break;
case double doubleValue:
cell.CellValue = new CellValue(doubleValue.ToString("0.#####", CultureInfo.InvariantCulture));
break;
default:
cell.DataType = CellValues.String;
cell.CellValue = new CellValue(Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty);
break;
}
return cell;
}
private static void AddChart(WorksheetPart worksheetPart, int lastRow)
{
var drawingsPart = worksheetPart.AddNewPart<DrawingsPart>();
var drawingRelationshipId = worksheetPart.GetIdOfPart(drawingsPart);
worksheetPart.Worksheet.Append(new DocumentFormat.OpenXml.Spreadsheet.Drawing { Id = drawingRelationshipId });
drawingsPart.WorksheetDrawing = new Xdr.WorksheetDrawing();
var chartPart = drawingsPart.AddNewPart<ChartPart>();
var chartRelationshipId = drawingsPart.GetIdOfPart(chartPart);
chartPart.ChartSpace = CreateChartSpace(lastRow);
chartPart.ChartSpace.Save();
var graphicFrame = new Xdr.GraphicFrame(
new Xdr.NonVisualGraphicFrameProperties(
new Xdr.NonVisualDrawingProperties { Id = 2U, Name = "防滑性能曲线图" },
new Xdr.NonVisualGraphicFrameDrawingProperties()),
new Xdr.Transform(
new A.Offset { X = 0L, Y = 0L },
new A.Extents { Cx = 0L, Cy = 0L }),
new A.Graphic(
new A.GraphicData(
new C.ChartReference { Id = chartRelationshipId })
{ Uri = "http://schemas.openxmlformats.org/drawingml/2006/chart" }));
var anchor = new Xdr.TwoCellAnchor(
new Xdr.FromMarker(
new Xdr.ColumnId("6"),
new Xdr.ColumnOffset("0"),
new Xdr.RowId("1"),
new Xdr.RowOffset("0")),
new Xdr.ToMarker(
new Xdr.ColumnId("15"),
new Xdr.ColumnOffset("0"),
new Xdr.RowId("23"),
new Xdr.RowOffset("0")),
graphicFrame,
new Xdr.ClientData());
drawingsPart.WorksheetDrawing.Append(anchor);
drawingsPart.WorksheetDrawing.Save();
worksheetPart.Worksheet.Save();
}
private static C.ChartSpace CreateChartSpace(int lastRow)
{
const uint xAxisId = 48650112U;
const uint yAxisId = 48672768U;
var scatterChart = new C.ScatterChart(
new C.ScatterStyle { Val = C.ScatterStyleValues.LineMarker },
CreateSeries(0, "正压力(N)", 2, lastRow),
CreateSeries(1, "摩擦力(N)", 3, lastRow),
CreateSeries(2, "位移量(mm)", 4, lastRow),
CreateSeries(3, "摩擦系数", 5, lastRow),
new C.AxisId { Val = xAxisId },
new C.AxisId { Val = yAxisId });
var chart = new C.Chart(
CreateTitle("防滑性能实时曲线"),
new C.PlotArea(
new C.Layout(),
scatterChart,
CreateValueAxis(xAxisId, yAxisId, C.AxisPositionValues.Bottom, "时间(s)"),
CreateValueAxis(yAxisId, xAxisId, C.AxisPositionValues.Left, "载荷 / 摩擦力 / 位移 / 系数")),
new C.Legend(
new C.LegendPosition { Val = C.LegendPositionValues.Bottom },
new C.Layout()),
new C.PlotVisibleOnly { Val = true });
return new C.ChartSpace(
new C.EditingLanguage { Val = "zh-CN" },
chart);
}
private static C.ScatterChartSeries CreateSeries(uint index, string title, int yColumnIndex, int lastRow)
{
var sheet = EscapeSheetName(DataSheetName);
var xFormula = $"'{sheet}'!$A$2:$A${lastRow}";
var yColumn = GetColumnName(yColumnIndex);
var yFormula = $"'{sheet}'!${yColumn}$2:${yColumn}${lastRow}";
var titleFormula = $"'{sheet}'!${yColumn}$1";
return new C.ScatterChartSeries(
new C.Index { Val = index },
new C.Order { Val = index },
new C.SeriesText(new C.StringReference(new C.Formula(titleFormula))),
new C.XValues(new C.NumberReference(new C.Formula(xFormula))),
new C.YValues(new C.NumberReference(new C.Formula(yFormula))),
new C.Smooth { Val = false });
}
private static C.ValueAxis CreateValueAxis(uint axisId, uint crossingAxisId, C.AxisPositionValues position, string title) =>
new(
new C.AxisId { Val = axisId },
new C.Scaling(new C.Orientation { Val = C.OrientationValues.MinMax }),
new C.AxisPosition { Val = position },
CreateTitle(title),
new C.NumberingFormat { FormatCode = "0.00", SourceLinked = false },
new C.MajorGridlines(),
new C.CrossingAxis { Val = crossingAxisId },
new C.Crosses { Val = C.CrossesValues.AutoZero },
new C.TickLabelPosition { Val = C.TickLabelPositionValues.NextTo });
private static C.Title CreateTitle(string text) =>
new(
new C.ChartText(
new C.RichText(
new A.BodyProperties(),
new A.ListStyle(),
new A.Paragraph(
new A.Run(
new A.RunProperties { Language = "zh-CN", FontSize = 1100 },
new A.Text(text))))),
new C.Layout(),
new C.Overlay { Val = false });
private static string MakeSafeFileName(string value)
{
foreach (var invalidChar in Path.GetInvalidFileNameChars())
{
value = value.Replace(invalidChar, '_');
}
return value;
}
private static string EscapeSheetName(string value) => value.Replace("'", "''");
private static string GetColumnName(int columnIndex)
{
var dividend = columnIndex;
var columnName = string.Empty;
while (dividend > 0)
{
var modulo = (dividend - 1) % 26;
columnName = Convert.ToChar('A' + modulo) + columnName;
dividend = (dividend - modulo) / 26;
}
return columnName;
}
private static int ColumnNameToIndex(string columnName)
{
var sum = 0;
foreach (var character in columnName)
{
sum *= 26;
sum += character - 'A' + 1;
}
return sum;
}
}
}