From 75cf8d40ba90f46c69df292153dd7c2b7ff92f2c Mon Sep 17 00:00:00 2001 From: gaozheng Date: Tue, 23 Jun 2026 12:44:42 +0800 Subject: [PATCH] =?UTF-8?q?fix(detail):=20=E5=8F=8D=E6=BC=94=E5=8A=A8?= =?UTF-8?q?=E6=80=81=E8=A1=A8=E5=8D=95=E6=94=AF=E6=8C=8111=E7=A7=8D?= =?UTF-8?q?=E6=8E=A7=E4=BB=B6+=E5=BF=85=E5=A1=AB=E6=A0=A1=E9=AA=8C=20+=20g?= =?UTF-8?q?rid=E6=8C=89=E9=92=AE=E8=A7=86=E8=A7=89=20+=20=E8=89=B2?= =?UTF-8?q?=E9=98=B6templateId?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - InversionFormDialog 动态表单不再一律下拉:复用项目既有 parseEditableForm + DynamicFormEditor(与对象/结构编辑同款),按 displayComponentType 渲染 11 种控件 (文本/只读/复选/下拉/日期/时间/日期时间/多行/数字按dataType+limit/树选降级/步进) + requiredType 必填校验/只读禁用。生成视电阻率纯select行为不变。 删除被孤立的 InversionFormParse + 其测试。 - grid 反演按钮行:左"电法列表"radio + 右蓝色主按钮 space-between(仅dd_grid) - 色阶保存带 templateId(ScatterPayload+DTO捕获色阶detail顶层templateId,measurement 与反演原数据两路;空可省,对照原版) 后续项(未动,与3D共享风险):ColorScaleConfigDialog 另存覆盖/散点模板库可用。 build all 绿,336/336。GPR/金字塔/.superpowers WIP 未碰。 --- src/app/CMakeLists.txt | 1 - src/app/panels/chart/DataTableView.cpp | 34 ++++++-- src/app/panels/chart/InversionFormDialog.cpp | 91 +++++++------------- src/app/panels/chart/InversionFormDialog.hpp | 23 ++--- src/app/panels/chart/InversionFormParse.cpp | 52 ----------- src/app/panels/chart/InversionFormParse.hpp | 35 -------- src/app/panels/chart/RawDataChartView.cpp | 2 + src/core/model/detail/DetailPayloads.hpp | 3 + src/data/api/ApiDatasetRepository.cpp | 6 +- src/data/dto/MeasurementDto.cpp | 2 + tests/CMakeLists.txt | 5 -- tests/app/test_inversion_form_parse.cpp | 87 ------------------- tests/data/test_measurement_dto.cpp | 12 +++ 13 files changed, 91 insertions(+), 262 deletions(-) delete mode 100644 src/app/panels/chart/InversionFormParse.cpp delete mode 100644 src/app/panels/chart/InversionFormParse.hpp delete mode 100644 tests/app/test_inversion_form_parse.cpp diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index f9535de..4dc9aa1 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -41,7 +41,6 @@ add_executable(geopro_desktop WIN32 panels/QuillDelta.cpp panels/chart/RawDataChartView.cpp panels/chart/InversionFormDialog.cpp - panels/chart/InversionFormParse.cpp panels/chart/ScatterDataOps.cpp panels/chart/SaveAsDialog.cpp panels/chart/ScatterFilterDialog.cpp diff --git a/src/app/panels/chart/DataTableView.cpp b/src/app/panels/chart/DataTableView.cpp index 919bcd0..3904d31 100644 --- a/src/app/panels/chart/DataTableView.cpp +++ b/src/app/panels/chart/DataTableView.cpp @@ -10,10 +10,13 @@ #include #include #include +#include #include #include #include +#include "Theme.hpp" + #include "panels/chart/InversionFormDialog.hpp" #include "panels/chart/ScatterDataOps.hpp" // toggledDisplayStatus #include "panels/chart/TablePager.hpp" @@ -155,11 +158,16 @@ DataTableView::DataTableView(QWidget* parent) : QWidget(parent) { lay->setContentsMargins(0, 0, 0, 0); lay->setSpacing(0); - // 顶部功能按钮行(默认隐藏;仅 dd_grid 载荷带 functionButtons 时显示)。右对齐贴近原版布局。 + // 顶部功能按钮行(默认隐藏;仅 dd_grid 载荷带 functionButtons 时显示)。 + // 布局对照原版 DdGrid/index.vue .swicth:左侧 radio-group(「电法列表」单选项) + 右侧主按钮组, + // space-between。radio 仅视觉占位(原版亦仅一项、无实际切换作用)。 toolbar_ = new QWidget(this); toolbarLay_ = new QHBoxLayout(toolbar_); toolbarLay_->setContentsMargins(0, 0, 0, 8); - toolbarLay_->addStretch(1); + auto* listRadio = new QRadioButton(QStringLiteral("电法列表"), toolbar_); + listRadio->setChecked(true); + toolbarLay_->addWidget(listRadio); // index 0:左侧单选项 + toolbarLay_->addStretch(1); // index 1:把功能按钮推到右侧(rebuildToolbar 在末尾追加) toolbar_->hide(); lay->addWidget(toolbar_); @@ -230,24 +238,36 @@ void DataTableView::setCommandRepo(geopro::data::IDatasetCommandRepository* repo } void DataTableView::rebuildToolbar(const std::vector& buttons) { - // 清空旧按钮(保留末尾 addStretch;逐项删 QPushButton)。 + // 清空旧功能按钮(仅删 QPushButton,保留左侧 radio 与中间 stretch)。 for (int i = toolbarLay_->count() - 1; i >= 0; --i) { - if (auto* w = toolbarLay_->itemAt(i)->widget()) { - toolbarLay_->removeWidget(w); - w->deleteLater(); + if (auto* btn = qobject_cast(toolbarLay_->itemAt(i)->widget())) { + toolbarLay_->removeWidget(btn); + btn->deleteLater(); } } // 仅渲染 enable 的按钮(原版 v-show="enable");全空/全禁用 → 隐藏整行。 + // 主按钮蓝色实心(对照原版 type="primary"),复用 primaryBtn QSS。 int shown = 0; for (const auto& b : buttons) { if (!b.enable) continue; auto* btn = new QPushButton(b.nameChn, toolbar_); + btn->setObjectName(QStringLiteral("primaryBtn")); const QString code = b.code; connect(btn, &QPushButton::clicked, this, [this, code] { onFunctionButton(code); }); - toolbarLay_->addWidget(btn); + toolbarLay_->addWidget(btn); // 末尾追加 → 落在 stretch 之后(右侧) ++shown; } + if (shown > 0) { + applyTokenizedStyleSheet( + toolbar_, + QStringLiteral( + "QPushButton#primaryBtn { background: {{accent/primary}}; color: {{text/on-primary}};" + " border: 1px solid {{accent/primary}}; border-radius: 6px; padding: 6px 14px; }" + "QPushButton#primaryBtn:hover { background: {{accent/primary-hover}};" + " border-color: {{accent/primary-hover}}; }" + "QPushButton#primaryBtn:pressed { background: {{accent/primary-pressed}}; }")); + } toolbar_->setVisible(shown > 0); } diff --git a/src/app/panels/chart/InversionFormDialog.cpp b/src/app/panels/chart/InversionFormDialog.cpp index 15d3de0..4f6880d 100644 --- a/src/app/panels/chart/InversionFormDialog.cpp +++ b/src/app/panels/chart/InversionFormDialog.cpp @@ -3,22 +3,21 @@ #include #include -#include -#include #include #include #include #include +#include #include #include #include "FormKit.hpp" +#include "dto/NavDto.hpp" // parseEditableForm(与对象/结构编辑共用的动态表单解析) +#include "panels/DynamicFormEditor.hpp" #include "repo/IDatasetCommandRepository.hpp" namespace geopro::app { -// 纯解析/组装函数定义在 InversionFormParse.cpp(Qt-Core-only,便于单测)。 - namespace { constexpr char kVisualResistivityCode[] = "script_visual_resistivity_data"; } // namespace @@ -45,11 +44,14 @@ InversionFormDialog::InversionFormDialog(Mode mode, geopro::data::IDatasetComman modelLay->addWidget(modelCombo_); root->addLayout(modelLay); - // 动态字段容器(按 groups_ 重建)。 - formHost_ = new QWidget(this); - formHostLay_ = new QVBoxLayout(formHost_); - formHostLay_->setContentsMargins(0, 0, 0, 0); - root->addWidget(formHost_, 1); + // 动态字段容器:复用 DynamicFormEditor(按 displayComponentType 渲染 11 种控件 + 必填校验)。 + // 放滚动区内,避免字段多时撑爆对话框。 + auto* scroll = new QScrollArea(this); + scroll->setWidgetResizable(true); + scroll->setFrameShape(QFrame::NoFrame); + editor_ = new DynamicFormEditor(); + scroll->setWidget(editor_); + root->addWidget(scroll, 1); // 底部按钮(取消 / 确定)。 auto* btnLay = new QHBoxLayout(); @@ -103,9 +105,8 @@ void InversionFormDialog::loadScripts() { void InversionFormDialog::onModelChanged() { const QString typeId = modelCombo_->currentData().toString(); - groups_.clear(); if (typeId.isEmpty()) { - rebuildFormArea(); // 清空表单(复刻 changeModel: 清空 dynamicForms) + editor_->clear(); // 清空表单(复刻 changeModel: 清空 dynamicForms) return; } loadDynamicForm(typeId); @@ -117,50 +118,12 @@ void InversionFormDialog::loadDynamicForm(const QString& typeId) { repo_->getDynamicForm(projectId_, typeId, [self](bool ok, QJsonObject data, QString) { if (!self) return; if (!ok) return; - self->groups_ = parseDynamicForm(data); - self->rebuildFormArea(); + // 复用 parseEditableForm(formList → values → displayComponentType/requiredType/optionsObject) + // + DynamicFormEditor(11 种控件渲染)。confType 对反演渲染无影响,取 0 占位。 + self->editor_->setForm(geopro::data::dto::parseEditableForm(data, 0)); }); } -void InversionFormDialog::rebuildFormArea() { - // 清空旧字段控件(含布局子项),重置取值索引。 - fieldCombos_.clear(); - fieldCodes_.clear(); - QLayoutItem* item = nullptr; - while ((item = formHostLay_->takeAt(0)) != nullptr) { - if (item->widget()) item->widget()->deleteLater(); - delete item; - } - - const bool fillDefaults = (mode_ == Mode::ApparentResistivity); - for (const auto& g : groups_) { - // 分组卡片:标题 + 字段两列网格(复刻原版 a-card 分组 + 半宽字段)。 - auto* card = new QFrame(formHost_); - card->setObjectName(QStringLiteral("inversionGroupCard")); - auto* cardLay = new QVBoxLayout(card); - if (!g.groupName.isEmpty()) - formkit::addSection(cardLay, g.groupName, card, /*topGap=*/false); - auto* grid = new QGridLayout(); - cardLay->addLayout(grid); - int col = 0, gridRow = 0; - for (const auto& f : g.fields) { - auto* fieldBox = new QVBoxLayout(); - fieldBox->addWidget(formkit::editLabel(f.fieldName, card)); - auto* combo = new QComboBox(card); - for (const auto& o : f.options) combo->addItem(o.label, o.value); - // 生成视电阻率:默认选首项(复刻 initDynamicFieldsDefaultValues)。 - if (fillDefaults && combo->count() > 0) combo->setCurrentIndex(0); - fieldBox->addWidget(combo); - grid->addLayout(fieldBox, gridRow, col); - fieldCombos_.push_back(combo); - fieldCodes_.push_back(f.fieldCode); - if (++col >= 2) { col = 0; ++gridRow; } - } - formHostLay_->addWidget(card); - } - formHostLay_->addStretch(); -} - void InversionFormDialog::onConfirm() { if (!repo_) { reject(); return; } const QString scriptId = modelCombo_->currentData().toString(); @@ -169,14 +132,24 @@ void InversionFormDialog::onConfirm() { return; } - // 由当前各字段下拉选值装配 {fieldCode: value}。 - QJsonObject selected; - for (size_t i = 0; i < fieldCombos_.size(); ++i) { - selected.insert(fieldCodes_[static_cast(i)], - fieldCombos_[i]->currentData().toString()); + // 必填校验(requiredType===1):拦截提交并聚焦首个缺失字段(对照原版 a-form rules)。 + QString missing; + if (!editor_->validateRequired(&missing)) { + editor_->focusFirstInvalid(); + QMessageBox::warning(this, windowTitle(), + QStringLiteral("请填写必填项:%1").arg(missing)); + return; + } + + // 由各动态控件收集 {fieldCode: value}。生成视电阻率:空值不进体(对照原版 if(selectedValue)); + // 反演运算:保留全部字段(对照原版 InversionForm 提交 form.properties 整体)。 + const auto values = editor_->collectValues(); + QJsonObject fields; + const bool omitEmpty = (mode_ == Mode::ApparentResistivity); + for (auto it = values.constBegin(); it != values.constEnd(); ++it) { + if (omitEmpty && it.value().trimmed().isEmpty()) continue; + fields.insert(it.key(), it.value()); } - const bool fillDefaults = (mode_ == Mode::ApparentResistivity); - const QJsonObject fields = assembleFieldMap(groups_, selected, fillDefaults); okBtn_->setEnabled(false); QPointer self(this); diff --git a/src/app/panels/chart/InversionFormDialog.hpp b/src/app/panels/chart/InversionFormDialog.hpp index fb01b92..f73c756 100644 --- a/src/app/panels/chart/InversionFormDialog.hpp +++ b/src/app/panels/chart/InversionFormDialog.hpp @@ -1,14 +1,9 @@ #pragma once -#include - #include #include #include -#include "panels/chart/InversionFormParse.hpp" // InversionGroup + 纯解析/组装函数 - class QComboBox; -class QVBoxLayout; class QPushButton; class QWidget; @@ -18,14 +13,18 @@ class IDatasetCommandRepository; namespace geopro::app { +class DynamicFormEditor; + // 反演动态表单对话框(1:1 复刻原版 web)。一套对话框服务两个入口,用 Mode 区分: // - Inversion → measurement「反演运算」(原版 InversionForm.vue + postInversionTask) // - ApparentResistivity → measurement「生成视电阻率」(原版 InversionDialog.vue + createVisualResistivityData) // 共同流程:① 拉模型列表 → ② 选模型 → ③ 按 typeId 拉动态表单 → ④ 分组卡片渲染字段 → ⑤ 提交。 +// 动态字段渲染复用项目内 DynamicFormEditor(与对象/结构编辑同一套控件),按 displayComponentType +// 渲染 11 种控件并做 requiredType===1 必填校验、requiredType===2 只读禁用(对照原版 FormItem.vue)。 // 差异(严格对照原版): -// Inversion:模型下拉可选(allow-clear)、无默认选中、无字段默认值;提交体 {dsId,scriptId,properties}。 -// ApparentResistivity:模型下拉禁用、默认选中 code=='script_visual_resistivity_data'、 -// 字段默认取首个选项;提交体 {dsObjectId,scriptId,scriptParamListJsonStr}。 +// Inversion:模型下拉可选(allow-clear)、无默认选中;提交体 {dsId,scriptId,properties}。 +// ApparentResistivity:模型下拉禁用、默认选中 code=='script_visual_resistivity_data'; +// 提交体 {dsObjectId,scriptId,scriptParamListJsonStr}。 // 回调用 QPointer 守卫(对话框 modal exec,但异步回调仍可能在关闭后到达)。 class InversionFormDialog : public QDialog { Q_OBJECT @@ -42,7 +41,6 @@ private: void loadScripts(); // 拉模型列表填下拉 void onModelChanged(); // 模型变更 → 拉动态表单 void loadDynamicForm(const QString& typeId); - void rebuildFormArea(); // 按 groups_ 重建分组卡片 void onConfirm(); // 提交(按 mode 走不同端点) Mode mode_; @@ -51,13 +49,8 @@ private: QString projectId_; QComboBox* modelCombo_ = nullptr; - QWidget* formHost_ = nullptr; // 动态字段容器(重建时清空重填) - QVBoxLayout* formHostLay_ = nullptr; + DynamicFormEditor* editor_ = nullptr; // 动态字段渲染/收集/必填校验(项目内复用) QPushButton* okBtn_ = nullptr; - - std::vector groups_; // 当前模型的动态表单(已解析) - std::vector fieldCombos_; // 与 groups_ 展平后的字段同序(取值用) - std::vector fieldCodes_; // 与 fieldCombos_ 同序的 fieldCode }; } // namespace geopro::app diff --git a/src/app/panels/chart/InversionFormParse.cpp b/src/app/panels/chart/InversionFormParse.cpp deleted file mode 100644 index 1e7410f..0000000 --- a/src/app/panels/chart/InversionFormParse.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "panels/chart/InversionFormParse.hpp" - -#include - -#include -#include - -namespace geopro::app { - -std::vector parseDynamicForm(const QJsonObject& data) { - std::vector groups; - const QJsonArray formList = data.value(QStringLiteral("formList")).toArray(); - for (const QJsonValue& gv : formList) { - const QJsonObject g = gv.toObject(); - InversionGroup group; - group.groupName = g.value(QStringLiteral("groupName")).toString(); - const QJsonArray values = g.value(QStringLiteral("values")).toArray(); - for (const QJsonValue& fv : values) { - const QJsonObject f = fv.toObject(); - InversionField field; - field.fieldCode = f.value(QStringLiteral("fieldCode")).toString(); - field.fieldName = f.value(QStringLiteral("fieldName")).toString(); - const QJsonArray opts = f.value(QStringLiteral("optionsObject")).toArray(); - for (const QJsonValue& ov : opts) { - const QJsonObject o = ov.toObject(); - field.options.push_back({o.value(QStringLiteral("label")).toString(), - o.value(QStringLiteral("value")).toString()}); - } - group.fields.push_back(std::move(field)); - } - groups.push_back(std::move(group)); - } - return groups; -} - -QJsonObject assembleFieldMap(const std::vector& groups, const QJsonObject& selected, - bool fillDefaults) { - QJsonObject out; - for (const auto& g : groups) { - for (const auto& f : g.fields) { - QString value; - if (selected.contains(f.fieldCode)) value = selected.value(f.fieldCode).toString(); - if (value.isEmpty() && fillDefaults && !f.options.empty()) { - value = f.options.front().value; // 复刻 initDynamicFieldsDefaultValues - } - if (!value.isEmpty()) out.insert(f.fieldCode, value); // 复刻 handleConfirm:空值不进体 - } - } - return out; -} - -} // namespace geopro::app diff --git a/src/app/panels/chart/InversionFormParse.hpp b/src/app/panels/chart/InversionFormParse.hpp deleted file mode 100644 index c98c749..0000000 --- a/src/app/panels/chart/InversionFormParse.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once -#include - -#include -#include - -namespace geopro::app { - -// 反演动态表单的数据模型 + 纯解析/组装函数(仅依赖 QtCore JSON,无 Widgets/MOC)。 -// 拆出独立 TU 以便单测(tests 链 geopro_data/Qt6::Core 即可,不必拖入对话框)。 - -// 一个动态表单字段(仅取渲染/提交所需,复刻原版 optionsObject + fieldCode/fieldName)。 -struct InversionFieldOption { - QString label; - QString value; -}; -struct InversionField { - QString fieldCode; - QString fieldName; - std::vector options; // optionsObject(Select 选项) -}; -struct InversionGroup { - QString groupName; - std::vector fields; -}; - -// 解析动态表单响应 data.formList → 分组/字段模型。 -std::vector parseDynamicForm(const QJsonObject& data); - -// 组装提交字段表 {fieldCode: value}。fillDefaults=true 时空选值回退首个选项(生成视电阻率用)。 -// 复刻原版 handleConfirm:仅写入有值的字段(空值不进体)。 -QJsonObject assembleFieldMap(const std::vector& groups, const QJsonObject& selected, - bool fillDefaults); - -} // namespace geopro::app diff --git a/src/app/panels/chart/RawDataChartView.cpp b/src/app/panels/chart/RawDataChartView.cpp index c1a56f1..76fade6 100644 --- a/src/app/panels/chart/RawDataChartView.cpp +++ b/src/app/panels/chart/RawDataChartView.cpp @@ -405,6 +405,7 @@ void RawDataChartView::openInversionColorScale(QWidget* anchor) { if (!cmdRepo_ || dsId.isEmpty()) return; QJsonObject body{ {QStringLiteral("dsObjectId"), dsId}, + {QStringLiteral("templateId"), data_.templateId}, // 读取到的色阶模板 id(对照原版,可空) {QStringLiteral("businessCode"), QString()}, {QStringLiteral("projectId"), projectId}, {QStringLiteral("properties"), buildColorScaleProperties(data_.scale, dlg.lineConfig())}, @@ -584,6 +585,7 @@ void RawDataChartView::openScatterColorScale(QWidget* anchor) { if (!cmdRepo_ || dsId.isEmpty()) return; // 无仓储 → 仅本地生效(不阻塞) QJsonObject body{ {QStringLiteral("dsObjectId"), dsId}, + {QStringLiteral("templateId"), data_.templateId}, // 读取到的色阶模板 id(对照原版,可空) {QStringLiteral("businessCode"), currentVFieldCode()}, {QStringLiteral("projectId"), projectId}, {QStringLiteral("properties"), buildColorScaleProperties(data_.scale, dlg.lineConfig())}, diff --git a/src/core/model/detail/DetailPayloads.hpp b/src/core/model/detail/DetailPayloads.hpp index b096ad5..d8a8135 100644 --- a/src/core/model/detail/DetailPayloads.hpp +++ b/src/core/model/detail/DetailPayloads.hpp @@ -38,6 +38,9 @@ struct ScatterPayload { ScatterToolbarConf toolbar; std::vector altXHorizontal, altXSlope; // x 下拉:平距 / 斜距 std::vector altYPseudo, altYElevationPseudo; // y 下拉:伪深度 / 伪深度+高程 + // 色阶模板 id(来自 lvl/colorGradation/getDetail 的 templateId):保存色阶时回带 + // (对照原版 newLvlColorLevel 带读取到的 templateId;可空)。 + QString templateId; }; // 等值面载荷:grid(rows) + 色阶 + 异常(≈ data::GridParts)。 diff --git a/src/data/api/ApiDatasetRepository.cpp b/src/data/api/ApiDatasetRepository.cpp index ee4472d..2a11a88 100644 --- a/src/data/api/ApiDatasetRepository.cpp +++ b/src/data/api/ApiDatasetRepository.cpp @@ -27,6 +27,7 @@ QString enc(const std::string& s) { struct ChartParts { geopro::core::ScatterField scatter; geopro::core::ColorScale scatterScale; + QString templateId; // 散点色阶模板 id(保存色阶回带,对照原版 lvlTemplateId) }; // 网格数据加载结果:grid(rows) + 网格色阶(type2) + 异常。 struct GridParts { @@ -58,6 +59,7 @@ ChartParts parseScatterParts(const QList& r) { ChartParts p; p.scatter = dto::parseScatterGraph(r[0].data); p.scatterScale = dto::parseColorBar(r[1].data); + p.templateId = r[1].data.value(QStringLiteral("templateId")).toVariant().toString(); return p; } @@ -168,7 +170,9 @@ DetailLoad* ApiDatasetRepository::makeInversionScatter(const std::string& dsId) // 复用同一批次 + 解析器,再映射为 ScatterPayload(不复制 JSON 解析逻辑)。 return new ApiDetailLoad(inversionScatterBatch(api_, dsId), [](const QList& r) { ChartParts p = parseScatterParts(r); - return QVariant::fromValue(core::ScatterPayload{p.scatter, p.scatterScale}); + core::ScatterPayload payload{p.scatter, p.scatterScale}; + payload.templateId = p.templateId; // 色阶保存回带(对照原版 lvlTemplateId) + return QVariant::fromValue(payload); }); } diff --git a/src/data/dto/MeasurementDto.cpp b/src/data/dto/MeasurementDto.cpp index ef80beb..0173dc7 100644 --- a/src/data/dto/MeasurementDto.cpp +++ b/src/data/dto/MeasurementDto.cpp @@ -130,6 +130,8 @@ ScatterPayload parseMeasurementScatter(const QJsonObject& scatterData, const QJs } p.scale = parseColorBar(colorBarData); // 复用既有混合格式解析器(AlphaScale::Unit) + // 色阶模板 id(保存色阶时回带,对照原版 lvlTemplateId = lvlConfig?.templateId)。 + p.templateId = objStr(colorBarData, QStringLiteral("templateId")); return p; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 86a0fde..bd2f07e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -154,11 +154,6 @@ target_sources(geopro_tests PRIVATE app/test_dataset_dimension.cpp ${CMAKE_SOURCE_DIR}/src/app/DatasetDimension.cpp ) -# 反演动态表单解析/组装纯函数(parseDynamicForm/assembleFieldMap,仅 Qt6::Core JSON)。 -target_sources(geopro_tests PRIVATE - app/test_inversion_form_parse.cpp - ${CMAKE_SOURCE_DIR}/src/app/panels/chart/InversionFormParse.cpp -) # measurement 散点纯逻辑(值类型变换 / 显隐 id 收集 / 过滤体 / 另存体,Qt6::Core JSON + core model)。 target_sources(geopro_tests PRIVATE app/test_scatter_data_ops.cpp diff --git a/tests/app/test_inversion_form_parse.cpp b/tests/app/test_inversion_form_parse.cpp deleted file mode 100644 index 035e133..0000000 --- a/tests/app/test_inversion_form_parse.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include - -#include -#include - -#include "panels/chart/InversionFormParse.hpp" - -using namespace geopro::app; - -namespace { - -// 取自原版动态表单响应 data 结构(POST /business/project/getDynamicForm): -// formList → [{groupName, values:[{fieldCode, fieldName, optionsObject:[{label,value}]}]}]。 -const char* kFormData = R"({ - "formList": [ - { - "groupName": "基础参数", - "values": [ - { "fieldCode": "elevation", "fieldName": "是否含高程", - "optionsObject": [ {"label": "是", "value": "1"}, {"label": "否", "value": "0"} ] }, - { "fieldCode": "method", "fieldName": "反演方法", - "optionsObject": [ {"label": "最小二乘", "value": "ls"} ] } - ] - }, - { - "groupName": "高级参数", - "values": [ - { "fieldCode": "noOptions", "fieldName": "无选项字段", "optionsObject": [] } - ] - } - ] -})"; - -QJsonObject formData() { return QJsonDocument::fromJson(kFormData).object(); } - -} // namespace - -TEST(InversionFormParse, ParsesGroupsFieldsAndOptions) { - const auto groups = parseDynamicForm(formData()); - - ASSERT_EQ(groups.size(), 2u); - EXPECT_EQ(groups[0].groupName.toStdString(), std::string("基础参数")); - ASSERT_EQ(groups[0].fields.size(), 2u); - - const auto& f0 = groups[0].fields[0]; - EXPECT_EQ(f0.fieldCode.toStdString(), std::string("elevation")); - EXPECT_EQ(f0.fieldName.toStdString(), std::string("是否含高程")); - ASSERT_EQ(f0.options.size(), 2u); - EXPECT_EQ(f0.options[0].label.toStdString(), std::string("是")); - EXPECT_EQ(f0.options[0].value.toStdString(), std::string("1")); - - EXPECT_TRUE(groups[1].fields[0].options.empty()); -} - -TEST(InversionFormParse, EmptyDataYieldsNoGroups) { - EXPECT_TRUE(parseDynamicForm(QJsonObject{}).empty()); -} - -TEST(InversionFormParse, AssembleUsesSelectedValues) { - const auto groups = parseDynamicForm(formData()); - QJsonObject selected{{"elevation", "0"}, {"method", "ls"}}; - - // fillDefaults=false(反演运算):仅含已选且非空的字段;无选值字段不进体。 - const QJsonObject out = assembleFieldMap(groups, selected, /*fillDefaults*/ false); - EXPECT_EQ(out.value("elevation").toString().toStdString(), std::string("0")); - EXPECT_EQ(out.value("method").toString().toStdString(), std::string("ls")); - EXPECT_FALSE(out.contains("noOptions")); // 无选项 + 未选 → 不进体 -} - -TEST(InversionFormParse, AssembleFillsDefaultsWhenRequested) { - const auto groups = parseDynamicForm(formData()); - - // fillDefaults=true(生成视电阻率):空选值回退首个选项。 - const QJsonObject out = assembleFieldMap(groups, QJsonObject{}, /*fillDefaults*/ true); - EXPECT_EQ(out.value("elevation").toString().toStdString(), std::string("1")); // 首项 value - EXPECT_EQ(out.value("method").toString().toStdString(), std::string("ls")); - EXPECT_FALSE(out.contains("noOptions")); // 无选项 → 即便 fillDefaults 也无值可填 -} - -TEST(InversionFormParse, AssembleOmitsEmptySelectedValues) { - const auto groups = parseDynamicForm(formData()); - QJsonObject selected{{"elevation", ""}}; // 显式空字符串 - - // fillDefaults=false:空值不进体(复刻原版 handleConfirm 的 if(selectedValue))。 - const QJsonObject out = assembleFieldMap(groups, selected, /*fillDefaults*/ false); - EXPECT_FALSE(out.contains("elevation")); -} diff --git a/tests/data/test_measurement_dto.cpp b/tests/data/test_measurement_dto.cpp index 318dd03..96b63da 100644 --- a/tests/data/test_measurement_dto.cpp +++ b/tests/data/test_measurement_dto.cpp @@ -218,6 +218,18 @@ TEST(MeasurementDto, ParsesScatterColorBarOpaque) { EXPECT_EQ(stops.back().second.a, 255); // 回归:alpha=1 须映射为 255 不透明 } +TEST(MeasurementDto, CapturesColorBarTemplateId) { + // 色阶 getDetail 顶层 templateId → ScatterPayload.templateId(保存色阶回带,对照原版)。 + auto withTpl = parseMeasurementScatter( + obj(kScatterData), + obj(R"json({"templateId":"tpl-123","properties":{"colorBar":[["0.00","#00008B"]]}})json")); + EXPECT_EQ(withTpl.templateId.toStdString(), "tpl-123"); + + // 缺省 templateId → 空串(原版亦可空)。 + auto noTpl = parseMeasurementScatter(obj(kScatterData), obj(kColorBarData)); + EXPECT_TRUE(noTpl.templateId.isEmpty()); +} + TEST(MeasurementDto, ParsesTableColumnsAndVmapFlattened) { auto t = parseMeasurementTable(obj(kRowsData));