refactor(net+data+app): ProjectListDialog 异步化 + 删同步 IProjectRepository/RepoResult/ApiClient.get|postJson(清除过渡技术债,全 App 网络层异步)
- ProjectListDialog 迁到 IAsyncProjectRepository:fillTypeFilter/query 改 abort-and-replace + 身份比对 + done/failed 双分支;析构 abort 在飞请求(退出契约) - main.cpp buildWorkbench 形参改 IAsyncProjectRepository& - ApiProjectRepository 删 public IProjectRepository 与 9 个同步方法实现;删不再用的 ok()/errorOf() helper - 删除 src/data/repo/IProjectRepository.hpp(含 RepoResult,已无消费者) - ApiClient 删同步 get/postJson + Impl::await + <QEventLoop>/ApiResponseParse.hpp include
This commit is contained in:
parent
93462d78ef
commit
5f00cdce7a
|
|
@ -15,6 +15,8 @@
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
#include "Theme.hpp"
|
#include "Theme.hpp"
|
||||||
|
#include "api/NavLoads.hpp"
|
||||||
|
#include "api/NavRequest.hpp"
|
||||||
|
|
||||||
|
|
||||||
namespace geopro::app {
|
namespace geopro::app {
|
||||||
|
|
@ -37,7 +39,7 @@ QColor statusColor(int s) {
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
ProjectListDialog::ProjectListDialog(data::IProjectRepository& repo, QWidget* parent)
|
ProjectListDialog::ProjectListDialog(data::IAsyncProjectRepository& repo, QWidget* parent)
|
||||||
: QDialog(parent), repo_(repo) {
|
: QDialog(parent), repo_(repo) {
|
||||||
setWindowTitle(QStringLiteral("全部项目"));
|
setWindowTitle(QStringLiteral("全部项目"));
|
||||||
resize(980, 560);
|
resize(980, 560);
|
||||||
|
|
@ -125,57 +127,85 @@ ProjectListDialog::ProjectListDialog(data::IProjectRepository& repo, QWidget* pa
|
||||||
query();
|
query();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ProjectListDialog::~ProjectListDialog() {
|
||||||
|
// 退出契约:模态 exec 关窗后本对话框析构 → abort 在飞请求,防回调打到已析构窗口。
|
||||||
|
if (typesReq_) typesReq_->abort();
|
||||||
|
if (queryReq_) queryReq_->abort();
|
||||||
|
}
|
||||||
|
|
||||||
void ProjectListDialog::fillTypeFilter() {
|
void ProjectListDialog::fillTypeFilter() {
|
||||||
typeCombo_->addItem(QStringLiteral("全部类型"), QString());
|
typeCombo_->addItem(QStringLiteral("全部类型"), QString());
|
||||||
const auto r = repo_.listProjectTypes();
|
// abort-and-replace:最新一次过滤填充为准。
|
||||||
if (!r.ok) return;
|
if (typesReq_) typesReq_->abort();
|
||||||
for (const auto& t : r.value)
|
auto* r = repo_.listProjectTypesAsync();
|
||||||
typeCombo_->addItem(QString::fromStdString(t.name), QString::fromStdString(t.id));
|
typesReq_ = r;
|
||||||
|
QObject::connect(r, &data::NavRequest::done, this, [this, r](const QVariant& v) {
|
||||||
|
if (r != typesReq_) return; // 身份比对:丢弃迟到/被替换信号
|
||||||
|
typesReq_.clear();
|
||||||
|
const auto types = qvariant_cast<std::vector<data::ProjectType>>(v);
|
||||||
|
for (const auto& t : types)
|
||||||
|
typeCombo_->addItem(QString::fromStdString(t.name), QString::fromStdString(t.id));
|
||||||
|
});
|
||||||
|
QObject::connect(r, &data::NavRequest::failed, this, [this, r](const QString&) {
|
||||||
|
if (r != typesReq_) return;
|
||||||
|
typesReq_.clear();
|
||||||
|
// 失败时仅保留“全部类型”项(与原同步版失败仅 return 一致)。
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProjectListDialog::query() {
|
void ProjectListDialog::query() {
|
||||||
const std::string name = nameEdit_->text().trimmed().toStdString();
|
const std::string name = nameEdit_->text().trimmed().toStdString();
|
||||||
const std::string typeId = typeCombo_->currentData().toString().toStdString();
|
const std::string typeId = typeCombo_->currentData().toString().toStdString();
|
||||||
const auto r = repo_.pageProjects(name, typeId, pageNo_, pageSize_);
|
// abort-and-replace:丢弃上一查询,仅最新结果落表。
|
||||||
if (!r.ok) {
|
if (queryReq_) queryReq_->abort();
|
||||||
|
auto* r = repo_.pageProjectsAsync(name, typeId, pageNo_, pageSize_);
|
||||||
|
queryReq_ = r;
|
||||||
|
QObject::connect(r, &data::NavRequest::done, this, [this, r](const QVariant& v) {
|
||||||
|
if (r != queryReq_) return; // 身份比对
|
||||||
|
queryReq_.clear();
|
||||||
|
const auto page = qvariant_cast<data::ProjectListPage>(v);
|
||||||
|
total_ = page.total;
|
||||||
|
const auto& rows = page.rows;
|
||||||
|
table_->setRowCount(static_cast<int>(rows.size()));
|
||||||
|
for (int i = 0; i < static_cast<int>(rows.size()); ++i) {
|
||||||
|
const auto& p = rows[i];
|
||||||
|
auto set = [&](int col, const QString& text) {
|
||||||
|
table_->setItem(i, col, new QTableWidgetItem(text));
|
||||||
|
};
|
||||||
|
set(0, QString::number((pageNo_ - 1) * pageSize_ + i + 1));
|
||||||
|
auto* nameItem = new QTableWidgetItem(QString::fromStdString(p.name));
|
||||||
|
nameItem->setData(Qt::UserRole, QString::fromStdString(p.id));
|
||||||
|
nameItem->setForeground(tokenColor("accent/primary"));
|
||||||
|
table_->setItem(i, 1, nameItem);
|
||||||
|
set(2, QString::fromStdString(p.code));
|
||||||
|
// 状态列语义着色:颜色承载“未开始/进行中”分类,进行中加粗强调(不只靠颜色)。
|
||||||
|
auto* statusItem = new QTableWidgetItem(statusText(p.status));
|
||||||
|
statusItem->setForeground(statusColor(p.status));
|
||||||
|
if (p.status == 2) {
|
||||||
|
QFont f = statusItem->font();
|
||||||
|
f.setBold(true);
|
||||||
|
statusItem->setFont(f);
|
||||||
|
}
|
||||||
|
table_->setItem(i, 3, statusItem);
|
||||||
|
set(4, QString::fromStdString(p.typeName));
|
||||||
|
set(5, QString::fromStdString(p.ownerCompany));
|
||||||
|
set(6, QString::fromStdString(p.responsiblePerson));
|
||||||
|
set(7, QString::fromStdString(p.createTime));
|
||||||
|
}
|
||||||
|
const int pages = total_ > 0 ? (total_ + pageSize_ - 1) / pageSize_ : 1;
|
||||||
|
pageLabel_->setText(
|
||||||
|
QStringLiteral("共 %1 条 第 %2 / %3 页").arg(total_).arg(pageNo_).arg(pages));
|
||||||
|
prevBtn_->setEnabled(pageNo_ > 1);
|
||||||
|
nextBtn_->setEnabled(pageNo_ < pages);
|
||||||
|
});
|
||||||
|
QObject::connect(r, &data::NavRequest::failed, this, [this, r](const QString& msg) {
|
||||||
|
if (r != queryReq_) return;
|
||||||
|
queryReq_.clear();
|
||||||
table_->setRowCount(0);
|
table_->setRowCount(0);
|
||||||
pageLabel_->setText(QStringLiteral("加载失败:%1").arg(QString::fromStdString(r.error)));
|
pageLabel_->setText(QStringLiteral("加载失败:%1").arg(msg));
|
||||||
prevBtn_->setEnabled(false);
|
prevBtn_->setEnabled(false);
|
||||||
nextBtn_->setEnabled(false);
|
nextBtn_->setEnabled(false);
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
total_ = r.value.total;
|
|
||||||
const auto& rows = r.value.rows;
|
|
||||||
table_->setRowCount(static_cast<int>(rows.size()));
|
|
||||||
for (int i = 0; i < static_cast<int>(rows.size()); ++i) {
|
|
||||||
const auto& p = rows[i];
|
|
||||||
auto set = [&](int col, const QString& text) {
|
|
||||||
table_->setItem(i, col, new QTableWidgetItem(text));
|
|
||||||
};
|
|
||||||
set(0, QString::number((pageNo_ - 1) * pageSize_ + i + 1));
|
|
||||||
auto* nameItem = new QTableWidgetItem(QString::fromStdString(p.name));
|
|
||||||
nameItem->setData(Qt::UserRole, QString::fromStdString(p.id));
|
|
||||||
nameItem->setForeground(tokenColor("accent/primary"));
|
|
||||||
table_->setItem(i, 1, nameItem);
|
|
||||||
set(2, QString::fromStdString(p.code));
|
|
||||||
// 状态列语义着色:颜色承载“未开始/进行中”分类,进行中加粗强调(不只靠颜色)。
|
|
||||||
auto* statusItem = new QTableWidgetItem(statusText(p.status));
|
|
||||||
statusItem->setForeground(statusColor(p.status));
|
|
||||||
if (p.status == 2) {
|
|
||||||
QFont f = statusItem->font();
|
|
||||||
f.setBold(true);
|
|
||||||
statusItem->setFont(f);
|
|
||||||
}
|
|
||||||
table_->setItem(i, 3, statusItem);
|
|
||||||
set(4, QString::fromStdString(p.typeName));
|
|
||||||
set(5, QString::fromStdString(p.ownerCompany));
|
|
||||||
set(6, QString::fromStdString(p.responsiblePerson));
|
|
||||||
set(7, QString::fromStdString(p.createTime));
|
|
||||||
}
|
|
||||||
const int pages = total_ > 0 ? (total_ + pageSize_ - 1) / pageSize_ : 1;
|
|
||||||
pageLabel_->setText(QStringLiteral("共 %1 条 第 %2 / %3 页").arg(total_).arg(pageNo_).arg(pages));
|
|
||||||
prevBtn_->setEnabled(pageNo_ > 1);
|
|
||||||
nextBtn_->setEnabled(pageNo_ < pages);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace geopro::app
|
} // namespace geopro::app
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
|
#include <QPointer>
|
||||||
|
|
||||||
#include "repo/IProjectRepository.hpp"
|
#include "repo/IAsyncProjectRepository.hpp"
|
||||||
|
|
||||||
class QLineEdit;
|
class QLineEdit;
|
||||||
class QComboBox;
|
class QComboBox;
|
||||||
|
|
@ -9,13 +10,16 @@ class QTableWidget;
|
||||||
class QLabel;
|
class QLabel;
|
||||||
class QPushButton;
|
class QPushButton;
|
||||||
|
|
||||||
|
namespace geopro::data { class NavRequest; }
|
||||||
|
|
||||||
namespace geopro::app {
|
namespace geopro::app {
|
||||||
|
|
||||||
// 项目列表弹窗:名称/类型过滤 + 分页表格;点项目名 → 切换项目并关闭。
|
// 项目列表弹窗:名称/类型过滤 + 分页表格;点项目名 → 切换项目并关闭。
|
||||||
class ProjectListDialog : public QDialog {
|
class ProjectListDialog : public QDialog {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit ProjectListDialog(data::IProjectRepository& repo, QWidget* parent = nullptr);
|
explicit ProjectListDialog(data::IAsyncProjectRepository& repo, QWidget* parent = nullptr);
|
||||||
|
~ProjectListDialog() override;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void projectChosen(const QString& projectId, const QString& projectName);
|
void projectChosen(const QString& projectId, const QString& projectName);
|
||||||
|
|
@ -24,7 +28,9 @@ private:
|
||||||
void query();
|
void query();
|
||||||
void fillTypeFilter();
|
void fillTypeFilter();
|
||||||
|
|
||||||
data::IProjectRepository& repo_;
|
data::IAsyncProjectRepository& repo_;
|
||||||
|
QPointer<data::NavRequest> typesReq_;
|
||||||
|
QPointer<data::NavRequest> queryReq_;
|
||||||
QLineEdit* nameEdit_ = nullptr;
|
QLineEdit* nameEdit_ = nullptr;
|
||||||
QComboBox* typeCombo_ = nullptr;
|
QComboBox* typeCombo_ = nullptr;
|
||||||
QTableWidget* table_ = nullptr;
|
QTableWidget* table_ = nullptr;
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,7 @@ constexpr const char* kWgs84 = "EPSG:4326";
|
||||||
// 在给定 QMainWindow 上构建 M1 工作台。
|
// 在给定 QMainWindow 上构建 M1 工作台。
|
||||||
// repo 生命周期须覆盖到事件循环结束(由调用方保证)。
|
// repo 生命周期须覆盖到事件循环结束(由调用方保证)。
|
||||||
void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& repo,
|
void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& repo,
|
||||||
geopro::data::IProjectRepository& projectRepo,
|
geopro::data::IAsyncProjectRepository& projectRepo,
|
||||||
geopro::controller::WorkbenchNavController& nav,
|
geopro::controller::WorkbenchNavController& nav,
|
||||||
geopro::controller::DatasetDetailController& detailCtrl)
|
geopro::controller::DatasetDetailController& detailCtrl)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Repository 抽象(**异步契约**:QFuture/回调 + 取消 + 分页),DTO 与领域模型分离。
|
Repository 抽象(**异步契约**:QFuture/回调 + 取消 + 分页),DTO 与领域模型分离。
|
||||||
|
|
||||||
子目录(设计 §3、§6):
|
子目录(设计 §3、§6):
|
||||||
- `repo/` — IProjectRepository, IDatasetRepository
|
- `repo/` — IAsyncProjectRepository, IDatasetRepository
|
||||||
- `local/` — LocalSampleRepository(M1,QtConcurrent 跑解析)+ 各格式解析器
|
- `local/` — LocalSampleRepository(M1,QtConcurrent 跑解析)+ 各格式解析器
|
||||||
- `api/` — ApiRepository(M1 骨架,签名对齐 pop-api)
|
- `api/` — ApiRepository(M1 骨架,签名对齐 pop-api)
|
||||||
- `dto/` — 后端 JSON DTO + → model 映射
|
- `dto/` — 后端 JSON DTO + → model 映射
|
||||||
|
|
|
||||||
|
|
@ -15,17 +15,9 @@ namespace geopro::data {
|
||||||
namespace {
|
namespace {
|
||||||
constexpr int kCodeSuccess = 200;
|
constexpr int kCodeSuccess = 200;
|
||||||
|
|
||||||
bool ok(const net::ApiResponse& r) { return r.code == kCodeSuccess; }
|
// 异步失败谓词:业务码非 200 或网络错误。
|
||||||
|
|
||||||
// 异步失败谓词(与同步 ok() 同口径:业务码非 200 或网络错误)。
|
|
||||||
bool isFailureA(const net::ApiResponse& r) { return r.code != kCodeSuccess || !r.rawError.isEmpty(); }
|
bool isFailureA(const net::ApiResponse& r) { return r.code != kCodeSuccess || !r.rawError.isEmpty(); }
|
||||||
|
|
||||||
std::string errorOf(const net::ApiResponse& r, const char* fallback) {
|
|
||||||
if (!r.msg.isEmpty()) return r.msg.toStdString();
|
|
||||||
if (!r.rawError.isEmpty()) return r.rawError.toStdString();
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 后端 id 进 URL 前做百分号编码(不可信外部数据:防 ? # & / 空格 破坏路径/查询)。
|
// 后端 id 进 URL 前做百分号编码(不可信外部数据:防 ? # & / 空格 破坏路径/查询)。
|
||||||
QString enc(const std::string& s) {
|
QString enc(const std::string& s) {
|
||||||
return QString::fromUtf8(QUrl::toPercentEncoding(QString::fromStdString(s)));
|
return QString::fromUtf8(QUrl::toPercentEncoding(QString::fromStdString(s)));
|
||||||
|
|
@ -34,97 +26,7 @@ QString enc(const std::string& s) {
|
||||||
|
|
||||||
ApiProjectRepository::ApiProjectRepository(net::ApiClient& api) : api_(api) {}
|
ApiProjectRepository::ApiProjectRepository(net::ApiClient& api) : api_(api) {}
|
||||||
|
|
||||||
RepoResult<std::vector<Workspace>> ApiProjectRepository::listWorkspaces() {
|
// ── 异步实现(薄封装:解析器在 try 内见 ApiNavRequest)──
|
||||||
const net::ApiResponse r =
|
|
||||||
api_.get(QStringLiteral("/business/system/tenant/enterprise/joined/list"));
|
|
||||||
if (!ok(r)) return {false, {}, errorOf(r, "listWorkspaces failed")};
|
|
||||||
return {true, dto::parseWorkspaces(r.data.value(QStringLiteral("value")).toArray()), {}};
|
|
||||||
}
|
|
||||||
|
|
||||||
RepoResult<bool> ApiProjectRepository::switchWorkspace(const std::string& tenantId) {
|
|
||||||
const QString path =
|
|
||||||
QStringLiteral("/business/system/tenant/enterprise/switch/%1").arg(enc(tenantId));
|
|
||||||
const net::ApiResponse r = api_.postJson(path, QJsonObject{});
|
|
||||||
if (!ok(r)) return {false, false, errorOf(r, "switchWorkspace failed")};
|
|
||||||
// 切换空间返回新 accessToken:必须重新注入,后续请求才落到新空间。
|
|
||||||
const QString token = r.data.value(QStringLiteral("accessToken")).toString();
|
|
||||||
if (!token.isEmpty()) api_.setToken(token);
|
|
||||||
return {true, true, {}};
|
|
||||||
}
|
|
||||||
|
|
||||||
RepoResult<ProjectListPage> ApiProjectRepository::pageProjects(const std::string& nameFilter,
|
|
||||||
const std::string& typeId, int pageNo,
|
|
||||||
int pageSize) {
|
|
||||||
QJsonObject body{{QStringLiteral("projectName"), QString::fromStdString(nameFilter)},
|
|
||||||
{QStringLiteral("pageNo"), pageNo},
|
|
||||||
{QStringLiteral("pageSize"), pageSize}};
|
|
||||||
if (!typeId.empty()) body[QStringLiteral("projectTypeId")] = QString::fromStdString(typeId);
|
|
||||||
const net::ApiResponse r = api_.postJson(QStringLiteral("/business/my/profile/project/page"), body);
|
|
||||||
if (!ok(r)) return {false, {}, errorOf(r, "pageProjects failed")};
|
|
||||||
return {true, dto::parseProjectPage(r.data), {}};
|
|
||||||
}
|
|
||||||
|
|
||||||
RepoResult<std::vector<ProjectType>> ApiProjectRepository::listProjectTypes() {
|
|
||||||
const net::ApiResponse r = api_.get(QStringLiteral("/business/project/type/list"));
|
|
||||||
if (!ok(r)) return {false, {}, errorOf(r, "listProjectTypes failed")};
|
|
||||||
return {true, dto::parseProjectTypes(r.data.value(QStringLiteral("value")).toArray()), {}};
|
|
||||||
}
|
|
||||||
|
|
||||||
RepoResult<std::vector<StructNode>> ApiProjectRepository::loadStructure(const std::string& projectId) {
|
|
||||||
// 项目结构(项目根 + GS + TM;不含 DS)。比 projectWorkbench 干净。
|
|
||||||
const QString path =
|
|
||||||
QStringLiteral("/business/projectStruct/queryProjectStruct/%1").arg(enc(projectId));
|
|
||||||
const net::ApiResponse r = api_.get(path);
|
|
||||||
if (!ok(r)) return {false, {}, errorOf(r, "loadStructure failed")};
|
|
||||||
return {true, dto::parseStructNodes(r.data.value(QStringLiteral("value")).toArray()), {}};
|
|
||||||
}
|
|
||||||
|
|
||||||
RepoResult<DsPage> ApiProjectRepository::loadRows(const std::string& projectId,
|
|
||||||
const std::string& parentId, int parentConfType,
|
|
||||||
int classifyType, int pageNo) {
|
|
||||||
const QString path = (classifyType == 1) ? QStringLiteral("/business/dsObject/file/page")
|
|
||||||
: QStringLiteral("/business/dsObject/data/page");
|
|
||||||
const QJsonObject body{
|
|
||||||
{QStringLiteral("projectId"), QString::fromStdString(projectId)},
|
|
||||||
{QStringLiteral("structParentId"), QString::fromStdString(parentId)},
|
|
||||||
{QStringLiteral("structParentConfType"), parentConfType},
|
|
||||||
{QStringLiteral("classifyTypeList"), QJsonArray{classifyType}},
|
|
||||||
{QStringLiteral("pageNo"), pageNo},
|
|
||||||
{QStringLiteral("pageSize"), 5}};
|
|
||||||
const net::ApiResponse r = api_.postJson(path, body);
|
|
||||||
if (!ok(r)) return {false, {}, errorOf(r, "loadRows failed")};
|
|
||||||
return {true, dto::parseDsPage(r.data), {}};
|
|
||||||
}
|
|
||||||
|
|
||||||
RepoResult<DynamicForm> ApiProjectRepository::loadObjectDetail(const std::string& objectId,
|
|
||||||
int confType) {
|
|
||||||
const QString path =
|
|
||||||
(confType == 1)
|
|
||||||
? QStringLiteral("/business/gsObject/getGsObjectDetail/%1").arg(enc(objectId))
|
|
||||||
: QStringLiteral("/business/tmObject/getDetail/%1").arg(enc(objectId));
|
|
||||||
const net::ApiResponse r = api_.get(path);
|
|
||||||
if (!ok(r)) return {false, {}, errorOf(r, "loadObjectDetail failed")};
|
|
||||||
return {true, dto::parseDynamicForm(r.data), {}};
|
|
||||||
}
|
|
||||||
|
|
||||||
RepoResult<DynamicForm> ApiProjectRepository::loadDatasetForm(const std::string& dsObjectId) {
|
|
||||||
const QString path =
|
|
||||||
QStringLiteral("/business/dsObject/dynamicForm/%1").arg(enc(dsObjectId));
|
|
||||||
const net::ApiResponse r = api_.get(path);
|
|
||||||
if (!ok(r)) return {false, {}, errorOf(r, "loadDatasetForm failed")};
|
|
||||||
return {true, dto::parseDynamicForm(r.data), {}};
|
|
||||||
}
|
|
||||||
|
|
||||||
RepoResult<std::vector<ExceptionRow>> ApiProjectRepository::loadExceptionsByTm(
|
|
||||||
const std::string& tmObjectId) {
|
|
||||||
const QString path =
|
|
||||||
QStringLiteral("/business/exception/queryExceptionByTmObjectId/%1").arg(enc(tmObjectId));
|
|
||||||
const net::ApiResponse r = api_.get(path);
|
|
||||||
if (!ok(r)) return {false, {}, errorOf(r, "loadExceptionsByTm failed")};
|
|
||||||
return {true, dto::parseExceptions(r.data.value(QStringLiteral("value")).toArray()), {}};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── 异步实现(薄封装:endpoint/body/parse 与同步版逐一对齐;解析器在 try 内见 ApiNavRequest)──
|
|
||||||
|
|
||||||
NavRequest* ApiProjectRepository::listWorkspacesAsync() {
|
NavRequest* ApiProjectRepository::listWorkspacesAsync() {
|
||||||
auto* call = api_.getAsync(QStringLiteral("/business/system/tenant/enterprise/joined/list"));
|
auto* call = api_.getAsync(QStringLiteral("/business/system/tenant/enterprise/joined/list"));
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "repo/IProjectRepository.hpp"
|
|
||||||
#include "repo/IAsyncProjectRepository.hpp"
|
#include "repo/IAsyncProjectRepository.hpp"
|
||||||
|
|
||||||
namespace geopro::net { class ApiClient; }
|
namespace geopro::net { class ApiClient; }
|
||||||
|
|
@ -8,26 +7,12 @@ namespace geopro::data {
|
||||||
|
|
||||||
class NavRequest;
|
class NavRequest;
|
||||||
|
|
||||||
// 用共享会话 ApiClient 实现导航仓储。过渡期同时实现同步(旧)+异步(新)两接口。
|
// 用共享会话 ApiClient 实现导航异步仓储。token 由调用方注入 ApiClient。
|
||||||
// token 由调用方注入 ApiClient。
|
class ApiProjectRepository : public IAsyncProjectRepository {
|
||||||
class ApiProjectRepository : public IProjectRepository, public IAsyncProjectRepository {
|
|
||||||
public:
|
public:
|
||||||
explicit ApiProjectRepository(net::ApiClient& api);
|
explicit ApiProjectRepository(net::ApiClient& api);
|
||||||
|
|
||||||
// ── 同步(旧,A6 删除) ──
|
// ── 异步 ── 返回 NavRequest*(薄封装,一方法一请求)。
|
||||||
RepoResult<std::vector<Workspace>> listWorkspaces() override;
|
|
||||||
RepoResult<bool> switchWorkspace(const std::string& tenantId) override;
|
|
||||||
RepoResult<ProjectListPage> pageProjects(const std::string& nameFilter, const std::string& typeId,
|
|
||||||
int pageNo, int pageSize) override;
|
|
||||||
RepoResult<std::vector<ProjectType>> listProjectTypes() override;
|
|
||||||
RepoResult<std::vector<StructNode>> loadStructure(const std::string& projectId) override;
|
|
||||||
RepoResult<DsPage> loadRows(const std::string& projectId, const std::string& parentId,
|
|
||||||
int parentConfType, int classifyType, int pageNo) override;
|
|
||||||
RepoResult<DynamicForm> loadObjectDetail(const std::string& objectId, int confType) override;
|
|
||||||
RepoResult<DynamicForm> loadDatasetForm(const std::string& dsObjectId) override;
|
|
||||||
RepoResult<std::vector<ExceptionRow>> loadExceptionsByTm(const std::string& tmObjectId) override;
|
|
||||||
|
|
||||||
// ── 异步(新) ── 返回 NavRequest*(薄封装,一方法一请求)。
|
|
||||||
NavRequest* listWorkspacesAsync() override;
|
NavRequest* listWorkspacesAsync() override;
|
||||||
NavRequest* switchWorkspaceAsync(const std::string& tenantId) override;
|
NavRequest* switchWorkspaceAsync(const std::string& tenantId) override;
|
||||||
NavRequest* pageProjectsAsync(const std::string& nameFilter, const std::string& typeId,
|
NavRequest* pageProjectsAsync(const std::string& nameFilter, const std::string& typeId,
|
||||||
|
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
#include "repo/RepoTypes.hpp"
|
|
||||||
|
|
||||||
namespace geopro::data {
|
|
||||||
|
|
||||||
// 仓储结果信封:网络可失败,故用显式 Result 而非抛异常,便于 UI 出错误/空状态。
|
|
||||||
template <class T>
|
|
||||||
struct RepoResult {
|
|
||||||
bool ok = false;
|
|
||||||
T value{};
|
|
||||||
std::string error;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 导航仓储抽象(同步;呼应既有 IDatasetRepository 风格)。
|
|
||||||
class IProjectRepository {
|
|
||||||
public:
|
|
||||||
virtual ~IProjectRepository() = default;
|
|
||||||
virtual RepoResult<std::vector<Workspace>> listWorkspaces() = 0;
|
|
||||||
virtual RepoResult<bool> switchWorkspace(const std::string& tenantId) = 0;
|
|
||||||
// 项目分页:nameFilter 名称模糊(可空)、typeId 类型过滤(空=不限)、pageNo 从 1 起。
|
|
||||||
virtual RepoResult<ProjectListPage> pageProjects(const std::string& nameFilter,
|
|
||||||
const std::string& typeId, int pageNo,
|
|
||||||
int pageSize) = 0;
|
|
||||||
// 项目类型列表(弹窗类型过滤下拉)。
|
|
||||||
virtual RepoResult<std::vector<ProjectType>> listProjectTypes() = 0;
|
|
||||||
virtual RepoResult<std::vector<StructNode>> loadStructure(const std::string& projectId) = 0;
|
|
||||||
// 按结构父节点分页拉数据/文件行:parentConfType 1=GS 2=TM;classifyType 3=数据 1=文件;
|
|
||||||
// pageNo 从 1 起,pageSize 固定 5。
|
|
||||||
virtual RepoResult<DsPage> loadRows(const std::string& projectId, const std::string& parentId,
|
|
||||||
int parentConfType, int classifyType, int pageNo) = 0;
|
|
||||||
// 对象详情:confType 1=GS(getGsObjectDetail) 2=TM(tmObject/getDetail) → 动态表单。
|
|
||||||
virtual RepoResult<DynamicForm> loadObjectDetail(const std::string& objectId, int confType) = 0;
|
|
||||||
// 数据集详情:dsObject/dynamicForm/{dsObjectId} → 动态表单。
|
|
||||||
virtual RepoResult<DynamicForm> loadDatasetForm(const std::string& dsObjectId) = 0;
|
|
||||||
// 单 TM 异常列表(含异常体归属字段)。
|
|
||||||
virtual RepoResult<std::vector<ExceptionRow>> loadExceptionsByTm(const std::string& tmObjectId) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace geopro::data
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
#include "ApiClient.hpp"
|
#include "ApiClient.hpp"
|
||||||
|
|
||||||
#include "ApiCall.hpp"
|
#include "ApiCall.hpp"
|
||||||
#include "ApiResponseParse.hpp"
|
|
||||||
|
|
||||||
#include <QEventLoop>
|
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
|
|
@ -34,14 +32,6 @@ struct ApiClient::Impl {
|
||||||
}
|
}
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 阻塞等待 reply 完成,解析为 ApiResponse。调用方负责 reply->deleteLater()。
|
|
||||||
static ApiResponse await(QNetworkReply* reply) {
|
|
||||||
QEventLoop loop;
|
|
||||||
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
|
||||||
loop.exec();
|
|
||||||
return buildResponse(reply);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ApiClient::ApiClient(QString baseUrl) : impl_(std::make_unique<Impl>(std::move(baseUrl))) {}
|
ApiClient::ApiClient(QString baseUrl) : impl_(std::make_unique<Impl>(std::move(baseUrl))) {}
|
||||||
|
|
@ -50,23 +40,6 @@ ApiClient::~ApiClient() = default;
|
||||||
|
|
||||||
void ApiClient::setToken(const QString& token) { impl_->token = token; }
|
void ApiClient::setToken(const QString& token) { impl_->token = token; }
|
||||||
|
|
||||||
ApiResponse ApiClient::get(const QString& path) {
|
|
||||||
QNetworkRequest req = impl_->buildRequest(path);
|
|
||||||
QNetworkReply* reply = impl_->nam.get(req);
|
|
||||||
ApiResponse resp = Impl::await(reply);
|
|
||||||
reply->deleteLater();
|
|
||||||
return resp;
|
|
||||||
}
|
|
||||||
|
|
||||||
ApiResponse ApiClient::postJson(const QString& path, const QJsonObject& body) {
|
|
||||||
QNetworkRequest req = impl_->buildRequest(path);
|
|
||||||
const QByteArray payload = QJsonDocument(body).toJson(QJsonDocument::Compact);
|
|
||||||
QNetworkReply* reply = impl_->nam.post(req, payload);
|
|
||||||
ApiResponse resp = Impl::await(reply);
|
|
||||||
reply->deleteLater();
|
|
||||||
return resp;
|
|
||||||
}
|
|
||||||
|
|
||||||
IApiCall* ApiClient::getAsync(const QString& path) {
|
IApiCall* ApiClient::getAsync(const QString& path) {
|
||||||
QNetworkRequest req = impl_->buildRequest(path);
|
QNetworkRequest req = impl_->buildRequest(path);
|
||||||
QNetworkReply* reply = impl_->nam.get(req);
|
QNetworkReply* reply = impl_->nam.get(req);
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ struct ApiResponse {
|
||||||
QString rawError;
|
QString rawError;
|
||||||
};
|
};
|
||||||
|
|
||||||
// QtNetwork 的同步 HTTP 封装。
|
// QtNetwork 的异步 HTTP 封装。
|
||||||
// 内部持有【唯一一个】QNetworkAccessManager 成员,默认共享 cookie jar,
|
// 内部持有【唯一一个】QNetworkAccessManager 成员,默认共享 cookie jar,
|
||||||
// 因此同一 ApiClient 实例发出的多次请求处于同一会话(共享 JSESSIONID)。
|
// 因此同一 ApiClient 实例发出的多次请求处于同一会话(共享 JSESSIONID)。
|
||||||
// 这是登录流程 getImageCode -> verifyCodeCheck -> login2 串联的关键。
|
// 这是登录流程 getImageCode -> verifyCodeCheck -> login2 串联的关键。
|
||||||
|
|
@ -36,10 +36,6 @@ public:
|
||||||
// 设置令牌;注入请求头 geomativeauthorization。token 值本身已含 "Geomative " 前缀。
|
// 设置令牌;注入请求头 geomativeauthorization。token 值本身已含 "Geomative " 前缀。
|
||||||
void setToken(const QString& token);
|
void setToken(const QString& token);
|
||||||
|
|
||||||
// 同步 GET / POST(JSON)。用 QNetworkReply + QEventLoop 阻塞等待响应。
|
|
||||||
ApiResponse get(const QString& path);
|
|
||||||
ApiResponse postJson(const QString& path, const QJsonObject& body);
|
|
||||||
|
|
||||||
// 异步 GET / POST(JSON):立即返回自管理句柄,不阻塞。调用方连 IApiCall::finished。
|
// 异步 GET / POST(JSON):立即返回自管理句柄,不阻塞。调用方连 IApiCall::finished。
|
||||||
IApiCall* getAsync(const QString& path);
|
IApiCall* getAsync(const QString& path);
|
||||||
IApiCall* postJsonAsync(const QString& path, const QJsonObject& body);
|
IApiCall* postJsonAsync(const QString& path, const QJsonObject& body);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue