feat(wpf): 添加数据导入功能
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
<Application x:Class="WCTDataMiner.Wpf.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:WCTDataMiner.Wpf">
|
||||
xmlns:local="clr-namespace:WCTDataMiner.Wpf"
|
||||
xmlns:converters="clr-namespace:WCTDataMiner.Wpf.Converters">
|
||||
<Application.Resources>
|
||||
|
||||
<converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
|
||||
<converters:InverseBooleanConverter x:Key="InverseBooleanConverter"/>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -29,12 +29,14 @@ public partial class App : Application
|
||||
services.AddTransient<ScenarioListViewModel>();
|
||||
services.AddTransient<PlossChartViewModel>();
|
||||
services.AddTransient<QfodChartViewModel>();
|
||||
services.AddTransient<ImportViewModel>();
|
||||
|
||||
// 注册 Views
|
||||
services.AddTransient<MainWindow>();
|
||||
services.AddTransient<ScenarioListView>();
|
||||
services.AddTransient<PlossChartView>();
|
||||
services.AddTransient<QfodChartView>();
|
||||
services.AddTransient<ImportView>();
|
||||
})
|
||||
.Build();
|
||||
|
||||
|
||||
53
src/WCTDataMiner.Wpf/Converters/BooleanConverters.cs
Normal file
53
src/WCTDataMiner.Wpf/Converters/BooleanConverters.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace WCTDataMiner.Wpf.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Boolean to Visibility 转换器
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 反转 Boolean 转换器
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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">
|
||||
<Window.DataContext>
|
||||
<viewmodels:MainViewModel/>
|
||||
</Window.DataContext>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
@@ -25,6 +20,10 @@
|
||||
<StackPanel Margin="10">
|
||||
<TextBlock Text="导航" FontSize="16" FontWeight="Bold" Margin="0,0,0,15"/>
|
||||
|
||||
<Button Content="数据导入" Command="{Binding NavigateToImportCommand}"
|
||||
Margin="0,5" Padding="10,5" HorizontalAlignment="Stretch"
|
||||
Background="#4CAF50" Foreground="White" FontWeight="Bold"/>
|
||||
|
||||
<Button Content="场景列表" Command="{Binding NavigateToScenariosCommand}"
|
||||
Margin="0,5" Padding="10,5" HorizontalAlignment="Stretch"/>
|
||||
|
||||
@@ -51,6 +50,9 @@
|
||||
<!-- 右侧内容区域 -->
|
||||
<ContentControl Grid.Column="1" Content="{Binding CurrentView}">
|
||||
<ContentControl.Resources>
|
||||
<DataTemplate DataType="{x:Type viewmodels:ImportViewModel}">
|
||||
<views:ImportView/>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type viewmodels:ScenarioListViewModel}">
|
||||
<views:ScenarioListView/>
|
||||
</DataTemplate>
|
||||
|
||||
@@ -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;
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
public MainWindow(MainViewModel viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = viewModel;
|
||||
}
|
||||
}
|
||||
149
src/WCTDataMiner.Wpf/ViewModels/ImportViewModel.cs
Normal file
149
src/WCTDataMiner.Wpf/ViewModels/ImportViewModel.cs
Normal file
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 数据导入 ViewModel
|
||||
/// </summary>
|
||||
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<ImportResultItem> _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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导入结果项
|
||||
/// </summary>
|
||||
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 ?? "失败";
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,9 @@ namespace WCTDataMiner.Wpf.ViewModels;
|
||||
/// </summary>
|
||||
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<string> TxSoftwares { get; } = new() { "全部", "hex2_1", "hex2_2" };
|
||||
public List<string> 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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
63
src/WCTDataMiner.Wpf/Views/ImportView.xaml
Normal file
63
src/WCTDataMiner.Wpf/Views/ImportView.xaml
Normal file
@@ -0,0 +1,63 @@
|
||||
<UserControl x:Class="WCTDataMiner.Wpf.Views.ImportView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:WCTDataMiner.Wpf.Views"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="600">
|
||||
<Grid Margin="20">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 标题 -->
|
||||
<TextBlock Grid.Row="0" Text="数据导入" FontSize="20" FontWeight="Bold" Margin="0,0,0,20"/>
|
||||
|
||||
<!-- 选择路径 -->
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,0,0,15">
|
||||
<Button Content="选择文件" Command="{Binding SelectFileCommand}"
|
||||
Width="100" Margin="0,0,10,0" Padding="10,5"/>
|
||||
<Button Content="选择目录" Command="{Binding SelectFolderCommand}"
|
||||
Width="100" Margin="0,0,10,0" Padding="10,5"/>
|
||||
<TextBox Text="{Binding SelectedPath, UpdateSourceTrigger=PropertyChanged}"
|
||||
Width="300" IsReadOnly="True" VerticalContentAlignment="Center"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 选项和导入按钮 -->
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" Margin="0,0,0,15">
|
||||
<CheckBox Content="递归搜索子目录" IsChecked="{Binding IsRecursive}"
|
||||
Margin="0,0,20,0" VerticalContentAlignment="Center"/>
|
||||
<Button Content="开始导入" Command="{Binding ImportCommand}"
|
||||
Width="120" Padding="15,8" FontWeight="Bold"
|
||||
IsEnabled="{Binding IsImporting, Converter={StaticResource InverseBooleanConverter}}"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 进度和状态 -->
|
||||
<StackPanel Grid.Row="3" Margin="0,0,0,15">
|
||||
<ProgressBar Height="20" Value="{Binding ProgressValue}" Maximum="100"
|
||||
Visibility="{Binding IsImporting, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
<TextBlock Text="{Binding StatusMessage}" FontSize="14" Margin="0,5,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 导入结果列表 -->
|
||||
<DataGrid Grid.Row="4" ItemsSource="{Binding ImportResults}"
|
||||
AutoGenerateColumns="False"
|
||||
IsReadOnly="True"
|
||||
GridLinesVisibility="Horizontal"
|
||||
AlternatingRowBackground="#F9F9F9"
|
||||
CanUserAddRows="False">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="文件名" Binding="{Binding FileName}" Width="*"/>
|
||||
<DataGridTextColumn Header="Qfod" Binding="{Binding QfodCount}" Width="80"/>
|
||||
<DataGridTextColumn Header="Ploss" Binding="{Binding PlossCount}" Width="80"/>
|
||||
<DataGridTextColumn Header="错误" Binding="{Binding ErrorCount}" Width="80"/>
|
||||
<DataGridTextColumn Header="状态" Binding="{Binding Status}" Width="100"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
12
src/WCTDataMiner.Wpf/Views/ImportView.xaml.cs
Normal file
12
src/WCTDataMiner.Wpf/Views/ImportView.xaml.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace WCTDataMiner.Wpf.Views;
|
||||
|
||||
public partial class ImportView : UserControl
|
||||
{
|
||||
public ImportView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -26,4 +26,10 @@
|
||||
<ProjectReference Include="..\WCTDataMiner.Core\WCTDataMiner.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="appsettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user