From 559890c482f5973f06aafdfec9550fee5ba3c7f2 Mon Sep 17 00:00:00 2001
From: wenxiangchao <wenxiangchao@ihengxin.com.cn>
Date: 星期一, 09 十一月 2026 17:02:15 +0800
Subject: [PATCH] feat: CAN 模拟器 TCP 化 + 多设备并发调试 + 后台生命周期管理

---
 Plugin.DeviceDebug/DeviceDebug.xaml.cs |  613 ++++++-------------------------------------------------
 1 files changed, 74 insertions(+), 539 deletions(-)

diff --git a/Plugin.DeviceDebug/DeviceDebug.xaml.cs b/Plugin.DeviceDebug/DeviceDebug.xaml.cs
index dc82505..f82274d 100644
--- a/Plugin.DeviceDebug/DeviceDebug.xaml.cs
+++ b/Plugin.DeviceDebug/DeviceDebug.xaml.cs
@@ -4,58 +4,41 @@
 using HMI.Core.Util;
 using HMI.UI.Util;
 using log4net;
-using Plugin.DeviceDebug.Entity;
 using Plugin.DeviceDebug.Model;
-using Plugin.DeviceDebug.Panels;
 using System;
+using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.IO;
 using System.Linq;
-using System.Threading;
 using System.Windows;
 using System.Windows.Controls;
-using System.Windows.Media;
 
 namespace Plugin.DeviceDebug
 {
     /// <summary>
-    /// DeviceDebug.xaml 鐨勪氦浜掗�昏緫
+    /// 纭欢璋冭瘯鎻掍欢锛堝3锛夛細瑁呴厤骞舵寔鏈夊叡浜熀纭�璁炬柦锛堜細璇濈鐞嗗櫒/璁惧鏈嶅姟/閰嶇疆搴撹矾寰勶級锛�
+    /// 浠� TabControl 鎵胯浇澶氫釜 <see cref="DeviceDebugTab"/>锛屾敮鎸佸悓鏃惰皟璇曞涓澶囷紝
+    /// 姣忎釜 Tab 鎷ユ湁鐙珛浼氳瘽涓庣淮鎶ょ绾︺�傝澶囦笅鎷変粎鍒楀嚭灏氭湭鎵撳紑鐨勮澶囥��
     /// </summary>
     [ObservableObject]
     public partial class DeviceDebug : SimpleMaskControl
     {
-        private static readonly ILog _log = LogManager.GetLogger(typeof(DeviceDebug));
+        private static readonly ILog log = LogManager.GetLogger(typeof(DeviceDebug));
         private DeviceDebugModel _debugModel;
         private DebugSessionManager _sessionManager;
 
-        // 鍩虹璁炬柦灞傛湇鍔�
-        private IDeviceManager? _deviceManager;
-        private IDeviceService? _deviceService;
-        private IResourceManager? _resourceManager;
+        private IDeviceManager _deviceManager;
+        private IDeviceService _deviceService;
+        private IResourceManager _resourceManager;
 
-        // 璁惧閰嶇疆鏁版嵁搴撹矾寰勶紙涓� DeviceConfig 鎻掍欢鍏变韩锛�
         private string _deviceConfigDbPath;
+
+        // 鍏ㄩ噺璁惧锛�"id [kind]"锛変笌宸叉墦寮�鏍囩鏄犲皠锛坉eviceId 鈫� TabItem锛�
+        private readonly List<string> _allDevices = new();
+        private readonly Dictionary<string, TabItem> _openTabs = new(StringComparer.OrdinalIgnoreCase);
 
         [ObservableProperty] private ObservableCollection<string> _deviceList = new();
         [ObservableProperty] private string _selectedDevice;
-        [ObservableProperty] private DebugSession _currentSession;
-        [ObservableProperty] private ObservableCollection<DebugOperation> _operationLogs = new();
-        [ObservableProperty] private string _connectionStateText = "鏈繛鎺�";
-        [ObservableProperty] private string _sessionStateText = "";
-        [ObservableProperty] private string _reservationStatusText = "";
-        [ObservableProperty] private string _currentDeviceId = "";
-        [ObservableProperty] private string _currentDeviceKind = "";
-        [ObservableProperty] private string _currentEffectiveVersion = "";
-        [ObservableProperty] private string _currentEndpoint = "";
-        [ObservableProperty] private string _currentDriverType = "";
-        [ObservableProperty] private Brush _connectionBrush = new SolidColorBrush(Colors.Gray);
-        [ObservableProperty] private Brush _sessionStateBrush = new SolidColorBrush(Colors.Gray);
-        [ObservableProperty] private bool _canEnterMaintenance;
-        [ObservableProperty] private bool _canExitMaintenance;
-        [ObservableProperty] private bool _hasDevice;
-
-        // 缂撳瓨褰撳墠閫変腑鐨勮澶囩被鍨�
-        private string _currentDeviceKindStr;
 
         public DeviceDebug()
         {
@@ -63,13 +46,10 @@
             InitializeComponent();
         }
 
-        /// <summary>
-        /// 寮哄埗鍒锋柊璁惧鍒楄〃锛堜緵闆嗘垚娴嬭瘯棰勭疆璁惧鍚庤皟鐢級
-        /// </summary>
         public void ForceRefreshForTest()
         {
             try { LoadDeviceList(); }
-            catch (Exception ex) { _log.Error("ForceRefreshForTest Error.", ex); }
+            catch (Exception ex) { log.Error("ForceRefreshForTest Error.", ex); }
         }
 
         public override void InitFinish()
@@ -77,21 +57,16 @@
             base.InitFinish();
             try
             {
-                // 鍒濆鍖栬皟璇曟暟鎹簱
                 _debugModel = new DeviceDebugModel();
                 string dbPath = Path.Combine(BaseFolder, "debug.db");
                 _debugModel.InitDatabase(dbPath);
 
-                // 浠� UtilContainer 鑾峰彇鍩虹璁炬柦鏈嶅姟
                 var container = new UtilContainer();
                 _deviceManager = container.GetUtil<IDeviceManager>("DeviceManager");
                 _deviceService = container.GetUtil<IDeviceService>("DeviceService");
                 _resourceManager = container.GetUtil<IResourceManager>("ResourceManager");
 
-                // 鍒涘缓浼氳瘽绠$悊鍣紙浼犲叆 ResourceManager锛�
                 _sessionManager = new DebugSessionManager(_debugModel, _resourceManager);
-
-                // 璁惧閰嶇疆鏁版嵁搴撹矾寰勶紙涓� DeviceConfig 鍏变韩锛�
                 _deviceConfigDbPath = Path.Combine(BaseFolder, "data.db");
 
                 log.Info("纭欢璋冭瘯妯″潡鍒濆鍖栧畬鎴�");
@@ -103,9 +78,14 @@
             }
         }
 
-        /// <summary>
-        /// 鍔犺浇璁惧鍒楄〃
-        /// </summary>
+        private void SimpleMaskControl_Loaded(object sender, RoutedEventArgs e)
+        {
+            LoadDeviceList();
+            Loaded -= SimpleMaskControl_Loaded;
+        }
+
+        private void RfhBtn_Click(object sender, RoutedEventArgs e) => LoadDeviceList();
+
         private void LoadDeviceList()
         {
             try
@@ -120,16 +100,12 @@
                 var sqliteHelper = new SqliteHelper(_deviceConfigDbPath);
                 var dt = sqliteHelper.GetDataTable("SELECT DeviceId, Kind FROM DeviceDefinition ORDER BY DeviceId");
 
-                var list = new ObservableCollection<string>();
+                _allDevices.Clear();
                 foreach (System.Data.DataRow row in dt.Rows)
-                {
-                    string deviceId = row["DeviceId"].ToString();
-                    string kind = row["Kind"].ToString();
-                    list.Add($"{deviceId} [{kind}]");
-                }
+                    _allDevices.Add($"{row["DeviceId"]} [{row["Kind"]}]");
 
-                DeviceList = list;
-                log.Info($"鍔犺浇璁惧鍒楄〃瀹屾垚锛屽叡 {list.Count} 涓澶�");
+                RefreshAvailableList();
+                log.Info($"鍔犺浇璁惧鍒楄〃瀹屾垚锛屽叡 {_allDevices.Count} 涓澶�");
             }
             catch (Exception ex)
             {
@@ -138,20 +114,23 @@
             }
         }
 
-        /// <summary>
-        /// 瑙f瀽璁惧閫夋嫨瀛楃涓诧紝鑾峰彇 DeviceId
-        /// </summary>
-        private string ParseDeviceId(string selection)
+        /// <summary>涓嬫媺浠呭垪鍑烘湭鎵撳紑鐨勮澶囷紙涓�浼氫竴璁惧锛夈��</summary>
+        private void RefreshAvailableList()
+        {
+            var avail = _allDevices
+                .Where(d => !_openTabs.ContainsKey(ParseDeviceId(d)))
+                .ToList();
+            DeviceList = new ObservableCollection<string>(avail);
+        }
+
+        private static string ParseDeviceId(string selection)
         {
             if (string.IsNullOrEmpty(selection)) return string.Empty;
             int idx = selection.IndexOf(" [");
             return idx > 0 ? selection.Substring(0, idx) : selection;
         }
 
-        /// <summary>
-        /// 瑙f瀽璁惧閫夋嫨瀛楃涓诧紝鑾峰彇 Kind
-        /// </summary>
-        private string ParseDeviceKind(string selection)
+        private static string ParseDeviceKind(string selection)
         {
             if (string.IsNullOrEmpty(selection)) return string.Empty;
             int start = selection.IndexOf(" [");
@@ -161,508 +140,64 @@
             return string.Empty;
         }
 
-        /// <summary>
-        /// 鍔犺浇璁惧璇︾粏淇℃伅
-        /// </summary>
-        private void LoadDeviceInfo(string deviceId)
-        {
-            try
-            {
-                if (!File.Exists(_deviceConfigDbPath)) return;
-
-                var sqliteHelper = new SqliteHelper(_deviceConfigDbPath);
-                var dt = sqliteHelper.GetDataTable(
-                    $"SELECT DeviceId, Kind, EffectiveVersion, Endpoint, DriverType FROM DeviceDefinition WHERE DeviceId = '{deviceId}'");
-
-                if (dt.Rows.Count > 0)
-                {
-                    var row = dt.Rows[0];
-                    CurrentDeviceId = row["DeviceId"].ToString();
-                    CurrentDeviceKind = row["Kind"].ToString();
-                    CurrentEffectiveVersion = row["EffectiveVersion"]?.ToString() ?? "";
-                    CurrentEndpoint = row["Endpoint"]?.ToString() ?? "";
-                    CurrentDriverType = row["DriverType"]?.ToString() ?? "";
-
-                    // 缂撳瓨璁惧绫诲瀷
-                    _currentDeviceKindStr = CurrentDeviceKind;
-                }
-            }
-            catch (Exception ex)
-            {
-                log.Error($"鍔犺浇璁惧淇℃伅澶辫触: {deviceId}", ex);
-            }
-        }
-
-        /// <summary>
-        /// 閫夋嫨璁惧鏃讹細鎵撳紑浼氳瘽锛圧eadOnly锛夛紝鍔犺浇瀵瑰簲闈㈡澘
-        /// </summary>
-        private void OnDeviceSelected()
+        private void AddDeviceBtn_Click(object sender, RoutedEventArgs e)
         {
             try
             {
                 if (string.IsNullOrEmpty(SelectedDevice))
                 {
-                    HasDevice = false;
-                    panelContainer.Content = new TextBlock
-                    {
-                        Text = "璇烽�夋嫨璁惧",
-                        HorizontalAlignment = HorizontalAlignment.Center,
-                        VerticalAlignment = VerticalAlignment.Center,
-                        FontSize = 16,
-                        Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#999"))
-                    };
+                    ToastHelper.Warning("璇峰厛鍦ㄨ澶囦笅鎷変腑閫夋嫨璁惧");
                     return;
                 }
-
                 string deviceId = ParseDeviceId(SelectedDevice);
                 string kind = ParseDeviceKind(SelectedDevice);
 
-                // 鍔犺浇璁惧璇︾粏淇℃伅
-                LoadDeviceInfo(deviceId);
-
-                HasDevice = true;
-
-                // 鎵撳紑浼氳瘽锛圧eadOnly锛�
-                CurrentSession = _sessionManager.OpenSession(deviceId, "", "operator", CurrentEffectiveVersion);
-
-                // 閫氳繃 DeviceManager 杩炴帴璁惧
-                if (_deviceManager != null)
+                // 宸叉墦寮�鍒欒仛鐒﹀叾 Tab锛堥槻寰℃�э細涓嬫媺鏈凡杩囨护锛�
+                if (_openTabs.TryGetValue(deviceId, out var existing))
                 {
-                    try
-                    {
-                        _deviceManager.ConnectAsync(deviceId, CancellationToken.None)
-                            .GetAwaiter().GetResult();
-                        ConnectionStateText = "宸茶繛鎺�";
-                        ConnectionBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4CAF50"));
-                    }
-                    catch (Exception ex)
-                    {
-                        log.Warn($"閫氳繃 DeviceManager 杩炴帴璁惧澶辫触: {ex.Message}");
-                        ConnectionStateText = "杩炴帴澶辫触";
-                        ConnectionBrush = new SolidColorBrush(Colors.OrangeRed);
-                    }
-                }
-
-                // 鍔犺浇鎿嶄綔鏃ュ織
-                LoadOperationLogs();
-
-                // 鏇存柊鎸夐挳鐘舵��
-                UpdateButtonStates();
-
-                // 鍔犺浇瀵瑰簲闈㈡澘
-                LoadPanel(kind);
-
-                log.Info($"宸查�夋嫨璁惧: {deviceId}, 绫诲瀷: {kind}");
-            }
-            catch (Exception ex)
-            {
-                log.Error("閫夋嫨璁惧澶辫触", ex);
-                ToastHelper.Error($"閫夋嫨璁惧澶辫触: {ex.Message}");
-            }
-        }
-
-        /// <summary>
-        /// 鏍规嵁璁惧绫诲瀷鍔犺浇瀵瑰簲鐨勮皟璇曢潰鏉�
-        /// </summary>
-        private void LoadPanel(string deviceKind)
-        {
-            try
-            {
-                UIElement panel = null;
-
-                switch (deviceKind)
-                {
-                    case "PowerSupply":
-                        var powerPanel = new PowerSupplyDebugPanel();
-                        powerPanel.IsMaintenanceMode = CurrentSession?.State == DebugSessionState.MaintenanceActive;
-                        powerPanel.OnCommandExecute += Panel_OnCommandExecute;
-                        panel = powerPanel;
-                        break;
-                    case "CanDevice":
-                        var canPanel = new CanDebugPanel
-                        {
-                            DeviceId = CurrentDeviceId,
-                            InitialBusName = ParseCanBusName(CurrentEndpoint),
-                            InitialNodeIndex = ParseCanNodeIndex(CurrentEndpoint),
-                            AutoConnectOnLoad = true
-                        };
-                        canPanel.IsMaintenanceMode = CurrentSession?.State == DebugSessionState.MaintenanceActive;
-                        canPanel.OnCommandExecute += Panel_OnCommandExecute;
-                        panel = canPanel;
-                        break;
-                    case "PlcDevice":
-                        var plcPanel = new PlcDebugPanel();
-                        plcPanel.IsMaintenanceMode = CurrentSession?.State == DebugSessionState.MaintenanceActive;
-                        plcPanel.OnCommandExecute += Panel_OnCommandExecute;
-                        panel = plcPanel;
-                        break;
-                    default:
-                        panel = new TextBlock
-                        {
-                            Text = $"涓嶆敮鎸佺殑璁惧绫诲瀷: {deviceKind}",
-                            HorizontalAlignment = HorizontalAlignment.Center,
-                            VerticalAlignment = VerticalAlignment.Center,
-                            FontSize = 14,
-                            Foreground = new SolidColorBrush(Colors.OrangeRed)
-                        };
-                        break;
-                }
-
-                panelContainer.Content = panel;
-            }
-            catch (Exception ex)
-            {
-                log.Error($"鍔犺浇璋冭瘯闈㈡澘澶辫触: {deviceKind}", ex);
-                ToastHelper.Error($"鍔犺浇闈㈡澘澶辫触: {ex.Message}");
-            }
-        }
-
-        /// <summary>
-        /// 闈㈡澘鍛戒护鎵ц浜嬩欢澶勭悊
-        /// </summary>
-        private void Panel_OnCommandExecute(object sender, DebugCommand command)
-        {
-            try
-            {
-                if (CurrentSession == null)
-                {
-                    ToastHelper.Warning("璇峰厛閫夋嫨璁惧");
-                    return;
-                }
-
-                // 楠岃瘉鍛戒护鏉冮檺
-                if (!_sessionManager.ValidateCommand(CurrentSession.SessionId, command))
-                {
-                    ToastHelper.Warning("褰撳墠浼氳瘽鐘舵�佷笉鍏佽鎵ц姝ゆ搷浣滐紝璇峰厛杩涘叆缁存姢妯″紡");
-                    return;
-                }
-
-                // 閫氳繃 DeviceService 鎵ц瀹為檯鍛戒护
-                if (_deviceService != null)
-                {
-                    ExecuteViaDeviceService(command);
-                }
-                else
-                {
-                    // 闄嶇骇妯″紡锛氬熀纭�璁炬柦鏈垵濮嬪寲鏃朵繚鐣欏師鏈夎涓�
-                    log.Warn("DeviceService 鏈垵濮嬪寲锛屼娇鐢ㄩ檷绾фā寮忚褰曞懡浠�");
-                    ExecuteFallback(command);
-                }
-            }
-            catch (Exception ex)
-            {
-                log.Error($"鎵ц鍛戒护澶辫触: {command.CommandType}", ex);
-                ToastHelper.Error($"鍛戒护鎵ц澶辫触: {ex.Message}");
-
-                // 璁板綍澶辫触鎿嶄綔
-                var operation = new DebugOperation
-                {
-                    SessionId = CurrentSession?.SessionId ?? "",
-                    CommandType = command.CommandType,
-                    Parameters = SerializeParameters(command),
-                    Outcome = "Failed",
-                    ErrorMessage = ex.Message,
-                    Timestamp = DateTime.Now
-                };
-                _debugModel.RecordOperation(operation);
-                OperationLogs.Insert(0, operation);
-            }
-        }
-
-        /// <summary>
-        /// 閫氳繃 DeviceService 鎵ц璁惧鍛戒护
-        /// </summary>
-        private void ExecuteViaDeviceService(DebugCommand command)
-        {
-            try
-            {
-                var ownerToken = new ResourceOwnerToken(
-                    OwnerType.Maintenance,
-                    CurrentSession.SessionId);
-
-                var deviceCommand = BuildDeviceCommand(command);
-
-                var result = _deviceService.ExecuteCommandAsync(
-                    command.DeviceId,
-                    deviceCommand,
-                    ownerToken,
-                    CancellationToken.None).GetAwaiter().GetResult();
-
-                var operation = new DebugOperation
-                {
-                    SessionId = CurrentSession.SessionId,
-                    CommandType = command.CommandType,
-                    Parameters = SerializeParameters(command),
-                    Outcome = result.Success ? "Success" : "Failed",
-                    DeviceReadback = result.Readback ?? "",
-                    ErrorCode = result.ErrorCode ?? "",
-                    ErrorMessage = result.ErrorMessage ?? "",
-                    Timestamp = DateTime.Now
-                };
-
-                _debugModel.RecordOperation(operation);
-                OperationLogs.Insert(0, operation);
-
-                if (result.Success)
-                    ToastHelper.Success($"鍛戒护 {command.CommandType} 鎵ц鎴愬姛");
-                else
-                    ToastHelper.Warning($"鍛戒护鎵ц澶辫触: {result.ErrorMessage}");
-
-                log.Info($"鎵ц鍛戒护: {command.CommandType}, 璁惧: {command.DeviceId}, 缁撴灉: {(result.Success ? "鎴愬姛" : "澶辫触")}");
-            }
-            catch (Exception ex)
-            {
-                log.Error($"DeviceService 鎵ц鍛戒护澶辫触: {ex.Message}", ex);
-                ToastHelper.Error($"璁惧閫氫俊澶辫触: {ex.Message}");
-
-                var operation = new DebugOperation
-                {
-                    SessionId = CurrentSession.SessionId,
-                    CommandType = command.CommandType,
-                    Parameters = SerializeParameters(command),
-                    Outcome = "Failed",
-                    ErrorMessage = ex.Message,
-                    Timestamp = DateTime.Now
-                };
-                _debugModel.RecordOperation(operation);
-                OperationLogs.Insert(0, operation);
-            }
-        }
-
-        /// <summary>
-        /// 闄嶇骇妯″紡锛氭棤 DeviceService 鏃剁殑鍚庡澶勭悊
-        /// </summary>
-        private void ExecuteFallback(DebugCommand command)
-        {
-            var operation = new DebugOperation
-            {
-                SessionId = CurrentSession.SessionId,
-                CommandType = command.CommandType,
-                Parameters = SerializeParameters(command),
-                Outcome = "Success",
-                Timestamp = DateTime.Now
-            };
-
-            _debugModel.RecordOperation(operation);
-            OperationLogs.Insert(0, operation);
-
-            ToastHelper.Success($"鍛戒护 {command.CommandType} 宸插彂閫侊紙闄嶇骇妯″紡锛�");
-            log.Info($"[闄嶇骇妯″紡] 鎵ц鍛戒护: {command.CommandType}, 璁惧: {command.DeviceId}");
-        }
-
-        /// <summary>
-        /// 鏍规嵁鍛戒护绫诲瀷鏋勫缓瀹為檯璁惧鍛戒护瀛楃涓�
-        /// </summary>
-        private string BuildDeviceCommand(DebugCommand command)
-        {
-            if (command is PowerSetCommand psc)
-            {
-                return $"VOLT {psc.Voltage};CURR {psc.CurrentLimit};OUTP {(psc.Output ? "ON" : "OFF")}";
-            }
-            else if (command is PowerMeasureCommand)
-            {
-                return "MEAS:ALL?";
-            }
-            else if (command is CanSendCommand csc)
-            {
-                return $"CAN:SEND {csc.Frame}";
-            }
-            else if (command is PlcWriteCommand pwc)
-            {
-                return $"WRITE:{pwc.IoAddress}:{(pwc.Value ? "TRUE" : "FALSE")}";
-            }
-            return command.CommandType;
-        }
-
-        /// <summary>
-        /// 搴忓垪鍖栧懡浠ゅ弬鏁颁负JSON瀛楃涓�
-        /// </summary>
-        private string SerializeParameters(DebugCommand command)
-        {
-            try
-            {
-                switch (command)
-                {
-                    case PowerSetCommand psc:
-                        return $"{{Voltage:{psc.Voltage},CurrentLimit:{psc.CurrentLimit},Output:{psc.Output}}}";
-                    case PowerMeasureCommand _:
-                        return "{}";
-                    case CanSendCommand csc:
-                        return $"{{Frame:{csc.Frame},Data:{BitConverter.ToString(csc.Data)}}}";
-                    case PlcWriteCommand pwc:
-                        return $"{{IoAddress:{pwc.IoAddress},Value:{pwc.Value}}}";
-                    default:
-                        return "{}";
-                }
-            }
-            catch
-            {
-                return "{}";
-            }
-        }
-
-        /// <summary>
-        /// 鍔犺浇鎿嶄綔鏃ュ織
-        /// </summary>
-        private void LoadOperationLogs()
-        {
-            try
-            {
-                if (CurrentSession == null) return;
-
-                var ops = _debugModel.GetOperationsBySession(CurrentSession.SessionId);
-                OperationLogs = new ObservableCollection<DebugOperation>(ops.Take(100));
-            }
-            catch (Exception ex)
-            {
-                log.Error("鍔犺浇鎿嶄綔鏃ュ織澶辫触", ex);
-            }
-        }
-
-        /// <summary>
-        /// 鏇存柊鎸夐挳鍚敤鐘舵��
-        /// </summary>
-        private void UpdateButtonStates()
-        {
-            if (CurrentSession == null)
-            {
-                CanEnterMaintenance = false;
-                CanExitMaintenance = false;
-                SessionStateText = "";
-                return;
-            }
-
-            var state = CurrentSession.State;
-            CanEnterMaintenance = state == DebugSessionState.ReadOnly;
-            CanExitMaintenance = state == DebugSessionState.MaintenanceActive;
-
-            // 鏇存柊浼氳瘽鐘舵�佹枃鏈拰棰滆壊
-            SessionStateText = state.ToString();
-            SessionStateBrush = state switch
-            {
-                DebugSessionState.ReadOnly => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#2196F3")),
-                DebugSessionState.MaintenanceActive => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4CAF50")),
-                DebugSessionState.Faulted => new SolidColorBrush(Colors.Red),
-                DebugSessionState.RecoveryRequired => new SolidColorBrush(Colors.Orange),
-                _ => new SolidColorBrush(Colors.Gray)
-            };
-
-            // 鏇存柊闈㈡澘鐨勭淮鎶ゆā寮�
-            if (panelContainer.Content is PowerSupplyDebugPanel powerPanel)
-                powerPanel.IsMaintenanceMode = state == DebugSessionState.MaintenanceActive;
-            else if (panelContainer.Content is CanDebugPanel canPanel)
-                canPanel.IsMaintenanceMode = state == DebugSessionState.MaintenanceActive;
-            else if (panelContainer.Content is PlcDebugPanel plcPanel)
-                plcPanel.IsMaintenanceMode = state == DebugSessionState.MaintenanceActive;
-        }
-
-        #region 浜嬩欢澶勭悊
-
-        private void SimpleMaskControl_Loaded(object sender, RoutedEventArgs e)
-        {
-            LoadDeviceList();
-            Loaded -= SimpleMaskControl_Loaded;
-        }
-
-        private void DeviceSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
-        {
-            OnDeviceSelected();
-        }
-
-        private void RfhBtn_Click(object sender, RoutedEventArgs e)
-        {
-            LoadDeviceList();
-        }
-
-        private void EnterMaintenanceBtn_Click(object sender, RoutedEventArgs e)
-        {
-            try
-            {
-                if (CurrentSession == null) return;
-
-                // 璇锋眰缁存姢妯″紡
-                if (_sessionManager.RequestMaintenance(CurrentSession.SessionId))
-                {
-                    // 婵�娲荤淮鎶ゆā寮�
-                    _sessionManager.ActivateMaintenance(CurrentSession.SessionId);
-                    UpdateButtonStates();
-
-                    // 閲嶆柊鍔犺浇闈㈡澘浠ユ洿鏂癐sMaintenanceMode
-                    LoadPanel(CurrentDeviceKind);
-
-                    ToastHelper.Success("宸茶繘鍏ョ淮鎶ゆ帶鍒舵ā寮�");
-                    log.Info($"浼氳瘽 {CurrentSession.SessionId} 杩涘叆缁存姢妯″紡");
-                }
-                else
-                {
-                    ToastHelper.Warning("鏃犳硶杩涘叆缁存姢妯″紡锛岃妫�鏌ヨ澶囩姸鎬�");
-                }
-            }
-            catch (Exception ex)
-            {
-                log.Error("杩涘叆缁存姢妯″紡澶辫触", ex);
-                ToastHelper.Error($"杩涘叆缁存姢妯″紡澶辫触: {ex.Message}");
-            }
-        }
-
-        private void ExitMaintenanceBtn_Click(object sender, RoutedEventArgs e)
-        {
-            try
-            {
-                if (CurrentSession == null) return;
-
-                if (MsgBoxHelper.WarnMessage(Window.GetWindow(this), "纭畾瑕侀��鍑虹淮鎶ゆā寮忓悧锛�") == MessageBoxResult.OK)
-                {
-                    _sessionManager.CloseSession(CurrentSession.SessionId);
-                    CurrentSession = null;
-                    UpdateButtonStates();
-
-                    // 娓呯┖闈㈡澘
-                    panelContainer.Content = new TextBlock
-                    {
-                        Text = "璇烽�夋嫨璁惧",
-                        HorizontalAlignment = HorizontalAlignment.Center,
-                        VerticalAlignment = VerticalAlignment.Center,
-                        FontSize = 16,
-                        Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#999"))
-                    };
-
-                    HasDevice = false;
+                    debugTabs.SelectedItem = existing;
                     SelectedDevice = null;
-
-                    ToastHelper.Success("宸查��鍑虹淮鎶ゆā寮�");
-                    log.Info("宸查��鍑虹淮鎶ゆā寮�");
+                    return;
                 }
+
+                var tabContent = new DeviceDebugTab();
+                tabContent.Initialize(_debugModel, _sessionManager, _deviceManager, _deviceService,
+                    _deviceConfigDbPath, deviceId, kind);
+
+                var tab = new TabItem { Header = deviceId, Content = tabContent, Tag = deviceId };
+                debugTabs.Items.Add(tab);
+                debugTabs.SelectedItem = tab;
+                _openTabs[deviceId] = tab;
+
+                RefreshAvailableList();
+                SelectedDevice = null;
+                log.Info($"宸插姞鍏ヨ皟璇曡澶�: {deviceId}锛堝綋鍓� {debugTabs.Items.Count} 涓爣绛撅級");
             }
             catch (Exception ex)
             {
-                log.Error("閫�鍑虹淮鎶ゆā寮忓け璐�", ex);
-                ToastHelper.Error($"閫�鍑虹淮鎶ゆā寮忓け璐�: {ex.Message}");
+                log.Error("鍔犲叆璋冭瘯璁惧澶辫触", ex);
+                ToastHelper.Error($"鍔犲叆璋冭瘯澶辫触: {ex.Message}");
             }
         }
 
-        /// <summary>
-        /// 浠� CAN Endpoint 瑙f瀽鎬荤嚎鍚嶇О锛堟牸寮�: 鎬荤嚎鍚�:鑺傜偣缂栧彿锛�
-        /// </summary>
-        private string ParseCanBusName(string endpoint)
+        private void CloseTabBtn_Click(object sender, RoutedEventArgs e)
         {
-            if (string.IsNullOrEmpty(endpoint)) return "CAN_BUS_1";
-            int idx = endpoint.IndexOf(':');
-            return idx > 0 ? endpoint.Substring(0, idx) : endpoint;
+            try
+            {
+                if (debugTabs.SelectedItem is not TabItem tab) return;
+                string deviceId = tab.Tag as string ?? "";
+                if (tab.Content is DeviceDebugTab content)
+                    content.CloseSessionSilently();   // 鍏抽棴鍗抽噴鏀剧淮鎶ょ绾︼紝鏉滅粷娉勬紡
+                debugTabs.Items.Remove(tab);
+                if (!string.IsNullOrEmpty(deviceId)) _openTabs.Remove(deviceId);
+                RefreshAvailableList();
+                log.Info($"宸插叧闂皟璇曟爣绛惧苟閲婃斁浼氳瘽: {deviceId}");
+            }
+            catch (Exception ex)
+            {
+                log.Error("鍏抽棴璋冭瘯鏍囩澶辫触", ex);
+                ToastHelper.Error($"鍏抽棴澶辫触: {ex.Message}");
+            }
         }
-
-        /// <summary>
-        /// 浠� CAN Endpoint 瑙f瀽鑺傜偣缂栧彿
-        /// </summary>
-        private int? ParseCanNodeIndex(string endpoint)
-        {
-            if (string.IsNullOrEmpty(endpoint)) return 0;
-            int idx = endpoint.IndexOf(':');
-            if (idx > 0 && idx < endpoint.Length - 1 && int.TryParse(endpoint.Substring(idx + 1), out int n))
-                return n;
-            return 0;
-        }
-
-        #endregion
     }
 }

--
Gitblit v1.9.1