- 数据模型: 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)
308 lines
10 KiB
C#
308 lines
10 KiB
C#
using System.Collections.ObjectModel;
|
||
using System.Linq;
|
||
using System.Windows;
|
||
using CommunityToolkit.Mvvm.ComponentModel;
|
||
using CommunityToolkit.Mvvm.Input;
|
||
using Microsoft.Extensions.DependencyInjection;
|
||
using PersonalToolBox.Helpers;
|
||
using PersonalToolBox.Models;
|
||
using PersonalToolBox.Services;
|
||
using PersonalToolBox.Views;
|
||
|
||
namespace PersonalToolBox.ViewModels;
|
||
|
||
/// <summary>
|
||
/// 主窗口 ViewModel,管理 UI 状态、数据绑定和用户交互逻辑
|
||
/// </summary>
|
||
public partial class MainViewModel : ObservableObject
|
||
{
|
||
private readonly ILogService _logService;
|
||
private readonly IDataService _dataService;
|
||
private readonly IProcessExecutionService _processService;
|
||
private readonly IServiceProvider _serviceProvider;
|
||
private readonly HotKeyManager _hotKeyManager;
|
||
|
||
/// <summary>
|
||
/// 全部分类的虚拟对象
|
||
/// </summary>
|
||
private static readonly Category AllCategory = new() { Id = "", Name = "全部" };
|
||
|
||
public MainViewModel(
|
||
ILogService logService,
|
||
IDataService dataService,
|
||
IProcessExecutionService processService,
|
||
IServiceProvider serviceProvider,
|
||
HotKeyManager hotKeyManager)
|
||
{
|
||
_logService = logService;
|
||
_dataService = dataService;
|
||
_processService = processService;
|
||
_serviceProvider = serviceProvider;
|
||
_hotKeyManager = hotKeyManager;
|
||
|
||
LoadData();
|
||
}
|
||
|
||
// ───────────────────────────── 可观察属性 ─────────────────────────────
|
||
|
||
[ObservableProperty]
|
||
private string _searchText = string.Empty;
|
||
|
||
[ObservableProperty]
|
||
private Category? _selectedCategory;
|
||
|
||
public ObservableCollection<LogEntry> Logs => _logService.Logs;
|
||
|
||
public ObservableCollection<Category> Categories { get; } = new();
|
||
|
||
public ObservableCollection<ToolItem> Tools { get; } = new();
|
||
|
||
[ObservableProperty]
|
||
private ObservableCollection<ToolItem> _filteredTools = new();
|
||
|
||
[ObservableProperty]
|
||
private string _currentTheme = "Dark";
|
||
|
||
[ObservableProperty]
|
||
private bool _isAutoStart;
|
||
|
||
// ───────────────────────────── 命令 ─────────────────────────────
|
||
|
||
[RelayCommand]
|
||
private void ClearLogs() => _logService.Clear();
|
||
|
||
[RelayCommand]
|
||
private void ToggleTheme()
|
||
{
|
||
CurrentTheme = CurrentTheme == "Dark" ? "Light" : "Dark";
|
||
ThemeHelper.ApplyTheme(CurrentTheme);
|
||
_dataService.Config.Theme = CurrentTheme;
|
||
_dataService.Save();
|
||
_logService.Info($"已切换到{(CurrentTheme == "Dark" ? "暗黑" : "明亮")}主题");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 切换开机自启动
|
||
/// </summary>
|
||
[RelayCommand]
|
||
private void ToggleAutoStart()
|
||
{
|
||
IsAutoStart = !IsAutoStart;
|
||
AutoStartHelper.SetAutoStart(IsAutoStart);
|
||
_dataService.Config.AutoStart = IsAutoStart;
|
||
_dataService.Save();
|
||
_logService.Info(IsAutoStart ? "已启用开机自启动" : "已关闭开机自启动");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 供 MainWindow 外部调用,用于输出提示信息
|
||
/// </summary>
|
||
public void LogServiceMessage(string message)
|
||
{
|
||
_logService.Info(message);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 打开添加工具弹窗
|
||
/// </summary>
|
||
[RelayCommand]
|
||
private void AddTool()
|
||
{
|
||
var vm = _serviceProvider.GetRequiredService<ToolEditViewModel>();
|
||
var window = new ToolEditWindow(vm);
|
||
window.ShowDialog();
|
||
|
||
if (vm.Saved)
|
||
{
|
||
RefreshData();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 打开编辑工具弹窗
|
||
/// </summary>
|
||
[RelayCommand]
|
||
private void EditTool(ToolItem tool)
|
||
{
|
||
if (tool == null) return;
|
||
|
||
// 创建编辑 ViewModel(需要新建实例,DI 容器无法区分参数)
|
||
var dataService = _serviceProvider.GetRequiredService<IDataService>();
|
||
var logService = _serviceProvider.GetRequiredService<ILogService>();
|
||
var editVm = new ToolEditViewModel(dataService, logService, tool);
|
||
var window = new ToolEditWindow(editVm);
|
||
window.ShowDialog();
|
||
|
||
if (editVm.Saved)
|
||
{
|
||
RefreshData();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行工具(双击卡片或右键菜单)
|
||
/// </summary>
|
||
[RelayCommand]
|
||
private async Task ExecuteTool(ToolItem? tool)
|
||
{
|
||
if (tool == null) return;
|
||
await _processService.ExecuteAsync(tool);
|
||
}
|
||
|
||
// ───────────────────────────── 分类管理命令 ─────────────────────────────
|
||
|
||
/// <summary>
|
||
/// 添加分类
|
||
/// </summary>
|
||
[RelayCommand]
|
||
private void AddCategory()
|
||
{
|
||
var vm = _serviceProvider.GetRequiredService<CategoryEditViewModel>();
|
||
var window = new CategoryEditWindow(vm);
|
||
window.ShowDialog();
|
||
|
||
if (vm.Saved) RefreshData();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 编辑分类(右键菜单触发)
|
||
/// </summary>
|
||
[RelayCommand]
|
||
private void EditCategory(Category? category)
|
||
{
|
||
if (category == null || string.IsNullOrEmpty(category.Id)) return;
|
||
|
||
var dataService = _serviceProvider.GetRequiredService<IDataService>();
|
||
var logService = _serviceProvider.GetRequiredService<ILogService>();
|
||
var editVm = new CategoryEditViewModel(dataService, logService, category);
|
||
var window = new CategoryEditWindow(editVm);
|
||
window.ShowDialog();
|
||
|
||
if (editVm.Saved) RefreshData();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除分类,其下所有工具移入"全部"
|
||
/// </summary>
|
||
[RelayCommand]
|
||
private void DeleteCategory(Category? category)
|
||
{
|
||
if (category == null || string.IsNullOrEmpty(category.Id)) return;
|
||
|
||
if (MessageBox.Show(
|
||
$"确定删除分类 \"{category.Name}\" 吗?\n该分类下的所有工具将移入「全部」。",
|
||
"确认删除", MessageBoxButton.YesNo, MessageBoxImage.Warning) != MessageBoxResult.Yes)
|
||
return;
|
||
|
||
// 将属于该分类的工具设为"全部"
|
||
foreach (var tool in _dataService.Config.Tools.Where(t => t.CategoryId == category.Id))
|
||
tool.CategoryId = string.Empty;
|
||
|
||
// 从配置中移除分类
|
||
_dataService.Config.Categories.Remove(category);
|
||
_dataService.Save();
|
||
|
||
_logService.Info($"已删除分类: {category.Name}");
|
||
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>
|
||
/// 从配置重新加载数据并刷新 UI
|
||
/// </summary>
|
||
public void RefreshData()
|
||
{
|
||
var previousCategoryId = SelectedCategory?.Id;
|
||
|
||
Categories.Clear();
|
||
Categories.Add(AllCategory);
|
||
|
||
foreach (var cat in _dataService.Config.Categories)
|
||
Categories.Add(cat);
|
||
|
||
Tools.Clear();
|
||
foreach (var tool in _dataService.Config.Tools)
|
||
Tools.Add(tool);
|
||
|
||
// 恢复之前选中的分类(若仍存在),否则选中"全部"
|
||
SelectedCategory = Categories.FirstOrDefault(c => c.Id == previousCategoryId) ?? AllCategory;
|
||
ApplyFilter();
|
||
|
||
// 工具列表变更后重新注册快捷键
|
||
RegisterAllHotKeys();
|
||
}
|
||
|
||
// ───────────────────────────── 快捷键 ─────────────────────────────
|
||
|
||
/// <summary>
|
||
/// 注册所有工具的全局快捷键(由 MainWindow.OnSourceInitialized 及数据变更后调用)
|
||
/// </summary>
|
||
public void RegisterAllHotKeys()
|
||
{
|
||
_hotKeyManager.RegisterAll(Tools);
|
||
}
|
||
|
||
private void LoadData()
|
||
{
|
||
RefreshData();
|
||
|
||
SelectedCategory = AllCategory;
|
||
CurrentTheme = _dataService.Config.Theme;
|
||
IsAutoStart = _dataService.Config.AutoStart;
|
||
ThemeHelper.ApplyTheme(CurrentTheme);
|
||
}
|
||
|
||
// ───────────────────────────── 过滤逻辑 ─────────────────────────────
|
||
|
||
partial void OnSearchTextChanged(string value) => ApplyFilter();
|
||
|
||
partial void OnSelectedCategoryChanged(Category? value) => ApplyFilter();
|
||
|
||
private void ApplyFilter()
|
||
{
|
||
var filtered = Tools.AsEnumerable();
|
||
|
||
if (SelectedCategory != null && !string.IsNullOrEmpty(SelectedCategory.Id))
|
||
filtered = filtered.Where(t => t.CategoryId == SelectedCategory.Id);
|
||
|
||
if (!string.IsNullOrWhiteSpace(SearchText))
|
||
filtered = filtered.Where(t =>
|
||
t.Name.Contains(SearchText, System.StringComparison.OrdinalIgnoreCase));
|
||
|
||
FilteredTools = new ObservableCollection<ToolItem>(filtered);
|
||
}
|
||
}
|