feat(nav): 项目列表弹窗(名称/类型过滤+分页+8列,点项目名切换)+ 下拉全部项目入口

This commit is contained in:
gaozheng 2026-06-09 18:17:23 +08:00
parent ee8342f4bf
commit b4824a6e4e
15 changed files with 331 additions and 20 deletions

View File

@ -23,7 +23,8 @@ add_executable(geopro_desktop WIN32
panels/AnomalyListPanel.cpp panels/AnomalyListPanel.cpp
panels/DatasetListPanel.cpp panels/DatasetListPanel.cpp
panels/ObjectTreePanel.cpp panels/ObjectTreePanel.cpp
CentralScene.cpp) CentralScene.cpp
ProjectListDialog.cpp)
target_include_directories(geopro_desktop PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(geopro_desktop PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})

View File

@ -0,0 +1,160 @@
#include "ProjectListDialog.hpp"
#include <QAbstractItemView>
#include <QColor>
#include <QComboBox>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QStringList>
#include <QTableWidget>
#include <QTableWidgetItem>
#include <QVBoxLayout>
namespace geopro::app {
namespace {
QString statusText(int s) {
switch (s) {
case 1: return QStringLiteral("未开始");
case 2: return QStringLiteral("进行中");
default: return QString::number(s);
}
}
} // namespace
ProjectListDialog::ProjectListDialog(data::IProjectRepository& repo, QWidget* parent)
: QDialog(parent), repo_(repo) {
setWindowTitle(QStringLiteral("全部项目"));
resize(980, 560);
auto* root = new QVBoxLayout(this);
auto* filter = new QHBoxLayout();
filter->addWidget(new QLabel(QStringLiteral("项目名称"), this));
nameEdit_ = new QLineEdit(this);
nameEdit_->setPlaceholderText(QStringLiteral("输入项目名称"));
nameEdit_->setFixedWidth(200);
filter->addWidget(nameEdit_);
filter->addSpacing(8);
filter->addWidget(new QLabel(QStringLiteral("项目类型"), this));
typeCombo_ = new QComboBox(this);
typeCombo_->setFixedWidth(160);
filter->addWidget(typeCombo_);
filter->addSpacing(8);
auto* searchBtn = new QPushButton(QStringLiteral("搜索"), this);
auto* resetBtn = new QPushButton(QStringLiteral("重置"), this);
filter->addWidget(searchBtn);
filter->addWidget(resetBtn);
filter->addStretch();
root->addLayout(filter);
table_ = new QTableWidget(this);
table_->setColumnCount(8);
table_->setHorizontalHeaderLabels(QStringList{
QStringLiteral("序号"), QStringLiteral("项目名称"), QStringLiteral("项目编号"),
QStringLiteral("项目状态"), QStringLiteral("项目类型"), QStringLiteral("业主单位"),
QStringLiteral("负责人"), QStringLiteral("创建时间")});
table_->setEditTriggers(QAbstractItemView::NoEditTriggers);
table_->setSelectionBehavior(QAbstractItemView::SelectRows);
table_->setSelectionMode(QAbstractItemView::SingleSelection);
table_->verticalHeader()->setVisible(false);
table_->horizontalHeader()->setStretchLastSection(true);
table_->setColumnWidth(0, 50);
table_->setColumnWidth(1, 260);
root->addWidget(table_, 1);
auto* bottom = new QHBoxLayout();
pageLabel_ = new QLabel(this);
bottom->addWidget(pageLabel_);
bottom->addStretch();
prevBtn_ = new QPushButton(QStringLiteral("上一页"), this);
nextBtn_ = new QPushButton(QStringLiteral("下一页"), this);
bottom->addWidget(prevBtn_);
bottom->addWidget(nextBtn_);
root->addLayout(bottom);
fillTypeFilter();
QObject::connect(searchBtn, &QPushButton::clicked, this, [this]() {
pageNo_ = 1;
query();
});
QObject::connect(resetBtn, &QPushButton::clicked, this, [this]() {
nameEdit_->clear();
typeCombo_->setCurrentIndex(0);
pageNo_ = 1;
query();
});
QObject::connect(prevBtn_, &QPushButton::clicked, this, [this]() {
if (pageNo_ > 1) {
--pageNo_;
query();
}
});
QObject::connect(nextBtn_, &QPushButton::clicked, this, [this]() {
if (pageNo_ * pageSize_ < total_) {
++pageNo_;
query();
}
});
QObject::connect(table_, &QTableWidget::cellClicked, this, [this](int row, int col) {
if (col != 1) return; // 仅"项目名称"列触发切换
auto* item = table_->item(row, 1);
if (!item) return;
const QString id = item->data(Qt::UserRole).toString();
if (id.isEmpty()) return;
emit projectChosen(id, item->text());
accept();
});
query();
}
void ProjectListDialog::fillTypeFilter() {
typeCombo_->addItem(QStringLiteral("全部类型"), QString());
const auto r = repo_.listProjectTypes();
if (!r.ok) return;
for (const auto& t : r.value)
typeCombo_->addItem(QString::fromStdString(t.name), QString::fromStdString(t.id));
}
void ProjectListDialog::query() {
const std::string name = nameEdit_->text().trimmed().toStdString();
const std::string typeId = typeCombo_->currentData().toString().toStdString();
const auto r = repo_.pageProjects(name, typeId, pageNo_, pageSize_);
if (!r.ok) {
table_->setRowCount(0);
pageLabel_->setText(QStringLiteral("加载失败:%1").arg(QString::fromStdString(r.error)));
prevBtn_->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(QColor("#2D6CB5"));
table_->setItem(i, 1, nameItem);
set(2, QString::fromStdString(p.code));
set(3, statusText(p.status));
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

View File

@ -0,0 +1,39 @@
#pragma once
#include <QDialog>
#include "repo/IProjectRepository.hpp"
class QLineEdit;
class QComboBox;
class QTableWidget;
class QLabel;
class QPushButton;
namespace geopro::app {
// 项目列表弹窗:名称/类型过滤 + 分页表格;点项目名 → 切换项目并关闭。
class ProjectListDialog : public QDialog {
Q_OBJECT
public:
explicit ProjectListDialog(data::IProjectRepository& repo, QWidget* parent = nullptr);
signals:
void projectChosen(const QString& projectId, const QString& projectName);
private:
void query();
void fillTypeFilter();
data::IProjectRepository& repo_;
QLineEdit* nameEdit_ = nullptr;
QComboBox* typeCombo_ = nullptr;
QTableWidget* table_ = nullptr;
QLabel* pageLabel_ = nullptr;
QPushButton* prevBtn_ = nullptr;
QPushButton* nextBtn_ = nullptr;
int pageNo_ = 1;
int pageSize_ = 20;
int total_ = 0;
};
} // namespace geopro::app

View File

@ -236,7 +236,8 @@ void TopBar::setWorkspaces(const std::vector<data::Workspace>& list, const QStri
QStringLiteral("")); QStringLiteral(""));
} }
void TopBar::setProjects(const std::vector<data::ProjectSummary>& list, const QString& currentId) { void TopBar::setProjects(const std::vector<data::ProjectSummary>& list, const QString& currentId,
bool hasMore) {
auto* menu = new QMenu(projBtn_); auto* menu = new QMenu(projBtn_);
auto* header = menu->addAction(QStringLiteral("切换项目")); auto* header = menu->addAction(QStringLiteral("切换项目"));
header->setEnabled(false); header->setEnabled(false);
@ -261,9 +262,18 @@ void TopBar::setProjects(const std::vector<data::ProjectSummary>& list, const QS
auto* none = menu->addAction(QStringLiteral("(暂无项目)")); auto* none = menu->addAction(QStringLiteral("(暂无项目)"));
none->setEnabled(false); none->setEnabled(false);
} }
if (hasMore) {
menu->addSeparator();
auto* all = menu->addAction(QStringLiteral("全部项目…"));
QObject::connect(all, &QAction::triggered, this, [this]() { emit allProjectsRequested(); });
}
projBtn_->setMenu(menu); projBtn_->setMenu(menu);
projBtn_->setText((currentName.isEmpty() ? QStringLiteral("选择项目") : currentName) + projBtn_->setText((currentName.isEmpty() ? QStringLiteral("选择项目") : currentName) +
QStringLiteral("")); QStringLiteral(""));
} }
void TopBar::setProjectButtonText(const QString& name) {
projBtn_->setText(name + QStringLiteral(""));
}
} // namespace geopro::app } // namespace geopro::app

View File

@ -17,11 +17,14 @@ public:
explicit TopBar(QWidget* parent = nullptr); explicit TopBar(QWidget* parent = nullptr);
void setWorkspaces(const std::vector<data::Workspace>& list, const QString& currentId); void setWorkspaces(const std::vector<data::Workspace>& list, const QString& currentId);
void setProjects(const std::vector<data::ProjectSummary>& list, const QString& currentId); void setProjects(const std::vector<data::ProjectSummary>& list, const QString& currentId,
bool hasMore);
void setProjectButtonText(const QString& name); // 弹窗切换项目后更新按钮文字
signals: signals:
void workspaceSwitchRequested(const QString& tenantId); void workspaceSwitchRequested(const QString& tenantId);
void projectSwitchRequested(const QString& projectId); void projectSwitchRequested(const QString& projectId);
void allProjectsRequested(); // 点击"全部项目…"
private: private:
QToolButton* wsBtn_ = nullptr; QToolButton* wsBtn_ = nullptr;

View File

@ -61,6 +61,7 @@
#include "Theme.hpp" #include "Theme.hpp"
#include "TopBar.hpp" #include "TopBar.hpp"
#include "CentralScene.hpp" #include "CentralScene.hpp"
#include "ProjectListDialog.hpp"
#include "WorkbenchNavController.hpp" #include "WorkbenchNavController.hpp"
#include "api/ApiProjectRepository.hpp" #include "api/ApiProjectRepository.hpp"
#include "panels/ObjectTreePanel.hpp" #include "panels/ObjectTreePanel.hpp"
@ -138,6 +139,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::controller::WorkbenchNavController& nav) geopro::controller::WorkbenchNavController& nav)
{ {
// ── 世界系:启动取一次 grid1 的 lat/lon用中位数作 GeoLocalFrame 原点 ── // ── 世界系:启动取一次 grid1 的 lat/lon用中位数作 GeoLocalFrame 原点 ──
@ -642,6 +644,17 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
&geopro::controller::WorkbenchNavController::switchWorkspace); &geopro::controller::WorkbenchNavController::switchWorkspace);
QObject::connect(topBar, &geopro::app::TopBar::projectSwitchRequested, &nav, QObject::connect(topBar, &geopro::app::TopBar::projectSwitchRequested, &nav,
&geopro::controller::WorkbenchNavController::switchProject); &geopro::controller::WorkbenchNavController::switchProject);
QObject::connect(topBar, &geopro::app::TopBar::allProjectsRequested, &window,
[&projectRepo, &nav, topBar, &window]() {
auto* dlg = new geopro::app::ProjectListDialog(projectRepo, &window);
dlg->setAttribute(Qt::WA_DeleteOnClose);
QObject::connect(dlg, &geopro::app::ProjectListDialog::projectChosen, &nav,
[&nav, topBar](const QString& id, const QString& name) {
topBar->setProjectButtonText(name);
nav.switchProject(id);
});
dlg->exec();
});
QObject::connect(objectTree, &geopro::app::ObjectTreePanel::tmClicked, &nav, QObject::connect(objectTree, &geopro::app::ObjectTreePanel::tmClicked, &nav,
&geopro::controller::WorkbenchNavController::selectTm); &geopro::controller::WorkbenchNavController::selectTm);
@ -651,7 +664,9 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
}); });
QObject::connect(&nav, &geopro::controller::WorkbenchNavController::projectsLoaded, topBar, QObject::connect(&nav, &geopro::controller::WorkbenchNavController::projectsLoaded, topBar,
[topBar](const std::vector<geopro::data::ProjectSummary>& list, [topBar](const std::vector<geopro::data::ProjectSummary>& list,
const QString& cur) { topBar->setProjects(list, cur); }); const QString& cur, int total) {
topBar->setProjects(list, cur, total > static_cast<int>(list.size()));
});
QObject::connect(&nav, &geopro::controller::WorkbenchNavController::structureLoaded, objectTree, QObject::connect(&nav, &geopro::controller::WorkbenchNavController::structureLoaded, objectTree,
[objectTree, datasetList, fileList, datasetTitle, datasetTabs]( [objectTree, datasetList, fileList, datasetTitle, datasetTabs](
const QString& projectName, const QString& projectName,
@ -772,7 +787,7 @@ int main(int argc, char* argv[])
window.resize(1280, 800); window.resize(1280, 800);
window.setMinimumSize(1024, 680); // 防止停靠面板被压到不可用尺寸 window.setMinimumSize(1024, 680); // 防止停靠面板被压到不可用尺寸
buildWorkbench(window, repo, nav); buildWorkbench(window, repo, projectRepo, nav);
window.show(); window.show();
nav.start(); // 进入工作台后拉真实 空间/项目/结构 nav.start(); // 进入工作台后拉真实 空间/项目/结构

View File

@ -42,15 +42,15 @@ void WorkbenchNavController::start() {
} }
void WorkbenchNavController::loadProjectsAndStructure() { void WorkbenchNavController::loadProjectsAndStructure() {
const auto ps = repo_.listProjects(std::string()); const auto ps = repo_.pageProjects(std::string(), std::string(), 1, 20);
if (!ps.ok) { if (!ps.ok) {
emit loadFailed(QStringLiteral("projects"), QString::fromStdString(ps.error)); emit loadFailed(QStringLiteral("projects"), QString::fromStdString(ps.error));
return; return;
} }
lastProjects_ = ps.value; lastProjects_ = ps.value.rows;
QString curP; QString curP;
if (!ps.value.empty()) { if (!ps.value.rows.empty()) {
const auto& first = ps.value.front(); const auto& first = ps.value.rows.front();
curP = QString::fromStdString(first.id); curP = QString::fromStdString(first.id);
currentProjectId_ = first.id; currentProjectId_ = first.id;
currentProjectName_ = first.name; currentProjectName_ = first.name;
@ -60,7 +60,7 @@ void WorkbenchNavController::loadProjectsAndStructure() {
currentProjectName_.clear(); currentProjectName_.clear();
currentCrsCode_.clear(); currentCrsCode_.clear();
} }
emit projectsLoaded(ps.value, curP); emit projectsLoaded(ps.value.rows, curP, ps.value.total);
if (curP.isEmpty()) { if (curP.isEmpty()) {
emit structureLoaded(QString(), {}); // 暂无项目 → 空树 emit structureLoaded(QString(), {}); // 暂无项目 → 空树

View File

@ -28,7 +28,8 @@ public slots:
signals: signals:
void busyChanged(bool busy); void busyChanged(bool busy);
void workspacesLoaded(const std::vector<geopro::data::Workspace>& list, const QString& currentId); void workspacesLoaded(const std::vector<geopro::data::Workspace>& list, const QString& currentId);
void projectsLoaded(const std::vector<geopro::data::ProjectSummary>& list, const QString& currentId); void projectsLoaded(const std::vector<geopro::data::ProjectSummary>& list,
const QString& currentId, int total);
void structureLoaded(const QString& projectName, const std::vector<geopro::data::StructNode>& nodes); void structureLoaded(const QString& projectName, const std::vector<geopro::data::StructNode>& nodes);
void datasetsLoaded(const QString& tmObjectId, const std::vector<geopro::data::DsRow>& rows, void datasetsLoaded(const QString& tmObjectId, const std::vector<geopro::data::DsRow>& rows,
int total, bool append); int total, bool append);

View File

@ -47,11 +47,22 @@ RepoResult<bool> ApiProjectRepository::switchWorkspace(const std::string& tenant
return {true, true, {}}; return {true, true, {}};
} }
RepoResult<std::vector<ProjectSummary>> ApiProjectRepository::listProjects(const std::string&) { RepoResult<ProjectListPage> ApiProjectRepository::pageProjects(const std::string& nameFilter,
// 我的工作台项目列表当前空间全部项目。queryByUser 实测为空,故用此接口。 const std::string& typeId, int pageNo,
const net::ApiResponse r = api_.get(QStringLiteral("/business/my/profile/queryProject")); int pageSize) {
if (!ok(r)) return {false, {}, errorOf(r, "listProjects failed")}; QJsonObject body{{QStringLiteral("projectName"), QString::fromStdString(nameFilter)},
return {true, dto::parseProjectList(r.data.value(QStringLiteral("value")).toArray()), {}}; {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) { RepoResult<std::vector<StructNode>> ApiProjectRepository::loadStructure(const std::string& projectId) {

View File

@ -12,7 +12,9 @@ public:
RepoResult<std::vector<Workspace>> listWorkspaces() override; RepoResult<std::vector<Workspace>> listWorkspaces() override;
RepoResult<bool> switchWorkspace(const std::string& tenantId) override; RepoResult<bool> switchWorkspace(const std::string& tenantId) override;
RepoResult<std::vector<ProjectSummary>> listProjects(const std::string& lastProjectId) 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<std::vector<StructNode>> loadStructure(const std::string& projectId) override;
RepoResult<DsPage> loadTmRows(const std::string& projectId, const std::string& tmObjectId, RepoResult<DsPage> loadTmRows(const std::string& projectId, const std::string& tmObjectId,
int classifyType, int pageNo) override; int classifyType, int pageNo) override;

View File

@ -19,6 +19,11 @@ ProjectSummary parseProjectItem(const QJsonObject& o) {
p.typeName = str(o, "projectTypeName"); p.typeName = str(o, "projectTypeName");
p.crsCode = str(o, "referenceCRSCode"); p.crsCode = str(o, "referenceCRSCode");
p.crsName = str(o, "referenceCRSName"); p.crsName = str(o, "referenceCRSName");
p.code = str(o, "projectCode");
p.projectTypeId = str(o, "projectTypeId");
p.ownerCompany = str(o, "ownerCompanyName");
p.responsiblePerson = str(o, "responsiblePersonName");
p.createTime = str(o, "createTime");
p.status = o.value(QStringLiteral("status")).toInt(); p.status = o.value(QStringLiteral("status")).toInt();
return p; return p;
} }
@ -55,6 +60,23 @@ std::vector<ProjectSummary> parseProjectList(const QJsonArray& arr) {
return out; return out;
} }
ProjectListPage parseProjectPage(const QJsonObject& data) {
ProjectListPage p;
p.rows = parseProjectList(data.value(QStringLiteral("list")).toArray());
p.total = data.value(QStringLiteral("total")).toInt();
return p;
}
std::vector<ProjectType> parseProjectTypes(const QJsonArray& arr) {
std::vector<ProjectType> out;
out.reserve(static_cast<size_t>(arr.size()));
for (const QJsonValue& v : arr) {
const QJsonObject o = v.toObject();
out.push_back(ProjectType{str(o, "id"), str(o, "name")});
}
return out;
}
std::vector<StructNode> parseStructNodes(const QJsonArray& arr) { std::vector<StructNode> parseStructNodes(const QJsonArray& arr) {
std::vector<StructNode> out; std::vector<StructNode> out;
out.reserve(static_cast<size_t>(arr.size())); out.reserve(static_cast<size_t>(arr.size()));

View File

@ -16,6 +16,11 @@ ProjectPage parseProjects(const QJsonObject& data);
// my/profile/queryProject 的 data["value"] 数组 → 模型(与 parseProjects 同字段映射)。 // my/profile/queryProject 的 data["value"] 数组 → 模型(与 parseProjects 同字段映射)。
std::vector<ProjectSummary> parseProjectList(const QJsonArray& arr); std::vector<ProjectSummary> parseProjectList(const QJsonArray& arr);
// my/profile/project/page 的整个 data 对象 → ProjectListPagerows + total
ProjectListPage parseProjectPage(const QJsonObject& data);
// project/type/list 的 data["value"] 数组 → 项目类型列表。
std::vector<ProjectType> parseProjectTypes(const QJsonArray& arr);
// 结构扁平节点数组queryProjectStruct 的 data["projectStructList"])→ 模型。 // 结构扁平节点数组queryProjectStruct 的 data["projectStructList"])→ 模型。
std::vector<StructNode> parseStructNodes(const QJsonArray& arr); std::vector<StructNode> parseStructNodes(const QJsonArray& arr);

View File

@ -19,7 +19,12 @@ public:
virtual ~IProjectRepository() = default; virtual ~IProjectRepository() = default;
virtual RepoResult<std::vector<Workspace>> listWorkspaces() = 0; virtual RepoResult<std::vector<Workspace>> listWorkspaces() = 0;
virtual RepoResult<bool> switchWorkspace(const std::string& tenantId) = 0; virtual RepoResult<bool> switchWorkspace(const std::string& tenantId) = 0;
virtual RepoResult<std::vector<ProjectSummary>> listProjects(const std::string& lastProjectId) = 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; virtual RepoResult<std::vector<StructNode>> loadStructure(const std::string& projectId) = 0;
// 按 TM 分页拉数据/文件行classifyType 3=数据 1=文件pageNo 从 1 起pageSize 固定 100。 // 按 TM 分页拉数据/文件行classifyType 3=数据 1=文件pageNo 从 1 起pageSize 固定 100。
virtual RepoResult<DsPage> loadTmRows(const std::string& projectId, virtual RepoResult<DsPage> loadTmRows(const std::string& projectId,

View File

@ -18,8 +18,17 @@ struct Project { std::string id, name; std::vector<GsNode> gss; };
// 工作空间(=企业租户/空间。ownerType: 1 个人空间 2 企业空间。 // 工作空间(=企业租户/空间。ownerType: 1 个人空间 2 企业空间。
struct Workspace { std::string id, name; int ownerType = 0; bool isCurrent = false; }; struct Workspace { std::string id, name; int ownerType = 0; bool isCurrent = false; };
// 项目摘要列表用。crsCode/crsName 为项目参考坐标系,下一轮替换硬编码 EPSG:4547。 // 项目摘要(下拉/弹窗列表用。crsCode 供下一轮替换硬编码 EPSG:4547。
struct ProjectSummary { std::string id, name, typeName, crsCode, crsName; int status = 0; }; struct ProjectSummary {
std::string id, name, typeName, crsCode, crsName;
std::string code, projectTypeId, ownerCompany, responsiblePerson, createTime;
int status = 0;
};
// 项目类型(弹窗类型过滤下拉用)。
struct ProjectType { std::string id, name; };
// 项目分页结果。
struct ProjectListPage { std::vector<ProjectSummary> rows; int total = 0; };
// 项目结构扁平节点(仅 GS / TM。客户端按 parentId 建树,叶子=TM。 // 项目结构扁平节点(仅 GS / TM。客户端按 parentId 建树,叶子=TM。
struct StructNode { std::string id, name, parentId, typeName, confCode; int type = 0; }; struct StructNode { std::string id, name, parentId, typeName, confCode; int type = 0; };

View File

@ -173,3 +173,31 @@ TEST(NavDto, ParseDsRowsDataAndFile) {
ASSERT_EQ(page.rows.size(), 1u); ASSERT_EQ(page.rows.size(), 1u);
EXPECT_EQ(page.rows[0].dsName, "a"); EXPECT_EQ(page.rows[0].dsName, "a");
} }
TEST(NavDto, ParseProjectItemFullFields) {
const auto v = dto::parseProjectList(arrOf(R"([
{"id":"p1","projectName":"演示","projectCode":"001","status":2,
"projectTypeId":"t1","projectTypeName":"全量类型",
"ownerCompanyName":"华南所","responsiblePersonName":"张三","createTime":"2026-01-01 00:00:00"}
])"));
ASSERT_EQ(v.size(), 1u);
EXPECT_EQ(v[0].code, "001");
EXPECT_EQ(v[0].status, 2);
EXPECT_EQ(v[0].projectTypeId, "t1");
EXPECT_EQ(v[0].typeName, "全量类型");
EXPECT_EQ(v[0].ownerCompany, "华南所");
EXPECT_EQ(v[0].responsiblePerson, "张三");
EXPECT_EQ(v[0].createTime, "2026-01-01 00:00:00");
}
TEST(NavDto, ParseProjectPageAndTypes) {
const auto page =
dto::parseProjectPage(objOf(R"({"total":20,"list":[{"id":"p1","projectName":"a"}]})"));
EXPECT_EQ(page.total, 20);
ASSERT_EQ(page.rows.size(), 1u);
EXPECT_EQ(page.rows[0].name, "a");
const auto types = dto::parseProjectTypes(arrOf(R"([{"id":"t1","name":""}])"));
ASSERT_EQ(types.size(), 1u);
EXPECT_EQ(types[0].id, "t1");
EXPECT_EQ(types[0].name, "全量类型");
}