feat: 统一项目命名并补充路径失效报告
将内部项目目录、命名空间、配置目录、自启注册表值和设计/开发文档统一为 PersonalToolbox。 扩展路径校验服务,输出失效工具、字段、原因和路径,并在启动日志、设置页路径检查与导入配置流程中展示明细报告。 验证:dotnet build PersonalToolbox.sln
This commit is contained in:
@@ -1,11 +1,11 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 17
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.0.31903.59
|
VisualStudioVersion = 17.0.31903.59
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{DF1687D8-B6FA-4DF9-90EC-E749B85024EC}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{DF1687D8-B6FA-4DF9-90EC-E749B85024EC}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ToolboxApp", "src\ToolboxApp\ToolboxApp.csproj", "{9A4ED1BF-510A-4481-AE7B-62490D3D7BBA}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PersonalToolbox", "src\PersonalToolbox\PersonalToolbox.csproj", "{9A4ED1BF-510A-4481-AE7B-62490D3D7BBA}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
|||||||
@@ -2,23 +2,24 @@
|
|||||||
|
|
||||||
## 当前项目状态
|
## 当前项目状态
|
||||||
|
|
||||||
- 项目名称:Personal Toolbox / ToolboxApp
|
- 项目名称:Personal Toolbox / PersonalToolbox
|
||||||
- 技术栈:WPF + .NET 8 + C#
|
- 技术栈:WPF + .NET 8 + C#
|
||||||
- 架构方向:MVVM 分层,UI 层通过 ViewModel 调用服务层
|
- 架构方向:MVVM 分层,UI 层通过 ViewModel 调用服务层
|
||||||
- 当前阶段:MVP 基础版本已搭建,可编译运行
|
- 当前阶段:MVP 基础版本已搭建,可编译运行
|
||||||
- 主要入口:`src/ToolboxApp/MainWindow.xaml`
|
- 主要入口:`src/PersonalToolbox/MainWindow.xaml`
|
||||||
|
- 配置目录:`%AppData%\PersonalToolbox`
|
||||||
|
|
||||||
## 已实现能力
|
## 已实现能力
|
||||||
|
|
||||||
1. 基础工程
|
1. 基础工程
|
||||||
- `PersonalToolbox.sln`
|
- `PersonalToolbox.sln`
|
||||||
- `src/ToolboxApp/ToolboxApp.csproj`
|
- `src/PersonalToolbox/PersonalToolbox.csproj`
|
||||||
- WPF 桌面应用,启用 WinForms 托盘能力
|
- WPF 桌面应用,启用 WinForms 托盘能力
|
||||||
|
- 内部命名空间、项目路径、启动项名称和配置目录已统一为 `PersonalToolbox`
|
||||||
|
|
||||||
2. 数据与配置
|
2. 数据与配置
|
||||||
- 本地 JSON 配置读写
|
- 本地 JSON 配置读写
|
||||||
- 原子写入:先写 `.tmp`,再替换正式文件
|
- 原子写入:先写 `.tmp`,再替换正式文件
|
||||||
- 默认配置目录:`%AppData%\ToolboxApp`
|
|
||||||
- 配置文件:
|
- 配置文件:
|
||||||
- `appsettings.json`
|
- `appsettings.json`
|
||||||
- `categories.json`
|
- `categories.json`
|
||||||
@@ -26,6 +27,7 @@
|
|||||||
- `autorun.json`
|
- `autorun.json`
|
||||||
- `icons/`
|
- `icons/`
|
||||||
- 导入、导出、重置配置基础能力
|
- 导入、导出、重置配置基础能力
|
||||||
|
- 路径失效检查会输出具体工具、字段、原因和路径
|
||||||
|
|
||||||
3. 工具模型
|
3. 工具模型
|
||||||
- 系统工具
|
- 系统工具
|
||||||
@@ -60,7 +62,7 @@
|
|||||||
- 托盘常驻服务
|
- 托盘常驻服务
|
||||||
- 托盘菜单:显示/隐藏主界面、全局快捷键开关、设置、退出
|
- 托盘菜单:显示/隐藏主界面、全局快捷键开关、设置、退出
|
||||||
- 关闭窗口时隐藏到托盘
|
- 关闭窗口时隐藏到托盘
|
||||||
- 当前用户开机自启
|
- 当前用户开机自启,注册表值名为 `PersonalToolbox`
|
||||||
- 全局快捷键注册、注销、内部冲突和注册失败提示
|
- 全局快捷键注册、注销、内部冲突和注册失败提示
|
||||||
|
|
||||||
8. 界面
|
8. 界面
|
||||||
@@ -73,6 +75,15 @@
|
|||||||
- 设置窗口
|
- 设置窗口
|
||||||
- 主要 UI 元素均提供 `ToolTip` 悬浮说明
|
- 主要 UI 元素均提供 `ToolTip` 悬浮说明
|
||||||
|
|
||||||
|
## 最近开发记录
|
||||||
|
|
||||||
|
### 2026-05-27
|
||||||
|
|
||||||
|
- 将项目内部名从旧临时代号统一更改为 `PersonalToolbox`,包括目录、项目文件、命名空间、XAML 类名、配置目录、自启注册表值和导出文件名。
|
||||||
|
- 设计文档中的临时代号和 AppData 路径同步改为 `PersonalToolbox`。
|
||||||
|
- 路径失效检查从单纯数量扩展为明细报告,设置页导入配置后也会提示具体失效工具。
|
||||||
|
- 验证命令:`dotnet build PersonalToolbox.sln`,结果为 0 警告、0 错误。
|
||||||
|
|
||||||
## 运行与验证
|
## 运行与验证
|
||||||
|
|
||||||
构建:
|
构建:
|
||||||
@@ -84,7 +95,7 @@ dotnet build PersonalToolbox.sln
|
|||||||
运行:
|
运行:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
dotnet run --project src\ToolboxApp\ToolboxApp.csproj
|
dotnet run --project src\PersonalToolbox\PersonalToolbox.csproj
|
||||||
```
|
```
|
||||||
|
|
||||||
当前验证结果:
|
当前验证结果:
|
||||||
@@ -98,7 +109,7 @@ dotnet build PersonalToolbox.sln
|
|||||||
## 目录说明
|
## 目录说明
|
||||||
|
|
||||||
```text
|
```text
|
||||||
src/ToolboxApp
|
src/PersonalToolbox
|
||||||
├─ Commands 命令封装
|
├─ Commands 命令封装
|
||||||
├─ Models 工具、分类、组合、配置、日志等模型
|
├─ Models 工具、分类、组合、配置、日志等模型
|
||||||
├─ Services 配置、启动、托盘、自启、快捷键、路径校验等服务
|
├─ Services 配置、启动、托盘、自启、快捷键、路径校验等服务
|
||||||
@@ -116,10 +127,11 @@ src/ToolboxApp
|
|||||||
- 配置写入必须保持原子写入策略,避免中途失败写坏 JSON。
|
- 配置写入必须保持原子写入策略,避免中途失败写坏 JSON。
|
||||||
- 快捷键必须包含至少一个修饰键,不支持纯单键快捷键。
|
- 快捷键必须包含至少一个修饰键,不支持纯单键快捷键。
|
||||||
- 开机自启只写当前用户注册表,不要求管理员权限。
|
- 开机自启只写当前用户注册表,不要求管理员权限。
|
||||||
|
- 本项目尚未投入使用,不需要维护旧临时代号对应配置目录的兼容迁移。
|
||||||
|
|
||||||
## Git 记录约定
|
## Git 记录约定
|
||||||
|
|
||||||
提交信息使用 Conventional Commits 格式,并使用中文说明:
|
提交信息使用 Conventional Commits 格式,并使用中文说明标题和正文:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
feat: 添加某项用户能力
|
feat: 添加某项用户能力
|
||||||
@@ -142,6 +154,5 @@ feat: 添加某项用户能力
|
|||||||
2. 更完整的图标选择器和分类图标编辑。
|
2. 更完整的图标选择器和分类图标编辑。
|
||||||
3. 更细的快捷键录入控件,减少手写格式错误。
|
3. 更细的快捷键录入控件,减少手写格式错误。
|
||||||
4. 拖拽调整卡片和分类顺序。
|
4. 拖拽调整卡片和分类顺序。
|
||||||
5. 导入配置后的更详细路径失效报告。
|
5. UI 视觉打磨和深浅色主题。
|
||||||
6. UI 视觉打磨和深浅色主题。
|
6. 为组合校验、快捷键解析和配置读写增加单元测试。
|
||||||
7. 为组合校验、快捷键解析和配置读写增加单元测试。
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<Application x:Class="ToolboxApp.App"
|
<Application x:Class="PersonalToolbox.App"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
StartupUri="MainWindow.xaml">
|
StartupUri="MainWindow.xaml">
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
using System.Configuration;
|
using System.Configuration;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
|
||||||
namespace ToolboxApp;
|
namespace PersonalToolbox;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interaction logic for App.xaml
|
/// Interaction logic for App.xaml
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
|
||||||
namespace ToolboxApp.Commands;
|
namespace PersonalToolbox.Commands;
|
||||||
|
|
||||||
public sealed class RelayCommand : ICommand
|
public sealed class RelayCommand : ICommand
|
||||||
{
|
{
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
<Window x:Class="ToolboxApp.MainWindow"
|
<Window x:Class="PersonalToolbox.MainWindow"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:vm="clr-namespace:ToolboxApp.ViewModels"
|
xmlns:vm="clr-namespace:PersonalToolbox.ViewModels"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Title="个人工具箱"
|
Title="个人工具箱"
|
||||||
Height="720"
|
Height="720"
|
||||||
@@ -3,12 +3,12 @@ using System.Windows;
|
|||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using ToolboxApp.Models;
|
using PersonalToolbox.Models;
|
||||||
using ToolboxApp.Services;
|
using PersonalToolbox.Services;
|
||||||
using ToolboxApp.ViewModels;
|
using PersonalToolbox.ViewModels;
|
||||||
using MessageBox = System.Windows.MessageBox;
|
using MessageBox = System.Windows.MessageBox;
|
||||||
|
|
||||||
namespace ToolboxApp;
|
namespace PersonalToolbox;
|
||||||
|
|
||||||
public partial class MainWindow : Window
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace ToolboxApp.Models;
|
namespace PersonalToolbox.Models;
|
||||||
|
|
||||||
public enum ToolType
|
public enum ToolType
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using ToolboxApp.Models;
|
using PersonalToolbox.Models;
|
||||||
|
|
||||||
namespace ToolboxApp.Services;
|
namespace PersonalToolbox.Services;
|
||||||
|
|
||||||
public sealed class AutoRunService
|
public sealed class AutoRunService
|
||||||
{
|
{
|
||||||
@@ -2,9 +2,9 @@ using System.Diagnostics;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using ToolboxApp.Models;
|
using PersonalToolbox.Models;
|
||||||
|
|
||||||
namespace ToolboxApp.Services;
|
namespace PersonalToolbox.Services;
|
||||||
|
|
||||||
public sealed class ConfigurationService
|
public sealed class ConfigurationService
|
||||||
{
|
{
|
||||||
@@ -28,7 +28,7 @@ public sealed class ConfigurationService
|
|||||||
public ConfigurationService()
|
public ConfigurationService()
|
||||||
{
|
{
|
||||||
var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||||
ConfigDirectory = Path.Combine(appData, "ToolboxApp");
|
ConfigDirectory = Path.Combine(appData, "PersonalToolbox");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ToolboxData> LoadAsync(CancellationToken cancellationToken = default)
|
public async Task<ToolboxData> LoadAsync(CancellationToken cancellationToken = default)
|
||||||
@@ -112,7 +112,7 @@ public sealed class ConfigurationService
|
|||||||
var tempDirectory = "";
|
var tempDirectory = "";
|
||||||
if (File.Exists(sourcePath) && Path.GetExtension(sourcePath).Equals(".zip", StringComparison.OrdinalIgnoreCase))
|
if (File.Exists(sourcePath) && Path.GetExtension(sourcePath).Equals(".zip", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
tempDirectory = Path.Combine(Path.GetTempPath(), "ToolboxAppImport_" + Guid.NewGuid().ToString("N"));
|
tempDirectory = Path.Combine(Path.GetTempPath(), "PersonalToolboxImport_" + Guid.NewGuid().ToString("N"));
|
||||||
ZipFile.ExtractToDirectory(sourcePath, tempDirectory);
|
ZipFile.ExtractToDirectory(sourcePath, tempDirectory);
|
||||||
importDirectory = tempDirectory;
|
importDirectory = tempDirectory;
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Interop;
|
using System.Windows.Interop;
|
||||||
using ToolboxApp.Models;
|
using PersonalToolbox.Models;
|
||||||
|
|
||||||
namespace ToolboxApp.Services;
|
namespace PersonalToolbox.Services;
|
||||||
|
|
||||||
public sealed class HotkeyService : IDisposable
|
public sealed class HotkeyService : IDisposable
|
||||||
{
|
{
|
||||||
126
src/PersonalToolbox/Services/PathValidationService.cs
Normal file
126
src/PersonalToolbox/Services/PathValidationService.cs
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using PersonalToolbox.Models;
|
||||||
|
|
||||||
|
namespace PersonalToolbox.Services;
|
||||||
|
|
||||||
|
public sealed class PathValidationService
|
||||||
|
{
|
||||||
|
public PathValidationReport ValidateTools(IEnumerable<ToolItem> tools)
|
||||||
|
{
|
||||||
|
var issues = new List<PathValidationIssue>();
|
||||||
|
foreach (var tool in tools)
|
||||||
|
{
|
||||||
|
tool.PathInvalid = false;
|
||||||
|
|
||||||
|
if (tool.IsDeleted || tool.Type != ToolType.Local)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(tool.LaunchTarget) || !PathExists(tool.LaunchTarget))
|
||||||
|
{
|
||||||
|
tool.PathInvalid = true;
|
||||||
|
issues.Add(CreateIssue(tool, "启动目标", tool.LaunchTarget, string.IsNullOrWhiteSpace(tool.LaunchTarget) ? "启动目标为空" : "启动目标不存在"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(tool.WorkingDirectory) && !Directory.Exists(tool.WorkingDirectory))
|
||||||
|
{
|
||||||
|
tool.PathInvalid = true;
|
||||||
|
issues.Add(CreateIssue(tool, "工作目录", tool.WorkingDirectory, "工作目录不存在"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PathValidationReport(issues);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsValidUrl(string? rawUrl, out string normalizedUrl)
|
||||||
|
{
|
||||||
|
normalizedUrl = "";
|
||||||
|
if (string.IsNullOrWhiteSpace(rawUrl))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = rawUrl.Trim();
|
||||||
|
if (!value.Contains("://", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
value = "https://" + value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Uri.TryCreate(value, UriKind.Absolute, out var uri))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizedUrl = uri.ToString();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool PathExists(string path)
|
||||||
|
{
|
||||||
|
return File.Exists(path) || Directory.Exists(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PathValidationIssue CreateIssue(ToolItem tool, string fieldName, string? path, string reason)
|
||||||
|
{
|
||||||
|
return new PathValidationIssue
|
||||||
|
{
|
||||||
|
ToolId = tool.Id,
|
||||||
|
ToolName = tool.Name,
|
||||||
|
FieldName = fieldName,
|
||||||
|
Path = path ?? "",
|
||||||
|
Reason = reason
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class PathValidationReport
|
||||||
|
{
|
||||||
|
public PathValidationReport(IReadOnlyList<PathValidationIssue> issues)
|
||||||
|
{
|
||||||
|
Issues = issues;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<PathValidationIssue> Issues { get; }
|
||||||
|
public bool HasIssues => Issues.Count > 0;
|
||||||
|
public int InvalidToolCount => Issues.Select(issue => issue.ToolId).Distinct(StringComparer.OrdinalIgnoreCase).Count();
|
||||||
|
|
||||||
|
public string ToStatusText(int maxIssues = 8)
|
||||||
|
{
|
||||||
|
if (!HasIssues)
|
||||||
|
{
|
||||||
|
return "路径检查完成:未发现失效工具。";
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
builder.AppendLine($"路径检查完成:发现 {InvalidToolCount} 个失效工具,{Issues.Count} 个问题。");
|
||||||
|
|
||||||
|
foreach (var issue in Issues.Take(maxIssues))
|
||||||
|
{
|
||||||
|
var pathText = string.IsNullOrWhiteSpace(issue.Path) ? "" : $"({issue.Path})";
|
||||||
|
builder.AppendLine($"- {issue.ToolName}:{issue.FieldName} {issue.Reason}{pathText}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Issues.Count > maxIssues)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"还有 {Issues.Count - maxIssues} 个问题未显示,请逐项检查本地工具路径。");
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString().TrimEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class PathValidationIssue
|
||||||
|
{
|
||||||
|
public string ToolId { get; init; } = "";
|
||||||
|
public string ToolName { get; init; } = "";
|
||||||
|
public string FieldName { get; init; } = "";
|
||||||
|
public string Path { get; init; } = "";
|
||||||
|
public string Reason { get; init; } = "";
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
|
|
||||||
namespace ToolboxApp.Services;
|
namespace PersonalToolbox.Services;
|
||||||
|
|
||||||
public sealed class StartupService
|
public sealed class StartupService
|
||||||
{
|
{
|
||||||
private const string RunKeyPath = @"Software\Microsoft\Windows\CurrentVersion\Run";
|
private const string RunKeyPath = @"Software\Microsoft\Windows\CurrentVersion\Run";
|
||||||
private const string RunValueName = "ToolboxApp";
|
private const string RunValueName = "PersonalToolbox";
|
||||||
|
|
||||||
public bool IsEnabled()
|
public bool IsEnabled()
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using ToolboxApp.Models;
|
using PersonalToolbox.Models;
|
||||||
|
|
||||||
namespace ToolboxApp.Services;
|
namespace PersonalToolbox.Services;
|
||||||
|
|
||||||
public static class SystemToolService
|
public static class SystemToolService
|
||||||
{
|
{
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using ToolboxApp.Models;
|
using PersonalToolbox.Models;
|
||||||
|
|
||||||
namespace ToolboxApp.Services;
|
namespace PersonalToolbox.Services;
|
||||||
|
|
||||||
public sealed class ToolLaunchService
|
public sealed class ToolLaunchService
|
||||||
{
|
{
|
||||||
@@ -2,7 +2,7 @@ using System.Drawing;
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
using Forms = System.Windows.Forms;
|
using Forms = System.Windows.Forms;
|
||||||
|
|
||||||
namespace ToolboxApp.Services;
|
namespace PersonalToolbox.Services;
|
||||||
|
|
||||||
public sealed class TrayService : IDisposable
|
public sealed class TrayService : IDisposable
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using ToolboxApp.Models;
|
using PersonalToolbox.Models;
|
||||||
|
|
||||||
namespace ToolboxApp.ViewModels;
|
namespace PersonalToolbox.ViewModels;
|
||||||
|
|
||||||
public sealed class CombinationMemberViewModel : ObservableObject
|
public sealed class CombinationMemberViewModel : ObservableObject
|
||||||
{
|
{
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using ToolboxApp.Commands;
|
using PersonalToolbox.Commands;
|
||||||
using ToolboxApp.Models;
|
using PersonalToolbox.Models;
|
||||||
using ToolboxApp.Services;
|
using PersonalToolbox.Services;
|
||||||
using ToolboxApp.Views;
|
using PersonalToolbox.Views;
|
||||||
using Clipboard = System.Windows.Clipboard;
|
using Clipboard = System.Windows.Clipboard;
|
||||||
using MessageBox = System.Windows.MessageBox;
|
using MessageBox = System.Windows.MessageBox;
|
||||||
|
|
||||||
namespace ToolboxApp.ViewModels;
|
namespace PersonalToolbox.ViewModels;
|
||||||
|
|
||||||
public sealed class MainWindowViewModel : ObservableObject
|
public sealed class MainWindowViewModel : ObservableObject
|
||||||
{
|
{
|
||||||
@@ -128,15 +128,15 @@ public sealed class MainWindowViewModel : ObservableObject
|
|||||||
_isLogPanelExpanded = _data.Settings.LogPanelExpanded;
|
_isLogPanelExpanded = _data.Settings.LogPanelExpanded;
|
||||||
OnPropertyChanged(nameof(IsLogPanelExpanded));
|
OnPropertyChanged(nameof(IsLogPanelExpanded));
|
||||||
|
|
||||||
var invalidCount = _pathValidationService.ValidateTools(_data.Tools);
|
var pathReport = _pathValidationService.ValidateTools(_data.Tools);
|
||||||
RefreshCategories();
|
RefreshCategories();
|
||||||
RefreshTools();
|
RefreshTools();
|
||||||
|
|
||||||
AddLog(LogLevel.Info, $"配置目录:{_configurationService.ConfigDirectory}");
|
AddLog(LogLevel.Info, $"配置目录:{_configurationService.ConfigDirectory}");
|
||||||
AddLog(LogLevel.Success, "个人工具箱已启动。");
|
AddLog(LogLevel.Success, "个人工具箱已启动。");
|
||||||
if (invalidCount > 0)
|
if (pathReport.HasIssues)
|
||||||
{
|
{
|
||||||
AddLog(LogLevel.Warning, $"路径检查完成:发现 {invalidCount} 个失效工具。");
|
LogPathValidationReport(pathReport);
|
||||||
}
|
}
|
||||||
|
|
||||||
await SaveAsync();
|
await SaveAsync();
|
||||||
@@ -230,7 +230,7 @@ public sealed class MainWindowViewModel : ObservableObject
|
|||||||
|
|
||||||
edited.SortOrder = NextToolSortOrder();
|
edited.SortOrder = NextToolSortOrder();
|
||||||
_data.Tools.Add(edited);
|
_data.Tools.Add(edited);
|
||||||
_pathValidationService.ValidateTools(_data.Tools);
|
_ = _pathValidationService.ValidateTools(_data.Tools);
|
||||||
RefreshTools();
|
RefreshTools();
|
||||||
AddLog(LogLevel.Success, $"已添加本地工具:{edited.Name}");
|
AddLog(LogLevel.Success, $"已添加本地工具:{edited.Name}");
|
||||||
_ = SaveAsync();
|
_ = SaveAsync();
|
||||||
@@ -297,7 +297,7 @@ public sealed class MainWindowViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
CopyToolValues(edited, original);
|
CopyToolValues(edited, original);
|
||||||
_pathValidationService.ValidateTools(_data.Tools);
|
_ = _pathValidationService.ValidateTools(_data.Tools);
|
||||||
RefreshTools();
|
RefreshTools();
|
||||||
AddLog(LogLevel.Success, $"已保存工具:{original.Name}");
|
AddLog(LogLevel.Success, $"已保存工具:{original.Name}");
|
||||||
_ = SaveAsync();
|
_ = SaveAsync();
|
||||||
@@ -346,7 +346,7 @@ public sealed class MainWindowViewModel : ObservableObject
|
|||||||
if (window.ShowDialog() == true)
|
if (window.ShowDialog() == true)
|
||||||
{
|
{
|
||||||
_data = window.Data;
|
_data = window.Data;
|
||||||
_pathValidationService.ValidateTools(_data.Tools);
|
_ = _pathValidationService.ValidateTools(_data.Tools);
|
||||||
RefreshCategories();
|
RefreshCategories();
|
||||||
RefreshTools();
|
RefreshTools();
|
||||||
AddLog(LogLevel.Success, "设置已保存。");
|
AddLog(LogLevel.Success, "设置已保存。");
|
||||||
@@ -444,6 +444,22 @@ public sealed class MainWindowViewModel : ObservableObject
|
|||||||
AddLog(LogLevel.Success, "已复制底部信息。");
|
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)
|
private ToolItem CreateBaseTool(ToolType type)
|
||||||
{
|
{
|
||||||
return new ToolItem
|
return new ToolItem
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace ToolboxApp.ViewModels;
|
namespace PersonalToolbox.ViewModels;
|
||||||
|
|
||||||
public abstract class ObservableObject : INotifyPropertyChanged
|
public abstract class ObservableObject : INotifyPropertyChanged
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using ToolboxApp.Models;
|
using PersonalToolbox.Models;
|
||||||
|
|
||||||
namespace ToolboxApp.ViewModels;
|
namespace PersonalToolbox.ViewModels;
|
||||||
|
|
||||||
public sealed class ToolCardViewModel : ObservableObject
|
public sealed class ToolCardViewModel : ObservableObject
|
||||||
{
|
{
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<Window x:Class="ToolboxApp.Views.CombinationEditorWindow"
|
<Window x:Class="PersonalToolbox.Views.CombinationEditorWindow"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
Title="编辑组合"
|
Title="编辑组合"
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using ToolboxApp.Models;
|
using PersonalToolbox.Models;
|
||||||
using ToolboxApp.Services;
|
using PersonalToolbox.Services;
|
||||||
using ToolboxApp.ViewModels;
|
using PersonalToolbox.ViewModels;
|
||||||
using MessageBox = System.Windows.MessageBox;
|
using MessageBox = System.Windows.MessageBox;
|
||||||
|
|
||||||
namespace ToolboxApp.Views;
|
namespace PersonalToolbox.Views;
|
||||||
|
|
||||||
public partial class CombinationEditorWindow : Window
|
public partial class CombinationEditorWindow : Window
|
||||||
{
|
{
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<Window x:Class="ToolboxApp.Views.PromptWindow"
|
<Window x:Class="PersonalToolbox.Views.PromptWindow"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
Title="输入"
|
Title="输入"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
|
||||||
namespace ToolboxApp.Views;
|
namespace PersonalToolbox.Views;
|
||||||
|
|
||||||
public partial class PromptWindow : Window
|
public partial class PromptWindow : Window
|
||||||
{
|
{
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<Window x:Class="ToolboxApp.Views.SettingsWindow"
|
<Window x:Class="PersonalToolbox.Views.SettingsWindow"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
Title="设置"
|
Title="设置"
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
Width="160"
|
Width="160"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
Margin="0,0,0,10"
|
Margin="0,0,0,10"
|
||||||
ToolTip="在资源管理器中打开 %AppData%\ToolboxApp。"
|
ToolTip="在资源管理器中打开 %AppData%\PersonalToolbox。"
|
||||||
Click="OpenConfigButton_OnClick" />
|
Click="OpenConfigButton_OnClick" />
|
||||||
<Button Content="导出全部配置"
|
<Button Content="导出全部配置"
|
||||||
Width="160"
|
Width="160"
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using ToolboxApp.Models;
|
using PersonalToolbox.Models;
|
||||||
using ToolboxApp.Services;
|
using PersonalToolbox.Services;
|
||||||
using MessageBox = System.Windows.MessageBox;
|
using MessageBox = System.Windows.MessageBox;
|
||||||
using OpenFileDialog = Microsoft.Win32.OpenFileDialog;
|
using OpenFileDialog = Microsoft.Win32.OpenFileDialog;
|
||||||
using SaveFileDialog = Microsoft.Win32.SaveFileDialog;
|
using SaveFileDialog = Microsoft.Win32.SaveFileDialog;
|
||||||
|
|
||||||
namespace ToolboxApp.Views;
|
namespace PersonalToolbox.Views;
|
||||||
|
|
||||||
public partial class SettingsWindow : Window
|
public partial class SettingsWindow : Window
|
||||||
{
|
{
|
||||||
@@ -132,7 +132,7 @@ public partial class SettingsWindow : Window
|
|||||||
{
|
{
|
||||||
Title = "导出配置",
|
Title = "导出配置",
|
||||||
Filter = "Zip 文件 (*.zip)|*.zip",
|
Filter = "Zip 文件 (*.zip)|*.zip",
|
||||||
FileName = $"ToolboxApp_Config_{DateTime.Now:yyyyMMdd_HHmmss}.zip"
|
FileName = $"PersonalToolbox_Config_{DateTime.Now:yyyyMMdd_HHmmss}.zip"
|
||||||
};
|
};
|
||||||
|
|
||||||
if (dialog.ShowDialog(this) != true)
|
if (dialog.ShowDialog(this) != true)
|
||||||
@@ -170,15 +170,17 @@ public partial class SettingsWindow : Window
|
|||||||
}
|
}
|
||||||
|
|
||||||
_data = await _configurationService.ImportAsync(dialog.FileName);
|
_data = await _configurationService.ImportAsync(dialog.FileName);
|
||||||
_pathValidationService.ValidateTools(_data.Tools);
|
var report = _pathValidationService.ValidateTools(_data.Tools);
|
||||||
LoadControls();
|
LoadControls();
|
||||||
DataStatusTextBlock.Text = "配置导入完成。建议检查自动运行列表和快捷键,确认其中没有不需要启动的工具。";
|
DataStatusTextBlock.Text = report.HasIssues
|
||||||
|
? $"配置导入完成。建议检查自动运行列表和快捷键,确认其中没有不需要启动的工具。{Environment.NewLine}{report.ToStatusText(int.MaxValue)}"
|
||||||
|
: "配置导入完成。建议检查自动运行列表和快捷键,确认其中没有不需要启动的工具。路径检查未发现失效工具。";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ValidatePathsButton_OnClick(object sender, RoutedEventArgs e)
|
private void ValidatePathsButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var invalidCount = _pathValidationService.ValidateTools(_data.Tools);
|
var report = _pathValidationService.ValidateTools(_data.Tools);
|
||||||
DataStatusTextBlock.Text = $"路径检查完成:发现 {invalidCount} 个失效工具。";
|
DataStatusTextBlock.Text = report.ToStatusText(int.MaxValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void ResetButton_OnClick(object sender, RoutedEventArgs e)
|
private async void ResetButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<Window x:Class="ToolboxApp.Views.ToolEditorWindow"
|
<Window x:Class="PersonalToolbox.Views.ToolEditorWindow"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
Title="编辑工具"
|
Title="编辑工具"
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
using Forms = System.Windows.Forms;
|
using Forms = System.Windows.Forms;
|
||||||
using ToolboxApp.Models;
|
using PersonalToolbox.Models;
|
||||||
using ToolboxApp.Services;
|
using PersonalToolbox.Services;
|
||||||
using MessageBox = System.Windows.MessageBox;
|
using MessageBox = System.Windows.MessageBox;
|
||||||
using OpenFileDialog = Microsoft.Win32.OpenFileDialog;
|
using OpenFileDialog = Microsoft.Win32.OpenFileDialog;
|
||||||
|
|
||||||
namespace ToolboxApp.Views;
|
namespace PersonalToolbox.Views;
|
||||||
|
|
||||||
public partial class ToolEditorWindow : Window
|
public partial class ToolEditorWindow : Window
|
||||||
{
|
{
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
using System.IO;
|
|
||||||
using ToolboxApp.Models;
|
|
||||||
|
|
||||||
namespace ToolboxApp.Services;
|
|
||||||
|
|
||||||
public sealed class PathValidationService
|
|
||||||
{
|
|
||||||
public int ValidateTools(IEnumerable<ToolItem> tools)
|
|
||||||
{
|
|
||||||
var invalidCount = 0;
|
|
||||||
foreach (var tool in tools)
|
|
||||||
{
|
|
||||||
tool.PathInvalid = false;
|
|
||||||
|
|
||||||
if (tool.IsDeleted || tool.Type != ToolType.Local)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(tool.LaunchTarget) || !PathExists(tool.LaunchTarget))
|
|
||||||
{
|
|
||||||
tool.PathInvalid = true;
|
|
||||||
invalidCount++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(tool.WorkingDirectory) && !Directory.Exists(tool.WorkingDirectory))
|
|
||||||
{
|
|
||||||
tool.PathInvalid = true;
|
|
||||||
invalidCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return invalidCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsValidUrl(string? rawUrl, out string normalizedUrl)
|
|
||||||
{
|
|
||||||
normalizedUrl = "";
|
|
||||||
if (string.IsNullOrWhiteSpace(rawUrl))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var value = rawUrl.Trim();
|
|
||||||
if (!value.Contains("://", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
value = "https://" + value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Uri.TryCreate(value, UriKind.Absolute, out var uri))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
normalizedUrl = uri.ToString();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool PathExists(string path)
|
|
||||||
{
|
|
||||||
return File.Exists(path) || Directory.Exists(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
实际开发时可使用临时代号:
|
实际开发时可使用临时代号:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
ToolboxApp
|
PersonalToolbox
|
||||||
```
|
```
|
||||||
|
|
||||||
### 1.2 产品定位
|
### 1.2 产品定位
|
||||||
@@ -1028,13 +1028,13 @@ Tool
|
|||||||
默认配置目录:
|
默认配置目录:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
%AppData%\ToolboxApp\
|
%AppData%\PersonalToolbox\
|
||||||
```
|
```
|
||||||
|
|
||||||
建议文件结构:
|
建议文件结构:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
ToolboxApp
|
PersonalToolbox
|
||||||
├─ appsettings.json // 软件设置
|
├─ appsettings.json // 软件设置
|
||||||
├─ categories.json // 分类列表
|
├─ categories.json // 分类列表
|
||||||
├─ tools.json // 工具列表,包含组合
|
├─ tools.json // 工具列表,包含组合
|
||||||
@@ -2223,13 +2223,13 @@ LogPanelViewModel
|
|||||||
从 exe、lnk 提取的图标可以缓存到:
|
从 exe、lnk 提取的图标可以缓存到:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
%AppData%\ToolboxApp\icons\cache\
|
%AppData%\PersonalToolbox\icons\cache\
|
||||||
```
|
```
|
||||||
|
|
||||||
用户手动选择的图标可以复制到:
|
用户手动选择的图标可以复制到:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
%AppData%\ToolboxApp\icons\custom\
|
%AppData%\PersonalToolbox\icons\custom\
|
||||||
```
|
```
|
||||||
|
|
||||||
避免原文件移动后图标丢失。
|
避免原文件移动后图标丢失。
|
||||||
|
|||||||
Reference in New Issue
Block a user