feat:初始化wuhan_QT界面代码

This commit is contained in:
xu
2026-09-09 17:00:37 +08:00
commit 40473ad10b
537 changed files with 23934 additions and 0 deletions
+35
View File
@@ -0,0 +1,35 @@
#include "alarmbanner.h"
#include <QHBoxLayout>
#include <QLabel>
#include "global.h"
AlarmBanner::AlarmBanner(QWidget* parent)
: QWidget(parent)
{
setFixedHeight(54);
setStyleSheet("background: transparent;");
QHBoxLayout* lay = new QHBoxLayout(this);
lay->setContentsMargins(0, 0, 0, 0);
m_label = new QLabel(this);
m_label->setAlignment(Qt::AlignCenter);
m_label->setProperty("alert", true);
m_label->setStyleSheet("font-size:30px;font-weight:700;");
lay->addWidget(m_label);
hide(); // 默认隐藏
}
void AlarmBanner::showAlarm(const QString& text)
{
m_label->setText(text);
startBlink(m_label, "blink");
show();
}
void AlarmBanner::clear()
{
stopBlink(m_label, "blink");
hide();
}
+23
View File
@@ -0,0 +1,23 @@
#ifndef ALARMBANNER_H
#define ALARMBANNER_H
#include <QWidget>
class QLabel;
// ===================== 告警横幅 =====================
// 整行红色文字,500ms 闪烁;showAlarm 显示,clear 隐藏
class AlarmBanner : public QWidget
{
Q_OBJECT
public:
explicit AlarmBanner(QWidget* parent = nullptr);
void showAlarm(const QString& text);
void clear();
private:
QLabel* m_label = nullptr;
};
#endif // ALARMBANNER_H
+96
View File
@@ -0,0 +1,96 @@
#include "confirmdialog.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QTimer>
#include "global.h"
ConfirmDialog::ConfirmDialog(QWidget* parent)
: QDialog(parent)
{
setWindowFlags(Qt::FramelessWindowHint | Qt::Dialog);
setAttribute(Qt::WA_TranslucentBackground);
setModal(true);
if (parent) setGeometry(parent->rect());
setStyleSheet("background-color: rgba(0,0,0,102);");
QWidget* panel = new QWidget(this);
panel->setObjectName("ConfirmPanel");
panel->setFixedSize(560, 260);
panel->setStyleSheet(QString(
"QWidget#ConfirmPanel{background-color:%1;border:2px solid %2;border-radius:16px;}")
.arg(C::PANEL, C::BORDER));
QVBoxLayout* lay = new QVBoxLayout(panel);
lay->setContentsMargins(28, 24, 28, 24);
lay->setSpacing(16);
m_title = new QLabel("", panel);
m_title->setStyleSheet(QString("color:%1;font-size:28px;font-weight:700;").arg(C::TEXT));
m_title->setAlignment(Qt::AlignCenter);
lay->addWidget(m_title);
m_body = new QLabel("", panel);
m_body->setStyleSheet(QString("color:%1;font-size:22px;").arg(C::TEXT));
m_body->setAlignment(Qt::AlignCenter);
m_body->setWordWrap(true);
lay->addWidget(m_body, 1);
QHBoxLayout* btnLay = new QHBoxLayout();
btnLay->addStretch(1);
QPushButton* ok = new QPushButton("确认", panel);
ok->setProperty("variant", "primary");
ok->setFixedSize(160, 72);
connect(ok, &QPushButton::clicked, this, &ConfirmDialog::accept);
QPushButton* cancel = new QPushButton("取消", panel);
cancel->setFixedSize(160, 72);
connect(cancel, &QPushButton::clicked, this, &ConfirmDialog::reject);
btnLay->addWidget(ok);
btnLay->addWidget(cancel);
btnLay->addStretch(1);
lay->addLayout(btnLay);
QVBoxLayout* root = new QVBoxLayout(this);
root->setContentsMargins(0, 0, 0, 0);
root->addStretch(1);
root->addWidget(panel, 0, Qt::AlignHCenter);
root->addStretch(1);
}
void ConfirmDialog::setTitle(const QString& t) { m_title->setText(t); }
void ConfirmDialog::setText(const QString& t) { m_body->setText(t); }
bool ConfirmDialog::confirm(QWidget* parent, const QString& title, const QString& text)
{
ConfirmDialog dlg(parent);
dlg.setTitle(title);
dlg.setText(text);
return dlg.exec() == QDialog::Accepted;
}
void ConfirmDialog::toast(QWidget* parent, const QString& text, int ms)
{
QDialog* d = new QDialog(parent);
d->setWindowFlags(Qt::FramelessWindowHint | Qt::Tool);
d->setAttribute(Qt::WA_TranslucentBackground);
d->setModal(true);
if (parent) d->setGeometry(parent->rect());
d->setStyleSheet("background-color: rgba(0,0,0,80);");
QLabel* lbl = new QLabel(text, d);
lbl->setAlignment(Qt::AlignCenter);
lbl->setStyleSheet(QString(
"background-color:%1;color:%2;font-size:28px;font-weight:700;border:2px solid %3;border-radius:16px;padding:32px 48px;")
.arg(C::PANEL, C::SUCCESS, C::BORDER));
lbl->adjustSize();
QVBoxLayout* root = new QVBoxLayout(d);
root->addStretch(1);
root->addWidget(lbl, 0, Qt::AlignHCenter);
root->addStretch(1);
QTimer::singleShot(ms, d, &QDialog::accept);
d->exec();
d->deleteLater();
}
+30
View File
@@ -0,0 +1,30 @@
#ifndef CONFIRMDIALOG_H
#define CONFIRMDIALOG_H
#include <QDialog>
class QLabel;
// ===================== 确认弹窗 =====================
// 标题 + 正文 + 确认/取消;另提供 2s 自动消失的轻提示(toast)
class ConfirmDialog : public QDialog
{
Q_OBJECT
public:
explicit ConfirmDialog(QWidget* parent = nullptr);
void setTitle(const QString& t);
void setText(const QString& t);
// 模态确认,返回是否“确认”
static bool confirm(QWidget* parent, const QString& title, const QString& text);
// 轻提示:显示 text,ms 毫秒后自动消失(用于“修改成功”等)
static void toast(QWidget* parent, const QString& text, int ms = 2000);
private:
QLabel* m_title = nullptr;
QLabel* m_body = nullptr;
};
#endif // CONFIRMDIALOG_H
+91
View File
@@ -0,0 +1,91 @@
#include "datacard.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include "global.h"
#include <QStyle>
DataCard::DataCard(const QString& title, QWidget* parent)
: QWidget(parent)
{
setProperty("card", true);
QVBoxLayout* lay = new QVBoxLayout(this);
lay->setContentsMargins(16, 0, 16, 14);
lay->setSpacing(8);
// 标题栏(高 44px)
m_title = new QLabel(title, this);
m_title->setFixedHeight(44);
m_title->setProperty("cardTitle", true);
m_title->setStyleSheet(QString("color:%1;font-size:24px;font-weight:700;border-bottom:1px solid %2;")
.arg(C::TEXT, C::BORDER));
lay->addWidget(m_title);
// 字段行容器
lay->addStretch(1);
}
void DataCard::setTitle(const QString& t)
{
m_title->setText(t);
}
int DataCard::addField(const QString& label, const QString& value, bool big)
{
int index = m_values.size();
QHBoxLayout* row = new QHBoxLayout();
row->setSpacing(8);
QLabel* lab = new QLabel(label + ":", this);
lab->setProperty("sub", true);
lab->setStyleSheet(QString("color:%1;font-size:22px;").arg(C::TEXT_SUB));
QLabel* val = new QLabel(value, this);
val->setStyleSheet(QString("color:%1;%2").arg(C::TEXT,
big ? "font-size:64px;font-weight:700;" : "font-size:30px;font-weight:700;"));
row->addWidget(lab);
row->addWidget(val, 1);
// 插入到标题栏之后(stretch 之前)
QVBoxLayout* lay = qobject_cast<QVBoxLayout*>(layout());
lay->insertLayout(lay->count() - 1, row);
m_values.append(val);
m_labels.append(label);
return index;
}
void DataCard::setField(int index, const QString& value)
{
if (index < 0 || index >= m_values.size())
return;
m_values[index]->setText(value);
}
void DataCard::setAlarm(int index, bool on)
{
if (index < 0 || index >= m_values.size())
return;
QLabel* val = m_values[index];
if (on) {
val->setProperty("alert", true);
startBlink(val, "blink");
} else {
stopBlink(val, "blink");
val->setProperty("alert", false);
val->style()->unpolish(val);
val->style()->polish(val);
}
}
void DataCard::setHighlight(bool on)
{
if (on)
setStyleSheet(QString("QWidget[card=\"true\"]{background-color:%1;border:3px solid %2;border-radius:12px;}")
.arg(C::PANEL, C::PRIMARY));
else
setStyleSheet(QString());
}
+29
View File
@@ -0,0 +1,29 @@
#ifndef DATACARD_H
#define DATACARD_H
#include <QWidget>
#include <QVector>
class QLabel;
// ===================== 数据卡片 =====================
// 标题栏 + 若干“标签:值”行;支持指定字段红色闪烁告警
class DataCard : public QWidget
{
Q_OBJECT
public:
explicit DataCard(const QString& title, QWidget* parent = nullptr);
void setTitle(const QString& t);
int addField(const QString& label, const QString& value = "--", bool big = false);
void setField(int index, const QString& value);
void setAlarm(int index, bool on);
void setHighlight(bool on); // 主色描边突出
private:
QLabel* m_title = nullptr;
QVector<QLabel*> m_values;
QVector<QString> m_labels;
};
#endif // DATACARD_H
+118
View File
@@ -0,0 +1,118 @@
#include "faulttable.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QTableWidget>
#include <QHeaderView>
#include <QLabel>
#include <QPushButton>
#include "global.h"
FaultTable::FaultTable(QWidget* parent)
: QWidget(parent)
{
QVBoxLayout* lay = new QVBoxLayout(this);
lay->setContentsMargins(0, 0, 0, 0);
lay->setSpacing(10);
// 分页行(上一页 / 页码 / 下一页)
QHBoxLayout* nav = new QHBoxLayout();
nav->addStretch(1);
m_prev = new QPushButton("上一页", this);
m_prev->setFixedSize(160, 64);
connect(m_prev, &QPushButton::clicked, this, [this]() {
if (m_page > 0) { setPage(m_page - 1); emit pageChanged(m_page); }
});
m_pageLabel = new QLabel("1/1", this);
m_pageLabel->setAlignment(Qt::AlignCenter);
m_pageLabel->setStyleSheet(QString("color:%1;font-size:22px;").arg(C::TEXT));
m_pageLabel->setFixedWidth(140);
m_next = new QPushButton("下一页", this);
m_next->setFixedSize(160, 64);
connect(m_next, &QPushButton::clicked, this, [this]() {
if (m_page < pageCount() - 1) { setPage(m_page + 1); emit pageChanged(m_page); }
});
nav->addWidget(m_prev);
nav->addWidget(m_pageLabel);
nav->addWidget(m_next);
lay->addLayout(nav);
// 表格
m_table = new QTableWidget(this);
m_table->setColumnCount(3);
m_table->setHorizontalHeaderLabels(QStringList() << "序号" << "故障时间" << "故障名称");
m_table->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_table->setSelectionBehavior(QAbstractItemView::SelectRows);
m_table->setSelectionMode(QAbstractItemView::SingleSelection);
m_table->verticalHeader()->setVisible(false);
m_table->setAlternatingRowColors(true);
m_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
m_table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed);
m_table->setColumnWidth(0, 120);
m_table->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);
m_table->verticalHeader()->setDefaultSectionSize(100);
m_table->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
connect(m_table, &QTableWidget::cellClicked, this, [this](int row, int) {
int global = m_page * m_pageSize + row;
if (global >= 0 && global < m_data.size())
emit rowClicked(global);
});
lay->addWidget(m_table, 1);
refresh();
}
void FaultTable::setData(const QVector<FaultRecord>& data)
{
m_data = data;
if (m_page >= pageCount() && pageCount() > 0)
m_page = pageCount() - 1;
refresh();
}
void FaultTable::setPage(int p)
{
m_page = qBound(0, p, qMax(0, pageCount() - 1));
refresh();
}
int FaultTable::pageCount() const
{
if (m_data.isEmpty()) return 1;
return (m_data.size() + m_pageSize - 1) / m_pageSize;
}
void FaultTable::refresh()
{
m_table->setRowCount(0);
m_table->setRowCount(m_pageSize);
int start = m_page * m_pageSize;
for (int r = 0; r < m_pageSize; ++r) {
int idx = start + r;
bool has = idx < m_data.size();
for (int c = 0; c < 3; ++c) {
QTableWidgetItem* it = new QTableWidgetItem();
it->setTextAlignment(Qt::AlignCenter);
if (has) {
const FaultRecord& rec = m_data[idx];
if (c == 0) it->setText(QString::number(idx + 1));
else if (c == 1) it->setText(rec.time);
else it->setText(rec.desc);
}
m_table->setItem(r, c, it);
}
}
// 空数据显示提示
if (m_data.isEmpty()) {
m_table->clearSelection();
}
m_pageLabel->setText(QString("%1/%2").arg(m_page + 1).arg(pageCount()));
m_prev->setEnabled(m_page > 0);
m_next->setEnabled(m_page < pageCount() - 1);
}
+41
View File
@@ -0,0 +1,41 @@
#ifndef FAULTTABLE_H
#define FAULTTABLE_H
#include <QWidget>
#include <QVector>
#include "appcontext.h" // FaultRecord
class QTableWidget;
class QLabel;
class QPushButton;
// ===================== 故障分页表格 =====================
// 表头(序号/故障时间/故障名称)+ 数据行 + 上一页/下一页 + 页码指示,每页 6 行
class FaultTable : public QWidget
{
Q_OBJECT
public:
explicit FaultTable(QWidget* parent = nullptr);
void setData(const QVector<FaultRecord>& data);
void setPage(int p);
int page() const { return m_page; }
int pageCount() const;
signals:
void pageChanged(int page);
void rowClicked(int globalIndex);
private:
void refresh();
QTableWidget* m_table = nullptr;
QLabel* m_pageLabel = nullptr;
QPushButton* m_prev = nullptr;
QPushButton* m_next = nullptr;
QVector<FaultRecord> m_data;
int m_page = 0;
int m_pageSize = 6;
};
#endif // FAULTTABLE_H
+91
View File
@@ -0,0 +1,91 @@
#include "ledindicator.h"
#include <QPainter>
#include <QRadialGradient>
#include "global.h"
LedIndicator::LedIndicator(const QString& text, QWidget* parent)
: QWidget(parent), m_text(text)
{
setFixedHeight(52);
setAttribute(Qt::WA_TranslucentBackground);
}
void LedIndicator::setState(State s)
{
m_state = s;
bool needBlink = (s == RedBlink || s == YellowBlink);
if (needBlink && !m_blink) {
m_blink = new QTimer(this);
m_blink->setInterval(500);
connect(m_blink, &QTimer::timeout, this, [this]() {
m_on = !m_on;
update();
});
m_blink->start();
m_on = true;
} else if (!needBlink && m_blink) {
m_blink->stop();
delete m_blink;
m_blink = nullptr;
m_on = true;
}
update();
}
void LedIndicator::setText(const QString& t)
{
m_text = t;
update();
}
void LedIndicator::paintEvent(QPaintEvent*)
{
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
// 中心圆点颜色
QColor color = QColor(C::DISABLED); // 灰
switch (m_state) {
case Green: color = QColor(C::SUCCESS); break;
case Red: color = QColor(C::DANGER); break;
case Yellow: color = QColor(C::WARNING); break;
case RedBlink: color = QColor(C::DANGER); break;
case YellowBlink: color = QColor(C::WARNING); break;
default: color = QColor(C::DISABLED); break;
}
// 闪烁:40% 透明度
if ((m_state == RedBlink || m_state == YellowBlink) && !m_on)
color.setAlpha(102);
const QPointF c(m_radius + 8, height() / 2.0);
// 外发光
if (color.alpha() > 0) {
QRadialGradient glow(c, m_radius + 8);
QColor g = color; g.setAlpha(color.alpha() / 3);
QColor g0 = g; g0.setAlpha(0);
glow.setColorAt(0.0, g);
glow.setColorAt(1.0, g0);
p.setPen(Qt::NoPen);
p.setBrush(glow);
p.drawEllipse(c, m_radius + 8, m_radius + 8);
}
// 圆形主体(径向渐变)
QRadialGradient grad(c + QPointF(-m_radius / 3.0, -m_radius / 3.0), m_radius * 1.6);
QColor hi = color.lighter(130);
grad.setColorAt(0.0, hi);
grad.setColorAt(1.0, color);
p.setBrush(grad);
p.setPen(QPen(QColor(C::BORDER), 1));
p.drawEllipse(c, m_radius, m_radius);
// 文字
p.setPen(QColor(C::TEXT));
QFont f = p.font();
f.setPixelSize(24);
p.setFont(f);
const QRectF textRect(m_radius * 2 + 16, 0, width() - m_radius * 2 - 16, height());
p.drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, m_text);
}
+35
View File
@@ -0,0 +1,35 @@
#ifndef LEDINDICATOR_H
#define LEDINDICATOR_H
#include <QWidget>
#include <QTimer>
// ===================== 指示灯(自绘圆形渐变 + 外发光) =====================
// 状态:灰=未运行/关闭/正常,绿=运行/开启/到位,红=故障/液位低,
// 黄=检测中;红闪/黄闪按 500ms 周期闪烁(透明度 40%↔100%)。
class LedIndicator : public QWidget
{
Q_OBJECT
public:
enum State { Off, Green, Red, RedBlink, Yellow, YellowBlink };
explicit LedIndicator(const QString& text, QWidget* parent = nullptr);
void setState(State s);
void setText(const QString& t);
State state() const { return m_state; }
QSize sizeHint() const override { return QSize(140, 44); }
protected:
void paintEvent(QPaintEvent*) override;
private:
State m_state = Off;
QString m_text;
QTimer* m_blink = nullptr;
bool m_on = true;
int m_radius = 16; // 圆形指示点 32×32
};
#endif // LEDINDICATOR_H
+139
View File
@@ -0,0 +1,139 @@
#include "numpaddialog.h"
#include <QVBoxLayout>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QPushButton>
#include <QLabel>
#include "global.h"
NumPadDialog::NumPadDialog(QWidget* parent)
: QDialog(parent)
{
setWindowFlags(Qt::FramelessWindowHint | Qt::Dialog);
setAttribute(Qt::WA_TranslucentBackground);
setModal(true);
// 覆盖整个父窗口,作为半透明遮罩
if (parent) setGeometry(parent->rect());
setStyleSheet("background-color: rgba(0,0,0,102);");
// 面板(居中)
QWidget* panel = new QWidget(this);
panel->setObjectName("NumPadPanel");
panel->setFixedSize(960, 486);
panel->setStyleSheet(QString(
"QWidget#NumPadPanel{background-color:%1;border:2px solid %2;border-radius:16px;}")
.arg(C::PANEL, C::BORDER));
QVBoxLayout* panelLay = new QVBoxLayout(panel);
panelLay->setContentsMargins(24, 24, 24, 24);
panelLay->setSpacing(16);
// 输入回显
m_echo = new QLineEdit(panel);
m_echo->setReadOnly(true);
m_echo->setEchoMode(QLineEdit::Password);
m_echo->setAlignment(Qt::AlignCenter);
m_echo->setFixedHeight(64);
panelLay->addWidget(m_echo);
// 数字键区(1-9, 0, 退格, 清除)
QGridLayout* grid = new QGridLayout();
grid->setSpacing(10);
const QString digits[3][3] = {{"1","2","3"},{"4","5","6"},{"7","8","9"}};
for (int r = 0; r < 3; ++r)
for (int c = 0; c < 3; ++c) {
QPushButton* b = new QPushButton(digits[r][c], panel);
b->setProperty("numpad", true);
b->setFixedSize(130, 98);
connect(b, &QPushButton::clicked, this, [this, d = digits[r][c]](){ onDigit(d); });
grid->addWidget(b, r, c);
}
QPushButton* b0 = new QPushButton("0", panel);
b0->setProperty("numpad", true);
b0->setFixedSize(130, 98);
connect(b0, &QPushButton::clicked, this, [this](){ onDigit("0"); });
grid->addWidget(b0, 3, 0);
QPushButton* bBack = new QPushButton("退格", panel);
bBack->setProperty("numpad", true);
bBack->setStyleSheet("font-size:28px;background-color:#2D3947;color:#E6EDF3;border:1px solid #3D4A59;border-radius:12px;");
bBack->setFixedSize(130, 98);
connect(bBack, &QPushButton::clicked, this, &NumPadDialog::onBackspace);
grid->addWidget(bBack, 3, 1);
QPushButton* bClear = new QPushButton("清除", panel);
bClear->setProperty("numpad", true);
bClear->setStyleSheet("font-size:28px;background-color:#2D3947;color:#E6EDF3;border:1px solid #3D4A59;border-radius:12px;");
bClear->setFixedSize(130, 98);
connect(bClear, &QPushButton::clicked, this, &NumPadDialog::onClear);
grid->addWidget(bClear, 3, 2);
// 确认 / 取消
QPushButton* bOk = new QPushButton("确认", panel);
bOk->setProperty("variant", "primary");
bOk->setFixedSize(130, 98);
connect(bOk, &QPushButton::clicked, this, &NumPadDialog::accept);
QPushButton* bCancel = new QPushButton("取消", panel);
bCancel->setFixedSize(130, 98);
connect(bCancel, &QPushButton::clicked, this, &NumPadDialog::reject);
grid->addWidget(bOk, 3, 3);
grid->addWidget(bCancel, 3, 4);
grid->setColumnStretch(4, 0);
grid->setColumnStretch(5, 1);
panelLay->addLayout(grid);
panelLay->addStretch(1);
// 居中放置面板
QVBoxLayout* root = new QVBoxLayout(this);
root->setContentsMargins(0, 0, 0, 0);
root->addStretch(1);
root->addWidget(panel, 0, Qt::AlignHCenter);
root->addStretch(1);
refreshEcho();
}
void NumPadDialog::setValue(const QString& v)
{
m_value = v.left(m_maxLen);
refreshEcho();
}
void NumPadDialog::clear()
{
m_value.clear();
refreshEcho();
}
void NumPadDialog::onDigit(const QString& d)
{
if (m_value.size() >= m_maxLen)
return; // 超过最大位数忽略
m_value += d;
refreshEcho();
}
void NumPadDialog::onBackspace()
{
if (!m_value.isEmpty())
m_value.chop(1);
refreshEcho();
}
void NumPadDialog::onClear()
{
m_value.clear();
refreshEcho();
}
void NumPadDialog::refreshEcho()
{
m_echo->setText(m_value);
m_echo->setCursorPosition(m_value.size());
}
+34
View File
@@ -0,0 +1,34 @@
#ifndef NUMPADDIALOG_H
#define NUMPADDIALOG_H
#include <QDialog>
class QLineEdit;
// ===================== 数字键盘弹窗(模态) =====================
// 半透明遮罩 + 键盘面板:顶部输入回显,数字键 1-9、0,退格、清除,确认/取消
class NumPadDialog : public QDialog
{
Q_OBJECT
public:
explicit NumPadDialog(QWidget* parent = nullptr);
void setMaxLen(int n) { m_maxLen = n; }
void setValue(const QString& v);
QString value() const { return m_value; }
void clear();
private slots:
void onDigit(const QString& d);
void onBackspace();
void onClear();
private:
void refreshEcho();
QLineEdit* m_echo = nullptr;
QString m_value;
int m_maxLen = 4;
};
#endif // NUMPADDIALOG_H
+107
View File
@@ -0,0 +1,107 @@
#include "paramcontrol.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#include "global.h"
ParamControl::ParamControl(const QString& name, const QString& unit,
double step, double min, double max,
bool withSave, bool withFactory, QWidget* parent)
: QWidget(parent), m_name(name), m_unit(unit),
m_step(step), m_min(min), m_max(max), m_value(min)
{
QVBoxLayout* lay = new QVBoxLayout(this);
lay->setContentsMargins(12, 8, 12, 8);
lay->setSpacing(6);
// 参数名
QLabel* nameLabel = new QLabel(name, this);
nameLabel->setProperty("sub", true);
nameLabel->setStyleSheet(QString("color:%1;font-size:22px;").arg(C::TEXT_SUB));
lay->addWidget(nameLabel);
// 数值 + 加减
QHBoxLayout* row = new QHBoxLayout();
row->setSpacing(12);
QPushButton* minus = new QPushButton("-", this);
minus->setFixedSize(96, 96);
minus->setStyleSheet(QString("font-size:44px;background-color:%1;color:%2;border:1px solid %3;border-radius:12px;")
.arg(C::PANEL_HI, C::TEXT, C::BORDER));
connect(minus, &QPushButton::clicked, this, &ParamControl::onMinus);
m_valueLabel = new QLabel(this);
m_valueLabel->setAlignment(Qt::AlignCenter);
m_valueLabel->setStyleSheet(QString("color:%1;font-size:52px;font-weight:700;").arg(C::TEXT));
m_valueLabel->setMinimumWidth(180);
QPushButton* plus = new QPushButton("+", this);
plus->setFixedSize(96, 96);
plus->setStyleSheet(QString("font-size:44px;background-color:%1;color:%2;border:1px solid %3;border-radius:12px;")
.arg(C::PANEL_HI, C::TEXT, C::BORDER));
connect(plus, &QPushButton::clicked, this, &ParamControl::onPlus);
row->addWidget(minus);
row->addWidget(m_valueLabel, 1);
row->addWidget(plus);
lay->addLayout(row);
// 保存 / 恢复出厂值(可选)
if (withSave || withFactory) {
QHBoxLayout* btnRow = new QHBoxLayout();
btnRow->setSpacing(12);
if (withSave) {
m_save = new QPushButton("保存", this);
m_save->setProperty("variant", "primary");
m_save->setMinimumHeight(56);
connect(m_save, &QPushButton::clicked, this, &ParamControl::saveClicked);
btnRow->addWidget(m_save);
}
if (withFactory) {
m_factory = new QPushButton("恢复出厂值", this);
m_factory->setMinimumHeight(56);
connect(m_factory, &QPushButton::clicked, this, &ParamControl::factoryResetClicked);
btnRow->addWidget(m_factory);
}
lay->addLayout(btnRow);
}
refresh();
}
void ParamControl::setValue(double v)
{
m_value = qBound(m_min, v, m_max);
refresh();
}
void ParamControl::setEnabled(bool enabled)
{
QWidget::setEnabled(enabled);
}
void ParamControl::onMinus()
{
m_value = qMax(m_min, m_value - m_step);
refresh();
emit valueChanged(m_value);
}
void ParamControl::onPlus()
{
m_value = qMin(m_max, m_value + m_step);
refresh();
emit valueChanged(m_value);
}
void ParamControl::refresh()
{
// 单位格式:整数不显示小数
QString s;
if (m_step < 1.0)
s = QString("%1").arg(m_value, 0, 'f', 1);
else
s = QString("%1").arg(m_value, 0, 'f', 0);
m_valueLabel->setText(s + " " + m_unit);
}
+46
View File
@@ -0,0 +1,46 @@
#ifndef PARAMCONTROL_H
#define PARAMCONTROL_H
#include <QWidget>
class QLabel;
class QPushButton;
// ===================== 参数调节控件 =====================
// 参数名 + 大数值显示 + 减/加按钮 + 保存按钮(可选)+ 恢复出厂值按钮(可选)
class ParamControl : public QWidget
{
Q_OBJECT
public:
explicit ParamControl(const QString& name, const QString& unit,
double step, double min, double max,
bool withSave, bool withFactory, QWidget* parent = nullptr);
void setValue(double v);
double value() const { return m_value; }
void setEnabled(bool enabled);
signals:
void valueChanged(double v);
void saveClicked();
void factoryResetClicked();
private slots:
void onMinus();
void onPlus();
private:
void refresh();
QString m_name;
QString m_unit;
double m_step;
double m_min;
double m_max;
double m_value;
QLabel* m_valueLabel = nullptr;
QPushButton* m_save = nullptr;
QPushButton* m_factory = nullptr;
};
#endif // PARAMCONTROL_H
+83
View File
@@ -0,0 +1,83 @@
#include "statusstrip.h"
#include "appcontext.h"
#include "udpcommunication.h"
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#include "global.h"
StatusStrip::StatusStrip(AppContext* ctx, QWidget* parent)
: QWidget(parent), m_ctx(ctx)
{
setObjectName("StatusStrip");
setFixedHeight(94);
setStyleSheet(QString("QWidget#StatusStrip{background-color:%1;border-top:1px solid %2;}")
.arg(C::PANEL, C::BORDER));
QHBoxLayout* lay = new QHBoxLayout(this);
lay->setContentsMargins(36, 0, 24, 0);
lay->setSpacing(48);
auto makeVal = [this](const QString& label) {
QLabel* l = new QLabel(this);
l->setStyleSheet(QString("color:%1;font-size:22px;").arg(C::TEXT_SUB));
l->setProperty("label", label);
return l;
};
m_battery = makeVal("底盘电量");
m_voltage = makeVal("底盘电压");
m_levelA = makeVal("清水液位");
m_levelFoam = makeVal("泡沫液位");
lay->addWidget(m_battery);
lay->addWidget(m_voltage);
lay->addWidget(m_levelA);
lay->addWidget(m_levelFoam);
lay->addStretch(1);
// 模式徽标(点击可切换行车/机械臂模式,模拟面板机械旋钮)
m_mode = new QPushButton("行车模式", this);
m_mode->setFixedSize(180, 48);
m_mode->setStyleSheet(QString("background-color:%1;color:#FFFFFF;border-radius:8px;font-size:24px;font-weight:700;border:none;")
.arg(C::SUCCESS));
m_mode->setCursor(Qt::PointingHandCursor);
connect(m_mode, &QPushButton::clicked, this, [this]() {
Mode next = (m_ctx->mode == Mode::Drive) ? Mode::Arm : Mode::Drive;
m_ctx->setMode(next);
});
lay->addWidget(m_mode);
// 返回按钮
m_back = new QPushButton("返回", this);
m_back->setFixedSize(144, 76);
connect(m_back, &QPushButton::clicked, this, &StatusStrip::backClicked);
lay->addWidget(m_back);
connect(m_ctx->comm(), &UdpCommunication::telemetryUpdated, this, &StatusStrip::refresh);
connect(m_ctx, &AppContext::modeChanged, this, [this](Mode m) {
m_mode->setText(m == Mode::Drive ? "行车模式" : "机械臂模式");
m_mode->setStyleSheet(QString("background-color:%1;color:#FFFFFF;border-radius:8px;font-size:24px;font-weight:700;")
.arg(m == Mode::Drive ? C::SUCCESS : C::WARNING));
});
refresh();
}
void StatusStrip::setBackVisible(bool visible)
{
m_back->setVisible(visible);
}
void StatusStrip::setBackEnabled(bool enabled)
{
m_back->setEnabled(enabled);
}
void StatusStrip::refresh()
{
const Telemetry& t = m_ctx->telemetry;
m_battery->setText(QString("底盘电量:%1%").arg(t.soc));
m_voltage->setText(QString("底盘电压:%1V").arg(t.volt, 0, 'f', 0));
m_levelA->setText(QString("清水液位:%1L").arg(t.levelA, 0, 'f', 0));
m_levelFoam->setText(QString("泡沫液位:%1L").arg(t.levelFoam, 0, 'f', 0));
}
+37
View File
@@ -0,0 +1,37 @@
#ifndef STATUSSTRIP_H
#define STATUSSTRIP_H
#include <QWidget>
class QLabel;
class QPushButton;
class AppContext;
// ===================== 底部状态条(高 94px) =====================
// 底盘电量、底盘电压、清水液位、泡沫液位 + 模式徽标 + 返回按钮
class StatusStrip : public QWidget
{
Q_OBJECT
public:
explicit StatusStrip(AppContext* ctx, QWidget* parent = nullptr);
void setBackVisible(bool visible);
void setBackEnabled(bool enabled);
signals:
void backClicked();
private slots:
void refresh();
private:
AppContext* m_ctx;
QLabel* m_battery = nullptr;
QLabel* m_voltage = nullptr;
QLabel* m_levelA = nullptr;
QLabel* m_levelFoam = nullptr;
QPushButton* m_mode = nullptr;
QPushButton* m_back = nullptr;
};
#endif // STATUSSTRIP_H
+99
View File
@@ -0,0 +1,99 @@
#include "titlebar.h"
#include "ledindicator.h"
#include "appcontext.h"
#include "udpcommunication.h"
#include "confirmdialog.h"
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QTime>
#include "global.h"
#include <QStyle>
TitleBar::TitleBar(AppContext* ctx, QWidget* parent)
: QWidget(parent), m_ctx(ctx)
{
setObjectName("TitleBar");
setFixedHeight(98);
setStyleSheet(QString("QWidget#TitleBar{background-color:%1;border-bottom:1px solid %2;}")
.arg(C::PANEL, C::BORDER));
QHBoxLayout* lay = new QHBoxLayout(this);
lay->setContentsMargins(24, 0, 24, 0);
lay->setSpacing(20);
// 通信状态
m_comm = new LedIndicator("通信正常", this);
m_comm->setState(LedIndicator::Green);
lay->addWidget(m_comm);
// 标题(居中)
m_title = new QLabel("", this);
m_title->setAlignment(Qt::AlignCenter);
m_title->setStyleSheet(QString("color:%1;font-size:40px;font-weight:700;").arg(C::TEXT));
lay->addWidget(m_title, 1);
// 时间
m_time = new QLabel("", this);
m_time->setStyleSheet(QString("color:%1;font-size:26px;").arg(C::TEXT_SUB));
m_time->setAlignment(Qt::AlignCenter);
lay->addWidget(m_time);
// 软急停按钮
m_estop = new QPushButton("急停", this);
m_estop->setProperty("variant", "estop");
m_estop->setFixedSize(130, 64);
connect(m_estop, &QPushButton::clicked, this, [this]() {
if (m_ctx->estopTriggered) {
if (ConfirmDialog::confirm(window(), "急停复位", "确定复位急停并恢复系统?"))
m_ctx->resetEstop();
} else {
m_ctx->triggerEstop();
}
});
lay->addWidget(m_estop);
// 通信状态刷新
connect(m_ctx->comm(), &UdpCommunication::commOkChanged, this, [this](bool ok) {
if (ok) { m_comm->setText("通信正常"); m_comm->setState(LedIndicator::Green); }
else { m_comm->setText("通信异常"); m_comm->setState(LedIndicator::RedBlink); }
});
// 急停状态刷新
connect(m_ctx, &AppContext::estopChanged, this, &TitleBar::updateEstop);
updateEstop();
// 时钟
m_clock = new QTimer(this);
connect(m_clock, &QTimer::timeout, this, &TitleBar::updateTime);
m_clock->start(1000);
updateTime();
}
void TitleBar::setTitle(const QString& title)
{
m_title->setText(title);
}
void TitleBar::setEstopVisible(bool visible)
{
m_estop->setVisible(visible);
}
void TitleBar::updateEstop()
{
if (m_ctx->estopTriggered) {
m_estop->setText("复位");
m_estop->setProperty("variant", "danger");
} else {
m_estop->setText("急停");
m_estop->setProperty("variant", "estop");
}
m_estop->style()->unpolish(m_estop);
m_estop->style()->polish(m_estop);
}
void TitleBar::updateTime()
{
m_time->setText(QTime::currentTime().toString("HH:mm"));
}
+36
View File
@@ -0,0 +1,36 @@
#ifndef TITLEBAR_H
#define TITLEBAR_H
#include <QWidget>
#include <QTimer>
class QLabel;
class QPushButton;
class AppContext;
class LedIndicator;
// ===================== 顶条组件(高 98px) =====================
// 左侧“通信状态:●正常/异常”,中央页面标题,右侧时间 + 软急停按钮
class TitleBar : public QWidget
{
Q_OBJECT
public:
explicit TitleBar(AppContext* ctx, QWidget* parent = nullptr);
void setTitle(const QString& title);
void setEstopVisible(bool visible);
private slots:
void updateEstop();
void updateTime();
private:
AppContext* m_ctx;
LedIndicator* m_comm;
QLabel* m_title = nullptr;
QLabel* m_time = nullptr;
QPushButton* m_estop = nullptr;
QTimer* m_clock = nullptr;
};
#endif // TITLEBAR_H