测试报告导出修改
This commit is contained in:
@@ -82,19 +82,23 @@ namespace ExportModule.ViewModels
|
||||
public ICommand LoadedCommand { get; }
|
||||
public ICommand QueryCommand { get; }
|
||||
public ICommand ExportCommand { get; }
|
||||
public ICommand ExportReportCommand { get; }
|
||||
#endregion
|
||||
|
||||
#region 私有字段
|
||||
private readonly ITestReportService _testReportService;
|
||||
private readonly ITestCheckRecordService _testCheckRecordService;
|
||||
#endregion
|
||||
|
||||
public ExportViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
_testReportService = containerProvider.Resolve<ITestReportService>();
|
||||
_testCheckRecordService = containerProvider.Resolve<ITestCheckRecordService>();
|
||||
|
||||
LoadedCommand = new AsyncDelegateCommand(OnLoad);
|
||||
QueryCommand = new AsyncDelegateCommand(OnQuery);
|
||||
ExportCommand = new AsyncDelegateCommand(OnExport);
|
||||
ExportReportCommand = new AsyncDelegateCommand(OnExportReportCommand);
|
||||
}
|
||||
|
||||
#region 命令处理
|
||||
@@ -184,6 +188,40 @@ namespace ExportModule.ViewModels
|
||||
ShowInfoMessageBox($"导出完成,共 {entities.Count} 条步骤记录,已保存至:{dialog.FileName}", () => { });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出测试报告:上层为各测试项(IsTestItem 子程序)的 PASS/NG 汇总,下层为每次 OKExpression 判断明细
|
||||
/// </summary>
|
||||
private async Task OnExportReportCommand()
|
||||
{
|
||||
if (SelectedTestReport == null)
|
||||
{
|
||||
ShowErrorMessageBox("请先在列表中选择一条测试记录。", () => { });
|
||||
return;
|
||||
}
|
||||
|
||||
StatusMessage = "正在查询测试项判断记录...";
|
||||
var checkResult = await _testCheckRecordService.GetByTestRoundIdAsync(SelectedTestReport.TestRoundId);
|
||||
if (!checkResult.IsSuccess || checkResult.Data == null || checkResult.Data.Count == 0)
|
||||
{
|
||||
ShowErrorMessageBox("未找到该测试记录的测试项判断数据(需将子程序标记为测试项后运行)。", () => { });
|
||||
return;
|
||||
}
|
||||
|
||||
var dialog = new SaveFileDialog
|
||||
{
|
||||
Filter = "Excel 工作簿 (*.xlsx)|*.xlsx|所有文件 (*.*)|*.*",
|
||||
DefaultExt = ".xlsx",
|
||||
FileName = $"测试报告_{SelectedTestReport.Scope}_{SelectedTestReport.StartTime:yyyyMMdd_HHmmss}.xlsx"
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() != true) return;
|
||||
|
||||
var records = checkResult.Data;
|
||||
StatusMessage = $"正在导出 {records.Count} 条测试项判断记录...";
|
||||
await Task.Run(() => ExportReportToExcel(dialog.FileName, records, SelectedTestReport));
|
||||
ShowInfoMessageBox($"导出完成,已保存至:{dialog.FileName}", () => { });
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 辅助方法
|
||||
@@ -245,6 +283,134 @@ namespace ExportModule.ViewModels
|
||||
workbook.SaveAs(filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试报告导出:上层为各测试项(IsTestItem 子程序)的 PASS/NG 汇总,下层为每次 OKExpression 判断明细(含重复判断、判断时间、数据值)
|
||||
/// </summary>
|
||||
private static void ExportReportToExcel(string filePath, List<TestCheckRecordEntity> records, TestReportModel report)
|
||||
{
|
||||
using var workbook = new XLWorkbook();
|
||||
var ws = workbook.Worksheets.Add("测试报告");
|
||||
const int colCount = 6;
|
||||
|
||||
var passColor = XLColor.FromArgb(0xB2, 0xFF, 0xB2); // 通过 - 浅绿(与现有导出配色一致)
|
||||
var ngColor = XLColor.FromArgb(0xFF, 0xB2, 0xB2); // 失败 - 浅红
|
||||
var headerColor = XLColor.FromArgb(0xEC, 0xEF, 0xF4);
|
||||
|
||||
int row = 1;
|
||||
|
||||
// ===== 标题与信息行 =====
|
||||
ws.Range(row, 1, row, colCount).Merge();
|
||||
ws.Cell(row, 1).Value = "测 试 报 告";
|
||||
ws.Range(row, 1, row, colCount).Style.Font.Bold = true;
|
||||
ws.Range(row, 1, row, colCount).Style.Font.FontSize = 16;
|
||||
ws.Range(row, 1, row, colCount).Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
|
||||
row++;
|
||||
ws.Cell(row, 1).Value = $"台架:{report.Scope ?? ""}";
|
||||
ws.Cell(row, 3).Value = $"测试文件:{report.FileName ?? ""}";
|
||||
row++;
|
||||
ws.Cell(row, 1).Value = $"开始时间:{report.StartTime:yyyy-MM-dd HH:mm:ss}";
|
||||
ws.Cell(row, 3).Value = $"结束时间:{report.EndTime:yyyy-MM-dd HH:mm:ss}";
|
||||
row += 2;
|
||||
|
||||
// 按测试项分组(保持首次出现顺序)
|
||||
var groups = records.GroupBy(r => r.TestItemName).ToList();
|
||||
|
||||
// ===== 上层:测试项汇总 =====
|
||||
ws.Cell(row, 1).Value = "【测试项汇总】";
|
||||
ws.Cell(row, 1).Style.Font.Bold = true;
|
||||
row++;
|
||||
|
||||
string[] summaryHeaders = { "序号", "测试项", "判定结果", "执行次数", "判定次数", "NG次数" };
|
||||
for (int c = 0; c < summaryHeaders.Length; c++)
|
||||
ws.Cell(row, c + 1).Value = summaryHeaders[c];
|
||||
var headerRange = ws.Range(row, 1, row, colCount);
|
||||
headerRange.Style.Font.Bold = true;
|
||||
headerRange.Style.Fill.BackgroundColor = headerColor;
|
||||
headerRange.Style.Border.OutsideBorder = XLBorderStyleValues.Thin;
|
||||
headerRange.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
|
||||
row++;
|
||||
|
||||
int seq = 1;
|
||||
foreach (var group in groups)
|
||||
{
|
||||
var summaries = group.Where(x => x.IsSummary).ToList();
|
||||
var details = group.Where(x => !x.IsSummary).ToList();
|
||||
bool overallPass = summaries.All(s => s.Pass) && details.All(d => d.Pass);
|
||||
|
||||
ws.Cell(row, 1).Value = seq++;
|
||||
ws.Cell(row, 2).Value = group.Key;
|
||||
ws.Cell(row, 3).Value = overallPass ? "PASS" : "NG";
|
||||
ws.Cell(row, 4).Value = summaries.Count;
|
||||
ws.Cell(row, 5).Value = details.Count;
|
||||
ws.Cell(row, 6).Value = details.Count(d => !d.Pass);
|
||||
|
||||
var dataRange = ws.Range(row, 1, row, colCount);
|
||||
dataRange.Style.Border.OutsideBorder = XLBorderStyleValues.Thin;
|
||||
dataRange.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
|
||||
ws.Cell(row, 3).Style.Font.Bold = true;
|
||||
ws.Cell(row, 3).Style.Fill.BackgroundColor = overallPass ? passColor : ngColor;
|
||||
row++;
|
||||
}
|
||||
row++;
|
||||
|
||||
// ===== 下层:判断明细 =====
|
||||
ws.Cell(row, 1).Value = "【判断明细】";
|
||||
ws.Cell(row, 1).Style.Font.Bold = true;
|
||||
row++;
|
||||
|
||||
string[] detailHeaders = { "序号", "判定时间", "步骤名称", "OKExpression", "判定结果", "数据值" };
|
||||
foreach (var group in groups)
|
||||
{
|
||||
var details = group.Where(x => !x.IsSummary).ToList();
|
||||
bool overallPass = group.Where(x => x.IsSummary).All(s => s.Pass) && details.All(d => d.Pass);
|
||||
|
||||
// 测试项块标题行(合并单元格)
|
||||
ws.Range(row, 1, row, colCount).Merge();
|
||||
ws.Cell(row, 1).Value = $"■ 测试项:{group.Key} 总体结果:{(overallPass ? "PASS" : "NG")}";
|
||||
ws.Cell(row, 1).Style.Font.Bold = true;
|
||||
ws.Range(row, 1, row, colCount).Style.Fill.BackgroundColor = overallPass ? passColor : ngColor;
|
||||
row++;
|
||||
|
||||
for (int c = 0; c < detailHeaders.Length; c++)
|
||||
ws.Cell(row, c + 1).Value = detailHeaders[c];
|
||||
var detailHeaderRange = ws.Range(row, 1, row, colCount);
|
||||
detailHeaderRange.Style.Font.Bold = true;
|
||||
detailHeaderRange.Style.Fill.BackgroundColor = headerColor;
|
||||
detailHeaderRange.Style.Border.OutsideBorder = XLBorderStyleValues.Thin;
|
||||
detailHeaderRange.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
|
||||
row++;
|
||||
|
||||
if (details.Count == 0)
|
||||
{
|
||||
ws.Range(row, 1, row, colCount).Merge();
|
||||
ws.Cell(row, 1).Value = "(无 OKExpression 判断,结果由步骤执行成败决定)";
|
||||
row++;
|
||||
}
|
||||
|
||||
int detailSeq = 1;
|
||||
foreach (var d in details)
|
||||
{
|
||||
ws.Cell(row, 1).Value = detailSeq++;
|
||||
ws.Cell(row, 2).Value = d.CreateTime.ToString("yyyy-MM-dd HH:mm:ss.fff");
|
||||
ws.Cell(row, 3).Value = d.StepName ?? "";
|
||||
ws.Cell(row, 4).Value = d.OKExpression ?? "";
|
||||
ws.Cell(row, 5).Value = d.Pass ? "PASS" : "NG";
|
||||
ws.Cell(row, 6).Value = d.Values ?? "";
|
||||
|
||||
var detailRange = ws.Range(row, 1, row, colCount);
|
||||
detailRange.Style.Border.OutsideBorder = XLBorderStyleValues.Thin;
|
||||
detailRange.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
|
||||
ws.Cell(row, 5).Style.Font.Bold = true;
|
||||
ws.Cell(row, 5).Style.Fill.BackgroundColor = d.Pass ? passColor : ngColor;
|
||||
row++;
|
||||
}
|
||||
row++;
|
||||
}
|
||||
|
||||
ws.Columns().AdjustToContents();
|
||||
workbook.SaveAs(filePath);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 生命周期
|
||||
|
||||
Reference in New Issue
Block a user