feat(wpf): 实现场景列表加载、筛选与消息导航功能
- ScenarioService新增筛选查询方法和动态筛选选项 - 使用WeakReferenceMessenger实现导入完成通知和导航消息 - ViewModel改为Singleton生命周期确保消息订阅正确工作 - 场景列表支持数据库加载和按维度筛选 - ScenarioId类型从int改为Guid Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,17 @@ using WCTDataMiner.Core.Parsers;
|
|||||||
|
|
||||||
namespace WCTDataMiner.Core.Services;
|
namespace WCTDataMiner.Core.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 篮选条件常量
|
||||||
|
/// </summary>
|
||||||
|
public static class FilterConstants
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 表示"全部"选项的常量值
|
||||||
|
/// </summary>
|
||||||
|
public const string AllOption = "全部";
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 测试场景服务 - 负责测试场景的创建和查询(并发安全版本)
|
/// 测试场景服务 - 负责测试场景的创建和查询(并发安全版本)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -129,6 +140,130 @@ public class ScenarioService
|
|||||||
.FirstOrDefaultAsync(s => s.Id == scenarioId);
|
.FirstOrDefaultAsync(s => s.Id == scenarioId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取所有测试场景(含维度属性)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pageSize">每页数量,默认50,传0表示不分页</param>
|
||||||
|
/// <param name="pageIndex">页码,从0开始</param>
|
||||||
|
public async Task<List<TestScenario>> GetAllScenariosAsync(int pageSize = 50, int pageIndex = 0)
|
||||||
|
{
|
||||||
|
var query = _context.TestScenarios
|
||||||
|
.Include(s => s.TxPanel)
|
||||||
|
.Include(s => s.TxHardware)
|
||||||
|
.Include(s => s.TxSoftware)
|
||||||
|
.Include(s => s.RxType)
|
||||||
|
.OrderByDescending(s => s.TestDate)
|
||||||
|
.ThenByDescending(s => s.TestSequence)
|
||||||
|
.AsNoTracking();
|
||||||
|
|
||||||
|
if (pageSize > 0)
|
||||||
|
{
|
||||||
|
query = query.Skip(pageIndex * pageSize).Take(pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await query.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取场景总数
|
||||||
|
/// </summary>
|
||||||
|
public async Task<int> GetScenariosCountAsync()
|
||||||
|
{
|
||||||
|
return await _context.TestScenarios.CountAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取可用的筛选选项
|
||||||
|
/// </summary>
|
||||||
|
public async Task<FilterOptions> GetFilterOptionsAsync()
|
||||||
|
{
|
||||||
|
var txPanels = await _context.TxPanels
|
||||||
|
.Select(p => p.Name)
|
||||||
|
.Distinct()
|
||||||
|
.OrderBy(n => n)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var txHardwares = await _context.TxHardwares
|
||||||
|
.Select(h => h.Version)
|
||||||
|
.Distinct()
|
||||||
|
.OrderBy(v => v)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var txSoftwares = await _context.TxSoftwares
|
||||||
|
.Select(s => s.Version)
|
||||||
|
.Distinct()
|
||||||
|
.OrderBy(v => v)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var rxTypes = await _context.RxTypes
|
||||||
|
.Select(r => r.Name)
|
||||||
|
.Distinct()
|
||||||
|
.OrderBy(n => n)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return new FilterOptions(
|
||||||
|
txPanels.Prepend(FilterConstants.AllOption).ToList(),
|
||||||
|
txHardwares.Prepend(FilterConstants.AllOption).ToList(),
|
||||||
|
txSoftwares.Prepend(FilterConstants.AllOption).ToList(),
|
||||||
|
rxTypes.Prepend(FilterConstants.AllOption).ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据筛选条件获取测试场景
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="txPanel">TxPanel 名称,null 或 "全部" 表示不过滤</param>
|
||||||
|
/// <param name="txHardware">TxHardware 版本,null 或 "全部" 表示不过滤</param>
|
||||||
|
/// <param name="txSoftware">TxSoftware 版本,null 或 "全部" 表示不过滤</param>
|
||||||
|
/// <param name="rxType">RxType 名称,null 或 "全部" 表示不过滤</param>
|
||||||
|
/// <param name="pageSize">每页数量,默认50,传0表示不分页</param>
|
||||||
|
/// <param name="pageIndex">页码,从0开始</param>
|
||||||
|
public async Task<List<TestScenario>> GetScenariosByFilterAsync(
|
||||||
|
string? txPanel = null,
|
||||||
|
string? txHardware = null,
|
||||||
|
string? txSoftware = null,
|
||||||
|
string? rxType = null,
|
||||||
|
int pageSize = 50,
|
||||||
|
int pageIndex = 0)
|
||||||
|
{
|
||||||
|
var query = _context.TestScenarios
|
||||||
|
.Include(s => s.TxPanel)
|
||||||
|
.Include(s => s.TxHardware)
|
||||||
|
.Include(s => s.TxSoftware)
|
||||||
|
.Include(s => s.RxType)
|
||||||
|
.AsNoTracking();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(txPanel) && txPanel != FilterConstants.AllOption)
|
||||||
|
{
|
||||||
|
query = query.Where(s => s.TxPanel.Name == txPanel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(txHardware) && txHardware != FilterConstants.AllOption)
|
||||||
|
{
|
||||||
|
query = query.Where(s => s.TxHardware.Version == txHardware);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(txSoftware) && txSoftware != FilterConstants.AllOption)
|
||||||
|
{
|
||||||
|
query = query.Where(s => s.TxSoftware.Version == txSoftware);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(rxType) && rxType != FilterConstants.AllOption)
|
||||||
|
{
|
||||||
|
query = query.Where(s => s.RxType.Name == rxType);
|
||||||
|
}
|
||||||
|
|
||||||
|
query = query
|
||||||
|
.OrderByDescending(s => s.TestDate)
|
||||||
|
.ThenByDescending(s => s.TestSequence);
|
||||||
|
|
||||||
|
if (pageSize > 0)
|
||||||
|
{
|
||||||
|
query = query.Skip(pageIndex * pageSize).Take(pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await query.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 检测 SQLite 唯一约束违反错误
|
/// 检测 SQLite 唯一约束违反错误
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -137,4 +272,13 @@ public class ScenarioService
|
|||||||
return ex.InnerException is Microsoft.Data.Sqlite.SqliteException sqliteEx
|
return ex.InnerException is Microsoft.Data.Sqlite.SqliteException sqliteEx
|
||||||
&& sqliteEx.SqliteErrorCode == 19; // SQLITE_CONSTRAINT
|
&& sqliteEx.SqliteErrorCode == 19; // SQLITE_CONSTRAINT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 筛选选项数据
|
||||||
|
/// </summary>
|
||||||
|
public record FilterOptions(
|
||||||
|
List<string> TxPanels,
|
||||||
|
List<string> TxHardwares,
|
||||||
|
List<string> TxSoftwares,
|
||||||
|
List<string> RxTypes);
|
||||||
@@ -68,12 +68,13 @@ public partial class App : Application
|
|||||||
services.AddDatabaseServices(context.Configuration);
|
services.AddDatabaseServices(context.Configuration);
|
||||||
services.AddApplicationServices();
|
services.AddApplicationServices();
|
||||||
|
|
||||||
// 注册 ViewModels
|
// 注册 ViewModels(使用 Singleton 确保消息订阅正确工作)
|
||||||
services.AddTransient<MainViewModel>();
|
services.AddSingleton<MainViewModel>();
|
||||||
services.AddTransient<ScenarioListViewModel>();
|
services.AddSingleton<ScenarioListViewModel>();
|
||||||
|
// Chart ViewModels 使用 Transient,每次导航创建新实例
|
||||||
services.AddTransient<PlossChartViewModel>();
|
services.AddTransient<PlossChartViewModel>();
|
||||||
services.AddTransient<QfodChartViewModel>();
|
services.AddTransient<QfodChartViewModel>();
|
||||||
services.AddTransient<ImportViewModel>();
|
services.AddSingleton<ImportViewModel>();
|
||||||
|
|
||||||
// 注册 Views
|
// 注册 Views
|
||||||
services.AddTransient<MainWindow>();
|
services.AddTransient<MainWindow>();
|
||||||
@@ -85,6 +86,23 @@ public partial class App : Application
|
|||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var mainWindow = _host.Services.GetRequiredService<MainWindow>();
|
var mainWindow = _host.Services.GetRequiredService<MainWindow>();
|
||||||
|
|
||||||
|
// 初始化加载场景数据
|
||||||
|
var mainViewModel = _host.Services.GetRequiredService<MainViewModel>();
|
||||||
|
mainWindow.Loaded += async (s, e) =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await mainViewModel.InitializeAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger?.Error(ex, "主窗口初始化失败");
|
||||||
|
MessageBox.Show($"应用初始化失败: {ex.Message}", "错误",
|
||||||
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
mainWindow.Show();
|
mainWindow.Show();
|
||||||
|
|
||||||
base.OnStartup(e);
|
base.OnStartup(e);
|
||||||
|
|||||||
19
src/WCTDataMiner.Wpf/Messages/DataImportedMessage.cs
Normal file
19
src/WCTDataMiner.Wpf/Messages/DataImportedMessage.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
namespace WCTDataMiner.Wpf.Messages;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 数据导入完成消息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ImportedCount">导入的文件数量</param>
|
||||||
|
public record DataImportedMessage(int ImportedCount);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 导航到 Ploss 图表消息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ScenarioId">场景 ID</param>
|
||||||
|
public record NavigateToPlossChartMessage(Guid ScenarioId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 导航到 Qfod 图表消息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ScenarioId">场景 ID</param>
|
||||||
|
public record NavigateToQfodChartMessage(Guid ScenarioId);
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using WCTDataMiner.Core.Services;
|
using WCTDataMiner.Core.Services;
|
||||||
|
using WCTDataMiner.Wpf.Messages;
|
||||||
|
|
||||||
namespace WCTDataMiner.Wpf.ViewModels;
|
namespace WCTDataMiner.Wpf.ViewModels;
|
||||||
|
|
||||||
@@ -99,6 +101,9 @@ public partial class ImportViewModel : ObservableObject
|
|||||||
StatusMessage = $"导入完成: Qfod={report.QfodCount}, Ploss={report.PlossCount}, 错误={report.ErrorCount}";
|
StatusMessage = $"导入完成: Qfod={report.QfodCount}, Ploss={report.PlossCount}, 错误={report.ErrorCount}";
|
||||||
_logger.LogInformation("单文件导入完成: {FileName}, Qfod={Qfod}, Ploss={Ploss}, 错误={Errors}",
|
_logger.LogInformation("单文件导入完成: {FileName}, Qfod={Qfod}, Ploss={Ploss}, 错误={Errors}",
|
||||||
report.FileName, report.QfodCount, report.PlossCount, report.ErrorCount);
|
report.FileName, report.QfodCount, report.PlossCount, report.ErrorCount);
|
||||||
|
|
||||||
|
// 发送导入完成消息,通知其他 ViewModel 刷新数据
|
||||||
|
WeakReferenceMessenger.Default.Send(new DataImportedMessage(1));
|
||||||
}
|
}
|
||||||
else if (System.IO.Directory.Exists(SelectedPath))
|
else if (System.IO.Directory.Exists(SelectedPath))
|
||||||
{
|
{
|
||||||
@@ -118,6 +123,9 @@ public partial class ImportViewModel : ObservableObject
|
|||||||
StatusMessage = $"导入完成: {reports.Count} 个文件, Qfod={totalQfod}, Ploss={totalPloss}, 错误={totalErrors}";
|
StatusMessage = $"导入完成: {reports.Count} 个文件, Qfod={totalQfod}, Ploss={totalPloss}, 错误={totalErrors}";
|
||||||
_logger.LogInformation("目录导入完成: {FileCount} 个文件, Qfod={Qfod}, Ploss={Ploss}, 错误={Errors}",
|
_logger.LogInformation("目录导入完成: {FileCount} 个文件, Qfod={Qfod}, Ploss={Ploss}, 错误={Errors}",
|
||||||
reports.Count, totalQfod, totalPloss, totalErrors);
|
reports.Count, totalQfod, totalPloss, totalErrors);
|
||||||
|
|
||||||
|
// 发送导入完成消息,通知其他 ViewModel 刷新数据
|
||||||
|
WeakReferenceMessenger.Default.Send(new DataImportedMessage(reports.Count));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,15 +1,22 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using WCTDataMiner.Core.Services;
|
||||||
|
using WCTDataMiner.Wpf.Messages;
|
||||||
|
|
||||||
namespace WCTDataMiner.Wpf.ViewModels;
|
namespace WCTDataMiner.Wpf.ViewModels;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 主窗口 ViewModel - 导航控制
|
/// 主窗口 ViewModel - 导航控制
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class MainViewModel : ObservableObject
|
public partial class MainViewModel : ObservableObject,
|
||||||
|
IRecipient<NavigateToPlossChartMessage>,
|
||||||
|
IRecipient<NavigateToQfodChartMessage>
|
||||||
{
|
{
|
||||||
private readonly ImportViewModel _importViewModel;
|
private readonly ImportViewModel _importViewModel;
|
||||||
private readonly ScenarioListViewModel _scenarioListViewModel;
|
private readonly ScenarioListViewModel _scenarioListViewModel;
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private ObservableObject _currentView = null!;
|
private ObservableObject _currentView = null!;
|
||||||
@@ -26,17 +33,48 @@ public partial class MainViewModel : ObservableObject
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string? _selectedRxType;
|
private string? _selectedRxType;
|
||||||
|
|
||||||
// 筛选选项列表
|
// 筛选选项列表(动态加载)
|
||||||
public List<string> TxPanels { get; } = new() { "全部", "singleMold", "dualMold" };
|
[ObservableProperty]
|
||||||
public List<string> TxHardwares { get; } = new() { "全部", "v1.0", "v2.0" };
|
private List<string> _txPanels = new() { FilterConstants.AllOption };
|
||||||
public List<string> TxSoftwares { get; } = new() { "全部", "hex2_1", "hex2_2" };
|
|
||||||
public List<string> RxTypes { get; } = new() { "全部", "iPhone15", "iPhone16", "Android" };
|
|
||||||
|
|
||||||
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;
|
_importViewModel = importViewModel;
|
||||||
_scenarioListViewModel = scenarioListViewModel;
|
_scenarioListViewModel = scenarioListViewModel;
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
_currentView = _scenarioListViewModel;
|
_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]
|
[RelayCommand]
|
||||||
@@ -46,20 +84,54 @@ public partial class MainViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void NavigateToScenarios()
|
private async Task NavigateToScenarios()
|
||||||
{
|
{
|
||||||
CurrentView = _scenarioListViewModel;
|
CurrentView = _scenarioListViewModel;
|
||||||
|
await _scenarioListViewModel.LoadScenariosAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[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]
|
[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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using LiveChartsCore;
|
using LiveChartsCore;
|
||||||
using LiveChartsCore.SkiaSharpView;
|
using LiveChartsCore.SkiaSharpView;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using WCTDataMiner.Core.Services;
|
||||||
|
|
||||||
namespace WCTDataMiner.Wpf.ViewModels;
|
namespace WCTDataMiner.Wpf.ViewModels;
|
||||||
|
|
||||||
@@ -9,8 +11,11 @@ namespace WCTDataMiner.Wpf.ViewModels;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class PlossChartViewModel : ObservableObject
|
public partial class PlossChartViewModel : ObservableObject
|
||||||
{
|
{
|
||||||
|
private readonly ScenarioService _scenarioService;
|
||||||
|
private readonly ILogger<PlossChartViewModel> _logger;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private int _scenarioId;
|
private Guid _scenarioId;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private ISeries[] _series;
|
private ISeries[] _series;
|
||||||
@@ -21,8 +26,13 @@ public partial class PlossChartViewModel : ObservableObject
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private Axis[] _yAxes;
|
private Axis[] _yAxes;
|
||||||
|
|
||||||
public PlossChartViewModel()
|
public PlossChartViewModel(
|
||||||
|
ScenarioService scenarioService,
|
||||||
|
ILogger<PlossChartViewModel> logger)
|
||||||
{
|
{
|
||||||
|
_scenarioService = scenarioService;
|
||||||
|
_logger = logger;
|
||||||
|
|
||||||
// 初始化图表配置
|
// 初始化图表配置
|
||||||
Series = Array.Empty<ISeries>();
|
Series = Array.Empty<ISeries>();
|
||||||
XAxes = new Axis[] { new Axis { Name = "Trigger Count" } };
|
XAxes = new Axis[] { new Axis { Name = "Trigger Count" } };
|
||||||
@@ -31,19 +41,26 @@ public partial class PlossChartViewModel : ObservableObject
|
|||||||
|
|
||||||
public async Task LoadDataAsync()
|
public async Task LoadDataAsync()
|
||||||
{
|
{
|
||||||
// TODO: 从数据库加载 Ploss 数据并构建图表
|
try
|
||||||
Series = new ISeries[]
|
|
||||||
{
|
{
|
||||||
new LineSeries<double>
|
// TODO: 从数据库加载 Ploss 数据并构建图表
|
||||||
|
Series = new ISeries[]
|
||||||
{
|
{
|
||||||
Name = "Ploss",
|
new LineSeries<double>
|
||||||
Values = new double[] { -2882, -3500, -4200, -3800, -4500 }
|
{
|
||||||
},
|
Name = "Ploss",
|
||||||
new LineSeries<double>
|
Values = new double[] { -2882, -3500, -4200, -3800, -4500 }
|
||||||
{
|
},
|
||||||
Name = "Threshold",
|
new LineSeries<double>
|
||||||
Values = new double[] { -3000, -3000, -3000, -3000, -3000 }
|
{
|
||||||
}
|
Name = "Threshold",
|
||||||
};
|
Values = new double[] { -3000, -3000, -3000, -3000, -3000 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "加载 Ploss 图表数据失败: ScenarioId={ScenarioId}", ScenarioId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using LiveChartsCore;
|
using LiveChartsCore;
|
||||||
using LiveChartsCore.SkiaSharpView;
|
using LiveChartsCore.SkiaSharpView;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using WCTDataMiner.Core.Services;
|
||||||
|
|
||||||
namespace WCTDataMiner.Wpf.ViewModels;
|
namespace WCTDataMiner.Wpf.ViewModels;
|
||||||
|
|
||||||
@@ -9,8 +11,11 @@ namespace WCTDataMiner.Wpf.ViewModels;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class QfodChartViewModel : ObservableObject
|
public partial class QfodChartViewModel : ObservableObject
|
||||||
{
|
{
|
||||||
|
private readonly ScenarioService _scenarioService;
|
||||||
|
private readonly ILogger<QfodChartViewModel> _logger;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private int _scenarioId;
|
private Guid _scenarioId;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private ISeries[] _series;
|
private ISeries[] _series;
|
||||||
@@ -21,8 +26,13 @@ public partial class QfodChartViewModel : ObservableObject
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private Axis[] _yAxes;
|
private Axis[] _yAxes;
|
||||||
|
|
||||||
public QfodChartViewModel()
|
public QfodChartViewModel(
|
||||||
|
ScenarioService scenarioService,
|
||||||
|
ILogger<QfodChartViewModel> logger)
|
||||||
{
|
{
|
||||||
|
_scenarioService = scenarioService;
|
||||||
|
_logger = logger;
|
||||||
|
|
||||||
// 初始化图表配置
|
// 初始化图表配置
|
||||||
Series = Array.Empty<ISeries>();
|
Series = Array.Empty<ISeries>();
|
||||||
XAxes = new Axis[] { new Axis { Name = "Coil Index" } };
|
XAxes = new Axis[] { new Axis { Name = "Coil Index" } };
|
||||||
@@ -31,19 +41,26 @@ public partial class QfodChartViewModel : ObservableObject
|
|||||||
|
|
||||||
public async Task LoadDataAsync()
|
public async Task LoadDataAsync()
|
||||||
{
|
{
|
||||||
// TODO: 从数据库加载 Qfod 数据并构建图表
|
try
|
||||||
Series = new ISeries[]
|
|
||||||
{
|
{
|
||||||
new LineSeries<double>
|
// TODO: 从数据库加载 Qfod 数据并构建图表
|
||||||
|
Series = new ISeries[]
|
||||||
{
|
{
|
||||||
Name = "DeltaQ",
|
new LineSeries<double>
|
||||||
Values = new double[] { 25, 30, 28 }
|
{
|
||||||
},
|
Name = "DeltaQ",
|
||||||
new LineSeries<double>
|
Values = new double[] { 25, 30, 28 }
|
||||||
{
|
},
|
||||||
Name = "CurrentQ",
|
new LineSeries<double>
|
||||||
Values = new double[] { 45.5, 50.2, 48.8 }
|
{
|
||||||
}
|
Name = "CurrentQ",
|
||||||
};
|
Values = new double[] { 45.5, 50.2, 48.8 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "加载 Qfod 图表数据失败: ScenarioId={ScenarioId}", ScenarioId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,163 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
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;
|
namespace WCTDataMiner.Wpf.ViewModels;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 场景列表 ViewModel
|
/// 场景列表 ViewModel
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class ScenarioListViewModel : ObservableObject
|
public partial class ScenarioListViewModel : ObservableObject, IRecipient<DataImportedMessage>
|
||||||
{
|
{
|
||||||
|
private readonly ScenarioService _scenarioService;
|
||||||
|
private readonly ILogger<ScenarioListViewModel> _logger;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private List<ScenarioItem> _scenarios;
|
private ObservableCollection<ScenarioItem> _scenarios = new();
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private ScenarioItem? _selectedScenario;
|
private ScenarioItem? _selectedScenario;
|
||||||
|
|
||||||
public ScenarioListViewModel()
|
[ObservableProperty]
|
||||||
|
private bool _isLoading;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _loadingMessage = "";
|
||||||
|
|
||||||
|
public ScenarioListViewModel(
|
||||||
|
ScenarioService scenarioService,
|
||||||
|
ILogger<ScenarioListViewModel> logger)
|
||||||
{
|
{
|
||||||
// TODO: 从数据库加载场景列表
|
_scenarioService = scenarioService;
|
||||||
_scenarios = new List<ScenarioItem>();
|
_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>
|
/// </summary>
|
||||||
public class ScenarioItem
|
public class ScenarioItem
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
public string TxPanel { get; set; } = "";
|
public string TxPanel { get; set; } = "";
|
||||||
public string TxHardware { get; set; } = "";
|
public string TxHardware { get; set; } = "";
|
||||||
public string TxSoftware { get; set; } = "";
|
public string TxSoftware { get; set; } = "";
|
||||||
@@ -33,4 +174,4 @@ public class ScenarioItem
|
|||||||
public string TestDate { get; set; } = "";
|
public string TestDate { get; set; } = "";
|
||||||
public int QfodCount { get; set; }
|
public int QfodCount { get; set; }
|
||||||
public int PlossCount { get; set; }
|
public int PlossCount { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
|
using WCTDataMiner.Wpf.Messages;
|
||||||
|
using WCTDataMiner.Wpf.ViewModels;
|
||||||
|
|
||||||
namespace WCTDataMiner.Wpf.Views;
|
namespace WCTDataMiner.Wpf.Views;
|
||||||
|
|
||||||
@@ -12,19 +15,17 @@ public partial class ScenarioListView : UserControl
|
|||||||
|
|
||||||
private void OnViewPlossChart(object sender, RoutedEventArgs e)
|
private void OnViewPlossChart(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
// TODO: 导航到 Ploss 图表页面
|
if (DataContext is ScenarioListViewModel vm && vm.SelectedScenario != null)
|
||||||
if (DataContext is ViewModels.ScenarioListViewModel vm && vm.SelectedScenario != null)
|
|
||||||
{
|
{
|
||||||
// Navigation logic
|
WeakReferenceMessenger.Default.Send(new NavigateToPlossChartMessage(vm.SelectedScenario.Id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnViewQfodChart(object sender, RoutedEventArgs e)
|
private void OnViewQfodChart(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
// TODO: 导航到 Qfod 图表页面
|
if (DataContext is ScenarioListViewModel vm && vm.SelectedScenario != null)
|
||||||
if (DataContext is ViewModels.ScenarioListViewModel vm && vm.SelectedScenario != null)
|
|
||||||
{
|
{
|
||||||
// Navigation logic
|
WeakReferenceMessenger.Default.Send(new NavigateToQfodChartMessage(vm.SelectedScenario.Id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user