NewLife/XCoder

feat: 批量实现 GPS辅助/图标水印/MQTT客户端/语音助手 四个工具
大石头 authored at 2026-07-14 01:41:38
3a266fe
Tree
1 Parent(s) 8d4a359
Summary: 31 changed files with 4774 additions and 77 deletions.
Modified +3 -8
Modified +31 -17
Modified +1 -1
Added +163 -0
Added +29 -0
Added +32 -0
Added +32 -0
Added +87 -0
Added +234 -0
Modified +23 -32
Added +275 -0
Added +406 -0
Added +473 -0
Added +804 -0
Added +178 -0
Added +42 -0
Added +308 -0
Added +154 -0
Added +16 -0
Added +173 -0
Added +42 -0
Added +235 -0
Added +49 -0
Added +305 -0
Added +49 -0
Modified +214 -4
Modified +60 -15
Added +196 -0
Added +16 -0
Added +128 -0
Added +16 -0
Modified +3 -8
diff --git a/CrazyCoder/App.xaml.cs b/CrazyCoder/App.xaml.cs
index a7f6fb8..4077c24 100644
--- a/CrazyCoder/App.xaml.cs
+++ b/CrazyCoder/App.xaml.cs
@@ -20,10 +20,6 @@ namespace CrazyCoder
 
         protected override void OnStartup(StartupEventArgs e)
         {
-#if NET45
-            XTrace.UseWinForm();
-#endif
-
             StartClient();
 
             StringHelper.EnableSpeechTip = Setting.Current.SpeechTip;
@@ -60,11 +56,10 @@ namespace CrazyCoder
             // 登录后保存证书
             client.OnLogined += (s, e) =>
             {
-                var inf = client.Info;
-                if (inf != null && !inf.Code.IsNullOrEmpty())
+                if (client.Logined && !client.Code.IsNullOrEmpty())
                 {
-                    set.Code = inf.Code;
-                    set.Secret = inf.Secret;
+                    set.Code = client.Code;
+                    set.Secret = client.Secret;
                     set.Save();
                 }
             };
Modified +31 -17
diff --git a/CrazyCoder/CrazyCoder.csproj b/CrazyCoder/CrazyCoder.csproj
index 86a5fe2..725a910 100644
--- a/CrazyCoder/CrazyCoder.csproj
+++ b/CrazyCoder/CrazyCoder.csproj
@@ -1,12 +1,25 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <TargetFramework>net6.0</TargetFramework>
+    <TargetFramework>net8.0-windows</TargetFramework>
     <OutputType>WinExe</OutputType>
+    <AssemblyTitle>新生命码神工具</AssemblyTitle>
+    <Description>码神工具。代码生成、网络工具、API工具、串口工具、正则工具、图标工具、加解密工具、地图接口</Description>
+    <Company>新生命开发团队</Company>
+    <Copyright>©2002-2026 NewLife</Copyright>
+    <VersionPrefix>8.2</VersionPrefix>
+    <VersionSuffix>$([System.DateTime]::Now.ToString(`yyyy.MMdd`))</VersionSuffix>
+    <Version>$(VersionPrefix).$(VersionSuffix)</Version>
+    <FileVersion>$(Version)</FileVersion>
+    <AssemblyVersion>$(VersionPrefix).*</AssemblyVersion>
     <Deterministic>false</Deterministic>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <UseWPF>true</UseWPF>
     <ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets>
-    <OutputPath>..\Bin\</OutputPath>
+    <OutputPath>..\Bin\CrazyCoder</OutputPath>
+    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <LangVersion>latest</LangVersion>
+    <NoWarn>CA1416</NoWarn>
   </PropertyGroup>
   <PropertyGroup>
     <ApplicationIcon>Images\leaf.ico</ApplicationIcon>
@@ -15,22 +28,23 @@
     <Resource Include="Fonts\iconfont.ttf" />
   </ItemGroup>
   <ItemGroup>
-    <PackageReference Include="MaterialDesignThemes">
-      <Version>4.3.0</Version>
-    </PackageReference>
+    <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
     <PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
-    <PackageReference Include="MvvmLightLibs">
-      <Version>5.4.1.1</Version>
-    </PackageReference>
-    <PackageReference Include="NewLife.Core">
-      <Version>8.11.2021.1204</Version>
-    </PackageReference>
-    <PackageReference Include="NewLife.Stardust">
-      <Version>1.5.2021.1204</Version>
-    </PackageReference>
-    <PackageReference Include="NewLife.XCode">
-      <Version>10.3.2021.1204</Version>
-    </PackageReference>
+    <PackageReference Include="NewLife.Core" Version="11.15.2026.501" />
+    <PackageReference Include="NewLife.Stardust" Version="3.7.2026.501" />
+    <PackageReference Include="NewLife.XCode" Version="11.26.2026.501" />
+    <PackageReference Include="NewLife.Net" Version="4.4.2026.206" />
+    <PackageReference Include="NewLife.Remoting" Version="3.7.2026.501" />
+    <PackageReference Include="NewLife.Security" Version="11.15.2026.501" />
+    <PackageReference Include="NewLife.MQTT" Version="3.0.2026.501" />
+    <PackageReference Include="NewLife.Redis" Version="6.5.2026.501" />
+    <PackageReference Include="NewLife.Map" Version="2.6.2026.102" />
+    <PackageReference Include="NewLife.ModbusRTU" Version="2.0.2025.701" />
+    <PackageReference Include="NAudio" Version="2.3.0" />
+    <PackageReference Include="SSH.NET" Version="2025.1.0" />
+    <PackageReference Include="System.IO.Ports" Version="10.0.7" />
+    <PackageReference Include="System.Speech" Version="10.0.7" />
+    <PackageReference Include="System.Management" Version="10.0.7" />
     <PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0" />
   </ItemGroup>
   <ItemGroup>
Modified +1 -1
diff --git a/CrazyCoder/MainWindow.xaml b/CrazyCoder/MainWindow.xaml
index 395775f..09b4e0f 100644
--- a/CrazyCoder/MainWindow.xaml
+++ b/CrazyCoder/MainWindow.xaml
@@ -131,7 +131,7 @@
                     <ListBox.ItemTemplate>
                         <DataTemplate>
                             <RadioButton Command="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type local:MainWindow}},
-                                Path=DataContext.SelectedCommand,Mode=TwoWay}" Style="{DynamicResource RadioButtonStyle}"
+                                Path=DataContext.SelectCommand,Mode=TwoWay}" Style="{DynamicResource RadioButtonStyle}"
                                          CommandParameter="{Binding}">
                                 <RadioButton.IsChecked>
                                     <Binding Path="IsSelected" RelativeSource="{RelativeSource AncestorType=ListBoxItem}" Mode="TwoWay"/>
Added +163 -0
diff --git a/CrazyCoder/Models/IconHelper.cs b/CrazyCoder/Models/IconHelper.cs
new file mode 100644
index 0000000..c206d8f
--- /dev/null
+++ b/CrazyCoder/Models/IconHelper.cs
@@ -0,0 +1,163 @@
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+
+namespace CrazyCoder.Models;
+
+/// <summary>ICO 图标文件辅助类</summary>
+public class IconHelper
+{
+    #region 静态方法
+    /// <summary>转换源图片到 ICO 文件流</summary>
+    /// <param name="bmp">源图片</param>
+    /// <param name="des">目标流</param>
+    /// <param name="sizes">尺寸数组</param>
+    /// <param name="bits">位深数组</param>
+    public static void Convert(Image bmp, Stream des, Int32[] sizes, Int32[] bits)
+    {
+        var ico = new IconHelper();
+        foreach (var bit in bits)
+        {
+            foreach (var item in sizes)
+            {
+                if (bit == 8)
+                    ico.AddBmp(bmp, item, bit);
+                else
+                    ico.AddPng(bmp, item, bit);
+            }
+        }
+        ico.Sort();
+        ico.Save(des);
+    }
+
+    /// <summary>转换源图片到 ICO 文件</summary>
+    /// <param name="srcFile">源图片路径</param>
+    /// <param name="desFile">目标 ICO 文件路径</param>
+    /// <param name="sizes">尺寸数组</param>
+    /// <param name="bits">位深数组</param>
+    public static void Convert(String srcFile, String desFile, Int32[] sizes, Int32[] bits)
+    {
+        using var bmp = new Bitmap(srcFile);
+        using var fs = File.Create(desFile);
+        Convert(bmp, fs, sizes, bits);
+    }
+    #endregion
+
+    #region 属性
+    private readonly List<IconItem> _items = [];
+    #endregion
+
+    #region 方法
+    private void AddPng(Image bmp, Int32 size, Int32 bit)
+    {
+        var ico = new Bitmap(bmp, size, size);
+
+        var ms = new MemoryStream();
+        ico.Save(ms, ImageFormat.Png);
+
+        var item = new IconItem
+        {
+            Data = ms.ToArray(),
+            Size = (UInt32)ms.Length,
+            BitCount = (UInt16)bit,
+            Width = (Byte)(size >= 256 ? 0 : size),
+            Height = (Byte)(size >= 256 ? 0 : size)
+        };
+
+        _items.Add(item);
+        ResetOffset();
+    }
+
+    private void AddBmp(Image bmp, Int32 size, Int32 bit)
+    {
+        var ico = new Bitmap(bmp, size, size);
+
+        var ms = new MemoryStream();
+        ico.Save(ms, ImageFormat.Bmp);
+
+        var item = new IconItem();
+        // BMP 跳过 14 字节文件头
+        ms.Position = 14;
+        item.Data = new Byte[ms.Length - 14];
+        ms.Read(item.Data, 0, item.Data.Length);
+        item.Size = (UInt32)item.Data.Length;
+        item.BitCount = (UInt16)bit;
+        // ICO 的高是 BMP 的 2 倍
+        var h = BitConverter.ToInt32(item.Data, 8);
+        h *= 2;
+        Buffer.BlockCopy(BitConverter.GetBytes(h), 0, item.Data, 8, 4);
+
+        item.Width = (Byte)size;
+        item.Height = (Byte)size;
+
+        _items.Add(item);
+        ResetOffset();
+    }
+
+    private void Sort()
+    {
+        _items.Sort((a, b) =>
+        {
+            var cmp = (a.Width == 0 ? 256 : a.Width).CompareTo(b.Width == 0 ? 256 : b.Width);
+            if (cmp != 0) return -cmp;
+            return -a.BitCount.CompareTo(b.BitCount);
+        });
+
+        ResetOffset();
+    }
+
+    private void Save(Stream stream)
+    {
+        var writer = new BinaryWriter(stream);
+        writer.Write((UInt16)0); // Reserved
+        writer.Write((UInt16)1); // ICO = 1
+        writer.Write((UInt16)_items.Count);
+
+        foreach (var item in _items)
+        {
+            item.Save(writer);
+        }
+        foreach (var item in _items)
+        {
+            writer.Write(item.Data);
+        }
+    }
+
+    private void ResetOffset()
+    {
+        var idx = (UInt32)(6 + _items.Count * 16);
+        foreach (var item in _items)
+        {
+            item.Offset = idx;
+            idx += item.Size;
+        }
+    }
+    #endregion
+
+    #region 内部类
+    private class IconItem
+    {
+        public Byte Width { get; set; } = 16;
+        public Byte Height { get; set; } = 16;
+        public Byte ColorCount { get; set; }
+        public Byte Reserved { get; set; }
+        public UInt16 Planes { get; set; } = 1;
+        public UInt16 BitCount { get; set; } = 32;
+        public UInt32 Size { get; set; }
+        public UInt32 Offset { get; set; }
+        public Byte[] Data { get; set; } = [];
+
+        public void Save(BinaryWriter writer)
+        {
+            writer.Write(Width);
+            writer.Write(Height);
+            writer.Write(ColorCount);
+            writer.Write(Reserved);
+            writer.Write(Planes);
+            writer.Write(BitCount);
+            writer.Write(Size);
+            writer.Write(Offset);
+        }
+    }
+    #endregion
+}
Added +29 -0
diff --git a/CrazyCoder/Models/RegexCaptureItem.cs b/CrazyCoder/Models/RegexCaptureItem.cs
new file mode 100644
index 0000000..b30febe
--- /dev/null
+++ b/CrazyCoder/Models/RegexCaptureItem.cs
@@ -0,0 +1,29 @@
+#nullable enable
+
+using System;
+using System.Text.RegularExpressions;
+
+namespace CrazyCoder.Models
+{
+    /// <summary>正则捕获结果项</summary>
+    public class RegexCaptureItem
+    {
+        /// <summary>序号</summary>
+        public Int32 Index { get; set; }
+
+        /// <summary>值</summary>
+        public String Value { get; set; } = "";
+
+        /// <summary>位置</summary>
+        public Int32 Position { get; set; }
+
+        /// <summary>长度</summary>
+        public Int32 Length { get; set; }
+
+        /// <summary>位置描述</summary>
+        public String Location => $"({Position},{Length})";
+
+        /// <summary>原始 Capture 对象</summary>
+        public Capture? Capture { get; set; }
+    }
+}
Added +32 -0
diff --git a/CrazyCoder/Models/RegexGroupItem.cs b/CrazyCoder/Models/RegexGroupItem.cs
new file mode 100644
index 0000000..48bc48d
--- /dev/null
+++ b/CrazyCoder/Models/RegexGroupItem.cs
@@ -0,0 +1,32 @@
+#nullable enable
+
+using System;
+using System.Text.RegularExpressions;
+
+namespace CrazyCoder.Models
+{
+    /// <summary>正则分组结果项</summary>
+    public class RegexGroupItem
+    {
+        /// <summary>序号</summary>
+        public Int32 Index { get; set; }
+
+        /// <summary>分组名称</summary>
+        public String Name { get; set; } = "";
+
+        /// <summary>值</summary>
+        public String Value { get; set; } = "";
+
+        /// <summary>位置</summary>
+        public Int32 Position { get; set; }
+
+        /// <summary>长度</summary>
+        public Int32 Length { get; set; }
+
+        /// <summary>位置描述</summary>
+        public String Location => $"({Position},{Length})";
+
+        /// <summary>原始 Group 对象</summary>
+        public Group? Group { get; set; }
+    }
+}
Added +32 -0
diff --git a/CrazyCoder/Models/RegexMatchItem.cs b/CrazyCoder/Models/RegexMatchItem.cs
new file mode 100644
index 0000000..e9ed1bf
--- /dev/null
+++ b/CrazyCoder/Models/RegexMatchItem.cs
@@ -0,0 +1,32 @@
+#nullable enable
+
+using System;
+using System.Text.RegularExpressions;
+
+namespace CrazyCoder.Models
+{
+    /// <summary>正则匹配结果项</summary>
+    public class RegexMatchItem
+    {
+        /// <summary>序号</summary>
+        public Int32 Index { get; set; }
+
+        /// <summary>匹配值</summary>
+        public String Value { get; set; } = "";
+
+        /// <summary>行号</summary>
+        public Int32 Line { get; set; }
+
+        /// <summary>位置</summary>
+        public Int32 Position { get; set; }
+
+        /// <summary>长度</summary>
+        public Int32 Length { get; set; }
+
+        /// <summary>位置描述</summary>
+        public String Location => $"({Line},{Position},{Length})";
+
+        /// <summary>原始 Match 对象</summary>
+        public Match? Match { get; set; }
+    }
+}
Added +87 -0
diff --git a/CrazyCoder/ViewModels/GpsViewModel.cs b/CrazyCoder/ViewModels/GpsViewModel.cs
new file mode 100644
index 0000000..7cdb4b1
--- /dev/null
+++ b/CrazyCoder/ViewModels/GpsViewModel.cs
@@ -0,0 +1,87 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using NewLife;
+
+namespace CrazyCoder.ViewModels;
+
+/// <summary>GPS 辅助工具 ViewModel</summary>
+public partial class GpsViewModel : ObservableObject
+{
+    #region 属性
+    /// <summary>HEX 纬度(8字符)</summary>
+    [ObservableProperty]
+    private String _hexLat = "";
+
+    /// <summary>HEX 经度(8字符)</summary>
+    [ObservableProperty]
+    private String _hexLong = "";
+
+    /// <summary>HEX 合并输入(16字符)</summary>
+    [ObservableProperty]
+    private String _hexCombined = "";
+
+    /// <summary>转换后纬度</summary>
+    [ObservableProperty]
+    private String _latitude = "";
+
+    /// <summary>转换后经度</summary>
+    [ObservableProperty]
+    private String _longitude = "";
+
+    /// <summary>合并坐标</summary>
+    [ObservableProperty]
+    private String _latLong = "";
+    #endregion
+
+    #region 方法
+    /// <summary>分离转换</summary>
+    [RelayCommand]
+    private void Convert()
+    {
+        var lat = HexLat?.Trim();
+        var lng = HexLong?.Trim();
+
+        if (lat.IsNullOrEmpty() || lng.IsNullOrEmpty()) return;
+
+        var vLat = BitConverter.ToSingle(lat.ToHex(), 0);
+        var vLng = BitConverter.ToSingle(lng.ToHex(), 0);
+
+        Latitude = vLat.ToString();
+        Longitude = vLng.ToString();
+        LatLong = $"{vLat},{vLng}";
+    }
+
+    /// <summary>合并转换</summary>
+    [RelayCommand]
+    private void ConvertCombined()
+    {
+        var temp = HexCombined?.Trim();
+        if (temp.IsNullOrEmpty() || temp.Length < 16) return;
+
+        var lat = temp[..8];
+        var lng = temp[8..];
+
+        HexLat = lat;
+        HexLong = lng;
+
+        var vLat = BitConverter.ToSingle(lat.ToHex(), 0);
+        var vLng = BitConverter.ToSingle(lng.ToHex(), 0);
+
+        Latitude = vLat.ToString();
+        Longitude = vLng.ToString();
+        LatLong = $"{vLat},{vLng}";
+    }
+
+    /// <summary>清空</summary>
+    [RelayCommand]
+    private void Clear()
+    {
+        HexLat = "";
+        HexLong = "";
+        HexCombined = "";
+        Latitude = "";
+        Longitude = "";
+        LatLong = "";
+    }
+    #endregion
+}
Added +234 -0
diff --git a/CrazyCoder/ViewModels/IconToolViewModel.cs b/CrazyCoder/ViewModels/IconToolViewModel.cs
new file mode 100644
index 0000000..e74ee03
--- /dev/null
+++ b/CrazyCoder/ViewModels/IconToolViewModel.cs
@@ -0,0 +1,234 @@
+#nullable enable
+using System.Collections.ObjectModel;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using CrazyCoder.Models;
+using Microsoft.Win32;
+using NewLife;
+
+namespace CrazyCoder.ViewModels;
+
+/// <summary>图标水印处理工具 ViewModel</summary>
+public partial class IconToolViewModel : ObservableObject
+{
+    #region 属性
+    /// <summary>源图片路径</summary>
+    [ObservableProperty]
+    private String _sourceFile = "";
+
+    /// <summary>源图片</summary>
+    [ObservableProperty]
+    private ImageSource? _sourceImage;
+
+    /// <summary>预览图片</summary>
+    [ObservableProperty]
+    private ImageSource? _previewImage;
+
+    /// <summary>水印文字</summary>
+    [ObservableProperty]
+    private String _watermarkText = "";
+
+    /// <summary>字体名称</summary>
+    [ObservableProperty]
+    private String _fontName = "Arial";
+
+    /// <summary>字体大小</summary>
+    [ObservableProperty]
+    private Int32 _fontSize = 96;
+
+    /// <summary>水印 X 位置</summary>
+    [ObservableProperty]
+    private Int32 _watermarkX;
+
+    /// <summary>水印 Y 位置</summary>
+    [ObservableProperty]
+    private Int32 _watermarkY;
+
+    /// <summary>水印颜色</summary>
+    [ObservableProperty]
+    private String _watermarkColor = "#FF0000";
+
+    /// <summary>可用字体列表</summary>
+    public ObservableCollection<String> FontNames { get; } = [];
+
+    /// <summary>ICO 大小选项</summary>
+    public ObservableCollection<IconSizeOption> IconSizes { get; } =
+    [
+        new() { Name = "16x16", Size = 16 },
+        new() { Name = "24x24", Size = 24 },
+        new() { Name = "32x32", Size = 32, IsSelected = true },
+        new() { Name = "48x48", Size = 48, IsSelected = true },
+        new() { Name = "64x64", Size = 64 },
+        new() { Name = "96x96", Size = 96 },
+        new() { Name = "128x128", Size = 128 },
+        new() { Name = "256x256", Size = 256 },
+    ];
+
+    private Bitmap? _originalBitmap;
+    #endregion
+
+    #region 构造
+    /// <summary>实例化图标水印处理工具 ViewModel</summary>
+    public IconToolViewModel()
+    {
+        foreach (var ft in System.Drawing.FontFamily.Families)
+        {
+            FontNames.Add(ft.Name);
+        }
+    }
+    #endregion
+
+    #region 命令
+    /// <summary>加载图片</summary>
+    [RelayCommand]
+    private void LoadImage()
+    {
+        var dlg = new OpenFileDialog
+        {
+            Filter = "图片文件|*.png;*.jpg;*.jpeg;*.bmp;*.gif;*.ico|所有文件|*.*",
+            Title = "选择图片"
+        };
+        if (dlg.ShowDialog() != true) return;
+
+        LoadFromFile(dlg.FileName);
+    }
+
+    /// <summary>从文件加载图片</summary>
+    public void LoadFromFile(String fileName)
+    {
+        SourceFile = fileName;
+
+        _originalBitmap?.Dispose();
+        _originalBitmap = new Bitmap(fileName);
+
+        SourceImage = BitmapToImageSource(_originalBitmap);
+
+        // 自动设置水印位置到右下角
+        WatermarkX = _originalBitmap.Width - 150;
+        WatermarkY = _originalBitmap.Height - 50;
+
+        MakeWater();
+    }
+
+    /// <summary>应用水印</summary>
+    [RelayCommand]
+    private void MakeWater()
+    {
+        if (_originalBitmap == null) return;
+
+        var bmp = MakeWatermarkImage();
+        PreviewImage = BitmapToImageSource(bmp);
+        bmp.Dispose();
+    }
+
+    /// <summary>保存图片</summary>
+    [RelayCommand]
+    private void SaveImage()
+    {
+        if (_originalBitmap == null) return;
+
+        var dlg = new SaveFileDialog
+        {
+            Filter = "PNG 图片|*.png|JPEG 图片|*.jpg|BMP 图片|*.bmp",
+            Title = "保存图片",
+            DefaultExt = "png"
+        };
+        if (dlg.ShowDialog() != true) return;
+
+        var bmp = MakeWatermarkImage();
+        var format = dlg.FilterIndex switch
+        {
+            2 => ImageFormat.Jpeg,
+            3 => ImageFormat.Bmp,
+            _ => ImageFormat.Png
+        };
+        bmp.Save(dlg.FileName, format);
+        bmp.Dispose();
+    }
+
+    /// <summary>生成 ICO 图标</summary>
+    [RelayCommand]
+    private void MakeIco()
+    {
+        if (_originalBitmap == null)
+        {
+            MessageBox.Show("请先加载图片!", "提示");
+            return;
+        }
+
+        var sizes = IconSizes.Where(e => e.IsSelected).Select(e => e.Size).ToArray();
+        if (sizes.Length == 0)
+        {
+            MessageBox.Show("请至少选择一个图标尺寸!", "提示");
+            return;
+        }
+
+        var dlg = new SaveFileDialog
+        {
+            Filter = "ICO 图标|*.ico",
+            Title = "保存图标",
+            DefaultExt = "ico"
+        };
+        if (dlg.ShowDialog() != true) return;
+
+        var bmp = MakeWatermarkImage();
+        var ms = new MemoryStream();
+        IconHelper.Convert(bmp, ms, sizes, [32]);
+        bmp.Dispose();
+
+        File.WriteAllBytes(dlg.FileName, ms.ToArray());
+    }
+    #endregion
+
+    #region 辅助
+    private Bitmap MakeWatermarkImage()
+    {
+        var bmp = new Bitmap(_originalBitmap!);
+
+        if (!WatermarkText.IsNullOrEmpty())
+        {
+            using var g = Graphics.FromImage(bmp);
+            using var brush = new SolidBrush(ColorTranslator.FromHtml(WatermarkColor));
+            using var ft = new System.Drawing.Font(FontName, FontSize, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Pixel);
+            g.DrawString(WatermarkText, ft, brush, WatermarkX, WatermarkY);
+        }
+
+        return bmp;
+    }
+
+    private static BitmapImage BitmapToImageSource(Bitmap bmp)
+    {
+        using var ms = new MemoryStream();
+        bmp.Save(ms, ImageFormat.Png);
+        ms.Position = 0;
+
+        var img = new BitmapImage();
+        img.BeginInit();
+        img.CacheOption = BitmapCacheOption.OnLoad;
+        img.StreamSource = ms;
+        img.EndInit();
+        img.Freeze();
+        return img;
+    }
+    #endregion
+}
+
+/// <summary>ICO 尺寸选项</summary>
+public partial class IconSizeOption : ObservableObject
+{
+    /// <summary>显示名称</summary>
+    public String Name { get; set; } = "";
+
+    /// <summary>尺寸</summary>
+    public Int32 Size { get; set; }
+
+    /// <summary>是否选中</summary>
+    [ObservableProperty]
+    private Boolean _isSelected;
+}
Modified +23 -32
diff --git a/CrazyCoder/ViewModels/MainViewModel.cs b/CrazyCoder/ViewModels/MainViewModel.cs
index 8007f39..cd454f2 100644
--- a/CrazyCoder/ViewModels/MainViewModel.cs
+++ b/CrazyCoder/ViewModels/MainViewModel.cs
@@ -1,53 +1,44 @@
 using System.Collections.ObjectModel;
 using System.Windows;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
 using CrazyCoder.Models;
 using CrazyCoder.Views;
-using GalaSoft.MvvmLight;
-using GalaSoft.MvvmLight.Command;
 using NewLife.Reflection;
 
 namespace CrazyCoder.ViewModels
 {
-    public class MainViewModel : ViewModelBase
+    public partial class MainViewModel : ObservableObject
     {
         public MainViewModel()
         {
-            Menus = new ObservableCollection<MenuModel>();
-            menus.Add(new MenuModel() { IconFont = "\xe635", Title = "数据建模", BackColor = "#218868" });
-            menus.Add(new MenuModel() { IconFont = "\xe6b6", Title = "网络工具", BackColor = "#EE3B3B" });
-            menus.Add(new MenuModel() { IconFont = "\xe6e1", Title = "RPC工具", BackColor = "#218868" });
-            menus.Add(new MenuModel() { IconFont = "\xe614", Title = "串口工具", BackColor = "#EE3B3B" });
-            menus.Add(new MenuModel() { IconFont = "\xe755", Title = "地图接口", BackColor = "#218868" });
-            menus.Add(new MenuModel() { IconFont = "\xe635", Title = "正则表达式", BackColor = "#218868", Type = typeof(RegexWindow) });
-            menus.Add(new MenuModel() { IconFont = "\xe6b6", Title = "图标水印", BackColor = "#EE3B3B" });
-            menus.Add(new MenuModel() { IconFont = "\xe6e1", Title = "加密解密", BackColor = "#218868" });
-            menus.Add(new MenuModel() { IconFont = "\xe614", Title = "语音助手", BackColor = "#EE3B3B" });
-            menus.Add(new MenuModel() { IconFont = "\xe755", Title = "文件夹统计", BackColor = "#218868" });
-            menus.Add(new MenuModel() { IconFont = "\xe635", Title = "文件编码", BackColor = "#218868" });
+            Menus =
+            [
+                new MenuModel() { IconFont = "\xe635", Title = "数据建模", BackColor = "#218868" },
+                new MenuModel() { IconFont = "\xe6b6", Title = "网络工具", BackColor = "#EE3B3B", Type = typeof(NetworkWindow) },
+                new MenuModel() { IconFont = "\xe6e1", Title = "RPC工具", BackColor = "#218868" },
+                new MenuModel() { IconFont = "\xe614", Title = "串口工具", BackColor = "#EE3B3B" },
+                new MenuModel() { IconFont = "\xe755", Title = "地图接口", BackColor = "#218868" },
+                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) },
+                new MenuModel() { IconFont = "\xe614", Title = "语音助手", BackColor = "#EE3B3B", Type = typeof(SpeechWindow) },
+                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" },
+            ];
 
             SelectedMenu = Menus[0];
-
-            SelectedCommand = new RelayCommand<MenuModel>(t => Select(t));
         }
 
-        private ObservableCollection<MenuModel> menus;
         /// <summary>菜单集合</summary>
-        public ObservableCollection<MenuModel> Menus
-        {
-            get => menus;
-            set { menus = value; RaisePropertyChanged(); }
-        }
-
-        private MenuModel selectedMenu;
-        /// <summary>选中菜单</summary>
-        public MenuModel SelectedMenu
-        {
-            get => selectedMenu;
-            set { selectedMenu = value; RaisePropertyChanged(); }
-        }
+        public ObservableCollection<MenuModel> Menus { get; }
 
-        public RelayCommand<MenuModel> SelectedCommand { get; set; }
+        [ObservableProperty]
+        private MenuModel _selectedMenu = null!;
 
+        [RelayCommand]
         private void Select(MenuModel model)
         {
             SelectedMenu = model;
Added +275 -0
diff --git a/CrazyCoder/ViewModels/MqttViewModel.cs b/CrazyCoder/ViewModels/MqttViewModel.cs
new file mode 100644
index 0000000..4358616
--- /dev/null
+++ b/CrazyCoder/ViewModels/MqttViewModel.cs
@@ -0,0 +1,275 @@
+#nullable enable
+using System.Windows;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using NewLife;
+using NewLife.Data;
+using NewLife.Log;
+using NewLife.MQTT;
+using NewLife.MQTT.Messaging;
+using NewLife.Net;
+
+namespace CrazyCoder.ViewModels;
+
+/// <summary>MQTT 客户端 ViewModel</summary>
+public partial class MqttViewModel : ObservableObject, IDisposable
+{
+    #region 属性
+    private MqttClient? _client;
+    private readonly ILog _log;
+
+    /// <summary>服务器地址</summary>
+    [ObservableProperty]
+    private String _server = "127.0.0.1:1883";
+
+    /// <summary>客户端标识</summary>
+    [ObservableProperty]
+    private String _clientId = "";
+
+    /// <summary>用户名</summary>
+    [ObservableProperty]
+    private String _userName = "";
+
+    /// <summary>密码</summary>
+    [ObservableProperty]
+    private String _password = "";
+
+    /// <summary>是否已连接</summary>
+    [ObservableProperty]
+    private Boolean _isConnected;
+
+    /// <summary>连接按钮文本</summary>
+    [ObservableProperty]
+    private String _connectButtonText = "连接";
+
+    /// <summary>订阅主题</summary>
+    [ObservableProperty]
+    private String _subscribeTopic = "";
+
+    /// <summary>订阅 QoS</summary>
+    [ObservableProperty]
+    private Int32 _subscribeQos;
+
+    /// <summary>发布主题</summary>
+    [ObservableProperty]
+    private String _publishTopic = "";
+
+    /// <summary>发布内容</summary>
+    [ObservableProperty]
+    private String _publishText = "";
+
+    /// <summary>发布 QoS</summary>
+    [ObservableProperty]
+    private Int32 _publishQos;
+
+    /// <summary>保留消息</summary>
+    [ObservableProperty]
+    private Boolean _retain;
+
+    /// <summary>QoS 选项</summary>
+    public String[] QosOptions { get; } = ["0", "1", "2"];
+
+    /// <summary>日志文本</summary>
+    [ObservableProperty]
+    private String _logText = "";
+
+    /// <summary>日志回调</summary>
+    public Action<String>? OnLog { get; set; }
+    #endregion
+
+    #region 构造
+    /// <summary>实例化 MQTT 客户端 ViewModel</summary>
+    public MqttViewModel()
+    {
+        ClientId = Environment.MachineName;
+        SubscribeTopic = "topic/test";
+        PublishTopic = "topic/test";
+        PublishText = "Hello MQTT";
+
+        _log = new MqttLog(this);
+    }
+    #endregion
+
+    #region 连接/断开
+    /// <summary>切换连接/断开</summary>
+    [RelayCommand]
+    private async Task ToggleConnect()
+    {
+        if (IsConnected)
+            await Disconnect();
+        else
+            await Connect();
+    }
+
+    private async Task Connect()
+    {
+        _client = null;
+
+        var remote = Server;
+        var uri = new NetUri(remote);
+        if (uri.Type == NetType.Unknown) uri.Type = NetType.Tcp;
+        if (uri.Port == 0) uri.Port = 1883;
+
+        var client = new MqttClient
+        {
+            Server = $"{uri}",
+            ClientId = ClientId,
+            UserName = UserName,
+            Password = Password,
+            Log = _log,
+        };
+        client.Received += OnReceived;
+        client.Connected += (s, e) => WriteLog("连接成功");
+        client.Disconnected += (s, e) => WriteLog("连接断开");
+
+        try
+        {
+            await client.ConnectAsync();
+            _client = client;
+
+            IsConnected = true;
+            ConnectButtonText = "断开";
+
+            WriteLog("已连接服务器");
+        }
+        catch (Exception ex)
+        {
+            WriteLog($"连接失败:{ex.Message}");
+            client.Dispose();
+        }
+    }
+
+    private async Task Disconnect()
+    {
+        if (_client != null)
+        {
+            _client.Reconnect = false;
+            try
+            {
+                await _client.DisconnectAsync();
+            }
+            catch (Exception ex)
+            {
+                WriteLog($"断开异常:{ex.Message}");
+            }
+            _client.Dispose();
+            _client = null;
+        }
+
+        IsConnected = false;
+        ConnectButtonText = "连接";
+        WriteLog("已断开连接");
+    }
+
+    private void OnReceived(Object? sender, EventArgs<PublishMessage> e)
+    {
+        var msg = e.Arg;
+        var payload = msg.Payload?.ToStr() ?? "";
+        WriteLog($"[{msg.Topic}] {payload}");
+    }
+    #endregion
+
+    #region 订阅/发布
+    /// <summary>订阅主题</summary>
+    [RelayCommand]
+    private async Task Subscribe()
+    {
+        if (_client == null) return;
+
+        var topic = SubscribeTopic;
+        if (topic.IsNullOrEmpty())
+        {
+            MessageBox.Show("请输入订阅主题!", "提示");
+            return;
+        }
+
+        var qos = (QualityOfService)SubscribeQos;
+
+        try
+        {
+            var rs = await _client.SubscribeAsync([topic], qos);
+            WriteLog($"订阅成功,Id={rs?.Id}");
+        }
+        catch (Exception ex)
+        {
+            WriteLog($"订阅失败:{ex.Message}");
+        }
+    }
+
+    /// <summary>发布消息</summary>
+    [RelayCommand]
+    private async Task Publish()
+    {
+        if (_client == null) return;
+
+        var topic = PublishTopic;
+        var str = PublishText;
+
+        if (topic.IsNullOrEmpty())
+        {
+            MessageBox.Show("请输入发布主题!", "提示");
+            return;
+        }
+        if (str.IsNullOrEmpty())
+        {
+            MessageBox.Show("请输入发布内容!", "提示");
+            return;
+        }
+
+        var qos = (QualityOfService)PublishQos;
+
+        try
+        {
+            WriteLog(str);
+            var rs = await _client.PublishAsync(topic, str, qos);
+            if (rs != null)
+                WriteLog($"发布成功,Id={rs.Id}");
+        }
+        catch (Exception ex)
+        {
+            WriteLog($"发布失败:{ex.Message}");
+        }
+    }
+
+    /// <summary>清空日志</summary>
+    [RelayCommand]
+    private void ClearLog()
+    {
+        LogText = "";
+        OnLog?.Invoke("__CLEAR__");
+    }
+    #endregion
+
+    #region 日志
+    private void WriteLog(String msg)
+    {
+        OnLog?.Invoke(msg);
+    }
+
+    private class MqttLog : Logger
+    {
+        private readonly MqttViewModel _vm;
+        public MqttLog(MqttViewModel vm) => _vm = vm;
+        protected override void OnWrite(LogLevel level, String format, params Object?[] args)
+        {
+            var msg = args is { Length: > 0 } ? String.Format(format, args) : format;
+            _vm.WriteLog(msg);
+        }
+    }
+    #endregion
+
+    #region IDisposable
+    /// <summary>释放资源</summary>
+    public void Dispose()
+    {
+        if (_client != null)
+        {
+            _client.Reconnect = false;
+            try { _client.DisconnectAsync().GetAwaiter().GetResult(); } catch { }
+            _client.Dispose();
+            _client = null;
+        }
+        GC.SuppressFinalize(this);
+    }
+    #endregion
+}
Added +406 -0
diff --git a/CrazyCoder/ViewModels/NetworkViewModel.cs b/CrazyCoder/ViewModels/NetworkViewModel.cs
new file mode 100644
index 0000000..527e1a3
--- /dev/null
+++ b/CrazyCoder/ViewModels/NetworkViewModel.cs
@@ -0,0 +1,406 @@
+using System.Collections.ObjectModel;
+using System.Net;
+using System.Text;
+using System.Windows;
+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>网络调试工具工作模式</summary>
+public enum NetworkWorkMode
+{
+    /// <summary>TCP/UDP混合</summary>
+    UdpTcp = 1,
+
+    /// <summary>UDP服务端</summary>
+    UdpServer,
+
+    /// <summary>UDP客户端</summary>
+    UdpClient,
+
+    /// <summary>TCP服务端</summary>
+    TcpServer,
+
+    /// <summary>TCP客户端</summary>
+    TcpClient
+}
+
+/// <summary>网络调试工具ViewModel</summary>
+public partial class NetworkViewModel : ObservableObject
+{
+    #region 属性
+    private NetServer _server;
+    private ISocketClient _client;
+    private TimerX _timer;
+
+    /// <summary>工作模式列表</summary>
+    public ObservableCollection<String> Modes { get; } =
+    [
+        "TCP/UDP混合",
+        "UDP服务端",
+        "UDP客户端",
+        "TCP服务端",
+        "TCP客户端"
+    ];
+
+    /// <summary>本地地址列表</summary>
+    public ObservableCollection<String> LocalAddresses { get; } = [];
+
+    [ObservableProperty]
+    private Int32 _selectedModeIndex;
+
+    [ObservableProperty]
+    private String _localAddress = "";
+
+    [ObservableProperty]
+    private String _remoteAddress = "";
+
+    [ObservableProperty]
+    private Int32 _port = 8080;
+
+    [ObservableProperty]
+    private Boolean _isConnected;
+
+    [ObservableProperty]
+    private String _connectButtonText = "打开";
+
+    [ObservableProperty]
+    private String _sendText = "新生命开发团队,学无先后达者为师";
+
+    [ObservableProperty]
+    private Boolean _hexSend;
+
+    [ObservableProperty]
+    private Int32 _sendTimes = 1;
+
+    [ObservableProperty]
+    private Int32 _sendSleep = 1000;
+
+    [ObservableProperty]
+    private Int32 _sendThreads = 1;
+
+    [ObservableProperty]
+    private Boolean _showLog = true;
+
+    [ObservableProperty]
+    private Boolean _showSocketLog = true;
+
+    [ObservableProperty]
+    private Boolean _showSend = true;
+
+    [ObservableProperty]
+    private Boolean _showReceive = true;
+
+    [ObservableProperty]
+    private Boolean _showStat = true;
+
+    [ObservableProperty]
+    private Boolean _showReceiveString = true;
+
+    [ObservableProperty]
+    private Int64 _receivedBytes;
+
+    [ObservableProperty]
+    private Int64 _sentBytes;
+
+    [ObservableProperty]
+    private Int32 _sessionCount;
+
+    /// <summary>日志回调,由View绑定到RichTextBox</summary>
+    public Action<String> OnLog { get; set; }
+    #endregion
+
+    #region 构造
+    /// <summary>实例化网络调试工具ViewModel</summary>
+    public NetworkViewModel()
+    {
+        LoadLocalAddresses();
+    }
+
+    private void LoadLocalAddresses()
+    {
+        LocalAddresses.Clear();
+        LocalAddresses.Add("所有本地IPv4/IPv6");
+        LocalAddresses.Add(IPAddress.Any.ToString());
+        LocalAddresses.Add(IPAddress.IPv6Any.ToString());
+
+        try
+        {
+            var host = System.Net.Dns.GetHostEntry("");
+            foreach (var addr in host.AddressList)
+            {
+                LocalAddresses.Add(addr.ToString());
+            }
+        }
+        catch
+        {
+        }
+    }
+    #endregion
+
+    #region 连接/断开
+    [RelayCommand]
+    private void ToggleConnect()
+    {
+        if (IsConnected)
+        {
+            Disconnect();
+        }
+        else
+        {
+            Connect();
+        }
+    }
+
+    private void Connect()
+    {
+        _server = null;
+        _client = null;
+
+        var mode = (NetworkWorkMode)(SelectedModeIndex + 1);
+        var local = LocalAddress;
+        var remote = RemoteAddress;
+        var port = Port;
+
+        switch (mode)
+        {
+            case NetworkWorkMode.UdpTcp:
+                _server = new NetServer();
+                break;
+            case NetworkWorkMode.UdpServer:
+                _server = new NetServer
+                {
+                    ProtocolType = NetType.Udp
+                };
+                break;
+            case NetworkWorkMode.TcpServer:
+                _server = new NetServer
+                {
+                    ProtocolType = NetType.Tcp
+                };
+                break;
+            case NetworkWorkMode.TcpClient:
+                _client = new TcpSession();
+                break;
+            case NetworkWorkMode.UdpClient:
+                _client = new UdpServer();
+                break;
+        }
+
+        if (_client != null)
+        {
+            _client.Log = ShowLog ? CreateBizLog() : Logger.Null;
+            if (!local.IsNullOrEmpty() && !local.StartsWith("所有本地"))
+                _client.Local.Host = local;
+            _client.Received += OnReceived;
+            _client.Remote.Port = port;
+            _client.Remote.Host = remote;
+
+            _client.LogSend = ShowSend;
+            _client.LogReceive = ShowReceive;
+
+            if (!_client.Open())
+            {
+                WriteLog("连接失败");
+                return;
+            }
+
+            WriteLog("已连接服务器");
+        }
+        else if (_server != null)
+        {
+            _server.Log = ShowLog ? CreateBizLog() : Logger.Null;
+            _server.SocketLog = ShowSocketLog ? CreateBizLog() : Logger.Null;
+            _server.Port = port;
+            if (!local.IsNullOrEmpty() && !local.StartsWith("所有本地"))
+                _server.Local.Host = local;
+            _server.Received += OnReceived;
+
+            _server.LogSend = ShowSend;
+            _server.LogReceive = ShowReceive;
+
+            _server.Start();
+
+            WriteLog($"正在监听 {port}");
+        }
+
+        IsConnected = true;
+        ConnectButtonText = "关闭";
+
+        _timer = new TimerX(OnShowStat, null, 5000, 5000) { Async = true };
+    }
+
+    private void Disconnect()
+    {
+        if (_client != null)
+        {
+            _client.Dispose();
+            _client = null;
+            WriteLog("关闭连接");
+        }
+
+        if (_server != null)
+        {
+            WriteLog($"停止监听 {_server.Port}");
+            _server.Dispose();
+            _server = null;
+        }
+
+        _timer?.Dispose();
+        _timer = null;
+
+        IsConnected = false;
+        ConnectButtonText = "打开";
+    }
+    #endregion
+
+    #region 收发数据
+    private String _lastStat = "";
+
+    private void OnShowStat(Object state)
+    {
+        if (!ShowStat) return;
+
+        var msg = "";
+        if (_server != null)
+            msg = _server.GetStat();
+
+        if (!msg.IsNullOrEmpty() && msg != _lastStat)
+        {
+            _lastStat = msg;
+            WriteLog(msg);
+        }
+    }
+
+    private void OnReceived(Object sender, ReceivedEventArgs e)
+    {
+        var session = sender as ISocketSession;
+        if (session == null)
+        {
+            if (sender is INetSession ns)
+                session = ns.Session;
+        }
+
+        if (session == null) return;
+
+        if (ShowReceiveString)
+        {
+            var line = e.Packet.ToStr();
+            WriteLog(line);
+        }
+
+        ReceivedBytes += e.Packet.Total;
+        SessionCount = _server?.SessionCount ?? (_client != null ? 1 : 0);
+    }
+
+    [RelayCommand]
+    private async Task Send()
+    {
+        var str = SendText;
+        if (String.IsNullOrEmpty(str))
+        {
+            MessageBox.Show("发送内容不能为空!", "网络调试");
+            return;
+        }
+
+        var count = SendTimes;
+        var sleep = SendSleep;
+        var ths = SendThreads;
+        if (count <= 0) count = 1;
+        if (sleep <= 0) sleep = 1;
+
+        // 处理换行
+        str = str.Replace("\n", "\r\n");
+        var buf = HexSend ? str.ToHex() : str.GetBytes();
+        var pk = new ArrayPacket(buf);
+
+        if (_client != null)
+        {
+            if (ths <= 1)
+            {
+                await SendHelper.SendConcurrency(_client, pk, count, sleep);
+            }
+            else
+            {
+                var any = _client.Local.Address.IsAny();
+                var list = new List<ISocketClient>();
+                for (var i = 0; i < ths; i++)
+                {
+                    var client = _client.Remote.CreateRemote();
+                    if (!any) client.Local.EndPoint = new IPEndPoint(_client.Local.Address, 2000 + i);
+                    list.Add(client);
+                }
+
+                var tasks = list.Select(c => SendHelper.SendConcurrency(c, pk, count, sleep));
+                await Task.WhenAll(tasks);
+
+                foreach (var item in list)
+                {
+                    item.TryDispose();
+                }
+            }
+
+            SentBytes += buf.Length * count;
+        }
+        else if (_server != null)
+        {
+            WriteLog($"准备向[{_server.SessionCount}]个客户端发送[{count}]次[{buf.Length}]的数据");
+            for (var i = 0; i < count && _server != null; i++)
+            {
+                var sw = System.Diagnostics.Stopwatch.StartNew();
+                var cs = await _server.SendAllAsync(pk);
+                sw.Stop();
+                WriteLog($"{i + 1}/{count} 已向[{cs}]个客户端发送[{buf.Length}]数据 {sw.ElapsedMilliseconds}ms");
+                if (sleep > 0) await Task.Delay(sleep);
+            }
+
+            SentBytes += buf.Length * count * (_server?.SessionCount ?? 0);
+        }
+    }
+
+    [RelayCommand]
+    private void ClearReceiveLog()
+    {
+        WriteLog("__CLEAR__");
+        ReceivedBytes = 0;
+    }
+    #endregion
+
+    #region 日志
+    private ILog CreateBizLog()
+    {
+        return TextFileLog.Create(null, "Net_{0:yyyy_MM_dd}.log");
+    }
+
+    /// <summary>写入日志到UI</summary>
+    /// <param name="msg">日志消息</param>
+    public void WriteLog(String msg)
+    {
+        OnLog?.Invoke(msg);
+    }
+    #endregion
+}
+
+/// <summary>异步多次发送数据</summary>
+internal static class SendHelper
+{
+    public static async Task SendConcurrency(ISocketRemote session, IPacket pk, Int32 times, Int32 msInterval)
+    {
+        await Task.Run(async () =>
+        {
+            for (var i = 0; i < times; i++)
+            {
+                session.Send(pk);
+
+                if (msInterval > 0)
+                    await Task.Delay(msInterval);
+            }
+        });
+    }
+}
Added +473 -0
diff --git a/CrazyCoder/ViewModels/RegexViewModel.cs b/CrazyCoder/ViewModels/RegexViewModel.cs
new file mode 100644
index 0000000..50d7dd0
--- /dev/null
+++ b/CrazyCoder/ViewModels/RegexViewModel.cs
@@ -0,0 +1,473 @@
+#nullable enable
+
+using System;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using System.Windows;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using CrazyCoder.Models;
+using Microsoft.Win32;
+
+namespace CrazyCoder.ViewModels
+{
+    /// <summary>正则表达式工具 ViewModel</summary>
+    public partial class RegexViewModel : ObservableObject
+    {
+        #region 属性
+
+        /// <summary>正则表达式</summary>
+        [ObservableProperty]
+        private String _pattern = "";
+
+        /// <summary>源文本</summary>
+        [ObservableProperty]
+        private String _sourceText = "";
+
+        /// <summary>替换文本</summary>
+        [ObservableProperty]
+        private String _replacement = "";
+
+        /// <summary>忽略大小写</summary>
+        [ObservableProperty]
+        private Boolean _ignoreCase;
+
+        /// <summary>多行模式</summary>
+        [ObservableProperty]
+        private Boolean _multiline;
+
+        /// <summary>单行模式</summary>
+        [ObservableProperty]
+        private Boolean _singleline;
+
+        /// <summary>忽略空白</summary>
+        [ObservableProperty]
+        private Boolean _ignorePatternWhitespace;
+
+        /// <summary>匹配模式(false 为替换模式)</summary>
+        [ObservableProperty]
+        private Boolean _isMatchMode = true;
+
+        /// <summary>当前选项文本</summary>
+        [ObservableProperty]
+        private String _optionText = "RegexOptions.None";
+
+        /// <summary>状态文本</summary>
+        [ObservableProperty]
+        private String _status = "就绪";
+
+        /// <summary>目录路径</summary>
+        [ObservableProperty]
+        private String _directoryPath = "";
+
+        /// <summary>文件过滤</summary>
+        [ObservableProperty]
+        private String _fileFilter = "*.*";
+
+        /// <summary>选中的匹配项</summary>
+        [ObservableProperty]
+        private RegexMatchItem? _selectedMatch;
+
+        /// <summary>选中的分组</summary>
+        [ObservableProperty]
+        private RegexGroupItem? _selectedGroup;
+
+        /// <summary>选中的捕获</summary>
+        [ObservableProperty]
+        private RegexCaptureItem? _selectedCapture;
+
+        /// <summary>匹配结果集合</summary>
+        public ObservableCollection<RegexMatchItem> Matches { get; } = [];
+
+        /// <summary>分组结果集合</summary>
+        public ObservableCollection<RegexGroupItem> Groups { get; } = [];
+
+        /// <summary>捕获结果集合</summary>
+        public ObservableCollection<RegexCaptureItem> Captures { get; } = [];
+
+        /// <summary>执行按钮文本</summary>
+        public String ExecuteButtonText => IsMatchMode ? "正则匹配" : "正则替换";
+
+        /// <summary>替换模式</summary>
+        public Boolean IsReplaceMode => !IsMatchMode;
+
+        /// <summary>替换面板可见性</summary>
+        public Visibility ReplacePanelVisibility => IsMatchMode ? Visibility.Collapsed : Visibility.Visible;
+
+        /// <summary>目录浏览面板可见性</summary>
+        public Visibility BatchPanelVisibility => IsMatchMode ? Visibility.Collapsed : Visibility.Visible;
+
+        /// <summary>结果面板可见性</summary>
+        public Visibility ResultPanelVisibility => IsMatchMode ? Visibility.Visible : Visibility.Collapsed;
+
+        #endregion
+
+        #region 构造
+
+        /// <summary>当前正则对象(用于分组名称查询)</summary>
+        private Regex? _regex;
+
+        #endregion
+
+        #region 选项变更
+
+        partial void OnIgnoreCaseChanged(Boolean value) => UpdateOptionText();
+        partial void OnMultilineChanged(Boolean value) => UpdateOptionText();
+        partial void OnSinglelineChanged(Boolean value) => UpdateOptionText();
+        partial void OnIgnorePatternWhitespaceChanged(Boolean value) => UpdateOptionText();
+
+        partial void OnIsMatchModeChanged(Boolean value)
+        {
+            OnPropertyChanged(nameof(ExecuteButtonText));
+            OnPropertyChanged(nameof(IsReplaceMode));
+            OnPropertyChanged(nameof(ReplacePanelVisibility));
+            OnPropertyChanged(nameof(BatchPanelVisibility));
+            OnPropertyChanged(nameof(ResultPanelVisibility));
+        }
+
+        #endregion
+
+        #region 选中项变更
+
+        partial void OnSelectedMatchChanged(RegexMatchItem? value)
+        {
+            Groups.Clear();
+            Captures.Clear();
+
+            if (value?.Match == null) return;
+
+            var reg = _regex;
+            var m = value.Match;
+            for (var i = 0; i < m.Groups.Count; i++)
+            {
+                var g = m.Groups[i];
+                var name = reg?.GroupNameFromNumber(i) ?? i.ToString();
+                Groups.Add(new RegexGroupItem
+                {
+                    Index = i,
+                    Name = name,
+                    Value = g.Value,
+                    Position = g.Index,
+                    Length = g.Length,
+                    Group = g
+                });
+            }
+        }
+
+        partial void OnSelectedGroupChanged(RegexGroupItem? value)
+        {
+            Captures.Clear();
+
+            if (value?.Group == null) return;
+
+            var g = value.Group;
+            for (var i = 0; i < g.Captures.Count; i++)
+            {
+                var c = g.Captures[i];
+                Captures.Add(new RegexCaptureItem
+                {
+                    Index = i,
+                    Value = c.Value,
+                    Position = c.Index,
+                    Length = c.Length,
+                    Capture = c
+                });
+            }
+        }
+
+        #endregion
+
+        #region 辅助方法
+
+        /// <summary>更新选项文本</summary>
+        void UpdateOptionText()
+        {
+            var sb = new StringBuilder();
+            var options = GetOptions();
+            if (options == RegexOptions.None)
+            {
+                sb.Append("RegexOptions.None");
+            }
+            else
+            {
+                if ((options & RegexOptions.IgnoreCase) != 0) AppendOption(sb, "IgnoreCase");
+                if ((options & RegexOptions.Multiline) != 0) AppendOption(sb, "Multiline");
+                if ((options & RegexOptions.Singleline) != 0) AppendOption(sb, "Singleline");
+                if ((options & RegexOptions.IgnorePatternWhitespace) != 0) AppendOption(sb, "IgnorePatternWhitespace");
+            }
+            OptionText = sb.ToString();
+        }
+
+        /// <summary>追加选项名称</summary>
+        static void AppendOption(StringBuilder sb, String name)
+        {
+            if (sb.Length > 0) sb.Append(" | ");
+            sb.Append("RegexOptions.");
+            sb.Append(name);
+        }
+
+        /// <summary>获取当前 RegexOptions</summary>
+        RegexOptions GetOptions()
+        {
+            var options = RegexOptions.None;
+            if (IgnoreCase) options |= RegexOptions.IgnoreCase;
+            if (Multiline) options |= RegexOptions.Multiline;
+            if (Singleline) options |= RegexOptions.Singleline;
+            if (IgnorePatternWhitespace) options |= RegexOptions.IgnorePatternWhitespace;
+            return options;
+        }
+
+        /// <summary>计算行号</summary>
+        static Int32 GetLineFromIndex(String text, Int32 index)
+        {
+            if (String.IsNullOrEmpty(text) || index <= 0) return 1;
+            var line = 1;
+            for (var i = 0; i < index && i < text.Length; i++)
+            {
+                if (text[i] == '\n') line++;
+            }
+            return line;
+        }
+
+        #endregion
+
+        #region 命令
+
+        /// <summary>执行匹配或替换</summary>
+        [RelayCommand]
+        async Task ExecuteAsync()
+        {
+            if (String.IsNullOrEmpty(Pattern)) return;
+            var text = SourceText;
+            if (String.IsNullOrEmpty(text)) return;
+
+            if (!IsMatchMode)
+            {
+                await ReplaceTextAsync(text);
+                return;
+            }
+
+            Status = "正在匹配...";
+            Matches.Clear();
+            Groups.Clear();
+            Captures.Clear();
+
+            var pattern = Pattern;
+            var options = GetOptions();
+            var timeout = TimeSpan.FromSeconds(5);
+
+            await Task.Run(() =>
+            {
+                try
+                {
+                    var sw = Stopwatch.StartNew();
+                    var reg = new Regex(pattern, options, timeout);
+                    _regex = reg;
+
+                    var ms = reg.Matches(text);
+                    var idx = 1;
+                    foreach (Match match in ms)
+                    {
+                        var line = GetLineFromIndex(text, match.Index);
+                        var item = new RegexMatchItem
+                        {
+                            Index = idx++,
+                            Value = match.Value,
+                            Line = line,
+                            Position = match.Index,
+                            Length = match.Length,
+                            Match = match
+                        };
+
+                        Application.Current.Dispatcher.Invoke(() =>
+                        {
+                            Matches.Add(item);
+                        });
+                    }
+
+                    sw.Stop();
+                    Application.Current.Dispatcher.Invoke(() =>
+                    {
+                        Status = $"成功匹配 {Matches.Count} 项!耗时 {sw.ElapsedMilliseconds}ms";
+                    });
+                }
+                catch (RegexMatchTimeoutException)
+                {
+                    Application.Current.Dispatcher.Invoke(() => Status = "正则匹配超时!");
+                }
+                catch (Exception ex)
+                {
+                    Application.Current.Dispatcher.Invoke(() => Status = $"错误:{ex.Message}");
+                }
+            });
+        }
+
+        /// <summary>执行替换</summary>
+        async Task ReplaceTextAsync(String text)
+        {
+            var replacement = Replacement;
+            if (String.IsNullOrEmpty(replacement))
+            {
+                Status = "请输入替换文本";
+                return;
+            }
+
+            Status = "正在替换...";
+            var pattern = Pattern;
+            var options = GetOptions();
+            var timeout = TimeSpan.FromSeconds(5);
+
+            await Task.Run(() =>
+            {
+                try
+                {
+                    var sw = Stopwatch.StartNew();
+                    var reg = new Regex(pattern, options, timeout);
+                    _regex = reg;
+
+                    var count = 0;
+                    var ms = reg.Matches(text);
+                    if (ms != null) count = ms.Count;
+
+                    if (count > 0)
+                    {
+                        var result = reg.Replace(text, replacement);
+                        Application.Current.Dispatcher.Invoke(() =>
+                        {
+                            SourceText = result;
+                        });
+                    }
+
+                    sw.Stop();
+                    Application.Current.Dispatcher.Invoke(() =>
+                    {
+                        Status = $"成功替换 {count} 项!耗时 {sw.ElapsedMilliseconds}ms";
+                    });
+                }
+                catch (RegexMatchTimeoutException)
+                {
+                    Application.Current.Dispatcher.Invoke(() => Status = "正则替换超时!");
+                }
+                catch (Exception ex)
+                {
+                    Application.Current.Dispatcher.Invoke(() => Status = $"错误:{ex.Message}");
+                }
+            });
+        }
+
+        /// <summary>浏览目录</summary>
+        [RelayCommand]
+        void BrowseDirectory()
+        {
+            var dialog = new OpenFolderDialog
+            {
+                Title = "选择目录"
+            };
+
+            if (dialog.ShowDialog() == true)
+            {
+                DirectoryPath = dialog.FolderName;
+            }
+        }
+
+        /// <summary>批量替换</summary>
+        [RelayCommand]
+        async Task BatchReplaceAsync()
+        {
+            if (String.IsNullOrEmpty(Pattern))
+            {
+                Status = "请输入正则表达式";
+                return;
+            }
+            var replacement = Replacement;
+            if (String.IsNullOrEmpty(replacement))
+            {
+                Status = "请输入替换文本";
+                return;
+            }
+
+            var path = DirectoryPath;
+            if (String.IsNullOrEmpty(path) || !Directory.Exists(path))
+            {
+                Status = "请先选择有效目录";
+                return;
+            }
+
+            var filter = FileFilter;
+            if (String.IsNullOrEmpty(filter)) filter = "*.*";
+
+            Status = "正在批量替换...";
+            var pattern = Pattern;
+            var options = GetOptions();
+
+            await Task.Run(() =>
+            {
+                try
+                {
+                    var sw = Stopwatch.StartNew();
+                    var reg = new Regex(pattern, options, TimeSpan.FromSeconds(5));
+
+                    var files = Directory.GetFiles(path, filter, SearchOption.AllDirectories);
+                    if (files == null || files.Length == 0)
+                    {
+                        Application.Current.Dispatcher.Invoke(() => Status = "没有符合条件的文件");
+                        return;
+                    }
+
+                    var totalCount = 0;
+                    var changedCount = 0;
+                    String? firstContent = null;
+
+                    foreach (var item in files.Take(1000))
+                    {
+                        try
+                        {
+                            var content = File.ReadAllText(item);
+                            var ms = reg.Matches(content);
+                            if (ms == null || ms.Count == 0) continue;
+
+                            totalCount += ms.Count;
+                            var content2 = reg.Replace(content, replacement);
+                            if (content != content2)
+                            {
+                                File.WriteAllText(item, content2);
+                                changedCount++;
+                                if (firstContent == null) firstContent = content2;
+                            }
+                        }
+                        catch
+                        {
+                            // 跳过无法读取的文件
+                        }
+                    }
+
+                    sw.Stop();
+
+                    if (firstContent != null)
+                    {
+                        Application.Current.Dispatcher.Invoke(() =>
+                        {
+                            SourceText = firstContent;
+                        });
+                    }
+
+                    Application.Current.Dispatcher.Invoke(() =>
+                    {
+                        Status = $"批量替换完成:{changedCount} 个文件,{totalCount} 项替换,耗时 {sw.ElapsedMilliseconds}ms";
+                    });
+                }
+                catch (Exception ex)
+                {
+                    Application.Current.Dispatcher.Invoke(() => Status = $"错误:{ex.Message}");
+                }
+            });
+        }
+
+        #endregion
+    }
+}
Added +804 -0
diff --git a/CrazyCoder/ViewModels/SecurityViewModel.cs b/CrazyCoder/ViewModels/SecurityViewModel.cs
new file mode 100644
index 0000000..7a805f5
--- /dev/null
+++ b/CrazyCoder/ViewModels/SecurityViewModel.cs
@@ -0,0 +1,804 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Net.Sockets;
+using System.Security.Cryptography;
+using System.Text;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using Microsoft.Win32;
+using NewLife;
+using NewLife.Collections;
+using NewLife.Data;
+using NewLife.Reflection;
+using NewLife.Security;
+using NewLife.Serialization;
+using NewLife.Web;
+
+namespace CrazyCoder.ViewModels;
+
+/// <summary>加密解密工具 ViewModel</summary>
+public partial class SecurityViewModel : ObservableObject
+{
+    #region 属性
+    /// <summary>原文</summary>
+    [ObservableProperty]
+    private String _sourceText = "学无先后达者为师";
+
+    /// <summary>密码</summary>
+    [ObservableProperty]
+    private String _passText = "NewLife";
+
+    /// <summary>结果</summary>
+    [ObservableProperty]
+    private String _resultText = "";
+
+    // 原文格式单选
+    /// <summary>原文格式-字符串</summary>
+    [ObservableProperty]
+    private Boolean _isSourceString = true;
+
+    /// <summary>原文格式-HEX</summary>
+    [ObservableProperty]
+    private Boolean _isSourceHex;
+
+    /// <summary>原文格式-Base64</summary>
+    [ObservableProperty]
+    private Boolean _isSourceBase64;
+
+    /// <summary>HEX单选可用</summary>
+    [ObservableProperty]
+    private Boolean _sourceHexEnabled = true;
+
+    /// <summary>Base64单选可用</summary>
+    [ObservableProperty]
+    private Boolean _sourceBase64Enabled = true;
+
+    // 密码格式单选
+    /// <summary>密码格式-字符串</summary>
+    [ObservableProperty]
+    private Boolean _isPassString = true;
+
+    /// <summary>密码格式-HEX</summary>
+    [ObservableProperty]
+    private Boolean _isPassHex;
+
+    /// <summary>密码格式-Base64</summary>
+    [ObservableProperty]
+    private Boolean _isPassBase64;
+
+    // 结果格式多选
+    /// <summary>结果格式-字符串</summary>
+    [ObservableProperty]
+    private Boolean _isResultString = true;
+
+    /// <summary>结果格式-HEX</summary>
+    [ObservableProperty]
+    private Boolean _isResultHex = true;
+
+    /// <summary>结果格式-Base64</summary>
+    [ObservableProperty]
+    private Boolean _isResultBase64 = true;
+    #endregion
+
+    #region 源文本变更 - 检测HEX/Base64可用性
+    partial void OnSourceTextChanged(String value)
+    {
+        if (value.IsNullOrEmpty()) return;
+
+        // 非纯ASCII(多字节字符)时禁用HEX/Base64单选
+        var enc = Encoding.UTF8;
+        if (enc.GetByteCount(value) != value.Length)
+        {
+            SourceHexEnabled = false;
+            SourceBase64Enabled = false;
+            return;
+        }
+
+        // 检测是否为合法HEX
+        try
+        {
+            SourceHexEnabled = value.ToHex().Length > 0;
+        }
+        catch
+        {
+            SourceHexEnabled = false;
+        }
+
+        // 检测是否为合法Base64
+        try
+        {
+            SourceBase64Enabled = value.ToBase64().Length > 0;
+        }
+        catch
+        {
+            SourceBase64Enabled = false;
+        }
+    }
+    #endregion
+
+    #region 辅助方法
+    /// <summary>从字符串中智能获取字节数组。自动识别 HEX(含-) → Base64 → UTF8</summary>
+    /// <param name="str">字符串</param>
+    /// <returns>字节数组</returns>
+    private static Byte[] GetBytes(String str)
+    {
+        if (str.IsNullOrEmpty()) return [];
+
+        try
+        {
+            if (str.Contains('-')) return str.ToHex();
+        }
+        catch { }
+
+        try
+        {
+            return str.ToBase64();
+        }
+        catch { }
+
+        return str.GetBytes();
+    }
+
+    /// <summary>按单选按钮状态从原文取字节</summary>
+    /// <returns>字节数组</returns>
+    private Byte[] GetSource()
+    {
+        var v = SourceText;
+
+        if (IsSourceString) return v.GetBytes();
+        if (IsSourceHex) return v.ToHex();
+        if (IsSourceBase64) return v.ToBase64();
+
+        return null;
+    }
+
+    /// <summary>按单选按钮状态从密码区取字节</summary>
+    /// <returns>字节数组</returns>
+    private Byte[] GetPass()
+    {
+        var v = PassText;
+
+        if (IsPassString) return v.GetBytes();
+        if (IsPassHex) return v.ToHex();
+        if (IsPassBase64) return v.ToBase64();
+
+        return null;
+    }
+
+    /// <summary>多行输出结果</summary>
+    /// <param name="rs">字符串数组</param>
+    private void SetResult(params String[] rs)
+    {
+        var sb = new StringBuilder();
+        foreach (var item in rs)
+        {
+            if (sb.Length > 0) sb.AppendLine();
+            sb.Append(item);
+        }
+        ResultText = sb.ToString();
+    }
+
+    /// <summary>按输出格式复选框输出字节数组</summary>
+    /// <param name="data">字节数组</param>
+    /// <returns>输出字符串列表</returns>
+    private List<String> SetResult(Byte[] data)
+    {
+        var list = new List<String>();
+        if (IsResultString) list.Add(data.ToStr());
+        if (IsResultHex)
+        {
+            list.Add(data.ToHex().ToUpper());
+            list.Add(data.ToHex().ToLower());
+            list.Add(data.ToHex("-"));
+            list.Add(data.ToHex(" "));
+        }
+        if (IsResultBase64)
+        {
+            list.Add(data.ToBase64());
+            list.Add(data.ToUrlBase64());
+        }
+
+        SetResult(list.ToArray());
+
+        return list;
+    }
+    #endregion
+
+    #region 功能命令
+    /// <summary>上下互换:交换原文区和结果区内容</summary>
+    [RelayCommand]
+    private void Exchange()
+    {
+        var v = SourceText;
+        var v2 = ResultText;
+        // 结果区只要第一行
+        if (!v2.IsNullOrEmpty())
+        {
+            var ss = v2.Split('\n');
+            var n = 0;
+            if (ss.Length > n + 1 && ss[n].StartsWith("/*") && ss[n].EndsWith("*/")) n++;
+            v2 = ss[n];
+        }
+        SourceText = v2;
+        ResultText = v;
+    }
+
+    /// <summary>HEX编码</summary>
+    [RelayCommand]
+    private void HexEncode()
+    {
+        var buf = GetSource();
+        SetResult(buf.ToHex(), buf.ToHex(" ", 32), buf.ToHex("-", 32));
+    }
+
+    /// <summary>HEX解码</summary>
+    [RelayCommand]
+    private void HexDecode()
+    {
+        var v = SourceText;
+        ResultText = v?.Trim().ToHex().ToStr();
+    }
+
+    /// <summary>Base64编码</summary>
+    [RelayCommand]
+    private void Base64Encode()
+    {
+        var buf = GetSource();
+        SetResult(buf.ToBase64(), buf.ToUrlBase64());
+    }
+
+    /// <summary>Base64解码</summary>
+    [RelayCommand]
+    private void Base64Decode()
+    {
+        var v = SourceText;
+
+        var vs = v.Split('.');
+        if (vs.Length <= 1)
+        {
+            var buf = v.Trim().ToBase64();
+            SetResult(buf);
+        }
+        else
+        {
+            SetResult(vs.Select(e2 => e2.Trim().ToBase64().ToStr()).ToArray());
+        }
+    }
+
+    /// <summary>MD5_32位</summary>
+    [RelayCommand]
+    private void MD5_32()
+    {
+        var buf = GetSource();
+        buf = buf.MD5();
+        SetResult(buf);
+    }
+
+    /// <summary>MD5_16位</summary>
+    [RelayCommand]
+    private void MD5_16()
+    {
+        var buf = GetSource();
+        buf = buf.MD5().Take(8).ToArray();
+        SetResult(buf);
+    }
+
+    /// <summary>SHA1</summary>
+    [RelayCommand]
+    private void SHA1()
+    {
+        var buf = GetSource();
+        var key = GetPass();
+
+        buf = buf.SHA1(key);
+        var rs = SetResult(buf);
+        rs.Add($"sha1${key.ToStr()}${buf.ToBase64()}");
+
+        SetResult(rs.ToArray());
+    }
+
+    /// <summary>SHA256</summary>
+    [RelayCommand]
+    private void SHA256()
+    {
+        var buf = GetSource();
+        var key = GetPass();
+
+        buf = buf.SHA256(key);
+        var rs = SetResult(buf);
+        rs.Add($"sha256${key.ToStr()}${buf.ToBase64()}");
+
+        SetResult(rs.ToArray());
+    }
+
+    /// <summary>SHA384</summary>
+    [RelayCommand]
+    private void SHA384()
+    {
+        var buf = GetSource();
+        var key = GetPass();
+
+        buf = buf.SHA384(key);
+        var rs = SetResult(buf);
+        rs.Add($"sha384${key.ToStr()}${buf.ToBase64()}");
+
+        SetResult(rs.ToArray());
+    }
+
+    /// <summary>SHA512</summary>
+    [RelayCommand]
+    private void SHA512()
+    {
+        var buf = GetSource();
+        var key = GetPass();
+
+        buf = buf.SHA512(key);
+        var rs = SetResult(buf);
+        rs.Add($"sha512${key.ToStr()}${buf.ToBase64()}");
+
+        SetResult(rs.ToArray());
+    }
+
+    /// <summary>CRC_32</summary>
+    [RelayCommand]
+    private void CRC_32()
+    {
+        var buf = GetSource();
+        var rs = buf.Crc();
+        var data = rs.GetBytes(false);
+        SetResult("/*数字、HEX编码、Base64编码*/", rs + "", data.ToHex(), data.ToBase64());
+    }
+
+    /// <summary>CRC_16</summary>
+    [RelayCommand]
+    private void CRC_16()
+    {
+        var buf = GetSource();
+        var rs = buf.Crc16();
+        var data = rs.GetBytes(false);
+        var mcrc = Modbus_CRC(buf, 0, buf.Length);
+        SetResult("/*数字、HEX编码、Base64编码、Modbus-Crc*/", rs + "", data.ToHex(), data.ToBase64(), mcrc.GetBytes().ToHex("-"));
+    }
+
+    /// <summary>RSA加密</summary>
+    [RelayCommand]
+    private void RSAEncrypt()
+    {
+        var buf = GetSource();
+        var key = PassText;
+
+        if (key.Length < 100)
+        {
+            key = RSAHelper.GenerateKey().First();
+            PassText = key;
+        }
+
+        buf = RSAHelper.Encrypt(buf, key);
+
+        SetResult(buf);
+    }
+
+    /// <summary>RSA解密</summary>
+    [RelayCommand]
+    private void RSADecrypt()
+    {
+        var buf = GetSource();
+        var pass = PassText;
+
+        try
+        {
+            buf = RSAHelper.Decrypt(buf, pass, true);
+        }
+        catch (CryptographicException)
+        {
+            // 换一种填充方式
+            buf = RSAHelper.Decrypt(buf, pass, false);
+        }
+
+        SetResult(buf);
+    }
+
+    /// <summary>DSA签名</summary>
+    [RelayCommand]
+    private void DSASign()
+    {
+        var buf = GetSource();
+        var key = PassText;
+
+        if (key.Length < 100)
+        {
+            key = DSAHelper.GenerateKey().First();
+            PassText = key;
+        }
+
+        buf = DSAHelper.Sign(buf, key);
+
+        SetResult(buf);
+    }
+
+    /// <summary>DSA验证</summary>
+    [RelayCommand]
+    private void DSAVerify()
+    {
+        var buf = GetSource();
+        var pass = PassText;
+
+        var v = ResultText;
+        if (v.Contains("\n\n")) v = v.Substring(null, "\n\n");
+        var sign = GetBytes(v);
+
+        var rs = DSAHelper.Verify(buf, pass, sign);
+        SetResult(rs ? "验证通过" : "验证失败");
+    }
+
+    /// <summary>Url编码</summary>
+    [RelayCommand]
+    private void UrlEncode()
+    {
+        var v = SourceText;
+        v = WebUtility.UrlEncode(v);
+        ResultText = v;
+    }
+
+    /// <summary>Url解码</summary>
+    [RelayCommand]
+    private void UrlDecode()
+    {
+        var v = SourceText;
+        v = WebUtility.UrlDecode(v);
+        ResultText = v;
+    }
+
+    /// <summary>Html编码</summary>
+    [RelayCommand]
+    private void HtmlEncode()
+    {
+        var v = SourceText;
+        v = WebUtility.HtmlEncode(v);
+        ResultText = v;
+    }
+
+    /// <summary>Html解码</summary>
+    [RelayCommand]
+    private void HtmlDecode()
+    {
+        var v = SourceText;
+        v = WebUtility.HtmlDecode(v);
+        ResultText = v;
+    }
+
+    /// <summary>时间戳</summary>
+    [RelayCommand]
+    private void Timestamp()
+    {
+        var v = SourceText;
+        if (v.IsNullOrEmpty()) return;
+
+        var sb = Pool.StringBuilder.Get();
+
+        DateTime.TryParse(PassText, out var baseTime);
+        var utc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+        if (baseTime > DateTime.MinValue) sb.AppendFormat("基准:{0:yyyy-MM-dd HH:mm:ss.fff} {1}\r\n", baseTime, baseTime.Kind);
+
+        var dt = v.ToDateTime();
+        if (dt.Year > 1 && dt.Year < 3000)
+        {
+            var s = dt.ToInt();
+            var m = dt.ToLong();
+
+            if (baseTime > DateTime.MinValue)
+            {
+                s -= baseTime.ToInt();
+                m -= baseTime.ToLong();
+            }
+
+            sb.AppendLine("Unix秒:" + s);
+            sb.AppendLine("Unix毫秒:" + m);
+        }
+
+        var now = DateTime.Now;
+        var n = v.ToLong();
+        if (n >= Int32.MaxValue)
+        {
+            dt = n.ToDateTime();
+            dt = dt.ToLocalTime().ToUniversalTime();
+            if (baseTime > DateTime.MinValue) dt = dt.Add(baseTime - utc);
+            if (dt.Year > 1000 && dt.Year < 3000)
+            {
+                sb.AppendFormat("时间:{0:yyyy-MM-dd HH:mm:ss.fff} (Unix毫秒) {1}\r\n", dt, dt.Kind);
+                dt = dt.ToLocalTime();
+                sb.AppendFormat("时间:{0:yyyy-MM-dd HH:mm:ss.fff} (现在) {1}\r\n", dt, dt.Kind);
+            }
+        }
+        else if (n > 0)
+        {
+            dt = v.ToInt().ToDateTime();
+            dt = dt.ToLocalTime().ToUniversalTime();
+            if (baseTime > DateTime.MinValue) dt = dt.Add(baseTime - utc);
+            if (dt.Year > 1000 && dt.Year < 3000)
+            {
+                sb.AppendFormat("时间:{0:yyyy-MM-dd HH:mm:ss} (Unix秒) {1}\r\n", dt, dt.Kind);
+                dt = dt.ToLocalTime();
+                sb.AppendFormat("时间:{0:yyyy-MM-dd HH:mm:ss} (现在) {1}\r\n", dt, dt.Kind);
+            }
+        }
+
+        // 过去/未来时间戳
+        if (n > 0 && n < 1000L * 365 * 24 * 3600 * 1000)
+        {
+            sb.AppendFormat("过去:{0:yyyy-MM-dd HH:mm:ss.fff} (now.AddMilliseconds(-n))\r\n", now.AddMilliseconds(-n));
+            sb.AppendFormat("未来:{0:yyyy-MM-dd HH:mm:ss.fff} (now.AddMilliseconds(n))\r\n", now.AddMilliseconds(n));
+
+            if (n < Int32.MaxValue)
+            {
+                sb.AppendFormat("过去:{0:yyyy-MM-dd HH:mm:ss} (now.AddSeconds(-n))\r\n", now.AddSeconds(-n));
+                sb.AppendFormat("未来:{0:yyyy-MM-dd HH:mm:ss} (now.AddSeconds(n))\r\n", now.AddSeconds(n));
+            }
+        }
+
+        ResultText = sb.Return(true);
+    }
+
+    /// <summary>机器信息</summary>
+    [RelayCommand]
+    private void ComputerInfo()
+    {
+        var sb = Pool.StringBuilder.Get();
+
+        var mi = MachineInfo.Current;
+        mi.Refresh();
+        sb.AppendLine(mi.ToJson(true));
+        sb.AppendLine();
+
+        var osName = GetWmiInfo("Win32_OperatingSystem", "Caption");
+        sb.AppendFormat("OSName:\t{0}\t(Win32_OperatingSystem.Caption)\r\n", osName);
+
+        var osVersion = GetWmiInfo("Win32_OperatingSystem", "Version");
+        sb.AppendFormat("OSVersion:\t{0}\t(Win32_OperatingSystem.Version)\r\n", osVersion);
+
+        var macs = GetMacs().ToList();
+        if (macs.Count > 0) sb.AppendFormat("MAC:\t{0}\r\n", macs.Join(",", x => x.ToHex("-")));
+
+        var processor = GetWmiInfo("Win32_Processor", "Name");
+        sb.AppendFormat("Processor:\t{0}\t(Win32_Processor.Name)\r\n", processor);
+
+        var cpuID = GetWmiInfo("Win32_Processor", "ProcessorId");
+        sb.AppendFormat("ProcessorId:\t{0}\t(Win32_Processor.ProcessorId)\r\n", cpuID);
+
+        var uuid = GetWmiInfo("Win32_ComputerSystemProduct", "UUID");
+        sb.AppendFormat("UUID:\t{0}\t(Win32_ComputerSystemProduct.UUID)\r\n", uuid);
+
+        var id = GetWmiInfo("Win32_ComputerSystemProduct", "IdentifyingNumber");
+        sb.AppendFormat("IdentifyingNumber:\t{0}\t(Win32_ComputerSystemProduct.IdentifyingNumber)\r\n", id);
+
+        var bios = GetWmiInfo("Win32_BIOS", "SerialNumber");
+        sb.AppendFormat("BIOS:\t{0}\t(Win32_BIOS.SerialNumber)\r\n", bios);
+
+        var baseBoard = GetWmiInfo("Win32_BaseBoard", "SerialNumber");
+        sb.AppendFormat("BaseBoard:\t{0}\t(Win32_BaseBoard.SerialNumber)\r\n", baseBoard);
+
+        var serialNumber = GetWmiInfo("Win32_DiskDrive", "SerialNumber");
+        sb.AppendFormat("DiskSerial:\t{0}\t(Win32_DiskDrive.SerialNumber)\r\n", serialNumber);
+
+        var reg = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Cryptography");
+        if (reg != null)
+        {
+            var guid = reg.GetValue("MachineGuid") + "";
+            sb.AppendFormat("MachineGuid:\t{0}\t(SOFTWARE\\Microsoft\\Cryptography)\r\n", guid);
+        }
+
+        ResultText = sb.Return(true);
+    }
+
+    private static readonly String[] _Excludes = ["Loopback", "VMware", "VBox", "Virtual", "Teredo", "Microsoft", "VPN", "VNIC", "IEEE"];
+
+    /// <summary>获取所有网卡MAC地址</summary>
+    /// <returns>MAC地址集合</returns>
+    public static IEnumerable<Byte[]> GetMacs()
+    {
+        foreach (var item in NetworkInterface.GetAllNetworkInterfaces())
+        {
+            if (_Excludes.Any(e => item.Description.Contains(e))) continue;
+            if (item.Speed < 1_000_000) continue;
+
+            var addrs = item.GetIPProperties().UnicastAddresses.Where(e => e.Address.AddressFamily == AddressFamily.InterNetwork).ToArray();
+            if (addrs.All(e => IPAddress.IsLoopback(e.Address))) continue;
+
+            var mac = item.GetPhysicalAddress()?.GetAddressBytes();
+            if (mac != null && mac.Length == 6) yield return mac;
+        }
+    }
+
+    /// <summary>获取WMI信息</summary>
+    /// <param name="path">WMI路径</param>
+    /// <param name="property">属性名</param>
+    /// <returns>属性值</returns>
+    public static String GetWmiInfo(String path, String property)
+    {
+        try
+        {
+            var wql = $"Select {property} From {path}";
+            var searcher = new ManagementObjectSearcher(wql);
+            var bbs = new List<String>();
+            foreach (var mo in searcher.Get())
+            {
+                if (mo != null && mo.Properties != null && mo.Properties[property] != null && mo.Properties[property].Value != null)
+                    bbs.Add(mo.Properties[property].Value.ToString());
+            }
+            bbs.Sort();
+            return bbs.Join(",");
+        }
+        catch
+        {
+            return "";
+        }
+    }
+
+    /// <summary>雪花Id</summary>
+    [RelayCommand]
+    private void Snowflake()
+    {
+        var v = SourceText.ToLong();
+        if (v <= 0) return;
+
+        var snow = new Snowflake();
+
+        // 指定基准时间
+        if (!PassText.IsNullOrEmpty())
+        {
+            var baseTime = PassText.ToDateTime();
+            if (baseTime.Year > 1000) snow.StartTimestamp = baseTime;
+        }
+
+        if (!snow.TryParse(v, out var time, out var workerId, out var sequence)) throw new Exception("解码失败!");
+
+        // 初始化一次,用于获取本机workerId
+        snow.NewId();
+
+        var t = (Int64)(time - snow.StartTimestamp).TotalMilliseconds;
+        SetResult(
+            $"十六:{v:X16}",
+            $"编码:{(t << 2):X8} {workerId:X2} {sequence:X3}",
+            $"基准:{snow.StartTimestamp.ToFullString()} {snow.StartTimestamp.Kind}",
+            $"时间:{time:yyyy-MM-dd HH:mm:ss.fff} {time.Kind} ({t} / {(t << 22):X8})",
+            $"节点:{workerId} ({workerId:X4})",
+            $"序号:{sequence} ({sequence:X4})",
+            $"本机:{snow.WorkerId} ({snow.WorkerId:X4})");
+    }
+
+    /// <summary>JWT令牌</summary>
+    [RelayCommand]
+    private void JwtToken()
+    {
+        var v = SourceText;
+        if (v.IsNullOrEmpty()) return;
+
+        var pass = PassText?.Trim();
+
+        var vs = v.Split('.');
+        if (vs.Length == 3)
+        {
+            var jwt = new JwtBuilder { Secret = "abcd" };
+
+            var ss = pass?.Split(':');
+            if (ss != null && ss.Length >= 2)
+            {
+                jwt.Algorithm = ss[0];
+                jwt.Secret = ss[1];
+            }
+
+            var rs = jwt.TryDecode(v, out var message);
+
+            SetResult($"验证结果:{rs}", jwt.ToJson(true));
+        }
+        else if (vs.Length == 2)
+        {
+            var prv = new TokenProvider
+            {
+                Key = pass
+            };
+
+            var rs = prv.TryDecode(v, out var user, out var expire);
+
+            SetResult($"验证结果:{rs}", new { user, expire }.ToJson(true));
+        }
+        else
+        {
+            SetResult(vs.Select(e2 => e2.ToBase64().ToStr()).ToArray());
+        }
+    }
+
+    /// <summary>版本号</summary>
+    [RelayCommand]
+    private void Version()
+    {
+        var v = SourceText;
+        if (v.IsNullOrEmpty()) return;
+
+        var dt = AssemblyX.GetCompileTime(v);
+
+        ResultText = dt.ToFullString();
+    }
+
+    /// <summary>TraceId</summary>
+    [RelayCommand]
+    private void TraceId()
+    {
+        var str = SourceText.Trim();
+        if (str.IsNullOrEmpty()) return;
+
+        var rs = new List<String>();
+        foreach (var item in str.Split('\n'))
+        {
+            var v = item.Trim();
+            if (v.IsNullOrEmpty()) continue;
+
+            // TraceId
+            if (v.Length >= 30)
+            {
+                if (v.Length >= 8)
+                    rs.Add($"地址:{v[..8]} {new IPAddress(v[..8].ToHex())}");
+
+                if (v.Length >= 8 + 13)
+                {
+                    var time = v.Substring(8, 13).ToLong();
+                    rs.Add($"时间:{time}({time.ToDateTime().ToLocalTime().ToFullString()})");
+                }
+
+                if (v.Length >= 21 + 4)
+                    rs.Add($"序列:{v[21..25]} {v[21..25].ToHex().ToUInt16()}");
+
+                if (v.Length >= 26 + 4)
+                    rs.Add($"进程:{v[26..30]} {v[26..30].ToHex().ToUInt16()}");
+            }
+            else if (v.Length >= 16)
+            {
+                if (v.Length >= 8)
+                    rs.Add($"地址:{v[..8]} {new IPAddress(v[..8].ToHex())}");
+
+                if (v.Length >= 8 + 4)
+                    rs.Add($"进程:{v[8..12]} {v[8..12].ToHex().ToUInt16()}");
+
+                if (v.Length >= 12 + 4)
+                    rs.Add($"序列:{v[12..16]} {v[12..16].ToHex().ToUInt16()}");
+            }
+            rs.Add("");
+        }
+
+        SetResult(rs.ToArray());
+    }
+    #endregion
+
+    #region Modbus_CRC
+    private static readonly UInt16[] crc_ta = [0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401, 0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400];
+
+    /// <summary>Crc校验</summary>
+    /// <param name="data">数据</param>
+    /// <param name="offset">偏移</param>
+    /// <param name="count">数量</param>
+    /// <returns>Crc结果</returns>
+    public static UInt16 Modbus_CRC(Byte[] data, Int32 offset, Int32 count = -1)
+    {
+        if (data == null || data.Length < 1) return 0;
+
+        UInt16 u = 0xFFFF;
+        Byte b;
+
+        if (count == 0) count = data.Length - offset;
+
+        for (var i = offset; i < count; i++)
+        {
+            b = data[i];
+            u = (UInt16)(crc_ta[(b ^ u) & 15] ^ (u >> 4));
+            u = (UInt16)(crc_ta[((b >> 4) ^ u) & 15] ^ (u >> 4));
+        }
+
+        return u;
+    }
+    #endregion
+}
Added +178 -0
diff --git a/CrazyCoder/ViewModels/SpeechViewModel.cs b/CrazyCoder/ViewModels/SpeechViewModel.cs
new file mode 100644
index 0000000..9413897
--- /dev/null
+++ b/CrazyCoder/ViewModels/SpeechViewModel.cs
@@ -0,0 +1,178 @@
+#nullable enable
+using System.Collections.ObjectModel;
+using System.Speech.Synthesis;
+using System.Windows;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using Microsoft.Win32;
+using NewLife;
+
+namespace CrazyCoder.ViewModels;
+
+/// <summary>语音助手 (TTS) ViewModel</summary>
+public partial class SpeechViewModel : ObservableObject
+{
+    #region 属性
+    private SpeechSynthesizer? _synth;
+
+    /// <summary>可用语音列表</summary>
+    public ObservableCollection<String> Voices { get; } = [];
+
+    /// <summary>语音数据列表</summary>
+    private List<VoiceInfo> _voiceInfos = [];
+
+    /// <summary>选中语音索引</summary>
+    [ObservableProperty]
+    private Int32 _selectedVoiceIndex;
+
+    /// <summary>音量 (0-100)</summary>
+    [ObservableProperty]
+    private Int32 _volume = 100;
+
+    /// <summary>语速 (-10 到 10)</summary>
+    [ObservableProperty]
+    private Int32 _rate;
+
+    /// <summary>朗读文本</summary>
+    [ObservableProperty]
+    private String _speakText = "学无先后达者为师";
+
+    /// <summary>是否正在朗读</summary>
+    [ObservableProperty]
+    private Boolean _isSpeaking;
+    #endregion
+
+    #region 构造
+    /// <summary>实例化语音助手 ViewModel</summary>
+    public SpeechViewModel()
+    {
+        LoadVoices();
+    }
+
+    private void LoadVoices()
+    {
+        try
+        {
+            using var sync = new SpeechSynthesizer();
+            var voices = sync.GetInstalledVoices();
+            _voiceInfos = voices.Select(e => e.VoiceInfo).ToList();
+
+            Voices.Clear();
+            foreach (var vi in _voiceInfos)
+            {
+                Voices.Add($"{vi.Name}[{vi.Culture}]");
+            }
+        }
+        catch (Exception ex)
+        {
+            MessageBox.Show($"加载语音失败:{ex.Message}", "提示");
+        }
+    }
+    #endregion
+
+    #region 命令
+    /// <summary>朗读</summary>
+    [RelayCommand]
+    private void Speak()
+    {
+        var txt = SpeakText;
+        if (txt.IsNullOrEmpty()) return;
+
+        Stop();
+
+        try
+        {
+            _synth = CreateSynthesizer();
+            _synth.SpeakCompleted += OnSpeakCompleted;
+            IsSpeaking = true;
+            _synth.SpeakAsync(txt);
+        }
+        catch (Exception ex)
+        {
+            MessageBox.Show($"朗读失败:{ex.Message}", "提示");
+            IsSpeaking = false;
+        }
+    }
+
+    /// <summary>停止朗读</summary>
+    [RelayCommand]
+    private void Stop()
+    {
+        if (_synth != null)
+        {
+            try
+            {
+                _synth.SpeakAsyncCancelAll();
+            }
+            catch
+            {
+            }
+            _synth.Dispose();
+            _synth = null;
+        }
+        IsSpeaking = false;
+    }
+
+    /// <summary>保存为 WAV 文件</summary>
+    [RelayCommand]
+    private void SaveWav()
+    {
+        var txt = SpeakText;
+        if (txt.IsNullOrEmpty())
+        {
+            MessageBox.Show("请输入要转换的文本!", "提示");
+            return;
+        }
+
+        var dlg = new SaveFileDialog
+        {
+            Filter = "音频文件 (*.wav)|*.wav",
+            Title = "保存为 WAV 文件",
+            DefaultExt = "wav"
+        };
+        if (dlg.ShowDialog() != true) return;
+
+        try
+        {
+            using var sync = CreateSynthesizer();
+            sync.SetOutputToWaveFile(dlg.FileName);
+            sync.Speak(txt);
+
+            MessageBox.Show("保存成功!", "提示");
+        }
+        catch (Exception ex)
+        {
+            MessageBox.Show($"保存失败:{ex.Message}", "提示");
+        }
+    }
+    #endregion
+
+    #region 辅助
+    private SpeechSynthesizer CreateSynthesizer()
+    {
+        var sync = new SpeechSynthesizer
+        {
+            Volume = Volume,
+            Rate = Rate
+        };
+
+        if (SelectedVoiceIndex >= 0 && SelectedVoiceIndex < _voiceInfos.Count)
+        {
+            sync.SelectVoice(_voiceInfos[SelectedVoiceIndex].Name);
+        }
+
+        return sync;
+    }
+
+    private void OnSpeakCompleted(Object? sender, SpeakCompletedEventArgs e)
+    {
+        IsSpeaking = false;
+        if (_synth != null)
+        {
+            _synth.SpeakCompleted -= OnSpeakCompleted;
+            _synth.Dispose();
+            _synth = null;
+        }
+    }
+    #endregion
+}
Added +42 -0
diff --git a/CrazyCoder/Views/FolderStatWindow.xaml b/CrazyCoder/Views/FolderStatWindow.xaml
new file mode 100644
index 0000000..dda4bbb
--- /dev/null
+++ b/CrazyCoder/Views/FolderStatWindow.xaml
@@ -0,0 +1,42 @@
+<Window x:Class="CrazyCoder.Views.FolderStatWindow"
+        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="1000" WindowStartupLocation="CenterScreen">
+    <Window.Resources>
+        <ContextMenu x:Key="FolderContextMenu">
+            <MenuItem Header="打开目录" Click="OnOpenDirectory"/>
+            <MenuItem Header="删除" Click="OnDelete"/>
+        </ContextMenu>
+    </Window.Resources>
+
+    <Grid Margin="4">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="*"/>
+            <RowDefinition Height="4"/>
+            <RowDefinition Height="150"/>
+        </Grid.RowDefinitions>
+
+        <!-- 树形目录 -->
+        <TreeView x:Name="treeView" BorderThickness="1" BorderBrush="#D0D0D0">
+            <TreeView.ItemContainerStyle>
+                <Style TargetType="TreeViewItem">
+                    <Setter Property="ContextMenu" Value="{StaticResource FolderContextMenu}"/>
+                    <EventSetter Event="TreeViewItem.Expanded" Handler="OnItemExpanded"/>
+                    <EventSetter Event="TreeViewItem.Collapsed" Handler="OnItemCollapsed"/>
+                    <EventSetter Event="TreeViewItem.PreviewMouseRightButtonDown" Handler="OnItemPreviewMouseRightButtonDown"/>
+                </Style>
+            </TreeView.ItemContainerStyle>
+        </TreeView>
+
+        <GridSplitter Grid.Row="1" Height="4" HorizontalAlignment="Stretch" Background="#F0F0F0"/>
+
+        <!-- 日志输出 -->
+        <Border Grid.Row="2" BorderBrush="#D0D0D0" BorderThickness="1">
+            <RichTextBox x:Name="txtLog" IsReadOnly="True" VerticalScrollBarVisibility="Auto"
+                         FontFamily="Consolas" FontSize="12" Background="#1E1E1E" Foreground="#D4D4D4"/>
+        </Border>
+    </Grid>
+</Window>
Added +308 -0
diff --git a/CrazyCoder/Views/FolderStatWindow.xaml.cs b/CrazyCoder/Views/FolderStatWindow.xaml.cs
new file mode 100644
index 0000000..56f3695
--- /dev/null
+++ b/CrazyCoder/Views/FolderStatWindow.xaml.cs
@@ -0,0 +1,308 @@
+using System.Diagnostics;
+using System.IO;
+using System.Text;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using NewLife.Caching;
+
+namespace CrazyCoder.Views;
+
+/// <summary>文件夹大小统计工具</summary>
+public partial class FolderStatWindow : Window
+{
+    private const String DummyNode = "\0";
+
+    private readonly ICache _cache = new MemoryCache();
+
+    #region 初始化
+    /// <summary>实例化文件夹大小统计窗口</summary>
+    public FolderStatWindow()
+    {
+        InitializeComponent();
+
+        // 加载固定硬盘驱动器作为根节点
+        foreach (var item in DriveInfo.GetDrives())
+        {
+            if (item.DriveType == DriveType.Fixed)
+            {
+                var header = $"{item.Name,-10} ({FormatSize(item.TotalSize)})";
+                var tn = new TreeViewItem
+                {
+                    Header = header,
+                    Tag = item.RootDirectory
+                };
+                // 预加虚拟子节点,展开时触发 MakeTree
+                tn.Items.Add(new TreeViewItem { Header = DummyNode });
+                treeView.Items.Add(tn);
+            }
+        }
+    }
+    #endregion
+
+    #region 构造目录树
+    private void MakeTree(TreeViewItem parent)
+    {
+        if (parent.Tag is not DirectoryInfo di) return;
+
+        WriteLog($"展开目录 {di.FullName}");
+
+        parent.Items.Clear();
+
+        var list = new List<FileSystemInfo>();
+        list.AddRange(di.GetDirectories());
+        list.AddRange(di.GetFiles());
+
+        var max = 0;
+        foreach (var item in list)
+        {
+            max = Math.Max(max, StrLen(item.Name));
+        }
+        max++;
+
+        foreach (var item in list)
+        {
+            var len = max;
+            len -= (StrLen(item.Name) - item.Name.Length);
+            Int64 size;
+
+            if (item is FileInfo fi)
+                size = fi.Length;
+            else
+                size = -1;
+
+            var header = String.Format("{0,-" + len + "} {1,10}", item.Name, FormatSize(size));
+            var tn = new TreeViewItem
+            {
+                Header = header,
+                Tag = item,
+                Background = GetBrush(size)
+            };
+
+            if (item is DirectoryInfo)
+            {
+                // 预加虚拟子节点,展开时继续加载
+                tn.Items.Add(new TreeViewItem { Header = DummyNode });
+
+                // 后台线程异步统计文件夹大小
+                var captured = tn;
+                Task.Run(() => TongJi(captured));
+            }
+
+            parent.Items.Add(tn);
+        }
+    }
+
+    /// <summary>格式化文件大小</summary>
+    private static String FormatSize(Int64 size)
+    {
+        if (size < 1024) return size + " Byte";
+        var ds = (Double)size / 1024;
+        if (ds < 1024) return ds.ToString("N2") + " K";
+        ds /= 1024;
+        if (ds < 1024) return ds.ToString("N2") + " M";
+        ds /= 1024;
+        if (ds < 1024) return ds.ToString("N2") + " G";
+        ds /= 1024;
+        if (ds < 1024) return ds.ToString("N2") + " T";
+        return "∞";
+    }
+
+    /// <summary>计算字符串显示宽度(中文字符算2个宽度)</summary>
+    private static Int32 StrLen(String str) => (str.Length + Encoding.UTF8.GetByteCount(str)) / 2;
+    #endregion
+
+    #region 统计文件夹大小
+    private Int64 FolderSize(DirectoryInfo di)
+    {
+        if (_cache.TryGetValue<Int64>(di.FullName, out var v)) return v;
+
+        Int64 size = 0;
+        try
+        {
+            foreach (var item in di.GetFiles())
+            {
+                size += item.Length;
+            }
+            foreach (var item in di.GetDirectories())
+            {
+                size += FolderSize(item);
+            }
+        }
+        catch { }
+
+        // 缓存30秒,避免重复遍历
+        _cache.Set(di.FullName, size, 30);
+
+        return size;
+    }
+    #endregion
+
+    #region 统计目录并设置大小
+    private void TongJi(TreeViewItem node)
+    {
+        if (node.Tag is not DirectoryInfo di) return;
+
+        try
+        {
+            var size = FolderSize(di);
+            var header = node.Header as String;
+            if (header != null && header.Length >= 10)
+            {
+                var str = header[..^10] + $"{FormatSize(size),10}";
+                Dispatcher.Invoke(() =>
+                {
+                    node.Header = str;
+                    node.Background = GetBrush(size);
+                });
+            }
+        }
+        catch (Exception ex)
+        {
+            WriteLog(ex.ToString());
+        }
+    }
+
+    /// <summary>根据大小获取背景色</summary>
+    private static Brush GetBrush(Int64 size)
+    {
+        if (size > 1024L * 1024 * 1024) return Brushes.Yellow;
+        if (size > 100L * 1024 * 1024) return Brushes.LawnGreen;
+        if (size > 1024L * 1024) return Brushes.LightBlue;
+        if (size > 1024L) return Brushes.MistyRose;
+        return Brushes.White;
+    }
+    #endregion
+
+    #region 展开折叠目录树
+    private void OnItemExpanded(Object sender, RoutedEventArgs e)
+    {
+        if (sender is TreeViewItem item && item.Tag is DirectoryInfo)
+        {
+            // 只有虚拟子节点时触发懒加载
+            if (item.Items.Count == 1 && item.Items[0] is TreeViewItem dummy && DummyNode.Equals(dummy.Header))
+            {
+                MakeTree(item);
+            }
+        }
+    }
+
+    private void OnItemCollapsed(Object sender, RoutedEventArgs e)
+    {
+        if (sender is TreeViewItem item && item.Tag is DirectoryInfo)
+        {
+            // 折叠后清空,再次展开时重新计算
+            item.Items.Clear();
+            item.Items.Add(new TreeViewItem { Header = DummyNode });
+        }
+    }
+    #endregion
+
+    #region 右键菜单
+    private static String GetSelectedPath(TreeView tree)
+    {
+        if (tree.SelectedItem is TreeViewItem node && node.Tag is FileSystemInfo fi)
+            return fi.FullName;
+
+        return null;
+    }
+
+    private void OnItemPreviewMouseRightButtonDown(Object sender, MouseButtonEventArgs e)
+    {
+        if (sender is TreeViewItem item)
+        {
+            item.Focus();
+            item.IsSelected = true;
+        }
+    }
+
+    private void OnOpenDirectory(Object sender, RoutedEventArgs e)
+    {
+        var path = GetSelectedPath(treeView);
+        if (String.IsNullOrEmpty(path)) return;
+
+        try
+        {
+            if (File.Exists(path))
+                Process.Start("explorer.exe", "/select,\"" + path + "\"");
+            else if (Directory.Exists(path))
+                Process.Start("explorer.exe", "\"" + path + "\"");
+        }
+        catch (Exception ex)
+        {
+            MessageBox.Show(ex.Message, "打开出错");
+        }
+    }
+
+    private void OnDelete(Object sender, RoutedEventArgs e)
+    {
+        var path = GetSelectedPath(treeView);
+        if (String.IsNullOrEmpty(path)) return;
+
+        if (MessageBox.Show($"准备删除{path}\n删除将不可恢复,是否删除?", "确认删除", MessageBoxButton.YesNo) != MessageBoxResult.Yes)
+            return;
+
+        try
+        {
+            if (File.Exists(path))
+                File.Delete(path);
+            else if (Directory.Exists(path))
+                DeleteRecursive(new DirectoryInfo(path));
+
+            if (treeView.SelectedItem is TreeViewItem node)
+            {
+                RemoveNode(node);
+            }
+        }
+        catch (Exception ex)
+        {
+            MessageBox.Show(ex.Message, "删除出错");
+        }
+    }
+
+    private void RemoveNode(TreeViewItem node)
+    {
+        // 从父级容器移除(父级可能是 TreeView 或 TreeViewItem)
+        if (node.Parent is ItemsControl parentControl)
+        {
+            parentControl.Items.Remove(node);
+        }
+    }
+
+    /// <summary>递归删除目录</summary>
+    private void DeleteRecursive(DirectoryInfo di)
+    {
+        foreach (var item in di.GetFiles())
+        {
+            WriteLog($"删除 {item.FullName}");
+            try { item.Delete(); }
+            catch (Exception ex) { WriteLog(ex.Message); }
+        }
+        foreach (var item in di.GetDirectories())
+        {
+            DeleteRecursive(item);
+        }
+        try { di.Delete(true); }
+        catch (Exception ex) { WriteLog(ex.Message); }
+    }
+    #endregion
+
+    #region 日志
+    /// <summary>输出日志到 RichTextBox</summary>
+    private void WriteLog(String message)
+    {
+        Dispatcher.Invoke(() =>
+        {
+            var paragraph = new Paragraph(new Run(message))
+            {
+                Foreground = Brushes.White,
+                Margin = new Thickness(0)
+            };
+            txtLog.Document.Blocks.Add(paragraph);
+            txtLog.ScrollToEnd();
+        });
+    }
+    #endregion
+}
Added +154 -0
diff --git a/CrazyCoder/Views/GpsWindow.xaml b/CrazyCoder/Views/GpsWindow.xaml
new file mode 100644
index 0000000..5c6f7f3
--- /dev/null
+++ b/CrazyCoder/Views/GpsWindow.xaml
@@ -0,0 +1,154 @@
+<Window x:Class="CrazyCoder.Views.GpsWindow"
+        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="GPS 辅助工具" Height="450" Width="600" 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="30"/>
+            <Setter Property="VerticalContentAlignment" Value="Center"/>
+            <Setter Property="FontFamily" Value="Consolas"/>
+            <Setter Property="FontSize" Value="14"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+        </Style>
+
+        <Style x:Key="ActionButton" TargetType="Button">
+            <Setter Property="Height" Value="30"/>
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Padding" Value="12,0"/>
+        </Style>
+
+        <Style x:Key="ResultText" TargetType="TextBox">
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Height" Value="30"/>
+            <Setter Property="VerticalContentAlignment" Value="Center"/>
+            <Setter Property="FontFamily" Value="Consolas"/>
+            <Setter Property="FontSize" Value="14"/>
+            <Setter Property="FontWeight" Value="Bold"/>
+            <Setter Property="IsReadOnly" Value="True"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="Background" Value="#FFFFF0"/>
+        </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"/>
+                </Grid.RowDefinitions>
+                <TextBlock Text="HEX 合并输入(16位)" Style="{StaticResource SectionTitle}"/>
+                <Grid Grid.Row="1" Margin="4,0,4,4">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="*"/>
+                        <ColumnDefinition Width="Auto"/>
+                    </Grid.ColumnDefinitions>
+                    <TextBox Grid.Column="0" Text="{Binding HexCombined, UpdateSourceTrigger=PropertyChanged}"
+                             Style="{StaticResource ConfigTextBox}"/>
+                    <Button Grid.Column="1" Content="转换" Command="{Binding ConvertCombinedCommand}"
+                            Style="{StaticResource ActionButton}" Margin="4,2" Width="80"/>
+                </Grid>
+            </Grid>
+        </Border>
+
+        <!-- 分离输入 -->
+        <Border Grid.Row="1" Style="{StaticResource GroupBorder}">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                </Grid.RowDefinitions>
+                <TextBlock Text="HEX 分离输入(各8位)" Style="{StaticResource SectionTitle}"/>
+                <Grid Grid.Row="1" Margin="4,0,4,4">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="Auto"/>
+                        <ColumnDefinition Width="*"/>
+                        <ColumnDefinition Width="Auto"/>
+                        <ColumnDefinition Width="*"/>
+                        <ColumnDefinition Width="Auto"/>
+                    </Grid.ColumnDefinitions>
+
+                    <TextBlock Grid.Column="0" Text="纬度" Style="{StaticResource ConfigLabel}" Width="40"/>
+                    <TextBox Grid.Column="1" Text="{Binding HexLat, UpdateSourceTrigger=PropertyChanged}"
+                             Style="{StaticResource ConfigTextBox}"/>
+                    <TextBlock Grid.Column="2" Text="经度" Style="{StaticResource ConfigLabel}" Width="40" Margin="8,2,2,2"/>
+                    <TextBox Grid.Column="3" Text="{Binding HexLong, UpdateSourceTrigger=PropertyChanged}"
+                             Style="{StaticResource ConfigTextBox}"/>
+                    <Button Grid.Column="4" Content="转换" Command="{Binding ConvertCommand}"
+                            Style="{StaticResource ActionButton}" Margin="4,2" Width="80"/>
+                </Grid>
+            </Grid>
+        </Border>
+
+        <!-- 结果输出 -->
+        <Border Grid.Row="2" Style="{StaticResource GroupBorder}">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                </Grid.RowDefinitions>
+                <TextBlock Text="转换结果" Style="{StaticResource SectionTitle}"/>
+
+                <Grid Grid.Row="1" Margin="4,0,4,2">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="Auto"/>
+                        <ColumnDefinition Width="*"/>
+                        <ColumnDefinition Width="Auto"/>
+                        <ColumnDefinition Width="*"/>
+                    </Grid.ColumnDefinitions>
+
+                    <TextBlock Grid.Column="0" Text="纬度" Style="{StaticResource ConfigLabel}" Width="40"/>
+                    <TextBox Grid.Column="1" Text="{Binding Latitude}" Style="{StaticResource ResultText}"/>
+
+                    <TextBlock Grid.Column="2" Text="经度" Style="{StaticResource ConfigLabel}" Width="40" Margin="8,2,2,2"/>
+                    <TextBox Grid.Column="3" Text="{Binding Longitude}" Style="{StaticResource ResultText}"/>
+                </Grid>
+
+                <Grid Grid.Row="2" Margin="4,0,4,4">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="Auto"/>
+                        <ColumnDefinition Width="*"/>
+                        <ColumnDefinition Width="Auto"/>
+                    </Grid.ColumnDefinitions>
+
+                    <TextBlock Grid.Column="0" Text="坐标" Style="{StaticResource ConfigLabel}" Width="40"/>
+                    <TextBox Grid.Column="1" Text="{Binding LatLong}" Style="{StaticResource ResultText}"/>
+                    <Button Grid.Column="2" Content="清空" Command="{Binding ClearCommand}"
+                            Style="{StaticResource ActionButton}" Margin="4,2" Width="60"/>
+                </Grid>
+            </Grid>
+        </Border>
+    </Grid>
+</Window>
Added +16 -0
diff --git a/CrazyCoder/Views/GpsWindow.xaml.cs b/CrazyCoder/Views/GpsWindow.xaml.cs
new file mode 100644
index 0000000..e89ff62
--- /dev/null
+++ b/CrazyCoder/Views/GpsWindow.xaml.cs
@@ -0,0 +1,16 @@
+using System.Windows;
+using CrazyCoder.ViewModels;
+
+namespace CrazyCoder.Views;
+
+/// <summary>GPS 辅助工具窗口</summary>
+public partial class GpsWindow : Window
+{
+    /// <summary>实例化 GPS 辅助工具窗口</summary>
+    public GpsWindow()
+    {
+        InitializeComponent();
+
+        DataContext = new GpsViewModel();
+    }
+}
Added +173 -0
diff --git a/CrazyCoder/Views/IconToolWindow.xaml b/CrazyCoder/Views/IconToolWindow.xaml
new file mode 100644
index 0000000..2c8d415
--- /dev/null
+++ b/CrazyCoder/Views/IconToolWindow.xaml
@@ -0,0 +1,173 @@
+<Window x:Class="CrazyCoder.Views.IconToolWindow"
+        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="1100" 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="26"/>
+            <Setter Property="VerticalContentAlignment" Value="Center"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+        </Style>
+
+        <Style x:Key="SmallNumeric" TargetType="TextBox">
+            <Setter Property="Width" Value="70"/>
+            <Setter Property="Height" Value="26"/>
+            <Setter Property="VerticalContentAlignment" Value="Center"/>
+            <Setter Property="HorizontalContentAlignment" Value="Right"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="Margin" Value="2"/>
+        </Style>
+
+        <Style x:Key="ActionButton" TargetType="Button">
+            <Setter Property="Height" Value="30"/>
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Padding" Value="8,0"/>
+        </Style>
+    </Window.Resources>
+
+    <Grid Margin="6">
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="260"/>
+            <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}"/>
+                        <Button Content="选择图片" Command="{Binding LoadImageCommand}"
+                                Style="{StaticResource ActionButton}" Margin="4,2"/>
+                        <TextBlock Text="提示:支持拖放图片到右侧预览区" Margin="4,0,4,4"
+                                   Foreground="Gray" FontSize="11"/>
+                    </StackPanel>
+                </Border>
+
+                <!-- 水印设置 -->
+                <Border Style="{StaticResource GroupBorder}">
+                    <StackPanel>
+                        <TextBlock Text="水印设置" Style="{StaticResource SectionTitle}"/>
+
+                        <TextBlock Text="文字内容" Style="{StaticResource ConfigLabel}"/>
+                        <TextBox Text="{Binding WatermarkText, UpdateSourceTrigger=PropertyChanged}"
+                                 Style="{StaticResource ConfigTextBox}"/>
+
+                        <Grid Margin="4,2">
+                            <Grid.RowDefinitions>
+                                <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}" Width="40"/>
+                            <ComboBox Grid.Row="0" Grid.Column="1" ItemsSource="{Binding FontNames}"
+                                      Text="{Binding FontName, UpdateSourceTrigger=PropertyChanged}"
+                                      Height="26" Margin="2" IsEditable="True"/>
+
+                            <TextBlock Grid.Row="1" Grid.Column="0" Text="字号" Style="{StaticResource ConfigLabel}" Width="40"/>
+                            <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding FontSize, UpdateSourceTrigger=PropertyChanged}"
+                                     Style="{StaticResource SmallNumeric}"/>
+                        </Grid>
+
+                        <Grid Margin="4,2">
+                            <Grid.RowDefinitions>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition Height="Auto"/>
+                            </Grid.RowDefinitions>
+                            <Grid.ColumnDefinitions>
+                                <ColumnDefinition Width="Auto"/>
+                                <ColumnDefinition Width="*"/>
+                                <ColumnDefinition Width="Auto"/>
+                                <ColumnDefinition Width="*"/>
+                            </Grid.ColumnDefinitions>
+
+                            <TextBlock Grid.Row="0" Grid.Column="0" Text="X" Style="{StaticResource ConfigLabel}" Width="20"/>
+                            <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding WatermarkX, UpdateSourceTrigger=PropertyChanged}"
+                                     Style="{StaticResource SmallNumeric}" Width="Auto"/>
+                            <TextBlock Grid.Row="0" Grid.Column="2" Text="Y" Style="{StaticResource ConfigLabel}" Width="20" Margin="4,2,2,2"/>
+                            <TextBox Grid.Row="0" Grid.Column="3" Text="{Binding WatermarkY, UpdateSourceTrigger=PropertyChanged}"
+                                     Style="{StaticResource SmallNumeric}" Width="Auto"/>
+
+                            <TextBlock Grid.Row="1" Grid.Column="0" Text="颜色" Style="{StaticResource ConfigLabel}" Width="40"/>
+                            <TextBox Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3"
+                                     Text="{Binding WatermarkColor, UpdateSourceTrigger=PropertyChanged}"
+                                     Style="{StaticResource ConfigTextBox}"/>
+                        </Grid>
+
+                        <Button Content="应用水印" Command="{Binding MakeWaterCommand}"
+                                Style="{StaticResource ActionButton}" Margin="4,2"/>
+                    </StackPanel>
+                </Border>
+
+                <!-- ICO 图标生成 -->
+                <Border Style="{StaticResource GroupBorder}">
+                    <StackPanel>
+                        <TextBlock Text="ICO 图标生成" Style="{StaticResource SectionTitle}"/>
+
+                        <ItemsControl ItemsSource="{Binding IconSizes}" Margin="4,2">
+                            <ItemsControl.ItemTemplate>
+                                <DataTemplate>
+                                    <CheckBox Content="{Binding Name}" IsChecked="{Binding IsSelected}" Margin="2"/>
+                                </DataTemplate>
+                            </ItemsControl.ItemTemplate>
+                        </ItemsControl>
+
+                        <Button Content="生成 ICO 图标" Command="{Binding MakeIcoCommand}"
+                                Style="{StaticResource ActionButton}" Margin="4,2" FontWeight="Bold"/>
+                        <Button Content="保存图片" Command="{Binding SaveImageCommand}"
+                                Style="{StaticResource ActionButton}" Margin="4,2"/>
+                    </StackPanel>
+                </Border>
+            </StackPanel>
+        </ScrollViewer>
+
+        <!-- ============ 右侧:图片预览 ============ -->
+        <Border Grid.Column="1" BorderBrush="#D0D0D0" BorderThickness="1" AllowDrop="True"
+                x:Name="previewBorder">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+
+                <TextBlock Text="图片预览" Style="{StaticResource SectionTitle}"/>
+
+                <ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
+                    <Image x:Name="previewImage" Source="{Binding PreviewImage}"
+                           Stretch="None" Margin="4"
+                           AllowDrop="True"
+                           Drop="OnImageDrop"
+                           DragOver="OnImageDragOver"/>
+                </ScrollViewer>
+            </Grid>
+        </Border>
+    </Grid>
+</Window>
Added +42 -0
diff --git a/CrazyCoder/Views/IconToolWindow.xaml.cs b/CrazyCoder/Views/IconToolWindow.xaml.cs
new file mode 100644
index 0000000..9f4c45e
--- /dev/null
+++ b/CrazyCoder/Views/IconToolWindow.xaml.cs
@@ -0,0 +1,42 @@
+using System.Windows;
+using CrazyCoder.ViewModels;
+
+namespace CrazyCoder.Views;
+
+/// <summary>图标水印处理工具窗口</summary>
+public partial class IconToolWindow : Window
+{
+    /// <summary>ViewModel</summary>
+    public IconToolViewModel ViewModel { get; }
+
+    /// <summary>实例化图标水印处理工具窗口</summary>
+    public IconToolWindow()
+    {
+        InitializeComponent();
+
+        ViewModel = new IconToolViewModel();
+        DataContext = ViewModel;
+    }
+
+    private void OnImageDrop(Object sender, DragEventArgs e)
+    {
+        if (e.Data.GetDataPresent(DataFormats.FileDrop))
+        {
+            var files = (String[])e.Data.GetData(DataFormats.FileDrop);
+            if (files is { Length: > 0 })
+            {
+                ViewModel.LoadFromFile(files[0]);
+            }
+        }
+    }
+
+    private void OnImageDragOver(Object sender, DragEventArgs e)
+    {
+        if (e.Data.GetDataPresent(DataFormats.FileDrop))
+            e.Effects = DragDropEffects.Copy;
+        else
+            e.Effects = DragDropEffects.None;
+
+        e.Handled = true;
+    }
+}
Added +235 -0
diff --git a/CrazyCoder/Views/MqttWindow.xaml b/CrazyCoder/Views/MqttWindow.xaml
new file mode 100644
index 0000000..4d6da4d
--- /dev/null
+++ b/CrazyCoder/Views/MqttWindow.xaml
@@ -0,0 +1,235 @@
+<Window x:Class="CrazyCoder.Views.MqttWindow"
+        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="MQTT 客户端" Height="750" Width="1100" 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="26"/>
+            <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="30"/>
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Padding" Value="8,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>
+    </Window.Resources>
+
+    <Grid Margin="6">
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="300"/>
+            <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}"/>
+
+                        <Grid>
+                            <Grid.RowDefinitions>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition Height="Auto"/>
+                            </Grid.RowDefinitions>
+                            <Grid.ColumnDefinitions>
+                                <ColumnDefinition Width="50"/>
+                                <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 Server, 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 ClientId, 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}"/>
+                            <TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}"
+                                     Style="{StaticResource ConfigTextBox}"/>
+
+                            <Button Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="2"
+                                    Content="{Binding ConnectButtonText}"
+                                    Command="{Binding ToggleConnectCommand}"
+                                    Style="{StaticResource ActionButton}" FontWeight="Bold" Margin="2,4"/>
+                        </Grid>
+                    </StackPanel>
+                </Border>
+
+                <!-- 订阅配置 -->
+                <Border Style="{StaticResource GroupBorder}" IsEnabled="{Binding IsConnected}">
+                    <StackPanel>
+                        <TextBlock Text="订阅配置" Style="{StaticResource SectionTitle}"/>
+
+                        <Grid>
+                            <Grid.RowDefinitions>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition Height="Auto"/>
+                            </Grid.RowDefinitions>
+                            <Grid.ColumnDefinitions>
+                                <ColumnDefinition Width="50"/>
+                                <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 SubscribeTopic, UpdateSourceTrigger=PropertyChanged}"
+                                     Style="{StaticResource ConfigTextBox}"/>
+
+                            <TextBlock Grid.Row="0" Grid.Column="2" Text="QoS" Style="{StaticResource ConfigLabel}" Margin="4,2"/>
+                            <ComboBox Grid.Row="0" Grid.Column="3" ItemsSource="{Binding QosOptions}"
+                                      SelectedIndex="{Binding SubscribeQos}"
+                                      Width="50" Height="26" Margin="2"/>
+
+                            <Button Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3"
+                                    Content="订阅" Command="{Binding SubscribeCommand}"
+                                    Style="{StaticResource ActionButton}" Margin="2,4"/>
+                        </Grid>
+                    </StackPanel>
+                </Border>
+
+                <!-- 发布配置 -->
+                <Border Style="{StaticResource GroupBorder}" IsEnabled="{Binding IsConnected}">
+                    <StackPanel>
+                        <TextBlock Text="发布配置" Style="{StaticResource SectionTitle}"/>
+
+                        <Grid>
+                            <Grid.RowDefinitions>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition Height="Auto"/>
+                            </Grid.RowDefinitions>
+                            <Grid.ColumnDefinitions>
+                                <ColumnDefinition Width="50"/>
+                                <ColumnDefinition Width="*"/>
+                                <ColumnDefinition Width="Auto"/>
+                                <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 PublishTopic, UpdateSourceTrigger=PropertyChanged}"
+                                     Style="{StaticResource ConfigTextBox}"/>
+
+                            <TextBlock Grid.Row="0" Grid.Column="2" Text="QoS" Style="{StaticResource ConfigLabel}" Margin="4,2"/>
+                            <ComboBox Grid.Row="0" Grid.Column="3" ItemsSource="{Binding QosOptions}"
+                                      SelectedIndex="{Binding PublishQos}"
+                                      Width="50" Height="26" Margin="2"/>
+
+                            <TextBlock Grid.Row="1" Grid.Column="0" Text="保留" Style="{StaticResource ConfigLabel}"/>
+                            <CheckBox Grid.Row="1" Grid.Column="1" IsChecked="{Binding Retain}" Margin="4,2"/>
+                        </Grid>
+                    </StackPanel>
+                </Border>
+            </StackPanel>
+        </ScrollViewer>
+
+        <!-- ============ 右侧:日志 + 发送 ============ -->
+        <Grid Grid.Column="1">
+            <Grid.RowDefinitions>
+                <RowDefinition Height="3*"/>
+                <RowDefinition Height="4"/>
+                <RowDefinition Height="2*"/>
+            </Grid.RowDefinitions>
+
+            <!-- 收发日志 -->
+            <Border Grid.Row="0" Style="{StaticResource GroupBorder}">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                    </Grid.RowDefinitions>
+
+                    <Grid>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="*"/>
+                            <ColumnDefinition Width="Auto"/>
+                        </Grid.ColumnDefinitions>
+                        <TextBlock Grid.Column="0" Text="收发日志" Style="{StaticResource SectionTitle}"/>
+                        <Button Grid.Column="1" Content="清空" Command="{Binding ClearLogCommand}"
+                                Style="{StaticResource ActionButton}" Margin="2"/>
+                    </Grid>
+
+                    <RichTextBox x:Name="txtLog" Grid.Row="1"
+                                 IsReadOnly="True" VerticalScrollBarVisibility="Auto"
+                                 FontFamily="Consolas" FontSize="12"
+                                 Background="#1E1E1E" Foreground="#D4D4D4"
+                                 BorderThickness="0" Margin="2"/>
+                </Grid>
+            </Border>
+
+            <GridSplitter Grid.Row="1" Height="4" HorizontalAlignment="Stretch" Background="#F0F0F0"/>
+
+            <!-- 发送区 -->
+            <Border Grid.Row="2" Style="{StaticResource GroupBorder}" IsEnabled="{Binding IsConnected}">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                    </Grid.RowDefinitions>
+
+                    <TextBlock Text="发送区" Style="{StaticResource SectionTitle}"/>
+
+                    <Grid Grid.Row="1" Margin="2">
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="*"/>
+                            <ColumnDefinition Width="120"/>
+                        </Grid.ColumnDefinitions>
+
+                        <TextBox Grid.Column="0"
+                                 Text="{Binding PublishText, UpdateSourceTrigger=PropertyChanged}"
+                                 AcceptsReturn="True" AcceptsTab="True"
+                                 VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
+                                 FontFamily="Consolas" FontSize="13"
+                                 BorderThickness="1" BorderBrush="#D0D0D0"
+                                 Margin="0,0,4,0"/>
+
+                        <Button Grid.Column="1" Content="发送"
+                                Command="{Binding PublishCommand}"
+                                Style="{StaticResource SendButton}"
+                                VerticalAlignment="Bottom"/>
+                    </Grid>
+                </Grid>
+            </Border>
+        </Grid>
+    </Grid>
+</Window>
Added +49 -0
diff --git a/CrazyCoder/Views/MqttWindow.xaml.cs b/CrazyCoder/Views/MqttWindow.xaml.cs
new file mode 100644
index 0000000..6ee1dec
--- /dev/null
+++ b/CrazyCoder/Views/MqttWindow.xaml.cs
@@ -0,0 +1,49 @@
+using System.Windows;
+using System.Windows.Documents;
+using System.Windows.Media;
+using CrazyCoder.ViewModels;
+
+namespace CrazyCoder.Views;
+
+/// <summary>MQTT 客户端窗口</summary>
+public partial class MqttWindow : Window
+{
+    /// <summary>ViewModel</summary>
+    public MqttViewModel ViewModel { get; }
+
+    /// <summary>实例化 MQTT 客户端窗口</summary>
+    public MqttWindow()
+    {
+        InitializeComponent();
+
+        ViewModel = new MqttViewModel();
+        DataContext = ViewModel;
+
+        // 绑定日志回调
+        ViewModel.OnLog += AppendLog;
+    }
+
+    private void AppendLog(String msg)
+    {
+        if (!Dispatcher.CheckAccess())
+        {
+            Dispatcher.Invoke(() => AppendLog(msg));
+            return;
+        }
+
+        if (msg == "__CLEAR__")
+        {
+            txtLog.Document.Blocks.Clear();
+            return;
+        }
+
+        var paragraph = new Paragraph(new Run(msg))
+        {
+            Margin = new Thickness(0),
+            Padding = new Thickness(0)
+        };
+
+        txtLog.Document.Blocks.Add(paragraph);
+        txtLog.ScrollToEnd();
+    }
+}
Added +305 -0
diff --git a/CrazyCoder/Views/NetworkWindow.xaml b/CrazyCoder/Views/NetworkWindow.xaml
new file mode 100644
index 0000000..fb02526
--- /dev/null
+++ b/CrazyCoder/Views/NetworkWindow.xaml
@@ -0,0 +1,305 @@
+<Window x:Class="CrazyCoder.Views.NetworkWindow"
+        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="1100" 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="26"/>
+            <Setter Property="VerticalContentAlignment" Value="Center"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+        </Style>
+
+        <Style x:Key="OptionCheckBox" TargetType="CheckBox">
+            <Setter Property="Margin" Value="4,2"/>
+        </Style>
+
+        <Style x:Key="ActionButton" TargetType="Button">
+            <Setter Property="Height" Value="30"/>
+            <Setter Property="Margin" Value="2"/>
+        </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="SmallNumeric" TargetType="TextBox">
+            <Setter Property="Width" Value="60"/>
+            <Setter Property="Height" Value="26"/>
+            <Setter Property="VerticalContentAlignment" Value="Center"/>
+            <Setter Property="HorizontalContentAlignment" Value="Right"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="Margin" Value="2"/>
+        </Style>
+    </Window.Resources>
+
+    <Grid Margin="6">
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="300"/>
+            <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.Style>
+                                <Style TargetType="ComboBox">
+                                    <Setter Property="IsEnabled" Value="True"/>
+                                    <Style.Triggers>
+                                        <DataTrigger Binding="{Binding IsConnected}" Value="True">
+                                            <Setter Property="IsEnabled" Value="False"/>
+                                        </DataTrigger>
+                                    </Style.Triggers>
+                                </Style>
+                            </ComboBox.Style>
+                        </ComboBox>
+                    </StackPanel>
+                </Border>
+
+                <!-- 连接配置 -->
+                <Border Style="{StaticResource GroupBorder}">
+                    <StackPanel>
+                        <TextBlock Text="连接配置" Style="{StaticResource SectionTitle}"/>
+
+                        <Grid>
+                            <Grid.RowDefinitions>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition Height="Auto"/>
+                            </Grid.RowDefinitions>
+                            <Grid.ColumnDefinitions>
+                                <ColumnDefinition Width="50"/>
+                                <ColumnDefinition Width="*"/>
+                            </Grid.ColumnDefinitions>
+
+                            <TextBlock Grid.Row="0" Grid.Column="0" Text="本地" Style="{StaticResource ConfigLabel}"/>
+                            <ComboBox Grid.Row="0" Grid.Column="1" ItemsSource="{Binding LocalAddresses}"
+                                      Text="{Binding LocalAddress, UpdateSourceTrigger=PropertyChanged}"
+                                      Height="26" Margin="2" IsEditable="True">
+                                <ComboBox.Style>
+                                    <Style TargetType="ComboBox">
+                                        <Setter Property="IsEnabled" Value="True"/>
+                                        <Style.Triggers>
+                                            <DataTrigger Binding="{Binding IsConnected}" Value="True">
+                                                <Setter Property="IsEnabled" Value="False"/>
+                                            </DataTrigger>
+                                        </Style.Triggers>
+                                    </Style>
+                                </ComboBox.Style>
+                            </ComboBox>
+
+                            <TextBlock Grid.Row="1" Grid.Column="0" Text="远程" Style="{StaticResource ConfigLabel}"/>
+                            <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding RemoteAddress, UpdateSourceTrigger=PropertyChanged}">
+                                <TextBox.Style>
+                                    <Style TargetType="TextBox" BasedOn="{StaticResource ConfigTextBox}">
+                                        <Setter Property="IsEnabled" Value="True"/>
+                                        <Style.Triggers>
+                                            <DataTrigger Binding="{Binding IsConnected}" Value="True">
+                                                <Setter Property="IsEnabled" Value="False"/>
+                                            </DataTrigger>
+                                        </Style.Triggers>
+                                    </Style>
+                                </TextBox.Style>
+                            </TextBox>
+
+                            <TextBlock Grid.Row="2" Grid.Column="0" Text="端口" Style="{StaticResource ConfigLabel}"/>
+                            <Grid Grid.Row="2" Grid.Column="1">
+                                <Grid.ColumnDefinitions>
+                                    <ColumnDefinition Width="*"/>
+                                    <ColumnDefinition Width="Auto"/>
+                                </Grid.ColumnDefinitions>
+                                <TextBox Grid.Column="0" Text="{Binding Port, UpdateSourceTrigger=PropertyChanged}">
+                                    <TextBox.Style>
+                                        <Style TargetType="TextBox" BasedOn="{StaticResource ConfigTextBox}">
+                                            <Setter Property="IsEnabled" Value="True"/>
+                                            <Style.Triggers>
+                                                <DataTrigger Binding="{Binding IsConnected}" Value="True">
+                                                    <Setter Property="IsEnabled" Value="False"/>
+                                                </DataTrigger>
+                                            </Style.Triggers>
+                                        </Style>
+                                    </TextBox.Style>
+                                </TextBox>
+                                <Button Grid.Column="1" Content="{Binding ConnectButtonText}"
+                                        Command="{Binding ToggleConnectCommand}"
+                                        Width="80" Style="{StaticResource ActionButton}"
+                                        FontWeight="Bold" Margin="4,2"/>
+                            </Grid>
+                        </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}"/>
+
+                        <Button Content="清空接收日志" Command="{Binding ClearReceiveLogCommand}"
+                                Style="{StaticResource ActionButton}" Margin="4,4"/>
+                    </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="*"/>
+                                <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 SendTimes, UpdateSourceTrigger=PropertyChanged}"
+                                     Style="{StaticResource SmallNumeric}" Width="Auto"/>
+                            <TextBlock Grid.Row="0" Grid.Column="2" Text="次" Style="{StaticResource ConfigLabel}" Margin="2,2,4,2"/>
+
+                            <TextBlock Grid.Row="1" Grid.Column="0" Text="间隔" Style="{StaticResource ConfigLabel}"/>
+                            <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding SendSleep, UpdateSourceTrigger=PropertyChanged}"
+                                     Style="{StaticResource SmallNumeric}" Width="Auto"/>
+                            <TextBlock Grid.Row="1" Grid.Column="2" Text="毫秒" Style="{StaticResource ConfigLabel}" Margin="2,2,4,2"/>
+
+                            <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 SmallNumeric}" Width="Auto"/>
+                            <TextBlock Grid.Row="2" Grid.Column="2" Text="连接" Style="{StaticResource ConfigLabel}" Margin="2,2,4,2"/>
+                        </Grid>
+                    </StackPanel>
+                </Border>
+
+                <!-- 统计信息 -->
+                <Border Style="{StaticResource GroupBorder}">
+                    <StackPanel>
+                        <TextBlock Text="统计信息" Style="{StaticResource SectionTitle}"/>
+                        <Grid Margin="4,2">
+                            <Grid.RowDefinitions>
+                                <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}"/>
+                            <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding ReceivedBytes, StringFormat={}{0:N0} 字节}"
+                                       Style="{StaticResource ConfigLabel}" FontWeight="Bold"/>
+
+                            <TextBlock Grid.Row="1" Grid.Column="0" Text="已发送" Style="{StaticResource ConfigLabel}"/>
+                            <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding SentBytes, StringFormat={}{0:N0} 字节}"
+                                       Style="{StaticResource ConfigLabel}" FontWeight="Bold"/>
+
+                            <TextBlock Grid.Row="2" Grid.Column="0" Text="连接数" Style="{StaticResource ConfigLabel}"/>
+                            <TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding SessionCount, StringFormat={}{0} 个}"
+                                       Style="{StaticResource ConfigLabel}" FontWeight="Bold"/>
+                        </Grid>
+                    </StackPanel>
+                </Border>
+            </StackPanel>
+        </ScrollViewer>
+
+        <!-- ============ 右侧:接收区 + 发送区 ============ -->
+        <Grid Grid.Column="1">
+            <Grid.RowDefinitions>
+                <RowDefinition Height="3*"/>
+                <RowDefinition Height="4"/>
+                <RowDefinition Height="2*"/>
+            </Grid.RowDefinitions>
+
+            <!-- 接收日志区 -->
+            <Border Grid.Row="0" Style="{StaticResource GroupBorder}">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                    </Grid.RowDefinitions>
+                    <TextBlock Grid.Row="0" Text="接收日志" Style="{StaticResource SectionTitle}"/>
+                    <RichTextBox x:Name="txtReceive" Grid.Row="1"
+                                 IsReadOnly="True" VerticalScrollBarVisibility="Auto"
+                                 FontFamily="Consolas" FontSize="12"
+                                 Background="#1E1E1E" Foreground="#D4D4D4"
+                                 BorderThickness="0" Margin="2"/>
+                </Grid>
+            </Border>
+
+            <GridSplitter Grid.Row="1" Height="4" HorizontalAlignment="Stretch" Background="#F0F0F0"/>
+
+            <!-- 发送区 -->
+            <Border Grid.Row="2" Style="{StaticResource GroupBorder}">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                    </Grid.RowDefinitions>
+
+                    <TextBlock Grid.Row="0" Text="发送区" Style="{StaticResource SectionTitle}"/>
+
+                    <Grid Grid.Row="1" Margin="2">
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="*"/>
+                            <ColumnDefinition Width="120"/>
+                        </Grid.ColumnDefinitions>
+
+                        <TextBox Grid.Column="0"
+                                 Text="{Binding SendText, UpdateSourceTrigger=PropertyChanged}"
+                                 AcceptsReturn="True" AcceptsTab="True"
+                                 VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
+                                 FontFamily="Consolas" FontSize="13"
+                                 BorderThickness="1" BorderBrush="#D0D0D0"
+                                 Margin="0,0,4,0"/>
+
+                        <Button Grid.Column="1" Content="发送"
+                                Command="{Binding SendCommand}"
+                                Style="{StaticResource SendButton}"
+                                VerticalAlignment="Bottom"/>
+                    </Grid>
+                </Grid>
+            </Border>
+        </Grid>
+    </Grid>
+</Window>
Added +49 -0
diff --git a/CrazyCoder/Views/NetworkWindow.xaml.cs b/CrazyCoder/Views/NetworkWindow.xaml.cs
new file mode 100644
index 0000000..b83b83c
--- /dev/null
+++ b/CrazyCoder/Views/NetworkWindow.xaml.cs
@@ -0,0 +1,49 @@
+using System.Windows;
+using System.Windows.Documents;
+using System.Windows.Media;
+using CrazyCoder.ViewModels;
+
+namespace CrazyCoder.Views;
+
+/// <summary>网络调试工具窗口</summary>
+public partial class NetworkWindow : Window
+{
+    /// <summary>ViewModel</summary>
+    public NetworkViewModel ViewModel { get; }
+
+    /// <summary>实例化网络调试工具窗口</summary>
+    public NetworkWindow()
+    {
+        InitializeComponent();
+
+        ViewModel = new NetworkViewModel();
+        DataContext = ViewModel;
+
+        // 绑定日志回调
+        ViewModel.OnLog += AppendLog;
+    }
+
+    private void AppendLog(String msg)
+    {
+        if (!Dispatcher.CheckAccess())
+        {
+            Dispatcher.Invoke(() => AppendLog(msg));
+            return;
+        }
+
+        if (msg == "__CLEAR__")
+        {
+            txtReceive.Document.Blocks.Clear();
+            return;
+        }
+
+        var paragraph = new Paragraph(new Run(msg))
+        {
+            Margin = new Thickness(0),
+            Padding = new Thickness(0)
+        };
+
+        txtReceive.Document.Blocks.Add(paragraph);
+        txtReceive.ScrollToEnd();
+    }
+}
Modified +214 -4
diff --git a/CrazyCoder/Views/RegexWindow.xaml b/CrazyCoder/Views/RegexWindow.xaml
index 9a5465d..e2201bb 100644
--- a/CrazyCoder/Views/RegexWindow.xaml
+++ b/CrazyCoder/Views/RegexWindow.xaml
@@ -3,10 +3,220 @@
         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"
-        xmlns:local="clr-namespace:CrazyCoder.Views"
+        xmlns:vm="clr-namespace:CrazyCoder.ViewModels"
+        xmlns:models="clr-namespace:CrazyCoder.Models"
         mc:Ignorable="d"
-        Title="正则表达式" Height="450" Width="800">
-    <Grid>
-        <TextBlock Text="正则表达式"/>
+        Title="正则表达式" Height="700" Width="1100" WindowStartupLocation="CenterScreen">
+    <Window.Resources>
+        <Style x:Key="ResultListView" TargetType="ListView">
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="Margin" Value="2"/>
+        </Style>
+
+        <Style x:Key="CodeTextBox" TargetType="TextBox">
+            <Setter Property="FontFamily" Value="Consolas"/>
+            <Setter Property="FontSize" Value="14"/>
+            <Setter Property="AcceptsReturn" Value="True"/>
+            <Setter Property="AcceptsTab" Value="True"/>
+            <Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
+            <Setter Property="HorizontalScrollBarVisibility" Value="Auto"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+        </Style>
+    </Window.Resources>
+
+    <Grid Margin="8">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="*"/>
+            <RowDefinition Height="3*"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+        </Grid.RowDefinitions>
+
+        <!-- 第1行:正则表达式输入 -->
+        <Border Grid.Row="0" BorderBrush="#D0D0D0" BorderThickness="1" Background="#FFF5E6" Margin="0,0,0,4">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+                <TextBlock Text="正则表达式" FontWeight="Bold" Margin="4,2"/>
+                <TextBox Grid.Row="1" Text="{Binding Pattern, UpdateSourceTrigger=PropertyChanged}"
+                         Style="{StaticResource CodeTextBox}" Background="#FFF5E6" MinHeight="60"/>
+            </Grid>
+        </Border>
+
+        <!-- 第2行:选项 + 模式切换 + 执行按钮 -->
+        <Grid Grid.Row="1" Margin="0,0,0,4">
+            <Grid.ColumnDefinitions>
+                <ColumnDefinition Width="Auto"/>
+                <ColumnDefinition Width="Auto"/>
+                <ColumnDefinition Width="Auto"/>
+                <ColumnDefinition Width="Auto"/>
+                <ColumnDefinition Width="Auto"/>
+                <ColumnDefinition Width="Auto"/>
+                <ColumnDefinition Width="Auto"/>
+                <ColumnDefinition Width="*"/>
+            </Grid.ColumnDefinitions>
+
+            <CheckBox Grid.Column="0" Content="忽略大小写" IsChecked="{Binding IgnoreCase}" Margin="4,0,8,0" ToolTip="使用不区分大小写的匹配"/>
+            <CheckBox Grid.Column="1" Content="多行模式" IsChecked="{Binding Multiline}" Margin="0,0,8,0" ToolTip="^和$匹配每行的开头和末尾"/>
+            <CheckBox Grid.Column="2" Content="单行模式" IsChecked="{Binding Singleline}" Margin="0,0,8,0" ToolTip="句点(.)匹配每个字符"/>
+            <CheckBox Grid.Column="3" Content="忽略空白" IsChecked="{Binding IgnorePatternWhitespace}" Margin="0,0,8,0" ToolTip="排除模式中的空白并启用#注释"/>
+
+            <Separator Grid.Column="4" Style="{StaticResource {x:Static ToolBar.SeparatorStyleKey}}" Margin="4,0"/>
+
+            <RadioButton Grid.Column="5" Content="匹配" IsChecked="{Binding IsMatchMode}" Margin="8,0,4,0" VerticalAlignment="Center"/>
+            <RadioButton Grid.Column="6" Content="替换" IsChecked="{Binding IsReplaceMode}" Margin="4,0,8,0" VerticalAlignment="Center"/>
+
+            <Button Grid.Column="7" Content="{Binding ExecuteButtonText}" Command="{Binding ExecuteCommand}"
+                    MinWidth="100" Height="28" HorizontalAlignment="Right" Margin="0,0,4,0"/>
+        </Grid>
+
+        <!-- 第3行:替换输入(替换模式可见) -->
+        <Border Grid.Row="2" BorderBrush="#D0D0D0" BorderThickness="1" Background="#E8FFE8" Margin="0,0,0,4"
+                Visibility="{Binding ReplacePanelVisibility}">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+                <TextBlock Text="替换为" FontWeight="Bold" Margin="4,2"/>
+                <TextBox Grid.Row="1" Text="{Binding Replacement, UpdateSourceTrigger=PropertyChanged}"
+                         Style="{StaticResource CodeTextBox}" Background="#E8FFE8" MinHeight="40"/>
+            </Grid>
+        </Border>
+
+        <!-- 第4行:数据源输入 -->
+        <Border Grid.Row="3" BorderBrush="#D0D0D0" BorderThickness="1" Background="#FFFFF0" Margin="0,0,0,4">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+                <TextBlock Text="数据源" FontWeight="Bold" Margin="4,2"/>
+                <TextBox Grid.Row="1" x:Name="txtSource" Text="{Binding SourceText, UpdateSourceTrigger=PropertyChanged}"
+                         Style="{StaticResource CodeTextBox}" Background="#FFFFF0"/>
+            </Grid>
+        </Border>
+
+        <!-- 第5行:结果区域 -->
+        <Grid Grid.Row="4" Visibility="{Binding ResultPanelVisibility}">
+            <Grid.ColumnDefinitions>
+                <ColumnDefinition Width="*"/>
+                <ColumnDefinition Width="*"/>
+                <ColumnDefinition Width="*"/>
+            </Grid.ColumnDefinitions>
+
+            <!-- Match -->
+            <Border Grid.Column="0" BorderBrush="#D0D0D0" BorderThickness="1" Margin="0,0,2,0">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                    </Grid.RowDefinitions>
+                    <TextBlock Text="匹配结果 (Match)" FontWeight="Bold" Background="#FFD0FF" Padding="4,2"/>
+                    <ListView Grid.Row="1" x:Name="lvMatch" ItemsSource="{Binding Matches}" SelectedItem="{Binding SelectedMatch}"
+                              Background="#FFD0FF" BorderThickness="0">
+                        <ListView.View>
+                            <GridView>
+                                <GridViewColumn Header="序号" Width="40" DisplayMemberBinding="{Binding Index}"/>
+                                <GridViewColumn Header="匹配项" Width="150" DisplayMemberBinding="{Binding Value}"/>
+                                <GridViewColumn Header="位置" Width="120" DisplayMemberBinding="{Binding Location}"/>
+                            </GridView>
+                        </ListView.View>
+                    </ListView>
+                </Grid>
+            </Border>
+
+            <!-- Group -->
+            <Border Grid.Column="1" BorderBrush="#D0D0D0" BorderThickness="1" Margin="1,0">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                    </Grid.RowDefinitions>
+                    <TextBlock Text="分组 (Group)" FontWeight="Bold" Background="#D0FFD0" Padding="4,2"/>
+                    <ListView Grid.Row="1" x:Name="lvGroup" ItemsSource="{Binding Groups}" SelectedItem="{Binding SelectedGroup}"
+                              Background="#D0FFD0" BorderThickness="0">
+                        <ListView.View>
+                            <GridView>
+                                <GridViewColumn Header="序号" Width="40" DisplayMemberBinding="{Binding Index}"/>
+                                <GridViewColumn Header="名称" Width="60" DisplayMemberBinding="{Binding Name}"/>
+                                <GridViewColumn Header="值" Width="130" DisplayMemberBinding="{Binding Value}"/>
+                                <GridViewColumn Header="位置" Width="100" DisplayMemberBinding="{Binding Location}"/>
+                            </GridView>
+                        </ListView.View>
+                    </ListView>
+                </Grid>
+            </Border>
+
+            <!-- Capture -->
+            <Border Grid.Column="2" BorderBrush="#D0D0D0" BorderThickness="1" Margin="2,0,0,0">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                    </Grid.RowDefinitions>
+                    <TextBlock Text="捕获 (Capture)" FontWeight="Bold" Background="#D0D0FF" Padding="4,2"/>
+                    <ListView Grid.Row="1" x:Name="lvCapture" ItemsSource="{Binding Captures}" SelectedItem="{Binding SelectedCapture}"
+                              Background="#D0D0FF" BorderThickness="0">
+                        <ListView.View>
+                            <GridView>
+                                <GridViewColumn Header="序号" Width="40" DisplayMemberBinding="{Binding Index}"/>
+                                <GridViewColumn Header="值" Width="150" DisplayMemberBinding="{Binding Value}"/>
+                                <GridViewColumn Header="位置" Width="100" DisplayMemberBinding="{Binding Location}"/>
+                            </GridView>
+                        </ListView.View>
+                    </ListView>
+                </Grid>
+            </Border>
+        </Grid>
+
+        <!-- 第6行:批量替换 -->
+        <Grid Grid.Row="5" Margin="0,4" Visibility="{Binding BatchPanelVisibility}">
+            <Grid.ColumnDefinitions>
+                <ColumnDefinition Width="Auto"/>
+                <ColumnDefinition Width="*"/>
+                <ColumnDefinition Width="Auto"/>
+                <ColumnDefinition Width="Auto"/>
+                <ColumnDefinition Width="Auto"/>
+            </Grid.ColumnDefinitions>
+
+            <Button Grid.Column="0" Content="打开目录" Command="{Binding BrowseDirectoryCommand}" MinWidth="80" Height="26" Margin="0,0,4,0"/>
+            <TextBox Grid.Column="1" Text="{Binding DirectoryPath, UpdateSourceTrigger=PropertyChanged}" IsReadOnly="True"
+                     Background="#F0F0F0" VerticalAlignment="Center" Height="26" Margin="0,0,4,0"/>
+            <TextBlock Grid.Column="2" Text="过滤:" VerticalAlignment="Center" Margin="0,0,4,0"/>
+            <TextBox Grid.Column="3" Text="{Binding FileFilter, UpdateSourceTrigger=PropertyChanged}" Width="80" VerticalAlignment="Center" Height="26" Margin="0,0,4,0"/>
+            <Button Grid.Column="4" Content="全部替换" Command="{Binding BatchReplaceCommand}" MinWidth="80" Height="26"/>
+        </Grid>
+
+        <!-- 第7行:状态栏 -->
+        <StatusBar Grid.Row="6" Margin="0,2,0,0">
+            <StatusBar.ItemsPanel>
+                <ItemsPanelTemplate>
+                    <Grid>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="Auto"/>
+                            <ColumnDefinition Width="*"/>
+                            <ColumnDefinition Width="Auto"/>
+                        </Grid.ColumnDefinitions>
+                    </Grid>
+                </ItemsPanelTemplate>
+            </StatusBar.ItemsPanel>
+            <StatusBarItem>
+                <TextBlock Text="选项:" FontWeight="Bold"/>
+            </StatusBarItem>
+            <StatusBarItem Grid.Column="1">
+                <TextBlock Text="{Binding OptionText}" Margin="4,0,0,0"/>
+            </StatusBarItem>
+            <StatusBarItem Grid.Column="2">
+                <TextBlock Text="{Binding Status}" Margin="4,0,0,0"/>
+            </StatusBarItem>
+        </StatusBar>
     </Grid>
 </Window>
Modified +60 -15
diff --git a/CrazyCoder/Views/RegexWindow.xaml.cs b/CrazyCoder/Views/RegexWindow.xaml.cs
index 5265977..662cc1f 100644
--- a/CrazyCoder/Views/RegexWindow.xaml.cs
+++ b/CrazyCoder/Views/RegexWindow.xaml.cs
@@ -1,27 +1,72 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows;
+using System.Windows;
 using System.Windows.Controls;
-using System.Windows.Data;
-using System.Windows.Documents;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using System.Windows.Shapes;
+using CrazyCoder.ViewModels;
 
 namespace CrazyCoder.Views
 {
-    /// <summary>
-    /// Regex.xaml 的交互逻辑
-    /// </summary>
+    /// <summary>正则表达式工具窗口</summary>
     public partial class RegexWindow : Window
     {
+        /// <summary>ViewModel</summary>
+        public RegexViewModel ViewModel { get; }
+
+        /// <summary>实例化正则表达式工具窗口</summary>
         public RegexWindow()
         {
             InitializeComponent();
+
+            ViewModel = new RegexViewModel();
+            DataContext = ViewModel;
+
+            // 选中匹配/分组/捕获项时在源文本中高亮
+            lvMatch.SelectionChanged += OnMatchSelectionChanged;
+            lvGroup.SelectionChanged += OnGroupSelectionChanged;
+            lvCapture.SelectionChanged += OnCaptureSelectionChanged;
+        }
+
+        /// <summary>匹配项选中变更 - 在源文本中高亮</summary>
+        private void OnMatchSelectionChanged(Object sender, SelectionChangedEventArgs e)
+        {
+            if (ViewModel.SelectedMatch is not { } match) return;
+            if (match.Match == null) return;
+
+            txtSource.Focus();
+            txtSource.Select(match.Position, match.Length);
+            txtSource.ScrollToLine(GetLineFromIndex(txtSource.Text, match.Position) - 1);
+        }
+
+        /// <summary>分组选中变更 - 在源文本中高亮</summary>
+        private void OnGroupSelectionChanged(Object sender, SelectionChangedEventArgs e)
+        {
+            if (ViewModel.SelectedGroup is not { } group) return;
+            if (group.Group == null) return;
+
+            txtSource.Focus();
+            txtSource.Select(group.Position, group.Length);
+            txtSource.ScrollToLine(GetLineFromIndex(txtSource.Text, group.Position) - 1);
+        }
+
+        /// <summary>捕获选中变更 - 在源文本中高亮</summary>
+        private void OnCaptureSelectionChanged(Object sender, SelectionChangedEventArgs e)
+        {
+            if (ViewModel.SelectedCapture is not { } capture) return;
+            if (capture.Capture == null) return;
+
+            txtSource.Focus();
+            txtSource.Select(capture.Position, capture.Length);
+            txtSource.ScrollToLine(GetLineFromIndex(txtSource.Text, capture.Position) - 1);
+        }
+
+        /// <summary>从索引计算行号(1-based)</summary>
+        private static Int32 GetLineFromIndex(String text, Int32 index)
+        {
+            if (String.IsNullOrEmpty(text) || index <= 0) return 1;
+            var line = 1;
+            for (var i = 0; i < index && i < text.Length; i++)
+            {
+                if (text[i] == '\n') line++;
+            }
+            return line;
         }
     }
 }
Added +196 -0
diff --git a/CrazyCoder/Views/SecurityWindow.xaml b/CrazyCoder/Views/SecurityWindow.xaml
new file mode 100644
index 0000000..3d14198
--- /dev/null
+++ b/CrazyCoder/Views/SecurityWindow.xaml
@@ -0,0 +1,196 @@
+<Window x:Class="CrazyCoder.Views.SecurityWindow"
+        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="1100" WindowStartupLocation="CenterScreen">
+    <Window.Resources>
+        <Style x:Key="FuncButton" TargetType="Button">
+            <Setter Property="Width" Value="110"/>
+            <Setter Property="Height" Value="46"/>
+            <Setter Property="Margin" Value="3"/>
+            <Setter Property="FontSize" Value="13"/>
+        </Style>
+
+        <Style x:Key="CodeTextBox" TargetType="TextBox">
+            <Setter Property="FontFamily" Value="Consolas"/>
+            <Setter Property="FontSize" Value="13"/>
+            <Setter Property="AcceptsReturn" Value="True"/>
+            <Setter Property="AcceptsTab" 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="GroupBorder" TargetType="Border">
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="Margin" Value="0,0,0,4"/>
+        </Style>
+
+        <Style x:Key="OptionRadioButton" TargetType="RadioButton">
+            <Setter Property="Margin" Value="4,0,8,0"/>
+            <Setter Property="VerticalAlignment" Value="Center"/>
+        </Style>
+
+        <Style x:Key="OptionCheckBox" TargetType="CheckBox">
+            <Setter Property="Margin" Value="4,0,8,0"/>
+            <Setter Property="VerticalAlignment" Value="Center"/>
+        </Style>
+    </Window.Resources>
+
+    <Grid Margin="6">
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="240"/>
+            <ColumnDefinition Width="*"/>
+        </Grid.ColumnDefinitions>
+
+        <!-- 左侧:功能按钮区 -->
+        <Border Grid.Column="0" BorderBrush="#D0D0D0" BorderThickness="1" Margin="0,0,4,0">
+            <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
+                <WrapPanel Orientation="Horizontal" Margin="4">
+                    <Button Content="HEX编码" Style="{StaticResource FuncButton}" Command="{Binding HexEncodeCommand}"/>
+                    <Button Content="HEX解码" Style="{StaticResource FuncButton}" Command="{Binding HexDecodeCommand}"/>
+                    <Button Content="Base64编码" Style="{StaticResource FuncButton}" Command="{Binding Base64EncodeCommand}"/>
+                    <Button Content="Base64解码" Style="{StaticResource FuncButton}" Command="{Binding Base64DecodeCommand}"/>
+                    <Button Content="MD5_32" Style="{StaticResource FuncButton}" Command="{Binding MD5_32Command}"/>
+                    <Button Content="MD5_16" Style="{StaticResource FuncButton}" Command="{Binding MD5_16Command}"/>
+                    <Button Content="SHA1" Style="{StaticResource FuncButton}" Command="{Binding SHA1Command}"/>
+                    <Button Content="SHA256" Style="{StaticResource FuncButton}" Command="{Binding SHA256Command}"/>
+                    <Button Content="SHA384" Style="{StaticResource FuncButton}" Command="{Binding SHA384Command}"/>
+                    <Button Content="SHA512" Style="{StaticResource FuncButton}" Command="{Binding SHA512Command}"/>
+                    <Button Content="CRC_32" Style="{StaticResource FuncButton}" Command="{Binding CRC_32Command}"/>
+                    <Button Content="CRC_16" Style="{StaticResource FuncButton}" Command="{Binding CRC_16Command}"/>
+                    <Button Content="RSA加密" Style="{StaticResource FuncButton}" Command="{Binding RSAEncryptCommand}"/>
+                    <Button Content="RSA解密" Style="{StaticResource FuncButton}" Command="{Binding RSADecryptCommand}"/>
+                    <Button Content="DSA签名" Style="{StaticResource FuncButton}" Command="{Binding DSASignCommand}"/>
+                    <Button Content="DSA验证" Style="{StaticResource FuncButton}" Command="{Binding DSAVerifyCommand}"/>
+                    <Button Content="Url编码" Style="{StaticResource FuncButton}" Command="{Binding UrlEncodeCommand}"/>
+                    <Button Content="Url解码" Style="{StaticResource FuncButton}" Command="{Binding UrlDecodeCommand}"/>
+                    <Button Content="Html编码" Style="{StaticResource FuncButton}" Command="{Binding HtmlEncodeCommand}"/>
+                    <Button Content="Html解码" Style="{StaticResource FuncButton}" Command="{Binding HtmlDecodeCommand}"/>
+                    <Button Content="时间戳" Style="{StaticResource FuncButton}" Command="{Binding TimestampCommand}"/>
+                    <Button Content="机器信息" Style="{StaticResource FuncButton}" Command="{Binding ComputerInfoCommand}"/>
+                    <Button Content="雪花Id" Style="{StaticResource FuncButton}" Command="{Binding SnowflakeCommand}"/>
+                    <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}"/>
+                </WrapPanel>
+            </ScrollViewer>
+        </Border>
+
+        <!-- 右侧:原文、密码、结果 -->
+        <Grid Grid.Column="1">
+            <Grid.RowDefinitions>
+                <RowDefinition Height="3*"/>
+                <RowDefinition Height="2*"/>
+                <RowDefinition Height="3*"/>
+            </Grid.RowDefinitions>
+
+            <!-- 原文区 -->
+            <Border Grid.Row="0" Style="{StaticResource GroupBorder}">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                    </Grid.RowDefinitions>
+
+                    <Grid>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="Auto"/>
+                            <ColumnDefinition Width="Auto"/>
+                            <ColumnDefinition Width="Auto"/>
+                            <ColumnDefinition Width="Auto"/>
+                            <ColumnDefinition Width="*"/>
+                            <ColumnDefinition Width="Auto"/>
+                        </Grid.ColumnDefinitions>
+
+                        <TextBlock Grid.Column="0" Text="原文" FontWeight="Bold" Margin="4,2" VerticalAlignment="Center"/>
+
+                        <RadioButton Grid.Column="1" Content="字符串" Style="{StaticResource OptionRadioButton}"
+                                     IsChecked="{Binding IsSourceString}" GroupName="SourceFormat"/>
+                        <RadioButton Grid.Column="2" Content="HEX" Style="{StaticResource OptionRadioButton}"
+                                     IsChecked="{Binding IsSourceHex}" GroupName="SourceFormat"
+                                     IsEnabled="{Binding SourceHexEnabled}"/>
+                        <RadioButton Grid.Column="3" Content="Base64" Style="{StaticResource OptionRadioButton}"
+                                     IsChecked="{Binding IsSourceBase64}" GroupName="SourceFormat"
+                                     IsEnabled="{Binding SourceBase64Enabled}"/>
+
+                        <Button Grid.Column="5" Content="上下互换" Command="{Binding ExchangeCommand}"
+                                MinWidth="80" Height="28" Margin="0,0,4,0"/>
+                    </Grid>
+
+                    <TextBox Grid.Row="1" x:Name="txtSource"
+                             Text="{Binding SourceText, UpdateSourceTrigger=PropertyChanged}"
+                             Style="{StaticResource CodeTextBox}" MinHeight="80"/>
+                </Grid>
+            </Border>
+
+            <!-- 密码区 -->
+            <Border Grid.Row="1" Style="{StaticResource GroupBorder}">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                    </Grid.RowDefinitions>
+
+                    <Grid>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="Auto"/>
+                            <ColumnDefinition Width="Auto"/>
+                            <ColumnDefinition Width="Auto"/>
+                            <ColumnDefinition Width="Auto"/>
+                        </Grid.ColumnDefinitions>
+
+                        <TextBlock Grid.Column="0" Text="密码 / 时间基准" FontWeight="Bold" Margin="4,2" VerticalAlignment="Center"/>
+
+                        <RadioButton Grid.Column="1" Content="字符串" Style="{StaticResource OptionRadioButton}"
+                                     IsChecked="{Binding IsPassString}" GroupName="PassFormat"/>
+                        <RadioButton Grid.Column="2" Content="HEX" Style="{StaticResource OptionRadioButton}"
+                                     IsChecked="{Binding IsPassHex}" GroupName="PassFormat"/>
+                        <RadioButton Grid.Column="3" Content="Base64" Style="{StaticResource OptionRadioButton}"
+                                     IsChecked="{Binding IsPassBase64}" GroupName="PassFormat"/>
+                    </Grid>
+
+                    <TextBox Grid.Row="1" Text="{Binding PassText, UpdateSourceTrigger=PropertyChanged}"
+                             Style="{StaticResource CodeTextBox}" MinHeight="50"/>
+                </Grid>
+            </Border>
+
+            <!-- 结果区 -->
+            <Border Grid.Row="2" Style="{StaticResource GroupBorder}">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                    </Grid.RowDefinitions>
+
+                    <Grid>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="Auto"/>
+                            <ColumnDefinition Width="Auto"/>
+                            <ColumnDefinition Width="Auto"/>
+                            <ColumnDefinition Width="Auto"/>
+                        </Grid.ColumnDefinitions>
+
+                        <TextBlock Grid.Column="0" Text="结果" FontWeight="Bold" Margin="4,2" VerticalAlignment="Center"/>
+
+                        <CheckBox Grid.Column="1" Content="字符串" Style="{StaticResource OptionCheckBox}"
+                                  IsChecked="{Binding IsResultString}"/>
+                        <CheckBox Grid.Column="2" Content="HEX" Style="{StaticResource OptionCheckBox}"
+                                  IsChecked="{Binding IsResultHex}"/>
+                        <CheckBox Grid.Column="3" Content="Base64" Style="{StaticResource OptionCheckBox}"
+                                  IsChecked="{Binding IsResultBase64}"/>
+                    </Grid>
+
+                    <TextBox Grid.Row="1" x:Name="txtResult"
+                             Text="{Binding ResultText, UpdateSourceTrigger=PropertyChanged}"
+                             Style="{StaticResource CodeTextBox}" IsReadOnly="True" Background="#F5F5F5"/>
+                </Grid>
+            </Border>
+        </Grid>
+    </Grid>
+</Window>
Added +16 -0
diff --git a/CrazyCoder/Views/SecurityWindow.xaml.cs b/CrazyCoder/Views/SecurityWindow.xaml.cs
new file mode 100644
index 0000000..46debcb
--- /dev/null
+++ b/CrazyCoder/Views/SecurityWindow.xaml.cs
@@ -0,0 +1,16 @@
+using System.Windows;
+using CrazyCoder.ViewModels;
+
+namespace CrazyCoder.Views;
+
+/// <summary>加密解密工具窗口</summary>
+public partial class SecurityWindow : Window
+{
+    /// <summary>实例化加密解密工具窗口</summary>
+    public SecurityWindow()
+    {
+        InitializeComponent();
+
+        DataContext = new SecurityViewModel();
+    }
+}
Added +128 -0
diff --git a/CrazyCoder/Views/SpeechWindow.xaml b/CrazyCoder/Views/SpeechWindow.xaml
new file mode 100644
index 0000000..bd8bb8d
--- /dev/null
+++ b/CrazyCoder/Views/SpeechWindow.xaml
@@ -0,0 +1,128 @@
+<Window x:Class="CrazyCoder.Views.SpeechWindow"
+        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="500" 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="34"/>
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Padding" Value="12,0"/>
+            <Setter Property="FontSize" Value="13"/>
+        </Style>
+    </Window.Resources>
+
+    <Grid Margin="8">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="*"/>
+            <RowDefinition Height="Auto"/>
+        </Grid.RowDefinitions>
+
+        <!-- 语音设置 -->
+        <Border Grid.Row="0" Style="{StaticResource GroupBorder}">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                </Grid.RowDefinitions>
+
+                <TextBlock Text="语音设置" Style="{StaticResource SectionTitle}"/>
+
+                <Grid Grid.Row="1" Margin="4,0,4,2">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="Auto"/>
+                        <ColumnDefinition Width="*"/>
+                    </Grid.ColumnDefinitions>
+
+                    <TextBlock Grid.Column="0" Text="语音角色" Style="{StaticResource ConfigLabel}" Width="70"/>
+                    <ComboBox Grid.Column="1" ItemsSource="{Binding Voices}"
+                              SelectedIndex="{Binding SelectedVoiceIndex}"
+                              Height="28" Margin="2"/>
+                </Grid>
+
+                <Grid Grid.Row="2" Margin="4,0,4,4">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="Auto"/>
+                        <ColumnDefinition Width="*"/>
+                        <ColumnDefinition Width="Auto"/>
+                        <ColumnDefinition Width="*"/>
+                    </Grid.ColumnDefinitions>
+
+                    <TextBlock Grid.Column="0" Text="音量" Style="{StaticResource ConfigLabel}" Width="40"/>
+                    <Slider Grid.Column="1" Minimum="0" Maximum="100"
+                            Value="{Binding Volume}" Margin="4,0"
+                            TickFrequency="10" IsSnapToTickEnabled="True"
+                            VerticalAlignment="Center"/>
+                    <TextBlock Grid.Column="2" Text="{Binding Volume, StringFormat={}{0}}" 
+                               Style="{StaticResource ConfigLabel}" Width="40"
+                               TextAlignment="Center"/>
+
+                    <TextBlock Grid.Column="0" Text="语速" Style="{StaticResource ConfigLabel}" Width="40"
+                               Grid.ColumnSpan="2" Visibility="Collapsed"/>
+                    <TextBlock Grid.Column="2" Text="语速" Style="{StaticResource ConfigLabel}" Width="40"
+                               Margin="8,2,2,2"/>
+                    <Slider Grid.Column="3" Minimum="-10" Maximum="10"
+                            Value="{Binding Rate}" Margin="4,0"
+                            TickFrequency="1" IsSnapToTickEnabled="True"
+                            VerticalAlignment="Center"/>
+                    <TextBlock Grid.Column="4" Text="{Binding Rate, StringFormat={}{0}}" 
+                               Style="{StaticResource ConfigLabel}" Width="40"
+                               TextAlignment="Center"/>
+                </Grid>
+            </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 SpeakText, UpdateSourceTrigger=PropertyChanged}"
+                         AcceptsReturn="True" AcceptsTab="True"
+                         VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
+                         FontFamily="Consolas" FontSize="14"
+                         BorderThickness="0" Margin="4"
+                         MinHeight="100"/>
+            </Grid>
+        </Border>
+
+        <!-- 操作按钮 -->
+        <Border Grid.Row="3" Style="{StaticResource GroupBorder}" Padding="4">
+            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
+                <Button Content="朗读" Command="{Binding SpeakCommand}"
+                        Style="{StaticResource ActionButton}" Width="100"/>
+                <Button Content="停止" Command="{Binding StopCommand}"
+                        Style="{StaticResource ActionButton}" Width="100"/>
+                <Button Content="保存为 WAV" Command="{Binding SaveWavCommand}"
+                        Style="{StaticResource ActionButton}" Width="120"/>
+            </StackPanel>
+        </Border>
+    </Grid>
+</Window>
Added +16 -0
diff --git a/CrazyCoder/Views/SpeechWindow.xaml.cs b/CrazyCoder/Views/SpeechWindow.xaml.cs
new file mode 100644
index 0000000..60a22bb
--- /dev/null
+++ b/CrazyCoder/Views/SpeechWindow.xaml.cs
@@ -0,0 +1,16 @@
+using System.Windows;
+using CrazyCoder.ViewModels;
+
+namespace CrazyCoder.Views;
+
+/// <summary>语音助手窗口</summary>
+public partial class SpeechWindow : Window
+{
+    /// <summary>实例化语音助手窗口</summary>
+    public SpeechWindow()
+    {
+        InitializeComponent();
+
+        DataContext = new SpeechViewModel();
+    }
+}