Phase 6: 一键多开 (工具组合) 功能

- 数据模型: ToolItem 新增 IsGroup(bool) + SubToolIds(List<string>) 字段

- 执行逻辑: ProcessExecutionService 改为 ExecuteAsync, 组合卡片遍历子工具逐一启动(500ms延迟), 孤儿ID跳过并打印警告

- 组合编辑: GroupEditViewModel + GroupEditWindow, 复选框列表勾选非组合工具

- 主界面: 标题栏新增 '+添加组合' 按钮(蓝色), 组合卡片右下角显示 📦 角标

- 右键菜单: 区分 '编辑工具' (普通) 和 '编辑组合' (IsGroup=true)

- 快捷键: HotKeyManager 适配 ExecuteAsync 异步调用

- 测试: 82 tests total (ProcessExecution 4->6, GroupEdit 5 new)
This commit is contained in:
2026-05-10 00:15:39 +08:00
parent 599964f078
commit 2c985e8d63
14 changed files with 668 additions and 31 deletions

View File

@@ -0,0 +1,144 @@
using System.Collections.ObjectModel;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using PersonalToolBox.Models;
using PersonalToolBox.Services;
namespace PersonalToolBox.ViewModels;
/// <summary>
/// 组合编辑窗口 ViewModel管理一键多开组合的创建与编辑
/// </summary>
public partial class GroupEditViewModel : ObservableObject
{
private readonly IDataService _dataService;
private readonly ILogService _logService;
private readonly ToolItem? _editingGroup;
// ──────── 可观察属性 ────────
[ObservableProperty]
private string _windowTitle = "添加组合";
[ObservableProperty]
private string _name = string.Empty;
[ObservableProperty]
private string _hotKey = string.Empty;
[ObservableProperty]
private Category? _selectedCategory;
/// <summary>
/// 可供勾选的普通工具列表IsGroup == false
/// </summary>
public ObservableCollection<SelectableTool> AvailableTools { get; } = new();
/// <summary>
/// 分类下拉列表
/// </summary>
public ObservableCollection<Category> Categories { get; } = new();
public Action<bool?>? CloseAction { get; set; }
public bool Saved { get; private set; }
// ──────── 构造 ────────
public GroupEditViewModel(IDataService dataService, ILogService logService, ToolItem? groupToEdit = null)
{
_dataService = dataService;
_logService = logService;
_editingGroup = groupToEdit;
foreach (var cat in _dataService.Config.Categories)
Categories.Add(cat);
// 加载所有普通工具(非组合)
var selectedIds = _editingGroup?.SubToolIds ?? new List<string>();
foreach (var tool in _dataService.Config.Tools.Where(t => !t.IsGroup))
{
AvailableTools.Add(new SelectableTool
{
Tool = tool,
IsSelected = selectedIds.Contains(tool.Id)
});
}
if (groupToEdit != null)
{
WindowTitle = "编辑组合";
Name = groupToEdit.Name;
HotKey = groupToEdit.HotKey;
SelectedCategory = Categories.FirstOrDefault(c => c.Id == groupToEdit.CategoryId);
}
}
// ──────── 命令 ────────
[RelayCommand]
private void Save()
{
try
{
if (string.IsNullOrWhiteSpace(Name))
{
_logService.Warning("组合名称不能为空");
return;
}
var selectedIds = AvailableTools
.Where(t => t.IsSelected)
.Select(t => t.Tool.Id)
.ToList();
if (_editingGroup != null)
{
_editingGroup.Name = Name.Trim();
_editingGroup.HotKey = HotKey.Trim();
_editingGroup.CategoryId = SelectedCategory?.Id ?? string.Empty;
_editingGroup.SubToolIds = selectedIds;
_editingGroup.IsValid = true;
_logService.Info($"已更新组合: {Name.Trim()}(包含 {selectedIds.Count} 个工具)");
}
else
{
_dataService.Config.Tools.Add(new ToolItem
{
Name = Name.Trim(),
HotKey = HotKey.Trim(),
CategoryId = SelectedCategory?.Id ?? string.Empty,
IsGroup = true,
SubToolIds = selectedIds,
IsValid = true
});
_logService.Info($"已添加组合: {Name.Trim()}(包含 {selectedIds.Count} 个工具)");
}
_dataService.Save();
Saved = true;
CloseAction?.Invoke(true);
}
catch (Exception ex)
{
_logService.Error($"保存组合失败: {ex.Message}");
}
}
[RelayCommand]
private void Cancel()
{
CloseAction?.Invoke(false);
}
}
/// <summary>
/// 可勾选的工具项(用于 CheckBox 列表绑定)
/// </summary>
public partial class SelectableTool : ObservableObject
{
public ToolItem Tool { get; set; } = null!;
[ObservableProperty]
private bool _isSelected;
}

View File

@@ -143,10 +143,10 @@ public partial class MainViewModel : ObservableObject
/// 执行工具(双击卡片或右键菜单)
/// </summary>
[RelayCommand]
private void ExecuteTool(ToolItem? tool)
private async Task ExecuteTool(ToolItem? tool)
{
if (tool == null) return;
_processService.Execute(tool);
await _processService.ExecuteAsync(tool);
}
// ───────────────────────────── 分类管理命令 ─────────────────────────────
@@ -206,6 +206,38 @@ public partial class MainViewModel : ObservableObject
RefreshData();
}
// ───────────────────────────── 组合管理命令 ─────────────────────────────
/// <summary>
/// 添加组合
/// </summary>
[RelayCommand]
private void AddGroup()
{
var vm = _serviceProvider.GetRequiredService<GroupEditViewModel>();
var window = new GroupEditWindow(vm);
window.ShowDialog();
if (vm.Saved) RefreshData();
}
/// <summary>
/// 编辑组合
/// </summary>
[RelayCommand]
private void EditGroup(ToolItem tool)
{
if (tool == null || !tool.IsGroup) return;
var dataService = _serviceProvider.GetRequiredService<IDataService>();
var logService = _serviceProvider.GetRequiredService<ILogService>();
var editVm = new GroupEditViewModel(dataService, logService, tool);
var window = new GroupEditWindow(editVm);
window.ShowDialog();
if (editVm.Saved) RefreshData();
}
// ───────────────────────────── 数据刷新 ─────────────────────────────
/// <summary>