Phase 7: 自定义图标系统 + 组合嵌套 + 删除功能 + UI 优化

- 安装 FontAwesome.Sharp v6.6.0,新增 Helpers/IconProvider.cs(190+ 图标,11 分类)
- ToolEditWindow/GroupEditWindow: 添加图标选择 ComboBox,按分类分组,带图标预览
- MainWindow 卡片模板: 使用 fa:IconBlock 渲染图标,IconCode 为空时回退首字母
- 组合角标从 emoji 改为 FontAwesome Cubes 图标,放置于卡片右上角
- ComboBox 样式修复: TextBlock→ContentPresenter,支持 ItemTemplate+DisplayMemberPath
- ComboBox 滚轮修复: CanContentScroll=False 解决分组模式下滑轮跳组问题
- 编辑弹窗: 所有输入控件添加 VerticalContentAlignment=Center
- 组合可包含其他组合: GetAncestorIds 递归排除自身及祖先防止循环引用
- ProcessExecutionService: 支持嵌套组合递归执行,visited 集合防死循环
- MainWindow 右键菜单新增删除功能,工具和组合均支持
- 移除侧边栏顶部个人工具箱标题文字
- 更新测试: 适配组合嵌套逻辑,新增祖先排除测试 (83/83 通过)
This commit is contained in:
2026-05-10 02:19:32 +08:00
parent 2c985e8d63
commit 85919381b1
13 changed files with 695 additions and 89 deletions

View File

@@ -1,11 +1,14 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using PersonalToolBox.Models;
namespace PersonalToolBox.Services;
/// <summary>
/// 进程执行服务,负责启动外部工具进程并处理异常
/// 支持一键多开:当 IsGroup 为 true 时批量启动子工具
/// 支持一键多开:当 IsGroup 为 true 时批量启动子工具(支持嵌套组合,带循环检测)
/// </summary>
public class ProcessExecutionService : IProcessExecutionService
{
@@ -26,14 +29,12 @@ public class ProcessExecutionService : IProcessExecutionService
return;
}
// 组合卡片:遍历子工具列表逐一启动
if (tool.IsGroup)
{
await ExecuteGroupAsync(tool);
await ExecuteGroupAsync(tool, new HashSet<string>());
return;
}
// 普通工具:直接启动
if (!tool.IsValid)
{
_logService.Warning($"无法运行工具 \"{tool.Name}\",路径失效: {tool.ExecutablePath}");
@@ -44,12 +45,17 @@ public class ProcessExecutionService : IProcessExecutionService
}
/// <summary>
/// 批量启动组合中的所有子工具,每次间隔 500ms 防止系统卡顿
/// 批量启动组合中的所有子工具,支持嵌套组合,每次间隔 500ms 防止系统卡顿
/// </summary>
private async Task ExecuteGroupAsync(ToolItem group)
private async Task ExecuteGroupAsync(ToolItem group, HashSet<string> visited)
{
var subTools = new List<ToolItem>();
if (!visited.Add(group.Id))
{
_logService.Warning($"组合启动跳过:检测到循环引用 ({group.Name})");
return;
}
var subTools = new List<ToolItem>();
foreach (var subId in group.SubToolIds)
{
var subTool = _dataService.Config.Tools.FirstOrDefault(t => t.Id == subId);
@@ -65,13 +71,19 @@ public class ProcessExecutionService : IProcessExecutionService
foreach (var subTool in subTools)
{
if (!subTool.IsValid)
if (subTool.IsGroup)
{
_logService.Warning($"组合启动跳过 \"{subTool.Name}\",路径失效: {subTool.ExecutablePath}");
continue;
await ExecuteGroupAsync(subTool, visited);
}
else
{
if (!subTool.IsValid)
{
_logService.Warning($"组合启动跳过 \"{subTool.Name}\",路径失效: {subTool.ExecutablePath}");
continue;
}
LaunchSingleTool(subTool);
}
LaunchSingleTool(subTool);
await Task.Delay(500);
}