From 08b8ebbf012c0e2cbdf1280c852a21f4e9d917f2 Mon Sep 17 00:00:00 2001 From: gaozheng Date: Tue, 23 Jun 2026 09:13:17 +0800 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=E7=BB=9F=E4=B8=80=E5=AF=B9?= =?UTF-8?q?=E8=AF=9D=E6=A1=86=E5=A4=96=E5=A3=B3+=E5=85=A8=E5=B1=80?= =?UTF-8?q?=E6=8E=A7=E4=BB=B6=E9=AB=98=E5=BA=A6,=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E9=9B=86=E5=B1=9E=E6=80=A7=E6=94=B9=E5=8F=AA=E8=AF=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 对话框外壳(formkit)统一,编辑态与只读态同款卡片/分组/边距/按钮栏: - 新增 dialogRoot/formCard/cardBody/addDialogButtons;buildDetailDialog 复用 formCard - 生成三维体/导入/导出/异常保存/对象新建/等值线/色阶/设置等对话框迁到统一外壳 - FormKit.hpp 直接 include QLabel/QFrame(editLabel/formCard 返回类型在调用点需完整) 全局控件高度/外观一次补齐(QSS,覆盖所有对话框): - QSpinBox/QDoubleSpinBox 经 QAbstractSpinBox 统一 box,与输入框/下拉框同高;上下按钮扁平 chevron - QPlainTextEdit/QTextEdit 补输入框同款边框/圆角/底色 - 新增 chevron-up.svg(数字框上箭头) 数据集属性改为纯只读:移除"修改描述"功能(QTextEdit+保存+saved 信号+repo 依赖), DatasetAttrPanel 构造简化、main.cpp 相应接线清理 --- src/app/AnomalySaveDialog.cpp | 38 +++++---- src/app/ColorGradientDialog.cpp | 28 +++---- src/app/ContourLevelDialog.cpp | 43 ++++++---- src/app/ContourLineDialog.cpp | 36 +++++---- src/app/ExportDatasetDialog.cpp | 37 +++++---- src/app/FormKit.cpp | 48 +++++++++--- src/app/FormKit.hpp | 16 +++- src/app/ImportDatasetDialog.cpp | 63 +++++++-------- src/app/ObjectFormDialog.cpp | 22 +++--- src/app/SettingsDialog.cpp | 50 ++++-------- src/app/Theme.cpp | 68 ++++++++++++++-- src/app/VolumeParamsDialog.cpp | 18 ++--- src/app/main.cpp | 15 ++-- src/app/panels/DatasetAttrPanel.cpp | 104 +------------------------ src/app/panels/DatasetAttrPanel.hpp | 33 +------- src/app/resources/icons.qrc | 1 + src/app/resources/icons/chevron-up.svg | 4 + 17 files changed, 290 insertions(+), 334 deletions(-) create mode 100644 src/app/resources/icons/chevron-up.svg diff --git a/src/app/AnomalySaveDialog.cpp b/src/app/AnomalySaveDialog.cpp index 26a39da..574aceb 100644 --- a/src/app/AnomalySaveDialog.cpp +++ b/src/app/AnomalySaveDialog.cpp @@ -1,13 +1,14 @@ #include "AnomalySaveDialog.hpp" #include -#include #include #include #include #include #include -#include + +#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 { diff --git a/src/app/ColorGradientDialog.cpp b/src/app/ColorGradientDialog.cpp index 57b6cfa..406aa97 100644 --- a/src/app/ColorGradientDialog.cpp +++ b/src/app/ColorGradientDialog.cpp @@ -28,6 +28,8 @@ #include #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& 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& init, double minValue, @@ -104,8 +100,8 @@ ColorGradientDialog::ColorGradientDialog(const std::vector& init, double m // ── 顶部两列 grid(grid-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& 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& 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& 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& 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& 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& 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& 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); diff --git a/src/app/ContourLevelDialog.cpp b/src/app/ContourLevelDialog.cpp index b1e30d1..ae223eb 100644 --- a/src/app/ContourLevelDialog.cpp +++ b/src/app/ContourLevelDialog.cpp @@ -15,6 +15,8 @@ #include #include +#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(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); diff --git a/src/app/ContourLineDialog.cpp b/src/app/ContourLineDialog.cpp index ecdcd53..c7cc5f3 100644 --- a/src/app/ContourLineDialog.cpp +++ b/src/app/ContourLineDialog.cpp @@ -9,6 +9,8 @@ #include #include +#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) { diff --git a/src/app/ExportDatasetDialog.cpp b/src/app/ExportDatasetDialog.cpp index 2311c67..36ab24c 100644 --- a/src/app/ExportDatasetDialog.cpp +++ b/src/app/ExportDatasetDialog.cpp @@ -3,16 +3,16 @@ #include #include +#include #include -#include #include #include #include #include #include #include -#include +#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(); diff --git a/src/app/FormKit.cpp b/src/app/FormKit.cpp index 9ffd676..f20809e 100644 --- a/src/app/FormKit.cpp +++ b/src/app/FormKit.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -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& 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(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& 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); diff --git a/src/app/FormKit.hpp b/src/app/FormKit.hpp index 5e87956..e73dc53 100644 --- a/src/app/FormKit.hpp +++ b/src/app/FormKit.hpp @@ -7,6 +7,8 @@ // 本模块把「只读键值详情」与「可编辑表单」的视觉度量收敛到唯一实现,所有表单必须经此产出, // 一致性由「代码复用」强制,而非「人工遵守文档」。 +#include // editLabel/formCard 返回 QLabel*/QFrame*;调用方常把结果直接传给 +#include // addRow/addWidget(QWidget*),需在调用点见到完整类型(QLabel/QFrame 派生自 QWidget)。 #include #include @@ -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& 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/reject;okText/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, diff --git a/src/app/ImportDatasetDialog.cpp b/src/app/ImportDatasetDialog.cpp index 402f624..e5b32ff 100644 --- a/src/app/ImportDatasetDialog.cpp +++ b/src/app/ImportDatasetDialog.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -15,9 +16,9 @@ #include #include #include -#include #include +#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(&QComboBox::currentIndexChanged), this, diff --git a/src/app/ObjectFormDialog.cpp b/src/app/ObjectFormDialog.cpp index 7def586..83b8ac6 100644 --- a/src/app/ObjectFormDialog.cpp +++ b/src/app/ObjectFormDialog.cpp @@ -13,6 +13,7 @@ #include #include +#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(); diff --git a/src/app/SettingsDialog.cpp b/src/app/SettingsDialog.cpp index fcac9f9..13b9c2f 100644 --- a/src/app/SettingsDialog.cpp +++ b/src/app/SettingsDialog.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -14,6 +13,7 @@ #include #include +#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.10):text/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, diff --git a/src/app/Theme.cpp b/src/app/Theme.cpp index 168f92b..e0231b3 100644 --- a/src/app/Theme.cpp +++ b/src/app/Theme.cpp @@ -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}}; diff --git a/src/app/VolumeParamsDialog.cpp b/src/app/VolumeParamsDialog.cpp index ce14888..ce12b9e 100644 --- a/src/app/VolumeParamsDialog.cpp +++ b/src/app/VolumeParamsDialog.cpp @@ -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 { diff --git a/src/app/main.cpp b/src/app/main.cpp index 4d86eaf..b9ab563 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -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]() { - nav.selectDataset(dsId); // 只读元字段 - propView->selectDataset(dsId); // 可编辑描述 + menu.addAction(QStringLiteral("属性"), datasetList, [&nav, dsId]() { + nav.selectDataset(dsId); // 只读元字段 }); menu.addSeparator(); QMenu* plugins = menu.addMenu(QStringLiteral("插件")); diff --git a/src/app/panels/DatasetAttrPanel.cpp b/src/app/panels/DatasetAttrPanel.cpp index 4d1e869..7ee1db9 100644 --- a/src/app/panels/DatasetAttrPanel.cpp +++ b/src/app/panels/DatasetAttrPanel.cpp @@ -1,61 +1,20 @@ #include "panels/DatasetAttrPanel.hpp" -#include -#include -#include -#include -#include -#include -#include #include -#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 diff --git a/src/app/panels/DatasetAttrPanel.hpp b/src/app/panels/DatasetAttrPanel.hpp index 7087ada..258c346 100644 --- a/src/app/panels/DatasetAttrPanel.hpp +++ b/src/app/panels/DatasetAttrPanel.hpp @@ -1,52 +1,25 @@ #pragma once -#include #include #include #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 loadReq_; - QPointer saveReq_; }; } // namespace geopro::app diff --git a/src/app/resources/icons.qrc b/src/app/resources/icons.qrc index 6023a48..7a03108 100644 --- a/src/app/resources/icons.qrc +++ b/src/app/resources/icons.qrc @@ -4,5 +4,6 @@ 跨明暗主题用中性灰,避免覆写 ::drop-down 后 Fusion 原生斜角箭头的复古观感。 --> icons/chevron-down.svg + icons/chevron-up.svg diff --git a/src/app/resources/icons/chevron-up.svg b/src/app/resources/icons/chevron-up.svg new file mode 100644 index 0000000..9d9d562 --- /dev/null +++ b/src/app/resources/icons/chevron-up.svg @@ -0,0 +1,4 @@ + + +