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:
@@ -1,7 +1,9 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using PersonalToolBox.Helpers;
|
||||
using PersonalToolBox.Models;
|
||||
using PersonalToolBox.Services;
|
||||
|
||||
@@ -30,6 +32,9 @@ public partial class GroupEditViewModel : ObservableObject
|
||||
[ObservableProperty]
|
||||
private Category? _selectedCategory;
|
||||
|
||||
[ObservableProperty]
|
||||
private IconProvider.IconOption? _selectedIcon;
|
||||
|
||||
/// <summary>
|
||||
/// 可供勾选的普通工具列表(IsGroup == false)
|
||||
/// </summary>
|
||||
@@ -40,6 +45,11 @@ public partial class GroupEditViewModel : ObservableObject
|
||||
/// </summary>
|
||||
public ObservableCollection<Category> Categories { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 按分类分组的可用图标列表
|
||||
/// </summary>
|
||||
public ListCollectionView AvailableIconsView { get; }
|
||||
|
||||
public Action<bool?>? CloseAction { get; set; }
|
||||
public bool Saved { get; private set; }
|
||||
|
||||
@@ -51,12 +61,16 @@ public partial class GroupEditViewModel : ObservableObject
|
||||
_logService = logService;
|
||||
_editingGroup = groupToEdit;
|
||||
|
||||
AvailableIconsView = new ListCollectionView(IconProvider.AvailableIcons);
|
||||
AvailableIconsView.GroupDescriptions.Add(new PropertyGroupDescription("Category"));
|
||||
|
||||
foreach (var cat in _dataService.Config.Categories)
|
||||
Categories.Add(cat);
|
||||
|
||||
// 加载所有普通工具(非组合)
|
||||
// 加载所有工具(排除自身及祖先组合,防止循环引用)
|
||||
var excludeIds = GetAncestorIds();
|
||||
var selectedIds = _editingGroup?.SubToolIds ?? new List<string>();
|
||||
foreach (var tool in _dataService.Config.Tools.Where(t => !t.IsGroup))
|
||||
foreach (var tool in _dataService.Config.Tools.Where(t => !excludeIds.Contains(t.Id)))
|
||||
{
|
||||
AvailableTools.Add(new SelectableTool
|
||||
{
|
||||
@@ -71,6 +85,11 @@ public partial class GroupEditViewModel : ObservableObject
|
||||
Name = groupToEdit.Name;
|
||||
HotKey = groupToEdit.HotKey;
|
||||
SelectedCategory = Categories.FirstOrDefault(c => c.Id == groupToEdit.CategoryId);
|
||||
|
||||
if (!string.IsNullOrEmpty(groupToEdit.IconCode))
|
||||
{
|
||||
SelectedIcon = IconProvider.AvailableIcons.FirstOrDefault(i => i.Icon.ToString() == groupToEdit.IconCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +114,7 @@ public partial class GroupEditViewModel : ObservableObject
|
||||
if (_editingGroup != null)
|
||||
{
|
||||
_editingGroup.Name = Name.Trim();
|
||||
_editingGroup.IconCode = SelectedIcon?.Icon.ToString() ?? string.Empty;
|
||||
_editingGroup.HotKey = HotKey.Trim();
|
||||
_editingGroup.CategoryId = SelectedCategory?.Id ?? string.Empty;
|
||||
_editingGroup.SubToolIds = selectedIds;
|
||||
@@ -106,6 +126,7 @@ public partial class GroupEditViewModel : ObservableObject
|
||||
_dataService.Config.Tools.Add(new ToolItem
|
||||
{
|
||||
Name = Name.Trim(),
|
||||
IconCode = SelectedIcon?.Icon.ToString() ?? string.Empty,
|
||||
HotKey = HotKey.Trim(),
|
||||
CategoryId = SelectedCategory?.Id ?? string.Empty,
|
||||
IsGroup = true,
|
||||
@@ -130,6 +151,31 @@ public partial class GroupEditViewModel : ObservableObject
|
||||
{
|
||||
CloseAction?.Invoke(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取需要排除的工具 ID 集合(自身 + 所有祖先组合),防止循环引用
|
||||
/// </summary>
|
||||
private System.Collections.Generic.HashSet<string> GetAncestorIds()
|
||||
{
|
||||
var ancestors = new System.Collections.Generic.HashSet<string>();
|
||||
if (_editingGroup != null)
|
||||
{
|
||||
ancestors.Add(_editingGroup.Id);
|
||||
CollectAncestors(_editingGroup.Id, ancestors);
|
||||
}
|
||||
return ancestors;
|
||||
}
|
||||
|
||||
private void CollectAncestors(string groupId, System.Collections.Generic.HashSet<string> ancestors)
|
||||
{
|
||||
foreach (var tool in _dataService.Config.Tools.Where(t => t.IsGroup))
|
||||
{
|
||||
if (tool.SubToolIds.Contains(groupId) && ancestors.Add(tool.Id))
|
||||
{
|
||||
CollectAncestors(tool.Id, ancestors);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user