Просмотр исходного кода

1、增加多小车机构切换
2、多探头使用重复项实现
3、优化实时曲线的返回重绘
4、增加实时数值的显示
5、增加虚拟化方法的属性(用于混淆代码)
6、

LC 2 недель назад
Родитель
Сommit
f47e793dd6
86 измененных файлов с 3861 добавлено и 3221 удалено
  1. 15 6
      App.xaml
  2. 113 34
      App.xaml.cs
  3. 130 39
      Config/Config.json
  4. 2 1
      Controls/DamageRangeControl.xaml
  5. 4 2
      Controls/DamageRecordControl.xaml
  6. 2 5
      Controls/DamageRecordControl.xaml.cs
  7. 8 7
      Controls/NotificationBar.xaml
  8. 7 6
      Controls/NotificationBar.xaml.cs
  9. 41 8
      Controls/RealTimeLineChart.xaml.cs
  10. 13 10
      Controls/RopeEquipmentControl.xaml
  11. 76 8
      Controls/RopeEquipmentControl.xaml.cs
  12. 45 0
      Converters/CollectionToVisibilityConverter.cs
  13. 25 7
      Converters/ConnectivityFaultToColorConverter.cs
  14. 49 32
      Converters/RiskLevelToColorConverter.cs
  15. 5 4
      Converters/RunningStatusToBooleanConverter.cs
  16. 2 2
      Converters/StringEmptyToVisibilityConverter.cs
  17. 22 0
      Converters/SubtractConverter.cs
  18. 30 0
      Converters/TrolleysToVisibilityConverter.cs
  19. 6 5
      Core/ConfigHelper.cs
  20. 153 84
      Core/TcpServerFrame.cs
  21. 14 0
      Dtos/FaultsMessageDto.cs
  22. 0 4
      Enums/FaultType.cs
  23. 3 0
      Extensions/DatFileHandler.cs
  24. 3 0
      Extensions/JsonCryptoHelper.cs
  25. 1 1
      Extensions/SimpleLogger.cs
  26. 8 0
      Extensions/SoftAuth.cs
  27. 54 5
      Extensions/Tools.cs
  28. 130 90
      MainWindow.xaml
  29. 135 51
      MainWindow.xaml.cs
  30. 8 2
      Mapping.cs
  31. 15 5
      Models/CalibrationDataModel.cs
  32. 7 0
      Models/ConfigModel.cs
  33. 6 1
      Models/DataCenterModel.cs
  34. 166 36
      Models/EquipmentDataModel.cs
  35. 8 1
      Models/EquipmentModel.cs
  36. 5 67
      Models/LiveStreamDataModel.cs
  37. 2 2
      Models/ParameterModel.cs
  38. 33 0
      Models/TrolleyActiveModel.cs
  39. 46 0
      Models/TrolleyLimitDataModel.cs
  40. 17 7
      Models/ViewModel/EquipmentSettingViewModel.cs
  41. 0 65
      Models/ViewModel/FourRopesViewModel.cs
  42. 14 1
      Models/ViewModel/MainViewModel.cs
  43. 0 21
      Models/ViewModel/OneRopeViewModel.cs
  44. 133 0
      Models/ViewModel/RealTimeViewModel.cs
  45. 13 1
      Models/ViewModel/RecordViewModel.cs
  46. 0 41
      Models/ViewModel/RopeViewBaseModel.cs
  47. 0 51
      Models/ViewModel/ThreeRopesViewModel.cs
  48. 0 29
      Models/ViewModel/TwoRopesViewModel.cs
  49. 2 2
      Pages/DamagesPage.xaml
  50. 1 2
      Pages/DamagesPage.xaml.cs
  51. 288 274
      Pages/ParameterDialog.xaml
  52. 47 28
      Pages/ParameterDialog.xaml.cs
  53. 196 0
      Pages/RealTime/FourRopesControl.xaml
  54. 67 0
      Pages/RealTime/FourRopesControl.xaml.cs
  55. 0 523
      Pages/RealTime/FourRopesPage.xaml
  56. 0 166
      Pages/RealTime/FourRopesPage.xaml.cs
  57. 174 0
      Pages/RealTime/OneRopeControl.xaml
  58. 70 0
      Pages/RealTime/OneRopeControl.xaml.cs
  59. 0 146
      Pages/RealTime/OneRopePage.xaml
  60. 0 77
      Pages/RealTime/OneRopePage.xaml.cs
  61. 101 0
      Pages/RealTime/RealTimePage.xaml
  62. 168 0
      Pages/RealTime/RealTimePage.xaml.cs
  63. 174 0
      Pages/RealTime/ThreeRopesControl.xaml
  64. 69 0
      Pages/RealTime/ThreeRopesControl.xaml.cs
  65. 0 400
      Pages/RealTime/ThreeRopesPage.xaml
  66. 0 139
      Pages/RealTime/ThreeRopesPage.xaml.cs
  67. 179 0
      Pages/RealTime/TwoRopesControl.xaml
  68. 69 0
      Pages/RealTime/TwoRopesControl.xaml.cs
  69. 0 268
      Pages/RealTime/TwoRopesPage.xaml
  70. 0 109
      Pages/RealTime/TwoRopesPage.xaml.cs
  71. 3 2
      Pages/RecordPage.xaml
  72. 179 26
      Pages/RecordPage.xaml.cs
  73. 255 231
      Pages/SettingDialog.xaml
  74. 124 51
      Pages/SettingDialog.xaml.cs
  75. 1 1
      Properties/app.manifest
  76. 27 0
      README.md
  77. BIN
      Resources/blue_bg.png
  78. BIN
      Resources/equipment.png
  79. BIN
      Resources/red_bg.png
  80. BIN
      Resources/rope_block.png
  81. BIN
      Resources/trolleyActived.png
  82. 50 19
      SWRIS.csproj
  83. 34 0
      Selectors/EquipmentsLayoutTemplateSelector.cs
  84. 3 0
      SoftAuthDialog.xaml.cs
  85. 7 16
      Styles/Styles.xaml
  86. 4 0
      packages.config

+ 15 - 6
App.xaml

@@ -7,8 +7,8 @@
     <Application.Resources>
         <ResourceDictionary>
             <FontFamily x:Key="PuHuiTiBold">pack://application:,,,/SWRIS;component/Fonts/Alibaba-PuHuiTi-Bold.ttf#Alibaba PuHuiTi</FontFamily>
-            <FontFamily x:Key="PuHuiTiRegular">pack://application:,,,/SWRIS;component/Fonts/Alibaba-PuHuiTi-Regular.ttf#Alibaba PuHuiTi</FontFamily>
-            <FontFamily x:Key="PuHuiTiMedium">pack://application:,,,/SWRIS;component/Fonts/Alibaba-PuHuiTi-Medium.ttf#Alibaba PuHuiTi</FontFamily>
+            <FontFamily x:Key="PuHuiTiRegular">pack://application:,,,/SWRIS;component/Fonts/Alibaba-PuHuiTi-Medium.ttf#Alibaba PuHuiTi</FontFamily>
+            <FontFamily x:Key="PuHuiTiMedium">pack://application:,,,/SWRIS;component/Fonts/Alibaba-PuHuiTi-Regular.ttf#Alibaba PuHuiTi</FontFamily>
             <FontFamily x:Key="PanuonIconFont">pack://application:,,,/SWRIS;component/Fonts/PanuonIcon.ttf#PanuonIcon</FontFamily>
             <ResourceDictionary.MergedDictionaries>
                 <pu:StyleDictionary Includes="All" />
@@ -30,7 +30,7 @@
                                 <Setter Property="Background" Value="#141332" />
                                 <Setter Property="Foreground" Value="#FFFFFF" />
                                 <Setter Property="BorderBrush" Value="#615CDD"/>
-                                <Setter Property="BorderThickness" Value="2"/>
+                                <Setter Property="BorderThickness" Value="1"/>
                             </Style>
                         </pu:MessageBoxXSettings.WindowXStyle>
                         <pu:MessageBoxXSettings.ButtonStyle>
@@ -39,6 +39,11 @@
                                 <Setter Property="Width" Value="100"/>
                                 <Setter Property="Height" Value="50"/>
                                 <Setter Property="Margin" Value="10,0"/>
+                                <Setter Property="FontFamily">
+                                    <Setter.Value>
+                                        <FontFamily>pack://application:,,,/SWRIS;component/Fonts/Alibaba-PuHuiTi-Medium.ttf#Alibaba PuHuiTi</FontFamily>
+                                    </Setter.Value>
+                                </Setter>
                                 <Style.Triggers>
                                     <Trigger Property="IsDefault" Value="True">
                                         <Setter Property="Foreground" Value="#FFFFFF" />
@@ -79,14 +84,17 @@
                                         <FontFamily>pack://application:,,,/SWRIS;component/Fonts/Alibaba-PuHuiTi-Medium.ttf#Alibaba PuHuiTi</FontFamily>
                                     </Setter.Value>
                                 </Setter>
-                                <Setter Property="Height" Value="160"/>
-                                <Setter Property="Width" Value="380"/>
-                                <Setter Property="Margin" Value="5,10"/>
+                                <Setter Property="Margin" Value="5,5"/>
                                 <Setter Property="CornerRadius" Value="5"/>
                                 <Setter Property="Foreground" Value="#FFFFFF" />
+                                <Setter Property="Width" Value="350" />
+                                <Setter Property="Height" Value="150" />
+                                <Setter Property="VerticalContentAlignment" Value="Stretch" />
+                                <Setter Property="HorizontalContentAlignment" Value="Stretch" />
                             </Style>
                         </pu:NoticeBoxSettings.NoticeBoxItemStyle>
                     </pu:NoticeBoxSettings>
+                    
                     <pu:PendingBoxSettings x:Key="pendingSetting">
                         <pu:PendingBoxSettings.WindowStyle>
                             <Style BasedOn="{StaticResource {x:Static pu:PendingBox.WindowStyleKey}}" TargetType="Window">
@@ -127,5 +135,6 @@
                 </ResourceDictionary>
             </ResourceDictionary.MergedDictionaries>
         </ResourceDictionary>
+      
     </Application.Resources>
 </Application>

+ 113 - 34
App.xaml.cs

@@ -1,4 +1,5 @@
-using Microsoft.Win32;
+using IWshRuntimeLibrary;
+using Microsoft.Win32;
 using SWRIS.Core;
 using SWRIS.Extensions;
 using SWRIS.Models;
@@ -6,6 +7,7 @@ using SWRIS.Properties;
 using SWRIS.Services;
 using System;
 using System.Diagnostics;
+using System.IO;
 using System.Linq;
 using System.Reflection;
 using System.Threading.Tasks;
@@ -27,6 +29,8 @@ namespace SWRIS
         public static DataCenterModel DataCenter { get; set; }
         public static HistoryCleanupService HistoryCleanup { get; set; }
 
+        private static string _processName = "SWRIS";
+        private static string _appName = "在线式钢丝绳电磁检测系统";
         public App()
         {
             //首先注册开始和退出事件
@@ -39,10 +43,13 @@ namespace SWRIS
                     return;
                 }
             }
+            _processName = Assembly.GetEntryAssembly()?.GetName().Name ?? _processName;
             //PDF初始化配置
             QuestPDF.Settings.License = QuestPDF.Infrastructure.LicenseType.Community;
             QuestPDF.Settings.CheckIfAllTextGlyphsAreAvailable = false;
         }
+
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         private void InitSoftAuthorize()
         {
             if (!SoftAuth.IsAuthorizeSuccess())
@@ -60,6 +67,7 @@ namespace SWRIS
         {
             _singleInstanceManager?.SwitchToExistingInstance(processName);
         }
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         protected override void OnStartup(StartupEventArgs e)
         {
             #region 阻止系统睡眠,阻止屏幕关闭。
@@ -67,7 +75,7 @@ namespace SWRIS
             #endregion
 
             #region 避免程序多次启动
-            _singleInstanceManager = new SingleInstanceManager("SWRIS");
+            _singleInstanceManager = new SingleInstanceManager(_processName);
             // 检查是否已有实例运行
             if (_singleInstanceManager.IsAlreadyRunning())
             {
@@ -85,27 +93,21 @@ namespace SWRIS
                 LogHelper.Error("读取配置文件失败");
                 Environment.Exit(0);
             }
-            if (!Tools.CheckIpAddressExists(Config.IpAddress))
-            {
-                MessageBox.Show($"当前在用网卡的IP地址中没有发现[{Config.IpAddress}],请检查网络配置", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
-                Environment.Exit(0);
-            }
-            if (Tools.CheckPortInUse(Config.Port))
+
+            if (!Config.AppName.IsNullOrEmptyOrWhiteSpace())
             {
-                MessageBox.Show("当前配置的端口已被占用,请更换端口或关闭占用该端口的程序", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
-                Environment.Exit(0);
+                _appName = Config.AppName;
             }
-
             if (Settings.Default.IsDebug)
             {
-                SetAutoStart(false);
+                SetAutoStartByStartupFolder(false);
             }
             else
             {
-                InitSoftAuthorize();
-                SetAutoStart(true);
+                //InitSoftAuthorize();
+                //SetAutoStartByStartupFolder(true);
                 // 创建桌面快捷方式
-                Tools.CreateShortcut(Assembly.GetExecutingAssembly().Location, Config.AppName);
+                CreateShortcut(Assembly.GetExecutingAssembly().Location, _appName);
             }
             // 显示启动画面
             var splashScreen = new SplashScreen("Resources/loading.png");
@@ -146,7 +148,7 @@ namespace SWRIS
         {
             if (equipment == null) return false;
 
-            var existingEquipment = Config.Equipments.FirstOrDefault(e => e.IpAddress == equipment.IpAddress);
+            var existingEquipment = Config.Equipments.FirstOrDefault(e => e.RopeNumber == equipment.RopeNumber);
             if (existingEquipment == null) return false;
 
             // 更新设备信息
@@ -236,9 +238,8 @@ namespace SWRIS
         /// 修改程序在注册表中的键值  
         /// </summary>  
         /// <param name="isAuto">true:开机启动,false:不开机自启</param> 
-        public static void SetAutoStart(bool isAuto, bool showInfo = true)
+        public static void SetAutoStartByRegistry(bool isAuto, bool showInfo = true)
         {
-            string appName = Config.AppName;
             try
             {
                 using (RegistryKey rLocal = Registry.CurrentUser)
@@ -249,20 +250,20 @@ namespace SWRIS
                     if (isAuto)
                     {
                         string currentPath = Process.GetCurrentProcess().MainModule.FileName;
-                        object existingValue = rRun.GetValue(appName);
+                        object existingValue = rRun.GetValue(_appName);
 
                         // 只有当值不存在或路径不同时才更新
                         if (existingValue == null || !currentPath.Equals(existingValue.ToString(), StringComparison.OrdinalIgnoreCase))
                         {
-                            rRun.SetValue(appName, currentPath);
+                            rRun.SetValue(_appName, currentPath);
                         }
                     }
                     else
                     {
                         // 如果存在则删除
-                        if (rRun.GetValue(appName) != null)
+                        if (rRun.GetValue(_appName) != null)
                         {
-                            rRun.DeleteValue(appName, false);
+                            rRun.DeleteValue(_appName, false);
                         }
                     }
                 }
@@ -282,27 +283,105 @@ namespace SWRIS
                 }
             }
         }
-        public static async void RestartApplication()
+
+        public static void SetAutoStartByStartupFolder(bool isAuto = true)
         {
+            string appName = Config.AppName ?? "在线式钢丝绳电磁检测系统";
+            string exePath = Process.GetCurrentProcess().MainModule.FileName;
+            // 获取当前用户的启动文件夹路径
+            string startupPath = Environment.GetFolderPath(Environment.SpecialFolder.Startup);
+            string shortcutPath = Path.Combine(startupPath, $"{appName}.lnk");
+
+            if (isAuto)
+            {
+                // 如果快捷方式已存在,先删除(防止重复或属性更新失败)
+                if (System.IO.File.Exists(shortcutPath))
+                    System.IO.File.Delete(shortcutPath);
+
+                // 创建 WshShell 对象
+                WshShell shell = new WshShell();
+                // 创建快捷方式
+                IWshShortcut shortcut = (IWshShortcut)shell.CreateShortcut(shortcutPath);
+
+                // 设置快捷方式的属性
+                shortcut.TargetPath = exePath;               // 目标程序路径
+                shortcut.WorkingDirectory = Path.GetDirectoryName(exePath); // 工作目录
+                shortcut.Description = $"开机自启 - {appName}"; // 描述信息
+
+                // 保存快捷方式文件
+                shortcut.Save();
+                Debug.WriteLine($"已创建启动快捷方式:{shortcutPath}");
+            }
+            else
+            {
+                // 删除快捷方式
+                if (System.IO.File.Exists(shortcutPath))
+                {
+                    System.IO.File.Delete(shortcutPath);
+                    Debug.WriteLine($"已删除启动快捷方式:{shortcutPath}");
+                }
+            }
+        }
+        /// <summary>
+        /// 创建桌面会计快捷方式,要求管理员权限运行
+        /// </summary>
+        /// <param name="applicationPath"></param>
+        /// <param name="shortcutName"></param>
+        /// <returns></returns>
+        public static bool CreateShortcut(string applicationPath, string shortcutName)
+        {
+            string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
+            string shortcutPath = Path.Combine(desktopPath, shortcutName + ".lnk");
+
+            // 检查快捷方式是否已存在
+            if (System.IO.File.Exists(shortcutPath))
+            {
+                return false; // 已存在,不创建
+            }
+
             try
             {
-                _singleInstanceManager?.Dispose();
-                string currentExecutablePath = Process.GetCurrentProcess().MainModule.FileName;
-                // 启动新进程
-                Process.Start(currentExecutablePath);
+                WshShell shell = new WshShell();
+                IWshShortcut shortcut = (IWshShortcut)shell.CreateShortcut(shortcutPath);
+
+                shortcut.TargetPath = applicationPath;
+                shortcut.WorkingDirectory = Path.GetDirectoryName(applicationPath);
+                shortcut.Description = "需要管理员权限运行";
+                shortcut.IconLocation = applicationPath + ",0";
 
-                // 安全关闭
-                Current?.Dispatcher.Invoke(() =>
+                // 保存快捷方式
+                shortcut.Save();
+
+                // 修改快捷方式以要求管理员权限
+                using (var fs = new FileStream(shortcutPath, FileMode.Open, FileAccess.ReadWrite))
                 {
-                    Current.Shutdown();
-                });
+                    fs.Seek(21, SeekOrigin.Begin);
+                    fs.WriteByte(0x22); // 设置标志位以管理员身份运行
+                }
+                return true; // 创建成功
             }
-            catch (Exception ex)
+            catch
             {
-                // 记录错误日志
-                MessageBox.Show($"重启失败: {ex.Message}");
+                // 如果创建过程中出错,尝试删除可能已部分创建的快捷方式
+                if (System.IO.File.Exists(shortcutPath))
+                {
+                    try { System.IO.File.Delete(shortcutPath); } catch { }
+                }
+                throw;
             }
         }
+        public static void RestartApplication()
+        {
+            _singleInstanceManager?.Dispose();
+            // 获取当前进程的可执行文件路径
+            string currentExecutablePath = Process.GetCurrentProcess().MainModule.FileName;
+
+            // 启动新的进程实例
+            Process.Start(currentExecutablePath);
+
+            // 关闭当前应用程序
+            Current.Shutdown();
+        }
         protected override void OnExit(ExitEventArgs e)
         {
             TcpServer?.Dispose();

+ 130 - 39
Config/Config.json

@@ -13,46 +13,17 @@
   "DamageExtent": 0.5,
   "AlarmValidCount": 0,
   "Crane": "炼钢一跨1#",
-  "Calibration": {
-    "ModuleType": "S1200",
-    "Limits": [
-      {
-        "Name": "上预限",
-        "Address": "I8.0",
-        "Position": 0.0,
-        "IsEnable": true,
-        "Function": "X1",
-        "SwitchIndex": 1,
-        "RelatedRopes": [ 1, 2 ]
-      },
-      {
-        "Name": "下预限",
-        "Address": "I8.1",
-        "Position": 0.0,
-        "IsEnable": true,
-        "Function": "X2",
-        "SwitchIndex": 2,
-        "RelatedRopes": [ 1, 2 ]
-      }
-    ],
-    "Tcp": {
-      "IpAddress": "192.168.1.127",
-      "Port": 102,
-      "Station": 1
-    },
-    "Serial": {
-      "PortName": "COM1",
-      "BaudRate": 9600,
-      "DataBits": 8,
-      "Parity": "None",
-      "StopBits": 1,
-      "Station": 1
+  "Trolleys": [
+    {
+      "Id": 1,
+      "Name": "主小车"
     }
-  },
+  ],
   "Equipments": [
     {
       "RopeNumber": 1,
       "RopeName": "起升钢丝绳1#",
+      "TrolleyId": 1,
       "IpAddress": "192.168.1.241",
       "SerialNo": "2509001",
       "Supplier": "宁夏恒力",
@@ -60,6 +31,7 @@
       "WireMaterialType": 1,
       "WireSurfaceType": 1,
       "LiftHightRatio": 2,
+      "TimeDomainSpeed": 1.2,
       "LayType": 1,
       "DisableAlarm": false,
       "InUseSensor": [ 1, 2, 3 ],
@@ -93,9 +65,50 @@
     },
     {
       "RopeNumber": 2,
+      "RopeName": "起升钢丝绳2#",
+      "TrolleyId": 1,
+      "IpAddress": "192.168.1.242",
+      "Supplier": "宁夏恒力",
+      "RopeCoreType": 1,
+      "WireMaterialType": 1,
+      "WireSurfaceType": 1,
+      "LiftHightRatio": 2,
+      "LayType": 1,
+      "DisableAlarm": false,
+      "InUseSensor": [ 1, 2, 3, 4 ],
+      "Parameter": {
+        "MainBoardSoftwareVersion": "2.07.01 1.1.0",
+        "SensorCount": 4,
+        "SamplingStep": 1.1275,
+        "FrequencyDivisionFactor": 1,
+        "TimeDomainFrequency": 1000,
+        "DamageThreshold": 0,
+        "ScrapUpperLimit": 10,
+        "FrontMagnetLength": 200,
+        "BackMagnetLength": 200,
+        "ValueCoefficient": 1.0,
+        "EffectiveStrokeLength": 2.0,
+        "ZeroPositionCorrectionDuration": 10,
+        "ZeroPositionCorrectionOffset": 50,
+        "WarningValue": 3.0,
+        "AlarmValue": 6.0,
+        "SoundLightAlarmAutoResetMode": -1,
+        "WireRopeType": 0,
+        "WireRopeCount": 1,
+        "WireRopeLength": 32.0,
+        "WireRopeDiameter": 20.0,
+        "WireRopeStrandCount": 6,
+        "WireRopeStrandWireCount": 19,
+        "EncoderDirection": 0,
+        "SystemTime": null,
+        "TwistFactor": 0.0
+      }
+    },
+    {
+      "RopeNumber": 3,
       "RopeName": "起升钢丝绳3#",
-      "IpAddress": "192.168.1.127",
-      "SerialNo": "2508001",
+      "TrolleyId": 1,
+      "IpAddress": "192.168.1.243",
       "Supplier": "宁夏恒力",
       "RopeCoreType": 1,
       "WireMaterialType": 1,
@@ -131,7 +144,85 @@
         "SystemTime": null,
         "TwistFactor": 0.0
       }
+    },
+    {
+      "RopeNumber": 4,
+      "RopeName": "起升钢丝绳4#",
+      "TrolleyId": 1,
+      "IpAddress": "192.168.1.244",
+      "Supplier": "宁夏恒力",
+      "RopeCoreType": 1,
+      "WireMaterialType": 1,
+      "WireSurfaceType": 1,
+      "LiftHightRatio": 2,
+      "LayType": 1,
+      "DisableAlarm": false,
+      "InUseSensor": [ 1, 2, 3, 4 ],
+      "Parameter": {
+        "MainBoardSoftwareVersion": "2.07.01 1.1.0",
+        "SensorCount": 4,
+        "SamplingStep": 1.1275,
+        "FrequencyDivisionFactor": 1,
+        "TimeDomainFrequency": 1000,
+        "DamageThreshold": 0,
+        "ScrapUpperLimit": 10,
+        "FrontMagnetLength": 200,
+        "BackMagnetLength": 200,
+        "ValueCoefficient": 1.0,
+        "EffectiveStrokeLength": 2.0,
+        "ZeroPositionCorrectionDuration": 10,
+        "ZeroPositionCorrectionOffset": 50,
+        "WarningValue": 3.0,
+        "AlarmValue": 6.0,
+        "SoundLightAlarmAutoResetMode": -1,
+        "WireRopeType": 0,
+        "WireRopeCount": 1,
+        "WireRopeLength": 32.0,
+        "WireRopeDiameter": 20.0,
+        "WireRopeStrandCount": 6,
+        "WireRopeStrandWireCount": 19,
+        "EncoderDirection": 0,
+        "SystemTime": null,
+        "TwistFactor": 0.0
+      }
+    }
+  ],
+  "Calibration": {
+    "ModuleType": "S1200",
+    "Limits": [
+      {
+        "TrolleyId": 1,
+        "Name": "上预限",
+        "Address": "I8.0",
+        "Position": 0.0,
+        "IsEnable": true,
+        "Function": "X1",
+        "SwitchIndex": 1,
+        "RelatedRopes": [ 1, 2 ]
+      },
+      {
+        "TrolleyId": 1,
+        "Name": "下预限",
+        "Address": "I8.1",
+        "Position": 0.0,
+        "IsEnable": true,
+        "Function": "X2",
+        "SwitchIndex": 2,
+        "RelatedRopes": [ 1, 2 ]
+      }
+    ],
+    "Tcp": {
+      "IpAddress": "192.168.1.127",
+      "Port": 102,
+      "Station": 1
+    },
+    "Serial": {
+      "PortName": "COM1",
+      "BaudRate": 9600,
+      "DataBits": 8,
+      "Parity": "None",
+      "StopBits": 1,
+      "Station": 1
     }
-
-  ]
+  }
 }

+ 2 - 1
Controls/DamageRangeControl.xaml

@@ -4,7 +4,8 @@
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
              xmlns:local="clr-namespace:SWRIS.Controls"
-             mc:Ignorable="d" 
+             mc:Ignorable="d" FontSize="18"
+             FontFamily="{StaticResource PuHuiTiRegular}"
              d:DesignHeight="50" d:DesignWidth="1100">
     <Grid>
         <Grid.RowDefinitions>

+ 4 - 2
Controls/DamageRecordControl.xaml

@@ -9,6 +9,7 @@
     <UserControl.Resources>
         <cvt:RiskLevelToColorConverter x:Key="RiskLevelToColorConverter"/>
         <cvt:RiskLevelToTextConverter x:Key="RiskLevelToTextConverter"/>
+        <cvt:SubtractConverter x:Key="SubtractConverter"/>
         <Storyboard x:Key="NewItemGlowAnimation" RepeatBehavior="3x">
             <DoubleAnimation
                 Storyboard.TargetProperty="Effect.Opacity"
@@ -28,7 +29,9 @@
                 <TextBlock Text="{Binding Title, RelativeSource={RelativeSource AncestorType=UserControl}}" 
                           HorizontalAlignment="Center" FontSize="22" FontFamily="{StaticResource PuHuiTiBold}" FontWeight="Bold" Margin="14" />
             </Border>
-            <ScrollViewer MaxHeight="842" VerticalScrollBarVisibility="Hidden" Margin="25,8">
+            <ScrollViewer VerticalScrollBarVisibility="Hidden" Margin="25,8,25,0" 
+                          MaxHeight="{Binding RelativeSource={RelativeSource AncestorType=UserControl},
+                Path=ActualHeight, Converter={StaticResource SubtractConverter}, ConverterParameter=67.5}">
                 <StackPanel Orientation="Vertical">
                     <ItemsControl ItemsSource="{Binding Records, RelativeSource={RelativeSource AncestorType=UserControl}}">
                         <ItemsControl.ItemTemplate>
@@ -54,7 +57,6 @@
                                             </Style.Triggers>
                                         </Style>
                                     </Border.Style>
-
                                     <!-- 鼠标悬停触发器 -->
                                     <Border.Triggers>
                                         <EventTrigger RoutedEvent="MouseEnter">

+ 2 - 5
Controls/DamageRecordControl.xaml.cs

@@ -37,12 +37,9 @@ namespace SWRIS.Controls
 
         private void Record_MouseDown(object sender, MouseButtonEventArgs e)
         {
-            if (e.ClickCount == 2)
+            if (sender is StackPanel panel && int.TryParse(panel.Tag?.ToString(), out int recordId))
             {
-                if (sender is StackPanel panel && int.TryParse(panel.Tag?.ToString(), out int recordId))
-                {
-                    (Application.Current.MainWindow as MainWindow).NavigatePage(new Uri($"Pages/DamagesPage.xaml?id=" + recordId, uriKind: UriKind.Relative));
-                }
+                (Application.Current.MainWindow as MainWindow).NavigatePage(new Uri($"Pages/DamagesPage.xaml?id=" + recordId, uriKind: UriKind.Relative));
             }
         }
     }

+ 8 - 7
Controls/NotificationBar.xaml

@@ -11,10 +11,10 @@
         <cvt:CollectionCountToVisibilityConverter x:Key="CollectionCountToVisibilityConverter"/>
     </UserControl.Resources>
     <Grid>
-        <Border Background="#1AFF0000"
-            CornerRadius="{Binding ElementName=Root,Path=CornerRadius}" 
-            Padding="{Binding ElementName=Root,Path=TextPadding}"
-            Visibility="{Binding Messages.Count, Converter={StaticResource CollectionCountToVisibilityConverter}}">
+        <Border Background="#2AFF0000"
+                CornerRadius="{Binding ElementName=Root,Path=CornerRadius}" 
+                Padding="{Binding ElementName=Root,Path=TextPadding}"
+                Visibility="{Binding Messages.Count, Converter={StaticResource CollectionCountToVisibilityConverter}}">
             <ItemsControl ItemsSource="{Binding Messages}">
                 <ItemsControl.ItemsPanel>
                     <ItemsPanelTemplate>
@@ -24,11 +24,12 @@
                 <ItemsControl.ItemTemplate>
                     <DataTemplate>
                         <StackPanel Orientation="Horizontal" Margin="5 0">
-                            <Grid Width="22" Height="22" Margin="0 0 8 0">
-                                <Ellipse Fill="#4DFF2626" Width="22" Height="22"/>
+                            <Grid Width="22" Height="22" Margin="0 0 5 0">
+                                <Ellipse Fill="#6DFF2626" Width="22" Height="22"/>
                                 <Ellipse Fill="#FF2626" Width="10" Height="10" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                             </Grid>
-                            <TextBlock Text="{Binding}" Foreground="#FF0000" VerticalAlignment="Center"/>
+                            <TextBlock Text="{Binding RopeName,StringFormat='{}【{0}】'}" Foreground="#FF0000" VerticalAlignment="Center"/>
+                            <TextBlock Text="{Binding DisplayMessage}" Foreground="#FF0000" VerticalAlignment="Center"/>
                         </StackPanel>
                     </DataTemplate>
                 </ItemsControl.ItemTemplate>

+ 7 - 6
Controls/NotificationBar.xaml.cs

@@ -1,4 +1,5 @@
-using System.Collections;
+using SWRIS.Dtos;
+using System.Collections.ObjectModel;
 using System.Windows;
 using System.Windows.Controls;
 
@@ -15,18 +16,18 @@ namespace SWRIS.Controls
         }
 
         public static readonly DependencyProperty MessagesProperty =
-           DependencyProperty.Register("Messages", typeof(IEnumerable), typeof(NotificationBar),
+           DependencyProperty.Register("Messages", typeof(ObservableCollection<FaultsMessageDto>), typeof(NotificationBar),
                new PropertyMetadata(null));
 
-        public IEnumerable Messages
+        public ObservableCollection<FaultsMessageDto> Messages
         {
-            get { return (IEnumerable)GetValue(MessagesProperty); }
+            get { return (ObservableCollection<FaultsMessageDto>)GetValue(MessagesProperty); }
             set { SetValue(MessagesProperty, value); }
         }
 
         public static readonly DependencyProperty CornerRadiusProperty =
          DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(NotificationBar),
-             new PropertyMetadata(new CornerRadius(25, 25, 25, 25)));
+             new PropertyMetadata(new CornerRadius(20, 20, 20, 20)));
 
         public CornerRadius CornerRadius
         {
@@ -36,7 +37,7 @@ namespace SWRIS.Controls
 
         public static readonly DependencyProperty TextPaddingProperty =
         DependencyProperty.Register("TextPadding", typeof(Thickness), typeof(NotificationBar),
-            new PropertyMetadata(new Thickness(20, 10, 20, 10)));
+            new PropertyMetadata(new Thickness(15, 5, 10, 5)));
 
         public Thickness TextPadding
         {

+ 41 - 8
Controls/RealTimeLineChart.xaml.cs

@@ -14,7 +14,7 @@ using System.Windows.Threading;
 namespace SWRIS.Controls
 {
     /// <summary>
-    /// 动态折线图用户控件(基于 ScottPlot.WPF 的高性能版本)
+    /// 动态折线图用户控件
     /// </summary>
     public partial class RealTimeLineChart : UserControl
     {
@@ -106,16 +106,22 @@ namespace SWRIS.Controls
             plot.DataBackground.Color = Colors.Transparent;
             plot.FigureBackground.Color = Colors.Transparent;
             plot.Grid.MajorLineColor = new Color(59, 59, 123).WithOpacity(0.6);
+
             plot.Grid.XAxis.TickLabelStyle.ForeColor = new Color(151, 166, 212);
             plot.Grid.YAxis.TickLabelStyle.ForeColor = new Color(151, 166, 212);
+            plot.Grid.YAxis.TickLabelStyle.FontSize = 9;
+            plot.Grid.XAxis.TickLabelStyle.FontSize = 9;
+
             plot.Grid.XAxis.FrameLineStyle.Color = new Color(53, 53, 112);
             plot.Grid.XAxis.FrameLineStyle.Width = 2;
             plot.Grid.YAxis.FrameLineStyle.Color = new Color(53, 53, 112);
             plot.Grid.YAxis.FrameLineStyle.Width = 2;
+
             plot.Grid.XAxis.MajorTickStyle.Color = new Color(53, 53, 112);
             plot.Grid.XAxis.MajorTickStyle.Width = 1;
             plot.Grid.YAxis.MajorTickStyle.Color = new Color(53, 53, 112);
             plot.Grid.YAxis.MajorTickStyle.Width = 1;
+
             plot.Grid.YAxis.MinorTickStyle.Color = new Color(53, 53, 112);
             plot.Grid.XAxis.MinorTickStyle.Color = new Color(53, 53, 112);
             plot.Axes.Hairline(false);
@@ -167,14 +173,37 @@ namespace SWRIS.Controls
         }
         public void AddDataPoints(Coordinates[] chartData)
         {
-            _dataLogger.Add(chartData);
-            _lastPosition = chartData.LastOrDefault().X;
+            if (chartData == null || chartData.Length == 0 || _dataLogger.Data.Coordinates.Any())
+                return;
+
+            // 检查是否需要排序
+            var sortedData = chartData;
+            bool isAscending = true;
+
+            for (int i = 1; i < chartData.Length; i++)
+            {
+                if (chartData[i].X < chartData[i - 1].X)
+                {
+                    isAscending = false;
+                    break;
+                }
+            }
+
+            // 如果不是升序,进行排序
+            if (!isAscending)
+            {
+                sortedData = chartData.OrderBy(p => p.X).ToArray();
+            }
+
+            _dataLogger.Add(sortedData);
+            _lastPosition = sortedData.LastOrDefault().X;
             WpfPlot.Refresh();
         }
         public Coordinates[] GetChartSource()
         {
             return _dataLogger.Data.Coordinates.ToArray();
         }
+
         public void AddDataPoints((double Position, LiveStreamDataModel ListStream) dataPoints, int[] inUseSensors)
         {
             if (_lastPosition == null)
@@ -191,11 +220,10 @@ namespace SWRIS.Controls
                 }
                 _dataLogger.Data.Clear();
             }
-            if (inUseSensors == null || inUseSensors.Length == 0)
+            if (inUseSensors == null || inUseSensors.Length == 0 || inUseSensors.Any(c => c >= dataPoints.ListStream.SensorCount))
             {
-                return;
+                inUseSensors = Enumerable.Range(1, dataPoints.ListStream.SensorCount).ToArray();
             }
-
             for (int i = 0; i < dataPoints.ListStream.SampleCount; i++)
             {
                 ushort totalValue = 0;
@@ -206,10 +234,8 @@ namespace SWRIS.Controls
                     totalCount++;
                     totalValue += values[j - 1];
                 }
-
                 var value = (ushort)(totalValue / totalCount);
                 _sensorDataQueues.Enqueue(value);
-
             }
 
             if (dataPoints.Position > _lastPosition)
@@ -249,6 +275,11 @@ namespace SWRIS.Controls
                 }
             }
         }
+        /// <summary>
+        /// 更新曲线绘制的循环方法,将队列中的数据批量添加到曲线中,并确保 x 值严格递增
+        /// </summary>
+        /// <param name="lastPosition">上一个位置</param>
+        /// <param name="position">当前位置</param>
         private void UpdateLoop(double lastPosition, double position)
         {
             try
@@ -345,5 +376,7 @@ namespace SWRIS.Controls
             });
         }
         #endregion
+
+
     }
 }

+ 13 - 10
Controls/RopeEquipmentControl.xaml

@@ -19,7 +19,7 @@
     <Grid Margin="0,0,30,0">
         <Grid.ColumnDefinitions>
             <ColumnDefinition Width="*"/>
-            <ColumnDefinition Width="*"/>
+            <ColumnDefinition Width="65"/>
         </Grid.ColumnDefinitions>
         <StackPanel Grid.Column="0" Orientation="Vertical" Margin="{Binding PanelMargin,ElementName=Root}">
             <ItemsControl ItemsSource="{Binding Limits}" HorizontalAlignment="Right" Margin="-10,-10,0,10">
@@ -34,15 +34,17 @@
                                 Background="{Binding State,Converter={StaticResource LimitStateToColorConverter},ConverterParameter=0.20}" 
                                 MinWidth="80" Height="24" CornerRadius="11" Margin="5,0,0,0">
                             <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
-                                <Ellipse Height="10" Width="10" Margin="0,0,5,0" Fill="{Binding State,Converter={StaticResource LimitStateToColorConverter}}"/>
-                                <TextBlock Text="{Binding Name}" Foreground="{Binding State,Converter={StaticResource LimitStateToColorConverter}}"
+                                <Ellipse Height="10" Width="10" Margin="0,0,5,0" 
+                                         Fill="{Binding State,Converter={StaticResource LimitStateToColorConverter}}"/>
+                                <TextBlock Text="{Binding Name}" 
+                                           Foreground="{Binding State,Converter={StaticResource LimitStateToColorConverter}}"
                                            FontSize="14" FontWeight="Regular" VerticalAlignment="Center"/>
                             </StackPanel>
                         </Border>
                     </DataTemplate>
                 </ItemsControl.ItemTemplate>
             </ItemsControl>
-            <TextBlock Text="检测位置" Foreground="#7886B2" FontSize="{Binding PanelNameFontSize,ElementName=Root}" 
+            <TextBlock Text="检测位置" Foreground="#7886B2" FontSize="{Binding PanelNameFontSize,ElementName=Root}"
                        FontWeight="Regular" HorizontalAlignment="Right" Visibility="Visible"/>
             <TextBlock FontSize="{Binding PanelValueFontSize,ElementName=Root}" FontWeight="Bold" FontFamily="{StaticResource PuHuiTiBold}"
                        Margin="-30,-3,0,0" HorizontalAlignment="Right" Visibility="Visible">
@@ -73,15 +75,16 @@
                 </TextBlock.Style>
             </TextBlock>
         </StackPanel>
-        <Canvas Grid.Column="1">
-            <Rectangle Width="40" Height="{Binding RopeHeight,ElementName=Root}" Canvas.Left="30">
+        <Canvas Grid.Column="1" x:Name="RopeCanvas" >
+            <Rectangle Width="26" Height="{Binding RopeHeight,ElementName=Root}" Canvas.Left="30">
                 <Rectangle.Fill>
-                    <ImageBrush x:Name="RopeRectangle" ImageSource="{Binding RopeImageSource,ElementName=Root}" TileMode="FlipX" Viewport="0,0,40,34" 
-                         ViewportUnits="Absolute"/>
+                    <ImageBrush x:Name="RopeRectangle" ImageSource="{Binding RopeImageSource,ElementName=Root}"
+                                TileMode="FlipX" Viewport="0,0,26,22" 
+                                ViewportUnits="Absolute"/>
                 </Rectangle.Fill>
             </Rectangle>
             <Canvas Canvas.Top="{Binding EquipmentTopPosition,ElementName=Root}">
-                <Image Width="90" Canvas.Left="5">
+                <Image Width="55" Canvas.Left="16" Canvas.Top="12">
                     <Image.Style>
                         <Style TargetType="Image">
                             <Style.Triggers>
@@ -95,7 +98,7 @@
                         </Style>
                     </Image.Style>
                 </Image>
-                <Image Source="{Binding EquipmentImageSource,ElementName=Root}" Width="100" Canvas.Top="70"/>
+                <Image Source="{Binding EquipmentImageSource,ElementName=Root}" Width="70" Canvas.Top="70" Canvas.Left="8"/>
             </Canvas>
         </Canvas>
     </Grid>

+ 76 - 8
Controls/RopeEquipmentControl.xaml.cs

@@ -1,7 +1,4 @@
-using SWRIS.Models;
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
+using System;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Media;
@@ -14,7 +11,9 @@ namespace SWRIS.Controls
     /// </summary>
     public partial class RopeEquipmentControl : UserControl
     {
+        private Canvas _ropeCanvas; // 添加Canvas引用
         private readonly Storyboard _animationStoryboard;
+
         public RopeEquipmentControl()
         {
             InitializeComponent();
@@ -24,6 +23,12 @@ namespace SWRIS.Controls
 
             // 设置初始动画值
             UpdateAnimationValues();
+
+            // 订阅SizeChanged事件
+            SizeChanged += RopeEquipmentControl_SizeChanged;
+
+            // 订阅Loaded事件,确保控件加载完成后获取正确的尺寸
+            Loaded += RopeEquipmentControl_Loaded;
         }
         #region 依赖属性
 
@@ -133,7 +138,70 @@ namespace SWRIS.Controls
             set => SetValue(EquipmentTopPositionProperty, value);
         }
         #endregion
+        #region 事件处理
+
+        private void RopeEquipmentControl_Loaded(object sender, RoutedEventArgs e)
+        {
+            // 控件加载完成后更新RopeHeight
+            UpdateRopeHeight();
+        }
+
+        private void RopeEquipmentControl_SizeChanged(object sender, SizeChangedEventArgs e)
+        {
+            // 当控件大小改变时更新RopeHeight
+            UpdateRopeHeight();
+        }
 
+        /// <summary>
+        /// 更新绳索高度以适应容器大小
+        /// </summary>
+        private void UpdateRopeHeight()
+        {
+            try
+            {
+                // 获取Canvas引用
+                if (_ropeCanvas == null)
+                {
+                    _ropeCanvas = FindName("RopeCanvas") as Canvas;
+                    if (_ropeCanvas == null)
+                    {
+                        // 如果找不到Canvas,使用控件自身高度
+                        RopeHeight = Math.Max(0, ActualHeight);
+                        return;
+                    }
+                }
+
+                // 使用Canvas的实际高度减去上下边距
+                double newHeight = _ropeCanvas.ActualHeight;
+
+                // 确保高度不为负数
+                if (newHeight < 0)
+                {
+                    newHeight = 0;
+                }
+
+                // 只有当高度变化时才更新,避免不必要的重新渲染
+                if (Math.Abs(RopeHeight - newHeight) > 0.1)
+                {
+                    RopeHeight = newHeight;
+                }
+            }
+            catch (Exception)
+            {
+                // 出错时使用备用方案:使用控件自身高度
+                try
+                {
+                    RopeHeight = Math.Max(0, ActualHeight);
+                }
+                catch
+                {
+                    // 如果还出错,使用默认值
+                    RopeHeight = 900.0;
+                }
+            }
+        }
+
+        #endregion
         #region 动画控制
 
         private static void OnIsAnimationRunningChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
@@ -161,13 +229,13 @@ namespace SWRIS.Controls
 
             if (IsAnimationReversed)
             {
-                rectAnimation.From = new Rect(0, -34, 40, 34);
-                rectAnimation.To = new Rect(0, 0, 40, 34);
+                rectAnimation.From = new Rect(0, -22, 26, 22);
+                rectAnimation.To = new Rect(0, 0, 26, 22);
             }
             else
             {
-                rectAnimation.From = new Rect(0, 0, 40, 34);
-                rectAnimation.To = new Rect(0, -34, 40, 34);
+                rectAnimation.From = new Rect(0, 0, 26, 22);
+                rectAnimation.To = new Rect(0, -22, 26, 22);
             }
 
             if (IsAnimationRunning)

+ 45 - 0
Converters/CollectionToVisibilityConverter.cs

@@ -0,0 +1,45 @@
+using System;
+using System.Collections;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace SWRIS.Converters
+{
+    public class CollectionToVisibilityConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            // 处理 null 值
+            if (value == null)
+                return Visibility.Collapsed;
+
+            // 处理集合类型
+            if (value is ICollection collection)
+                return collection.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
+
+            // 处理 IEnumerable(不推荐,因为可能触发多次枚举)
+            if (value is IEnumerable enumerable)
+            {
+                var enumerator = enumerable.GetEnumerator();
+                try
+                {
+                    return enumerator.MoveNext() ? Visibility.Visible : Visibility.Collapsed;
+                }
+                finally
+                {
+                    if (enumerator is IDisposable disposable)
+                        disposable.Dispose();
+                }
+            }
+
+            // 其他类型默认显示
+            return Visibility.Visible;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 25 - 7
Converters/ConnectivityFaultToColorConverter.cs

@@ -2,7 +2,6 @@
 using System;
 using System.Collections.ObjectModel;
 using System.Globalization;
-using System.Linq;
 using System.Windows.Data;
 using System.Windows.Media;
 
@@ -10,19 +9,38 @@ namespace SWRIS.Converters
 {
     public class ConnectivityFaultToColorConverter : IMultiValueConverter
     {
-        private static readonly SolidColorBrush OnlineBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#00FF78"));
-        private static readonly SolidColorBrush OfflineBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#7886B2"));
-        private static readonly SolidColorBrush FaultBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF0000"));
+        private static readonly Color OnlineColor = Color.FromRgb(0x00, 0xFF, 0x78);
+        private static readonly Color OfflineColor = Color.FromRgb(0x78, 0x86, 0xB2);
+        private static readonly Color FaultColor = Color.FromRgb(0xFF, 0x00, 0x00);
+
+        private static readonly SolidColorBrush OnlineBrush;
+        private static readonly SolidColorBrush OfflineBrush;
+        private static readonly SolidColorBrush FaultBrush;
+
+        static ConnectivityFaultToColorConverter()
+        {
+            OnlineBrush = new SolidColorBrush(OnlineColor);
+            OfflineBrush = new SolidColorBrush(OfflineColor);
+            FaultBrush = new SolidColorBrush(FaultColor);
+
+            OnlineBrush.Freeze();
+            OfflineBrush.Freeze();
+            FaultBrush.Freeze();
+        }
 
         public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
         {
-            if (values.Length < 2 || !(values[0] is bool isConnected) || !(values[1] is bool isFaulting))
+            if (values.Length < 3 ||
+                !(values[0] is bool isConnected) ||
+                !(values[1] is ObservableCollection<FaultDataModel> faults))
+            {
                 return OfflineBrush;
+            }
 
             if (!isConnected)
                 return OfflineBrush;
 
-            if (isFaulting)
+            if (faults != null && faults.Count > 0)
                 return FaultBrush;
 
             return OnlineBrush;
@@ -33,4 +51,4 @@ namespace SWRIS.Converters
             throw new NotImplementedException();
         }
     }
-}
+}

+ 49 - 32
Converters/RiskLevelToColorConverter.cs

@@ -1,5 +1,6 @@
 using SWRIS.Enums;
 using System;
+using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Windows.Data;
@@ -9,49 +10,65 @@ namespace SWRIS.Converters
 {
     public class RiskLevelToColorConverter : IValueConverter
     {
-        private static readonly Dictionary<string, SolidColorBrush> _brushCache = new Dictionary<string, SolidColorBrush>();
+        // 预定义颜色(非冻结的Color结构,轻量)
+        private static readonly Color NormalColor = Color.FromRgb(0x00, 0xFF, 0x78);
+        private static readonly Color MildColor = Color.FromRgb(0x00, 0xFF, 0x78);
+        private static readonly Color LightColor = Color.FromRgb(0xFF, 0xF0, 0x00);
+        private static readonly Color ModerateColor = Color.FromRgb(0xFF, 0x94, 0x00);
+        private static readonly Color SevereColor = Color.FromRgb(0xFF, 0x6C, 0x00);
+        private static readonly Color CriticalColor = Color.FromRgb(0xFF, 0x00, 0x00);
+        private static readonly Color ExceededLimitColor = Color.FromRgb(0xFF, 0x00, 0x8A);
+
+        // 映射枚举到颜色
+        private static readonly Dictionary<RiskLevel, Color> ColorMap = new Dictionary<RiskLevel, Color>
+        {
+            { RiskLevel.Normal, NormalColor },
+            { RiskLevel.Mild, MildColor },
+            { RiskLevel.Light, LightColor },
+            { RiskLevel.Moderate, ModerateColor },
+            { RiskLevel.Severe, SevereColor },
+            { RiskLevel.Critical, CriticalColor },
+            { RiskLevel.ExceededLimit, ExceededLimitColor }
+        };
+
+        // 缓存:键为 "risk_opacity",值为冻结的SolidColorBrush
+        private static readonly ConcurrentDictionary<string, SolidColorBrush> _brushCache = new ConcurrentDictionary<string, SolidColorBrush>();
+
         public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
         {
             if (value is RiskLevel risk)
             {
-                double opacity = parameter != null ? System.Convert.ToDouble(parameter) : 1;
-                string cacheKey = $"{risk}_{opacity}";
+                double opacity = 1.0;
+                if (parameter != null)
+                {
+                    // 尝试解析,若失败则使用默认1.0
+                    double.TryParse(parameter.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out opacity);
+                }
+
+                // 构建缓存键
+                string cacheKey = $"{risk}_{opacity:F2}"; // 保留两位小数避免浮点数精度问题
                 // 如果缓存中存在,直接返回
                 if (_brushCache.TryGetValue(cacheKey, out var cachedBrush))
                 {
                     return cachedBrush;
                 }
-                SolidColorBrush colorBrush;
-                switch (risk)
+
+                // 获取颜色
+                if (!ColorMap.TryGetValue(risk, out var color))
                 {
-                    case RiskLevel.Normal:
-                    case RiskLevel.Mild:
-                        colorBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#00FF78"));
-                        break;
-                    case RiskLevel.Light:
-                        colorBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FFF000"));
-                        break;
-                    case RiskLevel.Moderate:
-                        colorBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF9400"));
-                        break;
-                    case RiskLevel.Severe:
-                        colorBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF6C00"));
-                        break;
-                    case RiskLevel.Critical:
-                        colorBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF0000"));
-                        break;
-                    case RiskLevel.ExceededLimit:
-                        colorBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF008A"));
-                        break;
-                    default:
-                        colorBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#00FF78"));
-                        break;
+                    color = NormalColor; // 默认
                 }
-                colorBrush.Opacity = opacity;
-                colorBrush.Freeze();
-                _brushCache[cacheKey] = colorBrush;
-                return colorBrush;
+
+                // 创建新画笔并设置透明度
+                var brush = new SolidColorBrush(color);
+                brush.Opacity = opacity;
+                brush.Freeze(); // 冻结以便跨线程使用
+
+                // 缓存
+                _brushCache[cacheKey] = brush;
+                return brush;
             }
+
             return Brushes.Transparent;
         }
 
@@ -60,4 +77,4 @@ namespace SWRIS.Converters
             throw new NotImplementedException();
         }
     }
-}
+}

+ 5 - 4
Converters/RunningStatusToBooleanConverter.cs

@@ -20,17 +20,18 @@ namespace SWRIS.Converters
         public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
         {
             if (!(value is bool boolValue) || parameter == null)
-                return RunningStatus.Stopped;
+                return Binding.DoNothing;
 
             if (!Enum.TryParse(parameter.ToString(), true, out RunningStatus targetStatus))
-                return RunningStatus.Stopped;
+                return Binding.DoNothing;
 
             if (boolValue)
             {
                 return targetStatus;
             }
 
-            return RunningStatus.Stopped;
+            // 取消选中时不覆盖源值,避免误清空当前运行状态
+            return Binding.DoNothing;
         }
         private RunningStatus GetRunningStatus(RunningStatus runningStatus)
         {
@@ -41,7 +42,7 @@ namespace SWRIS.Converters
             if (runningStatus.HasFlag(RunningStatus.SpatialNormal))
                 return RunningStatus.SpatialNormal;
 
-            return RunningStatus.Stopped;
+            return RunningStatus.SpatialNormal;
         }
     }
 }

+ 2 - 2
Converters/StringEmptyToVisibilityConverter.cs

@@ -12,9 +12,9 @@ namespace SWRIS.Converters
         {
             if (value == null || !(value is string message))
             {
-                return Visibility.Collapsed;
+                return Visibility.Hidden;
             }
-            return message.IsNullOrEmpty() ? Visibility.Collapsed : Visibility.Visible;
+            return message.IsNullOrEmpty() ? Visibility.Hidden : Visibility.Visible;
         }
 
         public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)

+ 22 - 0
Converters/SubtractConverter.cs

@@ -0,0 +1,22 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace SWRIS.Converters
+{
+    public class SubtractConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value is double height && parameter is string param)
+            {
+                if (double.TryParse(param, out double subtract))
+                    return Math.Max(0, height - subtract);
+            }
+            return 200; // 默认值
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+            => throw new NotImplementedException();
+    }
+}

+ 30 - 0
Converters/TrolleysToVisibilityConverter.cs

@@ -0,0 +1,30 @@
+using System;
+using System.Collections;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace SWRIS.Converters
+{
+    public class TrolleysToVisibilityConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            // 判断是否为 null
+            if (value == null)
+                return Visibility.Collapsed;
+
+            // 判断是否为集合且为空
+            if (value is ICollection collection)
+            {
+                return collection.Count == 0 ? Visibility.Collapsed : Visibility.Visible;
+            }
+            // 其他情况默认可见
+            return Visibility.Visible;
+        }
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 6 - 5
Core/ConfigHelper.cs

@@ -5,6 +5,7 @@ using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
+using System.Reflection;
 using System.Threading;
 
 namespace SWRIS.Core
@@ -100,14 +101,14 @@ namespace SWRIS.Core
             }
         }
 
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         public static bool SetEquipment(EquipmentModel equipmentData)
         {
             // 参数检查
-            if (equipmentData == null || equipmentData.Parameter == null || string.IsNullOrEmpty(equipmentData.IpAddress))
+            if (equipmentData == null || equipmentData.Parameter == null)
             {
                 return false;
             }
-
             try
             {
                 // 获取写入锁
@@ -136,10 +137,10 @@ namespace SWRIS.Core
                     // 获取设备数组
                     if (!(jsonObject["Equipments"] is JArray equipments)) return false;
 
-                    // 查找要修改的设备
+                    // 查找要修改的设备(以 RopeNumber 为查询条件)
                     var targetEquipment = equipments.FirstOrDefault(c =>
-                        c["IpAddress"] != null &&
-                        c["IpAddress"].ToString() == equipmentData.IpAddress);
+                        c["RopeNumber"] != null &&
+                        c["RopeNumber"].ToObject<int>() == equipmentData.RopeNumber);
 
                     if (targetEquipment == null) return false;
 

+ 153 - 84
Core/TcpServerFrame.cs

@@ -10,11 +10,11 @@ using System.IO;
 using System.Linq;
 using System.Net;
 using System.Net.Sockets;
+using System.Reflection;
 using System.Security.Cryptography;
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
-using static System.Windows.Forms.AxHost;
 
 namespace SWRIS.Core
 {
@@ -38,12 +38,11 @@ namespace SWRIS.Core
         public event EventHandler<EncoderDirectionResultReceivedEventArgs> EncoderDirectionResultReceived;
         public event EventHandler<HeartbeatReceviedEventArgs> HeartbeatReceived;
         public event EventHandler<DebugMessageReceivedEventArgs> DebugMessageReceived;
-
         private Socket _listener;
         private bool _isRunning;
         private readonly string _ipAddress;
         private readonly int _port;
-        private readonly ByteTransform byteTransform;
+        private readonly ByteTransform _byteTransform;
         private const string PASSWORD = "WNDTM4";
         private static ConcurrentDictionary<string, ClientState> _connectedClients;
         private CancellationTokenSource ctsToken = new CancellationTokenSource();
@@ -51,7 +50,7 @@ namespace SWRIS.Core
         {
             _ipAddress = ipAddress;
             _port = port;
-            byteTransform = new ByteTransform(DataFormat.DCBA);
+            _byteTransform = new ByteTransform(DataFormat.DCBA);
             _connectedClients = new ConcurrentDictionary<string, ClientState>();
         }
         public void Start()
@@ -176,7 +175,7 @@ namespace SWRIS.Core
                 }
             }, ctsToken);
         }
-
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         private async void OnDataReceived(IAsyncResult ar)
         {
             var state = (ClientState)ar.AsyncState;
@@ -358,6 +357,7 @@ namespace SWRIS.Core
         /// <param name="payload"></param>
         /// <param name="client"></param>
         /// <returns></returns>
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         private async Task ProcessEventByType(byte eventType, byte[] payload, ClientState client)
         {
             switch (eventType)
@@ -497,7 +497,6 @@ namespace SWRIS.Core
                 var liveStreamData = ReceiveLiveStreamDataModel(buffer);
                 if (liveStreamData != null && liveStreamData.Data.Any())
                 {
-                    Console.WriteLine($"接收实时流数据: {liveStreamData.ToString()}");
                     // 触发事件
                     LiveStreamReceived?.Invoke(this,
                         new LiveStreamReceivedEventArgs(client.IpAddress, liveStreamData));
@@ -581,6 +580,7 @@ namespace SWRIS.Core
                     if (detectionData.DamageCount <= 0)
                     {
                         DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"检测结果未发现损伤,本次结果不记录",
+                            ipAddress: client.IpAddress,
                             serialNo: client.SerialNo));
                     }
                     else
@@ -677,17 +677,28 @@ namespace SWRIS.Core
             (string serialNo, byte[] bytes) = GetSerialNoAndData(buffer);
             return bytes[0]; // 开启/关闭结果,0=成功,>0=失败-原因码
         }
+        private DateTime? _lastLiveStreamReceiveTime = null;
+
         public LiveStreamDataModel ReceiveLiveStreamDataModel(byte[] buffer)
         {
             (string serialNo, byte[] bytes) = GetSerialNoAndData(buffer);
             var liveStreamData = new LiveStreamDataModel()
             {
                 SensorCount = bytes[0],
-                SampleCount = byteTransform.TransUInt16(bytes, 1)
+                SampleCount = _byteTransform.TransUInt16(bytes, 1)
             };
 
             var data = bytes.Skip(3).ToArray(); // 跳过前3个字节
             liveStreamData.Data = ParseSensorData(data, liveStreamData.SensorCount, liveStreamData.SampleCount);
+
+            // 计算本次接收与上次接收的时间差(毫秒)
+            var now = DateTime.Now;
+            if (_lastLiveStreamReceiveTime.HasValue)
+            {
+                liveStreamData.SampleInterval = (int)(now - _lastLiveStreamReceiveTime.Value).TotalMilliseconds;
+            }
+            _lastLiveStreamReceiveTime = now;
+
             return liveStreamData;
         }
 
@@ -697,7 +708,7 @@ namespace SWRIS.Core
             var twistFactorData = new TwistFactorDataModel
             {
                 Type = bytes[0],
-                TwistFactor = byteTransform.TransSingle(bytes, 1) // 判伤捻距系数
+                TwistFactor = _byteTransform.TransSingle(bytes, 1) // 判伤捻距系数
             };
             return twistFactorData;
         }
@@ -707,6 +718,7 @@ namespace SWRIS.Core
         /// <param name="serialNo"></param>
         /// <param name="client"></param>
         /// <returns></returns>
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         public bool SendGetEncoderDirectionData(string serialNo, Socket clientSocket)
         {
             try
@@ -714,12 +726,14 @@ namespace SWRIS.Core
                 if (clientSocket == null || !clientSocket.Connected)
                     return false;
 
+                // 获取不含端口的IP地址
+                string ipAddress = ((IPEndPoint)clientSocket.RemoteEndPoint).Address.ToString();
                 using (var ms = new MemoryStream())
                 {
                     ms.WriteByte(0x00); // 操作,0 = 获取编码器方向,1 = 设置编码器方向
                     ms.WriteByte(0x01); // 编码器方向,1=正向,2=反向
                     byte[] buffer = CreateData(0x12, ms.ToArray(), serialNo);
-                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"获取设备编码器方向", serialNo: serialNo));
+                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"获取设备编码器方向", ipAddress: ipAddress, serialNo: serialNo));
                     clientSocket.Send(buffer);
                     return true;
                 }
@@ -737,6 +751,7 @@ namespace SWRIS.Core
         /// <param name="serialNo"></param>
         /// <param name="stream"></param>
         /// <returns></returns>
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         public bool SendSetEncoderDirectionData(EncoderDirection direction, string serialNo, Socket clientSocket)
         {
             try
@@ -744,12 +759,14 @@ namespace SWRIS.Core
                 if (clientSocket == null || !clientSocket.Connected)
                     return false;
 
+                // 获取不含端口的IP地址
+                string ipAddress = ((IPEndPoint)clientSocket.RemoteEndPoint).Address.ToString();
                 using (var ms = new MemoryStream())
                 {
                     ms.WriteByte(0x01); // 操作,0 = 获取编码器方向,1 = 设置编码器方向
                     ms.WriteByte((byte)direction); // 编码器方向,1=正向,2=反向
                     byte[] buffer = CreateData(0x12, ms.ToArray(), serialNo);
-                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"设置设备编码器方向", serialNo: serialNo));
+                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"设置设备编码器方向", ipAddress: ipAddress, serialNo: serialNo));
                     clientSocket.Send(buffer);
                     return true;
                 }
@@ -766,6 +783,7 @@ namespace SWRIS.Core
         /// <param name="serialNo"></param>
         /// <param name="clientSocket"></param>
         /// <returns></returns>
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         private bool SendGetDetectionRawData(string serialNo, Socket clientSocket)
         {
             try
@@ -773,13 +791,17 @@ namespace SWRIS.Core
                 if (clientSocket == null || !clientSocket.Connected)
                     return false;
 
+                // 获取不含端口的IP地址
+                string ipAddress = ((IPEndPoint)clientSocket.RemoteEndPoint).Address.ToString();
                 using (var ms = new MemoryStream())
                 {
                     ms.WriteByte(0); // 数据类型,0=正常,1=报警,2=故障
                     WriteBytes(ms, (short)2048); // 单包数据大小,单位:Byte
                     ms.WriteByte(0x00); // 传输速度控制,单位:KB/s(B/ms),为0则不控速
                     byte[] buffer = CreateData(0x0E, ms.ToArray(), serialNo);
-                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"获取检测结果原始数据", serialNo: serialNo));
+                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"获取检测结果原始数据",
+                                                                                         ipAddress: ipAddress,
+                                                                                         serialNo: serialNo));
                     clientSocket.Send(buffer);
                     return true;
                 }
@@ -795,16 +817,17 @@ namespace SWRIS.Core
         /// </summary>
         /// <param name="buffer"></param>
         /// <returns></returns>
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         public DetectionRawResultDataModel ReceiveDetectionRawResultData(byte[] buffer)
         {
             (string serialNo, byte[] bytes) = GetSerialNoAndData(buffer);
             var detectionRawResultData = new DetectionRawResultDataModel
             {
                 Code = bytes[0],
-                Timestamp = byteTransform.TransInt32(bytes, 1),
-                SamplingStep = byteTransform.TransSingle(bytes, 5),
-                StartAbsolutePosition = byteTransform.TransSingle(bytes, 9),
-                EndAbsolutePosition = byteTransform.TransSingle(bytes, 13),
+                Timestamp = _byteTransform.TransInt32(bytes, 1),
+                SamplingStep = _byteTransform.TransSingle(bytes, 5),
+                StartAbsolutePosition = _byteTransform.TransSingle(bytes, 9),
+                EndAbsolutePosition = _byteTransform.TransSingle(bytes, 13),
                 SensorCount = bytes[17]
             };
             return detectionRawResultData;
@@ -814,14 +837,15 @@ namespace SWRIS.Core
         /// </summary>
         /// <param name="buffer"></param>
         /// <returns></returns>
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         public DetectionRawDataModel ReceiveDetectionRawData(byte[] buffer)
         {
             (string serialNo, byte[] bytes) = GetSerialNoAndData(buffer);
             var detectionRawData = new DetectionRawDataModel
             {
-                PacketNumber = byteTransform.TransUInt32(bytes, 0),
-                TotalPackets = byteTransform.TransUInt32(bytes, 4),
-                DataLength = byteTransform.TransUInt16(bytes, 8),
+                PacketNumber = _byteTransform.TransUInt32(bytes, 0),
+                TotalPackets = _byteTransform.TransUInt32(bytes, 4),
+                DataLength = _byteTransform.TransUInt16(bytes, 8),
                 Data = bytes.Skip(10).ToArray() // 跳过前10个字节
             };
 
@@ -834,6 +858,7 @@ namespace SWRIS.Core
         /// <param name="serialNo">设备序列号</param>
         /// <param name="client"></param>
         /// <returns></returns>
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         public bool SendTurnLiveStreamData(bool isOpen, string serialNo, Socket clientSocket)
         {
             try
@@ -841,11 +866,16 @@ namespace SWRIS.Core
                 if (clientSocket == null || !clientSocket.Connected)
                     return false;
 
+                // 获取不含端口的IP地址
+                string ipAddress = ((IPEndPoint)clientSocket.RemoteEndPoint).Address.ToString();
+
                 using (var ms = new MemoryStream())
                 {
                     ms.WriteByte((byte)(isOpen ? 1 : 0)); // 0=关闭,1=开启
                     byte[] buffer = CreateData(0x0C, ms.ToArray(), serialNo);
-                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"{(isOpen ? "开启" : "关闭")}设备实时流", serialNo: serialNo));
+                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"{(isOpen ? "开启" : "关闭")}设备实时流",
+                                                                                         ipAddress: ipAddress,
+                                                                                         serialNo: serialNo));
                     clientSocket.Send(buffer);
                     return true;
                 }
@@ -869,13 +899,15 @@ namespace SWRIS.Core
                 if (clientSocket == null || !clientSocket.Connected)
                     return false;
 
+                // 获取不含端口的IP地址
+                string ipAddress = ((IPEndPoint)clientSocket.RemoteEndPoint).Address.ToString();
                 using (var ms = new MemoryStream())
                 {
                     ms.WriteByte(0x00); // 操作,0=获取判伤捻距系数,1=设置判伤捻距系数
                     WriteBytes(ms, 0); // 判伤捻距系数
                     byte[] buffer = CreateData(0x13, ms.ToArray(), serialNo);
                     DebugMessageReceived?.Invoke(this,
-                        new DebugMessageReceivedEventArgs($"获取设备判伤捻距系数", serialNo: serialNo));
+                        new DebugMessageReceivedEventArgs($"获取设备判伤捻距系数", ipAddress: ipAddress, serialNo: serialNo));
                     clientSocket.Send(buffer);
                     return true;
                 }
@@ -891,7 +923,7 @@ namespace SWRIS.Core
         /// </summary>
         /// <param name="twistFactor"></param>
         /// <param name="serialNo"></param>
-        /// <param name="stream"></param>
+        /// <param name="clientSocket"></param>
         /// <returns></returns>
         public bool SendSetTwistFactorData(float twistFactor, string serialNo, Socket clientSocket)
         {
@@ -945,7 +977,7 @@ namespace SWRIS.Core
             {
                 Code = bytes[0],
                 Type = bytes[1],
-                Time = byteTransform.TransInt32(bytes, 2)
+                Time = _byteTransform.TransInt32(bytes, 2)
             };
             return clockData;
         }
@@ -992,12 +1024,14 @@ namespace SWRIS.Core
                 if (clientSocket == null || !clientSocket.Connected)
                     return false;
 
+                // 获取不含端口的IP地址
+                string ipAddress = ((IPEndPoint)clientSocket.RemoteEndPoint).Address.ToString();
                 using (var ms = new MemoryStream())
                 {
                     ms.WriteByte(0x00); // 操作,0=获取时钟,1=设置时钟
                     WriteBytes(ms, 0);// 设置时间戳
                     byte[] buffer = CreateData(0x10, ms.ToArray(), serialNo);
-                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"获取设备时钟", serialNo: serialNo));
+                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"获取设备时钟", ipAddress: ipAddress, serialNo: serialNo));
                     clientSocket.Send(buffer);
                     return true;
                 }
@@ -1013,7 +1047,7 @@ namespace SWRIS.Core
         /// </summary>
         /// <param name="timestamp">时间戳</param>
         /// <param name="serialNo">序列号</param>
-        /// <param name="stream">网络数据流</param>
+        /// <param name="clientSocket">网络数据流</param>
         /// <returns></returns>
         public bool SendSetClockData(int timestamp, string serialNo, Socket clientSocket)
         {
@@ -1022,12 +1056,16 @@ namespace SWRIS.Core
                 if (clientSocket == null || !clientSocket.Connected)
                     return false;
 
+                // 获取不含端口的IP地址
+                string ipAddress = ((IPEndPoint)clientSocket.RemoteEndPoint).Address.ToString();
                 using (var ms = new MemoryStream())
                 {
                     ms.WriteByte(0x01); // 操作,0=获取时钟,1=设置时钟
                     WriteBytes(ms, timestamp);// 设置时间戳
                     byte[] buffer = CreateData(0x10, ms.ToArray(), serialNo);
-                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"设置设备时钟", serialNo: serialNo));
+                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"设置设备时钟",
+                                                                                         ipAddress: ipAddress,
+                                                                                         serialNo: serialNo));
                     clientSocket.Send(buffer);
                     return true;
                 }
@@ -1049,9 +1087,9 @@ namespace SWRIS.Core
             RealTimeDataModel realTimeData = new RealTimeDataModel
             {
                 Status = (RunningStatus)bytes[0],
-                Speed = byteTransform.TransSingle(bytes, 1),
-                AbsolutePosition = byteTransform.TransSingle(bytes, 5),
-                Position = byteTransform.TransSingle(bytes, 9)
+                Speed = _byteTransform.TransSingle(bytes, 1),
+                AbsolutePosition = _byteTransform.TransSingle(bytes, 5),
+                Position = _byteTransform.TransSingle(bytes, 9)
             };
             return realTimeData;
         }
@@ -1071,13 +1109,13 @@ namespace SWRIS.Core
             var data = verifyData.Data;
             DetectionDataModel detectionData = new DetectionDataModel
             {
-                StartTime = byteTransform.TransInt32(data, 0),
-                EndTime = byteTransform.TransInt32(data, 4),
-                StartPoint = byteTransform.TransSingle(data, 8),
-                EndPoint = byteTransform.TransSingle(data, 12),
-                DetectionLength = byteTransform.TransSingle(data, 16),
-                DetectedSpeed = byteTransform.TransSingle(data, 20),
-                DamageCount = byteTransform.TransInt16(data, 24),
+                StartTime = _byteTransform.TransInt32(data, 0),
+                EndTime = _byteTransform.TransInt32(data, 4),
+                StartPoint = _byteTransform.TransSingle(data, 8),
+                EndPoint = _byteTransform.TransSingle(data, 12),
+                DetectionLength = _byteTransform.TransSingle(data, 16),
+                DetectedSpeed = _byteTransform.TransSingle(data, 20),
+                DamageCount = _byteTransform.TransInt16(data, 24),
                 Damages = new List<DamageModel>()
             };
             for (int i = 0; i < detectionData.DamageCount; i++)
@@ -1087,8 +1125,8 @@ namespace SWRIS.Core
                 var damageData = new DamageModel
                 {
                     RopeNumber = data[offset],
-                    DamagePoint = byteTransform.TransSingle(data, offset + 1),
-                    DamageValue = byteTransform.TransSingle(data, offset + 5),
+                    DamagePoint = _byteTransform.TransSingle(data, offset + 1),
+                    DamageValue = _byteTransform.TransSingle(data, offset + 5),
                     DamageLevel = (DamageLevel)data[offset + 9]
                 };
                 detectionData.Damages.Add(damageData);
@@ -1112,8 +1150,8 @@ namespace SWRIS.Core
             AlarmDataModel alarmData = new AlarmDataModel
             {
                 RopeNumber = data[1],
-                DamagePosition = byteTransform.TransSingle(data, 2),
-                DamageValue = byteTransform.TransSingle(data, 6),
+                DamagePosition = _byteTransform.TransSingle(data, 2),
+                DamageValue = _byteTransform.TransSingle(data, 6),
                 DamageLevel = (DamageLevel)data[10]
             };
             return alarmData;
@@ -1134,7 +1172,7 @@ namespace SWRIS.Core
             FaultDataModel faultData = new FaultDataModel();
             var data = verifyData.Data;
             faultData.FaultType = (FaultType)data[0];
-            faultData.FaultCode = byteTransform.TransInt32(data, 1);
+            faultData.FaultCode = _byteTransform.TransInt32(data, 1);
             return faultData;
         }
         public byte ReceiveStartOrStopDetectionResultData(byte[] buffer)
@@ -1158,11 +1196,15 @@ namespace SWRIS.Core
         /// <returns></returns>
         private void RespondHeartbeat(byte[] buffer, string ipAddress, Socket clientSocket)
         {
-            if (buffer[1] == 0x00)
+            try
             {
-                byte[] response = new byte[] { 0xD0, 0x00 };
-                clientSocket.Send(response); // 直接使用Socket发送
+                if (buffer[1] == 0x00)
+                {
+                    byte[] response = new byte[] { 0xD0, 0x00 };
+                    clientSocket.Send(response); // 直接使用Socket发送
+                }
             }
+            finally { }
         }
         private async Task ProcessSerialNoData(byte[] buffer, ClientState client)
         {
@@ -1204,44 +1246,44 @@ namespace SWRIS.Core
             #endregion
 
             #region 基础参数
-            parameterData.SensorCount = byteTransform.TransInt16(data, index); index += 2;
-            parameterData.SamplingStep = byteTransform.TransSingle(data, index); index += 4;
-            parameterData.FrequencyDivisionFactor = byteTransform.TransInt16(data, index); index += 2;
-            parameterData.TimeDomainFrequency = byteTransform.TransInt16(data, index); index += 2;
+            parameterData.SensorCount = _byteTransform.TransInt16(data, index); index += 2;
+            parameterData.SamplingStep = _byteTransform.TransSingle(data, index); index += 4;
+            parameterData.FrequencyDivisionFactor = _byteTransform.TransInt16(data, index); index += 2;
+            parameterData.DamageInterval = _byteTransform.TransInt16(data, index); index += 2;
             #endregion
 
             #region 判伤参数
-            parameterData.DamageThreshold = byteTransform.TransInt16(data, index); index += 2;
-            parameterData.ScrapUpperLimit = byteTransform.TransInt16(data, index); index += 2;
-            parameterData.FrontMagnetLength = byteTransform.TransInt16(data, index); index += 2;
-            parameterData.BackMagnetLength = byteTransform.TransInt16(data, index); index += 2;
-            parameterData.ValueCoefficient = byteTransform.TransSingle(data, index); index += 4;
+            parameterData.DamageThreshold = _byteTransform.TransInt16(data, index); index += 2;
+            parameterData.ScrapUpperLimit = _byteTransform.TransInt16(data, index); index += 2;
+            parameterData.FrontMagnetLength = _byteTransform.TransInt16(data, index); index += 2;
+            parameterData.BackMagnetLength = _byteTransform.TransInt16(data, index); index += 2;
+            parameterData.ValueCoefficient = _byteTransform.TransSingle(data, index); index += 4;
             #endregion
 
             #region 零点位参数
-            parameterData.EffectiveStrokeLength = byteTransform.TransSingle(data, index); index += 4;
-            parameterData.ZeroPositionCorrectionDuration = byteTransform.TransInt16(data, index); index += 2;
-            parameterData.ZeroPositionCorrectionOffset = byteTransform.TransInt16(data, index); index += 2;
+            parameterData.EffectiveStrokeLength = _byteTransform.TransSingle(data, index); index += 4;
+            parameterData.ZeroPositionCorrectionDuration = _byteTransform.TransInt16(data, index); index += 2;
+            parameterData.ZeroPositionCorrectionOffset = _byteTransform.TransInt16(data, index); index += 2;
             #endregion
 
             #region 报警参数
-            parameterData.WarningValue = byteTransform.TransSingle(data, index); index += 4;
-            parameterData.AlarmValue = byteTransform.TransSingle(data, index); index += 4;
-            parameterData.SoundLightAlarmAutoResetMode = byteTransform.TransInt16(data, index); index += 2;
+            parameterData.WarningValue = _byteTransform.TransSingle(data, index); index += 4;
+            parameterData.AlarmValue = _byteTransform.TransSingle(data, index); index += 4;
+            parameterData.SoundLightAlarmAutoResetMode = _byteTransform.TransInt16(data, index); index += 2;
             #endregion
 
             #region 钢丝绳信息
-            parameterData.WireRopeType = byteTransform.TransInt16(data, index); index += 2;
-            parameterData.WireRopeCount = byteTransform.TransInt16(data, index); index += 2;
-            parameterData.WireRopeLength = byteTransform.TransSingle(data, index); index += 4;
-            parameterData.WireRopeDiameter = byteTransform.TransSingle(data, index); index += 4;
-            parameterData.WireRopeStrandCount = byteTransform.TransInt16(data, index); index += 2;
-            parameterData.WireRopeStrandWireCount = byteTransform.TransInt16(data, index); index += 2;
+            parameterData.WireRopeType = _byteTransform.TransInt16(data, index); index += 2;
+            parameterData.WireRopeCount = _byteTransform.TransInt16(data, index); index += 2;
+            parameterData.WireRopeLength = _byteTransform.TransSingle(data, index); index += 4;
+            parameterData.WireRopeDiameter = _byteTransform.TransSingle(data, index); index += 4;
+            parameterData.WireRopeStrandCount = _byteTransform.TransInt16(data, index); index += 2;
+            parameterData.WireRopeStrandWireCount = _byteTransform.TransInt16(data, index); index += 2;
             #endregion
 
             #region 限位校准位置
-            parameterData.FirstCalibrationPositiont = byteTransform.TransSingle(data, index); index += 4;
-            parameterData.SecondCalibrationPositiont = byteTransform.TransSingle(data, index); index += 4;
+            parameterData.FirstCalibrationPositiont = _byteTransform.TransSingle(data, index); index += 4;
+            parameterData.SecondCalibrationPositiont = _byteTransform.TransSingle(data, index); index += 4;
             #endregion
 
             return (verifyData.SerialNo, parameterData);
@@ -1264,7 +1306,7 @@ namespace SWRIS.Core
                     WriteBytes(ms, parameters.SensorCount);
                     WriteBytes(ms, parameters.SamplingStep);
                     WriteBytes(ms, parameters.FrequencyDivisionFactor);
-                    WriteBytes(ms, parameters.TimeDomainFrequency);
+                    WriteBytes(ms, parameters.DamageInterval);
 
                     // Damage judgment parameters
                     WriteBytes(ms, parameters.DamageThreshold);
@@ -1310,7 +1352,7 @@ namespace SWRIS.Core
         /// <summary>
         /// 发送开始或停止检测命令
         /// </summary>
-        /// <param name="mode">检测模式 0=停止,1=启动空域检测,2=启动时域检测</param>
+        /// <param name="runningMode">检测模式 0=停止,1=启动空域检测,2=启动时域检测</param>
         /// <param name="serialNo">设备序列号</param>
         /// <param name="clientSocket">数据流</param>
         public bool SendStartOrStopDetection(byte runningMode, string serialNo, Socket clientSocket)
@@ -1408,11 +1450,14 @@ namespace SWRIS.Core
                 if (clientSocket == null || !clientSocket.Connected)
                     return;
 
+                string ipAddress = ((IPEndPoint)clientSocket.RemoteEndPoint).Address.ToString();
                 using (var ms = new MemoryStream())
                 {
                     ms.WriteByte((byte)(isOpen ? 1 : 0)); // 0=关闭,1=开启
                     byte[] buffer = CreateData(0x06, ms.ToArray(), serialNo);
-                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"{(isOpen ? "开启" : "停止")}设备实时数据", serialNo: serialNo));
+                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"{(isOpen ? "开启" : "停止")}设备实时数据",
+                                                                                         ipAddress: ipAddress,
+                                                                                         serialNo: serialNo));
                     clientSocket.Send(buffer);
                 }
             }
@@ -1442,7 +1487,7 @@ namespace SWRIS.Core
         }
         private void WriteBytes(MemoryStream ms, dynamic value)
         {
-            var bytes = byteTransform.TransByte(value);
+            var bytes = _byteTransform.TransByte(value);
             ms.Write(bytes, 0, bytes.Length);
         }
         /// <summary>
@@ -1456,13 +1501,15 @@ namespace SWRIS.Core
             {
                 if (clientSocket == null || !clientSocket.Connected)
                     return false;
+
+                string ipAddress = ((IPEndPoint)clientSocket.RemoteEndPoint).Address.ToString();
                 using (var ms = new MemoryStream())
                 {
                     WriteBytes(ms, startTime);
                     WriteBytes(ms, endTime);
 
                     byte[] buffer = CreateData(0x09, ms.ToArray(), serialNo);
-                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"确认设备检测结果", serialNo: serialNo));
+                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"确认设备检测结果", ipAddress: ipAddress, serialNo: serialNo));
                     clientSocket.Send(buffer);
                     return true;
                 }
@@ -1531,6 +1578,7 @@ namespace SWRIS.Core
         /// </summary>
         /// <param name="buffer">数据</param>
         /// <returns></returns>
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         private VerifyDataModel GetVerifyDataAndCheckCaption(byte[] buffer)
         {
             VerifyDataModel verifyData = new VerifyDataModel() { Password = PASSWORD };
@@ -1551,7 +1599,7 @@ namespace SWRIS.Core
 
             // 读取随机码
             if (span.Length < 4) return verifyData;
-            verifyData.RandomCode = byteTransform.TransUInt32(span.ToArray(), 0);
+            verifyData.RandomCode = _byteTransform.TransUInt32(span.ToArray(), 0);
             span = span.Slice(4);
 
             // 读取验证码长度
@@ -1574,27 +1622,47 @@ namespace SWRIS.Core
         /// 验证码校验
         /// </summary>
         /// <returns></returns>
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         private static bool VerifyCaptcha(VerifyDataModel verifyData)
         {
+            if (verifyData == null)
+                throw new ArgumentNullException(nameof(verifyData));
+
+            if (verifyData.Captcha == null || verifyData.Captcha.Length < 16)
+                return false;
+
             byte[] computedHash;
             using (var md5 = MD5.Create())
             {
-                computedHash = md5.ComputeHash(Encoding.UTF8.GetBytes(verifyData.SerialNo + verifyData.Password + verifyData.RandomCode));
-            }
-            var buffer = verifyData.Captcha;
-            for (int i = 0; i < 16; i++)
-            {
-                if (buffer[i] != computedHash[i])
-                    return false;
+                computedHash = md5.ComputeHash(
+                    Encoding.UTF8.GetBytes(
+                        verifyData.SerialNo + verifyData.Password + verifyData.RandomCode));
             }
 
-            return true;
+            // 常量时间比较(手动实现)
+            return FixedTimeEquals(verifyData.Captcha, computedHash, 16);
         }
 
+        /// <summary>
+        /// 常量时间比较两个字节数组,防止时序攻击
+        /// </summary>
+        private static bool FixedTimeEquals(byte[] a, byte[] b, int length)
+        {
+            if (a.Length < length || b.Length < length)
+                return false;
+
+            int result = 0;
+            for (int i = 0; i < length; i++)
+            {
+                result |= a[i] ^ b[i];  // XOR 操作,相同为0,不同为非0
+            }
+            return result == 0;
+        }
         ///<summary>
         ///创建数据验证码
         ///</summary>
         /// <returns></returns>
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         protected byte[] CreateData(byte eventType, byte[] data, string serialNo)
         {
             using (var md5 = MD5.Create())
@@ -1603,7 +1671,7 @@ namespace SWRIS.Core
                 {
                     // 1. 预先计算所有字段的长度
                     uint randomCode = (uint)DateTimeOffset.UtcNow.ToUnixTimeSeconds();
-                    byte[] randomCodeBytes = byteTransform.TransByte(randomCode);
+                    byte[] randomCodeBytes = _byteTransform.TransByte(randomCode);
                     byte[] hash = md5.ComputeHash(Encoding.ASCII.GetBytes(serialNo + PASSWORD + randomCode));
                     byte[] captcha = SoftBasic.BytesToAsciiBytes(hash);
 
@@ -1625,7 +1693,7 @@ namespace SWRIS.Core
                     else
                     {
                         // 假设byteTransform.TransByte将short转为2字节大端序
-                        byte[] remainingLengthBytes = byteTransform.TransByte(remainingLength);
+                        byte[] remainingLengthBytes = _byteTransform.TransByte(remainingLength);
                         ms.Write(remainingLengthBytes, 0, remainingLengthBytes.Length);
                     }
 
@@ -1640,7 +1708,7 @@ namespace SWRIS.Core
                 }
             }
         }
-
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         public ushort[][] ParseSensorData(byte[] data, int sensorCount, int sampleCount)
         {
             // 检查数据长度是否匹配
@@ -1678,6 +1746,7 @@ namespace SWRIS.Core
         /// <param name="bytes"></param>
         /// <param name="startIndex"></param>
         /// <returns></returns>
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         public static (int BytesConsumed, int Length) GetRemainingLength(byte[] bytes)
         {
             if (bytes == null || bytes.Length < 2) return (0, 0);
@@ -1705,7 +1774,7 @@ namespace SWRIS.Core
 
             return (bytesConsumed, value);
         }
-
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         private static (string SerialNo, byte[] Bytes) GetSerialNoAndData(byte[] buffer)
         {
             // 使用Span直接操作内存

+ 14 - 0
Dtos/FaultsMessageDto.cs

@@ -0,0 +1,14 @@
+using SWRIS.Models;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace SWRIS.Dtos
+{
+    public class FaultsMessageDto
+    {
+        public string RopeName { get; set; }
+        public List<FaultDataModel> Messages { get; set; }
+        public string DisplayMessage => $"{string.Join(",", Messages.Select(f => f.ToString()))}";
+
+    }
+}

+ 0 - 4
Enums/FaultType.cs

@@ -2,10 +2,6 @@
 {
     public enum FaultType
     {
-        /// <summary>
-        /// 无故障
-        /// </summary>
-        None = 0,
         /// <summary>
         /// 传感器故障
         /// </summary>

+ 3 - 0
Extensions/DatFileHandler.cs

@@ -1,12 +1,14 @@
 using SWRIS.Core;
 using System;
 using System.IO;
+using System.Reflection;
 using System.Text.Json;
 
 namespace SWRIS.Extensions
 {
     public class DatFileHandler
     {
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         public static bool CreateDatFile(string filePath, MemoryStream memoryData)
         {
             try
@@ -25,6 +27,7 @@ namespace SWRIS.Extensions
         {
             return GetDateFilePath(AppDomain.CurrentDomain.BaseDirectory, serialNo);
         }
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         public static byte[] ReadDatFile(string filePath)
         {
             filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, filePath);

+ 3 - 0
Extensions/JsonCryptoHelper.cs

@@ -1,6 +1,7 @@
 using Newtonsoft.Json;
 using System;
 using System.IO;
+using System.Reflection;
 using System.Security.Cryptography;
 
 namespace SWRIS.Extensions
@@ -31,6 +32,7 @@ namespace SWRIS.Extensions
             return JsonConvert.DeserializeObject<T>(json);
         }
 
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         private static byte[] EncryptStringToBytes(string plainText, string key, string iv)
         {
             byte[] keyBytes = Convert.FromBase64String(key);
@@ -60,6 +62,7 @@ namespace SWRIS.Extensions
             return encrypted;
         }
 
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         private static string DecryptStringFromBytes(byte[] cipherText, string key, string iv)
         {
             byte[] keyBytes = Convert.FromBase64String(key);

+ 1 - 1
Extensions/SimpleLogger.cs

@@ -149,7 +149,7 @@ namespace SWRIS.Extensions
         }
         public void Log(string content, string level, Exception ex)
         {
-#if Debug
+#if DEBUG
             Console.WriteLine(content);
 #endif
             string text = LogFormat(content, level, ex);

+ 8 - 0
Extensions/SoftAuth.cs

@@ -2,6 +2,7 @@
 using System;
 using System.IO;
 using System.Management;
+using System.Reflection;
 using System.Security.Cryptography;
 using System.Text;
 
@@ -16,6 +17,7 @@ namespace SWRIS.Core
         /// 获取本计算机唯一的机器码(长度取前12个字符)
         /// </summary>
         /// <returns>字符串形式的机器码</returns>
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         public static string GetMachineCode()
         {
             string unique = "";
@@ -42,6 +44,7 @@ namespace SWRIS.Core
         /// 获取本机激活码
         /// </summary>
         /// <returns></returns>
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         public static string GetFinalCode()
         {
             return MD5Encrypt(GetMachineCode()).Substring(0, 16);
@@ -50,6 +53,7 @@ namespace SWRIS.Core
         /// 判断是否已激活
         /// </summary>
         /// <returns></returns>
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         public static bool IsAuthorizeSuccess()
         {
             if (GetFinalCode() == ReadRegistry())
@@ -66,6 +70,7 @@ namespace SWRIS.Core
         /// </summary>
         /// <param name="finalCode">用户输入的注册码</param>
         /// <returns></returns>
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         public static bool CheckFinalCode(string finalCode)
         {
             if (GetFinalCode() == finalCode.Trim().ToUpper())
@@ -117,6 +122,7 @@ namespace SWRIS.Core
         /// <param name="pToEncrypt">待加密的数据</param>
         /// <param name="password">密钥,长度为8,英文或数字</param>
         /// <returns>加密后的数据</returns>
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         public static string MD5Encrypt(string pToEncrypt, string password = "!234Qwer")
         {
             DESCryptoServiceProvider des = new DESCryptoServiceProvider();
@@ -140,6 +146,7 @@ namespace SWRIS.Core
         /// </summary>
         /// <param name="code"></param>
         /// <returns></returns>
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         public static bool WriteRegistry(string code)
         {
             try
@@ -155,6 +162,7 @@ namespace SWRIS.Core
         /// 读取注册表内激活码
         /// </summary>
         /// <returns></returns>
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         public static string ReadRegistry()
         {
             try

+ 54 - 5
Extensions/Tools.cs

@@ -149,19 +149,68 @@ namespace SWRIS.Extensions
         /// <returns></returns>
         public static bool CheckPortInUse(int port)
         {
-            IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties();
-            IPEndPoint[] endPoints = ipProperties.GetActiveTcpListeners();
+            int currentProcessId = Process.GetCurrentProcess().Id;
 
-            foreach (IPEndPoint endPoint in endPoints)
+            var ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties();
+            var tcpListeners = ipGlobalProperties.GetActiveTcpListeners();
+
+            foreach (var ep in tcpListeners)
             {
-                if (endPoint.Port == port)
+                if (ep.Port == port)
                 {
-                    return true;
+                    // 用命令行查询占用端口的进程
+                    var processId = GetProcessIdByPort(port);
+                    if (processId.HasValue && processId.Value != currentProcessId)
+                    {
+                        return true; // 被其他进程占用
+                    }
+                    return false; // 被自己占用或未占用
                 }
             }
+
             return false;
         }
 
+        /// <summary>
+        /// 使用netstat获取端口对应的进程ID
+        /// </summary>
+        /// <param name="port"></param>
+        /// <returns></returns>
+        private static int? GetProcessIdByPort(int port)
+        {
+            try
+            {
+                var startInfo = new ProcessStartInfo
+                {
+                    FileName = "netstat",
+                    Arguments = $"-ano | findstr :{port}",
+                    UseShellExecute = false,
+                    RedirectStandardOutput = true,
+                    CreateNoWindow = true
+                };
+
+                using (Process process = Process.Start(startInfo))
+                {
+                    string output = process.StandardOutput.ReadToEnd();
+
+                    foreach (string line in output.Split('\n'))
+                    {
+                        if (line.Contains($"TCP") && line.Contains($":{port}") && line.Contains("LISTENING"))
+                        {
+                            var parts = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
+                            if (parts.Length >= 5 && int.TryParse(parts[4], out int pid))
+                            {
+                                return pid;
+                            }
+                        }
+                    }
+                }
+            }
+            catch { }
+
+            return null;
+        }
+
         public static byte[] GetMD5Bytes(string input)
         {
             using (MD5 md5 = MD5.Create())

+ 130 - 90
MainWindow.xaml

@@ -1,108 +1,148 @@
-<pu:WindowX x:Class="SWRIS.MainWindow" 
+<pu:WindowX x:Class="SWRIS.MainWindow"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:cvt="clr-namespace:SWRIS.Converters"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-            xmlns:local="clr-namespace:SWRIS" WindowStyle="None" 
+            xmlns:local="clr-namespace:SWRIS" 
+            WindowStyle="None"
             FontFamily="{StaticResource PuHuiTiRegular}"
-            ResizeMode="CanMinimize" Title="{Binding AppName}" Foreground="#FFFFFF"
-            xmlns:pu="clr-namespace:Panuon.WPF.UI;assembly=Panuon.WPF.UI" 
-            xmlns:vm="clr-namespace:SWRIS.Models.ViewModel" MaskBrush="#66000000"
-            mc:Ignorable="d" Closing="Window_Closing" Background="#141332" 
-            Height="1080" Width="1920" Left="0" Top="0" Loaded="WindowX_Loaded"
+            ResizeMode="CanMinimize" 
+            Title="{Binding AppName}" 
+            Foreground="#FFFFFF"
+            xmlns:pu="clr-namespace:Panuon.WPF.UI;assembly=Panuon.WPF.UI"
+            xmlns:vm="clr-namespace:SWRIS.Models.ViewModel"
+            xmlns:extension="clr-namespace:SWRIS.Extensions" 
+            xmlns:ctr="clr-namespace:SWRIS.Controls" 
+            d:DataContext="{d:DesignInstance Type=vm:MainViewModel}"
+            MaskBrush="#66000000"
+            mc:Ignorable="d" 
+            Closing="Window_Closing" 
+            Background="#141332"
+            WindowState="Maximized"
+            Left="0" Top="0"
+            Loaded="WindowX_Loaded" 
             pu:WindowXCaption.Height="0">
-    <Window.DataContext>
-        <vm:MainViewModel/>
-    </Window.DataContext>
     <Window.Resources>
         <cvt:MenuNameToPositionConverter x:Key="MenuNameToPositionConverter"/>
         <cvt:MenuNameToColorConverter x:Key="MenuNameToColorConverter"/>
         <cvt:StringEmptyToVisibilityConverter x:Key="StringEmptyToVisibilityConverter"/>
+        <cvt:TrolleysToVisibilityConverter x:Key="TrolleysToVisibilityConverter"/>
     </Window.Resources>
+    <!-- 外层 Grid 占满整个窗口 -->
     <Grid>
-        <Grid.RowDefinitions>
-            <RowDefinition Height="90"/>
-            <RowDefinition Height="*"/>
-            <RowDefinition Height="50"/>
-        </Grid.RowDefinitions>
-        <StackPanel Orientation="Horizontal" Grid.Row="0" pu:WindowX.IsDragMoveArea="True" MouseDown="StackPanel_MouseDown">
-            <StackPanel.Background>
-                <ImageBrush  ImageSource="/Resources/menu_bg.png"/>
-            </StackPanel.Background>
-            <Grid>
-                <Grid.ColumnDefinitions>
-                    <ColumnDefinition Width="715"/>
-                    <ColumnDefinition Width="460"/>
-                    <ColumnDefinition Width="340"/>
-                    <ColumnDefinition Width="*"/>
-                </Grid.ColumnDefinitions>
-                <StackPanel Orientation="Horizontal" Grid.Column="0">
-                    <Image Source="/Resources/logo.png" Margin="56,19,19,20"/>
-                    <TextBlock Text="{Binding AppName}" VerticalAlignment="Center" FontSize="40" FontWeight="Bold" FontFamily="{StaticResource PuHuiTiRegular}"/>
+        <!-- 关键:使用 ViewBox 包裹内容,设定基准分辨率为 1920x1080 -->
+        <!-- Stretch="Uniform" 保证等比例缩放,不会变形 -->
+        <Viewbox Stretch="Uniform">
+            <Grid x:Name="RootGrid" Width="1920" Height="1080">
+                <Grid.RowDefinitions>
+                    <!-- 这里的行高保持你原来的设计值 -->
+                    <RowDefinition Height="90"/>
+                    <RowDefinition Height="*"/>
+                    <RowDefinition Height="35"/>
+                </Grid.RowDefinitions>
+                <!-- 顶部菜单区域 -->
+                <StackPanel Orientation="Horizontal" Grid.Row="0" 
+                            Grid.ColumnSpan="2"
+                            pu:WindowX.IsDragMoveArea="True" 
+                            MouseDown="StackPanel_MouseDown">
+                    <StackPanel.Background>
+                        <ImageBrush ImageSource="/Resources/menu_bg.png"/>
+                    </StackPanel.Background>
+                    <Grid>
+                        <Grid.ColumnDefinitions>
+                            <!-- 这里的列宽保持原设计值,ViewBox会自动缩放 -->
+                            <ColumnDefinition Width="700"/>
+                            <ColumnDefinition Width="430"/>
+                            <ColumnDefinition Width="340"/>
+                            <ColumnDefinition Width="*"/>
+                        </Grid.ColumnDefinitions>
+                        <!-- Logo 区域 -->
+                        <StackPanel Orientation="Horizontal" Grid.Column="0">
+                            <Image Source="/Resources/logo.png" Margin="56,19,19,20"/>
+                            <TextBlock Text="{Binding AppName}" 
+                                       VerticalAlignment="Center" 
+                                       FontSize="40" 
+                                       FontWeight="Bold" 
+                                       FontFamily="{StaticResource PuHuiTiRegular}"/>
+                        </StackPanel>
+                        <!-- 动态菜单区域 (包含下划线动画) -->
+                        <Canvas Grid.Column="1">
+                            <!-- 下划线图片 -->
+                            <Image x:Name="popupImage" 
+                                   Source="/Resources/menu_dec.png" 
+                                   Width="300"
+                                   Canvas.Left="{Binding CurrentPage,Mode=TwoWay,Converter={StaticResource MenuNameToPositionConverter}}"/>
+                            <!-- 菜单文字 -->
+                            <StackPanel Orientation="Horizontal">
+                                <TextBlock Text="主页" Tag="Home" FontWeight="Bold" FontFamily="{StaticResource PuHuiTiRegular}" FontSize="30" Padding="30,0"
+                                           Margin="90,25,20,25" Cursor="Hand"
+                                           Foreground="{Binding CurrentPage,Converter={StaticResource MenuNameToColorConverter},ConverterParameter=Home}"
+                                           MouseDown="Menu_MouseDown"/>
+                                <TextBlock Text="记录" Tag="Record" FontWeight="Bold" FontFamily="{StaticResource PuHuiTiRegular}" FontSize="30" Padding="30,0"
+                                           Margin="20,25,90,25" Cursor="Hand"
+                                           Foreground="{Binding CurrentPage,Converter={StaticResource MenuNameToColorConverter},ConverterParameter=Record}"
+                                           MouseDown="Menu_MouseDown"/>
+                            </StackPanel>
+                        </Canvas>
+                        <!-- 实例切换区域 -->
+                        <ItemsControl Grid.Column="2" ItemsSource="{Binding SwitchInstances}">
+                            <ItemsControl.ItemsPanel>
+                                <ItemsPanelTemplate>
+                                    <StackPanel Margin="0,-20,0,0" Orientation="Horizontal" HorizontalAlignment="Left"/>
+                                </ItemsPanelTemplate>
+                            </ItemsControl.ItemsPanel>
+                            <ItemsControl.ItemTemplate>
+                                <DataTemplate>
+                                    <Border Background="#3650BE" Width="105" Height="75" Tag="{Binding ProgressName}" 
+                                            CornerRadius="0,0,15,15" Margin="10,0"
+                                            MouseLeftButtonDown="SwitchToInstance_MouseDown" Cursor="Hand">
+                                        <StackPanel Orientation="Vertical" VerticalAlignment="Center">
+                                            <Image Source="{Binding ImageSource}" Width="34" Margin="3"/>
+                                            <TextBlock Text="{Binding InstanceName}" FontSize="18" FontWeight="Bold"
+                                                       HorizontalAlignment="Center" Foreground="#AEBAE8"/>
+                                        </StackPanel>
+                                    </Border>
+                                </DataTemplate>
+                            </ItemsControl.ItemTemplate>
+                        </ItemsControl>
+                        <!-- 右侧工具栏 -->
+                        <StackPanel Orientation="Horizontal" Grid.Column="3" HorizontalAlignment="Right" Margin="0,0,0,0">
+                            <StackPanel Orientation="Vertical" Margin="50,0,0,0" VerticalAlignment="Center">
+                                <TextBlock Text="{Binding SystemTime.Date,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
+                                           FontSize="20" FontWeight="Bold" LineHeight="18" LineStackingStrategy="BlockLineHeight"/>
+                                <TextBlock Text="{Binding SystemTime.Time,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
+                                           FontSize="34" FontWeight="Bold" FontFamily="{StaticResource PuHuiTiRegular}"
+                                           LineHeight="38" LineStackingStrategy="BlockLineHeight"/>
+                            </StackPanel>
+                            <Image Source="/Resources/setting.png" MouseDown="Setting_MouseDown"  Margin="35,0,0,0"  Width="64" Cursor="Hand"/>
+                            <Image Source="/Resources/minimize.png" MouseDown="Minimize_MouseDown" Width="42" Margin="25,0,0,0"  Cursor="Hand"/>
+                            <Image Source="/Resources/closed.png" MouseDown="Close_MouseDown" Width="42" Margin="25,0,0,0" Cursor="Hand"/>
+                        </StackPanel>
+                    </Grid>
                 </StackPanel>
-                <Canvas Grid.Column="1">
-                    <Image x:Name="popupImage" Source="/Resources/menu_dec.png" Width="344"
-                           Canvas.Left="{Binding CurrentPage,Mode=TwoWay,Converter={StaticResource MenuNameToPositionConverter}}"/>
-                    <StackPanel Orientation="Horizontal">
-                        <TextBlock Text="主页" Tag="Home" FontWeight="Bold" FontFamily="{StaticResource PuHuiTiRegular}" FontSize="30" Padding="30,0" Margin="110,25,20,25" Cursor="Hand"
-                                   Foreground="{Binding CurrentPage,Converter={StaticResource MenuNameToColorConverter},ConverterParameter=Home}"
-                                   MouseDown="Menu_MouseDown"/>
-                        <TextBlock Text="记录" Tag="Record" FontWeight="Bold" FontFamily="{StaticResource PuHuiTiRegular}" FontSize="30" Padding="30,0" Margin="20,25,110,25" Cursor="Hand" 
-                                   Foreground="{Binding CurrentPage,Converter={StaticResource MenuNameToColorConverter},ConverterParameter=Record}" 
-                                   MouseDown="Menu_MouseDown"/>
+                <!-- 中间内容区域 -->
+                <Frame x:Name="Main_Frame" Grid.Row="1" NavigationUIVisibility="Hidden" />
+                <!-- 底部状态栏 -->
+                <DockPanel Grid.Row="3" Margin="20 0"  Grid.ColumnSpan="2">
+                    <StackPanel Orientation="Horizontal" Margin="5,5"
+                                Visibility="{Binding DebugMessage.Message,Converter={StaticResource StringEmptyToVisibilityConverter}}">
+                        <TextBlock Text="&#xE9ce;" FontSize="17" FontFamily="{StaticResource PanuonIconFont}" VerticalAlignment="Center" Foreground="#7F74FF"/>
+                        <TextBlock Text="{Binding DebugMessage.DateTime,StringFormat=HH:mm:ss}" FontSize="16" Margin="5,0"
+                                   VerticalAlignment="Center"
+                                   Foreground="#7F74FF"/>
+                        <TextBlock Text="{Binding DebugMessage.Message}" FontSize="16" Margin="5,0"
+                                   VerticalAlignment="Center"
+                                   Foreground="#7F74FF"/>
                     </StackPanel>
-                </Canvas>
-                <ItemsControl  Grid.Column="2" ItemsSource="{Binding SwitchInstances}">
-                    <ItemsControl.ItemsPanel>
-                        <ItemsPanelTemplate>
-                            <StackPanel Margin="0,-20,0,0" Orientation="Horizontal" HorizontalAlignment="Left"/>
-                        </ItemsPanelTemplate>
-                    </ItemsControl.ItemsPanel>
-                    <ItemsControl.ItemTemplate>
-                        <DataTemplate>
-                            <Border Background="#3650BE" Width="105" Height="75" Tag="{Binding ProgressName}" CornerRadius="0,0,15,15" Margin="10,0"
-                                    MouseLeftButtonDown="SwitchToInstance_MouseDown" Cursor="Hand">
-                                <StackPanel Orientation="Vertical" VerticalAlignment="Center">
-                                    <Image Source="{Binding ImageSource}" Width="34" Margin="3"/>
-                                    <TextBlock Text="{Binding InstanceName}" FontSize="18" FontWeight="Bold" HorizontalAlignment="Center" Foreground="#AEBAE8"/>
-                                </StackPanel>
-                            </Border>
-                        </DataTemplate>
-                    </ItemsControl.ItemTemplate>
-                </ItemsControl>
-                <StackPanel Orientation="Horizontal" Grid.Column="3" HorizontalAlignment="Right" Margin="0,0,0,0">
-                    <Image Source="/Resources/setting.png" MouseDown="Setting_MouseDown" Width="64" Cursor="Hand"/>
-                    <StackPanel Orientation="Vertical" Margin="30,0,0,0" VerticalAlignment="Center">
-                        <TextBlock Text="{Binding SystemTime.Date,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" 
-                                   FontSize="20" FontWeight="Bold" LineHeight="18" LineStackingStrategy="BlockLineHeight"/>
-                        <TextBlock Text="{Binding SystemTime.Time,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
-                                   FontSize="34" FontWeight="Bold" FontFamily="{StaticResource PuHuiTiRegular}"
-                                   LineHeight="36" LineStackingStrategy="BlockLineHeight"/>
+                    <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
+                        <TextBlock FontSize="18" Text="版本 " Foreground="#5B7098"/>
+                        <TextBlock FontSize="18" Text="{Binding Version}" Margin="0,0,30,0" Foreground="#5B7098"/>
+                        <TextBlock FontSize="18" Text="©" Foreground="#5B7098"/>
+                        <TextBlock Text="{Binding Copyright}" FontSize="18" Foreground="#5B7098"/>
                     </StackPanel>
-                    <Image Source="/Resources/minimize.png" MouseDown="Minimize_MouseDown" Width="42" Margin="55,0,0,0" Cursor="Hand"/>
-                    <Image Source="/Resources/closed.png" MouseDown="Close_MouseDown" Width="42" Margin="25,0,0,0" Cursor="Hand"/>
-                </StackPanel>
+                </DockPanel>
             </Grid>
-        </StackPanel>
-        <Frame x:Name="main_frame" Grid.Row="1"  NavigationUIVisibility="Hidden"/>
-        <DockPanel Grid.Row="2" Margin="20 0">
-            <StackPanel Orientation="Horizontal" Margin="5,5" 
-                            Visibility="{Binding DebugMessage.Message,Converter={StaticResource StringEmptyToVisibilityConverter}}">
-                <TextBlock Text="&#xE9ce;" FontSize="17" FontFamily="{StaticResource PanuonIconFont}" VerticalAlignment="Center" Foreground="#7F74FF"/>
-                <TextBlock Text="{Binding  DebugMessage.DateTime,StringFormat=HH:mm:ss}" FontSize="16" Margin="5,0" 
-                                           VerticalAlignment="Center" 
-                                           Foreground="#7F74FF"/>
-                <TextBlock Text="{Binding DebugMessage.Message}" FontSize="16" Margin="5,0" 
-                                           VerticalAlignment="Center"
-                                           Foreground="#7F74FF"/>
-            </StackPanel>
-            <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
-                <TextBlock FontSize="20" Text="版本 " Foreground="#5B7098"/>
-                <TextBlock FontSize="20" Text="{Binding Version}" Margin="0,0,30,0" Foreground="#5B7098"/>
-                <TextBlock FontSize="20" Text="©" Foreground="#5B7098"/>
-                <TextBlock Text="{Binding Copyright}" FontSize="20" Foreground="#5B7098"/>
-            </StackPanel>
-        </DockPanel>
+        </Viewbox>
     </Grid>
-</pu:WindowX>
+</pu:WindowX>

+ 135 - 51
MainWindow.xaml.cs

@@ -12,13 +12,14 @@ using System;
 using System.ComponentModel;
 using System.IO;
 using System.Linq;
+using System.Reflection;
 using System.Speech.Synthesis;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Markup;
-
+using Tools = SWRIS.Extensions.Tools;
 namespace SWRIS
 {
     /// <summary>
@@ -26,6 +27,8 @@ namespace SWRIS
     /// </summary>
     public partial class MainWindow : WindowX, IComponentConnector
     {
+        // 屏幕比例阈值
+        private const double ASPECT_RATIO_THRESHOLD = 1.5; // 大于此值视为宽屏(16:9),小于此值视为方屏(4:3)
         private readonly SpeechSynthesizer speech;
         public MainViewModel MainView { get; set; }
 
@@ -41,16 +44,12 @@ namespace SWRIS
                 Rate = 1,
                 Volume = 100
             };
-            MainView = new MainViewModel()
-            {
-                AppName = App.Config.AppName,
-                Version = App.Config.Version,
-                Copyright = App.Config.Copyright,
-                SwitchInstances = App.Config.SwitchInstances
-            };
+
+            MainView = new MainViewModel();
             Application.Current.MainWindow = this;
             App.TcpServer.ClientConnected += TcpServer_ClientConnected;
             App.TcpServer.ClientDisconnected += TcpServer_ClientDisconnected;
+            App.TcpServer.LiveStreamReceived += TcpServer_LiveStreamReceived;
             App.TcpServer.AlarmDataReceived += TcpServer_AlarmDataReceived;
             App.TcpServer.DetectionDataReceived += TcpServer_DetectionDataReceived;
             App.TcpServer.RealTimeDataReceived += TcpServer_RealTimeDataReceived;
@@ -64,13 +63,27 @@ namespace SWRIS
             App.Calibration.ConnectionStateChanged += Calibration_ConnectionStateChanged;
             App.Calibration.LimitStateChanged += Calibration_LimitStateChanged;
 
-
             App.TcpServer.Start();
 
             DataContext = MainView;
         }
 
-
+        private async void TcpServer_LiveStreamReceived(object sender, LiveStreamReceivedEventArgs e)
+        {
+            var equipmentData = App.DataCenter.Equipments.FirstOrDefault(c => c.IpAddress == e.IpAddress);
+            if (equipmentData != null)
+            {
+                if (equipmentData.DetectionData?.DetectionNo == equipmentData.DetectionNo)
+                {
+                    equipmentData.ClearData();
+                }
+                await Task.Run(() =>
+                {
+                    equipmentData.Chart?.AddDataPoints((equipmentData.AbsolutePosition, e.LiveStream), equipmentData.InUseSensor);
+                    equipmentData.AddLiveStream(e.LiveStream);
+                });
+            }
+        }
 
         /// <summary>
         /// 接收心跳数据
@@ -97,11 +110,17 @@ namespace SWRIS
                 DebugMessageShow(e.Message, e.IpAddress, e.SerialNo);
             }
         }
+        /// <summary>
+        /// 显示调试信息
+        /// </summary>
+        /// <param name="message"> 调试信息 </param>
+        /// <param name="ipAddress"> IP地址 </param>
+        /// <param name="serialNo"> 序列号 </param>
         public void DebugMessageShow(string message, string ipAddress, string serialNo)
         {
             if (App.Config.ShowDebugMessage)
             {
-                var equipmentData = App.DataCenter.Equipments.FirstOrDefault(c => c.IpAddress == ipAddress || (serialNo != null && c.SerialNo == serialNo));
+                var equipmentData = App.DataCenter.Equipments.FirstOrDefault(c => (!serialNo.IsNullOrEmpty() && c.SerialNo == serialNo) || c.IpAddress == ipAddress);
                 if (equipmentData != null)
                 {
                     equipmentData.DebugMessage.DateTime = DateTime.Now;
@@ -109,6 +128,10 @@ namespace SWRIS
                 }
             }
         }
+        /// <summary>
+        /// 显示调试信息
+        /// </summary>
+        /// <param name="message"> 调试信息 </param>
         public void DebugMessageShow(string message)
         {
             if (App.Config.ShowDebugMessage)
@@ -120,6 +143,7 @@ namespace SWRIS
         /// <summary>
         /// 标定限位状态更改
         /// </summary>
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         private void Calibration_LimitStateChanged(object sender, LimitData limitData)
         {
             foreach (var equipment in App.DataCenter.Equipments)
@@ -214,6 +238,7 @@ namespace SWRIS
         /// <summary>
         /// 接收实时数据
         /// </summary>
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         private void TcpServer_RealTimeDataReceived(object sender, RealTimeDataReceivedEventArgs e)
         {
             var equipmentData = App.DataCenter.Equipments.FirstOrDefault(c => c.IpAddress == e.IpAddress);
@@ -232,6 +257,7 @@ namespace SWRIS
         /// <summary>
         /// 客户端连接成功
         /// </summary>
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         private void TcpServer_ClientConnected(object sender, ClientConnectedEventArgs e)
         {
             var equipment = App.Config.Equipments.FirstOrDefault(c => c.IpAddress == e.IpAddress);
@@ -308,6 +334,7 @@ namespace SWRIS
         /// <summary>
         /// 接受检测结果原始数据
         /// </summary>
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         private async void TcpServer_DetectionRawDataReceived(object sender, DetectionRawDataReceivedEventArgs e)
         {
             var equipmentData = App.DataCenter.Equipments.FirstOrDefault(c => c.IpAddress == e.IpAddress);
@@ -443,7 +470,7 @@ namespace SWRIS
                 {
                     await Task.Delay(2000);
                     string url = $"http://192.168.1.200"; // 替换为您的网页地址
-                    Extensions.Tools.OpenUrl(url);
+                    Tools.OpenUrl(url);
                 }
             });
         }
@@ -451,47 +478,110 @@ namespace SWRIS
         {
             Application.Current.Dispatcher.Invoke(() =>
             {
-                if (main_frame.Content is Page currentPage && currentPage != null)
+                if (Main_Frame.Content is Page currentPage && currentPage != null)
                 {
                     var simpleRecord = record.ToSimpleRecordDto();
-                    switch (currentPage.Title)
+                    if (currentPage.Title == "RealTimePage")
                     {
-                        case "OneRopePage":
-                            if (currentPage.DataContext is OneRopeViewModel oneRopeView)
+                        if (currentPage.DataContext is RealTimeViewModel realTimeView)
+                        {
+                            realTimeView.AddRecord(simpleRecord);
+                        }
+                    }
+                }
+            });
+        }
+
+        private void WindowX_Loaded(object sender, RoutedEventArgs e)
+        {
+            AdjustViewboxStretch();
+
+            NavigateToPage("Home");
+
+            // 在程序启动时开启一个后台线程,每隔15秒检测一次
+            Task.Run(async () =>
+            {
+                bool lastIpOk = true;
+                bool lastPortOk = true;
+
+                while (true)
+                {
+                    bool ipOk = Tools.CheckIpAddressExists(App.Config.IpAddress);
+                    bool portOk = !Tools.CheckPortInUse(App.Config.Port);
+
+                    // IP异常
+                    if (!ipOk)
+                    {
+                        NoticeBox.Show($"当前在用网卡的IP地址中没有发现[{App.Config.IpAddress}],请检查网络配置。",
+                                      "警告", MessageBoxIcon.Warning, true, 5000);
+                    }
+                    // IP从异常恢复正常
+                    else if (!lastIpOk && ipOk)
+                    {
+                        if (!HasConnectedDevice())
+                        {
+                            NoticeBox.Show($"IP地址[{App.Config.IpAddress}]已恢复,5秒后将自动重启程序", "提示", MessageBoxIcon.Info, true, 5000);
+                            if (HasConnectedDevice())
                             {
-                                oneRopeView.AddRecord(simpleRecord);
+                                NoticeBox.Show($"检测到有设备处于连接状态,已取消自动重启", "提示", MessageBoxIcon.Info, true, 5000);
                             }
-                            break;
-                        case "TwoRopesPage":
-                            if (currentPage.DataContext is TwoRopesViewModel twoRopesView)
+                            else
                             {
-                                twoRopesView.AddRecord(simpleRecord);
+                                await RestartApplication();
                             }
-                            break;
-                        case "ThreeRopesPage":
-                            if (currentPage.DataContext is ThreeRopesViewModel threeRopesView)
+                        }
+                    }
+
+                    // 端口异常
+                    if (!portOk)
+                    {
+                        NoticeBox.Show("当前配置的端口已被占用,请更换端口或关闭占用该端口的程序。",
+                                      "警告", MessageBoxIcon.Warning, true, 5000);
+                    }
+                    // 端口从异常恢复正常
+                    else if (!lastPortOk && portOk)
+                    {
+                        if (!HasConnectedDevice())
+                        {
+                            NoticeBox.Show($"端口[{App.Config.Port}]已释放,5秒后将自动重启程序", "提示", MessageBoxIcon.Info, true, 5000);
+                            if (HasConnectedDevice())
                             {
-                                threeRopesView.AddRecord(simpleRecord);
+                                NoticeBox.Show($"检测到有设备处于连接状态,已取消自动重启", "提示", MessageBoxIcon.Info, true, 5000);
                             }
-                            break;
-                        case "FourRopesPage":
-                            if (currentPage.DataContext is FourRopesViewModel fourRopesView)
+                            else
                             {
-                                fourRopesView.AddRecord(simpleRecord);
+                                await RestartApplication();
                             }
-                            break;
+                        }
                     }
+
+                    lastIpOk = ipOk;
+                    lastPortOk = portOk;
+
+                    await Task.Delay(15000); // 每15秒检测一次
                 }
             });
         }
+        private async Task RestartApplication(int delay = 5000)
+        {
+            await Task.Delay(delay);
+            Application.Current.Dispatcher.Invoke(() =>
+            {
+                App.RestartApplication();
+            });
 
-        private void WindowX_Loaded(object sender, RoutedEventArgs e)
+        }
+
+        /// <summary>
+        /// 检查是否有设备处于连接状态
+        /// </summary>
+        private bool HasConnectedDevice()
         {
-            NavigateToPage("Home");
+            return App.DataCenter.Equipments.Any(e => e.IsConnect);
         }
         public void NavigatePage(Uri uri)
         {
-            main_frame.NavigationService.Navigate(uri);
+            Main_Frame.NavigationService.Navigate(uri);
         }
         private void NavigateToPage(object pageName)
         {
@@ -499,26 +589,11 @@ namespace SWRIS
             {
                 case "Home":
                     MainView.CurrentPage = "Home";
-                    int equipmentCount = App.Config.Equipments.Count;
-                    switch (equipmentCount)
-                    {
-                        case 1:
-                            main_frame.NavigationService.Navigate(new Uri($"Pages/RealTime/OneRopePage.xaml", uriKind: UriKind.Relative));
-                            break;
-                        case 2:
-                            main_frame.NavigationService.Navigate(new Uri($"Pages/RealTime/TwoRopesPage.xaml", uriKind: UriKind.Relative));
-                            break;
-                        case 3:
-                            main_frame.NavigationService.Navigate(new Uri($"Pages/RealTime/ThreeRopesPage.xaml", uriKind: UriKind.Relative));
-                            break;
-                        case 4:
-                            main_frame.NavigationService.Navigate(new Uri($"Pages/RealTime/FourRopesPage.xaml", uriKind: UriKind.Relative));
-                            break;
-                    }
+                    Main_Frame.NavigationService.Navigate(new Uri($"Pages/RealTime/RealTimePage.xaml", uriKind: UriKind.Relative));
                     break;
                 case "Record":
                     MainView.CurrentPage = "Record";
-                    main_frame.NavigationService.Navigate(new Uri($"Pages/RecordPage.xaml", uriKind: UriKind.Relative));
+                    Main_Frame.NavigationService.Navigate(new Uri($"Pages/RecordPage.xaml", uriKind: UriKind.Relative));
                     break;
             }
         }
@@ -526,7 +601,7 @@ namespace SWRIS
         {
             if (sender is TextBlock menuItem)
             {
-                CleanNavigation(main_frame);
+                CleanNavigation(Main_Frame);
                 NavigateToPage(menuItem?.Tag);
             }
         }
@@ -584,5 +659,14 @@ namespace SWRIS
                 App.SwitchToExistingInstance(instanceBorder.Tag.ToString());
             }
         }
+        private void AdjustViewboxStretch()
+        {
+            var screen = System.Windows.Forms.Screen.FromHandle(
+                new System.Windows.Interop.WindowInteropHelper(this).Handle);
+
+            double aspectRatio = (double)screen.Bounds.Width / screen.Bounds.Height;
+
+            RootGrid.Height = aspectRatio <= ASPECT_RATIO_THRESHOLD ? 1440 : 1080;
+        }
     }
 }

+ 8 - 2
Mapping.cs

@@ -19,6 +19,7 @@ namespace SWRIS
             // 赋值基本属性
             equipmentModel.RopeNumber = equipmentSettingViewModel.RopeNumber;
             equipmentModel.RopeName = equipmentSettingViewModel.RopeName;
+            equipmentModel.TrolleyId = equipmentSettingViewModel.TrolleyId;
             equipmentModel.IpAddress = equipmentSettingViewModel.IpAddress;
             equipmentModel.SerialNo = equipmentSettingViewModel.SerialNo;
             equipmentModel.Supplier = equipmentSettingViewModel.Supplier;
@@ -29,6 +30,7 @@ namespace SWRIS
             equipmentModel.WireSurfaceType = (WireSurfaceType)equipmentSettingViewModel.WireSurfaceType;
             equipmentModel.RopeCoreType = (RopeCoreType)equipmentSettingViewModel.RopeCoreType;
             equipmentModel.DisableAlarm = equipmentSettingViewModel.DisableAlarm;
+            equipmentModel.InUseSensor = equipmentSettingViewModel.InUseSensor;
             // 如果源Parameter为null,确保目标Parameter不为null
             if (equipmentSettingViewModel.Parameter == null)
             {
@@ -50,7 +52,7 @@ namespace SWRIS
             targetParam.SamplingStep = sourceParam.SamplingStep;
             targetParam.MainBoardSoftwareVersion = sourceParam.MainBoardSoftwareVersion;
             targetParam.FrequencyDivisionFactor = sourceParam.FrequencyDivisionFactor;
-            targetParam.TimeDomainFrequency = sourceParam.TimeDomainFrequency;
+            targetParam.DamageInterval = sourceParam.DamageInterval;
             targetParam.DamageThreshold = sourceParam.DamageThreshold;
             targetParam.ScrapUpperLimit = sourceParam.ScrapUpperLimit;
             targetParam.BackMagnetLength = sourceParam.BackMagnetLength;
@@ -96,6 +98,7 @@ namespace SWRIS
             // 赋值基本属性
             equipmentSettingViewModel.RopeNumber = equipmentModel.RopeNumber;
             equipmentSettingViewModel.RopeName = equipmentModel.RopeName;
+            equipmentSettingViewModel.TrolleyId = equipmentModel.TrolleyId;
             equipmentSettingViewModel.IpAddress = equipmentModel.IpAddress;
             equipmentSettingViewModel.SerialNo = equipmentModel.SerialNo;
             equipmentSettingViewModel.Supplier = equipmentModel.Supplier;
@@ -107,6 +110,7 @@ namespace SWRIS
             equipmentSettingViewModel.WireSurfaceType = (int)equipmentModel.WireSurfaceType;
             equipmentSettingViewModel.RopeCoreType = (int)equipmentModel.RopeCoreType;
             equipmentSettingViewModel.DisableAlarm = equipmentModel.DisableAlarm;
+            equipmentSettingViewModel.InUseSensor = equipmentModel.InUseSensor;
             // 如果源Parameter为null,确保目标Parameter不为null
             if (equipmentModel.Parameter == null)
             {
@@ -128,7 +132,7 @@ namespace SWRIS
             targetParam.SensorCount = sourceParam.SensorCount;
             targetParam.SamplingStep = sourceParam.SamplingStep;
             targetParam.FrequencyDivisionFactor = sourceParam.FrequencyDivisionFactor;
-            targetParam.TimeDomainFrequency = sourceParam.TimeDomainFrequency;
+            targetParam.DamageInterval = sourceParam.DamageInterval;
             targetParam.DamageThreshold = sourceParam.DamageThreshold;
             targetParam.ScrapUpperLimit = sourceParam.ScrapUpperLimit;
             targetParam.FrontMagnetLength = sourceParam.FrontMagnetLength;
@@ -160,10 +164,12 @@ namespace SWRIS
             // 赋值基本属性
             equipmentDataModel.RopeNumber = equipmentModel.RopeNumber;
             equipmentDataModel.RopeName = equipmentModel.RopeName;
+            equipmentDataModel.TrolleyId = equipmentModel.TrolleyId;
             equipmentDataModel.IpAddress = equipmentModel.IpAddress;
             equipmentDataModel.SerialNo = equipmentModel.SerialNo;
             equipmentDataModel.LiftHightRatio = equipmentModel.LiftHightRatio;
             equipmentDataModel.DisableAlarm = equipmentModel.DisableAlarm;
+            equipmentDataModel.InUseSensor = equipmentModel.InUseSensor;
             // 如果有Parameter数据,设置相关属性
             if (equipmentModel.Parameter != null)
             {

+ 15 - 5
Models/CalibrationDataModel.cs

@@ -14,7 +14,7 @@ namespace SWRIS.Models
         private int moduleType;
         private TcpDataModel tcp;
         private SerialDataModel serial;
-        private ObservableCollection<LimitDataModel> limits;
+        private ObservableCollection<TrolleyLimitDataModel> trolleys;
         public int ModuleType
         {
             get => moduleType;
@@ -42,13 +42,13 @@ namespace SWRIS.Models
                 OnPropertyChanged(nameof(Serial));
             }
         }
-        public ObservableCollection<LimitDataModel> Limits
+        public ObservableCollection<TrolleyLimitDataModel> Trolleys
         {
-            get => limits;
+            get => trolleys;
             set
             {
-                limits = value;
-                OnPropertyChanged(nameof(Limits));
+                trolleys = value;
+                OnPropertyChanged(nameof(Trolleys));
             }
         }
         public List<KeyAndValueDto> ModuleTypes => TypeExtension.ToKeyAndDescriptionList(typeof(ModuleType));
@@ -169,6 +169,7 @@ namespace SWRIS.Models
     public class LimitDataModel : INotifyPropertyChanged
     {
         private string name;
+        private int trolleyId;
         private string address;
         private double position;
         private bool isReverse;
@@ -220,6 +221,15 @@ namespace SWRIS.Models
                 OnPropertyChanged(nameof(IsEnable));
             }
         }
+        public int TrolleyId
+        {
+            get => trolleyId;
+            set
+            {
+                trolleyId = value;
+                OnPropertyChanged(nameof(TrolleyId));
+            }
+        }
         public List<RopeDataModel> RelatedRopes
         {
             get => relatedRopes;

+ 7 - 0
Models/ConfigModel.cs

@@ -22,6 +22,7 @@ namespace SWRIS.Models
         public RiskLevel SoundRiskLevel { get; set; } = RiskLevel.Moderate;
         public double SpeedVariationThreshold { get; set; } = 0.25;
         public string Crane { get; set; }
+        public List<TrolleyData> Trolleys { get; set; }
         public List<EquipmentModel> Equipments { get; set; }
         public List<SwitchInstance> SwitchInstances { get; set; }
         public CalibrationSettings Calibration { get; set; }
@@ -84,8 +85,14 @@ namespace SWRIS.Models
         public int Min { get; set; }
         public int Max { get; set; }
     }
+    public class TrolleyData
+    {
+        public int Id { get; set; }
+        public string Name { get; set; }
+    }
     public class LimitData
     {
+        public int TrolleyId { get; set; }
         public string Name { get; set; }
         public string Address { get; set; }
         public double Position { get; set; }

+ 6 - 1
Models/DataCenterModel.cs

@@ -1,4 +1,5 @@
-using SWRIS.Enums;
+using SWRIS.Dtos;
+using SWRIS.Enums;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.ComponentModel;
@@ -19,6 +20,7 @@ namespace SWRIS.Models
                     {
                         RopeNumber = equipment.RopeNumber,
                         RopeName = equipment.RopeName,
+                        TrolleyId = equipment.TrolleyId,
                         IpAddress = equipment.IpAddress,
                         SerialNo = equipment.SerialNo,
                         SensorCount = equipment.Parameter.SensorCount,
@@ -26,6 +28,7 @@ namespace SWRIS.Models
                         RopeLength = equipment.Parameter.WireRopeLength,
                         LiftHightRatio = equipment.LiftHightRatio,
                         DisableAlarm = equipment.DisableAlarm,
+                        InUseSensor = equipment.InUseSensor,
                         VariationThreshold = App.Config.SpeedVariationThreshold
                     };
                     limits?.ForEach(limit =>
@@ -46,6 +49,8 @@ namespace SWRIS.Models
             }
         }
         public ObservableCollection<EquipmentDataModel> Equipments { get; set; } = new ObservableCollection<EquipmentDataModel>();
+        public ObservableCollection<FaultsMessageDto> Messages { get; set; } = new ObservableCollection<FaultsMessageDto>();
+
 
         public event PropertyChangedEventHandler PropertyChanged;
         protected internal virtual void OnPropertyChanged(string propertyName)

+ 166 - 36
Models/EquipmentDataModel.cs

@@ -1,4 +1,5 @@
-using ScottPlot;
+using SWRIS.Controls;
+using SWRIS.Dtos;
 using SWRIS.Enums;
 using System;
 using System.Collections.Generic;
@@ -15,6 +16,7 @@ namespace SWRIS.Models
     {
         private int ropeNumber;
         private string ropeName;
+        private int trolleyId;
         private string ipAddress;
         private string serialNo;
         private bool isConnect = false;
@@ -23,6 +25,7 @@ namespace SWRIS.Models
         private double ropeLength = 100f; // 默认长度为100米
         private RunningStatus runningStatus = RunningStatus.Stopped;
         private DirectionState direction;
+        private FaultType? faultType;
         private double speed;
         private double position;
         private double absolutePosition;
@@ -42,14 +45,13 @@ namespace SWRIS.Models
         private double samplingStep;
         private DebugMessageModel debugMessage = new DebugMessageModel(string.Empty);
         private bool isAlerting = false;
-        private bool isFaulting = false;
         private bool isSpeedStable = true;
         private double variationThreshold = 0.25;
         private bool isHeartbeatReceived;
         private uint detectionNo;
         private bool disableAlarm;
         private int[] inUseSensor = new int[] { 1, 2, 3, 4 };
-
+        private RealTimeLineChart chart;
 
         /// <summary>
         /// 钢丝绳绳号
@@ -68,6 +70,14 @@ namespace SWRIS.Models
             set { ropeName = value; OnPropertyChanged(nameof(RopeName)); }
         }
         /// <summary>
+        /// 小车Id
+        /// </summary>
+        public int TrolleyId
+        {
+            get => trolleyId;
+            set { trolleyId = value; OnPropertyChanged(nameof(TrolleyId)); }
+        }
+        /// <summary>
         /// 设备Ip地址
         /// </summary>
         public string IpAddress
@@ -136,6 +146,15 @@ namespace SWRIS.Models
             get => direction;
             set { direction = value; OnPropertyChanged(nameof(Direction)); }
         }
+        /// <summary>
+        /// 故障类型
+        /// </summary>
+        public FaultType? FaultType
+        {
+            get => faultType;
+            set { faultType = value; OnPropertyChanged(nameof(FaultType)); }
+        }
+
         /// <summary>
         /// 速度
         /// </summary>
@@ -296,14 +315,6 @@ namespace SWRIS.Models
             get => isAlerting;
             set { isAlerting = value; OnPropertyChanged(nameof(IsAlerting)); }
         }
-        /// <summary>
-        /// 是否故障中
-        /// </summary>
-        public bool IsFaulting
-        {
-            get => isFaulting;
-            set { isFaulting = value; OnPropertyChanged(nameof(IsFaulting)); }
-        }
         public bool IsHeartbeatReceived
         {
             get => isHeartbeatReceived;
@@ -349,49 +360,169 @@ namespace SWRIS.Models
             get => inUseSensor;
             set { inUseSensor = value; OnPropertyChanged(nameof(InUseSensor)); }
         }
+        public RealTimeLineChart Chart
+        {
+            get => chart;
+            set
+            {
+                chart = value;
+            }
+        }
+        public ObservableCollection<FaultDataModel> Faults { get; set; } = new ObservableCollection<FaultDataModel>();
 
         public ObservableCollection<AlarmDataModel> Alarms { get; set; } = new ObservableCollection<AlarmDataModel>();
-        public Dictionary<FaultType, FaultDataModel> Faults { get; set; } = new Dictionary<FaultType, FaultDataModel>();
-        public ObservableCollection<string> Messages { get; set; } = new ObservableCollection<string>();
+
         public ObservableCollection<LimitModel> Limits { get; set; } = new ObservableCollection<LimitModel>();
-        public Coordinates[] ChartSource { get; internal set; }
         public Queue<double> RecentSpeeds { get; internal set; } = new Queue<double>();
-        public void AddFault(FaultDataModel faultData)
+        private readonly object _liveStreamLock = new object();
+        public Queue<int[]> LiveStreams { get; internal set; } = new Queue<int[]>();
+        public ObservableCollection<int> LiveStreamValues { get; set; } = new ObservableCollection<int>();
+        public void AddLiveStream(LiveStreamDataModel liveStream)
         {
-            if (Faults.ContainsKey(faultData.FaultType))
+            if (liveStream?.Data == null || liveStream.Data.Length == 0)
+                return;
+
+            lock (_liveStreamLock)
             {
-                if (faultData.FaultCode == 0)
+                // 新数据到来时清空之前的实时流数据
+                LiveStreams.Clear();
+
+                int sensorCount = liveStream.SensorCount;
+                int sampleCount = liveStream.Data.Length;
+
+                // 本次与上次接收的时间间隔对应的采样点数量(每50ms 1个点)
+                int targetCount = Math.Max(1, liveStream.SampleInterval / 50);
+                int actualCount = Math.Min(targetCount, sampleCount);
+
+                // 从 sampleCount 个采样点中平均抽取 actualCount 个点
+                for (int i = 0; i < actualCount; i++)
                 {
-                    Faults.Remove(faultData.FaultType);
+                    int sourceIndex = actualCount == 1
+                        ? 0
+                        : (int)Math.Round((double)i * (sampleCount - 1) / (actualCount - 1));
+
+                    var point = new int[sensorCount];
+                    for (int s = 0; s < sensorCount; s++)
+                    {
+                        point[s] = liveStream.Data[sourceIndex][s];
+                    }
+                    LiveStreams.Enqueue(point);
                 }
-                else
+            }
+        }
+
+        /// <summary>
+        /// 从实时流队列取出并移除一项(取出即移出)
+        /// </summary>
+        public bool TryDequeueLiveStream(out int[] sample)
+        {
+            lock (_liveStreamLock)
+            {
+                if (LiveStreams.Count > 0)
                 {
-                    Faults[faultData.FaultType] = faultData;
+                    sample = LiveStreams.Dequeue();
+                    return true;
                 }
             }
-            else if (faultData.FaultCode != 0)
+            sample = null;
+            return false;
+        }
+        public void AddFault(FaultDataModel fault)
+        {
+            if (fault == null)
+                return;
+
+            // 无故障(HasFault 为 false):取消对应的故障(含编码器故障)
+            if (!fault.HasFault)
+            {
+                RemoveFault(fault.FaultType);
+                return;
+            }
+
+            // 有故障:查找同类型的已有故障
+            var existingFault = Faults.FirstOrDefault(c => c.FaultType == fault.FaultType);
+            if (existingFault == null)
+            {
+                // 无同类型故障,直接添加
+                Faults.Add(fault);
+                AddFaultMessage(fault);
+                return;
+            }
+
+            // 已有同类型故障:判断故障传感器是否一致
+            if (!existingFault.FaultySensors.SequenceEqual(fault.FaultySensors))
+            {
+                // 故障传感器不一致,更新为最新的故障信息
+                existingFault.FaultCode = fault.FaultCode;
+                UpdateFaultMessage(fault.FaultType, fault.FaultCode);
+            }
+            // 传感器一致则忽略,不重复添加
+        }
+
+        /// <summary>
+        /// 取消指定类型的故障(含编码器故障)
+        /// </summary>
+        private void RemoveFault(FaultType faultType)
+        {
+            var existing = Faults.FirstOrDefault(c => c.FaultType == faultType);
+            if (existing != null)
             {
-                Faults.Add(faultData.FaultType, faultData);
+                Faults.Remove(existing);
             }
 
-            Application.Current.Dispatcher.Invoke(() =>
+            var message = App.DataCenter.Messages.FirstOrDefault(m => m.RopeName == RopeName);
+            if (message != null)
             {
-                if (Messages.Any())
+                Application.Current.Dispatcher.Invoke(() =>
                 {
-                    Messages.Clear();
-                }
-                foreach (var fault in Faults)
+                    message.Messages.RemoveAll(c => c.FaultType == faultType);
+                });
+            }
+        }
+
+        /// <summary>
+        /// 添加故障到消息中心
+        /// </summary>
+        private void AddFaultMessage(FaultDataModel fault)
+        {
+            var message = App.DataCenter.Messages.FirstOrDefault(m => m.RopeName == RopeName);
+            if (message == null)
+            {
+                Application.Current.Dispatcher.Invoke(() =>
                 {
-                    if (fault.Value != null)
+                    App.DataCenter.Messages.Add(new FaultsMessageDto
                     {
-                        Messages.Add(fault.Value.ToString());
-                    }
-                }
-            });
-            if (Messages.Any())
-                IsFaulting = true;
+                        RopeName = RopeName,
+                        Messages = new List<FaultDataModel> { fault }
+                    });
+                });
+            }
             else
-                IsFaulting = false;
+            {
+                Application.Current.Dispatcher.Invoke(() =>
+                {
+                    message.Messages.Add(fault);
+                });
+            }
+        }
+
+        /// <summary>
+        /// 更新消息中心中指定类型的故障码
+        /// </summary>
+        private void UpdateFaultMessage(FaultType faultType, int faultCode)
+        {
+            var message = App.DataCenter.Messages.FirstOrDefault(m => m.RopeName == RopeName);
+            if (message != null)
+            {
+                Application.Current.Dispatcher.Invoke(() =>
+                {
+                    var msgFault = message.Messages.FirstOrDefault(c => c.FaultType == faultType);
+                    if (msgFault != null)
+                    {
+                        msgFault.FaultCode = faultCode;
+                    }
+                });
+            }
         }
         public void AddSpeedData(double speed)
         {
@@ -466,7 +597,6 @@ namespace SWRIS.Models
             SevereCount = 0;
             CriticalCount = 0;
             ExtremeCount = 0;
-            ChartSource = null;
             RiskLevel = RiskLevel.Normal;
             DetectionNo += 1;
             Application.Current.Dispatcher.Invoke(() => Alarms.Clear());

+ 8 - 1
Models/EquipmentModel.cs

@@ -14,6 +14,10 @@ namespace SWRIS.Models
         /// </summary>
         public string RopeName { get; set; }
         /// <summary>
+        /// 小车机构Id
+        /// </summary>
+        public int TrolleyId { get; set; }
+        /// <summary>
         /// 地址
         /// </summary>
         public string IpAddress { get; set; }
@@ -53,7 +57,10 @@ namespace SWRIS.Models
         /// 禁用报警
         /// </summary>
         public bool DisableAlarm { get; set; }
-
+        /// <summary>
+        /// 在用传感器
+        /// </summary>
+        public int[] InUseSensor { get; set; } = new int[] { 1, 2, 3, 4 };
         /// <summary>
         /// 参数
         /// </summary>

+ 5 - 67
Models/LiveStreamDataModel.cs

@@ -1,6 +1,4 @@
-using System;
-
-namespace SWRIS.Models
+namespace SWRIS.Models
 {
     public class LiveStreamDataModel
     {
@@ -19,69 +17,9 @@ namespace SWRIS.Models
         /// </summary>
         public ushort[][] Data { get; set; }
 
-        public override string ToString()
-        {
-            // 创建一个字符串构建器来高效拼接字符串
-            var sb = new System.Text.StringBuilder();
-
-            // 添加类名
-            sb.Append(nameof(LiveStreamDataModel));
-            sb.Append(" { ");
-
-            // 添加基本属性
-            sb.Append("SensorCount = ").Append(SensorCount);
-            sb.Append(", SampleCount = ").Append(SampleCount);
-
-            // 添加数据摘要(如果Data不为空)
-            if (Data != null && Data.Length > 0)
-            {
-                sb.Append(", Data = [ ");
-
-                // 计算要显示的传感器数量(最多显示3个,避免输出过长)
-                int sensorsToShow = Math.Min(Data.Length, 3);
-
-                for (int i = 0; i < sensorsToShow; i++)
-                {
-                    sb.Append("[");
-
-                    if (Data[i] != null && Data[i].Length > 0)
-                    {
-                        // 计算要显示的采样点数量(最多显示5个)
-                        int samplesToShow = Math.Min(Data[i].Length, 5);
-
-                        for (int j = 0; j < samplesToShow; j++)
-                        {
-                            sb.Append(Data[i][j]);
-
-                            if (j < samplesToShow - 1)
-                                sb.Append(", ");
-                        }
-
-                        // 如果采样点超过显示数量,添加省略号
-                        if (Data[i].Length > 5)
-                            sb.Append(", ...");
-                    }
-
-                    sb.Append("]");
-
-                    if (i < sensorsToShow - 1)
-                        sb.Append(", ");
-                }
-
-                // 如果传感器数量超过显示数量,添加省略号
-                if (Data.Length > 3)
-                    sb.Append(", ...");
-
-                sb.Append(" ]");
-            }
-            else
-            {
-                sb.Append(", Data = null");
-            }
-
-            sb.Append(" }");
-
-            return sb.ToString();
-        }
+        /// <summary>
+        /// 采样间隔
+        /// </summary>
+        public int SampleInterval { get; set; } = 0;
     }
 }

+ 2 - 2
Models/ParameterModel.cs

@@ -22,9 +22,9 @@ namespace SWRIS.Models.Data
         /// </summary>
         public short FrequencyDivisionFactor { get; set; }
         /// <summary>
-        /// 时域频率  2字节整型
+        /// 判伤间隔  2字节整型
         /// </summary>
-        public short TimeDomainFrequency { get; set; }
+        public short DamageInterval { get; set; }
         /// <summary>
         /// 损伤门限   2字节整型
         /// </summary>

+ 33 - 0
Models/TrolleyActiveModel.cs

@@ -0,0 +1,33 @@
+using System.ComponentModel;
+
+namespace SWRIS.Models
+{
+    public class TrolleyActiveModel : INotifyPropertyChanged
+    {
+        private int id;
+        private string name;
+        private bool isActived = false;
+
+        public int Id
+        {
+            get { return id; }
+            set { id = value; OnPropertyChanged("Id"); }
+        }
+        public string Name
+        {
+            get { return name; }
+            set { name = value; OnPropertyChanged("Name"); }
+        }
+        public bool IsActived
+        {
+            get { return isActived; }
+            set { isActived = value; OnPropertyChanged("IsActived"); }
+        }
+        public event PropertyChangedEventHandler PropertyChanged;
+
+        protected internal virtual void OnPropertyChanged(string propertyName)
+        {
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+        }
+    }
+}

+ 46 - 0
Models/TrolleyLimitDataModel.cs

@@ -0,0 +1,46 @@
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+
+namespace SWRIS.Models
+{
+    public class TrolleyLimitDataModel : INotifyPropertyChanged
+    {
+        private int trolleyId;
+        private string trolleyName;
+        private ObservableCollection<LimitDataModel> limits;
+
+        public int TrolleyId
+        {
+            get { return trolleyId; }
+            set
+            {
+                trolleyId = value;
+                OnPropertyChanged(nameof(TrolleyId));
+            }
+        }
+        public string TrolleyName
+        {
+            get { return trolleyName; }
+            set
+            {
+                trolleyName = value;
+                OnPropertyChanged(nameof(trolleyName));
+            }
+        }
+
+        public ObservableCollection<LimitDataModel> Limits
+        {
+            get => limits;
+            set
+            {
+                limits = value;
+                OnPropertyChanged(nameof(Limits));
+            }
+        }
+        public event PropertyChangedEventHandler PropertyChanged;
+        protected internal virtual void OnPropertyChanged(string propertyName)
+        {
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+        }
+    }
+}

+ 17 - 7
Models/ViewModel/EquipmentSettingViewModel.cs

@@ -11,6 +11,7 @@ namespace SWRIS.Models.ViewModel
     {
         private int ropeNumber;
         private string ropeName;
+        private int trolleyId;
         private string ipAddress;
         private string serialNo;
         private string supplier;
@@ -49,6 +50,15 @@ namespace SWRIS.Models.ViewModel
                 OnPropertyChanged(nameof(RopeName));
             }
         }
+        public int TrolleyId
+        {
+            get { return trolleyId; }
+            set
+            {
+                trolleyId = value;
+                OnPropertyChanged(nameof(TrolleyId));
+            }
+        }
         /// <summary>
         /// Ip地址
         /// </summary>
@@ -146,6 +156,7 @@ namespace SWRIS.Models.ViewModel
                 OnPropertyChanged(nameof(LiftHightRatio));
             }
         }
+
         public RiskLevel SoundRiskLevel
         {
             get { return soundRiskLevel; }
@@ -218,7 +229,7 @@ namespace SWRIS.Models.ViewModel
         private short sensorCount;
         private float samplingStep;
         private short frequencyDivisionFactor;
-        private short timeDomainFrequency;
+        private short damageInterval;
         private short damageThreshold;
         private short scrapUpperLimit;
         private short frontMagnetLength;
@@ -292,15 +303,15 @@ namespace SWRIS.Models.ViewModel
             }
         }
         /// <summary>
-        /// 时域频率  2字节整型
+        /// 判伤间隔  2字节整型
         /// </summary>
-        public short TimeDomainFrequency
+        public short DamageInterval
         {
-            get => timeDomainFrequency;
+            get => damageInterval;
             set
             {
-                timeDomainFrequency = value;
-                OnPropertyChanged(nameof(TimeDomainFrequency));
+                damageInterval = value;
+                OnPropertyChanged(nameof(DamageInterval));
             }
         }
         /// <summary>
@@ -519,7 +530,6 @@ namespace SWRIS.Models.ViewModel
                 OnPropertyChanged(nameof(EncoderDirection));
             }
         }
-
         /// <summary>
         /// 系统时间
         /// </summary>

+ 0 - 65
Models/ViewModel/FourRopesViewModel.cs

@@ -1,65 +0,0 @@
-namespace SWRIS.Models.ViewModel
-{
-    public class FourRopesViewModel : RopeViewBaseModel
-    {
-        private EquipmentDataModel firstEquipmentData;
-        private EquipmentDataModel secondEquipmentData;
-        private EquipmentDataModel thirdEquipmentData;
-        private EquipmentDataModel fourthEquipmentData;
- 
-        public EquipmentModel FirstEquipment { get; set; }
-        public EquipmentModel SecondEquipment { get; set; }
-        public EquipmentModel ThirdEquipment { get; set; }
-        public EquipmentModel FourthEquipment { get; set; }
-
-        public EquipmentDataModel FirstEquipmentData
-        {
-            get => firstEquipmentData;
-            set
-            {
-                if (firstEquipmentData != value)
-                {
-                    firstEquipmentData = value;
-                    OnPropertyChanged(nameof(FirstEquipmentData));
-                }
-            }
-        }
-        public EquipmentDataModel SecondEquipmentData
-        {
-            get => secondEquipmentData;
-            set
-            {
-                if (secondEquipmentData != value)
-                {
-                    secondEquipmentData = value;
-                    OnPropertyChanged(nameof(SecondEquipmentData));
-                }
-            }
-        }
-        public EquipmentDataModel ThirdEquipmentData
-        {
-            get => thirdEquipmentData;
-            set
-            {
-                if (thirdEquipmentData != value)
-                {
-                    thirdEquipmentData = value;
-                    OnPropertyChanged(nameof(ThirdEquipmentData));
-                }
-            }
-        }
-        public EquipmentDataModel FourthEquipmentData
-        {
-            get => fourthEquipmentData;
-            set
-            {
-                if (fourthEquipmentData != value)
-                {
-                    fourthEquipmentData = value;
-                    OnPropertyChanged(nameof(FourthEquipmentData));
-                }
-            }
-        }
-        
-    }
-}

+ 14 - 1
Models/ViewModel/MainViewModel.cs

@@ -1,4 +1,6 @@
-using System.Collections.Generic;
+using SWRIS.Dtos;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.ComponentModel;
 
 namespace SWRIS.Models.ViewModel
@@ -9,7 +11,17 @@ namespace SWRIS.Models.ViewModel
         private string version;
         private string copyright;
         private string currentPage = "Home";
+
         private DebugMessageModel debugMessage = new DebugMessageModel(string.Empty);
+        public MainViewModel()
+        {
+            AppName = App.Config.AppName;
+            Version = App.Config.Version;
+            Copyright = App.Config.Copyright;
+            SwitchInstances = App.Config.SwitchInstances;
+          
+        }
+
         public string AppName
         {
             get => appName;
@@ -35,6 +47,7 @@ namespace SWRIS.Models.ViewModel
             get => debugMessage;
             set { debugMessage = value; OnPropertyChanged("DebugMessage"); }
         }
+ 
         public DateTimeModel SystemTime { set; get; } = new DateTimeModel();
         public List<SwitchInstance> SwitchInstances { get; set; } = new List<SwitchInstance>();
         public event PropertyChangedEventHandler PropertyChanged;

+ 0 - 21
Models/ViewModel/OneRopeViewModel.cs

@@ -1,21 +0,0 @@
-namespace SWRIS.Models.ViewModel
-{
-    public class OneRopeViewModel : RopeViewBaseModel
-    {
-        private EquipmentDataModel equipmentData;
-
-        public EquipmentDataModel EquipmentData
-        {
-            get => equipmentData;
-            set
-            {
-                if (equipmentData != value)
-                {
-                    equipmentData = value;
-                    OnPropertyChanged(nameof(EquipmentData));
-                }
-            }
-        }
-        public EquipmentModel Equipment { get; set; }
-    }
-}

+ 133 - 0
Models/ViewModel/RealTimeViewModel.cs

@@ -0,0 +1,133 @@
+using SWRIS.Dtos;
+using SWRIS.Extensions;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Linq;
+using System.Windows;
+
+namespace SWRIS.Models.ViewModel
+{
+    public class RealTimeViewModel : INotifyPropertyChanged
+    {
+        private TrolleyActiveModel activedTrolly;
+        private ObservableCollection<SimpleRecordDto> records;
+        private ObservableCollection<FaultsMessageDto> messages;
+        private ObservableCollection<TrolleyActiveModel> trolleys;
+        private ObservableCollection<EquipmentDataModel> equipments;
+
+        public RealTimeViewModel(ObservableCollection<SimpleRecordDto> records)
+        {
+            Equipments = App.DataCenter.Equipments.ConvertToObservableCollection();
+            Messages = App.DataCenter.Messages;
+            Records = records;
+            Trolleys = App.Config.Trolleys.Select(x => new TrolleyActiveModel
+            {
+                Id = x.Id,
+                Name = x.Name,
+                IsActived = false
+            }).ConvertToObservableCollection();
+            if (Trolleys.Any())
+            {
+                Trolleys[0].IsActived = true;
+                ActivedTrolly = Trolleys[0];
+            }
+        }
+
+        public ObservableCollection<SimpleRecordDto> Records
+        {
+            get => records;
+            set
+            {
+                if (records != value)
+                {
+                    records = value;
+                    OnPropertyChanged(nameof(Records));
+                }
+            }
+        }
+
+        public ObservableCollection<FaultsMessageDto> Messages
+        {
+            get => messages;
+            set
+            {
+                if (messages != value)
+                {
+                    messages = value;
+                    OnPropertyChanged(nameof(Messages));
+                }
+            }
+        }
+        public TrolleyActiveModel ActivedTrolly
+        {
+            get => activedTrolly;
+            set
+            {
+                if (activedTrolly != value)
+                {
+                    // 取消旧的选中项
+                    if (activedTrolly != null)
+                        activedTrolly.IsActived = false;
+
+                    activedTrolly = value;
+
+                    // 激活新的选中项
+                    if (activedTrolly != null)
+                        activedTrolly.IsActived = true;
+
+                    OnPropertyChanged(nameof(ActivedTrolly));
+                }
+            }
+        }
+        public ObservableCollection<TrolleyActiveModel> Trolleys
+        {
+            get => trolleys;
+            set
+            {
+                trolleys = value;
+                OnPropertyChanged(nameof(Trolleys));
+            }
+        }
+        public ObservableCollection<EquipmentDataModel> Equipments
+        {
+            get => equipments;
+            set
+            {
+                equipments = value;
+                OnPropertyChanged(nameof(Equipments));
+            }
+        }
+        public void AddRecord(SimpleRecordDto record)
+        {
+            record.IsNew = true;
+            SafeCollectionInsert(Records, 0, record);
+            // 延迟重置 IsNew 状态,避免重复触发动画
+            var timer = new System.Windows.Threading.DispatcherTimer
+            {
+                Interval = System.TimeSpan.FromSeconds(3)
+            };
+            timer.Tick += (s, e) =>
+            {
+                record.IsNew = false;
+                timer.Stop();
+            };
+            timer.Start();
+        }
+        protected void SafeCollectionInsert<T>(ObservableCollection<T> collection, int index, T item)
+        {
+            if (Application.Current.Dispatcher.CheckAccess())
+            {
+                collection.Insert(index, item);
+            }
+            else
+            {
+                Application.Current.Dispatcher.Invoke(() => collection.Insert(index, item));
+            }
+        }
+        public event PropertyChangedEventHandler PropertyChanged;
+        protected internal virtual void OnPropertyChanged(string propertyName)
+        {
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+        }
+    }
+}

+ 13 - 1
Models/ViewModel/RecordViewModel.cs

@@ -30,7 +30,19 @@ namespace SWRIS.Models.ViewModel
                 OnPropertyChanged("Records");
             }
         }
-       
+
+        public bool ShowExport
+        {
+            get
+            {
+#if DEBUG
+                return true;
+#else
+                return false;
+#endif
+            }
+        }
+
         public RecordViewModel()
         {
             RiskLevels.AddRange(TypeExtension.ToKeyAndDescriptionList(typeof(RiskLevel)).OrderBy(c => c.Key));

+ 0 - 41
Models/ViewModel/RopeViewBaseModel.cs

@@ -7,47 +7,6 @@ namespace SWRIS.Models.ViewModel
 {
     public class RopeViewBaseModel : INotifyPropertyChanged
     {
-        private ObservableCollection<SimpleRecordDto> records;
-        public ObservableCollection<SimpleRecordDto> Records
-        {
-            get => records;
-            set
-            {
-                if (records != value)
-                {
-                    records = value;
-                    OnPropertyChanged(nameof(Records));
-                }
-            }
-        }
-        public void AddRecord(SimpleRecordDto record)
-        {
-            record.IsNew = true;
-            SafeCollectionInsert(Records, 0, record);
-            // 延迟重置 IsNew 状态,避免重复触发动画
-            var timer = new System.Windows.Threading.DispatcherTimer
-            {
-                Interval = System.TimeSpan.FromSeconds(3)
-            };
-            timer.Tick += (s, e) =>
-            {
-                record.IsNew = false;
-                timer.Stop();
-            };
-            timer.Start();
-        }
-        protected void SafeCollectionInsert<T>(ObservableCollection<T> collection, int index, T item)
-        {
-            if (Application.Current.Dispatcher.CheckAccess())
-            {
-                collection.Insert(index, item);
-            }
-            else
-            {
-                Application.Current.Dispatcher.Invoke(() => collection.Insert(index, item));
-            }
-        }
-
         public event PropertyChangedEventHandler PropertyChanged;
         protected internal virtual void OnPropertyChanged(string propertyName)
         {

+ 0 - 51
Models/ViewModel/ThreeRopesViewModel.cs

@@ -1,51 +0,0 @@
-namespace SWRIS.Models.ViewModel
-{
-    public class ThreeRopesViewModel : RopeViewBaseModel
-    {
-        private EquipmentDataModel firstEquipmentData;
-        private EquipmentDataModel secondEquipmentData;
-        private EquipmentDataModel thirdEquipmentData;
- 
-
-        public EquipmentModel FirstEquipment { get; set; }
-        public EquipmentModel SecondEquipment { get; set; }
-        public EquipmentModel ThirdEquipment { get; set; }
-
-        public EquipmentDataModel FirstEquipmentData
-        {
-            get => firstEquipmentData;
-            set
-            {
-                if (firstEquipmentData != value)
-                {
-                    firstEquipmentData = value;
-                    OnPropertyChanged(nameof(FirstEquipmentData));
-                }
-            }
-        }
-        public EquipmentDataModel SecondEquipmentData
-        {
-            get => secondEquipmentData;
-            set
-            {
-                if (secondEquipmentData != value)
-                {
-                    secondEquipmentData = value;
-                    OnPropertyChanged(nameof(SecondEquipmentData));
-                }
-            }
-        }
-        public EquipmentDataModel ThirdEquipmentData
-        {
-            get => thirdEquipmentData;
-            set
-            {
-                if (thirdEquipmentData != value)
-                {
-                    thirdEquipmentData = value;
-                    OnPropertyChanged(nameof(ThirdEquipmentData));
-                }
-            }
-        }
-    }
-}

+ 0 - 29
Models/ViewModel/TwoRopesViewModel.cs

@@ -1,29 +0,0 @@
-namespace SWRIS.Models.ViewModel
-{
-    public class TwoRopesViewModel : RopeViewBaseModel
-    {
-        private EquipmentDataModel firstEquipmentData;
-        private EquipmentDataModel secondEquipmentData;
-
-        public EquipmentModel FirstEquipment { get; set; }
-        public EquipmentModel SecondEquipment { get; set; }
-        public EquipmentDataModel FirstEquipmentData
-        {
-            get => firstEquipmentData;
-            set
-            {
-                firstEquipmentData = value;
-                OnPropertyChanged(nameof(FirstEquipmentData));
-            }
-        }
-        public EquipmentDataModel SecondEquipmentData
-        {
-            get => secondEquipmentData;
-            set
-            {
-                secondEquipmentData = value;
-                OnPropertyChanged(nameof(SecondEquipmentData));
-            }
-        }
-    }
-}

+ 2 - 2
Pages/DamagesPage.xaml

@@ -70,9 +70,9 @@
                             <DataTemplate>
                                 <Border Background="#202045" Height="70" VerticalAlignment="Top" CornerRadius="10" Margin="0,0,0,10" >
                                     <StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="30,0">
-                                        <TextBlock Text="{Binding DamagePoint,StringFormat='{}{0:N1}m'}" FontFamily="{StaticResource PuHuiTiRegular}"
+                                        <TextBlock Text="{Binding DamagePoint,StringFormat='{}{0:N2}m'}" FontFamily="{StaticResource PuHuiTiRegular}"
                                                Foreground="#FFFFFF" FontSize="24" Width="130" FontWeight="Medium"/>
-                                        <TextBlock Text="{Binding DamageValue,StringFormat='{}{0:N1}%'}" FontFamily="{StaticResource PuHuiTiRegular}"
+                                        <TextBlock Text="{Binding DamageValue,StringFormat='{}{0:N2}%'}" FontFamily="{StaticResource PuHuiTiRegular}"
                                                Foreground="#FFFFFF" FontSize="24" Width="130" FontWeight="Medium"/>
                                         <TextBlock Text="{Binding DamageLevel,Converter={StaticResource DamageLevelToTextConverter}}"
                                                Foreground="{Binding DamageLevel,Converter={StaticResource DamageLevelToColorConverter}}" 

+ 1 - 2
Pages/DamagesPage.xaml.cs

@@ -1,5 +1,4 @@
-using OpenTK.Graphics.OpenGL;
-using Panuon.WPF.UI;
+using Panuon.WPF.UI;
 using SWRIS.Core;
 using SWRIS.Dtos;
 using SWRIS.Extensions;

+ 288 - 274
Pages/ParameterDialog.xaml

@@ -5,16 +5,17 @@
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
              xmlns:local="clr-namespace:SWRIS.Pages.RealTime"
-             xmlns:vm="clr-namespace:SWRIS.Models.ViewModel"  MaskBrush="#66000000"
-             xmlns:cvt="clr-namespace:SWRIS.Converters" AllowsTransparency="True"
+             xmlns:vm="clr-namespace:SWRIS.Models.ViewModel"  
+             MaskBrush="#66000000" AllowsTransparency="True"
+             xmlns:cvt="clr-namespace:SWRIS.Converters" 
              mc:Ignorable="d" Foreground="#7886B2"
              FontFamily="{StaticResource PuHuiTiRegular}"
              FontSize="18" Background="Transparent" 
-             WindowStyle="None" ResizeMode="NoResize"
+             WindowStyle="None" ResizeMode="CanResize"
              pu:WindowXCaption.Height="0"
              WindowStartupLocation="CenterScreen"
              IsEscEnabled="True"
-             Title="ParameterDialog" Height="900" Width="1300">
+             Title="ParameterDialog">
     <Window.DataContext>
         <vm:EquipmentSettingViewModel/>
     </Window.DataContext>
@@ -25,316 +26,329 @@
         <cvt:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
         <cvt:ResetTypeToBooleanConverter x:Key="ResetTypeConverter"/>
     </pu:WindowX.Resources>
-    <Border BorderThickness="2" BorderBrush="#3B3B7B" CornerRadius="10" Background="#141332">
-        <Grid Margin="0" HorizontalAlignment="Center">
-            <Grid.RowDefinitions>
-                <RowDefinition Height="70"/>
-                <RowDefinition Height="*"/>
-                <RowDefinition Height="130"/>
-            </Grid.RowDefinitions>
-            <Grid.ColumnDefinitions>
-                <ColumnDefinition Width="1*"/>
-                <ColumnDefinition Width="1*"/>
-                <ColumnDefinition Width="1.6*"/>
-            </Grid.ColumnDefinitions>
-            <TextBlock Grid.Row="0" Grid.ColumnSpan="3" Text="参数设置" Foreground="#FFFFFF" FontSize="36" 
+    <Grid>
+        <!-- 使用 Viewbox 包裹所有内容 -->
+        <Viewbox Stretch="Uniform">
+            <Border BorderThickness="2" BorderBrush="#3B3B7B" CornerRadius="10" Background="#141332">
+                <Grid Margin="10,0" HorizontalAlignment="Center">
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="70"/>
+                        <RowDefinition Height="*"/>
+                        <RowDefinition Height="130"/>
+                    </Grid.RowDefinitions>
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="1*"/>
+                        <ColumnDefinition Width="1*"/>
+                        <ColumnDefinition Width="1.6*"/>
+                    </Grid.ColumnDefinitions>
+                    <TextBlock Grid.Row="0" Grid.ColumnSpan="3" Text="参数设置" Foreground="#FFFFFF" FontSize="36" 
                        FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
-            <StackPanel Orientation="Vertical" Grid.Row="1" Grid.Column="0" HorizontalAlignment="Center" Margin="10,0">
-                <StackPanel Orientation="Vertical">
-                    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
-                        <Rectangle Height="2" Width="140" Stroke="#3B3B7B"/>
-                        <TextBlock Text="设备" Foreground="#615CDD" FontSize="24" Margin="15" FontWeight="Regular"/>
-                        <Rectangle Height="2" Width="140" Stroke="#3B3B7B"/>
-                    </StackPanel>
-                    <StackPanel Orientation="Vertical" HorizontalAlignment="Center">
-                        <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
-                            <TextBlock Text="版本号" VerticalAlignment="Center"/>
-                            <TextBox Text="{Binding Parameter.MainBoardSoftwareVersion}" IsEnabled="False" Margin="15,0"/>
-                            <Image Source="/Resources/upgrade.png" Width="26" Height="26" Margin="0,0,-26,0" Cursor="Hand"
-                                   Visibility="{Binding IsConnected, Converter={StaticResource BoolToVisibilityConverter}}"
-                                   MouseDown="Upgrade_MouseDown"/>
-                        </StackPanel>
-                        <StackPanel Orientation="Horizontal" Margin="0,7"  HorizontalAlignment="Right">
-                            <TextBlock Text="设备地址" VerticalAlignment="Center"/>
-                            <TextBox Margin="15,0" Text="{Binding IpAddress}"/>
-                        </StackPanel>
-                        <StackPanel Orientation="Horizontal" Margin="0,7"  HorizontalAlignment="Right">
-                            <TextBlock Text="传感器数量" VerticalAlignment="Center"/>
-                            <TextBox Margin="15,0" Text="{Binding Parameter.SensorCount}"/>
-                        </StackPanel>
-                        <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right" Visibility="Visible">
-                            <TextBlock Text="位/高比例" VerticalAlignment="Center"/>
-                            <TextBox Margin="15,0" Text="{Binding LiftHightRatio}"/>
+                    <StackPanel Orientation="Vertical" Grid.Row="1" Grid.Column="0" HorizontalAlignment="Center" Margin="10,0">
+                        <StackPanel Orientation="Vertical">
+                            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
+                                <Rectangle Height="2" Width="140" Stroke="#3B3B7B"/>
+                                <TextBlock Text="设备" Foreground="#615CDD" FontSize="24" Margin="15" FontWeight="Regular"/>
+                                <Rectangle Height="2" Width="140" Stroke="#3B3B7B"/>
+                            </StackPanel>
+                            <StackPanel Orientation="Vertical" HorizontalAlignment="Center">
+                                <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
+                                    <TextBlock Text="版本号" VerticalAlignment="Center"/>
+                                    <TextBox Text="{Binding Parameter.MainBoardSoftwareVersion}" IsEnabled="False" Margin="15,0"/>
+                                    <Image Source="/Resources/upgrade.png" Width="26" Height="26" Margin="0,0,-26,0" Cursor="Hand"
+                                           Visibility="{Binding IsConnected, Converter={StaticResource BoolToVisibilityConverter}}"
+                                           MouseDown="Upgrade_MouseDown"/>
+                                </StackPanel>
+                                <StackPanel Orientation="Horizontal" Margin="0,7"  HorizontalAlignment="Right">
+                                    <TextBlock Text="设备地址" VerticalAlignment="Center"/>
+                                    <TextBox Margin="15,0" Text="{Binding IpAddress}"/>
+                                </StackPanel>
+                                <StackPanel Orientation="Horizontal" Margin="0,7"  HorizontalAlignment="Right">
+                                    <TextBlock Text="传感器数量" VerticalAlignment="Center"/>
+                                    <TextBox Margin="15,0" Text="{Binding Parameter.SensorCount}"/>
+                                </StackPanel>
+                                <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right" Visibility="Visible">
+                                    <TextBlock Text="位/高比例" VerticalAlignment="Center"/>
+                                    <TextBox Margin="15,0" Text="{Binding LiftHightRatio}"/>
+                                </StackPanel>
+                            </StackPanel>
                         </StackPanel>
-                    </StackPanel>
-                </StackPanel>
-                <StackPanel Orientation="Vertical">
-                    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
-                        <Rectangle Height="2" Width="140" Stroke="#3B3B7B"/>
-                        <TextBlock Text="同步" Foreground="#615CDD" FontSize="24" Margin="15" FontWeight="Regular"/>
-                        <Rectangle Height="2" Width="140" Stroke="#3B3B7B"/>
-                    </StackPanel>
-                    <StackPanel Orientation="Vertical" HorizontalAlignment="Center" Margin="0,0,32,0">
-                        <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
-                            <TextBlock Text="编码器" Margin="10,0" VerticalAlignment="Center"/>
-                            <StackPanel Orientation="Horizontal">
-                                <ToggleButton Content="正向" Margin="12,0,5,0" FontSize="18"
+                        <StackPanel Orientation="Vertical">
+                            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
+                                <Rectangle Height="2" Width="140" Stroke="#3B3B7B"/>
+                                <TextBlock Text="同步" Foreground="#615CDD" FontSize="24" Margin="15" FontWeight="Regular"/>
+                                <Rectangle Height="2" Width="140" Stroke="#3B3B7B"/>
+                            </StackPanel>
+                            <StackPanel Orientation="Vertical" HorizontalAlignment="Center" Margin="0,0,32,0">
+                                <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
+                                    <TextBlock Text="编码器" Margin="10,0" VerticalAlignment="Center"/>
+                                    <StackPanel Orientation="Horizontal">
+                                        <ToggleButton Content="正向" Margin="12,0,5,0" FontSize="18"
                                           IsChecked="{Binding Parameter.EncoderDirection,
                                           Converter={StaticResource EncoderDirectionConverter},
                                           ConverterParameter=Forward}"/>
-                                <ToggleButton Content="反向" Margin="5,0,15,0" FontSize="18"
+                                        <ToggleButton Content="反向" Margin="5,0,15,0" FontSize="18"
                                           IsChecked="{Binding Parameter.EncoderDirection,
                                           Converter={StaticResource EncoderDirectionConverter},
                                           ConverterParameter=Reverse}"/>
-                            </StackPanel>
-                        </StackPanel>
-                        <StackPanel Orientation="Horizontal" Margin="0,7"  HorizontalAlignment="Right">
-                            <TextBlock Text="时钟" VerticalAlignment="Center"/>
-                            <TextBox Margin="15,0" Width="200" GotFocus="Clock_GotFocus"
+                                    </StackPanel>
+                                </StackPanel>
+                                <StackPanel Orientation="Horizontal" Margin="0,7"  HorizontalAlignment="Right">
+                                    <TextBlock Text="时钟" VerticalAlignment="Center"/>
+                                    <TextBox Margin="15,0" Width="200" GotFocus="Clock_GotFocus"
                                  Text="{Binding Parameter.SystemTime,StringFormat='yyyy-MM-dd HH:mm:ss',TargetNullValue='',Mode=TwoWay}"/>
+                                </StackPanel>
+                                <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="10,0" IsEnabled="{Binding IsConnected}">
+                                    <Button Content="读" Width="60" Height="40" Margin="5" Click="ReadClockAndDirection_Click"/>
+                                    <Button Content="设" Width="60" Height="40" Margin="5"  Click="SetClockAndDirection_Click"/>
+                                </StackPanel>
+                            </StackPanel>
                         </StackPanel>
-                        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="10,0" IsEnabled="{Binding IsConnected}">
-                            <Button Content="读" Width="60" Height="40" Margin="5" Click="ReadClockAndDirection_Click"/>
-                            <Button Content="设" Width="60" Height="40" Margin="5"  Click="SetClockAndDirection_Click"/>
-                        </StackPanel>
-                    </StackPanel>
-                </StackPanel>
-                <StackPanel Orientation="Vertical">
-                    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
-                        <Rectangle Height="2" Width="140" Stroke="#3B3B7B"/>
-                        <TextBlock Text="序列号" Foreground="#615CDD" FontSize="24" Margin="15" FontWeight="Regular"/>
-                        <Rectangle Height="2" Width="140" Stroke="#3B3B7B"/>
-                    </StackPanel>
-                    <StackPanel Orientation="Vertical" HorizontalAlignment="Center" Margin="0,0,32,0">
-                        <StackPanel Orientation="Horizontal" Margin="0,7"  HorizontalAlignment="Right">
-                            <TextBlock Text="序列号" Margin="7,0" VerticalAlignment="Center"/>
-                            <TextBox Margin="15,0" Width="200" Text="{Binding SerialNo}" GotFocus="SerialNo_GotFocus"/>
-                        </StackPanel>
-                        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="10,0" IsEnabled="{Binding IsConnected}">
-                            <Button Content="设" Width="60" Height="40" Margin="5"  Click="SetSerialNo_Click"/>
-                        </StackPanel>
-                    </StackPanel>
+                        <StackPanel Orientation="Vertical">
+                            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
+                                <Rectangle Height="2" Width="140" Stroke="#3B3B7B"/>
+                                <TextBlock Text="序列号" Foreground="#615CDD" FontSize="24" Margin="15" FontWeight="Regular"/>
+                                <Rectangle Height="2" Width="140" Stroke="#3B3B7B"/>
+                            </StackPanel>
+                            <StackPanel Orientation="Vertical" HorizontalAlignment="Center" Margin="0,0,32,0">
+                                <StackPanel Orientation="Horizontal" Margin="0,7"  HorizontalAlignment="Right">
+                                    <TextBlock Text="序列号" Margin="7,0" VerticalAlignment="Center"/>
+                                    <TextBox Margin="15,0" Width="200" Text="{Binding SerialNo}" GotFocus="SerialNo_GotFocus"/>
+                                </StackPanel>
+                                <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="10,0" IsEnabled="{Binding IsConnected}">
+                                    <Button Content="设" Width="60" Height="40" Margin="5"  Click="SetSerialNo_Click"/>
+                                </StackPanel>
+                            </StackPanel>
 
-                </StackPanel>
-            </StackPanel>
-            <StackPanel Orientation="Vertical" Grid.Row="1" Grid.Column="1" Margin="10,0,10,0">
-                <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
-                    <Rectangle Height="2" Width="140" Stroke="#3B3B7B"/>
-                    <TextBlock Text="钢丝绳" Foreground="#615CDD" FontSize="24" Margin="15" FontWeight="Regular"/>
-                    <Rectangle Height="2" Width="140" Stroke="#3B3B7B"/>
-                </StackPanel>
-                <StackPanel Orientation="Vertical" HorizontalAlignment="Center">
-                    <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
-                        <TextBlock Text="钢丝绳绳号" VerticalAlignment="Center"/>
-                        <TextBox Text="{Binding RopeNumber}" Margin="15,0"/>
-                    </StackPanel>
-                    <StackPanel Orientation="Horizontal" Margin="0,7"  HorizontalAlignment="Right">
-                        <TextBlock Text="钢丝绳名称" VerticalAlignment="Center"/>
-                        <TextBox Margin="15,0" Text="{Binding RopeName}"/>
-                    </StackPanel>
-                    <StackPanel Orientation="Horizontal" Margin="0,7"  HorizontalAlignment="Right">
-                        <TextBlock Text="直径" VerticalAlignment="Center"/>
-                        <TextBox Margin="15,0" Text="{Binding Parameter.WireRopeDiameter}"/>
-                    </StackPanel>
-                    <StackPanel Orientation="Horizontal" Margin="0,7"  HorizontalAlignment="Right">
-                        <TextBlock Text="股丝" VerticalAlignment="Center"/>
-                        <TextBox Margin="10,0,8,0" Width="64" Text="{Binding Parameter.WireRopeStrandCount}"/>
-                        <TextBlock Text="*" VerticalAlignment="Center"/>
-                        <TextBox Margin="7,0,15,0" Width="64" Text="{Binding Parameter.WireRopeStrandWireCount}"/>
-                    </StackPanel>
-                    <StackPanel Orientation="Horizontal" Margin="0,7"  HorizontalAlignment="Right">
-                        <TextBlock Text="长度" VerticalAlignment="Center"/>
-                        <TextBox Margin="15,0" Text="{Binding Parameter.WireRopeLength}"/>
-                    </StackPanel>
-                    <StackPanel Orientation="Horizontal" Margin="0,7"  HorizontalAlignment="Right">
-                        <TextBlock Text="采样步长" VerticalAlignment="Center"/>
-                        <TextBox Margin="15,0" Text="{Binding Parameter.SamplingStep}"/>
-                    </StackPanel>
-                    <StackPanel Orientation="Horizontal" Margin="0,7"  HorizontalAlignment="Right" Visibility="Collapsed">
-                        <TextBlock Text="判伤捻距系数" VerticalAlignment="Center"/>
-                        <TextBox Margin="15,0" Text="{Binding Parameter.TwistFactor}"/>
-                    </StackPanel>
-                    <StackPanel Orientation="Horizontal" Margin="0,7"  HorizontalAlignment="Right">
-                        <TextBlock Text="钢丝材质" VerticalAlignment="Center"/>
-                        <ComboBox Margin="15,0" Width="150"
-                                  ItemsSource="{Binding RopeCoreTypes}" 
-                                  DisplayMemberPath="Value" SelectedValuePath="Key" 
-                                  SelectedValue="{Binding RopeCoreType}"/>
-                    </StackPanel>
-                    <StackPanel Orientation="Horizontal" Margin="0,7"  HorizontalAlignment="Right">
-                        <TextBlock Text="绳芯材质" VerticalAlignment="Center"/>
-                        <ComboBox Margin="15,0"  Width="150"
-                                  ItemsSource="{Binding WireMaterialTypes}" 
-                                  SelectedValue="{Binding WireMaterialType}"
-                                  DisplayMemberPath="Value" SelectedValuePath="Key" />
-                    </StackPanel>
-                    <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
-                        <TextBlock Text="捻制方法" VerticalAlignment="Center"/>
-                        <ComboBox Margin="15,0" Width="150"
-                                  ItemsSource="{Binding LayTypes}" 
-                                  SelectedValue="{Binding LayType}"
-                                  DisplayMemberPath="Value" SelectedValuePath="Key" />
-                    </StackPanel>
-                    <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
-                        <TextBlock Text="表面状态" VerticalAlignment="Center"/>
-                        <ComboBox Margin="15,0" Width="150" 
-                                  ItemsSource="{Binding WireSurfaceTypes}" 
-                                  SelectedValue="{Binding WireSurfaceType}" 
-                                  DisplayMemberPath="Value" SelectedValuePath="Key" />
-                    </StackPanel>
-                </StackPanel>
-            </StackPanel>
-            <StackPanel Orientation="Vertical" Grid.Row="1" Grid.Column="2" Margin="10,0,10,0">
-                <StackPanel Orientation="Vertical" >
-                    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
-                        <Rectangle Height="2" Width="200" Stroke="#3B3B7B"/>
-                        <TextBlock Text="其它参数" Foreground="#615CDD" FontSize="24" Margin="15" FontWeight="Regular"/>
-                        <Rectangle Height="2" Width="200" Stroke="#3B3B7B"/>
+                        </StackPanel>
                     </StackPanel>
-                    <Grid>
-                        <Grid.RowDefinitions>
-                            <RowDefinition Height="55"/>
-                            <RowDefinition Height="*"/>
-                            <RowDefinition Height="300"/>
-                        </Grid.RowDefinitions>
-                        <Grid.ColumnDefinitions>
-                            <ColumnDefinition Width="*"/>
-                            <ColumnDefinition Width="*"/>
-                        </Grid.ColumnDefinitions>
-                        <StackPanel Orientation="Horizontal" Grid.Row="0" Grid.ColumnSpan="2" Margin="45,0,0,0" HorizontalAlignment="Left" IsEnabled="{Binding IsConnected}">
-                            <TextBlock Text="检测模式" Margin="5,0" VerticalAlignment="Center"/>
-                            <StackPanel Orientation="Horizontal">
-                                <ToggleButton Content="空域检测" Margin="12,0,5,0" FontSize="18" Width="110" IsChecked="{Binding RunningStatus,
-                                    Converter={StaticResource RunningStatusConverter},
-                                    ConverterParameter=SpatialNormal}" Tag="SpatialNormal" Click="RunningStatus_Click"/>
-                                <ToggleButton Content="时域检测" Margin="5,0,10,0" FontSize="18" Width="110" IsChecked="{Binding RunningStatus,
-                                    Converter={StaticResource RunningStatusConverter},
-                                    ConverterParameter=TemporalNormal}" Tag="TemporalNormal" Click="RunningStatus_Click"/>
-                                <ToggleButton Content="停止检测" Margin="5,0,10,0" FontSize="18" Width="110" IsChecked="{Binding RunningStatus,
-                                    Converter={StaticResource RunningStatusConverter},
-                                    ConverterParameter=Stopped}" Tag="Stopped" Click="RunningStatus_Click"/>
-                            </StackPanel>
+                    <StackPanel Orientation="Vertical" Grid.Row="1" Grid.Column="1" Margin="10,0,10,0">
+                        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
+                            <Rectangle Height="2" Width="140" Stroke="#3B3B7B"/>
+                            <TextBlock Text="钢丝绳" Foreground="#615CDD" FontSize="24" Margin="15" FontWeight="Regular"/>
+                            <Rectangle Height="2" Width="140" Stroke="#3B3B7B"/>
                         </StackPanel>
-
-                        <StackPanel Orientation="Vertical" Grid.Column="0" Grid.Row="1" HorizontalAlignment="Center">
+                        <StackPanel Orientation="Vertical" HorizontalAlignment="Center">
                             <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
-                                <TextBlock Text="分频系数" VerticalAlignment="Center"/>
-                                <TextBox Margin="15,0" Width="120" Text="{Binding Parameter.FrequencyDivisionFactor}"/>
+                                <TextBlock Text="钢丝绳绳号" VerticalAlignment="Center"/>
+                                <TextBox Text="{Binding RopeNumber}" Margin="15,0"/>
                             </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
-                                <TextBlock Text="前磁极" VerticalAlignment="Center"/>
+                            <StackPanel Orientation="Horizontal" Margin="0,7"  HorizontalAlignment="Right">
+                                <TextBlock Text="钢丝绳名称" VerticalAlignment="Center"/>
+                                <TextBox Margin="15,0" Text="{Binding RopeName}"/>
+                            </StackPanel>
+                            <StackPanel Orientation="Horizontal" Margin="0,7"  HorizontalAlignment="Right">
+                                <TextBlock Text="直径" VerticalAlignment="Center"/>
                                 <StackPanel Orientation="Horizontal"  Margin="0,0,4,0">
-                                    <TextBox Margin="15,0,6,0" Width="80" Text="{Binding Parameter.FrontMagnetLength}"/>
+                                    <TextBox Margin="15,0,6,0" Width="110" Text="{Binding Parameter.WireRopeDiameter}"/>
                                     <TextBlock  Text="mm" Width="45" VerticalAlignment="Center"/>
                                 </StackPanel>
                             </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
-                                <TextBlock Text="报废上限" VerticalAlignment="Center"/>
-                                <TextBox Margin="15,0" Width="120" Text="{Binding Parameter.ScrapUpperLimit}"/>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
-                                <TextBlock Text="量值系数" VerticalAlignment="Center"/>
-                                <TextBox Margin="15,0" Width="120" Text="{Binding Parameter.ValueCoefficient}"/>
+                            <StackPanel Orientation="Horizontal" Margin="0,7"  HorizontalAlignment="Right">
+                                <TextBlock Text="股丝" VerticalAlignment="Center"/>
+                                <TextBox Margin="10,0,8,0" Width="63" Text="{Binding Parameter.WireRopeStrandCount}"/>
+                                <TextBlock Text="*" VerticalAlignment="Center"/>
+                                <TextBox Margin="7,0,15,0" Width="64" Text="{Binding Parameter.WireRopeStrandWireCount}"/>
                             </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
-                                <StackPanel Orientation="Vertical" VerticalAlignment="Center">
-                                    <TextBlock Text="零点位有效行程" HorizontalAlignment="Right"/>
-                                </StackPanel>
+                            <StackPanel Orientation="Horizontal" Margin="0,7"  HorizontalAlignment="Right">
+                                <TextBlock Text="长度" VerticalAlignment="Center"/>
                                 <StackPanel Orientation="Horizontal"  Margin="0,0,4,0">
-                                    <TextBox Margin="15,0,6,0" Width="90" Text="{Binding Parameter.EffectiveStrokeLength}"/>
-                                    <TextBlock Text="m" Width="35" VerticalAlignment="Center"/>
+                                    <TextBox Margin="15,0,6,0" Width="110" Text="{Binding Parameter.WireRopeLength}"/>
+                                    <TextBlock  Text="m" Width="45" VerticalAlignment="Center"/>
                                 </StackPanel>
                             </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
-                                <StackPanel Orientation="Vertical" VerticalAlignment="Center">
-                                    <TextBlock Text="零点位静止时长" HorizontalAlignment="Right"/>
-                                </StackPanel>
+                            <StackPanel Orientation="Horizontal" Margin="0,7"  HorizontalAlignment="Right">
+                                <TextBlock Text="采样步长" VerticalAlignment="Center"/>
                                 <StackPanel Orientation="Horizontal"  Margin="0,0,4,0">
-                                    <TextBox Margin="15,0,6,0" Width="90" Text="{Binding Parameter.ZeroPositionCorrectionDuration}"/>
-                                    <TextBlock Text="秒" Width="35" VerticalAlignment="Center"/>
+                                    <TextBox Margin="15,0,6,0" Width="110" Text="{Binding Parameter.SamplingStep}"/>
+                                    <TextBlock  Text="mm" Width="45" VerticalAlignment="Center"/>
                                 </StackPanel>
                             </StackPanel>
-                        </StackPanel >
-                        <StackPanel Orientation="Vertical" Grid.Column="1" Grid.Row="1" HorizontalAlignment="Center">
-                            <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
-                                <TextBlock Text="时域频率" VerticalAlignment="Center"/>
-                                <TextBox Margin="15,0" Width="120" Text="{Binding Parameter.TimeDomainFrequency}"
-                                         IsEnabled="{Binding RunningStatus,Converter={StaticResource RunningStatusConverter},ConverterParameter=TemporalNormal}"/>
+                            <StackPanel Orientation="Horizontal" Margin="0,7"  HorizontalAlignment="Right" Visibility="Collapsed">
+                                <TextBlock Text="判伤捻距系数" VerticalAlignment="Center"/>
+                                <TextBox Margin="15,0" Text="{Binding Parameter.TwistFactor}"/>
                             </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
-                                <TextBlock Text="后磁极" VerticalAlignment="Center"/>
-                                <StackPanel Orientation="Horizontal"  Margin="0,0,4,0">
-                                    <TextBox Margin="15,0,6,0" Width="80" Text="{Binding Parameter.BackMagnetLength}"/>
-                                    <TextBlock  Text="mm" Width="45" VerticalAlignment="Center"/>
-                                </StackPanel>
+                            <StackPanel Orientation="Horizontal" Margin="0,7"  HorizontalAlignment="Right">
+                                <TextBlock Text="钢丝材质" VerticalAlignment="Center"/>
+                                <ComboBox Margin="15,0" Width="150"
+                                  ItemsSource="{Binding RopeCoreTypes}" 
+                                  DisplayMemberPath="Value" SelectedValuePath="Key" 
+                                  SelectedValue="{Binding RopeCoreType}"/>
                             </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
-                                <TextBlock Text="损伤门限" VerticalAlignment="Center"/>
-                                <TextBox Margin="15,0" Width="120" Text="{Binding Parameter.DamageThreshold}"/>
+                            <StackPanel Orientation="Horizontal" Margin="0,7"  HorizontalAlignment="Right">
+                                <TextBlock Text="绳芯材质" VerticalAlignment="Center"/>
+                                <ComboBox Margin="15,0"  Width="150"
+                                  ItemsSource="{Binding WireMaterialTypes}" 
+                                  SelectedValue="{Binding WireMaterialType}"
+                                  DisplayMemberPath="Value" SelectedValuePath="Key" />
                             </StackPanel>
                             <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
-                                <TextBlock Text="黄色警报" VerticalAlignment="Center"/>
-                                <TextBox Margin="15,0" Width="120" Text="{Binding Parameter.WarningValue}"/>
+                                <TextBlock Text="捻制方法" VerticalAlignment="Center"/>
+                                <ComboBox Margin="15,0" Width="150"
+                                  ItemsSource="{Binding LayTypes}" 
+                                  SelectedValue="{Binding LayType}"
+                                  DisplayMemberPath="Value" SelectedValuePath="Key" />
                             </StackPanel>
                             <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
-                                <TextBlock Text="红色警报" VerticalAlignment="Center"/>
-                                <TextBox Margin="15,0" Width="120" Text="{Binding Parameter.AlarmValue}"/>
+                                <TextBlock Text="表面状态" VerticalAlignment="Center"/>
+                                <ComboBox Margin="15,0" Width="150" 
+                                  ItemsSource="{Binding WireSurfaceTypes}" 
+                                  SelectedValue="{Binding WireSurfaceType}" 
+                                  DisplayMemberPath="Value" SelectedValuePath="Key" />
                             </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
-                                <StackPanel Orientation="Vertical" VerticalAlignment="Center">
-                                    <TextBlock Text="零点位偏移值" HorizontalAlignment="Right"/>
+                        </StackPanel>
+                    </StackPanel>
+                    <StackPanel Orientation="Vertical" Grid.Row="1" Grid.Column="2" Margin="10,0,10,0">
+                        <StackPanel Orientation="Vertical" >
+                            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
+                                <Rectangle Height="2" Width="200" Stroke="#3B3B7B"/>
+                                <TextBlock Text="其它参数" Foreground="#615CDD" FontSize="24" Margin="15" FontWeight="Regular"/>
+                                <Rectangle Height="2" Width="200" Stroke="#3B3B7B"/>
+                            </StackPanel>
+                            <Grid>
+                                <Grid.RowDefinitions>
+                                    <RowDefinition Height="55"/>
+                                    <RowDefinition Height="*"/>
+                                    <RowDefinition Height="300"/>
+                                </Grid.RowDefinitions>
+                                <Grid.ColumnDefinitions>
+                                    <ColumnDefinition Width="*"/>
+                                    <ColumnDefinition Width="*"/>
+                                </Grid.ColumnDefinitions>
+                                <StackPanel Orientation="Horizontal" Grid.Row="0" Grid.ColumnSpan="2" Margin="45,0,0,0" HorizontalAlignment="Left" IsEnabled="{Binding IsConnected}">
+                                    <TextBlock Text="检测模式" Margin="5,0" VerticalAlignment="Center"/>
+                                    <StackPanel Orientation="Horizontal">
+                                        <ToggleButton Content="空域检测" Margin="12,0,5,0" FontSize="18" Width="110" IsChecked="{Binding RunningStatus,
+                                    Converter={StaticResource RunningStatusConverter},
+                                    ConverterParameter=SpatialNormal}" Tag="SpatialNormal" Click="RunningStatus_Click"/>
+                                        <ToggleButton Content="时域检测" Margin="5,0,10,0" FontSize="18" Width="110" IsChecked="{Binding RunningStatus,
+                                    Converter={StaticResource RunningStatusConverter},
+                                    ConverterParameter=TemporalNormal}" Tag="TemporalNormal" Click="RunningStatus_Click"/>
+                                    </StackPanel>
                                 </StackPanel>
-                                <StackPanel Orientation="Horizontal"  Margin="0,0,4,0">
-                                    <TextBox Margin="15,0,6,0" Width="90" Text="{Binding Parameter.ZeroPositionCorrectionOffset}"/>
-                                    <TextBlock Text="mm" Width="35" VerticalAlignment="Center"/>
+
+                                <StackPanel Orientation="Vertical" Grid.Column="0" Grid.Row="1" HorizontalAlignment="Center">
+                                    <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
+                                        <TextBlock Text="分频系数" VerticalAlignment="Center"/>
+                                        <TextBox Margin="15,0" Width="120" Text="{Binding Parameter.FrequencyDivisionFactor}"/>
+                                    </StackPanel>
+                                    <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
+                                        <TextBlock Text="前磁极" VerticalAlignment="Center"/>
+                                        <StackPanel Orientation="Horizontal"  Margin="0,0,4,0">
+                                            <TextBox Margin="15,0,6,0" Width="80" Text="{Binding Parameter.FrontMagnetLength}"/>
+                                            <TextBlock  Text="mm" Width="45" VerticalAlignment="Center"/>
+                                        </StackPanel>
+                                    </StackPanel>
+                                    <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
+                                        <TextBlock Text="判伤门限" VerticalAlignment="Center"/>
+                                        <TextBox Margin="15,0" Width="120" Text="{Binding Parameter.DamageThreshold}"/>
+                                    </StackPanel>
+                                    <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
+                                        <TextBlock Text="报废上限" VerticalAlignment="Center"/>
+                                        <TextBox Margin="15,0" Width="120" Text="{Binding Parameter.ScrapUpperLimit}"/>
+                                    </StackPanel>
+                                    <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
+                                        <StackPanel Orientation="Vertical" VerticalAlignment="Center">
+                                            <TextBlock Text="零点位有效行程" HorizontalAlignment="Right"/>
+                                        </StackPanel>
+                                        <StackPanel Orientation="Horizontal"  Margin="0,0,4,0">
+                                            <TextBox Margin="15,0,6,0" Width="90" Text="{Binding Parameter.EffectiveStrokeLength}"/>
+                                            <TextBlock Text="m" Width="35" VerticalAlignment="Center"/>
+                                        </StackPanel>
+                                    </StackPanel>
+                                    <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
+                                        <StackPanel Orientation="Vertical" VerticalAlignment="Center">
+                                            <TextBlock Text="零点位静止时长" HorizontalAlignment="Right"/>
+                                        </StackPanel>
+                                        <StackPanel Orientation="Horizontal"  Margin="0,0,4,0">
+                                            <TextBox Margin="15,0,6,0" Width="90" Text="{Binding Parameter.ZeroPositionCorrectionDuration}"/>
+                                            <TextBlock Text="秒" Width="35" VerticalAlignment="Center"/>
+                                        </StackPanel>
+                                    </StackPanel>
+                                </StackPanel >
+                                <StackPanel Orientation="Vertical" Grid.Column="1" Grid.Row="1" HorizontalAlignment="Center">
+                                    <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
+                                        <TextBlock Text="量值系数" VerticalAlignment="Center"/>
+                                        <TextBox Margin="15,0" Width="120" Text="{Binding Parameter.ValueCoefficient}"/>
+                                    </StackPanel>
+                                    <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
+                                        <TextBlock Text="后磁极" VerticalAlignment="Center"/>
+                                        <StackPanel Orientation="Horizontal"  Margin="0,0,4,0">
+                                            <TextBox Margin="15,0,6,0" Width="80" Text="{Binding Parameter.BackMagnetLength}"/>
+                                            <TextBlock  Text="mm" Width="45" VerticalAlignment="Center"/>
+                                        </StackPanel>
+                                    </StackPanel>
+                                    <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
+                                        <TextBlock Text="判伤间隔" VerticalAlignment="Center"/>
+                                        <StackPanel Orientation="Horizontal"  Margin="0,0,4,0">
+                                            <TextBox Margin="15,0,6,0" Width="80" Text="{Binding Parameter.DamageInterval}"/>
+                                            <TextBlock  Text="mm" Width="45" VerticalAlignment="Center"/>
+                                        </StackPanel>
+                                    </StackPanel>
+                                    <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
+                                        <TextBlock Text="黄色警报" VerticalAlignment="Center"/>
+                                        <TextBox Margin="15,0" Width="120" Text="{Binding Parameter.WarningValue}"/>
+                                    </StackPanel>
+                                    <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
+                                        <TextBlock Text="红色警报" VerticalAlignment="Center"/>
+                                        <TextBox Margin="15,0" Width="120" Text="{Binding Parameter.AlarmValue}"/>
+                                    </StackPanel>
+                                    <StackPanel Orientation="Horizontal" Margin="0,7" HorizontalAlignment="Right">
+                                        <StackPanel Orientation="Vertical" VerticalAlignment="Center">
+                                            <TextBlock Text="零点位偏移值" HorizontalAlignment="Right"/>
+                                        </StackPanel>
+                                        <StackPanel Orientation="Horizontal"  Margin="0,0,4,0">
+                                            <TextBox Margin="15,0,6,0" Width="80" Text="{Binding Parameter.ZeroPositionCorrectionOffset}"/>
+                                            <TextBlock Text="mm" Width="45" VerticalAlignment="Center"/>
+                                        </StackPanel>
+                                    </StackPanel>
                                 </StackPanel>
-                            </StackPanel>
-                        </StackPanel>
-                        <StackPanel Orientation="Horizontal" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Visibility="Collapsed">
-                            <StackPanel Orientation="Horizontal">
-                                <TextBlock Text="报警重置" VerticalAlignment="Center"/>
-                                <ToggleButton Content="不重置" Width="90" Margin="10,0,5,0" FontSize="18"
+                                <StackPanel Orientation="Horizontal" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Visibility="Collapsed">
+                                    <StackPanel Orientation="Horizontal">
+                                        <TextBlock Text="报警重置" VerticalAlignment="Center"/>
+                                        <ToggleButton Content="不重置" Width="90" Margin="10,0,5,0" FontSize="18"
                                               IsChecked="{Binding Parameter.SoundLightAlarmAutoResetMode,Converter={StaticResource ResetTypeConverter},ConverterParameter=0}"/>
-                            </StackPanel>
-                            <ToggleButton Content="新测试重置" Width="110" Margin="5,5" FontSize="18" 
+                                    </StackPanel>
+                                    <ToggleButton Content="新测试重置" Width="110" Margin="5,5" FontSize="18" 
                                           IsChecked="{Binding Parameter.SoundLightAlarmAutoResetMode,Converter={StaticResource ResetTypeConverter},ConverterParameter=-1}"/>
-                            <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
-                                <StackPanel Orientation="Horizontal" Margin="5,7" HorizontalAlignment="Right">
-                                    <ToggleButton Content="延时重置" Width="90" FontSize="18" 
+                                    <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
+                                        <StackPanel Orientation="Horizontal" Margin="5,7" HorizontalAlignment="Right">
+                                            <ToggleButton Content="延时重置" Width="90" FontSize="18" 
                                                   IsChecked="{Binding Parameter.SoundLightAlarmAutoResetMode,Converter={StaticResource ResetTypeConverter},ConverterParameter=1}"/>
-                                    <StackPanel Orientation="Horizontal">
-                                        <TextBox Margin="6,0,3,0" Width="40" Text="{Binding Parameter.SoundLightAlarmAutoResetMode}"/>
-                                        <TextBlock Text="秒" Width="30" VerticalAlignment="Center"/>
+                                            <StackPanel Orientation="Horizontal">
+                                                <TextBox Margin="6,0,3,0" Width="40" Text="{Binding Parameter.SoundLightAlarmAutoResetMode}"/>
+                                                <TextBlock Text="秒" Width="30" VerticalAlignment="Center"/>
+                                            </StackPanel>
+                                        </StackPanel>
                                     </StackPanel>
                                 </StackPanel>
-                            </StackPanel>
-                        </StackPanel>
-                        <StackPanel Orientation="Vertical" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2">
-                            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
-                                <Rectangle Height="2" Width="200" Stroke="#3B3B7B"/>
-                                <TextBlock Text="位置校准" Foreground="#615CDD" FontSize="24" Margin="15" FontWeight="Regular"/>
-                                <Rectangle Height="2" Width="200" Stroke="#3B3B7B"/>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" >
-                                <StackPanel Orientation="Horizontal" Margin="10,5,15,5"  HorizontalAlignment="Right">
-                                    <TextBlock Text="校准位置1" Margin="5,0" VerticalAlignment="Center"/>
-                                    <TextBox Margin="5,0,5,0" Width="90" Text="{Binding Parameter.FirstCalibrationPositiont}"/>
-                                    <TextBlock Text="m" Width="20" VerticalAlignment="Center"/>
-                                </StackPanel>
-                                <StackPanel Orientation="Horizontal" Margin="15,5,0,5"  HorizontalAlignment="Right">
-                                    <TextBlock Text="校准位置2" Margin="5,0" VerticalAlignment="Center"/>
-                                    <TextBox Margin="5,0,5,0" Width="100" Text="{Binding Parameter.SecondCalibrationPositiont}"/>
-                                    <TextBlock Text="m" Width="20" VerticalAlignment="Center"/>
+                                <StackPanel Orientation="Vertical" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2">
+                                    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
+                                        <Rectangle Height="2" Width="200" Stroke="#3B3B7B"/>
+                                        <TextBlock Text="位置校准" Foreground="#615CDD" FontSize="24" Margin="15" FontWeight="Regular"/>
+                                        <Rectangle Height="2" Width="200" Stroke="#3B3B7B"/>
+                                    </StackPanel>
+                                    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" >
+                                        <StackPanel Orientation="Horizontal" Margin="10,5,15,5"  HorizontalAlignment="Right">
+                                            <TextBlock Text="校准位置1" Margin="5,0" VerticalAlignment="Center"/>
+                                            <TextBox Margin="5,0,5,0" Width="90" Text="{Binding Parameter.FirstCalibrationPositiont}"/>
+                                            <TextBlock Text="m" Width="20" VerticalAlignment="Center"/>
+                                        </StackPanel>
+                                        <StackPanel Orientation="Horizontal" Margin="15,5,0,5"  HorizontalAlignment="Right">
+                                            <TextBlock Text="校准位置2" Margin="5,0" VerticalAlignment="Center"/>
+                                            <TextBox Margin="5,0,5,0" Width="100" Text="{Binding Parameter.SecondCalibrationPositiont}"/>
+                                            <TextBlock Text="m" Width="20" VerticalAlignment="Center"/>
+                                        </StackPanel>
+                                    </StackPanel>
                                 </StackPanel>
-                            </StackPanel>
+                            </Grid>
                         </StackPanel>
-                    </Grid>
-                </StackPanel>
-            </StackPanel>
-            <StackPanel Orientation="Horizontal"  Grid.ColumnSpan="3" Grid.Row="2" Margin="30" HorizontalAlignment="Center">
-                <Button  Content="取消" Width="100" Height="50" Margin="10,0" IsCancel="True" Style="{StaticResource CancelButton}"/>
-                <Button  Content="保存" Width="150" Height="50" Margin="20,0" IsDefault="True" Click="Save_Click" IsEnabled="{Binding IsConnected}"/>
-            </StackPanel>
-        </Grid>
-    </Border>
+                    </StackPanel>
+                    <StackPanel Orientation="Horizontal"  Grid.ColumnSpan="3" Grid.Row="2" Margin="30" HorizontalAlignment="Center">
+                        <Button  Content="取消" Width="100" Height="50" Margin="10,0" IsCancel="True" Style="{StaticResource CancelButton}"/>
+                        <Button  Content="保存" Width="150" Height="50" Margin="20,0" IsDefault="True" Click="Save_Click"/>
+                    </StackPanel>
+                </Grid>
+            </Border>
+        </Viewbox>
+    </Grid>
 </pu:WindowX>

+ 47 - 28
Pages/ParameterDialog.xaml.cs

@@ -34,10 +34,12 @@ namespace SWRIS.Pages
             _runningMode = GetRunningStatusByte(runningStatus);
             Equipment = equipment.ToEquipmentSettingViewModel(runningStatus);
             Equipment.IsConnected = _clientSocket != null && _clientSocket.Connected;
-            App.TcpServer.SendGetTwistFactorData(_serialNo, _clientSocket);
-            App.TcpServer.ClockResultReceived += TcpServer_ClockResultReceived;
-            App.TcpServer.EncoderDirectionResultReceived += TcpServer_EncoderDirectionResultReceived;
-
+            if (Equipment.IsConnected)
+            {
+                App.TcpServer.SendGetTwistFactorData(_serialNo, _clientSocket);
+                App.TcpServer.ClockResultReceived += TcpServer_ClockResultReceived;
+                App.TcpServer.EncoderDirectionResultReceived += TcpServer_EncoderDirectionResultReceived;
+            }
             DataContext = Equipment;
         }
 
@@ -51,37 +53,47 @@ namespace SWRIS.Pages
             Equipment.Parameter.SystemTime = e.Timestamp.TimestampToDateTime();
         }
 
-        protected void SetParameter()
+        protected async Task SetParameter()
         {
-            ThreadPool.QueueUserWorkItem(act =>
+            bool isConnected = _clientSocket != null && _clientSocket.Connected;
+            EquipmentModel equipment;
+            if (isConnected)
             {
-                EquipmentModel equipment = Equipment.ToEquipmentModel();
-                if (_clientSocket != null && _clientSocket.Connected)
+                // 已连接:后台同步参数到设备
+                equipment = await Task.Run(() =>
                 {
-                    EquipmentDataModel equipmentData = App.DataCenter.Equipments.FirstOrDefault(c => c.IpAddress == equipment.IpAddress);
-                    equipment.ToEquipmentDataModel(equipmentData);
+                    EquipmentModel eq = Equipment.ToEquipmentModel();
+                    EquipmentDataModel equipmentData = App.DataCenter.Equipments.FirstOrDefault(c => c.RopeNumber == eq.RopeNumber);
+                    var runningStatus = equipmentData?.RunningStatus ?? RunningStatus.SpatialNormal;
+                    eq.ToEquipmentDataModel(equipmentData);
                     StartOrStopDetection(equipmentData, RunningStatus.Stopped);
                     Thread.Sleep(1000);
-                    App.TcpServer.SendSyncParameters(equipment.Parameter, Equipment.SerialNo, _clientSocket);
+                    App.TcpServer.SendSyncParameters(eq.Parameter, Equipment.SerialNo, _clientSocket);
                     Thread.Sleep(300);
                     App.TcpServer.SendTurnLiveStreamData(true, _serialNo, _clientSocket);
                     Thread.Sleep(1000);
-                    StartOrStopDetection(equipmentData, equipmentData.RunningStatus);
+                    StartOrStopDetection(equipmentData, runningStatus);
+                    return eq;
+                });
+            }
+            else
+            {
+                // 未连接:仅转换模型
+                equipment = await Task.Run(() => Equipment.ToEquipmentModel());
+            }
+            // 保存配置
+            if (!ConfigHelper.SetEquipment(equipment))
+                return;
 
-                }
-                // 更新设备数据
-                if (ConfigHelper.SetEquipment(equipment))
-                {
-                    Application.Current.Dispatcher.Invoke(() =>
-                    {
-                        Thread.Sleep(500);
-                        NoticeBox.Show("参数设置成功", "通知", MessageBoxIcon.Success, true, 5000);
-                        DialogResult = true;
-                    });
-                    App.UpdateEquipment(equipment);
-                }
-            });
+            DialogResult = true;
+            App.UpdateEquipment(equipment);
+            NoticeBox.Show(isConnected ? "参数设置成功" : "参数已保存(未连接设备,仅保存到配置文件)",
+                           "通知",
+                           isConnected ? MessageBoxIcon.Success : MessageBoxIcon.Info,
+                           true,
+                           5000);
         }
+
         private void StartOrStopDetection(EquipmentDataModel equipmentData, RunningStatus runningStatus)
         {
             byte runningMode;
@@ -126,9 +138,9 @@ namespace SWRIS.Pages
         {
             return App.Config.GenerateNewSerialNo();
         }
-        private void Save_Click(object sender, RoutedEventArgs e)
+        private async void Save_Click(object sender, RoutedEventArgs e)
         {
-            SetParameter();
+            await SetParameter();
         }
 
         private void SetClockAndDirection_Click(object sender, RoutedEventArgs e)
@@ -185,13 +197,20 @@ namespace SWRIS.Pages
         {
             if (sender is ToggleButton toggleButton)
             {
+                // 检测模式为互斥选择,点击后强制保持选中(不允许取消已选中的模式)
+                toggleButton.IsChecked = true;
+
                 if (Enum.TryParse(toggleButton.Tag.ToString(), true, out RunningStatus runningStatus))
                 {
                     var newMode = GetRunningStatusByte(runningStatus);
                     if (_runningMode != newMode)
                     {
                         _runningMode = newMode;
-                        StartOrStopDetection(App.DataCenter.Equipments.First(c => c.SerialNo == _serialNo), runningStatus);
+                        var equipmentData = App.DataCenter.Equipments.FirstOrDefault(c => c.SerialNo == _serialNo);
+                        if (equipmentData != null)
+                        {
+                            StartOrStopDetection(equipmentData, runningStatus);
+                        }
                     }
                 }
             }

+ 196 - 0
Pages/RealTime/FourRopesControl.xaml

@@ -0,0 +1,196 @@
+<UserControl x:Class="SWRIS.Pages.RealTime.FourRopesControl"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:SWRIS.Pages.RealTime" 
+             xmlns:vm="clr-namespace:SWRIS.Models.ViewModel" 
+             xmlns:cvt="clr-namespace:SWRIS.Converters"
+             xmlns:ctr="clr-namespace:SWRIS.Controls" 
+             xmlns:enum="clr-namespace:SWRIS.Enums"
+             mc:Ignorable="d"  FontSize="20"
+             Foreground="#FFFFFF"  
+             FontFamily="{StaticResource PuHuiTiRegular}"
+             d:DesignHeight="800" d:DesignWidth="1200" 
+             x:Name="FourRopesRoot">
+    <UserControl.Resources>
+        <cvt:RiskLevelToColorConverter x:Key="RiskLevelToColorConverter"/>
+        <cvt:RiskLevelToTextConverter x:Key="RiskLevelToTextConverter"/>
+        <cvt:ConnectivityFaultToColorConverter x:Key="ConnectivityFaultConverter"/>
+        <cvt:CollectionCountToVisibilityConverter x:Key="CollectionCountToVisibilityConverter"/>
+        <cvt:ConnectivityToVisibilityConverter x:Key="ConnectivityToVisibilityConverter"/>
+        <cvt:StringEmptyToVisibilityConverter x:Key="StringEmptyToVisibilityConverter"/>
+    </UserControl.Resources>
+    <ItemsControl ItemsSource="{Binding Equipments}" Margin="25,25,0,-20">
+        <ItemsControl.ItemsPanel>
+            <ItemsPanelTemplate>
+                <UniformGrid Columns="2" Rows="2"/>
+            </ItemsPanelTemplate>
+        </ItemsControl.ItemsPanel>
+        <ItemsControl.ItemTemplate>
+            <DataTemplate>
+                <Border CornerRadius="10" Margin="0,0,25,25" Loaded="OnCardLoaded">
+                    <Border.Background>
+                        <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
+                            <GradientStop Color="#2a2956" Offset="0"/>
+                            <GradientStop Color="#161536" Offset="0.40"/>
+                            <GradientStop Color="#1f1e44" Offset="1"/>
+                        </LinearGradientBrush>
+                    </Border.Background>
+                    <Grid>
+                        <Grid.RowDefinitions>
+                            <RowDefinition Height="200"/>
+                            <RowDefinition Height="*"/>
+                            <RowDefinition Height="Auto"/>
+                        </Grid.RowDefinitions>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="*"/>
+                            <ColumnDefinition Width="180"/>
+                        </Grid.ColumnDefinitions>
+                        <!-- 左侧信息区域 -->
+                        <StackPanel Orientation="Vertical" Grid.Row="0" Grid.Column="0" Margin="30,20,10,0">
+                            <DockPanel>
+                                <!-- 钢丝绳状态及名称 -->
+                                <Rectangle Height="22" Width="18" RadiusX="6" RadiusY="6">
+                                    <Rectangle.Fill>
+                                        <MultiBinding Converter="{StaticResource ConnectivityFaultConverter}">
+                                            <Binding Path="IsConnect"/>
+                                            <Binding Path="Faults"/>
+                                            <Binding Path="Faults.Count" />
+                                        </MultiBinding>
+                                    </Rectangle.Fill>
+                                </Rectangle>
+                                <TextBlock Text="{Binding RopeName}" FontSize="22" FontWeight="Bold" 
+                                           FontFamily="{StaticResource PuHuiTiBold}" Margin="13,0" 
+                                           VerticalAlignment="Center"/>
+                                <StackPanel Orientation="Horizontal"
+                                            MouseLeftButtonDown="ParameterSetting_MouseDown" 
+                                            Cursor="Hand">
+                                    <Image Source="/Resources/eqSet.png" Margin="10,0,5,0" Width="20" Height="20"/>
+                                    <TextBlock Text="参数设置" FontSize="18" VerticalAlignment="Center"
+                                               Foreground="#8D86E5" FontWeight="Regular"/>
+                                </StackPanel>
+                            </DockPanel>
+
+                            <StackPanel Margin="0,20">
+                                <UniformGrid Columns="2" Rows="2" Width="430" HorizontalAlignment="Left">
+                                    <StackPanel Orientation="Horizontal" Margin="0,2">
+                                        <TextBlock Text="风险等级" Foreground="#7886B2"/>
+                                        <TextBlock FontWeight="Bold" FontFamily="{StaticResource PuHuiTiBold}" 
+                                                   Margin="20,0" 
+                                                   Text="{Binding RiskLevel,Converter={StaticResource RiskLevelToTextConverter}}" 
+                                                   Foreground="{Binding RiskLevel,Converter={StaticResource RiskLevelToColorConverter}}"/>
+                                    </StackPanel>
+                                    <StackPanel Orientation="Horizontal" Margin="-20,2,0,0">
+                                        <TextBlock Text="起升高度" Foreground="#7886B2"/>
+                                        <TextBlock Foreground="#FFFFFF" FontWeight="Bold"
+                                                   FontFamily="{StaticResource PuHuiTiBold}" Margin="40,0">
+                                            <Run Text="{Binding LiftHeight,StringFormat=#0.0}"/>
+                                            <Run Text="m"/>
+                                        </TextBlock>
+                                    </StackPanel>
+                                    <StackPanel Orientation="Horizontal" Margin="0,2">
+                                        <TextBlock Text="损伤数量" Foreground="#7886B2"/>
+                                        <TextBlock Text="{Binding DamageCount}" Foreground="#FFFFFF" 
+                                                   FontWeight="Bold" FontFamily="{StaticResource PuHuiTiBold}" 
+                                                   Margin="20,0"/>
+                                        <TextBlock Text="详情" Foreground="#7F74FF" FontSize="18" 
+                                                   FontWeight="Regular" Cursor="Hand"
+                                                   MouseLeftButtonDown="EquipmentAlarm_MouseDown" 
+                                                   Visibility="{Binding DamageCount,Converter={StaticResource CollectionCountToVisibilityConverter}}"/>
+                                    </StackPanel>
+                                    <StackPanel Orientation="Horizontal" Margin="-20,2,0,2">
+                                        <TextBlock Text="最大损伤值" Foreground="#7886B2"/>
+                                        <TextBlock Foreground="#FFFFFF" FontWeight="Bold" FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0">
+                                            <Run Text="{Binding MaxDamageValue,StringFormat=#0.0}"/>
+                                            <Run Text="%"/>
+                                        </TextBlock>
+                                    </StackPanel>
+                                </UniformGrid>
+                                <ctr:DamageRangeControl HorizontalAlignment="Stretch" 
+                                                        Height="45" 
+                                                        FontSize="16" 
+                                                        Margin="0,20,30,0"
+                                                        MildCount="{Binding MildCount}" 
+                                                        LightCount="{Binding LightCount}" 
+                                                        ModerateCount="{Binding ModerateCount}" 
+                                                        SevereCount="{Binding SevereCount}"
+                                                        CriticalCount="{Binding CriticalCount}" 
+                                                        ExtremeCount="{Binding ExtremeCount}"/>
+                            </StackPanel>
+                        </StackPanel>
+                        <!-- 右侧绳索设备控制 -->
+                        <ctr:RopeEquipmentControl Grid.RowSpan="2" Grid.Column="1" Grid.Row="0"
+                                                  PanelMargin="-80,25,0,0" Margin="0,0,0,-16"
+                                                  EquipmentTopPosition="100"
+                                                  PanelNameFontSize="18"
+                                                  PanelValueFontSize="20"
+                                                  RopeImageSource="/Resources/rope_block.png" 
+                                                  EquipmentImageSource="/Resources/equipment.png"
+                                                  AnimationDuration="0.5">
+                            <ctr:RopeEquipmentControl.Style>
+                                <Style TargetType="ctr:RopeEquipmentControl">
+                                    <Style.Triggers>
+                                        <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Stoped}">
+                                            <Setter Property="IsAnimationRunning" Value="False"/>
+                                        </DataTrigger>
+                                        <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Forward}">
+                                            <Setter Property="IsAnimationRunning" Value="True"/>
+                                            <Setter Property="IsAnimationReversed" Value="False"/>
+                                        </DataTrigger>
+                                        <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Reverse}">
+                                            <Setter Property="IsAnimationRunning" Value="True"/>
+                                            <Setter Property="IsAnimationReversed" Value="True"/>
+                                        </DataTrigger>
+                                    </Style.Triggers>
+                                </Style>
+                            </ctr:RopeEquipmentControl.Style>
+                        </ctr:RopeEquipmentControl>
+
+                        <!-- 实时图表 -->
+                        <ctr:RealTimeLineChart Grid.Row="1" Margin="0,0,-90,-5" x:Name="ChartLine"
+                                               SensorCount="{Binding SensorCount}"
+                                               SamplingStep="{Binding SamplingStep,Mode=TwoWay}"
+                                               RopeLength="{Binding RopeLength}"/>
+                        <!-- 调试信息 + 实时数据 -->
+                        <DockPanel Grid.Row="2" VerticalAlignment="Center" Margin="20,-7,10,2">
+                            <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Left"
+                                        Visibility="{Binding DebugMessage.Message,Converter={StaticResource StringEmptyToVisibilityConverter}}">
+                                <TextBlock Style="{StaticResource HeartbeatTextBlockStyle}" FontSize="15" Margin="0,2,0,0"
+                                           VerticalAlignment="Center" Foreground="#7F74FF"/>
+                                <TextBlock Text="{Binding DebugMessage.DateTime,StringFormat=HH:mm:ss}" 
+                                           FontSize="15" Margin="5,0" VerticalAlignment="Center" 
+                                           Foreground="#7F74FF"/>
+                                <TextBlock Text="{Binding DebugMessage.Message}" FontSize="15" Margin="5,0" 
+                                           VerticalAlignment="Center" Foreground="#7F74FF"/>
+                            </StackPanel>
+                            <ItemsControl ItemsSource="{Binding LiveStreamValues}" Margin="10,4,-90,0" HorizontalAlignment="Right">
+                                <ItemsControl.ItemsPanel>
+                                    <ItemsPanelTemplate>
+                                        <WrapPanel/>
+                                    </ItemsPanelTemplate>
+                                </ItemsControl.ItemsPanel>
+                                <ItemsControl.ItemTemplate>
+                                    <DataTemplate>
+                                        <TextBlock Text="{Binding}" FontSize="12" Margin="4,0,0,0"
+                                                   Width="20" Foreground="White" Opacity="0.2" FontWeight="Medium">
+                                            <TextBlock.Style>
+                                                <Style TargetType="TextBlock">
+                                                    <Style.Triggers>
+                                                        <DataTrigger Binding="{Binding}" Value="0">
+                                                            <Setter Property="Visibility" Value="Collapsed"/>
+                                                        </DataTrigger>
+                                                    </Style.Triggers>
+                                                </Style>
+                                            </TextBlock.Style>
+                                        </TextBlock>
+                                    </DataTemplate>
+                                </ItemsControl.ItemTemplate>
+                            </ItemsControl>
+                        </DockPanel>
+                    </Grid>
+                </Border>
+            </DataTemplate>
+        </ItemsControl.ItemTemplate>
+    </ItemsControl>
+</UserControl>

+ 67 - 0
Pages/RealTime/FourRopesControl.xaml.cs

@@ -0,0 +1,67 @@
+using SWRIS.Controls;
+using SWRIS.Extensions;
+using SWRIS.Models;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace SWRIS.Pages.RealTime
+{
+    public partial class FourRopesControl : UserControl
+    {
+        public static readonly DependencyProperty EquipmentsProperty =
+        DependencyProperty.Register("Equipments", typeof(ObservableCollection<EquipmentDataModel>),
+            typeof(FourRopesControl),
+            new PropertyMetadata(null));
+
+        public ObservableCollection<EquipmentDataModel> Equipments
+        {
+            get { return (ObservableCollection<EquipmentDataModel>)GetValue(EquipmentsProperty); }
+            set { SetValue(EquipmentsProperty, value); }
+        }
+
+        public FourRopesControl()
+        {
+            InitializeComponent();
+            DataContext = this;
+        }
+
+        private void EquipmentAlarm_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            if ((sender as TextBlock)?.DataContext is EquipmentDataModel equipmentData)
+            {
+                DamagesDialog damagesDialog = new DamagesDialog(equipmentData.Alarms);
+                damagesDialog.ShowDialog(true);
+            }
+        }
+
+        private void ParameterSetting_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            if ((sender as StackPanel)?.DataContext is EquipmentDataModel equipmentData)
+            {
+                var equipment = App.Config.Equipments.FirstOrDefault(c => c.RopeNumber == equipmentData.RopeNumber);
+                if (equipmentData != null && equipment != null)
+                {
+                    ParameterDialog parameterDialog = new ParameterDialog(equipment, equipmentData.ClientSocket, equipmentData.RunningStatus);
+                    parameterDialog.ShowDialog(true);
+                }
+            }
+        }
+
+        private void OnCardLoaded(object sender, RoutedEventArgs e)
+        {
+            var border = sender as Border;
+            if (!(border?.DataContext is EquipmentDataModel data)) return;
+            if (border.FindName("ChartLine") is RealTimeLineChart chart)
+            {
+                var chartSource = data.Chart?.GetChartSource();
+                if (chart != null && chartSource != null)
+                {
+                    chart.AddDataPoints(chartSource);
+                }
+                data.Chart = chart;
+            }
+        }
+    }
+}

+ 0 - 523
Pages/RealTime/FourRopesPage.xaml

@@ -1,523 +0,0 @@
-<Page x:Class="SWRIS.Pages.RealTime.FourRopesPage"
-      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
-      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
-      xmlns:local="clr-namespace:SWRIS.Pages.RealTime" 
-      xmlns:vm="clr-namespace:SWRIS.Models.ViewModel" 
-      xmlns:cvt="clr-namespace:SWRIS.Converters"
-      xmlns:ctr="clr-namespace:SWRIS.Controls" 
-      xmlns:enum="clr-namespace:SWRIS.Enums"
-      mc:Ignorable="d"  FontSize="20"
-      Foreground="#FFFFFF"
-      FontFamily="{StaticResource PuHuiTiRegular}"
-      d:DesignHeight="900" d:DesignWidth="1920" 
-      Title="FourRopesPage">
-    <Page.DataContext>
-        <vm:FourRopesViewModel/>
-    </Page.DataContext>
-    <Page.Resources>
-        <cvt:RiskLevelToColorConverter x:Key="RiskLevelToColorConverter"/>
-        <cvt:RiskLevelToTextConverter x:Key="RiskLevelToTextConverter"/>
-        <cvt:ConnectivityFaultToColorConverter x:Key="ConnectivityFaultConverter"/>
-        <cvt:CollectionCountToVisibilityConverter x:Key="CollectionCountToVisibilityConverter"/>
-        <cvt:ConnectivityToVisibilityConverter x:Key="ConnectivityToVisibilityConverter"/>
-        <cvt:StringEmptyToVisibilityConverter x:Key="StringEmptyToVisibilityConverter"/>
-    </Page.Resources>
-    <Grid Margin="25,30,25,7">
-        <Grid.ColumnDefinitions>
-            <ColumnDefinition Width="*"/>
-            <ColumnDefinition Width="*"/>
-            <ColumnDefinition Width="370"/>
-        </Grid.ColumnDefinitions>
-        <Grid.RowDefinitions>
-            <RowDefinition Height="*"/>
-            <RowDefinition Height="*"/>
-        </Grid.RowDefinitions>
-        <Border Grid.Row="0" Grid.Column="0" CornerRadius="10"  Margin="0,0,15,15" DataContext="{Binding FirstEquipmentData}">
-            <Border.Background>
-                <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
-                    <GradientStop Color="#2a2956" Offset="0"/>
-                    <GradientStop Color="#161536" Offset="0.40"/>
-                    <GradientStop Color="#1f1e44" Offset="1"/>
-                </LinearGradientBrush>
-            </Border.Background>
-            <Grid>
-                <Grid.RowDefinitions>
-                    <RowDefinition Height="200"/>
-                    <RowDefinition Height="*"/>
-                    <RowDefinition Height="Auto"/>
-                </Grid.RowDefinitions>
-                <Grid.ColumnDefinitions>
-                    <ColumnDefinition Width="*"/>
-                    <ColumnDefinition Width="210"/>
-                </Grid.ColumnDefinitions>
-                <StackPanel Orientation="Vertical" Grid.Row="0" Grid.Column="0" Margin="30,20,10,0">
-                    <DockPanel>
-                        <!--钢丝绳状态及名称-->
-                        <Rectangle Height="20" Width="16" RadiusX="6" RadiusY="6">
-                            <Rectangle.Fill>
-                                <MultiBinding Converter="{StaticResource ConnectivityFaultConverter}">
-                                    <Binding Path="IsConnect"/>
-                                    <Binding Path="Faults"/>
-                                </MultiBinding>
-                            </Rectangle.Fill>
-                        </Rectangle>
-                        <TextBlock Text="{Binding RopeName}" FontSize="24" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" 
-                                   Margin="10,0" VerticalAlignment="Center"/>
-                        <StackPanel Orientation="Horizontal" MouseLeftButtonDown="FirstParameterSetting_MouseDown" Cursor="Hand">
-                            <Image Source="/Resources/eqSet.png" Margin="10,0,5,0" Width="20" Height="20"/>
-                            <TextBlock Text="参数设置" FontSize="18" VerticalAlignment="Center" Foreground="#8D86E5" FontWeight="Regular"/>
-                        </StackPanel>
-                        <!--消息通知-->
-                        <ctr:NotificationBar Height="35" MaxWidth="240" FontSize="20" HorizontalAlignment="Right"
-                                      Messages="{Binding Messages}" Margin="0,0,-120,0"/>
-                    </DockPanel>
-                    <StackPanel Margin="0,20">
-                        <UniformGrid Columns="2" Rows="2" Width="400" HorizontalAlignment="Left">
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="风险等级" Foreground="#7886B2"/>
-                                <TextBlock  FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0" 
-                                           Text="{Binding RiskLevel,Converter={StaticResource RiskLevelToTextConverter}}" 
-                                           Foreground="{Binding RiskLevel,Converter={StaticResource RiskLevelToColorConverter}}"/>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="起升高度"  Foreground="#7886B2"/>
-                                <TextBlock   Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="40,0">
-                                    <Run Text="{Binding LiftHeight,StringFormat=#0.0}"/>
-                                    <Run Text="m"/>
-                                </TextBlock>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="损伤数量"  Foreground="#7886B2"/>
-                                <TextBlock Text="{Binding DamageCount}"   Foreground="#FFFFFF" FontWeight="Bold"  
-                                           FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0" />
-                                <TextBlock Text="详情" Foreground="#7F74FF" FontSize="18" FontWeight="Regular"
-                                           Cursor="Hand"  MouseLeftButtonDown="FirstEquipmentAlarm_MouseDown" 
-                                           Visibility="{Binding DamageCount,Converter={StaticResource CollectionCountToVisibilityConverter}}"/>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="最大损伤值"  Foreground="#7886B2"/>
-                                <TextBlock   Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0">
-                                    <Run Text="{Binding MaxDamageValue,StringFormat=#0.0}"/>
-                                    <Run Text="%"/>
-                                </TextBlock>
-                            </StackPanel>
-                        </UniformGrid>
-                        <ctr:DamageRangeControl HorizontalAlignment="Stretch" Height="40" FontSize="16" Margin="0,20,30,0" 
-                                                MildCount="{Binding MildCount}" LightCount="{Binding LightCount}" 
-                                                ModerateCount="{Binding ModerateCount}" SevereCount="{Binding SevereCount}"
-                                                CriticalCount="{Binding CriticalCount}" ExtremeCount="{Binding ExtremeCount}"/>
-                    </StackPanel>
-                </StackPanel>
-                <ctr:RopeEquipmentControl Grid.RowSpan="2" Grid.Column="1" Grid.Row="0"
-                                          PanelMargin="-80,30,0,0"
-                                          EquipmentTopPosition="160"
-                                          PanelNameFontSize="18"
-                                          PanelValueFontSize="20"
-                                          RopeImageSource="/Resources/rope_block.png" 
-                                          EquipmentImageSource="/Resources/equipment.png"
-                                          AnimationDuration="0.5" RopeHeight="435">
-                    <ctr:RopeEquipmentControl.Style>
-                        <Style TargetType="ctr:RopeEquipmentControl">
-                            <Style.Triggers>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Stoped}">
-                                    <Setter Property="IsAnimationRunning" Value="False"/>
-                                </DataTrigger>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Forward}">
-                                    <Setter Property="IsAnimationRunning" Value="True"/>
-                                    <Setter Property="IsAnimationReversed" Value="False"/>
-                                </DataTrigger>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Reverse}">
-                                    <Setter Property="IsAnimationRunning" Value="True"/>
-                                    <Setter Property="IsAnimationReversed" Value="True"/>
-                                </DataTrigger>
-                            </Style.Triggers>
-                        </Style>
-                    </ctr:RopeEquipmentControl.Style>
-                </ctr:RopeEquipmentControl>
-
-                <ctr:RealTimeLineChart x:Name="firstChartLine" Grid.Row="1" 
-                               Margin="0,0,-90,-5"  
-                               SensorCount="{Binding SensorCount}"
-                               SamplingStep="{Binding SamplingStep,Mode=TwoWay}"
-                               RopeLength="{Binding RopeLength}"/>
-
-                <StackPanel Grid.Row="2" Orientation="Horizontal" Margin="20,-5,-100,3" 
-                            Visibility="{Binding DebugMessage.Message,Converter={StaticResource StringEmptyToVisibilityConverter}}">
-                    <TextBlock Style="{StaticResource HeartbeatTextBlockStyle}"  FontSize="17"
-                               VerticalAlignment="Center"
-                               Foreground="#7F74FF"/>
-                    <TextBlock Text="{Binding  DebugMessage.DateTime,StringFormat=HH:mm:ss}" FontSize="16" Margin="5,0" 
-                               VerticalAlignment="Center" 
-                               Foreground="#7F74FF"/>
-                    <TextBlock Text="{Binding DebugMessage.Message}" FontSize="16" Margin="5,0" 
-                               VerticalAlignment="Center"
-                               Foreground="#7F74FF"/>
-                </StackPanel>
-            </Grid>
-        </Border>
-        <Border Grid.Row="0" Grid.Column="1" CornerRadius="10"  Margin="15,0,0,15" DataContext="{Binding SecondEquipmentData}">
-            <Border.Background>
-                <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
-                    <GradientStop Color="#2a2956" Offset="0"/>
-                    <GradientStop Color="#161536" Offset="0.40"/>
-                    <GradientStop Color="#1f1e44" Offset="1"/>
-                </LinearGradientBrush>
-            </Border.Background>
-            <Grid>
-                <Grid.RowDefinitions>
-                    <RowDefinition Height="200"/>
-                    <RowDefinition Height="*"/>
-                    <RowDefinition Height="Auto"/>
-                </Grid.RowDefinitions>
-                <Grid.ColumnDefinitions>
-                    <ColumnDefinition Width="*"/>
-                    <ColumnDefinition Width="210"/>
-                </Grid.ColumnDefinitions>
-                <StackPanel Orientation="Vertical" Grid.Row="0" Grid.Column="0"  Margin="30,20,10,0">
-                    <DockPanel>
-                        <!--钢丝绳状态及名称-->
-                        <Rectangle Height="20" Width="16" RadiusX="6" RadiusY="6">
-                            <Rectangle.Fill>
-                                <MultiBinding Converter="{StaticResource ConnectivityFaultConverter}">
-                                    <Binding Path="IsConnect"/>
-                                    <Binding Path="Faults"/>
-                                </MultiBinding>
-                            </Rectangle.Fill>
-                        </Rectangle>
-                        <TextBlock Text="{Binding RopeName}" FontSize="24" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" 
-                                   Margin="13,0" VerticalAlignment="Center"/>
-                            <StackPanel Orientation="Horizontal" MouseLeftButtonDown="SecondParameterSetting_MouseDown" Cursor="Hand">
-                            <Image Source="/Resources/eqSet.png" Margin="10,0,5,0" Width="20" Height="20"/>
-                            <TextBlock Text="参数设置" FontSize="18" VerticalAlignment="Center" Foreground="#8D86E5" FontWeight="Regular"/>
-                        </StackPanel>
-                        <!--消息通知-->
-                        <ctr:NotificationBar Height="35" MaxWidth="240" FontSize="20" HorizontalAlignment="Right"
-                                Messages="{Binding Messages}"/>
-                    </DockPanel>
-                    <StackPanel Margin="0,20">
-                        <UniformGrid Columns="2" Rows="2" Width="400" HorizontalAlignment="Left">
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="风险等级"  Foreground="#7886B2"/>
-                                <TextBlock  FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0" 
-                                           Text="{Binding RiskLevel,Converter={StaticResource RiskLevelToTextConverter}}" 
-                                           Foreground="{Binding RiskLevel,Converter={StaticResource RiskLevelToColorConverter}}"/>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="起升高度"  Foreground="#7886B2"/>
-                                <TextBlock   Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="40,0">
-                                    <Run Text="{Binding LiftHeight,StringFormat=#0.0}"/>
-                                    <Run Text="m"/>
-                                </TextBlock>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="损伤数量"  Foreground="#7886B2"/>
-                                <TextBlock Text="{Binding DamageCount}"   Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0" />
-                                <TextBlock Text="详情" Foreground="#7F74FF" FontSize="18" FontWeight="Regular"
-                                           Cursor="Hand"  MouseLeftButtonDown="SecondEquipmentAlarm_MouseDown" 
-                                           Visibility="{Binding DamageCount,Converter={StaticResource CollectionCountToVisibilityConverter}}"/>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="最大损伤值"  Foreground="#7886B2"/>
-                                <TextBlock   Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0">
-                                    <Run Text="{Binding MaxDamageValue,StringFormat=#0.0}"/>
-                                    <Run Text="%"/>
-                                </TextBlock>
-                            </StackPanel>
-                        </UniformGrid>
-                        <ctr:DamageRangeControl HorizontalAlignment="Stretch" Height="40" FontSize="16" Margin="0,20,30,0"
-                                                MildCount="{Binding MildCount}" LightCount="{Binding LightCount}" 
-                                                ModerateCount="{Binding ModerateCount}" SevereCount="{Binding SevereCount}"
-                                                CriticalCount="{Binding CriticalCount}" ExtremeCount="{Binding ExtremeCount}"/>
-                    </StackPanel>
-                </StackPanel>
-                <ctr:RopeEquipmentControl Grid.RowSpan="2" Grid.Column="1" Grid.Row="0"
-                                          PanelMargin="-80,30,0,0"
-                                          EquipmentTopPosition="160"
-                                          PanelNameFontSize="18"
-                                          PanelValueFontSize="20"
-                                          RopeImageSource="/Resources/rope_block.png" 
-                                          EquipmentImageSource="/Resources/equipment.png"
-                                          AnimationDuration="0.5" RopeHeight="435">
-                    <ctr:RopeEquipmentControl.Style>
-                        <Style TargetType="ctr:RopeEquipmentControl">
-                            <Style.Triggers>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Stoped}">
-                                    <Setter Property="IsAnimationRunning" Value="False"/>
-                                </DataTrigger>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Forward}">
-                                    <Setter Property="IsAnimationRunning" Value="True"/>
-                                    <Setter Property="IsAnimationReversed" Value="False"/>
-                                </DataTrigger>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Reverse}">
-                                    <Setter Property="IsAnimationRunning" Value="True"/>
-                                    <Setter Property="IsAnimationReversed" Value="True"/>
-                                </DataTrigger>
-                            </Style.Triggers>
-                        </Style>
-                    </ctr:RopeEquipmentControl.Style>
-                </ctr:RopeEquipmentControl>
-                <ctr:RealTimeLineChart x:Name="secondChartLine" Grid.Row="1" 
-                               Margin="0,0,-90,-5"  
-                               SensorCount="{Binding SensorCount}"
-                               SamplingStep="{Binding SamplingStep,Mode=TwoWay}"
-                               RopeLength="{Binding RopeLength}"/>
-
-                <StackPanel Grid.Row="2" Orientation="Horizontal" Margin="20,-5,-100,3" 
-                            Visibility="{Binding DebugMessage.Message,Converter={StaticResource StringEmptyToVisibilityConverter}}">
-                    <TextBlock Style="{StaticResource HeartbeatTextBlockStyle}"  FontSize="17"
-                               VerticalAlignment="Center"
-                               Foreground="#7F74FF"/>
-                    <TextBlock Text="{Binding  DebugMessage.DateTime,StringFormat=HH:mm:ss}" FontSize="16" Margin="5,0" 
-                               VerticalAlignment="Center" 
-                               Foreground="#7F74FF"/>
-                    <TextBlock Text="{Binding DebugMessage.Message}" FontSize="16" Margin="5,0" 
-                               VerticalAlignment="Center"
-                               Foreground="#7F74FF"/>
-                </StackPanel>
-
-            </Grid>
-        </Border>
-        <Border Grid.Row="1" Grid.Column="0" CornerRadius="10"  Margin="0,15,15,0" DataContext="{Binding ThirdEquipmentData}">
-            <Border.Background>
-                <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
-                    <GradientStop Color="#2a2956" Offset="0"/>
-                    <GradientStop Color="#161536" Offset="0.40"/>
-                    <GradientStop Color="#1f1e44" Offset="1"/>
-                </LinearGradientBrush>
-            </Border.Background>
-            <Grid>
-                <Grid.RowDefinitions>
-                    <RowDefinition Height="200"/>
-                    <RowDefinition Height="*"/>
-                    <RowDefinition Height="Auto"/>
-                </Grid.RowDefinitions>
-                <Grid.ColumnDefinitions>
-                    <ColumnDefinition Width="*"/>
-                    <ColumnDefinition Width="210"/>
-                </Grid.ColumnDefinitions>
-                <StackPanel Orientation="Vertical" Grid.Row="0" Grid.Column="0" Margin="30,20,10,0">
-                    <DockPanel>
-                        <!--钢丝绳状态及名称-->
-                        <Rectangle Height="20" Width="16" RadiusX="6" RadiusY="6">
-                            <Rectangle.Fill>
-                                <MultiBinding Converter="{StaticResource ConnectivityFaultConverter}">
-                                    <Binding Path="IsConnect"/>
-                                    <Binding Path="Faults"/>
-                                </MultiBinding>
-                            </Rectangle.Fill>
-                        </Rectangle>
-                        <TextBlock Text="{Binding RopeName}" FontSize="24" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="13,0" VerticalAlignment="Center"/>
-                        <StackPanel Orientation="Horizontal" MouseLeftButtonDown="ThirdParameterSetting_MouseDown" Cursor="Hand">
-                            <Image Source="/Resources/eqSet.png" Margin="10,0,5,0" Width="20" Height="20"/>
-                            <TextBlock Text="参数设置" FontSize="18" VerticalAlignment="Center" Foreground="#8D86E5" FontWeight="Regular"/>
-                        </StackPanel>
-                        <!--消息通知-->
-                        <ctr:NotificationBar Height="35" MaxWidth="240" FontSize="20" HorizontalAlignment="Right" Messages="{Binding Messages}"/>
-                    </DockPanel>
-                    <StackPanel Margin="0,20">
-                        <UniformGrid Columns="2" Rows="2" Width="400" HorizontalAlignment="Left">
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="风险等级"  Foreground="#7886B2"/>
-                                <TextBlock  FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0" 
-                                           Text="{Binding RiskLevel,Converter={StaticResource RiskLevelToTextConverter}}" 
-                                           Foreground="{Binding RiskLevel,Converter={StaticResource RiskLevelToColorConverter}}"/>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="起升高度"  Foreground="#7886B2"/>
-                                <TextBlock   Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="40,0">
-                                    <Run Text="{Binding LiftHeight,StringFormat=#0.0}"/>
-                                    <Run Text="m"/>
-                                </TextBlock>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="损伤数量"  Foreground="#7886B2"/>
-                                <TextBlock Text="{Binding DamageCount}"   Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0" />
-                                <TextBlock Text="详情" Foreground="#7F74FF" FontSize="18" FontWeight="Regular"
-                                           Cursor="Hand"  MouseLeftButtonDown="ThirdEquipmentAlarm_MouseDown" 
-                                           Visibility="{Binding DamageCount,Converter={StaticResource CollectionCountToVisibilityConverter}}"/>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="最大损伤值"  Foreground="#7886B2"/>
-                                <TextBlock   Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0">
-                                    <Run Text="{Binding MaxDamageValue,StringFormat=#0.0}"/>
-                                    <Run Text="%"/>
-                                </TextBlock>
-                            </StackPanel>
-                        </UniformGrid>
-                        <ctr:DamageRangeControl HorizontalAlignment="Stretch" Height="40" FontSize="16" Margin="0,20,30,0"
-                                                MildCount="{Binding MildCount}" LightCount="{Binding LightCount}" 
-                                                ModerateCount="{Binding ModerateCount}" SevereCount="{Binding SevereCount}"
-                                                CriticalCount="{Binding CriticalCount}" ExtremeCount="{Binding ExtremeCount}"/>
-                    </StackPanel>
-                </StackPanel>
-                <ctr:RopeEquipmentControl Grid.RowSpan="2" Grid.Column="1" Grid.Row="0"
-                                          PanelMargin="-80,30,0,0"
-                                          EquipmentTopPosition="160"
-                                          PanelNameFontSize="18"
-                                          PanelValueFontSize="20"
-                                          RopeImageSource="/Resources/rope_block.png" 
-                                          EquipmentImageSource="/Resources/equipment.png"
-                                          AnimationDuration="0.5" RopeHeight="435">
-                    <ctr:RopeEquipmentControl.Style>
-                        <Style TargetType="ctr:RopeEquipmentControl">
-                            <Style.Triggers>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Stoped}">
-                                    <Setter Property="IsAnimationRunning" Value="False"/>
-                                </DataTrigger>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Forward}">
-                                    <Setter Property="IsAnimationRunning" Value="True"/>
-                                    <Setter Property="IsAnimationReversed" Value="False"/>
-                                </DataTrigger>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Reverse}">
-                                    <Setter Property="IsAnimationRunning" Value="True"/>
-                                    <Setter Property="IsAnimationReversed" Value="True"/>
-                                </DataTrigger>
-                            </Style.Triggers>
-                        </Style>
-                    </ctr:RopeEquipmentControl.Style>
-                </ctr:RopeEquipmentControl>
-                <ctr:RealTimeLineChart x:Name="thirdChartLine" Grid.Row="1"  Margin="0,0,-90,-5"  
-                                       SensorCount="{Binding SensorCount}"
-                                       SamplingStep="{Binding SamplingStep,Mode=TwoWay}"
-                                       RopeLength="{Binding RopeLength}"/>
-
-                <StackPanel Grid.Row="2" Orientation="Horizontal" Margin="20,-5,-100,3"
-                            Visibility="{Binding DebugMessage.Message,Converter={StaticResource StringEmptyToVisibilityConverter}}">
-                    <TextBlock Style="{StaticResource HeartbeatTextBlockStyle}"  FontSize="17"
-                               VerticalAlignment="Center"
-                               Foreground="#7F74FF"/>
-                    <TextBlock Text="{Binding DebugMessage.DateTime,StringFormat=HH:mm:ss}" FontSize="16" Margin="5,0" 
-                               VerticalAlignment="Center" 
-                               Foreground="#7F74FF"/>
-                    <TextBlock Text="{Binding DebugMessage.Message}" FontSize="16" Margin="5,0" 
-                               VerticalAlignment="Center"
-                               Foreground="#7F74FF"/>
-                </StackPanel>
-            </Grid>
-        </Border>
-        <Border Grid.Row="1" Grid.Column="1" CornerRadius="10" Margin="15,15,0,0" DataContext="{Binding FourthEquipmentData}">
-            <Border.Background>
-                <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
-                    <GradientStop Color="#2a2956" Offset="0"/>
-                    <GradientStop Color="#161536" Offset="0.40"/>
-                    <GradientStop Color="#1f1e44" Offset="1"/>
-                </LinearGradientBrush>
-            </Border.Background>
-            <Grid>
-                <Grid.RowDefinitions>
-                    <RowDefinition Height="200"/>
-                    <RowDefinition Height="*"/>
-                    <RowDefinition Height="Auto"/>
-                </Grid.RowDefinitions>
-                <Grid.ColumnDefinitions>
-                    <ColumnDefinition Width="*"/>
-                    <ColumnDefinition Width="210"/>
-                </Grid.ColumnDefinitions>
-                <StackPanel Orientation="Vertical" Grid.Row="0" Grid.Column="0"  Margin="30,20,10,0">
-                    <DockPanel>
-                        <!--钢丝绳状态及名称-->
-                        <Rectangle Height="20" Width="16" RadiusX="6" RadiusY="6">
-                            <Rectangle.Fill>
-                                <MultiBinding Converter="{StaticResource ConnectivityFaultConverter}">
-                                    <Binding Path="IsConnect"/>
-                                    <Binding Path="Faults"/>
-                                </MultiBinding>
-                            </Rectangle.Fill>
-                        </Rectangle>
-                        <TextBlock Text="{Binding RopeName}" FontSize="24" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="13,0" VerticalAlignment="Center"/>
-                        <StackPanel Orientation="Horizontal" MouseLeftButtonDown="FourthParameterSetting_MouseDown" Cursor="Hand">
-                            <Image Source="/Resources/eqSet.png" Margin="10,0,5,0" Width="20" Height="20"/>
-                            <TextBlock Text="参数设置" FontSize="18" VerticalAlignment="Center" Foreground="#8D86E5" FontWeight="Regular"/>
-                        </StackPanel>
-                        <!--消息通知-->
-                        <ctr:NotificationBar Height="35" MaxWidth="250" FontSize="20" Margin="0,0,-120,0" TextPadding="8,4"
-                                             CornerRadius="15"
-                                             HorizontalAlignment="Right" Messages="{Binding Messages}"/>
-                    </DockPanel>
-                    <StackPanel Margin="0,20">
-                        <UniformGrid Columns="2" Rows="2" Width="400" HorizontalAlignment="Left">
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="风险等级"  Foreground="#7886B2"/>
-                                <TextBlock  FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0" 
-                                           Text="{Binding RiskLevel,Converter={StaticResource RiskLevelToTextConverter}}" 
-                                           Foreground="{Binding RiskLevel,Converter={StaticResource RiskLevelToColorConverter}}"/>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="起升高度"  Foreground="#7886B2"/>
-                                <TextBlock   Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="40,0">
-                                    <Run Text="{Binding LiftHeight,StringFormat=#0.0}"/>
-                                    <Run Text="m"/>
-                                </TextBlock>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="损伤数量"  Foreground="#7886B2"/>
-                                <TextBlock Text="{Binding DamageCount}"   Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0" />
-                                <TextBlock Text="详情" Foreground="#7F74FF" FontSize="18" FontWeight="Regular"
-                                           Cursor="Hand"  MouseLeftButtonDown="FourthEquipmentAlarm_MouseDown" 
-                                           Visibility="{Binding DamageCount,Converter={StaticResource CollectionCountToVisibilityConverter}}"/>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="最大损伤值"  Foreground="#7886B2"/>
-                                <TextBlock   Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0">
-                                    <Run Text="{Binding MaxDamageValue,StringFormat=#0.0}"/>
-                                    <Run Text="%"/>
-                                </TextBlock>
-                            </StackPanel>
-                        </UniformGrid>
-                        <ctr:DamageRangeControl HorizontalAlignment="Stretch" Height="40" FontSize="16" Margin="0,20,30,0"
-                                                MildCount="{Binding MildCount}" LightCount="{Binding LightCount}" 
-                                                ModerateCount="{Binding ModerateCount}" SevereCount="{Binding SevereCount}"
-                                                CriticalCount="{Binding CriticalCount}" ExtremeCount="{Binding ExtremeCount}"/>
-                    </StackPanel>
-                </StackPanel>
-                <ctr:RopeEquipmentControl Grid.RowSpan="2" Grid.Column="1" Grid.Row="0" 
-                                          PanelMargin="-80,30,0,0"
-                                          EquipmentTopPosition="160"
-                                          PanelNameFontSize="18"
-                                          PanelValueFontSize="20"
-                                          RopeImageSource="/Resources/rope_block.png" 
-                                          EquipmentImageSource="/Resources/equipment.png"
-                                          AnimationDuration="0.5" RopeHeight="435">
-                    <ctr:RopeEquipmentControl.Style>
-                        <Style TargetType="ctr:RopeEquipmentControl">
-                            <Style.Triggers>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Stoped}">
-                                    <Setter Property="IsAnimationRunning" Value="False"/>
-                                </DataTrigger>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Forward}">
-                                    <Setter Property="IsAnimationRunning" Value="True"/>
-                                    <Setter Property="IsAnimationReversed" Value="False"/>
-                                </DataTrigger>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Reverse}">
-                                    <Setter Property="IsAnimationRunning" Value="True"/>
-                                    <Setter Property="IsAnimationReversed" Value="True"/>
-                                </DataTrigger>
-                            </Style.Triggers>
-                        </Style>
-                    </ctr:RopeEquipmentControl.Style>
-                </ctr:RopeEquipmentControl>
-                <ctr:RealTimeLineChart x:Name="fourthChartLine" Grid.Row="1" 
-                                       Margin="0,0,-90,-5"
-                                       SensorCount="{Binding SensorCount}"
-                                       SamplingStep="{Binding SamplingStep,Mode=TwoWay}"
-                                       RopeLength="{Binding RopeLength}"/>
-
-                <StackPanel Grid.Row="2" Orientation="Horizontal" Margin="20,-5,-100,3" 
-                            Visibility="{Binding DebugMessage.Message,Converter={StaticResource StringEmptyToVisibilityConverter}}">
-                    <TextBlock Style="{StaticResource HeartbeatTextBlockStyle}"  FontSize="17"
-                               VerticalAlignment="Center"
-                               Foreground="#7F74FF"/>
-                    <TextBlock Text="{Binding  DebugMessage.DateTime,StringFormat=HH:mm:ss}" FontSize="16" Margin="5,0" 
-                               VerticalAlignment="Center" 
-                               Foreground="#7F74FF"/>
-                    <TextBlock Text="{Binding DebugMessage.Message}" FontSize="16" Margin="5,0" 
-                               VerticalAlignment="Center"
-                               Foreground="#7F74FF"/>
-                </StackPanel>
-            </Grid>
-        </Border>
-        <ctr:DamageRecordControl Grid.Column="2" Grid.RowSpan="2" Margin="25,0,0,0" Records="{Binding Records}" Title="近期记录"/>
-    </Grid>
-</Page>

+ 0 - 166
Pages/RealTime/FourRopesPage.xaml.cs

@@ -1,166 +0,0 @@
-using SWRIS.Events;
-using SWRIS.Extensions;
-using SWRIS.Models.ViewModel;
-using SWRIS.Repository;
-using System.Linq;
-using System.Threading.Tasks;
-using System.Windows.Controls;
-
-namespace SWRIS.Pages.RealTime
-{
-    /// <summary>
-    /// FourRopesPage.xaml 的交互逻辑
-    /// </summary>
-    public partial class FourRopesPage : Page
-    {
-        private readonly IRecordRepository _recordRepository;
-        public FourRopesViewModel FourRopesView { get; set; }
-        public FourRopesPage()
-        {
-            InitializeComponent();
-            Loaded += OneRopePage_Loaded;
-            Unloaded += OneRopePage_Unloaded;
-            _recordRepository = new RecordRepository();
-            FourRopesView = new FourRopesViewModel
-            {
-                FirstEquipmentData = App.DataCenter.Equipments[0],
-                SecondEquipmentData = App.DataCenter.Equipments[1],
-                ThirdEquipmentData = App.DataCenter.Equipments[2],
-                FourthEquipmentData = App.DataCenter.Equipments[3],
-                Records = _recordRepository.GetLastRecords().ToSimpleRecordDtos()
-            };
-            FourRopesView.FirstEquipment = App.Config.Equipments.First(c => c.IpAddress == FourRopesView.FirstEquipmentData.IpAddress);
-            FourRopesView.SecondEquipment = App.Config.Equipments.First(c => c.IpAddress == FourRopesView.SecondEquipmentData.IpAddress);
-            FourRopesView.ThirdEquipment = App.Config.Equipments.First(c => c.IpAddress == FourRopesView.ThirdEquipmentData.IpAddress);
-            FourRopesView.FourthEquipment = App.Config.Equipments.First(c => c.IpAddress == FourRopesView.FourthEquipmentData.IpAddress);
-            DataContext = FourRopesView;
-
-            App.TcpServer.LiveStreamReceived += TcpServer_LiveStreamReceived;
-        }
-        private void OneRopePage_Loaded(object sender, System.Windows.RoutedEventArgs e)
-        {
-            if (FourRopesView.FirstEquipmentData.ChartSource != null)
-            {
-                firstChartLine.AddDataPoints(FourRopesView.FirstEquipmentData.ChartSource);
-            }
-            if (FourRopesView.SecondEquipmentData.ChartSource != null)
-            {
-                secondChartLine.AddDataPoints(FourRopesView.SecondEquipmentData.ChartSource);
-            }
-            if (FourRopesView.ThirdEquipmentData.ChartSource != null)
-            {
-                thirdChartLine.AddDataPoints(FourRopesView.ThirdEquipmentData.ChartSource);
-            }
-            if (FourRopesView.FourthEquipmentData.ChartSource != null)
-            {
-                fourthChartLine.AddDataPoints(FourRopesView.FourthEquipmentData.ChartSource);
-            }
-        }
-
-        private void OneRopePage_Unloaded(object sender, System.Windows.RoutedEventArgs e)
-        {
-            FourRopesView.FirstEquipmentData.ChartSource = firstChartLine.GetChartSource();
-            FourRopesView.SecondEquipmentData.ChartSource = secondChartLine.GetChartSource();
-            FourRopesView.ThirdEquipmentData.ChartSource = thirdChartLine.GetChartSource();
-            FourRopesView.FourthEquipmentData.ChartSource = fourthChartLine.GetChartSource();
-        }
-        private async void TcpServer_LiveStreamReceived(object sender, LiveStreamReceivedEventArgs e)
-        {
-            if (e.IpAddress == FourRopesView.FirstEquipment.IpAddress)
-            {
-                if (FourRopesView.FirstEquipmentData.DetectionData?.DetectionNo == FourRopesView.FirstEquipmentData.DetectionNo)
-                {
-                    FourRopesView.FirstEquipmentData.ClearData();
-                }
-                await Task.Run(() =>
-                {
-                    firstChartLine.AddDataPoints((FourRopesView.FirstEquipmentData.AbsolutePosition, e.LiveStream), FourRopesView.FirstEquipmentData.InUseSensor);
-                });
-            }
-            else if (e.IpAddress == FourRopesView.SecondEquipmentData.IpAddress)
-            {
-                if (FourRopesView.SecondEquipmentData.DetectionData?.DetectionNo == FourRopesView.SecondEquipmentData.DetectionNo)
-                {
-                    FourRopesView.SecondEquipmentData.ClearData();
-                }
-                await Task.Run(() =>
-                {
-                    secondChartLine.AddDataPoints((FourRopesView.SecondEquipmentData.AbsolutePosition, e.LiveStream), FourRopesView.SecondEquipmentData.InUseSensor);
-                });
-            }
-            else if (e.IpAddress == FourRopesView.ThirdEquipmentData.IpAddress)
-            {
-                if (FourRopesView.ThirdEquipmentData.DetectionData?.DetectionNo == FourRopesView.ThirdEquipmentData.DetectionNo)
-                {
-                    FourRopesView.ThirdEquipmentData.ClearData();
-                }
-                await Task.Run(() =>
-                {
-                    thirdChartLine.AddDataPoints((FourRopesView.ThirdEquipmentData.AbsolutePosition, e.LiveStream), FourRopesView.ThirdEquipmentData.InUseSensor);
-                });
-            }
-            else if (e.IpAddress == FourRopesView.FourthEquipmentData.IpAddress)
-            {
-                if (FourRopesView.FourthEquipmentData.DetectionData?.DetectionNo == FourRopesView.FourthEquipmentData.DetectionNo)
-                {
-                    FourRopesView.FourthEquipmentData.ClearData();
-                }
-                await Task.Run(() =>
-                {
-                    fourthChartLine.AddDataPoints((FourRopesView.FourthEquipmentData.AbsolutePosition, e.LiveStream), FourRopesView.FourthEquipmentData.InUseSensor);
-                });
-            }
-        }
-
-        private void FirstEquipmentAlarm_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
-        {
-            DamagesDialog damagesDialog = new DamagesDialog(FourRopesView.FirstEquipmentData.Alarms);
-            damagesDialog.ShowDialog(true);
-        }
-
-        private void SecondEquipmentAlarm_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
-        {
-            DamagesDialog damagesDialog = new DamagesDialog(FourRopesView.SecondEquipmentData.Alarms);
-            damagesDialog.ShowDialog(true);
-        }
-        private void ThirdEquipmentAlarm_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
-        {
-            DamagesDialog damagesDialog = new DamagesDialog(FourRopesView.ThirdEquipmentData.Alarms);
-            damagesDialog.ShowDialog(true);
-        }
-        private void FourthEquipmentAlarm_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
-        {
-            DamagesDialog damagesDialog = new DamagesDialog(FourRopesView.FourthEquipmentData.Alarms);
-            damagesDialog.ShowDialog(true);
-        }
-        private void FirstParameterSetting_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
-        {
-            ParameterDialog parameterDialog = new ParameterDialog(FourRopesView.FirstEquipment,
-                                                                  FourRopesView.FirstEquipmentData.ClientSocket,
-                                                                  FourRopesView.FirstEquipmentData.RunningStatus);
-            parameterDialog.ShowDialog(true);
-        }
-        private void SecondParameterSetting_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
-        {
-            ParameterDialog parameterDialog = new ParameterDialog(FourRopesView.SecondEquipment,
-                                                                  FourRopesView.SecondEquipmentData.ClientSocket,
-                                                                  FourRopesView.SecondEquipmentData.RunningStatus);
-            parameterDialog.ShowDialog(true);
-        }
-        private void ThirdParameterSetting_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
-        {
-            ParameterDialog parameterDialog = new ParameterDialog(FourRopesView.ThirdEquipment,
-                                                                  FourRopesView.ThirdEquipmentData.ClientSocket,
-                                                                  FourRopesView.ThirdEquipmentData.RunningStatus);
-            parameterDialog.ShowDialog(true);
-        }
-        private void FourthParameterSetting_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
-        {
-            ParameterDialog parameterDialog = new ParameterDialog(FourRopesView.FourthEquipment,
-                                                                  FourRopesView.FourthEquipmentData.ClientSocket,
-                                                                  FourRopesView.FourthEquipmentData.RunningStatus);
-            parameterDialog.ShowDialog(true);
-        }
-
-    }
-}

+ 174 - 0
Pages/RealTime/OneRopeControl.xaml

@@ -0,0 +1,174 @@
+<UserControl x:Class="SWRIS.Pages.RealTime.OneRopeControl"
+      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+      xmlns:local="clr-namespace:SWRIS.Pages.RealTime"
+      xmlns:cvt="clr-namespace:SWRIS.Converters" 
+      xmlns:ctr="clr-namespace:SWRIS.Controls" 
+      xmlns:vm="clr-namespace:SWRIS.Models.ViewModel" 
+      xmlns:enum="clr-namespace:SWRIS.Enums"
+      mc:Ignorable="d" FontSize="28"
+      Foreground="#FFFFFF" 
+      FontFamily="{StaticResource PuHuiTiRegular}">
+    <UserControl.Resources>
+        <cvt:RiskLevelToColorConverter x:Key="RiskLevelToColorConverter"/>
+        <cvt:RiskLevelToTextConverter x:Key="RiskLevelToTextConverter"/>
+        <cvt:ConnectivityFaultToColorConverter x:Key="ConnectivityFaultConverter"/>
+        <cvt:CollectionCountToVisibilityConverter x:Key="CollectionCountToVisibilityConverter"/>
+        <cvt:ConnectivityToVisibilityConverter x:Key="ConnectivityToVisibilityConverter"/>
+        <cvt:StringEmptyToVisibilityConverter x:Key="StringEmptyToVisibilityConverter"/>
+    </UserControl.Resources>
+    <ItemsControl ItemsSource="{Binding Equipments}" >
+        <ItemsControl.ItemsPanel>
+            <ItemsPanelTemplate>
+                <Grid Margin="25,25,25,5"/>
+            </ItemsPanelTemplate>
+        </ItemsControl.ItemsPanel>
+        <ItemsControl.ItemTemplate>
+            <DataTemplate>
+                <Border CornerRadius="10" Loaded="OnCardLoaded">
+                    <Border.Background>
+                        <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
+                            <GradientStop Color="#2a2956" Offset="0"/>
+                            <GradientStop Color="#161536" Offset="0.40"/>
+                            <GradientStop Color="#1f1e44" Offset="1"/>
+                        </LinearGradientBrush>
+                    </Border.Background>
+                    <Grid>
+                        <Grid.RowDefinitions>
+                            <RowDefinition Height="410"/>
+                            <RowDefinition Height="*"/>
+                            <RowDefinition Height="Auto"/>
+                        </Grid.RowDefinitions>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="*"/>
+                            <ColumnDefinition Width="220"/>
+                        </Grid.ColumnDefinitions>
+                        <StackPanel Orientation="Vertical" Grid.Row="0" Grid.Column="0" Margin="55,50,0,0">
+                            <DockPanel>
+                                <!--钢丝绳状态及名称-->
+                                <Rectangle Height="25" Width="20" RadiusX="6" RadiusY="6">
+                                    <Rectangle.Fill>
+                                        <MultiBinding Converter="{StaticResource ConnectivityFaultConverter}">
+                                            <Binding Path="IsConnect"/>
+                                            <Binding Path="Faults"/>
+                                            <Binding Path="Faults.Count" />
+                                        </MultiBinding>
+                                    </Rectangle.Fill>
+                                </Rectangle>
+                                <TextBlock Text="{Binding RopeName}" FontSize="30" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}"
+                                           Margin="13,0" VerticalAlignment="Center"/>
+                                <StackPanel Orientation="Horizontal" Margin="30,0"
+                                            MouseLeftButtonDown="ParameterSetting_MouseDown"   Cursor="Hand">
+                                    <Image Source="/Resources/eqSet.png" Margin="10,0" Width="26" Height="26"/>
+                                    <TextBlock Text="参数设置" FontSize="24" VerticalAlignment="Center" Foreground="#8D86E5" FontWeight="Regular"/>
+                                </StackPanel>
+                            </DockPanel>
+                            <UniformGrid Columns="4" Width="900" Margin="0,80,0,0" HorizontalAlignment="Left">
+                                <StackPanel Orientation="Vertical">
+                                    <TextBlock Text="风险等级" Foreground="#7886B2" FontWeight="Regular"/>
+                                    <TextBlock FontWeight="Bold"
+                                               Text="{Binding RiskLevel,Converter={StaticResource RiskLevelToTextConverter}}" 
+                                               Foreground="{Binding RiskLevel,Converter={StaticResource RiskLevelToColorConverter}}"
+                                               FontFamily="{StaticResource PuHuiTiRegular}" FontSize="36" Margin="0,10"/>
+                                </StackPanel>
+                                <StackPanel Orientation="Vertical">
+                                    <TextBlock Text="起升高度" Foreground="#7886B2" FontWeight="Regular"/>
+                                    <TextBlock FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiRegular}" FontSize="36" Margin="0,10">
+                                        <Run Text="{Binding LiftHeight,StringFormat=#0.0}"/>
+                                        <Run Text="m"/>
+                                    </TextBlock>
+                                </StackPanel>
+                                <StackPanel Orientation="Vertical">
+                                    <TextBlock Text="损伤数量" Foreground="#7886B2" FontWeight="Regular"/>
+                                    <StackPanel Orientation="Horizontal" Margin="0,10">
+                                        <TextBlock Text="{Binding DamageCount}" FontWeight="Bold" 
+                                                   FontFamily="{StaticResource PuHuiTiRegular}" FontSize="36" MaxWidth="80"/>
+                                        <TextBlock Text="详情" Foreground="#7F74FF" FontSize="24" FontWeight="Regular" 
+                                                   Margin="15,10" Cursor="Hand"
+                                                   MouseLeftButtonDown="EquipmentAlarm_MouseDown" 
+                                                   Visibility="{Binding DamageCount,Converter={StaticResource CollectionCountToVisibilityConverter}}"/>
+                                    </StackPanel>
+                                </StackPanel>
+                                <StackPanel Orientation="Vertical">
+                                    <TextBlock Text="最大损伤值" Foreground="#7886B2" FontWeight="Regular"/>
+                                    <TextBlock FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiRegular}" FontSize="36" Margin="0,10">
+                                        <Run Text="{Binding MaxDamageValue,StringFormat=#0.0}"/>
+                                        <Run Text="%"/>
+                                    </TextBlock>
+                                </StackPanel>
+                            </UniformGrid>
+                            <ctr:DamageRangeControl Margin="0,40,15,0" HorizontalAlignment="Stretch" Height="50" FontSize="20"
+                                         MildCount="{Binding MildCount}" LightCount="{Binding LightCount}"
+                                         ModerateCount="{Binding ModerateCount}" SevereCount="{Binding SevereCount}"
+                                         CriticalCount="{Binding CriticalCount}" ExtremeCount="{Binding ExtremeCount}"/>
+
+                        </StackPanel>
+                        <ctr:RopeEquipmentControl Grid.Row="0" Grid.RowSpan="2" Grid.Column="1"
+                                                  Margin="0,0,60,-26"
+                                                  PanelMargin="-100,120,0,0"
+                                                  EquipmentTopPosition="300"
+                                                  PanelNameFontSize="24"
+                                                  PanelValueFontSize="26"
+                                                  RopeImageSource="/Resources/rope_block.png"
+                                                  EquipmentImageSource="/Resources/equipment.png"
+                                                  AnimationDuration="0.5">
+                            <ctr:RopeEquipmentControl.Style>
+                                <Style TargetType="ctr:RopeEquipmentControl">
+                                    <Style.Triggers>
+                                        <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Stoped}">
+                                            <Setter Property="IsAnimationRunning" Value="False"/>
+                                        </DataTrigger>
+                                        <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Forward}">
+                                            <Setter Property="IsAnimationRunning" Value="True"/>
+                                            <Setter Property="IsAnimationReversed" Value="False"/>
+                                        </DataTrigger>
+                                        <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Reverse}">
+                                            <Setter Property="IsAnimationRunning" Value="True"/>
+                                            <Setter Property="IsAnimationReversed" Value="True"/>
+                                        </DataTrigger>
+                                    </Style.Triggers>
+                                </Style>
+                            </ctr:RopeEquipmentControl.Style>
+                        </ctr:RopeEquipmentControl>
+                        <ctr:RealTimeLineChart x:Name="chartLine" Grid.Row="1" Margin="20,0,0,-5"
+                                               SensorCount="{Binding SensorCount,Mode=TwoWay}"
+                                               SamplingStep="{Binding SamplingStep,Mode=TwoWay}"
+                                               RopeLength="{Binding RopeLength,Mode=TwoWay}"/>
+
+                        <DockPanel Grid.Row="2" VerticalAlignment="Center" Margin="40,0,10,5">
+                            <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Left"
+                                        Visibility="{Binding DebugMessage.Message,Converter={StaticResource StringEmptyToVisibilityConverter}}">
+                                <TextBlock Style="{StaticResource HeartbeatTextBlockStyle}" FontSize="17" VerticalAlignment="Center" Foreground="#7F74FF"/>
+                                <TextBlock Text="{Binding  DebugMessage.DateTime,StringFormat=HH:mm:ss}" FontSize="16" Margin="5,0" VerticalAlignment="Center" Foreground="#7F74FF"/>
+                                <TextBlock Text="{Binding DebugMessage.Message}" FontSize="16" Margin="5,0" VerticalAlignment="Center" Foreground="#7F74FF"/>
+                            </StackPanel>
+                            <ItemsControl ItemsSource="{Binding LiveStreamValues}" Margin="10,4,-95,0" HorizontalAlignment="Right">
+                                <ItemsControl.ItemsPanel>
+                                    <ItemsPanelTemplate><WrapPanel/></ItemsPanelTemplate>
+                                </ItemsControl.ItemsPanel>
+                                <ItemsControl.ItemTemplate>
+                                    <DataTemplate>
+                                        <TextBlock Text="{Binding}" FontSize="12" Margin="4,0,0,0"
+                                                   Width="20" Foreground="White" Opacity="0.2" FontWeight="Medium">
+                                            <TextBlock.Style>
+                                                <Style TargetType="TextBlock">
+                                                    <Style.Triggers>
+                                                        <DataTrigger Binding="{Binding}" Value="0">
+                                                            <Setter Property="Visibility" Value="Collapsed"/>
+                                                        </DataTrigger>
+                                                    </Style.Triggers>
+                                                </Style>
+                                            </TextBlock.Style>
+                                        </TextBlock>
+                                    </DataTemplate>
+                                </ItemsControl.ItemTemplate>
+                            </ItemsControl>
+                        </DockPanel>
+                    </Grid>
+                </Border>
+            </DataTemplate>
+        </ItemsControl.ItemTemplate>
+    </ItemsControl>
+</UserControl>

+ 70 - 0
Pages/RealTime/OneRopeControl.xaml.cs

@@ -0,0 +1,70 @@
+using SWRIS.Controls;
+using SWRIS.Extensions;
+using SWRIS.Models;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace SWRIS.Pages.RealTime
+{
+    /// <summary>
+    /// OneRopeControl.xaml 的交互逻辑
+    /// </summary>
+    public partial class OneRopeControl : UserControl
+    {
+        public static readonly DependencyProperty EquipmentsProperty =
+          DependencyProperty.Register("Equipments", typeof(ObservableCollection<EquipmentDataModel>),
+              typeof(OneRopeControl),
+              new PropertyMetadata(null));
+
+        public ObservableCollection<EquipmentDataModel> Equipments
+        {
+            get { return (ObservableCollection<EquipmentDataModel>)GetValue(EquipmentsProperty); }
+            set { SetValue(EquipmentsProperty, value); }
+        }
+
+        public OneRopeControl()
+        {
+            InitializeComponent();
+            DataContext = this;
+        }
+
+        private void EquipmentAlarm_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            if ((sender as TextBlock)?.DataContext is EquipmentDataModel equipmentData)
+            {
+                DamagesDialog damagesDialog = new DamagesDialog(equipmentData.Alarms);
+                damagesDialog.ShowDialog(true);
+            }
+        }
+        private void ParameterSetting_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            if ((sender as StackPanel)?.DataContext is EquipmentDataModel equipmentData)
+            {
+                var equipment = App.Config.Equipments.FirstOrDefault(c => c.RopeNumber == equipmentData.RopeNumber);
+                if (equipmentData != null && equipment != null)
+                {
+                    ParameterDialog parameterDialog = new ParameterDialog(equipment, equipmentData.ClientSocket, equipmentData.RunningStatus);
+                    parameterDialog.ShowDialog(true);
+                }
+            }
+        }
+        private void OnCardLoaded(object sender, RoutedEventArgs e)
+        {
+            var border = sender as Border;
+            if (!(border?.DataContext is EquipmentDataModel data)) return;
+            if (border.FindName("ChartLine") is RealTimeLineChart chart)
+            {
+                var chartSource = data.Chart?.GetChartSource();
+                if (chart != null && chartSource != null)
+                {
+                    chart.AddDataPoints(chartSource);
+                }
+                data.Chart = chart;
+            }
+        }
+
+
+    }
+}

+ 0 - 146
Pages/RealTime/OneRopePage.xaml

@@ -1,146 +0,0 @@
-<Page x:Class="SWRIS.Pages.RealTime.OneRopePage"
-      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
-      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
-      xmlns:local="clr-namespace:SWRIS.Pages.RealTime"
-      xmlns:cvt="clr-namespace:SWRIS.Converters" 
-      xmlns:ctr="clr-namespace:SWRIS.Controls" 
-      xmlns:vm="clr-namespace:SWRIS.Models.ViewModel" 
-      xmlns:enum="clr-namespace:SWRIS.Enums"
-      mc:Ignorable="d" FontSize="28"
-      Foreground="#FFFFFF" 
-      FontFamily="{StaticResource PuHuiTiRegular}"
-      d:DesignHeight="900" d:DesignWidth="1920" 
-      Title="OneRopePage">
-    <Page.Resources>
-        <cvt:RiskLevelToColorConverter x:Key="RiskLevelToColorConverter"/>
-        <cvt:RiskLevelToTextConverter x:Key="RiskLevelToTextConverter"/>
-        <cvt:ConnectivityFaultToColorConverter x:Key="ConnectivityFaultConverter"/>
-        <cvt:CollectionCountToVisibilityConverter x:Key="CollectionCountToVisibilityConverter"/>
-        <cvt:ConnectivityToVisibilityConverter x:Key="ConnectivityToVisibilityConverter"/>
-        <cvt:StringEmptyToVisibilityConverter x:Key="StringEmptyToVisibilityConverter"/>
-    </Page.Resources>
-    <Page.DataContext>
-        <vm:OneRopeViewModel/>
-    </Page.DataContext>
-    <Grid Margin="25,30,25,7">
-        <Grid.ColumnDefinitions>
-            <ColumnDefinition Width="*"/>
-            <ColumnDefinition Width="370"/>
-        </Grid.ColumnDefinitions>
-        <Border CornerRadius="10" Grid.Column="0">
-            <Border.Background>
-                <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
-                    <GradientStop Color="#2a2956" Offset="0"/>
-                    <GradientStop Color="#161536" Offset="0.40"/>
-                    <GradientStop Color="#1f1e44" Offset="1"/>
-                </LinearGradientBrush>
-            </Border.Background>
-            <Grid  DataContext="{Binding EquipmentData}">
-                <Grid.RowDefinitions>
-                    <RowDefinition Height="410"/>
-                    <RowDefinition Height="*"/>
-                    <RowDefinition Height="Auto"/>
-                </Grid.RowDefinitions>
-                <Grid.ColumnDefinitions>
-                    <ColumnDefinition Width="*"/>
-                    <ColumnDefinition Width="200"/>
-                </Grid.ColumnDefinitions>
-                <StackPanel Orientation="Vertical" Grid.Row="0" Grid.Column="0" Margin="55,50,80,0">
-                    <DockPanel>
-                        <!--钢丝绳状态及名称-->
-                        <Rectangle Height="25" Width="20" RadiusX="6" RadiusY="6">
-                            <Rectangle.Fill>
-                                <MultiBinding Converter="{StaticResource ConnectivityFaultConverter}">
-                                    <Binding Path="IsConnect"/>
-                                    <Binding Path="Faults"/>
-                                </MultiBinding>
-                            </Rectangle.Fill>
-                        </Rectangle>
-                        <TextBlock Text="{Binding RopeName}" FontSize="30" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="13,0" VerticalAlignment="Center"/>
-                        <StackPanel Orientation="Horizontal" Margin="30,0" MouseLeftButtonDown="ParameterSetting_MouseDown" Cursor="Hand">
-                            <Image Source="/Resources/eqSet.png" Margin="10,0" Width="26" Height="26"/>
-                            <TextBlock Text="参数设置" FontSize="24" VerticalAlignment="Center" Foreground="#8D86E5" FontWeight="Regular"/>
-                        </StackPanel>
-                        <!--消息通知-->
-                        <ctr:NotificationBar FontSize="24" Height="50" MaxWidth="800" HorizontalAlignment="Right" Messages="{Binding Messages}"/>
-                    </DockPanel>
-                    <UniformGrid Columns="4" Width="900" Margin="0,80,0,0" HorizontalAlignment="Left">
-                        <StackPanel Orientation="Vertical">
-                            <TextBlock Text="风险等级" Foreground="#7886B2" FontWeight="Regular"/>
-                            <TextBlock FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiRegular}" FontSize="36" Margin="0,10"/>
-                        </StackPanel>
-                        <StackPanel Orientation="Vertical">
-                            <TextBlock Text="起升高度" Foreground="#7886B2" FontWeight="Regular"/>
-                            <TextBlock FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiRegular}" FontSize="36" Margin="0,10">
-                                <Run Text="{Binding LiftHeight,StringFormat=#0.0}"/>
-                                <Run Text="m"/>
-                            </TextBlock>
-                        </StackPanel>
-                        <StackPanel Orientation="Vertical">
-                            <TextBlock Text="损伤数量" Foreground="#7886B2" FontWeight="Regular"/>
-                            <StackPanel Orientation="Horizontal" Margin="0,10">
-                                <TextBlock Text="{Binding DamageCount}" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiRegular}" FontSize="36" MaxWidth="80"/>
-                                <TextBlock Text="详情" Foreground="#7F74FF" FontSize="24" FontWeight="Regular" Margin="15,10" 
-                                           Cursor="Hand" MouseLeftButtonDown="AlarmDetail_MouseDown"
-                                           Visibility="{Binding DamageCount,Converter={StaticResource CollectionCountToVisibilityConverter}}"/>
-                            </StackPanel>
-                        </StackPanel>
-                        <StackPanel Orientation="Vertical">
-                            <TextBlock Text="最大损伤值" Foreground="#7886B2" FontWeight="Regular"/>
-                            <TextBlock FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiRegular}" FontSize="36" Margin="0,10">
-                                <Run Text="{Binding MaxDamageValue,StringFormat=#0.0}"/>
-                                <Run Text="%"/>
-                            </TextBlock>
-                        </StackPanel>
-                    </UniformGrid>
-                    <ctr:DamageRangeControl Margin="0,40,-105,0" HorizontalAlignment="Stretch" Height="50" FontSize="20"
-                                         MildCount="{Binding MildCount}" LightCount="{Binding LightCount}"
-                                         ModerateCount="{Binding ModerateCount}" SevereCount="{Binding SevereCount}"
-                                         CriticalCount="{Binding CriticalCount}" ExtremeCount="{Binding ExtremeCount}"/>
-
-                </StackPanel>
-                <ctr:RopeEquipmentControl Grid.RowSpan="2" Grid.Column="1" Grid.Row="0"
-                                          PanelMargin="-80,50,0,0"
-                                          EquipmentTopPosition="300"
-                                          PanelNameFontSize="24"
-                                          PanelValueFontSize="26"
-                                          RopeImageSource="/Resources/rope_block.png"
-                                          EquipmentImageSource="/Resources/equipment.png"
-                                          AnimationDuration="0.5" RopeHeight="903">
-                    <ctr:RopeEquipmentControl.Style>
-                        <Style TargetType="ctr:RopeEquipmentControl">
-                            <Style.Triggers>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Stoped}">
-                                    <Setter Property="IsAnimationRunning" Value="False"/>
-                                </DataTrigger>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Forward}">
-                                    <Setter Property="IsAnimationRunning" Value="True"/>
-                                    <Setter Property="IsAnimationReversed" Value="False"/>
-                                </DataTrigger>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Reverse}">
-                                    <Setter Property="IsAnimationRunning" Value="True"/>
-                                    <Setter Property="IsAnimationReversed" Value="True"/>
-                                </DataTrigger>
-                            </Style.Triggers>
-                        </Style>
-                    </ctr:RopeEquipmentControl.Style>
-                </ctr:RopeEquipmentControl>
-
-                <ctr:RealTimeLineChart x:Name="chartLine" Grid.Row="1"  Margin="20,0,-40,-5"
-                                       SensorCount="{Binding SensorCount,Mode=TwoWay}"
-                                       SamplingStep="{Binding SamplingStep,Mode=TwoWay}"
-                                       RopeLength="{Binding RopeLength,Mode=TwoWay}"/>
-
-                <StackPanel Grid.Row="2" Orientation="Horizontal" Margin="40,0,-100,5" 
-                            Visibility="{Binding DebugMessage.Message,Converter={StaticResource StringEmptyToVisibilityConverter}}">
-                    <TextBlock Style="{StaticResource HeartbeatTextBlockStyle}" FontSize="17" VerticalAlignment="Center" Foreground="#7F74FF"/>
-                    <TextBlock Text="{Binding  DebugMessage.DateTime,StringFormat=HH:mm:ss}" FontSize="16" Margin="5,0" VerticalAlignment="Center" Foreground="#7F74FF"/>
-                    <TextBlock Text="{Binding DebugMessage.Message}" FontSize="16" Margin="5,0" VerticalAlignment="Center" Foreground="#7F74FF"/>
-                </StackPanel>
-            </Grid>
-        </Border>
-        <ctr:DamageRecordControl Grid.Column="1" Margin="25,0,0,0" Records="{Binding Records}" Title="近期记录"/>
-    </Grid>
-</Page>

+ 0 - 77
Pages/RealTime/OneRopePage.xaml.cs

@@ -1,77 +0,0 @@
-using SWRIS.Events;
-using SWRIS.Extensions;
-using SWRIS.Models.ViewModel;
-using SWRIS.Repository;
-using System.Linq;
-using System.Threading.Tasks;
-using System.Windows.Controls;
-
-namespace SWRIS.Pages.RealTime
-{
-    /// <summary>
-    /// OneRopePage.xaml 的交互逻辑
-    /// </summary>
-    public partial class OneRopePage : Page
-    {
-        private readonly IRecordRepository _recordRepository;
-        public OneRopeViewModel OneRopeView { get; set; }
-
-        public OneRopePage()
-        {
-            InitializeComponent();
-            Loaded += OneRopePage_Loaded;
-            Unloaded += OneRopePage_Unloaded;
-            _recordRepository = new RecordRepository();
-
-            OneRopeView = new OneRopeViewModel
-            {
-                EquipmentData = App.DataCenter.Equipments[0],
-                Records = _recordRepository.GetLastRecords().ToSimpleRecordDtos()
-            };
-
-            OneRopeView.Equipment = App.Config.Equipments.First(c => c.IpAddress == OneRopeView.EquipmentData.IpAddress);
-            DataContext = OneRopeView;
-            App.TcpServer.LiveStreamReceived += TcpServer_LiveStreamReceived;
-        }
-
-        private void OneRopePage_Loaded(object sender, System.Windows.RoutedEventArgs e)
-        {
-            if (OneRopeView.EquipmentData.ChartSource != null)
-            {
-                chartLine.AddDataPoints(OneRopeView.EquipmentData.ChartSource);
-            }
-        }
-
-        private void OneRopePage_Unloaded(object sender, System.Windows.RoutedEventArgs e)
-        {
-            OneRopeView.EquipmentData.ChartSource = chartLine.GetChartSource();
-        }
-
-        private async void TcpServer_LiveStreamReceived(object sender, LiveStreamReceivedEventArgs e)
-        {
-            if (e.IpAddress == OneRopeView.EquipmentData.IpAddress)
-            {
-                if (OneRopeView.EquipmentData.DetectionData?.DetectionNo == OneRopeView.EquipmentData.DetectionNo)
-                {
-                    OneRopeView.EquipmentData.ClearData();
-                }
-                await Task.Run(() =>
-                {
-                    chartLine.AddDataPoints((OneRopeView.EquipmentData.AbsolutePosition, e.LiveStream),OneRopeView.EquipmentData.InUseSensor);
-                });
-            }
-        }
-
-        private void AlarmDetail_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
-        {
-            DamagesDialog damagesDialog = new DamagesDialog(OneRopeView.EquipmentData.Alarms);
-            damagesDialog.ShowDialog(true);
-        }
-
-        private void ParameterSetting_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
-        {
-            ParameterDialog parameterDialog = new ParameterDialog(OneRopeView.Equipment, OneRopeView.EquipmentData.ClientSocket, OneRopeView.EquipmentData.RunningStatus);
-            parameterDialog.ShowDialog(true);
-        }
-    }
-}

+ 101 - 0
Pages/RealTime/RealTimePage.xaml

@@ -0,0 +1,101 @@
+<Page x:Class="SWRIS.Pages.RealTime.RealTimePage"
+      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+      xmlns:local="clr-namespace:SWRIS.Pages.RealTime" 
+      xmlns:cvt="clr-namespace:SWRIS.Converters" 
+      xmlns:rel="clr-namespace:SWRIS.Pages.RealTime"
+      xmlns:stor="clr-namespace:SWRIS.Selectors" 
+      xmlns:ctr="clr-namespace:SWRIS.Controls" 
+      xmlns:viewmodel="clr-namespace:SWRIS.Models.ViewModel"
+      Foreground="#FFFFFF" 
+      d:DataContext="{d:DesignInstance Type=viewmodel:RealTimeViewModel}"
+      mc:Ignorable="d" 
+      Title="RealTimePage">
+    <Page.Resources>
+        <cvt:MenuNameToPositionConverter x:Key="MenuNameToPositionConverter"/>
+        <cvt:MenuNameToColorConverter x:Key="MenuNameToColorConverter"/>
+        <cvt:StringEmptyToVisibilityConverter x:Key="StringEmptyToVisibilityConverter"/>
+        <cvt:TrolleysToVisibilityConverter x:Key="TrolleysToVisibilityConverter"/>
+        <cvt:CollectionToVisibilityConverter x:Key="CollectionToVisibilityConverter"/>
+    </Page.Resources>
+    <Grid>
+        <Grid.Resources>
+            <stor:EquipmentsLayoutTemplateSelector x:Key="EquipmentsLayoutTemplateSelector">
+                <stor:EquipmentsLayoutTemplateSelector.OneTemplate>
+                    <DataTemplate>
+                        <rel:OneRopeControl Equipments="{Binding DataContext.Equipments, RelativeSource={RelativeSource AncestorType=Page}}"/>
+                    </DataTemplate>
+                </stor:EquipmentsLayoutTemplateSelector.OneTemplate>
+                <stor:EquipmentsLayoutTemplateSelector.TwoTemplate>
+                    <DataTemplate>
+                        <rel:TwoRopesControl Equipments="{Binding DataContext.Equipments, RelativeSource={RelativeSource AncestorType=Page}}"/>
+                    </DataTemplate>
+                </stor:EquipmentsLayoutTemplateSelector.TwoTemplate>
+                <stor:EquipmentsLayoutTemplateSelector.ThreeTemplate>
+                    <DataTemplate>
+                        <rel:ThreeRopesControl Equipments="{Binding DataContext.Equipments, RelativeSource={RelativeSource AncestorType=Page}}"/>
+                    </DataTemplate>
+                </stor:EquipmentsLayoutTemplateSelector.ThreeTemplate>
+                <stor:EquipmentsLayoutTemplateSelector.FourTemplate>
+                    <DataTemplate>
+                        <rel:FourRopesControl Equipments="{Binding DataContext.Equipments, RelativeSource={RelativeSource AncestorType=Page}}"/>
+                    </DataTemplate>
+                </stor:EquipmentsLayoutTemplateSelector.FourTemplate>
+            </stor:EquipmentsLayoutTemplateSelector>
+        </Grid.Resources>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="*"/>
+        </Grid.RowDefinitions>
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="*"/>
+            <ColumnDefinition Width="360"/>
+        </Grid.ColumnDefinitions>
+        <Border Grid.Row="0" Grid.Column="0" CornerRadius="10" Margin="25,20,25,0" Background="#282752" Padding="20,0"
+                Visibility="{Binding Trolleys,Converter={StaticResource TrolleysToVisibilityConverter}}">
+            <DockPanel>
+                <ItemsControl ItemsSource="{Binding Trolleys}">
+                    <ItemsControl.ItemsPanel>
+                        <ItemsPanelTemplate>
+                            <StackPanel Orientation="Horizontal"/>
+                        </ItemsPanelTemplate>
+                    </ItemsControl.ItemsPanel>
+                    <ItemsControl.ItemTemplate>
+                        <DataTemplate>
+                            <Border Width="180" Height="70" Cursor="Hand" Margin="3,0" MouseLeftButtonDown="Border_MouseLeftButtonDown">
+                                <Border.Style>
+                                    <Style TargetType="Border">
+                                        <Setter Property="Background">
+                                            <Setter.Value>
+                                                <ImageBrush ImageSource="/Resources/trolleyActived.png"/>
+                                            </Setter.Value>
+                                        </Setter>
+                                        <Style.Triggers>
+                                            <DataTrigger Binding="{Binding IsActived}" Value="False">
+                                                <Setter Property="Background" Value="Transparent"/>
+                                            </DataTrigger>
+                                        </Style.Triggers>
+                                    </Style>
+                                </Border.Style>
+                                <TextBlock FontSize="24" Text="{Binding Name}" TextAlignment="Center" FontWeight="Bold" VerticalAlignment="Center"/>
+                            </Border>
+                        </DataTemplate>
+                    </ItemsControl.ItemTemplate>
+                </ItemsControl>
+                <ctr:NotificationBar Height="35" MaxWidth="700" FontSize="16" HorizontalAlignment="Right" CornerRadius="15"
+                                     Messages="{Binding Messages}" Margin="0,0,0,0"/>
+            </DockPanel>
+        </Border>
+        <ContentControl Grid.Row="1" Grid.Column="0" Content="{Binding Equipments}"  
+                        Visibility="{Binding Equipments,Converter={StaticResource CollectionToVisibilityConverter}}"
+                        ContentTemplateSelector="{StaticResource EquipmentsLayoutTemplateSelector}"/>
+
+        <ctr:DamageRecordControl Grid.Row="0" Grid.RowSpan="2"
+                                 Grid.Column="1"
+                                 Margin="6,20,0,5"
+                                 Records="{Binding Records}"
+                                 Title="近期记录"/>
+    </Grid>
+</Page>

+ 168 - 0
Pages/RealTime/RealTimePage.xaml.cs

@@ -0,0 +1,168 @@
+using SWRIS.Extensions;
+using SWRIS.Models;
+using SWRIS.Models.ViewModel;
+using SWRIS.Repository;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Threading;
+
+namespace SWRIS.Pages.RealTime
+{
+    /// <summary>
+    /// RealTimePage.xaml 的交互逻辑
+    /// </summary>
+    public partial class RealTimePage : Page
+    {
+        /// <summary>实时流超时隐藏阈值:超过该时长未收到新数据则隐藏实时值</summary>
+        private const long LiveStreamTimeoutTicks = TimeSpan.TicksPerSecond;
+
+        private readonly IRecordRepository _recordRepository;
+        /// <summary>全部设备(小车过滤的原始全集)</summary>
+        private readonly ObservableCollection<EquipmentDataModel> _allEquipments;
+        private readonly DispatcherTimer _liveStreamTimer;
+        /// <summary>记录各设备最后一次收到实时数据的时间(UtcNow.Ticks),超过 1 秒未更新时隐藏</summary>
+        private readonly Dictionary<EquipmentDataModel, long> _lastLiveStreamTimes = new Dictionary<EquipmentDataModel, long>();
+
+        public RealTimeViewModel ViewModel { get; set; }
+
+        public RealTimePage()
+        {
+            InitializeComponent();
+            _recordRepository = new RecordRepository();
+            ViewModel = new RealTimeViewModel(_recordRepository.GetLastRecords().ToSimpleRecordDtos());
+            _allEquipments = ViewModel.Equipments;
+            FilterEquipmentsByTrolley(ViewModel.Trolleys.FirstOrDefault());
+
+            DataContext = ViewModel;
+
+            // 定时从各设备的实时流队列取出一项(取出即移出)填充 LiveStreamValues,供设备卡片显示
+            _liveStreamTimer = new DispatcherTimer
+            {
+                Interval = TimeSpan.FromMilliseconds(50)
+            };
+            _liveStreamTimer.Tick += LiveStreamTimer_Tick;
+            _liveStreamTimer.Start();
+            Unloaded += RealTimePage_Unloaded;
+        }
+
+        private void RealTimePage_Unloaded(object sender, RoutedEventArgs e)
+        {
+            _liveStreamTimer.Stop();
+        }
+
+        /// <summary>
+        /// 从 LiveStreams 队列取出一项(取出即移出)并显示各传感器值;设备离线时不显示,
+        /// 超过 1 秒未收到新数据则隐藏实时值,值未变化时跳过更新
+        /// </summary>
+        private void LiveStreamTimer_Tick(object sender, EventArgs e)
+        {
+            var equipments = ViewModel?.Equipments;
+            if (equipments == null)
+                return;
+
+            // 用 UtcNow.Ticks 作为单调时钟,避免 DateTime.Now 的时区/日历换算开销
+            long now = DateTime.UtcNow.Ticks;
+            foreach (var equipmentData in equipments)
+            {
+                UpdateEquipmentLiveStream(equipmentData, now);
+            }
+        }
+
+        private void UpdateEquipmentLiveStream(EquipmentDataModel equipmentData, long now)
+        {
+            var values = equipmentData.LiveStreamValues;
+
+            // 设备离线时不显示
+            if (!equipmentData.IsConnect)
+            {
+                ClearValues(values);
+                return;
+            }
+
+            // 超过 1 秒未收到新数据 → 隐藏实时值(首次未见数据时 last 为 0,直接隐藏)
+            if (!_lastLiveStreamTimes.TryGetValue(equipmentData, out long last) || now - last > LiveStreamTimeoutTicks)
+                ClearValues(values);
+
+            // 无新数据:保持当前显示状态
+            if (!equipmentData.TryDequeueLiveStream(out int[] sample))
+                return;
+
+            // 有新数据,记录更新时间
+            _lastLiveStreamTimes[equipmentData] = now;
+
+            // 值未变化则跳过,减少无谓的集合通知与 UI 重绘
+            if (ValuesEqual(values, sample))
+                return;
+
+            // 用索引器赋值(触发 Replace 通知,UI 容器复用)代替 Clear+Add 全量重建,减轻闪烁
+            ApplySample(values, sample);
+        }
+
+        /// <summary>比较当前显示值与新样本是否一致(长度与逐项内容均相同)</summary>
+        private static bool ValuesEqual(ObservableCollection<int> values, int[] sample)
+        {
+            if (values.Count != sample.Length)
+                return false;
+            for (int i = 0; i < sample.Length; i++)
+            {
+                if (values[i] != sample[i])
+                    return false;
+            }
+            return true;
+        }
+
+        /// <summary>用新样本更新集合:已有项索引器赋值(容器复用),数量差异用最小化 Add/RemoveAt 补齐</summary>
+        private static void ApplySample(ObservableCollection<int> values, int[] sample)
+        {
+            int common = Math.Min(values.Count, sample.Length);
+            for (int i = 0; i < common; i++)
+            {
+                values[i] = sample[i];
+            }
+            while (values.Count > sample.Length)
+            {
+                values.RemoveAt(values.Count - 1);
+            }
+            while (values.Count < sample.Length)
+            {
+                values.Add(sample[values.Count]);
+            }
+        }
+
+        /// <summary>仅在集合非空时清空,避免无谓的集合通知触发 UI 重绘</summary>
+        private static void ClearValues(ObservableCollection<int> values)
+        {
+            if (values.Count > 0)
+                values.Clear();
+        }
+
+        private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+        {
+            if ((sender as Border)?.DataContext is TrolleyActiveModel activedTrolley)
+            {
+                ViewModel.ActivedTrolly = activedTrolley;
+                FilterEquipmentsByTrolley(activedTrolley);
+            }
+        }
+
+        private void FilterEquipmentsByTrolley(TrolleyActiveModel trolley)
+        {
+            if (trolley == null)
+            {
+                ViewModel.Equipments = _allEquipments;
+                return;
+            }
+
+            var filtered = _allEquipments
+                .Where(e => e.TrolleyId == trolley.Id)
+                .ConvertToObservableCollection();
+
+            ViewModel.Equipments = filtered;
+        }
+    }
+}

+ 174 - 0
Pages/RealTime/ThreeRopesControl.xaml

@@ -0,0 +1,174 @@
+<UserControl x:Class="SWRIS.Pages.RealTime.ThreeRopesControl"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:SWRIS.Pages.RealTime"
+             mc:Ignorable="d" FontSize="20" Foreground="#FFFFFF" 
+             xmlns:cvt="clr-namespace:SWRIS.Converters" 
+             xmlns:ctr="clr-namespace:SWRIS.Controls" 
+             xmlns:enum="clr-namespace:SWRIS.Enums"
+             FontFamily="{StaticResource PuHuiTiRegular}"
+             d:DesignHeight="865" d:DesignWidth="1560">
+    <UserControl.Resources>
+        <cvt:RiskLevelToColorConverter x:Key="RiskLevelToColorConverter"/>
+        <cvt:RiskLevelToTextConverter x:Key="RiskLevelToTextConverter"/>
+        <cvt:ConnectivityFaultToColorConverter x:Key="ConnectivityFaultConverter"/>
+        <cvt:CollectionCountToVisibilityConverter x:Key="CollectionCountToVisibilityConverter"/>
+        <cvt:ConnectivityToVisibilityConverter x:Key="ConnectivityToVisibilityConverter"/>
+        <cvt:StringEmptyToVisibilityConverter x:Key="StringEmptyToVisibilityConverter"/>
+    </UserControl.Resources>
+    <ItemsControl ItemsSource="{Binding Equipments}" Margin="25,25,25,-20">
+        <ItemsControl.ItemsPanel>
+            <ItemsPanelTemplate>
+                <UniformGrid Columns="1" Rows="3"/>
+            </ItemsPanelTemplate>
+        </ItemsControl.ItemsPanel>
+        <ItemsControl.ItemTemplate>
+            <DataTemplate>
+                <Border Grid.Row="0" CornerRadius="10" Margin="0,0,0,25" Loaded="OnCardLoaded">
+                    <Border.Background>
+                        <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
+                            <GradientStop Color="#2a2956" Offset="0"/>
+                            <GradientStop Color="#161536" Offset="0.40"/>
+                            <GradientStop Color="#1f1e44" Offset="1"/>
+                        </LinearGradientBrush>
+                    </Border.Background>
+                    <Grid>
+                        <Grid.RowDefinitions>
+                            <RowDefinition Height="130"/>
+                            <RowDefinition Height="*"/>
+                            <RowDefinition Height="Auto"/>
+                        </Grid.RowDefinitions>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="*"/>
+                            <ColumnDefinition Width="180"/>
+                        </Grid.ColumnDefinitions>
+                        <StackPanel Orientation="Vertical" Grid.Row="0" Grid.Column="0" Margin="35,20,70,0">
+                            <DockPanel>
+                                <!--钢丝绳状态及名称-->
+                                <Rectangle Height="22" Width="18" RadiusX="6" RadiusY="6">
+                                    <Rectangle.Fill>
+                                        <MultiBinding Converter="{StaticResource ConnectivityFaultConverter}">
+                                            <Binding Path="IsConnect"/>
+                                            <Binding Path="Faults"/>
+                                            <Binding Path="Faults.Count"/>
+                                        </MultiBinding>
+                                    </Rectangle.Fill>
+                                </Rectangle>
+                                <TextBlock Text="{Binding RopeName}" FontSize="22" FontWeight="Bold" 
+                                           FontFamily="{StaticResource PuHuiTiBold}" Margin="13,0" VerticalAlignment="Center"/>
+                                <StackPanel Orientation="Horizontal" Margin="10,0" MouseLeftButtonDown="ParameterSetting_MouseDown" Cursor="Hand">
+                                    <Image Source="/Resources/eqSet.png" Margin="10,0" Width="20" Height="20"/>
+                                    <TextBlock Text="参数设置" FontSize="18" VerticalAlignment="Center" Foreground="#8D86E5" FontWeight="Regular"/>
+                                </StackPanel>
+                            </DockPanel>
+                            <DockPanel Margin="0,10">
+                                <UniformGrid Columns="2" Rows="2" Width="400" HorizontalAlignment="Left">
+                                    <StackPanel Orientation="Horizontal" Margin="0,2">
+                                        <TextBlock Text="风险等级"  Foreground="#7886B2"/>
+                                        <TextBlock  FontWeight="Bold" Margin="20,0"
+                                                    FontFamily="{StaticResource PuHuiTiBold}" 
+                                                    Text="{Binding RiskLevel,Converter={StaticResource RiskLevelToTextConverter}}" 
+                                                    Foreground="{Binding RiskLevel,Converter={StaticResource RiskLevelToColorConverter}}"/>
+                                    </StackPanel>
+                                    <StackPanel Orientation="Horizontal" Margin="0,2">
+                                        <TextBlock Text="起升高度"  Foreground="#7886B2"/>
+                                        <TextBlock Foreground="#FFFFFF" FontWeight="Bold"
+                                                   FontFamily="{StaticResource PuHuiTiBold}" Margin="40,0">
+                                            <Run Text="{Binding LiftHeight,StringFormat=#0.0}"/>
+                                            <Run Text="m"/>
+                                        </TextBlock>
+                                    </StackPanel>
+                                    <StackPanel Orientation="Horizontal" Margin="0,2">
+                                        <TextBlock Text="损伤数量"  Foreground="#7886B2"/>
+                                        <TextBlock Text="{Binding DamageCount}"   Foreground="#FFFFFF" FontWeight="Bold"  
+                                                   FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0" />
+                                        <TextBlock Text="详情" Foreground="#7F74FF" FontSize="18" FontWeight="Regular" Margin="0,2,0,0"
+                                                   Cursor="Hand"  MouseLeftButtonDown="EquipmentAlarm_MouseDown" 
+                                                   Visibility="{Binding DamageCount,Converter={StaticResource CollectionCountToVisibilityConverter}}"/>
+                                    </StackPanel>
+                                    <StackPanel Orientation="Horizontal" Margin="0,2">
+                                        <TextBlock Text="最大损伤值"  Foreground="#7886B2"/>
+                                        <TextBlock Foreground="#FFFFFF" FontWeight="Bold" FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0">
+                                            <Run Text="{Binding MaxDamageValue,StringFormat=#0.0}"/>
+                                            <Run Text="%"/>
+                                        </TextBlock>
+                                    </StackPanel>
+                                </UniformGrid>
+                                <ctr:DamageRangeControl HorizontalAlignment="Stretch" Height="45" FontSize="16" Margin="70,15,0,0" 
+                                                        MildCount="{Binding MildCount}" LightCount="{Binding LightCount}" 
+                                                        ModerateCount="{Binding ModerateCount}" SevereCount="{Binding SevereCount}"
+                                                        CriticalCount="{Binding CriticalCount}" ExtremeCount="{Binding ExtremeCount}"/>
+                            </DockPanel>
+                        </StackPanel>
+                        <ctr:RopeEquipmentControl Grid.RowSpan="2" Grid.Column="1" Grid.Row="0" 
+                                                  Margin="0,0,0,-15"
+                                                  PanelMargin="-80,25,15,0"
+                                                  PanelNameFontSize="18"
+                                                  PanelValueFontSize="20"
+                                                  EquipmentTopPosition="0"
+                                                  RopeImageSource="/Resources/rope_block.png" 
+                                                  EquipmentImageSource="/Resources/equipment.png"
+                                                  AnimationDuration="0.5">
+                            <ctr:RopeEquipmentControl.Style>
+                                <Style TargetType="ctr:RopeEquipmentControl">
+                                    <Style.Triggers>
+                                        <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Stoped}">
+                                            <Setter Property="IsAnimationRunning" Value="False"/>
+                                        </DataTrigger>
+                                        <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Forward}">
+                                            <Setter Property="IsAnimationRunning" Value="True"/>
+                                            <Setter Property="IsAnimationReversed" Value="False"/>
+                                        </DataTrigger>
+                                        <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Reverse}">
+                                            <Setter Property="IsAnimationRunning" Value="True"/>
+                                            <Setter Property="IsAnimationReversed" Value="True"/>
+                                        </DataTrigger>
+                                    </Style.Triggers>
+                                </Style>
+                            </ctr:RopeEquipmentControl.Style>
+                        </ctr:RopeEquipmentControl>
+                        <ctr:RealTimeLineChart x:Name="firstChartLine" Grid.Row="1" Margin="20,-15,55,-5"
+                                               SensorCount="{Binding SensorCount}"
+                                               SamplingStep="{Binding SamplingStep,Mode=TwoWay}"
+                                               RopeLength="{Binding RopeLength}" />
+                        
+                        <DockPanel Grid.Row="2" VerticalAlignment="Center" Margin="35,-7,10,2">
+                            <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Left"
+                                        Visibility="{Binding DebugMessage.Message,Converter={StaticResource StringEmptyToVisibilityConverter}}">
+                                <TextBlock Style="{StaticResource HeartbeatTextBlockStyle}"  FontSize="16"
+                                           VerticalAlignment="Center" Foreground="#7F74FF"/>
+                                <TextBlock Text="{Binding  DebugMessage.DateTime,StringFormat=HH:mm:ss}" FontSize="15" Margin="5,0" 
+                                           VerticalAlignment="Center" Foreground="#7F74FF"/>
+                                <TextBlock Text="{Binding DebugMessage.Message}" FontSize="15" Margin="5,0" 
+                                           VerticalAlignment="Center" Foreground="#7F74FF"/>
+                            </StackPanel>
+                            <ItemsControl ItemsSource="{Binding LiveStreamValues}" Margin="10,2,-85,0" HorizontalAlignment="Right">
+                                <ItemsControl.ItemsPanel>
+                                    <ItemsPanelTemplate><WrapPanel/></ItemsPanelTemplate>
+                                </ItemsControl.ItemsPanel>
+                                <ItemsControl.ItemTemplate>
+                                    <DataTemplate>
+                                        <TextBlock Text="{Binding}" FontSize="12" Margin="4,0,0,0"
+                                                   Width="20" Foreground="White" Opacity="0.2" FontWeight="Medium">
+                                            <TextBlock.Style>
+                                                <Style TargetType="TextBlock">
+                                                    <Style.Triggers>
+                                                        <DataTrigger Binding="{Binding}" Value="0">
+                                                            <Setter Property="Visibility" Value="Collapsed"/>
+                                                        </DataTrigger>
+                                                    </Style.Triggers>
+                                                </Style>
+                                            </TextBlock.Style>
+                                        </TextBlock>
+                                    </DataTemplate>
+                                </ItemsControl.ItemTemplate>
+                            </ItemsControl>
+                        </DockPanel>
+                    </Grid>
+                </Border>
+            </DataTemplate>
+        </ItemsControl.ItemTemplate>
+    </ItemsControl>
+</UserControl>

+ 69 - 0
Pages/RealTime/ThreeRopesControl.xaml.cs

@@ -0,0 +1,69 @@
+using SWRIS.Controls;
+using SWRIS.Extensions;
+using SWRIS.Models;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace SWRIS.Pages.RealTime
+{
+    /// <summary>
+    /// ThreeRopesPage.xaml 的交互逻辑
+    /// </summary>
+    public partial class ThreeRopesControl : UserControl
+    {
+        public static readonly DependencyProperty EquipmentsProperty =
+        DependencyProperty.Register("Equipments", typeof(ObservableCollection<EquipmentDataModel>),
+            typeof(ThreeRopesControl),
+            new PropertyMetadata(null));
+
+        public ObservableCollection<EquipmentDataModel> Equipments
+        {
+            get { return (ObservableCollection<EquipmentDataModel>)GetValue(EquipmentsProperty); }
+            set { SetValue(EquipmentsProperty, value); }
+        }
+
+        public ThreeRopesControl()
+        {
+            InitializeComponent();
+            DataContext = this;
+        }
+
+        private void EquipmentAlarm_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            if ((sender as TextBlock)?.DataContext is EquipmentDataModel equipmentData)
+            {
+                DamagesDialog damagesDialog = new DamagesDialog(equipmentData.Alarms);
+                damagesDialog.ShowDialog(true);
+            }
+        }
+
+        private void ParameterSetting_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            if ((sender as StackPanel)?.DataContext is EquipmentDataModel equipmentData)
+            {
+                var equipment = App.Config.Equipments.FirstOrDefault(c => c.RopeNumber == equipmentData.RopeNumber);
+                if (equipmentData != null && equipment != null)
+                {
+                    ParameterDialog parameterDialog = new ParameterDialog(equipment, equipmentData.ClientSocket, equipmentData.RunningStatus);
+                    parameterDialog.ShowDialog(true);
+                }
+            }
+        }
+        private void OnCardLoaded(object sender, RoutedEventArgs e)
+        {
+            var border = sender as Border;
+            if (!(border?.DataContext is EquipmentDataModel data)) return;
+            if (border.FindName("ChartLine") is RealTimeLineChart chart)
+            {
+                var chartSource = data.Chart?.GetChartSource();
+                if (chart != null && chartSource != null)
+                {
+                    chart.AddDataPoints(chartSource);
+                }
+                data.Chart = chart;
+            }
+        }
+    }
+}

+ 0 - 400
Pages/RealTime/ThreeRopesPage.xaml

@@ -1,400 +0,0 @@
-<Page x:Class="SWRIS.Pages.RealTime.ThreeRopesPage"
-      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
-      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
-      xmlns:local="clr-namespace:SWRIS.Pages.RealTime"
-      mc:Ignorable="d" FontSize="20" Foreground="#FFFFFF" 
-      xmlns:vm="clr-namespace:SWRIS.Models.ViewModel"
-      xmlns:cvt="clr-namespace:SWRIS.Converters" 
-      xmlns:ctr="clr-namespace:SWRIS.Controls" 
-      xmlns:enum="clr-namespace:SWRIS.Enums"
-      d:DesignHeight="900" 
-      FontFamily="{StaticResource PuHuiTiRegular}"
-      d:DesignWidth="1920" Title="ThreeRopesPage">
-    <Page.DataContext>
-        <vm:ThreeRopesViewModel/>
-    </Page.DataContext>
-    <Page.Resources>
-        <cvt:RiskLevelToColorConverter x:Key="RiskLevelToColorConverter"/>
-        <cvt:RiskLevelToTextConverter x:Key="RiskLevelToTextConverter"/>
-        <cvt:ConnectivityFaultToColorConverter x:Key="ConnectivityFaultConverter"/>
-        <cvt:CollectionCountToVisibilityConverter x:Key="CollectionCountToVisibilityConverter"/>
-        <cvt:ConnectivityToVisibilityConverter x:Key="ConnectivityToVisibilityConverter"/>
-        <cvt:StringEmptyToVisibilityConverter x:Key="StringEmptyToVisibilityConverter"/>
-    </Page.Resources>
-    <Grid Margin="25,30,25,7">
-        <Grid.ColumnDefinitions>
-            <ColumnDefinition Width="*"/>
-            <ColumnDefinition Width="370"/>
-        </Grid.ColumnDefinitions>
-        <Grid.RowDefinitions>
-            <RowDefinition Height="*"/>
-            <RowDefinition Height="25"/>
-            <RowDefinition Height="*"/>
-            <RowDefinition Height="25"/>
-            <RowDefinition Height="*"/>
-        </Grid.RowDefinitions>
-        <Border Grid.Row="0" Grid.Column="0" CornerRadius="10" DataContext="{Binding FirstEquipmentData}">
-            <Border.Background>
-                <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
-                    <GradientStop Color="#2a2956" Offset="0"/>
-                    <GradientStop Color="#161536" Offset="0.40"/>
-                    <GradientStop Color="#1f1e44" Offset="1"/>
-                </LinearGradientBrush>
-            </Border.Background>
-            <Grid>
-                <Grid.RowDefinitions>
-                    <RowDefinition Height="135"/>
-                    <RowDefinition Height="*"/>
-                    <RowDefinition Height="Auto"/>
-                </Grid.RowDefinitions>
-                <Grid.ColumnDefinitions>
-                    <ColumnDefinition Width="*"/>
-                    <ColumnDefinition Width="240"/>
-                </Grid.ColumnDefinitions>
-                <StackPanel Orientation="Vertical" Grid.Row="0" Grid.Column="0" Margin="35,25,70,0">
-                    <DockPanel>
-                        <!--钢丝绳状态及名称-->
-                        <Rectangle Height="25" Width="20" RadiusX="6" RadiusY="6">
-                            <Rectangle.Fill>
-                                <MultiBinding Converter="{StaticResource ConnectivityFaultConverter}">
-                                    <Binding Path="IsConnect"/>
-                                    <Binding Path="Faults"/>
-                                </MultiBinding>
-                            </Rectangle.Fill>
-                        </Rectangle>
-                        <TextBlock Text="{Binding RopeName}" FontSize="24" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="13,0" VerticalAlignment="Center"/>
-                        <StackPanel Orientation="Horizontal" Margin="10,0" MouseLeftButtonDown="FirstParameterSetting_MouseDown" Cursor="Hand">
-                            <Image Source="/Resources/eqSet.png" Margin="10,0" Width="22" Height="22"/>
-                            <TextBlock Text="参数设置"  VerticalAlignment="Center" Foreground="#8D86E5" FontWeight="Regular"/>
-                        </StackPanel>
-                        <!--消息通知-->
-                        <ctr:NotificationBar Height="35" MaxWidth="800" FontSize="20" HorizontalAlignment="Right"
-                                             CornerRadius="17.5" Messages="{Binding Messages}" TextPadding="10,5"/>
-                    </DockPanel>
-                    <DockPanel Margin="0,15">
-                        <UniformGrid Columns="2" Rows="2" Width="400" HorizontalAlignment="Left">
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="风险等级"  Foreground="#7886B2"/>
-                                <TextBlock  FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}"  Margin="20,0"
-                                           Text="{Binding RiskLevel,Converter={StaticResource RiskLevelToTextConverter}}" 
-                                           Foreground="{Binding RiskLevel,Converter={StaticResource RiskLevelToColorConverter}}"/>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="起升高度"  Foreground="#7886B2"/>
-                                <TextBlock   Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="40,0">
-                                    <Run Text="{Binding LiftHeight,StringFormat=#0.0}"/>
-                                    <Run Text="m"/>
-                                </TextBlock>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="损伤数量"  Foreground="#7886B2"/>
-                                <TextBlock Text="{Binding DamageCount}"   Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0" />
-                                <TextBlock Text="详情" Foreground="#7F74FF" FontSize="18" FontWeight="Regular" Margin="0,2,0,0"
-                                       Cursor="Hand"  MouseLeftButtonDown="FirstEquipmentAlarm_MouseDown" 
-                                       Visibility="{Binding DamageCount,Converter={StaticResource CollectionCountToVisibilityConverter}}"/>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="最大损伤值"  Foreground="#7886B2"/>
-                                <TextBlock   Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0">
-                                    <Run Text="{Binding MaxDamageValue,StringFormat=#0.0}"/>
-                                    <Run Text="%"/>
-                                </TextBlock>
-                            </StackPanel>
-                        </UniformGrid>
-                        <ctr:DamageRangeControl HorizontalAlignment="Stretch" Height="45" FontSize="16" Margin="70,15,0,0" 
-                                                MildCount="{Binding MildCount}" LightCount="{Binding LightCount}" 
-                                                ModerateCount="{Binding ModerateCount}" SevereCount="{Binding SevereCount}"
-                                                CriticalCount="{Binding CriticalCount}" ExtremeCount="{Binding ExtremeCount}"/>
-                    </DockPanel>
-                </StackPanel>
-                <ctr:RopeEquipmentControl Grid.RowSpan="2" Grid.Column="1" Grid.Row="0"
-                                          PanelMargin="-80,25,0,0"
-                                          PanelNameFontSize="18"
-                                          PanelValueFontSize="20"
-                                          EquipmentTopPosition="75"
-                                          RopeImageSource="/Resources/rope_block.png" 
-                                          EquipmentImageSource="/Resources/equipment.png"
-                                          AnimationDuration="0.5" RopeHeight="285">
-
-                    <ctr:RopeEquipmentControl.Style>
-                        <Style TargetType="ctr:RopeEquipmentControl">
-                            <Style.Triggers>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Stoped}">
-                                    <Setter Property="IsAnimationRunning" Value="False"/>
-                                </DataTrigger>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Forward}">
-                                    <Setter Property="IsAnimationRunning" Value="True"/>
-                                    <Setter Property="IsAnimationReversed" Value="False"/>
-                                </DataTrigger>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Reverse}">
-                                    <Setter Property="IsAnimationRunning" Value="True"/>
-                                    <Setter Property="IsAnimationReversed" Value="True"/>
-                                </DataTrigger>
-                            </Style.Triggers>
-                        </Style>
-                    </ctr:RopeEquipmentControl.Style>
-                </ctr:RopeEquipmentControl>
-                <ctr:RealTimeLineChart x:Name="firstChartLine" Grid.Row="1" Margin="20,0,-90,-5"
-                               SensorCount="{Binding SensorCount}"
-                               SamplingStep="{Binding SamplingStep,Mode=TwoWay}"
-                               RopeLength="{Binding RopeLength}"/>
-
-                <StackPanel Grid.Row="2" Orientation="Horizontal" Margin="35,-5,-100,5"  
-                            Visibility="{Binding DebugMessage.Message,Converter={StaticResource StringEmptyToVisibilityConverter}}">
-                    <TextBlock Style="{StaticResource HeartbeatTextBlockStyle}"  FontSize="17"
-                               VerticalAlignment="Center" 
-                               Foreground="#7F74FF"/>
-                    <TextBlock Text="{Binding  DebugMessage.DateTime,StringFormat=HH:mm:ss}" FontSize="16" Margin="5,0" 
-                               VerticalAlignment="Center" 
-                               Foreground="#7F74FF"/>
-                    <TextBlock Text="{Binding DebugMessage.Message}" FontSize="16" Margin="5,0" 
-                               VerticalAlignment="Center"
-                               Foreground="#7F74FF"/>
-                </StackPanel>
-            </Grid>
-        </Border>
-        <Border Grid.Row="2" Grid.Column="0" CornerRadius="10"  DataContext="{Binding SecondEquipmentData}">
-            <Border.Background>
-                <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
-                    <GradientStop Color="#2a2956" Offset="0"/>
-                    <GradientStop Color="#161536" Offset="0.40"/>
-                    <GradientStop Color="#1f1e44" Offset="1"/>
-                </LinearGradientBrush>
-            </Border.Background>
-            <Grid>
-                <Grid.RowDefinitions>
-                    <RowDefinition Height="135"/>
-                    <RowDefinition Height="*"/>
-                    <RowDefinition Height="Auto"/>
-                </Grid.RowDefinitions>
-                <Grid.ColumnDefinitions>
-                    <ColumnDefinition Width="*"/>
-                    <ColumnDefinition Width="240"/>
-                </Grid.ColumnDefinitions>
-                <StackPanel Orientation="Vertical" Grid.Row="0" Grid.Column="0" Margin="35,25,70,0">
-                    <DockPanel>
-                        <!--钢丝绳状态及名称-->
-                        <Rectangle Height="25" Width="20" RadiusX="6" RadiusY="6">
-                            <Rectangle.Fill>
-                                <MultiBinding Converter="{StaticResource ConnectivityFaultConverter}">
-                                    <Binding Path="IsConnect"/>
-                                    <Binding Path="Faults"/>
-                                </MultiBinding>
-                            </Rectangle.Fill>
-                        </Rectangle>
-                        <TextBlock Text="{Binding RopeName}" FontSize="24" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="13,0" VerticalAlignment="Center"/>
-                        <StackPanel Orientation="Horizontal" Margin="10,0" MouseLeftButtonDown="SecondParameterSetting_MouseDown" Cursor="Hand">
-                            <Image Source="/Resources/eqSet.png" Margin="10,0" Width="22" Height="22"/>
-                            <TextBlock Text="参数设置"  VerticalAlignment="Center" Foreground="#8D86E5" FontWeight="Regular"/>
-                        </StackPanel>
-                        <!--消息通知-->
-                        <ctr:NotificationBar Height="35" MaxWidth="800" FontSize="20" HorizontalAlignment="Right" 
-                                             CornerRadius="17.5" Messages="{Binding Messages}" TextPadding="10,5"/>
-                    </DockPanel>
-                    <DockPanel Margin="0,15">
-                        <UniformGrid Columns="2" Rows="2" Width="400" HorizontalAlignment="Left">
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="风险等级"  Foreground="#7886B2"/>
-                                <TextBlock Text="{Binding RiskLevel,Converter={StaticResource RiskLevelToTextConverter}}"  FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}"
-                                           Foreground="{Binding RiskLevel,Converter={StaticResource RiskLevelToColorConverter}}" Margin="20,0" />
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="起升高度"  Foreground="#7886B2"/>
-                                <TextBlock   Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="40,0">
-                                    <Run Text="{Binding LiftHeight,StringFormat=#0.0}"/>
-                                    <Run Text="m"/>
-                                </TextBlock>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="损伤数量"  Foreground="#7886B2"/>
-                                <TextBlock Text="{Binding DamageCount}"   Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0" />
-                                <TextBlock Text="详情" Foreground="#7F74FF" FontSize="18" FontWeight="Regular" Margin="0,2,0,0"
-                                           Cursor="Hand"  MouseLeftButtonDown="SecondEquipmentAlarm_MouseDown" 
-                                           Visibility="{Binding DamageCount,Converter={StaticResource CollectionCountToVisibilityConverter}}"/>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="最大损伤值"  Foreground="#7886B2"/>
-                                <TextBlock   Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0">
-                                    <Run Text="{Binding MaxDamageValue,StringFormat=#0.0}"/>
-                                    <Run Text="%"/>
-                                </TextBlock>
-                            </StackPanel>
-                        </UniformGrid>
-                        <ctr:DamageRangeControl HorizontalAlignment="Stretch" Height="45" FontSize="16" Margin="70,15,0,0"
-                                    MildCount="{Binding MildCount}" LightCount="{Binding LightCount}" 
-                                    ModerateCount="{Binding ModerateCount}" SevereCount="{Binding SevereCount}"
-                                    CriticalCount="{Binding CriticalCount}" ExtremeCount="{Binding ExtremeCount}"/>
-                    </DockPanel>
-                </StackPanel>
-                <ctr:RopeEquipmentControl Grid.RowSpan="2" Grid.Column="1" Grid.Row="0"
-                                          PanelMargin="-80,25,0,0" 
-                                          PanelNameFontSize="18"
-                                          PanelValueFontSize="20"
-                                          EquipmentTopPosition="75"
-                                          RopeImageSource="/Resources/rope_block.png" 
-                                          EquipmentImageSource="/Resources/equipment.png"
-                                          AnimationDuration="0.5" RopeHeight="285">
-                    <ctr:RopeEquipmentControl.Style>
-                        <Style TargetType="ctr:RopeEquipmentControl">
-                            <Style.Triggers>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Stoped}">
-                                    <Setter Property="IsAnimationRunning" Value="False"/>
-                                </DataTrigger>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Forward}">
-                                    <Setter Property="IsAnimationRunning" Value="True"/>
-                                    <Setter Property="IsAnimationReversed" Value="False"/>
-                                </DataTrigger>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Reverse}">
-                                    <Setter Property="IsAnimationRunning" Value="True"/>
-                                    <Setter Property="IsAnimationReversed" Value="True"/>
-                                </DataTrigger>
-                            </Style.Triggers>
-                        </Style>
-                    </ctr:RopeEquipmentControl.Style>
-                </ctr:RopeEquipmentControl>
-                <ctr:RealTimeLineChart x:Name="secondChartLine"  Grid.Row="1" Margin="20,0,-90,-5"
-                               SensorCount="{Binding SensorCount}"
-                               SamplingStep="{Binding SamplingStep,Mode=TwoWay}"
-                               RopeLength="{Binding RopeLength}"/>
-
-                <StackPanel Grid.Row="2" Orientation="Horizontal" Margin="35,-5,-100,5" 
-                            Visibility="{Binding DebugMessage.Message,Converter={StaticResource StringEmptyToVisibilityConverter}}">
-                    <TextBlock Style="{StaticResource HeartbeatTextBlockStyle}"  FontSize="17"
-                               VerticalAlignment="Center" 
-                               Foreground="#7F74FF"/>
-                    <TextBlock Text="{Binding  DebugMessage.DateTime,StringFormat=HH:mm:ss}" FontSize="16" Margin="5,0" 
-                               VerticalAlignment="Center" 
-                               Foreground="#7F74FF"/>
-                    <TextBlock Text="{Binding DebugMessage.Message}" FontSize="16" Margin="5,0" 
-                               VerticalAlignment="Center"
-                               Foreground="#7F74FF"/>
-                </StackPanel>
-            </Grid>
-        </Border>
-        <Border Grid.Row="4" Grid.Column="0" CornerRadius="10" DataContext="{Binding ThirdEquipmentData}">
-            <Border.Background>
-                <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
-                    <GradientStop Color="#2a2956" Offset="0"/>
-                    <GradientStop Color="#161536" Offset="0.40"/>
-                    <GradientStop Color="#1f1e44" Offset="1"/>
-                </LinearGradientBrush>
-            </Border.Background>
-            <Grid>
-                <Grid.RowDefinitions>
-                    <RowDefinition Height="135"/>
-                    <RowDefinition Height="*"/>
-                    <RowDefinition Height="Auto"/>
-                </Grid.RowDefinitions>
-                <Grid.ColumnDefinitions>
-                    <ColumnDefinition Width="*"/>
-                    <ColumnDefinition Width="240"/>
-                </Grid.ColumnDefinitions>
-                <StackPanel Orientation="Vertical" Grid.Row="0" Grid.Column="0" Margin="35,25,70,0">
-                    <DockPanel>
-                        <!--钢丝绳状态及名称-->
-                        <Rectangle Height="25" Width="20" RadiusX="6" RadiusY="6">
-                            <Rectangle.Fill>
-                                <MultiBinding Converter="{StaticResource ConnectivityFaultConverter}">
-                                    <Binding Path="IsConnect"/>
-                                    <Binding Path="Faults"/>
-                                </MultiBinding>
-                            </Rectangle.Fill>
-                        </Rectangle>
-                        <TextBlock Text="{Binding RopeName}" FontSize="24" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="13,0" VerticalAlignment="Center"/>
-                        <StackPanel Orientation="Horizontal" Margin="10,0" MouseLeftButtonDown="ThreeParameterSetting_MouseDown" Cursor="Hand">
-                            <Image Source="/Resources/eqSet.png" Margin="10,0" Width="22" Height="22"/>
-                            <TextBlock Text="参数设置"  VerticalAlignment="Center" Foreground="#8D86E5" FontWeight="Regular"/>
-                        </StackPanel>
-                        <!--消息通知-->
-                        <ctr:NotificationBar Height="35" MaxWidth="800" FontSize="20" HorizontalAlignment="Right" 
-                                             CornerRadius="17.5" Messages="{Binding Messages}" TextPadding="10,5"/>
-                    </DockPanel>
-                    <DockPanel Margin="0,15">
-                        <UniformGrid Columns="2" Rows="2" Width="400" HorizontalAlignment="Left">
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="风险等级"  Foreground="#7886B2"/>
-                                <TextBlock  FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0" 
-                                           Text="{Binding RiskLevel,Converter={StaticResource RiskLevelToTextConverter}}" 
-                                           Foreground="{Binding RiskLevel,Converter={StaticResource RiskLevelToColorConverter}}"/>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="起升高度"  Foreground="#7886B2"/>
-                                <TextBlock   Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="40,0">
-                                    <Run Text="{Binding LiftHeight,StringFormat=#0.0}"/>
-                                    <Run Text="m"/>
-                                </TextBlock>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="损伤数量"  Foreground="#7886B2"/>
-                                <TextBlock Text="{Binding DamageCount}"   Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0" />
-                                <TextBlock Text="详情" Foreground="#7F74FF" FontSize="18" FontWeight="Regular" Margin="0,2,0,0"
-                                           Cursor="Hand"  MouseLeftButtonDown="ThreeEquipmentAlarm_MouseDown" 
-                                           Visibility="{Binding DamageCount,Converter={StaticResource CollectionCountToVisibilityConverter}}"/>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,2">
-                                <TextBlock Text="最大损伤值"  Foreground="#7886B2"/>
-                                <TextBlock   Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0">
-                                    <Run Text="{Binding MaxDamageValue,StringFormat=#0.0}"/>
-                                    <Run Text="%"/>
-                                </TextBlock>
-                            </StackPanel>
-                        </UniformGrid>
-                        <ctr:DamageRangeControl HorizontalAlignment="Stretch" Height="45" FontSize="16" Margin="70,15,0,0"
-                                    MildCount="{Binding MildCount}" LightCount="{Binding LightCount}" 
-                                    ModerateCount="{Binding ModerateCount}" SevereCount="{Binding SevereCount}"
-                                    CriticalCount="{Binding CriticalCount}" ExtremeCount="{Binding ExtremeCount}"/>
-
-                    </DockPanel>
-                </StackPanel>
-                <ctr:RopeEquipmentControl Grid.RowSpan="2" Grid.Column="1" Grid.Row="0"
-                                          PanelMargin="-80,25,0,0"
-                                          EquipmentTopPosition="75"
-                                          PanelNameFontSize="18"
-                                          PanelValueFontSize="20"
-                                          RopeImageSource="/Resources/rope_block.png"
-                                          EquipmentImageSource="/Resources/equipment.png"
-                                          AnimationDuration="0.5" RopeHeight="285">
-                    <ctr:RopeEquipmentControl.Style>
-                        <Style TargetType="ctr:RopeEquipmentControl">
-                            <Style.Triggers>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Stoped}">
-                                    <Setter Property="IsAnimationRunning" Value="False"/>
-                                </DataTrigger>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Forward}">
-                                    <Setter Property="IsAnimationRunning" Value="True"/>
-                                    <Setter Property="IsAnimationReversed" Value="False"/>
-                                </DataTrigger>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Reverse}">
-                                    <Setter Property="IsAnimationRunning" Value="True"/>
-                                    <Setter Property="IsAnimationReversed" Value="True"/>
-                                </DataTrigger>
-                            </Style.Triggers>
-                        </Style>
-                    </ctr:RopeEquipmentControl.Style>
-                </ctr:RopeEquipmentControl>
-
-                <ctr:RealTimeLineChart x:Name="thirdChartLine" Grid.Row="1" Margin="20,0,-90,-5"
-                               SensorCount="{Binding SensorCount}"
-                               SamplingStep="{Binding SamplingStep,Mode=TwoWay}"
-                               RopeLength="{Binding RopeLength}"/>
-
-                <StackPanel Grid.Row="2" Orientation="Horizontal" Margin="35,-5,-100,5"  
-                            Visibility="{Binding DebugMessage.Message,Converter={StaticResource StringEmptyToVisibilityConverter}}">
-                    <TextBlock Style="{StaticResource HeartbeatTextBlockStyle}"  FontSize="17"
-                               VerticalAlignment="Center" 
-                               Foreground="#7F74FF"/>
-                    <TextBlock Text="{Binding  DebugMessage.DateTime,StringFormat=HH:mm:ss}" FontSize="16" Margin="5,0" 
-                               VerticalAlignment="Center" 
-                               Foreground="#7F74FF"/>
-                    <TextBlock Text="{Binding DebugMessage.Message}" FontSize="16" Margin="5,0" 
-                               VerticalAlignment="Center"
-                               Foreground="#7F74FF"/>
-                </StackPanel>
-
-            </Grid>
-        </Border>
-        <ctr:DamageRecordControl Grid.Column="1" Grid.RowSpan="5" Margin="25,0,0,0" Records="{Binding Records}" Title="近期记录"/>
-    </Grid>
-</Page>

+ 0 - 139
Pages/RealTime/ThreeRopesPage.xaml.cs

@@ -1,139 +0,0 @@
-using SWRIS.Core;
-using SWRIS.Dtos;
-using SWRIS.Events;
-using SWRIS.Extensions;
-using SWRIS.Models.ViewModel;
-using SWRIS.Repository;
-using System;
-using System.Collections.ObjectModel;
-using System.Linq;
-using System.Threading.Tasks;
-using System.Windows.Controls;
-
-namespace SWRIS.Pages.RealTime
-{
-    /// <summary>
-    /// ThreeRopesPage.xaml 的交互逻辑
-    /// </summary>
-    public partial class ThreeRopesPage : Page
-    {
-        private readonly IRecordRepository _recordRepository;
-        public ThreeRopesViewModel ThreeRopesView { get; set; }
-        public ThreeRopesPage()
-        {
-            InitializeComponent();
-            _recordRepository = new RecordRepository();
-            Loaded += OneRopePage_Loaded;
-            Unloaded += OneRopePage_Unloaded;
-            ThreeRopesView = new ThreeRopesViewModel
-            {
-                FirstEquipmentData = App.DataCenter.Equipments[0],
-                SecondEquipmentData = App.DataCenter.Equipments[1],
-                ThirdEquipmentData = App.DataCenter.Equipments[2],
-                Records = _recordRepository.GetLastRecords().ToSimpleRecordDtos()
-            };
-            ThreeRopesView.FirstEquipment = App.Config.Equipments.First(c => c.IpAddress == ThreeRopesView.FirstEquipmentData.IpAddress);
-            ThreeRopesView.SecondEquipment = App.Config.Equipments.First(c => c.IpAddress == ThreeRopesView.SecondEquipmentData.IpAddress);
-            ThreeRopesView.ThirdEquipment = App.Config.Equipments.First(c => c.IpAddress == ThreeRopesView.ThirdEquipmentData.IpAddress);
-            DataContext = ThreeRopesView;
-
-            App.TcpServer.LiveStreamReceived += TcpServer_LiveStreamReceived;
-        }
-        private void OneRopePage_Loaded(object sender, System.Windows.RoutedEventArgs e)
-        {
-            if (ThreeRopesView.FirstEquipmentData.ChartSource != null)
-            {
-                firstChartLine.AddDataPoints(ThreeRopesView.FirstEquipmentData.ChartSource);
-            }
-            if (ThreeRopesView.SecondEquipmentData.ChartSource != null)
-            {
-                secondChartLine.AddDataPoints(ThreeRopesView.SecondEquipmentData.ChartSource);
-            }
-            if (ThreeRopesView.ThirdEquipmentData.ChartSource != null)
-            {
-                thirdChartLine.AddDataPoints(ThreeRopesView.ThirdEquipmentData.ChartSource);
-            }
-        }
-
-        private void OneRopePage_Unloaded(object sender, System.Windows.RoutedEventArgs e)
-        {
-            ThreeRopesView.FirstEquipmentData.ChartSource = firstChartLine.GetChartSource();
-            ThreeRopesView.SecondEquipmentData.ChartSource = secondChartLine.GetChartSource();
-            ThreeRopesView.ThirdEquipmentData.ChartSource = thirdChartLine.GetChartSource();
-        }
-        private async void TcpServer_LiveStreamReceived(object sender, LiveStreamReceivedEventArgs e)
-        {
-            if (e.IpAddress == ThreeRopesView.FirstEquipment.IpAddress)
-            {
-                if (ThreeRopesView.FirstEquipmentData.DetectionData?.DetectionNo == ThreeRopesView.FirstEquipmentData.DetectionNo)
-                {
-                    ThreeRopesView.FirstEquipmentData.ClearData();
-                }
-                await Task.Run(() =>
-                {
-                    firstChartLine.AddDataPoints((ThreeRopesView.FirstEquipmentData.AbsolutePosition, e.LiveStream), ThreeRopesView.FirstEquipmentData.InUseSensor);
-                });
-            }
-            else if (e.IpAddress == ThreeRopesView.SecondEquipmentData.IpAddress)
-            {
-                if (ThreeRopesView.SecondEquipmentData.DetectionData?.DetectionNo == ThreeRopesView.SecondEquipmentData.DetectionNo)
-                {
-                    ThreeRopesView.SecondEquipmentData.ClearData();
-                }
-                await Task.Run(() =>
-                {
-                    secondChartLine.AddDataPoints((ThreeRopesView.SecondEquipmentData.AbsolutePosition, e.LiveStream), ThreeRopesView.SecondEquipmentData.InUseSensor);
-                });
-            }
-            else if (e.IpAddress == ThreeRopesView.ThirdEquipmentData.IpAddress)
-            {
-                if (ThreeRopesView.ThirdEquipmentData.DetectionData?.DetectionNo == ThreeRopesView.ThirdEquipmentData.DetectionNo)
-                {
-                    ThreeRopesView.ThirdEquipmentData.ClearData();
-                }
-                await Task.Run(() =>
-                {
-                    thirdChartLine.AddDataPoints((ThreeRopesView.ThirdEquipmentData.AbsolutePosition, e.LiveStream), ThreeRopesView.ThirdEquipmentData.InUseSensor);
-                });
-            }
-        }
-
-        private void FirstEquipmentAlarm_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
-        {
-            DamagesDialog damagesDialog = new DamagesDialog(ThreeRopesView.FirstEquipmentData.Alarms);
-            damagesDialog.ShowDialog(true);
-        }
-
-        private void SecondEquipmentAlarm_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
-        {
-            DamagesDialog damagesDialog = new DamagesDialog(ThreeRopesView.SecondEquipmentData.Alarms);
-            damagesDialog.ShowDialog(true);
-        }
-        private void ThreeEquipmentAlarm_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
-        {
-            DamagesDialog damagesDialog = new DamagesDialog(ThreeRopesView.ThirdEquipmentData.Alarms);
-            damagesDialog.ShowDialog(true);
-        }
-        private void FirstParameterSetting_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
-        {
-            ParameterDialog parameterDialog = new ParameterDialog(ThreeRopesView.FirstEquipment,
-                                                                  ThreeRopesView.FirstEquipmentData.ClientSocket,
-                                                                  ThreeRopesView.FirstEquipmentData.RunningStatus);
-            parameterDialog.ShowDialog(true);
-        }
-        private void SecondParameterSetting_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
-        {
-            ParameterDialog parameterDialog = new ParameterDialog(ThreeRopesView.SecondEquipment,
-                                                                  ThreeRopesView.SecondEquipmentData.ClientSocket,
-                                                                  ThreeRopesView.SecondEquipmentData.RunningStatus);
-            parameterDialog.ShowDialog(true);
-        }
-        private void ThreeParameterSetting_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
-        {
-            ParameterDialog parameterDialog = new ParameterDialog(ThreeRopesView.ThirdEquipment,
-                                                                  ThreeRopesView.ThirdEquipmentData.ClientSocket,
-                                                                  ThreeRopesView.ThirdEquipmentData.RunningStatus);
-            parameterDialog.ShowDialog(true);
-        }
-    }
-}

+ 179 - 0
Pages/RealTime/TwoRopesControl.xaml

@@ -0,0 +1,179 @@
+<UserControl x:Class="SWRIS.Pages.RealTime.TwoRopesControl"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:SWRIS.Pages.RealTime" 
+             xmlns:cvt="clr-namespace:SWRIS.Converters" 
+             xmlns:ctr="clr-namespace:SWRIS.Controls" 
+             xmlns:enum="clr-namespace:SWRIS.Enums"
+             mc:Ignorable="d" FontSize="20"
+             Foreground="#FFFFFF"
+             FontFamily="{StaticResource PuHuiTiRegular}">
+    <UserControl.Resources>
+        <cvt:RiskLevelToColorConverter x:Key="RiskLevelToColorConverter"/>
+        <cvt:RiskLevelToTextConverter x:Key="RiskLevelToTextConverter"/>
+        <cvt:ConnectivityFaultToColorConverter x:Key="ConnectivityFaultConverter"/>
+        <cvt:CollectionCountToVisibilityConverter x:Key="CollectionCountToVisibilityConverter"/>
+        <cvt:ConnectivityToVisibilityConverter x:Key="ConnectivityToVisibilityConverter"/>
+        <cvt:StringEmptyToVisibilityConverter x:Key="StringEmptyToVisibilityConverter"/>
+    </UserControl.Resources>
+    <ItemsControl ItemsSource="{Binding Equipments}" Margin="25,15,25,-10">
+        <ItemsControl.ItemsPanel>
+            <ItemsPanelTemplate>
+                <UniformGrid Columns="1" Rows="2"/>
+            </ItemsPanelTemplate>
+        </ItemsControl.ItemsPanel>
+        <ItemsControl.ItemTemplate>
+            <DataTemplate>
+                <Border Grid.Row="0" CornerRadius="10" Margin="0,10,0,15" Loaded="OnCardLoaded">
+                    <Border.Background>
+                        <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
+                            <GradientStop Color="#2a2956" Offset="0"/>
+                            <GradientStop Color="#161536" Offset="0.40"/>
+                            <GradientStop Color="#1f1e44" Offset="1"/>
+                        </LinearGradientBrush>
+                    </Border.Background>
+                    <Grid>
+                        <Grid.RowDefinitions>
+                            <RowDefinition Height="180"/>
+                            <RowDefinition Height="*"/>
+                            <RowDefinition Height="Auto"/>
+                        </Grid.RowDefinitions>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="*"/>
+                            <ColumnDefinition Width="180"/>
+                        </Grid.ColumnDefinitions>
+                        <StackPanel Orientation="Vertical" Grid.Row="0" Grid.Column="0" Margin="40,30,70,0">
+                            <DockPanel>
+                                <!--钢丝绳状态及名称-->
+                                <Rectangle Height="25" Width="20" RadiusX="6" RadiusY="6">
+                                    <Rectangle.Fill>
+                                        <MultiBinding Converter="{StaticResource ConnectivityFaultConverter}">
+                                            <Binding Path="IsConnect"/>
+                                            <Binding Path="Faults"/>
+                                            <Binding Path="Faults.Count"/>
+                                        </MultiBinding>
+                                    </Rectangle.Fill>
+                                </Rectangle>
+                                <TextBlock Text="{Binding RopeName}" FontSize="30" FontWeight="Bold" 
+                                           FontFamily="{StaticResource PuHuiTiBold}"
+                                           Margin="13,0" VerticalAlignment="Center"/>
+                                <StackPanel Orientation="Horizontal" Margin="30,0" Tag="{Binding IpAddress}"
+                                            MouseLeftButtonDown="ParameterSetting_MouseDown" Cursor="Hand">
+                                    <Image Source="/Resources/eqSet.png" Margin="10,0" Width="26" Height="26"/>
+                                    <TextBlock Text="参数设置" FontSize="22" VerticalAlignment="Center" 
+                                               Foreground="#8D86E5" FontWeight="Regular"/>
+                                </StackPanel>
+                            </DockPanel>
+                            <DockPanel Margin="0,20">
+                                <UniformGrid Columns="2" Rows="2" Width="400" HorizontalAlignment="Left">
+                                    <StackPanel Orientation="Horizontal" Margin="0,5">
+                                        <TextBlock Text="风险等级" Foreground="#7886B2"/>
+                                        <TextBlock FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0" 
+                                                   Text="{Binding RiskLevel,Converter={StaticResource RiskLevelToTextConverter}}" 
+                                                   Foreground="{Binding RiskLevel,Converter={StaticResource RiskLevelToColorConverter}}"/>
+                                    </StackPanel>
+                                    <StackPanel Orientation="Horizontal" Margin="0,5">
+                                        <TextBlock Text="起升高度" Foreground="#7886B2"/>
+                                        <TextBlock  Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="43,0">
+                                            <Run Text="{Binding LiftHeight,StringFormat=#0.0}"/>
+                                            <Run Text="m"/>
+                                        </TextBlock>
+                                    </StackPanel>
+                                    <StackPanel Orientation="Horizontal" Margin="0,5">
+                                        <TextBlock Text="损伤数量" Foreground="#7886B2"/>
+                                        <TextBlock Text="{Binding DamageCount}" Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0" />
+                                        <TextBlock Text="详情" Foreground="#7F74FF" FontSize="20" FontWeight="Regular" VerticalAlignment="Bottom" Cursor="Hand"  
+                                                   Visibility="{Binding DamageCount,Converter={StaticResource CollectionCountToVisibilityConverter}}"
+                                                   Tag="{Binding IpAddress}"
+                                                   MouseLeftButtonDown="EquipmentAlarm_MouseDown"/>
+                                    </StackPanel>
+                                    <StackPanel Orientation="Horizontal" Margin="0,5">
+                                        <TextBlock Text="最大损伤值" Foreground="#7886B2"/>
+                                        <TextBlock  Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0">
+                                            <Run Text="{Binding MaxDamageValue,StringFormat=#0.0}"/>
+                                            <Run Text="%"/>
+                                        </TextBlock>
+                                    </StackPanel>
+                                </UniformGrid>
+                                <ctr:DamageRangeControl HorizontalAlignment="Stretch" Height="45" 
+                                                        FontSize="16" Margin="60,25,0,0"
+                                                        MildCount="{Binding MildCount}"
+                                                        LightCount="{Binding LightCount}" 
+                                                        ModerateCount="{Binding ModerateCount}" 
+                                                        SevereCount="{Binding SevereCount}"
+                                                        CriticalCount="{Binding CriticalCount}" 
+                                                        ExtremeCount="{Binding ExtremeCount}"/>
+                            </DockPanel>
+                        </StackPanel>
+                        <ctr:RopeEquipmentControl Grid.RowSpan="2" Grid.Column="1"
+                                                  Grid.Row="0" Margin="0,0,0,-15"
+                                                  PanelMargin="-100,50,0,0"
+                                                  EquipmentTopPosition="120"
+                                                  PanelNameFontSize="18"
+                                                  PanelValueFontSize="20"
+                                                  RopeImageSource="/Resources/rope_block.png"
+                                                  EquipmentImageSource="/Resources/equipment.png"
+                                                  AnimationDuration="0.5">
+                            <ctr:RopeEquipmentControl.Style>
+                                <Style TargetType="ctr:RopeEquipmentControl">
+                                    <Style.Triggers>
+                                        <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Stoped}">
+                                            <Setter Property="IsAnimationRunning" Value="False"/>
+                                        </DataTrigger>
+                                        <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Forward}">
+                                            <Setter Property="IsAnimationRunning" Value="True"/>
+                                            <Setter Property="IsAnimationReversed" Value="False"/>
+                                        </DataTrigger>
+                                        <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Reverse}">
+                                            <Setter Property="IsAnimationRunning" Value="True"/>
+                                            <Setter Property="IsAnimationReversed" Value="True"/>
+                                        </DataTrigger>
+                                    </Style.Triggers>
+                                </Style>
+                            </ctr:RopeEquipmentControl.Style>
+                        </ctr:RopeEquipmentControl>
+
+                        <ctr:RealTimeLineChart x:Name="firstChartLine" Grid.Row="1"
+                                               Margin="25,10,-70,0" 
+                                               SensorCount="{Binding SensorCount}"
+                                               SamplingStep="{Binding SamplingStep}"
+                                               RopeLength="{Binding RopeLength}"/>
+
+                        <DockPanel Grid.Row="2" VerticalAlignment="Center" Margin="40,-10,10,3">
+                            <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Left"
+                                        Visibility="{Binding DebugMessage.Message,Converter={StaticResource StringEmptyToVisibilityConverter}}">
+                                <TextBlock Style="{StaticResource HeartbeatTextBlockStyle}" FontSize="17" VerticalAlignment="Center" Foreground="#7F74FF"/>
+                                <TextBlock Text="{Binding  DebugMessage.DateTime,StringFormat=HH:mm:ss}" FontSize="16" Margin="8,0,5,0" 
+                                           VerticalAlignment="Center" Foreground="#7F74FF"/>
+                                <TextBlock Text="{Binding DebugMessage.Message}" FontSize="16" Margin="5,0" VerticalAlignment="Center" Foreground="#7F74FF"/>
+                            </StackPanel>
+                            <ItemsControl ItemsSource="{Binding LiveStreamValues}" Margin="10,4,-70,0" HorizontalAlignment="Right">
+                                <ItemsControl.ItemsPanel>
+                                    <ItemsPanelTemplate><WrapPanel/></ItemsPanelTemplate>
+                                </ItemsControl.ItemsPanel>
+                                <ItemsControl.ItemTemplate>
+                                    <DataTemplate>
+                                        <TextBlock Text="{Binding}" FontSize="12" Margin="4,0,0,0"
+                                                   Width="20" Foreground="White" Opacity="0.2" FontWeight="Medium">
+                                            <TextBlock.Style>
+                                                <Style TargetType="TextBlock">
+                                                    <Style.Triggers>
+                                                        <DataTrigger Binding="{Binding}" Value="0">
+                                                            <Setter Property="Visibility" Value="Collapsed"/>
+                                                        </DataTrigger>
+                                                    </Style.Triggers>
+                                                </Style>
+                                            </TextBlock.Style>
+                                        </TextBlock>
+                                    </DataTemplate>
+                                </ItemsControl.ItemTemplate>
+                            </ItemsControl>
+                        </DockPanel>
+                    </Grid>
+                </Border>
+            </DataTemplate>
+        </ItemsControl.ItemTemplate>
+    </ItemsControl>
+</UserControl>

+ 69 - 0
Pages/RealTime/TwoRopesControl.xaml.cs

@@ -0,0 +1,69 @@
+using SWRIS.Controls;
+using SWRIS.Extensions;
+using SWRIS.Models;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace SWRIS.Pages.RealTime
+{
+    /// <summary>
+    /// TwoRopesPage.xaml 的交互逻辑
+    /// </summary>
+    public partial class TwoRopesControl : UserControl
+    {
+        public static readonly DependencyProperty EquipmentsProperty =
+        DependencyProperty.Register("Equipments", typeof(ObservableCollection<EquipmentDataModel>),
+            typeof(TwoRopesControl),
+            new PropertyMetadata(null));
+
+        public ObservableCollection<EquipmentDataModel> Equipments
+        {
+            get { return (ObservableCollection<EquipmentDataModel>)GetValue(EquipmentsProperty); }
+            set { SetValue(EquipmentsProperty, value); }
+        }
+
+        public TwoRopesControl()
+        {
+            InitializeComponent();
+            DataContext = this;
+        }
+
+        private void EquipmentAlarm_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            if ((sender as TextBlock)?.DataContext is EquipmentDataModel equipmentData)
+            {
+                DamagesDialog damagesDialog = new DamagesDialog(equipmentData.Alarms);
+                damagesDialog.ShowDialog(true);
+            }
+        }
+
+        private void ParameterSetting_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            if ((sender as StackPanel)?.DataContext is EquipmentDataModel equipmentData)
+            {
+                var equipment = App.Config.Equipments.FirstOrDefault(c => c.RopeNumber == equipmentData.RopeNumber);
+                if (equipmentData != null && equipment != null)
+                {
+                    ParameterDialog parameterDialog = new ParameterDialog(equipment, equipmentData.ClientSocket, equipmentData.RunningStatus);
+                    parameterDialog.ShowDialog(true);
+                }
+            }
+        }
+        private void OnCardLoaded(object sender, RoutedEventArgs e)
+        {
+            var border = sender as Border;
+            if (!(border?.DataContext is EquipmentDataModel data)) return;
+            if (border.FindName("ChartLine") is RealTimeLineChart chart)
+            {
+                var chartSource = data.Chart?.GetChartSource();
+                if (chart != null && chartSource != null)
+                {
+                    chart.AddDataPoints(chartSource);
+                }
+                data.Chart = chart;
+            }
+        }
+    }
+}

+ 0 - 268
Pages/RealTime/TwoRopesPage.xaml

@@ -1,268 +0,0 @@
-<Page x:Class="SWRIS.Pages.RealTime.TwoRopesPage"
-      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
-      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
-      xmlns:local="clr-namespace:SWRIS.Pages.RealTime" 
-      xmlns:cvt="clr-namespace:SWRIS.Converters" 
-      xmlns:ctr="clr-namespace:SWRIS.Controls" 
-      xmlns:vm="clr-namespace:SWRIS.Models.ViewModel" 
-      xmlns:enum="clr-namespace:SWRIS.Enums"
-      mc:Ignorable="d"  FontSize="20"
-      Foreground="#FFFFFF"
-      FontFamily="{StaticResource PuHuiTiRegular}"
-      d:DesignHeight="900" d:DesignWidth="1920"
-      Title="TwoRopesPage">
-    <Page.DataContext>
-        <vm:TwoRopesViewModel/>
-    </Page.DataContext>
-    <Page.Resources>
-        <cvt:RiskLevelToColorConverter x:Key="RiskLevelToColorConverter"/>
-        <cvt:RiskLevelToTextConverter x:Key="RiskLevelToTextConverter"/>
-        <cvt:ConnectivityFaultToColorConverter x:Key="ConnectivityFaultConverter"/>
-        <cvt:CollectionCountToVisibilityConverter x:Key="CollectionCountToVisibilityConverter"/>
-        <cvt:ConnectivityToVisibilityConverter x:Key="ConnectivityToVisibilityConverter"/>
-        <cvt:StringEmptyToVisibilityConverter x:Key="StringEmptyToVisibilityConverter"/>
-    </Page.Resources>
-    <Grid Margin="25,30,25,7">
-        <Grid.ColumnDefinitions>
-            <ColumnDefinition Width="*"/>
-            <ColumnDefinition Width="370"/>
-        </Grid.ColumnDefinitions>
-        <Grid.RowDefinitions>
-            <RowDefinition Height="*"/>
-            <RowDefinition Height="*"/>
-        </Grid.RowDefinitions>
-        <Border Grid.Row="0" Grid.Column="0" CornerRadius="10" Margin="0,0,0,15" DataContext="{Binding FirstEquipmentData}">
-            <Border.Background>
-                <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
-                    <GradientStop Color="#2a2956" Offset="0"/>
-                    <GradientStop Color="#161536" Offset="0.40"/>
-                    <GradientStop Color="#1f1e44" Offset="1"/>
-                </LinearGradientBrush>
-            </Border.Background>
-            <Grid>
-                <Grid.RowDefinitions>
-                    <RowDefinition Height="180"/>
-                    <RowDefinition Height="*"/>
-                    <RowDefinition Height="Auto"/>
-                </Grid.RowDefinitions>
-                <Grid.ColumnDefinitions>
-                    <ColumnDefinition Width="*"/>
-                    <ColumnDefinition Width="240"/>
-                </Grid.ColumnDefinitions>
-                <StackPanel Orientation="Vertical" Grid.Row="0" Grid.Column="0" Margin="40,30,70,0">
-                    <DockPanel>
-                        <!--钢丝绳状态及名称-->
-                        <Rectangle Height="25" Width="20" RadiusX="6" RadiusY="6">
-                            <Rectangle.Fill>
-                                <MultiBinding Converter="{StaticResource ConnectivityFaultConverter}">
-                                    <Binding Path="IsConnect"/>
-                                    <Binding Path="IsFaulting"/>
-                                </MultiBinding>
-                            </Rectangle.Fill>
-                        </Rectangle>
-                        <TextBlock Text="{Binding RopeName}" FontSize="30" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="13,0" VerticalAlignment="Center"/>
-                        <StackPanel Orientation="Horizontal" Margin="30,0" MouseLeftButtonDown="FirstParameterSetting_MouseDown" Cursor="Hand">
-                            <Image Source="/Resources/eqSet.png" Margin="10,0" Width="26" Height="26"/>
-                            <TextBlock Text="参数设置" FontSize="22" VerticalAlignment="Center" Foreground="#8D86E5" FontWeight="Regular"/>
-                        </StackPanel>
-                        <!--消息通知-->
-                        <ctr:NotificationBar Height="50" MaxWidth="800" HorizontalAlignment="Right"
-                                       Messages="{Binding Messages}"/>
-                    </DockPanel>
-                    <DockPanel Margin="0,20">
-                        <UniformGrid Columns="2" Rows="2" Width="400" HorizontalAlignment="Left">
-                            <StackPanel Orientation="Horizontal" Margin="0,5">
-                                <TextBlock Text="风险等级" Foreground="#7886B2"/>
-                                <TextBlock FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0" 
-                                     Text="{Binding RiskLevel,Converter={StaticResource RiskLevelToTextConverter}}" 
-                                     Foreground="{Binding RiskLevel,Converter={StaticResource RiskLevelToColorConverter}}"/>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,5">
-                                <TextBlock Text="起升高度" Foreground="#7886B2"/>
-                                <TextBlock  Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="43,0">
-                                    <Run Text="{Binding LiftHeight,StringFormat=#0.0}"/>
-                                    <Run Text="m"/>
-                                </TextBlock>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,5">
-                                <TextBlock Text="损伤数量" Foreground="#7886B2"/>
-                                <TextBlock Text="{Binding DamageCount}" Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0" />
-                                <TextBlock Text="详情" Foreground="#7F74FF" FontSize="20" FontWeight="Regular" VerticalAlignment="Bottom" Cursor="Hand"                                           
-                                     Visibility="{Binding DamageCount,Converter={StaticResource CollectionCountToVisibilityConverter}}"
-                                     MouseLeftButtonDown="FirstEquipmentAlarm_MouseDown"/>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,5">
-                                <TextBlock Text="最大损伤值" Foreground="#7886B2"/>
-                                <TextBlock  Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0">
-                                    <Run Text="{Binding MaxDamageValue,StringFormat=#0.0}"/>
-                                    <Run Text="%"/>
-                                </TextBlock>
-                            </StackPanel>
-                        </UniformGrid>
-                        <ctr:DamageRangeControl HorizontalAlignment="Stretch" Height="45" FontSize="16" Margin="100,35,0,0"
-                                MildCount="{Binding MildCount}" LightCount="{Binding LightCount}" 
-                                ModerateCount="{Binding ModerateCount}" SevereCount="{Binding SevereCount}"
-                                CriticalCount="{Binding CriticalCount}" ExtremeCount="{Binding ExtremeCount}"/>
-                    </DockPanel>
-                </StackPanel>
-                <ctr:RopeEquipmentControl Grid.RowSpan="2" Grid.Column="1" Grid.Row="0" Margin="0,0,0,-20"
-                                          PanelMargin="-80,40,0,0"
-                                          EquipmentTopPosition="160"
-                                          PanelNameFontSize="18"
-                                          PanelValueFontSize="20"
-                                          RopeImageSource="/Resources/rope_block.png" 
-                                          EquipmentImageSource="/Resources/equipment.png"
-                                          AnimationDuration="0.5" RopeHeight="435">
-                    <ctr:RopeEquipmentControl.Style>
-                        <Style TargetType="ctr:RopeEquipmentControl">
-                            <Style.Triggers>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Stoped}">
-                                    <Setter Property="IsAnimationRunning" Value="False"/>
-                                </DataTrigger>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Forward}">
-                                    <Setter Property="IsAnimationRunning" Value="True"/>
-                                    <Setter Property="IsAnimationReversed" Value="False"/>
-                                </DataTrigger>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Reverse}">
-                                    <Setter Property="IsAnimationRunning" Value="True"/>
-                                    <Setter Property="IsAnimationReversed" Value="True"/>
-                                </DataTrigger>
-                            </Style.Triggers>
-                        </Style>
-                    </ctr:RopeEquipmentControl.Style>
-                </ctr:RopeEquipmentControl>
-
-                <ctr:RealTimeLineChart x:Name="firstChartLine" Grid.Row="1"
-                         Margin="25,10,-70,0" 
-                         SensorCount="{Binding SensorCount}"
-                         SamplingStep="{Binding SamplingStep}"
-                         RopeLength="{Binding RopeLength}"/>
-
-                <StackPanel Grid.Row="2" Orientation="Horizontal" Margin="40,-5,-100,5" 
-                      Visibility="{Binding DebugMessage.Message,Converter={StaticResource StringEmptyToVisibilityConverter}}">
-                    <TextBlock Style="{StaticResource HeartbeatTextBlockStyle}" FontSize="17" VerticalAlignment="Center" Foreground="#7F74FF"/>
-                    <TextBlock Text="{Binding  DebugMessage.DateTime,StringFormat=HH:mm:ss}" FontSize="16" Margin="8,0,5,0" VerticalAlignment="Center" Foreground="#7F74FF"/>
-                    <TextBlock Text="{Binding DebugMessage.Message}" FontSize="16" Margin="5,0" VerticalAlignment="Center" Foreground="#7F74FF"/>
-                </StackPanel>
-            </Grid>
-        </Border>
-        <Border Grid.Row="1" Grid.Column="0" CornerRadius="10" Margin="0,15,0,0" DataContext="{Binding SecondEquipmentData}">
-            <Border.Background>
-                <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
-                    <GradientStop Color="#2a2956" Offset="0"/>
-                    <GradientStop Color="#161536" Offset="0.40"/>
-                    <GradientStop Color="#1f1e44" Offset="1"/>
-                </LinearGradientBrush>
-            </Border.Background>
-            <Grid>
-                <Grid.RowDefinitions>
-                    <RowDefinition Height="180"/>
-                    <RowDefinition Height="*"/>
-                    <RowDefinition Height="Auto"/>
-                </Grid.RowDefinitions>
-                <Grid.ColumnDefinitions>
-                    <ColumnDefinition Width="*"/>
-                    <ColumnDefinition Width="240"/>
-                </Grid.ColumnDefinitions>
-                <StackPanel Orientation="Vertical" Grid.Row="0" Grid.Column="0" Margin="40,30,70,0">
-                    <DockPanel>
-                        <!--钢丝绳状态及名称-->
-                        <Rectangle Height="25" Width="20" RadiusX="6" RadiusY="6">
-                            <Rectangle.Fill>
-                                <MultiBinding Converter="{StaticResource ConnectivityFaultConverter}">
-                                    <Binding Path="IsConnect"/>
-                                    <Binding Path="IsFaulting"/>
-                                </MultiBinding>
-                            </Rectangle.Fill>
-                        </Rectangle>
-                        <TextBlock Text="{Binding RopeName}" FontSize="30" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="13,0" VerticalAlignment="Center"/>
-                        <StackPanel Orientation="Horizontal" Margin="30,0" MouseLeftButtonDown="SecondParameterSetting_MouseDown" Cursor="Hand">
-                            <Image Source="/Resources/eqSet.png" Margin="10,0" Width="26" Height="26"/>
-                            <TextBlock Text="参数设置" FontSize="22" VerticalAlignment="Center" Foreground="#8D86E5" FontWeight="Regular"/>
-                        </StackPanel>
-                        <!--消息通知-->
-                        <ctr:NotificationBar Height="50" MaxWidth="800" HorizontalAlignment="Right"
-                                             Messages="{Binding Messages}"/>
-                    </DockPanel>
-                    <DockPanel Margin="0,20">
-                        <UniformGrid Columns="2" Rows="2" Width="400" HorizontalAlignment="Left">
-                            <StackPanel Orientation="Horizontal" Margin="0,5">
-                                <TextBlock Text="风险等级" Foreground="#7886B2"/>
-                                <TextBlock FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0" 
-                                           Text="{Binding RiskLevel,Converter={StaticResource RiskLevelToTextConverter}}" 
-                                           Foreground="{Binding RiskLevel,Converter={StaticResource RiskLevelToColorConverter}}"/>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,5">
-                                <TextBlock Text="起升高度" Foreground="#7886B2"/>
-                                <TextBlock  Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="43,0">
-                                    <Run Text="{Binding LiftHeight,StringFormat=#0.0}"/>
-                                    <Run Text="m"/>
-                                </TextBlock>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,5">
-                                <TextBlock Text="损伤数量" Foreground="#7886B2"/>
-                                <TextBlock Text="{Binding DamageCount}" Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0" />
-                                <TextBlock Text="详情" Foreground="#7F74FF" FontSize="20" FontWeight="Regular" VerticalAlignment="Bottom" Cursor="Hand"                                           
-                                           Visibility="{Binding DamageCount,Converter={StaticResource CollectionCountToVisibilityConverter}}"
-                                           MouseLeftButtonDown="SecondEquipmentAlarm_MouseDown"/>
-                            </StackPanel>
-                            <StackPanel Orientation="Horizontal" Margin="0,5">
-                                <TextBlock Text="最大损伤值" Foreground="#7886B2"/>
-                                <TextBlock  Foreground="#FFFFFF" FontWeight="Bold"  FontFamily="{StaticResource PuHuiTiBold}" Margin="20,0">
-                                    <Run Text="{Binding MaxDamageValue,StringFormat=#0.0}"/>
-                                    <Run Text="%"/>
-                                </TextBlock>
-                            </StackPanel>
-                        </UniformGrid>
-                        <ctr:DamageRangeControl HorizontalAlignment="Stretch" Height="45" FontSize="16" Margin="100,35,0,0"
-                                      MildCount="{Binding MildCount}" LightCount="{Binding LightCount}"
-                                      ModerateCount="{Binding ModerateCount}" SevereCount="{Binding SevereCount}"
-                                      CriticalCount="{Binding CriticalCount}" ExtremeCount="{Binding ExtremeCount}"/>
-                    </DockPanel>
-                </StackPanel>
-                <ctr:RopeEquipmentControl Grid.RowSpan="2" Grid.Column="1" Grid.Row="0" Margin="0,0,0,-20"
-                                          PanelMargin="-80,40,0,0"
-                                          EquipmentTopPosition="160"
-                                          PanelNameFontSize="18"
-                                          PanelValueFontSize="20"
-                                          RopeImageSource="/Resources/rope_block.png" 
-                                          EquipmentImageSource="/Resources/equipment.png"
-                                          AnimationDuration="0.5" RopeHeight="435">
-                    <ctr:RopeEquipmentControl.Style>
-                        <Style TargetType="ctr:RopeEquipmentControl">
-                            <Style.Triggers>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Stoped}">
-                                    <Setter Property="IsAnimationRunning" Value="False"/>
-                                </DataTrigger>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Forward}">
-                                    <Setter Property="IsAnimationRunning" Value="True"/>
-                                    <Setter Property="IsAnimationReversed" Value="False"/>
-                                </DataTrigger>
-                                <DataTrigger Binding="{Binding Direction}" Value="{x:Static enum:DirectionState.Reverse}">
-                                    <Setter Property="IsAnimationRunning" Value="True"/>
-                                    <Setter Property="IsAnimationReversed" Value="True"/>
-                                </DataTrigger>
-                            </Style.Triggers>
-                        </Style>
-                    </ctr:RopeEquipmentControl.Style>
-                </ctr:RopeEquipmentControl>
-
-                <ctr:RealTimeLineChart x:Name="secondChartLine" Grid.Row="1"
-                               Margin="25,10,-70,0" 
-                               SensorCount="{Binding SensorCount}"
-                               SamplingStep="{Binding SamplingStep}"
-                               RopeLength="{Binding RopeLength}"/>
-                
-                <StackPanel Grid.Row="2" Orientation="Horizontal" Margin="40,-5,-100,5"
-                            Visibility="{Binding DebugMessage.Message,Converter={StaticResource StringEmptyToVisibilityConverter}}">
-                    <TextBlock Style="{StaticResource HeartbeatTextBlockStyle}"  FontSize="17" VerticalAlignment="Center" Foreground="#7F74FF"/>
-                    <TextBlock Text="{Binding  DebugMessage.DateTime,StringFormat=HH:mm:ss}" FontSize="16" Margin="5,0" VerticalAlignment="Center" Foreground="#7F74FF"/>
-                    <TextBlock Text="{Binding DebugMessage.Message}" FontSize="16" Margin="5,0" VerticalAlignment="Center" Foreground="#7F74FF"/>
-                </StackPanel>
-            </Grid>
-        </Border>
-        <ctr:DamageRecordControl Grid.Column="1" Grid.RowSpan="2" Margin="25,0,0,0" Records="{Binding Records}" Title="近期记录"/>
-    </Grid>
-</Page>

+ 0 - 109
Pages/RealTime/TwoRopesPage.xaml.cs

@@ -1,109 +0,0 @@
-using SWRIS.Events;
-using SWRIS.Extensions;
-using SWRIS.Models.ViewModel;
-using SWRIS.Repository;
-using System.Linq;
-using System.Threading.Tasks;
-using System.Windows.Controls;
-
-namespace SWRIS.Pages.RealTime
-{
-    /// <summary>
-    /// TwoRopesPage.xaml 的交互逻辑
-    /// </summary>
-    public partial class TwoRopesPage : Page
-    {
-        private readonly IRecordRepository _recordRepository;
-        public TwoRopesViewModel TwoRopesView { get; set; }
-        public TwoRopesPage()
-        {
-            InitializeComponent();
-            Loaded += OneRopePage_Loaded;
-            Unloaded += OneRopePage_Unloaded;
-            _recordRepository = new RecordRepository();
-            TwoRopesView = new TwoRopesViewModel
-            {
-                FirstEquipmentData = App.DataCenter.Equipments[0],
-                SecondEquipmentData = App.DataCenter.Equipments[1],
-                Records = _recordRepository.GetLastRecords().ToSimpleRecordDtos()
-            };
- 
-            TwoRopesView.FirstEquipment = App.Config.Equipments.First(c => c.IpAddress == TwoRopesView.FirstEquipmentData.IpAddress);
-            TwoRopesView.SecondEquipment = App.Config.Equipments.First(c => c.IpAddress == TwoRopesView.SecondEquipmentData.IpAddress);
-
-            DataContext = TwoRopesView;
-            App.TcpServer.LiveStreamReceived += TcpServer_LiveStreamReceived;
-
-        }
-
-        private void OneRopePage_Loaded(object sender, System.Windows.RoutedEventArgs e)
-        {
-            if (TwoRopesView.FirstEquipmentData.ChartSource != null)
-            {
-                firstChartLine.AddDataPoints(TwoRopesView.FirstEquipmentData.ChartSource);
-            }
-            if (TwoRopesView.SecondEquipmentData.ChartSource != null)
-            {
-                secondChartLine.AddDataPoints(TwoRopesView.SecondEquipmentData.ChartSource);
-            }
-        }
-
-        private void OneRopePage_Unloaded(object sender, System.Windows.RoutedEventArgs e)
-        {
-            TwoRopesView.FirstEquipmentData.ChartSource = firstChartLine.GetChartSource();
-            TwoRopesView.SecondEquipmentData.ChartSource = secondChartLine.GetChartSource();
-        }
-        private async void TcpServer_LiveStreamReceived(object sender, LiveStreamReceivedEventArgs e)
-        {
-            if (e.IpAddress == TwoRopesView.FirstEquipment.IpAddress)
-            {
-                if (TwoRopesView.FirstEquipmentData.DetectionData?.DetectionNo == TwoRopesView.FirstEquipmentData.DetectionNo)
-                {
-                    TwoRopesView.FirstEquipmentData.ClearData();
-                }
-                await Task.Run(() =>
-                {
-                    firstChartLine.AddDataPoints((TwoRopesView.FirstEquipmentData.AbsolutePosition, e.LiveStream), TwoRopesView.FirstEquipmentData.InUseSensor);
-                });
-            }
-            else if (e.IpAddress == TwoRopesView.SecondEquipmentData.IpAddress)
-            {
-                if (TwoRopesView.SecondEquipmentData.DetectionData?.DetectionNo == TwoRopesView.SecondEquipmentData.DetectionNo)
-                {
-                    TwoRopesView.SecondEquipmentData.ClearData();
-                }
-                await Task.Run(() =>
-                {
-                    secondChartLine.AddDataPoints((TwoRopesView.SecondEquipmentData.AbsolutePosition, e.LiveStream), TwoRopesView.SecondEquipmentData.InUseSensor);
-                });
-            }
-        }
-
-        private void FirstEquipmentAlarm_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
-        {
-            DamagesDialog damagesDialog = new DamagesDialog(TwoRopesView.FirstEquipmentData.Alarms);
-            damagesDialog.ShowDialog(true);
-        }
-
-        private void SecondEquipmentAlarm_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
-        {
-            DamagesDialog damagesDialog = new DamagesDialog(TwoRopesView.SecondEquipmentData.Alarms);
-            damagesDialog.ShowDialog(true);
-        }
-
-        private void FirstParameterSetting_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
-        {
-            ParameterDialog parameterDialog = new ParameterDialog(TwoRopesView.FirstEquipment,
-                                                                  TwoRopesView.FirstEquipmentData.ClientSocket,
-                                                                  TwoRopesView.FirstEquipmentData.RunningStatus);
-            parameterDialog.ShowDialog(true);
-        }
-        private void SecondParameterSetting_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
-        {
-            ParameterDialog parameterDialog = new ParameterDialog(TwoRopesView.SecondEquipment,
-                                                                  TwoRopesView.SecondEquipmentData.ClientSocket,
-                                                                  TwoRopesView.SecondEquipmentData.RunningStatus);
-            parameterDialog.ShowDialog(true);
-        }
-    }
-}

+ 3 - 2
Pages/RecordPage.xaml

@@ -13,6 +13,7 @@
     <Page.Resources>
         <cvt:RiskLevelToColorConverter x:Key="RiskLevelToColorConverter"/>
         <cvt:RiskLevelToTextConverter x:Key="RiskLevelToTextConverter"/>
+        <cvt:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
     </Page.Resources>
     <Grid  Margin="25,20,25,7">
         <Grid.RowDefinitions>
@@ -44,7 +45,7 @@
             <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="5,5">
                 <Button Content="生成报告" Margin="5,0" Height="35" FontSize="18" pu:ButtonHelper.CornerRadius="12" Click="Batch_Report"/>
                 <Button Content="批量删除" Margin="5,0" Height="35" FontSize="18" pu:ButtonHelper.CornerRadius="12" Click="Batch_Delete"/>
-                <Button Content="批量导出" Margin="5,0" Height="35" FontSize="18" pu:ButtonHelper.CornerRadius="12" Click="Batch_Export"/>
+                <Button Content="批量导出" Margin="5,0" Height="35" FontSize="18" pu:ButtonHelper.CornerRadius="12" Click="Batch_Export" Visibility="{Binding ViewModel.ShowExport, Converter={StaticResource BoolToVisibilityConverter}}"/>
             </StackPanel>
         </StackPanel>
         <StackPanel Grid.Row="1" Orientation="Vertical">
@@ -212,7 +213,7 @@
                                     <Button Content="导出" Background="Transparent" Tag="{Binding}" Foreground="#615CDD"  Width="80"
                                             Click="Export_Click" Margin="10,0,0,0" FontWeight="Medium"  FontFamily="{StaticResource PuHuiTiRegular}"
                                             VerticalAlignment="Center" pu:ButtonHelper.Icon="&#xe9E2;" pu:IconHelper.FontSize="17"
-                                            pu:IconHelper.FontFamily="{StaticResource PanuonIconFont}"/>
+                                            pu:IconHelper.FontFamily="{StaticResource PanuonIconFont}" Visibility="{Binding DataContext.ViewModel.ShowExport, RelativeSource={RelativeSource AncestorType=Page}, Converter={StaticResource BoolToVisibilityConverter}}"/>
                                 </StackPanel>
                             </DataTemplate>
                         </DataGridTemplateColumn.CellTemplate>

+ 179 - 26
Pages/RecordPage.xaml.cs

@@ -1,5 +1,8 @@
 using Microsoft.WindowsAPICodePack.Dialogs;
+using OfficeOpenXml;
+using OfficeOpenXml.Style;
 using Panuon.WPF.UI;
+using SWRIS.Core;
 using SWRIS.Dtos;
 using SWRIS.Extensions;
 using SWRIS.Models.ViewModel;
@@ -7,9 +10,10 @@ using SWRIS.Repository;
 using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
+using System.Drawing;
 using System.IO;
 using System.Linq;
-using System.Text;
+using System.Threading.Tasks;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Input;
@@ -30,6 +34,51 @@ namespace SWRIS.Pages
             BindTable();
             DataContext = this;
         }
+
+        // 在非 UI 线程上使用 Graphics 测量内容并设置列宽
+        private void AutoFitColumnsByGraphics(ExcelWorksheet ws, int totalColumns)
+        {
+            if (ws?.Dimension == null)
+                return;
+
+            int rows = ws.Dimension.End.Row;
+
+            // 使用 Bitmap/Graphics 在当前线程上进行测量(后台线程上也可安全使用)
+            using (var bmp = new Bitmap(1, 1))
+            using (var g = Graphics.FromImage(bmp))
+            {
+                // 选择一个常见的字体,EPPlus 默认字体可能不同,可根据需要调整
+                using (var measureFont = new Font("Arial", 10))
+                {
+                    for (int col = 1; col <= totalColumns; col++)
+                    {
+                        double maxPx = 0;
+                        for (int row = 1; row <= rows; row++)
+                        {
+                            var txt = ws.Cells[row, col].Text ?? string.Empty;
+                            if (string.IsNullOrEmpty(txt))
+                                continue;
+                            var sz = g.MeasureString(txt, measureFont);
+                            if (sz.Width > maxPx) maxPx = sz.Width;
+                        }
+
+                        // 像素宽度转 Excel 列宽的近似转换
+                        double excelWidth = PixelToExcelColumnWidth((int)Math.Ceiling(maxPx)) + 1.5; // 适度内边距
+                        if (excelWidth < 1) excelWidth = 1;
+                        ws.Column(col).Width = excelWidth;
+                    }
+                }
+            }
+        }
+
+        // 简单像素->Excel列宽近似转换
+        private double PixelToExcelColumnWidth(int px)
+        {
+            // 近似:Excel 列宽 = (像素 - 5) / 7 (基于常见字体度量),结果可根据需要调整
+            if (px <= 0) return 8;
+            return Math.Round((px - 5) / 7.0, 2);
+        }
+
         void BindTable()
         {
             var records = _recordRepository.GetRecords(ViewModel.SearchInput.RopeNumber,
@@ -70,8 +119,10 @@ namespace SWRIS.Pages
             }
             return ExportMultipleReports(records, reportPath);
         }
+
         private int Export(List<RecordDto> records)
         {
+            // 保留兼容性:同步导出入口仍然可用(内部调用异步路径)
             string reportPath = SelectExportFolder(initialDirectory: "钢丝绳检测数据");
             if (string.IsNullOrEmpty(reportPath))
             {
@@ -197,12 +248,27 @@ namespace SWRIS.Pages
             }
         }
 
-        private void Batch_Export(object sender, RoutedEventArgs e)
+
+        private async void Batch_Export(object sender, RoutedEventArgs e)
         {
             var records = ViewModel.Records.Where(c => c.IsSelected).ToList();
-            if (records.Any())
+            if (!records.Any())
+            {
+                MessageBoxX.Show(Application.Current.MainWindow, "请选择要导出的检测记录", "通知", MessageBoxButton.OK,
+                                 MessageBoxIcon.Info, DefaultButton.YesOK);
+                return;
+            }
+
+            string reportPath = SelectExportFolder(initialDirectory: "钢丝绳检测数据");
+            if (string.IsNullOrEmpty(reportPath))
+                return;
+
+            // 可在此禁用导出按钮或显示进度指示器
+            int exportCount = 0;
+            try
             {
-                int exportCount = Export(records);
+                exportCount = await Task.Run(() => ExportMultipleRecords(records, reportPath));
+
                 if (exportCount > 0)
                 {
                     Application.Current.Dispatcher.Invoke(() =>
@@ -211,13 +277,20 @@ namespace SWRIS.Pages
                     });
                 }
             }
-            else
+            catch (Exception ex)
             {
-                MessageBoxX.Show(Application.Current.MainWindow, "请选择要导出的检测记录", "通知", MessageBoxButton.OK,
-                                 MessageBoxIcon.Info, DefaultButton.YesOK);
+                Application.Current.Dispatcher.Invoke(() =>
+                {
+                    MessageBoxX.Show(Application.Current.MainWindow, $"导出失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxIcon.Error, DefaultButton.YesOK);
+                });
+            }
+            finally
+            {
+                // 恢复按钮/进度
             }
         }
 
+
         private string SelectExportFolder(string title = "选择文件夹", string initialDirectory = "钢丝绳检测报告")
         {
             using (var dialog = new CommonOpenFileDialog())
@@ -260,37 +333,117 @@ namespace SWRIS.Pages
             }
             return savedCount;
         }
+
         private int ExportMultipleRecords(List<RecordDto> records, string folderPath)
         {
             int exportedCount = 0;
-            string craneName = App.Config.Crane;
             foreach (var record in records)
             {
-                if (record != null && record.DataFilePath != null)
+                if (record == null || string.IsNullOrEmpty(record.DataFilePath))
+                    continue;
+
+                var damageData = DatFileHandler.ReadDatFile(record.DataFilePath);
+                if ((damageData?.Length ?? 0) <= 0)
+                    continue;
+
+                var parsedData = ParseSensorData(damageData, record.SensorCount, record.StartPoint, record.EndPoint);
+                if (parsedData.Positions == null || parsedData.Damages == null)
+                    continue;
+
+                string fileName = GenerateExportFileName(record);
+                string filePath = Path.Combine(folderPath, fileName);
+
+                try
                 {
-                    var damageData = DatFileHandler.ReadDatFile(record.DataFilePath);
-                    if ((damageData?.Length ?? 0) > 0)
+                    using (var package = new ExcelPackage())
                     {
-                        string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(record.DataFilePath);
-                        string directroyName = Path.Combine(folderPath, fileNameWithoutExtension);
-                        Directory.CreateDirectory(directroyName);
-                        string datFileName = Path.Combine(directroyName, "data.dat");
-                        File.WriteAllBytes(datFileName, damageData);
-                        string txtFileName = Path.Combine(directroyName, "info.txt");
-                        File.WriteAllText(txtFileName, $"钢丝绳:{record.RopeName}\r\n" +
-                            $"起始位置:{record.StartPoint}\r\n" +
-                            $"结束位置:{record.EndPoint}\r\n" +
-                            $"检测长度:{record.DetectionLength}\r\n" +
-                            $"损伤数量:{record.DamageCount}处\r\n" +
-                            $"-------------------------------------------\r\n" +
-                            $"{string.Join("", record.Damages.Select(c => $"位置:{c.DamagePoint},损伤值:{c.DamageValue}\r\n"))}", Encoding.UTF8);
-
-                        exportedCount++;
+                        var ws = package.Workbook.Worksheets.Add("检测数据");
+
+                        // 设置表头
+                        ws.Cells[1, 1].Value = "位置(m)";
+                        for (int i = 0; i < record.SensorCount; i++)
+                        {
+                            ws.Cells[1, i + 2].Value = $"传感器{i + 1}";
+                        }
+                        ws.Cells[1, record.SensorCount + 2].Value = "均值";
+
+                        // 设置表头样式
+                        using (var headerRange = ws.Cells[1, 1, 1, record.SensorCount + 2])
+                        {
+                            headerRange.Style.Font.Bold = true;
+                            headerRange.Style.Fill.PatternType = ExcelFillStyle.Solid;
+                            headerRange.Style.Fill.BackgroundColor.SetColor(Color.LightGray);
+                        }
+
+                        // 填充数据行
+                        int sampleCount = parsedData.Positions.Length;
+                        for (int row = 0; row < sampleCount; row++)
+                        {
+                            int excelRow = row + 2;
+                            ws.Cells[excelRow, 1].Value = Math.Round(parsedData.Positions[row], 3);
+
+                            double sum = 0;
+                            for (int col = 0; col < record.SensorCount; col++)
+                            {
+                                ushort value = parsedData.Damages[col, row];
+                                ws.Cells[excelRow, col + 2].Value = value;
+                                sum += value;
+                            }
+                            ws.Cells[excelRow, record.SensorCount + 2].Value = Math.Round(sum / record.SensorCount, 1);
+                        }
+
+                        // 自动调整列宽:在后台线程上使用 Graphics.MeasureString 测量并手动设置列宽,避免调用可能依赖 UI 的 AutoFit
+                        AutoFitColumnsByGraphics(ws, record.SensorCount + 2);
+
+                        package.SaveAs(new FileInfo(filePath));
                     }
+
+                    exportedCount++;
+                    _recordRepository.AddRecordExportCount(record.Id.Value);
+                }
+                catch (Exception ex)
+                {
+                    LogHelper.Error($"导出记录 {record.Id} 到Excel失败: {ex.Message}", ex);
                 }
             }
             return exportedCount;
         }
+
+        public (double[] Positions, ushort[,] Damages) ParseSensorData(byte[] data, int sensorCount, double startPosition, double endPosition)
+        {
+            if (data.Length % sensorCount != 0)
+            {
+                LogHelper.Error($"数据长度不匹配。数据长度 {data.Length} 字节不能被传感器数量 {sensorCount} 整除。");
+                return (null, null);
+            }
+
+            int sampleCount = data.Length / sensorCount;
+            double samplingStep = (endPosition - startPosition) / (sampleCount - 1);
+
+            var positions = new double[sampleCount];
+            var damages = new ushort[sensorCount, sampleCount];
+
+            for (int sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
+            {
+                positions[sampleIndex] = startPosition;
+                for (int sensorIndex = 0; sensorIndex < sensorCount; sensorIndex++)
+                {
+                    int dataIndex = (sampleIndex * sensorCount) + sensorIndex;
+                    damages[sensorIndex, sampleIndex] = data[dataIndex];
+                }
+                startPosition += samplingStep;
+            }
+
+            return (positions, damages);
+        }
+
+        private string GenerateExportFileName(RecordDto record)
+        {
+            string ropeName = CleanFileName(record.RopeName);
+            string time = record.EndTime.ToString("yyyyMMdd_HHmmss");
+            return $"{ropeName}_{time}_检测数据.xlsx";
+        }
+
         private string GetDefaultExportDirectory(string folderName = "钢丝绳检测报告")
         {
             string docsPath = Path.Combine(

+ 255 - 231
Pages/SettingDialog.xaml

@@ -4,12 +4,12 @@
       xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
       xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
       xmlns:local="clr-namespace:SWRIS.Pages" 
-      mc:Ignorable="d" Foreground="#FFFFFF" 
+      mc:Ignorable="d" Foreground="#FFFFFF"
       xmlns:pu="https://opensource.panuon.com/wpf-ui"
-      xmlns:cvt="clr-namespace:SWRIS.Converters" AllowsTransparency="True"
+      xmlns:cvt="clr-namespace:SWRIS.Converters" 
       xmlns:vm="clr-namespace:SWRIS.Models.ViewModel"
-      MinHeight="750"  Width="1100" WindowStyle="None" 
-      ResizeMode="NoResize" FontSize="18" Background="#141332"
+      WindowStyle="None" AllowsTransparency="True"
+      ResizeMode="CanResize" FontSize="18" Background="Transparent"
       FontFamily="{StaticResource PuHuiTiRegular}"
       Title="系统设置" pu:WindowXCaption.Height="0" 
       WindowStartupLocation="CenterScreen" IsEscEnabled="True" >
@@ -23,268 +23,292 @@
         <cvt:ModuleTypeToVisibilityConverter x:Key="ModuleTypeToVisibilityConverter"/>
         <cvt:LimitSwitchNumberToBooleanConverter x:Key="LimitSwitchNumberToBooleanConverter"/>
     </pu:WindowX.Resources>
-    <Border BorderThickness="2" BorderBrush="#3B3B7B" CornerRadius="10">
-        <Grid Margin="20">
-            <Grid.RowDefinitions>
-                <RowDefinition Height="60"/>
-                <RowDefinition Height="*"/>
-                <RowDefinition Height="90"/>
-            </Grid.RowDefinitions>
-            <Grid.ColumnDefinitions>
-                <ColumnDefinition Width="400"/>
-                <ColumnDefinition Width="1.5"/>
-                <ColumnDefinition Width="*"/>
-            </Grid.ColumnDefinitions>
-            <TextBlock Text="系统设置" Grid.Row="0" Grid.ColumnSpan="3" HorizontalAlignment="Center" FontSize="36" FontWeight="Bold"/>
-            <StackPanel Orientation="Vertical" Grid.Row="1" Grid.Column="0" Margin="35,0">
-                <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
-                    <Rectangle Height="2" Width="110" Stroke="#3B3B7B"/>
-                    <TextBlock Text="基本参数" Foreground="#615CDD" FontSize="24" Margin="15" FontWeight="Regular"/>
-                    <Rectangle Height="2" Width="110" Stroke="#3B3B7B"/>
-                </StackPanel>
-                <StackPanel Orientation="Vertical" HorizontalAlignment="Center">
-                    <StackPanel Orientation="Horizontal">
-                        <StackPanel Orientation="Vertical">
-                            <TextBlock Text="设备名称" Foreground="#7886B2" FontWeight="Regular"/>
-                            <TextBox Margin="0,10" Text="{Binding Crane}" Width="247"/>
+    <Grid>
+        <!-- 使用 Viewbox 包裹所有内容 -->
+        <Viewbox Stretch="Uniform">
+            <Border BorderThickness="2" 
+                    BorderBrush="#3B3B7B"
+                    CornerRadius="10" 
+                    Background="#141332" Width="1100" Height="760">
+                <Grid Margin="20">
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="60"/>
+                        <RowDefinition Height="*"/>
+                        <RowDefinition Height="90"/>
+                    </Grid.RowDefinitions>
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="400"/>
+                        <ColumnDefinition Width="1.5"/>
+                        <ColumnDefinition Width="*"/>
+                    </Grid.ColumnDefinitions>
+                    <TextBlock Text="系统设置" Grid.Row="0" Grid.ColumnSpan="3" HorizontalAlignment="Center" FontSize="36" FontWeight="Bold"/>
+                    <StackPanel Orientation="Vertical" Grid.Row="1" Grid.Column="0" Margin="35,0">
+                        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
+                            <Rectangle Height="2" Width="110" Stroke="#3B3B7B"/>
+                            <TextBlock Text="基本参数" Foreground="#615CDD" FontSize="24" Margin="15" FontWeight="Regular"/>
+                            <Rectangle Height="2" Width="110" Stroke="#3B3B7B"/>
                         </StackPanel>
-                    </StackPanel>
-                    <StackPanel Orientation="Horizontal">
-                        <StackPanel Orientation="Vertical" Margin="0,0,27,0">
-                            <TextBlock Text="服务地址" Foreground="#7886B2" FontWeight="Regular"/>
-                            <TextBox Margin="0,10" Text="{Binding IpAddress}"/>
-                        </StackPanel>
-                        <StackPanel Orientation="Vertical">
-                            <TextBlock Text="端口号" Foreground="#7886B2" FontWeight="Regular"/>
-                            <TextBox Margin="0,10" Text="{Binding Port}" Width="70"/>
-                        </StackPanel>
-                    </StackPanel>
-                    <StackPanel Orientation="Horizontal">
-                        <StackPanel Orientation="Vertical" Margin="0,0,27,0">
-                            <TextBlock Text="数据清理时间" Foreground="#7886B2" FontWeight="Regular"/>
-                            <TextBox Margin="0,10" Text="{Binding CleanScheduledTime}" Width="150"/>
-                        </StackPanel>
-                        <StackPanel Orientation="Vertical">
-                            <TextBlock Text="存储时长" Foreground="#7886B2" FontWeight="Regular"/>
-                            <StackPanel Orientation="Horizontal" Margin="0,10" >
-                                <TextBox Text="{Binding DataSaveDays}" Width="70"/>
-                                <TextBlock  VerticalAlignment="Center" Margin="5" Text="天"/>
-                            </StackPanel>
-                        </StackPanel>
-                    </StackPanel>
-                    <StackPanel Orientation="Horizontal">
-                        <StackPanel Orientation="Vertical" Margin="0,0,27,0">
-                            <TextBlock Text="语音报警级别" Foreground="#7886B2" FontWeight="Regular"/>
-                            <ComboBox Margin="0,10" Width="150" ItemsSource="{Binding RiskLevels}"
-                                      SelectedValue="{Binding SoundRiskLevel}" 
-                                      DisplayMemberPath="Value" SelectedValuePath="Key"/>
-                        </StackPanel>
-                        <StackPanel Orientation="Vertical">
-                            <TextBlock Text="速度波动阈值" Foreground="#7886B2" FontWeight="Regular"/>
-                            <StackPanel Orientation="Horizontal" Margin="0,10">
-                                <TextBox Text="{Binding SpeedVariationThreshold,Converter={StaticResource PercentageConverter}}" Width="70"/>
-                                <TextBlock Text="%" Margin="5,0" VerticalAlignment="Center"/>
-                            </StackPanel>
-                        </StackPanel>
-                    </StackPanel>
-                    <StackPanel Orientation="Horizontal">
-                        <StackPanel Orientation="Vertical" Margin="0,0,22,0">
-                            <TextBlock Text="损伤容差范围" Foreground="#7886B2" FontWeight="Regular"/>
-                            <StackPanel Orientation="Horizontal" Margin="0,10">
-                                <TextBox Text="{Binding DamageExtent}" Width="125"/>
-                                <TextBlock Text="米" Margin="5,0" VerticalAlignment="Center"/>
-                            </StackPanel>
-                        </StackPanel>
-                        <StackPanel Orientation="Vertical">
-                            <TextBlock Text="损伤确认频次" Foreground="#7886B2" FontWeight="Regular"/>
-                            <StackPanel Orientation="Horizontal" Margin="0,10">
-                                <TextBox Text="{Binding AlarmValidCount}" Width="70"/>
-                                <TextBlock Text="次" Margin="5,0" VerticalAlignment="Center"/>
-                            </StackPanel>
-                        </StackPanel>
-                    </StackPanel>
-                </StackPanel>
-            </StackPanel>
-            <Rectangle Grid.Row="1" Grid.Column="1">
-                <Rectangle.Fill>
-                    <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
-                        <GradientStop Offset="0" Color="Transparent"/>
-                        <GradientStop Offset="0.5" Color="#3B3B7B"/>
-                        <GradientStop Offset="1" Color="Transparent"/>
-                    </LinearGradientBrush>
-                </Rectangle.Fill>
-            </Rectangle>
-            <StackPanel Orientation="Vertical" Grid.Row="1" Grid.Column="2">
-                <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
-                    <Rectangle Height="2" Width="250" Stroke="#3B3B7B"/>
-                    <TextBlock Text="标定" Foreground="#615CDD" FontSize="24" Margin="15" FontWeight="Regular"/>
-                    <Rectangle Height="2" Width="250" Stroke="#3B3B7B"/>
-                </StackPanel>
-                <StackPanel Orientation="Vertical" HorizontalAlignment="Center" DataContext="{Binding Calibration}">
-                    <StackPanel Orientation="Vertical">
-                        <TextBlock Text="类型" Foreground="#7886B2" FontWeight="Regular"/>
-                        <ComboBox Margin="0,10" Width="320" ItemsSource="{Binding ModuleTypes}"
-                                  SelectedValue="{Binding ModuleType}" 
-                                  HorizontalAlignment="Left"
-                                  DisplayMemberPath="Value" SelectedValuePath="Key"/>
-                    </StackPanel>
-                    <StackPanel Orientation="Vertical" 
-                                Visibility="{Binding ModuleType,Converter={StaticResource ModuleTypeToVisibilityConverter},ConverterParameter=Tcp}">
-                        <StackPanel Orientation="Horizontal" DataContext="{Binding Tcp}">
-                            <StackPanel Orientation="Vertical" Margin="0,0,20,0">
-                                <TextBlock Text="设备地址" Foreground="#7886B2" FontWeight="Regular"/>
-                                <TextBox Margin="0,10" Text="{Binding IpAddress}" Width="150"/>
-                            </StackPanel>
-                            <StackPanel Orientation="Vertical" Margin="0,0,20,0">
-                                <TextBlock Text="端口号" Foreground="#7886B2" FontWeight="Regular"/>
-                                <TextBox Margin="0,10" Text="{Binding Port}" Width="75"/>
+                        <StackPanel Orientation="Vertical" HorizontalAlignment="Center">
+                            <StackPanel Orientation="Horizontal">
+                                <StackPanel Orientation="Vertical">
+                                    <TextBlock Text="设备名称" Foreground="#7886B2" FontWeight="Regular"/>
+                                    <TextBox Margin="0,10" Text="{Binding Crane}" Width="247"/>
+                                </StackPanel>
                             </StackPanel>
-                            <StackPanel Orientation="Vertical">
-                                <TextBlock Text="站号" Foreground="#7886B2" FontWeight="Regular"/>
-                                <TextBox Margin="0,10" Text="{Binding Station}" Width="55"/>
+                            <StackPanel Orientation="Horizontal">
+                                <StackPanel Orientation="Vertical" Margin="0,0,27,0">
+                                    <TextBlock Text="服务地址" Foreground="#7886B2" FontWeight="Regular"/>
+                                    <TextBox Margin="0,10" Text="{Binding IpAddress}"/>
+                                </StackPanel>
+                                <StackPanel Orientation="Vertical">
+                                    <TextBlock Text="端口号" Foreground="#7886B2" FontWeight="Regular"/>
+                                    <TextBox Margin="0,10" Text="{Binding Port}" Width="70"/>
+                                </StackPanel>
                             </StackPanel>
-                        </StackPanel>
-                    </StackPanel>
-                    <StackPanel Orientation="Vertical" 
-                                Visibility="{Binding ModuleType,Converter={StaticResource ModuleTypeToVisibilityConverter},ConverterParameter=Serial}">
-                        <StackPanel Orientation="Vertical" DataContext="{Binding Serial}">
                             <StackPanel Orientation="Horizontal">
-                                <StackPanel Orientation="Vertical" Margin="0,0,20,0">
-                                    <TextBlock Text="串口名称" Foreground="#7886B2" FontWeight="Regular"/>
-                                    <ComboBox ItemsSource="{Binding Path=SerialNames}" Margin="0,10" SelectedValue="{Binding PortName}" Width="150"/>
+                                <StackPanel Orientation="Vertical" Margin="0,0,27,0">
+                                    <TextBlock Text="数据清理时间" Foreground="#7886B2" FontWeight="Regular"/>
+                                    <TextBox Margin="0,10" Text="{Binding CleanScheduledTime}" Width="150"/>
                                 </StackPanel>
                                 <StackPanel Orientation="Vertical">
-                                    <TextBlock Text="波特率" Foreground="#7886B2" FontWeight="Regular"/>
-                                    <ComboBox Margin="0,10" SelectedValue="{Binding BaudRate}"
-                                          SelectedValuePath="Content" Width="150">
-                                        <ComboBoxItem Content="1200"/>
-                                        <ComboBoxItem Content="4800"/>
-                                        <ComboBoxItem Content="9600"/>
-                                        <ComboBoxItem Content="19200"/>
-                                        <ComboBoxItem Content="38400"/>
-                                        <ComboBoxItem Content="57600"/>
-                                        <ComboBoxItem Content="115200"/>
-                                    </ComboBox>
+                                    <TextBlock Text="存储时长" Foreground="#7886B2" FontWeight="Regular"/>
+                                    <StackPanel Orientation="Horizontal" Margin="0,10" >
+                                        <TextBox Text="{Binding DataSaveDays}" Width="70"/>
+                                        <TextBlock  VerticalAlignment="Center" Margin="5" Text="天"/>
+                                    </StackPanel>
                                 </StackPanel>
                             </StackPanel>
                             <StackPanel Orientation="Horizontal">
-                                <StackPanel Orientation="Vertical" Margin="0,0,20,0">
-                                    <TextBlock Text="数据位" Foreground="#7886B2" FontWeight="Regular"/>
-                                    <ComboBox Margin="0,10" SelectedValue="{Binding DataBits}"
-                                              SelectedValuePath="Content" Width="63">
-                                        <ComboBoxItem Content="7"/>
-                                        <ComboBoxItem Content="8"/>
-                                    </ComboBox>
+                                <StackPanel Orientation="Vertical" Margin="0,0,27,0">
+                                    <TextBlock Text="语音报警级别" Foreground="#7886B2" FontWeight="Regular"/>
+                                    <ComboBox Margin="0,10" Width="150" ItemsSource="{Binding RiskLevels}"
+                                          SelectedValue="{Binding SoundRiskLevel}" 
+                                          DisplayMemberPath="Value" SelectedValuePath="Key"/>
                                 </StackPanel>
-                                <StackPanel Orientation="Vertical" Margin="0,0,20,0">
-                                    <TextBlock Text="校验位" Foreground="#7886B2" FontWeight="Regular"/>
-                                    <ComboBox Margin="0,10" SelectedValue="{Binding Parity}" 
-                                              SelectedValuePath="Content" Width="100">
-                                        <ComboBoxItem Content="None"/>
-                                        <ComboBoxItem Content="Odd"/>
-                                        <ComboBoxItem Content="Even"/>
-                                    </ComboBox>
+                                <StackPanel Orientation="Vertical">
+                                    <TextBlock Text="速度波动阈值" Foreground="#7886B2" FontWeight="Regular"/>
+                                    <StackPanel Orientation="Horizontal" Margin="0,10">
+                                        <TextBox Text="{Binding SpeedVariationThreshold,Converter={StaticResource PercentageConverter}}" Width="70"/>
+                                        <TextBlock Text="%" Margin="5,0" VerticalAlignment="Center"/>
+                                    </StackPanel>
                                 </StackPanel>
-                                <StackPanel Orientation="Vertical" Margin="0,0,20,0">
-                                    <TextBlock Text="停止位" Foreground="#7886B2" FontWeight="Regular"/>
-                                    <ComboBox Margin="0,10" SelectedValue="{Binding StopBits}" 
-                                              SelectedValuePath="Content" Width="63">
-                                        <ComboBoxItem Content="1"/>
-                                        <ComboBoxItem Content="2"/>
-                                    </ComboBox>
+                            </StackPanel>
+                            <StackPanel Orientation="Horizontal">
+                                <StackPanel Orientation="Vertical" Margin="0,0,22,0">
+                                    <TextBlock Text="损伤容差范围" Foreground="#7886B2" FontWeight="Regular"/>
+                                    <StackPanel Orientation="Horizontal" Margin="0,10">
+                                        <TextBox Text="{Binding DamageExtent}" Width="125"/>
+                                        <TextBlock Text="米" Margin="5,0" VerticalAlignment="Center"/>
+                                    </StackPanel>
                                 </StackPanel>
                                 <StackPanel Orientation="Vertical">
-                                    <TextBlock Text="站号" Foreground="#7886B2" FontWeight="Regular"/>
-                                    <TextBox Margin="0,10" Text="{Binding Station}" Width="35"/>
+                                    <TextBlock Text="损伤确认频次" Foreground="#7886B2" FontWeight="Regular"/>
+                                    <StackPanel Orientation="Horizontal" Margin="0,10">
+                                        <TextBox Text="{Binding AlarmValidCount}" Width="70"/>
+                                        <TextBlock Text="次" Margin="5,0" VerticalAlignment="Center"/>
+                                    </StackPanel>
                                 </StackPanel>
                             </StackPanel>
                         </StackPanel>
                     </StackPanel>
-                    <Rectangle Width="600" Height="1.5" Margin="-200,10,-200,5">
+                    <Rectangle Grid.Row="1" Grid.Column="1">
                         <Rectangle.Fill>
-                            <LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
+                            <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
                                 <GradientStop Offset="0" Color="Transparent"/>
                                 <GradientStop Offset="0.5" Color="#3B3B7B"/>
                                 <GradientStop Offset="1" Color="Transparent"/>
                             </LinearGradientBrush>
                         </Rectangle.Fill>
                     </Rectangle>
-                    <ItemsControl ItemsSource="{Binding Limits}" Margin="0,10">
-                        <ItemsControl.ItemsPanel>
-                            <ItemsPanelTemplate>
-                                <StackPanel Orientation="Vertical"/>
-                            </ItemsPanelTemplate>
-                        </ItemsControl.ItemsPanel>
-                        <ItemsControl.ItemTemplate>
-                            <DataTemplate>
-                                <StackPanel Orientation="Horizontal" >
-                                    <CheckBox IsChecked="{Binding IsEnable}" ToolTip="是否启用" Margin="0,25,5,0" BorderThickness="1.6"
-                                              pu:CheckBoxHelper.GlyphThickness="2"/>
-                                    <StackPanel Orientation="Horizontal" IsEnabled="{Binding IsEnable}">
+                    <StackPanel Orientation="Vertical" Grid.Row="1" Grid.Column="2">
+                        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
+                            <Rectangle Height="2" Width="250" Stroke="#3B3B7B"/>
+                            <TextBlock Text="标定" Foreground="#615CDD" FontSize="24" Margin="15" FontWeight="Regular"/>
+                            <Rectangle Height="2" Width="250" Stroke="#3B3B7B"/>
+                        </StackPanel>
+                        <StackPanel Orientation="Vertical" DataContext="{Binding Calibration}" Margin="50,0">
+                            <StackPanel Orientation="Vertical">
+                                <TextBlock Text="类型" Foreground="#7886B2" FontWeight="Regular"/>
+                                <ComboBox Margin="0,10" Width="320" ItemsSource="{Binding ModuleTypes}"
+                                      SelectedValue="{Binding ModuleType}" 
+                                      HorizontalAlignment="Left"
+                                      DisplayMemberPath="Value" SelectedValuePath="Key"/>
+                            </StackPanel>
+                            <StackPanel Orientation="Vertical" 
+                                    Visibility="{Binding ModuleType,Converter={StaticResource ModuleTypeToVisibilityConverter},ConverterParameter=Tcp}">
+                                <StackPanel Orientation="Horizontal" DataContext="{Binding Tcp}">
+                                    <StackPanel Orientation="Vertical" Margin="0,0,20,0">
+                                        <TextBlock Text="设备地址" Foreground="#7886B2" FontWeight="Regular"/>
+                                        <TextBox Margin="0,10" Text="{Binding IpAddress}" Width="150"/>
+                                    </StackPanel>
+                                    <StackPanel Orientation="Vertical" Margin="0,0,20,0">
+                                        <TextBlock Text="端口号" Foreground="#7886B2" FontWeight="Regular"/>
+                                        <TextBox Margin="0,10" Text="{Binding Port}" Width="75"/>
+                                    </StackPanel>
+                                    <StackPanel Orientation="Vertical">
+                                        <TextBlock Text="站号" Foreground="#7886B2" FontWeight="Regular"/>
+                                        <TextBox Margin="0,10" Text="{Binding Station}" Width="55"/>
+                                    </StackPanel>
+                                </StackPanel>
+                            </StackPanel>
+                            <StackPanel Orientation="Vertical" 
+                                    Visibility="{Binding ModuleType,Converter={StaticResource ModuleTypeToVisibilityConverter},ConverterParameter=Serial}">
+                                <StackPanel Orientation="Vertical" DataContext="{Binding Serial}">
+                                    <StackPanel Orientation="Horizontal">
                                         <StackPanel Orientation="Vertical" Margin="0,0,20,0">
-                                            <StackPanel Orientation="Horizontal">
-                                            <TextBlock Text="限位" Margin="0,0,5,0" Foreground="#7886B2" FontWeight="Regular"/>
-                                                <CheckBox Content="1" Padding="2,0" Margin="5,0"
-                                                          IsChecked="{Binding SwitchIndex, Converter={StaticResource LimitSwitchNumberToBooleanConverter}, 
-                                                                         ConverterParameter=1}"/>
-                                                <CheckBox Content="2" Padding="2,0" 
-                                                          IsChecked="{Binding SwitchIndex, Converter={StaticResource LimitSwitchNumberToBooleanConverter},
-                                                                         ConverterParameter=2}"/>
-                                            </StackPanel>
-                                            <TextBox Margin="0,10" Text="{Binding Name}" Width="120"/>
+                                            <TextBlock Text="串口名称" Foreground="#7886B2" FontWeight="Regular"/>
+                                            <ComboBox ItemsSource="{Binding Path=SerialNames}" Margin="0,10" SelectedValue="{Binding PortName}" Width="150"/>
                                         </StackPanel>
-                                        <StackPanel Orientation="Vertical" Margin="0,0,40,0">
-                                            <TextBlock Text="地址" Foreground="#7886B2" FontWeight="Regular"/>
-                                            <Grid Margin="0,10">
-                                                <TextBox Text="{Binding Address}" Width="80" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
-                                                <CheckBox Content="反" ToolTip="取反" IsChecked="{Binding IsReverse}" HorizontalAlignment="Right" 
-                                                          VerticalAlignment="Top"
-                                                          Foreground="LightGreen" BorderBrush="LightGreen"
-                                                          Margin="0,0,-30,0" Width="35" Height="22" FontSize="15" Padding="1"
-                                                          pu:CheckBoxHelper.CheckedGlyphBrush="OrangeRed"
-                                                          pu:CheckBoxHelper.CheckedForeground="OrangeRed"
-                                                          pu:CheckBoxHelper.CheckedBorderBrush="OrangeRed">
-                                                    <CheckBox.LayoutTransform>
-                                                        <ScaleTransform ScaleX="0.8" ScaleY="0.8"/>
-                                                    </CheckBox.LayoutTransform>
-                                                </CheckBox>
-                                            </Grid>
+                                        <StackPanel Orientation="Vertical">
+                                            <TextBlock Text="波特率" Foreground="#7886B2" FontWeight="Regular"/>
+                                            <ComboBox Margin="0,10" SelectedValue="{Binding BaudRate}"
+                                              SelectedValuePath="Content" Width="150">
+                                                <ComboBoxItem Content="1200"/>
+                                                <ComboBoxItem Content="4800"/>
+                                                <ComboBoxItem Content="9600"/>
+                                                <ComboBoxItem Content="19200"/>
+                                                <ComboBoxItem Content="38400"/>
+                                                <ComboBoxItem Content="57600"/>
+                                                <ComboBoxItem Content="115200"/>
+                                            </ComboBox>
+                                        </StackPanel>
+                                    </StackPanel>
+                                    <StackPanel Orientation="Horizontal">
+                                        <StackPanel Orientation="Vertical" Margin="0,0,20,0">
+                                            <TextBlock Text="数据位" Foreground="#7886B2" FontWeight="Regular"/>
+                                            <ComboBox Margin="0,10" SelectedValue="{Binding DataBits}"
+                                                  SelectedValuePath="Content" Width="63">
+                                                <ComboBoxItem Content="7"/>
+                                                <ComboBoxItem Content="8"/>
+                                            </ComboBox>
+                                        </StackPanel>
+                                        <StackPanel Orientation="Vertical" Margin="0,0,20,0">
+                                            <TextBlock Text="校验位" Foreground="#7886B2" FontWeight="Regular"/>
+                                            <ComboBox Margin="0,10" SelectedValue="{Binding Parity}" 
+                                                  SelectedValuePath="Content" Width="100">
+                                                <ComboBoxItem Content="None"/>
+                                                <ComboBoxItem Content="Odd"/>
+                                                <ComboBoxItem Content="Even"/>
+                                            </ComboBox>
+                                        </StackPanel>
+                                        <StackPanel Orientation="Vertical" Margin="0,0,20,0">
+                                            <TextBlock Text="停止位" Foreground="#7886B2" FontWeight="Regular"/>
+                                            <ComboBox Margin="0,10" SelectedValue="{Binding StopBits}" 
+                                                  SelectedValuePath="Content" Width="63">
+                                                <ComboBoxItem Content="1"/>
+                                                <ComboBoxItem Content="2"/>
+                                            </ComboBox>
                                         </StackPanel>
                                         <StackPanel Orientation="Vertical">
-                                            <TextBlock Text="高度" Foreground="#7886B2" FontWeight="Regular"/>
-                                            <StackPanel Orientation="Horizontal">
-                                                <TextBox Margin="0,10" Text="{Binding Position}" Width="65"/>
-                                                <TextBlock Text="米" Margin="5,0" VerticalAlignment="Center"/>
-                                            </StackPanel>
+                                            <TextBlock Text="站号" Foreground="#7886B2" FontWeight="Regular"/>
+                                            <TextBox Margin="0,10" Text="{Binding Station}" Width="35"/>
                                         </StackPanel>
-                                        <ItemsControl ItemsSource="{Binding RelatedRopes}" Margin="10,0,0,0">
+                                    </StackPanel>
+                                </StackPanel>
+                            </StackPanel>
+                            <Rectangle Width="600" Height="1.5" Margin="-200,10,-200,5">
+                                <Rectangle.Fill>
+                                    <LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
+                                        <GradientStop Offset="0" Color="Transparent"/>
+                                        <GradientStop Offset="0.5" Color="#3B3B7B"/>
+                                        <GradientStop Offset="1" Color="Transparent"/>
+                                    </LinearGradientBrush>
+                                </Rectangle.Fill>
+                            </Rectangle>
+                            <TabControl ItemsSource="{Binding Trolleys}" SelectedIndex="0">
+                                <TabControl.ItemContainerStyle>
+                                    <Style TargetType="TabItem" BasedOn="{StaticResource {x:Type TabItem}}">
+                                        <Setter Property="Header" Value="{Binding TrolleyName}" />
+                                    </Style>
+                                </TabControl.ItemContainerStyle>
+                                <TabControl.ContentTemplate>
+                                    <DataTemplate>
+                                        <ItemsControl ItemsSource="{Binding Limits}" Margin="0,20,0,0">
                                             <ItemsControl.ItemsPanel>
                                                 <ItemsPanelTemplate>
-                                                    <StackPanel Orientation="Vertical" VerticalAlignment="Bottom" Margin="0,5"/>
+                                                    <StackPanel Orientation="Vertical"/>
                                                 </ItemsPanelTemplate>
                                             </ItemsControl.ItemsPanel>
                                             <ItemsControl.ItemTemplate>
                                                 <DataTemplate>
-                                                    <CheckBox Content="{Binding RopeName}" Tag="{Binding RopeNumber}" IsChecked="{Binding IsSelected}"/>
+                                                    <StackPanel Orientation="Horizontal" VerticalAlignment="Center" Height="110" Margin="0,0,0,15">
+                                                        <CheckBox IsChecked="{Binding IsEnable}" ToolTip="是否启用"
+                                                                  BorderThickness="1.6" pu:CheckBoxHelper.GlyphThickness="2"/>
+                                                        <StackPanel Orientation="Horizontal" IsEnabled="{Binding IsEnable}">
+                                                            <StackPanel Orientation="Vertical" Margin="0,0,20,0">
+                                                                <StackPanel Orientation="Horizontal">
+                                                                    <TextBlock Text="限位" Margin="0,0,5,0" Foreground="#7886B2" FontWeight="Regular"/>
+                                                                    <CheckBox Content="1" Padding="2,0" Margin="5,0"
+                                                                              IsChecked="{Binding SwitchIndex, Converter={StaticResource LimitSwitchNumberToBooleanConverter}, 
+                                                                        ConverterParameter=1}"/>
+                                                                    <CheckBox Content="2" Padding="2,0" 
+                                                                              IsChecked="{Binding SwitchIndex, Converter={StaticResource LimitSwitchNumberToBooleanConverter},
+                                                                        ConverterParameter=2}"/>
+                                                                </StackPanel>
+                                                                <TextBox Margin="0,10" Text="{Binding Name}" Width="120"/>
+                                                            </StackPanel>
+                                                            <StackPanel Orientation="Vertical" Margin="0,0,40,0">
+                                                                <TextBlock Text="地址" Foreground="#7886B2" FontWeight="Regular"/>
+                                                                <Grid Margin="0,10">
+                                                                    <TextBox Text="{Binding Address}" Width="80" VerticalAlignment="Stretch" 
+                                                                             HorizontalAlignment="Stretch"/>
+                                                                    <CheckBox Content="反" ToolTip="取反" 
+                                                                              IsChecked="{Binding IsReverse}" 
+                                                                              HorizontalAlignment="Right" 
+                                                                              VerticalAlignment="Top"
+                                                                              Foreground="LightGreen" BorderBrush="LightGreen"
+                                                                              Margin="0,0,-30,0" Width="35" 
+                                                                              Height="22" FontSize="15" Padding="1"
+                                                                              pu:CheckBoxHelper.CheckedGlyphBrush="OrangeRed"
+                                                                              pu:CheckBoxHelper.CheckedForeground="OrangeRed"
+                                                                              pu:CheckBoxHelper.CheckedBorderBrush="OrangeRed">
+                                                                        <CheckBox.LayoutTransform>
+                                                                            <ScaleTransform ScaleX="0.8" ScaleY="0.8"/>
+                                                                        </CheckBox.LayoutTransform>
+                                                                    </CheckBox>
+                                                                </Grid>
+                                                            </StackPanel>
+                                                            <StackPanel Orientation="Vertical">
+                                                                <TextBlock Text="高度" Foreground="#7886B2" FontWeight="Regular"/>
+                                                                <StackPanel Orientation="Horizontal">
+                                                                    <TextBox Margin="0,10" Text="{Binding Position}" Width="65"/>
+                                                                    <TextBlock Text="米" Margin="5,0" VerticalAlignment="Center"/>
+                                                                </StackPanel>
+                                                            </StackPanel>
+                                                            <ItemsControl ItemsSource="{Binding RelatedRopes}">
+                                                                <ItemsControl.ItemsPanel>
+                                                                    <ItemsPanelTemplate>
+                                                                        <StackPanel Orientation="Vertical"  Margin="20,0,0,0"  VerticalAlignment="Center" />
+                                                                    </ItemsPanelTemplate>
+                                                                </ItemsControl.ItemsPanel>
+                                                                <ItemsControl.ItemTemplate>
+                                                                    <DataTemplate>
+                                                                        <CheckBox Content="{Binding RopeName}" Tag="{Binding RopeNumber}" IsChecked="{Binding IsSelected}"/>
+                                                                    </DataTemplate>
+                                                                </ItemsControl.ItemTemplate>
+                                                            </ItemsControl>
+                                                        </StackPanel>
+                                                    </StackPanel>
                                                 </DataTemplate>
                                             </ItemsControl.ItemTemplate>
                                         </ItemsControl>
-                                    </StackPanel>
-                                </StackPanel>
-                            </DataTemplate>
-                        </ItemsControl.ItemTemplate>
-                    </ItemsControl>
-                </StackPanel>
-            </StackPanel>
-            <Grid Grid.Row="4" Grid.ColumnSpan="3">
-                <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
-                    <Button  Content="取消" Width="100" Height="50" Margin="10,0" IsCancel="True" Style="{StaticResource CancelButton}"/>
-                    <Button  Content="保存" Width="150" Height="50" Margin="20,0" IsDefault="True" Click="Save_Click"/>
-                </StackPanel>
-                <CheckBox Content="保存后重新启动程序" Foreground="Orange" BorderBrush="Orange"
-                          pu:CheckBoxHelper.CheckedGlyphBrush="Orange" Margin="0,10,0,0" IsChecked="{Binding IsAutoStart}"
-                          HorizontalAlignment="Right" VerticalAlignment="Bottom"/>
-            </Grid>
-        </Grid>
-    </Border>
-</pu:WindowX>
+                                    </DataTemplate>
+                                </TabControl.ContentTemplate>
+                            </TabControl>
+
+                        </StackPanel>
+                    </StackPanel>
+                    <Grid Grid.Row="4" Grid.ColumnSpan="3">
+                        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
+                            <Button  Content="取消" Width="100" Height="50" Margin="10,0" IsCancel="True" Style="{StaticResource CancelButton}"/>
+                            <Button  Content="保存" Width="150" Height="50" Margin="20,0" IsDefault="True" Click="Save_Click"/>
+                        </StackPanel>
+                        <CheckBox Content="保存后重新启动程序" Foreground="Orange" BorderBrush="Orange"
+                              pu:CheckBoxHelper.CheckedGlyphBrush="Orange" Margin="0,10,0,0" IsChecked="{Binding IsAutoStart}"
+                              HorizontalAlignment="Right" VerticalAlignment="Bottom"/>
+                    </Grid>
+                </Grid>
+            </Border>
+        </Viewbox>
+    </Grid>
+</pu:WindowX>

+ 124 - 51
Pages/SettingDialog.xaml.cs

@@ -1,6 +1,7 @@
 using Panuon.WPF.UI;
 using SWRIS.Core;
 using SWRIS.Enums;
+using SWRIS.Extensions;
 using SWRIS.Models;
 using SWRIS.Models.ViewModel;
 using System;
@@ -18,9 +19,16 @@ namespace SWRIS.Pages
     {
         public SettingViewModel SettingView { get; set; }
         public bool IsAutoStart { get; set; }
+
         public SettingDialog()
         {
             InitializeComponent();
+            InitializeSettingView();
+            DataContext = SettingView;
+        }
+
+        private void InitializeSettingView()
+        {
             SettingView = new SettingViewModel
             {
                 AppName = App.Config.AppName,
@@ -40,7 +48,7 @@ namespace SWRIS.Pages
                 Calibration = new CalibrationDataModel
                 {
                     ModuleType = (int)App.Config.Calibration.ModuleType,
-                    Limits = new ObservableCollection<LimitDataModel>(),
+                    Trolleys = new ObservableCollection<TrolleyLimitDataModel>(),
                     Tcp = new TcpDataModel
                     {
                         IpAddress = App.Config.Calibration.Tcp.IpAddress,
@@ -59,31 +67,73 @@ namespace SWRIS.Pages
                 }
             };
 
-            foreach (var limit in App.Config.Calibration.Limits)
+            LoadTrolleyData();
+        }
+
+        private void LoadTrolleyData()
+        {
+            foreach (var trolley in App.Config.Trolleys)
             {
-                var limitModel = new LimitDataModel
+                var trolleyLimitData = new TrolleyLimitDataModel
                 {
-                    Address = limit.Address,
-                    IsReverse = limit.IsReverse,
-                    Name = limit.Name,
-                    Position = limit.Position,
-                    IsEnable = limit.IsEnable,
-                    SwitchIndex = limit.SwitchIndex,
-                    RelatedRopes = App.Config.Equipments.Select(c => new RopeDataModel
-                    {
-                        RopeName = c.RopeName,
-                        RopeNumber = c.RopeNumber,
-                        IsSelected = limit.RelatedRopes.Contains(c.RopeNumber)
-                    }).ToList()
+                    TrolleyId = trolley.Id,
+                    TrolleyName = trolley.Name,
+                    Limits = new ObservableCollection<LimitDataModel>()
                 };
-                SettingView.Calibration.Limits.Add(limitModel);
+
+                var trolleyLimits = App.Config.Calibration.Limits
+                    .Where(c => c.TrolleyId == trolley.Id)
+                    .Select(limit => new LimitDataModel
+                    {
+                        Name = limit.Name,
+                        Address = limit.Address,
+                        IsReverse = limit.IsReverse,
+                        TrolleyId = trolley.Id,
+                        Position = limit.Position,
+                        SwitchIndex = limit.SwitchIndex,
+                        IsEnable = limit.IsEnable,
+                        RelatedRopes = App.Config.Equipments.Where(c => c.TrolleyId == trolley.Id).Select(c => new RopeDataModel
+                        {
+                            RopeName = c.RopeName,
+                            RopeNumber = c.RopeNumber,
+                            IsSelected = limit.RelatedRopes.Contains(c.RopeNumber)
+                        }).ToList()
+                    })
+                    .ToList();
+
+                trolleyLimitData.Limits.AddRange(trolleyLimits);
+                SettingView.Calibration.Trolleys.Add(trolleyLimitData);
             }
-            DataContext = SettingView;
         }
 
         private void Save_Click(object sender, System.Windows.RoutedEventArgs e)
         {
+            try
+            {
+                UpdateConfiguration();
+
+                if (ConfigHelper.Set(App.Config))
+                {
+                    IsAutoStart = SettingView.IsAutoStart;
+                    DialogResult = true;
+                    Close();
+                }
+                else
+                {
+                    LogHelper.Warn("保存配置失败,请检查配置文件权限。");
+                    DialogResult = false;
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error($"保存配置时发生错误: {ex.Message}", ex);
+                DialogResult = false;
+            }
+        }
 
+        private void UpdateConfiguration()
+        {
+            // 更新基本配置
             App.Config.Crane = SettingView.Crane;
 
             App.Config.IpAddress = SettingView.IpAddress;
@@ -98,49 +148,72 @@ namespace SWRIS.Pages
             App.Config.DamageExtent = SettingView.DamageExtent;
             App.Config.AlarmValidCount = SettingView.AlarmValidCount;
 
+            App.Config.DamageNearPointCount = SettingView.DamageNearPointCount;
+
+            App.Config.ShowDebugMessage = SettingView.ShowDebugMessage;
+
+            // 更新校准配置
+            UpdateCalibrationConfiguration();
+
+            // 更新限制配置
+            UpdateLimitConfiguration();
+
+        }
+
+        private void UpdateCalibrationConfiguration()
+        {
             App.Config.Calibration.ModuleType = (ModuleType)SettingView.Calibration.ModuleType;
+
             if (SettingView.Calibration.ModuleType == (int)ModuleType.ModbusRtu)
             {
-                App.Config.Calibration.Serial.PortName = SettingView.Calibration.Serial.PortName;
-                App.Config.Calibration.Serial.BaudRate = SettingView.Calibration.Serial.BaudRate;
-                App.Config.Calibration.Serial.DataBits = SettingView.Calibration.Serial.DataBits;
-                App.Config.Calibration.Serial.Parity = (Parity)Enum.Parse(typeof(Parity), SettingView.Calibration.Serial.Parity);
-                App.Config.Calibration.Serial.StopBits = (StopBits)SettingView.Calibration.Serial.StopBits;
-                App.Config.Calibration.Serial.Station = SettingView.Calibration.Serial.Station;
+                var serial = SettingView.Calibration.Serial;
+                App.Config.Calibration.Serial.PortName = serial.PortName;
+                App.Config.Calibration.Serial.BaudRate = serial.BaudRate;
+                App.Config.Calibration.Serial.DataBits = serial.DataBits;
+                App.Config.Calibration.Serial.Parity = (Parity)Enum.Parse(typeof(Parity), serial.Parity);
+                App.Config.Calibration.Serial.StopBits = (StopBits)serial.StopBits;
+                App.Config.Calibration.Serial.Station = serial.Station;
             }
             else
             {
-                App.Config.Calibration.Tcp.IpAddress = SettingView.Calibration.Tcp.IpAddress;
-                App.Config.Calibration.Tcp.Port = SettingView.Calibration.Tcp.Port;
-                App.Config.Calibration.Tcp.Station = SettingView.Calibration.Tcp.Station;
-            }
-            for (int i = 0; i < 2; i++)
-            {
-                var sourceLimit = SettingView.Calibration.Limits[i];
-                var targetLimit = App.Config.Calibration.Limits[i];
-
-                targetLimit.Name = sourceLimit.Name;
-                targetLimit.Address = sourceLimit.Address;
-                targetLimit.Position = sourceLimit.Position;
-                targetLimit.IsEnable = sourceLimit.IsEnable;
-                targetLimit.IsReverse = sourceLimit.IsReverse;
-                targetLimit.SwitchIndex = sourceLimit.SwitchIndex;
-                targetLimit.RelatedRopes = sourceLimit.RelatedRopes
-                    .Where(c => c.IsSelected)
-                    .Select(c => c.RopeNumber)
-                    .ToList();
+                var tcp = SettingView.Calibration.Tcp;
+                App.Config.Calibration.Tcp.IpAddress = tcp.IpAddress;
+                App.Config.Calibration.Tcp.Port = tcp.Port;
+                App.Config.Calibration.Tcp.Station = tcp.Station;
             }
+        }
 
-            if (ConfigHelper.Set(App.Config))
-            {
-                IsAutoStart = SettingView.IsAutoStart;
-                DialogResult = true;
-            }
-            else
+        private void UpdateLimitConfiguration()
+        {
+            // 使用更安全的方式更新限制配置
+            var limitsToUpdate = SettingView.Calibration.Trolleys
+                .SelectMany(t => t.Limits)
+                .ToList();
+
+            foreach (var sourceLimit in limitsToUpdate)
             {
-                DialogResult = false;
-            }
+                var targetLimit = App.Config.Calibration.Limits
+                    .FirstOrDefault(limit => limit.TrolleyId == sourceLimit.TrolleyId && limit.Address == sourceLimit.Address);
 
+                if (targetLimit != null)
+                {
+                    targetLimit.Name = sourceLimit.Name;
+                    targetLimit.Address = sourceLimit.Address;
+                    targetLimit.Position = sourceLimit.Position;
+                    targetLimit.IsEnable = sourceLimit.IsEnable;
+                    targetLimit.IsReverse = sourceLimit.IsReverse;
+                    targetLimit.SwitchIndex = sourceLimit.SwitchIndex;
+                    targetLimit.RelatedRopes = sourceLimit.RelatedRopes
+                        .Where(r => r.IsSelected)
+                        .Select(r => r.RopeNumber)
+                        .ToList();
+                }
+            }
+        }
+        private void Cancel_Click(object sender, System.Windows.RoutedEventArgs e)
+        {
+            DialogResult = false;
+            Close();
         }
     }
-}
+}

+ 1 - 1
Properties/app.manifest

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
-	<assemblyIdentity version="1.0.0.0" name="MyApplication.app" />
+	<assemblyIdentity version="1.0.0.0" name="SWRIS.app" />
 	<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
 		<security>
 			<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">

+ 27 - 0
README.md

@@ -1,4 +1,31 @@
 # 在线钢丝绳检测系统升级记录
+
+## 版本 1.2.1 - 2026-8-17
+
+### 新增功能
+- **修改参数修改逻辑**
+  - 设备未连接时,可修改参数,但是不会发布设备报文到设备,只会修改本地配置文件
+
+## 版本 1.2.0 - 2026-7-6
+
+### 新增功能
+- **增加数据清理功能**
+  - 定期清理历史数据,释放存储空间
+- **增加多机构支持**
+  - 设备分机构管理,兼容多机构使用场景,并支持各机构独立标定项管理
+
+### 技术改进
+- **设备故障汇总显示**
+  - 将原来的单设备故障显示优化为多设备故障汇总显示,优化界面设计和维护便捷性
+- **网卡实时监测**
+  - 增加网卡实时监测,并在探查不到时发出提醒,由原来阻止程序运行改为提示用户检查网络连接,提升交互体验
+- **更改探头图案**
+  - 将探头图案改为最新设计,并修改钢丝绳粗细适配探头尺寸,提升视觉效果
+- **优化多探头布局**
+  - 由原来的探头固定布局改为重复项布局方式,减少维护成本,提升系统适配性
+- **优化曲线绘制逻辑**
+  - 优化曲线绘制逻辑,提升曲线绘制效率和流畅度
+
 ## 版本 1.1.2 - 2025-12-12
 
 ### 新增功能

BIN
Resources/blue_bg.png


BIN
Resources/equipment.png


BIN
Resources/red_bg.png


BIN
Resources/rope_block.png


BIN
Resources/trolleyActived.png


+ 50 - 19
SWRIS.csproj

@@ -69,6 +69,9 @@
     <ApplicationIcon>logo.ico</ApplicationIcon>
   </PropertyGroup>
   <ItemGroup>
+    <Reference Include="EPPlus, Version=4.5.3.3, Culture=neutral, PublicKeyToken=ea159fdaa78159a1, processorArchitecture=MSIL">
+      <HintPath>packages\EPPlus.4.5.3.3\lib\net40\EPPlus.dll</HintPath>
+    </Reference>
     <Reference Include="CustomMarshalers" />
     <Reference Include="Dapper, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
       <HintPath>packages\Dapper.2.1.66\lib\net461\Dapper.dll</HintPath>
@@ -95,6 +98,9 @@
     <Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=9.0.0.8, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
       <HintPath>packages\Microsoft.Bcl.AsyncInterfaces.9.0.8\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
     </Reference>
+    <Reference Include="Microsoft.Bcl.HashCode, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>packages\Microsoft.Bcl.HashCode.1.1.1\lib\net461\Microsoft.Bcl.HashCode.dll</HintPath>
+    </Reference>
     <Reference Include="Microsoft.WindowsAPICodePack, Version=1.1.5.0, Culture=neutral, PublicKeyToken=8985beaab7ea3f04, processorArchitecture=MSIL">
       <HintPath>packages\Microsoft-WindowsAPICodePack-Core.1.1.5\lib\net48\Microsoft.WindowsAPICodePack.dll</HintPath>
     </Reference>
@@ -122,12 +128,18 @@
     <Reference Include="QuestPDF, Version=2025.7.1.0, Culture=neutral, PublicKeyToken=0f3c2b2315ff52c8, processorArchitecture=MSIL">
       <HintPath>packages\QuestPDF.2025.7.1\lib\netstandard2.0\QuestPDF.dll</HintPath>
     </Reference>
+    <Reference Include="RBush, Version=4.0.0.0, Culture=neutral, PublicKeyToken=c77e27b81f4d0187, processorArchitecture=MSIL">
+      <HintPath>packages\RBush.Signed.4.0.0\lib\net47\RBush.dll</HintPath>
+    </Reference>
     <Reference Include="ScottPlot, Version=5.0.55.0, Culture=neutral, PublicKeyToken=86698dc10387c39e, processorArchitecture=MSIL">
       <HintPath>packages\ScottPlot.5.0.55\lib\net462\ScottPlot.dll</HintPath>
     </Reference>
     <Reference Include="ScottPlot.WPF, Version=5.0.55.0, Culture=neutral, PublicKeyToken=86698dc10387c39e, processorArchitecture=MSIL">
       <HintPath>packages\ScottPlot.WPF.5.0.55\lib\net462\ScottPlot.WPF.dll</HintPath>
     </Reference>
+    <Reference Include="SixLabors.Fonts, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d998eea7b14cab13, processorArchitecture=MSIL">
+      <HintPath>packages\SixLabors.Fonts.1.0.0\lib\netstandard2.0\SixLabors.Fonts.dll</HintPath>
+    </Reference>
     <Reference Include="SkiaSharp, Version=2.88.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
       <HintPath>packages\SkiaSharp.2.88.9\lib\net462\SkiaSharp.dll</HintPath>
     </Reference>
@@ -222,6 +234,7 @@
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
     </ApplicationDefinition>
+    <Compile Include="Converters\CollectionToVisibilityConverter.cs" />
     <Compile Include="Converters\LimitSwitchNumberToBooleanConverter.cs" />
     <Compile Include="Converters\RunningStatusToBooleanConverter.cs" />
     <Compile Include="Converters\ModuleTypeToVisibilityConverter.cs" />
@@ -237,8 +250,11 @@
     <Compile Include="Converters\ResetTypeToBooleanConverter.cs" />
     <Compile Include="Converters\EncoderDirectionToBooleanConverter.cs" />
     <Compile Include="Converters\StringToColorConverter.cs" />
+    <Compile Include="Converters\SubtractConverter.cs" />
+    <Compile Include="Converters\TrolleysToVisibilityConverter.cs" />
     <Compile Include="Core\CalibrationCore.cs" />
     <Compile Include="Dtos\DamageDto.cs" />
+    <Compile Include="Dtos\FaultsMessageDto.cs" />
     <Compile Include="Dtos\RecordDto.cs" />
     <Compile Include="Dtos\RecordIdAnDataFilePathDto.cs" />
     <Compile Include="Enums\AlarmSourceType.cs" />
@@ -337,6 +353,8 @@
     <Compile Include="Models\SensorModel.cs" />
     <Compile Include="Models\SpeedVariationModel.cs" />
     <Compile Include="Models\SwitchInstanceDataModel.cs" />
+    <Compile Include="Models\TrolleyActiveModel.cs" />
+    <Compile Include="Models\TrolleyLimitDataModel.cs" />
     <Compile Include="Models\TwistFactorDataModel.cs" />
     <Compile Include="Models\VerifyDataModel.cs" />
     <Compile Include="Enums\AlarmType.cs" />
@@ -353,11 +371,8 @@
     <Compile Include="Models\SoftAuthorizeModel.cs" />
     <Compile Include="Models\ViewModel\DamagesViewModel.cs" />
     <Compile Include="Models\ViewModel\EquipmentSettingViewModel.cs" />
-    <Compile Include="Models\ViewModel\FourRopesViewModel.cs" />
+    <Compile Include="Models\ViewModel\RealTimeViewModel.cs" />
     <Compile Include="Models\ViewModel\RopeViewBaseModel.cs" />
-    <Compile Include="Models\ViewModel\ThreeRopesViewModel.cs" />
-    <Compile Include="Models\ViewModel\TwoRopesViewModel.cs" />
-    <Compile Include="Models\ViewModel\OneRopeViewModel.cs" />
     <Compile Include="Models\ViewModel\MainViewModel.cs" />
     <Compile Include="Models\ViewModel\RecordViewModel.cs" />
     <Compile Include="Models\ViewModel\SettingViewModel.cs" />
@@ -367,23 +382,26 @@
     <Compile Include="Pages\DamagesPage.xaml.cs">
       <DependentUpon>DamagesPage.xaml</DependentUpon>
     </Compile>
-    <Compile Include="Pages\RealTime\FourRopesPage.xaml.cs">
-      <DependentUpon>FourRopesPage.xaml</DependentUpon>
+    <Compile Include="Pages\RealTime\FourRopesControl.xaml.cs">
+      <DependentUpon>FourRopesControl.xaml</DependentUpon>
     </Compile>
     <Compile Include="Controls\RecordLineChart.xaml.cs">
       <DependentUpon>RecordLineChart.xaml</DependentUpon>
     </Compile>
-    <Compile Include="Pages\RealTime\OneRopePage.xaml.cs">
-      <DependentUpon>OneRopePage.xaml</DependentUpon>
+    <Compile Include="Pages\RealTime\OneRopeControl.xaml.cs">
+      <DependentUpon>OneRopeControl.xaml</DependentUpon>
     </Compile>
     <Compile Include="Pages\ParameterDialog.xaml.cs">
       <DependentUpon>ParameterDialog.xaml</DependentUpon>
     </Compile>
-    <Compile Include="Pages\RealTime\ThreeRopesPage.xaml.cs">
-      <DependentUpon>ThreeRopesPage.xaml</DependentUpon>
+    <Compile Include="Pages\RealTime\RealTimePage.xaml.cs">
+      <DependentUpon>RealTimePage.xaml</DependentUpon>
     </Compile>
-    <Compile Include="Pages\RealTime\TwoRopesPage.xaml.cs">
-      <DependentUpon>TwoRopesPage.xaml</DependentUpon>
+    <Compile Include="Pages\RealTime\ThreeRopesControl.xaml.cs">
+      <DependentUpon>ThreeRopesControl.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Pages\RealTime\TwoRopesControl.xaml.cs">
+      <DependentUpon>TwoRopesControl.xaml</DependentUpon>
     </Compile>
     <Compile Include="Pages\RecordPage.xaml.cs">
       <DependentUpon>RecordPage.xaml</DependentUpon>
@@ -401,6 +419,7 @@
     <Compile Include="Repository\Record\IRecordRepository.cs" />
     <Compile Include="Repository\Record\RecordRepository.cs" />
     <Compile Include="Repository\SqLiteBaseRepository.cs" />
+    <Compile Include="Selectors\EquipmentsLayoutTemplateSelector.cs" />
     <Compile Include="Services\HistoryCleanupService.cs" />
     <Compile Include="SoftAuthDialog.xaml.cs">
       <DependentUpon>SoftAuthDialog.xaml</DependentUpon>
@@ -449,29 +468,33 @@
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
     </Page>
-    <Page Include="Pages\RealTime\FourRopesPage.xaml">
-      <SubType>Designer</SubType>
+    <Page Include="Pages\RealTime\FourRopesControl.xaml">
       <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
     </Page>
     <Page Include="Controls\RecordLineChart.xaml">
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
     </Page>
-    <Page Include="Pages\RealTime\OneRopePage.xaml">
-      <SubType>Designer</SubType>
+    <Page Include="Pages\RealTime\OneRopeControl.xaml">
       <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
     </Page>
     <Page Include="Pages\ParameterDialog.xaml">
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
     </Page>
-    <Page Include="Pages\RealTime\ThreeRopesPage.xaml">
-      <SubType>Designer</SubType>
+    <Page Include="Pages\RealTime\RealTimePage.xaml">
       <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
     </Page>
-    <Page Include="Pages\RealTime\TwoRopesPage.xaml">
+    <Page Include="Pages\RealTime\ThreeRopesControl.xaml">
+      <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
+    </Page>
+    <Page Include="Pages\RealTime\TwoRopesControl.xaml">
       <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
     </Page>
     <Page Include="Pages\RecordPage.xaml">
       <SubType>Designer</SubType>
@@ -499,12 +522,20 @@
       <DependentUpon>Settings.settings</DependentUpon>
       <DesignTimeSharedInput>True</DesignTimeSharedInput>
     </Compile>
+    <Resource Include="Resources\trolleyActived.png" />
+    <Resource Include="Resources\unconnect.png" />
+    <Resource Include="Resources\risk_alert.png" />
+    <Resource Include="Resources\risk_normal.png" />
+    <Resource Include="Resources\risk_warning.png" />
+    <Resource Include="Resources\pointer.png" />
+    <Resource Include="Resources\connected.png" />
     <Resource Include="Resources\upgrade.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Resource>
     <Resource Include="Fonts\Alibaba-PuHuiTi-Bold.ttf" />
     <Resource Include="Fonts\Alibaba-PuHuiTi-Regular.ttf" />
     <Resource Include="Fonts\Alibaba-PuHuiTi-Medium.ttf" />
+    <None Include="Converters\StringEmptyToVisibilityConverter.cs.bak" />
     <None Include="README.md" />
     <Resource Include="Resources\bucket.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

+ 34 - 0
Selectors/EquipmentsLayoutTemplateSelector.cs

@@ -0,0 +1,34 @@
+using SWRIS.Models;
+using System.Collections.ObjectModel;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace SWRIS.Selectors
+{
+    public class EquipmentsLayoutTemplateSelector : DataTemplateSelector
+    {
+        public DataTemplate FourTemplate { get; set; }
+        public DataTemplate ThreeTemplate { get; set; }
+        public DataTemplate TwoTemplate { get; set; }
+        public DataTemplate OneTemplate { get; set; }
+
+        public override DataTemplate SelectTemplate(object item, DependencyObject container)
+        {
+            if (item is ObservableCollection<EquipmentDataModel> equipments)
+            {
+                switch (equipments.Count)
+                {
+                    case 1:
+                        return OneTemplate;
+                    case 2:
+                        return TwoTemplate;
+                    case 3:
+                        return ThreeTemplate;
+                    case 4:
+                        return FourTemplate;
+                }
+            }
+            return base.SelectTemplate(item, container);
+        }
+    }
+}

+ 3 - 0
SoftAuthDialog.xaml.cs

@@ -5,6 +5,7 @@ using SWRIS.Models;
 using System;
 using System.Drawing;
 using System.Linq;
+using System.Reflection;
 using System.Threading.Tasks;
 using System.Windows;
 using System.Windows.Controls;
@@ -54,6 +55,7 @@ namespace SWRIS
             e.Handled = true;
         }
 
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         private void BtnSave_Click(object sender, RoutedEventArgs e)
         {
             DialogResult = SoftAuth.WriteRegistry(AuthorizeModel.FinalCode);
@@ -125,6 +127,7 @@ namespace SWRIS
                 }
             }
         }
+        [Obfuscation(Feature = "virtualization", Exclude = false)]
         private async void ValidateRegistrationCode()
         {
             // 显示加载状态(可选)

+ 7 - 16
Styles/Styles.xaml

@@ -12,18 +12,7 @@
            ShadowDepth="2"
            BlurRadius="20"
            Opacity="0.5" />
-    <Style x:Key="{ComponentResourceKey ResourceId=NoticeBoxItemStyle, TypeInTargetAssembly={x:Type pu:NoticeBox}}"
-           TargetType="pu:NoticeBoxItem">
-        <Setter Property="pu:IconHelper.FontFamily" Value="微软雅黑" />
-        <Setter Property="pu:IconHelper.FontSize" Value="16" />
-        <Setter Property="FontFamily" Value="微软雅黑" />
-        <Setter Property="FontSize" Value="14" />
-        <Setter Property="Width" Value="350" />
-        <Setter Property="Height" Value="150" />
-        <Setter Property="ClickEffect" Value="Sink" />
-        <Setter Property="VerticalContentAlignment" Value="Stretch" />
-        <Setter Property="HorizontalContentAlignment" Value="Stretch" />
-    </Style>
+   
     <Style TargetType="ScrollViewer">
         <Setter Property="pu:ScrollBarHelper.ThumbBackground" Value="#55736AFA"/>
         <Setter Property="pu:ScrollBarHelper.ThumbCornerRadius" Value="4"/>
@@ -210,6 +199,7 @@
             </Setter.Value>
         </Setter>
     </Style>
+
     <Style x:Key="BoxRadioButton" TargetType="{x:Type RadioButton}">
         <Setter Property="Background" Value="Transparent"/>
         <Setter Property="Foreground" Value="#363636"/>
@@ -271,17 +261,17 @@
         <Setter Property="pu:TabControlHelper.ItemsHeight" Value="45"/>
         <Setter Property="pu:TabControlHelper.ItemsPadding" Value="15,0"/>
         <Setter Property="BorderThickness" Value="0"/>
+        <Setter Property="Background" Value="Transparent"/>
+        <Setter Property="Foreground" Value="#FFFFFF"/>
         <Setter Property="pu:TabControlHelper.ItemsHoverBackground" Value="{x:Null}"/>
         <Setter Property="pu:TabControlHelper.ItemsRibbonLineVisibility" Value="Visible"/>
         <Setter Property="pu:TabControlHelper.ItemsRibbonLinePlacement" Value="Bottom"/>
         <Setter Property="pu:TabControlHelper.ItemsRibbonLineBrush" Value="Transparent"/>
-        <Setter Property="pu:TabControlHelper.ItemsHoverRibbonLineBrush" Value="#C62F2F"/>
+        <Setter Property="pu:TabControlHelper.ItemsHoverRibbonLineBrush" Value="#6158E5"/>
         <Setter Property="pu:TabControlHelper.ItemsHoverRibbonLineThickness" Value="1"/>
-        <Setter Property="pu:TabControlHelper.ItemsSelectedRibbonLineBrush" Value="#C62F2F"/>
+        <Setter Property="pu:TabControlHelper.ItemsSelectedRibbonLineBrush" Value="#6158E5"/>
         <Setter Property="pu:TabControlHelper.ItemsSelectedRibbonLineThickness" Value="3"/>
         <Setter Property="pu:TabControlHelper.ItemsSelectedBackground" Value="{x:Null}"/>
-        <Setter Property="pu:TabControlHelper.HeaderPanelBorderBrush" Value="LightGray"/>
-        <Setter Property="pu:TabControlHelper.HeaderPanelBorderThickness" Value="0,0,0,1"/>
     </Style>
     <Style TargetType="ProgressBar" BasedOn="{StaticResource {x:Type ProgressBar}}">
         <Setter Property="Minimum" Value="0" />
@@ -318,6 +308,7 @@
             </Setter.Value>
         </Setter>
     </Style>
+  
     <Style TargetType="ToggleButton" BasedOn="{StaticResource {x:Type ToggleButton}}">
         <Setter Property="Background" Value="#141332"/>
         <Setter Property="BorderBrush" Value="#3B3B7B"/>

+ 4 - 0
packages.config

@@ -2,6 +2,7 @@
 <packages>
   <package id="Dapper" version="2.1.66" targetFramework="net48" />
   <package id="EntityFramework" version="6.5.1" targetFramework="net48" />
+  <package id="EPPlus" version="4.5.3.3" targetFramework="net48" />
   <package id="HarfBuzzSharp" version="7.3.0.3" targetFramework="net48" />
   <package id="HarfBuzzSharp.NativeAssets.Linux" version="7.3.0.3" targetFramework="net48" />
   <package id="HarfBuzzSharp.NativeAssets.macOS" version="7.3.0.3" targetFramework="net48" />
@@ -12,6 +13,7 @@
   <package id="Microsoft.AspNet.WebApi.Core" version="5.3.0" targetFramework="net48" />
   <package id="Microsoft.AspNet.WebApi.SelfHost" version="5.3.0" targetFramework="net48" />
   <package id="Microsoft.Bcl.AsyncInterfaces" version="9.0.8" targetFramework="net48" />
+  <package id="Microsoft.Bcl.HashCode" version="1.1.1" targetFramework="net48" />
   <package id="Microsoft-WindowsAPICodePack-Core" version="1.1.5" targetFramework="net48" />
   <package id="Microsoft-WindowsAPICodePack-Shell" version="1.1.5" targetFramework="net48" />
   <package id="Newtonsoft.Json" version="13.0.3" targetFramework="net48" />
@@ -22,8 +24,10 @@
   <package id="Panuon.WPF.UI" version="1.3.0.2" targetFramework="net48" />
   <package id="QRCoder" version="1.6.0" targetFramework="net48" />
   <package id="QuestPDF" version="2025.7.1" targetFramework="net48" />
+  <package id="RBush.Signed" version="4.0.0" targetFramework="net48" />
   <package id="ScottPlot" version="5.0.55" targetFramework="net48" />
   <package id="ScottPlot.WPF" version="5.0.55" targetFramework="net48" />
+  <package id="SixLabors.Fonts" version="1.0.0" targetFramework="net48" />
   <package id="SkiaSharp" version="2.88.9" targetFramework="net48" />
   <package id="SkiaSharp.HarfBuzz" version="2.88.9" targetFramework="net48" />
   <package id="SkiaSharp.NativeAssets.Linux.NoDependencies" version="2.88.9" targetFramework="net48" />