#include "panels/chart/ExceptionDialog.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "FormKit.hpp" #include "Theme.hpp" #include "repo/IDatasetCommandRepository.hpp" namespace geopro::app { namespace { // 标注类型 → 最少坐标点数(点/文字=1,线≥2,面≥3)。 int minPoints(const QString& markType) { if (markType == QStringLiteral("2")) return 2; if (markType == QStringLiteral("3")) return 3; return 1; // 点("1")/文字("4") } } // namespace ExceptionDialog::ExceptionDialog(geopro::data::IDatasetCommandRepository* repo, QString projectId, QString remarkSourceId, QWidget* parent) : QDialog(parent), repo_(repo), projectId_(std::move(projectId)), remarkSourceId_(std::move(remarkSourceId)) { setWindowTitle(QStringLiteral("新建异常")); setModal(true); resize(440, 480); auto* root = formkit::dialogRoot(this); auto* card = formkit::formCard(this); auto* cardLay = formkit::cardBody(card); auto* form = formkit::makeEditForm(); // 标注类型(remarkSourceType "1".."4",与原版 annotationType 一致)。 markTypeCombo_ = new QComboBox(this); markTypeCombo_->addItem(QStringLiteral("点"), QStringLiteral("1")); markTypeCombo_->addItem(QStringLiteral("线"), QStringLiteral("2")); markTypeCombo_->addItem(QStringLiteral("面"), QStringLiteral("3")); markTypeCombo_->addItem(QStringLiteral("文字"), QStringLiteral("4")); formkit::capField(markTypeCombo_); form->addRow(formkit::editLabel(QStringLiteral("标注类型")), markTypeCombo_); // 异常类型行:下拉 + 「新增异常类型」按钮(对照原版 exceptionDialog 同行布局)。 exceptionTypeCombo_ = new QComboBox(this); formkit::capField(exceptionTypeCombo_); addTypeBtn_ = new QPushButton(QStringLiteral("新增异常类型"), this); auto* typeRow = new QWidget(this); auto* typeRowLay = new QHBoxLayout(typeRow); typeRowLay->setContentsMargins(0, 0, 0, 0); typeRowLay->addWidget(exceptionTypeCombo_, 1); typeRowLay->addWidget(addTypeBtn_); form->addRow(formkit::editLabel(QStringLiteral("异常类型")), typeRow); connect(addTypeBtn_, &QPushButton::clicked, this, [this]() { // 原版点开「标注类型」新增弹窗(异常属性+名称,含完整 legend),提交 addExceptionType。 // 该子流程含整套 legend 编辑,后端 addExceptionType 端点客户端尚未接入;此处先提示, // 不静默吞操作。用户可在 web 端新增类型后回到客户端选用。 QMessageBox::information( this, windowTitle(), QStringLiteral("新增异常类型请在 Web 端完成,新增后此处下拉会同步可选。")); }); nameEdit_ = new QLineEdit(this); nameEdit_->setPlaceholderText(QStringLiteral("数据名称+异常类型代号+序号")); formkit::capField(nameEdit_); form->addRow(formkit::editLabel(QStringLiteral("名称")), nameEdit_); remarkEdit_ = new QPlainTextEdit(this); remarkEdit_->setFixedHeight(geopro::app::scaledPx(60)); formkit::capField(remarkEdit_); form->addRow(formkit::editLabel(QStringLiteral("备注")), remarkEdit_); cardLay->addLayout(form); root->addWidget(card); // 坐标兜底表(x/y 多行):留空 → 确定后在图上绘形采集(主路径);手填 → 直接提交。 root->addWidget(new QLabel(QStringLiteral("坐标(x,y,留空则在图上绘制):"), this)); coordTable_ = new QTableWidget(0, 2, this); coordTable_->setHorizontalHeaderLabels({QStringLiteral("x"), QStringLiteral("y")}); coordTable_->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); root->addWidget(coordTable_, 1); auto* rowBtns = new QHBoxLayout(); auto* addRow = new QPushButton(QStringLiteral("加一行"), this); auto* delRow = new QPushButton(QStringLiteral("删一行"), this); rowBtns->addWidget(addRow); rowBtns->addWidget(delRow); rowBtns->addStretch(); root->addLayout(rowBtns); connect(addRow, &QPushButton::clicked, this, [this]() { coordTable_->insertRow(coordTable_->rowCount()); }); connect(delRow, &QPushButton::clicked, this, [this]() { if (coordTable_->rowCount() > 0) coordTable_->removeRow(coordTable_->rowCount() - 1); }); auto* btnLay = new QHBoxLayout(); btnLay->addStretch(); auto* cancelBtn = new QPushButton(QStringLiteral("取消"), this); okBtn_ = new QPushButton(QStringLiteral("确定"), this); okBtn_->setDefault(true); btnLay->addWidget(cancelBtn); btnLay->addWidget(okBtn_); root->addLayout(btnLay); connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject); connect(okBtn_, &QPushButton::clicked, this, &ExceptionDialog::onConfirm); connect(markTypeCombo_, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int) { onTypeChanged(); }); connect(exceptionTypeCombo_, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int) { suggestName(); }); onTypeChanged(); // 初始:拉首个类型的异常类型列表 + 铺最少坐标行 } QString ExceptionDialog::markTypeValue() const { return markTypeCombo_->currentData().toString(); } QString ExceptionDialog::exceptionTypeId() const { return exceptionTypeCombo_->currentData().toString(); } QString ExceptionDialog::exceptionName() const { return nameEdit_->text().trimmed(); } QString ExceptionDialog::exceptionRemark() const { return remarkEdit_->toPlainText(); } QJsonArray ExceptionDialog::manualCoordinates() const { QJsonArray coords; for (int r = 0; r < coordTable_->rowCount(); ++r) { auto* ix = coordTable_->item(r, 0); auto* iy = coordTable_->item(r, 1); if (!ix || !iy || ix->text().trimmed().isEmpty() || iy->text().trimmed().isEmpty()) continue; bool okx = false, oky = false; const double x = ix->text().toDouble(&okx); const double y = iy->text().toDouble(&oky); if (!okx || !oky) continue; coords.append(QJsonObject{{QStringLiteral("x"), x}, {QStringLiteral("y"), y}}); } return coords; } void ExceptionDialog::onTypeChanged() { // 对照原版 handleAnnotationTypeChange:标注类型变 → 清空名称(待重选类型后回填)+ // 重拉对应几何形态的异常类型列表 + 刷新「新增类型」按钮可用性。 nameEdit_->clear(); updateAddTypeEnabled(); loadExceptionTypes(); } void ExceptionDialog::updateAddTypeEnabled() { if (!addTypeBtn_) return; // 原版:文字类型(4) 或 未选标注类型时禁用「新增异常类型」。 const QString mt = markTypeValue(); addTypeBtn_->setEnabled(!mt.isEmpty() && mt != QStringLiteral("4")); } void ExceptionDialog::loadExceptionTypes() { if (!repo_) return; QPointer self(this); repo_->listExceptionTypes( projectId_, markTypeValue(), [self](bool ok, QJsonArray list, QString) { if (!self || !ok) return; self->exceptionTypeCombo_->clear(); for (const QJsonValue& v : list) { const QJsonObject o = v.toObject(); // 兼容 {label,value} 与 {exceptionTypeName,id} 两种返回形态。 QString label = o.value(QStringLiteral("label")).toString(); if (label.isEmpty()) label = o.value(QStringLiteral("exceptionTypeName")).toString(); QString id = o.value(QStringLiteral("value")).toString(); if (id.isEmpty()) id = o.value(QStringLiteral("id")).toString(); if (!id.isEmpty()) self->exceptionTypeCombo_->addItem(label, id); } if (self->exceptionTypeCombo_->count() > 0) self->suggestName(); }); } void ExceptionDialog::suggestName() { if (!repo_) return; const QString typeId = exceptionTypeCombo_->currentData().toString(); if (typeId.isEmpty()) return; QPointer self(this); // 对照原版 handleExceptionTypeChange:每次选/换异常类型都回填名称(res.data 为纯字符串)。 repo_->getExceptionName(typeId, remarkSourceId_, [self](bool ok, QString name, QString) { if (!self || !ok) return; self->nameEdit_->setText(name); }); } void ExceptionDialog::onConfirm() { if (!repo_) { reject(); return; } const QString name = nameEdit_->text().trimmed(); const QString typeId = exceptionTypeCombo_->currentData().toString(); if (name.isEmpty()) { QMessageBox::warning(this, windowTitle(), QStringLiteral("请输入名称")); return; } if (typeId.isEmpty()) { QMessageBox::warning(this, windowTitle(), QStringLiteral("请选择异常类型")); return; } // 主路径:坐标表留空 → accept(),由调用方在图上绘形采集坐标后 newException。 const QJsonArray coords = manualCoordinates(); if (coords.isEmpty()) { accept(); return; } // 兜底路径:用户手填了坐标 → 校验点数后直接弹窗内提交。 if (coords.size() < minPoints(markTypeValue())) { QMessageBox::warning(this, windowTitle(), QStringLiteral("坐标点数不足(点/文字≥1,线≥2,面≥3)")); return; } QJsonObject body{ {QStringLiteral("exceptionName"), name}, {QStringLiteral("exceptionTypeId"), typeId}, {QStringLiteral("remark"), remarkEdit_->toPlainText()}, {QStringLiteral("remarkSourceType"), markTypeValue()}, // 几何形态字符串 {QStringLiteral("remarkSourceId"), remarkSourceId_}, // = dsObjectId {QStringLiteral("projectId"), projectId_}, {QStringLiteral("location"), QJsonObject{{QStringLiteral("coordinate"), coords}}}, }; okBtn_->setEnabled(false); QPointer self(this); repo_->newException(body, [self](bool ok, QString msg) { if (!self) return; self->okBtn_->setEnabled(true); if (ok) { self->accept(); } else { QMessageBox::warning(self, self->windowTitle(), msg.isEmpty() ? QStringLiteral("创建失败") : msg); } }); } } // namespace geopro::app