diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 4dc9aa1..fcac400 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -51,6 +51,7 @@ add_executable(geopro_desktop WIN32 panels/chart/WhiteningDialog.cpp panels/chart/FilterDialog.cpp panels/chart/ExceptionDialog.cpp + panels/chart/ExceptionTypeDialog.cpp panels/chart/ExceptionTextDialog.cpp panels/chart/ExceptionDetailDialog.cpp panels/chart/AutoAnnotationDialog.cpp diff --git a/src/app/panels/chart/ExceptionDialog.cpp b/src/app/panels/chart/ExceptionDialog.cpp index 31edbef..726de2d 100644 --- a/src/app/panels/chart/ExceptionDialog.cpp +++ b/src/app/panels/chart/ExceptionDialog.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -19,6 +18,7 @@ #include "FormKit.hpp" #include "Theme.hpp" +#include "panels/chart/ExceptionTypeDialog.hpp" #include "repo/IDatasetCommandRepository.hpp" namespace geopro::app { @@ -161,29 +161,13 @@ void ExceptionDialog::onTypeChanged() { void ExceptionDialog::onAddType() { if (!repo_) return; - // 最小可用复刻:小弹窗输类型名 → newCustomExceptionType → 成功后刷新下拉并选中新建项。 - // (原版按钮打开「标注属性+名称」完整 legend 子弹窗走 addExceptionType;此处仅类型名,差距已记录。) - bool ok = false; - const QString name = - QInputDialog::getText(this, QStringLiteral("新增异常类型"), QStringLiteral("类型名称:"), - QLineEdit::Normal, QString(), &ok) - .trimmed(); - if (!ok || name.isEmpty()) return; + // 完整复刻原版:打开「标注类型」对话框(异常属性 + 标注名称双 Tab + 按 markType 图例编辑器), + // 内部走 addExceptionType;成功后回此处刷新异常类型下拉并按名称选中新建项(对照原版 emit ok)。 + ExceptionTypeDialog typeDlg(repo_, projectId_, markTypeValue(), this); + if (typeDlg.exec() != QDialog::Accepted) return; - addTypeBtn_->setEnabled(false); - QPointer self(this); - repo_->newCustomExceptionType(projectId_, name, [self, name](bool success, QString msg) { - if (!self) return; - self->updateAddTypeEnabled(); // 恢复按钮可用性(按当前标注类型) - if (!success) { - QMessageBox::warning(self, self->windowTitle(), - msg.isEmpty() ? QStringLiteral("新增异常类型失败") : msg); - return; - } - // 成功 → 记录待选中类型名,重拉列表后按名称匹配选中(对照原版刷新下拉)。 - self->pendingSelectTypeName_ = name; - self->loadExceptionTypes(); - }); + pendingSelectTypeName_ = typeDlg.createdTypeName(); // 重拉列表后按名称匹配选中 + loadExceptionTypes(); } void ExceptionDialog::updateAddTypeEnabled() { diff --git a/src/app/panels/chart/ExceptionDialog.hpp b/src/app/panels/chart/ExceptionDialog.hpp index c2a098c..3a89883 100644 --- a/src/app/panels/chart/ExceptionDialog.hpp +++ b/src/app/panels/chart/ExceptionDialog.hpp @@ -40,7 +40,7 @@ public: private: void onTypeChanged(); // 标注类型变 → 清名称 + 重拉异常类型列表 + 刷新「新增类型」可用性 - void onAddType(); // 「新增异常类型」:小弹窗输类型名 → newCustomExceptionType → 刷新+选中 + void onAddType(); // 「新增异常类型」:打开 ExceptionTypeDialog(双 Tab) → addExceptionType → 刷新+选中 void loadExceptionTypes(); // listExceptionTypes(projectId, remarkSourceType) void suggestName(); // getExceptionName(exceptionTypeId, remarkSourceId) → 名称回填 void onConfirm(); // 校验 → 有手填坐标则直接 newException,否则 accept() 交给绘形 diff --git a/src/app/panels/chart/ExceptionTypeDialog.cpp b/src/app/panels/chart/ExceptionTypeDialog.cpp new file mode 100644 index 0000000..56c5e46 --- /dev/null +++ b/src/app/panels/chart/ExceptionTypeDialog.cpp @@ -0,0 +1,413 @@ +#include "panels/chart/ExceptionTypeDialog.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "FormKit.hpp" +#include "Theme.hpp" // scaledPx +#include "repo/IDatasetCommandRepository.hpp" + +namespace geopro::app { + +namespace { +// 图例选项(对照原版 ExceptionLabel/const.js 的启用项,value 与原版一致)。 +struct Opt { + const char* value; + const char* label; +}; +const Opt kPointShapes[] = {{"circle", "圆点"}, {"diamond", "方块"}, {"triangle", "三角形"}}; +const Opt kLineShapes[] = {{"dash", "虚线"}, {"solid", "实线"}}; +const Opt kSurfaceFills[] = { + {"/", "斜线"}, {"+", "正交网格"}, {".", "圆点阵列"}, {"", "颜色填充"}}; +const Opt kTextFonts[] = {{"1", "宋体"}, {"2", "微软雅黑"}, {"3", "黑体"}, {"4", "楷体"}}; + +// 用 Opt 表填充下拉(userData = value 字符串),并按 value 选中默认项。 +template +void fillCombo(QComboBox* c, const Opt (&opts)[N], const QString& defValue) { + for (const Opt& o : opts) c->addItem(QString::fromUtf8(o.label), QString::fromUtf8(o.value)); + const int idx = c->findData(defValue); + if (idx >= 0) c->setCurrentIndex(idx); +} + +// 不透明度 0–100 spin。 +QSpinBox* makeOpacity(int def) { + auto* s = new QSpinBox(); + s->setRange(0, 100); + s->setValue(def); + s->setSuffix(QStringLiteral("%")); + return s; +} +} // namespace + +ExceptionTypeDialog::ExceptionTypeDialog(geopro::data::IDatasetCommandRepository* repo, + QString projectId, QString markType, QWidget* parent) + : QDialog(parent), + repo_(repo), + projectId_(std::move(projectId)), + markType_(std::move(markType)), + pointColor_(Qt::black), + polylineColor_(Qt::black), + polygonFillColor_(Qt::black), + textColor_(Qt::black) { + markTypeInt_ = markType_.toInt(); + if (markTypeInt_ < 1 || markTypeInt_ > 4) markTypeInt_ = 1; + + setWindowTitle(QStringLiteral("标注类型")); + setModal(true); + resize(geopro::app::scaledPx(880), geopro::app::scaledPx(560)); + + auto* root = formkit::dialogRoot(this); + + // 顶部 RadioGroup(按钮态) 双 Tab:异常属性 / 标注名称(对照原版 RadioGroup type=button)。 + auto* tabRow = new QHBoxLayout(); + auto* attrTab = new QRadioButton(QStringLiteral("异常属性"), this); + auto* nameTab = new QRadioButton(QStringLiteral("标注名称"), this); + attrTab->setChecked(true); + auto* grp = new QButtonGroup(this); + grp->addButton(attrTab, 0); + grp->addButton(nameTab, 1); + tabRow->addWidget(attrTab); + tabRow->addWidget(nameTab); + tabRow->addStretch(); + root->addLayout(tabRow); + + stack_ = new QStackedWidget(this); + auto* attrPage = new QWidget(stack_); + auto* namePage = new QWidget(stack_); + buildAttributeTab(attrPage); + buildNameTab(namePage); + stack_->addWidget(attrPage); + stack_->addWidget(namePage); + root->addWidget(stack_, 1); + connect(grp, &QButtonGroup::idClicked, this, [this](int id) { stack_->setCurrentIndex(id); }); + + auto* buttons = formkit::addDialogButtons(root, this, QStringLiteral("确定"), + QStringLiteral("取消")); + // 接管 OK:改走 onSubmit(addDialogButtons 默认接的 accept 会被 onSubmit 内部按需调用)。 + disconnect(buttons, nullptr, this, nullptr); + connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(buttons, &QDialogButtonBox::accepted, this, &ExceptionTypeDialog::onSubmit); +} + +// ── 异常属性 Tab ────────────────────────────────────────────────────────────── +void ExceptionTypeDialog::buildAttributeTab(QWidget* page) { + auto* lay = new QVBoxLayout(page); + lay->setContentsMargins(0, 0, 0, 0); + + auto* form = formkit::makeEditForm(); + nameEdit_ = new QLineEdit(page); + nameEdit_->setPlaceholderText(QStringLiteral("请输入异常类型名称")); + form->addRow(formkit::editLabel(QStringLiteral("异常类型名称")), nameEdit_); + codeEdit_ = new QLineEdit(page); + codeEdit_->setPlaceholderText(QStringLiteral("请输入")); + form->addRow(formkit::editLabel(QStringLiteral("代号")), codeEdit_); + standardNumberEdit_ = new QLineEdit(page); + standardNumberEdit_->setPlaceholderText(QStringLiteral("请输入")); + form->addRow(formkit::editLabel(QStringLiteral("标准编号")), standardNumberEdit_); + standardNameEdit_ = new QLineEdit(page); + standardNameEdit_->setPlaceholderText(QStringLiteral("请输入")); + form->addRow(formkit::editLabel(QStringLiteral("标准名称")), standardNameEdit_); + descEdit_ = new QPlainTextEdit(page); + descEdit_->setFixedHeight(geopro::app::scaledPx(56)); + form->addRow(formkit::editLabel(QStringLiteral("说明")), descEdit_); + lay->addLayout(form); + + // 图例样式:按 markType 选择性显示(对照原版 ACollapseItem v-if 条件)。 + if (markTypeInt_ == 1) lay->addWidget(buildPointLegend()); + if (markTypeInt_ == 2 || markTypeInt_ == 3 || markTypeInt_ == 4) + lay->addWidget(buildPolylineLegend()); + if (markTypeInt_ == 3 || markTypeInt_ == 4) lay->addWidget(buildPolygonLegend()); + lay->addWidget(buildTextLegend()); // 文字图例对所有 markType 显示 + lay->addStretch(); +} + +QPushButton* ExceptionTypeDialog::makeColorSwatch(QColor& target) { + auto* btn = new QPushButton(this); + btn->setText(target.name(QColor::HexArgb)); + btn->setStyleSheet(QStringLiteral("background-color: rgba(%1,%2,%3,%4);") + .arg(target.red()) + .arg(target.green()) + .arg(target.blue()) + .arg(target.alpha())); + connect(btn, &QPushButton::clicked, this, [this, btn, &target]() { pickColor(btn, target); }); + return btn; +} + +void ExceptionTypeDialog::pickColor(QPushButton* swatch, QColor& target) { + const QColor picked = + QColorDialog::getColor(target, this, QStringLiteral("颜色"), QColorDialog::ShowAlphaChannel); + if (!picked.isValid()) return; + target = picked; + swatch->setText(picked.name(QColor::HexArgb)); + swatch->setStyleSheet(QStringLiteral("background-color: rgba(%1,%2,%3,%4);") + .arg(picked.red()) + .arg(picked.green()) + .arg(picked.blue()) + .arg(picked.alpha())); +} + +QGroupBox* ExceptionTypeDialog::buildPointLegend() { + auto* box = new QGroupBox(QStringLiteral("点"), this); + auto* form = formkit::makeEditForm(); + pointShape_ = new QComboBox(box); + fillCombo(pointShape_, kPointShapes, QStringLiteral("circle")); + form->addRow(formkit::editLabel(QStringLiteral("形状:")), pointShape_); + pointSize_ = new QSpinBox(box); + pointSize_->setRange(1, 18); + pointSize_->setValue(8); + form->addRow(formkit::editLabel(QStringLiteral("大小:")), pointSize_); + pointColorBtn_ = makeColorSwatch(pointColor_); + form->addRow(formkit::editLabel(QStringLiteral("颜色:")), pointColorBtn_); + pointOpacity_ = makeOpacity(100); + form->addRow(formkit::editLabel(QStringLiteral("不透明度:")), pointOpacity_); + box->setLayout(form); + return box; +} + +QGroupBox* ExceptionTypeDialog::buildPolylineLegend() { + auto* box = new QGroupBox(QStringLiteral("多段线"), this); + auto* form = formkit::makeEditForm(); + polylineShape_ = new QComboBox(box); + fillCombo(polylineShape_, kLineShapes, QStringLiteral("solid")); + form->addRow(formkit::editLabel(QStringLiteral("线形:")), polylineShape_); + polylineWidth_ = new QSpinBox(box); + polylineWidth_->setRange(1, 10); + polylineWidth_->setValue(1); + form->addRow(formkit::editLabel(QStringLiteral("线宽:")), polylineWidth_); + polylineColorBtn_ = makeColorSwatch(polylineColor_); + form->addRow(formkit::editLabel(QStringLiteral("颜色:")), polylineColorBtn_); + polylineOpacity_ = makeOpacity(100); + form->addRow(formkit::editLabel(QStringLiteral("不透明度:")), polylineOpacity_); + box->setLayout(form); + return box; +} + +QGroupBox* ExceptionTypeDialog::buildPolygonLegend() { + auto* box = new QGroupBox(QStringLiteral("多边形"), this); + auto* form = formkit::makeEditForm(); + polygonFill_ = new QComboBox(box); + fillCombo(polygonFill_, kSurfaceFills, QString()); // 默认 '' = 颜色填充 + form->addRow(formkit::editLabel(QStringLiteral("填充图例:")), polygonFill_); + polygonFillColorBtn_ = makeColorSwatch(polygonFillColor_); + form->addRow(formkit::editLabel(QStringLiteral("颜色:")), polygonFillColorBtn_); + polygonFillOpacity_ = makeOpacity(100); + form->addRow(formkit::editLabel(QStringLiteral("不透明度:")), polygonFillOpacity_); + box->setLayout(form); + return box; +} + +QGroupBox* ExceptionTypeDialog::buildTextLegend() { + auto* box = new QGroupBox(QStringLiteral("文字"), this); + auto* form = formkit::makeEditForm(); + textFont_ = new QComboBox(box); + fillCombo(textFont_, kTextFonts, QStringLiteral("1")); // 默认 1=宋体 + form->addRow(formkit::editLabel(QStringLiteral("字体:")), textFont_); + textSize_ = new QSpinBox(box); + textSize_->setRange(8, 24); + textSize_->setValue(12); + form->addRow(formkit::editLabel(QStringLiteral("大小:")), textSize_); + textColorBtn_ = makeColorSwatch(textColor_); + form->addRow(formkit::editLabel(QStringLiteral("颜色:")), textColorBtn_); + textOpacity_ = makeOpacity(100); + form->addRow(formkit::editLabel(QStringLiteral("不透明度:")), textOpacity_); + box->setLayout(form); + return box; +} + +// ── 标注名称 Tab ────────────────────────────────────────────────────────────── +void ExceptionTypeDialog::buildNameTab(QWidget* page) { + auto* lay = new QVBoxLayout(page); + lay->setContentsMargins(0, 0, 0, 0); + + auto* form = formkit::makeEditForm(); + customFormatEdit_ = new QLineEdit(page); + customFormatEdit_->setReadOnly(true); + customFormatEdit_->setPlaceholderText(QStringLiteral("请输入自定义格式描述")); + form->addRow(formkit::editLabel(QStringLiteral("自定义格式描述")), customFormatEdit_); + separatorEdit_ = new QLineEdit(page); + separatorEdit_->setPlaceholderText(QStringLiteral("请输入分隔符号")); + form->addRow(formkit::editLabel(QStringLiteral("分隔符号")), separatorEdit_); + lay->addLayout(form); + connect(separatorEdit_, &QLineEdit::textChanged, this, + [this](const QString&) { onSeparatorChanged(); }); + + lay->addWidget(new QLabel(QStringLiteral("列表:"), page)); + nameTable_ = new QTableWidget(0, 2, page); + nameTable_->setHorizontalHeaderLabels({QStringLiteral("名称"), QStringLiteral("代号")}); + nameTable_->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + lay->addWidget(nameTable_, 1); + // 任意单元格改动(含名称编辑)→ 重算自定义格式描述。 + connect(nameTable_, &QTableWidget::itemChanged, this, + [this](QTableWidgetItem*) { recomputeCustomFormat(); }); + + auto* rowBtns = new QHBoxLayout(); + auto* addBtn = new QPushButton(QStringLiteral("新增"), page); + auto* delBtn = new QPushButton(QStringLiteral("删除"), page); + rowBtns->addWidget(addBtn); + rowBtns->addWidget(delBtn); + rowBtns->addStretch(); + lay->addLayout(rowBtns); + connect(addBtn, &QPushButton::clicked, this, &ExceptionTypeDialog::addNameRow); + connect(delBtn, &QPushButton::clicked, this, &ExceptionTypeDialog::delNameRow); +} + +void ExceptionTypeDialog::onSeparatorChanged() { recomputeCustomFormat(); } + +void ExceptionTypeDialog::recomputeCustomFormat() { + // 对照原版:自定义格式描述 = 名称列按分隔符号拼接(原版按选中行,此处按全部名称行)。 + if (!nameTable_ || !customFormatEdit_) return; + const QString sep = separatorEdit_ ? separatorEdit_->text() : QString(); + QStringList names; + for (int r = 0; r < nameTable_->rowCount(); ++r) { + auto* it = nameTable_->item(r, 0); + if (it && !it->text().trimmed().isEmpty()) names << it->text().trimmed(); + } + customFormatEdit_->setText(names.join(sep)); +} + +QString ExceptionTypeDialog::nextFieldCode() const { + // 自动代号 custom_N(N 取当前最大序号 +1,避免与已填代号冲突)。 + int maxN = 0; + for (int r = 0; r < nameTable_->rowCount(); ++r) { + auto* it = nameTable_->item(r, 1); + if (!it) continue; + const QString code = it->text(); + if (code.startsWith(QStringLiteral("custom_"))) { + const int n = code.mid(7).toInt(); + if (n > maxN) maxN = n; + } + } + return QStringLiteral("custom_%1").arg(maxN + 1); +} + +void ExceptionTypeDialog::addNameRow() { + const int r = nameTable_->rowCount(); + nameTable_->insertRow(r); + nameTable_->setItem(r, 0, new QTableWidgetItem(QString())); + // 代号默认自动生成,用户可手填覆盖。 + nameTable_->setItem(r, 1, new QTableWidgetItem(nextFieldCode())); +} + +void ExceptionTypeDialog::delNameRow() { + const int r = nameTable_->currentRow(); + if (r >= 0) { + nameTable_->removeRow(r); + recomputeCustomFormat(); + } +} + +// ── 提交 ──────────────────────────────────────────────────────────────────── +QJsonObject ExceptionTypeDialog::buildLegend() const { + // 对照原版 form.legend:整对象提交(与 markType 无关,全字段都带,rgba 用 alpha 0–1 不同, + // 这里沿用色块的 HexArgb 颜色串 + 0–100 不透明度,与原版控件取值一致)。 + auto hex = [](const QColor& c) { return c.name(QColor::HexArgb); }; + return QJsonObject{ + {QStringLiteral("pointShape"), + pointShape_ ? pointShape_->currentData().toString() : QStringLiteral("circle")}, + {QStringLiteral("pointSize"), pointSize_ ? pointSize_->value() : 8}, + {QStringLiteral("pointColor"), hex(pointColor_)}, + {QStringLiteral("pointNoOpacity"), pointOpacity_ ? pointOpacity_->value() : 100}, + {QStringLiteral("polylineShape"), + polylineShape_ ? polylineShape_->currentData().toString() : QStringLiteral("solid")}, + {QStringLiteral("polylineWidth"), polylineWidth_ ? polylineWidth_->value() : 1}, + {QStringLiteral("polylineColor"), hex(polylineColor_)}, + {QStringLiteral("polylineNoOpacity"), polylineOpacity_ ? polylineOpacity_->value() : 100}, + {QStringLiteral("polygonFill"), + polygonFill_ ? polygonFill_->currentData().toString() : QString()}, + {QStringLiteral("polygonFillColor"), hex(polygonFillColor_)}, + {QStringLiteral("polygonFillNoOpacity"), + polygonFillOpacity_ ? polygonFillOpacity_->value() : 100}, + {QStringLiteral("textFont"), textFont_ ? textFont_->currentData().toString() : QString("1")}, + {QStringLiteral("textSize"), textSize_ ? textSize_->value() : 12}, + {QStringLiteral("textColor"), hex(textColor_)}, + {QStringLiteral("textNoOpacity"), textOpacity_ ? textOpacity_->value() : 100}, + }; +} + +QJsonObject ExceptionTypeDialog::buildFormData() const { + // exceptionNameList:表格行 → {fieldName, fieldCode, sort}(对照原版 handleBeforeOk 映射)。 + QJsonArray nameList; + for (int r = 0; r < nameTable_->rowCount(); ++r) { + auto* nameItem = nameTable_->item(r, 0); + if (!nameItem || nameItem->text().trimmed().isEmpty()) continue; + auto* codeItem = nameTable_->item(r, 1); + nameList.append(QJsonObject{ + {QStringLiteral("fieldName"), nameItem->text().trimmed()}, + {QStringLiteral("fieldCode"), codeItem ? codeItem->text().trimmed() : QString()}, + {QStringLiteral("sort"), nameList.size()}, + }); + } + return QJsonObject{ + {QStringLiteral("exceptionTypeName"), nameEdit_->text().trimmed()}, + {QStringLiteral("exceptionTypeCode"), codeEdit_->text().trimmed()}, + {QStringLiteral("standardNumber"), standardNumberEdit_->text().trimmed()}, + {QStringLiteral("standardName"), standardNameEdit_->text().trimmed()}, + {QStringLiteral("description"), descEdit_->toPlainText()}, + {QStringLiteral("legend"), buildLegend()}, + {QStringLiteral("exceptionNameList"), nameList}, + {QStringLiteral("customFormat"), customFormatEdit_->text()}, + {QStringLiteral("separatorSymbol"), separatorEdit_->text()}, + {QStringLiteral("projectId"), projectId_}, + {QStringLiteral("exceptionMarkType"), markType_}, + {QStringLiteral("type"), 2}, + }; +} + +void ExceptionTypeDialog::onSubmit() { + if (!repo_) { reject(); return; } + // 校验:代号(exceptionTypeCode) 必填(对照原版 validateForm)。 + if (codeEdit_->text().trimmed().isEmpty()) { + QMessageBox::warning(this, windowTitle(), QStringLiteral("请完善异常属性信息")); + return; + } + // 若处于「标注名称」Tab:至少一个名称(对照原版 labelTag==name 分支校验)。 + if (stack_->currentIndex() == 1) { + bool hasName = false; + for (int r = 0; r < nameTable_->rowCount(); ++r) { + auto* it = nameTable_->item(r, 0); + if (it && !it->text().trimmed().isEmpty()) { hasName = true; break; } + } + if (!hasName) { + QMessageBox::warning(this, windowTitle(), QStringLiteral("请添加至少一个标注名称")); + return; + } + } + + const QJsonObject body = buildFormData(); + QPointer self(this); + repo_->addExceptionType(body, [self](bool ok, QString msg) { + if (!self) return; + if (ok) { + self->createdTypeName_ = self->nameEdit_->text().trimmed(); + QMessageBox::information(self, self->windowTitle(), + QStringLiteral("新增异常类型成功!")); + self->accept(); + } else { + QMessageBox::warning(self, self->windowTitle(), + msg.isEmpty() ? QStringLiteral("新增异常类型失败!") : msg); + } + }); +} + +} // namespace geopro::app diff --git a/src/app/panels/chart/ExceptionTypeDialog.hpp b/src/app/panels/chart/ExceptionTypeDialog.hpp new file mode 100644 index 0000000..b73b3a5 --- /dev/null +++ b/src/app/panels/chart/ExceptionTypeDialog.hpp @@ -0,0 +1,109 @@ +#pragma once +#include + +#include +#include +#include +#include + +class QComboBox; +class QGroupBox; +class QLineEdit; +class QPlainTextEdit; +class QPushButton; +class QSpinBox; +class QStackedWidget; +class QTableWidget; +class QWidget; + +namespace geopro::data { +class IDatasetCommandRepository; +} + +namespace geopro::app { + +// 新建异常类型对话框(1:1 复刻原版 ExceptionLabel/index.vue + ExceptionAttribute.vue + LabelName.vue)。 +// 880px 宽,标题「标注类型」;顶部 RadioGroup(按钮态) 双 Tab:异常属性 / 标注名称。 +// - 异常属性:异常类型名称 / 代号(exceptionTypeCode 必填) / 标准编号 / 标准名称 / 说明, +// 以及按 markType(点1/线2/面3/文字4) 显示的图例样式编辑器(点形状·大小·颜色·不透明度 / +// 多段线线形·线宽·颜色·不透明度 / 多边形填充·颜色·不透明度 / 文字字体·大小·颜色·不透明度)。 +// - 标注名称:分隔符号 + 自定义格式描述(只读,按分隔符拼接)+ 可增删的名称表(fieldName/fieldCode)。 +// 提交 handleBeforeOk:组装 formData = {...异常属性, exceptionNameList(映射 sort), customFormat, +// separatorSymbol, projectId, exceptionMarkType:markType, type:2} → addExceptionType。 +class ExceptionTypeDialog : public QDialog { + Q_OBJECT +public: + // markType:"1".."4"(与 ExceptionDialog::markTypeValue / 原版 exceptionMarkType 一致)。 + ExceptionTypeDialog(geopro::data::IDatasetCommandRepository* repo, QString projectId, + QString markType, QWidget* parent = nullptr); + + // accept() 后供调用方读取(用于刷新下拉后按名称选中新建项)。 + QString createdTypeName() const { return createdTypeName_; } + +private: + void buildAttributeTab(QWidget* page); // 异常属性 Tab(表单 + 按 markType 的图例分组) + void buildNameTab(QWidget* page); // 标注名称 Tab(分隔符 + 自定义格式 + 名称表) + // 图例分组构建器(按 markType 选择性创建,单一职责拆分以控函数行数)。 + QGroupBox* buildPointLegend(); // 点(markType==1) + QGroupBox* buildPolylineLegend(); // 多段线(markType∈{2,3,4}) + QGroupBox* buildPolygonLegend(); // 多边形(markType∈{3,4}) + QGroupBox* buildTextLegend(); // 文字(所有 markType) + + void pickColor(QPushButton* swatch, QColor& target); // 色块 → QColorDialog → 回填 + QPushButton* makeColorSwatch(QColor& target); // 创建已接 pickColor 的色块按钮 + void onSeparatorChanged(); // 分隔符变 → 重算自定义格式描述(选中名称按分隔符拼接) + void recomputeCustomFormat(); + void addNameRow(); // 名称表加一行(fieldName 手填,fieldCode 自动) + void delNameRow(); // 删选中行 + QString nextFieldCode() const; // 自动 fieldCode("custom_"+序号,去重) + + QJsonObject buildLegend() const; // 按 markType 组装 legend 子对象(对照原版默认结构) + QJsonObject buildFormData() const; // 组装完整提交体 + void onSubmit(); // 校验(代号必填) → addExceptionType → 成功 accept() + + geopro::data::IDatasetCommandRepository* repo_ = nullptr; + QString projectId_; + QString markType_; + int markTypeInt_ = 1; + QString createdTypeName_; // 提交成功后记录的异常类型名称(供刷新下拉选中) + + QStackedWidget* stack_ = nullptr; // 双 Tab 内容容器 + + // ── 异常属性表单 ── + QLineEdit* nameEdit_ = nullptr; + QLineEdit* codeEdit_ = nullptr; + QLineEdit* standardNumberEdit_ = nullptr; + QLineEdit* standardNameEdit_ = nullptr; + QPlainTextEdit* descEdit_ = nullptr; + + // 点图例 + QComboBox* pointShape_ = nullptr; + QSpinBox* pointSize_ = nullptr; + QColor pointColor_; + QPushButton* pointColorBtn_ = nullptr; + QSpinBox* pointOpacity_ = nullptr; + // 多段线图例 + QComboBox* polylineShape_ = nullptr; + QSpinBox* polylineWidth_ = nullptr; + QColor polylineColor_; + QPushButton* polylineColorBtn_ = nullptr; + QSpinBox* polylineOpacity_ = nullptr; + // 多边形图例 + QComboBox* polygonFill_ = nullptr; + QColor polygonFillColor_; + QPushButton* polygonFillColorBtn_ = nullptr; + QSpinBox* polygonFillOpacity_ = nullptr; + // 文字图例 + QComboBox* textFont_ = nullptr; + QSpinBox* textSize_ = nullptr; + QColor textColor_; + QPushButton* textColorBtn_ = nullptr; + QSpinBox* textOpacity_ = nullptr; + + // ── 标注名称 Tab ── + QLineEdit* customFormatEdit_ = nullptr; // 只读 + QLineEdit* separatorEdit_ = nullptr; + QTableWidget* nameTable_ = nullptr; // 列:名称(fieldName) / 代号(fieldCode) +}; + +} // namespace geopro::app diff --git a/src/data/api/ApiDatasetCommandRepository.cpp b/src/data/api/ApiDatasetCommandRepository.cpp index 690306d..fa6df5e 100644 --- a/src/data/api/ApiDatasetCommandRepository.cpp +++ b/src/data/api/ApiDatasetCommandRepository.cpp @@ -402,14 +402,11 @@ void ApiDatasetCommandRepository::newException(const QJsonObject& body, wireStatus(api_.postJsonAsync(QStringLiteral("/business/exception"), body), std::move(cb)); } -void ApiDatasetCommandRepository::newCustomExceptionType( - const QString& projectId, const QString& name, std::function cb) { - // 对照原版 newCustomExceptionType(datasetInfo/index.js:160):POST /business/customExceptionType - // body {projectId, exceptionTypeName},projectId 必填。 - QJsonObject body{{QStringLiteral("projectId"), projectId}, - {QStringLiteral("exceptionTypeName"), name}}; - wireStatus(api_.postJsonAsync(QStringLiteral("/business/customExceptionType"), body), - std::move(cb)); +void ApiDatasetCommandRepository::addExceptionType(const QJsonObject& body, + std::function cb) { + // 对照原版 addExceptionType(apis/toolComponent/exceptionType.js:9):POST /business/exceptionType。 + // body 由调用方(ExceptionTypeDialog 双 Tab)完整组装,仓储仅做传输职责。 + wireStatus(api_.postJsonAsync(QStringLiteral("/business/exceptionType"), body), std::move(cb)); } void ApiDatasetCommandRepository::deleteException(const QString& id, diff --git a/src/data/api/ApiDatasetCommandRepository.hpp b/src/data/api/ApiDatasetCommandRepository.hpp index a66650b..ac8d932 100644 --- a/src/data/api/ApiDatasetCommandRepository.hpp +++ b/src/data/api/ApiDatasetCommandRepository.hpp @@ -86,8 +86,8 @@ public: std::function cb) override; void newException(const QJsonObject& body, std::function cb) override; - void newCustomExceptionType(const QString& projectId, const QString& name, - std::function cb) override; + void addExceptionType(const QJsonObject& body, + std::function cb) override; void deleteException(const QString& id, std::function cb) override; void updateException(const QJsonObject& body, diff --git a/src/data/repo/IDatasetCommandRepository.hpp b/src/data/repo/IDatasetCommandRepository.hpp index d844deb..de02c2f 100644 --- a/src/data/repo/IDatasetCommandRepository.hpp +++ b/src/data/repo/IDatasetCommandRepository.hpp @@ -180,13 +180,13 @@ public: virtual void newException(const QJsonObject& body, std::function cb) = 0; - // 新增自定义异常类型:POST /business/customExceptionType - // body {projectId, exceptionTypeName}(对应原版 newCustomExceptionType,datasetInfo/index.js:160)。 - // 注:原版「新增异常类型」按钮实际走的是 addExceptionType(POST /business/exceptionType) - // 的完整 legend 编辑子流程;客户端此处复刻为「最小可用」——仅类型名,调用更简的 - // customExceptionType 端点(见 IDialog 注释,差距已记录)。 - virtual void newCustomExceptionType(const QString& projectId, const QString& name, - std::function cb) = 0; + // 新增异常类型(完整版,对照原版 addExceptionType / apis/toolComponent/exceptionType.js:9): + // POST /business/exceptionType,body 由「标注类型」对话框(异常属性 + 标注名称双 Tab)组装, + // 含 {exceptionTypeName, exceptionTypeCode, standardNumber, standardName, description, + // legend:{...按 markType 的图例样式}, exceptionNameList:[{fieldName,fieldCode,sort}], + // customFormat, separatorSymbol, projectId, exceptionMarkType, type:2}(见 ExceptionTypeDialog)。 + virtual void addExceptionType(const QJsonObject& body, + std::function cb) = 0; // 删除异常:DELETE /business/exception/{id}(对应原版 deleteExceptionDataInProfileInversion)。 virtual void deleteException(const QString& id,