浏览代码

dev: update blastoppage to be group by device uuid

YaoH 5 天之前
父节点
当前提交
3b84e4cd81

+ 3 - 3
CMakeLists.txt

@@ -30,6 +30,9 @@ if (APPLE)
 
 elseif(WIN32)
     message(STATUS "Compiling for Windows")
+    set(CMAKE_PREFIX_PATH "C:/Qt/6.9.0/msvc2022_64")
+    # 设置 OpenSSL 安装路径
+    set(OPENSSL_ROOT_DIR "C:/Qt/Tools/OpenSSLv3/Win_x64")
 
     # Windows MSVC 默认不需要特别设置,但你也可以切换成 clang-cl:
     # set(CMAKE_C_COMPILER "clang-cl")
@@ -39,11 +42,8 @@ elseif(WIN32)
     # set(CMAKE_TOOLCHAIN_FILE "C:/vcpkg/scripts/buildsystems/vcpkg.cmake" CACHE STRING "")
 endif()
 
-#set(CMAKE_PREFIX_PATH "C:/Qt/6.9.0/msvc2022_64")
 
 
-# # 设置 OpenSSL 安装路径
-# set(OPENSSL_ROOT_DIR "C:/Qt/Tools/OpenSSLv3/Win_x64")
 
 # 查找 OpenSSL 库
 find_package(OpenSSL REQUIRED)

+ 235 - 111
blastJob/blastopepage.cpp

@@ -6,7 +6,6 @@
 
 #include "../components/custommessagebox.h"
 #include "../components/loadingwidget.h"
-#include "../login/loginwindow.h"
 #include "../registryManager/registrymanager.h"
 #include "../utils/global.h"
 #include "../utils/logger.h"
@@ -255,12 +254,12 @@ QJsonObject BlastOpePage::getMetaInfo() {
 }
 
 void BlastOpePage::initPagination() {
-    pageWidget = new PageWidget;
-    connect(pageWidget, &PageWidget::currentPageChanged, this, &BlastOpePage::PageChanged);
-    connect(pageWidget->getComboBox(), QOverload<int>::of(&QComboBox::currentIndexChanged), this,
-            &BlastOpePage::onComboBoxIndexChanged);
-    ui->verticalLayout_4->addWidget(pageWidget);
-    m_pageSize = 10;
+    // // pageWidget = new PageWidget;
+    // //connect(pageWidget, &PageWidget::currentPageChanged, this, &BlastOpePage::PageChanged);
+    // connect(pageWidget->getComboBox(), QOverload<int>::of(&QComboBox::currentIndexChanged), this,
+    //         &BlastOpePage::onComboBoxIndexChanged);
+    // ui->verticalLayout_4->addWidget(pageWidget);
+    m_pageSize = 100;
     m_currentPage = 1;
     RefreshData();
 }
@@ -274,9 +273,21 @@ void BlastOpePage::loadDataAndDrawTable(int currentPage, int pageSize) {
 
     QList<QSharedPointer<HProject>> projectList =
         dao.getHProjectsFromJsonArray(result["data"].toObject()["list"].toArray());
+    QMap<QString, QList<QSharedPointer<HProject>>> groupedProjects;
+    for (const QSharedPointer<HProject> &project : projectList) {
+        QString key = project->getPcSn();
+        if (!groupedProjects.contains(key)) {
+            groupedProjects[key] = QList<QSharedPointer<HProject>>();
+        }
+        groupedProjects[key].append(project);
+    }
 
     totalCount = result["data"].toObject()["count"].toInt();
-    pageWidget->setMaxPage(ceil(static_cast<double>(totalCount) / pageSize));
+    // pageWidget->setMaxPage(ceil(static_cast<double>(totalCount) / pageSize));
+
+    // Clear previous data
+    pcSnToFirstRowMap.clear();
+    progressBars.clear();
 
     model = new QStandardItemModel(this);
 
@@ -300,46 +311,112 @@ void BlastOpePage::loadDataAndDrawTable(int currentPage, int pageSize) {
         propMap[i] = headers[i].prop;
     }
     model->setHorizontalHeaderLabels(headerLabels);
-    for (int row = 0; row < projectList.size(); ++row) {
-        HProject &HProject = *projectList.at(row).data();
-        QStandardItem *uuidItem = new QStandardItem();
-        uuidItem->setData(HProject.getUuid(), Qt::UserRole);
-        model->setItem(row, headerCount, uuidItem);
-        for (int col = 0; col < headerCount; ++col) {
-            QString prop = propMap[col];
-            QStandardItem *item = nullptr;
-            if (col == 0) {
-                item = new QStandardItem();
-                item->setCheckable(true);
-                item->setCheckState(Qt::Unchecked);
-                item->setText("");
+
+    // Create rows with sub-rows for each project, grouped by pcSn
+    QStringList pcSnKeys = groupedProjects.keys();
+    int currentRow = 0;
+
+    for (const QString &pcSn : pcSnKeys) {
+        QList<QSharedPointer<HProject>> projectsForPcSn = groupedProjects[pcSn];
+        pcSnToFirstRowMap[pcSn] = currentRow;  // Store the first row index for this pcSn
+
+        // Calculate aggregated blast status for this pcSn group
+        QString combinedBlastStatus = "";
+        QSet<QString> statusSet;
+        for (const QSharedPointer<HProject> &project : projectsForPcSn) {
+            QMetaProperty statusProp =
+                project->metaObject()->property(project->metaObject()->indexOfProperty("blastStatus"));
+            QString status = statusProp.read(project.data()).toString();
+            statusSet.insert(status);
+        }
+
+        // Determine the combined status based on all projects in the group
+        if (statusSet.size() == 1) {
+            // All projects have the same status
+            combinedBlastStatus = statusSet.values().first();
+        } else {
+            // Mixed statuses - use the most advanced status
+            if (statusSet.contains(BlastStatus::Blasted)) {
+                combinedBlastStatus = BlastStatus::Blasted;
+            } else {
+                combinedBlastStatus = BlastStatus::Registered;
             }
-            if (!prop.isEmpty()) {
-                QMetaProperty metaProp =
-                    HProject.metaObject()->property(HProject.metaObject()->indexOfProperty(prop.toUtf8()));
-                QVariant value = metaProp.read(&HProject);
-
-                if (prop == "blastStatus") {
-                    QString statusText;
-                    if (value.toString() == BlastStatus::Registered) {
-                        statusText = "待起爆";
-                        item = new QStandardItem(statusText);
-                        item->setForeground(QColor("#f3a3k'k1"));
-                    } else if (value.toString() == BlastStatus::Blasted) {
-                        statusText = "起 爆 完 成";
-                        item = new QStandardItem(statusText);
-                        item->setForeground(QColor("#90d543"));
+        }
+
+        // Store all UUIDs for this pcSn group in the first row
+        QStandardItem *uuidItem = new QStandardItem();
+        QStringList uuids;
+        for (const QSharedPointer<HProject> &project : projectsForPcSn) {
+            uuids << project->getUuid();
+        }
+        uuidItem->setData(uuids.join(","), Qt::UserRole);
+        model->setItem(currentRow, headerCount, uuidItem);
+
+        // Create a sub-row for each project in this pcSn group
+        for (int subIndex = 0; subIndex < projectsForPcSn.size(); ++subIndex) {
+            HProject &project = *projectsForPcSn[subIndex].data();
+
+            for (int col = 0; col < headerCount; ++col) {
+                QString prop = propMap[col];
+                QStandardItem *item = nullptr;
+
+                if (col == 0) {
+                    // Only show checkbox on the first row of each pcSn group
+                    if (subIndex == 0) {
+                        item = new QStandardItem();
+                        item->setCheckable(true);
+                        item->setCheckState(Qt::Unchecked);
+                        item->setText("");
                     } else {
-                        item = new QStandardItem(value.toString());
+                        item = new QStandardItem("");
                     }
-                } else {
+                } else if (prop == "pcSn") {
+                    // Only show pcSn on the first row of each group
+                    if (subIndex == 0) {
+                        item = new QStandardItem(pcSn);
+                    } else {
+                        item = new QStandardItem("");
+                    }
+                } else if (prop == "blastStatus") {
+                    // Only show aggregated blast status on the first row of each group
+                    if (subIndex == 0) {
+                        QString statusText;
+                        if (combinedBlastStatus == BlastStatus::Registered) {
+                            statusText = "待起爆";
+                            item = new QStandardItem(statusText);
+                            item->setForeground(QColor("#f3a3k'k1"));
+                        } else if (combinedBlastStatus == BlastStatus::Blasted) {
+                            statusText = "起 爆 完 成";
+                            item = new QStandardItem(statusText);
+                            item->setForeground(QColor("#90d543"));
+                        } else {
+                            item = new QStandardItem(combinedBlastStatus);
+                        }
+                    } else {
+                        item = new QStandardItem("");
+                    }
+                } else if (col == ColIndexProgressBar || col == ColIndexOpBtn) {
+                    // Progress and operation columns will be handled separately with widgets
+                    // Only on the first row of each group
+                    if (subIndex == 0) {
+                        item = new QStandardItem("");
+                    } else {
+                        item = new QStandardItem("");
+                    }
+                } else if (!prop.isEmpty()) {
+                    // Show individual project data for other columns
+                    QMetaProperty metaProp =
+                        project.metaObject()->property(project.metaObject()->indexOfProperty(prop.toUtf8()));
+                    QVariant value = metaProp.read(&project);
                     item = new QStandardItem(value.toString());
                 }
+
+                if (item) {
+                    item->setTextAlignment(Qt::AlignCenter);
+                    model->setItem(currentRow, col, item);
+                }
             }
-            if (item) {
-                item->setTextAlignment(Qt::AlignCenter);  // 设置文本居中对齐
-                model->setItem(row, col, item);
-            }
+            currentRow++;
         }
     }
     ui->tableView->setModel(model);
@@ -362,16 +439,21 @@ void BlastOpePage::loadDataAndDrawTable(int currentPage, int pageSize) {
     ui->tableView->setAlternatingRowColors(true);
     ui->tableView->verticalHeader()->setDefaultSectionSize(50);
 
-    for (int row = 0; row < projectList.size(); ++row) {
-        // 初始化progressBars
-        int progressCol = headers.size() - 2;  //
+    // Create progress bars and operation buttons only for the first row of each pcSn group
+    for (const QString &pcSn : pcSnKeys) {
+        QList<QSharedPointer<HProject>> projectsForPcSn = groupedProjects[pcSn];
+        int firstRowIndex = pcSnToFirstRowMap[pcSn];
+        int groupRowSpan = projectsForPcSn.size();
+
+        // 初始化progressBars for the first row of this pcSn group
+        int progressCol = headers.size() - 2;
         QProgressBar *progressBar1 = new QProgressBar(ui->tableView);
         QProgressBar *progressBar2 = new QProgressBar(ui->tableView);
         QProgressBar *progressBar3 = new QProgressBar(ui->tableView);
         QHBoxLayout *progressBarLayout = new QHBoxLayout;
         progressBars.append(ProgressBarTriple(progressBar1, progressBar2, progressBar3));
         for (QProgressBar *pb : {progressBar1, progressBar2, progressBar3}) {
-            pb->setRange(0, 100);  // 设置范围为0到100
+            pb->setRange(0, 100);
             pb->setValue(0);
             pb->setAlignment(Qt::AlignCenter);
             pb->setStyleSheet(
@@ -384,22 +466,24 @@ void BlastOpePage::loadDataAndDrawTable(int currentPage, int pageSize) {
         }
         progressBarLayout->setAlignment(Qt::AlignCenter);
         progressBarLayout->setContentsMargins(0, 0, 0, 0);
-        progressBarLayout->setSpacing(0);  // 设置进度条之间的间距为0
+        progressBarLayout->setSpacing(0);
         QWidget *progressBarContainer = new QWidget(ui->tableView);
         progressBarContainer->setLayout(progressBarLayout);
-        QModelIndex progressIndex = model->index(row, progressCol);
+        QModelIndex progressIndex = model->index(firstRowIndex, progressCol);
         if (progressIndex.isValid()) {
             ui->tableView->setIndexWidget(progressIndex, progressBarContainer);
+            // Set row span to cover all sub-rows for this pcSn
+            ui->tableView->setSpan(firstRowIndex, progressCol, groupRowSpan, 1);
         }
-        int col = headers.size() - 1;
 
-        // 创建操作按钮
+        // 创建操作按钮 for the first row of this pcSn group
+        int operationCol = headers.size() - 1;
         QWidget *widget = new QWidget(ui->tableView);
         QPushButton *button = new QPushButton(widget);
         button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
         button->setStyleSheet("QPushButton:disabled { background-color: #d3d3d3; color: gray; }");
 
-        QModelIndex statusIndex = model->index(row, ColIndexBlastStatus);
+        QModelIndex statusIndex = model->index(firstRowIndex, ColIndexBlastStatus);
         if (statusIndex.isValid()) {
             QString blastStatus = model->data(statusIndex).toString();
             if (blastStatus == "待起爆") {
@@ -413,12 +497,21 @@ void BlastOpePage::loadDataAndDrawTable(int currentPage, int pageSize) {
         layout->setContentsMargins(0, 0, 0, 0);
         widget->setLayout(layout);
 
-        QModelIndex index = model->index(row, col);
+        QModelIndex index = model->index(firstRowIndex, operationCol);
         if (index.isValid()) {
             ui->tableView->setIndexWidget(index, widget);
+            // Set row span to cover all sub-rows for this pcSn
+            ui->tableView->setSpan(firstRowIndex, operationCol, groupRowSpan, 1);
             connect(button, &QPushButton::clicked,
-                    [this, row, button]() { handleSingleBlastButtonClicked(row, button); });
+                    [this, firstRowIndex, button]() { handleSingleBlastButtonClicked(firstRowIndex, button); });
         }
+
+        // Also set span for the pcSn column to merge cells
+        ui->tableView->setSpan(firstRowIndex, ColIndexBlasterDev, groupRowSpan, 1);
+        // Set span for the checkbox column
+        ui->tableView->setSpan(firstRowIndex, 0, groupRowSpan, 1);
+        // Set span for the blast status column
+        ui->tableView->setSpan(firstRowIndex, ColIndexBlastStatus, groupRowSpan, 1);
     }
 
     LoadingWidget::hideLoading();
@@ -431,25 +524,45 @@ void BlastOpePage::PageChanged(int page) {
 }
 
 void BlastOpePage::onComboBoxIndexChanged(int index) {
-    QVariant variant = pageWidget->getComboBox()->itemData(index);
-    int value = variant.toInt();
-    m_pageSize = value;
-    m_currentPage = 1;
-    loadDataAndDrawTable(m_currentPage, m_pageSize);
+    // QVariant variant = pageWidget->getComboBox()->itemData(index);
+    // int value = variant.toInt();
+    // m_pageSize = value;
+    // m_currentPage = 1;
+    // loadDataAndDrawTable(m_currentPage, m_pageSize);
 }
 
 void BlastOpePage::updateProgressBar(int firingStage, int row) {
-    if (!progressBars.isEmpty()) {
-        QProgressBar *progressBar1 = progressBars[row].bar1;
-        QProgressBar *progressBar2 = progressBars[row].bar2;
-        QProgressBar *progressBar3 = progressBars[row].bar3;
-
-        progressBar1->setRange(0, 100);  // 设置范围为0到100
-        progressBar1->setValue(0);
-        progressBar2->setRange(0, 100);  // 设置范围为0到100
-        progressBar2->setValue(0);
-        progressBar3->setRange(0, 100);  // 设置范围为0到100
-        progressBar3->setValue(0);
+    // Find the progress bar index for this row's pcSn group
+    QString pcSn;
+    QStandardItem *devItem = model->item(row, ColIndexBlasterDev);
+    if (devItem) {
+        pcSn = devItem->text();
+        if (pcSn.isEmpty()) {
+            // This might be a sub-row, need to find the parent row with pcSn
+            for (int i = row - 1; i >= 0; i--) {
+                QStandardItem *parentDevItem = model->item(i, ColIndexBlasterDev);
+                if (parentDevItem && !parentDevItem->text().isEmpty()) {
+                    pcSn = parentDevItem->text();
+                    break;
+                }
+            }
+        }
+    }
+
+    // Find the progress bar index for this pcSn
+    QStringList pcSnKeys = QStringList(pcSnToFirstRowMap.keys());
+    int progressBarIndex = pcSnKeys.indexOf(pcSn);
+
+    if (progressBarIndex >= 0 && progressBarIndex < progressBars.size()) {
+        QProgressBar *progressBar1 = progressBars[progressBarIndex].bar1;
+        QProgressBar *progressBar2 = progressBars[progressBarIndex].bar2;
+        QProgressBar *progressBar3 = progressBars[progressBarIndex].bar3;
+        // reset all progress bars
+        for (QProgressBar *pb : {progressBar1, progressBar2, progressBar3}) {
+            pb->setRange(0, 100);  // 设置范围为0到100
+            pb->setValue(0);
+        }
+
         switch (firingStage) {
             case FiringStages::QuickTesting:
                 // 起爆检测状态
@@ -562,11 +675,18 @@ void BlastOpePage::onFiringStageUpdated(int stage, int row) {
 
 void BlastOpePage::handleSingleBlastButtonClicked(int row, QPushButton *button) {
     QStandardItem *devItem = model->item(row, ColIndexBlasterDev);
-    QString blasterDevUuid;
+    QString blasterDevPcSn;
     if (devItem) {
-        blasterDevUuid = devItem->text();
+        blasterDevPcSn = devItem->text();
+    }
+
+    // Get all UUIDs for this pcSn group
+    QStandardItem *uuidItem = model->item(row, headers.size());
+    QStringList uuids;
+    if (uuidItem) {
+        QString uuidData = uuidItem->data(Qt::UserRole).toString();
+        uuids = uuidData.split(",", Qt::SkipEmptyParts);
     }
-    qDebug() << "handleSingleBlastButtonClicked: row = " << row << ", uuid = " << blasterDevUuid;
 
     if (button->text() == startBlastButtonTxt) {
         button->setDisabled(true);
@@ -578,7 +698,10 @@ void BlastOpePage::handleSingleBlastButtonClicked(int row, QPushButton *button)
         });
         button->setMinimumWidth(80);
         button->setText(stopBlastButtonTxt);
-        firingWidget *widget = new firingWidget(row, false, blasterDevUuid);
+
+        // Create firing widget for the pcSn group (use pcSn as identifier)
+        // Use the first row index for this pcSn group for progress updates
+        firingWidget *widget = new firingWidget(row, false, blasterDevPcSn);
         connect(widget, &firingWidget::updatefiringStage, this, &BlastOpePage::onFiringStageUpdated);
         connect(widget, &firingWidget::startCountdown, this, &BlastOpePage::showCountDownWidget);
         connect(widget, &firingWidget::updateProjectStatus, this, &BlastOpePage::handlerUpdateProjectStatus);
@@ -588,7 +711,7 @@ void BlastOpePage::handleSingleBlastButtonClicked(int row, QPushButton *button)
         }
 
         widget->setAttribute(Qt::WA_DeleteOnClose);
-        firingWidgetByUuid.insert(blasterDevUuid, widget);
+        firingWidgetByUuid.insert(blasterDevPcSn, widget);
         widget->startBlasting();
 
     } else if (button->text() == stopBlastButtonTxt) {
@@ -597,7 +720,7 @@ void BlastOpePage::handleSingleBlastButtonClicked(int row, QPushButton *button)
             return;
         }
 
-        firingWidget *widget = firingWidgetByUuid.value(blasterDevUuid);
+        firingWidget *widget = firingWidgetByUuid.value(blasterDevPcSn);
         if (widget) {
             widget->sendCancelFiringMsg();
         }
@@ -677,9 +800,10 @@ void BlastOpePage::handleUpdateOpButton(int stage, int row) {
 }
 
 void BlastOpePage::handlerUpdateProjectStatus(QString uuid, const QString &newStatus) {
-    dao.updateBlastStatusByUuid(uuid, newStatus);
-
-    firingWidget *widget = firingWidgetByUuid.value(uuid);
+    // dao.updateBlastStatusByUuid(uuid, newStatus);
+    QJsonObject extParams;
+    qDebug() << "handlerUpdateProjectStatus: uuid = " << uuid << ", newStatus = " << newStatus;
+    // extParams.insert("blastStatus", QJsonArray({newStatus}));
 }
 
 void BlastOpePage::destroyFiringWidget(const QString &uuid, int row) {
@@ -696,25 +820,23 @@ void BlastOpePage::destroyFiringWidget(const QString &uuid, int row) {
 void BlastOpePage::onItemCheckboxChanged(QStandardItem *item) {
     if (item->column() == 0) {  // 仅处理第一列的勾选状态改变
         if (item->checkState() == Qt::Checked) {
-            QStandardItem *uuidItem = model->item(item->row(), 10);
+            // Find the first row for this pcSn group that contains the UUID data
+            int currentRow = item->row();
+            QStandardItem *uuidItem = model->item(currentRow, headers.size());
             if (uuidItem) {
-                QVariant uuidVariant = uuidItem->data(Qt::UserRole);
-                if (uuidVariant.isValid()) {
-                    QString uuid = uuidVariant.toString();
-                    uuidMap[item->row()] = uuid;
+                QString uuidData = uuidItem->data(Qt::UserRole).toString();
+                QStringList uuids = uuidData.split(",", Qt::SkipEmptyParts);
+                if (!uuids.isEmpty()) {
+                    // Get the pcSn for this group
+                    QStandardItem *pcSnItem = model->item(currentRow, ColIndexBlasterDev);
+                    if (pcSnItem) {
+                        QString pcSn = pcSnItem->text();
+                        uuidMap[currentRow] = pcSn;  // Store pcSn using the first row of the group
+                    }
                 }
             }
         } else if (item->checkState() == Qt::Unchecked) {
-            QStandardItem *uuidItem = model->item(item->row(), 10);
-            if (uuidItem) {
-                // 从 item 中获取 uuid 数据
-                QVariant uuidVariant = uuidItem->data(Qt::UserRole);
-                if (uuidVariant.isValid()) {
-                    QString uuid = uuidVariant.toString();
-                    // 从数组中移除该 uuid
-                    uuidMap.remove(item->row());
-                }
-            }
+            uuidMap.remove(item->row());
         }
     }
 }
@@ -732,15 +854,17 @@ void BlastOpePage::on_btnSelect_clicked() {
 
     for (auto it = uuidMap.begin(); it != uuidMap.end(); ++it) {
         int row = it.key();
-        QString uuid = it.value();
-        firingWidget *widgetSelect = new firingWidget(row, true, uuid);
-        QModelIndex index = model->index(row, ColIndexBlasterDev);
+        QString pcSn = it.value();  // This is now pcSn instead of uuid
+        firingWidget *widgetSelect = new firingWidget(row, true, pcSn);
+        QModelIndex index = model->index(row, ColIndexOpBtn);  // Use ColIndexOpBtn instead of ColIndexBlasterDev
         if (index.isValid()) {
             QWidget *widgetButton = ui->tableView->indexWidget(index);
             if (widgetButton) {
                 QPushButton *button = widgetButton->findChild<QPushButton *>();
-                button->setText(stopBlastButtonTxt);
-                button->setDisabled(true);
+                if (button) {
+                    button->setText(stopBlastButtonTxt);
+                    button->setDisabled(true);
+                }
             }
         }
 
@@ -750,7 +874,7 @@ void BlastOpePage::on_btnSelect_clicked() {
         connect(widgetSelect, &firingWidget::updateProjectStatus, this, &BlastOpePage::handlerUpdateProjectStatus);
         connect(widgetSelect, &firingWidget::closeFiring, this, &BlastOpePage::destroyBatchFiringWidget);
         widgetSelect->setAttribute(Qt::WA_DeleteOnClose);
-        uuidWidgetSMap.insert(uuid, widgetSelect);
+        uuidWidgetSMap.insert(pcSn, widgetSelect);
         widgetSelect->startBlasting();
 
         if (isShowTriggeringWidget) {
@@ -760,8 +884,8 @@ void BlastOpePage::on_btnSelect_clicked() {
 }
 
 // 完成充电
-void BlastOpePage::setBatchBlastTrigger(QString uuid) {
-    selectedUuids.insert(uuid);
+void BlastOpePage::setBatchBlastTrigger(QString pcSn) {
+    selectedUuids.insert(pcSn);  // Now storing pcSn instead of UUID
     bool isSame = checkUuidsSame();
     if (isSame) {
         bool successSelect;
@@ -791,17 +915,17 @@ void BlastOpePage::setBatchBlastTrigger(QString uuid) {
         }
     } else {
         Logger::getInstance().error(
-            QString("The uuids in selectedUuids and uuidMap are different. uuid: %1").arg(uuid));
+            QString("The pcSns in selectedUuids and uuidMap are different. pcSn: %1").arg(pcSn));
     }
 }
 
-// 检查 selectedUuids 和 uuidMap 中的 uuid 是否相同
+// 检查 selectedUuids 和 uuidMap 中的 pcSn 是否相同
 bool BlastOpePage::checkUuidsSame() {
-    QSet<QString> mapUuids;
+    QSet<QString> mapPcSns;
     for (const auto &value : uuidMap) {
-        mapUuids.insert(value);
+        mapPcSns.insert(value);
     }
-    return selectedUuids == mapUuids;
+    return selectedUuids == mapPcSns;
 }
 
 void BlastOpePage::showCountDownForBatchBlast() {
@@ -827,20 +951,20 @@ void BlastOpePage::showCountDownForBatchBlast() {
 
 void BlastOpePage::triggerBatchFiringBlast() {
     for (auto it = uuidWidgetSMap.begin(); it != uuidWidgetSMap.end(); ++it) {
-        QString uuid = it.key();
+        QString pcSn = it.key();
         firingWidget *widget = it.value();
-        QString topic = "hxgc/" + uuid + "/BlastTask";
+        QString topic = "hxgc/" + pcSn + "/BlastTask";
         QString message = "起爆";
         widget->onCountdownFinished(topic, message);
     }
 }
 
-void BlastOpePage::destroyBatchFiringWidget(const QString &uuid, int row) {
-    firingWidget *widget = uuidWidgetSMap.value(uuid);
+void BlastOpePage::destroyBatchFiringWidget(const QString &pcSn, int row) {
+    firingWidget *widget = uuidWidgetSMap.value(pcSn);
     if (widget) {
         widget->close();
         widget->deleteLater();
-        uuidWidgetSMap.remove(uuid);
+        uuidWidgetSMap.remove(pcSn);
     }
     // reset the table's row by row index
 

+ 3 - 1
blastJob/blastopepage.h

@@ -21,6 +21,7 @@
 #include "../fireWidget/firingwidget.h"  // 包含 firingWidget 头文件
 #include "../serial/serialtool.h"
 #include "countdownwidget.h"
+#include "faceverification.h"
 
 // 自定义结构体
 struct ProgressBarTriple {
@@ -79,12 +80,13 @@ class BlastOpePage : public QWidget {
     QWebEngineView *view;
     QVBoxLayout *layout;
     QString certifyId;
-    PageWidget *pageWidget;
+    // PageWidget *pageWidget;
     QJsonArray dataArray;
     HProjectDao dao;
     QVector<ProgressBarTriple> progressBars;  // 用于保存每行的两个进度条指针
     QMap<int, QString> uuidMap;               // 用于存储行号和对应的 uuid
     QMap<QString, firingWidget *> uuidWidgetSMap;
+    QMap<QString, int> pcSnToFirstRowMap;  // Map pcSn to its first row index for shared widgets
     SerialTool *serialTool;
     QSet<QString> selectedUuids;
     QMetaObject::Connection connectionItem;

+ 204 - 0
blastJob/faceverification.cpp

@@ -0,0 +1,204 @@
+#include "faceverification.h"
+
+#include <QEventLoop>
+#include <QJsonDocument>
+#include <QProcessEnvironment>
+#include <QWebEngineSettings>
+
+#include "../components/loadingwidget.h"
+#include "../registryManager/registrymanager.h"
+#include "../utils/global.h"
+#include "../utils/logger.h"
+
+FaceVerification::FaceVerification(QWidget *parent)
+    : QObject(parent), m_parent(parent), m_view(nullptr), m_layout(nullptr) {
+}
+
+FaceVerification::~FaceVerification() {
+    cleanup();
+}
+
+void FaceVerification::initFaceVerification() {
+    Logger::getInstance().info("start init face verification");
+    LoadingWidget::showLoading(m_parent, "请求创建人脸识别...");
+
+    m_layout = new QVBoxLayout(m_parent);
+    // TODO: release the qwebengineview when not successfully verified
+    m_view = new QWebEngineView(m_parent);
+    m_view->setAttribute(Qt::WA_OpaquePaintEvent);
+
+    QWebEnginePage *page = m_view->page();
+
+    QJsonObject metaInfo = getMetaInfo();
+    if (metaInfo["certName"] == "" || metaInfo["certNo"] == "") {
+        QMessageBox::information(nullptr, "获取用户信息错误", "未获得用户的身份证信息,请联系管理员");
+        emit verificationFailed("未获得用户的身份证信息");
+        return;
+    }
+
+    QObject::connect(page, &QWebEnginePage::featurePermissionRequested,
+                     [this, page](const QUrl &securityOrigin, QWebEnginePage::Feature feature) {
+                         handleFeaturePermission(page, securityOrigin, feature);
+                     });
+
+    Logger::getInstance().info("FaceVerification: connect");
+    QUrl postUrl(apiBackendUrl.resolved(QUrl("h-face-verify/pc")));
+    QJsonObject response = sendPostRequest(postUrl, metaInfo);
+    QString certifyUrl;
+    if (response["code"] != 200) {
+        Logger::getInstance().error(
+            QString("创建人脸识别请求服务器返回错误: userName: %1. response: %2")
+                .arg(metaInfo["certName"].toString(), QString::fromUtf8(QJsonDocument(response).toJson())));
+        QMessageBox::critical(nullptr, "错误", "无法创建人脸识别,请确认后台录入的身份信息正确");
+        emit verificationFailed("创建人脸识别请求失败");
+        return;
+    }
+    if (response.contains("data") && response["data"].isObject()) {
+        LoadingWidget::showLoading(m_parent, "人脸识别请求已创建...");
+        QJsonObject dataObject = response["data"].toObject();
+        if (dataObject.contains("ResultObject") && dataObject["ResultObject"].isObject()) {
+            QJsonObject resultObject = dataObject["ResultObject"].toObject();
+            if (resultObject.contains("CertifyId") && resultObject["CertifyId"].isString()) {
+                m_certifyId = resultObject["CertifyId"].toString();
+            }
+            if (resultObject.contains("CertifyUrl") && resultObject["CertifyUrl"].isString()) {
+                certifyUrl = resultObject["CertifyUrl"].toString();
+            }
+        }
+    }
+    if (!certifyUrl.isEmpty()) {
+        m_view->load(QUrl(certifyUrl));
+
+        m_layout->addWidget(m_view);
+
+        m_layout->setStretchFactor(m_view, 1);
+
+        QObject::connect(m_view->page(), &QWebEnginePage::urlChanged, this, &FaceVerification::onUrlChanged);
+    } else {
+        QMessageBox::information(nullptr, "提示", "人脸识别请求失败");
+        Logger::getInstance().error("FaceVerificationInit: Failed to get certifyUrl");
+        LoadingWidget::hideLoading();
+        emit verificationFailed("人脸识别请求失败");
+    }
+}
+
+void FaceVerification::cleanup() {
+    closeWebViewAndRestoreUI();
+}
+
+void FaceVerification::closeWebViewAndRestoreUI() {
+    if (m_view) {
+        if (m_layout) {
+            m_layout->removeWidget(m_view);
+        }
+        delete m_view;
+        m_view = nullptr;
+    }
+    if (m_layout) {
+        delete m_layout;
+        m_layout = nullptr;
+    }
+}
+
+void FaceVerification::onUrlChanged(const QUrl &newUrl) {
+    LoadingWidget::showLoading(m_parent, "查询验证结果...");
+    if (newUrl.host() == "www.integrateblaster.com") {
+        closeWebViewAndRestoreUI();
+        QNetworkAccessManager manager;
+        QUrl requestUrl(apiBackendUrl.resolved(QUrl(QString("h-face-verify/certifyId/%1").arg(m_certifyId))));
+        QNetworkRequest request(requestUrl);
+        QNetworkReply *reply = manager.get(request);
+        QEventLoop loop;
+        QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
+        loop.exec();
+
+        if (reply->error() == QNetworkReply::NoError) {
+            QByteArray responseData = reply->readAll();
+            QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData);
+            if (!jsonDoc.isNull() && jsonDoc.isObject()) {
+                QJsonObject rootObj = jsonDoc.object();
+                QJsonObject dataObj = rootObj["data"].toObject();
+                QString message = dataObj["Message"].toString();
+                QJsonObject resultObj = dataObj["ResultObject"].toObject();
+                if (resultObj.isEmpty()) {
+                    Logger::getInstance().error(QString("获取认证初始化数据失败. message: %1.").arg(message));
+                    int ret = QMessageBox::information(nullptr, "认证失败", message + " ,请重新认证!");
+                    if (ret == QMessageBox::Ok) {
+                        initFaceVerification();
+                    }
+                } else {
+                    QString passed = resultObj["Passed"].toString();
+                    if (passed == "T") {
+                        Logger::getInstance().info(QString("进入认证界面"));
+                        LoadingWidget::hideLoading();
+                        emit verificationSuccessful();
+                    } else if (passed == "F") {
+                        int ret = QMessageBox::critical(nullptr, "提示", "操作失败,请重新认证!");
+                        if (ret == QMessageBox::Ok) {
+                            initFaceVerification();
+                        }
+                    }
+                }
+            }
+        } else {
+            qDebug() << "Request failed:" << reply->errorString();
+            Logger::getInstance().error(
+                QString("InitFaseVerification request failed. error message: %1").arg(reply->errorString()));
+        }
+
+        reply->deleteLater();
+        LoadingWidget::hideLoading();
+    }
+    LoadingWidget::hideLoading();
+}
+
+QJsonObject FaceVerification::sendPostRequest(const QUrl &url, const QJsonObject &data) {
+    QNetworkAccessManager manager;
+    QNetworkRequest request(url);
+    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
+
+    QJsonDocument doc(data);
+    QByteArray postData = doc.toJson();
+
+    QNetworkReply *reply = manager.post(request, postData);
+
+    QEventLoop loop;
+    QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
+    loop.exec();
+
+    QJsonObject responseJson;
+    if (reply->error() == QNetworkReply::NoError) {
+        QByteArray responseData = reply->readAll();
+        QJsonDocument responseDoc = QJsonDocument::fromJson(responseData);
+        if (!responseDoc.isNull() && responseDoc.isObject()) {
+            responseJson = responseDoc.object();
+        }
+    } else {
+        qDebug() << "Error fetching content: " << reply->errorString();
+    }
+    reply->deleteLater();
+    return responseJson;
+}
+
+void FaceVerification::handleFeaturePermission(QWebEnginePage *page, const QUrl &securityOrigin,
+                                               QWebEnginePage::Feature feature) {
+    if (feature == QWebEnginePage::MediaAudioCapture || feature == QWebEnginePage::MediaAudioVideoCapture ||
+        feature == QWebEnginePage::MediaVideoCapture) {
+        page->setFeaturePermission(securityOrigin, feature, QWebEnginePage::PermissionGrantedByUser);
+    } else {
+        page->setFeaturePermission(securityOrigin, feature, QWebEnginePage::PermissionDeniedByUser);
+    }
+}
+
+QJsonObject FaceVerification::getMetaInfo() {
+    QJsonObject metaInfo;
+    QString certName;
+    QString certNo;
+
+    QMap<QString, QString> userInfo = RegistryManager::instance()->getCurentLoginUser();
+    certName = userInfo.value("certName", "");
+    certNo = userInfo.value("identity", "");
+    metaInfo["certName"] = certName;
+    metaInfo["certNo"] = certNo;
+    return metaInfo;
+}

+ 44 - 0
blastJob/faceverification.h

@@ -0,0 +1,44 @@
+#ifndef FACEVERIFICATION_H
+#define FACEVERIFICATION_H
+
+#include <QJsonObject>
+#include <QMessageBox>
+#include <QNetworkAccessManager>
+#include <QNetworkReply>
+#include <QObject>
+#include <QVBoxLayout>
+#include <QWebEnginePage>
+#include <QWebEngineView>
+#include <QWidget>
+
+class FaceVerification : public QObject {
+    Q_OBJECT
+
+public:
+    explicit FaceVerification(QWidget *parent = nullptr);
+    ~FaceVerification();
+
+    void initFaceVerification();
+    void cleanup();
+
+signals:
+    void verificationSuccessful();
+    void verificationFailed(const QString &message);
+
+private slots:
+    void onUrlChanged(const QUrl &newUrl);
+
+private:
+    void handleFeaturePermission(QWebEnginePage *page, const QUrl &securityOrigin, QWebEnginePage::Feature feature);
+    QJsonObject sendPostRequest(const QUrl &url, const QJsonObject &data);
+    QJsonObject getMetaInfo();
+    void closeWebViewAndRestoreUI();
+
+private:
+    QWidget *m_parent;
+    QWebEngineView *m_view;
+    QVBoxLayout *m_layout;
+    QString m_certifyId;
+};
+
+#endif // FACEVERIFICATION_H

+ 0 - 0
components/safetyinspect.cpp


+ 0 - 0
components/safetyinspect.h


+ 1 - 1
fireWidget/firingwidget.cpp

@@ -131,7 +131,7 @@ void firingWidget::handleProjectFiringMqttMessage(const QMqttMessage &message) {
                 QJsonObject relayInfo = jsonObj["data"].toObject();
                 int status = relayInfo["status"].toInt();
                 if (ErrorBlastStatus::isErrorStatus(status)) {
-                    Logger::getInstance().error(QString("爆破器返回异常. 工程uuid: %1; 错误code: %2")
+                    Logger::getInstance().error(QString("爆破器返回异常. dev uuid: %1; 错误code: %2")
                                                     .arg(m_blasterDevUuid, ErrorBlastStatus::getErrorMessage(status)));
                     QMessageBox::critical(nullptr, "爆破器报错",
                                           QString("错误错误信息: %1").arg(ErrorBlastStatus::getErrorMessage(status)));

+ 7 - 12
qss/tableview.qss

@@ -19,9 +19,12 @@ QLabel {
 
 /* 表格视图样式 */
 QTableView {
-    border: none; /* 无外边框 */
-    alternate-background-color: transparent; /* 交替行背景颜色 */
-    gridline-color: transparent; /* 网格线颜色透明 */
+    gridline-color: #4472C4;
+    border: 0.1px solid #4472C4; /* 外边框颜色 */
+    selection-color: #9f4b2c; /* 选中行文字颜色 */
+    font-size: 14px; /* 字体大小 */
+    font-family: "Microsoft YaHei", "Arial", sans-serif; /* 字体 */
+    show-decoration-selected: 1; /* 显示选中行的装饰 */
 }
 
 /* 序号列header */
@@ -36,17 +39,9 @@ QHeaderView::section {
     color: white;
     height: 34px; /* 表头高度 */
 }
-
-/* 交替行样式 */
-QTableView::item:alternate {
-    border: 0.5px solid transparent; /* 白色内边框 */
-    background-color: transparent;
-    padding: 5px; /* 设置单元格内边距,增加行距 */
-}
-
 /* 普通行样式 */
 QTableView::item {
-    border: 0.5px solid #BDC8E2; /* 白色内边框 */
+    border: 0.5px solid #000308; /* 白色内边框 */
     background-color: #D9E1F2;
     padding: 5px; /* 设置单元格内边距,增加行距 */
 }

+ 27 - 0
utils/backendapimanager.cpp

@@ -180,6 +180,33 @@ QJsonObject backendAPIManager::getHProjects(int page, int pageSize, QJsonObject
     return result;
 }
 
+QString backendAPIManager::updateProject(const QString uuid, const QJsonObject &params) {
+    if (!s_instance) return QString();
+
+    QNetworkReply *reply = s_instance->sendRequest(QString("h-project/%1").arg(uuid), params, "PUT");
+
+    QEventLoop loop;
+    QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
+    loop.exec();
+
+    QString result;
+    if (reply->error() == QNetworkReply::NoError) {
+        QJsonDocument response = QJsonDocument::fromJson(reply->readAll());
+        if (response.object()["code"].toInt() != 200) {
+            Logger::getInstance().error(QString("UpdateProjectsStatus request failed. error message: %1")
+                                            .arg(response.object()["message"].toString()));
+            return response.object()["message"].toString();
+        }
+        return "";
+    } else {
+        Logger::getInstance().error(
+            QString("UpdateProjectsStatus request failed. error message: %1").arg(reply->errorString()));
+    }
+
+    reply->deleteLater();
+    return result;
+}
+
 QJsonArray backendAPIManager::getSysUsers() {
     if (!s_instance) return QJsonArray();
     QJsonObject params = QJsonObject();

+ 1 - 0
utils/backendapimanager.h

@@ -19,6 +19,7 @@ class backendAPIManager : public QObject {
 
     static bool uploadBlastEquipments(const QJsonObject &regDetsData);
     static QJsonObject getHProjects(int page, int pageSize, QJsonObject extParams);
+    static QString updateProject(const QString uuid, const QJsonObject &params);
     static QJsonArray getSysUsers();
     static QJsonArray getHAddresses();
     static QJsonArray getDailyBlastedCount(int days);