feat(nav): 数据/文件页签接 data-page/file-page(按TM+classifyType拉取,文件页签展示名/大小)
This commit is contained in:
parent
5b18dc44ae
commit
839e5c3487
|
|
@ -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<geopro::data::ProjectSummary>& 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<geopro::data::StructNode>& 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<geopro::data::DsNode>& list) {
|
||||
const QString&, const std::vector<geopro::data::DsRow>& list) {
|
||||
geopro::app::populateDatasetList(datasetList, list);
|
||||
if (datasetTitle)
|
||||
datasetTitle->setText(QStringLiteral("数据集显示栏"));
|
||||
if (datasetTitle) datasetTitle->setText(QStringLiteral("数据集显示栏"));
|
||||
datasetTabs->setTabText(
|
||||
0, QStringLiteral("数据 (%1)").arg(static_cast<int>(list.size())));
|
||||
});
|
||||
QObject::connect(&nav, &geopro::controller::WorkbenchNavController::filesLoaded, fileList,
|
||||
[fileList, datasetTabs](const QString&,
|
||||
const std::vector<geopro::data::DsRow>& list) {
|
||||
geopro::app::populateFileList(fileList, list);
|
||||
datasetTabs->setTabText(
|
||||
1, QStringLiteral("文件 (%1)").arg(static_cast<int>(list.size())));
|
||||
});
|
||||
QObject::connect(&nav, &geopro::controller::WorkbenchNavController::loadFailed, objectTree,
|
||||
[objectTree, &window](const QString& stage, const QString& msg) {
|
||||
if (stage == QStringLiteral("structure") ||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "panels/DatasetListPanel.hpp"
|
||||
|
||||
#include <QColor>
|
||||
#include <QListWidget>
|
||||
#include <QListWidgetItem>
|
||||
#include <QString>
|
||||
|
|
@ -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<geopro::data::DsNode>& dss)
|
||||
{
|
||||
void populateDatasetList(QListWidget* list, const std::vector<geopro::data::DsRow>& 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<geopro::data::DsRow>& 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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<geopro::data::DsNode>& dss);
|
||||
// 数据页签:每条 = dsName +(类型名);UserRole 存 dsId、+1 存 ddCode。
|
||||
void populateDatasetList(QListWidget* list, const std::vector<geopro::data::DsRow>& rows);
|
||||
// 文件页签:每条 = 文件名 +(可读大小);UserRole 存 dsId、+2 存文件 url。空时显示占位。
|
||||
void populateFileList(QListWidget* list, const std::vector<geopro::data::DsRow>& rows);
|
||||
|
||||
} // namespace geopro::app
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ signals:
|
|||
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 structureLoaded(const QString& projectName, const std::vector<geopro::data::StructNode>& nodes);
|
||||
void datasetsLoaded(const QString& tmObjectId, const std::vector<geopro::data::DsNode>& list);
|
||||
void datasetsLoaded(const QString& tmObjectId, const std::vector<geopro::data::DsRow>& list);
|
||||
void filesLoaded(const QString& tmObjectId, const std::vector<geopro::data::DsRow>& list);
|
||||
void loadFailed(const QString& stage, const QString& message);
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -63,12 +63,21 @@ RepoResult<std::vector<StructNode>> ApiProjectRepository::loadStructure(const st
|
|||
return {true, dto::parseStructNodes(r.data.value(QStringLiteral("value")).toArray()), {}};
|
||||
}
|
||||
|
||||
RepoResult<std::vector<DsNode>> 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<std::vector<DsRow>> 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
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@ public:
|
|||
RepoResult<bool> switchWorkspace(const std::string& tenantId) override;
|
||||
RepoResult<std::vector<ProjectSummary>> listProjects(const std::string& lastProjectId) override;
|
||||
RepoResult<std::vector<StructNode>> loadStructure(const std::string& projectId) override;
|
||||
RepoResult<std::vector<DsNode>> loadDatasetsOfTm(const std::string& tmObjectId) override;
|
||||
RepoResult<std::vector<DsRow>> loadTmRows(const std::string& projectId,
|
||||
const std::string& tmObjectId,
|
||||
int classifyType) override;
|
||||
|
||||
private:
|
||||
net::ApiClient& api_;
|
||||
|
|
|
|||
|
|
@ -72,15 +72,20 @@ std::vector<StructNode> parseStructNodes(const QJsonArray& arr) {
|
|||
return out;
|
||||
}
|
||||
|
||||
std::vector<DsNode> parseDatasets(const QJsonArray& arr) {
|
||||
std::vector<DsNode> out;
|
||||
std::vector<DsRow> parseDsRows(const QJsonArray& arr) {
|
||||
std::vector<DsRow> out;
|
||||
out.reserve(static_cast<size_t>(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<long long>(f.value(QStringLiteral("size")).toDouble());
|
||||
out.push_back(std::move(d));
|
||||
}
|
||||
return out;
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ std::vector<ProjectSummary> parseProjectList(const QJsonArray& arr);
|
|||
// 结构扁平节点数组(queryProjectStruct 的 data["projectStructList"])→ 模型。
|
||||
std::vector<StructNode> parseStructNodes(const QJsonArray& arr);
|
||||
|
||||
// DS 聚合数组(queryDsByTmObjectId 的 data["value"])→ DsNode。ddCode → ddType。
|
||||
std::vector<DsNode> parseDatasets(const QJsonArray& arr);
|
||||
// data/page / file/page 的 data["list"] 数组 → DsRow(数据行无 file;文件行含 file{name,size,url})。
|
||||
std::vector<DsRow> parseDsRows(const QJsonArray& arr);
|
||||
|
||||
// 扁平 StructNode 按 parentId 建树。叶子(无子节点)=TM。处理:项目直挂 TM、孤儿 parentId、空表。
|
||||
struct StructTreeNode {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,10 @@ public:
|
|||
virtual RepoResult<bool> switchWorkspace(const std::string& tenantId) = 0;
|
||||
virtual RepoResult<std::vector<ProjectSummary>> listProjects(const std::string& lastProjectId) = 0;
|
||||
virtual RepoResult<std::vector<StructNode>> loadStructure(const std::string& projectId) = 0;
|
||||
virtual RepoResult<std::vector<DsNode>> loadDatasetsOfTm(const std::string& tmObjectId) = 0;
|
||||
// 按 TM 拉数据集/文件行:classifyType 3=数据(data/page) 1=文件(file/page)。
|
||||
virtual RepoResult<std::vector<DsRow>> loadTmRows(const std::string& projectId,
|
||||
const std::string& tmObjectId,
|
||||
int classifyType) = 0;
|
||||
};
|
||||
|
||||
} // namespace geopro::data
|
||||
|
|
|
|||
|
|
@ -3,6 +3,13 @@
|
|||
#include <vector>
|
||||
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<DsNode> dss; };
|
||||
struct GsNode { std::string id, name; std::vector<TmNode> tms; };
|
||||
struct Project { std::string id, name; std::vector<GsNode> gss; };
|
||||
|
|
|
|||
|
|
@ -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<StructNode> 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");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue