diff --git a/src/app/main.cpp b/src/app/main.cpp index 82b68e8..2842a80 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -344,13 +344,8 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re "QListWidget::item:hover{ background:#F5F8FD; }" "QListWidget::item:selected{ background:#EAF1FB; color:#1F2A3D; }")); datasetTabs->addTab(datasetList, QStringLiteral("数据")); - auto* fileList = new QListWidget(); // M1 文件 tab 占位 - { // 空状态引导:M1 暂无文件来源,给出说明而非空白面板(识别优先于回忆)。 - auto* hint = new QListWidgetItem(QStringLiteral("(M1 暂无关联文件)"), fileList); - hint->setFlags(Qt::NoItemFlags); - hint->setForeground(QColor("#9AA6B6")); - hint->setTextAlignment(Qt::AlignCenter); - } + auto* fileList = new QListWidget(); + fileList->setStyleSheet(datasetList->styleSheet()); // 与数据页签同款简洁分割 datasetTabs->addTab(fileList, QStringLiteral("文件")); auto* datasetDock = new ads::CDockWidget(QStringLiteral("数据真实显示栏")); auto* datasetBox = wrapWithHeader( @@ -637,23 +632,31 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re [topBar](const std::vector& list, const QString& cur) { topBar->setProjects(list, cur); }); QObject::connect(&nav, &geopro::controller::WorkbenchNavController::structureLoaded, objectTree, - [objectTree, datasetList, datasetTitle, datasetTabs]( + [objectTree, datasetList, fileList, datasetTitle, datasetTabs]( const QString& projectName, const std::vector& nodes) { objectTree->setStructure(projectName, nodes); - datasetList->clear(); // 切项目清空 DS 列表 + datasetList->clear(); + fileList->clear(); if (datasetTitle) datasetTitle->setText(QStringLiteral("数据集显示栏")); datasetTabs->setTabText(0, QStringLiteral("数据")); + datasetTabs->setTabText(1, QStringLiteral("文件")); }); QObject::connect(&nav, &geopro::controller::WorkbenchNavController::datasetsLoaded, datasetList, [datasetList, datasetTitle, datasetTabs]( - const QString&, const std::vector& list) { + const QString&, const std::vector& list) { geopro::app::populateDatasetList(datasetList, list); - if (datasetTitle) - datasetTitle->setText(QStringLiteral("数据集显示栏")); + if (datasetTitle) datasetTitle->setText(QStringLiteral("数据集显示栏")); datasetTabs->setTabText( 0, QStringLiteral("数据 (%1)").arg(static_cast(list.size()))); }); + QObject::connect(&nav, &geopro::controller::WorkbenchNavController::filesLoaded, fileList, + [fileList, datasetTabs](const QString&, + const std::vector& list) { + geopro::app::populateFileList(fileList, list); + datasetTabs->setTabText( + 1, QStringLiteral("文件 (%1)").arg(static_cast(list.size()))); + }); QObject::connect(&nav, &geopro::controller::WorkbenchNavController::loadFailed, objectTree, [objectTree, &window](const QString& stage, const QString& msg) { if (stage == QStringLiteral("structure") || diff --git a/src/app/panels/DatasetListPanel.cpp b/src/app/panels/DatasetListPanel.cpp index 386137a..f1c6f4c 100644 --- a/src/app/panels/DatasetListPanel.cpp +++ b/src/app/panels/DatasetListPanel.cpp @@ -1,5 +1,6 @@ #include "panels/DatasetListPanel.hpp" +#include #include #include #include @@ -7,30 +8,43 @@ namespace geopro::app { namespace { - -// dd 类型 → 中文标注。 -QString ddTypeLabel(const std::string& ddType) -{ - if (ddType == "dd_section") return QStringLiteral("剖面网格"); - if (ddType == "dd_voxel") return QStringLiteral("体素"); - return QString::fromStdString(ddType); +QString humanSize(long long b) { + if (b < 1024) return QStringLiteral("%1 B").arg(b); + const double kb = b / 1024.0; + if (kb < 1024.0) return QStringLiteral("%1 KB").arg(kb, 0, 'f', 1); + return QStringLiteral("%1 MB").arg(kb / 1024.0, 0, 'f', 1); } - } // namespace -void populateDatasetList(QListWidget* list, const std::vector& dss) -{ +void populateDatasetList(QListWidget* list, const std::vector& rows) { if (!list) return; list->clear(); - for (const auto& ds : dss) { - const QString name = QString::fromStdString(ds.name); - const QString label = ddTypeLabel(ds.ddType); - QString text = name; - if (!label.isEmpty()) text += QStringLiteral("\n%1").arg(label); - + for (const auto& d : rows) { + QString text = QString::fromStdString(d.dsName); + if (!d.typeName.empty()) text += QStringLiteral("\n%1").arg(QString::fromStdString(d.typeName)); auto* item = new QListWidgetItem(text, list); - item->setData(kDsIdRole, QString::fromStdString(ds.id)); - item->setData(kDsDdTypeRole, QString::fromStdString(ds.ddType)); + item->setData(kDsIdRole, QString::fromStdString(d.id)); + item->setData(kDsDdTypeRole, QString::fromStdString(d.ddCode)); + } +} + +void populateFileList(QListWidget* list, const std::vector& rows) { + if (!list) return; + list->clear(); + if (rows.empty()) { + auto* hint = new QListWidgetItem(QStringLiteral("(暂无文件)"), list); + hint->setFlags(Qt::NoItemFlags); + hint->setForeground(QColor("#9AA6B6")); + hint->setTextAlignment(Qt::AlignCenter); + return; + } + for (const auto& d : rows) { + const QString fname = + d.fileName.empty() ? QString::fromStdString(d.dsName) : QString::fromStdString(d.fileName); + const QString text = fname + QStringLiteral("\n%1").arg(humanSize(d.fileSize)); + auto* item = new QListWidgetItem(text, list); + item->setData(kDsIdRole, QString::fromStdString(d.id)); + item->setData(kDsFileUrlRole, QString::fromStdString(d.fileUrl)); } } diff --git a/src/app/panels/DatasetListPanel.hpp b/src/app/panels/DatasetListPanel.hpp index f2a61e6..da04fb5 100644 --- a/src/app/panels/DatasetListPanel.hpp +++ b/src/app/panels/DatasetListPanel.hpp @@ -8,12 +8,13 @@ class QListWidget; namespace geopro::app { // 数据列表条目角色(与 main.cpp 树一致:Qt::UserRole=dsId、+1=ddType)。 -constexpr int kDsIdRole = 0x0100; // Qt::UserRole -constexpr int kDsDdTypeRole = 0x0101; // Qt::UserRole + 1 +constexpr int kDsIdRole = 0x0100; // Qt::UserRole +constexpr int kDsDdTypeRole = 0x0101; // Qt::UserRole + 1 +constexpr int kDsFileUrlRole = 0x0102; // Qt::UserRole + 2(文件下载 url,备用) -// 用某测线(TM)的数据集(采集批次)填充 QListWidget(对齐原型左下「数据真实显示栏」)。 -// 每条目 = 名称 +(ddType 标注);UserRole 存 dsId、+1 存 ddType(供单击驱动数据详情)。 -// 清空旧条目后重填。 -void populateDatasetList(QListWidget* list, const std::vector& dss); +// 数据页签:每条 = dsName +(类型名);UserRole 存 dsId、+1 存 ddCode。 +void populateDatasetList(QListWidget* list, const std::vector& rows); +// 文件页签:每条 = 文件名 +(可读大小);UserRole 存 dsId、+2 存文件 url。空时显示占位。 +void populateFileList(QListWidget* list, const std::vector& rows); } // namespace geopro::app diff --git a/src/controller/WorkbenchNavController.cpp b/src/controller/WorkbenchNavController.cpp index 6e42046..03b060c 100644 --- a/src/controller/WorkbenchNavController.cpp +++ b/src/controller/WorkbenchNavController.cpp @@ -106,12 +106,20 @@ void WorkbenchNavController::switchProject(const QString& projectId) { void WorkbenchNavController::selectTm(const QString& tmObjectId) { if (tmObjectId.isEmpty() || busy_) return; BusyGuard guard(this, &busy_); - const auto ds = repo_.loadDatasetsOfTm(tmObjectId.toStdString()); - if (!ds.ok) { - emit loadFailed(QStringLiteral("datasets"), QString::fromStdString(ds.error)); + const std::string pid = currentProjectId_; + const std::string tm = tmObjectId.toStdString(); + const auto data = repo_.loadTmRows(pid, tm, 3); // 数据 + if (!data.ok) { + emit loadFailed(QStringLiteral("datasets"), QString::fromStdString(data.error)); return; } - emit datasetsLoaded(tmObjectId, ds.value); + emit datasetsLoaded(tmObjectId, data.value); + const auto files = repo_.loadTmRows(pid, tm, 1); // 文件 + if (!files.ok) { + emit loadFailed(QStringLiteral("files"), QString::fromStdString(files.error)); + return; + } + emit filesLoaded(tmObjectId, files.value); } } // namespace geopro::controller diff --git a/src/controller/WorkbenchNavController.hpp b/src/controller/WorkbenchNavController.hpp index dc5c08e..e676685 100644 --- a/src/controller/WorkbenchNavController.hpp +++ b/src/controller/WorkbenchNavController.hpp @@ -28,7 +28,8 @@ signals: void workspacesLoaded(const std::vector& list, const QString& currentId); void projectsLoaded(const std::vector& list, const QString& currentId); void structureLoaded(const QString& projectName, const std::vector& nodes); - void datasetsLoaded(const QString& tmObjectId, const std::vector& list); + void datasetsLoaded(const QString& tmObjectId, const std::vector& list); + void filesLoaded(const QString& tmObjectId, const std::vector& list); void loadFailed(const QString& stage, const QString& message); private: diff --git a/src/data/api/ApiProjectRepository.cpp b/src/data/api/ApiProjectRepository.cpp index 1629256..e6c406c 100644 --- a/src/data/api/ApiProjectRepository.cpp +++ b/src/data/api/ApiProjectRepository.cpp @@ -63,12 +63,21 @@ RepoResult> ApiProjectRepository::loadStructure(const st return {true, dto::parseStructNodes(r.data.value(QStringLiteral("value")).toArray()), {}}; } -RepoResult> ApiProjectRepository::loadDatasetsOfTm(const std::string& tmObjectId) { - const QString path = QStringLiteral("/business/projectWorkbench/queryDsByTmObjectId/%1") - .arg(enc(tmObjectId)); - const net::ApiResponse r = api_.get(path); - if (!ok(r)) return {false, {}, errorOf(r, "loadDatasetsOfTm failed")}; - return {true, dto::parseDatasets(r.data.value(QStringLiteral("value")).toArray()), {}}; +RepoResult> ApiProjectRepository::loadTmRows(const std::string& projectId, + const std::string& tmObjectId, + int classifyType) { + 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(tmObjectId)}, + {QStringLiteral("structParentConfType"), 2}, + {QStringLiteral("classifyTypeList"), QJsonArray{classifyType}}, + {QStringLiteral("pageNo"), 1}, + {QStringLiteral("pageSize"), 100}}; + const net::ApiResponse r = api_.postJson(path, body); + if (!ok(r)) return {false, {}, errorOf(r, "loadTmRows failed")}; + return {true, dto::parseDsRows(r.data.value(QStringLiteral("list")).toArray()), {}}; } } // namespace geopro::data diff --git a/src/data/api/ApiProjectRepository.hpp b/src/data/api/ApiProjectRepository.hpp index b52ec51..354e350 100644 --- a/src/data/api/ApiProjectRepository.hpp +++ b/src/data/api/ApiProjectRepository.hpp @@ -14,7 +14,9 @@ public: RepoResult switchWorkspace(const std::string& tenantId) override; RepoResult> listProjects(const std::string& lastProjectId) override; RepoResult> loadStructure(const std::string& projectId) override; - RepoResult> loadDatasetsOfTm(const std::string& tmObjectId) override; + RepoResult> loadTmRows(const std::string& projectId, + const std::string& tmObjectId, + int classifyType) override; private: net::ApiClient& api_; diff --git a/src/data/dto/NavDto.cpp b/src/data/dto/NavDto.cpp index 098d4ae..48e5bb8 100644 --- a/src/data/dto/NavDto.cpp +++ b/src/data/dto/NavDto.cpp @@ -72,15 +72,20 @@ std::vector parseStructNodes(const QJsonArray& arr) { return out; } -std::vector parseDatasets(const QJsonArray& arr) { - std::vector out; +std::vector parseDsRows(const QJsonArray& arr) { + std::vector out; out.reserve(static_cast(arr.size())); for (const QJsonValue& v : arr) { const QJsonObject o = v.toObject(); - DsNode d; + DsRow d; d.id = str(o, "id"); - d.name = str(o, "name"); - d.ddType = str(o, "ddCode"); + d.dsName = str(o, "dsName"); + d.typeName = str(o, "name"); // 注意:name 字段=ds类型名 + d.ddCode = str(o, "ddCode"); + const QJsonObject f = o.value(QStringLiteral("file")).toObject(); + d.fileName = str(f, "name"); + d.fileUrl = str(f, "url"); + d.fileSize = static_cast(f.value(QStringLiteral("size")).toDouble()); out.push_back(std::move(d)); } return out; diff --git a/src/data/dto/NavDto.hpp b/src/data/dto/NavDto.hpp index 79955b9..3d36326 100644 --- a/src/data/dto/NavDto.hpp +++ b/src/data/dto/NavDto.hpp @@ -19,8 +19,8 @@ std::vector parseProjectList(const QJsonArray& arr); // 结构扁平节点数组(queryProjectStruct 的 data["projectStructList"])→ 模型。 std::vector parseStructNodes(const QJsonArray& arr); -// DS 聚合数组(queryDsByTmObjectId 的 data["value"])→ DsNode。ddCode → ddType。 -std::vector parseDatasets(const QJsonArray& arr); +// data/page / file/page 的 data["list"] 数组 → DsRow(数据行无 file;文件行含 file{name,size,url})。 +std::vector parseDsRows(const QJsonArray& arr); // 扁平 StructNode 按 parentId 建树。叶子(无子节点)=TM。处理:项目直挂 TM、孤儿 parentId、空表。 struct StructTreeNode { diff --git a/src/data/repo/IProjectRepository.hpp b/src/data/repo/IProjectRepository.hpp index e064370..1f46922 100644 --- a/src/data/repo/IProjectRepository.hpp +++ b/src/data/repo/IProjectRepository.hpp @@ -21,7 +21,10 @@ public: virtual RepoResult switchWorkspace(const std::string& tenantId) = 0; virtual RepoResult> listProjects(const std::string& lastProjectId) = 0; virtual RepoResult> loadStructure(const std::string& projectId) = 0; - virtual RepoResult> loadDatasetsOfTm(const std::string& tmObjectId) = 0; + // 按 TM 拉数据集/文件行:classifyType 3=数据(data/page) 1=文件(file/page)。 + virtual RepoResult> loadTmRows(const std::string& projectId, + const std::string& tmObjectId, + int classifyType) = 0; }; } // namespace geopro::data diff --git a/src/data/repo/RepoTypes.hpp b/src/data/repo/RepoTypes.hpp index f78aeff..4307e93 100644 --- a/src/data/repo/RepoTypes.hpp +++ b/src/data/repo/RepoTypes.hpp @@ -3,6 +3,13 @@ #include namespace geopro::data { struct DsNode { std::string id, name, ddType; }; + +// data/page 或 file/page 的一条 ds。数据行只用 dsName/typeName/ddCode;文件行另含 file*。 +struct DsRow { + std::string id, dsName, typeName, ddCode; + std::string fileName, fileUrl; + long long fileSize = 0; +}; struct TmNode { std::string id, name, confCode; std::vector dss; }; struct GsNode { std::string id, name; std::vector tms; }; struct Project { std::string id, name; std::vector gss; }; diff --git a/tests/data/test_nav_dto.cpp b/tests/data/test_nav_dto.cpp index 0c515ef..918b384 100644 --- a/tests/data/test_nav_dto.cpp +++ b/tests/data/test_nav_dto.cpp @@ -64,17 +64,6 @@ TEST(NavDto, ParseStructNodesMapsParentAndType) { EXPECT_EQ(ns[1].type, 2); } -TEST(NavDto, ParseDatasetsMapsDdCodeToDdType) { - const auto arr = arrOf(R"([ - {"id":"ds1","name":"批次1","ddCode":"dd_section","typeName":"剖面"} - ])"); - const auto ds = dto::parseDatasets(arr); - ASSERT_EQ(ds.size(), 1u); - EXPECT_EQ(ds[0].id, "ds1"); - EXPECT_EQ(ds[0].name, "批次1"); - EXPECT_EQ(ds[0].ddType, "dd_section"); -} - TEST(NavDto, BuildStructTreeNestsGsTmAndDirectTm) { const std::vector flat = { {"gs1", "工区1", "", "GS", "", 1}, @@ -155,3 +144,23 @@ TEST(NavDto, BuildStructTreeDropsDsAndTmStaysLeaf) { EXPECT_TRUE(roots[0].children[0].isTm); // TM type2 EXPECT_TRUE(roots[0].children[0].children.empty()); // DS 不进树 } + +TEST(NavDto, ParseDsRowsDataAndFile) { + const auto d = dto::parseDsRows(arrOf(R"([ + {"id":"d1","dsName":"ERT1-WS","name":"电阻率数据","ddCode":"dd_inversion_data"} + ])")); + ASSERT_EQ(d.size(), 1u); + EXPECT_EQ(d[0].id, "d1"); + EXPECT_EQ(d[0].dsName, "ERT1-WS"); + EXPECT_EQ(d[0].typeName, "电阻率数据"); + EXPECT_TRUE(d[0].fileName.empty()); + + const auto f = dto::parseDsRows(arrOf(R"([ + {"id":"f1","dsName":"ERT1-WS.xlsx","name":"","ddCode":"dd_file", + "file":{"name":"ERT1-WS.xlsx","size":62760,"url":"/common/file/x.xlsx"}} + ])")); + ASSERT_EQ(f.size(), 1u); + EXPECT_EQ(f[0].fileName, "ERT1-WS.xlsx"); + EXPECT_EQ(f[0].fileSize, 62760); + EXPECT_EQ(f[0].fileUrl, "/common/file/x.xlsx"); +}