From 6cc973a183a663e8d21ceec051ef7638c97e9635 Mon Sep 17 00:00:00 2001 From: gaozheng Date: Tue, 23 Jun 2026 14:35:27 +0800 Subject: [PATCH] =?UTF-8?q?feat(detail):=20=E5=BC=82=E5=B8=B8=E8=AF=A6?= =?UTF-8?q?=E6=83=85=E5=9D=90=E6=A0=87=E7=B3=BB/=E7=BD=91=E6=A0=BC?= =?UTF-8?q?=E8=89=B2=E9=98=B6templateId/=E6=96=B0=E5=A2=9E=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E7=B1=BB=E5=9E=8B=20=E6=94=B6=E5=B0=BE1:1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - I11 异常详情经纬度/投影坐标:Anomaly 加 lonLatPts/eastNorthPts,parseDatasetAnomalies 按原版响应字段(latitudeLongitude.latLon / geographicalCoordinates.coordinates)解析; 坐标系下拉条件显示(有 latLon 才给三项,对照原版 latLon.length===0),纯展示不换算 - 网格剖面色阶 templateId:ContourPayload 加 templateId,inversion.grid 加载/重载解析 getDetail 顶层 templateId,GridDataChartView 传入色阶编辑器→网格色阶另存覆盖可用 - 新增异常类型:仓储加 newCustomExceptionType(POST /business/customExceptionType {projectId,exceptionTypeName}),ExceptionDialog 按钮接通+刷新类型下拉 build all 绿,338/338。 --- .../panels/chart/ExceptionDetailDialog.cpp | 46 ++++++++++--------- .../panels/chart/ExceptionDetailDialog.hpp | 4 +- src/app/panels/chart/ExceptionDialog.cpp | 43 +++++++++++++---- src/app/panels/chart/ExceptionDialog.hpp | 2 + src/app/panels/chart/GridDataChartView.cpp | 6 ++- src/app/panels/chart/GridDataChartView.hpp | 1 + src/core/model/Anomaly.hpp | 6 +++ src/core/model/detail/DetailPayloads.hpp | 3 ++ src/data/api/ApiDatasetCommandRepository.cpp | 13 ++++++ src/data/api/ApiDatasetCommandRepository.hpp | 2 + src/data/api/ApiDatasetRepository.cpp | 7 ++- src/data/dto/DatasetChartDto.cpp | 12 +++++ src/data/repo/IDatasetCommandRepository.hpp | 8 ++++ tests/data/test_dataset_chart_dto.cpp | 29 ++++++++++++ 14 files changed, 149 insertions(+), 33 deletions(-) diff --git a/src/app/panels/chart/ExceptionDetailDialog.cpp b/src/app/panels/chart/ExceptionDetailDialog.cpp index cf30b99..1babbc5 100644 --- a/src/app/panels/chart/ExceptionDetailDialog.cpp +++ b/src/app/panels/chart/ExceptionDetailDialog.cpp @@ -136,9 +136,12 @@ QWidget* ExceptionDetailDialog::buildCoordTab() { topRow->addWidget(new QLabel(QStringLiteral("坐标系:"), tab)); coordSysCombo_ = new QComboBox(tab); coordSysCombo_->addItem(QStringLiteral("图形坐标"), QStringLiteral("jb")); - // 经纬度/投影:客户端暂无换算数据(DTO 只解析图形坐标),先占位、切换给提示,不静默。 - coordSysCombo_->addItem(QStringLiteral("经纬度坐标"), QStringLiteral("lonlat")); - coordSysCombo_->addItem(QStringLiteral("投影坐标"), QStringLiteral("projection")); + // 条件显示(对照原版 drawerExceptionInfo:latLon.length===0 → 仅图形坐标;否则三项)。 + // 纯展示响应坐标,不做客户端换算;响应未携带经纬度时退化为仅图形坐标,与原版一致。 + if (!anomaly_.lonLatPts.empty()) { + coordSysCombo_->addItem(QStringLiteral("经纬度坐标"), QStringLiteral("lonlat")); + coordSysCombo_->addItem(QStringLiteral("投影坐标"), QStringLiteral("projection")); + } topRow->addWidget(coordSysCombo_); topRow->addStretch(); vertexCountLabel_ = @@ -163,31 +166,32 @@ QWidget* ExceptionDetailDialog::buildCoordTab() { return tab; } +const std::vector& ExceptionDetailDialog::activeCoords() const { + // 按当前坐标系返回对应点集(对照原版 handleCoordChange:jb=图形 / lonlat=经纬度 / projection=投影)。 + const QString sys = coordSysCombo_ ? coordSysCombo_->currentData().toString() : QString(); + if (sys == QStringLiteral("lonlat")) return anomaly_.lonLatPts; // x=经度 y=纬度 + if (sys == QStringLiteral("projection")) return anomaly_.eastNorthPts; // x=northCoord y=eastCoord + return anomaly_.localPts; // 图形坐标 +} + void ExceptionDetailDialog::onCoordSystemChanged() { if (!coordTable_) return; - const QString sys = coordSysCombo_->currentData().toString(); - // 仅图形坐标有数据;经纬度/投影暂无换算能力 → 清表 + 提示(不静默吞)。 - if (sys != QStringLiteral("jb")) { - coordTable_->setRowCount(0); - QMessageBox::information(this, windowTitle(), - QStringLiteral("经纬度/投影坐标换算暂未在客户端提供,仅支持图形坐标。")); - return; - } - const int n = static_cast(anomaly_.localPts.size()); + // 纯展示响应坐标(不做客户端换算):按当前坐标系填表(对照原版 showCoord 重填)。 + const std::vector& pts = activeCoords(); + const int n = static_cast(pts.size()); coordTable_->setRowCount(n); for (int r = 0; r < n; ++r) { coordTable_->setItem(r, 0, new QTableWidgetItem(QString::number(r + 1))); - coordTable_->setItem( - r, 1, new QTableWidgetItem(QString::number(anomaly_.localPts[r].x, 'f', 7))); - coordTable_->setItem( - r, 2, new QTableWidgetItem(QString::number(anomaly_.localPts[r].y, 'f', 7))); + coordTable_->setItem(r, 1, new QTableWidgetItem(QString::number(pts[r].x, 'f', 7))); + coordTable_->setItem(r, 2, new QTableWidgetItem(QString::number(pts[r].y, 'f', 7))); coordTable_->setItem(r, 3, new QTableWidgetItem(QString())); // Z 空(对照原版) } + if (vertexCountLabel_) vertexCountLabel_->setText(QStringLiteral("顶点数:%1").arg(n)); } void ExceptionDetailDialog::exportCoords() { - if (coordSysCombo_->currentData().toString() != QStringLiteral("jb") || - anomaly_.localPts.empty()) { + const std::vector& pts = activeCoords(); + if (pts.empty()) { QMessageBox::information(this, windowTitle(), QStringLiteral("当前坐标系无可导出的坐标。")); return; } @@ -205,9 +209,9 @@ void ExceptionDetailDialog::exportCoords() { QTextStream ts(&f); // 对照原版:TSV「序号\tX\tY\tZ」,X/Y 7位小数,Z 空。 ts << QStringLiteral("序号\tX\tY\tZ\n"); - for (int i = 0; i < static_cast(anomaly_.localPts.size()); ++i) { - ts << (i + 1) << '\t' << QString::number(anomaly_.localPts[i].x, 'f', 7) << '\t' - << QString::number(anomaly_.localPts[i].y, 'f', 7) << "\t\n"; + for (int i = 0; i < static_cast(pts.size()); ++i) { + ts << (i + 1) << '\t' << QString::number(pts[i].x, 'f', 7) << '\t' + << QString::number(pts[i].y, 'f', 7) << "\t\n"; } f.close(); } diff --git a/src/app/panels/chart/ExceptionDetailDialog.hpp b/src/app/panels/chart/ExceptionDetailDialog.hpp index 8df725f..b6644eb 100644 --- a/src/app/panels/chart/ExceptionDetailDialog.hpp +++ b/src/app/panels/chart/ExceptionDetailDialog.hpp @@ -32,8 +32,10 @@ public: private: void onConfirm(); - void onCoordSystemChanged(); // 切换坐标系 → 重填坐标表(图形有数据,经纬度/投影暂无) + void onCoordSystemChanged(); // 切换坐标系 → 按对应点集重填坐标表(图形/经纬度/投影) void exportCoords(); // 导出当前坐标系坐标为 txt(7位小数) + // 当前坐标系对应的点集(jb=图形 / lonlat=经纬度 / projection=投影;纯展示响应数据,不换算)。 + const std::vector& activeCoords() const; QWidget* buildLegendTab(); // 图例信息 Tab(只读样式) QWidget* buildCoordTab(); // 坐标信息 Tab diff --git a/src/app/panels/chart/ExceptionDialog.cpp b/src/app/panels/chart/ExceptionDialog.cpp index 5be5fda..31edbef 100644 --- a/src/app/panels/chart/ExceptionDialog.cpp +++ b/src/app/panels/chart/ExceptionDialog.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -66,14 +67,7 @@ ExceptionDialog::ExceptionDialog(geopro::data::IDatasetCommandRepository* repo, 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 端完成,新增后此处下拉会同步可选。")); - }); + connect(addTypeBtn_, &QPushButton::clicked, this, &ExceptionDialog::onAddType); nameEdit_ = new QLineEdit(this); nameEdit_->setPlaceholderText(QStringLiteral("数据名称+异常类型代号+序号")); @@ -165,6 +159,33 @@ void ExceptionDialog::onTypeChanged() { loadExceptionTypes(); } +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; + + 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(); + }); +} + void ExceptionDialog::updateAddTypeEnabled() { if (!addTypeBtn_) return; // 原版:文字类型(4) 或 未选标注类型时禁用「新增异常类型」。 @@ -188,6 +209,12 @@ void ExceptionDialog::loadExceptionTypes() { if (id.isEmpty()) id = o.value(QStringLiteral("id")).toString(); if (!id.isEmpty()) self->exceptionTypeCombo_->addItem(label, id); } + // 若刚新建了类型 → 按名称匹配选中(找不到则保持默认首项,不报错)。 + if (!self->pendingSelectTypeName_.isEmpty()) { + const int idx = self->exceptionTypeCombo_->findText(self->pendingSelectTypeName_); + if (idx >= 0) self->exceptionTypeCombo_->setCurrentIndex(idx); + self->pendingSelectTypeName_.clear(); + } if (self->exceptionTypeCombo_->count() > 0) self->suggestName(); }); } diff --git a/src/app/panels/chart/ExceptionDialog.hpp b/src/app/panels/chart/ExceptionDialog.hpp index 4407d67..c2a098c 100644 --- a/src/app/panels/chart/ExceptionDialog.hpp +++ b/src/app/panels/chart/ExceptionDialog.hpp @@ -40,6 +40,7 @@ public: private: void onTypeChanged(); // 标注类型变 → 清名称 + 重拉异常类型列表 + 刷新「新增类型」可用性 + void onAddType(); // 「新增异常类型」:小弹窗输类型名 → newCustomExceptionType → 刷新+选中 void loadExceptionTypes(); // listExceptionTypes(projectId, remarkSourceType) void suggestName(); // getExceptionName(exceptionTypeId, remarkSourceId) → 名称回填 void onConfirm(); // 校验 → 有手填坐标则直接 newException,否则 accept() 交给绘形 @@ -52,6 +53,7 @@ private: QComboBox* markTypeCombo_ = nullptr; // userData = "1".."4" QComboBox* exceptionTypeCombo_ = nullptr; // userData = 异常类型 id QPushButton* addTypeBtn_ = nullptr; // 新增异常类型(对照原版,文字/未选类型禁用) + QString pendingSelectTypeName_; // 新建类型后待选中的类型名(下一次列表刷新时匹配选中) QLineEdit* nameEdit_ = nullptr; QPlainTextEdit* remarkEdit_ = nullptr; QTableWidget* coordTable_ = nullptr; diff --git a/src/app/panels/chart/GridDataChartView.cpp b/src/app/panels/chart/GridDataChartView.cpp index 25bd664..7409ee4 100644 --- a/src/app/panels/chart/GridDataChartView.cpp +++ b/src/app/panels/chart/GridDataChartView.cpp @@ -250,6 +250,7 @@ void GridDataChartView::setPayload(const QVariant& payload) { return; } const auto p = payload.value(); + lvlTemplateId_ = p.templateId; // 色阶模板 id(保存/覆盖回带,对照原版 lvlTemplateId) setGridData(p.grid, p.scale, p.anomalies); } @@ -318,9 +319,9 @@ void GridDataChartView::openColorScaleEditor() { // projectId 在打开时取一次(随项目切换生效);无 getter 退化为空 → 后端按钮禁用。 const QString projectId = projectIdGetter_ ? projectIdGetter_() : QString(); - // 网格剖面载荷(ContourPayload)无 templateId 字段 → lvlTemplateId 传空(覆盖复选框禁用)。 + // 传入网格色阶模板 id(getDetail type2 顶层 templateId)→ 「另存为覆盖」可用(对照原版 lvlTemplateId)。 ColorScaleConfigDialog dlg(gridScale_, grid_.vmin, grid_.vmax, std::move(samples), lineCfg_, - tplRepo_, projectId, QString(), this); + tplRepo_, projectId, lvlTemplateId_, this); if (dlg.exec() != QDialog::Accepted) return; gridScale_ = dlg.colorScale(); @@ -369,6 +370,7 @@ void GridDataChartView::reloadGrid() { msg.isEmpty() ? QStringLiteral("重载失败") : msg); return; } + self->lvlTemplateId_ = p.templateId; // 重载后同步模板 id(色阶覆盖回带) self->setGridData(p.grid, p.scale, p.anomalies); }); } diff --git a/src/app/panels/chart/GridDataChartView.hpp b/src/app/panels/chart/GridDataChartView.hpp index b59cec4..b52139e 100644 --- a/src/app/panels/chart/GridDataChartView.hpp +++ b/src/app/panels/chart/GridDataChartView.hpp @@ -113,6 +113,7 @@ private: geopro::core::Grid grid_{1, 1}; geopro::core::ColorScale gridScale_; std::vector anoms_; + QString lvlTemplateId_; // 网格色阶模板 id(getDetail type2 顶层 templateId);色阶「另存为覆盖」用 bool hasGrid_ = false; // 工具条显隐开关 diff --git a/src/core/model/Anomaly.hpp b/src/core/model/Anomaly.hpp index da9f2b8..6f2e8d7 100644 --- a/src/core/model/Anomaly.hpp +++ b/src/core/model/Anomaly.hpp @@ -19,6 +19,12 @@ struct Anomaly { std::string createTime; // 创建时间(异常列表展示用,只读) AnomalyMarkType markType = AnomalyMarkType::Polyline; std::vector localPts; // 2D 局部坐标(剖面详情:x=距离, y=深度) + // 经纬度 / 投影坐标(详情坐标系切换用,纯展示,不做客户端换算;对照原版 drawerExceptionInfo)。 + // 来源响应字段:latitudeLongitude.latLon[].{longitude,latitude}(→lonLatPts: x=经度 y=纬度)、 + // geographicalCoordinates.coordinates[].{northCoord,eastCoord}(→eastNorthPts: x=northCoord y=eastCoord)。 + // 空 = 响应未携带 → 坐标系下拉退化为仅「图形坐标」(与原版 latLon.length===0 一致)。 + std::vector lonLatPts; // 经纬度坐标:x=经度(longitude), y=纬度(latitude) + std::vector eastNorthPts; // 投影坐标:x=northCoord, y=eastCoord(对照原版映射) // VTK 三维:异常多边形/折线/点的世界 3D 坐标(落在所在切片平面上)+ 平面(法向/一点), // 用于 3D 渲染与重定位/正视;与切片生命周期解耦(切片可删,异常按 worldPts/plane 仍可显示)。 std::vector worldPts; diff --git a/src/core/model/detail/DetailPayloads.hpp b/src/core/model/detail/DetailPayloads.hpp index d8a8135..749db53 100644 --- a/src/core/model/detail/DetailPayloads.hpp +++ b/src/core/model/detail/DetailPayloads.hpp @@ -49,6 +49,9 @@ struct ContourPayload { geopro::core::Grid grid{1, 1}; geopro::core::ColorScale scale; std::vector anomalies; + // 色阶模板 id(来自 lvl/colorGradation/getDetail type2 的顶层 templateId):保存/覆盖色阶时回带 + // (对照原版 contourPage lvlTemplateId = lvlConfig?.templateId;可空)。 + QString templateId; }; // 列渲染种类:Text=预格式化文本(默认);Toggle=每行开关(蓝色药丸开关,ON=可见)。 diff --git a/src/data/api/ApiDatasetCommandRepository.cpp b/src/data/api/ApiDatasetCommandRepository.cpp index 2f1988e..690306d 100644 --- a/src/data/api/ApiDatasetCommandRepository.cpp +++ b/src/data/api/ApiDatasetCommandRepository.cpp @@ -302,6 +302,9 @@ void ApiDatasetCommandRepository::loadInversionGrid( dto::parseColorBar(r[1].data), dto::parseDatasetAnomalies( r[2].data.value(QStringLiteral("value")).toArray())}; + // 顶层 templateId(对照原版 lvlTemplateId;色阶保存/覆盖回带)。 + p.templateId = + r[1].data.value(QStringLiteral("templateId")).toVariant().toString(); cb(true, p, {}); }); QObject::connect(batch, &net::ApiBatch::failed, batch, @@ -399,6 +402,16 @@ 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::deleteException(const QString& id, std::function cb) { wireStatus(api_.deleteAsync(QStringLiteral("/business/exception/%1").arg(enc(id))), diff --git a/src/data/api/ApiDatasetCommandRepository.hpp b/src/data/api/ApiDatasetCommandRepository.hpp index 4697033..a66650b 100644 --- a/src/data/api/ApiDatasetCommandRepository.hpp +++ b/src/data/api/ApiDatasetCommandRepository.hpp @@ -86,6 +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 deleteException(const QString& id, std::function cb) override; void updateException(const QJsonObject& body, diff --git a/src/data/api/ApiDatasetRepository.cpp b/src/data/api/ApiDatasetRepository.cpp index 2a11a88..a1f75e0 100644 --- a/src/data/api/ApiDatasetRepository.cpp +++ b/src/data/api/ApiDatasetRepository.cpp @@ -34,6 +34,7 @@ struct GridParts { geopro::core::Grid grid{1, 1}; // Grid 无默认构造;占位 geopro::core::ColorScale gridScale; std::vector anomalies; + QString templateId; // 网格色阶模板 id(保存/覆盖回带,对照原版 lvlTemplateId) }; // 失败判定(原 must() 口径):业务码 != 200 或传输错误。 @@ -78,6 +79,8 @@ GridParts parseGridParts(const QList& r) { p.grid = dto::parseInversionGrid(r[0].data); p.gridScale = dto::parseColorBar(r[1].data); p.anomalies = dto::parseDatasetAnomalies(r[2].data.value(QStringLiteral("value")).toArray()); + // 顶层 templateId(对照原版 lvlConfig?.templateId,与散点 parseScatterParts 同范式)。 + p.templateId = r[1].data.value(QStringLiteral("templateId")).toVariant().toString(); return p; } @@ -179,7 +182,9 @@ DetailLoad* ApiDatasetRepository::makeInversionScatter(const std::string& dsId) DetailLoad* ApiDatasetRepository::makeInversionGrid(const std::string& dsId) { return new ApiDetailLoad(inversionGridBatch(api_, dsId), [](const QList& r) { GridParts p = parseGridParts(r); - return QVariant::fromValue(core::ContourPayload{p.grid, p.gridScale, p.anomalies}); + core::ContourPayload payload{p.grid, p.gridScale, p.anomalies}; + payload.templateId = p.templateId; // 色阶保存/覆盖回带(对照原版 lvlTemplateId) + return QVariant::fromValue(payload); }); } diff --git a/src/data/dto/DatasetChartDto.cpp b/src/data/dto/DatasetChartDto.cpp index 098189f..53120c7 100644 --- a/src/data/dto/DatasetChartDto.cpp +++ b/src/data/dto/DatasetChartDto.cpp @@ -92,6 +92,18 @@ std::vector parseDatasetAnomalies(const QJsonArray& arr) { const QJsonObject p = c.toObject(); a.localPts.push_back(Vec2{p.value("x").toDouble(), p.value("y").toDouble()}); } + // 经纬度坐标(对照原版:latitudeLongitude.latLon[].{longitude,latitude})。 + // 纯展示,不换算;空/缺失 → 详情坐标系下拉只给「图形坐标」(与原版 latLon.length===0 一致)。 + for (auto c : o.value("latitudeLongitude").toObject().value("latLon").toArray()) { + const QJsonObject p = c.toObject(); + a.lonLatPts.push_back(Vec2{p.value("longitude").toDouble(), p.value("latitude").toDouble()}); + } + // 投影坐标(对照原版:geographicalCoordinates.coordinates[].{northCoord,eastCoord}, + // 原版映射 x=northCoord、y=eastCoord)。 + for (auto c : o.value("geographicalCoordinates").toObject().value("coordinates").toArray()) { + const QJsonObject p = c.toObject(); + a.eastNorthPts.push_back(Vec2{p.value("northCoord").toDouble(), p.value("eastCoord").toDouble()}); + } out.push_back(std::move(a)); } return out; diff --git a/src/data/repo/IDatasetCommandRepository.hpp b/src/data/repo/IDatasetCommandRepository.hpp index a30f85f..d844deb 100644 --- a/src/data/repo/IDatasetCommandRepository.hpp +++ b/src/data/repo/IDatasetCommandRepository.hpp @@ -180,6 +180,14 @@ 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; + // 删除异常:DELETE /business/exception/{id}(对应原版 deleteExceptionDataInProfileInversion)。 virtual void deleteException(const QString& id, std::function cb) = 0; diff --git a/tests/data/test_dataset_chart_dto.cpp b/tests/data/test_dataset_chart_dto.cpp index 030b50c..6fcc765 100644 --- a/tests/data/test_dataset_chart_dto.cpp +++ b/tests/data/test_dataset_chart_dto.cpp @@ -56,6 +56,35 @@ TEST(DatasetChartDto, ParsesAnomalyPolyline) { EXPECT_DOUBLE_EQ(v[0].localPts[1].x, 3.0); EXPECT_TRUE(v[0].dashed); } +TEST(DatasetChartDto, ParsesAnomalyLatLonAndEastNorth) { + // 对照原版 drawerExceptionInfo:经纬度 latitudeLongitude.latLon[].{longitude,latitude}、 + // 投影 geographicalCoordinates.coordinates[].{northCoord,eastCoord}(x=northCoord y=eastCoord)。 + auto arr = QJsonDocument::fromJson( + R"([{"id":"exc-2","exceptionName":"A2","exceptionMarkType":2, + "location":{"coordinate":[{"x":1,"y":2}]}, + "latitudeLongitude":{"latLon":[{"longitude":120.5,"latitude":30.25}]}, + "geographicalCoordinates":{"coordinates":[{"northCoord":3350000.0,"eastCoord":500000.0}]}}])") + .array(); + auto v = parseDatasetAnomalies(arr); + ASSERT_EQ(v.size(), 1u); + ASSERT_EQ(v[0].lonLatPts.size(), 1u); + EXPECT_DOUBLE_EQ(v[0].lonLatPts[0].x, 120.5); // 经度 + EXPECT_DOUBLE_EQ(v[0].lonLatPts[0].y, 30.25); // 纬度 + ASSERT_EQ(v[0].eastNorthPts.size(), 1u); + EXPECT_DOUBLE_EQ(v[0].eastNorthPts[0].x, 3350000.0); // northCoord → X + EXPECT_DOUBLE_EQ(v[0].eastNorthPts[0].y, 500000.0); // eastCoord → Y +} +TEST(DatasetChartDto, AnomalyWithoutGeoCoordsLeavesVectorsEmpty) { + // 响应未携带经纬度/投影 → 两向量为空(详情坐标系下拉退化为仅「图形坐标」,与原版一致)。 + auto arr = QJsonDocument::fromJson( + R"([{"id":"exc-3","exceptionName":"A3","exceptionMarkType":2, + "location":{"coordinate":[{"x":1,"y":2}]}}])") + .array(); + auto v = parseDatasetAnomalies(arr); + ASSERT_EQ(v.size(), 1u); + EXPECT_TRUE(v[0].lonLatPts.empty()); + EXPECT_TRUE(v[0].eastNorthPts.empty()); +} TEST(DatasetChartDto, ParsesAnomalyIdentityFields) { // I10/I11/I12 需要 id(删除/更新/定位)、备注、异常类型 id、创建时间。 auto arr = QJsonDocument::fromJson(