313 lines
12 KiB
C++
313 lines
12 KiB
C++
#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/根 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<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
|