feat/vtk-3d-view #7

Merged
gaozheng merged 301 commits from feat/vtk-3d-view into main 2026-06-27 18:43:52 +08:00
17 changed files with 290 additions and 334 deletions
Showing only changes of commit 08b8ebbf01 - Show all commits

View File

@ -1,13 +1,14 @@
#include "AnomalySaveDialog.hpp"
#include <QComboBox>
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPixmap>
#include <QPlainTextEdit>
#include <QVBoxLayout>
#include "FormKit.hpp"
#include "Theme.hpp"
namespace geopro::app {
@ -28,37 +29,42 @@ AnomalySaveDialog::AnomalySaveDialog(const QString& screenshotPath, int shotW, i
setWindowTitle(QStringLiteral("保存异常"));
setModal(true);
auto* root = new QVBoxLayout(this);
auto* root = formkit::dialogRoot(this);
auto* form = new QFormLayout();
auto* card = formkit::formCard(this);
auto* cardLay = formkit::cardBody(card);
auto* form = formkit::makeEditForm();
name_ = new QLineEdit(QStringLiteral("异常"));
form->addRow(QStringLiteral("名称"), name_);
formkit::capField(name_);
form->addRow(formkit::editLabel(QStringLiteral("名称")), name_);
type_ = new QComboBox();
for (const auto& t : kMockTypes)
type_->addItem(QString::fromUtf8(t.label), QString::fromUtf8(t.id));
form->addRow(QStringLiteral("异常类型"), type_);
formkit::capField(type_);
form->addRow(formkit::editLabel(QStringLiteral("异常类型")), type_);
remark_ = new QPlainTextEdit();
remark_->setFixedHeight(60);
form->addRow(QStringLiteral("备注"), remark_);
root->addLayout(form);
remark_->setFixedHeight(geopro::app::scaledPx(60));
formkit::capField(remark_);
form->addRow(formkit::editLabel(QStringLiteral("备注")), remark_);
cardLay->addLayout(form);
// 截图预览 + 大小R50「确定截图大小」
if (!screenshotPath.isEmpty()) {
root->addWidget(new QLabel(QStringLiteral("截图(%1 × %2").arg(shotW).arg(shotH)));
cardLay->addWidget(new QLabel(QStringLiteral("截图(%1 × %2").arg(shotW).arg(shotH)));
QPixmap pm(screenshotPath);
if (!pm.isNull()) {
auto* img = new QLabel();
img->setPixmap(pm.scaledToWidth(320, Qt::SmoothTransformation));
root->addWidget(img);
img->setPixmap(pm.scaledToWidth(geopro::app::scaledPx(320), Qt::SmoothTransformation));
cardLay->addWidget(img);
}
}
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
root->addWidget(buttons);
root->addWidget(card);
formkit::addDialogButtons(root, this);
}
QString AnomalySaveDialog::anomalyName() const {

View File

@ -28,6 +28,8 @@
#include <QVBoxLayout>
#include "ColorScaleIO.hpp"
#include "FormKit.hpp"
#include "Theme.hpp"
#include "repo/IColorTemplateRepository.hpp"
namespace geopro::app {
@ -78,12 +80,6 @@ QPixmap previewPixmap(const std::vector<Stop>& stops, int w = 100, int h = 16) {
return pm;
}
QLabel* rightLabel(const QString& text) {
auto* l = new QLabel(text);
l->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
l->setMinimumWidth(72);
return l;
}
} // namespace
ColorGradientDialog::ColorGradientDialog(const std::vector<Stop>& init, double minValue,
@ -104,8 +100,8 @@ ColorGradientDialog::ColorGradientDialog(const std::vector<Stop>& init, double m
// ── 顶部两列 gridgrid-template-columns: 50% 50% ──────────────────────
auto* grid = new QGridLayout();
grid->setHorizontalSpacing(12);
grid->setVerticalSpacing(8);
grid->setHorizontalSpacing(geopro::app::space::kLg); // formkit 标准列距
grid->setVerticalSpacing(geopro::app::space::kMd); // formkit 标准行距
int rowIdx = 0;
// 配色方案(下拉带预览色条)。
@ -113,7 +109,7 @@ ColorGradientDialog::ColorGradientDialog(const std::vector<Stop>& init, double m
schemeCombo_->setIconSize(QSize(100, 16));
{
auto* cell = new QHBoxLayout();
cell->addWidget(rightLabel(QStringLiteral("配色方案:")));
cell->addWidget(formkit::editLabel(QStringLiteral("配色方案:")));
cell->addWidget(schemeCombo_, 1);
grid->addLayout(cell, rowIdx, 0);
}
@ -121,7 +117,7 @@ ColorGradientDialog::ColorGradientDialog(const std::vector<Stop>& init, double m
// 分布方式disabled, 默认线性)+ 反向。
{
auto* cell = new QHBoxLayout();
cell->addWidget(rightLabel(QStringLiteral("分布方式:")));
cell->addWidget(formkit::editLabel(QStringLiteral("分布方式:")));
auto* distCombo = new QComboBox(this);
distCombo->addItem(QStringLiteral("线性"), QStringLiteral("linear"));
distCombo->addItem(QStringLiteral("对数"), QStringLiteral("log"));
@ -142,7 +138,7 @@ ColorGradientDialog::ColorGradientDialog(const std::vector<Stop>& init, double m
.arg(QString::number(originMax_, 'f', 6)));
{
auto* cell = new QHBoxLayout();
cell->addWidget(rightLabel(QStringLiteral("数值范围:")));
cell->addWidget(formkit::editLabel(QStringLiteral("数值范围:")));
cell->addWidget(rangeEdit_, 1);
grid->addLayout(cell, rowIdx, 0);
}
@ -150,13 +146,13 @@ ColorGradientDialog::ColorGradientDialog(const std::vector<Stop>& init, double m
// 最小值/最大值(可编辑)。
{
auto* cell = new QHBoxLayout();
cell->addWidget(rightLabel(QStringLiteral("最小值:")));
cell->addWidget(formkit::editLabel(QStringLiteral("最小值:")));
minSpin_ = new QDoubleSpinBox(this);
minSpin_->setDecimals(6);
minSpin_->setRange(-1e12, 1e12);
minSpin_->setValue(minValue);
cell->addWidget(minSpin_, 1);
cell->addWidget(rightLabel(QStringLiteral("最大值:")));
cell->addWidget(formkit::editLabel(QStringLiteral("最大值:")));
maxSpin_ = new QDoubleSpinBox(this);
maxSpin_->setDecimals(6);
maxSpin_->setRange(-1e12, 1e12);
@ -169,7 +165,7 @@ ColorGradientDialog::ColorGradientDialog(const std::vector<Stop>& init, double m
curDataLabel_ = new QLabel(QStringLiteral("-"), this);
{
auto* cell = new QHBoxLayout();
cell->addWidget(rightLabel(QStringLiteral("当前数据值:")));
cell->addWidget(formkit::editLabel(QStringLiteral("当前数据值:")));
cell->addWidget(curDataLabel_, 1);
grid->addLayout(cell, rowIdx, 0);
}
@ -178,7 +174,7 @@ ColorGradientDialog::ColorGradientDialog(const std::vector<Stop>& init, double m
curPosLabel_ = new QLabel(QStringLiteral("-"), this);
{
auto* cell = new QHBoxLayout();
cell->addWidget(rightLabel(QStringLiteral("当前数据位置:")));
cell->addWidget(formkit::editLabel(QStringLiteral("当前数据位置:")));
cell->addWidget(curPosLabel_, 1);
grid->addLayout(cell, rowIdx++, 1);
}
@ -189,7 +185,7 @@ ColorGradientDialog::ColorGradientDialog(const std::vector<Stop>& init, double m
curColorBtn_->setEnabled(false);
{
auto* cell = new QHBoxLayout();
cell->addWidget(rightLabel(QStringLiteral("当前颜色:")));
cell->addWidget(formkit::editLabel(QStringLiteral("当前颜色:")));
cell->addWidget(curColorBtn_);
cell->addStretch(1);
grid->addLayout(cell, rowIdx++, 0);

View File

@ -15,6 +15,8 @@
#include <QVBoxLayout>
#include <QWidget>
#include "FormKit.hpp"
namespace geopro::app {
namespace {
@ -27,12 +29,12 @@ ContourLevelDialog::ContourLevelDialog(const ContourLevelParams& init, double or
setWindowTitle(QStringLiteral("等值线层级"));
setModal(true);
auto* root = new QVBoxLayout(this);
auto* form = new QFormLayout();
auto* root = formkit::dialogRoot(this);
auto* form = formkit::makeEditForm();
root->addLayout(form);
// 数据范围(原始,只读展示)。
form->addRow(QStringLiteral("数据范围"),
form->addRow(formkit::editLabel(QStringLiteral("数据范围")),
new QLabel(QStringLiteral("%1 ~ %2").arg(originMin_).arg(originMax_)));
// 分层方式。
@ -41,7 +43,8 @@ ContourLevelDialog::ContourLevelDialog(const ContourLevelParams& init, double or
methodCombo_->addItem(QStringLiteral("对数"), 1);
methodCombo_->addItem(QStringLiteral("等积"), 2);
methodCombo_->setCurrentIndex(static_cast<int>(init.method));
form->addRow(QStringLiteral("分层方式"), methodCombo_);
formkit::capField(methodCombo_);
form->addRow(formkit::editLabel(QStringLiteral("分层方式")), methodCombo_);
auto* validator = new QDoubleValidator(this);
validator->setNotation(QDoubleValidator::StandardNotation);
@ -52,40 +55,50 @@ ContourLevelDialog::ContourLevelDialog(const ContourLevelParams& init, double or
maxEdit_ = new QLineEdit(QString::number(init.maxValue), this);
minEdit_->setValidator(validator);
maxEdit_->setValidator(validator);
formkit::capField(minEdit_);
formkit::capField(maxEdit_);
rangeRow_ = new QWidget(this);
auto* rangeForm = new QFormLayout(rangeRow_);
auto* rangeForm = formkit::makeEditForm();
rangeForm->setContentsMargins(0, 0, 0, 0);
rangeForm->addRow(QStringLiteral("最大等值线"), maxEdit_);
rangeForm->addRow(QStringLiteral("最小等值线"), minEdit_);
rangeForm->addRow(formkit::editLabel(QStringLiteral("最大等值线")), maxEdit_);
rangeForm->addRow(formkit::editLabel(QStringLiteral("最小等值线")), minEdit_);
rangeRow_->setLayout(rangeForm);
root->addWidget(rangeRow_);
// normal间隔数 + 层数(双向联动)。
intervalEdit_ = new QLineEdit(QString::number(init.interval), this);
layerCountEdit_ = new QLineEdit(QString::number(init.layerCount), this);
intervalEdit_->setValidator(validator);
formkit::capField(intervalEdit_);
formkit::capField(layerCountEdit_);
normalRow_ = new QWidget(this);
auto* normalForm = new QFormLayout(normalRow_);
auto* normalForm = formkit::makeEditForm();
normalForm->setContentsMargins(0, 0, 0, 0);
normalForm->addRow(QStringLiteral("数值间隔"), intervalEdit_);
normalForm->addRow(QStringLiteral("层数"), layerCountEdit_);
normalForm->addRow(formkit::editLabel(QStringLiteral("数值间隔")), intervalEdit_);
normalForm->addRow(formkit::editLabel(QStringLiteral("层数")), layerCountEdit_);
normalRow_->setLayout(normalForm);
root->addWidget(normalRow_);
// logarithmic每数量级次要等值线数。
logLinesEdit_ = new QLineEdit(QString::number(init.logLinesCount), this);
formkit::capField(logLinesEdit_);
logRow_ = new QWidget(this);
auto* logForm = new QFormLayout(logRow_);
auto* logForm = formkit::makeEditForm();
logForm->setContentsMargins(0, 0, 0, 0);
logForm->addRow(QStringLiteral("每数量级次要等值线数"), logLinesEdit_);
logForm->addRow(formkit::editLabel(QStringLiteral("每数量级次要等值线数")), logLinesEdit_);
logRow_->setLayout(logForm);
root->addWidget(logRow_);
// equalArea等积分层层数 + 区间面积(只读,自动算)。
equalAreaCountEdit_ = new QLineEdit(QString::number(init.equalAreaLayerCount), this);
formkit::capField(equalAreaCountEdit_);
intervalAreaLabel_ = new QLabel(this);
equalAreaRow_ = new QWidget(this);
auto* eaForm = new QFormLayout(equalAreaRow_);
auto* eaForm = formkit::makeEditForm();
eaForm->setContentsMargins(0, 0, 0, 0);
eaForm->addRow(QStringLiteral("层数"), equalAreaCountEdit_);
eaForm->addRow(QStringLiteral("区间面积"), intervalAreaLabel_);
eaForm->addRow(formkit::editLabel(QStringLiteral("层数")), equalAreaCountEdit_);
eaForm->addRow(formkit::editLabel(QStringLiteral("区间面积")), intervalAreaLabel_);
equalAreaRow_->setLayout(eaForm);
root->addWidget(equalAreaRow_);
auto* reset = new QPushButton(QStringLiteral("恢复默认值"), this);

View File

@ -9,6 +9,8 @@
#include <QPushButton>
#include <QVBoxLayout>
#include "FormKit.hpp"
namespace geopro::app {
namespace {
@ -26,41 +28,45 @@ ContourLineDialog::ContourLineDialog(const ContourLineConfig& init, QWidget* par
setWindowTitle(QStringLiteral("等值线修改"));
setModal(true);
auto* root = new QVBoxLayout(this);
auto* form = new QFormLayout();
root->addLayout(form);
auto* root = formkit::dialogRoot(this);
auto* card = formkit::formCard(this);
auto* cardLay = formkit::cardBody(card);
auto* form = formkit::makeEditForm();
// 复刻 contourLine.vue选项顺序「虚线」在前、「实线」在后。
lineTypeCombo_ = new QComboBox(this);
lineTypeCombo_->addItem(QStringLiteral("- - - - - - - - -"), true); // dashed
lineTypeCombo_->addItem(QStringLiteral("——————"), false); // solid
lineTypeCombo_->setCurrentIndex(cfg_.dashed ? 0 : 1);
form->addRow(QStringLiteral("线形:"), lineTypeCombo_);
formkit::capField(lineTypeCombo_);
form->addRow(formkit::editLabel(QStringLiteral("线形:")), lineTypeCombo_);
lineShowChk_ = new QCheckBox(this);
lineShowChk_->setChecked(cfg_.lineShow);
form->addRow(QStringLiteral("显示线段:"), lineShowChk_);
form->addRow(formkit::editLabel(QStringLiteral("显示线段:")), lineShowChk_);
lineColorBtn_ = new QPushButton(this);
paintSwatch(lineColorBtn_, cfg_.lineColor);
paintSwatch(lineColorBtn_, cfg_.lineColor); // 专用色块控件:样式表保留
connect(lineColorBtn_, &QPushButton::clicked, this, &ContourLineDialog::pickLineColor);
form->addRow(QStringLiteral("线段颜色:"), lineColorBtn_);
form->addRow(formkit::editLabel(QStringLiteral("线段颜色:")), lineColorBtn_);
labelShowChk_ = new QCheckBox(this);
labelShowChk_->setChecked(cfg_.labelShow);
form->addRow(QStringLiteral("显示标注:"), labelShowChk_);
form->addRow(formkit::editLabel(QStringLiteral("显示标注:")), labelShowChk_);
labelColorBtn_ = new QPushButton(this);
paintSwatch(labelColorBtn_, cfg_.labelColor);
paintSwatch(labelColorBtn_, cfg_.labelColor); // 专用色块控件:样式表保留
connect(labelColorBtn_, &QPushButton::clicked, this, &ContourLineDialog::pickLabelColor);
form->addRow(QStringLiteral("标注颜色:"), labelColorBtn_);
form->addRow(formkit::editLabel(QStringLiteral("标注颜色:")), labelColorBtn_);
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
buttons->button(QDialogButtonBox::Ok)->setText(QStringLiteral("应用"));
buttons->button(QDialogButtonBox::Cancel)->setText(QStringLiteral("取消"));
cardLay->addLayout(form);
root->addWidget(card);
auto* buttons = formkit::addDialogButtons(root, this, QStringLiteral("应用"),
QStringLiteral("取消"));
// addDialogButtons 默认 accepted→accept();本对话框须先在 onAccept 写回 cfg_ 再 accept。
disconnect(buttons, &QDialogButtonBox::accepted, this, nullptr);
connect(buttons, &QDialogButtonBox::accepted, this, &ContourLineDialog::onAccept);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
root->addWidget(buttons);
}
void ContourLineDialog::paintSwatch(QPushButton* btn, const geopro::core::Rgba& c) {

View File

@ -3,16 +3,16 @@
#include <utility>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLabel>
#include <QMessageBox>
#include <QPushButton>
#include <QVBoxLayout>
#include "FormKit.hpp"
#include "Theme.hpp"
#include "api/NavLoads.hpp"
#include "api/NavRequest.hpp"
@ -29,31 +29,30 @@ ExportDatasetDialog::ExportDatasetDialog(geopro::data::IAsyncProjectRepository&
setWindowTitle(QStringLiteral("导出数据集"));
setMinimumWidth(geopro::app::scaledPx(400));
auto* lay = new QVBoxLayout(this);
lay->setContentsMargins(geopro::app::space::kLg, geopro::app::space::kMd,
geopro::app::space::kLg, geopro::app::space::kMd);
lay->setSpacing(geopro::app::space::kMd);
auto* root = formkit::dialogRoot(this);
auto* fl = new QFormLayout();
auto* card = formkit::formCard(this);
auto* cardLay = formkit::cardBody(card);
auto* fl = formkit::makeEditForm();
templateCombo_ = new QComboBox(this);
fl->addRow(QStringLiteral("导出模板"), templateCombo_);
lay->addLayout(fl);
formkit::capField(templateCombo_);
fl->addRow(formkit::editLabel(QStringLiteral("导出模板")), templateCombo_);
cardLay->addLayout(fl);
status_ = new QLabel(QStringLiteral("加载模板…"), this);
geopro::app::applyTokenizedStyleSheet(status_, QStringLiteral("color:{{text/disabled}};"));
lay->addWidget(status_);
cardLay->addWidget(status_);
auto* btnRow = new QHBoxLayout();
btnRow->addStretch();
auto* cancel = new QPushButton(QStringLiteral("取消"), this);
okBtn_ = new QPushButton(QStringLiteral("导出"), this);
root->addWidget(card);
auto* buttons =
formkit::addDialogButtons(root, this, QStringLiteral("导出"), QStringLiteral("取消"));
// Ok 不直接 accept需先 onConfirm 校验/异步导出,成功后才 accept。断开默认 accepted→accept。
QObject::disconnect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
okBtn_ = buttons->button(QDialogButtonBox::Ok);
okBtn_->setDefault(true);
okBtn_->setEnabled(false);
btnRow->addWidget(cancel);
btnRow->addWidget(okBtn_);
lay->addLayout(btnRow);
QObject::connect(cancel, &QPushButton::clicked, this, &QDialog::reject);
QObject::connect(okBtn_, &QPushButton::clicked, this, &ExportDatasetDialog::onConfirm);
loadTemplates();

View File

@ -6,6 +6,7 @@
#include <QFormLayout>
#include <QFrame>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#include <utility>
@ -31,24 +32,49 @@ DetailForm& DetailForm::row(const QString& key, const QString& value) {
return *this;
}
void buildDetailDialog(QDialog* dlg, const geopro::data::DynamicForm& form,
const QList<QWidget*>& extras) {
dlg->setModal(true);
QVBoxLayout* dialogRoot(QDialog* dlg) {
auto* root = new QVBoxLayout(dlg);
root->setContentsMargins(space::kLg, space::kLg, space::kLg, space::kLg);
root->setSpacing(space::kMd);
return root;
}
// 卡片与属性面板同款bg/panel-subtle + 1px 边框 + 中圆角),内嵌唯一只读渲染器
// KeyValueView——对话框随内容自适应大小无嵌套滚动/留空。
auto* card = new QFrame(dlg);
card->setObjectName(QStringLiteral("detailCard"));
QFrame* formCard(QWidget* parent) {
auto* card = new QFrame(parent);
card->setObjectName(QStringLiteral("formCard"));
applyTokenizedStyleSheet(
card, QStringLiteral("#detailCard { background:{{bg/panel-subtle}};"
card, QStringLiteral("#formCard { background:{{bg/panel-subtle}};"
"border:1px solid {{border/default}}; border-radius:%1px; }")
.arg(radius::kMd));
auto* cardLay = new QVBoxLayout(card);
cardLay->setContentsMargins(space::kLg, space::kLg, space::kLg, space::kLg);
cardLay->setSpacing(space::kMd);
auto* lay = new QVBoxLayout(card);
lay->setContentsMargins(space::kLg, space::kLg, space::kLg, space::kLg);
lay->setSpacing(space::kMd);
return card;
}
QVBoxLayout* cardBody(QFrame* card) {
return qobject_cast<QVBoxLayout*>(card->layout());
}
QDialogButtonBox* addDialogButtons(QVBoxLayout* root, QDialog* dlg, const QString& okText,
const QString& cancelText) {
auto* box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, dlg);
if (!okText.isEmpty()) box->button(QDialogButtonBox::Ok)->setText(okText);
if (!cancelText.isEmpty()) box->button(QDialogButtonBox::Cancel)->setText(cancelText);
QObject::connect(box, &QDialogButtonBox::accepted, dlg, &QDialog::accept);
QObject::connect(box, &QDialogButtonBox::rejected, dlg, &QDialog::reject);
root->addWidget(box);
return box;
}
void buildDetailDialog(QDialog* dlg, const geopro::data::DynamicForm& form,
const QList<QWidget*>& extras) {
dlg->setModal(true);
auto* root = dialogRoot(dlg);
// 卡片:与属性面板/编辑对话框同款,内嵌唯一只读渲染器 KeyValueView——随内容自适应大小。
auto* card = formCard(dlg);
auto* cardLay = cardBody(card);
auto* kv = new KeyValueView(card);
kv->setForm(form);

View File

@ -7,6 +7,8 @@
// 本模块把「只读键值详情」与「可编辑表单」的视觉度量收敛到唯一实现,所有表单必须经此产出,
// 一致性由「代码复用」强制,而非「人工遵守文档」。
#include <QFrame> // editLabel/formCard 返回 QLabel*/QFrame*;调用方常把结果直接传给
#include <QLabel> // addRow/addWidget(QWidget*)需在调用点见到完整类型QLabel/QFrame 派生自 QWidget
#include <QList>
#include <QString>
@ -14,8 +16,9 @@
class QBoxLayout;
class QDialog;
class QDialogButtonBox;
class QFormLayout;
class QLabel;
class QVBoxLayout;
class QWidget;
namespace geopro::app::formkit {
@ -38,6 +41,17 @@ private:
void buildDetailDialog(QDialog* dlg, const geopro::data::DynamicForm& form,
const QList<QWidget*>& extras = {});
// ── 对话框外壳:统一所有对话框的边距/卡片/按钮栏(编辑态与只读态共用 → 外观一致)──────
// 标准根布局:统一外边距 space/lg + 行距 space/md。
QVBoxLayout* dialogRoot(QDialog* dlg);
// 标准表单卡片bg/panel-subtle + 1px 边框 + 中圆角 + 统一内距。把表单/分组/键值包成
// 与「数据详情 / 属性面板」同款的卡片面。返回 QFrame其 layout() 即 QVBoxLayout向内 addSection/addLayout。
QFrame* formCard(QWidget* parent);
QVBoxLayout* cardBody(QFrame* card); // 取 formCard 的内层 QVBoxLayout便捷器
// 标准底部按钮栏QDialogButtonBox(Ok|Cancel),已接 accept/rejectokText/cancelText 可定制文案。
QDialogButtonBox* addDialogButtons(QVBoxLayout* root, QDialog* dlg, const QString& okText = QString(),
const QString& cancelText = QString());
// ── 可编辑表单§7.0 统一度量DynamicFormEditor 与各参数对话框共用,单一真相)──────
QFormLayout* makeEditForm(); // 右对齐标签 + 标准行距/列距
QLabel* editLabel(const QString& text, QWidget* parent = nullptr,

View File

@ -3,6 +3,7 @@
#include <utility>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QFileDialog>
#include <QFormLayout>
#include <QHBoxLayout>
@ -15,9 +16,9 @@
#include <QScrollArea>
#include <QUrl>
#include <QUrlQuery>
#include <QVBoxLayout>
#include <QVariantMap>
#include "FormKit.hpp"
#include "Theme.hpp"
#include "api/NavLoads.hpp"
#include "api/NavRequest.hpp"
@ -39,29 +40,27 @@ ImportDatasetDialog::ImportDatasetDialog(geopro::data::IAsyncProjectRepository&
setWindowTitle(QStringLiteral("导入数据集"));
setMinimumSize(geopro::app::scaledPx(480), geopro::app::scaledPx(520));
auto* lay = new QVBoxLayout(this);
lay->setContentsMargins(0, 0, 0, 0);
lay->setSpacing(0);
auto* root = formkit::dialogRoot(this);
status_ = new QLabel(QStringLiteral("加载数据类型…"), this);
status_->setAlignment(Qt::AlignCenter);
geopro::app::applyTokenizedStyleSheet(status_,
QStringLiteral("color:{{text/disabled}};padding:12px;"));
lay->addWidget(status_);
root->addWidget(status_);
auto* form = new QWidget(this);
auto* fl = new QFormLayout(form);
fl->setContentsMargins(geopro::app::space::kLg, geopro::app::space::kMd,
geopro::app::space::kLg, 0);
fl->setLabelAlignment(Qt::AlignRight | Qt::AlignVCenter);
fl->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
auto* card = formkit::formCard(this);
auto* cardLay = formkit::cardBody(card);
typeCombo_ = new QComboBox(form);
fl->addRow(QStringLiteral("数据类型"), typeCombo_);
scriptCombo_ = new QComboBox(form);
fl->addRow(QStringLiteral("导入脚本"), scriptCombo_);
auto* fl = formkit::makeEditForm();
auto* fileRow = new QWidget(form);
typeCombo_ = new QComboBox(card);
formkit::capField(typeCombo_);
fl->addRow(formkit::editLabel(QStringLiteral("数据类型")), typeCombo_);
scriptCombo_ = new QComboBox(card);
formkit::capField(scriptCombo_);
fl->addRow(formkit::editLabel(QStringLiteral("导入脚本")), scriptCombo_);
auto* fileRow = new QWidget(card);
auto* fileLay = new QHBoxLayout(fileRow);
fileLay->setContentsMargins(0, 0, 0, 0);
fileEdit_ = new QLineEdit(fileRow);
@ -70,36 +69,28 @@ ImportDatasetDialog::ImportDatasetDialog(geopro::data::IAsyncProjectRepository&
auto* browse = new QPushButton(QStringLiteral("浏览…"), fileRow);
fileLay->addWidget(fileEdit_, 1);
fileLay->addWidget(browse);
fl->addRow(QStringLiteral("文件"), fileRow);
lay->addWidget(form);
fl->addRow(formkit::editLabel(QStringLiteral("文件")), fileRow);
cardLay->addLayout(fl);
auto* paramLabel = new QLabel(QStringLiteral("脚本参数"), this);
geopro::app::applyTokenizedStyleSheet(
paramLabel, QStringLiteral("color:{{text/secondary}};padding:%1px %2px 0;")
.arg(geopro::app::space::kSm)
.arg(geopro::app::space::kLg));
lay->addWidget(paramLabel);
formkit::addSection(cardLay, QStringLiteral("脚本参数"), card, true);
auto* scroll = new QScrollArea(this);
auto* scroll = new QScrollArea(card);
scroll->setWidgetResizable(true);
scroll->setFrameShape(QFrame::NoFrame);
paramEditor_ = new DynamicFormEditor();
scroll->setWidget(paramEditor_);
lay->addWidget(scroll, 1);
cardLay->addWidget(scroll, 1);
auto* btnRow = new QHBoxLayout();
btnRow->setContentsMargins(geopro::app::space::kLg, geopro::app::space::kSm,
geopro::app::space::kLg, geopro::app::space::kMd);
btnRow->addStretch();
auto* cancel = new QPushButton(QStringLiteral("取消"), this);
okBtn_ = new QPushButton(QStringLiteral("导入"), this);
root->addWidget(card, 1);
auto* buttons =
formkit::addDialogButtons(root, this, QStringLiteral("导入"), QStringLiteral("取消"));
// Ok 不直接 accept需先 onConfirm 校验/异步导入,成功后才 accept。断开默认 accepted→accept。
QObject::disconnect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
okBtn_ = buttons->button(QDialogButtonBox::Ok);
okBtn_->setDefault(true);
okBtn_->setEnabled(false);
btnRow->addWidget(cancel);
btnRow->addWidget(okBtn_);
lay->addLayout(btnRow);
QObject::connect(cancel, &QPushButton::clicked, this, &QDialog::reject);
QObject::connect(browse, &QPushButton::clicked, this, &ImportDatasetDialog::chooseFile);
QObject::connect(okBtn_, &QPushButton::clicked, this, &ImportDatasetDialog::onConfirm);
QObject::connect(typeCombo_, qOverload<int>(&QComboBox::currentIndexChanged), this,

View File

@ -13,6 +13,7 @@
#include <QScrollArea>
#include <QVBoxLayout>
#include "FormKit.hpp"
#include "Theme.hpp"
#include "api/NavLoads.hpp" // Q_DECLARE_METATYPE(EditableForm) / GsTypeOption
#include "api/NavRequest.hpp"
@ -121,22 +122,17 @@ void ObjectFormDialog::buildTopFields() {
delete old;
}
auto* fl = new QFormLayout(topBox_);
// body 内距:对话框用 space/xl(24) 环绕表单(规范 §7.0.6)。
fl->setContentsMargins(geopro::app::space::kXxl, geopro::app::space::kXxl,
geopro::app::space::kXxl, 0);
fl->setLabelAlignment(Qt::AlignRight | Qt::AlignVCenter);
fl->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
fl->setHorizontalSpacing(geopro::app::space::kLg); // 标签↔字段 space/md(12)=项目 kLg
fl->setVerticalSpacing(geopro::app::space::kMd); // 行距 ≈8px与动态表单一致
auto* fl = formkit::makeEditForm();
// body 内距:对话框用 space/lg 环绕顶部固定字段区(与统一外壳一致)。
fl->setContentsMargins(geopro::app::space::kLg, geopro::app::space::kLg,
geopro::app::space::kLg, 0);
// 等宽右标签列 + 字段最大宽上限(规范 §7.0.2),与动态表单对齐。
// 等宽右标签列formkit::editLabel+ 字段最大宽上限formkit::capField与动态表单对齐。
auto addRow = [&](const QString& text, QWidget* field) {
auto* lbl = new QLabel(text, topBox_);
lbl->setFixedWidth(geopro::app::scaledPx(geopro::app::space::kFormLabelCol));
field->setMaximumWidth(geopro::app::scaledPx(geopro::app::space::kFormFieldMax));
fl->addRow(lbl, field);
formkit::capField(field);
fl->addRow(formkit::editLabel(text, topBox_), field);
};
topBox_->setLayout(fl);
const bool isCreate = objectId_.isEmpty();

View File

@ -2,7 +2,6 @@
#include <QComboBox>
#include <QCoreApplication>
#include <QFrame>
#include <QHBoxLayout>
#include <QLabel>
#include <QListWidget>
@ -14,6 +13,7 @@
#include <QVBoxLayout>
#include <QWidget>
#include "FormKit.hpp"
#include "Glyphs.hpp"
#include "Theme.hpp"
@ -47,42 +47,20 @@ QWidget* makeRow(const QString& label, QWidget* control, const QString& caption
.arg(geopro::app::scaledPx(geopro::app::type::kCaption)));
lv->addWidget(cap);
}
labelCol->setMinimumWidth(160);
labelCol->setMinimumWidth(geopro::app::scaledPx(160));
lay->addWidget(labelCol);
lay->addWidget(control, 1, Qt::AlignTop);
return row;
}
// 分组标题§7.10text/heading 字号 + text/secondary 色,下方 1px divider。
QWidget* sectionTitle(const QString& text, QWidget* parent) {
auto* box = new QWidget(parent);
auto* v = new QVBoxLayout(box);
v->setContentsMargins(0, 0, 0, 0);
v->setSpacing(geopro::app::space::kSm);
auto* t = new QLabel(text, box);
geopro::app::applyTokenizedStyleSheet(
t, QStringLiteral("color:{{text/secondary}}; font-size:%1px; font-weight:%2;")
.arg(geopro::app::scaledPx(geopro::app::type::kHeading))
.arg(geopro::app::type::kWeightSemibold));
v->addWidget(t);
auto* divider = new QFrame(box);
divider->setFrameShape(QFrame::HLine);
divider->setFixedHeight(1);
geopro::app::applyTokenizedStyleSheet(
divider, QStringLiteral("background:{{divider}}; border:none;"));
v->addWidget(divider);
return box;
}
QWidget* buildAppearancePage() {
auto* page = new QWidget();
auto* v = new QVBoxLayout(page);
v->setContentsMargins(24, 20, 24, 20);
v->setSpacing(16);
v->addWidget(sectionTitle(QStringLiteral("外观"), page));
v->setContentsMargins(geopro::app::space::kXxl, geopro::app::scaledPx(20),
geopro::app::space::kXxl, geopro::app::scaledPx(20));
v->setSpacing(geopro::app::space::kXl);
geopro::app::formkit::addSection(v, QStringLiteral("外观"), page, false);
// 主题:跟随系统 / 浅色 / 深色(热切)。
auto* themeCombo = new QComboBox(page);
@ -111,8 +89,9 @@ QWidget* buildAppearancePage() {
// 字号改动:持久化 + 提示重启(提供立即重启)。
auto* restartRow = new QWidget(page);
auto* rlay = new QHBoxLayout(restartRow);
rlay->setContentsMargins(160 + geopro::app::space::kLg, 0, 0, 0); // 与控件列对齐
rlay->setSpacing(10);
rlay->setContentsMargins(geopro::app::scaledPx(160) + geopro::app::space::kLg, 0, 0,
0); // 与控件列对齐
rlay->setSpacing(geopro::app::space::kMl);
auto* hint = new QLabel(QStringLiteral("界面字号将在重启后生效"), restartRow);
geopro::app::applyTokenizedStyleSheet(
hint, QStringLiteral("color:{{text/secondary}}; font-size:%1px;")
@ -141,9 +120,10 @@ QWidget* buildAppearancePage() {
QWidget* buildAboutPage() {
auto* page = new QWidget();
auto* v = new QVBoxLayout(page);
v->setContentsMargins(24, 20, 24, 20);
v->setSpacing(12);
v->addWidget(sectionTitle(QStringLiteral("关于"), page));
v->setContentsMargins(geopro::app::space::kXxl, geopro::app::scaledPx(20),
geopro::app::space::kXxl, geopro::app::scaledPx(20));
v->setSpacing(geopro::app::space::kLg);
geopro::app::formkit::addSection(v, QStringLiteral("关于"), page, false);
auto* ver = new QLabel(QStringLiteral("Geopro 3.0 — 项目分析视图 (M1)"), page);
ver->setStyleSheet(QStringLiteral("font-size:%1px; font-weight:600;")
@ -169,7 +149,7 @@ QWidget* buildAboutPage() {
SettingsDialog::SettingsDialog(QWidget* parent) : QDialog(parent) {
setWindowTitle(QStringLiteral("设置"));
resize(720, 480);
resize(geopro::app::scaledPx(720), geopro::app::scaledPx(480));
auto* root = new QHBoxLayout(this);
root->setContentsMargins(0, 0, 0, 0);
@ -179,7 +159,7 @@ SettingsDialog::SettingsDialog(QWidget* parent) : QDialog(parent) {
// 左竖条用「选中项 2px 左边框 + accent/primary」实现非选中项留同宽透明左边框防文字跳动。
auto* sidebar = new QListWidget(this);
sidebar->setObjectName(QStringLiteral("settingsSidebar"));
sidebar->setFixedWidth(160);
sidebar->setFixedWidth(geopro::app::scaledPx(160));
sidebar->setIconSize(QSize(geopro::app::scaledPx(16), geopro::app::scaledPx(16)));
geopro::app::applyTokenizedStyleSheet(
sidebar,

View File

@ -263,6 +263,28 @@ QLineEdit:disabled {
color: {{text/disabled}};
}
/* 多行文本(备注/描述):与输入框同款 box边框/底色/圆角/内距),仅不设 min-height
沿 Fusion */
QPlainTextEdit, QTextEdit {
background: {{bg/panel}};
color: {{text/primary}};
border: 1px solid {{border/default}};
border-radius: 4px; /* radius/sm */
padding: 4px 8px;
selection-background-color: {{accent/primary}};
selection-color: {{text/on-primary}};
}
QPlainTextEdit:hover, QTextEdit:hover {
border-color: {{border/strong}};
}
QPlainTextEdit:focus, QTextEdit:focus {
border: 1px solid {{border/focus}};
}
QPlainTextEdit:disabled, QTextEdit:disabled {
background: {{bg/app}};
color: {{text/disabled}};
}
/* ── 滚动条:纤细现代(无需图片资源)───────────────────────── */
QScrollBar:vertical {
background: transparent;
@ -388,22 +410,24 @@ QComboBox:disabled {
background: {{bg/app}};
color: {{text/disabled}};
}
/* 日期/时间编辑器:与输入框/下拉框同款外观box 一致,避免「布设日期」与下拉框不一致)。 */
QDateEdit, QTimeEdit, QDateTimeEdit {
/* 数字框/日期/时间编辑器:与输入框/下拉框同款外观。QSpinBox/QDoubleSpinBox/QDateEdit/
QTimeEdit/QDateTimeEdit QAbstractSpinBox box///
/ QLineEdit/QComboBox/Date SpinBox */
QAbstractSpinBox {
background: {{bg/panel}};
color: {{text/primary}};
border: 1px solid {{border/default}};
border-radius: 4px; /* radius/sm */
padding: 6px 8px;
min-height: 16px;
min-height: 16px; /* 与 QLineEdit/QComboBox 完全一致 → 同高 */
}
QDateEdit:hover, QTimeEdit:hover, QDateTimeEdit:hover {
QAbstractSpinBox:hover {
border-color: {{border/strong}};
}
QDateEdit:focus, QTimeEdit:focus, QDateTimeEdit:focus {
QAbstractSpinBox:focus {
border-color: {{border/focus}};
}
QDateEdit:disabled, QTimeEdit:disabled, QDateTimeEdit:disabled {
QAbstractSpinBox:disabled {
background: {{bg/app}};
color: {{text/disabled}};
}
@ -421,6 +445,38 @@ QComboBox::down-arrow, QDateEdit::down-arrow, QTimeEdit::down-arrow, QDateTimeEd
width: 12px;
height: 12px;
}
/* 数字框上下按钮:平面化 + 扁平 chevron与下拉箭头同族。覆写 up/down-button 须同时给
up/down-arrow Fusion /::drop-down */
QSpinBox::up-button, QDoubleSpinBox::up-button {
subcontrol-origin: border;
subcontrol-position: top right;
width: 18px;
border: none;
border-left: 1px solid {{border/default}};
background: transparent;
}
QSpinBox::down-button, QDoubleSpinBox::down-button {
subcontrol-origin: border;
subcontrol-position: bottom right;
width: 18px;
border: none;
border-left: 1px solid {{border/default}};
background: transparent;
}
QSpinBox::up-button:hover, QDoubleSpinBox::up-button:hover,
QSpinBox::down-button:hover, QDoubleSpinBox::down-button:hover {
background: {{bg/hover}};
}
QSpinBox::up-arrow, QDoubleSpinBox::up-arrow {
image: url(:/icons/chevron-up.svg);
width: 10px;
height: 10px;
}
QSpinBox::down-arrow, QDoubleSpinBox::down-arrow {
image: url(:/icons/chevron-down.svg);
width: 10px;
height: 10px;
}
QComboBox QAbstractItemView {
background: {{bg/panel}};
border: 1px solid {{border/default}};

View File

@ -26,15 +26,17 @@ VolumeParamsDialog::VolumeParamsDialog(int sourceCount, QWidget* parent) : QDial
setWindowTitle(QStringLiteral("生成三维体"));
setModal(true);
auto* root = new QVBoxLayout(this);
root->setContentsMargins(geopro::app::space::kLg, geopro::app::space::kLg,
geopro::app::space::kLg, geopro::app::space::kLg);
root->setSpacing(geopro::app::space::kMd);
auto* root = formkit::dialogRoot(this);
auto* intro = new QLabel(QStringLiteral("由 %1 个源数据集插值生成三维体").arg(sourceCount));
geopro::app::applyTokenizedStyleSheet(intro, QStringLiteral("color:{{text/secondary}};"));
root->addWidget(intro);
// 统一外壳:表单卡片 + 分组标题(与「数据详情 / 属性面板」同款),编辑态/只读态一致。
auto* card = formkit::formCard(this);
auto* cardLay = formkit::cardBody(card);
formkit::addSection(cardLay, QStringLiteral("参数"), card, false);
auto* form = formkit::makeEditForm();
name_ = new QLineEdit(QStringLiteral("三维体"));
@ -72,12 +74,10 @@ VolumeParamsDialog::VolumeParamsDialog(int sourceCount, QWidget* parent) : QDial
form->addRow(formkit::editLabel(QStringLiteral("IDW 幂次")), power_);
form->addRow(formkit::editLabel(QStringLiteral("最大影响距离 (米)")), maxDist_);
root->addLayout(form);
cardLay->addLayout(form);
root->addWidget(card);
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
root->addWidget(buttons);
formkit::addDialogButtons(root, this, QStringLiteral("生成"), QStringLiteral("取消"));
}
QString VolumeParamsDialog::volumeName() const {

View File

@ -1015,8 +1015,8 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
rightDock->setWidget(anomalyPanel.container);
auto* rightArea = dockManager->addDockWidget(ads::RightDockWidgetArea, rightDock);
// 右下 dock属性数据集属性上半只读元字段 + 下半可编辑描述)。
auto* propView = new geopro::app::DatasetAttrPanel(projectRepo);
// 右下 dock属性数据集属性只读元字段;不可编辑)。
auto* propView = new geopro::app::DatasetAttrPanel();
auto* propDock = new ads::CDockWidget(QStringLiteral("数据集属性"));
propDock->setWidget(
wrapWithHeader(geopro::app::Glyph::Property, QStringLiteral("数据集属性"), propView));
@ -1040,7 +1040,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
// ── 单击左下数据列表的采集批次(DS) → 属性表单 + 聚焦详情已开页 ──
QObject::connect(datasetList, &QTreeWidget::itemClicked, datasetList,
[&nav, &detailCtrl, propView](QTreeWidgetItem* item, int) {
[&nav, &detailCtrl](QTreeWidgetItem* item, int) {
if (item->data(0, geopro::app::kDsLoadMoreRole).toBool()) {
nav.loadMoreData();
return;
@ -1048,7 +1048,6 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
const QString dsId = item->data(0, geopro::app::kDsIdRole).toString();
if (dsId.isEmpty()) return;
nav.selectDataset(dsId); // 只读元字段表单datasetDetailLoaded
propView->selectDataset(dsId); // 可编辑描述:回填 + 启用保存
detailCtrl.focusDataset(dsId); // 单击=聚焦已开页
});
@ -1370,9 +1369,6 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
toast(QStringLiteral("保存成功"));
nav.switchProject(nav.currentProjectId());
});
// 数据集属性面板描述保存成功 → toast。
QObject::connect(propView, &geopro::app::DatasetAttrPanel::saved, &window,
[toast]() { toast(QStringLiteral("描述已保存")); });
// 增删改结果 → 状态栏反馈(成功后控制器已自行刷新)。
QObject::connect(&nav, &geopro::controller::WorkbenchNavController::mutationSucceeded, &window,
@ -1473,9 +1469,8 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
[&detailCtrl, dsId, ddCode, dsName]() {
detailCtrl.openDataset(dsId, ddCode, dsName);
});
menu.addAction(QStringLiteral("属性"), datasetList, [&nav, propView, dsId]() {
menu.addAction(QStringLiteral("属性"), datasetList, [&nav, dsId]() {
nav.selectDataset(dsId); // 只读元字段
propView->selectDataset(dsId); // 可编辑描述
});
menu.addSeparator();
QMenu* plugins = menu.addMenu(QStringLiteral("插件"));

View File

@ -1,61 +1,20 @@
#include "panels/DatasetAttrPanel.hpp"
#include <QHBoxLayout>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLabel>
#include <QPushButton>
#include <QTextEdit>
#include <QVBoxLayout>
#include "Theme.hpp"
#include "api/NavRequest.hpp"
#include "panels/DynamicFormView.hpp"
#include "repo/IAsyncProjectRepository.hpp"
namespace geopro::app {
DatasetAttrPanel::DatasetAttrPanel(geopro::data::IAsyncProjectRepository& repo, QWidget* parent)
: QWidget(parent), repo_(repo) {
DatasetAttrPanel::DatasetAttrPanel(QWidget* parent) : QWidget(parent) {
auto* lay = new QVBoxLayout(this);
lay->setContentsMargins(0, 0, 0, 0);
lay->setSpacing(0);
// 上半:只读元字段(复用 DynamicFormView
// 只读元字段(复用 DynamicFormView。数据集属性不可编辑
metaView_ = new DynamicFormView(this);
metaView_->showMessage(QStringLiteral("(单击数据集查看属性)"));
lay->addWidget(metaView_, 1);
// 下半:可编辑描述区。
auto* descBox = new QWidget(this);
auto* descLay = new QVBoxLayout(descBox);
descLay->setContentsMargins(geopro::app::space::kLg, geopro::app::space::kSm,
geopro::app::space::kLg, geopro::app::space::kMd);
descLay->setSpacing(geopro::app::space::kMd); // 标签↔输入↔保存:统一 ≈8px 节奏(规范 §7.0
auto* hint = new QLabel(QStringLiteral("描述(备注)"), descBox);
geopro::app::applyTokenizedStyleSheet(hint, QStringLiteral("color:{{text/secondary}};"));
descLay->addWidget(hint);
descEdit_ = new QTextEdit(descBox);
descEdit_->setAcceptRichText(false); // 纯文本(与服务端 delta 纯文本往返一致)
descEdit_->setEnabled(false);
descEdit_->setMaximumHeight(geopro::app::scaledPx(120));
descLay->addWidget(descEdit_);
auto* btnRow = new QHBoxLayout();
status_ = new QLabel(QString(), descBox);
geopro::app::applyTokenizedStyleSheet(status_, QStringLiteral("color:{{text/disabled}};"));
btnRow->addWidget(status_, 1);
saveBtn_ = new QPushButton(QStringLiteral("保存"), descBox);
saveBtn_->setEnabled(false);
btnRow->addWidget(saveBtn_);
descLay->addLayout(btnRow);
lay->addWidget(descBox);
QObject::connect(saveBtn_, &QPushButton::clicked, this, &DatasetAttrPanel::onSave);
}
void DatasetAttrPanel::setForm(const geopro::data::DynamicForm& form) {
@ -63,66 +22,7 @@ void DatasetAttrPanel::setForm(const geopro::data::DynamicForm& form) {
}
void DatasetAttrPanel::showMessage(const QString& message) {
dsObjectId_.clear();
metaView_->showMessage(message);
descEdit_->clear();
descEdit_->setEnabled(false);
saveBtn_->setEnabled(false);
status_->clear();
}
void DatasetAttrPanel::selectDataset(const QString& dsObjectId) {
// 切换数据集:中止在途保存,避免旧 save 回调串台触发 saved()/启用按钮。
if (saveReq_) saveReq_->abort();
dsObjectId_ = dsObjectId;
descEdit_->setEnabled(false);
saveBtn_->setEnabled(false);
status_->setText(QStringLiteral("加载描述…"));
if (loadReq_) loadReq_->abort();
loadReq_ = repo_.loadDatasetDetailAsync(dsObjectId.toStdString());
QObject::connect(loadReq_, &geopro::data::NavRequest::done, this, [this](const QVariant& v) {
descEdit_->setPlainText(v.toString()); // payload=QString现有描述纯文本
descEdit_->setEnabled(true);
saveBtn_->setEnabled(true);
status_->clear();
});
QObject::connect(loadReq_, &geopro::data::NavRequest::failed, this, [this](const QString& msg) {
// 加载失败仍允许编辑/保存(视为新建描述),仅提示。
descEdit_->setEnabled(true);
saveBtn_->setEnabled(true);
status_->setText(QStringLiteral("加载描述失败:%1可直接编辑保存").arg(msg));
});
}
void DatasetAttrPanel::onSave() {
if (dsObjectId_.isEmpty()) return;
const QString text = descEdit_->toPlainText();
// 最小 Quill delta[{ insert: <文本 + "\n"> }],承载纯文本与服务端往返。
const QJsonArray deltaOps{QJsonObject{{QStringLiteral("insert"), text + QStringLiteral("\n")}}};
const QJsonObject body{
{QStringLiteral("dsObjectId"), dsObjectId_},
{QStringLiteral("description"), text},
{QStringLiteral("attachedParameters"),
QJsonObject{{QStringLiteral("deltaContent"), deltaOps}}}};
saveBtn_->setEnabled(false);
descEdit_->setEnabled(false);
status_->setText(QStringLiteral("保存中…"));
if (saveReq_) saveReq_->abort();
saveReq_ =
repo_.updateDatasetAsync(QJsonDocument(body).toJson(QJsonDocument::Compact).toStdString());
QObject::connect(saveReq_, &geopro::data::NavRequest::done, this, [this](const QVariant&) {
descEdit_->setEnabled(true);
saveBtn_->setEnabled(true);
status_->clear();
emit saved();
});
QObject::connect(saveReq_, &geopro::data::NavRequest::failed, this, [this](const QString& msg) {
status_->setText(QStringLiteral("保存失败:%1").arg(msg));
descEdit_->setEnabled(true);
saveBtn_->setEnabled(true);
});
}
} // namespace geopro::app

View File

@ -1,52 +1,25 @@
#pragma once
#include <QPointer>
#include <QString>
#include <QWidget>
#include "repo/RepoTypes.hpp"
namespace geopro::data {
class IAsyncProjectRepository;
class NavRequest;
} // namespace geopro::data
class QLabel;
class QPushButton;
class QTextEdit;
namespace geopro::app {
class DynamicFormView;
// 数据集属性面板右下「数据集属性」上半只读元字段DynamicForm+ 下半可编辑描述 + 保存。
// 元字段无写接口仅展示datasetDetailLoaded 推送的 DynamicForm
// 描述可写PUT dsObject/updateDsObject/ body
// { dsObjectId, description:<纯文本>, attachedParameters:{ deltaContent:[{ insert:<文本+"\n"> }] } }
// 描述文本经 loadDatasetDetailAsync(GET getDetail) 回填payload=QString
// 数据集属性面板(右下「数据集属性」):**只读**元字段展示datasetDetailLoaded 推送的 DynamicForm
// 数据集属性不可编辑——仅展示,无写接口(描述编辑功能已移除)。
class DatasetAttrPanel : public QWidget {
Q_OBJECT
public:
DatasetAttrPanel(geopro::data::IAsyncProjectRepository& repo, QWidget* parent = nullptr);
explicit DatasetAttrPanel(QWidget* parent = nullptr);
void setForm(const geopro::data::DynamicForm& form); // 元字段只读展示
void showMessage(const QString& message); // 空/占位
void selectDataset(const QString& dsObjectId); // 选中 → 回填描述、启用保存
signals:
void saved(); // 描述保存成功
private:
void onSave();
geopro::data::IAsyncProjectRepository& repo_;
QString dsObjectId_;
DynamicFormView* metaView_ = nullptr;
QTextEdit* descEdit_ = nullptr;
QLabel* status_ = nullptr;
QPushButton* saveBtn_ = nullptr;
QPointer<geopro::data::NavRequest> loadReq_;
QPointer<geopro::data::NavRequest> saveReq_;
};
} // namespace geopro::app

View File

@ -4,5 +4,6 @@
跨明暗主题用中性灰,避免覆写 ::drop-down 后 Fusion 原生斜角箭头的复古观感。 -->
<qresource prefix="/icons">
<file alias="chevron-down.svg">icons/chevron-down.svg</file>
<file alias="chevron-up.svg">icons/chevron-up.svg</file>
</qresource>
</RCC>

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
<path d="M3 7.5 L6 4.5 L9 7.5" fill="none" stroke="#6B7785" stroke-width="1.5"
stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 229 B