first commit

This commit is contained in:
2026-01-02 19:20:35 +09:00
commit a10cb30c4a
94 changed files with 28609 additions and 0 deletions

61
.gitignore vendored Normal file
View File

@@ -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

58
CMakeLists.txt Normal file
View File

@@ -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)

103
CalibratorLauncher.pro Normal file
View File

@@ -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

451
config/launcher_config.json Normal file
View File

@@ -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
}
}
}

349
hardware/channelmanager.cpp Normal file
View File

@@ -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<ChannelId>(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<ConnectionType>()
<< TWO_WIRE << THREE_WIRE << FOUR_WIRE;
inputCh1.supportedOperations = QList<OperationType>()
<< 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<ConnectionType>() << TWO_WIRE;
inputCh2.supportedOperations = QList<OperationType>()
<< 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<ConnectionType>()
<< TWO_WIRE << THREE_WIRE << FOUR_WIRE;
outputCh1.supportedOperations = QList<OperationType>()
<< 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<ConnectionType>() << TWO_WIRE;
outputCh2.supportedOperations = QList<OperationType>()
<< 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::OperationType>
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::ChannelId>
ChannelManager::getAvailableChannels(ChannelType type) const
{
QList<ChannelId> 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::ChannelId>
ChannelManager::getChannelsWithOperation(OperationType operation) const
{
QList<ChannelId> channels;
for (auto it = m_channelStatus.constBegin(); it != m_channelStatus.constEnd(); ++it)
{
if (it.value().operationDetails.operation == operation)
{
channels.append(it.key());
}
}
return channels;
}

137
hardware/channelmanager.h Normal file
View File

@@ -0,0 +1,137 @@
#ifndef CHANNELMANAGER_H
#define CHANNELMANAGER_H
#include <QDateTime>
#include <QList>
#include <QMap>
#include <QObject>
/**
* @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<ConnectionType> supportedConnections;
QList<OperationType> 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<ChannelId> getAvailableChannels(ChannelType type) const;
QList<OperationType> 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<ChannelId> 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<ChannelId, ChannelStatus> m_channelStatus;
QMap<ChannelId, ChannelCapability> m_channelCapabilities;
static ChannelManager *m_instance;
};
#endif // CHANNELMANAGER_H

25
icons/app.png Normal file
View File

@@ -0,0 +1,25 @@
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256">
<defs>
<linearGradient id="appGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#2196F3;stop-opacity:1" />
<stop offset="100%" style="stop-color:#1565C0;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="256" height="256" rx="48" fill="url(#appGrad)"/>
<g fill="white" transform="translate(48, 48)">
<!-- 校验仪图标 -->
<rect x="20" y="0" width="120" height="160" rx="12" fill="white"/>
<rect x="32" y="16" width="96" height="60" rx="4" fill="#2196F3"/>
<!-- 显示屏 -->
<text x="80" y="52" font-family="Arial" font-size="20" fill="white" text-anchor="middle" font-weight="bold">12.345</text>
<text x="80" y="68" font-family="Arial" font-size="10" fill="white" text-anchor="middle">mA</text>
<!-- 按钮 -->
<circle cx="50" cy="100" r="12" fill="#4CAF50"/>
<circle cx="80" cy="100" r="12" fill="#FF9800"/>
<circle cx="110" cy="100" r="12" fill="#F44336"/>
<!-- 端子 -->
<circle cx="50" cy="140" r="8" fill="#757575"/>
<circle cx="80" cy="140" r="8" fill="#757575"/>
<circle cx="110" cy="140" r="8" fill="#757575"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

4
icons/arrow_down.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
<path d="M2 4 L6 8 L10 4" stroke="#757575" stroke-width="2" fill="none" stroke-linecap="round"
stroke-linejoin="round" />
</svg>

After

Width:  |  Height:  |  Size: 218 B

4
icons/arrow_up.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
<path d="M2 8 L6 4 L10 8" stroke="#757575" stroke-width="2" fill="none" stroke-linecap="round"
stroke-linejoin="round" />
</svg>

After

Width:  |  Height:  |  Size: 218 B

18
icons/ic_ac_voltage.svg Normal file
View File

@@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96">
<defs>
<linearGradient id="grad7" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#E91E63;stop-opacity:1" />
<stop offset="100%" style="stop-color:#C2185B;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="96" height="96" rx="20" fill="url(#grad7)" />
<g fill="white" transform="translate(18, 18)">
<!-- AC 符号 -->
<text x="30" y="40" font-family="Arial" font-size="32" fill="white" text-anchor="middle"
font-weight="bold">AC</text>
<!-- 正弦波小图 -->
<path d="M10 50 Q25 35, 30 50 Q35 65, 50 50" stroke="white" stroke-width="3" fill="none" />
</g>
<text x="48" y="82" font-family="Arial" font-size="10" fill="white" text-anchor="middle"
font-weight="bold">220V</text>
</svg>

After

Width:  |  Height:  |  Size: 856 B

18
icons/ic_data.svg Normal file
View File

@@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96">
<defs>
<linearGradient id="grad14" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#8BC34A;stop-opacity:1" />
<stop offset="100%" style="stop-color:#689F38;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="96" height="96" rx="20" fill="url(#grad14)" />
<g fill="white" transform="translate(22, 18)">
<!-- 文件夹 -->
<path d="M0 12 L0 48 L52 48 L52 12 L24 12 L20 6 L0 6 Z" fill="white" />
<!-- 文件 -->
<rect x="12" y="22" width="16" height="20" rx="2" fill="#8BC34A" />
<rect x="32" y="22" width="16" height="20" rx="2" fill="#8BC34A" />
</g>
<text x="48" y="82" font-family="Arial" font-size="10" fill="white" text-anchor="middle"
font-weight="bold">Data</text>
</svg>

After

Width:  |  Height:  |  Size: 845 B

17
icons/ic_dc_current.svg Normal file
View File

@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96">
<defs>
<linearGradient id="grad4" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#4CAF50;stop-opacity:1" />
<stop offset="100%" style="stop-color:#388E3C;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="96" height="96" rx="20" fill="url(#grad4)" />
<g fill="white" transform="translate(20, 16)">
<!-- 电流符号 -->
<path d="M28 0 L28 20 L38 20 L22 48 L22 28 L12 28 Z" fill="white" />
<text x="28" y="62" font-family="Arial" font-size="16" fill="white" text-anchor="middle"
font-weight="bold">I</text>
</g>
<text x="48" y="82" font-family="Arial" font-size="10" fill="white" text-anchor="middle"
font-weight="bold">mA</text>
</svg>

After

Width:  |  Height:  |  Size: 804 B

16
icons/ic_dc_voltage.svg Normal file
View File

@@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96">
<defs>
<linearGradient id="grad5" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#9C27B0;stop-opacity:1" />
<stop offset="100%" style="stop-color:#7B1FA2;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="96" height="96" rx="20" fill="url(#grad5)" />
<g fill="white" transform="translate(28, 18)">
<!-- 电压符号 V -->
<text x="20" y="48" font-family="Arial" font-size="48" fill="white" text-anchor="middle"
font-weight="bold">V</text>
</g>
<text x="48" y="82" font-family="Arial" font-size="10" fill="white" text-anchor="middle"
font-weight="bold">DC</text>
</svg>

After

Width:  |  Height:  |  Size: 733 B

24
icons/ic_dual_channel.svg Normal file
View File

@@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96">
<defs>
<linearGradient id="grad12" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#673AB7;stop-opacity:1" />
<stop offset="100%" style="stop-color:#512DA8;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="96" height="96" rx="20" fill="url(#grad12)" />
<g fill="white" transform="translate(18, 22)">
<!-- 双通道符号 -->
<rect x="0" y="8" width="24" height="32" rx="4" fill="white" />
<rect x="36" y="8" width="24" height="32" rx="4" fill="white" />
<!-- 连接箭头 -->
<polygon points="28,20 32,16 32,24" fill="white" />
<polygon points="32,28 28,32 32,36" fill="white" />
<!-- 通道标识 -->
<text x="12" y="28" font-family="Arial" font-size="10" fill="#673AB7" text-anchor="middle"
font-weight="bold">1</text>
<text x="48" y="28" font-family="Arial" font-size="10" fill="#673AB7" text-anchor="middle"
font-weight="bold">2</text>
</g>
<text x="48" y="82" font-family="Arial" font-size="10" fill="white" text-anchor="middle"
font-weight="bold">2CH</text>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

17
icons/ic_frequency.svg Normal file
View File

@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96">
<defs>
<linearGradient id="grad6" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#00BCD4;stop-opacity:1" />
<stop offset="100%" style="stop-color:#0097A7;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="96" height="96" rx="20" fill="url(#grad6)" />
<g stroke="white" stroke-width="3" fill="none" transform="translate(12, 24)">
<!-- 正弦波 -->
<path d="M0 24 Q18 0, 36 24 Q54 48, 72 24" />
<!-- 方波 -->
<path d="M0 48 L18 48 L18 28 L36 28 L36 48 L54 48 L54 28 L72 28" />
</g>
<text x="48" y="82" font-family="Arial" font-size="10" fill="white" text-anchor="middle"
font-weight="bold">Hz</text>
</svg>

After

Width:  |  Height:  |  Size: 774 B

19
icons/ic_insulation.svg Normal file
View File

@@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96">
<defs>
<linearGradient id="grad3" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#F44336;stop-opacity:1" />
<stop offset="100%" style="stop-color:#D32F2F;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="96" height="96" rx="20" fill="url(#grad3)" />
<g fill="white" transform="translate(24, 16)">
<!-- 锁图标 -->
<rect x="8" y="24" width="32" height="28" rx="4" fill="white" />
<path d="M14 24 V16 C14 8, 20 4, 24 4 C28 4, 34 8, 34 16 V24" stroke="white" stroke-width="4"
fill="none" />
<circle cx="24" cy="38" r="4" fill="#F44336" />
<rect x="22" y="40" width="4" height="6" fill="#F44336" />
</g>
<text x="48" y="82" font-family="Arial" font-size="10" fill="white" text-anchor="middle"
font-weight="bold"></text>
</svg>

After

Width:  |  Height:  |  Size: 905 B

18
icons/ic_network_test.svg Normal file
View File

@@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96">
<defs>
<linearGradient id="grad19" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#03A9F4;stop-opacity:1" />
<stop offset="100%" style="stop-color:#0288D1;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="96" height="96" rx="20" fill="url(#grad19)" />
<g fill="white" transform="translate(20, 18)">
<!-- 网络图标 -->
<circle cx="28" cy="28" r="24" stroke="white" stroke-width="3" fill="none" />
<circle cx="28" cy="28" r="16" stroke="white" stroke-width="2" fill="none" />
<circle cx="28" cy="28" r="8" stroke="white" stroke-width="2" fill="none" />
<circle cx="28" cy="28" r="3" fill="white" />
</g>
<text x="48" y="82" font-family="Arial" font-size="9" fill="white" text-anchor="middle"
font-weight="bold">NET</text>
</svg>

After

Width:  |  Height:  |  Size: 901 B

17
icons/ic_ramp.svg Normal file
View File

@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96">
<defs>
<linearGradient id="grad10" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#3F51B5;stop-opacity:1" />
<stop offset="100%" style="stop-color:#303F9F;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="96" height="96" rx="20" fill="url(#grad10)" />
<g stroke="white" stroke-width="3" fill="none" transform="translate(18, 20)">
<!-- 斜波 -->
<path d="M0 48 L20 48 L40 16 L40 48 L60 48" />
<!-- 箭头 -->
<polygon points="35,22 40,12 45,22" fill="white" stroke="none" />
</g>
<text x="48" y="82" font-family="Arial" font-size="10" fill="white" text-anchor="middle"
font-weight="bold">Ramp</text>
</svg>

After

Width:  |  Height:  |  Size: 774 B

19
icons/ic_rcp63.svg Normal file
View File

@@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96">
<defs>
<linearGradient id="grad17" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#2196F3;stop-opacity:1" />
<stop offset="100%" style="stop-color:#1976D2;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="96" height="96" rx="20" fill="url(#grad17)" />
<g fill="white" transform="translate(20, 16)">
<!-- 地球 -->
<circle cx="28" cy="28" r="24" stroke="white" stroke-width="3" fill="none" />
<ellipse cx="28" cy="28" rx="10" ry="24" stroke="white" stroke-width="2" fill="none" />
<path d="M4 28 L52 28" stroke="white" stroke-width="2" />
<path d="M10 14 L46 14" stroke="white" stroke-width="2" />
<path d="M10 42 L46 42" stroke="white" stroke-width="2" />
</g>
<text x="48" y="82" font-family="Arial" font-size="9" fill="white" text-anchor="middle"
font-weight="bold">RCP63</text>
</svg>

After

Width:  |  Height:  |  Size: 964 B

19
icons/ic_ripple.svg Normal file
View File

@@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96">
<defs>
<linearGradient id="grad9" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#FF5722;stop-opacity:1" />
<stop offset="100%" style="stop-color:#E64A19;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="96" height="96" rx="20" fill="url(#grad9)" />
<g stroke="white" stroke-width="2" fill="none" transform="translate(16, 20)">
<!-- 纹波波形 -->
<path
d="M0 30 L10 30 L10 15 L20 15 L20 30 L30 30 L30 20 L40 20 L40 30 L50 30 L50 10 L60 10 L60 30 L64 30" />
<!-- AI 标识 -->
<text x="32" y="52" font-family="Arial" font-size="14" fill="white" text-anchor="middle"
font-weight="bold" stroke="none">AI</text>
</g>
<text x="48" y="82" font-family="Arial" font-size="9" fill="white" text-anchor="middle"
font-weight="bold">Ripple</text>
</svg>

After

Width:  |  Height:  |  Size: 923 B

21
icons/ic_rtd.svg Normal file
View File

@@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96">
<defs>
<linearGradient id="grad1" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#2196F3;stop-opacity:1" />
<stop offset="100%" style="stop-color:#1976D2;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="96" height="96" rx="20" fill="url(#grad1)" />
<g fill="white" transform="translate(24, 16)">
<!-- 温度计 -->
<rect x="18" y="0" width="12" height="50" rx="6" fill="white" />
<circle cx="24" cy="54" r="10" fill="white" />
<!-- 刻度 -->
<rect x="32" y="10" width="8" height="2" fill="white" opacity="0.7" />
<rect x="32" y="20" width="8" height="2" fill="white" opacity="0.7" />
<rect x="32" y="30" width="8" height="2" fill="white" opacity="0.7" />
<rect x="32" y="40" width="8" height="2" fill="white" opacity="0.7" />
</g>
<text x="48" y="82" font-family="Arial" font-size="10" fill="white" text-anchor="middle"
font-weight="bold">RTD</text>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

23
icons/ic_rtd_output.svg Normal file
View File

@@ -0,0 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96">
<defs>
<linearGradient id="grad1" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#1565C0;stop-opacity:1" />
<stop offset="100%" style="stop-color:#0D47A1;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="96" height="96" rx="20" fill="url(#grad1)" />
<g fill="white" transform="translate(18, 14)">
<!-- 温度计 -->
<rect x="10" y="4" width="10" height="40" rx="5" fill="white" />
<circle cx="15" cy="48" r="8" fill="white" />
<!-- 刻度 -->
<rect x="22" y="12" width="6" height="2" fill="white" opacity="0.7" />
<rect x="22" y="20" width="6" height="2" fill="white" opacity="0.7" />
<rect x="22" y="28" width="6" height="2" fill="white" opacity="0.7" />
<rect x="22" y="36" width="6" height="2" fill="white" opacity="0.7" />
<!-- 输出箭头 -->
<path d="M38 30 L54 30 L54 24 L66 32 L54 40 L54 34 L38 34 Z" fill="white" />
</g>
<text x="48" y="82" font-family="Arial" font-size="9" fill="white" text-anchor="middle"
font-weight="bold">OUTPUT</text>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

26
icons/ic_settings.svg Normal file
View File

@@ -0,0 +1,26 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96">
<defs>
<linearGradient id="grad18" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#9E9E9E;stop-opacity:1" />
<stop offset="100%" style="stop-color:#757575;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="96" height="96" rx="20" fill="url(#grad18)" />
<g fill="white" transform="translate(20, 16)">
<!-- 齿轮 -->
<circle cx="28" cy="28" r="12" stroke="white" stroke-width="6" fill="none" />
<circle cx="28" cy="28" r="4" fill="white" />
<!-- 齿轮齿 -->
<rect x="24" y="0" width="8" height="12" fill="white" />
<rect x="24" y="44" width="8" height="12" fill="white" />
<rect x="0" y="24" width="12" height="8" fill="white" />
<rect x="44" y="24" width="12" height="8" fill="white" />
<!-- 斜齿 -->
<rect x="6" y="6" width="8" height="10" fill="white" transform="rotate(45 10 11)" />
<rect x="42" y="6" width="8" height="10" fill="white" transform="rotate(-45 46 11)" />
<rect x="6" y="42" width="8" height="10" fill="white" transform="rotate(-45 10 47)" />
<rect x="42" y="42" width="8" height="10" fill="white" transform="rotate(45 46 47)" />
</g>
<text x="48" y="82" font-family="Arial" font-size="10" fill="white" text-anchor="middle"
font-weight="bold">设置</text>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

21
icons/ic_sop.svg Normal file
View File

@@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96">
<defs>
<linearGradient id="grad16" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#CDDC39;stop-opacity:1" />
<stop offset="100%" style="stop-color:#AFB42B;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="96" height="96" rx="20" fill="url(#grad16)" />
<g fill="white" transform="translate(22, 16)">
<!-- 剪贴板 -->
<rect x="4" y="8" width="44" height="52" rx="4" fill="white" />
<rect x="16" y="0" width="20" height="12" rx="2" fill="white" />
<!-- 勾选列表 -->
<rect x="12" y="20" width="28" height="4" rx="2" fill="#CDDC39" />
<rect x="12" y="30" width="20" height="4" rx="2" fill="#CDDC39" />
<rect x="12" y="40" width="24" height="4" rx="2" fill="#CDDC39" />
<rect x="12" y="50" width="16" height="4" rx="2" fill="#CDDC39" />
</g>
<text x="48" y="82" font-family="Arial" font-size="10" fill="white" text-anchor="middle"
font-weight="bold">SOP</text>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

18
icons/ic_switch.svg Normal file
View File

@@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96">
<defs>
<linearGradient id="grad8" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#795548;stop-opacity:1" />
<stop offset="100%" style="stop-color:#5D4037;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="96" height="96" rx="20" fill="url(#grad8)" />
<g fill="white" transform="translate(20, 20)">
<!-- 开关符号 -->
<rect x="0" y="16" width="56" height="24" rx="12" fill="white" />
<circle cx="16" cy="28" r="10" fill="#795548" />
<!-- ON/OFF 文字 -->
<text x="38" y="32" font-family="Arial" font-size="10" fill="#795548" text-anchor="middle">ON</text>
</g>
<text x="48" y="82" font-family="Arial" font-size="10" fill="white" text-anchor="middle"
font-weight="bold">SW</text>
</svg>

After

Width:  |  Height:  |  Size: 859 B

19
icons/ic_thermocouple.svg Normal file
View File

@@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96">
<defs>
<linearGradient id="grad2" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#FF9800;stop-opacity:1" />
<stop offset="100%" style="stop-color:#F57C00;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="96" height="96" rx="20" fill="url(#grad2)" />
<g fill="white" transform="translate(28, 18)">
<!-- 火焰 -->
<path d="M20 0 C20 0, 35 20, 35 35 C35 45, 27 50, 20 50 C13 50, 5 45, 5 35 C5 20, 20 0, 20 0 Z"
fill="white" />
<path
d="M20 20 C20 20, 28 30, 28 38 C28 44, 24 48, 20 48 C16 48, 12 44, 12 38 C12 30, 20 20, 20 20 Z"
fill="#FF9800" />
</g>
<text x="48" y="82" font-family="Arial" font-size="10" fill="white" text-anchor="middle"
font-weight="bold">TC</text>
</svg>

After

Width:  |  Height:  |  Size: 857 B

20
icons/ic_trim.svg Normal file
View File

@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96">
<defs>
<linearGradient id="grad13" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#607D8B;stop-opacity:1" />
<stop offset="100%" style="stop-color:#455A64;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="96" height="96" rx="20" fill="url(#grad13)" />
<g fill="white" transform="translate(18, 20)">
<!-- 滑块轨道 -->
<rect x="0" y="20" width="60" height="8" rx="4" fill="rgba(255,255,255,0.3)" />
<!-- 滑块 -->
<circle cx="36" cy="24" r="10" fill="white" />
<!-- +/- 符号 -->
<text x="8" y="50" font-family="Arial" font-size="16" fill="white" text-anchor="middle"></text>
<text x="52" y="50" font-family="Arial" font-size="16" fill="white" text-anchor="middle">+</text>
</g>
<text x="48" y="82" font-family="Arial" font-size="10" fill="white" text-anchor="middle"
font-weight="bold">±</text>
</svg>

After

Width:  |  Height:  |  Size: 990 B

20
icons/ic_waveform.svg Normal file
View File

@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96">
<defs>
<linearGradient id="grad11" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#009688;stop-opacity:1" />
<stop offset="100%" style="stop-color:#00796B;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="96" height="96" rx="20" fill="url(#grad11)" />
<g stroke="white" stroke-width="2" fill="none" transform="translate(14, 18)">
<!-- 示波器框 -->
<rect x="0" y="0" width="68" height="48" rx="4" fill="none" stroke="white" stroke-width="2" />
<!-- 波形 -->
<path d="M8 24 Q17 8, 24 24 Q31 40, 38 24 Q45 8, 52 24 Q59 40, 60 24" stroke="white"
stroke-width="2" />
<!-- 录制指示 -->
<circle cx="58" cy="10" r="4" fill="#F44336" />
</g>
<text x="48" y="82" font-family="Arial" font-size="10" fill="white" text-anchor="middle"
font-weight="bold">REC</text>
</svg>

After

Width:  |  Height:  |  Size: 950 B

21
icons/ic_wireless.svg Normal file
View File

@@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96">
<defs>
<linearGradient id="grad15" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#03A9F4;stop-opacity:1" />
<stop offset="100%" style="stop-color:#0288D1;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="96" height="96" rx="20" fill="url(#grad15)" />
<g fill="white" transform="translate(24, 16)">
<!-- 信号塔 -->
<rect x="20" y="20" width="8" height="36" fill="white" />
<polygon points="24,20 10,56 38,56" fill="white" />
<!-- WiFi 信号 -->
<path d="M24 8 Q8 8, 8 24" stroke="white" stroke-width="3" fill="none" stroke-linecap="round" />
<path d="M24 8 Q40 8, 40 24" stroke="white" stroke-width="3" fill="none" stroke-linecap="round" />
<path d="M24 0 Q0 0, 0 24" stroke="white" stroke-width="3" fill="none" stroke-linecap="round" />
<path d="M24 0 Q48 0, 48 24" stroke="white" stroke-width="3" fill="none" stroke-linecap="round" />
</g>
<text x="48" y="82" font-family="Arial" font-size="10" fill="white" text-anchor="middle"
font-weight="bold">4G</text>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

39
main.cpp Normal file
View File

@@ -0,0 +1,39 @@
#include <QApplication>
#include <QFontDatabase>
#include <QFile>
#include <QDir>
#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();
}

457
mainwindow.cpp Normal file
View File

@@ -0,0 +1,457 @@
#include "mainwindow.h"
#include "widgets/overlaydialogswidget.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QMouseEvent>
#include <QDebug>
#include <QProcess>
#include <QCoreApplication>
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));
}
}

105
mainwindow.h Normal file
View File

@@ -0,0 +1,105 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QStackedWidget>
#include <QPropertyAnimation>
#include <QParallelAnimationGroup>
#include <QVector>
#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<LauncherPage *> 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

View File

@@ -0,0 +1,479 @@
#include "functionregistry.h"
#include <QDebug>
#include <QtMath>
#include <QRandomGenerator>
#include <QThread>
// =============================================================================
// 單例實現
// =============================================================================
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 &parameterNames,
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 &parameters)
{
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 &params)
{
Q_UNUSED(metadata)
Q_UNUSED(params)
// 簡單驗證 - 可以擴展
return true;
}
// =============================================================================
// 函數執行
// =============================================================================
QVariantList FunctionRegistry::executeFunction(const QString &functionType,
const QVariant &parameters)
{
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 &params) -> 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 &params) -> 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 &params) -> 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 &params) -> 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 &params) -> 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 &params) -> 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 &params) -> 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 &params) -> 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 &params) -> 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 &params) -> 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<int>(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 &params) -> 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 &params) -> 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 &params) -> 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 &params) -> 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 &params) -> 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 &params) -> 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 &params) -> 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() << "個內建函數";
}

View File

@@ -0,0 +1,84 @@
#ifndef FUNCTIONREGISTRY_H
#define FUNCTIONREGISTRY_H
#include <QObject>
#include <QMap>
#include <QString>
#include <QStringList>
#include <QVariant>
#include <QVariantMap>
#include <QVariantList>
#include <functional>
/**
* @brief 功能函數註冊表
*
* 管理測量和輸出功能的註冊和執行。
* 每個功能函數接受參數映射,返回結果數組。
*/
class FunctionRegistry : public QObject
{
Q_OBJECT
public:
// 函數回調類型:接受 QVariantMap 參數,返回 QVariantList 結果
using FunctionCallback = std::function<QVariantList(const QVariantMap &)>;
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 &parameterNames,
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 &parameters);
// 查詢
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 &parameters);
bool validateParameters(const FunctionMetadata &metadata,
const QVariantMap &params);
void registerBuiltinFunctions();
QMap<QString, FunctionMetadata> m_functions;
static FunctionRegistry *m_instance;
};
#endif // FUNCTIONREGISTRY_H

319
procedure/proceduredata.h Normal file
View File

@@ -0,0 +1,319 @@
#ifndef PROCEDUREDATA_H
#define PROCEDUREDATA_H
#include <QString>
#include <QStringList>
#include <QVector>
#include <QList>
#include <QVariantMap>
#include <QDateTime>
#include <QPair>
// =====================================================
// 基本枚舉類型
// =====================================================
// 步驟類型
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<QVariantMap> uploadGroups;
QList<FieldDefinition> columnHeaders; // 用於grid類型表格
QList<QPair<QString, QString>> rowHeaders; // 用於grid類型表格 (id, name)
QList<FieldDefinition> fields; // 用於form和series類型表格
QList<StaticCell> staticCells; // 靜態內容
};
// =====================================================
// 測試動作相關結構
// =====================================================
// 字段選擇器
struct FieldSelector
{
QString tableRef;
int row = -1;
int column = -1;
QString fieldName;
QStringList fields; // 用於form類型表格
QList<QPair<QString, QString>> 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<QString, FieldSelector> inputs;
QMap<QString, FieldSelector> outputs;
QList<FieldSelector> 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<TestAction> 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<TestActivityGroup> 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<ActivityStep> activitySequence;
// 活動序列 (新格式stores QVariant of TestTaskGroup or ResultDisplay用于新解析器)
QList<QVariant> activityVariants;
// 定義存儲
QMap<QString, TestTaskGroup> testTaskGroups;
QMap<QString, TestActivityGroup> testActivityGroups;
QMap<QString, TestAction> testActions;
QMap<QString, ResultDisplay> resultDisplays;
QMap<QString, TableDefinition> tables;
// 舊版兼容
QList<TestTaskGroup> 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<TableField> columns;
QVector<QVariantMap> 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<StepData> steps;
};
// 程序定義(舊版)
struct ProcedureData
{
QString id;
QString name;
QString version;
QString description;
QVector<TaskGroup> taskGroups;
QVector<TableData> 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

View File

@@ -0,0 +1,865 @@
#include "procedureengine.h"
#include "procedureparser.h"
#include <QDebug>
#include <QFile>
#include <QTimer>
#include <QRegularExpression>
// =============================================================================
// 單例實現
// =============================================================================
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<QVector<QVariant>> 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<QVariant> 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<QVector<QVariant>> ProcedureEngine::getTableData(const QString &tableRef) const
{
if (m_tableData.contains(tableRef))
{
return m_tableData[tableRef];
}
return QVector<QVector<QVariant>>();
}
// =============================================================================
// 條件評估
// =============================================================================
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<double>(m_context.currentActivityIndex) / m_config.activitySequence.size();
// 可以進一步細化到階段和動作級別
// TODO: 更精確的進度計算
return qBound(0.0, activityProgress * 100.0, 100.0);
}

172
procedure/procedureengine.h Normal file
View File

@@ -0,0 +1,172 @@
#ifndef PROCEDUREENGINE_H
#define PROCEDUREENGINE_H
#include "proceduredata.h"
#include <QObject>
#include <QDateTime>
#include <QMap>
#include <QVariant>
#include <QVector>
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<QVector<QVariant>> 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<QString, QVector<QVector<QVariant>>> m_tableData;
};
#endif // PROCEDUREENGINE_H

View File

@@ -0,0 +1,655 @@
#include "proceduremanager.h"
#include "procedureparser.h"
#include <QDir>
#include <QFile>
#include <QDebug>
#include <QFileInfo>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QCoreApplication>
ProcedureManager::ProcedureManager(QObject *parent)
: QObject(parent)
{
// 不再自动创建示例数据,等待调用 loadProcedureList
}
ProcedureManager::~ProcedureManager()
{
}
QVector<ProcedureSummary> 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<ProcedureSummary> ProcedureManager::searchProcedures(const QString &keyword)
{
if (keyword.isEmpty())
{
return m_procedureList;
}
QVector<ProcedureSummary> 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<QVariantMap> &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;
}

View File

@@ -0,0 +1,58 @@
#ifndef PROCEDUREMANAGER_H
#define PROCEDUREMANAGER_H
#include <QObject>
#include <QVector>
#include <QMap>
#include "proceduredata.h"
class ProcedureManager : public QObject
{
Q_OBJECT
public:
explicit ProcedureManager(QObject *parent = nullptr);
~ProcedureManager();
// 加载规程列表
QVector<ProcedureSummary> loadProcedureList(const QString &directory);
// 搜索规程
QVector<ProcedureSummary> 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<QVariantMap> &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<ProcedureSummary> m_procedureList;
QMap<QString, ProcedureData> m_loadedProcedures;
};
#endif // PROCEDUREMANAGER_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,83 @@
#pragma once
#include <QList>
#include <QMap>
#include <QString>
#include <QVariant>
#include <memory>
#include <ryml.hpp>
// 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<QString, TableDefinition> 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<ryml::Tree> tree;
QMap<QString, ryml::ConstNodeRef> referenceCache;
QMap<QString, ryml::ConstNodeRef> stageNodeCache;
QMap<QString, ryml::ConstNodeRef> 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);
};

View File

@@ -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

View File

@@ -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**: 多行文本测试

View File

@@ -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": "特殊字符测试 <HTML> {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标签测试: <root><child>内容</child></root>。检查是否被误解析为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"
}
]
}
]
}
]
}

6009
procedures/rcp63.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,700 @@
testActions:
prereq_147: &prereq_147
id: prereq_147
document: '<p>在热停堆工况(热功率稳定),平均温度最大允许变化&lt;0.2℃若KIC中显示的三个环路平均温度变化AT&gt;0.2℃,则采取数据滑动平均方式,期间确保三个环路平均温度的滑动平均值最大最小值之差&lt;0.2℃、测量时间大于滑动周期。</p>'
mode: manual
dataFields:
- tableRef: '#/tables/manualConfirm'
fields: [confirm_147]
prereq_150: &prereq_150
id: prereq_150
document: '<p>三个蒸发器处于稳定状态,压力一致。</p>'
mode: manual
dataFields:
- tableRef: '#/tables/manualConfirm'
fields: [confirm_150]
prereq_43: &prereq_43
id: prereq_43
document: '<p>蒸汽发生器水位稳定。</p>'
mode: manual
dataFields:
- tableRef: '#/tables/manualConfirm'
fields: [confirm_43]
prereq_75: &prereq_75
id: prereq_75
document: '<p>RCV的一个下泄孔板投入运行确保测量期间无下泄孔板相关操作一回路温度保持稳定测量结果若超差需要分析下泄流量影响。</p>'
mode: manual
dataFields:
- tableRef: '#/tables/manualConfirm'
fields: [confirm_75]
prereq_92: &prereq_92
id: prereq_92
document: '<p>其它模式/工况下需进行相应的独立风险分析。</p>'
mode: manual
dataFields:
- tableRef: '#/tables/manualConfirm'
fields: [confirm_92]
pre_153: &pre_153
id: pre_153
document: '<p>本程序适用于热停堆(热功率稳定)工况下执行RCP63程序统一进行Sensor Check部分使用。</p>'
mode: manual
dataFields:
- tableRef: '#/tables/manualConfirm'
fields: [confirm_153]
pre_136: &pre_136
id: pre_136
document: '<p>执行前确认KIC中一回路三个环路平均温度变化量不超过0.2℃。</p>'
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: '<p>若三环路平均温度变化超过0.2℃记录滑动周期、各环路滑动平均最大最小值之差及最短数据测量时间确认均满足≤0.2℃且测量时间&gt;滑动周期。</p>'
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: '<p>确认 RPR、RPN上无相关的试验检修工作。</p>'
mode: manual
dataFields:
- tableRef: '#/tables/manualConfirm'
fields: [confirm_138]
pre_139: &pre_139
id: pre_139
document: '<p>按附表1检查相关模拟量指示并记录确认结果满意。</p>'
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: '<p>试验前根据附表1报警清单检查报警确认无异常报警方可进行后续检查工作。</p>'
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: '<p>采集X5端子排拆前KIC指示值。</p>'
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: '<p>采集X5端子排恢复后KIC指示值。</p>'
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: '<p><strong>测量位置:</strong>KCSO13AR/EB911/X5</p><p><strong>测量项目:</strong>R1/2, R1/3, R1/4, R2/3, R2/4, R3/4, R/4wires</p>'
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: '<p><strong>测量位置:</strong>KCSO13AR/EB911/X5</p><p><strong>注意事项:</strong></p><ul><li>测试电压50V</li><li>测试时间不小于20秒</li><li>测量后探头对地放电</li></ul>'
dataFields:
- tableRef: '#/tables/resistanceMeasurementTable'
cells:
- row: RCP030MT
column: insulation
uploadStrategy: inherit
rcp033mtResistance: &rcp033mtResistance
id: rcp033mtResistance
mode: auto
functionType: RESISTANCE_4WIRE
channel: input1
functionParameters:
document: '<p><strong>测量位置:</strong>KCSO13AR/EB911/X5</p><p><strong>测量项目:</strong>R1/2, R1/3, R1/4, R2/3, R2/4, R3/4, R/4wires</p>'
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: '<p><strong>测量位置:</strong>KCSO13AR/EB911/X5</p><p><strong>注意事项:</strong></p><ul><li>测试电压50V</li><li>测试时间不小于20秒</li><li>测量后探头对地放电</li><li>复归保险FB059 F11和F12</li></ul>'
dataFields:
- tableRef: '#/tables/resistanceMeasurementTable'
cells:
- row: RCP033MT
column: insulation
uploadStrategy: inherit
rcp031mtResistance: &rcp031mtResistance
id: rcp031mtResistance
mode: auto
functionType: RESISTANCE_4WIRE
channel: input1
functionParameters:
document: '<p><strong>测量位置:</strong>KCS013AR/EB911/X6</p><p><strong>测量项目:</strong>R1/2, R1/3, R1/4, R2/3, R2/4, R3/4, R/4wires</p>'
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: '<p><strong>测量位置:</strong>KCS013AR/EB911/X6</p><p><strong>注意事项:</strong></p><ul><li>测试电压50V</li><li>测试时间不小于20秒</li><li>测量后探头对地放电</li></ul>'
dataFields:
- tableRef: '#/tables/resistanceMeasurementTable'
cells:
- row: RCP031MT
column: insulation
uploadStrategy: inherit
rcp034mtResistance: &rcp034mtResistance
id: rcp034mtResistance
mode: auto
functionType: RESISTANCE_4WIRE
channel: input1
functionParameters:
document: '<p><strong>测量位置:</strong>KCS013AR/EB911/X6</p><p><strong>测量项目:</strong>R1/2, R1/3, R1/4, R2/3, R2/4, R3/4, R/4wires</p>'
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: '<p><strong>测量位置:</strong>KCS013AR/EB911/X6</p><p><strong>注意事项:</strong></p><ul><li>测试电压50V</li><li>测试时间不小于20秒</li><li>测量后探头对地放电</li></ul>'
dataFields:
- tableRef: '#/tables/resistanceMeasurementTable'
cells:
- row: RCP034MT
column: insulation
uploadStrategy: inherit
rcp043mtResistance: &rcp043mtResistance
id: rcp043mtResistance
mode: auto
functionType: RESISTANCE_3WIRE
channel: input1
functionParameters:
document: '<p><strong>测量位置:</strong>KCSO13AR/EB902/X1</p><p><strong>测量项目:</strong>R2/3, R2/4, R3/4</p><p><strong>注意:</strong>3线制传感器</p>'
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: '<p><strong>测量位置:</strong>KCSO13AR/EB902/X1</p><p><strong>注意事项:</strong></p><ul><li>测试电压50V</li><li>测试时间不小于20秒</li><li>测量后探头对地放电</li></ul>'
dataFields:
- tableRef: '#/tables/resistanceMeasurementTable'
cells:
- row: RCP043MT
column: insulation
uploadStrategy: inherit
rcp044mtResistance: &rcp044mtResistance
id: rcp044mtResistance
mode: auto
functionType: RESISTANCE_3WIRE
channel: input1
functionParameters:
document: '<p><strong>测量位置:</strong>KCSO13AR/EB902/X1</p><p><strong>测量项目:</strong>R2/3, R2/4, R3/4</p><p><strong>注意:</strong>3线制传感器</p>'
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: '<p><strong>测量位置:</strong>KCSO13AR/EB902/X1</p><p><strong>注意事项:</strong></p><ul><li>测试电压50V</li><li>测试时间不小于20秒</li><li>测量后探头对地放电</li><li>复归保险FB005 F11和F12</li><li>请主控确认报警消除</li></ul>'
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: '<p>显示本次实验的所有测量数据和检查结果</p>'
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:
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

View File

@@ -0,0 +1,59 @@
procedure:
id: SIMPLE_TEST
name: 简单测试规程
version: '1.0'
document: |
<p>这是一个简单的测试规程,用于演示 ProcedurePlayer 的基本功能。</p>
<p>包含一个测试任务组和结果展示。</p>
activitySequence:
- id: testGroup1
name: 测试组1
document: '<p>执行基本测试</p>'
stages:
- id: stage1
name: 测试阶段
document: '<p>填写测试数据</p>'
tableRefs:
- '#/tables/testTable'
actions:
- id: action1
document: '<p>请在测试数据表中填写字段1和字段2。</p>'
mode: manual
dataFields:
- tableRef: '#/tables/testTable'
fields:
- field1
- field2
- id: resultDisplay
name: 结果展示
document: '<p>查看测试结果</p>'
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

37
resources.qrc Normal file
View File

@@ -0,0 +1,37 @@
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource prefix="/">
<!-- 图标资源 -->
<file>icons/app.png</file>
<file>icons/ic_rtd.svg</file>
<file>icons/ic_rtd_output.svg</file>
<file>icons/ic_thermocouple.svg</file>
<file>icons/ic_insulation.svg</file>
<file>icons/ic_dc_current.svg</file>
<file>icons/ic_dc_voltage.svg</file>
<file>icons/ic_frequency.svg</file>
<file>icons/ic_ac_voltage.svg</file>
<file>icons/ic_switch.svg</file>
<file>icons/ic_ripple.svg</file>
<file>icons/ic_ramp.svg</file>
<file>icons/ic_waveform.svg</file>
<file>icons/ic_dual_channel.svg</file>
<file>icons/ic_trim.svg</file>
<file>icons/ic_data.svg</file>
<file>icons/ic_wireless.svg</file>
<file>icons/ic_sop.svg</file>
<file>icons/ic_rcp63.svg</file>
<file>icons/ic_settings.svg</file>
<file>icons/ic_network_test.svg</file>
<file>icons/arrow_down.svg</file>
<file>icons/arrow_up.svg</file>
<!-- 字体资源 (可选,如果需要嵌入字体) -->
<!-- <file>fonts/SourceHanSansSC-Regular.otf</file> -->
<!-- <file>fonts/SourceHanSansSC-Medium.otf</file> -->
<!-- <file>fonts/SourceHanSansSC-Bold.otf</file> -->
<!-- 配置文件 -->
<file>config/launcher_config.json</file>
</qresource>
</RCC>

13
run.sh Executable file
View File

@@ -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

602
schema.json Normal file
View File

@@ -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)"
}
}
}

382
utils/configmanager.cpp Normal file
View File

@@ -0,0 +1,382 @@
#include "configmanager.h"
#include <QFile>
#include <QDebug>
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": "",
"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<PageConfig> ConfigManager::getPages() const
{
QVector<PageConfig> pages;
QJsonArray pagesArray = m_config["pages"].toArray();
for (const QJsonValue &pageVal : pagesArray)
{
pages.append(parsePageConfig(pageVal.toObject()));
}
return pages;
}
QVector<AppIconData> ConfigManager::getDockApps() const
{
QVector<AppIconData> 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;
}

37
utils/configmanager.h Normal file
View File

@@ -0,0 +1,37 @@
#ifndef CONFIGMANAGER_H
#define CONFIGMANAGER_H
#include <QObject>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QVector>
#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<PageConfig> getPages() const;
QVector<AppIconData> 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

286
utils/stylehelper.cpp Normal file
View File

@@ -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)";
}

32
utils/stylehelper.h Normal file
View File

@@ -0,0 +1,32 @@
#ifndef STYLEHELPER_H
#define STYLEHELPER_H
#include <QApplication>
#include <QString>
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

283
widgets/appicon.cpp Normal file
View File

@@ -0,0 +1,283 @@
#include "appicon.h"
#include <QPainter>
#include <QPainterPath>
#include <QMouseEvent>
#include <QLinearGradient>
#include <QVBoxLayout>
#include <QDebug>
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();
// 保留基本ASCII32-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<QString, QString> 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);
}

74
widgets/appicon.h Normal file
View File

@@ -0,0 +1,74 @@
#ifndef APPICON_H
#define APPICON_H
#include <QWidget>
#include <QLabel>
#include <QPropertyAnimation>
#include <QGraphicsDropShadowEffect>
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

View File

@@ -0,0 +1,252 @@
#include "channelstatuswidget.h"
#include <QPainter>
#include <QPaintEvent>
#include <QFont>
#include <QDebug>
// =====================================================
// 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<ChannelInfo> 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);
}
}

View File

@@ -0,0 +1,98 @@
#ifndef CHANNELSTATUSWIDGET_H
#define CHANNELSTATUSWIDGET_H
#include "../hardware/channelmanager.h"
#include <QWidget>
#include <QLabel>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPainter>
/**
* @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<ChannelManager::ChannelId, ChannelStatusWidget *> m_channelWidgets;
ChannelManager *m_channelManager;
ChannelStatusWidget::DisplayMode m_displayMode;
void setupUI(LayoutOrientation orientation);
};
#endif // CHANNELSTATUSWIDGET_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,94 @@
#ifndef DATAMANAGEMENTWIDGET_H
#define DATAMANAGEMENTWIDGET_H
#include <QWidget>
#include <QLabel>
#include <QPushButton>
#include <QTableWidget>
#include <QComboBox>
#include <QDateEdit>
#include <QLineEdit>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QStackedWidget>
#include <QProgressBar>
#include <QCheckBox>
/**
* @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

View File

@@ -0,0 +1,172 @@
#include "devicestatuswidget.h"
#include <QPainter>
#include <QPainterPath>
#include <QHBoxLayout>
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<ChannelStatus> &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);
}

View File

@@ -0,0 +1,40 @@
#ifndef DEVICESTATUSWIDGET_H
#define DEVICESTATUSWIDGET_H
#include <QWidget>
#include <QLabel>
#include <QVector>
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<ChannelStatus> &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<ChannelStatus> m_channels;
QVector<QLabel *> m_channelLabels;
};
#endif // DEVICESTATUSWIDGET_H

142
widgets/dockbar.cpp Normal file
View File

@@ -0,0 +1,142 @@
#include "dockbar.h"
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QPainter>
#include <QPainterPath>
#include <QPushButton>
DockBar::DockBar(QWidget *parent)
: QWidget(parent)
{
setupUI();
}
DockBar::~DockBar()
{
}
void DockBar::setupUI()
{
setObjectName("dockBar");
}
void DockBar::setApps(const QVector<AppIconData> &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<QString, QString> 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);
}

34
widgets/dockbar.h Normal file
View File

@@ -0,0 +1,34 @@
#ifndef DOCKBAR_H
#define DOCKBAR_H
#include <QWidget>
#include <QVector>
#include <QLabel>
#include "appicon.h"
class DockBar : public QWidget
{
Q_OBJECT
public:
explicit DockBar(QWidget *parent = nullptr);
~DockBar();
void setApps(const QVector<AppIconData> &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<AppIconData> m_apps;
QVector<QWidget *> m_icons;
};
#endif // DOCKBAR_H

View File

@@ -0,0 +1,533 @@
#include "dualchannelwidget.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGroupBox>
#include <QScrollArea>
#include <QTimer>
#include <random>
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<int>::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<double>::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<int>::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<double>::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()]));
}
}

View File

@@ -0,0 +1,78 @@
#ifndef DUALCHANNELWIDGET_H
#define DUALCHANNELWIDGET_H
#include <QWidget>
#include <QPushButton>
#include <QLabel>
#include <QComboBox>
#include <QDoubleSpinBox>
#include <QGroupBox>
#include <QCheckBox>
/**
* @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

207
widgets/launcherpage.cpp Normal file
View File

@@ -0,0 +1,207 @@
#include "launcherpage.h"
#include <QGridLayout>
#include <QResizeEvent>
#include <QPainter>
#include <QPainterPath>
#include <QTimer>
#include <QDebug>
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<int, QRect> 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<int, QRect> &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);
}
}
}

63
widgets/launcherpage.h Normal file
View File

@@ -0,0 +1,63 @@
#ifndef LAUNCHERPAGE_H
#define LAUNCHERPAGE_H
#include <QWidget>
#include <QVector>
#include <QJsonObject>
#include <QMap>
#include <QRect>
#include "appicon.h"
struct PageConfig
{
QString id;
QString title;
QVector<AppIconData> apps;
};
// 分组信息结构
struct GroupInfo
{
QString groupId;
QVector<int> 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<int, QRect> &iconRects); // 更新分组矩形
PageConfig m_config;
QVector<AppIcon *> m_icons;
QMap<QString, GroupInfo> m_groups; // 分组信息
// 网格布局参数
int m_columns;
int m_rows;
int m_horizontalGap;
int m_verticalGap;
int m_horizontalPadding;
int m_verticalPadding;
};
#endif // LAUNCHERPAGE_H

View File

@@ -0,0 +1,470 @@
#include "networktestwidget.h"
#include "overlaydialogswidget.h"
#include <QScrollArea>
#include <QDateTime>
#include <QDebug>
// 样式常量定义
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("<span style='color: #888;'>[%1]</span> <span style='color: %2;'>%3</span><br>")
.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");
}

View File

@@ -0,0 +1,72 @@
#ifndef NETWORKTESTWIDGET_H
#define NETWORKTESTWIDGET_H
#include <QWidget>
#include <QLabel>
#include <QPushButton>
#include <QLineEdit>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QTextEdit>
#include <QProgressBar>
#include <QGroupBox>
#include <QTimer>
/**
* @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

View File

@@ -0,0 +1,322 @@
#include "overlaydialogswidget.h"
#include <QApplication>
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<void(bool, const QString &)> 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<void(bool)> 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<void(bool, const QString &)> 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();
}

View File

@@ -0,0 +1,81 @@
#ifndef OVERLAYDIALOGSWIDGET_H
#define OVERLAYDIALOGSWIDGET_H
#include <QWidget>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QLineEdit>
#include <QGraphicsOpacityEffect>
#include <QPropertyAnimation>
#include <functional>
/**
* @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<void(bool)> callback);
static void input(QWidget *parent, const QString &title, const QString &prompt,
std::function<void(bool, const QString &)> 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<void(bool, const QString &)> 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<void(bool, const QString &)> m_callback;
};
#endif // OVERLAYDIALOGSWIDGET_H

116
widgets/pageindicator.cpp Normal file
View File

@@ -0,0 +1,116 @@
#include "pageindicator.h"
#include <QPainter>
#include <QMouseEvent>
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);
}

37
widgets/pageindicator.h Normal file
View File

@@ -0,0 +1,37 @@
#ifndef PAGEINDICATOR_H
#define PAGEINDICATOR_H
#include <QWidget>
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

View File

@@ -0,0 +1,224 @@
#include "procedurelistwidget.h"
#include "overlaydialogswidget.h"
#include "../procedure/proceduremanager.h"
#include <QFrame>
#include <QScrollBar>
#include <QCoreApplication>
#include <QDir>
#include <QDebug>
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<ProcedureSummary> &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);
}

View File

@@ -0,0 +1,61 @@
#ifndef PROCEDURELISTWIDGET_H
#define PROCEDURELISTWIDGET_H
#include <QWidget>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QPushButton>
#include <QListWidget>
#include <QLabel>
#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<ProcedureSummary> &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

View File

@@ -0,0 +1,697 @@
#include "procedureplayerwidget.h"
#include "overlaydialogswidget.h"
#include "../procedure/proceduremanager.h"
#include <QHeaderView>
#include <QScrollBar>
// ============== 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<QVBoxLayout *>(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();
}

View File

@@ -0,0 +1,158 @@
#ifndef PROCEDUREPLAYERWIDGET_H
#define PROCEDUREPLAYERWIDGET_H
#include <QWidget>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QSplitter>
#include <QScrollArea>
#include <QLabel>
#include <QPushButton>
#include <QTreeWidget>
#include <QStackedWidget>
#include <QTableWidget>
#include <QCheckBox>
#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<QString, StepItemWidget *> m_stepWidgets;
QMap<QString, TableViewWidget *> m_tableWidgets;
QMap<QString, QTreeWidgetItem *> m_navItems;
QMap<QString, StepData> m_allSteps; // 存储所有步骤数据
// 表格浮层
TableOverlayWidget *m_tableOverlay;
};
#endif // PROCEDUREPLAYERWIDGET_H

583
widgets/settingswidget.cpp Normal file
View File

@@ -0,0 +1,583 @@
#include "settingswidget.h"
#include "overlaydialogswidget.h"
#include <QDateTime>
#include <QTabBar>
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, "恢复出厂", "此功能需要管理员权限");
}
});
}

87
widgets/settingswidget.h Normal file
View File

@@ -0,0 +1,87 @@
#ifndef SETTINGSWIDGET_H
#define SETTINGSWIDGET_H
#include <QWidget>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QComboBox>
#include <QSpinBox>
#include <QCheckBox>
#include <QTabWidget>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QGroupBox>
#include <QScrollArea>
/**
* @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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,483 @@
#ifndef SIGNALMEASUREMENTWIDGET_H
#define SIGNALMEASUREMENTWIDGET_H
#include <QWidget>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QStackedWidget>
#include <QScrollArea>
#include <QLabel>
#include <QPushButton>
#include <QComboBox>
#include <QLineEdit>
#include <QDoubleSpinBox>
#include <QGroupBox>
#include <QTableWidget>
#include <QHeaderView>
#include <QTimer>
#include <QRadioButton>
#include <QButtonGroup>
#include <QCheckBox>
#include <QProgressBar>
#include <QSplitter>
#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<ChannelManager::ChannelId, ChannelStatusWidget *> 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

View File

@@ -0,0 +1,618 @@
#include "signaltrimwidget.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGroupBox>
#include <QScrollArea>
#include <QFrame>
#include <cmath>
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<int>::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<double>::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<int>::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<double>::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({"Ω", "", ""});
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<int>(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<int>(1.0 / step);
m_fineSlider->setRange(-range, range);
m_fineValueSpin->setSingleStep(step);
}

View File

@@ -0,0 +1,81 @@
#ifndef SIGNALTRIMWIDGET_H
#define SIGNALTRIMWIDGET_H
#include <QWidget>
#include <QPushButton>
#include <QLabel>
#include <QComboBox>
#include <QDoubleSpinBox>
#include <QSlider>
#include <QGroupBox>
/**
* @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

156
widgets/statusbar.cpp Normal file
View File

@@ -0,0 +1,156 @@
#include "statusbar.h"
#include <QHBoxLayout>
#include <QDateTime>
#include <QPalette>
#include <QProcess>
#include <QRegularExpression>
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"); // 空心圆点表示未连接
}
}

41
widgets/statusbar.h Normal file
View File

@@ -0,0 +1,41 @@
#ifndef STATUSBAR_H
#define STATUSBAR_H
#include <QWidget>
#include <QLabel>
#include <QTimer>
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

View File

@@ -0,0 +1,361 @@
#include "tableoverlaywidget.h"
#include <QPropertyAnimation>
#include <QGraphicsOpacityEffect>
#include <QScrollBar>
#include <QDebug>
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<QPushButton *>(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);
}

View File

@@ -0,0 +1,111 @@
#ifndef TABLEOVERLAYWIDGET_H
#define TABLEOVERLAYWIDGET_H
#include <QWidget>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QStackedWidget>
#include <QMap>
#include <QScrollArea>
#include <QLabel>
#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<QString, TableTab> m_tables; // 表格映射
QString m_currentTableId; // 当前显示的表格ID
bool m_isVisible; // 是否可见
};
#endif // TABLEOVERLAYWIDGET_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,200 @@
#ifndef TABLEWIDGETFACTORY_H
#define TABLEWIDGETFACTORY_H
#include "../procedure/proceduredata.h"
#include <QDateTime>
#include <QMap>
#include <QObject>
#include <QString>
#include <QTableWidget>
#include <QVariant>
#include <QWidget>
/**
* @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 &timestamp,
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<QString, QWidget *> fieldWidgets; // fieldId -> widget (for form/series)
QTableWidget *gridWidget = nullptr; // for grid tables
QTableWidget *seriesWidget = nullptr; // for series tables
};
QMap<QWidget *, TableWidgetData> tableData;
};
#endif // TABLEWIDGETFACTORY_H

617
widgets/waveformwidget.cpp Normal file
View File

@@ -0,0 +1,617 @@
#include "waveformwidget.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGroupBox>
#include <QHeaderView>
#include <QScrollArea>
#include <QPainter>
#include <QDateTime>
#include <QFileDialog>
#include <QMessageBox>
#include <cmath>
#include <random>
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));
}
}

95
widgets/waveformwidget.h Normal file
View File

@@ -0,0 +1,95 @@
#ifndef WAVEFORMWIDGET_H
#define WAVEFORMWIDGET_H
#include <QWidget>
#include <QPushButton>
#include <QLabel>
#include <QComboBox>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <QTableWidget>
#include <QTimer>
#include <QVector>
#include <QCheckBox>
/**
* @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<double> m_ch1Data;
QVector<double> m_ch2Data;
int m_dataIndex;
};
#endif // WAVEFORMWIDGET_H

790
widgets/wirelesswidget.cpp Normal file
View File

@@ -0,0 +1,790 @@
#include "wirelesswidget.h"
#include "overlaydialogswidget.h"
#include <QScrollArea>
#include <QDebug>
// 样式常量定义
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");
}
}

101
widgets/wirelesswidget.h Normal file
View File

@@ -0,0 +1,101 @@
#ifndef WIRELESSWIDGET_H
#define WIRELESSWIDGET_H
#include <QWidget>
#include <QLabel>
#include <QPushButton>
#include <QLineEdit>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QComboBox>
#include <QListWidget>
#include <QProgressBar>
#include <QGroupBox>
#include <QStackedWidget>
#include <QTimer>
/**
* @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<QPushButton *> 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