blastopepage.cpp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869
  1. #include "blastopepage.h"
  2. #include <QFont>
  3. #include <QProcessEnvironment>
  4. #include <QWebEngineSettings>
  5. #include "../components/custommessagebox.h"
  6. #include "../components/loadingwidget.h"
  7. #include "../utils/global.h"
  8. #include "../utils/logger.h"
  9. #include "countdownwidget.h"
  10. #include "sequenceselectorwidget.h"
  11. #include "ui_blastopepage.h"
  12. const int ColIndexBlastStatus = 6;
  13. const int ColIndexProgressBar = 7;
  14. const int ColIndexOpBtn = 8;
  15. const int ColIndexBlasterDev = 1;
  16. BlastOpePage::BlastOpePage(QWidget *parent)
  17. : QWidget(parent),
  18. ui(new Ui::BlastOpePage),
  19. dao(DatabaseManager::getInstance().getDatabase()),
  20. m_faceVerification(nullptr),
  21. m_sequenceSelector(nullptr) {
  22. QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
  23. bool isAllSafetyInspectorConfirmed = true;
  24. const QJsonObject data = backendAPIManager::getSafetyInspect(QDate::currentDate().toString("yyyy-MM-dd"));
  25. for (const QJsonValue &value : data["safetyInspectors"].toArray()) {
  26. if (value.isObject()) {
  27. if (value.toObject()["confirmTime"].toString() == "") {
  28. isAllSafetyInspectorConfirmed = false;
  29. break;
  30. }
  31. }
  32. }
  33. if (!isAllSafetyInspectorConfirmed) {
  34. bool isContinue =
  35. CustomMessageBox::showConfirmDialog("警告", "安全检查没有全部确认,是否继续?", "继续", "取消", this);
  36. if (!isContinue) {
  37. return;
  38. }
  39. }
  40. if (isFaceVerificationEnabled) {
  41. initFaceVerification();
  42. } else {
  43. ui->setupUi(this);
  44. initPagination();
  45. }
  46. }
  47. void BlastOpePage::showCountDownWidget(QString uuid, const QString &topic, const QString &message) {
  48. CountdownWidget *countdownWidget = new CountdownWidget(this);
  49. countdownWidget->resize(200, 200);
  50. int x = (this->width() - countdownWidget->width()) / 2;
  51. int y = (this->height() - countdownWidget->height()) / 2;
  52. countdownWidget->move(x, y);
  53. countdownWidget->show();
  54. firingWidget *widget = firingWidgetByUuid.value(uuid);
  55. if (widget) {
  56. connect(
  57. countdownWidget, &CountdownWidget::countdownFinished, widget,
  58. [widget, topic, message, countdownWidget]() { widget->onCountdownFinished(topic, message); },
  59. Qt::SingleShotConnection);
  60. }
  61. }
  62. void BlastOpePage::initFaceVerification() {
  63. m_faceVerification = new FaceVerification(this);
  64. connect(m_faceVerification, &FaceVerification::verificationSuccessful, this,
  65. &BlastOpePage::onFaceVerificationSuccess);
  66. connect(m_faceVerification, &FaceVerification::verificationFailed, this, &BlastOpePage::onFaceVerificationFailed);
  67. m_faceVerification->initFaceVerification();
  68. }
  69. void BlastOpePage::onFaceVerificationSuccess() {
  70. ui->setupUi(this);
  71. initPagination();
  72. }
  73. void BlastOpePage::onFaceVerificationFailed(const QString &message) {
  74. // Handle face verification failure
  75. // The FaceVerification class already shows appropriate error messages
  76. Q_UNUSED(message)
  77. }
  78. BlastOpePage::~BlastOpePage() {
  79. delete ui;
  80. if (m_faceVerification) {
  81. delete m_faceVerification;
  82. }
  83. if (m_sequenceSelector) {
  84. delete m_sequenceSelector;
  85. }
  86. }
  87. void BlastOpePage::initPagination() {
  88. QJsonObject extParams{{"blastStatus", QJsonArray({"2"})}};
  89. QJsonObject result = backendAPIManager::getHProjects(1, 200, extParams);
  90. QList<QSharedPointer<HProject>> projectList =
  91. dao.getHProjectsFromJsonArray(result["data"].toObject()["list"].toArray());
  92. QMap<QString, QList<QSharedPointer<HProject>>> groupedProjects;
  93. for (const QSharedPointer<HProject> &project : projectList) {
  94. QString key = project->getPcSn();
  95. if (!groupedProjects.contains(key)) {
  96. groupedProjects[key] = QList<QSharedPointer<HProject>>();
  97. }
  98. groupedProjects[key].append(project);
  99. }
  100. int totalCount = result["data"].toObject()["count"].toInt();
  101. if (totalCount > 200) {
  102. QMessageBox::warning(
  103. this, "警告",
  104. QString("目前可以操作的数量是200。当前查询结果总数:%1, 如需调整请联系开发人员进行调整").arg(totalCount));
  105. }
  106. m_groupedProjects = groupedProjects;
  107. drawTable();
  108. }
  109. void BlastOpePage::drawTable() {
  110. LoadingWidget::showLoading(this, "正在加载数据...");
  111. // Clear previous data
  112. pcSnToFirstRowMap.clear();
  113. progressBars.clear();
  114. model = new QStandardItemModel(this);
  115. headers = {
  116. {"选择", ""},
  117. {"井下起爆器", "pcSn"},
  118. {"工程名称", "name"},
  119. {"井下地址", "addressPath"},
  120. {"雷管数量", "detSum"},
  121. {"炸药量(kg)", "blastCount"},
  122. {"起爆状态", "blastStatus"},
  123. {"进度", ""},
  124. {"操作", ""},
  125. };
  126. int headerCount = headers.size();
  127. QStringList headerLabels;
  128. QMap<int, QString> propMap;
  129. for (int i = 0; i < headers.size(); ++i) {
  130. headerLabels << headers[i].label;
  131. propMap[i] = headers[i].prop;
  132. }
  133. model->setHorizontalHeaderLabels(headerLabels);
  134. // Create rows with sub-rows for each project, grouped by pcSn
  135. QStringList pcSnKeys = m_groupedProjects.keys();
  136. int currentRow = 0;
  137. for (const QString &pcSn : pcSnKeys) {
  138. QList<QSharedPointer<HProject>> projectsForPcSn = m_groupedProjects[pcSn];
  139. pcSnToFirstRowMap[pcSn] = currentRow; // Store the first row index for this pcSn
  140. // Calculate aggregated blast status for this pcSn group
  141. QString combinedBlastStatus = "";
  142. QSet<QString> statusSet;
  143. for (const QSharedPointer<HProject> &project : projectsForPcSn) {
  144. QMetaProperty statusProp =
  145. project->metaObject()->property(project->metaObject()->indexOfProperty("blastStatus"));
  146. QString status = statusProp.read(project.data()).toString();
  147. statusSet.insert(status);
  148. }
  149. // Determine the combined status based on all projects in the group
  150. if (statusSet.size() == 1) {
  151. // All projects have the same status
  152. combinedBlastStatus = statusSet.values().first();
  153. } else {
  154. // Mixed statuses - use the most advanced status
  155. if (statusSet.contains(BlastStatus::Blasted)) {
  156. combinedBlastStatus = BlastStatus::Blasted;
  157. } else {
  158. combinedBlastStatus = BlastStatus::Registered;
  159. }
  160. }
  161. // Store all UUIDs for this pcSn group in the first row
  162. QStandardItem *uuidItem = new QStandardItem();
  163. QStringList uuids;
  164. for (const QSharedPointer<HProject> &project : projectsForPcSn) {
  165. uuids << project->getUuid();
  166. }
  167. uuidItem->setData(uuids.join(","), Qt::UserRole);
  168. model->setItem(currentRow, headerCount, uuidItem);
  169. // Create a sub-row for each project in this pcSn group
  170. for (int subIndex = 0; subIndex < projectsForPcSn.size(); ++subIndex) {
  171. HProject &project = *projectsForPcSn[subIndex].data();
  172. for (int col = 0; col < headerCount; ++col) {
  173. QString prop = propMap[col];
  174. QStandardItem *item = nullptr;
  175. if (col == 0) {
  176. // Only show checkbox on the first row of each pcSn group
  177. if (subIndex == 0) {
  178. item = new QStandardItem();
  179. item->setCheckable(true);
  180. item->setCheckState(Qt::Unchecked);
  181. item->setText("");
  182. } else {
  183. item = new QStandardItem("");
  184. }
  185. } else if (prop == "pcSn") {
  186. // Only show pcSn on the first row of each group
  187. if (subIndex == 0) {
  188. item = new QStandardItem(pcSn);
  189. } else {
  190. item = new QStandardItem("");
  191. }
  192. } else if (prop == "blastStatus") {
  193. // Only show aggregated blast status on the first row of each group
  194. if (subIndex == 0) {
  195. QString statusText;
  196. if (combinedBlastStatus == BlastStatus::Registered) {
  197. statusText = "待起爆";
  198. item = new QStandardItem(statusText);
  199. item->setForeground(QColor("#f3a3k'k1"));
  200. } else if (combinedBlastStatus == BlastStatus::Blasted) {
  201. statusText = "起 爆 完 成";
  202. item = new QStandardItem(statusText);
  203. item->setForeground(QColor("#90d543"));
  204. } else {
  205. item = new QStandardItem(combinedBlastStatus);
  206. }
  207. } else {
  208. item = new QStandardItem("");
  209. }
  210. } else if (col == ColIndexProgressBar || col == ColIndexOpBtn) {
  211. // Progress and operation columns will be handled separately with widgets
  212. // Only on the first row of each group
  213. if (subIndex == 0) {
  214. item = new QStandardItem("");
  215. } else {
  216. item = new QStandardItem("");
  217. }
  218. } else if (!prop.isEmpty()) {
  219. // Show individual project data for other columns
  220. QMetaProperty metaProp =
  221. project.metaObject()->property(project.metaObject()->indexOfProperty(prop.toUtf8()));
  222. QVariant value = metaProp.read(&project);
  223. item = new QStandardItem(value.toString());
  224. }
  225. if (item) {
  226. item->setTextAlignment(Qt::AlignCenter);
  227. model->setItem(currentRow, col, item);
  228. }
  229. }
  230. currentRow++;
  231. }
  232. }
  233. ui->tableView->setModel(model);
  234. connectionItem =
  235. QObject::connect(model, &QStandardItemModel::itemChanged, this, &BlastOpePage::onItemCheckboxChanged);
  236. ui->tableView->setColumnWidth(0, 30);
  237. ui->tableView->setEditTriggers(QAbstractItemView::NoEditTriggers); // 禁止编辑
  238. for (int i = 1; i < headerCount; ++i) {
  239. if (i == 8) { // Column: 进度
  240. ui->tableView->horizontalHeader()->setSectionResizeMode(i, QHeaderView::Custom);
  241. ui->tableView->setColumnWidth(i, 180);
  242. } else {
  243. ui->tableView->horizontalHeader()->setSectionResizeMode(i, QHeaderView::Stretch);
  244. }
  245. }
  246. ui->tableView->setColumnHidden(headerCount, true);
  247. ui->tableView->setAlternatingRowColors(true);
  248. ui->tableView->verticalHeader()->setDefaultSectionSize(50);
  249. // Create progress bars and operation buttons only for the first row of each pcSn group
  250. for (const QString &pcSn : pcSnKeys) {
  251. QList<QSharedPointer<HProject>> projectsForPcSn = m_groupedProjects[pcSn];
  252. int firstRowIndex = pcSnToFirstRowMap[pcSn];
  253. int groupRowSpan = projectsForPcSn.size();
  254. // 初始化progressBars for the first row of this pcSn group
  255. int progressCol = headers.size() - 2;
  256. QProgressBar *progressBar1 = new QProgressBar(ui->tableView);
  257. QProgressBar *progressBar2 = new QProgressBar(ui->tableView);
  258. QProgressBar *progressBar3 = new QProgressBar(ui->tableView);
  259. QHBoxLayout *progressBarLayout = new QHBoxLayout;
  260. progressBars.append(ProgressBarTriple(progressBar1, progressBar2, progressBar3));
  261. for (QProgressBar *pb : {progressBar1, progressBar2, progressBar3}) {
  262. pb->setRange(0, 100);
  263. pb->setValue(0);
  264. pb->setAlignment(Qt::AlignCenter);
  265. pb->setStyleSheet(
  266. "QProgressBar { border: 1px solid grey; border-radius: 5px; background-color: #EEEEEE; "
  267. "height: 10px; }"
  268. "QProgressBar::chunk { background-color: #05B8CC; width: 2px; margin: 0.5px; border - "
  269. "radius: 10px; }");
  270. pb->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
  271. progressBarLayout->addWidget(pb);
  272. }
  273. progressBarLayout->setAlignment(Qt::AlignCenter);
  274. progressBarLayout->setContentsMargins(0, 0, 0, 0);
  275. progressBarLayout->setSpacing(0);
  276. QWidget *progressBarContainer = new QWidget(ui->tableView);
  277. progressBarContainer->setLayout(progressBarLayout);
  278. QModelIndex progressIndex = model->index(firstRowIndex, progressCol);
  279. if (progressIndex.isValid()) {
  280. ui->tableView->setIndexWidget(progressIndex, progressBarContainer);
  281. // Set row span to cover all sub-rows for this pcSn
  282. ui->tableView->setSpan(firstRowIndex, progressCol, groupRowSpan, 1);
  283. }
  284. // 创建操作按钮 for the first row of this pcSn group
  285. int operationCol = headers.size() - 1;
  286. QWidget *widget = new QWidget(ui->tableView);
  287. QPushButton *singleTriggerBtn = new QPushButton(widget);
  288. singleTriggerBtn->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
  289. singleTriggerBtn->setStyleSheet("QPushButton:disabled { background-color: #d3d3d3; color: gray; }");
  290. QModelIndex statusIndex = model->index(firstRowIndex, ColIndexBlastStatus);
  291. if (statusIndex.isValid()) {
  292. QString blastStatus = model->data(statusIndex).toString();
  293. if (blastStatus == "待起爆") {
  294. singleTriggerBtn->setText(startBlastButtonTxt);
  295. singleTriggerBtn->setEnabled(true);
  296. }
  297. }
  298. QHBoxLayout *layout = new QHBoxLayout(widget);
  299. layout->addWidget(singleTriggerBtn);
  300. layout->setAlignment(Qt::AlignCenter);
  301. layout->setContentsMargins(0, 0, 0, 0);
  302. widget->setLayout(layout);
  303. QModelIndex index = model->index(firstRowIndex, operationCol);
  304. if (index.isValid()) {
  305. ui->tableView->setIndexWidget(index, widget);
  306. if (groupRowSpan > 1) {
  307. ui->tableView->setSpan(firstRowIndex, operationCol, groupRowSpan, 1);
  308. }
  309. connect(singleTriggerBtn, &QPushButton::clicked, [this, firstRowIndex, singleTriggerBtn]() {
  310. handleSingleBlastButtonClicked(firstRowIndex, singleTriggerBtn);
  311. });
  312. }
  313. if (groupRowSpan > 1) {
  314. ui->tableView->setSpan(firstRowIndex, ColIndexBlasterDev, groupRowSpan, 1);
  315. ui->tableView->setSpan(firstRowIndex, 0, groupRowSpan, 1);
  316. ui->tableView->setSpan(firstRowIndex, ColIndexBlastStatus, groupRowSpan, 1);
  317. }
  318. }
  319. LoadingWidget::hideLoading();
  320. }
  321. void BlastOpePage::onComboBoxIndexChanged(int index) {}
  322. void BlastOpePage::updateProgressBar(int firingStage, int row) {
  323. // Find the progress bar index for this row's pcSn group
  324. QString pcSn;
  325. QStandardItem *devItem = model->item(row, ColIndexBlasterDev);
  326. if (devItem) {
  327. pcSn = devItem->text();
  328. if (pcSn.isEmpty()) {
  329. // This might be a sub-row, need to find the parent row with pcSn
  330. for (int i = row - 1; i >= 0; i--) {
  331. QStandardItem *parentDevItem = model->item(i, ColIndexBlasterDev);
  332. if (parentDevItem && !parentDevItem->text().isEmpty()) {
  333. pcSn = parentDevItem->text();
  334. break;
  335. }
  336. }
  337. }
  338. }
  339. // Find the progress bar index for this pcSn
  340. QStringList pcSnKeys = QStringList(pcSnToFirstRowMap.keys());
  341. int progressBarIndex = pcSnKeys.indexOf(pcSn);
  342. if (progressBarIndex >= 0 && progressBarIndex < progressBars.size()) {
  343. QProgressBar *progressBar1 = progressBars[progressBarIndex].bar1;
  344. QProgressBar *progressBar2 = progressBars[progressBarIndex].bar2;
  345. QProgressBar *progressBar3 = progressBars[progressBarIndex].bar3;
  346. // reset all progress bars
  347. for (QProgressBar *pb : {progressBar1, progressBar2, progressBar3}) {
  348. pb->setRange(0, 100); // 设置范围为0到100
  349. pb->setValue(0);
  350. }
  351. switch (firingStage) {
  352. case FiringStages::QuickTesting:
  353. // 起爆检测状态
  354. progressBar1->setRange(0, 0); // 设置范围为0到100
  355. progressBar1->setValue(0);
  356. break;
  357. case FiringStages::QuickTestFinished:
  358. // 起爆检测完成
  359. progressBar1->setValue(100);
  360. break;
  361. case FiringStages::NetCharging:
  362. // 充电开始
  363. progressBar1->setValue(100);
  364. progressBar2->setRange(0, 0); // 设置范围为0到100
  365. progressBar2->setValue(0);
  366. break;
  367. case FiringStages::NetChargingFinished:
  368. // 充电完成
  369. progressBar1->setValue(100);
  370. progressBar2->setValue(100);
  371. break;
  372. case FiringStages::Blasting:
  373. progressBar1->setValue(100);
  374. progressBar2->setValue(100);
  375. progressBar3->setRange(0, 0); // 设置范围为0到100
  376. progressBar3->setValue(0);
  377. break;
  378. case FiringStages::BlastFinished:
  379. // 充电完成
  380. progressBar1->setValue(100);
  381. progressBar2->setValue(100);
  382. progressBar3->setValue(100);
  383. break;
  384. default:
  385. break;
  386. }
  387. }
  388. }
  389. // update tableView's fields about firing stage
  390. void BlastOpePage::onFiringStageUpdated(int stage, int row) {
  391. // update blast status item
  392. QModelIndex firingStatusItem = model->index(row, ColIndexBlastStatus);
  393. if (firingStatusItem.isValid()) {
  394. QColor customColor;
  395. QFont boldFont;
  396. boldFont.setBold(true);
  397. qDebug() << "opage::onFiringStageUpdated: stage = " << stage << ", row = " << row;
  398. switch (stage) {
  399. case FiringStages::Starting:
  400. model->setData(firingStatusItem, "待起爆");
  401. customColor = QColor("#44035b");
  402. model->setData(firingStatusItem, customColor, Qt::ForegroundRole);
  403. model->setData(firingStatusItem, boldFont, Qt::FontRole);
  404. break;
  405. case FiringStages::QuickTesting:
  406. model->setData(firingStatusItem, "起爆检测中...");
  407. customColor = QColor("#44035b");
  408. model->setData(firingStatusItem, customColor, Qt::ForegroundRole);
  409. model->setData(firingStatusItem, boldFont, Qt::FontRole);
  410. break;
  411. case FiringStages::QuickTestFinished:
  412. model->setData(firingStatusItem, "检测完成");
  413. customColor = QColor("#404185");
  414. model->setData(firingStatusItem, customColor, Qt::ForegroundRole);
  415. break;
  416. case FiringStages::NetCharging:
  417. model->setData(firingStatusItem, "充电中 ...");
  418. customColor = QColor("#31688e");
  419. model->setData(firingStatusItem, customColor, Qt::ForegroundRole);
  420. break;
  421. case FiringStages::NetChargingFinished:
  422. model->setData(firingStatusItem, "充电完成");
  423. customColor = QColor("#1f918d");
  424. model->setData(firingStatusItem, customColor, Qt::ForegroundRole);
  425. break;
  426. case FiringStages::Blasting:
  427. model->setData(firingStatusItem, "起爆中...");
  428. customColor = QColor("#38b775");
  429. model->setData(firingStatusItem, customColor, Qt::ForegroundRole);
  430. break;
  431. case FiringStages::BlastFinished:
  432. model->setData(firingStatusItem, "起爆完成");
  433. customColor = QColor("#90d543");
  434. model->setData(firingStatusItem, customColor, Qt::ForegroundRole);
  435. break;
  436. case FiringStages::PendingTriggerButtonClick:
  437. model->setData(firingStatusItem, "按下双键起爆...");
  438. customColor = QColor("#8e620");
  439. model->setData(firingStatusItem, customColor, Qt::ForegroundRole);
  440. break;
  441. case FiringStages::CancelConfirmed:
  442. model->setData(firingStatusItem, "已取消");
  443. customColor = QColor("#893c3c");
  444. model->setData(firingStatusItem, customColor, Qt::ForegroundRole);
  445. break;
  446. case FiringStages::ForceCanceled:
  447. model->setData(firingStatusItem, "已强制取消");
  448. customColor = QColor("#893c3c");
  449. model->setData(firingStatusItem, customColor, Qt::ForegroundRole);
  450. break;
  451. default:
  452. break;
  453. }
  454. }
  455. updateProgressBar(stage, row);
  456. handleUpdateOpButton(stage, row);
  457. }
  458. void BlastOpePage::handleSingleBlastButtonClicked(int row, QPushButton *button) {
  459. QStandardItem *devItem = model->item(row, ColIndexBlasterDev);
  460. QString blasterDevPcSn;
  461. if (devItem) {
  462. blasterDevPcSn = devItem->text();
  463. }
  464. // Get all UUIDs for this pcSn group
  465. QStandardItem *uuidItem = model->item(row, headers.size());
  466. QStringList projUuids;
  467. if (uuidItem) {
  468. QString uuidData = uuidItem->data(Qt::UserRole).toString();
  469. projUuids = uuidData.split(",", Qt::SkipEmptyParts);
  470. }
  471. if (button->text() == startBlastButtonTxt) {
  472. button->setDisabled(true);
  473. QTimer::singleShot(5000, [button]() {
  474. if (button->isEnabled()) {
  475. return;
  476. }
  477. button->setEnabled(true);
  478. });
  479. button->setMinimumWidth(80);
  480. button->setText(stopBlastButtonTxt);
  481. // Create firing widget for the pcSn group (use pcSn as identifier)
  482. // Use the first row index for this pcSn group for progress updates
  483. firingWidget *widget = new firingWidget(row, false, blasterDevPcSn);
  484. connect(widget, &firingWidget::updateFiringStage, this, &BlastOpePage::onFiringStageUpdated);
  485. connect(widget, &firingWidget::startCountdown, this, &BlastOpePage::showCountDownWidget);
  486. connect(widget, &firingWidget::updateProjectsStatus, this, &BlastOpePage::handlerUpdateProjectStatus);
  487. connect(widget, &firingWidget::closeFiring, this, &BlastOpePage::destroyFiringWidget);
  488. if (isShowTriggeringWidget) {
  489. widget->show();
  490. }
  491. widget->setAttribute(Qt::WA_DeleteOnClose);
  492. firingWidgetByUuid.insert(blasterDevPcSn, widget);
  493. widget->startBlasting();
  494. } else if (button->text() == stopBlastButtonTxt) {
  495. bool isContinue = CustomMessageBox::showConfirmDialog("提示", "是否取消爆破?", "继续", "取消", this);
  496. if (!isContinue) {
  497. return;
  498. }
  499. firingWidget *widget = firingWidgetByUuid.value(blasterDevPcSn);
  500. if (widget) {
  501. widget->sendCancelFiringMsg();
  502. }
  503. } else {
  504. Logger::getInstance().error(
  505. QString("handleSingleBlastButtonClicked: unknown button text %1").arg(button->text()));
  506. return;
  507. }
  508. }
  509. void BlastOpePage::handleUpdateOpButton(int stage, int row) {
  510. QModelIndex index = model->index(row, ColIndexOpBtn);
  511. if (!index.isValid()) {
  512. Logger::getInstance().error(QString("can not get updateOpBtnStage index %1").arg(row));
  513. return;
  514. }
  515. QWidget *widget = ui->tableView->indexWidget(index);
  516. QPushButton *button = widget->findChild<QPushButton *>();
  517. if (!button) {
  518. Logger::getInstance().error(QString("can not find button in row %1").arg(row));
  519. return;
  520. }
  521. switch (stage) {
  522. case FiringStages::CancelConfirmed:
  523. button->setDisabled(true);
  524. button->setText(reStartButtonTxt);
  525. break;
  526. case FiringStages::Starting:
  527. button->setDisabled(true);
  528. break;
  529. case FiringStages::QuickTesting:
  530. button->setEnabled(true);
  531. button->setText(stopBlastButtonTxt);
  532. break;
  533. case FiringStages::QuickTestFinished:
  534. button->setEnabled(true);
  535. button->setText(stopBlastButtonTxt);
  536. break;
  537. case FiringStages::NetCharging:
  538. button->setEnabled(true);
  539. button->setText(stopBlastButtonTxt);
  540. break;
  541. case FiringStages::NetChargingFinished:
  542. button->setEnabled(true);
  543. button->setText(stopBlastButtonTxt);
  544. break;
  545. case FiringStages::PendingTriggerButtonClick:
  546. button->setEnabled(true);
  547. button->setText(stopBlastButtonTxt);
  548. break;
  549. case FiringStages::Blasting:
  550. button->setDisabled(true);
  551. button->setText("...");
  552. break;
  553. case FiringStages::BlastFinished:
  554. button->setDisabled(true);
  555. button->setText("");
  556. // 使用样式表设置图标居中
  557. button->setStyleSheet(
  558. "QPushButton {"
  559. " padding: 0px;"
  560. " border: none;"
  561. " background-color: transparent;"
  562. "}");
  563. button->setIcon(QIcon(":/icons/icons/svg/blast.svg"));
  564. button->setIconSize(QSize(32, 32));
  565. break;
  566. case FiringStages::ForceCanceled:
  567. button->setDisabled(true);
  568. button->setText(reStartButtonTxt);
  569. break;
  570. default:
  571. button->setEnabled(true);
  572. break;
  573. }
  574. }
  575. void BlastOpePage::handlerUpdateProjectStatus(QString blasterDevUuid, const QString &newStatus) {
  576. QJsonObject extParams;
  577. extParams.insert("blastStatus", QJsonArray({newStatus}));
  578. for (const auto &project : m_groupedProjects.value(blasterDevUuid)) {
  579. QString errMsg = backendAPIManager::updateProject(project->getUuid(), extParams);
  580. if (errMsg != "") {
  581. Logger::getInstance().error(
  582. QString("handlerUpdateProjectStatus: update project(%1) failed: %2").arg(blasterDevUuid, errMsg));
  583. QMessageBox::critical(this, "错误",
  584. QString("更新项目状态失败: %1\n错误信息: %2").arg(blasterDevUuid, errMsg));
  585. return;
  586. }
  587. Logger::getInstance().info(
  588. QString("handlerUpdateProjectStatus: update project %1 to status %2").arg(blasterDevUuid).arg(newStatus));
  589. }
  590. }
  591. void BlastOpePage::destroyFiringWidget(const QString &uuid, int row) {
  592. firingWidget *widget = firingWidgetByUuid.value(uuid);
  593. if (widget) {
  594. widget->close(); // 关闭窗口
  595. widget->deleteLater(); // 释放内存
  596. widget->disconnect(); // 断开信号连接
  597. firingWidgetByUuid.remove(uuid); // 从映射中移除
  598. }
  599. }
  600. // 槽函数,当 item 状态改变时触发
  601. void BlastOpePage::onItemCheckboxChanged(QStandardItem *item) {
  602. if (item->column() == 0) { // 仅处理第一列的勾选状态改变
  603. if (item->checkState() == Qt::Checked) {
  604. // Find the first row for this pcSn group that contains the UUID data
  605. int currentRow = item->row();
  606. QStandardItem *uuidItem = model->item(currentRow, headers.size());
  607. if (uuidItem) {
  608. QString uuidData = uuidItem->data(Qt::UserRole).toString();
  609. QStringList uuids = uuidData.split(",", Qt::SkipEmptyParts);
  610. if (!uuids.isEmpty()) {
  611. // Get the pcSn for this group
  612. QStandardItem *pcSnItem = model->item(currentRow, ColIndexBlasterDev);
  613. if (pcSnItem) {
  614. QString pcSn = pcSnItem->text();
  615. uuidMap[currentRow] = pcSn; // Store pcSn using the first row of the group
  616. }
  617. }
  618. }
  619. } else if (item->checkState() == Qt::Unchecked) {
  620. uuidMap.remove(item->row());
  621. }
  622. }
  623. }
  624. void BlastOpePage::on_btnSelect_clicked() {
  625. if (uuidMap.isEmpty()) {
  626. QMessageBox::warning(this, "警告", "请先选择要起爆的设备!");
  627. return;
  628. }
  629. showSequenceSelector();
  630. }
  631. // 完成充电
  632. void BlastOpePage::setBatchBlastTrigger(QString pcSn) {
  633. selectedUuids.insert(pcSn); // Now storing pcSn instead of UUID
  634. bool isSame = checkUuidsSame();
  635. if (isSame) {
  636. bool successSelect;
  637. serialTool = SerialTool::getInstance(nullptr, &successSelect);
  638. connect(serialTool, &SerialTool::triggerButtonPressed, this, &BlastOpePage::showCountDownForBatchBlast,
  639. Qt::SingleShotConnection);
  640. if (serialTool) {
  641. QByteArray data = "\r\nENABLE_BUTTON\r\n";
  642. bool success = serialTool->sendData(data);
  643. if (success) {
  644. Logger::getInstance().info("enable batch trigger button message send.");
  645. } else {
  646. Logger::getInstance().warn("enable batch trigger button message send failed.");
  647. }
  648. connect(
  649. serialTool, &SerialTool::triggerButtonEnabled, this,
  650. [this]() {
  651. for (const auto &row : uuidMap.keys()) {
  652. onFiringStageUpdated(FiringStages::PendingTriggerButtonClick, row);
  653. }
  654. },
  655. Qt::SingleShotConnection);
  656. } else {
  657. qDebug() << "serialTool Not fond.";
  658. QMessageBox::critical(nullptr, "错误", "trigger button devices not found");
  659. }
  660. } else {
  661. Logger::getInstance().error(
  662. QString("The pcSns in selectedUuids and uuidMap are different. pcSn: %1").arg(pcSn));
  663. }
  664. }
  665. // 检查 selectedUuids 和 uuidMap 中的 pcSn 是否相同
  666. bool BlastOpePage::checkUuidsSame() {
  667. QSet<QString> mapPcSns;
  668. for (const auto &value : uuidMap) {
  669. mapPcSns.insert(value);
  670. }
  671. return selectedUuids == mapPcSns;
  672. }
  673. void BlastOpePage::showCountDownForBatchBlast() {
  674. QByteArray data = "\r\nDISABLE_BUTTON\r\n";
  675. bool success = serialTool->sendData(data);
  676. if (success) {
  677. qDebug() << "Data sent successfully";
  678. } else {
  679. qDebug() << "Failed to send data";
  680. }
  681. serialTool->releaseInstance();
  682. CountdownWidget *countdownWidgetSelect = new CountdownWidget(this);
  683. countdownWidgetSelect->resize(200, 200);
  684. int x = (this->width() - countdownWidgetSelect->width()) / 2;
  685. int y = (this->height() - countdownWidgetSelect->height()) / 2;
  686. countdownWidgetSelect->move(x, y);
  687. countdownWidgetSelect->show();
  688. connect(countdownWidgetSelect, &CountdownWidget::countdownFinished, this, &BlastOpePage::triggerBatchFiringBlast,
  689. Qt::SingleShotConnection);
  690. }
  691. void BlastOpePage::triggerBatchFiringBlast() {
  692. for (auto it = uuidWidgetSMap.begin(); it != uuidWidgetSMap.end(); ++it) {
  693. QString pcSn = it.key();
  694. firingWidget *widget = it.value();
  695. QString topic = "hxgc/" + pcSn + "/BlastTask";
  696. QString message = "起爆";
  697. widget->onCountdownFinished(topic, message);
  698. }
  699. }
  700. void BlastOpePage::destroyBatchFiringWidget(const QString &pcSn, int row) {
  701. firingWidget *widget = uuidWidgetSMap.value(pcSn);
  702. if (widget) {
  703. widget->close();
  704. widget->deleteLater();
  705. uuidWidgetSMap.remove(pcSn);
  706. }
  707. // reset the table's row by row index
  708. for (int row = 0; row < model->rowCount(); ++row) {
  709. // 恢复 checkbox 可选
  710. QStandardItem *item = model->item(row, 0);
  711. if (item) {
  712. Qt::ItemFlags flags = item->flags();
  713. flags |= Qt::ItemIsEnabled;
  714. item->setFlags(flags);
  715. }
  716. }
  717. }
  718. void BlastOpePage::showSequenceSelector() {
  719. // Get selected pcSn list from uuidMap
  720. QStringList selectedPcSnList;
  721. for (auto it = uuidMap.begin(); it != uuidMap.end(); ++it) {
  722. QString pcSn = it.value();
  723. if (!selectedPcSnList.contains(pcSn)) {
  724. selectedPcSnList << pcSn;
  725. }
  726. }
  727. if (selectedPcSnList.isEmpty()) {
  728. QMessageBox::warning(this, "警告", "没有选择的设备!");
  729. return;
  730. }
  731. m_sequenceSelector = new SequenceSelectorWidget(selectedPcSnList, this);
  732. connect(m_sequenceSelector, &SequenceSelectorWidget::startFiring, this, &BlastOpePage::onSequenceSelected);
  733. connect(m_sequenceSelector, &SequenceSelectorWidget::cancel, this, &BlastOpePage::onSequenceCanceled);
  734. m_sequenceSelector->show();
  735. }
  736. void BlastOpePage::onSequenceSelected(const QStringList &orderedPcSnList) {
  737. // 禁用表格第一列的选项
  738. for (int row = 0; row < model->rowCount(); ++row) {
  739. QStandardItem *item = model->item(row, 0);
  740. if (item) {
  741. Qt::ItemFlags flags = item->flags();
  742. flags &= ~Qt::ItemIsEnabled;
  743. item->setFlags(flags);
  744. }
  745. }
  746. // Start firing process with ordered sequence
  747. for (auto it = uuidMap.begin(); it != uuidMap.end(); ++it) {
  748. int row = it.key();
  749. QString pcSn = it.value(); // This is now pcSn instead of uuid
  750. firingWidget *widgetSelect = new firingWidget(row, true, pcSn);
  751. QModelIndex index = model->index(row, ColIndexOpBtn); // Use ColIndexOpBtn instead of ColIndexBlasterDev
  752. if (index.isValid()) {
  753. QWidget *widgetButton = ui->tableView->indexWidget(index);
  754. if (widgetButton) {
  755. QPushButton *button = widgetButton->findChild<QPushButton *>();
  756. if (button) {
  757. button->setText(stopBlastButtonTxt);
  758. button->setDisabled(true);
  759. }
  760. }
  761. }
  762. // 信号连接
  763. connect(widgetSelect, &firingWidget::updateFiringStage, this, &BlastOpePage::onFiringStageUpdated);
  764. connect(widgetSelect, &firingWidget::batchFiringSignal, this, &BlastOpePage::setBatchBlastTrigger);
  765. connect(widgetSelect, &firingWidget::updateProjectsStatus, this, &BlastOpePage::handlerUpdateProjectStatus);
  766. connect(widgetSelect, &firingWidget::closeFiring, this, &BlastOpePage::destroyBatchFiringWidget);
  767. widgetSelect->setAttribute(Qt::WA_DeleteOnClose);
  768. uuidWidgetSMap.insert(pcSn, widgetSelect);
  769. widgetSelect->startBlasting();
  770. if (isShowTriggeringWidget) {
  771. widgetSelect->show();
  772. }
  773. }
  774. // Clean up sequence selector
  775. if (m_sequenceSelector) {
  776. m_sequenceSelector->deleteLater();
  777. m_sequenceSelector = nullptr;
  778. }
  779. }
  780. void BlastOpePage::onSequenceCanceled() {
  781. // Clean up sequence selector
  782. if (m_sequenceSelector) {
  783. m_sequenceSelector->deleteLater();
  784. m_sequenceSelector = nullptr;
  785. }
  786. }