NewLife/XCoder

feat: CrazyCoder 批量实现12个剩余工具

新增工具窗口(每个独立 Window + ViewModel):
1. IP设置工具 (IpConfigWindow)
2. SSH工具 (SshWindow)
3. API调试工具 (ApiDebugWindow)
4. 消息调试工具 (MessageDebugWindow)
5. API服务发现 (ApiDiscoverWindow)
6. 地图接口 (MapWindow)
7. 手机备份 (BackupWindow)
8. USB设备检测 (UsbDeviceWindow)
9. 声卡选择器 (AudioDeviceWindow)
10. 音频梅尔频谱 (MelSpectrumWindow)
11. 文件编码转换 (FileEncodingWindow)
12. MD5破解 + 对称加密(合并到 SecurityWindow)

注册到 MainViewModel 菜单列表。
使用 CommunityToolkit.Mvvm 和 NewLife 系列库。
大石头 authored at 2026-07-14 02:39:55
a77d60d
Tree
1 Parent(s) b46e350
Summary: 36 changed files with 3966 additions and 3 deletions.
Added +286 -0
Added +166 -0
Added +185 -0
Added +186 -0
Added +164 -0
Added +186 -0
Modified +11 -3
Added +200 -0
Added +204 -0
Added +238 -0
Modified +272 -0
Added +142 -0
Added +241 -0
Added +175 -0
Added +16 -0
Added +102 -0
Added +16 -0
Added +96 -0
Added +16 -0
Added +115 -0
Added +16 -0
Added +116 -0
Added +16 -0
Added +117 -0
Added +16 -0
Added +111 -0
Added +16 -0
Added +99 -0
Added +16 -0
Added +174 -0
Added +16 -0
Modified +5 -0
Added +128 -0
Added +16 -0
Added +71 -0
Added +16 -0
Added +286 -0
diff --git a/CrazyCoder/ViewModels/ApiDebugViewModel.cs b/CrazyCoder/ViewModels/ApiDebugViewModel.cs
new file mode 100644
index 0000000..4c32e72
--- /dev/null
+++ b/CrazyCoder/ViewModels/ApiDebugViewModel.cs
@@ -0,0 +1,286 @@
+using System.Collections.ObjectModel;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using NewLife;
+using NewLife.Data;
+using NewLife.Log;
+using NewLife.Net;
+using NewLife.Remoting;
+using NewLife.Threading;
+
+namespace CrazyCoder.ViewModels;
+
+/// <summary>文本日志输出</summary>
+internal class SimpleLog : Logger
+{
+    /// <summary>写入回调</summary>
+    public Action<String> WriteAction { get; set; }
+
+    /// <summary>写日志</summary>
+    protected override void OnWrite(LogLevel level, String format, params Object[] args)
+    {
+        WriteAction?.Invoke(Format(level, format, args));
+    }
+
+    private static String Format(LogLevel level, String format, params Object[] args)
+    {
+        if (args.Length > 0)
+            return $"[{level}] {String.Format(format, args)}";
+        return $"[{level}] {format}";
+    }
+}
+
+/// <summary>API 调试工具 ViewModel</summary>
+public partial class ApiDebugViewModel : ObservableObject
+{
+    #region 属性
+    private ApiServer _server;
+    private ApiClient _client;
+    private TimerX _timer;
+
+    /// <summary>工作模式列表</summary>
+    public ObservableCollection<String> Modes { get; } = ["服务端", "客户端"];
+
+    /// <summary>选中模式</summary>
+    [ObservableProperty]
+    private Int32 _selectedModeIndex;
+
+    /// <summary>地址</summary>
+    [ObservableProperty]
+    private String _address = "tcp://127.0.0.1:8080";
+
+    /// <summary>端口</summary>
+    [ObservableProperty]
+    private Int32 _port = 8080;
+
+    /// <summary>是否已连接</summary>
+    [ObservableProperty]
+    private Boolean _isConnected;
+
+    /// <summary>连接按钮文本</summary>
+    [ObservableProperty]
+    private String _connectButtonText = "打开";
+
+    /// <summary>发送内容</summary>
+    [ObservableProperty]
+    private String _sendText = "";
+
+    /// <summary>API 动作列表</summary>
+    public ObservableCollection<String> ApiActions { get; } = [];
+
+    /// <summary>选中动作</summary>
+    [ObservableProperty]
+    private String _selectedAction = "";
+
+    /// <summary>接收日志</summary>
+    [ObservableProperty]
+    private String _receiveLog = "";
+
+    /// <summary>显示应用日志</summary>
+    [ObservableProperty]
+    private Boolean _showLog = true;
+
+    /// <summary>显示编码日志</summary>
+    [ObservableProperty]
+    private Boolean _showEncoderLog;
+
+    /// <summary>显示发送数据</summary>
+    [ObservableProperty]
+    private Boolean _showSend;
+
+    /// <summary>显示接收数据</summary>
+    [ObservableProperty]
+    private Boolean _showReceive;
+
+    /// <summary>显示统计信息</summary>
+    [ObservableProperty]
+    private Boolean _showStat;
+
+    /// <summary>发送次数</summary>
+    [ObservableProperty]
+    private Int32 _sendTimes = 1;
+
+    /// <summary>发送间隔(ms)</summary>
+    [ObservableProperty]
+    private Int32 _sendSleep = 1000;
+
+    /// <summary>并发数</summary>
+    [ObservableProperty]
+    private Int32 _sendThreads = 1;
+    #endregion
+
+    #region 构造
+    /// <summary>实例化 API 调试工具 ViewModel</summary>
+    public ApiDebugViewModel()
+    {
+    }
+    #endregion
+
+    #region 连接/断开
+    /// <summary>切换连接状态</summary>
+    [RelayCommand]
+    private void ToggleConnect()
+    {
+        if (IsConnected)
+            Disconnect();
+        else
+            Connect();
+    }
+
+    private void Connect()
+    {
+        _server = null;
+        _client = null;
+        _timer = null;
+
+        var port = Port;
+        var uri = new NetUri(Address);
+        var log = CreateLog();
+
+        try
+        {
+            if (SelectedModeIndex == 0) // 服务端
+            {
+                var svr = new ApiServer(port)
+                {
+                    Log = ShowLog ? log : Logger.Null,
+                    EncoderLog = ShowEncoderLog ? log : Logger.Null
+                };
+
+                if (ShowSend || ShowReceive)
+                {
+                    svr.EnsureCreate();
+                    if (svr.Server is NetServer ns)
+                    {
+                        ns.Log = log;
+                        ns.LogSend = ShowSend;
+                        ns.LogReceive = ShowReceive;
+                    }
+                }
+
+                svr.Start();
+
+                AppendLog($"正在监听 {port}");
+                _server = svr;
+            }
+            else // 客户端
+            {
+                var client = new ApiClient(uri + "")
+                {
+                    Log = ShowLog ? log : Logger.Null,
+                    EncoderLog = ShowEncoderLog ? log : Logger.Null
+                };
+
+                _client = client;
+                client.Open();
+
+                // 连接成功后拉取 API 列表
+                GetApiAll();
+
+                AppendLog($"已连接服务器 {uri}");
+            }
+
+            IsConnected = true;
+            ConnectButtonText = "关闭";
+            _timer = new TimerX(RefreshStat, null, 5000, 5000) { Async = true };
+        }
+        catch (Exception ex)
+        {
+            AppendLog($"连接失败: {ex.Message}");
+        }
+    }
+
+    private async void GetApiAll()
+    {
+        try
+        {
+            var apis = await _client.InvokeAsync<String[]>("Api/All");
+            if (apis != null)
+            {
+                ApiActions.Clear();
+                foreach (var item in apis)
+                {
+                    ApiActions.Add(item);
+                }
+                if (ApiActions.Count > 0) SelectedAction = ApiActions[0];
+            }
+        }
+        catch (Exception ex)
+        {
+            AppendLog($"获取 API 列表失败: {ex.Message}");
+        }
+    }
+
+    private void Disconnect()
+    {
+        _client?.Dispose();
+        _client = null;
+
+        if (_server != null)
+        {
+            _server.Dispose();
+            _server = null;
+        }
+
+        _timer?.Dispose();
+        _timer = null;
+
+        IsConnected = false;
+        ConnectButtonText = "打开";
+        AppendLog("已断开连接");
+    }
+
+    /// <summary>发送请求</summary>
+    [RelayCommand]
+    private async void Send()
+    {
+        var str = SendText;
+        if (str.IsNullOrEmpty()) return;
+
+        if (_client == null)
+        {
+            AppendLog("客户端未连接");
+            return;
+        }
+
+        var act = SelectedAction;
+        if (act.IsNullOrEmpty()) return;
+
+        try
+        {
+            AppendLog($"> {act}: {str}");
+
+            // 尝试解析参数
+            Object args = str;
+            var result = await _client.InvokeAsync<Object>(act, args);
+            AppendLog($"< {result}");
+        }
+        catch (Exception ex)
+        {
+            AppendLog($"请求失败: {ex.Message}");
+        }
+    }
+
+    /// <summary>清空日志</summary>
+    [RelayCommand]
+    private void ClearLog()
+    {
+        ReceiveLog = "";
+    }
+
+    private void RefreshStat(Object state)
+    {
+        // 统计信息简化处理
+    }
+
+    private void AppendLog(String msg)
+    {
+        ReceiveLog += $"{msg}\r\n";
+    }
+
+    private ILog CreateLog()
+    {
+        return new SimpleLog { WriteAction = msg => AppendLog(msg) };
+    }
+    #endregion
+}
Added +166 -0
diff --git a/CrazyCoder/ViewModels/ApiDiscoverViewModel.cs b/CrazyCoder/ViewModels/ApiDiscoverViewModel.cs
new file mode 100644
index 0000000..1801e4f
--- /dev/null
+++ b/CrazyCoder/ViewModels/ApiDiscoverViewModel.cs
@@ -0,0 +1,166 @@
+using System.Collections.ObjectModel;
+using System.Net;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using NewLife;
+using NewLife.Log;
+using NewLife.Net;
+using NewLife.Remoting;
+using NewLife.Serialization;
+
+namespace CrazyCoder.ViewModels;
+
+/// <summary>API 服务信息</summary>
+public class ApiServiceInfo
+{
+    /// <summary>名称</summary>
+    public String Name { get; set; } = "";
+
+    /// <summary>IP</summary>
+    public String RemoteIP { get; set; } = "";
+
+    /// <summary>端口</summary>
+    public Int32 Port { get; set; }
+
+    /// <summary>ID</summary>
+    public Int32 Id { get; set; }
+
+    /// <summary>版本</summary>
+    public String Version { get; set; } = "";
+
+    /// <summary>编码</summary>
+    public String Code { get; set; } = "";
+
+    /// <summary>地址</summary>
+    public String Address { get; set; } = "";
+}
+
+/// <summary>API 服务发现 ViewModel</summary>
+public partial class ApiDiscoverViewModel : ObservableObject
+{
+    #region 属性
+    /// <summary>端口</summary>
+    [ObservableProperty]
+    private Int32 _port = 5500;
+
+    /// <summary>发现的服务列表</summary>
+    public ObservableCollection<ApiServiceInfo> Services { get; } = [];
+
+    /// <summary>日志输出</summary>
+    [ObservableProperty]
+    private String _log = "";
+
+    /// <summary>是否正在扫描</summary>
+    [ObservableProperty]
+    private Boolean _isScanning;
+    #endregion
+
+    #region 扫描
+    /// <summary>扫描网络中的 API 服务</summary>
+    [RelayCommand]
+    private async void Scan()
+    {
+        if (IsScanning) return;
+
+        IsScanning = true;
+        Services.Clear();
+        AppendLog($"开始在端口 {Port} 上扫描 API 服务...");
+
+        try
+        {
+            var port = Port;
+            var ts = new List<Task>();
+
+            // 广播
+            var ep = new IPEndPoint(IPAddress.Broadcast, port);
+            ts.Add(Task.Run(() => DiscoverUdp(null, ep)));
+
+            // 各本地 IP
+            foreach (var ip in NetHelper.GetIPs())
+            {
+                var ep2 = new IPEndPoint(IPAddress.Broadcast, port);
+                ts.Add(Task.Run(() => DiscoverUdp(ip, ep2)));
+            }
+
+            await Task.WhenAll(ts);
+            AppendLog($"扫描完成,发现 {Services.Count} 个服务");
+        }
+        catch (Exception ex)
+        {
+            AppendLog($"扫描出错: {ex.Message}");
+        }
+        finally
+        {
+            IsScanning = false;
+        }
+    }
+
+    private async Task DiscoverUdp(IPAddress local, IPEndPoint ep)
+    {
+        try
+        {
+            var client = new ApiClient($"udp://{ep.Address}:{ep.Port}");
+            if (local != null) client.Local = new NetUri { Address = local };
+            client.Received += OnReceived;
+
+            await client.InvokeAsync<Object>("Api/Info");
+            await Task.Delay(1000);
+        }
+        catch
+        {
+            // 超时忽略
+        }
+    }
+
+    private void OnReceived(Object sender, ApiReceivedEventArgs e)
+    {
+        if (e.Message == null || !e.Message.Reply) return;
+
+        var msg = e.ApiMessage;
+        var client = sender as ApiClient;
+        var enc = client?.Encoder;
+        if (enc == null) return;
+
+        try
+        {
+            var result = enc.DecodeResult(msg.Action, msg.Data, e.Message, typeof(Object));
+            var remote = (e.UserState as ReceivedEventArgs)?.Remote;
+
+            if (msg.Action == "Api/Info" && result != null)
+            {
+                var json = result.ToJson();
+                AppendLog($"发现服务: {remote} -> {json}");
+                Services.Add(new ApiServiceInfo
+                {
+                    Name = remote?.Address + "",
+                    RemoteIP = remote?.Address + "",
+                    Port = remote?.Port ?? 0,
+                });
+            }
+        }
+        catch
+        {
+            // 解析失败跳过
+        }
+    }
+
+    /// <summary>清空日志</summary>
+    [RelayCommand]
+    private void ClearLog()
+    {
+        Log = "";
+    }
+
+    /// <summary>清空服务列表</summary>
+    [RelayCommand]
+    private void ClearServices()
+    {
+        Services.Clear();
+    }
+
+    private void AppendLog(String msg)
+    {
+        Log += $"{msg}\r\n";
+    }
+    #endregion
+}
Added +185 -0
diff --git a/CrazyCoder/ViewModels/AudioDeviceViewModel.cs b/CrazyCoder/ViewModels/AudioDeviceViewModel.cs
new file mode 100644
index 0000000..620e2e7
--- /dev/null
+++ b/CrazyCoder/ViewModels/AudioDeviceViewModel.cs
@@ -0,0 +1,185 @@
+using System.Collections.ObjectModel;
+using System.Text;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using NAudio.CoreAudioApi;
+using NewLife;
+
+namespace CrazyCoder.ViewModels;
+
+/// <summary>音频设备信息</summary>
+public class AudioDeviceInfo
+{
+    /// <summary>设备 ID</summary>
+    public String Id { get; set; } = "";
+
+    /// <summary>设备名称</summary>
+    public String Name { get; set; } = "";
+
+    /// <summary>设备友好名称</summary>
+    public String FriendlyName { get; set; } = "";
+
+    /// <summary>声道数</summary>
+    public Int32 Channels { get; set; }
+
+    /// <summary>采样率</summary>
+    public Int32 SampleRate { get; set; }
+
+    /// <summary>是否有播放功能</summary>
+    public Boolean HasPlayback { get; set; }
+
+    /// <summary>是否有录音功能</summary>
+    public Boolean HasRecording { get; set; }
+
+    /// <summary>音量</summary>
+    public Single Volume { get; set; } = 0.5f;
+}
+
+/// <summary>声卡选择器 ViewModel</summary>
+public partial class AudioDeviceViewModel : ObservableObject
+{
+    #region 属性
+    /// <summary>设备列表</summary>
+    public ObservableCollection<String> DeviceList { get; } = [];
+
+    /// <summary>选中的设备</summary>
+    [ObservableProperty]
+    private Int32 _selectedDeviceIndex;
+
+    /// <summary>设备详细信息</summary>
+    [ObservableProperty]
+    private String _deviceDetail = "";
+
+    /// <summary>音量</summary>
+    [ObservableProperty]
+    private Double _volumeValue = 50;
+
+    /// <summary>日志输出</summary>
+    [ObservableProperty]
+    private String _log = "";
+
+    private MMDeviceEnumerator _enumerator;
+    private Dictionary<String, AudioDeviceInfo> _devices = [];
+    #endregion
+
+    #region 构造
+    /// <summary>实例化声卡选择器 ViewModel</summary>
+    public AudioDeviceViewModel()
+    {
+        try
+        {
+            _enumerator = new MMDeviceEnumerator();
+            RefreshDevices();
+        }
+        catch (Exception ex)
+        {
+            AppendLog($"初始化音频设备失败: {ex.Message}");
+        }
+    }
+    #endregion
+
+    #region 方法
+    /// <summary>刷新设备列表</summary>
+    [RelayCommand]
+    private void RefreshDevices()
+    {
+        DeviceList.Clear();
+        _devices.Clear();
+
+        try
+        {
+            // 枚举播放设备
+            var playbackDevices = _enumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active);
+            foreach (var dev in playbackDevices)
+            {
+                var id = dev.ID;
+                if (!_devices.ContainsKey(id))
+                {
+                    _devices[id] = new AudioDeviceInfo
+                    {
+                        Id = id,
+                        Name = dev.FriendlyName,
+                        FriendlyName = dev.DeviceFriendlyName,
+                        HasPlayback = true,
+                        Channels = dev.AudioClient.MixFormat.Channels,
+                        SampleRate = dev.AudioClient.MixFormat.SampleRate,
+                    };
+                }
+                else
+                {
+                    _devices[id].HasPlayback = true;
+                }
+            }
+
+            // 枚举录音设备
+            var recordingDevices = _enumerator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.Active);
+            foreach (var dev in recordingDevices)
+            {
+                var id = dev.ID;
+                if (!_devices.ContainsKey(id))
+                {
+                    _devices[id] = new AudioDeviceInfo
+                    {
+                        Id = id,
+                        Name = dev.FriendlyName,
+                        FriendlyName = dev.DeviceFriendlyName,
+                        HasRecording = true,
+                        Channels = dev.AudioClient.MixFormat.Channels,
+                        SampleRate = dev.AudioClient.MixFormat.SampleRate,
+                    };
+                }
+                else
+                {
+                    _devices[id].HasRecording = true;
+                }
+            }
+
+            foreach (var kv in _devices)
+            {
+                DeviceList.Add(kv.Value.Name);
+            }
+
+            if (DeviceList.Count > 0) SelectedDeviceIndex = 0;
+
+            AppendLog($"刷新完成,找到 {_devices.Count} 个音频设备");
+        }
+        catch (Exception ex)
+        {
+            AppendLog($"刷新设备失败: {ex.Message}");
+        }
+    }
+
+    partial void OnSelectedDeviceIndexChanged(Int32 value)
+    {
+        if (value < 0 || value >= DeviceList.Count) return;
+
+        var name = DeviceList[value];
+        var dev = _devices.Values.FirstOrDefault(d => d.Name == name);
+        if (dev == null) return;
+
+        var sb = new StringBuilder();
+        sb.AppendLine($"设备: {dev.Name}");
+        sb.AppendLine($"友好名称: {dev.FriendlyName}");
+        sb.AppendLine($"播放: {(dev.HasPlayback ? "是" : "否")}");
+        sb.AppendLine($"录音: {(dev.HasRecording ? "是" : "否")}");
+        sb.AppendLine($"声道数: {dev.Channels}");
+        sb.AppendLine($"采样率: {dev.SampleRate} Hz");
+
+        DeviceDetail = sb.ToString();
+        VolumeValue = dev.Volume * 100;
+    }
+
+    /// <summary>测试播放</summary>
+    [RelayCommand]
+    private void TestPlayback()
+    {
+        // NAudio 播放测试需要额外实现,这里简化处理
+        AppendLog("播放测试功能需要在 View 层实现");
+    }
+
+    private void AppendLog(String msg)
+    {
+        Log += $"{msg}\r\n";
+    }
+    #endregion
+}
Added +186 -0
diff --git a/CrazyCoder/ViewModels/BackupViewModel.cs b/CrazyCoder/ViewModels/BackupViewModel.cs
new file mode 100644
index 0000000..39c3ffa
--- /dev/null
+++ b/CrazyCoder/ViewModels/BackupViewModel.cs
@@ -0,0 +1,186 @@
+using System.IO;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using NewLife;
+
+namespace CrazyCoder.ViewModels;
+
+/// <summary>手机备份 ViewModel</summary>
+public partial class BackupViewModel : ObservableObject
+{
+    #region 属性
+    /// <summary>目标目录</summary>
+    [ObservableProperty]
+    private String _destDir = "";
+
+    /// <summary>源目录1</summary>
+    [ObservableProperty]
+    private String _srcDir1 = "";
+
+    /// <summary>源目录2</summary>
+    [ObservableProperty]
+    private String _srcDir2 = "";
+
+    /// <summary>源目录3</summary>
+    [ObservableProperty]
+    private String _srcDir3 = "";
+
+    /// <summary>源目录4</summary>
+    [ObservableProperty]
+    private String _srcDir4 = "";
+
+    /// <summary>源目录5</summary>
+    [ObservableProperty]
+    private String _srcDir5 = "";
+
+    /// <summary>允许删除文件</summary>
+    [ObservableProperty]
+    private Boolean _allowDelete;
+
+    /// <summary>是否正在备份</summary>
+    [ObservableProperty]
+    private Boolean _isBackingUp;
+
+    /// <summary>日志输出</summary>
+    [ObservableProperty]
+    private String _log = "";
+    #endregion
+
+    #region 备份
+    /// <summary>浏览目录</summary>
+    [RelayCommand]
+    private void Browse(String propertyName)
+    {
+        // View 层通过回调设置
+    }
+
+    /// <summary>开始备份</summary>
+    [RelayCommand]
+    private async void StartBackup()
+    {
+        if (IsBackingUp) return;
+
+        var dest = DestDir;
+        if (dest.IsNullOrEmpty())
+        {
+            Log += "目标目录不能为空\r\n";
+            return;
+        }
+
+        var srcs = new[] { SrcDir1, SrcDir2, SrcDir3, SrcDir4, SrcDir5 }
+            .Where(s => !s.IsNullOrEmpty())
+            .ToArray();
+
+        if (srcs.Length == 0)
+        {
+            Log += "源目录不能为空\r\n";
+            return;
+        }
+
+        IsBackingUp = true;
+        AppendLog($"开始备份到 {dest}...");
+
+        try
+        {
+            var total = 0;
+            foreach (var src in srcs)
+            {
+                total += await Task.Run(() => DoBackup(dest, src, AllowDelete));
+            }
+
+            AppendLog($"备份完成,共处理 {total} 个文件");
+        }
+        catch (Exception ex)
+        {
+            AppendLog($"备份出错: {ex.Message}");
+        }
+        finally
+        {
+            IsBackingUp = false;
+        }
+    }
+
+    private static Int32 DoBackup(String dest, String src, Boolean allowDelete)
+    {
+        if (src.IsNullOrEmpty()) return 0;
+
+        var total = 0;
+        foreach (var fi in src.AsDirectory().GetAllFiles("*.jpg;*.jpeg;*.png;*.mp4;*.m4a;*.aac;*.mp3", true))
+        {
+            var newName = fi.Name;
+            var p = fi.Name.IndexOf('~');
+            if (p > 0)
+            {
+                newName = $"{fi.Name[..p]}{fi.Extension}";
+            }
+
+            if (TryGetTime(fi, ref newName, out var dt))
+            {
+                newName = $"{dest}\\{dt:yyyy}\\{dt:yyyyMM}\\{newName}";
+                newName.EnsureDirectory(true);
+
+                if (!File.Exists(newName))
+                {
+                    if (allowDelete)
+                        fi.MoveTo(newName);
+                    else
+                        fi.CopyTo(newName);
+
+                    total++;
+                }
+                else
+                {
+                    var nfi = newName.AsFile();
+                    if (allowDelete && fi.Length == nfi.Length)
+                    {
+                        fi.Delete();
+                        total++;
+                    }
+                }
+            }
+        }
+
+        return total;
+    }
+
+    private static Boolean TryGetTime(FileInfo fi, ref String fileName, out DateTime time)
+    {
+        time = DateTime.MinValue;
+
+        var ss = fileName.Split('_', '-', '.');
+        if (ss.Length >= 2 && ss[1].Length >= 6 &&
+            DateTime.TryParseExact($"{ss[0]}_{ss[1][..6]}", "yyyyMMdd_HHmmss", null,
+                System.Globalization.DateTimeStyles.None, out var dt) && dt.Year > 2000)
+        {
+            time = dt;
+            return true;
+        }
+
+        if (ss.Length >= 3 && ss[2].Length >= 6 &&
+            DateTime.TryParseExact($"{ss[1]}_{ss[2][..6]}", "yyyyMMdd_HHmmss", null,
+                System.Globalization.DateTimeStyles.None, out dt) && dt.Year > 2000)
+        {
+            time = dt;
+            return true;
+        }
+
+        if (fileName.Length == 3 + 14 + 4 && fileName.StartsWith("IMG"))
+        {
+            var str = fileName.TrimStart("IMG").Substring(null, ".");
+            if (str.Length >= 14 && DateTime.TryParseExact(str[..14], "yyyyMMddHHmmss", null,
+                    System.Globalization.DateTimeStyles.None, out dt) && dt.Year > 2000)
+            {
+                time = dt;
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private void AppendLog(String msg)
+    {
+        Log += $"{msg}\r\n";
+    }
+    #endregion
+}
Added +164 -0
diff --git a/CrazyCoder/ViewModels/FileEncodingViewModel.cs b/CrazyCoder/ViewModels/FileEncodingViewModel.cs
new file mode 100644
index 0000000..431ad3e
--- /dev/null
+++ b/CrazyCoder/ViewModels/FileEncodingViewModel.cs
@@ -0,0 +1,164 @@
+using System.IO;
+using System.Text;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using NewLife;
+using NewLife.IO;
+
+namespace CrazyCoder.ViewModels;
+
+/// <summary>文件编码转换 ViewModel</summary>
+public partial class FileEncodingViewModel : ObservableObject
+{
+    #region 属性
+    /// <summary>文件夹路径</summary>
+    [ObservableProperty]
+    private String _folderPath = "";
+
+    /// <summary>文件后缀过滤</summary>
+    [ObservableProperty]
+    private String _suffixFilter = "*.cs;*.aspx;*.html;*.js;*.css;*.xml;*.json";
+
+    /// <summary>目标编码</summary>
+    [ObservableProperty]
+    private String _targetEncoding = "UTF-8";
+
+    /// <summary>可用编码列表</summary>
+    public String[] Encodings { get; } = ["UTF-8", "UTF-8 NoBOM", "ASNI", "Unicode", "gb2312"];
+
+    /// <summary>扫描结果</summary>
+    [ObservableProperty]
+    private String _scanResult = "";
+
+    /// <summary>日志输出</summary>
+    [ObservableProperty]
+    private String _log = "";
+    #endregion
+
+    #region 方法
+    static FileEncodingViewModel()
+    {
+        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
+    }
+
+    /// <summary>选择文件夹</summary>
+    [RelayCommand]
+    private void BrowseFolder()
+    {
+        // View 层通过回调设置 FolderPath
+    }
+
+    /// <summary>扫描文件</summary>
+    [RelayCommand]
+    private void Scan()
+    {
+        var path = FolderPath;
+        if (path.IsNullOrEmpty() || !Directory.Exists(path))
+        {
+            Log += "请选择有效的文件夹\r\n";
+            return;
+        }
+
+        var extensions = SuffixFilter.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
+        var sb = new StringBuilder();
+        var count = 0;
+
+        foreach (var ext in extensions)
+        {
+            var files = Directory.GetFiles(path, ext, SearchOption.AllDirectories);
+            foreach (var file in files)
+            {
+                try
+                {
+                    using var fs = File.OpenRead(file);
+                    var buf = new Byte[fs.Length > 1024 ? 1024 : (Int32)fs.Length];
+                    fs.Read(buf, 0, buf.Length);
+
+                    var enc = DetectEncoding(buf);
+                    var relative = file[(path.Length + 1)..];
+                    sb.AppendLine($"{(enc ?? "未知"),-12} {relative}");
+                    count++;
+                }
+                catch
+                {
+                    // 跳过无法读取的文件
+                }
+            }
+        }
+
+        ScanResult = sb.ToString();
+        Log += $"扫描完成,共 {count} 个文件\r\n";
+    }
+
+    /// <summary>批量转换编码</summary>
+    [RelayCommand]
+    private void Convert()
+    {
+        var path = FolderPath;
+        if (path.IsNullOrEmpty() || !Directory.Exists(path))
+        {
+            Log += "请选择有效的文件夹\r\n";
+            return;
+        }
+
+        var targetEnc = GetEncoding(TargetEncoding);
+        if (targetEnc == null)
+        {
+            Log += $"不支持的编码: {TargetEncoding}\r\n";
+            return;
+        }
+
+        var extensions = SuffixFilter.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
+        var total = 0;
+
+        foreach (var ext in extensions)
+        {
+            var files = Directory.GetFiles(path, ext, SearchOption.AllDirectories);
+            foreach (var file in files)
+            {
+                try
+                {
+                    var content = File.ReadAllText(file);
+                    // 跳过已经是目标编码的文件
+                    var currentEnc = DetectEncoding(File.ReadAllBytes(file));
+                    if (currentEnc != null && currentEnc.EqualIgnoreCase(TargetEncoding)) continue;
+
+                    File.WriteAllText(file, content, targetEnc);
+                    total++;
+                }
+                catch (Exception ex)
+                {
+                    Log += $"转换失败: {file} - {ex.Message}\r\n";
+                }
+            }
+        }
+
+        Log += $"转换完成,共处理 {total} 个文件\r\n";
+    }
+
+    private static Encoding GetEncoding(String name)
+    {
+        return name switch
+        {
+            "UTF-8" => Encoding.UTF8,
+            "UTF-8 NoBOM" => new UTF8Encoding(false),
+            "ASNI" => Encoding.ASCII,
+            "Unicode" => Encoding.Unicode,
+            "gb2312" => Encoding.GetEncoding("gb2312"),
+            _ => Encoding.UTF8
+        };
+    }
+
+    private static String DetectEncoding(Byte[] buf)
+    {
+        if (buf.Length >= 3 && buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF)
+            return "UTF-8";
+        if (buf.Length >= 2 && buf[0] == 0xFF && buf[1] == 0xFE)
+            return "Unicode";
+        if (buf.Length >= 2 && buf[0] == 0xFE && buf[1] == 0xFF)
+            return "Unicode BigEndian";
+
+        return null;
+    }
+    #endregion
+}
Added +186 -0
diff --git a/CrazyCoder/ViewModels/IpConfigViewModel.cs b/CrazyCoder/ViewModels/IpConfigViewModel.cs
new file mode 100644
index 0000000..82ebdbe
--- /dev/null
+++ b/CrazyCoder/ViewModels/IpConfigViewModel.cs
@@ -0,0 +1,186 @@
+using System.Linq;
+using System.Net;
+using System.Net.NetworkInformation;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using NewLife;
+
+namespace CrazyCoder.ViewModels;
+
+/// <summary>IP 设置工具 ViewModel</summary>
+public partial class IpConfigViewModel : ObservableObject
+{
+    #region 属性
+    /// <summary>网络适配器列表</summary>
+    [ObservableProperty]
+    private String[] _adapters = [];
+
+    /// <summary>选中的适配器名称</summary>
+    [ObservableProperty]
+    private String _selectedAdapter = "";
+
+    /// <summary>适配器描述</summary>
+    [ObservableProperty]
+    private String _adapterDescription = "";
+
+    /// <summary>IP 地址</summary>
+    [ObservableProperty]
+    private String _ipAddress = "";
+
+    /// <summary>子网掩码</summary>
+    [ObservableProperty]
+    private String _subnetMask = "";
+
+    /// <summary>网关</summary>
+    [ObservableProperty]
+    private String _gateway = "";
+
+    /// <summary>DNS</summary>
+    [ObservableProperty]
+    private String _dns = "";
+
+    /// <summary>辅助 IP(多行)</summary>
+    [ObservableProperty]
+    private String _secondaryIps = "";
+
+    /// <summary>日志输出</summary>
+    [ObservableProperty]
+    private String _log = "";
+    #endregion
+
+    #region 构造
+    /// <summary>实例化 IP 设置工具 ViewModel</summary>
+    public IpConfigViewModel()
+    {
+        LoadAdapters();
+    }
+    #endregion
+
+    #region 方法
+    /// <summary>加载网络适配器列表</summary>
+    private void LoadAdapters()
+    {
+        var ns = NetworkInterface.GetAllNetworkInterfaces();
+        Adapters = ns.Select(e => e.Name).ToArray();
+        if (Adapters.Length > 0) SelectedAdapter = Adapters[0];
+    }
+
+    partial void OnSelectedAdapterChanged(String value)
+    {
+        if (value.IsNullOrEmpty()) return;
+
+        var ns = NetworkInterface.GetAllNetworkInterfaces();
+        var ni = ns.FirstOrDefault(e => e.Name == value);
+        if (ni == null) return;
+
+        AdapterDescription = ni.Description;
+
+        var ps = ni.GetIPProperties();
+        var ips = ps.UnicastAddresses.Where(e => e.Address.IsIPv4()).ToArray();
+        if (ips.Length == 0) return;
+
+        IpAddress = ips[0].Address + "";
+        SubnetMask = ips[0].IPv4Mask + "";
+        Gateway = ps.GatewayAddresses.Where(e => e.Address.IsIPv4()).Join(",", e => e.Address);
+        Dns = ps.DnsAddresses.Where(e => e.IsIPv4()).Join();
+
+        var ips2 = ips.Skip(1).OrderBy(e => e.Address.GetAddressBytes().ToLong()).Select(e => e.Address).ToArray();
+        SecondaryIps = ips2.Join("\r\n");
+    }
+
+    /// <summary>设置 IP</summary>
+    [RelayCommand]
+    private void Apply()
+    {
+        var ip = IpAddress?.Trim();
+        var mark = SubnetMask?.Trim();
+        var gateway = Gateway?.Trim();
+        if (ip.IsNullOrEmpty() || mark.IsNullOrEmpty())
+        {
+            Log += "IP 地址和子网掩码不能为空\r\n";
+            return;
+        }
+
+        var name = SelectedAdapter;
+        if (name.IsNullOrEmpty()) return;
+
+        AppendLog($"设置主 IP: {ip} {mark} {gateway}");
+        var args = $"interface ip add address name=\"{name}\" {ip} {mark} {gateway}";
+        var rs = "netsh".Run(args, 5_000, s => AppendLog(s));
+        AppendLog($"结果: {rs}");
+
+        // 设置 DNS
+        var dns = Dns?.Split(',');
+        if (dns != null && dns.Length > 0 && !dns[0].IsNullOrEmpty())
+        {
+            args = $"interface ip set dns name=\"{name}\" source=static addr={dns[0]} register=primary";
+            rs = "netsh".Run(args, 5_000, s => AppendLog(s));
+            if (dns.Length > 1)
+            {
+                args = $"interface ip add dnsservers name=\"{name}\" addr={dns[1]} index=2";
+                rs = "netsh".Run(args, 5_000, s => AppendLog(s));
+            }
+        }
+        else
+        {
+            args = $"interface ip set dns name=\"{name}\" source=dhcp";
+            rs = "netsh".Run(args, 5_000, s => AppendLog(s));
+        }
+
+        // 解析辅助 IP
+        var ips = SecondaryIps.Split("\r", "\n", "\t", ",", " ").ToList();
+        for (var i = ips.Count - 1; i >= 0; i--)
+        {
+            var addr = ips[i];
+            var p = addr.LastIndexOf('-');
+            if (p > 0)
+            {
+                var p2 = addr.LastIndexOf('.');
+                if (p2 > 0)
+                {
+                    ips.RemoveAt(i);
+                    var prefix = addr.Substring(0, p2 + 1);
+                    var start = addr.Substring(p2 + 1, p - p2 - 1).ToInt();
+                    var end = addr.Substring(p + 1).ToInt();
+                    for (var k = start; k <= end; k++)
+                    {
+                        ips.Add($"{prefix}{k}");
+                    }
+                }
+            }
+        }
+
+        var addrs = ips.Select(e => IPAddress.Parse(e)).OrderBy(e => e.GetAddressBytes().ToLong()).ToArray();
+        foreach (var item in addrs)
+        {
+            args = $"interface ip add address name=\"{name}\" {item} {mark} {gateway}";
+            rs = "netsh".Run(args, 5_000, s => AppendLog(s));
+        }
+
+        AppendLog("IP 设置完成");
+    }
+
+    /// <summary>恢复 DHCP</summary>
+    [RelayCommand]
+    private void Restore()
+    {
+        var name = SelectedAdapter;
+        if (name.IsNullOrEmpty()) return;
+
+        AppendLog("恢复 DHCP...");
+        var args = $"interface ip set address name=\"{name}\" source=dhcp";
+        var rs = "netsh".Run(args, 5_000, s => AppendLog(s));
+
+        args = $"interface ip set dns name=\"{name}\" source=dhcp";
+        rs = "netsh".Run(args, 5_000, s => AppendLog(s));
+
+        rs = "ipconfig".Run("/renew", 10_000, s => AppendLog(s));
+        AppendLog("DHCP 恢复完成");
+    }
+
+    private void AppendLog(String msg)
+    {
+        Log += $"{msg}\r\n";
+    }
+    #endregion
+}
Modified +11 -3
diff --git a/CrazyCoder/ViewModels/MainViewModel.cs b/CrazyCoder/ViewModels/MainViewModel.cs
index 541bdb3..1efef4d 100644
--- a/CrazyCoder/ViewModels/MainViewModel.cs
+++ b/CrazyCoder/ViewModels/MainViewModel.cs
@@ -5,7 +5,6 @@ using CommunityToolkit.Mvvm.Input;
 using CrazyCoder.Models;
 using CrazyCoder.Views;
 using NewLife.Reflection;
-// DataModeling, RedisManager, DataSync 窗口已在 Menus 中注册 Type
 
 namespace CrazyCoder.ViewModels
 {
@@ -19,12 +18,20 @@ namespace CrazyCoder.ViewModels
                 new MenuModel() { IconFont = "\xe6b6", Title = "Redis 管理器", BackColor = "#EE3B3B", Type = typeof(RedisManagerWindow) },
                 new MenuModel() { IconFont = "\xe6e1", Title = "跨库同步", BackColor = "#218868", Type = typeof(DataSyncWindow) },
                 new MenuModel() { IconFont = "\xe6b6", Title = "网络工具", BackColor = "#EE3B3B", Type = typeof(NetworkWindow) },
+                new MenuModel() { IconFont = "\xe614", Title = "IP设置", BackColor = "#2196F3", Type = typeof(IpConfigWindow) },
+                new MenuModel() { IconFont = "\xe614", Title = "SSH工具", BackColor = "#9C27B0", Type = typeof(SshWindow) },
+                new MenuModel() { IconFont = "\xe6b6", Title = "API调试", BackColor = "#FF9800", Type = typeof(ApiDebugWindow) },
+                new MenuModel() { IconFont = "\xe6e1", Title = "消息调试", BackColor = "#607D8B", Type = typeof(MessageDebugWindow) },
+                new MenuModel() { IconFont = "\xe6b6", Title = "API发现", BackColor = "#795548", Type = typeof(ApiDiscoverWindow) },
                 new MenuModel() { IconFont = "\xe6e1", Title = "RPC工具", BackColor = "#218868" },
                 new MenuModel() { IconFont = "\xe614", Title = "串口工具", BackColor = "#EE3B3B", Type = typeof(SerialPortWindow) },
                 new MenuModel() { IconFont = "\xe614", Title = "Modbus RTU", BackColor = "#EE3B3B", Type = typeof(ModbusRtuWindow) },
                 new MenuModel() { IconFont = "\xe6b6", Title = "Modbus TCP", BackColor = "#EE3B3B", Type = typeof(ModbusTcpWindow) },
                 new MenuModel() { IconFont = "\xe755", Title = "I/O 控制", BackColor = "#218868", Type = typeof(IoControlWindow) },
-                new MenuModel() { IconFont = "\xe755", Title = "地图接口", BackColor = "#218868" },
+                new MenuModel() { IconFont = "\xe755", Title = "地图接口", BackColor = "#218868", Type = typeof(MapWindow) },
+                new MenuModel() { IconFont = "\xe755", Title = "USB设备", BackColor = "#3F51B5", Type = typeof(UsbDeviceWindow) },
+                new MenuModel() { IconFont = "\xe614", Title = "声卡选择", BackColor = "#009688", Type = typeof(AudioDeviceWindow) },
+                new MenuModel() { IconFont = "\xe614", Title = "梅尔频谱", BackColor = "#673AB7", Type = typeof(MelSpectrumWindow) },
                 new MenuModel() { IconFont = "\xe635", Title = "正则表达式", BackColor = "#218868", Type = typeof(RegexWindow) },
                 new MenuModel() { IconFont = "\xe6b6", Title = "图标水印", BackColor = "#EE3B3B", Type = typeof(IconToolWindow) },
                 new MenuModel() { IconFont = "\xe6e1", Title = "加密解密", BackColor = "#218868", Type = typeof(SecurityWindow) },
@@ -32,7 +39,8 @@ namespace CrazyCoder.ViewModels
                 new MenuModel() { IconFont = "\xe755", Title = "GPS 辅助", BackColor = "#218868", Type = typeof(GpsWindow) },
                 new MenuModel() { IconFont = "\xe635", Title = "MQTT 客户端", BackColor = "#EE3B3B", Type = typeof(MqttWindow) },
                 new MenuModel() { IconFont = "\xe755", Title = "文件夹统计", BackColor = "#218868", Type = typeof(FolderStatWindow) },
-                new MenuModel() { IconFont = "\xe635", Title = "文件编码", BackColor = "#218868" },
+                new MenuModel() { IconFont = "\xe635", Title = "文件编码", BackColor = "#218868", Type = typeof(FileEncodingWindow) },
+                new MenuModel() { IconFont = "\xe635", Title = "手机备份", BackColor = "#E91E63", Type = typeof(BackupWindow) },
             ];
 
             SelectedMenu = Menus[0];
Added +200 -0
diff --git a/CrazyCoder/ViewModels/MapViewModel.cs b/CrazyCoder/ViewModels/MapViewModel.cs
new file mode 100644
index 0000000..1b77b9d
--- /dev/null
+++ b/CrazyCoder/ViewModels/MapViewModel.cs
@@ -0,0 +1,200 @@
+using System.Collections.ObjectModel;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using NewLife;
+using NewLife.Data;
+using NewLife.Log;
+using NewLife.Map;
+using NewLife.Model;
+using NewLife.Reflection;
+using NewLife.Serialization;
+
+namespace CrazyCoder.ViewModels;
+
+/// <summary>地图接口 ViewModel</summary>
+public partial class MapViewModel : ObservableObject
+{
+    #region 属性
+    /// <summary>地图类型列表</summary>
+    public ObservableCollection<String> MapTypes { get; } = [];
+
+    /// <summary>选中的地图类型</summary>
+    [ObservableProperty]
+    private String _selectedMap = "";
+
+    /// <summary>方法列表</summary>
+    public ObservableCollection<String> Methods { get; } = [];
+
+    /// <summary>选中的方法</summary>
+    [ObservableProperty]
+    private String _selectedMethod = "";
+
+    /// <summary>坐标类型</summary>
+    public ObservableCollection<String> CoordTypes { get; } = ["wgs84", "gcj02", "bd09ll"];
+
+    /// <summary>选中的坐标类型</summary>
+    [ObservableProperty]
+    private String _selectedCoordType = "wgs84";
+
+    /// <summary>地址</summary>
+    [ObservableProperty]
+    private String _address = "";
+
+    /// <summary>城市</summary>
+    [ObservableProperty]
+    private String _city = "";
+
+    /// <summary>经纬度</summary>
+    [ObservableProperty]
+    private String _location = "";
+
+    /// <summary>经纬度2</summary>
+    [ObservableProperty]
+    private String _location2 = "";
+
+    /// <summary>结果日志</summary>
+    [ObservableProperty]
+    private String _resultLog = "";
+
+    /// <summary>格式化地址</summary>
+    [ObservableProperty]
+    private Boolean _formatAddress;
+
+    private Dictionary<String, Map> _cache = [];
+    #endregion
+
+    #region 构造
+    /// <summary>实例化地图接口 ViewModel</summary>
+    public MapViewModel()
+    {
+        LoadMaps();
+    }
+
+    private void LoadMaps()
+    {
+        MapTypes.Clear();
+        foreach (var item in typeof(IMap).GetAllSubclasses())
+        {
+            MapTypes.Add(item.Name);
+        }
+        if (MapTypes.Count > 0) SelectedMap = MapTypes[0];
+    }
+
+    partial void OnSelectedMapChanged(String value)
+    {
+        Methods.Clear();
+        if (value.IsNullOrEmpty()) return;
+
+        var type = typeof(IMap).GetAllSubclasses().FirstOrDefault(e => e.Name == value);
+        if (type == null) return;
+
+        var methods = type.GetMethods()
+            .Where(m => m.IsPublic && !m.IsStatic && m.DeclaringType != typeof(Object))
+            .Select(m => m.Name);
+        foreach (var m in methods)
+        {
+            Methods.Add(m);
+        }
+        if (Methods.Count > 0) SelectedMethod = Methods[0];
+    }
+    #endregion
+
+    #region 调用
+    /// <summary>调用地图接口</summary>
+    [RelayCommand]
+    private async void Invoke()
+    {
+        var typeName = SelectedMap;
+        var methodName = SelectedMethod;
+        if (typeName.IsNullOrEmpty() || methodName.IsNullOrEmpty()) return;
+
+        var type = typeof(IMap).GetAllSubclasses().FirstOrDefault(e => e.Name == typeName);
+        if (type == null) return;
+
+        try
+        {
+            if (!_cache.TryGetValue(typeName, out var map))
+            {
+                var provider = ObjectContainer.Provider;
+                map = type.GetConstructor(new[] { typeof(IServiceProvider) }) != null
+                    ? type.CreateInstance(provider) as Map
+                    : type.CreateInstance() as Map;
+                if (map != null) map.Log = XTrace.Log;
+                _cache[typeName] = map!;
+            }
+
+            if (map == null) return;
+
+            // 设置 AppKey
+            if (map is BaiduMap bmap)
+                bmap.AppKey = "C73357a276668f8b0563d3f936475007";
+            else if (map is AMap amap)
+                amap.AppKey = "038a84bf20e8306fdd2203110739110c";
+            else if (map is WeMap wmap)
+                wmap.AppKey = "YGEBZ-BDCCX-AJG4X-ZUH6W-MESMV-P2BFF";
+
+            var im = map as IMap;
+            var addr = Address;
+            var city = City;
+            var point = new GeoPoint(Location);
+            var point2 = new GeoPoint(Location2);
+
+            Object result = null;
+
+            if (methodName == nameof(im.GetGeoAsync))
+            {
+                result = await im.GetGeoAsync(addr, city, SelectedCoordType, FormatAddress);
+            }
+            else if (methodName == nameof(im.GetReverseGeoAsync))
+            {
+                result = await im.GetReverseGeoAsync(point, SelectedCoordType);
+            }
+            else if (methodName == nameof(im.GetDistanceAsync))
+            {
+                result = await im.GetDistanceAsync(point, point2, SelectedCoordType);
+            }
+            else if (map is BaiduMap bd)
+            {
+                if (methodName == nameof(bd.PlaceSearchAsync))
+                    result = await bd.PlaceSearchAsync(addr, null, city, SelectedCoordType, FormatAddress);
+                else if (methodName == nameof(bd.ConvertAsync))
+                    result = await bd.ConvertAsync(new[] { point }, SelectedCoordType, "bd09ll");
+                else if (methodName == nameof(bd.IpLocationAsync))
+                    result = await bd.IpLocationAsync(addr, SelectedCoordType);
+            }
+            else if (map is AMap am && methodName == nameof(am.GetAreaAsync))
+            {
+                result = (await am.GetAreaAsync(city))?.ToArray();
+            }
+            else
+            {
+                var ps = new Dictionary<String, Object>();
+                if (methodName.Contains("address", StringComparison.OrdinalIgnoreCase)) ps["address"] = addr;
+                if (methodName.Contains("city", StringComparison.OrdinalIgnoreCase)) ps["city"] = city;
+                if (methodName.Contains("point", StringComparison.OrdinalIgnoreCase)) ps["point"] = point;
+
+                var task = map.InvokeWithParams(type.GetMethod(methodName), ps) as Task;
+                if (task != null)
+                {
+                    await task;
+                    result = task.GetValue("Result");
+                }
+            }
+
+            ResultLog = result?.ToJson(true) ?? "无结果";
+        }
+        catch (Exception ex)
+        {
+            ex = ex.GetTrue();
+            ResultLog = ex.ToString();
+        }
+    }
+
+    /// <summary>清空日志</summary>
+    [RelayCommand]
+    private void ClearLog()
+    {
+        ResultLog = "";
+    }
+    #endregion
+}
Added +204 -0
diff --git a/CrazyCoder/ViewModels/MelSpectrumViewModel.cs b/CrazyCoder/ViewModels/MelSpectrumViewModel.cs
new file mode 100644
index 0000000..d273251
--- /dev/null
+++ b/CrazyCoder/ViewModels/MelSpectrumViewModel.cs
@@ -0,0 +1,204 @@
+using System.Collections.ObjectModel;
+using System.Drawing;
+using System.IO;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using NewLife;
+
+namespace CrazyCoder.ViewModels;
+
+/// <summary>音频梅尔频谱 ViewModel</summary>
+public partial class MelSpectrumViewModel : ObservableObject
+{
+    #region 属性
+    /// <summary>声道列表</summary>
+    public ObservableCollection<String> ChannelList { get; } = [];
+
+    /// <summary>选中的声道索引</summary>
+    [ObservableProperty]
+    private Int32 _selectedChannelIndex;
+
+    /// <summary>文件路径</summary>
+    [ObservableProperty]
+    private String _filePath = "";
+
+    /// <summary>状态日志</summary>
+    [ObservableProperty]
+    private String _log = "";
+
+    /// <summary>梅尔频谱图路径</summary>
+    [ObservableProperty]
+    private String _melImagePath = "";
+
+    /// <summary>音量曲线图路径</summary>
+    [ObservableProperty]
+    private String _volumeImagePath = "";
+
+    private List<String> _melFiles = [];
+    private List<String> _volFiles = [];
+    #endregion
+
+    #region 方法
+    /// <summary>打开文件</summary>
+    [RelayCommand]
+    private void OpenFile()
+    {
+        // View 层通过 OpenFileDialog 设置 FilePath
+    }
+
+    partial void OnFilePathChanged(String value)
+    {
+        if (value.IsNullOrEmpty() || !File.Exists(value)) return;
+
+        ChannelList.Clear();
+        _melFiles.Clear();
+        _volFiles.Clear();
+
+        if (!value.EndsWith(".wav", StringComparison.OrdinalIgnoreCase))
+        {
+            Log += "仅支持 WAV 文件\r\n";
+            return;
+        }
+
+        try
+        {
+            Log += $"正在处理: {value}\r\n";
+
+            // 使用 NAudio 读取 WAV 文件
+            using var reader = new NAudio.Wave.AudioFileReader(value);
+            var channels = reader.WaveFormat.Channels;
+            var sampleRate = reader.WaveFormat.SampleRate;
+            var bitsPerSample = reader.WaveFormat.BitsPerSample;
+
+            Log += $"声道数: {channels}, 采样率: {sampleRate}, 位深: {bitsPerSample}\r\n";
+
+            var totalSamples = (Int32)(reader.Length / (bitsPerSample / 8));
+            var samplesPerChannel = totalSamples / channels;
+
+            // 读取所有样本
+            var allSamples = new Single[totalSamples];
+            var read = reader.Read(allSamples, 0, totalSamples);
+
+            if (read == 0)
+            {
+                Log += "未能读取音频数据\r\n";
+                return;
+            }
+
+            // 为每个声道生成频谱
+            for (var ch = 0; ch < channels; ch++)
+            {
+                // 提取该声道数据
+                var chSamples = new Single[samplesPerChannel];
+                for (var i = 0; i < samplesPerChannel; i++)
+                {
+                    chSamples[i] = allSamples[i * channels + ch];
+                }
+
+                // 生成简单的频谱图(简化版 - 用振幅数据生成 bitmap)
+                var melFile = Path.Combine(Path.GetTempPath(), $"mel_ch{ch}.png");
+                GenerateMelSpectrogram(chSamples, sampleRate, melFile);
+                _melFiles.Add(melFile);
+
+                // 生成音量曲线
+                var volFile = Path.Combine(Path.GetTempPath(), $"vol_ch{ch}.png");
+                GenerateVolumeCurve(chSamples, volFile);
+                _volFiles.Add(volFile);
+
+                ChannelList.Add($"声道 {ch + 1}");
+            }
+
+            if (ChannelList.Count > 0) SelectedChannelIndex = 0;
+
+            Log += "频谱生成完成\r\n";
+        }
+        catch (Exception ex)
+        {
+            Log += $"处理失败: {ex.Message}\r\n";
+        }
+    }
+
+    partial void OnSelectedChannelIndexChanged(Int32 value)
+    {
+        if (value < 0 || value >= _melFiles.Count) return;
+
+        MelImagePath = _melFiles[value];
+        VolumeImagePath = _volFiles[value];
+    }
+
+    private static void GenerateMelSpectrogram(Single[] samples, Int32 sampleRate, String outputPath)
+    {
+        const Int32 fftSize = 1024;
+        const Int32 hopSize = 512;
+        var width = Math.Max(1, samples.Length / hopSize);
+        var height = fftSize / 2;
+
+        using var bmp = new Bitmap(width, height);
+        for (var x = 0; x < width; x++)
+        {
+            var offset = x * hopSize;
+            if (offset + fftSize > samples.Length) break;
+
+            // 简单 FFT 幅度计算
+            var window = new Double[fftSize];
+            for (var i = 0; i < fftSize; i++)
+            {
+                if (offset + i < samples.Length)
+                {
+                    // Hann 窗
+                    var hann = 0.5 * (1 - Math.Cos(2 * Math.PI * i / (fftSize - 1)));
+                    window[i] = samples[offset + i] * hann;
+                }
+            }
+
+            // 计算幅度谱
+            for (var y = 0; y < height; y++)
+            {
+                var mag = Math.Abs(window[y]);
+                var intensity = Math.Min(255, (Int32)(mag * 500));
+                var color = Color.FromArgb(intensity, Math.Max(0, 255 - intensity * 2), Math.Max(0, 255 - intensity));
+                bmp.SetPixel(x, height - 1 - y, color);
+            }
+        }
+
+        bmp.Save(outputPath, System.Drawing.Imaging.ImageFormat.Png);
+    }
+
+    private static void GenerateVolumeCurve(Single[] samples, String outputPath)
+    {
+        const Int32 width = 800;
+        const Int32 height = 200;
+        var step = Math.Max(1, samples.Length / width);
+
+        using var bmp = new Bitmap(width, height);
+        using var g = Graphics.FromImage(bmp);
+        g.Clear(Color.White);
+
+        var pen = new Pen(Color.Blue, 1);
+        var midY = height / 2;
+
+        for (var x = 0; x < width; x++)
+        {
+            var idx = x * step;
+            if (idx >= samples.Length) break;
+
+            var amp = Math.Abs(samples[idx]);
+            var y = (Int32)(midY - amp * midY);
+            y = Math.Clamp(y, 0, height - 1);
+
+            if (x == 0)
+                bmp.SetPixel(0, y, Color.Blue);
+            else
+            {
+                var prevIdx = (x - 1) * step;
+                var prevAmp = Math.Abs(samples[Math.Min(prevIdx, samples.Length - 1)]);
+                var prevY = (Int32)(midY - prevAmp * midY);
+                prevY = Math.Clamp(prevY, 0, height - 1);
+                g.DrawLine(pen, x - 1, prevY, x, y);
+            }
+        }
+
+        bmp.Save(outputPath, System.Drawing.Imaging.ImageFormat.Png);
+    }
+    #endregion
+}
Added +238 -0
diff --git a/CrazyCoder/ViewModels/MessageDebugViewModel.cs b/CrazyCoder/ViewModels/MessageDebugViewModel.cs
new file mode 100644
index 0000000..c0fa854
--- /dev/null
+++ b/CrazyCoder/ViewModels/MessageDebugViewModel.cs
@@ -0,0 +1,238 @@
+using System.Collections.ObjectModel;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using NewLife;
+using NewLife.Data;
+using NewLife.Log;
+using NewLife.Net;
+using NewLife.Threading;
+
+namespace CrazyCoder.ViewModels;
+
+/// <summary>消息调试工具 ViewModel</summary>
+public partial class MessageDebugViewModel : ObservableObject
+{
+    #region 属性
+    private NetServer _server;
+    private ISocketClient _client;
+    private TimerX _timer;
+
+    /// <summary>工作模式列表</summary>
+    public ObservableCollection<String> Modes { get; } = ["服务端", "客户端"];
+
+    /// <summary>选中模式</summary>
+    [ObservableProperty]
+    private Int32 _selectedModeIndex;
+
+    /// <summary>地址</summary>
+    [ObservableProperty]
+    private String _address = "tcp://127.0.0.1:8888";
+
+    /// <summary>端口</summary>
+    [ObservableProperty]
+    private Int32 _port = 8888;
+
+    /// <summary>是否已连接</summary>
+    [ObservableProperty]
+    private Boolean _isConnected;
+
+    /// <summary>连接按钮文本</summary>
+    [ObservableProperty]
+    private String _connectButtonText = "打开";
+
+    /// <summary>发送内容</summary>
+    [ObservableProperty]
+    private String _sendText = "";
+
+    /// <summary>HEX 发送</summary>
+    [ObservableProperty]
+    private Boolean _hexSend;
+
+    /// <summary>接收日志</summary>
+    [ObservableProperty]
+    private String _receiveLog = "";
+
+    /// <summary>显示应用日志</summary>
+    [ObservableProperty]
+    private Boolean _showLog = true;
+
+    /// <summary>显示网络日志</summary>
+    [ObservableProperty]
+    private Boolean _showSocketLog = true;
+
+    /// <summary>显示接收字符串</summary>
+    [ObservableProperty]
+    private Boolean _showReceiveString = true;
+
+    /// <summary>显示发送数据</summary>
+    [ObservableProperty]
+    private Boolean _showSend;
+
+    /// <summary>显示接收数据</summary>
+    [ObservableProperty]
+    private Boolean _showReceive;
+
+    /// <summary>显示统计信息</summary>
+    [ObservableProperty]
+    private Boolean _showStat;
+
+    /// <summary>发送次数</summary>
+    [ObservableProperty]
+    private Int32 _sendTimes = 1;
+
+    /// <summary>发送间隔(ms)</summary>
+    [ObservableProperty]
+    private Int32 _sendSleep = 1000;
+
+    /// <summary>并发数</summary>
+    [ObservableProperty]
+    private Int32 _sendThreads = 1;
+    #endregion
+
+    #region 连接/断开
+    /// <summary>切换连接状态</summary>
+    [RelayCommand]
+    private void ToggleConnect()
+    {
+        if (IsConnected)
+            Disconnect();
+        else
+            Connect();
+    }
+
+    private void Connect()
+    {
+        _server = null;
+        _client = null;
+        _timer = null;
+
+        var uri = new NetUri(Address);
+        var log = CreateLog();
+
+        try
+        {
+            if (SelectedModeIndex == 0) // 服务端
+            {
+                var svr = new NetServer();
+                svr.Log = ShowLog ? log : Logger.Null;
+                svr.SocketLog = ShowSocketLog ? log : Logger.Null;
+                svr.Port = uri.Port;
+                if (uri.IsTcp || uri.IsUdp) svr.ProtocolType = uri.Type;
+
+                svr.LogSend = ShowSend;
+                svr.LogReceive = ShowReceive;
+
+                svr.Received += OnReceived;
+                svr.Start();
+
+                AppendLog($"正在监听 {svr.Port}");
+                _server = svr;
+            }
+            else // 客户端
+            {
+                var client = uri.CreateRemote();
+                client.Log = ShowLog ? log : Logger.Null;
+
+                client.LogSend = ShowSend;
+                client.LogReceive = ShowReceive;
+
+                client.Received += OnReceived;
+                client.Open();
+
+                AppendLog($"已连接服务器 {uri}");
+                _client = client;
+            }
+
+            IsConnected = true;
+            ConnectButtonText = "关闭";
+            _timer = new TimerX(RefreshStat, null, 5000, 5000) { Async = true };
+        }
+        catch (Exception ex)
+        {
+            AppendLog($"连接失败: {ex.Message}");
+        }
+    }
+
+    private void Disconnect()
+    {
+        _client?.Dispose();
+        _client = null;
+
+        if (_server != null)
+        {
+            _server.Dispose();
+            _server = null;
+        }
+
+        _timer?.Dispose();
+        _timer = null;
+
+        IsConnected = false;
+        ConnectButtonText = "打开";
+        AppendLog("已断开连接");
+    }
+
+    private void OnReceived(Object sender, ReceivedEventArgs e)
+    {
+        if (ShowReceiveString && e.Packet != null)
+        {
+            var line = e.Packet.ToStr();
+            AppendLog(line);
+        }
+    }
+
+    /// <summary>发送消息</summary>
+    [RelayCommand]
+    private void Send()
+    {
+        var str = SendText;
+        if (str.IsNullOrEmpty()) return;
+
+        var buf = HexSend ? str.ToHex() : str.GetBytes();
+        var pk = new Packet(buf);
+
+        if (_client != null)
+        {
+            try
+            {
+                for (var i = 0; i < SendTimes; i++)
+                {
+                    _client.Send(pk);
+                    if (i < SendTimes - 1 && SendSleep > 0)
+                        Thread.Sleep(SendSleep);
+                }
+                AppendLog($"发送: {str}");
+            }
+            catch (Exception ex)
+            {
+                AppendLog($"发送失败: {ex.Message}");
+            }
+        }
+        else
+        {
+            AppendLog("客户端未连接");
+        }
+    }
+
+    /// <summary>清空日志</summary>
+    [RelayCommand]
+    private void ClearLog()
+    {
+        ReceiveLog = "";
+    }
+
+    private void RefreshStat(Object state)
+    {
+    }
+
+    private void AppendLog(String msg)
+    {
+        ReceiveLog += $"{msg}\r\n";
+    }
+
+    private ILog CreateLog()
+    {
+        return new SimpleLog { WriteAction = msg => AppendLog(msg) };
+    }
+    #endregion
+}
Modified +272 -0
diff --git a/CrazyCoder/ViewModels/SecurityViewModel.cs b/CrazyCoder/ViewModels/SecurityViewModel.cs
index 7a805f5..67fe95e 100644
--- a/CrazyCoder/ViewModels/SecurityViewModel.cs
+++ b/CrazyCoder/ViewModels/SecurityViewModel.cs
@@ -772,6 +772,278 @@ public partial class SecurityViewModel : ObservableObject
 
         SetResult(rs.ToArray());
     }
+
+    #region MD5破解
+    /// <summary>MD5 长度</summary>
+    [ObservableProperty]
+    private Int32 _md5Length = 4;
+
+    /// <summary>包含数字</summary>
+    [ObservableProperty]
+    private Boolean _md5UseNumber = true;
+
+    /// <summary>包含小写字母</summary>
+    [ObservableProperty]
+    private Boolean _md5UseLower = true;
+
+    /// <summary>包含大写字母</summary>
+    [ObservableProperty]
+    private Boolean _md5UseUpper;
+
+    /// <summary>MD5 破解最大尝试数</summary>
+    [ObservableProperty]
+    private Int32 _md5MaxAttempts = 1000000;
+
+    /// <summary>MD5 破解</summary>
+    [RelayCommand]
+    private async void Md5Crack()
+    {
+        var targetHash = SourceText.Trim().ToUpper();
+        if (targetHash.Length != 32 && targetHash.Length != 16)
+        {
+            ResultText = "请输入有效的 MD5 哈希(16 或 32 位 HEX)";
+            return;
+        }
+
+        // 构建可用字符集
+        var chars = new StringBuilder();
+        if (Md5UseNumber) chars.Append("0123456789");
+        if (Md5UseLower) chars.Append("abcdefghijklmnopqrstuvwxyz");
+        if (Md5UseUpper) chars.Append("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+
+        if (chars.Length == 0)
+        {
+            ResultText = "请至少选择一种字符集";
+            return;
+        }
+
+        var charSet = chars.ToString();
+        var length = Md5Length;
+        var maxAttempts = Md5MaxAttempts;
+        var found = "";
+        var attempted = 0;
+
+        SourceText = $"开始 MD5 破解: 长度={length}, 字符集大小={charSet.Length}, 最大尝试={maxAttempts}";
+
+        await Task.Run(() =>
+        {
+            var indices = new Int32[length];
+            var total = (Int32)Math.Pow(charSet.Length, length);
+            var maxCheck = Math.Min(total, maxAttempts);
+
+            for (var i = 0; i < maxCheck; i++)
+            {
+                // 构建当前字符串
+                var sb = Pool.StringBuilder.Get();
+                for (var j = 0; j < length; j++)
+                {
+                    sb.Append(charSet[indices[j]]);
+                }
+                var guess = sb.Return(true);
+
+                // 计算 MD5
+                var hash = guess.GetBytes().MD5().ToHex().ToUpper();
+                if (targetHash.Length == 16)
+                    hash = hash[..16];
+
+                if (hash == targetHash)
+                {
+                    found = guess;
+                    return;
+                }
+
+                attempted = i + 1;
+
+                // 递增索引
+                for (var j = length - 1; j >= 0; j--)
+                {
+                    indices[j]++;
+                    if (indices[j] < charSet.Length) break;
+                    indices[j] = 0;
+                }
+            }
+        });
+
+        if (!found.IsNullOrEmpty())
+            SetResult($"破解成功!原文: {found}", $"尝试次数: {attempted}");
+        else
+            SetResult($"破解失败,已尝试 {attempted} 次", $"MD5: {targetHash}");
+    }
+    #endregion
+
+    #region 对称加密
+    /// <summary>加密算法(AES/DES)</summary>
+    [ObservableProperty]
+    private String _symmetricAlgorithm = "AES";
+
+    /// <summary>密钥大小</summary>
+    [ObservableProperty]
+    private Int32 _symmetricKeySize = 256;
+
+    /// <summary>AES 加密</summary>
+    [RelayCommand]
+    private void AesEncrypt()
+    {
+        var buf = GetSource();
+        var pass = PassText;
+
+        if (pass.IsNullOrEmpty())
+        {
+            SetResult("密码不能为空");
+            return;
+        }
+
+        try
+        {
+            var key = pass.GetBytes();
+            using var aes = Aes.Create();
+            aes.Key = AdjustKey(key, aes.KeySize / 8);
+            aes.Mode = CipherMode.CBC;
+            aes.Padding = PaddingMode.PKCS7;
+            aes.GenerateIV();
+
+            using var encryptor = aes.CreateEncryptor();
+            var result = encryptor.TransformFinalBlock(buf, 0, buf.Length);
+            var iv = aes.IV;
+
+            // 返回 IV + 密文
+            var output = new Byte[iv.Length + result.Length];
+            Buffer.BlockCopy(iv, 0, output, 0, iv.Length);
+            Buffer.BlockCopy(result, 0, output, iv.Length, result.Length);
+
+            SetResult(output);
+        }
+        catch (Exception ex)
+        {
+            SetResult($"加密失败: {ex.Message}");
+        }
+    }
+
+    /// <summary>AES 解密</summary>
+    [RelayCommand]
+    private void AesDecrypt()
+    {
+        var buf = GetSource();
+        var pass = PassText;
+
+        if (pass.IsNullOrEmpty())
+        {
+            SetResult("密码不能为空");
+            return;
+        }
+
+        try
+        {
+            var key = pass.GetBytes();
+            using var aes = Aes.Create();
+            aes.Key = AdjustKey(key, aes.KeySize / 8);
+
+            // 前16字节是IV
+            var iv = buf[..16];
+            var cipher = buf[16..];
+
+            aes.IV = iv;
+            aes.Mode = CipherMode.CBC;
+            aes.Padding = PaddingMode.PKCS7;
+
+            using var decryptor = aes.CreateDecryptor();
+            var result = decryptor.TransformFinalBlock(cipher, 0, cipher.Length);
+
+            SetResult(result);
+        }
+        catch (Exception ex)
+        {
+            SetResult($"解密失败: {ex.Message}");
+        }
+    }
+
+    /// <summary>DES 加密</summary>
+    [RelayCommand]
+    private void DesEncrypt()
+    {
+        var buf = GetSource();
+        var pass = PassText;
+
+        if (pass.IsNullOrEmpty())
+        {
+            SetResult("密码不能为空");
+            return;
+        }
+
+        try
+        {
+            var key = pass.GetBytes();
+            using var des = DES.Create();
+            des.Key = AdjustKey(key, des.KeySize / 8);
+            des.Mode = CipherMode.CBC;
+            des.Padding = PaddingMode.PKCS7;
+            des.GenerateIV();
+
+            using var encryptor = des.CreateEncryptor();
+            var result = encryptor.TransformFinalBlock(buf, 0, buf.Length);
+            var iv = des.IV;
+
+            var output = new Byte[iv.Length + result.Length];
+            Buffer.BlockCopy(iv, 0, output, 0, iv.Length);
+            Buffer.BlockCopy(result, 0, output, iv.Length, result.Length);
+
+            SetResult(output);
+        }
+        catch (Exception ex)
+        {
+            SetResult($"加密失败: {ex.Message}");
+        }
+    }
+
+    /// <summary>DES 解密</summary>
+    [RelayCommand]
+    private void DesDecrypt()
+    {
+        var buf = GetSource();
+        var pass = PassText;
+
+        if (pass.IsNullOrEmpty())
+        {
+            SetResult("密码不能为空");
+            return;
+        }
+
+        try
+        {
+            var key = pass.GetBytes();
+            using var des = DES.Create();
+            des.Key = AdjustKey(key, des.KeySize / 8);
+
+            var iv = buf[..8];
+            var cipher = buf[8..];
+
+            des.IV = iv;
+            des.Mode = CipherMode.CBC;
+            des.Padding = PaddingMode.PKCS7;
+
+            using var decryptor = des.CreateDecryptor();
+            var result = decryptor.TransformFinalBlock(cipher, 0, cipher.Length);
+
+            SetResult(result);
+        }
+        catch (Exception ex)
+        {
+            SetResult($"解密失败: {ex.Message}");
+        }
+    }
+
+    private static Byte[] AdjustKey(Byte[] key, Int32 targetSize)
+    {
+        if (key.Length == targetSize) return key;
+
+        var result = new Byte[targetSize];
+        if (key.Length > targetSize)
+            Buffer.BlockCopy(key, 0, result, 0, targetSize);
+        else
+            Buffer.BlockCopy(key, 0, result, 0, key.Length);
+        return result;
+    }
+    #endregion
     #endregion
 
     #region Modbus_CRC
Added +142 -0
diff --git a/CrazyCoder/ViewModels/SshViewModel.cs b/CrazyCoder/ViewModels/SshViewModel.cs
new file mode 100644
index 0000000..66feb9b
--- /dev/null
+++ b/CrazyCoder/ViewModels/SshViewModel.cs
@@ -0,0 +1,142 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using NewLife;
+using NewLife.Net;
+using Renci.SshNet;
+
+namespace CrazyCoder.ViewModels;
+
+/// <summary>SSH 工具 ViewModel</summary>
+public partial class SshViewModel : ObservableObject
+{
+    #region 属性
+    private SshClient _client;
+
+    /// <summary>远程地址</summary>
+    [ObservableProperty]
+    private String _remoteAddress = "";
+
+    /// <summary>用户名</summary>
+    [ObservableProperty]
+    private String _userName = "root";
+
+    /// <summary>密码</summary>
+    [ObservableProperty]
+    private String _password = "";
+
+    /// <summary>端口</summary>
+    [ObservableProperty]
+    private Int32 _port = 22;
+
+    /// <summary>是否已连接</summary>
+    [ObservableProperty]
+    private Boolean _isConnected;
+
+    /// <summary>连接按钮文本</summary>
+    [ObservableProperty]
+    private String _connectButtonText = "打开";
+
+    /// <summary>发送命令文本</summary>
+    [ObservableProperty]
+    private String _sendText = "";
+
+    /// <summary>接收日志</summary>
+    [ObservableProperty]
+    private String _receiveLog = "";
+    #endregion
+
+    #region 连接/断开
+    /// <summary>切换连接状态</summary>
+    [RelayCommand]
+    private void ToggleConnect()
+    {
+        if (IsConnected)
+            Disconnect();
+        else
+            Connect();
+    }
+
+    private void Connect()
+    {
+        _client?.Dispose();
+        _client = null;
+
+        var remote = RemoteAddress;
+        if (remote.IsNullOrEmpty()) return;
+
+        var uri = new NetUri(remote);
+        if (uri.Type == NetType.Unknown) uri.Type = NetType.Tcp;
+        if (uri.Port == 0) uri.Port = Port;
+
+        try
+        {
+            var client = new SshClient(uri.Host ?? (uri.Address + ""), uri.Port, UserName, Password);
+            client.ErrorOccurred += (_, e) => AppendLog($"错误: {e.Exception.Message}");
+            client.Connect();
+
+            _client = client;
+            IsConnected = true;
+            ConnectButtonText = "关闭";
+            AppendLog($"已连接到 {uri.Host}:{uri.Port}");
+        }
+        catch (Exception ex)
+        {
+            AppendLog($"连接失败: {ex.Message}");
+        }
+    }
+
+    private void Disconnect()
+    {
+        if (_client != null)
+        {
+            _client.Dispose();
+            _client = null;
+        }
+
+        IsConnected = false;
+        ConnectButtonText = "打开";
+        AppendLog("已断开连接");
+    }
+
+    /// <summary>发送命令</summary>
+    [RelayCommand]
+    private void Send()
+    {
+        var str = SendText;
+        if (str.IsNullOrEmpty()) return;
+
+        if (_client == null || !_client.IsConnected)
+        {
+            AppendLog("未连接到服务器");
+            return;
+        }
+
+        try
+        {
+            AppendLog($"> {str}");
+            var rs = _client.RunCommand(str);
+            if (rs != null)
+            {
+                if (!rs.Result.IsNullOrEmpty()) AppendLog(rs.Result);
+                if (!rs.Error.IsNullOrEmpty()) AppendLog($"错误: {rs.Error}");
+            }
+        }
+        catch (Exception ex)
+        {
+            AppendLog($"命令执行失败: {ex.Message}");
+        }
+    }
+
+    /// <summary>清空接收日志</summary>
+    [RelayCommand]
+    private void ClearLog()
+    {
+        ReceiveLog = "";
+    }
+
+    private void AppendLog(String msg)
+    {
+        ReceiveLog += msg + "\r\n";
+    }
+    #endregion
+}
Added +241 -0
diff --git a/CrazyCoder/ViewModels/UsbDeviceViewModel.cs b/CrazyCoder/ViewModels/UsbDeviceViewModel.cs
new file mode 100644
index 0000000..522e705
--- /dev/null
+++ b/CrazyCoder/ViewModels/UsbDeviceViewModel.cs
@@ -0,0 +1,241 @@
+using System.Collections.ObjectModel;
+using System.Management;
+using System.Text;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using NewLife;
+
+namespace CrazyCoder.ViewModels;
+
+/// <summary>USB 设备信息</summary>
+public class UsbDeviceInfo
+{
+    /// <summary>端口号</summary>
+    public Int32 Port { get; set; }
+
+    /// <summary>设备名称</summary>
+    public String Name { get; set; } = "";
+
+    /// <summary>容器 ID</summary>
+    public String ContainerId { get; set; } = "";
+
+    /// <summary>VID</summary>
+    public String VID { get; set; } = "";
+
+    /// <summary>PID</summary>
+    public String PID { get; set; } = "";
+
+    /// <summary>子设备</summary>
+    public String SubDevices { get; set; } = "";
+
+    /// <summary>Hub 编号</summary>
+    public Int32 Hub { get; set; }
+}
+
+/// <summary>USB 设备检测 ViewModel</summary>
+public partial class UsbDeviceViewModel : ObservableObject
+{
+    #region 属性
+    /// <summary>Hub 列表</summary>
+    public ObservableCollection<String> HubList { get; } = [];
+
+    /// <summary>选中的 Hub</summary>
+    [ObservableProperty]
+    private Int32 _selectedHubIndex;
+
+    /// <summary>设备信息</summary>
+    [ObservableProperty]
+    private String _deviceInfo = "";
+
+    /// <summary>日志输出</summary>
+    [ObservableProperty]
+    private String _log = "";
+
+    private Dictionary<Int32, List<UsbDeviceInfo>> _hubs = [];
+    #endregion
+
+    #region 构造
+    /// <summary>实例化 USB 设备检测 ViewModel</summary>
+    public UsbDeviceViewModel()
+    {
+        RefreshDevices();
+    }
+    #endregion
+
+    #region 方法
+    /// <summary>刷新设备列表</summary>
+    [RelayCommand]
+    private void RefreshDevices()
+    {
+        HubList.Clear();
+        _hubs.Clear();
+
+        try
+        {
+            var roots = new Dictionary<Int32, List<UsbDeviceInfo>>();
+
+            using (var searcher = new ManagementObjectSearcher(@"SELECT * FROM Win32_PnPEntity WHERE PNPDeviceID LIKE 'USB%'"))
+            {
+                var devices = searcher.Get();
+                foreach (var device in devices)
+                {
+                    var name = device["Name"]?.ToString();
+                    var pnpId = device["PNPDeviceID"]?.ToString();
+                    if (name == null || pnpId == null) continue;
+
+                    if (pnpId.IndexOf("&MI_", StringComparison.Ordinal) >= 0) continue;
+
+                    var location = GetLocationInformation(pnpId);
+                    if (location == null) continue;
+
+                    var phead = "Port_#";
+                    var hhead = "Hub_#";
+                    if (location.IndexOf(phead, StringComparison.Ordinal) < 0) continue;
+
+                    var strs = location.Split('.');
+                    var port = strs[0].TrimStart(phead).ToInt();
+                    var hub = strs[1].TrimStart(hhead).ToInt();
+                    var cid = GetContainerId(pnpId);
+                    var (vid, pid) = GetVidPid(pnpId);
+
+                    var dev = new UsbDeviceInfo
+                    {
+                        Port = port,
+                        Name = name,
+                        ContainerId = cid,
+                        VID = vid,
+                        PID = pid,
+                        Hub = hub
+                    };
+
+                    if (roots.ContainsKey(hub))
+                        roots[hub].Add(dev);
+                    else
+                        roots[hub] = [dev];
+                }
+
+                // 处理复合设备
+                foreach (var device in devices)
+                {
+                    var name = device["Name"]?.ToString();
+                    var pnpId = device["PNPDeviceID"]?.ToString();
+                    if (name == null || pnpId == null) continue;
+
+                    if (pnpId.IndexOf("&MI_", StringComparison.Ordinal) < 0) continue;
+
+                    var cid = GetContainerId(pnpId);
+
+                    UsbDeviceInfo? parent = null;
+                    foreach (var kv in roots)
+                    {
+                        parent = kv.Value.FirstOrDefault(d => d.ContainerId == cid);
+                        if (parent != null) break;
+                    }
+
+                    if (parent == null) continue;
+
+                    if (parent.SubDevices.IsNullOrEmpty())
+                        parent.SubDevices = name;
+                    else
+                        parent.SubDevices += " + " + name;
+                }
+            }
+
+            // 排序
+            foreach (var kv in roots)
+            {
+                kv.Value.Sort((x, y) => x.Port.CompareTo(y.Port));
+            }
+
+            _hubs = roots;
+
+            foreach (var kv in roots.OrderBy(k => k.Key))
+            {
+                HubList.Add($"Hub_{kv.Key}");
+            }
+
+            if (HubList.Count > 0) SelectedHubIndex = 0;
+
+            AppendLog($"刷新完成,找到 {_hubs.Count} 个 HUB");
+        }
+        catch (Exception ex)
+        {
+            AppendLog($"刷新失败: {ex.Message}");
+        }
+    }
+
+    partial void OnSelectedHubIndexChanged(Int32 value)
+    {
+        if (value < 0 || value >= HubList.Count) return;
+
+        var hubText = HubList[value];
+        var hub = hubText.TrimStart("Hub_").ToInt();
+
+        if (!_hubs.TryGetValue(hub, out var devs)) return;
+
+        var sb = new StringBuilder();
+        foreach (var dev in devs)
+        {
+            sb.AppendLine($"端口 {dev.Port,2}  VID_{dev.VID} PID_{dev.PID}  {dev.ContainerId}");
+            if (!dev.SubDevices.IsNullOrEmpty())
+                sb.AppendLine($"         {dev.SubDevices}");
+            else
+                sb.AppendLine($"         {dev.Name}");
+        }
+
+        DeviceInfo = sb.ToString();
+    }
+
+    private static String GetLocationInformation(String pnpId)
+    {
+        try
+        {
+            var path = $@"SYSTEM\CurrentControlSet\Enum\{pnpId}\Device Parameters";
+            using var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(path);
+            if (key != null)
+            {
+                var val = key.GetValue("LocationInformation");
+                if (val != null) return val.ToString();
+            }
+            return "";
+        }
+        catch
+        {
+            return "";
+        }
+    }
+
+    private static String GetContainerId(String pnpId)
+    {
+        try
+        {
+            var path = $@"SYSTEM\CurrentControlSet\Enum\{pnpId}";
+            using var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(path);
+            return key?.GetValue("ContainerId")?.ToString() ?? "";
+        }
+        catch
+        {
+            return "";
+        }
+    }
+
+    private static (String vid, String pid) GetVidPid(String pnpId)
+    {
+        var vid = "";
+        var pid = "";
+
+        var p = pnpId.IndexOf("VID_", StringComparison.Ordinal);
+        if (p >= 0) vid = pnpId.Substring(p + 4, 4);
+
+        p = pnpId.IndexOf("PID_", StringComparison.Ordinal);
+        if (p >= 0) pid = pnpId.Substring(p + 4, 4);
+
+        return (vid, pid);
+    }
+
+    private void AppendLog(String msg)
+    {
+        Log += $"{msg}\r\n";
+    }
+    #endregion
+}
Added +175 -0
diff --git a/CrazyCoder/Views/ApiDebugWindow.xaml b/CrazyCoder/Views/ApiDebugWindow.xaml
new file mode 100644
index 0000000..7978140
--- /dev/null
+++ b/CrazyCoder/Views/ApiDebugWindow.xaml
@@ -0,0 +1,175 @@
+<Window x:Class="CrazyCoder.Views.ApiDebugWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        mc:Ignorable="d"
+        Title="API 调试工具" Height="750" Width="1000" WindowStartupLocation="CenterScreen">
+    <Window.Resources>
+        <Style x:Key="GroupBorder" TargetType="Border">
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="Margin" Value="0,0,0,6"/>
+        </Style>
+        <Style x:Key="SectionTitle" TargetType="TextBlock">
+            <Setter Property="FontWeight" Value="Bold"/>
+            <Setter Property="Margin" Value="4,3"/>
+            <Setter Property="FontSize" Value="13"/>
+        </Style>
+        <Style x:Key="ConfigLabel" TargetType="TextBlock">
+            <Setter Property="VerticalAlignment" Value="Center"/>
+            <Setter Property="Margin" Value="4,2"/>
+        </Style>
+        <Style x:Key="ConfigTextBox" TargetType="TextBox">
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Height" Value="28"/>
+            <Setter Property="VerticalContentAlignment" Value="Center"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+        </Style>
+        <Style x:Key="ActionButton" TargetType="Button">
+            <Setter Property="Height" Value="32"/>
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Padding" Value="16,0"/>
+        </Style>
+        <Style x:Key="SendButton" TargetType="Button">
+            <Setter Property="Height" Value="50"/>
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="FontSize" Value="14"/>
+            <Setter Property="FontWeight" Value="Bold"/>
+        </Style>
+        <Style x:Key="LogTextBox" TargetType="TextBox">
+            <Setter Property="FontFamily" Value="Consolas"/>
+            <Setter Property="FontSize" Value="12"/>
+            <Setter Property="IsReadOnly" Value="True"/>
+            <Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
+            <Setter Property="HorizontalScrollBarVisibility" Value="Auto"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="TextWrapping" Value="Wrap"/>
+        </Style>
+        <Style x:Key="OptionCheckBox" TargetType="CheckBox">
+            <Setter Property="Margin" Value="4,2"/>
+        </Style>
+    </Window.Resources>
+
+    <Grid Margin="8">
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="280"/>
+            <ColumnDefinition Width="*"/>
+        </Grid.ColumnDefinitions>
+
+        <!-- 左侧配置 -->
+        <ScrollViewer Grid.Column="0" VerticalScrollBarVisibility="Auto" Margin="0,0,4,0">
+            <StackPanel>
+                <Border Style="{StaticResource GroupBorder}">
+                    <StackPanel>
+                        <TextBlock Text="工作模式" Style="{StaticResource SectionTitle}"/>
+                        <ComboBox ItemsSource="{Binding Modes}" SelectedIndex="{Binding SelectedModeIndex}"
+                                  Margin="4,2" Height="30" IsEnabled="{Binding IsConnected, Converter={x:Null}}"/>
+                    </StackPanel>
+                </Border>
+
+                <Border Style="{StaticResource GroupBorder}">
+                    <StackPanel>
+                        <TextBlock Text="连接配置" Style="{StaticResource SectionTitle}"/>
+                        <Grid>
+                            <Grid.RowDefinitions>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition Height="Auto"/>
+                            </Grid.RowDefinitions>
+                            <Grid.ColumnDefinitions>
+                                <ColumnDefinition Width="40"/>
+                                <ColumnDefinition Width="*"/>
+                                <ColumnDefinition Width="Auto"/>
+                            </Grid.ColumnDefinitions>
+                            <TextBlock Grid.Row="0" Grid.Column="0" Text="地址" Style="{StaticResource ConfigLabel}"/>
+                            <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Address, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+                            <TextBlock Grid.Row="1" Grid.Column="0" Text="端口" Style="{StaticResource ConfigLabel}"/>
+                            <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Port, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+                            <Button Grid.Row="1" Grid.Column="2" Content="{Binding ConnectButtonText}"
+                                    Command="{Binding ToggleConnectCommand}" Width="80" Margin="4,2" Height="28"/>
+                        </Grid>
+                    </StackPanel>
+                </Border>
+
+                <Border Style="{StaticResource GroupBorder}">
+                    <StackPanel>
+                        <TextBlock Text="日志配置" Style="{StaticResource SectionTitle}"/>
+                        <CheckBox Content="显示应用日志" IsChecked="{Binding ShowLog}" Style="{StaticResource OptionCheckBox}"/>
+                        <CheckBox Content="显示编码日志" IsChecked="{Binding ShowEncoderLog}" Style="{StaticResource OptionCheckBox}"/>
+                        <CheckBox Content="显示发送数据" IsChecked="{Binding ShowSend}" Style="{StaticResource OptionCheckBox}"/>
+                        <CheckBox Content="显示接收数据" IsChecked="{Binding ShowReceive}" Style="{StaticResource OptionCheckBox}"/>
+                        <CheckBox Content="显示统计信息" IsChecked="{Binding ShowStat}" Style="{StaticResource OptionCheckBox}"/>
+                    </StackPanel>
+                </Border>
+
+                <Border Style="{StaticResource GroupBorder}">
+                    <StackPanel>
+                        <TextBlock Text="发送配置" Style="{StaticResource SectionTitle}"/>
+                        <Grid Margin="4,2">
+                            <Grid.RowDefinitions>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition Height="Auto"/>
+                            </Grid.RowDefinitions>
+                            <Grid.ColumnDefinitions>
+                                <ColumnDefinition Width="Auto"/>
+                                <ColumnDefinition Width="*"/>
+                            </Grid.ColumnDefinitions>
+                            <TextBlock Grid.Row="0" Grid.Column="0" Text="次数" Style="{StaticResource ConfigLabel}"/>
+                            <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding SendTimes, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+                            <TextBlock Grid.Row="1" Grid.Column="0" Text="间隔(ms)" Style="{StaticResource ConfigLabel}"/>
+                            <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding SendSleep, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+                            <TextBlock Grid.Row="2" Grid.Column="0" Text="并发" Style="{StaticResource ConfigLabel}"/>
+                            <TextBox Grid.Row="2" Grid.Column="1" Text="{Binding SendThreads, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+                        </Grid>
+                    </StackPanel>
+                </Border>
+            </StackPanel>
+        </ScrollViewer>
+
+        <!-- 右侧日志区域 -->
+        <Grid Grid.Column="1">
+            <Grid.RowDefinitions>
+                <RowDefinition Height="3*"/>
+                <RowDefinition Height="2*"/>
+            </Grid.RowDefinitions>
+
+            <!-- 接收日志 -->
+            <Border Grid.Row="0" Style="{StaticResource GroupBorder}">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                    </Grid.RowDefinitions>
+                    <TextBlock Text="接收日志" Style="{StaticResource SectionTitle}"/>
+                    <ComboBox Grid.Row="1" ItemsSource="{Binding ApiActions}" SelectedItem="{Binding SelectedAction}"
+                              Margin="4,0" Height="28" Visibility="{Binding IsConnected, Converter={x:Null}}"/>
+                    <TextBox Grid.Row="2" Text="{Binding ReceiveLog, Mode=OneWay}" Style="{StaticResource LogTextBox}" Margin="2"/>
+                </Grid>
+            </Border>
+
+            <!-- 发送区 -->
+            <Border Grid.Row="1" Style="{StaticResource GroupBorder}">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                        <RowDefinition Height="Auto"/>
+                    </Grid.RowDefinitions>
+                    <TextBlock Text="发送内容" Style="{StaticResource SectionTitle}"/>
+                    <TextBox Grid.Row="1" Text="{Binding SendText, UpdateSourceTrigger=PropertyChanged}"
+                             AcceptsReturn="True" VerticalScrollBarVisibility="Auto"
+                             FontFamily="Consolas" FontSize="13"
+                             BorderThickness="1" BorderBrush="#D0D0D0" Margin="2"/>
+                    <StackPanel Grid.Row="2" Orientation="Horizontal" Margin="4,2">
+                        <Button Content="发送" Command="{Binding SendCommand}" Style="{StaticResource SendButton}" Width="100" Background="#4CAF50" Foreground="White"/>
+                        <Button Content="清空日志" Command="{Binding ClearLogCommand}" Style="{StaticResource ActionButton}" Margin="6,2"/>
+                    </StackPanel>
+                </Grid>
+            </Border>
+        </Grid>
+    </Grid>
+</Window>
Added +16 -0
diff --git a/CrazyCoder/Views/ApiDebugWindow.xaml.cs b/CrazyCoder/Views/ApiDebugWindow.xaml.cs
new file mode 100644
index 0000000..80d2cc4
--- /dev/null
+++ b/CrazyCoder/Views/ApiDebugWindow.xaml.cs
@@ -0,0 +1,16 @@
+using System.Windows;
+using CrazyCoder.ViewModels;
+
+namespace CrazyCoder.Views;
+
+/// <summary>API 调试工具窗口</summary>
+public partial class ApiDebugWindow : Window
+{
+    /// <summary>实例化 API 调试工具窗口</summary>
+    public ApiDebugWindow()
+    {
+        InitializeComponent();
+
+        DataContext = new ApiDebugViewModel();
+    }
+}
Added +102 -0
diff --git a/CrazyCoder/Views/ApiDiscoverWindow.xaml b/CrazyCoder/Views/ApiDiscoverWindow.xaml
new file mode 100644
index 0000000..4acd767
--- /dev/null
+++ b/CrazyCoder/Views/ApiDiscoverWindow.xaml
@@ -0,0 +1,102 @@
+<Window x:Class="CrazyCoder.Views.ApiDiscoverWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        mc:Ignorable="d"
+        Title="API 服务发现" Height="600" Width="800" WindowStartupLocation="CenterScreen">
+    <Window.Resources>
+        <Style x:Key="GroupBorder" TargetType="Border">
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="Margin" Value="0,0,0,6"/>
+        </Style>
+        <Style x:Key="SectionTitle" TargetType="TextBlock">
+            <Setter Property="FontWeight" Value="Bold"/>
+            <Setter Property="Margin" Value="4,3"/>
+            <Setter Property="FontSize" Value="13"/>
+        </Style>
+        <Style x:Key="ConfigLabel" TargetType="TextBlock">
+            <Setter Property="VerticalAlignment" Value="Center"/>
+            <Setter Property="Margin" Value="4,2"/>
+        </Style>
+        <Style x:Key="ActionButton" TargetType="Button">
+            <Setter Property="Height" Value="32"/>
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Padding" Value="16,0"/>
+        </Style>
+        <Style x:Key="LogTextBox" TargetType="TextBox">
+            <Setter Property="FontFamily" Value="Consolas"/>
+            <Setter Property="FontSize" Value="12"/>
+            <Setter Property="IsReadOnly" Value="True"/>
+            <Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
+            <Setter Property="HorizontalScrollBarVisibility" Value="Auto"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="TextWrapping" Value="Wrap"/>
+        </Style>
+    </Window.Resources>
+
+    <Grid Margin="8">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="*"/>
+            <RowDefinition Height="Auto"/>
+        </Grid.RowDefinitions>
+
+        <!-- 扫描配置 -->
+        <Border Grid.Row="0" Style="{StaticResource GroupBorder}">
+            <StackPanel>
+                <TextBlock Text="API 服务发现" Style="{StaticResource SectionTitle}"/>
+                <Grid Margin="4,0,4,4">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="Auto"/>
+                        <ColumnDefinition Width="100"/>
+                        <ColumnDefinition Width="Auto"/>
+                        <ColumnDefinition Width="Auto"/>
+                    </Grid.ColumnDefinitions>
+                    <TextBlock Grid.Column="0" Text="端口" VerticalAlignment="Center" Margin="4,2"/>
+                    <TextBox Grid.Column="1" Text="{Binding Port, UpdateSourceTrigger=PropertyChanged}" Margin="2" Height="28" VerticalContentAlignment="Center"/>
+                    <Button Grid.Column="2" Content="扫描" Command="{Binding ScanCommand}"
+                            Style="{StaticResource ActionButton}" Width="100" Background="#2196F3" Foreground="White"/>
+                    <Button Grid.Column="3" Content="清空列表" Command="{Binding ClearServicesCommand}"
+                            Style="{StaticResource ActionButton}" Width="80"/>
+                </Grid>
+            </StackPanel>
+        </Border>
+
+        <!-- 服务列表 -->
+        <Border Grid.Row="1" Style="{StaticResource GroupBorder}">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+                <TextBlock Text="发现的服务" Style="{StaticResource SectionTitle}"/>
+                <ListView Grid.Row="1" ItemsSource="{Binding Services}" Margin="2">
+                    <ListView.View>
+                        <GridView>
+                            <GridViewColumn Header="名称" Width="120" DisplayMemberBinding="{Binding Name}"/>
+                            <GridViewColumn Header="IP" Width="120" DisplayMemberBinding="{Binding RemoteIP}"/>
+                            <GridViewColumn Header="端口" Width="80" DisplayMemberBinding="{Binding Port}"/>
+                            <GridViewColumn Header="ID" Width="80" DisplayMemberBinding="{Binding Id}"/>
+                            <GridViewColumn Header="版本" Width="100" DisplayMemberBinding="{Binding Version}"/>
+                        </GridView>
+                    </ListView.View>
+                </ListView>
+            </Grid>
+        </Border>
+
+        <!-- 日志 -->
+        <Border Grid.Row="2" Style="{StaticResource GroupBorder}" MaxHeight="150">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+                <TextBlock Text="日志" Style="{StaticResource SectionTitle}"/>
+                <TextBox Grid.Row="1" Text="{Binding Log, Mode=OneWay}" Style="{StaticResource LogTextBox}" Margin="2"/>
+            </Grid>
+        </Border>
+    </Grid>
+</Window>
Added +16 -0
diff --git a/CrazyCoder/Views/ApiDiscoverWindow.xaml.cs b/CrazyCoder/Views/ApiDiscoverWindow.xaml.cs
new file mode 100644
index 0000000..e1066fa
--- /dev/null
+++ b/CrazyCoder/Views/ApiDiscoverWindow.xaml.cs
@@ -0,0 +1,16 @@
+using System.Windows;
+using CrazyCoder.ViewModels;
+
+namespace CrazyCoder.Views;
+
+/// <summary>API 服务发现窗口</summary>
+public partial class ApiDiscoverWindow : Window
+{
+    /// <summary>实例化 API 服务发现窗口</summary>
+    public ApiDiscoverWindow()
+    {
+        InitializeComponent();
+
+        DataContext = new ApiDiscoverViewModel();
+    }
+}
Added +96 -0
diff --git a/CrazyCoder/Views/AudioDeviceWindow.xaml b/CrazyCoder/Views/AudioDeviceWindow.xaml
new file mode 100644
index 0000000..9c42d72
--- /dev/null
+++ b/CrazyCoder/Views/AudioDeviceWindow.xaml
@@ -0,0 +1,96 @@
+<Window x:Class="CrazyCoder.Views.AudioDeviceWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        mc:Ignorable="d"
+        Title="声卡选择器" Height="550" Width="700" WindowStartupLocation="CenterScreen">
+    <Window.Resources>
+        <Style x:Key="GroupBorder" TargetType="Border">
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="Margin" Value="0,0,0,6"/>
+        </Style>
+        <Style x:Key="SectionTitle" TargetType="TextBlock">
+            <Setter Property="FontWeight" Value="Bold"/>
+            <Setter Property="Margin" Value="4,3"/>
+            <Setter Property="FontSize" Value="13"/>
+        </Style>
+        <Style x:Key="ConfigLabel" TargetType="TextBlock">
+            <Setter Property="VerticalAlignment" Value="Center"/>
+            <Setter Property="Margin" Value="4,2"/>
+        </Style>
+        <Style x:Key="ActionButton" TargetType="Button">
+            <Setter Property="Height" Value="32"/>
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Padding" Value="16,0"/>
+        </Style>
+        <Style x:Key="InfoTextBox" TargetType="TextBox">
+            <Setter Property="FontFamily" Value="Consolas"/>
+            <Setter Property="FontSize" Value="13"/>
+            <Setter Property="IsReadOnly" Value="True"/>
+            <Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
+            <Setter Property="HorizontalScrollBarVisibility" Value="Auto"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="TextWrapping" Value="Wrap"/>
+        </Style>
+    </Window.Resources>
+
+    <Grid Margin="8">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="*"/>
+        </Grid.RowDefinitions>
+
+        <!-- 设备选择 -->
+        <Border Grid.Row="0" Style="{StaticResource GroupBorder}">
+            <StackPanel>
+                <TextBlock Text="声卡选择" Style="{StaticResource SectionTitle}"/>
+                <Grid Margin="4,0,4,4">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="*"/>
+                        <ColumnDefinition Width="Auto"/>
+                        <ColumnDefinition Width="Auto"/>
+                    </Grid.ColumnDefinitions>
+                    <ComboBox Grid.Column="0" ItemsSource="{Binding DeviceList}" SelectedIndex="{Binding SelectedDeviceIndex}"
+                              Margin="2" Height="30"/>
+                    <Button Grid.Column="1" Content="刷新" Command="{Binding RefreshDevicesCommand}"
+                            Style="{StaticResource ActionButton}" Width="80" Margin="4,2"/>
+                    <Button Grid.Column="2" Content="测试播放" Command="{Binding TestPlaybackCommand}"
+                            Style="{StaticResource ActionButton}" Width="80" Margin="4,2"/>
+                </Grid>
+            </StackPanel>
+        </Border>
+
+        <!-- 音量 -->
+        <Border Grid.Row="1" Style="{StaticResource GroupBorder}">
+            <StackPanel>
+                <TextBlock Text="音量设置" Style="{StaticResource SectionTitle}"/>
+                <Grid Margin="4,0,4,4">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="Auto"/>
+                        <ColumnDefinition Width="*"/>
+                        <ColumnDefinition Width="Auto"/>
+                    </Grid.ColumnDefinitions>
+                    <TextBlock Grid.Column="0" Text="音量" VerticalAlignment="Center" Margin="4,2"/>
+                    <Slider Grid.Column="1" Value="{Binding VolumeValue}" Minimum="0" Maximum="100" Margin="4,2"/>
+                    <TextBlock Grid.Column="2" Text="{Binding VolumeValue, StringFormat={}{0:F0}%}" VerticalAlignment="Center" Width="40"/>
+                </Grid>
+            </StackPanel>
+        </Border>
+
+        <!-- 设备详情 -->
+        <Border Grid.Row="2" Style="{StaticResource GroupBorder}">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+                <TextBlock Text="设备信息" Style="{StaticResource SectionTitle}"/>
+                <TextBox Grid.Row="1" Text="{Binding DeviceDetail, Mode=OneWay}" Style="{StaticResource InfoTextBox}" Margin="2"/>
+            </Grid>
+        </Border>
+    </Grid>
+</Window>
Added +16 -0
diff --git a/CrazyCoder/Views/AudioDeviceWindow.xaml.cs b/CrazyCoder/Views/AudioDeviceWindow.xaml.cs
new file mode 100644
index 0000000..c38aa80
--- /dev/null
+++ b/CrazyCoder/Views/AudioDeviceWindow.xaml.cs
@@ -0,0 +1,16 @@
+using System.Windows;
+using CrazyCoder.ViewModels;
+
+namespace CrazyCoder.Views;
+
+/// <summary>声卡选择器窗口</summary>
+public partial class AudioDeviceWindow : Window
+{
+    /// <summary>实例化声卡选择器窗口</summary>
+    public AudioDeviceWindow()
+    {
+        InitializeComponent();
+
+        DataContext = new AudioDeviceViewModel();
+    }
+}
Added +115 -0
diff --git a/CrazyCoder/Views/BackupWindow.xaml b/CrazyCoder/Views/BackupWindow.xaml
new file mode 100644
index 0000000..b736abc
--- /dev/null
+++ b/CrazyCoder/Views/BackupWindow.xaml
@@ -0,0 +1,115 @@
+<Window x:Class="CrazyCoder.Views.BackupWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        mc:Ignorable="d"
+        Title="手机备份" Height="650" Width="800" WindowStartupLocation="CenterScreen">
+    <Window.Resources>
+        <Style x:Key="GroupBorder" TargetType="Border">
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="Margin" Value="0,0,0,6"/>
+        </Style>
+        <Style x:Key="SectionTitle" TargetType="TextBlock">
+            <Setter Property="FontWeight" Value="Bold"/>
+            <Setter Property="Margin" Value="4,3"/>
+            <Setter Property="FontSize" Value="13"/>
+        </Style>
+        <Style x:Key="ConfigLabel" TargetType="TextBlock">
+            <Setter Property="VerticalAlignment" Value="Center"/>
+            <Setter Property="Margin" Value="4,2"/>
+        </Style>
+        <Style x:Key="ConfigTextBox" TargetType="TextBox">
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Height" Value="28"/>
+            <Setter Property="VerticalContentAlignment" Value="Center"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+        </Style>
+        <Style x:Key="ActionButton" TargetType="Button">
+            <Setter Property="Height" Value="32"/>
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Padding" Value="16,0"/>
+        </Style>
+        <Style x:Key="LogTextBox" TargetType="TextBox">
+            <Setter Property="FontFamily" Value="Consolas"/>
+            <Setter Property="FontSize" Value="12"/>
+            <Setter Property="IsReadOnly" Value="True"/>
+            <Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
+            <Setter Property="HorizontalScrollBarVisibility" Value="Auto"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="TextWrapping" Value="Wrap"/>
+        </Style>
+        <Style x:Key="OptionCheckBox" TargetType="CheckBox">
+            <Setter Property="Margin" Value="4,2"/>
+        </Style>
+    </Window.Resources>
+
+    <Grid Margin="8">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="*"/>
+        </Grid.RowDefinitions>
+
+        <!-- 配置 -->
+        <Border Grid.Row="0" Style="{StaticResource GroupBorder}">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                </Grid.RowDefinitions>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="60"/>
+                    <ColumnDefinition Width="*"/>
+                </Grid.ColumnDefinitions>
+
+                <TextBlock Grid.Row="0" Grid.Column="0" Text="目标目录" Style="{StaticResource ConfigLabel}"/>
+                <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding DestDir, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+
+                <TextBlock Grid.Row="1" Grid.Column="0" Text="源目录1" Style="{StaticResource ConfigLabel}"/>
+                <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding SrcDir1, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+
+                <TextBlock Grid.Row="2" Grid.Column="0" Text="源目录2" Style="{StaticResource ConfigLabel}"/>
+                <TextBox Grid.Row="2" Grid.Column="1" Text="{Binding SrcDir2, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+
+                <TextBlock Grid.Row="3" Grid.Column="0" Text="源目录3" Style="{StaticResource ConfigLabel}"/>
+                <TextBox Grid.Row="3" Grid.Column="1" Text="{Binding SrcDir3, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+
+                <TextBlock Grid.Row="4" Grid.Column="0" Text="源目录4" Style="{StaticResource ConfigLabel}"/>
+                <TextBox Grid.Row="4" Grid.Column="1" Text="{Binding SrcDir4, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+
+                <TextBlock Grid.Row="5" Grid.Column="0" Text="源目录5" Style="{StaticResource ConfigLabel}"/>
+                <TextBox Grid.Row="5" Grid.Column="1" Text="{Binding SrcDir5, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+
+                <CheckBox Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="2" Content="允许移动删除文件" IsChecked="{Binding AllowDelete}" Style="{StaticResource OptionCheckBox}"/>
+
+                <StackPanel Grid.Row="7" Grid.Column="0" Grid.ColumnSpan="2" Orientation="Horizontal" Margin="4,4">
+                    <Button Content="开始备份" Command="{Binding StartBackupCommand}"
+                            Style="{StaticResource ActionButton}" Width="120"
+                            Background="#4CAF50" Foreground="White" FontWeight="Bold"/>
+                </StackPanel>
+            </Grid>
+        </Border>
+
+        <!-- 日志 -->
+        <Border Grid.Row="1" Style="{StaticResource GroupBorder}">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+                <TextBlock Text="备份日志" Style="{StaticResource SectionTitle}"/>
+                <TextBox Grid.Row="1" Text="{Binding Log, Mode=OneWay}" Style="{StaticResource LogTextBox}" Margin="2"/>
+            </Grid>
+        </Border>
+    </Grid>
+</Window>
Added +16 -0
diff --git a/CrazyCoder/Views/BackupWindow.xaml.cs b/CrazyCoder/Views/BackupWindow.xaml.cs
new file mode 100644
index 0000000..6512e4d
--- /dev/null
+++ b/CrazyCoder/Views/BackupWindow.xaml.cs
@@ -0,0 +1,16 @@
+using System.Windows;
+using CrazyCoder.ViewModels;
+
+namespace CrazyCoder.Views;
+
+/// <summary>手机备份工具窗口</summary>
+public partial class BackupWindow : Window
+{
+    /// <summary>实例化手机备份工具窗口</summary>
+    public BackupWindow()
+    {
+        InitializeComponent();
+
+        DataContext = new BackupViewModel();
+    }
+}
Added +116 -0
diff --git a/CrazyCoder/Views/FileEncodingWindow.xaml b/CrazyCoder/Views/FileEncodingWindow.xaml
new file mode 100644
index 0000000..2d28ed1
--- /dev/null
+++ b/CrazyCoder/Views/FileEncodingWindow.xaml
@@ -0,0 +1,116 @@
+<Window x:Class="CrazyCoder.Views.FileEncodingWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        mc:Ignorable="d"
+        Title="文件编码转换" Height="650" Width="800" WindowStartupLocation="CenterScreen">
+    <Window.Resources>
+        <Style x:Key="GroupBorder" TargetType="Border">
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="Margin" Value="0,0,0,6"/>
+        </Style>
+        <Style x:Key="SectionTitle" TargetType="TextBlock">
+            <Setter Property="FontWeight" Value="Bold"/>
+            <Setter Property="Margin" Value="4,3"/>
+            <Setter Property="FontSize" Value="13"/>
+        </Style>
+        <Style x:Key="ConfigLabel" TargetType="TextBlock">
+            <Setter Property="VerticalAlignment" Value="Center"/>
+            <Setter Property="Margin" Value="4,2"/>
+        </Style>
+        <Style x:Key="ConfigTextBox" TargetType="TextBox">
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Height" Value="28"/>
+            <Setter Property="VerticalContentAlignment" Value="Center"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+        </Style>
+        <Style x:Key="ActionButton" TargetType="Button">
+            <Setter Property="Height" Value="32"/>
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Padding" Value="16,0"/>
+        </Style>
+        <Style x:Key="LogTextBox" TargetType="TextBox">
+            <Setter Property="FontFamily" Value="Consolas"/>
+            <Setter Property="FontSize" Value="12"/>
+            <Setter Property="IsReadOnly" Value="True"/>
+            <Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
+            <Setter Property="HorizontalScrollBarVisibility" Value="Auto"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="TextWrapping" Value="Wrap"/>
+        </Style>
+    </Window.Resources>
+
+    <Grid Margin="8">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="*"/>
+        </Grid.RowDefinitions>
+
+        <!-- 路径与编码设置 -->
+        <Border Grid.Row="0" Style="{StaticResource GroupBorder}">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                </Grid.RowDefinitions>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="70"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="Auto"/>
+                </Grid.ColumnDefinitions>
+
+                <TextBlock Grid.Row="0" Grid.Column="0" Text="文件夹" Style="{StaticResource ConfigLabel}"/>
+                <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding FolderPath, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+                <Button Grid.Row="0" Grid.Column="2" Content="浏览..." Command="{Binding BrowseFolderCommand}" Style="{StaticResource ActionButton}" Width="80" Margin="2"/>
+
+                <TextBlock Grid.Row="1" Grid.Column="0" Text="后缀" Style="{StaticResource ConfigLabel}"/>
+                <TextBox Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Text="{Binding SuffixFilter, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+
+                <TextBlock Grid.Row="2" Grid.Column="0" Text="目标编码" Style="{StaticResource ConfigLabel}"/>
+                <ComboBox Grid.Row="2" Grid.Column="1" ItemsSource="{Binding Encodings}" SelectedItem="{Binding TargetEncoding}" Height="28" Margin="2"/>
+            </Grid>
+        </Border>
+
+        <!-- 操作按钮 -->
+        <Border Grid.Row="1" Style="{StaticResource GroupBorder}">
+            <StackPanel Orientation="Horizontal" Margin="4">
+                <Button Content="扫描文件" Command="{Binding ScanCommand}" Style="{StaticResource ActionButton}" Background="#2196F3" Foreground="White" Width="100"/>
+                <Button Content="批量转换" Command="{Binding ConvertCommand}" Style="{StaticResource ActionButton}" Background="#FF9800" Foreground="White" Width="100" Margin="6,2"/>
+            </StackPanel>
+        </Border>
+
+        <!-- 扫描结果 -->
+        <Border Grid.Row="2" Style="{StaticResource GroupBorder}">
+            <Grid MinHeight="100">
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+                <TextBlock Text="扫描结果" Style="{StaticResource SectionTitle}"/>
+                <TextBox Grid.Row="1" Text="{Binding ScanResult, Mode=OneWay}"
+                         FontFamily="Consolas" FontSize="12" IsReadOnly="True"
+                         VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
+                         BorderThickness="0" Margin="2"/>
+            </Grid>
+        </Border>
+
+        <!-- 日志 -->
+        <Border Grid.Row="3" Style="{StaticResource GroupBorder}">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+                <TextBlock Text="日志" Style="{StaticResource SectionTitle}"/>
+                <TextBox Grid.Row="1" Text="{Binding Log, Mode=OneWay}" Style="{StaticResource LogTextBox}" Margin="2"/>
+            </Grid>
+        </Border>
+    </Grid>
+</Window>
Added +16 -0
diff --git a/CrazyCoder/Views/FileEncodingWindow.xaml.cs b/CrazyCoder/Views/FileEncodingWindow.xaml.cs
new file mode 100644
index 0000000..2671fa0
--- /dev/null
+++ b/CrazyCoder/Views/FileEncodingWindow.xaml.cs
@@ -0,0 +1,16 @@
+using System.Windows;
+using CrazyCoder.ViewModels;
+
+namespace CrazyCoder.Views;
+
+/// <summary>文件编码转换工具窗口</summary>
+public partial class FileEncodingWindow : Window
+{
+    /// <summary>实例化文件编码转换工具窗口</summary>
+    public FileEncodingWindow()
+    {
+        InitializeComponent();
+
+        DataContext = new FileEncodingViewModel();
+    }
+}
Added +117 -0
diff --git a/CrazyCoder/Views/IpConfigWindow.xaml b/CrazyCoder/Views/IpConfigWindow.xaml
new file mode 100644
index 0000000..f82e0e6
--- /dev/null
+++ b/CrazyCoder/Views/IpConfigWindow.xaml
@@ -0,0 +1,117 @@
+<Window x:Class="CrazyCoder.Views.IpConfigWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        mc:Ignorable="d"
+        Title="IP 设置工具" Height="650" Width="800" WindowStartupLocation="CenterScreen">
+    <Window.Resources>
+        <Style x:Key="GroupBorder" TargetType="Border">
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="Margin" Value="0,0,0,6"/>
+        </Style>
+        <Style x:Key="SectionTitle" TargetType="TextBlock">
+            <Setter Property="FontWeight" Value="Bold"/>
+            <Setter Property="Margin" Value="4,3"/>
+            <Setter Property="FontSize" Value="13"/>
+        </Style>
+        <Style x:Key="ConfigLabel" TargetType="TextBlock">
+            <Setter Property="VerticalAlignment" Value="Center"/>
+            <Setter Property="Margin" Value="4,2"/>
+        </Style>
+        <Style x:Key="ConfigTextBox" TargetType="TextBox">
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Height" Value="28"/>
+            <Setter Property="VerticalContentAlignment" Value="Center"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+        </Style>
+        <Style x:Key="ActionButton" TargetType="Button">
+            <Setter Property="Height" Value="32"/>
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Padding" Value="16,0"/>
+            <Setter Property="FontWeight" Value="Bold"/>
+        </Style>
+        <Style x:Key="LogTextBox" TargetType="TextBox">
+            <Setter Property="FontFamily" Value="Consolas"/>
+            <Setter Property="FontSize" Value="12"/>
+            <Setter Property="IsReadOnly" Value="True"/>
+            <Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
+            <Setter Property="HorizontalScrollBarVisibility" Value="Auto"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="TextWrapping" Value="Wrap"/>
+        </Style>
+    </Window.Resources>
+
+    <Grid Margin="8">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="*"/>
+        </Grid.RowDefinitions>
+
+        <!-- 适配器选择 -->
+        <Border Grid.Row="0" Style="{StaticResource GroupBorder}">
+            <StackPanel>
+                <TextBlock Text="网络适配器" Style="{StaticResource SectionTitle}"/>
+                <ComboBox ItemsSource="{Binding Adapters}" SelectedItem="{Binding SelectedAdapter}"
+                          Margin="4,2" Height="30"/>
+                <TextBlock Text="{Binding AdapterDescription}" Margin="4,2" TextWrapping="Wrap" Foreground="#666"/>
+            </StackPanel>
+        </Border>
+
+        <!-- IP 配置 -->
+        <Border Grid.Row="1" Style="{StaticResource GroupBorder}">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="60"/>
+                    <ColumnDefinition Width="*"/>
+                </Grid.ColumnDefinitions>
+
+                <TextBlock Grid.Row="0" Grid.Column="0" Text="IP" Style="{StaticResource ConfigLabel}"/>
+                <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding IpAddress, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+
+                <TextBlock Grid.Row="1" Grid.Column="0" Text="掩码" Style="{StaticResource ConfigLabel}"/>
+                <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding SubnetMask, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+
+                <TextBlock Grid.Row="2" Grid.Column="0" Text="网关" Style="{StaticResource ConfigLabel}"/>
+                <TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Gateway, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+
+                <TextBlock Grid.Row="3" Grid.Column="0" Text="DNS" Style="{StaticResource ConfigLabel}"/>
+                <TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Dns, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+
+                <TextBlock Grid.Row="4" Grid.Column="0" Text="辅助IP" Style="{StaticResource ConfigLabel}"/>
+                <TextBox Grid.Row="4" Grid.Column="1" Text="{Binding SecondaryIps, UpdateSourceTrigger=PropertyChanged}"
+                         AcceptsReturn="True" VerticalScrollBarVisibility="Auto" MinHeight="60"
+                         Margin="2" BorderThickness="1" BorderBrush="#D0D0D0"/>
+
+                <StackPanel Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="2" Orientation="Horizontal" Margin="4">
+                    <Button Content="应用设置" Command="{Binding ApplyCommand}" Style="{StaticResource ActionButton}" Background="#4CAF50" Foreground="White"/>
+                    <Button Content="恢复 DHCP" Command="{Binding RestoreCommand}" Style="{StaticResource ActionButton}" Margin="6,2" Background="#F44336" Foreground="White"/>
+                </StackPanel>
+            </Grid>
+        </Border>
+
+        <!-- 日志 -->
+        <Border Grid.Row="2" Style="{StaticResource GroupBorder}">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+                <TextBlock Text="执行日志" Style="{StaticResource SectionTitle}"/>
+                <TextBox Grid.Row="1" Text="{Binding Log, Mode=OneWay}" Style="{StaticResource LogTextBox}" Margin="2"/>
+            </Grid>
+        </Border>
+    </Grid>
+</Window>
Added +16 -0
diff --git a/CrazyCoder/Views/IpConfigWindow.xaml.cs b/CrazyCoder/Views/IpConfigWindow.xaml.cs
new file mode 100644
index 0000000..7966f57
--- /dev/null
+++ b/CrazyCoder/Views/IpConfigWindow.xaml.cs
@@ -0,0 +1,16 @@
+using System.Windows;
+using CrazyCoder.ViewModels;
+
+namespace CrazyCoder.Views;
+
+/// <summary>IP 设置工具窗口</summary>
+public partial class IpConfigWindow : Window
+{
+    /// <summary>实例化 IP 设置工具窗口</summary>
+    public IpConfigWindow()
+    {
+        InitializeComponent();
+
+        DataContext = new IpConfigViewModel();
+    }
+}
Added +111 -0
diff --git a/CrazyCoder/Views/MapWindow.xaml b/CrazyCoder/Views/MapWindow.xaml
new file mode 100644
index 0000000..a5709d5
--- /dev/null
+++ b/CrazyCoder/Views/MapWindow.xaml
@@ -0,0 +1,111 @@
+<Window x:Class="CrazyCoder.Views.MapWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        mc:Ignorable="d"
+        Title="地图接口" Height="700" Width="900" WindowStartupLocation="CenterScreen">
+    <Window.Resources>
+        <Style x:Key="GroupBorder" TargetType="Border">
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="Margin" Value="0,0,0,6"/>
+        </Style>
+        <Style x:Key="SectionTitle" TargetType="TextBlock">
+            <Setter Property="FontWeight" Value="Bold"/>
+            <Setter Property="Margin" Value="4,3"/>
+            <Setter Property="FontSize" Value="13"/>
+        </Style>
+        <Style x:Key="ConfigLabel" TargetType="TextBlock">
+            <Setter Property="VerticalAlignment" Value="Center"/>
+            <Setter Property="Margin" Value="4,2"/>
+        </Style>
+        <Style x:Key="ConfigTextBox" TargetType="TextBox">
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Height" Value="28"/>
+            <Setter Property="VerticalContentAlignment" Value="Center"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+        </Style>
+        <Style x:Key="ActionButton" TargetType="Button">
+            <Setter Property="Height" Value="32"/>
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Padding" Value="16,0"/>
+        </Style>
+        <Style x:Key="LogTextBox" TargetType="TextBox">
+            <Setter Property="FontFamily" Value="Consolas"/>
+            <Setter Property="FontSize" Value="12"/>
+            <Setter Property="IsReadOnly" Value="True"/>
+            <Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
+            <Setter Property="HorizontalScrollBarVisibility" Value="Auto"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="TextWrapping" Value="Wrap"/>
+        </Style>
+    </Window.Resources>
+
+    <Grid Margin="8">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="*"/>
+        </Grid.RowDefinitions>
+
+        <!-- 配置区域 -->
+        <Border Grid.Row="0" Style="{StaticResource GroupBorder}">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                </Grid.RowDefinitions>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="70"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="120"/>
+                </Grid.ColumnDefinitions>
+
+                <TextBlock Grid.Row="0" Grid.Column="0" Text="地图" Style="{StaticResource ConfigLabel}"/>
+                <ComboBox Grid.Row="0" Grid.Column="1" ItemsSource="{Binding MapTypes}" SelectedItem="{Binding SelectedMap}" Margin="2" Height="28"/>
+                <TextBlock Grid.Row="0" Grid.Column="2" Text="" Style="{StaticResource ConfigLabel}"/>
+
+                <TextBlock Grid.Row="1" Grid.Column="0" Text="方法" Style="{StaticResource ConfigLabel}"/>
+                <ComboBox Grid.Row="1" Grid.Column="1" ItemsSource="{Binding Methods}" SelectedItem="{Binding SelectedMethod}" Margin="2" Height="28"/>
+
+                <TextBlock Grid.Row="2" Grid.Column="0" Text="坐标" Style="{StaticResource ConfigLabel}"/>
+                <ComboBox Grid.Row="2" Grid.Column="1" ItemsSource="{Binding CoordTypes}" SelectedItem="{Binding SelectedCoordType}" Margin="2" Height="28"/>
+
+                <TextBlock Grid.Row="3" Grid.Column="0" Text="地址" Style="{StaticResource ConfigLabel}"/>
+                <TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Address, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+
+                <TextBlock Grid.Row="4" Grid.Column="0" Text="城市" Style="{StaticResource ConfigLabel}"/>
+                <TextBox Grid.Row="4" Grid.Column="1" Text="{Binding City, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+
+                <TextBlock Grid.Row="5" Grid.Column="0" Text="经纬度" Style="{StaticResource ConfigLabel}"/>
+                <TextBox Grid.Row="5" Grid.Column="1" Text="{Binding Location, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+
+                <TextBlock Grid.Row="6" Grid.Column="0" Text="经纬度2" Style="{StaticResource ConfigLabel}"/>
+                <TextBox Grid.Row="6" Grid.Column="1" Text="{Binding Location2, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+                <Button Grid.Row="6" Grid.Column="2" Content="调用接口" Command="{Binding InvokeCommand}"
+                        Style="{StaticResource ActionButton}" Background="#4CAF50" Foreground="White"/>
+            </Grid>
+        </Border>
+
+        <!-- 结果 -->
+        <Border Grid.Row="1" Style="{StaticResource GroupBorder}">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="*"/>
+                    <RowDefinition Height="Auto"/>
+                </Grid.RowDefinitions>
+                <TextBlock Text="结果" Style="{StaticResource SectionTitle}"/>
+                <TextBox Grid.Row="1" Text="{Binding ResultLog, Mode=OneWay}" Style="{StaticResource LogTextBox}" Margin="2"/>
+                <Button Grid.Row="2" Content="清空日志" Command="{Binding ClearLogCommand}" Style="{StaticResource ActionButton}" HorizontalAlignment="Left" Margin="4,2"/>
+            </Grid>
+        </Border>
+    </Grid>
+</Window>
Added +16 -0
diff --git a/CrazyCoder/Views/MapWindow.xaml.cs b/CrazyCoder/Views/MapWindow.xaml.cs
new file mode 100644
index 0000000..ed877d6
--- /dev/null
+++ b/CrazyCoder/Views/MapWindow.xaml.cs
@@ -0,0 +1,16 @@
+using System.Windows;
+using CrazyCoder.ViewModels;
+
+namespace CrazyCoder.Views;
+
+/// <summary>地图接口窗口</summary>
+public partial class MapWindow : Window
+{
+    /// <summary>实例化地图接口窗口</summary>
+    public MapWindow()
+    {
+        InitializeComponent();
+
+        DataContext = new MapViewModel();
+    }
+}
Added +99 -0
diff --git a/CrazyCoder/Views/MelSpectrumWindow.xaml b/CrazyCoder/Views/MelSpectrumWindow.xaml
new file mode 100644
index 0000000..bfc1d6e
--- /dev/null
+++ b/CrazyCoder/Views/MelSpectrumWindow.xaml
@@ -0,0 +1,99 @@
+<Window x:Class="CrazyCoder.Views.MelSpectrumWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        mc:Ignorable="d"
+        Title="音频梅尔频谱" Height="700" Width="900" WindowStartupLocation="CenterScreen">
+    <Window.Resources>
+        <Style x:Key="GroupBorder" TargetType="Border">
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="Margin" Value="0,0,0,6"/>
+        </Style>
+        <Style x:Key="SectionTitle" TargetType="TextBlock">
+            <Setter Property="FontWeight" Value="Bold"/>
+            <Setter Property="Margin" Value="4,3"/>
+            <Setter Property="FontSize" Value="13"/>
+        </Style>
+        <Style x:Key="ActionButton" TargetType="Button">
+            <Setter Property="Height" Value="32"/>
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Padding" Value="16,0"/>
+        </Style>
+        <Style x:Key="LogTextBox" TargetType="TextBox">
+            <Setter Property="FontFamily" Value="Consolas"/>
+            <Setter Property="FontSize" Value="12"/>
+            <Setter Property="IsReadOnly" Value="True"/>
+            <Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
+            <Setter Property="HorizontalScrollBarVisibility" Value="Auto"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="TextWrapping" Value="Wrap"/>
+        </Style>
+    </Window.Resources>
+
+    <Grid Margin="8">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="*"/>
+        </Grid.RowDefinitions>
+
+        <!-- 文件选择 -->
+        <Border Grid.Row="0" Style="{StaticResource GroupBorder}">
+            <StackPanel>
+                <TextBlock Text="音频文件" Style="{StaticResource SectionTitle}"/>
+                <Grid Margin="4,0,4,4">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="*"/>
+                        <ColumnDefinition Width="Auto"/>
+                    </Grid.ColumnDefinitions>
+                    <TextBox Grid.Column="0" Text="{Binding FilePath, UpdateSourceTrigger=PropertyChanged}"
+                             IsReadOnly="True" Margin="2" Height="28" VerticalContentAlignment="Center"/>
+                    <Button Grid.Column="1" Content="打开..." Command="{Binding OpenFileCommand}"
+                            Style="{StaticResource ActionButton}" Width="80" Margin="4,2"/>
+                </Grid>
+            </StackPanel>
+        </Border>
+
+        <!-- 声道选择 -->
+        <Border Grid.Row="1" Style="{StaticResource GroupBorder}">
+            <StackPanel>
+                <TextBlock Text="声道选择" Style="{StaticResource SectionTitle}"/>
+                <ComboBox ItemsSource="{Binding ChannelList}" SelectedIndex="{Binding SelectedChannelIndex}"
+                          Margin="4,0,4,4" Height="30"/>
+            </StackPanel>
+        </Border>
+
+        <!-- 频谱图 -->
+        <Grid Grid.Row="2">
+            <Grid.RowDefinitions>
+                <RowDefinition Height="*"/>
+                <RowDefinition Height="*"/>
+            </Grid.RowDefinitions>
+
+            <Border Grid.Row="0" Style="{StaticResource GroupBorder}">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                    </Grid.RowDefinitions>
+                    <TextBlock Text="梅尔频谱图" Style="{StaticResource SectionTitle}"/>
+                    <Image Grid.Row="1" Source="{Binding MelImagePath}" Stretch="Uniform" Margin="2"/>
+                </Grid>
+            </Border>
+
+            <Border Grid.Row="1" Style="{StaticResource GroupBorder}">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                    </Grid.RowDefinitions>
+                    <TextBlock Text="音量曲线" Style="{StaticResource SectionTitle}"/>
+                    <Image Grid.Row="1" Source="{Binding VolumeImagePath}" Stretch="Uniform" Margin="2"/>
+                </Grid>
+            </Border>
+        </Grid>
+    </Grid>
+</Window>
Added +16 -0
diff --git a/CrazyCoder/Views/MelSpectrumWindow.xaml.cs b/CrazyCoder/Views/MelSpectrumWindow.xaml.cs
new file mode 100644
index 0000000..6b3f58d
--- /dev/null
+++ b/CrazyCoder/Views/MelSpectrumWindow.xaml.cs
@@ -0,0 +1,16 @@
+using System.Windows;
+using CrazyCoder.ViewModels;
+
+namespace CrazyCoder.Views;
+
+/// <summary>音频梅尔频谱窗口</summary>
+public partial class MelSpectrumWindow : Window
+{
+    /// <summary>实例化音频梅尔频谱窗口</summary>
+    public MelSpectrumWindow()
+    {
+        InitializeComponent();
+
+        DataContext = new MelSpectrumViewModel();
+    }
+}
Added +174 -0
diff --git a/CrazyCoder/Views/MessageDebugWindow.xaml b/CrazyCoder/Views/MessageDebugWindow.xaml
new file mode 100644
index 0000000..9da1f11
--- /dev/null
+++ b/CrazyCoder/Views/MessageDebugWindow.xaml
@@ -0,0 +1,174 @@
+<Window x:Class="CrazyCoder.Views.MessageDebugWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        mc:Ignorable="d"
+        Title="消息调试工具" Height="750" Width="1000" WindowStartupLocation="CenterScreen">
+    <Window.Resources>
+        <Style x:Key="GroupBorder" TargetType="Border">
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="Margin" Value="0,0,0,6"/>
+        </Style>
+        <Style x:Key="SectionTitle" TargetType="TextBlock">
+            <Setter Property="FontWeight" Value="Bold"/>
+            <Setter Property="Margin" Value="4,3"/>
+            <Setter Property="FontSize" Value="13"/>
+        </Style>
+        <Style x:Key="ConfigLabel" TargetType="TextBlock">
+            <Setter Property="VerticalAlignment" Value="Center"/>
+            <Setter Property="Margin" Value="4,2"/>
+        </Style>
+        <Style x:Key="ConfigTextBox" TargetType="TextBox">
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Height" Value="28"/>
+            <Setter Property="VerticalContentAlignment" Value="Center"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+        </Style>
+        <Style x:Key="ActionButton" TargetType="Button">
+            <Setter Property="Height" Value="32"/>
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Padding" Value="16,0"/>
+        </Style>
+        <Style x:Key="SendButton" TargetType="Button">
+            <Setter Property="Height" Value="50"/>
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="FontSize" Value="14"/>
+            <Setter Property="FontWeight" Value="Bold"/>
+        </Style>
+        <Style x:Key="LogTextBox" TargetType="TextBox">
+            <Setter Property="FontFamily" Value="Consolas"/>
+            <Setter Property="FontSize" Value="12"/>
+            <Setter Property="IsReadOnly" Value="True"/>
+            <Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
+            <Setter Property="HorizontalScrollBarVisibility" Value="Auto"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="TextWrapping" Value="Wrap"/>
+        </Style>
+        <Style x:Key="OptionCheckBox" TargetType="CheckBox">
+            <Setter Property="Margin" Value="4,2"/>
+        </Style>
+    </Window.Resources>
+
+    <Grid Margin="8">
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="280"/>
+            <ColumnDefinition Width="*"/>
+        </Grid.ColumnDefinitions>
+
+        <!-- 左侧配置 -->
+        <ScrollViewer Grid.Column="0" VerticalScrollBarVisibility="Auto" Margin="0,0,4,0">
+            <StackPanel>
+                <Border Style="{StaticResource GroupBorder}">
+                    <StackPanel>
+                        <TextBlock Text="工作模式" Style="{StaticResource SectionTitle}"/>
+                        <ComboBox ItemsSource="{Binding Modes}" SelectedIndex="{Binding SelectedModeIndex}" Margin="4,2" Height="30"/>
+                        <ComboBox ItemsSource="{Binding PacketTypes}" SelectedIndex="{Binding SelectedPacketIndex}" Margin="4,2" Height="30"/>
+                    </StackPanel>
+                </Border>
+
+                <Border Style="{StaticResource GroupBorder}">
+                    <StackPanel>
+                        <TextBlock Text="连接配置" Style="{StaticResource SectionTitle}"/>
+                        <Grid>
+                            <Grid.RowDefinitions>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition Height="Auto"/>
+                            </Grid.RowDefinitions>
+                            <Grid.ColumnDefinitions>
+                                <ColumnDefinition Width="40"/>
+                                <ColumnDefinition Width="*"/>
+                                <ColumnDefinition Width="Auto"/>
+                            </Grid.ColumnDefinitions>
+                            <TextBlock Grid.Row="0" Grid.Column="0" Text="地址" Style="{StaticResource ConfigLabel}"/>
+                            <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Address, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+                            <TextBlock Grid.Row="1" Grid.Column="0" Text="端口" Style="{StaticResource ConfigLabel}"/>
+                            <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Port, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+                            <Button Grid.Row="1" Grid.Column="2" Content="{Binding ConnectButtonText}"
+                                    Command="{Binding ToggleConnectCommand}" Width="80" Margin="4,2" Height="28"/>
+                        </Grid>
+                    </StackPanel>
+                </Border>
+
+                <Border Style="{StaticResource GroupBorder}">
+                    <StackPanel>
+                        <TextBlock Text="日志配置" Style="{StaticResource SectionTitle}"/>
+                        <CheckBox Content="显示应用日志" IsChecked="{Binding ShowLog}" Style="{StaticResource OptionCheckBox}"/>
+                        <CheckBox Content="显示网络日志" IsChecked="{Binding ShowSocketLog}" Style="{StaticResource OptionCheckBox}"/>
+                        <CheckBox Content="显示接收字符串" IsChecked="{Binding ShowReceiveString}" Style="{StaticResource OptionCheckBox}"/>
+                        <CheckBox Content="显示发送数据" IsChecked="{Binding ShowSend}" Style="{StaticResource OptionCheckBox}"/>
+                        <CheckBox Content="显示接收数据" IsChecked="{Binding ShowReceive}" Style="{StaticResource OptionCheckBox}"/>
+                        <CheckBox Content="显示统计信息" IsChecked="{Binding ShowStat}" Style="{StaticResource OptionCheckBox}"/>
+                    </StackPanel>
+                </Border>
+
+                <Border Style="{StaticResource GroupBorder}">
+                    <StackPanel>
+                        <TextBlock Text="发送配置" Style="{StaticResource SectionTitle}"/>
+                        <CheckBox Content="HEX发送" IsChecked="{Binding HexSend}" Style="{StaticResource OptionCheckBox}"/>
+                        <Grid Margin="4,2">
+                            <Grid.RowDefinitions>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition Height="Auto"/>
+                            </Grid.RowDefinitions>
+                            <Grid.ColumnDefinitions>
+                                <ColumnDefinition Width="Auto"/>
+                                <ColumnDefinition Width="*"/>
+                            </Grid.ColumnDefinitions>
+                            <TextBlock Grid.Row="0" Grid.Column="0" Text="次数" Style="{StaticResource ConfigLabel}"/>
+                            <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding SendTimes, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+                            <TextBlock Grid.Row="1" Grid.Column="0" Text="间隔(ms)" Style="{StaticResource ConfigLabel}"/>
+                            <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding SendSleep, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+                            <TextBlock Grid.Row="2" Grid.Column="0" Text="并发" Style="{StaticResource ConfigLabel}"/>
+                            <TextBox Grid.Row="2" Grid.Column="1" Text="{Binding SendThreads, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+                        </Grid>
+                    </StackPanel>
+                </Border>
+            </StackPanel>
+        </ScrollViewer>
+
+        <!-- 右侧日志区域 -->
+        <Grid Grid.Column="1">
+            <Grid.RowDefinitions>
+                <RowDefinition Height="3*"/>
+                <RowDefinition Height="2*"/>
+            </Grid.RowDefinitions>
+
+            <!-- 接收日志 -->
+            <Border Grid.Row="0" Style="{StaticResource GroupBorder}">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                    </Grid.RowDefinitions>
+                    <TextBlock Text="接收日志" Style="{StaticResource SectionTitle}"/>
+                    <TextBox Grid.Row="1" Text="{Binding ReceiveLog, Mode=OneWay}" Style="{StaticResource LogTextBox}" Margin="2"/>
+                </Grid>
+            </Border>
+
+            <!-- 发送区 -->
+            <Border Grid.Row="1" Style="{StaticResource GroupBorder}">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                        <RowDefinition Height="Auto"/>
+                    </Grid.RowDefinitions>
+                    <TextBlock Text="发送内容" Style="{StaticResource SectionTitle}"/>
+                    <TextBox Grid.Row="1" Text="{Binding SendText, UpdateSourceTrigger=PropertyChanged}"
+                             AcceptsReturn="True" VerticalScrollBarVisibility="Auto"
+                             FontFamily="Consolas" FontSize="13"
+                             BorderThickness="1" BorderBrush="#D0D0D0" Margin="2"/>
+                    <StackPanel Grid.Row="2" Orientation="Horizontal" Margin="4,2">
+                        <Button Content="发送" Command="{Binding SendCommand}" Style="{StaticResource SendButton}" Width="100" Background="#4CAF50" Foreground="White"/>
+                        <Button Content="清空日志" Command="{Binding ClearLogCommand}" Style="{StaticResource ActionButton}" Margin="6,2"/>
+                    </StackPanel>
+                </Grid>
+            </Border>
+        </Grid>
+    </Grid>
+</Window>
Added +16 -0
diff --git a/CrazyCoder/Views/MessageDebugWindow.xaml.cs b/CrazyCoder/Views/MessageDebugWindow.xaml.cs
new file mode 100644
index 0000000..3a848ff
--- /dev/null
+++ b/CrazyCoder/Views/MessageDebugWindow.xaml.cs
@@ -0,0 +1,16 @@
+using System.Windows;
+using CrazyCoder.ViewModels;
+
+namespace CrazyCoder.Views;
+
+/// <summary>消息调试工具窗口</summary>
+public partial class MessageDebugWindow : Window
+{
+    /// <summary>实例化消息调试工具窗口</summary>
+    public MessageDebugWindow()
+    {
+        InitializeComponent();
+
+        DataContext = new MessageDebugViewModel();
+    }
+}
Modified +5 -0
diff --git a/CrazyCoder/Views/SecurityWindow.xaml b/CrazyCoder/Views/SecurityWindow.xaml
index 3d14198..e48644c 100644
--- a/CrazyCoder/Views/SecurityWindow.xaml
+++ b/CrazyCoder/Views/SecurityWindow.xaml
@@ -78,6 +78,11 @@
                     <Button Content="JWT令牌" Style="{StaticResource FuncButton}" Command="{Binding JwtTokenCommand}"/>
                     <Button Content="版本号" Style="{StaticResource FuncButton}" Command="{Binding VersionCommand}"/>
                     <Button Content="TraceId" Style="{StaticResource FuncButton}" Command="{Binding TraceIdCommand}"/>
+                    <Button Content="MD5破解" Style="{StaticResource FuncButton}" Command="{Binding Md5CrackCommand}" Background="#FFF3E0"/>
+                    <Button Content="AES加密" Style="{StaticResource FuncButton}" Command="{Binding AesEncryptCommand}" Background="#E8F5E9"/>
+                    <Button Content="AES解密" Style="{StaticResource FuncButton}" Command="{Binding AesDecryptCommand}" Background="#E8F5E9"/>
+                    <Button Content="DES加密" Style="{StaticResource FuncButton}" Command="{Binding DesEncryptCommand}" Background="#E8F5E9"/>
+                    <Button Content="DES解密" Style="{StaticResource FuncButton}" Command="{Binding DesDecryptCommand}" Background="#E8F5E9"/>
                 </WrapPanel>
             </ScrollViewer>
         </Border>
Added +128 -0
diff --git a/CrazyCoder/Views/SshWindow.xaml b/CrazyCoder/Views/SshWindow.xaml
new file mode 100644
index 0000000..d28a167
--- /dev/null
+++ b/CrazyCoder/Views/SshWindow.xaml
@@ -0,0 +1,128 @@
+<Window x:Class="CrazyCoder.Views.SshWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        mc:Ignorable="d"
+        Title="SSH 工具" Height="700" Width="900" WindowStartupLocation="CenterScreen">
+    <Window.Resources>
+        <Style x:Key="GroupBorder" TargetType="Border">
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="Margin" Value="0,0,0,6"/>
+        </Style>
+        <Style x:Key="SectionTitle" TargetType="TextBlock">
+            <Setter Property="FontWeight" Value="Bold"/>
+            <Setter Property="Margin" Value="4,3"/>
+            <Setter Property="FontSize" Value="13"/>
+        </Style>
+        <Style x:Key="ConfigLabel" TargetType="TextBlock">
+            <Setter Property="VerticalAlignment" Value="Center"/>
+            <Setter Property="Margin" Value="4,2"/>
+        </Style>
+        <Style x:Key="ConfigTextBox" TargetType="TextBox">
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Height" Value="28"/>
+            <Setter Property="VerticalContentAlignment" Value="Center"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+        </Style>
+        <Style x:Key="ActionButton" TargetType="Button">
+            <Setter Property="Height" Value="32"/>
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Padding" Value="16,0"/>
+            <Setter Property="FontWeight" Value="Bold"/>
+        </Style>
+        <Style x:Key="LogTextBox" TargetType="TextBox">
+            <Setter Property="FontFamily" Value="Consolas"/>
+            <Setter Property="FontSize" Value="12"/>
+            <Setter Property="IsReadOnly" Value="True"/>
+            <Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
+            <Setter Property="HorizontalScrollBarVisibility" Value="Auto"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="TextWrapping" Value="Wrap"/>
+        </Style>
+    </Window.Resources>
+
+    <Grid Margin="8">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="*"/>
+        </Grid.RowDefinitions>
+
+        <!-- 连接配置 -->
+        <Border Grid.Row="0" Style="{StaticResource GroupBorder}">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                </Grid.RowDefinitions>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="60"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="Auto"/>
+                </Grid.ColumnDefinitions>
+
+                <TextBlock Grid.Row="0" Grid.Column="0" Text="地址" Style="{StaticResource ConfigLabel}"/>
+                <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding RemoteAddress, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+
+                <TextBlock Grid.Row="1" Grid.Column="0" Text="端口" Style="{StaticResource ConfigLabel}"/>
+                <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Port, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+
+                <TextBlock Grid.Row="2" Grid.Column="0" Text="用户" Style="{StaticResource ConfigLabel}"/>
+                <TextBox Grid.Row="2" Grid.Column="1" Text="{Binding UserName, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+
+                <TextBlock Grid.Row="3" Grid.Column="0" Text="密码" Style="{StaticResource ConfigLabel}"/>
+                <Grid Grid.Row="3" Grid.Column="1">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="*"/>
+                        <ColumnDefinition Width="Auto"/>
+                    </Grid.ColumnDefinitions>
+                    <TextBox Grid.Column="0" Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource ConfigTextBox}"/>
+                    <Button Grid.Column="1" Content="{Binding ConnectButtonText}" Command="{Binding ToggleConnectCommand}"
+                            Style="{StaticResource ActionButton}" Width="80" Margin="4,2"/>
+                </Grid>
+            </Grid>
+        </Border>
+
+        <!-- 命令发送 -->
+        <Border Grid.Row="1" Style="{StaticResource GroupBorder}">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                </Grid.RowDefinitions>
+                <TextBlock Text="发送命令" Style="{StaticResource SectionTitle}"/>
+                <Grid Grid.Row="1" Margin="4,0,4,4">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="*"/>
+                        <ColumnDefinition Width="Auto"/>
+                        <ColumnDefinition Width="Auto"/>
+                    </Grid.ColumnDefinitions>
+                    <TextBox Grid.Column="0" Text="{Binding SendText, UpdateSourceTrigger=PropertyChanged}"
+                             Style="{StaticResource ConfigTextBox}" Height="30"/>
+                    <Button Grid.Column="1" Content="发送" Command="{Binding SendCommand}"
+                            Style="{StaticResource ActionButton}" Width="80" Margin="4,0"/>
+                    <Button Grid.Column="2" Content="清空日志" Command="{Binding ClearLogCommand}"
+                            Style="{StaticResource ActionButton}" Width="80"/>
+                </Grid>
+            </Grid>
+        </Border>
+
+        <!-- 日志 -->
+        <Border Grid.Row="2" Style="{StaticResource GroupBorder}">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+                <TextBlock Text="输出日志" Style="{StaticResource SectionTitle}"/>
+                <TextBox Grid.Row="1" Text="{Binding ReceiveLog, Mode=OneWay}" Style="{StaticResource LogTextBox}" Margin="2"/>
+            </Grid>
+        </Border>
+    </Grid>
+</Window>
Added +16 -0
diff --git a/CrazyCoder/Views/SshWindow.xaml.cs b/CrazyCoder/Views/SshWindow.xaml.cs
new file mode 100644
index 0000000..49192cf
--- /dev/null
+++ b/CrazyCoder/Views/SshWindow.xaml.cs
@@ -0,0 +1,16 @@
+using System.Windows;
+using CrazyCoder.ViewModels;
+
+namespace CrazyCoder.Views;
+
+/// <summary>SSH 工具窗口</summary>
+public partial class SshWindow : Window
+{
+    /// <summary>实例化 SSH 工具窗口</summary>
+    public SshWindow()
+    {
+        InitializeComponent();
+
+        DataContext = new SshViewModel();
+    }
+}
Added +71 -0
diff --git a/CrazyCoder/Views/UsbDeviceWindow.xaml b/CrazyCoder/Views/UsbDeviceWindow.xaml
new file mode 100644
index 0000000..8c53ecf
--- /dev/null
+++ b/CrazyCoder/Views/UsbDeviceWindow.xaml
@@ -0,0 +1,71 @@
+<Window x:Class="CrazyCoder.Views.UsbDeviceWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        mc:Ignorable="d"
+        Title="USB 设备检测" Height="600" Width="800" WindowStartupLocation="CenterScreen">
+    <Window.Resources>
+        <Style x:Key="GroupBorder" TargetType="Border">
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="Margin" Value="0,0,0,6"/>
+        </Style>
+        <Style x:Key="SectionTitle" TargetType="TextBlock">
+            <Setter Property="FontWeight" Value="Bold"/>
+            <Setter Property="Margin" Value="4,3"/>
+            <Setter Property="FontSize" Value="13"/>
+        </Style>
+        <Style x:Key="ActionButton" TargetType="Button">
+            <Setter Property="Height" Value="32"/>
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Padding" Value="16,0"/>
+        </Style>
+        <Style x:Key="InfoTextBox" TargetType="TextBox">
+            <Setter Property="FontFamily" Value="Consolas"/>
+            <Setter Property="FontSize" Value="13"/>
+            <Setter Property="IsReadOnly" Value="True"/>
+            <Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
+            <Setter Property="HorizontalScrollBarVisibility" Value="Auto"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="TextWrapping" Value="Wrap"/>
+        </Style>
+    </Window.Resources>
+
+    <Grid Margin="8">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="*"/>
+        </Grid.RowDefinitions>
+
+        <!-- Hub 选择 -->
+        <Border Grid.Row="0" Style="{StaticResource GroupBorder}">
+            <StackPanel>
+                <TextBlock Text="USB HUB 选择" Style="{StaticResource SectionTitle}"/>
+                <Grid Margin="4,0,4,4">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="*"/>
+                        <ColumnDefinition Width="Auto"/>
+                    </Grid.ColumnDefinitions>
+                    <ComboBox Grid.Column="0" ItemsSource="{Binding HubList}" SelectedIndex="{Binding SelectedHubIndex}"
+                              Margin="2" Height="30"/>
+                    <Button Grid.Column="1" Content="刷新" Command="{Binding RefreshDevicesCommand}"
+                            Style="{StaticResource ActionButton}" Width="80" Margin="4,2"/>
+                </Grid>
+            </StackPanel>
+        </Border>
+
+        <!-- 设备信息 -->
+        <Border Grid.Row="1" Style="{StaticResource GroupBorder}">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+                <TextBlock Text="设备信息" Style="{StaticResource SectionTitle}"/>
+                <TextBox Grid.Row="1" Text="{Binding DeviceInfo, Mode=OneWay}" Style="{StaticResource InfoTextBox}" Margin="2"/>
+            </Grid>
+        </Border>
+    </Grid>
+</Window>
Added +16 -0
diff --git a/CrazyCoder/Views/UsbDeviceWindow.xaml.cs b/CrazyCoder/Views/UsbDeviceWindow.xaml.cs
new file mode 100644
index 0000000..bcd5774
--- /dev/null
+++ b/CrazyCoder/Views/UsbDeviceWindow.xaml.cs
@@ -0,0 +1,16 @@
+using System.Windows;
+using CrazyCoder.ViewModels;
+
+namespace CrazyCoder.Views;
+
+/// <summary>USB 设备检测窗口</summary>
+public partial class UsbDeviceWindow : Window
+{
+    /// <summary>实例化 USB 设备检测窗口</summary>
+    public UsbDeviceWindow()
+    {
+        InitializeComponent();
+
+        DataContext = new UsbDeviceViewModel();
+    }
+}