geopro/src/app/ObjectFormDialog.cpp

313 lines
12 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "ObjectFormDialog.hpp"
#include <utility>
#include <QComboBox>
#include "EmptyAwareComboBox.hpp"
#include <QFormLayout>
#include <QHBoxLayout>
#include <QJsonDocument>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QScrollArea>
#include <QVBoxLayout>
#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/根 idGS 编辑忽略)
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<int>(&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<std::vector<geopro::data::GsTypeOption>>(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<std::vector<geopro::data::TmTypeOption>>(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<geopro::data::EditableForm>(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