Browse Source

初始提交

wangchong 3 days ago
parent
commit
3c129eb8ee
57 changed files with 4257 additions and 0 deletions
  1. 7 0
      .gitignore
  2. 108 0
      App.config
  3. 130 0
      App.xaml
  4. 308 0
      App.xaml.cs
  5. 108 0
      MainWindow.xaml
  6. 588 0
      MainWindow.xaml.cs
  7. 254 0
      Mapping.cs
  8. 25 0
      OpenTK.dll.config
  9. 52 0
      Properties/AssemblyInfo.cs
  10. 223 0
      Properties/Resources.Designer.cs
  11. 169 0
      Properties/Resources.resx
  12. 43 0
      Properties/Settings.Designer.cs
  13. 7 0
      Properties/Settings.settings
  14. 73 0
      Properties/app.manifest
  15. 82 0
      README.md
  16. 79 0
      Repository/Alarm/AlarmRepository.cs
  17. 11 0
      Repository/Alarm/IAlarmRepository.cs
  18. 28 0
      Repository/Record/IRecordRepository.cs
  19. 406 0
      Repository/Record/RecordRepository.cs
  20. 35 0
      Repository/SqLiteBaseRepository.cs
  21. BIN
      Resources/back.png
  22. BIN
      Resources/blue_bg.png
  23. BIN
      Resources/bucket.png
  24. BIN
      Resources/cims.png
  25. BIN
      Resources/closed.png
  26. BIN
      Resources/connected.png
  27. BIN
      Resources/delete.png
  28. BIN
      Resources/eqSet.png
  29. BIN
      Resources/equipment.png
  30. BIN
      Resources/export.png
  31. BIN
      Resources/loading.png
  32. BIN
      Resources/logo.png
  33. BIN
      Resources/logo_loading.png
  34. BIN
      Resources/menu_bg.png
  35. BIN
      Resources/menu_dec.png
  36. BIN
      Resources/minimize.png
  37. BIN
      Resources/option_bg.png
  38. BIN
      Resources/pointer.png
  39. BIN
      Resources/red_bg.png
  40. BIN
      Resources/risk_alert.png
  41. BIN
      Resources/risk_normal.png
  42. BIN
      Resources/risk_warning.png
  43. BIN
      Resources/rope_block.png
  44. BIN
      Resources/setting.png
  45. BIN
      Resources/swris.png
  46. BIN
      Resources/top_banner.png
  47. BIN
      Resources/unconnect.png
  48. BIN
      Resources/upgrade.png
  49. 642 0
      SWRIS.csproj
  50. 14 0
      SWRIS.csproj.user
  51. 25 0
      SWRIS.sln
  52. 280 0
      Services/HistoryCleanupService.cs
  53. 67 0
      SoftAuthDialog.xaml
  54. 80 0
      SoftAuthDialog.xaml.cs
  55. 362 0
      Styles/Styles.xaml
  56. BIN
      logo.ico
  57. 51 0
      packages.config

+ 7 - 0
.gitignore

@@ -0,0 +1,7 @@
+/obj
+bin
+.vs
+packages
+/packages
+/.vs
+/bin

+ 108 - 0
App.config

@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+  <configSections>
+    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
+    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
+  </configSections>
+  <startup>
+    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
+  </startup>
+  <entityFramework>
+    <providers>
+      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
+      <provider invariantName="System.Data.SQLite.EF6" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />
+    </providers>
+  </entityFramework>
+  <system.data>
+    <DbProviderFactories>
+      <remove invariant="System.Data.SQLite.EF6" />
+      <add name="SQLite Data Provider (Entity Framework 6)" invariant="System.Data.SQLite.EF6" description=".NET Framework Data Provider for SQLite (Entity Framework 6)" type="System.Data.SQLite.EF6.SQLiteProviderFactory, System.Data.SQLite.EF6" />
+      <remove invariant="System.Data.SQLite" />
+      <add name="SQLite Data Provider" invariant="System.Data.SQLite" description=".NET Framework Data Provider for SQLite" type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" />
+    </DbProviderFactories>
+  </system.data>
+  <runtime>
+    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+      <dependentAssembly>
+        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Bcl.AsyncInterfaces" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-9.0.0.8" newVersion="9.0.0.8" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-6.0.3.0" newVersion="6.0.3.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Extensions.DependencyInjection.Abstractions" publicKeyToken="adb9793829ddae60" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-9.0.0.7" newVersion="9.0.0.7" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Extensions.Logging.Abstractions" publicKeyToken="adb9793829ddae60" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-9.0.0.7" newVersion="9.0.0.7" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Extensions.Options" publicKeyToken="adb9793829ddae60" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-9.0.0.7" newVersion="9.0.0.7" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.2.4.0" newVersion="4.2.4.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.IdentityModel.Tokens" publicKeyToken="31bf3856ad364e35" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-8.13.0.0" newVersion="8.13.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.IdentityModel.JsonWebTokens" publicKeyToken="31bf3856ad364e35" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-8.13.0.0" newVersion="8.13.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Numerics.Vectors" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.1.6.0" newVersion="4.1.6.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Diagnostics.DiagnosticSource" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-9.0.0.7" newVersion="9.0.0.7" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Text.Json" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-9.0.0.7" newVersion="9.0.0.7" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Text.Encodings.Web" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-9.0.0.7" newVersion="9.0.0.7" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Bcl.TimeProvider" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-9.0.0.7" newVersion="9.0.0.7" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Microsoft.Extensions.Logging" publicKeyToken="adb9793829ddae60" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-9.0.0.7" newVersion="9.0.0.7" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Serilog" publicKeyToken="24c2f752a8e58a10" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.3.0.0" newVersion="4.3.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.ComponentModel.Annotations" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.2.1.0" newVersion="4.2.1.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="ScottPlot" publicKeyToken="86698dc10387c39e" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-5.0.55.0" newVersion="5.0.55.0" />
+      </dependentAssembly>
+    </assemblyBinding>
+  </runtime>
+</configuration>

+ 130 - 0
App.xaml

@@ -0,0 +1,130 @@
+<Application x:Class="SWRIS.App"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:local="clr-namespace:SWRIS" 
+             xmlns:pu="https://opensource.panuon.com/wpf-ui"
+             xmlns:d1p1="http://schemas.openxmlformats.org/markup-compatibility/2006">
+    <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="PanuonIconFont">pack://application:,,,/SWRIS;component/Fonts/PanuonIcon.ttf#PanuonIcon</FontFamily>
+            <ResourceDictionary.MergedDictionaries>
+                <pu:StyleDictionary Includes="All" />
+                <ResourceDictionary Source="pack://application:,,,/SWRIS;component/Styles/Styles.xaml" />
+                <ResourceDictionary>
+                    <pu:MessageBoxXSettings x:Key="messageSetting">
+                        <pu:MessageBoxXSettings.WindowXStyle>
+                            <Style TargetType="pu:WindowX" BasedOn="{StaticResource {x:Static pu:MessageBoxX.WindowXStyleKey}}">
+                                <Setter Property="SizeToContent" Value="Manual" />
+                                <Setter Property="Width" Value="600" />
+                                <Setter Property="Height" Value="320" />
+                                <Setter Property="FontSize" Value="22"/>
+                                <Setter Property="FontWeight" Value="Medium"/>
+                                <Setter Property="FontFamily">
+                                    <Setter.Value>
+                                        <FontFamily>pack://application:,,,/SWRIS;component/Fonts/Alibaba-PuHuiTi-Medium.ttf#Alibaba PuHuiTi</FontFamily>
+                                    </Setter.Value>
+                                </Setter>
+                                <Setter Property="Background" Value="#141332" />
+                                <Setter Property="Foreground" Value="#FFFFFF" />
+                                <Setter Property="BorderBrush" Value="#615CDD"/>
+                                <Setter Property="BorderThickness" Value="1"/>
+                            </Style>
+                        </pu:MessageBoxXSettings.WindowXStyle>
+                        <pu:MessageBoxXSettings.ButtonStyle>
+                            <Style TargetType="Button" BasedOn="{StaticResource {x:Static pu:MessageBoxX.ButtonStyleKey}}">
+                                <Setter Property="pu:ButtonHelper.CornerRadius" Value="10" />
+                                <Setter Property="Width" Value="100"/>
+                                <Setter Property="Height" Value="50"/>
+                                <Setter Property="Margin" Value="10,0"/>
+                                <Style.Triggers>
+                                    <Trigger Property="IsDefault" Value="True">
+                                        <Setter Property="Foreground" Value="#FFFFFF" />
+                                        <Setter Property="Background">
+                                            <Setter.Value>
+                                                <LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
+                                                    <GradientStop Color="#4E38F0" Offset="0.6"/>
+                                                    <GradientStop Color="#736AFA" Offset="1.2"/>
+                                                </LinearGradientBrush>
+                                            </Setter.Value>
+                                        </Setter>
+                                    </Trigger>
+                                    <Trigger Property="IsDefault" Value="False">
+                                        <Setter Property="Foreground" Value="#FFFFFF" />
+                                        <Setter Property="Background">
+                                            <Setter.Value>
+                                                <LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
+                                                    <GradientStop Color="#615E7C" Offset="0.6"/>
+                                                    <GradientStop Color="#807E9F" Offset="1.2"/>
+                                                </LinearGradientBrush>
+                                            </Setter.Value>
+                                        </Setter>
+                                    </Trigger>
+                                </Style.Triggers>
+                            </Style>
+                        </pu:MessageBoxXSettings.ButtonStyle>
+                    </pu:MessageBoxXSettings>
+
+                    <pu:NoticeBoxSettings x:Key="noticeSetting" Position="BottomRight">
+                        <pu:NoticeBoxSettings.NoticeBoxItemStyle>
+                            <Style TargetType="pu:NoticeBoxItem" BasedOn="{StaticResource {x:Static pu:NoticeBox.NoticeBoxItemStyleKey}}">
+                                <Setter Property="Background" Value="#141332" />
+                                <Setter Property="BorderBrush" Value="#615CDD" />
+                                <Setter Property="FontSize" Value="18"/>
+                                <Setter Property="FontWeight" Value="Medium"/>
+                                <Setter Property="FontFamily">
+                                    <Setter.Value>
+                                        <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="CornerRadius" Value="5"/>
+                                <Setter Property="Foreground" Value="#FFFFFF" />
+                            </Style>
+                        </pu:NoticeBoxSettings.NoticeBoxItemStyle>
+                    </pu:NoticeBoxSettings>
+                    <pu:PendingBoxSettings x:Key="pendingSetting">
+                        <pu:PendingBoxSettings.WindowStyle>
+                            <Style BasedOn="{StaticResource {x:Static pu:PendingBox.WindowStyleKey}}" TargetType="Window">
+                                <Setter Property="SizeToContent" Value="Manual" />
+                                <Setter Property="Width" Value="400" />
+                                <Setter Property="Height" Value="200" />
+                                <Setter Property="Background" Value="{DynamicResource WindowBackground}" />
+                                <Setter Property="Foreground" Value="{DynamicResource BodyForeground}" />
+                            </Style>
+                        </pu:PendingBoxSettings.WindowStyle>
+                        <pu:PendingBoxSettings.SpinStyle>
+                            <Style BasedOn="{StaticResource {x:Static pu:PendingBox.SpinStyleKey}}" TargetType="pu:Spin">
+                                <Setter Property="SpinStyle" Value="Ring2" />
+                                <Setter Property="GlyphBrush" Value="#6CBCEA" />
+                            </Style>
+                        </pu:PendingBoxSettings.SpinStyle>
+                        <pu:PendingBoxSettings.CancelButtonStyle>
+                            <Style BasedOn="{StaticResource {x:Static pu:PendingBox.CancelButtonStyleKey}}" TargetType="Button">
+                                <Setter Property="Background" Value="#6CBCEA" />
+                                <Setter Property="Foreground" Value="White" />
+                                <Setter Property="Height" Value="30" />
+                                <Style.Triggers>
+                                    <Trigger Property="IsPressed" Value="True">
+                                        <Setter Property="Background" Value="#6CABEA" />
+                                    </Trigger>
+                                </Style.Triggers>
+                            </Style>
+                        </pu:PendingBoxSettings.CancelButtonStyle>
+                    </pu:PendingBoxSettings>
+                    <pu:ToastSettings x:Key="toastSetting" Spacing="25">
+                        <pu:ToastSettings.LabelStyle>
+                            <Style TargetType="Label" BasedOn="{StaticResource {x:Static pu:Toast.LabelStyleKey}}">
+                                <Setter Property="Background" Value="{DynamicResource ToastBackground}"/>
+                                <Setter Property="Foreground" Value="{DynamicResource ToastForeground}"/>
+                            </Style>
+                        </pu:ToastSettings.LabelStyle>
+                    </pu:ToastSettings>
+                </ResourceDictionary>
+            </ResourceDictionary.MergedDictionaries>
+        </ResourceDictionary>
+    </Application.Resources>
+</Application>

+ 308 - 0
App.xaml.cs

@@ -0,0 +1,308 @@
+using Microsoft.Win32;
+using SWRIS.Core;
+using SWRIS.Extensions;
+using SWRIS.Models;
+using SWRIS.Properties;
+using SWRIS.Services;
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+using System.Web.Http;
+using System.Web.Http.SelfHost;
+using System.Windows;
+using System.Windows.Threading;
+
+namespace SWRIS
+{
+    /// <summary>
+    /// App.xaml 的交互逻辑
+    /// </summary>
+    public partial class App : Application
+    {
+        public static ConfigModel Config { get; set; }
+        public static TcpServerFrame TcpServer { get; set; }
+        public static CalibrationCore Calibration { get; set; }
+        public static DataCenterModel DataCenter { get; set; }
+        public static HistoryCleanupService HistoryCleanup { get; set; }
+
+        public App()
+        {
+            //首先注册开始和退出事件
+            if (!Settings.Default.IsDebug)
+            {
+                Startup += new StartupEventHandler(App_Startup);
+                if (!HslCommunication.Authorization.SetAuthorizationCode("946778b1-967c-4e05-beab-7f47bb248b71"))
+                {
+                    LogHelper.Info("HSL授权失败");
+                    return;
+                }
+            }
+            //PDF初始化配置
+            QuestPDF.Settings.License = QuestPDF.Infrastructure.LicenseType.Community;
+            QuestPDF.Settings.CheckIfAllTextGlyphsAreAvailable = false;
+        }
+        private void InitSoftAuthorize()
+        {
+            if (!SoftAuth.IsAuthorizeSuccess())
+            {
+                SoftAuthDialog authDialog = new SoftAuthDialog();
+                var result = authDialog.ShowDialog();
+                if (result != true)
+                {
+                    Environment.Exit(0);
+                }
+            }
+        }
+        private static SingleInstanceManager _singleInstanceManager;
+        public static void SwitchToExistingInstance(string processName)
+        {
+            _singleInstanceManager?.SwitchToExistingInstance(processName);
+        }
+        protected override void OnStartup(StartupEventArgs e)
+        {
+            #region 阻止系统睡眠,阻止屏幕关闭。
+            SystemSleep.PreventForCurrentThread();
+            #endregion
+
+            #region 避免程序多次启动
+            _singleInstanceManager = new SingleInstanceManager("SWRIS");
+            // 检查是否已有实例运行
+            if (_singleInstanceManager.IsAlreadyRunning())
+            {
+                // 切换到已有实例
+                _singleInstanceManager.SwitchToExistingInstance();
+                Shutdown();
+                return;
+            }
+
+            #endregion
+
+            Config = ConfigHelper.Read();
+            if (Config == null)
+            {
+                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))
+            {
+                MessageBox.Show("当前配置的端口已被占用,请更换端口或关闭占用该端口的程序", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+                Environment.Exit(0);
+            }
+          
+            if (Settings.Default.IsDebug)
+            {
+                SetAutoStart(false);
+            }
+            else
+            {
+                InitSoftAuthorize();
+                SetAutoStart(true);
+                // 创建桌面快捷方式
+                Tools.CreateShortcut(Assembly.GetExecutingAssembly().Location, Config.AppName);
+            }
+            // 显示启动画面
+            var splashScreen = new SplashScreen("Resources/loading.png");
+            splashScreen.Show(false, true);
+            Task.Run(async () =>
+            {
+                SqLiteBaseRepository.CreateDatabase();
+
+                //初始化TcpServer
+                TcpServer = new TcpServerFrame(Config.IpAddress, Config.Port);
+
+                //初始化标定模块
+                Calibration = new CalibrationCore(Config.Calibration);
+
+                //初始化数据中心能模块
+                DataCenter = new DataCenterModel(Config.Equipments, Config.Calibration.Limits);
+
+                //初始化清理计划模块
+                HistoryCleanup = new HistoryCleanupService();
+                HistoryCleanup.StartScheduledCleanup();
+
+                // 数据加载完成后,在UI线程更新
+                await Current.Dispatcher.InvokeAsync(() =>
+                {
+                    splashScreen.Close(TimeSpan.FromMilliseconds(200));
+                    var mainWindow = new MainWindow
+                    {
+                        Height = SystemParameters.PrimaryScreenHeight,
+                        Width = SystemParameters.PrimaryScreenWidth
+                    };
+                    mainWindow.Activate();
+                    mainWindow.Show();
+                });
+            });
+            base.OnStartup(e);
+        }
+        public static bool UpdateEquipment(EquipmentModel equipment)
+        {
+            if (equipment == null) return false;
+
+            var existingEquipment = Config.Equipments.FirstOrDefault(e => e.IpAddress == equipment.IpAddress);
+            if (existingEquipment == null) return false;
+
+            // 更新设备信息
+            Mapping.AssignFrom(existingEquipment, equipment);
+
+            return true;
+        }
+        private void StartHttpServer()
+        {
+            //添加防火墙8002端口允许出站规则
+            FirewallHelper.CreateTCPOutRule(Assembly.GetEntryAssembly().Location, remotePorts: "8002");
+            var config = new HttpSelfHostConfiguration($"http://{Tools.GetIpAddress()}:{8002}");
+            config.MapHttpAttributeRoutes();
+            config.Routes.MapHttpRoute(name: "DefaultApi",
+                                       routeTemplate: "api/{controller}/{action}",
+                                       defaults: new { id = RouteParameter.Optional });
+            new HttpSelfHostServer(config).OpenAsync();
+        }
+
+        private void App_Startup(object sender, StartupEventArgs e)
+        {
+            //UI线程未捕获异常处理事件
+            DispatcherUnhandledException += new DispatcherUnhandledExceptionEventHandler(App_DispatcherUnhandledException);
+            //Task线程内未捕获异常处理事件
+            TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
+            //非UI线程未捕获异常处理事件
+            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
+        }
+
+        void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
+        {
+            try
+            {
+                LogHelper.Error("UI线程异常:" + e.Exception.Message, e.Exception);
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error("UI线程异常trycatch:" + ex.Message, ex);
+            }
+            finally
+            {
+                e.Handled = true;
+            }
+        }
+        void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
+        {
+            try
+            {
+                var exception = e.ExceptionObject as Exception;
+                if (exception != null)
+                {
+                    LogHelper.Error("非UI线程发生致命错误", exception);
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error("非UI线程发生致命错误trycatch", ex);
+            }
+            finally
+            {
+                //此时程序出现严重异常,将强制结束退出
+                Current.Shutdown();
+                Process.Start(ResourceAssembly.Location);//重启软件
+            }
+        }
+
+        void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
+        {
+            try
+            {
+                var exception = e.Exception as Exception;
+                if (exception != null)
+                {
+                    LogHelper.Error("Task线程异常:" + exception.Message, exception);
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error("Task线程异常trycatch:" + ex.Message, ex);
+            }
+            finally
+            {
+                e.SetObserved();
+            }
+        }
+        /// <summary>  
+        /// 修改程序在注册表中的键值  
+        /// </summary>  
+        /// <param name="isAuto">true:开机启动,false:不开机自启</param> 
+        public static void SetAutoStart(bool isAuto, bool showInfo = true)
+        {
+            string appName = Config.AppName;
+            try
+            {
+                using (RegistryKey rLocal = Registry.CurrentUser)
+                using (RegistryKey rRun = rLocal.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", true))
+                {
+                    if (rRun == null) return;
+
+                    if (isAuto)
+                    {
+                        string currentPath = Process.GetCurrentProcess().MainModule.FileName;
+                        object existingValue = rRun.GetValue(appName);
+
+                        // 只有当值不存在或路径不同时才更新
+                        if (existingValue == null || !currentPath.Equals(existingValue.ToString(), StringComparison.OrdinalIgnoreCase))
+                        {
+                            rRun.SetValue(appName, currentPath);
+                        }
+                    }
+                    else
+                    {
+                        // 如果存在则删除
+                        if (rRun.GetValue(appName) != null)
+                        {
+                            rRun.DeleteValue(appName, false);
+                        }
+                    }
+                }
+            }
+            catch (UnauthorizedAccessException)
+            {
+                if (showInfo)
+                {
+                    MessageBox.Show("需要管理员权限才能修改开机启动设置", "权限不足");
+                }
+            }
+            catch (Exception ex)
+            {
+                if (showInfo)
+                {
+                    MessageBox.Show($"修改开机启动设置时出错: {ex.Message}", "错误");
+                }
+            }
+        }
+        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();
+            Calibration?.Dispose();
+            HistoryCleanup?.Dispose();
+            _singleInstanceManager?.Dispose();
+            // 恢复此线程曾经阻止的系统休眠和屏幕关闭。
+            SystemSleep.RestoreForCurrentThread();
+            base.OnExit(e);
+        }
+    }
+}

+ 108 - 0
MainWindow.xaml

@@ -0,0 +1,108 @@
+<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" 
+            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"
+            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"/>
+    </Window.Resources>
+    <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}"/>
+                </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"/>
+                    </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>
+                    <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>
+            </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>
+    </Grid>
+</pu:WindowX>

+ 588 - 0
MainWindow.xaml.cs

@@ -0,0 +1,588 @@
+using Panuon.WPF.UI;
+using SWRIS.Core;
+using SWRIS.Dtos;
+using SWRIS.Enums;
+using SWRIS.Events;
+using SWRIS.Extensions;
+using SWRIS.Models;
+using SWRIS.Models.ViewModel;
+using SWRIS.Pages;
+using SWRIS.Repository;
+using System;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Speech.Synthesis;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Markup;
+
+namespace SWRIS
+{
+    /// <summary>
+    /// MainWindow.xaml 的交互逻辑
+    /// </summary>
+    public partial class MainWindow : WindowX, IComponentConnector
+    {
+        private readonly SpeechSynthesizer speech;
+        public MainViewModel MainView { get; set; }
+
+        private readonly IRecordRepository _recordRepository;
+        private readonly IAlarmRepository _alarmRepository;
+        public MainWindow()
+        {
+            InitializeComponent();
+            _recordRepository = new RecordRepository();
+            _alarmRepository = new AlarmRepository();
+            speech = new SpeechSynthesizer()
+            {
+                Rate = 1,
+                Volume = 100
+            };
+            MainView = new MainViewModel()
+            {
+                AppName = App.Config.AppName,
+                Version = App.Config.Version,
+                Copyright = App.Config.Copyright,
+                SwitchInstances = App.Config.SwitchInstances
+            };
+            Application.Current.MainWindow = this;
+            App.TcpServer.ClientConnected += TcpServer_ClientConnected;
+            App.TcpServer.ClientDisconnected += TcpServer_ClientDisconnected;
+            App.TcpServer.AlarmDataReceived += TcpServer_AlarmDataReceived;
+            App.TcpServer.DetectionDataReceived += TcpServer_DetectionDataReceived;
+            App.TcpServer.RealTimeDataReceived += TcpServer_RealTimeDataReceived;
+            App.TcpServer.FaultDataReceived += TcpServer_FaultDataReceived;
+            App.TcpServer.DetectionRawDataReceived += TcpServer_DetectionRawDataReceived;
+            App.TcpServer.DetectionRawDataResultReceived += TcpServer_DetectionRawDataResultReceived;
+            App.TcpServer.DetectionStatusResultReceived += TcpServer_DetectionStatusResultReceived;
+            App.TcpServer.DebugMessageReceived += TcpServer_DebugMessageReceived;
+            App.TcpServer.HeartbeatReceived += TcpServer_HeartbeatReceived;
+            App.TcpServer.UpgradedRequestResultReceived += TcpServer_UpgradedRequestResultReceived;
+            App.Calibration.ConnectionStateChanged += Calibration_ConnectionStateChanged;
+            App.Calibration.LimitStateChanged += Calibration_LimitStateChanged;
+
+
+            App.TcpServer.Start();
+
+            DataContext = MainView;
+        }
+
+
+
+        /// <summary>
+        /// 接收心跳数据
+        /// </summary>
+        private void TcpServer_HeartbeatReceived(object sender, HeartbeatReceviedEventArgs e)
+        {
+            var equipmentData = App.DataCenter.Equipments.FirstOrDefault(c => c.IpAddress == e.IpAddress);
+            if (equipmentData != null)
+            {
+                equipmentData.IsHeartbeatReceived = true;
+            }
+        }
+        /// <summary>
+        /// 接收调试信息
+        /// </summary>
+        private void TcpServer_DebugMessageReceived(object sender, DebugMessageReceivedEventArgs e)
+        {
+            if (e.IpAddress.IsNullOrEmpty() && e.SerialNo.IsNullOrEmpty())
+            {
+                DebugMessageShow(e.Message);
+            }
+            else
+            {
+                DebugMessageShow(e.Message, e.IpAddress, e.SerialNo);
+            }
+        }
+        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));
+                if (equipmentData != null)
+                {
+                    equipmentData.DebugMessage.DateTime = DateTime.Now;
+                    equipmentData.DebugMessage.Message = message;
+                }
+            }
+        }
+        public void DebugMessageShow(string message)
+        {
+            if (App.Config.ShowDebugMessage)
+            {
+                MainView.DebugMessage.DateTime = DateTime.Now;
+                MainView.DebugMessage.Message = message;
+            }
+        }
+        /// <summary>
+        /// 标定限位状态更改
+        /// </summary>
+        private void Calibration_LimitStateChanged(object sender, LimitData limitData)
+        {
+            foreach (var equipment in App.DataCenter.Equipments)
+            {
+                foreach (var limit in equipment.Limits)
+                {
+                    if (limit.Name == limitData.Name && limit.IsEnable)
+                    {
+                        if (limitData.State == LimitState.AtLimit)
+                        {
+                            limit.State = LimitState.AtLimit;
+                            // 当前运行状态添加预标定状态
+                            equipment.RunningStatus |= RunningStatus.PreCalibration;
+                            if (App.TcpServer.SendRealTimeAbsolutePositionData((float)limitData.Position, equipment.SerialNo, equipment.ClientSocket))
+                            {
+                                DebugMessageShow($"{limitData.Name}标定成功", equipment.IpAddress, equipment.SerialNo);
+                            }
+                        }
+                        else if (limitData.State == LimitState.NotInLimit)
+                        {
+                            limit.State = LimitState.NotInLimit;
+                        }
+                    }
+                }
+            }
+        }
+        /// <summary>
+        /// 标定模块连接状态更改
+        /// </summary>
+        private void Calibration_ConnectionStateChanged(object sender, bool isConnect)
+        {
+            foreach (var equipment in App.DataCenter.Equipments)
+            {
+                foreach (var limit in equipment.Limits)
+                {
+                    if (isConnect)
+                    {
+                        if (limit.State == LimitState.Offline)
+                        {
+                            limit.State = LimitState.NotInLimit;
+                        }
+                    }
+                    else
+                    {
+                        if (limit.State != LimitState.Offline)
+                        {
+                            limit.State = LimitState.Offline;
+                        }
+                    }
+                }
+            }
+
+        }
+        /// <summary>
+        /// 接收检测状态开关结果
+        /// </summary>
+        private void TcpServer_DetectionStatusResultReceived(object sender, DetectionStatusResultReceivedEventArgs e)
+        {
+            var equipmentData = App.DataCenter.Equipments.FirstOrDefault(c => c.IpAddress == e.IpAddress);
+            if (equipmentData != null)
+            {
+                if (equipmentData.RunningStatus.HasFlag(RunningStatus.PreStop))
+                {
+                    equipmentData.RunningStatus = RunningStatus.Stopped;
+                    Application.Current.Dispatcher.Invoke(() =>
+                    {
+                        NoticeBox.Show("自动检测停止", "通知", MessageBoxIcon.Warning, true, 3000);
+                    });
+                }
+                else if (equipmentData.RunningStatus.HasFlag(RunningStatus.PreRunning))
+                {
+                    equipmentData.RunningStatus = RunningStatus.RunningNormal;
+                    Application.Current.Dispatcher.Invoke(() =>
+                    {
+                        NoticeBox.Show("自动检测恢复", "通知", MessageBoxIcon.Success, true, 3000);
+                    });
+                }
+                else if (equipmentData.RunningStatus.HasFlag(RunningStatus.PreCalibration))
+                {
+                    equipmentData.RunningStatus = RunningStatus.RunningNormal;
+                }
+            }
+        }
+        /// <summary>
+        /// 接收故障数据
+        /// </summary>
+        private void TcpServer_FaultDataReceived(object sender, FaultDataReceivedEventArgs e)
+        {
+            var equipmentData = App.DataCenter.Equipments.FirstOrDefault(c => c.IpAddress == e.IpAddress);
+            equipmentData?.Faults.Add(e.FaultData);
+        }
+        /// <summary>
+        /// 接收实时数据
+        /// </summary>
+        private void TcpServer_RealTimeDataReceived(object sender, RealTimeDataReceivedEventArgs e)
+        {
+            var equipmentData = App.DataCenter.Equipments.FirstOrDefault(c => c.IpAddress == e.IpAddress);
+            if (equipmentData != null)
+            {
+                equipmentData.Speed = Math.Round(e.RealTimeData.Speed * 60, 2);
+                equipmentData.Position = e.RealTimeData.Position;
+                equipmentData.RunningStatus = e.RealTimeData.Status;
+                equipmentData.AbsolutePosition = e.RealTimeData.AbsolutePosition;
+                equipmentData.LiftHeight = (equipmentData.RopeLength - equipmentData.AbsolutePosition) / equipmentData.LiftHightRatio;
+                equipmentData.Direction = equipmentData.Speed > 0 ? DirectionState.Forward :
+                    (equipmentData.Speed == 0f ? DirectionState.Stoped : DirectionState.Reverse);
+                Console.WriteLine(equipmentData.AbsolutePosition);
+                equipmentData.AddSpeedData(equipmentData.Speed);
+            }
+        }
+        /// <summary>
+        /// 客户端连接成功
+        /// </summary>
+        private void TcpServer_ClientConnected(object sender, ClientConnectedEventArgs e)
+        {
+            var equipment = App.Config.Equipments.FirstOrDefault(c => c.IpAddress == e.IpAddress);
+            if (equipment == null)
+            {
+                LogHelper.Error($"未找到设备,IP地址:{e.IpAddress}");
+                return;
+            }
+            var server = (TcpServerFrame)sender;
+            server.SendTurnLiveStreamData(true, equipment.SerialNo, e.ClientSocket);
+            Thread.Sleep(20);
+            server.SendSetClockData(DateTime.Now.DateTimeToTimestamp(), equipment.SerialNo, e.ClientSocket);
+            var equipmentData = App.DataCenter.Equipments.FirstOrDefault(c => c.IpAddress == e.IpAddress);
+            if (equipmentData != null)
+            {
+                equipmentData.IsConnect = true;
+                equipmentData.ClientSocket = e.ClientSocket;
+                if (equipmentData.RunningStatus != RunningStatus.RunningNormal)
+                {
+                    equipmentData.RunningStatus |= RunningStatus.RunningNormal;
+                }
+            }
+        }
+        /// <summary>
+        /// 客户端连接断开
+        /// </summary>
+        private void TcpServer_ClientDisconnected(object sender, ClientDisconnectedEventArgs e)
+        {
+            var equipmentData = App.DataCenter.Equipments.FirstOrDefault(c => c.IpAddress == e.IpAddress);
+            if (equipmentData != null)
+            {
+                equipmentData.IsConnect = false;
+                equipmentData.ClientSocket = null;
+                equipmentData.Speed = 0;
+                equipmentData.AbsolutePosition = 0;
+                equipmentData.Position = 0;
+                equipmentData.Direction = DirectionState.Stoped;
+            }
+        }
+        /// <summary>
+        /// 接收检测结果
+        /// </summary>
+        private void TcpServer_DetectionDataReceived(object sender, DetectionDataReceivedEventArgs e)
+        {
+            var equipmentData = App.DataCenter.Equipments.FirstOrDefault(c => c.IpAddress == e.IpAddress);
+            if (equipmentData != null)
+            {
+                e.DetectionData.DetectionNo = equipmentData.DetectionNo;
+                equipmentData.DetectionData = e.DetectionData;
+            }
+        }
+        /// <summary>
+        /// 接检测原始数据结果
+        /// </summary>
+        private void TcpServer_DetectionRawDataResultReceived(object sender, DetectionRawDataResultReceivedEventArgs e)
+        {
+            var equipmentData = App.DataCenter.Equipments.FirstOrDefault(c => c.IpAddress == e.IpAddress);
+            if (equipmentData != null)
+            {
+                // 处理检测原始结果数据
+                if (e.DetectionRawResultData.Code == 0)
+                {
+                    if (equipmentData.DetectionData != null)
+                    {
+                        equipmentData.DetectionData.RawResultData = e.DetectionRawResultData;
+                    }
+                }
+                else
+                {
+                    LogHelper.Error($"获取检测原始数据失败,错误码:{e.DetectionRawResultData.Code}");
+                }
+            }
+        }
+        /// <summary>
+        /// 接受检测结果原始数据
+        /// </summary>
+        private async void TcpServer_DetectionRawDataReceived(object sender, DetectionRawDataReceivedEventArgs e)
+        {
+            var equipmentData = App.DataCenter.Equipments.FirstOrDefault(c => c.IpAddress == e.IpAddress);
+            if (equipmentData != null)
+            {
+                if (equipmentData.DisableAlarm)
+                {
+                    DebugMessageShow($"获取检测结果原始数据结束", equipmentData.IpAddress, equipmentData.SerialNo);
+                    return;
+                }
+                await Task.Run(() =>
+                {
+                    if (equipmentData.DetectionData != null && equipmentData.DetectionData.RawResultData != null)
+                    {
+                        DebugMessageShow($"数据接收:{e.DetectionRawData.PacketNumber}/{e.DetectionRawData.TotalPackets}", equipmentData.IpAddress, equipmentData.SerialNo);
+                        if (e.DetectionRawData.PacketNumber == 1)
+                        {
+                            equipmentData.DetectionData.RawDataReceiving = true;
+                            equipmentData.DetectionData.RawData = new MemoryStream();
+                            equipmentData.DetectionData.RawData.Write(e.DetectionRawData.Data, 0, e.DetectionRawData.Data.Length);
+                        }
+                        else if (e.DetectionRawData.PacketNumber <= e.DetectionRawData.TotalPackets)
+                        {
+                            // 添加原始数据到检测数据中
+                            equipmentData.DetectionData.RawData.Write(e.DetectionRawData.Data, 0, e.DetectionRawData.Data.Length);
+                            // 如果是最后一包数据,处理检测数据
+                            if (e.DetectionRawData.PacketNumber == e.DetectionRawData.TotalPackets)
+                            {
+                                // 生成数据文件路径
+                                (string absolutePath, string relativePath) = DatFileHandler.GetDatFilePath(equipmentData.SerialNo);
+                                // 处理检测数据逻辑
+                                var record = new RecordDto
+                                {
+                                    RopeName = equipmentData.RopeName,
+                                    RopeNumber = equipmentData.RopeNumber,
+                                    StartPoint = equipmentData.DetectionData.StartPoint,
+                                    EndPoint = equipmentData.DetectionData.EndPoint,
+                                    DetectedSpeed = equipmentData.DetectionData.DetectedSpeed,
+                                    DetectionLength = equipmentData.DetectionData.DetectionLength,
+                                    StartTime = equipmentData.DetectionData.StartTime.TimestampToDateTime(),
+                                    EndTime = equipmentData.DetectionData.EndTime.TimestampToDateTime(),
+                                    SensorCount = equipmentData.DetectionData.RawResultData.SensorCount,
+                                    SamplingStep = equipmentData.DetectionData.RawResultData.SamplingStep,
+                                    DataFilePath = relativePath,
+                                    InUseSensors = string.Join(",", equipmentData.InUseSensor)
+                                };
+                                foreach (var damage in equipmentData.DetectionData.Damages)
+                                {
+                                    var alarmCount = _alarmRepository.AddAlarm(new AlarmDataModel
+                                    {
+                                        DamageLevel = damage.DamageLevel,
+                                        DamagePosition = damage.DamagePoint,
+                                        DamageValue = damage.DamageValue,
+                                        RopeNumber = equipmentData.RopeNumber
+                                    }, AlarmSourceType.Detection, App.Config.DamageExtent);
+                                    if (alarmCount >= App.Config.AlarmValidCount)
+                                    {
+                                        record.Damages.Add(new DamageDto
+                                        {
+                                            DamageLevel = damage.DamageLevel,
+                                            DamagePoint = damage.DamagePoint,
+                                            DamageValue = damage.DamageValue,
+                                        });
+                                        record.DamageCount++;
+                                        //纠正健康度 以客户端上传的损伤数据 重新计算
+                                        if ((int)record.RiskLevel < (int)damage.DamageLevel)
+                                        {
+                                            record.RiskLevel = (RiskLevel)damage.DamageLevel;
+                                        }
+                                    }
+                                }
+                                equipmentData.RiskLevel = record.RiskLevel;
+                                if (record.DamageCount > 0)
+                                {
+                                    int? recordId = _recordRepository.CreateRecord(record);
+                                    if (recordId.HasValue)
+                                    {
+                                        record.Id = recordId.Value;
+                                        AddRecordToPageRecords(record);
+                                        if (!DatFileHandler.CreateDatFile(absolutePath, equipmentData.DetectionData.RawData))
+                                        {
+                                            LogHelper.Error($"创建检测数据文件失败,路径:{absolutePath}");
+                                        }
+                                        DebugMessageShow($"检测数据记录成功", equipmentData.IpAddress, equipmentData.SerialNo);
+                                        if ((int)record.RiskLevel >= (int)App.Config.SoundRiskLevel)
+                                        {
+                                            Application.Current.Dispatcher.Invoke(() =>
+                                            {
+                                                speech.SpeakAsync($"{equipmentData.RopeName},检测到{record.DamageCount}处损伤");
+                                            });
+                                        }
+                                    }
+                                    else
+                                    {
+                                        LogHelper.Error("检测数据记录失败");
+                                    }
+                                }
+                                else
+                                {
+                                    DebugMessageShow($"检测完成,未发现损伤,本次检测不做记录。", equipmentData.IpAddress, equipmentData.SerialNo);
+                                }
+                                equipmentData.DetectionData.RawDataReceiving = false;
+                            }
+                        }
+                    }
+                });
+            }
+        }
+        /// <summary>
+        /// 接收报警数据
+        /// </summary>
+        private void TcpServer_AlarmDataReceived(object sender, AlarmDataReceivedEventArgs e)
+        {
+            var equipmentData = App.DataCenter.Equipments.FirstOrDefault(c => c.IpAddress == e.IpAddress);
+            if (equipmentData != null && equipmentData.IsSpeedStable && !equipmentData.DisableAlarm)
+            {
+                Dispatcher.InvokeAsync(() =>
+                {
+                    e.AlarmData.RopeNumber = equipmentData.RopeNumber;
+                    var alarmCount = _alarmRepository.AddAlarm(e.AlarmData, AlarmSourceType.RealTime, App.Config.DamageExtent);
+                    if (alarmCount >= App.Config.AlarmValidCount)
+                    {
+                        equipmentData.AddAlarm(e.AlarmData);
+                    }
+                });
+            }
+        }
+        private void TcpServer_UpgradedRequestResultReceived(object sender, UpgradedResultReceivedEventArgs e)
+        {
+            Dispatcher.InvokeAsync(async () =>
+            {
+                if (e.Code == 0)
+                {
+                    await Task.Delay(2000);
+                    string url = $"http://192.168.1.200"; // 替换为您的网页地址
+                    Extensions.Tools.OpenUrl(url);
+                }
+            });
+        }
+        public void AddRecordToPageRecords(RecordDto record)
+        {
+            Application.Current.Dispatcher.Invoke(() =>
+            {
+                if (main_frame.Content is Page currentPage && currentPage != null)
+                {
+                    var simpleRecord = record.ToSimpleRecordDto();
+                    switch (currentPage.Title)
+                    {
+                        case "OneRopePage":
+                            if (currentPage.DataContext is OneRopeViewModel oneRopeView)
+                            {
+                                oneRopeView.AddRecord(simpleRecord);
+                            }
+                            break;
+                        case "TwoRopesPage":
+                            if (currentPage.DataContext is TwoRopesViewModel twoRopesView)
+                            {
+                                twoRopesView.AddRecord(simpleRecord);
+                            }
+                            break;
+                        case "ThreeRopesPage":
+                            if (currentPage.DataContext is ThreeRopesViewModel threeRopesView)
+                            {
+                                threeRopesView.AddRecord(simpleRecord);
+                            }
+                            break;
+                        case "FourRopesPage":
+                            if (currentPage.DataContext is FourRopesViewModel fourRopesView)
+                            {
+                                fourRopesView.AddRecord(simpleRecord);
+                            }
+                            break;
+                    }
+                }
+            });
+        }
+        private void WindowX_Loaded(object sender, RoutedEventArgs e)
+        {
+            NavigateToPage("Home");
+        }
+        public void NavigatePage(Uri uri)
+        {
+            main_frame.NavigationService.Navigate(uri);
+        }
+        private void NavigateToPage(object pageName)
+        {
+            switch (pageName)
+            {
+                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;
+                    }
+                    break;
+                case "Record":
+                    MainView.CurrentPage = "Record";
+                    main_frame.NavigationService.Navigate(new Uri($"Pages/RecordPage.xaml", uriKind: UriKind.Relative));
+                    break;
+            }
+        }
+        private void Menu_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            if (sender is TextBlock menuItem)
+            {
+                CleanNavigation(main_frame);
+                NavigateToPage(menuItem?.Tag);
+            }
+        }
+        private void CleanNavigation(Frame frame)
+        {
+            while (frame.CanGoBack)
+                frame.RemoveBackEntry();
+        }
+        private void StackPanel_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            if (e.ClickCount == 2)
+            {
+                Left = 0;
+                Top = 0;
+            }
+        }
+        private void Setting_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            SettingDialog settingDialog = new SettingDialog();
+            if (settingDialog.ShowDialog(true) == true)
+            {
+                if (settingDialog.IsAutoStart)
+                {
+                    App.RestartApplication();
+                }
+            }
+        }
+
+        private void Window_Closing(object sender, CancelEventArgs e)
+        {
+            App.TcpServer?.Dispose();
+            speech?.Dispose();
+        }
+
+        private void Minimize_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            WindowState = WindowState.Minimized;
+        }
+
+        private void Close_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            var result = MessageBoxX.Show(this, "退出后将无法进行实时检测,是否确认退出程序?", "警告",
+                MessageBoxButton.YesNo,
+                MessageBoxIcon.Warning, DefaultButton.YesOK);
+            if (result == MessageBoxResult.Yes)
+            {
+                Close();
+            }
+        }
+
+        private void SwitchToInstance_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            if (sender is Border instanceBorder && instanceBorder.Tag != null)
+            {
+                App.SwitchToExistingInstance(instanceBorder.Tag.ToString());
+            }
+        }
+    }
+}

+ 254 - 0
Mapping.cs

@@ -0,0 +1,254 @@
+using SWRIS.Dtos;
+using SWRIS.Enums;
+using SWRIS.Models;
+using SWRIS.Models.Data;
+using SWRIS.Models.ViewModel;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+
+namespace SWRIS
+{
+    public static class Mapping
+    {
+        public static void ToEquipmentModel(this EquipmentSettingViewModel equipmentSettingViewModel, EquipmentModel equipmentModel)
+        {
+            if (equipmentSettingViewModel == null || equipmentModel == null)
+                return;
+
+            // 赋值基本属性
+            equipmentModel.RopeNumber = equipmentSettingViewModel.RopeNumber;
+            equipmentModel.RopeName = equipmentSettingViewModel.RopeName;
+            equipmentModel.IpAddress = equipmentSettingViewModel.IpAddress;
+            equipmentModel.SerialNo = equipmentSettingViewModel.SerialNo;
+            equipmentModel.Supplier = equipmentSettingViewModel.Supplier;
+            equipmentModel.LayType = (LayType)equipmentSettingViewModel.LayType;
+            equipmentModel.LiftHightRatio = equipmentSettingViewModel.LiftHightRatio;
+            equipmentModel.SoundRiskLevel = equipmentSettingViewModel.SoundRiskLevel;
+            equipmentModel.WireMaterialType = (WireMaterialType)equipmentSettingViewModel.WireMaterialType;
+            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)
+            {
+                equipmentModel.Parameter = new ParameterModel();
+                return;
+            }
+
+            // 如果目标Parameter为Null,创建新实例
+            if (equipmentModel.Parameter == null)
+            {
+                equipmentModel.Parameter = new ParameterModel();
+            }
+
+            // 赋值Parameter属性
+            var sourceParam = equipmentSettingViewModel.Parameter;
+            var targetParam = equipmentModel.Parameter;
+
+            targetParam.SensorCount = sourceParam.SensorCount;
+            targetParam.SamplingStep = sourceParam.SamplingStep;
+            targetParam.MainBoardSoftwareVersion = sourceParam.MainBoardSoftwareVersion;
+            targetParam.FrequencyDivisionFactor = sourceParam.FrequencyDivisionFactor;
+            targetParam.TimeDomainFrequency = sourceParam.TimeDomainFrequency;
+            targetParam.DamageThreshold = sourceParam.DamageThreshold;
+            targetParam.ScrapUpperLimit = sourceParam.ScrapUpperLimit;
+            targetParam.BackMagnetLength = sourceParam.BackMagnetLength;
+            targetParam.ValueCoefficient = sourceParam.ValueCoefficient;
+            targetParam.FrontMagnetLength = sourceParam.FrontMagnetLength;
+            targetParam.EffectiveStrokeLength = sourceParam.EffectiveStrokeLength;
+            targetParam.ZeroPositionCorrectionOffset = sourceParam.ZeroPositionCorrectionOffset;
+            targetParam.SoundLightAlarmAutoResetMode = sourceParam.SoundLightAlarmAutoResetMode;
+            targetParam.ZeroPositionCorrectionDuration = sourceParam.ZeroPositionCorrectionDuration;
+            targetParam.SystemTime = sourceParam.SystemTime;
+            targetParam.AlarmValue = sourceParam.AlarmValue;
+            targetParam.TwistFactor = sourceParam.TwistFactor;
+            targetParam.WarningValue = sourceParam.WarningValue;
+            targetParam.WireRopeType = sourceParam.WireRopeType;
+            targetParam.WireRopeCount = sourceParam.WireRopeCount;
+            targetParam.WireRopeLength = sourceParam.WireRopeLength;
+            targetParam.WireRopeDiameter = sourceParam.WireRopeDiameter;
+            targetParam.EncoderDirection = sourceParam.EncoderDirection;
+            targetParam.WireRopeStrandCount = sourceParam.WireRopeStrandCount;
+            targetParam.WireRopeStrandWireCount = sourceParam.WireRopeStrandWireCount;
+        }
+
+        // 可选:创建新实例的扩展方法
+        public static EquipmentModel ToEquipmentModel(this EquipmentSettingViewModel equipmentSettingViewModel)
+        {
+            var equipmentModel = new EquipmentModel();
+            equipmentSettingViewModel.ToEquipmentModel(equipmentModel);
+            return equipmentModel;
+        }
+        public static EquipmentSettingViewModel ToEquipmentSettingViewModel(this EquipmentModel equipmentModel)
+        {
+            var equipmentSettingViewModel = new EquipmentSettingViewModel();
+            equipmentModel.ToEquipmentSettingViewModel(equipmentSettingViewModel);
+            return equipmentSettingViewModel;
+        }
+        public static void ToEquipmentSettingViewModel(this EquipmentModel equipmentModel, EquipmentSettingViewModel equipmentSettingViewModel)
+        {
+            if (equipmentModel == null || equipmentSettingViewModel == null)
+                return;
+
+            // 赋值基本属性
+            equipmentSettingViewModel.RopeNumber = equipmentModel.RopeNumber;
+            equipmentSettingViewModel.RopeName = equipmentModel.RopeName;
+            equipmentSettingViewModel.IpAddress = equipmentModel.IpAddress;
+            equipmentSettingViewModel.SerialNo = equipmentModel.SerialNo;
+            equipmentSettingViewModel.Supplier = equipmentModel.Supplier;
+            equipmentSettingViewModel.LayType = (int)equipmentModel.LayType;
+            equipmentSettingViewModel.LiftHightRatio = equipmentModel.LiftHightRatio;
+            equipmentSettingViewModel.SoundRiskLevel = equipmentModel.SoundRiskLevel;
+            equipmentSettingViewModel.WireMaterialType = (int)equipmentModel.WireMaterialType;
+            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)
+            {
+                equipmentSettingViewModel.Parameter = new ParameterData();
+                return;
+            }
+
+            // 如果目标Parameter为null,创建新实例
+            if (equipmentSettingViewModel.Parameter == null)
+            {
+                equipmentSettingViewModel.Parameter = new ParameterData();
+            }
+
+            // 赋值Parameter属性
+            var sourceParam = equipmentModel.Parameter;
+            var targetParam = equipmentSettingViewModel.Parameter;
+
+            targetParam.MainBoardSoftwareVersion = sourceParam.MainBoardSoftwareVersion;
+            targetParam.SensorCount = sourceParam.SensorCount;
+            targetParam.SamplingStep = sourceParam.SamplingStep;
+            targetParam.FrequencyDivisionFactor = sourceParam.FrequencyDivisionFactor;
+            targetParam.TimeDomainFrequency = sourceParam.TimeDomainFrequency;
+            targetParam.DamageThreshold = sourceParam.DamageThreshold;
+            targetParam.ScrapUpperLimit = sourceParam.ScrapUpperLimit;
+            targetParam.FrontMagnetLength = sourceParam.FrontMagnetLength;
+            targetParam.BackMagnetLength = sourceParam.BackMagnetLength;
+            targetParam.ValueCoefficient = sourceParam.ValueCoefficient;
+            targetParam.EffectiveStrokeLength = sourceParam.EffectiveStrokeLength;
+            targetParam.ZeroPositionCorrectionDuration = sourceParam.ZeroPositionCorrectionDuration;
+            targetParam.ZeroPositionCorrectionOffset = sourceParam.ZeroPositionCorrectionOffset;
+            targetParam.WarningValue = sourceParam.WarningValue;
+            targetParam.AlarmValue = sourceParam.AlarmValue;
+            targetParam.SoundLightAlarmAutoResetMode = sourceParam.SoundLightAlarmAutoResetMode;
+            targetParam.WireRopeType = sourceParam.WireRopeType;
+            targetParam.WireRopeCount = sourceParam.WireRopeCount;
+            targetParam.WireRopeLength = sourceParam.WireRopeLength;
+            targetParam.WireRopeDiameter = sourceParam.WireRopeDiameter;
+            targetParam.WireRopeStrandCount = sourceParam.WireRopeStrandCount;
+            targetParam.WireRopeStrandWireCount = sourceParam.WireRopeStrandWireCount;
+            targetParam.EncoderDirection = sourceParam.EncoderDirection;
+            targetParam.SystemTime = sourceParam.SystemTime;
+            targetParam.TwistFactor = sourceParam.TwistFactor;
+        }
+        public static void ToEquipmentDataModel(this EquipmentModel equipmentModel, EquipmentDataModel equipmentDataModel)
+        {
+            if (equipmentModel == null || equipmentDataModel == null)
+                return;
+
+            // 赋值基本属性
+            equipmentDataModel.RopeNumber = equipmentModel.RopeNumber;
+            equipmentDataModel.RopeName = equipmentModel.RopeName;
+            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)
+            {
+                equipmentDataModel.SensorCount = equipmentModel.Parameter.SensorCount;
+                equipmentDataModel.RopeLength = equipmentModel.Parameter.WireRopeLength;
+                equipmentDataModel.SamplingStep = equipmentModel.Parameter.SamplingStep;
+            }
+        }
+        public static EquipmentDataModel ToEquipmentDataModel(this EquipmentModel equipmentModel)
+        {
+            var equipmentDataModel = new EquipmentDataModel();
+            equipmentModel.ToEquipmentDataModel(equipmentDataModel);
+            return equipmentDataModel;
+        }
+
+        // 可选:批量转换方法
+        public static ObservableCollection<EquipmentDataModel> ToEquipmentDataModels(this ObservableCollection<EquipmentModel> equipmentModels)
+        {
+            var result = new ObservableCollection<EquipmentDataModel>();
+            if (equipmentModels == null) return result;
+
+            foreach (var equipmentModel in equipmentModels)
+            {
+                result.Add(equipmentModel.ToEquipmentDataModel());
+            }
+
+            return result;
+        }
+        public static SimpleRecordDto ToSimpleRecordDto(this RecordDto record)
+        {
+            if (record == null)
+                return null;
+            var simpleRecord = new SimpleRecordDto
+            {
+                Id = record.Id ?? 0,
+                RopeName = record.RopeName,
+                Time = record.StartTime,
+                RiskLevel = record.RiskLevel,
+                Description = $"检测出{record.DamageCount}处损伤",
+                DetectionLength = record.DetectionLength
+            };
+            return simpleRecord;
+        }
+        public static ObservableCollection<SimpleRecordDto> ToSimpleRecordDtos(this List<RecordDto> records)
+        {
+            var result = new ObservableCollection<SimpleRecordDto>();
+            if (records == null) return result;
+
+            foreach (var record in records)
+            {
+                result.Add(record.ToSimpleRecordDto());
+            }
+
+            return result;
+        }
+        public static TTarget AssignFrom<TSource, TTarget>(this TTarget target, TSource source)
+           where TTarget : class
+           where TSource : class
+        {
+            if (source == null || target == null)
+            {
+                return target;
+            }
+            var sourceProperties = typeof(TSource).GetProperties();
+            var targetProperties = typeof(TTarget).GetProperties();
+            foreach (var sourceProp in sourceProperties)
+            {
+                var targetProp = targetProperties.FirstOrDefault(p => p.Name == sourceProp.Name && p.PropertyType == sourceProp.PropertyType);
+                if (targetProp != null && targetProp.CanWrite)
+                {
+                    var value = sourceProp.GetValue(source);
+                    targetProp.SetValue(target, value);
+                }
+            }
+            return target;
+        }
+        public static TTarget CloneTo<TSource, TTarget>(this TSource source)
+            where TTarget : class, new()
+            where TSource : class
+        {
+            if (source == null)
+            {
+                return null;
+            }
+            var target = new TTarget();
+            return target.AssignFrom(source);
+        }
+
+    }
+}

+ 25 - 0
OpenTK.dll.config

@@ -0,0 +1,25 @@
+<configuration>
+  <dllmap os="linux" dll="opengl32.dll" target="libGL.so.1"/>
+  <dllmap os="linux" dll="glu32.dll" target="libGLU.so.1"/>
+  <dllmap os="linux" dll="openal32.dll" target="libopenal.so.1"/>
+  <dllmap os="linux" dll="alut.dll" target="libalut.so.0"/>
+  <dllmap os="linux" dll="opencl.dll" target="libOpenCL.so"/>
+  <dllmap os="linux" dll="libX11" target="libX11.so.6"/>
+  <dllmap os="linux" dll="libXi" target="libXi.so.6"/>
+  <dllmap os="linux" dll="SDL2.dll" target="libSDL2-2.0.so.0"/>
+  <dllmap os="osx" dll="opengl32.dll" target="/System/Library/Frameworks/OpenGL.framework/OpenGL"/>
+  <dllmap os="osx" dll="openal32.dll" target="/System/Library/Frameworks/OpenAL.framework/OpenAL" />
+  <dllmap os="osx" dll="alut.dll" target="/System/Library/Frameworks/OpenAL.framework/OpenAL" />
+  <dllmap os="osx" dll="libGLES.dll" target="/System/Library/Frameworks/OpenGLES.framework/OpenGLES" />
+  <dllmap os="osx" dll="libGLESv1_CM.dll" target="/System/Library/Frameworks/OpenGLES.framework/OpenGLES" />
+  <dllmap os="osx" dll="libGLESv2.dll" target="/System/Library/Frameworks/OpenGLES.framework/OpenGLES" />
+  <dllmap os="osx" dll="opencl.dll" target="/System/Library/Frameworks/OpenCL.framework/OpenCL"/>
+  <dllmap os="osx" dll="SDL2.dll" target="libSDL2.dylib"/>
+  <!-- XQuartz compatibility (X11 on Mac) -->
+  <dllmap os="osx" dll="libGL.so.1" target="/usr/X11/lib/libGL.dylib"/>
+  <dllmap os="osx" dll="libX11" target="/usr/X11/lib/libX11.dylib"/>
+  <dllmap os="osx" dll="libXcursor.so.1" target="/usr/X11/lib/libXcursor.dylib"/>
+  <dllmap os="osx" dll="libXi" target="/usr/X11/lib/libXi.dylib"/>
+  <dllmap os="osx" dll="libXinerama" target="/usr/X11/lib/libXinerama.dylib"/>
+  <dllmap os="osx" dll="libXrandr.so.2" target="/usr/X11/lib/libXrandr.dylib"/>
+</configuration>

+ 52 - 0
Properties/AssemblyInfo.cs

@@ -0,0 +1,52 @@
+using System.Reflection;
+using System.Resources;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+// 有关程序集的一般信息由以下
+// 控制。更改这些特性值可修改
+// 与程序集关联的信息。
+[assembly: AssemblyTitle("SWRIS")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("SWRIS")]
+[assembly: AssemblyCopyright("Copyright ©  2025")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 将 ComVisible 设置为 false 会使此程序集中的类型
+//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
+//请将此类型的 ComVisible 特性设置为 true。
+[assembly: ComVisible(false)]
+
+//若要开始生成可本地化的应用程序,请设置
+//.csproj 文件中的 <UICulture>CultureYouAreCodingWith</UICulture>
+//在 <PropertyGroup> 中。例如,如果你使用的是美国英语。
+//使用的是美国英语,请将 <UICulture> 设置为 en-US。  然后取消
+//对以下 NeutralResourceLanguage 特性的注释。  更新
+//以下行中的“en-US”以匹配项目文件中的 UICulture 设置。
+
+//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
+
+
+[assembly: ThemeInfo(
+    ResourceDictionaryLocation.None, //主题特定资源词典所处位置
+                                     //(未在页面中找到资源时使用,
+                                     //或应用程序资源字典中找到时使用)
+    ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置
+                                              //(未在页面中找到资源时使用,
+                                              //、应用程序或任何主题专用资源字典中找到时使用)
+)]
+
+
+// 程序集的版本信息由下列四个值组成: 
+//
+//      主版本
+//      次版本
+//      生成号
+//      修订号
+//
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

+ 223 - 0
Properties/Resources.Designer.cs

@@ -0,0 +1,223 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     此代码由工具生成。
+//     运行时版本:4.0.30319.42000
+//
+//     对此文件的更改可能会导致不正确的行为,并且如果
+//     重新生成代码,这些更改将会丢失。
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace SWRIS.Properties {
+    using System;
+    
+    
+    /// <summary>
+    ///   一个强类型的资源类,用于查找本地化的字符串等。
+    /// </summary>
+    // 此类是由 StronglyTypedResourceBuilder
+    // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
+    // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
+    // (以 /str 作为命令选项),或重新生成 VS 项目。
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources {
+        
+        private static global::System.Resources.ResourceManager resourceMan;
+        
+        private static global::System.Globalization.CultureInfo resourceCulture;
+        
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources() {
+        }
+        
+        /// <summary>
+        ///   返回此类使用的缓存的 ResourceManager 实例。
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager {
+            get {
+                if (object.ReferenceEquals(resourceMan, null)) {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SWRIS.Properties.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+        
+        /// <summary>
+        ///   重写当前线程的 CurrentUICulture 属性,对
+        ///   使用此强类型资源类的所有资源查找执行重写。
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture {
+            get {
+                return resourceCulture;
+            }
+            set {
+                resourceCulture = value;
+            }
+        }
+        
+        /// <summary>
+        ///   查找 System.Drawing.Bitmap 类型的本地化资源。
+        /// </summary>
+        internal static System.Drawing.Bitmap back {
+            get {
+                object obj = ResourceManager.GetObject("back", resourceCulture);
+                return ((System.Drawing.Bitmap)(obj));
+            }
+        }
+        
+        /// <summary>
+        ///   查找 System.Drawing.Bitmap 类型的本地化资源。
+        /// </summary>
+        internal static System.Drawing.Bitmap closed {
+            get {
+                object obj = ResourceManager.GetObject("closed", resourceCulture);
+                return ((System.Drawing.Bitmap)(obj));
+            }
+        }
+        
+        /// <summary>
+        ///   查找 System.Drawing.Bitmap 类型的本地化资源。
+        /// </summary>
+        internal static System.Drawing.Bitmap delete {
+            get {
+                object obj = ResourceManager.GetObject("delete", resourceCulture);
+                return ((System.Drawing.Bitmap)(obj));
+            }
+        }
+        
+        /// <summary>
+        ///   查找 System.Drawing.Bitmap 类型的本地化资源。
+        /// </summary>
+        internal static System.Drawing.Bitmap eqSet {
+            get {
+                object obj = ResourceManager.GetObject("eqSet", resourceCulture);
+                return ((System.Drawing.Bitmap)(obj));
+            }
+        }
+        
+        /// <summary>
+        ///   查找 System.Drawing.Bitmap 类型的本地化资源。
+        /// </summary>
+        internal static System.Drawing.Bitmap equipment {
+            get {
+                object obj = ResourceManager.GetObject("equipment", resourceCulture);
+                return ((System.Drawing.Bitmap)(obj));
+            }
+        }
+        
+        /// <summary>
+        ///   查找 System.Drawing.Bitmap 类型的本地化资源。
+        /// </summary>
+        internal static System.Drawing.Bitmap export {
+            get {
+                object obj = ResourceManager.GetObject("export", resourceCulture);
+                return ((System.Drawing.Bitmap)(obj));
+            }
+        }
+        
+        /// <summary>
+        ///   查找 System.Drawing.Bitmap 类型的本地化资源。
+        /// </summary>
+        internal static System.Drawing.Bitmap loading {
+            get {
+                object obj = ResourceManager.GetObject("loading", resourceCulture);
+                return ((System.Drawing.Bitmap)(obj));
+            }
+        }
+        
+        /// <summary>
+        ///   查找 System.Drawing.Bitmap 类型的本地化资源。
+        /// </summary>
+        internal static System.Drawing.Bitmap logo {
+            get {
+                object obj = ResourceManager.GetObject("logo", resourceCulture);
+                return ((System.Drawing.Bitmap)(obj));
+            }
+        }
+        
+        /// <summary>
+        ///   查找 System.Drawing.Bitmap 类型的本地化资源。
+        /// </summary>
+        internal static System.Drawing.Bitmap logo_loading {
+            get {
+                object obj = ResourceManager.GetObject("logo_loading", resourceCulture);
+                return ((System.Drawing.Bitmap)(obj));
+            }
+        }
+        
+        /// <summary>
+        ///   查找 System.Byte[] 类型的本地化资源。
+        /// </summary>
+        internal static byte[] menu_bg {
+            get {
+                object obj = ResourceManager.GetObject("menu_bg", resourceCulture);
+                return ((byte[])(obj));
+            }
+        }
+        
+        /// <summary>
+        ///   查找 System.Drawing.Bitmap 类型的本地化资源。
+        /// </summary>
+        internal static System.Drawing.Bitmap menu_dec {
+            get {
+                object obj = ResourceManager.GetObject("menu_dec", resourceCulture);
+                return ((System.Drawing.Bitmap)(obj));
+            }
+        }
+        
+        /// <summary>
+        ///   查找 System.Drawing.Bitmap 类型的本地化资源。
+        /// </summary>
+        internal static System.Drawing.Bitmap minimize {
+            get {
+                object obj = ResourceManager.GetObject("minimize", resourceCulture);
+                return ((System.Drawing.Bitmap)(obj));
+            }
+        }
+        
+        /// <summary>
+        ///   查找 System.Drawing.Bitmap 类型的本地化资源。
+        /// </summary>
+        internal static System.Drawing.Bitmap option_bg {
+            get {
+                object obj = ResourceManager.GetObject("option_bg", resourceCulture);
+                return ((System.Drawing.Bitmap)(obj));
+            }
+        }
+        
+        /// <summary>
+        ///   查找 System.Drawing.Bitmap 类型的本地化资源。
+        /// </summary>
+        internal static System.Drawing.Bitmap rope_block {
+            get {
+                object obj = ResourceManager.GetObject("rope_block", resourceCulture);
+                return ((System.Drawing.Bitmap)(obj));
+            }
+        }
+        
+        /// <summary>
+        ///   查找 System.Drawing.Bitmap 类型的本地化资源。
+        /// </summary>
+        internal static System.Drawing.Bitmap setting {
+            get {
+                object obj = ResourceManager.GetObject("setting", resourceCulture);
+                return ((System.Drawing.Bitmap)(obj));
+            }
+        }
+        
+        /// <summary>
+        ///   查找 System.Drawing.Bitmap 类型的本地化资源。
+        /// </summary>
+        internal static System.Drawing.Bitmap top_banner {
+            get {
+                object obj = ResourceManager.GetObject("top_banner", resourceCulture);
+                return ((System.Drawing.Bitmap)(obj));
+            }
+        }
+    }
+}

+ 169 - 0
Properties/Resources.resx

@@ -0,0 +1,169 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
+  <data name="back" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\back.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  </data>
+  <data name="closed" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\closed.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  </data>
+  <data name="delete" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\delete.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  </data>
+  <data name="eqSet" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\eqSet.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  </data>
+  <data name="equipment" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\equipment.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  </data>
+  <data name="export" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\export.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  </data>
+  <data name="loading" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\loading.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  </data>
+  <data name="logo" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\logo.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  </data>
+  <data name="logo_loading" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\logo_loading.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  </data>
+  <data name="menu_bg" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\menu_bg.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
+  <data name="menu_dec" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\menu_dec.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  </data>
+  <data name="minimize" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\minimize.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  </data>
+  <data name="option_bg" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\option_bg.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  </data>
+  <data name="rope_block" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\rope_block.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  </data>
+  <data name="setting" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\setting.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  </data>
+  <data name="top_banner" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\top_banner.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  </data>
+</root>

+ 43 - 0
Properties/Settings.Designer.cs

@@ -0,0 +1,43 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.42000
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace SWRIS.Properties
+{
+
+
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+    {
+
+        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+        public static Settings Default
+        {
+            get
+            {
+                return defaultInstance;
+            }
+        }
+        public bool IsDebug
+        {
+            get
+            {
+#if DEBUG
+                return true;
+#endif
+#if !DEBUG
+                return false;
+#endif
+
+            }
+        }
+    }
+}

+ 7 - 0
Properties/Settings.settings

@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
+  <Profiles>
+    <Profile Name="(Default)" />
+  </Profiles>
+  <Settings />
+</SettingsFile>

+ 73 - 0
Properties/app.manifest

@@ -0,0 +1,73 @@
+<?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" />
+	<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+		<security>
+			<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
+				<!-- UAC 清单选项
+             如果想要更改 Windows 用户帐户控制级别,请使用
+             以下节点之一替换 requestedExecutionLevel 节点。
+
+        <requestedExecutionLevel  level="asInvoker" uiAccess="false" />
+        <requestedExecutionLevel  level="requireAdministrator" uiAccess="false" />
+        <requestedExecutionLevel  level="highestAvailable" uiAccess="false" />
+
+            指定 requestedExecutionLevel 元素将禁用文件和注册表虚拟化。
+            如果你的应用程序需要此虚拟化来实现向后兼容性,则移除此
+            元素。
+        -->
+				<requestedExecutionLevel level="asInvoker" uiAccess="false" />
+			</requestedPrivileges>
+			<applicationRequestMinimum>
+				<defaultAssemblyRequest permissionSetReference="Custom" />
+				<PermissionSet class="System.Security.PermissionSet" version="1" ID="Custom" SameSite="site" Unrestricted="true" />
+			</applicationRequestMinimum>
+		</security>
+	</trustInfo>
+	<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+		<application>
+			<!-- 设计此应用程序与其一起工作且已针对此应用程序进行测试的
+           Windows 版本的列表。取消评论适当的元素,
+           Windows 将自动选择最兼容的环境。 -->
+			<!-- Windows Vista -->
+			<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
+			<!-- Windows 7 -->
+			<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
+			<!-- Windows 8 -->
+			<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
+			<!-- Windows 8.1 -->
+			<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
+			<!-- Windows 10 -->
+			<!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->
+		</application>
+	</compatibility>
+	<!-- 指示该应用程序可感知 DPI 且 Windows 在 DPI 较高时将不会对其进行
+       自动缩放。Windows Presentation Foundation (WPF)应用程序自动感知 DPI,无需
+       选择加入。选择加入此设置的 Windows 窗体应用程序(面向 .NET Framework 4.6)还应
+       在其 app.config 中将 "EnableWindowsFormsHighDpiAutoResizing" 设置设置为 "true"。
+       
+       将应用程序设为感知长路径。请参阅 https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->
+
+	<!--<application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
+      <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
+    </windowsSettings>
+  </application>-->
+
+	<!-- 启用 Windows 公共控件和对话框的主题(Windows XP 和更高版本) -->
+	<!--
+  <dependency>
+    <dependentAssembly>
+      <assemblyIdentity
+          type="win32"
+          name="Microsoft.Windows.Common-Controls"
+          version="6.0.0.0"
+          processorArchitecture="*"
+          publicKeyToken="6595b64144ccf1df"
+          language="*"
+        />
+    </dependentAssembly>
+  </dependency>
+  -->
+</assembly>

+ 82 - 0
README.md

@@ -0,0 +1,82 @@
+# 在线钢丝绳检测系统升级记录
+## 版本 1.1.2 - 2025-12-12
+
+### 新增功能
+- **增加记录批量操作功能**
+  - 可批量操作选择项,包含生成报告、删除记录、导出数据
+
+---
+
+## 版本 1.1.1 - 2025-12-04
+
+### 新增功能
+- **增加系统设置功能**
+  - 可修改起重及名称、服务地址、损伤报警级别等
+  - 可设置标定模块类型、地址及限位信息
+	- 标定项可设置关联设备,可针对部分设备标定
+	- 标定可实现两个限位进行标定
+
+- **自定义曲线右键菜单**
+  - 实时曲线自定义为“保存图片”、“清除曲线”、“重置视图”
+  - 历史曲线自定义为“保存图片”、“重置视图”
+
+- **屏蔽传感器数据**
+  - 可在配置文件中设置需要显示曲线的传感器,包含实时曲线和历史曲线
+    - 此设置只是起到屏蔽曲线作用,不能影响到设备的损伤报警
+
+---
+
+## 版本 1.1.0 - 2025-11-12
+
+### 新增功能
+- **智能数据记录优化**
+  - 实现无损伤时不记录数据功能,显著减少存储空间占用
+  - 实现在速度变化不稳定时不记录数据功能,提高数据准确性
+  
+- **实时语音播报**
+  - 实现发现损伤并在记录时自动语音播报功能,增强现场警示效果
+  
+- **损伤判断算法优化**
+  - 优化损伤判断逻辑,同一位置需出现三次以上损伤才记录,有效减少误报率
+  
+- **位置校准方式更改**
+  - 可知校准由原来整体校准改为校准指定设备,可适配多起升场景
+  - 支持多点校准,如上限位、下限位等多位置校准
+
+- **历史曲线损伤点增加标记**
+  - 在检测记录的合值曲线标注损伤位置标记,标记颜色根据损伤级别显示不同颜色
+
+- **已运行程序切换**
+  - 切换到电脑上运行的其它程序,如钢包工位、安全监控
+
+### 技术改进
+- 提升系统稳定性和数据处理效率
+- 优化内存管理和存储资源利用
+
+---
+
+## 版本 1.0.0 - 2025-10-15
+
+### 初始发布功能
+- **核心检测功能**
+  - 支持基本的钢丝绳检测功能
+  - 实现实时曲线和模拟动画显示
+
+- **数据管理**
+  - 实现数据记录和报表生成功能
+  - 实现在线设置设备基本参数功能
+
+- **设备接入与显示**
+  - 支持1路、2路、3路、4路设备接入
+  - 实现设备4路传感器曲线单独、合并显示功能
+
+- **系统监控**
+  - 实现设备故障报警功能
+
+### 系统特性
+- 稳定可靠的基础框架
+- 直观友好的用户界面
+- 完善的数据管理机制
+
+---
+*文档最后更新:2025-11-27*

+ 79 - 0
Repository/Alarm/AlarmRepository.cs

@@ -0,0 +1,79 @@
+using Dapper;
+using SWRIS.Core;
+using SWRIS.Enums;
+using SWRIS.Models;
+using System;
+
+namespace SWRIS.Repository
+{
+    public class AlarmRepository : SqLiteBaseRepository, IAlarmRepository
+    {
+        public int AddAlarm(AlarmDataModel alarm, AlarmSourceType sourceType, double damageExtent)
+        {
+            if (alarm != null)
+            {
+                damageExtent = Math.Abs(damageExtent / 2);
+                using (var cnn = DbConnection())
+                {
+                    try
+                    {
+                        cnn.Open();
+                        var sql = @"
+                            -- 先插入新记录
+                            INSERT INTO Alarms (RopeNumber, DamagePosition, DamageValue, DamageLevel, CreateTime, SourceType)
+                            VALUES (@RopeNumber, @DamagePosition, @DamageValue, @DamageLevel, @CreateTime, @SourceType);
+                            
+                            -- 查询该位置范围内的总报警次数(包括刚插入的这条)
+                            SELECT COUNT(*) 
+                            FROM Alarms 
+                            WHERE RopeNumber = @RopeNumber 
+                            AND SourceType = @SourceType 
+                            AND ABS(DamagePosition - @DamagePosition) <= @DamageExtent;";
+
+                        var parameters = new
+                        {
+                            alarm.RopeNumber,
+                            alarm.DamagePosition,
+                            alarm.DamageValue,
+                            alarm.DamageLevel,
+                            CreateTime = DateTime.Now,
+                            DamageExtent = damageExtent,
+                            SourceType = sourceType
+                        };
+
+                        int totalCount = cnn.QueryFirst<int>(sql, parameters);
+                        return totalCount;
+                    }
+                    catch (Exception ex)
+                    {
+                        LogHelper.Error("执行AddAlarm SQL语句时出错:" + ex.Message, ex);
+                        return -1;
+                    }
+                }
+            }
+            return -1;
+        }
+        public bool ClearAlarms(int ropeNumbner)
+        {
+            using (var cnn = DbConnection())
+            {
+                try
+                {
+                    cnn.Open();
+                    var sql = "DELETE FROM Alarms WHERE RopeNumber=@RopeNumber;";
+                    int effect = cnn.Execute(sql, new
+                    {
+                        RopeNumbner = ropeNumbner
+                    });
+                    cnn.Close();
+                    return effect > 0;
+                }
+                catch (Exception ex)
+                {
+                    LogHelper.Error("执行ClearAlarms SQL语句时出错:" + ex.Message, ex);
+                    return false;
+                }
+            }
+        }
+    }
+}

+ 11 - 0
Repository/Alarm/IAlarmRepository.cs

@@ -0,0 +1,11 @@
+using SWRIS.Enums;
+using SWRIS.Models;
+
+namespace SWRIS.Repository
+{
+    public interface IAlarmRepository
+    {
+        int AddAlarm(AlarmDataModel alarm, AlarmSourceType sourceType, double damageExtent);
+        bool ClearAlarms(int ropeNumbner);
+    }
+}

+ 28 - 0
Repository/Record/IRecordRepository.cs

@@ -0,0 +1,28 @@
+using SWRIS.Dtos;
+using System;
+using System.Collections.Generic;
+
+namespace SWRIS.Repository
+{
+    internal interface IRecordRepository
+    {
+        int? CreateRecord(RecordDto record);
+        bool UpdateRecord(RecordDto record);
+        bool DeleteRecord(int id);
+        int DeleteRecords(int[] ids);
+        RecordDto GetRecord(int id);
+        List<RecordDto> GetRecords(int? ropeNumber,
+                                   DateTime? startTime = null,
+                                   DateTime? endTime = null,
+                                   int? riskLevel = null,
+                                   int? limit = null,
+                                   int? offset = null,
+                                   bool hasDamages = false);
+        bool DeleteDamage(int id);
+        bool UpdateDamage(DamageDto damage);
+        bool UpdateRecordDamageCount(int id, int damageCount);
+        List<RecordDto> GetLastRecords(int lastCount = 10, bool hasDamages = false);
+        bool AddRecordExportCount(int id);
+        (int, List<string>) DeleteRecords(DateTime endTime);
+    }
+}

+ 406 - 0
Repository/Record/RecordRepository.cs

@@ -0,0 +1,406 @@
+using Dapper;
+using SWRIS.Core;
+using SWRIS.Dtos;
+using SWRIS.Extensions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace SWRIS.Repository
+{
+    public class RecordRepository : SqLiteBaseRepository, IRecordRepository
+    {
+        public RecordDto GetRecord(int id)
+        {
+            using (var cnn = DbConnection())
+            {
+                cnn.Open();
+                try
+                {
+                    RecordDto result = cnn.QueryFirstOrDefault<RecordDto>(
+                         @"SELECT Id,
+                               RopeNumber,
+                               RopeName,
+                               StartTime,
+                               EndTime,
+                               StartPoint,
+                               EndPoint,
+                               DetectionLength,
+                               DetectedSpeed,
+                               RiskLevel,
+                               DamageCount,
+                               DataFilePath,
+                               SensorCount,
+                               SamplingStep,
+                               ExportCount,
+                               InUseSensors
+                          FROM Records 
+                          WHERE Id = @id", new { id });
+
+                    if (result != null)
+                    {
+                        var damages = cnn.Query<DamageDto>(
+                            @"SELECT Id,
+                                   RecordId,
+                                   DamagePoint,
+                                   DamageValue,
+                                   DamageLevel
+                              FROM Damages  
+                              WHERE RecordId = @id", new { id }).ToList();
+
+                        result.Damages.AddRange(damages);
+                    }
+                    return result;
+                }
+                catch (Exception ex)
+                {
+                    LogHelper.Error("执行SQL语句时出错:" + ex.Message, ex);
+                }
+                return null;
+
+            }
+        }
+        public List<RecordDto> GetLastRecords(int lastCount = 10, bool hasDamages = false)
+        {
+            using (var cnn = DbConnection())
+            {
+                try
+                {
+                    cnn.Open();
+                    var sql = @"SELECT Id,
+                                   RopeNumber,
+                                   RopeName,
+                                   StartTime,
+                                   EndTime,
+                                   StartPoint,
+                                   EndPoint,
+                                   DetectionLength,
+                                   DetectedSpeed,
+                                   RiskLevel,
+                                   DamageCount,
+                                   DataFilePath,
+                                   SensorCount,
+                                   SamplingStep,
+                                   ExportCount,
+                                   InUseSensors
+                                FROM Records
+                                ORDER BY StartTime DESC
+                                LIMIT @Limit OFFSET @Offset";
+
+                    var result = cnn.Query<RecordDto>(sql, new
+                    {
+                        Limit = lastCount,
+                        Offset = 0
+                    });
+                    if (hasDamages && result != null && result.Any())
+                    {
+                        foreach (var item in result)
+                        {
+                            var damages = cnn.Query<DamageDto>(
+                                @"SELECT Id,
+                                         RecordId,
+                                         DamagePoint,
+                                         DamageValue,
+                                         DamageLevel
+                                  FROM Damages 
+                                  WHERE RecordId = @id", new { id = item.Id }).ToList();
+                            item.Damages.AddRange(damages);
+                        }
+                    }
+                    return result.ToList();
+                }
+                catch (Exception ex)
+                {
+                    LogHelper.Error("执行SQL语句时出错:" + ex.Message, ex);
+                }
+                return new List<RecordDto>();
+            }
+        }
+        public List<RecordDto> GetRecords(int? ropeNumber,
+                                          DateTime? startTime = null,
+                                          DateTime? endTime = null,
+                                          int? riskLevel = null,
+                                          int? limit = null,
+                                          int? offset = null,
+                                          bool hasDamages = false)
+        {
+            using (var cnn = DbConnection())
+            {
+                try
+                {
+                    cnn.Open();
+                    var sql = @"SELECT Id,
+                                   RopeNumber,
+                                   RopeName,
+                                   StartTime,
+                                   EndTime,
+                                   StartPoint,
+                                   EndPoint,
+                                   DetectionLength,
+                                   DetectedSpeed,
+                                   RiskLevel,
+                                   DamageCount,
+                                   DataFilePath,
+                                   SensorCount,
+                                   SamplingStep,
+                                   ExportCount,
+                                   InUseSensors
+                                FROM Records 
+                                WHERE 1=1 ";
+                    if (ropeNumber.HasValue)
+                    {
+                        sql += @"AND RopeNumber==@RopeNumber ";
+                    }
+                    if (riskLevel.HasValue)
+                    {
+                        sql += @"AND RiskLevel==@RiskLevel ";
+                    }
+                    if (startTime.HasValue)
+                    {
+                        sql += @"AND StartTime>=@StartTime ";
+                    }
+                    if (endTime.HasValue)
+                    {
+                        sql += @"AND EndTime<=@EndTime ";
+                    }
+                    sql += @"ORDER BY StartTime DESC ";
+                    if (limit.HasValue && offset.HasValue)
+                    {
+                        sql += @"LIMIT @Limit OFFSET @Offset ";
+                    }
+                    var result = cnn.Query<RecordDto>(sql, new
+                    {
+                        Limit = limit,
+                        Offset = offset,
+                        StartTime = startTime,
+                        EndTime = endTime,
+                        RopeNumber = ropeNumber,
+                        RiskLevel = riskLevel,
+                    });
+                    if (hasDamages && result != null && result.Any())
+                    {
+                        foreach (var item in result)
+                        {
+                            var damages = cnn.Query<DamageDto>(
+                                @"SELECT Id,
+                                         RecordId,
+                                         DamagePoint,
+                                         DamageValue,
+                                         DamageLevel
+                                  FROM Damages 
+                                  WHERE RecordId = @id", new { id = item.Id }).ToList();
+                            item.Damages.AddRange(damages);
+                        }
+                    }
+                    return result.ToList();
+                }
+                catch (Exception ex)
+                {
+                    LogHelper.Error("执行SQL语句时出错:" + ex.Message, ex);
+                }
+                return new List<RecordDto>();
+            }
+        }
+
+        public int? CreateRecord(RecordDto record)
+        {
+            using (var cnn = DbConnection())
+            {
+                try
+                {
+                    cnn.Open();
+                    record.Id = cnn.ExecuteScalar<int>(
+                        @"INSERT INTO Records (
+                             RopeNumber,
+                             RopeName,
+                             StartTime,
+                             EndTime,
+                             StartPoint,
+                             EndPoint,
+                             DetectionLength,
+                             DetectedSpeed,
+                             DamageCount,
+                             SensorCount,
+                             SamplingStep,
+                             DataFilePath,
+                             RiskLevel,
+                             ExportCount,
+                             InUseSensors)
+                          VALUES (
+                            @RopeNumber,
+                            @RopeName,
+                            @StartTime,
+                            @EndTime,
+                            @StartPoint,
+                            @EndPoint,
+                            @DetectionLength,
+                            @DetectedSpeed,
+                            @DamageCount,
+                            @SensorCount,
+                            @SamplingStep,
+                            @DataFilePath,
+                            @RiskLevel,
+                            @ExportCount,
+                            @InUseSensors);
+                         SELECT LAST_INSERT_ROWID()", record);
+                    if (record?.Id > 0)
+                    {
+                        if (record.Damages != null && record.Damages.Any())
+                        {
+                            foreach (var d in record.Damages)
+                            {
+                                d.RecordId = record.Id.Value;
+                            }
+                            cnn.Execute(@"INSERT INTO Damages (
+                                                RecordId,
+                                                DamagePoint,
+                                                DamageValue,
+                                                DamageLevel)
+                                          VALUES (
+                                                @RecordId,
+                                                @DamagePoint,
+                                                @DamageValue,
+                                                @DamageLevel)", record.Damages);
+                        }
+                    }
+                }
+                catch (Exception ex)
+                {
+                    LogHelper.Error("执行SQL语句时出错:" + ex.Message, ex);
+                    return null;
+                }
+                return record.Id;
+            }
+        }
+        public bool UpdateRecord(RecordDto record)
+        {
+            using (var cnn = DbConnection())
+            {
+                try
+                {
+                    cnn.Open();
+                    int affected = cnn.Execute(
+                         @"UPDATE Records
+                               SET 
+                                   RopeNumber = @RopeNumber,
+                                   RopeName = @RopeName,
+                                   StartTime = @StartTime,
+                                   EndTime = @EndTime,
+                                   StartPoint = @StartPoint,
+                                   EndPoint = @EndPoint,
+                                   DetectionLength = @DetectionLength,
+                                   DetectedSpeed = @DetectedSpeed,
+                                   DamageCount = @DamageCount,
+                                   DataFilePath = @DataFilePath,
+                                   SensorCount = @SensorCount,
+                                   SamplingStep = @SamplingStep,
+                                   ExportCount = @ExportCount,
+                                   RiskLevel=@RiskLevel,
+                                   InUseSensors=@InUseSensors
+                           WHERE Id = @Id;", record);
+
+                    foreach (var item in record.Damages)
+                    {
+                        affected += cnn.Execute(
+                          @"UPDATE Damages
+                               SET RecordId = @RecordId,
+                                   DamagePoint =@DamagePoint,
+                                   DamageValue =@DamageValue,
+                                   DamageLevel =@DamageLevel 
+                               WHERE Id = @Id;", item);
+                    }
+                    return affected > 0;
+                }
+                catch (Exception ex)
+                {
+                    LogHelper.Error("执行SQL语句时出错:" + ex.Message, ex);
+                    return false;
+                }
+            }
+        }
+        public bool UpdateRecordDamageCount(int id, int damageCount)
+        {
+            using (var cnn = DbConnection())
+            {
+                cnn.Open();
+                return cnn.Execute(@"UPDATE Records Set DamageCount=@DamageCount WHERE Id = @Id;", new { Id = id, DamageCount = damageCount }) > 0;
+            }
+        }
+        public bool AddRecordExportCount(int id)
+        {
+            using (var cnn = DbConnection())
+            {
+                cnn.Open();
+                return cnn.Execute(@"UPDATE Records Set ExportCount=ExportCount+1 WHERE Id = @Id;", new { Id = id }) > 0;
+            }
+        }
+        public bool DeleteRecord(int id)
+        {
+            using (var cnn = DbConnection())
+            {
+                cnn.Open();
+                return cnn.Execute(@"DELETE FROM Damages WHERE RecordId = @Id;DELETE FROM Records WHERE Id = @Id;", new { Id = id }) > 0;
+            }
+        }
+        public int DeleteRecords(int[] ids)
+        {
+            using (var cnn = DbConnection())
+            {
+                cnn.Open();
+                return cnn.Execute(@"DELETE FROM Damages WHERE RecordId in @Ids;DELETE FROM Records WHERE Id in @Ids;", new { Ids = ids });
+            }
+        }
+        public (int, List<string>) DeleteRecords(DateTime endTime)
+        {
+            using (var cnn = DbConnection())
+            {
+                cnn.Open();
+
+                // 首先查询需要删除的记录的DataFilePath
+                var filePaths = cnn.Query<string>(
+                    @"SELECT DataFilePath 
+                        FROM Records
+                        WHERE StartTime <= @EndTime AND ExportCount = 0",
+                    new { EndTime = endTime }).ToList();
+
+                // 执行删除操作
+                int effect = cnn.Execute(@"
+                      BEGIN TRANSACTION;
+                          DELETE FROM Damages 
+                          WHERE RecordId IN (
+                              SELECT Id 
+                              FROM Records 
+                              WHERE StartTime <= @EndTime AND ExportCount = 0
+                          );
+                          DELETE FROM Records 
+                          WHERE StartTime <= @EndTime AND ExportCount = 0;
+                      COMMIT;",
+                  new { EndTime = endTime });
+
+                return (effect / 2, filePaths);
+            }
+        }
+        public bool UpdateDamage(DamageDto damage)
+        {
+            using (var cnn = DbConnection())
+            {
+                cnn.Open();
+                return cnn.Execute(@"UPDATE Damages
+                                       SET RecordId = @RecordId,
+                                           DamagePoint = @DamagePoint,
+                                           DamageValue = @DamageValue,
+                                           DamageLevel = @DamageLevel 
+                                       WHERE Id = @Id;", damage) > 0;
+            }
+        }
+        public bool DeleteDamage(int id)
+        {
+            using (var cnn = DbConnection())
+            {
+                cnn.Open();
+                return cnn.Execute(@"DELETE FROM Damages WHERE Id = @Id;", new { Id = id }) > 0;
+            }
+        }
+
+    }
+}

+ 35 - 0
Repository/SqLiteBaseRepository.cs

@@ -0,0 +1,35 @@
+using SWRIS.Migrations;
+using System;
+using System.Data.SQLite;
+using System.IO;
+
+namespace SWRIS.Core
+{
+    public class SqLiteBaseRepository
+    {
+        public static string Folder
+        {
+            get { return Environment.CurrentDirectory + "\\Data"; }
+        }
+        public static string DbFile
+        {
+            get { return Folder + "\\Db.sqlite"; }
+        }
+
+        public static SQLiteConnection DbConnection()
+        {
+            return new SQLiteConnection($"Data Source={DbFile};Version=3;");
+        }
+        public static void CreateDatabase()
+        {
+            if (!File.Exists(DbFile))
+            {
+                Directory.CreateDirectory(Folder);
+                SQLiteConnection.CreateFile(DbFile);
+
+                Initial_Migration.Excute();
+            }
+            Add_Table_Alarms.Excute();
+        }
+    }
+}

BIN
Resources/back.png


BIN
Resources/blue_bg.png


BIN
Resources/bucket.png


BIN
Resources/cims.png


BIN
Resources/closed.png


BIN
Resources/connected.png


BIN
Resources/delete.png


BIN
Resources/eqSet.png


BIN
Resources/equipment.png


BIN
Resources/export.png


BIN
Resources/loading.png


BIN
Resources/logo.png


BIN
Resources/logo_loading.png


BIN
Resources/menu_bg.png


BIN
Resources/menu_dec.png


BIN
Resources/minimize.png


BIN
Resources/option_bg.png


BIN
Resources/pointer.png


BIN
Resources/red_bg.png


BIN
Resources/risk_alert.png


BIN
Resources/risk_normal.png


BIN
Resources/risk_warning.png


BIN
Resources/rope_block.png


BIN
Resources/setting.png


BIN
Resources/swris.png


BIN
Resources/top_banner.png


BIN
Resources/unconnect.png


BIN
Resources/upgrade.png


+ 642 - 0
SWRIS.csproj

@@ -0,0 +1,642 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="packages\EntityFramework.6.5.1\build\EntityFramework.props" Condition="Exists('packages\EntityFramework.6.5.1\build\EntityFramework.props')" />
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <HotReloadAutoRestart>true</HotReloadAutoRestart>
+  </PropertyGroup>
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{3F57ECFF-C6C5-4EF7-9A4C-2E3295D7F774}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <RootNamespace>SWRIS</RootNamespace>
+    <AssemblyName>SWRIS</AssemblyName>
+    <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <WarningLevel>4</WarningLevel>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <Deterministic>true</Deterministic>
+    <NuGetPackageImportStamp>
+    </NuGetPackageImportStamp>
+    <IsWebBootstrapper>false</IsWebBootstrapper>
+    <PublishUrl>publish\</PublishUrl>
+    <Install>true</Install>
+    <InstallFrom>Disk</InstallFrom>
+    <UpdateEnabled>false</UpdateEnabled>
+    <UpdateMode>Foreground</UpdateMode>
+    <UpdateInterval>7</UpdateInterval>
+    <UpdateIntervalUnits>Days</UpdateIntervalUnits>
+    <UpdatePeriodically>false</UpdatePeriodically>
+    <UpdateRequired>false</UpdateRequired>
+    <MapFileExtensions>true</MapFileExtensions>
+    <ApplicationRevision>0</ApplicationRevision>
+    <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
+    <UseApplicationTrust>false</UseApplicationTrust>
+    <BootstrapperEnabled>true</BootstrapperEnabled>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup>
+    <TargetZone>LocalIntranet</TargetZone>
+  </PropertyGroup>
+  <PropertyGroup>
+    <GenerateManifests>false</GenerateManifests>
+  </PropertyGroup>
+  <PropertyGroup />
+  <PropertyGroup>
+    <ApplicationManifest>Properties\app.manifest</ApplicationManifest>
+  </PropertyGroup>
+  <PropertyGroup>
+    <ApplicationIcon>logo.ico</ApplicationIcon>
+  </PropertyGroup>
+  <ItemGroup>
+    <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>
+    </Reference>
+    <Reference Include="EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
+      <HintPath>packages\EntityFramework.6.5.1\lib\net45\EntityFramework.dll</HintPath>
+    </Reference>
+    <Reference Include="EntityFramework.SqlServer, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
+      <HintPath>packages\EntityFramework.6.5.1\lib\net45\EntityFramework.SqlServer.dll</HintPath>
+    </Reference>
+    <Reference Include="GLWpfControl, Version=3.3.0.0, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>packages\OpenTK.GLWpfControl.3.3.0\lib\net452\GLWpfControl.dll</HintPath>
+    </Reference>
+    <Reference Include="HarfBuzzSharp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
+      <HintPath>packages\HarfBuzzSharp.7.3.0.3\lib\net462\HarfBuzzSharp.dll</HintPath>
+    </Reference>
+    <Reference Include="HslCommunication, Version=12.3.3.0, Culture=neutral, PublicKeyToken=3d72ad3b6b5ec0e3, processorArchitecture=MSIL">
+      <HintPath>packages\HslCommunication.12.3.3\lib\net451\HslCommunication.dll</HintPath>
+    </Reference>
+    <Reference Include="Interop.IWshRuntimeLibrary, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>packages\Interop.IWshRuntimeLibrary.1.0.1\lib\net45\Interop.IWshRuntimeLibrary.dll</HintPath>
+      <EmbedInteropTypes>True</EmbedInteropTypes>
+    </Reference>
+    <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.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>
+    <Reference Include="Microsoft.WindowsAPICodePack.Shell, Version=1.1.5.0, Culture=neutral, PublicKeyToken=8985beaab7ea3f04, processorArchitecture=MSIL">
+      <HintPath>packages\Microsoft-WindowsAPICodePack-Shell.1.1.5\lib\net48\Microsoft.WindowsAPICodePack.Shell.dll</HintPath>
+    </Reference>
+    <Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+      <HintPath>packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
+    </Reference>
+    <Reference Include="Newtonsoft.Json.Bson, Version=1.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+      <HintPath>packages\Newtonsoft.Json.Bson.1.0.3\lib\net45\Newtonsoft.Json.Bson.dll</HintPath>
+    </Reference>
+    <Reference Include="OpenTK, Version=3.3.1.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
+      <HintPath>packages\OpenTK.3.3.1\lib\net20\OpenTK.dll</HintPath>
+    </Reference>
+    <Reference Include="Panuon.WPF, Version=1.1.2.2, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>packages\Panuon.WPF.1.1.3\lib\net48\Panuon.WPF.dll</HintPath>
+    </Reference>
+    <Reference Include="Panuon.WPF.UI, Version=1.3.0.2, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>packages\Panuon.WPF.UI.1.3.0.2\lib\net48\Panuon.WPF.UI.dll</HintPath>
+    </Reference>
+    <Reference Include="QRCoder, Version=1.6.0.0, Culture=neutral, PublicKeyToken=c4ed5b9ae8358a28, processorArchitecture=MSIL">
+      <HintPath>packages\QRCoder.1.6.0\lib\net40\QRCoder.dll</HintPath>
+    </Reference>
+    <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="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="SkiaSharp, Version=2.88.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
+      <HintPath>packages\SkiaSharp.2.88.9\lib\net462\SkiaSharp.dll</HintPath>
+    </Reference>
+    <Reference Include="SkiaSharp.HarfBuzz, Version=2.88.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
+      <HintPath>packages\SkiaSharp.HarfBuzz.2.88.9\lib\net462\SkiaSharp.HarfBuzz.dll</HintPath>
+    </Reference>
+    <Reference Include="SkiaSharp.Views.Desktop.Common, Version=2.88.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
+      <HintPath>packages\SkiaSharp.Views.Desktop.Common.2.88.9\lib\net462\SkiaSharp.Views.Desktop.Common.dll</HintPath>
+    </Reference>
+    <Reference Include="SkiaSharp.Views.WPF, Version=2.88.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
+      <HintPath>packages\SkiaSharp.Views.WPF.2.88.9\lib\net462\SkiaSharp.Views.WPF.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Buffers, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>packages\System.Buffers.4.6.1\lib\net462\System.Buffers.dll</HintPath>
+    </Reference>
+    <Reference Include="System.ComponentModel.Annotations, Version=4.2.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>packages\System.ComponentModel.Annotations.5.0.0\lib\net461\System.ComponentModel.Annotations.dll</HintPath>
+    </Reference>
+    <Reference Include="System.ComponentModel.DataAnnotations" />
+    <Reference Include="System.Configuration" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Data.SQLite, Version=1.0.119.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
+      <HintPath>packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.119.0\lib\net46\System.Data.SQLite.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Data.SQLite.EF6, Version=1.0.119.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
+      <HintPath>packages\System.Data.SQLite.EF6.1.0.119.0\lib\net46\System.Data.SQLite.EF6.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Data.SQLite.Linq, Version=1.0.119.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
+      <HintPath>packages\System.Data.SQLite.Linq.1.0.119.0\lib\net46\System.Data.SQLite.Linq.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Drawing.Common, Version=4.0.0.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>packages\System.Drawing.Common.4.7.3\lib\net461\System.Drawing.Common.dll</HintPath>
+    </Reference>
+    <Reference Include="System.IO.Pipelines, Version=9.0.0.8, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>packages\System.IO.Pipelines.9.0.8\lib\net462\System.IO.Pipelines.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Management" />
+    <Reference Include="System.Memory, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>packages\System.Memory.4.6.3\lib\net462\System.Memory.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Net.Http.Formatting, Version=6.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <HintPath>packages\Microsoft.AspNet.WebApi.Client.6.0.0\lib\net45\System.Net.Http.Formatting.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Numerics" />
+    <Reference Include="System.Numerics.Vectors, Version=4.1.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>packages\System.Numerics.Vectors.4.6.1\lib\net462\System.Numerics.Vectors.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Runtime" />
+    <Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>packages\System.Runtime.CompilerServices.Unsafe.6.1.2\lib\net462\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Speech" />
+    <Reference Include="System.Text.Encodings.Web, Version=9.0.0.8, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>packages\System.Text.Encodings.Web.9.0.8\lib\net462\System.Text.Encodings.Web.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Text.Json, Version=9.0.0.8, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>packages\System.Text.Json.9.0.8\lib\net462\System.Text.Json.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Threading.Tasks.Extensions, Version=4.2.4.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>packages\System.Threading.Tasks.Extensions.4.6.3\lib\net462\System.Threading.Tasks.Extensions.dll</HintPath>
+    </Reference>
+    <Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Web" />
+    <Reference Include="System.Web.Extensions" />
+    <Reference Include="System.Web.Http, Version=5.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <HintPath>packages\Microsoft.AspNet.WebApi.Core.5.3.0\lib\net45\System.Web.Http.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Web.Http.SelfHost, Version=5.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <HintPath>packages\Microsoft.AspNet.WebApi.SelfHost.5.3.0\lib\net45\System.Web.Http.SelfHost.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Xaml">
+      <RequiredTargetFramework>4.0</RequiredTargetFramework>
+    </Reference>
+    <Reference Include="WindowsBase" />
+    <Reference Include="PresentationCore" />
+    <Reference Include="PresentationFramework" />
+    <Reference Include="WindowsFormsIntegration" />
+  </ItemGroup>
+  <ItemGroup>
+    <ApplicationDefinition Include="App.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </ApplicationDefinition>
+    <Compile Include="Converters\ModuleTypeToVisibilityConverter.cs" />
+    <Compile Include="Converters\BoolToVisibilityConverter.cs" />
+    <Compile Include="Converters\NullToVisibilityConverter.cs" />
+    <Compile Include="Converters\PercentageConverter.cs" />
+    <Compile Include="Converters\StringEmptyToVisibilityConverter.cs" />
+    <Compile Include="Converters\LimitStateToColorConverter.cs" />
+    <Compile Include="Converters\ColorToHoverBackgroundConverter.cs" />
+    <Compile Include="Converters\ConnectivityToVisibilityConverter.cs" />
+    <Compile Include="Converters\DirectionStateToBooleanConverter.cs" />
+    <Compile Include="Converters\NullToBooleanConverter.cs" />
+    <Compile Include="Converters\ResetTypeToBooleanConverter.cs" />
+    <Compile Include="Converters\EncoderDirectionToBooleanConverter.cs" />
+    <Compile Include="Converters\StringToColorConverter.cs" />
+    <Compile Include="Core\CalibrationCore.cs" />
+    <Compile Include="Dtos\DamageDto.cs" />
+    <Compile Include="Dtos\RecordDto.cs" />
+    <Compile Include="Dtos\RecordIdAnDataFilePathDto.cs" />
+    <Compile Include="Enums\AlarmSourceType.cs" />
+    <Compile Include="Enums\DataFormat.cs" />
+    <Compile Include="Enums\LayType.cs" />
+    <Compile Include="Enums\LimitState.cs" />
+    <Compile Include="Enums\ModuleType.cs" />
+    <Compile Include="Enums\ResetType.cs" />
+    <Compile Include="Controllers\RecordController.cs" />
+    <Compile Include="Controls\DamageRangeControl.xaml.cs">
+      <DependentUpon>DamageRangeControl.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Controls\DamageRecordControl.xaml.cs">
+      <DependentUpon>DamageRecordControl.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Controls\RealTimeLineChart.xaml.cs">
+      <DependentUpon>RealTimeLineChart.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Controls\NotificationBar.xaml.cs">
+      <DependentUpon>NotificationBar.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Controls\RopeEquipmentControl.xaml.cs">
+      <DependentUpon>RopeEquipmentControl.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Converters\BooleanInverterConverter.cs" />
+    <Compile Include="Converters\CollectionCountToVisibilityConverter.cs" />
+    <Compile Include="Converters\DamageLevelToColorConverter.cs" />
+    <Compile Include="Converters\DamageLevelToTextConverter.cs" />
+    <Compile Include="Converters\MenuNameToColorConverter.cs" />
+    <Compile Include="Converters\MenuNameToPositionConverter.cs" />
+    <Compile Include="Converters\ConnectivityFaultToColorConverter.cs" />
+    <Compile Include="Converters\RiskLevelToColorConverter.cs" />
+    <Compile Include="Converters\RiskLevelToTextConverter.cs" />
+    <Compile Include="Converters\StringToImageSourceConverter.cs" />
+    <Compile Include="Core\ConfigHelper.cs" />
+    <Compile Include="Core\TcpServerFrame.cs" />
+    <Compile Include="Dtos\GetRecordsInputDto.cs" />
+    <Compile Include="Dtos\KeyAndValueDto.cs" />
+    <Compile Include="Dtos\SimpleRecordDto.cs" />
+    <Compile Include="Enums\DirectionState.cs" />
+    <Compile Include="Enums\EncoderDirection.cs" />
+    <Compile Include="Enums\FaultType.cs" />
+    <Compile Include="Enums\RiskLevel.cs" />
+    <Compile Include="Enums\RunningStatus.cs" />
+    <Compile Include="Enums\RopeCoreType.cs" />
+    <Compile Include="Enums\WireMaterialType.cs" />
+    <Compile Include="Enums\WireSurfaceType.cs" />
+    <Compile Include="Events\AlarmDataReceivedEventArgs.cs" />
+    <Compile Include="Events\UpgradedResultReceivedEventArgs.cs" />
+    <Compile Include="Events\ClientConnectedEventArgs.cs" />
+    <Compile Include="Events\ClientDisconnectedEventArgs.cs" />
+    <Compile Include="Events\ClockResultReceivedEventArgs.cs" />
+    <Compile Include="Events\DebugMessageReceivedEventArgs.cs" />
+    <Compile Include="Events\DetectionDataReceivedEventArgs.cs" />
+    <Compile Include="Events\ResetSystemDataReceivedEventArgs.cs" />
+    <Compile Include="Events\DetectionRawDataReceivedEventArgs.cs" />
+    <Compile Include="Events\DetectionRawDataResultReceivedEventArgs.cs" />
+    <Compile Include="Events\DetectionStatusResultReceivedEventArgs.cs" />
+    <Compile Include="Events\EncoderDirectionResultReceivedEventArgs.cs" />
+    <Compile Include="Events\FaultDataReceivedEventArgs.cs" />
+    <Compile Include="Events\HeartbeatReceviedEventArgs.cs" />
+    <Compile Include="Events\LiveStreamReceivedEventArgs.cs" />
+    <Compile Include="Events\RealTimeDataReceivedEventArgs.cs" />
+    <Compile Include="Events\SetAbsolutePositionDataReceivedEventArgs.cs" />
+    <Compile Include="Extensions\FileHelper.cs" />
+    <Compile Include="Extensions\JsonCryptoHelper.cs" />
+    <Compile Include="Extensions\LogHelper.cs" />
+    <Compile Include="Extensions\ReportPdfExporter.cs" />
+    <Compile Include="Extensions\SimpleLogger.cs" />
+    <Compile Include="Extensions\SingleInstanceManager.cs" />
+    <Compile Include="Mapping.cs" />
+    <Compile Include="Migrations\Add_Table_Alarms.cs" />
+    <Compile Include="Models\AlarmDataModel.cs" />
+    <Compile Include="Models\ByteCircularBuffer.cs" />
+    <Compile Include="Models\CalibrationDataModel.cs" />
+    <Compile Include="Models\ClientState.cs" />
+    <Compile Include="Models\ClockDataModel.cs" />
+    <Compile Include="Models\DamageModel.cs" />
+    <Compile Include="Models\DataCenterModel.cs" />
+    <Compile Include="Models\DebugMessageModel.cs" />
+    <Compile Include="Models\DeleteResultModel.cs" />
+    <Compile Include="Models\DetectionDataModel.cs" />
+    <Compile Include="Models\DetectionRawDataModel.cs" />
+    <Compile Include="Models\DetectionRawResultDataModel.cs" />
+    <Compile Include="Models\EncoderDirectionDataModel.cs" />
+    <Compile Include="Models\EquipmentDataModel.cs" />
+    <Compile Include="Models\EquipmentModel.cs" />
+    <Compile Include="Models\FaultDataModel.cs" />
+    <Compile Include="Models\LimitModel.cs" />
+    <Compile Include="Models\LiveStreamDataModel.cs" />
+    <Compile Include="Models\SetAbsolutePositionDataModel.cs" />
+    <Compile Include="Models\LiveStreamPositionDataModel.cs" />
+    <Compile Include="Models\ModuleStateModel.cs" />
+    <Compile Include="Models\RealTimeDataModel.cs" />
+    <Compile Include="Models\ResetSystemDataModel.cs" />
+    <Compile Include="Models\SensorModel.cs" />
+    <Compile Include="Models\SpeedVariationModel.cs" />
+    <Compile Include="Models\SwitchInstanceDataModel.cs" />
+    <Compile Include="Models\TwistFactorDataModel.cs" />
+    <Compile Include="Models\VerifyDataModel.cs" />
+    <Compile Include="Enums\AlarmType.cs" />
+    <Compile Include="Enums\DamageLevel.cs" />
+    <Compile Include="Enums\FaultState.cs" />
+    <Compile Include="Extensions\ByteTransform.cs" />
+    <Compile Include="Extensions\DatFileHandler.cs" />
+    <Compile Include="Extensions\FirewallHelper.cs" />
+    <Compile Include="Extensions\SoftBasic.cs" />
+    <Compile Include="Extensions\Tools.cs" />
+    <Compile Include="Migrations\Initial_Migration.cs" />
+    <Compile Include="Models\ConfigModel.cs" />
+    <Compile Include="Models\ParameterModel.cs" />
+    <Compile Include="Models\DateTimeModel.cs" />
+    <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\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" />
+    <Compile Include="Pages\DamagesDialog.xaml.cs">
+      <DependentUpon>DamagesDialog.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Pages\DamagesPage.xaml.cs">
+      <DependentUpon>DamagesPage.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Pages\RealTime\FourRopesPage.xaml.cs">
+      <DependentUpon>FourRopesPage.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>
+    <Compile Include="Pages\ParameterDialog.xaml.cs">
+      <DependentUpon>ParameterDialog.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Pages\RealTime\ThreeRopesPage.xaml.cs">
+      <DependentUpon>ThreeRopesPage.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Pages\RealTime\TwoRopesPage.xaml.cs">
+      <DependentUpon>TwoRopesPage.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Pages\RecordPage.xaml.cs">
+      <DependentUpon>RecordPage.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Pages\SettingDialog.xaml.cs">
+      <DependentUpon>SettingDialog.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Properties\Resources.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DesignTime>True</DesignTime>
+      <DependentUpon>Resources.resx</DependentUpon>
+    </Compile>
+    <Compile Include="Repository\Alarm\IAlarmRepository.cs" />
+    <Compile Include="Repository\Alarm\AlarmRepository.cs" />
+    <Compile Include="Repository\Record\IRecordRepository.cs" />
+    <Compile Include="Repository\Record\RecordRepository.cs" />
+    <Compile Include="Repository\SqLiteBaseRepository.cs" />
+    <Compile Include="Services\HistoryCleanupService.cs" />
+    <Compile Include="SoftAuthDialog.xaml.cs">
+      <DependentUpon>SoftAuthDialog.xaml</DependentUpon>
+    </Compile>
+    <Page Include="Controls\DamageRangeControl.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="Controls\DamageRecordControl.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="Controls\RealTimeLineChart.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
+    <Page Include="Controls\NotificationBar.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="Controls\RopeEquipmentControl.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="MainWindow.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
+    <Compile Include="App.xaml.cs">
+      <DependentUpon>App.xaml</DependentUpon>
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="Enums\ExecutionState.cs" />
+    <Compile Include="Extensions\SoftAuth.cs" />
+    <Compile Include="Extensions\TypeExtension.cs" />
+    <Compile Include="Extensions\WinHelper.cs" />
+    <Compile Include="MainWindow.xaml.cs">
+      <DependentUpon>MainWindow.xaml</DependentUpon>
+      <SubType>Code</SubType>
+    </Compile>
+    <Page Include="Pages\DamagesDialog.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="Pages\DamagesPage.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
+    <Page Include="Pages\RealTime\FourRopesPage.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="Controls\RecordLineChart.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
+    <Page Include="Pages\RealTime\OneRopePage.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="Pages\ParameterDialog.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="Pages\RealTime\ThreeRopesPage.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="Pages\RealTime\TwoRopesPage.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="Pages\RecordPage.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="Pages\SettingDialog.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
+    <Page Include="SoftAuthDialog.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
+    <Page Include="Styles\Styles.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Properties\AssemblyInfo.cs">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="Properties\Settings.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Settings.settings</DependentUpon>
+      <DesignTimeSharedInput>True</DesignTimeSharedInput>
+    </Compile>
+    <Resource Include="Resources\upgrade.png">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Resource>
+    <Resource Include="Fonts\Alibaba-PuHuiTi-Bold.ttf" />
+    <Resource Include="Fonts\Alibaba-PuHuiTi-Regular.ttf" />
+    <None Include="README.md" />
+    <Resource Include="Resources\bucket.png">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Resource>
+    <Resource Include="Resources\cims.png">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Resource>
+    <Resource Include="Resources\swris.png">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Resource>
+    <Resource Include="Resources\blue_bg.png">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Resource>
+    <Resource Include="Resources\red_bg.png">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Resource>
+    <Resource Include="Resources\minimize.png">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Resource>
+    <Resource Include="Resources\closed.png">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Resource>
+    <Resource Include="Resources\eqSet.png">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Resource>
+    <Resource Include="logo.ico" />
+    <Resource Include="Resources\equipment.png">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Resource>
+    <Resource Include="Resources\logo.png">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Resource>
+    <Resource Include="Resources\logo_loading.png">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Resource>
+    <Resource Include="Resources\loading.png">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Resource>
+    <Resource Include="Resources\export.png">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Resource>
+    <Resource Include="Resources\delete.png">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Resource>
+    <Resource Include="Resources\back.png">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Resource>
+    <Resource Include="Resources\menu_dec.png">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Resource>
+    <Resource Include="Resources\option_bg.png">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Resource>
+    <Resource Include="Resources\rope_block.png">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Resource>
+    <Resource Include="Resources\setting.png">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Resource>
+    <Resource Include="Resources\top_banner.png">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Resource>
+    <EmbeddedResource Include="Properties\Resources.resx">
+      <Generator>ResXFileCodeGenerator</Generator>
+      <SubType>Designer</SubType>
+      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+    </EmbeddedResource>
+    <Resource Include="Fonts\PanuonIcon.ttf" />
+    <Content Include="Config\Config.json">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <None Include="OpenTK.dll.config" />
+    <None Include="packages.config" />
+    <None Include="Properties\app.manifest" />
+    <None Include="Properties\Settings.settings">
+      <Generator>SettingsSingleFileGenerator</Generator>
+      <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+    </None>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <COMReference Include="NetFwTypeLib">
+      <Guid>{58FBCF7C-E7A9-467C-80B3-FC65E8FCCA08}</Guid>
+      <VersionMajor>1</VersionMajor>
+      <VersionMinor>0</VersionMinor>
+      <Lcid>0</Lcid>
+      <WrapperTool>tlbimp</WrapperTool>
+      <Isolated>False</Isolated>
+      <EmbedInteropTypes>True</EmbedInteropTypes>
+    </COMReference>
+  </ItemGroup>
+  <ItemGroup>
+    <BootstrapperPackage Include=".NETFramework,Version=v4.8">
+      <Visible>False</Visible>
+      <ProductName>Microsoft .NET Framework 4.8 %28x86 和 x64%29</ProductName>
+      <Install>true</Install>
+    </BootstrapperPackage>
+    <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
+      <Visible>False</Visible>
+      <ProductName>.NET Framework 3.5 SP1</ProductName>
+      <Install>false</Install>
+    </BootstrapperPackage>
+  </ItemGroup>
+  <ItemGroup>
+    <Resource Include="Resources\menu_bg.png">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Resource>
+  </ItemGroup>
+  <ItemGroup />
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.119.0\build\net46\Stub.System.Data.SQLite.Core.NetFramework.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.119.0\build\net46\Stub.System.Data.SQLite.Core.NetFramework.targets'))" />
+    <Error Condition="!Exists('packages\EntityFramework.6.5.1\build\EntityFramework.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\EntityFramework.6.5.1\build\EntityFramework.props'))" />
+    <Error Condition="!Exists('packages\EntityFramework.6.5.1\build\EntityFramework.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\EntityFramework.6.5.1\build\EntityFramework.targets'))" />
+    <Error Condition="!Exists('packages\HarfBuzzSharp.NativeAssets.macOS.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.macOS.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\HarfBuzzSharp.NativeAssets.macOS.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.macOS.targets'))" />
+    <Error Condition="!Exists('packages\HarfBuzzSharp.NativeAssets.Win32.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Win32.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\HarfBuzzSharp.NativeAssets.Win32.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Win32.targets'))" />
+    <Error Condition="!Exists('packages\SkiaSharp.NativeAssets.macOS.2.88.9\build\net462\SkiaSharp.NativeAssets.macOS.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\SkiaSharp.NativeAssets.macOS.2.88.9\build\net462\SkiaSharp.NativeAssets.macOS.targets'))" />
+    <Error Condition="!Exists('packages\SkiaSharp.NativeAssets.Win32.2.88.9\build\net462\SkiaSharp.NativeAssets.Win32.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\SkiaSharp.NativeAssets.Win32.2.88.9\build\net462\SkiaSharp.NativeAssets.Win32.targets'))" />
+    <Error Condition="!Exists('packages\HarfBuzzSharp.NativeAssets.Linux.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Linux.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\HarfBuzzSharp.NativeAssets.Linux.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Linux.targets'))" />
+    <Error Condition="!Exists('packages\SkiaSharp.NativeAssets.Linux.NoDependencies.2.88.9\build\net462\SkiaSharp.NativeAssets.Linux.NoDependencies.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\SkiaSharp.NativeAssets.Linux.NoDependencies.2.88.9\build\net462\SkiaSharp.NativeAssets.Linux.NoDependencies.targets'))" />
+    <Error Condition="!Exists('packages\QuestPDF.2025.7.1\build\net4\QuestPDF.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\QuestPDF.2025.7.1\build\net4\QuestPDF.targets'))" />
+  </Target>
+  <Import Project="packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.119.0\build\net46\Stub.System.Data.SQLite.Core.NetFramework.targets" Condition="Exists('packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.119.0\build\net46\Stub.System.Data.SQLite.Core.NetFramework.targets')" />
+  <Import Project="packages\EntityFramework.6.5.1\build\EntityFramework.targets" Condition="Exists('packages\EntityFramework.6.5.1\build\EntityFramework.targets')" />
+  <Import Project="packages\HarfBuzzSharp.NativeAssets.macOS.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.macOS.targets" Condition="Exists('packages\HarfBuzzSharp.NativeAssets.macOS.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.macOS.targets')" />
+  <Import Project="packages\HarfBuzzSharp.NativeAssets.Win32.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Win32.targets" Condition="Exists('packages\HarfBuzzSharp.NativeAssets.Win32.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Win32.targets')" />
+  <Import Project="packages\SkiaSharp.NativeAssets.macOS.2.88.9\build\net462\SkiaSharp.NativeAssets.macOS.targets" Condition="Exists('packages\SkiaSharp.NativeAssets.macOS.2.88.9\build\net462\SkiaSharp.NativeAssets.macOS.targets')" />
+  <Import Project="packages\SkiaSharp.NativeAssets.Win32.2.88.9\build\net462\SkiaSharp.NativeAssets.Win32.targets" Condition="Exists('packages\SkiaSharp.NativeAssets.Win32.2.88.9\build\net462\SkiaSharp.NativeAssets.Win32.targets')" />
+  <Import Project="packages\HarfBuzzSharp.NativeAssets.Linux.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Linux.targets" Condition="Exists('packages\HarfBuzzSharp.NativeAssets.Linux.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Linux.targets')" />
+  <Import Project="packages\SkiaSharp.NativeAssets.Linux.NoDependencies.2.88.9\build\net462\SkiaSharp.NativeAssets.Linux.NoDependencies.targets" Condition="Exists('packages\SkiaSharp.NativeAssets.Linux.NoDependencies.2.88.9\build\net462\SkiaSharp.NativeAssets.Linux.NoDependencies.targets')" />
+  <Import Project="packages\QuestPDF.2025.7.1\build\net4\QuestPDF.targets" Condition="Exists('packages\QuestPDF.2025.7.1\build\net4\QuestPDF.targets')" />
+</Project>

+ 14 - 0
SWRIS.csproj.user

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <PublishUrlHistory>publish\</PublishUrlHistory>
+    <InstallUrlHistory />
+    <SupportUrlHistory />
+    <UpdateUrlHistory />
+    <BootstrapperUrlHistory />
+    <ErrorReportUrlHistory />
+    <FallbackCulture>zh-CN</FallbackCulture>
+    <VerifyUploadedFiles>false</VerifyUploadedFiles>
+    <ProjectView>ProjectFiles</ProjectView>
+  </PropertyGroup>
+</Project>

+ 25 - 0
SWRIS.sln

@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.13.35913.81 d17.13
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SWRIS", "SWRIS.csproj", "{3F57ECFF-C6C5-4EF7-9A4C-2E3295D7F774}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{3F57ECFF-C6C5-4EF7-9A4C-2E3295D7F774}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{3F57ECFF-C6C5-4EF7-9A4C-2E3295D7F774}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{3F57ECFF-C6C5-4EF7-9A4C-2E3295D7F774}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{3F57ECFF-C6C5-4EF7-9A4C-2E3295D7F774}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {5C24695B-216C-4511-ACA7-DF9791E78506}
+	EndGlobalSection
+EndGlobal

+ 280 - 0
Services/HistoryCleanupService.cs

@@ -0,0 +1,280 @@
+using SWRIS.Core;
+using SWRIS.Models;
+using SWRIS.Repository;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace SWRIS.Services
+{
+    public class HistoryCleanupService : IDisposable
+    {
+        private Timer _cleanupTimer;
+        private readonly IRecordRepository _recordRepository;
+        private bool _isRunning = false;
+        private bool _disposed = false;
+        private int _consecutiveFailures = 0;
+
+        public event EventHandler<DeleteResultModel> OnCleanupCompleted;
+        public event EventHandler<Exception> OnCleanupFailed;
+
+        public bool IsRunning => _isRunning;
+        public DateTime? NextScheduledTime { get; private set; }
+        public DateTime? LastCleanupTime { get; private set; }
+        public DeleteResultModel LastCleanupResult { get; private set; }
+
+        public HistoryCleanupService()
+        {
+            _recordRepository = new RecordRepository();
+            ValidateConfiguration();
+        }
+
+        public void StartScheduledCleanup()
+        {
+            if (_isRunning)
+            {
+                LogHelper.Warn("清理服务已在运行中");
+                return;
+            }
+
+            try
+            {
+                ValidateConfiguration();
+
+                var now = DateTime.Now;
+                var scheduledTime = CalculateNextScheduledTime(now);
+
+                var initialDelay = scheduledTime - now;
+
+                _cleanupTimer = new Timer(
+                    callback: _ => ExecuteCleanupAsync().ConfigureAwait(false),
+                    state: null,
+                    dueTime: initialDelay,
+                    period: TimeSpan.FromHours(24)
+                );
+
+                _isRunning = true;
+                NextScheduledTime = scheduledTime;
+                _consecutiveFailures = 0;
+
+                LogHelper.Info($"计划清理已启动。下次执行: {scheduledTime:yyyy-MM-dd HH:mm:ss}");
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error($"启动计划任务失败: {ex.Message}");
+                _isRunning = false;
+                OnCleanupFailed?.Invoke(this, ex);
+
+                // 延迟重试
+                Task.Delay(TimeSpan.FromMinutes(5)).ContinueWith(_ => RetryStartup());
+            }
+        }
+
+        private DateTime CalculateNextScheduledTime(DateTime now)
+        {
+            var timeSpan = App.Config.CleanScheduledTime;
+            var scheduledTime = new DateTime(now.Year, now.Month, now.Day,
+                timeSpan.Hours, timeSpan.Minutes, timeSpan.Seconds);
+
+            if (now > scheduledTime)
+            {
+                scheduledTime = scheduledTime.AddDays(1);
+            }
+
+            return scheduledTime;
+        }
+
+        private void RetryStartup(int retryCount = 0)
+        {
+            if (retryCount >= 3)
+            {
+                LogHelper.Error("启动重试次数超过限制,请检查系统配置");
+                return;
+            }
+
+            LogHelper.Info($"尝试重新启动清理服务 (重试 {retryCount + 1}/3)");
+            StartScheduledCleanup();
+        }
+
+        private async Task ExecuteCleanupAsync()
+        {
+            try
+            {
+                LastCleanupTime = DateTime.Now;
+                LogHelper.Info($"开始执行计划清理任务 ({LastCleanupTime:yyyy-MM-dd HH:mm:ss})");
+
+                var result = await Task.Run(() =>
+                    CleanupHistoryOlderThanDays(App.Config.DataSaveDays));
+
+                // 更新下一次执行时间
+                NextScheduledTime = CalculateNextScheduledTime(DateTime.Now);
+
+                // 重置连续失败计数
+                _consecutiveFailures = 0;
+
+                // 在UI线程更新结果
+                await Application.Current.Dispatcher.InvokeAsync(() =>
+                {
+                    LastCleanupResult = result;
+
+                    if (result.IsSuccess)
+                    {
+                        LogHelper.Info($"{result.Message} (执行时间: {result.OperationTime:HH:mm:ss})");
+                        LogHelper.Info($"删除记录: {result.DeletedRecords} 条, 删除文件: {result.DeletedFiles} 个");
+                    }
+                    else
+                    {
+                        LogHelper.Warn($"清理任务完成但有警告: {result.Message}");
+                    }
+
+                    OnCleanupCompleted?.Invoke(this, result);
+                });
+            }
+            catch (Exception ex)
+            {
+                _consecutiveFailures++;
+                LogHelper.Error($"清理执行失败 (失败次数: {_consecutiveFailures}): {ex.Message}", ex);
+
+                // 连续失败告警
+                if (_consecutiveFailures >= 3)
+                {
+                    LogHelper.Warn("连续清理失败次数过多,请检查系统状态");
+                }
+
+                OnCleanupFailed?.Invoke(this, ex);
+            }
+        }
+
+        public DeleteResultModel CleanupHistoryOlderThanDays(int days)
+        {
+            var result = new DeleteResultModel
+            {
+                OperationTime = DateTime.Now,
+                DataSaveDays = days
+            };
+            try
+            {
+                if (days <= 0)
+                {
+                    throw new ArgumentException("数据保存天数必须大于0", nameof(days));
+                }
+
+                var endTime = DateTime.Today.AddDays(-days);
+                LogHelper.Info($"开始清理 {endTime:yyyy-MM-dd} 之前的历史数据");
+
+                var (effect, filePaths) = _recordRepository.DeleteRecords(endTime);
+                result.DeletedRecords = effect;
+                result.DeletedFiles = DeleteAssociatedFiles(filePaths);
+                result.IsSuccess = true;
+                result.Message = $"清理完成,删除了 {result.DeletedRecords} 条记录和 {result.DeletedFiles} 个文件。";
+
+                LogHelper.Info($"数据库记录清理: {result.DeletedRecords} 条");
+                LogHelper.Info($"关联文件清理: {result.DeletedFiles} 个");
+            }
+            catch (Exception ex)
+            {
+                result.IsSuccess = false;
+                result.Message = $"清理过程中发生错误: {ex.Message}";
+                LogHelper.Error($"Cleanup Error: {ex}", ex);
+            }
+
+            return result;
+        }
+
+        private int DeleteAssociatedFiles(List<string> filePaths)
+        {
+            int deletedCount = 0;
+            int errorCount = 0;
+
+            if (filePaths == null || filePaths.Count == 0)
+            {
+                return 0;
+            }
+
+            LogHelper.Info($"开始清理 {filePaths.Count} 个关联文件");
+
+            foreach (var filePath in filePaths)
+            {
+                try
+                {
+                    if (File.Exists(filePath))
+                    {
+                        File.Delete(filePath);
+                        deletedCount++;
+                    }
+                    else
+                    {
+                        LogHelper.Warn($"文件不存在: {filePath}");
+                    }
+                }
+                catch (Exception ex)
+                {
+                    errorCount++;
+                    LogHelper.Error($"删除文件失败 {filePath}: {ex.Message}");
+                }
+            }
+
+            if (errorCount > 0)
+            {
+                LogHelper.Warn($"{errorCount} 个文件删除失败");
+            }
+
+            return deletedCount;
+        }
+
+        public void StopScheduledCleanup()
+        {
+            _cleanupTimer?.Change(Timeout.Infinite, Timeout.Infinite);
+            _isRunning = false;
+            NextScheduledTime = null;
+            LogHelper.Info("计划清理已停止");
+        }
+
+        public void TriggerManualCleanup()
+        {
+            LogHelper.Info("手动触发清理任务");
+            ExecuteCleanupAsync().ConfigureAwait(false);
+        }
+
+        private void ValidateConfiguration()
+        {
+            if (App.Config?.CleanScheduledTime == null)
+            {
+                throw new InvalidOperationException("清理计划时间未配置");
+            }
+
+            if (App.Config.DataSaveDays <= 0)
+            {
+                throw new InvalidOperationException("数据保存天数必须大于0");
+            }
+        }
+
+        public void Dispose()
+        {
+            Dispose(true);
+            GC.SuppressFinalize(this);
+        }
+
+        protected virtual void Dispose(bool disposing)
+        {
+            if (!_disposed)
+            {
+                if (disposing)
+                {
+                    StopScheduledCleanup();
+                    _cleanupTimer?.Dispose();
+                    _cleanupTimer = null;
+                }
+                _disposed = true;
+            }
+        }
+
+        ~HistoryCleanupService()
+        {
+            Dispose(false);
+        }
+    }
+}

+ 67 - 0
SoftAuthDialog.xaml

@@ -0,0 +1,67 @@
+<Window x:Class="SWRIS.SoftAuthDialog"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        xmlns:local="clr-namespace:SWRIS" 
+        xmlns:pu="https://opensource.panuon.com/wpf-ui"
+        TextElement.FontSize="16"   
+        FontFamily="Microsoft YaHei"   
+        WindowStyle="None"     
+        mc:Ignorable="d" Loaded="Window_Loaded"  
+        WindowStartupLocation="CenterScreen" 
+        AllowsTransparency="True"     
+        Background="Transparent"
+        Title="SoftAuthDialog"  Width="540" Height="470" >
+    <Border BorderThickness="5"  CornerRadius="15" Background="#FFFFFF" MouseLeftButtonDown="Card_MouseLeftButtonDown">
+        <Grid>
+            <StackPanel Orientation="Vertical" Margin="25,0" HorizontalAlignment="Center">
+                <Image Name="imgQRCode" Height="200" Width="200" Margin="40" />
+                <StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
+                    <Label Content="本机码:" HorizontalAlignment="Right"/>
+                    <TextBlock Name="txtMachineCode" Text="{Binding Path=AuthorizeModel.MachineCode}"  VerticalAlignment="Center" TextAlignment="Right"/>
+                    <Button Content="复制" Margin="10,0"   Cursor="Hand" BorderThickness="0" Foreground="#0496fc" Click="Copy_Click"/>
+                </StackPanel>
+                <StackPanel Orientation="Vertical">
+                    <StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,15,0,25">
+                        <Label Content="注册码:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
+                        <ContentControl  Content="{Binding Path=AuthorizeModel.FinalData}">
+                            <ContentControl.ContentTemplate>
+                                <DataTemplate>
+                                    <StackPanel Orientation="Horizontal">
+                                        <TextBox Text="{Binding First}" MinWidth="45" VerticalAlignment="Center" MaxLength="4" Padding="2" >
+                                            <TextBox.CommandBindings>
+                                                <CommandBinding Command="ApplicationCommands.Paste" Executed="PasteExecuted" />
+                                            </TextBox.CommandBindings>
+                                        </TextBox>
+                                        <Label Content="-"/>
+                                        <TextBox Text="{Binding Second}" MinWidth="45" VerticalAlignment="Center"  MaxLength="4" Padding="2" >
+                                            <TextBox.CommandBindings>
+                                                <CommandBinding Command="ApplicationCommands.Paste" Executed="PasteExecuted" />
+                                            </TextBox.CommandBindings>
+                                        </TextBox>
+                                        <Label Content="-"/>
+                                        <TextBox Text="{Binding Third}" MinWidth="45" VerticalAlignment="Center"  MaxLength="4" Padding="2" >
+                                            <TextBox.CommandBindings>
+                                                <CommandBinding Command="ApplicationCommands.Paste" Executed="PasteExecuted" />
+                                            </TextBox.CommandBindings>
+                                        </TextBox>
+                                        <Label Content="-"/>
+                                        <TextBox Text="{Binding Fourth}" MinWidth="45" VerticalAlignment="Center"  MaxLength="4" Padding="2"  TextChanged="Fourth_TextChanged" />
+                                    </StackPanel>
+                                </DataTemplate>
+                            </ContentControl.ContentTemplate>
+                        </ContentControl>
+                        <TextBlock Name="piCheckResult" Margin="10,0,0,0" Width="25" Height="25" VerticalAlignment="Center" Foreground="LawnGreen" Visibility="Hidden"/>
+                    </StackPanel>
+                </StackPanel>
+                <StackPanel  Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,20">
+                    <Button Name="btnSave" IsDefault="True" Margin="0 0 20 0" Content="激活" Height="30"
+                              Width="50" Click="BtnSave_Click" Cursor="Hand"/>
+                    <Button IsCancel="True" Margin="20 0 0 0" Content="取消" Width="50" Height="30" 
+                             Cursor="Hand"/>
+                </StackPanel>
+            </StackPanel>
+        </Grid>
+    </Border>
+</Window>

+ 80 - 0
SoftAuthDialog.xaml.cs

@@ -0,0 +1,80 @@
+using QRCoder;
+using SWRIS.Core;
+using SWRIS.Extensions;
+using SWRIS.Models;
+using System.Drawing;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+
+namespace SWRIS
+{
+    /// <summary>
+    /// SoftAuthDialog.xaml 的交互逻辑
+    /// </summary>
+    public partial class SoftAuthDialog : Window
+    {
+        public SoftAuthorizeModel AuthorizeModel { get; set; }
+        public SoftAuthDialog()
+        {
+            InitializeComponent();
+            AuthorizeModel = new SoftAuthorizeModel() { MachineCode = SoftAuth.GetMachineCode() };
+            DataContext = this;
+        }
+
+        private void Window_Loaded(object sender, RoutedEventArgs e)
+        {
+            if (AuthorizeModel.MachineCode != null)
+            {
+                QRCodeGenerator qrGenerator = new QRCodeGenerator();
+                QRCodeData qrCodeData = qrGenerator.CreateQrCode(AuthorizeModel.MachineCode, QRCodeGenerator.ECCLevel.Q);
+                QRCode qrCode = new QRCode(qrCodeData);
+                Bitmap qrCodeImage = qrCode.GetGraphic(10);
+                imgQRCode.Source = qrCodeImage.ConvertBitmapToBitmapImage();
+            }
+        }
+
+        private void Copy_Click(object sender, RoutedEventArgs e)
+        {
+            Clipboard.SetDataObject(AuthorizeModel.MachineCode);
+        }
+
+        private void PasteExecuted(object sender, ExecutedRoutedEventArgs e)
+        {
+            IDataObject iData = Clipboard.GetDataObject();
+            if (iData.GetDataPresent(DataFormats.Text))
+            {
+                var finalCode = (string)iData.GetData(DataFormats.Text);
+                AuthorizeModel.FinalData.Set(finalCode);
+            }
+            e.Handled = true;
+        }
+
+        private void BtnSave_Click(object sender, RoutedEventArgs e)
+        {
+            DialogResult = SoftAuth.WriteRegistry(AuthorizeModel.FinalCode);
+        }
+
+        private void Fourth_TextChanged(object sender, TextChangedEventArgs e)
+        {
+            var fourth = sender as TextBox;
+            if (fourth.Text.Length == fourth.MaxLength)
+            {
+                if (SoftAuth.CheckFinalCode(AuthorizeModel.FinalCode))
+                {
+                    piCheckResult.Visibility = Visibility.Visible;
+                    btnSave.IsEnabled = true;
+                    return;
+                }
+            }
+            piCheckResult.Visibility = Visibility.Hidden;
+            btnSave.IsEnabled = false;
+        }
+
+        private void Card_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+        {
+            base.OnMouseLeftButtonDown(e);
+            DragMove();
+        }
+    }
+}

+ 362 - 0
Styles/Styles.xaml

@@ -0,0 +1,362 @@
+<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+          xmlns:pu="https://opensource.panuon.com/wpf-ui">
+
+    <Style TargetType="ToolTip">
+        <Setter Property="Background" Value="#141332"/>
+        <Setter Property="BorderBrush" Value="#3B3B7B"/>
+        <Setter Property="Foreground" Value="LightGray"/>
+    </Style>
+    <DropShadowEffect x:Key="DropShadow"
+           Color="LightGray"
+           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"/>
+        <Setter Property="pu:ScrollBarHelper.HoverThumbBackground" Value="#88736AFA"/>
+    </Style>
+
+    <Style TargetType="DataGrid" BasedOn="{StaticResource {x:Type DataGrid}}">
+        <Setter Property="Background" Value="Transparent"/>
+        <Setter Property="Foreground" Value="#FFFFFF"/>
+        <Setter Property="VerticalContentAlignment" Value="Center"/>
+        <Setter Property="CanUserSortColumns" Value="True"/>
+        <Setter Property="MinRowHeight" Value="45"/>
+        <Setter Property="FontSize" Value="18"/>
+        <Setter Property="BorderThickness" Value="0"/>
+        <Setter Property="CanUserAddRows" Value="False" />
+        <Setter Property="CanUserDeleteRows" Value="False" />
+        <Setter Property="AutoGenerateColumns" Value="False" />
+        <Setter Property="HorizontalGridLinesBrush" Value="Transparent" />
+        <Setter Property="VerticalGridLinesBrush" Value="Transparent" />
+        <Setter Property="GridLinesVisibility" Value="None"/>
+        <Setter Property="pu:DataGridHelper.ColumnHeaderBackground" Value="#34346A"/>
+        <Setter Property="pu:DataGridHelper.ColumnHeaderPanelBackground" Value="#34346A"/>
+        <Setter Property="pu:DataGridHelper.ColumnHeaderPanelSeparatorVisibility" Value="Hidden"/>
+        <Setter Property="pu:DataGridHelper.ColumnHeaderHoverBackground" Value="#34346A"/>
+        <Setter Property="pu:DataGridHelper.ColumnHeaderClickBackground" Value="#34346A"/>
+        <Setter Property="pu:DataGridHelper.ColumnHeaderPanelCornerRadius" Value="10,10,0,0"/>
+        <Setter Property="pu:DataGridHelper.RowHeaderBackground" Value="Transparent"/>
+        <Setter Property="pu:ScrollBarHelper.ThumbBackground" Value="#55736AFA"/>
+        <Setter Property="pu:ScrollBarHelper.ThumbCornerRadius" Value="4"/>
+        <Setter Property="pu:ScrollBarHelper.HoverThumbBackground" Value="#88736AFA"/>
+        
+    </Style>
+    <Style TargetType="DataGridColumnHeader" BasedOn="{StaticResource {x:Type DataGridColumnHeader}}">
+        <Setter Property="SnapsToDevicePixels" Value="False" />
+        <Setter Property="VerticalContentAlignment" Value="Center"/>
+        <Setter Property="HorizontalContentAlignment" Value="Center"/>
+        <Setter Property="Foreground" Value="#97A6D4" />
+        <Setter Property="FontWeight" Value="Medium" />
+        <Setter Property="Padding" Value="15,0"/>
+        <Setter Property="Height" Value="70"/>
+    </Style>
+    <Style TargetType="ComboBox" BasedOn="{StaticResource {x:Type ComboBox}}">
+        <Setter Property="BorderBrush" Value="#3B3B7B"/>
+        <Setter Property="Background" Value="#202045"/>
+        <Setter Property="Foreground" Value="#7886B2"/>
+        <Setter Property="pu:ComboBoxHelper.CornerRadius" Value="5"/>
+        <Setter Property="Padding" Value="10,0"/>
+        <Setter Property="Height" Value="40"/>
+        <Setter Property="Width" Value="200"/>
+        <Setter Property="FontSize" Value="18"/>
+        <Setter Property="MaxDropDownHeight" Value="500"/>
+        <Setter Property="BorderThickness" Value="2,2,2,2"/>
+        <Setter Property="pu:ComboBoxHelper.WatermarkForeground" Value="#7886B2"/>
+        <Setter Property="pu:ComboBoxHelper.ItemsSelectedBackground" Value="#34346A"/>
+        <Setter Property="pu:ComboBoxHelper.ItemsHoverBackground" Value="#7334346A"/>
+        <Setter Property="pu:ComboBoxHelper.HoverBorderBrush" Value="#6158E5"/>
+        <Setter Property="pu:ComboBoxHelper.HoverForeground" Value="#FFFFFF"/>
+        <Setter Property="pu:ComboBoxHelper.FocusedForeground" Value="#FFFFFF"/>
+        <Setter Property="pu:ComboBoxHelper.ItemsSelectedForeground" Value="#FFFFFF"/>
+    </Style>
+    <Style TargetType="pu:DateTimePicker" BasedOn="{StaticResource {x:Type pu:DateTimePicker}}">
+        <Setter Property="Padding" Value="15,0"/>
+        <Setter Property="Background" Value="#141332"/>
+        <Setter Property="BorderBrush" Value="#3B3B7B"/>
+        <Setter Property="CornerRadius" Value="5"/>
+        <Setter Property="Height" Value="40"/>
+        <Setter Property="Width" Value="280"/>
+        <Setter Property="FontFamily" Value="{StaticResource PuHuiTiRegular}"/>
+        <Setter Property="FontSize" Value="18"/>
+        <Setter Property="FirstDayOfWeek" Value="Monday"/>
+        <Setter Property="TextStringFormat" Value="yyyy/MM/dd HH:mm:ss"/>
+        <Setter Property="Mode" Value="DateTime"/>
+        <Setter Property="Foreground" Value="#7886B2"/>
+        <Setter Property="BorderThickness" Value="2,2,2,2"/>
+        <Setter Property="WatermarkForeground" Value="#7886B2"/>
+        <Setter Property="ItemsCheckedForeground" Value="#FFFFFF"/>
+        <Setter Property="ItemsCheckedBackground" Value="#615CDD"/>
+        <Setter Property="ItemsHoverBackground" Value="#77615CDD"/>
+        <Setter Property="ItemsHoverForeground" Value="#FFFFFF"/>
+        <Setter Property="ItemsForeground" Value="#7886B2"/>
+        <Setter Property="ItemsCheckedCornerRadius" Value="5"/>
+        <Setter Property="ItemsHoverCornerRadius" Value="5"/>
+        <Setter Property="pu:IconHelper.ToggleArrowFontSize" Value="18"/>
+        <Setter Property="FontStretch" Value="Condensed"/>
+        <Setter Property="HoverForeground" Value="#FFFFFF"/>
+        <Setter Property="HoverBorderBrush" Value="#6158E5"/>
+        <Setter Property="FocusedForeground" Value="#FFFFFF"/>
+    </Style>
+    <Style TargetType="PasswordBox" BasedOn="{StaticResource {x:Type PasswordBox}}">
+        <Setter Property="Height" Value="30"/>
+        <Setter Property="BorderBrush" Value="#FFC9CCD4"/>
+        <Setter Property="pu:PasswordBoxHelper.CornerRadius" Value="5"/>
+        <Setter Property="pu:TextBoxHelper.FocusedShadowColor" Value="#FFDEE8F8"/>
+        <Setter Property="pu:PasswordBoxHelper.WatermarkForeground" Value="#CCCCCC"/>
+    </Style>
+    <Style TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
+        <Setter Property="Height" Value="40"/>
+        <Setter Property="Width" Value="150"/>
+        <Setter Property="Background" Value="#202045"/>
+        <Setter Property="BorderBrush" Value="#3B3B7B"/>
+        <Setter Property="BorderThickness" Value="2,2,2,2"/>
+        <Setter Property="Foreground" Value="#FFFFFF"/>
+        <Setter Property="Padding" Value="6"/>
+        <Setter Property="FontSize" Value="18"/>
+        <Setter Property="HorizontalAlignment" Value="Center"/>
+        <Setter Property="pu:TextBoxHelper.CornerRadius" Value="5"/>
+        <Setter Property="pu:TextBoxHelper.HoverBorderBrush" Value="#6158E5"/>
+    </Style>
+    <Style TargetType="pu:NumberInput" BasedOn="{StaticResource {x:Type pu:NumberInput}}">
+        <Setter Property="Height" Value="30"/>
+        <Setter Property="BorderBrush" Value="#FFC9CCD4"/>
+        <Setter Property="pu:NumberInput.CornerRadius" Value="5"/>
+        <Setter Property="pu:NumberInput.FocusedShadowColor" Value="#FFDEE8F8"/>
+        <Setter Property="pu:NumberInput.WatermarkForeground" Value="#CCCCCC"/>
+        <Setter Property="pu:NumberInput.HoverBorderBrush" Value="#6158E5"/>
+    </Style>
+    <Style TargetType="CheckBox" BasedOn="{StaticResource {x:Type CheckBox}}">
+        <Setter Property="Foreground" Value="#7886B2"/>
+        <Setter Property="BorderBrush" Value="#7886B2"/>
+        <Setter Property="BorderThickness" Value="1.6"/>
+        <Setter Property="FontSize" Value="18"/>
+        <Setter Property="pu:CheckBoxHelper.CheckedGlyphBrush" Value="Green"/>
+        <Setter Property="pu:CheckBoxHelper.CheckedBorderThickness" Value="1.6"/>
+        <Setter Property="pu:CheckBoxHelper.GlyphThickness" Value="1.6"/>
+        <Setter Property="pu:CheckBoxHelper.CornerRadius" Value="2"/>
+    </Style>
+    <Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
+        <Setter Property="pu:ButtonHelper.CornerRadius" Value="15"/>
+        <Setter Property="Foreground" Value="#FFFFFF"/>
+        <Setter Property="FontSize" Value="20"/>
+        <Setter Property="Width" Value="100"/>
+        <Setter Property="Cursor" Value="Hand"/>
+        <Setter Property="Height" Value="40"/>
+        <Setter Property="Background">
+            <Setter.Value>
+                <LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
+                    <GradientStop Color="#4E38F0" Offset="0.6"/>
+                    <GradientStop Color="#736AFA" Offset="1.2"/>
+                </LinearGradientBrush>
+            </Setter.Value>
+        </Setter>
+    </Style>
+    <Style x:Key="CancelButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
+        <Setter Property="pu:ButtonHelper.CornerRadius" Value="15"/>
+        <Setter Property="Foreground" Value="#FFFFFF"/>
+        <Setter Property="Width" Value="120"/>
+        <Setter Property="Cursor" Value="Hand"/>
+        <Setter Property="Height" Value="60"/>
+        <Setter Property="Background">
+            <Setter.Value>
+                <LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
+                    <GradientStop Color="#615E7C" Offset="0.6"/>
+                    <GradientStop Color="#807E9F" Offset="1.2"/>
+                </LinearGradientBrush>
+            </Setter.Value>
+        </Setter>
+    </Style>
+    <Style x:Key="BoxRadioButton" TargetType="{x:Type RadioButton}">
+        <Setter Property="Background" Value="Transparent"/>
+        <Setter Property="Foreground" Value="#363636"/>
+        <Setter Property="Padding" Value="3 2 3 2"/>
+        <Setter Property="FontSize" Value="14"/>
+        <Setter Property="BorderThickness" Value="2"/>
+        <Setter Property="Height" Value="32"/>
+        <Setter Property="Width" Value="110"/>
+        <Setter Property="SnapsToDevicePixels" Value="true"/>
+        <Setter Property="BorderBrush" Value="#C9CCD4" />
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate TargetType="{x:Type RadioButton}">
+                    <Grid x:Name="grid" VerticalAlignment="Center">
+                        <Border BorderThickness="{TemplateBinding BorderThickness}"
+                                BorderBrush="{TemplateBinding BorderBrush}"
+                                Height="{TemplateBinding Height}"
+                                HorizontalAlignment="Center"
+                                Background="{TemplateBinding Background}" Width="{TemplateBinding Width}" CornerRadius="5,5,0,5">
+                            <ContentPresenter VerticalAlignment="Center"
+                                              HorizontalAlignment="Center"
+                                              Margin="{TemplateBinding Padding}"
+                                              SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
+                        </Border>
+                        <!--选中的状态标识-->
+                        <TextBlock Text="&#xe9eb;"
+                                   x:Name="checkState"
+                                   FontFamily="{StaticResource PanuonIconFont }"
+                                   VerticalAlignment="Bottom" Visibility="Collapsed"
+                                   FontSize="16" Margin="3,0,0,3" HorizontalAlignment="Right" Foreground="#017DE6"/>
+                    </Grid>
+                    <!--触发器:设置选中状态符号-->
+                    <ControlTemplate.Triggers>
+                        <Trigger Property="IsChecked" Value="true">
+                            <Setter Property="Visibility" Value="Visible" TargetName="checkState" ></Setter>
+                            <Setter Property="BorderBrush" Value="#017DE6"></Setter>
+                        </Trigger>
+                        <Trigger Property="IsMouseOver" Value="true">
+                            <Setter Property="BorderBrush" Value="#BD017DE6"></Setter>
+                        </Trigger>
+                        <Trigger Property="IsEnabled" Value="False">
+                            <Setter Property="Opacity" Value="0.5" TargetName="grid" ></Setter>
+                        </Trigger>
+                    </ControlTemplate.Triggers>
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </Style>
+    <Style TargetType="Label" BasedOn="{StaticResource {x:Type Label}}">
+        <Setter Property="Background" Value="Transparent"/>
+    </Style>
+    <Style TargetType="{x:Type pu:Carousel}" BasedOn="{StaticResource {x:Type pu:Carousel}}">
+        <Setter Property="IsCyclic" Value="True"/>
+        <Setter Property="pu:Carousel.IndicatorVisibility" Value="Collapsed"/>
+        <Setter Property="pu:Carousel.PageTurnButtonVisibility" Value="Collapsed"/>
+    </Style>
+    <Style TargetType="TabControl" BasedOn="{StaticResource {x:Type TabControl}}">
+        <Setter Property="pu:TabControlHelper.HeaderPanelAlignment" Value="Center" />
+        <Setter Property="pu:TabControlHelper.ItemsHeight" Value="45"/>
+        <Setter Property="pu:TabControlHelper.ItemsPadding" Value="15,0"/>
+        <Setter Property="BorderThickness" Value="0"/>
+        <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.ItemsHoverRibbonLineThickness" Value="1"/>
+        <Setter Property="pu:TabControlHelper.ItemsSelectedRibbonLineBrush" Value="#C62F2F"/>
+        <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" />
+        <Setter Property="Maximum" Value="100" />
+        <Setter Property="Height" Value="14" />
+        <Setter Property="pu:ProgressBarHelper.CornerRadius" Value="7" />
+        <Setter Property="pu:ProgressBarHelper.InvertedForeground" Value="#F1F1F1" />
+        <Setter Property="pu:ProgressBarHelper.IsPercentVisible" Value="True" />
+        <Setter Property="pu:ShadowHelper.ShadowDepth" Value="5" />
+        <Setter Property="pu:ShadowHelper.BlurRadius" Value="15" />
+    </Style>
+    <Style TargetType="pu:Pagination">
+        <Setter Property="Cursor" Value="Hand"/>
+        <Setter Property="Height" Value="30" />
+        <Setter Property="FontSize" Value="14"/>
+        <Setter Property="Foreground" Value="#000000" />
+        <Setter Property="ItemsBackground" Value="Transparent" />
+        <Setter Property="ItemsCornerRadius" Value="5" />
+        <Setter Property="ItemsSelectedBackground" Value="#017DE6" />
+        <Setter Property="ItemsSelectedForeground" Value="#FFFFFF" />
+        <Setter Property="ItemsHoverBackground" Value="LightGray" />
+        <Setter Property="ItemsHoverForeground" Value="#000000" />
+        <Setter Property="ItemsMargin" Value="6,0"/>
+        <Setter Property="PageTurnButtonStyle">
+            <Setter.Value>
+                <Style TargetType="RepeatButton" BasedOn="{StaticResource {x:Static pu:Pagination.PageTurnButtonStyleKey}}">
+                    <Setter Property="Background" Value="Transparent" />
+                    <Setter Property="pu:RepeatButtonHelper.CornerRadius"  Value="5" />
+                    <Setter Property="Foreground" Value="#000000" />
+                    <Setter Property="FontWeight" Value="Bold" />
+                    <Setter Property="pu:RepeatButtonHelper.HoverBackground" Value="LightGray" />
+                    <Setter Property="pu:RepeatButtonHelper.HoverForeground" Value="#000000" />
+                </Style>
+            </Setter.Value>
+        </Setter>
+    </Style>
+    <Style TargetType="ToggleButton" BasedOn="{StaticResource {x:Type ToggleButton}}">
+        <Setter Property="Background" Value="#141332"/>
+        <Setter Property="BorderBrush" Value="#3B3B7B"/>
+        <Setter Property="BorderThickness" Value="2"/>
+        <Setter Property="Foreground" Value="#615CDD"/>
+        <Setter Property="FontSize" Value="18"/>
+        <Setter Property="Height" Value="40"/>
+        <Setter Property="Width" Value="95"/>
+        <Setter Property="FontWeight" Value="Bold"/>
+        <Setter Property="pu:ToggleButtonHelper.CornerRadius" Value="5"/>
+        <Setter Property="pu:ToggleButtonHelper.HoverBackground" Value="#88615CDD"/>
+        <Setter Property="pu:ToggleButtonHelper.HoverForeground" Value="#88FFFFFF"/>
+        <Setter Property="pu:ToggleButtonHelper.HoverBorderBrush" Value="#6158E5"/>
+        <Setter Property="pu:ToggleButtonHelper.CheckedBackground" Value="#615CDD"/>
+        <Setter Property="pu:ToggleButtonHelper.CheckedForeground" Value="#FFFFFF"/>
+        <Setter Property="pu:ToggleButtonHelper.CheckedBorderThickness" Value="0"/>
+    </Style>
+    <Style TargetType="TextBlock" x:Key="HeartbeatTextBlockStyle">
+        <Setter Property="Text" Value="&#xE955;"/>
+        <Setter Property="FontFamily" Value="{StaticResource PanuonIconFont}"/>
+        <Setter Property="RenderTransform">
+            <Setter.Value>
+                <ScaleTransform ScaleX="1" ScaleY="1"/>
+            </Setter.Value>
+        </Setter>
+        <Style.Triggers>
+            <DataTrigger Binding="{Binding IsHeartbeatReceived}" Value="True">
+                <DataTrigger.EnterActions>
+                    <BeginStoryboard>
+                        <Storyboard>
+                            <!-- 心跳动画序列 -->
+                            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="RenderTransform.ScaleX">
+                                <!-- 第一次强跳动 -->
+                                <LinearDoubleKeyFrame KeyTime="0:0:0.0" Value="1"/>
+                                <LinearDoubleKeyFrame KeyTime="0:0:0.15" Value="1.3"/>
+                                <LinearDoubleKeyFrame KeyTime="0:0:0.3" Value="1"/>
+                                <!-- 第二次弱跳动 -->
+                                <LinearDoubleKeyFrame KeyTime="0:0:0.4" Value="1.15"/>
+                                <LinearDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/>
+                            </DoubleAnimationUsingKeyFrames>
+                            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="RenderTransform.ScaleY">
+                                <!-- 第一次强跳动 -->
+                                <LinearDoubleKeyFrame KeyTime="0:0:0.0" Value="1"/>
+                                <LinearDoubleKeyFrame KeyTime="0:0:0.15" Value="1.3"/>
+                                <LinearDoubleKeyFrame KeyTime="0:0:0.3" Value="1"/>
+                                <!-- 第二次弱跳动 -->
+                                <LinearDoubleKeyFrame KeyTime="0:0:0.4" Value="1.15"/>
+                                <LinearDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/>
+                            </DoubleAnimationUsingKeyFrames>
+                            <!-- 颜色动画 - 心跳红色效果 -->
+                            <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground.Color">
+                                <DiscreteColorKeyFrame KeyTime="0:0:0.0" Value="#7F74FF"/>
+                                <!-- 第一次跳动 - 深红色 -->
+                                <LinearColorKeyFrame KeyTime="0:0:0.08" Value="#C62F2F"/>
+                                <LinearColorKeyFrame KeyTime="0:0:0.2" Value="#C62F2F"/>
+                                <!-- 快速回到原色 -->
+                                <LinearColorKeyFrame KeyTime="0:0:0.28" Value="#7F74FF"/>
+                                <!-- 第二次跳动 - 浅红色 -->
+                                <LinearColorKeyFrame KeyTime="0:0:0.33" Value="#C62F2F"/>
+                                <LinearColorKeyFrame KeyTime="0:0:0.4" Value="#C62F2F"/>
+                                <!-- 最终回到原色 -->
+                                <LinearColorKeyFrame KeyTime="0:0:0.5" Value="#7F74FF"/>
+                            </ColorAnimationUsingKeyFrames>
+                        </Storyboard>
+                    </BeginStoryboard>
+                </DataTrigger.EnterActions>
+            </DataTrigger>
+        </Style.Triggers>
+    </Style>
+
+</ResourceDictionary>

BIN
logo.ico


+ 51 - 0
packages.config

@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Dapper" version="2.1.66" targetFramework="net48" />
+  <package id="EntityFramework" version="6.5.1" 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" />
+  <package id="HarfBuzzSharp.NativeAssets.Win32" version="7.3.0.3" targetFramework="net48" />
+  <package id="HslCommunication" version="12.3.3" targetFramework="net48" />
+  <package id="Interop.IWshRuntimeLibrary" version="1.0.1" targetFramework="net48" />
+  <package id="Microsoft.AspNet.WebApi.Client" version="6.0.0" targetFramework="net48" />
+  <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-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" />
+  <package id="Newtonsoft.Json.Bson" version="1.0.3" targetFramework="net48" />
+  <package id="OpenTK" version="3.3.1" targetFramework="net48" />
+  <package id="OpenTK.GLWpfControl" version="3.3.0" targetFramework="net48" />
+  <package id="Panuon.WPF" version="1.1.3" targetFramework="net48" />
+  <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="ScottPlot" version="5.0.55" targetFramework="net48" />
+  <package id="ScottPlot.WPF" version="5.0.55" 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" />
+  <package id="SkiaSharp.NativeAssets.macOS" version="2.88.9" targetFramework="net48" />
+  <package id="SkiaSharp.NativeAssets.Win32" version="2.88.9" targetFramework="net48" />
+  <package id="SkiaSharp.Views.Desktop.Common" version="2.88.9" targetFramework="net48" />
+  <package id="SkiaSharp.Views.WPF" version="2.88.9" targetFramework="net48" />
+  <package id="Stub.System.Data.SQLite.Core.NetFramework" version="1.0.119.0" targetFramework="net48" />
+  <package id="System.Buffers" version="4.6.1" targetFramework="net48" />
+  <package id="System.ComponentModel.Annotations" version="5.0.0" targetFramework="net48" />
+  <package id="System.Data.SQLite" version="1.0.119.0" targetFramework="net48" />
+  <package id="System.Data.SQLite.Core" version="1.0.119.0" targetFramework="net48" />
+  <package id="System.Data.SQLite.EF6" version="1.0.119.0" targetFramework="net48" />
+  <package id="System.Data.SQLite.Linq" version="1.0.119.0" targetFramework="net48" />
+  <package id="System.Drawing.Common" version="4.7.3" targetFramework="net48" />
+  <package id="System.IO.Pipelines" version="9.0.8" targetFramework="net48" />
+  <package id="System.Memory" version="4.6.3" targetFramework="net48" />
+  <package id="System.Numerics.Vectors" version="4.6.1" targetFramework="net48" />
+  <package id="System.Runtime.CompilerServices.Unsafe" version="6.1.2" targetFramework="net48" />
+  <package id="System.Speech" version="10.0.0" targetFramework="net48" />
+  <package id="System.Text.Encodings.Web" version="9.0.8" targetFramework="net48" />
+  <package id="System.Text.Json" version="9.0.8" targetFramework="net48" />
+  <package id="System.Threading.Tasks.Extensions" version="4.6.3" targetFramework="net48" />
+  <package id="System.ValueTuple" version="4.5.0" targetFramework="net48" />
+</packages>