feat: 统一项目命名并补充路径失效报告
将内部项目目录、命名空间、配置目录、自启注册表值和设计/开发文档统一为 PersonalToolbox。 扩展路径校验服务,输出失效工具、字段、原因和路径,并在启动日志、设置页路径检查与导入配置流程中展示明细报告。 验证:dotnet build PersonalToolbox.sln
This commit is contained in:
55
src/PersonalToolbox/ViewModels/CombinationMemberViewModel.cs
Normal file
55
src/PersonalToolbox/ViewModels/CombinationMemberViewModel.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using PersonalToolbox.Models;
|
||||
|
||||
namespace PersonalToolbox.ViewModels;
|
||||
|
||||
public sealed class CombinationMemberViewModel : ObservableObject
|
||||
{
|
||||
private readonly Func<string, ToolItem?> _toolResolver;
|
||||
private bool _enabled;
|
||||
private int _intervalAfterMs;
|
||||
|
||||
public CombinationMemberViewModel(CombinationMember member, Func<string, ToolItem?> toolResolver)
|
||||
{
|
||||
Member = member;
|
||||
_toolResolver = toolResolver;
|
||||
_enabled = member.Enabled;
|
||||
_intervalAfterMs = member.IntervalAfterMs;
|
||||
}
|
||||
|
||||
public CombinationMember Member { get; }
|
||||
public string ToolId => Member.ToolId;
|
||||
public string ToolName => _toolResolver(Member.ToolId)?.Name ?? "引用不存在";
|
||||
public string ToolTypeLabel => _toolResolver(Member.ToolId)?.Type switch
|
||||
{
|
||||
ToolType.System => "系统",
|
||||
ToolType.Local => "本地",
|
||||
ToolType.Url => "网址",
|
||||
ToolType.Combination => "组合",
|
||||
_ => "未知"
|
||||
};
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _enabled, value))
|
||||
{
|
||||
Member.Enabled = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int IntervalAfterMs
|
||||
{
|
||||
get => _intervalAfterMs;
|
||||
set
|
||||
{
|
||||
var normalized = Math.Max(0, value);
|
||||
if (SetProperty(ref _intervalAfterMs, normalized))
|
||||
{
|
||||
Member.IntervalAfterMs = normalized;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
568
src/PersonalToolbox/ViewModels/MainWindowViewModel.cs
Normal file
568
src/PersonalToolbox/ViewModels/MainWindowViewModel.cs
Normal file
@@ -0,0 +1,568 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows;
|
||||
using PersonalToolbox.Commands;
|
||||
using PersonalToolbox.Models;
|
||||
using PersonalToolbox.Services;
|
||||
using PersonalToolbox.Views;
|
||||
using Clipboard = System.Windows.Clipboard;
|
||||
using MessageBox = System.Windows.MessageBox;
|
||||
|
||||
namespace PersonalToolbox.ViewModels;
|
||||
|
||||
public sealed class MainWindowViewModel : ObservableObject
|
||||
{
|
||||
private readonly ConfigurationService _configurationService;
|
||||
private readonly ToolLaunchService _toolLaunchService;
|
||||
private readonly AutoRunService _autoRunService;
|
||||
private readonly PathValidationService _pathValidationService;
|
||||
private readonly StartupService _startupService;
|
||||
private ToolboxData _data = new();
|
||||
private CategoryItem? _selectedCategory;
|
||||
private ToolCardViewModel? _selectedTool;
|
||||
private string _searchText = "";
|
||||
private bool _isLogPanelExpanded = true;
|
||||
|
||||
public MainWindowViewModel()
|
||||
{
|
||||
_configurationService = new ConfigurationService();
|
||||
_toolLaunchService = new ToolLaunchService();
|
||||
_autoRunService = new AutoRunService(_toolLaunchService);
|
||||
_pathValidationService = new PathValidationService();
|
||||
_startupService = new StartupService();
|
||||
|
||||
AddLocalToolCommand = new RelayCommand(AddLocalTool);
|
||||
AddUrlToolCommand = new RelayCommand(AddUrlTool);
|
||||
AddCombinationCommand = new RelayCommand(AddCombination);
|
||||
LaunchSelectedCommand = new AsyncRelayCommand(LaunchSelectedAsync, () => SelectedTool is not null);
|
||||
EditSelectedCommand = new RelayCommand(EditSelectedTool, () => SelectedTool is not null);
|
||||
DeleteSelectedCommand = new RelayCommand(DeleteSelectedTool, () => SelectedTool is not null);
|
||||
OpenSettingsCommand = new RelayCommand(OpenSettings);
|
||||
AddCategoryCommand = new RelayCommand(AddCategory);
|
||||
RenameCategoryCommand = new RelayCommand(RenameCategory, () => SelectedCategory is not null);
|
||||
DeleteCategoryCommand = new RelayCommand(DeleteCategory, () => SelectedCategory is not null && SelectedCategory.Id != SystemToolService.UncategorizedCategoryId);
|
||||
ClearLogsCommand = new RelayCommand(() => Logs.Clear());
|
||||
CopyLogsCommand = new RelayCommand(CopyLogs, () => Logs.Count > 0);
|
||||
ToggleLogPanelCommand = new RelayCommand(() => IsLogPanelExpanded = !IsLogPanelExpanded);
|
||||
}
|
||||
|
||||
public event EventHandler? HotkeysRefreshRequested;
|
||||
|
||||
public Window? Owner { get; set; }
|
||||
|
||||
public ObservableCollection<CategoryItem> Categories { get; } = [];
|
||||
public ObservableCollection<ToolCardViewModel> Tools { get; } = [];
|
||||
public ObservableCollection<LogMessage> Logs { get; } = [];
|
||||
|
||||
public RelayCommand AddLocalToolCommand { get; }
|
||||
public RelayCommand AddUrlToolCommand { get; }
|
||||
public RelayCommand AddCombinationCommand { get; }
|
||||
public AsyncRelayCommand LaunchSelectedCommand { get; }
|
||||
public RelayCommand EditSelectedCommand { get; }
|
||||
public RelayCommand DeleteSelectedCommand { get; }
|
||||
public RelayCommand OpenSettingsCommand { get; }
|
||||
public RelayCommand AddCategoryCommand { get; }
|
||||
public RelayCommand RenameCategoryCommand { get; }
|
||||
public RelayCommand DeleteCategoryCommand { get; }
|
||||
public RelayCommand ClearLogsCommand { get; }
|
||||
public RelayCommand CopyLogsCommand { get; }
|
||||
public RelayCommand ToggleLogPanelCommand { get; }
|
||||
|
||||
public ToolboxData Data => _data;
|
||||
public ConfigurationService ConfigurationService => _configurationService;
|
||||
public StartupService StartupService => _startupService;
|
||||
|
||||
public CategoryItem? SelectedCategory
|
||||
{
|
||||
get => _selectedCategory;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedCategory, value))
|
||||
{
|
||||
RefreshTools();
|
||||
RaiseSelectionCommandState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ToolCardViewModel? SelectedTool
|
||||
{
|
||||
get => _selectedTool;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedTool, value))
|
||||
{
|
||||
RaiseSelectionCommandState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string SearchText
|
||||
{
|
||||
get => _searchText;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _searchText, value))
|
||||
{
|
||||
RefreshTools();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsLogPanelExpanded
|
||||
{
|
||||
get => _isLogPanelExpanded;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _isLogPanelExpanded, value))
|
||||
{
|
||||
_data.Settings.LogPanelExpanded = value;
|
||||
_ = SaveAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task InitializeAsync(Window owner)
|
||||
{
|
||||
Owner = owner;
|
||||
_data = await _configurationService.LoadAsync();
|
||||
_isLogPanelExpanded = _data.Settings.LogPanelExpanded;
|
||||
OnPropertyChanged(nameof(IsLogPanelExpanded));
|
||||
|
||||
var pathReport = _pathValidationService.ValidateTools(_data.Tools);
|
||||
RefreshCategories();
|
||||
RefreshTools();
|
||||
|
||||
AddLog(LogLevel.Info, $"配置目录:{_configurationService.ConfigDirectory}");
|
||||
AddLog(LogLevel.Success, "个人工具箱已启动。");
|
||||
if (pathReport.HasIssues)
|
||||
{
|
||||
LogPathValidationReport(pathReport);
|
||||
}
|
||||
|
||||
await SaveAsync();
|
||||
}
|
||||
|
||||
public async Task RunAutoRunAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
_configurationService.MergeAutoRunEntries(_data);
|
||||
await SaveAsync(cancellationToken);
|
||||
await _autoRunService.ExecuteAsync(_data, AddLog, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task LaunchToolByIdAsync(string toolId)
|
||||
{
|
||||
var tool = _data.Tools.FirstOrDefault(item => item.Id == toolId && !item.IsDeleted);
|
||||
if (tool is null)
|
||||
{
|
||||
AddLog(LogLevel.Warning, $"快捷键目标不存在:{toolId}");
|
||||
return;
|
||||
}
|
||||
|
||||
await _toolLaunchService.LaunchAsync(tool, _data.Tools, AddLog);
|
||||
}
|
||||
|
||||
public async Task SaveAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
_configurationService.MergeAutoRunEntries(_data);
|
||||
await _configurationService.SaveAsync(_data, cancellationToken);
|
||||
}
|
||||
|
||||
public void RefreshHotkeys()
|
||||
{
|
||||
HotkeysRefreshRequested?.Invoke(this, EventArgs.Empty);
|
||||
RefreshTools();
|
||||
}
|
||||
|
||||
public void SetGlobalHotkeysEnabled(bool enabled)
|
||||
{
|
||||
_data.Settings.GlobalHotkeysEnabled = enabled;
|
||||
AddLog(LogLevel.Info, enabled ? "全局快捷键已开启。" : "全局快捷键已关闭。");
|
||||
_ = SaveAsync();
|
||||
RefreshHotkeys();
|
||||
}
|
||||
|
||||
public void AddLog(LogLevel level, string message)
|
||||
{
|
||||
System.Windows.Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
Logs.Add(new LogMessage
|
||||
{
|
||||
Level = level,
|
||||
Message = message
|
||||
});
|
||||
|
||||
while (Logs.Count > 500)
|
||||
{
|
||||
Logs.RemoveAt(0);
|
||||
}
|
||||
|
||||
CopyLogsCommand.RaiseCanExecuteChanged();
|
||||
});
|
||||
}
|
||||
|
||||
private void AddLog(LogMessage message)
|
||||
{
|
||||
AddLog(message.Level, message.Message);
|
||||
}
|
||||
|
||||
private async Task LaunchSelectedAsync()
|
||||
{
|
||||
if (SelectedTool is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _toolLaunchService.LaunchAsync(SelectedTool.Tool, _data.Tools, AddLog);
|
||||
RefreshTools();
|
||||
}
|
||||
|
||||
private void AddLocalTool()
|
||||
{
|
||||
var tool = CreateBaseTool(ToolType.Local);
|
||||
tool.Name = "新的本地工具";
|
||||
tool.IconKey = "folder";
|
||||
|
||||
var edited = ToolEditorWindow.Edit(tool, Categories, Owner);
|
||||
if (edited is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
edited.SortOrder = NextToolSortOrder();
|
||||
_data.Tools.Add(edited);
|
||||
_ = _pathValidationService.ValidateTools(_data.Tools);
|
||||
RefreshTools();
|
||||
AddLog(LogLevel.Success, $"已添加本地工具:{edited.Name}");
|
||||
_ = SaveAsync();
|
||||
RefreshHotkeys();
|
||||
}
|
||||
|
||||
private void AddUrlTool()
|
||||
{
|
||||
var tool = CreateBaseTool(ToolType.Url);
|
||||
tool.Name = "新的网址";
|
||||
tool.IconKey = "link";
|
||||
|
||||
var edited = ToolEditorWindow.Edit(tool, Categories, Owner);
|
||||
if (edited is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
edited.SortOrder = NextToolSortOrder();
|
||||
_data.Tools.Add(edited);
|
||||
RefreshTools();
|
||||
AddLog(LogLevel.Success, $"已添加网址工具:{edited.Name}");
|
||||
_ = SaveAsync();
|
||||
RefreshHotkeys();
|
||||
}
|
||||
|
||||
private void AddCombination()
|
||||
{
|
||||
var tool = CreateBaseTool(ToolType.Combination);
|
||||
tool.Name = "新的组合";
|
||||
tool.IconKey = "combination";
|
||||
tool.Combination = new CombinationConfig();
|
||||
|
||||
var edited = CombinationEditorWindow.Edit(tool, Categories, _data.Tools, _toolLaunchService, Owner);
|
||||
if (edited is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
edited.SortOrder = NextToolSortOrder();
|
||||
_data.Tools.Add(edited);
|
||||
RefreshTools();
|
||||
AddLog(LogLevel.Success, $"已添加组合:{edited.Name}");
|
||||
_ = SaveAsync();
|
||||
RefreshHotkeys();
|
||||
}
|
||||
|
||||
private void EditSelectedTool()
|
||||
{
|
||||
if (SelectedTool is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var original = SelectedTool.Tool;
|
||||
var candidate = original.Clone();
|
||||
ToolItem? edited = original.Type == ToolType.Combination
|
||||
? CombinationEditorWindow.Edit(candidate, Categories, _data.Tools, _toolLaunchService, Owner)
|
||||
: ToolEditorWindow.Edit(candidate, Categories, Owner);
|
||||
|
||||
if (edited is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CopyToolValues(edited, original);
|
||||
_ = _pathValidationService.ValidateTools(_data.Tools);
|
||||
RefreshTools();
|
||||
AddLog(LogLevel.Success, $"已保存工具:{original.Name}");
|
||||
_ = SaveAsync();
|
||||
RefreshHotkeys();
|
||||
}
|
||||
|
||||
private void DeleteSelectedTool()
|
||||
{
|
||||
if (SelectedTool is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var tool = SelectedTool.Tool;
|
||||
var confirmed = MessageBox.Show(
|
||||
Owner,
|
||||
$"确定删除“{tool.Name}”吗?组合中对它的引用也会失效。",
|
||||
"删除工具",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Warning) == MessageBoxResult.Yes;
|
||||
|
||||
if (!confirmed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tool.IsDeleted = true;
|
||||
foreach (var combination in _data.Tools.Where(item => item.Type == ToolType.Combination && item.Combination is not null))
|
||||
{
|
||||
combination.Combination!.Members.RemoveAll(member => member.ToolId == tool.Id);
|
||||
}
|
||||
|
||||
RefreshTools();
|
||||
AddLog(LogLevel.Warning, $"已删除工具:{tool.Name}");
|
||||
_ = SaveAsync();
|
||||
RefreshHotkeys();
|
||||
}
|
||||
|
||||
private void OpenSettings()
|
||||
{
|
||||
var window = new SettingsWindow(_data, _configurationService, _startupService, _pathValidationService, _toolLaunchService)
|
||||
{
|
||||
Owner = Owner
|
||||
};
|
||||
|
||||
if (window.ShowDialog() == true)
|
||||
{
|
||||
_data = window.Data;
|
||||
_ = _pathValidationService.ValidateTools(_data.Tools);
|
||||
RefreshCategories();
|
||||
RefreshTools();
|
||||
AddLog(LogLevel.Success, "设置已保存。");
|
||||
_ = SaveAsync();
|
||||
RefreshHotkeys();
|
||||
}
|
||||
}
|
||||
|
||||
private void AddCategory()
|
||||
{
|
||||
var name = PromptWindow.Prompt("新建分类", "分类名称", "", Owner);
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_data.Categories.Add(new CategoryItem
|
||||
{
|
||||
Name = name.Trim(),
|
||||
IconKey = "category",
|
||||
SortOrder = _data.Categories.Count == 0 ? 0 : _data.Categories.Max(category => category.SortOrder) + 1
|
||||
});
|
||||
|
||||
RefreshCategories();
|
||||
AddLog(LogLevel.Success, $"已新建分类:{name.Trim()}");
|
||||
_ = SaveAsync();
|
||||
}
|
||||
|
||||
private void RenameCategory()
|
||||
{
|
||||
if (SelectedCategory is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var name = PromptWindow.Prompt("重命名分类", "分类名称", SelectedCategory.Name, Owner);
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SelectedCategory.Name = name.Trim();
|
||||
RefreshCategories();
|
||||
RefreshTools();
|
||||
AddLog(LogLevel.Success, $"已重命名分类:{name.Trim()}");
|
||||
_ = SaveAsync();
|
||||
}
|
||||
|
||||
private void DeleteCategory()
|
||||
{
|
||||
if (SelectedCategory is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (SelectedCategory.Id == SystemToolService.UncategorizedCategoryId)
|
||||
{
|
||||
AddLog(LogLevel.Warning, "未分类为固定分类,不能删除。");
|
||||
return;
|
||||
}
|
||||
|
||||
var confirmed = MessageBox.Show(
|
||||
Owner,
|
||||
$"删除分类“{SelectedCategory.Name}”后,其中工具会移动到“未分类”。是否继续?",
|
||||
"删除分类",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Warning) == MessageBoxResult.Yes;
|
||||
|
||||
if (!confirmed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var tool in _data.Tools.Where(tool => tool.CategoryId == SelectedCategory.Id))
|
||||
{
|
||||
tool.CategoryId = SystemToolService.UncategorizedCategoryId;
|
||||
}
|
||||
|
||||
var deletedName = SelectedCategory.Name;
|
||||
_data.Categories.Remove(SelectedCategory);
|
||||
RefreshCategories();
|
||||
SelectedCategory = _data.Categories.FirstOrDefault(category => category.Id == SystemToolService.UncategorizedCategoryId);
|
||||
AddLog(LogLevel.Warning, $"已删除分类:{deletedName}");
|
||||
_ = SaveAsync();
|
||||
}
|
||||
|
||||
private void CopyLogs()
|
||||
{
|
||||
if (Logs.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Clipboard.SetText(string.Join(Environment.NewLine, Logs.Select(log => log.DisplayText)));
|
||||
AddLog(LogLevel.Success, "已复制底部信息。");
|
||||
}
|
||||
|
||||
private void LogPathValidationReport(PathValidationReport report)
|
||||
{
|
||||
AddLog(LogLevel.Warning, $"路径检查完成:发现 {report.InvalidToolCount} 个失效工具,{report.Issues.Count} 个问题。");
|
||||
|
||||
foreach (var issue in report.Issues.Take(8))
|
||||
{
|
||||
var pathText = string.IsNullOrWhiteSpace(issue.Path) ? "" : $",{issue.Path}";
|
||||
AddLog(LogLevel.Warning, $"路径失效:{issue.ToolName},{issue.FieldName} {issue.Reason}{pathText}");
|
||||
}
|
||||
|
||||
if (report.Issues.Count > 8)
|
||||
{
|
||||
AddLog(LogLevel.Warning, $"还有 {report.Issues.Count - 8} 个路径问题未显示,可在设置中查看完整报告。");
|
||||
}
|
||||
}
|
||||
|
||||
private ToolItem CreateBaseTool(ToolType type)
|
||||
{
|
||||
return new ToolItem
|
||||
{
|
||||
Id = Guid.NewGuid().ToString("N"),
|
||||
Type = type,
|
||||
CategoryId = SelectedCategory?.Id ?? Categories.FirstOrDefault()?.Id ?? SystemToolService.UncategorizedCategoryId,
|
||||
SortOrder = NextToolSortOrder()
|
||||
};
|
||||
}
|
||||
|
||||
private int NextToolSortOrder()
|
||||
{
|
||||
return _data.Tools.Count == 0 ? 0 : _data.Tools.Max(tool => tool.SortOrder) + 1;
|
||||
}
|
||||
|
||||
private void RefreshCategories()
|
||||
{
|
||||
Categories.Clear();
|
||||
foreach (var category in _data.Categories.OrderBy(category => category.SortOrder).ThenBy(category => category.Name))
|
||||
{
|
||||
Categories.Add(category);
|
||||
}
|
||||
|
||||
SelectedCategory ??= Categories.FirstOrDefault();
|
||||
if (SelectedCategory is not null && Categories.All(category => category.Id != SelectedCategory.Id))
|
||||
{
|
||||
SelectedCategory = Categories.FirstOrDefault();
|
||||
}
|
||||
|
||||
DeleteCategoryCommand.RaiseCanExecuteChanged();
|
||||
RenameCategoryCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
private void RefreshTools()
|
||||
{
|
||||
var selectedId = SelectedTool?.Id;
|
||||
Tools.Clear();
|
||||
|
||||
var query = _data.Tools.Where(tool => !tool.IsDeleted);
|
||||
if (!string.IsNullOrWhiteSpace(SearchText))
|
||||
{
|
||||
var keyword = SearchText.Trim();
|
||||
query = query.Where(tool => MatchesSearch(tool, keyword));
|
||||
}
|
||||
else if (SelectedCategory is not null)
|
||||
{
|
||||
query = query.Where(tool => tool.CategoryId == SelectedCategory.Id);
|
||||
}
|
||||
|
||||
foreach (var tool in query.OrderBy(tool => tool.SortOrder).ThenBy(tool => tool.Name))
|
||||
{
|
||||
Tools.Add(new ToolCardViewModel(tool, ResolveCategoryName));
|
||||
}
|
||||
|
||||
SelectedTool = Tools.FirstOrDefault(tool => tool.Id == selectedId);
|
||||
RaiseSelectionCommandState();
|
||||
}
|
||||
|
||||
private bool MatchesSearch(ToolItem tool, string keyword)
|
||||
{
|
||||
return Contains(tool.Name, keyword)
|
||||
|| Contains(tool.Description, keyword)
|
||||
|| Contains(tool.LaunchTarget, keyword)
|
||||
|| Contains(tool.Url, keyword)
|
||||
|| Contains(ResolveCategoryName(tool.CategoryId), keyword);
|
||||
}
|
||||
|
||||
private static bool Contains(string? source, string keyword)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(source)
|
||||
&& source.Contains(keyword, StringComparison.CurrentCultureIgnoreCase);
|
||||
}
|
||||
|
||||
private string ResolveCategoryName(string categoryId)
|
||||
{
|
||||
return _data.Categories.FirstOrDefault(category => category.Id == categoryId)?.Name ?? "未分类";
|
||||
}
|
||||
|
||||
private void RaiseSelectionCommandState()
|
||||
{
|
||||
LaunchSelectedCommand.RaiseCanExecuteChanged();
|
||||
EditSelectedCommand.RaiseCanExecuteChanged();
|
||||
DeleteSelectedCommand.RaiseCanExecuteChanged();
|
||||
RenameCategoryCommand.RaiseCanExecuteChanged();
|
||||
DeleteCategoryCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
private static void CopyToolValues(ToolItem source, ToolItem target)
|
||||
{
|
||||
target.Name = source.Name;
|
||||
target.Description = source.Description;
|
||||
target.Type = source.Type;
|
||||
target.CategoryId = source.CategoryId;
|
||||
target.IconKey = source.IconKey;
|
||||
target.LaunchTarget = source.LaunchTarget;
|
||||
target.Url = source.Url;
|
||||
target.Arguments = source.Arguments;
|
||||
target.WorkingDirectory = source.WorkingDirectory;
|
||||
target.SystemToolKey = source.SystemToolKey;
|
||||
target.Hotkey = source.Hotkey;
|
||||
target.AutoRunEnabled = source.AutoRunEnabled;
|
||||
target.RunAsAdmin = source.RunAsAdmin;
|
||||
target.Combination = source.Combination?.Clone();
|
||||
}
|
||||
}
|
||||
26
src/PersonalToolbox/ViewModels/ObservableObject.cs
Normal file
26
src/PersonalToolbox/ViewModels/ObservableObject.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PersonalToolbox.ViewModels;
|
||||
|
||||
public abstract class ObservableObject : INotifyPropertyChanged
|
||||
{
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(storage, value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
storage = value;
|
||||
OnPropertyChanged(propertyName);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
124
src/PersonalToolbox/ViewModels/ToolCardViewModel.cs
Normal file
124
src/PersonalToolbox/ViewModels/ToolCardViewModel.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using PersonalToolbox.Models;
|
||||
|
||||
namespace PersonalToolbox.ViewModels;
|
||||
|
||||
public sealed class ToolCardViewModel : ObservableObject
|
||||
{
|
||||
private readonly Func<string, string> _categoryNameResolver;
|
||||
|
||||
public ToolCardViewModel(ToolItem tool, Func<string, string> categoryNameResolver)
|
||||
{
|
||||
Tool = tool;
|
||||
_categoryNameResolver = categoryNameResolver;
|
||||
}
|
||||
|
||||
public ToolItem Tool { get; }
|
||||
|
||||
public string Id => Tool.Id;
|
||||
public string Name => Tool.Name;
|
||||
public string Description => string.IsNullOrWhiteSpace(Tool.Description) ? "暂无说明" : Tool.Description;
|
||||
public string CategoryName => _categoryNameResolver(Tool.CategoryId);
|
||||
public string IconText => IconKeyToText(Tool.IconKey, Tool.Type);
|
||||
public string TypeLabel => Tool.Type switch
|
||||
{
|
||||
ToolType.System => "系统",
|
||||
ToolType.Local => "本地",
|
||||
ToolType.Url => "网址",
|
||||
ToolType.Combination => "组合",
|
||||
_ => "工具"
|
||||
};
|
||||
|
||||
public IReadOnlyList<string> StatusBadges
|
||||
{
|
||||
get
|
||||
{
|
||||
var badges = new List<string>();
|
||||
if (Tool.PathInvalid)
|
||||
{
|
||||
badges.Add("路径失效");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(Tool.Hotkey))
|
||||
{
|
||||
badges.Add("快捷键");
|
||||
}
|
||||
|
||||
if (Tool.AutoRunEnabled)
|
||||
{
|
||||
badges.Add("自动运行");
|
||||
}
|
||||
|
||||
if (Tool.RunAsAdmin)
|
||||
{
|
||||
badges.Add("管理员");
|
||||
}
|
||||
|
||||
return badges;
|
||||
}
|
||||
}
|
||||
|
||||
public string DetailText
|
||||
{
|
||||
get
|
||||
{
|
||||
return Tool.Type switch
|
||||
{
|
||||
ToolType.Local => Tool.LaunchTarget ?? "",
|
||||
ToolType.Url => Tool.Url ?? "",
|
||||
ToolType.System => Tool.LaunchTarget ?? "",
|
||||
ToolType.Combination => $"{Tool.Combination?.Members.Count ?? 0} 个成员",
|
||||
_ => ""
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
OnPropertyChanged(nameof(Name));
|
||||
OnPropertyChanged(nameof(Description));
|
||||
OnPropertyChanged(nameof(CategoryName));
|
||||
OnPropertyChanged(nameof(IconText));
|
||||
OnPropertyChanged(nameof(TypeLabel));
|
||||
OnPropertyChanged(nameof(StatusBadges));
|
||||
OnPropertyChanged(nameof(DetailText));
|
||||
}
|
||||
|
||||
private static string IconKeyToText(string iconKey, ToolType type)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(iconKey))
|
||||
{
|
||||
var mapped = iconKey.ToLowerInvariant() switch
|
||||
{
|
||||
"notepad" => "TXT",
|
||||
"calculator" => "123",
|
||||
"taskmgr" => "CPU",
|
||||
"control" => "CTL",
|
||||
"settings" => "SET",
|
||||
"device" => "DEV",
|
||||
"disk" => "DSK",
|
||||
"service" => "SVC",
|
||||
"registry" => "REG",
|
||||
"network" => "NET",
|
||||
"apps" => "APP",
|
||||
"link" => "URL",
|
||||
"folder" => "DIR",
|
||||
"combination" => "COM",
|
||||
_ => ""
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(mapped))
|
||||
{
|
||||
return mapped;
|
||||
}
|
||||
}
|
||||
|
||||
return type switch
|
||||
{
|
||||
ToolType.System => "SYS",
|
||||
ToolType.Local => "LOC",
|
||||
ToolType.Url => "URL",
|
||||
ToolType.Combination => "COM",
|
||||
_ => "BOX"
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user