From a10cb30c4a0b9693f187fa30b87fd2edcf7edacf Mon Sep 17 00:00:00 2001 From: Hikaru Chang Date: Fri, 2 Jan 2026 19:20:35 +0900 Subject: [PATCH] first commit --- .gitignore | 61 + CMakeLists.txt | 58 + CalibratorLauncher.pro | 103 + config/launcher_config.json | 451 ++ hardware/channelmanager.cpp | 349 ++ hardware/channelmanager.h | 137 + icons/app.png | 25 + icons/arrow_down.svg | 4 + icons/arrow_up.svg | 4 + icons/ic_ac_voltage.svg | 18 + icons/ic_data.svg | 18 + icons/ic_dc_current.svg | 17 + icons/ic_dc_voltage.svg | 16 + icons/ic_dual_channel.svg | 24 + icons/ic_frequency.svg | 17 + icons/ic_insulation.svg | 19 + icons/ic_network_test.svg | 18 + icons/ic_ramp.svg | 17 + icons/ic_rcp63.svg | 19 + icons/ic_ripple.svg | 19 + icons/ic_rtd.svg | 21 + icons/ic_rtd_output.svg | 23 + icons/ic_settings.svg | 26 + icons/ic_sop.svg | 21 + icons/ic_switch.svg | 18 + icons/ic_thermocouple.svg | 19 + icons/ic_trim.svg | 20 + icons/ic_waveform.svg | 20 + icons/ic_wireless.svg | 21 + main.cpp | 39 + mainwindow.cpp | 457 ++ mainwindow.h | 105 + procedure/functionregistry.cpp | 479 +++ procedure/functionregistry.h | 84 + procedure/proceduredata.h | 319 ++ procedure/procedureengine.cpp | 865 ++++ procedure/procedureengine.h | 172 + procedure/proceduremanager.cpp | 655 +++ procedure/proceduremanager.h | 58 + procedure/procedureparser.cpp | 1594 +++++++ procedure/procedureparser.h | 83 + procedures/FUNCTION_GUIDE.md | 623 +++ procedures/README_MOCK_DATA.md | 96 + procedures/mock_procedures.json | 367 ++ procedures/rcp63.json | 6009 +++++++++++++++++++++++++++ procedures/simple_rcp63.yaml | 700 ++++ procedures/simple_test.yaml | 59 + resources.qrc | 37 + run.sh | 13 + schema.json | 602 +++ utils/configmanager.cpp | 382 ++ utils/configmanager.h | 37 + utils/stylehelper.cpp | 286 ++ utils/stylehelper.h | 32 + widgets/appicon.cpp | 283 ++ widgets/appicon.h | 74 + widgets/channelstatuswidget.cpp | 252 ++ widgets/channelstatuswidget.h | 98 + widgets/datamanagementwidget.cpp | 1074 +++++ widgets/datamanagementwidget.h | 94 + widgets/devicestatuswidget.cpp | 172 + widgets/devicestatuswidget.h | 40 + widgets/dockbar.cpp | 142 + widgets/dockbar.h | 34 + widgets/dualchannelwidget.cpp | 533 +++ widgets/dualchannelwidget.h | 78 + widgets/launcherpage.cpp | 207 + widgets/launcherpage.h | 63 + widgets/networktestwidget.cpp | 470 +++ widgets/networktestwidget.h | 72 + widgets/overlaydialogswidget.cpp | 322 ++ widgets/overlaydialogswidget.h | 81 + widgets/pageindicator.cpp | 116 + widgets/pageindicator.h | 37 + widgets/procedurelistwidget.cpp | 224 + widgets/procedurelistwidget.h | 61 + widgets/procedureplayerwidget.cpp | 697 ++++ widgets/procedureplayerwidget.h | 158 + widgets/settingswidget.cpp | 583 +++ widgets/settingswidget.h | 87 + widgets/signalmeasurementwidget.cpp | 1760 ++++++++ widgets/signalmeasurementwidget.h | 483 +++ widgets/signaltrimwidget.cpp | 618 +++ widgets/signaltrimwidget.h | 81 + widgets/statusbar.cpp | 156 + widgets/statusbar.h | 41 + widgets/tableoverlaywidget.cpp | 361 ++ widgets/tableoverlaywidget.h | 111 + widgets/tablewidgetfactory.cpp | 1407 +++++++ widgets/tablewidgetfactory.h | 200 + widgets/waveformwidget.cpp | 617 +++ widgets/waveformwidget.h | 95 + widgets/wirelesswidget.cpp | 790 ++++ widgets/wirelesswidget.h | 101 + 94 files changed, 28609 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 CalibratorLauncher.pro create mode 100644 config/launcher_config.json create mode 100644 hardware/channelmanager.cpp create mode 100644 hardware/channelmanager.h create mode 100644 icons/app.png create mode 100644 icons/arrow_down.svg create mode 100644 icons/arrow_up.svg create mode 100644 icons/ic_ac_voltage.svg create mode 100644 icons/ic_data.svg create mode 100644 icons/ic_dc_current.svg create mode 100644 icons/ic_dc_voltage.svg create mode 100644 icons/ic_dual_channel.svg create mode 100644 icons/ic_frequency.svg create mode 100644 icons/ic_insulation.svg create mode 100644 icons/ic_network_test.svg create mode 100644 icons/ic_ramp.svg create mode 100644 icons/ic_rcp63.svg create mode 100644 icons/ic_ripple.svg create mode 100644 icons/ic_rtd.svg create mode 100644 icons/ic_rtd_output.svg create mode 100644 icons/ic_settings.svg create mode 100644 icons/ic_sop.svg create mode 100644 icons/ic_switch.svg create mode 100644 icons/ic_thermocouple.svg create mode 100644 icons/ic_trim.svg create mode 100644 icons/ic_waveform.svg create mode 100644 icons/ic_wireless.svg create mode 100644 main.cpp create mode 100644 mainwindow.cpp create mode 100644 mainwindow.h create mode 100644 procedure/functionregistry.cpp create mode 100644 procedure/functionregistry.h create mode 100644 procedure/proceduredata.h create mode 100644 procedure/procedureengine.cpp create mode 100644 procedure/procedureengine.h create mode 100644 procedure/proceduremanager.cpp create mode 100644 procedure/proceduremanager.h create mode 100644 procedure/procedureparser.cpp create mode 100644 procedure/procedureparser.h create mode 100644 procedures/FUNCTION_GUIDE.md create mode 100644 procedures/README_MOCK_DATA.md create mode 100644 procedures/mock_procedures.json create mode 100644 procedures/rcp63.json create mode 100644 procedures/simple_rcp63.yaml create mode 100644 procedures/simple_test.yaml create mode 100644 resources.qrc create mode 100755 run.sh create mode 100644 schema.json create mode 100644 utils/configmanager.cpp create mode 100644 utils/configmanager.h create mode 100644 utils/stylehelper.cpp create mode 100644 utils/stylehelper.h create mode 100644 widgets/appicon.cpp create mode 100644 widgets/appicon.h create mode 100644 widgets/channelstatuswidget.cpp create mode 100644 widgets/channelstatuswidget.h create mode 100644 widgets/datamanagementwidget.cpp create mode 100644 widgets/datamanagementwidget.h create mode 100644 widgets/devicestatuswidget.cpp create mode 100644 widgets/devicestatuswidget.h create mode 100644 widgets/dockbar.cpp create mode 100644 widgets/dockbar.h create mode 100644 widgets/dualchannelwidget.cpp create mode 100644 widgets/dualchannelwidget.h create mode 100644 widgets/launcherpage.cpp create mode 100644 widgets/launcherpage.h create mode 100644 widgets/networktestwidget.cpp create mode 100644 widgets/networktestwidget.h create mode 100644 widgets/overlaydialogswidget.cpp create mode 100644 widgets/overlaydialogswidget.h create mode 100644 widgets/pageindicator.cpp create mode 100644 widgets/pageindicator.h create mode 100644 widgets/procedurelistwidget.cpp create mode 100644 widgets/procedurelistwidget.h create mode 100644 widgets/procedureplayerwidget.cpp create mode 100644 widgets/procedureplayerwidget.h create mode 100644 widgets/settingswidget.cpp create mode 100644 widgets/settingswidget.h create mode 100644 widgets/signalmeasurementwidget.cpp create mode 100644 widgets/signalmeasurementwidget.h create mode 100644 widgets/signaltrimwidget.cpp create mode 100644 widgets/signaltrimwidget.h create mode 100644 widgets/statusbar.cpp create mode 100644 widgets/statusbar.h create mode 100644 widgets/tableoverlaywidget.cpp create mode 100644 widgets/tableoverlaywidget.h create mode 100644 widgets/tablewidgetfactory.cpp create mode 100644 widgets/tablewidgetfactory.h create mode 100644 widgets/waveformwidget.cpp create mode 100644 widgets/waveformwidget.h create mode 100644 widgets/wirelesswidget.cpp create mode 100644 widgets/wirelesswidget.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fc52b68 --- /dev/null +++ b/.gitignore @@ -0,0 +1,61 @@ +# Build directories +build/ +release/ +debug/ + +# Qt generated files +*.moc +moc_*.cpp +moc_*.h +qrc_*.cpp +ui_*.h +*.qm + +# Object files +*.o +*.obj + +# Executables +*.app +*.exe +*.out + +# Qt Creator files +*.pro.user +*.pro.user.* +*.qbs.user +*.qbs.user.* +*.moc.depends +*.qmake.stash + +# Makefiles (generated by qmake) +Makefile +Makefile.* + +# macOS specific +.DS_Store +*.dSYM/ +*.dylib + +# Windows specific +*.dll +*.lib +*.pdb + +# Linux specific +*.so +*.so.* + +# IDE files +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# Logs +*.log + +# Temporary files +*.tmp +*.temp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..42151eb --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,58 @@ +cmake_minimum_required(VERSION 3.16) + +project(CalibratorLauncher VERSION 1.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Network) + +set(PROJECT_SOURCES + main.cpp + mainwindow.cpp + mainwindow.h + widgets/launcherpage.cpp + widgets/launcherpage.h + widgets/appicon.cpp + widgets/appicon.h + widgets/statusbar.cpp + widgets/statusbar.h + widgets/devicestatuswidget.cpp + widgets/devicestatuswidget.h + widgets/dockbar.cpp + widgets/dockbar.h + widgets/pageindicator.cpp + widgets/pageindicator.h + utils/configmanager.cpp + utils/configmanager.h + utils/stylehelper.cpp + utils/stylehelper.h + resources.qrc +) + +qt_add_executable(CalibratorLauncher + MANUAL_FINALIZATION + ${PROJECT_SOURCES} +) + +target_link_libraries(CalibratorLauncher PRIVATE + Qt6::Core + Qt6::Gui + Qt6::Widgets + Qt6::Network +) + +set_target_properties(CalibratorLauncher PROPERTIES + MACOSX_BUNDLE TRUE + WIN32_EXECUTABLE TRUE +) + +install(TARGETS CalibratorLauncher + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_finalize_executable(CalibratorLauncher) diff --git a/CalibratorLauncher.pro b/CalibratorLauncher.pro new file mode 100644 index 0000000..6a93c38 --- /dev/null +++ b/CalibratorLauncher.pro @@ -0,0 +1,103 @@ +QT += core gui widgets network + +greaterThan(QT_MAJOR_VERSION, 5): QT += core5compat + +CONFIG += c++17 + +TARGET = CalibratorLauncher +TEMPLATE = app + +# 编译输出目录设置 - 保持根目录整洁 +DESTDIR = $$PWD/build +OBJECTS_DIR = $$PWD/build/obj +MOC_DIR = $$PWD/build/moc +RCC_DIR = $$PWD/build/rcc +UI_DIR = $$PWD/build/ui + +# 应用程序信息 +VERSION = 1.0.0 +QMAKE_TARGET_COMPANY = "智能校验仪" +QMAKE_TARGET_PRODUCT = "CalibratorLauncher" +QMAKE_TARGET_DESCRIPTION = "智能校验仪启动器" +QMAKE_TARGET_COPYRIGHT = "Copyright 2025" + +# 源文件 +SOURCES += \ + main.cpp \ + mainwindow.cpp \ + widgets/launcherpage.cpp \ + widgets/appicon.cpp \ + widgets/statusbar.cpp \ + widgets/devicestatuswidget.cpp \ + widgets/dockbar.cpp \ + widgets/pageindicator.cpp \ + widgets/procedurelistwidget.cpp \ + widgets/procedureplayerwidget.cpp \ + widgets/signalmeasurementwidget.cpp \ + widgets/channelstatuswidget.cpp \ + widgets/tablewidgetfactory.cpp \ + widgets/tableoverlaywidget.cpp \ + widgets/overlaydialogswidget.cpp \ + widgets/settingswidget.cpp \ + widgets/datamanagementwidget.cpp \ + widgets/wirelesswidget.cpp \ + widgets/networktestwidget.cpp \ + widgets/waveformwidget.cpp \ + widgets/dualchannelwidget.cpp \ + widgets/signaltrimwidget.cpp \ + hardware/channelmanager.cpp \ + procedure/proceduremanager.cpp \ + procedure/procedureengine.cpp \ + procedure/procedureparser.cpp \ + procedure/functionregistry.cpp \ + utils/configmanager.cpp \ + utils/stylehelper.cpp + +HEADERS += \ + mainwindow.h \ + widgets/launcherpage.h \ + widgets/appicon.h \ + widgets/statusbar.h \ + widgets/devicestatuswidget.h \ + widgets/dockbar.h \ + widgets/pageindicator.h \ + widgets/procedurelistwidget.h \ + widgets/procedureplayerwidget.h \ + widgets/signalmeasurementwidget.h \ + widgets/channelstatuswidget.h \ + widgets/tablewidgetfactory.h \ + widgets/tableoverlaywidget.h \ + widgets/overlaydialogswidget.h \ + widgets/settingswidget.h \ + widgets/datamanagementwidget.h \ + widgets/wirelesswidget.h \ + widgets/networktestwidget.h \ + widgets/waveformwidget.h \ + widgets/dualchannelwidget.h \ + widgets/signaltrimwidget.h \ + hardware/channelmanager.h \ + procedure/proceduredata.h \ + procedure/proceduremanager.h \ + procedure/procedureengine.h \ + procedure/procedureparser.h \ + procedure/functionregistry.h \ + utils/configmanager.h \ + utils/stylehelper.h + +RESOURCES += \ + resources.qrc + +# ryml 库支持 (用于解析 YAML/JSON) +INCLUDEPATH += /opt/homebrew/Cellar/rapidyaml/0.10.0/include +LIBS += -L/opt/homebrew/Cellar/rapidyaml/0.10.0/lib -lryml -lc4core + +# 默认部署规则 +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target + +# Windows 图标 +# win32:RC_ICONS += icons/app.ico + +# macOS 图标 (需要 .icns 文件) +# macx:ICON = icons/app.icns diff --git a/config/launcher_config.json b/config/launcher_config.json new file mode 100644 index 0000000..af34371 --- /dev/null +++ b/config/launcher_config.json @@ -0,0 +1,451 @@ +{ + "name": "智能校验仪启动器", + "version": "1.0.0", + "description": "Android Launcher Style Design for Calibrator", + "resolution": { + "width": 1920, + "height": 1200 + }, + "colorSystem": { + "primary": "#2196F3", + "accent": "#FF9800", + "success": "#4CAF50", + "warning": "#FFC107", + "danger": "#F44336", + "background": "#F5F5F5", + "surface": "#FFFFFF", + "textPrimary": "#212121", + "textSecondary": "#757575" + }, + "pages": [ + { + "id": "page1", + "title": "信号输出和测量", + "apps": [ + { + "id": "rtd_measure", + "name": "热电阻测量", + "nameEn": "RTD Measure", + "icon": "ic_rtd", + "color": "#2196F3", + "gradient": [ + "#2196F3", + "#1976D2" + ], + "badge": "Ω", + "group": "rtd", + "description": "2/3/4线热电阻测量", + "features": [ + "测量2/3/4线热电阻阻值", + "自动计算线电阻(四线制)", + "自动转换为温度" + ] + }, + { + "id": "rtd_output", + "name": "热电阻输出", + "nameEn": "RTD Output", + "icon": "ic_rtd", + "color": "#1565C0", + "gradient": [ + "#1565C0", + "#0D47A1" + ], + "badge": "PT", + "group": "rtd", + "description": "PT100模拟输出", + "features": [ + "PT100分度表模拟输出", + "双通道温度输出", + "温度转电阻自动转换" + ] + }, + { + "id": "thermocouple", + "name": "热电偶", + "nameEn": "Thermocouple", + "icon": "ic_thermocouple", + "color": "#FF9800", + "gradient": [ + "#FF9800", + "#F57C00" + ], + "badge": "mV", + "description": "热电偶电压测量,分度表模拟输出", + "features": [ + "测量热电偶电压", + "自动转换为温度", + "热电偶分度表模拟输出毫伏电压" + ] + }, + { + "id": "insulation", + "name": "绝缘电阻", + "nameEn": "Insulation", + "icon": "ic_insulation", + "color": "#F44336", + "gradient": [ + "#F44336", + "#D32F2F" + ], + "badge": "MΩ", + "description": "50V/100V绝缘电阻测量", + "features": [ + "手动测量50V绝缘电阻", + "手动测量100V绝缘电阻", + "自动测量绝缘电阻", + "热电阻/热电偶测量后自动测试对地电阻", + "测试后自动完成对地放电" + ], + "warning": true + }, + { + "id": "dc_current", + "name": "直流电流", + "nameEn": "DC Current", + "icon": "ic_dc_current", + "color": "#4CAF50", + "gradient": [ + "#4CAF50", + "#388E3C" + ], + "badge": "mA", + "description": "0-20mA电流输出/测量", + "features": [ + "0-20mA电流输出", + "0-20mA电流测量", + "有源输出模式", + "无源输出模式" + ] + }, + { + "id": "dc_voltage", + "name": "直流电压", + "nameEn": "DC Voltage", + "icon": "ic_dc_voltage", + "color": "#9C27B0", + "gradient": [ + "#9C27B0", + "#7B1FA2" + ], + "badge": "V", + "description": "0-20V电压输出/测量", + "features": [ + "0-20V电压输出", + "0-20V电压测量" + ] + }, + { + "id": "frequency", + "name": "频率信号", + "nameEn": "Frequency", + "icon": "ic_frequency", + "color": "#00BCD4", + "gradient": [ + "#00BCD4", + "#0097A7" + ], + "badge": "Hz", + "description": "转速测量,方波/正弦波输出", + "features": [ + "转速频率信号测量", + "方波频率信号输出", + "正弦波频率信号输出", + "方波幅值和频率可设", + "正弦波幅值和频率可设" + ] + }, + { + "id": "ac_voltage", + "name": "交流电压", + "nameEn": "AC Voltage", + "icon": "ic_ac_voltage", + "color": "#E91E63", + "gradient": [ + "#E91E63", + "#C2185B" + ], + "badge": "VAC", + "description": "220VAC测量功能", + "features": [ + "220VAC电压测量" + ], + "warning": true + } + ] + }, + { + "id": "page2", + "title": "信号检测功能", + "apps": [ + { + "id": "switch_detect", + "name": "开关量检测", + "nameEn": "Switch Detect", + "icon": "ic_switch", + "color": "#795548", + "gradient": [ + "#795548", + "#5D4037" + ], + "badge": "SW", + "description": "电阻通断/电压判断开关状态", + "features": [ + "电阻通断测量模式", + "50V以内电压判断模式", + "开关通断状态判断" + ] + }, + { + "id": "ripple", + "name": "纹波诊断", + "nameEn": "Ripple Diagnosis", + "icon": "ic_ripple", + "color": "#FF5722", + "gradient": [ + "#FF5722", + "#E64A19" + ], + "badge": "AI", + "description": "电源纹波检测,AI故障诊断", + "features": [ + "电源纹波自动检测", + "最大输出50V", + "小波变换分析", + "深度学习故障诊断", + "报警提示功能", + "诊断准确率≥85%" + ] + }, + { + "id": "ramp", + "name": "斜波输出", + "nameEn": "Ramp Output", + "icon": "ic_ramp", + "color": "#3F51B5", + "gradient": [ + "#3F51B5", + "#303F9F" + ], + "badge": "△", + "description": "可设速率的斜波信号输出", + "features": [ + "斜波信号输出", + "斜波速率可设" + ] + }, + { + "id": "waveform", + "name": "波形采集", + "nameEn": "Waveform Capture", + "icon": "ic_waveform", + "color": "#009688", + "gradient": [ + "#009688", + "#00796B" + ], + "badge": "REC", + "description": "信号采集记录,≤25ms/点", + "features": [ + "信号采集和记录", + "生成曲线功能", + "采集速率≤25ms/点", + "采集通道数≥2", + "采集电压信号", + "采集电流信号", + "自动计算最大值", + "自动计算最小值", + "自动计算平均值" + ] + }, + { + "id": "dual_channel", + "name": "双通道", + "nameEn": "Dual Channel", + "icon": "ic_dual_channel", + "color": "#673AB7", + "gradient": [ + "#673AB7", + "#512DA8" + ], + "badge": "2CH", + "description": "同时输出2路,采集2路信号", + "features": [ + "同时输出2路信号", + "同时采集2路信号" + ] + }, + { + "id": "trim", + "name": "信号微调", + "nameEn": "Signal Trim", + "icon": "ic_trim", + "color": "#607D8B", + "gradient": [ + "#607D8B", + "#455A64" + ], + "badge": "±", + "description": "任意位数信号微调", + "features": [ + "输出值任意位数微调" + ] + } + ] + }, + { + "id": "page3", + "title": "系统和通讯", + "apps": [ + { + "id": "data_management", + "name": "数据管理", + "nameEn": "Data Management", + "icon": "ic_data", + "color": "#8BC34A", + "gradient": [ + "#8BC34A", + "#689F38" + ], + "badge": "📁", + "description": "数据存储、导出、搜索", + "features": [ + "手动存储至机内文件", + "自动化流程存储", + "无线4G发送", + "电缆通讯发送", + "功能位置命名", + "时间命名", + "搜索功能" + ] + }, + { + "id": "wireless", + "name": "无线通讯", + "nameEn": "Wireless", + "icon": "ic_wireless", + "color": "#03A9F4", + "gradient": [ + "#03A9F4", + "#0288D1" + ], + "badge": "4G", + "description": "4G/WIFI通讯设置", + "features": [ + "4G通讯", + "WIFI通讯", + "安卓APP控制", + "上位机通信" + ] + }, + { + "id": "sop", + "name": "规程管理", + "nameEn": "SOP", + "icon": "ic_sop", + "color": "#CDDC39", + "gradient": [ + "#CDDC39", + "#AFB42B" + ], + "badge": "📋", + "description": "自动化校验流程", + "features": [ + "自动化流程管理", + "校验规程编辑", + "规程执行" + ] + }, + { + "id": "rcp63", + "name": "RCP63", + "nameEn": "RCP63", + "icon": "ic_rcp63", + "color": "#2196F3", + "gradient": [ + "#2196F3", + "#1976D2" + ], + "badge": "🌐", + "description": "RCP63协议通讯", + "features": [ + "RCP63协议支持" + ] + }, + { + "id": "settings", + "name": "系统设置", + "nameEn": "Settings", + "icon": "ic_settings", + "color": "#9E9E9E", + "gradient": [ + "#9E9E9E", + "#757575" + ], + "badge": "⚙️", + "description": "系统参数配置", + "features": [ + "系统参数配置", + "设备信息", + "校准设置" + ] + } + ] + } + ], + "dock": [ + { + "id": "dock_sop", + "name": "规程", + "icon": "ic_sop", + "color": "#CDDC39", + "gradient": [ + "#CDDC39", + "#AFB42B" + ] + }, + { + "id": "dock_rcp63", + "name": "RCP63", + "icon": "ic_rcp63", + "color": "#2196F3", + "gradient": [ + "#2196F3", + "#1976D2" + ] + }, + { + "id": "dock_network", + "name": "网络测试", + "icon": "ic_network_test", + "color": "#03A9F4", + "gradient": [ + "#03A9F4", + "#0288D1" + ] + }, + { + "id": "dock_settings", + "name": "系统设置", + "icon": "ic_settings", + "color": "#9E9E9E", + "gradient": [ + "#9E9E9E", + "#757575" + ] + } + ], + "animations": { + "pageTransition": { + "duration": 300, + "easing": "ease-out" + }, + "iconPress": { + "scale": 0.95, + "duration": 100 + }, + "longPress": { + "delay": 500 + } + } +} \ No newline at end of file diff --git a/hardware/channelmanager.cpp b/hardware/channelmanager.cpp new file mode 100644 index 0000000..71c1591 --- /dev/null +++ b/hardware/channelmanager.cpp @@ -0,0 +1,349 @@ +#include "channelmanager.h" + +ChannelManager *ChannelManager::m_instance = nullptr; + +ChannelManager::ChannelManager(QObject *parent) : QObject(parent) +{ + initializeChannelCapabilities(); + + // Initialize all channels as available + for (int i = INPUT_CHANNEL_1; i <= OUTPUT_CHANNEL_2; ++i) + { + ChannelId channelId = static_cast(i); + ChannelStatus status; + status.occupied = false; + status.operationDetails.operation = NONE; + status.operationDetails.description = QString(); + status.operationDetails.connectionType = TWO_WIRE; + m_channelStatus[channelId] = status; + } +} + +ChannelManager *ChannelManager::instance() +{ + if (!m_instance) + { + m_instance = new ChannelManager(); + } + return m_instance; +} + +void ChannelManager::initializeChannelCapabilities() +{ + // 输入通道1: 支持最多四线连接(白-红-灰-黑),可进行所有测量功能 + ChannelCapability inputCh1; + inputCh1.type = INPUT_CHANNEL; + inputCh1.maxWires = 4; + inputCh1.wireColors = QStringList() << "白" << "红" << "灰" << "黑"; + inputCh1.supportedConnections = QList() + << TWO_WIRE << THREE_WIRE << FOUR_WIRE; + inputCh1.supportedOperations = QList() + << VOLTAGE_MEASUREMENT << MV_MEASUREMENT + << MA_MEASUREMENT << AC_MEASUREMENT + << RESISTANCE_MEASUREMENT << FREQUENCY_MEASUREMENT + << SWITCH_MEASUREMENT << INSULATION_MEASUREMENT; + m_channelCapabilities[INPUT_CHANNEL_1] = inputCh1; + + // 输入通道2: 支持最多两线连接(白-灰),仅支持二线制测量 + ChannelCapability inputCh2; + inputCh2.type = INPUT_CHANNEL; + inputCh2.maxWires = 2; + inputCh2.wireColors = QStringList() << "白" << "灰"; + inputCh2.supportedConnections = QList() << TWO_WIRE; + inputCh2.supportedOperations = QList() + << VOLTAGE_MEASUREMENT << MV_MEASUREMENT + << MA_MEASUREMENT << AC_MEASUREMENT + << RESISTANCE_MEASUREMENT << FREQUENCY_MEASUREMENT + << SWITCH_MEASUREMENT; + m_channelCapabilities[INPUT_CHANNEL_2] = inputCh2; + + // 输出通道1: 支持最多四线连接(用于电阻模拟) + ChannelCapability outputCh1; + outputCh1.type = OUTPUT_CHANNEL; + outputCh1.maxWires = 4; + outputCh1.wireColors = QStringList() << "白" << "红" << "灰" << "黑"; + outputCh1.supportedConnections = QList() + << TWO_WIRE << THREE_WIRE << FOUR_WIRE; + outputCh1.supportedOperations = QList() + << VOLTAGE_OUTPUT << MV_OUTPUT << MA_OUTPUT + << WAVEFORM_OUTPUT << RESISTANCE_SIMULATION; + m_channelCapabilities[OUTPUT_CHANNEL_1] = outputCh1; + + // 输出通道2: 支持两线连接 + ChannelCapability outputCh2; + outputCh2.type = OUTPUT_CHANNEL; + outputCh2.maxWires = 2; + outputCh2.wireColors = QStringList() << "白" << "灰"; + outputCh2.supportedConnections = QList() << TWO_WIRE; + outputCh2.supportedOperations = QList() + << VOLTAGE_OUTPUT << MV_OUTPUT << MA_OUTPUT + << WAVEFORM_OUTPUT; + m_channelCapabilities[OUTPUT_CHANNEL_2] = outputCh2; +} + +bool ChannelManager::isChannelAvailable(ChannelId channel) const +{ + return !m_channelStatus.value(channel).occupied; +} + +bool ChannelManager::canChannelSupport(ChannelId channel, OperationType operation, + ConnectionType connection) const +{ + if (!m_channelCapabilities.contains(channel)) + { + return false; + } + + const ChannelCapability &capability = m_channelCapabilities[channel]; + + // 检查连接类型支持 + if (!capability.supportedConnections.contains(connection)) + { + return false; + } + + // 检查操作类型支持 + if (operation != NONE && !capability.supportedOperations.contains(operation)) + { + return false; + } + + // 检查操作类型与通道类型的匹配 + if (isInputChannelOperation(operation)) + { + return capability.type == INPUT_CHANNEL; + } + else if (isOutputChannelOperation(operation)) + { + return capability.type == OUTPUT_CHANNEL; + } + else if (operation == NONE) + { + return true; + } + + return false; +} + +bool ChannelManager::occupyChannel(ChannelId channel, OperationType operation, + const QString &description, + ConnectionType connectionType) +{ + if (!isChannelAvailable(channel)) + { + return false; + } + + if (!canChannelSupport(channel, operation, connectionType)) + { + return false; + } + + ChannelStatus &status = m_channelStatus[channel]; + status.occupied = true; + status.operationDetails.operation = operation; + status.operationDetails.description = description; + status.operationDetails.connectionType = connectionType; + status.occupiedTime = QDateTime::currentDateTime(); + + emit channelStatusChanged(channel, operation, description); + return true; +} + +void ChannelManager::releaseChannel(ChannelId channel) +{ + if (m_channelStatus.contains(channel)) + { + ChannelStatus &status = m_channelStatus[channel]; + status.occupied = false; + status.operationDetails.operation = NONE; + status.operationDetails.description.clear(); + status.operationDetails.connectionType = TWO_WIRE; + status.occupiedTime = QDateTime(); + + emit channelReleased(channel); + emit channelStatusChanged(channel, NONE, QString()); + } +} + +ChannelManager::OperationType ChannelManager::getChannelOperation(ChannelId channel) const +{ + return m_channelStatus.value(channel).operationDetails.operation; +} + +QString ChannelManager::getChannelDescription(ChannelId channel) const +{ + return m_channelStatus.value(channel).operationDetails.description; +} + +ChannelManager::OperationDetails +ChannelManager::getChannelOperationDetails(ChannelId channel) const +{ + return m_channelStatus.value(channel).operationDetails; +} + +QList +ChannelManager::getSupportedOperations(ChannelId channel) const +{ + return m_channelCapabilities.value(channel).supportedOperations; +} + +ChannelManager::ChannelCapability +ChannelManager::getChannelCapability(ChannelId channel) const +{ + return m_channelCapabilities.value(channel); +} + +QList +ChannelManager::getAvailableChannels(ChannelType type) const +{ + QList availableChannels; + + for (auto it = m_channelCapabilities.constBegin(); + it != m_channelCapabilities.constEnd(); ++it) + { + if (it.value().type == type && isChannelAvailable(it.key())) + { + availableChannels.append(it.key()); + } + } + + return availableChannels; +} + +QString ChannelManager::getOperationDisplayName(OperationType operation) const +{ + switch (operation) + { + case NONE: + return "空闲"; + // 输入通道操作 + case VOLTAGE_MEASUREMENT: + return "电压测量"; + case MV_MEASUREMENT: + return "mV测量"; + case MA_MEASUREMENT: + return "mA测量"; + case AC_MEASUREMENT: + return "交流测量"; + case RESISTANCE_MEASUREMENT: + return "电阻测量"; + case FREQUENCY_MEASUREMENT: + return "频率测量"; + case SWITCH_MEASUREMENT: + return "开关量测量"; + case INSULATION_MEASUREMENT: + return "绝缘电阻测量"; + // 输出通道操作 + case VOLTAGE_OUTPUT: + return "电压输出"; + case MV_OUTPUT: + return "mV输出"; + case MA_OUTPUT: + return "mA输出"; + case WAVEFORM_OUTPUT: + return "波形输出"; + case RESISTANCE_SIMULATION: + return "电阻模拟"; + } + return "未知"; +} + +QString ChannelManager::getConnectionTypeDisplayName(ConnectionType connection) const +{ + switch (connection) + { + case TWO_WIRE: + return "二线制"; + case THREE_WIRE: + return "三线制"; + case FOUR_WIRE: + return "四线制"; + } + return "未知"; +} + +QString ChannelManager::getDetailedOperationDescription( + const OperationDetails &details) const +{ + QString baseDescription = getOperationDisplayName(details.operation); + + if (details.operation == RESISTANCE_MEASUREMENT || + details.operation == RESISTANCE_SIMULATION) + { + baseDescription += + QString("(%1)").arg(getConnectionTypeDisplayName(details.connectionType)); + } + + if (!details.description.isEmpty()) + { + baseDescription += QString(" - %1").arg(details.description); + } + + return baseDescription; +} + +bool ChannelManager::isInputChannelOperation(OperationType operation) const +{ + switch (operation) + { + case VOLTAGE_MEASUREMENT: + case MV_MEASUREMENT: + case MA_MEASUREMENT: + case AC_MEASUREMENT: + case RESISTANCE_MEASUREMENT: + case FREQUENCY_MEASUREMENT: + case SWITCH_MEASUREMENT: + case INSULATION_MEASUREMENT: + return true; + default: + return false; + } +} + +bool ChannelManager::isOutputChannelOperation(OperationType operation) const +{ + switch (operation) + { + case VOLTAGE_OUTPUT: + case MV_OUTPUT: + case MA_OUTPUT: + case WAVEFORM_OUTPUT: + case RESISTANCE_SIMULATION: + return true; + default: + return false; + } +} + +QString ChannelManager::getChannelDisplayName(ChannelId channel) const +{ + switch (channel) + { + case INPUT_CHANNEL_1: + return "输入通道1"; + case INPUT_CHANNEL_2: + return "输入通道2"; + case OUTPUT_CHANNEL_1: + return "输出通道1"; + case OUTPUT_CHANNEL_2: + return "输出通道2"; + } + return "未知通道"; +} + +QList +ChannelManager::getChannelsWithOperation(OperationType operation) const +{ + QList channels; + + for (auto it = m_channelStatus.constBegin(); it != m_channelStatus.constEnd(); ++it) + { + if (it.value().operationDetails.operation == operation) + { + channels.append(it.key()); + } + } + + return channels; +} diff --git a/hardware/channelmanager.h b/hardware/channelmanager.h new file mode 100644 index 0000000..9e2952f --- /dev/null +++ b/hardware/channelmanager.h @@ -0,0 +1,137 @@ +#ifndef CHANNELMANAGER_H +#define CHANNELMANAGER_H + +#include +#include +#include +#include + +/** + * @brief 通道管理器 + * + * 管理校验仪的输入/输出通道状态 + * - 输入通道1: 4线制,支持所有测量功能 + * - 输入通道2: 2线制,支持基本测量 + * - 输出通道1/2: 2线制,支持电压/电流/波形输出 + */ +class ChannelManager : public QObject +{ + Q_OBJECT + +public: + enum ChannelType + { + INPUT_CHANNEL, + OUTPUT_CHANNEL + }; + + enum ChannelId + { + INPUT_CHANNEL_1 = 1, + INPUT_CHANNEL_2 = 2, + OUTPUT_CHANNEL_1 = 3, + OUTPUT_CHANNEL_2 = 4 + }; + + enum OperationType + { + // 通用状态 + NONE, + + // 输入通道操作状态 + VOLTAGE_MEASUREMENT, // 电压测量 + MV_MEASUREMENT, // mV测量 + MA_MEASUREMENT, // mA测量 + AC_MEASUREMENT, // 交流测量 + RESISTANCE_MEASUREMENT, // 电阻测量(含热电阻及多线制) + FREQUENCY_MEASUREMENT, // 频率测量 + SWITCH_MEASUREMENT, // 开关量测量 + INSULATION_MEASUREMENT, // 绝缘电阻测量 + + // 输出通道操作状态 + VOLTAGE_OUTPUT, // 电压输出 + MV_OUTPUT, // mV输出 + MA_OUTPUT, // mA输出 + WAVEFORM_OUTPUT, // 波形输出 + RESISTANCE_SIMULATION // 电阻模拟(含热电阻及多线制) + }; + + enum ConnectionType + { + TWO_WIRE, + THREE_WIRE, + FOUR_WIRE + }; + + struct ChannelCapability + { + ChannelType type; + int maxWires; + QStringList wireColors; + QList supportedConnections; + QList supportedOperations; + }; + + struct OperationDetails + { + OperationType operation; + QString description; + ConnectionType connectionType; + }; + + static ChannelManager *instance(); + + // 通道状态查询 + bool isChannelAvailable(ChannelId channel) const; + bool canChannelSupport(ChannelId channel, OperationType operation, + ConnectionType connection = TWO_WIRE) const; + + // 通道占用/释放 + bool occupyChannel(ChannelId channel, OperationType operation, + const QString &description = QString(), + ConnectionType connectionType = TWO_WIRE); + void releaseChannel(ChannelId channel); + + // 通道信息获取 + OperationType getChannelOperation(ChannelId channel) const; + QString getChannelDescription(ChannelId channel) const; + OperationDetails getChannelOperationDetails(ChannelId channel) const; + ChannelCapability getChannelCapability(ChannelId channel) const; + QList getAvailableChannels(ChannelType type) const; + QList getSupportedOperations(ChannelId channel) const; + + // 显示名称 + QString getOperationDisplayName(OperationType operation) const; + QString getConnectionTypeDisplayName(ConnectionType connection) const; + QString getDetailedOperationDescription(const OperationDetails &details) const; + QString getChannelDisplayName(ChannelId channel) const; + + // 类型判断 + bool isInputChannelOperation(OperationType operation) const; + bool isOutputChannelOperation(OperationType operation) const; + + // 查询 + QList getChannelsWithOperation(OperationType operation) const; + +signals: + void channelStatusChanged(ChannelId channel, OperationType operation, + const QString &description); + void channelReleased(ChannelId channel); + +private: + explicit ChannelManager(QObject *parent = nullptr); + void initializeChannelCapabilities(); + + struct ChannelStatus + { + bool occupied; + OperationDetails operationDetails; + QDateTime occupiedTime; + }; + + QMap m_channelStatus; + QMap m_channelCapabilities; + static ChannelManager *m_instance; +}; + +#endif // CHANNELMANAGER_H diff --git a/icons/app.png b/icons/app.png new file mode 100644 index 0000000..811cf1b --- /dev/null +++ b/icons/app.png @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + 12.345 + mA + + + + + + + + + + diff --git a/icons/arrow_down.svg b/icons/arrow_down.svg new file mode 100644 index 0000000..1a60b2f --- /dev/null +++ b/icons/arrow_down.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/icons/arrow_up.svg b/icons/arrow_up.svg new file mode 100644 index 0000000..6a7f455 --- /dev/null +++ b/icons/arrow_up.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/icons/ic_ac_voltage.svg b/icons/ic_ac_voltage.svg new file mode 100644 index 0000000..338bdf7 --- /dev/null +++ b/icons/ic_ac_voltage.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + AC + + + + 220V + \ No newline at end of file diff --git a/icons/ic_data.svg b/icons/ic_data.svg new file mode 100644 index 0000000..7e39843 --- /dev/null +++ b/icons/ic_data.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + Data + \ No newline at end of file diff --git a/icons/ic_dc_current.svg b/icons/ic_dc_current.svg new file mode 100644 index 0000000..0f59535 --- /dev/null +++ b/icons/ic_dc_current.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + I + + mA + \ No newline at end of file diff --git a/icons/ic_dc_voltage.svg b/icons/ic_dc_voltage.svg new file mode 100644 index 0000000..ee7e62b --- /dev/null +++ b/icons/ic_dc_voltage.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + V + + DC + \ No newline at end of file diff --git a/icons/ic_dual_channel.svg b/icons/ic_dual_channel.svg new file mode 100644 index 0000000..db7c2e6 --- /dev/null +++ b/icons/ic_dual_channel.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + 1 + 2 + + 2CH + \ No newline at end of file diff --git a/icons/ic_frequency.svg b/icons/ic_frequency.svg new file mode 100644 index 0000000..03c53e2 --- /dev/null +++ b/icons/ic_frequency.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + Hz + \ No newline at end of file diff --git a/icons/ic_insulation.svg b/icons/ic_insulation.svg new file mode 100644 index 0000000..c66ff97 --- /dev/null +++ b/icons/ic_insulation.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/icons/ic_network_test.svg b/icons/ic_network_test.svg new file mode 100644 index 0000000..ab8f3e4 --- /dev/null +++ b/icons/ic_network_test.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + NET + \ No newline at end of file diff --git a/icons/ic_ramp.svg b/icons/ic_ramp.svg new file mode 100644 index 0000000..3e0e162 --- /dev/null +++ b/icons/ic_ramp.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + Ramp + \ No newline at end of file diff --git a/icons/ic_rcp63.svg b/icons/ic_rcp63.svg new file mode 100644 index 0000000..0ed36d8 --- /dev/null +++ b/icons/ic_rcp63.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + RCP63 + \ No newline at end of file diff --git a/icons/ic_ripple.svg b/icons/ic_ripple.svg new file mode 100644 index 0000000..9a31d1e --- /dev/null +++ b/icons/ic_ripple.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + AI + + Ripple + \ No newline at end of file diff --git a/icons/ic_rtd.svg b/icons/ic_rtd.svg new file mode 100644 index 0000000..e24c95a --- /dev/null +++ b/icons/ic_rtd.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + RTD + \ No newline at end of file diff --git a/icons/ic_rtd_output.svg b/icons/ic_rtd_output.svg new file mode 100644 index 0000000..06848ca --- /dev/null +++ b/icons/ic_rtd_output.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + OUTPUT + \ No newline at end of file diff --git a/icons/ic_settings.svg b/icons/ic_settings.svg new file mode 100644 index 0000000..c80e4fa --- /dev/null +++ b/icons/ic_settings.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + 设置 + \ No newline at end of file diff --git a/icons/ic_sop.svg b/icons/ic_sop.svg new file mode 100644 index 0000000..9bfbd6b --- /dev/null +++ b/icons/ic_sop.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + SOP + \ No newline at end of file diff --git a/icons/ic_switch.svg b/icons/ic_switch.svg new file mode 100644 index 0000000..b408416 --- /dev/null +++ b/icons/ic_switch.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + ON + + SW + \ No newline at end of file diff --git a/icons/ic_thermocouple.svg b/icons/ic_thermocouple.svg new file mode 100644 index 0000000..19fa3a6 --- /dev/null +++ b/icons/ic_thermocouple.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + TC + \ No newline at end of file diff --git a/icons/ic_trim.svg b/icons/ic_trim.svg new file mode 100644 index 0000000..14f9aba --- /dev/null +++ b/icons/ic_trim.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + ± + \ No newline at end of file diff --git a/icons/ic_waveform.svg b/icons/ic_waveform.svg new file mode 100644 index 0000000..41d49e7 --- /dev/null +++ b/icons/ic_waveform.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + REC + \ No newline at end of file diff --git a/icons/ic_wireless.svg b/icons/ic_wireless.svg new file mode 100644 index 0000000..13d1cf1 --- /dev/null +++ b/icons/ic_wireless.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + 4G + \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..50b81ff --- /dev/null +++ b/main.cpp @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include "mainwindow.h" +#include "utils/stylehelper.h" + +int main(int argc, char *argv[]) +{ + // 启用高DPI支持 + QApplication::setHighDpiScaleFactorRoundingPolicy( + Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); + + QApplication app(argc, argv); + + // 设置应用程序信息 + app.setApplicationName("智能校验仪"); + app.setApplicationVersion("1.0.0"); + app.setOrganizationName("Calibrator"); + + // 加载字体 + QFontDatabase::addApplicationFont(":/fonts/SourceHanSansSC-Regular.otf"); + QFontDatabase::addApplicationFont(":/fonts/SourceHanSansSC-Medium.otf"); + QFontDatabase::addApplicationFont(":/fonts/SourceHanSansSC-Bold.otf"); + + // 设置默认字体 + QFont defaultFont("Source Han Sans SC", 12); + defaultFont.setStyleHint(QFont::SansSerif); + app.setFont(defaultFont); + + // 应用全局样式 + StyleHelper::applyGlobalStyle(&app); + + // 创建主窗口 + MainWindow mainWindow; + mainWindow.show(); + + return app.exec(); +} diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..03c316c --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,457 @@ +#include "mainwindow.h" +#include "widgets/overlaydialogswidget.h" +#include +#include +#include +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent), m_currentPage(0), m_totalPages(1), m_isSwiping(false), m_swipeThreshold(100) +{ + // 设置窗口属性 + setWindowTitle("智能校验仪"); + setMinimumSize(1024, 600); + resize(1024, 600); + + // 初始化配置管理器 + m_configManager = new ConfigManager(this); + + // 初始化规程管理器 + m_procedureManager = new ProcedureManager(this); + + // 加载配置 + loadConfig(); + + // 设置UI + setupUI(); + + // 设置页面 + setupPages(); + + // 设置规程播放器 + setupProcedurePlayer(); + + // 设置信号测量功能 + setupSignalMeasurement(); +} + +MainWindow::~MainWindow() +{ +} + +void MainWindow::setupUI() +{ + // 创建中心部件 + m_centralWidget = new QWidget(this); + m_centralWidget->setObjectName("centralWidget"); + setCentralWidget(m_centralWidget); + + // 主布局 + QVBoxLayout *mainLayout = new QVBoxLayout(m_centralWidget); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(0); + + // 主视图切换器 - 用于在启动器和规程播放器之间切换 + m_mainStackedWidget = new QStackedWidget(this); + mainLayout->addWidget(m_mainStackedWidget, 1); + + // 启动器容器 + m_launcherContainer = new QWidget(this); + m_launcherContainer->setObjectName("launcherContainer"); + + QVBoxLayout *launcherLayout = new QVBoxLayout(m_launcherContainer); + launcherLayout->setContentsMargins(0, 0, 0, 0); + launcherLayout->setSpacing(0); + + // 状态栏 - 透明风格 + m_statusBar = new StatusBar(this); + launcherLayout->addWidget(m_statusBar); + + // 页面容器 - 放置启动器页面 + m_pageContainer = new QWidget(this); + m_pageContainer->setObjectName("pageContainer"); + m_pageContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + launcherLayout->addWidget(m_pageContainer, 1); + + // 页面指示器 - Android风格点状指示器 + m_pageIndicator = new PageIndicator(1, this); + m_pageIndicator->setFixedHeight(40); + m_pageIndicator->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + launcherLayout->addWidget(m_pageIndicator); + connect(m_pageIndicator, &PageIndicator::pageClicked, this, &MainWindow::goToPage); + + // 添加启动器到主视图 + m_mainStackedWidget->addWidget(m_launcherContainer); + + // Android风格渐变壁纸背景 + m_launcherContainer->setStyleSheet(R"( + #launcherContainer { + background: qlineargradient(x1:0, y1:0, x2:1, y2:1, + stop:0 #667eea, stop:0.5 #764ba2, stop:1 #f093fb); + } + #pageContainer { + background: transparent; + } + )"); +} + +void MainWindow::setupPages() +{ + // 获取页面配置 + auto pagesConfig = m_configManager->getPages(); + m_totalPages = pagesConfig.size(); + m_pageIndicator->setPageCount(m_totalPages); + + qDebug() << "=== setupPages Debug ==="; + qDebug() << "Total pages:" << m_totalPages; + for (int i = 0; i < pagesConfig.size(); ++i) + { + qDebug() << "Page" << i << ":" << pagesConfig[i].title; + qDebug() << " Apps count:" << pagesConfig[i].apps.size(); + for (const auto &app : pagesConfig[i].apps) + { + qDebug() << " - " << app.name << "(" << app.id << ")"; + } + } + + // 为页面容器创建布局 + QVBoxLayout *containerLayout = new QVBoxLayout(m_pageContainer); + containerLayout->setContentsMargins(0, 0, 0, 0); + containerLayout->setSpacing(0); + + // 使用 QStackedWidget 来管理多个页面 + m_stackedWidget = new QStackedWidget(m_pageContainer); + containerLayout->addWidget(m_stackedWidget); + + // 创建各个页面 + for (int i = 0; i < pagesConfig.size(); ++i) + { + LauncherPage *page = new LauncherPage(pagesConfig[i], m_stackedWidget); + connect(page, &LauncherPage::appClicked, this, &MainWindow::onAppClicked); + m_pages.append(page); + m_stackedWidget->addWidget(page); + } + + // 显示当前页面 + m_stackedWidget->setCurrentIndex(m_currentPage); + + // 不再需要Dock栏 + // m_dockBar->setApps(m_configManager->getDockApps()); +} + +void MainWindow::loadConfig() +{ + // 从配置文件加载 + QString configPath = QCoreApplication::applicationDirPath() + "/config/launcher_config.json"; + if (!m_configManager->loadFromFile(configPath)) + { + // 使用默认配置 + m_configManager->loadDefaultConfig(); + } +} + +void MainWindow::setupProcedurePlayer() +{ + // 规程列表页 + m_procedureListWidget = new ProcedureListWidget(m_procedureManager, this); + connect(m_procedureListWidget, &ProcedureListWidget::procedureSelected, + this, &MainWindow::onProcedureSelected); + connect(m_procedureListWidget, &ProcedureListWidget::backRequested, + this, &MainWindow::onBackToLauncher); + m_mainStackedWidget->addWidget(m_procedureListWidget); + + // 规程执行页 + m_procedurePlayerWidget = new ProcedurePlayerWidget(m_procedureManager, this); + connect(m_procedurePlayerWidget, &ProcedurePlayerWidget::backToListRequested, + this, [this]() + { showProcedureList(); }); + m_mainStackedWidget->addWidget(m_procedurePlayerWidget); +} + +void MainWindow::setupSignalMeasurement() +{ + m_signalMeasurementWidget = new SignalMeasurementWidget(this); + connect(m_signalMeasurementWidget, &SignalMeasurementWidget::backRequested, + this, &MainWindow::onBackToLauncherFromSignal); + m_mainStackedWidget->addWidget(m_signalMeasurementWidget); + + // 设置系统设置页面 + m_settingsWidget = new SettingsWidget(this); + connect(m_settingsWidget, &SettingsWidget::backRequested, + this, &MainWindow::showLauncher); + m_mainStackedWidget->addWidget(m_settingsWidget); + + // 设置数据管理页面 + m_dataManagementWidget = new DataManagementWidget(this); + connect(m_dataManagementWidget, &DataManagementWidget::backRequested, + this, &MainWindow::showLauncher); + m_mainStackedWidget->addWidget(m_dataManagementWidget); + + // 设置无线通讯页面 + m_wirelessWidget = new WirelessWidget(this); + connect(m_wirelessWidget, &WirelessWidget::backRequested, + this, &MainWindow::showLauncher); + m_mainStackedWidget->addWidget(m_wirelessWidget); + + // 设置网络测试页面 + m_networkTestWidget = new NetworkTestWidget(this); + connect(m_networkTestWidget, &NetworkTestWidget::backRequested, + this, &MainWindow::showLauncher); + m_mainStackedWidget->addWidget(m_networkTestWidget); + + // 设置波形采集页面 + m_waveformWidget = new WaveformWidget(this); + connect(m_waveformWidget, &WaveformWidget::backRequested, + this, &MainWindow::showLauncher); + m_mainStackedWidget->addWidget(m_waveformWidget); + + // 设置双通道页面 + m_dualChannelWidget = new DualChannelWidget(this); + connect(m_dualChannelWidget, &DualChannelWidget::backRequested, + this, &MainWindow::showLauncher); + m_mainStackedWidget->addWidget(m_dualChannelWidget); + + // 设置信号微调页面 + m_signalTrimWidget = new SignalTrimWidget(this); + connect(m_signalTrimWidget, &SignalTrimWidget::backRequested, + this, &MainWindow::showLauncher); + m_mainStackedWidget->addWidget(m_signalTrimWidget); +} + +void MainWindow::showLauncher() +{ + m_mainStackedWidget->setCurrentWidget(m_launcherContainer); +} + +void MainWindow::showProcedureList() +{ + m_procedureListWidget->refreshList(); + m_mainStackedWidget->setCurrentWidget(m_procedureListWidget); +} + +void MainWindow::showProcedurePlayer(const QString &procedureId) +{ + m_procedurePlayerWidget->loadProcedure(procedureId); + m_mainStackedWidget->setCurrentWidget(m_procedurePlayerWidget); +} + +void MainWindow::showSignalMeasurement(const QString &appId) +{ + // 根据appId设置对应的模块 + if (appId == "rtd_measure") + m_signalMeasurementWidget->setModule(0); // 热电阻测量 + else if (appId == "rtd_output") + m_signalMeasurementWidget->setModule(11); // 热电阻输出(新增) + else if (appId == "rtd") + m_signalMeasurementWidget->setModule(0); // 兼容旧的rtd + else if (appId == "thermocouple") + m_signalMeasurementWidget->setModule(1); + else if (appId == "insulation") + m_signalMeasurementWidget->setModule(2); + else if (appId == "dc_current") + m_signalMeasurementWidget->setModule(3); + else if (appId == "dc_voltage") + m_signalMeasurementWidget->setModule(4); + else if (appId == "frequency") + m_signalMeasurementWidget->setModule(5); + else if (appId == "ac_voltage") + m_signalMeasurementWidget->setModule(6); + else if (appId == "switch_detect") + m_signalMeasurementWidget->setModule(7); + else if (appId == "waveform") + m_signalMeasurementWidget->setModule(8); + else if (appId == "ramp") + m_signalMeasurementWidget->setModule(9); + else if (appId == "ripple") + m_signalMeasurementWidget->setModule(10); + else + m_signalMeasurementWidget->setModule(0); // 默认热电阻 + + m_mainStackedWidget->setCurrentWidget(m_signalMeasurementWidget); +} + +void MainWindow::onProcedureSelected(const QString &procedureId) +{ + showProcedurePlayer(procedureId); +} + +void MainWindow::onBackToLauncher() +{ + showLauncher(); +} + +void MainWindow::onBackToLauncherFromSignal() +{ + showLauncher(); +} + +void MainWindow::resizeEvent(QResizeEvent *event) +{ + QMainWindow::resizeEvent(event); + + // 调整页面大小 + for (auto page : m_pages) + { + page->setGeometry(m_pageContainer->rect()); + } +} + +void MainWindow::mousePressEvent(QMouseEvent *event) +{ + // 检查是否在页面容器区域 + if (m_pageContainer->geometry().contains(event->pos())) + { + m_swipeStartPos = event->pos(); + m_isSwiping = true; + } + QMainWindow::mousePressEvent(event); +} + +void MainWindow::mouseMoveEvent(QMouseEvent *event) +{ + if (m_isSwiping) + { + // 可以添加实时跟随效果 + } + QMainWindow::mouseMoveEvent(event); +} + +void MainWindow::mouseReleaseEvent(QMouseEvent *event) +{ + if (m_isSwiping) + { + int deltaX = event->pos().x() - m_swipeStartPos.x(); + handleSwipe(deltaX); + m_isSwiping = false; + } + QMainWindow::mouseReleaseEvent(event); +} + +void MainWindow::handleSwipe(int deltaX) +{ + if (qAbs(deltaX) < m_swipeThreshold) + { + return; // 滑动距离不够 + } + + int newPage = m_currentPage; + if (deltaX > 0 && m_currentPage > 0) + { + // 向右滑动,前一页 + newPage = m_currentPage - 1; + } + else if (deltaX < 0 && m_currentPage < m_totalPages - 1) + { + // 向左滑动,后一页 + newPage = m_currentPage + 1; + } + + if (newPage != m_currentPage) + { + goToPage(newPage); + } +} + +void MainWindow::goToPage(int pageIndex) +{ + if (pageIndex < 0 || pageIndex >= m_totalPages || pageIndex == m_currentPage) + { + return; + } + + m_currentPage = pageIndex; + m_stackedWidget->setCurrentIndex(pageIndex); + if (m_pageIndicator) + { + m_pageIndicator->setCurrentPage(pageIndex); + } +} + +void MainWindow::animatePageTransition(int fromPage, int toPage) +{ + Q_UNUSED(fromPage) + Q_UNUSED(toPage) + // 使用 QStackedWidget 后不需要手动动画 +} + +void MainWindow::onAppClicked(const QString &appId) +{ + openApplication(appId); +} + +void MainWindow::onDockAppClicked(const QString &appId) +{ + openApplication(appId); +} + +void MainWindow::openApplication(const QString &appId) +{ + qDebug() << "Opening application:" << appId; + + // 根据appId打开对应功能窗口 + if (appId == "procedures" || appId == "procedure") + { + // 打开规程播放器 + showProcedureList(); + } + else if (appId == "signal_measurement" || appId == "rtd" || appId == "rtd_measure" || + appId == "rtd_output" || appId == "thermocouple" || + appId == "insulation" || appId == "dc_current" || appId == "dc_voltage" || + appId == "frequency" || appId == "ac_voltage" || appId == "switch_detect" || + appId == "ripple" || appId == "ramp") + { + // 打开信号测量功能页面 + showSignalMeasurement(appId); + } + else if (appId == "waveform") + { + // 打开波形采集页面 + m_mainStackedWidget->setCurrentWidget(m_waveformWidget); + } + else if (appId == "dual_channel") + { + // 打开双通道页面 + m_mainStackedWidget->setCurrentWidget(m_dualChannelWidget); + } + else if (appId == "trim") + { + // 打开信号微调页面 + m_mainStackedWidget->setCurrentWidget(m_signalTrimWidget); + } + else if (appId == "data_management") + { + // 打开数据管理页面 + m_mainStackedWidget->setCurrentWidget(m_dataManagementWidget); + } + else if (appId == "wireless") + { + // 打开无线通讯页面 + m_mainStackedWidget->setCurrentWidget(m_wirelessWidget); + } + else if (appId == "sop" || appId == "dock_sop") + { + // 打开规程播放器 + showProcedureList(); + } + else if (appId == "rcp63" || appId == "dock_rcp63") + { + // 直接打开RCP63规程 + showProcedurePlayer("KMCIXRCP503"); + } + else if (appId == "settings" || appId == "dock_settings") + { + // 打开系统设置页面 + m_mainStackedWidget->setCurrentWidget(m_settingsWidget); + } + else if (appId == "dock_network") + { + // 打开网络测试页面 + m_mainStackedWidget->setCurrentWidget(m_networkTestWidget); + } + else + { + OverlayDialog::information(this, "功能", QString("功能 ID: %1").arg(appId)); + } +} diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 index 0000000..13032ce --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,105 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include +#include +#include "widgets/statusbar.h" +#include "widgets/launcherpage.h" +#include "widgets/pageindicator.h" +#include "widgets/dockbar.h" +#include "widgets/procedurelistwidget.h" +#include "widgets/procedureplayerwidget.h" +#include "widgets/signalmeasurementwidget.h" +#include "widgets/settingswidget.h" +#include "widgets/datamanagementwidget.h" +#include "widgets/wirelesswidget.h" +#include "widgets/networktestwidget.h" +#include "widgets/waveformwidget.h" +#include "widgets/dualchannelwidget.h" +#include "widgets/signaltrimwidget.h" +#include "utils/configmanager.h" +#include "procedure/proceduremanager.h" + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +protected: + void resizeEvent(QResizeEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + +private slots: + void onAppClicked(const QString &appId); + void onDockAppClicked(const QString &appId); + void goToPage(int pageIndex); + void animatePageTransition(int fromPage, int toPage); + + // 规程播放器相关 + void onProcedureSelected(const QString &procedureId); + void onBackToLauncher(); + void onBackToLauncherFromSignal(); + +private: + void setupUI(); + void setupPages(); + void setupProcedurePlayer(); + void setupSignalMeasurement(); + void loadConfig(); + void handleSwipe(int deltaX); + void openApplication(const QString &appId); + void showLauncher(); + void showProcedureList(); + void showProcedurePlayer(const QString &procedureId); + void showSignalMeasurement(const QString &appId = QString()); + + // UI 组件 + QWidget *m_centralWidget; + StatusBar *m_statusBar; + QWidget *m_pageContainer; + QStackedWidget *m_stackedWidget; + QVector m_pages; + PageIndicator *m_pageIndicator; + DockBar *m_dockBar; + + // 规程播放器组件 + QStackedWidget *m_mainStackedWidget; // 主视图切换 + QWidget *m_launcherContainer; + ProcedureListWidget *m_procedureListWidget; + ProcedurePlayerWidget *m_procedurePlayerWidget; + ProcedureManager *m_procedureManager; + + // 信号测量组件 + SignalMeasurementWidget *m_signalMeasurementWidget; + + // 设置和数据管理组件 + SettingsWidget *m_settingsWidget; + DataManagementWidget *m_dataManagementWidget; + WirelessWidget *m_wirelessWidget; + NetworkTestWidget *m_networkTestWidget; + WaveformWidget *m_waveformWidget; + DualChannelWidget *m_dualChannelWidget; + SignalTrimWidget *m_signalTrimWidget; + + // 配置 + ConfigManager *m_configManager; + + // 页面状态 + int m_currentPage; + int m_totalPages; + + // 滑动手势 + QPoint m_swipeStartPos; + bool m_isSwiping; + int m_swipeThreshold; +}; + +#endif // MAINWINDOW_H diff --git a/procedure/functionregistry.cpp b/procedure/functionregistry.cpp new file mode 100644 index 0000000..c8dec44 --- /dev/null +++ b/procedure/functionregistry.cpp @@ -0,0 +1,479 @@ +#include "functionregistry.h" + +#include +#include +#include +#include + +// ============================================================================= +// 單例實現 +// ============================================================================= + +FunctionRegistry *FunctionRegistry::m_instance = nullptr; + +FunctionRegistry *FunctionRegistry::instance() +{ + if (!m_instance) + { + m_instance = new FunctionRegistry(); + } + return m_instance; +} + +FunctionRegistry::FunctionRegistry(QObject *parent) + : QObject(parent) +{ + registerBuiltinFunctions(); +} + +FunctionRegistry::~FunctionRegistry() +{ +} + +// ============================================================================= +// 函數註冊 +// ============================================================================= + +bool FunctionRegistry::registerFunction(const QString &functionType, + const QStringList ¶meterNames, + FunctionCallback callback, + const QString &displayName, + const QString &description, + const QString &category) +{ + FunctionMetadata metadata; + metadata.functionType = functionType; + metadata.parameterNames = parameterNames; + metadata.callback = callback; + metadata.displayName = displayName.isEmpty() ? functionType : displayName; + metadata.description = description; + metadata.category = category; + + return registerFunction(metadata); +} + +bool FunctionRegistry::registerFunction(const FunctionMetadata &metadata) +{ + if (metadata.functionType.isEmpty()) + { + qWarning() << "無法註冊空函數類型"; + return false; + } + + if (m_functions.contains(metadata.functionType)) + { + qWarning() << "函數已存在,將被覆蓋:" << metadata.functionType; + } + + m_functions[metadata.functionType] = metadata; + + qDebug() << "註冊函數:" << metadata.functionType << "分類:" << metadata.category; + + emit functionRegistered(metadata.functionType); + return true; +} + +bool FunctionRegistry::unregisterFunction(const QString &functionType) +{ + if (!m_functions.contains(functionType)) + { + return false; + } + + m_functions.remove(functionType); + emit functionUnregistered(functionType); + return true; +} + +bool FunctionRegistry::hasFunction(const QString &functionType) const +{ + return m_functions.contains(functionType); +} + +QStringList FunctionRegistry::getAllFunctionTypes() const +{ + return m_functions.keys(); +} + +QStringList FunctionRegistry::getFunctionsByCategory(const QString &category) const +{ + QStringList result; + for (auto it = m_functions.begin(); it != m_functions.end(); ++it) + { + if (it.value().category == category) + { + result.append(it.key()); + } + } + return result; +} + +FunctionRegistry::FunctionMetadata FunctionRegistry::getFunctionMetadata(const QString &functionType) const +{ + if (m_functions.contains(functionType)) + { + return m_functions[functionType]; + } + return FunctionMetadata(); +} + +// ============================================================================= +// 參數處理 +// ============================================================================= + +QVariantMap FunctionRegistry::normalizeParameters(const FunctionMetadata &metadata, + const QVariant ¶meters) +{ + QVariantMap result; + + if (parameters.type() == QVariant::Map) + { + result = parameters.toMap(); + } + else if (parameters.type() == QVariant::List) + { + QVariantList list = parameters.toList(); + for (int i = 0; i < qMin(list.size(), metadata.parameterNames.size()); i++) + { + result[metadata.parameterNames[i]] = list[i]; + } + } + + // 應用默認值 + for (auto it = metadata.defaultParameters.begin(); it != metadata.defaultParameters.end(); ++it) + { + if (!result.contains(it.key())) + { + result[it.key()] = it.value(); + } + } + + return result; +} + +bool FunctionRegistry::validateParameters(const FunctionMetadata &metadata, + const QVariantMap ¶ms) +{ + Q_UNUSED(metadata) + Q_UNUSED(params) + // 簡單驗證 - 可以擴展 + return true; +} + +// ============================================================================= +// 函數執行 +// ============================================================================= + +QVariantList FunctionRegistry::executeFunction(const QString &functionType, + const QVariant ¶meters) +{ + if (!m_functions.contains(functionType)) + { + qWarning() << "找不到函數:" << functionType; + emit functionExecuted(functionType, false); + return QVariantList(); + } + + const FunctionMetadata &metadata = m_functions[functionType]; + + QVariantMap params = normalizeParameters(metadata, parameters); + + if (!validateParameters(metadata, params)) + { + qWarning() << "參數驗證失敗:" << functionType; + emit functionExecuted(functionType, false); + return QVariantList(); + } + + try + { + QVariantList result = metadata.callback(params); + emit functionExecuted(functionType, true); + return result; + } + catch (const std::exception &e) + { + qWarning() << "函數執行異常:" << functionType << "-" << e.what(); + emit functionExecuted(functionType, false); + return QVariantList(); + } + catch (...) + { + qWarning() << "函數執行未知異常:" << functionType; + emit functionExecuted(functionType, false); + return QVariantList(); + } +} + +// ============================================================================= +// 內建函數註冊 +// ============================================================================= + +void FunctionRegistry::registerBuiltinFunctions() +{ + // -------------------------------------------------- + // 電阻測量函數 + // -------------------------------------------------- + registerFunction("RESISTANCE_4WIRE", {"channel", "range", "samples"}, [](const QVariantMap ¶ms) -> QVariantList + { + int channel = params.value("channel", 1).toInt(); + double range = params.value("range", 1000.0).toDouble(); + int samples = params.value("samples", 10).toInt(); + Q_UNUSED(samples) + + qDebug() << "四線電阻測量: 通道=" << channel << "量程=" << range; + + // 模擬數據 + double resistance = 100.0 + QRandomGenerator::global()->bounded(1.0); + double temperature = (resistance - 100.0) / 0.385; + + QVariantList result; + result << resistance << temperature; + return result; }, "四線電阻測量", "使用四線法測量電阻,消除導線電阻影響", "measurement"); + + registerFunction("RESISTANCE_2WIRE", {"channel", "range", "samples"}, [](const QVariantMap ¶ms) -> QVariantList + { + int channel = params.value("channel", 1).toInt(); + double range = params.value("range", 1000.0).toDouble(); + Q_UNUSED(range) + + qDebug() << "二線電阻測量: 通道=" << channel; + + double resistance = 100.0 + QRandomGenerator::global()->bounded(2.0); + + QVariantList result; + result << resistance; + return result; }, "二線電阻測量", "使用二線法測量電阻", "measurement"); + + // -------------------------------------------------- + // 電壓測量函數 + // -------------------------------------------------- + registerFunction("VOLTAGE_V", {"channel", "range", "samples"}, [](const QVariantMap ¶ms) -> QVariantList + { + int channel = params.value("channel", 1).toInt(); + double range = params.value("range", 10.0).toDouble(); + Q_UNUSED(range) + + qDebug() << "電壓測量: 通道=" << channel; + + double voltage = 5.0 + QRandomGenerator::global()->bounded(0.1); + + QVariantList result; + result << voltage; + return result; }, "電壓測量", "測量直流電壓", "measurement"); + + registerFunction("VOLTAGE_MV", {"channel", "range", "samples"}, [](const QVariantMap ¶ms) -> QVariantList + { + int channel = params.value("channel", 1).toInt(); + Q_UNUSED(channel) + + double voltage_mv = 10.0 + QRandomGenerator::global()->bounded(1.0); + + QVariantList result; + result << voltage_mv; + return result; }, "毫伏測量", "測量毫伏級電壓", "measurement"); + + // -------------------------------------------------- + // 電流測量函數 + // -------------------------------------------------- + registerFunction("CURRENT_MA", {"channel", "range", "samples"}, [](const QVariantMap ¶ms) -> QVariantList + { + int channel = params.value("channel", 1).toInt(); + Q_UNUSED(channel) + + double current_ma = 12.0 + QRandomGenerator::global()->bounded(0.5); + + QVariantList result; + result << current_ma; + return result; }, "毫安電流測量", "測量毫安級電流(如4-20mA信號)", "measurement"); + + registerFunction("CURRENT_A", {"channel", "range", "samples"}, [](const QVariantMap ¶ms) -> QVariantList + { + int channel = params.value("channel", 1).toInt(); + Q_UNUSED(channel) + + double current_a = 1.0 + QRandomGenerator::global()->bounded(0.1); + + QVariantList result; + result << current_a; + return result; }, "安培電流測量", "測量安培級電流", "measurement"); + + // -------------------------------------------------- + // 絕緣測量函數 + // -------------------------------------------------- + registerFunction("INSULATION", {"channel", "voltage", "duration"}, [](const QVariantMap ¶ms) -> QVariantList + { + double voltage = params.value("voltage", 500.0).toDouble(); + + double insulation_resistance = 500.0 + QRandomGenerator::global()->bounded(100.0); + double leakage_current = voltage / (insulation_resistance * 1e6) * 1e6; + + QVariantList result; + result << insulation_resistance << leakage_current; + return result; }, "絕緣電阻測量", "測量絕緣電阻和漏電流", "measurement"); + + // -------------------------------------------------- + // 溫度測量函數 + // -------------------------------------------------- + registerFunction("TEMPERATURE_RTD", {"channel", "rtd_type", "wire_mode"}, [](const QVariantMap ¶ms) -> QVariantList + { + QString rtdType = params.value("rtd_type", "PT100").toString(); + Q_UNUSED(rtdType) + + double resistance = 100.0 + QRandomGenerator::global()->bounded(10.0); + double temperature = (resistance - 100.0) / 0.385; + + QVariantList result; + result << temperature << resistance; + return result; }, "RTD溫度測量", "使用RTD(如PT100)測量溫度", "measurement"); + + registerFunction("TEMPERATURE_TC", {"channel", "tc_type", "cjc_channel"}, [](const QVariantMap ¶ms) -> QVariantList + { + QString tcType = params.value("tc_type", "K").toString(); + Q_UNUSED(tcType) + + double voltage_mv = 4.0 + QRandomGenerator::global()->bounded(1.0); + double temperature = voltage_mv / 0.040; + + QVariantList result; + result << temperature << voltage_mv; + return result; }, "熱電偶溫度測量", "使用熱電偶測量溫度", "measurement"); + + // -------------------------------------------------- + // 數據採集函數 + // -------------------------------------------------- + registerFunction("DATA_ACQUISITION", {"channels", "sample_rate", "duration"}, [](const QVariantMap ¶ms) -> QVariantList + { + QVariantList channels = params.value("channels").toList(); + double sampleRate = params.value("sample_rate", 1000.0).toDouble(); + double duration = params.value("duration", 1.0).toDouble(); + + int sampleCount = static_cast(sampleRate * duration); + QVariantList dataArray; + QVariantList timestamps; + + for (int i = 0; i < qMin(sampleCount, 100); i++) { // 限制最大樣本數 + QVariantList sample; + for (int ch = 0; ch < channels.size(); ch++) { + sample << QRandomGenerator::global()->bounded(10.0); + } + dataArray << QVariant(sample); + timestamps << (i / sampleRate); + } + + QVariantList result; + result << QVariant(dataArray) << QVariant(timestamps); + return result; }, "數據採集", "多通道數據採集", "acquisition"); + + // -------------------------------------------------- + // 輸出控制函數 + // -------------------------------------------------- + registerFunction("SET_VOLTAGE", {"channel", "voltage"}, [](const QVariantMap ¶ms) -> QVariantList + { + int channel = params.value("channel", 1).toInt(); + double voltage = params.value("voltage", 0.0).toDouble(); + + qDebug() << "設置電壓輸出: 通道=" << channel << "電壓=" << voltage; + + QVariantList result; + result << true; + return result; }, "設置電壓輸出", "設置指定通道的電壓輸出", "output"); + + registerFunction("SET_CURRENT", {"channel", "current_ma"}, [](const QVariantMap ¶ms) -> QVariantList + { + int channel = params.value("channel", 1).toInt(); + double current_ma = params.value("current_ma", 0.0).toDouble(); + + qDebug() << "設置電流輸出: 通道=" << channel << "電流=" << current_ma << "mA"; + + QVariantList result; + result << true; + return result; }, "設置電流輸出", "設置指定通道的電流輸出", "output"); + + registerFunction("SET_RESISTANCE", {"channel", "resistance"}, [](const QVariantMap ¶ms) -> QVariantList + { + int channel = params.value("channel", 1).toInt(); + double resistance = params.value("resistance", 100.0).toDouble(); + + qDebug() << "設置電阻輸出: 通道=" << channel << "電阻=" << resistance << "Ω"; + + QVariantList result; + result << true; + return result; }, "設置電阻輸出", "設置指定通道的模擬電阻輸出", "output"); + + // -------------------------------------------------- + // 計算函數 + // -------------------------------------------------- + registerFunction("CALCULATE_ERROR", {"measured", "standard"}, [](const QVariantMap ¶ms) -> QVariantList + { + double measured = params.value("measured", 0.0).toDouble(); + double standard = params.value("standard", 1.0).toDouble(); + + double error = measured - standard; + double errorPercent = (standard != 0.0) ? (error / standard * 100.0) : 0.0; + + QVariantList result; + result << error << errorPercent; + return result; }, "計算誤差", "計算測量值與標準值的誤差", "calculation"); + + registerFunction("CALCULATE_AVERAGE", {"values"}, [](const QVariantMap ¶ms) -> QVariantList + { + QVariantList values = params.value("values").toList(); + + if (values.isEmpty()) { + QVariantList result; + result << 0.0 << 0.0 << 0.0 << 0.0; + return result; + } + + double sum = 0.0; + double minVal = values[0].toDouble(); + double maxVal = values[0].toDouble(); + + for (const QVariant &v : values) { + double val = v.toDouble(); + sum += val; + minVal = qMin(minVal, val); + maxVal = qMax(maxVal, val); + } + + double average = sum / values.size(); + + double sumSquares = 0.0; + for (const QVariant &v : values) { + double diff = v.toDouble() - average; + sumSquares += diff * diff; + } + double stdDev = qSqrt(sumSquares / values.size()); + + QVariantList result; + result << average << stdDev << minVal << maxVal; + return result; }, "計算平均值", "計算數值的平均值、標準差、最小值和最大值", "calculation"); + + // -------------------------------------------------- + // 控制函數 + // -------------------------------------------------- + registerFunction("DELAY", {"duration_ms"}, [](const QVariantMap ¶ms) -> QVariantList + { + int duration_ms = params.value("duration_ms", 1000).toInt(); + + qDebug() << "延遲:" << duration_ms << "ms"; + QThread::msleep(duration_ms); + + QVariantList result; + result << true; + return result; }, "延遲", "等待指定時間", "control"); + + registerFunction("WAIT_STABLE", {"channel", "threshold", "timeout"}, [](const QVariantMap ¶ms) -> QVariantList + { + int timeout = params.value("timeout", 30000).toInt(); + + QThread::msleep(qMin(timeout, 2000)); + double finalValue = 100.0 + QRandomGenerator::global()->bounded(0.05); + + QVariantList result; + result << true << finalValue; + return result; }, "等待穩定", "等待測量值穩定在閾值範圍內", "control"); + + qDebug() << "已註冊" << m_functions.size() << "個內建函數"; +} diff --git a/procedure/functionregistry.h b/procedure/functionregistry.h new file mode 100644 index 0000000..e4afd45 --- /dev/null +++ b/procedure/functionregistry.h @@ -0,0 +1,84 @@ +#ifndef FUNCTIONREGISTRY_H +#define FUNCTIONREGISTRY_H + +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief 功能函數註冊表 + * + * 管理測量和輸出功能的註冊和執行。 + * 每個功能函數接受參數映射,返回結果數組。 + */ +class FunctionRegistry : public QObject +{ + Q_OBJECT + +public: + // 函數回調類型:接受 QVariantMap 參數,返回 QVariantList 結果 + using FunctionCallback = std::function; + + struct FunctionMetadata + { + QString functionType; // 函數類型標識 + QString displayName; // 顯示名稱 + QString description; // 功能描述 + QString category; // 類別 (measurement, output, etc.) + QStringList parameterNames; // 參數名稱列表 + QVariantMap defaultParameters; // 默認參數值 + FunctionCallback callback; // 回調函數 + }; + + static FunctionRegistry *instance(); + + // 註冊功能函數 + bool registerFunction(const QString &functionType, + const QStringList ¶meterNames, + FunctionCallback callback, + const QString &displayName = QString(), + const QString &description = QString(), + const QString &category = QString()); + + bool registerFunction(const FunctionMetadata &metadata); + + // 註銷功能函數 + bool unregisterFunction(const QString &functionType); + + // 執行功能函數 + QVariantList executeFunction(const QString &functionType, + const QVariant ¶meters); + + // 查詢 + bool hasFunction(const QString &functionType) const; + FunctionMetadata getFunctionMetadata(const QString &functionType) const; + QStringList getAllFunctionTypes() const; + QStringList getFunctionsByCategory(const QString &category) const; + +signals: + void functionRegistered(const QString &functionType); + void functionUnregistered(const QString &functionType); + void functionExecuted(const QString &functionType, bool success); + void functionProgress(const QString &functionType, int progress, const QString &message); + +private: + explicit FunctionRegistry(QObject *parent = nullptr); + ~FunctionRegistry(); + + QVariantMap normalizeParameters(const FunctionMetadata &metadata, + const QVariant ¶meters); + bool validateParameters(const FunctionMetadata &metadata, + const QVariantMap ¶ms); + + void registerBuiltinFunctions(); + + QMap m_functions; + static FunctionRegistry *m_instance; +}; + +#endif // FUNCTIONREGISTRY_H diff --git a/procedure/proceduredata.h b/procedure/proceduredata.h new file mode 100644 index 0000000..632dd0a --- /dev/null +++ b/procedure/proceduredata.h @@ -0,0 +1,319 @@ +#ifndef PROCEDUREDATA_H +#define PROCEDUREDATA_H + +#include +#include +#include +#include +#include +#include +#include + +// ===================================================== +// 基本枚舉類型 +// ===================================================== + +// 步驟類型 +enum class StepType +{ + Manual, // 手動步驟 + Automatic // 自動步驟 +}; + +// 步驟狀態 +enum class StepStatus +{ + Pending, // 待執行 + InProgress, // 執行中 + Passed, // 已通過 + Failed, // 未通過 + Skipped, // 已跳過 + Confirmed // 已確認(手動) +}; + +// 活動類型 +enum class ActivityType +{ + TEST_TASK_GROUP, // 測試任務組 + RESULT_DISPLAY // 結果顯示 +}; + +// 活動步驟 +struct ActivityStep +{ + ActivityType type = ActivityType::TEST_TASK_GROUP; + QString ref; // 引用ID + QString name; // 顯示名稱 +}; + +// ===================================================== +// 表格相關結構 +// ===================================================== + +// 字段定義 +struct FieldDefinition +{ + QString id; + QString name; + QString label; + QString type; // numeric, text, selection, boolean, datetime, calculated + QVariant defaultValue; + QStringList options; // 用於 selection 類型 + QString formula; // 用於 calculated 類型 + QVariantMap validationRules; + bool isRequired = false; + bool isReadOnly = false; + bool editable = false; + bool visible = true; + QString unit; + QString description; + QString alignment; + int width = 100; + int decimals = 2; + bool uploadImmediately = false; + QVariantMap layoutConfig; +}; + +// 靜態單元格 +struct StaticCell +{ + int row = 0; + int column = 0; + QString field; // 字段ID(用於form類型表格) + QVariant value; // 靜態值 + QString content; // 靜態文本內容 + QString style; +}; + +// 表格定義 +struct TableDefinition +{ + QString tableId; + QString id; + QString name; + QString description; + QString tableType; // grid, form, series + int rowCount = 0; + int columnCount = 0; + QVariantMap layoutConfig; + bool isShared = false; + QString uploadStrategy; // immediate, grouped, byRow, onComplete + QList uploadGroups; + QList columnHeaders; // 用於grid類型表格 + QList> rowHeaders; // 用於grid類型表格 (id, name) + QList fields; // 用於form和series類型表格 + QList staticCells; // 靜態內容 +}; + +// ===================================================== +// 測試動作相關結構 +// ===================================================== + +// 字段選擇器 +struct FieldSelector +{ + QString tableRef; + int row = -1; + int column = -1; + QString fieldName; + QStringList fields; // 用於form類型表格 + QList> cells; // 用於grid類型表格 (row, column) + bool ignore = false; +}; + +// 測試動作 +struct TestAction +{ + QString actionId; + QString id; + QString name; + QString actionType; + QString functionName; + QString document; // HTML格式的說明文檔 + QString mode; // manual 或 auto + QString sequence; + QString functionType; // RESISTANCE_4WIRE, VOLTAGE_V, DATA_ACQUISITION 等 + QString channel; // input1, input2, output1, output2 + QVariantMap params; // 函數參數 + QVariant functionParameters; + QVariantMap metadata; + QMap inputs; + QMap outputs; + QList dataFields; + QVariantMap validationCriteria; + QString uploadStrategy; + QStringList uploadFields; + int timeout = 30000; + int retryCount = 0; + + // 運行時狀態 + bool isCompleted = false; + bool isWaitingConfirm = false; + QVariantList results; +}; + +// 測試活動組(階段) +struct TestActivityGroup +{ + QString groupId; + QString id; + QString name; + QString description; + QString document; + QString skipCondition; + QVariantMap metadata; + QStringList tableRefs; + QList actions; + + // 運行時狀態 + bool isCompleted = false; + bool isActionsParsed = false; +}; + +// 測試任務組 +struct TestTaskGroup +{ + QString taskGroupId; + QString id; + QString name; + QString description; + QString executionMode; + int loopCount = 1; + QVariantMap metadata; + QStringList tableRefs; + QList stages; + + // 運行時狀態 + bool isCompleted = false; + bool isParsed = false; +}; + +// 結果顯示 +struct ResultDisplay +{ + QString displayId; + QString id; + QString name; + QString displayType; + QString document; + QString tableRef; + QVariantMap passCondition; + QStringList tableRefs; +}; + +// ===================================================== +// 程序配置結構 +// ===================================================== + +struct ProcedureConfig +{ + QString procedureId; + QString procedureName; + QString version; + QString description; + QString author; + QString document; + QVariantMap metadata; + + // 活動序列 (旧格式,用于 ProcedureEngine) + QList activitySequence; + + // 活動序列 (新格式,stores QVariant of TestTaskGroup or ResultDisplay,用于新解析器) + QList activityVariants; + + // 定義存儲 + QMap testTaskGroups; + QMap testActivityGroups; + QMap testActions; + QMap resultDisplays; + QMap tables; + + // 舊版兼容 + QList taskGroups; +}; + +// ===================================================== +// 舊版兼容結構(用於ProcedureManager) +// ===================================================== + +// 表格字段定義(舊版) +struct TableField +{ + QString id; + QString name; + QString type; // numeric, text, boolean + QString unit; + bool isRequired = false; + bool isHighlighted = false; +}; + +// 表格數據(舊版) +struct TableData +{ + QString id; + QString name; + QString description; + QVector columns; + QVector rows; +}; + +// 步驟定義(舊版) +struct StepData +{ + QString id; + QString content; + StepType type = StepType::Manual; + StepStatus status = StepStatus::Pending; + QStringList tableRefs; + QStringList highlightFields; + QString result; + QDateTime executedAt; + QString executedBy; + QString cancelReason; +}; + +// 任務組定義(舊版) +struct TaskGroup +{ + QString id; + QString name; + QVector steps; +}; + +// 程序定義(舊版) +struct ProcedureData +{ + QString id; + QString name; + QString version; + QString description; + QVector taskGroups; + QVector tables; + QString workOrderId; + int completedSteps = 0; + int totalSteps = 0; +}; + +// 程序摘要(用於列表顯示) +struct ProcedureSummary +{ + QString id; + QString name; + QString version; + QString description; + QString filePath; +}; + +// Qt Meta-Type Declarations for QVariant support +Q_DECLARE_METATYPE(FieldDefinition) +Q_DECLARE_METATYPE(StaticCell) +Q_DECLARE_METATYPE(TableDefinition) +Q_DECLARE_METATYPE(FieldSelector) +Q_DECLARE_METATYPE(TestAction) +Q_DECLARE_METATYPE(TestActivityGroup) +Q_DECLARE_METATYPE(TestTaskGroup) +Q_DECLARE_METATYPE(ResultDisplay) +Q_DECLARE_METATYPE(ProcedureConfig) +Q_DECLARE_METATYPE(ActivityStep) + +#endif // PROCEDUREDATA_H diff --git a/procedure/procedureengine.cpp b/procedure/procedureengine.cpp new file mode 100644 index 0000000..9c4c5fe --- /dev/null +++ b/procedure/procedureengine.cpp @@ -0,0 +1,865 @@ +#include "procedureengine.h" +#include "procedureparser.h" + +#include +#include +#include +#include + +// ============================================================================= +// 單例實現 +// ============================================================================= + +ProcedureEngine *ProcedureEngine::s_instance = nullptr; + +ProcedureEngine *ProcedureEngine::instance() +{ + if (!s_instance) + { + s_instance = new ProcedureEngine(); + } + return s_instance; +} + +ProcedureEngine::ProcedureEngine(QObject *parent) + : QObject(parent), m_parser(new ProcedureParser(this)), m_status(ProcedureStatus::Idle), m_executionTimer(new QTimer(this)) +{ + connect(m_executionTimer, &QTimer::timeout, this, &ProcedureEngine::onExecutionTimeout); +} + +ProcedureEngine::~ProcedureEngine() +{ +} + +// ============================================================================= +// 配置載入 +// ============================================================================= + +bool ProcedureEngine::loadProcedure(const QString &configPath) +{ + if (m_status == ProcedureStatus::Running || m_status == ProcedureStatus::Paused) + { + qWarning() << "程序正在執行中,無法載入新配置"; + return false; + } + + // 檢查文件是否存在 + if (!QFile::exists(configPath)) + { + emit errorOccurred(QString("配置文件不存在: %1").arg(configPath)); + return false; + } + + // 解析配置 + if (!m_parser->loadConfig(configPath)) + { + emit errorOccurred(QString("配置文件解析失敗: %1").arg(configPath)); + return false; + } + + m_config = m_parser->parseProcedureConfig(); + + // 驗證配置 + if (!m_parser->validateConfig()) + { + QStringList errors = m_parser->getValidationErrors(); + emit errorOccurred(QString("配置驗證失敗:\n%1").arg(errors.join("\n"))); + return false; + } + + // 初始化執行上下文 + resetExecutionContext(); + m_configPath = configPath; + + setStatus(ProcedureStatus::Loaded); + emit procedureLoaded(m_config.procedureName); + + qDebug() << "程序載入成功:" << m_config.procedureName; + qDebug() << " - 任務組數量:" << m_config.testTaskGroups.size(); + qDebug() << " - 活動序列數量:" << m_config.activitySequence.size(); + + return true; +} + +void ProcedureEngine::resetExecutionContext() +{ + m_context = ExecutionContext(); + m_context.currentActivityIndex = -1; + m_context.currentStageIndex = -1; + m_context.currentActionIndex = -1; + m_context.loopIteration = 0; + m_context.startTime = QDateTime(); +} + +// ============================================================================= +// 程序控制 +// ============================================================================= + +void ProcedureEngine::startProcedure() +{ + if (m_status != ProcedureStatus::Loaded && m_status != ProcedureStatus::Completed) + { + qWarning() << "程序狀態不正確,無法啟動"; + return; + } + + resetExecutionContext(); + m_context.startTime = QDateTime::currentDateTime(); + + setStatus(ProcedureStatus::Running); + emit procedureStarted(); + + // 移動到第一個活動 + moveToFirstActivity(); +} + +void ProcedureEngine::pauseProcedure() +{ + if (m_status != ProcedureStatus::Running) + { + return; + } + + m_executionTimer->stop(); + setStatus(ProcedureStatus::Paused); + emit procedurePaused(); +} + +void ProcedureEngine::resumeProcedure() +{ + if (m_status != ProcedureStatus::Paused) + { + return; + } + + setStatus(ProcedureStatus::Running); + emit procedureResumed(); + + // 繼續執行當前動作 + executeCurrentAction(); +} + +void ProcedureEngine::stopProcedure() +{ + if (m_status == ProcedureStatus::Idle || m_status == ProcedureStatus::Loaded) + { + return; + } + + m_executionTimer->stop(); + setStatus(ProcedureStatus::Stopped); + emit procedureStopped(); +} + +void ProcedureEngine::resetProcedure() +{ + stopProcedure(); + resetExecutionContext(); + setStatus(ProcedureStatus::Loaded); + + // 清除所有表格數據 + m_tableData.clear(); +} + +// ============================================================================= +// 活動導航 +// ============================================================================= + +void ProcedureEngine::moveToFirstActivity() +{ + if (m_config.activitySequence.isEmpty()) + { + completeProcedure(); + return; + } + + m_context.currentActivityIndex = 0; + m_context.currentStageIndex = -1; + m_context.currentActionIndex = -1; + + enterCurrentActivity(); +} + +bool ProcedureEngine::moveToNextActivity() +{ + if (m_status != ProcedureStatus::Running) + { + return false; + } + + int nextIndex = m_context.currentActivityIndex + 1; + + if (nextIndex >= m_config.activitySequence.size()) + { + // 沒有更多活動 + completeProcedure(); + return false; + } + + m_context.currentActivityIndex = nextIndex; + m_context.currentStageIndex = -1; + m_context.currentActionIndex = -1; + + enterCurrentActivity(); + return true; +} + +bool ProcedureEngine::moveToPreviousActivity() +{ + if (m_status != ProcedureStatus::Running && m_status != ProcedureStatus::Paused) + { + return false; + } + + int prevIndex = m_context.currentActivityIndex - 1; + + if (prevIndex < 0) + { + return false; + } + + m_context.currentActivityIndex = prevIndex; + m_context.currentStageIndex = -1; + m_context.currentActionIndex = -1; + + enterCurrentActivity(); + return true; +} + +bool ProcedureEngine::jumpToActivity(int index) +{ + if (index < 0 || index >= m_config.activitySequence.size()) + { + return false; + } + + m_context.currentActivityIndex = index; + m_context.currentStageIndex = -1; + m_context.currentActionIndex = -1; + + enterCurrentActivity(); + return true; +} + +void ProcedureEngine::enterCurrentActivity() +{ + if (m_context.currentActivityIndex < 0 || + m_context.currentActivityIndex >= m_config.activitySequence.size()) + { + return; + } + + const ActivityStep &step = m_config.activitySequence[m_context.currentActivityIndex]; + + qDebug() << "進入活動:" << step.name << "(" << step.ref << ")"; + + emit activityStarted(m_context.currentActivityIndex, step); + + if (step.type == ActivityType::TEST_TASK_GROUP) + { + // 開始執行任務組 + startTaskGroupExecution(step.ref); + } + else if (step.type == ActivityType::RESULT_DISPLAY) + { + // 顯示結果 + displayResult(step.ref); + } +} + +// ============================================================================= +// 任務組執行 +// ============================================================================= + +void ProcedureEngine::startTaskGroupExecution(const QString &taskGroupRef) +{ + if (!m_config.testTaskGroups.contains(taskGroupRef)) + { + emit errorOccurred(QString("找不到任務組: %1").arg(taskGroupRef)); + return; + } + + const TestTaskGroup &taskGroup = m_config.testTaskGroups[taskGroupRef]; + + qDebug() << "開始執行任務組:" << taskGroup.name; + qDebug() << " - 階段數量:" << taskGroup.stages.size(); + + emit taskGroupStarted(taskGroupRef, taskGroup.name); + + // 初始化表格 + for (const QString &tableRef : taskGroup.tableRefs) + { + initializeTable(tableRef); + } + + // 移動到第一個階段 + if (!taskGroup.stages.isEmpty()) + { + m_context.currentStageIndex = 0; + m_context.currentActionIndex = -1; + enterCurrentStage(taskGroupRef); + } + else + { + // 沒有階段,完成任務組 + completeTaskGroup(taskGroupRef); + } +} + +void ProcedureEngine::enterCurrentStage(const QString &taskGroupRef) +{ + if (!m_config.testTaskGroups.contains(taskGroupRef)) + { + return; + } + + const TestTaskGroup &taskGroup = m_config.testTaskGroups[taskGroupRef]; + + if (m_context.currentStageIndex < 0 || + m_context.currentStageIndex >= taskGroup.stages.size()) + { + completeTaskGroup(taskGroupRef); + return; + } + + const TestActivityGroup &stage = taskGroup.stages[m_context.currentStageIndex]; + + qDebug() << "進入階段:" << stage.name; + + emit stageStarted(m_context.currentStageIndex, stage.name); + + // 檢查跳過條件 + if (!stage.skipCondition.isEmpty() && evaluateCondition(stage.skipCondition)) + { + qDebug() << "階段被跳過:" << stage.name; + moveToNextStage(taskGroupRef); + return; + } + + // 移動到第一個動作 + if (!stage.actions.isEmpty()) + { + m_context.currentActionIndex = 0; + executeCurrentAction(); + } + else + { + // 沒有動作,移動到下一個階段 + moveToNextStage(taskGroupRef); + } +} + +void ProcedureEngine::moveToNextStage(const QString &taskGroupRef) +{ + if (!m_config.testTaskGroups.contains(taskGroupRef)) + { + return; + } + + const TestTaskGroup &taskGroup = m_config.testTaskGroups[taskGroupRef]; + + int nextStageIndex = m_context.currentStageIndex + 1; + + if (nextStageIndex >= taskGroup.stages.size()) + { + // 檢查循環 + m_context.loopIteration++; + if (m_context.loopIteration < taskGroup.loopCount) + { + // 繼續下一輪 + m_context.currentStageIndex = 0; + m_context.currentActionIndex = -1; + enterCurrentStage(taskGroupRef); + } + else + { + // 完成任務組 + completeTaskGroup(taskGroupRef); + } + return; + } + + m_context.currentStageIndex = nextStageIndex; + m_context.currentActionIndex = -1; + enterCurrentStage(taskGroupRef); +} + +void ProcedureEngine::completeTaskGroup(const QString &taskGroupRef) +{ + qDebug() << "任務組完成:" << taskGroupRef; + + m_context.loopIteration = 0; + emit taskGroupCompleted(taskGroupRef); + + // 自動移動到下一個活動(如果處於運行狀態) + if (m_status == ProcedureStatus::Running) + { + // 使用延遲以允許UI更新 + QTimer::singleShot(100, this, [this]() + { moveToNextActivity(); }); + } +} + +// ============================================================================= +// 動作執行 +// ============================================================================= + +void ProcedureEngine::executeCurrentAction() +{ + if (m_status != ProcedureStatus::Running) + { + return; + } + + const ActivityStep &step = m_config.activitySequence[m_context.currentActivityIndex]; + if (step.type != ActivityType::TEST_TASK_GROUP) + { + return; + } + + if (!m_config.testTaskGroups.contains(step.ref)) + { + return; + } + + const TestTaskGroup &taskGroup = m_config.testTaskGroups[step.ref]; + + if (m_context.currentStageIndex < 0 || + m_context.currentStageIndex >= taskGroup.stages.size()) + { + return; + } + + const TestActivityGroup &stage = taskGroup.stages[m_context.currentStageIndex]; + + if (m_context.currentActionIndex < 0 || + m_context.currentActionIndex >= stage.actions.size()) + { + // 階段動作執行完畢,移動到下一個階段 + emit stageCompleted(m_context.currentStageIndex); + moveToNextStage(step.ref); + return; + } + + const TestAction &action = stage.actions[m_context.currentActionIndex]; + + qDebug() << "執行動作:" << action.actionId << "-" << action.functionName; + + emit actionStarted(m_context.currentActionIndex, action.actionId); + + // 設置超時 + if (action.timeout > 0) + { + m_executionTimer->start(action.timeout); + } + + // 請求執行動作(通過信號通知外部執行) + emit executeActionRequested(action); +} + +void ProcedureEngine::onActionCompleted(const QString &actionId, const QVariantMap &results) +{ + m_executionTimer->stop(); + + qDebug() << "動作完成:" << actionId; + + emit actionCompleted(m_context.currentActionIndex, actionId, results); + + // 處理結果(寫入表格等) + processActionResults(actionId, results); + + // 移動到下一個動作 + moveToNextAction(); +} + +void ProcedureEngine::onActionFailed(const QString &actionId, const QString &error) +{ + m_executionTimer->stop(); + + qWarning() << "動作執行失敗:" << actionId << "-" << error; + + emit actionFailed(m_context.currentActionIndex, actionId, error); + + // 根據配置決定是否繼續 + // TODO: 可配置的錯誤處理策略 + moveToNextAction(); +} + +void ProcedureEngine::moveToNextAction() +{ + if (m_status != ProcedureStatus::Running) + { + return; + } + + m_context.currentActionIndex++; + executeCurrentAction(); +} + +void ProcedureEngine::onExecutionTimeout() +{ + qWarning() << "動作執行超時"; + + const ActivityStep &step = m_config.activitySequence[m_context.currentActivityIndex]; + if (step.type == ActivityType::TEST_TASK_GROUP && + m_config.testTaskGroups.contains(step.ref)) + { + + const TestTaskGroup &taskGroup = m_config.testTaskGroups[step.ref]; + if (m_context.currentStageIndex >= 0 && + m_context.currentStageIndex < taskGroup.stages.size()) + { + + const TestActivityGroup &stage = taskGroup.stages[m_context.currentStageIndex]; + if (m_context.currentActionIndex >= 0 && + m_context.currentActionIndex < stage.actions.size()) + { + + const TestAction &action = stage.actions[m_context.currentActionIndex]; + onActionFailed(action.actionId, "執行超時"); + return; + } + } + } + + // 如果無法確定當前動作,直接移動到下一個 + moveToNextAction(); +} + +// ============================================================================= +// 結果處理 +// ============================================================================= + +void ProcedureEngine::processActionResults(const QString &actionId, const QVariantMap &results) +{ + // 獲取當前動作定義 + const ActivityStep &step = m_config.activitySequence[m_context.currentActivityIndex]; + if (step.type != ActivityType::TEST_TASK_GROUP) + { + return; + } + + if (!m_config.testTaskGroups.contains(step.ref)) + { + return; + } + + const TestTaskGroup &taskGroup = m_config.testTaskGroups[step.ref]; + if (m_context.currentStageIndex < 0 || + m_context.currentStageIndex >= taskGroup.stages.size()) + { + return; + } + + const TestActivityGroup &stage = taskGroup.stages[m_context.currentStageIndex]; + if (m_context.currentActionIndex < 0 || + m_context.currentActionIndex >= stage.actions.size()) + { + return; + } + + const TestAction &action = stage.actions[m_context.currentActionIndex]; + + // 根據輸出映射寫入表格 + for (auto it = action.outputs.begin(); it != action.outputs.end(); ++it) + { + const QString &outputKey = it.key(); + const FieldSelector &selector = it.value(); + + if (results.contains(outputKey)) + { + QVariant value = results[outputKey]; + setTableCellValue(selector.tableRef, selector.row, selector.column, value); + } + } +} + +void ProcedureEngine::displayResult(const QString &displayRef) +{ + if (!m_config.resultDisplays.contains(displayRef)) + { + emit errorOccurred(QString("找不到結果顯示: %1").arg(displayRef)); + return; + } + + const ResultDisplay &display = m_config.resultDisplays[displayRef]; + + qDebug() << "顯示結果:" << display.name; + + emit resultDisplayRequested(displayRef, display); +} + +void ProcedureEngine::completeProcedure() +{ + qDebug() << "程序執行完成"; + + setStatus(ProcedureStatus::Completed); + emit procedureCompleted(); +} + +// ============================================================================= +// 表格數據管理 +// ============================================================================= + +void ProcedureEngine::initializeTable(const QString &tableRef) +{ + if (!m_config.tables.contains(tableRef)) + { + qWarning() << "找不到表格定義:" << tableRef; + return; + } + + const TableDefinition &tableDef = m_config.tables[tableRef]; + + // 初始化表格數據 + QVector> data; + int rowCount = tableDef.rowCount > 0 ? tableDef.rowCount : 10; + int colCount = tableDef.fields.size() > 0 ? tableDef.fields.size() : tableDef.columnCount; + + for (int row = 0; row < rowCount; row++) + { + QVector rowData; + for (int col = 0; col < colCount; col++) + { + if (col < tableDef.fields.size()) + { + rowData.append(tableDef.fields[col].defaultValue); + } + else + { + rowData.append(QVariant()); + } + } + data.append(rowData); + } + + // 應用靜態單元格 + for (const StaticCell &cell : tableDef.staticCells) + { + if (cell.row < data.size() && cell.column < data[cell.row].size()) + { + data[cell.row][cell.column] = cell.value; + } + } + + m_tableData[tableRef] = data; + + emit tableInitialized(tableRef, tableDef); +} + +QVariant ProcedureEngine::getTableCellValue(const QString &tableRef, int row, int column) const +{ + if (!m_tableData.contains(tableRef)) + { + return QVariant(); + } + + const auto &data = m_tableData[tableRef]; + if (row < 0 || row >= data.size()) + { + return QVariant(); + } + + if (column < 0 || column >= data[row].size()) + { + return QVariant(); + } + + return data[row][column]; +} + +void ProcedureEngine::setTableCellValue(const QString &tableRef, int row, int column, const QVariant &value) +{ + if (!m_tableData.contains(tableRef)) + { + return; + } + + auto &data = m_tableData[tableRef]; + if (row < 0 || row >= data.size()) + { + return; + } + + // 自動擴展列 + while (column >= data[row].size()) + { + data[row].append(QVariant()); + } + + data[row][column] = value; + + emit tableDataChanged(tableRef, row, column, value); +} + +QVector> ProcedureEngine::getTableData(const QString &tableRef) const +{ + if (m_tableData.contains(tableRef)) + { + return m_tableData[tableRef]; + } + return QVector>(); +} + +// ============================================================================= +// 條件評估 +// ============================================================================= + +bool ProcedureEngine::evaluateCondition(const QString &condition) +{ + // 簡單的條件評估 + // TODO: 實現完整的表達式解析器 + + if (condition.isEmpty() || condition == "true") + { + return true; + } + + if (condition == "false") + { + return false; + } + + // 支持簡單的表格引用條件 + // 格式: table[row][col] == value + static QRegularExpression tableRefRe(R"((\w+)\[(\d+)\]\[(\d+)\]\s*(==|!=|>|<|>=|<=)\s*(.+))"); + QRegularExpressionMatch match = tableRefRe.match(condition); + + if (match.hasMatch()) + { + QString tableRef = match.captured(1); + int row = match.captured(2).toInt(); + int col = match.captured(3).toInt(); + QString op = match.captured(4); + QString valueStr = match.captured(5).trimmed(); + + QVariant cellValue = getTableCellValue(tableRef, row, col); + QVariant compareValue = valueStr; + + // 簡單比較 + if (op == "==") + { + return cellValue == compareValue; + } + else if (op == "!=") + { + return cellValue != compareValue; + } + else if (op == ">") + { + return cellValue.toDouble() > compareValue.toDouble(); + } + else if (op == "<") + { + return cellValue.toDouble() < compareValue.toDouble(); + } + else if (op == ">=") + { + return cellValue.toDouble() >= compareValue.toDouble(); + } + else if (op == "<=") + { + return cellValue.toDouble() <= compareValue.toDouble(); + } + } + + qWarning() << "無法評估條件:" << condition; + return false; +} + +// ============================================================================= +// 狀態查詢 +// ============================================================================= + +void ProcedureEngine::setStatus(ProcedureStatus status) +{ + if (m_status != status) + { + m_status = status; + emit statusChanged(status); + } +} + +ProcedureStatus ProcedureEngine::status() const +{ + return m_status; +} + +ProcedureConfig ProcedureEngine::config() const +{ + return m_config; +} + +ExecutionContext ProcedureEngine::executionContext() const +{ + return m_context; +} + +ActivityStep ProcedureEngine::getCurrentActivity() const +{ + if (m_context.currentActivityIndex >= 0 && + m_context.currentActivityIndex < m_config.activitySequence.size()) + { + return m_config.activitySequence[m_context.currentActivityIndex]; + } + return ActivityStep(); +} + +TestTaskGroup ProcedureEngine::getCurrentTaskGroup() const +{ + ActivityStep step = getCurrentActivity(); + if (step.type == ActivityType::TEST_TASK_GROUP && + m_config.testTaskGroups.contains(step.ref)) + { + return m_config.testTaskGroups[step.ref]; + } + return TestTaskGroup(); +} + +TestActivityGroup ProcedureEngine::getCurrentStage() const +{ + TestTaskGroup taskGroup = getCurrentTaskGroup(); + if (m_context.currentStageIndex >= 0 && + m_context.currentStageIndex < taskGroup.stages.size()) + { + return taskGroup.stages[m_context.currentStageIndex]; + } + return TestActivityGroup(); +} + +TestAction ProcedureEngine::getCurrentAction() const +{ + TestActivityGroup stage = getCurrentStage(); + if (m_context.currentActionIndex >= 0 && + m_context.currentActionIndex < stage.actions.size()) + { + return stage.actions[m_context.currentActionIndex]; + } + return TestAction(); +} + +int ProcedureEngine::getActivityCount() const +{ + return m_config.activitySequence.size(); +} + +int ProcedureEngine::getCurrentActivityIndex() const +{ + return m_context.currentActivityIndex; +} + +double ProcedureEngine::getProgress() const +{ + if (m_config.activitySequence.isEmpty()) + { + return 0.0; + } + + // 基於活動數量計算簡單進度 + double activityProgress = static_cast(m_context.currentActivityIndex) / m_config.activitySequence.size(); + + // 可以進一步細化到階段和動作級別 + // TODO: 更精確的進度計算 + + return qBound(0.0, activityProgress * 100.0, 100.0); +} diff --git a/procedure/procedureengine.h b/procedure/procedureengine.h new file mode 100644 index 0000000..2fc2ad6 --- /dev/null +++ b/procedure/procedureengine.h @@ -0,0 +1,172 @@ +#ifndef PROCEDUREENGINE_H +#define PROCEDUREENGINE_H + +#include "proceduredata.h" + +#include +#include +#include +#include +#include + +class ProcedureParser; +class FunctionRegistry; +class QTimer; + +/** + * @brief 程序執行狀態 + */ +enum class ProcedureStatus +{ + Idle, // 空閒 + Loaded, // 已載入 + Running, // 運行中 + Paused, // 已暫停 + Stopped, // 已停止 + Completed, // 已完成 + Error // 錯誤 +}; + +/** + * @brief 執行上下文 + */ +struct ExecutionContext +{ + int currentActivityIndex = -1; + int currentStageIndex = -1; + int currentActionIndex = -1; + int loopIteration = 0; + QDateTime startTime; + QDateTime endTime; +}; + +/** + * @brief 程序執行引擎 + * + * 負責程序的載入、執行、暫停、恢復和停止。 + * 管理執行上下文、活動序列和表格數據。 + */ +class ProcedureEngine : public QObject +{ + Q_OBJECT + +public: + static ProcedureEngine *instance(); + + // 配置載入 + bool loadProcedure(const QString &configPath); + + // 程序控制 + void startProcedure(); + void pauseProcedure(); + void resumeProcedure(); + void stopProcedure(); + void resetProcedure(); + + // 活動導航 + bool moveToNextActivity(); + bool moveToPreviousActivity(); + bool jumpToActivity(int index); + + // 狀態查詢 + ProcedureStatus status() const; + ProcedureConfig config() const; + ExecutionContext executionContext() const; + + // 獲取當前執行項 + ActivityStep getCurrentActivity() const; + TestTaskGroup getCurrentTaskGroup() const; + TestActivityGroup getCurrentStage() const; + TestAction getCurrentAction() const; + + // 進度 + int getActivityCount() const; + int getCurrentActivityIndex() const; + double getProgress() const; + + // 表格數據管理 + QVariant getTableCellValue(const QString &tableRef, int row, int column) const; + void setTableCellValue(const QString &tableRef, int row, int column, const QVariant &value); + QVector> getTableData(const QString &tableRef) const; + +public slots: + void onActionCompleted(const QString &actionId, const QVariantMap &results); + void onActionFailed(const QString &actionId, const QString &error); + +signals: + // 程序狀態信號 + void procedureLoaded(const QString &name); + void procedureStarted(); + void procedurePaused(); + void procedureResumed(); + void procedureStopped(); + void procedureCompleted(); + void statusChanged(ProcedureStatus status); + void errorOccurred(const QString &error); + + // 活動信號 + void activityStarted(int index, const ActivityStep &step); + void taskGroupStarted(const QString &ref, const QString &name); + void taskGroupCompleted(const QString &ref); + void stageStarted(int index, const QString &name); + void stageCompleted(int index); + void actionStarted(int index, const QString &actionId); + void actionCompleted(int index, const QString &actionId, const QVariantMap &results); + void actionFailed(int index, const QString &actionId, const QString &error); + + // 執行請求 + void executeActionRequested(const TestAction &action); + void resultDisplayRequested(const QString &displayRef, const ResultDisplay &display); + + // 表格信號 + void tableInitialized(const QString &tableRef, const TableDefinition &definition); + void tableDataChanged(const QString &tableRef, int row, int column, const QVariant &value); + +private: + explicit ProcedureEngine(QObject *parent = nullptr); + ~ProcedureEngine(); + + // 執行控制 + void resetExecutionContext(); + void setStatus(ProcedureStatus status); + + // 活動執行 + void moveToFirstActivity(); + void enterCurrentActivity(); + void startTaskGroupExecution(const QString &taskGroupRef); + void enterCurrentStage(const QString &taskGroupRef); + void moveToNextStage(const QString &taskGroupRef); + void completeTaskGroup(const QString &taskGroupRef); + + // 動作執行 + void executeCurrentAction(); + void moveToNextAction(); + void processActionResults(const QString &actionId, const QVariantMap &results); + + // 結果顯示 + void displayResult(const QString &displayRef); + void completeProcedure(); + + // 表格管理 + void initializeTable(const QString &tableRef); + + // 條件評估 + bool evaluateCondition(const QString &condition); + +private slots: + void onExecutionTimeout(); + +private: + static ProcedureEngine *s_instance; + + ProcedureParser *m_parser; + ProcedureStatus m_status; + ProcedureConfig m_config; + ExecutionContext m_context; + QString m_configPath; + + QTimer *m_executionTimer; + QMap>> m_tableData; +}; + +#endif // PROCEDUREENGINE_H diff --git a/procedure/proceduremanager.cpp b/procedure/proceduremanager.cpp new file mode 100644 index 0000000..b8fdc27 --- /dev/null +++ b/procedure/proceduremanager.cpp @@ -0,0 +1,655 @@ +#include "proceduremanager.h" +#include "procedureparser.h" +#include +#include +#include +#include +#include +#include +#include +#include + +ProcedureManager::ProcedureManager(QObject *parent) + : QObject(parent) +{ + // 不再自动创建示例数据,等待调用 loadProcedureList +} + +ProcedureManager::~ProcedureManager() +{ +} + +QVector ProcedureManager::loadProcedureList(const QString &directory) +{ + m_procedureDirectory = directory; + m_procedureList.clear(); + + QDir dir(directory); + if (!dir.exists()) + { + qWarning() << "Procedure directory does not exist:" << directory; + createSampleProcedures(); + return m_procedureList; + } + + // 扫描目录中的 YAML 和 JSON 文件 + QStringList filters; + filters << "*.yaml" << "*.yml" << "*.json"; + dir.setNameFilters(filters); + + QFileInfoList files = dir.entryInfoList(QDir::Files | QDir::Readable); + qDebug() << "Found" << files.size() << "procedure files in" << directory; + + for (const QFileInfo &fileInfo : files) + { + QString filePath = fileInfo.absoluteFilePath(); + qDebug() << "Scanning procedure file:" << filePath; + + // 使用 ProcedureParser 解析文件 + ProcedureParser parser; + if (parser.loadConfig(filePath)) + { + ProcedureConfig config = parser.parseProcedureConfig(); + + // 创建摘要信息 + ProcedureSummary summary; + summary.id = config.procedureId; + summary.name = config.procedureName; + summary.version = config.version; + summary.description = config.description; + summary.filePath = filePath; + + m_procedureList.append(summary); + + qDebug() << "Loaded procedure:" << summary.name + << "ID:" << summary.id + << "Version:" << summary.version; + } + else + { + qWarning() << "Failed to parse procedure file:" << filePath; + } + } + + // 如果没有找到任何文件,创建示例数据 + if (m_procedureList.isEmpty()) + { + qDebug() << "No valid procedure files found, creating sample data"; + createSampleProcedures(); + } + else + { + // 即使找到了真实文件,也添加mock异常数据用于测试 + qDebug() << "Adding mock exception data for testing"; + createMockExceptionData(); + } + + return m_procedureList; +} + +QVector ProcedureManager::searchProcedures(const QString &keyword) +{ + if (keyword.isEmpty()) + { + return m_procedureList; + } + + QVector results; + for (const auto &proc : m_procedureList) + { + if (proc.id.contains(keyword, Qt::CaseInsensitive) || + proc.name.contains(keyword, Qt::CaseInsensitive)) + { + results.append(proc); + } + } + return results; +} + +ProcedureData ProcedureManager::loadProcedure(const QString &procedureId) +{ + if (m_loadedProcedures.contains(procedureId)) + { + return m_loadedProcedures[procedureId]; + } + + // 创建示例规程 + ProcedureData proc; + proc.id = procedureId; + proc.workOrderId = m_workOrderId; + + if (procedureId == "KMCIXRCP503") + { + proc.name = "一回路温度传感器绝缘和连续性检查:TP RCP63"; + proc.version = "C4"; + proc.description = "本程序适用于热停堆(热功率稳定)工况下执行RCP63程序"; + + // 创建任务组 + TaskGroup group1; + group1.id = "preInspection"; + group1.name = "5.1 试验前状态说明和检查"; + + // 添加步骤 + StepData step1; + step1.id = "step_001"; + step1.type = StepType::Manual; + step1.content = "本程序适用于热停堆(热功率稳定)工况下执行RCP63程序(K-MT-I-X-RCP-501)统一进行Sensor Check 部分使用。"; + step1.status = StepStatus::Confirmed; + group1.steps.append(step1); + + StepData step2; + step2.id = "step_002"; + step2.type = StepType::Automatic; + step2.content = "执行前确认KIC中一回路三个环路平均温度变化量不超过0.2℃;"; + step2.status = StepStatus::Passed; + step2.tableRefs.append("temperatureCheckTable"); + step2.highlightFields.append("loop1Temp"); + step2.highlightFields.append("loop2Temp"); + step2.highlightFields.append("loop3Temp"); + group1.steps.append(step2); + + StepData step3; + step3.id = "step_003"; + step3.type = StepType::Automatic; + step3.content = "若KIC中显示的一回路三个环路平均温度变化超过0.2℃,则采取滑动平均的计算方式记录滑动周期: (建议60s), 一环路平均温度滑动平均最大最小值之差;二环路平均温度滑动平均最大最小值之差;三环路平均温度滑动平均最大最小值之差;每个数据测量的最短时间(应大于滑动周期):。确认三个环路平均温度的滑动平均最大与最小值之差不超过0.2℃。"; + step3.status = StepStatus::Pending; + group1.steps.append(step3); + + StepData step4; + step4.id = "step_004"; + step4.type = StepType::Manual; + step4.content = "确认 RPR、RPN上无相关的试验检修工作。"; + step4.status = StepStatus::Pending; + group1.steps.append(step4); + + StepData step5; + step5.id = "step_005"; + step5.type = StepType::Automatic; + step5.content = "按附表1检查相关模拟量指示并记录,确认结果满意。"; + step5.status = StepStatus::Pending; + step5.tableRefs.append("analogReadingsTable"); + group1.steps.append(step5); + + StepData step6; + step6.id = "step_006"; + step6.type = StepType::Automatic; + step6.content = "试验前根据附表1报警清单检查报警,确认无异常报警,方可进行后续检查工作"; + step6.status = StepStatus::Pending; + step6.tableRefs.append("alarmCheckTable"); + group1.steps.append(step6); + + proc.taskGroups.append(group1); + + // 创建表格 + TableData tempTable; + tempTable.id = "temperatureCheckTable"; + tempTable.name = "环路平均温度变化"; + tempTable.description = "检验前后的环路温度数据"; + + TableField f1; + f1.id = "loop1Temp"; + f1.name = "一环路平均温度"; + f1.type = "numeric"; + f1.unit = "℃"; + TableField f2; + f2.id = "loop2Temp"; + f2.name = "二环路平均温度"; + f2.type = "numeric"; + f2.unit = "℃"; + TableField f3; + f3.id = "loop3Temp"; + f3.name = "三环路平均温度"; + f3.type = "numeric"; + f3.unit = "℃"; + tempTable.columns.append(f1); + tempTable.columns.append(f2); + tempTable.columns.append(f3); + proc.tables.append(tempTable); + + TableData analogTable; + analogTable.id = "analogReadingsTable"; + analogTable.name = "实验前后模拟量数据"; + proc.tables.append(analogTable); + + TableData alarmTable; + alarmTable.id = "alarmCheckTable"; + alarmTable.name = "试验前后报警检查"; + proc.tables.append(alarmTable); + + // 计算步骤数 + proc.totalSteps = group1.steps.size(); + proc.completedSteps = 2; // 已完成2个步骤 + } + else if (procedureId == "SIMPLE63") + { + proc.name = "范例 一回路温度传感器绝缘和连续性检查 TP RCP63"; + proc.version = "SIMPLE"; + proc.description = "范例规程"; + proc.totalSteps = 6; + proc.completedSteps = 0; + } + else if (procedureId == "SIMPLE_TEST") + { + proc.name = "简单测试规程"; + proc.version = "1.0"; + proc.description = "这是一个简单的测试规程,用于演示 ProcedurePlayer 的基本功能。包含一个测试任务组和结果展示。"; + proc.totalSteps = 4; + proc.completedSteps = 0; + } + else + { + // 尝试从 JSON 文件加载 mock 数据 + proc = loadMockProcedureFromJson(procedureId); + + // 如果 JSON 中没有找到,返回空数据 + if (proc.name.isEmpty()) + { + qWarning() << "Procedure not found:" << procedureId; + } + } + + m_loadedProcedures[procedureId] = proc; + emit procedureLoaded(proc); + return proc; +} + +void ProcedureManager::setWorkOrderId(const QString &workOrderId) +{ + m_workOrderId = workOrderId; +} + +bool ProcedureManager::confirmStep(const QString &procedureId, const QString &stepId) +{ + if (!m_loadedProcedures.contains(procedureId)) + { + return false; + } + + auto &proc = m_loadedProcedures[procedureId]; + for (auto &group : proc.taskGroups) + { + for (auto &step : group.steps) + { + if (step.id == stepId) + { + step.status = StepStatus::Confirmed; + step.executedAt = QDateTime::currentDateTime(); + emit stepStatusChanged(stepId, StepStatus::Confirmed); + return true; + } + } + } + return false; +} + +bool ProcedureManager::cancelStepConfirm(const QString &procedureId, const QString &stepId, const QString &reason) +{ + if (!m_loadedProcedures.contains(procedureId)) + { + return false; + } + + auto &proc = m_loadedProcedures[procedureId]; + for (auto &group : proc.taskGroups) + { + for (auto &step : group.steps) + { + if (step.id == stepId) + { + step.status = StepStatus::Pending; + step.cancelReason = reason; + emit stepStatusChanged(stepId, StepStatus::Pending); + return true; + } + } + } + return false; +} + +bool ProcedureManager::executeAutoStep(const QString &procedureId, const QString &stepId) +{ + // 模拟自动步骤执行 + if (!m_loadedProcedures.contains(procedureId)) + { + return false; + } + + auto &proc = m_loadedProcedures[procedureId]; + for (auto &group : proc.taskGroups) + { + for (auto &step : group.steps) + { + if (step.id == stepId && step.type == StepType::Automatic) + { + step.status = StepStatus::InProgress; + emit stepStatusChanged(stepId, StepStatus::InProgress); + // 实际应用中这里会执行真正的自动化逻辑 + return true; + } + } + } + return false; +} + +TableData ProcedureManager::getTableData(const QString &procedureId, const QString &tableId) +{ + if (m_loadedProcedures.contains(procedureId)) + { + const auto &proc = m_loadedProcedures[procedureId]; + for (const auto &table : proc.tables) + { + if (table.id == tableId) + { + return table; + } + } + } + return TableData(); +} + +bool ProcedureManager::updateTableData(const QString &procedureId, const QString &tableId, const QVector &rows) +{ + if (!m_loadedProcedures.contains(procedureId)) + { + return false; + } + + auto &proc = m_loadedProcedures[procedureId]; + for (auto &table : proc.tables) + { + if (table.id == tableId) + { + table.rows = rows; + emit tableDataUpdated(tableId); + return true; + } + } + return false; +} + +void ProcedureManager::createSampleProcedures() +{ + m_procedureList.clear(); + + ProcedureSummary p1; + p1.id = "KMCIXRCP503"; + p1.name = "一回路温度传感器绝缘和连续性检查:TP RCP63"; + p1.version = "C4"; + p1.description = ""; + p1.filePath = "[SAMPLE]"; + m_procedureList.append(p1); + + ProcedureSummary p2; + p2.id = "SIMPLE63"; + p2.name = "范例 一回路温度传感器绝缘和连续性检查 TP RCP63"; + p2.version = "SIMPLE"; + p2.description = ""; + p2.filePath = "[SAMPLE]"; + m_procedureList.append(p2); + + ProcedureSummary p3; + p3.id = "SIMPLE_TEST"; + p3.name = "简单测试规程"; + p3.version = "1.0"; + p3.description = "这是一个简单的测试规程,用于演示 ProcedurePlayer 的基本功能。包含一个测试任务组和结果展示。"; + p3.filePath = "[SAMPLE]"; + m_procedureList.append(p3); + + // 也添加异常数据 + createMockExceptionData(); +} + +void ProcedureManager::createMockExceptionData() +{ + ProcedureSummary p7; + p7.id = "EMPTY_DESC"; + p7.name = "空描述测试"; + p7.version = "1.0"; + p7.description = ""; + p7.filePath = "[MOCK_EXCEPTION]"; + m_procedureList.append(p7); + + // Mock 异常数据 - 超长描述 + ProcedureSummary p8; + p8.id = "LONG_DESC"; + p8.name = "超长描述测试"; + p8.version = "2.0"; + p8.description = "这是一个包含超长描述的测试规程,用于验证当描述文本过长时,界面能否正确处理和显示。" + "描述内容可能包含多个段落、技术细节、注意事项等。在实际应用中,规程描述可能会非常详细," + "包括前置条件、执行步骤概述、预期结果、风险提示、相关文档引用等信息。这样的长描述需要" + "界面能够适当地截断、折叠或提供滚动功能来确保用户体验。"; + p8.filePath = "[MOCK_EXCEPTION]"; + m_procedureList.append(p8); + + // Mock 异常数据 - 数字和符号混合的 ID + ProcedureSummary p9; + p9.id = "TEST-2024-12-31_V1.0"; + p9.name = "日期版本号混合 ID"; + p9.version = "2024.12.31"; + p9.description = "测试复杂的 ID 和版本号格式"; + p9.filePath = "[MOCK_EXCEPTION]"; + m_procedureList.append(p9); + + // Mock 异常数据 - 换行符 + ProcedureSummary p10; + p10.id = "MULTILINE"; + p10.name = "多行\n文本\n测试"; + p10.version = "1.0"; + p10.description = "第一行描述\n第二行描述\n第三行描述"; + p10.filePath = "[MOCK_EXCEPTION]"; + m_procedureList.append(p10); +} + +StepType ProcedureManager::parseStepType(const QString &typeStr) +{ + if (typeStr == "Manual") + return StepType::Manual; + if (typeStr == "Automatic") + return StepType::Automatic; + return StepType::Manual; +} + +StepStatus ProcedureManager::parseStepStatus(const QString &statusStr) +{ + if (statusStr == "Pending") + return StepStatus::Pending; + if (statusStr == "InProgress") + return StepStatus::InProgress; + if (statusStr == "Confirmed") + return StepStatus::Confirmed; + if (statusStr == "Passed") + return StepStatus::Passed; + if (statusStr == "Failed") + return StepStatus::Failed; + if (statusStr == "Skipped") + return StepStatus::Skipped; + return StepStatus::Pending; +} + +ProcedureData ProcedureManager::loadMockProcedureFromJson(const QString &procedureId) +{ + ProcedureData proc; + proc.id = procedureId; + proc.workOrderId = m_workOrderId; + + // 尝试多个可能的 JSON 文件路径 + QStringList possiblePaths; + possiblePaths << m_procedureDirectory + "/mock_procedures.json"; + possiblePaths << QDir::currentPath() + "/procedures/mock_procedures.json"; + possiblePaths << QCoreApplication::applicationDirPath() + "/procedures/mock_procedures.json"; + possiblePaths << QCoreApplication::applicationDirPath() + "/../../../procedures/mock_procedures.json"; + + QString jsonFilePath; + for (const QString &path : possiblePaths) + { + if (QFile::exists(path)) + { + jsonFilePath = path; + qDebug() << "Found mock procedures JSON at:" << path; + break; + } + } + + if (jsonFilePath.isEmpty()) + { + qWarning() << "Mock procedures JSON file not found. Tried paths:" << possiblePaths; + return proc; + } + + // 读取 JSON 文件 + QFile file(jsonFilePath); + if (!file.open(QIODevice::ReadOnly)) + { + qWarning() << "Failed to open mock procedures JSON:" << jsonFilePath; + return proc; + } + + QByteArray jsonData = file.readAll(); + file.close(); + + QJsonDocument doc = QJsonDocument::fromJson(jsonData); + if (!doc.isObject()) + { + qWarning() << "Invalid JSON format in mock procedures file"; + return proc; + } + + QJsonObject root = doc.object(); + QJsonArray mockProcedures = root["mockProcedures"].toArray(); + + // 查找匹配的规程 + for (const QJsonValue &value : mockProcedures) + { + QJsonObject procObj = value.toObject(); + if (procObj["id"].toString() != procedureId) + continue; + + // 解析基本信息 + proc.name = procObj["name"].toString(); + proc.version = procObj["version"].toString(); + proc.description = procObj["description"].toString(); + + // 解析任务组 + QJsonArray taskGroupsArray = procObj["taskGroups"].toArray(); + for (const QJsonValue &groupValue : taskGroupsArray) + { + QJsonObject groupObj = groupValue.toObject(); + TaskGroup group; + group.id = groupObj["id"].toString(); + group.name = groupObj["name"].toString(); + + // 解析步骤 + QJsonArray stepsArray = groupObj["steps"].toArray(); + for (const QJsonValue &stepValue : stepsArray) + { + QJsonObject stepObj = stepValue.toObject(); + StepData step; + step.id = stepObj["id"].toString(); + step.content = stepObj["content"].toString(); + step.type = parseStepType(stepObj["type"].toString()); + step.status = parseStepStatus(stepObj["status"].toString()); + + // 解析表格引用 + if (stepObj.contains("tableRefs")) + { + QJsonArray tableRefsArray = stepObj["tableRefs"].toArray(); + for (const QJsonValue &refValue : tableRefsArray) + { + step.tableRefs.append(refValue.toString()); + } + } + + // 解析高亮字段 + if (stepObj.contains("highlightFields")) + { + QJsonArray highlightArray = stepObj["highlightFields"].toArray(); + for (const QJsonValue &fieldValue : highlightArray) + { + step.highlightFields.append(fieldValue.toString()); + } + } + + group.steps.append(step); + } + + proc.taskGroups.append(group); + } + + // 解析表格 + if (procObj.contains("tables")) + { + QJsonArray tablesArray = procObj["tables"].toArray(); + for (const QJsonValue &tableValue : tablesArray) + { + QJsonObject tableObj = tableValue.toObject(); + TableData table; + table.id = tableObj["id"].toString(); + table.name = tableObj["name"].toString(); + if (tableObj.contains("description")) + table.description = tableObj["description"].toString(); + + // 解析列 + if (tableObj.contains("columns")) + { + QJsonArray columnsArray = tableObj["columns"].toArray(); + for (const QJsonValue &colValue : columnsArray) + { + QJsonObject colObj = colValue.toObject(); + TableField field; + field.id = colObj["id"].toString(); + field.name = colObj["name"].toString(); + field.type = colObj["type"].toString(); + if (colObj.contains("unit")) + field.unit = colObj["unit"].toString(); + if (colObj.contains("isRequired")) + field.isRequired = colObj["isRequired"].toBool(); + if (colObj.contains("isHighlighted")) + field.isHighlighted = colObj["isHighlighted"].toBool(); + + table.columns.append(field); + } + } + + proc.tables.append(table); + } + } + + // 计算步骤统计 + if (procObj.contains("totalSteps")) + { + proc.totalSteps = procObj["totalSteps"].toInt(); + } + else + { + proc.totalSteps = 0; + for (const TaskGroup &group : proc.taskGroups) + { + proc.totalSteps += group.steps.size(); + } + } + + if (procObj.contains("completedSteps")) + { + proc.completedSteps = procObj["completedSteps"].toInt(); + } + else + { + proc.completedSteps = 0; + for (const TaskGroup &group : proc.taskGroups) + { + for (const StepData &step : group.steps) + { + if (step.status == StepStatus::Confirmed || step.status == StepStatus::Passed) + { + proc.completedSteps++; + } + } + } + } + + qDebug() << "Loaded mock procedure from JSON:" << procedureId; + break; + } + + return proc; +} diff --git a/procedure/proceduremanager.h b/procedure/proceduremanager.h new file mode 100644 index 0000000..e03f512 --- /dev/null +++ b/procedure/proceduremanager.h @@ -0,0 +1,58 @@ +#ifndef PROCEDUREMANAGER_H +#define PROCEDUREMANAGER_H + +#include +#include +#include +#include "proceduredata.h" + +class ProcedureManager : public QObject +{ + Q_OBJECT + +public: + explicit ProcedureManager(QObject *parent = nullptr); + ~ProcedureManager(); + + // 加载规程列表 + QVector loadProcedureList(const QString &directory); + + // 搜索规程 + QVector searchProcedures(const QString &keyword); + + // 加载规程详情 + ProcedureData loadProcedure(const QString &procedureId); + + // 设置工单号 + void setWorkOrderId(const QString &workOrderId); + QString workOrderId() const { return m_workOrderId; } + + // 步骤操作 + bool confirmStep(const QString &procedureId, const QString &stepId); + bool cancelStepConfirm(const QString &procedureId, const QString &stepId, const QString &reason); + bool executeAutoStep(const QString &procedureId, const QString &stepId); + + // 获取表格数据 + TableData getTableData(const QString &procedureId, const QString &tableId); + bool updateTableData(const QString &procedureId, const QString &tableId, const QVector &rows); + +signals: + void procedureLoaded(const ProcedureData &procedure); + void stepStatusChanged(const QString &stepId, StepStatus status); + void tableDataUpdated(const QString &tableId); + +private: + ProcedureData parseProcedureYaml(const QString &filePath); + void createSampleProcedures(); + void createMockExceptionData(); + ProcedureData loadMockProcedureFromJson(const QString &procedureId); + StepType parseStepType(const QString &typeStr); + StepStatus parseStepStatus(const QString &statusStr); + + QString m_workOrderId; + QString m_procedureDirectory; + QVector m_procedureList; + QMap m_loadedProcedures; +}; + +#endif // PROCEDUREMANAGER_H diff --git a/procedure/procedureparser.cpp b/procedure/procedureparser.cpp new file mode 100644 index 0000000..62be570 --- /dev/null +++ b/procedure/procedureparser.cpp @@ -0,0 +1,1594 @@ +#include "procedureparser.h" + +#include +#include +#include + +#include +#include + +ProcedureParser::ProcedureParser(QObject *parent) + : QObject(parent), tree(std::make_unique()) {} + +ProcedureParser::~ProcedureParser() {} + +bool ProcedureParser::loadConfig(const QString &filePath) +{ + configFilePath = filePath; + validationErrors.clear(); + + QFileInfo fileInfo(filePath); + QString suffix = fileInfo.suffix().toLower(); + + bool success = false; + if (suffix == "json" || suffix == "yaml" || suffix == "yml") + { + success = parseFile(filePath); + } + else + { + validationErrors << QString("Unsupported file format: %1").arg(suffix); + emit configError("Unsupported file format", 0, 0); + return false; + } + + if (success) + { + buildReferenceCache(); + emit configLoaded(filePath); + } + + return success; +} + +bool ProcedureParser::parseFile(const QString &filePath) +{ + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + validationErrors << QString("Failed to open file: %1").arg(filePath); + emit configError("Failed to open file", 0, 0); + return false; + } + + QByteArray fileContent = file.readAll(); + file.close(); + + QFileInfo fileInfo(filePath); + QString suffix = fileInfo.suffix().toLower(); + + try + { + yamlContent.assign(fileContent.begin(), fileContent.end()); + + // Use appropriate parser based on file type + if (suffix == "json") + { + *tree = ryml::parse_json_in_arena(ryml::to_csubstr(yamlContent)); + } + else + { + // For YAML files, parse and resolve anchors/aliases + *tree = ryml::parse_in_arena(ryml::to_csubstr(yamlContent)); + + // Resolve all references (anchors and aliases) + tree->resolve(); + } + + return true; + } + catch (const std::exception &e) + { + validationErrors << QString("Parsing error: %1").arg(e.what()); + emit configError(QString("Parsing error: %1").arg(e.what()), 0, 0); + return false; + } +} + +void ProcedureParser::buildReferenceCache() +{ + referenceCache.clear(); + + if (!tree || tree->empty()) + { + return; + } + + ryml::ConstNodeRef root = tree->rootref(); + if (!root.valid()) + { + return; + } + + // Cache all top-level collections that can be referenced according to + // schema.json + QStringList collections = {"tables", "testTaskGroups", "testActivityGroups", + "resultDisplays", "testActions", "fieldDefinitions"}; + + for (const QString &collection : collections) + { + std::string collectionStr = collection.toStdString(); + ryml::csubstr collectionKey(collectionStr.data(), collectionStr.size()); + + if (root.has_child(collectionKey)) + { + ryml::ConstNodeRef collectionNode = root[collectionKey]; + + if (collectionNode.is_map()) + { + // For map-type collections, cache each child with its key + for (ryml::ConstNodeRef child : collectionNode.children()) + { + if (child.has_key()) + { + ryml::csubstr key = child.key(); + QString keyQString = QString::fromUtf8(key.data(), key.size()); + QString refPath = QString("#/%1/%2").arg(collection, keyQString); + referenceCache[refPath] = child; + + qDebug() << "Cached reference:" << refPath; + } + } + } + } + } + + qDebug() << "Built reference cache with" << referenceCache.size() << "entries"; +} + +ryml::ConstNodeRef ProcedureParser::resolveReference(const QString &ref) +{ + // First check cache for performance + if (referenceCache.contains(ref)) + { + return referenceCache[ref]; + } + + // If not in cache, try to navigate the tree manually + if (!tree || tree->empty()) + { + qWarning() << "Cannot resolve reference: tree is empty"; + return ryml::ConstNodeRef(); + } + + // Parse reference path (e.g., "#/tables/resistanceMeasurementTable") + if (!ref.startsWith("#/")) + { + qWarning() << "Invalid reference format (must start with #/):" << ref; + return ryml::ConstNodeRef(); + } + + QStringList parts = ref.mid(2).split('/', Qt::SkipEmptyParts); + if (parts.isEmpty()) + { + qWarning() << "Invalid reference path (no components):" << ref; + return ryml::ConstNodeRef(); + } + + ryml::ConstNodeRef current = tree->rootref(); + + for (const QString &part : parts) + { + std::string partStr = part.toStdString(); + ryml::csubstr partKey(partStr.data(), partStr.size()); + + if (!current.has_child(partKey)) + { + qWarning() << "Reference path not found:" << ref << "at component:" << part; + return ryml::ConstNodeRef(); + } + current = current[partKey]; + } + + // Cache the resolved reference for future use + referenceCache[ref] = current; + + return current; +} + +QString ProcedureParser::extractRefPath(const ryml::ConstNodeRef &node) +{ + // Check if node has a "$ref" key (JSON-style references) + if (node.is_map() && node.has_child("$ref")) + { + ryml::ConstNodeRef refNode = node["$ref"]; + if (refNode.is_val()) + { + ryml::csubstr val = refNode.val(); + return QString::fromUtf8(val.data(), val.size()); + } + } + + return QString(); +} + +QString ProcedureParser::nodeToQString(const ryml::ConstNodeRef &node) +{ + if (node.invalid() || !node.has_val()) + { + return QString(); + } + + // Convert node value to QString + ryml::csubstr val = node.val(); + return QString::fromUtf8(val.data(), val.size()); +} + +QVariant ProcedureParser::nodeToQVariant(const ryml::ConstNodeRef &node) +{ + if (node.invalid()) + { + return QVariant(); + } + + if (node.is_map()) + { + return nodeToQVariantMap(node); + } + else if (node.is_seq()) + { + return nodeToQVariantList(node); + } + else if (node.is_val()) + { + ryml::csubstr val = node.val(); + QString str = QString::fromUtf8(val.data(), val.size()); + + // Try to convert to appropriate type + bool ok; + + // Try integer first (most common for numeric values) + int intVal = str.toInt(&ok); + if (ok) + { + return intVal; + } + + // Try double + double doubleVal = str.toDouble(&ok); + if (ok) + { + return doubleVal; + } + + // Try boolean (case-insensitive) + QString lower = str.toLower(); + if (lower == "true") + { + return true; + } + else if (lower == "false") + { + return false; + } + + // Return as string + return str; + } + + return QVariant(); +} + +QVariantMap ProcedureParser::nodeToQVariantMap(const ryml::ConstNodeRef &node) +{ + QVariantMap map; + + if (!node.is_map()) + { + return map; + } + + for (ryml::ConstNodeRef child : node.children()) + { + if (child.has_key()) + { + ryml::csubstr keyData = child.key(); + map.insert(QString::fromUtf8(keyData.data(), keyData.size()), nodeToQVariant(child)); + } + } + + return map; +} + +QVariantList ProcedureParser::nodeToQVariantList(const ryml::ConstNodeRef &node) +{ + QVariantList list; + + if (!node.is_seq()) + { + return list; + } + + list.reserve(node.num_children()); + for (ryml::ConstNodeRef child : node.children()) + { + list.append(nodeToQVariant(child)); + } + + return list; +} + +FieldDefinition ProcedureParser::parseFieldDefinition(const ryml::ConstNodeRef &node) +{ +#ifdef _WIN32 + VMProtectBeginVirtualizationLockByKey("ProcedureParser::parseFieldDefinition"); +#endif + + FieldDefinition field; + + if (node.invalid()) + { + return field; + } + + // Handle JSON $ref references + QString refPath = extractRefPath(node); + if (!refPath.isEmpty()) + { + ryml::ConstNodeRef resolvedNode = resolveReference(refPath); + if (!resolvedNode.invalid()) + { + return parseFieldDefinition(resolvedNode); + } + else + { + qWarning() << "Failed to resolve field definition reference:" << refPath; + return field; + } + } + + if (!node.is_map()) + { + return field; + } + + // Parse basic properties + if (node.has_child("id")) + { + field.id = nodeToQString(node["id"]); + } + if (node.has_child("name")) + { + field.name = nodeToQString(node["name"]); + } + if (node.has_child("type")) + { + field.type = nodeToQString(node["type"]); + } + if (node.has_child("defaultValue")) + { + field.defaultValue = nodeToQVariant(node["defaultValue"]); + } + if (node.has_child("options")) + { + QVariantList optionsList = nodeToQVariantList(node["options"]); + for (const QVariant &opt : optionsList) + { + field.options.append(opt.toString()); + } + } + if (node.has_child("formula")) + { + field.formula = nodeToQString(node["formula"]); + } + if (node.has_child("validationRules")) + { + field.validationRules = nodeToQVariantMap(node["validationRules"]); + } + if (node.has_child("isRequired")) + { + field.isRequired = nodeToQVariant(node["isRequired"]).toBool(); + } + if (node.has_child("isReadOnly")) + { + field.isReadOnly = nodeToQVariant(node["isReadOnly"]).toBool(); + } + if (node.has_child("unit")) + { + field.unit = nodeToQString(node["unit"]); + } + if (node.has_child("description")) + { + field.description = nodeToQString(node["description"]); + } + if (node.has_child("uploadImmediately")) + { + field.uploadImmediately = nodeToQVariant(node["uploadImmediately"]).toBool(); + } + if (node.has_child("layoutConfig")) + { + field.layoutConfig = nodeToQVariantMap(node["layoutConfig"]); + } + + return field; + +#ifdef _WIN32 + VMProtectEnd(); +#endif +} + +TableDefinition ProcedureParser::parseTableDefinition(const ryml::ConstNodeRef &node) +{ +#ifdef _WIN32 + VMProtectBeginVirtualizationLockByKey("ProcedureParser::parseTableDefinition"); +#endif + + TableDefinition table; + + if (node.invalid()) + { + return table; + } + + // Handle JSON $ref references + QString refPath = extractRefPath(node); + if (!refPath.isEmpty()) + { + ryml::ConstNodeRef resolvedNode = resolveReference(refPath); + if (!resolvedNode.invalid()) + { + return parseTableDefinition(resolvedNode); + } + else + { + qWarning() << "Failed to resolve table definition reference:" << refPath; + return table; + } + } + + if (!node.is_map()) + { + return table; + } + + // Parse basic properties + if (node.has_child("id")) + { + table.id = nodeToQString(node["id"]); + } + if (node.has_child("name")) + { + table.name = nodeToQString(node["name"]); + } + if (node.has_child("description")) + { + table.description = nodeToQString(node["description"]); + } + if (node.has_child("tableType")) + { + table.tableType = nodeToQString(node["tableType"]); + } + else + { + table.tableType = "grid"; // Default + } + if (node.has_child("layoutConfig")) + { + table.layoutConfig = nodeToQVariantMap(node["layoutConfig"]); + } + if (node.has_child("isShared")) + { + table.isShared = nodeToQVariant(node["isShared"]).toBool(); + } + if (node.has_child("uploadStrategy")) + { + table.uploadStrategy = nodeToQString(node["uploadStrategy"]); + } + else + { + table.uploadStrategy = "onComplete"; // Default + } + + // Parse column headers (for grid-type tables) + if (node.has_child("columnHeaders")) + { + ryml::ConstNodeRef colHeaders = node["columnHeaders"]; + if (colHeaders.is_seq()) + { + for (ryml::ConstNodeRef colNode : colHeaders.children()) + { + if (!colNode.valid()) + { + continue; + } + table.columnHeaders.append(parseFieldDefinition(colNode)); + } + } + } + + // Parse row headers (for grid-type tables) + if (node.has_child("rowHeaders")) + { + ryml::ConstNodeRef rowHeaders = node["rowHeaders"]; + if (rowHeaders.is_seq()) + { + for (ryml::ConstNodeRef rowNode : rowHeaders.children()) + { + if (rowNode.has_child("id") && rowNode.has_child("name")) + { + QString id = nodeToQString(rowNode["id"]); + QString name = nodeToQString(rowNode["name"]); + table.rowHeaders.append(qMakePair(id, name)); + } + } + } + } + + // Parse fields (for form-type and series tables) + if (node.has_child("fields")) + { + ryml::ConstNodeRef fields = node["fields"]; + if (fields.is_seq()) + { + for (ryml::ConstNodeRef fieldNode : fields.children()) + { + if (!fieldNode.valid()) + { + continue; + } + table.fields.append(parseFieldDefinition(fieldNode)); + } + } + } + + // Parse static cells + if (node.has_child("staticCells")) + { + ryml::ConstNodeRef staticCells = node["staticCells"]; + if (staticCells.is_seq()) + { + for (ryml::ConstNodeRef cellNode : staticCells.children()) + { + if (!cellNode.valid()) + { + continue; + } + table.staticCells.append(parseStaticCell(cellNode)); + } + } + } + + return table; + +#ifdef _WIN32 + VMProtectEnd(); +#endif +} + +StaticCell ProcedureParser::parseStaticCell(const ryml::ConstNodeRef &node) +{ + StaticCell cell; + + if (node.invalid()) + { + return cell; + } + + if (!node.is_map()) + { + return cell; + } + + // Parse field ID (for form-type tables) + if (node.has_child("field")) + { + cell.field = nodeToQString(node["field"]); + } + + // Parse row and column (for grid-type tables) + if (node.has_child("row")) + { + QString rowStr = nodeToQString(node["row"]); + cell.row = rowStr.toInt(); + } + if (node.has_child("column")) + { + QString colStr = nodeToQString(node["column"]); + cell.column = colStr.toInt(); + } + + // Parse content + if (node.has_child("content")) + { + cell.content = nodeToQString(node["content"]); + } + + return cell; +} + +FieldSelector ProcedureParser::parseFieldSelector(const ryml::ConstNodeRef &node) +{ +#ifdef _WIN32 + VMProtectBeginVirtualizationLockByKey("ProcedureParser::parseFieldSelector"); +#endif + + FieldSelector selector; + + if (node.invalid()) + { + return selector; + } + + // Handle JSON $ref references + QString refPath = extractRefPath(node); + if (!refPath.isEmpty()) + { + ryml::ConstNodeRef resolvedNode = resolveReference(refPath); + if (!resolvedNode.invalid()) + { + return parseFieldSelector(resolvedNode); + } + else + { + qWarning() << "Failed to resolve field selector reference:" << refPath; + return selector; + } + } + + if (!node.is_map()) + { + return selector; + } + + if (node.has_child("tableRef")) + { + selector.tableRef = nodeToQString(node["tableRef"]); + } + + if (node.has_child("fields")) + { + QVariantList fieldsList = nodeToQVariantList(node["fields"]); + for (const QVariant &field : fieldsList) + { + selector.fields.append(field.toString()); + } + } + + if (node.has_child("cells")) + { + ryml::ConstNodeRef cells = node["cells"]; + if (cells.is_seq()) + { + for (ryml::ConstNodeRef cellNode : cells.children()) + { + if (cellNode.has_child("row") && cellNode.has_child("column")) + { + QString row = nodeToQString(cellNode["row"]); + QString column = nodeToQString(cellNode["column"]); + selector.cells.append(qMakePair(row, column)); + } + } + } + } + + if (node.has_child("ignore")) + { + selector.ignore = nodeToQVariant(node["ignore"]).toBool(); + } + + return selector; + +#ifdef _WIN32 + VMProtectEnd(); +#endif +} + +TestAction ProcedureParser::parseTestAction(const ryml::ConstNodeRef &node) +{ + TestAction action; + + if (node.invalid()) + { + qWarning() << "parseTestAction: invalid node"; + return action; + } + + qDebug() << "parseTestAction: node is_map:" << node.is_map() << "is_val:" << node.is_val() + << "is_ref:" << node.is_ref() << "has_val:" << node.has_val(); + + // Handle JSON $ref references + QString refPath = extractRefPath(node); + if (!refPath.isEmpty()) + { + qDebug() << "parseTestAction: resolving reference:" << refPath; + ryml::ConstNodeRef resolvedNode = resolveReference(refPath); + if (!resolvedNode.invalid()) + { + return parseTestAction(resolvedNode); + } + else + { + qWarning() << "Failed to resolve test action reference:" << refPath; + return action; + } + } + + if (!node.is_map()) + { + qWarning() << "parseTestAction: node is not a map, cannot parse"; + return action; + } + + if (node.has_child("id")) + { + action.id = nodeToQString(node["id"]); + } + if (node.has_child("name")) + { + action.name = nodeToQString(node["name"]); + } + if (node.has_child("document")) + { + action.document = nodeToQString(node["document"]); + } + if (node.has_child("mode")) + { + action.mode = nodeToQString(node["mode"]); + } + if (node.has_child("sequence")) + { + action.sequence = nodeToQString(node["sequence"]); + } + if (node.has_child("functionType")) + { + action.functionType = nodeToQString(node["functionType"]); + } + if (node.has_child("channel")) + { + action.channel = nodeToQString(node["channel"]); + } + if (node.has_child("functionParameters")) + { + // functionParameters can be object or array per schema + action.functionParameters = nodeToQVariant(node["functionParameters"]); + } + if (node.has_child("metadata")) + { + action.metadata = nodeToQVariantMap(node["metadata"]); + } + if (node.has_child("dataFields")) + { + ryml::ConstNodeRef dataFields = node["dataFields"]; + if (dataFields.is_seq()) + { + for (ryml::ConstNodeRef fieldNode : dataFields.children()) + { + action.dataFields.append(parseFieldSelector(fieldNode)); + } + } + } + if (node.has_child("validationCriteria")) + { + action.validationCriteria = nodeToQVariantMap(node["validationCriteria"]); + } + if (node.has_child("uploadStrategy")) + { + action.uploadStrategy = nodeToQString(node["uploadStrategy"]); + } + else + { + action.uploadStrategy = "inherit"; // Default + } + if (node.has_child("uploadFields")) + { + QVariantList fields = nodeToQVariantList(node["uploadFields"]); + for (const QVariant &field : fields) + { + action.uploadFields.append(field.toString()); + } + } + + return action; +} + +TestActivityGroup ProcedureParser::parseTestActivityGroup(const ryml::ConstNodeRef &node) +{ + TestActivityGroup group; + + if (node.invalid()) + { + return group; + } + + // Handle JSON $ref references + QString refPath = extractRefPath(node); + if (!refPath.isEmpty()) + { + ryml::ConstNodeRef resolvedNode = resolveReference(refPath); + if (!resolvedNode.invalid()) + { + return parseTestActivityGroup(resolvedNode); + } + else + { + qWarning() << "Failed to resolve test activity group reference:" << refPath; + return group; + } + } + + if (!node.is_map()) + { + return group; + } + + if (node.has_child("id")) + { + group.id = nodeToQString(node["id"]); + } + if (node.has_child("name")) + { + group.name = nodeToQString(node["name"]); + } + if (node.has_child("document")) + { + group.document = nodeToQString(node["document"]); + } + if (node.has_child("metadata")) + { + group.metadata = nodeToQVariantMap(node["metadata"]); + } + if (node.has_child("tableRefs")) + { + QVariantList refs = nodeToQVariantList(node["tableRefs"]); + for (const QVariant &ref : refs) + { + group.tableRefs.append(ref.toString()); + } + } + if (node.has_child("actions")) + { + ryml::ConstNodeRef actions = node["actions"]; + qDebug() << "Parsing actions for TestActivityGroup:" << group.name; + qDebug() << " actions node is_seq:" << actions.is_seq() << "is_map:" << actions.is_map() + << "is_val:" << actions.is_val(); + + if (actions.is_seq()) + { + qDebug() << " actions sequence has" << actions.num_children() << "children"; + + int childIndex = 0; + for (ryml::ConstNodeRef actionNode : actions.children()) + { + qDebug() << " Processing child" << childIndex << "- valid:" << actionNode.valid() + << "is_map:" << actionNode.is_map() << "is_val:" << actionNode.is_val(); + + if (!actionNode.valid()) + { + qWarning() << " Invalid action node at index" << childIndex; + childIndex++; + continue; + } + + // Check if it's a YAML alias (reference) + if (actionNode.is_ref()) + { + qDebug() << " Child" << childIndex << "is a YAML reference/alias"; + } + + TestAction action = parseTestAction(actionNode); + qDebug() << " Parsed action" << childIndex << ":" << action.name << "(" << action.id + << ")"; + group.actions.append(action); + childIndex++; + } + qDebug() << " Total actions parsed:" << group.actions.size(); + } + else + { + qWarning() << " actions node is not a sequence!"; + } + } + else + { + qWarning() << " TestActivityGroup has no 'actions' child"; + + // Debug: list all children of this node + qDebug() << " Node has" << node.num_children() << "children:"; + for (ryml::ConstNodeRef child : node.children()) + { + if (child.has_key()) + { + ryml::csubstr key = child.key(); + QString keyStr = QString::fromUtf8(key.data(), key.size()); + qDebug() << " -" << keyStr; + } + } + } + + qDebug() << "TestActivityGroup" << group.name << "final actions count:" << group.actions.size(); + return group; +} + +TestTaskGroup ProcedureParser::parseTestTaskGroup(const ryml::ConstNodeRef &node) +{ + TestTaskGroup group; + + if (node.invalid()) + { + return group; + } + + // Handle JSON $ref references + QString refPath = extractRefPath(node); + if (!refPath.isEmpty()) + { + ryml::ConstNodeRef resolvedNode = resolveReference(refPath); + if (!resolvedNode.invalid()) + { + return parseTestTaskGroup(resolvedNode); + } + else + { + qWarning() << "Failed to resolve test task group reference:" << refPath; + return group; + } + } + + if (!node.is_map()) + { + return group; + } + + if (node.has_child("id")) + { + group.id = nodeToQString(node["id"]); + } + if (node.has_child("name")) + { + group.name = nodeToQString(node["name"]); + } + if (node.has_child("metadata")) + { + group.metadata = nodeToQVariantMap(node["metadata"]); + } + if (node.has_child("stages")) + { + ryml::ConstNodeRef stages = node["stages"]; + qDebug() << "Stage 1: Parsing stage metadata for TestTaskGroup:" << group.name; + qDebug() << " stages node is_seq:" << stages.is_seq() + << "num_children:" << stages.num_children(); + + if (stages.is_seq()) + { + int stageIndex = 0; + for (ryml::ConstNodeRef stageNode : stages.children()) + { + qDebug() << " Processing stage" << stageIndex << "- valid:" << stageNode.valid() + << "is_map:" << stageNode.is_map() << "is_ref:" << stageNode.is_ref(); + + if (!stageNode.valid()) + { + qWarning() << " Invalid stage node at index" << stageIndex; + stageIndex++; + continue; + } + + // Resolve the node if it's a reference + ryml::ConstNodeRef resolvedStage = stageNode; + QString refPath = extractRefPath(stageNode); + if (!refPath.isEmpty()) + { + qDebug() << " Stage" << stageIndex << "is a $ref reference:" << refPath; + resolvedStage = resolveReference(refPath); + if (resolvedStage.invalid()) + { + qWarning() << " Failed to resolve stage reference:" << refPath; + stageIndex++; + continue; + } + } + else if (stageNode.is_ref()) + { + qDebug() << " Stage" << stageIndex << "is a YAML alias (will be auto-resolved)"; + } + + // Stage 1: Parse only metadata (id, name, document, tableRefs) + // Do NOT parse actions yet + TestActivityGroup activityGroup; + + if (resolvedStage.has_child("id")) + { + activityGroup.id = nodeToQString(resolvedStage["id"]); + } + if (resolvedStage.has_child("name")) + { + activityGroup.name = nodeToQString(resolvedStage["name"]); + } + if (resolvedStage.has_child("document")) + { + activityGroup.document = nodeToQString(resolvedStage["document"]); + } + if (resolvedStage.has_child("metadata")) + { + activityGroup.metadata = nodeToQVariantMap(resolvedStage["metadata"]); + } + if (resolvedStage.has_child("tableRefs")) + { + QVariantList refs = nodeToQVariantList(resolvedStage["tableRefs"]); + for (const QVariant &ref : refs) + { + activityGroup.tableRefs.append(ref.toString()); + } + } + + // Mark actions as not parsed yet + activityGroup.isActionsParsed = false; + + // Cache the node for later action parsing (Stage 2) + stageNodeCache[activityGroup.id] = resolvedStage; + + qDebug() << " Stage" << stageIndex << "metadata parsed:" << activityGroup.name + << "(actions deferred)"; + group.stages.append(activityGroup); + stageIndex++; + } + qDebug() << " Total stages metadata parsed:" << group.stages.size(); + } + else + { + qWarning() << " stages node is not a sequence!"; + } + } + else + { + qWarning() << " TestTaskGroup has no 'stages' child"; + } + + return group; +} + +ResultDisplay ProcedureParser::parseResultDisplay(const ryml::ConstNodeRef &node) +{ + ResultDisplay display; + + if (node.invalid()) + { + return display; + } + + // Handle JSON $ref references + QString refPath = extractRefPath(node); + if (!refPath.isEmpty()) + { + ryml::ConstNodeRef resolvedNode = resolveReference(refPath); + if (!resolvedNode.invalid()) + { + return parseResultDisplay(resolvedNode); + } + else + { + qWarning() << "Failed to resolve result display reference:" << refPath; + return display; + } + } + + if (!node.is_map()) + { + return display; + } + + if (node.has_child("id")) + { + display.id = nodeToQString(node["id"]); + } + if (node.has_child("name")) + { + display.name = nodeToQString(node["name"]); + } + if (node.has_child("document")) + { + display.document = nodeToQString(node["document"]); + } + if (node.has_child("tableRefs")) + { + QVariantList refs = nodeToQVariantList(node["tableRefs"]); + for (const QVariant &ref : refs) + { + display.tableRefs.append(ref.toString()); + } + } + + return display; +} + +// Stage 2: Parse TestActivityGroup actions on demand +bool ProcedureParser::parseTestActivityGroupActions(TestActivityGroup &group, + const QString &groupId) +{ + // Already parsed + if (group.isActionsParsed) + { + qDebug() << "Actions already parsed for TestActivityGroup:" << group.name; + return true; + } + + // Check if node is cached + if (!stageNodeCache.contains(groupId)) + { + qWarning() << "Stage node not found in cache for id:" << groupId; + qDebug() << "Available cached stage IDs:" << stageNodeCache.keys(); + return false; + } + + ryml::ConstNodeRef node = stageNodeCache[groupId]; + + qDebug() << "Stage 2: Parsing actions for TestActivityGroup:" << group.name; + + // Parse actions + if (node.has_child("actions")) + { + ryml::ConstNodeRef actions = node["actions"]; + qDebug() << " actions node is_seq:" << actions.is_seq() << "is_map:" << actions.is_map() + << "is_val:" << actions.is_val(); + + if (actions.is_seq()) + { + qDebug() << " actions sequence has" << actions.num_children() << "children"; + + int childIndex = 0; + for (ryml::ConstNodeRef actionNode : actions.children()) + { + qDebug() << " Processing child" << childIndex << "- valid:" << actionNode.valid() + << "is_map:" << actionNode.is_map() << "is_val:" << actionNode.is_val(); + + if (!actionNode.valid()) + { + qWarning() << " Invalid action node at index" << childIndex; + childIndex++; + continue; + } + + // Check if it's a YAML alias (reference) + if (actionNode.is_ref()) + { + qDebug() << " Child" << childIndex << "is a YAML reference/alias"; + } + + TestAction action = parseTestAction(actionNode); + qDebug() << " Parsed action" << childIndex << ":" << action.name << "(" << action.id + << ")"; + group.actions.append(action); + childIndex++; + } + qDebug() << " Total actions parsed:" << group.actions.size(); + } + else + { + qWarning() << " actions node is not a sequence!"; + } + } + else + { + qDebug() << " TestActivityGroup has no 'actions' child (empty stage)"; + } + + group.isActionsParsed = true; + qDebug() << "TestActivityGroup" << group.name + << "actions parsing completed:" << group.actions.size(); + return true; +} + +ProcedureConfig ProcedureParser::parseProcedureConfig() +{ + ProcedureConfig config; + + if (!tree || tree->empty()) + { + validationErrors << "Tree is empty or not loaded"; + return config; + } + + ryml::ConstNodeRef root = tree->rootref(); + + // Parse procedure section + if (root.has_child("procedure")) + { + ryml::ConstNodeRef procedure = root["procedure"]; + + if (procedure.has_child("id")) + { + config.procedureId = nodeToQString(procedure["id"]); + } + if (procedure.has_child("name")) + { + config.procedureName = nodeToQString(procedure["name"]); + } + if (procedure.has_child("version")) + { + config.version = nodeToQString(procedure["version"]); + } + if (procedure.has_child("document")) + { + config.document = nodeToQString(procedure["document"]); + } + if (procedure.has_child("metadata")) + { + config.metadata = nodeToQVariantMap(procedure["metadata"]); + } + + // Parse activity sequence (LAZY LOADING - only parse metadata) + if (procedure.has_child("activitySequence")) + { + ryml::ConstNodeRef activitySeq = procedure["activitySequence"]; + if (activitySeq.is_seq()) + { + for (ryml::ConstNodeRef activityNode : activitySeq.children()) + { + qDebug() << "Processing activity node - valid:" << activityNode.valid() + << "is_map:" << activityNode.is_map() << "is_ref:" << activityNode.is_ref() + << "is_val:" << activityNode.is_val(); + + if (!activityNode.valid()) + { + qWarning() << "Invalid activity node in sequence"; + continue; + } + + // Resolve the node if it's a reference + ryml::ConstNodeRef resolvedNode = activityNode; + QString refPath = extractRefPath(activityNode); + + // Handle YAML aliases (references like *protectionGroup1) + if (activityNode.is_ref()) + { + qDebug() << "Activity node is a YAML alias (will be auto-resolved " + "by ryml)"; + // For YAML aliases, ryml should auto-resolve them, so use + // activityNode directly + resolvedNode = activityNode; + } + else if (!refPath.isEmpty()) + { + qDebug() << "Activity node has JSON $ref:" << refPath; + resolvedNode = resolveReference(refPath); + if (resolvedNode.invalid()) + { + qWarning() << "Failed to resolve activity reference:" << refPath; + continue; + } + } + + if (!resolvedNode.is_map()) + { + qWarning() << "Resolved activity node is not a map"; + continue; + } + + // Debug: check what fields the resolved node has + qDebug() << "Resolved node has" << resolvedNode.num_children() << "children:"; + for (ryml::ConstNodeRef child : resolvedNode.children()) + { + if (child.has_key()) + { + ryml::csubstr key = child.key(); + QString keyStr = QString::fromUtf8(key.data(), key.size()); + qDebug() << " -" << keyStr; + } + } + + // Determine activity type: only TestTaskGroup or ResultDisplay + // allowed + bool hasStages = resolvedNode.has_child("stages"); + bool hasTableRefs = resolvedNode.has_child("tableRefs"); + qDebug() << "Type detection - refPath contains testTaskGroups:" + << refPath.contains("testTaskGroups") << "hasStages:" << hasStages + << "refPath contains resultDisplays:" << refPath.contains("resultDisplays") + << "hasTableRefs:" << hasTableRefs; + + if (refPath.contains("testTaskGroups") || hasStages) + { + // TestTaskGroup - LAZY: only parse id, name, metadata (NOT stages) + TestTaskGroup group; + if (resolvedNode.has_child("id")) + { + group.id = nodeToQString(resolvedNode["id"]); + } + if (resolvedNode.has_child("name")) + { + group.name = nodeToQString(resolvedNode["name"]); + } + if (resolvedNode.has_child("metadata")) + { + group.metadata = nodeToQVariantMap(resolvedNode["metadata"]); + } + group.isParsed = false; // Mark as not fully parsed + + // Cache the node for later parsing + activityNodeCache[group.id] = resolvedNode; + + config.activityVariants.append(QVariant::fromValue(group)); + + qDebug() << "Lazy loaded TestTaskGroup metadata:" << group.id << group.name; + } + else if (refPath.contains("resultDisplays") || resolvedNode.has_child("tableRefs")) + { + // ResultDisplay - parse fully (lightweight) + config.activityVariants.append(QVariant::fromValue(parseResultDisplay(resolvedNode))); + } + else + { + QString nodeId = + resolvedNode.has_child("id") ? nodeToQString(resolvedNode["id"]) : ""; + qWarning() << "Unknown activity type in sequence, id:" << nodeId; + } + } + } + } + } + + // Convert activitySequence to direct storage for efficient access + qDebug() << "Converting" << config.activityVariants.size() + << "activities from QVariant to direct storage"; + for (int i = 0; i < config.activityVariants.size(); ++i) + { + const QVariant &activityVariant = config.activityVariants[i]; + qDebug() << "Activity" << i << "- type:" << activityVariant.typeName() + << "canConvert:" << activityVariant.canConvert() + << "canConvert:" << activityVariant.canConvert(); + + if (activityVariant.canConvert()) + { + TestTaskGroup group = activityVariant.value(); + config.taskGroups.append(group); + qDebug() << "Added TestTaskGroup to direct storage:" << group.id << group.name; + } + else if (activityVariant.canConvert()) + { + ResultDisplay display = activityVariant.value(); + config.resultDisplays[display.id] = display; + qDebug() << "Added ResultDisplay to direct storage:" << display.id << display.name; + } + else + { + qWarning() << "Activity" << i + << "cannot be converted to known type, typeName:" << activityVariant.typeName(); + } + } + + // Parse tables + if (root.has_child("tables")) + { + ryml::ConstNodeRef tables = root["tables"]; + if (tables.is_map()) + { + for (ryml::ConstNodeRef tableNode : tables.children()) + { + if (tableNode.has_key()) + { + // Convert key to QString + ryml::csubstr keyData = tableNode.key(); + QString tableId = QString::fromUtf8(keyData.data(), keyData.size()); + config.tables[tableId] = parseTableDefinition(tableNode); + } + } + } + } + + qDebug() << "Parsed procedure config:" << config.procedureId << "with" << config.taskGroups.size() + << "task groups" + << "and" << config.resultDisplays.size() << "result displays"; + + return config; +} + +TableDefinition ProcedureParser::getTableDefinition(const QString &tableRef) +{ + ryml::ConstNodeRef tableNode = resolveReference(tableRef); + if (!tableNode.invalid()) + { + return parseTableDefinition(tableNode); + } + + return TableDefinition(); +} + +QMap ProcedureParser::getAllTableDefinitions() +{ + QMap tables; + + if (!tree || tree->empty()) + { + return tables; + } + + ryml::ConstNodeRef root = tree->rootref(); + if (root.has_child("tables")) + { + ryml::ConstNodeRef tablesNode = root["tables"]; + if (tablesNode.is_map()) + { + for (ryml::ConstNodeRef tableNode : tablesNode.children()) + { + if (tableNode.has_key()) + { + // Convert key to QString + ryml::csubstr keyData = tableNode.key(); + QString tableId = QString::fromUtf8(keyData.data(), keyData.size()); + tables[tableId] = parseTableDefinition(tableNode); + } + } + } + } + + return tables; +} + +bool ProcedureParser::parseTestTaskGroupStages(TestTaskGroup &group, const QString &groupId) +{ + // Check if already parsed + if (group.isParsed) + { + qDebug() << "TestTaskGroup" << groupId << "already parsed, skipping"; + return true; + } + + // Find cached node + if (!activityNodeCache.contains(groupId)) + { + qWarning() << "No cached node found for TestTaskGroup:" << groupId; + return false; + } + + ryml::ConstNodeRef node = activityNodeCache[groupId]; + + qDebug() << "Lazy parsing stages for TestTaskGroup:" << groupId; + + // Parse stages + if (node.has_child("stages")) + { + ryml::ConstNodeRef stages = node["stages"]; + qDebug() << " Parsing stages - is_seq:" << stages.is_seq() + << "num_children:" << stages.num_children(); + + if (stages.is_seq()) + { + group.stages.clear(); + group.stages.reserve(stages.num_children()); + + int stageIndex = 0; + for (ryml::ConstNodeRef stageNode : stages.children()) + { + if (!stageNode.valid()) + { + qWarning() << " Invalid stage node at index" << stageIndex; + stageIndex++; + continue; + } + + // Resolve reference if needed + ryml::ConstNodeRef resolvedStage = stageNode; + QString refPath = extractRefPath(stageNode); + if (!refPath.isEmpty()) + { + resolvedStage = resolveReference(refPath); + if (resolvedStage.invalid()) + { + qWarning() << " Failed to resolve stage reference:" << refPath; + stageIndex++; + continue; + } + } + + // Parse TestActivityGroup + TestActivityGroup activityGroup = parseTestActivityGroup(resolvedStage); + qDebug() << " Parsed stage" << stageIndex << ":" << activityGroup.name << "with" + << activityGroup.actions.size() << "actions"; + group.stages.append(activityGroup); + stageIndex++; + } + + qDebug() << " Total stages parsed:" << group.stages.size(); + } + } + + group.isParsed = true; + return true; +} + +bool ProcedureParser::validateConfig() +{ + validationErrors.clear(); + + if (!tree || tree->empty()) + { + validationErrors << "Configuration tree is empty"; + return false; + } + + ryml::ConstNodeRef root = tree->rootref(); + + // Validate required top-level sections + if (!root.has_child("procedure")) + { + validationErrors << "Missing required 'procedure' section"; + } + if (!root.has_child("tables")) + { + validationErrors << "Missing required 'tables' section"; + } + + // Validate procedure section + if (root.has_child("procedure")) + { + ryml::ConstNodeRef procedure = root["procedure"]; + + if (!procedure.has_child("id")) + { + validationErrors << "Procedure missing required 'id' field"; + } + if (!procedure.has_child("name")) + { + validationErrors << "Procedure missing required 'name' field"; + } + if (!procedure.has_child("version")) + { + validationErrors << "Procedure missing required 'version' field"; + } + if (!procedure.has_child("activitySequence")) + { + validationErrors << "Procedure missing required 'activitySequence' field"; + } + else + { + if (!validateActivitySequence()) + { + validationErrors << "Activity sequence validation failed"; + } + } + } + + // Validate table definitions + if (!validateTableDefinitions()) + { + validationErrors << "Table definitions validation failed"; + } + + return validationErrors.isEmpty(); +} + +bool ProcedureParser::validateActivitySequence() +{ + // Placeholder for activity sequence validation + // In a full implementation, this would check: + // - All references are valid + // - Metadata inheritance rules are followed + // - Required fields are present + return true; +} + +bool ProcedureParser::validateTableDefinitions() +{ + // Placeholder for table definitions validation + // In a full implementation, this would check: + // - All table references are valid + // - Field definitions are complete + // - Upload strategies are valid + return true; +} + +QStringList ProcedureParser::getValidationErrors() const +{ + return validationErrors; +} + +// Register custom types with Qt's meta-object system +namespace +{ + struct TypeRegistrar + { + TypeRegistrar() + { + qRegisterMetaType("FieldDefinition"); + qRegisterMetaType("StaticCell"); + qRegisterMetaType("TableDefinition"); + qRegisterMetaType("FieldSelector"); + qRegisterMetaType("TestAction"); + qRegisterMetaType("TestActivityGroup"); + qRegisterMetaType("TestTaskGroup"); + qRegisterMetaType("ResultDisplay"); + qRegisterMetaType("ProcedureConfig"); + } + }; + + static TypeRegistrar typeRegistrar; +} // namespace diff --git a/procedure/procedureparser.h b/procedure/procedureparser.h new file mode 100644 index 0000000..178b075 --- /dev/null +++ b/procedure/procedureparser.h @@ -0,0 +1,83 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#include + +// Include existing data structures +#include "proceduredata.h" + +class ProcedureParser : public QObject +{ + Q_OBJECT + +public: + explicit ProcedureParser(QObject *parent = nullptr); + ~ProcedureParser(); + + // Configuration file loading and parsing + bool loadConfig(const QString &filePath); + ProcedureConfig parseProcedureConfig(); + + // Configuration validation + bool validateConfig(); + QStringList getValidationErrors() const; + + // Table definition extraction + TableDefinition getTableDefinition(const QString &tableRef); + QMap getAllTableDefinitions(); + + // Lazy parsing - parse TestTaskGroup stages on demand (Stage 1: load stage + // metadata) + bool parseTestTaskGroupStages(TestTaskGroup &group, const QString &groupId); + + // Lazy parsing - parse TestActivityGroup actions on demand (Stage 2: load + // actions) + bool parseTestActivityGroupActions(TestActivityGroup &group, const QString &groupId); + +signals: + void configLoaded(const QString &filePath); + void configError(const QString &error, int line, int column); + +private: + std::unique_ptr tree; + QMap referenceCache; + QMap stageNodeCache; + QMap activityNodeCache; // Cache for lazy parsing of TestTaskGroups + QStringList validationErrors; + QString configFilePath; + std::string yamlContent; // Store YAML content for in-place parsing + + // File parsing methods + bool parseFile(const QString &filePath); + + // Reference handling + void buildReferenceCache(); + ryml::ConstNodeRef resolveReference(const QString &ref); + QString extractRefPath(const ryml::ConstNodeRef &node); + + // Validation methods + bool validateActivitySequence(); + bool validateTableDefinitions(); + + // Parsing helper methods + QString nodeToQString(const ryml::ConstNodeRef &node); + QVariant nodeToQVariant(const ryml::ConstNodeRef &node); + QVariantMap nodeToQVariantMap(const ryml::ConstNodeRef &node); + QVariantList nodeToQVariantList(const ryml::ConstNodeRef &node); + + // Structure parsing methods + FieldDefinition parseFieldDefinition(const ryml::ConstNodeRef &node); + TableDefinition parseTableDefinition(const ryml::ConstNodeRef &node); + StaticCell parseStaticCell(const ryml::ConstNodeRef &node); + FieldSelector parseFieldSelector(const ryml::ConstNodeRef &node); + TestAction parseTestAction(const ryml::ConstNodeRef &node); + TestActivityGroup parseTestActivityGroup(const ryml::ConstNodeRef &node); + TestTaskGroup parseTestTaskGroup(const ryml::ConstNodeRef &node); + ResultDisplay parseResultDisplay(const ryml::ConstNodeRef &node); +}; diff --git a/procedures/FUNCTION_GUIDE.md b/procedures/FUNCTION_GUIDE.md new file mode 100644 index 0000000..9dac6e6 --- /dev/null +++ b/procedures/FUNCTION_GUIDE.md @@ -0,0 +1,623 @@ +# 测量函数 + +## 目录 + +1. [电阻测量函数](#电阻测量函数) +2. [电压测量函数](#电压测量函数) +3. [电流测量函数](#电流测量函数) +4. [频率测量函数](#频率测量函数) +5. [数字输入函数](#数字输入函数) +6. [绝缘测试函数](#绝缘测试函数) +7. [数据采集函数](#数据采集函数) + +--- + +## 电阻测量函数 + +### 1. RESISTANCE_4WIRE - 4 线制电阻测量 + +**功能描述**:高精度 4 线制电阻测量,返回 7 个测量值。 + +**适用场景**:精密电阻测量,RTD 温度传感器检测。 + +**返回值数量**:7 个 + +- R1/2, R1/3, R1/4 +- R2/3, R2/4, R3/4 +- R/4wires + +**YAML 配置示例**: + +```yaml +testActions: + measureResistance4Wire: + id: measureResistance4Wire + name: 4线制电阻测量 + mode: auto + functionType: RESISTANCE_4WIRE + channel: input1 + functionParameters: {} + dataFields: + - tableRef: '#/tables/resistanceTable' + cells: + - row: sensor1 + column: r12 + - row: sensor1 + column: r13 + - row: sensor1 + column: r14 + - row: sensor1 + column: r23 + - row: sensor1 + column: r24 + - row: sensor1 + column: r34 + - row: sensor1 + column: r4wires +``` + +**注意事项**: + +- 必须指定 `channel` (input1 或 input2) +- `functionParameters` 为空对象 `{}` +- `dataFields` 必须包含 7 个单元格,顺序固定 + +--- + +### 2. RESISTANCE_3WIRE - 3 线制电阻测量 + +**功能描述**:3 线制电阻测量,返回 3 个测量值。 + +**适用场景**:3 线制 RTD 温度传感器检测。 + +**返回值数量**:3 个 + +- R2/3, R2/4, R3/4 + +**YAML 配置示例**: + +```yaml +testActions: + measureResistance3Wire: + id: measureResistance3Wire + name: 3线制电阻测量 + mode: auto + functionType: RESISTANCE_3WIRE + channel: input1 + functionParameters: {} + dataFields: + - tableRef: '#/tables/resistanceTable' + cells: + - row: sensor1 + column: r23 + - row: sensor1 + column: r24 + - row: sensor1 + column: r34 +``` + +**注意事项**: + +- 必须指定 `channel` +- `functionParameters` 为空对象 `{}` +- `dataFields` 必须包含 3 个单元格,顺序固定 + +--- + +### 3. RESISTANCE_2WIRE - 2 线制电阻测量 + +**功能描述**:2 线制电阻测量,返回 1 个测量值。 + +**适用场景**:简单电阻测量,2 线制传感器。 + +**返回值数量**:1 个 + +- R1/2 + +**YAML 配置示例**: + +```yaml +testActions: + measureResistance2Wire: + id: measureResistance2Wire + name: 2线制电阻测量 + mode: auto + functionType: RESISTANCE_2WIRE + channel: input1 + functionParameters: {} + dataFields: + - tableRef: '#/tables/resistanceTable' + cells: + - row: sensor1 + column: resistance +``` + +**注意事项**: + +- 必须指定 `channel` +- `functionParameters` 为空对象 `{}` +- `dataFields` 包含 1 个单元格 + +--- + +## 电压测量函数 + +### 4. VOLTAGE_AC - 交流电压测量 + +**功能描述**:交流电压测量,返回 RMS 电压、峰值电压和频率。 + +**适用场景**:交流电源检测,信号分析。 + +**返回值数量**:3 个 + +- RMS 电压 (有效值) +- 峰值电压 +- 频率 + +**YAML 配置示例**: + +```yaml +testActions: + measureACVoltage: + id: measureACVoltage + name: 交流电压测量 + mode: auto + functionType: VOLTAGE_AC + channel: input1 + functionParameters: {} + dataFields: + - tableRef: '#/tables/voltageTable' + cells: + - row: measurement1 + column: rmsVoltage + - row: measurement1 + column: peakVoltage + - row: measurement1 + column: frequency +``` + +**注意事项**: + +- 必须指定 `channel` +- `functionParameters` 为空对象 `{}` +- `dataFields` 必须包含 3 个单元格,顺序固定 + +--- + +### 5. VOLTAGE_V - 直流电压测量 (伏特) + +**功能描述**:直流电压测量,单位为伏特(V)。 + +**适用场景**:直流电源检测,信号电压测量。 + +**返回值数量**:1 个 + +- 电压值 (V) + +**YAML 配置示例**: + +```yaml +testActions: + measureDCVoltage: + id: measureDCVoltage + name: 直流电压测量 + mode: auto + functionType: VOLTAGE_V + channel: input1 + functionParameters: {} + dataFields: + - tableRef: '#/tables/voltageTable' + cells: + - row: measurement1 + column: voltage +``` + +**注意事项**: + +- 必须指定 `channel` +- `functionParameters` 为空对象 `{}` +- `dataFields` 包含 1 个单元格 + +--- + +### 6. VOLTAGE_MV - 直流电压测量 (毫伏) + +**功能描述**:直流电压测量,单位为毫伏(mV)。 + +**适用场景**:微小信号测量,传感器输出检测。 + +**返回值数量**:1 个 + +- 电压值 (mV) + +**YAML 配置示例**: + +```yaml +testActions: + measureMillivolt: + id: measureMillivolt + name: 毫伏电压测量 + mode: auto + functionType: VOLTAGE_MV + channel: input1 + functionParameters: {} + dataFields: + - tableRef: '#/tables/voltageTable' + cells: + - row: measurement1 + column: millivolt +``` + +**注意事项**: + +- 必须指定 `channel` +- `functionParameters` 为空对象 `{}` +- `dataFields` 包含 1 个单元格 + +--- + +## 电流测量函数 + +### 7. CURRENT_MA - 直流电流测量 (毫安) + +**功能描述**:直流电流测量,单位为毫安(mA)。 + +**适用场景**:回路电流检测,4-20mA 信号测量。 + +**返回值数量**:1 个 + +- 电流值 (mA) + +**YAML 配置示例**: + +```yaml +testActions: + measureCurrent: + id: measureCurrent + name: 电流测量 + mode: auto + functionType: CURRENT_MA + channel: input1 + functionParameters: {} + dataFields: + - tableRef: '#/tables/currentTable' + cells: + - row: measurement1 + column: current +``` + +**注意事项**: + +- 必须指定 `channel` +- `functionParameters` 为空对象 `{}` +- `dataFields` 包含 1 个单元格 + +--- + +## 频率测量函数 + +### 8. FREQUENCY - 频率测量 + +**功能描述**:信号频率测量,单位为 Hz。 + +**适用场景**:交流信号频率检测,脉冲频率测量。 + +**返回值数量**:1 个 + +- 频率值 (Hz) + +**YAML 配置示例**: +dat +testActions: +measureFrequency: +id: measureFrequency +name: 频率测量 +mode: auto +functionType: FREQUENCY +channel: input1 +functionParameters: {} +dataFields: - tableRef: '#/tables/frequencyTable' +cells: - row: measurement1 +column: frequency + +```` + +**注意事项**: +- 必须指定 `channel` +- `functionParameters` 为空对象 `{}` +- `dataFields` 包含1个单元格 + +--- + +## 数字输入函数 + +### 9. DIGITAL_INPUT - 数字输入读取 + +**功能描述**:读取数字输入状态,返回布尔值。 + +**适用场景**:开关状态检测,数字信号读取。 + +**返回值数量**:1个 +- 状态值 (true/false) + +**YAML 配置示例**: + +```yaml +testActions: + readDigitalInput: + id: readDigitalInput + name: 数字输入读取 + mode: auto + functionType: DIGITAL_INPUT + channel: input1 + functionParameters: {} + dataFields: + - tableRef: '#/tables/digitalTable' + cells: + - row: input1 + column: state +```` + +**注意事项**: + +- 必须指定 `channel` +- `functionParameters` 为空对象 `{}` +- `dataFields` 包含 1 个单元格 +- 表格字段类型应设置为 `boolean` + +--- + +## 绝缘测试函数 + +### 10. INSULATION - 绝缘电阻测试 + +**功能描述**:绝缘电阻测试,返回绝缘电阻值(MΩ)。 + +**适用场景**:电气设备绝缘检测,传感器绝缘测试。 + +**返回值数量**:1 个 + +- 绝缘电阻 (MΩ) + +**YAML 配置示例**: + +```yaml +testActions: + insulationTest: + id: insulationTest + name: 绝缘测试 + mode: auto + functionType: INSULATION + channel: input1 + functionParameters: + testVoltage: 50 + testTime: 20 + dischargeTime: 5 + dataFields: + - tableRef: '#/tables/insulationTable' + cells: + - row: sensor1 + column: insulation +``` + +**functionParameters 参数说明**: + +- `testVoltage`: 测试电压 (V) +- `testTime`: 测试时间 (秒) +- `dischargeTime`: 放电时间 (秒) + +**注意事项**: + +- 必须指定 `channel` +- `functionParameters` 可以指定测试参数,也可以使用空对象 `{}` +- `dataFields` 包含 1 个单元格 + +--- + +## 数据采集函数 + +### 11. DATA_ACQUISITION - 通用数据采集 + +**功能描述**:从上位机或 DCS 系统采集多个参数的数据。 + +**适用场景**:批量读取传感器数据、报警状态、系统参数等。 + +**返回值数量**:可变,与参数列表数量相同 + +**YAML 配置示例**: + +```yaml +testActions: + acquireData: + id: acquireData + name: 数据采集 + mode: auto + functionType: DATA_ACQUISITION + functionParameters: + - RCP030MT + - RCP033MT + - RCP031MT + - RCP034MT + dataFields: + - tableRef: '#/tables/dataTable' + cells: + - row: RCP030MT + column: value + - row: RCP033MT + column: value + - row: RCP031MT + column: value + - row: RCP034MT + column: value +``` + +**functionParameters 参数说明**: + +- 参数为数组形式,每个元素是要采集的数据标识符 +- 返回值顺序与参数顺序一致 +- 支持任意数量的参数 + +**数据类型识别**: + +- 包含 "MT" 的参数:识别为温度传感器,返回温度值 +- 包含 "AA" 的参数:识别为报警状态,返回 "有报警" 或 "无报警" +- 其他参数:返回通用数值 + +**注意事项**: + +- **不需要**指定 `channel` +- `functionParameters` 为数组,不能为空 +- `dataFields` 中的单元格数量必须与参数数量一致 + +--- + +### 12. DATA_ACQUISITION_WITH_VALIDATION - 带验证的数据采集 + +**功能描述**:从上位机采集数据,并返回每个数据的验证结果。 + +**适用场景**:需要服务器端验证的数据采集。 + +**返回值数量**:参数数量的 2 倍 (每个参数返回:值 + 验证结果) + +**YAML 配置示例**: + +```yaml +testActions: + acquireDataWithValidation: + id: acquireDataWithValidation + name: 带验证的数据采集 + mode: auto + functionType: DATA_ACQUISITION_WITH_VALIDATION + functionParameters: + - RCP030MT + - RCP033MT + dataFields: + - tableRef: '#/tables/dataTable' + cells: + - row: RCP030MT + column: value + - row: RCP030MT + column: valid + - row: RCP033MT + column: value + - row: RCP033MT + column: valid +``` + +**functionParameters 参数说明**: + +- 参数为数组形式,每个元素是要采集的数据标识符 +- 每个参数返回 2 个值:数据值和验证结果 + +**返回值格式**: + +- 第 1 个值:第 1 个参数的数据值 +- 第 2 个值:第 1 个参数的验证结果 (true/false) +- 第 3 个值:第 2 个参数的数据值 +- 第 4 个值:第 2 个参数的验证结果 (true/false) +- 以此类推... + +**注意事项**: + +- **不需要**指定 `channel` +- `functionParameters` 为数组,不能为空 +- `dataFields` 中的单元格数量必须是参数数量的 2 倍 +- 验证结果字段类型应设置为 `boolean` + +--- + +## 字段映射 + +### 基本规则 + +**dataFields 是一个 fieldSelector 数组,每个 fieldSelector 可以是:** + +1. **包含 cells 的对象** - 用于 Grid 表格,通过 `row` 和 `column` 定位 +2. **包含 fields 的对象** - 用于 Form 表格,直接指定字段 ID +3. **ignore 对象** - `ignore: true` 用于跳过不需要的返回值 + +**注意**: + +- 每个 fieldSelector 中的 cells 或 fields 可以包含多个项 +- **所有 fieldSelector 消耗的返回值总数必须与函数返回值数量相等** + - 包含 cells 的 fieldSelector:消耗 cells 数组长度个返回值 + - 包含 fields 的 fieldSelector:消耗 fields 数组长度个返回值 + - ignore 的 fieldSelector:消耗 1 个返回值 +- 可以在同一个 dataFields 中混合使用不同类型的 fieldSelector + +### Grid 类型表格 - 使用 cells + +```yaml +dataFields: + - tableRef: '#/tables/gridTable' + cells: + - row: sensor1 + column: value1 + - row: sensor1 + column: value2 +``` + +### Form 类型表格 - 使用 fields + +```yaml +dataFields: + - tableRef: '#/tables/formTable' + fields: + - field1 + - field2 + - field3 +``` + +### 忽略返回值 + +如果函数返回 5 个值,但只需要其中 4 个,忽略第 3 个: + +```yaml +dataFields: + - tableRef: '#/tables/gridTable' + cells: + - row: sensor1 + column: value1 + - row: sensor1 + column: value2 + - ignore: true # 忽略第3个返回值 + - tableRef: '#/tables/gridTable' + cells: + - row: sensor1 + column: value4 + - row: sensor1 + column: value5 +``` + +### 混合使用 + +`ignore` 可以与 `cells` 和 `fields` 自由混合,不局限于单一类型: + +```yaml +dataFields: + - tableRef: '#/tables/gridTable' + cells: + - row: sensor1 + column: value1 + - tableRef: '#/tables/formTable' + fields: + - field1 + - field2 + - ignore: true # 忽略第4个返回值 + - tableRef: '#/tables/gridTable' + cells: + - row: sensor2 + column: value5 + - ignore: true # 忽略第6个返回值 + - tableRef: '#/tables/formTable' + fields: + - field3 +``` + +**说明**:上述示例处理 7 个返回值: +- 第 1 个 → gridTable 的 cell +- 第 2-3 个 → formTable 的 field1 和 field2 +- 第 4 个 → 忽略 +- 第 5 个 → gridTable 的 cell +- 第 6 个 → 忽略 +- 第 7 个 → formTable 的 field3 diff --git a/procedures/README_MOCK_DATA.md b/procedures/README_MOCK_DATA.md new file mode 100644 index 0000000..222dcc4 --- /dev/null +++ b/procedures/README_MOCK_DATA.md @@ -0,0 +1,96 @@ +# Mock Procedures 数据说明 + +## 文件位置 +`procedures/mock_procedures.json` + +## 数据结构 + +```json +{ + "mockProcedures": [ + { + "id": "规程ID", + "name": "规程名称", + "version": "版本号", + "description": "规程描述", + "taskGroups": [ + { + "id": "任务组ID", + "name": "任务组名称", + "steps": [ + { + "id": "步骤ID", + "type": "步骤类型", + "content": "步骤内容", + "status": "步骤状态", + "tableRefs": ["表格引用ID"], + "highlightFields": ["高亮字段ID"] + } + ] + } + ], + "tables": [ + { + "id": "表格ID", + "name": "表格名称", + "description": "表格描述(可选)", + "columns": [ + { + "id": "字段ID", + "name": "字段名称", + "type": "字段类型", + "unit": "单位(可选)", + "isRequired": false, + "isHighlighted": false + } + ] + } + ], + "totalSteps": 总步骤数(可选,会自动计算), + "completedSteps": 已完成步骤数(可选,会自动计算) + } + ] +} +``` + +## 枚举值说明 + +### 步骤类型 (type) +- `Manual`: 手动步骤 +- `Automatic`: 自动步骤 + +### 步骤状态 (status) +- `Pending`: 待执行 +- `InProgress`: 执行中 +- `Confirmed`: 已确认(手动) +- `Passed`: 已通过 +- `Failed`: 未通过 +- `Skipped`: 已跳过 + +### 字段类型 (type) +- `text`: 文本 +- `numeric`: 数值 +- `boolean`: 布尔值 + +## 使用说明 + +1. **添加新的 Mock 规程**:在 `mockProcedures` 数组中添加新对象 +2. **修改现有规程**:直接编辑对应的 JSON 对象 +3. **删除规程**:从数组中移除对应的对象 + +## 注意事项 + +- JSON 中的特殊字符(如 `<`, `>`, `&`, `"`, `'`)会被正确处理,无需转义 +- 多行文本使用 `\n` 表示换行 +- 如果不提供 `totalSteps` 和 `completedSteps`,系统会自动计算 +- `tableRefs` 和 `highlightFields` 是可选字段 + +## 当前 Mock 数据 + +1. **LONG_NAME_TEST**: 超长名称测试 +2. **NO_VERSION**: 无版本号测试 +3. **SPECIAL_CHAR_<>{}[]**: 特殊字符测试 +4. **EMPTY_DESC**: 空描述测试 +5. **LONG_DESC**: 超长描述测试(23个步骤,3个任务组) +6. **TEST-2024-12-31_V1.0**: 日期戳测试 +7. **MULTILINE**: 多行文本测试 diff --git a/procedures/mock_procedures.json b/procedures/mock_procedures.json new file mode 100644 index 0000000..5731cd8 --- /dev/null +++ b/procedures/mock_procedures.json @@ -0,0 +1,367 @@ +{ + "mockProcedures": [ + { + "id": "LONG_NAME_TEST", + "name": "这是一个非常非常非常非常非常非常长的规程名称,用于测试UI在处理超长名称时的显示效果和换行逻辑是否正常工作", + "version": "1.0.0-longname", + "description": "这个规程专门用于测试长名称和长描述的显示效果。在实际工程中,规程名称可能会很长,需要确保UI能够正确处理这种情况。", + "taskGroups": [ + { + "id": "longTestGroup", + "name": "超长名称测试任务组", + "steps": [ + { + "id": "longStep1", + "type": "Manual", + "content": "这是一个非常详细的步骤描述,包含了大量的文字说明。在实际测试过程中,可能需要详细说明每一个操作步骤,确保操作人员能够准确理解并执行。这种长描述可能会包含安全注意事项、操作顺序、参数设置等多方面的内容。操作要点:检查显示是否完整,文字是否能够正常换行,滚动条是否正常工作。", + "status": "Pending" + }, + { + "id": "longStep2", + "type": "Manual", + "content": "第二个步骤的描述相对简短一些。继续测试各项功能是否正常。", + "status": "Pending" + } + ] + } + ], + "tables": [] + }, + { + "id": "NO_VERSION", + "name": "无版本号规程", + "version": "", + "description": "这个规程没有版本号,用于测试系统对缺失版本信息的处理能力", + "taskGroups": [ + { + "id": "noVersionGroup", + "name": "测试任务组", + "steps": [ + { + "id": "step1", + "type": "Manual", + "content": "执行基本测试步骤。检查系统是否能正常处理无版本规程。", + "status": "Pending" + } + ] + } + ], + "tables": [] + }, + { + "id": "SPECIAL_CHAR_<>{}[]", + "name": "特殊字符测试 {JSON} [Array]", + "version": "2.0", + "description": "包含特殊字符: < > { } [ ] & \" ' 的规程,测试HTML转义和显示", + "taskGroups": [ + { + "id": "specialCharGroup", + "name": "特殊字符 <测试> 组", + "steps": [ + { + "id": "specialStep1", + "type": "Manual", + "content": "测试内容: 检查 <标签> 和 {对象} 以及 [数组] 的显示。确认特殊字符 & \" ' 能正确显示。", + "status": "Pending" + }, + { + "id": "specialStep2", + "type": "Manual", + "content": "XML标签测试: 内容。检查是否被误解析为HTML。", + "status": "Pending" + } + ] + } + ], + "tables": [] + }, + { + "id": "EMPTY_DESC", + "name": "空描述测试", + "version": "1.0", + "description": "", + "taskGroups": [ + { + "id": "emptyGroup", + "name": "任务组", + "steps": [ + { + "id": "emptyStep1", + "type": "Manual", + "content": "", + "status": "Pending" + }, + { + "id": "emptyStep2", + "type": "Manual", + "content": "正常步骤内容", + "status": "Pending" + } + ] + } + ], + "tables": [] + }, + { + "id": "LONG_DESC", + "name": "超长描述测试规程", + "version": "3.0.1", + "description": "这是一个包含大量步骤和详细描述的规程,用于测试系统在处理复杂规程时的性能和稳定性。\n规程包含多个任务组,每个任务组包含多个步骤。\n这种规程通常用于复杂的校准或验证过程。", + "taskGroups": [ + { + "id": "preparation", + "name": "准备阶段", + "steps": [ + { + "id": "prep_step_1", + "type": "Manual", + "content": "准备步骤 1: 详细说明操作内容和注意事项。这是第 1 个步骤,需要仔细执行。", + "status": "Confirmed" + }, + { + "id": "prep_step_2", + "type": "Manual", + "content": "准备步骤 2: 详细说明操作内容和注意事项。这是第 2 个步骤,需要仔细执行。", + "status": "Confirmed" + }, + { + "id": "prep_step_3", + "type": "Manual", + "content": "准备步骤 3: 详细说明操作内容和注意事项。这是第 3 个步骤,需要仔细执行。", + "status": "Confirmed" + }, + { + "id": "prep_step_4", + "type": "Manual", + "content": "准备步骤 4: 详细说明操作内容和注意事项。这是第 4 个步骤,需要仔细执行。", + "status": "Pending" + }, + { + "id": "prep_step_5", + "type": "Manual", + "content": "准备步骤 5: 详细说明操作内容和注意事项。这是第 5 个步骤,需要仔细执行。", + "status": "Pending" + }, + { + "id": "prep_step_6", + "type": "Manual", + "content": "准备步骤 6: 详细说明操作内容和注意事项。这是第 6 个步骤,需要仔细执行。", + "status": "Pending" + }, + { + "id": "prep_step_7", + "type": "Manual", + "content": "准备步骤 7: 详细说明操作内容和注意事项。这是第 7 个步骤,需要仔细执行。", + "status": "Pending" + }, + { + "id": "prep_step_8", + "type": "Manual", + "content": "准备步骤 8: 详细说明操作内容和注意事项。这是第 8 个步骤,需要仔细执行。", + "status": "Pending" + }, + { + "id": "prep_step_9", + "type": "Manual", + "content": "准备步骤 9: 详细说明操作内容和注意事项。这是第 9 个步骤,需要仔细执行。", + "status": "Pending" + }, + { + "id": "prep_step_10", + "type": "Manual", + "content": "准备步骤 10: 详细说明操作内容和注意事项。这是第 10 个步骤,需要仔细执行。", + "status": "Pending" + } + ] + }, + { + "id": "measurement", + "name": "测量阶段", + "steps": [ + { + "id": "meas_step_1", + "type": "Automatic", + "content": "测量步骤 1: 进行精确的测量和数据采集。", + "status": "Pending" + }, + { + "id": "meas_step_2", + "type": "Automatic", + "content": "测量步骤 2: 进行精确的测量和数据采集。", + "status": "Pending" + }, + { + "id": "meas_step_3", + "type": "Automatic", + "content": "测量步骤 3: 进行精确的测量和数据采集。", + "status": "Pending" + }, + { + "id": "meas_step_4", + "type": "Automatic", + "content": "测量步骤 4: 进行精确的测量和数据采集。", + "status": "Pending" + }, + { + "id": "meas_step_5", + "type": "Automatic", + "content": "测量步骤 5: 进行精确的测量和数据采集。", + "status": "Pending" + }, + { + "id": "meas_step_6", + "type": "Automatic", + "content": "测量步骤 6: 进行精确的测量和数据采集。", + "status": "Pending" + }, + { + "id": "meas_step_7", + "type": "Automatic", + "content": "测量步骤 7: 进行精确的测量和数据采集。", + "status": "Pending" + }, + { + "id": "meas_step_8", + "type": "Automatic", + "content": "测量步骤 8: 进行精确的测量和数据采集。", + "status": "Pending" + } + ] + }, + { + "id": "verification", + "name": "验证阶段", + "steps": [ + { + "id": "verify_step_1", + "type": "Manual", + "content": "验证步骤 1: 确认测量结果的准确性和可靠性。", + "status": "Pending" + }, + { + "id": "verify_step_2", + "type": "Manual", + "content": "验证步骤 2: 确认测量结果的准确性和可靠性。", + "status": "Pending" + }, + { + "id": "verify_step_3", + "type": "Manual", + "content": "验证步骤 3: 确认测量结果的准确性和可靠性。", + "status": "Pending" + }, + { + "id": "verify_step_4", + "type": "Manual", + "content": "验证步骤 4: 确认测量结果的准确性和可靠性。", + "status": "Pending" + }, + { + "id": "verify_step_5", + "type": "Manual", + "content": "验证步骤 5: 确认测量结果的准确性和可靠性。", + "status": "Pending" + } + ] + } + ], + "tables": [], + "totalSteps": 23, + "completedSteps": 3 + }, + { + "id": "TEST-2024-12-31_V1.0", + "name": "日期戳测试规程 2024-12-31", + "version": "V1.0", + "description": "创建日期: 2024-12-31\n修订日期: 2024-12-31\n\n这个规程使用了日期戳命名格式,用于测试系统对不同命名约定的支持。", + "taskGroups": [ + { + "id": "dateTestGroup", + "name": "日期戳测试组", + "steps": [ + { + "id": "dateStep1", + "type": "Manual", + "content": "检查日期格式显示: 2024-12-31。验证日期解析功能。", + "status": "Pending" + }, + { + "id": "dateStep2", + "type": "Automatic", + "content": "测试时间戳: 2024-12-31 23:59:59。验证时间格式处理。", + "status": "Pending", + "tableRefs": [ + "dateInfoTable" + ] + } + ] + } + ], + "tables": [ + { + "id": "dateInfoTable", + "name": "日期信息表", + "columns": [ + { + "id": "createDate", + "name": "创建日期", + "type": "text" + }, + { + "id": "modifyDate", + "name": "修改日期", + "type": "text" + } + ] + } + ] + }, + { + "id": "MULTILINE", + "name": "多行文本测试", + "version": "1.0", + "description": "第一行描述\n第二行描述\n第三行描述\n\n这个规程用于测试多行文本的显示效果。\n包括:\n- 换行处理\n- 段落间距\n- 列表显示", + "taskGroups": [ + { + "id": "multilineGroup", + "name": "多行测试组", + "steps": [ + { + "id": "multilineStep1", + "type": "Manual", + "content": "步骤说明:\n1. 第一步操作\n2. 第二步操作\n3. 第三步操作\n\n注意事项:\n- 注意A\n- 注意B\n- 注意C\n\n按照上述步骤执行,确保每一步都正确完成,记录执行结果。", + "status": "Pending", + "tableRefs": [ + "multilineTable" + ] + }, + { + "id": "multilineStep2", + "type": "Manual", + "content": "这是一个包含\n多行\n文本\n的步骤\n\n测试多行显示效果", + "status": "Pending" + } + ] + } + ], + "tables": [ + { + "id": "multilineTable", + "name": "多行数据表", + "columns": [ + { + "id": "description", + "name": "描述", + "type": "text" + }, + { + "id": "notes", + "name": "备注\n(多行)", + "type": "text" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/procedures/rcp63.json b/procedures/rcp63.json new file mode 100644 index 0000000..fe922f6 --- /dev/null +++ b/procedures/rcp63.json @@ -0,0 +1,6009 @@ +{ + "procedure": { + "id": "KMCIXRCP503", + "name": "一回路温度传感器绝缘和连续性检查:TP RCP63", + "version": "C4", + "activitySequence": [ + { + "id": "1", + "name": "3.0先决条件", + "stages": [ + { + "id": "0", + "name": "3.0先决条件", + "actions": [ + { + "id": "147", + "document": "

在热停堆工况(热功率稳定),平均温度最大允许变化<0.2°℃;若KIC中显示的三个环路平均温度变化 AT>0.2℃,则采取数据滑动平均计算的方式,测量期间确保三个环路平均温度的滑动平均值最大最小值之差<0.2℃℃、测量时间大于滑动周期。

", + "mode": "manual", + "dataFields": [ + { + "tableRef": "#/tables/manualConfirm", + "fields": ["confirm_147"] + } + ] + }, + { + "id": "150", + "document": "

三个蒸发器处于稳定状态,压力一致;

", + "mode": "manual", + "dataFields": [ + { + "tableRef": "#/tables/manualConfirm", + "fields": ["confirm_150"] + } + ] + }, + { + "id": "43", + "document": "

蒸汽发生器水位稳定

", + "mode": "manual", + "dataFields": [ + { + "tableRef": "#/tables/manualConfirm", + "fields": ["confirm_43"] + } + ] + }, + { + "id": "75", + "document": "

RCV的一个下泄孔板投入运行(如果不是一个孔板,确保工作期间没有下泄孔板相0关操作,一回路温度保持稳定,测量结果若超差需要分析下泄流量的影响);

", + "mode": "manual", + "dataFields": [ + { + "tableRef": "#/tables/manualConfirm", + "fields": ["confirm_75"] + } + ] + }, + { + "id": "92", + "document": "

其它模式/工况下需进行相应的独立风险分析。

", + "mode": "manual", + "dataFields": [ + { + "tableRef": "#/tables/manualConfirm", + "fields": ["confirm_92"] + } + ] + } + ] + } + ] + }, + { + "id": "2", + "name": "5.1试验前状态说明和检查", + "stages": [ + { + "id": "1", + "name": "试验前状态说明和检查", + "actions": [ + { + "id": "153", + "document": "

本程序适用于热停堆(热功率稳定)工况下执行RCP63程序(K-MT-I-X-RCP-501)统一进行Sensor Check 部分使用。

", + "mode": "manual", + "dataFields": [ + { + "tableRef": "#/tables/manualConfirm", + "fields": ["confirm_153"] + } + ] + }, + { + "id": "136", + "document": "

执行前确认KIC中一回路三个环路平均温度变化量不超过0.2℃;

", + "mode": "auto", + "functionType": "DATA_ACQUISITION_WITH_VALIDATION", + "functionParameters": ["RCP614KM", "RCP618KM", "RCP622KM"] + }, + { + "id": "137", + "document": "

若KIC中显示的一回路三个环路平均温度变化超过0.2℃,则采取滑动平均的计算方式记录滑动周期: (建议60s),一环路平均温度滑动平均最大最小值之差: ,二环路平均温度滑动平均最大最小值之差: ,三环路平均温度滑动平均最大最小值之差: ,每个数据测量的最短时间(应大于滑动周期): 。确认三个环路平均温度的滑动平均最大与最小值之差不超过0.2℃。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION_WITH_VALIDATION", + "functionParameters": [ + "RCP614KM", + "RCP618KM", + "RCP622KM", + "RCP612KM", + "RCP616KM", + "RCP620KM", + "RCP613KM", + "RCP617KM", + "RCP621KM", + "RCP611KM", + "RCP615KM", + "RCP619KM" + ] + }, + { + "id": "138", + "document": "

确认 RPR、RPN上无相关的试验检修工作。

", + "mode": "manual", + "dataFields": [ + { + "tableRef": "#/tables/manualConfirm", + "fields": ["confirm_138"] + } + ] + }, + { + "id": "139", + "document": "

按附表1检查相关模拟量指示并记录,确认结果满意。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "RCP614KM", + "RCP618KM", + "RCP622KM", + "RCP612KM", + "RCP616KM", + "RCP620KM", + "RCP613KM", + "RCP617KM", + "RCP621KM", + "RCP611KM", + "RCP615KM", + "RCP619KM" + ] + }, + { + "id": "140", + "document": "

试验前根据附表1报警清单检查报警,确认无异常报警,方可进行后续检查工作

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "RCP455AA", + "RPA/B138AA", + "RPA/B167AA", + "RPA/B171AA", + "RPA/B079AA", + "RPA/B039LA", + "RPA/B168KA", + "RPA/B169KA", + "RPA/B170KA", + "RPA/B171KA", + "RPA/B164KA", + "RPA/B165KA", + "RPA/B166KA", + "RPA/B167KA", + "RPA/B036KA", + "RPA/B037KA", + "RPA/B038KA", + "RPA/B039KS", + "RPA/B096KS", + "RPA/B097KS", + "RPA/B098KS", + "RPA/B079KA", + "RPA/B134KA", + "RPA/B135KA", + "RPA/B136KA", + "RPA/B137KA", + "RPA/B138KA", + "RGL507KA", + "RGL508KA", + "RGL509KA", + "RGL503KA", + "RGL510KA", + "RGL511KA", + "RGL512KA", + "RGL504KA", + "RCP455KA1", + "RCP455KA2", + "RCP455KA3", + "RGL405KA", + "RGL407KA", + "RGL408KA", + "RGL406KA", + "RCP536KA", + "RCP674KA", + "RCP675KA", + "RCP680KA", + "RCP681KA", + "RCP676KA", + "RCP677KA", + "RCP682KA", + "RCP683KA", + "RCP678KA", + "RCP679KA", + "RCP684KA", + "RCP685KA", + "RCP811KA1", + "RCP811KA2", + "RCP813KA1", + "RCP813KA2", + "RCP815KA1", + "RCP815KA2", + "RGL001KA", + "RGL008KA", + "RGL033KA", + "RGL035KA", + "KCS001KA", + "KCS007KA", + "KCS002KA", + "KCS008KA", + "KCS003KA", + "KCS009KA" + ] + } + ], + "tableRefs": [ + "#/tables/1", + "#/tables/11", + "#/tables/12", + "#/tables/13" + ] + } + ] + }, + { + "id": "3", + "name": "5.2保护Ⅰ组传感器及线缆绝缘及电阻检查", + "stages": [ + { + "id": "2", + "name": "保护Ⅰ组传感器及线缆绝缘及电阻检查", + "actions": [ + { + "id": "141", + "document": "

根据附表1确认没有保护组超温超功通道相关异常报警,参数正常

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "RCP614KM", + "RCP618KM", + "RCP622KM", + "RCP612KM", + "RCP616KM", + "RCP620KM", + "RCP613KM", + "RCP617KM", + "RCP621KM", + "RCP611KM", + "RCP615KM", + "RCP619KM", + "RCP455AA", + "RPA/B138AA", + "RPA/B167AA", + "RPA/B171AA", + "RPA/B079AA", + "RPA/B039LA", + "RPA/B168KA", + "RPA/B169KA", + "RPA/B170KA", + "RPA/B171KA", + "RPA/B164KA", + "RPA/B165KA", + "RPA/B166KA", + "RPA/B167KA", + "RPA/B036KA", + "RPA/B037KA", + "RPA/B038KA", + "RPA/B039KS", + "RPA/B096KS", + "RPA/B097KS", + "RPA/B098KS", + "RPA/B079KA", + "RPA/B134KA", + "RPA/B135KA", + "RPA/B136KA", + "RPA/B137KA", + "RPA/B138KA", + "RGL507KA", + "RGL508KA", + "RGL509KA", + "RGL503KA", + "RGL510KA", + "RGL511KA", + "RGL512KA", + "RGL504KA", + "RCP455KA1", + "RCP455KA2", + "RCP455KA3", + "RGL405KA", + "RGL407KA", + "RGL408KA", + "RGL406KA", + "RCP536KA", + "RCP674KA", + "RCP675KA", + "RCP680KA", + "RCP681KA", + "RCP676KA", + "RCP677KA", + "RCP682KA", + "RCP683KA", + "RCP678KA", + "RCP679KA", + "RCP684KA", + "RCP685KA", + "RCP811KA1", + "RCP811KA2", + "RCP813KA1", + "RCP813KA2", + "RCP815KA1", + "RCP815KA2", + "RGL001KA", + "RGL008KA", + "RGL033KA", + "RGL035KA", + "KCS001KA", + "KCS007KA", + "KCS002KA", + "KCS008KA", + "KCS003KA", + "KCS009KA" + ] + }, + { + "id": "142", + "document": "

与主控取得联系,确认可以开始保护I组拆线测量工作。

", + "mode": "manual", + "dataFields": [ + { + "tableRef": "#/tables/manualConfirm", + "fields": ["confirm_142"] + } + ] + }, + { + "id": "143", + "document": "

在KCS013AR/EB911/X5端子排处,用小螺丝刀松开两边紧固螺丝,拔下X5接线端子,插入专用接头。

", + "mode": "manual", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "RCP030MT", + "RCP033MT", + "RCP044MT", + "RCP043MT" + ] + }, + { + "id": "146", + "document": "

测量RCP030MT的4根导线两两间电阻及四线制电阻,结果记录在附表2。

", + "functionType": "RESISTANCE_4WIRE", + "functionParameters": { + "R1/2": 0, + "R1/3": 0, + "R1/4": 0, + "R2/3": 0, + "R2/4": 0, + "R3/4": 0, + "R/4wires": 0 + }, + "channel": "input1", + "uploadStrategy": "onComplete" + }, + { + "id": "27", + "document": "

测量RCP030MT的50VDC对地绝缘电阻,测量时间不小于20秒,将测量结果填入附表2,测量后将两个探头对地放电。

", + "functionType": "DATA_ACQUISITION", + "functionParameters": ["Insulation"], + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "28", + "document": "

测量RCP033MT的4根导线两两间电阻及四线制电阻,结果记录在附表2。

", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "R1/2", + "R1/3", + "R1/4", + "R2/3", + "R2/4", + "R3/4", + "R/4wires" + ], + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "29", + "document": "

测量RCP033MT的50VDC对地绝缘电阻,测量时间不小于20秒,将测量结果填入附表2,测量后将两个探头对地放电。

", + "functionType": "DATA_ACQUISITION", + "functionParameters": ["Insulation"], + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "30", + "document": "

插回X5端子排,并用螺丝刀上紧螺丝,确保恢复良好。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "RCP030MT", + "RCP033MT", + "RCP044MT", + "RCP043MT" + ] + }, + { + "id": "33", + "document": "

测量RCP031MT的4根导线两两间电阻及四线制电阻,结果记录在附表2。

", + "functionType": "RESISTANCE_4WIRE", + "functionParameters": { + "R1/2": 0, + "R1/3": 0, + "R1/4": 0, + "R2/3": 0, + "R2/4": 0, + "R3/4": 0, + "R/4wires": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "34", + "document": "

测量RCP031MT的50VDC对地绝缘电阻,测量时间不小于20秒,将测量结果填入附表2,测量后将两个探头对地放电。

", + "functionType": "INSULATION", + "functionParameters": { + "Insulation": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "35", + "document": "

测量RCP034MT的4根导线两两间电阻及四线制电阻,结果记录在附表2。

", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "R1/2", + "R1/3", + "R1/4", + "R2/3", + "R2/4", + "R3/4", + "R/4wires" + ], + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "36", + "document": "

测量RCP034MT的50VDC对地绝缘电阻,测量时间不小于20秒,将测量结果填入附表2,测量后将两个探头对地放电。

", + "functionType": "INSULATION", + "functionParameters": { + "Insulation": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "37", + "document": "

在KCS013AR/EB902/X1端子排处,用小螺丝刀松开两边紧固螺丝,拔下X1接线端子,插入专用接头。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "RCP030MT", + "RCP033MT", + "RCP044MT", + "RCP043MT" + ] + }, + { + "id": "38", + "document": "

测量RCP043MT的3根导线两两间电阻,结果记录在附表2。

", + "functionType": "RESISTANCE_3WIRE", + "functionParameters": { + "R2/3": 0, + "R2/4": 0, + "R3/4": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "39", + "document": "

测量 RCP043MT的50VDC对地绝缘电阻,测量时间不小于20秒,将测量结果填入附表2,测量后将两个探头对地放电。

", + "functionType": "INSULATION", + "functionParameters": { + "Insulation": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "40", + "document": "

测量RCP044MT的3根导线两两间电阻,结果记录在附表2。

", + "functionType": "RESISTANCE_3WIRE", + "functionParameters": { + "R2/3": 0, + "R2/4": 0, + "R3/4": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "41", + "document": "

测量 RCP044MT的50VDC对地绝缘电阻,测量时间不小于20秒,将测量结果填入附表2,测量后将两个探头对地放电。

", + "functionType": "INSULATION", + "functionParameters": { + "Insulation": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "42", + "document": "

插回X1端子排,并用螺丝刀上紧螺丝,确保恢复良好。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "RCP030MT", + "RCP033MT", + "RCP044MT", + "RCP043MT" + ] + }, + { + "id": "44", + "document": "

请主控检查保护Ⅰ组相关报警消除,各参数正常。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "RCP614KM", + "RCP618KM", + "RCP622KM", + "RCP612KM", + "RCP616KM", + "RCP620KM", + "RCP613KM", + "RCP617KM", + "RCP621KM", + "RCP611KM", + "RCP615KM", + "RCP619KM", + "RCP455AA", + "RPA/B138AA", + "RPA/B167AA", + "RPA/B171AA", + "RPA/B079AA", + "RPA/B039LA", + "RPA/B168KA", + "RPA/B169KA", + "RPA/B170KA", + "RPA/B171KA", + "RPA/B164KA", + "RPA/B165KA", + "RPA/B166KA", + "RPA/B167KA", + "RPA/B036KA", + "RPA/B037KA", + "RPA/B038KA", + "RPA/B039KS", + "RPA/B096KS", + "RPA/B097KS", + "RPA/B098KS", + "RPA/B079KA", + "RPA/B134KA", + "RPA/B135KA", + "RPA/B136KA", + "RPA/B137KA", + "RPA/B138KA", + "RGL507KA", + "RGL508KA", + "RGL509KA", + "RGL503KA", + "RGL510KA", + "RGL511KA", + "RGL512KA", + "RGL504KA", + "RCP455KA1", + "RCP455KA2", + "RCP455KA3", + "RGL405KA", + "RGL407KA", + "RGL408KA", + "RGL406KA", + "RCP536KA", + "RCP674KA", + "RCP675KA", + "RCP680KA", + "RCP681KA", + "RCP676KA", + "RCP677KA", + "RCP682KA", + "RCP683KA", + "RCP678KA", + "RCP679KA", + "RCP684KA", + "RCP685KA", + "RCP811KA1", + "RCP811KA2", + "RCP813KA1", + "RCP813KA2", + "RCP815KA1", + "RCP815KA2", + "RGL001KA", + "RGL008KA", + "RGL033KA", + "RGL035KA", + "KCS001KA", + "KCS007KA", + "KCS002KA", + "KCS008KA", + "KCS003KA", + "KCS009KA", + "RCP030MT", + "RCP033MT", + "RCP044MT", + "RCP043MT" + ] + } + ], + "tableRefs": [ + "#/tables/13", + "#/tables/17", + "#/tables/20", + "#/tables/12" + ] + } + ] + }, + { + "id": "4", + "name": "5.3保护Ⅱ组传感器及线缆绝缘及电阻检查", + "stages": [ + { + "id": "3", + "name": "保护Ⅱ组传感器及线缆绝缘及电阻检查", + "actions": [ + { + "id": "45", + "document": "

根据附表1确认没有保护组超温超功通道相关异常报警,参数正常

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "RCP614KM", + "RCP618KM", + "RCP622KM", + "RCP612KM", + "RCP616KM", + "RCP620KM", + "RCP613KM", + "RCP617KM", + "RCP621KM", + "RCP611KM", + "RCP615KM", + "RCP619KM", + "RCP455AA", + "RPA/B138AA", + "RPA/B167AA", + "RPA/B171AA", + "RPA/B079AA", + "RPA/B039LA", + "RPA/B168KA", + "RPA/B169KA", + "RPA/B170KA", + "RPA/B171KA", + "RPA/B164KA", + "RPA/B165KA", + "RPA/B166KA", + "RPA/B167KA", + "RPA/B036KA", + "RPA/B037KA", + "RPA/B038KA", + "RPA/B039KS", + "RPA/B096KS", + "RPA/B097KS", + "RPA/B098KS", + "RPA/B079KA", + "RPA/B134KA", + "RPA/B135KA", + "RPA/B136KA", + "RPA/B137KA", + "RPA/B138KA", + "RGL507KA", + "RGL508KA", + "RGL509KA", + "RGL503KA", + "RGL510KA", + "RGL511KA", + "RGL512KA", + "RGL504KA", + "RCP455KA1", + "RCP455KA2", + "RCP455KA3", + "RGL405KA", + "RGL407KA", + "RGL408KA", + "RGL406KA", + "RCP536KA", + "RCP674KA", + "RCP675KA", + "RCP680KA", + "RCP681KA", + "RCP676KA", + "RCP677KA", + "RCP682KA", + "RCP683KA", + "RCP678KA", + "RCP679KA", + "RCP684KA", + "RCP685KA", + "RCP811KA1", + "RCP811KA2", + "RCP813KA1", + "RCP813KA2", + "RCP815KA1", + "RCP815KA2", + "RGL001KA", + "RGL008KA", + "RGL033KA", + "RGL035KA", + "KCS001KA", + "KCS007KA", + "KCS002KA", + "KCS008KA", + "KCS003KA", + "KCS009KA" + ] + }, + { + "id": "46", + "document": "

与主控取得联系,确认可以开始保护Ⅱ组拆线测量工作。

", + "mode": "manual" + }, + { + "id": "47", + "document": "

在KCS023AR/EB911/X5端子排处,用小螺丝刀松开两边紧固螺丝,拔下X5接线端子,插入专用接头。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "RCP045MT", + "RCP048MT", + "RCP028MT", + "RCP029MT" + ] + }, + { + "id": "48", + "document": "

测量RCP045MT的4根导线两两间电阻及四线制电阻,结果记录在附表2。

", + "functionType": "RESISTANCE_4WIRE", + "functionParameters": { + "R1/2": 0, + "R1/3": 0, + "R1/4": 0, + "R2/3": 0, + "R2/4": 0, + "R3/4": 0, + "R/4wires": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "49", + "document": "

测量RCP045MT的50VDC对地绝缘电阻电阻,测量时间不小于20秒,将测量结果填入附表2,测量后将两个探头对地放电。

", + "functionType": "INSULATION", + "functionParameters": { + "Insulation": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "50", + "document": "

测量RCP048MT的4根导线两两间电阻及四线制电阻,结果记录在附表2。

", + "functionType": "RESISTANCE_4WIRE", + "functionParameters": { + "R1/2": 0, + "R1/3": 0, + "R1/4": 0, + "R2/3": 0, + "R2/4": 0, + "R3/4": 0, + "R/4wires": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "51", + "document": "

测量RCP048MT的50VDC对地绝缘电阻电阻,测量时间不小于20秒,将测量结果填入附表2,测量后将两个探头对地放电。

", + "functionType": "INSULATION", + "functionParameters": { + "Insulation": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "52", + "document": "

插回X5端子排,并用螺丝刀上紧螺丝,确保恢复良好。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "RCP045MT", + "RCP048MT", + "RCP028MT", + "RCP029MT" + ] + }, + { + "id": "55", + "document": "

测量RCP046MT的4根导线两两间电阻及四线制电阻,结果记录在附表2。

", + "functionType": "RESISTANCE_4WIRE", + "functionParameters": { + "R1/2": 0, + "R1/3": 0, + "R1/4": 0, + "R2/3": 0, + "R2/4": 0, + "R3/4": 0, + "R/4wires": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "56", + "document": "

测量 RCP046MT的50VDC对地绝缘电阻,测量时间不小于20秒,将测量结果填入附表2,测量后将两个探头对地放电。

", + "functionType": "INSULATION", + "functionParameters": { + "Insulation": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "57", + "document": "

测量RCP049MT的4根导线两两间电阻及四线制电阻,结果记录在附表2。

", + "functionType": "RESISTANCE_4WIRE", + "functionParameters": { + "R1/2": 0, + "R1/3": 0, + "R1/4": 0, + "R2/3": 0, + "R2/4": 0, + "R3/4": 0, + "R/4wires": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "58", + "document": "

测量 RCP049MT的50VDC对地绝缘电阻,测量时间不小于20秒,将测量结果填入附表2,测量后将两个探头对地放电。

", + "functionType": "INSULATION", + "functionParameters": { + "Insulation": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "59", + "document": "

在KCS023AR/EB902/X1端子排处,用小螺丝刀松开两边紧固螺丝,拔下X1接线端子,插入专用接头。

", + "mode": "manual", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "RCP045MT", + "RCP048MT", + "RCP028MT", + "RCP029MT" + ], + "dataFields": [ + { + "tableRef": "#/tables/manualConfirm", + "fields": ["confirm_59"] + } + ] + }, + { + "id": "60", + "document": "

测量RCP028MT的3根导线两两间电阻,结果记录在附表2。

", + "functionType": "RESISTANCE_3WIRE", + "functionParameters": { + "R2/3": 0, + "R2/4": 0, + "R3/4": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "61", + "document": "

测量RCP028MT的50VDC对地绝缘电阻,测量时间不小于20秒,将测量结果填入附表2,测量后将两个探头对地放电。

", + "functionType": "INSULATION", + "functionParameters": { + "Insulation": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "62", + "document": "

测量RCP029MT的3根导线两两间电阻,结果记录在附表2。

", + "functionType": "RESISTANCE_3WIRE", + "functionParameters": { + "R2/3": 0, + "R2/4": 0, + "R3/4": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "63", + "document": "

测量RCP029MT的50VDC对地绝缘电阻,测量时间不小于20秒,将测量结果填入附表2,测量后将两个探头对地放电。

", + "functionType": "INSULATION", + "functionParameters": { + "Insulation": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "64", + "document": "

插回X1端子排,并用螺丝刀上紧螺丝,确保恢复良好。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "RCP045MT", + "RCP048MT", + "RCP028MT", + "RCP029MT" + ] + }, + { + "id": "66", + "document": "

请主控检查保护Ⅱ组相关报警消除,各参数正常。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "RCP614KM", + "RCP618KM", + "RCP622KM", + "RCP612KM", + "RCP616KM", + "RCP620KM", + "RCP613KM", + "RCP617KM", + "RCP621KM", + "RCP611KM", + "RCP615KM", + "RCP619KM", + "RCP455AA", + "RPA/B138AA", + "RPA/B167AA", + "RPA/B171AA", + "RPA/B079AA", + "RPA/B039LA", + "RPA/B168KA", + "RPA/B169KA", + "RPA/B170KA", + "RPA/B171KA", + "RPA/B164KA", + "RPA/B165KA", + "RPA/B166KA", + "RPA/B167KA", + "RPA/B036KA", + "RPA/B037KA", + "RPA/B038KA", + "RPA/B039KS", + "RPA/B096KS", + "RPA/B097KS", + "RPA/B098KS", + "RPA/B079KA", + "RPA/B134KA", + "RPA/B135KA", + "RPA/B136KA", + "RPA/B137KA", + "RPA/B138KA", + "RGL507KA", + "RGL508KA", + "RGL509KA", + "RGL503KA", + "RGL510KA", + "RGL511KA", + "RGL512KA", + "RGL504KA", + "RCP455KA1", + "RCP455KA2", + "RCP455KA3", + "RGL405KA", + "RGL407KA", + "RGL408KA", + "RGL406KA", + "RCP536KA", + "RCP674KA", + "RCP675KA", + "RCP680KA", + "RCP681KA", + "RCP676KA", + "RCP677KA", + "RCP682KA", + "RCP683KA", + "RCP678KA", + "RCP679KA", + "RCP684KA", + "RCP685KA", + "RCP811KA1", + "RCP811KA2", + "RCP813KA1", + "RCP813KA2", + "RCP815KA1", + "RCP815KA2", + "RGL001KA", + "RGL008KA", + "RGL033KA", + "RGL035KA", + "KCS001KA", + "KCS007KA", + "KCS002KA", + "KCS008KA", + "KCS003KA", + "KCS009KA", + "RCP045MT", + "RCP048MT", + "RCP028MT", + "RCP029MT" + ] + } + ], + "tableRefs": [ + "#/tables/13", + "#/tables/18", + "#/tables/21", + "#/tables/12" + ] + } + ] + }, + { + "id": "5", + "name": "5.4保护Ⅲ组传感器及线缆绝缘及电阻检查", + "stages": [ + { + "id": "4", + "name": "保护Ⅲ组传感器及线缆绝缘及电阻检查", + "actions": [ + { + "id": "67", + "document": "

根据附表1确认没有保护组超温超功通道相关异常报警,参数正常

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "RCP614KM", + "RCP618KM", + "RCP622KM", + "RCP612KM", + "RCP616KM", + "RCP620KM", + "RCP613KM", + "RCP617KM", + "RCP621KM", + "RCP611KM", + "RCP615KM", + "RCP619KM", + "RCP455AA", + "RPA/B138AA", + "RPA/B167AA", + "RPA/B171AA", + "RPA/B079AA", + "RPA/B039LA", + "RPA/B168KA", + "RPA/B169KA", + "RPA/B170KA", + "RPA/B171KA", + "RPA/B164KA", + "RPA/B165KA", + "RPA/B166KA", + "RPA/B167KA", + "RPA/B036KA", + "RPA/B037KA", + "RPA/B038KA", + "RPA/B039KS", + "RPA/B096KS", + "RPA/B097KS", + "RPA/B098KS", + "RPA/B079KA", + "RPA/B134KA", + "RPA/B135KA", + "RPA/B136KA", + "RPA/B137KA", + "RPA/B138KA", + "RGL507KA", + "RGL508KA", + "RGL509KA", + "RGL503KA", + "RGL510KA", + "RGL511KA", + "RGL512KA", + "RGL504KA", + "RCP455KA1", + "RCP455KA2", + "RCP455KA3", + "RGL405KA", + "RGL407KA", + "RGL408KA", + "RGL406KA", + "RCP536KA", + "RCP674KA", + "RCP675KA", + "RCP680KA", + "RCP681KA", + "RCP676KA", + "RCP677KA", + "RCP682KA", + "RCP683KA", + "RCP678KA", + "RCP679KA", + "RCP684KA", + "RCP685KA", + "RCP811KA1", + "RCP811KA2", + "RCP813KA1", + "RCP813KA2", + "RCP815KA1", + "RCP815KA2", + "RGL001KA", + "RGL008KA", + "RGL033KA", + "RGL035KA", + "KCS001KA", + "KCS007KA", + "KCS002KA", + "KCS008KA", + "KCS003KA", + "KCS009KA" + ] + }, + { + "id": "68", + "document": "

15m与主控取得联系,确认可以开始保护组拆线测量工作

", + "mode": "manual", + "dataFields": [ + { + "tableRef": "#/tables/manualConfirm", + "fields": ["confirm_68"] + } + ] + }, + { + "id": "69", + "document": "

在KCS033AR/EB911/X5端子排处,用小螺丝刀松开两边紧固螺丝,拔下X5接线端子,插入专用接头。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "RCP057MT", + "RCP060MT", + "RCP055MT", + "RCP056MT" + ] + }, + { + "id": "70", + "document": "

测量RCP057MT的4根导线两两间电阻及四线制电阻,结果记录在附表2。

", + "functionType": "RESISTANCE_4WIRE", + "functionParameters": { + "R1/2": 0, + "R1/3": 0, + "R1/4": 0, + "R2/3": 0, + "R2/4": 0, + "R3/4": 0, + "R/4wires": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "71", + "document": "

测量RCP057MT的50VDC对地绝缘电阻,测量时间不小于20秒,将测量结果填入附表2,测量后将两个探头对地放电。

", + "functionType": "INSULATION", + "functionParameters": { + "Insulation": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "72", + "document": "

测量RCP060MT的4根导线两两间电阻及四线制电阻,结果记录在附表2。

", + "functionType": "RESISTANCE_4WIRE", + "functionParameters": { + "R1/2": 0, + "R1/3": 0, + "R1/4": 0, + "R2/3": 0, + "R2/4": 0, + "R3/4": 0, + "R/4wires": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "73", + "document": "

测量RCP060MT的50VDC对地绝缘电阻,测量时间不小于20秒,将测量结果填入附表2,测量后将两个探头对地放电。

", + "functionType": "INSULATION", + "functionParameters": { + "Insulation": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "74", + "document": "

插回X5端子排,并用螺丝刀上紧螺丝,确保恢复良好。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "RCP057MT", + "RCP060MT", + "RCP055MT", + "RCP056MT" + ] + }, + { + "id": "77", + "document": "

测量RCP058MT的4根导线两两间电阻及四线制电阻,结果记录在附表2。

", + "functionType": "RESISTANCE_4WIRE", + "functionParameters": { + "R1/2": 0, + "R1/3": 0, + "R1/4": 0, + "R2/3": 0, + "R2/4": 0, + "R3/4": 0, + "R/4wires": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "78", + "document": "

测量RCP058MT的50VDC对地绝缘电阻,测量时间不小于20秒,将测量结果填入附表2,测量后将两个探头对地放电。

", + "functionType": "INSULATION", + "functionParameters": { + "Insulation": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "79", + "document": "

测量RCP061MT的4根导线两两间电阻及四线制电阻,结果记录在附表2。

", + "functionType": "RESISTANCE_4WIRE", + "functionParameters": { + "R1/2": 0, + "R1/3": 0, + "R1/4": 0, + "R2/3": 0, + "R2/4": 0, + "R3/4": 0, + "R/4wires": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "80", + "document": "

测量RCP061MT的50VDC对地绝缘电阻,测量时间不小于20秒,将测量结果填入附表2,测量后将两个探头对地放电。

", + "functionType": "INSULATION", + "functionParameters": { + "Insulation": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "81", + "document": "

在KCSO33AR/EB902/X1端子排处,用小螺丝刀松开两边紧固螺丝,拔下X1接线端子,插入专用接头。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "RCP057MT", + "RCP060MT", + "RCP055MT", + "RCP056MT" + ] + }, + { + "id": "82", + "document": "

测量RCP055MT的3根导线两两间电阻,结果记录在附表2。

", + "functionType": "RESISTANCE_3WIRE", + "functionParameters": { + "R2/3": 0, + "R2/4": 0, + "R3/4": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "83", + "document": "

测量RCP055MT的50VDC对地绝缘电阻,测量时间不小于20秒,将测量结果填入附表2,测量后将两个探头对地放电。

", + "functionType": "INSULATION", + "functionParameters": { + "Insulation": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "84", + "document": "

测量RCP056MT的3根导线两两间电阻,结果记录在附表2。

", + "functionType": "RESISTANCE_3WIRE", + "functionParameters": { + "R2/3": 0, + "R2/4": 0, + "R3/4": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "85", + "document": "

测量RCP056MT的50VDC对地绝缘电阻,测量时间不小于20秒,将测量结果填入附表2,测量后将两个探头对地放电。

", + "functionType": "INSULATION", + "functionParameters": { + "Insulation": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "86", + "document": "

插回X1端子排,并用螺丝刀上紧螺丝,确保恢复良好。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "RCP057MT", + "RCP060MT", + "RCP055MT", + "RCP056MT" + ] + }, + { + "id": "88", + "document": "

请主控检查保护组所有相关报警消除,各参数正常。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "RCP614KM", + "RCP618KM", + "RCP622KM", + "RCP612KM", + "RCP616KM", + "RCP620KM", + "RCP613KM", + "RCP617KM", + "RCP621KM", + "RCP611KM", + "RCP615KM", + "RCP619KM", + "RCP455AA", + "RPA/B138AA", + "RPA/B167AA", + "RPA/B171AA", + "RPA/B079AA", + "RPA/B039LA", + "RPA/B168KA", + "RPA/B169KA", + "RPA/B170KA", + "RPA/B171KA", + "RPA/B164KA", + "RPA/B165KA", + "RPA/B166KA", + "RPA/B167KA", + "RPA/B036KA", + "RPA/B037KA", + "RPA/B038KA", + "RPA/B039KS", + "RPA/B096KS", + "RPA/B097KS", + "RPA/B098KS", + "RPA/B079KA", + "RPA/B134KA", + "RPA/B135KA", + "RPA/B136KA", + "RPA/B137KA", + "RPA/B138KA", + "RGL507KA", + "RGL508KA", + "RGL509KA", + "RGL503KA", + "RGL510KA", + "RGL511KA", + "RGL512KA", + "RGL504KA", + "RCP455KA1", + "RCP455KA2", + "RCP455KA3", + "RGL405KA", + "RGL407KA", + "RGL408KA", + "RGL406KA", + "RCP536KA", + "RCP674KA", + "RCP675KA", + "RCP680KA", + "RCP681KA", + "RCP676KA", + "RCP677KA", + "RCP682KA", + "RCP683KA", + "RCP678KA", + "RCP679KA", + "RCP684KA", + "RCP685KA", + "RCP811KA1", + "RCP811KA2", + "RCP813KA1", + "RCP813KA2", + "RCP815KA1", + "RCP815KA2", + "RGL001KA", + "RGL008KA", + "RGL033KA", + "RGL035KA", + "KCS001KA", + "KCS007KA", + "KCS002KA", + "KCS008KA", + "KCS003KA", + "KCS009KA" + ] + } + ], + "tableRefs": [ + "#/tables/13", + "#/tables/19", + "#/tables/22", + "#/tables/12" + ] + } + ] + }, + { + "id": "6", + "name": "5.5控制组环路1传感器及线缆绝缘及电阻检查", + "stages": [ + { + "id": "5", + "name": "控制组环路1传感器及线缆绝缘及电阻检查", + "actions": [ + { + "id": "90", + "document": "

按附表1检查各参数正常稳定。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "RCP614KM", + "RCP618KM", + "RCP622KM", + "RCP612KM", + "RCP616KM", + "RCP620KM", + "RCP613KM", + "RCP617KM", + "RCP621KM", + "RCP611KM", + "RCP615KM", + "RCP619KM", + "RCP455AA", + "RPA/B138AA", + "RPA/B167AA", + "RPA/B171AA", + "RPA/B079AA", + "RPA/B039LA", + "RPA/B168KA", + "RPA/B169KA", + "RPA/B170KA", + "RPA/B171KA", + "RPA/B164KA", + "RPA/B165KA", + "RPA/B166KA", + "RPA/B167KA", + "RPA/B036KA", + "RPA/B037KA", + "RPA/B038KA", + "RPA/B039KS", + "RPA/B096KS", + "RPA/B097KS", + "RPA/B098KS", + "RPA/B079KA", + "RPA/B134KA", + "RPA/B135KA", + "RPA/B136KA", + "RPA/B137KA", + "RPA/B138KA", + "RGL507KA", + "RGL508KA", + "RGL509KA", + "RGL503KA", + "RGL510KA", + "RGL511KA", + "RGL512KA", + "RGL504KA", + "RCP455KA1", + "RCP455KA2", + "RCP455KA3", + "RGL405KA", + "RGL407KA", + "RGL408KA", + "RGL406KA", + "RCP536KA", + "RCP674KA", + "RCP675KA", + "RCP680KA", + "RCP681KA", + "RCP676KA", + "RCP677KA", + "RCP682KA", + "RCP683KA", + "RCP678KA", + "RCP679KA", + "RCP684KA", + "RCP685KA", + "RCP811KA1", + "RCP811KA2", + "RCP813KA1", + "RCP813KA2", + "RCP815KA1", + "RCP815KA2", + "RGL001KA", + "RGL008KA", + "RGL033KA", + "RGL035KA", + "KCS001KA", + "KCS007KA", + "KCS002KA", + "KCS008KA", + "KCS003KA", + "KCS009KA" + ] + }, + { + "id": "93", + "document": "

确认1环路已切除,通知15m可以进行环路1拆线测量。

", + "mode": "manual", + "dataFields": [ + { + "tableRef": "#/tables/manualConfirm", + "fields": ["confirm_93"] + } + ] + }, + { + "id": "94", + "document": "

确认RGL301AR/DB035 SIC5模块上无异常报警。

", + "mode": "manual", + "dataFields": [ + { + "tableRef": "#/tables/manualConfirm", + "fields": ["confirm_94"] + } + ] + }, + { + "id": "95", + "document": "

在 RGL301AR/EB902/X1端子排处,用小螺丝刀松开两边紧固螺丝,拔下X1接线端子,插入专用接头。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": ["RCP035MT", "RCP032MT"] + }, + { + "id": "96", + "document": "

测量RCP032MT的4根导线两两间电阻及四线制电阻,结果记录在附表2。

", + "functionType": "RESISTANCE_4WIRE", + "functionParameters": { + "R1/2": 0, + "R1/3": 0, + "R1/4": 0, + "R2/3": 0, + "R2/4": 0, + "R3/4": 0, + "R/4wires": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "97", + "document": "

测量RCP032MT的 50VDC对地绝缘电阻,测量时间不小于20秒,将测量结果填入附表 2,测量后探头对地放电。

", + "functionType": "INSULATION", + "functionParameters": { + "Insulation": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "98", + "document": "

插回X1端子排,并用螺丝刀上紧螺丝,确保恢复良好。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": ["RCP035MT", "RCP032MT"] + }, + { + "id": "99", + "document": "

在RGL301AR/EB902/X2端子排处,用小螺丝刀松开两边紧固螺丝,拔下X2接线端子,插入专用接头。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": ["RCP035MT", "RCP032MT"] + }, + { + "id": "100", + "document": "

测量RCP035MT的4根导线两两间电阻及四线制电阻,结果记录在附表2。

", + "functionType": "RESISTANCE_4WIRE", + "functionParameters": { + "R1/2": 0, + "R1/3": 0, + "R1/4": 0, + "R2/3": 0, + "R2/4": 0, + "R3/4": 0, + "R/4wires": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "101", + "document": "

测量RCP035MT的50VDC对地绝缘电阻,测量时间不小于20秒,将测量结果填入附表2,测量后探头对地放电。

", + "functionType": "INSULATION", + "functionParameters": { + "Insulation": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "102", + "document": "

插回X2端子排,并用螺丝刀上紧螺丝,确保恢复良好。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": ["RCP035MT", "RCP032MT"] + }, + { + "id": "104", + "document": "

检查确认RGL301AR/DB035 SIC5模块上无异常报警。

", + "mode": "manual", + "dataFields": [ + { + "tableRef": "#/tables/manualConfirm", + "fields": ["confirm_104"] + } + ] + }, + { + "id": "105", + "document": "

通知主控检查确认一环路 RCP032/035MT恢复良好,各参数正常,无任何异常报警

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "RCP614KM", + "RCP618KM", + "RCP622KM", + "RCP612KM", + "RCP616KM", + "RCP620KM", + "RCP613KM", + "RCP617KM", + "RCP621KM", + "RCP611KM", + "RCP615KM", + "RCP619KM\n\nRCP455AA", + "RPA/B138AA", + "RPA/B167AA", + "RPA/B171AA", + "RPA/B079AA", + "RPA/B039LA\nRPA/B168KA", + "RPA/B169KA", + "RPA/B170KA\nRPA/B171KA", + "RPA/B164KA", + "RPA/B165KA\nRPA/B166KA", + "RPA/B167KA", + "RPA/B036KA\nRPA/B037KA", + "RPA/B038KA", + "RPA/B039KS\nRPA/B096KS", + "RPA/B097KS", + "RPA/B098KS\nRPA/B079KA", + "RPA/B134KA", + "RPA/B135KA\nRPA/B136KA", + "RPA/B137KA", + "RPA/B138KA\nRGL507KA", + "RGL508KA", + "RGL509KA\nRGL503KA", + "RGL510KA", + "RGL511KA\nRGL512KA", + "RGL504KA", + "RCP455KA1\nRCP455KA2", + "RCP455KA3", + "RGL405KA\nRGL407KA", + "RGL408KA", + "RGL406KA\nRCP536KA", + "RCP674KA", + "RCP675KA\nRCP680KA", + "RCP681KA", + "RCP676KA\nRCP677KA", + "RCP682KA", + "RCP683KA\nRCP678KA", + "RCP679KA", + "RCP684KA\nRCP685KA", + "RCP811KA1", + "RCP811KA2\nRCP813KA1", + "RCP813KA2", + "RCP815KA1\nRCP815KA2", + "RGL001KA", + "RGL008KA\nRGL033KA", + "RGL035KA", + "KCS001KA\nKCS007KA", + "KCS002KA", + "KCS008KA\nKCS003KA", + "KCS009KA" + ] + } + ], + "tableRefs": [ + "#/tables/14", + "#/tables/13", + "#/tables/23", + "#/tables/12" + ] + } + ] + }, + { + "id": "7", + "name": "5.6控制组环路2传感器及线缆绝缘及电阻检查", + "stages": [ + { + "id": "6", + "name": "控制组环路2传感器及线缆绝缘及电阻检查", + "actions": [ + { + "id": "108", + "document": "

确认2环路已切除,通知15m可以进行环路2拆线测量。

", + "mode": "manual", + "dataFields": [ + { + "tableRef": "#/tables/manualConfirm", + "fields": ["confirm_108"] + } + ] + }, + { + "id": "109", + "document": "

在RGL301AR/EB902/X3端子排处,用小螺丝刀松开两边紧固螺丝,拔下X3接线端子,插入专用接头。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": ["RCP050MT", "RCP047MT"] + }, + { + "id": "110", + "document": "

测量RCP047MT的4根导线两两间电阻及四线制电阻,结果记录在附表2。

", + "functionType": "RESISTANCE_4WIRE", + "functionParameters": { + "R1/2": 0, + "R1/3": 0, + "R1/4": 0, + "R2/3": 0, + "R2/4": 0, + "R3/4": 0, + "R/4wires": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "111", + "document": "

测量RCP047MT的50VDC对地绝缘电阻,测量时间不小于20秒,将测量结果填入附表2,测量后探头对地放电。

", + "functionType": "INSULATION", + "functionParameters": { + "Insulation": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "112", + "document": "

插回X3端子排,并用螺丝刀上紧螺丝,确保恢复良好。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": ["RCP050MT", "RCP047MT"] + }, + { + "id": "113", + "document": "

在RGL301AR/EB902/X4端子排处,用小螺丝刀松开两边紧固螺丝,拔下X4接线端子,插入专用接头。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": ["RCP050MT", "RCP047MT"] + }, + { + "id": "114", + "document": "

测量RCP050MT的4根导线两两间电阻及四线制电阻,结果记录在附表2。

", + "functionType": "RESISTANCE_4WIRE", + "functionParameters": { + "R1/2": 0, + "R1/3": 0, + "R1/4": 0, + "R2/3": 0, + "R2/4": 0, + "R3/4": 0, + "R/4wires": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "115", + "document": "

测量RCP050MT的50VDC对地绝缘电阻,测量时间不小于20秒,将测量结果填入附表2,测量后探头对地放电。

", + "functionType": "INSULATION", + "functionParameters": { + "Insulation": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "116", + "document": "

插回X3端子排,并用螺丝刀上紧螺丝,确保恢复良好。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": ["RCP050MT", "RCP047MT"] + }, + { + "id": "118", + "document": "

通知主控检查确认二环路 RCP047/050MT恢复良好,各参数正常,无任何异常报警

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "RCP614KM", + "RCP618KM", + "RCP622KM", + "RCP612KM", + "RCP616KM", + "RCP620KM", + "RCP613KM", + "RCP617KM", + "RCP621KM", + "RCP611KM", + "RCP615KM", + "RCP619KM", + "RCP455AA", + "RPA/B138AA", + "RPA/B167AA", + "RPA/B171AA", + "RPA/B079AA", + "RPA/B039LA", + "RPA/B168KA", + "RPA/B169KA", + "RPA/B170KA", + "RPA/B171KA", + "RPA/B164KA", + "RPA/B165KA", + "RPA/B166KA", + "RPA/B167KA", + "RPA/B036KA", + "RPA/B037KA", + "RPA/B038KA", + "RPA/B039KS", + "RPA/B096KS", + "RPA/B097KS", + "RPA/B098KS", + "RPA/B079KA", + "RPA/B134KA", + "RPA/B135KA", + "RPA/B136KA", + "RPA/B137KA", + "RPA/B138KA", + "RGL507KA", + "RGL508KA", + "RGL509KA", + "RGL503KA", + "RGL510KA", + "RGL511KA", + "RGL512KA", + "RGL504KA", + "RCP455KA1", + "RCP455KA2", + "RCP455KA3", + "RGL405KA", + "RGL407KA", + "RGL408KA", + "RGL406KA", + "RCP536KA", + "RCP674KA", + "RCP675KA", + "RCP680KA", + "RCP681KA", + "RCP676KA", + "RCP677KA", + "RCP682KA", + "RCP683KA", + "RCP678KA", + "RCP679KA", + "RCP684KA", + "RCP685KA", + "RCP811KA1", + "RCP811KA2", + "RCP813KA1", + "RCP813KA2", + "RCP815KA1", + "RCP815KA2", + "RGL001KA", + "RGL008KA", + "RGL033KA", + "RGL035KA", + "KCS001KA", + "KCS007KA", + "KCS002KA", + "KCS008KA", + "KCS003KA", + "KCS009KA", + "RCP050MT", + "RCP047MT" + ] + } + ], + "tableRefs": [ + "#/tables/15", + "#/tables/24", + "#/tables/13", + "#/tables/12" + ] + } + ] + }, + { + "id": "8", + "name": "5.7控制组环路3传感器及线缆绝缘及电阻检查", + "stages": [ + { + "id": "7", + "name": "控制组环路3传感器及线缆绝缘及电阻检查", + "actions": [ + { + "id": "121", + "document": "

确认3环路已切除,通知15m可以进行环路3拆线测量。

", + "mode": "manual", + "dataFields": [ + { + "tableRef": "#/tables/manualConfirm", + "fields": ["confirm_121"] + } + ] + }, + { + "id": "122", + "document": "

在RGL301AR/EB902/X5端子排处,用小螺丝刀松开两边紧固螺丝,拔下X5接线端子,插入专用接头。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": ["RCP062MT", "RCP059MT"] + }, + { + "id": "123", + "document": "

测量RCP059MT的4根导线两两间电阻及四线制电阻,结果记录在附表2。

", + "functionType": "RESISTANCE_4WIRE", + "functionParameters": { + "R1/2": 0, + "R1/3": 0, + "R1/4": 0, + "R2/3": 0, + "R2/4": 0, + "R3/4": 0, + "R/4wires": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "124", + "document": "

测量RCP059MT的50VDC对地绝缘电阻,测量时间不小于20秒,将测量结果填入附表2,测量后探头对地放电。

", + "functionType": "INSULATION", + "functionParameters": { + "Insulation": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "125", + "document": "

插回X5端子排,并用螺丝刀上紧螺丝,确保恢复良好。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": ["RCP062MT", "RCP059MT"] + }, + { + "id": "126", + "document": "

在RGL301AR/EB902/X6端子排处,用小螺丝刀松开两边紧固螺丝,拔下X6接线端子,插入专用接头。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": ["RCP062MT", "RCP059MT"] + }, + { + "id": "127", + "document": "

测量RCP062MT的4根导线两两间电阻及四线制电阻,结果记录在附表2。

", + "functionType": "RESISTANCE_4WIRE", + "functionParameters": { + "R1/2": 0, + "R1/3": 0, + "R1/4": 0, + "R2/3": 0, + "R2/4": 0, + "R3/4": 0, + "R/4wires": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "128", + "document": "

测量RCP062MT的50VDC对地绝缘电阻,测量时间不小于20秒,将测量结果填入附表2,测量后探头对地放电。

", + "functionType": "INSULATION", + "functionParameters": { + "Insulation": 0 + }, + "channel": "input1", + "uploadStrategy": "immediate" + }, + { + "id": "129", + "document": "

插回X6端子排,并用螺丝刀上紧螺丝,确保恢复良好。

", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": ["RCP062MT", "RCP059MT"] + }, + { + "id": "131", + "document": "

通知主控检查确认环路三 RCPO59/062MT恢复良好,各参数正常,无任何异常报警

", + "mode": "manual", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "RCP614KM", + "RCP618KM", + "RCP622KM", + "RCP612KM", + "RCP616KM", + "RCP620KM", + "RCP613KM", + "RCP617KM", + "RCP621KM", + "RCP611KM", + "RCP615KM", + "RCP619KM", + "RCP455AA", + "RPA/B138AA", + "RPA/B167AA", + "RPA/B171AA", + "RPA/B079AA", + "RPA/B039LA", + "RPA/B168KA", + "RPA/B169KA", + "RPA/B170KA", + "RPA/B171KA", + "RPA/B164KA", + "RPA/B165KA", + "RPA/B166KA", + "RPA/B167KA", + "RPA/B036KA", + "RPA/B037KA", + "RPA/B038KA", + "RPA/B039KS", + "RPA/B096KS", + "RPA/B097KS", + "RPA/B098KS", + "RPA/B079KA", + "RPA/B134KA", + "RPA/B135KA", + "RPA/B136KA", + "RPA/B137KA", + "RPA/B138KA", + "RGL507KA", + "RGL508KA", + "RGL509KA", + "RGL503KA", + "RGL510KA", + "RGL511KA", + "RGL512KA", + "RGL504KA", + "RCP455KA1", + "RCP455KA2", + "RCP455KA3", + "RGL405KA", + "RGL407KA", + "RGL408KA", + "RGL406KA", + "RCP536KA", + "RCP674KA", + "RCP675KA", + "RCP680KA", + "RCP681KA", + "RCP676KA", + "RCP677KA", + "RCP682KA", + "RCP683KA", + "RCP678KA", + "RCP679KA", + "RCP684KA", + "RCP685KA", + "RCP811KA1", + "RCP811KA2", + "RCP813KA1", + "RCP813KA2", + "RCP815KA1", + "RCP815KA2", + "RGL001KA", + "RGL008KA", + "RGL033KA", + "RGL035KA", + "KCS001KA", + "KCS007KA", + "KCS002KA", + "KCS008KA", + "KCS003KA", + "KCS009KA", + "RCP062MT", + "RCP059MT" + ] + } + ], + "tableRefs": [ + "#/tables/16", + "#/tables/25", + "#/tables/13", + "#/tables/12" + ] + } + ] + }, + { + "id": "9", + "name": "结束机柜工作", + "stages": [ + { + "id": "id_1764071736026_o5o7mw9yr", + "name": "结束后检查组", + "actions": [ + { + "id": "133", + "document": "

试验结束后打印温度变化趋势,确认温度变化<0.2℃。或者采用滑动平均方式时每个数据测量时间大于滑动周期、每个环路滑动平均值的最大最小值不超过0.2℃,否则需重新执行RCP63;

", + "mode": "manual", + "dataFields": [ + { + "tableRef": "#/tables/manualConfirm", + "fields": ["confirm_133"] + } + ] + }, + { + "id": "134", + "document": "

按附表1检查试验后相关参数指示并记录

", + "mode": "manual", + "dataFields": [ + { + "tableRef": "#/tables/manualConfirm", + "fields": ["confirm_134"] + } + ] + }, + { + "id": "134_auto", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "RCP614KM", + "RCP618KM", + "RCP622KM", + "RCP612KM", + "RCP616KM", + "RCP620KM", + "RCP613KM", + "RCP617KM", + "RCP621KM", + "RCP611KM", + "RCP615KM", + "RCP619KM" + ] + }, + { + "id": "135", + "document": "

根据报警清单检查试验后相关报警状态,确认无异常报警。

", + "mode": "manual", + "dataFields": [ + { + "tableRef": "#/tables/manualConfirm", + "fields": ["confirm_135"] + } + ] + }, + { + "id": "135_auto", + "mode": "auto", + "functionType": "DATA_ACQUISITION", + "functionParameters": [ + "RCP455AA", + "RPA/B138AA", + "RPA/B167AA", + "RPA/B171AA", + "RPA/B079AA", + "RPA/B039LA", + "RPA/B168KA", + "RPA/B169KA", + "RPA/B170KA", + "RPA/B171KA", + "RPA/B164KA", + "RPA/B165KA", + "RPA/B166KA", + "RPA/B167KA", + "RPA/B036KA", + "RPA/B037KA", + "RPA/B038KA", + "RPA/B039KS", + "RPA/B096KS", + "RPA/B097KS", + "RPA/B098KS", + "RPA/B079KA", + "RPA/B134KA", + "RPA/B135KA", + "RPA/B136KA", + "RPA/B137KA", + "RPA/B138KA", + "RGL507KA", + "RGL508KA", + "RGL509KA", + "RGL503KA", + "RGL510KA", + "RGL511KA", + "RGL512KA", + "RGL504KA", + "RCP455KA1", + "RCP455KA2", + "RCP455KA3", + "RGL405KA", + "RGL407KA", + "RGL408KA", + "RGL406KA", + "RCP536KA", + "RCP674KA", + "RCP675KA", + "RCP680KA", + "RCP681KA", + "RCP676KA", + "RCP677KA", + "RCP682KA", + "RCP683KA", + "RCP678KA", + "RCP679KA", + "RCP684KA", + "RCP685KA", + "RCP811KA1", + "RCP811KA2", + "RCP813KA1", + "RCP813KA2", + "RCP815KA1", + "RCP815KA2", + "RGL001KA", + "RGL008KA", + "RGL033KA", + "RGL035KA", + "KCS001KA", + "KCS007KA", + "KCS002KA", + "KCS008KA", + "KCS003KA", + "KCS009KA" + ] + } + ], + "tableRefs": ["#/tables/12", "#/tables/13"] + } + ] + } + ] + }, + "tables": { + "manualConfirm": { + "id": "manualConfirm", + "name": "人工确认", + "tableType": "form", + "uploadStrategy": "immediate", + "fields": [ + { "id": "confirm_147", "name": "确认 147", "type": "boolean" }, + { "id": "confirm_150", "name": "确认 150", "type": "boolean" }, + { "id": "confirm_43", "name": "确认 43", "type": "boolean" }, + { "id": "confirm_75", "name": "确认 75", "type": "boolean" }, + { "id": "confirm_92", "name": "确认 92", "type": "boolean" }, + { "id": "confirm_153", "name": "确认 153", "type": "boolean" }, + { "id": "confirm_138", "name": "确认 138", "type": "boolean" }, + { "id": "confirm_142", "name": "确认 142", "type": "boolean" }, + { "id": "confirm_59", "name": "确认 59", "type": "boolean" }, + { "id": "confirm_68", "name": "确认 68", "type": "boolean" }, + { "id": "confirm_93", "name": "确认 93", "type": "boolean" }, + { "id": "confirm_94", "name": "确认 94", "type": "boolean" }, + { "id": "confirm_104", "name": "确认 104", "type": "boolean" }, + { "id": "confirm_108", "name": "确认 108", "type": "boolean" }, + { "id": "confirm_121", "name": "确认 121", "type": "boolean" }, + { "id": "confirm_133", "name": "确认 133", "type": "boolean" }, + { "id": "confirm_134", "name": "确认 134", "type": "boolean" }, + { "id": "confirm_135", "name": "确认 135", "type": "boolean" } + ] + }, + "1": { + "id": "1", + "name": "环路平均温度变化数据记录表", + "tableType": "grid", + "uploadStrategy": "onComplete", + "columnHeaders": [ + { + "id": "r0", + "name": "环路编号" + }, + { + "id": "r1", + "name": "平均温度变化量(℃)" + }, + { + "id": "r2", + "name": "结束时间" + }, + { + "id": "r3", + "name": "平均温度变化量(℃)" + }, + { + "id": "r4", + "name": "是否≤0.2℃" + } + ], + "rowHeaders": [ + { + "id": "RCP624KM", + "name": "RCP624KM" + }, + { + "id": "RCP624KM", + "name": "RCP624KM" + }, + { + "id": "RCP628KM", + "name": "RCP628KM" + } + ] + }, + "11": { + "id": "11", + "name": "环路滑动平均温度变化数据记录表", + "tableType": "grid", + "uploadStrategy": "onComplete", + "columnHeaders": [ + { + "id": "r0", + "name": "滑动周期s(建议60秒)" + }, + { + "id": "r1", + "name": "" + }, + { + "id": "r2", + "name": "" + } + ], + "rowHeaders": [ + { + "id": "每个数据记录最短时间s(应大于滑动周期)", + "name": "每个数据记录最短时间s(应大于滑动周期)" + }, + { + "id": "环路编号", + "name": "环路编号" + }, + { + "id": "RCP624KM", + "name": "RCP624KM" + }, + { + "id": "RCP626KM", + "name": "RCP626KM" + }, + { + "id": "RCP628KM", + "name": "RCP628KM" + } + ], + "staticCells": [ + { + "row": "环路编号", + "column": "r1", + "content": "平均温度滑动平均最大最小值之差(℃)" + }, + { + "row": "环路编号", + "column": "r2", + "content": "是否≤0.2℃" + } + ] + }, + "12": { + "id": "12", + "name": "实验前后模拟量数据记录表", + "tableType": "grid", + "uploadStrategy": "onComplete", + "columnHeaders": [ + { + "id": "r0", + "name": "信号编码" + }, + { + "id": "r1", + "name": "描述" + }, + { + "id": "r2", + "name": "允许误差" + }, + { + "id": "r3", + "name": "试验前KIC指示" + }, + { + "id": "r4", + "name": "试验前满意否" + }, + { + "id": "r5", + "name": "试验后KIC指示" + }, + { + "id": "r6", + "name": "试验后满意否" + } + ], + "rowHeaders": [ + { + "id": "RCP614KM", + "name": "RCP614KM" + }, + { + "id": "RCP618KM", + "name": "RCP618KM" + }, + { + "id": "RCP622KM", + "name": "RCP622KM" + }, + { + "id": "RCP612KM", + "name": "RCP612KM" + }, + { + "id": "RCP616KM", + "name": "RCP616KM" + }, + { + "id": "RCP620KM", + "name": "RCP620KM" + }, + { + "id": "RCP613KM", + "name": "RCP613KM" + }, + { + "id": "RCP617KM", + "name": "RCP617KM" + }, + { + "id": "RCP621KM", + "name": "RCP621KM" + }, + { + "id": "RCP611KM", + "name": "RCP611KM" + }, + { + "id": "RCP615KM", + "name": "RCP615KM" + }, + { + "id": "RCP619KM", + "name": "RCP619KM" + } + ], + "staticCells": [ + { + "row": "RCP614KM", + "column": "r1", + "content": "1环路实测ΔT温度" + }, + { + "row": "RCP614KM", + "column": "r2", + "content": "三者之间的差值≤4.23%FP" + }, + { + "row": "RCP618KM", + "column": "r1", + "content": "2环路实测ΔT温度" + }, + { + "row": "RCP622KM", + "column": "r1", + "content": "3环路实测ΔT温度" + }, + { + "row": "RCP612KM", + "column": "r1", + "content": "1环路超温ΔT温度" + }, + { + "row": "RCP612KM", + "column": "r2", + "content": "三者之间的差值≤8.05%FP" + }, + { + "row": "RCP616KM", + "column": "r1", + "content": "2环路超温ΔT温度" + }, + { + "row": "RCP620KM", + "column": "r1", + "content": "3环路超温ΔT温度" + }, + { + "row": "RCP613KM", + "column": "r1", + "content": "1环路超功ΔT温度" + }, + { + "row": "RCP613KM", + "column": "r2", + "content": "三者之间的差值≤10.11%FP" + }, + { + "row": "RCP617KM", + "column": "r1", + "content": "2环路超功ΔT温度" + }, + { + "row": "RCP621KM", + "column": "r1", + "content": "3环路超功ΔT温度" + }, + { + "row": "RCP611KM", + "column": "r1", + "content": "1环路平均温度" + }, + { + "row": "RCP611KM", + "column": "r2", + "content": "三者之间的差值≤1.40℃或与RCP475ID的差值≤1.12℃" + }, + { + "row": "RCP615KM", + "column": "r1", + "content": "2环路平均温度" + }, + { + "row": "RCP619KM", + "column": "r1", + "content": "3环路平均温度" + } + ] + }, + "13": { + "id": "13", + "name": "试验前后报警检查记录表", + "tableType": "grid", + "uploadStrategy": "onComplete", + "isShared": true, + "columnHeaders": [ + { + "id": "r0", + "name": "信号" + }, + { + "id": "r1", + "name": "描述" + }, + { + "id": "r2", + "name": "位置" + }, + { + "id": "r3", + "name": "正常状态" + }, + { + "id": "r4", + "name": "出现时间" + }, + { + "id": "r5", + "name": "试验前A列" + }, + { + "id": "r6", + "name": "试验前B列" + }, + { + "id": "r7", + "name": "试验后A列" + }, + { + "id": "r8", + "name": "试验后B列" + } + ], + "rowHeaders": [ + { + "id": "RCP455AA", + "name": "RCP455AA" + }, + { + "id": "RPA/B138AA", + "name": "RPA/B138AA" + }, + { + "id": "RPA/B167AA", + "name": "RPA/B167AA" + }, + { + "id": "RPA/B171AA", + "name": "RPA/B171AA" + }, + { + "id": "RPA/B079AA", + "name": "RPA/B079AA" + }, + { + "id": "RPA/B039LA", + "name": "RPA/B039LA" + }, + { + "id": "RPA/B168KA", + "name": "RPA/B168KA" + }, + { + "id": "RPA/B169KA", + "name": "RPA/B169KA" + }, + { + "id": "RPA/B170KA", + "name": "RPA/B170KA" + }, + { + "id": "RPA/B171KA", + "name": "RPA/B171KA" + }, + { + "id": "RPA/B164KA", + "name": "RPA/B164KA" + }, + { + "id": "RPA/B165KA", + "name": "RPA/B165KA" + }, + { + "id": "RPA/B166KA", + "name": "RPA/B166KA" + }, + { + "id": "RPA/B167KA", + "name": "RPA/B167KA" + }, + { + "id": "RPA/B036KA", + "name": "RPA/B036KA" + }, + { + "id": "RPA/B037KA", + "name": "RPA/B037KA" + }, + { + "id": "RPA/B038KA", + "name": "RPA/B038KA" + }, + { + "id": "RPA/B039KS", + "name": "RPA/B039KS" + }, + { + "id": "RPA/B096KS", + "name": "RPA/B096KS" + }, + { + "id": "RPA/B097KS", + "name": "RPA/B097KS" + }, + { + "id": "RPA/B098KS", + "name": "RPA/B098KS" + }, + { + "id": "RPA/B079KA", + "name": "RPA/B079KA" + }, + { + "id": "RPA/B134KA", + "name": "RPA/B134KA" + }, + { + "id": "RPA/B135KA", + "name": "RPA/B135KA" + }, + { + "id": "RPA/B136KA", + "name": "RPA/B136KA" + }, + { + "id": "RPA/B137KA", + "name": "RPA/B137KA" + }, + { + "id": "RPA/B138KA", + "name": "RPA/B138KA" + }, + { + "id": "RGL507KA", + "name": "RGL507KA" + }, + { + "id": "RGL508KA", + "name": "RGL508KA" + }, + { + "id": "RGL509KA", + "name": "RGL509KA" + }, + { + "id": "RGL503KA", + "name": "RGL503KA" + }, + { + "id": "RGL510KA", + "name": "RGL510KA" + }, + { + "id": "RGL511KA", + "name": "RGL511KA" + }, + { + "id": "RGL512KA", + "name": "RGL512KA" + }, + { + "id": "RGL504KA", + "name": "RGL504KA" + }, + { + "id": "RCP455KA1", + "name": "RCP455KA1" + }, + { + "id": "RCP455KA2", + "name": "RCP455KA2" + }, + { + "id": "RCP455KA3", + "name": "RCP455KA3" + }, + { + "id": "RGL405KA", + "name": "RGL405KA" + }, + { + "id": "RGL407KA", + "name": "RGL407KA" + }, + { + "id": "RGL408KA", + "name": "RGL408KA" + }, + { + "id": "RGL406KA", + "name": "RGL406KA" + }, + { + "id": "RCP536KA", + "name": "RCP536KA" + }, + { + "id": "RCP674KA", + "name": "RCP674KA" + }, + { + "id": "RCP675KA", + "name": "RCP675KA" + }, + { + "id": "RCP680KA", + "name": "RCP680KA" + }, + { + "id": "RCP681KA", + "name": "RCP681KA" + }, + { + "id": "RCP676KA", + "name": "RCP676KA" + }, + { + "id": "RCP677KA", + "name": "RCP677KA" + }, + { + "id": "RCP682KA", + "name": "RCP682KA" + }, + { + "id": "RCP683KA", + "name": "RCP683KA" + }, + { + "id": "RCP678KA", + "name": "RCP678KA" + }, + { + "id": "RCP679KA", + "name": "RCP679KA" + }, + { + "id": "RCP684KA", + "name": "RCP684KA" + }, + { + "id": "RCP685KA", + "name": "RCP685KA" + }, + { + "id": "RCP811KA1", + "name": "RCP811KA1" + }, + { + "id": "RCP811KA2", + "name": "RCP811KA2" + }, + { + "id": "RCP813KA1", + "name": "RCP813KA1" + }, + { + "id": "RCP813KA2", + "name": "RCP813KA2" + }, + { + "id": "RCP815KA1", + "name": "RCP815KA1" + }, + { + "id": "RCP815KA2", + "name": "RCP815KA2" + }, + { + "id": "RGL001KA", + "name": "RGL001KA" + }, + { + "id": "RGL008KA", + "name": "RGL008KA" + }, + { + "id": "RGL033KA", + "name": "RGL033KA" + }, + { + "id": "RGL035KA", + "name": "RGL035KA" + }, + { + "id": "KCS001KA", + "name": "KCS001KA" + }, + { + "id": "KCS007KA", + "name": "KCS007KA" + }, + { + "id": "KCS002KA", + "name": "KCS002KA" + }, + { + "id": "KCS008KA", + "name": "KCS008KA" + }, + { + "id": "KCS003KA", + "name": "KCS003KA" + }, + { + "id": "KCS009KA", + "name": "KCS009KA" + } + ], + "staticCells": [ + { + "row": "RCP455AA", + "column": "r1", + "content": "1,2或3环路Tavg高" + }, + { + "row": "RCP455AA", + "column": "r2", + "content": "BUP" + }, + { + "row": "RCP455AA", + "column": "r3", + "content": "0" + }, + { + "row": "RCP455AA", + "column": "r4", + "content": "*" + }, + { + "row": "RPA/B138AA", + "column": "r1", + "content": "高中子通量率跳堆" + }, + { + "row": "RPA/B138AA", + "column": "r2", + "content": "BUP" + }, + { + "row": "RPA/B138AA", + "column": "r3", + "content": "0" + }, + { + "row": "RPA/B138AA", + "column": "r4", + "content": "Never" + }, + { + "row": "RPA/B167AA", + "column": "r1", + "content": "ΔT超功跳堆" + }, + { + "row": "RPA/B167AA", + "column": "r2", + "content": "BUP" + }, + { + "row": "RPA/B167AA", + "column": "r3", + "content": "0" + }, + { + "row": "RPA/B167AA", + "column": "r4", + "content": "Never" + }, + { + "row": "RPA/B171AA", + "column": "r1", + "content": "ΔT超温跳堆" + }, + { + "row": "RPA/B171AA", + "column": "r2", + "content": "BUP" + }, + { + "row": "RPA/B171AA", + "column": "r3", + "content": "0" + }, + { + "row": "RPA/B171AA", + "column": "r4", + "content": "Never" + }, + { + "row": "RPA/B079AA", + "column": "r1", + "content": "ARE隔离" + }, + { + "row": "RPA/B079AA", + "column": "r2", + "content": "BUP" + }, + { + "row": "RPA/B079AA", + "column": "r3", + "content": "0" + }, + { + "row": "RPA/B079AA", + "column": "r4", + "content": "Never" + }, + { + "row": "RPA/B039LA", + "column": "r1", + "content": "P12信号" + }, + { + "row": "RPA/B039LA", + "column": "r2", + "content": "BUP" + }, + { + "row": "RPA/B039LA", + "column": "r3", + "content": "0" + }, + { + "row": "RPA/B039LA", + "column": "r4", + "content": "Never" + }, + { + "row": "RPA/B168KA", + "column": "r1", + "content": "1环路超温ΔT" + }, + { + "row": "RPA/B168KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RPA/B168KA", + "column": "r3", + "content": "0" + }, + { + "row": "RPA/B168KA", + "column": "r4", + "content": "Loop1(P)" + }, + { + "row": "RPA/B169KA", + "column": "r1", + "content": "2环路超温ΔT" + }, + { + "row": "RPA/B169KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RPA/B169KA", + "column": "r3", + "content": "0" + }, + { + "row": "RPA/B169KA", + "column": "r4", + "content": "Loop2(P)" + }, + { + "row": "RPA/B170KA", + "column": "r1", + "content": "3环路超温ΔT" + }, + { + "row": "RPA/B170KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RPA/B170KA", + "column": "r3", + "content": "0" + }, + { + "row": "RPA/B170KA", + "column": "r4", + "content": "Loop3(P)" + }, + { + "row": "RPA/B171KA", + "column": "r1", + "content": "ΔT超温跳堆" + }, + { + "row": "RPA/B171KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RPA/B171KA", + "column": "r3", + "content": "0" + }, + { + "row": "RPA/B171KA", + "column": "r4", + "content": "Never" + }, + { + "row": "RPA/B164KA", + "column": "r1", + "content": "1环路超功ΔT" + }, + { + "row": "RPA/B164KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RPA/B164KA", + "column": "r3", + "content": "0" + }, + { + "row": "RPA/B164KA", + "column": "r4", + "content": "Loop1(P)" + }, + { + "row": "RPA/B165KA", + "column": "r1", + "content": "2环路超功ΔT" + }, + { + "row": "RPA/B165KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RPA/B165KA", + "column": "r3", + "content": "0" + }, + { + "row": "RPA/B165KA", + "column": "r4", + "content": "Loop2(P)" + }, + { + "row": "RPA/B166KA", + "column": "r1", + "content": "3环路超功ΔT" + }, + { + "row": "RPA/B166KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RPA/B166KA", + "column": "r3", + "content": "0" + }, + { + "row": "RPA/B166KA", + "column": "r4", + "content": "Loop3(P)" + }, + { + "row": "RPA/B167KA", + "column": "r1", + "content": "ΔT超功跳堆" + }, + { + "row": "RPA/B167KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RPA/B167KA", + "column": "r3", + "content": "0" + }, + { + "row": "RPA/B167KA", + "column": "r4", + "content": "Never" + }, + { + "row": "RPA/B036KA", + "column": "r1", + "content": "1环路Lo-Lo Tavg" + }, + { + "row": "RPA/B036KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RPA/B036KA", + "column": "r3", + "content": "0" + }, + { + "row": "RPA/B036KA", + "column": "r4", + "content": "Loop1(P)" + }, + { + "row": "RPA/B037KA", + "column": "r1", + "content": "2环路Lo-Lo Tavg" + }, + { + "row": "RPA/B037KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RPA/B037KA", + "column": "r3", + "content": "0" + }, + { + "row": "RPA/B037KA", + "column": "r4", + "content": "Loop2(P)" + }, + { + "row": "RPA/B038KA", + "column": "r1", + "content": "3环路Lo-Lo Tavg" + }, + { + "row": "RPA/B038KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RPA/B038KA", + "column": "r3", + "content": "0" + }, + { + "row": "RPA/B038KA", + "column": "r4", + "content": "Loop3(P)" + }, + { + "row": "RPA/B039KS", + "column": "r1", + "content": "P12信号" + }, + { + "row": "RPA/B039KS", + "column": "r2", + "content": "KIC" + }, + { + "row": "RPA/B039KS", + "column": "r3", + "content": "0" + }, + { + "row": "RPA/B039KS", + "column": "r4", + "content": "Never" + }, + { + "row": "RPA/B096KS", + "column": "r1", + "content": "1环路Low Tavg" + }, + { + "row": "RPA/B096KS", + "column": "r2", + "content": "KIC" + }, + { + "row": "RPA/B096KS", + "column": "r3", + "content": "0" + }, + { + "row": "RPA/B096KS", + "column": "r4", + "content": "*" + }, + { + "row": "RPA/B097KS", + "column": "r1", + "content": "2环路Low Tavg" + }, + { + "row": "RPA/B097KS", + "column": "r2", + "content": "KIC" + }, + { + "row": "RPA/B097KS", + "column": "r3", + "content": "0" + }, + { + "row": "RPA/B097KS", + "column": "r4", + "content": "*" + }, + { + "row": "RPA/B098KS", + "column": "r1", + "content": "3环路Low Tavg" + }, + { + "row": "RPA/B098KS", + "column": "r2", + "content": "KIC" + }, + { + "row": "RPA/B098KS", + "column": "r3", + "content": "0" + }, + { + "row": "RPA/B098KS", + "column": "r4", + "content": "*" + }, + { + "row": "RPA/B079KA", + "column": "r1", + "content": "ARE隔离" + }, + { + "row": "RPA/B079KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RPA/B079KA", + "column": "r3", + "content": "0" + }, + { + "row": "RPA/B079KA", + "column": "r4", + "content": "Never" + }, + { + "row": "RPA/B134KA", + "column": "r1", + "content": "中子通量变化率高ⅠP" + }, + { + "row": "RPA/B134KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RPA/B134KA", + "column": "r3", + "content": "0" + }, + { + "row": "RPA/B134KA", + "column": "r4", + "content": "Never" + }, + { + "row": "RPA/B135KA", + "column": "r1", + "content": "中子通量变化率高ⅡP" + }, + { + "row": "RPA/B135KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RPA/B135KA", + "column": "r3", + "content": "0" + }, + { + "row": "RPA/B135KA", + "column": "r4", + "content": "Never" + }, + { + "row": "RPA/B136KA", + "column": "r1", + "content": "中子通量变化率高ⅢP" + }, + { + "row": "RPA/B136KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RPA/B136KA", + "column": "r3", + "content": "0" + }, + { + "row": "RPA/B136KA", + "column": "r4", + "content": "Never" + }, + { + "row": "RPA/B137KA", + "column": "r1", + "content": "中子通量变化率高ⅣP" + }, + { + "row": "RPA/B137KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RPA/B137KA", + "column": "r3", + "content": "0" + }, + { + "row": "RPA/B137KA", + "column": "r4", + "content": "Never" + }, + { + "row": "RPA/B138KA", + "column": "r1", + "content": "高中子通量率跳堆" + }, + { + "row": "RPA/B138KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RPA/B138KA", + "column": "r3", + "content": "0" + }, + { + "row": "RPA/B138KA", + "column": "r4", + "content": "Never" + }, + { + "row": "RGL507KA", + "column": "r1", + "content": "1环路超温ΔT" + }, + { + "row": "RGL507KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RGL507KA", + "column": "r3", + "content": "0" + }, + { + "row": "RGL507KA", + "column": "r4", + "content": "Loop1(P)" + }, + { + "row": "RGL508KA", + "column": "r1", + "content": "2环路超温ΔT" + }, + { + "row": "RGL508KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RGL508KA", + "column": "r3", + "content": "0" + }, + { + "row": "RGL508KA", + "column": "r4", + "content": "Loop2(P)" + }, + { + "row": "RGL509KA", + "column": "r1", + "content": "3环路超温ΔT" + }, + { + "row": "RGL509KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RGL509KA", + "column": "r3", + "content": "0" + }, + { + "row": "RGL509KA", + "column": "r4", + "content": "Loop3(P)" + }, + { + "row": "RGL503KA", + "column": "r1", + "content": "超温ΔT报警(C3)" + }, + { + "row": "RGL503KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RGL503KA", + "column": "r3", + "content": "0" + }, + { + "row": "RGL503KA", + "column": "r4", + "content": "Never" + }, + { + "row": "RGL510KA", + "column": "r1", + "content": "1环路超功ΔT" + }, + { + "row": "RGL510KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RGL510KA", + "column": "r3", + "content": "0" + }, + { + "row": "RGL510KA", + "column": "r4", + "content": "Loop1(P)" + }, + { + "row": "RGL511KA", + "column": "r1", + "content": "3环路超功ΔT" + }, + { + "row": "RGL511KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RGL511KA", + "column": "r3", + "content": "0" + }, + { + "row": "RGL511KA", + "column": "r4", + "content": "Loop2(P)" + }, + { + "row": "RGL512KA", + "column": "r1", + "content": "3环路超功ΔT" + }, + { + "row": "RGL512KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RGL512KA", + "column": "r3", + "content": "0" + }, + { + "row": "RGL512KA", + "column": "r4", + "content": "Loop3(P)" + }, + { + "row": "RGL504KA", + "column": "r1", + "content": "超温ΔT报警(C4)" + }, + { + "row": "RGL504KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RGL504KA", + "column": "r3", + "content": "0" + }, + { + "row": "RGL504KA", + "column": "r4", + "content": "Never" + }, + { + "row": "RCP455KA1", + "column": "r1", + "content": "1环路Tavg高" + }, + { + "row": "RCP455KA1", + "column": "r2", + "content": "KIC" + }, + { + "row": "RCP455KA1", + "column": "r3", + "content": "0" + }, + { + "row": "RCP455KA1", + "column": "r4", + "content": "*" + }, + { + "row": "RCP455KA2", + "column": "r1", + "content": "2环路Tavg高" + }, + { + "row": "RCP455KA2", + "column": "r2", + "content": "KIC" + }, + { + "row": "RCP455KA2", + "column": "r3", + "content": "0" + }, + { + "row": "RCP455KA2", + "column": "r4", + "content": "*" + }, + { + "row": "RCP455KA3", + "column": "r1", + "content": "3环路Tavg高" + }, + { + "row": "RCP455KA3", + "column": "r2", + "content": "KIC" + }, + { + "row": "RCP455KA3", + "column": "r3", + "content": "0" + }, + { + "row": "RCP455KA3", + "column": "r4", + "content": "*" + }, + { + "row": "RGL405KA", + "column": "r1", + "content": "Temperature Low" + }, + { + "row": "RGL405KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RGL405KA", + "column": "r3", + "content": "0" + }, + { + "row": "RGL405KA", + "column": "r4", + "content": "3 Loops" + }, + { + "row": "RGL407KA", + "column": "r1", + "content": "Temp.avg max Tref(high)" + }, + { + "row": "RGL407KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RGL407KA", + "column": "r3", + "content": "0" + }, + { + "row": "RGL407KA", + "column": "r4", + "content": "Control(In CCS)" + }, + { + "row": "RGL408KA", + "column": "r1", + "content": "Temp.avg max high" + }, + { + "row": "RGL408KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RGL408KA", + "column": "r3", + "content": "0" + }, + { + "row": "RGL408KA", + "column": "r4", + "content": "Control(In CCS)" + }, + { + "row": "RGL406KA", + "column": "r1", + "content": "Temp.avg max Tref(low)" + }, + { + "row": "RGL406KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RGL406KA", + "column": "r3", + "content": "0" + }, + { + "row": "RGL406KA", + "column": "r4", + "content": "*" + }, + { + "row": "RCP536KA", + "column": "r1", + "content": "RCP/RRA VLV.NOT OPEN AT LOW TAVG" + }, + { + "row": "RCP536KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RCP536KA", + "column": "r3", + "content": "*" + }, + { + "row": "RCP536KA", + "column": "r4", + "content": "ⅡP" + }, + { + "row": "RCP674KA", + "column": "r1", + "content": "Tavg 1 Max Low" + }, + { + "row": "RCP674KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RCP674KA", + "column": "r3", + "content": "0" + }, + { + "row": "RCP674KA", + "column": "r4", + "content": "Loop1(C)" + }, + { + "row": "RCP675KA", + "column": "r1", + "content": "Tavg 1 Max High" + }, + { + "row": "RCP675KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RCP675KA", + "column": "r3", + "content": "0" + }, + { + "row": "RCP675KA", + "column": "r4", + "content": "Loop1(C)" + }, + { + "row": "RCP680KA", + "column": "r1", + "content": "DeltaT 1 Max Low" + }, + { + "row": "RCP680KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RCP680KA", + "column": "r3", + "content": "0" + }, + { + "row": "RCP680KA", + "column": "r4", + "content": "Loop1(C)" + }, + { + "row": "RCP681KA", + "column": "r1", + "content": "DeltaT 1 Max High" + }, + { + "row": "RCP681KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RCP681KA", + "column": "r3", + "content": "0" + }, + { + "row": "RCP681KA", + "column": "r4", + "content": "Loop1(C)" + }, + { + "row": "RCP676KA", + "column": "r1", + "content": "Tavg 2 Max Low" + }, + { + "row": "RCP676KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RCP676KA", + "column": "r3", + "content": "0" + }, + { + "row": "RCP676KA", + "column": "r4", + "content": "Loop2(C)" + }, + { + "row": "RCP677KA", + "column": "r1", + "content": "Tavg 2 Max High" + }, + { + "row": "RCP677KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RCP677KA", + "column": "r3", + "content": "0" + }, + { + "row": "RCP677KA", + "column": "r4", + "content": "Loop2(C)" + }, + { + "row": "RCP682KA", + "column": "r1", + "content": "DeltaT 2 Max Low" + }, + { + "row": "RCP682KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RCP682KA", + "column": "r3", + "content": "0" + }, + { + "row": "RCP682KA", + "column": "r4", + "content": "Loop2(C)" + }, + { + "row": "RCP683KA", + "column": "r1", + "content": "DeltaT 2 Max High" + }, + { + "row": "RCP683KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RCP683KA", + "column": "r3", + "content": "0" + }, + { + "row": "RCP683KA", + "column": "r4", + "content": "Loop2(C)" + }, + { + "row": "RCP678KA", + "column": "r1", + "content": "Tavg 3 Max Low" + }, + { + "row": "RCP678KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RCP678KA", + "column": "r3", + "content": "0" + }, + { + "row": "RCP678KA", + "column": "r4", + "content": "Loop3(C)" + }, + { + "row": "RCP679KA", + "column": "r1", + "content": "Tavg 3 Max High" + }, + { + "row": "RCP679KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RCP679KA", + "column": "r3", + "content": "0" + }, + { + "row": "RCP679KA", + "column": "r4", + "content": "Loop3(C)" + }, + { + "row": "RCP684KA", + "column": "r1", + "content": "DeltaT 3 Max Low" + }, + { + "row": "RCP684KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RCP684KA", + "column": "r3", + "content": "0" + }, + { + "row": "RCP684KA", + "column": "r4", + "content": "Loop3(C)" + }, + { + "row": "RCP685KA", + "column": "r1", + "content": "DeltaT 3 Max High" + }, + { + "row": "RCP685KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RCP685KA", + "column": "r3", + "content": "0" + }, + { + "row": "RCP685KA", + "column": "r4", + "content": "Loop3(C)" + }, + { + "row": "RCP811KA1", + "column": "r1", + "content": "Real Th Power Loop1>985MW" + }, + { + "row": "RCP811KA1", + "column": "r2", + "content": "KIC" + }, + { + "row": "RCP811KA1", + "column": "r3", + "content": "0" + }, + { + "row": "RCP811KA1", + "column": "r4", + "content": "Loop1(C)" + }, + { + "row": "RCP811KA2", + "column": "r1", + "content": "Real Th Power Loop1>987.7MW" + }, + { + "row": "RCP811KA2", + "column": "r2", + "content": "KIC" + }, + { + "row": "RCP811KA2", + "column": "r3", + "content": "0" + }, + { + "row": "RCP811KA2", + "column": "r4", + "content": "Loop1(C)" + }, + { + "row": "RCP813KA1", + "column": "r1", + "content": "Real Th Power Loop2>985MW" + }, + { + "row": "RCP813KA1", + "column": "r2", + "content": "KIC" + }, + { + "row": "RCP813KA1", + "column": "r3", + "content": "0" + }, + { + "row": "RCP813KA1", + "column": "r4", + "content": "Loop2(C)" + }, + { + "row": "RCP813KA2", + "column": "r1", + "content": "Real Th Power Loop2>987.7MW" + }, + { + "row": "RCP813KA2", + "column": "r2", + "content": "KIC" + }, + { + "row": "RCP813KA2", + "column": "r3", + "content": "0" + }, + { + "row": "RCP813KA2", + "column": "r4", + "content": "Loop2(C)" + }, + { + "row": "RCP815KA1", + "column": "r1", + "content": "Real Th Power Loop3>985MW" + }, + { + "row": "RCP815KA1", + "column": "r2", + "content": "KIC" + }, + { + "row": "RCP815KA1", + "column": "r3", + "content": "0" + }, + { + "row": "RCP815KA1", + "column": "r4", + "content": "Loop3(C)" + }, + { + "row": "RCP815KA2", + "column": "r1", + "content": "Real Th Power Loop3>987.7MW" + }, + { + "row": "RCP815KA2", + "column": "r2", + "content": "KIC" + }, + { + "row": "RCP815KA2", + "column": "r3", + "content": "0" + }, + { + "row": "RCP815KA2", + "column": "r4", + "content": "Loop3(C)" + }, + { + "row": "RGL001KA", + "column": "r1", + "content": "RGL OPERATING FAULT" + }, + { + "row": "RGL001KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RGL001KA", + "column": "r3", + "content": "*" + }, + { + "row": "RGL001KA", + "column": "r4", + "content": "Loop1(C)" + }, + { + "row": "RGL008KA", + "column": "r1", + "content": "RGL ALARM ROOM L609/649" + }, + { + "row": "RGL008KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RGL008KA", + "column": "r3", + "content": "*" + }, + { + "row": "RGL008KA", + "column": "r4", + "content": "Loop1(C)" + }, + { + "row": "RGL033KA", + "column": "r1", + "content": "RGL MEASURING RANGE VIOLATION" + }, + { + "row": "RGL033KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RGL033KA", + "column": "r3", + "content": "*" + }, + { + "row": "RGL033KA", + "column": "r4", + "content": "Loop1(C)" + }, + { + "row": "RGL035KA", + "column": "r1", + "content": "DOOR OPEN L609/649" + }, + { + "row": "RGL035KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "RGL035KA", + "column": "r3", + "content": "*" + }, + { + "row": "RGL035KA", + "column": "r4", + "content": "Loop1(C)" + }, + { + "row": "KCS001KA", + "column": "r1", + "content": "ⅠP KCS CABINET FAULT ALARM" + }, + { + "row": "KCS001KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "KCS001KA", + "column": "r3", + "content": "0" + }, + { + "row": "KCS001KA", + "column": "r4", + "content": "ⅠP" + }, + { + "row": "KCS007KA", + "column": "r1", + "content": "ⅠP KCS CABINET DOOR OPEN ALARM" + }, + { + "row": "KCS007KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "KCS007KA", + "column": "r3", + "content": "0" + }, + { + "row": "KCS007KA", + "column": "r4", + "content": "ⅠP" + }, + { + "row": "KCS002KA", + "column": "r1", + "content": "ⅡP KCS CABINET FAULT ALARM" + }, + { + "row": "KCS002KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "KCS002KA", + "column": "r3", + "content": "*" + }, + { + "row": "KCS002KA", + "column": "r4", + "content": "ⅡP" + }, + { + "row": "KCS008KA", + "column": "r1", + "content": "ⅡP KCS CABINET DOOR OPEN ALARM" + }, + { + "row": "KCS008KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "KCS008KA", + "column": "r3", + "content": "0" + }, + { + "row": "KCS008KA", + "column": "r4", + "content": "ⅡP" + }, + { + "row": "KCS003KA", + "column": "r1", + "content": "ⅢP KCS CABINET FAULT ALARM" + }, + { + "row": "KCS003KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "KCS003KA", + "column": "r3", + "content": "0" + }, + { + "row": "KCS003KA", + "column": "r4", + "content": "ⅢP" + }, + { + "row": "KCS009KA", + "column": "r1", + "content": "ⅢP KCS CABINET DOOR OPEN ALARM" + }, + { + "row": "KCS009KA", + "column": "r2", + "content": "KIC" + }, + { + "row": "KCS009KA", + "column": "r3", + "content": "0" + }, + { + "row": "KCS009KA", + "column": "r4", + "content": "ⅢP" + } + ] + }, + "14": { + "id": "14", + "name": "拆装前后控制组环路1通道鉴定表", + "tableType": "grid", + "description": "机柜:RGL301AR\n标准:断开前与连接后的差值 <0.15 ℃", + "uploadStrategy": "immediate", + "isShared": true, + "columnHeaders": [ + { + "id": "r0", + "name": "物理参数" + }, + { + "id": "r1", + "name": "传感器" + }, + { + "id": "r2", + "name": "传感器标识" + }, + { + "id": "r3", + "name": "测量点" + }, + { + "id": "r4", + "name": "测量点标识" + }, + { + "id": "r5", + "name": "断开前" + }, + { + "id": "r6", + "name": "连接后" + }, + { + "id": "r7", + "name": "物理参数" + }, + { + "id": "r8", + "name": "传感器" + }, + { + "id": "r9", + "name": "传感器标识" + }, + { + "id": "r10", + "name": "测量点" + }, + { + "id": "r11", + "name": "测量点标识" + }, + { + "id": "r12", + "name": "断开前" + }, + { + "id": "r13", + "name": "连接后" + } + ], + "rowHeaders": [ + { + "id": "NR CBP 1", + "name": "NR CBP 1" + }, + { + "id": "NR HBP 1", + "name": "NR HBP 1" + } + ], + "staticCells": [ + { + "row": "NR CBP 1", + "column": "r1", + "content": "RCP 035 MT" + }, + { + "row": "NR CBP 1", + "column": "r2", + "content": "C" + }, + { + "row": "NR CBP 1", + "column": "r3", + "content": "KIC/SU 读数" + }, + { + "row": "NR CBP 1", + "column": "r4", + "content": "S" + }, + { + "row": "NR HBP 1", + "column": "r1", + "content": "RCP 032 MT" + }, + { + "row": "NR HBP 1", + "column": "r2", + "content": "C" + }, + { + "row": "NR HBP 1", + "column": "r3", + "content": "KIC/SU 读数" + }, + { + "row": "NR HBP 1", + "column": "r4", + "content": "S" + } + ] + }, + "15": { + "id": "15", + "name": "拆装前后控制组环路2通道鉴定表", + "tableType": "grid", + "description": "机柜:RGL301AR\n标准:断开前与连接后的差值 <0.15 ℃", + "uploadStrategy": "immediate", + "isShared": true, + "columnHeaders": [ + { + "id": "r0", + "name": "物理参数" + }, + { + "id": "r1", + "name": "传感器" + }, + { + "id": "r2", + "name": "传感器标识" + }, + { + "id": "r3", + "name": "测量点" + }, + { + "id": "r4", + "name": "测量点标识" + }, + { + "id": "r5", + "name": "断开前" + }, + { + "id": "r6", + "name": "连接后" + } + ], + "rowHeaders": [ + { + "id": "NR CBP 2", + "name": "NR CBP 2" + }, + { + "id": "NR HBP 2", + "name": "NR HBP 2" + } + ], + "staticCells": [ + { + "row": "NR CBP 2", + "column": "r1", + "content": "RCP 050 MT" + }, + { + "row": "NR CBP 2", + "column": "r2", + "content": "C" + }, + { + "row": "NR CBP 2", + "column": "r3", + "content": "KIC/SU 读数" + }, + { + "row": "NR CBP 2", + "column": "r4", + "content": "S" + }, + { + "row": "NR HBP 2", + "column": "r1", + "content": "RCP 047 MT" + }, + { + "row": "NR HBP 2", + "column": "r2", + "content": "C" + }, + { + "row": "NR HBP 2", + "column": "r3", + "content": "KIC/SU 读数" + }, + { + "row": "NR HBP 2", + "column": "r4", + "content": "S" + } + ] + }, + "16": { + "id": "16", + "name": "拆装前后控制组环路3通道鉴定表", + "tableType": "grid", + "description": "机柜:RGL301AR\n标准:断开前与连接后的差值 <0.15 ℃", + "uploadStrategy": "immediate", + "isShared": true, + "columnHeaders": [ + { + "id": "r0", + "name": "物理参数" + }, + { + "id": "r1", + "name": "传感器" + }, + { + "id": "r2", + "name": "传感器标识" + }, + { + "id": "r3", + "name": "测量点" + }, + { + "id": "r4", + "name": "测量点标识" + }, + { + "id": "r5", + "name": "断开前" + }, + { + "id": "r6", + "name": "连接后" + } + ], + "rowHeaders": [ + { + "id": "NR CBP 3", + "name": "NR CBP 3" + }, + { + "id": "NR HBP 3", + "name": "NR HBP 3" + } + ], + "staticCells": [ + { + "row": "NR CBP 3", + "column": "r1", + "content": "RCP 062 MT" + }, + { + "row": "NR CBP 3", + "column": "r2", + "content": "C" + }, + { + "row": "NR CBP 3", + "column": "r3", + "content": "KIC/SU 读数" + }, + { + "row": "NR CBP 3", + "column": "r4", + "content": "S" + }, + { + "row": "NR HBP 3", + "column": "r1", + "content": "RCP 059 MT" + }, + { + "row": "NR HBP 3", + "column": "r2", + "content": "C" + }, + { + "row": "NR HBP 3", + "column": "r3", + "content": "KIC/SU 读数" + }, + { + "row": "NR HBP 3", + "column": "r4", + "content": "S" + } + ] + }, + "17": { + "id": "17", + "name": "拆装前后保护Ⅰ组通道鉴定表", + "tableType": "grid", + "description": "机柜:KCS013AR\n标准:断开前与连接后的差值 <0.15 ℃", + "uploadStrategy": "immediate", + "isShared": true, + "columnHeaders": [ + { + "id": "r0", + "name": "物理参数" + }, + { + "id": "r1", + "name": "传感器" + }, + { + "id": "r2", + "name": "传感器标识" + }, + { + "id": "r3", + "name": "测量点" + }, + { + "id": "r4", + "name": "测量点标识" + }, + { + "id": "r5", + "name": "断开前" + }, + { + "id": "r6", + "name": "连接后" + } + ], + "rowHeaders": [ + { + "id": "NR HBP 1", + "name": "NR HBP 1" + }, + { + "id": "NR CBP 1", + "name": "NR CBP 1" + }, + { + "id": "WR CL 2", + "name": "WR CL 2" + }, + { + "id": "WR HL 2", + "name": "WR HL 2" + } + ], + "staticCells": [ + { + "row": "NR HBP 1", + "column": "r1", + "content": "RCP 030 MT" + }, + { + "row": "NR HBP 1", + "column": "r2", + "content": "P" + }, + { + "row": "NR HBP 1", + "column": "r3", + "content": "KIC/SU 读数" + }, + { + "row": "NR HBP 1", + "column": "r4", + "content": "S" + }, + { + "row": "NR CBP 1", + "column": "r1", + "content": "RCP 033 MT" + }, + { + "row": "NR CBP 1", + "column": "r2", + "content": "P" + }, + { + "row": "NR CBP 1", + "column": "r3", + "content": "KIC/SU 读数" + }, + { + "row": "NR CBP 1", + "column": "r4", + "content": "S" + }, + { + "row": "WR CL 2", + "column": "r1", + "content": "RCP 044 MT" + }, + { + "row": "WR CL 2", + "column": "r2", + "content": "C" + }, + { + "row": "WR CL 2", + "column": "r3", + "content": "KIC/SU 读数" + }, + { + "row": "WR CL 2", + "column": "r4", + "content": "S" + }, + { + "row": "WR HL 2", + "column": "r1", + "content": "RCP 043 MT" + }, + { + "row": "WR HL 2", + "column": "r2", + "content": "C" + }, + { + "row": "WR HL 2", + "column": "r3", + "content": "KIC/SU 读数" + }, + { + "row": "WR HL 2", + "column": "r4", + "content": "S" + } + ] + }, + "18": { + "id": "18", + "name": "拆装前后保护Ⅱ组通道鉴定表", + "tableType": "grid", + "description": "机柜:KCS023AR\n标准:断开前与连接后的差值 <0.15 ℃", + "uploadStrategy": "immediate", + "isShared": true, + "columnHeaders": [ + { + "id": "r0", + "name": "物理参数" + }, + { + "id": "r1", + "name": "传感器" + }, + { + "id": "r2", + "name": "传感器标识" + }, + { + "id": "r3", + "name": "测量点" + }, + { + "id": "r4", + "name": "测量点标识" + }, + { + "id": "r5", + "name": "断开前" + }, + { + "id": "r6", + "name": "连接后" + } + ], + "rowHeaders": [ + { + "id": "NR HBP 2", + "name": "NR HBP 2" + }, + { + "id": "NR CBP 2", + "name": "NR CBP 2" + }, + { + "id": "WR HL 1", + "name": "WR HL 1" + }, + { + "id": "WR CL 1", + "name": "WR CL 1" + } + ], + "staticCells": [ + { + "row": "NR HBP 2", + "column": "r1", + "content": "RCP 045 MT" + }, + { + "row": "NR HBP 2", + "column": "r2", + "content": "P" + }, + { + "row": "NR HBP 2", + "column": "r3", + "content": "KIC/SU 读数" + }, + { + "row": "NR HBP 2", + "column": "r4", + "content": "S" + }, + { + "row": "NR CBP 2", + "column": "r1", + "content": "RCP 048 MT" + }, + { + "row": "NR CBP 2", + "column": "r2", + "content": "P" + }, + { + "row": "NR CBP 2", + "column": "r3", + "content": "KIC/SU 读数" + }, + { + "row": "NR CBP 2", + "column": "r4", + "content": "S" + }, + { + "row": "WR HL 1", + "column": "r1", + "content": "RCP 028 MT" + }, + { + "row": "WR HL 1", + "column": "r2", + "content": "C" + }, + { + "row": "WR HL 1", + "column": "r3", + "content": "KIC/SU 读数" + }, + { + "row": "WR HL 1", + "column": "r4", + "content": "S" + }, + { + "row": "WR CL 1", + "column": "r1", + "content": "RCP 029 MT" + }, + { + "row": "WR CL 1", + "column": "r2", + "content": "C" + }, + { + "row": "WR CL 1", + "column": "r3", + "content": "KIC/SU 读数" + }, + { + "row": "WR CL 1", + "column": "r4", + "content": "S" + } + ] + }, + "19": { + "id": "19", + "name": "拆装前后保护Ⅲ组通道鉴定表", + "tableType": "grid", + "description": "机柜:KCS033AR\n标准:断开前与连接后的差值 <0.15 ℃", + "uploadStrategy": "immediate", + "isShared": true, + "columnHeaders": [ + { + "id": "r0", + "name": "物理参数" + }, + { + "id": "r1", + "name": "传感器" + }, + { + "id": "r2", + "name": "传感器标识" + }, + { + "id": "r3", + "name": "测量点" + }, + { + "id": "r4", + "name": "测量点标识" + }, + { + "id": "r5", + "name": "断开前" + }, + { + "id": "r6", + "name": "连接后" + } + ], + "rowHeaders": [ + { + "id": "NR HBP 3", + "name": "NR HBP 3" + }, + { + "id": "NR CBP 3", + "name": "NR CBP 3" + }, + { + "id": "WR HL 3", + "name": "WR HL 3" + }, + { + "id": "WR CL 3", + "name": "WR CL 3" + } + ], + "staticCells": [ + { + "row": "NR HBP 3", + "column": "r1", + "content": "RCP 057 MT" + }, + { + "row": "NR HBP 3", + "column": "r2", + "content": "P" + }, + { + "row": "NR HBP 3", + "column": "r3", + "content": "KIC/SU 读数" + }, + { + "row": "NR HBP 3", + "column": "r4", + "content": "S" + }, + { + "row": "NR CBP 3", + "column": "r1", + "content": "RCP 060 MT" + }, + { + "row": "NR CBP 3", + "column": "r2", + "content": "P" + }, + { + "row": "NR CBP 3", + "column": "r3", + "content": "KIC/SU 读数" + }, + { + "row": "NR CBP 3", + "column": "r4", + "content": "S" + }, + { + "row": "WR HL 3", + "column": "r1", + "content": "RCP 055 MT" + }, + { + "row": "WR HL 3", + "column": "r2", + "content": "C" + }, + { + "row": "WR HL 3", + "column": "r3", + "content": "KIC/SU 读数" + }, + { + "row": "WR HL 3", + "column": "r4", + "content": "S" + }, + { + "row": "WR CL 3", + "column": "r1", + "content": "RCP 056 MT" + }, + { + "row": "WR CL 3", + "column": "r2", + "content": "C" + }, + { + "row": "WR CL 3", + "column": "r3", + "content": "KIC/SU 读数" + }, + { + "row": "WR CL 3", + "column": "r4", + "content": "S" + } + ] + }, + "20": { + "id": "20", + "name": "保护Ⅰ组绝缘连续性记录表", + "tableType": "grid", + "description": "机柜:KCS013AR", + "uploadStrategy": "immediate", + "isShared": true, + "columnHeaders": [ + { + "id": "r0", + "name": "物理参数" + }, + { + "id": "r1", + "name": "传感器" + }, + { + "id": "r2", + "name": "传感器标识" + }, + { + "id": "r3", + "name": "端子排" + }, + { + "id": "r4", + "name": "绝缘值MΩ" + }, + { + "id": "r5", + "name": "R1/2(Ω)" + }, + { + "id": "r6", + "name": "R1/3(Ω)" + }, + { + "id": "r7", + "name": "R1/4(Ω)" + }, + { + "id": "r8", + "name": "R2/3(Ω)" + }, + { + "id": "r9", + "name": "R2/4(Ω)" + }, + { + "id": "r10", + "name": "R3/4(Ω)" + }, + { + "id": "r11", + "name": "R4wires(Ω)" + } + ], + "rowHeaders": [ + { + "id": "NR HBP 1", + "name": "NR HBP 1" + }, + { + "id": "NR CBP 1", + "name": "NR CBP 1" + }, + { + "id": "NR HBP 1", + "name": "NR HBP 1" + }, + { + "id": "NR CBP 1", + "name": "NR CBP 1" + }, + { + "id": "WR HL 2", + "name": "WR HL 2" + }, + { + "id": "WR CL 2", + "name": "WR CL 2" + } + ], + "staticCells": [ + { + "row": "NR HBP 1", + "column": "r1", + "content": "RCP 030 MT" + }, + { + "row": "NR HBP 1", + "column": "r2", + "content": "P" + }, + { + "row": "NR HBP 1", + "column": "r3", + "content": "EB911 X5 Z18,B18,D18,F18" + }, + { + "row": "NR CBP 1", + "column": "r1", + "content": "RCP 033 MT" + }, + { + "row": "NR CBP 1", + "column": "r2", + "content": "P" + }, + { + "row": "NR CBP 1", + "column": "r3", + "content": "EB911 X5 Z20,B20,D20,F20" + }, + { + "row": "NR HBP 1", + "column": "r1", + "content": "RCP 031 MT" + }, + { + "row": "NR HBP 1", + "column": "r2", + "content": "S" + }, + { + "row": "NR HBP 1", + "column": "r3", + "content": "EB911 X6 Z22,B22,D22,F22" + }, + { + "row": "NR CBP 1", + "column": "r1", + "content": "RCP 034 MT" + }, + { + "row": "NR CBP 1", + "column": "r2", + "content": "S" + }, + { + "row": "NR CBP 1", + "column": "r3", + "content": "EB911 X6 Z24,B24,D24,F24" + }, + { + "row": "WR HL 2", + "column": "r1", + "content": "RCP 043 MT" + }, + { + "row": "WR HL 2", + "column": "r2", + "content": "C" + }, + { + "row": "WR HL 2", + "column": "r3", + "content": "EB902 X1 Z02,B02,D02,F02" + }, + { + "row": "WR HL 2", + "column": "r5", + "content": "/" + }, + { + "row": "WR HL 2", + "column": "r6", + "content": "/" + }, + { + "row": "WR HL 2", + "column": "r7", + "content": "/" + }, + { + "row": "WR HL 2", + "column": "r11", + "content": "/" + }, + { + "row": "WR CL 2", + "column": "r1", + "content": "RCP 044 MT" + }, + { + "row": "WR CL 2", + "column": "r2", + "content": "C" + }, + { + "row": "WR CL 2", + "column": "r3", + "content": "EB902 X1 Z04,B04,D04,F04" + }, + { + "row": "WR CL 2", + "column": "r5", + "content": "/" + }, + { + "row": "WR CL 2", + "column": "r6", + "content": "/" + }, + { + "row": "WR CL 2", + "column": "r7", + "content": "/" + }, + { + "row": "WR CL 2", + "column": "r11", + "content": "/" + } + ] + }, + "21": { + "id": "21", + "name": "保护Ⅱ组绝缘连续性记录表", + "tableType": "grid", + "description": "机柜:KCS023AR", + "uploadStrategy": "immediate", + "isShared": true, + "columnHeaders": [ + { + "id": "r0", + "name": "物理参数" + }, + { + "id": "r1", + "name": "传感器" + }, + { + "id": "r2", + "name": "传感器标识" + }, + { + "id": "r3", + "name": "端子排" + }, + { + "id": "r4", + "name": "绝缘值(MΩ)" + }, + { + "id": "r5", + "name": "R1/2(Ω)" + }, + { + "id": "r6", + "name": "R1/3(Ω)" + }, + { + "id": "r7", + "name": "R1/4(Ω)" + }, + { + "id": "r8", + "name": "R2/3(Ω)" + }, + { + "id": "r9", + "name": "R2/4(Ω)" + }, + { + "id": "r10", + "name": "R3/4(Ω)" + }, + { + "id": "r11", + "name": "R4wires(Ω)" + } + ], + "rowHeaders": [ + { + "id": "NR HBP 2", + "name": "NR HBP 2" + }, + { + "id": "NR CBP 2", + "name": "NR CBP 2" + }, + { + "id": "NR HBP 2", + "name": "NR HBP 2" + }, + { + "id": "NR CBP 2", + "name": "NR CBP 2" + }, + { + "id": "WR HL 1", + "name": "WR HL 1" + }, + { + "id": "WR CL 1", + "name": "WR CL 1" + } + ], + "staticCells": [ + { + "row": "NR HBP 2", + "column": "r1", + "content": "RCP 045 MT" + }, + { + "row": "NR HBP 2", + "column": "r2", + "content": "P" + }, + { + "row": "NR HBP 2", + "column": "r3", + "content": "EB911 X5 Z18,B18,D18,F18" + }, + { + "row": "NR CBP 2", + "column": "r1", + "content": "RCP 048 MT" + }, + { + "row": "NR CBP 2", + "column": "r2", + "content": "P" + }, + { + "row": "NR CBP 2", + "column": "r3", + "content": "EB911 X5 Z20,B20,D20,F20" + }, + { + "row": "NR HBP 2", + "column": "r1", + "content": "RCP 046 MT" + }, + { + "row": "NR HBP 2", + "column": "r2", + "content": "S" + }, + { + "row": "NR HBP 2", + "column": "r3", + "content": "EB911 X6 Z22,B22,D22,F22" + }, + { + "row": "NR CBP 2", + "column": "r1", + "content": "RCP 049 MT" + }, + { + "row": "NR CBP 2", + "column": "r2", + "content": "S" + }, + { + "row": "NR CBP 2", + "column": "r3", + "content": "EB911 X6 Z24,B24,D24,F24" + }, + { + "row": "WR HL 1", + "column": "r1", + "content": "RCP 028 MT" + }, + { + "row": "WR HL 1", + "column": "r2", + "content": "C" + }, + { + "row": "WR HL 1", + "column": "r3", + "content": "EB902 X1 Z02,B02,D02,F02" + }, + { + "row": "WR HL 1", + "column": "r5", + "content": "/" + }, + { + "row": "WR HL 1", + "column": "r6", + "content": "/" + }, + { + "row": "WR HL 1", + "column": "r7", + "content": "/" + }, + { + "row": "WR HL 1", + "column": "r11", + "content": "/" + }, + { + "row": "WR CL 1", + "column": "r1", + "content": "RCP 029 MT" + }, + { + "row": "WR CL 1", + "column": "r2", + "content": "C" + }, + { + "row": "WR CL 1", + "column": "r3", + "content": "EB902 X1 Z04,B04,D04,F04" + }, + { + "row": "WR CL 1", + "column": "r5", + "content": "/" + }, + { + "row": "WR CL 1", + "column": "r6", + "content": "/" + }, + { + "row": "WR CL 1", + "column": "r7", + "content": "/" + }, + { + "row": "WR CL 1", + "column": "r11", + "content": "/" + } + ] + }, + "22": { + "id": "22", + "name": "保护Ⅲ组绝缘连续性记录表", + "tableType": "grid", + "description": "机柜:KCS033AR", + "uploadStrategy": "immediate", + "isShared": true, + "columnHeaders": [ + { + "id": "r0", + "name": "物理参数" + }, + { + "id": "r1", + "name": "传感器" + }, + { + "id": "r2", + "name": "传感器标识" + }, + { + "id": "r3", + "name": "端子排" + }, + { + "id": "r4", + "name": "绝缘值(MΩ)" + }, + { + "id": "r5", + "name": "R1/2(Ω)" + }, + { + "id": "r6", + "name": "R1/3(Ω)" + }, + { + "id": "r7", + "name": "R1/4(Ω)" + }, + { + "id": "r8", + "name": "R2/3(Ω)" + }, + { + "id": "r9", + "name": "R2/4(Ω)" + }, + { + "id": "r10", + "name": "R3/4(Ω)" + }, + { + "id": "r11", + "name": "R4wires(Ω)" + } + ], + "rowHeaders": [ + { + "id": "NR HBP 3", + "name": "NR HBP 3" + }, + { + "id": "NR CBP 3", + "name": "NR CBP 3" + }, + { + "id": "NR HBP 3", + "name": "NR HBP 3" + }, + { + "id": "NR CBP 3", + "name": "NR CBP 3" + }, + { + "id": "WR HL 3", + "name": "WR HL 3" + }, + { + "id": "WR CL 3", + "name": "WR CL 3" + } + ], + "staticCells": [ + { + "row": "NR HBP 3", + "column": "r1", + "content": "RCP 057 MT" + }, + { + "row": "NR HBP 3", + "column": "r2", + "content": "P" + }, + { + "row": "NR HBP 3", + "column": "r3", + "content": "EB911 X5 Z18,B18,D18,F18" + }, + { + "row": "NR CBP 3", + "column": "r1", + "content": "RCP 060 MT" + }, + { + "row": "NR CBP 3", + "column": "r2", + "content": "P" + }, + { + "row": "NR CBP 3", + "column": "r3", + "content": "EB911 X5 Z20,B20,D20,F20" + }, + { + "row": "NR HBP 3", + "column": "r1", + "content": "RCP 058 MT" + }, + { + "row": "NR HBP 3", + "column": "r2", + "content": "S" + }, + { + "row": "NR HBP 3", + "column": "r3", + "content": "EB911 X6 Z22,B22,D22,F22" + }, + { + "row": "NR CBP 3", + "column": "r1", + "content": "RCP 061 MT" + }, + { + "row": "NR CBP 3", + "column": "r2", + "content": "S" + }, + { + "row": "NR CBP 3", + "column": "r3", + "content": "EB911 X6 Z24,B24,D24,F24" + }, + { + "row": "WR HL 3", + "column": "r1", + "content": "RCP 055 MT" + }, + { + "row": "WR HL 3", + "column": "r2", + "content": "C" + }, + { + "row": "WR HL 3", + "column": "r3", + "content": "EB902 X1 Z02,B02,D02,F02" + }, + { + "row": "WR HL 3", + "column": "r5", + "content": "/" + }, + { + "row": "WR HL 3", + "column": "r6", + "content": "/" + }, + { + "row": "WR HL 3", + "column": "r7", + "content": "/" + }, + { + "row": "WR HL 3", + "column": "r11", + "content": "/" + }, + { + "row": "WR CL 3", + "column": "r1", + "content": "RCP 056 MT" + }, + { + "row": "WR CL 3", + "column": "r2", + "content": "C" + }, + { + "row": "WR CL 3", + "column": "r3", + "content": "EB902 X1 Z04,B04,D04,F04" + }, + { + "row": "WR CL 3", + "column": "r5", + "content": "/" + }, + { + "row": "WR CL 3", + "column": "r6", + "content": "/" + }, + { + "row": "WR CL 3", + "column": "r7", + "content": "/" + }, + { + "row": "WR CL 3", + "column": "r11", + "content": "/" + } + ] + }, + "23": { + "id": "23", + "name": "控制组环路1绝缘连续性记录表", + "tableType": "grid", + "description": "机柜:RGL301AR", + "uploadStrategy": "immediate", + "isShared": true, + "columnHeaders": [ + { + "id": "r0", + "name": "物理参数" + }, + { + "id": "r1", + "name": "传感器" + }, + { + "id": "r2", + "name": "传感器标识" + }, + { + "id": "r3", + "name": "端子排" + }, + { + "id": "r4", + "name": "绝缘值(MΩ)" + }, + { + "id": "r5", + "name": "R1/2(Ω)" + }, + { + "id": "r6", + "name": "R1/3(Ω)" + }, + { + "id": "r7", + "name": "R1/4(Ω)" + }, + { + "id": "r8", + "name": "R2/3(Ω)" + }, + { + "id": "r9", + "name": "R2/4(Ω)" + }, + { + "id": "r10", + "name": "R3/4(Ω)" + }, + { + "id": "r11", + "name": "R4wires(Ω)" + } + ], + "rowHeaders": [ + { + "id": "NR CBP 1", + "name": "NR CBP 1" + }, + { + "id": "NR HBP 1", + "name": "NR HBP 1" + } + ], + "staticCells": [ + { + "row": "NR CBP 1", + "column": "r1", + "content": "RCP 035 MT" + }, + { + "row": "NR CBP 1", + "column": "r2", + "content": "C" + }, + { + "row": "NR CBP 1", + "column": "r3", + "content": "EB902 X2" + }, + { + "row": "NR HBP 1", + "column": "r1", + "content": "RCP 032 MT" + }, + { + "row": "NR HBP 1", + "column": "r2", + "content": "C" + }, + { + "row": "NR HBP 1", + "column": "r3", + "content": "EB902 X1" + } + ] + }, + "24": { + "id": "24", + "name": "控制组环路2绝缘连续性记录表", + "tableType": "grid", + "description": "机柜:RGL301AR", + "uploadStrategy": "immediate", + "isShared": true, + "columnHeaders": [ + { + "id": "r0", + "name": "物理参数" + }, + { + "id": "r1", + "name": "传感器" + }, + { + "id": "r2", + "name": "传感器标识" + }, + { + "id": "r3", + "name": "端子排" + }, + { + "id": "r4", + "name": "绝缘值(MΩ)" + }, + { + "id": "r5", + "name": "R1/2(Ω)" + }, + { + "id": "r6", + "name": "R1/3(Ω)" + }, + { + "id": "r7", + "name": "R1/4(Ω)" + }, + { + "id": "r8", + "name": "R2/3(Ω)" + }, + { + "id": "r9", + "name": "R2/4(Ω)" + }, + { + "id": "r10", + "name": "R3/4(Ω)" + }, + { + "id": "r11", + "name": "R4wires(Ω)" + } + ], + "rowHeaders": [ + { + "id": "NR CBP 2", + "name": "NR CBP 2" + }, + { + "id": "NR HBP 2", + "name": "NR HBP 2" + } + ], + "staticCells": [ + { + "row": "NR CBP 2", + "column": "r1", + "content": "RCP 050 MT" + }, + { + "row": "NR CBP 2", + "column": "r2", + "content": "C" + }, + { + "row": "NR CBP 2", + "column": "r3", + "content": "EB902 X4" + }, + { + "row": "NR HBP 2", + "column": "r1", + "content": "RCP 047 MT" + }, + { + "row": "NR HBP 2", + "column": "r2", + "content": "C" + }, + { + "row": "NR HBP 2", + "column": "r3", + "content": "EB902 X3" + } + ] + }, + "25": { + "id": "25", + "name": "控制组环路3绝缘连续性记录表", + "tableType": "grid", + "description": "机柜:RGL301AR", + "uploadStrategy": "immediate", + "isShared": true, + "columnHeaders": [ + { + "id": "r0", + "name": "物理参数" + }, + { + "id": "r1", + "name": "传感器" + }, + { + "id": "r2", + "name": "传感器标识" + }, + { + "id": "r3", + "name": "端子排" + }, + { + "id": "r4", + "name": "绝缘值(MΩ)" + }, + { + "id": "r5", + "name": "R1/2(Ω)" + }, + { + "id": "r6", + "name": "R1/3(Ω)" + }, + { + "id": "r7", + "name": "R1/4(Ω)" + }, + { + "id": "r8", + "name": "R2/3(Ω)" + }, + { + "id": "r9", + "name": "R2/4(Ω)" + }, + { + "id": "r10", + "name": "R3/4(Ω)" + }, + { + "id": "r11", + "name": "R4wires(Ω)" + } + ], + "rowHeaders": [ + { + "id": "NR CBP 3", + "name": "NR CBP 3" + }, + { + "id": "NR HBP 3", + "name": "NR HBP 3" + } + ], + "staticCells": [ + { + "row": "NR CBP 3", + "column": "r1", + "content": "RCP 062 MT" + }, + { + "row": "NR CBP 3", + "column": "r2", + "content": "C" + }, + { + "row": "NR CBP 3", + "column": "r3", + "content": "EB902 X6" + }, + { + "row": "NR HBP 3", + "column": "r1", + "content": "RCP 059 MT" + }, + { + "row": "NR HBP 3", + "column": "r2", + "content": "C" + }, + { + "row": "NR HBP 3", + "column": "r3", + "content": "EB902 X5" + } + ] + } + } +} diff --git a/procedures/simple_rcp63.yaml b/procedures/simple_rcp63.yaml new file mode 100644 index 0000000..354b8b0 --- /dev/null +++ b/procedures/simple_rcp63.yaml @@ -0,0 +1,700 @@ +testActions: + prereq_147: &prereq_147 + id: prereq_147 + document: '

在热停堆工况(热功率稳定),平均温度最大允许变化<0.2℃;若KIC中显示的三个环路平均温度变化AT>0.2℃,则采取数据滑动平均方式,期间确保三个环路平均温度的滑动平均值最大最小值之差<0.2℃、测量时间大于滑动周期。

' + mode: manual + dataFields: + - tableRef: '#/tables/manualConfirm' + fields: [confirm_147] + prereq_150: &prereq_150 + id: prereq_150 + document: '

三个蒸发器处于稳定状态,压力一致。

' + mode: manual + dataFields: + - tableRef: '#/tables/manualConfirm' + fields: [confirm_150] + prereq_43: &prereq_43 + id: prereq_43 + document: '

蒸汽发生器水位稳定。

' + mode: manual + dataFields: + - tableRef: '#/tables/manualConfirm' + fields: [confirm_43] + prereq_75: &prereq_75 + id: prereq_75 + document: '

RCV的一个下泄孔板投入运行;确保测量期间无下泄孔板相关操作,一回路温度保持稳定,测量结果若超差需要分析下泄流量影响。

' + mode: manual + dataFields: + - tableRef: '#/tables/manualConfirm' + fields: [confirm_75] + prereq_92: &prereq_92 + id: prereq_92 + document: '

其它模式/工况下需进行相应的独立风险分析。

' + mode: manual + dataFields: + - tableRef: '#/tables/manualConfirm' + fields: [confirm_92] + pre_153: &pre_153 + id: pre_153 + document: '

本程序适用于热停堆(热功率稳定)工况下执行RCP63程序,统一进行Sensor Check部分使用。

' + mode: manual + dataFields: + - tableRef: '#/tables/manualConfirm' + fields: [confirm_153] + pre_136: &pre_136 + id: pre_136 + document: '

执行前确认KIC中一回路三个环路平均温度变化量不超过0.2℃。

' + mode: auto + functionType: DATA_ACQUISITION_WITH_VALIDATION + functionParameters: + - RCP614KM + - RCP618KM + - RCP622KM + dataFields: + - tableRef: '#/tables/temperatureCheckTable' + fields: [loop1TempVariation, loop2TempVariation, loop3TempVariation] + pre_137: &pre_137 + id: pre_137 + document: '

若三环路平均温度变化超过0.2℃,记录滑动周期、各环路滑动平均最大最小值之差及最短数据测量时间,确认均满足≤0.2℃且测量时间>滑动周期。

' + mode: auto + functionType: DATA_ACQUISITION_WITH_VALIDATION + functionParameters: + - RCP614KM + - RCP618KM + - RCP622KM + - RCP612KM + - RCP616KM + - RCP620KM + - RCP613KM + - RCP617KM + - RCP621KM + - RCP611KM + - RCP615KM + - RCP619KM + dataFields: + - tableRef: '#/tables/temperatureCheckTable' + fields: [loop1TempVariation, loop2TempVariation, loop3TempVariation] + pre_138: &pre_138 + id: pre_138 + document: '

确认 RPR、RPN上无相关的试验检修工作。

' + mode: manual + dataFields: + - tableRef: '#/tables/manualConfirm' + fields: [confirm_138] + pre_139: &pre_139 + id: pre_139 + document: '

按附表1检查相关模拟量指示并记录,确认结果满意。

' + mode: auto + functionType: DATA_ACQUISITION + functionParameters: + - RCP614KM + - RCP618KM + - RCP622KM + dataFields: + - tableRef: '#/tables/alarmCheckTable' + cells: + - row: RCP614KM + column: preInspectionStatus + - row: RCP618KM + column: preInspectionStatus + - row: RCP622KM + column: preInspectionStatus + pre_140: &pre_140 + id: pre_140 + document: '

试验前根据附表1报警清单检查报警,确认无异常报警,方可进行后续检查工作。

' + mode: auto + functionType: DATA_ACQUISITION + functionParameters: + - RCP455AA + - RPAB138AA + - RPAB167AA + - RPAB171AA + dataFields: + - tableRef: '#/tables/alarmCheckTable' + cells: + - row: RCP455AA + column: preInspectionStatus + - row: RPAB138AA + column: preInspectionStatus + - row: RPAB167AA + column: preInspectionStatus + - row: RPAB171AA + column: preInspectionStatus + x5KicReadingsPreCheck: &x5KicReadingsPreCheck + id: x5KicReadingsPreCheck + document: '

采集X5端子排拆前KIC指示值。

' + mode: auto + functionType: DATA_ACQUISITION + functionParameters: + - RCP030MT + - RCP033MT + dataFields: + - tableRef: '#/tables/kicReadingsTable' + cells: + - row: RCP030MT + column: preInspectionValue + - row: RCP033MT + column: preInspectionValue + x5KicReadingsPostCheck: &x5KicReadingsPostCheck + id: x5KicReadingsPostCheck + document: '

采集X5端子排恢复后KIC指示值。

' + mode: auto + functionType: DATA_ACQUISITION + functionParameters: + - RCP030MT + - RCP033MT + dataFields: + - tableRef: '#/tables/kicReadingsTable' + cells: + - row: RCP030MT + column: postInspectionValue + - row: RCP033MT + column: postInspectionValue + rcp030mtResistance: &rcp030mtResistance + id: rcp030mtResistance + mode: auto + functionType: RESISTANCE_4WIRE + channel: input1 + functionParameters: + document: '

测量位置:KCSO13AR/EB911/X5

测量项目:R1/2, R1/3, R1/4, R2/3, R2/4, R3/4, R/4wires

' + dataFields: + - tableRef: '#/tables/resistanceMeasurementTable' + cells: + - row: RCP030MT + column: r12 + - row: RCP030MT + column: r13 + - row: RCP030MT + column: r14 + - row: RCP030MT + column: r23 + - row: RCP030MT + column: r24 + - row: RCP030MT + column: r34 + - row: RCP030MT + column: r4wires + uploadStrategy: inherit + rcp030mtInsulation: &rcp030mtInsulation + id: rcp030mtInsulation + mode: auto + functionType: INSULATION + channel: input1 + functionParameters: + testVoltage: 50 + testTime: 20 + dischargeTime: 5 + document: '

测量位置:KCSO13AR/EB911/X5

注意事项:

  • 测试电压50V
  • 测试时间不小于20秒
  • 测量后探头对地放电
' + dataFields: + - tableRef: '#/tables/resistanceMeasurementTable' + cells: + - row: RCP030MT + column: insulation + uploadStrategy: inherit + rcp033mtResistance: &rcp033mtResistance + id: rcp033mtResistance + mode: auto + functionType: RESISTANCE_4WIRE + channel: input1 + functionParameters: + document: '

测量位置:KCSO13AR/EB911/X5

测量项目:R1/2, R1/3, R1/4, R2/3, R2/4, R3/4, R/4wires

' + dataFields: + - tableRef: '#/tables/resistanceMeasurementTable' + cells: + - row: RCP033MT + column: r12 + - row: RCP033MT + column: r13 + - row: RCP033MT + column: r14 + - row: RCP033MT + column: r23 + - row: RCP033MT + column: r24 + - row: RCP033MT + column: r34 + - row: RCP033MT + column: r4wires + uploadStrategy: inherit + rcp033mtInsulation: &rcp033mtInsulation + id: rcp033mtInsulation + mode: auto + functionType: INSULATION + channel: input1 + functionParameters: + testVoltage: 50 + testTime: 20 + dischargeTime: 5 + document: '

测量位置:KCSO13AR/EB911/X5

注意事项:

  • 测试电压50V
  • 测试时间不小于20秒
  • 测量后探头对地放电
  • 复归保险FB059 F11和F12
' + dataFields: + - tableRef: '#/tables/resistanceMeasurementTable' + cells: + - row: RCP033MT + column: insulation + uploadStrategy: inherit + rcp031mtResistance: &rcp031mtResistance + id: rcp031mtResistance + mode: auto + functionType: RESISTANCE_4WIRE + channel: input1 + functionParameters: + document: '

测量位置:KCS013AR/EB911/X6

测量项目:R1/2, R1/3, R1/4, R2/3, R2/4, R3/4, R/4wires

' + dataFields: + - tableRef: '#/tables/resistanceMeasurementTable' + cells: + - row: RCP031MT + column: r12 + - row: RCP031MT + column: r13 + - row: RCP031MT + column: r14 + - row: RCP031MT + column: r23 + - row: RCP031MT + column: r24 + - row: RCP031MT + column: r34 + - row: RCP031MT + column: r4wires + uploadStrategy: inherit + rcp031mtInsulation: &rcp031mtInsulation + id: rcp031mtInsulation + mode: auto + functionType: INSULATION + channel: input1 + functionParameters: + testVoltage: 50 + testTime: 20 + dischargeTime: 5 + document: '

测量位置:KCS013AR/EB911/X6

注意事项:

  • 测试电压50V
  • 测试时间不小于20秒
  • 测量后探头对地放电
' + dataFields: + - tableRef: '#/tables/resistanceMeasurementTable' + cells: + - row: RCP031MT + column: insulation + uploadStrategy: inherit + rcp034mtResistance: &rcp034mtResistance + id: rcp034mtResistance + mode: auto + functionType: RESISTANCE_4WIRE + channel: input1 + functionParameters: + document: '

测量位置:KCS013AR/EB911/X6

测量项目:R1/2, R1/3, R1/4, R2/3, R2/4, R3/4, R/4wires

' + dataFields: + - tableRef: '#/tables/resistanceMeasurementTable' + cells: + - row: RCP034MT + column: r12 + - row: RCP034MT + column: r13 + - row: RCP034MT + column: r14 + - row: RCP034MT + column: r23 + - row: RCP034MT + column: r24 + - row: RCP034MT + column: r34 + - row: RCP034MT + column: r4wires + uploadStrategy: inherit + rcp034mtInsulation: &rcp034mtInsulation + id: rcp034mtInsulation + mode: auto + functionType: INSULATION + channel: input1 + functionParameters: + testVoltage: 50 + testTime: 20 + dischargeTime: 5 + document: '

测量位置:KCS013AR/EB911/X6

注意事项:

  • 测试电压50V
  • 测试时间不小于20秒
  • 测量后探头对地放电
' + dataFields: + - tableRef: '#/tables/resistanceMeasurementTable' + cells: + - row: RCP034MT + column: insulation + uploadStrategy: inherit + rcp043mtResistance: &rcp043mtResistance + id: rcp043mtResistance + mode: auto + functionType: RESISTANCE_3WIRE + channel: input1 + functionParameters: + document: '

测量位置:KCSO13AR/EB902/X1

测量项目:R2/3, R2/4, R3/4

注意:3线制传感器

' + dataFields: + - tableRef: '#/tables/resistanceMeasurementTable' + cells: + - row: RCP043MT + column: r23 + - row: RCP043MT + column: r24 + - row: RCP043MT + column: r34 + uploadStrategy: inherit + rcp043mtInsulation: &rcp043mtInsulation + id: rcp043mtInsulation + mode: auto + functionType: INSULATION + channel: input1 + functionParameters: + testVoltage: 50 + testTime: 20 + dischargeTime: 5 + document: '

测量位置:KCSO13AR/EB902/X1

注意事项:

  • 测试电压50V
  • 测试时间不小于20秒
  • 测量后探头对地放电
' + dataFields: + - tableRef: '#/tables/resistanceMeasurementTable' + cells: + - row: RCP043MT + column: insulation + uploadStrategy: inherit + rcp044mtResistance: &rcp044mtResistance + id: rcp044mtResistance + mode: auto + functionType: RESISTANCE_3WIRE + channel: input1 + functionParameters: + document: '

测量位置:KCSO13AR/EB902/X1

测量项目:R2/3, R2/4, R3/4

注意:3线制传感器

' + dataFields: + - tableRef: '#/tables/resistanceMeasurementTable' + cells: + - row: RCP044MT + column: r23 + - row: RCP044MT + column: r24 + - row: RCP044MT + column: r34 + uploadStrategy: inherit + rcp044mtInsulation: &rcp044mtInsulation + id: rcp044mtInsulation + mode: auto + functionType: INSULATION + channel: input1 + functionParameters: + testVoltage: 50 + testTime: 20 + dischargeTime: 5 + document: '

测量位置:KCSO13AR/EB902/X1

注意事项:

  • 测试电压50V
  • 测试时间不小于20秒
  • 测量后探头对地放电
  • 复归保险FB005 F11和F12
  • 请主控确认报警消除
' + dataFields: + - tableRef: '#/tables/resistanceMeasurementTable' + cells: + - row: RCP044MT + column: insulation + uploadStrategy: inherit +testActivityGroups: + prerequisites: &prerequisites + id: prerequisites + name: 3.0先决条件 + actions: + - *prereq_147 + - *prereq_150 + - *prereq_43 + - *prereq_75 + - *prereq_92 + preInspectionChecks: &preInspectionChecks + id: preInspectionChecks + name: 5.1试验前状态说明和检查 + tableRefs: + - '#/tables/temperatureCheckTable' + - '#/tables/alarmCheckTable' + actions: + - *pre_153 + - *pre_136 + - *pre_137 + - *pre_138 + - *pre_139 + - *pre_140 + protectionIGroup: &protectionIGroup + id: protectionIGroup + name: 5.2保护Ⅰ组传感器及线缆绝缘及电阻检查 + tableRefs: + - '#/tables/kicReadingsTable' + - '#/tables/resistanceMeasurementTable' + actions: + - *x5KicReadingsPreCheck + - *rcp030mtResistance + - *rcp030mtInsulation + - *rcp033mtResistance + - *rcp033mtInsulation + - *x5KicReadingsPostCheck +testTaskGroups: + prerequisitesTask: &prerequisitesTask + id: prerequisitesTask + name: 3.0先决条件 + stages: + - *prerequisites + preInspectionTask: &preInspectionTask + id: preInspectionTask + name: 5.1试验前状态说明和检查 + stages: + - *preInspectionChecks + protectionITask: &protectionITask + id: protectionITask + name: 5.2保护Ⅰ组传感器及线缆绝缘及电阻检查 + stages: + - *protectionIGroup +resultDisplays: + finalResultDisplay: &finalResultDisplay + id: finalResultDisplay + name: 实验结果汇总 + document: '

显示本次实验的所有测量数据和检查结果

' + tableRefs: + - '#/tables/resistanceMeasurementTable' + - '#/tables/temperatureCheckTable' + - '#/tables/alarmCheckTable' + - '#/tables/kicReadingsTable' +procedure: + id: SIMPLE63 + name: 范例 一回路温度传感器绝缘和连续性检查 TP RCP63 + version: SIMPLE + activitySequence: + - *prerequisitesTask + - *preInspectionTask + - *protectionITask + - *finalResultDisplay +tables: + kicReadingsTable: + id: kicReadingsTable + name: KIC指示读数表 + description: 检验前后的KIC指示读数 + tableType: grid + isShared: true + uploadStrategy: onComplete + columnHeaders: + - id: preInspectionValue + name: 拆前数据 + type: numeric + isRequired: true + unit: '℃' + - id: postInspectionValue + name: 拆后数据 + type: numeric + isRequired: true + unit: '℃' + rowHeaders: + - id: RCP030MT + name: RCP030MT + - id: RCP033MT + name: RCP033MT + - id: RCP031MT + name: RCP031MT + - id: RCP034MT + name: RCP034MT + - id: RCP043MT + name: RCP043MT + - id: RCP044MT + name: RCP044MT + alarmCheckTable: + id: alarmCheckTable + name: 试验前后报警检查记录表 + description: 依据TP RCP63的报警检查记录结构 + tableType: grid + isShared: true + uploadStrategy: onComplete + columnHeaders: + - id: signal + name: 信号 + type: text + isRequired: false + - id: description + name: 描述 + type: text + isRequired: false + - id: location + name: 位置 + type: text + isRequired: false + - id: normalState + name: 正常状态 + type: text + isRequired: false + - id: eventTime + name: 出现时间 + type: text + isRequired: false + - id: preInspectionStatus + name: 拆前状态 + type: text + isRequired: true + - id: preInspectionStatusB + name: 拆前B列 + type: text + isRequired: false + - id: postInspectionStatus + name: 拆后状态 + type: text + isRequired: true + - id: postInspectionStatusB + name: 拆后B列 + type: text + isRequired: false + rowHeaders: + - id: RCP455AA + name: RCP455AA + - id: RPAB138AA + name: RPA/B138AA + - id: RPAB167AA + name: RPA/B167AA + - id: RPAB171AA + name: RPA/B171AA + - id: RPA_B079AA + name: RPA/B079AA + - id: RPA_B039LA + name: RPA/B039LA + - id: RCP614KM + name: RCP614KM + - id: RCP618KM + name: RCP618KM + - id: RCP622KM + name: RCP622KM + manualConfirm: + id: manualConfirm + name: 人工确认 + tableType: form + uploadStrategy: immediate + layoutConfig: + totalColumns: 12 + fields: + - id: confirm_147 + name: 确认 147 + type: boolean + layoutConfig: + labelSpan: 4 + valueSpan: 8 + - id: confirm_150 + name: 确认 150 + type: boolean + layoutConfig: + labelSpan: 4 + valueSpan: 8 + - id: confirm_43 + name: 确认 43 + type: boolean + layoutConfig: + labelSpan: 4 + valueSpan: 8 + - id: confirm_75 + name: 确认 75 + type: boolean + layoutConfig: + labelSpan: 4 + valueSpan: 8 + - id: confirm_92 + name: 确认 92 + type: boolean + layoutConfig: + labelSpan: 4 + valueSpan: 8 + - id: confirm_153 + name: 确认 153 + type: boolean + layoutConfig: + labelSpan: 4 + valueSpan: 8 + - id: confirm_138 + name: 确认 138 + type: boolean + layoutConfig: + labelSpan: 4 + valueSpan: 8 + - id: confirm_142 + name: 确认 142 + type: boolean + layoutConfig: + labelSpan: 4 + valueSpan: 8 + resistanceMeasurementTable: + id: resistanceMeasurementTable + name: 电阻测量表 + description: 传感器的电阻和绝缘测量数据 + tableType: grid + uploadStrategy: onComplete + columnHeaders: + - id: r12 + name: R1/2 + type: numeric + isRequired: false + unit: Ω + - id: r13 + name: R1/3 + type: numeric + isRequired: false + unit: Ω + - id: r14 + name: R1/4 + type: numeric + isRequired: false + unit: Ω + - id: r23 + name: R2/3 + type: numeric + isRequired: true + unit: Ω + - id: r24 + name: R2/4 + type: numeric + isRequired: true + unit: Ω + - id: r34 + name: R3/4 + type: numeric + isRequired: true + unit: Ω + - id: r4wires + name: R/4wires + type: numeric + isRequired: false + unit: Ω + - id: insulation + name: Insulation + type: numeric + isRequired: true + unit: MΩ + rowHeaders: + - id: RCP030MT + name: RCP030MT + - id: RCP033MT + name: RCP033MT + - id: RCP031MT + name: RCP031MT + - id: RCP034MT + name: RCP034MT + - id: RCP043MT + name: RCP043MT + - id: RCP044MT + name: RCP044MT + temperatureCheckTable: + id: temperatureCheckTable + name: 环路温度检查表 + description: 环路平均温度变化量检查 + tableType: form + uploadStrategy: onComplete + layoutConfig: + totalColumns: 12 + fields: + - id: loop1TempVariation + name: 一环路平均温度变化量 + type: numeric + isRequired: true + unit: '℃' + layoutConfig: + labelSpan: 4 + valueSpan: 8 + order: 1 + - id: loop2TempVariation + name: 二环路平均温度变化量 + type: numeric + isRequired: true + unit: '℃' + layoutConfig: + labelSpan: 4 + valueSpan: 8 + order: 2 + - id: loop3TempVariation + name: 三环路平均温度变化量 + type: numeric + isRequired: true + unit: '℃' + layoutConfig: + labelSpan: 4 + valueSpan: 8 + order: 3 diff --git a/procedures/simple_test.yaml b/procedures/simple_test.yaml new file mode 100644 index 0000000..a08e82c --- /dev/null +++ b/procedures/simple_test.yaml @@ -0,0 +1,59 @@ +procedure: + id: SIMPLE_TEST + name: 简单测试规程 + version: '1.0' + document: | +

这是一个简单的测试规程,用于演示 ProcedurePlayer 的基本功能。

+

包含一个测试任务组和结果展示。

+ activitySequence: + - id: testGroup1 + name: 测试组1 + document: '

执行基本测试

' + stages: + - id: stage1 + name: 测试阶段 + document: '

填写测试数据

' + tableRefs: + - '#/tables/testTable' + actions: + - id: action1 + document: '

请在测试数据表中填写字段1和字段2。

' + mode: manual + dataFields: + - tableRef: '#/tables/testTable' + fields: + - field1 + - field2 + - id: resultDisplay + name: 结果展示 + document: '

查看测试结果

' + tableRefs: + - '#/tables/testTable' + +tables: + testTable: + id: testTable + name: 测试数据表 + description: 简单的测试数据表 + tableType: form + uploadStrategy: onComplete + layoutConfig: + totalColumns: 12 + fields: + - id: field1 + name: 测试字段1 + type: text + isRequired: true + layoutConfig: + labelSpan: 4 + valueSpan: 8 + order: 1 + - id: field2 + name: 测试字段2 + type: numeric + isRequired: true + unit: 'V' + layoutConfig: + labelSpan: 4 + valueSpan: 8 + order: 2 diff --git a/resources.qrc b/resources.qrc new file mode 100644 index 0000000..62a84dc --- /dev/null +++ b/resources.qrc @@ -0,0 +1,37 @@ + + + + + icons/app.png + icons/ic_rtd.svg + icons/ic_rtd_output.svg + icons/ic_thermocouple.svg + icons/ic_insulation.svg + icons/ic_dc_current.svg + icons/ic_dc_voltage.svg + icons/ic_frequency.svg + icons/ic_ac_voltage.svg + icons/ic_switch.svg + icons/ic_ripple.svg + icons/ic_ramp.svg + icons/ic_waveform.svg + icons/ic_dual_channel.svg + icons/ic_trim.svg + icons/ic_data.svg + icons/ic_wireless.svg + icons/ic_sop.svg + icons/ic_rcp63.svg + icons/ic_settings.svg + icons/ic_network_test.svg + icons/arrow_down.svg + icons/arrow_up.svg + + + + + + + + config/launcher_config.json + + diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..e9b26a5 --- /dev/null +++ b/run.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# 设置Qt插件路径 +export QT_PLUGIN_PATH=/opt/homebrew/Cellar/qt@5/5.15.18/plugins + +# 进入脚本所在目录 +cd "$(dirname "$0")" + +# 确保配置文件存在 +mkdir -p build/CalibratorLauncher.app/Contents/MacOS/config +cp -f config/launcher_config.json build/CalibratorLauncher.app/Contents/MacOS/config/ + +# 启动应用 +exec ./build/CalibratorLauncher.app/Contents/MacOS/CalibratorLauncher diff --git a/schema.json b/schema.json new file mode 100644 index 0000000..73008c8 --- /dev/null +++ b/schema.json @@ -0,0 +1,602 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/schema/procedure.json", + "title": "Procedure Configuration Schema", + "description": "Schema for intelligent calibrator procedure configuration. Structure: Procedure → TestTaskGroup/ResultDisplay → TestActivityGroup → TestAction", + "type": "object", + "required": ["procedure", "tables"], + "properties": { + "procedure": { + "type": "object", + "description": "Main procedure definition", + "required": ["id", "name", "version", "activitySequence"], + "properties": { + "id": { + "type": "string", + "description": "Unique procedure identifier" + }, + "name": { + "type": "string", + "description": "Procedure display name" + }, + "version": { + "type": "string", + "description": "Procedure version" + }, + "document": { + "type": "string", + "description": "Procedure documentation in HTML/Markdown" + }, + "metadata": { + "type": "object", + "description": "Additional procedure metadata" + }, + "activitySequence": { + "type": "array", + "description": "Top-level activity sequence: testTaskGroups and resultDisplays", + "items": { + "anyOf": [ + { "$ref": "#/$defs/testTaskGroup" }, + { "$ref": "#/$defs/resultDisplay" }, + { "$ref": "#/$defs/activityReference" } + ] + }, + "minItems": 1 + } + } + }, + "tables": { + "type": "object", + "description": "Reusable table definitions for data storage and display", + "additionalProperties": { "$ref": "#/$defs/tableDefinition" } + }, + "testTaskGroups": { + "type": "object", + "description": "Reusable test task group definitions", + "additionalProperties": { "$ref": "#/$defs/testTaskGroup" } + }, + "testActivityGroups": { + "type": "object", + "description": "Reusable test activity group definitions", + "additionalProperties": { "$ref": "#/$defs/testActivityGroup" } + }, + "testActions": { + "type": "object", + "description": "Reusable test action definitions", + "additionalProperties": { "$ref": "#/$defs/testAction" } + }, + "resultDisplays": { + "type": "object", + "description": "Reusable result display definitions", + "additionalProperties": { "$ref": "#/$defs/resultDisplay" } + }, + "fieldDefinitions": { + "type": "object", + "description": "Reusable field definitions for tables", + "additionalProperties": { "$ref": "#/$defs/fieldDefinition" } + } + }, + "$defs": { + "testTaskGroup": { + "type": "object", + "description": "Top-level test task group containing multiple test activity groups", + "required": ["id", "name", "stages"], + "properties": { + "id": { + "type": "string", + "description": "Unique test task group identifier" + }, + "name": { + "type": "string", + "description": "Test task group display name" + }, + "metadata": { + "type": "object", + "description": "Additional metadata" + }, + "stages": { + "type": "array", + "description": "Ordered sequence of test activity groups (stages)", + "items": { + "anyOf": [ + { "$ref": "#/$defs/testActivityGroup" }, + { "$ref": "#/$defs/groupReference" } + ] + }, + "minItems": 1 + } + } + }, + "resultDisplay": { + "type": "object", + "description": "Result display activity for showing test results", + "required": ["id"], + "properties": { + "id": { + "type": "string", + "description": "Unique result display identifier" + }, + "name": { + "type": "string", + "description": "Result display title" + }, + "document": { + "type": "string", + "description": "Display instructions in HTML/Markdown" + }, + "tableRefs": { + "type": "array", + "items": { "$ref": "#/$defs/tableReference" } + } + } + }, + "testActivityGroup": { + "type": "object", + "description": "Test activity group containing multiple test actions", + "required": ["id", "name", "actions"], + "properties": { + "id": { + "type": "string", + "description": "Unique activity group identifier" + }, + "name": { + "type": "string", + "description": "Activity group display name" + }, + "document": { + "type": "string", + "description": "Documentation in HTML/Markdown" + }, + "metadata": { + "type": "object", + "description": "Additional metadata" + }, + "tableRefs": { + "type": "array", + "items": { "$ref": "#/$defs/tableReference" } + }, + "actions": { + "type": "array", + "description": "Array of test actions in this group, or references to them", + "items": { + "anyOf": [ + { "$ref": "#/$defs/testAction" }, + { "$ref": "#/$defs/testActionReference" } + ] + }, + "minItems": 1 + } + } + }, + "testAction": { + "type": "object", + "description": "Individual test action (measurement, check, or data acquisition)", + "required": ["id", "document", "mode"], + "properties": { + "id": { + "type": "string", + "description": "Unique test action identifier" + }, + "document": { + "type": "string", + "description": "Instructions/documentation in HTML/Markdown" + }, + "mode": { + "type": "string", + "enum": ["manual", "auto"], + "description": "Execution mode: manual requires user input, auto attempts automatic data acquisition" + }, + "functionType": { + "type": "string", + "description": "Function type for this test action (e.g., RESISTANCE_4WIRE, VOLTAGE_V, etc.)" + }, + "channel": { + "type": "string", + "enum": ["input1", "input2", "output1", "output2"], + "description": "Channel used for this test action: input1, input2, output1, or output2" + }, + "functionParameters": { + "description": "Function-specific parameters as key-value pairs", + "oneOf": [ + { + "type": "object" + }, + { + "type": "array" + } + ] + }, + "metadata": { "type": "object" }, + "dataFields": { + "type": "array", + "items": { "$ref": "#/$defs/fieldSelector" } + }, + "validationCriteria": { "type": "object" }, + "uploadStrategy": { + "type": "string", + "enum": ["immediate", "onComplete", "inherit"], + "default": "inherit", + "description": "Data upload strategy for this action: immediate (upload after action), onComplete (upload with table), inherit (use table's strategy)" + }, + "uploadFields": { + "type": "array", + "description": "Specific fields to upload immediately after this action completes", + "items": { "type": "string" } + } + }, + "allOf": [ + { + "oneOf": [ + { + "properties": { "mode": { "const": "auto" } }, + "required": ["functionType", "functionParameters"], + "description": "Auto mode check items must define functionType and functionParameters" + }, + { + "properties": { "mode": { "const": "manual" } } + } + ] + }, + { + "oneOf": [ + { + "properties": { + "functionType": { + "enum": [ + "DATA_ACQUISITION", + "DATA_ACQUISITION_WITH_VALIDATION" + ] + } + } + }, + { "required": ["channel"] } + ] + } + ] + }, + "tableDefinition": { + "type": "object", + "description": "Table definition for data storage (grid or form layout)", + "required": ["id", "name"], + "properties": { + "id": { + "type": "string", + "description": "Unique table identifier" + }, + "name": { + "type": "string", + "description": "Table display name" + }, + "description": { + "type": "string", + "description": "Table description" + }, + "tableType": { + "type": "string", + "enum": ["grid", "form", "series"], + "default": "grid", + "description": "Table layout type: grid for row-column table, form for key-value layout, series for time-series data recording" + }, + "layoutConfig": { + "type": "object", + "description": "Layout configuration for form-type tables", + "properties": { + "totalColumns": { + "type": "integer", + "default": 12, + "description": "Total columns in grid system (typically 12)" + } + } + }, + "isShared": { "type": "boolean", "default": false }, + "uploadStrategy": { + "type": "string", + "enum": ["immediate", "grouped", "byRow", "onComplete"], + "default": "onComplete", + "description": "Data upload strategy: immediate (per field), grouped (per group), byRow (per row), onComplete (entire table)" + }, + "uploadGroups": { + "type": "array", + "description": "Upload groups for grouped upload strategy, supporting field groups or specific cells by row-column position", + "items": { "$ref": "#/$defs/fieldSelector" } + }, + "columnHeaders": { + "type": "array", + "description": "Column headers for grid-type tables, or references to them", + "items": { + "anyOf": [ + { "$ref": "#/$defs/fieldDefinition" }, + { "$ref": "#/$defs/fieldDefinitionReference" } + ] + } + }, + "rowHeaders": { + "type": "array", + "description": "Row headers for grid-type tables", + "items": { + "type": "object", + "required": ["id", "name"], + "properties": { + "id": { "type": "string" }, + "name": { "type": "string" } + } + } + }, + "staticCells": { + "type": "array", + "description": "Static content entries for tables. Each entry selects one or more cells/fields via fieldSelector and renders fixed text.", + "items": { + "type": "object", + "field": { + "type": "string", + "description": "Field ID (for form-type tables)" + }, + "row": { + "oneOf": [ + { "type": "string", "description": "Row ID" }, + { + "type": "integer", + "description": "Row index (0-based)" + } + ] + }, + "column": { + "oneOf": [ + { "type": "string", "description": "Column ID" }, + { + "type": "integer", + "description": "Column index (0-based)" + } + ] + }, + "content": { + "type": "string", + "description": "Static text content to display. When targeting form fields, the field is treated as read-only and shows this fixed value." + }, + "oneOf": [ + { "required": ["field", "content"] }, + { "required": ["row", "column", "content"] } + ] + } + }, + "fields": { + "type": "array", + "description": "Fields for form-type and time-series tables, or references to them", + "items": { + "anyOf": [ + { "$ref": "#/$defs/fieldDefinition" }, + { "$ref": "#/$defs/fieldDefinitionReference" } + ] + } + } + }, + "oneOf": [ + { + "properties": { "tableType": { "const": "form" } }, + "required": ["fields"], + "description": "Form-type tables must define fields" + }, + { + "properties": { "tableType": { "const": "grid" } }, + "required": ["columnHeaders", "rowHeaders"], + "description": "Grid-type tables must define columnHeaders and rowHeaders" + }, + { + "properties": { "tableType": { "const": "series" } }, + "required": ["fields"], + "description": "Time-series tables must define fields" + } + ] + }, + "fieldDefinition": { + "type": "object", + "description": "Field definition for table columns or form fields", + "required": ["id", "name"], + "properties": { + "id": { + "type": "string", + "description": "Unique field identifier" + }, + "name": { + "type": "string", + "description": "Field display name" + }, + "type": { + "type": "string", + "enum": [ + "numeric", + "text", + "selection", + "boolean", + "datetime", + "calculated" + ] + }, + "defaultValue": { + "description": "Default value for the field" + }, + "options": { + "type": "array", + "description": "Options for selection type fields" + }, + "formula": { + "type": "string", + "description": "Formula for calculated type fields" + }, + "validationRules": { + "type": "object", + "properties": { + "min": { "type": "number" }, + "max": { "type": "number" }, + "precision": { "type": "integer", "minimum": 0 }, + "pattern": { "type": "string" }, + "minLength": { "type": "integer", "minimum": 0 }, + "maxLength": { "type": "integer", "minimum": 0 }, + "required": { "type": "boolean" } + } + }, + "isRequired": { "type": "boolean", "default": false }, + "isReadOnly": { "type": "boolean", "default": false }, + "unit": { "type": "string" }, + "description": { "type": "string" }, + "uploadImmediately": { + "type": "boolean", + "default": false, + "description": "Whether to upload this field immediately after value change" + }, + "layoutConfig": { + "type": "object", + "description": "Layout configuration for form-type tables", + "properties": { + "labelSpan": { + "type": "integer", + "minimum": 1, + "maximum": 11, + "default": 1, + "description": "Columns occupied by field label" + }, + "valueSpan": { + "type": "integer", + "minimum": 1, + "maximum": 11, + "default": 11, + "description": "Columns occupied by field value" + }, + "rowSpan": { + "type": "integer", + "minimum": 1, + "default": 1, + "description": "Rows occupied by this field" + }, + "order": { + "type": "integer", + "description": "Display order in form layout" + } + } + } + }, + "oneOf": [ + { + "properties": { "type": { "const": "selection" } }, + "required": ["options"], + "description": "Selection-type fields must define options" + }, + { + "properties": { "type": { "const": "calculated" } }, + "required": ["formula"], + "description": "Calculated-type fields must define formula" + }, + { + "properties": { + "type": { + "enum": ["numeric", "text", "boolean", "datetime"] + } + } + } + ] + }, + "fieldSelector": { + "type": "object", + "description": "Selector for table fields or cells, supporting both form-type (field IDs) and grid-type (row-column positions) tables", + "properties": { + "tableRef": { "$ref": "#/$defs/tableReference" }, + "fields": { + "type": "array", + "items": { "type": "string" }, + "description": "Array of field IDs (for form-type tables)" + }, + "cells": { + "type": "array", + "description": "Array of specific cells by row-column position (for grid-type tables)", + "items": { + "type": "object", + "required": ["row", "column"], + "properties": { + "row": { + "oneOf": [ + { "type": "string", "description": "Row ID" }, + { "type": "integer", "description": "Row index (0-based)" } + ] + }, + "column": { + "oneOf": [ + { "type": "string", "description": "Column ID" }, + { + "type": "integer", + "description": "Column index (0-based)" + } + ] + } + } + } + }, + "ignore": { + "type": "boolean", + "default": false, + "description": "Whether to skip this field when filling table data from function return values" + } + }, + "oneOf": [ + { "required": ["fields"] }, + { "required": ["cells"] }, + { + "properties": { "ignore": { "const": true } }, + "required": ["ignore"] + } + ] + }, + "activityReference": { + "type": "object", + "description": "Reference to a top-level activity (testTaskGroup or resultDisplay)", + "required": ["$ref"], + "properties": { + "$ref": { + "type": "string", + "pattern": "^#/(testTaskGroups|resultDisplays)/.+$", + "description": "JSON Pointer to a testTaskGroup or resultDisplay" + } + } + }, + "groupReference": { + "type": "object", + "description": "Reference to a test activity group", + "required": ["$ref"], + "properties": { + "$ref": { + "type": "string", + "pattern": "^#/testActivityGroups/.+$", + "description": "JSON Pointer to a testActivityGroup" + } + } + }, + "testActionReference": { + "type": "object", + "description": "Reference to a test action", + "required": ["$ref"], + "properties": { + "$ref": { + "type": "string", + "pattern": "^#/testActions/.+$", + "description": "JSON Pointer to a testAction" + } + } + }, + "fieldDefinitionReference": { + "type": "object", + "description": "Reference to a field definition", + "required": ["$ref"], + "properties": { + "$ref": { + "type": "string", + "pattern": "^#/fieldDefinitions/.+$", + "description": "JSON Pointer to a fieldDefinition" + } + } + }, + "tableReference": { + "type": "string", + "pattern": "^#/tables/.+$", + "description": "JSON Pointer to a table definition (e.g., #/tables/myTable)" + } + } +} diff --git a/utils/configmanager.cpp b/utils/configmanager.cpp new file mode 100644 index 0000000..f21c80c --- /dev/null +++ b/utils/configmanager.cpp @@ -0,0 +1,382 @@ +#include "configmanager.h" +#include +#include + +ConfigManager::ConfigManager(QObject *parent) + : QObject(parent) +{ +} + +ConfigManager::~ConfigManager() +{ +} + +bool ConfigManager::loadFromFile(const QString &filePath) +{ + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly)) + { + qWarning() << "Cannot open config file:" << filePath; + return false; + } + + QByteArray data = file.readAll(); + file.close(); + + QJsonParseError error; + m_configDoc = QJsonDocument::fromJson(data, &error); + + if (error.error != QJsonParseError::NoError) + { + qWarning() << "JSON parse error:" << error.errorString(); + return false; + } + + m_config = m_configDoc.object(); + return true; +} + +bool ConfigManager::loadDefaultConfig() +{ + // 默认配置 - 基于需求规格 + QString defaultConfig = R"({ + "name": "智能校验仪启动器", + "version": "1.0.0", + "colorSystem": { + "primary": "#2196F3", + "accent": "#FF9800", + "success": "#4CAF50", + "warning": "#FFC107", + "danger": "#F44336", + "background": "#F5F5F5" + }, + "pages": [ + { + "id": "page1", + "title": "信号输出和测量", + "apps": [ + { + "id": "rtd", + "name": "热电阻", + "nameEn": "RTD", + "color": "#2196F3", + "gradient": ["#2196F3", "#1976D2"], + "badge": "Ω", + "description": "2/3/4线热电阻测量,PT100模拟输出", + "features": ["测量2/3/4线热电阻阻值", "自动计算线电阻", "自动转换为温度", "PT100分度表模拟输出"] + }, + { + "id": "thermocouple", + "name": "热电偶", + "nameEn": "TC", + "color": "#FF9800", + "gradient": ["#FF9800", "#F57C00"], + "badge": "mV", + "description": "热电偶电压测量,分度表模拟输出" + }, + { + "id": "insulation", + "name": "绝缘电阻", + "nameEn": "Insulation", + "color": "#F44336", + "gradient": ["#F44336", "#D32F2F"], + "badge": "MΩ", + "description": "50V/100V绝缘电阻测量", + "warning": true + }, + { + "id": "dc_current", + "name": "直流电流", + "nameEn": "DC Current", + "color": "#4CAF50", + "gradient": ["#4CAF50", "#388E3C"], + "badge": "mA", + "description": "0-20mA电流输出/测量" + }, + { + "id": "dc_voltage", + "name": "直流电压", + "nameEn": "DC Voltage", + "color": "#9C27B0", + "gradient": ["#9C27B0", "#7B1FA2"], + "badge": "V", + "description": "0-20V电压输出/测量" + }, + { + "id": "frequency", + "name": "频率信号", + "nameEn": "Frequency", + "color": "#00BCD4", + "gradient": ["#00BCD4", "#0097A7"], + "badge": "Hz", + "description": "转速测量,方波/正弦波输出" + }, + { + "id": "ac_voltage", + "name": "交流电压", + "nameEn": "AC Voltage", + "color": "#E91E63", + "gradient": ["#E91E63", "#C2185B"], + "badge": "VAC", + "description": "220VAC测量功能", + "warning": true + } + ] + }, + { + "id": "page2", + "title": "信号检测功能", + "apps": [ + { + "id": "switch_detect", + "name": "开关量检测", + "nameEn": "Switch Detect", + "color": "#795548", + "gradient": ["#795548", "#5D4037"], + "badge": "SW", + "description": "电阻通断/电压判断开关状态" + }, + { + "id": "ripple", + "name": "纹波诊断", + "nameEn": "Ripple Diagnosis", + "color": "#FF5722", + "gradient": ["#FF5722", "#E64A19"], + "badge": "AI", + "description": "电源纹波检测,AI故障诊断" + }, + { + "id": "ramp", + "name": "斜波输出", + "nameEn": "Ramp Output", + "color": "#3F51B5", + "gradient": ["#3F51B5", "#303F9F"], + "badge": "△", + "description": "可设速率的斜波信号输出" + }, + { + "id": "waveform", + "name": "波形采集", + "nameEn": "Waveform Capture", + "color": "#009688", + "gradient": ["#009688", "#00796B"], + "badge": "REC", + "description": "信号采集记录,≤25ms/点" + }, + { + "id": "dual_channel", + "name": "双通道", + "nameEn": "Dual Channel", + "color": "#673AB7", + "gradient": ["#673AB7", "#512DA8"], + "badge": "2CH", + "description": "同时输出2路,采集2路信号" + }, + { + "id": "trim", + "name": "信号微调", + "nameEn": "Signal Trim", + "color": "#607D8B", + "gradient": ["#607D8B", "#455A64"], + "badge": "±", + "description": "任意位数信号微调" + } + ] + }, + { + "id": "page3", + "title": "系统和通讯", + "apps": [ + { + "id": "data_management", + "name": "数据管理", + "nameEn": "Data Management", + "color": "#8BC34A", + "gradient": ["#8BC34A", "#689F38"], + "badge": "📁", + "description": "数据存储、导出、搜索" + }, + { + "id": "wireless", + "name": "无线通讯", + "nameEn": "Wireless", + "color": "#03A9F4", + "gradient": ["#03A9F4", "#0288D1"], + "badge": "4G", + "description": "4G/WIFI通讯设置" + }, + { + "id": "sop", + "name": "规程管理", + "nameEn": "SOP", + "color": "#CDDC39", + "gradient": ["#CDDC39", "#AFB42B"], + "badge": "📋", + "description": "自动化校验流程" + }, + { + "id": "rcp63", + "name": "RCP63", + "nameEn": "RCP63", + "color": "#2196F3", + "gradient": ["#2196F3", "#1976D2"], + "badge": "🌐", + "description": "RCP63协议通讯" + }, + { + "id": "settings", + "name": "系统设置", + "nameEn": "Settings", + "color": "#9E9E9E", + "gradient": ["#9E9E9E", "#757575"], + "badge": "⚙️", + "description": "系统参数配置" + } + ] + } + ], + "dock": [ + { + "id": "dock_sop", + "name": "规程", + "color": "#CDDC39", + "gradient": ["#CDDC39", "#AFB42B"] + }, + { + "id": "dock_rcp63", + "name": "RCP63", + "color": "#2196F3", + "gradient": ["#2196F3", "#1976D2"] + }, + { + "id": "dock_network", + "name": "网络测试", + "color": "#03A9F4", + "gradient": ["#03A9F4", "#0288D1"] + }, + { + "id": "dock_settings", + "name": "系统设置", + "color": "#9E9E9E", + "gradient": ["#9E9E9E", "#757575"] + } + ], + "animations": { + "pageTransition": { + "duration": 300, + "easing": "ease-out" + }, + "iconPress": { + "scale": 0.95, + "duration": 100 + } + } + })"; + + QJsonParseError error; + m_configDoc = QJsonDocument::fromJson(defaultConfig.toUtf8(), &error); + + if (error.error != QJsonParseError::NoError) + { + qWarning() << "Default config parse error:" << error.errorString(); + return false; + } + + m_config = m_configDoc.object(); + return true; +} + +bool ConfigManager::saveToFile(const QString &filePath) +{ + QFile file(filePath); + if (!file.open(QIODevice::WriteOnly)) + { + qWarning() << "Cannot write config file:" << filePath; + return false; + } + + file.write(m_configDoc.toJson(QJsonDocument::Indented)); + file.close(); + return true; +} + +QVector ConfigManager::getPages() const +{ + QVector pages; + + QJsonArray pagesArray = m_config["pages"].toArray(); + for (const QJsonValue &pageVal : pagesArray) + { + pages.append(parsePageConfig(pageVal.toObject())); + } + + return pages; +} + +QVector ConfigManager::getDockApps() const +{ + QVector apps; + + QJsonArray dockArray = m_config["dock"].toArray(); + for (const QJsonValue &appVal : dockArray) + { + apps.append(parseAppData(appVal.toObject())); + } + + return apps; +} + +QJsonObject ConfigManager::getColorSystem() const +{ + return m_config["colorSystem"].toObject(); +} + +QJsonObject ConfigManager::getAnimations() const +{ + return m_config["animations"].toObject(); +} + +AppIconData ConfigManager::parseAppData(const QJsonObject &obj) const +{ + AppIconData data; + data.id = obj["id"].toString(); + data.name = obj["name"].toString(); + data.nameEn = obj["nameEn"].toString(); + data.icon = obj["icon"].toString(); + data.color = obj["color"].toString(); + data.badge = obj["badge"].toString(); + data.group = obj["group"].toString(); // 解析分组标识 + data.description = obj["description"].toString(); + data.warning = obj["warning"].toBool(false); + + // 解析渐变色 + QJsonArray gradientArray = obj["gradient"].toArray(); + for (const QJsonValue &val : gradientArray) + { + data.gradient.append(val.toString()); + } + + // 解析功能列表 + QJsonArray featuresArray = obj["features"].toArray(); + for (const QJsonValue &val : featuresArray) + { + data.features.append(val.toString()); + } + + return data; +} + +PageConfig ConfigManager::parsePageConfig(const QJsonObject &obj) const +{ + PageConfig config; + config.id = obj["id"].toString(); + config.title = obj["title"].toString(); + + QJsonArray appsArray = obj["apps"].toArray(); + for (const QJsonValue &appVal : appsArray) + { + config.apps.append(parseAppData(appVal.toObject())); + } + + return config; +} diff --git a/utils/configmanager.h b/utils/configmanager.h new file mode 100644 index 0000000..48f0ea4 --- /dev/null +++ b/utils/configmanager.h @@ -0,0 +1,37 @@ +#ifndef CONFIGMANAGER_H +#define CONFIGMANAGER_H + +#include +#include +#include +#include +#include +#include "../widgets/appicon.h" +#include "../widgets/launcherpage.h" + +class ConfigManager : public QObject +{ + Q_OBJECT + +public: + explicit ConfigManager(QObject *parent = nullptr); + ~ConfigManager(); + + bool loadFromFile(const QString &filePath); + bool loadDefaultConfig(); + bool saveToFile(const QString &filePath); + + QVector getPages() const; + QVector getDockApps() const; + QJsonObject getColorSystem() const; + QJsonObject getAnimations() const; + +private: + AppIconData parseAppData(const QJsonObject &obj) const; + PageConfig parsePageConfig(const QJsonObject &obj) const; + + QJsonDocument m_configDoc; + QJsonObject m_config; +}; + +#endif // CONFIGMANAGER_H diff --git a/utils/stylehelper.cpp b/utils/stylehelper.cpp new file mode 100644 index 0000000..e724937 --- /dev/null +++ b/utils/stylehelper.cpp @@ -0,0 +1,286 @@ +#include "stylehelper.h" + +void StyleHelper::applyGlobalStyle(QApplication *app) +{ + app->setStyleSheet(getGlobalStyleSheet()); +} + +QString StyleHelper::getGlobalStyleSheet() +{ + return R"( + /* 全局样式 */ + QWidget { + font-family: "Source Han Sans SC", "Noto Sans SC", "Microsoft YaHei", sans-serif; + } + + /* 主窗口 */ + QMainWindow { + background-color: #F5F5F5; + } + + /* 按钮样式 - 触摸屏优化 */ + QPushButton { + background-color: #2196F3; + color: white; + border: none; + border-radius: 8px; + padding: 10px 20px; + font-size: 14px; + font-weight: 500; + min-height: 38px; + } + + QPushButton:hover { + background-color: #1976D2; + } + + QPushButton:pressed { + background-color: #1565C0; + } + + QPushButton:disabled { + background-color: #BDBDBD; + color: #757575; + } + + /* 次要按钮 */ + QPushButton[flat="true"] { + background-color: transparent; + color: #2196F3; + } + + QPushButton[flat="true"]:hover { + background-color: rgba(33, 150, 243, 0.1); + } + + /* 输入框 - 触摸屏优化 */ + QLineEdit { + background-color: white; + border: 1px solid #E0E0E0; + border-radius: 8px; + padding: 10px 14px; + font-size: 14px; + color: #212121; + min-height: 38px; + } + + QLineEdit:focus { + border-color: #2196F3; + border-width: 2px; + } + + QLineEdit:disabled { + background-color: #FAFAFA; + color: #9E9E9E; + } + + /* 下拉框 - 触摸屏优化 */ + QComboBox { + background-color: white; + border: 1px solid #E0E0E0; + border-radius: 8px; + padding: 10px 14px; + font-size: 14px; + color: #212121; + min-height: 38px; + } + + QComboBox:hover { + border-color: #BDBDBD; + } + + QComboBox:focus { + border-color: #2196F3; + } + + QComboBox::drop-down { + border: none; + width: 40px; + } + + QComboBox::down-arrow { + image: url(:/icons/arrow_down.svg); + width: 16px; + height: 16px; + } + + /* 滚动条 - 触摸屏优化 */ + QScrollBar:vertical { + background-color: #F5F5F5; + width: 14px; + border-radius: 7px; + } + + QScrollBar::handle:vertical { + background-color: #BDBDBD; + border-radius: 7px; + min-height: 50px; + } + + QScrollBar::handle:vertical:hover { + background-color: #9E9E9E; + } + + QScrollBar::add-line:vertical, + QScrollBar::sub-line:vertical { + height: 0px; + } + + QScrollBar:horizontal { + background-color: #F5F5F5; + height: 14px; + border-radius: 7px; + } + + QScrollBar::handle:horizontal { + background-color: #BDBDBD; + border-radius: 7px; + min-width: 50px; + } + + QScrollBar::handle:horizontal:hover { + background-color: #9E9E9E; + } + + QScrollBar::add-line:horizontal, + QScrollBar::sub-line:horizontal { + width: 0px; + } + + /* 标签页 */ + QTabWidget::pane { + border: 1px solid #E0E0E0; + border-radius: 8px; + background-color: white; + } + + QTabBar::tab { + background-color: #F5F5F5; + color: #757575; + padding: 12px 24px; + border-top-left-radius: 8px; + border-top-right-radius: 8px; + margin-right: 2px; + } + + QTabBar::tab:selected { + background-color: white; + color: #2196F3; + font-weight: 500; + } + + QTabBar::tab:hover:!selected { + background-color: #EEEEEE; + } + + /* 进度条 */ + QProgressBar { + background-color: #E0E0E0; + border: none; + border-radius: 4px; + height: 8px; + text-align: center; + } + + QProgressBar::chunk { + background-color: #2196F3; + border-radius: 4px; + } + + /* 工具提示 */ + QToolTip { + background-color: #424242; + color: white; + border: none; + border-radius: 4px; + padding: 8px 12px; + font-size: 12px; + } + + /* 消息框 */ + QMessageBox { + background-color: white; + } + + QMessageBox QLabel { + color: #212121; + font-size: 14px; + } + + /* 分组框 */ + QGroupBox { + background-color: white; + border: 1px solid #E0E0E0; + border-radius: 8px; + margin-top: 16px; + padding: 16px; + font-weight: 500; + } + + QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top left; + left: 16px; + padding: 0 8px; + color: #424242; + } + + /* 列表 */ + QListWidget { + background-color: white; + border: 1px solid #E0E0E0; + border-radius: 8px; + padding: 8px; + } + + QListWidget::item { + padding: 12px; + border-radius: 4px; + } + + QListWidget::item:selected { + background-color: #E3F2FD; + color: #1976D2; + } + + QListWidget::item:hover:!selected { + background-color: #F5F5F5; + } + + /* 表格 */ + QTableWidget { + background-color: white; + border: 1px solid #E0E0E0; + border-radius: 8px; + gridline-color: #EEEEEE; + } + + QTableWidget::item { + padding: 12px; + } + + QTableWidget::item:selected { + background-color: #E3F2FD; + color: #1976D2; + } + + QHeaderView::section { + background-color: #FAFAFA; + color: #424242; + padding: 12px; + border: none; + border-bottom: 1px solid #E0E0E0; + font-weight: 500; + } + )"; +} + +QString StyleHelper::cardShadow() +{ + return "0 4px 8px rgba(0,0,0,0.15)"; +} + +QString StyleHelper::buttonShadow() +{ + return "0 2px 4px rgba(0,0,0,0.2)"; +} diff --git a/utils/stylehelper.h b/utils/stylehelper.h new file mode 100644 index 0000000..0c92189 --- /dev/null +++ b/utils/stylehelper.h @@ -0,0 +1,32 @@ +#ifndef STYLEHELPER_H +#define STYLEHELPER_H + +#include +#include + +class StyleHelper +{ +public: + static void applyGlobalStyle(QApplication *app); + static QString getGlobalStyleSheet(); + + // 颜色获取 + static QString primaryColor() { return "#2196F3"; } + static QString accentColor() { return "#FF9800"; } + static QString successColor() { return "#4CAF50"; } + static QString warningColor() { return "#FFC107"; } + static QString dangerColor() { return "#F44336"; } + static QString backgroundColor() { return "#F5F5F5"; } + static QString surfaceColor() { return "#FFFFFF"; } + static QString textPrimaryColor() { return "#212121"; } + static QString textSecondaryColor() { return "#757575"; } + + // 阴影样式 + static QString cardShadow(); + static QString buttonShadow(); + +private: + StyleHelper() = default; +}; + +#endif // STYLEHELPER_H diff --git a/widgets/appicon.cpp b/widgets/appicon.cpp new file mode 100644 index 0000000..bf3fb79 --- /dev/null +++ b/widgets/appicon.cpp @@ -0,0 +1,283 @@ +#include "appicon.h" +#include +#include +#include +#include +#include +#include + +AppIcon::AppIcon(const AppIconData &data, QWidget *parent) + : QWidget(parent), m_data(data), m_scale(1.0), m_isPressed(false), m_longPressTimerId(0) +{ + qDebug() << "AppIcon constructor:" << data.name << "(" << data.id << ")"; + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + setCursor(Qt::PointingHandCursor); + setAttribute(Qt::WA_Hover); + + setupUI(); + qDebug() << "AppIcon created successfully:" << data.name; +} + +AppIcon::~AppIcon() +{ + if (m_longPressTimerId) + { + killTimer(m_longPressTimerId); + } +} + +void AppIcon::setupUI() +{ + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setContentsMargins(3, 3, 3, 3); + layout->setSpacing(5); + layout->setAlignment(Qt::AlignCenter); + + // Android风格图标尺寸 - 10英寸屏幕优化 + int iconSize = 80; + int borderRadius = 16; + + QWidget *iconContainer = new QWidget(this); + iconContainer->setFixedSize(iconSize, iconSize); + iconContainer->setObjectName("iconContainer"); + + // 设置阴影效果 - 更明显的阴影 + m_shadowEffect = new QGraphicsDropShadowEffect(this); + m_shadowEffect->setBlurRadius(16); + m_shadowEffect->setColor(QColor(0, 0, 0, 60)); + m_shadowEffect->setOffset(0, 4); + iconContainer->setGraphicsEffect(m_shadowEffect); + + // 图标标签 + m_iconLabel = new QLabel(iconContainer); + m_iconLabel->setAlignment(Qt::AlignCenter); + m_iconLabel->setFixedSize(iconSize, iconSize); + m_iconLabel->move(0, 0); + + // 设置图标样式 + QString gradientStart = m_data.gradient.size() > 0 ? m_data.gradient[0] : m_data.color; + QString gradientEnd = m_data.gradient.size() > 1 ? m_data.gradient[1] : m_data.color; + + QString iconStyle = QString(R"( + QLabel { + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, + stop:0 %1, stop:1 %2); + border-radius: %3px; + font-size: 36px; + color: white; + } + )") + .arg(gradientStart, gradientEnd) + .arg(borderRadius); + + m_iconLabel->setStyleSheet(iconStyle); + + // 设置 SVG 图标 + QString iconPath = getIconSymbol(m_data.id); + if (!iconPath.isEmpty()) + { + QPixmap pixmap(iconPath); + if (!pixmap.isNull()) + { + // 缩放图标到合适大小 + int svgSize = iconSize - 24; // 留出边距 + pixmap = pixmap.scaled(svgSize, svgSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + m_iconLabel->setPixmap(pixmap); + } + } + + // 徽章标签 - 过滤掉emoji,只显示纯文字badge + if (!m_data.badge.isEmpty()) + { + QString badgeText = m_data.badge; + // 过滤掉emoji字符(保留ASCII和常见中文字符) + QString filteredBadge; + for (const QChar &ch : badgeText) + { + ushort unicode = ch.unicode(); + // 保留基本ASCII(32-126)、中文字符(0x4E00-0x9FFF)、以及一些常用符号 + if ((unicode >= 32 && unicode <= 126) || + (unicode >= 0x4E00 && unicode <= 0x9FFF) || + ch == QChar(0xB1) || // ± + ch == QChar(0x394) || // Δ + ch == QChar(0x3A9)) + { // Ω + filteredBadge += ch; + } + } + + if (!filteredBadge.isEmpty()) + { + m_badgeLabel = new QLabel(iconContainer); + m_badgeLabel->setText(filteredBadge); + m_badgeLabel->setFixedSize(26, 16); + m_badgeLabel->setAlignment(Qt::AlignCenter); + m_badgeLabel->move(iconSize - 28, iconSize - 18); + m_badgeLabel->setStyleSheet(R"( + QLabel { + background-color: rgba(255, 255, 255, 0.95); + color: #333; + border-radius: 4px; + font-size: 9px; + font-weight: bold; + } + )"); + } + } + + layout->addWidget(iconContainer, 0, Qt::AlignCenter); + + // 名称标签 - Android风格白色文字带阴影 + m_nameLabel = new QLabel(m_data.name, this); + m_nameLabel->setAlignment(Qt::AlignCenter); + m_nameLabel->setFixedWidth(150); + m_nameLabel->setWordWrap(false); + m_nameLabel->setStyleSheet(R"( + QLabel { + color: white; + font-size: 13px; + font-weight: 500; + background: transparent; + } + )"); + layout->addWidget(m_nameLabel, 0, Qt::AlignCenter); +} + +QString AppIcon::getIconSymbol(const QString &id) const +{ + // 返回对应的SVG图标路径 + static QMap iconMap = { + {"rtd", ":/icons/ic_rtd.svg"}, + {"rtd_measure", ":/icons/ic_rtd.svg"}, + {"rtd_output", ":/icons/ic_rtd_output.svg"}, + {"thermocouple", ":/icons/ic_thermocouple.svg"}, + {"insulation", ":/icons/ic_insulation.svg"}, + {"dc_current", ":/icons/ic_dc_current.svg"}, + {"dc_voltage", ":/icons/ic_dc_voltage.svg"}, + {"frequency", ":/icons/ic_frequency.svg"}, + {"ac_voltage", ":/icons/ic_ac_voltage.svg"}, + {"switch_detect", ":/icons/ic_switch.svg"}, + {"ripple", ":/icons/ic_ripple.svg"}, + {"ramp", ":/icons/ic_ramp.svg"}, + {"waveform", ":/icons/ic_waveform.svg"}, + {"dual_channel", ":/icons/ic_dual_channel.svg"}, + {"trim", ":/icons/ic_trim.svg"}, + {"data_management", ":/icons/ic_data.svg"}, + {"wireless", ":/icons/ic_wireless.svg"}, + {"sop", ":/icons/ic_sop.svg"}, + {"rcp63", ":/icons/ic_rcp63.svg"}, + {"settings", ":/icons/ic_settings.svg"}, + {"network_test", ":/icons/ic_network_test.svg"}, + {"dock_sop", ":/icons/ic_sop.svg"}, + {"dock_rcp63", ":/icons/ic_rcp63.svg"}, + {"dock_network", ":/icons/ic_network_test.svg"}, + {"dock_settings", ":/icons/ic_settings.svg"}}; + + return iconMap.value(id, ""); +} + +void AppIcon::setScale(qreal scale) +{ + if (qFuzzyCompare(m_scale, scale)) + return; + m_scale = scale; + update(); +} + +void AppIcon::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + if (!qFuzzyCompare(m_scale, 1.0)) + { + // 应用缩放变换 + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + QTransform transform; + transform.translate(width() / 2, height() / 2); + transform.scale(m_scale, m_scale); + transform.translate(-width() / 2, -height() / 2); + + painter.setTransform(transform); + } + + QWidget::paintEvent(event); +} + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +void AppIcon::enterEvent(QEnterEvent *event) +#else +void AppIcon::enterEvent(QEvent *event) +#endif +{ + Q_UNUSED(event) + m_shadowEffect->setBlurRadius(20); + m_shadowEffect->setColor(QColor(0, 0, 0, 60)); +} + +void AppIcon::leaveEvent(QEvent *event) +{ + Q_UNUSED(event) + m_shadowEffect->setBlurRadius(16); + m_shadowEffect->setColor(QColor(0, 0, 0, 40)); +} + +void AppIcon::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) + { + m_isPressed = true; + animateScale(0.95); + + // 启动长按计时器 + m_longPressTimerId = startTimer(LONG_PRESS_DELAY); + } + QWidget::mousePressEvent(event); +} + +void AppIcon::mouseReleaseEvent(QMouseEvent *event) +{ + if (m_longPressTimerId) + { + killTimer(m_longPressTimerId); + m_longPressTimerId = 0; + } + + if (m_isPressed && event->button() == Qt::LeftButton) + { + m_isPressed = false; + animateScale(1.0); + + if (rect().contains(event->pos())) + { + emit clicked(m_data.id); + } + } + QWidget::mouseReleaseEvent(event); +} + +void AppIcon::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == m_longPressTimerId) + { + killTimer(m_longPressTimerId); + m_longPressTimerId = 0; + + if (m_isPressed) + { + emit longPressed(m_data.id); + } + } + QWidget::timerEvent(event); +} + +void AppIcon::animateScale(qreal targetScale, int duration) +{ + QPropertyAnimation *anim = new QPropertyAnimation(this, "scale", this); + anim->setDuration(duration); + anim->setStartValue(m_scale); + anim->setEndValue(targetScale); + anim->setEasingCurve(QEasingCurve::OutCubic); + anim->start(QAbstractAnimation::DeleteWhenStopped); +} diff --git a/widgets/appicon.h b/widgets/appicon.h new file mode 100644 index 0000000..5522e6d --- /dev/null +++ b/widgets/appicon.h @@ -0,0 +1,74 @@ +#ifndef APPICON_H +#define APPICON_H + +#include +#include +#include +#include + +struct AppIconData +{ + QString id; + QString name; + QString nameEn; + QString icon; + QString color; + QStringList gradient; + QString badge; + QString group; // 分组标识,相同group的图标会被框起来 + QString description; + QStringList features; + bool warning = false; +}; + +class AppIcon : public QWidget +{ + Q_OBJECT + Q_PROPERTY(qreal scale READ scale WRITE setScale) + +public: + explicit AppIcon(const AppIconData &data, QWidget *parent = nullptr); + ~AppIcon(); + + QString appId() const { return m_data.id; } + qreal scale() const { return m_scale; } + void setScale(qreal scale); + +signals: + void clicked(const QString &appId); + void longPressed(const QString &appId); + +protected: + void paintEvent(QPaintEvent *event) override; +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + void enterEvent(QEnterEvent *event) override; +#else + void enterEvent(QEvent *event) override; +#endif + void leaveEvent(QEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void timerEvent(QTimerEvent *event) override; + +private: + void setupUI(); + void animateScale(qreal targetScale, int duration = 100); + QString generateGradientStyle() const; + QString getIconSymbol(const QString &id) const; + + AppIconData m_data; + QLabel *m_iconLabel; + QLabel *m_nameLabel; + QLabel *m_badgeLabel; + QGraphicsDropShadowEffect *m_shadowEffect; + + qreal m_scale; + bool m_isPressed; + int m_longPressTimerId; + + static constexpr int ICON_SIZE = 96; + static constexpr int BORDER_RADIUS = 20; + static constexpr int LONG_PRESS_DELAY = 500; +}; + +#endif // APPICON_H diff --git a/widgets/channelstatuswidget.cpp b/widgets/channelstatuswidget.cpp new file mode 100644 index 0000000..ff63a7c --- /dev/null +++ b/widgets/channelstatuswidget.cpp @@ -0,0 +1,252 @@ +#include "channelstatuswidget.h" + +#include +#include +#include +#include + +// ===================================================== +// ChannelStatusWidget 实现 +// ===================================================== + +ChannelStatusWidget::ChannelStatusWidget(ChannelManager::ChannelId channelId, + const QString &channelName, + QWidget *parent, + ChannelStatusWidget::DisplayMode mode) + : QWidget(parent), m_channelId(channelId), m_channelName(channelName), m_currentOperation(ChannelManager::NONE), m_channelManager(ChannelManager::instance()), m_displayMode(mode) +{ + setFixedSize(140, 80); // 紧凑尺寸适配launcher布局 + setStyleSheet("background-color: #34495e; border: 2px solid #2c3e50; " + "border-radius: 8px;"); +} + +void ChannelStatusWidget::updateStatus(ChannelManager::OperationType operation, + const QString &description) +{ + m_currentOperation = operation; + m_description = description; + update(); // 触发重绘 +} + +void ChannelStatusWidget::setDisplayMode(ChannelStatusWidget::DisplayMode mode) +{ + m_displayMode = mode; + update(); // 触发重绘 +} + +void ChannelStatusWidget::paintEvent(QPaintEvent *event) +{ + QWidget::paintEvent(event); + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + QRect rect = this->rect().adjusted(6, 6, -6, -6); + + // 绘制通道连接器 + drawChannelConnector(painter, rect); + + // 绘制通道名称 + if (m_displayMode == ChannelStatusWidget::DETAILED_MODE) + { + // 详细模式:通道名称在顶部 + painter.setPen(QColor("#ecf0f1")); + QFont nameFont("Microsoft YaHei", 9, QFont::Bold); + painter.setFont(nameFont); + painter.drawText(rect.adjusted(0, 0, 0, -50), Qt::AlignCenter, m_channelName); + + // 绘制状态文本 + QString statusText = m_channelManager->getOperationDisplayName(m_currentOperation); + if (!m_description.isEmpty() && m_description.length() < 15) + { + statusText += "\n" + m_description; + } + + QFont statusFont("Microsoft YaHei", 8); + painter.setFont(statusFont); + painter.drawText(rect.adjusted(0, 28, 0, 0), Qt::AlignCenter, statusText); + + // 绘制接线信息 + ChannelManager::ChannelCapability capability = + m_channelManager->getChannelCapability(m_channelId); + QString wiringText = capability.wireColors.join("-"); + painter.setPen(QColor("#bdc3c7")); + QFont wiringFont("Microsoft YaHei", 7); + painter.setFont(wiringFont); + painter.drawText(rect.adjusted(0, 50, 0, 0), Qt::AlignCenter, wiringText); + } + else + { + // 概览模式:通道名称在整个区域居中显示 + painter.setPen(QColor("#ffffff")); + QFont overviewFont("Microsoft YaHei", 10, QFont::Bold); + painter.setFont(overviewFont); + painter.drawText(rect.adjusted(0, 25, 0, 0), Qt::AlignHCenter | Qt::AlignTop, m_channelName); + } +} + +void ChannelStatusWidget::drawChannelConnector(QPainter &painter, const QRect &rect) +{ + // 绘制通道连接器图形,颜色根据状态变化 + QRect connectorRect(rect.center().x() - 25, rect.top() + 5, 50, 20); + + // 根据状态设置连接器颜色 + QColor connectorColor = getStatusColor(); + painter.setBrush(connectorColor); + painter.setPen(QPen(connectorColor.darker(120), 1)); + painter.drawRoundedRect(connectorRect, 4, 4); + + // 绘制接线端子 + ChannelManager::ChannelCapability capability = + m_channelManager->getChannelCapability(m_channelId); + int wireCount = capability.wireColors.size(); + int wireSpacing = connectorRect.width() / (wireCount + 1); + + for (int i = 0; i < wireCount; ++i) + { + QRect wireRect(connectorRect.left() + wireSpacing * (i + 1) - 2, + connectorRect.bottom() - 3, 4, 6); + + // 根据线色设置颜色 + QColor wireColor; + QString colorName = capability.wireColors[i]; + if (colorName == "白") + wireColor = QColor("#f8f9fa"); + else if (colorName == "红") + wireColor = QColor("#e74c3c"); + else if (colorName == "灰") + wireColor = QColor("#95a5a6"); + else if (colorName == "黑") + wireColor = QColor("#1a1a1a"); + else + wireColor = QColor("#bdc3c7"); + + painter.setBrush(wireColor); + painter.setPen(QPen(QColor("#34495e"), 1)); + painter.drawRect(wireRect); + } +} + +QColor ChannelStatusWidget::getStatusColor() const +{ + switch (m_currentOperation) + { + case ChannelManager::NONE: + return QColor("#27ae60"); // 绿色 - 空闲 + + // 输入通道测量操作 - 蓝色系 + case ChannelManager::VOLTAGE_MEASUREMENT: + case ChannelManager::MV_MEASUREMENT: + case ChannelManager::MA_MEASUREMENT: + case ChannelManager::AC_MEASUREMENT: + case ChannelManager::RESISTANCE_MEASUREMENT: + case ChannelManager::FREQUENCY_MEASUREMENT: + case ChannelManager::SWITCH_MEASUREMENT: + case ChannelManager::INSULATION_MEASUREMENT: + return QColor("#3498db"); // 蓝色 - 测量中 + + // 输出通道操作 - 橙色系 + case ChannelManager::VOLTAGE_OUTPUT: + case ChannelManager::MV_OUTPUT: + case ChannelManager::MA_OUTPUT: + case ChannelManager::RESISTANCE_SIMULATION: + return QColor("#f39c12"); // 橙色 - 输出中 + + // 波形输出 - 紫色 + case ChannelManager::WAVEFORM_OUTPUT: + return QColor("#9b59b6"); // 紫色 - 波形输出中 + + default: + return QColor("#95a5a6"); // 灰色 - 未知 + } +} + +// ===================================================== +// ChannelStatusPanel 实现 +// ===================================================== + +ChannelStatusPanel::ChannelStatusPanel(QWidget *parent, + LayoutOrientation orientation) + : QWidget(parent), m_channelManager(ChannelManager::instance()), m_displayMode(ChannelStatusWidget::OVERVIEW_MODE) +{ + setupUI(orientation); + + // 连接 ChannelManager 的信号 + connect(m_channelManager, &ChannelManager::channelStatusChanged, + this, &ChannelStatusPanel::onChannelStatusChanged); +} + +void ChannelStatusPanel::setupUI(LayoutOrientation orientation) +{ + QBoxLayout *layout; + if (orientation == HORIZONTAL) + { + layout = new QHBoxLayout(this); + } + else + { + layout = new QVBoxLayout(this); + } + layout->setContentsMargins(4, 4, 4, 4); + layout->setSpacing(8); + + // 创建四个通道的状态显示 + struct ChannelInfo + { + ChannelManager::ChannelId id; + QString name; + }; + + QList channels = { + {ChannelManager::INPUT_CHANNEL_1, "输入通道1"}, + {ChannelManager::INPUT_CHANNEL_2, "输入通道2"}, + {ChannelManager::OUTPUT_CHANNEL_1, "输出通道1"}, + {ChannelManager::OUTPUT_CHANNEL_2, "输出通道2"}}; + + for (const auto &channel : channels) + { + ChannelStatusWidget *widget = new ChannelStatusWidget( + channel.id, channel.name, this, m_displayMode); + m_channelWidgets[channel.id] = widget; + layout->addWidget(widget); + } + + layout->addStretch(); +} + +void ChannelStatusPanel::setDisplayMode(ChannelStatusWidget::DisplayMode mode) +{ + m_displayMode = mode; + for (auto widget : m_channelWidgets) + { + widget->setDisplayMode(mode); + } +} + +void ChannelStatusPanel::refresh() +{ + for (auto it = m_channelWidgets.constBegin(); + it != m_channelWidgets.constEnd(); ++it) + { + ChannelManager::ChannelId channelId = it.key(); + ChannelStatusWidget *widget = it.value(); + + ChannelManager::OperationType operation = + m_channelManager->getChannelOperation(channelId); + QString description = + m_channelManager->getChannelDescription(channelId); + + widget->updateStatus(operation, description); + } +} + +void ChannelStatusPanel::onChannelStatusChanged( + ChannelManager::ChannelId channel, + ChannelManager::OperationType operation, + const QString &description) +{ + if (m_channelWidgets.contains(channel)) + { + m_channelWidgets[channel]->updateStatus(operation, description); + } +} diff --git a/widgets/channelstatuswidget.h b/widgets/channelstatuswidget.h new file mode 100644 index 0000000..c94e409 --- /dev/null +++ b/widgets/channelstatuswidget.h @@ -0,0 +1,98 @@ +#ifndef CHANNELSTATUSWIDGET_H +#define CHANNELSTATUSWIDGET_H + +#include "../hardware/channelmanager.h" + +#include +#include +#include +#include +#include + +/** + * @brief 通道状态显示组件 + * + * 用于显示单个通道的状态,包括: + * - 通道连接器图形(显示接线端子和线色) + * - 通道名称 + * - 当前操作状态 + * - 接线信息(详细模式下) + */ +class ChannelStatusWidget : public QWidget +{ + Q_OBJECT + +public: + /** + * @brief 显示模式 + */ + enum DisplayMode + { + OVERVIEW_MODE, // 概览模式 - 只显示通道名称和连接器图形 + DETAILED_MODE // 详细模式 - 显示状态文本和接线信息 + }; + + explicit ChannelStatusWidget(ChannelManager::ChannelId channelId, + const QString &channelName, + QWidget *parent = nullptr, + DisplayMode mode = OVERVIEW_MODE); + + void updateStatus(ChannelManager::OperationType operation, + const QString &description); + void setDisplayMode(DisplayMode mode); + DisplayMode displayMode() const { return m_displayMode; } + + ChannelManager::ChannelId channelId() const { return m_channelId; } + ChannelManager::OperationType currentOperation() const { return m_currentOperation; } + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + ChannelManager::ChannelId m_channelId; + QString m_channelName; + ChannelManager::OperationType m_currentOperation; + QString m_description; + ChannelManager *m_channelManager; + DisplayMode m_displayMode; + + void drawChannelConnector(QPainter &painter, const QRect &rect); + QColor getStatusColor() const; +}; + +/** + * @brief 通道状态面板 + * + * 显示所有通道的状态,支持水平和垂直布局 + */ +class ChannelStatusPanel : public QWidget +{ + Q_OBJECT + +public: + enum LayoutOrientation + { + HORIZONTAL, + VERTICAL + }; + + explicit ChannelStatusPanel(QWidget *parent = nullptr, + LayoutOrientation orientation = HORIZONTAL); + + void setDisplayMode(ChannelStatusWidget::DisplayMode mode); + void refresh(); + +public slots: + void onChannelStatusChanged(ChannelManager::ChannelId channel, + ChannelManager::OperationType operation, + const QString &description); + +private: + QMap m_channelWidgets; + ChannelManager *m_channelManager; + ChannelStatusWidget::DisplayMode m_displayMode; + + void setupUI(LayoutOrientation orientation); +}; + +#endif // CHANNELSTATUSWIDGET_H diff --git a/widgets/datamanagementwidget.cpp b/widgets/datamanagementwidget.cpp new file mode 100644 index 0000000..1d09155 --- /dev/null +++ b/widgets/datamanagementwidget.cpp @@ -0,0 +1,1074 @@ +#include "datamanagementwidget.h" +#include "overlaydialogswidget.h" +#include +#include +#include +#include +#include +#include +#include +#include + +// 样式常量定义 +const QString DataManagementWidget::LABEL_STYLE = R"( + QLabel { + color: #333333; + font-size: 14px; + font-weight: 500; + } +)"; + +const QString DataManagementWidget::BUTTON_STYLE = R"( + QPushButton { + background-color: #f5f5f5; + color: #333333; + border: 1px solid #ddd; + border-radius: 6px; + padding: 8px 16px; + font-size: 14px; + font-weight: 500; + min-height: 36px; + } + QPushButton:hover { + background-color: #e8e8e8; + border-color: #ccc; + } + QPushButton:pressed { + background-color: #d0d0d0; + } +)"; + +const QString DataManagementWidget::PRIMARY_BUTTON_STYLE = R"( + QPushButton { + background-color: #2196F3; + color: white; + border: none; + border-radius: 6px; + padding: 8px 16px; + font-size: 14px; + font-weight: 500; + min-height: 36px; + } + QPushButton:hover { + background-color: #1976D2; + } + QPushButton:pressed { + background-color: #1565C0; + } +)"; + +const QString DataManagementWidget::DANGER_BUTTON_STYLE = R"( + QPushButton { + background-color: #f44336; + color: white; + border: none; + border-radius: 6px; + padding: 8px 16px; + font-size: 14px; + font-weight: 500; + min-height: 36px; + } + QPushButton:hover { + background-color: #d32f2f; + } + QPushButton:pressed { + background-color: #c62828; + } +)"; + +const QString DataManagementWidget::INPUT_STYLE = R"( + QLineEdit, QComboBox, QDateEdit { + background-color: white; + border: 1px solid #ddd; + border-radius: 6px; + padding: 8px 12px; + font-size: 14px; + min-height: 36px; + } + QLineEdit:focus, QComboBox:focus, QDateEdit:focus { + border-color: #2196F3; + } + QComboBox::drop-down, QDateEdit::drop-down { + subcontrol-origin: padding; + subcontrol-position: center right; + width: 30px; + border: none; + } + QComboBox::down-arrow, QDateEdit::down-arrow { + image: url(:/icons/arrow_down.svg); + width: 14px; + height: 14px; + } + QCalendarWidget { + background-color: white; + border: 1px solid #ccc; + border-radius: 8px; + } + QCalendarWidget QToolButton { + color: #333; + background-color: transparent; + border: none; + border-radius: 4px; + padding: 8px; + font-size: 14px; + font-weight: 600; + } + QCalendarWidget QToolButton:hover { + background-color: #e3f2fd; + } + QCalendarWidget QMenu { + background-color: white; + border: 1px solid #ccc; + } + QCalendarWidget QMenu::item { + padding: 8px 20px; + } + QCalendarWidget QMenu::item:selected { + background-color: #2196F3; + color: white; + } + QCalendarWidget QSpinBox { + background-color: white; + border: 1px solid #ddd; + border-radius: 4px; + padding: 4px; + font-size: 14px; + } + QCalendarWidget QAbstractItemView { + background-color: white; + selection-background-color: #2196F3; + selection-color: white; + font-size: 14px; + } + QCalendarWidget QAbstractItemView:enabled { + color: #333; + } + QCalendarWidget QAbstractItemView:disabled { + color: #999; + } +)"; + +const QString DataManagementWidget::TABLE_STYLE = R"( + QTableWidget { + background-color: white; + border: 1px solid #ddd; + border-radius: 8px; + gridline-color: #eee; + font-size: 13px; + } + QTableWidget::item { + padding: 8px; + border-bottom: 1px solid #eee; + } + QTableWidget::item:selected { + background-color: #e3f2fd; + color: #333; + } + QHeaderView::section { + background-color: #f5f5f5; + color: #333; + font-weight: 600; + padding: 10px; + border: none; + border-bottom: 2px solid #2196F3; + } +)"; + +const QString DataManagementWidget::GROUP_STYLE = R"( + QGroupBox { + background-color: white; + border: 1px solid #e0e0e0; + border-radius: 8px; + margin-top: 16px; + padding: 16px; + font-size: 14px; + font-weight: 600; + } + QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top left; + left: 16px; + padding: 0 8px; + color: #333; + } +)"; + +DataManagementWidget::DataManagementWidget(QWidget *parent) + : QWidget(parent) +{ + setupUI(); + populateSampleData(); + updateStorageInfo(); +} + +void DataManagementWidget::setupUI() +{ + setStyleSheet("background-color: #f5f5f5;"); + + QVBoxLayout *mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(0); + + // 标题栏 + QWidget *headerWidget = new QWidget; + headerWidget->setFixedHeight(60); + headerWidget->setStyleSheet("background-color: #2196F3;"); + + QHBoxLayout *headerLayout = new QHBoxLayout(headerWidget); + headerLayout->setContentsMargins(16, 0, 16, 0); + + m_backBtn = new QPushButton("← 返回"); + m_backBtn->setStyleSheet( + "QPushButton { background: rgba(255,255,255,0.2); color: white; border: none; " + "border-radius: 6px; padding: 8px 16px; font-size: 14px; }" + "QPushButton:hover { background: rgba(255,255,255,0.3); }"); + m_backBtn->setCursor(Qt::PointingHandCursor); + connect(m_backBtn, &QPushButton::clicked, this, &DataManagementWidget::backRequested); + + m_titleLabel = new QLabel("数据管理"); + m_titleLabel->setStyleSheet("color: white; font-size: 18px; font-weight: bold;"); + m_titleLabel->setAlignment(Qt::AlignCenter); + + headerLayout->addWidget(m_backBtn); + headerLayout->addWidget(m_titleLabel, 1); + + // 占位,保持标题居中 + auto *spacer = new QWidget(this); + spacer->setFixedWidth(80); + headerLayout->addWidget(spacer); + + mainLayout->addWidget(headerWidget); + + // 内容区域 + QScrollArea *scrollArea = new QScrollArea; + scrollArea->setWidgetResizable(true); + scrollArea->setFrameShape(QFrame::NoFrame); + scrollArea->setStyleSheet("QScrollArea { background-color: #f5f5f5; border: none; }"); + + QWidget *contentWidget = new QWidget; + QVBoxLayout *contentLayout = new QVBoxLayout(contentWidget); + contentLayout->setContentsMargins(16, 16, 16, 16); + contentLayout->setSpacing(16); + + // 存储信息面板 + contentLayout->addWidget(createStorageInfoPanel()); + + // 筛选面板 + contentLayout->addWidget(createFilterPanel()); + + // 工具栏 + contentLayout->addWidget(createToolBar()); + + // 数据表格 + contentLayout->addWidget(createDataTable(), 1); + + scrollArea->setWidget(contentWidget); + mainLayout->addWidget(scrollArea, 1); +} + +QWidget *DataManagementWidget::createStorageInfoPanel() +{ + QWidget *panel = new QWidget; + panel->setStyleSheet(R"( + QWidget { + background-color: white; + border-radius: 8px; + } + )"); + panel->setFixedHeight(100); + + QHBoxLayout *layout = new QHBoxLayout(panel); + layout->setContentsMargins(20, 16, 20, 16); + layout->setSpacing(40); + + // 存储使用情况 + QVBoxLayout *storageLayout = new QVBoxLayout; + storageLayout->setSpacing(8); + + QLabel *storageTitle = new QLabel("存储空间"); + storageTitle->setStyleSheet("font-size: 12px; color: #888;"); + storageLayout->addWidget(storageTitle); + + m_storageUsedLabel = new QLabel("0 MB / 0 GB"); + m_storageUsedLabel->setStyleSheet("font-size: 16px; font-weight: 600; color: #333;"); + storageLayout->addWidget(m_storageUsedLabel); + + m_storageProgressBar = new QProgressBar; + m_storageProgressBar->setFixedHeight(6); + m_storageProgressBar->setTextVisible(false); + m_storageProgressBar->setStyleSheet(R"( + QProgressBar { + background-color: #e0e0e0; + border-radius: 3px; + } + QProgressBar::chunk { + background-color: #4CAF50; + border-radius: 3px; + } + )"); + storageLayout->addWidget(m_storageProgressBar); + + layout->addLayout(storageLayout, 1); + + // 记录数量 + QVBoxLayout *recordLayout = new QVBoxLayout; + recordLayout->setSpacing(8); + + QLabel *recordTitle = new QLabel("校准记录"); + recordTitle->setStyleSheet("font-size: 12px; color: #888;"); + recordLayout->addWidget(recordTitle); + + m_recordCountLabel = new QLabel("0 条"); + m_recordCountLabel->setStyleSheet("font-size: 24px; font-weight: 600; color: #2196F3;"); + recordLayout->addWidget(m_recordCountLabel); + + layout->addLayout(recordLayout); + + // 快捷操作 + QVBoxLayout *actionLayout = new QVBoxLayout; + actionLayout->setSpacing(8); + + m_syncBtn = new QPushButton("云端同步"); + m_syncBtn->setStyleSheet(PRIMARY_BUTTON_STYLE); + m_syncBtn->setFixedWidth(100); + connect(m_syncBtn, &QPushButton::clicked, this, &DataManagementWidget::onSyncClicked); + actionLayout->addWidget(m_syncBtn); + + layout->addLayout(actionLayout); + + return panel; +} + +QWidget *DataManagementWidget::createFilterPanel() +{ + QWidget *panel = new QWidget; + panel->setStyleSheet(R"( + QWidget { + background-color: white; + border-radius: 8px; + } + )"); + + QVBoxLayout *layout = new QVBoxLayout(panel); + layout->setContentsMargins(16, 16, 16, 16); + layout->setSpacing(12); + + QLabel *filterTitle = new QLabel("筛选条件"); + filterTitle->setStyleSheet("font-size: 14px; font-weight: 600; color: #333;"); + layout->addWidget(filterTitle); + + QHBoxLayout *filterLayout = new QHBoxLayout; + filterLayout->setSpacing(12); + + // 数据类型 + QVBoxLayout *categoryLayout = new QVBoxLayout; + QLabel *categoryLabel = new QLabel("数据类型"); + categoryLabel->setStyleSheet("font-size: 12px; color: #666;"); + categoryLayout->addWidget(categoryLabel); + + m_categoryCombo = new QComboBox; + m_categoryCombo->setFixedWidth(150); + m_categoryCombo->addItems({"全部", "校准记录", "测量数据", "诊断日志", "系统日志"}); + m_categoryCombo->setCursor(Qt::PointingHandCursor); + + // 设置下拉列表视图 + QListView *listView = new QListView(m_categoryCombo); + listView->setStyleSheet(R"( + QListView { + background-color: white; + border: 1px solid #ccc; + border-radius: 6px; + outline: none; + } + QListView::item { + height: 40px; + padding-left: 12px; + border: none; + } + QListView::item:hover { + background-color: #e3f2fd; + } + QListView::item:selected { + background-color: #2196F3; + color: white; + } + )"); + m_categoryCombo->setView(listView); + + m_categoryCombo->setStyleSheet(R"( + QComboBox { + background-color: white; + border: 1px solid #ddd; + border-radius: 6px; + padding: 8px 12px; + font-size: 14px; + min-height: 36px; + } + QComboBox:hover { + border-color: #2196F3; + } + QComboBox::drop-down { + subcontrol-origin: padding; + subcontrol-position: center right; + width: 30px; + border: none; + } + QComboBox::down-arrow { + image: url(:/icons/arrow_down.svg); + width: 14px; + height: 14px; + } + )"); + + connect(m_categoryCombo, QOverload::of(&QComboBox::currentIndexChanged), + this, &DataManagementWidget::onCategoryChanged); + categoryLayout->addWidget(m_categoryCombo); + filterLayout->addLayout(categoryLayout); + + // 开始日期 + QVBoxLayout *startLayout = new QVBoxLayout; + QLabel *startLabel = new QLabel("开始日期"); + startLabel->setStyleSheet("font-size: 12px; color: #666;"); + startLayout->addWidget(startLabel); + + m_startDateEdit = new QDateEdit; + m_startDateEdit->setFixedWidth(140); + m_startDateEdit->setCalendarPopup(true); + m_startDateEdit->setDate(QDate::currentDate().addMonths(-1)); + m_startDateEdit->setCursor(Qt::PointingHandCursor); + setupDateEditStyle(m_startDateEdit); + startLayout->addWidget(m_startDateEdit); + filterLayout->addLayout(startLayout); + + // 结束日期 + QVBoxLayout *endLayout = new QVBoxLayout; + QLabel *endLabel = new QLabel("结束日期"); + endLabel->setStyleSheet("font-size: 12px; color: #666;"); + endLayout->addWidget(endLabel); + + m_endDateEdit = new QDateEdit; + m_endDateEdit->setFixedWidth(140); + m_endDateEdit->setCalendarPopup(true); + m_endDateEdit->setDate(QDate::currentDate()); + m_endDateEdit->setCursor(Qt::PointingHandCursor); + setupDateEditStyle(m_endDateEdit); + endLayout->addWidget(m_endDateEdit); + filterLayout->addLayout(endLayout); + + // 搜索框 + QVBoxLayout *searchLayout = new QVBoxLayout; + QLabel *searchLabel = new QLabel("关键字搜索"); + searchLabel->setStyleSheet("font-size: 12px; color: #666;"); + searchLayout->addWidget(searchLabel); + + QHBoxLayout *searchInputLayout = new QHBoxLayout; + m_searchEdit = new QLineEdit; + m_searchEdit->setStyleSheet(INPUT_STYLE); + m_searchEdit->setPlaceholderText("输入设备序列号、操作员等"); + searchInputLayout->addWidget(m_searchEdit, 1); + + m_searchBtn = new QPushButton("搜索"); + m_searchBtn->setStyleSheet(PRIMARY_BUTTON_STYLE); + m_searchBtn->setFixedWidth(80); + connect(m_searchBtn, &QPushButton::clicked, this, &DataManagementWidget::onSearchClicked); + searchInputLayout->addWidget(m_searchBtn); + + searchLayout->addLayout(searchInputLayout); + filterLayout->addLayout(searchLayout, 1); + + layout->addLayout(filterLayout); + + return panel; +} + +QWidget *DataManagementWidget::createToolBar() +{ + QWidget *toolbar = new QWidget; + toolbar->setStyleSheet("background-color: transparent;"); + + QHBoxLayout *layout = new QHBoxLayout(toolbar); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(12); + + m_selectAllCheck = new QCheckBox("全选"); + m_selectAllCheck->setStyleSheet("font-size: 14px; color: #666;"); + connect(m_selectAllCheck, &QCheckBox::stateChanged, this, &DataManagementWidget::onSelectAllChanged); + layout->addWidget(m_selectAllCheck); + + layout->addStretch(); + + m_refreshBtn = new QPushButton("刷新"); + m_refreshBtn->setStyleSheet(BUTTON_STYLE); + m_refreshBtn->setFixedWidth(80); + connect(m_refreshBtn, &QPushButton::clicked, this, &DataManagementWidget::onRefreshClicked); + layout->addWidget(m_refreshBtn); + + m_exportBtn = new QPushButton("导出"); + m_exportBtn->setStyleSheet(PRIMARY_BUTTON_STYLE); + m_exportBtn->setFixedWidth(80); + connect(m_exportBtn, &QPushButton::clicked, this, &DataManagementWidget::onExportClicked); + layout->addWidget(m_exportBtn); + + m_backupBtn = new QPushButton("备份"); + m_backupBtn->setStyleSheet(BUTTON_STYLE); + m_backupBtn->setFixedWidth(80); + connect(m_backupBtn, &QPushButton::clicked, this, &DataManagementWidget::onBackupClicked); + layout->addWidget(m_backupBtn); + + m_restoreBtn = new QPushButton("恢复"); + m_restoreBtn->setStyleSheet(BUTTON_STYLE); + m_restoreBtn->setFixedWidth(80); + connect(m_restoreBtn, &QPushButton::clicked, this, &DataManagementWidget::onRestoreClicked); + layout->addWidget(m_restoreBtn); + + m_deleteBtn = new QPushButton("删除选中"); + m_deleteBtn->setStyleSheet(DANGER_BUTTON_STYLE); + m_deleteBtn->setFixedWidth(100); + connect(m_deleteBtn, &QPushButton::clicked, this, &DataManagementWidget::onDeleteClicked); + layout->addWidget(m_deleteBtn); + + m_clearHistoryBtn = new QPushButton("清空历史"); + m_clearHistoryBtn->setStyleSheet(DANGER_BUTTON_STYLE); + m_clearHistoryBtn->setFixedWidth(100); + connect(m_clearHistoryBtn, &QPushButton::clicked, this, &DataManagementWidget::onClearHistoryClicked); + layout->addWidget(m_clearHistoryBtn); + + return toolbar; +} + +QWidget *DataManagementWidget::createDataTable() +{ + QWidget *tableContainer = new QWidget; + tableContainer->setStyleSheet(R"( + QWidget { + background-color: white; + border-radius: 8px; + } + )"); + + QVBoxLayout *layout = new QVBoxLayout(tableContainer); + layout->setContentsMargins(0, 0, 0, 0); + + m_dataTable = new QTableWidget; + m_dataTable->setStyleSheet(TABLE_STYLE); + m_dataTable->setColumnCount(7); + m_dataTable->setHorizontalHeaderLabels({"", "记录ID", "类型", "设备序列号", "操作员", "时间", "状态"}); + + // 设置列宽 + m_dataTable->setColumnWidth(0, 40); // 复选框 + m_dataTable->setColumnWidth(1, 100); // 记录ID + m_dataTable->setColumnWidth(2, 100); // 类型 + m_dataTable->setColumnWidth(3, 150); // 设备序列号 + m_dataTable->setColumnWidth(4, 100); // 操作员 + m_dataTable->setColumnWidth(5, 160); // 时间 + m_dataTable->horizontalHeader()->setStretchLastSection(true); + + m_dataTable->setSelectionBehavior(QAbstractItemView::SelectRows); + m_dataTable->setAlternatingRowColors(true); + m_dataTable->verticalHeader()->setVisible(false); + m_dataTable->setShowGrid(false); + + connect(m_dataTable, &QTableWidget::cellDoubleClicked, + this, &DataManagementWidget::onRecordDoubleClicked); + + layout->addWidget(m_dataTable); + + return tableContainer; +} + +void DataManagementWidget::populateSampleData() +{ + // 示例数据 + struct RecordData + { + QString id; + QString type; + QString serial; + QString op; + QString time; + QString status; + }; + + QList sampleData = { + {"REC-2024-0001", "校准记录", "SN-A12345678", "张工", "2024-01-15 09:30:25", "成功"}, + {"REC-2024-0002", "测量数据", "SN-B87654321", "李工", "2024-01-15 10:15:42", "成功"}, + {"REC-2024-0003", "诊断日志", "SN-A12345678", "系统", "2024-01-15 11:00:00", "完成"}, + {"REC-2024-0004", "校准记录", "SN-C11223344", "王工", "2024-01-14 14:22:18", "成功"}, + {"REC-2024-0005", "测量数据", "SN-A12345678", "张工", "2024-01-14 15:45:33", "异常"}, + {"REC-2024-0006", "系统日志", "-", "系统", "2024-01-14 16:00:00", "信息"}, + {"REC-2024-0007", "校准记录", "SN-D55667788", "李工", "2024-01-13 09:10:05", "成功"}, + {"REC-2024-0008", "测量数据", "SN-B87654321", "王工", "2024-01-13 10:30:15", "成功"}, + {"REC-2024-0009", "诊断日志", "SN-C11223344", "系统", "2024-01-12 08:00:00", "警告"}, + {"REC-2024-0010", "校准记录", "SN-A12345678", "张工", "2024-01-12 11:20:45", "成功"}, + }; + + m_dataTable->setRowCount(sampleData.size()); + + for (int row = 0; row < sampleData.size(); ++row) + { + const auto &data = sampleData[row]; + + // 复选框 + QWidget *checkWidget = new QWidget; + QHBoxLayout *checkLayout = new QHBoxLayout(checkWidget); + checkLayout->setContentsMargins(0, 0, 0, 0); + checkLayout->setAlignment(Qt::AlignCenter); + QCheckBox *checkBox = new QCheckBox; + checkLayout->addWidget(checkBox); + m_dataTable->setCellWidget(row, 0, checkWidget); + + // 数据列 + m_dataTable->setItem(row, 1, new QTableWidgetItem(data.id)); + m_dataTable->setItem(row, 2, new QTableWidgetItem(data.type)); + m_dataTable->setItem(row, 3, new QTableWidgetItem(data.serial)); + m_dataTable->setItem(row, 4, new QTableWidgetItem(data.op)); + m_dataTable->setItem(row, 5, new QTableWidgetItem(data.time)); + + // 状态标签 + QTableWidgetItem *statusItem = new QTableWidgetItem(data.status); + if (data.status == "成功" || data.status == "完成") + { + statusItem->setForeground(QColor("#4CAF50")); + } + else if (data.status == "异常" || data.status == "警告") + { + statusItem->setForeground(QColor("#FF9800")); + } + else + { + statusItem->setForeground(QColor("#2196F3")); + } + m_dataTable->setItem(row, 6, statusItem); + + m_dataTable->setRowHeight(row, 45); + } + + m_recordCountLabel->setText(QString("%1 条").arg(sampleData.size())); +} + +void DataManagementWidget::updateStorageInfo() +{ + // 获取当前存储信息 + QStorageInfo storage = QStorageInfo::root(); + qint64 totalBytes = storage.bytesTotal(); + qint64 usedBytes = totalBytes - storage.bytesAvailable(); + + // 模拟数据占用(实际应该计算数据文件大小) + qint64 dataUsed = 256 * 1024 * 1024; // 假设256MB + + m_storageUsedLabel->setText(QString("%1 / %2") + .arg(formatFileSize(dataUsed)) + .arg(formatFileSize(2LL * 1024 * 1024 * 1024))); // 假设2GB配额 + + int percentage = static_cast((dataUsed * 100) / (2LL * 1024 * 1024 * 1024)); + m_storageProgressBar->setValue(percentage); + + // 根据使用率改变颜色 + if (percentage > 90) + { + m_storageProgressBar->setStyleSheet(R"( + QProgressBar { + background-color: #e0e0e0; + border-radius: 3px; + } + QProgressBar::chunk { + background-color: #f44336; + border-radius: 3px; + } + )"); + } + else if (percentage > 70) + { + m_storageProgressBar->setStyleSheet(R"( + QProgressBar { + background-color: #e0e0e0; + border-radius: 3px; + } + QProgressBar::chunk { + background-color: #FF9800; + border-radius: 3px; + } + )"); + } +} + +QString DataManagementWidget::formatFileSize(qint64 bytes) +{ + const qint64 KB = 1024; + const qint64 MB = KB * 1024; + const qint64 GB = MB * 1024; + + if (bytes >= GB) + { + return QString::number(bytes / static_cast(GB), 'f', 2) + " GB"; + } + else if (bytes >= MB) + { + return QString::number(bytes / static_cast(MB), 'f', 2) + " MB"; + } + else if (bytes >= KB) + { + return QString::number(bytes / static_cast(KB), 'f', 2) + " KB"; + } + else + { + return QString::number(bytes) + " B"; + } +} + +void DataManagementWidget::onSearchClicked() +{ + QString keyword = m_searchEdit->text().trimmed(); + QString category = m_categoryCombo->currentText(); + QDate startDate = m_startDateEdit->date(); + QDate endDate = m_endDateEdit->date(); + + // 这里应该实现实际的搜索逻辑 + QString searchInfo = QString("搜索条件:\n类型: %1\n日期范围: %2 至 %3\n关键字: %4") + .arg(category) + .arg(startDate.toString("yyyy-MM-dd")) + .arg(endDate.toString("yyyy-MM-dd")) + .arg(keyword.isEmpty() ? "(无)" : keyword); + + OverlayDialog::information(window(), "搜索", searchInfo); +} + +void DataManagementWidget::onExportClicked() +{ + // 统计选中的记录 + int selectedCount = 0; + for (int row = 0; row < m_dataTable->rowCount(); ++row) + { + QWidget *widget = m_dataTable->cellWidget(row, 0); + if (widget) + { + QCheckBox *checkBox = widget->findChild(); + if (checkBox && checkBox->isChecked()) + { + selectedCount++; + } + } + } + + if (selectedCount == 0) + { + OverlayDialog::warning(window(), "导出", "请先选择要导出的记录"); + return; + } + + OverlayDialog::question(window(), "确认导出", + QString("确定要导出选中的 %1 条记录吗?").arg(selectedCount), + [this, selectedCount](bool confirmed) + { + if (confirmed) + { + // 这里应该实现实际的导出逻辑 + OverlayDialog::information(window(), "导出成功", + QString("已成功导出 %1 条记录到 USB 设备").arg(selectedCount)); + } + }); +} + +void DataManagementWidget::onDeleteClicked() +{ + // 统计选中的记录 + int selectedCount = 0; + for (int row = 0; row < m_dataTable->rowCount(); ++row) + { + QWidget *widget = m_dataTable->cellWidget(row, 0); + if (widget) + { + QCheckBox *checkBox = widget->findChild(); + if (checkBox && checkBox->isChecked()) + { + selectedCount++; + } + } + } + + if (selectedCount == 0) + { + OverlayDialog::warning(window(), "删除", "请先选择要删除的记录"); + return; + } + + OverlayDialog::question(window(), "确认删除", + QString("确定要删除选中的 %1 条记录吗?\n此操作不可恢复!").arg(selectedCount), + [this, selectedCount](bool confirmed) + { + if (confirmed) + { + // 从后往前删除选中的行 + for (int row = m_dataTable->rowCount() - 1; row >= 0; --row) + { + QWidget *widget = m_dataTable->cellWidget(row, 0); + if (widget) + { + QCheckBox *checkBox = widget->findChild(); + if (checkBox && checkBox->isChecked()) + { + m_dataTable->removeRow(row); + } + } + } + + m_recordCountLabel->setText(QString("%1 条").arg(m_dataTable->rowCount())); + m_selectAllCheck->setChecked(false); + + OverlayDialog::information(window(), "删除成功", + QString("已删除 %1 条记录").arg(selectedCount)); + } + }); +} + +void DataManagementWidget::onRefreshClicked() +{ + // 重新加载数据 + populateSampleData(); + updateStorageInfo(); + m_selectAllCheck->setChecked(false); + + OverlayDialog::information(window(), "刷新", "数据已刷新"); +} + +void DataManagementWidget::onSelectAllChanged(int state) +{ + bool checked = (state == Qt::Checked); + + for (int row = 0; row < m_dataTable->rowCount(); ++row) + { + QWidget *widget = m_dataTable->cellWidget(row, 0); + if (widget) + { + QCheckBox *checkBox = widget->findChild(); + if (checkBox) + { + checkBox->setChecked(checked); + } + } + } +} + +void DataManagementWidget::onRecordDoubleClicked(int row, int column) +{ + Q_UNUSED(column); + + if (row < 0 || row >= m_dataTable->rowCount()) + return; + + QString recordId = m_dataTable->item(row, 1)->text(); + QString type = m_dataTable->item(row, 2)->text(); + QString serial = m_dataTable->item(row, 3)->text(); + QString op = m_dataTable->item(row, 4)->text(); + QString time = m_dataTable->item(row, 5)->text(); + QString status = m_dataTable->item(row, 6)->text(); + + QString details = QString( + "记录详情\n\n" + "记录ID: %1\n" + "类型: %2\n" + "设备序列号: %3\n" + "操作员: %4\n" + "时间: %5\n" + "状态: %6") + .arg(recordId) + .arg(type) + .arg(serial) + .arg(op) + .arg(time) + .arg(status); + + OverlayDialog::information(window(), "记录详情", details); +} + +void DataManagementWidget::onCategoryChanged(int index) +{ + Q_UNUSED(index); + // 切换类型时可以触发自动搜索 + // onSearchClicked(); +} + +void DataManagementWidget::onSyncClicked() +{ + OverlayDialog::question(window(), "云端同步", + "确定要将本地数据同步到云端吗?\n这可能需要几分钟时间。", + [this](bool confirmed) + { + if (confirmed) + { + // 这里应该实现实际的同步逻辑 + OverlayDialog::information(window(), "同步完成", "数据已成功同步到云端"); + } + }); +} + +void DataManagementWidget::onBackupClicked() +{ + OverlayDialog::question(window(), "数据备份", + "确定要备份所有数据吗?\n备份文件将保存到 USB 设备。", + [this](bool confirmed) + { + if (confirmed) + { + // 这里应该实现实际的备份逻辑 + OverlayDialog::information(window(), "备份完成", + "数据已成功备份\n文件: backup_2024-01-15.dat"); + } + }); +} + +void DataManagementWidget::onRestoreClicked() +{ + OverlayDialog::question(window(), "数据恢复", + "确定要从备份文件恢复数据吗?\n当前数据将被覆盖!", + [this](bool confirmed) + { + if (confirmed) + { + // 这里应该实现实际的恢复逻辑 + OverlayDialog::information(window(), "恢复完成", "数据已成功从备份恢复"); + populateSampleData(); + updateStorageInfo(); + } + }); +} + +void DataManagementWidget::onClearHistoryClicked() +{ + OverlayDialog::question(window(), "清空历史", + "确定要清空所有历史数据吗?\n此操作不可恢复!", + [this](bool confirmed) + { + if (confirmed) + { + m_dataTable->setRowCount(0); + m_recordCountLabel->setText("0 条"); + m_selectAllCheck->setChecked(false); + + OverlayDialog::information(window(), "清空完成", "所有历史数据已清空"); + updateStorageInfo(); + } + }); +} + +void DataManagementWidget::setupDateEditStyle(QDateEdit *dateEdit) +{ + dateEdit->setStyleSheet(R"( + QDateEdit { + background-color: white; + border: 1px solid #ddd; + border-radius: 6px; + padding: 8px 12px; + font-size: 14px; + min-height: 36px; + } + QDateEdit:hover { + border-color: #2196F3; + } + QDateEdit::drop-down { + subcontrol-origin: padding; + subcontrol-position: center right; + width: 30px; + border: none; + } + QDateEdit::down-arrow { + image: url(:/icons/arrow_down.svg); + width: 14px; + height: 14px; + } + )"); + + // 获取并样式化日历弹出窗口 + QCalendarWidget *calendar = dateEdit->calendarWidget(); + if (calendar) + { + calendar->setStyleSheet(R"( + QCalendarWidget { + background-color: white; + border: 1px solid #ccc; + border-radius: 8px; + } + QCalendarWidget QWidget#qt_calendar_navigationbar { + background-color: #f5f5f5; + border-top-left-radius: 8px; + border-top-right-radius: 8px; + padding: 8px; + } + QCalendarWidget QToolButton { + color: #333; + background-color: transparent; + border: none; + border-radius: 4px; + padding: 8px 12px; + font-size: 14px; + font-weight: 600; + min-width: 40px; + } + QCalendarWidget QToolButton:hover { + background-color: #e3f2fd; + } + QCalendarWidget QToolButton::menu-indicator { + image: none; + width: 0px; + } + QCalendarWidget QSpinBox { + background-color: white; + border: 1px solid #ddd; + border-radius: 4px; + padding: 4px 8px; + font-size: 14px; + min-width: 60px; + } + QCalendarWidget QMenu { + background-color: white; + border: 1px solid #ccc; + border-radius: 4px; + } + QCalendarWidget QMenu::item { + padding: 10px 20px; + font-size: 14px; + } + QCalendarWidget QMenu::item:selected { + background-color: #2196F3; + color: white; + } + QCalendarWidget QAbstractItemView { + background-color: white; + selection-background-color: #2196F3; + selection-color: white; + font-size: 14px; + outline: none; + } + QCalendarWidget QAbstractItemView:enabled { + color: #333; + } + QCalendarWidget QAbstractItemView:disabled { + color: #bbb; + } + )"); + + // 设置日历格式让日期更大更明显 + QTextCharFormat headerFormat; + headerFormat.setFontPointSize(12); + headerFormat.setForeground(QColor("#666")); + calendar->setHeaderTextFormat(headerFormat); + + QTextCharFormat dayFormat; + dayFormat.setFontPointSize(13); + calendar->setWeekdayTextFormat(Qt::Monday, dayFormat); + calendar->setWeekdayTextFormat(Qt::Tuesday, dayFormat); + calendar->setWeekdayTextFormat(Qt::Wednesday, dayFormat); + calendar->setWeekdayTextFormat(Qt::Thursday, dayFormat); + calendar->setWeekdayTextFormat(Qt::Friday, dayFormat); + + QTextCharFormat weekendFormat; + weekendFormat.setFontPointSize(13); + weekendFormat.setForeground(QColor("#e53935")); + calendar->setWeekdayTextFormat(Qt::Saturday, weekendFormat); + calendar->setWeekdayTextFormat(Qt::Sunday, weekendFormat); + + calendar->setVerticalHeaderFormat(QCalendarWidget::NoVerticalHeader); + calendar->setGridVisible(false); + calendar->setMinimumSize(320, 280); + } +} diff --git a/widgets/datamanagementwidget.h b/widgets/datamanagementwidget.h new file mode 100644 index 0000000..6cb4aa9 --- /dev/null +++ b/widgets/datamanagementwidget.h @@ -0,0 +1,94 @@ +#ifndef DATAMANAGEMENTWIDGET_H +#define DATAMANAGEMENTWIDGET_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief 数据管理页面 + * 提供校准记录、历史数据、数据导出等功能 + */ +class DataManagementWidget : public QWidget +{ + Q_OBJECT + +public: + explicit DataManagementWidget(QWidget *parent = nullptr); + +signals: + void backRequested(); + +private slots: + void onSearchClicked(); + void onExportClicked(); + void onDeleteClicked(); + void onRefreshClicked(); + void onSelectAllChanged(int state); + void onRecordDoubleClicked(int row, int column); + void onCategoryChanged(int index); + void onSyncClicked(); + void onBackupClicked(); + void onRestoreClicked(); + void onClearHistoryClicked(); + +private: + void setupUI(); + QWidget *createFilterPanel(); + QWidget *createDataTable(); + QWidget *createToolBar(); + QWidget *createStorageInfoPanel(); + void populateSampleData(); + void updateStorageInfo(); + QString formatFileSize(qint64 bytes); + void setupDateEditStyle(QDateEdit *dateEdit); + + // 标题栏 + QLabel *m_titleLabel; + QPushButton *m_backBtn; + + // 筛选控件 + QComboBox *m_categoryCombo; + QDateEdit *m_startDateEdit; + QDateEdit *m_endDateEdit; + QLineEdit *m_searchEdit; + QPushButton *m_searchBtn; + + // 数据表格 + QTableWidget *m_dataTable; + QCheckBox *m_selectAllCheck; + + // 工具栏 + QPushButton *m_exportBtn; + QPushButton *m_deleteBtn; + QPushButton *m_refreshBtn; + QPushButton *m_syncBtn; + QPushButton *m_backupBtn; + QPushButton *m_restoreBtn; + QPushButton *m_clearHistoryBtn; + + // 存储信息 + QLabel *m_storageUsedLabel; + QLabel *m_recordCountLabel; + QProgressBar *m_storageProgressBar; + + // 样式常量 + static const QString LABEL_STYLE; + static const QString BUTTON_STYLE; + static const QString PRIMARY_BUTTON_STYLE; + static const QString DANGER_BUTTON_STYLE; + static const QString INPUT_STYLE; + static const QString TABLE_STYLE; + static const QString GROUP_STYLE; +}; + +#endif // DATAMANAGEMENTWIDGET_H diff --git a/widgets/devicestatuswidget.cpp b/widgets/devicestatuswidget.cpp new file mode 100644 index 0000000..2cae197 --- /dev/null +++ b/widgets/devicestatuswidget.cpp @@ -0,0 +1,172 @@ +#include "devicestatuswidget.h" +#include +#include +#include + +DeviceStatusWidget::DeviceStatusWidget(QWidget *parent) + : QWidget(parent) +{ + // 初始化通道状态 + m_channels = { + {"输入通道1", true, "input"}, + {"输入通道2", true, "input"}, + {"输出通道1", true, "output"}, + {"输出通道2", true, "output"}}; + + setupUI(); +} + +DeviceStatusWidget::~DeviceStatusWidget() +{ +} + +void DeviceStatusWidget::setupUI() +{ + setStyleSheet(R"( + DeviceStatusWidget { + background-color: white; + border: 1px solid #E0E0E0; + border-radius: 8px; + margin: 16px; + } + )"); +} + +void DeviceStatusWidget::setChannelStatus(int channel, bool connected) +{ + if (channel >= 0 && channel < m_channels.size()) + { + m_channels[channel].connected = connected; + update(); + } +} + +void DeviceStatusWidget::updateChannels(const QVector &channels) +{ + m_channels = channels; + update(); +} + +void DeviceStatusWidget::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + // 绘制背景 + painter.setPen(Qt::NoPen); + painter.setBrush(QColor("#FFFFFF")); + painter.drawRoundedRect(rect().adjusted(16, 8, -16, -8), 12, 12); + + // 绘制边框 + painter.setPen(QPen(QColor("#E0E0E0"), 1)); + painter.setBrush(Qt::NoBrush); + painter.drawRoundedRect(rect().adjusted(16, 8, -16, -8), 12, 12); + + // 绘制线缆图 + drawCableDiagram(painter); +} + +void DeviceStatusWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + update(); +} + +void DeviceStatusWidget::drawCableDiagram(QPainter &painter) +{ + int contentWidth = width() - 64; + int contentHeight = height() - 32; + int startX = 48; + int startY = 24; + + // 绘制主线缆(曲线) + QPainterPath cablePath; + painter.setPen(QPen(QColor("#F44336"), 4, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); + + int cableY = startY + contentHeight / 3; + cablePath.moveTo(startX, cableY); + + // 绘制波浪形线缆 + int segments = 4; + int segmentWidth = contentWidth / segments; + for (int i = 0; i < segments; ++i) + { + int x1 = startX + i * segmentWidth + segmentWidth / 3; + int y1 = cableY - 20 * (i % 2 == 0 ? 1 : -1); + int x2 = startX + i * segmentWidth + 2 * segmentWidth / 3; + int y2 = cableY + 20 * (i % 2 == 0 ? 1 : -1); + int x3 = startX + (i + 1) * segmentWidth; + int y3 = cableY; + + cablePath.cubicTo(x1, y1, x2, y2, x3, y3); + } + + painter.drawPath(cablePath); + + // 绘制通道指示器 + int channelSpacing = contentWidth / (m_channels.size() + 1); + int indicatorY = startY + contentHeight * 2 / 3; + + for (int i = 0; i < m_channels.size(); ++i) + { + int x = startX + (i + 1) * channelSpacing - 30; + int connectorY = cableY + 10; + + // 绘制连接器 + painter.setPen(QPen(QColor("#4CAF50"), 2)); + painter.setBrush(QColor("#4CAF50")); + + // 连接器主体 + QRect connectorRect(x, connectorY, 60, 35); + painter.drawRoundedRect(connectorRect, 4, 4); + + // 连接器引脚 + painter.setPen(Qt::NoPen); + painter.setBrush(QColor("#388E3C")); + painter.drawRect(x + 5, connectorY + 35, 8, 10); + painter.drawRect(x + 47, connectorY + 35, 8, 10); + + // 绘制状态标签 + QString statusText = m_channels[i].name; + QFont font = painter.font(); + font.setPixelSize(11); + painter.setFont(font); + + QColor statusColor = m_channels[i].connected ? QColor("#4CAF50") : QColor("#9E9E9E"); + painter.setPen(statusColor); + + QRect textRect(x - 10, connectorY + 50, 80, 20); + painter.drawText(textRect, Qt::AlignCenter, statusText); + + // 绘制状态指示点 + int dotX = x + 25; + int dotY = connectorY + 70; + painter.setPen(Qt::NoPen); + painter.setBrush(statusColor); + painter.drawEllipse(QPoint(dotX + 5, dotY + 5), 4, 4); + } +} + +void DeviceStatusWidget::drawChannelIndicator(QPainter &painter, int x, int y, + const QString &label, bool connected) +{ + // 绘制指示器背景 + QColor bgColor = connected ? QColor("#E8F5E9") : QColor("#FAFAFA"); + painter.setPen(Qt::NoPen); + painter.setBrush(bgColor); + painter.drawRoundedRect(x, y, 100, 40, 6, 6); + + // 绘制状态点 + QColor dotColor = connected ? QColor("#4CAF50") : QColor("#9E9E9E"); + painter.setBrush(dotColor); + painter.drawEllipse(x + 10, y + 14, 12, 12); + + // 绘制标签 + painter.setPen(QColor("#424242")); + QFont font = painter.font(); + font.setPixelSize(12); + painter.setFont(font); + painter.drawText(x + 28, y + 8, 68, 24, Qt::AlignVCenter, label); +} diff --git a/widgets/devicestatuswidget.h b/widgets/devicestatuswidget.h new file mode 100644 index 0000000..e1e3765 --- /dev/null +++ b/widgets/devicestatuswidget.h @@ -0,0 +1,40 @@ +#ifndef DEVICESTATUSWIDGET_H +#define DEVICESTATUSWIDGET_H + +#include +#include +#include + +struct ChannelStatus +{ + QString name; + bool connected; + QString type; // input, output +}; + +class DeviceStatusWidget : public QWidget +{ + Q_OBJECT + +public: + explicit DeviceStatusWidget(QWidget *parent = nullptr); + ~DeviceStatusWidget(); + + void setChannelStatus(int channel, bool connected); + void updateChannels(const QVector &channels); + +protected: + void paintEvent(QPaintEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + +private: + void setupUI(); + void drawCableDiagram(QPainter &painter); + void drawChannelIndicator(QPainter &painter, int x, int y, + const QString &label, bool connected); + + QVector m_channels; + QVector m_channelLabels; +}; + +#endif // DEVICESTATUSWIDGET_H diff --git a/widgets/dockbar.cpp b/widgets/dockbar.cpp new file mode 100644 index 0000000..ab1adfc --- /dev/null +++ b/widgets/dockbar.cpp @@ -0,0 +1,142 @@ +#include "dockbar.h" +#include +#include +#include +#include +#include + +DockBar::DockBar(QWidget *parent) + : QWidget(parent) +{ + setupUI(); +} + +DockBar::~DockBar() +{ +} + +void DockBar::setupUI() +{ + setObjectName("dockBar"); +} + +void DockBar::setApps(const QVector &apps) +{ + m_apps = apps; + createDockIcons(); +} + +void DockBar::createDockIcons() +{ + // 清除现有布局 + if (layout()) + { + QLayoutItem *item; + while ((item = layout()->takeAt(0)) != nullptr) + { + if (item->widget()) + { + delete item->widget(); + } + delete item; + } + delete layout(); + } + m_icons.clear(); + + // 创建水平布局 + QHBoxLayout *layout = new QHBoxLayout(this); + layout->setContentsMargins(30, 8, 30, 8); + layout->setSpacing(20); + + layout->addStretch(); + + // 创建Dock图标 + for (const auto &appData : m_apps) + { + // 创建图标容器 + QWidget *iconWidget = new QWidget(this); + iconWidget->setFixedSize(60, 75); + iconWidget->setCursor(Qt::PointingHandCursor); + + QVBoxLayout *iconLayout = new QVBoxLayout(iconWidget); + iconLayout->setContentsMargins(0, 0, 0, 0); + iconLayout->setSpacing(2); + + // 图标按钮 - 触摸屏优化尺寸 + QPushButton *btn = new QPushButton(iconWidget); + btn->setFixedSize(52, 52); + btn->setProperty("appId", appData.id); + + QString gradientStart = appData.gradient.size() > 0 ? appData.gradient[0] : appData.color; + QString gradientEnd = appData.gradient.size() > 1 ? appData.gradient[1] : appData.color; + + // 获取图标符号 + QString iconText = getIconSymbol(appData.id); + btn->setText(iconText); + + btn->setStyleSheet(QString(R"( + QPushButton { + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 %1, stop:1 %2); + border-radius: 15px; + font-size: 28px; + color: white; + border: none; + } + QPushButton:hover { + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 %1, stop:1 %1); + } + QPushButton:pressed { + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 %2, stop:1 %2); + } + )") + .arg(gradientStart, gradientEnd)); + + connect(btn, &QPushButton::clicked, this, [this, appData]() + { emit appClicked(appData.id); }); + + iconLayout->addWidget(btn, 0, Qt::AlignCenter); + + // 名称标签 - 深色文字配合白色背景 + QLabel *nameLabel = new QLabel(appData.name, iconWidget); + nameLabel->setAlignment(Qt::AlignCenter); + nameLabel->setStyleSheet("font-size: 11px; color: #555; background: transparent; font-weight: 500;"); + iconLayout->addWidget(nameLabel, 0, Qt::AlignCenter); + + m_icons.append(iconWidget); + layout->addWidget(iconWidget); + } + + layout->addStretch(); +} + +QString DockBar::getIconSymbol(const QString &id) const +{ + static QMap iconMap = { + {"dock_sop", "📋"}, + {"dock_rcp63", "🌐"}, + {"dock_network", "🌐"}, + {"dock_settings", "⚙️"}}; + return iconMap.value(id, "📱"); +} + +void DockBar::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + // Android风格Dock背景 - 半透明毛玻璃,增加高度以容纳图标 + QRect bgRect = rect().adjusted(110, 5, -110, -5); + + // 半透明白色背景 + painter.setPen(Qt::NoPen); + painter.setBrush(QColor(255, 255, 255, 200)); + painter.drawRoundedRect(bgRect, 30, 30); + + // 细边框 + painter.setPen(QPen(QColor(255, 255, 255, 100), 1)); + painter.setBrush(Qt::NoBrush); + painter.drawRoundedRect(bgRect, 30, 30); +} diff --git a/widgets/dockbar.h b/widgets/dockbar.h new file mode 100644 index 0000000..6c4bc3e --- /dev/null +++ b/widgets/dockbar.h @@ -0,0 +1,34 @@ +#ifndef DOCKBAR_H +#define DOCKBAR_H + +#include +#include +#include +#include "appicon.h" + +class DockBar : public QWidget +{ + Q_OBJECT + +public: + explicit DockBar(QWidget *parent = nullptr); + ~DockBar(); + + void setApps(const QVector &apps); + +signals: + void appClicked(const QString &appId); + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + void setupUI(); + void createDockIcons(); + QString getIconSymbol(const QString &id) const; + + QVector m_apps; + QVector m_icons; +}; + +#endif // DOCKBAR_H diff --git a/widgets/dualchannelwidget.cpp b/widgets/dualchannelwidget.cpp new file mode 100644 index 0000000..aa6dd0f --- /dev/null +++ b/widgets/dualchannelwidget.cpp @@ -0,0 +1,533 @@ +#include "dualchannelwidget.h" +#include +#include +#include +#include +#include +#include + +static const char *GROUP_STYLE = R"( + QGroupBox { + background-color: white; + border: 1px solid #e0e0e0; + border-radius: 12px; + margin-top: 8px; + padding: 16px; + font-size: 14px; + font-weight: 600; + } + QGroupBox::title { + subcontrol-origin: margin; + left: 16px; + padding: 0 8px; + color: #333; + } +)"; + +static const char *BUTTON_STYLE = R"( + QPushButton { + background-color: #2196F3; + color: white; + border: none; + border-radius: 8px; + padding: 12px 32px; + font-size: 14px; + font-weight: 600; + } + QPushButton:hover { + background-color: #1976D2; + } + QPushButton:pressed { + background-color: #1565C0; + } + QPushButton:disabled { + background-color: #BDBDBD; + } +)"; + +static const char *STOP_BUTTON_STYLE = R"( + QPushButton { + background-color: #f44336; + color: white; + border: none; + border-radius: 8px; + padding: 12px 32px; + font-size: 14px; + font-weight: 600; + } + QPushButton:hover { + background-color: #d32f2f; + } + QPushButton:pressed { + background-color: #c62828; + } + QPushButton:disabled { + background-color: #BDBDBD; + } +)"; + +static const char *VALUE_DISPLAY_STYLE = R"( + QLabel { + background-color: #1a1a2e; + color: #00ff88; + border: 1px solid #333; + border-radius: 8px; + padding: 16px; + font-size: 28px; + font-weight: bold; + font-family: 'Courier New', monospace; + min-width: 200px; + } +)"; + +DualChannelWidget::DualChannelWidget(QWidget *parent) + : QWidget(parent), m_isOutputting(false) +{ + setupUI(); + + m_measureTimer = new QTimer(this); + connect(m_measureTimer, &QTimer::timeout, this, &DualChannelWidget::updateMeasurement); +} + +void DualChannelWidget::setupUI() +{ + QVBoxLayout *mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(0); + + // 标题栏 + mainLayout->addWidget(createTitleBar()); + + // 内容区域 + QScrollArea *scrollArea = new QScrollArea; + scrollArea->setWidgetResizable(true); + scrollArea->setFrameShape(QFrame::NoFrame); + scrollArea->setStyleSheet("QScrollArea { background-color: #f5f5f5; }"); + + QWidget *contentWidget = new QWidget; + contentWidget->setStyleSheet("background-color: #f5f5f5;"); + QVBoxLayout *contentLayout = new QVBoxLayout(contentWidget); + contentLayout->setContentsMargins(20, 20, 20, 20); + contentLayout->setSpacing(16); + + // 双通道输出面板 + QHBoxLayout *channelsLayout = new QHBoxLayout; + channelsLayout->setSpacing(16); + channelsLayout->addWidget(createChannel1Panel()); + channelsLayout->addWidget(createChannel2Panel()); + contentLayout->addLayout(channelsLayout); + + // 测量显示面板 + contentLayout->addWidget(createMeasurementPanel()); + + // 控制面板 + contentLayout->addWidget(createControlPanel()); + + contentLayout->addStretch(); + + scrollArea->setWidget(contentWidget); + mainLayout->addWidget(scrollArea, 1); +} + +QWidget *DualChannelWidget::createTitleBar() +{ + QWidget *titleBar = new QWidget; + titleBar->setFixedHeight(60); + titleBar->setStyleSheet("background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #1976D2, stop:1 #1565C0);"); + + QHBoxLayout *layout = new QHBoxLayout(titleBar); + layout->setContentsMargins(16, 0, 16, 0); + + // 返回按钮 + m_backBtn = new QPushButton("← 返回"); + m_backBtn->setCursor(Qt::PointingHandCursor); + m_backBtn->setStyleSheet(R"( + QPushButton { + background: rgba(255,255,255,0.2); + color: white; + border: none; + border-radius: 6px; + padding: 8px 16px; + font-size: 14px; + } + QPushButton:hover { + background: rgba(255,255,255,0.3); + } + )"); + connect(m_backBtn, &QPushButton::clicked, this, &DualChannelWidget::backRequested); + layout->addWidget(m_backBtn); + + // 标题(居中) + m_titleLabel = new QLabel("双通道"); + m_titleLabel->setStyleSheet("color: white; font-size: 18px; font-weight: bold;"); + m_titleLabel->setAlignment(Qt::AlignCenter); + layout->addWidget(m_titleLabel, 1); + + // 右侧占位,保持标题居中 + QWidget *spacer = new QWidget; + spacer->setFixedWidth(m_backBtn->sizeHint().width()); + layout->addWidget(spacer); + + return titleBar; +} + +QWidget *DualChannelWidget::createChannel1Panel() +{ + QGroupBox *group = new QGroupBox("通道1 - 输出"); + group->setStyleSheet(GROUP_STYLE); + + QVBoxLayout *layout = new QVBoxLayout(group); + layout->setContentsMargins(16, 24, 16, 16); + layout->setSpacing(16); + + // 启用复选框 + m_ch1EnableCheck = new QCheckBox("启用通道1"); + m_ch1EnableCheck->setChecked(true); + m_ch1EnableCheck->setStyleSheet("font-weight: 600; color: #00aa55;"); + layout->addWidget(m_ch1EnableCheck); + + // 信号类型选择 + QHBoxLayout *typeLayout = new QHBoxLayout; + typeLayout->addWidget(new QLabel("信号类型:")); + m_ch1TypeCombo = new QComboBox; + m_ch1TypeCombo->addItems({"直流电压 (V)", "直流电流 (mA)", "电阻 (Ω)", "频率 (Hz)"}); + m_ch1TypeCombo->setStyleSheet(R"( + QComboBox { + background: white; + border: 1px solid #ddd; + border-radius: 6px; + padding: 8px 12px; + min-width: 150px; + } + )"); + connect(m_ch1TypeCombo, QOverload::of(&QComboBox::currentIndexChanged), + this, [this](int) + { onChannel1OutputChanged(); }); + typeLayout->addWidget(m_ch1TypeCombo); + typeLayout->addStretch(); + layout->addLayout(typeLayout); + + // 输出值设置 + QHBoxLayout *valueLayout = new QHBoxLayout; + valueLayout->addWidget(new QLabel("输出值:")); + m_ch1ValueSpin = new QDoubleSpinBox; + m_ch1ValueSpin->setRange(-100, 100); + m_ch1ValueSpin->setValue(5.0); + m_ch1ValueSpin->setDecimals(4); + m_ch1ValueSpin->setSuffix(" V"); + m_ch1ValueSpin->setStyleSheet(R"( + QDoubleSpinBox { + padding: 8px 12px; + border: 1px solid #ddd; + border-radius: 6px; + background: white; + font-size: 16px; + font-weight: bold; + min-width: 150px; + } + QDoubleSpinBox::up-button, QDoubleSpinBox::down-button { + subcontrol-origin: border; + width: 24px; + border-left: 1px solid #ddd; + background: #f5f5f5; + } + QDoubleSpinBox::up-button { subcontrol-position: top right; border-top-right-radius: 5px; } + QDoubleSpinBox::down-button { subcontrol-position: bottom right; border-bottom-right-radius: 5px; } + QDoubleSpinBox::up-button:hover, QDoubleSpinBox::down-button:hover { background: #e0e0e0; } + QDoubleSpinBox::up-arrow { image: url(:/icons/arrow_up.svg); width: 12px; height: 12px; } + QDoubleSpinBox::down-arrow { image: url(:/icons/arrow_down.svg); width: 12px; height: 12px; } + )"); + connect(m_ch1ValueSpin, QOverload::of(&QDoubleSpinBox::valueChanged), + this, [this](double) + { onChannel1OutputChanged(); }); + valueLayout->addWidget(m_ch1ValueSpin); + valueLayout->addStretch(); + layout->addLayout(valueLayout); + + // 实际测量值显示 + QLabel *measureTitle = new QLabel("实际测量:"); + measureTitle->setStyleSheet("color: #666;"); + layout->addWidget(measureTitle); + + m_ch1MeasureLabel = new QLabel("-- V"); + m_ch1MeasureLabel->setStyleSheet(VALUE_DISPLAY_STYLE); + m_ch1MeasureLabel->setAlignment(Qt::AlignCenter); + layout->addWidget(m_ch1MeasureLabel); + + layout->addStretch(); + + return group; +} + +QWidget *DualChannelWidget::createChannel2Panel() +{ + QGroupBox *group = new QGroupBox("通道2 - 输出"); + group->setStyleSheet(GROUP_STYLE); + + QVBoxLayout *layout = new QVBoxLayout(group); + layout->setContentsMargins(16, 24, 16, 16); + layout->setSpacing(16); + + // 启用复选框 + m_ch2EnableCheck = new QCheckBox("启用通道2"); + m_ch2EnableCheck->setChecked(true); + m_ch2EnableCheck->setStyleSheet("font-weight: 600; color: #d32f2f;"); + layout->addWidget(m_ch2EnableCheck); + + // 信号类型选择 + QHBoxLayout *typeLayout = new QHBoxLayout; + typeLayout->addWidget(new QLabel("信号类型:")); + m_ch2TypeCombo = new QComboBox; + m_ch2TypeCombo->addItems({"直流电压 (V)", "直流电流 (mA)", "电阻 (Ω)", "频率 (Hz)"}); + m_ch2TypeCombo->setStyleSheet(R"( + QComboBox { + background: white; + border: 1px solid #ddd; + border-radius: 6px; + padding: 8px 12px; + min-width: 150px; + } + )"); + connect(m_ch2TypeCombo, QOverload::of(&QComboBox::currentIndexChanged), + this, [this](int) + { onChannel2OutputChanged(); }); + typeLayout->addWidget(m_ch2TypeCombo); + typeLayout->addStretch(); + layout->addLayout(typeLayout); + + // 输出值设置 + QHBoxLayout *valueLayout = new QHBoxLayout; + valueLayout->addWidget(new QLabel("输出值:")); + m_ch2ValueSpin = new QDoubleSpinBox; + m_ch2ValueSpin->setRange(-100, 100); + m_ch2ValueSpin->setValue(10.0); + m_ch2ValueSpin->setDecimals(4); + m_ch2ValueSpin->setSuffix(" V"); + m_ch2ValueSpin->setStyleSheet(R"( + QDoubleSpinBox { + padding: 8px 12px; + border: 1px solid #ddd; + border-radius: 6px; + background: white; + font-size: 16px; + font-weight: bold; + min-width: 150px; + } + QDoubleSpinBox::up-button, QDoubleSpinBox::down-button { + subcontrol-origin: border; + width: 24px; + border-left: 1px solid #ddd; + background: #f5f5f5; + } + QDoubleSpinBox::up-button { subcontrol-position: top right; border-top-right-radius: 5px; } + QDoubleSpinBox::down-button { subcontrol-position: bottom right; border-bottom-right-radius: 5px; } + QDoubleSpinBox::up-button:hover, QDoubleSpinBox::down-button:hover { background: #e0e0e0; } + QDoubleSpinBox::up-arrow { image: url(:/icons/arrow_up.svg); width: 12px; height: 12px; } + QDoubleSpinBox::down-arrow { image: url(:/icons/arrow_down.svg); width: 12px; height: 12px; } + )"); + connect(m_ch2ValueSpin, QOverload::of(&QDoubleSpinBox::valueChanged), + this, [this](double) + { onChannel2OutputChanged(); }); + valueLayout->addWidget(m_ch2ValueSpin); + valueLayout->addStretch(); + layout->addLayout(valueLayout); + + // 实际测量值显示 + QLabel *measureTitle = new QLabel("实际测量:"); + measureTitle->setStyleSheet("color: #666;"); + layout->addWidget(measureTitle); + + m_ch2MeasureLabel = new QLabel("-- V"); + m_ch2MeasureLabel->setStyleSheet(R"( + QLabel { + background-color: #1a1a2e; + color: #ff6b6b; + border: 1px solid #333; + border-radius: 8px; + padding: 16px; + font-size: 28px; + font-weight: bold; + font-family: 'Courier New', monospace; + min-width: 200px; + } + )"); + m_ch2MeasureLabel->setAlignment(Qt::AlignCenter); + layout->addWidget(m_ch2MeasureLabel); + + layout->addStretch(); + + return group; +} + +QWidget *DualChannelWidget::createMeasurementPanel() +{ + QGroupBox *group = new QGroupBox("采集输入"); + group->setStyleSheet(GROUP_STYLE); + + QHBoxLayout *layout = new QHBoxLayout(group); + layout->setContentsMargins(16, 24, 16, 16); + layout->setSpacing(32); + + // 通道1输入 + QVBoxLayout *ch1Layout = new QVBoxLayout; + QLabel *ch1Title = new QLabel("通道1 采集"); + ch1Title->setStyleSheet("font-weight: 600; color: #00aa55;"); + ch1Layout->addWidget(ch1Title); + m_ch1InputLabel = new QLabel("-- V"); + m_ch1InputLabel->setStyleSheet(R"( + QLabel { + background-color: #e8f5e9; + color: #2e7d32; + border: 2px solid #4caf50; + border-radius: 8px; + padding: 12px 24px; + font-size: 24px; + font-weight: bold; + font-family: monospace; + } + )"); + m_ch1InputLabel->setAlignment(Qt::AlignCenter); + ch1Layout->addWidget(m_ch1InputLabel); + layout->addLayout(ch1Layout); + + // 通道2输入 + QVBoxLayout *ch2Layout = new QVBoxLayout; + QLabel *ch2Title = new QLabel("通道2 采集"); + ch2Title->setStyleSheet("font-weight: 600; color: #d32f2f;"); + ch2Layout->addWidget(ch2Title); + m_ch2InputLabel = new QLabel("-- V"); + m_ch2InputLabel->setStyleSheet(R"( + QLabel { + background-color: #ffebee; + color: #c62828; + border: 2px solid #f44336; + border-radius: 8px; + padding: 12px 24px; + font-size: 24px; + font-weight: bold; + font-family: monospace; + } + )"); + m_ch2InputLabel->setAlignment(Qt::AlignCenter); + ch2Layout->addWidget(m_ch2InputLabel); + layout->addLayout(ch2Layout); + + layout->addStretch(); + + return group; +} + +QWidget *DualChannelWidget::createControlPanel() +{ + QGroupBox *group = new QGroupBox("控制"); + group->setStyleSheet(GROUP_STYLE); + + QHBoxLayout *layout = new QHBoxLayout(group); + layout->setContentsMargins(16, 24, 16, 16); + layout->setSpacing(16); + + // 同步控制 + m_syncCheck = new QCheckBox("同步输出(两通道同步变化)"); + m_syncCheck->setStyleSheet("font-weight: 600;"); + connect(m_syncCheck, &QCheckBox::toggled, this, &DualChannelWidget::onSyncToggled); + layout->addWidget(m_syncCheck); + + layout->addStretch(); + + // 开始按钮 + m_startBtn = new QPushButton("开始输出"); + m_startBtn->setStyleSheet(BUTTON_STYLE); + m_startBtn->setCursor(Qt::PointingHandCursor); + connect(m_startBtn, &QPushButton::clicked, this, &DualChannelWidget::onStartOutput); + layout->addWidget(m_startBtn); + + // 停止按钮 + m_stopBtn = new QPushButton("停止输出"); + m_stopBtn->setStyleSheet(STOP_BUTTON_STYLE); + m_stopBtn->setCursor(Qt::PointingHandCursor); + m_stopBtn->setEnabled(false); + connect(m_stopBtn, &QPushButton::clicked, this, &DualChannelWidget::onStopOutput); + layout->addWidget(m_stopBtn); + + return group; +} + +void DualChannelWidget::onChannel1OutputChanged() +{ + QStringList suffixes = {" V", " mA", " Ω", " Hz"}; + m_ch1ValueSpin->setSuffix(suffixes[m_ch1TypeCombo->currentIndex()]); +} + +void DualChannelWidget::onChannel2OutputChanged() +{ + QStringList suffixes = {" V", " mA", " Ω", " Hz"}; + m_ch2ValueSpin->setSuffix(suffixes[m_ch2TypeCombo->currentIndex()]); +} + +void DualChannelWidget::onSyncToggled(bool checked) +{ + if (checked) + { + // 同步通道2到通道1的设置 + m_ch2TypeCombo->setCurrentIndex(m_ch1TypeCombo->currentIndex()); + m_ch2ValueSpin->setValue(m_ch1ValueSpin->value()); + } +} + +void DualChannelWidget::onStartOutput() +{ + m_isOutputting = true; + m_startBtn->setEnabled(false); + m_stopBtn->setEnabled(true); + m_measureTimer->start(100); +} + +void DualChannelWidget::onStopOutput() +{ + m_isOutputting = false; + m_measureTimer->stop(); + m_startBtn->setEnabled(true); + m_stopBtn->setEnabled(false); + + m_ch1MeasureLabel->setText("-- V"); + m_ch2MeasureLabel->setText("-- V"); + m_ch1InputLabel->setText("-- V"); + m_ch2InputLabel->setText("-- V"); +} + +void DualChannelWidget::updateMeasurement() +{ + static std::random_device rd; + static std::mt19937 gen(rd()); + static std::normal_distribution<> noise(0, 0.001); + + QStringList units = {"V", "mA", "Ω", "Hz"}; + + if (m_ch1EnableCheck->isChecked()) + { + double value = m_ch1ValueSpin->value() + noise(gen); + m_ch1MeasureLabel->setText(QString("%1 %2") + .arg(value, 0, 'f', 4) + .arg(units[m_ch1TypeCombo->currentIndex()])); + + // 模拟采集输入 + double inputValue = value * 0.98 + noise(gen) * 10; + m_ch1InputLabel->setText(QString("%1 %2") + .arg(inputValue, 0, 'f', 4) + .arg(units[m_ch1TypeCombo->currentIndex()])); + } + + if (m_ch2EnableCheck->isChecked()) + { + double value = m_ch2ValueSpin->value() + noise(gen); + m_ch2MeasureLabel->setText(QString("%1 %2") + .arg(value, 0, 'f', 4) + .arg(units[m_ch2TypeCombo->currentIndex()])); + + // 模拟采集输入 + double inputValue = value * 0.98 + noise(gen) * 10; + m_ch2InputLabel->setText(QString("%1 %2") + .arg(inputValue, 0, 'f', 4) + .arg(units[m_ch2TypeCombo->currentIndex()])); + } +} diff --git a/widgets/dualchannelwidget.h b/widgets/dualchannelwidget.h new file mode 100644 index 0000000..a458818 --- /dev/null +++ b/widgets/dualchannelwidget.h @@ -0,0 +1,78 @@ +#ifndef DUALCHANNELWIDGET_H +#define DUALCHANNELWIDGET_H + +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief 双通道页面 + * + * 功能说明: + * - 同时输出2路信号 + * - 同时采集2路信号 + */ +class DualChannelWidget : public QWidget +{ + Q_OBJECT + +public: + explicit DualChannelWidget(QWidget *parent = nullptr); + ~DualChannelWidget() = default; + +signals: + void backRequested(); + +private slots: + void onChannel1OutputChanged(); + void onChannel2OutputChanged(); + void onSyncToggled(bool checked); + void onStartOutput(); + void onStopOutput(); + void updateMeasurement(); + +private: + void setupUI(); + QWidget *createTitleBar(); + QWidget *createChannel1Panel(); + QWidget *createChannel2Panel(); + QWidget *createMeasurementPanel(); + QWidget *createControlPanel(); + + // 标题栏 + QPushButton *m_backBtn; + QLabel *m_titleLabel; + + // 通道1控制 + QComboBox *m_ch1TypeCombo; + QDoubleSpinBox *m_ch1ValueSpin; + QLabel *m_ch1MeasureLabel; + QCheckBox *m_ch1EnableCheck; + + // 通道2控制 + QComboBox *m_ch2TypeCombo; + QDoubleSpinBox *m_ch2ValueSpin; + QLabel *m_ch2MeasureLabel; + QCheckBox *m_ch2EnableCheck; + + // 同步控制 + QCheckBox *m_syncCheck; + + // 控制按钮 + QPushButton *m_startBtn; + QPushButton *m_stopBtn; + + // 测量显示 + QLabel *m_ch1InputLabel; + QLabel *m_ch2InputLabel; + + // 状态 + bool m_isOutputting; + QTimer *m_measureTimer; +}; + +#endif // DUALCHANNELWIDGET_H diff --git a/widgets/launcherpage.cpp b/widgets/launcherpage.cpp new file mode 100644 index 0000000..46cdeaf --- /dev/null +++ b/widgets/launcherpage.cpp @@ -0,0 +1,207 @@ +#include "launcherpage.h" +#include +#include +#include +#include +#include +#include + +LauncherPage::LauncherPage(const PageConfig &config, QWidget *parent) + : QWidget(parent), m_config(config), m_columns(4), m_rows(3), m_horizontalGap(20), m_verticalGap(18), m_horizontalPadding(30), m_verticalPadding(20) +{ + setObjectName("launcherPage_" + config.id); + setupUI(); +} + +LauncherPage::~LauncherPage() +{ +} + +void LauncherPage::setupUI() +{ + // 设置背景透明,同时确保可以绑定paintEvent + setAttribute(Qt::WA_StyledBackground, true); + setStyleSheet("background: transparent;"); + + qDebug() << "=== LauncherPage setupUI ==="; + qDebug() << "Page ID:" << m_config.id; + qDebug() << "Apps count:" << m_config.apps.size(); + + // 创建图标(不使用布局,使用绝对定位) + for (int i = 0; i < m_config.apps.size(); ++i) + { + const auto &appData = m_config.apps[i]; + qDebug() << "Creating icon for:" << appData.name << "(" << appData.id << ")" << "group:" << appData.group; + AppIcon *icon = new AppIcon(appData, this); + icon->setFixedSize(190, 180); + connect(icon, &AppIcon::clicked, this, &LauncherPage::appClicked); + connect(icon, &AppIcon::longPressed, this, &LauncherPage::appLongPressed); + m_icons.append(icon); + + // 收集分组信息 + if (!appData.group.isEmpty()) + { + if (!m_groups.contains(appData.group)) + { + GroupInfo info; + info.groupId = appData.group; + m_groups[appData.group] = info; + } + m_groups[appData.group].iconIndices.append(i); + } + } + + qDebug() << "Total icons created:" << m_icons.size(); + qDebug() << "Groups found:" << m_groups.keys(); +} + +void LauncherPage::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + layoutIcons(); +} + +void LauncherPage::layoutIcons() +{ + if (m_icons.isEmpty()) + { + qDebug() << "layoutIcons: No icons to layout!"; + return; + } + + int w = width(); + int h = height(); + + qDebug() << "=== layoutIcons ==="; + qDebug() << "Page ID:" << m_config.id; + qDebug() << "Widget size:" << w << "x" << h; + qDebug() << "Icons count:" << m_icons.size(); + + // Android风格:图标均匀分布 + int iconWidth = 190; + int iconHeight = 180; + + // 动态计算可以容纳的行数 + int minVerticalSpacing = 12; + int maxPossibleRows = (h + minVerticalSpacing) / (iconHeight + minVerticalSpacing); + int actualRows = qMin(maxPossibleRows, m_rows); + actualRows = qMax(actualRows, 1); // 至少显示1行 + + qDebug() << "Max possible rows:" << maxPossibleRows << "Using rows:" << actualRows; + + // 计算间距,使图标均匀分布 + int horizontalSpacing = (w - m_columns * iconWidth) / (m_columns + 1); + int verticalSpacing = (h - actualRows * iconHeight) / (actualRows + 1); + + // 确保最小间距 + horizontalSpacing = qMax(horizontalSpacing, 10); + verticalSpacing = qMax(verticalSpacing, 12); + + qDebug() << "Spacing - H:" << horizontalSpacing << "V:" << verticalSpacing; + + // 重新计算起始位置使其居中 + int startX = (w - (m_columns * iconWidth + (m_columns - 1) * horizontalSpacing)) / 2; + int startY = (h - (actualRows * iconHeight + (actualRows - 1) * verticalSpacing)) / 2; + + qDebug() << "Start position - X:" << startX << "Y:" << startY; + + // 布局图标,同时记录位置用于分组框计算 + QMap iconRects; + int index = 0; + for (AppIcon *icon : m_icons) + { + int row = index / m_columns; + int col = index % m_columns; + + if (row < actualRows) + { + int x = startX + col * (iconWidth + horizontalSpacing); + int y = startY + row * (iconHeight + verticalSpacing); + qDebug() << " Icon" << index << "at" << x << "," << y; + icon->move(x, y); + icon->show(); + // 记录计算出的位置 + iconRects[index] = QRect(x, y, iconWidth, iconHeight); + } + else + { + qDebug() << " Icon" << index << "hidden (too many rows)"; + icon->hide(); + } + index++; + } + + qDebug() << "layoutIcons done, visible icons:" << qMin(index, m_rows * m_columns); + + // 更新分组矩形,使用计算出的位置 + updateGroupRects(iconRects); +} + +void LauncherPage::updateGroupRects(const QMap &iconRects) +{ + // 为每个分组计算边界矩形 + for (auto it = m_groups.begin(); it != m_groups.end(); ++it) + { + GroupInfo &group = it.value(); + if (group.iconIndices.isEmpty()) + continue; + + QRect boundingRect; + bool first = true; + + for (int idx : group.iconIndices) + { + if (iconRects.contains(idx)) + { + QRect iconRect = iconRects[idx]; + qDebug() << " Group icon" << idx << "rect:" << iconRect; + if (first) + { + boundingRect = iconRect; + first = false; + } + else + { + boundingRect = boundingRect.united(iconRect); + } + } + } + + // 扩展边界矩形,添加内边距 + int padding = 3; + group.boundingRect = boundingRect.adjusted(-padding, -padding, padding, padding); + qDebug() << "Group" << group.groupId << "bounding rect:" << group.boundingRect; + } + + // 触发重绘 + update(); +} + +void LauncherPage::paintEvent(QPaintEvent *event) +{ + QWidget::paintEvent(event); + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + // 绘制分组背景框 + for (const GroupInfo &group : m_groups) + { + qDebug() << "Drawing group:" << group.groupId << "rect:" << group.boundingRect; + if (group.boundingRect.isValid() && !group.boundingRect.isEmpty()) + { + // 设置半透明背景 - 增加透明度使其更明显 + QColor bgColor(255, 255, 255, 50); // 白色半透明 + painter.setBrush(bgColor); + + // 设置边框 + QColor borderColor(255, 255, 255, 100); // 白色半透明边框 + painter.setPen(QPen(borderColor, 2.0)); + + // 绘制圆角矩形 + QPainterPath path; + path.addRoundedRect(QRectF(group.boundingRect), 20, 20); + painter.drawPath(path); + } + } +} diff --git a/widgets/launcherpage.h b/widgets/launcherpage.h new file mode 100644 index 0000000..4b15040 --- /dev/null +++ b/widgets/launcherpage.h @@ -0,0 +1,63 @@ +#ifndef LAUNCHERPAGE_H +#define LAUNCHERPAGE_H + +#include +#include +#include +#include +#include +#include "appicon.h" + +struct PageConfig +{ + QString id; + QString title; + QVector apps; +}; + +// 分组信息结构 +struct GroupInfo +{ + QString groupId; + QVector iconIndices; // 该分组包含的图标索引 + QRect boundingRect; // 分组的边界矩形 +}; + +class LauncherPage : public QWidget +{ + Q_OBJECT + +public: + explicit LauncherPage(const PageConfig &config, QWidget *parent = nullptr); + ~LauncherPage(); + + QString pageId() const { return m_config.id; } + QString pageTitle() const { return m_config.title; } + +signals: + void appClicked(const QString &appId); + void appLongPressed(const QString &appId); + +protected: + void resizeEvent(QResizeEvent *event) override; + void paintEvent(QPaintEvent *event) override; + +private: + void setupUI(); + void layoutIcons(); + void updateGroupRects(const QMap &iconRects); // 更新分组矩形 + + PageConfig m_config; + QVector m_icons; + QMap m_groups; // 分组信息 + + // 网格布局参数 + int m_columns; + int m_rows; + int m_horizontalGap; + int m_verticalGap; + int m_horizontalPadding; + int m_verticalPadding; +}; + +#endif // LAUNCHERPAGE_H diff --git a/widgets/networktestwidget.cpp b/widgets/networktestwidget.cpp new file mode 100644 index 0000000..be8581b --- /dev/null +++ b/widgets/networktestwidget.cpp @@ -0,0 +1,470 @@ +#include "networktestwidget.h" +#include "overlaydialogswidget.h" +#include +#include +#include + +// 样式常量定义 +const QString NetworkTestWidget::BUTTON_STYLE = R"( + QPushButton { + background-color: #f5f5f5; + color: #333333; + border: 1px solid #ddd; + border-radius: 6px; + padding: 8px 16px; + font-size: 14px; + font-weight: 500; + min-height: 36px; + } + QPushButton:hover { + background-color: #e8e8e8; + border-color: #ccc; + } + QPushButton:pressed { + background-color: #d0d0d0; + } + QPushButton:disabled { + background-color: #f0f0f0; + color: #999; + } +)"; + +const QString NetworkTestWidget::PRIMARY_BUTTON_STYLE = R"( + QPushButton { + background-color: #2196F3; + color: white; + border: none; + border-radius: 6px; + padding: 8px 16px; + font-size: 14px; + font-weight: 500; + min-height: 36px; + } + QPushButton:hover { + background-color: #1976D2; + } + QPushButton:pressed { + background-color: #1565C0; + } + QPushButton:disabled { + background-color: #90CAF9; + } +)"; + +const QString NetworkTestWidget::INPUT_STYLE = R"( + QLineEdit { + background-color: white; + border: 1px solid #ddd; + border-radius: 6px; + padding: 8px 12px; + font-size: 14px; + min-height: 36px; + } + QLineEdit:focus { + border-color: #2196F3; + } +)"; + +const QString NetworkTestWidget::GROUP_STYLE = R"( + QGroupBox { + background-color: white; + border: 1px solid #e0e0e0; + border-radius: 8px; + margin-top: 16px; + padding-top: 16px; + font-size: 14px; + font-weight: 600; + } + QGroupBox::title { + subcontrol-origin: margin; + left: 16px; + padding: 0 8px; + color: #333; + } +)"; + +NetworkTestWidget::NetworkTestWidget(QWidget *parent) + : QWidget(parent), m_testStep(0) +{ + setupUI(); + + m_testTimer = new QTimer(this); + connect(m_testTimer, &QTimer::timeout, this, [this]() + { + m_testStep++; + m_progressBar->setValue(m_testStep * 10); + + if (m_testStep >= 10) { + m_testTimer->stop(); + m_progressBar->setValue(100); + } }); +} + +void NetworkTestWidget::setupUI() +{ + QVBoxLayout *mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(0); + + // 标题栏 + mainLayout->addWidget(createTitleBar()); + + // 内容区域 + QScrollArea *scrollArea = new QScrollArea; + scrollArea->setWidgetResizable(true); + scrollArea->setFrameShape(QFrame::NoFrame); + scrollArea->setStyleSheet("QScrollArea { background-color: #f5f5f5; }"); + + QWidget *contentWidget = new QWidget; + contentWidget->setStyleSheet("background-color: #f5f5f5;"); + QVBoxLayout *contentLayout = new QVBoxLayout(contentWidget); + contentLayout->setContentsMargins(20, 20, 20, 20); + contentLayout->setSpacing(16); + + contentLayout->addWidget(createTestPanel()); + contentLayout->addWidget(createResultPanel(), 1); + + scrollArea->setWidget(contentWidget); + mainLayout->addWidget(scrollArea, 1); +} + +QWidget *NetworkTestWidget::createTitleBar() +{ + QWidget *titleBar = new QWidget; + titleBar->setFixedHeight(60); + titleBar->setStyleSheet("background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #1976D2, stop:1 #1565C0);"); + + QHBoxLayout *layout = new QHBoxLayout(titleBar); + layout->setContentsMargins(16, 0, 16, 0); + + // 返回按钮 + m_backBtn = new QPushButton("← 返回"); + m_backBtn->setCursor(Qt::PointingHandCursor); + m_backBtn->setStyleSheet(R"( + QPushButton { + background: rgba(255,255,255,0.2); + color: white; + border: none; + border-radius: 6px; + padding: 8px 16px; + font-size: 14px; + } + QPushButton:hover { + background: rgba(255,255,255,0.3); + } + )"); + connect(m_backBtn, &QPushButton::clicked, this, &NetworkTestWidget::backRequested); + layout->addWidget(m_backBtn); + + // 标题(居中) + m_titleLabel = new QLabel("网络测试"); + m_titleLabel->setStyleSheet("color: white; font-size: 18px; font-weight: bold;"); + m_titleLabel->setAlignment(Qt::AlignCenter); + layout->addWidget(m_titleLabel, 1); + + // 右侧占位,保持标题居中 + QWidget *spacer = new QWidget; + spacer->setFixedWidth(m_backBtn->sizeHint().width()); + layout->addWidget(spacer); + + return titleBar; +} + +QWidget *NetworkTestWidget::createTestPanel() +{ + QGroupBox *group = new QGroupBox("测试设置"); + group->setStyleSheet(GROUP_STYLE); + + QVBoxLayout *layout = new QVBoxLayout(group); + layout->setContentsMargins(16, 24, 16, 16); + layout->setSpacing(16); + + // Ping测试 + QHBoxLayout *pingLayout = new QHBoxLayout; + QLabel *pingLabel = new QLabel("Ping主机:"); + pingLabel->setFixedWidth(80); + pingLabel->setStyleSheet("font-size: 14px; color: #333;"); + pingLayout->addWidget(pingLabel); + + m_hostEdit = new QLineEdit; + m_hostEdit->setStyleSheet(INPUT_STYLE); + m_hostEdit->setText("www.baidu.com"); + m_hostEdit->setPlaceholderText("输入主机名或IP地址"); + pingLayout->addWidget(m_hostEdit, 1); + + m_pingBtn = new QPushButton("Ping测试"); + m_pingBtn->setStyleSheet(PRIMARY_BUTTON_STYLE); + m_pingBtn->setFixedWidth(100); + m_pingBtn->setCursor(Qt::PointingHandCursor); + connect(m_pingBtn, &QPushButton::clicked, this, &NetworkTestWidget::onPingTest); + pingLayout->addWidget(m_pingBtn); + + layout->addLayout(pingLayout); + + // 服务器连接测试 + QHBoxLayout *serverLayout = new QHBoxLayout; + QLabel *serverLabel = new QLabel("服务器:"); + serverLabel->setFixedWidth(80); + serverLabel->setStyleSheet("font-size: 14px; color: #333;"); + serverLayout->addWidget(serverLabel); + + m_serverEdit = new QLineEdit; + m_serverEdit->setStyleSheet(INPUT_STYLE); + m_serverEdit->setText("192.168.1.100"); + m_serverEdit->setPlaceholderText("服务器地址"); + serverLayout->addWidget(m_serverEdit, 1); + + QLabel *portLabel = new QLabel("端口:"); + portLabel->setStyleSheet("font-size: 14px; color: #333;"); + serverLayout->addWidget(portLabel); + + m_portEdit = new QLineEdit; + m_portEdit->setStyleSheet(INPUT_STYLE); + m_portEdit->setFixedWidth(80); + m_portEdit->setText("8080"); + serverLayout->addWidget(m_portEdit); + + m_serverTestBtn = new QPushButton("连接测试"); + m_serverTestBtn->setStyleSheet(PRIMARY_BUTTON_STYLE); + m_serverTestBtn->setFixedWidth(100); + m_serverTestBtn->setCursor(Qt::PointingHandCursor); + connect(m_serverTestBtn, &QPushButton::clicked, this, &NetworkTestWidget::onServerTest); + serverLayout->addWidget(m_serverTestBtn); + + layout->addLayout(serverLayout); + + // 其他测试按钮 + QHBoxLayout *otherLayout = new QHBoxLayout; + + m_dnsTestBtn = new QPushButton("DNS解析测试"); + m_dnsTestBtn->setStyleSheet(BUTTON_STYLE); + m_dnsTestBtn->setCursor(Qt::PointingHandCursor); + connect(m_dnsTestBtn, &QPushButton::clicked, this, &NetworkTestWidget::onDnsTest); + otherLayout->addWidget(m_dnsTestBtn); + + m_speedTestBtn = new QPushButton("网速测试"); + m_speedTestBtn->setStyleSheet(BUTTON_STYLE); + m_speedTestBtn->setCursor(Qt::PointingHandCursor); + connect(m_speedTestBtn, &QPushButton::clicked, this, &NetworkTestWidget::onSpeedTest); + otherLayout->addWidget(m_speedTestBtn); + + otherLayout->addStretch(); + + layout->addLayout(otherLayout); + + return group; +} + +QWidget *NetworkTestWidget::createResultPanel() +{ + QGroupBox *group = new QGroupBox("测试结果"); + group->setStyleSheet(GROUP_STYLE); + + QVBoxLayout *layout = new QVBoxLayout(group); + layout->setContentsMargins(16, 24, 16, 16); + layout->setSpacing(12); + + // 进度条 + m_progressBar = new QProgressBar; + m_progressBar->setFixedHeight(6); + m_progressBar->setRange(0, 100); + m_progressBar->setValue(0); + m_progressBar->setTextVisible(false); + m_progressBar->setStyleSheet(R"( + QProgressBar { + background-color: #e0e0e0; + border-radius: 3px; + } + QProgressBar::chunk { + background-color: #4CAF50; + border-radius: 3px; + } + )"); + layout->addWidget(m_progressBar); + + // 日志显示 + m_logEdit = new QTextEdit; + m_logEdit->setReadOnly(true); + m_logEdit->setStyleSheet(R"( + QTextEdit { + background-color: #1e1e1e; + color: #d4d4d4; + border: 1px solid #ddd; + border-radius: 6px; + padding: 12px; + font-family: 'Courier New', monospace; + font-size: 13px; + } + )"); + m_logEdit->setMinimumHeight(300); + layout->addWidget(m_logEdit, 1); + + // 清除按钮 + QHBoxLayout *buttonLayout = new QHBoxLayout; + buttonLayout->addStretch(); + + m_clearBtn = new QPushButton("清除日志"); + m_clearBtn->setStyleSheet(BUTTON_STYLE); + m_clearBtn->setFixedWidth(100); + m_clearBtn->setCursor(Qt::PointingHandCursor); + connect(m_clearBtn, &QPushButton::clicked, this, &NetworkTestWidget::onClearLog); + buttonLayout->addWidget(m_clearBtn); + + layout->addLayout(buttonLayout); + + // 添加初始日志 + appendLog("网络测试工具已就绪", "#4CAF50"); + appendLog("请选择测试项目开始测试...", "#888"); + + return group; +} + +void NetworkTestWidget::appendLog(const QString &message, const QString &color) +{ + QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss"); + QString html = QString("[%1] %3
") + .arg(timestamp, color, message); + m_logEdit->insertHtml(html); + m_logEdit->moveCursor(QTextCursor::End); +} + +void NetworkTestWidget::onPingTest() +{ + QString host = m_hostEdit->text().trimmed(); + if (host.isEmpty()) + { + OverlayDialog::warning(window(), "提示", "请输入要Ping的主机名或IP地址"); + return; + } + + m_pingBtn->setEnabled(false); + m_progressBar->setValue(0); + m_testStep = 0; + + appendLog(QString("开始Ping测试: %1").arg(host), "#2196F3"); + + m_testTimer->start(200); + + // 模拟Ping测试 + QTimer::singleShot(2000, this, [this, host]() + { + m_testTimer->stop(); + m_progressBar->setValue(100); + m_pingBtn->setEnabled(true); + + // 模拟结果 + appendLog(QString("正在解析主机 %1...").arg(host), "#888"); + appendLog(QString("目标IP: 110.242.68.66"), "#888"); + appendLog("", "#888"); + appendLog("来自 110.242.68.66 的回复: 字节=64 时间=15ms TTL=52", "#4CAF50"); + appendLog("来自 110.242.68.66 的回复: 字节=64 时间=14ms TTL=52", "#4CAF50"); + appendLog("来自 110.242.68.66 的回复: 字节=64 时间=16ms TTL=52", "#4CAF50"); + appendLog("来自 110.242.68.66 的回复: 字节=64 时间=15ms TTL=52", "#4CAF50"); + appendLog("", "#888"); + appendLog("Ping统计: 已发送=4, 已接收=4, 丢失=0 (0% 丢失)", "#4CAF50"); + appendLog("往返时间: 最短=14ms, 最长=16ms, 平均=15ms", "#888"); + appendLog("", "#888"); + appendLog("✓ Ping测试完成", "#4CAF50"); }); +} + +void NetworkTestWidget::onSpeedTest() +{ + m_speedTestBtn->setEnabled(false); + m_progressBar->setValue(0); + m_testStep = 0; + + appendLog("开始网速测试...", "#2196F3"); + + m_testTimer->start(300); + + QTimer::singleShot(3000, this, [this]() + { + m_testTimer->stop(); + m_progressBar->setValue(100); + m_speedTestBtn->setEnabled(true); + + appendLog("正在测试下载速度...", "#888"); + appendLog("下载速度: 45.6 Mbps", "#4CAF50"); + appendLog("正在测试上传速度...", "#888"); + appendLog("上传速度: 12.3 Mbps", "#4CAF50"); + appendLog("网络延迟: 18ms", "#888"); + appendLog("抖动: 2ms", "#888"); + appendLog("", "#888"); + appendLog("✓ 网速测试完成", "#4CAF50"); }); +} + +void NetworkTestWidget::onDnsTest() +{ + m_dnsTestBtn->setEnabled(false); + m_progressBar->setValue(0); + m_testStep = 0; + + appendLog("开始DNS解析测试...", "#2196F3"); + + m_testTimer->start(150); + + QTimer::singleShot(1500, this, [this]() + { + m_testTimer->stop(); + m_progressBar->setValue(100); + m_dnsTestBtn->setEnabled(true); + + appendLog("DNS服务器: 114.114.114.114", "#888"); + appendLog("", "#888"); + appendLog("www.baidu.com -> 110.242.68.66 (8ms)", "#4CAF50"); + appendLog("www.google.com -> 142.250.80.46 (25ms)", "#4CAF50"); + appendLog("www.github.com -> 20.205.243.166 (45ms)", "#4CAF50"); + appendLog("", "#888"); + appendLog("✓ DNS解析测试完成", "#4CAF50"); }); +} + +void NetworkTestWidget::onServerTest() +{ + QString server = m_serverEdit->text().trimmed(); + QString port = m_portEdit->text().trimmed(); + + if (server.isEmpty()) + { + OverlayDialog::warning(window(), "提示", "请输入服务器地址"); + return; + } + + m_serverTestBtn->setEnabled(false); + m_progressBar->setValue(0); + m_testStep = 0; + + appendLog(QString("开始连接测试: %1:%2").arg(server, port), "#2196F3"); + + m_testTimer->start(200); + + QTimer::singleShot(2000, this, [this, server, port]() + { + m_testTimer->stop(); + m_progressBar->setValue(100); + m_serverTestBtn->setEnabled(true); + + // 随机模拟成功或超时 + bool success = (QDateTime::currentMSecsSinceEpoch() % 3) != 0; + + if (success) { + appendLog(QString("正在连接 %1:%2...").arg(server, port), "#888"); + appendLog("TCP握手成功", "#4CAF50"); + appendLog("连接延迟: 25ms", "#888"); + appendLog("服务器响应: HTTP 200 OK", "#4CAF50"); + appendLog("", "#888"); + appendLog("✓ 服务器连接测试成功", "#4CAF50"); + } else { + appendLog(QString("正在连接 %1:%2...").arg(server, port), "#888"); + appendLog("连接超时 (5000ms)", "#f44336"); + appendLog("", "#888"); + appendLog("✗ 服务器连接测试失败", "#f44336"); + appendLog("请检查服务器地址和端口是否正确", "#ff9800"); + } }); +} + +void NetworkTestWidget::onClearLog() +{ + m_logEdit->clear(); + m_progressBar->setValue(0); + appendLog("日志已清除", "#888"); +} diff --git a/widgets/networktestwidget.h b/widgets/networktestwidget.h new file mode 100644 index 0000000..aec9e95 --- /dev/null +++ b/widgets/networktestwidget.h @@ -0,0 +1,72 @@ +#ifndef NETWORKTESTWIDGET_H +#define NETWORKTESTWIDGET_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief 网络测试页面 + * 提供网络连接测试和诊断功能 + */ +class NetworkTestWidget : public QWidget +{ + Q_OBJECT + +public: + explicit NetworkTestWidget(QWidget *parent = nullptr); + +signals: + void backRequested(); + +private slots: + void onPingTest(); + void onSpeedTest(); + void onDnsTest(); + void onServerTest(); + void onClearLog(); + +private: + void setupUI(); + QWidget *createTitleBar(); + QWidget *createTestPanel(); + QWidget *createResultPanel(); + void appendLog(const QString &message, const QString &color = "#333"); + + // 标题栏 + QLabel *m_titleLabel; + QPushButton *m_backBtn; + + // 测试面板 + QLineEdit *m_hostEdit; + QLineEdit *m_serverEdit; + QLineEdit *m_portEdit; + QPushButton *m_pingBtn; + QPushButton *m_speedTestBtn; + QPushButton *m_dnsTestBtn; + QPushButton *m_serverTestBtn; + + // 结果面板 + QTextEdit *m_logEdit; + QProgressBar *m_progressBar; + QPushButton *m_clearBtn; + + // 测试定时器 + QTimer *m_testTimer; + int m_testStep; + + // 样式常量 + static const QString BUTTON_STYLE; + static const QString PRIMARY_BUTTON_STYLE; + static const QString INPUT_STYLE; + static const QString GROUP_STYLE; +}; + +#endif // NETWORKTESTWIDGET_H diff --git a/widgets/overlaydialogswidget.cpp b/widgets/overlaydialogswidget.cpp new file mode 100644 index 0000000..554f5ed --- /dev/null +++ b/widgets/overlaydialogswidget.cpp @@ -0,0 +1,322 @@ +#include "overlaydialogswidget.h" +#include + +OverlayDialog::OverlayDialog(QWidget *parent) + : QWidget(parent), m_type(Information), m_inputMode(false) +{ + setupUI(); + hide(); +} + +void OverlayDialog::setupUI() +{ + // 设置为覆盖整个父窗口 + setAttribute(Qt::WA_StyledBackground); + setStyleSheet("background: transparent;"); + + auto *mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + + // 半透明背景遮罩 + m_backgroundOverlay = new QWidget(this); + m_backgroundOverlay->setStyleSheet("background: rgba(0, 0, 0, 0.5);"); + mainLayout->addWidget(m_backgroundOverlay); + + // 对话框容器 - 居中显示 + m_dialogContainer = new QWidget(m_backgroundOverlay); + m_dialogContainer->setFixedWidth(420); + m_dialogContainer->setStyleSheet( + "QWidget { background: white; border-radius: 12px; }"); + + auto *dialogLayout = new QVBoxLayout(m_dialogContainer); + dialogLayout->setContentsMargins(24, 24, 24, 20); + dialogLayout->setSpacing(16); + + // 标题栏 + auto *headerLayout = new QHBoxLayout(); + headerLayout->setSpacing(12); + + m_iconLabel = new QLabel(this); + m_iconLabel->setFixedSize(32, 32); + m_iconLabel->setAlignment(Qt::AlignCenter); + m_iconLabel->setStyleSheet("font-size: 24px;"); + headerLayout->addWidget(m_iconLabel); + + m_titleLabel = new QLabel("提示", this); + m_titleLabel->setStyleSheet( + "font-size: 18px; font-weight: bold; color: #263238;"); + headerLayout->addWidget(m_titleLabel, 1); + + dialogLayout->addLayout(headerLayout); + + // 消息内容 + m_messageLabel = new QLabel(this); + m_messageLabel->setWordWrap(true); + m_messageLabel->setStyleSheet( + "font-size: 14px; color: #546E7A; line-height: 1.6; padding: 8px 0;"); + dialogLayout->addWidget(m_messageLabel); + + // 输入框(默认隐藏) + m_inputEdit = new QLineEdit(this); + m_inputEdit->setFixedHeight(44); + m_inputEdit->setStyleSheet( + "QLineEdit { border: 2px solid #E0E0E0; border-radius: 8px; padding: 0 12px; " + "font-size: 14px; background: #FAFAFA; }" + "QLineEdit:focus { border-color: #1976D2; background: white; }"); + m_inputEdit->hide(); + dialogLayout->addWidget(m_inputEdit); + + // 按钮区域 + auto *buttonLayout = new QHBoxLayout(); + buttonLayout->setSpacing(12); + buttonLayout->addStretch(); + + m_rejectBtn = new QPushButton("取消", this); + m_rejectBtn->setFixedSize(100, 44); + m_rejectBtn->setCursor(Qt::PointingHandCursor); + m_rejectBtn->setStyleSheet( + "QPushButton { background: #ECEFF1; color: #546E7A; border: none; " + "border-radius: 8px; font-size: 14px; font-weight: 500; }" + "QPushButton:hover { background: #CFD8DC; }" + "QPushButton:pressed { background: #B0BEC5; }"); + connect(m_rejectBtn, &QPushButton::clicked, this, &OverlayDialog::onRejectClicked); + buttonLayout->addWidget(m_rejectBtn); + + m_acceptBtn = new QPushButton("确定", this); + m_acceptBtn->setFixedSize(100, 44); + m_acceptBtn->setCursor(Qt::PointingHandCursor); + m_acceptBtn->setStyleSheet( + "QPushButton { background: #1976D2; color: white; border: none; " + "border-radius: 8px; font-size: 14px; font-weight: 500; }" + "QPushButton:hover { background: #1565C0; }" + "QPushButton:pressed { background: #0D47A1; }"); + connect(m_acceptBtn, &QPushButton::clicked, this, &OverlayDialog::onAcceptClicked); + buttonLayout->addWidget(m_acceptBtn); + + dialogLayout->addLayout(buttonLayout); + + // 居中布局 + auto *overlayLayout = new QVBoxLayout(m_backgroundOverlay); + overlayLayout->addStretch(); + auto *centerLayout = new QHBoxLayout(); + centerLayout->addStretch(); + centerLayout->addWidget(m_dialogContainer); + centerLayout->addStretch(); + overlayLayout->addLayout(centerLayout); + overlayLayout->addStretch(); +} + +void OverlayDialog::setTitle(const QString &title) +{ + m_titleLabel->setText(title); +} + +void OverlayDialog::setMessage(const QString &message) +{ + m_messageLabel->setText(message); +} + +void OverlayDialog::setDialogType(DialogType type) +{ + m_type = type; + updateTypeIcon(); + + // 根据类型调整按钮显示 + switch (type) + { + case Information: + case Warning: + m_rejectBtn->hide(); + m_acceptBtn->setText("确定"); + break; + case Question: + m_rejectBtn->show(); + m_rejectBtn->setText("取消"); + m_acceptBtn->setText("确定"); + break; + case Input: + m_rejectBtn->show(); + m_rejectBtn->setText("取消"); + m_acceptBtn->setText("确定"); + break; + } +} + +void OverlayDialog::setInputMode(bool inputMode, const QString &placeholder) +{ + m_inputMode = inputMode; + m_inputEdit->setVisible(inputMode); + m_inputEdit->setPlaceholderText(placeholder); + m_inputEdit->clear(); +} + +void OverlayDialog::setCallback(std::function callback) +{ + m_callback = callback; +} + +void OverlayDialog::updateTypeIcon() +{ + QString icon; + QString color; + + switch (m_type) + { + case Information: + icon = "ℹ"; + color = "#1976D2"; + break; + case Warning: + icon = "⚠"; + color = "#F57C00"; + break; + case Question: + icon = "?"; + color = "#1976D2"; + break; + case Input: + icon = "✏"; + color = "#1976D2"; + break; + } + + m_iconLabel->setText(icon); + m_iconLabel->setStyleSheet(QString("font-size: 24px; color: %1;").arg(color)); +} + +void OverlayDialog::showDialog() +{ + if (parentWidget()) + { + setGeometry(parentWidget()->rect()); + } + show(); + raise(); + animateShow(); + + if (m_inputMode) + { + m_inputEdit->setFocus(); + } +} + +void OverlayDialog::hideDialog() +{ + animateHide(); +} + +void OverlayDialog::onAcceptClicked() +{ + QString inputText = m_inputMode ? m_inputEdit->text() : QString(); + emit accepted(inputText); + + if (m_callback) + { + m_callback(true, inputText); + } + + hideDialog(); +} + +void OverlayDialog::onRejectClicked() +{ + emit rejected(); + + if (m_callback) + { + m_callback(false, QString()); + } + + hideDialog(); +} + +void OverlayDialog::animateShow() +{ + QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect(this); + setGraphicsEffect(effect); + + QPropertyAnimation *animation = new QPropertyAnimation(effect, "opacity"); + animation->setDuration(150); + animation->setStartValue(0.0); + animation->setEndValue(1.0); + animation->start(QPropertyAnimation::DeleteWhenStopped); +} + +void OverlayDialog::animateHide() +{ + QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect(this); + setGraphicsEffect(effect); + + QPropertyAnimation *animation = new QPropertyAnimation(effect, "opacity"); + animation->setDuration(100); + animation->setStartValue(1.0); + animation->setEndValue(0.0); + + connect(animation, &QPropertyAnimation::finished, this, [this]() + { + hide(); + deleteLater(); }); + animation->start(QPropertyAnimation::DeleteWhenStopped); +} + +// 静态便捷方法 +void OverlayDialog::information(QWidget *parent, const QString &title, const QString &message) +{ + // 找到顶层窗口 + QWidget *topLevel = parent; + while (topLevel->parentWidget()) + topLevel = topLevel->parentWidget(); + + auto *dialog = new OverlayDialog(topLevel); + dialog->setTitle(title); + dialog->setMessage(message); + dialog->setDialogType(Information); + dialog->showDialog(); +} + +void OverlayDialog::warning(QWidget *parent, const QString &title, const QString &message) +{ + QWidget *topLevel = parent; + while (topLevel->parentWidget()) + topLevel = topLevel->parentWidget(); + + auto *dialog = new OverlayDialog(topLevel); + dialog->setTitle(title); + dialog->setMessage(message); + dialog->setDialogType(Warning); + dialog->showDialog(); +} + +void OverlayDialog::question(QWidget *parent, const QString &title, const QString &message, + std::function callback) +{ + QWidget *topLevel = parent; + while (topLevel->parentWidget()) + topLevel = topLevel->parentWidget(); + + auto *dialog = new OverlayDialog(topLevel); + dialog->setTitle(title); + dialog->setMessage(message); + dialog->setDialogType(Question); + dialog->setCallback([callback](bool accepted, const QString &) + { callback(accepted); }); + dialog->showDialog(); +} + +void OverlayDialog::input(QWidget *parent, const QString &title, const QString &prompt, + std::function callback, + const QString &defaultText) +{ + QWidget *topLevel = parent; + while (topLevel->parentWidget()) + topLevel = topLevel->parentWidget(); + + auto *dialog = new OverlayDialog(topLevel); + dialog->setTitle(title); + dialog->setMessage(prompt); + dialog->setDialogType(Input); + dialog->setInputMode(true, "请输入..."); + dialog->m_inputEdit->setText(defaultText); + dialog->setCallback(callback); + dialog->showDialog(); +} diff --git a/widgets/overlaydialogswidget.h b/widgets/overlaydialogswidget.h new file mode 100644 index 0000000..be106cd --- /dev/null +++ b/widgets/overlaydialogswidget.h @@ -0,0 +1,81 @@ +#ifndef OVERLAYDIALOGSWIDGET_H +#define OVERLAYDIALOGSWIDGET_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @class OverlayDialog + * @brief 浮层对话框基类 + * + * 用于IoT设备的模态浮层对话框,替代QMessageBox和QInputDialog + */ +class OverlayDialog : public QWidget +{ + Q_OBJECT + +public: + enum DialogType + { + Information, + Warning, + Question, + Input + }; + + explicit OverlayDialog(QWidget *parent = nullptr); + + // 静态便捷方法 + static void information(QWidget *parent, const QString &title, const QString &message); + static void warning(QWidget *parent, const QString &title, const QString &message); + static void question(QWidget *parent, const QString &title, const QString &message, + std::function callback); + static void input(QWidget *parent, const QString &title, const QString &prompt, + std::function callback, + const QString &defaultText = ""); + + void setTitle(const QString &title); + void setMessage(const QString &message); + void setDialogType(DialogType type); + void setInputMode(bool inputMode, const QString &placeholder = ""); + void setCallback(std::function callback); + + void showDialog(); + void hideDialog(); + +signals: + void accepted(const QString &inputText = QString()); + void rejected(); + +private slots: + void onAcceptClicked(); + void onRejectClicked(); + +private: + void setupUI(); + void updateTypeIcon(); + void animateShow(); + void animateHide(); + + QWidget *m_backgroundOverlay; + QWidget *m_dialogContainer; + QLabel *m_iconLabel; + QLabel *m_titleLabel; + QLabel *m_messageLabel; + QLineEdit *m_inputEdit; + QPushButton *m_acceptBtn; + QPushButton *m_rejectBtn; + + DialogType m_type; + bool m_inputMode; + std::function m_callback; +}; + +#endif // OVERLAYDIALOGSWIDGET_H diff --git a/widgets/pageindicator.cpp b/widgets/pageindicator.cpp new file mode 100644 index 0000000..6e65f16 --- /dev/null +++ b/widgets/pageindicator.cpp @@ -0,0 +1,116 @@ +#include "pageindicator.h" +#include +#include + +PageIndicator::PageIndicator(int pageCount, QWidget *parent) + : QWidget(parent), m_pageCount(pageCount), m_currentPage(0), m_dotSize(8), m_selectedDotSize(10), m_dotSpacing(14), m_dotColor(QColor(255, 255, 255, 120)), m_selectedColor(QColor(255, 255, 255, 255)) +{ + setCursor(Qt::PointingHandCursor); + setAttribute(Qt::WA_TranslucentBackground); +} + +PageIndicator::~PageIndicator() +{ +} + +void PageIndicator::setPageCount(int count) +{ + if (count != m_pageCount) + { + m_pageCount = count; + if (m_currentPage >= m_pageCount) + { + m_currentPage = m_pageCount - 1; + } + update(); + } +} + +void PageIndicator::setCurrentPage(int page) +{ + if (page >= 0 && page < m_pageCount && page != m_currentPage) + { + m_currentPage = page; + update(); + } +} + +void PageIndicator::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + // 计算总宽度 + int totalWidth = 0; + for (int i = 0; i < m_pageCount; ++i) + { + int dotSize = (i == m_currentPage) ? m_selectedDotSize : m_dotSize; + totalWidth += dotSize; + if (i < m_pageCount - 1) + { + totalWidth += m_dotSpacing; + } + } + + // 计算起始位置(居中) + int startX = (width() - totalWidth) / 2; + int centerY = height() / 2; + + // 绘制指示点 + int x = startX; + for (int i = 0; i < m_pageCount; ++i) + { + bool isSelected = (i == m_currentPage); + int dotSize = isSelected ? m_selectedDotSize : m_dotSize; + QColor color = isSelected ? m_selectedColor : m_dotColor; + + painter.setPen(Qt::NoPen); + painter.setBrush(color); + + int y = centerY - dotSize / 2; + painter.drawEllipse(x, y, dotSize, dotSize); + + x += dotSize + m_dotSpacing; + } +} + +void PageIndicator::mousePressEvent(QMouseEvent *event) +{ + // 计算点击位置对应的页面 + int totalWidth = 0; + for (int i = 0; i < m_pageCount; ++i) + { + int dotSize = (i == m_currentPage) ? m_selectedDotSize : m_dotSize; + totalWidth += dotSize; + if (i < m_pageCount - 1) + { + totalWidth += m_dotSpacing; + } + } + + int startX = (width() - totalWidth) / 2; + int x = startX; + + for (int i = 0; i < m_pageCount; ++i) + { + int dotSize = (i == m_currentPage) ? m_selectedDotSize : m_dotSize; + + // 扩大点击区域 + QRect hitRect(x - 8, 0, dotSize + 16, height()); + + if (hitRect.contains(event->pos())) + { + if (i != m_currentPage) + { + emit pageClicked(i); + } + break; + } + + x += dotSize + m_dotSpacing; + } + + QWidget::mousePressEvent(event); +} diff --git a/widgets/pageindicator.h b/widgets/pageindicator.h new file mode 100644 index 0000000..79c2ae4 --- /dev/null +++ b/widgets/pageindicator.h @@ -0,0 +1,37 @@ +#ifndef PAGEINDICATOR_H +#define PAGEINDICATOR_H + +#include + +class PageIndicator : public QWidget +{ + Q_OBJECT + +public: + explicit PageIndicator(int pageCount = 3, QWidget *parent = nullptr); + ~PageIndicator(); + + void setPageCount(int count); + void setCurrentPage(int page); + int currentPage() const { return m_currentPage; } + +signals: + void pageClicked(int page); + +protected: + void paintEvent(QPaintEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + +private: + int m_pageCount; + int m_currentPage; + + // 指示器样式参数 + int m_dotSize; + int m_selectedDotSize; + int m_dotSpacing; + QColor m_dotColor; + QColor m_selectedColor; +}; + +#endif // PAGEINDICATOR_H diff --git a/widgets/procedurelistwidget.cpp b/widgets/procedurelistwidget.cpp new file mode 100644 index 0000000..e5ed59c --- /dev/null +++ b/widgets/procedurelistwidget.cpp @@ -0,0 +1,224 @@ +#include "procedurelistwidget.h" +#include "overlaydialogswidget.h" +#include "../procedure/proceduremanager.h" +#include +#include +#include +#include +#include + +ProcedureListWidget::ProcedureListWidget(ProcedureManager *manager, QWidget *parent) + : QWidget(parent), m_manager(manager) +{ + setupUI(); + refreshList(); +} + +ProcedureListWidget::~ProcedureListWidget() {} + +void ProcedureListWidget::setupUI() +{ + auto *mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(0); + + // 顶部工具栏 - Material Design 深色 + auto *toolbar = new QWidget(this); + toolbar->setFixedHeight(56); + toolbar->setStyleSheet("background: #263238;"); + + auto *toolbarLayout = new QHBoxLayout(toolbar); + toolbarLayout->setContentsMargins(8, 0, 16, 0); + toolbarLayout->setSpacing(8); + + m_backBtn = new QPushButton("返回", this); + m_backBtn->setFixedHeight(40); + m_backBtn->setStyleSheet( + "QPushButton { background: #455A64; color: white; border: none; border-radius: 4px; padding: 0 16px; font-size: 14px; }" + "QPushButton:hover { background: #546E7A; }" + "QPushButton:pressed { background: #37474F; }"); + connect(m_backBtn, &QPushButton::clicked, this, &ProcedureListWidget::backRequested); + toolbarLayout->addWidget(m_backBtn); + + auto *titleLabel = new QLabel("规程列表", this); + titleLabel->setStyleSheet("color: white; font-size: 18px; font-weight: bold;"); + toolbarLayout->addWidget(titleLabel); + toolbarLayout->addStretch(); + + mainLayout->addWidget(toolbar); + + // 内容区域 + auto *contentWidget = new QWidget(this); + contentWidget->setStyleSheet("background: #FAFAFA;"); + auto *contentLayout = new QVBoxLayout(contentWidget); + contentLayout->setContentsMargins(20, 20, 20, 20); + contentLayout->setSpacing(16); + + // 搜索卡片 + auto *searchCard = new QWidget(this); + searchCard->setStyleSheet( + "QWidget#searchCard { background: white; border-radius: 8px; border: 1px solid #E0E0E0; }"); + searchCard->setObjectName("searchCard"); + auto *searchCardLayout = new QVBoxLayout(searchCard); + searchCardLayout->setContentsMargins(20, 16, 20, 16); + searchCardLayout->setSpacing(16); + + auto *searchRow = new QHBoxLayout(); + searchRow->setSpacing(12); + m_searchEdit = new QLineEdit(this); + m_searchEdit->setPlaceholderText("输入规程ID或名称搜索..."); + m_searchEdit->setFixedHeight(44); + m_searchEdit->setStyleSheet( + "QLineEdit { border: 2px solid #E0E0E0; border-radius: 6px; padding: 0 16px; font-size: 15px; background: #FAFAFA; }" + "QLineEdit:focus { border-color: #1976D2; background: white; }"); + searchRow->addWidget(m_searchEdit, 1); + + m_scanProcedureBtn = new QPushButton("扫描条码", this); + m_scanProcedureBtn->setFixedHeight(44); + m_scanProcedureBtn->setStyleSheet( + "QPushButton { background: #1976D2; color: white; border: none; border-radius: 6px; padding: 0 20px; font-size: 14px; font-weight: bold; }" + "QPushButton:hover { background: #1565C0; }" + "QPushButton:pressed { background: #0D47A1; }"); + searchRow->addWidget(m_scanProcedureBtn); + searchCardLayout->addLayout(searchRow); + + auto *workOrderRow = new QHBoxLayout(); + workOrderRow->setSpacing(12); + auto *workOrderLabel = new QLabel("工单号:", this); + workOrderLabel->setStyleSheet("color: #424242; font-size: 15px; font-weight: bold;"); + workOrderLabel->setFixedWidth(70); + workOrderRow->addWidget(workOrderLabel); + + m_workOrderEdit = new QLineEdit(this); + m_workOrderEdit->setPlaceholderText("输入或扫描工单号"); + m_workOrderEdit->setFixedHeight(44); + m_workOrderEdit->setStyleSheet( + "QLineEdit { border: 2px solid #E0E0E0; border-radius: 6px; padding: 0 16px; font-size: 15px; background: #FAFAFA; }" + "QLineEdit:focus { border-color: #1976D2; background: white; }"); + workOrderRow->addWidget(m_workOrderEdit, 1); + + m_scanWorkOrderBtn = new QPushButton("扫描", this); + m_scanWorkOrderBtn->setFixedHeight(44); + m_scanWorkOrderBtn->setStyleSheet( + "QPushButton { background: #455A64; color: white; border: none; border-radius: 6px; padding: 0 20px; font-size: 14px; }" + "QPushButton:hover { background: #546E7A; }" + "QPushButton:pressed { background: #37474F; }"); + workOrderRow->addWidget(m_scanWorkOrderBtn); + searchCardLayout->addLayout(workOrderRow); + + contentLayout->addWidget(searchCard); + + // 规程列表 + m_procedureList = new QListWidget(this); + m_procedureList->setStyleSheet( + "QListWidget { border: 1px solid #E0E0E0; border-radius: 8px; background: white; padding: 8px; }" + "QListWidget::item { background: #FAFAFA; border-radius: 6px; padding: 16px; margin: 4px 0; border: 1px solid #EEEEEE; }" + "QListWidget::item:selected { background: #E3F2FD; border: 2px solid #1976D2; }" + "QListWidget::item:hover { background: #F5F5F5; }"); + contentLayout->addWidget(m_procedureList, 1); + + // 底部按钮 + auto *bottomLayout = new QHBoxLayout(); + bottomLayout->addStretch(); + m_startBtn = new QPushButton("进入规程", this); + m_startBtn->setEnabled(false); + m_startBtn->setFixedSize(200, 52); + m_startBtn->setStyleSheet( + "QPushButton { background: #43A047; color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: bold; }" + "QPushButton:hover { background: #388E3C; }" + "QPushButton:pressed { background: #2E7D32; }" + "QPushButton:disabled { background: #BDBDBD; color: #757575; }"); + bottomLayout->addWidget(m_startBtn); + bottomLayout->addStretch(); + contentLayout->addLayout(bottomLayout); + + mainLayout->addWidget(contentWidget, 1); + + connect(m_searchEdit, &QLineEdit::textChanged, this, &ProcedureListWidget::onSearchTextChanged); + connect(m_scanProcedureBtn, &QPushButton::clicked, this, &ProcedureListWidget::onScanProcedureBarcode); + connect(m_scanWorkOrderBtn, &QPushButton::clicked, this, &ProcedureListWidget::onScanWorkOrderBarcode); + connect(m_procedureList, &QListWidget::itemClicked, this, &ProcedureListWidget::onProcedureItemClicked); + connect(m_procedureList, &QListWidget::itemDoubleClicked, this, &ProcedureListWidget::onStartProcedure); + connect(m_startBtn, &QPushButton::clicked, this, &ProcedureListWidget::onStartProcedure); +} + +void ProcedureListWidget::refreshList() +{ + // 尝试多个可能的 procedures 目录位置 + QStringList possiblePaths; + possiblePaths << QCoreApplication::applicationDirPath() + "/procedures"; + possiblePaths << QCoreApplication::applicationDirPath() + "/../../procedures"; + possiblePaths << QCoreApplication::applicationDirPath() + "/../../../procedures"; + possiblePaths << QCoreApplication::applicationDirPath() + "/../../../../procedures"; + + QString proceduresDir; + for (const QString &path : possiblePaths) + { + QString absPath = QDir(path).absolutePath(); + qDebug() << "Checking procedures path:" << absPath << "exists:" << QDir(absPath).exists(); + if (QDir(absPath).exists()) + { + proceduresDir = absPath; + break; + } + } + + if (proceduresDir.isEmpty()) + { + qWarning() << "Could not find procedures directory! Tried paths:" << possiblePaths; + proceduresDir = QCoreApplication::applicationDirPath() + "/procedures"; // 使用默认路径 + } + + qDebug() << "Loading procedures from:" << proceduresDir; + auto procedures = m_manager->loadProcedureList(proceduresDir); + updateProcedureList(procedures); +} + +void ProcedureListWidget::updateProcedureList(const QVector &procedures) +{ + m_procedureList->clear(); + for (const auto &proc : procedures) + { + auto *item = new QListWidgetItem(); + item->setData(Qt::UserRole, proc.id); + item->setText(QString("%1\n%2\n版本: %3").arg(proc.id).arg(proc.name).arg(proc.version)); + item->setSizeHint(QSize(0, 80)); + m_procedureList->addItem(item); + } +} + +void ProcedureListWidget::onSearchTextChanged(const QString &text) +{ + updateProcedureList(m_manager->searchProcedures(text)); +} + +void ProcedureListWidget::onScanProcedureBarcode() +{ + OverlayDialog::information(this, "扫码", "请将条码对准扫描窗口..."); +} + +void ProcedureListWidget::onScanWorkOrderBarcode() +{ + OverlayDialog::information(this, "扫码", "请将工单条码对准扫描窗口..."); +} + +void ProcedureListWidget::onProcedureItemClicked(QListWidgetItem *item) +{ + m_selectedProcedureId = item->data(Qt::UserRole).toString(); + m_startBtn->setEnabled(!m_selectedProcedureId.isEmpty()); +} + +void ProcedureListWidget::onStartProcedure() +{ + if (m_selectedProcedureId.isEmpty()) + { + OverlayDialog::warning(this, "提示", "请先选择一个规程"); + return; + } + QString workOrder = m_workOrderEdit->text().trimmed(); + if (!workOrder.isEmpty()) + { + m_manager->setWorkOrderId(workOrder); + } + emit procedureSelected(m_selectedProcedureId); +} diff --git a/widgets/procedurelistwidget.h b/widgets/procedurelistwidget.h new file mode 100644 index 0000000..72dea59 --- /dev/null +++ b/widgets/procedurelistwidget.h @@ -0,0 +1,61 @@ +#ifndef PROCEDURELISTWIDGET_H +#define PROCEDURELISTWIDGET_H + +#include +#include +#include +#include +#include +#include +#include +#include "../procedure/proceduredata.h" + +class ProcedureManager; + +/** + * @class ProcedureListWidget + * @brief 规程列表页面 + * + * 显示可用规程列表,支持搜索(包括条码扫描) + * 支持输入工单号 + */ +class ProcedureListWidget : public QWidget +{ + Q_OBJECT + +public: + explicit ProcedureListWidget(ProcedureManager *manager, QWidget *parent = nullptr); + ~ProcedureListWidget(); + + void refreshList(); + +signals: + void procedureSelected(const QString &procedureId); + void backRequested(); + +private slots: + void onSearchTextChanged(const QString &text); + void onScanProcedureBarcode(); + void onScanWorkOrderBarcode(); + void onProcedureItemClicked(QListWidgetItem *item); + void onStartProcedure(); + +private: + void setupUI(); + void updateProcedureList(const QVector &procedures); + + ProcedureManager *m_manager; + + // UI 组件 + QPushButton *m_backBtn; + QLineEdit *m_searchEdit; + QPushButton *m_scanProcedureBtn; + QLineEdit *m_workOrderEdit; + QPushButton *m_scanWorkOrderBtn; + QListWidget *m_procedureList; + QPushButton *m_startBtn; + + QString m_selectedProcedureId; +}; + +#endif // PROCEDURELISTWIDGET_H diff --git a/widgets/procedureplayerwidget.cpp b/widgets/procedureplayerwidget.cpp new file mode 100644 index 0000000..c90ef02 --- /dev/null +++ b/widgets/procedureplayerwidget.cpp @@ -0,0 +1,697 @@ +#include "procedureplayerwidget.h" +#include "overlaydialogswidget.h" +#include "../procedure/proceduremanager.h" +#include +#include + +// ============== StepItemWidget ============== + +StepItemWidget::StepItemWidget(const StepData &step, QWidget *parent) + : QWidget(parent), m_step(step), m_typeBtn(nullptr), m_confirmCheckbox(nullptr), m_tableBtn(nullptr) +{ + setupUI(); + updateStatusDisplay(); +} + +void StepItemWidget::setupUI() +{ + auto *mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(0); + + // 主容器 + m_container = new QWidget(this); + m_container->setObjectName("stepContainer"); + + auto *containerLayout = new QHBoxLayout(m_container); + containerLayout->setContentsMargins(12, 10, 12, 10); + containerLayout->setSpacing(12); + + // 左侧区域:类型按钮 + 状态标签 + auto *leftArea = new QVBoxLayout(); + leftArea->setAlignment(Qt::AlignTop); + leftArea->setSpacing(6); + + // 类型切换按钮 - Material Design 风格,可点击切换 - 触摸屏优化 + m_typeBtn = new QPushButton(this); + updateTypeButton(); + m_typeBtn->setFixedSize(78, 36); + m_typeBtn->setCursor(Qt::PointingHandCursor); + connect(m_typeBtn, &QPushButton::clicked, this, &StepItemWidget::onTypeBtnClicked); + leftArea->addWidget(m_typeBtn); + + // 状态标签 - 放在类型按钮下方,大小一致 + m_statusLabel = new QLabel(this); + m_statusLabel->setFixedSize(78, 36); + m_statusLabel->setAlignment(Qt::AlignCenter); + leftArea->addWidget(m_statusLabel); + + containerLayout->addLayout(leftArea); + + // 内容区域 + auto *contentArea = new QVBoxLayout(); + contentArea->setSpacing(6); + + m_contentLabel = new QLabel(m_step.content, this); + m_contentLabel->setWordWrap(true); + m_contentLabel->setTextFormat(Qt::PlainText); + m_contentLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); + m_contentLabel->setMinimumWidth(0); + m_contentLabel->setStyleSheet("font-size: 14px; color: #37474F; line-height: 1.6;"); + contentArea->addWidget(m_contentLabel); + + containerLayout->addLayout(contentArea, 1); + + // 右侧区域:仅确认复选框(如果是手动步骤) + if (m_step.type == StepType::Manual) + { + auto *rightArea = new QVBoxLayout(); + rightArea->setAlignment(Qt::AlignTop | Qt::AlignRight); + rightArea->setSpacing(10); + + m_confirmCheckbox = new QCheckBox("确认", this); + m_confirmCheckbox->setStyleSheet( + "QCheckBox { font-size: 14px; color: #455A64; spacing: 8px; }" + "QCheckBox::indicator { width: 24px; height: 24px; border: 2px solid #90A4AE; border-radius: 4px; background: white; }" + "QCheckBox::indicator:checked { background: #43A047; border-color: #43A047; image: url(:/icons/check-white.png); }" + "QCheckBox::indicator:hover { border-color: #1976D2; }"); + m_confirmCheckbox->setChecked(m_step.status == StepStatus::Confirmed); + connect(m_confirmCheckbox, &QCheckBox::toggled, this, &StepItemWidget::onCheckboxToggled); + rightArea->addWidget(m_confirmCheckbox); + + containerLayout->addLayout(rightArea); + } + + mainLayout->addWidget(m_container); + + // 底部表格按钮 - banner 样式 + if (!m_step.tableRefs.isEmpty()) + { + m_tableBtn = new QPushButton("📋 查看关联表格", this); + m_tableBtn->setFixedHeight(40); + m_tableBtn->setCursor(Qt::PointingHandCursor); + m_tableBtn->setStyleSheet( + "QPushButton { " + " background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #E3F2FD, stop:1 #BBDEFB); " + " color: #1565C0; " + " border: none; " + " border-top: 1px solid #E0E0E0; " + " border-bottom-left-radius: 8px; " + " border-bottom-right-radius: 8px; " + " font-size: 13px; " + " font-weight: 500; " + " padding: 0px 16px; " + "}" + "QPushButton:hover { background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #BBDEFB, stop:1 #90CAF9); }" + "QPushButton:pressed { background: #90CAF9; }"); + connect(m_tableBtn, &QPushButton::clicked, this, [this]() + { + if (!m_step.tableRefs.isEmpty()) emit tableExpandRequested(m_step.tableRefs.first()); }); + mainLayout->addWidget(m_tableBtn); + + // 有表格按钮时,主容器底部不要圆角 + m_container->setStyleSheet( + "QWidget#stepContainer { background: white; border: 1px solid #E0E0E0; border-bottom: none; " + "border-top-left-radius: 8px; border-top-right-radius: 8px; " + "border-bottom-left-radius: 0px; border-bottom-right-radius: 0px; }"); + } + else + { + m_container->setStyleSheet( + "QWidget#stepContainer { background: white; border: 1px solid #E0E0E0; border-radius: 8px; }"); + } +} + +void StepItemWidget::setHighlighted(bool highlighted) +{ + m_container->setStyleSheet(highlighted + ? "QWidget#stepContainer { background: #FFF8E1; border: 2px solid #FFA000; border-radius: 8px; }" + : "QWidget#stepContainer { background: white; border: 1px solid #E0E0E0; border-radius: 8px; }"); +} + +void StepItemWidget::updateStatus(StepStatus status) +{ + m_step.status = status; + updateStatusDisplay(); + if (m_confirmCheckbox) + { + m_confirmCheckbox->blockSignals(true); + m_confirmCheckbox->setChecked(status == StepStatus::Confirmed); + m_confirmCheckbox->blockSignals(false); + } +} + +void StepItemWidget::updateStatusDisplay() +{ + QString statusText, bgColor, textColor = "white"; + switch (m_step.status) + { + case StepStatus::Pending: + statusText = "待执行"; + bgColor = "#78909C"; + break; + case StepStatus::InProgress: + statusText = "执行中"; + bgColor = "#1976D2"; + break; + case StepStatus::Passed: + case StepStatus::Confirmed: + statusText = "✓ 完成"; + bgColor = "#388E3C"; + break; + case StepStatus::Failed: + statusText = "✗ 失败"; + bgColor = "#D32F2F"; + break; + case StepStatus::Skipped: + statusText = "跳过"; + bgColor = "#FFA000"; + textColor = "#333"; + break; + } + m_statusLabel->setText(statusText); + m_statusLabel->setStyleSheet(QString( + "QLabel { background: %1; color: %2; border-radius: 6px; padding: 6px 12px; font-size: 13px; font-weight: bold; }") + .arg(bgColor) + .arg(textColor)); +} + +void StepItemWidget::onCheckboxToggled(bool checked) +{ + if (checked) + { + OverlayDialog::question(this, "确认步骤", + QString("确定已完成此步骤?\n\n%1").arg(m_step.content.left(100)), + [this](bool accepted) + { + if (accepted) + { + emit confirmRequested(m_step.id); + } + else + { + m_confirmCheckbox->blockSignals(true); + m_confirmCheckbox->setChecked(false); + m_confirmCheckbox->blockSignals(false); + } + }); + } + else + { + OverlayDialog::input(this, "取消确认", "请输入取消原因:", + [this](bool ok, const QString &reason) + { + if (ok && !reason.isEmpty()) + { + emit confirmCancelled(m_step.id, reason); + } + else + { + m_confirmCheckbox->blockSignals(true); + m_confirmCheckbox->setChecked(true); + m_confirmCheckbox->blockSignals(false); + } + }); + } +} + +void StepItemWidget::onTypeBtnClicked() +{ + // 切换类型 + m_step.type = (m_step.type == StepType::Automatic) ? StepType::Manual : StepType::Automatic; + updateTypeButton(); + + // 如果切换为手动模式,显示确认复选框 + if (m_step.type == StepType::Manual && !m_confirmCheckbox) + { + m_confirmCheckbox = new QCheckBox("确认", this); + m_confirmCheckbox->setStyleSheet( + "QCheckBox { font-size: 15px; color: #455A64; spacing: 10px; }" + "QCheckBox::indicator { width: 28px; height: 28px; border: 2px solid #90A4AE; border-radius: 5px; background: white; }" + "QCheckBox::indicator:checked { background: #43A047; border-color: #43A047; }" + "QCheckBox::indicator:hover { border-color: #1976D2; }"); + connect(m_confirmCheckbox, &QCheckBox::toggled, this, &StepItemWidget::onCheckboxToggled); + // 找到右侧布局并添加 + if (auto *rightLayout = qobject_cast(m_container->layout()->itemAt(m_container->layout()->count() - 1)->layout())) + { + rightLayout->addWidget(m_confirmCheckbox); + } + } + else if (m_step.type == StepType::Automatic && m_confirmCheckbox) + { + m_confirmCheckbox->hide(); + m_confirmCheckbox->deleteLater(); + m_confirmCheckbox = nullptr; + } + + emit stepTypeChanged(m_step.id, m_step.type); +} + +void StepItemWidget::updateTypeButton() +{ + QString typeText = (m_step.type == StepType::Automatic) ? "自动" : "手动"; + QString typeBg = (m_step.type == StepType::Automatic) ? "#0288D1" : "#F57C00"; + QString hoverBg = (m_step.type == StepType::Automatic) ? "#0277BD" : "#EF6C00"; + m_typeBtn->setText(typeText); + m_typeBtn->setStyleSheet(QString( + "QPushButton { background: %1; color: white; border: none; border-radius: 6px; " + "font-size: 13px; font-weight: bold; padding: 0px; margin: 0px; }" + "QPushButton:hover { background: %2; }" + "QPushButton:pressed { background: %2; }") + .arg(typeBg) + .arg(hoverBg)); +} + +// ============== TableViewWidget ============== + +TableViewWidget::TableViewWidget(const TableData &table, QWidget *parent) + : QWidget(parent), m_table(table), m_expanded(true) +{ + setupUI(); +} + +void TableViewWidget::setupUI() +{ + auto *mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(0); + + // 直接显示表格,不需要头部和展开/收起按钮 + m_tableContainer = new QWidget(this); + auto *tableLayout = new QVBoxLayout(m_tableContainer); + tableLayout->setContentsMargins(8, 8, 8, 8); + + m_tableWidget = new QTableWidget(this); + m_tableWidget->setStyleSheet( + "QTableWidget { border: 1px solid #dcdcdc; gridline-color: #E0E0E0; background: white; }" + "QTableWidget::item { padding: 8px; }" + "QHeaderView::section { background: #ECEFF1; padding: 8px; border: none; border-bottom: 1px solid #CFD8DC; font-weight: bold; }"); + m_tableWidget->horizontalHeader()->setStretchLastSection(true); + m_tableWidget->verticalHeader()->setVisible(false); + m_tableWidget->setAlternatingRowColors(true); + + tableLayout->addWidget(m_tableWidget); + m_tableContainer->setVisible(true); + mainLayout->addWidget(m_tableContainer); + + updateTable(); +} + +void TableViewWidget::setHighlightedFields(const QStringList &fields) +{ + m_highlightedFields = fields; + updateTable(); +} + +void TableViewWidget::toggleExpanded() +{ + m_expanded = !m_expanded; + m_tableContainer->setVisible(m_expanded); + m_toggleBtn->setText(m_expanded ? "收起 ▲" : "展开 ▼"); +} + +void TableViewWidget::updateTable() +{ + if (m_table.columns.isEmpty()) + { + m_tableWidget->setColumnCount(3); + m_tableWidget->setHorizontalHeaderLabels({"字段", "数值", "单位"}); + m_tableWidget->setRowCount(1); + m_tableWidget->setItem(0, 0, new QTableWidgetItem("暂无数据")); + return; + } + + m_tableWidget->setColumnCount(m_table.columns.size()); + QStringList headers; + for (const auto &col : m_table.columns) + headers << col.name; + m_tableWidget->setHorizontalHeaderLabels(headers); + + int rowCount = m_table.rows.isEmpty() ? 1 : m_table.rows.size(); + m_tableWidget->setRowCount(rowCount); + + for (int col = 0; col < m_table.columns.size(); ++col) + { + auto *item = new QTableWidgetItem("-"); + item->setTextAlignment(Qt::AlignCenter); + if (m_highlightedFields.contains(m_table.columns[col].id)) + { + item->setBackground(QColor("#FFF8E1")); + } + m_tableWidget->setItem(0, col, item); + } +} + +// ============== ProcedurePlayerWidget ============== + +ProcedurePlayerWidget::ProcedurePlayerWidget(ProcedureManager *manager, QWidget *parent) + : QWidget(parent), m_manager(manager) +{ + setupUI(); + connect(m_manager, &ProcedureManager::stepStatusChanged, this, &ProcedurePlayerWidget::onStepStatusChanged); +} + +ProcedurePlayerWidget::~ProcedurePlayerWidget() {} + +void ProcedurePlayerWidget::setupUI() +{ + auto *mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(0); + + // 顶部工具栏 - Material Design 深色 + auto *headerWidget = new QWidget(this); + headerWidget->setStyleSheet("background: #263238;"); + headerWidget->setFixedHeight(56); + + auto *headerLayout = new QHBoxLayout(headerWidget); + headerLayout->setContentsMargins(8, 0, 16, 0); + headerLayout->setSpacing(12); + + m_backBtn = new QPushButton("返回列表", this); + m_backBtn->setFixedHeight(40); + m_backBtn->setStyleSheet( + "QPushButton { background: #455A64; color: white; border: none; border-radius: 4px; padding: 0 16px; font-size: 14px; }" + "QPushButton:hover { background: #546E7A; }" + "QPushButton:pressed { background: #37474F; }"); + connect(m_backBtn, &QPushButton::clicked, this, &ProcedurePlayerWidget::onBackClicked); + headerLayout->addWidget(m_backBtn); + + m_procedureIdLabel = new QLabel(this); + m_procedureIdLabel->setStyleSheet("background: #1976D2; color: white; border-radius: 4px; padding: 8px 16px; font-size: 14px; font-weight: bold;"); + headerLayout->addWidget(m_procedureIdLabel); + + m_procedureNameLabel = new QLabel(this); + m_procedureNameLabel->setStyleSheet("color: white; font-size: 16px; font-weight: bold; margin-left: 8px;"); + headerLayout->addWidget(m_procedureNameLabel, 1); + + mainLayout->addWidget(headerWidget); + + // 内容区域 + auto *contentSplitter = new QSplitter(Qt::Horizontal, this); + + // 左侧导航 + auto *navWidget = new QWidget(this); + navWidget->setFixedWidth(280); + navWidget->setStyleSheet("background: white; border-right: 1px solid #E0E0E0;"); + + auto *navLayout = new QVBoxLayout(navWidget); + navLayout->setContentsMargins(0, 0, 0, 0); + + auto *navTitle = new QLabel("步骤导航", navWidget); + navTitle->setStyleSheet( + "font-size: 15px; font-weight: bold; color: #263238; padding: 16px; " + "background: #FAFAFA; border-bottom: 1px solid #E0E0E0;"); + navLayout->addWidget(navTitle); + + m_navigationTree = new QTreeWidget(navWidget); + m_navigationTree->setHeaderHidden(true); + m_navigationTree->setStyleSheet( + "QTreeWidget { border: none; background: white; font-size: 13px; }" + "QTreeWidget::item { padding: 10px 16px; border-bottom: 1px solid #F5F5F5; }" + "QTreeWidget::item:selected { background: #E3F2FD; color: #1976D2; border-left: 3px solid #1976D2; }" + "QTreeWidget::item:hover { background: #F5F5F5; }"); + connect(m_navigationTree, &QTreeWidget::itemClicked, this, &ProcedurePlayerWidget::onNavigationItemClicked); + navLayout->addWidget(m_navigationTree, 1); + + contentSplitter->addWidget(navWidget); + + // 右侧内容区 + auto *rightContainer = new QWidget(this); + auto *rightLayout = new QVBoxLayout(rightContainer); + rightLayout->setContentsMargins(0, 0, 0, 0); + rightLayout->setSpacing(0); + + // 步骤标题区 + m_stepTitleLabel = new QLabel("请选择一个步骤", rightContainer); + m_stepTitleLabel->setWordWrap(true); + m_stepTitleLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); + m_stepTitleLabel->setMinimumWidth(0); + m_stepTitleLabel->setMinimumHeight(50); + m_stepTitleLabel->setStyleSheet( + "font-size: 15px; font-weight: bold; color: #263238; padding: 16px 24px; " + "background: white; border-bottom: 1px solid #E0E0E0;"); + rightLayout->addWidget(m_stepTitleLabel); + + m_contentArea = new QScrollArea(rightContainer); + m_contentArea->setWidgetResizable(true); + m_contentArea->setStyleSheet("QScrollArea { border: none; background: #FAFAFA; }"); + + m_contentWidget = new QWidget(); + m_contentWidget->setStyleSheet("background: #FAFAFA;"); + m_contentLayout = new QVBoxLayout(m_contentWidget); + m_contentLayout->setContentsMargins(24, 24, 24, 24); + m_contentLayout->setSpacing(16); + m_contentLayout->setAlignment(Qt::AlignTop); + + m_contentArea->setWidget(m_contentWidget); + rightLayout->addWidget(m_contentArea, 1); + + contentSplitter->addWidget(rightContainer); + + contentSplitter->setStretchFactor(0, 0); + contentSplitter->setStretchFactor(1, 1); + + mainLayout->addWidget(contentSplitter, 1); + + // 创建表格浮层(初始隐藏) + m_tableOverlay = new TableOverlayWidget(this); + m_tableOverlay->setGeometry(0, 0, width(), height()); + m_tableOverlay->hide(); +} + +void ProcedurePlayerWidget::loadProcedure(const QString &procedureId) +{ + m_currentProcedure = m_manager->loadProcedure(procedureId); + m_procedureIdLabel->setText(m_currentProcedure.id); + m_procedureNameLabel->setText(m_currentProcedure.name); + populateNavigation(); + populateStepContent(); +} + +void ProcedurePlayerWidget::populateNavigation() +{ + m_navigationTree->clear(); + m_navItems.clear(); + + for (const auto &group : m_currentProcedure.taskGroups) + { + auto *groupItem = new QTreeWidgetItem(m_navigationTree); + groupItem->setText(0, group.name); + groupItem->setData(0, Qt::UserRole, group.id); + groupItem->setExpanded(true); + QFont font = groupItem->font(0); + font.setBold(true); + groupItem->setFont(0, font); + + for (const auto &step : group.steps) + { + auto *stepItem = new QTreeWidgetItem(groupItem); + QString typeIcon = (step.type == StepType::Automatic) ? "⚡" : "✋"; + QString statusIcon; + switch (step.status) + { + case StepStatus::Passed: + case StepStatus::Confirmed: + statusIcon = "✅"; + break; + case StepStatus::Failed: + statusIcon = "❌"; + break; + case StepStatus::InProgress: + statusIcon = "🔄"; + break; + default: + statusIcon = "○"; + break; + } + stepItem->setText(0, QString("%1 %2 %3").arg(statusIcon).arg(typeIcon).arg(step.content.left(20) + "...")); + stepItem->setData(0, Qt::UserRole, step.id); + + if (step.status == StepStatus::Passed || step.status == StepStatus::Confirmed) + { + stepItem->setForeground(0, QColor("#388E3C")); + } + else if (step.status == StepStatus::Failed) + { + stepItem->setForeground(0, QColor("#D32F2F")); + } + m_navItems[step.id] = stepItem; + } + } +} + +void ProcedurePlayerWidget::populateStepContent() +{ + m_stepWidgets.clear(); + m_tableWidgets.clear(); + m_allSteps.clear(); + + // 清空浮层中的表格 + m_tableOverlay->clearTables(); + + // 清空当前内容 + QLayoutItem *item; + while ((item = m_contentLayout->takeAt(0)) != nullptr) + { + if (item->widget()) + item->widget()->deleteLater(); + delete item; + } + + // 收集所有表格定义,添加到浮层 + for (const auto &table : m_currentProcedure.tables) + { + auto *tableWidget = new TableViewWidget(table, nullptr); + m_tableOverlay->addTable(table.id, table.name, tableWidget); + } + + // 预存所有步骤数据 + for (const auto &group : m_currentProcedure.taskGroups) + { + for (const auto &step : group.steps) + { + m_allSteps[step.id] = step; + } + } + + // 默认显示第一个组的所有步骤 + if (!m_currentProcedure.taskGroups.isEmpty()) + { + showGroupSteps(m_currentProcedure.taskGroups.first().id); + } +} + +void ProcedurePlayerWidget::showStep(const QString &stepId) +{ + if (!m_allSteps.contains(stepId)) + return; + + // 清空当前内容 + QLayoutItem *item; + while ((item = m_contentLayout->takeAt(0)) != nullptr) + { + if (item->widget()) + item->widget()->deleteLater(); + delete item; + } + m_stepWidgets.clear(); + + const StepData &step = m_allSteps[stepId]; + m_currentStepId = stepId; + + // 更新标题 + m_stepTitleLabel->setText(step.content.left(50) + (step.content.length() > 50 ? "..." : "")); + + // 创建步骤组件 + auto *stepWidget = new StepItemWidget(step, m_contentWidget); + connect(stepWidget, &StepItemWidget::confirmRequested, this, &ProcedurePlayerWidget::onStepConfirmRequested); + connect(stepWidget, &StepItemWidget::confirmCancelled, this, &ProcedurePlayerWidget::onStepConfirmCancelled); + connect(stepWidget, &StepItemWidget::tableExpandRequested, this, &ProcedurePlayerWidget::onTableExpandRequested); + m_stepWidgets[stepId] = stepWidget; + m_contentLayout->addWidget(stepWidget); + + // 高亮导航项 + if (m_navItems.contains(stepId)) + { + m_navigationTree->setCurrentItem(m_navItems[stepId]); + } +} + +void ProcedurePlayerWidget::showGroupSteps(const QString &groupId) +{ + // 清空当前内容 + QLayoutItem *item; + while ((item = m_contentLayout->takeAt(0)) != nullptr) + { + if (item->widget()) + item->widget()->deleteLater(); + delete item; + } + m_stepWidgets.clear(); + m_currentGroupId = groupId; + + // 查找对应的组 + for (const auto &group : m_currentProcedure.taskGroups) + { + if (group.id == groupId) + { + // 更新标题 + m_stepTitleLabel->setText(group.name); + + // 显示该组下的所有步骤 + for (const auto &step : group.steps) + { + auto *stepWidget = new StepItemWidget(step, m_contentWidget); + connect(stepWidget, &StepItemWidget::confirmRequested, this, &ProcedurePlayerWidget::onStepConfirmRequested); + connect(stepWidget, &StepItemWidget::confirmCancelled, this, &ProcedurePlayerWidget::onStepConfirmCancelled); + connect(stepWidget, &StepItemWidget::tableExpandRequested, this, &ProcedurePlayerWidget::onTableExpandRequested); + m_stepWidgets[step.id] = stepWidget; + m_contentLayout->addWidget(stepWidget); + } + break; + } + } +} + +void ProcedurePlayerWidget::onBackClicked() { emit backToListRequested(); } + +void ProcedurePlayerWidget::onNavigationItemClicked(QTreeWidgetItem *item, int) +{ + QString id = item->data(0, Qt::UserRole).toString(); + + // 检查是否是组(没有父节点的是组) + if (item->parent() == nullptr) + { + // 点击的是组,显示该组的所有步骤 + showGroupSteps(id); + } + else + { + // 点击的是步骤,显示单个步骤 + if (m_allSteps.contains(id)) + { + showStep(id); + } + } +} + +void ProcedurePlayerWidget::onStepConfirmRequested(const QString &stepId) +{ + m_manager->confirmStep(m_currentProcedure.id, stepId); +} + +void ProcedurePlayerWidget::onStepConfirmCancelled(const QString &stepId, const QString &reason) +{ + m_manager->cancelStepConfirm(m_currentProcedure.id, stepId, reason); +} + +void ProcedurePlayerWidget::onTableExpandRequested(const QString &tableId) +{ + // 显示浮层并切换到指定表格 + m_tableOverlay->showTable(tableId); + m_tableOverlay->showOverlay(); + + // 调整浮层位置和大小 - 覆盖整个页面 + int overlayWidth = width(); + int overlayHeight = height(); + int overlayX = 0; + int overlayY = 0; + + m_tableOverlay->setGeometry(overlayX, overlayY, overlayWidth, overlayHeight); + m_tableOverlay->raise(); +} + +void ProcedurePlayerWidget::onStepStatusChanged(const QString &stepId, StepStatus status) +{ + if (m_stepWidgets.contains(stepId)) + { + m_stepWidgets[stepId]->updateStatus(status); + } + updateNavigationStatus(); +} + +void ProcedurePlayerWidget::updateNavigationStatus() +{ + populateNavigation(); +} diff --git a/widgets/procedureplayerwidget.h b/widgets/procedureplayerwidget.h new file mode 100644 index 0000000..7a20153 --- /dev/null +++ b/widgets/procedureplayerwidget.h @@ -0,0 +1,158 @@ +#ifndef PROCEDUREPLAYERWIDGET_H +#define PROCEDUREPLAYERWIDGET_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../procedure/proceduredata.h" +#include "tableoverlaywidget.h" + +class ProcedureManager; + +/** + * @class StepItemWidget + * @brief 步骤项组件 + * + * 显示单个步骤的内容,包括: + * - 步骤类型标记(自动/手动) + * - 步骤内容 + * - 执行状态 + * - 确认复选框(手动步骤) + */ +class StepItemWidget : public QWidget +{ + Q_OBJECT + +public: + explicit StepItemWidget(const StepData &step, QWidget *parent = nullptr); + + QString stepId() const { return m_step.id; } + void setHighlighted(bool highlighted); + void updateStatus(StepStatus status); + +signals: + void confirmRequested(const QString &stepId); + void confirmCancelled(const QString &stepId, const QString &reason); + void tableExpandRequested(const QString &tableId); + void stepTypeChanged(const QString &stepId, StepType newType); + +private slots: + void onCheckboxToggled(bool checked); + void onTypeBtnClicked(); + +private: + void setupUI(); + void updateStatusDisplay(); + void updateTypeButton(); + + StepData m_step; + QPushButton *m_typeBtn; + QLabel *m_contentLabel; + QLabel *m_statusLabel; + QCheckBox *m_confirmCheckbox; + QPushButton *m_tableBtn; + QWidget *m_container; +}; + +/** + * @class TableViewWidget + * @brief 表格展示组件 + * + * 内联展示相关表格,可展开/收起 + */ +class TableViewWidget : public QWidget +{ + Q_OBJECT + +public: + explicit TableViewWidget(const TableData &table, QWidget *parent = nullptr); + + void setHighlightedFields(const QStringList &fields); + void toggleExpanded(); + bool isExpanded() const { return m_expanded; } + +private: + void setupUI(); + void updateTable(); + + TableData m_table; + QTableWidget *m_tableWidget; + QLabel *m_headerLabel; + QPushButton *m_toggleBtn; + QWidget *m_tableContainer; + QStringList m_highlightedFields; + bool m_expanded; +}; + +/** + * @class ProcedurePlayerWidget + * @brief 规程执行页面 + * + * 主执行界面,包括: + * - 顶部信息栏(规程ID、名称、返回按钮) + * - 左侧步骤导航 + * - 中间步骤内容区 + * - 内联表格展示 + */ +class ProcedurePlayerWidget : public QWidget +{ + Q_OBJECT + +public: + explicit ProcedurePlayerWidget(ProcedureManager *manager, QWidget *parent = nullptr); + ~ProcedurePlayerWidget(); + + void loadProcedure(const QString &procedureId); + +signals: + void backToListRequested(); + +private slots: + void onBackClicked(); + void onNavigationItemClicked(QTreeWidgetItem *item, int column); + void onStepConfirmRequested(const QString &stepId); + void onStepConfirmCancelled(const QString &stepId, const QString &reason); + void onTableExpandRequested(const QString &tableId); + void onStepStatusChanged(const QString &stepId, StepStatus status); + +private: + void setupUI(); + void populateNavigation(); + void populateStepContent(); + void showStep(const QString &stepId); + void showGroupSteps(const QString &groupId); + void updateNavigationStatus(); + + ProcedureManager *m_manager; + ProcedureData m_currentProcedure; + QString m_currentStepId; + QString m_currentGroupId; + + // UI 组件 + QLabel *m_procedureIdLabel; + QLabel *m_procedureNameLabel; + QPushButton *m_backBtn; + QTreeWidget *m_navigationTree; + QScrollArea *m_contentArea; + QWidget *m_contentWidget; + QVBoxLayout *m_contentLayout; + QLabel *m_stepTitleLabel; // 当前步骤标题 + + QMap m_stepWidgets; + QMap m_tableWidgets; + QMap m_navItems; + QMap m_allSteps; // 存储所有步骤数据 + + // 表格浮层 + TableOverlayWidget *m_tableOverlay; +}; + +#endif // PROCEDUREPLAYERWIDGET_H diff --git a/widgets/settingswidget.cpp b/widgets/settingswidget.cpp new file mode 100644 index 0000000..8ef430c --- /dev/null +++ b/widgets/settingswidget.cpp @@ -0,0 +1,583 @@ +#include "settingswidget.h" +#include "overlaydialogswidget.h" +#include +#include + +const QString SettingsWidget::LABEL_STYLE = + "font-size: 14px; color: #37474F;"; + +const QString SettingsWidget::VALUE_STYLE = + "font-size: 14px; color: #1976D2; font-weight: bold;"; + +const QString SettingsWidget::BUTTON_STYLE = + "QPushButton { background: #ECEFF1; color: #37474F; border: none; border-radius: 6px; " + "padding: 10px 20px; font-size: 14px; font-weight: 500; }" + "QPushButton:hover { background: #CFD8DC; }" + "QPushButton:pressed { background: #B0BEC5; }"; + +const QString SettingsWidget::BUTTON_PRIMARY_STYLE = + "QPushButton { background: #1976D2; color: white; border: none; border-radius: 6px; " + "padding: 10px 20px; font-size: 14px; font-weight: 500; }" + "QPushButton:hover { background: #1565C0; }" + "QPushButton:pressed { background: #0D47A1; }"; + +const QString SettingsWidget::BUTTON_DANGER_STYLE = + "QPushButton { background: #D32F2F; color: white; border: none; border-radius: 6px; " + "padding: 10px 20px; font-size: 14px; font-weight: 500; }" + "QPushButton:hover { background: #C62828; }" + "QPushButton:pressed { background: #B71C1C; }"; + +SettingsWidget::SettingsWidget(QWidget *parent) + : QWidget(parent) +{ + setupUI(); +} + +void SettingsWidget::setupUI() +{ + auto *mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(0); + + // 标题栏 + auto *headerWidget = new QWidget(this); + headerWidget->setFixedHeight(60); + headerWidget->setStyleSheet("background: qlineargradient(x1:0, y1:0, x2:1, y2:0, " + "stop:0 #1976D2, stop:1 #1565C0);"); + + auto *headerLayout = new QHBoxLayout(headerWidget); + headerLayout->setContentsMargins(16, 0, 16, 0); + + m_backBtn = new QPushButton("← 返回", this); + m_backBtn->setStyleSheet( + "QPushButton { background: rgba(255,255,255,0.2); color: white; border: none; " + "border-radius: 6px; padding: 8px 16px; font-size: 14px; }" + "QPushButton:hover { background: rgba(255,255,255,0.3); }"); + m_backBtn->setCursor(Qt::PointingHandCursor); + connect(m_backBtn, &QPushButton::clicked, this, &SettingsWidget::backRequested); + headerLayout->addWidget(m_backBtn); + + m_titleLabel = new QLabel("系统设置", this); + m_titleLabel->setStyleSheet("color: white; font-size: 18px; font-weight: bold;"); + m_titleLabel->setAlignment(Qt::AlignCenter); + headerLayout->addWidget(m_titleLabel, 1); + + // 占位,保持标题居中 + auto *spacer = new QWidget(this); + spacer->setFixedWidth(80); + headerLayout->addWidget(spacer); + + mainLayout->addWidget(headerWidget); + + // 选项卡 + m_tabWidget = new QTabWidget(this); + m_tabWidget->setStyleSheet( + "QTabWidget::pane { border: none; background: #F5F5F5; }" + "QTabBar::tab { background: #ECEFF1; color: #546E7A; padding: 12px 20px; " + "font-size: 14px; border: none; margin-right: 2px; min-width: 70px; }" + "QTabBar::tab:selected { background: white; color: #1976D2; font-weight: bold; " + "border-bottom: 3px solid #1976D2; }" + "QTabBar::tab:hover { background: #CFD8DC; }"); + m_tabWidget->tabBar()->setExpanding(false); + + m_tabWidget->addTab(createDeviceInfoTab(), "设备信息"); + m_tabWidget->addTab(createSystemConfigTab(), "系统配置"); + m_tabWidget->addTab(createCalibrationTab(), "校准设置"); + m_tabWidget->addTab(createNetworkTab(), "网络设置"); + m_tabWidget->addTab(createAboutTab(), "关于"); + + mainLayout->addWidget(m_tabWidget, 1); +} + +QGroupBox *SettingsWidget::createStyledGroupBox(const QString &title) +{ + auto *groupBox = new QGroupBox(title, this); + groupBox->setStyleSheet( + "QGroupBox { font-size: 15px; font-weight: bold; color: #37474F; " + "border: 1px solid #E0E0E0; border-radius: 8px; margin-top: 12px; padding-top: 8px; " + "background: white; }" + "QGroupBox::title { subcontrol-origin: margin; left: 16px; padding: 0 8px; }"); + return groupBox; +} + +QWidget *SettingsWidget::createDeviceInfoTab() +{ + auto *scrollArea = new QScrollArea(this); + scrollArea->setWidgetResizable(true); + scrollArea->setStyleSheet("QScrollArea { border: none; background: #F5F5F5; }"); + + auto *content = new QWidget(); + auto *layout = new QVBoxLayout(content); + layout->setContentsMargins(24, 24, 24, 24); + layout->setSpacing(16); + + // 设备信息组 + auto *deviceGroup = createStyledGroupBox("设备信息"); + auto *deviceLayout = new QGridLayout(deviceGroup); + deviceLayout->setContentsMargins(16, 24, 16, 16); + deviceLayout->setSpacing(12); + + deviceLayout->addWidget(new QLabel("设备型号:", this), 0, 0); + m_deviceModelLabel = new QLabel("iCALI-Pro 2000", this); + m_deviceModelLabel->setStyleSheet(VALUE_STYLE); + deviceLayout->addWidget(m_deviceModelLabel, 0, 1); + + deviceLayout->addWidget(new QLabel("序列号:", this), 1, 0); + m_serialNumberLabel = new QLabel("ICALI-2024-001234", this); + m_serialNumberLabel->setStyleSheet(VALUE_STYLE); + deviceLayout->addWidget(m_serialNumberLabel, 1, 1); + + deviceLayout->addWidget(new QLabel("固件版本:", this), 2, 0); + m_firmwareVersionLabel = new QLabel("v2.1.0 (Build 20241231)", this); + m_firmwareVersionLabel->setStyleSheet(VALUE_STYLE); + deviceLayout->addWidget(m_firmwareVersionLabel, 2, 1); + + deviceLayout->addWidget(new QLabel("硬件版本:", this), 3, 0); + m_hardwareVersionLabel = new QLabel("Rev.C", this); + m_hardwareVersionLabel->setStyleSheet(VALUE_STYLE); + deviceLayout->addWidget(m_hardwareVersionLabel, 3, 1); + + deviceLayout->setColumnStretch(1, 1); + layout->addWidget(deviceGroup); + + // 存储信息组 + auto *storageGroup = createStyledGroupBox("存储信息"); + auto *storageLayout = new QGridLayout(storageGroup); + storageLayout->setContentsMargins(16, 24, 16, 16); + storageLayout->setSpacing(12); + + storageLayout->addWidget(new QLabel("内部存储:", this), 0, 0); + auto *internalStorage = new QLabel("已用 2.3GB / 8GB", this); + internalStorage->setStyleSheet(VALUE_STYLE); + storageLayout->addWidget(internalStorage, 0, 1); + + storageLayout->addWidget(new QLabel("SD卡:", this), 1, 0); + auto *sdCard = new QLabel("已用 1.2GB / 32GB", this); + sdCard->setStyleSheet(VALUE_STYLE); + storageLayout->addWidget(sdCard, 1, 1); + + storageLayout->setColumnStretch(1, 1); + layout->addWidget(storageGroup); + + // 运行状态组 + auto *statusGroup = createStyledGroupBox("运行状态"); + auto *statusLayout = new QGridLayout(statusGroup); + statusLayout->setContentsMargins(16, 24, 16, 16); + statusLayout->setSpacing(12); + + statusLayout->addWidget(new QLabel("运行时间:", this), 0, 0); + auto *uptime = new QLabel("3天 12小时 45分", this); + uptime->setStyleSheet(VALUE_STYLE); + statusLayout->addWidget(uptime, 0, 1); + + statusLayout->addWidget(new QLabel("CPU温度:", this), 1, 0); + auto *cpuTemp = new QLabel("42°C", this); + cpuTemp->setStyleSheet(VALUE_STYLE); + statusLayout->addWidget(cpuTemp, 1, 1); + + statusLayout->addWidget(new QLabel("电池状态:", this), 2, 0); + auto *battery = new QLabel("充电中 85%", this); + battery->setStyleSheet("font-size: 14px; color: #43A047; font-weight: bold;"); + statusLayout->addWidget(battery, 2, 1); + + statusLayout->setColumnStretch(1, 1); + layout->addWidget(statusGroup); + + layout->addStretch(); + scrollArea->setWidget(content); + return scrollArea; +} + +QWidget *SettingsWidget::createSystemConfigTab() +{ + auto *scrollArea = new QScrollArea(this); + scrollArea->setWidgetResizable(true); + scrollArea->setStyleSheet("QScrollArea { border: none; background: #F5F5F5; }"); + + auto *content = new QWidget(); + auto *layout = new QVBoxLayout(content); + layout->setContentsMargins(24, 24, 24, 24); + layout->setSpacing(16); + + // 显示设置组 + auto *displayGroup = createStyledGroupBox("显示设置"); + auto *displayLayout = new QGridLayout(displayGroup); + displayLayout->setContentsMargins(16, 24, 16, 16); + displayLayout->setSpacing(12); + + displayLayout->addWidget(new QLabel("语言:", this), 0, 0); + m_languageCombo = new QComboBox(this); + m_languageCombo->addItems({"简体中文", "English"}); + m_languageCombo->setStyleSheet( + "QComboBox { padding: 8px 12px; border: 1px solid #E0E0E0; border-radius: 6px; " + "background: white; font-size: 14px; min-height: 36px; }" + "QComboBox::drop-down { subcontrol-origin: padding; subcontrol-position: top right; " + "width: 36px; border: none; border-left: 1px solid #E0E0E0; background: #f5f5f5; " + "border-top-right-radius: 6px; border-bottom-right-radius: 6px; }" + "QComboBox::down-arrow { border-left: 6px solid transparent; border-right: 6px solid transparent; " + "border-top: 8px solid #666; }" + "QComboBox QAbstractItemView { background: white; border: 1px solid #ddd; border-radius: 6px; " + "selection-background-color: #e3f2fd; outline: none; }" + "QComboBox QAbstractItemView::item { min-height: 40px; padding: 10px 12px; }"); + m_languageCombo->setCursor(Qt::PointingHandCursor); + displayLayout->addWidget(m_languageCombo, 0, 1); + + displayLayout->addWidget(new QLabel("主题:", this), 1, 0); + m_themeCombo = new QComboBox(this); + m_themeCombo->addItems({"浅色", "深色", "跟随系统"}); + m_themeCombo->setStyleSheet( + "QComboBox { padding: 8px 12px; border: 1px solid #E0E0E0; border-radius: 6px; " + "background: white; font-size: 14px; min-height: 36px; }" + "QComboBox::drop-down { subcontrol-origin: padding; subcontrol-position: top right; " + "width: 36px; border: none; border-left: 1px solid #E0E0E0; background: #f5f5f5; " + "border-top-right-radius: 6px; border-bottom-right-radius: 6px; }" + "QComboBox::down-arrow { border-left: 6px solid transparent; border-right: 6px solid transparent; " + "border-top: 8px solid #666; }" + "QComboBox QAbstractItemView { background: white; border: 1px solid #ddd; border-radius: 6px; " + "selection-background-color: #e3f2fd; outline: none; }" + "QComboBox QAbstractItemView::item { min-height: 40px; padding: 10px 12px; }"); + m_themeCombo->setCursor(Qt::PointingHandCursor); + displayLayout->addWidget(m_themeCombo, 1, 1); + + displayLayout->addWidget(new QLabel("屏幕亮度:", this), 2, 0); + m_brightnessSpinBox = new QSpinBox(this); + m_brightnessSpinBox->setRange(10, 100); + m_brightnessSpinBox->setValue(80); + m_brightnessSpinBox->setSuffix("%"); + m_brightnessSpinBox->setStyleSheet( + "QSpinBox { padding: 8px; border: 1px solid #E0E0E0; border-radius: 4px; " + "background: white; font-size: 14px; }"); + displayLayout->addWidget(m_brightnessSpinBox, 2, 1); + + displayLayout->setColumnStretch(1, 1); + layout->addWidget(displayGroup); + + // 声音设置组 + auto *soundGroup = createStyledGroupBox("声音设置"); + auto *soundLayout = new QGridLayout(soundGroup); + soundLayout->setContentsMargins(16, 24, 16, 16); + soundLayout->setSpacing(12); + + soundLayout->addWidget(new QLabel("音量:", this), 0, 0); + m_volumeSpinBox = new QSpinBox(this); + m_volumeSpinBox->setRange(0, 100); + m_volumeSpinBox->setValue(50); + m_volumeSpinBox->setSuffix("%"); + m_volumeSpinBox->setStyleSheet( + "QSpinBox { padding: 8px; border: 1px solid #E0E0E0; border-radius: 4px; " + "background: white; font-size: 14px; }"); + soundLayout->addWidget(m_volumeSpinBox, 0, 1); + + soundLayout->setColumnStretch(1, 1); + layout->addWidget(soundGroup); + + // 省电设置组 + auto *powerGroup = createStyledGroupBox("省电设置"); + auto *powerLayout = new QGridLayout(powerGroup); + powerLayout->setContentsMargins(16, 24, 16, 16); + powerLayout->setSpacing(12); + + m_autoSleepCheck = new QCheckBox("自动休眠", this); + m_autoSleepCheck->setChecked(true); + m_autoSleepCheck->setStyleSheet("QCheckBox { font-size: 14px; }"); + powerLayout->addWidget(m_autoSleepCheck, 0, 0, 1, 2); + + powerLayout->addWidget(new QLabel("休眠时间:", this), 1, 0); + m_sleepTimeSpinBox = new QSpinBox(this); + m_sleepTimeSpinBox->setRange(1, 60); + m_sleepTimeSpinBox->setValue(10); + m_sleepTimeSpinBox->setSuffix(" 分钟"); + m_sleepTimeSpinBox->setStyleSheet( + "QSpinBox { padding: 8px; border: 1px solid #E0E0E0; border-radius: 4px; " + "background: white; font-size: 14px; }"); + powerLayout->addWidget(m_sleepTimeSpinBox, 1, 1); + + powerLayout->setColumnStretch(1, 1); + layout->addWidget(powerGroup); + + // 按钮区域 + auto *buttonLayout = new QHBoxLayout(); + buttonLayout->addStretch(); + + auto *resetBtn = new QPushButton("恢复默认", this); + resetBtn->setStyleSheet(BUTTON_STYLE); + resetBtn->setCursor(Qt::PointingHandCursor); + connect(resetBtn, &QPushButton::clicked, this, &SettingsWidget::onResetClicked); + buttonLayout->addWidget(resetBtn); + + auto *saveBtn = new QPushButton("保存设置", this); + saveBtn->setStyleSheet(BUTTON_PRIMARY_STYLE); + saveBtn->setCursor(Qt::PointingHandCursor); + connect(saveBtn, &QPushButton::clicked, this, &SettingsWidget::onSaveClicked); + buttonLayout->addWidget(saveBtn); + + layout->addLayout(buttonLayout); + layout->addStretch(); + + scrollArea->setWidget(content); + return scrollArea; +} + +QWidget *SettingsWidget::createCalibrationTab() +{ + auto *scrollArea = new QScrollArea(this); + scrollArea->setWidgetResizable(true); + scrollArea->setStyleSheet("QScrollArea { border: none; background: #F5F5F5; }"); + + auto *content = new QWidget(); + auto *layout = new QVBoxLayout(content); + layout->setContentsMargins(24, 24, 24, 24); + layout->setSpacing(16); + + // 校准状态组 + auto *statusGroup = createStyledGroupBox("校准状态"); + auto *statusLayout = new QGridLayout(statusGroup); + statusLayout->setContentsMargins(16, 24, 16, 16); + statusLayout->setSpacing(12); + + statusLayout->addWidget(new QLabel("上次校准:", this), 0, 0); + m_lastCalibrationLabel = new QLabel("2024-12-15 09:30:00", this); + m_lastCalibrationLabel->setStyleSheet(VALUE_STYLE); + statusLayout->addWidget(m_lastCalibrationLabel, 0, 1); + + statusLayout->addWidget(new QLabel("校准状态:", this), 1, 0); + m_calibrationStatusLabel = new QLabel("✓ 已校准", this); + m_calibrationStatusLabel->setStyleSheet("font-size: 14px; color: #43A047; font-weight: bold;"); + statusLayout->addWidget(m_calibrationStatusLabel, 1, 1); + + statusLayout->addWidget(new QLabel("下次校准:", this), 2, 0); + auto *nextCal = new QLabel("2025-12-15 (剩余350天)", this); + nextCal->setStyleSheet(VALUE_STYLE); + statusLayout->addWidget(nextCal, 2, 1); + + statusLayout->setColumnStretch(1, 1); + layout->addWidget(statusGroup); + + // 校准操作组 + auto *actionGroup = createStyledGroupBox("校准操作"); + auto *actionLayout = new QVBoxLayout(actionGroup); + actionLayout->setContentsMargins(16, 24, 16, 16); + actionLayout->setSpacing(12); + + auto *note = new QLabel("请确保设备处于稳定状态,并连接标准校准源后再进行校准操作。", this); + note->setWordWrap(true); + note->setStyleSheet("font-size: 13px; color: #757575; padding: 8px; " + "background: #FFF8E1; border-radius: 4px;"); + actionLayout->addWidget(note); + + auto *calibrateBtn = new QPushButton("开始校准", this); + calibrateBtn->setStyleSheet(BUTTON_PRIMARY_STYLE); + calibrateBtn->setFixedHeight(44); + calibrateBtn->setCursor(Qt::PointingHandCursor); + connect(calibrateBtn, &QPushButton::clicked, this, &SettingsWidget::onCalibrateClicked); + actionLayout->addWidget(calibrateBtn); + + layout->addWidget(actionGroup); + + layout->addStretch(); + scrollArea->setWidget(content); + return scrollArea; +} + +QWidget *SettingsWidget::createNetworkTab() +{ + auto *scrollArea = new QScrollArea(this); + scrollArea->setWidgetResizable(true); + scrollArea->setStyleSheet("QScrollArea { border: none; background: #F5F5F5; }"); + + auto *content = new QWidget(); + auto *layout = new QVBoxLayout(content); + layout->setContentsMargins(24, 24, 24, 24); + layout->setSpacing(16); + + // 服务器设置组 + auto *serverGroup = createStyledGroupBox("服务器设置"); + auto *serverLayout = new QGridLayout(serverGroup); + serverLayout->setContentsMargins(16, 24, 16, 16); + serverLayout->setSpacing(12); + + serverLayout->addWidget(new QLabel("服务器地址:", this), 0, 0); + m_serverAddressEdit = new QLineEdit(this); + m_serverAddressEdit->setPlaceholderText("例如: 192.168.1.100"); + m_serverAddressEdit->setStyleSheet( + "QLineEdit { padding: 8px; border: 1px solid #E0E0E0; border-radius: 4px; " + "background: white; font-size: 14px; }"); + serverLayout->addWidget(m_serverAddressEdit, 0, 1); + + serverLayout->addWidget(new QLabel("端口号:", this), 1, 0); + m_serverPortSpinBox = new QSpinBox(this); + m_serverPortSpinBox->setRange(1, 65535); + m_serverPortSpinBox->setValue(8080); + m_serverPortSpinBox->setStyleSheet( + "QSpinBox { padding: 8px; border: 1px solid #E0E0E0; border-radius: 4px; " + "background: white; font-size: 14px; }"); + serverLayout->addWidget(m_serverPortSpinBox, 1, 1); + + m_autoConnectCheck = new QCheckBox("启动时自动连接", this); + m_autoConnectCheck->setStyleSheet("QCheckBox { font-size: 14px; }"); + serverLayout->addWidget(m_autoConnectCheck, 2, 0, 1, 2); + + serverLayout->setColumnStretch(1, 1); + layout->addWidget(serverGroup); + + // 连接状态组 + auto *connGroup = createStyledGroupBox("连接状态"); + auto *connLayout = new QGridLayout(connGroup); + connLayout->setContentsMargins(16, 24, 16, 16); + connLayout->setSpacing(12); + + connLayout->addWidget(new QLabel("网络状态:", this), 0, 0); + auto *netStatus = new QLabel("● 已连接 (WiFi)", this); + netStatus->setStyleSheet("font-size: 14px; color: #43A047; font-weight: bold;"); + connLayout->addWidget(netStatus, 0, 1); + + connLayout->addWidget(new QLabel("IP地址:", this), 1, 0); + auto *ipAddr = new QLabel("192.168.1.88", this); + ipAddr->setStyleSheet(VALUE_STYLE); + connLayout->addWidget(ipAddr, 1, 1); + + connLayout->addWidget(new QLabel("服务器状态:", this), 2, 0); + auto *serverStatus = new QLabel("○ 未连接", this); + serverStatus->setStyleSheet("font-size: 14px; color: #757575;"); + connLayout->addWidget(serverStatus, 2, 1); + + connLayout->setColumnStretch(1, 1); + layout->addWidget(connGroup); + + layout->addStretch(); + scrollArea->setWidget(content); + return scrollArea; +} + +QWidget *SettingsWidget::createAboutTab() +{ + auto *scrollArea = new QScrollArea(this); + scrollArea->setWidgetResizable(true); + scrollArea->setStyleSheet("QScrollArea { border: none; background: #F5F5F5; }"); + + auto *content = new QWidget(); + auto *layout = new QVBoxLayout(content); + layout->setContentsMargins(24, 24, 24, 24); + layout->setSpacing(16); + + // 关于组 + auto *aboutGroup = createStyledGroupBox("关于"); + auto *aboutLayout = new QVBoxLayout(aboutGroup); + aboutLayout->setContentsMargins(16, 24, 16, 16); + aboutLayout->setSpacing(12); + + auto *appName = new QLabel("智能校验仪 iCALI", this); + appName->setStyleSheet("font-size: 20px; font-weight: bold; color: #1976D2;"); + appName->setAlignment(Qt::AlignCenter); + aboutLayout->addWidget(appName); + + auto *version = new QLabel("版本 2.1.0", this); + version->setStyleSheet("font-size: 14px; color: #757575;"); + version->setAlignment(Qt::AlignCenter); + aboutLayout->addWidget(version); + + auto *copyright = new QLabel("© 2024 某公司. 保留所有权利.", this); + copyright->setStyleSheet("font-size: 12px; color: #9E9E9E;"); + copyright->setAlignment(Qt::AlignCenter); + aboutLayout->addWidget(copyright); + + layout->addWidget(aboutGroup); + + // 维护操作组 + auto *maintainGroup = createStyledGroupBox("维护操作"); + auto *maintainLayout = new QVBoxLayout(maintainGroup); + maintainLayout->setContentsMargins(16, 24, 16, 16); + maintainLayout->setSpacing(12); + + auto *exportLogBtn = new QPushButton("导出日志", this); + exportLogBtn->setStyleSheet(BUTTON_STYLE); + exportLogBtn->setFixedHeight(44); + exportLogBtn->setCursor(Qt::PointingHandCursor); + connect(exportLogBtn, &QPushButton::clicked, this, &SettingsWidget::onExportLogClicked); + maintainLayout->addWidget(exportLogBtn); + + auto *clearCacheBtn = new QPushButton("清除缓存", this); + clearCacheBtn->setStyleSheet(BUTTON_STYLE); + clearCacheBtn->setFixedHeight(44); + clearCacheBtn->setCursor(Qt::PointingHandCursor); + connect(clearCacheBtn, &QPushButton::clicked, this, &SettingsWidget::onClearCacheClicked); + maintainLayout->addWidget(clearCacheBtn); + + auto *factoryResetBtn = new QPushButton("恢复出厂设置", this); + factoryResetBtn->setStyleSheet(BUTTON_DANGER_STYLE); + factoryResetBtn->setFixedHeight(44); + factoryResetBtn->setCursor(Qt::PointingHandCursor); + connect(factoryResetBtn, &QPushButton::clicked, this, &SettingsWidget::onFactoryResetClicked); + maintainLayout->addWidget(factoryResetBtn); + + layout->addWidget(maintainGroup); + + layout->addStretch(); + scrollArea->setWidget(content); + return scrollArea; +} + +void SettingsWidget::onSaveClicked() +{ + OverlayDialog::information(this, "保存成功", "系统设置已保存"); +} + +void SettingsWidget::onResetClicked() +{ + OverlayDialog::question(this, "恢复默认", "确定要恢复默认设置吗?", + [this](bool confirmed) + { + if (confirmed) + { + m_languageCombo->setCurrentIndex(0); + m_themeCombo->setCurrentIndex(0); + m_brightnessSpinBox->setValue(80); + m_volumeSpinBox->setValue(50); + m_autoSleepCheck->setChecked(true); + m_sleepTimeSpinBox->setValue(10); + OverlayDialog::information(this, "已恢复", "设置已恢复为默认值"); + } + }); +} + +void SettingsWidget::onCalibrateClicked() +{ + OverlayDialog::question(this, "开始校准", "确定要开始校准吗?\n请确保已连接标准校准源。", + [this](bool confirmed) + { + if (confirmed) + { + OverlayDialog::information(this, "校准", "校准向导功能开发中..."); + } + }); +} + +void SettingsWidget::onExportLogClicked() +{ + OverlayDialog::information(this, "导出日志", "日志已导出到: /sdcard/logs/"); +} + +void SettingsWidget::onClearCacheClicked() +{ + OverlayDialog::question(this, "清除缓存", "确定要清除缓存吗?", + [this](bool confirmed) + { + if (confirmed) + { + OverlayDialog::information(this, "清除成功", "缓存已清除,释放空间: 128MB"); + } + }); +} + +void SettingsWidget::onFactoryResetClicked() +{ + OverlayDialog::question(this, "恢复出厂设置", + "⚠️ 警告\n\n此操作将清除所有数据和设置,设备将恢复到出厂状态。\n\n确定要继续吗?", + [this](bool confirmed) + { + if (confirmed) + { + OverlayDialog::warning(this, "恢复出厂", "此功能需要管理员权限"); + } + }); +} diff --git a/widgets/settingswidget.h b/widgets/settingswidget.h new file mode 100644 index 0000000..404cc35 --- /dev/null +++ b/widgets/settingswidget.h @@ -0,0 +1,87 @@ +#ifndef SETTINGSWIDGET_H +#define SETTINGSWIDGET_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief 系统设置页面 + * 提供系统参数配置、设备信息、校准设置等功能 + */ +class SettingsWidget : public QWidget +{ + Q_OBJECT + +public: + explicit SettingsWidget(QWidget *parent = nullptr); + +signals: + void backRequested(); + +private slots: + void onSaveClicked(); + void onResetClicked(); + void onCalibrateClicked(); + void onExportLogClicked(); + void onClearCacheClicked(); + void onFactoryResetClicked(); + +private: + void setupUI(); + QWidget *createDeviceInfoTab(); + QWidget *createSystemConfigTab(); + QWidget *createCalibrationTab(); + QWidget *createNetworkTab(); + QWidget *createAboutTab(); + QGroupBox *createStyledGroupBox(const QString &title); + + // 标题栏 + QLabel *m_titleLabel; + QPushButton *m_backBtn; + + // 设备信息 + QLabel *m_deviceModelLabel; + QLabel *m_serialNumberLabel; + QLabel *m_firmwareVersionLabel; + QLabel *m_hardwareVersionLabel; + + // 系统配置 + QComboBox *m_languageCombo; + QComboBox *m_themeCombo; + QSpinBox *m_brightnessSpinBox; + QSpinBox *m_volumeSpinBox; + QCheckBox *m_autoSleepCheck; + QSpinBox *m_sleepTimeSpinBox; + + // 校准设置 + QLabel *m_lastCalibrationLabel; + QLabel *m_calibrationStatusLabel; + + // 网络设置 + QLineEdit *m_serverAddressEdit; + QSpinBox *m_serverPortSpinBox; + QCheckBox *m_autoConnectCheck; + + // 选项卡 + QTabWidget *m_tabWidget; + + // 样式常量 + static const QString LABEL_STYLE; + static const QString VALUE_STYLE; + static const QString BUTTON_STYLE; + static const QString BUTTON_PRIMARY_STYLE; + static const QString BUTTON_DANGER_STYLE; +}; + +#endif // SETTINGSWIDGET_H diff --git a/widgets/signalmeasurementwidget.cpp b/widgets/signalmeasurementwidget.cpp new file mode 100644 index 0000000..da4e91d --- /dev/null +++ b/widgets/signalmeasurementwidget.cpp @@ -0,0 +1,1760 @@ +#include "signalmeasurementwidget.h" +#include "overlaydialogswidget.h" +#include +#include +#include +#include +#include +#include +#include + +// ============== 样式常量定义 ============== + +const QString MeasurementModuleBase::CARD_STYLE = + "QWidget { background: white; border-radius: 8px; }"; + +const QString MeasurementModuleBase::INPUT_STYLE = + "QLineEdit, QSpinBox, QDoubleSpinBox { " + "background: white; border: 2px solid #E0E0E0; border-radius: 6px; " + "padding: 8px 12px; font-size: 14px; color: #263238; }" + "QLineEdit:focus, QSpinBox:focus, QDoubleSpinBox:focus { border-color: #1976D2; }"; + +const QString MeasurementModuleBase::PRIMARY_BTN_STYLE = + "QPushButton { background: #1976D2; color: white; border: none; border-radius: 6px; " + "padding: 8px 16px; font-size: 13px; font-weight: 500; }" + "QPushButton:hover { background: #1565C0; }" + "QPushButton:pressed { background: #0D47A1; }" + "QPushButton:disabled { background: #BDBDBD; }"; + +const QString MeasurementModuleBase::SECONDARY_BTN_STYLE = + "QPushButton { background: #455A64; color: white; border: none; border-radius: 6px; " + "padding: 8px 16px; font-size: 12px; font-weight: 500; }" + "QPushButton:hover { background: #546E7A; }" + "QPushButton:pressed { background: #37474F; }"; + +const QString MeasurementModuleBase::VALUE_DISPLAY_STYLE = + "QLabel { background: #ECEFF1; color: #1976D2; border: 2px solid #90CAF9; " + "border-radius: 6px; padding: 10px 14px; font-size: 18px; font-weight: bold; " + "font-family: 'Consolas', 'Monaco', monospace; }"; + +const QString MeasurementModuleBase::LABEL_STYLE = + "QLabel { font-size: 13px; color: #455A64; font-weight: 500; }"; + +const QString MeasurementModuleBase::GROUPBOX_STYLE = + "QGroupBox { font-size: 14px; font-weight: bold; color: #263238; " + "border: 2px solid #E0E0E0; border-radius: 8px; margin-top: 10px; padding-top: 14px; }" + "QGroupBox::title { subcontrol-origin: margin; left: 16px; padding: 0 8px; " + "background: white; }"; + +// ============== MeasurementModuleBase ============== + +MeasurementModuleBase::MeasurementModuleBase(const QString &title, QWidget *parent) + : QWidget(parent), m_title(title) +{ + m_mainLayout = new QVBoxLayout(this); + m_mainLayout->setContentsMargins(12, 12, 12, 12); + m_mainLayout->setSpacing(10); + + setStyleSheet("background: #FAFAFA;"); +} + +QLineEdit *MeasurementModuleBase::createStyledInput(const QString &placeholder, int width) +{ + auto *input = new QLineEdit(this); + input->setPlaceholderText(placeholder); + input->setFixedWidth(width > 0 ? width : 100); + input->setFixedHeight(36); + input->setStyleSheet(INPUT_STYLE); + return input; +} + +QDoubleSpinBox *MeasurementModuleBase::createStyledSpinBox(double min, double max, int decimals, int width) +{ + auto *spin = new QDoubleSpinBox(this); + spin->setRange(min, max); + spin->setDecimals(decimals); + spin->setFixedWidth(width > 0 ? width : 100); + spin->setFixedHeight(36); + spin->setButtonSymbols(QAbstractSpinBox::NoButtons); + spin->setStyleSheet( + "QDoubleSpinBox { background: white; border: 2px solid #E0E0E0; border-radius: 6px; " + "padding: 6px 12px 6px 12px; font-size: 13px; color: #263238; }" + "QDoubleSpinBox:focus { border-color: #1976D2; }"); + + // 创建加减按钮 + auto *plusBtn = new QPushButton("+", this); + plusBtn->setFixedSize(36, 36); + plusBtn->setStyleSheet( + "QPushButton { background: #F5F5F5; border: 2px solid #E0E0E0; border-radius: 6px; " + "font-size: 20px; font-weight: bold; color: #263238; }" + "QPushButton:hover { background: #E8E8E8; }" + "QPushButton:pressed { background: #D0D0D0; }"); + connect(plusBtn, &QPushButton::clicked, spin, &QDoubleSpinBox::stepUp); + + auto *minusBtn = new QPushButton("-", this); + minusBtn->setFixedSize(36, 36); + minusBtn->setStyleSheet( + "QPushButton { background: #F5F5F5; border: 2px solid #E0E0E0; border-radius: 6px; " + "font-size: 20px; font-weight: bold; color: #263238; }" + "QPushButton:hover { background: #E8E8E8; }" + "QPushButton:pressed { background: #D0D0D0; }"); + connect(minusBtn, &QPushButton::clicked, spin, &QDoubleSpinBox::stepDown); + + // 将按钮存储到SpinBox的动态属性中,以便外部可以访问 + spin->setProperty("plusButton", QVariant::fromValue(plusBtn)); + spin->setProperty("minusButton", QVariant::fromValue(minusBtn)); + + return spin; +} + +QComboBox *MeasurementModuleBase::createStyledComboBox(const QStringList &items, int width) +{ + auto *combo = new QComboBox(this); + combo->addItems(items); + combo->setFixedWidth(width > 0 ? width : 120); + combo->setFixedHeight(36); + combo->setStyleSheet( + "QComboBox { background: white; border: 2px solid #E0E0E0; border-radius: 6px; " + "padding: 6px 12px 6px 12px; font-size: 13px; color: #263238; }" + "QComboBox:focus { border-color: #1976D2; }" + "QComboBox::drop-down { subcontrol-origin: padding; subcontrol-position: center right; " + "width: 42px; border-left: 2px solid #E0E0E0; background: #F5F5F5; " + "border-top-right-radius: 4px; border-bottom-right-radius: 4px; }" + "QComboBox::drop-down:hover { background: #E8E8E8; }" + "QComboBox::drop-down:pressed { background: #D0D0D0; }" + "QComboBox QAbstractItemView { background: white; selection-background-color: #1976D2; " + "selection-color: white; outline: none; }" + "QComboBox QAbstractItemView::item { min-height: 42px; padding: 8px 12px; font-size: 13px; }" + "QComboBox QAbstractItemView::item:hover { background: #E3F2FD; }"); + return combo; +} + +QPushButton *MeasurementModuleBase::createPrimaryButton(const QString &text, int width) +{ + auto *btn = new QPushButton(text, this); + btn->setFixedSize(width > 0 ? width : 90, 36); + btn->setCursor(Qt::PointingHandCursor); + btn->setStyleSheet(PRIMARY_BTN_STYLE); + return btn; +} + +QPushButton *MeasurementModuleBase::createSecondaryButton(const QString &text, int width) +{ + auto *btn = new QPushButton(text, this); + btn->setFixedSize(width > 0 ? width : 80, 36); + btn->setCursor(Qt::PointingHandCursor); + btn->setStyleSheet(SECONDARY_BTN_STYLE); + return btn; +} + +QLabel *MeasurementModuleBase::createValueDisplay(const QString &value, int width) +{ + auto *label = new QLabel(value, this); + label->setFixedSize(width > 0 ? width : 140, 42); + label->setAlignment(Qt::AlignCenter); + label->setStyleSheet(VALUE_DISPLAY_STYLE); + return label; +} + +QGroupBox *MeasurementModuleBase::createStyledGroupBox(const QString &title) +{ + auto *group = new QGroupBox(title, this); + group->setStyleSheet(GROUPBOX_STYLE); + return group; +} + +QHBoxLayout *MeasurementModuleBase::createLabeledRow(const QString &label, QWidget *widget, const QString &unit) +{ + auto *row = new QHBoxLayout(); + row->setSpacing(12); + + auto *lbl = new QLabel(label, this); + lbl->setFixedWidth(100); + lbl->setStyleSheet(LABEL_STYLE); + lbl->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); + lbl->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + row->addWidget(lbl, 0, Qt::AlignVCenter); + + row->addWidget(widget, 0, Qt::AlignVCenter); + + // 如果是SpinBox,添加+/-按钮 + if (auto *spin = qobject_cast(widget)) + { + if (spin->property("minusButton").isValid()) + { + auto *minusBtn = qvariant_cast(spin->property("minusButton")); + auto *plusBtn = qvariant_cast(spin->property("plusButton")); + if (minusBtn && plusBtn) + { + row->addWidget(minusBtn, 0, Qt::AlignVCenter); + row->addWidget(plusBtn, 0, Qt::AlignVCenter); + } + } + } + + if (!unit.isEmpty()) + { + auto *unitLbl = new QLabel(unit, this); + unitLbl->setStyleSheet("font-size: 13px; color: #78909C;"); + unitLbl->setAlignment(Qt::AlignVCenter); + unitLbl->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + row->addWidget(unitLbl, 0, Qt::AlignVCenter); + } + + row->addStretch(); + return row; +} + +// ============== RTDMeasurementWidget ============== + +RTDMeasurementWidget::RTDMeasurementWidget(QWidget *parent) + : MeasurementModuleBase("热电阻测量", parent), m_channelManager(ChannelManager::instance()), m_selectedChannel(ChannelManager::INPUT_CHANNEL_1), m_currentChannel(ChannelManager::INPUT_CHANNEL_1), m_measurementTimer(new QTimer(this)), m_isMeasuring(false), m_currentMode(FOUR_WIRE_MODE), m_currentRTDType(PT100), m_currentResistance(100.0), m_currentTemperature(0.0) +{ + setupUI(); + + // 连接定时器 + connect(m_measurementTimer, &QTimer::timeout, this, &RTDMeasurementWidget::updateMeasurement); + m_measurementTimer->setInterval(200); // 每200ms更新 +} + +RTDMeasurementWidget::~RTDMeasurementWidget() +{ + if (m_isMeasuring) + { + m_measurementTimer->stop(); + m_channelManager->releaseChannel(m_currentChannel); + } +} + +void RTDMeasurementWidget::setupUI() +{ + // === 通道选择区域 === + m_channelSelectionWidget = new QWidget(this); + m_channelSelectionWidget->setStyleSheet("background: #37474F; border-radius: 6px;"); + auto *channelLayout = new QHBoxLayout(m_channelSelectionWidget); + channelLayout->setContentsMargins(20, 14, 20, 14); + channelLayout->setSpacing(28); + + auto *channelLabel = new QLabel("选择测量通道:", this); + channelLabel->setStyleSheet("font-weight: bold; color: #FFFFFF; font-size: 14px;"); + channelLabel->setAlignment(Qt::AlignVCenter); + channelLayout->addWidget(channelLabel); + + setupChannelSelection(); + channelLayout->addStretch(); + + m_mainLayout->addWidget(m_channelSelectionWidget); + + // === 测量组 === + auto *measureGroup = createStyledGroupBox("热电阻测量"); + auto *measureLayout = new QVBoxLayout(measureGroup); + measureLayout->setSpacing(12); + + // 接线方式和传感器类型 + auto *settingsRow = new QHBoxLayout(); + settingsRow->setSpacing(20); + settingsRow->setAlignment(Qt::AlignVCenter); + + m_wireTypeCombo = createStyledComboBox({"2线制", "3线制", "4线制"}, 120); + m_wireTypeCombo->setCurrentIndex(2); // 默认4线制 + connect(m_wireTypeCombo, QOverload::of(&QComboBox::currentIndexChanged), + this, &RTDMeasurementWidget::onWireTypeChanged); + settingsRow->addLayout(createLabeledRow("接线方式", m_wireTypeCombo)); + + m_rtdTypeCombo = createStyledComboBox({"PT100", "PT1000", "CU50", "CU100"}, 120); + settingsRow->addLayout(createLabeledRow("传感器类型", m_rtdTypeCombo)); + + settingsRow->addStretch(); + measureLayout->addLayout(settingsRow); + + // 接线图提示 + m_wiringDiagramLabel = new QLabel("接线图: 输入通道1 四线制 (白-红-灰-黑)", this); + m_wiringDiagramLabel->setStyleSheet("color: #1976D2; font-size: 13px; padding: 8px; " + "background: #E3F2FD; border-radius: 4px;"); + measureLayout->addWidget(m_wiringDiagramLabel); + + // 测量值显示 + auto *displayRow = new QHBoxLayout(); + displayRow->setSpacing(12); + + auto *resBox = new QVBoxLayout(); + auto *resLabel = new QLabel("电阻值 (Ω)", this); + resLabel->setStyleSheet(LABEL_STYLE); + resBox->addWidget(resLabel); + m_resistanceDisplay = createValueDisplay("---", 140); + resBox->addWidget(m_resistanceDisplay); + displayRow->addLayout(resBox); + + auto *tempBox = new QVBoxLayout(); + auto *tempLabel = new QLabel("温度 (°C)", this); + tempLabel->setStyleSheet(LABEL_STYLE); + tempBox->addWidget(tempLabel); + m_temperatureDisplay = createValueDisplay("---", 140); + tempBox->addWidget(m_temperatureDisplay); + displayRow->addLayout(tempBox); + + auto *lineResBox = new QVBoxLayout(); + auto *lineResLabel = new QLabel("线电阻 (Ω)", this); + lineResLabel->setStyleSheet(LABEL_STYLE); + lineResBox->addWidget(lineResLabel); + m_lineResDisplay = createValueDisplay("---", 120); + lineResBox->addWidget(m_lineResDisplay); + displayRow->addLayout(lineResBox); + + displayRow->addStretch(); + measureLayout->addLayout(displayRow); + + // 通道状态 + m_channelStatusLabel = new QLabel("通道状态: 输入通道1 - 空闲", this); + m_channelStatusLabel->setStyleSheet("color: #455A64; font-size: 13px;"); + measureLayout->addWidget(m_channelStatusLabel); + + // 按钮 + auto *btnRow = new QHBoxLayout(); + btnRow->setSpacing(12); + m_measureBtn = createPrimaryButton("开始测量", 120); + connect(m_measureBtn, &QPushButton::clicked, this, &RTDMeasurementWidget::onMeasureClicked); + btnRow->addWidget(m_measureBtn); + + m_stopBtn = createSecondaryButton("停止测量", 120); + m_stopBtn->setEnabled(false); + connect(m_stopBtn, &QPushButton::clicked, this, &RTDMeasurementWidget::onStopClicked); + btnRow->addWidget(m_stopBtn); + + btnRow->addStretch(); + measureLayout->addLayout(btnRow); + + m_mainLayout->addWidget(measureGroup); + m_mainLayout->addStretch(); + + // 初始化 + updateConnectionModeVisibility(); +} + +void RTDMeasurementWidget::setupChannelSelection() +{ + QList> channels = { + {ChannelManager::INPUT_CHANNEL_1, "输入通道1"}, + {ChannelManager::INPUT_CHANNEL_2, "输入通道2"}}; + + auto *channelLayout = qobject_cast(m_channelSelectionWidget->layout()); + + for (const auto &channel : channels) + { + ChannelStatusWidget *widget = new ChannelStatusWidget( + channel.first, channel.second, this, ChannelStatusWidget::OVERVIEW_MODE); + widget->setFixedSize(88, 48); + widget->setCursor(Qt::PointingHandCursor); + + // 安装事件过滤器处理点击 + widget->installEventFilter(this); + + m_channelWidgets[channel.first] = widget; + channelLayout->addWidget(widget); + } + + updateChannelSelection(); +} + +void RTDMeasurementWidget::updateChannelSelection() +{ + for (auto it = m_channelWidgets.begin(); it != m_channelWidgets.end(); ++it) + { + ChannelManager::ChannelId channelId = it.key(); + ChannelStatusWidget *widget = it.value(); + + if (channelId == m_selectedChannel) + { + widget->setStyleSheet("background-color: #2c3e50; border: 3px solid #1976D2; border-radius: 8px;"); + } + else + { + widget->setStyleSheet("background-color: #34495e; border: 2px solid #2c3e50; border-radius: 8px;"); + } + widget->update(); + } +} + +void RTDMeasurementWidget::onChannelSelected(ChannelManager::ChannelId channelId) +{ + if (m_isMeasuring) + { + OverlayDialog::information(this, "通道切换", "请先停止当前测量再切换通道。"); + return; + } + + m_selectedChannel = channelId; + m_currentChannel = channelId; + updateChannelSelection(); + updateConnectionModeVisibility(); + + // 更新状态显示 + QString statusText = QString("通道状态: %1 - %2") + .arg(m_channelManager->getChannelDisplayName(m_currentChannel)) + .arg(m_channelManager->isChannelAvailable(m_currentChannel) ? "空闲" : "占用中"); + m_channelStatusLabel->setText(statusText); +} + +void RTDMeasurementWidget::updateConnectionModeVisibility() +{ + // 输入通道2只支持二线制 + if (m_selectedChannel == ChannelManager::INPUT_CHANNEL_2) + { + m_wireTypeCombo->setEnabled(false); + m_wireTypeCombo->setCurrentIndex(0); // 强制二线制 + m_currentMode = TWO_WIRE_MODE; + m_wiringDiagramLabel->setText("接线图: 输入通道2 二线制 (白-灰)"); + } + else + { + m_wireTypeCombo->setEnabled(true); + onWireTypeChanged(m_wireTypeCombo->currentIndex()); + } +} + +void RTDMeasurementWidget::onMeasureClicked() +{ + if (!validateChannelForMode()) + { + return; + } + + // 确定连接类型 + ChannelManager::ConnectionType connectionType; + switch (m_currentMode) + { + case TWO_WIRE_MODE: + connectionType = ChannelManager::TWO_WIRE; + break; + case THREE_WIRE_MODE: + connectionType = ChannelManager::THREE_WIRE; + break; + case FOUR_WIRE_MODE: + connectionType = ChannelManager::FOUR_WIRE; + break; + } + + QString description = QString("热电阻测量(%1)").arg(m_wireTypeCombo->currentText()); + + if (!m_channelManager->occupyChannel(m_currentChannel, + ChannelManager::RESISTANCE_MEASUREMENT, + description, connectionType)) + { + OverlayDialog::warning(this, "通道占用", + QString("无法占用%1,通道可能正在被其他功能使用。") + .arg(m_channelManager->getChannelDisplayName(m_currentChannel))); + return; + } + + m_isMeasuring = true; + m_measurementTimer->start(); + + m_measureBtn->setEnabled(false); + m_stopBtn->setEnabled(true); + m_wireTypeCombo->setEnabled(false); + + // 更新通道状态显示 + QString statusText = QString("通道状态: 正在测量 (%1)") + .arg(m_channelManager->getChannelDisplayName(m_currentChannel)); + m_channelStatusLabel->setText(statusText); + m_channelStatusLabel->setStyleSheet("color: #1976D2; font-size: 13px; font-weight: bold;"); + + // 更新通道widget状态 + if (m_channelWidgets.contains(m_currentChannel)) + { + m_channelWidgets[m_currentChannel]->updateStatus( + ChannelManager::RESISTANCE_MEASUREMENT, "测量中"); + } +} + +void RTDMeasurementWidget::onStopClicked() +{ + if (m_isMeasuring) + { + m_measurementTimer->stop(); + m_channelManager->releaseChannel(m_currentChannel); + m_isMeasuring = false; + + m_measureBtn->setEnabled(true); + m_stopBtn->setEnabled(false); + if (m_selectedChannel == ChannelManager::INPUT_CHANNEL_1) + { + m_wireTypeCombo->setEnabled(true); + } + + QString statusText = QString("通道状态: %1 - 空闲") + .arg(m_channelManager->getChannelDisplayName(m_currentChannel)); + m_channelStatusLabel->setText(statusText); + m_channelStatusLabel->setStyleSheet("color: #455A64; font-size: 13px;"); + + if (m_channelWidgets.contains(m_currentChannel)) + { + m_channelWidgets[m_currentChannel]->updateStatus(ChannelManager::NONE, ""); + } + } +} + +void RTDMeasurementWidget::onWireTypeChanged(int index) +{ + m_currentMode = static_cast(index); + m_lineResDisplay->setVisible(index == 2); // 仅4线制显示线电阻 + + QString channelName = m_channelManager->getChannelDisplayName(m_currentChannel); + QString wiringText; + switch (index) + { + case 0: // 二线制 + wiringText = QString("接线图: %1 二线制 (白-灰)").arg(channelName); + break; + case 1: // 三线制 + wiringText = QString("接线图: %1 三线制 (白-红-灰)").arg(channelName); + break; + case 2: // 四线制 + wiringText = QString("接线图: %1 四线制 (白-红-灰-黑)").arg(channelName); + break; + } + m_wiringDiagramLabel->setText(wiringText); +} + +void RTDMeasurementWidget::updateMeasurement() +{ + generateSimulatedData(); + + m_resistanceDisplay->setText(QString::number(m_currentResistance, 'f', 3)); + m_temperatureDisplay->setText(QString::number(m_currentTemperature, 'f', 2)); + + if (m_currentMode == FOUR_WIRE_MODE) + { + // 模拟线电阻(四线制可以测量) + static std::random_device rd; + static std::mt19937 gen(rd()); + std::uniform_real_distribution<> lineResNoise(0.1, 0.3); + m_lineResDisplay->setText(QString::number(lineResNoise(gen), 'f', 3)); + } + + emit measurementDataReady(m_currentResistance, m_currentTemperature); +} + +void RTDMeasurementWidget::generateSimulatedData() +{ + // 生成逼真的RTD模拟数据 + static std::random_device rd; + static std::mt19937 gen(rd()); + static std::uniform_real_distribution<> noise(-0.02, 0.02); + + // 基础温度缓慢变化 + static double baseTemp = 20.0; + static int counter = 0; + + if (counter++ % 50 == 0) + { + baseTemp += noise(gen) * 10; + baseTemp = std::max(-50.0, std::min(200.0, baseTemp)); + } + + m_currentTemperature = baseTemp + noise(gen); + + // PT100 温度转电阻计算 (IEC 60751) + double R0 = 100.0; + double A = 3.9083e-3; + double B = -5.775e-7; + double C = -4.183e-12; + double T = m_currentTemperature; + if (T >= 0) + { + m_currentResistance = R0 * (1 + A * T + B * T * T); + } + else + { + m_currentResistance = R0 * (1 + A * T + B * T * T + C * T * T * T * (T - 100)); + } + + // 根据接线方式添加测量噪声 + double noiseLevel = 0.001; + switch (m_currentMode) + { + case TWO_WIRE_MODE: + noiseLevel = 0.005; // 二线制噪声最大 + break; + case THREE_WIRE_MODE: + noiseLevel = 0.002; // 三线制中等 + break; + case FOUR_WIRE_MODE: + noiseLevel = 0.001; // 四线制噪声最小 + break; + } + + m_currentResistance += noise(gen) * noiseLevel; +} + +double RTDMeasurementWidget::convertResistanceToTemperature(double resistance) +{ + // PT100简化逆运算 + double R0 = 100.0; + double A = 3.9083e-3; + return (resistance - R0) / (R0 * A); +} + +bool RTDMeasurementWidget::validateChannelForMode() +{ + ChannelManager::ConnectionType connectionType; + switch (m_currentMode) + { + case TWO_WIRE_MODE: + connectionType = ChannelManager::TWO_WIRE; + break; + case THREE_WIRE_MODE: + connectionType = ChannelManager::THREE_WIRE; + break; + case FOUR_WIRE_MODE: + connectionType = ChannelManager::FOUR_WIRE; + break; + } + + if (!m_channelManager->canChannelSupport(m_currentChannel, + ChannelManager::RESISTANCE_MEASUREMENT, + connectionType)) + { + QString message; + if (m_currentChannel == ChannelManager::INPUT_CHANNEL_2 && + (m_currentMode == THREE_WIRE_MODE || m_currentMode == FOUR_WIRE_MODE)) + { + message = "输入通道2只支持二线制测量,请选择二线制或切换到输入通道1。"; + } + else + { + message = QString("所选通道不支持%1测量。").arg(m_wireTypeCombo->currentText()); + } + OverlayDialog::warning(this, "通道限制", message); + return false; + } + + return true; +} + +bool RTDMeasurementWidget::eventFilter(QObject *obj, QEvent *event) +{ + // 处理通道选择widget的鼠标事件 + for (auto it = m_channelWidgets.begin(); it != m_channelWidgets.end(); ++it) + { + if (it.value() == obj && event->type() == QEvent::MouseButtonPress) + { + onChannelSelected(it.key()); + return true; + } + } + return MeasurementModuleBase::eventFilter(obj, event); +} + +// ============== ThermocoupleMeasurementWidget ============== + +ThermocoupleMeasurementWidget::ThermocoupleMeasurementWidget(QWidget *parent) + : MeasurementModuleBase("热电偶测量", parent) +{ + setupUI(); +} + +void ThermocoupleMeasurementWidget::setupUI() +{ + auto *measureGroup = createStyledGroupBox("热电偶测量"); + auto *measureLayout = new QVBoxLayout(measureGroup); + measureLayout->setSpacing(16); + + m_tcTypeCombo = createStyledComboBox({"K型", "J型", "T型", "E型", "S型", "R型", "B型"}, 150); + measureLayout->addLayout(createLabeledRow("热电偶类型", m_tcTypeCombo)); + + auto *displayRow = new QHBoxLayout(); + displayRow->setSpacing(16); + + auto *voltBox = new QVBoxLayout(); + voltBox->addWidget(new QLabel("电压 (mV)", this)); + m_voltageDisplay = createValueDisplay("---", 160); + voltBox->addWidget(m_voltageDisplay); + displayRow->addLayout(voltBox); + + auto *tempBox = new QVBoxLayout(); + tempBox->addWidget(new QLabel("温度 (°C)", this)); + m_temperatureDisplay = createValueDisplay("---", 160); + tempBox->addWidget(m_temperatureDisplay); + displayRow->addLayout(tempBox); + + displayRow->addStretch(); + measureLayout->addLayout(displayRow); + + m_measureBtn = createPrimaryButton("开始测量", 140); + connect(m_measureBtn, &QPushButton::clicked, this, &ThermocoupleMeasurementWidget::onMeasureClicked); + measureLayout->addWidget(m_measureBtn); + + m_mainLayout->addWidget(measureGroup); + + auto *outputGroup = createStyledGroupBox("热电偶模拟输出"); + auto *outputLayout = new QVBoxLayout(outputGroup); + outputLayout->setSpacing(16); + + m_outputTempSpin = createStyledSpinBox(-200, 1300, 1, 150); + m_outputTempSpin->setValue(100.0); + outputLayout->addLayout(createLabeledRow("设定温度", m_outputTempSpin, "°C")); + + auto *outDisplayRow = new QHBoxLayout(); + outDisplayRow->setSpacing(16); + auto *outVoltLbl = new QLabel("输出电压 (mV):", this); + outDisplayRow->addWidget(outVoltLbl); + m_outputVoltageDisplay = createValueDisplay("---", 180); + outDisplayRow->addWidget(m_outputVoltageDisplay); + outDisplayRow->addStretch(); + outputLayout->addLayout(outDisplayRow); + + m_outputBtn = createPrimaryButton("启动输出", 140); + connect(m_outputBtn, &QPushButton::clicked, this, &ThermocoupleMeasurementWidget::onOutputClicked); + outputLayout->addWidget(m_outputBtn); + + m_mainLayout->addWidget(outputGroup); + m_mainLayout->addStretch(); +} + +void ThermocoupleMeasurementWidget::onMeasureClicked() +{ + m_voltageDisplay->setText("4.096"); + m_temperatureDisplay->setText("100.2"); +} + +void ThermocoupleMeasurementWidget::onOutputClicked() +{ + double temp = m_outputTempSpin->value(); + // K型热电偶简化: ~40µV/°C + double voltage = temp * 0.04; + m_outputVoltageDisplay->setText(QString::number(voltage, 'f', 3)); + OverlayDialog::information(this, "输出启动", + QString("已启动热电偶输出\n温度: %1°C\n电压: %2mV").arg(temp).arg(voltage, 0, 'f', 3)); +} + +// ============== InsulationMeasurementWidget ============== + +InsulationMeasurementWidget::InsulationMeasurementWidget(QWidget *parent) + : MeasurementModuleBase("绝缘电阻测量", parent) +{ + setupUI(); +} + +void InsulationMeasurementWidget::setupUI() +{ + auto *measureGroup = createStyledGroupBox("绝缘电阻测量"); + auto *measureLayout = new QVBoxLayout(measureGroup); + measureLayout->setSpacing(16); + + auto *voltageRow = new QHBoxLayout(); + auto *voltLbl = new QLabel("测试电压:", this); + voltLbl->setFixedWidth(100); + voltLbl->setStyleSheet(LABEL_STYLE); + voltageRow->addWidget(voltLbl); + + auto *btnGroup = new QButtonGroup(this); + m_50vRadio = new QRadioButton("50V", this); + m_100vRadio = new QRadioButton("100V", this); + m_50vRadio->setChecked(true); + btnGroup->addButton(m_50vRadio); + btnGroup->addButton(m_100vRadio); + + m_50vRadio->setStyleSheet("QRadioButton { font-size: 14px; spacing: 8px; }"); + m_100vRadio->setStyleSheet("QRadioButton { font-size: 14px; spacing: 8px; }"); + + voltageRow->addWidget(m_50vRadio); + voltageRow->addWidget(m_100vRadio); + voltageRow->addStretch(); + measureLayout->addLayout(voltageRow); + + m_autoTestCheck = new QCheckBox("测量后自动检测绝缘电阻", this); + m_autoTestCheck->setStyleSheet("QCheckBox { font-size: 14px; color: #455A64; }"); + connect(m_autoTestCheck, &QCheckBox::toggled, this, &InsulationMeasurementWidget::onAutoTestToggled); + measureLayout->addWidget(m_autoTestCheck); + + auto *displayBox = new QVBoxLayout(); + displayBox->addWidget(new QLabel("绝缘电阻 (MΩ)", this)); + m_insulationDisplay = createValueDisplay("---", 200); + displayBox->addWidget(m_insulationDisplay); + measureLayout->addLayout(displayBox); + + m_statusDisplay = new QLabel("就绪", this); + m_statusDisplay->setStyleSheet("font-size: 14px; color: #43A047; font-weight: bold;"); + measureLayout->addWidget(m_statusDisplay); + + m_dischargeProgress = new QProgressBar(this); + m_dischargeProgress->setRange(0, 100); + m_dischargeProgress->setValue(0); + m_dischargeProgress->setVisible(false); + m_dischargeProgress->setStyleSheet( + "QProgressBar { border: 2px solid #E0E0E0; border-radius: 6px; text-align: center; " + "background: white; height: 28px; }" + "QProgressBar::chunk { background: #43A047; border-radius: 4px; }"); + measureLayout->addWidget(m_dischargeProgress); + + m_measureBtn = createPrimaryButton("开始测量", 140); + connect(m_measureBtn, &QPushButton::clicked, this, &InsulationMeasurementWidget::onMeasureClicked); + measureLayout->addWidget(m_measureBtn); + + m_mainLayout->addWidget(measureGroup); + m_mainLayout->addStretch(); +} + +void InsulationMeasurementWidget::onMeasureClicked() +{ + m_statusDisplay->setText("测量中..."); + m_statusDisplay->setStyleSheet("font-size: 14px; color: #1976D2; font-weight: bold;"); + + // 模拟测量 + QTimer::singleShot(1000, this, [this]() + { + m_insulationDisplay->setText("125.6"); + m_statusDisplay->setText("测量完成,放电中..."); + m_dischargeProgress->setVisible(true); + + // 模拟放电过程 + QTimer *timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, [this, timer]() { + int val = m_dischargeProgress->value() + 10; + m_dischargeProgress->setValue(val); + if (val >= 100) { + timer->stop(); + timer->deleteLater(); + m_dischargeProgress->setVisible(false); + m_dischargeProgress->setValue(0); + m_statusDisplay->setText("就绪"); + m_statusDisplay->setStyleSheet("font-size: 14px; color: #43A047; font-weight: bold;"); + } + }); + timer->start(200); }); +} + +void InsulationMeasurementWidget::onAutoTestToggled(bool enabled) +{ + if (enabled) + { + OverlayDialog::information(this, "自动测试", "已启用自动绝缘电阻测试\n将在热电阻/热电偶测量后自动执行"); + } +} + +// ============== DCCurrentWidget ============== + +DCCurrentWidget::DCCurrentWidget(QWidget *parent) + : MeasurementModuleBase("直流电流", parent) +{ + setupUI(); +} + +void DCCurrentWidget::setupUI() +{ + auto *measureGroup = createStyledGroupBox("电流测量 (0-20mA)"); + auto *measureLayout = new QVBoxLayout(measureGroup); + measureLayout->setSpacing(16); + + auto *displayRow = new QHBoxLayout(); + displayRow->setSpacing(12); + + auto *currentBox = new QVBoxLayout(); + currentBox->addWidget(new QLabel("当前值 (mA)", this)); + m_currentDisplay = createValueDisplay("---", 140); + currentBox->addWidget(m_currentDisplay); + displayRow->addLayout(currentBox); + + auto *statsBox = new QVBoxLayout(); + statsBox->setSpacing(8); + + auto *maxRow = new QHBoxLayout(); + maxRow->addWidget(new QLabel("最大:", this)); + m_maxDisplay = new QLabel("---", this); + m_maxDisplay->setStyleSheet("font-size: 14px; color: #D32F2F; font-weight: bold;"); + maxRow->addWidget(m_maxDisplay); + maxRow->addStretch(); + statsBox->addLayout(maxRow); + + auto *minRow = new QHBoxLayout(); + minRow->addWidget(new QLabel("最小:", this)); + m_minDisplay = new QLabel("---", this); + m_minDisplay->setStyleSheet("font-size: 14px; color: #1976D2; font-weight: bold;"); + minRow->addWidget(m_minDisplay); + minRow->addStretch(); + statsBox->addLayout(minRow); + + auto *avgRow = new QHBoxLayout(); + avgRow->addWidget(new QLabel("平均:", this)); + m_avgDisplay = new QLabel("---", this); + m_avgDisplay->setStyleSheet("font-size: 14px; color: #43A047; font-weight: bold;"); + avgRow->addWidget(m_avgDisplay); + avgRow->addStretch(); + statsBox->addLayout(avgRow); + + displayRow->addLayout(statsBox); + displayRow->addStretch(); + measureLayout->addLayout(displayRow); + + m_measureBtn = createPrimaryButton("开始测量", 140); + connect(m_measureBtn, &QPushButton::clicked, this, &DCCurrentWidget::onMeasureClicked); + measureLayout->addWidget(m_measureBtn); + + m_mainLayout->addWidget(measureGroup); + + auto *outputGroup = createStyledGroupBox("电流输出"); + auto *outputLayout = new QVBoxLayout(outputGroup); + outputLayout->setSpacing(16); + + auto *modeRow = new QHBoxLayout(); + auto *modeLbl = new QLabel("输出模式:", this); + modeLbl->setFixedWidth(100); + modeLbl->setStyleSheet(LABEL_STYLE); + modeRow->addWidget(modeLbl); + + auto *modeGroup = new QButtonGroup(this); + m_activeRadio = new QRadioButton("有源", this); + m_passiveRadio = new QRadioButton("无源", this); + m_activeRadio->setChecked(true); + modeGroup->addButton(m_activeRadio); + modeGroup->addButton(m_passiveRadio); + + m_activeRadio->setStyleSheet("QRadioButton { font-size: 14px; }"); + m_passiveRadio->setStyleSheet("QRadioButton { font-size: 14px; }"); + + modeRow->addWidget(m_activeRadio); + modeRow->addWidget(m_passiveRadio); + modeRow->addStretch(); + outputLayout->addLayout(modeRow); + + m_outputSpin = createStyledSpinBox(0, 20, 3, 150); + m_outputSpin->setValue(4.0); + outputLayout->addLayout(createLabeledRow("输出电流", m_outputSpin, "mA")); + + m_outputBtn = createPrimaryButton("启动输出", 140); + connect(m_outputBtn, &QPushButton::clicked, this, &DCCurrentWidget::onOutputClicked); + outputLayout->addWidget(m_outputBtn); + + m_mainLayout->addWidget(outputGroup); + m_mainLayout->addStretch(); +} + +void DCCurrentWidget::onMeasureClicked() +{ + m_currentDisplay->setText("12.456"); + m_maxDisplay->setText("12.502 mA"); + m_minDisplay->setText("12.401 mA"); + m_avgDisplay->setText("12.451 mA"); +} + +void DCCurrentWidget::onOutputClicked() +{ + QString mode = m_activeRadio->isChecked() ? "有源" : "无源"; + OverlayDialog::information(this, "输出启动", + QString("已启动电流输出\n模式: %1\n电流: %2mA").arg(mode).arg(m_outputSpin->value())); +} + +// ============== DCVoltageWidget ============== + +DCVoltageWidget::DCVoltageWidget(QWidget *parent) + : MeasurementModuleBase("直流电压", parent) +{ + setupUI(); +} + +void DCVoltageWidget::setupUI() +{ + auto *measureGroup = createStyledGroupBox("电压测量 (0-20V)"); + auto *measureLayout = new QVBoxLayout(measureGroup); + measureLayout->setSpacing(16); + + auto *displayRow = new QHBoxLayout(); + displayRow->setSpacing(12); + + auto *voltBox = new QVBoxLayout(); + voltBox->addWidget(new QLabel("当前值 (V)", this)); + m_voltageDisplay = createValueDisplay("---", 140); + voltBox->addWidget(m_voltageDisplay); + displayRow->addLayout(voltBox); + + auto *statsBox = new QVBoxLayout(); + statsBox->setSpacing(8); + + auto *maxRow = new QHBoxLayout(); + maxRow->addWidget(new QLabel("最大:", this)); + m_maxDisplay = new QLabel("---", this); + m_maxDisplay->setStyleSheet("font-size: 14px; color: #D32F2F; font-weight: bold;"); + maxRow->addWidget(m_maxDisplay); + maxRow->addStretch(); + statsBox->addLayout(maxRow); + + auto *minRow = new QHBoxLayout(); + minRow->addWidget(new QLabel("最小:", this)); + m_minDisplay = new QLabel("---", this); + m_minDisplay->setStyleSheet("font-size: 14px; color: #1976D2; font-weight: bold;"); + minRow->addWidget(m_minDisplay); + minRow->addStretch(); + statsBox->addLayout(minRow); + + auto *avgRow = new QHBoxLayout(); + avgRow->addWidget(new QLabel("平均:", this)); + m_avgDisplay = new QLabel("---", this); + m_avgDisplay->setStyleSheet("font-size: 14px; color: #43A047; font-weight: bold;"); + avgRow->addWidget(m_avgDisplay); + avgRow->addStretch(); + statsBox->addLayout(avgRow); + + displayRow->addLayout(statsBox); + displayRow->addStretch(); + measureLayout->addLayout(displayRow); + + m_measureBtn = createPrimaryButton("开始测量", 140); + connect(m_measureBtn, &QPushButton::clicked, this, &DCVoltageWidget::onMeasureClicked); + measureLayout->addWidget(m_measureBtn); + + m_mainLayout->addWidget(measureGroup); + + auto *outputGroup = createStyledGroupBox("电压输出"); + auto *outputLayout = new QVBoxLayout(outputGroup); + outputLayout->setSpacing(16); + + m_outputSpin = createStyledSpinBox(0, 20, 3, 150); + m_outputSpin->setValue(5.0); + outputLayout->addLayout(createLabeledRow("输出电压", m_outputSpin, "V")); + + m_outputBtn = createPrimaryButton("启动输出", 140); + connect(m_outputBtn, &QPushButton::clicked, this, &DCVoltageWidget::onOutputClicked); + outputLayout->addWidget(m_outputBtn); + + m_mainLayout->addWidget(outputGroup); + m_mainLayout->addStretch(); +} + +void DCVoltageWidget::onMeasureClicked() +{ + m_voltageDisplay->setText("5.123"); + m_maxDisplay->setText("5.156 V"); + m_minDisplay->setText("5.098 V"); + m_avgDisplay->setText("5.124 V"); +} + +void DCVoltageWidget::onOutputClicked() +{ + OverlayDialog::information(this, "输出启动", + QString("已启动电压输出\n电压: %1V").arg(m_outputSpin->value())); +} + +// ============== FrequencyWidget ============== + +FrequencyWidget::FrequencyWidget(QWidget *parent) + : MeasurementModuleBase("频率信号", parent) +{ + setupUI(); +} + +void FrequencyWidget::setupUI() +{ + auto *measureGroup = createStyledGroupBox("频率测量"); + auto *measureLayout = new QVBoxLayout(measureGroup); + measureLayout->setSpacing(16); + + auto *displayBox = new QVBoxLayout(); + displayBox->addWidget(new QLabel("频率 (Hz)", this)); + m_frequencyDisplay = createValueDisplay("---", 200); + displayBox->addWidget(m_frequencyDisplay); + measureLayout->addLayout(displayBox); + + m_measureBtn = createPrimaryButton("开始测量", 140); + connect(m_measureBtn, &QPushButton::clicked, this, &FrequencyWidget::onMeasureClicked); + measureLayout->addWidget(m_measureBtn); + + m_mainLayout->addWidget(measureGroup); + + auto *outputGroup = createStyledGroupBox("频率信号输出"); + auto *outputLayout = new QVBoxLayout(outputGroup); + outputLayout->setSpacing(16); + + m_waveTypeCombo = createStyledComboBox({"方波", "正弦波"}, 150); + connect(m_waveTypeCombo, QOverload::of(&QComboBox::currentIndexChanged), + this, &FrequencyWidget::onWaveTypeChanged); + outputLayout->addLayout(createLabeledRow("波形类型", m_waveTypeCombo)); + + m_freqSpin = createStyledSpinBox(0.1, 10000, 1, 150); + m_freqSpin->setValue(1000.0); + outputLayout->addLayout(createLabeledRow("频率", m_freqSpin, "Hz")); + + m_amplitudeSpin = createStyledSpinBox(0.1, 10, 2, 150); + m_amplitudeSpin->setValue(5.0); + outputLayout->addLayout(createLabeledRow("幅值", m_amplitudeSpin, "V")); + + m_outputBtn = createPrimaryButton("启动输出", 140); + connect(m_outputBtn, &QPushButton::clicked, this, &FrequencyWidget::onOutputClicked); + outputLayout->addWidget(m_outputBtn); + + m_mainLayout->addWidget(outputGroup); + m_mainLayout->addStretch(); +} + +void FrequencyWidget::onMeasureClicked() +{ + m_frequencyDisplay->setText("1250.5"); +} + +void FrequencyWidget::onOutputClicked() +{ + QString waveType = m_waveTypeCombo->currentText(); + OverlayDialog::information(this, "输出启动", + QString("已启动频率输出\n波形: %1\n频率: %2Hz\n幅值: %3V") + .arg(waveType) + .arg(m_freqSpin->value()) + .arg(m_amplitudeSpin->value())); +} + +void FrequencyWidget::onWaveTypeChanged(int) +{ + // 波形类型变化处理 +} + +// ============== ACVoltageWidget ============== + +ACVoltageWidget::ACVoltageWidget(QWidget *parent) + : MeasurementModuleBase("220VAC测量", parent) +{ + setupUI(); +} + +void ACVoltageWidget::setupUI() +{ + auto *measureGroup = createStyledGroupBox("交流电压测量"); + auto *measureLayout = new QVBoxLayout(measureGroup); + measureLayout->setSpacing(16); + + auto *displayRow = new QHBoxLayout(); + displayRow->setSpacing(16); + + auto *voltBox = new QVBoxLayout(); + voltBox->addWidget(new QLabel("电压 (V)", this)); + m_voltageDisplay = createValueDisplay("---", 160); + voltBox->addWidget(m_voltageDisplay); + displayRow->addLayout(voltBox); + + auto *freqBox = new QVBoxLayout(); + freqBox->addWidget(new QLabel("频率 (Hz)", this)); + m_frequencyDisplay = createValueDisplay("---", 160); + freqBox->addWidget(m_frequencyDisplay); + displayRow->addLayout(freqBox); + + displayRow->addStretch(); + measureLayout->addLayout(displayRow); + + m_measureBtn = createPrimaryButton("开始测量", 140); + connect(m_measureBtn, &QPushButton::clicked, this, &ACVoltageWidget::onMeasureClicked); + measureLayout->addWidget(m_measureBtn); + + m_mainLayout->addWidget(measureGroup); + m_mainLayout->addStretch(); +} + +void ACVoltageWidget::onMeasureClicked() +{ + m_voltageDisplay->setText("220.5"); + m_frequencyDisplay->setText("50.0"); +} + +// ============== SwitchDetectionWidget ============== + +SwitchDetectionWidget::SwitchDetectionWidget(QWidget *parent) + : MeasurementModuleBase("开关量检测", parent) +{ + setupUI(); +} + +void SwitchDetectionWidget::setupUI() +{ + auto *detectGroup = createStyledGroupBox("开关量检测"); + auto *detectLayout = new QVBoxLayout(detectGroup); + detectLayout->setSpacing(16); + + auto *modeRow = new QHBoxLayout(); + auto *modeLbl = new QLabel("检测模式:", this); + modeLbl->setFixedWidth(100); + modeLbl->setStyleSheet(LABEL_STYLE); + modeRow->addWidget(modeLbl); + + auto *modeGroup = new QButtonGroup(this); + m_resistanceMode = new QRadioButton("电阻通断", this); + m_voltageMode = new QRadioButton("电压模式 (50V内)", this); + m_resistanceMode->setChecked(true); + modeGroup->addButton(m_resistanceMode); + modeGroup->addButton(m_voltageMode); + + m_resistanceMode->setStyleSheet("QRadioButton { font-size: 14px; }"); + m_voltageMode->setStyleSheet("QRadioButton { font-size: 14px; }"); + + modeRow->addWidget(m_resistanceMode); + modeRow->addWidget(m_voltageMode); + modeRow->addStretch(); + detectLayout->addLayout(modeRow); + + auto *displayRow = new QHBoxLayout(); + displayRow->setSpacing(16); + + auto *statusBox = new QVBoxLayout(); + statusBox->addWidget(new QLabel("开关状态", this)); + m_statusDisplay = createValueDisplay("---", 140); + statusBox->addWidget(m_statusDisplay); + displayRow->addLayout(statusBox); + + auto *valueBox = new QVBoxLayout(); + valueBox->addWidget(new QLabel("测量值", this)); + m_valueDisplay = createValueDisplay("---", 160); + valueBox->addWidget(m_valueDisplay); + displayRow->addLayout(valueBox); + + displayRow->addStretch(); + detectLayout->addLayout(displayRow); + + m_detectBtn = createPrimaryButton("开始检测", 140); + connect(m_detectBtn, &QPushButton::clicked, this, &SwitchDetectionWidget::onDetectClicked); + detectLayout->addWidget(m_detectBtn); + + m_mainLayout->addWidget(detectGroup); + m_mainLayout->addStretch(); +} + +void SwitchDetectionWidget::onDetectClicked() +{ + if (m_resistanceMode->isChecked()) + { + m_statusDisplay->setText("闭合"); + m_statusDisplay->setStyleSheet(VALUE_DISPLAY_STYLE + " color: #43A047;"); + m_valueDisplay->setText("0.5 Ω"); + } + else + { + m_statusDisplay->setText("断开"); + m_statusDisplay->setStyleSheet(VALUE_DISPLAY_STYLE + " color: #D32F2F;"); + m_valueDisplay->setText("24.5 V"); + } +} + +// ============== SignalAcquisitionWidget ============== + +SignalAcquisitionWidget::SignalAcquisitionWidget(QWidget *parent) + : MeasurementModuleBase("信号采集", parent) +{ + setupUI(); + m_acquisitionTimer = new QTimer(this); +} + +void SignalAcquisitionWidget::setupUI() +{ + auto *configGroup = createStyledGroupBox("采集配置"); + auto *configLayout = new QVBoxLayout(configGroup); + configLayout->setSpacing(16); + + m_channel1Type = createStyledComboBox({"电压", "电流"}, 120); + configLayout->addLayout(createLabeledRow("通道1", m_channel1Type)); + + m_channel2Type = createStyledComboBox({"电压", "电流"}, 120); + configLayout->addLayout(createLabeledRow("通道2", m_channel2Type)); + + m_rateSpin = createStyledSpinBox(25, 1000, 0, 120); + m_rateSpin->setValue(25); + m_rateSpin->setSuffix(" ms/点"); + configLayout->addLayout(createLabeledRow("采集速率", m_rateSpin)); + + auto *btnRow = new QHBoxLayout(); + m_startBtn = createPrimaryButton("开始采集", 120); + m_stopBtn = createSecondaryButton("停止", 100); + m_exportBtn = createSecondaryButton("导出数据", 120); + m_stopBtn->setEnabled(false); + + connect(m_startBtn, &QPushButton::clicked, this, &SignalAcquisitionWidget::onStartClicked); + connect(m_stopBtn, &QPushButton::clicked, this, &SignalAcquisitionWidget::onStopClicked); + connect(m_exportBtn, &QPushButton::clicked, this, &SignalAcquisitionWidget::onExportClicked); + + btnRow->addWidget(m_startBtn); + btnRow->addWidget(m_stopBtn); + btnRow->addWidget(m_exportBtn); + btnRow->addStretch(); + configLayout->addLayout(btnRow); + + m_mainLayout->addWidget(configGroup); + + auto *dataGroup = createStyledGroupBox("采集数据"); + auto *dataLayout = new QVBoxLayout(dataGroup); + + m_dataTable = new QTableWidget(this); + m_dataTable->setColumnCount(3); + m_dataTable->setHorizontalHeaderLabels({"时间", "通道1", "通道2"}); + m_dataTable->setStyleSheet( + "QTableWidget { border: 1px solid #E0E0E0; gridline-color: #E0E0E0; background: white; }" + "QHeaderView::section { background: #ECEFF1; padding: 8px; border: none; " + "border-bottom: 2px solid #CFD8DC; font-weight: bold; }"); + m_dataTable->horizontalHeader()->setStretchLastSection(true); + m_dataTable->setMinimumHeight(250); + dataLayout->addWidget(m_dataTable); + + m_mainLayout->addWidget(dataGroup); + m_mainLayout->addStretch(); +} + +void SignalAcquisitionWidget::onStartClicked() +{ + m_startBtn->setEnabled(false); + m_stopBtn->setEnabled(true); + m_dataTable->setRowCount(0); + OverlayDialog::information(this, "采集启动", "信号采集已开始"); +} + +void SignalAcquisitionWidget::onStopClicked() +{ + m_startBtn->setEnabled(true); + m_stopBtn->setEnabled(false); + OverlayDialog::information(this, "采集停止", "信号采集已停止"); +} + +void SignalAcquisitionWidget::onExportClicked() +{ + QString filename = QFileDialog::getSaveFileName(this, "导出数据", "", "CSV文件 (*.csv)"); + if (!filename.isEmpty()) + { + OverlayDialog::information(this, "导出成功", "数据已导出到: " + filename); + } +} + +// ============== RampSignalWidget ============== + +RampSignalWidget::RampSignalWidget(QWidget *parent) + : MeasurementModuleBase("斜波输出", parent) +{ + setupUI(); +} + +void RampSignalWidget::setupUI() +{ + auto *configGroup = createStyledGroupBox("斜波配置"); + auto *configLayout = new QVBoxLayout(configGroup); + configLayout->setSpacing(16); + + m_signalType = createStyledComboBox({"电压", "电流"}, 120); + configLayout->addLayout(createLabeledRow("信号类型", m_signalType)); + + m_startValue = createStyledSpinBox(0, 20, 3, 120); + m_startValue->setValue(0); + configLayout->addLayout(createLabeledRow("起始值", m_startValue)); + + m_endValue = createStyledSpinBox(0, 20, 3, 120); + m_endValue->setValue(10); + configLayout->addLayout(createLabeledRow("结束值", m_endValue)); + + m_rampRate = createStyledSpinBox(0.01, 10, 2, 120); + m_rampRate->setValue(0.1); + m_rampRate->setSuffix(" /s"); + configLayout->addLayout(createLabeledRow("斜波速率", m_rampRate)); + + auto *displayBox = new QVBoxLayout(); + displayBox->addWidget(new QLabel("当前输出值", this)); + m_currentValueDisplay = createValueDisplay("---", 160); + displayBox->addWidget(m_currentValueDisplay); + configLayout->addLayout(displayBox); + + m_rampProgress = new QProgressBar(this); + m_rampProgress->setRange(0, 100); + m_rampProgress->setValue(0); + m_rampProgress->setStyleSheet( + "QProgressBar { border: 2px solid #E0E0E0; border-radius: 6px; text-align: center; " + "background: white; height: 28px; }" + "QProgressBar::chunk { background: #1976D2; border-radius: 4px; }"); + configLayout->addWidget(m_rampProgress); + + auto *btnRow = new QHBoxLayout(); + m_startBtn = createPrimaryButton("启动斜波", 120); + m_stopBtn = createSecondaryButton("停止", 100); + m_stopBtn->setEnabled(false); + + connect(m_startBtn, &QPushButton::clicked, this, &RampSignalWidget::onStartClicked); + connect(m_stopBtn, &QPushButton::clicked, this, &RampSignalWidget::onStopClicked); + + btnRow->addWidget(m_startBtn); + btnRow->addWidget(m_stopBtn); + btnRow->addStretch(); + configLayout->addLayout(btnRow); + + m_mainLayout->addWidget(configGroup); + m_mainLayout->addStretch(); +} + +void RampSignalWidget::onStartClicked() +{ + m_startBtn->setEnabled(false); + m_stopBtn->setEnabled(true); + m_rampProgress->setValue(0); + m_currentValueDisplay->setText(QString::number(m_startValue->value(), 'f', 3)); + OverlayDialog::information(this, "斜波启动", "斜波信号输出已启动"); +} + +void RampSignalWidget::onStopClicked() +{ + m_startBtn->setEnabled(true); + m_stopBtn->setEnabled(false); + OverlayDialog::information(this, "斜波停止", "斜波信号输出已停止"); +} + +// ============== RippleDetectionWidget ============== + +RippleDetectionWidget::RippleDetectionWidget(QWidget *parent) + : MeasurementModuleBase("纹波检测", parent) +{ + setupUI(); +} + +void RippleDetectionWidget::setupUI() +{ + auto *detectGroup = createStyledGroupBox("电源纹波检测 (最大50V)"); + auto *detectLayout = new QVBoxLayout(detectGroup); + detectLayout->setSpacing(16); + + auto *displayRow = new QHBoxLayout(); + displayRow->setSpacing(16); + + auto *rippleBox = new QVBoxLayout(); + rippleBox->addWidget(new QLabel("纹波值 (mVpp)", this)); + m_rippleDisplay = createValueDisplay("---", 160); + rippleBox->addWidget(m_rippleDisplay); + displayRow->addLayout(rippleBox); + + displayRow->addStretch(); + detectLayout->addLayout(displayRow); + + auto *btnRow = new QHBoxLayout(); + m_detectBtn = createPrimaryButton("检测纹波", 120); + m_diagnoseBtn = createPrimaryButton("AI诊断", 120); + m_diagnoseBtn->setEnabled(false); + + connect(m_detectBtn, &QPushButton::clicked, this, &RippleDetectionWidget::onDetectClicked); + connect(m_diagnoseBtn, &QPushButton::clicked, this, &RippleDetectionWidget::onDiagnoseClicked); + + btnRow->addWidget(m_detectBtn); + btnRow->addWidget(m_diagnoseBtn); + btnRow->addStretch(); + detectLayout->addLayout(btnRow); + + m_mainLayout->addWidget(detectGroup); + + auto *diagGroup = createStyledGroupBox("故障诊断结果"); + auto *diagLayout = new QVBoxLayout(diagGroup); + diagLayout->setSpacing(12); + + auto *diagRow = new QHBoxLayout(); + diagRow->addWidget(new QLabel("诊断结果:", this)); + m_diagnosisDisplay = new QLabel("未诊断", this); + m_diagnosisDisplay->setStyleSheet("font-size: 15px; color: #455A64; font-weight: bold;"); + diagRow->addWidget(m_diagnosisDisplay); + diagRow->addStretch(); + diagLayout->addLayout(diagRow); + + auto *confRow = new QHBoxLayout(); + confRow->addWidget(new QLabel("置信度:", this)); + m_confidenceDisplay = new QLabel("---", this); + m_confidenceDisplay->setStyleSheet("font-size: 15px; color: #1976D2; font-weight: bold;"); + confRow->addWidget(m_confidenceDisplay); + confRow->addStretch(); + diagLayout->addLayout(confRow); + + m_mainLayout->addWidget(diagGroup); + m_mainLayout->addStretch(); +} + +void RippleDetectionWidget::onDetectClicked() +{ + m_rippleDisplay->setText("125.6"); + m_diagnoseBtn->setEnabled(true); + OverlayDialog::information(this, "检测完成", "纹波检测完成,可进行AI诊断"); +} + +void RippleDetectionWidget::onDiagnoseClicked() +{ + m_diagnosisDisplay->setText("⚠ 滤波电容老化"); + m_diagnosisDisplay->setStyleSheet("font-size: 15px; color: #F57C00; font-weight: bold;"); + m_confidenceDisplay->setText("92.5%"); + OverlayDialog::warning(this, "诊断完成", + "AI诊断结果:\n滤波电容老化\n置信度: 92.5%\n\n建议: 更换输出滤波电容"); +} + +// ============== SignalMeasurementWidget ============== + +SignalMeasurementWidget::SignalMeasurementWidget(QWidget *parent) + : QWidget(parent), m_titleLabel(nullptr), m_currentModuleIndex(-1) +{ + setupUI(); +} + +void SignalMeasurementWidget::setModule(int moduleIndex) +{ + onModuleSelected(moduleIndex); +} + +void SignalMeasurementWidget::setupUI() +{ + auto *mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(0); + + // 顶部工具栏 + auto *headerWidget = new QWidget(this); + headerWidget->setStyleSheet("background: #263238;"); + headerWidget->setFixedHeight(48); + + auto *headerLayout = new QHBoxLayout(headerWidget); + headerLayout->setContentsMargins(8, 0, 16, 0); + headerLayout->setSpacing(12); + + auto *backBtn = new QPushButton("返回", this); + backBtn->setFixedHeight(34); + backBtn->setStyleSheet( + "QPushButton { background: #455A64; color: white; border: none; border-radius: 4px; " + "padding: 0 14px; font-size: 13px; }" + "QPushButton:hover { background: #546E7A; }" + "QPushButton:pressed { background: #37474F; }"); + connect(backBtn, &QPushButton::clicked, this, &SignalMeasurementWidget::onBackClicked); + headerLayout->addWidget(backBtn); + + m_titleLabel = new QLabel("信号测量功能", this); + m_titleLabel->setStyleSheet("color: white; font-size: 16px; font-weight: bold;"); + headerLayout->addWidget(m_titleLabel, 1); + + mainLayout->addWidget(headerWidget); + + // 内容区域 - 添加滚动支持 + setupContentArea(); + + auto *scrollArea = new QScrollArea(this); + scrollArea->setWidget(m_contentStack); + scrollArea->setWidgetResizable(true); + scrollArea->setFrameShape(QFrame::NoFrame); + scrollArea->setStyleSheet( + "QScrollArea { background: #FAFAFA; border: none; }" + "QScrollBar:vertical { width: 8px; background: transparent; }" + "QScrollBar::handle:vertical { background: #BDBDBD; border-radius: 4px; min-height: 30px; }" + "QScrollBar::handle:vertical:hover { background: #9E9E9E; }" + "QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0px; }" + "QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: transparent; }"); + + mainLayout->addWidget(scrollArea, 1); +} + +void SignalMeasurementWidget::setupContentArea() +{ + m_contentStack = new QStackedWidget(this); + + m_rtdWidget = new RTDMeasurementWidget(this); + m_tcWidget = new ThermocoupleMeasurementWidget(this); + m_insulationWidget = new InsulationMeasurementWidget(this); + m_dcCurrentWidget = new DCCurrentWidget(this); + m_dcVoltageWidget = new DCVoltageWidget(this); + m_frequencyWidget = new FrequencyWidget(this); + m_acVoltageWidget = new ACVoltageWidget(this); + m_switchWidget = new SwitchDetectionWidget(this); + m_acquisitionWidget = new SignalAcquisitionWidget(this); + m_rampWidget = new RampSignalWidget(this); + m_rippleWidget = new RippleDetectionWidget(this); + m_rtdOutputWidget = new RTDOutputWidget(this); + + m_contentStack->addWidget(m_rtdWidget); + m_contentStack->addWidget(m_tcWidget); + m_contentStack->addWidget(m_insulationWidget); + m_contentStack->addWidget(m_dcCurrentWidget); + m_contentStack->addWidget(m_dcVoltageWidget); + m_contentStack->addWidget(m_frequencyWidget); + m_contentStack->addWidget(m_acVoltageWidget); + m_contentStack->addWidget(m_switchWidget); + m_contentStack->addWidget(m_acquisitionWidget); + m_contentStack->addWidget(m_rampWidget); + m_contentStack->addWidget(m_rippleWidget); + m_contentStack->addWidget(m_rtdOutputWidget); +} + +void SignalMeasurementWidget::onBackClicked() +{ + emit backRequested(); +} + +void SignalMeasurementWidget::onModuleSelected(int index) +{ + QStringList moduleTitles = { + "热电阻测量", "热电偶测量", "绝缘电阻测量", + "直流电流测量", "直流电压测量", "频率信号测量", + "220VAC测量", "开关量检测", "信号采集", + "斜波信号输出", "纹波检测诊断", "PT100模拟输出"}; + + m_currentModuleIndex = index; + if (index >= 0 && index < moduleTitles.size()) + { + m_titleLabel->setText(moduleTitles[index]); + m_contentStack->setCurrentIndex(index); + } +} + +// ============== RTDOutputWidget ============== + +RTDOutputWidget::RTDOutputWidget(QWidget *parent) + : MeasurementModuleBase("PT100模拟输出", parent), + m_isChannel1Outputting(false), + m_isChannel2Outputting(false) +{ + setupUI(); +} + +RTDOutputWidget::~RTDOutputWidget() +{ +} + +void RTDOutputWidget::setupUI() +{ + // === 通道1输出组 === + auto *channel1Group = createStyledGroupBox("PT100 模拟输出 (输出通道1)"); + auto *ch1Layout = new QVBoxLayout(channel1Group); + ch1Layout->setSpacing(16); + + m_channel1TempSpin = createStyledSpinBox(-200, 850, 1, 150); + m_channel1TempSpin->setValue(25.0); + connect(m_channel1TempSpin, QOverload::of(&QDoubleSpinBox::valueChanged), + this, &RTDOutputWidget::onChannel1TempChanged); + ch1Layout->addLayout(createLabeledRow("设定温度", m_channel1TempSpin, "°C")); + + auto *ch1DisplayRow = new QHBoxLayout(); + ch1DisplayRow->setSpacing(16); + auto *ch1ResLbl = new QLabel("输出电阻 (Ω):", this); + ch1ResLbl->setStyleSheet(LABEL_STYLE); + ch1DisplayRow->addWidget(ch1ResLbl); + m_channel1ResDisplay = createValueDisplay("---", 180); + ch1DisplayRow->addWidget(m_channel1ResDisplay); + ch1DisplayRow->addStretch(); + ch1Layout->addLayout(ch1DisplayRow); + + auto *ch1BtnRow = new QHBoxLayout(); + m_channel1OutputBtn = createPrimaryButton("启动输出", 120); + connect(m_channel1OutputBtn, &QPushButton::clicked, this, &RTDOutputWidget::onChannel1OutputClicked); + ch1BtnRow->addWidget(m_channel1OutputBtn); + + m_channel1StopBtn = createSecondaryButton("停止输出", 120); + m_channel1StopBtn->setEnabled(false); + connect(m_channel1StopBtn, &QPushButton::clicked, this, &RTDOutputWidget::onChannel1StopClicked); + ch1BtnRow->addWidget(m_channel1StopBtn); + + ch1BtnRow->addStretch(); + ch1Layout->addLayout(ch1BtnRow); + + m_mainLayout->addWidget(channel1Group); + + // === 通道2输出组 === + auto *channel2Group = createStyledGroupBox("PT100 模拟输出 (输出通道2)"); + auto *ch2Layout = new QVBoxLayout(channel2Group); + ch2Layout->setSpacing(16); + + m_channel2TempSpin = createStyledSpinBox(-200, 850, 1, 150); + m_channel2TempSpin->setValue(25.0); + connect(m_channel2TempSpin, QOverload::of(&QDoubleSpinBox::valueChanged), + this, &RTDOutputWidget::onChannel2TempChanged); + ch2Layout->addLayout(createLabeledRow("设定温度", m_channel2TempSpin, "°C")); + + auto *ch2DisplayRow = new QHBoxLayout(); + ch2DisplayRow->setSpacing(16); + auto *ch2ResLbl = new QLabel("输出电阻 (Ω):", this); + ch2ResLbl->setStyleSheet(LABEL_STYLE); + ch2DisplayRow->addWidget(ch2ResLbl); + m_channel2ResDisplay = createValueDisplay("---", 180); + ch2DisplayRow->addWidget(m_channel2ResDisplay); + ch2DisplayRow->addStretch(); + ch2Layout->addLayout(ch2DisplayRow); + + auto *ch2BtnRow = new QHBoxLayout(); + m_channel2OutputBtn = createPrimaryButton("启动输出", 120); + connect(m_channel2OutputBtn, &QPushButton::clicked, this, &RTDOutputWidget::onChannel2OutputClicked); + ch2BtnRow->addWidget(m_channel2OutputBtn); + + m_channel2StopBtn = createSecondaryButton("停止输出", 120); + m_channel2StopBtn->setEnabled(false); + connect(m_channel2StopBtn, &QPushButton::clicked, this, &RTDOutputWidget::onChannel2StopClicked); + ch2BtnRow->addWidget(m_channel2StopBtn); + + ch2BtnRow->addStretch(); + ch2Layout->addLayout(ch2BtnRow); + + m_mainLayout->addWidget(channel2Group); + m_mainLayout->addStretch(); + + // 初始化显示 + onChannel1TempChanged(m_channel1TempSpin->value()); + onChannel2TempChanged(m_channel2TempSpin->value()); +} + +void RTDOutputWidget::onChannel1OutputClicked() +{ + m_isChannel1Outputting = true; + m_channel1OutputBtn->setEnabled(false); + m_channel1StopBtn->setEnabled(true); + m_channel1TempSpin->setEnabled(false); + + // TODO: 实际发送输出命令到硬件 + qDebug() << "通道1开始输出:" << m_channel1TempSpin->value() << "°C"; +} + +void RTDOutputWidget::onChannel1StopClicked() +{ + m_isChannel1Outputting = false; + m_channel1OutputBtn->setEnabled(true); + m_channel1StopBtn->setEnabled(false); + m_channel1TempSpin->setEnabled(true); + + // TODO: 发送停止输出命令到硬件 + qDebug() << "通道1停止输出"; +} + +void RTDOutputWidget::onChannel2OutputClicked() +{ + m_isChannel2Outputting = true; + m_channel2OutputBtn->setEnabled(false); + m_channel2StopBtn->setEnabled(true); + m_channel2TempSpin->setEnabled(false); + + // TODO: 实际发送输出命令到硬件 + qDebug() << "通道2开始输出:" << m_channel2TempSpin->value() << "°C"; +} + +void RTDOutputWidget::onChannel2StopClicked() +{ + m_isChannel2Outputting = false; + m_channel2OutputBtn->setEnabled(true); + m_channel2StopBtn->setEnabled(false); + m_channel2TempSpin->setEnabled(true); + + // TODO: 发送停止输出命令到硬件 + qDebug() << "通道2停止输出"; +} + +void RTDOutputWidget::onChannel1TempChanged(double value) +{ + double resistance = convertTemperatureToResistance(value); + m_channel1ResDisplay->setText(QString::number(resistance, 'f', 2)); +} + +void RTDOutputWidget::onChannel2TempChanged(double value) +{ + double resistance = convertTemperatureToResistance(value); + m_channel2ResDisplay->setText(QString::number(resistance, 'f', 2)); +} + +double RTDOutputWidget::convertTemperatureToResistance(double temperature) +{ + // PT100标准公式 R(T) = R0 * (1 + A*T + B*T^2) + // R0 = 100Ω, A = 3.9083e-3, B = -5.775e-7 (0-850°C) + const double R0 = 100.0; + const double A = 3.9083e-3; + const double B = -5.775e-7; + + if (temperature >= 0) + { + return R0 * (1.0 + A * temperature + B * temperature * temperature); + } + else + { + // 负温度区 R(T) = R0 * (1 + A*T + B*T^2 + C*(T-100)*T^3) + const double C = -4.183e-12; + return R0 * (1.0 + A * temperature + B * temperature * temperature + + C * (temperature - 100.0) * temperature * temperature * temperature); + } +} diff --git a/widgets/signalmeasurementwidget.h b/widgets/signalmeasurementwidget.h new file mode 100644 index 0000000..5e929ad --- /dev/null +++ b/widgets/signalmeasurementwidget.h @@ -0,0 +1,483 @@ +#ifndef SIGNALMEASUREMENTWIDGET_H +#define SIGNALMEASUREMENTWIDGET_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../hardware/channelmanager.h" +#include "channelstatuswidget.h" + +/** + * @brief 信号测量功能模块基类 + * 提供统一的工业风格UI框架 + */ +class MeasurementModuleBase : public QWidget +{ + Q_OBJECT + +public: + explicit MeasurementModuleBase(const QString &title, QWidget *parent = nullptr); + +protected: + // 创建标准样式的输入控件 + QLineEdit *createStyledInput(const QString &placeholder = "", int width = 120); + QDoubleSpinBox *createStyledSpinBox(double min, double max, int decimals = 2, int width = 120); + QComboBox *createStyledComboBox(const QStringList &items, int width = 150); + QPushButton *createPrimaryButton(const QString &text, int width = 120); + QPushButton *createSecondaryButton(const QString &text, int width = 100); + QLabel *createValueDisplay(const QString &value = "---", int width = 180); + QGroupBox *createStyledGroupBox(const QString &title); + + // 添加带标签的输入行 + QHBoxLayout *createLabeledRow(const QString &label, QWidget *widget, const QString &unit = ""); + + // 统一样式常量 + static const QString CARD_STYLE; + static const QString INPUT_STYLE; + static const QString PRIMARY_BTN_STYLE; + static const QString SECONDARY_BTN_STYLE; + static const QString VALUE_DISPLAY_STYLE; + static const QString LABEL_STYLE; + static const QString GROUPBOX_STYLE; + + QVBoxLayout *m_mainLayout; + QString m_title; +}; + +/** + * @brief 热电阻测量模块 + * 支持2/3/4线热电阻测量和PT100输出 + * 整合ChannelManager进行通道管理 + */ +class RTDMeasurementWidget : public MeasurementModuleBase +{ + Q_OBJECT + +public: + enum RTDType + { + PT100, + PT1000, + CU50, + CU100 + }; + enum MeasurementMode + { + TWO_WIRE_MODE, + THREE_WIRE_MODE, + FOUR_WIRE_MODE + }; + + explicit RTDMeasurementWidget(QWidget *parent = nullptr); + ~RTDMeasurementWidget(); + +signals: + void measurementDataReady(double resistance, double temperature); + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; + +private slots: + void onMeasureClicked(); + void onStopClicked(); + void onWireTypeChanged(int index); + void onChannelSelected(ChannelManager::ChannelId channelId); + void updateMeasurement(); + +private: + void setupUI(); + void setupChannelSelection(); + void updateChannelSelection(); + void updateConnectionModeVisibility(); + void generateSimulatedData(); + double convertResistanceToTemperature(double resistance); + bool validateChannelForMode(); + + // 通道管理 + ChannelManager *m_channelManager; + QMap m_channelWidgets; + ChannelManager::ChannelId m_selectedChannel; + ChannelManager::ChannelId m_currentChannel; + QTimer *m_measurementTimer; + + // 测量状态 + bool m_isMeasuring; + MeasurementMode m_currentMode; + RTDType m_currentRTDType; + double m_currentResistance; + double m_currentTemperature; + + // UI控件 + QWidget *m_channelSelectionWidget; + QComboBox *m_wireTypeCombo; + QComboBox *m_rtdTypeCombo; + QLabel *m_resistanceDisplay; + QLabel *m_temperatureDisplay; + QLabel *m_lineResDisplay; + QLabel *m_channelStatusLabel; + QLabel *m_wiringDiagramLabel; + QPushButton *m_measureBtn; + QPushButton *m_stopBtn; +}; + +/** + * @brief PT100输出模块 - 双通道 + * 支持两个通道的PT100温度输出 + */ +class RTDOutputWidget : public MeasurementModuleBase +{ + Q_OBJECT + +public: + explicit RTDOutputWidget(QWidget *parent = nullptr); + ~RTDOutputWidget(); + +private slots: + void onChannel1OutputClicked(); + void onChannel1StopClicked(); + void onChannel2OutputClicked(); + void onChannel2StopClicked(); + void onChannel1TempChanged(double value); + void onChannel2TempChanged(double value); + +private: + void setupUI(); + double convertTemperatureToResistance(double temperature); + + // 输出状态 + bool m_isChannel1Outputting; + bool m_isChannel2Outputting; + + // 通道1控件 + QDoubleSpinBox *m_channel1TempSpin; + QLabel *m_channel1ResDisplay; + QPushButton *m_channel1OutputBtn; + QPushButton *m_channel1StopBtn; + + // 通道2控件 + QDoubleSpinBox *m_channel2TempSpin; + QLabel *m_channel2ResDisplay; + QPushButton *m_channel2OutputBtn; + QPushButton *m_channel2StopBtn; +}; + +/** + * @brief 热电偶测量模块 + * 支持热电偶电压测量和毫伏输出 + */ +class ThermocoupleMeasurementWidget : public MeasurementModuleBase +{ + Q_OBJECT + +public: + explicit ThermocoupleMeasurementWidget(QWidget *parent = nullptr); + +private slots: + void onMeasureClicked(); + void onOutputClicked(); + +private: + void setupUI(); + + QComboBox *m_tcTypeCombo; // K/J/T/E/S/R/B型选择 + QLabel *m_voltageDisplay; // 电压显示(mV) + QLabel *m_temperatureDisplay; // 温度显示 + QDoubleSpinBox *m_outputTempSpin; // 输出温度设置 + QLabel *m_outputVoltageDisplay; // 输出电压显示 + QPushButton *m_measureBtn; + QPushButton *m_outputBtn; +}; + +/** + * @brief 绝缘电阻测量模块 + * 支持50V/100V绝缘电阻测量 + */ +class InsulationMeasurementWidget : public MeasurementModuleBase +{ + Q_OBJECT + +public: + explicit InsulationMeasurementWidget(QWidget *parent = nullptr); + +private slots: + void onMeasureClicked(); + void onAutoTestToggled(bool enabled); + +private: + void setupUI(); + + QRadioButton *m_50vRadio; + QRadioButton *m_100vRadio; + QCheckBox *m_autoTestCheck; // 自动测试 + QLabel *m_insulationDisplay; // 绝缘电阻显示 + QLabel *m_statusDisplay; // 状态显示 + QProgressBar *m_dischargeProgress; // 放电进度 + QPushButton *m_measureBtn; +}; + +/** + * @brief 直流电流信号模块 + * 支持0-20mA电流输出/测量 + */ +class DCCurrentWidget : public MeasurementModuleBase +{ + Q_OBJECT + +public: + explicit DCCurrentWidget(QWidget *parent = nullptr); + +private slots: + void onMeasureClicked(); + void onOutputClicked(); + +private: + void setupUI(); + + QRadioButton *m_activeRadio; // 有源模式 + QRadioButton *m_passiveRadio; // 无源模式 + QLabel *m_currentDisplay; // 电流显示 + QLabel *m_maxDisplay; // 最大值 + QLabel *m_minDisplay; // 最小值 + QLabel *m_avgDisplay; // 平均值 + QDoubleSpinBox *m_outputSpin; // 输出设置 + QPushButton *m_measureBtn; + QPushButton *m_outputBtn; +}; + +/** + * @brief 直流电压信号模块 + * 支持0-20V电压输出/测量 + */ +class DCVoltageWidget : public MeasurementModuleBase +{ + Q_OBJECT + +public: + explicit DCVoltageWidget(QWidget *parent = nullptr); + +private slots: + void onMeasureClicked(); + void onOutputClicked(); + +private: + void setupUI(); + + QLabel *m_voltageDisplay; + QLabel *m_maxDisplay; + QLabel *m_minDisplay; + QLabel *m_avgDisplay; + QDoubleSpinBox *m_outputSpin; + QPushButton *m_measureBtn; + QPushButton *m_outputBtn; +}; + +/** + * @brief 频率信号模块 + * 支持频率测量和方波/正弦波输出 + */ +class FrequencyWidget : public MeasurementModuleBase +{ + Q_OBJECT + +public: + explicit FrequencyWidget(QWidget *parent = nullptr); + +private slots: + void onMeasureClicked(); + void onOutputClicked(); + void onWaveTypeChanged(int index); + +private: + void setupUI(); + + QLabel *m_frequencyDisplay; + QComboBox *m_waveTypeCombo; // 方波/正弦波 + QDoubleSpinBox *m_freqSpin; // 频率设置 + QDoubleSpinBox *m_amplitudeSpin; // 幅值设置 + QPushButton *m_measureBtn; + QPushButton *m_outputBtn; +}; + +/** + * @brief 220VAC测量模块 + */ +class ACVoltageWidget : public MeasurementModuleBase +{ + Q_OBJECT + +public: + explicit ACVoltageWidget(QWidget *parent = nullptr); + +private slots: + void onMeasureClicked(); + +private: + void setupUI(); + + QLabel *m_voltageDisplay; + QLabel *m_frequencyDisplay; + QPushButton *m_measureBtn; +}; + +/** + * @brief 开关量检测模块 + */ +class SwitchDetectionWidget : public MeasurementModuleBase +{ + Q_OBJECT + +public: + explicit SwitchDetectionWidget(QWidget *parent = nullptr); + +private slots: + void onDetectClicked(); + +private: + void setupUI(); + + QRadioButton *m_resistanceMode; // 电阻通断模式 + QRadioButton *m_voltageMode; // 电压模式 + QLabel *m_statusDisplay; // 通断状态 + QLabel *m_valueDisplay; // 测量值 + QPushButton *m_detectBtn; +}; + +/** + * @brief 信号采集记录模块 + */ +class SignalAcquisitionWidget : public MeasurementModuleBase +{ + Q_OBJECT + +public: + explicit SignalAcquisitionWidget(QWidget *parent = nullptr); + +private slots: + void onStartClicked(); + void onStopClicked(); + void onExportClicked(); + +private: + void setupUI(); + + QComboBox *m_channel1Type; // 通道1类型 + QComboBox *m_channel2Type; // 通道2类型 + QDoubleSpinBox *m_rateSpin; // 采集速率 + QTableWidget *m_dataTable; // 数据表格 + QPushButton *m_startBtn; + QPushButton *m_stopBtn; + QPushButton *m_exportBtn; + QTimer *m_acquisitionTimer; +}; + +/** + * @brief 斜波信号输出模块 + */ +class RampSignalWidget : public MeasurementModuleBase +{ + Q_OBJECT + +public: + explicit RampSignalWidget(QWidget *parent = nullptr); + +private slots: + void onStartClicked(); + void onStopClicked(); + +private: + void setupUI(); + + QComboBox *m_signalType; // 电压/电流 + QDoubleSpinBox *m_startValue; // 起始值 + QDoubleSpinBox *m_endValue; // 结束值 + QDoubleSpinBox *m_rampRate; // 斜波速率 + QLabel *m_currentValueDisplay; // 当前值 + QProgressBar *m_rampProgress; + QPushButton *m_startBtn; + QPushButton *m_stopBtn; +}; + +/** + * @brief 电源纹波检测模块 + */ +class RippleDetectionWidget : public MeasurementModuleBase +{ + Q_OBJECT + +public: + explicit RippleDetectionWidget(QWidget *parent = nullptr); + +private slots: + void onDetectClicked(); + void onDiagnoseClicked(); + +private: + void setupUI(); + + QLabel *m_rippleDisplay; // 纹波值 + QLabel *m_diagnosisDisplay; // 诊断结果 + QLabel *m_confidenceDisplay; // 置信度 + QPushButton *m_detectBtn; + QPushButton *m_diagnoseBtn; +}; + +/** + * @brief 信号测量主页面 + * 包含所有测量功能模块的导航 + */ +class SignalMeasurementWidget : public QWidget +{ + Q_OBJECT + +public: + explicit SignalMeasurementWidget(QWidget *parent = nullptr); + + void setModule(int moduleIndex); + +signals: + void backRequested(); + +private slots: + void onBackClicked(); + void onModuleSelected(int index); + +private: + void setupUI(); + void setupContentArea(); + + QStackedWidget *m_contentStack; + QLabel *m_titleLabel; + int m_currentModuleIndex; + + // 各功能模块 + RTDMeasurementWidget *m_rtdWidget; + RTDOutputWidget *m_rtdOutputWidget; + ThermocoupleMeasurementWidget *m_tcWidget; + InsulationMeasurementWidget *m_insulationWidget; + DCCurrentWidget *m_dcCurrentWidget; + DCVoltageWidget *m_dcVoltageWidget; + FrequencyWidget *m_frequencyWidget; + ACVoltageWidget *m_acVoltageWidget; + SwitchDetectionWidget *m_switchWidget; + SignalAcquisitionWidget *m_acquisitionWidget; + RampSignalWidget *m_rampWidget; + RippleDetectionWidget *m_rippleWidget; +}; + +#endif // SIGNALMEASUREMENTWIDGET_H diff --git a/widgets/signaltrimwidget.cpp b/widgets/signaltrimwidget.cpp new file mode 100644 index 0000000..35de7ca --- /dev/null +++ b/widgets/signaltrimwidget.cpp @@ -0,0 +1,618 @@ +#include "signaltrimwidget.h" +#include +#include +#include +#include +#include +#include + +static const char *GROUP_STYLE = R"( + QGroupBox { + background-color: white; + border: 1px solid #e0e0e0; + border-radius: 12px; + margin-top: 8px; + padding: 16px; + font-size: 14px; + font-weight: 600; + } + QGroupBox::title { + subcontrol-origin: margin; + left: 16px; + padding: 0 8px; + color: #333; + } +)"; + +static const char *BUTTON_STYLE = R"( + QPushButton { + background-color: #2196F3; + color: white; + border: none; + border-radius: 8px; + padding: 12px 32px; + font-size: 14px; + font-weight: 600; + } + QPushButton:hover { + background-color: #1976D2; + } + QPushButton:pressed { + background-color: #1565C0; + } + QPushButton:disabled { + background-color: #BDBDBD; + } +)"; + +static const char *TRIM_BUTTON_STYLE = R"( + QPushButton { + background-color: #ff9800; + color: white; + border: none; + border-radius: 8px; + padding: 12px 20px; + font-size: 28px; + font-weight: bold; + min-width: 60px; + min-height: 50px; + } + QPushButton:hover { + background-color: #f57c00; + } + QPushButton:pressed { + background-color: #ef6c00; + } +)"; + +SignalTrimWidget::SignalTrimWidget(QWidget *parent) + : QWidget(parent), m_currentValue(0), m_minValue(-100), m_maxValue(100), m_decimals(4) +{ + setupUI(); + updateTotalValue(); +} + +void SignalTrimWidget::setupUI() +{ + QVBoxLayout *mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(0); + + // 标题栏 + mainLayout->addWidget(createTitleBar()); + + // 内容区域 + QScrollArea *scrollArea = new QScrollArea; + scrollArea->setWidgetResizable(true); + scrollArea->setFrameShape(QFrame::NoFrame); + scrollArea->setStyleSheet("QScrollArea { background-color: #f5f5f5; }"); + + QWidget *contentWidget = new QWidget; + contentWidget->setStyleSheet("background-color: #f5f5f5;"); + QVBoxLayout *contentLayout = new QVBoxLayout(contentWidget); + contentLayout->setContentsMargins(20, 20, 20, 20); + contentLayout->setSpacing(16); + + // 信号选择面板 + contentLayout->addWidget(createSignalSelectPanel()); + + // 粗调面板 + contentLayout->addWidget(createCoarseAdjustPanel()); + + // 微调面板 + contentLayout->addWidget(createFineAdjustPanel()); + + // 输出面板 + contentLayout->addWidget(createOutputPanel()); + + contentLayout->addStretch(); + + scrollArea->setWidget(contentWidget); + mainLayout->addWidget(scrollArea, 1); +} + +QWidget *SignalTrimWidget::createTitleBar() +{ + QWidget *titleBar = new QWidget; + titleBar->setFixedHeight(60); + titleBar->setStyleSheet("background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #1976D2, stop:1 #1565C0);"); + + QHBoxLayout *layout = new QHBoxLayout(titleBar); + layout->setContentsMargins(16, 0, 16, 0); + + // 返回按钮 + m_backBtn = new QPushButton("← 返回"); + m_backBtn->setCursor(Qt::PointingHandCursor); + m_backBtn->setStyleSheet(R"( + QPushButton { + background: rgba(255,255,255,0.2); + color: white; + border: none; + border-radius: 6px; + padding: 8px 16px; + font-size: 14px; + } + QPushButton:hover { + background: rgba(255,255,255,0.3); + } + )"); + connect(m_backBtn, &QPushButton::clicked, this, &SignalTrimWidget::backRequested); + layout->addWidget(m_backBtn); + + // 标题(居中) + m_titleLabel = new QLabel("信号微调"); + m_titleLabel->setStyleSheet("color: white; font-size: 18px; font-weight: bold;"); + m_titleLabel->setAlignment(Qt::AlignCenter); + layout->addWidget(m_titleLabel, 1); + + // 右侧占位,保持标题居中 + QWidget *spacer = new QWidget; + spacer->setFixedWidth(m_backBtn->sizeHint().width()); + layout->addWidget(spacer); + + return titleBar; +} + +QWidget *SignalTrimWidget::createSignalSelectPanel() +{ + QGroupBox *group = new QGroupBox("信号选择"); + group->setStyleSheet(GROUP_STYLE); + + QHBoxLayout *layout = new QHBoxLayout(group); + layout->setContentsMargins(16, 24, 16, 16); + layout->setSpacing(24); + + // 信号类型 + QVBoxLayout *typeLayout = new QVBoxLayout; + QLabel *typeLabel = new QLabel("信号类型"); + typeLabel->setStyleSheet("color: #666; font-size: 12px; font-weight: normal;"); + m_signalTypeCombo = new QComboBox; + m_signalTypeCombo->addItems({"直流电压 (DC V)", + "直流电流 (DC mA)", + "交流电压 (AC V)", + "电阻 (Ω)", + "热电偶 (°C)", + "热电阻 (°C)", + "频率 (Hz)"}); + m_signalTypeCombo->setStyleSheet(R"( + QComboBox { + background: white; + border: 1px solid #ddd; + border-radius: 6px; + padding: 10px 16px; + min-width: 180px; + font-size: 14px; + } + )"); + connect(m_signalTypeCombo, QOverload::of(&QComboBox::currentIndexChanged), + this, &SignalTrimWidget::onSignalTypeChanged); + typeLayout->addWidget(typeLabel); + typeLayout->addWidget(m_signalTypeCombo); + layout->addLayout(typeLayout); + + // 单位选择 + QVBoxLayout *unitLayout = new QVBoxLayout; + QLabel *unitLabel = new QLabel("单位"); + unitLabel->setStyleSheet("color: #666; font-size: 12px; font-weight: normal;"); + m_unitCombo = new QComboBox; + m_unitCombo->addItems({"V", "mV", "µV"}); + m_unitCombo->setStyleSheet(R"( + QComboBox { + background: white; + border: 1px solid #ddd; + border-radius: 6px; + padding: 10px 16px; + min-width: 100px; + font-size: 14px; + } + )"); + unitLayout->addWidget(unitLabel); + unitLayout->addWidget(m_unitCombo); + layout->addLayout(unitLayout); + + layout->addStretch(); + + return group; +} + +QWidget *SignalTrimWidget::createCoarseAdjustPanel() +{ + QGroupBox *group = new QGroupBox("粗调"); + group->setStyleSheet(GROUP_STYLE); + + QHBoxLayout *layout = new QHBoxLayout(group); + layout->setContentsMargins(16, 24, 16, 16); + layout->setSpacing(16); + + QLabel *valueLabel = new QLabel("设定值:"); + valueLabel->setStyleSheet("font-size: 16px; font-weight: 600;"); + layout->addWidget(valueLabel); + + m_coarseValueSpin = new QDoubleSpinBox; + m_coarseValueSpin->setRange(m_minValue, m_maxValue); + m_coarseValueSpin->setValue(0); + m_coarseValueSpin->setDecimals(m_decimals); + m_coarseValueSpin->setSingleStep(1.0); + m_coarseValueSpin->setStyleSheet(R"( + QDoubleSpinBox { + padding: 12px 16px; + border: 2px solid #2196F3; + border-radius: 8px; + background: white; + font-size: 24px; + font-weight: bold; + font-family: monospace; + min-width: 200px; + } + QDoubleSpinBox::up-button, QDoubleSpinBox::down-button { + subcontrol-origin: border; + width: 32px; + border-left: 2px solid #2196F3; + background: #e3f2fd; + } + QDoubleSpinBox::up-button { subcontrol-position: top right; border-top-right-radius: 6px; } + QDoubleSpinBox::down-button { subcontrol-position: bottom right; border-bottom-right-radius: 6px; } + QDoubleSpinBox::up-button:hover, QDoubleSpinBox::down-button:hover { background: #bbdefb; } + QDoubleSpinBox::up-arrow { image: url(:/icons/arrow_up.svg); width: 14px; height: 14px; } + QDoubleSpinBox::down-arrow { image: url(:/icons/arrow_down.svg); width: 14px; height: 14px; } + )"); + connect(m_coarseValueSpin, QOverload::of(&QDoubleSpinBox::valueChanged), + this, &SignalTrimWidget::onCoarseValueChanged); + layout->addWidget(m_coarseValueSpin); + + m_coarseUnitLabel = new QLabel("V"); + m_coarseUnitLabel->setStyleSheet("font-size: 20px; font-weight: bold; color: #2196F3;"); + layout->addWidget(m_coarseUnitLabel); + + layout->addStretch(); + + // 快捷设置按钮 + QVBoxLayout *presetLayout = new QVBoxLayout; + QLabel *presetLabel = new QLabel("快捷设置:"); + presetLabel->setStyleSheet("color: #666; font-size: 12px;"); + presetLayout->addWidget(presetLabel); + + QHBoxLayout *presetBtnLayout = new QHBoxLayout; + QStringList presets = {"0", "1", "5", "10", "20"}; + for (const QString &preset : presets) + { + QPushButton *btn = new QPushButton(preset); + btn->setStyleSheet(R"( + QPushButton { + background-color: #e3f2fd; + color: #1976D2; + border: 1px solid #90caf9; + border-radius: 6px; + padding: 8px 16px; + font-weight: 600; + } + QPushButton:hover { + background-color: #bbdefb; + } + )"); + btn->setCursor(Qt::PointingHandCursor); + connect(btn, &QPushButton::clicked, this, [this, preset]() + { m_coarseValueSpin->setValue(preset.toDouble()); }); + presetBtnLayout->addWidget(btn); + } + presetLayout->addLayout(presetBtnLayout); + layout->addLayout(presetLayout); + + return group; +} + +QWidget *SignalTrimWidget::createFineAdjustPanel() +{ + QGroupBox *group = new QGroupBox("微调"); + group->setStyleSheet(GROUP_STYLE); + + QVBoxLayout *mainLayout = new QVBoxLayout(group); + mainLayout->setContentsMargins(16, 24, 16, 16); + mainLayout->setSpacing(16); + + // 步进选择 + QHBoxLayout *stepLayout = new QHBoxLayout; + QLabel *stepLabel = new QLabel("微调步进:"); + stepLabel->setStyleSheet("font-weight: 600;"); + stepLayout->addWidget(stepLabel); + + m_stepSizeCombo = new QComboBox; + m_stepSizeCombo->addItems({"0.0001", "0.001", "0.01", "0.1", "1"}); + m_stepSizeCombo->setCurrentIndex(1); // 默认 0.001 + m_stepSizeCombo->setStyleSheet(R"( + QComboBox { + background: white; + border: 1px solid #ddd; + border-radius: 6px; + padding: 8px 12px; + min-width: 100px; + } + )"); + connect(m_stepSizeCombo, QOverload::of(&QComboBox::currentIndexChanged), + this, [this](int) + { updateSliderRange(); }); + stepLayout->addWidget(m_stepSizeCombo); + stepLayout->addStretch(); + mainLayout->addLayout(stepLayout); + + // 微调控制 + QHBoxLayout *trimLayout = new QHBoxLayout; + trimLayout->setSpacing(16); + + // 减少按钮 + m_decrementBtn = new QPushButton("-"); + m_decrementBtn->setStyleSheet(TRIM_BUTTON_STYLE); + m_decrementBtn->setCursor(Qt::PointingHandCursor); + connect(m_decrementBtn, &QPushButton::clicked, this, &SignalTrimWidget::onDecrementClicked); + trimLayout->addWidget(m_decrementBtn); + + // 微调值显示 + m_fineValueSpin = new QDoubleSpinBox; + m_fineValueSpin->setRange(-10, 10); + m_fineValueSpin->setValue(0); + m_fineValueSpin->setDecimals(4); + m_fineValueSpin->setSingleStep(0.001); + m_fineValueSpin->setPrefix("微调: "); + m_fineValueSpin->setStyleSheet(R"( + QDoubleSpinBox { + padding: 12px 16px; + border: 2px solid #ff9800; + border-radius: 8px; + background: #fff3e0; + font-size: 18px; + font-weight: bold; + font-family: monospace; + min-width: 180px; + } + QDoubleSpinBox::up-button, QDoubleSpinBox::down-button { + subcontrol-origin: border; + width: 28px; + border-left: 2px solid #ff9800; + background: #ffe0b2; + } + QDoubleSpinBox::up-button { subcontrol-position: top right; border-top-right-radius: 6px; } + QDoubleSpinBox::down-button { subcontrol-position: bottom right; border-bottom-right-radius: 6px; } + QDoubleSpinBox::up-button:hover, QDoubleSpinBox::down-button:hover { background: #ffcc80; } + QDoubleSpinBox::up-arrow { image: url(:/icons/arrow_up.svg); width: 12px; height: 12px; } + QDoubleSpinBox::down-arrow { image: url(:/icons/arrow_down.svg); width: 12px; height: 12px; } + )"); + connect(m_fineValueSpin, QOverload::of(&QDoubleSpinBox::valueChanged), + this, &SignalTrimWidget::onFineValueChanged); + trimLayout->addWidget(m_fineValueSpin); + + // 增加按钮 + m_incrementBtn = new QPushButton("+"); + m_incrementBtn->setStyleSheet(TRIM_BUTTON_STYLE); + m_incrementBtn->setCursor(Qt::PointingHandCursor); + connect(m_incrementBtn, &QPushButton::clicked, this, &SignalTrimWidget::onIncrementClicked); + trimLayout->addWidget(m_incrementBtn); + + trimLayout->addStretch(); + mainLayout->addLayout(trimLayout); + + // 滑块微调 + QHBoxLayout *sliderLayout = new QHBoxLayout; + QLabel *minLabel = new QLabel("-1.000"); + minLabel->setStyleSheet("color: #666; font-family: monospace;"); + sliderLayout->addWidget(minLabel); + + m_fineSlider = new QSlider(Qt::Horizontal); + m_fineSlider->setRange(-1000, 1000); + m_fineSlider->setValue(0); + m_fineSlider->setStyleSheet(R"( + QSlider::groove:horizontal { + border: 1px solid #bbb; + background: #e0e0e0; + height: 8px; + border-radius: 4px; + } + QSlider::handle:horizontal { + background: #ff9800; + border: 2px solid #f57c00; + width: 24px; + margin: -8px 0; + border-radius: 12px; + } + QSlider::handle:horizontal:hover { + background: #ffa726; + } + QSlider::sub-page:horizontal { + background: #ffcc80; + border-radius: 4px; + } + )"); + connect(m_fineSlider, &QSlider::valueChanged, this, &SignalTrimWidget::onSliderChanged); + sliderLayout->addWidget(m_fineSlider, 1); + + QLabel *maxLabel = new QLabel("+1.000"); + maxLabel->setStyleSheet("color: #666; font-family: monospace;"); + sliderLayout->addWidget(maxLabel); + + mainLayout->addLayout(sliderLayout); + + return group; +} + +QWidget *SignalTrimWidget::createOutputPanel() +{ + QGroupBox *group = new QGroupBox("最终输出"); + group->setStyleSheet(GROUP_STYLE); + + QHBoxLayout *layout = new QHBoxLayout(group); + layout->setContentsMargins(16, 24, 16, 16); + layout->setSpacing(24); + + // 总值显示 + m_totalValueLabel = new QLabel("0.0000 V"); + m_totalValueLabel->setStyleSheet(R"( + QLabel { + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, + stop:0 #1a1a2e, stop:1 #16213e); + color: #00ff88; + border: 2px solid #00aa55; + border-radius: 12px; + padding: 20px 40px; + font-size: 36px; + font-weight: bold; + font-family: 'Courier New', monospace; + min-width: 300px; + } + )"); + m_totalValueLabel->setAlignment(Qt::AlignCenter); + layout->addWidget(m_totalValueLabel); + + layout->addStretch(); + + // 控制按钮 + QVBoxLayout *btnLayout = new QVBoxLayout; + btnLayout->setSpacing(12); + + m_applyBtn = new QPushButton("应用输出"); + m_applyBtn->setStyleSheet(BUTTON_STYLE); + m_applyBtn->setCursor(Qt::PointingHandCursor); + connect(m_applyBtn, &QPushButton::clicked, this, &SignalTrimWidget::onApplyOutput); + btnLayout->addWidget(m_applyBtn); + + m_resetBtn = new QPushButton("重置归零"); + m_resetBtn->setStyleSheet(R"( + QPushButton { + background-color: #757575; + color: white; + border: none; + border-radius: 8px; + padding: 12px 32px; + font-size: 14px; + font-weight: 600; + } + QPushButton:hover { + background-color: #616161; + } + )"); + m_resetBtn->setCursor(Qt::PointingHandCursor); + connect(m_resetBtn, &QPushButton::clicked, this, &SignalTrimWidget::onResetValue); + btnLayout->addWidget(m_resetBtn); + + layout->addLayout(btnLayout); + + return group; +} + +void SignalTrimWidget::onSignalTypeChanged(int index) +{ + // 根据信号类型更新单位选项 + m_unitCombo->clear(); + switch (index) + { + case 0: // DC V + m_unitCombo->addItems({"V", "mV", "µV"}); + m_coarseUnitLabel->setText("V"); + m_minValue = -100; + m_maxValue = 100; + m_decimals = 4; + break; + case 1: // DC mA + m_unitCombo->addItems({"mA", "µA"}); + m_coarseUnitLabel->setText("mA"); + m_minValue = -30; + m_maxValue = 30; + m_decimals = 4; + break; + case 2: // AC V + m_unitCombo->addItems({"V", "mV"}); + m_coarseUnitLabel->setText("V"); + m_minValue = 0; + m_maxValue = 300; + m_decimals = 3; + break; + case 3: // Ω + m_unitCombo->addItems({"Ω", "kΩ", "MΩ"}); + m_coarseUnitLabel->setText("Ω"); + m_minValue = 0; + m_maxValue = 10000; + m_decimals = 2; + break; + case 4: // 热电偶 + case 5: // 热电阻 + m_unitCombo->addItems({"°C"}); + m_coarseUnitLabel->setText("°C"); + m_minValue = -200; + m_maxValue = 1800; + m_decimals = 2; + break; + case 6: // Hz + m_unitCombo->addItems({"Hz", "kHz", "MHz"}); + m_coarseUnitLabel->setText("Hz"); + m_minValue = 0; + m_maxValue = 100000; + m_decimals = 2; + break; + } + + m_coarseValueSpin->setRange(m_minValue, m_maxValue); + m_coarseValueSpin->setDecimals(m_decimals); + updateTotalValue(); +} + +void SignalTrimWidget::onCoarseValueChanged(double value) +{ + Q_UNUSED(value) + updateTotalValue(); +} + +void SignalTrimWidget::onFineValueChanged(double value) +{ + // 更新滑块位置 + m_fineSlider->blockSignals(true); + double step = m_stepSizeCombo->currentText().toDouble(); + m_fineSlider->setValue(static_cast(value / step)); + m_fineSlider->blockSignals(false); + + updateTotalValue(); +} + +void SignalTrimWidget::onSliderChanged(int value) +{ + double step = m_stepSizeCombo->currentText().toDouble(); + m_fineValueSpin->setValue(value * step); +} + +void SignalTrimWidget::onIncrementClicked() +{ + double step = m_stepSizeCombo->currentText().toDouble(); + m_fineValueSpin->setValue(m_fineValueSpin->value() + step); +} + +void SignalTrimWidget::onDecrementClicked() +{ + double step = m_stepSizeCombo->currentText().toDouble(); + m_fineValueSpin->setValue(m_fineValueSpin->value() - step); +} + +void SignalTrimWidget::onApplyOutput() +{ + // 应用输出值到设备 + m_currentValue = m_coarseValueSpin->value() + m_fineValueSpin->value(); + // TODO: 发送到硬件 +} + +void SignalTrimWidget::onResetValue() +{ + m_coarseValueSpin->setValue(0); + m_fineValueSpin->setValue(0); + m_fineSlider->setValue(0); +} + +void SignalTrimWidget::updateTotalValue() +{ + double total = m_coarseValueSpin->value() + m_fineValueSpin->value(); + QString unit = m_coarseUnitLabel->text(); + m_totalValueLabel->setText(QString("%1 %2").arg(total, 0, 'f', m_decimals).arg(unit)); +} + +void SignalTrimWidget::updateSliderRange() +{ + double step = m_stepSizeCombo->currentText().toDouble(); + int range = static_cast(1.0 / step); + m_fineSlider->setRange(-range, range); + m_fineValueSpin->setSingleStep(step); +} diff --git a/widgets/signaltrimwidget.h b/widgets/signaltrimwidget.h new file mode 100644 index 0000000..7820b8a --- /dev/null +++ b/widgets/signaltrimwidget.h @@ -0,0 +1,81 @@ +#ifndef SIGNALTRIMWIDGET_H +#define SIGNALTRIMWIDGET_H + +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief 信号微调页面 + * + * 功能说明: + * - 输出值任意位数微调 + * - 精确调节信号输出 + */ +class SignalTrimWidget : public QWidget +{ + Q_OBJECT + +public: + explicit SignalTrimWidget(QWidget *parent = nullptr); + ~SignalTrimWidget() = default; + +signals: + void backRequested(); + +private slots: + void onSignalTypeChanged(int index); + void onCoarseValueChanged(double value); + void onFineValueChanged(double value); + void onSliderChanged(int value); + void onIncrementClicked(); + void onDecrementClicked(); + void onApplyOutput(); + void onResetValue(); + +private: + void setupUI(); + QWidget *createTitleBar(); + QWidget *createSignalSelectPanel(); + QWidget *createCoarseAdjustPanel(); + QWidget *createFineAdjustPanel(); + QWidget *createOutputPanel(); + void updateTotalValue(); + void updateSliderRange(); + + // 标题栏 + QPushButton *m_backBtn; + QLabel *m_titleLabel; + + // 信号选择 + QComboBox *m_signalTypeCombo; + QComboBox *m_unitCombo; + + // 粗调 + QDoubleSpinBox *m_coarseValueSpin; + QLabel *m_coarseUnitLabel; + + // 微调 + QDoubleSpinBox *m_fineValueSpin; + QSlider *m_fineSlider; + QPushButton *m_incrementBtn; + QPushButton *m_decrementBtn; + QComboBox *m_stepSizeCombo; + + // 输出显示 + QLabel *m_totalValueLabel; + QPushButton *m_applyBtn; + QPushButton *m_resetBtn; + + // 当前值 + double m_currentValue; + double m_minValue; + double m_maxValue; + int m_decimals; +}; + +#endif // SIGNALTRIMWIDGET_H diff --git a/widgets/statusbar.cpp b/widgets/statusbar.cpp new file mode 100644 index 0000000..5ae4ddb --- /dev/null +++ b/widgets/statusbar.cpp @@ -0,0 +1,156 @@ +#include "statusbar.h" +#include +#include +#include +#include +#include + +StatusBar::StatusBar(QWidget *parent) + : QWidget(parent), m_batteryLevel(100), m_networkConnected(true), m_networkType("WiFi") +{ + setFixedHeight(40); + setupUI(); + + // 定时更新时间 + m_timer = new QTimer(this); + connect(m_timer, &QTimer::timeout, this, &StatusBar::updateTime); + m_timer->start(1000); + updateTime(); + + // 定时更新电池和WiFi状态(每10秒) + QTimer *statusTimer = new QTimer(this); + connect(statusTimer, &QTimer::timeout, this, &StatusBar::updateBatteryLevel); + connect(statusTimer, &QTimer::timeout, this, &StatusBar::updateNetworkStatus); + statusTimer->start(10000); + updateBatteryLevel(); + updateNetworkStatus(); +} + +StatusBar::~StatusBar() +{ +} + +void StatusBar::setupUI() +{ + // 透明背景 - Android风格 + setAttribute(Qt::WA_TranslucentBackground); + setStyleSheet("background: transparent;"); + + QHBoxLayout *layout = new QHBoxLayout(this); + layout->setContentsMargins(24, 8, 24, 8); + layout->setSpacing(20); + + // 标题 - 使用白色带阴影效果 + m_titleLabel = new QLabel("智能校验仪", this); + m_titleLabel->setStyleSheet(R"( + font-size: 16px; + font-weight: 600; + color: white; + background: transparent; + )"); + layout->addWidget(m_titleLabel); + + layout->addStretch(); + + // 网络状态 + m_networkLabel = new QLabel("\u25CF", this); // 实心圆点 + m_networkLabel->setStyleSheet("font-size: 16px; color: rgba(255,255,255,0.9); background: transparent;"); + layout->addWidget(m_networkLabel); + + // 电池状态 + m_batteryLabel = new QLabel("100%", this); + m_batteryLabel->setStyleSheet("font-size: 13px; color: rgba(255,255,255,0.9); background: transparent;"); + layout->addWidget(m_batteryLabel); + + // 时间 + m_timeLabel = new QLabel(this); + m_timeLabel->setStyleSheet("font-size: 13px; min-width: 50px; color: rgba(255,255,255,0.9); background: transparent;"); + layout->addWidget(m_timeLabel); +} + +void StatusBar::setTitle(const QString &title) +{ + m_titleLabel->setText(title); +} + +void StatusBar::setBatteryLevel(int level) +{ + m_batteryLevel = qBound(0, level, 100); + QString icon = m_batteryLevel > 20 ? "🔋" : "🪫"; + m_batteryLabel->setText(QString("%1 %2%").arg(icon).arg(m_batteryLevel)); +} + +void StatusBar::setNetworkStatus(bool connected, const QString &type) +{ + m_networkConnected = connected; + m_networkType = type; + + QString icon; + if (!connected) + { + icon = "📵"; + } + else if (type == "4G") + { + icon = "📶"; + } + else + { + icon = "📶"; + } + m_networkLabel->setText(icon); +} + +void StatusBar::updateTime() +{ + QString timeStr = QDateTime::currentDateTime().toString("HH:mm"); + m_timeLabel->setText(timeStr); +} + +int StatusBar::getBatteryPercentage() +{ + QProcess process; + process.start("pmset", QStringList() << "-g" << "batt"); + process.waitForFinished(1000); + QString output = process.readAllStandardOutput(); + + // 解析输出,格式如:Now drawing from 'Battery Power' -InternalBattery-0 (id=123456) 85%; discharging; 3:45 remaining + QRegularExpression re("(\\d+)%"); + QRegularExpressionMatch match = re.match(output); + if (match.hasMatch()) + { + return match.captured(1).toInt(); + } + return 100; // 默认值 +} + +bool StatusBar::isWiFiConnected() +{ + QProcess process; + process.start("/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport", QStringList() << "-I"); + process.waitForFinished(1000); + QString output = process.readAllStandardOutput(); + + // 如果输出包含SSID,说明WiFi已连接 + return output.contains("SSID:"); +} + +void StatusBar::updateBatteryLevel() +{ + int level = getBatteryPercentage(); + setBatteryLevel(level); +} + +void StatusBar::updateNetworkStatus() +{ + bool connected = isWiFiConnected(); + + if (connected) + { + m_networkLabel->setText("\u25CF"); // 实心圆点表示已连接 + } + else + { + m_networkLabel->setText("\u25CB"); // 空心圆点表示未连接 + } +} diff --git a/widgets/statusbar.h b/widgets/statusbar.h new file mode 100644 index 0000000..4eec2f2 --- /dev/null +++ b/widgets/statusbar.h @@ -0,0 +1,41 @@ +#ifndef STATUSBAR_H +#define STATUSBAR_H + +#include +#include +#include + +class StatusBar : public QWidget +{ + Q_OBJECT + +public: + explicit StatusBar(QWidget *parent = nullptr); + ~StatusBar(); + + void setTitle(const QString &title); + void setBatteryLevel(int level); + void setNetworkStatus(bool connected, const QString &type = "WiFi"); + +private slots: + void updateTime(); + void updateBatteryLevel(); + void updateNetworkStatus(); + +private: + void setupUI(); + int getBatteryPercentage(); + bool isWiFiConnected(); + + QLabel *m_titleLabel; + QLabel *m_timeLabel; + QLabel *m_batteryLabel; + QLabel *m_networkLabel; + QTimer *m_timer; + + int m_batteryLevel; + bool m_networkConnected; + QString m_networkType; +}; + +#endif // STATUSBAR_H diff --git a/widgets/tableoverlaywidget.cpp b/widgets/tableoverlaywidget.cpp new file mode 100644 index 0000000..c21c40d --- /dev/null +++ b/widgets/tableoverlaywidget.cpp @@ -0,0 +1,361 @@ +#include "tableoverlaywidget.h" +#include +#include +#include +#include + +TableOverlayWidget::TableOverlayWidget(QWidget *parent) + : QWidget(parent), m_isVisible(false) +{ + setupUI(); + hide(); // 初始隐藏 +} + +void TableOverlayWidget::setupUI() +{ + // 设置浮层属性 + setAttribute(Qt::WA_StyledBackground); + setStyleSheet("TableOverlayWidget { background: transparent; }"); + + // 主布局 + auto *mainLayout = new QHBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(0); + + // 左侧标签栏滚动区域 + m_tabScrollArea = new QScrollArea(this); + m_tabScrollArea->setFixedWidth(130); + m_tabScrollArea->setWidgetResizable(true); + m_tabScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_tabScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + m_tabScrollArea->setStyleSheet( + "QScrollArea { " + " background: #34495e; " + " border-right: 1px solid #2c3e50; " + " border: none; " + "}" + "QScrollBar:vertical { " + " background: #2c3e50; " + " width: 10px; " + " margin: 0px; " + "}" + "QScrollBar::handle:vertical { " + " background: #546e7a; " + " min-height: 20px; " + " border-radius: 5px; " + "}" + "QScrollBar::handle:vertical:hover { " + " background: #607d8b; " + "}" + "QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { " + " height: 0px; " + "}" + "QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { " + " background: none; " + "}"); + + // 标签栏容器 + m_tabBar = new QWidget(); + m_tabBar->setStyleSheet( + "QWidget { " + " background: #34495e; " + "}"); + + m_tabLayout = new QVBoxLayout(m_tabBar); + m_tabLayout->setContentsMargins(8, 12, 8, 12); + m_tabLayout->setSpacing(6); + m_tabLayout->setAlignment(Qt::AlignTop); + + m_tabScrollArea->setWidget(m_tabBar); + mainLayout->addWidget(m_tabScrollArea); + + // 内容容器(右侧) + m_container = new QWidget(this); + m_container->setStyleSheet( + "QWidget { " + " background: #f5f5f5; " + " border: none; " + " border-top-right-radius: 10px; " + " border-bottom-right-radius: 10px; " + "}"); + + auto *containerLayout = new QVBoxLayout(m_container); + containerLayout->setContentsMargins(0, 0, 0, 0); + containerLayout->setSpacing(0); + + // 标题栏 + auto *headerWidget = new QWidget(this); + headerWidget->setStyleSheet( + "QWidget { " + " background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #3498db, stop:1 #2980b9); " + " border-top-left-radius: 0px; " + " border-top-right-radius: 10px; " + "}"); + auto *headerLayout = new QHBoxLayout(headerWidget); + headerLayout->setContentsMargins(20, 10, 10, 10); + headerLayout->setSpacing(8); + + m_titleLabel = new QLabel("表格", this); + m_titleLabel->setStyleSheet( + "QLabel { " + " color: white; " + " font-size: 14pt; " + " font-weight: bold; " + " background: transparent; " + "}"); + headerLayout->addWidget(m_titleLabel); + headerLayout->addStretch(); + + // 关闭按钮 - 正方形圆角,图标居中 + m_closeBtn = new QPushButton("×", this); + m_closeBtn->setFixedSize(32, 32); + m_closeBtn->setCursor(Qt::PointingHandCursor); + m_closeBtn->setStyleSheet( + "QPushButton { " + " background: rgba(255,255,255,0.2); " + " color: white; " + " border: none; " + " border-radius: 6px; " + " font-size: 20px; " + " font-weight: normal; " + " text-align: center; " + " padding: 0px; " + " margin: 0px; " + "}" + "QPushButton:hover { background: rgba(255,255,255,0.35); }" + "QPushButton:pressed { background: rgba(255,255,255,0.5); }"); + connect(m_closeBtn, &QPushButton::clicked, this, &TableOverlayWidget::onCloseButtonClicked); + headerLayout->addWidget(m_closeBtn); + + containerLayout->addWidget(headerWidget); + + // 内容堆栈 + m_contentStack = new QStackedWidget(this); + m_contentStack->setStyleSheet( + "QStackedWidget { " + " background: white; " + " border: none; " + "}"); + containerLayout->addWidget(m_contentStack, 1); + + mainLayout->addWidget(m_container, 1); + + // 设置默认大小(占父容器的70%宽度,90%高度) + setMinimumWidth(600); + setMaximumWidth(1200); +} + +void TableOverlayWidget::addTable(const QString &tableId, const QString &tableName, QWidget *tableWidget) +{ + if (m_tables.contains(tableId)) + { + qDebug() << "Table already exists:" << tableId; + return; + } + + TableTab tab; + tab.id = tableId; + tab.name = tableName; + tab.widget = tableWidget; + + // 创建标签按钮 + tab.tabButton = new QPushButton(tableName, m_tabBar); + tab.tabButton->setFixedHeight(48); + tab.tabButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + tab.tabButton->setProperty("tableId", tableId); + tab.tabButton->setCursor(Qt::PointingHandCursor); + tab.tabButton->setStyleSheet( + "QPushButton {" + " background: #455a64;" + " color: #ecf0f1;" + " border: none;" + " border-radius: 6px;" + " padding: 6px 6px;" + " font-size: 9.5pt;" + " font-weight: 500;" + " text-align: center;" + " border-left: 3px solid transparent;" + "}" + "QPushButton:hover {" + " background: #546e7a;" + " color: white;" + "}" + "QPushButton:checked {" + " background: #2980b9;" + " color: white;" + " font-weight: bold;" + " border-left: 3px solid #1976d2;" + "}" + "QPushButton:pressed {" + " background: #21618c;" + "}"); + tab.tabButton->setCheckable(true); + connect(tab.tabButton, &QPushButton::clicked, this, &TableOverlayWidget::onTabButtonClicked); + + // 添加到布局 + m_tabLayout->addWidget(tab.tabButton); + + // 添加到内容堆栈 + m_contentStack->addWidget(tableWidget); + + m_tables[tableId] = tab; + + // 如果是第一个表格,自动显示 + if (m_tables.size() == 1) + { + showTable(tableId); + } +} + +void TableOverlayWidget::showTable(const QString &tableId) +{ + if (!m_tables.contains(tableId)) + { + qDebug() << "Table not found:" << tableId; + return; + } + + // 取消之前的选中状态 + if (!m_currentTableId.isEmpty() && m_tables.contains(m_currentTableId)) + { + m_tables[m_currentTableId].tabButton->setChecked(false); + } + + // 切换到新表格 + m_currentTableId = tableId; + const TableTab &tab = m_tables[tableId]; + + tab.tabButton->setChecked(true); + m_contentStack->setCurrentWidget(tab.widget); + m_titleLabel->setText(tab.name); + + emit tableChanged(tableId); +} + +void TableOverlayWidget::removeTable(const QString &tableId) +{ + if (!m_tables.contains(tableId)) + return; + + TableTab tab = m_tables.take(tableId); + + // 移除按钮 + m_tabLayout->removeWidget(tab.tabButton); + tab.tabButton->deleteLater(); + + // 移除内容 + m_contentStack->removeWidget(tab.widget); + + // 如果删除的是当前表格,切换到第一个 + if (m_currentTableId == tableId && !m_tables.isEmpty()) + { + showTable(m_tables.firstKey()); + } + else if (m_tables.isEmpty()) + { + m_currentTableId.clear(); + hideOverlay(); + } +} + +void TableOverlayWidget::clearTables() +{ + // 清除所有表格 + for (const QString &tableId : m_tables.keys()) + { + removeTable(tableId); + } + m_currentTableId.clear(); +} + +void TableOverlayWidget::showOverlay() +{ + if (m_isVisible || m_tables.isEmpty()) + return; + + // 如果当前没有选中的表格,显示第一个 + if (m_currentTableId.isEmpty() && !m_tables.isEmpty()) + { + showTable(m_tables.firstKey()); + } + + m_isVisible = true; + m_container->show(); + m_tabBar->show(); + show(); + animateShow(); +} + +void TableOverlayWidget::hideOverlay() +{ + if (!m_isVisible) + return; + + m_isVisible = false; + animateHide(); +} + +void TableOverlayWidget::toggleOverlay() +{ + if (m_isVisible) + hideOverlay(); + else + showOverlay(); +} + +void TableOverlayWidget::onTabButtonClicked() +{ + QPushButton *btn = qobject_cast(sender()); + if (!btn) + return; + + QString tableId = btn->property("tableId").toString(); + if (!tableId.isEmpty()) + { + showTable(tableId); + } +} + +void TableOverlayWidget::onCloseButtonClicked() +{ + hideOverlay(); + emit closed(); +} + +void TableOverlayWidget::updateTabButtons() +{ + for (const TableTab &tab : m_tables) + { + tab.tabButton->setChecked(tab.id == m_currentTableId); + } +} + +void TableOverlayWidget::animateShow() +{ + // 淡入动画 + QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect(this); + setGraphicsEffect(effect); + + QPropertyAnimation *animation = new QPropertyAnimation(effect, "opacity"); + animation->setDuration(200); + animation->setStartValue(0.0); + animation->setEndValue(1.0); + animation->setEasingCurve(QEasingCurve::OutCubic); + animation->start(QPropertyAnimation::DeleteWhenStopped); +} + +void TableOverlayWidget::animateHide() +{ + // 淡出动画 + QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect(this); + setGraphicsEffect(effect); + + QPropertyAnimation *animation = new QPropertyAnimation(effect, "opacity"); + animation->setDuration(150); + animation->setStartValue(1.0); + animation->setEndValue(0.0); + animation->setEasingCurve(QEasingCurve::InCubic); + + connect(animation, &QPropertyAnimation::finished, this, &TableOverlayWidget::hide); + animation->start(QPropertyAnimation::DeleteWhenStopped); +} diff --git a/widgets/tableoverlaywidget.h b/widgets/tableoverlaywidget.h new file mode 100644 index 0000000..1d330d0 --- /dev/null +++ b/widgets/tableoverlaywidget.h @@ -0,0 +1,111 @@ +#ifndef TABLEOVERLAYWIDGET_H +#define TABLEOVERLAYWIDGET_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../procedure/proceduredata.h" + +/** + * @class TableOverlayWidget + * @brief 表格浮层组件 + * + * 在页面上显示浮层,支持多个表格的标签页切换 + * 特点: + * - 浮层形式,不阻塞主界面 + * - 左侧垂直标签页导航 + * - 可关闭 + */ +class TableOverlayWidget : public QWidget +{ + Q_OBJECT + +public: + explicit TableOverlayWidget(QWidget *parent = nullptr); + + /** + * @brief 添加表格到浮层 + * @param tableId 表格ID + * @param tableName 表格名称 + * @param tableWidget 表格组件 + */ + void addTable(const QString &tableId, const QString &tableName, QWidget *tableWidget); + + /** + * @brief 显示特定表格 + * @param tableId 表格ID + */ + void showTable(const QString &tableId); + + /** + * @brief 移除表格 + * @param tableId 表格ID + */ + void removeTable(const QString &tableId); + + /** + * @brief 清除所有表格 + */ + void clearTables(); + + /** + * @brief 显示浮层 + */ + void showOverlay(); + + /** + * @brief 隐藏浮层 + */ + void hideOverlay(); + + /** + * @brief 切换浮层显示状态 + */ + void toggleOverlay(); + + /** + * @brief 是否显示 + */ + bool isOverlayVisible() const { return m_isVisible; } + +signals: + void closed(); + void tableChanged(const QString &tableId); + +private slots: + void onTabButtonClicked(); + void onCloseButtonClicked(); + +private: + void setupUI(); + void updateTabButtons(); + void animateShow(); + void animateHide(); + + struct TableTab + { + QString id; + QString name; + QWidget *widget; + QPushButton *tabButton; + }; + + QWidget *m_container; // 浮层容器 + QScrollArea *m_tabScrollArea; // 标签栏滚动区域 + QWidget *m_tabBar; // 标签栏容器 + QVBoxLayout *m_tabLayout; // 标签栏布局 + QStackedWidget *m_contentStack; // 内容堆栈 + QPushButton *m_closeBtn; // 关闭按钮 + QLabel *m_titleLabel; // 标题标签 + + QMap m_tables; // 表格映射 + QString m_currentTableId; // 当前显示的表格ID + bool m_isVisible; // 是否可见 +}; + +#endif // TABLEOVERLAYWIDGET_H diff --git a/widgets/tablewidgetfactory.cpp b/widgets/tablewidgetfactory.cpp new file mode 100644 index 0000000..6a4608a --- /dev/null +++ b/widgets/tablewidgetfactory.cpp @@ -0,0 +1,1407 @@ +#include "tablewidgetfactory.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +TableWidgetFactory::TableWidgetFactory(QObject *parent) + : QObject(parent) +{ +} + +QWidget *TableWidgetFactory::createTableWidget(const TableDefinition &tableDef) +{ + QWidget *widget = nullptr; + + if (tableDef.tableType == "grid") + { + widget = createGridTable(tableDef); + } + else if (tableDef.tableType == "form") + { + widget = createFormTable(tableDef); + } + else if (tableDef.tableType == "series") + { + widget = createSeriesTable(tableDef); + } + else + { + qWarning() << "Unknown table type:" << tableDef.tableType; + widget = new QWidget(); + } + + if (widget) + { + tableData[widget].definition = tableDef; + qDebug() << "Created table widget for:" << tableDef.id << "name:" << tableDef.name; + } + + return widget; +} + +QWidget *TableWidgetFactory::createGridTable(const TableDefinition &tableDef) +{ + QWidget *container = new QWidget(); + QVBoxLayout *layout = new QVBoxLayout(container); + layout->setContentsMargins(12, 12, 12, 12); + + // 標題 - Material Design 風格 + if (!tableDef.name.isEmpty()) + { + QLabel *titleLabel = new QLabel(tableDef.name, container); + titleLabel->setStyleSheet("font-weight: 500; font-size: 14px; color: #212121; margin-bottom: 4px;"); + layout->addWidget(titleLabel); + } + + // 描述 + if (!tableDef.description.isEmpty()) + { + QLabel *descLabel = new QLabel(tableDef.description, container); + descLabel->setWordWrap(true); + descLabel->setStyleSheet("color: #757575; font-size: 13px; margin-bottom: 8px;"); + layout->addWidget(descLabel); + } + + // 創建表格 + QTableWidget *table = new QTableWidget(container); + table->setRowCount(tableDef.rowHeaders.size()); + table->setColumnCount(tableDef.columnHeaders.size()); + + // Material Design 樣式 + table->setStyleSheet( + "QTableWidget {" + " border: 1px solid #E0E0E0;" + " border-radius: 8px;" + " gridline-color: #EEEEEE;" + " background-color: white;" + "}" + "QTableWidget::item {" + " padding: 8px 12px;" + " color: #212121;" + "}" + "QTableWidget::item:selected {" + " background-color: #E3F2FD;" + " color: #1976D2;" + "}" + "QHeaderView::section {" + " background-color: #2196F3;" + " color: white;" + " padding: 10px 12px;" + " border: none;" + " font-weight: 500;" + " font-size: 13px;" + "}"); + + // 列標題 + QStringList columnLabels; + for (const FieldDefinition &colDef : tableDef.columnHeaders) + { + QString label = colDef.name; + if (!colDef.unit.isEmpty()) + { + label += QString("\n(%1)").arg(colDef.unit); + } + if (colDef.isRequired) + { + label += " *"; + } + columnLabels << label; + } + table->setHorizontalHeaderLabels(columnLabels); + + // 行標題 + QStringList rowLabels; + for (const auto &rowHeader : tableDef.rowHeaders) + { + rowLabels << rowHeader.second; + } + table->setVerticalHeaderLabels(rowLabels); + + // 調整列寬 + table->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); + table->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); + + // 初始化單元格 + for (int row = 0; row < table->rowCount(); ++row) + { + for (int col = 0; col < table->columnCount(); ++col) + { + QTableWidgetItem *item = new QTableWidgetItem(""); + table->setItem(row, col, item); + + // 設置只讀(計算字段) + if (col < tableDef.columnHeaders.size()) + { + const FieldDefinition &colDef = tableDef.columnHeaders[col]; + if (colDef.isReadOnly || colDef.type == "calculated") + { + item->setFlags(item->flags() & ~Qt::ItemIsEditable); + item->setBackground(QColor("#FAFAFA")); + } + } + } + } + + // 連接信號 + connect(table, &QTableWidget::itemChanged, this, + [this, container, tableDef](QTableWidgetItem *item) + { + if (!item) + return; + + int row = item->row(); + int col = item->column(); + + if (row < tableDef.rowHeaders.size() && col < tableDef.columnHeaders.size()) + { + QString rowId = tableDef.rowHeaders[row].first; + QString colId = tableDef.columnHeaders[col].id; + emit cellValueChanged(container, rowId, colId, item->text()); + } + }); + + layout->addWidget(table); + + // 存儲 grid widget + tableData[container].gridWidget = table; + + // 應用靜態單元格 + applyStaticCells(container, tableDef); + + return container; +} + +QWidget *TableWidgetFactory::createSeriesTable(const TableDefinition &tableDef) +{ + QWidget *container = new QWidget(); + QVBoxLayout *layout = new QVBoxLayout(container); + layout->setContentsMargins(12, 12, 12, 12); + + // 標題 - Material Design 風格 + if (!tableDef.name.isEmpty()) + { + QLabel *titleLabel = new QLabel(tableDef.name, container); + titleLabel->setStyleSheet("font-weight: 500; font-size: 14px; color: #212121; margin-bottom: 4px;"); + layout->addWidget(titleLabel); + } + + // 描述 + if (!tableDef.description.isEmpty()) + { + QLabel *descLabel = new QLabel(tableDef.description, container); + descLabel->setWordWrap(true); + descLabel->setStyleSheet("color: #757575; font-size: 13px; margin-bottom: 8px;"); + layout->addWidget(descLabel); + } + + // 創建時序數據表格 + QTableWidget *table = new QTableWidget(container); + + // 設置列:時間戳 + 字段 + int columnCount = 1 + tableDef.fields.size(); + table->setColumnCount(columnCount); + + // 列標題 + QStringList columnLabels; + columnLabels << "時間戳"; + + for (const FieldDefinition &fieldDef : tableDef.fields) + { + QString label = fieldDef.name; + if (!fieldDef.unit.isEmpty()) + { + label += QString(" (%1)").arg(fieldDef.unit); + } + columnLabels << label; + } + table->setHorizontalHeaderLabels(columnLabels); + + // Material Design 樣式 - 使用綠色強調時序數據 + table->setStyleSheet( + "QTableWidget {" + " border: 1px solid #E0E0E0;" + " border-radius: 8px;" + " gridline-color: #EEEEEE;" + " background-color: white;" + "}" + "QTableWidget::item {" + " padding: 8px 12px;" + " color: #212121;" + "}" + "QTableWidget::item:selected {" + " background-color: #E8F5E9;" + " color: #388E3C;" + "}" + "QHeaderView::section {" + " background-color: #4CAF50;" + " color: white;" + " padding: 10px 12px;" + " border: none;" + " font-weight: 500;" + " font-size: 13px;" + "}"); + + // 配置表格 + table->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); + table->verticalHeader()->setVisible(false); + table->setAlternatingRowColors(true); + table->setSelectionBehavior(QAbstractItemView::SelectRows); + table->setSortingEnabled(true); + + layout->addWidget(table); + + // 存儲 series widget + tableData[container].seriesWidget = table; + + // 應用靜態單元格 + applyStaticCells(container, tableDef); + + return container; +} + +QWidget *TableWidgetFactory::createFormTable(const TableDefinition &tableDef) +{ + QWidget *container = new QWidget(); + QVBoxLayout *mainLayout = new QVBoxLayout(container); + mainLayout->setContentsMargins(12, 12, 12, 12); + + // 標題 - Material Design 風格 + if (!tableDef.name.isEmpty()) + { + QLabel *titleLabel = new QLabel(tableDef.name, container); + titleLabel->setStyleSheet("font-weight: 500; font-size: 14px; color: #212121; margin-bottom: 4px;"); + mainLayout->addWidget(titleLabel); + } + + // 描述 + if (!tableDef.description.isEmpty()) + { + QLabel *descLabel = new QLabel(tableDef.description, container); + descLabel->setWordWrap(true); + descLabel->setStyleSheet("color: #757575; font-size: 13px; margin-bottom: 12px;"); + mainLayout->addWidget(descLabel); + } + + // 創建網格佈局(12列系統) + int totalColumns = 12; + if (tableDef.layoutConfig.contains("totalColumns")) + { + totalColumns = tableDef.layoutConfig["totalColumns"].toInt(); + } + Q_UNUSED(totalColumns); + + QGridLayout *gridLayout = new QGridLayout(); + gridLayout->setSpacing(10); + + // 按順序排序字段 + QList sortedFields = tableDef.fields; + std::sort(sortedFields.begin(), sortedFields.end(), + [](const FieldDefinition &a, const FieldDefinition &b) + { + int orderA = a.layoutConfig.value("order", 999).toInt(); + int orderB = b.layoutConfig.value("order", 999).toInt(); + return orderA < orderB; + }); + + // 創建字段組件 + int currentRow = 0; + QMap fields; + + for (const FieldDefinition &fieldDef : sortedFields) + { + // 獲取佈局配置 + int labelSpan = fieldDef.layoutConfig.value("labelSpan", 4).toInt(); + int valueSpan = fieldDef.layoutConfig.value("valueSpan", 8).toInt(); + int rowSpan = fieldDef.layoutConfig.value("rowSpan", 1).toInt(); + + // 創建標籤 + QString labelText = fieldDef.name; + if (!fieldDef.unit.isEmpty()) + { + labelText += QString(" (%1)").arg(fieldDef.unit); + } + if (fieldDef.isRequired) + { + labelText += " *"; + } + + QLabel *label = new QLabel(labelText, container); + label->setTextFormat(Qt::RichText); + label->setStyleSheet("font-weight: 500; color: #455A64; font-size: 14px;"); + label->setAlignment(Qt::AlignRight | Qt::AlignVCenter); + + // 創建字段組件 + QWidget *fieldWidget = createFieldWidget(fieldDef, container); + + if (fieldWidget) + { + // 添加到網格 + gridLayout->addWidget(label, currentRow, 0, rowSpan, labelSpan); + gridLayout->addWidget(fieldWidget, currentRow, labelSpan, rowSpan, valueSpan); + + fields[fieldDef.id] = fieldWidget; + connectFieldSignals(fieldWidget, fieldDef.id, container); + } + + currentRow += rowSpan; + } + + mainLayout->addLayout(gridLayout); + mainLayout->addStretch(); + + // 存儲字段組件 + tableData[container].fieldWidgets = fields; + + // 應用靜態單元格 + applyStaticCells(container, tableDef); + + return container; +} + +QWidget *TableWidgetFactory::createFieldWidget(const FieldDefinition &fieldDef, QWidget *parent) +{ + QWidget *widget = nullptr; + + if (fieldDef.type == "numeric") + { + widget = createNumericField(fieldDef, parent); + } + else if (fieldDef.type == "text") + { + widget = createTextField(fieldDef, parent); + } + else if (fieldDef.type == "selection") + { + widget = createSelectionField(fieldDef, parent); + } + else if (fieldDef.type == "boolean") + { + widget = createBooleanField(fieldDef, parent); + } + else if (fieldDef.type == "datetime") + { + widget = createDateTimeField(fieldDef, parent); + } + else if (fieldDef.type == "calculated") + { + widget = createCalculatedField(fieldDef, parent); + } + + if (widget) + { + widget->setEnabled(!fieldDef.isReadOnly); + } + + return widget; +} + +QWidget *TableWidgetFactory::createNumericField(const FieldDefinition &fieldDef, QWidget *parent) +{ + QDoubleSpinBox *spinBox = new QDoubleSpinBox(parent); + + // 範圍 + double minVal = -999999.0; + double maxVal = 999999.0; + + if (fieldDef.validationRules.contains("min")) + { + minVal = fieldDef.validationRules["min"].toDouble(); + } + if (fieldDef.validationRules.contains("max")) + { + maxVal = fieldDef.validationRules["max"].toDouble(); + } + + spinBox->setRange(minVal, maxVal); + + // 精度 + int decimals = 3; + if (fieldDef.validationRules.contains("precision")) + { + decimals = fieldDef.validationRules["precision"].toInt(); + } + spinBox->setDecimals(decimals); + + // 默認值 + if (fieldDef.defaultValue.isValid()) + { + spinBox->setValue(fieldDef.defaultValue.toDouble()); + } + + // Material Design 樣式 + spinBox->setStyleSheet( + "QDoubleSpinBox {" + " padding: 10px 12px;" + " border: 1px solid #E0E0E0;" + " border-radius: 8px;" + " background-color: white;" + " font-size: 14px;" + " color: #212121;" + "}" + "QDoubleSpinBox:focus {" + " border: 2px solid #2196F3;" + "}" + "QDoubleSpinBox:disabled {" + " background-color: #FAFAFA;" + " color: #9E9E9E;" + "}"); + + return spinBox; +} + +QWidget *TableWidgetFactory::createTextField(const FieldDefinition &fieldDef, QWidget *parent) +{ + QLineEdit *lineEdit = new QLineEdit(parent); + + // 默認值 + if (fieldDef.defaultValue.isValid()) + { + lineEdit->setText(fieldDef.defaultValue.toString()); + } + + // 最大長度 + if (fieldDef.validationRules.contains("maxLength")) + { + lineEdit->setMaxLength(fieldDef.validationRules["maxLength"].toInt()); + } + + // 正則驗證 + if (fieldDef.validationRules.contains("pattern")) + { + QString pattern = fieldDef.validationRules["pattern"].toString(); + QRegularExpression regex(pattern); + QRegularExpressionValidator *validator = new QRegularExpressionValidator(regex, lineEdit); + lineEdit->setValidator(validator); + } + + // Material Design 樣式 + lineEdit->setStyleSheet( + "QLineEdit {" + " padding: 10px 12px;" + " border: 1px solid #E0E0E0;" + " border-radius: 8px;" + " background-color: white;" + " font-size: 14px;" + " color: #212121;" + "}" + "QLineEdit:focus {" + " border: 2px solid #2196F3;" + "}" + "QLineEdit:disabled {" + " background-color: #FAFAFA;" + " color: #9E9E9E;" + "}"); + + return lineEdit; +} + +QWidget *TableWidgetFactory::createSelectionField(const FieldDefinition &fieldDef, QWidget *parent) +{ + QComboBox *comboBox = new QComboBox(parent); + + // 添加選項 + for (const QString &option : fieldDef.options) + { + comboBox->addItem(option); + } + + // 默認值 + if (fieldDef.defaultValue.isValid()) + { + int index = comboBox->findText(fieldDef.defaultValue.toString()); + if (index >= 0) + { + comboBox->setCurrentIndex(index); + } + } + + // Material Design 樣式 + comboBox->setStyleSheet( + "QComboBox {" + " padding: 10px 12px;" + " border: 1px solid #E0E0E0;" + " border-radius: 8px;" + " background-color: white;" + " font-size: 14px;" + " color: #212121;" + "}" + "QComboBox:hover {" + " border-color: #BDBDBD;" + "}" + "QComboBox:focus {" + " border: 2px solid #2196F3;" + "}"); + + return comboBox; +} + +QWidget *TableWidgetFactory::createBooleanField(const FieldDefinition &fieldDef, QWidget *parent) +{ + QCheckBox *checkBox = new QCheckBox(parent); + + // 默認值 + if (fieldDef.defaultValue.isValid()) + { + checkBox->setChecked(fieldDef.defaultValue.toBool()); + } + + // Material Design 樣式 + checkBox->setStyleSheet( + "QCheckBox { font-size: 14px; color: #455A64; spacing: 8px; }" + "QCheckBox::indicator { width: 22px; height: 22px; border: 2px solid #BDBDBD; border-radius: 4px; background: white; }" + "QCheckBox::indicator:checked { background: #2196F3; border-color: #2196F3; }" + "QCheckBox::indicator:hover { border-color: #2196F3; }"); + + return checkBox; +} + +QWidget *TableWidgetFactory::createDateTimeField(const FieldDefinition &fieldDef, QWidget *parent) +{ + QDateTimeEdit *dateTimeEdit = new QDateTimeEdit(parent); + dateTimeEdit->setCalendarPopup(true); + dateTimeEdit->setDisplayFormat("yyyy-MM-dd HH:mm:ss"); + + // 默認值 + if (fieldDef.defaultValue.isValid()) + { + dateTimeEdit->setDateTime(fieldDef.defaultValue.toDateTime()); + } + else + { + dateTimeEdit->setDateTime(QDateTime::currentDateTime()); + } + + // Material Design 樣式 + dateTimeEdit->setStyleSheet( + "QDateTimeEdit {" + " padding: 10px 12px;" + " border: 1px solid #E0E0E0;" + " border-radius: 8px;" + " background-color: white;" + " font-size: 14px;" + " color: #212121;" + "}" + "QDateTimeEdit:focus {" + " border: 2px solid #2196F3;" + "}"); + + return dateTimeEdit; +} + +QWidget *TableWidgetFactory::createCalculatedField(const FieldDefinition &fieldDef, QWidget *parent) +{ + Q_UNUSED(fieldDef); + QLineEdit *lineEdit = new QLineEdit(parent); + lineEdit->setReadOnly(true); + lineEdit->setPlaceholderText("計算字段"); + + // Material Design 只讀樣式 + lineEdit->setStyleSheet( + "QLineEdit {" + " padding: 10px 12px;" + " border: 1px solid #E0E0E0;" + " border-radius: 8px;" + " background-color: #FAFAFA;" + " color: #9E9E9E;" + " font-size: 14px;" + "}"); + + return lineEdit; +} + +void TableWidgetFactory::connectFieldSignals(QWidget *fieldWidget, const QString &fieldId, + QWidget *tableWidget) +{ + if (QDoubleSpinBox *spinBox = qobject_cast(fieldWidget)) + { + connect(spinBox, QOverload::of(&QDoubleSpinBox::valueChanged), this, + [this, fieldId, tableWidget](double value) + { + emit fieldValueChanged(tableWidget, fieldId, value); + }); + } + else if (QLineEdit *lineEdit = qobject_cast(fieldWidget)) + { + connect(lineEdit, &QLineEdit::textChanged, this, + [this, fieldId, tableWidget](const QString &text) + { + emit fieldValueChanged(tableWidget, fieldId, text); + }); + } + else if (QComboBox *comboBox = qobject_cast(fieldWidget)) + { + connect(comboBox, &QComboBox::currentTextChanged, this, + [this, fieldId, tableWidget](const QString &text) + { + emit fieldValueChanged(tableWidget, fieldId, text); + }); + } + else if (QCheckBox *checkBox = qobject_cast(fieldWidget)) + { + connect(checkBox, &QCheckBox::toggled, this, + [this, fieldId, tableWidget](bool checked) + { + emit fieldValueChanged(tableWidget, fieldId, checked); + }); + } + else if (QDateTimeEdit *dateTimeEdit = qobject_cast(fieldWidget)) + { + connect(dateTimeEdit, &QDateTimeEdit::dateTimeChanged, this, + [this, fieldId, tableWidget](const QDateTime &dateTime) + { + emit fieldValueChanged(tableWidget, fieldId, dateTime); + }); + } +} + +void TableWidgetFactory::applyStaticCells(QWidget *tableWidget, const TableDefinition &tableDef) +{ + if (!tableData.contains(tableWidget) || tableDef.staticCells.isEmpty()) + { + return; + } + + const TableWidgetData &data = tableData[tableWidget]; + + for (const StaticCell &staticCell : tableDef.staticCells) + { + if (tableDef.tableType == "form" || tableDef.tableType == "series") + { + // 處理 form/series 類型表格 + if (!staticCell.field.isEmpty() && data.fieldWidgets.contains(staticCell.field)) + { + QWidget *fieldWidget = data.fieldWidgets[staticCell.field]; + + // 設置靜態內容並設為只讀 - Material Design 風格 + if (QLineEdit *lineEdit = qobject_cast(fieldWidget)) + { + lineEdit->setText(staticCell.content); + lineEdit->setReadOnly(true); + lineEdit->setStyleSheet( + "QLineEdit {" + " padding: 10px 12px;" + " border: 1px solid #E0E0E0;" + " border-radius: 8px;" + " background-color: #FAFAFA;" + " color: #616161;" + " font-size: 14px;" + "}"); + } + else if (QDoubleSpinBox *spinBox = qobject_cast(fieldWidget)) + { + spinBox->setValue(staticCell.content.toDouble()); + spinBox->setReadOnly(true); + spinBox->setStyleSheet( + "QDoubleSpinBox {" + " padding: 10px 12px;" + " border: 1px solid #E0E0E0;" + " border-radius: 8px;" + " background-color: #FAFAFA;" + " color: #616161;" + " font-size: 14px;" + "}"); + } + else if (QComboBox *comboBox = qobject_cast(fieldWidget)) + { + int index = comboBox->findText(staticCell.content); + if (index >= 0) + { + comboBox->setCurrentIndex(index); + } + comboBox->setEnabled(false); + } + else if (QCheckBox *checkBox = qobject_cast(fieldWidget)) + { + checkBox->setChecked(staticCell.content.toLower() == "true"); + checkBox->setEnabled(false); + } + } + } + else if (tableDef.tableType == "grid") + { + // 處理 grid 類型表格 + if (data.gridWidget) + { + QTableWidget *table = data.gridWidget; + int rowIndex = staticCell.row; + int colIndex = staticCell.column; + + if (rowIndex >= 0 && colIndex >= 0 && + rowIndex < table->rowCount() && colIndex < table->columnCount()) + { + QTableWidgetItem *item = table->item(rowIndex, colIndex); + if (!item) + { + item = new QTableWidgetItem(); + table->setItem(rowIndex, colIndex, item); + } + + // 設置靜態內容並設為只讀 - Material Design 風格 + item->setText(staticCell.content); + item->setFlags(item->flags() & ~Qt::ItemIsEditable); + item->setBackground(QColor("#FAFAFA")); + item->setForeground(QColor("#616161")); + } + } + } + } +} + +// ===================================================== +// Form 表格數據訪問 +// ===================================================== + +bool TableWidgetFactory::setFieldValue(QWidget *tableWidget, const QString &fieldId, + const QVariant &value) +{ + if (!tableData.contains(tableWidget)) + { + return false; + } + + const TableWidgetData &data = tableData[tableWidget]; + if (!data.fieldWidgets.contains(fieldId)) + { + return false; + } + + QWidget *fieldWidget = data.fieldWidgets[fieldId]; + + if (QDoubleSpinBox *spinBox = qobject_cast(fieldWidget)) + { + spinBox->setValue(value.toDouble()); + } + else if (QLineEdit *lineEdit = qobject_cast(fieldWidget)) + { + lineEdit->setText(value.toString()); + } + else if (QComboBox *comboBox = qobject_cast(fieldWidget)) + { + int index = comboBox->findText(value.toString()); + if (index >= 0) + { + comboBox->setCurrentIndex(index); + } + } + else if (QCheckBox *checkBox = qobject_cast(fieldWidget)) + { + checkBox->setChecked(value.toBool()); + } + else if (QDateTimeEdit *dateTimeEdit = qobject_cast(fieldWidget)) + { + dateTimeEdit->setDateTime(value.toDateTime()); + } + else + { + return false; + } + + return true; +} + +QVariant TableWidgetFactory::getFieldValue(QWidget *tableWidget, const QString &fieldId) const +{ + if (!tableData.contains(tableWidget)) + { + return QVariant(); + } + + const TableWidgetData &data = tableData[tableWidget]; + if (!data.fieldWidgets.contains(fieldId)) + { + return QVariant(); + } + + QWidget *fieldWidget = data.fieldWidgets[fieldId]; + + if (QDoubleSpinBox *spinBox = qobject_cast(fieldWidget)) + { + return spinBox->value(); + } + else if (QLineEdit *lineEdit = qobject_cast(fieldWidget)) + { + return lineEdit->text(); + } + else if (QComboBox *comboBox = qobject_cast(fieldWidget)) + { + return comboBox->currentText(); + } + else if (QCheckBox *checkBox = qobject_cast(fieldWidget)) + { + return checkBox->isChecked(); + } + else if (QDateTimeEdit *dateTimeEdit = qobject_cast(fieldWidget)) + { + return dateTimeEdit->dateTime(); + } + + return QVariant(); +} + +// ===================================================== +// Grid 表格數據訪問 +// ===================================================== + +bool TableWidgetFactory::setCellValue(QWidget *tableWidget, const QString &rowId, + const QString &columnId, const QVariant &value) +{ + if (!tableData.contains(tableWidget)) + { + qWarning() << "setCellValue: tableWidget not found in tableData"; + return false; + } + + const TableWidgetData &data = tableData[tableWidget]; + if (!data.gridWidget) + { + qWarning() << "setCellValue: gridWidget is null"; + return false; + } + + QTableWidget *table = data.gridWidget; + const TableDefinition &tableDef = data.definition; + + // 查找行索引 + int rowIndex = -1; + for (int i = 0; i < tableDef.rowHeaders.size(); ++i) + { + if (tableDef.rowHeaders[i].first == rowId) + { + rowIndex = i; + break; + } + } + + // 查找列索引 + int colIndex = -1; + for (int i = 0; i < tableDef.columnHeaders.size(); ++i) + { + if (tableDef.columnHeaders[i].id == columnId) + { + colIndex = i; + break; + } + } + + if (rowIndex >= 0 && colIndex >= 0 && + rowIndex < table->rowCount() && colIndex < table->columnCount()) + { + QTableWidgetItem *item = table->item(rowIndex, colIndex); + if (!item) + { + item = new QTableWidgetItem(); + table->setItem(rowIndex, colIndex, item); + } + item->setText(value.toString()); + return true; + } + + qWarning() << "setCellValue: Invalid row/column index or out of bounds"; + return false; +} + +QVariant TableWidgetFactory::getCellValue(QWidget *tableWidget, const QString &rowId, + const QString &columnId) const +{ + if (!tableData.contains(tableWidget)) + { + return QVariant(); + } + + const TableWidgetData &data = tableData[tableWidget]; + if (!data.gridWidget) + { + return QVariant(); + } + + QTableWidget *table = data.gridWidget; + const TableDefinition &tableDef = data.definition; + + // 查找索引 + int rowIndex = -1; + for (int i = 0; i < tableDef.rowHeaders.size(); ++i) + { + if (tableDef.rowHeaders[i].first == rowId) + { + rowIndex = i; + break; + } + } + + int colIndex = -1; + for (int i = 0; i < tableDef.columnHeaders.size(); ++i) + { + if (tableDef.columnHeaders[i].id == columnId) + { + colIndex = i; + break; + } + } + + if (rowIndex >= 0 && colIndex >= 0) + { + QTableWidgetItem *item = table->item(rowIndex, colIndex); + if (item) + { + return item->text(); + } + } + + return QVariant(); +} + +// ===================================================== +// 通用方法 +// ===================================================== + +TableDefinition TableWidgetFactory::getTableDefinition(QWidget *tableWidget) const +{ + if (tableData.contains(tableWidget)) + { + return tableData[tableWidget].definition; + } + return TableDefinition(); +} + +bool TableWidgetFactory::loadDataFromEngine(QWidget *tableWidget, const QString &tableRef, + const QVariantMap &engineData) +{ + if (!tableData.contains(tableWidget)) + { + qWarning() << "loadDataFromEngine: tableWidget not found in tableData"; + return false; + } + + const TableWidgetData &data = tableData[tableWidget]; + const TableDefinition &tableDef = data.definition; + + qDebug() << "Loading engine data into table widget - tableRef:" << tableRef + << "entries:" << engineData.size() << "tableType:" << tableDef.tableType; + + if (engineData.isEmpty()) + { + qDebug() << " No data to load"; + return true; + } + + // 批量加載時阻斷信號 + bool containerSignalsBlocked = tableWidget->signalsBlocked(); + tableWidget->blockSignals(true); + + bool gridSignalsBlocked = false; + if (data.gridWidget) + { + gridSignalsBlocked = data.gridWidget->signalsBlocked(); + data.gridWidget->blockSignals(true); + } + + // 阻斷字段組件信號 + QList fieldSignalStates; + for (auto it = data.fieldWidgets.begin(); it != data.fieldWidgets.end(); ++it) + { + fieldSignalStates.append(it.value()->signalsBlocked()); + it.value()->blockSignals(true); + } + + bool success = true; + + if (tableDef.tableType == "form") + { + // 加載表單字段 + for (auto it = engineData.begin(); it != engineData.end(); ++it) + { + qDebug() << " Loading form field:" << it.key() << "=" << it.value(); + if (!setFieldValue(tableWidget, it.key(), it.value())) + { + qWarning() << " Failed to set form field:" << it.key(); + success = false; + } + } + } + else if (tableDef.tableType == "grid") + { + // 加載網格單元格 + for (auto it = engineData.begin(); it != engineData.end(); ++it) + { + QStringList parts = it.key().split("_"); + if (parts.size() == 2) + { + QString rowId = parts[0]; + QString colId = parts[1]; + qDebug() << " Loading grid cell:" << rowId << "," << colId << "=" << it.value(); + if (!setCellValue(tableWidget, rowId, colId, it.value())) + { + qWarning() << " Failed to set grid cell:" << rowId << "," << colId; + success = false; + } + } + else + { + qWarning() << " Invalid cell key format:" << it.key(); + } + } + } + else if (tableDef.tableType == "series") + { + // 時序數據格式:timestamp_fieldId -> value + QMap timePoints; + + for (auto it = engineData.begin(); it != engineData.end(); ++it) + { + QStringList parts = it.key().split("_", Qt::KeepEmptyParts); + if (parts.size() >= 2) + { + QString timestamp = parts[0]; + QString fieldId = parts.mid(1).join("_"); + + if (!timePoints.contains(timestamp)) + { + timePoints[timestamp] = QVariantMap(); + } + timePoints[timestamp][fieldId] = it.value(); + } + } + + // 按時間順序添加數據點 + QStringList timestamps = timePoints.keys(); + timestamps.sort(); + + for (const QString ×tampStr : timestamps) + { + QDateTime timestamp = QDateTime::fromString(timestampStr, Qt::ISODate); + if (!timestamp.isValid()) + { + timestamp = QDateTime::fromString(timestampStr, "yyyy-MM-dd hh:mm:ss.zzz"); + } + if (!timestamp.isValid()) + { + qWarning() << " Invalid timestamp format:" << timestampStr; + continue; + } + + if (!addSeriesDataPoint(tableWidget, timestamp, timePoints[timestampStr])) + { + qWarning() << " Failed to add series data point for timestamp:" << timestampStr; + success = false; + } + } + } + + // 恢復信號狀態 + tableWidget->blockSignals(containerSignalsBlocked); + + if (data.gridWidget) + { + data.gridWidget->blockSignals(gridSignalsBlocked); + } + + int i = 0; + for (auto it = data.fieldWidgets.begin(); it != data.fieldWidgets.end(); ++it) + { + if (i < fieldSignalStates.size()) + { + it.value()->blockSignals(fieldSignalStates[i]); + } + i++; + } + + qDebug() << "Finished loading engine data - success:" << success; + return success; +} + +// ===================================================== +// Series 表格特有方法 +// ===================================================== + +bool TableWidgetFactory::addSeriesDataPoint(QWidget *tableWidget, const QDateTime ×tamp, + const QVariantMap &fieldValues) +{ + if (!tableData.contains(tableWidget)) + { + qWarning() << "addSeriesDataPoint: tableWidget not found in tableData"; + return false; + } + + const TableWidgetData &data = tableData[tableWidget]; + if (!data.seriesWidget || data.definition.tableType != "series") + { + qWarning() << "addSeriesDataPoint: not a series table or seriesWidget is null"; + return false; + } + + QTableWidget *table = data.seriesWidget; + const TableDefinition &tableDef = data.definition; + + // 添加新行 + int newRow = table->rowCount(); + table->insertRow(newRow); + + // 設置時間戳(第一列)- Material Design 風格 + QTableWidgetItem *timestampItem = + new QTableWidgetItem(timestamp.toString("yyyy-MM-dd hh:mm:ss.zzz")); + timestampItem->setFlags(timestampItem->flags() & ~Qt::ItemIsEditable); + timestampItem->setBackground(QColor("#FAFAFA")); + table->setItem(newRow, 0, timestampItem); + + // 設置字段值 + for (int i = 0; i < tableDef.fields.size(); ++i) + { + const FieldDefinition &fieldDef = tableDef.fields[i]; + QVariant value = fieldValues.value(fieldDef.id); + + QTableWidgetItem *item = new QTableWidgetItem(); + if (value.isValid()) + { + if (fieldDef.type == "numeric") + { + item->setText(QString::number(value.toDouble(), 'f', 3)); + item->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); + } + else + { + item->setText(value.toString()); + } + } + + // 設為只讀 + item->setFlags(item->flags() & ~Qt::ItemIsEditable); + table->setItem(newRow, i + 1, item); + } + + // 自動滾動到底部 + table->scrollToBottom(); + + return true; +} + +void TableWidgetFactory::clearSeriesData(QWidget *tableWidget) +{ + if (!tableData.contains(tableWidget)) + { + return; + } + + const TableWidgetData &data = tableData[tableWidget]; + if (!data.seriesWidget || data.definition.tableType != "series") + { + return; + } + + data.seriesWidget->setRowCount(0); +} + +// ===================================================== +// 驗證與控制 +// ===================================================== + +bool TableWidgetFactory::validateField(QWidget *tableWidget, const QString &fieldId) const +{ + if (!tableData.contains(tableWidget)) + { + return false; + } + + const TableWidgetData &data = tableData[tableWidget]; + const TableDefinition &tableDef = data.definition; + + // 查找字段定義 + FieldDefinition fieldDef; + bool found = false; + + if (tableDef.tableType == "form") + { + for (const FieldDefinition &def : tableDef.fields) + { + if (def.id == fieldId) + { + fieldDef = def; + found = true; + break; + } + } + } + else if (tableDef.tableType == "grid") + { + for (const FieldDefinition &def : tableDef.columnHeaders) + { + if (def.id == fieldId) + { + fieldDef = def; + found = true; + break; + } + } + } + + if (!found) + { + return false; + } + + // 獲取當前值 + QVariant value; + if (tableDef.tableType == "form") + { + value = getFieldValue(tableWidget, fieldId); + } + else + { + return true; // Grid 驗證需要更多上下文 + } + + // 基本驗證規則 + if (fieldDef.isRequired && (!value.isValid() || value.toString().isEmpty())) + { + return false; + } + + // 類型特定驗證 + if (fieldDef.type == "numeric") + { + bool ok; + double numValue = value.toDouble(&ok); + if (!ok) + { + return false; + } + + // 範圍驗證 + if (fieldDef.validationRules.contains("min")) + { + double minVal = fieldDef.validationRules["min"].toDouble(); + if (numValue < minVal) + { + return false; + } + } + if (fieldDef.validationRules.contains("max")) + { + double maxVal = fieldDef.validationRules["max"].toDouble(); + if (numValue > maxVal) + { + return false; + } + } + } + else if (fieldDef.type == "text") + { + QString textValue = value.toString(); + + // 最大長度驗證 + if (fieldDef.validationRules.contains("maxLength")) + { + int maxLen = fieldDef.validationRules["maxLength"].toInt(); + if (textValue.length() > maxLen) + { + return false; + } + } + + // 正則驗證 + if (fieldDef.validationRules.contains("pattern")) + { + QString pattern = fieldDef.validationRules["pattern"].toString(); + QRegularExpression regex(pattern); + if (!regex.match(textValue).hasMatch()) + { + return false; + } + } + } + + return true; +} + +void TableWidgetFactory::enableManualInput(QWidget *tableWidget, bool enabled) +{ + if (!tableData.contains(tableWidget)) + { + return; + } + + const TableWidgetData &data = tableData[tableWidget]; + const TableDefinition &tableDef = data.definition; + + if (tableDef.tableType == "form") + { + // 啟用/禁用所有字段組件 + for (auto it = data.fieldWidgets.begin(); it != data.fieldWidgets.end(); ++it) + { + QWidget *fieldWidget = it.value(); + if (fieldWidget) + { + QString fieldId = it.key(); + bool isReadOnly = false; + + for (const FieldDefinition &fieldDef : tableDef.fields) + { + if (fieldDef.id == fieldId) + { + isReadOnly = fieldDef.isReadOnly || fieldDef.type == "calculated"; + break; + } + } + + // 只對非只讀字段操作 + if (!isReadOnly) + { + fieldWidget->setEnabled(enabled); + } + } + } + } + else if (tableDef.tableType == "grid" && data.gridWidget) + { + QTableWidget *table = data.gridWidget; + + // 啟用/禁用非只讀單元格 + for (int row = 0; row < table->rowCount(); ++row) + { + for (int col = 0; col < table->columnCount(); ++col) + { + QTableWidgetItem *item = table->item(row, col); + if (item) + { + bool isReadOnly = false; + if (col < tableDef.columnHeaders.size()) + { + const FieldDefinition &colDef = tableDef.columnHeaders[col]; + isReadOnly = colDef.isReadOnly || colDef.type == "calculated"; + } + + if (enabled && !isReadOnly) + { + item->setFlags(item->flags() | Qt::ItemIsEditable); + } + else + { + item->setFlags(item->flags() & ~Qt::ItemIsEditable); + } + } + } + } + } + else if (tableDef.tableType == "series" && data.seriesWidget) + { + // Series 表格通常是只讀的 + data.seriesWidget->setEnabled(enabled); + } +} diff --git a/widgets/tablewidgetfactory.h b/widgets/tablewidgetfactory.h new file mode 100644 index 0000000..9fc47be --- /dev/null +++ b/widgets/tablewidgetfactory.h @@ -0,0 +1,200 @@ +#ifndef TABLEWIDGETFACTORY_H +#define TABLEWIDGETFACTORY_H + +#include "../procedure/proceduredata.h" + +#include +#include +#include +#include +#include +#include +#include + +/** + * @class TableWidgetFactory + * @brief 表格組件工廠類 + * + * 根據 YAML 配置動態創建不同類型的表格組件: + * - Grid 表格:數據矩陣(行列形式) + * - Form 表格:表單字段佈局 + * - Series 表格:時序數據記錄 + */ +class TableWidgetFactory : public QObject +{ + Q_OBJECT + +public: + explicit TableWidgetFactory(QObject *parent = nullptr); + + /** + * @brief 創建表格組件 + * @param tableDef 表格定義 + * @return 創建的表格組件 + */ + QWidget *createTableWidget(const TableDefinition &tableDef); + + // ===================================================== + // Form 表格數據訪問 + // ===================================================== + + /** + * @brief 設置字段值 + * @param tableWidget 表格組件 + * @param fieldId 字段ID + * @param value 值 + * @return 是否成功 + */ + bool setFieldValue(QWidget *tableWidget, const QString &fieldId, const QVariant &value); + + /** + * @brief 獲取字段值 + * @param tableWidget 表格組件 + * @param fieldId 字段ID + * @return 字段值 + */ + QVariant getFieldValue(QWidget *tableWidget, const QString &fieldId) const; + + // ===================================================== + // Grid 表格數據訪問 + // ===================================================== + + /** + * @brief 設置單元格值 + * @param tableWidget 表格組件 + * @param rowId 行ID + * @param columnId 列ID + * @param value 值 + * @return 是否成功 + */ + bool setCellValue(QWidget *tableWidget, const QString &rowId, + const QString &columnId, const QVariant &value); + + /** + * @brief 獲取單元格值 + * @param tableWidget 表格組件 + * @param rowId 行ID + * @param columnId 列ID + * @return 單元格值 + */ + QVariant getCellValue(QWidget *tableWidget, const QString &rowId, + const QString &columnId) const; + + // ===================================================== + // 通用方法 + // ===================================================== + + /** + * @brief 獲取表格定義 + * @param tableWidget 表格組件 + * @return 表格定義 + */ + TableDefinition getTableDefinition(QWidget *tableWidget) const; + + /** + * @brief 從引擎加載數據到表格 + * @param tableWidget 表格組件 + * @param tableRef 表格引用ID + * @param engineData 引擎數據 + * @return 是否成功 + */ + bool loadDataFromEngine(QWidget *tableWidget, const QString &tableRef, + const QVariantMap &engineData); + + // ===================================================== + // Series 表格特有方法 + // ===================================================== + + /** + * @brief 添加時序數據點 + * @param tableWidget 表格組件 + * @param timestamp 時間戳 + * @param fieldValues 字段值映射 + * @return 是否成功 + */ + bool addSeriesDataPoint(QWidget *tableWidget, const QDateTime ×tamp, + const QVariantMap &fieldValues); + + /** + * @brief 清除時序數據 + * @param tableWidget 表格組件 + */ + void clearSeriesData(QWidget *tableWidget); + + // ===================================================== + // 驗證與控制 + // ===================================================== + + /** + * @brief 驗證字段 + * @param tableWidget 表格組件 + * @param fieldId 字段ID + * @return 是否有效 + */ + bool validateField(QWidget *tableWidget, const QString &fieldId) const; + + /** + * @brief 啟用/禁用手動輸入 + * @param tableWidget 表格組件 + * @param enabled 是否啟用 + */ + void enableManualInput(QWidget *tableWidget, bool enabled); + +signals: + /** + * @brief 字段值變更信號(Form 表格) + */ + void fieldValueChanged(QWidget *tableWidget, const QString &fieldId, + const QVariant &value); + + /** + * @brief 單元格值變更信號(Grid 表格) + */ + void cellValueChanged(QWidget *tableWidget, const QString &rowId, + const QString &columnId, const QVariant &value); + +private: + // ===================================================== + // 表格創建方法 + // ===================================================== + + QWidget *createGridTable(const TableDefinition &tableDef); + QWidget *createFormTable(const TableDefinition &tableDef); + QWidget *createSeriesTable(const TableDefinition &tableDef); + + // ===================================================== + // 字段組件創建方法 + // ===================================================== + + QWidget *createFieldWidget(const FieldDefinition &fieldDef, QWidget *parent); + QWidget *createNumericField(const FieldDefinition &fieldDef, QWidget *parent); + QWidget *createTextField(const FieldDefinition &fieldDef, QWidget *parent); + QWidget *createSelectionField(const FieldDefinition &fieldDef, QWidget *parent); + QWidget *createBooleanField(const FieldDefinition &fieldDef, QWidget *parent); + QWidget *createDateTimeField(const FieldDefinition &fieldDef, QWidget *parent); + QWidget *createCalculatedField(const FieldDefinition &fieldDef, QWidget *parent); + + // ===================================================== + // 輔助方法 + // ===================================================== + + void connectFieldSignals(QWidget *fieldWidget, const QString &fieldId, + QWidget *tableWidget); + void applyStaticCells(QWidget *tableWidget, const TableDefinition &tableDef); + + // ===================================================== + // 內部數據結構 + // ===================================================== + + struct TableWidgetData + { + TableDefinition definition; + QMap fieldWidgets; // fieldId -> widget (for form/series) + QTableWidget *gridWidget = nullptr; // for grid tables + QTableWidget *seriesWidget = nullptr; // for series tables + }; + + QMap tableData; +}; + +#endif // TABLEWIDGETFACTORY_H diff --git a/widgets/waveformwidget.cpp b/widgets/waveformwidget.cpp new file mode 100644 index 0000000..3543da8 --- /dev/null +++ b/widgets/waveformwidget.cpp @@ -0,0 +1,617 @@ +#include "waveformwidget.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *GROUP_STYLE = R"( + QGroupBox { + background-color: white; + border: 1px solid #e0e0e0; + border-radius: 12px; + margin-top: 8px; + padding: 16px; + font-size: 14px; + font-weight: 600; + } + QGroupBox::title { + subcontrol-origin: margin; + left: 16px; + padding: 0 8px; + color: #333; + } +)"; + +static const char *BUTTON_STYLE = R"( + QPushButton { + background-color: #2196F3; + color: white; + border: none; + border-radius: 8px; + padding: 12px 24px; + font-size: 14px; + font-weight: 600; + } + QPushButton:hover { + background-color: #1976D2; + } + QPushButton:pressed { + background-color: #1565C0; + } + QPushButton:disabled { + background-color: #BDBDBD; + } +)"; + +static const char *STOP_BUTTON_STYLE = R"( + QPushButton { + background-color: #f44336; + color: white; + border: none; + border-radius: 8px; + padding: 12px 24px; + font-size: 14px; + font-weight: 600; + } + QPushButton:hover { + background-color: #d32f2f; + } + QPushButton:pressed { + background-color: #c62828; + } + QPushButton:disabled { + background-color: #BDBDBD; + } +)"; + +WaveformWidget::WaveformWidget(QWidget *parent) + : QWidget(parent), m_isCapturing(false), m_dataIndex(0) +{ + setupUI(); + + m_captureTimer = new QTimer(this); + connect(m_captureTimer, &QTimer::timeout, this, &WaveformWidget::onTimerTick); +} + +void WaveformWidget::setupUI() +{ + QVBoxLayout *mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(0); + + // 标题栏 + mainLayout->addWidget(createTitleBar()); + + // 内容区域 + QScrollArea *scrollArea = new QScrollArea; + scrollArea->setWidgetResizable(true); + scrollArea->setFrameShape(QFrame::NoFrame); + scrollArea->setStyleSheet("QScrollArea { background-color: #f5f5f5; }"); + + QWidget *contentWidget = new QWidget; + contentWidget->setStyleSheet("background-color: #f5f5f5;"); + QVBoxLayout *contentLayout = new QVBoxLayout(contentWidget); + contentLayout->setContentsMargins(20, 20, 20, 20); + contentLayout->setSpacing(16); + + // 配置面板 + contentLayout->addWidget(createConfigPanel()); + + // 波形显示区域 + contentLayout->addWidget(createWaveformDisplay()); + + // 底部区域:数据表格和统计 + QHBoxLayout *bottomLayout = new QHBoxLayout; + bottomLayout->setSpacing(16); + bottomLayout->addWidget(createDataPanel(), 2); + bottomLayout->addWidget(createStatisticsPanel(), 1); + contentLayout->addLayout(bottomLayout); + + scrollArea->setWidget(contentWidget); + mainLayout->addWidget(scrollArea, 1); +} + +QWidget *WaveformWidget::createTitleBar() +{ + QWidget *titleBar = new QWidget; + titleBar->setFixedHeight(60); + titleBar->setStyleSheet("background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #1976D2, stop:1 #1565C0);"); + + QHBoxLayout *layout = new QHBoxLayout(titleBar); + layout->setContentsMargins(16, 0, 16, 0); + + // 返回按钮 + m_backBtn = new QPushButton("← 返回"); + m_backBtn->setCursor(Qt::PointingHandCursor); + m_backBtn->setStyleSheet(R"( + QPushButton { + background: rgba(255,255,255,0.2); + color: white; + border: none; + border-radius: 6px; + padding: 8px 16px; + font-size: 14px; + } + QPushButton:hover { + background: rgba(255,255,255,0.3); + } + )"); + connect(m_backBtn, &QPushButton::clicked, this, &WaveformWidget::backRequested); + layout->addWidget(m_backBtn); + + // 标题(居中) + m_titleLabel = new QLabel("波形采集"); + m_titleLabel->setStyleSheet("color: white; font-size: 18px; font-weight: bold;"); + m_titleLabel->setAlignment(Qt::AlignCenter); + layout->addWidget(m_titleLabel, 1); + + // 右侧占位,保持标题居中 + QWidget *spacer = new QWidget; + spacer->setFixedWidth(m_backBtn->sizeHint().width()); + layout->addWidget(spacer); + + return titleBar; +} + +QWidget *WaveformWidget::createConfigPanel() +{ + QGroupBox *group = new QGroupBox("采集配置"); + group->setStyleSheet(GROUP_STYLE); + + QVBoxLayout *mainLayout = new QVBoxLayout(group); + mainLayout->setContentsMargins(16, 24, 16, 16); + mainLayout->setSpacing(12); + + // 第一行:配置选项 + QHBoxLayout *configLayout = new QHBoxLayout; + configLayout->setSpacing(16); + + // 信号类型 + QVBoxLayout *typeLayout = new QVBoxLayout; + QLabel *typeLabel = new QLabel("信号类型"); + typeLabel->setStyleSheet("color: #666; font-size: 12px; font-weight: normal;"); + m_signalTypeCombo = new QComboBox; + m_signalTypeCombo->addItems({"直流电压", "直流电流", "交流电压", "交流电流"}); + m_signalTypeCombo->setStyleSheet(R"( + QComboBox { + background: white; + border: 1px solid #ddd; + border-radius: 6px; + padding: 6px 10px; + min-width: 100px; + } + )"); + typeLayout->addWidget(typeLabel); + typeLayout->addWidget(m_signalTypeCombo); + configLayout->addLayout(typeLayout); + + // 采样率 + QVBoxLayout *rateLayout = new QVBoxLayout; + QLabel *rateLabel = new QLabel("采样率"); + rateLabel->setStyleSheet("color: #666; font-size: 12px; font-weight: normal;"); + m_sampleRateCombo = new QComboBox; + m_sampleRateCombo->addItems({"25ms/点", "50ms/点", "100ms/点", "200ms/点", "500ms/点", "1s/点"}); + m_sampleRateCombo->setStyleSheet(R"( + QComboBox { + background: white; + border: 1px solid #ddd; + border-radius: 6px; + padding: 6px 10px; + min-width: 80px; + } + )"); + rateLayout->addWidget(rateLabel); + rateLayout->addWidget(m_sampleRateCombo); + configLayout->addLayout(rateLayout); + + // 采集时长 + QVBoxLayout *durationLayout = new QVBoxLayout; + QLabel *durationLabel = new QLabel("时长(秒)"); + durationLabel->setStyleSheet("color: #666; font-size: 12px; font-weight: normal;"); + m_durationSpin = new QSpinBox; + m_durationSpin->setRange(1, 3600); + m_durationSpin->setValue(60); + m_durationSpin->setStyleSheet(R"( + QSpinBox { + padding: 6px 10px; + border: 1px solid #ddd; + border-radius: 6px; + background: white; + min-width: 60px; + } + QSpinBox::up-button, QSpinBox::down-button { + subcontrol-origin: border; + width: 20px; + border-left: 1px solid #ddd; + background: #f5f5f5; + } + QSpinBox::up-button { subcontrol-position: top right; border-top-right-radius: 5px; } + QSpinBox::down-button { subcontrol-position: bottom right; border-bottom-right-radius: 5px; } + QSpinBox::up-button:hover, QSpinBox::down-button:hover { background: #e0e0e0; } + QSpinBox::up-arrow { image: url(:/icons/arrow_up.svg); width: 10px; height: 10px; } + QSpinBox::down-arrow { image: url(:/icons/arrow_down.svg); width: 10px; height: 10px; } + )"); + ; + durationLayout->addWidget(durationLabel); + durationLayout->addWidget(m_durationSpin); + configLayout->addLayout(durationLayout); + + // 通道选择 + QVBoxLayout *channelLayout = new QVBoxLayout; + QLabel *channelLabel = new QLabel("采集通道"); + channelLabel->setStyleSheet("color: #666; font-size: 12px; font-weight: normal;"); + QHBoxLayout *checkLayout = new QHBoxLayout; + checkLayout->setSpacing(8); + m_ch1Enable = new QCheckBox("CH1"); + m_ch1Enable->setChecked(true); + m_ch2Enable = new QCheckBox("CH2"); + m_ch2Enable->setChecked(true); + checkLayout->addWidget(m_ch1Enable); + checkLayout->addWidget(m_ch2Enable); + channelLayout->addWidget(channelLabel); + channelLayout->addLayout(checkLayout); + configLayout->addLayout(channelLayout); + + configLayout->addStretch(); + mainLayout->addLayout(configLayout); + + // 第二行:控制按钮 + QHBoxLayout *buttonLayout = new QHBoxLayout; + buttonLayout->setSpacing(12); + + m_startBtn = new QPushButton("开始采集"); + m_startBtn->setStyleSheet(R"( + QPushButton { + background-color: #2196F3; + color: white; + border: none; + border-radius: 6px; + padding: 8px 16px; + font-size: 13px; + font-weight: 600; + } + QPushButton:hover { background-color: #1976D2; } + QPushButton:disabled { background-color: #BDBDBD; } + )"); + m_startBtn->setCursor(Qt::PointingHandCursor); + connect(m_startBtn, &QPushButton::clicked, this, &WaveformWidget::onStartCapture); + buttonLayout->addWidget(m_startBtn); + + m_stopBtn = new QPushButton("停止"); + m_stopBtn->setStyleSheet(R"( + QPushButton { + background-color: #f44336; + color: white; + border: none; + border-radius: 6px; + padding: 8px 16px; + font-size: 13px; + font-weight: 600; + } + QPushButton:hover { background-color: #d32f2f; } + QPushButton:disabled { background-color: #BDBDBD; } + )"); + m_stopBtn->setCursor(Qt::PointingHandCursor); + m_stopBtn->setEnabled(false); + connect(m_stopBtn, &QPushButton::clicked, this, &WaveformWidget::onStopCapture); + buttonLayout->addWidget(m_stopBtn); + + m_clearBtn = new QPushButton("清除"); + m_clearBtn->setStyleSheet(R"( + QPushButton { + background-color: #757575; + color: white; + border: none; + border-radius: 6px; + padding: 8px 16px; + font-size: 13px; + font-weight: 600; + } + QPushButton:hover { background-color: #616161; } + )"); + m_clearBtn->setCursor(Qt::PointingHandCursor); + connect(m_clearBtn, &QPushButton::clicked, this, &WaveformWidget::onClearData); + buttonLayout->addWidget(m_clearBtn); + + m_exportBtn = new QPushButton("导出"); + m_exportBtn->setStyleSheet(R"( + QPushButton { + background-color: #4CAF50; + color: white; + border: none; + border-radius: 6px; + padding: 8px 16px; + font-size: 13px; + font-weight: 600; + } + QPushButton:hover { background-color: #43A047; } + )"); + m_exportBtn->setCursor(Qt::PointingHandCursor); + connect(m_exportBtn, &QPushButton::clicked, this, &WaveformWidget::onExportData); + buttonLayout->addWidget(m_exportBtn); + + buttonLayout->addStretch(); + mainLayout->addLayout(buttonLayout); + + return group; +} + +QWidget *WaveformWidget::createWaveformDisplay() +{ + QGroupBox *group = new QGroupBox("波形显示"); + group->setStyleSheet(GROUP_STYLE); + group->setMinimumHeight(250); + + QVBoxLayout *layout = new QVBoxLayout(group); + layout->setContentsMargins(16, 24, 16, 16); + + // 波形显示区域(简化版,使用渐变背景模拟) + m_waveformArea = new QWidget; + m_waveformArea->setMinimumHeight(180); + m_waveformArea->setStyleSheet(R"( + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, + stop:0 #1a1a2e, stop:1 #16213e); + border-radius: 8px; + )"); + + QHBoxLayout *waveLayout = new QHBoxLayout(m_waveformArea); + waveLayout->setContentsMargins(20, 20, 20, 20); + + // 通道标签 + QVBoxLayout *labelLayout = new QVBoxLayout; + m_ch1Label = new QLabel("CH1: -- V"); + m_ch1Label->setStyleSheet("color: #00ff88; font-size: 16px; font-weight: bold; font-family: monospace;"); + m_ch2Label = new QLabel("CH2: -- V"); + m_ch2Label->setStyleSheet("color: #ff6b6b; font-size: 16px; font-weight: bold; font-family: monospace;"); + labelLayout->addWidget(m_ch1Label); + labelLayout->addWidget(m_ch2Label); + labelLayout->addStretch(); + waveLayout->addLayout(labelLayout); + + waveLayout->addStretch(); + + // 状态提示 + QLabel *hintLabel = new QLabel("点击\"开始采集\"开始记录波形数据"); + hintLabel->setStyleSheet("color: #888; font-size: 14px;"); + hintLabel->setAlignment(Qt::AlignCenter); + waveLayout->addWidget(hintLabel); + + waveLayout->addStretch(); + + layout->addWidget(m_waveformArea); + + return group; +} + +QWidget *WaveformWidget::createDataPanel() +{ + QGroupBox *group = new QGroupBox("采集数据"); + group->setStyleSheet(GROUP_STYLE); + + QVBoxLayout *layout = new QVBoxLayout(group); + layout->setContentsMargins(16, 24, 16, 16); + + m_dataTable = new QTableWidget; + m_dataTable->setColumnCount(4); + m_dataTable->setHorizontalHeaderLabels({"序号", "时间", "通道1", "通道2"}); + m_dataTable->horizontalHeader()->setStretchLastSection(true); + m_dataTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + m_dataTable->setStyleSheet(R"( + QTableWidget { + background-color: white; + border: 1px solid #e0e0e0; + border-radius: 8px; + gridline-color: #f0f0f0; + } + QHeaderView::section { + background-color: #f5f5f5; + padding: 8px; + border: none; + border-bottom: 1px solid #e0e0e0; + font-weight: 600; + } + )"); + m_dataTable->setMinimumHeight(200); + layout->addWidget(m_dataTable); + + return group; +} + +QWidget *WaveformWidget::createStatisticsPanel() +{ + QGroupBox *group = new QGroupBox("统计信息"); + group->setStyleSheet(GROUP_STYLE); + + QVBoxLayout *layout = new QVBoxLayout(group); + layout->setContentsMargins(16, 24, 16, 16); + layout->setSpacing(12); + + // 通道1统计 + QLabel *ch1Title = new QLabel("通道1 (绿色)"); + ch1Title->setStyleSheet("color: #00aa55; font-weight: bold;"); + layout->addWidget(ch1Title); + + QHBoxLayout *ch1MaxLayout = new QHBoxLayout; + ch1MaxLayout->addWidget(new QLabel("最大值:")); + m_ch1MaxLabel = new QLabel("-- V"); + m_ch1MaxLabel->setStyleSheet("font-weight: bold; color: #333;"); + ch1MaxLayout->addWidget(m_ch1MaxLabel); + ch1MaxLayout->addStretch(); + layout->addLayout(ch1MaxLayout); + + QHBoxLayout *ch1MinLayout = new QHBoxLayout; + ch1MinLayout->addWidget(new QLabel("最小值:")); + m_ch1MinLabel = new QLabel("-- V"); + m_ch1MinLabel->setStyleSheet("font-weight: bold; color: #333;"); + ch1MinLayout->addWidget(m_ch1MinLabel); + ch1MinLayout->addStretch(); + layout->addLayout(ch1MinLayout); + + QHBoxLayout *ch1AvgLayout = new QHBoxLayout; + ch1AvgLayout->addWidget(new QLabel("平均值:")); + m_ch1AvgLabel = new QLabel("-- V"); + m_ch1AvgLabel->setStyleSheet("font-weight: bold; color: #333;"); + ch1AvgLayout->addWidget(m_ch1AvgLabel); + ch1AvgLayout->addStretch(); + layout->addLayout(ch1AvgLayout); + + layout->addSpacing(16); + + // 通道2统计 + QLabel *ch2Title = new QLabel("通道2 (红色)"); + ch2Title->setStyleSheet("color: #d32f2f; font-weight: bold;"); + layout->addWidget(ch2Title); + + QHBoxLayout *ch2MaxLayout = new QHBoxLayout; + ch2MaxLayout->addWidget(new QLabel("最大值:")); + m_ch2MaxLabel = new QLabel("-- V"); + m_ch2MaxLabel->setStyleSheet("font-weight: bold; color: #333;"); + ch2MaxLayout->addWidget(m_ch2MaxLabel); + ch2MaxLayout->addStretch(); + layout->addLayout(ch2MaxLayout); + + QHBoxLayout *ch2MinLayout = new QHBoxLayout; + ch2MinLayout->addWidget(new QLabel("最小值:")); + m_ch2MinLabel = new QLabel("-- V"); + m_ch2MinLabel->setStyleSheet("font-weight: bold; color: #333;"); + ch2MinLayout->addWidget(m_ch2MinLabel); + ch2MinLayout->addStretch(); + layout->addLayout(ch2MinLayout); + + QHBoxLayout *ch2AvgLayout = new QHBoxLayout; + ch2AvgLayout->addWidget(new QLabel("平均值:")); + m_ch2AvgLabel = new QLabel("-- V"); + m_ch2AvgLabel->setStyleSheet("font-weight: bold; color: #333;"); + ch2AvgLayout->addWidget(m_ch2AvgLabel); + ch2AvgLayout->addStretch(); + layout->addLayout(ch2AvgLayout); + + layout->addStretch(); + + return group; +} + +void WaveformWidget::onStartCapture() +{ + m_isCapturing = true; + m_startBtn->setEnabled(false); + m_stopBtn->setEnabled(true); + m_dataIndex = 0; + + // 根据采样率设置定时器间隔 + QStringList rates = {"25", "50", "100", "200", "500", "1000"}; + int interval = rates[m_sampleRateCombo->currentIndex()].toInt(); + m_captureTimer->start(interval); +} + +void WaveformWidget::onStopCapture() +{ + m_isCapturing = false; + m_captureTimer->stop(); + m_startBtn->setEnabled(true); + m_stopBtn->setEnabled(false); +} + +void WaveformWidget::onClearData() +{ + m_ch1Data.clear(); + m_ch2Data.clear(); + m_dataTable->setRowCount(0); + m_dataIndex = 0; + + m_ch1Label->setText("CH1: -- V"); + m_ch2Label->setText("CH2: -- V"); + m_ch1MaxLabel->setText("-- V"); + m_ch1MinLabel->setText("-- V"); + m_ch1AvgLabel->setText("-- V"); + m_ch2MaxLabel->setText("-- V"); + m_ch2MinLabel->setText("-- V"); + m_ch2AvgLabel->setText("-- V"); +} + +void WaveformWidget::onExportData() +{ + if (m_ch1Data.isEmpty() && m_ch2Data.isEmpty()) + { + QMessageBox::warning(this, "导出", "没有可导出的数据"); + return; + } + + QString fileName = QFileDialog::getSaveFileName(this, "导出数据", + QString("waveform_%1.csv").arg(QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss")), + "CSV文件 (*.csv)"); + + if (!fileName.isEmpty()) + { + QMessageBox::information(this, "导出", "数据已导出到:\n" + fileName); + } +} + +void WaveformWidget::onTimerTick() +{ + // 模拟采集数据 + static std::random_device rd; + static std::mt19937 gen(rd()); + static std::normal_distribution<> dis(5.0, 0.5); + + double ch1Value = m_ch1Enable->isChecked() ? dis(gen) : 0; + double ch2Value = m_ch2Enable->isChecked() ? dis(gen) + 2.0 : 0; + + addDataPoint(ch1Value, ch2Value); +} + +void WaveformWidget::addDataPoint(double ch1Value, double ch2Value) +{ + m_dataIndex++; + + if (m_ch1Enable->isChecked()) + m_ch1Data.append(ch1Value); + if (m_ch2Enable->isChecked()) + m_ch2Data.append(ch2Value); + + // 更新实时显示 + m_ch1Label->setText(QString("CH1: %1 V").arg(ch1Value, 0, 'f', 4)); + m_ch2Label->setText(QString("CH2: %1 V").arg(ch2Value, 0, 'f', 4)); + + // 添加到表格 + int row = m_dataTable->rowCount(); + m_dataTable->insertRow(row); + m_dataTable->setItem(row, 0, new QTableWidgetItem(QString::number(m_dataIndex))); + m_dataTable->setItem(row, 1, new QTableWidgetItem(QDateTime::currentDateTime().toString("HH:mm:ss.zzz"))); + m_dataTable->setItem(row, 2, new QTableWidgetItem(QString::number(ch1Value, 'f', 4))); + m_dataTable->setItem(row, 3, new QTableWidgetItem(QString::number(ch2Value, 'f', 4))); + m_dataTable->scrollToBottom(); + + // 更新统计 + updateStatistics(); +} + +void WaveformWidget::updateStatistics() +{ + if (!m_ch1Data.isEmpty()) + { + double maxVal = *std::max_element(m_ch1Data.begin(), m_ch1Data.end()); + double minVal = *std::min_element(m_ch1Data.begin(), m_ch1Data.end()); + double avgVal = std::accumulate(m_ch1Data.begin(), m_ch1Data.end(), 0.0) / m_ch1Data.size(); + m_ch1MaxLabel->setText(QString("%1 V").arg(maxVal, 0, 'f', 4)); + m_ch1MinLabel->setText(QString("%1 V").arg(minVal, 0, 'f', 4)); + m_ch1AvgLabel->setText(QString("%1 V").arg(avgVal, 0, 'f', 4)); + } + + if (!m_ch2Data.isEmpty()) + { + double maxVal = *std::max_element(m_ch2Data.begin(), m_ch2Data.end()); + double minVal = *std::min_element(m_ch2Data.begin(), m_ch2Data.end()); + double avgVal = std::accumulate(m_ch2Data.begin(), m_ch2Data.end(), 0.0) / m_ch2Data.size(); + m_ch2MaxLabel->setText(QString("%1 V").arg(maxVal, 0, 'f', 4)); + m_ch2MinLabel->setText(QString("%1 V").arg(minVal, 0, 'f', 4)); + m_ch2AvgLabel->setText(QString("%1 V").arg(avgVal, 0, 'f', 4)); + } +} diff --git a/widgets/waveformwidget.h b/widgets/waveformwidget.h new file mode 100644 index 0000000..aa6e3d8 --- /dev/null +++ b/widgets/waveformwidget.h @@ -0,0 +1,95 @@ +#ifndef WAVEFORMWIDGET_H +#define WAVEFORMWIDGET_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief 波形采集页面 + * + * 功能说明: + * - 信号采集和记录 + * - 生成曲线功能 + * - 采集速率≤25ms/点 + * - 采集通道数≥2 + * - 采集电压/电流信号 + * - 自动计算最大/最小/平均值 + */ +class WaveformWidget : public QWidget +{ + Q_OBJECT + +public: + explicit WaveformWidget(QWidget *parent = nullptr); + ~WaveformWidget() = default; + +signals: + void backRequested(); + +private slots: + void onStartCapture(); + void onStopCapture(); + void onClearData(); + void onExportData(); + void onTimerTick(); + +private: + void setupUI(); + QWidget *createTitleBar(); + QWidget *createConfigPanel(); + QWidget *createWaveformDisplay(); + QWidget *createDataPanel(); + QWidget *createStatisticsPanel(); + void updateStatistics(); + void addDataPoint(double ch1Value, double ch2Value); + + // 标题栏 + QPushButton *m_backBtn; + QLabel *m_titleLabel; + + // 配置面板 + QComboBox *m_signalTypeCombo; + QComboBox *m_sampleRateCombo; + QSpinBox *m_durationSpin; + QCheckBox *m_ch1Enable; + QCheckBox *m_ch2Enable; + + // 控制按钮 + QPushButton *m_startBtn; + QPushButton *m_stopBtn; + QPushButton *m_clearBtn; + QPushButton *m_exportBtn; + + // 波形显示 + QWidget *m_waveformArea; + QLabel *m_ch1Label; + QLabel *m_ch2Label; + + // 数据表格 + QTableWidget *m_dataTable; + + // 统计信息 + QLabel *m_ch1MaxLabel; + QLabel *m_ch1MinLabel; + QLabel *m_ch1AvgLabel; + QLabel *m_ch2MaxLabel; + QLabel *m_ch2MinLabel; + QLabel *m_ch2AvgLabel; + + // 采集状态 + QTimer *m_captureTimer; + bool m_isCapturing; + QVector m_ch1Data; + QVector m_ch2Data; + int m_dataIndex; +}; + +#endif // WAVEFORMWIDGET_H diff --git a/widgets/wirelesswidget.cpp b/widgets/wirelesswidget.cpp new file mode 100644 index 0000000..2e98304 --- /dev/null +++ b/widgets/wirelesswidget.cpp @@ -0,0 +1,790 @@ +#include "wirelesswidget.h" +#include "overlaydialogswidget.h" +#include +#include + +// 样式常量定义 +const QString WirelessWidget::BUTTON_STYLE = R"( + QPushButton { + background-color: #f5f5f5; + color: #333333; + border: 1px solid #ddd; + border-radius: 6px; + padding: 8px 16px; + font-size: 14px; + font-weight: 500; + min-height: 36px; + } + QPushButton:hover { + background-color: #e8e8e8; + border-color: #ccc; + } + QPushButton:pressed { + background-color: #d0d0d0; + } +)"; + +const QString WirelessWidget::PRIMARY_BUTTON_STYLE = R"( + QPushButton { + background-color: #2196F3; + color: white; + border: none; + border-radius: 6px; + padding: 8px 16px; + font-size: 14px; + font-weight: 500; + min-height: 36px; + } + QPushButton:hover { + background-color: #1976D2; + } + QPushButton:pressed { + background-color: #1565C0; + } +)"; + +const QString WirelessWidget::DANGER_BUTTON_STYLE = R"( + QPushButton { + background-color: #f44336; + color: white; + border: none; + border-radius: 6px; + padding: 8px 16px; + font-size: 14px; + font-weight: 500; + min-height: 36px; + } + QPushButton:hover { + background-color: #d32f2f; + } + QPushButton:pressed { + background-color: #c62828; + } +)"; + +const QString WirelessWidget::INPUT_STYLE = R"( + QLineEdit, QComboBox { + background-color: white; + border: 1px solid #ddd; + border-radius: 6px; + padding: 8px 12px; + font-size: 14px; + min-height: 36px; + } + QLineEdit:focus, QComboBox:focus { + border-color: #2196F3; + } + QComboBox::drop-down { + subcontrol-origin: padding; + subcontrol-position: center right; + width: 30px; + border: none; + } + QComboBox::down-arrow { + image: url(:/icons/arrow_down.svg); + width: 14px; + height: 14px; + } +)"; + +const QString WirelessWidget::GROUP_STYLE = R"( + QGroupBox { + background-color: white; + border: 1px solid #e0e0e0; + border-radius: 8px; + margin-top: 16px; + padding-top: 16px; + font-size: 14px; + font-weight: 600; + } + QGroupBox::title { + subcontrol-origin: margin; + left: 16px; + padding: 0 8px; + color: #333; + } +)"; + +const QString WirelessWidget::TAB_STYLE = R"( + QPushButton { + background-color: transparent; + color: #666; + border: none; + border-bottom: 2px solid transparent; + padding: 12px 24px; + font-size: 14px; + font-weight: 500; + min-width: 100px; + } + QPushButton:hover { + color: #2196F3; + background-color: rgba(33, 150, 243, 0.05); + } +)"; + +const QString WirelessWidget::TAB_ACTIVE_STYLE = R"( + QPushButton { + background-color: transparent; + color: #2196F3; + border: none; + border-bottom: 2px solid #2196F3; + padding: 12px 24px; + font-size: 14px; + font-weight: 600; + min-width: 100px; + } +)"; + +WirelessWidget::WirelessWidget(QWidget *parent) + : QWidget(parent), m_currentTab(0) +{ + setupUI(); + + // 初始化状态刷新定时器 + m_statusTimer = new QTimer(this); + connect(m_statusTimer, &QTimer::timeout, this, &WirelessWidget::onRefreshStatus); + m_statusTimer->start(5000); // 每5秒刷新一次 + + // 初始刷新 + onRefreshStatus(); +} + +void WirelessWidget::setupUI() +{ + QVBoxLayout *mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(0); + + // 标题栏 + mainLayout->addWidget(createTitleBar()); + + // Tab栏 + mainLayout->addWidget(createTabBar()); + + // 内容区域(滚动区域) + QScrollArea *scrollArea = new QScrollArea; + scrollArea->setWidgetResizable(true); + scrollArea->setFrameShape(QFrame::NoFrame); + scrollArea->setStyleSheet("QScrollArea { background-color: #f5f5f5; }"); + + m_contentStack = new QStackedWidget; + m_contentStack->addWidget(createWifiPanel()); + m_contentStack->addWidget(create4GPanel()); + m_contentStack->addWidget(createStatusPanel()); + + scrollArea->setWidget(m_contentStack); + mainLayout->addWidget(scrollArea, 1); + + // 默认显示WiFi面板 + onTabChanged(0); +} + +QWidget *WirelessWidget::createTitleBar() +{ + QWidget *titleBar = new QWidget; + titleBar->setFixedHeight(60); + titleBar->setStyleSheet("background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #1976D2, stop:1 #1565C0);"); + + QHBoxLayout *layout = new QHBoxLayout(titleBar); + layout->setContentsMargins(16, 0, 16, 0); + + // 返回按钮 + m_backBtn = new QPushButton("← 返回"); + m_backBtn->setCursor(Qt::PointingHandCursor); + m_backBtn->setStyleSheet(R"( + QPushButton { + background: rgba(255,255,255,0.2); + color: white; + border: none; + border-radius: 6px; + padding: 8px 16px; + font-size: 14px; + } + QPushButton:hover { + background: rgba(255,255,255,0.3); + } + )"); + connect(m_backBtn, &QPushButton::clicked, this, &WirelessWidget::backRequested); + layout->addWidget(m_backBtn); + + // 标题(居中) + m_titleLabel = new QLabel("无线通讯"); + m_titleLabel->setStyleSheet("color: white; font-size: 18px; font-weight: bold;"); + m_titleLabel->setAlignment(Qt::AlignCenter); + layout->addWidget(m_titleLabel, 1); + + // 右侧占位,保持标题居中 + QWidget *spacer = new QWidget; + spacer->setFixedWidth(m_backBtn->sizeHint().width()); + layout->addWidget(spacer); + + return titleBar; +} + +QWidget *WirelessWidget::createTabBar() +{ + QWidget *tabBar = new QWidget; + tabBar->setFixedHeight(48); + tabBar->setStyleSheet("background-color: white; border-bottom: 1px solid #e0e0e0;"); + + QHBoxLayout *layout = new QHBoxLayout(tabBar); + layout->setContentsMargins(16, 0, 16, 0); + layout->setSpacing(0); + + QStringList tabNames = {"WiFi", "4G网络", "连接状态"}; + for (int i = 0; i < tabNames.size(); ++i) + { + QPushButton *tabBtn = new QPushButton(tabNames[i]); + tabBtn->setStyleSheet(i == 0 ? TAB_ACTIVE_STYLE : TAB_STYLE); + tabBtn->setCursor(Qt::PointingHandCursor); + connect(tabBtn, &QPushButton::clicked, this, [this, i]() + { onTabChanged(i); }); + layout->addWidget(tabBtn); + m_tabButtons.append(tabBtn); + } + + layout->addStretch(); + + return tabBar; +} + +QWidget *WirelessWidget::createWifiPanel() +{ + QWidget *panel = new QWidget; + panel->setStyleSheet("background-color: #f5f5f5;"); + + QVBoxLayout *layout = new QVBoxLayout(panel); + layout->setContentsMargins(20, 20, 20, 20); + layout->setSpacing(16); + + // WiFi列表区域 + QGroupBox *listGroup = new QGroupBox("可用网络"); + listGroup->setStyleSheet(GROUP_STYLE); + QVBoxLayout *listLayout = new QVBoxLayout(listGroup); + listLayout->setContentsMargins(16, 24, 16, 16); + listLayout->setSpacing(12); + + // 扫描按钮 + QHBoxLayout *scanLayout = new QHBoxLayout; + m_wifiScanBtn = new QPushButton("扫描网络"); + m_wifiScanBtn->setStyleSheet(PRIMARY_BUTTON_STYLE); + m_wifiScanBtn->setFixedWidth(120); + m_wifiScanBtn->setCursor(Qt::PointingHandCursor); + connect(m_wifiScanBtn, &QPushButton::clicked, this, &WirelessWidget::onWifiScan); + scanLayout->addWidget(m_wifiScanBtn); + scanLayout->addStretch(); + listLayout->addLayout(scanLayout); + + // WiFi列表 + m_wifiList = new QListWidget; + m_wifiList->setFixedHeight(200); + m_wifiList->setStyleSheet(R"( + QListWidget { + background-color: white; + border: 1px solid #ddd; + border-radius: 6px; + font-size: 14px; + } + QListWidget::item { + padding: 12px; + border-bottom: 1px solid #eee; + } + QListWidget::item:selected { + background-color: #e3f2fd; + color: #1976D2; + } + QListWidget::item:hover { + background-color: #f5f5f5; + } + )"); + connect(m_wifiList, &QListWidget::itemClicked, this, &WirelessWidget::onWifiItemClicked); + listLayout->addWidget(m_wifiList); + + layout->addWidget(listGroup); + + // 连接设置区域 + QGroupBox *connectGroup = new QGroupBox("连接设置"); + connectGroup->setStyleSheet(GROUP_STYLE); + QVBoxLayout *connectLayout = new QVBoxLayout(connectGroup); + connectLayout->setContentsMargins(16, 24, 16, 16); + connectLayout->setSpacing(12); + + // 密码输入 + QHBoxLayout *passwordLayout = new QHBoxLayout; + QLabel *passwordLabel = new QLabel("密码:"); + passwordLabel->setFixedWidth(60); + passwordLabel->setStyleSheet("font-size: 14px; color: #333;"); + passwordLayout->addWidget(passwordLabel); + + m_wifiPasswordEdit = new QLineEdit; + m_wifiPasswordEdit->setStyleSheet(INPUT_STYLE); + m_wifiPasswordEdit->setEchoMode(QLineEdit::Password); + m_wifiPasswordEdit->setPlaceholderText("输入WiFi密码"); + passwordLayout->addWidget(m_wifiPasswordEdit, 1); + connectLayout->addLayout(passwordLayout); + + // 连接状态 + QHBoxLayout *statusLayout = new QHBoxLayout; + QLabel *statusTitle = new QLabel("状态:"); + statusTitle->setFixedWidth(60); + statusTitle->setStyleSheet("font-size: 14px; color: #333;"); + statusLayout->addWidget(statusTitle); + + m_wifiStatusLabel = new QLabel("未连接"); + m_wifiStatusLabel->setStyleSheet("font-size: 14px; color: #f44336;"); + statusLayout->addWidget(m_wifiStatusLabel); + statusLayout->addStretch(); + connectLayout->addLayout(statusLayout); + + // 信号强度 + QHBoxLayout *signalLayout = new QHBoxLayout; + QLabel *signalTitle = new QLabel("信号:"); + signalTitle->setFixedWidth(60); + signalTitle->setStyleSheet("font-size: 14px; color: #333;"); + signalLayout->addWidget(signalTitle); + + m_wifiSignalBar = new QProgressBar; + m_wifiSignalBar->setFixedHeight(10); + m_wifiSignalBar->setRange(0, 100); + m_wifiSignalBar->setValue(0); + m_wifiSignalBar->setTextVisible(false); + m_wifiSignalBar->setStyleSheet(R"( + QProgressBar { + background-color: #e0e0e0; + border-radius: 5px; + } + QProgressBar::chunk { + background-color: #4CAF50; + border-radius: 5px; + } + )"); + signalLayout->addWidget(m_wifiSignalBar, 1); + connectLayout->addLayout(signalLayout); + + // 操作按钮 + QHBoxLayout *buttonLayout = new QHBoxLayout; + buttonLayout->addStretch(); + + m_wifiDisconnectBtn = new QPushButton("断开连接"); + m_wifiDisconnectBtn->setStyleSheet(DANGER_BUTTON_STYLE); + m_wifiDisconnectBtn->setFixedWidth(100); + m_wifiDisconnectBtn->setCursor(Qt::PointingHandCursor); + m_wifiDisconnectBtn->setEnabled(false); + connect(m_wifiDisconnectBtn, &QPushButton::clicked, this, &WirelessWidget::onWifiDisconnect); + buttonLayout->addWidget(m_wifiDisconnectBtn); + + m_wifiConnectBtn = new QPushButton("连接"); + m_wifiConnectBtn->setStyleSheet(PRIMARY_BUTTON_STYLE); + m_wifiConnectBtn->setFixedWidth(100); + m_wifiConnectBtn->setCursor(Qt::PointingHandCursor); + connect(m_wifiConnectBtn, &QPushButton::clicked, this, &WirelessWidget::onWifiConnect); + buttonLayout->addWidget(m_wifiConnectBtn); + + connectLayout->addLayout(buttonLayout); + layout->addWidget(connectGroup); + + layout->addStretch(); + + // 添加模拟WiFi数据 + updateWifiList(); + + return panel; +} + +QWidget *WirelessWidget::create4GPanel() +{ + QWidget *panel = new QWidget; + panel->setStyleSheet("background-color: #f5f5f5;"); + + QVBoxLayout *layout = new QVBoxLayout(panel); + layout->setContentsMargins(20, 20, 20, 20); + layout->setSpacing(16); + + // SIM卡状态 + QGroupBox *simGroup = new QGroupBox("SIM卡状态"); + simGroup->setStyleSheet(GROUP_STYLE); + QVBoxLayout *simLayout = new QVBoxLayout(simGroup); + simLayout->setContentsMargins(16, 24, 16, 16); + simLayout->setSpacing(12); + + QHBoxLayout *simStatusLayout = new QHBoxLayout; + QLabel *simTitle = new QLabel("SIM卡:"); + simTitle->setFixedWidth(80); + simTitle->setStyleSheet("font-size: 14px; color: #333;"); + simStatusLayout->addWidget(simTitle); + + m_simStatusLabel = new QLabel("已插入 (中国移动)"); + m_simStatusLabel->setStyleSheet("font-size: 14px; color: #4CAF50;"); + simStatusLayout->addWidget(m_simStatusLabel); + simStatusLayout->addStretch(); + simLayout->addLayout(simStatusLayout); + + // 信号强度 + QHBoxLayout *signal4GLayout = new QHBoxLayout; + QLabel *signal4GTitle = new QLabel("信号强度:"); + signal4GTitle->setFixedWidth(80); + signal4GTitle->setStyleSheet("font-size: 14px; color: #333;"); + signal4GLayout->addWidget(signal4GTitle); + + m_4gSignalBar = new QProgressBar; + m_4gSignalBar->setFixedHeight(10); + m_4gSignalBar->setRange(0, 100); + m_4gSignalBar->setValue(75); + m_4gSignalBar->setTextVisible(false); + m_4gSignalBar->setStyleSheet(R"( + QProgressBar { + background-color: #e0e0e0; + border-radius: 5px; + } + QProgressBar::chunk { + background-color: #4CAF50; + border-radius: 5px; + } + )"); + signal4GLayout->addWidget(m_4gSignalBar, 1); + simLayout->addLayout(signal4GLayout); + + layout->addWidget(simGroup); + + // APN设置 + QGroupBox *apnGroup = new QGroupBox("APN设置"); + apnGroup->setStyleSheet(GROUP_STYLE); + QVBoxLayout *apnLayout = new QVBoxLayout(apnGroup); + apnLayout->setContentsMargins(16, 24, 16, 16); + apnLayout->setSpacing(12); + + // APN选择 + QHBoxLayout *apnSelectLayout = new QHBoxLayout; + QLabel *apnSelectLabel = new QLabel("预设APN:"); + apnSelectLabel->setFixedWidth(80); + apnSelectLabel->setStyleSheet("font-size: 14px; color: #333;"); + apnSelectLayout->addWidget(apnSelectLabel); + + m_apnCombo = new QComboBox; + m_apnCombo->setStyleSheet(INPUT_STYLE); + m_apnCombo->addItems({"中国移动 (cmnet)", "中国联通 (3gnet)", "中国电信 (ctnet)", "自定义"}); + m_apnCombo->setCursor(Qt::PointingHandCursor); + apnSelectLayout->addWidget(m_apnCombo, 1); + apnLayout->addLayout(apnSelectLayout); + + // 自定义APN + QHBoxLayout *apnCustomLayout = new QHBoxLayout; + QLabel *apnCustomLabel = new QLabel("APN:"); + apnCustomLabel->setFixedWidth(80); + apnCustomLabel->setStyleSheet("font-size: 14px; color: #333;"); + apnCustomLayout->addWidget(apnCustomLabel); + + m_apnEdit = new QLineEdit; + m_apnEdit->setStyleSheet(INPUT_STYLE); + m_apnEdit->setPlaceholderText("输入自定义APN"); + m_apnEdit->setText("cmnet"); + apnCustomLayout->addWidget(m_apnEdit, 1); + apnLayout->addLayout(apnCustomLayout); + + // 用户名 + QHBoxLayout *usernameLayout = new QHBoxLayout; + QLabel *usernameLabel = new QLabel("用户名:"); + usernameLabel->setFixedWidth(80); + usernameLabel->setStyleSheet("font-size: 14px; color: #333;"); + usernameLayout->addWidget(usernameLabel); + + m_4gUsernameEdit = new QLineEdit; + m_4gUsernameEdit->setStyleSheet(INPUT_STYLE); + m_4gUsernameEdit->setPlaceholderText("可选"); + usernameLayout->addWidget(m_4gUsernameEdit, 1); + apnLayout->addLayout(usernameLayout); + + // 密码 + QHBoxLayout *password4GLayout = new QHBoxLayout; + QLabel *password4GLabel = new QLabel("密码:"); + password4GLabel->setFixedWidth(80); + password4GLabel->setStyleSheet("font-size: 14px; color: #333;"); + password4GLayout->addWidget(password4GLabel); + + m_4gPasswordEdit = new QLineEdit; + m_4gPasswordEdit->setStyleSheet(INPUT_STYLE); + m_4gPasswordEdit->setEchoMode(QLineEdit::Password); + m_4gPasswordEdit->setPlaceholderText("可选"); + password4GLayout->addWidget(m_4gPasswordEdit, 1); + apnLayout->addLayout(password4GLayout); + + layout->addWidget(apnGroup); + + // 连接状态 + QGroupBox *statusGroup = new QGroupBox("连接状态"); + statusGroup->setStyleSheet(GROUP_STYLE); + QVBoxLayout *statusLayout = new QVBoxLayout(statusGroup); + statusLayout->setContentsMargins(16, 24, 16, 16); + statusLayout->setSpacing(12); + + QHBoxLayout *status4GLayout = new QHBoxLayout; + QLabel *status4GTitle = new QLabel("状态:"); + status4GTitle->setFixedWidth(80); + status4GTitle->setStyleSheet("font-size: 14px; color: #333;"); + status4GLayout->addWidget(status4GTitle); + + m_4gStatusLabel = new QLabel("未连接"); + m_4gStatusLabel->setStyleSheet("font-size: 14px; color: #f44336;"); + status4GLayout->addWidget(m_4gStatusLabel); + status4GLayout->addStretch(); + statusLayout->addLayout(status4GLayout); + + // 操作按钮 + QHBoxLayout *button4GLayout = new QHBoxLayout; + button4GLayout->addStretch(); + + m_4gDisconnectBtn = new QPushButton("断开连接"); + m_4gDisconnectBtn->setStyleSheet(DANGER_BUTTON_STYLE); + m_4gDisconnectBtn->setFixedWidth(100); + m_4gDisconnectBtn->setCursor(Qt::PointingHandCursor); + m_4gDisconnectBtn->setEnabled(false); + connect(m_4gDisconnectBtn, &QPushButton::clicked, this, &WirelessWidget::on4GDisconnect); + button4GLayout->addWidget(m_4gDisconnectBtn); + + m_4gConnectBtn = new QPushButton("连接"); + m_4gConnectBtn->setStyleSheet(PRIMARY_BUTTON_STYLE); + m_4gConnectBtn->setFixedWidth(100); + m_4gConnectBtn->setCursor(Qt::PointingHandCursor); + connect(m_4gConnectBtn, &QPushButton::clicked, this, &WirelessWidget::on4GConnect); + button4GLayout->addWidget(m_4gConnectBtn); + + statusLayout->addLayout(button4GLayout); + layout->addWidget(statusGroup); + + layout->addStretch(); + + return panel; +} + +QWidget *WirelessWidget::createStatusPanel() +{ + QWidget *panel = new QWidget; + panel->setStyleSheet("background-color: #f5f5f5;"); + + QVBoxLayout *layout = new QVBoxLayout(panel); + layout->setContentsMargins(20, 20, 20, 20); + layout->setSpacing(16); + + // 网络信息 + QGroupBox *infoGroup = new QGroupBox("网络信息"); + infoGroup->setStyleSheet(GROUP_STYLE); + QVBoxLayout *infoLayout = new QVBoxLayout(infoGroup); + infoLayout->setContentsMargins(16, 24, 16, 16); + infoLayout->setSpacing(12); + + auto createInfoRow = [](const QString &title, QLabel *&valueLabel, const QString &defaultValue) -> QHBoxLayout * + { + QHBoxLayout *row = new QHBoxLayout; + QLabel *titleLabel = new QLabel(title); + titleLabel->setFixedWidth(100); + titleLabel->setStyleSheet("font-size: 14px; color: #666;"); + row->addWidget(titleLabel); + + valueLabel = new QLabel(defaultValue); + valueLabel->setStyleSheet("font-size: 14px; color: #333; font-weight: 500;"); + row->addWidget(valueLabel); + row->addStretch(); + return row; + }; + + infoLayout->addLayout(createInfoRow("连接类型:", m_connectionTypeLabel, "未连接")); + infoLayout->addLayout(createInfoRow("IP地址:", m_ipAddressLabel, "--")); + infoLayout->addLayout(createInfoRow("MAC地址:", m_macAddressLabel, "--")); + infoLayout->addLayout(createInfoRow("信号强度:", m_signalStrengthLabel, "--")); + + layout->addWidget(infoGroup); + + // 流量统计 + QGroupBox *trafficGroup = new QGroupBox("流量统计"); + trafficGroup->setStyleSheet(GROUP_STYLE); + QVBoxLayout *trafficLayout = new QVBoxLayout(trafficGroup); + trafficLayout->setContentsMargins(16, 24, 16, 16); + trafficLayout->setSpacing(12); + + trafficLayout->addLayout(createInfoRow("上传速度:", m_uploadSpeedLabel, "0 KB/s")); + trafficLayout->addLayout(createInfoRow("下载速度:", m_downloadSpeedLabel, "0 KB/s")); + + layout->addWidget(trafficGroup); + + // 刷新按钮 + QHBoxLayout *refreshLayout = new QHBoxLayout; + refreshLayout->addStretch(); + QPushButton *refreshBtn = new QPushButton("刷新状态"); + refreshBtn->setStyleSheet(PRIMARY_BUTTON_STYLE); + refreshBtn->setFixedWidth(120); + refreshBtn->setCursor(Qt::PointingHandCursor); + connect(refreshBtn, &QPushButton::clicked, this, &WirelessWidget::onRefreshStatus); + refreshLayout->addWidget(refreshBtn); + layout->addLayout(refreshLayout); + + layout->addStretch(); + + return panel; +} + +void WirelessWidget::onTabChanged(int index) +{ + m_currentTab = index; + m_contentStack->setCurrentIndex(index); + + // 更新Tab样式 + for (int i = 0; i < m_tabButtons.size(); ++i) + { + m_tabButtons[i]->setStyleSheet(i == index ? TAB_ACTIVE_STYLE : TAB_STYLE); + } +} + +void WirelessWidget::updateWifiList() +{ + m_wifiList->clear(); + + // 添加模拟WiFi数据 + QStringList wifiNetworks = { + "🔒 Office_5G (信号: 优)", + "🔒 Office_2.4G (信号: 良)", + "🔒 Guest_Network (信号: 中)", + "🔓 Public_WiFi (信号: 弱)", + "🔒 HomeNetwork (信号: 弱)"}; + + for (const QString &network : wifiNetworks) + { + QListWidgetItem *item = new QListWidgetItem(network); + m_wifiList->addItem(item); + } +} + +void WirelessWidget::onWifiScan() +{ + m_wifiScanBtn->setEnabled(false); + m_wifiScanBtn->setText("扫描中..."); + + // 模拟扫描延迟 + QTimer::singleShot(1500, this, [this]() + { + updateWifiList(); + m_wifiScanBtn->setEnabled(true); + m_wifiScanBtn->setText("扫描网络"); + OverlayDialog::information(window(), "扫描完成", "发现 5 个可用网络"); }); +} + +void WirelessWidget::onWifiItemClicked(QListWidgetItem *item) +{ + Q_UNUSED(item) + m_wifiPasswordEdit->setFocus(); +} + +void WirelessWidget::onWifiConnect() +{ + if (m_wifiList->currentItem() == nullptr) + { + OverlayDialog::warning(window(), "提示", "请先选择要连接的WiFi网络"); + return; + } + + QString network = m_wifiList->currentItem()->text(); + bool needPassword = network.contains("🔒"); + + if (needPassword && m_wifiPasswordEdit->text().isEmpty()) + { + OverlayDialog::warning(window(), "提示", "请输入WiFi密码"); + return; + } + + m_wifiConnectBtn->setEnabled(false); + m_wifiConnectBtn->setText("连接中..."); + + // 模拟连接 + QTimer::singleShot(2000, this, [this, network]() + { + m_wifiConnectBtn->setEnabled(true); + m_wifiConnectBtn->setText("连接"); + + // 模拟连接成功 + m_wifiStatusLabel->setText("已连接"); + m_wifiStatusLabel->setStyleSheet("font-size: 14px; color: #4CAF50;"); + m_wifiSignalBar->setValue(85); + m_wifiDisconnectBtn->setEnabled(true); + + updateConnectionStatus(); + OverlayDialog::information(window(), "连接成功", "已成功连接到WiFi网络"); }); +} + +void WirelessWidget::onWifiDisconnect() +{ + m_wifiStatusLabel->setText("未连接"); + m_wifiStatusLabel->setStyleSheet("font-size: 14px; color: #f44336;"); + m_wifiSignalBar->setValue(0); + m_wifiDisconnectBtn->setEnabled(false); + m_wifiPasswordEdit->clear(); + + updateConnectionStatus(); + OverlayDialog::information(window(), "已断开", "WiFi连接已断开"); +} + +void WirelessWidget::on4GConnect() +{ + m_4gConnectBtn->setEnabled(false); + m_4gConnectBtn->setText("连接中..."); + + QTimer::singleShot(2000, this, [this]() + { + m_4gConnectBtn->setEnabled(true); + m_4gConnectBtn->setText("连接"); + + m_4gStatusLabel->setText("已连接 (4G LTE)"); + m_4gStatusLabel->setStyleSheet("font-size: 14px; color: #4CAF50;"); + m_4gDisconnectBtn->setEnabled(true); + + updateConnectionStatus(); + OverlayDialog::information(window(), "连接成功", "4G网络连接成功"); }); +} + +void WirelessWidget::on4GDisconnect() +{ + m_4gStatusLabel->setText("未连接"); + m_4gStatusLabel->setStyleSheet("font-size: 14px; color: #f44336;"); + m_4gDisconnectBtn->setEnabled(false); + + updateConnectionStatus(); + OverlayDialog::information(window(), "已断开", "4G网络连接已断开"); +} + +void WirelessWidget::onRefreshStatus() +{ + updateConnectionStatus(); +} + +void WirelessWidget::updateConnectionStatus() +{ + // 检查连接状态并更新状态面板 + bool wifiConnected = m_wifiStatusLabel->text().contains("已连接"); + bool g4Connected = m_4gStatusLabel->text().contains("已连接"); + + if (wifiConnected) + { + m_connectionTypeLabel->setText("WiFi"); + m_ipAddressLabel->setText("192.168.1.105"); + m_macAddressLabel->setText("AA:BB:CC:DD:EE:FF"); + m_signalStrengthLabel->setText(QString("%1%").arg(m_wifiSignalBar->value())); + m_uploadSpeedLabel->setText("125 KB/s"); + m_downloadSpeedLabel->setText("2.3 MB/s"); + } + else if (g4Connected) + { + m_connectionTypeLabel->setText("4G LTE"); + m_ipAddressLabel->setText("10.0.0.55"); + m_macAddressLabel->setText("--"); + m_signalStrengthLabel->setText(QString("%1%").arg(m_4gSignalBar->value())); + m_uploadSpeedLabel->setText("85 KB/s"); + m_downloadSpeedLabel->setText("1.2 MB/s"); + } + else + { + m_connectionTypeLabel->setText("未连接"); + m_ipAddressLabel->setText("--"); + m_macAddressLabel->setText("--"); + m_signalStrengthLabel->setText("--"); + m_uploadSpeedLabel->setText("0 KB/s"); + m_downloadSpeedLabel->setText("0 KB/s"); + } +} diff --git a/widgets/wirelesswidget.h b/widgets/wirelesswidget.h new file mode 100644 index 0000000..6033b26 --- /dev/null +++ b/widgets/wirelesswidget.h @@ -0,0 +1,101 @@ +#ifndef WIRELESSWIDGET_H +#define WIRELESSWIDGET_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief 无线通讯设置页面 + * 提供4G和WIFI通讯配置功能 + */ +class WirelessWidget : public QWidget +{ + Q_OBJECT + +public: + explicit WirelessWidget(QWidget *parent = nullptr); + +signals: + void backRequested(); + +private slots: + void onWifiScan(); + void onWifiConnect(); + void onWifiDisconnect(); + void onWifiItemClicked(QListWidgetItem *item); + void on4GConnect(); + void on4GDisconnect(); + void onRefreshStatus(); + void onTabChanged(int index); + +private: + void setupUI(); + QWidget *createTitleBar(); + QWidget *createTabBar(); + QWidget *createWifiPanel(); + QWidget *create4GPanel(); + QWidget *createStatusPanel(); + void updateWifiList(); + void updateConnectionStatus(); + + // 标题栏 + QLabel *m_titleLabel; + QPushButton *m_backBtn; + + // Tab栏 + QList m_tabButtons; + QStackedWidget *m_contentStack; + int m_currentTab; + + // WiFi面板 + QListWidget *m_wifiList; + QPushButton *m_wifiScanBtn; + QPushButton *m_wifiConnectBtn; + QPushButton *m_wifiDisconnectBtn; + QLineEdit *m_wifiPasswordEdit; + QLabel *m_wifiStatusLabel; + QProgressBar *m_wifiSignalBar; + + // 4G面板 + QComboBox *m_apnCombo; + QLineEdit *m_apnEdit; + QLineEdit *m_4gUsernameEdit; + QLineEdit *m_4gPasswordEdit; + QPushButton *m_4gConnectBtn; + QPushButton *m_4gDisconnectBtn; + QLabel *m_4gStatusLabel; + QProgressBar *m_4gSignalBar; + QLabel *m_simStatusLabel; + + // 状态面板 + QLabel *m_connectionTypeLabel; + QLabel *m_ipAddressLabel; + QLabel *m_macAddressLabel; + QLabel *m_signalStrengthLabel; + QLabel *m_uploadSpeedLabel; + QLabel *m_downloadSpeedLabel; + + // 刷新定时器 + QTimer *m_statusTimer; + + // 样式常量 + static const QString BUTTON_STYLE; + static const QString PRIMARY_BUTTON_STYLE; + static const QString DANGER_BUTTON_STYLE; + static const QString INPUT_STYLE; + static const QString GROUP_STYLE; + static const QString TAB_STYLE; + static const QString TAB_ACTIVE_STYLE; +}; + +#endif // WIRELESSWIDGET_H