- ToolItem 模型新增 AutoRunOnStart 属性,持久化到配置文件 - App.OnStartup 启动后自动执行标记为自启动的工具/组合(间隔500ms) - 工具编辑窗口和组合编辑窗口新增「随软件启动自动运行」CheckBox - 主界面工具卡片左上角 Rocket 角标标识已启用自启的项目
218 lines
7.0 KiB
C#
218 lines
7.0 KiB
C#
using System.Collections.ObjectModel;
|
||
using System.ComponentModel;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using CommunityToolkit.Mvvm.ComponentModel;
|
||
using CommunityToolkit.Mvvm.Input;
|
||
using Microsoft.Win32;
|
||
using PersonalToolBox.Helpers;
|
||
using PersonalToolBox.Models;
|
||
using PersonalToolBox.Services;
|
||
|
||
namespace PersonalToolBox.ViewModels;
|
||
|
||
/// <summary>
|
||
/// 工具编辑窗口 ViewModel,管理添加/编辑工具的交互逻辑
|
||
/// </summary>
|
||
public partial class ToolEditViewModel : ObservableObject
|
||
{
|
||
private readonly IDataService _dataService;
|
||
private readonly ILogService _logService;
|
||
private readonly ToolItem? _editingTool;
|
||
|
||
// ───────────────────────────── 可观察属性 ─────────────────────────────
|
||
|
||
[ObservableProperty]
|
||
private string _windowTitle = "添加工具";
|
||
|
||
[ObservableProperty]
|
||
private string _name = string.Empty;
|
||
|
||
[ObservableProperty]
|
||
private string _executablePath = string.Empty;
|
||
|
||
[ObservableProperty]
|
||
private string _arguments = string.Empty;
|
||
|
||
[ObservableProperty]
|
||
private string _hotKey = string.Empty;
|
||
|
||
[ObservableProperty]
|
||
private Category? _selectedCategory;
|
||
|
||
[ObservableProperty]
|
||
private IconProvider.IconOption? _selectedIcon;
|
||
|
||
[ObservableProperty]
|
||
private bool _autoRunOnStart;
|
||
|
||
/// <summary>
|
||
/// 分类下拉列表
|
||
/// </summary>
|
||
public ObservableCollection<Category> Categories { get; } = new();
|
||
|
||
/// <summary>
|
||
/// 按分类分组的可用图标列表
|
||
/// </summary>
|
||
public ListCollectionView AvailableIconsView { get; }
|
||
|
||
/// <summary>
|
||
/// 窗口关闭回调(由 View 层设置)
|
||
/// </summary>
|
||
public Action<bool?>? CloseAction { get; set; }
|
||
|
||
/// <summary>
|
||
/// 是否保存成功(供调用方判断是否需要刷新)
|
||
/// </summary>
|
||
public bool Saved { get; private set; }
|
||
|
||
// ───────────────────────────── 构造函数 ─────────────────────────────
|
||
|
||
public ToolEditViewModel(IDataService dataService, ILogService logService, ToolItem? toolToEdit = null)
|
||
{
|
||
_dataService = dataService;
|
||
_logService = logService;
|
||
_editingTool = toolToEdit;
|
||
|
||
AvailableIconsView = new ListCollectionView(IconProvider.AvailableIcons);
|
||
AvailableIconsView.GroupDescriptions.Add(new PropertyGroupDescription("Category"));
|
||
|
||
// 加载分类列表
|
||
foreach (var cat in _dataService.Config.Categories)
|
||
Categories.Add(cat);
|
||
|
||
// 编辑模式
|
||
if (toolToEdit != null)
|
||
{
|
||
WindowTitle = "编辑工具";
|
||
Name = toolToEdit.Name;
|
||
ExecutablePath = toolToEdit.ExecutablePath;
|
||
Arguments = toolToEdit.Arguments;
|
||
HotKey = toolToEdit.HotKey;
|
||
SelectedCategory = Categories.FirstOrDefault(c => c.Id == toolToEdit.CategoryId);
|
||
AutoRunOnStart = toolToEdit.AutoRunOnStart;
|
||
|
||
if (!string.IsNullOrEmpty(toolToEdit.IconCode))
|
||
{
|
||
SelectedIcon = IconProvider.AvailableIcons.FirstOrDefault(i => i.Icon.ToString() == toolToEdit.IconCode);
|
||
}
|
||
}
|
||
}
|
||
|
||
// ───────────────────────────── 命令 ─────────────────────────────
|
||
|
||
/// <summary>
|
||
/// 浏览本地文件
|
||
/// </summary>
|
||
[RelayCommand]
|
||
private void BrowseFile()
|
||
{
|
||
var dialog = new OpenFileDialog
|
||
{
|
||
Title = "选择可执行文件或脚本",
|
||
Filter = "所有文件|*.*|可执行文件|*.exe|脚本文件|*.bat;*.cmd;*.ps1;*.py|快捷方式|*.lnk"
|
||
};
|
||
|
||
if (dialog.ShowDialog() == true)
|
||
{
|
||
ExecutablePath = dialog.FileName;
|
||
if (string.IsNullOrWhiteSpace(Name))
|
||
{
|
||
Name = Path.GetFileNameWithoutExtension(dialog.FileName);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存工具
|
||
/// </summary>
|
||
[RelayCommand]
|
||
private void Save()
|
||
{
|
||
try
|
||
{
|
||
if (string.IsNullOrWhiteSpace(Name))
|
||
{
|
||
_logService.Warning("工具名称不能为空");
|
||
return;
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(ExecutablePath))
|
||
{
|
||
_logService.Warning($"工具 \"{Name}\" 路径不能为空");
|
||
return;
|
||
}
|
||
|
||
// 编辑模式:更新已有工具
|
||
if (_editingTool != null)
|
||
{
|
||
_editingTool.Name = Name.Trim();
|
||
_editingTool.IconCode = SelectedIcon?.Icon.ToString() ?? string.Empty;
|
||
_editingTool.ExecutablePath = ExecutablePath.Trim();
|
||
_editingTool.Arguments = Arguments.Trim();
|
||
_editingTool.HotKey = HotKey.Trim();
|
||
_editingTool.CategoryId = SelectedCategory?.Id ?? string.Empty;
|
||
_editingTool.IsValid = IsExecutablePathValid(ExecutablePath.Trim());
|
||
_editingTool.AutoRunOnStart = AutoRunOnStart;
|
||
|
||
_logService.Info($"已更新工具: {Name.Trim()}");
|
||
}
|
||
// 添加模式:创建新工具
|
||
else
|
||
{
|
||
var newTool = new ToolItem
|
||
{
|
||
Name = Name.Trim(),
|
||
IconCode = SelectedIcon?.Icon.ToString() ?? string.Empty,
|
||
ExecutablePath = ExecutablePath.Trim(),
|
||
Arguments = Arguments.Trim(),
|
||
HotKey = HotKey.Trim(),
|
||
CategoryId = SelectedCategory?.Id ?? string.Empty,
|
||
IsValid = IsExecutablePathValid(ExecutablePath.Trim()),
|
||
AutoRunOnStart = AutoRunOnStart
|
||
};
|
||
|
||
_dataService.Config.Tools.Add(newTool);
|
||
_logService.Info($"已添加工具: {Name.Trim()}");
|
||
}
|
||
|
||
_dataService.Save();
|
||
Saved = true;
|
||
CloseAction?.Invoke(true);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logService.Error($"保存工具失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 取消编辑
|
||
/// </summary>
|
||
[RelayCommand]
|
||
private void Cancel()
|
||
{
|
||
CloseAction?.Invoke(false);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证可执行路径是否有效
|
||
/// </summary>
|
||
private static bool IsExecutablePathValid(string path)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(path))
|
||
return false;
|
||
|
||
// URL 格式(https://...)直接通过
|
||
if (Uri.TryCreate(path, UriKind.Absolute, out var uri) &&
|
||
(uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps))
|
||
return true;
|
||
|
||
// 纯文件名(无路径分隔符)可能位于系统 PATH 中
|
||
if (!path.Contains('\\') && !path.Contains('/'))
|
||
return true;
|
||
|
||
return File.Exists(path);
|
||
}
|
||
}
|