From 5b28791bbfd7fddfc7bbd6e484b832c987a354f7 Mon Sep 17 00:00:00 2001 From: lichongyuan Date: Wed, 18 Sep 2024 09:53:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=A2=9E=E5=8A=A0=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=8A=A0=E8=BD=BD=E5=A4=B1=E8=B4=A5=E6=8F=90?= =?UTF-8?q?=E9=86=92=EF=BC=9B=E6=96=B0=E5=A2=9E=E5=9B=BE=E6=A0=87=EF=BC=9B?= =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=AF=B9=E6=95=B0=E6=8D=AE=E5=BA=93=E5=92=8C?= =?UTF-8?q?QProcess=E7=9A=84=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 9 + LaunchRegisterKey.pro | 5 +- common.cpp | 103 +++++++---- common.h | 10 ++ main.cpp | 11 +- network/udpdatahandler.cpp | 5 +- resources.qrc | 2 + resources/icon.ico | Bin 0 -> 167885 bytes resources/icon.png | Bin 0 -> 3849 bytes ui/countdownmessagebox.cpp | 2 +- ui/settingwgt.cpp | 2 + ui/widget.cpp | 339 +++++++++++++++++++++++++++++++------ ui/widget.h | 11 +- utils/interface.h | 1 + 14 files changed, 409 insertions(+), 91 deletions(-) create mode 100644 resources/icon.ico create mode 100644 resources/icon.png diff --git a/.gitignore b/.gitignore index 30a4a93..aba7feb 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,12 @@ LaunchRegisterKey.vcxproj.filters .vs/LaunchRegisterKey/v17/ipch/AutoPCH/e407f47aecf49074/UDPDATATHREAD.ipch debug/ release/ +.vs/ +LaunchRe.82F475D3.tlog/ +qmake/ +LaunchRegisterKey.log +LaunchRegisterKey.sln +qt_work.log +vc141.pdb +vcpkg.applocal.log +CMakeLists.txt diff --git a/LaunchRegisterKey.pro b/LaunchRegisterKey.pro index bc8c760..1907df6 100644 --- a/LaunchRegisterKey.pro +++ b/LaunchRegisterKey.pro @@ -1,9 +1,12 @@ +QMAKE_CXXFLAGS_RELEASE = $$QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO +QMAKE_LFLAGS_RELEASE = $$QMAKE_LFLAGS_RELEASE_WITH_DEBUGINFO + QT += core gui network sql greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += c++11 - +RC_ICONS = resources/icon.ico # The following define makes your compiler emit warnings if you use # any Qt feature that has been marked deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the diff --git a/common.cpp b/common.cpp index 7d8cf95..03ca7dc 100644 --- a/common.cpp +++ b/common.cpp @@ -1,4 +1,5 @@ -#include "common.h" +#pragma execution_character_set("utf-8") +#include "common.h" #include #include #include @@ -14,39 +15,47 @@ Common::Common() bool Common::loadConfig(const QString &filePath) { - // 提取网络配置 - QString iniFilePath = filePath + "/network.ini"; - QSettings *configIni = new QSettings(iniFilePath, QSettings::IniFormat); - m_ipAddr = configIni->value("network/ip").toString(); - m_srcPort = configIni->value("network/src_port").toInt(); - m_dstPort = configIni->value("network/dst_port").toInt(); - - m_configFilePath = filePath + "/Config.json"; - std::ifstream jsonStream(m_configFilePath.toLocal8Bit()); - m_configJson = json::parse(jsonStream); - jsonStream.close(); - // 提取工作模式 - auto workmodes = m_configJson.at("workmode"); - for (const auto& workmode : workmodes) + try { - QString name = QString::fromStdString(workmode["name"]); - int index = workmode["index"]; - QString path = QString::fromStdString(workmode["exe_path"]); - QString reworkPath = QString::fromStdString(workmode["rework_exe_path"]); - m_modeConfigs[index] = ModeConfig{index, name, path, reworkPath}; + // 提取网络配置 + QString iniFilePath = filePath + "/network.ini"; + QSettings* configIni = new QSettings(iniFilePath, QSettings::IniFormat); + m_ipAddr = configIni->value("network/ip").toString(); + m_srcPort = configIni->value("network/src_port").toInt(); + m_dstPort = configIni->value("network/dst_port").toInt(); + + m_configFilePath = filePath + "/Config.json"; + std::ifstream jsonStream(m_configFilePath.toLocal8Bit()); + m_configJson = json::parse(jsonStream); + jsonStream.close(); + // 提取工作模式 + auto workmodes = m_configJson.at("workmode"); + for (const auto& workmode : workmodes) + { + QString name = QString::fromStdString(workmode["name"]); + int index = workmode["index"]; + QString path = QString::fromStdString(workmode["exe_path"]); + QString reworkPath = QString::fromStdString(workmode["rework_exe_path"]); + m_modeConfigs[index] = ModeConfig{ index, name, path, reworkPath }; + } + // 提取产品配置 + auto products = m_configJson.at("products"); + for (const auto& product : products) + { + int index = product["index"]; + bool selected = product["selected"]; + int mode = product["mode"]; + QString formatStr = QString::fromStdString(product["format"]); + m_productConfigs[index] = ProductConfig{ index, static_cast(mode), selected, formatStr }; + } + FilterSelectedProducts(); + return true; } - // 提取产品配置 - auto products = m_configJson.at("products"); - for (const auto& product : products) - { - int index = product["index"]; - bool selected = product["selected"]; - int mode = product["mode"]; - QString formatStr = QString::fromStdString(product["format"]); - m_productConfigs[index] = ProductConfig{index, static_cast(mode), selected, formatStr}; + catch(const std::exception& e) + { + LError(QString("Load config file error: %1").arg(e.what())); + return false; } - FilterSelectedProducts(); - return true; } bool Common::saveConfig(const QMap &productsCfg, const QMap &workmodeCfg) @@ -79,6 +88,18 @@ int Common::getDstPort() const return m_dstPort; } +const QString Common::getCurrWorkModeStr() const +{ + if (m_currentMode.load() == -1) + { + return getWorkModeStr(-1); + } + + QString reStr = m_isRepair.load() == 1 ? "反修" : "正常"; + QString logStr = QString("%1-%2").arg(getWorkModeStr(m_currentMode.load())).arg(reStr); + return logStr; +} + bool Common::saveConfig() { std::ofstream outFile(m_configFilePath.toLocal8Bit()); @@ -156,3 +177,23 @@ void Common::setCurrentWorkMode(const int workMode) { m_currentMode.store(workMode); } + +bool Common::getRepairFlag() const +{ + return m_isRepair.load(); +} + +void Common::setRepairFlag(const bool isRepair) +{ + m_isRepair.store(isRepair); +} + +QString Common::getWorkModeStr(int mode) +{ + static QMap modeMap = { {-1, "未连接"}, {2, "GLOBAL_M2M"}, {3, "LOCAL_M2M"}}; + if (!modeMap.contains(mode)) + { + return "unknown mode"; + } + return modeMap[mode]; +} diff --git a/common.h b/common.h index f378aef..f9a278a 100644 --- a/common.h +++ b/common.h @@ -9,6 +9,8 @@ #include "nlohmann/json.hpp" #include "EasySpdLog.h" +#define TEST_MODE + class Common : public QObject, public Singleton { Q_OBJECT @@ -25,6 +27,7 @@ public: // 获取端口号 int getSrcPort() const; int getDstPort() const; + const QString getCurrWorkModeStr() const; // 获取产品配置 QMap getProductConfigs(); // 设置产品配置 @@ -40,6 +43,11 @@ public: // 设置当前工作模式 void setCurrentWorkMode(const int workMode); + bool getRepairFlag() const; + + void setRepairFlag(const bool isRepair); + + static QString getWorkModeStr(int mode); signals: private: @@ -52,12 +60,14 @@ private: nlohmann::json m_configJson{}; QString m_currentWorkPath{}; // 当前工作路径 std::atomic m_currentMode{ -1 }; // 当前正在进行的工作模式 + std::atomic m_isRepair{ false }; QString m_ipAddr{}; int m_dstPort{}; int m_srcPort{}; QMap m_modeConfigs{}; // 模式配置 QMap m_productConfigs{}; // 产品配置 QMap m_selectedProduct{}; // 选择的产品 + std::atomic m_isLockSNLineEdit{false}; }; #endif // COMMON_H diff --git a/main.cpp b/main.cpp index 140b982..2e9baa8 100644 --- a/main.cpp +++ b/main.cpp @@ -1,7 +1,8 @@ -#include "widget.h" +#pragma execution_character_set("utf-8") +#include "widget.h" #include - +#include #include "common.h" #include "EasySpdLog.h" @@ -13,7 +14,11 @@ int main(int argc, char *argv[]) SetLogger(loggerPath.data()); // 加载配置文件 QString filePath = QApplication::applicationDirPath() + "/config"; - Common::GetInstance()->loadConfig(filePath); + if (!Common::GetInstance()->loadConfig(filePath)) + { + QMessageBox::warning(nullptr, "错误", "配置文件加载失败!\n 请检查配置文件是否存在或格式是否正确!"); + return 0; + } // Widget w; w.move(0, 0); diff --git a/network/udpdatahandler.cpp b/network/udpdatahandler.cpp index 77eb2f5..6316304 100644 --- a/network/udpdatahandler.cpp +++ b/network/udpdatahandler.cpp @@ -1,4 +1,5 @@ -#include "udpdatahandler.h" +#pragma execution_character_set("utf-8") +#include "udpdatahandler.h" #include #include "common.h" #include "EasySpdLog.h" @@ -91,6 +92,8 @@ void UdpDataHandler::recvData(const QByteArray &data) { emit GetDoneSig(ret); } + QString snMsg = QString("590200"); + m_udpWorker->sendData(snMsg.toLocal8Bit(), m_hostAddr, Common::GetInstance()->getDstPort()); break; } default: diff --git a/resources.qrc b/resources.qrc index 59b3064..cad6872 100644 --- a/resources.qrc +++ b/resources.qrc @@ -2,5 +2,7 @@ resources/folding.png resources/unfolding.png + resources/icon.ico + resources/icon.png diff --git a/resources/icon.ico b/resources/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a4eb66235bba830a495b09c20e149f393eafde18 GIT binary patch literal 167885 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 P>6}gHUY(A7Z@c*ac!-7o literal 0 HcmV?d00001 diff --git a/resources/icon.png b/resources/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..736a12aecf80aedf625a839e331b0b0e07cd0e25 GIT binary patch literal 3849 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 diff --git a/ui/widget.cpp b/ui/widget.cpp index 1ece6aa..4b28f0c 100644 --- a/ui/widget.cpp +++ b/ui/widget.cpp @@ -2,14 +2,20 @@ #include "widget.h" #include "ui_widget.h" +#include +#include + +#include #include #include #include -#include + #include #include #include #include +#include + #include "countdownmessagebox.h" #include "common.h" @@ -23,7 +29,6 @@ Widget::Widget(QWidget *parent) , m_settingDlg(new SettingWgt(this)) , m_udpDataThread(new UdpDataHandler(this)) , m_showSetting(false) - , m_isSwitching(false) { ui->setupUi(this); @@ -31,18 +36,27 @@ Widget::Widget(QWidget *parent) connect(m_settingDlg, &SettingWgt::SettingFinishSig, this, &Widget::on_pushButton_setting_clicked); connect(m_udpDataThread, &UdpDataHandler::GetDoneSig, this, &Widget::RegisterDoneSlot); connect(m_udpDataThread, &UdpDataHandler::GetConnStatusSig, this, &Widget::GetConnStatusSlot); + addLogItem("程序初始化成功!"); + + ProcessJudgment(); } Widget::~Widget() { delete ui; + if (m_db.isOpen()) + { + m_db.close(); + QSqlDatabase::removeDatabase(QSqlDatabase::defaultConnection); + } } void Widget::initUI() { - this->setWindowTitle(""); - //this->setWindowFlags(Qt::Window | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint | Qt::WindowStaysOnTopHint | Qt::CustomizeWindowHint); - this->setWindowFlags(Qt::Window | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint | Qt::CustomizeWindowHint); + this->setWindowTitle("LaunchRegisterKey"); + this->setWindowFlags(Qt::Window | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint | Qt::WindowStaysOnTopHint | Qt::CustomizeWindowHint); + //this->setWindowFlags(Qt::Window | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint | Qt::CustomizeWindowHint); + this->setWindowIcon(QIcon(":/resources/icon.ico")); ui->list_log->setEditTriggers(QAbstractItemView::NoEditTriggers); QHBoxLayout *layout = new QHBoxLayout(this); layout->addWidget(m_settingDlg); @@ -54,7 +68,11 @@ void Widget::initUI() void Widget::setSNLineEditFocus() { // 全选LineEdit并且焦点放在LineEdit上 - ui->lineEdit_snId->selectAll(); +#ifdef TEST_MODE + QString tmpSn = ui->lineEdit_snId->text(); + ui->lineEdit_snId->setText("1234567890123456"); +#endif + ui->lineEdit_snId->clear(); ui->lineEdit_snId->setFocus(); } @@ -63,6 +81,7 @@ int Widget::checkSNId(const QString &snId) if (snId.isEmpty()) { QMessageBox::warning(this, "产品SN错误", "产品SN为空! 请重新扫码!"); + addLogItem("产品SN为空! 请重新扫码!"); LWarn("Product SN id is empty!"); return SNEmptyError; } @@ -70,6 +89,7 @@ int Widget::checkSNId(const QString &snId) if (reg.indexIn(snId) != -1) { QMessageBox::information(this,"提示",QString("产品SN(%1)包含小写! 请重新扫码!").arg(snId)); + addLogItem(QString("产品SN(%1)包含小写! 请重新扫码!").arg(snId)); LWarn("Product SN contains lowercase!"); ui->lineEdit_snId->setText(""); return SNLowerCaseError; @@ -77,6 +97,7 @@ int Widget::checkSNId(const QString &snId) if (snId.contains(" ")) { QMessageBox::information(this,"提示",QString("产品SN(%1)包含空格! 请重新扫码!").arg(snId)); + addLogItem(QString("产品SN(%1)包含空格! 请重新扫码!").arg(snId)); LWarn("Product SN contains Spaces!"); ui->lineEdit_snId->setText(""); return SNSpaceError; @@ -86,15 +107,22 @@ int Widget::checkSNId(const QString &snId) int Widget::startIECSProgram(const QString& workPath, const QString& snId) { + LInfo("Function enter"); QProcess *process = new QProcess(this); - process->start(workPath); + connect(process, QOverload::of(&QProcess::finished), + process, &QProcess::deleteLater); + // 使用 cmd.exe 来启动进程并使用 start 命令使其脱离父进程 + QString command = QString("cmd.exe /C start \"\" \"%1\"").arg(workPath); + process->start(command); + if (!process->waitForStarted()) { - LError("Failed to start the program with arguments:{}", process->errorString()); + LError("Failed to start the program with arguments: {}", process->errorString()); + process->deleteLater(); + addLogItem(QString("%1启动失败!").arg(workPath)); return ExecIECSError; } - m_udpDataThread->sendSnId(snId); - m_isSwitching = false; + LInfo("Process started: {}", workPath); return NoError; } @@ -113,19 +141,31 @@ int Widget::checkForRepairProgram(const int mode, const QString& sn, bool& isFin int Widget::databaseQuery(const QString& databasePath, const QString& deviceSn, bool& isFind) { - // 创建和打开 SQLite 数据库 - QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); - db.setDatabaseName(databasePath); // 使用传入的数据库路径 + // 静态变量来保存上一次使用的数据库路径 + static QString previousDatabasePath; + // 如果数据库路径发生变动,关闭之前的连接并移除数据库 + if (m_db.isOpen() && previousDatabasePath != databasePath) { + m_db.close(); + QSqlDatabase::removeDatabase(QSqlDatabase::defaultConnection); + } - if (!db.open()) - { - LError("Failed to open database:{}", db.lastError().text()); - isFind = false; - return DatabaseOpenError; + // 如果数据库尚未打开或路径已变动,重新打开数据库 + if (!m_db.isOpen() || previousDatabasePath != databasePath) { + m_db = QSqlDatabase::addDatabase("QSQLITE"); + m_db.setDatabaseName(databasePath); // 使用传入的数据库路径 + + if (!m_db.open()) { + LError("Failed to open database:{}", m_db.lastError().text()); + isFind = false; + return DatabaseOpenError; + } + + // 更新路径 + previousDatabasePath = databasePath; } // 创建查询对象 - QSqlQuery query(db); + QSqlQuery query(m_db); // 准备 SQL 查询 query.prepare("SELECT COUNT(1) FROM history WHERE device_sn = :device_sn;"); @@ -135,44 +175,209 @@ int Widget::databaseQuery(const QString& databasePath, const QString& deviceSn, if (!query.exec()) { LError("Failed to execute query:{}", query.lastError().text()); isFind = false; - db.close(); - QSqlDatabase::removeDatabase(QSqlDatabase::defaultConnection); return DatabaseSelectError; } // 处理查询结果 - if (query.next()) - { + if (query.next()) { int count = query.value(0).toInt(); isFind = (count > 0); - db.close(); - QSqlDatabase::removeDatabase(QSqlDatabase::defaultConnection); return NoError; } isFind = false; - db.close(); - QSqlDatabase::removeDatabase(QSqlDatabase::defaultConnection); return DatabaseUnknownError; } +// 判断是否为单板 SN +static bool isSingleBoardSN(const QString& sn) { + // 单板 SN 格式:9位前缀-固定12位尾数 + QRegularExpression regex("^\\d{9}-[A-Z0-9]{12}$"); + return regex.match(sn).hasMatch(); +} + +// 判断是否为整机 SN +static bool isCompleteMachineSN(const QString& sn) { + // 整机 SN 格式:前25位前缀-固定8位尾数 + QRegularExpression regex("^.{25}[A-Z0-9]{10,}$"); + return sn.contains('#') && regex.match(sn).hasMatch(); +} + +bool Widget::identifyProduct(const QString &sn, int &mode) +{ + const auto selectedProducts = Common::GetInstance()->getSelectedProducts(); + if (isSingleBoardSN(sn)) + { + QString productKey = sn.mid(9); // 提取单板 SN 的尾数部分 + if (selectedProducts.contains(productKey)) + { + mode = selectedProducts[productKey]; + return true; // 返回匹配的产品名称 + } + } + else if (isCompleteMachineSN(sn)) + { + QString productKey = sn.mid(25); // 提取整机 SN 的尾数部分 + if (selectedProducts.contains(productKey)) + { + mode = selectedProducts[productKey]; + return true; // 返回匹配的产品名称 + } + } + return false; +} + void Widget::RegisterDoneSlot(bool ret) { - qDebug()<<"11111"; + if (ret) + { + addLogItem("注KEY成功"); + } + else + { + addLogItem("注KEY失败,请检查对应程序注KEY详情!"); + } ui->lineEdit_snId->setEnabled(true); + ui->pushButton_sendSnId->setEnabled(true); + setSNLineEditFocus(); } void Widget::GetConnStatusSlot(bool ret) { - if (!ret && !m_isSwitching) + if (ret) { - qDebug()<<"ret:"<lineEdit_snId->setEnabled(true); + //Common::GetInstance()-> + setWindowTitle(QString("LaunchRegisterKey-已连接")); + } + else + { + setWindowTitle("LaunchRegisterKey-未连接"); + } } +// 辅助函数:比较进程路径和目标路径 +static bool isMatchingProcess(const QString& processPath, const QString& targetPath) { + return processPath.compare(targetPath, Qt::CaseInsensitive) == 0; +} + +bool Widget::ProcessJudgment() +{ + // 创建一个快照来获取当前系统的所有进程 + HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hProcessSnap == INVALID_HANDLE_VALUE) + { + qWarning() << "Failed to create process snapshot."; + return false; + } + + PROCESSENTRY32 pe32{}; + pe32.dwSize = sizeof(PROCESSENTRY32); + + if (!Process32First(hProcessSnap, &pe32)) + { + qWarning() << "Failed to retrieve process information."; + CloseHandle(hProcessSnap); + return false; + } + + const auto& workModeCfgs = Common::GetInstance()->getWorkModeConfigs(); + + do { + HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pe32.th32ProcessID); + if (!hProcess) + { + continue; // 无法打开进程,继续下一个进程 + } + + WCHAR exePath[MAX_PATH]; + DWORD size = MAX_PATH; + if (QueryFullProcessImageName(hProcess, 0, exePath, &size)) + { + QString processPath = QDir::fromNativeSeparators(QString::fromWCharArray(exePath)); + + for (const auto& workmodeCfg : workModeCfgs) + { + int matchedType = -1; // 用于保存匹配的路径类型 + + if (isMatchingProcess(processPath, workmodeCfg.workPath)) + { + matchedType = 0; // workPath 匹配 + } + else if (isMatchingProcess(processPath, workmodeCfg.reworkPath)) + { + matchedType = 1; // reworkPath 匹配 + } + + if (matchedType != -1) + { + Common::GetInstance()->setCurrentWorkMode(workmodeCfg.index); + Common::GetInstance()->setRepairFlag(matchedType == 1); // 如果是 reworkPath,设置为 true + + QString logStr = QString("正在使用%1").arg(Common::GetInstance()->getCurrWorkModeStr()); + addLogItem(logStr); + qDebug() << "Matching process found:" + << QString::fromWCharArray(pe32.szExeFile) + << "PID:" << pe32.th32ProcessID + << "Path:" << processPath + << "Matched Type:" << (matchedType == 0 ? "workPath" : "reworkPath"); + + CloseHandle(hProcess); + CloseHandle(hProcessSnap); + return true; + } + } + } + CloseHandle(hProcess); // 确保句柄被关闭 + } while (Process32Next(hProcessSnap, &pe32)); + + CloseHandle(hProcessSnap); + qDebug() << "No matching process found."; + return false; +} + +bool Widget::isProcessRunningWithName(const QString& processNamePart) +{ + // 创建一个快照来获取当前系统的所有进程 + HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hProcessSnap == INVALID_HANDLE_VALUE) { + qWarning() << "Failed to create process snapshot."; + return false; + } + + // 初始化进程信息结构体 + PROCESSENTRY32 pe32; + pe32.dwSize = sizeof(PROCESSENTRY32); + + // 获取第一个进程信息 + if (!Process32First(hProcessSnap, &pe32)) { + qWarning() << "Failed to retrieve process information."; + CloseHandle(hProcessSnap); + return false; + } + + // 遍历所有进程 + do { + // 将进程名称转换为QString + QString processName = QString::fromWCharArray(pe32.szExeFile); + + // 判断进程名称是否包含指定字符串 + if (processName.contains(processNamePart, Qt::CaseInsensitive)) { + qDebug() << "Found matching process:" << processName; + CloseHandle(hProcessSnap); + return true; // 找到匹配的进程,返回 true + } + + } while (Process32Next(hProcessSnap, &pe32)); + + // 关闭快照句柄 + CloseHandle(hProcessSnap); + return false; +} + void Widget::on_pushButton_setting_clicked() { + static auto initSize = this->size(); if (ui->widget_setting->isHidden()) { ui->pushButton_setting->setIcon(QIcon(":/resources/folding.png")); @@ -188,7 +393,7 @@ void Widget::on_pushButton_setting_clicked() ui->widget_setting->hide(); int currentWidth = this->width(); this->adjustSize(); - this->resize(currentWidth, this->height()); // 只保持高度变化 + this->resize(initSize); // 只保持高度变化 setSNLineEditFocus(); } } @@ -199,16 +404,19 @@ void Widget::addLogItem(const QString &text) { delete ui->list_log->takeItem(0); } - ui->list_log->addItem(text); // 添加新条目 + QString dateTimeStr = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss"); + ui->list_log->addItem(dateTimeStr + " " + text); // 添加新条目 + ui->list_log->scrollToBottom(); // 滚动到底部 } int Widget::switchProgram(const QString snId, const int mode, const bool isRepair) { + LTrace("Function enter"); static bool isFirstRepair = true; if (isRepair && isFirstRepair) { isFirstRepair = false; - CountdownMessageBox::showCountdownMessageBox(this, 3, "正在切换返修程序包!"); + CountdownMessageBox::showCountdownMessageBox(this, 3, "启用返修程序包!"); } else if (!isRepair) { @@ -220,13 +428,38 @@ int Widget::switchProgram(const QString snId, const int mode, const bool isRepai QTimer *timer = new QTimer(this); connect(timer, &QTimer::timeout, this, [this, timer, snId, workPath]() { + static int times = 0; if (Common::GetInstance()->getCurrentWorkMode() == -1) { - m_isSwitching = true; m_udpDataThread->stopClose(); - timer->stop(); // 停止定时器 - // 启动 IECS 程序 - startIECSProgram(workPath, snId); + if (!isProcessRunningWithName("IECS")) + { + timer->stop(); // 停止定时器 + times = 0; + // 启动 IECS 程序 + if (startIECSProgram(workPath, snId) != NoError) + { + ui->lineEdit_snId->setEnabled(true); + ui->pushButton_sendSnId->setEnabled(true); + QMessageBox::warning(this, "程序启动失败!", QString("启动%1失败,请检查程序路径").arg(workPath)); + return; + } + // 发送 SN ID 和记录日志 + m_udpDataThread->sendSnId(snId); + addLogItem(QString("已发送产品SN:%1").arg(snId)); + } + else + { + times++; + if (times >= 10) + { + times = 0; + timer->stop(); + ui->lineEdit_snId->setEnabled(true); + ui->pushButton_sendSnId->setEnabled(true); + QMessageBox::warning(this, "程序启动失败!", QString("启动%1失败,进程已存在").arg(workPath)); + } + } } }); timer->start(1000); @@ -235,6 +468,7 @@ int Widget::switchProgram(const QString snId, const int mode, const bool isRepai void Widget::on_lineEdit_snId_returnPressed() { + LTrace("Function enter"); QString snId = ui->lineEdit_snId->text(); int ret = 0; ret = checkSNId(snId); @@ -243,45 +477,44 @@ void Widget::on_lineEdit_snId_returnPressed() LError("checkSNId error!code:{}", ret); return; } - const auto selectedProducts = Common::GetInstance()->getSelectedProducts(); - QString productFormat{}; - for (auto& products : selectedProducts.keys()) - { - if (snId.contains(products)) - { - productFormat = products; - break; - } - } - if (productFormat.isEmpty()) + int snMode = -1; + if (!identifyProduct(snId, snMode)) { QMessageBox::information(this, "警告", "当前SN与启用设值SN不匹配,请检查设置或重新扫码"); return; } ui->lineEdit_snId->setEnabled(false); + ui->pushButton_sendSnId->setEnabled(false); + LInfo("SN ID:{}, Mode:{}", snId, snMode); auto currWorkMode = Common::GetInstance()->getCurrentWorkMode(); + bool repairFlag = Common::GetInstance()->getRepairFlag(); bool isRepair = false; // 查找数据库,判断是否需要返修程序 - ret = checkForRepairProgram(selectedProducts[productFormat], snId, isRepair); + ret = checkForRepairProgram(snMode, snId, isRepair); if (ret != NoError) { LError("checkForRepairProgram error!{}", ret); return; } - auto selectMode = selectedProducts[productFormat]; - if (selectMode != currWorkMode || isRepair) + if (snMode != currWorkMode || isRepair != repairFlag) { + Common::GetInstance()->setRepairFlag(isRepair); + QString reStr = isRepair ? "反修" : "正常"; + QString logStr = QString("启动%1-%2").arg(Common::getWorkModeStr(snMode)).arg(reStr); + addLogItem(logStr); // 切换工作模式 if (currWorkMode != -1) { m_udpDataThread->sendClose(); } + //TODO:不相等时,m_udpDataThread发送关闭信号,等待Common单例类中的标志位变成-1后,m_udpDataThread停止发送关闭信号并启动IECS程序 //同时需要去IECS程序路径中查找数据库,判断是否使用返修程序 - switchProgram(snId, selectMode, isRepair); + switchProgram(snId, snMode, isRepair); return; } m_udpDataThread->sendSnId(snId); + addLogItem(QString("已发送产品SN:%1").arg(snId)); } void Widget::on_pushButton_sendSnId_clicked() diff --git a/ui/widget.h b/ui/widget.h index 7dc2815..a3ca2a1 100644 --- a/ui/widget.h +++ b/ui/widget.h @@ -2,6 +2,8 @@ #define WIDGET_H #include +#include + #include "settingwgt.h" #include "udpdatahandler.h" @@ -23,20 +25,27 @@ private: int checkForRepairProgram(const int mode, const QString& sn, bool& isFind); int databaseQuery(const QString& databasePath, const QString& deviceSn, bool& isFind); + + bool identifyProduct(const QString& sn, int& mode); + private slots: void RegisterDoneSlot(bool ret); void GetConnStatusSlot(bool ret); + bool ProcessJudgment(); + bool isProcessRunningWithName(const QString& processNamePart); + void addLogItem(const QString& text); int switchProgram(const QString snId, const int mode, const bool isRepair); void on_pushButton_setting_clicked(); void on_lineEdit_snId_returnPressed(); void on_pushButton_sendSnId_clicked(); + private: Ui::Widget *ui; SettingWgt *m_settingDlg; UdpDataHandler *m_udpDataThread; bool m_showSetting; - bool m_isSwitching; + QSqlDatabase m_db; }; #endif // WIDGET_H diff --git a/utils/interface.h b/utils/interface.h index 3448302..ab6eab2 100644 --- a/utils/interface.h +++ b/utils/interface.h @@ -1,6 +1,7 @@ #ifndef INTERFACE_H #define INTERFACE_H #include +#include enum ErrorCode { NoError = 0,