feat(wpf): 实现场景列表加载、筛选与消息导航功能

- ScenarioService新增筛选查询方法和动态筛选选项
- 使用WeakReferenceMessenger实现导入完成通知和导航消息
- ViewModel改为Singleton生命周期确保消息订阅正确工作
- 场景列表支持数据库加载和按维度筛选
- ScenarioId类型从int改为Guid

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ssss
2026-07-06 10:23:41 +08:00
parent 440088648b
commit 52435ab8cd
9 changed files with 498 additions and 61 deletions

View File

@@ -1,9 +1,11 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.Logging;
using Microsoft.Win32;
using System.Collections.ObjectModel;
using WCTDataMiner.Core.Services;
using WCTDataMiner.Wpf.Messages;
namespace WCTDataMiner.Wpf.ViewModels;
@@ -99,6 +101,9 @@ public partial class ImportViewModel : ObservableObject
StatusMessage = $"导入完成: Qfod={report.QfodCount}, Ploss={report.PlossCount}, 错误={report.ErrorCount}";
_logger.LogInformation("单文件导入完成: {FileName}, Qfod={Qfod}, Ploss={Ploss}, 错误={Errors}",
report.FileName, report.QfodCount, report.PlossCount, report.ErrorCount);
// 发送导入完成消息,通知其他 ViewModel 刷新数据
WeakReferenceMessenger.Default.Send(new DataImportedMessage(1));
}
else if (System.IO.Directory.Exists(SelectedPath))
{
@@ -118,6 +123,9 @@ public partial class ImportViewModel : ObservableObject
StatusMessage = $"导入完成: {reports.Count} 个文件, Qfod={totalQfod}, Ploss={totalPloss}, 错误={totalErrors}";
_logger.LogInformation("目录导入完成: {FileCount} 个文件, Qfod={Qfod}, Ploss={Ploss}, 错误={Errors}",
reports.Count, totalQfod, totalPloss, totalErrors);
// 发送导入完成消息,通知其他 ViewModel 刷新数据
WeakReferenceMessenger.Default.Send(new DataImportedMessage(reports.Count));
}
else
{

View File

@@ -1,15 +1,22 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.DependencyInjection;
using WCTDataMiner.Core.Services;
using WCTDataMiner.Wpf.Messages;
namespace WCTDataMiner.Wpf.ViewModels;
/// <summary>
/// 主窗口 ViewModel - 导航控制
/// </summary>
public partial class MainViewModel : ObservableObject
public partial class MainViewModel : ObservableObject,
IRecipient<NavigateToPlossChartMessage>,
IRecipient<NavigateToQfodChartMessage>
{
private readonly ImportViewModel _importViewModel;
private readonly ScenarioListViewModel _scenarioListViewModel;
private readonly IServiceProvider _serviceProvider;
[ObservableProperty]
private ObservableObject _currentView = null!;
@@ -26,17 +33,48 @@ public partial class MainViewModel : ObservableObject
[ObservableProperty]
private string? _selectedRxType;
// 筛选选项列表
public List<string> TxPanels { get; } = new() { "全部", "singleMold", "dualMold" };
public List<string> TxHardwares { get; } = new() { "全部", "v1.0", "v2.0" };
public List<string> TxSoftwares { get; } = new() { "全部", "hex2_1", "hex2_2" };
public List<string> RxTypes { get; } = new() { "全部", "iPhone15", "iPhone16", "Android" };
// 筛选选项列表(动态加载)
[ObservableProperty]
private List<string> _txPanels = new() { FilterConstants.AllOption };
public MainViewModel(ImportViewModel importViewModel, ScenarioListViewModel scenarioListViewModel)
[ObservableProperty]
private List<string> _txHardwares = new() { FilterConstants.AllOption };
[ObservableProperty]
private List<string> _txSoftwares = new() { FilterConstants.AllOption };
[ObservableProperty]
private List<string> _rxTypes = new() { FilterConstants.AllOption };
public MainViewModel(
ImportViewModel importViewModel,
ScenarioListViewModel scenarioListViewModel,
IServiceProvider serviceProvider)
{
_importViewModel = importViewModel;
_scenarioListViewModel = scenarioListViewModel;
_serviceProvider = serviceProvider;
_currentView = _scenarioListViewModel;
// 注册导航消息订阅
WeakReferenceMessenger.Default.Register<NavigateToPlossChartMessage>(this);
WeakReferenceMessenger.Default.Register<NavigateToQfodChartMessage>(this);
}
/// <summary>
/// 初始化加载场景数据和筛选选项
/// </summary>
public async Task InitializeAsync()
{
// 加载筛选选项
var filterOptions = await _scenarioListViewModel.GetFilterOptionsAsync();
TxPanels = filterOptions.TxPanels;
TxHardwares = filterOptions.TxHardwares;
TxSoftwares = filterOptions.TxSoftwares;
RxTypes = filterOptions.RxTypes;
// 加载场景列表
await _scenarioListViewModel.LoadScenariosAsync();
}
[RelayCommand]
@@ -46,20 +84,54 @@ public partial class MainViewModel : ObservableObject
}
[RelayCommand]
private void NavigateToScenarios()
private async Task NavigateToScenarios()
{
CurrentView = _scenarioListViewModel;
await _scenarioListViewModel.LoadScenariosAsync();
}
[RelayCommand]
private void NavigateToPlossChart(int scenarioId)
private void NavigateToPlossChart(Guid scenarioId)
{
CurrentView = new PlossChartViewModel { ScenarioId = scenarioId };
var vm = _serviceProvider.GetRequiredService<PlossChartViewModel>();
vm.ScenarioId = scenarioId;
CurrentView = vm;
}
[RelayCommand]
private void NavigateToQfodChart(int scenarioId)
private void NavigateToQfodChart(Guid scenarioId)
{
CurrentView = new QfodChartViewModel { ScenarioId = scenarioId };
var vm = _serviceProvider.GetRequiredService<QfodChartViewModel>();
vm.ScenarioId = scenarioId;
CurrentView = vm;
}
}
/// <summary>
/// 应用筛选条件并刷新场景列表
/// </summary>
public async Task ApplyFilterAsync()
{
await _scenarioListViewModel.LoadScenariosByFilterAsync(
SelectedTxPanel, SelectedTxHardware, SelectedTxSoftware, SelectedRxType);
}
/// <summary>
/// 接收导航到 Ploss 图表消息
/// </summary>
public void Receive(NavigateToPlossChartMessage message)
{
var vm = _serviceProvider.GetRequiredService<PlossChartViewModel>();
vm.ScenarioId = message.ScenarioId;
CurrentView = vm;
}
/// <summary>
/// 接收导航到 Qfod 图表消息
/// </summary>
public void Receive(NavigateToQfodChartMessage message)
{
var vm = _serviceProvider.GetRequiredService<QfodChartViewModel>();
vm.ScenarioId = message.ScenarioId;
CurrentView = vm;
}
}

View File

@@ -1,6 +1,8 @@
using CommunityToolkit.Mvvm.ComponentModel;
using LiveChartsCore;
using LiveChartsCore.SkiaSharpView;
using Microsoft.Extensions.Logging;
using WCTDataMiner.Core.Services;
namespace WCTDataMiner.Wpf.ViewModels;
@@ -9,8 +11,11 @@ namespace WCTDataMiner.Wpf.ViewModels;
/// </summary>
public partial class PlossChartViewModel : ObservableObject
{
private readonly ScenarioService _scenarioService;
private readonly ILogger<PlossChartViewModel> _logger;
[ObservableProperty]
private int _scenarioId;
private Guid _scenarioId;
[ObservableProperty]
private ISeries[] _series;
@@ -21,8 +26,13 @@ public partial class PlossChartViewModel : ObservableObject
[ObservableProperty]
private Axis[] _yAxes;
public PlossChartViewModel()
public PlossChartViewModel(
ScenarioService scenarioService,
ILogger<PlossChartViewModel> logger)
{
_scenarioService = scenarioService;
_logger = logger;
// 初始化图表配置
Series = Array.Empty<ISeries>();
XAxes = new Axis[] { new Axis { Name = "Trigger Count" } };
@@ -31,19 +41,26 @@ public partial class PlossChartViewModel : ObservableObject
public async Task LoadDataAsync()
{
// TODO: 从数据库加载 Ploss 数据并构建图表
Series = new ISeries[]
try
{
new LineSeries<double>
// TODO: 从数据库加载 Ploss 数据并构建图表
Series = new ISeries[]
{
Name = "Ploss",
Values = new double[] { -2882, -3500, -4200, -3800, -4500 }
},
new LineSeries<double>
{
Name = "Threshold",
Values = new double[] { -3000, -3000, -3000, -3000, -3000 }
}
};
new LineSeries<double>
{
Name = "Ploss",
Values = new double[] { -2882, -3500, -4200, -3800, -4500 }
},
new LineSeries<double>
{
Name = "Threshold",
Values = new double[] { -3000, -3000, -3000, -3000, -3000 }
}
};
}
catch (Exception ex)
{
_logger.LogError(ex, "加载 Ploss 图表数据失败: ScenarioId={ScenarioId}", ScenarioId);
}
}
}
}

View File

@@ -1,6 +1,8 @@
using CommunityToolkit.Mvvm.ComponentModel;
using LiveChartsCore;
using LiveChartsCore.SkiaSharpView;
using Microsoft.Extensions.Logging;
using WCTDataMiner.Core.Services;
namespace WCTDataMiner.Wpf.ViewModels;
@@ -9,8 +11,11 @@ namespace WCTDataMiner.Wpf.ViewModels;
/// </summary>
public partial class QfodChartViewModel : ObservableObject
{
private readonly ScenarioService _scenarioService;
private readonly ILogger<QfodChartViewModel> _logger;
[ObservableProperty]
private int _scenarioId;
private Guid _scenarioId;
[ObservableProperty]
private ISeries[] _series;
@@ -21,8 +26,13 @@ public partial class QfodChartViewModel : ObservableObject
[ObservableProperty]
private Axis[] _yAxes;
public QfodChartViewModel()
public QfodChartViewModel(
ScenarioService scenarioService,
ILogger<QfodChartViewModel> logger)
{
_scenarioService = scenarioService;
_logger = logger;
// 初始化图表配置
Series = Array.Empty<ISeries>();
XAxes = new Axis[] { new Axis { Name = "Coil Index" } };
@@ -31,19 +41,26 @@ public partial class QfodChartViewModel : ObservableObject
public async Task LoadDataAsync()
{
// TODO: 从数据库加载 Qfod 数据并构建图表
Series = new ISeries[]
try
{
new LineSeries<double>
// TODO: 从数据库加载 Qfod 数据并构建图表
Series = new ISeries[]
{
Name = "DeltaQ",
Values = new double[] { 25, 30, 28 }
},
new LineSeries<double>
{
Name = "CurrentQ",
Values = new double[] { 45.5, 50.2, 48.8 }
}
};
new LineSeries<double>
{
Name = "DeltaQ",
Values = new double[] { 25, 30, 28 }
},
new LineSeries<double>
{
Name = "CurrentQ",
Values = new double[] { 45.5, 50.2, 48.8 }
}
};
}
catch (Exception ex)
{
_logger.LogError(ex, "加载 Qfod 图表数据失败: ScenarioId={ScenarioId}", ScenarioId);
}
}
}
}

View File

@@ -1,22 +1,163 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.Logging;
using System.Collections.ObjectModel;
using WCTDataMiner.Core.Services;
using WCTDataMiner.Wpf.Messages;
namespace WCTDataMiner.Wpf.ViewModels;
/// <summary>
/// 场景列表 ViewModel
/// </summary>
public partial class ScenarioListViewModel : ObservableObject
public partial class ScenarioListViewModel : ObservableObject, IRecipient<DataImportedMessage>
{
private readonly ScenarioService _scenarioService;
private readonly ILogger<ScenarioListViewModel> _logger;
[ObservableProperty]
private List<ScenarioItem> _scenarios;
private ObservableCollection<ScenarioItem> _scenarios = new();
[ObservableProperty]
private ScenarioItem? _selectedScenario;
public ScenarioListViewModel()
[ObservableProperty]
private bool _isLoading;
[ObservableProperty]
private string _loadingMessage = "";
public ScenarioListViewModel(
ScenarioService scenarioService,
ILogger<ScenarioListViewModel> logger)
{
// TODO: 从数据库加载场景列表
_scenarios = new List<ScenarioItem>();
_scenarioService = scenarioService;
_logger = logger;
// 注册消息订阅,接收导入完成通知
WeakReferenceMessenger.Default.Register<DataImportedMessage>(this);
}
/// <summary>
/// 从数据库加载场景列表
/// </summary>
public async Task LoadScenariosAsync()
{
// 避免并发加载
if (IsLoading)
{
_logger.LogWarning("跳过重复加载请求:当前正在加载");
return;
}
IsLoading = true;
LoadingMessage = "正在加载场景列表...";
try
{
var scenarios = await _scenarioService.GetAllScenariosAsync();
// 批量更新,避免逐项 Add 导致频繁 UI 刷新
var items = scenarios.Select(s => new ScenarioItem
{
Id = s.Id,
TxPanel = s.TxPanel?.Name ?? "",
TxHardware = s.TxHardware?.Version ?? "",
TxSoftware = s.TxSoftware?.Version ?? "",
RxType = s.RxType?.Name ?? "",
TestDate = s.TestDate.ToString("yyyy-MM-dd"),
QfodCount = s.QfodCount,
PlossCount = s.PlossCount
}).ToList();
Scenarios = new ObservableCollection<ScenarioItem>(items);
}
catch (Exception ex)
{
_logger.LogError(ex, "加载场景列表失败");
LoadingMessage = $"加载失败: {ex.Message}";
return;
}
finally
{
IsLoading = false;
LoadingMessage = "";
}
}
/// <summary>
/// 根据筛选条件加载场景
/// </summary>
public async Task LoadScenariosByFilterAsync(
string? txPanel = null,
string? txHardware = null,
string? txSoftware = null,
string? rxType = null)
{
// 避免并发加载
if (IsLoading)
{
_logger.LogWarning("跳过重复筛选请求:当前正在加载");
return;
}
IsLoading = true;
LoadingMessage = "正在筛选场景...";
try
{
var scenarios = await _scenarioService.GetScenariosByFilterAsync(
txPanel, txHardware, txSoftware, rxType);
// 批量更新,避免逐项 Add 导致频繁 UI 刷新
var items = scenarios.Select(s => new ScenarioItem
{
Id = s.Id,
TxPanel = s.TxPanel?.Name ?? "",
TxHardware = s.TxHardware?.Version ?? "",
TxSoftware = s.TxSoftware?.Version ?? "",
RxType = s.RxType?.Name ?? "",
TestDate = s.TestDate.ToString("yyyy-MM-dd"),
QfodCount = s.QfodCount,
PlossCount = s.PlossCount
}).ToList();
Scenarios = new ObservableCollection<ScenarioItem>(items);
}
catch (Exception ex)
{
_logger.LogError(ex, "筛选场景失败");
LoadingMessage = $"筛选失败: {ex.Message}";
return;
}
finally
{
IsLoading = false;
LoadingMessage = "";
}
}
/// <summary>
/// 接收数据导入完成消息,自动刷新列表
/// </summary>
public async void Receive(DataImportedMessage message)
{
try
{
await LoadScenariosAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "接收数据导入消息后刷新场景列表失败");
}
}
/// <summary>
/// 获取可用的筛选选项
/// </summary>
public async Task<FilterOptions> GetFilterOptionsAsync()
{
return await _scenarioService.GetFilterOptionsAsync();
}
}
@@ -25,7 +166,7 @@ public partial class ScenarioListViewModel : ObservableObject
/// </summary>
public class ScenarioItem
{
public int Id { get; set; }
public Guid Id { get; set; }
public string TxPanel { get; set; } = "";
public string TxHardware { get; set; } = "";
public string TxSoftware { get; set; } = "";
@@ -33,4 +174,4 @@ public class ScenarioItem
public string TestDate { get; set; } = "";
public int QfodCount { get; set; }
public int PlossCount { get; set; }
}
}