first commit
This commit is contained in:
283
widgets/appicon.cpp
Normal file
283
widgets/appicon.cpp
Normal 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();
|
||||
// 保留基本ASCII(32-126)、中文字符(0x4E00-0x9FFF)、以及一些常用符号
|
||||
if ((unicode >= 32 && unicode <= 126) ||
|
||||
(unicode >= 0x4E00 && unicode <= 0x9FFF) ||
|
||||
ch == QChar(0xB1) || // ±
|
||||
ch == QChar(0x394) || // Δ
|
||||
ch == QChar(0x3A9))
|
||||
{ // Ω
|
||||
filteredBadge += ch;
|
||||
}
|
||||
}
|
||||
|
||||
if (!filteredBadge.isEmpty())
|
||||
{
|
||||
m_badgeLabel = new QLabel(iconContainer);
|
||||
m_badgeLabel->setText(filteredBadge);
|
||||
m_badgeLabel->setFixedSize(26, 16);
|
||||
m_badgeLabel->setAlignment(Qt::AlignCenter);
|
||||
m_badgeLabel->move(iconSize - 28, iconSize - 18);
|
||||
m_badgeLabel->setStyleSheet(R"(
|
||||
QLabel {
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
color: #333;
|
||||
border-radius: 4px;
|
||||
font-size: 9px;
|
||||
font-weight: bold;
|
||||
}
|
||||
)");
|
||||
}
|
||||
}
|
||||
|
||||
layout->addWidget(iconContainer, 0, Qt::AlignCenter);
|
||||
|
||||
// 名称标签 - Android风格白色文字带阴影
|
||||
m_nameLabel = new QLabel(m_data.name, this);
|
||||
m_nameLabel->setAlignment(Qt::AlignCenter);
|
||||
m_nameLabel->setFixedWidth(150);
|
||||
m_nameLabel->setWordWrap(false);
|
||||
m_nameLabel->setStyleSheet(R"(
|
||||
QLabel {
|
||||
color: white;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
background: transparent;
|
||||
}
|
||||
)");
|
||||
layout->addWidget(m_nameLabel, 0, Qt::AlignCenter);
|
||||
}
|
||||
|
||||
QString AppIcon::getIconSymbol(const QString &id) const
|
||||
{
|
||||
// 返回对应的SVG图标路径
|
||||
static QMap<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
74
widgets/appicon.h
Normal 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
|
||||
252
widgets/channelstatuswidget.cpp
Normal file
252
widgets/channelstatuswidget.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
98
widgets/channelstatuswidget.h
Normal file
98
widgets/channelstatuswidget.h
Normal 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
|
||||
1074
widgets/datamanagementwidget.cpp
Normal file
1074
widgets/datamanagementwidget.cpp
Normal file
File diff suppressed because it is too large
Load Diff
94
widgets/datamanagementwidget.h
Normal file
94
widgets/datamanagementwidget.h
Normal 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
|
||||
172
widgets/devicestatuswidget.cpp
Normal file
172
widgets/devicestatuswidget.cpp
Normal 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);
|
||||
}
|
||||
40
widgets/devicestatuswidget.h
Normal file
40
widgets/devicestatuswidget.h
Normal 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
142
widgets/dockbar.cpp
Normal 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
34
widgets/dockbar.h
Normal 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
|
||||
533
widgets/dualchannelwidget.cpp
Normal file
533
widgets/dualchannelwidget.cpp
Normal 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()]));
|
||||
}
|
||||
}
|
||||
78
widgets/dualchannelwidget.h
Normal file
78
widgets/dualchannelwidget.h
Normal 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
207
widgets/launcherpage.cpp
Normal 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
63
widgets/launcherpage.h
Normal 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
|
||||
470
widgets/networktestwidget.cpp
Normal file
470
widgets/networktestwidget.cpp
Normal 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");
|
||||
}
|
||||
72
widgets/networktestwidget.h
Normal file
72
widgets/networktestwidget.h
Normal 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
|
||||
322
widgets/overlaydialogswidget.cpp
Normal file
322
widgets/overlaydialogswidget.cpp
Normal 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();
|
||||
}
|
||||
81
widgets/overlaydialogswidget.h
Normal file
81
widgets/overlaydialogswidget.h
Normal 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
116
widgets/pageindicator.cpp
Normal 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
37
widgets/pageindicator.h
Normal 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
|
||||
224
widgets/procedurelistwidget.cpp
Normal file
224
widgets/procedurelistwidget.cpp
Normal 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);
|
||||
}
|
||||
61
widgets/procedurelistwidget.h
Normal file
61
widgets/procedurelistwidget.h
Normal 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
|
||||
697
widgets/procedureplayerwidget.cpp
Normal file
697
widgets/procedureplayerwidget.cpp
Normal 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();
|
||||
}
|
||||
158
widgets/procedureplayerwidget.h
Normal file
158
widgets/procedureplayerwidget.h
Normal 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
583
widgets/settingswidget.cpp
Normal 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
87
widgets/settingswidget.h
Normal 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
|
||||
1760
widgets/signalmeasurementwidget.cpp
Normal file
1760
widgets/signalmeasurementwidget.cpp
Normal file
File diff suppressed because it is too large
Load Diff
483
widgets/signalmeasurementwidget.h
Normal file
483
widgets/signalmeasurementwidget.h
Normal 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
|
||||
618
widgets/signaltrimwidget.cpp
Normal file
618
widgets/signaltrimwidget.cpp
Normal 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({"Ω", "kΩ", "MΩ"});
|
||||
m_coarseUnitLabel->setText("Ω");
|
||||
m_minValue = 0;
|
||||
m_maxValue = 10000;
|
||||
m_decimals = 2;
|
||||
break;
|
||||
case 4: // 热电偶
|
||||
case 5: // 热电阻
|
||||
m_unitCombo->addItems({"°C"});
|
||||
m_coarseUnitLabel->setText("°C");
|
||||
m_minValue = -200;
|
||||
m_maxValue = 1800;
|
||||
m_decimals = 2;
|
||||
break;
|
||||
case 6: // Hz
|
||||
m_unitCombo->addItems({"Hz", "kHz", "MHz"});
|
||||
m_coarseUnitLabel->setText("Hz");
|
||||
m_minValue = 0;
|
||||
m_maxValue = 100000;
|
||||
m_decimals = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
m_coarseValueSpin->setRange(m_minValue, m_maxValue);
|
||||
m_coarseValueSpin->setDecimals(m_decimals);
|
||||
updateTotalValue();
|
||||
}
|
||||
|
||||
void SignalTrimWidget::onCoarseValueChanged(double value)
|
||||
{
|
||||
Q_UNUSED(value)
|
||||
updateTotalValue();
|
||||
}
|
||||
|
||||
void SignalTrimWidget::onFineValueChanged(double value)
|
||||
{
|
||||
// 更新滑块位置
|
||||
m_fineSlider->blockSignals(true);
|
||||
double step = m_stepSizeCombo->currentText().toDouble();
|
||||
m_fineSlider->setValue(static_cast<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);
|
||||
}
|
||||
81
widgets/signaltrimwidget.h
Normal file
81
widgets/signaltrimwidget.h
Normal 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
156
widgets/statusbar.cpp
Normal 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
41
widgets/statusbar.h
Normal 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
|
||||
361
widgets/tableoverlaywidget.cpp
Normal file
361
widgets/tableoverlaywidget.cpp
Normal 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);
|
||||
}
|
||||
111
widgets/tableoverlaywidget.h
Normal file
111
widgets/tableoverlaywidget.h
Normal 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
|
||||
1407
widgets/tablewidgetfactory.cpp
Normal file
1407
widgets/tablewidgetfactory.cpp
Normal file
File diff suppressed because it is too large
Load Diff
200
widgets/tablewidgetfactory.h
Normal file
200
widgets/tablewidgetfactory.h
Normal 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 ×tamp,
|
||||
const QVariantMap &fieldValues);
|
||||
|
||||
/**
|
||||
* @brief 清除時序數據
|
||||
* @param tableWidget 表格組件
|
||||
*/
|
||||
void clearSeriesData(QWidget *tableWidget);
|
||||
|
||||
// =====================================================
|
||||
// 驗證與控制
|
||||
// =====================================================
|
||||
|
||||
/**
|
||||
* @brief 驗證字段
|
||||
* @param tableWidget 表格組件
|
||||
* @param fieldId 字段ID
|
||||
* @return 是否有效
|
||||
*/
|
||||
bool validateField(QWidget *tableWidget, const QString &fieldId) const;
|
||||
|
||||
/**
|
||||
* @brief 啟用/禁用手動輸入
|
||||
* @param tableWidget 表格組件
|
||||
* @param enabled 是否啟用
|
||||
*/
|
||||
void enableManualInput(QWidget *tableWidget, bool enabled);
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief 字段值變更信號(Form 表格)
|
||||
*/
|
||||
void fieldValueChanged(QWidget *tableWidget, const QString &fieldId,
|
||||
const QVariant &value);
|
||||
|
||||
/**
|
||||
* @brief 單元格值變更信號(Grid 表格)
|
||||
*/
|
||||
void cellValueChanged(QWidget *tableWidget, const QString &rowId,
|
||||
const QString &columnId, const QVariant &value);
|
||||
|
||||
private:
|
||||
// =====================================================
|
||||
// 表格創建方法
|
||||
// =====================================================
|
||||
|
||||
QWidget *createGridTable(const TableDefinition &tableDef);
|
||||
QWidget *createFormTable(const TableDefinition &tableDef);
|
||||
QWidget *createSeriesTable(const TableDefinition &tableDef);
|
||||
|
||||
// =====================================================
|
||||
// 字段組件創建方法
|
||||
// =====================================================
|
||||
|
||||
QWidget *createFieldWidget(const FieldDefinition &fieldDef, QWidget *parent);
|
||||
QWidget *createNumericField(const FieldDefinition &fieldDef, QWidget *parent);
|
||||
QWidget *createTextField(const FieldDefinition &fieldDef, QWidget *parent);
|
||||
QWidget *createSelectionField(const FieldDefinition &fieldDef, QWidget *parent);
|
||||
QWidget *createBooleanField(const FieldDefinition &fieldDef, QWidget *parent);
|
||||
QWidget *createDateTimeField(const FieldDefinition &fieldDef, QWidget *parent);
|
||||
QWidget *createCalculatedField(const FieldDefinition &fieldDef, QWidget *parent);
|
||||
|
||||
// =====================================================
|
||||
// 輔助方法
|
||||
// =====================================================
|
||||
|
||||
void connectFieldSignals(QWidget *fieldWidget, const QString &fieldId,
|
||||
QWidget *tableWidget);
|
||||
void applyStaticCells(QWidget *tableWidget, const TableDefinition &tableDef);
|
||||
|
||||
// =====================================================
|
||||
// 內部數據結構
|
||||
// =====================================================
|
||||
|
||||
struct TableWidgetData
|
||||
{
|
||||
TableDefinition definition;
|
||||
QMap<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
617
widgets/waveformwidget.cpp
Normal 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
95
widgets/waveformwidget.h
Normal 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
790
widgets/wirelesswidget.cpp
Normal 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
101
widgets/wirelesswidget.h
Normal 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
|
||||
Reference in New Issue
Block a user