From d960cb5912956cda0840a04136b0e266daed7676 Mon Sep 17 00:00:00 2001 From: czj Date: Fri, 5 Jun 2026 10:57:09 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=A1=B9=E7=9B=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ADP.sln | 116 +++ ADP/ADP.csproj | 24 + ADP/App.xaml | 15 + ADP/App.xaml.cs | 88 ++ ADP/AssemblyInfo.cs | 10 + ADP/Resources/Images/error.png | Bin 0 -> 7146 bytes ADP/Resources/Images/info.png | Bin 0 -> 5521 bytes ADP/Resources/Images/warning.png | Bin 0 -> 5216 bytes ADP/ViewModels/Dialogs/MessageBoxViewModel.cs | 120 +++ ADP/ViewModels/ShellViewModel.cs | 772 ++++++++++++++++++ ADP/Views/Dialogs/MessageBoxView.xaml | 87 ++ ADP/Views/Dialogs/MessageBoxView.xaml.cs | 36 + ADP/Views/LoginModuleView.xaml | 21 + ADP/Views/LoginModuleView.xaml.cs | 38 + ADP/Views/ShellView.xaml | 339 ++++++++ ADP/Views/ShellView.xaml.cs | 51 ++ Command/Command.csproj | 13 + Command/CommandApplication.cs | 268 ++++++ Command/CommandArray.cs | 261 ++++++ Command/CommandMath.cs | 473 +++++++++++ Command/CommandRadixChange.cs | 153 ++++ Command/CommandStringProcessing.cs | 196 +++++ Command/CommandSystem.cs | 463 +++++++++++ Command/CommandTime.cs | 95 +++ Command/Delay.cs | 58 ++ Common/Attributes/ADPCommandAttribute.cs | 32 + Common/Common.csproj | 15 + Common/MiniDump.cs | 112 +++ Common/Tool/ExpressionEvaluator.cs | 104 +++ DeviceCommand/Base/IBaseInterface.cs | 16 + DeviceCommand/Base/IModbusDevice.cs | 19 + DeviceCommand/Base/ISerialPort.cs | 13 + DeviceCommand/Base/ITcp.cs | 15 + DeviceCommand/Base/ModbusRtu.cs | 164 ++++ DeviceCommand/Base/ModbusTcp.cs | 158 ++++ DeviceCommand/Base/Serial_Port.cs | 156 ++++ DeviceCommand/Base/TCP.cs | 179 ++++ DeviceCommand/DeviceCommand.csproj | 18 + DeviceCommand/Devices/IT7800E.cs | 249 ++++++ DeviceCommand/Devices/N36200.cs | 382 +++++++++ DeviceCommand/Devices/N36600.cs | 459 +++++++++++ DeviceCommand/Devices/N69200.cs | 294 +++++++ DeviceCommand/Devices/SDS2000X_HD.cs | 213 +++++ DeviceCommand/Devices/SPAW7000.cs | 217 +++++ Logger/Logger.csproj | 13 + Logger/LoggerHelper.cs | 114 +++ Logger/Nlog.config | 37 + LoginModule/LoginModule.cs | 21 + LoginModule/LoginModule.csproj | 14 + LoginModule/ViewModels/LoginViewModel.cs | 48 ++ LoginModule/ViewModels/RegisterViewModel.cs | 51 ++ LoginModule/Views/LoginView.xaml | 163 ++++ LoginModule/Views/LoginView.xaml.cs | 28 + LoginModule/Views/RegisterView.xaml | 175 ++++ LoginModule/Views/RegisterView.xaml.cs | 28 + MainModule/MainModule.cs | 28 + MainModule/MainModule.csproj | 15 + .../ViewModels/AutomatedTestingViewModel.cs | 114 +++ MainModule/ViewModels/MainViewModel.cs | 67 ++ .../ViewModels/ProtocolStartViewModel.cs | 109 +++ MainModule/Views/AutomatedTestingView.xaml | 128 +++ MainModule/Views/AutomatedTestingView.xaml.cs | 28 + MainModule/Views/MainView.xaml | 258 ++++++ MainModule/Views/MainView.xaml.cs | 19 + MainModule/Views/ProtocolStartView.xaml | 85 ++ MainModule/Views/ProtocolStartView.xaml.cs | 28 + Model/Entity/BaseEntity.cs | 27 + Model/Model.csproj | 13 + Model/Result.cs | 180 ++++ MonitorModule.txt | 0 MonitorModule/MonitorModule.cs | 21 + MonitorModule/MonitorModule.csproj | 18 + MonitorModule/ViewModels/MonitorViewModel.cs | 330 ++++++++ MonitorModule/ViewModels/RecordViewModel.cs | 362 ++++++++ MonitorModule/Views/MonitorViewView.xaml | 236 ++++++ MonitorModule/Views/MonitorViewView.xaml.cs | 28 + MonitorModule/Views/RecordView.xaml | 170 ++++ MonitorModule/Views/RecordView.xaml.cs | 28 + ORM/DatabaseConfig.cs | 132 +++ ORM/ORM.csproj | 14 + ORM/SqlSugarContext.cs | 123 +++ ORM/SqlSugarRepository.cs | 396 +++++++++ Service/Implement/BaseService.cs | 132 +++ Service/Interface/IBaseService.cs | 55 ++ Service/Service.csproj | 13 + SettingModule/SettingModule.csproj | 14 + TestingModule/TestingModule.cs | 25 + TestingModule/TestingModule.csproj | 18 + .../ViewModels/CommandTreeViewModel.cs | 590 +++++++++++++ .../Dialogs/ParameterSettingViewModel.cs | 135 +++ TestingModule/ViewModels/LogAreaViewModel.cs | 58 ++ .../ViewModels/ParametersManagerViewModel.cs | 165 ++++ .../ViewModels/SingleStepEditViewModel.cs | 201 +++++ .../ViewModels/StepsManagerViewModel.cs | 237 ++++++ TestingModule/Views/CommandTree.xaml | 79 ++ TestingModule/Views/CommandTree.xaml.cs | 28 + .../Views/Dialogs/ParameterSetting.xaml | 136 +++ .../Views/Dialogs/ParameterSetting.xaml.cs | 37 + TestingModule/Views/LogArea.xaml | 33 + TestingModule/Views/LogArea.xaml.cs | 34 + TestingModule/Views/ParametersManager.xaml | 136 +++ TestingModule/Views/ParametersManager.xaml.cs | 28 + TestingModule/Views/SingleStepEdit.xaml | 371 +++++++++ TestingModule/Views/SingleStepEdit.xaml.cs | 28 + TestingModule/Views/StepsManager.xaml | 244 ++++++ TestingModule/Views/StepsManager.xaml.cs | 31 + UIShare/Behaviors/MouseDoubleClickBehavior.cs | 54 ++ .../TabControlSelectionChangedBehavior.cs | 59 ++ UIShare/Converters/BoolArrayConverter.cs | 40 + .../BooleanToVisibilityConverter.cs | 40 + UIShare/Converters/DeviceNameConverter.cs | 49 ++ UIShare/Converters/EnumValueConverter.cs | 82 ++ UIShare/Converters/EnumValuesConverter.cs | 23 + .../Converters/FilteredParametersConverter.cs | 74 ++ UIShare/Converters/HexConverter.cs | 40 + UIShare/Converters/InverseBooleanConverter.cs | 22 + UIShare/Converters/IsEnumTypeConverter .cs | 36 + UIShare/Converters/LessThanConverter.cs | 27 + .../ParameterCategoryToStringConverter.cs | 41 + .../ParameterCategoryToVisibilityConverter.cs | 41 + .../ParameterTypeToBoolConverter.cs | 31 + .../ParameterValueToStringConverter.cs | 37 + .../Converters/StringToVisibilityConverter.cs | 35 + .../Converters/TimeSpanToStringConverter.cs | 38 + UIShare/GlobalVariable/ConfigService.cs | 85 ++ UIShare/GlobalVariable/GlobalInfo.cs | 37 + UIShare/GlobalVariable/ScopedContext.cs | 42 + UIShare/GlobalVariable/StepRunning.cs | 730 +++++++++++++++++ UIShare/GlobalVariable/SystemConfig.cs | 41 + UIShare/Helpers/PasswordBoxHelper.cs | 52 ++ UIShare/Helpers/WindowDragHelper.cs | 41 + UIShare/PubEvent/AlarmEvent.cs | 12 + UIShare/PubEvent/ChangeCurrentTagEvent.cs | 12 + .../CollectedCANMessageChangedEvent.cs | 12 + UIShare/PubEvent/ConnectionChangeEvent.cs | 12 + UIShare/PubEvent/CurveDataEvent.cs | 13 + UIShare/PubEvent/CurveInteractionEvent.cs | 12 + UIShare/PubEvent/DeletedStepEvent.cs | 12 + UIShare/PubEvent/EditSetpEvent.cs | 12 + UIShare/PubEvent/ExpandViewEvent.cs | 12 + UIShare/PubEvent/LoginSuccessEvent.cs | 12 + UIShare/PubEvent/OverlayEvent.cs | 12 + UIShare/PubEvent/ParamsChangedEvent.cs | 12 + UIShare/PubEvent/RunSingalCompletedEvent.cs | 12 + UIShare/PubEvent/SettingChangedEvent.cs | 12 + UIShare/PubEvent/StartProcessEvent.cs | 12 + UIShare/PubEvent/WaitingEvent.cs | 12 + UIShare/Styles/CommonStyle.xaml | 21 + UIShare/Styles/WindowStyle.xaml | 22 + UIShare/UIShare.csproj | 23 + UIShare/UIViewModel/CanMessageShowModel.cs | 71 ++ UIShare/UIViewModel/CustomPanelItem.cs | 22 + UIShare/UIViewModel/DeviceInfoModel.cs | 44 + UIShare/UIViewModel/InstructionNode.cs | 18 + UIShare/UIViewModel/MethodModel.cs | 40 + UIShare/UIViewModel/ParameterModel.cs | 316 +++++++ UIShare/UIViewModel/ProgramModel.cs | 51 ++ UIShare/UIViewModel/StepModel.cs | 178 ++++ UIShare/UIViewModel/SubProgramItem.cs | 15 + UIShare/ViewModelBase/DialogViewModelBase.cs | 27 + .../ViewModelBase/NavigateViewModelBase.cs | 63 ++ UpdateInfoMoudle/UpdateInfoModule.cs | 19 + UpdateInfoMoudle/UpdateInfoMoudle.csproj | 14 + .../ViewModels/UpdateInfoViewModel.cs | 30 + UpdateInfoMoudle/Views/UpdateInfoView.xaml | 15 + UpdateInfoMoudle/Views/UpdateInfoView.xaml.cs | 28 + 166 files changed, 15996 insertions(+) create mode 100644 ADP.sln create mode 100644 ADP/ADP.csproj create mode 100644 ADP/App.xaml create mode 100644 ADP/App.xaml.cs create mode 100644 ADP/AssemblyInfo.cs create mode 100644 ADP/Resources/Images/error.png create mode 100644 ADP/Resources/Images/info.png create mode 100644 ADP/Resources/Images/warning.png create mode 100644 ADP/ViewModels/Dialogs/MessageBoxViewModel.cs create mode 100644 ADP/ViewModels/ShellViewModel.cs create mode 100644 ADP/Views/Dialogs/MessageBoxView.xaml create mode 100644 ADP/Views/Dialogs/MessageBoxView.xaml.cs create mode 100644 ADP/Views/LoginModuleView.xaml create mode 100644 ADP/Views/LoginModuleView.xaml.cs create mode 100644 ADP/Views/ShellView.xaml create mode 100644 ADP/Views/ShellView.xaml.cs create mode 100644 Command/Command.csproj create mode 100644 Command/CommandApplication.cs create mode 100644 Command/CommandArray.cs create mode 100644 Command/CommandMath.cs create mode 100644 Command/CommandRadixChange.cs create mode 100644 Command/CommandStringProcessing.cs create mode 100644 Command/CommandSystem.cs create mode 100644 Command/CommandTime.cs create mode 100644 Command/Delay.cs create mode 100644 Common/Attributes/ADPCommandAttribute.cs create mode 100644 Common/Common.csproj create mode 100644 Common/MiniDump.cs create mode 100644 Common/Tool/ExpressionEvaluator.cs create mode 100644 DeviceCommand/Base/IBaseInterface.cs create mode 100644 DeviceCommand/Base/IModbusDevice.cs create mode 100644 DeviceCommand/Base/ISerialPort.cs create mode 100644 DeviceCommand/Base/ITcp.cs create mode 100644 DeviceCommand/Base/ModbusRtu.cs create mode 100644 DeviceCommand/Base/ModbusTcp.cs create mode 100644 DeviceCommand/Base/Serial_Port.cs create mode 100644 DeviceCommand/Base/TCP.cs create mode 100644 DeviceCommand/DeviceCommand.csproj create mode 100644 DeviceCommand/Devices/IT7800E.cs create mode 100644 DeviceCommand/Devices/N36200.cs create mode 100644 DeviceCommand/Devices/N36600.cs create mode 100644 DeviceCommand/Devices/N69200.cs create mode 100644 DeviceCommand/Devices/SDS2000X_HD.cs create mode 100644 DeviceCommand/Devices/SPAW7000.cs create mode 100644 Logger/Logger.csproj create mode 100644 Logger/LoggerHelper.cs create mode 100644 Logger/Nlog.config create mode 100644 LoginModule/LoginModule.cs create mode 100644 LoginModule/LoginModule.csproj create mode 100644 LoginModule/ViewModels/LoginViewModel.cs create mode 100644 LoginModule/ViewModels/RegisterViewModel.cs create mode 100644 LoginModule/Views/LoginView.xaml create mode 100644 LoginModule/Views/LoginView.xaml.cs create mode 100644 LoginModule/Views/RegisterView.xaml create mode 100644 LoginModule/Views/RegisterView.xaml.cs create mode 100644 MainModule/MainModule.cs create mode 100644 MainModule/MainModule.csproj create mode 100644 MainModule/ViewModels/AutomatedTestingViewModel.cs create mode 100644 MainModule/ViewModels/MainViewModel.cs create mode 100644 MainModule/ViewModels/ProtocolStartViewModel.cs create mode 100644 MainModule/Views/AutomatedTestingView.xaml create mode 100644 MainModule/Views/AutomatedTestingView.xaml.cs create mode 100644 MainModule/Views/MainView.xaml create mode 100644 MainModule/Views/MainView.xaml.cs create mode 100644 MainModule/Views/ProtocolStartView.xaml create mode 100644 MainModule/Views/ProtocolStartView.xaml.cs create mode 100644 Model/Entity/BaseEntity.cs create mode 100644 Model/Model.csproj create mode 100644 Model/Result.cs create mode 100644 MonitorModule.txt create mode 100644 MonitorModule/MonitorModule.cs create mode 100644 MonitorModule/MonitorModule.csproj create mode 100644 MonitorModule/ViewModels/MonitorViewModel.cs create mode 100644 MonitorModule/ViewModels/RecordViewModel.cs create mode 100644 MonitorModule/Views/MonitorViewView.xaml create mode 100644 MonitorModule/Views/MonitorViewView.xaml.cs create mode 100644 MonitorModule/Views/RecordView.xaml create mode 100644 MonitorModule/Views/RecordView.xaml.cs create mode 100644 ORM/DatabaseConfig.cs create mode 100644 ORM/ORM.csproj create mode 100644 ORM/SqlSugarContext.cs create mode 100644 ORM/SqlSugarRepository.cs create mode 100644 Service/Implement/BaseService.cs create mode 100644 Service/Interface/IBaseService.cs create mode 100644 Service/Service.csproj create mode 100644 SettingModule/SettingModule.csproj create mode 100644 TestingModule/TestingModule.cs create mode 100644 TestingModule/TestingModule.csproj create mode 100644 TestingModule/ViewModels/CommandTreeViewModel.cs create mode 100644 TestingModule/ViewModels/Dialogs/ParameterSettingViewModel.cs create mode 100644 TestingModule/ViewModels/LogAreaViewModel.cs create mode 100644 TestingModule/ViewModels/ParametersManagerViewModel.cs create mode 100644 TestingModule/ViewModels/SingleStepEditViewModel.cs create mode 100644 TestingModule/ViewModels/StepsManagerViewModel.cs create mode 100644 TestingModule/Views/CommandTree.xaml create mode 100644 TestingModule/Views/CommandTree.xaml.cs create mode 100644 TestingModule/Views/Dialogs/ParameterSetting.xaml create mode 100644 TestingModule/Views/Dialogs/ParameterSetting.xaml.cs create mode 100644 TestingModule/Views/LogArea.xaml create mode 100644 TestingModule/Views/LogArea.xaml.cs create mode 100644 TestingModule/Views/ParametersManager.xaml create mode 100644 TestingModule/Views/ParametersManager.xaml.cs create mode 100644 TestingModule/Views/SingleStepEdit.xaml create mode 100644 TestingModule/Views/SingleStepEdit.xaml.cs create mode 100644 TestingModule/Views/StepsManager.xaml create mode 100644 TestingModule/Views/StepsManager.xaml.cs create mode 100644 UIShare/Behaviors/MouseDoubleClickBehavior.cs create mode 100644 UIShare/Behaviors/TabControlSelectionChangedBehavior.cs create mode 100644 UIShare/Converters/BoolArrayConverter.cs create mode 100644 UIShare/Converters/BooleanToVisibilityConverter.cs create mode 100644 UIShare/Converters/DeviceNameConverter.cs create mode 100644 UIShare/Converters/EnumValueConverter.cs create mode 100644 UIShare/Converters/EnumValuesConverter.cs create mode 100644 UIShare/Converters/FilteredParametersConverter.cs create mode 100644 UIShare/Converters/HexConverter.cs create mode 100644 UIShare/Converters/InverseBooleanConverter.cs create mode 100644 UIShare/Converters/IsEnumTypeConverter .cs create mode 100644 UIShare/Converters/LessThanConverter.cs create mode 100644 UIShare/Converters/ParameterCategoryToStringConverter.cs create mode 100644 UIShare/Converters/ParameterCategoryToVisibilityConverter.cs create mode 100644 UIShare/Converters/ParameterTypeToBoolConverter.cs create mode 100644 UIShare/Converters/ParameterValueToStringConverter.cs create mode 100644 UIShare/Converters/StringToVisibilityConverter.cs create mode 100644 UIShare/Converters/TimeSpanToStringConverter.cs create mode 100644 UIShare/GlobalVariable/ConfigService.cs create mode 100644 UIShare/GlobalVariable/GlobalInfo.cs create mode 100644 UIShare/GlobalVariable/ScopedContext.cs create mode 100644 UIShare/GlobalVariable/StepRunning.cs create mode 100644 UIShare/GlobalVariable/SystemConfig.cs create mode 100644 UIShare/Helpers/PasswordBoxHelper.cs create mode 100644 UIShare/Helpers/WindowDragHelper.cs create mode 100644 UIShare/PubEvent/AlarmEvent.cs create mode 100644 UIShare/PubEvent/ChangeCurrentTagEvent.cs create mode 100644 UIShare/PubEvent/CollectedCANMessageChangedEvent.cs create mode 100644 UIShare/PubEvent/ConnectionChangeEvent.cs create mode 100644 UIShare/PubEvent/CurveDataEvent.cs create mode 100644 UIShare/PubEvent/CurveInteractionEvent.cs create mode 100644 UIShare/PubEvent/DeletedStepEvent.cs create mode 100644 UIShare/PubEvent/EditSetpEvent.cs create mode 100644 UIShare/PubEvent/ExpandViewEvent.cs create mode 100644 UIShare/PubEvent/LoginSuccessEvent.cs create mode 100644 UIShare/PubEvent/OverlayEvent.cs create mode 100644 UIShare/PubEvent/ParamsChangedEvent.cs create mode 100644 UIShare/PubEvent/RunSingalCompletedEvent.cs create mode 100644 UIShare/PubEvent/SettingChangedEvent.cs create mode 100644 UIShare/PubEvent/StartProcessEvent.cs create mode 100644 UIShare/PubEvent/WaitingEvent.cs create mode 100644 UIShare/Styles/CommonStyle.xaml create mode 100644 UIShare/Styles/WindowStyle.xaml create mode 100644 UIShare/UIShare.csproj create mode 100644 UIShare/UIViewModel/CanMessageShowModel.cs create mode 100644 UIShare/UIViewModel/CustomPanelItem.cs create mode 100644 UIShare/UIViewModel/DeviceInfoModel.cs create mode 100644 UIShare/UIViewModel/InstructionNode.cs create mode 100644 UIShare/UIViewModel/MethodModel.cs create mode 100644 UIShare/UIViewModel/ParameterModel.cs create mode 100644 UIShare/UIViewModel/ProgramModel.cs create mode 100644 UIShare/UIViewModel/StepModel.cs create mode 100644 UIShare/UIViewModel/SubProgramItem.cs create mode 100644 UIShare/ViewModelBase/DialogViewModelBase.cs create mode 100644 UIShare/ViewModelBase/NavigateViewModelBase.cs create mode 100644 UpdateInfoMoudle/UpdateInfoModule.cs create mode 100644 UpdateInfoMoudle/UpdateInfoMoudle.csproj create mode 100644 UpdateInfoMoudle/ViewModels/UpdateInfoViewModel.cs create mode 100644 UpdateInfoMoudle/Views/UpdateInfoView.xaml create mode 100644 UpdateInfoMoudle/Views/UpdateInfoView.xaml.cs diff --git a/ADP.sln b/ADP.sln new file mode 100644 index 0000000..9850756 --- /dev/null +++ b/ADP.sln @@ -0,0 +1,116 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35527.113 d17.12 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ADP", "ADP\ADP.csproj", "{8B48B0CD-55F9-4623-9A10-BFE25B21EBD6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Common\Common.csproj", "{520D7017-1A2F-427D-B8A4-54E3D0575EFD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Model", "Model\Model.csproj", "{42623437-0ADD-4149-B33F-DB30E7B13D69}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Service", "Service\Service.csproj", "{F9A522A1-869E-462E-9782-57ED3DE5566E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ORM", "ORM\ORM.csproj", "{19ED286B-4E83-4EA0-9BD4-37D2AB796F22}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logger", "Logger\Logger.csproj", "{84F26384-D1DC-4175-98A3-A64A4FB961D2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceCommand", "DeviceCommand\DeviceCommand.csproj", "{1F4E6A3D-B273-4F76-8D59-37F7C8E858BB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Command", "Command\Command.csproj", "{FC353140-A722-4E8E-97BA-FB866E8188A9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UIShare", "UIShare\UIShare.csproj", "{53198B54-BDF9-446D-B1FD-EC0A52D794E5}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{1E92D601-68FC-45F6-8251-BD0F39226E5B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoginModule", "LoginModule\LoginModule.csproj", "{DF1DE1F7-B570-41D7-A2B5-AB2CDCD4BD72}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SettingModule", "SettingModule\SettingModule.csproj", "{51384EB4-1940-44FB-8E18-56F7201642CD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestingModule", "TestingModule\TestingModule.csproj", "{BD74E7F3-BBB5-4375-A37E-F24E8507A436}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MainModule", "MainModule\MainModule.csproj", "{DFC084AE-FE45-4492-A2DA-EAE2941F26E9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UpdateInfoMoudle", "UpdateInfoMoudle\UpdateInfoMoudle.csproj", "{3456C3AC-E7FF-45F8-A68F-BEED9969812C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonitorModule", "MonitorModule\MonitorModule.csproj", "{850A5BE8-0BC1-4D6A-8351-DD812C9C4427}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8B48B0CD-55F9-4623-9A10-BFE25B21EBD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8B48B0CD-55F9-4623-9A10-BFE25B21EBD6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8B48B0CD-55F9-4623-9A10-BFE25B21EBD6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8B48B0CD-55F9-4623-9A10-BFE25B21EBD6}.Release|Any CPU.Build.0 = Release|Any CPU + {520D7017-1A2F-427D-B8A4-54E3D0575EFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {520D7017-1A2F-427D-B8A4-54E3D0575EFD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {520D7017-1A2F-427D-B8A4-54E3D0575EFD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {520D7017-1A2F-427D-B8A4-54E3D0575EFD}.Release|Any CPU.Build.0 = Release|Any CPU + {42623437-0ADD-4149-B33F-DB30E7B13D69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42623437-0ADD-4149-B33F-DB30E7B13D69}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42623437-0ADD-4149-B33F-DB30E7B13D69}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42623437-0ADD-4149-B33F-DB30E7B13D69}.Release|Any CPU.Build.0 = Release|Any CPU + {F9A522A1-869E-462E-9782-57ED3DE5566E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F9A522A1-869E-462E-9782-57ED3DE5566E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F9A522A1-869E-462E-9782-57ED3DE5566E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F9A522A1-869E-462E-9782-57ED3DE5566E}.Release|Any CPU.Build.0 = Release|Any CPU + {19ED286B-4E83-4EA0-9BD4-37D2AB796F22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {19ED286B-4E83-4EA0-9BD4-37D2AB796F22}.Debug|Any CPU.Build.0 = Debug|Any CPU + {19ED286B-4E83-4EA0-9BD4-37D2AB796F22}.Release|Any CPU.ActiveCfg = Release|Any CPU + {19ED286B-4E83-4EA0-9BD4-37D2AB796F22}.Release|Any CPU.Build.0 = Release|Any CPU + {84F26384-D1DC-4175-98A3-A64A4FB961D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {84F26384-D1DC-4175-98A3-A64A4FB961D2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {84F26384-D1DC-4175-98A3-A64A4FB961D2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {84F26384-D1DC-4175-98A3-A64A4FB961D2}.Release|Any CPU.Build.0 = Release|Any CPU + {1F4E6A3D-B273-4F76-8D59-37F7C8E858BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F4E6A3D-B273-4F76-8D59-37F7C8E858BB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F4E6A3D-B273-4F76-8D59-37F7C8E858BB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F4E6A3D-B273-4F76-8D59-37F7C8E858BB}.Release|Any CPU.Build.0 = Release|Any CPU + {FC353140-A722-4E8E-97BA-FB866E8188A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC353140-A722-4E8E-97BA-FB866E8188A9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC353140-A722-4E8E-97BA-FB866E8188A9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC353140-A722-4E8E-97BA-FB866E8188A9}.Release|Any CPU.Build.0 = Release|Any CPU + {53198B54-BDF9-446D-B1FD-EC0A52D794E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {53198B54-BDF9-446D-B1FD-EC0A52D794E5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {53198B54-BDF9-446D-B1FD-EC0A52D794E5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {53198B54-BDF9-446D-B1FD-EC0A52D794E5}.Release|Any CPU.Build.0 = Release|Any CPU + {DF1DE1F7-B570-41D7-A2B5-AB2CDCD4BD72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF1DE1F7-B570-41D7-A2B5-AB2CDCD4BD72}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF1DE1F7-B570-41D7-A2B5-AB2CDCD4BD72}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF1DE1F7-B570-41D7-A2B5-AB2CDCD4BD72}.Release|Any CPU.Build.0 = Release|Any CPU + {51384EB4-1940-44FB-8E18-56F7201642CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {51384EB4-1940-44FB-8E18-56F7201642CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {51384EB4-1940-44FB-8E18-56F7201642CD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {51384EB4-1940-44FB-8E18-56F7201642CD}.Release|Any CPU.Build.0 = Release|Any CPU + {BD74E7F3-BBB5-4375-A37E-F24E8507A436}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BD74E7F3-BBB5-4375-A37E-F24E8507A436}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BD74E7F3-BBB5-4375-A37E-F24E8507A436}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BD74E7F3-BBB5-4375-A37E-F24E8507A436}.Release|Any CPU.Build.0 = Release|Any CPU + {DFC084AE-FE45-4492-A2DA-EAE2941F26E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DFC084AE-FE45-4492-A2DA-EAE2941F26E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DFC084AE-FE45-4492-A2DA-EAE2941F26E9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DFC084AE-FE45-4492-A2DA-EAE2941F26E9}.Release|Any CPU.Build.0 = Release|Any CPU + {3456C3AC-E7FF-45F8-A68F-BEED9969812C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3456C3AC-E7FF-45F8-A68F-BEED9969812C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3456C3AC-E7FF-45F8-A68F-BEED9969812C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3456C3AC-E7FF-45F8-A68F-BEED9969812C}.Release|Any CPU.Build.0 = Release|Any CPU + {850A5BE8-0BC1-4D6A-8351-DD812C9C4427}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {850A5BE8-0BC1-4D6A-8351-DD812C9C4427}.Debug|Any CPU.Build.0 = Debug|Any CPU + {850A5BE8-0BC1-4D6A-8351-DD812C9C4427}.Release|Any CPU.ActiveCfg = Release|Any CPU + {850A5BE8-0BC1-4D6A-8351-DD812C9C4427}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {DF1DE1F7-B570-41D7-A2B5-AB2CDCD4BD72} = {1E92D601-68FC-45F6-8251-BD0F39226E5B} + {51384EB4-1940-44FB-8E18-56F7201642CD} = {1E92D601-68FC-45F6-8251-BD0F39226E5B} + {BD74E7F3-BBB5-4375-A37E-F24E8507A436} = {1E92D601-68FC-45F6-8251-BD0F39226E5B} + {DFC084AE-FE45-4492-A2DA-EAE2941F26E9} = {1E92D601-68FC-45F6-8251-BD0F39226E5B} + {3456C3AC-E7FF-45F8-A68F-BEED9969812C} = {1E92D601-68FC-45F6-8251-BD0F39226E5B} + {850A5BE8-0BC1-4D6A-8351-DD812C9C4427} = {1E92D601-68FC-45F6-8251-BD0F39226E5B} + EndGlobalSection +EndGlobal diff --git a/ADP/ADP.csproj b/ADP/ADP.csproj new file mode 100644 index 0000000..e915270 --- /dev/null +++ b/ADP/ADP.csproj @@ -0,0 +1,24 @@ + + + + WinExe + net8.0-windows + enable + enable + true + + + + + + + + + + + + + + + + diff --git a/ADP/App.xaml b/ADP/App.xaml new file mode 100644 index 0000000..ec1b892 --- /dev/null +++ b/ADP/App.xaml @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/ADP/App.xaml.cs b/ADP/App.xaml.cs new file mode 100644 index 0000000..f5a0595 --- /dev/null +++ b/ADP/App.xaml.cs @@ -0,0 +1,88 @@ +using Common; +using ADP.ViewModels; +using ADP.ViewModels.Dialogs; +using ADP.Views; +using ADP.Views; +using ADP.Views.Dialogs; +using Logger; +using Notifications.Wpf.Core; +using ORM; +using Service.Implement; +using Service.Interface; +using System.Configuration; +using System.Data; +using System.Reflection; +using System.Windows; +using UIShare.PubEvent; +using static System.Runtime.InteropServices.JavaScript.JSType; +using UIShare.GlobalVariable; +using UIShare; + +namespace ADP +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : PrismApplication + { + protected override Window CreateShell() + { + //UI线程未捕获异常处理事件 + this.DispatcherUnhandledException += OnDispatcherUnhandledException; + //Task线程内未捕获异常处理事件 + TaskScheduler.UnobservedTaskException += OnUnobservedTaskException; + ////多线程异常 + AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; + return Container.Resolve(); + } + private void OnDispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) + { + LoggerHelper.Error(e.Exception.Message, e.Exception.StackTrace); + } + + private void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) + { + LoggerHelper.Error(e.Exception.Message, e.Exception.StackTrace); + } + + private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e) + { + //记录dump文件 + Exception ex = e.ExceptionObject as Exception; + MiniDump.TryDump($"dumps\\Error_{DateTime.Now:yyyy-MM-dd HH-mm-ss-ms}.dmp", MiniDump.Option.WithFullMemory, ex); + } + protected override void OnInitialized() + { + //初始化数据库 + //DatabaseConfig.SetTenant(10001); + //DatabaseConfig.InitMySql("127.0.0.1",3306,"ADP","root","123456"); + //DatabaseConfig.CreateDatabaseAndCheckConnection(createDatabase: true, checkConnection: true); + //SqlSugarContext.InitDatabase(); + //显示登录窗口 + var login = Container.Resolve(); + var re = Container.Resolve(); + RegionManager.SetRegionManager(login, re); + RegionManager.SetRegionManager(Application.Current.MainWindow, re); + login.Show(); + } + protected override void RegisterTypes(IContainerRegistry containerRegistry) + { + //注册弹窗 + containerRegistry.RegisterDialog("MessageBox"); + // 注册通知管理器 + INotificationManager NotificationManager = new NotificationManager(); + containerRegistry.RegisterInstance(NotificationManager); + //注册全局变量 + containerRegistry.RegisterScoped(); + containerRegistry.RegisterScoped(); + containerRegistry.RegisterScoped(); + containerRegistry.RegisterSingleton(); + } + //指定模块加载方式(需要手动将模块生成的dll放入Modules文件夹中) + protected override IModuleCatalog CreateModuleCatalog() + { + //指定模块加载方式为从文件夹中以反射发现并加载module(推荐用法) + return new DirectoryModuleCatalog() { ModulePath = @".\Modules" }; + } + } +} diff --git a/ADP/AssemblyInfo.cs b/ADP/AssemblyInfo.cs new file mode 100644 index 0000000..b0ec827 --- /dev/null +++ b/ADP/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/ADP/Resources/Images/error.png b/ADP/Resources/Images/error.png new file mode 100644 index 0000000000000000000000000000000000000000..de079d0b7e0670dac8c52f5ace3adf9772b4b381 GIT binary patch literal 7146 zcmV_rb?+X$%CG(>Jf)qiv zOE4)CpA9_hZUB;UqkpqY;B)b=Ksea&`)_xj8Q#DB`*R)s`~K}GhA+SW_Q=3<7i=8H zccFM!<%8qw71sic0upHuVDJ-y!VP{7bQ|8kEwj_<8R5}}Ec=u(7;~T1p)}@>!DQKi zf#)t{jKlaYoK96fIDSFi2+u5sK-sbZ4(k%BS-a0h0SOoa9DoQ1KHWJuF-RQ*&u;K@ zpxY3Tzh{zy^; zatj9zfGH84EilkBxTi0H1Qd`SA+>?Pz^XI^%$*ej(bJN%%M~!tGHBC_hEZV7gw%dB zBOvW!(l~FRc>k6@!}H2D#8jcaXqXxpYDmc7Fg4>XrQbau7!c>C6Sagf96V-ce+3^1 zG##m32+Pje-`U*V0^-2HfY`dG=Kk$dv(y)x8?6LQp8v)zxr@3Z)5%P!Q}ycIFkq zIGVD2!c^-=8}j_Nd9(xlSpb{{6ziuH?>0HHu_-_n1mkF7Fyw0IQSq_*6 z6l(8vNg}*9$pb>I!TnI&hZHV`&Xg4t@ENEfQUIc}6|IY%z(5POqQp40rk9=d+Q15Z z(tz+uMXeEY`v*pd1*3riJ_D|8@@0X67CbY>;L5{~hGr2ceJ zaQ5hi+9FcENn~R|sZB8lODrd!edIt%LKqj7ObQTyQCM^8bb7pKN9fxl2E?t?7NMDJ zDV>AGIr$ji4-wkeHoKCV?Mem^z;VEn=sAix#kGk%zp-F!*o2xoun7xi=kO4@i3SQD zZZwUj)@`ZuNdQ8D0*>ig!-2uIiR1UdHHf7;Acb<;pkPY(eQY&+N z3)&KDvaZ_XS{)#EmTH1Y1=amvoo~$Vqto=YfCRX`1={3xirIyJvnJO9)%7Z3Re%tV z3A0qzoZ_sDmjpC&6bVq9L@>LGwfQBh00jPqb;d*C;FHU0jonV!Mj7K#G9f6|>(?jWM)4`53xB>+ODFzBNc?9$14Oe3AQleK zbq?bE0ul^|ZW)=EtzIg$9149V)G~#@n_8I2Jf2d|GL@q|C4bI65HK@9_DBrm}qEOoopzH175# zz%k(oN!w|UCZMQ*c>5iILl*&~_2uR=3V|fHKN1ZPZ@)v~*xZ1gr9pXK{SJySb7-BY z{zxt?cQl%(-(f%OKbomV*u4bkw4d^vTjj+cyo%|ML;=Lt@4$Tbhu!zQ3P3CYLV_`K zD^36(7!T}_k5jxVVlp5!t&GQd2UOrkG+3u|Uv-6E7?J?S%+YiD*Z#csP@a-|uK|&y zYQ+Jm@+sX?XRKPH(^F3gaQYYPYvM1{MqF->pR}oQ5+HWg3hGSQPW3y4g3@UH67cj# ziu`o)TClz>IT#R_R`#Qiml^iGhYhKIN1t%?x>y3%)XH{`c@?{NdojoJia(P*kKO> zg!9bO*0oJ5X&Q~E5|BuseDg-GMT`k@*u4R%m-0n31DHj4fI8GET}f|i9B&C=Y}s$| z7bXmH*u4RPKXE6lkf~=Jq|r5=O2A(NdCqBH(JDK~uCK$M`_!n`9-<^%)FI)b$Qnr{ z5GVnTF^n~CMG@tI$tAC8UoSvNpr{sH5d8L&WQ(F}B$dDv33xf|SyuRH5iub3qM)c9 zaGu0Oi!vOKq%n3b0gO4R!%ko1FR8f1fWYAvsV2lz$6P9XjTcPVC+dP7qJ!*0Rl2bDhcsad&KT_%}*?W04*}QTrmoWT{RbO zNCd&EJ;dFq5$BKq#-NWD8Fd>*0gH$(huYsp{PO;7iSW!1e!Vs)=jg_D=1F;Q zDj%{)Rvx}uWUMC!0ok(lVB50O>AAJ1tl&97zuzMyP9AVz-9m7(^6=3jZ1)f(gMj2& z_OGGD%{{JrB=mKmcv2pq8GAqIMM(H0ZQ8f-6AqFwc`!~YV(`%-=<7lMdJvG-(_~OK zn1hzueS`Tdbu&Mm28F_Uu2WSxQZp)Cck0o%}aex(QdragX#^h1F9sYFc9S^;LNS_!!`(Rch+p-jGZex8* zW9EZ|1zah3!BN$*s1E7;ms&PMW4#v;I;tAQ0evZ%(Znr?UJ{>uoZp6dVS;_rwCyByDbm!cjj6le84@FckGO`nJ;#c?Afk zKrzxq_NLutjL0dz_&Iq9MI^j-`;aC{NJ8PDqnq*%sp4Vy#5T11u;;wf%z6cg`SnW# z2cIfE1toKN%aF7?VG0~u`1buW%S4t=mznhn5Dfg$H3AAB^UTTFq^$R4*g7P8hr{-F z^|4%gI`j|L!7DTC6(9x+CqtRIdscP1H;Es&3W>r|4NQzx>6!@5tTPMh3=j&m%5wNW zv`mUqeuItL8YJw?EXzI(HoEI(Sw3ySth#KP)XYME^Y-9 zg`?G}?ljLuvv8MwNbb_}n3bh$bK-^<2UCY+0XR5RQVgO+!%IKp3=n9LS58WoB& zbz>vfQiVj}=pTS~%))%^kI~$rAJTBp07Bt5Q3g)7=znxIH#JDuf9i8|_2kPn4?mzoy(aQQ z;A%81G=Q`(_GXV8)|SI@Pp`E?vOPHF9aVPh3jGjQlNUe)-(0d6|HBEVeOwzP3P-zl z#?< z)SEXlKcxN`ZUu<+^Ts=K(EGaLT97CleLdoJT?7cg_y=>v(a_GaZ+KxSSSM#F!jA4O z4T-`rad6ei>oGhEiJR56fRx5GD~Cq^#1dEv5`{zDC9;STSE075EFj3WKp1>w#%yi} zuZi$CHzW#2gigB1BFnN-4T2<=2?q$?>nl_Ob3vkTg!agAI_YdIXLob{8_uq}yQeKC zRg3-p$A(1V@RPq+Y~hv@=BXNlQiv2{L85R(>X50~{Q6bof;Et;80q|R6??b;r$VA| zEHZQ?K8US|Y7i@ixc{a=0tZq*CL6aq!gIq+mJc9e>cVTZos^-FcuSz%=4Z&gdb`$Usqb$v1Oajp$Q8>m#9reBf5>=eTnN5a7;TRUM&t1uWEaWEzWKkszn0(Bo-X5c!k7CV$xAGo>hZPlJR)r*l@TK6%r?j zd5^ir7cq#4o42}Y(L8xB`(g~Ulhe*9d#6F=;8qubc! zlC5Ko9fIM&ItK>)$Fck`?<39Kf8W3L-vxY`k-k5d0H)+sgPe1)RxF1is zbZ@-)=h$chx(ZL;jrk2Nh-n3;pC{0 zI7xUMvrOWHG|Iwi5N0o($wJ&+0kT#&&=2;dkXSK@{;@A55i?_+O#RmYBAv;~e*LRPg+wSRp^MP|sgD;xjA_)08Tu0zGL%IM_fmnw$?~d@xJs=2 zDD^+M2g^030mM2bc=EX$14sHH5<6}ni*?^h4Gt$wg~Umsc&vW9000Yr6AF>rV*r^N(38nB7ut+L%3+e?zm+}k{YkTd`57`lcN@80i;UT>2lB;VVF-eu!9Q zv6PjDt;6BOzIRBZWA)Pip!{9|0u}RQc3@`h1rByH!(Jg_KcoFJ^Xl8rvl%R;YwgA2 zyE;hCtW;OkKNs%~TbQk?yGwkS0tsa%E@(v&rL$LE<`tXqN`vqs%&b==+3T18u&SP- z;DY=oXw?{>=eQK%((gkdN%dg}^Xjfg#%GsLdH(OS3-y~$KwxGarDm2%@IzD(nrzCi z77}H#1vp%Df*`@e(4|l1SesX(>SD|{8x-3KIsst>45Ch=c&{Cxn$nxil5Op;nz<3p zQ8nh}e*h#RJN~7F>z_%-W?7aW`fA$?NPELpU*h`ucK2p&6pp&zj29%3g-e&h5vG4( zgnO@s#(FOxP&raF%cN$XmT%tlJ~5ik$AH+9S=F@b;zSAwWaj;)+80GF`WAks=wlhs zG7omF_tdTz5XKh^1*C#lKf9gFd=(8oI=|J98UaWkWe0wS*MNe@>tUmW3fR0 z%AE7E{LfI`2LVAsenx}=r>$4VX=+-;4vIX#p#xqw$7;+n&u{Tg@ zrwX))%;dzQu+b&xK2op$&L);=eoj&2HFO|Sz z5`h0J9&;XJxqCNnSF3!q2+|im`(O}XUn+rM2|#-sq^su8GJ^|)mz5A}5GM2?FUzuf zRJ>q14Jv^-CE#PRU<`})7`pm;0phDg;INBqLRT3*sswx`P&?+L2XU=EywV`cV@vFqv+4EN<*7J$141o=brC`$D#S!jj?Hz3T}?q4ZOsj4b!HOQnIR02^XU?+jHOlqARxz{YD;2=O) zP$}6m&WH$^F@HfFN*jX|`cet>O8{f3z$B1(TDQz|*-ns-B7*^;x+;y4np$>kgbnMc zgI-bzcuBy=w8F!g)4Eb?=T$v|0kJvdIltUNQ#-j-+-K$sybM4ON2!ch2p`b(JS_M+{)MRtsM zIOY6~0glC3Vv7cdtq^CmVEfr~!9M_Xr%t;-P&4I%mRolz|$W=n!-X- zygT4rUkgZJZAAbM|JhCWy!bBO zL}#}kw5`ARxejO!UpNfl_`tC>w|r&?gbiRr&>%v>x8eYwL?IDNE4_#`rF}lv;abSC zgfSImrQGG&cU%Mz7LEo{CEoDdClC_5n(C8C<6c_=qvf+t*|(z+FCr`n2a8?|5Ef=3 z8Th#?#_0-}GQRur{;jVA*OirCTY&_)w~Br0Ta~5ZVEq;agavYYd>H@<(iVgS4xQ*L zUJzejE0X~Gj&K;Cy;XG^IA-vGUBs-ai?oNu0b!|AAfZD?+m-ofNJj#-GyWV5$H))e z%q5Ad0K&#tNcam+eB&254Sa8(M1JWs_LU@H{f#UWf5}3-n%1nk5*)0@)c|4PP?^qg z_n87_Qo|=vZF1?#yIJR-GpR!S4YtU_ar}N^R`Jeq;i`bJnKdMw0``Z5i;DboqAn^@ zl&cC;Oe!>l56)ROA0ltB*VO@GZ6`xw%)M$8O7i-_X^oKejk@-$frIr~1t6@5g9LNq z{3;<{@(sF=%U5EN<8$%^lPE!sxUEWAKJf=cITq~u75rNbAe03K!|ySI&FZyPo@wh) zIq+CBVQ1CX^f0popfwKE;CQ%seZZ+*Eu?*zb60M=ET8jL0m6zC5`J9^9W=ZT@k@}* zyns@?({&Rw&mX`jIGYHq!M#+$a^@PxDt?>3Lrkj!#MKd?JXuJfcHOmrBh9WyPBXGI z1JY}f4hc|})X)GU+?=ZNt^AEEPUBY9=>NZ3Kv-o!a^z>+CzxE!ra<~Tbf$trX-o)h zFklh|n^UX;(m&Mx##N_rS(l-(YCu?VLK4B`Vpf-+==uqzA_y_TU~#kz)@o9*e(kV& zKwM=3N(_^W*(oTLf*`~O14SMI#|`@u=SjSh0ff?oVC_dNVKQ-+E7XB!RyH6XYkF}8 zhBmdmxbuGwirV>phAA}3l&ZyfgseZ-Ce`GAtMgps5DSn+D(ebyCKrY7u1!2PvtHJr z|NXbSiTNd$+<^gKt%4$8$8Be$G09@V`1<4T&igCBm?R*SDYZ!r$_>Bvh1Vj4%KXZ* zPt+3j~0#{fgj)y3lQ9gisAQ&{K*asa^lD(7wApWvtC(W!;+La;<4KR30+b1=lVW6gA z;P!7jfNA0s?+TnVT|1xNbH;6jgU##x+rODFK|nc`4^hK_0ejES%k*X#=?1km!Qh$( zWgaQpRb{hDK%9ixX$ykligiZgnstKWx0o=1LKCW+e3rfZQKi7S+Yg;kQv#xAzSuPmMkHWb*izX zvLw5*gvK64w)oBW_j~>RcfEJ8l-xjq8pv#$ogl4zN6i5i%V}&W8~0j>?qsvx63TuLWSyTJkV`lAY_? zBb1gCpeU^X>>_c%1YL5z5|D^=ri&&E1Du>rS84bGLmu3JyO<>(IAbo?F@1b1ORe+x*5(f(=9;jx(+rabJ!7q7ll8U_BN){yI~o<_-q9K*W?T5v-6u?P zIy!LU`R<>8#Hmi4Z&XM2J$<2TGl@u;;c0Dv?hd{>o0S0ZpeT4@htF8}6AUTn5Q+Zv z0VEgKh;t5C(X>wmhZCb@Sj+)@ZqIDS$|diIm#Lt{O2~7?ca)X4a9_xqXzqJ=DnGN% z)r(OG*q1DPPF@@gHM37`0}9rvCBpO}4}}RnI?Rq@B_BMzpjKdM55MB*2UgQyBqs6| zB&`KU?a^5Q@T z{svS+8JO!LxtN0d&_svt6zZg|PWtacvh#+`OB+t71pYNgKU2!?1-JqR#nSCAb6>o# z3eFk()}79w8@K5FiLAknB3qx=g+|B76@J;D04a`%h7F!p_~J+WQQk`H@2?+g1QTbI zV=V8Ko-eEFo$KyaWUehkuAXOXa<2!Via9p#IVK{*O55M)t^Z?F81+&u+m$c^Npv+@ z;SJIDoIXCsr=JELBw@OOPik(yQ@zzBRX}CD)A)wo)0qobkl||l&LkPxTw-ZG!0*2} zB_FBio!II7$Jsw@`No)4t#0x?NtBY7JtXGp5{-gw`u1hxnp?` zt|`SfJ5R$dpmA5YrX+O0>9^fU=a15|L4|1^t~ufEHpOGj@Aj47_fJla?JvJ#(k8f4 zSxT``wSuWI`MP}9EM;Py>`LUzKaVuHHk7PWG?b*E!gaDzFZrW8NqUn7uDkH{};>K%OH^Z zgnq;NZ_ByEVW!yx3Ak|7ul{eP=Dw|#os@ijABuXbS@Y$lfu&=F6XDan9>Mw`i4fa2 z-$a35QMFIFFN1q66c#JDjJ2%w2~kh`_piQ_=yiL>L+=(NKkn@8I%f|p|GkFBYOx!n zD}4WTg+k8Hl4x=8hy#Vfw5Cm5=Ra%h7X{Mgu(O{~h(I@6e~-%F zZaEI|m|G_I+Seluf2n88vrG2%Dz;fd@0Si*hV)Ace;^`lePU8|x-ONR#xmcIL9Yg( zZNaYi5-wmBQ3?iXDK;mhdX|Vn(x^^N(~h2}OW0keXBe4kGP*-ze$OQ#e`ve4Uw|Ve z%u#V=V{i3yq7C+h{27A({4u}ioT8_7dC}OxqPA8iZR>XR5S*ALi^QXb6YVEOi&)>L zH?FSCo%X`jYfQsli@J+-?79L&K@-}G)2(|?Z+`v?$91V6K{H>94G8qt@qz}Z-Vp57 zXxl|<68)kQYfL#?8j>Ko`cd}mMXuH#+;wbbAaMKI81wM%y@gn<9W9<|Hhdq!xk0@; zZDQVu-NS{7Wj6zZadrXIP)Cd7BNr3wx2J5nFEo>abiLHdQk<_6XW5bY>Sq(w%*K2f z&d=V>S3NKlW%gjipxu6aZHeU64Q0G{eDg!iNJZb1$sKH9mClD|3dYazXCw!(0o{O{ z>1*;2Hccs38@z#kt;`I&hIkhpou|hj7JH6`U;=->Z2l^za#-Ls3qm_9?djOUjPXk0 zwLp-F+eP+k=ZRV$+B#KhOO%EUcJL*T@jH8T7n6j^5h+Ei+TijUifyBq$ARZDGQvjp z`Sm9LySxIAkd(v}J;A^naJmbF+o{)uT#WK6iQ*dnDitPiruRTqut`O)e7CV)&9qhg zu;4UI>03(|_%_6sxEFtnc)>sIaIY+`TJvX!cp{=DDj9zVA-b5GheX5s?*wLBk5_*u z!43V^dJP^(^du`vVMjMc>WV$=f5wH+vNwc%-F+Sn@ngN+lO+AGFECpelw;vc76>gjAgH>TF#neJ0otiw4TzZ(&?J~B}#~ce)8VM4N;WhkkwkvE6 z7OAEt9{?`@M%=9}FJAN8*W0Rxj%5X~=m}}7;fK2kw>NC~>5ewCuWgi{x-7>uI`KKa zKV3LYMgVI_YOn7xX0PxJ`6)tm5Kya3pQu6usZO=Cc3rSLJAHZ(W%ss?;VzUBe4bvO z_@dWlxAK-6pOM^9SfxrSXHt1tJ>|?J@9)D^hyoSZDeg^YBM&c;DkC^&{&$|^#)%HX zP(N&{fq=)F?)19Ck-Tx6DbUV4yDGR{bBQ7r1(c!6@2R2j3Io%iKPXI z^Q$dl1YGuBIl>J+uxcX@Sh2}t){7^>M_mI}2fZW1KY7=tzJjr@;|x!)y}y!$3~Lus z*~n|!`vK9vua=)+>Vze|k(%kwWrvEivX6^*dpM_5ZBS!QPBNI*W}Tjb+kg0M7mi#~81l6`L&#y2Agz~}U zR-S*jFcsQhoz^L?QTAI>D*)@>)2FYsJx{;hbicA6$=PUVw})IFTUm;Upzxv}4s@_E zSrV(Few$y<<7@lnFh*upcXm{UrHzgG`j0%U1PD$ZR3E@6eJyYanUx`cB+c$Lt^gmF z8&j1P;@sHHe;sB{MwD}*?a1dkH$nN75g|Ilbhy2KK60z#`>W1C_2~NQ0S69JTm7pm z*J!`uwRR~x`NF>=wsMDSG_sLLHgN-1#6#ZI_9fg6r$qA^XU zs%dwOf=d5zJ?~Jf;eH$lQZ@9B2<8%~STsAz&OGZWixQ77{%fyWzXPHe4^XHh~NGh z^MYz3Wu>`d?@N3nb@e4FQY}$dNch>uVmYGO(<#@d^ca8R!VgR^hr1!%%{IEc36L%~ zo(dOaqm<}H=0Pl6G+!@vdVc=*w=?ygoNz<@srd=9@vjh9TDngx;q4b??YsHljSxwu zGw7%8gi)PrK4cCDK!1o!+0WaQ7B{3g-5i1+Z7*)Lx@>>!hGzXd3WbW!oKkF6?H{!mou;d%BKRp*0&bsx;uJ7X^8 zcTSyD?{H(``IoPJGYFJx>9FKRL3~}Es*t*ic?xlnHx};r5pi%NQ}73S;bI={reC3d znXg|8sAi3hkWj_3;9GC7-LnCKcU2m%W+N+~VgN_ikrCO?NsF0eY&HGy(#lzxW>u@Jt>zK*Px3!ad!g+SR8U-Kz+EO=e)1 z+op>V2roC|Oul^f;jX&rFZQ@Hd(fL!Ih9GMUOMCKYrRPwwtEc_$U!=nAe6xTd0rsv zqaDOYDQ|L%0f_Lr&(epoYIQONDPp}I(B9XB^P5Pf)?0>G;-_+g=*aL5sk+=sRSW95 zKwn&&4Hp2zps~#0VMv!OseqEt5#DImI_%1nO-9h`Q#Pb3yuxi@stOBhOfVES&HrFb zx+=A(>&Bc~NtcA|r9J6bbDt_`V0vK2$^PQX!Agd2$%hJ{ye$dC^!C?HnoGxg!gV7ateBKyKN4u9Gid$vmyw zTz=-g9Q;0MU8BKr+hzHnlSdC^r9)!_64FwE_GbDUu^f43Pb5ttcH(G;L75C8Qo9OK z%ax@2FdvW&{HSa`n-w^a*_<)~!$bFnfmwt_5XVc(lM(^FRKV?#NHm)IS#~z-eK~38 ztj`UmhOV)x)1P?xSt?wfQy04%Li-aURY5`7!I$|mq z1K$k(emXy@%*IuQ0$GLV|9{Chs_wX=M#1?TZAuzES(R$Vbh#31Ye&|#sI91${5IOt^ZZ7 zzE1}J#nm}MvTALqIc&p#e1GNj62)LSA^gzb&EZ-V{EHSCyM4TZNH^0@XUOC$WK-Oz zm7jq$@bP_`CCe~io12I}^hZSMBm46?gu_ME4zjgXTPC57E|M4BaB>%zaDXXrwv|=* zbmFuWN)cJSA-ypZjx<8C@t>3hVdz_zgk(f|^}=-SI`a+$exn;vQd`f$E}44NO@88i z!t~^siav?!>lbkrcx1LX{|wVQ>-~yT4Z1;Vc}zY7%298wGLmL|CgJsT>3DX6jHJRv zevw`n{*hYr*Sb>;i6XP!76mShBpxNQzpbJaa>}AG^`gFk;21yl+|>n)7B9cpQ7}dB zZ!^9jm8c?#WM}Rj<{m)bWlnWb^5pAOow;=iR#cRa;n23vi&EBpDVlyFfK7+4V;JX) zB53+6*Ky;fHi}PL77L7`zx=NlL8_a|T3@B%yrG`2ws|0p<}pbPM;E6=@M8anH}J9% zSZNT?sR-ag+0&rn{z^CcBZS8H7>pG-w)M#Hv|2BddR9;}0x3}BB>IULN=rJH-gKG_ zG(%Bx;(P;5qjYm*ikw2@j=rqM0iQiMvM*qrUj&@qtD{lHWWBzcTya`do62ttVKOuW zpIX1_92ddilzF!aE0Pewv0zUlyf_XN02-#apA-bj-L4e`~cuEyI0QKXAlzN$eqIs z`~WW4vpyj03JF;|#vHthL1?GT@m1CA%+#YhqTZSLwW_AOt0EuuWquhE8QD1t#}^SG z0*wfeAdS%Q1`%+M1Ys{A0viw@K{kNHTSR~aVILp@8xR;G9b9j+J6K{kWw4+D-pPk010v(4Sps9B?L&25+M9a1g;}Mf?P*q zq|g8T`0YC{&Ug2V^XSF-y_4t9b|2#V^H1*Y;r>WfVKG|}AVIbO)Yolveh@+4(~ITe ziR61!ocqotdr9IBopVP?x<0{u$jNy`vIr^@9wf-X;7cbLO>ytUIVXfj_Kd~t7i1w( zjKK9JPJ;A>WGNq*NAYoth-jY9O$r;n4#EB#042m{!g- zo!gEau6RM4SV=;EqyVpnHxk4MqL@||?eP?*lCg-2ct(=c{kC$W`NAEVTFu~C?vNk` zwDMUir=HR+Mt;}FD>JuppG1`%O)V8nTZKrFtq|n`ur_`ynwCIQvSHa8|2p1CSsJ1as3$9}rw# zq?mb9VORn8-TP6TNaQ=Rz9p)s+2HecrBUub)T9vDb2>@uT^7Reh5Ai%3 z?uT@%w`UgpFSM1?VRwv;v}gh4U3vH$(374U41hcqF zf)qetG==F=eBJt%ju`XUTA4zCrd9z&k8dPM0VYh2;-lg%Jsmsdp{P_QVmGxGhgm}Y zsYQSU5m@q=hkh|)A3uVUVh1~n+HIf9P@0=!RDv{DW4b$L(<*h3rmL;4WN0@`An2qs zDK10FV5t6yo**{UYWkQ*wF&F57)>oML#c<+MNJTE%)@B9G(djy^8cRQ|L*hidp~>r z>Fzr>?{xnrTEw&rCC8(Cxgq5cH9;iiq27Q&I@=?dE`2rr^N-(t@5Qs-KfO%We@|TU zhvdS2Ailuo|M}PMPx>mO&?4BuQI#d|fXS&6jzlP0g5-|5Q!hp=I-Bd`_1XFE50mBM z)FtjmrTkt?*Y1y>pYQ&(biMUgiH*Q#QpQHO;nEb~Q` zT9R;9s{l4Cf>=!}IOZ@>3aom0ySPYIm?vwUoVHSUj?}O_BTP`>rOiK7IaOnY^66XMn!ECT*W-JDg2Nto=a(JC40F^>c_JUQOjruf;oNz zfTM4~V5nOsQN(g+YUv@2f*{2xsE&E8cFrc*fI;7E-6XB1meDlrR=Js_4xS)zG)<I~ny(w0%%aj@fL$psjOPImSk|BN!|}jE?2xds}U9 zs$nDwQ%kEwqFaU%BtbImkvR2ltT?xS3#O%(BvCCx36dZ-$2^_w1`PGx zx&Ny)wVE*vjv#r=L+zM9UG3Qae|a;U^SNWKiQUwSV;M?N1TmXdk;FW-WGPHdrKuIi zGL)bQVl%DciFs%V!euB`q=F%cHRe%_mQ_R|zhX7DjI)K_p&JZAB<3;M1`OV{`t}MV zfVf>sB(RL<#rf{Od<^mkf*`qL?$nDBi&4_?Rgb`G=L}0waMlUbT?=;OIqwN#bcux?UW1jZ90Yk{_VQeICU9Sm}n^x+Whg% zRI5dTT!!K;L9C`#jEfQbpzbpA-B7$Gh;e!`+Qo=nNiwdf)wQ<$r}vT|hGuimLO)+iW@Mr@97;<=BAd@Y7qyE z0B!3$dCis}0MdXV9diR%eTyzlD}IwbIBx(>3z?-sw0Tml zJpv&vLzxvpY>qi&9__pGa4C(Z*6d>=vmr=sTIn70Wges&uBDlCYXm}yjm&}|X45Kl z(fv25*1dItgt-<8V;RaU2x6Sk(8W9^=ol>W3V}kyYXgY0HMJ%u2x1;|%&$6DD4#-! zjZ97u#ymoyMD;mxQ_E?!$SjwkOiGa4F?Wo4xNu)f&zGT0MvxYcx%vk>;dVFwp3HYs zYchhEV;*mvJ)BGTU_ERI*iEg)p{<1Bq$eRrzQlkrkKvkiemNUgJ?t0e_v8;UIzeDs z$y0L!1_2?a!@3MbW;Wk1`pTp*jZP58JSJ`LNeefcTC<3aj7kuU2r@FL?MtItp0x2< zxDEm9G8DwgbU6-0tB*Jt23DG zWA2RDcWciiKZ%WCXGU!RaZ*!j6oOy`Fqu}|fMF(Gd6;VW6dM_ZAhwtXH=Q1)Th1%j z!qn1g5eM@e1MS#PF*reTCrcmmpkqFE7nrU*F|}YuYe_PRsWmu3#K}6*Hi67=Ji>gr zR-SSe1m-8}gQ+zrK?=uQ+cNx}hZd=TAzJCd&_;Q zk_$x0YHAsmEVV|dcY=V~V7RtMGIB(<0mJHzkN%>;X7T^z&lDfiUkBTEwsn<-*d}T7lLp zK?+9iYim+CXIk-KC4s5*`CzzK+eCW&V81q$=?<(`MHdu_h0`;eA+{B$PR04t^@B zo>xds5Tn{>%%h=W$RMLy&?&QoSnb=83onm_fQlec_fY%VO3}2^B1O5mBcLLP%`s=p zWA2U4`soqaN|4;N(mUqMh&Eu*!%RtE5!gr&vuTyO&KofJ3MA_tCITA?B97vr;j!tM z8^EIIun1svf}Bqfkd^l%4EAkGY$Qk?^KgjrI?aoYxdtW542=N#tq>xWa?p2W#xQda z_GPetlDi9X$8aV_pNf=8{Z@s~S)%rztZQ z8SI!N1Gm~IP7?P%`u!$+UJ=BkJ@`A15~ljE#dP>qk`!|Kj47ERfSpg|!&ZDn2FF&1 zJtGKMBy7^oTn)b6BWfPzeeG*^Gc-2}~;kP+h7`S|lS#lBDm|B`jZCaw&Tm zl1|^@RARIPDONjY$N1hULneNO0I*CAJL)uPkwtEQWed7qW!~`NDGF`4$IC+@ziHcF*<5P`WM08>jWO9g~E22K9H{}O?DAb@`P;(YfA{nRIVe9Ld>R;~@PHJAal>Aw<>>Z%oyStmdfQXWf!GCO}}AB1JjWT2MC#f})of>j&~qk_ZSqqCw!eP!5#`t`Ma7Q4~xqlB5C`FNB9c zdb-*f{6hfbcnpiFvFf!1fiyJ9YUlCgh62!*e+iOhvD>TLd z`meQeCW2hg9EIg%c??$GMJ2?s&xj*(2Otkm_lOt_hXMK-&TOz2>jwP~C;FnC;$w8* zYpgXvq+*o=jy;GYmCNRpK_H%I4~fAr7@(iwM4!Wn{)e;DF*{u7l^`AJ!K6fBmI(Ak zkXb$)D@6plB0z$4MUd%(Mt}qfx_S9@ zJpv?1d+eAp0t85q2pXA>*d75Aq&;>_836($NCb_{M{JKk+yrTl8&gJt00|OFL-R4) zAwYt(!;PsTL4X8_q@nqk?GPYA+Tq4jksv^V4AjufHVg#500030|HY(2RsaA121!Ig aR09BF1iD2y*bf%~0000 _Title; + set => SetProperty(ref _Title, value); + } + + private string _Message = ""; + public string Message + { + get => _Message; + set => SetProperty(ref _Message, value); + } + + private string _Icon= $"pack://siteoforigin:,,,/Resources/Images/info.png"; + public string Icon + { + get => _Icon; + set => SetProperty(ref _Icon, value); + } + + private bool _ShowYes; + public bool ShowYes + { + get => _ShowYes; + set => SetProperty(ref _ShowYes, value); + } + + private bool _ShowNo; + public bool ShowNo + { + get => _ShowNo; + set => SetProperty(ref _ShowNo, value); + } + + private bool _ShowOk; + public bool ShowOk + { + get => _ShowOk; + set => SetProperty(ref _ShowOk, value); + } + + private bool _ShowCancel; + public bool ShowCancel + { + get => _ShowCancel; + set => SetProperty(ref _ShowCancel, value); + } + + #endregion + + #region 命令 + public ICommand YesCommand { get; set; } + public ICommand NoCommand { get; set; } + public ICommand OkCommand { get; set; } + public ICommand CancelCommand { get; set; } + #endregion + + public DialogCloseListener RequestClose { get; set; } + + public MessageBoxViewModel(IContainerProvider containerProvider):base(containerProvider) + { + YesCommand = new DelegateCommand(OnYes); + NoCommand = new DelegateCommand(OnNo); + OkCommand = new DelegateCommand(OnOk); + CancelCommand = new DelegateCommand(OnCancel); + } + + private void CloseDialog(ButtonResult result) + { + var parameters = new DialogParameters(); + RequestClose.Invoke(new DialogResult(result)); + } + + private void OnYes() => CloseDialog(ButtonResult.Yes); + private void OnNo() => CloseDialog(ButtonResult.No); + private void OnOk() => CloseDialog(ButtonResult.OK); + private void OnCancel() => CloseDialog(ButtonResult.Cancel); + + #region Prism Dialog 规范 + public bool CanCloseDialog() => true; + + public override void OnDialogClosed() + { + _eventAggregator.GetEvent().Publish(false); + } + + public override void OnDialogOpened(IDialogParameters parameters) + { + _eventAggregator.GetEvent().Publish(true); + Title = parameters.GetValue("Title"); + Message = parameters.GetValue("Message"); + var iconKey = parameters.GetValue("Icon"); // info / error / warn + Icon = iconKey switch + { + "info" => $"pack://siteoforigin:,,,/Resources/Images/info.png", + "error" => $"pack://siteoforigin:,,,/Resources/Images/error.png", + "warn" => $"pack://siteoforigin:,,,/Resources/Images/warning.png", + _ => $"pack://siteoforigin:,,,/Resources/Images/info.png" // 默认 + }; + + + ShowYes = parameters.GetValue("ShowYes"); + ShowNo = parameters.GetValue("ShowNo"); + ShowOk = parameters.GetValue("ShowOk"); + ShowCancel = parameters.GetValue("ShowCancel"); + } + #endregion + } +} diff --git a/ADP/ViewModels/ShellViewModel.cs b/ADP/ViewModels/ShellViewModel.cs new file mode 100644 index 0000000..05ffb65 --- /dev/null +++ b/ADP/ViewModels/ShellViewModel.cs @@ -0,0 +1,772 @@ + +using Logger; +using MaterialDesignThemes.Wpf; +using Notifications.Wpf.Core; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.IO; +using System.Windows; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Threading; // 💡 新增:引入定时器命名空间 +using UIShare; +using UIShare.GlobalVariable; +using UIShare.PubEvent; +using UIShare.UIViewModel; + +namespace ADP.ViewModels +{ + public class ShellViewModel : BindableBase + { + #region 私有字段 + private string _Title = ""; + private bool _IsLeftDrawerOpen; + + private readonly ConcurrentDictionary _executionTasks = new(); + private readonly ConcurrentDictionary _errorExecutionTasks = new(); + + private readonly IEventAggregator _eventAggregator; + private readonly IRegionManager _regionManager; + private readonly IContainerProvider _containerProvider; + private readonly INotificationManager _notificationManager; + private readonly IModuleManager _moduleManager; + private readonly GlobalInfo _globalInfo; + + // 💡 新增:UI 刷新定时器,用于高频让前端重新拉取当前工位的 SW 累计时间 + private readonly DispatcherTimer _uiRefreshTimer; + #endregion + + #region 属性 + public string Title + { + get => _Title; + set => SetProperty(ref _Title, value); + } + public bool IsLeftDrawerOpen + { + get => _IsLeftDrawerOpen; + set => SetProperty(ref _IsLeftDrawerOpen, value); + } + + // 代理属性:动态反映当前被激活的 Scope 状态 + public TimeSpan RunningTime + { + get + { + if (CurrentContext == null) return TimeSpan.Zero; + // 💡 核心:如果当前工位的计时器正在跑,动态累加 SW 当前的时间,否则返回其保存的固定时间值 + return CurrentContext.SW.IsRunning ? CurrentContext.SW.Elapsed : CurrentContext.RunningTime; + } + set + { + if (CurrentContext != null && CurrentContext.RunningTime != value) + { + CurrentContext.RunningTime = value; + RaisePropertyChanged(); + } + } + } + + public bool IsTerminate + { + get => CurrentContext?.IsTerminate ?? false; + set + { + if (CurrentContext != null && CurrentContext.IsTerminate != value) + { + CurrentContext.IsTerminate = value; + RaisePropertyChanged(); + } + } + } + + private ScopedContext? CurrentContext => + _globalInfo.ContextDic.TryGetValue(_globalInfo.CurrentScope, out var ctx) ? ctx : null; + private StepRunning? CurrentRunner => + _globalInfo.StepRunningDic.TryGetValue(_globalInfo.CurrentScope, out var runner) ? runner : null; + + public string RunState + { + get => CurrentContext?.RunState ?? "运行"; + set + { + if (CurrentContext != null && CurrentContext.RunState != value) + { + CurrentContext.RunState = value; + RaisePropertyChanged(); + } + } + } + + public bool SingleStep + { + get => CurrentContext?.SingleStep ?? false; + set + { + if (CurrentContext != null && CurrentContext.SingleStep != value) + { + CurrentContext.SingleStep = value; + RaisePropertyChanged(); + } + } + } + + public PackIconKind RunIcon + { + get => CurrentContext?.RunIcon ?? PackIconKind.Play; + set + { + if (CurrentContext != null && CurrentContext.RunIcon != value) + { + CurrentContext.RunIcon = value; + RaisePropertyChanged(); + } + } + } + #endregion + + #region 命令 + public ICommand LeftDrawerOpenCommand { get; set; } + public ICommand MinimizeCommand { get; set; } + public ICommand MaximizeCommand { get; set; } + public ICommand CloseCommand { get; set; } + public ICommand NavigateCommand { get; set; } + public ICommand LoadCommand { get; set; } + public ICommand RefreshCommand { get; set; } + public ICommand DestroyCommand { get; set; } + public ICommand RunningCommand { get; set; } + public ICommand RunSingleCommand { get; set; } + public ICommand RestorationCommand { get; set; } + public ICommand RunAbnormalStepsCommand { get; set; } + public ICommand SaveAsCommand { get; set; } + public ICommand SaveCommand { get; set; } + public ICommand OpenCommand { get; set; } + public ICommand NewCommand { get; set; } + public ICommand SetDefaultCommand { get; set; } + #endregion + + public ShellViewModel(IContainerProvider containerProvider) + { + _containerProvider = containerProvider; + _globalInfo = containerProvider.Resolve(); + _eventAggregator = containerProvider.Resolve(); + _regionManager = containerProvider.Resolve(); + _notificationManager = containerProvider.Resolve(); + _moduleManager = containerProvider.Resolve(); + + LeftDrawerOpenCommand = new DelegateCommand(LeftDrawerOpen); + MinimizeCommand = new DelegateCommand(MinimizeWindow); + MaximizeCommand = new DelegateCommand(MaximizeWindow); + CloseCommand = new DelegateCommand(CloseWindow); + NavigateCommand = new DelegateCommand(Navigate); + LoadCommand = new DelegateCommand(Load); + RefreshCommand = new DelegateCommand(OnRefresh); + DestroyCommand = new DelegateCommand(OnDestroy); + RunningCommand = new DelegateCommand(OnRunning); + RunSingleCommand = new DelegateCommand(RunSingle); + RestorationCommand = new AsyncDelegateCommand(OnRestoration); + RunAbnormalStepsCommand = new AsyncDelegateCommand(OnRunAbnormalSteps); + NewCommand = new DelegateCommand(New); + OpenCommand = new AsyncDelegateCommand(Open); + SaveAsCommand = new DelegateCommand(SaveAs); + SaveCommand = new DelegateCommand(Save); + SetDefaultCommand = new DelegateCommand(SetDefault); + + _globalInfo.ContextDic.Add("default", new ScopedContext()); + + _eventAggregator.GetEvent().Subscribe(() => + { + Application.Current.MainWindow.Show(); + _regionManager.RequestNavigate("ShellViewManager", "MainView"); + }); + + _eventAggregator.GetEvent().Subscribe(UpdateRunIcon); + + _globalInfo.ScopeChanged += (s, e) => + { + RefreshAllContextProperties(); + }; + + // 💡 初始化轻量级 UI 定时器:每 100 毫秒刷新一次当前可见工位的界面时间 + _uiRefreshTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) }; + _uiRefreshTimer.Tick += (s, e) => RaisePropertyChanged(nameof(RunningTime)); + _uiRefreshTimer.Start(); + } + + private void RefreshAllContextProperties() + { + RaisePropertyChanged(nameof(RunState)); + RaisePropertyChanged(nameof(SingleStep)); + RaisePropertyChanged(nameof(RunIcon)); + RaisePropertyChanged(nameof(RunningTime)); + RaisePropertyChanged(nameof(IsTerminate)); + } + + #region 命令处理与事件 + private async void OnRunning() + { + string runningScope = _globalInfo.CurrentScope; + ScopedContext? targetContext = CurrentContext; + StepRunning? targetRunner = CurrentRunner; + + if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null || targetRunner == null) + { + return; + } + + if (targetContext.RunState == "运行") + { + targetContext.SingleStep = false; + LoggerHelper.InfoWithNotify($"{_globalInfo.UserName} 执行工位 [{runningScope}] 运行命令"); + + targetContext.RunState = "暂停"; + targetContext.RunIcon = PackIconKind.Pause; + + if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties(); + + if (targetContext.IsStop == null) + { + targetContext.IsStop = false; + + // 💡 启动或恢复:启动属于该快照工位独立的 SW 计时器 + targetContext.SW.Start(); + + if (!_executionTasks.TryGetValue(runningScope, out var existingTask) || existingTask == null) + { + existingTask = targetRunner.ExecuteSteps(targetContext.Program, cancellationToken: targetRunner.stepCTS.Token); + _executionTasks[runningScope] = existingTask; + } + + try + { + await existingTask; + } + catch (Exception ex) + { + LoggerHelper.ErrorWithNotify($"工位 [{runningScope}] 异常中止: {ex.Message}"); + } + + // 💡 测试正常完毕或取消:停止当前工位的 SW,并将最终时间固化到 RunningTime 字段中 + targetContext.SW.Stop(); + targetContext.RunningTime = targetContext.SW.Elapsed; + + targetContext.RunState = "运行"; + targetContext.RunIcon = PackIconKind.Play; + targetContext.IsStop = null; + + if (targetRunner.stepCTS.IsCancellationRequested) + { + targetRunner.stepCTS = new CancellationTokenSource(); + _executionTasks.TryRemove(runningScope, out _); + } + else if (_executionTasks.TryGetValue(runningScope, out var currentTask) && currentTask != null && currentTask.IsCompleted) + { + targetContext.IsTerminate = true; + _executionTasks.TryRemove(runningScope, out _); + } + + if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties(); + } + else if (targetContext.IsStop == true) + { + targetContext.IsStop = false; + // 💡 从暂停中恢复,继续累加当前工位计时 + targetContext.SW.Start(); + } + } + else // 用户点击了暂停 + { + LoggerHelper.InfoWithNotify($"{_globalInfo.UserName} 点击工位 [{runningScope}] 暂停命令"); + targetContext.SingleStep = true; + targetContext.IsStop = true; + targetContext.RunState = "运行"; + targetContext.RunIcon = PackIconKind.Play; + if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties(); + } + } + + private async void RunSingle() + { + string runningScope = _globalInfo.CurrentScope; + ScopedContext? targetContext = CurrentContext; + StepRunning? targetRunner = CurrentRunner; + + if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null || targetRunner == null) + { + return; + } + + if (targetContext.RunState == "运行") + { + targetContext.SingleStep = true; + LoggerHelper.InfoWithNotify($"{_globalInfo.UserName} 执行工位 [{runningScope}] 单步执行命令"); + + targetContext.RunState = "暂停"; + targetContext.RunIcon = PackIconKind.Pause; + + if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties(); + + if (targetContext.IsStop == null) + { + targetContext.IsStop = false; + + // 💡 单步启动:让专属工位的 SW 跑起来 + targetContext.SW.Start(); + + if (!_executionTasks.TryGetValue(runningScope, out var existingTask) || existingTask == null) + { + existingTask = targetRunner.ExecuteSteps(targetContext.Program, cancellationToken: targetRunner.stepCTS.Token); + _executionTasks[runningScope] = existingTask; + } + + try + { + await existingTask; + } + catch (Exception ex) + { + LoggerHelper.ErrorWithNotify($"工位 [{runningScope}] 单步执行异常: {ex.Message}"); + } + + // 💡 单步单步完成后会被 StepRunning 挂起,在此暂时停止 SW + targetContext.SW.Stop(); + targetContext.RunningTime = targetContext.SW.Elapsed; + + targetContext.RunState = "运行"; + targetContext.RunIcon = PackIconKind.Play; + targetContext.IsStop = null; + + if (targetRunner.stepCTS.IsCancellationRequested) + { + targetRunner.stepCTS = new CancellationTokenSource(); + _executionTasks.TryRemove(runningScope, out _); + } + else if (_executionTasks.TryGetValue(runningScope, out var currentTask) && currentTask != null && currentTask.IsCompleted) + { + targetContext.IsTerminate = true; + _executionTasks.TryRemove(runningScope, out _); + } + + if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties(); + } + else if (targetContext.IsStop == true) + { + targetContext.IsStop = false; + // 💡 继续单步,继续计时 + targetContext.SW.Start(); + } + } + } + + private async Task OnRestoration() + { + string runningScope = _globalInfo.CurrentScope; + ScopedContext? targetContext = CurrentContext; + StepRunning? targetRunner = CurrentRunner; + + if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null || targetRunner == null) + { + return; + } + + LoggerHelper.InfoWithNotify($"{_globalInfo.UserName} 执行工位 [{runningScope}] 复位命令"); + + _executionTasks.TryGetValue(runningScope, out var currentTask); + _errorExecutionTasks.TryGetValue(runningScope, out var errorTask); + + if (currentTask != null || errorTask != null) + { + targetRunner.stepCTS.Cancel(); + targetRunner.errorStepCTS.Cancel(); + + await Task.Delay(200); + + // 💡 彻底复位:停止当前工位的计时器,并且完全归零(Reset) + targetContext.SW.Reset(); + targetContext.RunningTime = TimeSpan.Zero; + + targetContext.IsStop = null; + targetRunner.ResetAllStepStatus(targetContext.Program.StepCollection); + targetRunner.ResetAllStepStatus(targetContext.Program.ErrorStepCollection); + targetContext.IsTerminate = false; + } + else + { + targetRunner.stepCTS = new CancellationTokenSource(); + targetRunner.errorStepCTS = new CancellationTokenSource(); + + // 💡 无任务状态下的直接归零 + targetContext.SW.Reset(); + targetContext.RunningTime = TimeSpan.Zero; + + targetContext.IsStop = null; + targetRunner.ResetAllStepStatus(targetContext.Program.StepCollection); + targetRunner.ResetAllStepStatus(targetContext.Program.ErrorStepCollection); + targetContext.IsTerminate = false; + } + + if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties(); + } + + private async Task OnRunAbnormalSteps() + { + string runningScope = _globalInfo.CurrentScope; + ScopedContext? targetContext = CurrentContext; + StepRunning? targetRunner = CurrentRunner; + + if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null || targetRunner == null) + { + return; + } + + LoggerHelper.InfoWithNotify($"{_globalInfo.UserName} 执行工位 [{runningScope}] 异常流程命令"); + + // 💡 异常测试启动计时 + targetContext.SW.Start(); + + if (!_errorExecutionTasks.TryGetValue(runningScope, out var existingErrorTask) || existingErrorTask == null) + { + existingErrorTask = targetRunner.ExecuteErrorSteps(targetContext.Program, cancellationToken: targetRunner.errorStepCTS.Token); + _errorExecutionTasks[runningScope] = existingErrorTask; + } + + try + { + await existingErrorTask; + } + catch (Exception ex) + { + LoggerHelper.ErrorWithNotify($"工位 [{runningScope}] 异常流程执行出错: {ex.Message}"); + } + + // 💡 异常测试结束停止计时 + targetContext.SW.Stop(); + targetContext.RunningTime = targetContext.SW.Elapsed; + + if (targetRunner.errorStepCTS.IsCancellationRequested) + { + targetRunner.errorStepCTS = new CancellationTokenSource(); + _errorExecutionTasks.TryRemove(runningScope, out _); + } + else if (_errorExecutionTasks.TryGetValue(runningScope, out var currentErrorTask) && currentErrorTask != null && currentErrorTask.IsCompleted) + { + targetContext.IsTerminate = true; + _errorExecutionTasks.TryRemove(runningScope, out _); + } + + if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties(); + } + + private void SetDefault() + { + // 💡 1. 抓取触发瞬间的 Scope 与 Context 快照 + string runningScope = _globalInfo.CurrentScope; + ScopedContext? targetContext = CurrentContext; + + if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null) return; + + if (targetContext.CurrentFilePath != null) + { + //SystemConfig.DefaultProgramFilePath = targetContext.CurrentFilePath; + //ConfigService.Save(); + LoggerHelper.SuccessWithNotify($"工位 [{runningScope}] 已成功将当前程序设为默认启动程序"); + } + } + + private void New() + { + // 💡 1. 抓取快照 + string runningScope = _globalInfo.CurrentScope; + ScopedContext? targetContext = CurrentContext; + + if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null) return; + + // 💡 2. 严格对快照隔离的 Context 数据进行清理,不影响其他工位 + targetContext.CurrentFilePath = null; + targetContext.Program.Parameters.Clear(); + targetContext.Program.StepCollection.Clear(); + targetContext.Program.ErrorStepCollection.Clear(); + + LoggerHelper.InfoWithNotify($"工位 [{runningScope}] 创建了空程序文件"); + + // 如果当前正看着该工位,刷新 UI 显示 + if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties(); + } + + private async Task Open(string filePath = null) + { + // 💡 1. 抓取进方法瞬间的快照,锁死上下文 + string runningScope = _globalInfo.CurrentScope; + ScopedContext? targetContext = CurrentContext; + + if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null) return; + + try + { + // 如果没有传路径,弹出文件选择对话框(WPF 对话框是模态的,会阻塞当前 UI 线程) + if (string.IsNullOrEmpty(filePath)) + { + var openFileDialog = new Microsoft.Win32.OpenFileDialog + { + Filter = "ADP程序文件|*.ADP|所有文件|*.*", + Title = $"工位 [{runningScope}] 打开程序", + }; + + if (openFileDialog.ShowDialog() != true) + return; // 用户取消选择 + + filePath = openFileDialog.FileName; + } + + // 确认文件存在 + if (!File.Exists(filePath)) + { + LoggerHelper.ErrorWithNotify($"文件不存在: {filePath}"); + return; + } + + // 读取 JSON 文件 + string json = File.ReadAllText(filePath); + + // 反序列化为 ProgramModel + var program = Newtonsoft.Json.JsonConvert.DeserializeObject(json); + + if (program == null) + { + LoggerHelper.WarnWithNotify($"文件格式不正确或为空: {filePath}"); + return; + } + + // 💡 2. 严格赋值给快照锁定下的当前工位上下文,实现数据完全隔离 + targetContext.Program.Parameters = program.Parameters; + targetContext.Program.StepCollection = program.StepCollection; + targetContext.Program.ErrorStepCollection = program.ErrorStepCollection; + targetContext.CurrentFilePath = filePath; + + LoggerHelper.SuccessWithNotify($"工位 [{runningScope}] 成功打开文件: {filePath}"); + + // 💡 3. 安全调用异步复位,确保重置的是对应工位的数据 + // 注意:由于在 OnRestoration 内部第一行也做了快照拦截, + // 如果用户此时正好在看这个工位,它会自动完美执行 + await OnRestoration(); + } + catch (Exception ex) + { + LoggerHelper.ErrorWithNotify($"工位 [{runningScope}] 打开文件失败: {ex.Message}"); + } + finally + { + // 💡 4. 异步回归:不管中途用户切去了哪里,回到该工位时及时刷新显示 + if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties(); + } + } + + private void SaveAs() + { + if (!_globalInfo.IsAdmin) return; + + // 💡 1. 抓取快照 + string runningScope = _globalInfo.CurrentScope; + ScopedContext? targetContext = CurrentContext; + + if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null) return; + + string defaultPath = @"D:\ADP\子程序"; + if (!Directory.Exists(defaultPath)) + Directory.CreateDirectory(defaultPath); + + var saveFileDialog = new Microsoft.Win32.SaveFileDialog + { + Filter = "ADP程序文件|*.adp|所有文件|*.*", + Title = $"工位 [{runningScope}] 程序另存为", + FileName = "NewProgram.ADP", + InitialDirectory = defaultPath + }; + + if (saveFileDialog.ShowDialog() == true) + { + // 💡 2. 将新路径安全写入快照工位 + targetContext.CurrentFilePath = saveFileDialog.FileName; + + // 💡 3. 调用你的底层文件保存逻辑,传入快照工位的 Program 模型 + SaveProgramToFile(targetContext.CurrentFilePath, targetContext.Program); + + LoggerHelper.InfoWithNotify($"{_globalInfo.UserName} 另存为文件成功: {saveFileDialog.FileName}"); + + if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties(); + } + } + + private void Save() + { + if (!_globalInfo.IsAdmin) return; + + // 💡 1. 抓取快照 + string runningScope = _globalInfo.CurrentScope; + ScopedContext? targetContext = CurrentContext; + + if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null) return; + + // 💡 2. 判断当前工位是否有历史路径 + if (targetContext.CurrentFilePath == null) + { + SaveAs(); + return; + } + + // 💡 3. 持久化当前快照工位的 Program + SaveProgramToFile(targetContext.CurrentFilePath, targetContext.Program); + LoggerHelper.SuccessWithNotify($"工位 [{runningScope}] 程序保存成功!"); + } + + // 💡 辅助方法:建议将你的通用序列化落盘代码调整为接收 ProgramModel 参数,提高复用性 + private void SaveProgramToFile(string filePath, ProgramModel programModel) + { + try + { + string json = Newtonsoft.Json.JsonConvert.SerializeObject(programModel, Newtonsoft.Json.Formatting.Indented); + File.WriteAllText(filePath, json); + } + catch (Exception ex) + { + LoggerHelper.ErrorWithNotify($"写入程序文件失败: {ex.Message}"); + } + } + + private void UpdateRunIcon(string obj) + { + CurrentContext?.SW.Stop(); + CurrentContext.RunningTime = CurrentContext.SW.Elapsed; + RunIcon = obj switch + { + "Play" => PackIconKind.Play, + "Pause" => PackIconKind.Pause, + _ => RunIcon + }; + } + + private void OnRefresh() + { + _eventAggregator.GetEvent().Publish(""); + _globalInfo.CurrentScope = "default"; + } + private void OnDestroy() + { + if (_globalInfo.CurrentScope == "default" || string.IsNullOrEmpty(_globalInfo.CurrentScope)) return; + + string targetRegionName = _globalInfo.CurrentScope; + + // 💡 物理销毁工位时,顺便清理任务字典,防止内存泄漏 + _executionTasks.TryRemove(targetRegionName, out _); + _errorExecutionTasks.TryRemove(targetRegionName, out _); + + if (_regionManager.Regions.ContainsRegionWithName(targetRegionName)) + { + var region = _regionManager.Regions[targetRegionName]; + var viewsToDestroy = region.Views.ToList(); + + foreach (var view in viewsToDestroy) + { + if (view is FrameworkElement element) + { + var viewModel = element.DataContext; + region.Remove(view); + if (viewModel is IDisposable disposableVM) disposableVM.Dispose(); + } + } + } + + _globalInfo.ContextDic.Remove(targetRegionName); + _globalInfo.StepRunningDic.Remove(targetRegionName); + + var parameters = new NavigationParameters { { "Name", targetRegionName } }; + _regionManager.RequestNavigate(targetRegionName, "ProtocolStartView", parameters); + _eventAggregator.GetEvent().Publish(""); + } + private void Load() + { + _notificationManager.ShowAsync(new NotificationContent { Title = "登录成功", Message = "", Type = NotificationType.Success }); + //默认导航到主界面 + Type moduleAType = typeof(MainModule.MainModule); + _moduleManager.LoadModule(moduleAType.Name); + } + private void Navigate(string content) + { + switch (content) + { + case "主界面": + if (_globalInfo.CurrentScope == "default" || string.IsNullOrEmpty(_globalInfo.CurrentScope)) + { + _regionManager.RequestNavigate("ShellViewManager", "MainView"); + break; + } + var TestingRegion = _globalInfo.CurrentScope; + if (!_regionManager.Regions.ContainsRegionWithName(TestingRegion)) break; + _regionManager.RequestNavigate(TestingRegion, "AutomatedTestingView"); + break; + case "监控界面": + // 仅当某个工位被选中(九宫格已展开/选择)时才跳转,默认 default 跳过。 + if (_globalInfo.CurrentScope == "default" || string.IsNullOrEmpty(_globalInfo.CurrentScope)) break; + + // 记录界面不是填到全局的 ShellViewManager,而是填到该工位专属的 region。 + var monitorRegion = _globalInfo.CurrentScope; + if (!_regionManager.Regions.ContainsRegionWithName(monitorRegion)) break; + + var monitorParameters = new NavigationParameters { { "Name", monitorRegion } }; + _regionManager.RequestNavigate(monitorRegion, "MonitorView", monitorParameters); + IsLeftDrawerOpen = false; + break; + case "记录界面": + if (_globalInfo.CurrentScope == "default" || string.IsNullOrEmpty(_globalInfo.CurrentScope)) break; + + // 记录界面不是填到全局的 ShellViewManager,而是填到该工位专属的 region。 + var recordRegion = _globalInfo.CurrentScope; + if (!_regionManager.Regions.ContainsRegionWithName(recordRegion)) break; + + var recordParameters = new NavigationParameters { { "Name", recordRegion } }; + _regionManager.RequestNavigate(recordRegion, "RecordView", recordParameters); + IsLeftDrawerOpen = false; + break; + case "设置界面": + if (_globalInfo.CurrentScope == "default" || string.IsNullOrEmpty(_globalInfo.CurrentScope)) break; + + // 记录界面不是填到全局的 ShellViewManager,而是填到该工位专属的 region。 + var settingRegion = _globalInfo.CurrentScope; + if (!_regionManager.Regions.ContainsRegionWithName(settingRegion)) break; + + var settingParameters = new NavigationParameters { { "Name", settingRegion } }; + _regionManager.RequestNavigate(settingRegion, "SettingView", settingParameters); + IsLeftDrawerOpen = false; + break; + case "更新界面": + _regionManager.RequestNavigate("ShellViewManager", "UpdateInfoView"); + break; + } + } + + private void LeftDrawerOpen() + { + IsLeftDrawerOpen = true; + } + + private void MinimizeWindow(Window window) + { + if (window != null) + window.WindowState = WindowState.Minimized; + } + + private void MaximizeWindow(Window window) + { + if (window != null) + { + window.WindowState = window.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; + } + } + + private void CloseWindow(Window window) + { + window?.Close(); + } + #endregion + } +} \ No newline at end of file diff --git a/ADP/Views/Dialogs/MessageBoxView.xaml b/ADP/Views/Dialogs/MessageBoxView.xaml new file mode 100644 index 0000000..d4de9e6 --- /dev/null +++ b/ADP/Views/Dialogs/MessageBoxView.xaml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LoginModule/Views/LoginView.xaml.cs b/LoginModule/Views/LoginView.xaml.cs new file mode 100644 index 0000000..31db76b --- /dev/null +++ b/LoginModule/Views/LoginView.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace LoginModule.Views +{ + /// + /// LoginView.xaml 的交互逻辑 + /// + public partial class LoginView : UserControl + { + public LoginView() + { + InitializeComponent(); + } + } +} diff --git a/LoginModule/Views/RegisterView.xaml b/LoginModule/Views/RegisterView.xaml new file mode 100644 index 0000000..5874855 --- /dev/null +++ b/LoginModule/Views/RegisterView.xaml @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LoginModule/Views/RegisterView.xaml.cs b/LoginModule/Views/RegisterView.xaml.cs new file mode 100644 index 0000000..f8d1138 --- /dev/null +++ b/LoginModule/Views/RegisterView.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace LoginModule.Views +{ + /// + /// RegisterView.xaml 的交互逻辑 + /// + public partial class RegisterView : UserControl + { + public RegisterView() + { + InitializeComponent(); + } + } +} diff --git a/MainModule/MainModule.cs b/MainModule/MainModule.cs new file mode 100644 index 0000000..39a030c --- /dev/null +++ b/MainModule/MainModule.cs @@ -0,0 +1,28 @@ +using MainModule.Views; +using System.Reflection; +using UIShare.GlobalVariable; + +namespace MainModule +{ + [Module(OnDemand=true)] + public class MainModule : IModule + { + public void OnInitialized(IContainerProvider containerProvider) + { + IRegionManager regionManager = containerProvider.Resolve(); + regionManager.RegisterViewWithRegion("ShellViewManager", typeof(MainView)); + } + + public void RegisterTypes(IContainerRegistry containerRegistry) + { + containerRegistry.RegisterForNavigation("MainView"); + containerRegistry.RegisterForNavigation("AutomatedTestingView"); + containerRegistry.RegisterForNavigation("ProtocolStartView"); + // Scoped: one ScopedContext per container scope. + // AutomatedTestingViewModel creates its own scope (IContainerExtension.CreateScope) + // and resolves the 5 child VMs from it, so all siblings inside one parent share + // the same ScopedContext, while different parents get isolated instances. + + } + } +} diff --git a/MainModule/MainModule.csproj b/MainModule/MainModule.csproj new file mode 100644 index 0000000..8b229f9 --- /dev/null +++ b/MainModule/MainModule.csproj @@ -0,0 +1,15 @@ + + + + net8.0-windows + enable + true + enable + + + + + + + + diff --git a/MainModule/ViewModels/AutomatedTestingViewModel.cs b/MainModule/ViewModels/AutomatedTestingViewModel.cs new file mode 100644 index 0000000..d7dbbaa --- /dev/null +++ b/MainModule/ViewModels/AutomatedTestingViewModel.cs @@ -0,0 +1,114 @@ +using System; +using System.Windows.Input; +using Prism.Ioc; +using TestingModule.ViewModels; +using UIShare; +using UIShare.GlobalVariable; +using UIShare.PubEvent; +using UIShare.ViewModelBase; + +namespace MainModule.ViewModels +{ + public class AutomatedTestingViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable + { + #region 私有字段 + private string _testStatus; + private readonly IScopedProvider _scope; + #endregion + + #region 属性 + public bool KeepAlive => true; // 保持存活 + + public string TestStatus + { + get => _testStatus; + set => SetProperty(ref _testStatus, value); + } + + // 该 AutomatedTestingView 实例独占的 ScopedContext,5 个子面板共享 + public ScopedContext _scopedContext { get; } + public StepRunning _stepRunning { get; } + public GlobalInfo _globalInfo { get; } + public SystemConfig _systemConfig { get; } + + // 5 个子 ViewModel 全部从同一个 scope 解析,自动注入同一个 ScopedContext + public CommandTreeViewModel CommandTreeVM { get; } + public StepsManagerViewModel StepsManagerVM { get; } + public SingleStepEditViewModel SingleStepEditVM { get; } + public LogAreaViewModel LogAreaVM { get; } + public ParametersManagerViewModel ParametersManagerVM { get; } + #endregion + + public ICommand RefreshCommand { get; set; } + public ICommand BackToProtocolCommand { get; set; } + + public AutomatedTestingViewModel(IContainerExtension container) : base(container) + { + // 每个 AutomatedTestingViewModel 实例创建独立的容器作用域 + _scope = container.CreateScope(); + _globalInfo=container.Resolve(); + // 在该作用域内解析 ScopedContext —— 当前作用域唯一 + _scopedContext = _scope.Resolve(); + _stepRunning = _scope.Resolve(); + _systemConfig=_scope.Resolve(); + // 关键:从同一个 _scope 解析 5 个子 VM,DI 会把同一个 ScopedContext 注入它们 + CommandTreeVM = _scope.Resolve(); + StepsManagerVM = _scope.Resolve(); + SingleStepEditVM = _scope.Resolve(); + LogAreaVM = _scope.Resolve(); + ParametersManagerVM = _scope.Resolve(); + RefreshCommand = new DelegateCommand(OnRefresh); + BackToProtocolCommand = new DelegateCommand(OnBackToProtocol); + } + + public void Dispose() + { + if (string.IsNullOrEmpty(TestStatus)) + { + _scope?.Dispose(); + return; + } + try + { + _globalInfo.ContextDic?.Remove(TestStatus); + _globalInfo.StepRunningDic?.Remove(TestStatus); + } + catch (Exception ex) + { + Logger.LoggerHelper.ErrorWithNotify($"卸载机台 [{TestStatus}] 全局引用失败: {ex.Message}"); + } + finally + { + _scope?.Dispose(); + } + } + #region 命令处理与事件 + private void OnRefresh() + { + // 双击:把自己的名字扔出去 + _eventAggregator.GetEvent().Publish(TestStatus); + _globalInfo.CurrentScope = TestStatus; + } + + private void OnBackToProtocol() + { + // 返回:扔个空字符串出去 + _eventAggregator.GetEvent().Publish(""); + } + #endregion + + #region 重写 + public override void OnNavigatedTo(NavigationContext navigationContext) + { + base.OnNavigatedTo(navigationContext); + if (navigationContext.Parameters.ContainsKey("Name")) + { + TestStatus = navigationContext.Parameters.GetValue("Name"); + _globalInfo.ContextDic.Add(TestStatus, _scopedContext); + _globalInfo.StepRunningDic.Add(TestStatus, _stepRunning); + } + + } + #endregion + } +} \ No newline at end of file diff --git a/MainModule/ViewModels/MainViewModel.cs b/MainModule/ViewModels/MainViewModel.cs new file mode 100644 index 0000000..db8d819 --- /dev/null +++ b/MainModule/ViewModels/MainViewModel.cs @@ -0,0 +1,67 @@ +using System; +using System.Linq; +using System.Windows; +using System.Windows.Input; +using UIShare.PubEvent; +using MainModule.ViewModels; +using UIShare.ViewModelBase; + + +namespace MainModule.ViewModels +{ + public class MainViewModel : NavigateViewModelBase, IRegionMemberLifetime + { + #region 私有字段 + private bool IsInitialized = false; + private string _expandedCellName = string.Empty; + #endregion + + #region 属性 + public bool KeepAlive => true; // 保持存活 + + public string ExpandedCellName + { + get => _expandedCellName; + set => SetProperty(ref _expandedCellName, value); + } + #endregion + + #region 命令 + public ICommand LoadedCommand { get; set; } + #endregion + + public MainViewModel(IContainerProvider containerProvider) : base(containerProvider) + { + LoadedCommand = new DelegateCommand(OnLoaded); + _eventAggregator.GetEvent().Subscribe(OnCellExpandRequested); + } + + #region 命令处理与事件 + private void OnLoaded() + { + if (IsInitialized) return; + for (int i = 1; i <= 9; i++) + { + var parameters = new NavigationParameters { { "Name", $"TestCell{i}" } }; + _regionManager.RequestNavigate($"TestCell{i}", "ProtocolStartView", parameters); + } + IsInitialized = true; + } + + // 不再物理搬迁视图,只需修改一个字符串属性 ExpandedCellName, + // XAML 里每个单元的 Style.Triggers 会根据该值处理隐藏 / 跨越 3x3。 + private void OnCellExpandRequested(string cellName) + { + ExpandedCellName = cellName ?? string.Empty; + } + #endregion + + #region 重写 + public override bool IsNavigationTarget(NavigationContext navigationContext) + { + // 之前帮你改好的单例复用逻辑(防止重复 new 和初始化视图) + return true; + } + #endregion + } +} \ No newline at end of file diff --git a/MainModule/ViewModels/ProtocolStartViewModel.cs b/MainModule/ViewModels/ProtocolStartViewModel.cs new file mode 100644 index 0000000..2c12011 --- /dev/null +++ b/MainModule/ViewModels/ProtocolStartViewModel.cs @@ -0,0 +1,109 @@ +using Prism.Navigation.Regions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Media; +using UIShare.ViewModelBase; + +namespace MainModule.ViewModels +{ + public class ProtocolStartViewModel : NavigateViewModelBase + { + #region 私有字段 + private string _testStatus; + private string _moduleColor; + #endregion + + #region 属性 + public string TestStatus + { + get => _testStatus; + set => SetProperty(ref _testStatus, value); + } + + public string ModuleColor + { + get => _moduleColor; + set => SetProperty(ref _moduleColor, value); + } + #endregion + + #region 命令 + public DelegateCommand StartProtocolCommand { get; } + #endregion + + public ProtocolStartViewModel(IContainerProvider containerProvider) : base(containerProvider) + { + StartProtocolCommand = new DelegateCommand(OnStart); + } + + #region 命令处理与事件 + private void OnStart() + { + // 只在当前格子所属的 Cell Region 内完成切换,不会影响其他格子位置 + SwitchNavigate("AutomatedTestingView"); + } + + /// + /// 定位当前视图所在的 Cell Region(TestCell1..TestCell9), + /// 在同一个 Region 内完成跳转。Region 位置由 XAML Grid 锁定,永远不会错位。 + /// + public void SwitchNavigate(string viewName) + { + // 1. 反向查找:哪个 Cell Region 当前托着“我”这个 ProtocolStartView + for (int i = 1; i <= 9; i++) + { + var regionName = $"TestCell{i}"; + if (!_regionManager.Regions.ContainsRegionWithName(regionName)) continue; + + var region = _regionManager.Regions[regionName]; + var myView = region.Views + .OfType() + .FirstOrDefault(v => v.DataContext == this); + + if (myView == null) continue; + + // 2. 透传名称与颜色参数,使 AutomatedTestingViewModel 能正确初始化 + var parameters = new NavigationParameters(); + parameters.Add("Name", TestStatus); + parameters.Add("Color", ModuleColor); + + // 3. 在本格子 Region 内请求导航,导航成功后再移除旧的 ProtocolStartView 以释放资源 + _regionManager.RequestNavigate(regionName, viewName, navResult => + { + if (navResult.Success == true) + { + region.Remove(myView); + } + }, parameters); + + return; + } + } + #endregion + + #region 重写 + public override bool IsNavigationTarget(NavigationContext navigationContext) + { + if (navigationContext.Parameters.ContainsKey("Name")) + return TestStatus == navigationContext.Parameters.GetValue("Name"); + return true; + } + + public override void OnNavigatedTo(NavigationContext navigationContext) + { + base.OnNavigatedTo(navigationContext); + + // 接收导航传参:名称与颜色 + if (navigationContext.Parameters.ContainsKey("Name")) + TestStatus = navigationContext.Parameters.GetValue("Name"); + + if (navigationContext.Parameters.ContainsKey("Color")) + ModuleColor = navigationContext.Parameters.GetValue("Color"); + } + #endregion + } +} \ No newline at end of file diff --git a/MainModule/Views/AutomatedTestingView.xaml b/MainModule/Views/AutomatedTestingView.xaml new file mode 100644 index 0000000..786cd78 --- /dev/null +++ b/MainModule/Views/AutomatedTestingView.xaml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MainModule/Views/AutomatedTestingView.xaml.cs b/MainModule/Views/AutomatedTestingView.xaml.cs new file mode 100644 index 0000000..3d56642 --- /dev/null +++ b/MainModule/Views/AutomatedTestingView.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace MainModule.Views +{ + /// + /// AutomatedTestingView.xaml 的交互逻辑 + /// + public partial class AutomatedTestingView : UserControl + { + public AutomatedTestingView() + { + InitializeComponent(); + } + } +} diff --git a/MainModule/Views/MainView.xaml b/MainModule/Views/MainView.xaml new file mode 100644 index 0000000..1b411b6 --- /dev/null +++ b/MainModule/Views/MainView.xaml @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MainModule/Views/MainView.xaml.cs b/MainModule/Views/MainView.xaml.cs new file mode 100644 index 0000000..f8052f9 --- /dev/null +++ b/MainModule/Views/MainView.xaml.cs @@ -0,0 +1,19 @@ +using MainModule.ViewModels; +using System.Windows; +using System.Windows.Controls; + +namespace MainModule.Views +{ + /// + /// MainView.xaml 的交互逻辑 + /// + public partial class MainView : UserControl + { + public MainView() + { + InitializeComponent(); + } + + + } +} diff --git a/MainModule/Views/ProtocolStartView.xaml b/MainModule/Views/ProtocolStartView.xaml new file mode 100644 index 0000000..d3362bc --- /dev/null +++ b/MainModule/Views/ProtocolStartView.xaml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MainModule/Views/ProtocolStartView.xaml.cs b/MainModule/Views/ProtocolStartView.xaml.cs new file mode 100644 index 0000000..2dca76e --- /dev/null +++ b/MainModule/Views/ProtocolStartView.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace MainModule.Views +{ + /// + /// ProtocolStartView.xaml 的交互逻辑 + /// + public partial class ProtocolStartView : UserControl + { + public ProtocolStartView() + { + InitializeComponent(); + } + } +} diff --git a/Model/Entity/BaseEntity.cs b/Model/Entity/BaseEntity.cs new file mode 100644 index 0000000..8f6f7b0 --- /dev/null +++ b/Model/Entity/BaseEntity.cs @@ -0,0 +1,27 @@ +using SqlSugar; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Model.Entity +{ + public abstract class BaseEntity + { + /// + /// 主键Id + /// + [SugarColumn(ColumnName = "id", ColumnDescription = "主键Id", IsPrimaryKey = true, CreateTableFieldSort = 0)] + public virtual long Id { get; set; } + + /// + /// 删除状态 + /// + [SugarColumn(ColumnName = "IsDel", ColumnDescription = "删除状态(0、未删除;1、已删除)", ColumnDataType = "tinyint", DefaultValue = "0", CreateTableFieldSort = 106)] + public virtual byte IsDel { get; set; } + + [SugarColumn(ColumnName = "CreateTime")] + public DateTime CreateTime { get; set; } + } +} diff --git a/Model/Model.csproj b/Model/Model.csproj new file mode 100644 index 0000000..48d5f57 --- /dev/null +++ b/Model/Model.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/Model/Result.cs b/Model/Result.cs new file mode 100644 index 0000000..6582ea0 --- /dev/null +++ b/Model/Result.cs @@ -0,0 +1,180 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Model +{ + public class Result + { + public Result() + { + + } + private int _code; + private readonly string _msg; + + /// + /// 结果是否成功 + /// + public bool IsSuccess => _code == 0; + + /// + /// 结果代码 + /// + public int Code { set => _code = value; get => _code; } + + /// + /// 结果消息 + /// + public string Msg => _msg; + + /// + /// 构造方法 + /// + /// + /// + /// + protected Result(int code, string msg, Exception? exception = null) + { + if (exception != null) + { + var listMoreMsg = new List(); + if (exception is ResultException resultException) + { + listMoreMsg.AddRange(resultException.MessageList); + } + //else if (exception is DeviceControlException deviceControlException) + //{ + // listMoreMsg.Add(deviceControlException.ErrorInfo); + //} + else + { + listMoreMsg.Add(exception.Message); + } + var strMoreMsg = string.Join("、", listMoreMsg.Where(it => !string.IsNullOrWhiteSpace(it))); + if (!string.IsNullOrWhiteSpace(strMoreMsg)) + { + msg += $"({strMoreMsg})"; + } + } + _code = code; + _msg = msg; + } + + /// + /// 返回成功结果 + /// + /// + /// + public static Result Success() + { + return new Result(0, ""); + } + + /// + /// 返回错误结果 + /// + /// + /// + /// + public static Result Error(string msg, Exception? exception = null) + { + return new Result(-1, msg, exception); + } + + + } + + public class Result : Result + { + public Result() + { + + } + private T? _data; + + /// + /// 结果数据 + /// + public T? Data { set => _data = value; get => _data; } + + /// + /// 构造方法 + /// + /// + /// + /// + /// + private Result(int code, string msg, T? data, Exception? exception = null) : base(code, msg, exception) + { + _data = data; + } + + /// + /// 返回成功数据 + /// + /// + /// + public static Result Success(T data) + { + return new Result(0, "", data); + } + + /// + /// 返回失败信息 + /// + /// + /// + /// + public new static Result Error(string msg, Exception? exception = null) + { + return new Result(-1, msg, default, exception); + } + + /// + /// 返回失败信息和数据 + /// + /// + /// + /// + /// + public static Result Error(string msg, T data, Exception? exception = null) + { + return new Result(-1, msg, data, exception); + } + + + } + + public class ResultException : Exception + { + private List? _messageList; + + public List MessageList + { + get + { + List messages = new(); + if (_messageList != null) + { + messages.AddRange(_messageList.Where(it => !string.IsNullOrWhiteSpace(it))); + } + return messages; + } + } + + public override string Message => string.Join(", ", MessageList); + + public ResultException() : base(null) + { + } + + public void AddAdditionMessage(string message) + { + _messageList ??= new(); + _messageList.Add(message); + } + } +} diff --git a/MonitorModule.txt b/MonitorModule.txt new file mode 100644 index 0000000..e69de29 diff --git a/MonitorModule/MonitorModule.cs b/MonitorModule/MonitorModule.cs new file mode 100644 index 0000000..5099bf7 --- /dev/null +++ b/MonitorModule/MonitorModule.cs @@ -0,0 +1,21 @@ + +using MonitorModule.Views; +using System.Reflection; + +namespace MonitorModule +{ + public class MonitorModule: IModule + { + public void OnInitialized(IContainerProvider containerProvider) + { + IRegionManager regionManager = containerProvider.Resolve(); + } + + public void RegisterTypes(IContainerRegistry containerRegistry) + { + containerRegistry.RegisterForNavigation("MonitorView"); + containerRegistry.RegisterForNavigation("RecordView"); + } + } + +} diff --git a/MonitorModule/MonitorModule.csproj b/MonitorModule/MonitorModule.csproj new file mode 100644 index 0000000..45b0b00 --- /dev/null +++ b/MonitorModule/MonitorModule.csproj @@ -0,0 +1,18 @@ + + + + net8.0-windows + enable + true + enable + + + + + + + + + + + diff --git a/MonitorModule/ViewModels/MonitorViewModel.cs b/MonitorModule/ViewModels/MonitorViewModel.cs new file mode 100644 index 0000000..f04732b --- /dev/null +++ b/MonitorModule/ViewModels/MonitorViewModel.cs @@ -0,0 +1,330 @@ +using OxyPlot; +using OxyPlot.Axes; +using OxyPlot.Legends; +using OxyPlot.Series; +using System.Collections.ObjectModel; +using System.Windows.Input; +using System.Windows.Threading; +using UIShare.GlobalVariable; +using UIShare.PubEvent; +using UIShare.ViewModelBase; + +namespace MonitorModule.ViewModels +{ + /// + /// 监控界面 VM —— 基于 OxyPlot 的曲线监控壳。 + /// 当前阶段不接入真实数据源,仅搭好框架: + /// - 添加信号 / 删除信号 + /// - 重置视图(按数据范围复原) + /// - 刷新(重绘 PlotView) + /// 仍延续 ScopedContext 隔离 + 双击展开 的范式,每个工位独立一份图表与信号列表。 + /// + public class MonitorViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable + { + #region 私有字段 + private readonly IScopedProvider _scope; + // 颜色轮转池,给新加的信号自动分配区分色 + private static readonly OxyColor[] _palette = + { + OxyColors.SteelBlue, OxyColors.IndianRed, OxyColors.SeaGreen, + OxyColors.DarkOrange, OxyColors.MediumPurple, OxyColors.Goldenrod, + OxyColors.Teal, OxyColors.Crimson, OxyColors.OliveDrab + }; + private int _signalCounter; + + // ===== 模拟数据相关 ===== + // 模拟定时器:周期性给每条信号追加新点,形成滚动效果 + private readonly DispatcherTimer _simTimer; + // 全局采样时间(秒),每 tick 自增 _simInterval + private double _simT; + // 采样间隔(秒),与 DispatcherTimer.Interval 保持一致 + private const double _simInterval = 0.1; + // 滚动窗口大小:每条曲线最多保留 200 个点(约 20 秒) + private const int _maxPoints = 200; + // 用于 random walk 的随机源 + private static readonly Random _rng = new(); + #endregion + + #region 隔离 / 标题 + public bool KeepAlive => true; + public ScopedContext _scopedContext { get; } + public GlobalInfo GlobalInfoRef { get; } + + private string _testStatus = string.Empty; + public string TestStatus + { + get => _testStatus; + set => SetProperty(ref _testStatus, value); + } + #endregion + + #region OxyPlot + /// PlotView 直接绑这个 PlotModel + public PlotModel Plot { get; } + + /// 当前已添加的信号集合(左侧列表展示) + public ObservableCollection Signals { get; } = new(); + + private SignalItem? _selectedSignal; + public SignalItem? SelectedSignal + { + get => _selectedSignal; + set => SetProperty(ref _selectedSignal, value); + } + + private string _statusMessage = "图表已就绪,暂无信号"; + public string StatusMessage + { + get => _statusMessage; + set => SetProperty(ref _statusMessage, value); + } + #endregion + + #region 命令 + public ICommand AddSignalCommand { get; } + public ICommand DeleteSignalCommand { get; } + public ICommand ResetViewCommand { get; } + public ICommand RefreshDataCommand { get; } + // 双击展开/折叠:与 RecordView/AutomatedTestingView 共用同一套 ExpandViewEvent + public ICommand RefreshCommand { get; } + #endregion + + public MonitorViewModel(IContainerExtension container) : base(container) + { + _scope = container.CreateScope(); + GlobalInfoRef = container.Resolve(); + _scopedContext = _scope.Resolve(); + + Plot = BuildEmptyPlot(); + + AddSignalCommand = new DelegateCommand(OnAddSignal); + DeleteSignalCommand = new DelegateCommand(OnDeleteSignal); + ResetViewCommand = new DelegateCommand(OnResetView); + RefreshDataCommand = new DelegateCommand(OnRefreshData); + RefreshCommand = new DelegateCommand(OnExpand); + + // 启动模拟数据定时器:100ms 一帧,给每条信号喂一个新点 + _simTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(_simInterval) }; + _simTimer.Tick += OnSimTick; + _simTimer.Start(); + + // 默认预置 3 条模拟信号,便于直接看到滚动效果 + OnAddSignal(); // sin + OnAddSignal(); // cos + OnAddSignal(); // random walk + } + + public void Dispose() + { + _simTimer?.Stop(); + _scope?.Dispose(); + } + + #region PlotModel 构建 + private static PlotModel BuildEmptyPlot() + { + var pm = new PlotModel + { + Title = string.Empty, + PlotAreaBorderColor = OxyColors.LightGray, + Background = OxyColors.White + }; + pm.Axes.Add(new LinearAxis + { + Position = AxisPosition.Bottom, + Title = "X", + MajorGridlineStyle = LineStyle.Dot, + MinorGridlineStyle = LineStyle.None + }); + pm.Axes.Add(new LinearAxis + { + Position = AxisPosition.Left, + Title = "Y", + MajorGridlineStyle = LineStyle.Dot, + MinorGridlineStyle = LineStyle.None + }); + pm.Legends.Add(new Legend + { + LegendPosition = LegendPosition.RightTop, + LegendBackground = OxyColor.FromAColor(200, OxyColors.White), + LegendBorder = OxyColors.LightGray + }); + return pm; + } + #endregion + + #region 命令处理 + /// + /// 添加一条信号:按 _signalCounter 轮转选择不同波形 generator, + /// 后续 _simTimer 每帧会调用 generator(t) 给曲线追加新点形成滚动效果。 + /// + private void OnAddSignal() + { + _signalCounter++; + var color = _palette[(_signalCounter - 1) % _palette.Length]; + + // 6 种波形轮转:sin / cos / 锯齿 / 方波 / 衰减正弦 / random walk + var (waveName, generator) = BuildGenerator(_signalCounter); + var name = $"{waveName}_{_signalCounter}"; + + var series = new LineSeries + { + Title = name, + Color = color, + StrokeThickness = 1.5 + }; + + var item = new SignalItem(name, series, generator); + Signals.Add(item); + SelectedSignal = item; + + Plot.Series.Add(series); + Plot.InvalidatePlot(true); + + StatusMessage = $"已添加信号 [{name}],当前共 {Signals.Count} 条"; + } + + /// + /// 按索引返回一种模拟波形发生器。 + /// 振幅 / 频率 / 相位都做了区分,让多条曲线视觉上分开。 + /// + private static (string waveName, Func generator) BuildGenerator(int index) + { + // 给同种波形不同实例一些随机偏移,避免完全重叠 + double phase = (index * 0.7) % (2 * Math.PI); + double amp = 1.0 + (index % 3) * 0.3; + double freq = 0.5 + (index % 4) * 0.2; + + return (index % 6) switch + { + 0 => ("Sin", t => amp * Math.Sin(2 * Math.PI * freq * t + phase)), + 1 => ("Cos", t => amp * Math.Cos(2 * Math.PI * freq * t + phase)), + 2 => ("Saw", t => amp * (2 * ((t * freq) - Math.Floor(t * freq + 0.5)))), + 3 => ("Square", t => amp * Math.Sign(Math.Sin(2 * Math.PI * freq * t + phase))), + 4 => ("Decay", t => amp * Math.Exp(-0.05 * t) * Math.Sin(2 * Math.PI * freq * t + phase)), + _ => RandomWalkGenerator(amp), + }; + } + + /// 构造一个随机游走 generator:在前一次值基础上叠加高斯噪声。 + private static (string, Func) RandomWalkGenerator(double amp) + { + double last = 0; + return ("Walk", _ => + { + last += (_rng.NextDouble() - 0.5) * 0.2 * amp; + // 软约束在 [-amp*3, amp*3],避免一直跑偏 + if (last > amp * 3) last = amp * 3; + if (last < -amp * 3) last = -amp * 3; + return last; + }); + } + + /// 删除当前选中信号;若未选中则删除最后一条。 + private void OnDeleteSignal() + { + var target = SelectedSignal ?? Signals.LastOrDefault(); + if (target == null) + { + StatusMessage = "无可删除的信号"; + return; + } + + Plot.Series.Remove(target.Series); + Signals.Remove(target); + SelectedSignal = Signals.LastOrDefault(); + + Plot.InvalidatePlot(true); + StatusMessage = $"已删除信号 [{target.Name}],剩余 {Signals.Count} 条"; + } + + /// 按数据范围复原视图(重置所有坐标轴的缩放/平移)。 + private void OnResetView() + { + Plot.ResetAllAxes(); + Plot.InvalidatePlot(false); + StatusMessage = "视图已按数据范围复原"; + } + + /// 刷新:触发 PlotView 重绘;后续接数据源时可在此重拉数据。 + private void OnRefreshData() + { + Plot.InvalidatePlot(true); + StatusMessage = $"已刷新({DateTime.Now:HH:mm:ss})"; + } + + /// 双击展开 / 折叠九宫格。 + private void OnExpand() + { + if (string.IsNullOrEmpty(TestStatus)) return; + _eventAggregator.GetEvent().Publish(TestStatus); + GlobalInfoRef.CurrentScope = TestStatus; + } + + /// + /// 模拟数据 tick:每条信号按当前 _simT 算出新点并追加。 + /// 超过 _maxPoints 时丢弃最旧的点形成滚动窗口。 + /// + private void OnSimTick(object? sender, EventArgs e) + { + _simT += _simInterval; + + if (Signals.Count == 0) + { + return; + } + + foreach (var item in Signals) + { + double y = item.Generator(_simT); + item.Series.Points.Add(new DataPoint(_simT, y)); + if (item.Series.Points.Count > _maxPoints) + { + item.Series.Points.RemoveAt(0); + } + } + + // 让 X 轴跟着最新数据滚动 + var xAxis = Plot.Axes.FirstOrDefault(a => a.Position == AxisPosition.Bottom); + if (xAxis != null) + { + double window = _maxPoints * _simInterval; + xAxis.Minimum = Math.Max(0, _simT - window); + xAxis.Maximum = _simT + 0.5; + } + + Plot.InvalidatePlot(true); + } + #endregion + + #region 导航 + public override void OnNavigatedTo(NavigationContext navigationContext) + { + base.OnNavigatedTo(navigationContext); + if (navigationContext.Parameters.ContainsKey("Name")) + { + TestStatus = navigationContext.Parameters.GetValue("Name"); + Plot.Title = $"监控 - {TestStatus}"; + Plot.InvalidatePlot(false); + } + } + #endregion + } + + /// + /// 信号列表项:UI 显示名 + 对应的 OxyPlot LineSeries 引用 + 模拟数据 generator。 + /// generator(t) 接收当前模拟时间,返回该时刻 y 值。 + /// + public class SignalItem + { + public string Name { get; } + public LineSeries Series { get; } + public Func Generator { get; } + public SignalItem(string name, LineSeries series, Func generator) + { + Name = name; + Series = series; + Generator = generator; + } + } +} diff --git a/MonitorModule/ViewModels/RecordViewModel.cs b/MonitorModule/ViewModels/RecordViewModel.cs new file mode 100644 index 0000000..d11bf99 --- /dev/null +++ b/MonitorModule/ViewModels/RecordViewModel.cs @@ -0,0 +1,362 @@ +using Logger; +using SqlSugar; +using System.Collections.ObjectModel; +using System.Data; +using System.IO; +using System.Text; +using System.Windows.Input; +using UIShare.GlobalVariable; +using UIShare.PubEvent; +using UIShare.ViewModelBase; + +namespace MonitorModule.ViewModels +{ + /// + /// 记录界面:用于查看运行目录下 SQL/ADP.db 中的数据。 + /// 功能:表选择 / 关键字 WHERE 查询 / 分页 / 导出 CSV。 + /// 仍延续 ScopedContext 隔离 + 双击展开 的范式,每个工位独立一份查询状态。 + /// + public class RecordViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable + { + #region 私有字段 + // 数据库相对路径:运行目录\SQL\ADP.db(数据库未就绪时容错处理,不抛异常) + private static readonly string DbFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SQL"); + private static readonly string DbPath = Path.Combine(DbFolder, "ADP.db"); + private static readonly string ConnStr = $"Data Source={DbPath};Version=3;"; + + private readonly IScopedProvider _scope; + #endregion + + #region 隔离演示属性(保留 ScopedContext 验证用) + public bool KeepAlive => true; + public ScopedContext _scopedContext { get; } + // 公开一份 GlobalInfo 供双击展开逻辑使用(基类的 _globalInfo 是 private) + public GlobalInfo GlobalInfoRef { get; } + #endregion + + #region 顶部工位标题 + private string _testStatus = string.Empty; + public string TestStatus + { + get => _testStatus; + set => SetProperty(ref _testStatus, value); + } + #endregion + + #region 数据库 / 表 / 查询条件 + private ObservableCollection _tableNames = new(); + public ObservableCollection TableNames + { + get => _tableNames; + set => SetProperty(ref _tableNames, value); + } + + private string? _selectedTable; + public string? SelectedTable + { + get => _selectedTable; + set + { + if (SetProperty(ref _selectedTable, value)) + { + PageIndex = 1; + Query(); + } + } + } + + // 用户填写的 WHERE 子句(不带 WHERE 关键字),例如:Status='OK' AND Id>10 + private string _whereClause = string.Empty; + public string WhereClause + { + get => _whereClause; + set => SetProperty(ref _whereClause, value); + } + + private DataTable _resultTable = new(); + public DataTable ResultTable + { + get => _resultTable; + set => SetProperty(ref _resultTable, value); + } + + private string _statusMessage = "未连接"; + public string StatusMessage + { + get => _statusMessage; + set => SetProperty(ref _statusMessage, value); + } + #endregion + + #region 分页 + private int _pageIndex = 1; + public int PageIndex + { + get => _pageIndex; + set => SetProperty(ref _pageIndex, value); + } + + private int _pageSize = 50; + public int PageSize + { + get => _pageSize; + set + { + if (SetProperty(ref _pageSize, value <= 0 ? 50 : value)) + { + RaisePropertyChanged(nameof(TotalPages)); + PageIndex = 1; + Query(); + } + } + } + + private long _totalCount; + public long TotalCount + { + get => _totalCount; + set + { + SetProperty(ref _totalCount, value); + RaisePropertyChanged(nameof(TotalPages)); + } + } + + public int TotalPages + { + get + { + if (PageSize <= 0) return 1; + var pages = (int)((TotalCount + PageSize - 1) / PageSize); + return pages <= 0 ? 1 : pages; + } + } + #endregion + + #region 命令 + public ICommand LoadedCommand { get; } + public ICommand RefreshTablesCommand { get; } + public ICommand QueryCommand { get; } + public ICommand FirstPageCommand { get; } + public ICommand PrevPageCommand { get; } + public ICommand NextPageCommand { get; } + public ICommand LastPageCommand { get; } + public ICommand ExportCsvCommand { get; } + // 双击展开/折叠:与 MonitorView/AutomatedTestingView 共用 + public ICommand RefreshCommand { get; } + #endregion + + public RecordViewModel(IContainerExtension container) : base(container) + { + _scope = container.CreateScope(); + GlobalInfoRef = container.Resolve(); + _scopedContext = _scope.Resolve(); + + LoadedCommand = new DelegateCommand(LoadTables); + RefreshTablesCommand = new DelegateCommand(LoadTables); + QueryCommand = new DelegateCommand(() => { PageIndex = 1; Query(); }); + FirstPageCommand = new DelegateCommand(() => { if (PageIndex > 1) { PageIndex = 1; Query(); } }); + PrevPageCommand = new DelegateCommand(() => { if (PageIndex > 1) { PageIndex--; Query(); } }); + NextPageCommand = new DelegateCommand(() => { if (PageIndex < TotalPages) { PageIndex++; Query(); } }); + LastPageCommand = new DelegateCommand(() => { if (PageIndex < TotalPages) { PageIndex = TotalPages; Query(); } }); + ExportCsvCommand = new DelegateCommand(ExportCsv); + RefreshCommand = new DelegateCommand(OnExpand); + } + + public void Dispose() + { + _scope?.Dispose(); + } + + #region 数据库操作 + private SqlSugarClient CreateClient() + { + return new SqlSugarClient(new ConnectionConfig + { + DbType = SqlSugar.DbType.Sqlite, + ConnectionString = ConnStr, + IsAutoCloseConnection = true, + InitKeyType = InitKeyType.Attribute + }); + } + + /// + /// 读取数据库中的所有用户表。数据库不存在时不报错,仅在状态栏提示。 + /// + private void LoadTables() + { + try + { + if (!Directory.Exists(DbFolder)) Directory.CreateDirectory(DbFolder); + if (!File.Exists(DbPath)) + { + TableNames = new ObservableCollection(); + SelectedTable = null; + ResultTable = new DataTable(); + TotalCount = 0; + StatusMessage = $"数据库尚未创建:{DbPath}"; + return; + } + + using var db = CreateClient(); + var tables = db.DbMaintenance.GetTableInfoList(false) + .Select(t => t.Name) + .OrderBy(n => n) + .ToList(); + + TableNames = new ObservableCollection(tables); + if (tables.Count == 0) + { + SelectedTable = null; + ResultTable = new DataTable(); + TotalCount = 0; + StatusMessage = "数据库已连接,但暂无数据表"; + } + else + { + StatusMessage = $"已连接:{DbPath}({tables.Count} 张表)"; + if (string.IsNullOrEmpty(SelectedTable) || !tables.Contains(SelectedTable)) + { + SelectedTable = tables[0]; // setter 会触发 Query + } + else + { + Query(); + } + } + } + catch (Exception ex) + { + LoggerHelper.ErrorWithNotify($"加载数据表失败:{ex.Message}"); + StatusMessage = $"加载失败:{ex.Message}"; + } + } + + /// + /// 执行分页查询。WHERE 为空则查询全部。 + /// + private void Query() + { + if (string.IsNullOrEmpty(SelectedTable) || !File.Exists(DbPath)) + { + ResultTable = new DataTable(); + TotalCount = 0; + return; + } + + try + { + using var db = CreateClient(); + string table = $"[{SelectedTable}]"; + string where = string.IsNullOrWhiteSpace(WhereClause) ? "1=1" : WhereClause; + + // COUNT 总数 + var countSql = $"SELECT COUNT(*) FROM {table} WHERE {where}"; + TotalCount = db.Ado.GetLong(countSql); + + // 修正越界 + if (PageIndex > TotalPages) PageIndex = TotalPages; + if (PageIndex < 1) PageIndex = 1; + + int offset = (PageIndex - 1) * PageSize; + var dataSql = $"SELECT * FROM {table} WHERE {where} LIMIT {PageSize} OFFSET {offset}"; + ResultTable = db.Ado.GetDataTable(dataSql); + + StatusMessage = $"表 [{SelectedTable}] 共 {TotalCount} 行 第 {PageIndex}/{TotalPages} 页"; + } + catch (Exception ex) + { + LoggerHelper.ErrorWithNotify($"查询失败:{ex.Message}"); + StatusMessage = $"查询失败:{ex.Message}"; + ResultTable = new DataTable(); + TotalCount = 0; + } + } + #endregion + + #region 导出 CSV + /// + /// 按当前 WHERE 条件导出全部结果到 CSV(不分页)。 + /// + private void ExportCsv() + { + if (string.IsNullOrEmpty(SelectedTable) || !File.Exists(DbPath)) + { + StatusMessage = "无可导出数据"; + return; + } + + var dlg = new Microsoft.Win32.SaveFileDialog + { + Filter = "CSV 文件 (*.csv)|*.csv|所有文件|*.*", + FileName = $"{SelectedTable}_{DateTime.Now:yyyyMMdd_HHmmss}.csv", + Title = $"导出 [{SelectedTable}] 为 CSV" + }; + if (dlg.ShowDialog() != true) return; + + try + { + using var db = CreateClient(); + string table = $"[{SelectedTable}]"; + string where = string.IsNullOrWhiteSpace(WhereClause) ? "1=1" : WhereClause; + var sql = $"SELECT * FROM {table} WHERE {where}"; + var dt = db.Ado.GetDataTable(sql); + + WriteCsv(dlg.FileName, dt); + + StatusMessage = $"导出完成:{dlg.FileName}({dt.Rows.Count} 行)"; + LoggerHelper.InfoWithNotify($"工位 [{TestStatus}] 导出 [{SelectedTable}] 至 {dlg.FileName},共 {dt.Rows.Count} 行"); + } + catch (Exception ex) + { + LoggerHelper.ErrorWithNotify($"导出失败:{ex.Message}"); + StatusMessage = $"导出失败:{ex.Message}"; + } + } + + private static void WriteCsv(string path, DataTable dt) + { + // 写 UTF-8 BOM 让 Excel 直接识别中文 + using var sw = new StreamWriter(path, false, new UTF8Encoding(true)); + // 表头 + sw.WriteLine(string.Join(",", dt.Columns.Cast().Select(c => Escape(c.ColumnName)))); + // 行 + foreach (DataRow row in dt.Rows) + { + sw.WriteLine(string.Join(",", row.ItemArray.Select(v => Escape(v?.ToString() ?? string.Empty)))); + } + } + + private static string Escape(string field) + { + if (field.Contains('"') || field.Contains(',') || field.Contains('\r') || field.Contains('\n')) + { + return "\"" + field.Replace("\"", "\"\"") + "\""; + } + return field; + } + #endregion + + #region 双击展开 + private void OnExpand() + { + if (string.IsNullOrEmpty(TestStatus)) return; + _eventAggregator.GetEvent().Publish(TestStatus); + GlobalInfoRef.CurrentScope = TestStatus; + } + #endregion + + #region 导航 + public override void OnNavigatedTo(NavigationContext navigationContext) + { + base.OnNavigatedTo(navigationContext); + if (navigationContext.Parameters.ContainsKey("Name")) + { + TestStatus = navigationContext.Parameters.GetValue("Name"); + } + // 进入界面时自动加载表 + LoadTables(); + } + #endregion + } +} diff --git a/MonitorModule/Views/MonitorViewView.xaml b/MonitorModule/Views/MonitorViewView.xaml new file mode 100644 index 0000000..087152e --- /dev/null +++ b/MonitorModule/Views/MonitorViewView.xaml @@ -0,0 +1,236 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +