using OfficeOpenXml; using OfficeOpenXml.Drawing.Chart; using OfficeOpenXml.Style; using System; using System.Collections.Generic; using System.IO; using System.Linq; using TabletTester2025.Models; namespace TabletTester2025.Services { public class ExcelExportService { public void ExportToExcel(IEnumerable batches, string filePath) { using var package = new ExcelPackage(new FileInfo(filePath)); var hardnessData = batches.Where(b => b.TestType == "硬度").ToList(); var friabilityData = batches.Where(b => b.TestType == "脆碎度").ToList(); var disintegrationData = batches.Where(b => b.TestType == "崩解").ToList(); var dissolutionData = batches.Where(b => b.TestType == "溶出").ToList(); AddHardnessSheet(package, hardnessData); AddFriabilitySheet(package, friabilityData); AddDisintegrationSheet(package, disintegrationData); AddDissolutionSheet(package, dissolutionData); package.Save(); } public void ExportHardnessToExcel(IEnumerable batches, string filePath) { using var package = new ExcelPackage(new FileInfo(filePath)); AddHardnessSheet(package, batches); package.Save(); } public void ExportFriabilityToExcel(IEnumerable batches, string filePath) { using var package = new ExcelPackage(new FileInfo(filePath)); AddFriabilitySheet(package, batches); package.Save(); } public void ExportDisintegrationToExcel(IEnumerable batches, string filePath) { using var package = new ExcelPackage(new FileInfo(filePath)); AddDisintegrationSheet(package, batches); package.Save(); } public void ExportDissolutionToExcel(IEnumerable batches, string filePath) { using var package = new ExcelPackage(new FileInfo(filePath)); AddDissolutionSheet(package, batches); package.Save(); } private static void AddHardnessSheet(ExcelPackage package, IEnumerable batches) { var data = batches.ToList(); var sheet = package.Workbook.Worksheets.Add("硬度报表"); WriteHeader(sheet, "检测时间", "样品名称", "平均值(N)", "平均偏差(N)", "RSD(%)", "最大值(N)", "测试次数", "单次数据(N)", "判定"); if (data.Count == 0) { sheet.Cells[2, 1].Value = "无硬度测试数据"; sheet.Cells.AutoFitColumns(); return; } int row = 2; foreach (var b in data) { sheet.Cells[row, 1].Value = b.TestTime.ToString("yyyy-MM-dd HH:mm:ss"); sheet.Cells[row, 2].Value = b.SampleName; sheet.Cells[row, 3].Value = b.HardnessAvg; sheet.Cells[row, 4].Value = b.HardnessAverageDeviation; sheet.Cells[row, 5].Value = b.HardnessRSD; sheet.Cells[row, 6].Value = b.HardnessMax; sheet.Cells[row, 7].Value = b.HardnessTestCount; sheet.Cells[row, 8].Value = b.HardnessSampleSummary; sheet.Cells[row, 9].Value = b.HardnessPassText; row++; } sheet.Cells.AutoFitColumns(); AddHardnessSamplesSheet(package, data); } private static void AddHardnessSamplesSheet(ExcelPackage package, IEnumerable batches) { var samples = GetHardnessSampleRows(batches).ToList(); if (samples.Count == 0) return; var sheet = package.Workbook.Worksheets.Add("硬度单次明细"); WriteHeader(sheet, "检测时间", "样品名称", "序号", "硬度值(N)", "与平均值偏差(N)", "记录时间"); int row = 2; foreach (var item in samples) { sheet.Cells[row, 1].Value = item.Batch.TestTime.ToString("yyyy-MM-dd HH:mm:ss"); sheet.Cells[row, 2].Value = item.Batch.SampleName; sheet.Cells[row, 3].Value = item.Sample.SequenceNo; sheet.Cells[row, 4].Value = item.Sample.Value; sheet.Cells[row, 5].Value = item.Sample.DeviationFromAverage; sheet.Cells[row, 6].Value = item.Sample.RecordedAt.ToString("yyyy-MM-dd HH:mm:ss"); row++; } sheet.Cells.AutoFitColumns(); } private static void AddFriabilitySheet(ExcelPackage package, IEnumerable batches) { var data = batches.ToList(); var sheet = package.Workbook.Worksheets.Add("脆碎度报表"); WriteHeader(sheet, "检测时间", "样品名称", "失重率(%)", "设定转速(r/min)", "试验时间(min)", "试验转数", "前重(g)", "后重(g)", "判定"); if (data.Count == 0) { sheet.Cells[2, 1].Value = "无脆碎度测试数据"; sheet.Cells.AutoFitColumns(); return; } int row = 2; foreach (var b in data) { sheet.Cells[row, 1].Value = b.TestTime.ToString("yyyy-MM-dd HH:mm:ss"); sheet.Cells[row, 2].Value = b.SampleName; sheet.Cells[row, 3].Value = b.FriabilityLoss; sheet.Cells[row, 4].Value = b.FriabilityTargetRpm; sheet.Cells[row, 5].Value = b.FriabilityTargetTimeMin; sheet.Cells[row, 6].Value = b.FriabilityTargetRounds; sheet.Cells[row, 7].Value = b.WeightBefore; sheet.Cells[row, 8].Value = b.WeightAfter; sheet.Cells[row, 9].Value = b.FriabilityPassText; row++; } sheet.Cells.AutoFitColumns(); } private static void AddDisintegrationSheet(ExcelPackage package, IEnumerable batches) { var data = batches.ToList(); var sheet = package.Workbook.Worksheets.Add("崩解报表"); WriteHeader(sheet, "检测时间", "样品名称", "崩解时间(秒)", "水浴温度(℃)", "判定"); if (data.Count == 0) { sheet.Cells[2, 1].Value = "无崩解测试数据"; sheet.Cells.AutoFitColumns(); return; } int row = 2; foreach (var b in data) { sheet.Cells[row, 1].Value = b.TestTime.ToString("yyyy-MM-dd HH:mm:ss"); sheet.Cells[row, 2].Value = b.SampleName; sheet.Cells[row, 3].Value = b.DisintegrationTimeSec; sheet.Cells[row, 4].Value = b.DisintegrationTemp; sheet.Cells[row, 5].Value = b.DisintegrationPassText; row++; } sheet.Cells.AutoFitColumns(); } private static void AddDissolutionSheet(ExcelPackage package, IEnumerable batches) { var data = batches.ToList(); var sheet = package.Workbook.Worksheets.Add("溶出报表"); WriteHeader(sheet, "检测时间", "样品名称", "通道", "30min溶出度(%)", "R²", "取样明细", "判定"); if (data.Count == 0) { sheet.Cells[2, 1].Value = "无溶出测试数据"; sheet.Cells.AutoFitColumns(); return; } int row = 2; foreach (var b in data) { sheet.Cells[row, 1].Value = b.TestTime.ToString("yyyy-MM-dd HH:mm:ss"); sheet.Cells[row, 2].Value = b.SampleName; sheet.Cells[row, 3].Value = b.DissolutionChannel; sheet.Cells[row, 4].Value = b.DissolutionRate30Min; sheet.Cells[row, 5].Value = b.DissolutionRSquared; sheet.Cells[row, 6].Value = b.DissolutionSampleSummary; sheet.Cells[row, 7].Value = b.DissolutionPassText; row++; } sheet.Cells.AutoFitColumns(); AddDissolutionSamplesSheet(package, data); AddDissolutionChartSheet(package, data); } private static void AddDissolutionSamplesSheet(ExcelPackage package, IEnumerable batches) { var samples = GetDissolutionSampleRows(batches).ToList(); if (samples.Count == 0) return; var sheet = package.Workbook.Worksheets.Add("溶出取样明细"); WriteHeader(sheet, "检测时间", "样品名称", "通道", "计划时间(min)", "实际时间(min)", "溶出度(%)", "记录时间"); int row = 2; foreach (var item in samples) { sheet.Cells[row, 1].Value = item.Batch.TestTime.ToString("yyyy-MM-dd HH:mm:ss"); sheet.Cells[row, 2].Value = item.Batch.SampleName; sheet.Cells[row, 3].Value = item.Sample.ChannelName; sheet.Cells[row, 4].Value = item.Sample.ScheduledTimeMin; sheet.Cells[row, 5].Value = item.Sample.ActualTimeMin; sheet.Cells[row, 6].Value = item.Sample.Percent; sheet.Cells[row, 7].Value = item.Sample.RecordedAt?.ToString("yyyy-MM-dd HH:mm:ss"); row++; } sheet.Cells.AutoFitColumns(); } private static void AddDissolutionChartSheet(ExcelPackage package, IEnumerable batches) { var seriesGroups = GetDissolutionSampleRows(batches) .Where(x => x.Sample.Percent.HasValue && double.IsFinite(x.Sample.ScheduledTimeMin) && x.Sample.ScheduledTimeMin >= 0 && double.IsFinite(x.Sample.Percent.Value)) .GroupBy(x => new { x.Batch.Id, x.Batch.TestTime, x.Batch.SampleName, x.Sample.ChannelName }) .Select(group => new { Name = $"{group.Key.TestTime:MM-dd HH:mm} {group.Key.SampleName} {group.Key.ChannelName}", Points = group .OrderBy(x => x.Sample.ScheduledTimeMin) .Select(x => new { Time = x.Sample.ScheduledTimeMin, Percent = x.Sample.Percent!.Value }) .ToList() }) .Where(group => group.Points.Count > 0) .ToList(); var sheet = package.Workbook.Worksheets.Add("溶出曲线"); if (seriesGroups.Count == 0) { sheet.Cells[1, 1].Value = "无可绘制溶出曲线数据"; sheet.Cells.AutoFitColumns(); return; } int column = 1; foreach (var group in seriesGroups) { sheet.Cells[1, column].Value = $"{group.Name} 时间(min)"; sheet.Cells[1, column + 1].Value = $"{group.Name} 溶出度(%)"; int row = 2; foreach (var point in group.Points) { sheet.Cells[row, column].Value = point.Time; sheet.Cells[row, column + 1].Value = point.Percent; row++; } column += 3; } using (var header = sheet.Cells[1, 1, 1, Math.Max(1, column - 2)]) { header.Style.Font.Bold = true; } int dataEndRow = seriesGroups.Max(group => group.Points.Count) + 1; int chartStartRow = dataEndRow + 2; var chart = sheet.Drawings.AddChart("DissolutionCurve", eChartType.LineMarkers); chart.Title.Text = "溶出曲线"; chart.SetPosition(chartStartRow - 1, 0, 0, 0); chart.SetSize(900, 420); chart.XAxis.Title.Text = "计划时间(min)"; chart.YAxis.Title.Text = "溶出度(%)"; chart.YAxis.MinValue = 0; chart.YAxis.MaxValue = 150; chart.Legend.Position = eLegendPosition.Bottom; column = 1; foreach (var group in seriesGroups) { int endRow = group.Points.Count + 1; var series = chart.Series.Add( sheet.Cells[2, column + 1, endRow, column + 1], sheet.Cells[2, column, endRow, column]); series.Header = group.Name; column += 3; } sheet.Cells.AutoFitColumns(); } private static IEnumerable<(TestBatch Batch, DissolutionSamplePoint Sample)> GetDissolutionSampleRows(IEnumerable batches) { return batches .SelectMany(batch => (batch.DissolutionSamples ?? Enumerable.Empty()) .Select(sample => (Batch: batch, Sample: sample))) .OrderBy(x => x.Batch.TestTime) .ThenBy(x => x.Sample.Channel) .ThenBy(x => x.Sample.ScheduledTimeMin); } private static IEnumerable<(TestBatch Batch, HardnessSamplePoint Sample)> GetHardnessSampleRows(IEnumerable batches) { return batches .SelectMany(batch => (batch.HardnessSamples ?? Enumerable.Empty()) .Select(sample => (Batch: batch, Sample: sample))) .OrderBy(x => x.Batch.TestTime) .ThenBy(x => x.Sample.SequenceNo); } private static void WriteHeader(ExcelWorksheet sheet, params string[] headers) { for (int i = 0; i < headers.Length; i++) sheet.Cells[1, i + 1].Value = headers[i]; using var range = sheet.Cells[1, 1, 1, headers.Length]; range.Style.Font.Bold = true; range.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center; } } }