#include "o3_JTR.h"
#include "DatabaseManager.h"
TemperatureGauge::TemperatureGauge(QWidget *parent)
:QWidget(parent)
{
QVBoxLayout* layout = new QVBoxLayout(this);
layout->setContentsMargins(2,2,2,2);
layout->setSpacing(2);
gauge = new QProgressBar();
tempLabel = new QLabel(this);
gauge->setRange(0,300);
gauge->setValue(0);
gauge->setOrientation(Qt::Vertical);
gauge->setTextVisible(false);
gauge->setStyleSheet("QProgressBar{border:2px solid #888;border-radius:5px;background:white;}" "QProgressBar::chunk{background:qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #4CAF50, stop:0.5 #FFC107, stop:1 #F44336);border-radius:3px;}");
tempLabel->setAlignment(Qt::AlignCenter);
tempLabel->setStyleSheet("font-size:12px;");
layout->addWidget(gauge,1);
layout->addWidget(tempLabel);
}
void TemperatureGauge::setTemperatureDirect(float temp)
{
gauge->setValue(temp);
tempLabel->setText(QString("%1°C").arg(temp));
gauge->setStyleSheet("QProgressBar::chunk{background:#c9ffa7;border-radius:3px;}");
}
void TemperatureGauge::setTemperature(float temp)
{
gauge->setValue(temp);
tempLabel->setText(QString("%1°C").arg(temp));
gauge->setStyleSheet("QProgressBar::chunk{background:#c9ffa7;border-radius:3px;}");
}
JTR::JTR(QWidget *parent)
:QWidget(parent),dbManager(DatabaseManager::instance())
{
setUpUI();
// 立即创建条形图
createBarChart();
//定时刷新数据
refreshTimer = new QTimer(this);
connect(refreshTimer, &QTimer::timeout, this, &JTR::refreshData);
refreshTimer->start(2000); //每2秒刷新一次
// //延迟2秒后开启动画
// QTimer::singleShot(2000, this, &JTR::startAnimation);
}
// void JTR::startAnimation()
// {
// if(animation)
// {
// movingRect->move(0, 10); //重置位置
// animation->start(); //开启动画
// }
// }
//显示温度,进去板子数量,出来板子数量
void JTR::setUpUI()
{
QHBoxLayout* mainLayout = new QHBoxLayout(this); // 改为水平布局
mainLayout->setContentsMargins(10, 10, 10, 10);
mainLayout->setSpacing(15);
//左侧区域 - 温度显示
QWidget* tempWidget = new QWidget();
createTemperatureDisplay(tempWidget); //修改为在tempWidget上创建温度显示
//右侧区域 - 日志显示
QWidget* logWidget = new QWidget();
createLogDisplay(logWidget);
//设置左右比例约为3:2
mainLayout->addWidget(tempWidget, 3);
mainLayout->addWidget(logWidget, 2);
this->setLayout(mainLayout);
//初始化出板数定时器 (5s刷新一次)
boardCountTimer = new QTimer(this);
connect(boardCountTimer, &QTimer::timeout, this, &JTR::refreshBoardCount);
boardCountTimer->start(5000); //5s
QTimer::singleShot(0, this, &JTR::refreshBoardCount); //立即刷新一次
}
void JTR::createLogDisplay(QWidget* parent)
{
QVBoxLayout* layout = new QVBoxLayout(parent);
layout->setContentsMargins(5, 5, 5, 5);
layout->setSpacing(10);
QLabel* titleLabel = new QLabel("操作日志");
titleLabel->setStyleSheet("font-weight: bold; font-size: 14px;");
layout->addWidget(titleLabel);
logDisplay = new QTextEdit();
logDisplay->setReadOnly(true);
logDisplay->setStyleSheet(
"font-family: 'Courier New';"
"font-size: 12px;"
"background-color: white;"
"border: 1px solid #ccc;"
);
layout->addWidget(logDisplay,1); //拉伸因子为1
//出板数显示区域
QHBoxLayout* boardCountLayout = new QHBoxLayout();
boardCountLabel = new QLabel("出板数:");
boardCountLabel->setStyleSheet("font-weight: bold; font-size: 14px;");
boardCountDisplay = new QTextEdit();
boardCountDisplay->setReadOnly(true);
boardCountDisplay->setFixedHeight(30);
boardCountDisplay->setStyleSheet(
"font-size: 14px;"
"background-color: white;"
"border: 1px solid #ccc;"
);
boardCountLayout->addWidget(boardCountLabel);
boardCountLayout->addWidget(boardCountDisplay, 1); //设置拉伸因子
layout->addLayout(boardCountLayout);
}
//获取当天日期字符串
QString JTR::getTodayDateString()
{
return QDateTime::currentDateTime().toString("yyyy-MM-dd");
}
void JTR::createBarChart()
{
QSqlDatabase db = dbManager.getDatabase();
QString today = getTodayDateString();
//获取今天的第一条数据的年月日
QString productionDateStr = "生产日期:" + today;
QDateTime startTime = QDateTime::fromString(today + " 07:00:00", "yyyy-MM-dd hh:mm:ss"); //固定为当天7:00
QDateTime endTime = QDateTime::fromString(today + " 23:00:00", "yyyy-MM-dd hh:mm:ss"); //固定为当天23:00
//查询保持不变
QString queryStr = QString(
"SELECT "
"CONCAT(LPAD(HOUR(record_time), 2, '0'), ':', "
"IF(MINUTE(record_time) < 30, '00', '30')) AS time_slot, "
"MAX(board_out_count) - MIN(board_out_count) + 1 AS board_count "
"FROM board_count "
"WHERE DATE(record_time) = '%1' "
"AND TIME(record_time) BETWEEN '07:00:00' AND '23:00:00' "
"GROUP BY HOUR(record_time), FLOOR(MINUTE(record_time)/30) "
"ORDER BY time_slot"
).arg(today);
QSqlQuery query(db);
if(!query.exec(queryStr))
{
qDebug() << "条形图查询失败:" << query.lastError().text();
// 即使查询失败也创建空图表,保证UI显示
createEmptyBarChart(productionDateStr);
return;
}
//创建时间槽列表(7:00-23:00,半小时一个槽)
QStringList timeSlots;
QMap<QString, int> timeSlotCounts;
//生成所有可能的时间槽并初始化为0
for(int hour = 7; hour <= 23; hour++)
{
timeSlots << QString("%1:00").arg(hour, 2, 10, QChar('0'));
timeSlotCounts[QString("%1:00").arg(hour, 2, 10, QChar('0'))] = 0;
if(hour < 23)
{ //23点不添加30分(避免超过23:00)
timeSlots << QString("%1:30").arg(hour, 2, 10, QChar('0'));
timeSlotCounts[QString("%1:30").arg(hour, 2, 10, QChar('0'))] = 0;
}
}
//填充查询结果
bool hasData = false;
while(query.next())
{
QString timeSlot = query.value("time_slot").toString();
int count = query.value("board_count").toInt();
timeSlotCounts[timeSlot] = count;
if(count > 0) hasData = true;
}
//如果没有数据,创建空图表
if(!hasData)
{
createEmptyBarChart(productionDateStr);
return;
}
//创建条形图
QChart* barChart = new QChart();
barChart->setAnimationOptions(QChart::SeriesAnimations);
barChart->legend()->hide();
//设置标题样式
QFont titleFont = barChart->titleFont();
titleFont.setPointSize(10);
titleFont.setBold(true);
barChart->setTitleFont(titleFont);
//设置标题内容
barChart->setTitle(QString("生产数量统计(半小时) - %1").arg(productionDateStr));
QBarSeries *series = new QBarSeries();
QBarSet *set = new QBarSet("生产数量");
set->setBrush(QBrush(QColor("#4CAF50")));
int maxCount = 0;
foreach(const QString &slot, timeSlots)
{
int count = timeSlotCounts[slot];
*set << count;
if(count > maxCount)
{
maxCount = count;
}
}
maxCount = ((maxCount / 10) + 1) * 10;
if(maxCount < 15) maxCount = 15;
series->append(set);
barChart->addSeries(series);
//===== 使用 QCategoryAxis =====
QCategoryAxis *axisX = new QCategoryAxis();
axisX->setMin(0);
axisX->setLabelsPosition(QCategoryAxis::AxisLabelsPositionOnValue);
//创建时间标签(7:00-23:00,每半小时一个刻度)
QStringList timeLabels;
QList<double> tickPositions;
for(int hour = 7; hour <= 23; hour++)
{
//整点标签和位置
timeLabels << QString("%1:00").arg(hour, 2, 10, QChar('0'));
tickPositions << (hour - 7) * 2; //转换为0-based索引(每半小时=1单位)
//半点标签和位置(23:00除外)
if(hour < 23)
{
timeLabels << QString("%1:30").arg(hour, 2, 10, QChar('0'));
tickPositions << (hour - 7) * 2 + 1;
}
}
//设置分类边界(关键修正:直接使用刻度位置)
for(int i = 0; i < timeLabels.size(); i++)
{
axisX->append(timeLabels[i], tickPositions[i] - 0.5);
}
//设置轴范围(覆盖所有时间点)
axisX->setRange(0, tickPositions.last());
//其他设置
axisX->setTitleText("时间");
axisX->setLabelsAngle(-90);
//设置Y轴(数量)
QValueAxis *axisY = new QValueAxis();
axisY->setRange(0, maxCount);
axisY->setTitleText("数量");
axisY->setTickCount((maxCount / 5) + 1);
axisY->setLabelFormat("%d");
//将轴添加到图表并将系列附加到轴
barChart->addAxis(axisX, Qt::AlignBottom);
barChart->addAxis(axisY, Qt::AlignLeft);
series->attachAxis(axisX);
series->attachAxis(axisY);
//在条的顶部添加值标签
series->setLabelsVisible(true);
series->setLabelsFormat("@value");
series->setLabelsPosition(QAbstractBarSeries::LabelsCenter);
//调整图表边距,确保标签显示完整
barChart->setMargins(QMargins(30, 10, 30, 30));
//设置条形宽度
series->setBarWidth(0.9); //适当调整条形宽度
//将图表应用于视图
if(barChartView->chart())
{
delete barChartView->chart();
}
barChartView->setChart(barChart);
barChartView->setRenderHint(QPainter::Antialiasing);
}
// 创建空条形图
void JTR::createEmptyBarChart(const QString& title)
{
QChart* barChart = new QChart();
barChart->setAnimationOptions(QChart::SeriesAnimations);
barChart->legend()->hide();
QFont titleFont = barChart->titleFont();
titleFont.setPointSize(10);
titleFont.setBold(true);
barChart->setTitleFont(titleFont);
barChart->setTitle(QString("生产数量统计(半小时) - %1").arg(title));
QBarSeries *series = new QBarSeries();
QBarSet *set = new QBarSet("生产数量");
set->setBrush(QBrush(QColor("#4CAF50")));
// 添加空数据
for(int i = 0; i < 32; i++) // 7:00-23:00共32个半小时段
{
*set << 0;
}
series->append(set);
barChart->addSeries(series);
// 创建X轴
QCategoryAxis *axisX = new QCategoryAxis();
axisX->setMin(0);
axisX->setLabelsPosition(QCategoryAxis::AxisLabelsPositionOnValue);
QStringList timeLabels;
QList<double> tickPositions;
for(int hour = 7; hour <= 23; hour++)
{
timeLabels << QString("%1:00").arg(hour, 2, 10, QChar('0'));
tickPositions << (hour - 7) * 2;
if(hour < 23)
{
timeLabels << QString("%1:30").arg(hour, 2, 10, QChar('0'));
tickPositions << (hour - 7) * 2 + 1;
}
}
for(int i = 0; i < timeLabels.size(); i++)
{
axisX->append(timeLabels[i], tickPositions[i] - 0.5);
}
axisX->setRange(0, tickPositions.last());
axisX->setTitleText("时间");
axisX->setLabelsAngle(-90);
// 创建Y轴
QValueAxis *axisY = new QValueAxis();
axisY->setRange(0, 10); // 默认范围0-10
axisY->setTitleText("数量");
axisY->setTickCount(3);
axisY->setLabelFormat("%d");
barChart->addAxis(axisX, Qt::AlignBottom);
barChart->addAxis(axisY, Qt::AlignLeft);
series->attachAxis(axisX);
series->attachAxis(axisY);
barChart->setMargins(QMargins(30, 10, 30, 30));
if(barChartView->chart())
{
delete barChartView->chart();
}
barChartView->setChart(barChart);
barChartView->setRenderHint(QPainter::Antialiasing);
}
void JTR::updateBarChart()
{
createBarChart(); // 直接调用现有的创建函数
}
//创建温区
void JTR::createTemperatureDisplay(QWidget* parent)
{
QVBoxLayout* vLayout = new QVBoxLayout(parent);
vLayout->setSpacing(15);
//创建上层温区
QHBoxLayout* upperHeaderLayout = new QHBoxLayout();
upperHeaderLayout->addSpacing(0); //增加间距对齐
for(int i = 1;i < 11;++i)
{
QLabel* numLabel = new QLabel(QString::number(i));
numLabel->setAlignment(Qt::AlignCenter);
numLabel->setFixedWidth(50);
upperHeaderLayout->addWidget(numLabel);
}
vLayout->addLayout(upperHeaderLayout,1);
//创建上层温区
QGroupBox* upGroup = new QGroupBox("上层温区");
QHBoxLayout* upLayout = new QHBoxLayout();
upLayout->setSpacing(15);
for(int i =0;i < 10;++i)
{
TemperatureGauge* gauge = new TemperatureGauge();
gauge->setFixedSize(60,150);
upLayout->addWidget(gauge);
upGauges.append(gauge);
}
upGroup->setLayout(upLayout);
vLayout->addWidget(upGroup,2);
// //创建动画区域
// animationWidget = new QWidget();
// animationWidget->setFixedHeight(30);
// animationWidget->setStyleSheet("background-color:white;");
// animLayout = new QHBoxLayout(animationWidget);
// animLayout->setContentsMargins(0,0,0,0);
// animLayout->setSpacing(0);
// //创建十个动画片段
// for(int i = 0;i < 10;++i)
// {
// QWidget* segment = new QWidget();
// segment->setStyleSheet("background-color:transparent;border-right:1px dashed #888");
// segment->setFixedWidth(60);
// animLayout->addWidget(segment);
// }
// //创建移动的矩形
// movingRect = new QWidget(animationWidget);
// movingRect->setStyleSheet("background-color: #4CAF50;");
// movingRect->setFixedSize(60, 10);
// movingRect->move(0, 10);
// //设置动画
// animation = new QPropertyAnimation(movingRect,"pos",this);
// animation->setDuration(300000); //通过时间
// animation->setStartValue(QPoint(0,10));
// animation->setEndValue(QPoint(1400, 10)); //间距
// animation->setEasingCurve(QEasingCurve::Linear);
// animation->setLoopCount(-1);
// vLayout->addWidget(animationWidget,1);
//创建下层温区
QGroupBox* lowerGroup = new QGroupBox("下层温区");
QHBoxLayout* lowerLayout = new QHBoxLayout();
lowerLayout->setSpacing(15);
for(int i = 0;i < 10;++i)
{
TemperatureGauge* gauge = new TemperatureGauge();
gauge->setFixedSize(60,150);
lowerLayout->addWidget(gauge);
lowGauges.append(gauge);
}
lowerGroup->setLayout(lowerLayout);
vLayout->addWidget(lowerGroup,2);
//添加条形图
QGroupBox* barChartGroup = new QGroupBox("生产数量统计(半小时)");
barChartGroup->setStyleSheet("font:600 12pt '宋体';");
QVBoxLayout* barChartLayout = new QVBoxLayout(barChartGroup);
//创建条形图
barChartView = new QChartView(this);
barChartView->setRenderHint(QPainter::Antialiasing);
barChartLayout->addWidget(barChartView);
vLayout->addWidget(barChartGroup,4);
//添加主布局
QVBoxLayout *mainLayout = qobject_cast<QVBoxLayout*>(this->layout());
if(mainLayout)
{
mainLayout->insertLayout(0,vLayout);
}
createBarChart();
}
void JTR::refreshData()
{
if(!DatabaseManager::instance().isConnected())
{
qDebug() << "数据库未连接";
return;
}
//刷新温度数据
refreshTemperatureData();
//刷新日志数据
refreshLogData();
}
void JTR::refreshLogData()
{
QSqlQuery query(DatabaseManager::instance().getDatabase());
bool querySuccess = query.exec("SELECT time, roleE, roleC, content FROM logs WHERE date(time) = CURRENT_DATE ORDER BY time ASC");
if(!querySuccess)
{
qDebug() << "日志查询失败:" << query.lastError().text();
return;
}
QString logText;
while(query.next())
{
QDateTime time = query.value(0).toDateTime();
QString roleE = query.value(1).toString();
QString roleC = query.value(2).toString();
QString content = query.value(3).toString();
//格式化日志行
logText += QString("%1\t%2\t%3\t%4\n")
.arg(time.toString("yyyy-MM-dd hh:mm:ss"))
.arg(roleE, -8) //左对齐,占8字符宽度
.arg(roleC, -6) //左对齐,占6字符宽度
.arg(content);
}
//只有当内容变化时才更新,避免频繁刷新
if(logDisplay->toPlainText() != logText)
{
logDisplay->setPlainText(logText);
logDisplay->moveCursor(QTextCursor::Start); //滚动到顶部
}
}
// void JTR::refreshTemperatureData()
// {
// //获取最新的20条记录
// QSqlQuery query(DatabaseManager::instance().getDatabase());
// bool querySuccess = query.exec("SELECT zone_name, zone_level, actual_value, record_time FROM temperature_data "
// "WHERE zone_level IN ('上层', '下层') "
// "ORDER BY record_time DESC LIMIT 20");
// if(!querySuccess)
// {
// qDebug() << "查询失败:" << query.lastError().text();
// return;
// }
// //初始化温度数组
// QVector<float> upTemps(10,0.0f);
// QVector<float> lowTemps(10,0.0f);
// bool needUpdate = false;
// //QDateTime latestTime;
// while(query.next())
// {
// QString zoneName = query.value(0).toString();
// QString zoneLevel = query.value(1).toString();
// float temp = query.value(2).toFloat();
// //解析温区位置和编号
// QRegularExpression re("(\\d+)");
// QRegularExpressionMatch match = re.match(zoneName);
// if(match.hasMatch())
// {
// int zoneNum = match.captured(1).toInt();
// if(zoneNum >= 1 && zoneNum <= 10)
// {
// if(zoneLevel == "上层")
// {
// if(upTemps[zoneNum - 1] == 0.0f) // 只取第一个有效值
// {
// upTemps[zoneNum - 1] = temp;
// // 检查是否需要更新
// if(lastUpTemps.size() > zoneNum - 1 &&
// qAbs(lastUpTemps[zoneNum - 1] - temp) > 0.1f)
// {
// needUpdate = true;
// }
// }
// }
// else if(zoneLevel == "下层")
// {
// if(lowTemps[zoneNum - 1] == 0.0f) // 只取第一个有效值
// {
// lowTemps[zoneNum - 1] = temp;
// // 检查是否需要更新
// if(lastLowTemps.size() > zoneNum - 1 &&
// qAbs(lastLowTemps[zoneNum - 1] - temp) > 0.1f)
// {
// needUpdate = true;
// }
// }
// }
// }
// }
// }
// // 如果没有变化且不是第一次更新,则不执行更新
// if(!needUpdate && !lastUpTemps.isEmpty())
// {
// return;
// }
// // 更新显示 - 使用直接设置方法避免闪烁
// for(int i = 0; i < upGauges.size() && i < upTemps.size(); ++i)
// {
// if(lastUpTemps.size() <= i || qAbs(lastUpTemps[i] - upTemps[i]) > 0.1f)
// {
// upGauges[i]->setTemperatureDirect(upTemps[i]);
// }
// }
// for(int i = 0; i < lowGauges.size() && i < lowTemps.size(); ++i)
// {
// if(lastLowTemps.size() <= i || qAbs(lastLowTemps[i] - lowTemps[i]) > 0.1f)
// {
// lowGauges[i]->setTemperatureDirect(lowTemps[i]);
// }
// }
// // 保存当前温度值
// lastUpTemps = upTemps;
// lastLowTemps = lowTemps;
// }
void JTR::refreshTemperatureData()
{
if(!DatabaseManager::instance().isConnected())
{
qDebug() << "数据库未连接";
return;
}
// 初始化温度数组,使用上一次的值或0
QVector<float> upTemps = lastUpTemps.isEmpty() ? QVector<float>(10, 0.0f) : lastUpTemps;
QVector<float> lowTemps = lastLowTemps.isEmpty() ? QVector<float>(10, 0.0f) : lastLowTemps;
bool upUpdated = false;
bool lowUpdated = false;
// 获取最新的温度数据(扩大查询范围确保能获取到所有温区数据)
QSqlQuery query(DatabaseManager::instance().getDatabase());
bool querySuccess = query.exec("SELECT zone_name, zone_level, actual_value, record_time FROM temperature_data "
"WHERE zone_level IN ('上层', '下层') "
"ORDER BY record_time DESC LIMIT 40"); // 增加查询数量
if(!querySuccess)
{
qDebug() << "温度查询失败:" << query.lastError().text();
return;
}
// 使用QSet记录已经处理过的温区,避免重复处理
QSet<QString> processedZones;
while(query.next())
{
QString zoneName = query.value(0).toString();
QString zoneLevel = query.value(1).toString();
float temp = query.value(2).toFloat();
QString zoneKey = zoneLevel + zoneName; // 创建唯一键
// 如果已经处理过这个温区,跳过
if(processedZones.contains(zoneKey))
continue;
processedZones.insert(zoneKey);
// 解析温区编号
QRegularExpression re("(\\d+)");
QRegularExpressionMatch match = re.match(zoneName);
if(!match.hasMatch())
continue;
int zoneNum = match.captured(1).toInt();
if(zoneNum < 1 || zoneNum > 10)
continue;
if(zoneLevel == "上层")
{
if(upTemps[zoneNum - 1] != temp)
{
upTemps[zoneNum - 1] = temp;
upUpdated = true;
}
}
else if(zoneLevel == "下层")
{
if(lowTemps[zoneNum - 1] != temp)
{
lowTemps[zoneNum - 1] = temp;
lowUpdated = true;
}
}
}
// 更新上层温区显示
if(upUpdated || lastUpTemps.isEmpty())
{
for(int i = 0; i < qMin(upGauges.size(), upTemps.size()); ++i)
{
upGauges[i]->setTemperatureDirect(upTemps[i]);
}
}
// 更新下层温区显示
if(lowUpdated || lastLowTemps.isEmpty())
{
for(int i = 0; i < qMin(lowGauges.size(), lowTemps.size()); ++i)
{
lowGauges[i]->setTemperatureDirect(lowTemps[i]);
}
}
// 保存当前温度值
lastUpTemps = upTemps;
lastLowTemps = lowTemps;
qDebug() << "温度更新完成 - 上层:" << upTemps << "下层:" << lowTemps;
}
void JTR::refreshBoardCount()
{
if(!DatabaseManager::instance().isConnected())
{
qDebug() << "数据库未连接";
return;
}
QSqlQuery query(DatabaseManager::instance().getDatabase());
bool querySuccess = query.exec(
"SELECT board_out_count, record_time FROM board_count "
"ORDER BY record_time DESC LIMIT 1"
);
if(!querySuccess)
{
qDebug() << "出板数查询失败:" << query.lastError().text();
return;
}
if(query.next())
{
int count = query.value(0).toInt();
QDateTime recordTime = query.value(1).toDateTime();
// 只更新比当前显示更新的数据
if(!lastBoardCountTime.isValid() || recordTime > lastBoardCountTime)
{
boardCountDisplay->setPlainText(QString::number(count));
lastBoardCountTime = recordTime;
// 出板数更新后也更新条形图
updateBarChart();
qDebug() << "更新出板数显示:" << count << "时间:" << recordTime.toString();
}
}else
{
boardCountDisplay->setPlainText("0");
}
}
现在这个更新条形图的时候会闪烁更新,可能是因为每次更新会把原来的图像清除掉,然后显示新的数据图像,但是我需要不是这样的,我需要的是在原来的图像的基础上变化为新的图像,这样看起来比较流畅