feat(detail): 新增异常类型完整1:1(ExceptionTypeDialog 880px双Tab图例编辑器)

替换最小版,完整复刻原版 ExceptionLabel 子弹窗:
- 新建 ExceptionTypeDialog(880px,双Tab异常属性/标注名称):
  异常属性Tab(类型名称/代号必填/标准编号/标准名称/说明 + 按markType点/线/面/文字的
  图例样式编辑器:形状/大小/颜色/不透明度/线形/填充/字体,选项默认对照原版)
  标注名称Tab(自定义格式+分隔符+可增删名称列表 fieldName/fieldCode)
- 仓储 newCustomExceptionType 替换为 addExceptionType(POST /business/exceptionType,
  body 全字段对照原版 handleBeforeOk:legend/exceptionNameList/type:2/exceptionMarkType)
- ExceptionDialog「新增异常类型」按钮接通,成功刷新类型下拉并选中

build all 绿,341/341。
This commit is contained in:
gaozheng 2026-06-23 14:58:36 +08:00
parent 6cc973a183
commit 438ed78aad
8 changed files with 545 additions and 41 deletions

View File

@ -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

View File

@ -6,7 +6,6 @@
#include <QFormLayout>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QInputDialog>
#include <QJsonArray>
#include <QLabel>
#include <QLineEdit>
@ -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<ExceptionDialog> 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() {

View File

@ -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() 交给绘形

View File

@ -0,0 +1,413 @@
#include "panels/chart/ExceptionTypeDialog.hpp"
#include <utility>
#include <QButtonGroup>
#include <QColorDialog>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QJsonArray>
#include <QJsonObject>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QPlainTextEdit>
#include <QPointer>
#include <QPushButton>
#include <QRadioButton>
#include <QSpinBox>
#include <QStackedWidget>
#include <QTableWidget>
#include <QVBoxLayout>
#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 <std::size_t N>
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);
}
// 不透明度 0100 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改走 onSubmitaddDialogButtons 默认接的 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_NN 取当前最大序号 +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 01 不同,
// 这里沿用色块的 HexArgb 颜色串 + 0100 不透明度,与原版控件取值一致)。
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<ExceptionTypeDialog> 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

View File

@ -0,0 +1,109 @@
#pragma once
#include <functional>
#include <QColor>
#include <QDialog>
#include <QJsonObject>
#include <QString>
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

View File

@ -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<void(bool, QString)> cb) {
// 对照原版 newCustomExceptionTypedatasetInfo/index.js:160POST /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<void(bool, QString)> cb) {
// 对照原版 addExceptionTypeapis/toolComponent/exceptionType.js:9POST /business/exceptionType。
// body 由调用方ExceptionTypeDialog 双 Tab完整组装仓储仅做传输职责。
wireStatus(api_.postJsonAsync(QStringLiteral("/business/exceptionType"), body), std::move(cb));
}
void ApiDatasetCommandRepository::deleteException(const QString& id,

View File

@ -86,8 +86,8 @@ public:
std::function<void(bool ok, QString name, QString msg)> cb) override;
void newException(const QJsonObject& body,
std::function<void(bool ok, QString msg)> cb) override;
void newCustomExceptionType(const QString& projectId, const QString& name,
std::function<void(bool ok, QString msg)> cb) override;
void addExceptionType(const QJsonObject& body,
std::function<void(bool ok, QString msg)> cb) override;
void deleteException(const QString& id,
std::function<void(bool ok, QString msg)> cb) override;
void updateException(const QJsonObject& body,

View File

@ -180,13 +180,13 @@ public:
virtual void newException(const QJsonObject& body,
std::function<void(bool ok, QString msg)> cb) = 0;
// 新增自定义异常类型POST /business/customExceptionType
// body {projectId, exceptionTypeName}(对应原版 newCustomExceptionTypedatasetInfo/index.js:160
// 注:原版「新增异常类型」按钮实际走的是 addExceptionType(POST /business/exceptionType)
// 的完整 legend 编辑子流程;客户端此处复刻为「最小可用」——仅类型名,调用更简的
// customExceptionType 端点(见 IDialog 注释,差距已记录)。
virtual void newCustomExceptionType(const QString& projectId, const QString& name,
std::function<void(bool ok, QString msg)> cb) = 0;
// 新增异常类型(完整版,对照原版 addExceptionType / apis/toolComponent/exceptionType.js:9
// POST /business/exceptionTypebody 由「标注类型」对话框(异常属性 + 标注名称双 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<void(bool ok, QString msg)> cb) = 0;
// 删除异常DELETE /business/exception/{id}(对应原版 deleteExceptionDataInProfileInversion
virtual void deleteException(const QString& id,