#include "ObjectFormDialog.hpp" #include #include #include "EmptyAwareComboBox.hpp" #include #include #include #include #include #include #include #include #include #include "FormKit.hpp" #include "Theme.hpp" #include "api/NavLoads.hpp" // Q_DECLARE_METATYPE(EditableForm) / GsTypeOption #include "api/NavRequest.hpp" #include "panels/DynamicFormEditor.hpp" #include "repo/IAsyncProjectRepository.hpp" namespace geopro::app { namespace { constexpr int kConfGs = 1; constexpr int kConfTm = 2; } // namespace ObjectFormDialog::ObjectFormDialog(geopro::data::IAsyncProjectRepository& repo, QString projectId, QWidget* parent) : QDialog(parent), repo_(repo), projectId_(std::move(projectId)) { setModal(true); setMinimumSize(geopro::app::scaledPx(480), geopro::app::scaledPx(560)); auto* lay = new QVBoxLayout(this); lay->setContentsMargins(0, 0, 0, 0); lay->setSpacing(0); status_ = new QLabel(QStringLiteral("加载中…"), this); status_->setAlignment(Qt::AlignCenter); geopro::app::applyTokenizedStyleSheet(status_, QStringLiteral("color:{{text/disabled}};padding:16px;")); lay->addWidget(status_); auto* scroll = new QScrollArea(this); scroll->setWidgetResizable(true); scroll->setFrameShape(QFrame::NoFrame); auto* content = new QWidget(); auto* contentLay = new QVBoxLayout(content); contentLay->setContentsMargins(0, 0, 0, 0); contentLay->setSpacing(0); topBox_ = new QWidget(content); // 顶层固定字段容器(buildTopFields 重填) contentLay->addWidget(topBox_); editor_ = new DynamicFormEditor(); contentLay->addWidget(editor_, 1); scroll->setWidget(content); lay->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); 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, &ObjectFormDialog::onConfirm); } void ObjectFormDialog::editObject(const QString& typeId, const QString& objectId, int confType, const QString& displayName, const QString& parentId) { confType_ = confType; objectId_ = objectId; typeId_ = typeId; parentId_ = parentId; // TM 编辑 PUT 须带真实父 GS/根 id(GS 编辑忽略) setWindowTitle(QStringLiteral("编辑 — %1").arg(displayName)); buildTopFields(); nameEdit_->setText(displayName); // 编辑态:用对象名预填并禁用 loadForm(typeId, objectId); } void ObjectFormDialog::newGs(const QString& parentId) { confType_ = kConfGs; objectId_.clear(); parentId_ = parentId; typeId_.clear(); setWindowTitle(QStringLiteral("新建检测对象")); buildTopFields(); loadGsTypes(); // 拉类型 → 选第一项 → 加载动态表单 } void ObjectFormDialog::newTm(const QString& parentId) { confType_ = kConfTm; objectId_.clear(); parentId_ = parentId; typeId_.clear(); setWindowTitle(QStringLiteral("新建方法对象")); buildTopFields(); loadTmTypes(); // 拉全局方法类型 → 选第一项 → 加载动态表单(type=2) } // 顶层固定字段:按 confType 与 新建/编辑 决定。整体重建 topBox_。 void ObjectFormDialog::buildTopFields() { typeCombo_ = nullptr; typeNameLabel_ = nullptr; nameEdit_ = nullptr; responsibleEdit_ = nullptr; // 清空旧布局/控件。 if (auto* old = topBox_->layout()) { QLayoutItem* it = nullptr; while ((it = old->takeAt(0)) != nullptr) { if (it->widget()) it->widget()->deleteLater(); delete it; } delete old; } auto* fl = formkit::makeEditForm(); // body 内距:对话框用 space/lg 环绕顶部固定字段区(与统一外壳一致)。 fl->setContentsMargins(geopro::app::space::kLg, geopro::app::space::kLg, geopro::app::space::kLg, 0); // 等宽右标签列(formkit::editLabel)+ 字段最大宽上限(formkit::capField),与动态表单对齐。 auto addRow = [&](const QString& text, QWidget* field) { formkit::capField(field); fl->addRow(formkit::editLabel(text, topBox_), field); }; topBox_->setLayout(fl); const bool isCreate = objectId_.isEmpty(); if (isCreate) { // 新建 GS/TM:类型下拉(数据源 gsList / tmList,选择后重载动态表单)。 const QString label = confType_ == kConfTm ? QStringLiteral("方法类型") : QStringLiteral("对象类型"); typeCombo_ = new EmptyAwareComboBox(topBox_); addRow(label, typeCombo_); QObject::connect(typeCombo_, qOverload(&QComboBox::currentIndexChanged), this, [this](int) { const QString tid = typeCombo_->currentData().toString(); if (!tid.isEmpty()) { typeId_ = tid; loadForm(tid, QString()); } }); } else { // 编辑:类型名只读展示。 typeNameLabel_ = new QLabel(topBox_); geopro::app::applyTokenizedStyleSheet(typeNameLabel_, QStringLiteral("color:{{text/secondary}};")); addRow(QStringLiteral("类型"), typeNameLabel_); } nameEdit_ = new QLineEdit(topBox_); nameEdit_->setPlaceholderText(QStringLiteral("名称")); if (!isCreate) nameEdit_->setEnabled(false); // 编辑态名称禁用 addRow(QStringLiteral("名称"), nameEdit_); if (confType_ == kConfGs) { responsibleEdit_ = new QLineEdit(topBox_); responsibleEdit_->setPlaceholderText(QStringLiteral("负责人")); addRow(QStringLiteral("负责人"), responsibleEdit_); } } void ObjectFormDialog::loadGsTypes() { status_->setText(QStringLiteral("加载类型…")); status_->setVisible(true); okBtn_->setEnabled(false); if (typeReq_) typeReq_->abort(); typeReq_ = repo_.queryGsTypesAsync(projectId_.toStdString()); QObject::connect(typeReq_, &geopro::data::NavRequest::done, this, [this](const QVariant& v) { const auto types = qvariant_cast>(v); if (types.empty()) { status_->setText(QStringLiteral("无可用对象类型")); return; } if (typeCombo_) { for (const auto& t : types) typeCombo_->addItem(QString::fromStdString(t.name), QString::fromStdString(t.gsTypeId)); // 触发 currentIndexChanged → 加载首个类型的动态表单。 if (typeCombo_->count() > 0) { typeId_ = typeCombo_->itemData(0).toString(); loadForm(typeId_, QString()); } } }); QObject::connect(typeReq_, &geopro::data::NavRequest::failed, this, [this](const QString& msg) { status_->setText(QStringLiteral("加载类型失败:%1").arg(msg)); status_->setVisible(true); }); } void ObjectFormDialog::loadTmTypes() { status_->setText(QStringLiteral("加载类型…")); status_->setVisible(true); okBtn_->setEnabled(false); if (typeReq_) typeReq_->abort(); typeReq_ = repo_.queryTmMethodTypesAsync(projectId_.toStdString()); QObject::connect(typeReq_, &geopro::data::NavRequest::done, this, [this](const QVariant& v) { const auto types = qvariant_cast>(v); if (types.empty()) { status_->setText(QStringLiteral("无可用方法类型")); return; } if (typeCombo_) { for (const auto& t : types) typeCombo_->addItem(QString::fromStdString(t.label), QString::fromStdString(t.value)); // 触发 currentIndexChanged → 加载首个类型的动态表单(type=2)。 if (typeCombo_->count() > 0) { typeId_ = typeCombo_->itemData(0).toString(); loadForm(typeId_, QString()); } } }); QObject::connect(typeReq_, &geopro::data::NavRequest::failed, this, [this](const QString& msg) { status_->setText(QStringLiteral("加载类型失败:%1").arg(msg)); status_->setVisible(true); }); } void ObjectFormDialog::loadForm(const QString& typeId, const QString& objectId) { status_->setText(QStringLiteral("加载中…")); status_->setVisible(true); okBtn_->setEnabled(false); if (req_) req_->abort(); req_ = repo_.loadEditableFormAsync(typeId.toStdString(), objectId.toStdString(), confType_, projectId_.toStdString()); QObject::connect(req_, &geopro::data::NavRequest::done, this, [this](const QVariant& v) { const auto form = qvariant_cast(v); editor_->setForm(form); status_->setVisible(false); okBtn_->setEnabled(true); }); QObject::connect(req_, &geopro::data::NavRequest::failed, this, [this](const QString& msg) { status_->setText(QStringLiteral("加载失败:%1").arg(msg)); status_->setVisible(true); }); } // 按 spec §B 拼提交体。 QJsonObject ObjectFormDialog::buildBody() const { QJsonObject props; const auto values = editor_->collectValues(); for (auto it = values.constBegin(); it != values.constEnd(); ++it) props.insert(it.key(), it.value()); const bool isCreate = objectId_.isEmpty(); QJsonObject body; body.insert(QStringLiteral("name"), nameEdit_ ? nameEdit_->text() : QString()); body.insert(QStringLiteral("projectId"), projectId_); body.insert(QStringLiteral("properties"), props); if (!isCreate) body.insert(QStringLiteral("id"), objectId_); if (confType_ == kConfGs) { body.insert(QStringLiteral("gsTypeId"), typeId_); body.insert(QStringLiteral("responsiblePersonName"), responsibleEdit_ ? responsibleEdit_->text() : QString()); if (isCreate) body.insert(QStringLiteral("parentId"), parentId_); // GS 编辑无 parentId } else { body.insert(QStringLiteral("tmTypeId"), typeId_); body.insert(QStringLiteral("parentId"), parentId_); // create/edit 同一 spread body.insert(QStringLiteral("parentType"), QStringLiteral("1")); // 恒字符串 "1" } return body; } void ObjectFormDialog::onConfirm() { if (nameEdit_ && nameEdit_->text().trimmed().isEmpty()) { if (nameEdit_->isEnabled()) nameEdit_->setFocus(Qt::OtherFocusReason); // 聚焦名称 QMessageBox::warning(this, QStringLiteral("校验"), QStringLiteral("请填写名称")); return; } QString missing; if (!editor_->validateRequired(&missing)) { editor_->focusFirstInvalid(); // 规范 §7.0.5:聚焦第一个错误字段 QMessageBox::warning(this, QStringLiteral("校验"), QStringLiteral("请填写必填项:%1").arg(missing)); return; } const QJsonObject body = buildBody(); const bool isCreate = objectId_.isEmpty(); okBtn_->setEnabled(false); status_->setText(QStringLiteral("提交中…")); status_->setVisible(true); if (subReq_) subReq_->abort(); subReq_ = repo_.submitObjectAsync( confType_, isCreate, QJsonDocument(body).toJson(QJsonDocument::Compact).toStdString()); QObject::connect(subReq_, &geopro::data::NavRequest::done, this, [this](const QVariant&) { emit submitted(confType_); accept(); }); QObject::connect(subReq_, &geopro::data::NavRequest::failed, this, [this](const QString& msg) { status_->setText(QStringLiteral("提交失败:%1").arg(msg)); status_->setVisible(true); okBtn_->setEnabled(true); }); } } // namespace geopro::app