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

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