using QuestPDF.Fluent; using QuestPDF.Helpers; using QuestPDF.Infrastructure; using SWRIS.Core; using SWRIS.Dtos; using SWRIS.Models; using System; using System.Collections.Generic; using System.Linq; namespace SWRIS.Extensions { public class ReportPdfExporter { private readonly string[] ChineseNumbers = { "零", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十" }; private readonly string _crane; private readonly RecordDto _record; private readonly EquipmentModel _equipment; private readonly int _damageNearPointCount = 20; public ReportPdfExporter(string crane, RecordDto record, EquipmentModel equipment) { _crane = crane; _record = record; _equipment = equipment; _damageNearPointCount = App.Config.DamageNearPointCount; } public bool SavePdf(string filePath) { if (_record == null || _equipment == null) { return false; } Document.Create(document => { var damageList = _record.Damages.OrderByDescending(c => c.DamageValue).Take(5).OrderBy(c => c.DamagePoint).ToList(); List<(double? position, double? damage, byte[] image)> images = GenerateImage(_record.DataFilePath, _record.SensorCount, _record.StartPoint, _record.EndPoint, damageList: damageList); document.Page(page => { page.Size(PageSizes.A4); page.MarginTop(2, Unit.Centimetre); page.MarginBottom(1, Unit.Centimetre); page.MarginLeft(1, Unit.Centimetre); page.MarginRight(1, Unit.Centimetre); page.Header().AlignCenter().AlignTop().Text("钢丝绳无损检测报告").FontSize(20).Bold(); page.Content() .AlignCenter() .AlignTop() .PaddingTop(20) .Table(table => { table.ColumnsDefinition(columns => { columns.ConstantColumn(22); columns.RelativeColumn(); columns.RelativeColumn(); columns.RelativeColumn(); columns.RelativeColumn(); columns.RelativeColumn(); columns.RelativeColumn(); }); table.Cell().Row(1).Column(1).ColumnSpan(4).PaddingBottom(5).Text($"所在起重机:{_crane}").AlignLeft(); table.Cell().Row(1).Column(5).ColumnSpan(3).PaddingBottom(5).Text($"检测时间:{_record.StartTime:yyyy-MM-dd HH:mm:ss}") .AlignRight(); table.Cell().Row(2).Column(1).RowSpan(7).Border(1).AlignMiddle().Text("基本信息").Bold().AlignCenter(); table.Cell().Row(2).Column(2).Element(CellNameStyle).Text("被检测钢丝绳"); table.Cell().Row(2).Column(3).ColumnSpan(2).Element(CellValueStyle).Text(_equipment.RopeName); table.Cell().Row(2).Column(5).Element(CellNameStyle).Text("钢丝绳制造商"); table.Cell().Row(2).Column(6).ColumnSpan(2).Element(CellValueStyle).Text(_equipment.Supplier); table.Cell().Row(3).Column(2).Element(CellNameStyle).Text("钢丝绳长"); table.Cell().Row(3).Column(3).ColumnSpan(2).Element(CellValueStyle).Text(_equipment.Parameter.WireRopeLength.ToString() + "m"); table.Cell().Row(3).Column(5).Element(CellNameStyle).Text("检测长度"); table.Cell().Row(3).Column(6).ColumnSpan(2).Element(CellValueStyle).Text(_record.DetectionLength.ToString("F3") + "m"); table.Cell().Row(4).Column(2).Element(CellNameStyle).Text("开始位置"); table.Cell().Row(4).Column(3).ColumnSpan(2).Element(CellValueStyle).Text(_record.StartPoint.ToString("F3") + "m"); table.Cell().Row(4).Column(5).Element(CellNameStyle).Text("结束位置"); table.Cell().Row(4).Column(6).ColumnSpan(2).Element(CellValueStyle).Text(_record.EndPoint.ToString("F3") + "m"); table.Cell().Row(5).Column(2).Element(CellNameStyle).Text("钢丝绳规格"); table.Cell().Row(5).Column(3).ColumnSpan(2).Element(CellValueStyle).Text( $"{_equipment.Parameter.WireRopeDiameter} {_equipment.WireSurfaceType} " + $"{_equipment.Parameter.WireRopeStrandCount}*" + $"{_equipment.Parameter.WireRopeStrandWireCount}S+{_equipment.RopeCoreType} " + $"{_equipment.LayType}"); table.Cell().Row(5).Column(5).Element(CellNameStyle).Text("钢丝材质"); table.Cell().Row(5).Column(6).ColumnSpan(2).Element(CellValueStyle).Text(_equipment.WireMaterialType.GetDescription()); table.Cell().Row(6).Column(2).Element(CellNameStyle).Text("钢丝绳公称直径"); table.Cell().Row(6).Column(3).ColumnSpan(2).Element(CellValueStyle).Text(_equipment.Parameter.WireRopeDiameter + "mm"); table.Cell().Row(6).Column(5).Element(CellNameStyle).Text("绳芯材质"); table.Cell().Row(6).Column(6).ColumnSpan(2).Element(CellValueStyle).Text(_equipment.RopeCoreType.GetDescription()); table.Cell().Row(7).Column(2).Element(CellNameStyle).Text("捻制方法"); table.Cell().Row(7).Column(3).ColumnSpan(2).Element(CellValueStyle).Text(_equipment.LayType.GetDescription()); table.Cell().Row(7).Column(5).Element(CellNameStyle).Text("钢丝绳表面状态"); table.Cell().Row(7).Column(6).ColumnSpan(2).Element(CellValueStyle).Text(_equipment.WireSurfaceType.GetDescription()); table.Cell().Row(8).Column(2).Element(CellNameStyle).Text("依据标准"); table.Cell().Row(8).Column(3).ColumnSpan(5).Element(CellValueStyle).AlignLeft().Text( "GB/T 21837-2023 《铁磁性钢丝绳电磁检测方法》\r\n" + "GB/T 26832-2011 《无损检测仪器 钢丝绳电磁检测仪技术条件》\r\n" + "GB/T 5972-2023 《起重机 钢丝绳 保养、维护、检验和报废》"); table.Cell().Row(9).Column(1).RowSpan(5).Border(1).AlignMiddle().Text("检测结果").Bold().AlignCenter(); table.Cell().Row(9).RowSpan(2).Column(2).Element(CellNameStyle).Text("最大损伤\r\n程度"); table.Cell().Row(9).Column(3).ColumnSpan(5).Element(CellNameStyle).Text("五处重要损伤"); table.Cell().Row(10).Column(3).Element(CellValueStyle).Text("第一处"); table.Cell().Row(10).Column(4).Element(CellValueStyle).Text("第二处"); table.Cell().Row(10).Column(5).Element(CellValueStyle).Text("第三处"); table.Cell().Row(10).Column(6).Element(CellValueStyle).Text("第四处"); table.Cell().Row(10).Column(7).Element(CellValueStyle).Text("第五处"); table.Cell().Row(11).Column(2).Element(CellNameStyle).Text("损伤位置"); table.Cell().Row(12).Column(2).Element(CellNameStyle).Text("损伤当量"); table.Cell().Row(13).Column(2).Element(CellNameStyle).Text("损伤级别"); for (int i = 0; i < 5; i++) { string positionValue = i < damageList.Count ? damageList[i].DamagePoint.ToString("F3") + "m" : "-"; string damageValue = i < damageList.Count ? damageList[i].DamageValue.ToString("F3") + "%" : "-"; string damageLevel = i < damageList.Count ? damageList[i].DamageLevel.GetDescription() : "-"; table.Cell().Row(11).Column((uint)(3 + i)).Element(CellValueStyle).Text(positionValue); table.Cell().Row(12).Column((uint)(3 + i)).Element(CellValueStyle).Text(damageValue); table.Cell().Row(13).Column((uint)(3 + i)).Element(CellValueStyle).Text(damageLevel); } }); }); document.Page(page => { page.Size(PageSizes.A4); page.MarginTop(2, Unit.Centimetre); page.MarginBottom(2, Unit.Centimetre); page.MarginLeft(1, Unit.Centimetre); page.MarginRight(1, Unit.Centimetre); page.Content().Column(column => { for (int i = 0; i < images.Count; i++) { if (i == 0) { column.Item().PaddingLeft(20).Text($"整绳检测波形图").FontSize(12).Bold(); column.Item().Image(images[i].image); column.Item().Height(35); } else { column.Item().PaddingLeft(20).Text($"第{ChineseNumbers[i]}处重要损伤").FontSize(12).Bold(); column.Item().Image(images[i].image); column.Item().AlignCenter().Text($"损伤位置:{images[i].position?.ToString("F3")}m " + $"损伤量值:{images[i].damage?.ToString("F3")}%").FontSize(10).FontColor(Color.FromHex("#7a08fa")); column.Item().Height(34); } } }); }); }).GeneratePdf(filePath); return true; } protected List<(double? position, double? damage, byte[] image)> GenerateImage(string dataFilePath, int sensorCount, double startPoint, double endPoint, List damageList) { List<(double? position, double? damage, byte[] image)> imageDamages = new List<(double? position, double? damage, byte[] image)>(); if (dataFilePath.IsNullOrEmpty()) { return null; } var damageData = DatFileHandler.ReadDatFile(dataFilePath); if (damageData.Length > 0) { (double[] positions, ushort[] damages) = ParseSensorData(damageData, sensorCount, startPoint, endPoint); if (positions != null && damages != null) { ScottPlot.Plot plot = new ScottPlot.Plot(); plot.Add.Scatter(positions, damages, new ScottPlot.Color("#70a1d7")); plot.Axes.SetLimits(startPoint * 0.95, endPoint * 1.05, 40, 80); imageDamages.Add((null, null, plot.GetImageBytes(900, 300))); foreach (var damage in damageList) { int damageIndex = FindClosestIndexBinarySearch(positions, damage.DamagePoint); if (damageIndex >= 0) { // 计算左右各10个点的索引范围 int startIndex = Math.Max(0, damageIndex - _damageNearPointCount); int endIndex = Math.Min(positions.Length - 1, damageIndex + _damageNearPointCount); // 设置X轴范围为左右各10个点 double start = positions[startIndex]; double end = positions[endIndex]; plot.Axes.SetLimitsX(start, end); } else { // 如果找不到精确匹配,使用近似值 double start = damage.DamagePoint * 0.995; double end = damage.DamagePoint * 1.005; plot.Axes.SetLimitsX(start, end); } imageDamages.Add((damage.DamagePoint, damage.DamageValue, plot.GetImageBytes(900, 300))); } } } return imageDamages; } public (double[] Positions, ushort[] Damages) ParseSensorData(byte[] data, int sensorCount, double startPosition, double endPosition) { // 检查数据长度是否能被传感器数量整除 if (data.Length % sensorCount != 0) { LogHelper.Error($"数据长度不匹配。数据长度 {data.Length} 字节不能被传感器数量 {sensorCount} 整除。"); return (null, null); } // 计算样本数量 int sampleCount = data.Length / sensorCount; // 计算采样步长 double samplingStep = (endPosition - startPosition) / (sampleCount - 1); // 初始化结果数组 double[] positions = new double[sampleCount]; ushort[] damages = new ushort[sampleCount]; // 解析数据并计算平均值 for (int sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) { positions[sampleIndex] = startPosition; // 计算当前样本的所有传感器Damage总和 int sum = 0; for (int sensorIndex = 0; sensorIndex < sensorCount; sensorIndex++) { // 计算数据在字节数组中的位置 int dataIndex = (sampleIndex * sensorCount) + sensorIndex; sum += data[dataIndex]; } // 计算平均值 damages[sampleIndex] = (ushort)(sum / sensorCount); startPosition += samplingStep; } return (positions, damages); } /// /// 使用二分查找在有序数组中查找最接近目标值的索引 /// /// private int FindClosestIndexBinarySearch(double[] array, double targetValue) { if (array == null || array.Length == 0) return -1; int left = 0; int right = array.Length - 1; while (left <= right) { int mid = left + (right - left) / 2; if (array[mid] == targetValue) return mid; else if (array[mid] < targetValue) left = mid + 1; else right = mid - 1; } // 此时left和right已经交叉,需要比较最接近的两个值 if (right < 0) return 0; if (left >= array.Length) return array.Length - 1; return Math.Abs(array[left] - targetValue) < Math.Abs(array[right] - targetValue) ? left : right; } static IContainer CellNameStyle(IContainer container) { return container .DefaultTextStyle(x => x.FontColor(Colors.Black).Bold()) .Border(1) .Padding(10) .AlignCenter() .AlignMiddle(); } static IContainer CellValueStyle(IContainer container) { return container .DefaultTextStyle(x => x.FontColor(Colors.Black).Medium()) .Border(1) .Padding(10) .AlignCenter() .AlignMiddle(); } } }