#include "blastopepage.h" #include #include #include #include "../components/custommessagebox.h" #include "../components/loadingwidget.h" #include "../utils/global.h" #include "../utils/logger.h" #include "countdownwidget.h" #include "sequenceselectorwidget.h" #include "ui_blastopepage.h" const int ColIndexBlastStatus = 6; const int ColIndexProgressBar = 7; const int ColIndexOpBtn = 8; const int ColIndexBlasterDev = 1; BlastOpePage::BlastOpePage(QWidget *parent) : QWidget(parent), ui(new Ui::BlastOpePage), dao(DatabaseManager::getInstance().getDatabase()), m_faceVerification(nullptr), m_sequenceSelector(nullptr) { QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); bool isAllSafetyInspectorConfirmed = true; const QJsonObject data = backendAPIManager::getSafetyInspect(QDate::currentDate().toString("yyyy-MM-dd")); for (const QJsonValue &value : data["safetyInspectors"].toArray()) { if (value.isObject()) { if (value.toObject()["confirmTime"].toString() == "") { isAllSafetyInspectorConfirmed = false; break; } } } if (!isAllSafetyInspectorConfirmed) { bool isContinue = CustomMessageBox::showConfirmDialog("警告", "安全检查没有全部确认,是否继续?", "继续", "取消", this); if (!isContinue) { return; } } if (isFaceVerificationEnabled) { initFaceVerification(); } else { ui->setupUi(this); initPagination(); } } void BlastOpePage::showCountDownWidget(QString uuid, const QString &topic, const QString &message) { CountdownWidget *countdownWidget = new CountdownWidget(this); countdownWidget->resize(200, 200); int x = (this->width() - countdownWidget->width()) / 2; int y = (this->height() - countdownWidget->height()) / 2; countdownWidget->move(x, y); countdownWidget->show(); firingWidget *widget = firingWidgetByUuid.value(uuid); if (widget) { connect( countdownWidget, &CountdownWidget::countdownFinished, widget, [widget, topic, message, countdownWidget]() { widget->onCountdownFinished(topic, message); }, Qt::SingleShotConnection); } } void BlastOpePage::initFaceVerification() { m_faceVerification = new FaceVerification(this); connect(m_faceVerification, &FaceVerification::verificationSuccessful, this, &BlastOpePage::onFaceVerificationSuccess); connect(m_faceVerification, &FaceVerification::verificationFailed, this, &BlastOpePage::onFaceVerificationFailed); m_faceVerification->initFaceVerification(); } void BlastOpePage::onFaceVerificationSuccess() { ui->setupUi(this); initPagination(); } void BlastOpePage::onFaceVerificationFailed(const QString &message) { // Handle face verification failure // The FaceVerification class already shows appropriate error messages Q_UNUSED(message) } BlastOpePage::~BlastOpePage() { delete ui; if (m_faceVerification) { delete m_faceVerification; } if (m_sequenceSelector) { delete m_sequenceSelector; } } void BlastOpePage::initPagination() { QJsonObject extParams{{"blastStatus", QJsonArray({"2"})}}; QJsonObject result = backendAPIManager::getHProjects(1, 200, extParams); QList> projectList = dao.getHProjectsFromJsonArray(result["data"].toObject()["list"].toArray()); QMap>> groupedProjects; for (const QSharedPointer &project : projectList) { QString key = project->getPcSn(); if (!groupedProjects.contains(key)) { groupedProjects[key] = QList>(); } groupedProjects[key].append(project); } int totalCount = result["data"].toObject()["count"].toInt(); if (totalCount > 200) { QMessageBox::warning( this, "警告", QString("目前可以操作的数量是200。当前查询结果总数:%1, 如需调整请联系开发人员进行调整").arg(totalCount)); } m_groupedProjects = groupedProjects; drawTable(); } void BlastOpePage::drawTable() { LoadingWidget::showLoading(this, "正在加载数据..."); // Clear previous data pcSnToFirstRowMap.clear(); progressBars.clear(); model = new QStandardItemModel(this); headers = { {"选择", ""}, {"井下起爆器", "pcSn"}, {"工程名称", "name"}, {"井下地址", "addressPath"}, {"雷管数量", "detSum"}, {"炸药量(kg)", "blastCount"}, {"起爆状态", "blastStatus"}, {"进度", ""}, {"操作", ""}, }; int headerCount = headers.size(); QStringList headerLabels; QMap propMap; for (int i = 0; i < headers.size(); ++i) { headerLabels << headers[i].label; propMap[i] = headers[i].prop; } model->setHorizontalHeaderLabels(headerLabels); // Create rows with sub-rows for each project, grouped by pcSn QStringList pcSnKeys = m_groupedProjects.keys(); int currentRow = 0; for (const QString &pcSn : pcSnKeys) { QList> projectsForPcSn = m_groupedProjects[pcSn]; pcSnToFirstRowMap[pcSn] = currentRow; // Store the first row index for this pcSn // Calculate aggregated blast status for this pcSn group QString combinedBlastStatus = ""; QSet statusSet; for (const QSharedPointer &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; } } // Store all UUIDs for this pcSn group in the first row QStandardItem *uuidItem = new QStandardItem(); QStringList uuids; for (const QSharedPointer &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(""); } } 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); } } currentRow++; } } ui->tableView->setModel(model); connectionItem = QObject::connect(model, &QStandardItemModel::itemChanged, this, &BlastOpePage::onItemCheckboxChanged); ui->tableView->setColumnWidth(0, 30); ui->tableView->setEditTriggers(QAbstractItemView::NoEditTriggers); // 禁止编辑 for (int i = 1; i < headerCount; ++i) { if (i == 8) { // Column: 进度 ui->tableView->horizontalHeader()->setSectionResizeMode(i, QHeaderView::Custom); ui->tableView->setColumnWidth(i, 180); } else { ui->tableView->horizontalHeader()->setSectionResizeMode(i, QHeaderView::Stretch); } } ui->tableView->setColumnHidden(headerCount, true); ui->tableView->setAlternatingRowColors(true); ui->tableView->verticalHeader()->setDefaultSectionSize(50); // Create progress bars and operation buttons only for the first row of each pcSn group for (const QString &pcSn : pcSnKeys) { QList> projectsForPcSn = m_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); pb->setValue(0); pb->setAlignment(Qt::AlignCenter); pb->setStyleSheet( "QProgressBar { border: 1px solid grey; border-radius: 5px; background-color: #EEEEEE; " "height: 10px; }" "QProgressBar::chunk { background-color: #05B8CC; width: 2px; margin: 0.5px; border - " "radius: 10px; }"); pb->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); progressBarLayout->addWidget(pb); } progressBarLayout->setAlignment(Qt::AlignCenter); progressBarLayout->setContentsMargins(0, 0, 0, 0); progressBarLayout->setSpacing(0); QWidget *progressBarContainer = new QWidget(ui->tableView); progressBarContainer->setLayout(progressBarLayout); 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); } // 创建操作按钮 for the first row of this pcSn group int operationCol = headers.size() - 1; QWidget *widget = new QWidget(ui->tableView); QPushButton *singleTriggerBtn = new QPushButton(widget); singleTriggerBtn->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); singleTriggerBtn->setStyleSheet("QPushButton:disabled { background-color: #d3d3d3; color: gray; }"); QModelIndex statusIndex = model->index(firstRowIndex, ColIndexBlastStatus); if (statusIndex.isValid()) { QString blastStatus = model->data(statusIndex).toString(); if (blastStatus == "待起爆") { singleTriggerBtn->setText(startBlastButtonTxt); singleTriggerBtn->setEnabled(true); } } QHBoxLayout *layout = new QHBoxLayout(widget); layout->addWidget(singleTriggerBtn); layout->setAlignment(Qt::AlignCenter); layout->setContentsMargins(0, 0, 0, 0); widget->setLayout(layout); QModelIndex index = model->index(firstRowIndex, operationCol); if (index.isValid()) { ui->tableView->setIndexWidget(index, widget); if (groupRowSpan > 1) { ui->tableView->setSpan(firstRowIndex, operationCol, groupRowSpan, 1); } connect(singleTriggerBtn, &QPushButton::clicked, [this, firstRowIndex, singleTriggerBtn]() { handleSingleBlastButtonClicked(firstRowIndex, singleTriggerBtn); }); } if (groupRowSpan > 1) { ui->tableView->setSpan(firstRowIndex, ColIndexBlasterDev, groupRowSpan, 1); ui->tableView->setSpan(firstRowIndex, 0, groupRowSpan, 1); ui->tableView->setSpan(firstRowIndex, ColIndexBlastStatus, groupRowSpan, 1); } } LoadingWidget::hideLoading(); } void BlastOpePage::onComboBoxIndexChanged(int index) {} void BlastOpePage::updateProgressBar(int firingStage, int row) { // 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: // 起爆检测状态 progressBar1->setRange(0, 0); // 设置范围为0到100 progressBar1->setValue(0); break; case FiringStages::QuickTestFinished: // 起爆检测完成 progressBar1->setValue(100); break; case FiringStages::NetCharging: // 充电开始 progressBar1->setValue(100); progressBar2->setRange(0, 0); // 设置范围为0到100 progressBar2->setValue(0); break; case FiringStages::NetChargingFinished: // 充电完成 progressBar1->setValue(100); progressBar2->setValue(100); break; case FiringStages::Blasting: progressBar1->setValue(100); progressBar2->setValue(100); progressBar3->setRange(0, 0); // 设置范围为0到100 progressBar3->setValue(0); break; case FiringStages::BlastFinished: // 充电完成 progressBar1->setValue(100); progressBar2->setValue(100); progressBar3->setValue(100); break; default: break; } } } // update tableView's fields about firing stage void BlastOpePage::onFiringStageUpdated(int stage, int row) { // update blast status item QModelIndex firingStatusItem = model->index(row, ColIndexBlastStatus); if (firingStatusItem.isValid()) { QColor customColor; QFont boldFont; boldFont.setBold(true); qDebug() << "opage::onFiringStageUpdated: stage = " << stage << ", row = " << row; switch (stage) { case FiringStages::Starting: model->setData(firingStatusItem, "待起爆"); customColor = QColor("#44035b"); model->setData(firingStatusItem, customColor, Qt::ForegroundRole); model->setData(firingStatusItem, boldFont, Qt::FontRole); break; case FiringStages::QuickTesting: model->setData(firingStatusItem, "起爆检测中..."); customColor = QColor("#44035b"); model->setData(firingStatusItem, customColor, Qt::ForegroundRole); model->setData(firingStatusItem, boldFont, Qt::FontRole); break; case FiringStages::QuickTestFinished: model->setData(firingStatusItem, "检测完成"); customColor = QColor("#404185"); model->setData(firingStatusItem, customColor, Qt::ForegroundRole); break; case FiringStages::NetCharging: model->setData(firingStatusItem, "充电中 ..."); customColor = QColor("#31688e"); model->setData(firingStatusItem, customColor, Qt::ForegroundRole); break; case FiringStages::NetChargingFinished: model->setData(firingStatusItem, "充电完成"); customColor = QColor("#1f918d"); model->setData(firingStatusItem, customColor, Qt::ForegroundRole); break; case FiringStages::Blasting: model->setData(firingStatusItem, "起爆中..."); customColor = QColor("#38b775"); model->setData(firingStatusItem, customColor, Qt::ForegroundRole); break; case FiringStages::BlastFinished: model->setData(firingStatusItem, "起爆完成"); customColor = QColor("#90d543"); model->setData(firingStatusItem, customColor, Qt::ForegroundRole); break; case FiringStages::PendingTriggerButtonClick: model->setData(firingStatusItem, "按下双键起爆..."); customColor = QColor("#8e620"); model->setData(firingStatusItem, customColor, Qt::ForegroundRole); break; case FiringStages::CancelConfirmed: model->setData(firingStatusItem, "已取消"); customColor = QColor("#893c3c"); model->setData(firingStatusItem, customColor, Qt::ForegroundRole); break; case FiringStages::ForceCanceled: model->setData(firingStatusItem, "已强制取消"); customColor = QColor("#893c3c"); model->setData(firingStatusItem, customColor, Qt::ForegroundRole); break; default: break; } } updateProgressBar(stage, row); handleUpdateOpButton(stage, row); } void BlastOpePage::handleSingleBlastButtonClicked(int row, QPushButton *button) { QStandardItem *devItem = model->item(row, ColIndexBlasterDev); QString blasterDevPcSn; if (devItem) { blasterDevPcSn = devItem->text(); } // Get all UUIDs for this pcSn group QStandardItem *uuidItem = model->item(row, headers.size()); QStringList projUuids; if (uuidItem) { QString uuidData = uuidItem->data(Qt::UserRole).toString(); projUuids = uuidData.split(",", Qt::SkipEmptyParts); } if (button->text() == startBlastButtonTxt) { button->setDisabled(true); QTimer::singleShot(5000, [button]() { if (button->isEnabled()) { return; } button->setEnabled(true); }); button->setMinimumWidth(80); button->setText(stopBlastButtonTxt); // 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::updateProjectsStatus, this, &BlastOpePage::handlerUpdateProjectStatus); connect(widget, &firingWidget::closeFiring, this, &BlastOpePage::destroyFiringWidget); if (isShowTriggeringWidget) { widget->show(); } widget->setAttribute(Qt::WA_DeleteOnClose); firingWidgetByUuid.insert(blasterDevPcSn, widget); widget->startBlasting(); } else if (button->text() == stopBlastButtonTxt) { bool isContinue = CustomMessageBox::showConfirmDialog("提示", "是否取消爆破?", "继续", "取消", this); if (!isContinue) { return; } firingWidget *widget = firingWidgetByUuid.value(blasterDevPcSn); if (widget) { widget->sendCancelFiringMsg(); } } else { Logger::getInstance().error( QString("handleSingleBlastButtonClicked: unknown button text %1").arg(button->text())); return; } } void BlastOpePage::handleUpdateOpButton(int stage, int row) { QModelIndex index = model->index(row, ColIndexOpBtn); if (!index.isValid()) { Logger::getInstance().error(QString("can not get updateOpBtnStage index %1").arg(row)); return; } QWidget *widget = ui->tableView->indexWidget(index); QPushButton *button = widget->findChild(); if (!button) { Logger::getInstance().error(QString("can not find button in row %1").arg(row)); return; } switch (stage) { case FiringStages::CancelConfirmed: button->setDisabled(true); button->setText(reStartButtonTxt); break; case FiringStages::Starting: button->setDisabled(true); break; case FiringStages::QuickTesting: button->setEnabled(true); button->setText(stopBlastButtonTxt); break; case FiringStages::QuickTestFinished: button->setEnabled(true); button->setText(stopBlastButtonTxt); break; case FiringStages::NetCharging: button->setEnabled(true); button->setText(stopBlastButtonTxt); break; case FiringStages::NetChargingFinished: button->setEnabled(true); button->setText(stopBlastButtonTxt); break; case FiringStages::PendingTriggerButtonClick: button->setEnabled(true); button->setText(stopBlastButtonTxt); break; case FiringStages::Blasting: button->setDisabled(true); button->setText("..."); break; case FiringStages::BlastFinished: button->setDisabled(true); button->setText(""); // 使用样式表设置图标居中 button->setStyleSheet( "QPushButton {" " padding: 0px;" " border: none;" " background-color: transparent;" "}"); button->setIcon(QIcon(":/icons/icons/svg/blast.svg")); button->setIconSize(QSize(32, 32)); break; case FiringStages::ForceCanceled: button->setDisabled(true); button->setText(reStartButtonTxt); break; default: button->setEnabled(true); break; } } void BlastOpePage::handlerUpdateProjectStatus(QString blasterDevUuid, const QString &newStatus) { QJsonObject extParams; extParams.insert("blastStatus", QJsonArray({newStatus})); for (const auto &project : m_groupedProjects.value(blasterDevUuid)) { QString errMsg = backendAPIManager::updateProject(project->getUuid(), extParams); if (errMsg != "") { Logger::getInstance().error( QString("handlerUpdateProjectStatus: update project(%1) failed: %2").arg(blasterDevUuid, errMsg)); QMessageBox::critical(this, "错误", QString("更新项目状态失败: %1\n错误信息: %2").arg(blasterDevUuid, errMsg)); return; } Logger::getInstance().info( QString("handlerUpdateProjectStatus: update project %1 to status %2").arg(blasterDevUuid).arg(newStatus)); } } void BlastOpePage::destroyFiringWidget(const QString &uuid, int row) { firingWidget *widget = firingWidgetByUuid.value(uuid); if (widget) { widget->close(); // 关闭窗口 widget->deleteLater(); // 释放内存 widget->disconnect(); // 断开信号连接 firingWidgetByUuid.remove(uuid); // 从映射中移除 } } // 槽函数,当 item 状态改变时触发 void BlastOpePage::onItemCheckboxChanged(QStandardItem *item) { if (item->column() == 0) { // 仅处理第一列的勾选状态改变 if (item->checkState() == Qt::Checked) { // 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) { 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) { uuidMap.remove(item->row()); } } } void BlastOpePage::on_btnSelect_clicked() { if (uuidMap.isEmpty()) { QMessageBox::warning(this, "警告", "请先选择要起爆的设备!"); return; } showSequenceSelector(); } // 完成充电 void BlastOpePage::setBatchBlastTrigger(QString pcSn) { selectedUuids.insert(pcSn); // Now storing pcSn instead of UUID bool isSame = checkUuidsSame(); if (isSame) { bool successSelect; serialTool = SerialTool::getInstance(nullptr, &successSelect); connect(serialTool, &SerialTool::triggerButtonPressed, this, &BlastOpePage::showCountDownForBatchBlast, Qt::SingleShotConnection); if (serialTool) { QByteArray data = "\r\nENABLE_BUTTON\r\n"; bool success = serialTool->sendData(data); if (success) { Logger::getInstance().info("enable batch trigger button message send."); } else { Logger::getInstance().warn("enable batch trigger button message send failed."); } connect( serialTool, &SerialTool::triggerButtonEnabled, this, [this]() { for (const auto &row : uuidMap.keys()) { onFiringStageUpdated(FiringStages::PendingTriggerButtonClick, row); } }, Qt::SingleShotConnection); } else { qDebug() << "serialTool Not fond."; QMessageBox::critical(nullptr, "错误", "trigger button devices not found"); } } else { Logger::getInstance().error( QString("The pcSns in selectedUuids and uuidMap are different. pcSn: %1").arg(pcSn)); } } // 检查 selectedUuids 和 uuidMap 中的 pcSn 是否相同 bool BlastOpePage::checkUuidsSame() { QSet mapPcSns; for (const auto &value : uuidMap) { mapPcSns.insert(value); } return selectedUuids == mapPcSns; } void BlastOpePage::showCountDownForBatchBlast() { QByteArray data = "\r\nDISABLE_BUTTON\r\n"; bool success = serialTool->sendData(data); if (success) { qDebug() << "Data sent successfully"; } else { qDebug() << "Failed to send data"; } serialTool->releaseInstance(); CountdownWidget *countdownWidgetSelect = new CountdownWidget(this); countdownWidgetSelect->resize(200, 200); int x = (this->width() - countdownWidgetSelect->width()) / 2; int y = (this->height() - countdownWidgetSelect->height()) / 2; countdownWidgetSelect->move(x, y); countdownWidgetSelect->show(); connect(countdownWidgetSelect, &CountdownWidget::countdownFinished, this, &BlastOpePage::triggerBatchFiringBlast, Qt::SingleShotConnection); } void BlastOpePage::triggerBatchFiringBlast() { for (auto it = uuidWidgetSMap.begin(); it != uuidWidgetSMap.end(); ++it) { QString pcSn = it.key(); firingWidget *widget = it.value(); QString topic = "hxgc/" + pcSn + "/BlastTask"; QString message = "起爆"; widget->onCountdownFinished(topic, message); } } void BlastOpePage::destroyBatchFiringWidget(const QString &pcSn, int row) { firingWidget *widget = uuidWidgetSMap.value(pcSn); if (widget) { widget->close(); widget->deleteLater(); uuidWidgetSMap.remove(pcSn); } // reset the table's row by row index for (int row = 0; row < model->rowCount(); ++row) { // 恢复 checkbox 可选 QStandardItem *item = model->item(row, 0); if (item) { Qt::ItemFlags flags = item->flags(); flags |= Qt::ItemIsEnabled; item->setFlags(flags); } } } void BlastOpePage::showSequenceSelector() { // Get selected pcSn list from uuidMap QStringList selectedPcSnList; for (auto it = uuidMap.begin(); it != uuidMap.end(); ++it) { QString pcSn = it.value(); if (!selectedPcSnList.contains(pcSn)) { selectedPcSnList << pcSn; } } if (selectedPcSnList.isEmpty()) { QMessageBox::warning(this, "警告", "没有选择的设备!"); return; } m_sequenceSelector = new SequenceSelectorWidget(selectedPcSnList, this); connect(m_sequenceSelector, &SequenceSelectorWidget::startFiring, this, &BlastOpePage::onSequenceSelected); connect(m_sequenceSelector, &SequenceSelectorWidget::cancel, this, &BlastOpePage::onSequenceCanceled); m_sequenceSelector->show(); } void BlastOpePage::onSequenceSelected(const QStringList &orderedPcSnList) { // 禁用表格第一列的选项 for (int row = 0; row < model->rowCount(); ++row) { QStandardItem *item = model->item(row, 0); if (item) { Qt::ItemFlags flags = item->flags(); flags &= ~Qt::ItemIsEnabled; item->setFlags(flags); } } // Start firing process with ordered sequence for (auto it = uuidMap.begin(); it != uuidMap.end(); ++it) { int row = it.key(); 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(); if (button) { button->setText(stopBlastButtonTxt); button->setDisabled(true); } } } // 信号连接 connect(widgetSelect, &firingWidget::updateFiringStage, this, &BlastOpePage::onFiringStageUpdated); connect(widgetSelect, &firingWidget::batchFiringSignal, this, &BlastOpePage::setBatchBlastTrigger); connect(widgetSelect, &firingWidget::updateProjectsStatus, this, &BlastOpePage::handlerUpdateProjectStatus); connect(widgetSelect, &firingWidget::closeFiring, this, &BlastOpePage::destroyBatchFiringWidget); widgetSelect->setAttribute(Qt::WA_DeleteOnClose); uuidWidgetSMap.insert(pcSn, widgetSelect); widgetSelect->startBlasting(); if (isShowTriggeringWidget) { widgetSelect->show(); } } // Clean up sequence selector if (m_sequenceSelector) { m_sequenceSelector->deleteLater(); m_sequenceSelector = nullptr; } } void BlastOpePage::onSequenceCanceled() { // Clean up sequence selector if (m_sequenceSelector) { m_sequenceSelector->deleteLater(); m_sequenceSelector = nullptr; } }