feat(detail): 色阶编辑器另存覆盖 + 散点模板库可用(1:1)

- IColorTemplateRepository/Api 加 updateLvlTemplate(PUT /business/lvlTemplate
  {id,templateName,properties}),对照原版 updateLvlTemplate
- ColorScaleConfigDialog 另存为改自定义弹窗:名称+「覆盖原模板」复选(仅 lvlTemplateId
  非空可勾)→勾选 updateLvlTemplate / 否则 saveLvlTemplate;ctor 增 lvlTemplateId(默认空)
- 散点路径接通模板库:工厂给 Scatter 视图注入 colorTplRepo,构造色阶编辑器传
  colorTplRepo+projectId+data_.templateId→另存/打开/覆盖可用(原 nullptr 禁用)
- 3D 体色阶编辑器(main.cpp)及网格(GridDataChartView)用默认空 templateId,行为不变

build all 绿,336/336。
This commit is contained in:
gaozheng 2026-06-23 14:08:35 +08:00
parent 0212fb5d2e
commit 3dfe8b54f5
10 changed files with 102 additions and 25 deletions

View File

@ -97,14 +97,16 @@ ColorScaleConfigDialog::ColorScaleConfigDialog(const geopro::core::ColorScale& i
double vmax, std::vector<double> samples,
const ContourLineConfig& lineInit,
geopro::data::IColorTemplateRepository* tplRepo,
QString projectId, QWidget* parent)
QString projectId, QString lvlTemplateId,
QWidget* parent)
: QDialog(parent),
vmin_(vmin),
vmax_(vmax),
samples_(std::move(samples)),
lineCfg_(lineInit),
tplRepo_(tplRepo),
projectId_(std::move(projectId)) {
projectId_(std::move(projectId)),
lvlTemplateId_(std::move(lvlTemplateId)) {
setWindowTitle(QStringLiteral("色阶配置"));
setModal(true);
resize(560, 420);
@ -433,11 +435,31 @@ void ColorScaleConfigDialog::loadColorBar(
void ColorScaleConfigDialog::onSaveOther() {
if (tplRepo_ == nullptr || projectId_.isEmpty()) return;
bool ok = false;
const QString name = QInputDialog::getText(this, QStringLiteral("另存模板配置"),
QStringLiteral("模板名称:"), QLineEdit::Normal,
QStringLiteral("等值线配置.lvl"), &ok);
if (!ok || name.trimmed().isEmpty()) return;
// 自定义另存为弹窗(复刻 handleSaveOther名称输入 + 覆盖复选框。
// 「覆盖」仅当有来源模板 idlvlTemplateId_ 非空)时可勾选,对照原版 props.data.lvlTemplateId。
QDialog askDlg(this);
askDlg.setWindowTitle(QStringLiteral("另存模板配置"));
askDlg.setModal(true);
auto* askRoot = new QVBoxLayout(&askDlg);
auto* nameRow = new QHBoxLayout();
nameRow->addWidget(new QLabel(QStringLiteral("模板名称:"), &askDlg));
auto* nameEdit = new QLineEdit(QStringLiteral("等值线配置.lvl"), &askDlg);
nameRow->addWidget(nameEdit, 1);
askRoot->addLayout(nameRow);
auto* overwriteCheck = new QCheckBox(QStringLiteral("覆盖原模板"), &askDlg);
overwriteCheck->setEnabled(!lvlTemplateId_.isEmpty()); // 无来源模板 → 禁用覆盖
askRoot->addWidget(overwriteCheck);
auto* askBtns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &askDlg);
askBtns->button(QDialogButtonBox::Ok)->setText(QStringLiteral("应用"));
askBtns->button(QDialogButtonBox::Cancel)->setText(QStringLiteral("取消"));
connect(askBtns, &QDialogButtonBox::accepted, &askDlg, &QDialog::accept);
connect(askBtns, &QDialogButtonBox::rejected, &askDlg, &QDialog::reject);
askRoot->addWidget(askBtns);
if (askDlg.exec() != QDialog::Accepted) return;
const QString name = nameEdit->text().trimmed();
if (name.isEmpty()) return;
const bool overwrite = overwriteCheck->isChecked() && !lvlTemplateId_.isEmpty();
// 组装 properties复刻 handleSaveOther
QJsonArray colorBar;
@ -457,18 +479,22 @@ void ColorScaleConfigDialog::onSaveOther() {
{QStringLiteral("colorBar"), colorBar}};
// 走仓储传输;回调里用 QPointer 守卫 this模态对话框可能已关
// 勾选覆盖 → PUT 更新来源模板updateLvlTemplate否则 → POST 新建saveLvlTemplate
QPointer<ColorScaleConfigDialog> self(this);
tplRepo_->saveLvlTemplate(projectId_, name.trimmed(), properties,
[self](bool ok, QString msg) {
auto onDone = [self, overwrite](bool ok, QString msg) {
if (!self) return;
if (ok)
QMessageBox::information(self, QStringLiteral("另存"),
QStringLiteral("另存成功。"));
else
QMessageBox::warning(
QMessageBox::information(
self, QStringLiteral("另存"),
overwrite ? QStringLiteral("更新成功。") : QStringLiteral("另存成功。"));
else
QMessageBox::warning(self, QStringLiteral("另存"),
QStringLiteral("另存失败:%1").arg(msg));
});
};
if (overwrite)
tplRepo_->updateLvlTemplate(lvlTemplateId_, name, properties, std::move(onDone));
else
tplRepo_->saveLvlTemplate(projectId_, name, properties, std::move(onDone));
}
void ColorScaleConfigDialog::onOpen() {

View File

@ -29,12 +29,15 @@ public:
// init当前色阶升序断点填表vmin/vmax数据原始范围层级/颜色子对话框 + 新增外推用);
// samples数据原始标量等积分层 + 颜色编辑器直方图用,空则等积退化为线性);
// lineInit线形/标注初值2D 传当前态3D 用默认);
// tplRepo/projectIdlvl 模板库仓储句柄(可空 → 另存为/打开 禁用)。
// tplRepo/projectIdlvl 模板库仓储句柄(可空 → 另存为/打开 禁用);
// lvlTemplateId当前色阶来源模板 id可空对照原版 props.data.lvlTemplateId
// 非空时「另存为」弹窗的「覆盖」复选框可勾选 → 走 PUT 更新该模板3D/无模板场景不传即可。
ColorScaleConfigDialog(const geopro::core::ColorScale& init, double vmin, double vmax,
std::vector<double> samples = {},
const ContourLineConfig& lineInit = {},
geopro::data::IColorTemplateRepository* tplRepo = nullptr,
QString projectId = {}, QWidget* parent = nullptr);
QString projectId = {}, QString lvlTemplateId = {},
QWidget* parent = nullptr);
// 由表格当前断点装配的新色阶(按层级升序 addStop
geopro::core::ColorScale colorScale() const;
@ -74,6 +77,7 @@ private:
geopro::data::IColorTemplateRepository* tplRepo_ = nullptr; // lvl 模板库仓储(可空)
QString projectId_;
QString lvlTemplateId_; // 当前色阶来源模板 id可空 → 另存为弹窗禁用「覆盖」)
// 随子对话框更新、写入另存为 properties复刻原版透传字段
QString lvlSchemeType_ = QStringLiteral("normal");
int logLinesCount_ = 8;

View File

@ -807,10 +807,11 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
}
// 3D 无等值线,线形/标注配置忽略(用默认);仅取色阶应用。
// 「另存为/打开」与「新建色阶/配色方案」走色阶模板仓储projectId 取当前项目。
// 3D 体无来源 lvl 模板 → lvlTemplateId 传空(覆盖复选框禁用,行为不变)。
geopro::app::ColorScaleConfigDialog dlg(
sceneView->currentColorScale(), sceneView->currentVmin(),
sceneView->currentVmax(), std::move(samples), {}, &colorTplRepo,
nav.currentProjectId(), &window);
nav.currentProjectId(), QString(), &window);
if (dlg.exec() == QDialog::Accepted)
sceneCtrl->setVolumeColorScale(dsId, dlg.colorScale());
});

View File

@ -23,6 +23,8 @@ std::unique_ptr<IDetailView> makeDetailView(controller::ViewKind kind, QWidget*
auto* raw = new RawDataChartView(parent);
// 注入反演命令仓储 + dsId/projectId 取值回调measurement 反演运算/生成视电阻率)。
raw->setCommandRepo(cmdRepo, dsIdGetter, projectIdGetter);
// 注入色阶模板仓储(散点「色阶配置」编辑器另存为/打开/覆盖用projectId 复用上面的 getter
raw->setColorTemplateRepo(colorTplRepo);
return std::unique_ptr<IDetailView>(raw);
}
case controller::ViewKind::FilledContour: {

View File

@ -318,8 +318,9 @@ void GridDataChartView::openColorScaleEditor() {
// projectId 在打开时取一次(随项目切换生效);无 getter 退化为空 → 后端按钮禁用。
const QString projectId = projectIdGetter_ ? projectIdGetter_() : QString();
// 网格剖面载荷ContourPayload无 templateId 字段 → lvlTemplateId 传空(覆盖复选框禁用)。
ColorScaleConfigDialog dlg(gridScale_, grid_.vmin, grid_.vmax, std::move(samples), lineCfg_,
tplRepo_, projectId, this);
tplRepo_, projectId, QString(), this);
if (dlg.exec() != QDialog::Accepted) return;
gridScale_ = dlg.colorScale();

View File

@ -352,6 +352,10 @@ void RawDataChartView::setCommandRepo(geopro::data::IDatasetCommandRepository* r
projectIdGetter_ = std::move(projectIdGetter);
}
void RawDataChartView::setColorTemplateRepo(geopro::data::IColorTemplateRepository* repo) {
colorTplRepo_ = repo;
}
void RawDataChartView::openInversionDialog(bool apparentResistivity, QWidget* anchor) {
// 无仓储/无 dsId 取值回调 → 退化占位(与未注入时一致)。
const QString dsId = dsIdGetter_ ? dsIdGetter_() : QString();
@ -388,7 +392,10 @@ void RawDataChartView::openInversionColorScale(QWidget* anchor) {
}
if (vMin > vMax) { vMin = 0.0; vMax = 1.0; }
std::vector<double> samples = data_.scatter.v;
ColorScaleConfigDialog dlg(data_.scale, vMin, vMax, std::move(samples), {}, nullptr, {}, this);
// 接通色阶模板库:注入仓储 + 当前 projectId + 载荷 templateId另存为/打开/覆盖 可用)。
ColorScaleConfigDialog dlg(data_.scale, vMin, vMax, std::move(samples), {}, colorTplRepo_,
projectIdGetter_ ? projectIdGetter_() : QString(), data_.templateId,
this);
if (dlg.exec() != QDialog::Accepted) return;
// 本地重建上色重绘。
@ -565,8 +572,10 @@ void RawDataChartView::openScatterColorScale(QWidget* anchor) {
if (vMin > vMax) { vMin = 0.0; vMax = 1.0; }
std::vector<double> samples = data_.scatter.v; // 直方图/等积分层用原始标量
// 散点无独立 lvl 模板仓储(视图只持命令仓储)→ tplRepo 传空(另存为/打开 禁用)。
ColorScaleConfigDialog dlg(data_.scale, vMin, vMax, std::move(samples), {}, nullptr, {}, this);
// 接通色阶模板库:注入仓储 + 当前 projectId + 载荷 templateId另存为/打开/覆盖 可用)。
ColorScaleConfigDialog dlg(data_.scale, vMin, vMax, std::move(samples), {}, colorTplRepo_,
projectIdGetter_ ? projectIdGetter_() : QString(), data_.templateId,
this);
if (dlg.exec() != QDialog::Accepted) return;
// 本地重建 colorSvc_ 重绘散点M8 即时生效)。

View File

@ -15,6 +15,7 @@ class QwtPlotRescaler;
namespace geopro::data {
class IDatasetCommandRepository;
class IColorTemplateRepository;
}
namespace geopro::app {
@ -47,6 +48,10 @@ public:
std::function<QString()> dsIdGetter,
std::function<QString()> projectIdGetter);
// 注入色阶模板仓储(散点「色阶配置」编辑器「另存为/打开/覆盖」用projectId 复用
// setCommandRepo 注入的 projectIdGetter_。可传空 → 编辑器后端按钮禁用。
void setColorTemplateRepo(geopro::data::IColorTemplateRepository* repo);
protected:
// 信息模式M13下捕获画布点击找最近散点显示属性。其余事件不消费。
bool eventFilter(QObject* obj, QEvent* ev) override;
@ -123,6 +128,8 @@ private:
geopro::data::IDatasetCommandRepository* cmdRepo_ = nullptr;
std::function<QString()> dsIdGetter_;
std::function<QString()> projectIdGetter_;
// 色阶模板仓储(注入;空则编辑器「另存为/打开」禁用)。
geopro::data::IColorTemplateRepository* colorTplRepo_ = nullptr;
};
} // namespace geopro::app

View File

@ -35,6 +35,24 @@ void ApiColorTemplateRepository::saveLvlTemplate(const QString& projectId,
});
}
void ApiColorTemplateRepository::updateLvlTemplate(const QString& id,
const QString& templateName,
const QJsonObject& properties,
std::function<void(bool, QString)> cb) {
QJsonObject body{{QStringLiteral("id"), id},
{QStringLiteral("templateName"), templateName},
{QStringLiteral("properties"), properties}};
auto* call = api_.putJsonAsync(QStringLiteral("/business/lvlTemplate"), body);
if (call == nullptr) {
if (cb) cb(false, QStringLiteral("请求创建失败"));
return;
}
QObject::connect(call, &net::IApiCall::finished, call,
[cb = std::move(cb)](const net::ApiResponse& resp) {
if (cb) cb(isOk(resp), resp.msg);
});
}
void ApiColorTemplateRepository::listLvlTemplates(
const QString& projectId, std::function<void(bool, QJsonArray, QString)> cb) {
QJsonObject body{{QStringLiteral("projectId"), projectId},

View File

@ -15,6 +15,9 @@ public:
void saveLvlTemplate(const QString& projectId, const QString& templateName,
const QJsonObject& properties,
std::function<void(bool ok, QString msg)> cb) override;
void updateLvlTemplate(const QString& id, const QString& templateName,
const QJsonObject& properties,
std::function<void(bool ok, QString msg)> cb) override;
void listLvlTemplates(const QString& projectId,
std::function<void(bool ok, QJsonArray list, QString msg)> cb) override;
void newClrScheme(const QString& projectId, const QJsonObject& properties,

View File

@ -19,6 +19,12 @@ public:
const QJsonObject& properties,
std::function<void(bool ok, QString msg)> cb) = 0;
// 覆盖更新 lvl 模板PUT /business/lvlTemplatebody {id, templateName, properties})。
// 复刻原版 updateLvlTemplate另存为弹窗勾选「覆盖」时调用id=当前模板 lvlTemplateId
virtual void updateLvlTemplate(const QString& id, const QString& templateName,
const QJsonObject& properties,
std::function<void(bool ok, QString msg)> cb) = 0;
// 列 lvl 模板POST /business/lvlTemplate/pagepageNo=1,pageSize=1000
// 回调 list = 响应 data.list 数组(每项含 templateName/properties
virtual void listLvlTemplates(const QString& projectId,