SSPC-Tester/SSPCTester.UI/ViewModels/ReportViewModel.cs

343 lines
13 KiB
C#
Raw Permalink Normal View History

using System.IO;
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.Options;
using SSPCTester.Logic.Models;
using SSPCTester.Logic.Report;
using SSPCTester.Logic.Storage;
using SSPCTester.UI.Services;
namespace SSPCTester.UI.ViewModels;
/// <summary>测试报告页。</summary>
public partial class ReportViewModel : ObservableObject
{
private readonly ReportGenerator _gen;
private readonly WordReportGenerator _wordGen;
private readonly ReportOptions _opts;
private readonly ProjectStore _store;
private readonly UiLogSink _log;
private readonly UserSettingsStore _settingsStore;
public ObservableCollection<ReportFileDescriptor> ReportFiles { get; } = new();
public IReadOnlyList<ReportFormatChoice> FormatChoices { get; } = new[]
{
new ReportFormatChoice(ReportOutputFormat.Pdf, "PDF"),
new ReportFormatChoice(ReportOutputFormat.Word, "Word"),
new ReportFormatChoice(ReportOutputFormat.Both, "PDF + Word")
};
[ObservableProperty] private Project? _project;
[ObservableProperty] private string _lastOutputPath = "";
[ObservableProperty] private ReportFileDescriptor? _selectedReportFile;
[ObservableProperty] private ReportFormatChoice? _selectedFormatChoice;
[ObservableProperty] private Uri? _previewSource;
[ObservableProperty] private string _previewMessage = "暂无可预览的报告。";
[ObservableProperty] private bool _hasPreview;
[ObservableProperty] private bool _hasWordInfo;
[ObservableProperty] private string _wordInfoText = "";
[ObservableProperty] private string _selectedFilePath = "";
public ReportViewModel(
ReportGenerator gen,
WordReportGenerator wordGen,
IOptions<ReportOptions> opts,
ProjectStore store,
UiLogSink log,
UserSettingsStore settingsStore)
{
_gen = gen;
_wordGen = wordGen;
_opts = opts.Value;
_store = store;
_log = log;
_settingsStore = settingsStore;
SelectedFormatChoice = FormatChoices.First(x => x.Format == ReportFileHelper.ParseFormat(_opts.Format));
}
public void SetProject(Project p)
{
Project = p;
if (string.IsNullOrWhiteSpace(p.Report.FileName) ||
string.Equals(p.Report.FileName, "report.pdf", StringComparison.OrdinalIgnoreCase) ||
string.Equals(p.Report.FileName, "report_{yyyyMMdd_HHmmss}.pdf", StringComparison.OrdinalIgnoreCase))
{
p.Report.FileName = ReportFileHelper.DefaultTemplate;
}
RefreshReportFiles(selectLatest: true);
}
[RelayCommand]
private async Task Generate()
{
if (Project == null) return;
try
{
await _store.SaveAsync(Project);
string dir = ReportDirectory(Project);
Directory.CreateDirectory(dir);
DateTime now = DateTime.Now;
var format = CurrentFormat;
string? pdfPath = null;
string? wordPath = null;
var curveImages = await BuildCurveImagesAsync(Project);
if (format is ReportOutputFormat.Pdf or ReportOutputFormat.Both)
{
pdfPath = Path.Combine(dir, ReportFileHelper.ResolveFileName(Project, now, ".pdf"));
_gen.CurveImages = curveImages;
_gen.Generate(Project, pdfPath);
}
if (format is ReportOutputFormat.Word or ReportOutputFormat.Both)
{
wordPath = Path.Combine(dir, ReportFileHelper.ResolveFileName(Project, now, ".docx"));
_wordGen.CurveImages = curveImages;
_wordGen.Generate(Project, wordPath);
}
string primaryPath = pdfPath ?? wordPath!;
ReportFileHelper.SaveInfo(BuildInfo(Project, format, now, pdfPath, wordPath), ReportFileHelper.BuildInfoPath(primaryPath));
if (wordPath != null && !string.Equals(ReportFileHelper.BuildInfoPath(wordPath), ReportFileHelper.BuildInfoPath(primaryPath), StringComparison.OrdinalIgnoreCase))
ReportFileHelper.SaveInfo(BuildInfo(Project, format, now, pdfPath, wordPath), ReportFileHelper.BuildInfoPath(wordPath));
LastOutputPath = primaryPath;
RefreshReportFiles(primaryPath);
_log.Add(UiLogLevel.Success, "报告已生成:" + primaryPath);
}
catch (Exception ex)
{
_log.Add(UiLogLevel.Error, "生成报告失败:" + ex.Message);
}
}
[RelayCommand]
private void OpenFolder()
{
if (Project == null) return;
var dir = ReportDirectory(Project);
if (Directory.Exists(dir))
System.Diagnostics.Process.Start("explorer.exe", dir);
}
[RelayCommand]
private void RefreshReports() => RefreshReportFiles(selectLatest: true);
[RelayCommand]
private void OpenSelectedFile()
{
if (SelectedReportFile == null || !File.Exists(SelectedReportFile.FullPath)) return;
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(SelectedReportFile.FullPath)
{
UseShellExecute = true
});
}
partial void OnSelectedFormatChoiceChanged(ReportFormatChoice? value)
{
if (value == null) return;
_opts.Format = value.Format.ToString();
RefreshReportFiles(selectLatest: true);
}
partial void OnSelectedReportFileChanged(ReportFileDescriptor? value)
{
if (value == null)
{
ClearPreview("暂无可预览的报告。");
return;
}
SelectedFilePath = value.FullPath;
if (value.IsPdf)
LoadPreview(value.FullPath);
else
LoadWordInfo(value.FullPath);
}
public void PreviewUnavailable(string message) => ClearPreview(message);
private void RefreshReportFiles(string? selectPath = null, bool selectLatest = false)
{
ReportFiles.Clear();
var project = Project;
if (project == null)
{
ClearPreview("暂无项目,无法预览报告。");
return;
}
foreach (var file in ReportFileHelper.EnumerateReportFiles(ReportDirectory(project), CurrentFormat))
ReportFiles.Add(file);
SelectedReportFile = selectPath == null
? selectLatest ? DefaultSelectedReportFile() : SelectedReportFile
: ReportFiles.FirstOrDefault(x => string.Equals(x.FullPath, selectPath, StringComparison.OrdinalIgnoreCase));
if (SelectedReportFile == null)
ClearPreview(CurrentFormat == ReportOutputFormat.Word
? "当前报告目录下暂无 Word 报告。生成报告后将在这里显示文件信息。"
: "当前报告目录下暂无报告。生成报告后将在这里预览或显示文件信息。");
}
private void LoadPreview(string path)
{
if (!File.Exists(path))
{
ClearPreview("选中的报告文件不存在。");
return;
}
LastOutputPath = path;
PreviewSource = new Uri(path);
PreviewMessage = "";
HasPreview = true;
var info = ReportFileHelper.LoadInfoForReport(path);
if (!string.IsNullOrWhiteSpace(info?.WordPath))
SetWordInfo(info, path);
else
HasWordInfo = false;
}
private void LoadWordInfo(string path)
{
if (!File.Exists(path))
{
ClearPreview("选中的 Word 报告文件不存在。");
return;
}
LastOutputPath = path;
HasPreview = false;
PreviewSource = null;
PreviewMessage = "";
SetWordInfo(ReportFileHelper.LoadInfoForReport(path), path);
}
private void ClearPreview(string message)
{
HasPreview = false;
PreviewSource = null;
PreviewMessage = message;
HasWordInfo = false;
WordInfoText = "";
SelectedFilePath = "";
}
private string ReportDirectory(Project project) =>
string.IsNullOrWhiteSpace(project.FolderPath) ? _opts.SavePath : project.FolderPath;
private ReportOutputFormat CurrentFormat => SelectedFormatChoice?.Format ?? ReportFileHelper.ParseFormat(_opts.Format);
private ReportFileDescriptor? DefaultSelectedReportFile()
{
if (CurrentFormat == ReportOutputFormat.Both)
return ReportFiles.FirstOrDefault(x => x.IsPdf) ?? ReportFiles.FirstOrDefault();
return ReportFiles.FirstOrDefault();
}
private static ReportInfo BuildInfo(Project project, ReportOutputFormat format, DateTime generatedAt, string? pdfPath, string? wordPath) =>
new()
{
ProjectName = project.Name,
ProductName = project.Dut.ProductName,
Model = project.Dut.Model,
SerialNumber = project.Dut.SerialNumber,
GeneratedAt = generatedAt,
Format = format.ToString(),
PdfPath = pdfPath,
WordPath = wordPath,
OverallStatus = project.OverallStatus.ToString(),
Sections = new ReportSectionInfo
{
Cover = project.Report.Cover,
Basic = project.Report.Basic,
Curve = project.Report.Curve,
Power = project.Report.Power,
Protect = project.Report.Protect
}
};
private void SetWordInfo(ReportInfo? info, string selectedPath)
{
var file = new FileInfo(selectedPath);
string? wordPath = info?.WordPath;
string? pdfPath = info?.PdfPath;
var lines = new List<string>
{
$"文件名:{file.Name}",
$"完整路径:{file.FullName}",
$"文件大小:{FormatBytes(file.Length)}",
$"修改时间:{file.LastWriteTime:yyyy-MM-dd HH:mm:ss}",
$"格式:{(file.Extension.Equals(".docx", StringComparison.OrdinalIgnoreCase) ? "Word" : "PDF")}"
};
if (info != null)
{
lines.Add($"项目名称:{info.ProjectName}");
lines.Add($"设备名称:{info.ProductName}");
lines.Add($"设备型号:{info.Model}");
lines.Add($"设备编号:{info.SerialNumber}");
lines.Add($"生成时间:{info.GeneratedAt:yyyy-MM-dd HH:mm:ss}");
lines.Add($"输出格式:{info.Format}");
lines.Add($"综合判定:{info.OverallStatus}");
lines.Add($"关联 PDF{(string.IsNullOrWhiteSpace(pdfPath) ? "" : File.Exists(pdfPath) ? pdfPath : "")}");
lines.Add($"关联 Word{(string.IsNullOrWhiteSpace(wordPath) ? "" : File.Exists(wordPath) ? wordPath : "")}");
}
else
{
lines.Add("报告信息文件:未找到,已显示文件系统信息。");
}
WordInfoText = string.Join(Environment.NewLine, lines);
HasWordInfo = true;
}
private static string FormatBytes(long bytes)
{
if (bytes >= 1024 * 1024) return $"{bytes / 1024d / 1024d:F2} MB";
if (bytes >= 1024) return $"{bytes / 1024d:F1} KB";
return $"{bytes} B";
}
private async Task<Dictionary<int, CurveReportImages>> BuildCurveImagesAsync(Project project)
{
var images = new Dictionary<int, CurveReportImages>();
foreach (var row in project.CurveResults.OrderBy(x => x.Channel))
{
try
{
var rising = await WaveformStore.LoadAsync(project, row.RisingWaveformPath);
var falling = await WaveformStore.LoadAsync(project, row.FallingWaveformPath);
if (rising == null && falling == null) continue;
images[row.Channel] = new CurveReportImages(
rising == null
? null
: CurveImageRenderer.RenderRisingChannel(
row.Channel,
rising,
row.RisingViewStartMs,
row.RisingViewEndMs),
falling == null
? null
: CurveImageRenderer.RenderFallingChannel(
row.Channel,
falling,
row.FallingViewStartMs,
row.FallingViewEndMs));
}
catch (Exception ex)
{
_log.Add(UiLogLevel.Warning, $"CH{row.Channel} 曲线插图生成失败:{ex.Message}");
}
}
return images;
}
}
public sealed record ReportFormatChoice(ReportOutputFormat Format, string DisplayName);