| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497 |
- using Microsoft.WindowsAPICodePack.Dialogs;
- using OfficeOpenXml;
- using OfficeOpenXml.Style;
- using Panuon.WPF.UI;
- using SWRIS.Core;
- using SWRIS.Dtos;
- using SWRIS.Extensions;
- using SWRIS.Models.ViewModel;
- using SWRIS.Repository;
- using System;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.Drawing;
- using System.IO;
- using System.Linq;
- using System.Threading.Tasks;
- using System.Windows;
- using System.Windows.Controls;
- using System.Windows.Input;
- namespace SWRIS.Pages
- {
- /// <summary>
- /// RecordPage.xaml 的交互逻辑
- /// </summary>
- public partial class RecordPage : Page
- {
- private readonly IRecordRepository _recordRepository;
- public RecordViewModel ViewModel { get; set; } = new RecordViewModel();
- public RecordPage()
- {
- _recordRepository = new RecordRepository();
- InitializeComponent();
- BindTable();
- DataContext = this;
- }
- // 在非 UI 线程上使用 Graphics 测量内容并设置列宽
- private void AutoFitColumnsByGraphics(ExcelWorksheet ws, int totalColumns)
- {
- if (ws?.Dimension == null)
- return;
- int rows = ws.Dimension.End.Row;
- // 使用 Bitmap/Graphics 在当前线程上进行测量(后台线程上也可安全使用)
- using (var bmp = new Bitmap(1, 1))
- using (var g = Graphics.FromImage(bmp))
- {
- // 选择一个常见的字体,EPPlus 默认字体可能不同,可根据需要调整
- using (var measureFont = new Font("Arial", 10))
- {
- for (int col = 1; col <= totalColumns; col++)
- {
- double maxPx = 0;
- for (int row = 1; row <= rows; row++)
- {
- var txt = ws.Cells[row, col].Text ?? string.Empty;
- if (string.IsNullOrEmpty(txt))
- continue;
- var sz = g.MeasureString(txt, measureFont);
- if (sz.Width > maxPx) maxPx = sz.Width;
- }
- // 像素宽度转 Excel 列宽的近似转换
- double excelWidth = PixelToExcelColumnWidth((int)Math.Ceiling(maxPx)) + 1.5; // 适度内边距
- if (excelWidth < 1) excelWidth = 1;
- ws.Column(col).Width = excelWidth;
- }
- }
- }
- }
- // 简单像素->Excel列宽近似转换
- private double PixelToExcelColumnWidth(int px)
- {
- // 近似:Excel 列宽 = (像素 - 5) / 7 (基于常见字体度量),结果可根据需要调整
- if (px <= 0) return 8;
- return Math.Round((px - 5) / 7.0, 2);
- }
- void BindTable()
- {
- var records = _recordRepository.GetRecords(ViewModel.SearchInput.RopeNumber,
- ViewModel.SearchInput.StartTime,
- ViewModel.SearchInput.EndTime,
- ViewModel.SearchInput.RiskLevel,
- ViewModel.SearchInput.Limit,
- ViewModel.SearchInput.Offset,
- true);
- ViewModel.IsAllSelected = false;
- ViewModel.Records = new ObservableCollection<RecordDto>(records);
- }
- private void Search_Click(object sender, RoutedEventArgs e)
- {
- BindTable();
- }
- private void Report_Click(object sender, RoutedEventArgs e)
- {
- if (sender is Button button && button.Tag is RecordDto record)
- {
- int reportedCount = Report(new List<RecordDto> { record });
- if (reportedCount > 0)
- {
- NoticeBox.Show("报告生成成功", "通知", MessageBoxIcon.Success, true, 2000);
- }
- else if (reportedCount == 0)
- {
- NoticeBox.Show("报告生成失败", "通知", MessageBoxIcon.Error, true, 5000);
- }
- }
- }
- private int Report(List<RecordDto> records)
- {
- string reportPath = SelectExportFolder();
- if (string.IsNullOrEmpty(reportPath))
- {
- return -1;
- }
- return ExportMultipleReports(records, reportPath);
- }
- private int Export(List<RecordDto> records)
- {
- // 保留兼容性:同步导出入口仍然可用(内部调用异步路径)
- string reportPath = SelectExportFolder(initialDirectory: "钢丝绳检测数据");
- if (string.IsNullOrEmpty(reportPath))
- {
- return -1;
- }
- return ExportMultipleRecords(records, reportPath);
- }
- private void Export_Click(object sender, RoutedEventArgs e)
- {
- if (sender is Button button && button.Tag is RecordDto record)
- {
- int exportedCount = Export(new List<RecordDto> { record });
- if (exportedCount > 0)
- {
- NoticeBox.Show("记录导出成功", "通知", MessageBoxIcon.Success, true, 2000);
- }
- else if (exportedCount == 0)
- {
- NoticeBox.Show("记录导出失败", "通知", MessageBoxIcon.Error, true, 5000);
- }
- }
- }
- private void RecordDataGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e)
- {
- if (e.ChangedButton == MouseButton.Left && sender is DataGrid dataGrid && dataGrid.SelectedItem != null)
- {
- var selectedItem = dataGrid.SelectedItem as RecordDto; // 获取当前选中行的数据
- (Application.Current.MainWindow as MainWindow).NavigatePage(new Uri($"Pages/DamagesPage.xaml?id=" + selectedItem.Id, uriKind: UriKind.Relative));
- }
- }
- private void Delete_Click(object sender, RoutedEventArgs e)
- {
- if (sender is Button button)
- {
- int id = (int)button.Tag;
- var result = MessageBoxX.Show(Application.Current.MainWindow,
- "此操作不可恢复,是否确认删除记录?",
- "警告",
- MessageBoxButton.YesNo,
- MessageBoxIcon.Warning,
- DefaultButton.YesOK);
- if (result == MessageBoxResult.Yes)
- {
- var effect = _recordRepository.DeleteRecord(id);
- if (effect)
- {
- var deletedRecord = ViewModel.Records.FirstOrDefault(x => x.Id == id);
- if (deletedRecord != null)
- {
- ViewModel.Records.Remove(deletedRecord);
- DatFileHandler.DeleteDatFile(deletedRecord.DataFilePath);
- }
- NoticeBox.Show("记录删除成功", "通知", MessageBoxIcon.Success, true, 5000);
- }
- else
- {
- NoticeBox.Show("记录删除失败", "通知", MessageBoxIcon.Error, true, 5000);
- }
- }
- }
- }
- private int Delete(int[] recordIds)
- {
- var result = MessageBoxX.Show(Application.Current.MainWindow,
- "此操作不可恢复,是否确认删除记录?",
- "警告",
- MessageBoxButton.YesNo,
- MessageBoxIcon.Warning,
- DefaultButton.YesOK);
- if (result == MessageBoxResult.Yes)
- {
- return _recordRepository.DeleteRecords(recordIds);
- }
- return 0;
- }
- private void Batch_Report(object sender, RoutedEventArgs e)
- {
- var records = ViewModel.Records.Where(c => c.IsSelected).ToList();
- if (records.Any())
- {
- int reportCount = Report(records);
- if (reportCount > 0)
- {
- Application.Current.Dispatcher.Invoke(() =>
- {
- NoticeBox.Show($"检测报告文件{(reportCount > 1 ? $"({reportCount})条" : "")}已生成成功", "通知", MessageBoxIcon.Success, true, 5000);
- });
- }
- }
- else
- {
- MessageBoxX.Show(Application.Current.MainWindow, "请选择要生成报告的检测记录", "通知", MessageBoxButton.OK,
- MessageBoxIcon.Info, DefaultButton.YesOK);
- }
- }
- private void Batch_Delete(object sender, RoutedEventArgs e)
- {
- var records = ViewModel.Records.Where(c => c.IsSelected).ToList();
- if (records.Any())
- {
- int[] recordIds = records.Select(x => (int)x.Id).ToArray();
- int deletedCount = Delete(recordIds);
- if (deletedCount > 0)
- {
- foreach (var record in records)
- {
- ViewModel.Records.Remove(record);
- DatFileHandler.DeleteDatFile(record.DataFilePath);
- }
- Application.Current.Dispatcher.Invoke(() =>
- {
- NoticeBox.Show($"检测记录{(recordIds.Length > 1 ? $"({recordIds.Length}条)" : "")}已成功删除", "通知", MessageBoxIcon.Success, true, 5000);
- });
- }
- }
- else
- {
- MessageBoxX.Show(Application.Current.MainWindow, "请选择要删除的检测记录", "提醒", MessageBoxButton.OK,
- MessageBoxIcon.Info, DefaultButton.YesOK);
- }
- }
- private async void Batch_Export(object sender, RoutedEventArgs e)
- {
- var records = ViewModel.Records.Where(c => c.IsSelected).ToList();
- if (!records.Any())
- {
- MessageBoxX.Show(Application.Current.MainWindow, "请选择要导出的检测记录", "通知", MessageBoxButton.OK,
- MessageBoxIcon.Info, DefaultButton.YesOK);
- return;
- }
- string reportPath = SelectExportFolder(initialDirectory: "钢丝绳检测数据");
- if (string.IsNullOrEmpty(reportPath))
- return;
- // 可在此禁用导出按钮或显示进度指示器
- int exportCount = 0;
- try
- {
- exportCount = await Task.Run(() => ExportMultipleRecords(records, reportPath));
- if (exportCount > 0)
- {
- Application.Current.Dispatcher.Invoke(() =>
- {
- NoticeBox.Show($"检测数据文件{(exportCount > 1 ? $"({exportCount})条" : "")}已成功导出", "通知", MessageBoxIcon.Success, true, 5000);
- });
- }
- }
- catch (Exception ex)
- {
- Application.Current.Dispatcher.Invoke(() =>
- {
- MessageBoxX.Show(Application.Current.MainWindow, $"导出失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxIcon.Error, DefaultButton.YesOK);
- });
- }
- finally
- {
- // 恢复按钮/进度
- }
- }
- private string SelectExportFolder(string title = "选择文件夹", string initialDirectory = "钢丝绳检测报告")
- {
- using (var dialog = new CommonOpenFileDialog())
- {
- dialog.Title = title;
- dialog.IsFolderPicker = true;
- dialog.EnsurePathExists = true;
- dialog.InitialDirectory = GetDefaultExportDirectory(initialDirectory);
- if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
- {
- return dialog.FileName;
- }
- }
- return null;
- }
- private int ExportMultipleReports(List<RecordDto> records, string folderPath)
- {
- int savedCount = 0;
- string craneName = App.Config.Crane;
- // 按钢丝绳编号分组处理
- var groupedRecords = records.OrderBy(r => r.RopeNumber).GroupBy(r => r.RopeNumber);
- foreach (var group in groupedRecords)
- {
- var equipment = App.Config.Equipments
- .FirstOrDefault(c => c.RopeNumber == group.Key);
- foreach (var record in group)
- {
- string fileName = GenerateReportFileName(record);
- string filePath = Path.Combine(folderPath, fileName);
- var export = new ReportPdfExporter(craneName, record, equipment);
- if (export.SavePdf(filePath))
- {
- savedCount++;
- _recordRepository.AddRecordExportCount(record.Id.Value);
- }
- }
- }
- return savedCount;
- }
- private int ExportMultipleRecords(List<RecordDto> records, string folderPath)
- {
- int exportedCount = 0;
- foreach (var record in records)
- {
- if (record == null || string.IsNullOrEmpty(record.DataFilePath))
- continue;
- var damageData = DatFileHandler.ReadDatFile(record.DataFilePath);
- if ((damageData?.Length ?? 0) <= 0)
- continue;
- var parsedData = ParseSensorData(damageData, record.SensorCount, record.StartPoint, record.EndPoint);
- if (parsedData.Positions == null || parsedData.Damages == null)
- continue;
- string fileName = GenerateExportFileName(record);
- string filePath = Path.Combine(folderPath, fileName);
- try
- {
- using (var package = new ExcelPackage())
- {
- var ws = package.Workbook.Worksheets.Add("检测数据");
- // 设置表头
- ws.Cells[1, 1].Value = "位置(m)";
- for (int i = 0; i < record.SensorCount; i++)
- {
- ws.Cells[1, i + 2].Value = $"传感器{i + 1}";
- }
- ws.Cells[1, record.SensorCount + 2].Value = "均值";
- // 设置表头样式
- using (var headerRange = ws.Cells[1, 1, 1, record.SensorCount + 2])
- {
- headerRange.Style.Font.Bold = true;
- headerRange.Style.Fill.PatternType = ExcelFillStyle.Solid;
- headerRange.Style.Fill.BackgroundColor.SetColor(Color.LightGray);
- }
- // 填充数据行
- int sampleCount = parsedData.Positions.Length;
- for (int row = 0; row < sampleCount; row++)
- {
- int excelRow = row + 2;
- ws.Cells[excelRow, 1].Value = Math.Round(parsedData.Positions[row], 3);
- double sum = 0;
- for (int col = 0; col < record.SensorCount; col++)
- {
- ushort value = parsedData.Damages[col, row];
- ws.Cells[excelRow, col + 2].Value = value;
- sum += value;
- }
- ws.Cells[excelRow, record.SensorCount + 2].Value = Math.Round(sum / record.SensorCount, 1);
- }
- // 自动调整列宽:在后台线程上使用 Graphics.MeasureString 测量并手动设置列宽,避免调用可能依赖 UI 的 AutoFit
- AutoFitColumnsByGraphics(ws, record.SensorCount + 2);
- package.SaveAs(new FileInfo(filePath));
- }
- exportedCount++;
- _recordRepository.AddRecordExportCount(record.Id.Value);
- }
- catch (Exception ex)
- {
- LogHelper.Error($"导出记录 {record.Id} 到Excel失败: {ex.Message}", ex);
- }
- }
- return exportedCount;
- }
- 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);
- var positions = new double[sampleCount];
- var damages = new ushort[sensorCount, sampleCount];
- for (int sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
- {
- positions[sampleIndex] = startPosition;
- for (int sensorIndex = 0; sensorIndex < sensorCount; sensorIndex++)
- {
- int dataIndex = (sampleIndex * sensorCount) + sensorIndex;
- damages[sensorIndex, sampleIndex] = data[dataIndex];
- }
- startPosition += samplingStep;
- }
- return (positions, damages);
- }
- private string GenerateExportFileName(RecordDto record)
- {
- string ropeName = CleanFileName(record.RopeName);
- string time = record.EndTime.ToString("yyyyMMdd_HHmmss");
- return $"{ropeName}_{time}_检测数据.xlsx";
- }
- private string GetDefaultExportDirectory(string folderName = "钢丝绳检测报告")
- {
- string docsPath = Path.Combine(
- Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
- folderName,
- DateTime.Now.ToString("yyyy-MM"));
- // 确保目录存在
- try
- {
- Directory.CreateDirectory(docsPath);
- return docsPath;
- }
- catch
- {
- // 失败时返回桌面
- return Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
- }
- }
- private string GenerateReportFileName(RecordDto record)
- {
- // 清理文件名中的非法字符
- string ropeName = CleanFileName(record.RopeName);
- string time = record.EndTime.ToString("yyyyMMdd_HHmmss");
- return $"{ropeName}_{time}_检测报告.pdf";
- }
- private string CleanFileName(string fileName)
- {
- if (string.IsNullOrEmpty(fileName))
- return "未知";
- // 移除非法字符
- char[] invalidChars = Path.GetInvalidFileNameChars();
- foreach (char c in invalidChars)
- {
- fileName = fileName.Replace(c, '_');
- }
- // 限制长度
- if (fileName.Length > 50)
- {
- fileName = fileName.Substring(0, 50);
- }
- return fileName.Trim();
- }
- }
- }
|