fix: 代码评审整改(控制器防重入 + URL 百分号编码 + 测试/注释完善)
This commit is contained in:
parent
405fb2ae4f
commit
601706d120
|
|
@ -498,6 +498,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
.arg(name).arg(g.nx()).arg(g.ny()).arg(g.vmin).arg(g.vmax)
|
.arg(name).arg(g.nx()).arg(g.ny()).arg(g.vmin).arg(g.vmax)
|
||||||
.arg(anomalies.size()));
|
.arg(anomalies.size()));
|
||||||
};
|
};
|
||||||
|
(void)loadDataset; // 暂未触发:保留待下一轮真实 DS 详情渲染复用
|
||||||
|
|
||||||
// ── 单击左下数据列表的采集批次(DS) → 占位(真实剖面/反演渲染下一阶段接 dd 接口)──
|
// ── 单击左下数据列表的采集批次(DS) → 占位(真实剖面/反演渲染下一阶段接 dd 接口)──
|
||||||
QObject::connect(datasetList, &QListWidget::itemClicked, datasetList,
|
QObject::connect(datasetList, &QListWidget::itemClicked, datasetList,
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ public:
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void tmClicked(const QString& tmObjectId);
|
void tmClicked(const QString& tmObjectId);
|
||||||
|
// 前瞻钩子:勾选驱动中央渲染留待下一轮接真实 DS(本轮暂无消费者)。
|
||||||
void tmCheckToggled(const QString& tmObjectId, bool checked);
|
void tmCheckToggled(const QString& tmObjectId, bool checked);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,27 @@ using data::Workspace;
|
||||||
WorkbenchNavController::WorkbenchNavController(data::IProjectRepository& repo, QObject* parent)
|
WorkbenchNavController::WorkbenchNavController(data::IProjectRepository& repo, QObject* parent)
|
||||||
: QObject(parent), repo_(repo) {}
|
: QObject(parent), repo_(repo) {}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// RAII:进入公共导航操作时置忙(驱动等待光标),任何返回路径都复位——保证 busyChanged 配平。
|
||||||
|
struct BusyGuard {
|
||||||
|
WorkbenchNavController* self;
|
||||||
|
bool* busy;
|
||||||
|
BusyGuard(WorkbenchNavController* s, bool* b) : self(s), busy(b) {
|
||||||
|
*busy = true;
|
||||||
|
emit self->busyChanged(true);
|
||||||
|
}
|
||||||
|
~BusyGuard() {
|
||||||
|
*busy = false;
|
||||||
|
emit self->busyChanged(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
void WorkbenchNavController::start() {
|
void WorkbenchNavController::start() {
|
||||||
emit busyChanged(true);
|
if (busy_) return;
|
||||||
|
BusyGuard guard(this, &busy_);
|
||||||
const auto ws = repo_.listWorkspaces();
|
const auto ws = repo_.listWorkspaces();
|
||||||
if (!ws.ok) {
|
if (!ws.ok) {
|
||||||
emit busyChanged(false);
|
|
||||||
emit loadFailed(QStringLiteral("workspaces"), QString::fromStdString(ws.error));
|
emit loadFailed(QStringLiteral("workspaces"), QString::fromStdString(ws.error));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -22,9 +38,7 @@ void WorkbenchNavController::start() {
|
||||||
if (cur.isEmpty() && !ws.value.empty()) cur = QString::fromStdString(ws.value.front().id);
|
if (cur.isEmpty() && !ws.value.empty()) cur = QString::fromStdString(ws.value.front().id);
|
||||||
currentWorkspaceId_ = cur.toStdString();
|
currentWorkspaceId_ = cur.toStdString();
|
||||||
emit workspacesLoaded(ws.value, cur);
|
emit workspacesLoaded(ws.value, cur);
|
||||||
|
|
||||||
loadProjectsAndStructure();
|
loadProjectsAndStructure();
|
||||||
emit busyChanged(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WorkbenchNavController::loadProjectsAndStructure() {
|
void WorkbenchNavController::loadProjectsAndStructure() {
|
||||||
|
|
@ -61,22 +75,20 @@ void WorkbenchNavController::loadProjectsAndStructure() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void WorkbenchNavController::switchWorkspace(const QString& tenantId) {
|
void WorkbenchNavController::switchWorkspace(const QString& tenantId) {
|
||||||
if (tenantId.isEmpty()) return;
|
if (tenantId.isEmpty() || busy_) return;
|
||||||
emit busyChanged(true);
|
BusyGuard guard(this, &busy_);
|
||||||
const auto r = repo_.switchWorkspace(tenantId.toStdString());
|
const auto r = repo_.switchWorkspace(tenantId.toStdString());
|
||||||
if (!r.ok) {
|
if (!r.ok) {
|
||||||
emit busyChanged(false);
|
|
||||||
emit loadFailed(QStringLiteral("switchWorkspace"), QString::fromStdString(r.error));
|
emit loadFailed(QStringLiteral("switchWorkspace"), QString::fromStdString(r.error));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
currentWorkspaceId_ = tenantId.toStdString();
|
currentWorkspaceId_ = tenantId.toStdString();
|
||||||
loadProjectsAndStructure();
|
loadProjectsAndStructure();
|
||||||
emit busyChanged(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WorkbenchNavController::switchProject(const QString& projectId) {
|
void WorkbenchNavController::switchProject(const QString& projectId) {
|
||||||
if (projectId.isEmpty()) return;
|
if (projectId.isEmpty() || busy_) return;
|
||||||
emit busyChanged(true);
|
BusyGuard guard(this, &busy_);
|
||||||
currentProjectId_ = projectId.toStdString();
|
currentProjectId_ = projectId.toStdString();
|
||||||
for (const auto& p : lastProjects_)
|
for (const auto& p : lastProjects_)
|
||||||
if (p.id == currentProjectId_) {
|
if (p.id == currentProjectId_) {
|
||||||
|
|
@ -85,19 +97,16 @@ void WorkbenchNavController::switchProject(const QString& projectId) {
|
||||||
}
|
}
|
||||||
const auto st = repo_.loadStructure(currentProjectId_);
|
const auto st = repo_.loadStructure(currentProjectId_);
|
||||||
if (!st.ok) {
|
if (!st.ok) {
|
||||||
emit busyChanged(false);
|
|
||||||
emit loadFailed(QStringLiteral("structure"), QString::fromStdString(st.error));
|
emit loadFailed(QStringLiteral("structure"), QString::fromStdString(st.error));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
emit structureLoaded(QString::fromStdString(currentProjectName_), st.value);
|
emit structureLoaded(QString::fromStdString(currentProjectName_), st.value);
|
||||||
emit busyChanged(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WorkbenchNavController::selectTm(const QString& tmObjectId) {
|
void WorkbenchNavController::selectTm(const QString& tmObjectId) {
|
||||||
if (tmObjectId.isEmpty()) return;
|
if (tmObjectId.isEmpty() || busy_) return;
|
||||||
emit busyChanged(true);
|
BusyGuard guard(this, &busy_);
|
||||||
const auto ds = repo_.loadDatasetsOfTm(tmObjectId.toStdString());
|
const auto ds = repo_.loadDatasetsOfTm(tmObjectId.toStdString());
|
||||||
emit busyChanged(false);
|
|
||||||
if (!ds.ok) {
|
if (!ds.ok) {
|
||||||
emit loadFailed(QStringLiteral("datasets"), QString::fromStdString(ds.error));
|
emit loadFailed(QStringLiteral("datasets"), QString::fromStdString(ds.error));
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ private:
|
||||||
void loadProjectsAndStructure(); // start + switchWorkspace 共用
|
void loadProjectsAndStructure(); // start + switchWorkspace 共用
|
||||||
|
|
||||||
data::IProjectRepository& repo_;
|
data::IProjectRepository& repo_;
|
||||||
|
bool busy_ = false;
|
||||||
std::vector<data::ProjectSummary> lastProjects_;
|
std::vector<data::ProjectSummary> lastProjects_;
|
||||||
std::string currentWorkspaceId_, currentProjectId_, currentProjectName_, currentCrsCode_;
|
std::string currentWorkspaceId_, currentProjectId_, currentProjectName_, currentCrsCode_;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
#include "ApiClient.hpp"
|
#include "ApiClient.hpp"
|
||||||
#include "dto/NavDto.hpp"
|
#include "dto/NavDto.hpp"
|
||||||
|
|
@ -19,6 +20,11 @@ std::string errorOf(const net::ApiResponse& r, const char* fallback) {
|
||||||
if (!r.rawError.isEmpty()) return r.rawError.toStdString();
|
if (!r.rawError.isEmpty()) return r.rawError.toStdString();
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 后端 id 进 URL 前做百分号编码(不可信外部数据:防 ? # & / 空格 破坏路径/查询)。
|
||||||
|
QString enc(const std::string& s) {
|
||||||
|
return QString::fromUtf8(QUrl::toPercentEncoding(QString::fromStdString(s)));
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
ApiProjectRepository::ApiProjectRepository(net::ApiClient& api) : api_(api) {}
|
ApiProjectRepository::ApiProjectRepository(net::ApiClient& api) : api_(api) {}
|
||||||
|
|
@ -32,7 +38,7 @@ RepoResult<std::vector<Workspace>> ApiProjectRepository::listWorkspaces() {
|
||||||
|
|
||||||
RepoResult<bool> ApiProjectRepository::switchWorkspace(const std::string& tenantId) {
|
RepoResult<bool> ApiProjectRepository::switchWorkspace(const std::string& tenantId) {
|
||||||
const QString path =
|
const QString path =
|
||||||
QStringLiteral("/business/system/tenant/enterprise/switch/%1").arg(QString::fromStdString(tenantId));
|
QStringLiteral("/business/system/tenant/enterprise/switch/%1").arg(enc(tenantId));
|
||||||
const net::ApiResponse r = api_.postJson(path, QJsonObject{});
|
const net::ApiResponse r = api_.postJson(path, QJsonObject{});
|
||||||
if (!ok(r)) return {false, false, errorOf(r, "switchWorkspace failed")};
|
if (!ok(r)) return {false, false, errorOf(r, "switchWorkspace failed")};
|
||||||
return {true, true, {}};
|
return {true, true, {}};
|
||||||
|
|
@ -41,7 +47,8 @@ RepoResult<bool> ApiProjectRepository::switchWorkspace(const std::string& tenant
|
||||||
RepoResult<std::vector<ProjectSummary>> ApiProjectRepository::listProjects(
|
RepoResult<std::vector<ProjectSummary>> ApiProjectRepository::listProjects(
|
||||||
const std::string& lastProjectId) {
|
const std::string& lastProjectId) {
|
||||||
const QString path = QStringLiteral("/business/project/queryByUser?lastProjectId=%1")
|
const QString path = QStringLiteral("/business/project/queryByUser?lastProjectId=%1")
|
||||||
.arg(QString::fromStdString(lastProjectId));
|
.arg(enc(lastProjectId));
|
||||||
|
// 本轮仅取首页;hasNextPage 暂不跟进(分页"加载更多"留下一轮)。
|
||||||
const net::ApiResponse r = api_.get(path);
|
const net::ApiResponse r = api_.get(path);
|
||||||
if (!ok(r)) return {false, {}, errorOf(r, "listProjects failed")};
|
if (!ok(r)) return {false, {}, errorOf(r, "listProjects failed")};
|
||||||
return {true, dto::parseProjects(r.data).projects, {}};
|
return {true, dto::parseProjects(r.data).projects, {}};
|
||||||
|
|
@ -57,7 +64,7 @@ RepoResult<std::vector<StructNode>> ApiProjectRepository::loadStructure(const st
|
||||||
|
|
||||||
RepoResult<std::vector<DsNode>> ApiProjectRepository::loadDatasetsOfTm(const std::string& tmObjectId) {
|
RepoResult<std::vector<DsNode>> ApiProjectRepository::loadDatasetsOfTm(const std::string& tmObjectId) {
|
||||||
const QString path = QStringLiteral("/business/projectWorkbench/queryDsByTmObjectId/%1")
|
const QString path = QStringLiteral("/business/projectWorkbench/queryDsByTmObjectId/%1")
|
||||||
.arg(QString::fromStdString(tmObjectId));
|
.arg(enc(tmObjectId));
|
||||||
const net::ApiResponse r = api_.get(path);
|
const net::ApiResponse r = api_.get(path);
|
||||||
if (!ok(r)) return {false, {}, errorOf(r, "loadDatasetsOfTm failed")};
|
if (!ok(r)) return {false, {}, errorOf(r, "loadDatasetsOfTm failed")};
|
||||||
return {true, dto::parseDatasets(r.data.value(QStringLiteral("value")).toArray()), {}};
|
return {true, dto::parseDatasets(r.data.value(QStringLiteral("value")).toArray()), {}};
|
||||||
|
|
|
||||||
|
|
@ -119,3 +119,10 @@ TEST(NavDto, BuildStructTreeHandlesCycleWithoutInfiniteRecursion) {
|
||||||
ASSERT_EQ(roots.size(), 1u);
|
ASSERT_EQ(roots.size(), 1u);
|
||||||
EXPECT_EQ(roots[0].node.id, "R");
|
EXPECT_EQ(roots[0].node.id, "R");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(NavDto, ParseProjectsEmptyAndMissingListGraceful) {
|
||||||
|
EXPECT_TRUE(dto::parseProjects(objOf(R"({})")).projects.empty());
|
||||||
|
EXPECT_FALSE(dto::parseProjects(objOf(R"({"hasNextPage":false})")).hasNextPage);
|
||||||
|
const auto p = dto::parseProjects(objOf(R"({"projectList":[]})"));
|
||||||
|
EXPECT_TRUE(p.projects.empty());
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue