From 6e183f84e47ffc6680fe550c92eb1bc3294f0cea Mon Sep 17 00:00:00 2001
From: ssss <111@qq.com>
Date: Fri, 3 Jul 2026 10:49:23 +0800
Subject: [PATCH] =?UTF-8?q?feat(wpf):=20=E6=B7=BB=E5=8A=A0=E6=95=B0?=
=?UTF-8?q?=E6=8D=AE=E5=AF=BC=E5=85=A5=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/WCTDataMiner.Wpf/App.xaml | 6 +-
src/WCTDataMiner.Wpf/App.xaml.cs | 2 +
.../Converters/BooleanConverters.cs | 53 +++++++
src/WCTDataMiner.Wpf/MainWindow.xaml | 12 +-
src/WCTDataMiner.Wpf/MainWindow.xaml.cs | 13 +-
.../ViewModels/ImportViewModel.cs | 149 ++++++++++++++++++
.../ViewModels/MainViewModel.cs | 19 ++-
src/WCTDataMiner.Wpf/Views/ImportView.xaml | 63 ++++++++
src/WCTDataMiner.Wpf/Views/ImportView.xaml.cs | 12 ++
src/WCTDataMiner.Wpf/WCTDataMiner.Wpf.csproj | 6 +
10 files changed, 314 insertions(+), 21 deletions(-)
create mode 100644 src/WCTDataMiner.Wpf/Converters/BooleanConverters.cs
create mode 100644 src/WCTDataMiner.Wpf/ViewModels/ImportViewModel.cs
create mode 100644 src/WCTDataMiner.Wpf/Views/ImportView.xaml
create mode 100644 src/WCTDataMiner.Wpf/Views/ImportView.xaml.cs
diff --git a/src/WCTDataMiner.Wpf/App.xaml b/src/WCTDataMiner.Wpf/App.xaml
index ebfa722..5e2c2cf 100644
--- a/src/WCTDataMiner.Wpf/App.xaml
+++ b/src/WCTDataMiner.Wpf/App.xaml
@@ -1,8 +1,10 @@
+ xmlns:local="clr-namespace:WCTDataMiner.Wpf"
+ xmlns:converters="clr-namespace:WCTDataMiner.Wpf.Converters">
-
+
+
\ No newline at end of file
diff --git a/src/WCTDataMiner.Wpf/App.xaml.cs b/src/WCTDataMiner.Wpf/App.xaml.cs
index be0ab14..d4fbc2b 100644
--- a/src/WCTDataMiner.Wpf/App.xaml.cs
+++ b/src/WCTDataMiner.Wpf/App.xaml.cs
@@ -29,12 +29,14 @@ public partial class App : Application
services.AddTransient();
services.AddTransient();
services.AddTransient();
+ services.AddTransient();
// 注册 Views
services.AddTransient();
services.AddTransient();
services.AddTransient();
services.AddTransient();
+ services.AddTransient();
})
.Build();
diff --git a/src/WCTDataMiner.Wpf/Converters/BooleanConverters.cs b/src/WCTDataMiner.Wpf/Converters/BooleanConverters.cs
new file mode 100644
index 0000000..582319d
--- /dev/null
+++ b/src/WCTDataMiner.Wpf/Converters/BooleanConverters.cs
@@ -0,0 +1,53 @@
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace WCTDataMiner.Wpf.Converters;
+
+///
+/// Boolean to Visibility 转换器
+///
+public class BooleanToVisibilityConverter : IValueConverter
+{
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is bool boolValue)
+ {
+ return boolValue ? Visibility.Visible : Visibility.Collapsed;
+ }
+ return Visibility.Collapsed;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is Visibility visibility)
+ {
+ return visibility == Visibility.Visible;
+ }
+ return false;
+ }
+}
+
+///
+/// 反转 Boolean 转换器
+///
+public class InverseBooleanConverter : IValueConverter
+{
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is bool boolValue)
+ {
+ return !boolValue;
+ }
+ return true;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is bool boolValue)
+ {
+ return !boolValue;
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/WCTDataMiner.Wpf/MainWindow.xaml b/src/WCTDataMiner.Wpf/MainWindow.xaml
index a7f7e16..64e3b8b 100644
--- a/src/WCTDataMiner.Wpf/MainWindow.xaml
+++ b/src/WCTDataMiner.Wpf/MainWindow.xaml
@@ -3,16 +3,11 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:local="clr-namespace:WCTDataMiner.Wpf"
xmlns:views="clr-namespace:WCTDataMiner.Wpf.Views"
xmlns:viewmodels="clr-namespace:WCTDataMiner.Wpf.ViewModels"
- xmlns:lvc="clr-namespace:LiveChartsCore.SkiaSharpView.WPF;assembly=LiveChartsCore.SkiaSharpView.WPF"
mc:Ignorable="d"
Title="WCTDataMiner - FOD 数据分析工具" Height="600" Width="900"
WindowStartupLocation="CenterScreen">
-
-
-
@@ -25,6 +20,10 @@
+
+
@@ -51,6 +50,9 @@
+
+
+
diff --git a/src/WCTDataMiner.Wpf/MainWindow.xaml.cs b/src/WCTDataMiner.Wpf/MainWindow.xaml.cs
index ef13ab8..333688a 100644
--- a/src/WCTDataMiner.Wpf/MainWindow.xaml.cs
+++ b/src/WCTDataMiner.Wpf/MainWindow.xaml.cs
@@ -1,13 +1,5 @@
-using System.Text;
using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Data;
-using System.Windows.Documents;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using System.Windows.Navigation;
-using System.Windows.Shapes;
+using WCTDataMiner.Wpf.ViewModels;
namespace WCTDataMiner.Wpf;
@@ -16,8 +8,9 @@ namespace WCTDataMiner.Wpf;
///
public partial class MainWindow : Window
{
- public MainWindow()
+ public MainWindow(MainViewModel viewModel)
{
InitializeComponent();
+ DataContext = viewModel;
}
}
\ No newline at end of file
diff --git a/src/WCTDataMiner.Wpf/ViewModels/ImportViewModel.cs b/src/WCTDataMiner.Wpf/ViewModels/ImportViewModel.cs
new file mode 100644
index 0000000..081b1f8
--- /dev/null
+++ b/src/WCTDataMiner.Wpf/ViewModels/ImportViewModel.cs
@@ -0,0 +1,149 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using Microsoft.Win32;
+using System.Collections.ObjectModel;
+using WCTDataMiner.Core.Services;
+
+namespace WCTDataMiner.Wpf.ViewModels;
+
+///
+/// 数据导入 ViewModel
+///
+public partial class ImportViewModel : ObservableObject
+{
+ private readonly ParseService _parseService;
+
+ [ObservableProperty]
+ private string _selectedPath = "";
+
+ [ObservableProperty]
+ private bool _isRecursive;
+
+ [ObservableProperty]
+ private bool _isImporting;
+
+ [ObservableProperty]
+ private int _progressValue;
+
+ [ObservableProperty]
+ private string _statusMessage = "请选择日志文件或目录";
+
+ [ObservableProperty]
+ private ObservableCollection _importResults = new();
+
+ public ImportViewModel(ParseService parseService)
+ {
+ _parseService = parseService;
+ }
+
+ [RelayCommand]
+ private void SelectFile()
+ {
+ var dialog = new OpenFileDialog
+ {
+ Title = "选择日志文件",
+ Filter = "日志文件 (*.log)|*.log|所有文件 (*.*)|*.*",
+ Multiselect = false
+ };
+
+ if (dialog.ShowDialog() == true)
+ {
+ SelectedPath = dialog.FileName;
+ StatusMessage = $"已选择文件: {System.IO.Path.GetFileName(SelectedPath)}";
+ }
+ }
+
+ [RelayCommand]
+ private void SelectFolder()
+ {
+ var dialog = new OpenFolderDialog
+ {
+ Title = "选择日志目录",
+ Multiselect = false
+ };
+
+ if (dialog.ShowDialog() == true)
+ {
+ SelectedPath = dialog.FolderName;
+ StatusMessage = $"已选择目录: {System.IO.Path.GetFileName(SelectedPath)}";
+ }
+ }
+
+ [RelayCommand]
+ private async Task ImportAsync()
+ {
+ if (string.IsNullOrEmpty(SelectedPath))
+ {
+ StatusMessage = "请先选择文件或目录";
+ return;
+ }
+
+ IsImporting = true;
+ ImportResults.Clear();
+ StatusMessage = "正在导入...";
+
+ try
+ {
+ if (System.IO.File.Exists(SelectedPath))
+ {
+ // 导入单个文件
+ var report = await _parseService.ParseFileAsync(SelectedPath);
+ ImportResults.Add(new ImportResultItem(report));
+ StatusMessage = $"导入完成: Qfod={report.QfodCount}, Ploss={report.PlossCount}, 错误={report.ErrorCount}";
+ }
+ else if (System.IO.Directory.Exists(SelectedPath))
+ {
+ // 导入目录
+ var reports = await _parseService.ParseDirectoryAsync(SelectedPath, IsRecursive);
+
+ int totalQfod = 0, totalPloss = 0, totalErrors = 0;
+ foreach (var report in reports)
+ {
+ ImportResults.Add(new ImportResultItem(report));
+ totalQfod += report.QfodCount;
+ totalPloss += report.PlossCount;
+ totalErrors += report.ErrorCount;
+ ProgressValue = (int)((double)ImportResults.Count / reports.Count * 100);
+ }
+
+ StatusMessage = $"导入完成: {reports.Count} 个文件, Qfod={totalQfod}, Ploss={totalPloss}, 错误={totalErrors}";
+ }
+ else
+ {
+ StatusMessage = "路径不存在";
+ }
+ }
+ catch (Exception ex)
+ {
+ StatusMessage = $"导入失败: {ex.Message}";
+ }
+ finally
+ {
+ IsImporting = false;
+ ProgressValue = 100;
+ }
+ }
+}
+
+///
+/// 导入结果项
+///
+public class ImportResultItem
+{
+ public string FileName { get; }
+ public int QfodCount { get; }
+ public int PlossCount { get; }
+ public int ErrorCount { get; }
+ public string Status { get; }
+ public bool IsSuccess { get; }
+
+ public ImportResultItem(ParseReport report)
+ {
+ FileName = report.FileName;
+ QfodCount = report.QfodCount;
+ PlossCount = report.PlossCount;
+ ErrorCount = report.ErrorCount;
+ IsSuccess = report.IsSuccess;
+ Status = report.IsSuccess ? "成功" : report.ErrorMessage ?? "失败";
+ }
+}
\ No newline at end of file
diff --git a/src/WCTDataMiner.Wpf/ViewModels/MainViewModel.cs b/src/WCTDataMiner.Wpf/ViewModels/MainViewModel.cs
index d6ec4fa..aa19288 100644
--- a/src/WCTDataMiner.Wpf/ViewModels/MainViewModel.cs
+++ b/src/WCTDataMiner.Wpf/ViewModels/MainViewModel.cs
@@ -8,6 +8,9 @@ namespace WCTDataMiner.Wpf.ViewModels;
///
public partial class MainViewModel : ObservableObject
{
+ private readonly ImportViewModel _importViewModel;
+ private readonly ScenarioListViewModel _scenarioListViewModel;
+
[ObservableProperty]
private ObservableObject _currentView = null!;
@@ -29,15 +32,23 @@ public partial class MainViewModel : ObservableObject
public List TxSoftwares { get; } = new() { "全部", "hex2_1", "hex2_2" };
public List RxTypes { get; } = new() { "全部", "iPhone15", "iPhone16", "Android" };
- public MainViewModel()
+ public MainViewModel(ImportViewModel importViewModel, ScenarioListViewModel scenarioListViewModel)
{
- _currentView = new ScenarioListViewModel();
+ _importViewModel = importViewModel;
+ _scenarioListViewModel = scenarioListViewModel;
+ _currentView = _scenarioListViewModel;
+ }
+
+ [RelayCommand]
+ private void NavigateToImport()
+ {
+ CurrentView = _importViewModel;
}
[RelayCommand]
private void NavigateToScenarios()
{
- CurrentView = new ScenarioListViewModel();
+ CurrentView = _scenarioListViewModel;
}
[RelayCommand]
@@ -51,4 +62,4 @@ public partial class MainViewModel : ObservableObject
{
CurrentView = new QfodChartViewModel { ScenarioId = scenarioId };
}
-}
+}
\ No newline at end of file
diff --git a/src/WCTDataMiner.Wpf/Views/ImportView.xaml b/src/WCTDataMiner.Wpf/Views/ImportView.xaml
new file mode 100644
index 0000000..f78fd43
--- /dev/null
+++ b/src/WCTDataMiner.Wpf/Views/ImportView.xaml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/WCTDataMiner.Wpf/Views/ImportView.xaml.cs b/src/WCTDataMiner.Wpf/Views/ImportView.xaml.cs
new file mode 100644
index 0000000..2c9d705
--- /dev/null
+++ b/src/WCTDataMiner.Wpf/Views/ImportView.xaml.cs
@@ -0,0 +1,12 @@
+using System.Windows;
+using System.Windows.Controls;
+
+namespace WCTDataMiner.Wpf.Views;
+
+public partial class ImportView : UserControl
+{
+ public ImportView()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/src/WCTDataMiner.Wpf/WCTDataMiner.Wpf.csproj b/src/WCTDataMiner.Wpf/WCTDataMiner.Wpf.csproj
index 419e5fc..e0e4a8d 100644
--- a/src/WCTDataMiner.Wpf/WCTDataMiner.Wpf.csproj
+++ b/src/WCTDataMiner.Wpf/WCTDataMiner.Wpf.csproj
@@ -26,4 +26,10 @@
+
+
+ PreserveNewest
+
+
+
\ No newline at end of file