From 7915140acbba9f111c141ff679b0d9b06da1acbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Tue, 26 Aug 2025 01:31:20 +0800 Subject: [PATCH] feat: implement GearTaskStorageService for task definition persistence --- BetterGenshinImpact/App.xaml.cs | 1 + .../Model/Gear/GearTaskData.cs | 65 ++++ .../Service/GearTaskStorageService.cs | 304 ++++++++++++++++++ .../View/Pages/GearTaskListPage.xaml | 10 +- .../Pages/GearTaskListPageViewModel.cs | 193 +++++++++-- 5 files changed, 546 insertions(+), 27 deletions(-) create mode 100644 BetterGenshinImpact/Model/Gear/GearTaskData.cs create mode 100644 BetterGenshinImpact/Service/GearTaskStorageService.cs diff --git a/BetterGenshinImpact/App.xaml.cs b/BetterGenshinImpact/App.xaml.cs index e5da5253..0da6eb9c 100644 --- a/BetterGenshinImpact/App.xaml.cs +++ b/BetterGenshinImpact/App.xaml.cs @@ -136,6 +136,7 @@ public partial class App : Application services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); // Configuration //services.Configure(context.Configuration.GetSection(nameof(AppConfig))); diff --git a/BetterGenshinImpact/Model/Gear/GearTaskData.cs b/BetterGenshinImpact/Model/Gear/GearTaskData.cs new file mode 100644 index 00000000..fae62464 --- /dev/null +++ b/BetterGenshinImpact/Model/Gear/GearTaskData.cs @@ -0,0 +1,65 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; + +namespace BetterGenshinImpact.Model.Gear; + +/// +/// 任务定义数据模型,用于 JSON 序列化 +/// +public class GearTaskDefinitionData +{ + [JsonProperty("name")] + public string Name { get; set; } = string.Empty; + + [JsonProperty("description")] + public string Description { get; set; } = string.Empty; + + [JsonProperty("created_time")] + public DateTime CreatedTime { get; set; } = DateTime.Now; + + [JsonProperty("modified_time")] + public DateTime ModifiedTime { get; set; } = DateTime.Now; + + [JsonProperty("root_task")] + public GearTaskData? RootTask { get; set; } +} + +/// +/// 任务数据模型,用于 JSON 序列化 +/// +public class GearTaskData +{ + [JsonProperty("name")] + public string Name { get; set; } = string.Empty; + + [JsonProperty("description")] + public string Description { get; set; } = string.Empty; + + [JsonProperty("task_type")] + public string TaskType { get; set; } = string.Empty; + + [JsonProperty("is_enabled")] + public bool IsEnabled { get; set; } = true; + + [JsonProperty("is_directory")] + public bool IsDirectory { get; set; } = false; + + [JsonProperty("parameters")] + public string Parameters { get; set; } = "{}"; + + [JsonProperty("created_time")] + public DateTime CreatedTime { get; set; } = DateTime.Now; + + [JsonProperty("modified_time")] + public DateTime ModifiedTime { get; set; } = DateTime.Now; + + [JsonProperty("priority")] + public int Priority { get; set; } = 0; + + [JsonProperty("tags")] + public string Tags { get; set; } = string.Empty; + + [JsonProperty("children")] + public List Children { get; set; } = new(); +} \ No newline at end of file diff --git a/BetterGenshinImpact/Service/GearTaskStorageService.cs b/BetterGenshinImpact/Service/GearTaskStorageService.cs new file mode 100644 index 00000000..abe59bb8 --- /dev/null +++ b/BetterGenshinImpact/Service/GearTaskStorageService.cs @@ -0,0 +1,304 @@ +using BetterGenshinImpact.Model.Gear; +using BetterGenshinImpact.ViewModel.Pages.Component; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using BetterGenshinImpact.Core.Config; + +namespace BetterGenshinImpact.Service; + +/// +/// 齿轮任务存储服务,负责任务定义的 JSON 持久化 +/// +public class GearTaskStorageService +{ + private readonly ILogger _logger; + private readonly string _taskStoragePath; + private readonly JsonSerializerSettings _jsonSettings; + + public GearTaskStorageService(ILogger logger) + { + _logger = logger; + _taskStoragePath = Path.Combine(Global.Absolute("User"), "task_v2", "list"); + + // 确保目录存在 + Directory.CreateDirectory(_taskStoragePath); + + // 配置 JSON 序列化设置 + _jsonSettings = new JsonSerializerSettings + { + Formatting = Formatting.Indented, + DateFormatString = "yyyy-MM-dd HH:mm:ss", + NullValueHandling = NullValueHandling.Ignore + }; + } + + /// + /// 保存任务定义到 JSON 文件 + /// + /// 任务定义 + /// + public async Task SaveTaskDefinitionAsync(GearTaskDefinitionViewModel taskDefinition) + { + try + { + var data = ConvertToData(taskDefinition); + var fileName = GetSafeFileName(taskDefinition.Name) + ".json"; + var filePath = Path.Combine(_taskStoragePath, fileName); + + var json = JsonConvert.SerializeObject(data, _jsonSettings); + await File.WriteAllTextAsync(filePath, json); + + _logger.LogInformation("任务定义 '{TaskName}' 已保存到 {FilePath}", taskDefinition.Name, filePath); + } + catch (Exception ex) + { + _logger.LogError(ex, "保存任务定义 '{TaskName}' 时发生错误", taskDefinition.Name); + throw; + } + } + + /// + /// 从 JSON 文件加载任务定义 + /// + /// 文件名(不含扩展名) + /// + public async Task LoadTaskDefinitionAsync(string fileName) + { + try + { + var filePath = Path.Combine(_taskStoragePath, fileName + ".json"); + if (!File.Exists(filePath)) + { + _logger.LogWarning("任务定义文件不存在: {FilePath}", filePath); + return null; + } + + var json = await File.ReadAllTextAsync(filePath); + var data = JsonConvert.DeserializeObject(json, _jsonSettings); + + if (data == null) + { + _logger.LogWarning("无法反序列化任务定义文件: {FilePath}", filePath); + return null; + } + + var viewModel = ConvertToViewModel(data); + _logger.LogInformation("任务定义 '{TaskName}' 已从 {FilePath} 加载", data.Name, filePath); + + return viewModel; + } + catch (Exception ex) + { + _logger.LogError(ex, "加载任务定义文件 '{FileName}' 时发生错误", fileName); + throw; + } + } + + /// + /// 加载所有任务定义 + /// + /// + public async Task> LoadAllTaskDefinitionsAsync() + { + var taskDefinitions = new List(); + + try + { + var jsonFiles = Directory.GetFiles(_taskStoragePath, "*.json"); + + foreach (var filePath in jsonFiles) + { + try + { + var json = await File.ReadAllTextAsync(filePath); + var data = JsonConvert.DeserializeObject(json, _jsonSettings); + + if (data != null) + { + var viewModel = ConvertToViewModel(data); + taskDefinitions.Add(viewModel); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "加载任务定义文件 '{FilePath}' 时发生错误", filePath); + } + } + + _logger.LogInformation("已加载 {Count} 个任务定义", taskDefinitions.Count); + } + catch (Exception ex) + { + _logger.LogError(ex, "加载任务定义列表时发生错误"); + } + + return taskDefinitions; + } + + /// + /// 删除任务定义文件 + /// + /// 任务名称 + /// + public async Task DeleteTaskDefinitionAsync(string taskName) + { + try + { + var fileName = GetSafeFileName(taskName) + ".json"; + var filePath = Path.Combine(_taskStoragePath, fileName); + + if (File.Exists(filePath)) + { + await Task.Run(() => File.Delete(filePath)); + _logger.LogInformation("任务定义文件已删除: {FilePath}", filePath); + } + else + { + _logger.LogWarning("要删除的任务定义文件不存在: {FilePath}", filePath); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "删除任务定义 '{TaskName}' 时发生错误", taskName); + throw; + } + } + + /// + /// 重命名任务定义文件 + /// + /// 旧名称 + /// 新名称 + /// + public async Task RenameTaskDefinitionAsync(string oldName, string newName) + { + try + { + var oldFileName = GetSafeFileName(oldName) + ".json"; + var newFileName = GetSafeFileName(newName) + ".json"; + var oldFilePath = Path.Combine(_taskStoragePath, oldFileName); + var newFilePath = Path.Combine(_taskStoragePath, newFileName); + + if (File.Exists(oldFilePath)) + { + await Task.Run(() => File.Move(oldFilePath, newFilePath)); + _logger.LogInformation("任务定义文件已重命名: {OldPath} -> {NewPath}", oldFilePath, newFilePath); + } + else + { + _logger.LogWarning("要重命名的任务定义文件不存在: {FilePath}", oldFilePath); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "重命名任务定义 '{OldName}' -> '{NewName}' 时发生错误", oldName, newName); + throw; + } + } + + /// + /// 将 ViewModel 转换为数据模型 + /// + /// 视图模型 + /// + private GearTaskDefinitionData ConvertToData(GearTaskDefinitionViewModel viewModel) + { + return new GearTaskDefinitionData + { + Name = viewModel.Name, + Description = viewModel.Description, + CreatedTime = viewModel.CreatedTime, + ModifiedTime = viewModel.ModifiedTime, + RootTask = viewModel.RootTask != null ? ConvertTaskToData(viewModel.RootTask) : null + }; + } + + /// + /// 将任务 ViewModel 转换为数据模型 + /// + /// 任务视图模型 + /// + private GearTaskData ConvertTaskToData(GearTaskViewModel viewModel) + { + return new GearTaskData + { + Name = viewModel.Name, + Description = viewModel.Description, + TaskType = viewModel.TaskType, + IsEnabled = viewModel.IsEnabled, + IsDirectory = viewModel.IsDirectory, + Parameters = viewModel.Parameters, + CreatedTime = viewModel.CreatedTime, + ModifiedTime = viewModel.ModifiedTime, + Priority = viewModel.Priority, + Tags = viewModel.Tags, + Children = viewModel.Children.Select(ConvertTaskToData).ToList() + }; + } + + /// + /// 将数据模型转换为 ViewModel + /// + /// 数据模型 + /// + private GearTaskDefinitionViewModel ConvertToViewModel(GearTaskDefinitionData data) + { + var viewModel = new GearTaskDefinitionViewModel + { + Name = data.Name, + Description = data.Description, + CreatedTime = data.CreatedTime, + ModifiedTime = data.ModifiedTime, + RootTask = data.RootTask != null ? ConvertTaskToViewModel(data.RootTask) : null + }; + + return viewModel; + } + + /// + /// 将任务数据模型转换为 ViewModel + /// + /// 任务数据模型 + /// + private GearTaskViewModel ConvertTaskToViewModel(GearTaskData data) + { + var viewModel = new GearTaskViewModel + { + Name = data.Name, + Description = data.Description, + TaskType = data.TaskType, + IsEnabled = data.IsEnabled, + IsDirectory = data.IsDirectory, + Parameters = data.Parameters, + CreatedTime = data.CreatedTime, + ModifiedTime = data.ModifiedTime, + Priority = data.Priority, + Tags = data.Tags + }; + + // 递归转换子任务 + foreach (var childData in data.Children) + { + viewModel.Children.Add(ConvertTaskToViewModel(childData)); + } + + return viewModel; + } + + /// + /// 获取安全的文件名(移除非法字符) + /// + /// 原始名称 + /// + private string GetSafeFileName(string name) + { + var invalidChars = Path.GetInvalidFileNameChars(); + var safeName = string.Join("_", name.Split(invalidChars, StringSplitOptions.RemoveEmptyEntries)); + return string.IsNullOrWhiteSpace(safeName) ? "unnamed_task" : safeName; + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/View/Pages/GearTaskListPage.xaml b/BetterGenshinImpact/View/Pages/GearTaskListPage.xaml index 0689195f..6fc17787 100644 --- a/BetterGenshinImpact/View/Pages/GearTaskListPage.xaml +++ b/BetterGenshinImpact/View/Pages/GearTaskListPage.xaml @@ -7,6 +7,7 @@ xmlns:pages="clr-namespace:BetterGenshinImpact.ViewModel.Pages" xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" xmlns:component="clr-namespace:BetterGenshinImpact.ViewModel.Pages.Component" + xmlns:dd="urn:gong-wpf-dragdrop" d:DataContext="{d:DesignInstance Type=pages:GearTaskListPageViewModel}" d:DesignHeight="850" d:DesignWidth="1200" @@ -153,6 +154,9 @@ ItemTemplate="{StaticResource TaskDefinitionItemTemplate}" Background="Transparent" BorderThickness="0" + dd:DragDrop.IsDragSource="True" + dd:DragDrop.IsDropTarget="True" + dd:DragDrop.UseDefaultDragAdorner="True" Padding="4">