Files
huadongmocaceshiyi/ReportPage.xaml.cs
2026-03-11 16:42:31 +08:00

538 lines
27 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Microsoft.Win32;
using NPOI.SS.UserModel;
using NPOI.SS.UserModel.Charts;
using NPOI.SS.Util;
using NPOI.XSSF.UserModel;
using OfficeOpenXml;
using OfficeOpenXml.Drawing.Chart;
using OfficeOpenXml.Style;
using System.Collections.ObjectModel;
using System.IO;
using System.Windows;
using System.Windows.Controls;
namespace PLCDataMonitor
{
public partial class ReportPage : UserControl
{
public ObservableCollection<ReportData> ReportList { get; set; }
// 关键修改:使用属性注入,而不是自己创建
public System.Collections.Concurrent.ConcurrentQueue<Tuple<DateTime, double, double>> CurveDataQueue { get; set; }
public ReportPage()
{
InitializeComponent();
ReportList = new ObservableCollection<ReportData>();
// 不在这里创建队列,等待主窗口注入
ReportDataGrid.ItemsSource = ReportList;
}
public void AddReportRecord(ReportData record)
{
Dispatcher.Invoke(() =>
{
ReportList.Insert(0, record);
});
}
private void ExportExcelButton_Click(object sender, RoutedEventArgs e)
{
if (ReportDataGrid.ItemsSource is IEnumerable<ReportData> reportData &&
CurveDataQueue != null && CurveDataQueue.Count > 0)
{
Microsoft.Win32.SaveFileDialog saveFileDialog = new Microsoft.Win32.SaveFileDialog
{
Filter = "Excel 文件 (*.xlsx)|*.xlsx",
FileName = $"滑动摩擦磨损试验报告_{DateTime.Now:yyyyMMddHHmm}.xlsx"
};
if (saveFileDialog.ShowDialog() == true)
{
try
{
// 设置EPPlus许可证上下文
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
using (ExcelPackage excelPackage = new ExcelPackage())
{
// ====== 第一个Sheet按照图片模板的报表 ======
ExcelWorksheet reportSheet = excelPackage.Workbook.Worksheets.Add("滑动摩擦磨损试验报告");
// 设置A4纸大小
reportSheet.PrinterSettings.PaperSize = ePaperSize.A4;
reportSheet.PrinterSettings.Orientation = eOrientation.Portrait;
// 设置标题
reportSheet.Cells["A1:E1"].Merge = true;
reportSheet.Cells["A1"].Value = "滑动摩擦磨损试验报告";
reportSheet.Cells["A1"].Style.Font.Size = 14;
reportSheet.Cells["A1"].Style.Font.Bold = true;
reportSheet.Cells["A1"].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
reportSheet.Cells["A1"].Style.VerticalAlignment = ExcelVerticalAlignment.Center;
reportSheet.Row(1).Height = 30;
// 报告编号和试验日期
reportSheet.Cells["A2"].Value = "报告编号:";
reportSheet.Cells["D2:E2"].Merge = true;
reportSheet.Cells["D2"].Value = "试验日期______年______月______日";
reportSheet.Row(2).Height = 25;
// 空行
reportSheet.Row(3).Height = 10;
// 创建表格标题行
reportSheet.Cells["A4"].Value = "序号";
reportSheet.Cells["B4"].Value = "项目名称";
reportSheet.Cells["C4"].Value = "工位1";
reportSheet.Cells["D4"].Value = "工位2";
reportSheet.Cells["E4"].Value = "备注/说明";
// 设置标题行样式
for (int col = 1; col <= 5; col++)
{
reportSheet.Cells[4, col].Style.Font.Bold = true;
reportSheet.Cells[4, col].Style.Font.Size = 11;
reportSheet.Cells[4, col].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
reportSheet.Cells[4, col].Style.VerticalAlignment = ExcelVerticalAlignment.Center;
reportSheet.Cells[4, col].Style.Border.Top.Style = ExcelBorderStyle.Thin;
reportSheet.Cells[4, col].Style.Border.Bottom.Style = ExcelBorderStyle.Thin;
reportSheet.Cells[4, col].Style.Border.Left.Style = ExcelBorderStyle.Thin;
reportSheet.Cells[4, col].Style.Border.Right.Style = ExcelBorderStyle.Thin;
}
reportSheet.Row(4).Height = 25;
// 设置表格数据
int currentRow = 5;
var timesdata = new List<Tuple<DateTime, double, double>>(CurveDataQueue);
var timedata = timesdata.Select(s => s.Item1).ToList();
var startTime = timedata.Min();
var endTime = timedata.Max();
// 序号1油管型号
CreateFormattedRowEPPlus(reportSheet, currentRow++, "1", "油管型号", OilPipeType1.Text, OilPipeType2.Text, "");
// 序号2滑块型号
CreateFormattedRowEPPlus(reportSheet, currentRow++, "2", "样件型号", SliderType1.Text, SliderType2.Text, "");
// 序号3试验开始时间
CreateFormattedRowEPPlus(reportSheet, currentRow++, "3", "试验开始时间", startTime.ToString(), "", "试验开始的时间点");
// 序号4试验停止时间
CreateFormattedRowEPPlus(reportSheet, currentRow++, "4", "试验停止时间", endTime.ToString(), "", "试验结束的时间点");
// 序号5样件试验前壁厚
CreateFormattedRowEPPlus(reportSheet, currentRow++, "5", "样件1试验前壁厚(mm)", PreTestThickness.Text, PreTestThickness3.Text, "样件试验前壁厚(mm)");
// 序号6样件试验后壁厚
CreateFormattedRowEPPlus(reportSheet, currentRow++, "6", "样件1试验后壁厚(mm)", PostTestThickness.Text, PostTestThickness3.Text, "样件试验后壁厚(mm)");
// 序号7油管磨损深度
CreateFormattedRowEPPlus(reportSheet, currentRow++, "7", "油管1磨损深度(mm)", PipeWearDepth.Text, PipeWearDepth3.Text, "油管磨损深度(mm)");
//// 序号8样件试验前壁厚
//CreateFormattedRowEPPlus(reportSheet, currentRow++, "8", "样件2试验前壁厚(mm)", PreTestThickness3.Text, "", "样件2试验前壁厚(mm)");
//// 序号9样件试验后壁厚
//CreateFormattedRowEPPlus(reportSheet, currentRow++, "9", "样件2试验后壁厚(mm)", PostTestThickness3.Text, "", "样件2试验后壁厚(mm)");
//// 序号10油管磨损深度
//CreateFormattedRowEPPlus(reportSheet, currentRow++, "10", "油管2磨损深度(mm)", PipeWearDepth3.Text, "", "油管2磨损深度(mm)");
// 序号5样件试验前壁厚
CreateFormattedRowEPPlus(reportSheet, currentRow++, "8", "样件1试验前重量(g)", PreTestThickness4.Text, PreTestThickness2.Text, "样件试验前重量(g)");
// 序号6样件试验后壁厚
CreateFormattedRowEPPlus(reportSheet, currentRow++, "9", "样件1试验后重量(g)", PostTestThickness4.Text, PostTestThickness2.Text, "样件试验后重量(g)");
// 序号7油管磨损深度
CreateFormattedRowEPPlus(reportSheet, currentRow++, "10", "样件1磨损量(g)", PipeWearDepth4.Text, PipeWearDepth2.Text, "样件磨损量(g)");
//// 序号8样件试验前壁厚
//CreateFormattedRowEPPlus(reportSheet, currentRow++, "14", "样件2试验前重量(g)", PreTestThickness2.Text, "", "样件2试验前壁厚(g)");
//// 序号9样件试验后壁厚
//CreateFormattedRowEPPlus(reportSheet, currentRow++, "15", "样件2试验后重量(g)", PostTestThickness2.Text, "", "样件2试验后壁厚(g)");
//// 序号10油管磨损深度
//CreateFormattedRowEPPlus(reportSheet, currentRow++, "16", "样件2磨损量(g)", PipeWearDepth2.Text, "", "油管2磨损深度(g)");
if (reportData.Count() > 0)
{
reportData = reportData.OrderBy(s => s.Time).ToList();
}
// 序号8水箱温度
CreateFormattedRowEPPlus(reportSheet, currentRow++, "11", "水箱温度(℃)",
reportData.Last().t1?.ToString("F1"),
reportData.Last().t2?.ToString("F1"),
"实时监测温度");
// 序号9测试实际距离
CreateFormattedRowEPPlus(reportSheet, currentRow++, "12", "测试实际距离m",
reportData.Last().D?.ToString(),
reportData.Last().D?.ToString(),
"累计滑动行程");
// 序号10压力
CreateFormattedRowEPPlus(reportSheet, currentRow++, "13", "压力N",
reportData.Last().Pressure1.ToString(),
reportData.Last().Pressure2.ToString(),
"施加载荷");
var allData = new List<Tuple<DateTime, double, double>>(CurveDataQueue);
var maxFriction1 = allData.Select(x => Math.Abs(x.Item2)).Max(x => x);
var maxFriction2 = allData.Select(x => Math.Abs(x.Item3)).Max(x => x);
// 序号11最大摩擦力
CreateFormattedRowEPPlus(reportSheet, currentRow++, "14", "最大摩擦力N",
maxFriction1.ToString("F1"),
maxFriction2.ToString("F1"),
"测试过程中最大值");
//// 序号12磨损量
//CreateFormattedRowEPPlus(reportSheet, currentRow++, "21", "磨损量g", "", "", "试验前后质量差");
// 空行
currentRow++;
// 备注标题
reportSheet.Cells[currentRow, 1].Value = "备注:";
reportSheet.Cells[currentRow, 1].Style.Font.Bold = true;
// ====== 修复1调整备注行高度 ======
// 先合并备注内容的单元格A到E列
for (int i = 0; i < 4; i++)
{
int remarkRow = currentRow + 1 + i;
reportSheet.Cells[remarkRow, 1, remarkRow, 5].Merge = true; // 合并A到E列
reportSheet.Cells[remarkRow, 1].Style.WrapText = true;
reportSheet.Cells[remarkRow, 1].Style.VerticalAlignment = ExcelVerticalAlignment.Top;
reportSheet.Row(remarkRow).Height = 40; // 设置足够的高度
}
// 备注内容
reportSheet.Cells[currentRow + 1, 1].Value = "1. 所有数据应在试验结束后立即记录,确保真实、准确。";
reportSheet.Cells[currentRow + 2, 1].Value = "2. 壁厚和磨损深度需使用精密测量仪器测量。";
reportSheet.Cells[currentRow + 3, 1].Value = "3. 若试验过程中出现异常(如异响、过热、泄漏等),请在备注中说明。";
reportSheet.Cells[currentRow + 4, 1].Value = "4. 磨损量需通过精密电子天平称重(试验前后)。";
currentRow += 5; // 移动到备注之后
// 空行
currentRow += 2;
// 签名行
reportSheet.Cells[currentRow, 1].Value = "测试人签字______";
reportSheet.Cells[currentRow, 4].Value = "审核人签字______";
reportSheet.Row(currentRow).Height = 30;
currentRow++;
reportSheet.Cells[currentRow, 1].Value = "报告日期______年______月______日";
reportSheet.Row(currentRow).Height = 25;
// 设置列宽
reportSheet.Column(1).Width = 23; // A列
reportSheet.Column(2).Width = 30; // B列
reportSheet.Column(3).Width = 20; // C列
reportSheet.Column(4).Width = 20; // D列
reportSheet.Column(5).Width = 20; // E列
// ====== 第二个Sheet曲线数据和图表 ======
ExcelWorksheet curveSheet = excelPackage.Workbook.Worksheets.Add("实时摩擦力曲线数据");
// 获取曲线数据
if (allData.Count == 0)
{
MessageBox.Show("没有曲线数据可导出!", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
// 按时间排序
allData = allData.OrderBy(d => d.Item1.Ticks).ToList();
// 计算时间范围
DateTime firstTime = allData.First().Item1;
DateTime lastTime = allData.Last().Item1;
double totalSeconds = Math.Max(1, (lastTime - firstTime).TotalSeconds);
// ====== 修复2X轴要比最大时间稍大 ======
// 方案1向上取整到最近的整数再加1秒
double xAxisMax = Math.Ceiling(totalSeconds) + 1;
// 方案2确保至少有12秒显示范围如果数据在10秒左右
if (totalSeconds > 8 && totalSeconds < 12)
{
xAxisMax = 12; // 10秒左右的数据显示到12秒
}
else if (xAxisMax < 10)
{
xAxisMax = 10; // 最小显示到10秒
}
Console.WriteLine($"数据最大时间: {totalSeconds:F3}秒, X轴设置: 0-{xAxisMax:F1}秒");
// 设置列宽
curveSheet.Column(1).Width = 15; // A列
curveSheet.Column(2).Width = 20; // B列
curveSheet.Column(3).Width = 20; // C列
// 表头行
curveSheet.Cells["A1"].Value = "时间(秒)";
curveSheet.Cells["B1"].Value = "工位1摩擦力(N)";
curveSheet.Cells["C1"].Value = "工位2摩擦力(N)";
// 设置表头样式
curveSheet.Cells["A1:C1"].Style.Font.Bold = true;
curveSheet.Cells["A1:C1"].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
curveSheet.Cells["A1:C1"].Style.VerticalAlignment = ExcelVerticalAlignment.Center;
curveSheet.Cells["A1:C1"].Style.Border.Bottom.Style = ExcelBorderStyle.Medium;
curveSheet.Row(1).Height = 25;
// 写入数据
int dataRow = 2;
foreach (var data in allData)
{
double seconds = (data.Item1 - firstTime).TotalSeconds;
curveSheet.Cells[dataRow, 1].Value = Math.Round(seconds, 3);
curveSheet.Cells[dataRow, 2].Value = Math.Round(data.Item2, 1);
curveSheet.Cells[dataRow, 3].Value = Math.Round(data.Item3, 1);
// 设置数值格式
curveSheet.Cells[dataRow, 1].Style.Numberformat.Format = "0.000";
curveSheet.Cells[dataRow, 2].Style.Numberformat.Format = "0.00";
curveSheet.Cells[dataRow, 3].Style.Numberformat.Format = "0.00";
dataRow++;
}
int lastDataRow = dataRow - 1;
Console.WriteLine($"数据写入完成: 第2行到第{lastDataRow}行");
// ====== 第二个Sheet曲线数据和图表 ======
if (allData.Count == 0)
{
MessageBox.Show("没有曲线数据可导出!", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
// 按时间排序
allData = allData.OrderBy(d => d.Item1.Ticks).ToList();
if (totalSeconds > 8 && totalSeconds < 12)
{
xAxisMax = 12; // 10秒左右的数据显示到12秒
}
else if (xAxisMax < 10)
{
xAxisMax = 10; // 最小显示到10秒
}
Console.WriteLine($"数据最大时间: {totalSeconds:F3}秒, X轴设置: 0-{xAxisMax:F1}秒");
// 设置列宽
curveSheet.Column(1).Width = 15; // A列
curveSheet.Column(2).Width = 20; // B列
curveSheet.Column(3).Width = 20; // C列
// 表头行
curveSheet.Cells["A1"].Value = "时间(秒)";
curveSheet.Cells["B1"].Value = "工位1摩擦力(N)";
curveSheet.Cells["C1"].Value = "工位2摩擦力(N)";
// 设置表头样式
curveSheet.Cells["A1:C1"].Style.Font.Bold = true;
curveSheet.Cells["A1:C1"].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
curveSheet.Cells["A1:C1"].Style.VerticalAlignment = ExcelVerticalAlignment.Center;
curveSheet.Cells["A1:C1"].Style.Border.Bottom.Style = ExcelBorderStyle.Medium;
curveSheet.Row(1).Height = 25;
Console.WriteLine($"数据写入完成: 第2行到第{lastDataRow}行");
// ====== 创建散点图 ======
var chart = curveSheet.Drawings.AddChart("摩擦力曲线", eChartType.XYScatterLines);
// 设置图表位置和大小
chart.SetPosition(0, 0, 3, 0); // 从第0行第3列开始
chart.SetSize(800, 400); // 宽度800px高度400px
// 设置图表标题
chart.Title.Text = "实时摩擦力曲线";
chart.Title.Font.Size = 14;
chart.Title.Font.Bold = true;
// 添加工位1数据系列
var series1 = chart.Series.Add(
curveSheet.Cells[2, 2, lastDataRow, 2], // Y值B2到B{lastDataRow}
curveSheet.Cells[2, 1, lastDataRow, 1] // X值A2到A{lastDataRow}
);
series1.Header = "工位1摩擦力";
// 添加工位2数据系列
var series2 = chart.Series.Add(
curveSheet.Cells[2, 3, lastDataRow, 3], // Y值C2到C{lastDataRow}
curveSheet.Cells[2, 1, lastDataRow, 1] // X值A2到A{lastDataRow}
);
series2.Header = "工位2摩擦力";
// 设置X轴范围
chart.XAxis.MinValue = 0;
chart.XAxis.MaxValue = xAxisMax;
// 计算合适的刻度
double majorUnit = CalculateMajorUnit(xAxisMax);
chart.XAxis.MajorUnit = majorUnit;
Console.WriteLine($"散点图X轴设置: 0-{xAxisMax:F1}秒, 主要刻度: {majorUnit:F1}秒");
// 设置X轴标题
chart.XAxis.Title.Text = "时间 (秒)";
chart.XAxis.Title.Font.Size = 11;
chart.XAxis.Title.Font.Bold = true;
// 设置Y轴标题
chart.YAxis.Title.Text = "摩擦力 (N)";
chart.YAxis.Title.Font.Size = 11;
chart.YAxis.Title.Font.Bold = true;
// 设置图例位置
chart.Legend.Position = eLegendPosition.Bottom;
// 添加网格线
chart.XAxis.MajorGridlines.Fill.Color = System.Drawing.Color.LightGray;
chart.YAxis.MajorGridlines.Fill.Color = System.Drawing.Color.LightGray;
// ====== 添加信息行 ======
int infoRow = lastDataRow + 2;
curveSheet.Cells[infoRow, 1].Value = "图表信息:";
curveSheet.Cells[infoRow, 1].Style.Font.Bold = true;
curveSheet.Cells[infoRow + 1, 1].Value = "数据时间范围:";
curveSheet.Cells[infoRow + 1, 2].Value = $"0 - {totalSeconds:F3} 秒";
curveSheet.Cells[infoRow + 2, 1].Value = "X轴显示范围:";
curveSheet.Cells[infoRow + 2, 2].Value = $"0 - {xAxisMax:F1} 秒";
curveSheet.Cells[infoRow + 3, 1].Value = "X轴主要刻度:";
curveSheet.Cells[infoRow + 3, 2].Value = $"{majorUnit:F1} 秒";
curveSheet.Cells[infoRow + 4, 1].Value = "数据点数:";
curveSheet.Cells[infoRow + 4, 2].Value = allData.Count;
// 保存文件
FileInfo excelFile = new FileInfo(saveFileDialog.FileName);
excelPackage.SaveAs(excelFile);
MessageBox.Show("滑动摩擦磨损试验报告导出成功!", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
//// 清理数据
ReportList.Clear();
if (CurveDataQueue != null)
{
while (CurveDataQueue.TryDequeue(out _)) { }
}
ReportDataGrid.ItemsSource = ReportList;
OnExportCompleted?.Invoke();
}
}
catch (Exception ex)
{
MessageBox.Show($"导出失败: {ex.Message}\n\n详细错误:\n{ex.StackTrace}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
else
{
MessageBox.Show("没有可导出的数据!", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
// 辅助方法:计算合适的刻度
private double CalculateMajorUnit(double xAxisMax)
{
if (xAxisMax <= 5) return 1; // 0-5秒每1秒一个刻度
if (xAxisMax <= 10) return 2; // 5-10秒每2秒一个刻度
if (xAxisMax <= 20) return 5; // 10-20秒每5秒一个刻度
if (xAxisMax <= 50) return 10; // 20-50秒每10秒一个刻度
if (xAxisMax <= 100) return 20; // 50-100秒每20秒一个刻度
return Math.Ceiling(xAxisMax / 10); // 超过100秒分成10个刻度
}
// EPPlus版本的CreateFormattedRow辅助方法
private void CreateFormattedRowEPPlus(ExcelWorksheet worksheet, int row, string seq, string projectName,
string station1, string station2, string remark)
{
worksheet.Cells[row, 1].Value = seq;
worksheet.Cells[row, 2].Value = projectName;
worksheet.Cells[row, 3].Value = station1;
worksheet.Cells[row, 4].Value = station2;
worksheet.Cells[row, 5].Value = remark;
// 设置样式
for (int col = 1; col <= 5; col++)
{
worksheet.Cells[row, col].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
worksheet.Cells[row, col].Style.VerticalAlignment = ExcelVerticalAlignment.Center;
worksheet.Cells[row, col].Style.Border.Top.Style = ExcelBorderStyle.Thin;
worksheet.Cells[row, col].Style.Border.Bottom.Style = ExcelBorderStyle.Thin;
worksheet.Cells[row, col].Style.Border.Left.Style = ExcelBorderStyle.Thin;
worksheet.Cells[row, col].Style.Border.Right.Style = ExcelBorderStyle.Thin;
}
// 备注列左对齐并自动换行
worksheet.Cells[row, 5].Style.HorizontalAlignment = ExcelHorizontalAlignment.Left;
worksheet.Cells[row, 5].Style.WrapText = true;
worksheet.Row(row).Height = 25;
}
public event Action OnExportCompleted;
}
}