feat: 新增采集与锄地一键采集工具页面2

This commit is contained in:
辉鸭蛋
2026-05-18 02:05:18 +08:00
parent 3dcc9dde70
commit 4978b72107
9 changed files with 248 additions and 35 deletions

View File

@@ -156,6 +156,7 @@ public partial class App : Application
services.AddView<KeyMouseRecordPage, KeyMouseRecordPageViewModel>();
services.AddView<JsListPage, JsListViewModel>();
services.AddView<MapPathingPage, MapPathingViewModel>();
services.AddView<GatheringAndFarmingPage, GatheringAndFarmingPageViewModel>();
services.AddView<OneDragonFlowPage, OneDragonFlowViewModel>();
services.AddSingleton<PathingConfigViewModel>();
// services.AddView<PathingConfigView, PathingConfigViewModel>();

View File

@@ -1,3 +1,4 @@
using BetterGenshinImpact.Core.Script.Group;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
@@ -24,6 +25,9 @@ public class GearTaskDefinitionData
[JsonProperty("order")]
public int Order { get; set; } = 0;
[JsonProperty("group_config")]
public ScriptGroupConfig GroupConfig { get; set; } = new();
[JsonProperty("root_task")]
public GearTaskData? RootTask { get; set; }
}
@@ -65,4 +69,4 @@ public class GearTaskData
[JsonProperty("children")]
public List<GearTaskData> Children { get; set; } = new();
}
}

View File

@@ -9,6 +9,7 @@ using BetterGenshinImpact.Model.Gear.Tasks;
using BetterGenshinImpact.Model.Gear.Parameter;
using BetterGenshinImpact.Core.Script;
using BetterGenshinImpact.Core.Config;
using BetterGenshinImpact.Core.Script.Group;
using BetterGenshinImpact.Service.GearTask;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
@@ -52,7 +53,7 @@ public class GearTaskConverter
{
_logger.LogInformation("开始转换任务定义: {TaskDefinitionName}", taskDefinition.Name);
var rootTask = await ConvertTaskDataAsync(taskDefinition.RootTask);
var rootTask = await ConvertTaskDataAsync(taskDefinition.RootTask, null, taskDefinition.GroupConfig);
tasks.Add(rootTask);
_logger.LogInformation("任务定义转换完成: {TaskDefinitionName}, 共 {TaskCount} 个任务",
@@ -73,7 +74,7 @@ public class GearTaskConverter
/// <param name="taskData">任务数据</param>
/// <param name="parent">父任务</param>
/// <returns>转换后的任务</returns>
public async Task<BaseGearTask> ConvertTaskDataAsync(GearTaskData taskData, BaseGearTask? parent = null)
public async Task<BaseGearTask> ConvertTaskDataAsync(GearTaskData taskData, BaseGearTask? parent = null, ScriptGroupConfig? groupConfig = null)
{
if (taskData == null)
{
@@ -106,7 +107,7 @@ public class GearTaskConverter
else
{
// 使用工厂创建具体的任务实例
var preparedTaskData = PrepareTaskDataForExecution(taskData);
var preparedTaskData = PrepareTaskDataForExecution(taskData, groupConfig);
task = await _taskFactory.CreateTaskAsync(preparedTaskData);
task.Father = parent;
@@ -123,7 +124,7 @@ public class GearTaskConverter
{
try
{
var childTask = await ConvertTaskDataAsync(childData, task);
var childTask = await ConvertTaskDataAsync(childData, task, groupConfig);
task.Children.Add(childTask);
}
catch (Exception ex)
@@ -369,49 +370,54 @@ public class GearTaskConverter
return result;
}
private GearTaskData PrepareTaskDataForExecution(GearTaskData taskData)
private GearTaskData PrepareTaskDataForExecution(GearTaskData taskData, ScriptGroupConfig? groupConfig)
{
if (string.Equals(taskData.TaskType, "Javascript", StringComparison.OrdinalIgnoreCase))
{
var javascriptParameters = DeserializeJavascriptParams(taskData.Parameters);
javascriptParameters.PathingPartyConfig = groupConfig?.PathingConfig;
return BuildPreparedTaskData(taskData, JsonConvert.SerializeObject(javascriptParameters));
}
if (string.Equals(taskData.TaskType, "Shell", StringComparison.OrdinalIgnoreCase))
{
if (groupConfig?.EnableShellConfig == true)
{
return BuildPreparedTaskData(taskData, JsonConvert.SerializeObject(groupConfig.ShellConfig));
}
return taskData;
}
if (!string.Equals(taskData.TaskType, "Pathing", StringComparison.OrdinalIgnoreCase))
{
return taskData;
}
var parameters = DeserializePathingParams(taskData.Parameters);
if (!string.IsNullOrWhiteSpace(parameters.Path)
&& !TryExtractPathingRepoRelativePath(parameters.Path, out _))
var pathingParameters = DeserializePathingParams(taskData.Parameters);
pathingParameters.PathingPartyConfig = groupConfig?.PathingConfig;
if (!string.IsNullOrWhiteSpace(pathingParameters.Path)
&& !TryExtractPathingRepoRelativePath(pathingParameters.Path, out _))
{
return taskData;
return BuildPreparedTaskData(taskData, JsonConvert.SerializeObject(pathingParameters));
}
if (string.IsNullOrWhiteSpace(taskData.Path))
{
return taskData;
return BuildPreparedTaskData(taskData, JsonConvert.SerializeObject(pathingParameters));
}
if (TryExtractPathingRepoRelativePath(taskData.Path, out var repoRelativePath)
&& repoRelativePath.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
{
parameters.Path = GetPathingExecutionFilePath(repoRelativePath);
pathingParameters.Path = GetPathingExecutionFilePath(repoRelativePath);
}
else
{
parameters.Path = taskData.Path.Trim().TrimEnd('\\', '/');
pathingParameters.Path = taskData.Path.Trim().TrimEnd('\\', '/');
}
return new GearTaskData
{
Name = taskData.Name,
TaskType = taskData.TaskType,
Path = taskData.Path,
IsEnabled = taskData.IsEnabled,
IsDirectory = taskData.IsDirectory,
IsExpanded = taskData.IsExpanded,
Parameters = JsonConvert.SerializeObject(parameters),
CreatedTime = taskData.CreatedTime,
ModifiedTime = taskData.ModifiedTime,
Priority = taskData.Priority,
Children = taskData.Children
};
return BuildPreparedTaskData(taskData, JsonConvert.SerializeObject(pathingParameters));
}
private PathingGearTaskParams DeserializePathingParams(string? parametersJson)
@@ -431,6 +437,41 @@ public class GearTaskConverter
}
}
private JavascriptGearTaskParams DeserializeJavascriptParams(string? parametersJson)
{
if (string.IsNullOrWhiteSpace(parametersJson))
{
return new JavascriptGearTaskParams();
}
try
{
return JsonConvert.DeserializeObject<JavascriptGearTaskParams>(parametersJson) ?? new JavascriptGearTaskParams();
}
catch
{
return new JavascriptGearTaskParams();
}
}
private static GearTaskData BuildPreparedTaskData(GearTaskData taskData, string parametersJson)
{
return new GearTaskData
{
Name = taskData.Name,
TaskType = taskData.TaskType,
Path = taskData.Path,
IsEnabled = taskData.IsEnabled,
IsDirectory = taskData.IsDirectory,
IsExpanded = taskData.IsExpanded,
Parameters = parametersJson,
CreatedTime = taskData.CreatedTime,
ModifiedTime = taskData.ModifiedTime,
Priority = taskData.Priority,
Children = taskData.Children
};
}
private static string BuildPathingPlaceholderPath(string repoRelativePath, bool isDirectory)
{
var normalized = repoRelativePath.Replace('/', Path.DirectorySeparatorChar);

View File

@@ -185,6 +185,7 @@ public class GearTaskStorageService
CreatedTime = viewModel.CreatedTime,
ModifiedTime = viewModel.ModifiedTime,
Order = viewModel.Order,
GroupConfig = viewModel.GroupConfig,
RootTask = viewModel.RootTask != null ? ConvertTaskToData(viewModel.RootTask) : null
};
}
@@ -229,6 +230,7 @@ public class GearTaskStorageService
CreatedTime = data.CreatedTime,
ModifiedTime = data.ModifiedTime,
Order = data.Order,
GroupConfig = data.GroupConfig ?? new(),
RootTask = data.RootTask != null ? ConvertTaskToViewModel(data.RootTask) : null
};
@@ -276,4 +278,4 @@ public class GearTaskStorageService
var safeName = string.Join("_", name.Split(invalidChars, StringSplitOptions.RemoveEmptyEntries));
return string.IsNullOrWhiteSpace(safeName) ? "unnamed_task" : safeName;
}
}
}

View File

@@ -138,6 +138,13 @@
<ui:SymbolIcon Symbol="Map24" />
</ui:NavigationViewItem.Icon>
</ui:NavigationViewItem>
<ui:NavigationViewItem Content="采集与锄地"
NavigationCacheMode="Enabled"
TargetPageType="{x:Type pages:GatheringAndFarmingPage}">
<ui:NavigationViewItem.Icon>
<ui:SymbolIcon Symbol="Map24" />
</ui:NavigationViewItem.Icon>
</ui:NavigationViewItem>
<!-- Script16 -->
<ui:NavigationViewItem Content="录制回放"
NavigationCacheMode="Enabled"

View File

@@ -621,11 +621,15 @@
<MenuItem Header="Shell" Command="{Binding AddTaskNodeCommand}" CommandParameter="Shell" />
<MenuItem Header="C# 方法" Command="{Binding AddTaskNodeCommand}" CommandParameter="CSharp" />
</ContextMenu>
</ui:DropDownButton.Flyout>
</ui:DropDownButton.Flyout>
</ui:DropDownButton>
<ui:Button Command="{Binding AddTaskGroupCommand}"
Content="添加组"
Icon="{ui:SymbolIcon Folder24}" />
Icon="{ui:SymbolIcon Folder24}"
Margin="0,0,10,0" />
<ui:Button Command="{Binding OpenSelectedTaskDefinitionSettingsCommand}"
Content="设置"
Icon="{ui:SymbolIcon Settings24}" />
</StackPanel>
<!-- 任务树 -->

View File

@@ -1,5 +1,4 @@
using System.Windows.Controls;
using BetterGenshinImpact.Core.Script.Group;
using System.Windows.Controls;
using BetterGenshinImpact.ViewModel.Pages.View;
namespace BetterGenshinImpact.View.Pages.View
@@ -13,9 +12,8 @@ namespace BetterGenshinImpact.View.Pages.View
public ScriptGroupConfigView(ScriptGroupConfigViewModel viewModel)
{
DataContext = ViewModel = viewModel;
DataContext = ViewModel = viewModel;
InitializeComponent();
}
}
}

View File

@@ -1,5 +1,5 @@
using System;
using System.Linq;
using BetterGenshinImpact.Core.Script.Group;
using CommunityToolkit.Mvvm.ComponentModel;
namespace BetterGenshinImpact.ViewModel.Pages.Component;
@@ -39,6 +39,9 @@ public partial class GearTaskDefinitionViewModel : ObservableObject
[ObservableProperty]
private bool _hasExecutionBadge;
[ObservableProperty]
private ScriptGroupConfig _groupConfig = new();
/// <summary>
/// 任务根节点
/// </summary>

View File

@@ -1,24 +1,32 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using BetterGenshinImpact.Core.Config;
using BetterGenshinImpact.Core.Script;
using BetterGenshinImpact.GameTask;
using BetterGenshinImpact.Helpers.Ui;
using BetterGenshinImpact.Service;
using BetterGenshinImpact.Service.GearTask;
using BetterGenshinImpact.Service.GearTask.Execution;
using BetterGenshinImpact.View.Pages.View;
using BetterGenshinImpact.View.Windows;
using BetterGenshinImpact.View.Windows.GearTask;
using BetterGenshinImpact.ViewModel.Pages.Component;
using BetterGenshinImpact.ViewModel.Pages.View;
using BetterGenshinImpact.ViewModel.Windows;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.Logging;
using Wpf.Ui;
using Wpf.Ui.Violeta.Controls;
namespace BetterGenshinImpact.ViewModel.Pages;
@@ -432,6 +440,37 @@ public partial class GearTaskListPageViewModel : ViewModel
_taskExecutor.StopExecution();
}
[RelayCommand]
private async Task OpenSelectedTaskDefinitionSettings()
{
if (SelectedTaskDefinition == null)
{
Toast.Warning("请先选择一个任务定义");
return;
}
var dialogWindow = new Wpf.Ui.Controls.FluentWindow
{
Title = "任务组设置",
Content = new ScriptGroupConfigView(new ScriptGroupConfigViewModel(TaskContext.Instance().Config, SelectedTaskDefinition.GroupConfig)),
Width = 800,
Height = 600,
MinWidth = 800,
MaxWidth = 800,
MinHeight = 600,
Owner = Application.Current.MainWindow,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
ExtendsContentIntoTitleBar = true,
WindowBackdropType = Wpf.Ui.Controls.WindowBackdropType.Auto,
};
dialogWindow.SourceInitialized += (_, _) => WindowHelper.TryApplySystemBackdrop(dialogWindow);
dialogWindow.ShowDialog();
SelectedTaskDefinition.ModifiedTime = DateTime.Now;
await _storageService.SaveTaskDefinitionAsync(SelectedTaskDefinition);
}
[RelayCommand]
private async Task AddTaskNode(string? taskType = null)
{
@@ -679,6 +718,11 @@ public partial class GearTaskListPageViewModel : ViewModel
{
SetupTaskPropertyChangeListener(taskDefinition.RootTask, taskDefinition);
}
if (taskDefinition.GroupConfig != null)
{
SetupTaskGroupConfigChangeListener(taskDefinition.GroupConfig, taskDefinition);
}
}
/// <summary>
@@ -726,6 +770,115 @@ public partial class GearTaskListPageViewModel : ViewModel
};
}
/// <summary>
/// 递归监听任务组配置及其子配置变化,实现自动保存。
/// </summary>
private void SetupTaskGroupConfigChangeListener(object configObject, GearTaskDefinitionViewModel parentDefinition)
{
var visited = new HashSet<object>();
SubscribeTaskGroupConfigChanges(configObject, parentDefinition, visited);
}
private void SubscribeTaskGroupConfigChanges(object? current, GearTaskDefinitionViewModel parentDefinition, HashSet<object> visited)
{
if (current == null || !visited.Add(current))
{
return;
}
if (current is INotifyPropertyChanged notifyPropertyChanged)
{
notifyPropertyChanged.PropertyChanged += async (_, _) =>
{
try
{
parentDefinition.ModifiedTime = DateTime.Now;
await _storageService.SaveTaskDefinitionAsync(parentDefinition);
}
catch (Exception ex)
{
_logger.LogError(ex, "任务组配置自动保存失败: {TaskName}", parentDefinition.Name);
}
};
}
if (current is INotifyCollectionChanged notifyCollectionChanged)
{
notifyCollectionChanged.CollectionChanged += async (_, e) =>
{
if (e.NewItems != null)
{
foreach (var item in e.NewItems)
{
SubscribeTaskGroupConfigChanges(item, parentDefinition, visited);
}
}
try
{
parentDefinition.ModifiedTime = DateTime.Now;
await _storageService.SaveTaskDefinitionAsync(parentDefinition);
}
catch (Exception ex)
{
_logger.LogError(ex, "任务组配置自动保存失败: {TaskName}", parentDefinition.Name);
}
};
}
foreach (var child in EnumerateConfigChildren(current))
{
SubscribeTaskGroupConfigChanges(child, parentDefinition, visited);
}
}
private static IEnumerable<object> EnumerateConfigChildren(object current)
{
var properties = current.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var property in properties)
{
if (!property.CanRead || property.GetIndexParameters().Length > 0)
{
continue;
}
if (property.PropertyType == typeof(string) || property.PropertyType.IsValueType)
{
continue;
}
object? value;
try
{
value = property.GetValue(current);
}
catch
{
continue;
}
if (value == null || value is string)
{
continue;
}
if (value is IEnumerable enumerable)
{
foreach (var item in enumerable)
{
if (item != null && item is not string)
{
yield return item;
}
}
continue;
}
yield return value;
}
}
private async Task RefreshTaskDefinitionExecutionSummariesAsync()
{
foreach (var taskDefinition in TaskDefinitions)