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
{
///
/// RecordPage.xaml 的交互逻辑
///
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(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 { record });
if (reportedCount > 0)
{
NoticeBox.Show("报告生成成功", "通知", MessageBoxIcon.Success, true, 2000);
}
else if (reportedCount == 0)
{
NoticeBox.Show("报告生成失败", "通知", MessageBoxIcon.Error, true, 5000);
}
}
}
private int Report(List records)
{
string reportPath = SelectExportFolder();
if (string.IsNullOrEmpty(reportPath))
{
return -1;
}
return ExportMultipleReports(records, reportPath);
}
private int Export(List 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 { 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 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 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();
}
}
}