geopro/src/app/ImportDatasetDialog.cpp

258 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 "ImportDatasetDialog.hpp"
#include <utility>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QFileDialog>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QScrollArea>
#include <QUrl>
#include <QUrlQuery>
#include <QVariantMap>
#include "FormKit.hpp"
#include "Theme.hpp"
#include "api/NavLoads.hpp"
#include "api/NavRequest.hpp"
#include "panels/DynamicFormEditor.hpp"
#include "repo/IAsyncProjectRepository.hpp"
namespace geopro::app {
namespace {
constexpr int kFormTypeScript = 6; // getDynamicForm type=6导入脚本参数
constexpr int kStructConfTm = 2; // structParentConfTypeTM=2
} // namespace
ImportDatasetDialog::ImportDatasetDialog(geopro::data::IAsyncProjectRepository& repo,
QString projectId, QString tmObjectId, QWidget* parent)
: QDialog(parent), repo_(repo), projectId_(std::move(projectId)),
tmObjectId_(std::move(tmObjectId)) {
setModal(true);
setWindowTitle(QStringLiteral("导入数据集"));
setMinimumSize(geopro::app::scaledPx(480), geopro::app::scaledPx(520));
auto* root = formkit::dialogRoot(this);
status_ = new QLabel(QStringLiteral("加载数据类型…"), this);
status_->setAlignment(Qt::AlignCenter);
geopro::app::applyTokenizedStyleSheet(status_,
QStringLiteral("color:{{text/disabled}};padding:12px;"));
root->addWidget(status_);
auto* card = formkit::formCard(this);
auto* cardLay = formkit::cardBody(card);
auto* fl = formkit::makeEditForm();
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);
fileEdit_->setReadOnly(true);
fileEdit_->setPlaceholderText(QStringLiteral("选择导入文件…"));
auto* browse = new QPushButton(QStringLiteral("浏览…"), fileRow);
fileLay->addWidget(fileEdit_, 1);
fileLay->addWidget(browse);
fl->addRow(formkit::editLabel(QStringLiteral("文件")), fileRow);
cardLay->addLayout(fl);
formkit::addSection(cardLay, QStringLiteral("脚本参数"), card, true);
auto* scroll = new QScrollArea(card);
scroll->setWidgetResizable(true);
scroll->setFrameShape(QFrame::NoFrame);
paramEditor_ = new DynamicFormEditor();
scroll->setWidget(paramEditor_);
cardLay->addWidget(scroll, 1);
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);
QObject::connect(browse, &QPushButton::clicked, this, &ImportDatasetDialog::chooseFile);
QObject::connect(okBtn_, &QPushButton::clicked, this, &ImportDatasetDialog::onConfirm);
QObject::connect(typeCombo_, qOverload<int>(&QComboBox::currentIndexChanged), this,
[this](int) { onTypeChanged(); });
QObject::connect(scriptCombo_, qOverload<int>(&QComboBox::currentIndexChanged), this,
[this](int) { onScriptChanged(); });
loadTypes();
}
ImportDatasetDialog::~ImportDatasetDialog() {
// 析构时取消在途请求,避免回调命中已销毁的 this。
if (typeReq_) typeReq_->abort();
if (scriptReq_) scriptReq_->abort();
if (formReq_) formReq_->abort();
if (checkReq_) checkReq_->abort();
if (importReq_) importReq_->abort();
}
void ImportDatasetDialog::loadTypes() {
if (typeReq_) typeReq_->abort();
typeReq_ = repo_.loadTmImportTypesAsync(tmObjectId_.toStdString());
QObject::connect(typeReq_, &geopro::data::NavRequest::done, this, [this](const QVariant& v) {
types_ = qvariant_cast<std::vector<geopro::data::DsImportType>>(v);
if (types_.empty()) {
status_->setText(QStringLiteral("该方法对象下无可导入的数据类型"));
return;
}
status_->setVisible(false);
for (const auto& t : types_)
typeCombo_->addItem(QString::fromStdString(t.name),
QString::fromStdString(t.dsTypeId));
});
QObject::connect(typeReq_, &geopro::data::NavRequest::failed, this, [this](const QString& msg) {
status_->setText(QStringLiteral("加载数据类型失败:%1").arg(msg));
});
}
void ImportDatasetDialog::onTypeChanged() {
scriptCombo_->clear();
scripts_.clear();
const int idx = typeCombo_->currentIndex();
if (idx < 0 || idx >= static_cast<int>(types_.size())) return;
const QString dsTypeId = typeCombo_->currentData().toString();
if (scriptReq_) scriptReq_->abort();
// tmTypeBaseConfId暂用 TM 对象 id 占位query/script 需方法基础配置 id。TODO来源待精确。
scriptReq_ = repo_.queryImportScriptsAsync(dsTypeId.toStdString(), tmObjectId_.toStdString());
QObject::connect(scriptReq_, &geopro::data::NavRequest::done, this, [this](const QVariant& v) {
scripts_ = qvariant_cast<std::vector<geopro::data::ScriptOption>>(v);
for (const auto& s : scripts_) {
// itemData 同时携带 scriptId/scriptCode取值不再依赖 scripts_ 同序索引。
QVariantMap d{{QStringLiteral("scriptId"), QString::fromStdString(s.scriptId)},
{QStringLiteral("scriptCode"), QString::fromStdString(s.scriptCode)}};
scriptCombo_->addItem(QString::fromStdString(s.name), d);
}
if (scripts_.empty()) {
paramEditor_->setForm({}); // 无脚本:清空参数区
okBtn_->setEnabled(!filePath_.isEmpty());
}
});
QObject::connect(scriptReq_, &geopro::data::NavRequest::failed, this, [this](const QString&) {
// 脚本可选:失败不阻塞导入(部分类型无脚本)。
okBtn_->setEnabled(!filePath_.isEmpty());
});
}
void ImportDatasetDialog::onScriptChanged() {
const int idx = scriptCombo_->currentIndex();
if (idx < 0) return;
const QString scriptId = scriptCombo_->currentData().toMap()
.value(QStringLiteral("scriptId")).toString();
if (scriptId.isEmpty()) return;
if (formReq_) formReq_->abort();
// 脚本参数表单getDynamicForm(typeId=scriptId, type=6)。
formReq_ = repo_.loadEditableFormAsync(scriptId.toStdString(), std::string(), kFormTypeScript,
projectId_.toStdString());
QObject::connect(formReq_, &geopro::data::NavRequest::done, this, [this](const QVariant& v) {
paramEditor_->setForm(qvariant_cast<geopro::data::EditableForm>(v));
});
QObject::connect(formReq_, &geopro::data::NavRequest::failed, this, [this](const QString& msg) {
// 参数表单加载失败:清空参数区,避免脏参数随导入提交。
paramEditor_->setForm({});
status_->setText(QStringLiteral("加载脚本参数失败:%1").arg(msg));
});
}
void ImportDatasetDialog::chooseFile() {
const QString p = QFileDialog::getOpenFileName(this, QStringLiteral("选择导入文件"));
if (p.isEmpty()) return;
filePath_ = p;
fileEdit_->setText(p);
okBtn_->setEnabled(true);
}
void ImportDatasetDialog::onConfirm() {
if (filePath_.isEmpty()) {
QMessageBox::warning(this, QStringLiteral("校验"), QStringLiteral("请选择导入文件"));
return;
}
QString missing;
if (!paramEditor_->validateRequired(&missing)) {
QMessageBox::warning(this, QStringLiteral("校验"),
QStringLiteral("请填写脚本参数:%1").arg(missing));
return;
}
const QString dsTypeId = typeCombo_->currentData().toString();
const QString scriptCode = scriptCombo_->currentData().toMap()
.value(QStringLiteral("scriptCode")).toString();
// 脚本参数 → scriptParamListJsonStr{fieldCode: 值} 序列化)。
// TODO原版 scriptParamListJsonStr 确切结构未捕获,此处用 properties 形态,失败时按服务端 msg 校正。
QJsonObject paramObj;
const auto params = paramEditor_->collectValues();
for (auto it = params.constBegin(); it != params.constEnd(); ++it)
paramObj.insert(it.key(), it.value());
const QString scriptParamJson =
QString::fromUtf8(QJsonDocument(paramObj).toJson(QJsonDocument::Compact));
// import 参数走 query stringspec §B
QUrlQuery q;
q.addQueryItem(QStringLiteral("dsTypeId"), dsTypeId);
q.addQueryItem(QStringLiteral("projectId"), projectId_);
q.addQueryItem(QStringLiteral("structParentConfType"), QString::number(kStructConfTm));
q.addQueryItem(QStringLiteral("structParentId"), tmObjectId_);
if (!scriptCode.isEmpty()) q.addQueryItem(QStringLiteral("scriptCode"), scriptCode);
if (!params.isEmpty()) q.addQueryItem(QStringLiteral("scriptParamListJsonStr"), scriptParamJson);
const QString queryString = q.query(QUrl::FullyEncoded);
// 先 checkImport 校验(坐标/轨迹文件存在性等),通过后 import。
QJsonObject checkBody{{QStringLiteral("dsTypeId"), dsTypeId},
{QStringLiteral("projectId"), projectId_},
{QStringLiteral("structParentConfType"), kStructConfTm},
{QStringLiteral("structParentId"), tmObjectId_}};
okBtn_->setEnabled(false);
status_->setText(QStringLiteral("校验中…"));
status_->setVisible(true);
if (checkReq_) checkReq_->abort();
checkReq_ = repo_.checkImportAsync(
QJsonDocument(checkBody).toJson(QJsonDocument::Compact).toStdString());
QObject::connect(checkReq_, &geopro::data::NavRequest::done, this,
[this, queryString](const QVariant&) {
status_->setText(QStringLiteral("导入中…"));
if (importReq_) importReq_->abort();
importReq_ = repo_.importDatasetAsync(queryString.toStdString(),
filePath_.toStdString());
QObject::connect(importReq_, &geopro::data::NavRequest::done, this,
[this](const QVariant&) {
emit imported();
accept();
});
QObject::connect(importReq_, &geopro::data::NavRequest::failed, this,
[this](const QString& msg) {
status_->setText(QStringLiteral("导入失败:%1").arg(msg));
okBtn_->setEnabled(true);
});
});
QObject::connect(checkReq_, &geopro::data::NavRequest::failed, this, [this](const QString& msg) {
status_->setText(QStringLiteral("校验失败:%1").arg(msg));
okBtn_->setEnabled(true);
});
}
} // namespace geopro::app