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

@@ -5,17 +5,20 @@ namespace PersonalToolBox.Services;
/// <summary>
/// 进程执行服务,负责启动外部工具进程并处理异常
/// 支持一键多开:当 IsGroup 为 true 时批量启动子工具
/// </summary>
public class ProcessExecutionService : IProcessExecutionService
{
private readonly ILogService _logService;
private readonly IDataService _dataService;
public ProcessExecutionService(ILogService logService)
public ProcessExecutionService(ILogService logService, IDataService dataService)
{
_logService = logService;
_dataService = dataService;
}
public void Execute(ToolItem tool)
public async Task ExecuteAsync(ToolItem tool)
{
if (tool == null)
{
@@ -23,12 +26,63 @@ public class ProcessExecutionService : IProcessExecutionService
return;
}
// 组合卡片:遍历子工具列表逐一启动
if (tool.IsGroup)
{
await ExecuteGroupAsync(tool);
return;
}
// 普通工具:直接启动
if (!tool.IsValid)
{
_logService.Warning($"无法运行工具 \"{tool.Name}\",路径失效: {tool.ExecutablePath}");
return;
}
LaunchSingleTool(tool);
}
/// <summary>
/// 批量启动组合中的所有子工具,每次间隔 500ms 防止系统卡顿
/// </summary>
private async Task ExecuteGroupAsync(ToolItem group)
{
var subTools = new List<ToolItem>();
foreach (var subId in group.SubToolIds)
{
var subTool = _dataService.Config.Tools.FirstOrDefault(t => t.Id == subId);
if (subTool == null)
{
_logService.Warning($"组合启动跳过:找不到 ID 为 {subId} 的工具");
continue;
}
subTools.Add(subTool);
}
_logService.Info($"开始启动组合 \"{group.Name}\",共 {subTools.Count} 个工具");
foreach (var subTool in subTools)
{
if (!subTool.IsValid)
{
_logService.Warning($"组合启动跳过 \"{subTool.Name}\",路径失效: {subTool.ExecutablePath}");
continue;
}
LaunchSingleTool(subTool);
await Task.Delay(500);
}
_logService.Info($"组合 \"{group.Name}\" 启动完成");
}
/// <summary>
/// 启动单个工具进程
/// </summary>
private void LaunchSingleTool(ToolItem tool)
{
try
{
var startInfo = new ProcessStartInfo