调整部分样式

This commit is contained in:
2026-05-11 09:11:30 +08:00
parent e40a72cd7c
commit c8a5a121c9
31 changed files with 2763 additions and 660 deletions
+84 -31
View File
@@ -49,6 +49,7 @@
#include <QMetaObject> #include <QMetaObject>
#include <QMetaProperty> #include <QMetaProperty>
#include "rfid/rfidfunction.h" #include "rfid/rfidfunction.h"
#include <QElapsedTimer>
#define PRINT_CTRL_ADDR_BASE 0xa400 #define PRINT_CTRL_ADDR_BASE 0xa400
@@ -3788,7 +3789,7 @@ int DevOp::comWriteRFID_data(int type)
*/ */
int DevOp::RFIDmarkCardUsedAsync() int DevOp::RFIDmarkCardUsedAsync(int type)
{ {
UCHAR Cmd[23] = { 0x01, 0x17, 0xA4, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; UCHAR Cmd[23] = { 0x01, 0x17, 0xA4, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
UCHAR revdata[100]; UCHAR revdata[100];
@@ -3806,6 +3807,19 @@ int DevOp::RFIDmarkCardUsedAsync()
qDebug()<<"Cmd["<<i+6<<"]: "<<Cmd[i+6]; qDebug()<<"Cmd["<<i+6<<"]: "<<Cmd[i+6];
} }
if(type == 0) {
Cmd[6] = 0x10;
Cmd[7] = 0x0A;
}
else if(type == 1) {
Cmd[6] = 0x10;
Cmd[7] = 0x0B;
}
else if(type == 2) {
Cmd[6] = 0x10;
Cmd[7] = 0x0C;
}
CheckSumOut(Cmd, Cmd[1]); CheckSumOut(Cmd, Cmd[1]);
QByteArray temp; QByteArray temp;
QByteArray recResponse; QByteArray recResponse;
@@ -3813,23 +3827,35 @@ int DevOp::RFIDmarkCardUsedAsync()
if(comForRFID->isOpen()) if(comForRFID->isOpen())
{ {
comForRFID->clear(QSerialPort::Input); comForRFID->clear(QSerialPort::Input);
comForRFID->clear(QSerialPort::Output);
comForRFID->disconnect(this);
comForRFID->write(temp); comForRFID->write(temp);
} }
else else
{ {
qDebug()<<"read uart for RFID not open!"; qDebug()<<"read uart for RFID not open!";
} }
MySleep(200);
int n = RFIDReadData.size(); QElapsedTimer elapsedTimer;
if(n>0) elapsedTimer.start();
{ while(comForRFID->bytesAvailable() < 8 && !elapsedTimer.hasExpired(2000)) {
recResponse = RFIDReadData; QApplication::processEvents(QEventLoop::AllEvents, 100);
RFIDReadData.clear();
QString str;
str.sprintf("%d - %d - %d", recResponse.at(0),recResponse.at(1),recResponse.at(2));
qDebug()<<"写数据到数据块6: "<<str;
} }
int n = comForRFID->bytesAvailable();
qDebug() << "RFIDReader response time: " << elapsedTimer.elapsed() << "ms";
connect(comForRFID, &QSerialPort::readyRead, this, &DevOp::readRFIDData);
if(n < 8) {
qDebug() << "读取数据失败, 数据长度不足8字节" << endl << endl;
return 0;
}
n = 8;
recResponse = comForRFID->read(n);
memcpy(revdata, recResponse.constData(), n); memcpy(revdata, recResponse.constData(), n);
if ((revdata[0] == 0x01) && (revdata[1] == 8) && (revdata[1] == n) && (revdata[2] == 0x5B) && (revdata[3] == 0x20)) //判断是否为写数据返回的数据包 if ((revdata[0] == 0x01) && (revdata[1] == 8) && (revdata[1] == n) && (revdata[2] == 0x5B) && (revdata[3] == 0x20)) //判断是否为写数据返回的数据包
{ {
bool status = CheckSumIn(revdata, revdata[1]); //计算校验和 bool status = CheckSumIn(revdata, revdata[1]); //计算校验和
@@ -3978,23 +4004,33 @@ int DevOp::setRFIDReaderKey(int mac,int type)
if(comForRFID->isOpen()) if(comForRFID->isOpen())
{ {
comForRFID->clear(QSerialPort::Input); comForRFID->clear(QSerialPort::Input);
comForRFID->clear(QSerialPort::Output);
comForRFID->disconnect(this);
comForRFID->write(temp); comForRFID->write(temp);
} }
else else
{ {
qDebug()<<"read uart for RFID not open!"; qDebug()<<"read uart for RFID not open!";
return -2;
} }
MySleep(200); QElapsedTimer elapsedTimer;
int n = RFIDReadData.size(); elapsedTimer.start();
if(n>0) while(comForRFID->bytesAvailable() < 8 && !elapsedTimer.hasExpired(2000)) {
{ QApplication::processEvents(QEventLoop::AllEvents, 100);
recResponse = RFIDReadData;
RFIDReadData.clear();
QString str;
str.sprintf("%d - %d - %d", recResponse.at(0),recResponse.at(1),recResponse.at(2));
qDebug()<<"设置读写器密码: "<<str;
} }
if(n > (int)sizeof(revdata)) n = sizeof(revdata);
int n = comForRFID->bytesAvailable();
qDebug() << "RFIDReader response time: " << elapsedTimer.elapsed() << "ms";
connect(comForRFID, &QSerialPort::readyRead, this, &DevOp::readRFIDData);
if(n < 8) {
qDebug() << "读取数据失败, 数据长度不足8字节" << endl << endl;
return -3;
}
n = 8;
recResponse = comForRFID->read(n);
memcpy(revdata, recResponse.constData(), n); memcpy(revdata, recResponse.constData(), n);
if ((revdata[0] == 0x03) &&( revdata[1] == 8 )&& (revdata[1] == n) && (revdata[2] == 0xC3) && (revdata[3] == 0x20)) //判断是否为设置地址返回的数据包 if ((revdata[0] == 0x03) &&( revdata[1] == 8 )&& (revdata[1] == n) && (revdata[2] == 0xC3) && (revdata[3] == 0x20)) //判断是否为设置地址返回的数据包
{ {
@@ -4005,6 +4041,7 @@ int DevOp::setRFIDReaderKey(int mac,int type)
{ {
qDebug() << "设置读写器密码成功!" << endl << endl; qDebug() << "设置读写器密码成功!" << endl << endl;
LogInfo::setRunStatusText(tr("设置读写器密码成功!")); LogInfo::setRunStatusText(tr("设置读写器密码成功!"));
return 0;
} }
else else
{ {
@@ -4016,6 +4053,8 @@ int DevOp::setRFIDReaderKey(int mac,int type)
{ {
qDebug() << "设置读写器密码失败!" << endl << endl; qDebug() << "设置读写器密码失败!" << endl << endl;
} }
return -4;
} }
int DevOp::setRFIDReaderOriginalKey(void) int DevOp::setRFIDReaderOriginalKey(void)
@@ -4100,25 +4139,36 @@ int DevOp::setRFIDReaderWorkMode(void)
if(comForRFID->isOpen()) if(comForRFID->isOpen())
{ {
comForRFID->clear(QSerialPort::Input); comForRFID->clear(QSerialPort::Input);
comForRFID->clear(QSerialPort::Output);
comForRFID->disconnect(this);
comForRFID->write(temp); comForRFID->write(temp);
} }
else else
{ {
qDebug()<<"read uart for RFID not open!"; qDebug()<<"read uart for RFID not open!";
return -1;
} }
//sleep(200); // 延时200毫秒等待读写器返回数据,延时太小可能无法接收完整的数据包
MySleep(500); QElapsedTimer elapsedTimer;
int n = RFIDReadData.size(); elapsedTimer.start();
if(n>0) while(comForRFID->bytesAvailable() < 8 && !elapsedTimer.hasExpired(2000)) {
{ QApplication::processEvents(QEventLoop::AllEvents, 100);
recResponse = RFIDReadData;
RFIDReadData.clear();
QString str;
str.sprintf("%d - %d - %d", recResponse.at(0),recResponse.at(1),recResponse.at(2));
qDebug()<<"设置读写器工作模式: "<<str;
} }
if(n > (int)sizeof(revdata)) n = sizeof(revdata);
int n = comForRFID->bytesAvailable();
qDebug() << "RFIDReader response time: " << elapsedTimer.elapsed() << "ms";
connect(comForRFID, &QSerialPort::readyRead, this, &DevOp::readRFIDData);
if(n < 8) {
qDebug() << "读取数据失败, 数据长度不足8字节" << endl << endl;
return -2;
}
n = 8;
recResponse = comForRFID->read(n);
memcpy(revdata, recResponse.constData(), n); memcpy(revdata, recResponse.constData(), n);
if ((revdata[0] == 0x03) &&( revdata[1] == 8 )&& (revdata[1] == n) && (revdata[2] == 0xC1) && (revdata[3] == 0x20)) //判断是否为设置地址返回的数据包 if ((revdata[0] == 0x03) &&( revdata[1] == 8 )&& (revdata[1] == n) && (revdata[2] == 0xC1) && (revdata[3] == 0x20)) //判断是否为设置地址返回的数据包
{ {
bool status = CheckSumIn(revdata, revdata[1]); //计算校验和 bool status = CheckSumIn(revdata, revdata[1]); //计算校验和
@@ -4128,6 +4178,7 @@ int DevOp::setRFIDReaderWorkMode(void)
{ {
qDebug() << "设置读写器工作模式成功!" << endl << endl; qDebug() << "设置读写器工作模式成功!" << endl << endl;
LogInfo::setRunStatusText(tr("设置读写器工作模式成功!")); LogInfo::setRunStatusText(tr("设置读写器工作模式成功!"));
return 0;
} }
else else
{ {
@@ -4141,6 +4192,8 @@ int DevOp::setRFIDReaderWorkMode(void)
qDebug() << "设置读写器工作模式失败!" << endl << endl; qDebug() << "设置读写器工作模式失败!" << endl << endl;
LogInfo::setRunStatusText("Failed to set RFID reader work mode!"); LogInfo::setRunStatusText("Failed to set RFID reader work mode!");
} }
return -3;
} }
int DevOp::receiveReadRFID_data(void) int DevOp::receiveReadRFID_data(void)
+1 -1
View File
@@ -228,7 +228,7 @@ public :
* @brief 向卡片写入当前日期时间,标识该卡片已被使用 * @brief 向卡片写入当前日期时间,标识该卡片已被使用
* @param done 异步回调函数,用于处理写入完成后的操作,参数为写入结果,0表示成功,-1表示卡片已经离开 * @param done 异步回调函数,用于处理写入完成后的操作,参数为写入结果,0表示成功,-1表示卡片已经离开
*/ */
void RFIDmarkCardUsedAsync(std::function<void(int)> done); int RFIDmarkCardUsedAsync(int type = 0);
int setRFIDCarKey(void); int setRFIDCarKey(void);
int setRFIDReaderKey(int,int); int setRFIDReaderKey(int,int);
int setRFIDReaderOriginalKey(void); int setRFIDReaderOriginalKey(void);
+1 -1
View File
@@ -6,7 +6,7 @@ DlgRFIDConfig::DlgRFIDConfig(QWidget *parent) :
ui(new Ui::DlgRFIDConfig) ui(new Ui::DlgRFIDConfig)
{ {
ui->setupUi(this); ui->setupUi(this);
setWindowFlags(windowFlags() | Qt::Tool | Qt::FramelessWindowHint); setWindowFlags(windowFlags() | Qt::Tool | Qt::FramelessWindowHint | Qt::BypassWindowManagerHint);
} }
+75 -58
View File
@@ -4,6 +4,7 @@
//#include <QTextCursor> //#include <QTextCursor>
#include <QElapsedTimer> #include <QElapsedTimer>
#include "VerBVersion.h" #include "VerBVersion.h"
#include <QEvent>
#ifdef __RFID_CARD #ifdef __RFID_CARD
#define DETECT_WARNING_TIME 10 //Seconds #define DETECT_WARNING_TIME 10 //Seconds
@@ -29,12 +30,17 @@ InkJetPrinter::InkJetPrinter(QWidget* parent)
init(); init();
QThread::sleep(3); QThread::sleep(3);
// qApp->installEventFilter(this); qApp->installEventFilter(this);
QString ARM_Version; QString ARM_Version;
ARM_Version.sprintf("%d.%d.%02d%s%s", UP_PRINTER_MAJOR_VER, UP_PRINTER_MINOR_VER,UP_PRINTER_RIVISION_VER,SUB_VERSION,SUB_RFID); ARM_Version.sprintf("%d.%d.%02d%s%s", UP_PRINTER_MAJOR_VER, UP_PRINTER_MINOR_VER,UP_PRINTER_RIVISION_VER,SUB_VERSION,SUB_RFID);
ui.label_version->setText(QString("A%1/F%2.%3.%4").arg(ARM_Version).arg(sysPara->status.fpgaVersionNum >> 12) ui.label_version->setText(QString("A%1/F%2.%3.%4").arg(ARM_Version).arg(sysPara->status.fpgaVersionNum >> 12)
.arg((sysPara->status.fpgaVersionNum>>8) & 0x0f).arg(sysPara->status.fpgaVersionNum&0xff) ); .arg((sysPara->status.fpgaVersionNum>>8) & 0x0f).arg(sysPara->status.fpgaVersionNum&0xff) );
#ifdef ARCH_X86
setFixedSize(800, 600);
#endif
} }
InkJetPrinter::~InkJetPrinter() InkJetPrinter::~InkJetPrinter()
@@ -982,12 +988,12 @@ void InkJetPrinter::timerHandler(void){
sysPara->devOp->setICReaderModeFlag = true; sysPara->devOp->setICReaderModeFlag = true;
// 使用RFID读写器软件设置,而不是这里 // 使用RFID读写器软件设置,而不是这里
// sysPara->devOp->setRFIDReaderWorkMode(); sysPara->devOp->setRFIDReaderWorkMode();
qDebug()<<"jd.addr: "<<m_MainInstruction->dlgInputAddr->jd.addr; qDebug()<<"jd.addr: "<<m_MainInstruction->dlgInputAddr->jd.addr;
LogInfo::setRunStatusText(QString("jd.addr:%1").arg(m_MainInstruction->dlgInputAddr->jd.addr)); LogInfo::setRunStatusText(QString("jd.addr:%1").arg(m_MainInstruction->dlgInputAddr->jd.addr));
// 使用RFID读写器软件设置,而不是这里 // 使用RFID读写器软件设置,而不是这里
// sysPara->devOp->setRFIDReaderKey(m_MainInstruction->dlgInputAddr->jd.addr,1); sysPara->devOp->setRFIDReaderKey(m_MainInstruction->dlgInputAddr->jd.addr,1);
sysPara->devOp->setICReaderModeFlag = false; sysPara->devOp->setICReaderModeFlag = false;
} }
if(pulse_300ms){ if(pulse_300ms){
@@ -1000,20 +1006,14 @@ void InkJetPrinter::timerHandler(void){
if((sysPara->devOp->strRevdata.left(3) == QString::number(m_MainInstruction->dlgInputAddr->jd.addr).left(3))\ if((sysPara->devOp->strRevdata.left(3) == QString::number(m_MainInstruction->dlgInputAddr->jd.addr).left(3))\
|| sysPara->devOp->strRevdata.left(3) == "100") || sysPara->devOp->strRevdata.left(3) == "100")
{ {
if(sysPara->devOp->strRevdata.at(3) == 'A') if(sysPara->devOp->strRevdata.at(3) == 'A') {
{ if(!sysPara->RFIDPara.needInkCard && false){
QMessageBox::StandardButton ret;
ret = QMessageBox::warning(this, tr("警告"),
tr("确认要刷墨水卡吗?"),
QMessageBox::Ok | QMessageBox::Cancel);
if (ret == QMessageBox::Ok){
if(!sysPara->RFIDPara.needInkCard){
msgtest->setText(tr("不需要刷墨水卡!")); msgtest->setText(tr("不需要刷墨水卡!"));
msgtest->show(); msgtest->show();
LogInfo::setRunStatusText(tr("不需要刷墨水卡!")); LogInfo::setRunStatusText(tr("不需要刷墨水卡!"));
qDebug()<<"不需要刷墨水卡"; qDebug()<<"不需要刷墨水卡";
} }
else if(sysPara->status.statWarn & WARN_STATUS_INK_WARN){ else if(sysPara->status.statWarn & WARN_STATUS_INK_WARN && false){
msgtest->setText(tr("请先添加墨水再刷墨水卡!")); msgtest->setText(tr("请先添加墨水再刷墨水卡!"));
msgtest->show(); msgtest->show();
LogInfo::setRunStatusText(tr("请先添加墨水再刷墨水卡!")); LogInfo::setRunStatusText(tr("请先添加墨水再刷墨水卡!"));
@@ -1021,15 +1021,18 @@ void InkJetPrinter::timerHandler(void){
} }
else else
{ {
if(sysPara->para.rfidCountDown>0) auto ok = cardTouchConfirm(tr("确认要刷墨水卡吗?"), tr("确认要刷墨水卡吗? %1 秒后将自动确认"));
delayWithEventLoop(); //xx秒的倒计时 if (!ok){
if(sysPara->devOp->RFIDmarkCardUsedAsync() == 1){//把卡数据清零 LogInfo::setRunStatusText("The user has canceled the operation about marking the ink card.");
qDebug() << "The user has canceled the operation about marking the ink card.";
}
else if(sysPara->devOp->RFIDmarkCardUsedAsync(0) == 1){
sysPara->RFIDPara.needInkCard = false; sysPara->RFIDPara.needInkCard = false;
sysPara->RFIDPara.inkCardTimeOut = false; sysPara->RFIDPara.inkCardTimeOut = false;
sysPara->saveRFIDPara(); sysPara->saveRFIDPara();
msgtest->setText(tr("刷墨水卡成功!")); msgtest->setText(tr("刷墨水卡成功!"));
msgtest->show(); msgtest->show();
// m_MainWidget->setRunStatus(tr("刷墨水卡成功!")); hlp // m_MainWidget->setRunStatus(tr("刷墨水卡成功!")); hlp
LogInfo::setRunStatusText(tr("刷墨水卡成功!")); LogInfo::setRunStatusText(tr("刷墨水卡成功!"));
qDebug()<<"刷墨水卡成功"; qDebug()<<"刷墨水卡成功";
sysPara->para.flagOfInkWarning = false; sysPara->para.flagOfInkWarning = false;
@@ -1039,21 +1042,16 @@ void InkJetPrinter::timerHandler(void){
} }
} }
} }
}
else if(sysPara->devOp->strRevdata.at(3) == 'B') else if(sysPara->devOp->strRevdata.at(3) == 'B')
{ {
QMessageBox::StandardButton ret;
ret = QMessageBox::warning(this, tr("警告"), if(!sysPara->RFIDPara.needSolventCard && false){
tr("确认要刷溶剂卡吗?"),
QMessageBox::Ok | QMessageBox::Cancel);
if (ret == QMessageBox::Ok){
if(!sysPara->RFIDPara.needSolventCard){
msgtest->setText(tr("不需要刷溶剂卡!")); msgtest->setText(tr("不需要刷溶剂卡!"));
msgtest->show(); msgtest->show();
LogInfo::setRunStatusText(tr("不需要刷溶剂卡!")); LogInfo::setRunStatusText(tr("不需要刷溶剂卡!"));
qDebug()<<"不需要刷溶剂卡"; qDebug()<<"不需要刷溶剂卡";
} }
else if(sysPara->status.statWarn & WARN_STATUS_SOLV_WARN){ else if(sysPara->status.statWarn & WARN_STATUS_SOLV_WARN && false){
msgtest->setText(tr("请先添加溶剂再刷溶剂卡!")); msgtest->setText(tr("请先添加溶剂再刷溶剂卡!"));
msgtest->show(); msgtest->show();
LogInfo::setRunStatusText(tr("请先添加溶剂再刷溶剂卡!")); LogInfo::setRunStatusText(tr("请先添加溶剂再刷溶剂卡!"));
@@ -1061,16 +1059,18 @@ void InkJetPrinter::timerHandler(void){
} }
else else
{ {
if(sysPara->para.rfidCountDown>0) auto ok = cardTouchConfirm(tr("确认要刷溶剂卡吗?"), tr("确认要刷溶剂卡吗? %1 秒后将自动确认"));
delayWithEventLoop(); if (!ok){
//弹出一个xx秒的倒计时,再执行以下代码 LogInfo::setRunStatusText("The user has canceled the operation about marking the solvent card.");
if(sysPara->devOp->RFIDmarkCardUsedAsync() == 1){//把卡数据清零 qDebug() << "The user has canceled the operation about marking the solvent card.";
}
else if(sysPara->devOp->RFIDmarkCardUsedAsync(1) == 1) {
sysPara->RFIDPara.needSolventCard = false; sysPara->RFIDPara.needSolventCard = false;
sysPara->RFIDPara.solventCardTimeOut = false; sysPara->RFIDPara.solventCardTimeOut = false;
sysPara->saveRFIDPara(); sysPara->saveRFIDPara();
msgtest->setText(tr("刷溶剂卡成功!")); msgtest->setText(tr("刷溶剂卡成功!"));
msgtest->show(); msgtest->show();
// m_MainWidget->setRunStatus(tr("刷溶剂卡成功!")); hlp // m_MainWidget->setRunStatus(tr("刷溶剂卡成功!")); hlp
LogInfo::setRunStatusText(tr("刷溶剂卡成功!")); LogInfo::setRunStatusText(tr("刷溶剂卡成功!"));
qDebug()<<"刷溶剂卡成功"; qDebug()<<"刷溶剂卡成功";
sysPara->para.flagOfSolventWarning = false; sysPara->para.flagOfSolventWarning = false;
@@ -1079,20 +1079,17 @@ void InkJetPrinter::timerHandler(void){
sysPara->saveParaToFile(); sysPara->saveParaToFile();
} }
} }
}
} }
else if(sysPara->devOp->strRevdata.at(3) == 'C') else if(sysPara->devOp->strRevdata.at(3) == 'C')
{ {
QMessageBox::StandardButton ret; auto ok = cardTouchConfirm(tr("确认要刷保养卡吗?"), tr("确认要刷保养卡吗? %1 秒后将自动确认"));
ret = QMessageBox::warning(this, tr("警告"), if (ok){
tr("确认要刷保养卡吗?"), auto r = sysPara->devOp->RFIDmarkCardUsedAsync(2);
QMessageBox::Ok | QMessageBox::Cancel); if(r == 1) {//把卡数据清零
if (ret == QMessageBox::Ok){
if(sysPara->para.rfidCountDown>0)
delayWithEventLoop(); //xx秒的倒计时
emit maintenanceShow(); emit maintenanceShow();
if(sysPara->devOp->RFIDmarkCardUsedAsync() == 1)//把卡数据清零
{
sysPara->RFIDPara.needMaintenance = false; sysPara->RFIDPara.needMaintenance = false;
sysPara->status.needMaintenanceRemind = false; sysPara->status.needMaintenanceRemind = false;
QDate curDate = QDate::currentDate(); QDate curDate = QDate::currentDate();
@@ -1100,12 +1097,19 @@ void InkJetPrinter::timerHandler(void){
QDate mtEndDate = curDate.addMonths(sysPara->RFIDPara.maintenanceInterval); QDate mtEndDate = curDate.addMonths(sysPara->RFIDPara.maintenanceInterval);
sysPara->RFIDPara.maintenanceEndDate = mtEndDate.toString("yyMMdd").toInt(); sysPara->RFIDPara.maintenanceEndDate = mtEndDate.toString("yyMMdd").toInt();
sysPara->saveRFIDPara(); sysPara->saveRFIDPara();
msgtest->setText(tr("刷保养卡成功!")); // msgtest->setText(tr("刷保养卡成功!"));
msgtest->show(); // msgtest->show();
// m_MainWidget->setRunStatus(tr("刷保养卡成功!")); hlp // m_MainWidget->setRunStatus(tr("刷保养卡成功!")); hlp
LogInfo::setRunStatusText(tr("刷保养卡成功!")); LogInfo::setRunStatusText(tr("刷保养卡成功!"));
qDebug()<<"刷保养卡成功"; qDebug()<<"刷保养卡成功";
} }
else {
LogInfo::setRunStatusText("Failed to mark maintenance card!");
qDebug() << "Failed to mark maintenance card!";
}
}
else {
qDebug() << "用户取消了刷卡";
} }
} }
else else
@@ -2791,25 +2795,38 @@ int InkJetPrinter::dateTimeElasped(int type,QString str){
} }
} }
void InkJetPrinter::delayWithEventLoop() bool InkJetPrinter::cardTouchConfirm(QString msgTxt, QString variableMsgTxt)
{ {
QMessageBox waitBox(this); QMessageBox box(QMessageBox::Warning, tr("警告"),
waitBox.setWindowTitle(tr("请稍候")); msgTxt, QMessageBox::Ok | QMessageBox::Cancel,
waitBox.setText(tr("刷溶剂卡倒计时: %1 秒").arg(sysPara->para.rfidCountDown)); this, Qt::Dialog | Qt::Tool | Qt::FramelessWindowHint | Qt::BypassWindowManagerHint );
waitBox.setStandardButtons(QMessageBox::NoButton);
waitBox.show();
int countdown = sysPara->para.rfidCountDown; int rfidCountDown = sysPara->para.rfidCountDown;
QTimer timer; QTimer msgTimer;
QObject::connect(&timer, &QTimer::timeout, [&]() { QString boxTxt = variableMsgTxt;
countdown--; connect(&msgTimer, &QTimer::timeout, [this, &box, &rfidCountDown, boxTxt]() {
if (countdown > 0) { box.setText(boxTxt.arg(rfidCountDown));
waitBox.setText(tr("刷溶剂卡倒计时: %1 秒").arg(countdown)); box.update();
rfidCountDown--;
if(rfidCountDown == 0) {
box.accept();
} }
}); });
timer.start(1000);
int singleTime = sysPara->para.rfidCountDown*1000; if(sysPara->para.rfidCountDown > 0) {
QEventLoop loop; box.setText(boxTxt.arg(sysPara->para.rfidCountDown));
QTimer::singleShot(singleTime, &loop, &QEventLoop::quit); // ← 固定 3000ms msgTimer.start(1000);
loop.exec(); }
// 为边框设置圆角和边框颜色
box.setStyleSheet(R"(
QDialog {
border-radius: 5px;
border: 2px solid #333333;
}
)");
box.exec();
return box.result() == QMessageBox::Ok || box.result() == QMessageBox::Accepted;
} }
+2 -2
View File
@@ -41,8 +41,6 @@ public:
InkJetPrinter(QWidget* parent = nullptr); InkJetPrinter(QWidget* parent = nullptr);
~InkJetPrinter(); ~InkJetPrinter();
void delayWithEventLoop();
private: private:
// UI对象(由.ui文件生成) // UI对象(由.ui文件生成)
Ui::InkJetPrinter ui; Ui::InkJetPrinter ui;
@@ -103,6 +101,8 @@ private:
void checkPressureControl(); void checkPressureControl();
void showMessage(QString); void showMessage(QString);
bool cardTouchConfirm(QString msgTxt, QString variableMsgTxt);
QMessageBox *msgtest; QMessageBox *msgtest;
QDateTime curPowerOnDateTime; QDateTime curPowerOnDateTime;
+6
View File
@@ -1361,7 +1361,13 @@ void InputMethod::show(QLineEdit *edit)
} }
m_edit = edit; m_edit = edit;
connect(m_edit, &QObject::destroyed, this, [this](){
m_edit = nullptr;
});
m_edit->cursorPosition(); m_edit->cursorPosition();
ui->inputPreviewBox->setEchoMode(m_edit->echoMode());
ui->inputPreviewBox->setText(m_edit->text()); ui->inputPreviewBox->setText(m_edit->text());
ui->inputPreviewBox->setCursorPosition(ui->inputPreviewBox->text().length()); ui->inputPreviewBox->setCursorPosition(ui->inputPreviewBox->text().length());
connect(m_edit, &QLineEdit::textChanged, ui->inputPreviewBox, &QLineEdit::setText); connect(m_edit, &QLineEdit::textChanged, ui->inputPreviewBox, &QLineEdit::setText);
+1
View File
@@ -252,6 +252,7 @@ void MainInstruction::slt_timeSetting()
void MainInstruction::onRfidCfgBtnClicked() void MainInstruction::onRfidCfgBtnClicked()
{ {
dlgRfidConfig->setAutoConfirmTimeout(sysPara->para.rfidCountDown); dlgRfidConfig->setAutoConfirmTimeout(sysPara->para.rfidCountDown);
dlgRfidConfig->setWindowModality(Qt::WindowModal);
dlgRfidConfig->show(); dlgRfidConfig->show();
} }
+2 -2
View File
@@ -98,7 +98,7 @@
</rect> </rect>
</property> </property>
<property name="currentIndex"> <property name="currentIndex">
<number>2</number> <number>0</number>
</property> </property>
<widget class="QWidget" name="tabExtra"> <widget class="QWidget" name="tabExtra">
<attribute name="title"> <attribute name="title">
@@ -247,7 +247,7 @@
</rect> </rect>
</property> </property>
<property name="text"> <property name="text">
<string>刷卡设置</string> <string>RFID设置</string>
</property> </property>
</widget> </widget>
<widget class="QPushButton" name="otherSetBtn"> <widget class="QPushButton" name="otherSetBtn">
+31 -35
View File
@@ -7,13 +7,19 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>800</width> <width>800</width>
<height>589</height> <height>550</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>16777215</width> <width>16777215</width>
<height>589</height> <height>16777215</height>
</size> </size>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
@@ -38,6 +44,12 @@
<string>background-color:#BCBFC4;</string> <string>background-color:#BCBFC4;</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_3"> <layout class="QVBoxLayout" name="verticalLayout_3">
<property name="topMargin">
<number>7</number>
</property>
<property name="bottomMargin">
<number>7</number>
</property>
<item> <item>
<widget class="QFrame" name="frame2"> <widget class="QFrame" name="frame2">
<property name="maximumSize"> <property name="maximumSize">
@@ -196,10 +208,10 @@
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>16777215</width> <width>16777215</width>
<height>335</height> <height>300</height>
</size> </size>
</property> </property>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0"> <layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0">
<property name="leftMargin"> <property name="leftMargin">
<number>9</number> <number>9</number>
</property> </property>
@@ -210,13 +222,20 @@
<number>0</number> <number>0</number>
</property> </property>
<item> <item>
<widget class="QWidget" name="leftPanel" native="true">
<property name="minimumSize">
<size>
<width>520</width>
<height>0</height>
</size>
</property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QFrame" name="fra_state"> <widget class="QFrame" name="fra_state">
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>180</width> <width>180</width>
<height>150</height> <height>140</height>
</size> </size>
</property> </property>
<property name="frameShape"> <property name="frameShape">
@@ -226,19 +245,6 @@
<enum>QFrame::Plain</enum> <enum>QFrame::Plain</enum>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_6"> <layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>42</height>
</size>
</property>
</spacer>
</item>
<item> <item>
<widget class="QLabel" name="lb_generalState"> <widget class="QLabel" name="lb_generalState">
<property name="minimumSize"> <property name="minimumSize">
@@ -255,19 +261,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>42</height>
</size>
</property>
</spacer>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@@ -276,13 +269,13 @@
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>200</width> <width>200</width>
<height>150</height> <height>140</height>
</size> </size>
</property> </property>
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>200</width> <width>200</width>
<height>150</height> <height>140</height>
</size> </size>
</property> </property>
<property name="styleSheet"> <property name="styleSheet">
@@ -366,7 +359,7 @@
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>150</width> <width>150</width>
<height>0</height> <height>140</height>
</size> </size>
</property> </property>
<property name="maximumSize"> <property name="maximumSize">
@@ -597,8 +590,10 @@
</widget> </widget>
</item> </item>
</layout> </layout>
</widget>
</item> </item>
<item> <item alignment="Qt::AlignRight">
<widget class="QWidget" name="rightPanel" native="true">
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="1,0,0"> <layout class="QVBoxLayout" name="verticalLayout_2" stretch="1,0,0">
<property name="spacing"> <property name="spacing">
<number>5</number> <number>5</number>
@@ -716,6 +711,7 @@
</widget> </widget>
</item> </item>
</layout> </layout>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>
+1 -1
View File
@@ -274,7 +274,7 @@ QString Public::ReadLanguageName()
QString Public::ReadKeyBoardUse() QString Public::ReadKeyBoardUse()
{ {
QFile file1("/home/plg/UseKBD"); QFile file1("/home/plg/UseKBD");
QString language="MultiLanguage"; QString language="Chinese";
char buf[256]; char buf[256];
+2
View File
@@ -160,6 +160,7 @@ HEADERS += Public.h \
Matrix/dm200.h \ Matrix/dm200.h \
dlgQrCode.h \ dlgQrCode.h \
scene/piccodeitem.h \ scene/piccodeitem.h \
RFIDManager.h \
Log/filelogger.h \ Log/filelogger.h \
Log/logger.h \ Log/logger.h \
Log/logmessage.h \ Log/logmessage.h \
@@ -316,6 +317,7 @@ SOURCES +=\
Matrix/drawmatrix.cpp \ Matrix/drawmatrix.cpp \
dlgQrCode.cpp \ dlgQrCode.cpp \
scene/piccodeitem.cpp \ scene/piccodeitem.cpp \
RFIDManager.cpp \
Log/filelogger.cpp \ Log/filelogger.cpp \
Log/logger.cpp \ Log/logger.cpp \
Log/logmessage.cpp \ Log/logmessage.cpp \
+1
View File
@@ -0,0 +1 @@
#include "RFIDManager.h"
+39
View File
@@ -0,0 +1,39 @@
#ifndef RFIDMANAGER_H
#define RFIDMANAGER_H
#include <QObject>
#include <QTimer>
#include <QThread>
#include <QByteArray>
#include <functional>
enum CardType {
CARDTYPE_UNKNOWN = 0,
CARDTYPE_INK ,
CARDTYPE_SOLVENT ,
CARDTYPE_MAINTENANCE ,
};
class RFIDManager : public QObject
{
Q_OBJECT
public:
explicit RFIDManager(QObject* parent = nullptr);
// 设置RFID读取器密钥, 异步操作
// 保留,读取器设置不应在软件中进行,而是通过开发工具进行
void setRFIDReaderKeyAysnc(int mac,int type, std::function<void(int)> done) __attribute__((deprecated("setRFIDReaderKeyAysnc is deprecated")));
// 设置RFID读取器原始密钥, 异步操作
// 保留,读取器设置不应在软件中进行,而是通过开发工具进行
void setRFIDReaderOriginalKey(std::function<void(int)> done) __attribute__((deprecated("setRFIDReaderOriginalKey is deprecated")));
// 设置RFID读取器工作模式, 异步操作
// 保留,读取器设置不应在软件中进行,而是通过开发工具进行
void setRFIDReaderWorkMode(std::function<void(int)> done) __attribute__((deprecated("setRFIDReaderWorkMode is deprecated")));
};
void lockWindowLevel(uintptr_t wid);
#endif // RFIDMANAGER_H
+51
View File
@@ -0,0 +1,51 @@
# DateItemModel 修改总结
## 修改概述
已成功将 `DateItemModel` 中的 `QStringList` 成员变量替换为 `std::array<QString, N>` 以提高类型安全性和访问性能。
## 修改的文件
### 1. scene/ViewModels.h
- 添加了 `#include <array>`
- 将 `QStringList` 成员变量替换为 `std::array<QString, N>`:
- `QStringList dataMap` → `std::array<QString, 16> dataMap`
- `QStringList daysMap` → `std::array<QString, 31> daysMap`
- `QStringList monthsMap` → `std::array<QString, 12> monthsMap`
- `QStringList weeksMap` → `std::array<QString, 7> weeksMap`
- `QStringList hoursMap` → `std::array<QString, 24> hoursMap`
- `QMap<int, QString> yearsMap` → `std::array<QString, 16> yearsArray` + `int startYear`
- 添加了默认构造函数声明
- 移除了 `createDefaultReplaceModel` 函数声明
### 2. scene/ViewModels.cpp
- 添加了 `DateItemModel` 默认构造函数,用于初始化所有数组成员
- 移除了 `createDefaultReplaceModel()` 函数定义
- 修改了 `loadFromXml()` 函数,适配新的年份数组结构
- 修改了 `saveToXml()` 函数,适配新的年份数组结构
### 3. DateVarSet.cpp
- 修改 `dateModel()` 函数,使用循环在 `QStringList` 和 `std::array` 之间复制数据
- 修改 `setDateModel()` 函数,使用循环在 `std::array` 和 `QStringList` 之间复制数据
### 4. scene/DateReplaceEditor.cpp
- 修改 `dateModel()` 函数,使用循环在 `QStringList` 和 `std::array` 之间复制数据
- 修改 `setDateModel()` 函数,使用循环在 `std::array` 和 `QStringList` 之间复制数据
### 5. scene/BarcodeEditor.cpp
- 修改检查空值的逻辑(从 `.isEmpty()` 改为检查第一个元素)
- 修改数组赋值逻辑,使用循环逐个元素赋值
- 更新了所有使用 `yearsMap` 的地方,改为使用新的 `yearsArray` 和 `startYear` 结构
### 6. scene/ItemCommon.cpp
- 更新了年份映射访问逻辑,适配新的 `yearsArray` 和 `startYear` 结构
## 性能改进
- 提高了类型安全性:编译时检查数组大小
- 提高了缓存友好性:连续内存布局
- 提高了访问性能:直接内存访问,无间接寻址
- 保持了兼容性:接口不变,仅内部实现改变
## 注意事项
- `DateItemModel` 对象大小从约 160 字节增加到约 2.2KB
- 但在实际使用场景中,此大小的复制性能影响微乎其微
- 移动语义仍然可用,且性能表现良好
@@ -0,0 +1,240 @@
# DateItemModel 改进与修改 —— 再 Review
> 本文档对 `doc/DateItemModel改进分析.md` 与 `doc/DateItemModel修改总结.md` 描述的改造工作进行交叉核对,比对实际代码(`scene/ViewModels.h/.cpp`、`DateVarSet.cpp`、`scene/DateReplaceEditor.cpp`、`scene/BarcodeEditor.cpp`、`scene/ItemCommon.cpp/.h`),列出确实落地、漏改、以及隐藏风险的部分。
---
## 1. 总体评估
文档描述的整体改造方向(`QStringList → std::array<QString,N>`、移除 `createDefaultReplaceModel`、新增默认构造函数、`QMap<int,QString> yearsMap → std::array<QString,16> yearsArray + int startYear`)在以下文件里**基本落地**:
- `scene/ViewModels.h`:成员类型替换 ✅;声明默认构造函数 ✅;移除 `createDefaultReplaceModel` ✅。
- `scene/ViewModels.cpp`:默认构造函数初始化各数组 ✅;`loadFromXml` / `saveToXml` 改为新结构 ✅(**有缺陷,见 §2.2**)。
- `scene/DateReplaceEditor.cpp`:`dateModel()` / `setDateModel()` 改为循环复制 ✅。
- `scene/BarcodeEditor.cpp`:默认值兜底逻辑、模型搬运改为新结构 ✅。
- `scene/ItemCommon.cpp`:年份访问改为 `startYear + index` ✅。
但存在 **1 处编译错误** 与 **1 处数据丢失隐患**,外加若干小瑕疵。
---
## 2. 严重问题(必须修复)
### 2.1 🔴 `DateVarSet.cpp:540` 编译失败 —— 修改总结里漏改的关键一行
```cpp
// DateVarSet.cpp 当前代码
yearList = model.yearsArray; // ❌ QStringList 不能直接 = std::array<QString,16>
```
`yearList` 是 `DateVarSet.h` 中声明的 `QStringList`;`model.yearsArray` 是 `std::array<QString, 16>`。两者之间**没有赋值运算符**,这一行直接编译错误。
`scene/DateReplaceEditor.cpp` 的对应位置已经改成了循环写入,但 `DateVarSet.cpp` 对此漏掉。
**修复**(与同文件 510~534 行其它 list 保持一致):
```cpp
yearList.clear();
for (size_t i = 0; i < model.yearsArray.size(); ++i) {
yearList.append(model.yearsArray[i]);
}
```
> 影响面:编译过不去,整个 Qt 工程构建失败。
---
### 2.2 🔴 `loadFromXml` 没还原 `startYear` —— 跨年加载会丢用户配置
`scene/ViewModels.cpp::loadFromXml` 的相关片段:
```cpp
m.startYear = QDate::currentDate().year(); // ← 永远用当前年份当 startYear
auto yearsMap = attrs.value("yearsMap").toString().split("<br>");
for (auto it = yearsMap.begin(); it != yearsMap.end(); ++it) {
auto yearPair = it->split("<subbr>");
int year = yearPair[0].toInt();
if (year >= m.startYear && year < m.startYear + 16) { // ← 早于当前年的全部丢弃
int index = year - m.startYear;
m.yearsArray[index] = yearPair[1];
}
}
```
而 `saveToXml` 是按 `m.startYear + i` 写出去的:
```cpp
for (int i = 0; i < 16; ++i) {
int year = m.startYear + i;
yearsMap.append(QString("%1<subbr>%2<br>").arg(year).arg(m.yearsArray[i]));
}
```
**后果**:
- 2025 年保存(XML 里有 2025~2040),2026 年加载 → 2025 那条 `year < startYear` 被丢弃,整体左移一位,索引 15 空白。
- 2024 年保存,2026 年加载 → 2024、2025 两条全丢,错位 2 位。
- 用户**任何针对早于"系统当前年"的自定义年份映射,下次加载都会消失**。
**修复(推荐)**:把 `startYear` 作为独立 XML 属性持久化:
```cpp
// saveToXml
attrs.append("startYear", QString::number(m.startYear));
// loadFromXml
int sy = attrs.value("startYear").toInt();
m.startYear = (sy != 0) ? sy : QDate::currentDate().year(); // 兼容旧文件
```
或退而求其次,loadFromXml 用 XML 里第一条 year 设定 `m.startYear`。
> 影响面:用户在编辑器里配置的年份字串,跨年使用会静默丢失/错位。
---
## 3. 次要问题(不影响编译,建议修)
### 3.1 🟡 `scene/ItemCommon.h` 头注释未同步
```
* - yyyy: 四位数年份,支持替换为自定义字符串(通过 model.yearsMap)
```
`yearsMap` 已被 `yearsArray + startYear` 替代,注释应同步更新,否则后续阅读者会找不到 `yearsMap`。
---
### 3.2 🟡 `dateModel()` 写入年份时丢了"用户起始年"语义
`DateVarSet.cpp::dateModel()` 与 `scene/DateReplaceEditor.cpp::dateModel()` 都是这种写法:
```cpp
DateItemModel model; // model.startYear = 当前年(来自默认构造函数)
for (int i = 0; i < yearInitList.size() && i < 16; ++i) {
int year = yearInitList[i];
if (year >= model.startYear && year < model.startYear + 16) {
int index = year - model.startYear;
model.yearsArray[index] = yearList[i];
}
}
```
由于当前 `yearInitList` 也是从今年开始填充的,**当前能凑巧跑通**;但这里没有把 UI 里实际看到的起始年同步回 `model.startYear`。一旦未来扩展为"展示历史年份"或"展示从某指定年开始的年份序列",这里会静默丢数据。
**更直白的写法**:
```cpp
if (!yearInitList.isEmpty()) {
model.startYear = yearInitList.first();
}
const int n = std::min(yearInitList.size(), 16);
for (int i = 0; i < n; ++i) {
model.yearsArray[i] = yearList[i];
}
```
---
### 3.3 🟡 signed/unsigned 比较警告
`DateVarSet.cpp` / `scene/DateReplaceEditor.cpp` 中大量这样写:
```cpp
for (size_t i = 0; i < std::min(dataList.size(), static_cast<int>(model.dataMap.size())); ++i)
```
- 循环变量是 `size_t`(无符号),与 `int` 比较会触发 GCC 的 `-Wsign-compare` 警告;
- 每次循环都重新 `std::min` 也没必要。
**建议**改为:
```cpp
const int n = std::min<int>(dataList.size(), static_cast<int>(model.dataMap.size()));
for (int i = 0; i < n; ++i) { ... }
```
---
### 3.4 🟡 `scene/BarcodeEditor.cpp` 五个手写循环冗余
```cpp
for (size_t i = 0; i < m_vm.dateViewModel.dataMap.size(); ++i) {
m_vm.dateViewModel.dataMap[i] = rep.dataMap[i];
}
// ... 类似 5 段 ...
m_vm.dateViewModel.yearsArray = rep.yearsArray; // 这里直接赋值是对的
```
`std::array` 自身就支持 `=`,最后一行 `yearsArray` 已经这样写了。前面 5 段 `for` 循环都可以直接简化:
```cpp
m_vm.dateViewModel.dataMap = rep.dataMap;
m_vm.dateViewModel.daysMap = rep.daysMap;
m_vm.dateViewModel.monthsMap = rep.monthsMap;
m_vm.dateViewModel.weeksMap = rep.weeksMap;
m_vm.dateViewModel.hoursMap = rep.hoursMap;
```
不影响功能,纯属冗余。
---
## 4. 文档与实现不一致清单
| 文档原话 | 实际代码 | 影响 |
|---|---|---|
| "DateVarSet.cpp 修改 setDateModel 函数,使用循环在 std::array 和 QStringList 之间复制数据" | 其它字段都用了循环,但 `yearList = model.yearsArray;` 一行漏改 | 🔴 编译失败 |
| "loadFromXml 函数,适配新的年份数组结构" | 适配了 `yearsArray`,但 `startYear` 未持久化 | 🔴 跨年加载丢用户数据 |
| "ItemCommon.cpp 更新了年份映射访问逻辑,适配新的 yearsArray 和 startYear 结构" | `.cpp` 已改,但 `.h` 头注释仍引用 `yearsMap` | 🟡 文档失同步 |
| "保持了兼容性:接口不变,仅内部实现改变" | XML schema 已变(`yearsMap` 编码方式变化、缺少 `startYear` 字段) | 🔴 与已部署设备的存档不完全互通 |
---
## 5. 关于改进分析文档中性能权衡部分的看法
文档中关于 std::array vs QStringList 在嵌入式 H618 上的权衡论述本身**没有问题**:
- `std::array` 类型安全、缓存友好、内存碎片少 —— 成立;
- 移动 31 个 QString 微秒级开销 —— 成立(QString 是 implicitly shared,移动很快);
- 2.2KB 结构体复制可接受 —— 在低频路径成立;
- 移除 `createDefaultReplaceModel`、由默认构造函数承担初始化 —— 是合理的简化。
但有两点提醒:
1. **`std::array<QString,N>` 移动 ≠ "transfer pointer" 的 O(1)**。每个 QString 元素逐个移动,引用计数不变但仍要逐个调用移动构造,总体 O(N)。N=31 时不是性能问题,但与文档"只需转移指针和引用计数"那一段的描述不太一致。
2. **2.2KB 结构体值传递在 setter 链路上无所谓**,但留意如果将来出现"每帧都把整个 model 复制一遍渲染"这种用法,复制成本就不再可忽略。建议给关键 setter 保留 `setModel(DateItemModel model)`(值传 + move)这种签名,避免被改成"const DateItemModel&"后失去外部 rvalue 移动的好处。
---
## 6. 结论
- **建议至少修掉 §2.1 与 §2.2**:前者不修无法编译;后者不修跨年加载会丢用户在编辑器里配置的年份字串,且无任何运行时报错(最危险的那种 bug)。
- §3 的几条都是清理性工作,可按需进行。
- §4 中关于"接口不变 = 完全兼容"的说法在 XML schema 层面其实已不成立,老存档与新固件的双向兼容性需要单独评估。
---
## 7. 修复 Patch 摘要(建议改动)
```diff
// DateVarSet.cpp (around line 540)
- yearList = model.yearsArray;
+ yearList.clear();
+ for (size_t i = 0; i < model.yearsArray.size(); ++i) {
+ yearList.append(model.yearsArray[i]);
+ }
// scene/ViewModels.cpp saveToXml
+ attrs.append("startYear", QString::number(m.startYear));
// scene/ViewModels.cpp loadFromXml
- m.startYear = QDate::currentDate().year();
+ {
+ int sy = attrs.value("startYear").toInt();
+ m.startYear = (sy != 0) ? sy : QDate::currentDate().year();
+ }
// scene/ItemCommon.h 注释更新
- * - yyyy: 四位数年份,支持替换为自定义字符串(通过 model.yearsMap)
+ * - yyyy: 四位数年份,支持替换为自定义字符串(通过 model.yearsArray + model.startYear)
+294
View File
@@ -0,0 +1,294 @@
# DateItemModel 改进分析
## 1. 当前 DateItemModel 使用分析
### 1.1 结构定义
```cpp
struct DateItemModel {
QString datetimePattern;
int monthsOffset = 0;
int daysOffset = 0;
QStringList dataMap; // unused
QStringList daysMap;
QStringList monthsMap;
QMap<int, QString> yearsMap;
QStringList weeksMap;
QStringList hoursMap;
static DateItemModel createDefaultReplaceModel();
static void loadFromXml(DateItemModel& vm, const QXmlStreamAttributes& attrs);
static void saveToXml(const DateItemModel& vm, QXmlStreamAttributes& attrs);
};
```
### 1.2 主要使用场景
**PicDateItem 类**:
- 存储为成员变量 `m_model`
- 通过 `setModel(DateItemModel model)` 方法接收新模型(使用移动语义)
- 在 `setCustomAttrs` 和 `saveCustomAttrs` 中进行 XML 序列化
- 用于生成实时更新的日期时间字符串
**DateReplaceEditor 类**:
- 在 UI 编辑器中双向绑定数据
- 通过 `dateModel()` 返回完整的模型数据
- 通过 `setDateModel(const DateItemModel& model)` 接收模型
**DateVarSet 类**:
- 与 UI 界面进行双向数据同步
- 在 `dateModel()` 和 `setDateModel()` 中进行数据转换
## 2. 性能分析 (针对全志H618平台)
### 2.1 平台特性
- 全志H618是ARM架构处理器,主频约1.5GHz
- 通常配备1-2GB RAM
- 资源相对受限,需要优化内存和CPU使用
### 2.2 QStringList vs std::array 性能对比
#### 2.2.1 内存占用对比
- **QStringList**: 每个列表包含额外的元数据(引用计数、大小、容量等)
- dataMap (16个QString): ~16*(QString开销) + 指针数组开销
- daysMap (31个QString): ~31*(QString开销) + 指针数组开销
- monthsMap (12个QString): ~12*(QString开销) + 指针数组开销
- weeksMap (7个QString): ~7*(QString开销) + 指针数组开销
- hoursMap (24个QString): ~24*(QString开销) + 指针数组开销
- **std::array<QString, N>**: 直接内联存储,无额外指针
- 更好的缓存局部性
- 减少内存碎片
#### 2.2.2 访问性能
- **QStringList**: 间接访问(指针跳转)
- **std::array**: 直接访问(连续内存),更好的缓存命中率
#### 2.2.3 构造/析构性能
- **QStringList**: 共享数据,浅拷贝,但在赋值时可能触发深度拷贝
- **std::array**: 需要移动/拷贝每个元素
### 2.3 移动语义分析
#### 2.3.1 当前使用模式
在 `PicDateItem::setModel` 中:
```cpp
void PicDateItem::setModel(DateItemModel model)
{
m_model = std::move(model); // 显式使用移动语义
}
```
在 `DateItemModel::createDefaultReplaceModel` 中:
```cpp
return model; // 依赖 NRVO/RVO 或移动语义
```
#### 2.3.2 性能影响评估
对于小规模数组(N < 32):
- **QStringList 移动**: O(1) - 只需转移指针和引用计数
- **std::array<QString, N> 移动**: O(N) - 需要移动 N 个 QString
但要注意:
1. QString 本身实现了移动语义,移动是高效的操作
2. N 值较小(最大31个元素),移动开销可接受
3. 在 H618 上,移动 31 个 QString 的时间仍远小于毫秒级
## 3. 实际使用场景分析
### 3.1 频繁操作类型
- 读取操作: 高频(UI 显示、日期计算)
- 写入操作: 中频(用户编辑、配置变更)
- 复制/移动操作: 低频(对象传递、赋值)
### 3.2 性能瓶颈预测
- 读取操作: `std::array` 胜出(更好的缓存局部性)
- 写入操作: 两者相当
- 移动操作: `QStringList` 略优,但差距很小
## 4. 推荐方案
### 4.1 最优实现
```cpp
#include <array>
struct DateItemModel {
QString datetimePattern;
int monthsOffset = 0;
int daysOffset = 0;
std::array<QString, 16> dataMap; // 固定大小,类型安全
std::array<QString, 31> daysMap; // 固定大小,类型安全
std::array<QString, 12> monthsMap; // 固定大小,类型安全
std::array<QString, 7> weeksMap; // 固定大小,类型安全
std::array<QString, 24> hoursMap; // 固定大小,类型安全
std::array<QString, 16> yearsArray; // 年份数组,固定大小
int startYear; // 起始年份,用于计算索引对应的年份
};
```
### 4.2 实现注意事项
```cpp
// 在 ViewModel.cpp 中相应修改
DateItemModel DateItemModel::createDefaultReplaceModel()
{
DateItemModel model;
// 初始化 dataMap
const char* dataValues[] = {"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"};
for (int i = 0; i < 16; ++i) {
model.dataMap[i] = QString(dataValues[i]);
}
// 初始化 daysMap
for (int i = 0; i < 31; ++i) {
model.daysMap[i] = QString("%1").arg(i + 1, 2, 10, QChar('0'));
}
// 初始化 monthsMap
for (int i = 0; i < 12; ++i) {
model.monthsMap[i] = QString("%1").arg(i + 1, 2, 10, QChar('0'));
}
// 初始化 weeksMap
const char* weekValues[] = {"MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"};
for (int i = 0; i < 7; ++i) {
model.weeksMap[i] = QString(weekValues[i]);
}
// 初始化 hoursMap
for (int i = 0; i < 24; ++i) {
model.hoursMap[i] = QString("%1").arg(i, 2, 10, QChar('0'));
}
int startYear = QDate::currentDate().year();
for (int i = 0; i < 16; ++i) {
model.yearsMap.insert(startYear + i, QString::number(startYear + i));
}
return model;
}
```
## 5. 性能权衡结论
### 5.1 移动语义影响
在全志H618平台上:
- **移动开销**: 对于固定小数组,移动 31 个 QString 的开销非常小(微秒级)
- **访问优势**: `std::array` 提供更好的缓存局部性和访问性能
- **总体影响**: 类型安全和访问性能的收益远大于移动语义的轻微损失
### 5.2 最终建议
**推荐使用 `std::array`**,理由如下:
1. **类型安全**: 编译时确保固定大小,防止越界
2. **性能优势**: 更好的缓存局部性和访问性能
3. **内存效率**: 减少内存碎片,更紧凑的布局
4. **可维护性**: 清晰表达数据结构的固定大小特征
5. **移动性能**: 在实际应用场景中,移动开销可忽略不计
在资源受限的嵌入式环境(如H618)中,`std::array` 的内存布局优势和缓存友好性比微小的移动性能损失更重要。
## 6. 关于2.2KB结构体大小的分析
### 6.1 大小计算详情
改为 `std::array` 后的 `DateItemModel` 总大小约为:
- `QString datetimePattern`: ~24 字节
- `int monthsOffset`: 4 字节
- `int daysOffset`: 4 字节
- `std::array<QString, 16> dataMap`: 16 × ~24 = ~384 字节
- `std::array<QString, 31> daysMap`: 31 × ~24 = ~744 字节
- `std::array<QString, 12> monthsMap`: 12 × ~24 = ~288 字节
- `QMap<int, QString> yearsMap`: ~32 字节
- `std::array<QString, 7> weeksMap`: 7 × ~24 = ~168 字节
- `std::array<QString, 24> hoursMap`: 24 × ~24 = ~576 字节
**总计:约 2220 字节(~2.2KB)**
### 6.2 性能影响评估
在全志H618平台上:
- **复制时间估算**:2.2KB 复制约需 1-5 微秒
- **使用频率**:UI 编辑操作每秒最多几次,非常低频
- **CPU 占用**:即使每秒 100 次复制,也仅占 CPU 时间的 0.05%
### 6.3 可接受性结论
**2.2KB 结构体复制在当前代码中是完全可以接受的**,因为:
1. **性能影响微乎其微**:复制时间远小于其他系统操作(UI渲染、I/O等)
2. **使用频率极低**:实际复制操作非常不频繁
3. **硬件能力充足**:全志H618足以处理这种规模的数据复制
4. **代码简洁性**:值语义比智能指针语义更简单直观
5. **调试便利性**:值对象更容易调试和跟踪
## 7. 关于createDefaultReplaceModel函数的改进
随着我们将DateItemModel结构改为使用构造函数初始化,我们也可以移除createDefaultReplaceModel静态函数。这个函数的功能可以由默认构造函数完全替代:
```cpp
// 旧方式
DateItemModel model = DateItemModel::createDefaultReplaceModel();
// 新方式
DateItemModel model; // 使用默认构造函数
```
这样做的好处:
1. 简化了API:不再需要单独的工厂函数
2. 保持了一致性:所有实例都通过构造函数初始化
3. 减少了代码冗余:构造函数已经提供了相同的初始化逻辑
## 8. 关于传递方式的分析
### 8.1 当前传递方式
在 `PicDateItem::setModel(DateItemModel model)` 中,使用值传递但配合移动语义:
```cpp
void PicDateItem::setModel(DateItemModel model)
{
m_model = std::move(model); // 实际使用移动语义
}
```
### 7.2 传递方式对比
| 方式 | 复制开销 | 复杂性 | 适用场景 |
|------|----------|--------|----------|
| 值传递 + 移动 | 低(移动而非复制) | 低 | 推荐,适合当前场景 |
| const 引用 | 无传参开销 | 中 | 需要额外复制到成员 |
| 智能指针 | 8字节指针 | 高 | 需要共享数据时 |
## 9. 年份映射的优化改进
原来的 `QMap<int, QString> yearsMap` 被替换为 `std::array<QString, 16> yearsArray` 和 `int startYear` 的组合:
**优点:**
1. **内存效率更高**:`std::array` 比 `QMap` 占用更少内存
2. **访问速度更快**:数组访问比哈希表访问更快
3. **缓存友好**:连续内存布局提供更好的缓存性能
4. **类型安全**:编译时确定大小,防止越界
**实现方式:**
- `startYear` 存储起始年份(如 2023)
- `yearsArray[0]` 对应 `startYear` 年(2023年)
- `yearsArray[1]` 对应 `startYear + 1` 年(2024年)
- 以此类推,最多支持16个连续年份
**访问逻辑:**
```cpp
// 访问特定年份的映射值
int year = 2025;
if (year >= startYear && year < startYear + 16) {
int index = year - startYear;
QString mappedValue = yearsArray[index];
}
```
### 7.3 推荐方案
保持当前的值传递设计,利用移动语义获得最佳性能:
```cpp
// 调用时使用移动
DateItemModel model = DateItemModel::createDefaultReplaceModel();
picItem.setModel(std::move(model)); // 零拷贝移动
```
这种方法既保持了接口的简洁性,又获得了最佳性能,无需引入智能指针的复杂性。
+204
View File
@@ -0,0 +1,204 @@
# DateVarSet 类分析与解决方案
## 一、类功能概述
`DateVarSet` 是一个用于日期变量设置的 Qt 界面类,继承自 `QWidget` 和 `ParaSettingUI`。主要功能包括:
- 提供日期格式编辑界面
- 支持多种日期组件(年、月、日、时、分、秒等)的设置
- 允许用户自定义替换值(如月份名称、星期几等)
- 管理日期偏移量(天数和月份)
## 二、输入输出接口分析
### 输入接口:
1. `void edit(PicDateItem* item)` - 编辑指定的日期项目
2. `void setDateModel(const DateItemModel& model)` - 设置日期模型数据
3. `void bindSysPara(SysPara *sysPara)` - 绑定系统参数
4. `void bindTxTEdit(QTextEdit* textEdit)` - 绑定文本编辑器
### 输出接口:
1. `DateItemModel dateModel()` - 获取当前日期模型
2. `void getValue(int *varType,int *digitalWidth,unsigned char *splitMode)` - 获取变量类型、数字宽度和分割模式
### UI 交互逻辑:
- 使用 Tab 控制不同的功能区域
- 支持翻页浏览大量数据(月份、日期、年份等)
- 实时编辑替换值并保存到内部列表
## 三、发现的 Bug 分析
### 1. 内存管理问题
在构造函数中,虽然创建了 `ItemGeneralSettings` 对象,但没有在析构函数中释放它:
```cpp
// 在构造函数中创建
m_generalSettings = new ItemGeneralSettings();
// 但在析构函数中没有删除
DateVarSet::~DateVarSet()
{
delete ui; // 缺少 delete m_generalSettings;
}
```
### 2. 数组越界风险
在 `onLineEditReplaceChanged` 方法中,没有检查数组索引是否有效:
```cpp
void DateVarSet::onLineEditReplaceChanged(int i, QString txt)
{
auto mapIdx = currPage * 4 + i;
// 没有检查 mapIdx 是否超出数组范围
switch(ui->cb_replaceType->currentIndex()) {
case 0:
dataList[mapIdx] = txt; // 可能导致越界
break;
// ...
}
}
```
### 3. 信号阻塞处理不一致
在 `loadDayShow` 方法中,信号阻塞没有正确恢复:
```cpp
void DateVarSet::loadDayShow(){
// ...
if(lineEdit2) {
auto b = lineEdit2->blockSignals(true);
lineEdit2->setText(dayList.at(i));
lineEdit2->blockSignals(false); // 直接设为false,而不是恢复原状态
}
}
```
### 4. 未使用的成员变量
`dataMap` 在 `DateItemModel` 中被标记为 "unused",但仍参与序列化操作。
### 5. 重复代码
存在多处相似的加载方法(`loadMonthShow`, `loadDataShow`, `loadWeekShow` 等),代码结构几乎相同。
### 6. 静态全局变量使用不当
使用了全局静态变量 `vartypeR` 和 `replaceValueMap`,这可能导致不同实例间的数据污染。
## 四、解决方案
### 1. 修复内存泄漏
```cpp
DateVarSet::~DateVarSet()
{
delete ui;
delete m_generalSettings; // 添加这一行
}
```
### 2. 添加边界检查
```cpp
void DateVarSet::onLineEditReplaceChanged(int i, QString txt)
{
auto mapIdx = currPage * 4 + i;
// 添加边界检查
switch(ui->cb_replaceType->currentIndex()) {
case 0:
if(mapIdx < dataList.size()) {
dataList[mapIdx] = txt;
}
break;
case 1:
if(mapIdx < weekList.size()) {
weekList[mapIdx] = txt;
}
break;
case 2:
if(mapIdx < monthList.size()) {
monthList[mapIdx] = txt;
}
break;
case 3:
if(mapIdx < yearList.size()) {
yearList[mapIdx] = txt;
}
break;
case 4:
if(mapIdx < dayList.size()) {
dayList[mapIdx] = txt;
}
break;
case 5:
if(mapIdx < hourList.size()) {
hourList[mapIdx] = txt;
}
break;
}
}
```
### 3. 修复信号阻塞
```cpp
void DateVarSet::loadDayShow(){
// ...
if(lineEdit2) {
auto b = lineEdit2->blockSignals(true);
lineEdit2->setText(dayList.at(i));
lineEdit2->blockSignals(b); // 恢复原始状态,不是硬编码为false
}
}
```
### 4. 重构重复代码
创建通用模板方法来替代重复的加载逻辑:
```cpp
template<typename T>
void DateVarSet::loadGenericShow(const QStringList& sourceList, int maxPages, int itemsPerPage = 4) {
if(currPage > maxPages) {
currPage = maxPages;
}
int objectIndex = 1;
for(int i = currPage * itemsPerPage; (i < (currPage + 1) * itemsPerPage) && (i < sourceList.size()); i++) {
QString strObject = QString("lineEditName") + QString::number(objectIndex);
QLineEdit *lineEdit = this->findChild<QLineEdit*>(strObject);
if(lineEdit) {
lineEdit->setText(QString::number(i + 1));
}
QString strObject2 = QString("lineEdit_Replace") + QString::number(objectIndex);
QLineEdit *lineEdit2 = this->findChild<QLineEdit*>(strObject2);
if(lineEdit2) {
auto b = lineEdit2->blockSignals(true);
lineEdit2->setText(sourceList.at(i));
lineEdit2->blockSignals(b);
}
objectIndex++;
}
}
```
### 5. 移除不必要的全局变量
将全局静态变量移入类中作为成员变量:
```cpp
private:
unsigned char m_varTypeR = 0x00;
QMap<int, QMap<int, QString>> m_replaceValueMap;
```
## 五、可移除的无用逻辑
根据要求,需要移除构造默认 `DateItemModel` 的逻辑。但是通过代码审查发现,`DateItemModel::createDefaultReplaceModel()` 方法实际上并未在此文件中使用。该方法存在于 `ViewModels.h` 中,但 `DateVarSet` 类并未调用它。
不过,在 `DateItemModel` 结构体中,确实存在一个未使用的成员:
```cpp
QStringList dataMap; // unused
```
这个字段应该从结构体中移除,因为它从未被使用过。
## 六、其他改进建议
1. **改进错误处理**:在 `edit` 方法中添加更详细的参数验证
2. **优化性能**:减少不必要的 UI 更新操作
3. **增强类型安全**:使用枚举代替魔法数字(如 `cb_replaceType` 的索引值)
4. **完善注释**:为复杂的业务逻辑添加详细注释
这些修改将提高代码的健壮性、可维护性和性能。
## 七、总结
`DateVarSet` 类存在多个潜在问题,主要包括内存泄漏、数组越界风险、代码重复和全局变量使用不当。通过上述解决方案可以显著改善代码质量,同时移除无用的 `dataMap` 字段可以简化数据结构。
+189
View File
@@ -0,0 +1,189 @@
# RFIDManager 实现方案
## 概述
当前项目中的RFID功能通过`__RFID_CARD`宏控制,该宏通过条件编译启用或禁用RFID相关代码。这种方法在可维护性、测试和运行时灵活性方面存在局限。为了解决这些问题,我们建议引入`RFIDManager`类,该类封装所有RFID操作,提供更清洁、更模块化的设计。
`RFIDManager`将处理RFID初始化、数据读取和验证,仅通过QT信号通知外部卡片检测事件,让外部组件(如InkJetPrinter)负责具体的处理逻辑,替换代码库中分散的`#ifdef __RFID_CARD`块。
## 目标
- **封装**:将RFID逻辑集中到一个类中。
- **运行时控制**:允许在运行时而不是编译时启用/禁用RFID功能。
- **可维护性**:通过移除预处理器指令简化代码。
- **可扩展性**:更容易添加新RFID功能或修改现有功能。
- **测试**:无需重新编译即可对RFID组件进行单元测试。
## 类设计
### RFIDManager 类
```cpp
class RFIDManager : public QObject {
Q_OBJECT
public:
RFIDManager(QObject* parent = nullptr);
~RFIDManager();
// 初始化
bool initialize();
// 主处理循环(定期调用,例如每300ms)
void processRFID();
// 启用/禁用RFID功能
void setEnabled(bool enabled);
bool isEnabled() const;
// 配置
void setValidationDelay(int seconds); // 例如,5秒用于卡片稳定性
void setSerialPort(const QString& port); // 例如,"/dev/ttyAS1"
void setBaudRate(int baudRate); // 例如,9600
// 数据访问
QString getLastCardData() const;
bool isCardPresent() const;
signals:
// 通知外部卡片检测到
void cardDetected(QString cardType); // cardType: 'A', 'B', 'C' 等
private:
// 私有方法
void readRFIDData();
void validateCardPresence();
// 成员变量
bool m_enabled;
int m_validationDelay; // 以秒为单位
QString m_serialPort;
int m_baudRate;
QString m_lastCardData;
bool m_cardPresent;
int m_stableCount; // 验证延迟计数器
QString m_previousCardData; // 检测卡片变化
// 依赖项(内部注入或创建)
DevOp* m_devOp; // 用于串口通信
};
```
### 关键方法
- **`initialize()`**:设置串口通信并初始化参数。固定串口名为"/dev/ttyAS1",比特率9600等
- **`processRFID()`**:主方法,在定时器循环中调用。读取数据、验证卡片存在,如果验证通过则发射`cardDetected`信号。
- **`setEnabled(bool)`**:RFID功能的运行时切换。
- **`setValidationDelay(int)`**:配置卡片验证延迟(例如,5秒)。
- **`readRFIDData()`**:与`DevOp`接口读取串口数据。
- **`validateCardPresence()`**:通过计数连续稳定读取实现延迟逻辑,如果延迟满足则发射信号。
## 集成计划
### 步骤1:创建RFIDManager类
- 在项目源目录中创建`RFIDManager.h`和`RFIDManager.cpp`。
- 按照上述大纲实现类。
### 步骤2:在InkJetPrinter.cpp中替换宏使用
- 在`InkJetPrinter.cpp`中移除`#ifdef __RFID_CARD`块。
- 将`RFIDManager`实例添加为成员变量。
- 在构造函数中初始化`RFIDManager`,并连接信号到槽函数。
- 在`timerHandler()`中,将RFID逻辑替换为调用`rfidManager.processRFID()`。
- 添加槽函数(如`onCardDetected(QString cardType)`)来处理卡片检测逻辑,包括显示对话框和更新参数。
在`InkJetPrinter.cpp`中的示例更改:
```cpp
// 在构造函数中
rfidManager = new RFIDManager(this);
connect(rfidManager, &RFIDManager::cardDetected, this, &InkJetPrinter::onCardDetected);
// 在timerHandler中
if (rfidManager->isEnabled()) {
rfidManager->processRFID();
}
// 新增槽函数
void InkJetPrinter::onCardDetected(QString cardType) {
// 处理卡片逻辑:显示对话框、更新SysPara等
if (cardType == "A") {
// 处理墨卡
} else if (cardType == "B") {
// 处理维护卡
} else if (cardType == "C") {
// 处理其他卡
}
}
```
### 步骤3:更新DevOp.cpp
- 确保`DevOp`方法(`comForRFID_init`、`receiveReadRFID_data`)可供`RFIDManager`访问。
- 可能使`DevOp`成为单例,或通过依赖注入将其传递给`RFIDManager`。
### 步骤4:更新SysPara.h
- 确保`RFIDPara_t`可供`RFIDManager`访问以进行参数更新。
### 步骤5:配置
- 将RFID设置添加到配置文件或UI中,允许运行时更改启用/禁用和设置延迟。
### 步骤6:移除宏
- 完全集成后,从`myinclude.h`中移除`__RFID_CARD`宏并清理任何剩余的条件代码。
## 验证延迟实现
为了防止误操作,实现5秒延迟:
- 跟踪具有相同卡片数据的连续读取。
- 仅在~17次稳定读取后发射`cardDetected`信号(300ms * 17 ≈ 5.1秒)。
- 如果卡片数据变化或消失,则重置计数器。
```cpp
void RFIDManager::validateCardPresence() {
QString currentData = readRFIDData();
if (currentData == m_previousCardData && !currentData.isEmpty()) {
m_stableCount++;
if (m_stableCount >= (m_validationDelay * 1000) / 300) { // 假设300ms间隔
emit cardDetected(currentData); // 发射信号,传递卡片类型
m_stableCount = 0; // 处理后重置
}
} else {
m_stableCount = 0;
}
m_previousCardData = currentData;
}
```
## 优势
- **运行时灵活性**:无需重新编译即可启用/禁用RFID。
- **更清洁的代码**:逻辑中无预处理器指令混乱。
- **更好的测试**:为单元测试模拟`RFIDManager`。
- **模块化**:更容易扩展或修改RFID功能。
- **错误处理**:RFID操作的集中错误处理。
## 潜在挑战
- **依赖注入**:确保`DevOp`和`SysPara`正确注入或可访问。
- **线程安全**:如果从定时器调用`processRFID()`,确保线程安全。
- **向后兼容性**:彻底测试以确保功能无回归。
## 测试计划
- `RFIDManager`方法的单元测试。
- 与`InkJetPrinter`的集成测试。
- 使用实际RFID硬件的手动测试,以验证延迟和检测。
## 时间表
1. **第1周**:设计并实现`RFIDManager`类。
2. **第2周**:集成到`InkJetPrinter.cpp`并测试。
3. **第3周**:更新配置并移除宏。
4. **第4周**:全面测试和文档。
此方案为将RFID功能重构为更可维护和灵活的系统提供了坚实基础。
+1
View File
@@ -0,0 +1 @@
# RFIDManager说明
+460
View File
@@ -0,0 +1,460 @@
# Scene 及相关模块 BUG/逻辑排查报告
> **排查范围**:`scene/` 目录全部代码 + `testprintwidget.cpp/.h` + `mainwidget.cpp/.h` + `InkJetPrinter.cpp/.h`
> **排查目标**:找出可能导致程序崩溃、内存泄漏、悬空指针、UI 异常等隐患
> **本次只输出排查结果与解决方案,不修改代码**
---
## 一、排查思路
针对桌面 Qt + QGraphicsScene 框架下打印机控制软件这一类典型的「长生命周期 + 多线程 + 多对话框 + 共享场景」程序,我按以下维度系统性地审查代码:
1. **空指针/悬空指针**
- `scene()` / `parent()` / `focusItem()` / `selectedItems()` 在 item 未入场或已删除时是否被解引用
- `dynamic_cast<>` 失败后是否仍当成有效指针使用
- 槽函数捕获的裸指针 item 在异步触发时是否已销毁
2. **生命周期/所有权**
- `new` 出来的子 widget、QGraphicsScene、QThread、Serial、Timer 是否有 parent,析构是否释放
- `m_pSavePicScene = new QGraphicsScene()` 这类无 parent 的对象
- 子线程退出顺序与 `moveToThread` 的对象 `delete` 时机
- 「编辑器对话框」持有的 `item*` 在 item 被场景删除后未置空
3. **类型与边界**
- `QImage` 创建尺寸可能为 0/负数
- 像素读写、`scanLine`、`copy()` 越界
- `QVector` / `QList` 索引越界(`m_currCounterValues[1]` 等)
- `QVariant` 类型不匹配强转(`toString` / `toMap`)
4. **重入与状态机**
- `setSource()` → `applyModify()` → `update()` 是否产生递归
- `itemChange()` 返回值不正确导致几何更新无限触发
- 信号-槽链中是否互发导致死循环
5. **资源未清理**
- 析构里漏掉 `timer->stop()`、`thread->quit()`、`disconnect()`、子 widget delete
- `connect` 未配 `disconnect`,导致已删除对象的回调被触发
6. **UI 与 IO 健壮性**
- 文件路径不存在、XML 不合法、字体不存在、图片加载失败时的回退
- 用户操作(双击、空选中、未填字段)下的边界路径
排查方式:**逐文件 read** + **关键字交叉 grep**(`scene()`, `selectedItems()`, `dynamic_cast`, `delete `, `new `, `connect(`, `qDebug`, `assert` 等),并对 InkJetPrinter / TestPrintWidget / MainWidget / 各 Item 类的关键流程绘制调用链。
---
## 二、排查结果总览
| 模块 | 高风险 | 中风险 | 低风险 | 合计 |
|------|------|------|------|------|
| scene 目录 Item 类(pictextitem / piccodeitem / PicImageItem 等) | 5 | 5 | 2 | 12 |
| scene 目录 编辑器/视图(picscene / myview / 各 Editor) | 3 | 4 | 1 | 8 |
| testprintwidget | 4 | 4 | 1 | 9 |
| mainwidget | 3 | 3 | 1 | 7 |
| InkJetPrinter | 4 | 4 | 1 | 9 |
| **合计** | **19** | **20** | **6** | **45** |
> 说明:以下问题中带 ⚠️ 标志的为已经较为确定可触发崩溃;其余为基于代码模式判断的潜在隐患。
---
## 三、scene/ 目录下 Item 类相关问题
### 3.1 ⚠️【高】pictextitem.cpp:441-442 `clampItemPos` 未判空 `scene()`
```cpp
QPointF PicTextItem::clampItemPos(const QPointF &p)
{
return QPointF (std::min(qreal(scene()->width() - boundingRect().width()), std::max(0.0, p.x())),
std::min(qreal(scene()->height() - boundingRect().height()), std::max(0.0, p.y())));
}
```
**问题**:item 尚未 `addItem` 到 scene、或粘贴/克隆时短暂离开 scene 时调用此函数,`scene()` 返回 `nullptr`,直接解引用即崩。
**复现**:复制粘贴 item、或在删除瞬间触发 mouseRelease/keyPress。
**修复方案**:
```cpp
auto* sc = scene();
if (!sc) return p;
qreal w = sc->width() - boundingRect().width();
qreal h = sc->height() - boundingRect().height();
return { std::min(w, std::max(0.0, p.x())), std::min(h, std::max(0.0, p.y())) };
```
**澄清**
已修复
### 3.2 ⚠️【高】pictextitem.cpp:346 / 352 / 375-377 `mouseReleaseEvent` & `keyPressEvent` 未判空 `scene()`
两处均直接 `scene()->update()` 与 `scene()->selectedItems()`,与 3.1 同样在 item 离场时崩溃。
**修复方案**:进入函数先 `if(!scene()){ ... ::xxxEvent(event); return; }`。
### 3.3 ⚠️【高】pictextitem.cpp:415-432 `strToImage` 可能创建 0 宽/0 高 QImage
```cpp
QImage img(fm.horizontalAdvance(str), fm.height(), QImage::Format_ARGB32);
```
**问题**:`str` 为空、或字符全部为不可打印字符时,`horizontalAdvance` 返回 0,`QImage(0, h, ...)` 是无效图像,后续 `img.fill / img.rect / clipImage` 中循环按 width=0 跳过,但 `fm.height()` 也可能因字体加载失败为 0;最终 `clipImage` 内可能跳到 `end:` 直接 `img.copy(QRect())` 返回空。再走 `applyImgeTranslation` 中 `scaled` 到 0 宽,下游 `paint` 用空 QImage 仍可工作,但 `boundingRect` 变为 (0,0,0,0),被选中后 `drawRect(r)` 画零矩形,并不至于 crash,但 `fm.horizontalAdvance` 在某些极端字符串/字体下,`QFontMetrics` 实现里有 [QTBUG] 历史已知崩溃记录。
**修复方案**:
```cpp
int w = std::max(1, fm.horizontalAdvance(str));
int h = std::max(1, fm.height());
QImage img(w, h, QImage::Format_ARGB32);
```
**澄清**
已修复
### 3.4 ⚠️【高】pictextitem.cpp:304-316 `itemChange()` 修改 `newPos` 后未返回
```cpp
QVariant PicTextItem::itemChange(GraphicsItemChange change, const QVariant &value)
{
if (change == ItemSceneChange && scene()) {
QPointF newPos = value.toPoint();
QRectF rect = scene()->sceneRect();
if (!rect.contains(newPos)) {
newPos.setX(...); newPos.setY(...);
}
}
return QGraphicsItem::itemChange(change, value); // <- 返回原值
}
```
**问题**:
1. `change` 变量名错了:clamp 应该用 `ItemPositionChange`,而代码写的是 `ItemSceneChange`,逻辑根本没生效;
2. clamp 后 `newPos` 没有 return,等于做了无用功。
**风险**:item 可被拖出 scene;与 `mouseReleaseEvent` 中的 clamp 配合时位置异常,并非崩溃但是逻辑 BUG。
**修复方案**:
```cpp
if (change == ItemPositionChange && scene()) {
QPointF newPos = value.toPointF();
QRectF rect = scene()->sceneRect();
if (!rect.contains(newPos)) {
newPos.setX(qBound(rect.left(), newPos.x(), rect.right()));
newPos.setY(qBound(rect.top(), newPos.y(), rect.bottom()));
}
return newPos;
}
return QGraphicsItem::itemChange(change, value);
```
注:此处还把 `value.toPoint()` 写成了整型 Point,丢精度,应改 `toPointF()`。
**澄清**
该段代码使用已久,未发现异常。暂不修复
### 3.5 ⚠️【高】piccodeitem.cpp:73 `m_currCounterValues` 索引可能越界
```cpp
auto txt = generateTxtFromBarcodeViewModel(m_vm, {m_currCounterValues[0], m_currCounterValues[1]}, ...);
```
**问题**:构造函数初始化 `m_currCounterValues({0, 0})`(size=2 OK),但 `updateVariants(const QVector<int>)` 入参 `counters` 直接赋值 `m_currCounterValues = counters`。一旦上层只传 1 个或空 vector,`[0]/[1]` 越界。
**修复方案**:
```cpp
int c0 = m_currCounterValues.size() > 0 ? m_currCounterValues[0] : 0;
int c1 = m_currCounterValues.size() > 1 ? m_currCounterValues[1] : 0;
auto txt = generateTxtFromBarcodeViewModel(m_vm, {c0, c1}, QDateTime::currentDateTime());
```
或在 `updateVariants(const QVector<int>)` 入口校验/补齐为 size=2。
**澄清**
已经在`updateVariants`入口处校验size
### 3.6【中】piccodeitem.cpp:31-34 `setSource` 直接忽略入参
```cpp
void PicCodeItem::setSource(QVariant var) { applyModify(); }
```
**问题**:参数 `var` 未使用,但调用者通常假设 `setSource(text)` 会更新文本;该类把数据来源换成了 `m_vm`。
**风险**:被 `setCustomFont` 之类重新调用时可能逻辑错乱,但不会崩。
**修复方案**:用 `Q_UNUSED(var);` 显式声明,或将公共抽象上提。
**澄清**: source/setSouce 机制准备弃用,不与修复
### 3.7【高/中】pictextitem.cpp:153 `setBoldMultiple` 缺少返回值
```cpp
int PicTextItem::setBoldMultiple(int multiple) { m_boldMultiple = multiple; }
```
**问题**:声明返回 int 却没有 return,未定义行为;GCC/MSVC 优化下可能取到栈垃圾。如果调用方使用了返回值,可能挂掉。
**修复方案**:返回类型改为 `void`,或 `return multiple;`。
**澄清**: 已修复
### 3.8【高】PicImageItem.cpp:16-18 图片加载失败仍使用旧/空对象
```cpp
QImage img(var.toString());
if (img.isNull()) return;
m_img = img;
```
若加载失败,`m_img` 仍然是旧值(可能未初始化)。后续 `boundingRect` / `paint` 仍按之前尺寸绘制可能不一致;如果是首次加载失败,`m_img` 默认构造的空 QImage 会让 `boundingRect` 为 0 区域,后续 `applyImgeTranslation` 中 `scaled` 到 0 宽,与 3.3 同样存在风险。
**修复方案**:
```cpp
if (img.isNull()) { m_img = emptyImage(); applyModify(); return; }
```
**澄清**: 已修复
### 3.9【高】PicScheduleShiftItem.cpp:98 返回临时对象引用导致悬空
(来自子代理报告,已确认)
**修复方案**:函数返回值改为按值或返回成员引用。
**澄清**: 已修复
### 3.10【中】pictextitem.cpp:24-25 文档对象 stylesheet 在 `delete document()` 后失效
`document()->setDefaultStyleSheet(...)` 是合法的,但 `setSource` 后我们用的是 `strToImage` 自绘的 QImage,与 `QGraphicsTextItem` 内部 `QTextDocument` 的内容可能不一致。
**风险**:若上层 `paint` 走 fallback 分支(`SourceType::UNKNOWN`)调用 `QGraphicsTextItem::paint`,会绘制空文档;但属于显示瑕疵不至于崩。
**修复方案**:在 `setSource` 同时也 `setPlainText(str)`,或在 fallback 中显式 setPlainText。
**澄清**: 全面自绘,不与修复
### 3.11【中】piccodeitem.cpp:14-19 lambda 捕获 `this` 但 `m_dateVarTimer` 父对象为 nullptr
`QTimer m_dateVarTimer` 是 `PicCodeItem` 的成员变量,但 PicCodeItem 不继承 QObject(继承 QGraphicsTextItem),析构时对 lambda `this` 指向已开始析构的对象。当前构造时未 start,看似无害,但若有人启用 `m_dateVarTimer.start(...)`,析构里 `m_dateVarTimer.stop()` 后仍可能正在派发的事件循环里持有 lambda → 崩。
**修复方案**:在析构前 `m_dateVarTimer.disconnect(); m_dateVarTimer.stop();`。
### 3.12【低】pictextitem.cpp:41-87 `clipImage` 使用 goto + `originRect` 变量未使用
代码可读性差,未使用的 `originRect`
**澄清**: 不予修复
---
## 四、scene/ 编辑器/视图相关问题
### 4.1 ⚠️【高】picscene `onFocusItemChanged` 持有 `currFocusItem` 裸指针
当前 `currFocusItem` 在 item 被 scene 删除后没有被清空(场景层未 connect `QGraphicsScene::changed` 或 `QObject::destroyed`)。
**复现**:聚焦一个 item,再点击删除按钮,再做依赖 currFocusItem 的操作(如 `Ctrl+B` 加粗、键盘移动)。
**修复方案**:在 `removeItem` 前后显式清空 `currFocusItem`,并在 `onFocusItemChanged` 之外用一个专门的「current 选中」并通过 `connect(scene, &QGraphicsScene::selectionChanged, ...)` 单一来源,避免栈悬空。
### 4.2 ⚠️【高】BarcodeEditor / DlgBarcodeFormat / DlgQrcodeFormat 等编辑器持有 `PicCodeItem*` 后,item 可能被场景删除
`testprintwidget.cpp` 中 `codeItem_Operation` 长期持有 item 指针;编辑器(modal/non-modal 不一)打开后用户在外部做了「全部删除」或加载新模板,原 item 已 `removeItem + delete`,编辑器返回时 `codeItem_Operation->setBarcodeViewModel(...)` → 崩。
**修复方案**:
- 编辑期间禁用其他可能删除 item 的按钮;或
- 用 `QPointer<PicCodeItem>` 持有,使用前判空;或
- 在 `removeItem` 前发信号通知所有打开的编辑器关闭。
### 4.3【中】myview / sceneviewwidget `setSceneRect` / `centerOn` 在 scene 未就绪时调用
若 `bindPicScene(nullptr)`,后续视图操作崩。
**修复方案**:所有调用前 `if(!scene()) return;`。
### 4.4【中】ItemEditor / ItemGeneralSettings 信号槽传 raw `PicTextItem*`
信号槽走 `Qt::QueuedConnection` 时 item 已释放则崩。
**修复方案**:参数改 `QPointer<PicTextItem>`,或改成传 id(`item->data(0)` 存 uuid),槽内查表。
### 4.5【中】ScheduleShiftsEditor 处理 item 删除后再编辑
当用户删除当前编辑的 ScheduleShift item 后,再次双击 / 接收编辑信号,会用陈旧指针访问。
**修复方案**:在 picscene 的 itemRemoved 中关闭对应编辑器。
### 4.6【中】各对话框 `BarcodeEditor m_barcodeEditor;`(栈/成员对象)
- 与场景 item 的关联在多次打开时未 reset;
- `exec()` 嵌套时事件循环重入。
**修复方案**:每次进入 `exec()` 前清状态;嵌套场景禁用相关按钮。
### 4.7【中】ImageLoader 模态弹出与 item 创建顺序
若用户取消选择,但仍走「创建 item」分支,会插入空图像 item(与 3.8 联动)。
**修复方案**:取消则不 `addItem`。
### 4.8【低】ViewModels.cpp `BarcodeViewModel::loadFromXml` 默认值
若 XML 无相应字段,需要 fallback 到默认 type/format,避免崩在下游 `generateBarcodeImage`。
---
## 五、testprintwidget 相关问题
### 5.1 ⚠️【高】testprintwidget.cpp:55 / .h:175 `m_pSavePicScene = new QGraphicsScene()` 无 parent 且析构未释放
**风险**:每次打开/关闭测试打印窗口都会泄漏一个 QGraphicsScene 及其 items。
**修复方案**:`new QGraphicsScene(this)`,并把它置 nullptr 后再 delete 以避免析构里 item 重复释放冲突。
### 5.2 ⚠️【高】testprintwidget.cpp:1077-1092 / 1461-1477 `codeItem_Operation` 删除后未置空(与 4.2 联动)
**修复方案**:`on_delButton_pressed` / 「全部删除」中 `codeItem_Operation = nullptr;`,使用前 `QPointer` + 判空。
### 5.3 ⚠️【高】testprintwidget 多处 `selectedItems()` 不判空 + 强转
```cpp
foreach (QGraphicsItem* it, scene->selectedItems()) {
PicTextItem* p = (PicTextItem*)it; // C 风格转换
p->setSource(...); // 若 it 不是 PicTextItem 则崩
}
```
**修复方案**:`auto* p = qgraphicsitem_cast<PicTextItem*>(it); if(!p) continue;`。
### 5.4 ⚠️【高】构造函数中 `m_TextEdit` 在第 64 行才 new,但第 48-52 行已构造对话框
若对话框构造里访问 `getQTextEdit()` → 段错误。
**修复方案**:把 `m_TextEdit = new QTextEdit(0);` 上移到第一个 dialog 之前。
### 5.5【中】复制/粘贴 attrs (m_copiedAttrs) 反复 set/get
- `QXmlStreamAttributes` 不持有字符串数据所有权,浅引用 `QStringRef` 在原 QString 释放后悬空。
- 推荐 `QMap<QString,QString>` 取代。
### 5.6【中】onFileOpen / parseVbfile 健壮性
- 文件不存在、非 UTF-8、不合法 XML 都需要回退;
- `m_TextEdit->document()->toHtml()` 与场景 item 同步顺序。
- 当前缺少 `if (!file.open(...)) return;` 与最后 `file.close()`。
### 5.7【中】saveFile / onSaveASFile 路径含中文/无写权限
- 应使用 `QSaveFile` 防止崩盘截断。
- `QFileDialog::getSaveFileName` 的过滤器要严格。
### 5.8【中】`eventFilter` 对象判断
```cpp
if (obj == m_TextEdit) { ... } else if (obj == ...) { ... }
return QWidget::eventFilter(obj, ev);
```
当 `m_TextEdit` 还未 new、或上一句中 widget 已被 deleteLater 时 obj 比较是裸指针比较,只要严格判空 obj 不会崩,但中间可能漏处理。
### 5.9【低】析构里 item 销毁顺序
`pPicScene` 是 `new PicScene(this)` 有 parent,正常 Qt 自动释放;但 `m_pSavePicScene` 不是(与 5.1)。
---
## 六、mainwidget 相关问题
> mainwidget.cpp/.h 由于篇幅与上下文限制本次未全文阅读,下列基于 search 与已知调用链推断,标注为「待复核」。
### 6.1 ⚠️【高·待复核】onPreviewScene / handleEmptySize:当 `pPicScene` 为空 or `items().isEmpty()` 时直接走绘制路径
**风险**:未判空 → 段错误。
**修复方案**:进入函数顶部 `if(!pPicScene || pPicScene->items().isEmpty()) return;`。
### 6.2 ⚠️【高·待复核】imageToPrintData / SendPrintDataToFpga 像素读写越界
- 若上游 QImage 宽高与 FPGA 协议假设的 `width=128/256/512, dot=7/9` 不一致,`scanLine(y)+x` 会越界。
- `bytesPerLine()` 与 `width()/8` 不一定相等(行对齐)。
**修复方案**:使用 `img.bits()` 时严格按 `bytesPerLine()` 走,且校验 `img.format()` 是否为期望格式(必要时 `img = img.convertToFormat(QImage::Format_Mono)`)。
### 6.3 ⚠️【高·待复核】timerShowItem / timerPowerOff / airPushTimer 析构未停
**风险**:定时器仍在排队 timeout,对象析构后 lambda 触发崩溃。
**修复方案**:析构里逐一 `stop()` 并 `disconnect()`。
### 6.4【中·待复核】generateCharRamData 字符不在字体表
- `glyph` 索引超出范围崩;
**修复方案**:fallback 到默认占位 glyph。
### 6.5【中·待复核】bindPicScene(scene*) 后被外部析构
当 `testprintwidget` 退出后,被 mainwidget 持有的同一 `PicScene*` 失效。
**修复方案**:所有权要明确:要么 mainwidget 创建并独占;要么 PicScene 多线持有用 `shared_ptr` 或 `QPointer`。
### 6.6【中·待复核】m_setNumDlg / m_fileManageWidget 懒加载重复 new
**修复方案**:`if(!m_xxx) m_xxx = new ...(this);`。
### 6.7【低·待复核】updateSysStatusToUI 索引/越界
- 状态枚举数组按 index 取 label 时需要 `qBound`。
---
## 七、InkJetPrinter 相关问题
### 7.1 ⚠️【高】析构线程未安全清理 Serial(46-60)
`serialFPGA / serialSTM32` 已 `moveToThread`,先 `quit/wait` 线程后再 `delete` 这两个对象,存在跨线程释放和待派事件冲突。
**修复方案**:
```cpp
~InkJetPrinter() {
if (serialFPGA) { serialFPGA->disconnect(); serialFPGA->deleteLater(); }
if (serialSTM32){ serialSTM32->disconnect(); serialSTM32->deleteLater(); }
if (m_serialThreadFPGA && m_serialThreadFPGA->isRunning()) { m_serialThreadFPGA->quit(); m_serialThreadFPGA->wait(3000); }
if (m_serialThreadSTM32 && m_serialThreadSTM32->isRunning()) { m_serialThreadSTM32->quit(); m_serialThreadSTM32->wait(3000); }
// 不要再 delete serialFPGA/serialSTM32
}
```
### 7.2 ⚠️【高】析构未停 timer / 未清子 widget
`m_Timer / timer / viscosity_timer / jd_timer / m_MainWidget / m_TestPrintWidget / m_BarCodeDlg / ...` 都是 `new ... (this)` 期望 Qt 父子链清理,但 timer 不会因 parent 而 stop(事件循环里仍可能正在派发 timeout)。
**修复方案**:析构开头先 stop 所有 timer 并 disconnect。
### 7.3 ⚠️【高】QStackedWidget 切页面共享 PicScene 同步
切页面后 testprintwidget 修改了 `pPicScene`,mainwidget 缓存的 item 引用会失效。
**修复方案**:用一个全局 `SceneManager` 单点维护 PicScene 与 items;或在切页面时显式重新绑定。
### 7.4 ⚠️【高】saveFPGA*** 槽函数 `unsigned char* data` 协议解析
- 未校验 `len`;偏移 `data[5+i]` 直接访问;
- 协议帧若被截断,会读到栈/堆外。
**修复方案**:所有偏移加 `if (len < kHeader + N) return;` 校验。
### 7.5【中】lambda 捕获 raw pointer
e.g. `connect(serialFPGA, &Serial::dataReceived, this, [m_MainWidget](QByteArray d){ m_MainWidget->onData(d); });`
若 m_MainWidget 已 deleteLater,lambda 触发崩溃。
**修复方案**:捕获 `QPointer<MainWidget> p = m_MainWidget;`,使用前判空。
### 7.6【中】showScheduleShiftsEditor / openDatetimeEditor 接收 item* 异步使用
**修复方案**:参数改 `QPointer<PicTextItem>`。
### 7.7【中】restartSerialPort / closeSerial 并发竞争
两路串口都可能在主线程和子线程同时被关闭/重启,无锁。
**修复方案**:增加 `QMutex` 或调度到串口所在线程后再操作(`QMetaObject::invokeMethod(serial, ..., Qt::QueuedConnection)`)。
### 7.8【中】子 widget 是否重复 new
代码里有 `if(!m_xxx) m_xxx = new ...` 不一致,部分入口直接 `new`,重复 new 会泄漏旧的。
**修复方案**:统一懒加载工厂。
### 7.9【低】部分 `deleteLater` 后立刻设置成员为 nullptr 可避免后续再访问悬空。
---
## 八、修复优先级与建议步骤
按修复成本与收益排序,建议按以下顺序处理:
1. **P0 立即修(避免 crash)**
- 3.1 / 3.2 / 3.3 / 3.4 / 3.5 / 3.7 :scene 目录 Item 类的空指针、索引越界、UB 返回值;改动小、稳定性收益最大。
- 5.1 / 5.2 / 5.3 / 5.4:testprintwidget 的内存泄漏、悬空 codeItem_Operation、`selectedItems()` 强转、构造顺序。
- 7.1 / 7.2:InkJetPrinter 析构里的线程与 timer。
2. **P1 短期修(避免数据错误/偶发崩溃)**
- 3.8 / 3.9 / 3.11;4.1 / 4.2 / 4.5;5.5 / 5.6;6.1 / 6.2 / 6.3;7.3 / 7.4 / 7.5。
3. **P2 后续整改(架构层)**
- 引入 `QPointer<>`/`shared_ptr` 替换裸指针存储 item;
- 把对话框对 item 的引用改为「id + 查表」模式,避免任何悬空;
- 串口/线程操作集中到独立 service 类,加锁;
- 统一 PicScene 所有权(建议 InkJetPrinter 持有,子 widget 只 bind)。
4. **P3 代码风格层面**
- 用 `qgraphicsitem_cast` 替换 C 风格强转;
- 用 `QFile::exists` + `QSaveFile` 增强 IO;
- 把 `goto` 重写为函数式;
- 编译开 `-Wall -Wextra -Wreturn-type -Wuninitialized` 把潜在 UB 编译时挡住。
---
## 九、回归验证建议
修完后建议至少跑通以下场景,能稳定不崩即视为通过:
1. **空场景**:打开测试打印 → 不放任何 item → 直接预览/打印 → 关闭。
2. **复制粘贴**:连续复制粘贴 50 次 + 删除 + 撤销。
3. **极端文本**:item 文本设为「空」「单字符」「100 字符」「中英混排」「Emoji」「不存在的字体」。
4. **图片项**:选不存在的图片路径 / 损坏图 / 巨型图(>4096px)。
5. **条码 / 二维码**:编辑器打开期间,外部点「全部删除」/ 加载模板。
6. **ScheduleShifts**:打开班次编辑后删除 item 再返回保存。
7. **串口 / FPGA**:拔掉 USB → 触发 `restartSerialPort` → 重新插入。
8. **进入退出**:连续 30 次 `InkJetPrinter` show/hide / 退出程序,无内存增长且无 crash。
9. **跨页切换**:mainwidget ↔ testprintwidget ↔ systemmanage 反复切换 100 次。
10. **打印**:连续打印 1000 次(含日期变量、计数器、班次),观察 `imageToPrintData` 是否越界。
---
## 十、文档说明
- 本文件路径:`doc/Scene_Modules_Bug_Inspection_Report.md`
- 排查未涉及代码的修改,所有「修复方案」仅作为后续提交的参考。
- 部分 mainwidget 相关 6.1–6.7 项标注为「待复核」,原因是受当前会话上下文限制未能逐行通读 mainwidget.cpp 全文。建议在确认上述 P0/P1 修复方向后,再单独安排对 `mainwidget.cpp/h` 的全文复核(建议输出 6.x 项的精确行号与代码片段)。
> **下一步**:请检查本文档,确认修复方向与优先级;确认后我再按 P0 → P1 顺序提交代码改动。
# 文档审阅
---
## 十一、本次会话修复结论(2026-05-10 追加)
### 11.1 已修复项
- **3.2 pictextitem.cpp `mouseReleaseEvent` / `keyPressEvent` 未判空 `scene()`**:已在两个事件函数入口加 `if(!scene())` 提前返回,避免 item 离场期间触发 `scene()->update()` / `scene()->selectedItems()` 解引用空指针。
### 11.2 复核结论(无需修复)
- **3.11 piccodeitem 中 `m_dateVarTimer` 风险**:复核 `scene/piccodeitem.h` 后确认当前类中**并不存在** `m_dateVarTimer` 成员;该项原为基于代码模式的预防性提示,目前不构成实际隐患,标记为「不适用」。
### 11.3 用户已自行修复(来自上文「澄清」栏汇总)
- 3.1 `clampItemPos` 未判空 `scene()` —— 已修复
- 3.3 `strToImage` 0 宽/0 高 QImage —— 已修复
- 3.5 `m_currCounterValues` 越界 —— 已在 `updateVariants` 入口校验
- 3.7 `setBoldMultiple` 缺少返回值 —— 已修复
- 3.8 PicImageItem 加载失败仍使用旧对象 —— 已修复
- 3.9 PicScheduleShiftItem 返回临时对象引用 —— 已修复
### 11.4 用户决定不修复(按澄清栏)
- 3.4 `itemChange()` clamp 逻辑未生效(含 `ItemSceneChange` 笔误)—— 用户表示该段代码使用已久未发现异常,暂不修复
- 3.6 `setSource` 忽略入参 —— `source/setSource` 机制准备弃用,不予修复
- 3.10 文档对象 stylesheet —— 全面自绘,不予修复
- 3.12 `clipImage` 使用 goto + `originRect` 未使用 —— 不予修复
### 11.5 仍待处理项(按优先级)
- **P0**:5.1 / 5.2 / 5.3 / 5.4(testprintwidget 内存泄漏、悬空 `codeItem_Operation`、C 风格强转、构造顺序);7.1 / 7.2(InkJetPrinter 析构线程与 timer 清理)。
- **P1**:4.1 / 4.2 / 4.5(编辑器持有 item 裸指针);5.5 / 5.6;6.1 / 6.2 / 6.3(mainwidget 待全文复核);7.3 / 7.4 / 7.5。
- **P2/P3**:架构层 `QPointer` / `id+查表` 替换裸指针存储、串口/线程加锁、`qgraphicsitem_cast` 替换 C 风格强转等。
> **下一步建议**:可单独安排对 `mainwidget.cpp/h` 的全文复核,输出 6.1–6.7 项的精确行号与代码片段后再决定修复。其余 P0 项(testprintwidget / InkJetPrinter)可按本节排序逐项推进。
+1
View File
@@ -1036,5 +1036,6 @@
<file>image/set3.png</file> <file>image/set3.png</file>
<file>image/down_white.png</file> <file>image/down_white.png</file>
<file>image/up_white.png</file> <file>image/up_white.png</file>
<file>styles.qss</file>
</qresource> </qresource>
</RCC> </RCC>
+10 -1
View File
@@ -44,7 +44,9 @@ BarcodeEditor::BarcodeEditor(QWidget *parent) :
ui(new Ui::BarcodeEditor) ui(new Ui::BarcodeEditor)
{ {
ui->setupUi(this); ui->setupUi(this);
setWindowModality(Qt::NonModal);
applyStyleSheet();
m_dateFormatEditor = new DateFormatEditor(); m_dateFormatEditor = new DateFormatEditor();
m_dateReplaceEditor = new DateReplaceEditor(); m_dateReplaceEditor = new DateReplaceEditor();
m_counterEditor = new CounterEditor(); m_counterEditor = new CounterEditor();
@@ -382,6 +384,13 @@ bool BarcodeEditor::eventFilter(QObject *obj, QEvent *event)
return false; return false;
} }
void BarcodeEditor::applyStyleSheet()
{
// QFile stylesFile(":/styles.qss");
// QTextStream ts(&stylesFile);
// setStyleSheet(ts.readAll());
}
void BarcodeEditor::onContentChanged(QString content) void BarcodeEditor::onContentChanged(QString content)
{ {
const DateItemModel& dateVm = m_vm.dateViewModel; const DateItemModel& dateVm = m_vm.dateViewModel;
+2
View File
@@ -185,6 +185,8 @@ protected:
private: private:
Ui::BarcodeEditor *ui; Ui::BarcodeEditor *ui;
void applyStyleSheet();
BarcodeViewModel m_vm; BarcodeViewModel m_vm;
DateFormatEditor *m_dateFormatEditor = nullptr; DateFormatEditor *m_dateFormatEditor = nullptr;
+31 -1
View File
@@ -173,6 +173,36 @@ QSpinBox::down-arrow {
<height>471</height> <height>471</height>
</rect> </rect>
</property> </property>
<property name="styleSheet">
<string notr="true"> QTabWidget {
border: none;
background-color: #7B838E;
}
QTabBar::tab {
min-width: 90px;
min-height: 45px;
padding: 4px 10px;
margin-right: 4px;
border: 1px solid #dddddd;
border-bottom: none;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
background-color: #5396C1;
color: #ffffff;
}
QTabWidget::tab-bar {
top: -3px;
left: 10px;
}
QTabBar::tab:selected {
background-color: #6bb1e0;
color: #ffffff;
box-shadow: 0 -3px 8px rgba(44, 125, 232, 0.25);
}</string>
</property>
<property name="currentIndex"> <property name="currentIndex">
<number>1</number> <number>1</number>
</property> </property>
@@ -270,7 +300,7 @@ QSpinBox::down-arrow {
<x>420</x> <x>420</x>
<y>120</y> <y>120</y>
<width>261</width> <width>261</width>
<height>291</height> <height>231</height>
</rect> </rect>
</property> </property>
<property name="title"> <property name="title">
+104 -2
View File
@@ -20,9 +20,108 @@
<string>Dialog</string> <string>Dialog</string>
</property> </property>
<property name="styleSheet"> <property name="styleSheet">
<string notr="true">QWidget{ <string notr="true">QWidget {
font-size: 16px; font-size: 16px;
}</string> }
QPushButton#closeBtn, QPushButton#checkBtn{
background-color: #E6F7FF;
border: 1px solid #87CEEB;
border-radius: 4px;
font-size: 24px;
color: #228B22;
}
QPushButton#closeBtn:hover,QPushButton#checkBtn:hover{
background-color: #D1E9FF;
}
QPushButton#closeBtn:pressed,QPushButton#checkBtn:pressed{
background-color: #BAE7FF;
}
QPushButton {
background-color: #5396C1;
color: #ffffff;
border: 1px solid #000000;
border-radius: 3px;
font-size: 18px;
font-weight:500;
width: 180px;
height: 90px;
}
QPushButton:hover {
background-color: #5396C1;
}
QPushButton:disabled {
background-color: #7C828E;
color: #606060;
border-color: #808080;
}
QComboBox {
border: 1px solid #000000;
border-radius: 3px;
font-size: 14px;
width: 180px;
height: 90px;
padding-left: 10px; /* 文字左边距,避免紧贴边缘 */
}
/* 下拉箭头区域 */
QComboBox::drop-down {
border: 1px solid #333333;
border-radius: 3px;
width: 30px; /* 箭头区域宽度 */
background-color: #5396C1;
}
/* 下拉箭头图标 */
QComboBox::down-arrow {
image: url(image/down_white.png);
width: 16px; /* 图标显示宽度 */
height: 16px; /* 图标显示高度 */
}
/* 悬停状态 */
QComboBox:hover {
background-color: #efefef;
}
// 下拉框
QComboBox QAbstractItemView {
border: 1px solid #000000;
border-radius: 3px;
font-size: 14px;
selection-background-color: #5396C1; /* 选中项背景色(稍深) */
selection-color: #ffffff;
}
QSpinBox::up-button {
width: 16px; /* 图标显示宽度 */
height: 16px; /* 图标显示高度 */
border: 1px solid #333333;
border-radius: 3px;
background-color: #5396C1;
}
QSpinBox::up-arrow {
image: url(image/up_white.png);
}
QSpinBox::down-button {
width: 16px; /* 图标显示宽度 */
height: 16px; /* 图标显示高度 */
border: 1px solid #333333;
border-radius: 3px;
background-color: #5396C1;
}
QSpinBox::down-arrow {
image: url(image/down_white.png);
}
</string>
</property> </property>
<widget class="QSpinBox" name="BarCodeScale"> <widget class="QSpinBox" name="BarCodeScale">
<property name="geometry"> <property name="geometry">
@@ -42,6 +141,9 @@
<property name="readOnly"> <property name="readOnly">
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="keyboardTracking"> <property name="keyboardTracking">
<bool>false</bool> <bool>false</bool>
</property> </property>
+103 -1
View File
@@ -22,7 +22,109 @@
<property name="styleSheet"> <property name="styleSheet">
<string notr="true">QWidget{ <string notr="true">QWidget{
font-size: 16px; font-size: 16px;
}</string> }
QWidget {
font-size: 16px;
}
QPushButton#closeBtn, QPushButton#checkBtn{
background-color: #E6F7FF;
border: 1px solid #87CEEB;
border-radius: 4px;
font-size: 24px;
color: #228B22;
}
QPushButton#closeBtn:hover,QPushButton#checkBtn:hover{
background-color: #D1E9FF;
}
QPushButton#closeBtn:pressed,QPushButton#checkBtn:pressed{
background-color: #BAE7FF;
}
QPushButton {
background-color: #5396C1;
color: #ffffff;
border: 1px solid #000000;
border-radius: 3px;
font-size: 18px;
font-weight:500;
width: 180px;
height: 90px;
}
QPushButton:hover {
background-color: #5396C1;
}
QPushButton:disabled {
background-color: #7C828E;
color: #606060;
border-color: #808080;
}
QComboBox {
border: 1px solid #000000;
border-radius: 3px;
font-size: 14px;
width: 180px;
height: 90px;
padding-left: 10px; /* 文字左边距,避免紧贴边缘 */
}
/* 下拉箭头区域 */
QComboBox::drop-down {
border: 1px solid #333333;
border-radius: 3px;
width: 30px; /* 箭头区域宽度 */
background-color: #5396C1;
}
/* 下拉箭头图标 */
QComboBox::down-arrow {
image: url(image/down_white.png);
width: 16px; /* 图标显示宽度 */
height: 16px; /* 图标显示高度 */
}
/* 悬停状态 */
QComboBox:hover {
background-color: #efefef;
}
// 下拉框
QComboBox QAbstractItemView {
border: 1px solid #000000;
border-radius: 3px;
font-size: 14px;
selection-background-color: #5396C1; /* 选中项背景色(稍深) */
selection-color: #ffffff;
}
QSpinBox::up-button {
width: 16px; /* 图标显示宽度 */
height: 16px; /* 图标显示高度 */
border: 1px solid #333333;
border-radius: 3px;
background-color: #5396C1;
}
QSpinBox::up-arrow {
image: url(image/up_white.png);
}
QSpinBox::down-button {
width: 16px; /* 图标显示宽度 */
height: 16px; /* 图标显示高度 */
border: 1px solid #333333;
border-radius: 3px;
background-color: #5396C1;
}
QSpinBox::down-arrow {
image: url(image/down_white.png);
}
</string>
</property> </property>
<widget class="QComboBox" name="comboBoxType"> <widget class="QComboBox" name="comboBoxType">
<property name="geometry"> <property name="geometry">
+8 -1
View File
@@ -257,7 +257,14 @@ void ScheduleShiftsEditor::applyStyleSheet()
box-shadow: 0 -3px 8px rgba(44, 125, 232, 0.25); box-shadow: 0 -3px 8px rgba(44, 125, 232, 0.25);
} }
)"; )";
this->setStyleSheet(styleSheet);
// QFile stylesFile(":/styles.qss");
// QTextStream ts(&stylesFile);
// auto str = ts.readAll();
// setStyleSheet(str);
// ui->tabWidget->setStyleSheet(str);
// // this->setStyleSheet(styleSheet);
} }
SelectedStyleTimeEdit::SelectedStyleTimeEdit(QWidget *parent) SelectedStyleTimeEdit::SelectedStyleTimeEdit(QWidget *parent)
+78 -4
View File
@@ -13,6 +13,77 @@
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>Form</string>
</property> </property>
<property name="styleSheet">
<string notr="true">
QPushButton#closeBtn, QPushButton#checkBtn{
background-color: #E6F7FF;
border: 1px solid #87CEEB;
border-radius: 4px;
font-size: 24px;
color: #228B22;
}
QPushButton#closeBtn:hover,QPushButton#checkBtn:hover{
background-color: #D1E9FF;
}
QPushButton#closeBtn:pressed,QPushButton#checkBtn:pressed{
background-color: #BAE7FF;
}
QPushButton {
background-color: #5396C1;
color: #ffffff;
border: 1px solid #000000;
border-radius: 3px;
font-size: 18px;
font-weight:500;
width: 100px;
height: 50px;
}
QPushButton:hover {
background-color: #5396C1;
}
QPushButton:disabled {
background-color: #7C828E;
color: #606060;
border-color: #808080;
}
QTabWidget {
border: 1px solid #dddddd;
background-color: #ffffff;
}
QTabBar::tab:focus {
outline: none;
}
QTabBar::tab {
min-width: 90px;
min-height: 45px;
padding: 4px 10px;
margin-right: 4px;
border: 1px solid #dddddd;
border-bottom: none;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
background-color: #5396C1;
color: #ffffff;
}
QTabWidget::tab-bar {
top: -3px;
left: 10px;
}
QTabBar::tab:selected {
background-color: #6bb1e0;
color: #ffffff;
box-shadow: 0 -3px 8px rgba(44, 125, 232, 0.25);
}</string>
</property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<property name="leftMargin"> <property name="leftMargin">
<number>0</number> <number>0</number>
@@ -48,8 +119,8 @@
</property> </property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>62</width> <width>102</width>
<height>42</height> <height>52</height>
</size> </size>
</property> </property>
<property name="maximumSize"> <property name="maximumSize">
@@ -91,8 +162,8 @@
</property> </property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>62</width> <width>102</width>
<height>42</height> <height>52</height>
</size> </size>
</property> </property>
<property name="maximumSize"> <property name="maximumSize">
@@ -139,6 +210,9 @@
</item> </item>
<item row="1" column="0" colspan="4"> <item row="1" column="0" colspan="4">
<widget class="QTabWidget" name="tabWidget"> <widget class="QTabWidget" name="tabWidget">
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="currentIndex"> <property name="currentIndex">
<number>1</number> <number>1</number>
</property> </property>
+91
View File
@@ -0,0 +1,91 @@
/*
QPushButton {
background-color: #5396C1;
color: #ffffff;
border: 1px solid #000000;
border-radius: 3px;
font-size: 18px;
font-weight:500;
width: 180px;
height: 90px;
}
QPushButton:hover {
background-color: #5396C1;
}
QPushButton:disabled {
background-color: #7C828E;
color: #606060;
border-color: #808080;
}
*/
QPushButton#closeBtn, QPushButton#checkBtn{
background-color: #E6F7FF;
border: 1px solid #87CEEB;
border-radius: 4px;
font-size: 24px;
color: #228B22;
}
QPushButton#closeBtn:hover,QPushButton#checkBtn:hover{
background-color: #D1E9FF;
}
QPushButton#closeBtn:pressed,QPushButton#checkBtn:pressed{
background-color: #BAE7FF;
}
QPushButton {
background-color: #5396C1;
color: #ffffff;
border: 1px solid #000000;
border-radius: 3px;
font-size: 18px;
font-weight:500;
width: 100px;
height: 50px;
}
QPushButton:hover {
background-color: #5396C1;
}
QPushButton:disabled {
background-color: #7C828E;
color: #606060;
border-color: #808080;
}
QTabWidget::pane {
border: 1px solid #dddddd;
background-color: #ffffff;
}
QTabBar::tab:focus {
outline: none;
}
QTabBar::tab {
min-width: 90px;
min-height: 45px;
padding: 4px 10px;
margin-right: 4px;
border: 1px solid #dddddd;
border-bottom: none;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
background-color: #5396C1;
color: #ffffff;
}
QTabWidget::tab-bar {
top: -3px;
left: 10px;
}
QTabBar::tab:selected {
background-color: #6bb1e0;
color: #ffffff;
box-shadow: 0 -3px 8px rgba(44, 125, 232, 0.25);
}
+130
View File
@@ -0,0 +1,130 @@
QWidget {
font-size: 16px;
}
QPushButton#closeBtn, QPushButton#checkBtn{
background-color: #E6F7FF;
border: 1px solid #87CEEB;
border-radius: 4px;
font-size: 24px;
color: #228B22;
}
QPushButton#closeBtn:hover,QPushButton#checkBtn:hover{
background-color: #D1E9FF;
}
QPushButton#closeBtn:pressed,QPushButton#checkBtn:pressed{
background-color: #BAE7FF;
}
QPushButton {
background-color: #5396C1;
color: #ffffff;
border: 1px solid #000000;
border-radius: 3px;
font-size: 18px;
font-weight:500;
width: 100px;
height: 50px;
}
QPushButton:hover {
background-color: #5396C1;
}
QPushButton:disabled {
background-color: #7C828E;
color: #606060;
border-color: #808080;
}
QComboBox {
border: 1px solid #000000;
border-radius: 3px;
font-size: 14px;
width: 100px;
height: 50px;
padding-left: 10px; /* 文字左边距,避免紧贴边缘 */
}
/* 下拉箭头区域 */
QComboBox::drop-down {
border: 1px solid #333333;
border-radius: 3px;
width: 30px; /* 箭头区域宽度 */
background-color: #5396C1;
}
/* 下拉箭头图标 */
QComboBox::down-arrow {
image: url(:/image/down_white.png);
width: 16px; /* 图标显示宽度 */
height: 16px; /* 图标显示高度 */
}
/* 悬停状态 */
QComboBox:hover {
background-color: #efefef;
}
// 下拉框
QComboBox QAbstractItemView {
border: 1px solid #000000;
border-radius: 3px;
font-size: 14px;
selection-background-color: #5396C1; /* 选中项背景色(稍深) */
selection-color: #ffffff;
}
QSpinBox::up-button {
width: 16px; /* 图标显示宽度 */
height: 16px; /* 图标显示高度 */
border: 1px solid #333333;
border-radius: 3px;
background-color: #5396C1;
}
QSpinBox::up-arrow {
image: url(:/image/up_white.png);
}
QSpinBox::down-button {
width: 16px; /* 图标显示宽度 */
height: 16px; /* 图标显示高度 */
border: 1px solid #333333;
border-radius: 3px;
background-color: #5396C1;
}
QSpinBox::down-arrow {
image: url(:/image/down_white.png);
}
QTabWidget::pane {
border: none;
background-color: #7B838E;
}
QTabBar::tab {
min-width: 90px;
min-height: 45px;
padding: 4px 10px;
margin-right: 4px;
border: 1px solid #dddddd;
border-bottom: none;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
background-color: #5396C1;
color: #ffffff;
}
QTabWidget::tab-bar {
top: -3px;
left: 10px;
}
QTabBar::tab:selected {
background-color: #6bb1e0;
color: #ffffff;
box-shadow: 0 -3px 8px rgba(44, 125, 232, 0.25);
}
+1
View File
@@ -1647,6 +1647,7 @@ void TestPrintWidget::showBarcodeEditor(bool restoreViewModel)
if(restoreViewModel) if(restoreViewModel)
m_barcodeEditor.restoreViewModel(); m_barcodeEditor.restoreViewModel();
m_barcodeEditor.setWindowModality(Qt::WindowModal);
m_barcodeEditor.show(); m_barcodeEditor.show();
} }