feat/vtk-3d-view #7
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -10,10 +10,13 @@
|
|||
#include <QPainter>
|
||||
#include <QPointer>
|
||||
#include <QPushButton>
|
||||
#include <QRadioButton>
|
||||
#include <QTableView>
|
||||
#include <QToolTip>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#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<geopro::core::TableFunctionButton>& 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<QPushButton*>(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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,22 +3,21 @@
|
|||
#include <utility>
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QFrame>
|
||||
#include <QGridLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QMessageBox>
|
||||
#include <QPointer>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QSignalBlocker>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#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<int>(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<InversionFormDialog> self(this);
|
||||
|
|
|
|||
|
|
@ -1,14 +1,9 @@
|
|||
#pragma once
|
||||
#include <vector>
|
||||
|
||||
#include <QDialog>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
|
||||
#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<InversionGroup> groups_; // 当前模型的动态表单(已解析)
|
||||
std::vector<QComboBox*> fieldCombos_; // 与 groups_ 展平后的字段同序(取值用)
|
||||
std::vector<QString> fieldCodes_; // 与 fieldCombos_ 同序的 fieldCode
|
||||
};
|
||||
|
||||
} // namespace geopro::app
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
#include "panels/chart/InversionFormParse.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
|
||||
namespace geopro::app {
|
||||
|
||||
std::vector<InversionGroup> parseDynamicForm(const QJsonObject& data) {
|
||||
std::vector<InversionGroup> 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<InversionGroup>& 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
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
#pragma once
|
||||
#include <vector>
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
|
||||
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<InversionFieldOption> options; // optionsObject(Select 选项)
|
||||
};
|
||||
struct InversionGroup {
|
||||
QString groupName;
|
||||
std::vector<InversionField> fields;
|
||||
};
|
||||
|
||||
// 解析动态表单响应 data.formList → 分组/字段模型。
|
||||
std::vector<InversionGroup> parseDynamicForm(const QJsonObject& data);
|
||||
|
||||
// 组装提交字段表 {fieldCode: value}。fillDefaults=true 时空选值回退首个选项(生成视电阻率用)。
|
||||
// 复刻原版 handleConfirm:仅写入有值的字段(空值不进体)。
|
||||
QJsonObject assembleFieldMap(const std::vector<InversionGroup>& groups, const QJsonObject& selected,
|
||||
bool fillDefaults);
|
||||
|
||||
} // namespace geopro::app
|
||||
|
|
@ -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())},
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ struct ScatterPayload {
|
|||
ScatterToolbarConf toolbar;
|
||||
std::vector<double> altXHorizontal, altXSlope; // x 下拉:平距 / 斜距
|
||||
std::vector<double> altYPseudo, altYElevationPseudo; // y 下拉:伪深度 / 伪深度+高程
|
||||
// 色阶模板 id(来自 lvl/colorGradation/getDetail 的 templateId):保存色阶时回带
|
||||
// (对照原版 newLvlColorLevel 带读取到的 templateId;可空)。
|
||||
QString templateId;
|
||||
};
|
||||
|
||||
// 等值面载荷:grid(rows) + 色阶 + 异常(≈ data::GridParts)。
|
||||
|
|
|
|||
|
|
@ -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<net::ApiResponse>& 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<net::ApiResponse>& 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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,87 +0,0 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
#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"));
|
||||
}
|
||||
|
|
@ -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));
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue