259 lines
12 KiB
C++
259 lines
12 KiB
C++
#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; // structParentConfType:TM=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_ = formkit::comboBox(QStringLiteral("请选择数据类型"), card);
|
||
formkit::capField(typeCombo_);
|
||
fl->addRow(formkit::editLabel(QStringLiteral("数据类型")), typeCombo_);
|
||
scriptCombo_ = formkit::comboBox(QStringLiteral("请选择导入脚本"), 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 string(spec §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
|