| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309 |
- 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<DamageDto> 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);
- }
- /// <summary>
- /// 使用二分查找在有序数组中查找最接近目标值的索引
- /// </summary>
- /// <returns></returns>
- 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();
- }
- }
- }
|