添加项目文件。

This commit is contained in:
xyy
2026-03-11 16:42:31 +08:00
parent ab6c623f56
commit 84fae8c87c
61 changed files with 4314 additions and 0 deletions

537
ReportPage.xaml.cs Normal file
View File

@@ -0,0 +1,537 @@
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;
}
}