diff --git a/BetterGenshinImpact/GameTask/LogParse/LogParse.cs b/BetterGenshinImpact/GameTask/LogParse/LogParse.cs index d5b4b058..ad3e83b1 100644 --- a/BetterGenshinImpact/GameTask/LogParse/LogParse.cs +++ b/BetterGenshinImpact/GameTask/LogParse/LogParse.cs @@ -386,7 +386,7 @@ namespace LogParse return "Invalid input time format. Please use 'yyyy-MM-dd HH:mm:ss'."; } } - public static string GenerHtmlByConfigGroupEntity(List configGroups, GameInfo gameInfo,LogParseConfig.ScriptGroupLogParseConfig scriptGroupLogParseConfig) + public static string GenerHtmlByConfigGroupEntity(List configGroups, GameInfo? gameInfo,LogParseConfig.ScriptGroupLogParseConfig scriptGroupLogParseConfig) { (string name, Func value)[] colConfigs = [ diff --git a/BetterGenshinImpact/Helpers/Ui/FileTreeNodeHelper.cs b/BetterGenshinImpact/Helpers/Ui/FileTreeNodeHelper.cs index 574cc437..554fe52c 100644 --- a/BetterGenshinImpact/Helpers/Ui/FileTreeNodeHelper.cs +++ b/BetterGenshinImpact/Helpers/Ui/FileTreeNodeHelper.cs @@ -1,4 +1,7 @@ -using BetterGenshinImpact.Model; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using BetterGenshinImpact.Model; using System.IO; using System.Linq; @@ -54,4 +57,103 @@ public class FileTreeNodeHelper parentNode.Children.Add(fileNode); } } + + /// + /// 根据路径过滤树形结构,排除指定路径的节点 + /// + /// 节点数据类型 + /// 树形结构的根节点集合 + /// 要排除的路径集合 + /// 过滤后的树形结构 + public static ObservableCollection> FilterTree( + ObservableCollection> nodes, + List folderNames) + { + // 递归过滤节点 + ObservableCollection> FilterNodes(ObservableCollection> inputNodes, List paths) + { + var filteredNodes = new ObservableCollection>(); + + foreach (var node in inputNodes) + { + // 获取当前层需要排除的路径 + var matchedPaths = paths + .Where(path => IsPathMatch(node.FileName ??"", path)) + .Select(path => GetRemainingPath(node.FileName ?? "" , path)) + .Where(remainingPath => remainingPath != null) + .ToList(); + + // 如果当前路径完全匹配,跳过当前节点 + if (matchedPaths.Any(path => path == "")) + { + continue; + } + + // 递归对子节点过滤 + node.Children = FilterNodes(node.Children, matchedPaths); + + // 添加过滤后的节点 + filteredNodes.Add(node); + } + + return filteredNodes; + } + + return FilterNodes(nodes, folderNames); + } + + /// + /// 判断文件路径是否匹配多级路径的前缀 + /// + /// 当前节点路径 + /// 目标路径 + /// 是否匹配 + private static bool IsPathMatch(string fileName, string path) + { + // 匹配路径是否以指定前缀开始,路径分隔符对齐 + return path.StartsWith(fileName, StringComparison.OrdinalIgnoreCase) + && (path.Length == fileName.Length || path[fileName.Length] == '\\'); + } + + /// + /// 获取路径中去掉当前节点后的剩余路径 + /// + /// 当前节点路径 + /// 完整路径 + /// 剩余路径,如果不匹配返回 null + private static string? GetRemainingPath(string fileName, string path) + { + if (IsPathMatch(fileName, path)) + { + return path.Length > fileName.Length ? path.Substring(fileName.Length + 1) : ""; + } + return null; + } + public static ObservableCollection> FilterEmptyNodes(ObservableCollection> nodes) + { + // 递归过滤节点 + ObservableCollection> Filter(ObservableCollection> inputNodes) + { + var filteredNodes = new ObservableCollection>(); + + foreach (var node in inputNodes) + { + // 递归处理子节点 + node.Children = Filter(node.Children); + + // 如果是目录并且没有子节点,跳过当前节点 + if (node.IsDirectory && !node.Children.Any()) + { + continue; + } + + // 其他情况保留节点 + filteredNodes.Add(node); + } + + return filteredNodes; + } + + return Filter(nodes); + } } diff --git a/BetterGenshinImpact/View/Pages/ScriptControlPage.xaml b/BetterGenshinImpact/View/Pages/ScriptControlPage.xaml index 1c5b8769..6c615f85 100644 --- a/BetterGenshinImpact/View/Pages/ScriptControlPage.xaml +++ b/BetterGenshinImpact/View/Pages/ScriptControlPage.xaml @@ -331,6 +331,22 @@ + + + + + diff --git a/BetterGenshinImpact/ViewModel/Pages/ScriptControlViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/ScriptControlViewModel.cs index 98406c43..b5160bde 100644 --- a/BetterGenshinImpact/ViewModel/Pages/ScriptControlViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/ScriptControlViewModel.cs @@ -11,7 +11,6 @@ using System.Text.Json; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; -using System.Windows.Controls.Primitives; using BetterGenshinImpact.Core.Config; using BetterGenshinImpact.Core.Script; using BetterGenshinImpact.Core.Script.Group; @@ -30,6 +29,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using LogParse; using Microsoft.Extensions.Logging; +using SharpCompress; using Wpf.Ui; using Wpf.Ui.Controls; using Wpf.Ui.Violeta.Controls; @@ -107,25 +107,32 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware [RelayCommand] private void ClearTasks() { + if (SelectedScriptGroup == null) + { + return; + } SelectedScriptGroup.Projects.Clear(); WriteScriptGroup(SelectedScriptGroup); } [RelayCommand] private async Task OpenLogParse() { - - GameInfo gameInfo = null; + if (SelectedScriptGroup == null) + { + return; + } + GameInfo? gameInfo = null; var config = LogParse.LogParse.LoadConfig(); if (!string.IsNullOrEmpty(config.Cookie)) { config.CookieDictionary.TryGetValue(config.Cookie, out gameInfo); } - LogParseConfig.ScriptGroupLogParseConfig sgpc; - if (!config.ScriptGroupLogDictionary.TryGetValue(_selectedScriptGroup.Name,out sgpc)) + LogParseConfig.ScriptGroupLogParseConfig? sgpc; + if (!config.ScriptGroupLogDictionary.TryGetValue(SelectedScriptGroup.Name,out sgpc)) { sgpc=new LogParseConfig.ScriptGroupLogParseConfig(); } - + // 创建 StackPanel var stackPanel = new StackPanel @@ -265,9 +272,9 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware PrimaryButtonText = "确定", Owner = Application.Current.MainWindow, }; - questionButton.Click += (sender, args) => - { + void OnQuestionButtonOnClick(object sender, RoutedEventArgs args) + { WebpageWindow cookieWin = new() { Title = "日志分析", @@ -278,8 +285,9 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware }; cookieWin.NavigateToHtml(TravelsDiaryDetailManager.generHtmlMessage()); cookieWin.Show(); + } - }; + questionButton.Click += OnQuestionButtonOnClick; //对象赋值 rangeComboBox.SelectedValue = sgpc.RangeValue; @@ -305,7 +313,7 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware sgpc.HoeingDelay = hoeingDelayTextBox.Text; config.Cookie = cookieValue; - config.ScriptGroupLogDictionary[_selectedScriptGroup.Name]=sgpc; + config.ScriptGroupLogDictionary[SelectedScriptGroup.Name]=sgpc; LogParse.LogParse.WriteConfigFile(config); @@ -339,7 +347,7 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware Toast.Warning("未填写cookie,此次将不启用锄地统计!"); } //真正存储的gameinfo - GameInfo realGameInfo = gameInfo; + GameInfo? realGameInfo = gameInfo; //统计锄地开关打开,并且不为cookie不为空 if ((hoeingStatsSwitch.IsChecked ?? false) && !string.IsNullOrEmpty(cookieValue)) { @@ -350,7 +358,7 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware Toast.Success($"米游社数据获取成功,开始进行解析,请耐心等待!"); } - catch (Exception e) + catch (Exception) { if (realGameInfo!=null) { @@ -378,7 +386,7 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware var configGroupEntities = LogParse.LogParse.ParseFile(fs); if (rangeValue == "CurrentConfig") { //Toast.Success(_selectedScriptGroup.Name); - configGroupEntities =configGroupEntities.Where(item => _selectedScriptGroup.Name == item.Name).ToList(); + configGroupEntities =configGroupEntities.Where(item => SelectedScriptGroup.Name == item.Name).ToList(); } if (configGroupEntities.Count == 0) { @@ -414,11 +422,11 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware ScriptRepoUpdater.Instance.OpenLocalRepoInWebView(); } [RelayCommand] - private async Task UpdateTasks() + private void UpdateTasks() { List projects = new(); List oldProjects = new(); - oldProjects.AddRange(SelectedScriptGroup?.Projects); + oldProjects.AddRange(SelectedScriptGroup?.Projects ?? []); var oldcount = oldProjects.Count; List folderNames = new(); foreach (var project in oldProjects) @@ -451,25 +459,25 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware } } - SelectedScriptGroup.Projects.Clear(); + SelectedScriptGroup?.Projects.Clear(); foreach (var scriptGroupProject in projects) { SelectedScriptGroup?.AddProject(scriptGroupProject); } Toast.Success($"增加了{projects.Count - oldcount}个路径追踪任务"); - WriteScriptGroup(SelectedScriptGroup); - + if (SelectedScriptGroup != null) WriteScriptGroup(SelectedScriptGroup); } [RelayCommand] private void ReverseTaskOrder() { + List projects = new(); - projects.AddRange(SelectedScriptGroup?.Projects.Reverse()); - SelectedScriptGroup.Projects.Clear(); - projects.ForEach(item=>SelectedScriptGroup.Projects.Add(item)); - WriteScriptGroup(SelectedScriptGroup); + projects.AddRange(SelectedScriptGroup?.Projects.Reverse() ?? []); + SelectedScriptGroup?.Projects.Clear(); + projects.ForEach(item=>SelectedScriptGroup?.Projects.Add(item)); + if (SelectedScriptGroup != null) WriteScriptGroup(SelectedScriptGroup); } [RelayCommand] @@ -498,8 +506,12 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware else { var newScriptGroup =JsonSerializer.Deserialize(JsonSerializer.Serialize(item)) ; - newScriptGroup.Name = str; - ScriptGroups.Add(newScriptGroup); + if (newScriptGroup != null) + { + newScriptGroup.Name = str; + ScriptGroups.Add(newScriptGroup); + } + //WriteScriptGroup(newScriptGroup); } } @@ -628,12 +640,26 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware private ScrollViewer CreatePathingScriptSelectionPanel(IEnumerable> list) { var stackPanel = new StackPanel(); + CheckBox excludeCheckBox = new CheckBox + { + Content = "排除已选择过的目录", + VerticalAlignment = VerticalAlignment.Center, + }; + stackPanel.Children.Add(excludeCheckBox); + var filterTextBox = new TextBox { Margin = new Thickness(0, 0, 0, 10), - PlaceholderText = "输入筛选条件..." + PlaceholderText = "输入筛选条件...", + }; + filterTextBox.TextChanged += delegate + { + ApplyFilter(stackPanel, list, filterTextBox.Text, excludeCheckBox.IsChecked); + }; + excludeCheckBox.Click += delegate + { + ApplyFilter(stackPanel, list, filterTextBox.Text, excludeCheckBox.IsChecked); }; - filterTextBox.TextChanged += (s, e) => ApplyFilter(stackPanel, list, filterTextBox.Text); stackPanel.Children.Add(filterTextBox); AddNodesToPanel(stackPanel, list, 0, filterTextBox.Text); @@ -647,14 +673,50 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware return scrollViewer; } - private void ApplyFilter(StackPanel parentPanel, IEnumerable> nodes, string filter) + private void ApplyFilter(StackPanel parentPanel, IEnumerable> nodes, string filter,bool? excludeSelectedFolder = false) { - if (parentPanel.Children.Count > 0 && parentPanel.Children[0] is TextBox filterTextBox) + if (parentPanel.Children.Count > 0) + { + List removeElements = new List(); + foreach (UIElement parentPanelChild in parentPanel.Children) + { + if (parentPanelChild is FrameworkElement frameworkElement && frameworkElement.Name.StartsWith("dynamic_")) + { + removeElements.Add(frameworkElement); + } + + } + removeElements.ForEach(parentPanel.Children.Remove); + } + + if (excludeSelectedFolder ?? false) + { + + List skipFolderNames = SelectedScriptGroup?.Projects.ToList().Select(item=>item.FolderName).Distinct().ToList() ?? []; + //复制Nodes + string jsonString = JsonSerializer.Serialize(nodes); + var copiedNodes = JsonSerializer.Deserialize>>(jsonString); + if (copiedNodes!=null) + { + //路径过滤 + copiedNodes = FileTreeNodeHelper.FilterTree(copiedNodes, skipFolderNames); + copiedNodes = FileTreeNodeHelper.FilterEmptyNodes(copiedNodes); + AddNodesToPanel(parentPanel, copiedNodes, 0,filter); + } + + } + else + { + AddNodesToPanel(parentPanel, nodes, 0,filter); + } + + + /*if (parentPanel.Children.Count > 0 && parentPanel.Children[1] is TextBox filterTextBox) { parentPanel.Children.Clear(); parentPanel.Children.Add(filterTextBox); // 保留筛选框 AddNodesToPanel(parentPanel, nodes, 0, filter); - } + }*/ } private void AddNodesToPanel(StackPanel parentPanel, IEnumerable> nodes, int depth, string filter) @@ -671,6 +733,7 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware Content = node.FileName, Tag = node.FilePath, Margin = new Thickness(depth * 30, 0, 0, 0) // 根据深度计算Margin + ,Name = "dynamic_"+Guid.NewGuid().ToString().Replace("-","_") }; if (node.IsDirectory) @@ -683,6 +746,7 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware Header = checkBox, Content = childPanel, IsExpanded = false // 默认不展开 + ,Name = "dynamic_"+Guid.NewGuid().ToString().Replace("-","_") }; checkBox.Checked += (s, e) => SetChildCheckBoxesState(childPanel, true); @@ -793,7 +857,7 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware [RelayCommand] private void AddNextFlag(ScriptGroupProject? item) { - if (item == null) + if (item == null || SelectedScriptGroup == null) { return; } @@ -805,8 +869,8 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware nextScheduledTask.Remove(nst); } - nextScheduledTask.Add((SelectedScriptGroup?.Name, item.Index, item.FolderName, item.Name)); - foreach (var item1 in SelectedScriptGroup.Projects) + nextScheduledTask.Add((SelectedScriptGroup?.Name ?? "", item.Index, item.FolderName, item.Name)); + foreach (var item1 in SelectedScriptGroup?.Projects ?? []) { item1.NextFlag = false; } @@ -878,7 +942,15 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware Toast.Warning("只有JS脚本才有自定义配置"); } } - + [RelayCommand] + public void OnDeleteScriptByFolder(ScriptGroupProject? item) + { + if (item == null) + { + return; + } + SelectedScriptGroup?.Projects.ToList().Where(item2=>item2.FolderName == item.FolderName).ForEach(OnDeleteScript); + } [RelayCommand] public void OnDeleteScript(ScriptGroupProject? item) {