NewLife/XCoder

feat: CrazyCoder 新增3个数据工具

- 数据建模/代码生成器(DataModelingWindow)
- Redis 管理器(RedisManagerWindow)
- 跨库数据同步(DataSyncWindow)

每个工具 = 独立 Window + ViewModel 架构,使用 CommunityToolkit.Mvvm。
已注册到 MainViewModel 菜单列表。

注意:本次提交仅含本次新增的 3 个工具文件,
其他变更(IoControl/ModbusRtu/ModbusTcp/SerialPort/XCoderAv)由单独提交处理。
大石头 authored at 2026-07-14 02:29:50
b46e350
Tree
1 Parent(s) 3a266fe
Summary: 65 changed files with 9397 additions and 97 deletions.
Modified +2 -0
Added +23 -0
Added +36 -0
Added +269 -0
Added +347 -0
Added +491 -0
Modified +8 -2
Added +369 -0
Added +578 -0
Added +517 -0
Added +314 -0
Added +172 -0
Added +20 -0
Added +155 -0
Added +20 -0
Added +368 -0
Added +55 -0
Added +196 -0
Added +55 -0
Added +247 -0
Added +55 -0
Added +106 -0
Added +41 -0
Added +179 -0
Added +59 -0
Renamed +1 -2
XCoderAv/App.xaml → XCoderAv/App.axaml
Added +7 -0
Deleted +0 -13
XCoderAv/App.xaml.cs
Added +80 -0
Added +60 -0
Added +14 -0
Deleted +0 -9
XCoderAv/MainWindow.xaml
Deleted +0 -22
XCoderAv/MainWindow.xaml.cs
Added +144 -0
Added +19 -0
Added +25 -0
Added +28 -0
Added +28 -0
Modified +59 -19
Added +259 -0
Added +87 -0
Added +279 -0
Added +76 -0
Added +274 -0
Added +409 -0
Added +440 -0
Added +743 -0
Added +219 -0
Added +46 -0
Added +55 -0
Added +152 -0
Added +15 -0
Added +150 -0
Added +15 -0
Added +180 -0
Added +37 -0
Added +204 -0
Added +37 -0
Added +220 -0
Added +15 -0
Added +164 -0
Added +15 -0
Added +112 -0
Added +15 -0
Modified +32 -30
Modified +2 -0
diff --git a/CrazyCoder/CrazyCoder.csproj b/CrazyCoder/CrazyCoder.csproj
index 725a910..f6e0fb9 100644
--- a/CrazyCoder/CrazyCoder.csproj
+++ b/CrazyCoder/CrazyCoder.csproj
@@ -45,6 +45,8 @@
     <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="NewLife.IoT" Version="2.7.2026.501" />
+    <PackageReference Include="NewLife.Modbus" Version="2.0.2025.701" />
     <PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0" />
   </ItemGroup>
   <ItemGroup>
Added +23 -0
diff --git a/CrazyCoder/Models/RedisConfig.cs b/CrazyCoder/Models/RedisConfig.cs
new file mode 100644
index 0000000..7bade8f
--- /dev/null
+++ b/CrazyCoder/Models/RedisConfig.cs
@@ -0,0 +1,23 @@
+using System;
+
+namespace CrazyCoder.Models
+{
+    /// <summary>Redis 服务器节点配置</summary>
+    public class RedisConfig
+    {
+        /// <summary>节点名称</summary>
+        public String Name { get; set; } = "";
+
+        /// <summary>服务器地址</summary>
+        public String Server { get; set; } = "127.0.0.1";
+
+        /// <summary>端口</summary>
+        public Int32 Port { get; set; } = 6379;
+
+        /// <summary>用户名</summary>
+        public String Username { get; set; } = "";
+
+        /// <summary>密码</summary>
+        public String Password { get; set; } = "";
+    }
+}
Added +36 -0
diff --git a/CrazyCoder/Models/SyncTableModel.cs b/CrazyCoder/Models/SyncTableModel.cs
new file mode 100644
index 0000000..1d0e4f7
--- /dev/null
+++ b/CrazyCoder/Models/SyncTableModel.cs
@@ -0,0 +1,36 @@
+using System.ComponentModel;
+
+namespace CrazyCoder.Models
+{
+    /// <summary>同步表模型</summary>
+    public class SyncTableModel
+    {
+        /// <summary>表名</summary>
+        [DisplayName("名称")]
+        public String Name { get; set; } = "";
+
+        /// <summary>显示名</summary>
+        [DisplayName("昵称")]
+        public String DisplayName { get; set; } = "";
+
+        /// <summary>启用同步</summary>
+        [DisplayName("同步")]
+        public Boolean EnableSync { get; set; } = true;
+
+        /// <summary>源表行数</summary>
+        [DisplayName("源行数")]
+        public Int32 SourceCount { get; set; } = -1;
+
+        /// <summary>目标表行数</summary>
+        [DisplayName("目标行数")]
+        public Int32 TargetCount { get; set; } = -1;
+
+        /// <summary>已同步行数</summary>
+        [DisplayName("已同步")]
+        public Int32 SyncCount { get; set; }
+
+        /// <summary>备注</summary>
+        [DisplayName("备注")]
+        public String Description { get; set; } = "";
+    }
+}
Added +269 -0
diff --git a/CrazyCoder/ViewModels/DataModelingViewModel.cs b/CrazyCoder/ViewModels/DataModelingViewModel.cs
new file mode 100644
index 0000000..57a0fe4
--- /dev/null
+++ b/CrazyCoder/ViewModels/DataModelingViewModel.cs
@@ -0,0 +1,269 @@
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.IO;
+using System.Windows;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using NewLife;
+using NewLife.Log;
+using NewLife.Reflection;
+using XCode;
+using XCode.Code;
+using XCode.DataAccessLayer;
+
+namespace CrazyCoder.ViewModels;
+
+/// <summary>数据建模/代码生成器 ViewModel</summary>
+public partial class DataModelingViewModel : ObservableObject
+{
+    #region 属性
+
+    /// <summary>当前连接名</summary>
+    [ObservableProperty]
+    private String _connName = "";
+
+    /// <summary>是否已连接</summary>
+    [ObservableProperty]
+    private Boolean _isConnected;
+
+    /// <summary>是否包含视图</summary>
+    [ObservableProperty]
+    private Boolean _includeView = true;
+
+    /// <summary>数据表列表</summary>
+    public ObservableCollection<IDataTable> Tables { get; } = [];
+
+    /// <summary>选中的数据表</summary>
+    [ObservableProperty]
+    private IDataTable _selectedTable;
+
+    /// <summary>命名空间</summary>
+    [ObservableProperty]
+    private String _nameSpace = "";
+
+    /// <summary>实体连接名</summary>
+    [ObservableProperty]
+    private String _entityConnName = "";
+
+    /// <summary>输出路径</summary>
+    [ObservableProperty]
+    private String _outputPath = "";
+
+    /// <summary>实体基类</summary>
+    [ObservableProperty]
+    private String _baseClass = "Entity";
+
+    /// <summary>使用中文文件名</summary>
+    [ObservableProperty]
+    private Boolean _useCNFileName;
+
+    /// <summary>生成泛型实体类</summary>
+    [ObservableProperty]
+    private Boolean _renderGenEntity;
+
+    /// <summary>连接按钮文本</summary>
+    public String ConnectButtonText => IsConnected ? "断开" : "连接";
+
+    partial void OnIsConnectedChanged(Boolean value)
+    {
+        OnPropertyChanged(nameof(ConnectButtonText));
+    }
+
+    /// <summary>状态文本</summary>
+    [ObservableProperty]
+    private String _status = "就绪";
+
+    /// <summary>所有可用连接</summary>
+    public ObservableCollection<String> Connections { get; } = [];
+
+    #endregion
+
+    #region 构造
+
+    /// <summary>实例化数据建模 ViewModel</summary>
+    public DataModelingViewModel()
+    {
+        LoadConnections();
+
+        if (Connections.Count > 0)
+            ConnName = Connections[0];
+    }
+
+    private void LoadConnections()
+    {
+        Connections.Clear();
+        foreach (var item in DAL.ConnStrs.Keys.OrderBy(e => e))
+        {
+            Connections.Add(item);
+        }
+    }
+
+    #endregion
+
+    #region 命令
+
+    /// <summary>连接/断开数据库</summary>
+    [RelayCommand]
+    private async Task Connect()
+    {
+        if (IsConnected)
+        {
+            Tables.Clear();
+            IsConnected = false;
+            Status = "已断开";
+            return;
+        }
+
+        if (ConnName.IsNullOrEmpty()) return;
+
+        try
+        {
+            Status = "正在连接...";
+
+            // 确保连接字符串可用
+            var dal = DAL.Create(ConnName);
+            _ = dal.ConnStr;
+
+            // 异步加载表
+            await Task.Run(() => LoadTables());
+
+            IsConnected = true;
+            Status = $"已连接 {ConnName},共 {Tables.Count} 个表";
+
+            if (NameSpace.IsNullOrEmpty()) NameSpace = ConnName;
+            if (EntityConnName.IsNullOrEmpty()) EntityConnName = ConnName;
+            if (OutputPath.IsNullOrEmpty()) OutputPath = ConnName;
+        }
+        catch (Exception ex)
+        {
+            Status = $"连接失败:{ex.Message}";
+            XTrace.WriteException(ex);
+        }
+    }
+
+    /// <summary>刷新数据表</summary>
+    [RelayCommand]
+    private async Task RefreshTables()
+    {
+        if (ConnName.IsNullOrEmpty()) return;
+
+        Status = "正在刷新表...";
+        await Task.Run(() => LoadTables());
+        Status = $"已刷新,共 {Tables.Count} 个表";
+    }
+
+    private void LoadTables()
+    {
+        var dal = DAL.Create(ConnName);
+        var list = dal.Tables;
+
+        if (!IncludeView)
+            list = list.Where(t => !t.IsView).ToList();
+
+        // 过滤系统表
+        list = list.Where(t => t.Name != "dtproperties" && t.Name != "sysconstraints" && t.Name != "syssegments").ToList();
+
+        // 排序
+        list = [.. list.OrderBy(t => t.Name)];
+
+        Application.Current.Dispatcher.Invoke(() =>
+        {
+            Tables.Clear();
+            foreach (var table in list)
+            {
+                Tables.Add(table);
+            }
+        });
+    }
+
+    /// <summary>生成选中表的代码</summary>
+    [RelayCommand]
+    private async Task GenerateTable()
+    {
+        if (SelectedTable == null) return;
+
+        await Task.Run(() =>
+        {
+            try
+            {
+                var option = new EntityBuilderOption
+                {
+                    BaseClass = BaseClass,
+                    ConnName = EntityConnName,
+                    Namespace = NameSpace,
+                    Output = OutputPath,
+                };
+
+                var sw = Stopwatch.StartNew();
+                var rs = EntityBuilder.BuildTables([SelectedTable], option);
+                sw.Stop();
+
+                Application.Current.Dispatcher.Invoke(() =>
+                {
+                    Status = $"生成 {SelectedTable.Name} 完成!耗时:{sw.Elapsed}";
+                });
+            }
+            catch (Exception ex)
+            {
+                XTrace.WriteException(ex);
+                Application.Current.Dispatcher.Invoke(() =>
+                {
+                    Status = $"生成失败:{ex.Message}";
+                });
+            }
+        });
+    }
+
+    /// <summary>生成所有表的代码</summary>
+    [RelayCommand]
+    private async Task GenerateAll()
+    {
+        if (Tables.Count == 0) return;
+
+        var tables = Tables.ToList();
+        await Task.Run(() =>
+        {
+            try
+            {
+                var option = new EntityBuilderOption
+                {
+                    BaseClass = BaseClass,
+                    ConnName = EntityConnName,
+                    Namespace = NameSpace,
+                    Output = OutputPath,
+                };
+
+                var sw = Stopwatch.StartNew();
+                var rs = EntityBuilder.BuildTables(tables, option);
+                sw.Stop();
+
+                Application.Current.Dispatcher.Invoke(() =>
+                {
+                    Status = $"生成 {tables.Count} 个类完成!耗时:{sw.Elapsed}";
+                });
+            }
+            catch (Exception ex)
+            {
+                XTrace.WriteException(ex);
+                Application.Current.Dispatcher.Invoke(() =>
+                {
+                    Status = $"生成失败:{ex.Message}";
+                });
+            }
+        });
+    }
+
+    /// <summary>打开输出目录</summary>
+    [RelayCommand]
+    private void OpenOutputDir()
+    {
+        var dir = OutputPath.GetFullPath();
+        if (!Directory.Exists(dir))
+            dir = AppDomain.CurrentDomain.BaseDirectory;
+
+        Process.Start("explorer.exe", "\"" + dir + "\"");
+    }
+
+    #endregion
+}
Added +347 -0
diff --git a/CrazyCoder/ViewModels/DataSyncViewModel.cs b/CrazyCoder/ViewModels/DataSyncViewModel.cs
new file mode 100644
index 0000000..fb7380a
--- /dev/null
+++ b/CrazyCoder/ViewModels/DataSyncViewModel.cs
@@ -0,0 +1,347 @@
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Windows;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using CrazyCoder.Models;
+using NewLife;
+using NewLife.Log;
+using XCode;
+using XCode.DataAccessLayer;
+
+namespace CrazyCoder.ViewModels;
+
+/// <summary>跨库数据同步 ViewModel</summary>
+public partial class DataSyncViewModel : ObservableObject
+{
+    #region 属性
+
+    /// <summary>所有可用连接</summary>
+    public ObservableCollection<String> Connections { get; } = [];
+
+    /// <summary>目标连接列表(排除源连接)</summary>
+    public ObservableCollection<String> TargetConnections { get; } = [];
+
+    /// <summary>选中的源连接</summary>
+    [ObservableProperty]
+    private String _sourceConn = "";
+
+    /// <summary>选中的目标连接</summary>
+    [ObservableProperty]
+    private String _targetConn = "";
+
+    /// <summary>源数据库是否已连接</summary>
+    [ObservableProperty]
+    private Boolean _isSourceConnected;
+
+    /// <summary>目标数据库是否已连接</summary>
+    [ObservableProperty]
+    private Boolean _isTargetConnected;
+
+    /// <summary>同步表集合</summary>
+    public ObservableCollection<SyncTableModel> Tables { get; } = [];
+
+    /// <summary>是否同步架构</summary>
+    [ObservableProperty]
+    private Boolean _syncSchema = true;
+
+    /// <summary>是否忽略错误</summary>
+    [ObservableProperty]
+    private Boolean _ignoreError;
+
+    /// <summary>是否正在同步中</summary>
+    [ObservableProperty]
+    private Boolean _isSyncing;
+
+    /// <summary>同步进度文本</summary>
+    [ObservableProperty]
+    private String _progressText = "";
+
+    /// <summary>状态文本</summary>
+    [ObservableProperty]
+    private String _status = "就绪";
+
+    /// <summary>源 DAL</summary>
+    private DAL _sourceDal;
+
+    /// <summary>源数据表集合</summary>
+    private IList<IDataTable> _sourceTables;
+
+    #endregion
+
+    #region 构造
+
+    /// <summary>实例化数据同步 ViewModel</summary>
+    public DataSyncViewModel()
+    {
+        LoadConnections();
+    }
+
+    private void LoadConnections()
+    {
+        Connections.Clear();
+        foreach (var item in DAL.ConnStrs.Keys.OrderBy(e => e))
+        {
+            Connections.Add(item);
+        }
+    }
+
+    #endregion
+
+    #region 命令
+
+    /// <summary>连接源数据库</summary>
+    [RelayCommand]
+    private async Task ConnectSource()
+    {
+        if (SourceConn.IsNullOrEmpty()) return;
+
+        try
+        {
+            Status = "正在连接源数据库...";
+            _sourceDal = DAL.Create(SourceConn);
+
+            await Task.Run(() =>
+            {
+                _sourceTables = _sourceDal.Tables;
+            });
+
+            if (_sourceTables == null) return;
+
+            // 填充表列表
+            Tables.Clear();
+            foreach (var tb in _sourceTables.OrderBy(e => e.TableName))
+            {
+                Tables.Add(new SyncTableModel
+                {
+                    Name = tb.TableName,
+                    DisplayName = tb.DisplayName,
+                    EnableSync = true,
+                });
+            }
+
+            // 加载行数
+            await Task.Run(() => LoadRowCounts());
+
+            // 更新目标列表
+            TargetConnections.Clear();
+            foreach (var item in Connections.Where(e => e != SourceConn))
+            {
+                TargetConnections.Add(item);
+            }
+
+            IsSourceConnected = true;
+            Status = $"已连接源 {SourceConn},共 {Tables.Count} 个表";
+        }
+        catch (Exception ex)
+        {
+            Status = $"连接失败:{ex.Message}";
+            XTrace.WriteException(ex);
+        }
+    }
+
+    /// <summary>断开源数据库</summary>
+    [RelayCommand]
+    private void DisconnectSource()
+    {
+        _sourceDal = null;
+        _sourceTables = null;
+        Tables.Clear();
+        TargetConnections.Clear();
+        IsSourceConnected = false;
+        IsTargetConnected = false;
+        Status = "已断开源数据库";
+    }
+
+    /// <summary>连接目标数据库</summary>
+    [RelayCommand]
+    private async Task ConnectTarget()
+    {
+        if (TargetConn.IsNullOrEmpty()) return;
+
+        try
+        {
+            Status = "正在连接目标数据库...";
+            var dal = DAL.Create(TargetConn);
+
+            await Task.Run(() =>
+            {
+                var tables = dal.Tables;
+                if (tables == null) return;
+
+                foreach (var item in Tables)
+                {
+                    var tb = tables.FirstOrDefault(e => e.TableName.EqualIgnoreCase(item.Name));
+                    if (tb != null)
+                    {
+                        var sb = new SelectBuilder { Table = item.Name };
+                        item.TargetCount = dal.SelectCount(sb);
+                    }
+                }
+            });
+
+            IsTargetConnected = true;
+            Status = $"已连接目标 {TargetConn}";
+        }
+        catch (Exception ex)
+        {
+            Status = $"连接目标失败:{ex.Message}";
+            XTrace.WriteException(ex);
+        }
+    }
+
+    /// <summary>断开目标数据库</summary>
+    [RelayCommand]
+    private void DisconnectTarget()
+    {
+        IsTargetConnected = false;
+        Status = "已断开目标数据库";
+    }
+
+    /// <summary>加载源表行数</summary>
+    private void LoadRowCounts()
+    {
+        if (_sourceDal == null || _sourceTables == null) return;
+
+        foreach (var item in Tables)
+        {
+            var sb = new SelectBuilder { Table = item.Name };
+            item.SourceCount = _sourceDal.SelectCount(sb);
+        }
+    }
+
+    /// <summary>开始同步数据</summary>
+    [RelayCommand]
+    private async Task SyncData()
+    {
+        if (_sourceDal == null || TargetConn.IsNullOrEmpty()) return;
+
+        var syncTables = Tables.Where(e => e.EnableSync).ToList();
+        if (syncTables.Count == 0)
+        {
+            Status = "没有选中任何需要同步的表";
+            return;
+        }
+
+        IsSyncing = true;
+        Status = "正在同步...";
+
+        try
+        {
+            await Task.Run(() =>
+            {
+                var dal = DAL.Create(TargetConn);
+                var targetTables = dal.Tables ?? new List<IDataTable>();
+
+                _sourceDal.Db.ShowSQL = false;
+                _sourceDal.Session.ShowSQL = false;
+                dal.Db.ShowSQL = false;
+                dal.Session.ShowSQL = false;
+
+                var total = syncTables.Count;
+                var index = 0;
+
+                foreach (var item in syncTables)
+                {
+                    index++;
+                    ProgressText = $"[{index}/{total}] {item.Name}";
+
+                    try
+                    {
+                        if (!SyncSchema && !targetTables.Any(e => e.TableName.EqualIgnoreCase(item.Name)))
+                        {
+                            item.Description = "目标表不存在,跳过!";
+                        }
+                        else
+                        {
+                            var tb = _sourceTables?.FirstOrDefault(e => e.TableName == item.Name);
+                            if (tb != null)
+                            {
+                                var rs = _sourceDal.Sync(tb, TargetConn, SyncSchema);
+                                item.SyncCount = rs;
+                                item.Description = "成功!";
+                            }
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        item.Description = ex.Message;
+                        XTrace.WriteException(ex);
+
+                        if (!IgnoreError) throw;
+                    }
+
+                    Application.Current.Dispatcher.Invoke(() =>
+                    {
+                        // 刷新 UI
+                        var _ = Tables;
+                    });
+                }
+
+                _sourceDal.Session.ShowSQL = true;
+                _sourceDal.Db.ShowSQL = true;
+                dal.Session.ShowSQL = true;
+                dal.Db.ShowSQL = true;
+
+                ProgressText = "";
+                Application.Current.Dispatcher.Invoke(() =>
+                {
+                    Status = $"同步完成!共处理 {total} 个表";
+                });
+            });
+        }
+        catch (Exception ex)
+        {
+            Status = $"同步失败:{ex.Message}";
+            XTrace.WriteException(ex);
+        }
+        finally
+        {
+            IsSyncing = false;
+        }
+    }
+
+    /// <summary>全选</summary>
+    [RelayCommand]
+    private void SelectAll()
+    {
+        foreach (var item in Tables)
+        {
+            item.EnableSync = true;
+        }
+    }
+
+    /// <summary>反选</summary>
+    [RelayCommand]
+    private void InvertSelection()
+    {
+        foreach (var item in Tables)
+        {
+            item.EnableSync = !item.EnableSync;
+        }
+    }
+
+    /// <summary>选择源多出(源有目标无)的表</summary>
+    [RelayCommand]
+    private async Task SelectDifferent()
+    {
+        if (TargetConn.IsNullOrEmpty()) return;
+
+        await Task.Run(() =>
+        {
+            var dal = DAL.Create(TargetConn);
+            var tables = dal.Tables;
+            if (tables == null) return;
+
+            Application.Current.Dispatcher.Invoke(() =>
+            {
+                foreach (var item in Tables)
+                {
+                    item.EnableSync = !tables.Any(e => e.TableName.EqualIgnoreCase(item.Name));
+                }
+            });
+        });
+    }
+
+    #endregion
+}
Added +491 -0
diff --git a/CrazyCoder/ViewModels/IoControlViewModel.cs b/CrazyCoder/ViewModels/IoControlViewModel.cs
new file mode 100644
index 0000000..87c1c7a
--- /dev/null
+++ b/CrazyCoder/ViewModels/IoControlViewModel.cs
@@ -0,0 +1,491 @@
+#nullable enable
+
+using System.Collections.ObjectModel;
+using System.IO.Ports;
+using System.Windows;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using NewLife;
+using NewLife.Log;
+using NewLife.Serial.Protocols;
+
+namespace CrazyCoder.ViewModels;
+
+/// <summary>I/O 控制面板 ViewModel</summary>
+public partial class IoControlViewModel : ObservableObject, IDisposable
+{
+    #region 属性
+    private ModbusRtu? _modbus;
+
+    /// <summary>端口名称列表</summary>
+    public ObservableCollection<String> PortNames { get; } = [];
+
+    /// <summary>波特率列表</summary>
+    public ObservableCollection<Int32> BaudRates { get; } = [1200, 2400, 4800, 9600, 14400, 19200, 38400, 56000, 57600, 115200, 128000, 194000, 256000, 512000, 1024000, 2048000];
+
+    /// <summary>校验位列表</summary>
+    public ObservableCollection<String> ParityOptions { get; } = ["None", "Odd", "Even", "Mark", "Space"];
+
+    /// <summary>停止位列表</summary>
+    public ObservableCollection<String> StopBitsOptions { get; } = ["One", "Two", "OnePointFive"];
+
+    /// <summary>数据位列表</summary>
+    public ObservableCollection<Int32> DataBitsOptions { get; } = [5, 6, 7, 8];
+
+    /// <summary>端口名称</summary>
+    [ObservableProperty]
+    private String _portName = "COM1";
+
+    /// <summary>波特率</summary>
+    [ObservableProperty]
+    private Int32 _baudRate = 115200;
+
+    /// <summary>校验位索引</summary>
+    [ObservableProperty]
+    private Int32 _parityIndex;
+
+    /// <summary>数据位</summary>
+    [ObservableProperty]
+    private Int32 _dataBits = 8;
+
+    /// <summary>停止位索引</summary>
+    [ObservableProperty]
+    private Int32 _stopBitsIndex;
+
+    /// <summary>从站地址</summary>
+    [ObservableProperty]
+    private Byte _slaveAddress = 1;
+
+    /// <summary>是否已连接</summary>
+    [ObservableProperty]
+    private Boolean _isConnected;
+
+    /// <summary>连接按钮文本</summary>
+    [ObservableProperty]
+    private String _connectButtonText = "连接";
+
+    /// <summary>输出 1 状态</summary>
+    [ObservableProperty]
+    private Boolean _output1;
+
+    /// <summary>输出 2 状态</summary>
+    [ObservableProperty]
+    private Boolean _output2;
+
+    /// <summary>输出 3 状态</summary>
+    [ObservableProperty]
+    private Boolean _output3;
+
+    /// <summary>输出 4 状态</summary>
+    [ObservableProperty]
+    private Boolean _output4;
+
+    /// <summary>输出 5 状态</summary>
+    [ObservableProperty]
+    private Boolean _output5;
+
+    /// <summary>输出 6 状态</summary>
+    [ObservableProperty]
+    private Boolean _output6;
+
+    /// <summary>输出 7 状态</summary>
+    [ObservableProperty]
+    private Boolean _output7;
+
+    /// <summary>输出 8 状态</summary>
+    [ObservableProperty]
+    private Boolean _output8;
+
+    /// <summary>输入 1 状态</summary>
+    [ObservableProperty]
+    private Boolean _input1;
+
+    /// <summary>输入 2 状态</summary>
+    [ObservableProperty]
+    private Boolean _input2;
+
+    /// <summary>输入 3 状态</summary>
+    [ObservableProperty]
+    private Boolean _input3;
+
+    /// <summary>输入 4 状态</summary>
+    [ObservableProperty]
+    private Boolean _input4;
+
+    /// <summary>输入 5 状态</summary>
+    [ObservableProperty]
+    private Boolean _input5;
+
+    /// <summary>输入 6 状态</summary>
+    [ObservableProperty]
+    private Boolean _input6;
+
+    /// <summary>输入 7 状态</summary>
+    [ObservableProperty]
+    private Boolean _input7;
+
+    /// <summary>输入 8 状态</summary>
+    [ObservableProperty]
+    private Boolean _input8;
+
+    /// <summary>延迟(毫秒)</summary>
+    [ObservableProperty]
+    private Int32 _delay = 100;
+
+    /// <summary>产品型号</summary>
+    [ObservableProperty]
+    private String _productType = "";
+
+    /// <summary>固件版本</summary>
+    [ObservableProperty]
+    private String _firmwareVersion = "";
+
+    /// <summary>日志回调</summary>
+    public Action<String>? OnLog { get; set; }
+    #endregion
+
+    #region 构造
+    /// <summary>实例化 I/O 控制面板 ViewModel</summary>
+    public IoControlViewModel()
+    {
+        RefreshPorts();
+    }
+    #endregion
+
+    #region 方法
+    /// <summary>刷新串口列表</summary>
+    [RelayCommand]
+    private void RefreshPorts()
+    {
+        PortNames.Clear();
+        foreach (var name in SerialPort.GetPortNames())
+        {
+            PortNames.Add(name);
+        }
+        if (PortNames.Count > 0) PortName = PortNames[0];
+    }
+
+    private ModbusRtu CreateModbus()
+    {
+        var parity = ParityIndex switch
+        {
+            1 => Parity.Odd,
+            2 => Parity.Even,
+            3 => Parity.Mark,
+            4 => Parity.Space,
+            _ => Parity.None,
+        };
+        var stopBits = StopBitsIndex switch
+        {
+            1 => StopBits.Two,
+            2 => StopBits.OnePointFive,
+            _ => StopBits.One,
+        };
+
+        return new ModbusRtu
+        {
+            PortName = PortName,
+            Baudrate = BaudRate,
+            DataBits = DataBits,
+            Parity = parity,
+            StopBits = stopBits,
+            Log = new IoControlLog(this),
+        };
+    }
+    #endregion
+
+    #region 连接/断开
+    /// <summary>切换连接/断开</summary>
+    [RelayCommand]
+    private void ToggleConnect()
+    {
+        if (IsConnected)
+            Disconnect();
+        else
+            Connect();
+    }
+
+    private void Connect()
+    {
+        try
+        {
+            _modbus?.Dispose();
+            _modbus = CreateModbus();
+            _modbus.Open();
+
+            IsConnected = true;
+            ConnectButtonText = "断开";
+
+            WriteLog($"已连接 {PortName},{BaudRate}/{DataBits}/{ParityOptions[ParityIndex]}/{StopBitsOptions[StopBitsIndex]}");
+
+            // 读取设备信息
+            ReadDeviceInfo();
+        }
+        catch (Exception ex)
+        {
+            WriteLog($"连接失败:{ex.Message}");
+            MessageBox.Show($"连接失败:{ex.Message}", "错误");
+        }
+    }
+
+    private void Disconnect()
+    {
+        if (_modbus != null)
+        {
+            try { _modbus.Dispose(); } catch { }
+            _modbus = null;
+        }
+
+        IsConnected = false;
+        ConnectButtonText = "连接";
+
+        WriteLog("已断开连接");
+    }
+    #endregion
+
+    #region I/O 操作
+    /// <summary>读取设备信息</summary>
+    [RelayCommand]
+    private void ReadDeviceInfo()
+    {
+        var mb = _modbus;
+        if (mb == null) return;
+
+        try
+        {
+            var data = mb.ReadRegister(SlaveAddress, 0x1000, 14);
+            if (data == null || data.Length < 8)
+            {
+                WriteLog("读取设备信息失败:无返回数据");
+                return;
+            }
+
+            // 每个寄存器2字节,大端序
+            var buf = new Byte[data.Length * 2];
+            for (var i = 0; i < data.Length; i++)
+            {
+                buf[i * 2] = (Byte)(data[i] >> 8);
+                buf[i * 2 + 1] = (Byte)(data[i] & 0xFF);
+            }
+
+            ProductType = System.Text.Encoding.ASCII.GetString(buf, 0, 4);
+            FirmwareVersion = System.Text.Encoding.ASCII.GetString(buf, 4, 4);
+            WriteLog($"设备信息:型号={ProductType} 版本={FirmwareVersion}");
+        }
+        catch (Exception ex)
+        {
+            WriteLog($"读取设备信息失败:{ex.Message}");
+        }
+    }
+
+    /// <summary>打开指定输出</summary>
+    [RelayCommand]
+    private void TurnOn(String index)
+    {
+        var mb = _modbus;
+        if (mb == null) return;
+
+        var addr = index.ToInt() - 1;
+        try
+        {
+            if (Delay > 0)
+                mb.WriteRegisters(SlaveAddress, (UInt16)(0x0003 + addr * 5), [0x0004, (UInt16)(Delay / 100)]);
+            else
+                mb.WriteCoil(SlaveAddress, (UInt16)addr, 0xFF00);
+
+            WriteLog($"输出 {index} 已打开 (地址={addr})");
+            UpdateOutputProperty(index, true);
+        }
+        catch (Exception ex)
+        {
+            WriteLog($"打开输出 {index} 失败:{ex.Message}");
+        }
+    }
+
+    /// <summary>关闭指定输出</summary>
+    [RelayCommand]
+    private void TurnOff(String index)
+    {
+        var mb = _modbus;
+        if (mb == null) return;
+
+        var addr = index.ToInt() - 1;
+        try
+        {
+            if (Delay > 0)
+                mb.WriteRegisters(SlaveAddress, (UInt16)(0x0003 + addr * 5), [0x0002, (UInt16)(Delay / 100)]);
+            else
+                mb.WriteCoil(SlaveAddress, (UInt16)addr, 0);
+
+            WriteLog($"输出 {index} 已关闭 (地址={addr})");
+            UpdateOutputProperty(index, false);
+        }
+        catch (Exception ex)
+        {
+            WriteLog($"关闭输出 {index} 失败:{ex.Message}");
+        }
+    }
+
+    /// <summary>打开所有输出</summary>
+    [RelayCommand]
+    private void TurnOnAll()
+    {
+        var mb = _modbus;
+        if (mb == null) return;
+
+        try
+        {
+            mb.WriteCoils(SlaveAddress, 0, [0xFF00, 0xFF00, 0xFF00, 0xFF00, 0xFF00, 0xFF00, 0xFF00, 0xFF00]);
+            WriteLog("所有输出已打开");
+            for (var i = 1; i <= 8; i++) UpdateOutputProperty(i.ToString(), true);
+        }
+        catch (Exception ex)
+        {
+            WriteLog($"打开所有输出失败:{ex.Message}");
+        }
+    }
+
+    /// <summary>关闭所有输出</summary>
+    [RelayCommand]
+    private void TurnOffAll()
+    {
+        var mb = _modbus;
+        if (mb == null) return;
+
+        try
+        {
+            mb.WriteCoils(SlaveAddress, 0, [0, 0, 0, 0, 0, 0, 0, 0]);
+            WriteLog("所有输出已关闭");
+            for (var i = 1; i <= 8; i++) UpdateOutputProperty(i.ToString(), false);
+        }
+        catch (Exception ex)
+        {
+            WriteLog($"关闭所有输出失败:{ex.Message}");
+        }
+    }
+
+    /// <summary>读取所有输入</summary>
+    [RelayCommand]
+    private void ReadInputs()
+    {
+        var mb = _modbus;
+        if (mb == null) return;
+
+        try
+        {
+            var data = mb.ReadDiscrete(SlaveAddress, 0x0100, 8);
+            if (data == null || data.Length == 0)
+            {
+                WriteLog("读取输入失败:无返回数据");
+                return;
+            }
+
+            for (var i = 0; i < Math.Min(8, data.Length); i++)
+            {
+                UpdateInputProperty((i + 1).ToString(), data[i]);
+            }
+            var bits = String.Join(" ", data.Select(e => e ? "1" : "0"));
+            WriteLog($"输入已读取:{bits}");
+        }
+        catch (Exception ex)
+        {
+            WriteLog($"读取输入失败:{ex.Message}");
+        }
+    }
+
+    /// <summary>读取所有输出状态</summary>
+    [RelayCommand]
+    private void ReadOutputs()
+    {
+        var mb = _modbus;
+        if (mb == null) return;
+
+        try
+        {
+            var data = mb.ReadCoil(SlaveAddress, 0, 8);
+            if (data == null || data.Length == 0)
+            {
+                WriteLog("读取输出失败:无返回数据");
+                return;
+            }
+
+            for (var i = 0; i < Math.Min(8, data.Length); i++)
+            {
+                UpdateOutputProperty((i + 1).ToString(), data[i]);
+            }
+            var bits = String.Join(" ", data.Select(e => e ? "1" : "0"));
+            WriteLog($"输出已读取:{bits}");
+        }
+        catch (Exception ex)
+        {
+            WriteLog($"读取输出失败:{ex.Message}");
+        }
+    }
+
+    private void UpdateOutputProperty(String index, Boolean value)
+    {
+        switch (index)
+        {
+            case "1": Output1 = value; break;
+            case "2": Output2 = value; break;
+            case "3": Output3 = value; break;
+            case "4": Output4 = value; break;
+            case "5": Output5 = value; break;
+            case "6": Output6 = value; break;
+            case "7": Output7 = value; break;
+            case "8": Output8 = value; break;
+        }
+    }
+
+    private void UpdateInputProperty(String index, Boolean value)
+    {
+        switch (index)
+        {
+            case "1": Input1 = value; break;
+            case "2": Input2 = value; break;
+            case "3": Input3 = value; break;
+            case "4": Input4 = value; break;
+            case "5": Input5 = value; break;
+            case "6": Input6 = value; break;
+            case "7": Input7 = value; break;
+            case "8": Input8 = value; break;
+        }
+    }
+    #endregion
+
+    #region 清空日志
+    /// <summary>清空日志</summary>
+    [RelayCommand]
+    private void ClearLog()
+    {
+        OnLog?.Invoke("__CLEAR__");
+    }
+    #endregion
+
+    #region 日志
+    private void WriteLog(String msg)
+    {
+        OnLog?.Invoke(msg);
+    }
+
+    private class IoControlLog : Logger
+    {
+        private readonly IoControlViewModel _vm;
+        public IoControlLog(IoControlViewModel 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()
+    {
+        Disconnect();
+        GC.SuppressFinalize(this);
+    }
+    #endregion
+}
Modified +8 -2
diff --git a/CrazyCoder/ViewModels/MainViewModel.cs b/CrazyCoder/ViewModels/MainViewModel.cs
index cd454f2..541bdb3 100644
--- a/CrazyCoder/ViewModels/MainViewModel.cs
+++ b/CrazyCoder/ViewModels/MainViewModel.cs
@@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.Input;
 using CrazyCoder.Models;
 using CrazyCoder.Views;
 using NewLife.Reflection;
+// DataModeling, RedisManager, DataSync 窗口已在 Menus 中注册 Type
 
 namespace CrazyCoder.ViewModels
 {
@@ -14,10 +15,15 @@ namespace CrazyCoder.ViewModels
         {
             Menus =
             [
-                new MenuModel() { IconFont = "\xe635", Title = "数据建模", BackColor = "#218868" },
+                new MenuModel() { IconFont = "\xe635", Title = "数据建模", BackColor = "#218868", Type = typeof(DataModelingWindow) },
+                new MenuModel() { IconFont = "\xe6b6", Title = "Redis 管理器", BackColor = "#EE3B3B", Type = typeof(RedisManagerWindow) },
+                new MenuModel() { IconFont = "\xe6e1", Title = "跨库同步", BackColor = "#218868", Type = typeof(DataSyncWindow) },
                 new MenuModel() { IconFont = "\xe6b6", Title = "网络工具", BackColor = "#EE3B3B", Type = typeof(NetworkWindow) },
                 new MenuModel() { IconFont = "\xe6e1", Title = "RPC工具", BackColor = "#218868" },
-                new MenuModel() { IconFont = "\xe614", Title = "串口工具", BackColor = "#EE3B3B" },
+                new MenuModel() { IconFont = "\xe614", Title = "串口工具", BackColor = "#EE3B3B", Type = typeof(SerialPortWindow) },
+                new MenuModel() { IconFont = "\xe614", Title = "Modbus RTU", BackColor = "#EE3B3B", Type = typeof(ModbusRtuWindow) },
+                new MenuModel() { IconFont = "\xe6b6", Title = "Modbus TCP", BackColor = "#EE3B3B", Type = typeof(ModbusTcpWindow) },
+                new MenuModel() { IconFont = "\xe755", Title = "I/O 控制", BackColor = "#218868", Type = typeof(IoControlWindow) },
                 new MenuModel() { IconFont = "\xe755", Title = "地图接口", BackColor = "#218868" },
                 new MenuModel() { IconFont = "\xe635", Title = "正则表达式", BackColor = "#218868", Type = typeof(RegexWindow) },
                 new MenuModel() { IconFont = "\xe6b6", Title = "图标水印", BackColor = "#EE3B3B", Type = typeof(IconToolWindow) },
Added +369 -0
diff --git a/CrazyCoder/ViewModels/ModbusRtuViewModel.cs b/CrazyCoder/ViewModels/ModbusRtuViewModel.cs
new file mode 100644
index 0000000..a43e8f9
--- /dev/null
+++ b/CrazyCoder/ViewModels/ModbusRtuViewModel.cs
@@ -0,0 +1,369 @@
+#nullable enable
+
+using System.Collections.ObjectModel;
+using System.IO.Ports;
+using System.Text;
+using System.Windows;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using NewLife;
+using NewLife.Log;
+using NewLife.Serial.Protocols;
+
+namespace CrazyCoder.ViewModels;
+
+/// <summary>Modbus RTU 工具 ViewModel</summary>
+public partial class ModbusRtuViewModel : ObservableObject, IDisposable
+{
+    #region 属性
+    private ModbusRtu? _modbus;
+
+    /// <summary>端口名称列表</summary>
+    public ObservableCollection<String> PortNames { get; } = [];
+
+    /// <summary>波特率列表</summary>
+    public ObservableCollection<Int32> BaudRates { get; } = [1200, 2400, 4800, 9600, 14400, 19200, 38400, 56000, 57600, 115200, 128000, 194000, 256000, 512000, 1024000, 2048000];
+
+    /// <summary>校验位列表</summary>
+    public ObservableCollection<String> ParityOptions { get; } = ["None", "Odd", "Even", "Mark", "Space"];
+
+    /// <summary>停止位列表</summary>
+    public ObservableCollection<String> StopBitsOptions { get; } = ["One", "Two", "OnePointFive"];
+
+    /// <summary>数据位列表</summary>
+    public ObservableCollection<Int32> DataBitsOptions { get; } = [5, 6, 7, 8];
+
+    /// <summary>功能码列表</summary>
+    public ObservableCollection<String> FunctionCodes { get; } =
+    [
+        "01 读线圈",
+        "02 读离散量输入",
+        "03 读保持寄存器",
+        "04 读输入寄存器",
+        "05 写单线圈",
+        "06 写单寄存器",
+        "0F 写多线圈",
+        "10 写多寄存器",
+    ];
+
+    /// <summary>端口名称</summary>
+    [ObservableProperty]
+    private String _portName = "COM1";
+
+    /// <summary>波特率</summary>
+    [ObservableProperty]
+    private Int32 _baudRate = 115200;
+
+    /// <summary>校验位索引</summary>
+    [ObservableProperty]
+    private Int32 _parityIndex;
+
+    /// <summary>数据位</summary>
+    [ObservableProperty]
+    private Int32 _dataBits = 8;
+
+    /// <summary>停止位索引</summary>
+    [ObservableProperty]
+    private Int32 _stopBitsIndex;
+
+    /// <summary>从站地址</summary>
+    [ObservableProperty]
+    private Byte _slaveAddress = 1;
+
+    /// <summary>功能码索引</summary>
+    [ObservableProperty]
+    private Int32 _functionCodeIndex;
+
+    /// <summary>起始地址</summary>
+    [ObservableProperty]
+    private UInt16 _startAddress;
+
+    /// <summary>读取数量</summary>
+    [ObservableProperty]
+    private UInt16 _readCount = 10;
+
+    /// <summary>写入值</summary>
+    [ObservableProperty]
+    private String _writeValue = "0";
+
+    /// <summary>是否已连接</summary>
+    [ObservableProperty]
+    private Boolean _isConnected;
+
+    /// <summary>连接按钮文本</summary>
+    [ObservableProperty]
+    private String _connectButtonText = "连接";
+
+    /// <summary>读取结果(十六进制)</summary>
+    [ObservableProperty]
+    private String _readResult = "";
+
+    /// <summary>日志回调</summary>
+    public Action<String>? OnLog { get; set; }
+    #endregion
+
+    #region 构造
+    /// <summary>实例化 Modbus RTU 工具 ViewModel</summary>
+    public ModbusRtuViewModel()
+    {
+        RefreshPorts();
+    }
+    #endregion
+
+    #region 方法
+    /// <summary>刷新串口列表</summary>
+    [RelayCommand]
+    private void RefreshPorts()
+    {
+        PortNames.Clear();
+        foreach (var name in SerialPort.GetPortNames())
+        {
+            PortNames.Add(name);
+        }
+        if (PortNames.Count > 0) PortName = PortNames[0];
+    }
+
+    private ModbusRtu CreateModbus()
+    {
+        var parity = ParityIndex switch
+        {
+            1 => Parity.Odd,
+            2 => Parity.Even,
+            3 => Parity.Mark,
+            4 => Parity.Space,
+            _ => Parity.None,
+        };
+        var stopBits = StopBitsIndex switch
+        {
+            1 => StopBits.Two,
+            2 => StopBits.OnePointFive,
+            _ => StopBits.One,
+        };
+
+        return new ModbusRtu
+        {
+            PortName = PortName,
+            Baudrate = BaudRate,
+            DataBits = DataBits,
+            Parity = parity,
+            StopBits = stopBits,
+            Log = new ModbusLog(this),
+        };
+    }
+    #endregion
+
+    #region 连接/断开
+    /// <summary>切换连接/断开</summary>
+    [RelayCommand]
+    private void ToggleConnect()
+    {
+        if (IsConnected)
+            Disconnect();
+        else
+            Connect();
+    }
+
+    private void Connect()
+    {
+        try
+        {
+            _modbus?.Dispose();
+            _modbus = CreateModbus();
+            _modbus.Open();
+
+            IsConnected = true;
+            ConnectButtonText = "断开";
+
+            WriteLog($"Modbus RTU 已连接 {PortName},{BaudRate}/{DataBits}/{ParityOptions[ParityIndex]}/{StopBitsOptions[StopBitsIndex]}");
+        }
+        catch (Exception ex)
+        {
+            WriteLog($"连接失败:{ex.Message}");
+            MessageBox.Show($"连接失败:{ex.Message}", "错误");
+        }
+    }
+
+    private void Disconnect()
+    {
+        if (_modbus != null)
+        {
+            try { _modbus.Dispose(); } catch { }
+            _modbus = null;
+        }
+
+        IsConnected = false;
+        ConnectButtonText = "连接";
+
+        WriteLog("Modbus RTU 已断开");
+    }
+    #endregion
+
+    #region 读写操作
+    /// <summary>执行读写操作</summary>
+    [RelayCommand]
+    private void Execute()
+    {
+        var mb = _modbus;
+        if (mb == null)
+        {
+            WriteLog("未连接");
+            return;
+        }
+
+        var host = SlaveAddress;
+        var address = StartAddress;
+        var code = FunctionCodeIndex;
+
+        try
+        {
+            switch (code)
+            {
+                case 0: // 01 读线圈
+                    {
+                        var count = ReadCount;
+                        var data = mb.ReadCoil(host, address, count);
+                        if (data != null)
+                        {
+                            var bits = String.Join(" ", data.Select(e => e ? "1" : "0"));
+                            WriteLog($"读线圈 [{data.Length}位] {bits}");
+                            ReadResult = bits;
+                        }
+                        else
+                        {
+                            WriteLog("读线圈:无返回数据");
+                            ReadResult = "无返回数据";
+                        }
+                        break;
+                    }
+                case 1: // 02 读离散量输入
+                    {
+                        var count = ReadCount;
+                        var data = mb.ReadDiscrete(host, address, count);
+                        if (data != null)
+                        {
+                            var bits = String.Join(" ", data.Select(e => e ? "1" : "0"));
+                            WriteLog($"读离散量输入 [{data.Length}位] {bits}");
+                            ReadResult = bits;
+                        }
+                        else
+                        {
+                            WriteLog("读离散量输入:无返回数据");
+                            ReadResult = "无返回数据";
+                        }
+                        break;
+                    }
+                case 2: // 03 读保持寄存器
+                    {
+                        var count = ReadCount;
+                        var data = mb.ReadRegister(host, address, count);
+                        if (data != null)
+                        {
+                            var hex = String.Join(", ", data);
+                            WriteLog($"读保持寄存器 [{data.Length}个] {hex}");
+                            ReadResult = hex;
+                        }
+                        else
+                        {
+                            WriteLog("读保持寄存器:无返回数据");
+                            ReadResult = "无返回数据";
+                        }
+                        break;
+                    }
+                case 3: // 04 读输入寄存器
+                    {
+                        var count = ReadCount;
+                        var data = mb.ReadInput(host, address, count);
+                        if (data != null)
+                        {
+                            var hex = String.Join(", ", data);
+                            WriteLog($"读输入寄存器 [{data.Length}个] {hex}");
+                            ReadResult = hex;
+                        }
+                        else
+                        {
+                            WriteLog("读输入寄存器:无返回数据");
+                            ReadResult = "无返回数据";
+                        }
+                        break;
+                    }
+                case 4: // 05 写单线圈
+                    {
+                        var value = (UInt16)(WriteValue.ToInt() > 0 ? 0xFF00 : 0x0000);
+                        mb.WriteCoil(host, address, value);
+                        WriteLog($"写单线圈 地址={address} 值=0x{value:X4}");
+                        ReadResult = $"写入成功:地址={address} 值=0x{value:X4}";
+                        break;
+                    }
+                case 5: // 06 写单寄存器
+                    {
+                        var value = (UInt16)WriteValue.ToInt();
+                        mb.WriteRegister(host, address, value);
+                        WriteLog($"写单寄存器 地址={address} 值=0x{value:X4}");
+                        ReadResult = $"写入成功:地址={address} 值=0x{value:X4}";
+                        break;
+                    }
+                case 6: // 0F 写多线圈
+                    {
+                        var count = ReadCount;
+                        var values = new UInt16[count];
+                        for (var i = 0; i < count; i++) values[i] = 0xFF00;
+                        mb.WriteCoils(host, address, values);
+                        WriteLog($"写多线圈 地址={address} 数量={count}");
+                        ReadResult = $"写入成功:地址={address} 数量={count}";
+                        break;
+                    }
+                case 7: // 10 写多寄存器
+                    {
+                        var count = ReadCount;
+                        var values = new UInt16[count];
+                        for (var i = 0; i < count; i++) values[i] = (UInt16)(WriteValue.ToInt() + i);
+                        mb.WriteRegisters(host, address, values);
+                        WriteLog($"写多寄存器 地址={address} 数量={count}");
+                        ReadResult = $"写入成功:地址={address} 数量={count}";
+                        break;
+                    }
+            }
+        }
+        catch (Exception ex)
+        {
+            WriteLog($"操作失败:{ex.Message}");
+            ReadResult = $"操作失败:{ex.Message}";
+        }
+    }
+
+
+    /// <summary>清空日志</summary>
+    [RelayCommand]
+    private void ClearLog()
+    {
+        OnLog?.Invoke("__CLEAR__");
+    }
+    #endregion
+
+    #region 日志
+    private void WriteLog(String msg)
+    {
+        OnLog?.Invoke(msg);
+    }
+
+    private class ModbusLog : Logger
+    {
+        private readonly ModbusRtuViewModel _vm;
+        public ModbusLog(ModbusRtuViewModel 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()
+    {
+        Disconnect();
+        GC.SuppressFinalize(this);
+    }
+    #endregion
+}
Added +578 -0
diff --git a/CrazyCoder/ViewModels/ModbusTcpViewModel.cs b/CrazyCoder/ViewModels/ModbusTcpViewModel.cs
new file mode 100644
index 0000000..9710952
--- /dev/null
+++ b/CrazyCoder/ViewModels/ModbusTcpViewModel.cs
@@ -0,0 +1,578 @@
+#nullable enable
+
+using System.Collections.ObjectModel;
+using System.Windows;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using NewLife;
+using NewLife.Buffers;
+using NewLife.Data;
+using NewLife.IoT.Protocols;
+using NewLife.Log;
+using NewLife.Net;
+using NewLife.Net.Handlers;
+using NewLife.Security;
+using NewLife.Threading;
+
+namespace CrazyCoder.ViewModels;
+
+/// <summary>Modbus TCP 主站/从站模式</summary>
+public enum ModbusTcpMode
+{
+    /// <summary>主站(Master)</summary>
+    Master,
+
+    /// <summary>从站(Slave)</summary>
+    Slave
+}
+
+/// <summary>寄存器单元</summary>
+public class RegisterUnit
+{
+    /// <summary>寄存器地址</summary>
+    public Int32 Address { get; set; }
+
+    /// <summary>寄存器数值</summary>
+    public UInt16 Value { get; set; }
+
+    /// <summary>十六进制显示</summary>
+    public String Hex => Value.GetBytes(false).ToHex();
+}
+
+/// <summary>线圈单元</summary>
+public class CoilUnit
+{
+    /// <summary>线圈地址</summary>
+    public Int32 Address { get; set; }
+
+    /// <summary>线圈数值</summary>
+    public Byte Value { get; set; }
+}
+
+/// <summary>Modbus TCP 工具 ViewModel</summary>
+public partial class ModbusTcpViewModel : ObservableObject, IDisposable
+{
+    #region 属性
+    private ModbusTcp? _modbus;
+    private NetServer? _server;
+    private TimerX? _timer;
+    private List<RegisterUnit> _regs = [];
+    private List<CoilUnit> _coils = [];
+
+    /// <summary>功能码列表</summary>
+    public ObservableCollection<String> FuncCodeOptions { get; } =
+    [
+        "01 读线圈",
+        "02 读离散量输入",
+        "03 读保持寄存器",
+        "04 读输入寄存器",
+        "05 写单线圈",
+        "06 写单寄存器",
+        "0F 写多线圈",
+        "10 写多寄存器",
+    ];
+
+    /// <summary>工作模式列表</summary>
+    public ObservableCollection<String> ModeOptions { get; } = ["主站(Master)", "从站(Slave)"];
+
+    /// <summary>寄存器数据模式列表</summary>
+    public ObservableCollection<String> DataModeOptions { get; } = ["0x0000", "0x7777", "0xFFFF", "递增", "静态随机", "动态随机"];
+
+    /// <summary>工作模式(0=Master, 1=Slave)</summary>
+    [ObservableProperty]
+    private Int32 _selectedMode;
+
+    /// <summary>服务器地址</summary>
+    [ObservableProperty]
+    private String _serverAddress = "127.0.0.1";
+
+    /// <summary>端口</summary>
+    [ObservableProperty]
+    private Int32 _port = 502;
+
+    /// <summary>从站地址</summary>
+    [ObservableProperty]
+    private Byte _slaveAddress = 1;
+
+    /// <summary>功能码索引</summary>
+    [ObservableProperty]
+    private Int32 _functionCodeIndex;
+
+    /// <summary>起始地址</summary>
+    [ObservableProperty]
+    private UInt16 _startAddress;
+
+    /// <summary>读取数量</summary>
+    [ObservableProperty]
+    private UInt16 _readCount = 10;
+
+    /// <summary>写入值</summary>
+    [ObservableProperty]
+    private String _writeValue = "0";
+
+    /// <summary>是否已连接</summary>
+    [ObservableProperty]
+    private Boolean _isConnected;
+
+    /// <summary>连接按钮文本</summary>
+    [ObservableProperty]
+    private String _connectButtonText = "打开";
+
+    /// <summary>读取结果</summary>
+    [ObservableProperty]
+    private String _readResult = "";
+
+    /// <summary>从站数据模式索引</summary>
+    [ObservableProperty]
+    private Int32 _dataModeIndex;
+
+    /// <summary>从站数据地址</summary>
+    [ObservableProperty]
+    private UInt16 _slaveDataAddress;
+
+    /// <summary>从站数据数量</summary>
+    [ObservableProperty]
+    private UInt16 _slaveDataCount = 100;
+
+    /// <summary>寄存器列表(从站模式)</summary>
+    public ObservableCollection<RegisterUnit> Registers { get; } = [];
+
+    /// <summary>线圈列表(从站模式)</summary>
+    public ObservableCollection<CoilUnit> Coils { get; } = [];
+
+    /// <summary>是否从站模式</summary>
+    public Boolean IsSlaveMode => SelectedMode == 1;
+
+    /// <summary>是否主站模式</summary>
+    public Boolean IsMasterMode => SelectedMode == 0;
+
+    /// <summary>日志回调</summary>
+    public Action<String>? OnLog { get; set; }
+    #endregion
+
+    #region 连接/断开
+    /// <summary>切换连接/断开</summary>
+    [RelayCommand]
+    private void ToggleConnect()
+    {
+        if (IsConnected)
+            Disconnect();
+        else
+            Connect();
+    }
+
+    private void Connect()
+    {
+        try
+        {
+            if (SelectedMode == 0)
+                ConnectMaster();
+            else
+                ConnectSlave();
+        }
+        catch (Exception ex)
+        {
+            WriteLog($"连接失败:{ex.Message}");
+            MessageBox.Show($"连接失败:{ex.Message}", "错误");
+        }
+    }
+
+    private void ConnectMaster()
+    {
+        _modbus?.Dispose();
+
+        var mb = new ModbusTcp
+        {
+            Server = $"{ServerAddress}:{Port}",
+            Log = new ModbusTcpLog(this),
+        };
+        mb.Open();
+
+        _modbus = mb;
+        IsConnected = true;
+        ConnectButtonText = "断开";
+
+        WriteLog($"Modbus TCP 主站已连接 {ServerAddress}:{Port}");
+    }
+
+    private void ConnectSlave()
+    {
+        _server?.Dispose();
+
+        var svr = new NetServer(Port)
+        {
+            Log = new ModbusTcpLog(this),
+            SessionLog = new ModbusTcpLog(this),
+        };
+        svr.Add(new LengthFieldCodec { Offset = 4, Size = -2 });
+        svr.Received += OnSlaveReceived;
+        svr.Start();
+
+        _server = svr;
+        _regs = [];
+        _coils = [];
+        Registers.Clear();
+        Coils.Clear();
+
+        RefreshSlaveData();
+
+        IsConnected = true;
+        ConnectButtonText = "停止";
+
+        WriteLog($"Modbus TCP 从站已启动,监听端口 {Port}");
+    }
+
+    private void Disconnect()
+    {
+        if (_modbus != null)
+        {
+            try { _modbus.Dispose(); } catch { }
+            _modbus = null;
+        }
+
+        if (_server != null)
+        {
+            try { _server.Dispose(); } catch { }
+            _server = null;
+        }
+
+        _timer.TryDispose();
+        _timer = null;
+
+        IsConnected = false;
+        ConnectButtonText = "打开";
+
+        WriteLog("连接已关闭");
+    }
+    #endregion
+
+    #region 主站操作
+    /// <summary>执行读写操作</summary>
+    [RelayCommand]
+    private void Execute()
+    {
+        var mb = _modbus;
+        if (mb == null || SelectedMode != 0)
+        {
+            WriteLog("主站未连接");
+            return;
+        }
+
+        var host = SlaveAddress;
+        var address = StartAddress;
+        var code = FunctionCodeIndex;
+
+        try
+        {
+            switch (code)
+            {
+                case 0: // 01 读线圈
+                    {
+                        var count = ReadCount;
+                        var data = mb.ReadCoil(host, address, count);
+                        if (data != null)
+                        {
+                            var bits = String.Join(" ", data.Select(e => e ? "1" : "0"));
+                            WriteLog($"读线圈 [{data.Length}位] {bits}");
+                            ReadResult = bits;
+                        }
+                        else
+                        {
+                            WriteLog("读线圈:无返回数据");
+                            ReadResult = "无返回数据";
+                        }
+                        break;
+                    }
+                case 1: // 02 读离散量输入
+                    {
+                        var count = ReadCount;
+                        var data = mb.ReadDiscrete(host, address, count);
+                        if (data != null)
+                        {
+                            var bits = String.Join(" ", data.Select(e => e ? "1" : "0"));
+                            WriteLog($"读离散量输入 [{data.Length}位] {bits}");
+                            ReadResult = bits;
+                        }
+                        else
+                        {
+                            WriteLog("读离散量输入:无返回数据");
+                            ReadResult = "无返回数据";
+                        }
+                        break;
+                    }
+                case 2: // 03 读保持寄存器
+                    {
+                        var count = ReadCount;
+                        var data = mb.ReadRegister(host, address, count);
+                        if (data != null)
+                        {
+                            var hex = String.Join(", ", data);
+                            WriteLog($"读保持寄存器 [{data.Length}个] {hex}");
+                            ReadResult = hex;
+                        }
+                        else
+                        {
+                            WriteLog("读保持寄存器:无返回数据");
+                            ReadResult = "无返回数据";
+                        }
+                        break;
+                    }
+                case 3: // 04 读输入寄存器
+                    {
+                        var count = ReadCount;
+                        var data = mb.ReadInput(host, address, count);
+                        if (data != null)
+                        {
+                            var hex = String.Join(", ", data);
+                            WriteLog($"读输入寄存器 [{data.Length}个] {hex}");
+                            ReadResult = hex;
+                        }
+                        else
+                        {
+                            WriteLog("读输入寄存器:无返回数据");
+                            ReadResult = "无返回数据";
+                        }
+                        break;
+                    }
+                case 4: // 05 写单线圈
+                    {
+                        var value = (UInt16)(WriteValue.ToInt() > 0 ? 0xFF00 : 0x0000);
+                        mb.WriteCoil(host, address, value);
+                        WriteLog($"写单线圈 地址={address} 值=0x{value:X4}");
+                        ReadResult = $"写入成功:地址={address} 值=0x{value:X4}";
+                        break;
+                    }
+                case 5: // 06 写单寄存器
+                    {
+                        var value = (UInt16)WriteValue.ToInt();
+                        mb.WriteRegister(host, address, value);
+                        WriteLog($"写单寄存器 地址={address} 值=0x{value:X4}");
+                        ReadResult = $"写入成功:地址={address} 值=0x{value:X4}";
+                        break;
+                    }
+                case 6: // 0F 写多线圈
+                    {
+                        var count = ReadCount;
+                        var values = new UInt16[count];
+                        for (var i = 0; i < count; i++) values[i] = 0xFF00;
+                        mb.WriteCoils(host, address, values);
+                        WriteLog($"写多线圈 地址={address} 数量={count}");
+                        ReadResult = $"写入成功:地址={address} 数量={count}";
+                        break;
+                    }
+                case 7: // 10 写多寄存器
+                    {
+                        var count = ReadCount;
+                        var values = new UInt16[count];
+                        for (var i = 0; i < count; i++) values[i] = (UInt16)(WriteValue.ToInt() + i);
+                        mb.WriteRegisters(host, address, values);
+                        WriteLog($"写多寄存器 地址={address} 数量={count}");
+                        ReadResult = $"写入成功:地址={address} 数量={count}";
+                        break;
+                    }
+            }
+        }
+        catch (Exception ex)
+        {
+            WriteLog($"操作失败:{ex.Message}");
+            ReadResult = $"操作失败:{ex.Message}";
+        }
+    }
+
+    private void ShowResult(String title, Byte[]? data)
+    {
+        if (data == null || data.Length == 0)
+        {
+            WriteLog($"{title}:无返回数据");
+            ReadResult = "无返回数据";
+            return;
+        }
+
+        var hex = data.ToHex(" ", 0, data.Length);
+        WriteLog($"{title} [{data.Length}字节] {hex}");
+        ReadResult = hex;
+    }
+    #endregion
+
+    #region 从站处理
+    private void RefreshSlaveData()
+    {
+        var addr = SlaveDataAddress;
+        var count = SlaveDataCount;
+        var modeIndex = DataModeIndex;
+
+        Registers.Clear();
+        Coils.Clear();
+
+        // 从站使用寄存器模式(默认)
+        for (var i = 0; i < count; i++)
+        {
+            var value = modeIndex switch
+            {
+                0 => (UInt16)0x0000,
+                1 => (UInt16)0x7777,
+                2 => (UInt16)0xFFFF,
+                3 => (UInt16)i,
+                _ => (UInt16)Rand.Next(UInt16.MaxValue),
+            };
+            Registers.Add(new RegisterUnit { Address = addr + i, Value = value });
+        }
+
+        if (modeIndex == 5) // 动态随机
+        {
+            _timer = new TimerX(DoRefreshData, null, 1_000, 1_000) { Async = true };
+        }
+
+        WriteLog($"从站数据已初始化:{count} 个寄存器");
+    }
+
+    private void DoRefreshData(Object? state)
+    {
+        foreach (var reg in Registers)
+        {
+            var val = reg.Value;
+            if (val == 0)
+                val = (UInt16)Rand.Next(UInt16.MaxValue);
+            else
+            {
+                var x = (Rand.Next(75) - 30) / 100.0;
+                val = (UInt16)(val * (1 + x));
+            }
+            reg.Value = val;
+        }
+    }
+
+    /// <summary>刷新从站数据</summary>
+    [RelayCommand]
+    private void RefreshData()
+    {
+        if (!IsConnected || SelectedMode != 1) return;
+        RefreshSlaveData();
+    }
+
+    private void OnSlaveReceived(Object? sender, ReceivedEventArgs e)
+    {
+        var session = sender as NetSession;
+        if (session == null) return;
+
+        var pk = e.Packet is ArrayPacket ap ? ap : new ArrayPacket(e.Packet.ReadBytes());
+        try
+        {
+            var spanReader = new NewLife.Buffers.SpanReader(pk);
+            var msg = new ModbusIpMessage();
+            msg.Read(ref spanReader);
+            if (msg == null) return;
+
+            session.Log?.Info("<= {0}", msg);
+
+            var addrMsg = msg.GetAddress();
+            var addrAddress = addrMsg;
+            var rs = msg.CreateReply();
+
+            switch (msg.Code)
+            {
+                case FunctionCodes.ReadCoil:
+                case FunctionCodes.ReadDiscrete:
+                    // Return coils data
+                    {
+                        var regCount = msg.Payload.ReadBytes(2, 2).ToUInt16(0, false);
+                        var bytesCount = (Int32)Math.Ceiling(regCount / 8.0);
+                        var bits = new Byte[1 + bytesCount];
+                        bits[0] = (Byte)bytesCount;
+                        for (var i = 0; i < bytesCount; i++)
+                        {
+                            var b = 0;
+                            var max = regCount - i * 8;
+                            if (max > 8) max = 8;
+                            for (var j = 0; j < max; j++)
+                            {
+                                var idx = addrAddress + i * 8 + j;
+                                var coil = Coils.FirstOrDefault(e => e.Address == idx);
+                                if (coil != null && coil.Value > 0)
+                                    b |= 1 << j;
+                            }
+                            bits[1 + i] = (Byte)b;
+                        }
+                        rs.Payload = new ArrayPacket(bits);
+                    }
+                    break;
+
+                case FunctionCodes.ReadRegister:
+                case FunctionCodes.ReadInput:
+                    {
+                        var regCount = msg.Payload.ReadBytes(2, 2).ToUInt16(0, false);
+                        var regs = Registers.Where(e => e.Address >= addrAddress && e.Address < addrAddress + regCount).ToList();
+                        if (regs.Count > 0)
+                        {
+                            var buf2 = regs.SelectMany(e => e.Value.GetBytes(false)).ToArray();
+                            rs.Payload = new ArrayPacket(new Byte[] { (Byte)buf2.Length }.Concat(buf2).ToArray());
+                        }
+                    }
+                    break;
+
+                case FunctionCodes.WriteCoil:
+                    break;
+
+                case FunctionCodes.WriteRegister:
+                    {
+                        var value = msg.Payload.ReadBytes(2, 2).ToUInt16(0, false);
+                        var reg = Registers.FirstOrDefault(e => e.Address == addrAddress);
+                        if (reg != null)
+                        {
+                            reg.Value = value;
+                            Application.Current?.Dispatcher.Invoke(() => { });
+                        }
+                    }
+                    break;
+
+                case FunctionCodes.WriteCoils:
+                case FunctionCodes.WriteRegisters:
+                    break;
+            }
+
+            session.Log?.Info("=> {0}", rs);
+            session.Send(rs.ToPacket());
+        }
+        catch (Exception ex)
+        {
+            WriteLog($"从站处理异常:{ex.Message}");
+        }
+    }
+    #endregion
+
+    #region 清空日志
+    /// <summary>清空日志</summary>
+    [RelayCommand]
+    private void ClearLog()
+    {
+        OnLog?.Invoke("__CLEAR__");
+    }
+    #endregion
+
+    #region 日志
+    private void WriteLog(String msg)
+    {
+        OnLog?.Invoke(msg);
+    }
+
+    private class ModbusTcpLog : Logger
+    {
+        private readonly ModbusTcpViewModel _vm;
+        public ModbusTcpLog(ModbusTcpViewModel 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()
+    {
+        Disconnect();
+        GC.SuppressFinalize(this);
+    }
+    #endregion
+}
Added +517 -0
diff --git a/CrazyCoder/ViewModels/RedisManagerViewModel.cs b/CrazyCoder/ViewModels/RedisManagerViewModel.cs
new file mode 100644
index 0000000..ca295af
--- /dev/null
+++ b/CrazyCoder/ViewModels/RedisManagerViewModel.cs
@@ -0,0 +1,517 @@
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Windows;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using CrazyCoder.Models;
+using NewLife;
+using NewLife.Caching;
+using NewLife.Log;
+using NewLife.Serialization;
+
+namespace CrazyCoder.ViewModels;
+
+/// <summary>Redis 管理器 ViewModel</summary>
+public partial class RedisManagerViewModel : ObservableObject
+{
+    #region 属性
+
+    /// <summary>Redis 节点配置列表</summary>
+    public ObservableCollection<RedisConfig> Nodes { get; } = [];
+
+    /// <summary>树形节点数据</summary>
+    public ObservableCollection<RedisTreeNode> TreeNodes { get; } = [];
+
+    /// <summary>Key 搜索模式</summary>
+    [ObservableProperty]
+    private String _searchPattern = "*";
+
+    /// <summary>Key 值内容</summary>
+    [ObservableProperty]
+    private String _keyValue = "";
+
+    /// <summary>选中的 Key</summary>
+    [ObservableProperty]
+    private String _selectedKey = "";
+
+    /// <summary>状态文本</summary>
+    [ObservableProperty]
+    private String _status = "就绪";
+
+    /// <summary>配置数据路径</summary>
+    private readonly String _configFile;
+
+    #endregion
+
+    #region 构造
+
+    /// <summary>实例化 Redis 管理器 ViewModel</summary>
+    public RedisManagerViewModel()
+    {
+        _configFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config", "RedisNodes.json");
+
+        LoadConfig();
+    }
+
+    #endregion
+
+    #region 配置持久化
+
+    private void LoadConfig()
+    {
+        try
+        {
+            var file = _configFile.GetFullPath();
+            if (File.Exists(file))
+            {
+                var json = File.ReadAllText(file);
+                var list = JsonHelper.Convert<IList<RedisConfig>>(json);
+                if (list != null)
+                {
+                    Nodes.Clear();
+                    foreach (var node in list)
+                    {
+                        Nodes.Add(node);
+                    }
+                }
+            }
+        }
+        catch (Exception ex)
+        {
+            XTrace.WriteException(ex);
+        }
+
+        BuildTree();
+    }
+
+    private void SaveConfig()
+    {
+        try
+        {
+            var json = Nodes.ToJson(true);
+            var file = _configFile.GetFullPath();
+            file.EnsureDirectory(true);
+            File.WriteAllText(file, json);
+        }
+        catch (Exception ex)
+        {
+            XTrace.WriteException(ex);
+        }
+    }
+
+    #endregion
+
+    #region 树形结构
+
+    /// <summary>构建树节点</summary>
+    private void BuildTree()
+    {
+        TreeNodes.Clear();
+
+        foreach (var cfg in Nodes)
+        {
+            var node = new RedisTreeNode
+            {
+                Title = cfg.Name,
+                Tag = cfg,
+                IsExpanded = false,
+            };
+            TreeNodes.Add(node);
+        }
+    }
+
+    /// <summary>展开 Redis 服务器节点,显示 db0~db15</summary>
+    private void ExpandServer(RedisTreeNode serverNode, RedisConfig cfg)
+    {
+        try
+        {
+            var rds = new FullRedis
+            {
+                Name = cfg.Name,
+                Server = $"{cfg.Server}:{cfg.Port}",
+                Password = cfg.Password,
+                UserName = cfg.Username,
+            };
+
+            serverNode.Children.Clear();
+            for (var i = 0; i < 16; i++)
+            {
+                var sub = rds.CreateSub(i) as FullRedis;
+                if (sub != null)
+                {
+                    var count = sub.Count;
+                    var dbNode = new RedisTreeNode
+                    {
+                        Title = $"db{i}({count})",
+                        Tag = sub,
+                        IsExpanded = false,
+                    };
+                    serverNode.Children.Add(dbNode);
+                }
+            }
+            serverNode.IsExpanded = true;
+        }
+        catch (Exception ex)
+        {
+            Status = $"连接失败:{ex.Message}";
+            XTrace.WriteException(ex);
+        }
+    }
+
+    /// <summary>展开数据库节点,显示 Key 列表</summary>
+    private void ExpandDatabase(RedisTreeNode dbNode, FullRedis redis)
+    {
+        try
+        {
+            var list = redis.Search(SearchPattern, 0, 100).ToList();
+
+            dbNode.Children.Clear();
+            foreach (var key in list)
+            {
+                var keyNode = new RedisTreeNode
+                {
+                    Title = key,
+                    Tag = key,
+                    IsExpanded = false,
+                };
+                dbNode.Children.Add(keyNode);
+            }
+            dbNode.IsExpanded = true;
+            Status = $"共 {list.Count} 个 Key";
+        }
+        catch (Exception ex)
+        {
+            Status = $"搜索失败:{ex.Message}";
+            XTrace.WriteException(ex);
+        }
+    }
+
+    /// <summary>加载 Key 的值</summary>
+    private void LoadKeyValue(FullRedis redis, String key)
+    {
+        try
+        {
+            var value = redis.Get<String>(key);
+            KeyValue = value ?? "";
+            Status = $"Key: {key}";
+        }
+        catch (Exception ex)
+        {
+            Status = $"读取失败:{ex.Message}";
+            XTrace.WriteException(ex);
+        }
+    }
+
+    /// <summary>处理节点双击</summary>
+    public void HandleNodeDoubleClick(RedisTreeNode node)
+    {
+        if (node == null) return;
+
+        if (node.Tag is RedisConfig cfg)
+        {
+            ExpandServer(node, cfg);
+        }
+        else if (node.Tag is FullRedis redis)
+        {
+            ExpandDatabase(node, redis);
+        }
+        else if (node.Tag is String key)
+        {
+            if (node.Parent?.Tag is FullRedis parentRedis)
+                LoadKeyValue(parentRedis, key);
+        }
+    }
+
+    #endregion
+
+    #region 命令
+
+    /// <summary>添加 Redis 节点</summary>
+    [RelayCommand]
+    private void AddNode()
+    {
+        var cfg = new RedisConfig
+        {
+            Name = "NewNode",
+            Server = "127.0.0.1",
+            Port = 6379,
+        };
+
+        // 简单编辑对话框(使用 WPF 内置方式)
+        var result = ShowEditDialog(cfg, "添加Redis节点");
+        if (result == true)
+        {
+            Nodes.Add(cfg);
+            BuildTree();
+            SaveConfig();
+            Status = $"已添加节点:{cfg.Name}";
+        }
+    }
+
+    /// <summary>编辑选中的 Redis 节点</summary>
+    [RelayCommand]
+    private void EditNode()
+    {
+        var node = FindSelectedConfigNode();
+        if (node == null) return;
+
+        var cfg = node.Tag as RedisConfig;
+        if (cfg == null) return;
+
+        // 复制一份以便取消
+        var copy = new RedisConfig
+        {
+            Name = cfg.Name,
+            Server = cfg.Server,
+            Port = cfg.Port,
+            Username = cfg.Username,
+            Password = cfg.Password,
+        };
+
+        var result = ShowEditDialog(copy, "编辑Redis节点");
+        if (result == true)
+        {
+            cfg.Name = copy.Name;
+            cfg.Server = copy.Server;
+            cfg.Port = copy.Port;
+            cfg.Username = copy.Username;
+            cfg.Password = copy.Password;
+
+            BuildTree();
+            SaveConfig();
+            Status = $"已更新节点:{cfg.Name}";
+        }
+    }
+
+    /// <summary>删除选中的 Redis 节点</summary>
+    [RelayCommand]
+    private void DeleteNode()
+    {
+        var node = FindSelectedConfigNode();
+        if (node == null) return;
+
+        var cfg = node.Tag as RedisConfig;
+        if (cfg == null) return;
+
+        var result = MessageBox.Show($"确定删除节点「{cfg.Name}」吗?", "确认删除",
+            MessageBoxButton.YesNo, MessageBoxImage.Question);
+
+        if (result == MessageBoxResult.Yes)
+        {
+            Nodes.Remove(cfg);
+            BuildTree();
+            SaveConfig();
+            Status = $"已删除节点:{cfg.Name}";
+        }
+    }
+
+    /// <summary>搜索 Key</summary>
+    [RelayCommand]
+    private void SearchKeys()
+    {
+        // 在当前展开的数据库节点上搜索
+        var dbNode = FindSelectedDbNode();
+        if (dbNode?.Tag is FullRedis redis)
+        {
+            ExpandDatabase(dbNode, redis);
+        }
+    }
+
+    private RedisTreeNode FindSelectedConfigNode()
+    {
+        // 遍历 TreeNodes 查找选中项
+        foreach (var node in TreeNodes)
+        {
+            if (node.IsSelected) return node;
+            // 检查子节点
+            foreach (var child in GetAllNodes(node))
+            {
+                if (child.IsSelected) return child;
+            }
+        }
+        return null;
+    }
+
+    private RedisTreeNode FindSelectedDbNode()
+    {
+        foreach (var node in TreeNodes)
+        {
+            foreach (var child in node.Children)
+            {
+                if (child.IsSelected) return child;
+                // 也可能选中了 key 节点,取父节点
+                if (child.Children.Any(c => c.IsSelected)) return child;
+            }
+        }
+        return null;
+    }
+
+    private IEnumerable<RedisTreeNode> GetAllNodes(RedisTreeNode root)
+    {
+        foreach (var child in root.Children)
+        {
+            yield return child;
+            foreach (var grandChild in GetAllNodes(child))
+            {
+                yield return grandChild;
+            }
+        }
+    }
+
+    #endregion
+
+    #region 编辑对话框
+
+    /// <summary>显示编辑对话框</summary>
+    private Boolean? ShowEditDialog(RedisConfig cfg, String title)
+    {
+        // 使用简化的 WPF 输入对话框
+        var window = new Window
+        {
+            Title = title,
+            Width = 420,
+            Height = 320,
+            WindowStartupLocation = WindowStartupLocation.CenterScreen,
+            ResizeMode = ResizeMode.NoResize,
+        };
+
+        var stack = new System.Windows.Controls.StackPanel { Margin = new Thickness(12) };
+
+        AddTextField(stack, "名称:", cfg, nameof(cfg.Name));
+        AddTextField(stack, "服务器:", cfg, nameof(cfg.Server));
+        AddTextField(stack, "端口:", cfg, nameof(cfg.Port));
+        AddTextField(stack, "用户名:", cfg, nameof(cfg.Username));
+        AddPasswordField(stack, "密码:", cfg, nameof(cfg.Password));
+
+        var btnPanel = new System.Windows.Controls.StackPanel
+        {
+            Orientation = System.Windows.Controls.Orientation.Horizontal,
+            HorizontalAlignment = System.Windows.HorizontalAlignment.Right,
+            Margin = new Thickness(0, 12, 0, 0),
+        };
+
+        var btnOk = new System.Windows.Controls.Button
+        {
+            Content = "确定",
+            Width = 80,
+            Height = 30,
+            Margin = new Thickness(0, 0, 8, 0),
+            IsDefault = true,
+        };
+        var btnCancel = new System.Windows.Controls.Button
+        {
+            Content = "取消",
+            Width = 80,
+            Height = 30,
+            IsCancel = true,
+        };
+
+        btnOk.Click += (s, e) => window.DialogResult = true;
+        btnPanel.Children.Add(btnOk);
+        btnPanel.Children.Add(btnCancel);
+        stack.Children.Add(btnPanel);
+
+        window.Content = stack;
+        return window.ShowDialog();
+    }
+
+    private static void AddTextField(System.Windows.Controls.StackPanel stack, String label, Object dataContext, String propertyName)
+    {
+        var panel = new System.Windows.Controls.WrapPanel { Margin = new Thickness(0, 4, 0, 4) };
+        var lbl = new System.Windows.Controls.Label
+        {
+            Content = label,
+            Width = 60,
+            VerticalAlignment = System.Windows.VerticalAlignment.Center,
+        };
+        var txt = new System.Windows.Controls.TextBox
+        {
+            Width = 300,
+            VerticalAlignment = System.Windows.VerticalAlignment.Center,
+            DataContext = dataContext,
+        };
+
+        var prop = dataContext.GetType().GetProperty(propertyName);
+        if (prop != null)
+        {
+            txt.Text = prop.GetValue(dataContext)?.ToString() ?? "";
+            txt.TextChanged += (s, e) =>
+            {
+                var value = txt.Text;
+                if (prop.PropertyType == typeof(Int32))
+                {
+                    if (Int32.TryParse(value, out var n))
+                        prop.SetValue(dataContext, n);
+                }
+                else
+                {
+                    prop.SetValue(dataContext, value);
+                }
+            };
+        }
+
+        panel.Children.Add(lbl);
+        panel.Children.Add(txt);
+        stack.Children.Add(panel);
+    }
+
+    private static void AddPasswordField(System.Windows.Controls.StackPanel stack, String label, Object dataContext, String propertyName)
+    {
+        var panel = new System.Windows.Controls.WrapPanel { Margin = new Thickness(0, 4, 0, 4) };
+        var lbl = new System.Windows.Controls.Label
+        {
+            Content = label,
+            Width = 60,
+            VerticalAlignment = System.Windows.VerticalAlignment.Center,
+        };
+        var txt = new System.Windows.Controls.PasswordBox
+        {
+            Width = 300,
+            VerticalAlignment = System.Windows.VerticalAlignment.Center,
+        };
+
+        var prop = dataContext.GetType().GetProperty(propertyName);
+        if (prop != null)
+        {
+            txt.Password = prop.GetValue(dataContext)?.ToString() ?? "";
+            txt.PasswordChanged += (s, e) => prop.SetValue(dataContext, txt.Password);
+        }
+
+        panel.Children.Add(lbl);
+        panel.Children.Add(txt);
+        stack.Children.Add(panel);
+    }
+
+    #endregion
+}
+
+/// <summary>Redis 树节点</summary>
+public partial class RedisTreeNode : ObservableObject
+{
+    /// <summary>节点标题</summary>
+    [ObservableProperty]
+    private String _title = "";
+
+    /// <summary>关联数据</summary>
+    public Object Tag { get; set; }
+
+    /// <summary>是否展开</summary>
+    [ObservableProperty]
+    private Boolean _isExpanded;
+
+    /// <summary>是否选中</summary>
+    [ObservableProperty]
+    private Boolean _isSelected;
+
+    /// <summary>父节点</summary>
+    public RedisTreeNode Parent { get; set; }
+
+    /// <summary>子节点集合</summary>
+    public ObservableCollection<RedisTreeNode> Children { get; } = [];
+
+    /// <summary>层级</summary>
+    public Int32 Level => Parent?.Level + 1 ?? 0;
+
+    /// <summary>缩进</summary>
+    public Thickness Indent => new Thickness(Level * 16, 0, 0, 0);
+}
Added +314 -0
diff --git a/CrazyCoder/ViewModels/SerialPortViewModel.cs b/CrazyCoder/ViewModels/SerialPortViewModel.cs
new file mode 100644
index 0000000..b630495
--- /dev/null
+++ b/CrazyCoder/ViewModels/SerialPortViewModel.cs
@@ -0,0 +1,314 @@
+#nullable enable
+
+using System.Collections.ObjectModel;
+using System.IO.Ports;
+using System.Text;
+using System.Windows;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using NewLife;
+using NewLife.Threading;
+
+namespace CrazyCoder.ViewModels;
+
+/// <summary>串口调试工具 ViewModel</summary>
+public partial class SerialPortViewModel : ObservableObject, IDisposable
+{
+    #region 属性
+    private SerialPort? _port;
+    private TimerX? _timer;
+
+    /// <summary>端口名称列表</summary>
+    public ObservableCollection<String> PortNames { get; } = [];
+
+    /// <summary>波特率列表</summary>
+    public ObservableCollection<Int32> BaudRates { get; } = [1200, 2400, 4800, 9600, 14400, 19200, 38400, 56000, 57600, 115200, 128000, 194000, 256000, 512000, 1024000, 2048000];
+
+    /// <summary>校验位列表</summary>
+    public ObservableCollection<String> ParityOptions { get; } = ["None", "Odd", "Even", "Mark", "Space"];
+
+    /// <summary>停止位列表</summary>
+    public ObservableCollection<String> StopBitsOptions { get; } = ["One", "Two", "OnePointFive"];
+
+    /// <summary>数据位列表</summary>
+    public ObservableCollection<Int32> DataBitsOptions { get; } = [5, 6, 7, 8];
+
+    /// <summary>端口名称</summary>
+    [ObservableProperty]
+    private String _portName = "COM1";
+
+    /// <summary>波特率</summary>
+    [ObservableProperty]
+    private Int32 _baudRate = 115200;
+
+    /// <summary>校验位索引</summary>
+    [ObservableProperty]
+    private Int32 _parityIndex;
+
+    /// <summary>数据位</summary>
+    [ObservableProperty]
+    private Int32 _dataBits = 8;
+
+    /// <summary>停止位索引</summary>
+    [ObservableProperty]
+    private Int32 _stopBitsIndex;
+
+    /// <summary>十六进制显示</summary>
+    [ObservableProperty]
+    private Boolean _hexDisplay;
+
+    /// <summary>十六进制发送</summary>
+    [ObservableProperty]
+    private Boolean _hexSend;
+
+    /// <summary>是否已连接</summary>
+    [ObservableProperty]
+    private Boolean _isConnected;
+
+    /// <summary>连接按钮文本</summary>
+    [ObservableProperty]
+    private String _connectButtonText = "打开";
+
+    /// <summary>发送文本</summary>
+    [ObservableProperty]
+    private String _sendText = "";
+
+    /// <summary>已发送字节数</summary>
+    [ObservableProperty]
+    private Int64 _bytesSent;
+
+    /// <summary>已接收字节数</summary>
+    [ObservableProperty]
+    private Int64 _bytesReceived;
+
+    /// <summary>日志回调,由 View 绑定到 RichTextBox</summary>
+    public Action<String>? OnLog { get; set; }
+    #endregion
+
+    #region 构造
+    /// <summary>实例化串口调试工具 ViewModel</summary>
+    public SerialPortViewModel()
+    {
+        RefreshPorts();
+    }
+    #endregion
+
+    #region 方法
+    /// <summary>刷新串口列表</summary>
+    [RelayCommand]
+    private void RefreshPorts()
+    {
+        PortNames.Clear();
+        foreach (var name in SerialPort.GetPortNames())
+        {
+            PortNames.Add(name);
+        }
+        if (PortNames.Count > 0) PortName = PortNames[0];
+    }
+    #endregion
+
+    #region 连接/断开
+    /// <summary>切换连接/断开</summary>
+    [RelayCommand]
+    private void ToggleConnect()
+    {
+        if (IsConnected)
+            Disconnect();
+        else
+            Connect();
+    }
+
+    private void Connect()
+    {
+        try
+        {
+            var parity = ParityIndex switch
+            {
+                1 => Parity.Odd,
+                2 => Parity.Even,
+                3 => Parity.Mark,
+                4 => Parity.Space,
+                _ => Parity.None,
+            };
+            var stopBits = StopBitsIndex switch
+            {
+                1 => StopBits.Two,
+                2 => StopBits.OnePointFive,
+                _ => StopBits.One,
+            };
+
+            var port = new SerialPort(PortName, BaudRate, parity, DataBits, stopBits);
+            port.DataReceived += OnDataReceived;
+            port.ErrorReceived += OnErrorReceived;
+            port.Open();
+
+            _port = port;
+
+            IsConnected = true;
+            ConnectButtonText = "关闭";
+
+            WriteLog($"串口 {PortName} 已打开,{BaudRate}/{DataBits}/{ParityOptions[ParityIndex]}/{StopBitsOptions[StopBitsIndex]}");
+
+            BytesSent = 0;
+            BytesReceived = 0;
+
+            _timer = new TimerX(OnTimer, null, 1000, 1000) { Async = true };
+        }
+        catch (Exception ex)
+        {
+            WriteLog($"打开失败:{ex.Message}");
+            MessageBox.Show($"打开串口失败:{ex.Message}", "错误");
+        }
+    }
+
+    private void Disconnect()
+    {
+        _timer.TryDispose();
+        _timer = null;
+
+        if (_port != null)
+        {
+            try
+            {
+                if (_port.IsOpen) _port.Close();
+            }
+            catch (Exception ex)
+            {
+                WriteLog($"关闭异常:{ex.Message}");
+            }
+            _port.Dispose();
+            _port = null;
+        }
+
+        IsConnected = false;
+        ConnectButtonText = "打开";
+
+        WriteLog($"串口 {PortName} 已关闭");
+    }
+
+    private void OnTimer(Object? state)
+    {
+        var port = _port;
+        if (port == null || !port.IsOpen)
+        {
+            if (IsConnected)
+            {
+                WriteLog("串口已断开");
+                Disconnect();
+            }
+            return;
+        }
+
+        // 更新计数
+        // SerialPort doesn't have built-in byte counters, we track via events
+    }
+
+    private void OnDataReceived(Object sender, SerialDataReceivedEventArgs e)
+    {
+        var port = _port;
+        if (port == null) return;
+
+        try
+        {
+            var count = port.BytesToRead;
+            if (count <= 0) return;
+
+            var buf = new Byte[count];
+            var n = port.Read(buf, 0, count);
+            if (n <= 0) return;
+
+            BytesReceived += n;
+
+            var hex = buf.ToHex(" ", 0, n);
+            var str = Encoding.UTF8.GetString(buf, 0, n);
+
+            if (HexDisplay)
+                WriteLog($"[接收] [{n}字节] {hex}");
+            else
+                WriteLog($"[接收] [{n}字节] {str}");
+        }
+        catch (Exception ex)
+        {
+            WriteLog($"接收异常:{ex.Message}");
+        }
+    }
+
+    private void OnErrorReceived(Object sender, SerialErrorReceivedEventArgs e)
+    {
+        WriteLog($"串口错误:{e.EventType}");
+    }
+    #endregion
+
+    #region 发送
+    /// <summary>发送数据</summary>
+    [RelayCommand]
+    private void Send()
+    {
+        var port = _port;
+        if (port == null || !port.IsOpen)
+        {
+            WriteLog("串口未打开");
+            return;
+        }
+
+        var str = SendText;
+        if (String.IsNullOrEmpty(str))
+        {
+            MessageBox.Show("发送内容不能为空!", "提示");
+            return;
+        }
+
+        try
+        {
+            if (HexSend)
+            {
+                // 十六进制发送
+                var hex = str.Replace(" ", "").Replace("-", "");
+                if (hex.Length % 2 != 0) hex = "0" + hex;
+                var buf = hex.ToHex();
+                if (buf.Length > 0)
+                {
+                    port.Write(buf, 0, buf.Length);
+                    BytesSent += buf.Length;
+                    WriteLog($"[发送] [{buf.Length}字节] {buf.ToHex(" ", 0, buf.Length)}");
+                }
+            }
+            else
+            {
+                // 文本发送
+                var data = Encoding.UTF8.GetBytes(str);
+                port.Write(data, 0, data.Length);
+                BytesSent += data.Length;
+                WriteLog($"[发送] [{data.Length}字节] {str}");
+            }
+        }
+        catch (Exception ex)
+        {
+            WriteLog($"发送失败:{ex.Message}");
+        }
+    }
+
+    /// <summary>清空日志</summary>
+    [RelayCommand]
+    private void ClearLog()
+    {
+        OnLog?.Invoke("__CLEAR__");
+    }
+    #endregion
+
+    #region 日志
+    private void WriteLog(String msg)
+    {
+        OnLog?.Invoke(msg);
+    }
+    #endregion
+
+    #region IDisposable
+    /// <summary>释放资源</summary>
+    public void Dispose()
+    {
+        Disconnect();
+        GC.SuppressFinalize(this);
+    }
+    #endregion
+}
Added +172 -0
diff --git a/CrazyCoder/Views/DataModelingWindow.xaml b/CrazyCoder/Views/DataModelingWindow.xaml
new file mode 100644
index 0000000..8faebf7
--- /dev/null
+++ b/CrazyCoder/Views/DataModelingWindow.xaml
@@ -0,0 +1,172 @@
+<Window x:Class="CrazyCoder.Views.DataModelingWindow"
+        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"
+        xmlns:xcode="clr-namespace:XCode.DataAccessLayer;assembly=NewLife.XCode"
+        mc:Ignorable="d"
+        Title="数据建模" Height="700" 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="LabelText" TargetType="TextBlock">
+            <Setter Property="Margin" Value="4,0,4,0"/>
+            <Setter Property="VerticalAlignment" Value="Center"/>
+        </Style>
+
+        <Style x:Key="ActionButton" TargetType="Button">
+            <Setter Property="Height" Value="30"/>
+            <Setter Property="Margin" Value="4,0"/>
+            <Setter Property="Padding" Value="12,0"/>
+        </Style>
+    </Window.Resources>
+
+    <Grid Margin="8">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="*"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+        </Grid.RowDefinitions>
+
+        <!-- 第1行:连接区 -->
+        <Border Grid.Row="0" Style="{StaticResource GroupBorder}" Background="#FFF5E6" Padding="8">
+            <Grid>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="Auto"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="Auto"/>
+                    <ColumnDefinition Width="Auto"/>
+                </Grid.ColumnDefinitions>
+
+                <TextBlock Text="连接:" Style="{StaticResource LabelText}" FontWeight="Bold"/>
+                <ComboBox Grid.Column="1" ItemsSource="{Binding Connections}" Text="{Binding ConnName, UpdateSourceTrigger=PropertyChanged}"
+                          Margin="4,0" Height="28" IsEditable="True"/>
+
+                <Button Grid.Column="2" Content="{Binding ConnectButtonText}" Command="{Binding ConnectCommand}"
+                        Style="{StaticResource ActionButton}" MinWidth="80"/>
+            </Grid>
+        </Border>
+
+        <!-- 第2行:表选择和生成按钮 -->
+        <Border Grid.Row="1" Style="{StaticResource GroupBorder}" Background="#E8FFE8" Padding="8"
+                IsEnabled="{Binding IsConnected}">
+            <Grid>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="Auto"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="Auto"/>
+                    <ColumnDefinition Width="Auto"/>
+                    <ColumnDefinition Width="Auto"/>
+                    <ColumnDefinition Width="Auto"/>
+                </Grid.ColumnDefinitions>
+
+                <TextBlock Text="数据表:" Style="{StaticResource LabelText}" FontWeight="Bold"/>
+                <ComboBox Grid.Column="1" ItemsSource="{Binding Tables}" SelectedItem="{Binding SelectedTable}"
+                          Margin="4,0" Height="28" DisplayMemberPath="Name">
+                    <ComboBox.ItemTemplate>
+                        <DataTemplate>
+                            <TextBlock Text="{Binding Name}"/>
+                        </DataTemplate>
+                    </ComboBox.ItemTemplate>
+                </ComboBox>
+
+                <CheckBox Grid.Column="2" Content="包含视图" IsChecked="{Binding IncludeView}" Margin="8,0" VerticalAlignment="Center"/>
+
+                <Button Grid.Column="3" Content="刷新" Command="{Binding RefreshTablesCommand}" Style="{StaticResource ActionButton}"/>
+                <Button Grid.Column="4" Content="生成选中" Command="{Binding GenerateTableCommand}" Style="{StaticResource ActionButton}"/>
+                <Button Grid.Column="5" Content="生成所有" Command="{Binding GenerateAllCommand}" Style="{StaticResource ActionButton}"/>
+            </Grid>
+        </Border>
+
+        <!-- 第3行:表列表 -->
+        <Border Grid.Row="2" Style="{StaticResource GroupBorder}" IsEnabled="{Binding IsConnected}">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+                <TextBlock Text="表结构列表" FontWeight="Bold" Background="#F0F0F0" Padding="6,3"/>
+                <ListView Grid.Row="1" ItemsSource="{Binding Tables}" SelectedItem="{Binding SelectedTable}"
+                          BorderThickness="0">
+                    <ListView.View>
+                        <GridView>
+                            <GridViewColumn Header="表名" Width="200" DisplayMemberBinding="{Binding Name}"/>
+                            <GridViewColumn Header="描述" Width="200" DisplayMemberBinding="{Binding Description}"/>
+                            <GridViewColumn Header="类型" Width="100" DisplayMemberBinding="{Binding DbType}"/>
+                            <GridViewColumn Header="行数" Width="80" DisplayMemberBinding="{Binding Rows}"/>
+                        </GridView>
+                    </ListView.View>
+                </ListView>
+            </Grid>
+        </Border>
+
+        <!-- 第4行:配置区 -->
+        <Border Grid.Row="3" Style="{StaticResource GroupBorder}" Padding="8"
+                IsEnabled="{Binding IsConnected}">
+            <Grid>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="Auto"/>
+                </Grid.ColumnDefinitions>
+
+                <StackPanel Grid.Column="0" Margin="4,0">
+                    <TextBlock Text="命名空间" FontWeight="Bold"/>
+                    <TextBox Text="{Binding NameSpace, UpdateSourceTrigger=PropertyChanged}" Height="26" Margin="0,2,0,0"/>
+                </StackPanel>
+
+                <StackPanel Grid.Column="1" Margin="4,0">
+                    <TextBlock Text="连接名" FontWeight="Bold"/>
+                    <TextBox Text="{Binding EntityConnName, UpdateSourceTrigger=PropertyChanged}" Height="26" Margin="0,2,0,0"/>
+                </StackPanel>
+
+                <StackPanel Grid.Column="2" Margin="4,0">
+                    <TextBlock Text="基类" FontWeight="Bold"/>
+                    <TextBox Text="{Binding BaseClass, UpdateSourceTrigger=PropertyChanged}" Height="26" Margin="0,2,0,0"/>
+                </StackPanel>
+
+                <StackPanel Grid.Column="3" Margin="4,0">
+                    <TextBlock Text="输出路径" FontWeight="Bold"/>
+                    <Grid>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="*"/>
+                            <ColumnDefinition Width="Auto"/>
+                        </Grid.ColumnDefinitions>
+                        <TextBox Text="{Binding OutputPath, UpdateSourceTrigger=PropertyChanged}" Height="26" Margin="0,2,0,0"/>
+                        <Button Grid.Column="1" Content="..." Command="{Binding OpenOutputDirCommand}" Height="26" Width="30" Margin="2,2,0,0"/>
+                    </Grid>
+                </StackPanel>
+
+                <StackPanel Grid.Column="4" Margin="8,0,0,0" VerticalAlignment="Center">
+                    <CheckBox Content="中文文件名" IsChecked="{Binding UseCNFileName}" Margin="0,2"/>
+                    <CheckBox Content="泛型实体" IsChecked="{Binding RenderGenEntity}" Margin="0,2"/>
+                </StackPanel>
+            </Grid>
+        </Border>
+
+        <!-- 第5行:状态栏 -->
+        <StatusBar Grid.Row="4" Margin="0,2,0,0">
+            <StatusBar.ItemsPanel>
+                <ItemsPanelTemplate>
+                    <Grid>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="*"/>
+                            <ColumnDefinition Width="Auto"/>
+                        </Grid.ColumnDefinitions>
+                    </Grid>
+                </ItemsPanelTemplate>
+            </StatusBar.ItemsPanel>
+            <StatusBarItem>
+                <TextBlock Text="{Binding Status}"/>
+            </StatusBarItem>
+        </StatusBar>
+    </Grid>
+</Window>
Added +20 -0
diff --git a/CrazyCoder/Views/DataModelingWindow.xaml.cs b/CrazyCoder/Views/DataModelingWindow.xaml.cs
new file mode 100644
index 0000000..f3b0655
--- /dev/null
+++ b/CrazyCoder/Views/DataModelingWindow.xaml.cs
@@ -0,0 +1,20 @@
+using System.Windows;
+using CrazyCoder.ViewModels;
+
+namespace CrazyCoder.Views;
+
+/// <summary>数据建模工具窗口</summary>
+public partial class DataModelingWindow : Window
+{
+    /// <summary>ViewModel</summary>
+    public DataModelingViewModel ViewModel { get; }
+
+    /// <summary>实例化数据建模工具窗口</summary>
+    public DataModelingWindow()
+    {
+        InitializeComponent();
+
+        ViewModel = new DataModelingViewModel();
+        DataContext = ViewModel;
+    }
+}
Added +155 -0
diff --git a/CrazyCoder/Views/DataSyncWindow.xaml b/CrazyCoder/Views/DataSyncWindow.xaml
new file mode 100644
index 0000000..ab4aaae
--- /dev/null
+++ b/CrazyCoder/Views/DataSyncWindow.xaml
@@ -0,0 +1,155 @@
+<Window x:Class="CrazyCoder.Views.DataSyncWindow"
+        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="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="LabelText" TargetType="TextBlock">
+            <Setter Property="Margin" Value="4,0,4,0"/>
+            <Setter Property="VerticalAlignment" Value="Center"/>
+        </Style>
+
+        <Style x:Key="ActionButton" TargetType="Button">
+            <Setter Property="Height" Value="30"/>
+            <Setter Property="Margin" Value="4,0"/>
+            <Setter Property="Padding" Value="12,0"/>
+        </Style>
+    </Window.Resources>
+
+    <Grid Margin="8">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="*"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+        </Grid.RowDefinitions>
+
+        <!-- 第1行:源数据库连接 -->
+        <Border Grid.Row="0" Style="{StaticResource GroupBorder}" Background="#FFF5E6" Padding="8">
+            <Grid>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="Auto"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="Auto"/>
+                    <ColumnDefinition Width="Auto"/>
+                </Grid.ColumnDefinitions>
+
+                <TextBlock Text="源数据库:" Style="{StaticResource LabelText}" FontWeight="Bold"/>
+                <ComboBox Grid.Column="1" ItemsSource="{Binding Connections}" Text="{Binding SourceConn, UpdateSourceTrigger=PropertyChanged}"
+                          Margin="4,0" Height="28" IsEditable="True"/>
+
+                <Button Grid.Column="2" Content="连接" Command="{Binding ConnectSourceCommand}" Style="{StaticResource ActionButton}" MinWidth="80"/>
+                <Button Grid.Column="3" Content="断开" Command="{Binding DisconnectSourceCommand}" Style="{StaticResource ActionButton}" MinWidth="80"
+                        IsEnabled="{Binding IsSourceConnected}"/>
+            </Grid>
+        </Border>
+
+        <!-- 第2行:目标数据库连接 -->
+        <Border Grid.Row="1" Style="{StaticResource GroupBorder}" Background="#E8FFE8" Padding="8"
+                IsEnabled="{Binding IsSourceConnected}">
+            <Grid>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="Auto"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="Auto"/>
+                    <ColumnDefinition Width="Auto"/>
+                </Grid.ColumnDefinitions>
+
+                <TextBlock Text="目标数据库:" Style="{StaticResource LabelText}" FontWeight="Bold"/>
+                <ComboBox Grid.Column="1" ItemsSource="{Binding TargetConnections}" Text="{Binding TargetConn, UpdateSourceTrigger=PropertyChanged}"
+                          Margin="4,0" Height="28" IsEditable="True"/>
+
+                <Button Grid.Column="2" Content="连接" Command="{Binding ConnectTargetCommand}" Style="{StaticResource ActionButton}" MinWidth="80"/>
+                <Button Grid.Column="3" Content="断开" Command="{Binding DisconnectTargetCommand}" Style="{StaticResource ActionButton}" MinWidth="80"
+                        IsEnabled="{Binding IsTargetConnected}"/>
+            </Grid>
+        </Border>
+
+        <!-- 第3行:表列表 -->
+        <Border Grid.Row="2" Style="{StaticResource GroupBorder}">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+                <Grid>
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="*"/>
+                        <ColumnDefinition Width="Auto"/>
+                    </Grid.ColumnDefinitions>
+                    <TextBlock Text="数据表列表" FontWeight="Bold" Background="#F0F0F0" Padding="6,3"/>
+                    <StackPanel Grid.Column="1" Orientation="Horizontal" Background="#F0F0F0">
+                        <Button Content="全选" Command="{Binding SelectAllCommand}" Height="24" Width="60" Margin="2"/>
+                        <Button Content="反选" Command="{Binding InvertSelectionCommand}" Height="24" Width="60" Margin="2"/>
+                        <Button Content="选差异" Command="{Binding SelectDifferentCommand}" Height="24" Width="60" Margin="2"/>
+                    </StackPanel>
+                </Grid>
+                <ListView Grid.Row="1" ItemsSource="{Binding Tables}" BorderThickness="0">
+                    <ListView.View>
+                        <GridView>
+                            <GridViewColumn Header="同步" Width="50">
+                                <GridViewColumn.CellTemplate>
+                                    <DataTemplate>
+                                        <CheckBox IsChecked="{Binding EnableSync}" HorizontalAlignment="Center"/>
+                                    </DataTemplate>
+                                </GridViewColumn.CellTemplate>
+                            </GridViewColumn>
+                            <GridViewColumn Header="表名" Width="150" DisplayMemberBinding="{Binding Name}"/>
+                            <GridViewColumn Header="描述" Width="150" DisplayMemberBinding="{Binding DisplayName}"/>
+                            <GridViewColumn Header="源行数" Width="80" DisplayMemberBinding="{Binding SourceCount}"/>
+                            <GridViewColumn Header="目标行数" Width="80" DisplayMemberBinding="{Binding TargetCount}"/>
+                            <GridViewColumn Header="已同步" Width="80" DisplayMemberBinding="{Binding SyncCount}"/>
+                            <GridViewColumn Header="备注" Width="*" DisplayMemberBinding="{Binding Description}"/>
+                        </GridView>
+                    </ListView.View>
+                </ListView>
+            </Grid>
+        </Border>
+
+        <!-- 第4行:同步设置 -->
+        <Border Grid.Row="3" Style="{StaticResource GroupBorder}" Padding="8"
+                IsEnabled="{Binding IsTargetConnected}">
+            <Grid>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="Auto"/>
+                    <ColumnDefinition Width="Auto"/>
+                    <ColumnDefinition Width="Auto"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="Auto"/>
+                </Grid.ColumnDefinitions>
+
+                <CheckBox Grid.Column="0" Content="同步架构" IsChecked="{Binding SyncSchema}" Margin="4,0" VerticalAlignment="Center"/>
+                <CheckBox Grid.Column="1" Content="忽略错误" IsChecked="{Binding IgnoreError}" Margin="4,0" VerticalAlignment="Center"/>
+                <TextBlock Grid.Column="2" Text="{Binding ProgressText}" VerticalAlignment="Center" Margin="8,0"/>
+                <Button Grid.Column="4" Content="开始同步" Command="{Binding SyncDataCommand}"
+                        Style="{StaticResource ActionButton}" MinWidth="100"
+                        IsEnabled="{Binding IsTargetConnected}"/>
+            </Grid>
+        </Border>
+
+        <!-- 第5行:状态栏 -->
+        <StatusBar Grid.Row="4" Margin="0,2,0,0">
+            <StatusBar.ItemsPanel>
+                <ItemsPanelTemplate>
+                    <Grid>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="*"/>
+                        </Grid.ColumnDefinitions>
+                    </Grid>
+                </ItemsPanelTemplate>
+            </StatusBar.ItemsPanel>
+            <StatusBarItem>
+                <TextBlock Text="{Binding Status}"/>
+            </StatusBarItem>
+        </StatusBar>
+    </Grid>
+</Window>
Added +20 -0
diff --git a/CrazyCoder/Views/DataSyncWindow.xaml.cs b/CrazyCoder/Views/DataSyncWindow.xaml.cs
new file mode 100644
index 0000000..caaeff8
--- /dev/null
+++ b/CrazyCoder/Views/DataSyncWindow.xaml.cs
@@ -0,0 +1,20 @@
+using System.Windows;
+using CrazyCoder.ViewModels;
+
+namespace CrazyCoder.Views;
+
+/// <summary>跨库数据同步窗口</summary>
+public partial class DataSyncWindow : Window
+{
+    /// <summary>ViewModel</summary>
+    public DataSyncViewModel ViewModel { get; }
+
+    /// <summary>实例化跨库数据同步窗口</summary>
+    public DataSyncWindow()
+    {
+        InitializeComponent();
+
+        ViewModel = new DataSyncViewModel();
+        DataContext = ViewModel;
+    }
+}
Added +368 -0
diff --git a/CrazyCoder/Views/IoControlWindow.xaml b/CrazyCoder/Views/IoControlWindow.xaml
new file mode 100644
index 0000000..5cab05c
--- /dev/null
+++ b/CrazyCoder/Views/IoControlWindow.xaml
@@ -0,0 +1,368 @@
+<Window x:Class="CrazyCoder.Views.IoControlWindow"
+        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="I/O 控制面板" Height="650" Width="900" WindowStartupLocation="CenterScreen">
+    <Window.Resources>
+        <Style x:Key="GroupBorder" TargetType="Border">
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="Margin" Value="0,0,0,6"/>
+        </Style>
+
+        <Style x:Key="SectionTitle" TargetType="TextBlock">
+            <Setter Property="FontWeight" Value="Bold"/>
+            <Setter Property="Margin" Value="4,3"/>
+            <Setter Property="FontSize" Value="13"/>
+        </Style>
+
+        <Style x:Key="ConfigLabel" TargetType="TextBlock">
+            <Setter Property="VerticalAlignment" Value="Center"/>
+            <Setter Property="Margin" Value="4,2"/>
+        </Style>
+
+        <Style x:Key="ConfigComboBox" TargetType="ComboBox">
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Height" Value="26"/>
+            <Setter Property="VerticalContentAlignment" Value="Center"/>
+        </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="BigButton" TargetType="Button">
+            <Setter Property="Height" Value="40"/>
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="FontSize" Value="14"/>
+            <Setter Property="FontWeight" Value="Bold"/>
+        </Style>
+
+        <Style x:Key="PortButton" TargetType="Button">
+            <Setter Property="Width" Value="80"/>
+            <Setter Property="Height" Value="40"/>
+            <Setter Property="Margin" Value="4"/>
+            <Setter Property="FontSize" Value="14"/>
+            <Setter Property="FontWeight" Value="Bold"/>
+        </Style>
+    </Window.Resources>
+
+    <Grid Margin="6">
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="280"/>
+            <ColumnDefinition Width="*"/>
+        </Grid.ColumnDefinitions>
+
+        <!-- ============ 左侧:配置面板 ============ -->
+        <ScrollViewer Grid.Column="0" VerticalScrollBarVisibility="Auto" Margin="0,0,4,0">
+            <StackPanel>
+                <!-- 串口配置 -->
+                <Border Style="{StaticResource GroupBorder}">
+                    <StackPanel>
+                        <TextBlock Text="串口配置" Style="{StaticResource SectionTitle}"/>
+
+                        <Grid>
+                            <Grid.RowDefinitions>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition Height="Auto"/>
+                            </Grid.RowDefinitions>
+                            <Grid.ColumnDefinitions>
+                                <ColumnDefinition Width="50"/>
+                                <ColumnDefinition Width="*"/>
+                                <ColumnDefinition Width="Auto"/>
+                            </Grid.ColumnDefinitions>
+
+                            <TextBlock Grid.Row="0" Grid.Column="0" Text="端口" Style="{StaticResource ConfigLabel}"/>
+                            <ComboBox Grid.Row="0" Grid.Column="1"
+                                      ItemsSource="{Binding PortNames}"
+                                      SelectedItem="{Binding PortName, UpdateSourceTrigger=PropertyChanged}"
+                                      Style="{StaticResource ConfigComboBox}"/>
+                            <Button Grid.Row="0" Grid.Column="2" Content="刷新" Command="{Binding RefreshPortsCommand}"
+                                    Style="{StaticResource ActionButton}" Width="50"/>
+
+                            <TextBlock Grid.Row="1" Grid.Column="0" Text="波特率" Style="{StaticResource ConfigLabel}"/>
+                            <ComboBox Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2"
+                                      ItemsSource="{Binding BaudRates}"
+                                      SelectedItem="{Binding BaudRate, UpdateSourceTrigger=PropertyChanged}"
+                                      Style="{StaticResource ConfigComboBox}"/>
+
+                            <TextBlock Grid.Row="2" Grid.Column="0" Text="校验位" Style="{StaticResource ConfigLabel}"/>
+                            <ComboBox Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2"
+                                      ItemsSource="{Binding ParityOptions}"
+                                      SelectedIndex="{Binding ParityIndex, UpdateSourceTrigger=PropertyChanged}"
+                                      Style="{StaticResource ConfigComboBox}"/>
+
+                            <TextBlock Grid.Row="3" Grid.Column="0" Text="数据位" Style="{StaticResource ConfigLabel}"/>
+                            <ComboBox Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2"
+                                      ItemsSource="{Binding DataBitsOptions}"
+                                      SelectedItem="{Binding DataBits, UpdateSourceTrigger=PropertyChanged}"
+                                      Style="{StaticResource ConfigComboBox}"/>
+
+                            <TextBlock Grid.Row="4" Grid.Column="0" Text="停止位" Style="{StaticResource ConfigLabel}"/>
+                            <ComboBox Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="2"
+                                      ItemsSource="{Binding StopBitsOptions}"
+                                      SelectedIndex="{Binding StopBitsIndex, UpdateSourceTrigger=PropertyChanged}"
+                                      Style="{StaticResource ConfigComboBox}"/>
+
+                            <TextBlock Grid.Row="5" Grid.Column="0" Text="站号" Style="{StaticResource ConfigLabel}"/>
+                            <TextBox Grid.Row="5" Grid.Column="1" Grid.ColumnSpan="2"
+                                     Text="{Binding SlaveAddress, UpdateSourceTrigger=PropertyChanged}"
+                                     Style="{StaticResource ConfigTextBox}"/>
+
+                            <Button Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="3"
+                                    Content="{Binding ConnectButtonText}"
+                                    Command="{Binding ToggleConnectCommand}"
+                                    Style="{StaticResource BigButton}" Foreground="White"/>
+                        </Grid>
+                    </StackPanel>
+                </Border>
+
+                <!-- 设备信息 -->
+                <Border Style="{StaticResource GroupBorder}">
+                    <StackPanel>
+                        <TextBlock Text="设备信息" Style="{StaticResource SectionTitle}"/>
+                        <TextBlock Margin="4,2">
+                            <Run FontWeight="Bold">型号:</Run>
+                            <Run Text="{Binding ProductType, Mode=OneWay}"/>
+                        </TextBlock>
+                        <TextBlock Margin="4,2">
+                            <Run FontWeight="Bold">版本:</Run>
+                            <Run Text="{Binding FirmwareVersion, Mode=OneWay}"/>
+                        </TextBlock>
+                        <Button Content="读取设备信息" Command="{Binding ReadDeviceInfoCommand}"
+                                Style="{StaticResource ActionButton}" Margin="4,2"/>
+                    </StackPanel>
+                </Border>
+
+                <!-- 批量操作 -->
+                <Border Style="{StaticResource GroupBorder}">
+                    <StackPanel>
+                        <TextBlock Text="批量操作" Style="{StaticResource SectionTitle}"/>
+                        <Button Content="读取输入" Command="{Binding ReadInputsCommand}"
+                                Style="{StaticResource ActionButton}" Margin="4,2"/>
+                        <Button Content="读取输出" Command="{Binding ReadOutputsCommand}"
+                                Style="{StaticResource ActionButton}" Margin="4,2"/>
+                        <Button Content="全部打开" Command="{Binding TurnOnAllCommand}"
+                                Style="{StaticResource ActionButton}" Margin="4,2" Foreground="Green"/>
+                        <Button Content="全部关闭" Command="{Binding TurnOffAllCommand}"
+                                Style="{StaticResource ActionButton}" Margin="4,2" Foreground="Red"/>
+                        <WrapPanel Margin="4,2">
+                            <TextBlock Text="延迟(ms)" VerticalAlignment="Center" Margin="0,0,4,0"/>
+                            <TextBox Text="{Binding Delay, UpdateSourceTrigger=PropertyChanged}" Width="60"
+                                     Style="{StaticResource ConfigTextBox}"/>
+                        </WrapPanel>
+                    </StackPanel>
+                </Border>
+            </StackPanel>
+        </ScrollViewer>
+
+        <!-- ============ 右侧:I/O 面板 + 日志 ============ -->
+        <Grid Grid.Column="1">
+            <Grid.RowDefinitions>
+                <RowDefinition Height="Auto"/>
+                <RowDefinition Height="Auto"/>
+                <RowDefinition Height="*"/>
+            </Grid.RowDefinitions>
+
+            <!-- 输出面板 -->
+            <Border Grid.Row="0" Style="{StaticResource GroupBorder}" Padding="4">
+                <StackPanel>
+                    <TextBlock Text="输出端口(Output)" Style="{StaticResource SectionTitle}"/>
+                    <WrapPanel>
+                        <Border BorderBrush="#4CAF50" BorderThickness="1" CornerRadius="4" Margin="4" Padding="4" Width="90">
+                            <StackPanel>
+                                <TextBlock Text="输出 1" HorizontalAlignment="Center" FontSize="11"/>
+                                <Ellipse Width="20" Height="20" Fill="{Binding Output1, Converter={x:Null}}" HorizontalAlignment="Center" Margin="0,2"/>
+                                <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
+                                    <Button Content="开" Command="{Binding TurnOnCommand}" CommandParameter="1"
+                                            Width="30" Height="24" FontSize="11" Margin="1"/>
+                                    <Button Content="关" Command="{Binding TurnOffCommand}" CommandParameter="1"
+                                            Width="30" Height="24" FontSize="11" Margin="1"/>
+                                </StackPanel>
+                            </StackPanel>
+                        </Border>
+                        <Border BorderBrush="#4CAF50" BorderThickness="1" CornerRadius="4" Margin="4" Padding="4" Width="90">
+                            <StackPanel>
+                                <TextBlock Text="输出 2" HorizontalAlignment="Center" FontSize="11"/>
+                                <Ellipse Width="20" Height="20" Fill="{Binding Output2, Converter={x:Null}}" HorizontalAlignment="Center" Margin="0,2"/>
+                                <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
+                                    <Button Content="开" Command="{Binding TurnOnCommand}" CommandParameter="2"
+                                            Width="30" Height="24" FontSize="11" Margin="1"/>
+                                    <Button Content="关" Command="{Binding TurnOffCommand}" CommandParameter="2"
+                                            Width="30" Height="24" FontSize="11" Margin="1"/>
+                                </StackPanel>
+                            </StackPanel>
+                        </Border>
+                        <Border BorderBrush="#4CAF50" BorderThickness="1" CornerRadius="4" Margin="4" Padding="4" Width="90">
+                            <StackPanel>
+                                <TextBlock Text="输出 3" HorizontalAlignment="Center" FontSize="11"/>
+                                <Ellipse Width="20" Height="20" Fill="{Binding Output3, Converter={x:Null}}" HorizontalAlignment="Center" Margin="0,2"/>
+                                <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
+                                    <Button Content="开" Command="{Binding TurnOnCommand}" CommandParameter="3"
+                                            Width="30" Height="24" FontSize="11" Margin="1"/>
+                                    <Button Content="关" Command="{Binding TurnOffCommand}" CommandParameter="3"
+                                            Width="30" Height="24" FontSize="11" Margin="1"/>
+                                </StackPanel>
+                            </StackPanel>
+                        </Border>
+                        <Border BorderBrush="#4CAF50" BorderThickness="1" CornerRadius="4" Margin="4" Padding="4" Width="90">
+                            <StackPanel>
+                                <TextBlock Text="输出 4" HorizontalAlignment="Center" FontSize="11"/>
+                                <Ellipse Width="20" Height="20" Fill="{Binding Output4, Converter={x:Null}}" HorizontalAlignment="Center" Margin="0,2"/>
+                                <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
+                                    <Button Content="开" Command="{Binding TurnOnCommand}" CommandParameter="4"
+                                            Width="30" Height="24" FontSize="11" Margin="1"/>
+                                    <Button Content="关" Command="{Binding TurnOffCommand}" CommandParameter="4"
+                                            Width="30" Height="24" FontSize="11" Margin="1"/>
+                                </StackPanel>
+                            </StackPanel>
+                        </Border>
+                        <Border BorderBrush="#4CAF50" BorderThickness="1" CornerRadius="4" Margin="4" Padding="4" Width="90">
+                            <StackPanel>
+                                <TextBlock Text="输出 5" HorizontalAlignment="Center" FontSize="11"/>
+                                <Ellipse Width="20" Height="20" Fill="{Binding Output5, Converter={x:Null}}" HorizontalAlignment="Center" Margin="0,2"/>
+                                <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
+                                    <Button Content="开" Command="{Binding TurnOnCommand}" CommandParameter="5"
+                                            Width="30" Height="24" FontSize="11" Margin="1"/>
+                                    <Button Content="关" Command="{Binding TurnOffCommand}" CommandParameter="5"
+                                            Width="30" Height="24" FontSize="11" Margin="1"/>
+                                </StackPanel>
+                            </StackPanel>
+                        </Border>
+                        <Border BorderBrush="#4CAF50" BorderThickness="1" CornerRadius="4" Margin="4" Padding="4" Width="90">
+                            <StackPanel>
+                                <TextBlock Text="输出 6" HorizontalAlignment="Center" FontSize="11"/>
+                                <Ellipse Width="20" Height="20" Fill="{Binding Output6, Converter={x:Null}}" HorizontalAlignment="Center" Margin="0,2"/>
+                                <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
+                                    <Button Content="开" Command="{Binding TurnOnCommand}" CommandParameter="6"
+                                            Width="30" Height="24" FontSize="11" Margin="1"/>
+                                    <Button Content="关" Command="{Binding TurnOffCommand}" CommandParameter="6"
+                                            Width="30" Height="24" FontSize="11" Margin="1"/>
+                                </StackPanel>
+                            </StackPanel>
+                        </Border>
+                        <Border BorderBrush="#4CAF50" BorderThickness="1" CornerRadius="4" Margin="4" Padding="4" Width="90">
+                            <StackPanel>
+                                <TextBlock Text="输出 7" HorizontalAlignment="Center" FontSize="11"/>
+                                <Ellipse Width="20" Height="20" Fill="{Binding Output7, Converter={x:Null}}" HorizontalAlignment="Center" Margin="0,2"/>
+                                <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
+                                    <Button Content="开" Command="{Binding TurnOnCommand}" CommandParameter="7"
+                                            Width="30" Height="24" FontSize="11" Margin="1"/>
+                                    <Button Content="关" Command="{Binding TurnOffCommand}" CommandParameter="7"
+                                            Width="30" Height="24" FontSize="11" Margin="1"/>
+                                </StackPanel>
+                            </StackPanel>
+                        </Border>
+                        <Border BorderBrush="#4CAF50" BorderThickness="1" CornerRadius="4" Margin="4" Padding="4" Width="90">
+                            <StackPanel>
+                                <TextBlock Text="输出 8" HorizontalAlignment="Center" FontSize="11"/>
+                                <Ellipse Width="20" Height="20" Fill="{Binding Output8, Converter={x:Null}}" HorizontalAlignment="Center" Margin="0,2"/>
+                                <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
+                                    <Button Content="开" Command="{Binding TurnOnCommand}" CommandParameter="8"
+                                            Width="30" Height="24" FontSize="11" Margin="1"/>
+                                    <Button Content="关" Command="{Binding TurnOffCommand}" CommandParameter="8"
+                                            Width="30" Height="24" FontSize="11" Margin="1"/>
+                                </StackPanel>
+                            </StackPanel>
+                        </Border>
+                    </WrapPanel>
+                </StackPanel>
+            </Border>
+
+            <!-- 输入面板 -->
+            <Border Grid.Row="1" Style="{StaticResource GroupBorder}" Padding="4">
+                <StackPanel>
+                    <TextBlock Text="输入端口(Input)" Style="{StaticResource SectionTitle}"/>
+                    <WrapPanel>
+                        <Border BorderBrush="#2196F3" BorderThickness="1" CornerRadius="4" Margin="4" Padding="4" Width="90">
+                            <StackPanel>
+                                <TextBlock Text="输入 1" HorizontalAlignment="Center" FontSize="11"/>
+                                <Ellipse Width="20" Height="20" Fill="{Binding Input1, Converter={x:Null}}" HorizontalAlignment="Center" Margin="0,2"/>
+                            </StackPanel>
+                        </Border>
+                        <Border BorderBrush="#2196F3" BorderThickness="1" CornerRadius="4" Margin="4" Padding="4" Width="90">
+                            <StackPanel>
+                                <TextBlock Text="输入 2" HorizontalAlignment="Center" FontSize="11"/>
+                                <Ellipse Width="20" Height="20" Fill="{Binding Input2, Converter={x:Null}}" HorizontalAlignment="Center" Margin="0,2"/>
+                            </StackPanel>
+                        </Border>
+                        <Border BorderBrush="#2196F3" BorderThickness="1" CornerRadius="4" Margin="4" Padding="4" Width="90">
+                            <StackPanel>
+                                <TextBlock Text="输入 3" HorizontalAlignment="Center" FontSize="11"/>
+                                <Ellipse Width="20" Height="20" Fill="{Binding Input3, Converter={x:Null}}" HorizontalAlignment="Center" Margin="0,2"/>
+                            </StackPanel>
+                        </Border>
+                        <Border BorderBrush="#2196F3" BorderThickness="1" CornerRadius="4" Margin="4" Padding="4" Width="90">
+                            <StackPanel>
+                                <TextBlock Text="输入 4" HorizontalAlignment="Center" FontSize="11"/>
+                                <Ellipse Width="20" Height="20" Fill="{Binding Input4, Converter={x:Null}}" HorizontalAlignment="Center" Margin="0,2"/>
+                            </StackPanel>
+                        </Border>
+                        <Border BorderBrush="#2196F3" BorderThickness="1" CornerRadius="4" Margin="4" Padding="4" Width="90">
+                            <StackPanel>
+                                <TextBlock Text="输入 5" HorizontalAlignment="Center" FontSize="11"/>
+                                <Ellipse Width="20" Height="20" Fill="{Binding Input5, Converter={x:Null}}" HorizontalAlignment="Center" Margin="0,2"/>
+                            </StackPanel>
+                        </Border>
+                        <Border BorderBrush="#2196F3" BorderThickness="1" CornerRadius="4" Margin="4" Padding="4" Width="90">
+                            <StackPanel>
+                                <TextBlock Text="输入 6" HorizontalAlignment="Center" FontSize="11"/>
+                                <Ellipse Width="20" Height="20" Fill="{Binding Input6, Converter={x:Null}}" HorizontalAlignment="Center" Margin="0,2"/>
+                            </StackPanel>
+                        </Border>
+                        <Border BorderBrush="#2196F3" BorderThickness="1" CornerRadius="4" Margin="4" Padding="4" Width="90">
+                            <StackPanel>
+                                <TextBlock Text="输入 7" HorizontalAlignment="Center" FontSize="11"/>
+                                <Ellipse Width="20" Height="20" Fill="{Binding Input7, Converter={x:Null}}" HorizontalAlignment="Center" Margin="0,2"/>
+                            </StackPanel>
+                        </Border>
+                        <Border BorderBrush="#2196F3" BorderThickness="1" CornerRadius="4" Margin="4" Padding="4" Width="90">
+                            <StackPanel>
+                                <TextBlock Text="输入 8" HorizontalAlignment="Center" FontSize="11"/>
+                                <Ellipse Width="20" Height="20" Fill="{Binding Input8, Converter={x:Null}}" HorizontalAlignment="Center" Margin="0,2"/>
+                            </StackPanel>
+                        </Border>
+                    </WrapPanel>
+                </StackPanel>
+            </Border>
+
+            <!-- 日志 -->
+            <Grid Grid.Row="2">
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+
+                <Border Grid.Row="0" Style="{StaticResource GroupBorder}" Background="#FFF5E6">
+                    <Grid>
+                        <TextBlock Text="日志" Style="{StaticResource SectionTitle}" VerticalAlignment="Center"/>
+                        <Button HorizontalAlignment="Right" Content="清空" Command="{Binding ClearLogCommand}"
+                                Style="{StaticResource ActionButton}" Margin="0,2,4,2"/>
+                    </Grid>
+                </Border>
+
+                <Border Grid.Row="1" Style="{StaticResource GroupBorder}">
+                    <RichTextBox x:Name="txtLog" IsReadOnly="True" VerticalScrollBarVisibility="Auto"
+                                 FontFamily="Consolas" FontSize="12" Background="#1E1E1E" Foreground="#D4D4D4"/>
+                </Border>
+            </Grid>
+        </Grid>
+    </Grid>
+</Window>
Added +55 -0
diff --git a/CrazyCoder/Views/IoControlWindow.xaml.cs b/CrazyCoder/Views/IoControlWindow.xaml.cs
new file mode 100644
index 0000000..eb09bea
--- /dev/null
+++ b/CrazyCoder/Views/IoControlWindow.xaml.cs
@@ -0,0 +1,55 @@
+using System.Windows;
+using System.Windows.Documents;
+using System.Windows.Media;
+using CrazyCoder.ViewModels;
+
+using static System.Windows.Media.Brushes;
+
+namespace CrazyCoder.Views;
+
+/// <summary>I/O 控制面板窗口</summary>
+public partial class IoControlWindow : Window
+{
+    /// <summary>ViewModel</summary>
+    public IoControlViewModel ViewModel { get; }
+
+    /// <summary>实例化 I/O 控制面板窗口</summary>
+    public IoControlWindow()
+    {
+        InitializeComponent();
+
+        ViewModel = new IoControlViewModel();
+        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 brush = new SolidColorBrush(Color.FromRgb(0xD4, 0xD4, 0xD4));
+        if (msg.Contains("失败") || msg.Contains("错误") || msg.Contains("异常"))
+            brush = Brushes.Red;
+
+        var run = new Run(msg) { Foreground = brush };
+        var paragraph = new Paragraph(run)
+        {
+            Margin = new Thickness(0),
+            Padding = new Thickness(0)
+        };
+
+        txtLog.Document.Blocks.Add(paragraph);
+        txtLog.ScrollToEnd();
+    }
+}
Added +196 -0
diff --git a/CrazyCoder/Views/ModbusRtuWindow.xaml b/CrazyCoder/Views/ModbusRtuWindow.xaml
new file mode 100644
index 0000000..6a16742
--- /dev/null
+++ b/CrazyCoder/Views/ModbusRtuWindow.xaml
@@ -0,0 +1,196 @@
+<Window x:Class="CrazyCoder.Views.ModbusRtuWindow"
+        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="Modbus RTU 工具" Height="700" Width="1000" WindowStartupLocation="CenterScreen">
+    <Window.Resources>
+        <Style x:Key="GroupBorder" TargetType="Border">
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="Margin" Value="0,0,0,6"/>
+        </Style>
+
+        <Style x:Key="SectionTitle" TargetType="TextBlock">
+            <Setter Property="FontWeight" Value="Bold"/>
+            <Setter Property="Margin" Value="4,3"/>
+            <Setter Property="FontSize" Value="13"/>
+        </Style>
+
+        <Style x:Key="ConfigLabel" TargetType="TextBlock">
+            <Setter Property="VerticalAlignment" Value="Center"/>
+            <Setter Property="Margin" Value="4,2"/>
+        </Style>
+
+        <Style x:Key="ConfigComboBox" TargetType="ComboBox">
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Height" Value="26"/>
+            <Setter Property="VerticalContentAlignment" Value="Center"/>
+        </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="BigButton" TargetType="Button">
+            <Setter Property="Height" Value="40"/>
+            <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"/>
+                                <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}"/>
+                            <ComboBox Grid.Row="0" Grid.Column="1"
+                                      ItemsSource="{Binding PortNames}"
+                                      SelectedItem="{Binding PortName, UpdateSourceTrigger=PropertyChanged}"
+                                      Style="{StaticResource ConfigComboBox}"/>
+                            <Button Grid.Row="0" Grid.Column="2" Content="刷新" Command="{Binding RefreshPortsCommand}"
+                                    Style="{StaticResource ActionButton}" Width="50"/>
+
+                            <TextBlock Grid.Row="1" Grid.Column="0" Text="波特率" Style="{StaticResource ConfigLabel}"/>
+                            <ComboBox Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2"
+                                      ItemsSource="{Binding BaudRates}"
+                                      SelectedItem="{Binding BaudRate, UpdateSourceTrigger=PropertyChanged}"
+                                      Style="{StaticResource ConfigComboBox}"/>
+
+                            <TextBlock Grid.Row="2" Grid.Column="0" Text="校验位" Style="{StaticResource ConfigLabel}"/>
+                            <ComboBox Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2"
+                                      ItemsSource="{Binding ParityOptions}"
+                                      SelectedIndex="{Binding ParityIndex, UpdateSourceTrigger=PropertyChanged}"
+                                      Style="{StaticResource ConfigComboBox}"/>
+
+                            <TextBlock Grid.Row="3" Grid.Column="0" Text="数据位" Style="{StaticResource ConfigLabel}"/>
+                            <ComboBox Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2"
+                                      ItemsSource="{Binding DataBitsOptions}"
+                                      SelectedItem="{Binding DataBits, UpdateSourceTrigger=PropertyChanged}"
+                                      Style="{StaticResource ConfigComboBox}"/>
+
+                            <TextBlock Grid.Row="4" Grid.Column="0" Text="停止位" Style="{StaticResource ConfigLabel}"/>
+                            <ComboBox Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="2"
+                                      ItemsSource="{Binding StopBitsOptions}"
+                                      SelectedIndex="{Binding StopBitsIndex, UpdateSourceTrigger=PropertyChanged}"
+                                      Style="{StaticResource ConfigComboBox}"/>
+
+                            <Button Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="3"
+                                    Content="{Binding ConnectButtonText}"
+                                    Command="{Binding ToggleConnectCommand}"
+                                    Style="{StaticResource BigButton}" Foreground="White"/>
+                        </Grid>
+                    </StackPanel>
+                </Border>
+
+                <!-- Modbus 操作 -->
+                <Border Style="{StaticResource GroupBorder}">
+                    <StackPanel>
+                        <TextBlock Text="Modbus 操作" 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="*"/>
+                            </Grid.ColumnDefinitions>
+
+                            <TextBlock Grid.Row="0" Grid.Column="0" Text="站号" Style="{StaticResource ConfigLabel}"/>
+                            <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding SlaveAddress, UpdateSourceTrigger=PropertyChanged}"
+                                     Style="{StaticResource ConfigTextBox}"/>
+
+                            <TextBlock Grid.Row="1" Grid.Column="0" Text="功能" Style="{StaticResource ConfigLabel}"/>
+                            <ComboBox Grid.Row="1" Grid.Column="1"
+                                      ItemsSource="{Binding FuncCodeOptions}"
+                                      SelectedIndex="{Binding FunctionCodeIndex, UpdateSourceTrigger=PropertyChanged}"
+                                      Style="{StaticResource ConfigComboBox}"/>
+
+                            <TextBlock Grid.Row="2" Grid.Column="0" Text="地址" Style="{StaticResource ConfigLabel}"/>
+                            <TextBox Grid.Row="2" Grid.Column="1" Text="{Binding StartAddress, 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 ReadCount, UpdateSourceTrigger=PropertyChanged}"
+                                     Style="{StaticResource ConfigTextBox}"/>
+
+                            <TextBlock Grid.Row="4" Grid.Column="0" Text="值" Style="{StaticResource ConfigLabel}"/>
+                            <TextBox Grid.Row="4" Grid.Column="1" Text="{Binding WriteValue, UpdateSourceTrigger=PropertyChanged}"
+                                     Style="{StaticResource ConfigTextBox}"/>
+                        </Grid>
+
+                        <Button Content="执行" Command="{Binding ExecuteCommand}"
+                                Style="{StaticResource BigButton}" Foreground="White" Background="#218868"/>
+
+                        <TextBox Text="{Binding ReadResult, Mode=OneWay}" IsReadOnly="True"
+                                 Style="{StaticResource ConfigTextBox}" Height="60"
+                                 AcceptsReturn="True" TextWrapping="Wrap" Margin="2,4,2,2"/>
+                    </StackPanel>
+                </Border>
+            </StackPanel>
+        </ScrollViewer>
+
+        <!-- ============ 右侧:日志显示 ============ -->
+        <Grid Grid.Column="1">
+            <Grid.RowDefinitions>
+                <RowDefinition Height="Auto"/>
+                <RowDefinition Height="*"/>
+            </Grid.RowDefinitions>
+
+            <Border Grid.Row="0" Style="{StaticResource GroupBorder}" Background="#FFF5E6">
+                <Grid>
+                    <TextBlock Text="日志" Style="{StaticResource SectionTitle}" VerticalAlignment="Center"/>
+                    <Button HorizontalAlignment="Right" Content="清空" Command="{Binding ClearLogCommand}"
+                            Style="{StaticResource ActionButton}" Margin="0,2,4,2"/>
+                </Grid>
+            </Border>
+
+            <Border Grid.Row="1" Style="{StaticResource GroupBorder}">
+                <RichTextBox x:Name="txtLog" IsReadOnly="True" VerticalScrollBarVisibility="Auto"
+                             FontFamily="Consolas" FontSize="12" Background="#1E1E1E" Foreground="#D4D4D4"/>
+            </Border>
+        </Grid>
+    </Grid>
+</Window>
Added +55 -0
diff --git a/CrazyCoder/Views/ModbusRtuWindow.xaml.cs b/CrazyCoder/Views/ModbusRtuWindow.xaml.cs
new file mode 100644
index 0000000..e2acb2c
--- /dev/null
+++ b/CrazyCoder/Views/ModbusRtuWindow.xaml.cs
@@ -0,0 +1,55 @@
+using System.Windows;
+using System.Windows.Documents;
+using System.Windows.Media;
+using CrazyCoder.ViewModels;
+
+using static System.Windows.Media.Brushes;
+
+namespace CrazyCoder.Views;
+
+/// <summary>Modbus RTU 工具窗口</summary>
+public partial class ModbusRtuWindow : Window
+{
+    /// <summary>ViewModel</summary>
+    public ModbusRtuViewModel ViewModel { get; }
+
+    /// <summary>实例化 Modbus RTU 工具窗口</summary>
+    public ModbusRtuWindow()
+    {
+        InitializeComponent();
+
+        ViewModel = new ModbusRtuViewModel();
+        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 brush = new SolidColorBrush(Color.FromRgb(0xD4, 0xD4, 0xD4));
+        if (msg.Contains("失败") || msg.Contains("错误") || msg.Contains("异常"))
+            brush = Brushes.Red;
+
+        var run = new Run(msg) { Foreground = brush };
+        var paragraph = new Paragraph(run)
+        {
+            Margin = new Thickness(0),
+            Padding = new Thickness(0)
+        };
+
+        txtLog.Document.Blocks.Add(paragraph);
+        txtLog.ScrollToEnd();
+    }
+}
Added +247 -0
diff --git a/CrazyCoder/Views/ModbusTcpWindow.xaml b/CrazyCoder/Views/ModbusTcpWindow.xaml
new file mode 100644
index 0000000..c614ca8
--- /dev/null
+++ b/CrazyCoder/Views/ModbusTcpWindow.xaml
@@ -0,0 +1,247 @@
+<Window x:Class="CrazyCoder.Views.ModbusTcpWindow"
+        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="Modbus TCP 工具" 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="ConfigComboBox" TargetType="ComboBox">
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Height" Value="26"/>
+            <Setter Property="VerticalContentAlignment" Value="Center"/>
+        </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="BigButton" TargetType="Button">
+            <Setter Property="Height" Value="40"/>
+            <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="320"/>
+            <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"/>
+                            </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 ModeOptions}"
+                                      SelectedIndex="{Binding SelectedMode, UpdateSourceTrigger=PropertyChanged}"
+                                      Style="{StaticResource ConfigComboBox}"/>
+
+                            <TextBlock Grid.Row="1" Grid.Column="0" Text="地址" Style="{StaticResource ConfigLabel}"/>
+                            <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding ServerAddress, 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 Port, UpdateSourceTrigger=PropertyChanged}"
+                                     Style="{StaticResource ConfigTextBox}"/>
+
+                            <Button Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2"
+                                    Content="{Binding ConnectButtonText}"
+                                    Command="{Binding ToggleConnectCommand}"
+                                    Style="{StaticResource BigButton}" Foreground="White"/>
+                        </Grid>
+                    </StackPanel>
+                </Border>
+
+                <!-- Modbus 操作(主站模式) -->
+                <Border Style="{StaticResource GroupBorder}">
+                    <StackPanel>
+                        <TextBlock Text="Modbus 操作" 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="*"/>
+                            </Grid.ColumnDefinitions>
+
+                            <TextBlock Grid.Row="0" Grid.Column="0" Text="站号" Style="{StaticResource ConfigLabel}"/>
+                            <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding SlaveAddress, UpdateSourceTrigger=PropertyChanged}"
+                                     Style="{StaticResource ConfigTextBox}"/>
+
+                            <TextBlock Grid.Row="1" Grid.Column="0" Text="功能" Style="{StaticResource ConfigLabel}"/>
+                            <ComboBox Grid.Row="1" Grid.Column="1"
+                                      ItemsSource="{Binding FuncCodeOptions}"
+                                      SelectedIndex="{Binding FunctionCodeIndex, UpdateSourceTrigger=PropertyChanged}"
+                                      Style="{StaticResource ConfigComboBox}"/>
+
+                            <TextBlock Grid.Row="2" Grid.Column="0" Text="地址" Style="{StaticResource ConfigLabel}"/>
+                            <TextBox Grid.Row="2" Grid.Column="1" Text="{Binding StartAddress, 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 ReadCount, UpdateSourceTrigger=PropertyChanged}"
+                                     Style="{StaticResource ConfigTextBox}"/>
+
+                            <TextBlock Grid.Row="4" Grid.Column="0" Text="值" Style="{StaticResource ConfigLabel}"/>
+                            <TextBox Grid.Row="4" Grid.Column="1" Text="{Binding WriteValue, UpdateSourceTrigger=PropertyChanged}"
+                                     Style="{StaticResource ConfigTextBox}"/>
+                        </Grid>
+
+                        <Button Content="执行" Command="{Binding ExecuteCommand}"
+                                Style="{StaticResource BigButton}" Foreground="White" Background="#218868"/>
+
+                        <TextBox Text="{Binding ReadResult, Mode=OneWay}" IsReadOnly="True"
+                                 Style="{StaticResource ConfigTextBox}" Height="60"
+                                 AcceptsReturn="True" TextWrapping="Wrap" Margin="2,4,2,2"/>
+                    </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="*"/>
+                                <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 SlaveDataAddress, 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 SlaveDataCount, UpdateSourceTrigger=PropertyChanged}"
+                                     Style="{StaticResource ConfigTextBox}"/>
+
+                            <TextBlock Grid.Row="2" Grid.Column="0" Text="模式" Style="{StaticResource ConfigLabel}"/>
+                            <ComboBox Grid.Row="2" Grid.Column="1"
+                                      ItemsSource="{Binding DataModeOptions}"
+                                      SelectedIndex="{Binding DataModeIndex, UpdateSourceTrigger=PropertyChanged}"
+                                      Style="{StaticResource ConfigComboBox}"/>
+                            <Button Grid.Row="2" Grid.Column="2" Content="刷新" Command="{Binding RefreshDataCommand}"
+                                    Style="{StaticResource ActionButton}" Width="50"/>
+                        </Grid>
+                    </StackPanel>
+                </Border>
+            </StackPanel>
+        </ScrollViewer>
+
+        <!-- ============ 右侧:日志 + 从站数据 ============ -->
+        <Grid Grid.Column="1">
+            <Grid.RowDefinitions>
+                <RowDefinition Height="*"/>
+                <RowDefinition Height="Auto"/>
+                <RowDefinition Height="2*"/>
+            </Grid.RowDefinitions>
+
+            <!-- 日志 -->
+            <Grid Grid.Row="0">
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+
+                <Border Grid.Row="0" Style="{StaticResource GroupBorder}" Background="#FFF5E6">
+                    <Grid>
+                        <TextBlock Text="日志" Style="{StaticResource SectionTitle}" VerticalAlignment="Center"/>
+                        <Button HorizontalAlignment="Right" Content="清空" Command="{Binding ClearLogCommand}"
+                                Style="{StaticResource ActionButton}" Margin="0,2,4,2"/>
+                    </Grid>
+                </Border>
+
+                <Border Grid.Row="1" Style="{StaticResource GroupBorder}">
+                    <RichTextBox x:Name="txtLog" IsReadOnly="True" VerticalScrollBarVisibility="Auto"
+                                 FontFamily="Consolas" FontSize="12" Background="#1E1E1E" Foreground="#D4D4D4"/>
+                </Border>
+            </Grid>
+
+            <!-- 分隔 -->
+            <GridSplitter Grid.Row="1" Height="4" HorizontalAlignment="Stretch" Background="#D0D0D0"/>
+
+            <!-- 从站数据表格 -->
+            <Grid Grid.Row="2">
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+
+                <Border Grid.Row="0" Style="{StaticResource GroupBorder}" Background="#E8F5E9">
+                    <TextBlock Text="从站数据(寄存器列表)" Style="{StaticResource SectionTitle}"/>
+                </Border>
+
+                <Border Grid.Row="1" Style="{StaticResource GroupBorder}">
+                    <ListView x:Name="lvRegisters" ItemsSource="{Binding Registers}" HorizontalContentAlignment="Stretch">
+                        <ListView.View>
+                            <GridView>
+                                <GridViewColumn Header="地址" Width="100" DisplayMemberBinding="{Binding Address}"/>
+                                <GridViewColumn Header="数值(十进制)" Width="150" DisplayMemberBinding="{Binding Value}"/>
+                                <GridViewColumn Header="数值(十六进制)" Width="150" DisplayMemberBinding="{Binding Hex}"/>
+                            </GridView>
+                        </ListView.View>
+                    </ListView>
+                </Border>
+            </Grid>
+        </Grid>
+    </Grid>
+</Window>
Added +55 -0
diff --git a/CrazyCoder/Views/ModbusTcpWindow.xaml.cs b/CrazyCoder/Views/ModbusTcpWindow.xaml.cs
new file mode 100644
index 0000000..7cc3518
--- /dev/null
+++ b/CrazyCoder/Views/ModbusTcpWindow.xaml.cs
@@ -0,0 +1,55 @@
+using System.Windows;
+using System.Windows.Documents;
+using System.Windows.Media;
+using CrazyCoder.ViewModels;
+
+using static System.Windows.Media.Brushes;
+
+namespace CrazyCoder.Views;
+
+/// <summary>Modbus TCP 工具窗口</summary>
+public partial class ModbusTcpWindow : Window
+{
+    /// <summary>ViewModel</summary>
+    public ModbusTcpViewModel ViewModel { get; }
+
+    /// <summary>实例化 Modbus TCP 工具窗口</summary>
+    public ModbusTcpWindow()
+    {
+        InitializeComponent();
+
+        ViewModel = new ModbusTcpViewModel();
+        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 brush = new SolidColorBrush(Color.FromRgb(0xD4, 0xD4, 0xD4));
+        if (msg.Contains("失败") || msg.Contains("错误") || msg.Contains("异常"))
+            brush = Brushes.Red;
+
+        var run = new Run(msg) { Foreground = brush };
+        var paragraph = new Paragraph(run)
+        {
+            Margin = new Thickness(0),
+            Padding = new Thickness(0)
+        };
+
+        txtLog.Document.Blocks.Add(paragraph);
+        txtLog.ScrollToEnd();
+    }
+}
Added +106 -0
diff --git a/CrazyCoder/Views/RedisManagerWindow.xaml b/CrazyCoder/Views/RedisManagerWindow.xaml
new file mode 100644
index 0000000..4ccd3c0
--- /dev/null
+++ b/CrazyCoder/Views/RedisManagerWindow.xaml
@@ -0,0 +1,106 @@
+<Window x:Class="CrazyCoder.Views.RedisManagerWindow"
+        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"
+        xmlns:vm="clr-namespace:CrazyCoder.ViewModels"
+        mc:Ignorable="d"
+        Title="Redis 管理器" Height="700" 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,4,0"/>
+        </Style>
+
+        <HierarchicalDataTemplate DataType="{x:Type vm:RedisTreeNode}" ItemsSource="{Binding Children}">
+            <StackPanel Orientation="Horizontal" Margin="{Binding Indent}">
+                <TextBlock Text="{Binding Title}" Margin="4,2"/>
+            </StackPanel>
+        </HierarchicalDataTemplate>
+    </Window.Resources>
+
+    <Grid Margin="8">
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="280"/>
+            <ColumnDefinition Width="*"/>
+        </Grid.ColumnDefinitions>
+
+        <!-- 左侧:树形节点 -->
+        <Border Grid.Column="0" Style="{StaticResource GroupBorder}">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+
+                <TextBlock Text="Redis 服务器" FontWeight="Bold" Background="#F0F0F0" Padding="6,3"/>
+
+                <!-- 搜索框 -->
+                <Grid Grid.Row="1" Margin="4">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="*"/>
+                        <ColumnDefinition Width="Auto"/>
+                    </Grid.ColumnDefinitions>
+                    <TextBox Text="{Binding SearchPattern, UpdateSourceTrigger=PropertyChanged}" Height="26"/>
+                    <Button Grid.Column="1" Content="搜索" Command="{Binding SearchKeysCommand}" Height="26" Width="50" Margin="4,0,0,0"/>
+                </Grid>
+
+                <!-- 树 -->
+                <TreeView x:Name="treeView" Grid.Row="2" ItemsSource="{Binding TreeNodes}" BorderThickness="0"
+                          SelectedItemChanged="OnTreeSelectedItemChanged"
+                          MouseDoubleClick="OnTreeMouseDoubleClick">
+                    <TreeView.ItemTemplate>
+                        <HierarchicalDataTemplate ItemsSource="{Binding Children}">
+                            <TextBlock Text="{Binding Title}" Margin="4,2"/>
+                        </HierarchicalDataTemplate>
+                    </TreeView.ItemTemplate>
+                </TreeView>
+
+                <!-- 底部操作按钮 -->
+                <StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,4">
+                    <Button Content="添加节点" Command="{Binding AddNodeCommand}" Width="80" Height="28" Margin="2"/>
+                    <Button Content="编辑节点" Command="{Binding EditNodeCommand}" Width="80" Height="28" Margin="2"/>
+                    <Button Content="删除节点" Command="{Binding DeleteNodeCommand}" Width="80" Height="28" Margin="2"/>
+                </StackPanel>
+            </Grid>
+        </Border>
+
+        <!-- 右侧:Key 值显示 -->
+        <Border Grid.Column="1" BorderBrush="#D0D0D0" BorderThickness="1" Margin="4,0,0,0">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+
+                <TextBlock Text="Key 值" FontWeight="Bold" Background="#F0F0F0" Padding="6,3"/>
+
+                <TextBox Grid.Row="1" Text="{Binding KeyValue, Mode=OneWay}"
+                         FontFamily="Consolas" FontSize="13"
+                         IsReadOnly="True"
+                         VerticalScrollBarVisibility="Auto"
+                         HorizontalScrollBarVisibility="Auto"
+                         BorderThickness="0"
+                         TextWrapping="Wrap"/>
+            </Grid>
+        </Border>
+
+        <!-- 状态栏 -->
+        <StatusBar Grid.ColumnSpan="2" VerticalAlignment="Bottom">
+            <StatusBar.ItemsPanel>
+                <ItemsPanelTemplate>
+                    <Grid>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="*"/>
+                        </Grid.ColumnDefinitions>
+                    </Grid>
+                </ItemsPanelTemplate>
+            </StatusBar.ItemsPanel>
+            <StatusBarItem>
+                <TextBlock Text="{Binding Status}"/>
+            </StatusBarItem>
+        </StatusBar>
+    </Grid>
+</Window>
Added +41 -0
diff --git a/CrazyCoder/Views/RedisManagerWindow.xaml.cs b/CrazyCoder/Views/RedisManagerWindow.xaml.cs
new file mode 100644
index 0000000..06c7d95
--- /dev/null
+++ b/CrazyCoder/Views/RedisManagerWindow.xaml.cs
@@ -0,0 +1,41 @@
+using System.Windows;
+using System.Windows.Controls;
+using CrazyCoder.ViewModels;
+
+namespace CrazyCoder.Views;
+
+/// <summary>Redis 管理器窗口</summary>
+public partial class RedisManagerWindow : Window
+{
+    /// <summary>ViewModel</summary>
+    public RedisManagerViewModel ViewModel { get; }
+
+    /// <summary>实例化 Redis 管理器窗口</summary>
+    public RedisManagerWindow()
+    {
+        InitializeComponent();
+
+        ViewModel = new RedisManagerViewModel();
+        DataContext = ViewModel;
+    }
+
+    private void OnTreeSelectedItemChanged(Object sender, RoutedPropertyChangedEventArgs<Object> e)
+    {
+        if (e.NewValue is RedisTreeNode node)
+        {
+            node.IsSelected = true;
+        }
+        if (e.OldValue is RedisTreeNode oldNode)
+        {
+            oldNode.IsSelected = false;
+        }
+    }
+
+    private void OnTreeMouseDoubleClick(Object sender, System.Windows.Input.MouseButtonEventArgs e)
+    {
+        if (treeView.SelectedItem is RedisTreeNode node)
+        {
+            ViewModel.HandleNodeDoubleClick(node);
+        }
+    }
+}
Added +179 -0
diff --git a/CrazyCoder/Views/SerialPortWindow.xaml b/CrazyCoder/Views/SerialPortWindow.xaml
new file mode 100644
index 0000000..0601203
--- /dev/null
+++ b/CrazyCoder/Views/SerialPortWindow.xaml
@@ -0,0 +1,179 @@
+<Window x:Class="CrazyCoder.Views.SerialPortWindow"
+        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>
+        <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="ConfigComboBox" TargetType="ComboBox">
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Height" Value="26"/>
+            <Setter Property="VerticalContentAlignment" Value="Center"/>
+        </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="280"/>
+            <ColumnDefinition Width="*"/>
+        </Grid.ColumnDefinitions>
+
+        <!-- ============ 左侧:配置面板 ============ -->
+        <ScrollViewer Grid.Column="0" VerticalScrollBarVisibility="Auto" Margin="0,0,4,0">
+            <StackPanel>
+                <!-- 串口配置 -->
+                <Border Style="{StaticResource GroupBorder}">
+                    <StackPanel>
+                        <TextBlock Text="串口配置" Style="{StaticResource SectionTitle}"/>
+
+                        <Grid>
+                            <Grid.RowDefinitions>
+                                <RowDefinition Height="Auto"/>
+                                <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}"/>
+                            <ComboBox Grid.Row="0" Grid.Column="1"
+                                      ItemsSource="{Binding PortNames}"
+                                      SelectedItem="{Binding PortName, UpdateSourceTrigger=PropertyChanged}"
+                                      Style="{StaticResource ConfigComboBox}"/>
+                            <Button Grid.Row="0" Grid.Column="2" Content="刷新" Command="{Binding RefreshPortsCommand}"
+                                    Style="{StaticResource ActionButton}" Width="50"/>
+
+                            <TextBlock Grid.Row="1" Grid.Column="0" Text="波特率" Style="{StaticResource ConfigLabel}"/>
+                            <ComboBox Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2"
+                                      ItemsSource="{Binding BaudRates}"
+                                      SelectedItem="{Binding BaudRate, UpdateSourceTrigger=PropertyChanged}"
+                                      Style="{StaticResource ConfigComboBox}"/>
+
+                            <TextBlock Grid.Row="2" Grid.Column="0" Text="校验位" Style="{StaticResource ConfigLabel}"/>
+                            <ComboBox Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2"
+                                      ItemsSource="{Binding ParityOptions}"
+                                      SelectedIndex="{Binding ParityIndex, UpdateSourceTrigger=PropertyChanged}"
+                                      Style="{StaticResource ConfigComboBox}"/>
+
+                            <TextBlock Grid.Row="3" Grid.Column="0" Text="数据位" Style="{StaticResource ConfigLabel}"/>
+                            <ComboBox Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2"
+                                      ItemsSource="{Binding DataBitsOptions}"
+                                      SelectedItem="{Binding DataBits, UpdateSourceTrigger=PropertyChanged}"
+                                      Style="{StaticResource ConfigComboBox}"/>
+
+                            <TextBlock Grid.Row="4" Grid.Column="0" Text="停止位" Style="{StaticResource ConfigLabel}"/>
+                            <ComboBox Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="2"
+                                      ItemsSource="{Binding StopBitsOptions}"
+                                      SelectedIndex="{Binding StopBitsIndex, UpdateSourceTrigger=PropertyChanged}"
+                                      Style="{StaticResource ConfigComboBox}"/>
+
+                            <Button Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="3"
+                                    Content="{Binding ConnectButtonText}"
+                                    Command="{Binding ToggleConnectCommand}"
+                                    Style="{StaticResource SendButton}" Foreground="White"
+                                    Background="{Binding IsConnected, Converter={x:Null}}"/>
+                        </Grid>
+                    </StackPanel>
+                </Border>
+
+                <!-- 发送配置 -->
+                <Border Style="{StaticResource GroupBorder}">
+                    <StackPanel>
+                        <TextBlock Text="发送配置" Style="{StaticResource SectionTitle}"/>
+
+                        <CheckBox Content="十六进制发送" IsChecked="{Binding HexSend}" Margin="4,2"/>
+
+                        <TextBox Text="{Binding SendText, UpdateSourceTrigger=PropertyChanged}"
+                                 Style="{StaticResource ConfigTextBox}" Height="80"
+                                 AcceptsReturn="True" TextWrapping="Wrap"/>
+
+                        <Button Content="发送" Command="{Binding SendCommand}"
+                                Style="{StaticResource SendButton}" Foreground="White"
+                                Background="#218868"/>
+                    </StackPanel>
+                </Border>
+
+                <!-- 统计信息 -->
+                <Border Style="{StaticResource GroupBorder}">
+                    <StackPanel>
+                        <TextBlock Text="统计" Style="{StaticResource SectionTitle}"/>
+                        <TextBlock Margin="4,2">
+                            <Run FontWeight="Bold">已发送:</Run>
+                            <Run Text="{Binding BytesSent}"/> 字节
+                        </TextBlock>
+                        <TextBlock Margin="4,2">
+                            <Run FontWeight="Bold">已接收:</Run>
+                            <Run Text="{Binding BytesReceived}"/> 字节
+                        </TextBlock>
+                    </StackPanel>
+                </Border>
+            </StackPanel>
+        </ScrollViewer>
+
+        <!-- ============ 右侧:日志显示 ============ -->
+        <Grid Grid.Column="1">
+            <Grid.RowDefinitions>
+                <RowDefinition Height="Auto"/>
+                <RowDefinition Height="*"/>
+            </Grid.RowDefinitions>
+
+            <Border Grid.Row="0" Style="{StaticResource GroupBorder}" Background="#FFF5E6">
+                <Grid>
+                    <TextBlock Text="日志" Style="{StaticResource SectionTitle}" VerticalAlignment="Center"/>
+                    <Button HorizontalAlignment="Right" Content="清空" Command="{Binding ClearLogCommand}"
+                            Style="{StaticResource ActionButton}" Margin="0,2,4,2"/>
+                </Grid>
+            </Border>
+
+            <Border Grid.Row="1" Style="{StaticResource GroupBorder}">
+                <RichTextBox x:Name="txtLog" IsReadOnly="True" VerticalScrollBarVisibility="Auto"
+                             FontFamily="Consolas" FontSize="12" Background="#1E1E1E" Foreground="#D4D4D4"/>
+            </Border>
+        </Grid>
+    </Grid>
+</Window>
Added +59 -0
diff --git a/CrazyCoder/Views/SerialPortWindow.xaml.cs b/CrazyCoder/Views/SerialPortWindow.xaml.cs
new file mode 100644
index 0000000..277d852
--- /dev/null
+++ b/CrazyCoder/Views/SerialPortWindow.xaml.cs
@@ -0,0 +1,59 @@
+using System.Windows;
+using System.Windows.Documents;
+using System.Windows.Media;
+using CrazyCoder.ViewModels;
+
+using static System.Windows.Media.Brushes;
+
+namespace CrazyCoder.Views;
+
+/// <summary>串口调试工具窗口</summary>
+public partial class SerialPortWindow : Window
+{
+    /// <summary>ViewModel</summary>
+    public SerialPortViewModel ViewModel { get; }
+
+    /// <summary>实例化串口调试工具窗口</summary>
+    public SerialPortWindow()
+    {
+        InitializeComponent();
+
+        ViewModel = new SerialPortViewModel();
+        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 brush = new SolidColorBrush(Color.FromRgb(0xD4, 0xD4, 0xD4));
+        if (msg.StartsWith("[发送]"))
+            brush = Brushes.LimeGreen;
+        else if (msg.StartsWith("[接收]"))
+            brush = Brushes.Orange;
+        else if (msg.Contains("错误") || msg.Contains("失败") || msg.Contains("异常"))
+            brush = Brushes.Red;
+
+        var run = new Run(msg) { Foreground = brush };
+        var paragraph = new Paragraph(run)
+        {
+            Margin = new Thickness(0),
+            Padding = new Thickness(0)
+        };
+
+        txtLog.Document.Blocks.Add(paragraph);
+        txtLog.ScrollToEnd();
+    }
+}
Renamed +1 -2
XCoderAv/App.xaml → XCoderAv/App.axaml
diff --git a/XCoderAv/App.xaml b/XCoderAv/App.axaml
similarity index 66%
rename from XCoderAv/App.xaml
rename to XCoderAv/App.axaml
index bacd61f..72abfab 100644
--- a/XCoderAv/App.xaml
+++ b/XCoderAv/App.axaml
@@ -2,7 +2,6 @@
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              x:Class="XCoderAv.App">
     <Application.Styles>
-        <StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>
-        <StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>
+        <FluentTheme />
     </Application.Styles>
 </Application>
Added +7 -0
diff --git a/XCoderAv/App.axaml.cs b/XCoderAv/App.axaml.cs
new file mode 100644
index 0000000..3ae29da
--- /dev/null
+++ b/XCoderAv/App.axaml.cs
@@ -0,0 +1,7 @@
+using Avalonia;
+
+namespace XCoderAv;
+
+public partial class App : Application
+{
+}
Deleted +0 -13
XCoderAv/App.xaml.cs
Added +80 -0
diff --git a/XCoderAv/Common/XConfig.cs b/XCoderAv/Common/XConfig.cs
new file mode 100644
index 0000000..8d8b04d
--- /dev/null
+++ b/XCoderAv/Common/XConfig.cs
@@ -0,0 +1,80 @@
+using System.ComponentModel;
+using NewLife;
+using NewLife.Configuration;
+
+namespace XCoderAv;
+
+/// <summary>应用配置</summary>
+[Config("XCoderAv")]
+public class XConfig : Config<XConfig>
+{
+    #region 属性
+    /// <summary>标题</summary>
+    [Description("标题")]
+    public String Title { get; set; } = "";
+
+    /// <summary>宽度</summary>
+    [Description("宽度")]
+    public Int32 Width { get; set; }
+
+    /// <summary>高度</summary>
+    [Description("高度")]
+    public Int32 Height { get; set; }
+
+    /// <summary>顶部</summary>
+    [Description("顶部")]
+    public Int32 Top { get; set; }
+
+    /// <summary>左边</summary>
+    [Description("左边")]
+    public Int32 Left { get; set; }
+
+    /// <summary>日志着色</summary>
+    [Description("日志着色")]
+    public Boolean ColorLog { get; set; } = true;
+
+    /// <summary>语音提示。默认true</summary>
+    [Description("语音提示。默认true")]
+    public Boolean SpeechTip { get; set; } = true;
+
+    /// <summary>证书</summary>
+    [Description("证书")]
+    public String? Code { get; set; }
+
+    /// <summary>密钥</summary>
+    [Description("密钥")]
+    public String? Secret { get; set; }
+
+    /// <summary>服务地址端口。默认为空,子网内自动发现</summary>
+    [Description("服务地址端口。默认为空,子网内自动发现")]
+    public String Server { get; set; } = "";
+
+    /// <summary>更新通道。默认Release</summary>
+    [Description("更新通道。默认Release")]
+    public String Channel { get; set; } = "Release";
+
+    /// <summary>更新服务器</summary>
+    [Description("更新服务器")]
+    public String UpdateServer { get; set; } = "";
+
+    /// <summary>最后更新时间</summary>
+    [DisplayName("最后更新时间")]
+    public DateTime LastUpdate { get; set; }
+
+    /// <summary>最后一个使用的工具</summary>
+    [DisplayName("最后一个使用的工具")]
+    public String LastTool { get; set; } = "";
+    #endregion
+
+    #region 加载/保存
+    protected override void OnLoaded()
+    {
+        if (UpdateServer.IsNullOrEmpty() || UpdateServer.EqualIgnoreCase("http://x.newlifex.com/"))
+            UpdateServer = NewLife.Setting.Current.PluginServer;
+
+        if (Server.IsNullOrEmpty()) Server = "http://s.newlifex.com:6600";
+
+        base.OnLoaded();
+    }
+    #endregion
+}
\ No newline at end of file
Added +60 -0
diff --git a/XCoderAv/MainWindow.axaml b/XCoderAv/MainWindow.axaml
new file mode 100644
index 0000000..07f94c3
--- /dev/null
+++ b/XCoderAv/MainWindow.axaml
@@ -0,0 +1,60 @@
+<Window xmlns="https://github.com/avaloniaui"
+        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:vm="clr-namespace:XCoderAv.ViewModels"
+        xmlns:views="clr-namespace:XCoderAv.Views"
+        mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="600"
+        x:Class="XCoderAv.MainWindow"
+        Title="新生命码神工具(跨平台)" Width="1000" Height="600"
+        WindowStartupLocation="CenterScreen">
+    <Grid>
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="250"/>
+            <ColumnDefinition Width="4"/>
+            <ColumnDefinition Width="*"/>
+        </Grid.ColumnDefinitions>
+
+        <!-- 左侧导航 -->
+        <Border Grid.Column="0" Background="#F5F5F5">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="80"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+
+                <!-- 标题 -->
+                <Border Grid.Row="0" Margin="20 0" BorderBrush="#E0E0E0" BorderThickness="0 0 0 1">
+                    <StackPanel VerticalAlignment="Center">
+                        <TextBlock Text="新生命码神工具" FontSize="18" FontWeight="Light" HorizontalAlignment="Center"/>
+                        <TextBlock Text="跨平台版" FontSize="11" Foreground="#888" HorizontalAlignment="Center"/>
+                    </StackPanel>
+                </Border>
+
+                <!-- 菜单列表 -->
+                <ListBox Grid.Row="1" ItemsSource="{Binding Menus}" SelectedItem="{Binding SelectedMenu}"
+                         BorderThickness="0" Background="Transparent"
+                         Margin="0 10 0 0">
+                    <ListBox.ItemTemplate>
+                        <DataTemplate>
+                            <Border Padding="15 10" Margin="5 2" CornerRadius="4">
+                                <StackPanel Orientation="Horizontal" Spacing="10">
+                                    <TextBlock Text="{Binding IconFont}" FontSize="18" Foreground="{Binding BackColor}"/>
+                                    <TextBlock Text="{Binding Title}" FontSize="14" VerticalAlignment="Center"/>
+                                </StackPanel>
+                            </Border>
+                        </DataTemplate>
+                    </ListBox.ItemTemplate>
+                </ListBox>
+            </Grid>
+        </Border>
+
+        <!-- 分隔线 -->
+        <GridSplitter Grid.Column="1" Width="4" Background="#E0E0E0" HorizontalAlignment="Stretch"/>
+
+        <!-- 右侧内容区 -->
+        <Border Grid.Column="2" Padding="20">
+            <ContentControl Content="{Binding CurrentView}" />
+        </Border>
+    </Grid>
+</Window>
Added +14 -0
diff --git a/XCoderAv/MainWindow.axaml.cs b/XCoderAv/MainWindow.axaml.cs
new file mode 100644
index 0000000..c717806
--- /dev/null
+++ b/XCoderAv/MainWindow.axaml.cs
@@ -0,0 +1,14 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using XCoderAv.ViewModels;
+
+namespace XCoderAv;
+
+public partial class MainWindow : Window
+{
+    public MainWindow()
+    {
+        InitializeComponent();
+        DataContext = new MainViewModel();
+    }
+}
Deleted +0 -9
XCoderAv/MainWindow.xaml
Deleted +0 -22
XCoderAv/MainWindow.xaml.cs
Added +144 -0
diff --git a/XCoderAv/Models/IconHelper.cs b/XCoderAv/Models/IconHelper.cs
new file mode 100644
index 0000000..9f8a65b
--- /dev/null
+++ b/XCoderAv/Models/IconHelper.cs
@@ -0,0 +1,144 @@
+using Avalonia;
+using Avalonia.Media.Imaging;
+using Avalonia.Platform;
+using NewLife;
+
+namespace XCoderAv.Models;
+
+/// <summary>ICO 图标文件辅助类(跨平台版,基于 Avalonia)</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(Bitmap bmp, Stream des, Int32[] sizes, Int32[] bits)
+    {
+        var ico = new IconHelper();
+        foreach (var bit in bits)
+        {
+            foreach (var item in sizes)
+            {
+                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(Bitmap bmp, Int32 size, Int32 bit)
+    {
+        // 缩放图片到目标尺寸
+        var resized = ResizeBitmap(bmp, size, size);
+
+        var ms = new MemoryStream();
+        resized.Save(ms);
+        resized.Dispose();
+
+        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();
+    }
+
+    /// <summary>缩放 Bitmap</summary>
+    private static Bitmap ResizeBitmap(Bitmap source, Int32 width, Int32 height)
+    {
+        // 使用 Avalonia 的 CreateScaledBitmap
+        return source.CreateScaledBitmap(new PixelSize(width, height), BitmapInterpolationMode.HighQuality);
+    }
+
+    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 +19 -0
diff --git a/XCoderAv/Models/MenuModel.cs b/XCoderAv/Models/MenuModel.cs
new file mode 100644
index 0000000..44f44e3
--- /dev/null
+++ b/XCoderAv/Models/MenuModel.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace XCoderAv.Models;
+
+/// <summary>菜单项模型</summary>
+public class MenuModel
+{
+    /// <summary>图标字体编码</summary>
+    public String IconFont { get; set; } = "";
+
+    /// <summary>标题</summary>
+    public String Title { get; set; } = "";
+
+    /// <summary>背景颜色</summary>
+    public String BackColor { get; set; } = "#888";
+
+    /// <summary>关联视图类型</summary>
+    public Type? ViewType { get; set; }
+}
\ No newline at end of file
Added +25 -0
diff --git a/XCoderAv/Models/RegexCaptureItem.cs b/XCoderAv/Models/RegexCaptureItem.cs
new file mode 100644
index 0000000..1bff322
--- /dev/null
+++ b/XCoderAv/Models/RegexCaptureItem.cs
@@ -0,0 +1,25 @@
+using System.Text.RegularExpressions;
+
+namespace XCoderAv.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 +28 -0
diff --git a/XCoderAv/Models/RegexGroupItem.cs b/XCoderAv/Models/RegexGroupItem.cs
new file mode 100644
index 0000000..90f04c4
--- /dev/null
+++ b/XCoderAv/Models/RegexGroupItem.cs
@@ -0,0 +1,28 @@
+using System.Text.RegularExpressions;
+
+namespace XCoderAv.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 +28 -0
diff --git a/XCoderAv/Models/RegexMatchItem.cs b/XCoderAv/Models/RegexMatchItem.cs
new file mode 100644
index 0000000..a56019d
--- /dev/null
+++ b/XCoderAv/Models/RegexMatchItem.cs
@@ -0,0 +1,28 @@
+using System.Text.RegularExpressions;
+
+namespace XCoderAv.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; }
+}
Modified +59 -19
diff --git a/XCoderAv/Program.cs b/XCoderAv/Program.cs
index cea434d..a05ebbc 100644
--- a/XCoderAv/Program.cs
+++ b/XCoderAv/Program.cs
@@ -1,27 +1,67 @@
-using System;
+using System.Text;
 using Avalonia;
-using Avalonia.Logging.Serilog;
+using NewLife;
+using NewLife.Log;
+using Stardust;
 
-namespace XCoderAv
+namespace XCoderAv;
+
+class Program
 {
-    class Program
+    public static void Main(String[] args)
     {
-        // Initialization code. Don't use any Avalonia, third-party APIs or any
-        // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
-        // yet and stuff might break.
-        public static void Main(string[] args) => BuildAvaloniaApp().Start(AppMain, args);
-
-        // Avalonia configuration, don't remove; also used by visual designer.
-        public static AppBuilder BuildAvaloniaApp()
-            => AppBuilder.Configure<App>()
-                .UsePlatformDetect()
-                .LogToDebug();
-
-        // Your application's entry point. Here you can initialize your MVVM framework, DI
-        // container, etc.
-        private static void AppMain(Application app, string[] args)
+        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
+        MachineInfo.RegisterAsync();
+
+        StartClient();
+
+        var set = XConfig.Current;
+        StringHelper.EnableSpeechTip = set.SpeechTip;
+
+        if (set.IsNew)
         {
-            app.Run(new MainWindow());
+            try { "学无先后达者为师,欢迎使用新生命码神工具!".SpeechTip(); } catch { }
         }
+
+        BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
+    }
+
+    public static AppBuilder BuildAvaloniaApp()
+        => AppBuilder.Configure<App>()
+            .UsePlatformDetect()
+            .WithInterFont()
+            .LogToTrace();
+
+    static StarClient? _Client;
+    private static void StartClient()
+    {
+        var set = XConfig.Current;
+        var server = set.Server;
+        if (server.IsNullOrEmpty()) return;
+
+        XTrace.WriteLine("初始化服务端地址:{0}", server);
+
+        var client = new StarClient(server)
+        {
+            Code = set.Code,
+            Secret = set.Secret,
+            Log = XTrace.Log,
+        };
+
+        client.OnLogined += (s, e) =>
+        {
+            if (client.Logined && !client.Code.IsNullOrEmpty())
+            {
+                set.Code = client.Code;
+                set.Secret = client.Secret;
+                set.Save();
+            }
+        };
+
+        client.Open();
+
+        NewLife.Model.Host.RegisterExit(() => client.Logout("ApplicationExit"));
+
+        _Client = client;
     }
 }
Added +259 -0
diff --git a/XCoderAv/ViewModels/FolderStatViewModel.cs b/XCoderAv/ViewModels/FolderStatViewModel.cs
new file mode 100644
index 0000000..9accb8c
--- /dev/null
+++ b/XCoderAv/ViewModels/FolderStatViewModel.cs
@@ -0,0 +1,259 @@
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using CommunityToolkit.Mvvm.ComponentModel;
+using NewLife.Caching;
+
+namespace XCoderAv.ViewModels;
+
+/// <summary>文件夹大小统计 ViewModel</summary>
+public partial class FolderStatViewModel : ObservableObject
+{
+    #region 属性
+    private readonly ICache _cache = new MemoryCache();
+
+    /// <summary>目录根节点</summary>
+    public ObservableCollection<FolderItem> Roots { get; } = [];
+
+    /// <summary>日志文本</summary>
+    [ObservableProperty]
+    private String _logText = "";
+
+    /// <summary>日志回调</summary>
+    public Action<String>? OnLog { get; set; }
+    #endregion
+
+    #region 构造
+    /// <summary>实例化文件夹大小统计 ViewModel</summary>
+    public FolderStatViewModel()
+    {
+        LoadDrives();
+    }
+
+    private void LoadDrives()
+    {
+        foreach (var item in DriveInfo.GetDrives())
+        {
+            if (item.DriveType == DriveType.Fixed)
+            {
+                var root = new FolderItem
+                {
+                    Name = item.Name,
+                    FullPath = item.RootDirectory.FullName,
+                    Size = item.TotalSize,
+                    SizeText = FormatSize(item.TotalSize),
+                    IsDirectory = true,
+                    IsExpanded = false
+                };
+                // 添加占位子节点,展开时触发加载
+                root.Children.Add(new FolderItem { IsPlaceholder = true });
+                Roots.Add(root);
+            }
+        }
+    }
+    #endregion
+
+    #region 目录树操作
+    /// <summary>展开目录</summary>
+    public async Task ExpandFolder(FolderItem item)
+    {
+        if (!item.IsDirectory) return;
+        if (item.Children.Count == 1 && item.Children[0].IsPlaceholder)
+        {
+            await LoadChildren(item);
+        }
+    }
+
+    /// <summary>折叠目录</summary>
+    public void CollapseFolder(FolderItem item)
+    {
+        if (!item.IsDirectory) return;
+        item.Children.Clear();
+        item.Children.Add(new FolderItem { IsPlaceholder = true });
+    }
+
+    private async Task LoadChildren(FolderItem parent)
+    {
+        parent.Children.Clear();
+
+        var di = new DirectoryInfo(parent.FullPath);
+        if (!di.Exists) return;
+
+        WriteLog($"展开目录 {parent.FullPath}");
+
+        var list = new List<FileSystemInfo>();
+        try
+        {
+            list.AddRange(di.GetDirectories());
+        }
+        catch { }
+        try
+        {
+            list.AddRange(di.GetFiles());
+        }
+        catch { }
+
+        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 child = new FolderItem
+            {
+                Name = item.Name,
+                FullPath = item.FullName,
+                Size = size,
+                SizeText = FormatSize(size),
+                IsDirectory = item is DirectoryInfo,
+                DisplayName = String.Format("{0,-" + len + "} {1,10}", item.Name, FormatSize(size)),
+                BackgroundColor = GetSizeColor(size)
+            };
+
+            if (item is DirectoryInfo)
+            {
+                // 占位子节点,展开时继续加载
+                child.Children.Add(new FolderItem { IsPlaceholder = true });
+
+                // 异步统计文件夹大小
+                var captured = child;
+                _ = Task.Run(() => CalculateSize(captured));
+            }
+
+            parent.Children.Add(child);
+        }
+    }
+
+    private void CalculateSize(FolderItem node)
+    {
+        if (!node.IsDirectory) return;
+
+        var di = new DirectoryInfo(node.FullPath);
+        try
+        {
+            var size = FolderSize(di);
+            node.Size = size;
+            node.SizeText = FormatSize(size);
+            node.BackgroundColor = GetSizeColor(size);
+            node.DisplayName = $"{node.Name,-20} {FormatSize(size),10}";
+        }
+        catch (Exception ex)
+        {
+            WriteLog(ex.ToString());
+        }
+    }
+
+    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 辅助方法
+    /// <summary>格式化文件大小</summary>
+    public 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;
+
+    /// <summary>根据大小获取背景色</summary>
+    private static String GetSizeColor(Int64 size)
+    {
+        if (size > 1024L * 1024 * 1024) return "#FFFF00";
+        if (size > 100L * 1024 * 1024) return "#7CFC00";
+        if (size > 1024L * 1024) return "#ADD8E6";
+        if (size > 1024L) return "#FFE4E1";
+        return "#FFFFFF";
+    }
+
+    /// <summary>写入日志</summary>
+    public void WriteLog(String msg)
+    {
+        OnLog?.Invoke(msg);
+    }
+    #endregion
+}
+
+/// <summary>文件夹项</summary>
+public partial class FolderItem : ObservableObject
+{
+    /// <summary>显示名称</summary>
+    [ObservableProperty]
+    private String _displayName = "";
+
+    /// <summary>名称</summary>
+    public String Name { get; set; } = "";
+
+    /// <summary>完整路径</summary>
+    public String FullPath { get; set; } = "";
+
+    /// <summary>大小</summary>
+    [ObservableProperty]
+    private Int64 _size;
+
+    /// <summary>大小文本</summary>
+    [ObservableProperty]
+    private String _sizeText = "";
+
+    /// <summary>是否为目录</summary>
+    public Boolean IsDirectory { get; set; }
+
+    /// <summary>是否已展开</summary>
+    [ObservableProperty]
+    private Boolean _isExpanded;
+
+    /// <summary>是否占位节点</summary>
+    public Boolean IsPlaceholder { get; set; }
+
+    /// <summary>背景颜色</summary>
+    [ObservableProperty]
+    private String _backgroundColor = "#FFFFFF";
+
+    /// <summary>子节点</summary>
+    public ObservableCollection<FolderItem> Children { get; } = [];
+}
Added +87 -0
diff --git a/XCoderAv/ViewModels/GpsViewModel.cs b/XCoderAv/ViewModels/GpsViewModel.cs
new file mode 100644
index 0000000..3e3025c
--- /dev/null
+++ b/XCoderAv/ViewModels/GpsViewModel.cs
@@ -0,0 +1,87 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using NewLife;
+
+namespace XCoderAv.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 +279 -0
diff --git a/XCoderAv/ViewModels/IconToolViewModel.cs b/XCoderAv/ViewModels/IconToolViewModel.cs
new file mode 100644
index 0000000..06b4afd
--- /dev/null
+++ b/XCoderAv/ViewModels/IconToolViewModel.cs
@@ -0,0 +1,279 @@
+using System.Collections.ObjectModel;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Media;
+using Avalonia.Media.Imaging;
+using Avalonia.Platform;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using NewLife;
+using XCoderAv.Models;
+
+namespace XCoderAv.ViewModels;
+
+/// <summary>图标水印处理工具 ViewModel(跨平台版,基于 Avalonia + Skia)</summary>
+public partial class IconToolViewModel : ObservableObject
+{
+    #region 属性
+    /// <summary>源图片路径</summary>
+    [ObservableProperty]
+    private String _sourceFile = "";
+
+    /// <summary>源图片</summary>
+    [ObservableProperty]
+    private Bitmap? _sourceImage;
+
+    /// <summary>预览图片</summary>
+    [ObservableProperty]
+    private Bitmap? _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>
+    [ObservableProperty]
+    private String _status = "就绪";
+
+    /// <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 FontManager.Current.SystemFonts)
+        {
+            FontNames.Add(ft.Name);
+        }
+    }
+    #endregion
+
+    #region 命令
+    /// <summary>加载图片</summary>
+    [RelayCommand]
+    private async Task LoadImage()
+    {
+        var window = GetParentWindow();
+        if (window == null) return;
+
+        var dialog = new OpenFileDialog
+        {
+            Filters =
+            [
+                new FileDialogFilter { Name = "图片文件", Extensions = ["png", "jpg", "jpeg", "bmp", "gif", "ico"] },
+                new FileDialogFilter { Name = "所有文件", Extensions = ["*"] }
+            ],
+            Title = "选择图片",
+            AllowMultiple = false
+        };
+
+        var result = await dialog.ShowAsync(window);
+        if (result == null || result.Length == 0) return;
+
+        LoadFromFile(result[0]);
+    }
+
+    /// <summary>从文件加载图片</summary>
+    public void LoadFromFile(String fileName)
+    {
+        SourceFile = fileName;
+
+        _originalBitmap?.Dispose();
+        _originalBitmap = new Bitmap(fileName);
+
+        SourceImage = _originalBitmap;
+
+        // 自动设置水印位置到右下角
+        WatermarkX = _originalBitmap.PixelSize.Width - 150;
+        WatermarkY = _originalBitmap.PixelSize.Height - 50;
+
+        MakeWater();
+    }
+
+    /// <summary>应用水印</summary>
+    [RelayCommand]
+    private void MakeWater()
+    {
+        if (_originalBitmap == null) return;
+
+        var bmp = MakeWatermarkImage();
+        PreviewImage = bmp;
+    }
+
+    /// <summary>保存图片</summary>
+    [RelayCommand]
+    private async Task SaveImage()
+    {
+        if (_originalBitmap == null) return;
+
+        var window = GetParentWindow();
+        if (window == null) return;
+
+        var dialog = new SaveFileDialog
+        {
+            Filters =
+            [
+                new FileDialogFilter { Name = "PNG 图片", Extensions = ["png"] },
+                new FileDialogFilter { Name = "JPEG 图片", Extensions = ["jpg"] },
+                new FileDialogFilter { Name = "BMP 图片", Extensions = ["bmp"] }
+            ],
+            Title = "保存图片",
+            DefaultExtension = "png"
+        };
+
+        var result = await dialog.ShowAsync(window);
+        if (result == null) return;
+
+        var bmp = MakeWatermarkImage();
+        bmp.Save(result);
+        bmp.Dispose();
+
+        Status = $"已保存到 {result}";
+    }
+
+    /// <summary>生成 ICO 图标</summary>
+    [RelayCommand]
+    private async Task MakeIco()
+    {
+        if (_originalBitmap == null)
+        {
+            Status = "请先加载图片!";
+            return;
+        }
+
+        var sizes = IconSizes.Where(e => e.IsSelected).Select(e => e.Size).ToArray();
+        if (sizes.Length == 0)
+        {
+            Status = "请至少选择一个图标尺寸!";
+            return;
+        }
+
+        var window = GetParentWindow();
+        if (window == null) return;
+
+        var dialog = new SaveFileDialog
+        {
+            Filters =
+            [
+                new FileDialogFilter { Name = "ICO 图标", Extensions = ["ico"] }
+            ],
+            Title = "保存图标",
+            DefaultExtension = "ico"
+        };
+
+        var result = await dialog.ShowAsync(window);
+        if (result == null) return;
+
+        var bmp = MakeWatermarkImage();
+        var ms = new MemoryStream();
+        IconHelper.Convert(bmp, ms, sizes, [32]);
+        bmp.Dispose();
+
+        await File.WriteAllBytesAsync(result, ms.ToArray());
+
+        Status = $"已保存 ICO 到 {result}";
+    }
+    #endregion
+
+    #region 辅助
+    private Bitmap MakeWatermarkImage()
+    {
+        if (_originalBitmap == null) throw new InvalidOperationException("没有加载图片");
+
+        var size = _originalBitmap.PixelSize;
+        var dpi = _originalBitmap.Dpi;
+
+        // 使用渲染目标位图来绘制
+        var rtBmp = new RenderTargetBitmap(size, dpi);
+        using (var ctx = rtBmp.CreateDrawingContext())
+        {
+            ctx.DrawImage(_originalBitmap, new Rect(0, 0, size.Width, size.Height));
+
+            if (!WatermarkText.IsNullOrEmpty())
+            {
+                var color = Color.Parse(WatermarkColor);
+                var typeface = new Typeface(FontName);
+                var formattedText = new FormattedText(
+                    WatermarkText,
+                    CultureInfo.CurrentCulture,
+                    FlowDirection.LeftToRight,
+                    typeface,
+                    FontSize,
+                    new SolidColorBrush(color));
+                formattedText.TextAlignment = TextAlignment.Left;
+                formattedText.MaxTextWidth = size.Width;
+
+                ctx.DrawText(formattedText, new Point(WatermarkX, WatermarkY));
+            }
+        }
+
+        return rtBmp;
+    }
+
+    private static Window? GetParentWindow()
+    {
+        if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+        {
+            return desktop.Windows?.OfType<Window>().FirstOrDefault(w => w.IsActive);
+        }
+        return null;
+    }
+    #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;
+}
Added +76 -0
diff --git a/XCoderAv/ViewModels/MainViewModel.cs b/XCoderAv/ViewModels/MainViewModel.cs
new file mode 100644
index 0000000..402d0ef
--- /dev/null
+++ b/XCoderAv/ViewModels/MainViewModel.cs
@@ -0,0 +1,76 @@
+using System.Collections.ObjectModel;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using XCoderAv.Models;
+using XCoderAv.Views;
+
+namespace XCoderAv.ViewModels;
+
+/// <summary>主视图模型</summary>
+public partial class MainViewModel : ObservableObject
+{
+    public MainViewModel()
+    {
+        Menus =
+        [
+            new MenuModel() { IconFont = "📊", Title = "数据建模", BackColor = "#218868" },
+            new MenuModel() { IconFont = "🌐", Title = "网络工具", BackColor = "#EE3B3B", ViewType = typeof(NetworkWindow) },
+            new MenuModel() { IconFont = "🔗", Title = "RPC工具", BackColor = "#218868" },
+            new MenuModel() { IconFont = "🔌", Title = "串口工具", BackColor = "#EE3B3B" },
+            new MenuModel() { IconFont = "🗺️", Title = "地图接口", BackColor = "#218868" },
+            new MenuModel() { IconFont = "🔍", Title = "正则表达式", BackColor = "#218868", ViewType = typeof(RegexWindow) },
+            new MenuModel() { IconFont = "🎨", Title = "图标水印", BackColor = "#EE3B3B", ViewType = typeof(IconToolWindow) },
+            new MenuModel() { IconFont = "🔒", Title = "加密解密", BackColor = "#218868", ViewType = typeof(SecurityWindow) },
+            new MenuModel() { IconFont = "🗣️", Title = "语音助手", BackColor = "#EE3B3B", ViewType = typeof(SpeechWindow) },
+            new MenuModel() { IconFont = "📁", Title = "文件夹统计", BackColor = "#218868", ViewType = typeof(FolderStatWindow) },
+            new MenuModel() { IconFont = "📄", Title = "文件编码", BackColor = "#218868" },
+            new MenuModel() { IconFont = "📍", Title = "GPS辅助", BackColor = "#218868", ViewType = typeof(GpsWindow) },
+            new MenuModel() { IconFont = "📡", Title = "MQTT客户端", BackColor = "#EE3B3B", ViewType = typeof(MqttWindow) },
+        ];
+
+        SelectedMenu = Menus[0];
+    }
+
+    /// <summary>菜单集合</summary>
+    public ObservableCollection<MenuModel> Menus { get; }
+
+    [ObservableProperty]
+    private MenuModel _selectedMenu = null!;
+
+    partial void OnSelectedMenuChanged(MenuModel value)
+    {
+        // 点击菜单项时打开对应的工具窗口
+        if (value.ViewType == null) return;
+
+        // 检查窗口是否已打开
+        var existing = GetOpenedWindow(value.ViewType);
+        if (existing != null)
+        {
+            existing.Activate();
+            return;
+        }
+
+        // 创建新窗口
+        if (Activator.CreateInstance(value.ViewType) is Window window)
+        {
+            window.Show();
+        }
+    }
+
+    /// <summary>获取已打开的窗口</summary>
+    private static Window? GetOpenedWindow(Type viewType)
+    {
+        if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+        {
+            foreach (var w in desktop.Windows)
+            {
+                if (w.GetType() == viewType)
+                    return w;
+            }
+        }
+        return null;
+    }
+}
\ No newline at end of file
Added +274 -0
diff --git a/XCoderAv/ViewModels/MqttViewModel.cs b/XCoderAv/ViewModels/MqttViewModel.cs
new file mode 100644
index 0000000..5342d03
--- /dev/null
+++ b/XCoderAv/ViewModels/MqttViewModel.cs
@@ -0,0 +1,274 @@
+using System.Threading.Tasks;
+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 XCoderAv.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())
+        {
+            WriteLog("请输入订阅主题!");
+            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())
+        {
+            WriteLog("请输入发布主题!");
+            return;
+        }
+        if (str.IsNullOrEmpty())
+        {
+            WriteLog("请输入发布内容!");
+            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 +409 -0
diff --git a/XCoderAv/ViewModels/NetworkViewModel.cs b/XCoderAv/ViewModels/NetworkViewModel.cs
new file mode 100644
index 0000000..f58f91c
--- /dev/null
+++ b/XCoderAv/ViewModels/NetworkViewModel.cs
@@ -0,0 +1,409 @@
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Controls;
+using Avalonia.Threading;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using NewLife;
+using NewLife.Data;
+using NewLife.Log;
+using NewLife.Net;
+using NewLife.Threading;
+
+namespace XCoderAv.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绑定到TextBox</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))
+        {
+            WriteLog("发送内容不能为空!");
+            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 +440 -0
diff --git a/XCoderAv/ViewModels/RegexViewModel.cs b/XCoderAv/ViewModels/RegexViewModel.cs
new file mode 100644
index 0000000..312ecdf
--- /dev/null
+++ b/XCoderAv/ViewModels/RegexViewModel.cs
@@ -0,0 +1,440 @@
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.IO;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Avalonia.Threading;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using XCoderAv.Models;
+
+namespace XCoderAv.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;
+
+    #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));
+    }
+
+    #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
+                    };
+
+                    Dispatcher.UIThread.Post(() =>
+                    {
+                        Matches.Add(item);
+                    });
+                }
+
+                sw.Stop();
+                Dispatcher.UIThread.Post(() =>
+                {
+                    Status = $"成功匹配 {Matches.Count} 项!耗时 {sw.ElapsedMilliseconds}ms";
+                });
+            }
+            catch (RegexMatchTimeoutException)
+            {
+                Dispatcher.UIThread.Post(() => Status = "正则匹配超时!");
+            }
+            catch (Exception ex)
+            {
+                Dispatcher.UIThread.Post(() => 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);
+                    Dispatcher.UIThread.Post(() =>
+                    {
+                        SourceText = result;
+                    });
+                }
+
+                sw.Stop();
+                Dispatcher.UIThread.Post(() =>
+                {
+                    Status = $"成功替换 {count} 项!耗时 {sw.ElapsedMilliseconds}ms";
+                });
+            }
+            catch (RegexMatchTimeoutException)
+            {
+                Dispatcher.UIThread.Post(() => Status = "正则替换超时!");
+            }
+            catch (Exception ex)
+            {
+                Dispatcher.UIThread.Post(() => Status = $"错误:{ex.Message}");
+            }
+        });
+    }
+
+    /// <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)
+                {
+                    Dispatcher.UIThread.Post(() => 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)
+                {
+                    Dispatcher.UIThread.Post(() =>
+                    {
+                        SourceText = firstContent;
+                    });
+                }
+
+                Dispatcher.UIThread.Post(() =>
+                {
+                    Status = $"批量替换完成:{changedCount} 个文件,{totalCount} 项替换,耗时 {sw.ElapsedMilliseconds}ms";
+                });
+            }
+            catch (Exception ex)
+            {
+                Dispatcher.UIThread.Post(() => Status = $"错误:{ex.Message}");
+            }
+        });
+    }
+
+    #endregion
+}
Added +743 -0
diff --git a/XCoderAv/ViewModels/SecurityViewModel.cs b/XCoderAv/ViewModels/SecurityViewModel.cs
new file mode 100644
index 0000000..5c52198
--- /dev/null
+++ b/XCoderAv/ViewModels/SecurityViewModel.cs
@@ -0,0 +1,743 @@
+using System.Collections.Generic;
+using System.Linq;
+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 NewLife;
+using NewLife.Collections;
+using NewLife.Data;
+using NewLife.Reflection;
+using NewLife.Security;
+using NewLife.Serialization;
+using NewLife.Web;
+
+namespace XCoderAv.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 macs = GetMacs().ToList();
+        if (macs.Count > 0) sb.AppendFormat("MAC:\t{0}\r\n", macs.Join(",", x => x.ToHex("-")));
+
+        // 跨平台无法获取 WMI/Registry 信息,仅输出 MachineInfo
+        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>雪花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 +219 -0
diff --git a/XCoderAv/ViewModels/SpeechViewModel.cs b/XCoderAv/ViewModels/SpeechViewModel.cs
new file mode 100644
index 0000000..d61ba11
--- /dev/null
+++ b/XCoderAv/ViewModels/SpeechViewModel.cs
@@ -0,0 +1,219 @@
+using System.Collections.ObjectModel;
+using System.Linq;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using NewLife;
+
+namespace XCoderAv.ViewModels;
+
+/// <summary>语音助手 (TTS) ViewModel(跨平台版)</summary>
+public partial class SpeechViewModel : ObservableObject
+{
+    #region 属性
+    /// <summary>可用语音列表</summary>
+    public ObservableCollection<String> Voices { get; } = [];
+
+    /// <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;
+
+    /// <summary>状态文本</summary>
+    [ObservableProperty]
+    private String _status = "就绪";
+
+    /// <summary>TTS 引擎可用</summary>
+    [ObservableProperty]
+    private Boolean _isTtsAvailable;
+
+    /// <summary>日志文本</summary>
+    [ObservableProperty]
+    private String _logText = "";
+    #endregion
+
+    #region 构造
+    /// <summary>实例化语音助手 ViewModel</summary>
+    public SpeechViewModel()
+    {
+        LoadVoices();
+    }
+
+    private void LoadVoices()
+    {
+        // 跨平台 TTS 支持:Windows 使用 System.Speech,其他平台暂不支持
+        // 可通过条件编译或运行时检测扩展
+        var available = false;
+        var voices = new List<String>();
+
+#if NETFRAMEWORK || WINDOWS
+        try
+        {
+            using var synth = new System.Speech.Synthesis.SpeechSynthesizer();
+            foreach (var vi in synth.GetInstalledVoices().Select(e => e.VoiceInfo))
+            {
+                voices.Add($"{vi.Name}[{vi.Culture}]");
+            }
+            available = voices.Count > 0;
+        }
+        catch (Exception ex)
+        {
+            voices.Add($"TTS 不可用:{ex.Message}");
+        }
+#else
+        // 跨平台 TTS 可通过 System.Speech 在 Windows 上工作
+        // Linux/macOS 需要额外库
+        try
+        {
+            // 尝试加载 System.Speech (仅 Windows)
+            var asm = System.Reflection.Assembly.Load("System.Speech");
+            if (asm != null)
+            {
+                var type = asm.GetType("System.Speech.Synthesis.SpeechSynthesizer");
+                if (type != null)
+                {
+                    using var synth = (IDisposable)Activator.CreateInstance(type)!;
+                    var getVoices = type.GetMethod("GetInstalledVoices");
+                    if (getVoices != null)
+                    {
+                        var installedVoices = (System.Collections.IList)getVoices.Invoke(synth, null)!;
+                        foreach (var iv in installedVoices)
+                        {
+                            var viProp = iv.GetType().GetProperty("VoiceInfo");
+                            if (viProp?.GetValue(iv) is Object vi)
+                            {
+                                var name = vi.GetType().GetProperty("Name")?.GetValue(vi)?.ToString() ?? "";
+                                var culture = vi.GetType().GetProperty("Culture")?.GetValue(vi)?.ToString() ?? "";
+                                voices.Add($"{name}[{culture}]");
+                            }
+                        }
+                        available = voices.Count > 0;
+                    }
+                }
+            }
+        }
+        catch
+        {
+            // TTS 不可用
+        }
+#endif
+
+        IsTtsAvailable = available;
+
+        Voices.Clear();
+        if (voices.Count > 0)
+        {
+            foreach (var v in voices)
+                Voices.Add(v);
+        }
+        else
+        {
+            Voices.Add("TTS 引擎不可用(仅支持 Windows)");
+            Status = "TTS 引擎不可用,当前平台不支持语音合成";
+        }
+    }
+    #endregion
+
+    #region 命令
+    /// <summary>朗读</summary>
+    [RelayCommand]
+    private void Speak()
+    {
+        var txt = SpeakText;
+        if (txt.IsNullOrEmpty()) return;
+
+        Stop();
+
+#if NETFRAMEWORK || WINDOWS
+        try
+        {
+            var synth = new System.Speech.Synthesis.SpeechSynthesizer
+            {
+                Volume = Volume,
+                Rate = Rate
+            };
+
+            if (SelectedVoiceIndex >= 0 && SelectedVoiceIndex < Voices.Count)
+            {
+                // 获取实际语音名称
+                var voiceName = Voices[SelectedVoiceIndex];
+                if (voiceName.Contains('['))
+                    voiceName = voiceName[..voiceName.IndexOf('[')];
+                synth.SelectVoice(voiceName);
+            }
+
+            IsSpeaking = true;
+            Status = "正在朗读...";
+            synth.SpeakCompleted += (s, e) =>
+            {
+                IsSpeaking = false;
+                Status = "朗读完成";
+                synth.Dispose();
+            };
+            synth.SpeakAsync(txt);
+        }
+        catch (Exception ex)
+        {
+            Status = $"朗读失败:{ex.Message}";
+            IsSpeaking = false;
+        }
+#else
+        Status = "当前平台不支持语音合成";
+#endif
+    }
+
+    /// <summary>停止朗读</summary>
+    [RelayCommand]
+    private void Stop()
+    {
+#if NETFRAMEWORK || WINDOWS
+        try
+        {
+            // 通过反射停止所有正在进行的朗读
+            var asm = System.Reflection.Assembly.Load("System.Speech");
+            var synthType = asm.GetType("System.Speech.Synthesis.SpeechSynthesizer");
+            if (synthType != null)
+            {
+                // 创建新实例来取消
+                using var synth = (IDisposable)Activator.CreateInstance(synthType)!;
+                var speakAsyncCancelAll = synthType.GetMethod("SpeakAsyncCancelAll");
+                speakAsyncCancelAll?.Invoke(synth, null);
+            }
+        }
+        catch
+        {
+        }
+#endif
+        IsSpeaking = false;
+        Status = "已停止";
+    }
+
+    /// <summary>保存为 WAV 文件</summary>
+    [RelayCommand]
+    private void SaveWav()
+    {
+        var txt = SpeakText;
+        if (txt.IsNullOrEmpty())
+        {
+            Status = "请输入要转换的文本!";
+            return;
+        }
+
+        Status = "当前平台不支持保存为 WAV 文件";
+    }
+    #endregion
+}
Added +46 -0
diff --git a/XCoderAv/Views/FolderStatWindow.axaml b/XCoderAv/Views/FolderStatWindow.axaml
new file mode 100644
index 0000000..5112d75
--- /dev/null
+++ b/XCoderAv/Views/FolderStatWindow.axaml
@@ -0,0 +1,46 @@
+<Window xmlns="https://github.com/avaloniaui"
+        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:vm="clr-namespace:XCoderAv.ViewModels"
+        mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="700"
+        x:Class="XCoderAv.Views.FolderStatWindow"
+        Title="文件夹大小统计" Width="1000" Height="700"
+        WindowStartupLocation="CenterOwner">
+    <Window.Styles>
+        <Style Selector="TreeViewItem">
+            <Setter Property="Foreground" Value="#D4D4D4"/>
+            <Setter Property="FontFamily" Value="Consolas"/>
+            <Setter Property="FontSize" Value="13"/>
+        </Style>
+    </Window.Styles>
+
+    <Grid Margin="4">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="*"/>
+            <RowDefinition Height="4"/>
+            <RowDefinition Height="150"/>
+        </Grid.RowDefinitions>
+
+        <!-- 树形目录 -->
+        <TreeView Grid.Row="0" x:Name="treeView" BorderThickness="1" BorderBrush="#D0D0D0"
+                  Background="#1E1E1E">
+            <TreeView.ItemTemplate>
+                <TreeDataTemplate ItemsSource="{Binding Children}">
+                    <Border Background="{Binding BackgroundColor}" Padding="4 2">
+                        <TextBlock Text="{Binding DisplayName}" FontFamily="Consolas" FontSize="13"/>
+                    </Border>
+                </TreeDataTemplate>
+            </TreeView.ItemTemplate>
+        </TreeView>
+
+        <GridSplitter Grid.Row="1" Height="4" HorizontalAlignment="Stretch" Background="#F0F0F0"/>
+
+        <!-- 日志输出 -->
+        <Border Grid.Row="2" BorderBrush="#D0D0D0" BorderThickness="1">
+            <TextBox x:Name="txtLog" IsReadOnly="True"
+                     FontFamily="Consolas" FontSize="12" Background="#1E1E1E" Foreground="#D4D4D4"
+                     Margin="2"/>
+        </Border>
+    </Grid>
+</Window>
Added +55 -0
diff --git a/XCoderAv/Views/FolderStatWindow.axaml.cs b/XCoderAv/Views/FolderStatWindow.axaml.cs
new file mode 100644
index 0000000..87bdcd8
--- /dev/null
+++ b/XCoderAv/Views/FolderStatWindow.axaml.cs
@@ -0,0 +1,55 @@
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Threading;
+using XCoderAv.ViewModels;
+
+namespace XCoderAv.Views;
+
+/// <summary>文件夹大小统计工具窗口</summary>
+public partial class FolderStatWindow : Window
+{
+    /// <summary>实例化文件夹大小统计窗口</summary>
+    public FolderStatWindow()
+    {
+        InitializeComponent();
+
+        var vm = new FolderStatViewModel();
+        vm.OnLog = OnLog;
+        DataContext = vm;
+
+        // 加载根节点
+        treeView.ItemsSource = vm.Roots;
+
+        // 订阅 TreeViewItem 的 Expanded/Collapsed 路由事件
+        treeView.AddHandler(TreeViewItem.ExpandedEvent, OnItemExpanded);
+        treeView.AddHandler(TreeViewItem.CollapsedEvent, OnItemCollapsed);
+    }
+
+    private async void OnItemExpanded(Object? sender, RoutedEventArgs e)
+    {
+        if (e.Source is TreeViewItem tvi && tvi.DataContext is FolderItem folderItem && DataContext is FolderStatViewModel vm)
+        {
+            await vm.ExpandFolder(folderItem);
+        }
+    }
+
+    private void OnItemCollapsed(Object? sender, RoutedEventArgs e)
+    {
+        if (e.Source is TreeViewItem tvi && tvi.DataContext is FolderItem folderItem && DataContext is FolderStatViewModel vm)
+        {
+            vm.CollapseFolder(folderItem);
+        }
+    }
+
+    private void OnLog(String msg)
+    {
+        Dispatcher.UIThread.Post(() =>
+        {
+            txtLog.Text += msg + "\n";
+            if (txtLog.Text.Length > 10000)
+                txtLog.Text = txtLog.Text[^5000..];
+
+            txtLog.CaretIndex = txtLog.Text.Length;
+        });
+    }
+}
Added +152 -0
diff --git a/XCoderAv/Views/GpsWindow.axaml b/XCoderAv/Views/GpsWindow.axaml
new file mode 100644
index 0000000..707f8cf
--- /dev/null
+++ b/XCoderAv/Views/GpsWindow.axaml
@@ -0,0 +1,152 @@
+<Window xmlns="https://github.com/avaloniaui"
+        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" d:DesignWidth="600" d:DesignHeight="450"
+        x:Class="XCoderAv.Views.GpsWindow"
+        Title="GPS 辅助工具" Width="600" Height="450"
+        WindowStartupLocation="CenterOwner">
+    <Window.Styles>
+        <Style Selector="Border.GroupBorder">
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="Margin" Value="0,0,0,6"/>
+        </Style>
+        <Style Selector="TextBlock.SectionTitle">
+            <Setter Property="FontWeight" Value="Bold"/>
+            <Setter Property="Margin" Value="4,3"/>
+            <Setter Property="FontSize" Value="13"/>
+        </Style>
+        <Style Selector="TextBox.ConfigTextBox">
+            <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 Selector="Button.ActionButton">
+            <Setter Property="Height" Value="30"/>
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Padding" Value="12,0"/>
+        </Style>
+        <Style Selector="TextBox.ResultText">
+            <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.Styles>
+
+    <Grid Margin="8">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="*"/>
+        </Grid.RowDefinitions>
+
+        <!-- 合并输入 -->
+        <Border Grid.Row="0" Classes="GroupBorder">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                </Grid.RowDefinitions>
+                <TextBlock Text="HEX 合并输入(16位)" Classes="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}" Classes="ConfigTextBox"/>
+                    <Button Grid.Column="1" Content="转换" Command="{Binding ConvertCombinedCommand}"
+                            Classes="ActionButton" Margin="4,2" Width="80"/>
+                </Grid>
+            </Grid>
+        </Border>
+
+        <!-- 分离输入 -->
+        <Border Grid.Row="1" Classes="GroupBorder">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                </Grid.RowDefinitions>
+                <TextBlock Text="HEX 分离输入(各8位)" Classes="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="纬度" VerticalAlignment="Center" Margin="4,0"/>
+                    <TextBox Grid.Column="1" Text="{Binding HexLat}" Classes="ConfigTextBox"/>
+                    <TextBlock Grid.Column="2" Text="经度" VerticalAlignment="Center" Margin="4,0"/>
+                    <TextBox Grid.Column="3" Text="{Binding HexLong}" Classes="ConfigTextBox"/>
+                    <Button Grid.Column="4" Content="转换" Command="{Binding ConvertCommand}"
+                            Classes="ActionButton" Margin="4,2" Width="80"/>
+                </Grid>
+            </Grid>
+        </Border>
+
+        <!-- 结果 -->
+        <Border Grid.Row="2" Classes="GroupBorder">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                </Grid.RowDefinitions>
+                <TextBlock Text="转换结果" Classes="SectionTitle"/>
+
+                <Grid Grid.Row="1" Margin="4,0,4,2">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="Auto"/>
+                        <ColumnDefinition Width="*"/>
+                    </Grid.ColumnDefinitions>
+                    <TextBlock Grid.Column="0" Text="纬度" VerticalAlignment="Center" Width="50"/>
+                    <TextBox Grid.Column="1" Text="{Binding Latitude}" Classes="ResultText"/>
+                </Grid>
+
+                <Grid Grid.Row="2" Margin="4,0,4,4">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="Auto"/>
+                        <ColumnDefinition Width="*"/>
+                    </Grid.ColumnDefinitions>
+                    <TextBlock Grid.Column="0" Text="经度" VerticalAlignment="Center" Width="50"/>
+                    <TextBox Grid.Column="1" Text="{Binding Longitude}" Classes="ResultText"/>
+                </Grid>
+            </Grid>
+        </Border>
+
+        <!-- 合并坐标 -->
+        <Border Grid.Row="3" Classes="GroupBorder">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+                <TextBlock Text="合并坐标" Classes="SectionTitle"/>
+
+                <TextBox Grid.Row="1" Text="{Binding LatLong}" Classes="ResultText" Margin="4,0,4,2"/>
+
+                <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right"
+                            VerticalAlignment="Bottom" Margin="4">
+                    <Button Content="清空" Command="{Binding ClearCommand}" Classes="ActionButton" Width="80"/>
+                </StackPanel>
+            </Grid>
+        </Border>
+    </Grid>
+</Window>
Added +15 -0
diff --git a/XCoderAv/Views/GpsWindow.axaml.cs b/XCoderAv/Views/GpsWindow.axaml.cs
new file mode 100644
index 0000000..5839433
--- /dev/null
+++ b/XCoderAv/Views/GpsWindow.axaml.cs
@@ -0,0 +1,15 @@
+using Avalonia.Controls;
+using XCoderAv.ViewModels;
+
+namespace XCoderAv.Views;
+
+/// <summary>GPS 辅助工具窗口</summary>
+public partial class GpsWindow : Window
+{
+    /// <summary>实例化 GPS 辅助工具窗口</summary>
+    public GpsWindow()
+    {
+        InitializeComponent();
+        DataContext = new GpsViewModel();
+    }
+}
Added +150 -0
diff --git a/XCoderAv/Views/IconToolWindow.axaml b/XCoderAv/Views/IconToolWindow.axaml
new file mode 100644
index 0000000..fb12be7
--- /dev/null
+++ b/XCoderAv/Views/IconToolWindow.axaml
@@ -0,0 +1,150 @@
+<Window xmlns="https://github.com/avaloniaui"
+        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" d:DesignWidth="1100" d:DesignHeight="750"
+        x:Class="XCoderAv.Views.IconToolWindow"
+        Title="图标水印处理工具" Width="1100" Height="750"
+        WindowStartupLocation="CenterOwner">
+    <Window.Styles>
+        <Style Selector="Border.GroupBorder">
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="Margin" Value="0,0,0,6"/>
+        </Style>
+        <Style Selector="TextBlock.SectionTitle">
+            <Setter Property="FontWeight" Value="Bold"/>
+            <Setter Property="Margin" Value="4,3"/>
+            <Setter Property="FontSize" Value="13"/>
+        </Style>
+        <Style Selector="TextBox.ConfigTextBox">
+            <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 Selector="TextBox.SmallNumeric">
+            <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 Selector="Button.ActionButton">
+            <Setter Property="Height" Value="30"/>
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Padding" Value="8,0"/>
+        </Style>
+    </Window.Styles>
+
+    <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 Classes="GroupBorder">
+                    <StackPanel>
+                        <TextBlock Text="图片加载" Classes="SectionTitle"/>
+                        <Button Content="选择图片" Command="{Binding LoadImageCommand}"
+                                Classes="ActionButton" Margin="4,2"/>
+                        <TextBlock Text="提示:支持拖放图片到右侧预览区" Margin="4,0,4,4"
+                                   Foreground="Gray" FontSize="11"/>
+                    </StackPanel>
+                </Border>
+
+                <!-- 水印设置 -->
+                <Border Classes="GroupBorder">
+                    <StackPanel>
+                        <TextBlock Text="水印设置" Classes="SectionTitle"/>
+
+                        <TextBlock Text="文字内容" Margin="4,2,4,0"/>
+                        <TextBox Text="{Binding WatermarkText}" Classes="ConfigTextBox"/>
+
+                        <Grid Margin="4,2">
+                            <Grid.ColumnDefinitions>
+                                <ColumnDefinition Width="Auto"/>
+                                <ColumnDefinition Width="*"/>
+                            </Grid.ColumnDefinitions>
+                            <Grid.RowDefinitions>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition Height="Auto"/>
+                            </Grid.RowDefinitions>
+
+                            <TextBlock Grid.Row="0" Grid.Column="0" Text="字体" VerticalAlignment="Center" Margin="0,0,4,0"/>
+                            <ComboBox Grid.Row="0" Grid.Column="1" ItemsSource="{Binding FontNames}"
+                                      SelectedItem="{Binding FontName}" Height="28"/>
+
+                            <TextBlock Grid.Row="1" Grid.Column="0" Text="大小" VerticalAlignment="Center" Margin="0,0,4,0"/>
+                            <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding FontSize}" Classes="SmallNumeric"/>
+
+                            <TextBlock Grid.Row="2" Grid.Column="0" Text="X位置" VerticalAlignment="Center" Margin="0,0,4,0"/>
+                            <TextBox Grid.Row="2" Grid.Column="1" Text="{Binding WatermarkX}" Classes="SmallNumeric"/>
+
+                            <TextBlock Grid.Row="3" Grid.Column="0" Text="Y位置" VerticalAlignment="Center" Margin="0,0,4,0"/>
+                            <TextBox Grid.Row="3" Grid.Column="1" Text="{Binding WatermarkY}" Classes="SmallNumeric"/>
+                        </Grid>
+
+                        <Button Content="应用水印" Command="{Binding MakeWaterCommand}"
+                                Classes="ActionButton" Margin="4,2"/>
+                    </StackPanel>
+                </Border>
+
+                <!-- ICO 设置 -->
+                <Border Classes="GroupBorder">
+                    <StackPanel>
+                        <TextBlock Text="ICO 图标" Classes="SectionTitle"/>
+                        <ItemsControl ItemsSource="{Binding IconSizes}">
+                            <ItemsControl.ItemTemplate>
+                                <DataTemplate>
+                                    <CheckBox Content="{Binding Name}" IsChecked="{Binding IsSelected}" Margin="4,2"/>
+                                </DataTemplate>
+                            </ItemsControl.ItemTemplate>
+                        </ItemsControl>
+                        <Button Content="生成 ICO" Command="{Binding MakeIcoCommand}"
+                                Classes="ActionButton" Margin="4,2"/>
+                    </StackPanel>
+                </Border>
+
+                <!-- 保存 -->
+                <Border Classes="GroupBorder">
+                    <StackPanel>
+                        <TextBlock Text="保存" Classes="SectionTitle"/>
+                        <Button Content="保存图片" Command="{Binding SaveImageCommand}"
+                                Classes="ActionButton" Margin="4,2"/>
+                    </StackPanel>
+                </Border>
+            </StackPanel>
+        </ScrollViewer>
+
+        <!-- ============ 右侧:预览区 ============ -->
+        <Border Grid.Column="1" BorderBrush="#D0D0D0" BorderThickness="1" Padding="4">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="*"/>
+                    <RowDefinition Height="Auto"/>
+                </Grid.RowDefinitions>
+
+                <TextBlock Text="预览" FontWeight="Bold" Margin="4,2"/>
+
+                <ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
+                    <Image Source="{Binding PreviewImage}" MaxWidth="800" MaxHeight="500" Stretch="Uniform"/>
+                </ScrollViewer>
+
+                <Border Grid.Row="2" BorderBrush="#D0D0D0" BorderThickness="0,1,0,0" Padding="4,2">
+                    <TextBlock Text="{Binding Status}"/>
+                </Border>
+            </Grid>
+        </Border>
+    </Grid>
+</Window>
Added +15 -0
diff --git a/XCoderAv/Views/IconToolWindow.axaml.cs b/XCoderAv/Views/IconToolWindow.axaml.cs
new file mode 100644
index 0000000..ca6af81
--- /dev/null
+++ b/XCoderAv/Views/IconToolWindow.axaml.cs
@@ -0,0 +1,15 @@
+using Avalonia.Controls;
+using XCoderAv.ViewModels;
+
+namespace XCoderAv.Views;
+
+/// <summary>图标水印处理工具窗口</summary>
+public partial class IconToolWindow : Window
+{
+    /// <summary>实例化图标水印处理工具窗口</summary>
+    public IconToolWindow()
+    {
+        InitializeComponent();
+        DataContext = new IconToolViewModel();
+    }
+}
Added +180 -0
diff --git a/XCoderAv/Views/MqttWindow.axaml b/XCoderAv/Views/MqttWindow.axaml
new file mode 100644
index 0000000..774a838
--- /dev/null
+++ b/XCoderAv/Views/MqttWindow.axaml
@@ -0,0 +1,180 @@
+<Window xmlns="https://github.com/avaloniaui"
+        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" d:DesignWidth="1100" d:DesignHeight="750"
+        x:Class="XCoderAv.Views.MqttWindow"
+        Title="MQTT 客户端" Width="1100" Height="750"
+        WindowStartupLocation="CenterOwner">
+    <Window.Styles>
+        <Style Selector="Border.GroupBorder">
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="Margin" Value="0,0,0,6"/>
+        </Style>
+        <Style Selector="TextBlock.SectionTitle">
+            <Setter Property="FontWeight" Value="Bold"/>
+            <Setter Property="Margin" Value="4,3"/>
+            <Setter Property="FontSize" Value="13"/>
+        </Style>
+        <Style Selector="TextBox.ConfigTextBox">
+            <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 Selector="Button.ActionButton">
+            <Setter Property="Height" Value="30"/>
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Padding" Value="8,0"/>
+        </Style>
+        <Style Selector="Button.SendButton">
+            <Setter Property="Height" Value="50"/>
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="FontSize" Value="14"/>
+            <Setter Property="FontWeight" Value="Bold"/>
+        </Style>
+    </Window.Styles>
+
+    <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 Classes="GroupBorder">
+                    <StackPanel>
+                        <TextBlock Text="连接配置" Classes="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="地址" VerticalAlignment="Center" Margin="4,2"/>
+                            <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Server}" Classes="ConfigTextBox"/>
+
+                            <TextBlock Grid.Row="1" Grid.Column="0" Text="标识" VerticalAlignment="Center" Margin="4,2"/>
+                            <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding ClientId}" Classes="ConfigTextBox"/>
+
+                            <TextBlock Grid.Row="2" Grid.Column="0" Text="用户" VerticalAlignment="Center" Margin="4,2"/>
+                            <TextBox Grid.Row="2" Grid.Column="1" Text="{Binding UserName}" Classes="ConfigTextBox"/>
+
+                            <TextBlock Grid.Row="3" Grid.Column="0" Text="密码" VerticalAlignment="Center" Margin="4,2"/>
+                            <TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Password}" Classes="ConfigTextBox"/>
+
+                            <Button Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2"
+                                    Content="{Binding ConnectButtonText}" Command="{Binding ToggleConnectCommand}"
+                                    Classes="ActionButton" Margin="4,4"/>
+                        </Grid>
+                    </StackPanel>
+                </Border>
+
+                <!-- 订阅配置 -->
+                <Border Classes="GroupBorder">
+                    <StackPanel>
+                        <TextBlock Text="订阅配置" Classes="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="主题" VerticalAlignment="Center" Margin="4,2"/>
+                            <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding SubscribeTopic}" Classes="ConfigTextBox"/>
+                            <ComboBox Grid.Row="0" Grid.Column="2" ItemsSource="{Binding QosOptions}"
+                                      SelectedIndex="{Binding SubscribeQos}" Width="60" Margin="2"/>
+
+                            <Button Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3"
+                                    Content="订阅" Command="{Binding SubscribeCommand}"
+                                    Classes="ActionButton" Margin="4,4"/>
+                        </Grid>
+                    </StackPanel>
+                </Border>
+            </StackPanel>
+        </ScrollViewer>
+
+        <!-- ============ 右侧:发布 + 日志 ============ -->
+        <Grid Grid.Column="1">
+            <Grid.RowDefinitions>
+                <RowDefinition Height="Auto"/>
+                <RowDefinition Height="*"/>
+            </Grid.RowDefinitions>
+
+            <!-- 发布区 -->
+            <Border Grid.Row="0" Classes="GroupBorder">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                        <RowDefinition Height="Auto"/>
+                    </Grid.RowDefinitions>
+
+                    <Grid Grid.Row="0">
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="50"/>
+                            <ColumnDefinition Width="*"/>
+                            <ColumnDefinition Width="Auto"/>
+                            <ColumnDefinition Width="Auto"/>
+                        </Grid.ColumnDefinitions>
+                        <TextBlock Grid.Column="0" Text="主题" VerticalAlignment="Center" Margin="4,2"/>
+                        <TextBox Grid.Column="1" Text="{Binding PublishTopic}" Classes="ConfigTextBox"/>
+                        <ComboBox Grid.Column="2" ItemsSource="{Binding QosOptions}"
+                                  SelectedIndex="{Binding PublishQos}" Width="60" Margin="2"/>
+                        <CheckBox Grid.Column="3" Content="保留" IsChecked="{Binding Retain}" VerticalAlignment="Center" Margin="4,0"/>
+                    </Grid>
+
+                    <TextBox Grid.Row="1" Text="{Binding PublishText}" AcceptsReturn="True"
+                             MinHeight="80" FontFamily="Consolas" Margin="2"/>
+
+                    <Button Grid.Row="2" Content="发布" Command="{Binding PublishCommand}"
+                            Classes="SendButton" Height="40" Margin="4,4"/>
+                </Grid>
+            </Border>
+
+            <!-- 日志区 -->
+            <Border Grid.Row="1" Classes="GroupBorder">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                    </Grid.RowDefinitions>
+                    <Grid>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="*"/>
+                            <ColumnDefinition Width="Auto"/>
+                        </Grid.ColumnDefinitions>
+                        <TextBlock Text="日志" FontWeight="Bold" Margin="4,2"/>
+                        <Button Grid.Column="1" Content="清空" Command="{Binding ClearLogCommand}"
+                                Height="26" Margin="2" Padding="8,0"/>
+                    </Grid>
+                    <TextBox Grid.Row="1" x:Name="txtLog" IsReadOnly="True"
+                             AcceptsReturn="True"
+                             FontFamily="Consolas" FontSize="12" Background="#1E1E1E" Foreground="#D4D4D4"
+                             Margin="2"/>
+                </Grid>
+            </Border>
+        </Grid>
+    </Grid>
+</Window>
Added +37 -0
diff --git a/XCoderAv/Views/MqttWindow.axaml.cs b/XCoderAv/Views/MqttWindow.axaml.cs
new file mode 100644
index 0000000..74f1e54
--- /dev/null
+++ b/XCoderAv/Views/MqttWindow.axaml.cs
@@ -0,0 +1,37 @@
+using Avalonia.Controls;
+using Avalonia.Threading;
+using XCoderAv.ViewModels;
+
+namespace XCoderAv.Views;
+
+/// <summary>MQTT 客户端窗口</summary>
+public partial class MqttWindow : Window
+{
+    /// <summary>实例化 MQTT 客户端窗口</summary>
+    public MqttWindow()
+    {
+        InitializeComponent();
+
+        var vm = new MqttViewModel();
+        vm.OnLog = OnLog;
+        DataContext = vm;
+    }
+
+    private void OnLog(String msg)
+    {
+        Dispatcher.UIThread.Post(() =>
+        {
+            if (msg == "__CLEAR__")
+            {
+                txtLog.Text = "";
+                return;
+            }
+
+            txtLog.Text += msg + "\n";
+            if (txtLog.Text.Length > 10000)
+                txtLog.Text = txtLog.Text[^5000..];
+
+            txtLog.CaretIndex = txtLog.Text.Length;
+        });
+    }
+}
Added +204 -0
diff --git a/XCoderAv/Views/NetworkWindow.axaml b/XCoderAv/Views/NetworkWindow.axaml
new file mode 100644
index 0000000..7944cfe
--- /dev/null
+++ b/XCoderAv/Views/NetworkWindow.axaml
@@ -0,0 +1,204 @@
+<Window xmlns="https://github.com/avaloniaui"
+        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:vm="clr-namespace:XCoderAv.ViewModels"
+        mc:Ignorable="d" d:DesignWidth="1100" d:DesignHeight="750"
+        x:Class="XCoderAv.Views.NetworkWindow"
+        Title="网络调试工具" Width="1100" Height="750"
+        WindowStartupLocation="CenterOwner">
+    <Window.Styles>
+        <Style Selector="Border.GroupBorder">
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="Margin" Value="0,0,0,6"/>
+        </Style>
+        <Style Selector="TextBlock.SectionTitle">
+            <Setter Property="FontWeight" Value="Bold"/>
+            <Setter Property="Margin" Value="4,3"/>
+            <Setter Property="FontSize" Value="13"/>
+        </Style>
+        <Style Selector="TextBox.ConfigTextBox">
+            <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 Selector="Button.ActionButton">
+            <Setter Property="Height" Value="30"/>
+            <Setter Property="Margin" Value="2"/>
+        </Style>
+        <Style Selector="Button.SendButton">
+            <Setter Property="Height" Value="50"/>
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="FontSize" Value="14"/>
+            <Setter Property="FontWeight" Value="Bold"/>
+        </Style>
+        <Style Selector="TextBox.SmallNumeric">
+            <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.Styles>
+
+    <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 Classes="GroupBorder">
+                    <StackPanel>
+                        <TextBlock Text="工作模式" Classes="SectionTitle"/>
+                        <ComboBox ItemsSource="{Binding Modes}" SelectedIndex="{Binding SelectedModeIndex}"
+                                  Margin="4,2" Height="30"/>
+                    </StackPanel>
+                </Border>
+
+                <!-- 连接配置 -->
+                <Border Classes="GroupBorder">
+                    <StackPanel>
+                        <TextBlock Text="连接配置" Classes="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="本地" VerticalAlignment="Center" Margin="4,2"/>
+                            <ComboBox Grid.Row="0" Grid.Column="1" ItemsSource="{Binding LocalAddresses}"
+                                      SelectedItem="{Binding LocalAddress}" Margin="2"/>
+
+                            <TextBlock Grid.Row="1" Grid.Column="0" Text="远程" VerticalAlignment="Center" Margin="4,2"/>
+                            <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding RemoteAddress}" Classes="ConfigTextBox"/>
+
+                            <TextBlock Grid.Row="2" Grid.Column="0" Text="端口" VerticalAlignment="Center" Margin="4,2"/>
+                            <TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Port}" Classes="ConfigTextBox"/>
+                        </Grid>
+
+                        <Button Content="{Binding ConnectButtonText}" Command="{Binding ToggleConnectCommand}"
+                                Classes="ActionButton" Margin="4,4"/>
+                    </StackPanel>
+                </Border>
+
+                <!-- 日志选项 -->
+                <Border Classes="GroupBorder">
+                    <StackPanel>
+                        <TextBlock Text="日志选项" Classes="SectionTitle"/>
+                        <CheckBox Content="业务日志" IsChecked="{Binding ShowLog}" Margin="4,2"/>
+                        <CheckBox Content="Socket日志" IsChecked="{Binding ShowSocketLog}" Margin="4,2"/>
+                        <CheckBox Content="显示发送" IsChecked="{Binding ShowSend}" Margin="4,2"/>
+                        <CheckBox Content="显示接收" IsChecked="{Binding ShowReceive}" Margin="4,2"/>
+                        <CheckBox Content="显示统计" IsChecked="{Binding ShowStat}" Margin="4,2"/>
+                        <CheckBox Content="接收字符串" IsChecked="{Binding ShowReceiveString}" Margin="4,2"/>
+                    </StackPanel>
+                </Border>
+            </StackPanel>
+        </ScrollViewer>
+
+        <!-- ============ 右侧:收发区 ============ -->
+        <Grid Grid.Column="1">
+            <Grid.RowDefinitions>
+                <RowDefinition Height="Auto"/>
+                <RowDefinition Height="Auto"/>
+                <RowDefinition Height="Auto"/>
+                <RowDefinition Height="*"/>
+            </Grid.RowDefinitions>
+
+            <!-- 状态栏 -->
+            <Border Grid.Row="0" BorderBrush="#D0D0D0" BorderThickness="0,0,0,1" Padding="4,2" Margin="0,0,0,4">
+                <TextBlock>
+                    <Run Text="状态:"/>
+                    <Run Text="{Binding ConnectButtonText}"/>
+                    <Run Text=" | 接收:"/>
+                    <Run Text="{Binding ReceivedBytes}"/>
+                    <Run Text=" | 发送:"/>
+                    <Run Text="{Binding SentBytes}"/>
+                    <Run Text=" | 会话:"/>
+                    <Run Text="{Binding SessionCount}"/>
+                </TextBlock>
+            </Border>
+
+            <!-- 发送区 -->
+            <Border Grid.Row="1" Classes="GroupBorder">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                    </Grid.RowDefinitions>
+
+                    <Grid>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="*"/>
+                            <ColumnDefinition Width="Auto"/>
+                            <ColumnDefinition Width="Auto"/>
+                            <ColumnDefinition Width="Auto"/>
+                            <ColumnDefinition Width="Auto"/>
+                        </Grid.ColumnDefinitions>
+
+                        <TextBox Grid.Column="0" Text="{Binding SendText}" AcceptsReturn="True"
+                                 MinHeight="60" FontFamily="Consolas" Margin="2"/>
+                        <Button Grid.Column="1" Content="发送" Command="{Binding SendCommand}"
+                                Classes="SendButton" Width="100"/>
+                        <StackPanel Grid.Column="2" Margin="4,0">
+                            <CheckBox Content="HEX发送" IsChecked="{Binding HexSend}" Margin="2"/>
+                            <StackPanel Orientation="Horizontal">
+                                <TextBlock Text="次数" VerticalAlignment="Center" Margin="2,0"/>
+                                <TextBox Text="{Binding SendTimes}" Classes="SmallNumeric"/>
+                            </StackPanel>
+                            <StackPanel Orientation="Horizontal">
+                                <TextBlock Text="间隔" VerticalAlignment="Center" Margin="2,0"/>
+                                <TextBox Text="{Binding SendSleep}" Classes="SmallNumeric"/>
+                            </StackPanel>
+                            <StackPanel Orientation="Horizontal">
+                                <TextBlock Text="线程" VerticalAlignment="Center" Margin="2,0"/>
+                                <TextBox Text="{Binding SendThreads}" Classes="SmallNumeric"/>
+                            </StackPanel>
+                        </StackPanel>
+                    </Grid>
+                </Grid>
+            </Border>
+
+            <!-- 日志区 -->
+            <Border Grid.Row="3" Classes="GroupBorder">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                    </Grid.RowDefinitions>
+                    <Grid>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="*"/>
+                            <ColumnDefinition Width="Auto"/>
+                        </Grid.ColumnDefinitions>
+                        <TextBlock Text="日志" FontWeight="Bold" Margin="4,2"/>
+                        <Button Grid.Column="1" Content="清空" Command="{Binding ClearReceiveLogCommand}"
+                                Height="26" Margin="2" Padding="8,0"/>
+                    </Grid>
+                    <TextBox Grid.Row="2" x:Name="txtLog" IsReadOnly="True"
+                             AcceptsReturn="True"
+                             FontFamily="Consolas" FontSize="12" Background="#1E1E1E" Foreground="#D4D4D4"
+                             Margin="2"/>
+                </Grid>
+            </Border>
+        </Grid>
+    </Grid>
+</Window>
Added +37 -0
diff --git a/XCoderAv/Views/NetworkWindow.axaml.cs b/XCoderAv/Views/NetworkWindow.axaml.cs
new file mode 100644
index 0000000..5f5b421
--- /dev/null
+++ b/XCoderAv/Views/NetworkWindow.axaml.cs
@@ -0,0 +1,37 @@
+using Avalonia.Controls;
+using Avalonia.Threading;
+using XCoderAv.ViewModels;
+
+namespace XCoderAv.Views;
+
+/// <summary>网络调试工具窗口</summary>
+public partial class NetworkWindow : Window
+{
+    /// <summary>实例化网络调试工具窗口</summary>
+    public NetworkWindow()
+    {
+        InitializeComponent();
+
+        var vm = new NetworkViewModel();
+        vm.OnLog = OnLog;
+        DataContext = vm;
+    }
+
+    private void OnLog(String msg)
+    {
+        Dispatcher.UIThread.Post(() =>
+        {
+            if (msg == "__CLEAR__")
+            {
+                txtLog.Text = "";
+                return;
+            }
+
+            txtLog.Text += msg + "\n";
+            if (txtLog.Text.Length > 10000)
+                txtLog.Text = txtLog.Text[^5000..];
+
+            txtLog.CaretIndex = txtLog.Text.Length;
+        });
+    }
+}
Added +220 -0
diff --git a/XCoderAv/Views/RegexWindow.axaml b/XCoderAv/Views/RegexWindow.axaml
new file mode 100644
index 0000000..49fe53e
--- /dev/null
+++ b/XCoderAv/Views/RegexWindow.axaml
@@ -0,0 +1,220 @@
+<Window xmlns="https://github.com/avaloniaui"
+        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:vm="clr-namespace:XCoderAv.ViewModels"
+        xmlns:models="clr-namespace:XCoderAv.Models"
+        mc:Ignorable="d" d:DesignWidth="1100" d:DesignHeight="700"
+        x:Class="XCoderAv.Views.RegexWindow"
+        Title="正则表达式" Width="1100" Height="700"
+        WindowStartupLocation="CenterOwner">
+    <Window.Styles>
+        <Style Selector="TextBox.CodeTextBox">
+            <Setter Property="FontFamily" Value="Consolas"/>
+            <Setter Property="FontSize" Value="14"/>
+            <Setter Property="AcceptsReturn" Value="True"/>
+            <Setter Property="AcceptsTab" Value="True"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+        </Style>
+        <Style Selector="ListBox.ResultListView">
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="Margin" Value="2"/>
+        </Style>
+    </Window.Styles>
+
+    <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}"
+                         Classes="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="*"/>
+            </Grid.ColumnDefinitions>
+
+            <CheckBox Grid.Column="0" Content="忽略大小写" IsChecked="{Binding IgnoreCase}" Margin="4,0,8,0"/>
+            <CheckBox Grid.Column="1" Content="多行模式" IsChecked="{Binding Multiline}" Margin="0,0,8,0"/>
+            <CheckBox Grid.Column="2" Content="单行模式" IsChecked="{Binding Singleline}" Margin="0,0,8,0"/>
+            <CheckBox Grid.Column="3" Content="忽略空白" IsChecked="{Binding IgnorePatternWhitespace}" Margin="0,0,8,0"/>
+
+            <RadioButton Grid.Column="4" Content="匹配" IsChecked="{Binding IsMatchMode}" Margin="8,0,4,0" VerticalAlignment="Center"/>
+            <RadioButton Grid.Column="5" Content="替换" IsChecked="{Binding IsReplaceMode}" Margin="4,0,8,0" VerticalAlignment="Center"/>
+
+            <Button Grid.Column="6" 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"
+                IsVisible="{Binding IsReplaceMode}">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+                <TextBlock Text="替换为" FontWeight="Bold" Margin="4,2"/>
+                <TextBox Grid.Row="1" Text="{Binding Replacement}"
+                         Classes="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" Text="{Binding SourceText}"
+                         Classes="CodeTextBox" Background="#FFFFF0"/>
+            </Grid>
+        </Border>
+
+        <!-- 第5行:匹配结果 -->
+        <Grid Grid.Row="4" Margin="0">
+            <Grid.ColumnDefinitions>
+                <ColumnDefinition Width="*"/>
+                <ColumnDefinition Width="4"/>
+                <ColumnDefinition Width="*"/>
+            </Grid.ColumnDefinitions>
+
+            <!-- 匹配列表 -->
+            <Grid Grid.Column="0">
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+                <TextBlock Text="匹配结果" FontWeight="Bold" Margin="4,2"/>
+                <ListBox Grid.Row="1" ItemsSource="{Binding Matches}"
+                         SelectedItem="{Binding SelectedMatch}" Classes="ResultListView">
+                    <ListBox.ItemTemplate>
+                        <DataTemplate>
+                            <Grid>
+                                <Grid.ColumnDefinitions>
+                                    <ColumnDefinition Width="Auto"/>
+                                    <ColumnDefinition Width="*"/>
+                                    <ColumnDefinition Width="Auto"/>
+                                </Grid.ColumnDefinitions>
+                                <TextBlock Grid.Column="0" Text="{Binding Index}" Width="30" FontWeight="Bold"/>
+                                <TextBlock Grid.Column="1" Text="{Binding Value}" FontFamily="Consolas"/>
+                                <TextBlock Grid.Column="2" Text="{Binding Location}" Foreground="Gray"/>
+                            </Grid>
+                        </DataTemplate>
+                    </ListBox.ItemTemplate>
+                </ListBox>
+            </Grid>
+
+            <!-- 分组 + 捕获 -->
+            <Grid Grid.Column="2">
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="*"/>
+                    <RowDefinition Height="4"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+
+                <Grid Grid.Row="0">
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                    </Grid.RowDefinitions>
+                    <TextBlock Text="分组" FontWeight="Bold" Margin="4,2"/>
+                    <ListBox Grid.Row="1" ItemsSource="{Binding Groups}"
+                             SelectedItem="{Binding SelectedGroup}" Classes="ResultListView">
+                        <ListBox.ItemTemplate>
+                            <DataTemplate>
+                                <Grid>
+                                    <Grid.ColumnDefinitions>
+                                        <ColumnDefinition Width="Auto"/>
+                                        <ColumnDefinition Width="*"/>
+                                        <ColumnDefinition Width="Auto"/>
+                                    </Grid.ColumnDefinitions>
+                                    <TextBlock Grid.Column="0" Text="{Binding Name}" Width="40" FontWeight="Bold"/>
+                                    <TextBlock Grid.Column="1" Text="{Binding Value}" FontFamily="Consolas"/>
+                                    <TextBlock Grid.Column="2" Text="{Binding Location}" Foreground="Gray"/>
+                                </Grid>
+                            </DataTemplate>
+                        </ListBox.ItemTemplate>
+                    </ListBox>
+                </Grid>
+
+                <Grid Grid.Row="2">
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                    </Grid.RowDefinitions>
+                    <TextBlock Text="捕获" FontWeight="Bold" Margin="4,2"/>
+                    <ListBox Grid.Row="1" ItemsSource="{Binding Captures}"
+                             SelectedItem="{Binding SelectedCapture}" Classes="ResultListView">
+                        <ListBox.ItemTemplate>
+                            <DataTemplate>
+                                <Grid>
+                                    <Grid.ColumnDefinitions>
+                                        <ColumnDefinition Width="Auto"/>
+                                        <ColumnDefinition Width="*"/>
+                                        <ColumnDefinition Width="Auto"/>
+                                    </Grid.ColumnDefinitions>
+                                    <TextBlock Grid.Column="0" Text="{Binding Index}" Width="30" FontWeight="Bold"/>
+                                    <TextBlock Grid.Column="1" Text="{Binding Value}" FontFamily="Consolas"/>
+                                    <TextBlock Grid.Column="2" Text="{Binding Location}" Foreground="Gray"/>
+                                </Grid>
+                            </DataTemplate>
+                        </ListBox.ItemTemplate>
+                    </ListBox>
+                </Grid>
+            </Grid>
+        </Grid>
+
+        <!-- 第6行:状态栏 -->
+        <Border Grid.Row="5" BorderBrush="#D0D0D0" BorderThickness="0,1,0,0" Padding="4,2" Margin="0,4,0,0">
+            <TextBlock Text="{Binding Status}"/>
+        </Border>
+
+        <!-- 第7行:批量替换面板 -->
+        <Border Grid.Row="6" BorderBrush="#D0D0D0" BorderThickness="1" Background="#E8E8FF" Margin="0,4,0,0"
+                IsVisible="{Binding IsReplaceMode}">
+            <Grid>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="Auto"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="Auto"/>
+                    <ColumnDefinition Width="Auto"/>
+                </Grid.ColumnDefinitions>
+
+                <TextBlock Grid.Column="0" Text="目录:" VerticalAlignment="Center" Margin="4,2"/>
+                <TextBox Grid.Column="1" Text="{Binding DirectoryPath}" Margin="2" Height="26"/>
+                <TextBox Grid.Column="2" Text="{Binding FileFilter}" Width="80" Margin="2" Height="26"/>
+                <Button Grid.Column="3" Content="批量替换" Command="{Binding BatchReplaceCommand}"
+                        Height="26" Margin="2" Padding="8,0"/>
+            </Grid>
+        </Border>
+    </Grid>
+</Window>
Added +15 -0
diff --git a/XCoderAv/Views/RegexWindow.axaml.cs b/XCoderAv/Views/RegexWindow.axaml.cs
new file mode 100644
index 0000000..42c2e12
--- /dev/null
+++ b/XCoderAv/Views/RegexWindow.axaml.cs
@@ -0,0 +1,15 @@
+using Avalonia.Controls;
+using XCoderAv.ViewModels;
+
+namespace XCoderAv.Views;
+
+/// <summary>正则表达式工具窗口</summary>
+public partial class RegexWindow : Window
+{
+    /// <summary>实例化正则表达式工具窗口</summary>
+    public RegexWindow()
+    {
+        InitializeComponent();
+        DataContext = new RegexViewModel();
+    }
+}
Added +164 -0
diff --git a/XCoderAv/Views/SecurityWindow.axaml b/XCoderAv/Views/SecurityWindow.axaml
new file mode 100644
index 0000000..77f30ca
--- /dev/null
+++ b/XCoderAv/Views/SecurityWindow.axaml
@@ -0,0 +1,164 @@
+<Window xmlns="https://github.com/avaloniaui"
+        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" d:DesignWidth="1100" d:DesignHeight="750"
+        x:Class="XCoderAv.Views.SecurityWindow"
+        Title="加密解密" Width="1100" Height="750"
+        WindowStartupLocation="CenterOwner">
+    <Window.Styles>
+        <Style Selector="Button.FuncButton">
+            <Setter Property="Width" Value="110"/>
+            <Setter Property="Height" Value="46"/>
+            <Setter Property="Margin" Value="3"/>
+            <Setter Property="FontSize" Value="13"/>
+        </Style>
+        <Style Selector="TextBox.CodeTextBox">
+            <Setter Property="FontFamily" Value="Consolas"/>
+            <Setter Property="FontSize" Value="13"/>
+            <Setter Property="AcceptsReturn" Value="True"/>
+            <Setter Property="AcceptsTab" Value="True"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="TextWrapping" Value="Wrap"/>
+        </Style>
+        <Style Selector="Border.GroupBorder">
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="Margin" Value="0,0,0,4"/>
+        </Style>
+    </Window.Styles>
+
+    <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编码" Classes="FuncButton" Command="{Binding HexEncodeCommand}"/>
+                    <Button Content="HEX解码" Classes="FuncButton" Command="{Binding HexDecodeCommand}"/>
+                    <Button Content="Base64编码" Classes="FuncButton" Command="{Binding Base64EncodeCommand}"/>
+                    <Button Content="Base64解码" Classes="FuncButton" Command="{Binding Base64DecodeCommand}"/>
+                    <Button Content="MD5_32" Classes="FuncButton" Command="{Binding MD5_32Command}"/>
+                    <Button Content="MD5_16" Classes="FuncButton" Command="{Binding MD5_16Command}"/>
+                    <Button Content="SHA1" Classes="FuncButton" Command="{Binding SHA1Command}"/>
+                    <Button Content="SHA256" Classes="FuncButton" Command="{Binding SHA256Command}"/>
+                    <Button Content="SHA384" Classes="FuncButton" Command="{Binding SHA384Command}"/>
+                    <Button Content="SHA512" Classes="FuncButton" Command="{Binding SHA512Command}"/>
+                    <Button Content="CRC_32" Classes="FuncButton" Command="{Binding CRC_32Command}"/>
+                    <Button Content="CRC_16" Classes="FuncButton" Command="{Binding CRC_16Command}"/>
+                    <Button Content="RSA加密" Classes="FuncButton" Command="{Binding RSAEncryptCommand}"/>
+                    <Button Content="RSA解密" Classes="FuncButton" Command="{Binding RSADecryptCommand}"/>
+                    <Button Content="DSA签名" Classes="FuncButton" Command="{Binding DSASignCommand}"/>
+                    <Button Content="DSA验证" Classes="FuncButton" Command="{Binding DSAVerifyCommand}"/>
+                    <Button Content="Url编码" Classes="FuncButton" Command="{Binding UrlEncodeCommand}"/>
+                    <Button Content="Url解码" Classes="FuncButton" Command="{Binding UrlDecodeCommand}"/>
+                    <Button Content="Html编码" Classes="FuncButton" Command="{Binding HtmlEncodeCommand}"/>
+                    <Button Content="Html解码" Classes="FuncButton" Command="{Binding HtmlDecodeCommand}"/>
+                    <Button Content="时间戳" Classes="FuncButton" Command="{Binding TimestampCommand}"/>
+                    <Button Content="机器信息" Classes="FuncButton" Command="{Binding ComputerInfoCommand}"/>
+                    <Button Content="雪花Id" Classes="FuncButton" Command="{Binding SnowflakeCommand}"/>
+                    <Button Content="JWT令牌" Classes="FuncButton" Command="{Binding JwtTokenCommand}"/>
+                    <Button Content="版本号" Classes="FuncButton" Command="{Binding VersionCommand}"/>
+                    <Button Content="TraceId" Classes="FuncButton" Command="{Binding TraceIdCommand}"/>
+                </WrapPanel>
+            </ScrollViewer>
+        </Border>
+
+        <!-- 右侧:输入输出区 -->
+        <Grid Grid.Column="1">
+            <Grid.RowDefinitions>
+                <RowDefinition Height="Auto"/>
+                <RowDefinition Height="Auto"/>
+                <RowDefinition Height="Auto"/>
+                <RowDefinition Height="*"/>
+                <RowDefinition Height="Auto"/>
+            </Grid.RowDefinitions>
+
+            <!-- 原文输入 -->
+            <Border Grid.Row="0" Classes="GroupBorder">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                    </Grid.RowDefinitions>
+                    <Grid>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="Auto"/>
+                            <ColumnDefinition Width="Auto"/>
+                            <ColumnDefinition Width="Auto"/>
+                            <ColumnDefinition Width="*"/>
+                        </Grid.ColumnDefinitions>
+                        <TextBlock Grid.Column="0" Text="原文" FontWeight="Bold" Margin="4,2"/>
+                        <RadioButton Grid.Column="1" Content="字符串" IsChecked="{Binding IsSourceString}" Margin="4,0"/>
+                        <RadioButton Grid.Column="2" Content="HEX" IsChecked="{Binding IsSourceHex}" Margin="4,0"
+                                     IsEnabled="{Binding SourceHexEnabled}"/>
+                        <RadioButton Grid.Column="3" Content="Base64" IsChecked="{Binding IsSourceBase64}" Margin="4,0"
+                                     IsEnabled="{Binding SourceBase64Enabled}"/>
+                    </Grid>
+                    <TextBox Grid.Row="2" Text="{Binding SourceText}" Classes="CodeTextBox" MinHeight="60"/>
+                </Grid>
+            </Border>
+
+            <!-- 密码输入 -->
+            <Border Grid.Row="1" Classes="GroupBorder">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                    </Grid.RowDefinitions>
+                    <Grid>
+                        <Grid.ColumnDefinitions>
+                            <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"/>
+                        <RadioButton Grid.Column="1" Content="字符串" IsChecked="{Binding IsPassString}" Margin="4,0"/>
+                        <RadioButton Grid.Column="2" Content="HEX" IsChecked="{Binding IsPassHex}" Margin="4,0"/>
+                        <RadioButton Grid.Column="3" Content="Base64" IsChecked="{Binding IsPassBase64}" Margin="4,0"/>
+                        <Button Grid.Column="4" Content="上下互换" Command="{Binding ExchangeCommand}"
+                                Height="26" Margin="2" Padding="8,0"/>
+                    </Grid>
+                    <TextBox Grid.Row="2" Text="{Binding PassText}" Classes="CodeTextBox" MinHeight="40"/>
+                </Grid>
+            </Border>
+
+            <!-- 输出格式 -->
+            <Border Grid.Row="2" Classes="GroupBorder" Padding="4">
+                <StackPanel Orientation="Horizontal">
+                    <TextBlock Text="输出格式" FontWeight="Bold" VerticalAlignment="Center" Margin="4,0"/>
+                    <CheckBox Content="字符串" IsChecked="{Binding IsResultString}" Margin="8,0"/>
+                    <CheckBox Content="HEX" IsChecked="{Binding IsResultHex}" Margin="8,0"/>
+                    <CheckBox Content="Base64" IsChecked="{Binding IsResultBase64}" Margin="8,0"/>
+                </StackPanel>
+            </Border>
+
+            <!-- 结果输出 -->
+            <Border Grid.Row="3" Classes="GroupBorder">
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                    </Grid.RowDefinitions>
+                    <TextBlock Text="结果" FontWeight="Bold" Margin="4,2"/>
+                    <TextBox Grid.Row="1" Text="{Binding ResultText}" Classes="CodeTextBox"
+                             IsReadOnly="True" Background="#FFFFF0"/>
+                </Grid>
+            </Border>
+
+            <!-- 状态栏 -->
+            <Border Grid.Row="4" BorderBrush="#D0D0D0" BorderThickness="0,1,0,0" Padding="4,2">
+                <TextBlock Text="提示:RSA加密时可自动生成密钥对"/>
+            </Border>
+        </Grid>
+    </Grid>
+</Window>
Added +15 -0
diff --git a/XCoderAv/Views/SecurityWindow.axaml.cs b/XCoderAv/Views/SecurityWindow.axaml.cs
new file mode 100644
index 0000000..39a9acc
--- /dev/null
+++ b/XCoderAv/Views/SecurityWindow.axaml.cs
@@ -0,0 +1,15 @@
+using Avalonia.Controls;
+using XCoderAv.ViewModels;
+
+namespace XCoderAv.Views;
+
+/// <summary>加密解密工具窗口</summary>
+public partial class SecurityWindow : Window
+{
+    /// <summary>实例化加密解密工具窗口</summary>
+    public SecurityWindow()
+    {
+        InitializeComponent();
+        DataContext = new SecurityViewModel();
+    }
+}
Added +112 -0
diff --git a/XCoderAv/Views/SpeechWindow.axaml b/XCoderAv/Views/SpeechWindow.axaml
new file mode 100644
index 0000000..1770ae2
--- /dev/null
+++ b/XCoderAv/Views/SpeechWindow.axaml
@@ -0,0 +1,112 @@
+<Window xmlns="https://github.com/avaloniaui"
+        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" d:DesignWidth="700" d:DesignHeight="500"
+        x:Class="XCoderAv.Views.SpeechWindow"
+        Title="语音助手" Width="700" Height="500"
+        WindowStartupLocation="CenterOwner">
+    <Window.Styles>
+        <Style Selector="Border.GroupBorder">
+            <Setter Property="BorderBrush" Value="#D0D0D0"/>
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="Margin" Value="0,0,0,6"/>
+        </Style>
+        <Style Selector="TextBlock.SectionTitle">
+            <Setter Property="FontWeight" Value="Bold"/>
+            <Setter Property="Margin" Value="4,3"/>
+            <Setter Property="FontSize" Value="13"/>
+        </Style>
+        <Style Selector="Button.ActionButton">
+            <Setter Property="Height" Value="34"/>
+            <Setter Property="Margin" Value="2"/>
+            <Setter Property="Padding" Value="12,0"/>
+            <Setter Property="FontSize" Value="13"/>
+        </Style>
+    </Window.Styles>
+
+    <Grid Margin="8">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="*"/>
+            <RowDefinition Height="Auto"/>
+        </Grid.RowDefinitions>
+
+        <!-- 语音设置 -->
+        <Border Grid.Row="0" Classes="GroupBorder">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="Auto"/>
+                </Grid.RowDefinitions>
+
+                <TextBlock Text="语音设置" Classes="SectionTitle"/>
+
+                <Grid Grid.Row="1" Margin="4,0,4,2">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="Auto"/>
+                        <ColumnDefinition Width="*"/>
+                    </Grid.ColumnDefinitions>
+
+                    <TextBlock Grid.Column="0" Text="语音角色" VerticalAlignment="Center" 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="音量" VerticalAlignment="Center" 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}" 
+                               VerticalAlignment="Center" Width="40" TextAlignment="Center"/>
+
+                    <TextBlock Grid.Column="3" Text="语速" VerticalAlignment="Center" Width="40"/>
+                    <Slider Grid.Column="4" Minimum="-10" Maximum="10"
+                            Value="{Binding Rate}" Margin="4,0"
+                            TickFrequency="1" IsSnapToTickEnabled="True"
+                            VerticalAlignment="Center"/>
+                    <TextBlock Grid.Column="5" Text="{Binding Rate}" 
+                               VerticalAlignment="Center" Width="30" TextAlignment="Center"/>
+                </Grid>
+            </Grid>
+        </Border>
+
+        <!-- 朗读内容 -->
+        <Border Grid.Row="1" Classes="GroupBorder">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+                <TextBlock Text="朗读内容" Classes="SectionTitle"/>
+                <TextBox Grid.Row="1" Text="{Binding SpeakText}" AcceptsReturn="True"
+                         MinHeight="60" FontSize="14" Margin="4,0,4,4"/>
+            </Grid>
+        </Border>
+
+        <!-- 操作按钮 -->
+        <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center"
+                    VerticalAlignment="Center" Spacing="10">
+            <Button Content="朗读" Command="{Binding SpeakCommand}" Classes="ActionButton" Width="100"/>
+            <Button Content="停止" Command="{Binding StopCommand}" Classes="ActionButton" Width="100"/>
+            <Button Content="保存 WAV" Command="{Binding SaveWavCommand}" Classes="ActionButton" Width="100"/>
+        </StackPanel>
+
+        <!-- 状态栏 -->
+        <Border Grid.Row="3" BorderBrush="#D0D0D0" BorderThickness="0,1,0,0" Padding="4,2">
+            <TextBlock Text="{Binding Status}"/>
+        </Border>
+    </Grid>
+</Window>
Added +15 -0
diff --git a/XCoderAv/Views/SpeechWindow.axaml.cs b/XCoderAv/Views/SpeechWindow.axaml.cs
new file mode 100644
index 0000000..1c0a235
--- /dev/null
+++ b/XCoderAv/Views/SpeechWindow.axaml.cs
@@ -0,0 +1,15 @@
+using Avalonia.Controls;
+using XCoderAv.ViewModels;
+
+namespace XCoderAv.Views;
+
+/// <summary>语音助手窗口</summary>
+public partial class SpeechWindow : Window
+{
+    /// <summary>实例化语音助手窗口</summary>
+    public SpeechWindow()
+    {
+        InitializeComponent();
+        DataContext = new SpeechViewModel();
+    }
+}
Modified +32 -30
diff --git a/XCoderAv/XCoderAv.csproj b/XCoderAv/XCoderAv.csproj
index a9f0206..11ea373 100644
--- a/XCoderAv/XCoderAv.csproj
+++ b/XCoderAv/XCoderAv.csproj
@@ -1,41 +1,43 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <OutputType>Exe</OutputType>
-    <TargetFramework>netcoreapp2.1</TargetFramework>
-    <RootNamespace>XCoder</RootNamespace>
-    <AssemblyName>XCoder</AssemblyName>
-    <AssemblyTitle>新生命码神工具</AssemblyTitle>
-    <Description>众多开发者工具</Description>
+    <TargetFramework>net8.0</TargetFramework>
+    <RootNamespace>XCoderAv</RootNamespace>
+    <AssemblyName>XCoderAv</AssemblyName>
+    <AssemblyTitle>新生命码神工具(跨平台)</AssemblyTitle>
+    <Description>码神工具。代码生成、网络工具、API工具、串口工具、正则工具、图标工具、加解密工具、地图接口</Description>
     <Company>新生命开发团队</Company>
     <Copyright>©2002-2026 新生命开发团队</Copyright>
-    <Version>8.0.2019.1007</Version>
-    <FileVersion>8.0.2019.1007</FileVersion>
-    <AssemblyVersion>8.0.*</AssemblyVersion>
+    <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>
-    <ApplicationIcon>..\XCoder\leaf.ico</ApplicationIcon>
-    <OutputPath>..\..\XCoder\</OutputPath>
-    <DebugType>pdbonly</DebugType>
-    <Optimize>true</Optimize>
-    <DefineConstants>TRACE;NC30;__CORE__</DefineConstants>
+    <Nullable>enable</Nullable>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <LangVersion>latest</LangVersion>
+    <OutputPath>..\Bin\XCoderAv</OutputPath>
+    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
   </PropertyGroup>
 
-  <PropertyGroup Condition="'$(Configuration)'=='Debug'">
-    <DefineConstants>$(DefineConstants);DEBUG</DefineConstants>
-    <DebugType>full</DebugType>
-    <Optimize>false</Optimize>
-  </PropertyGroup>
-  <ItemGroup>
-    <Compile Update="**\*.xaml.cs">
-      <DependentUpon>%(Filename)</DependentUpon>
-    </Compile>
-    <AvaloniaResource Include="**\*.xaml">
-      <SubType>Designer</SubType>
-    </AvaloniaResource>
-  </ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Avalonia" Version="0.8.2" />
-    <PackageReference Include="Avalonia.Desktop" Version="0.8.2" />
-    <PackageReference Include="NewLife.Core" Version="8.4.2019.1007" />
-    <PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="1.68.0" />
+    <PackageReference Include="Avalonia" Version="11.2.3" />
+    <PackageReference Include="Avalonia.Desktop" Version="11.2.3" />
+    <PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.3" />
+    <PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.3" />
+    <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
+    <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="SSH.NET" Version="2025.1.0" />
+    <PackageReference Include="System.IO.Ports" Version="10.0.7" />
   </ItemGroup>
 </Project>