feat(data): buildStructTree 扁平→树(叶子=TM,含直挂/孤儿/空表)

This commit is contained in:
gaozheng 2026-06-09 11:33:14 +08:00
parent a32bd763da
commit 2bc22a55d6
2 changed files with 65 additions and 1 deletions

View File

@ -2,6 +2,9 @@
#include <QJsonValue>
#include <functional>
#include <set>
namespace geopro::data::dto {
namespace {
@ -75,6 +78,35 @@ std::vector<DsNode> parseDatasets(const QJsonArray& arr) {
return out;
}
std::vector<StructTreeNode> buildStructTree(const std::vector<StructNode>&) { return {}; }
std::vector<StructTreeNode> buildStructTree(const std::vector<StructNode>& flat) {
std::set<std::string> ids;
std::set<std::string> hasChild;
for (const auto& n : flat) {
ids.insert(n.id);
if (!n.parentId.empty()) hasChild.insert(n.parentId);
}
// 叶子(无子节点)= TM。
auto isLeaf = [&](const std::string& id) { return hasChild.find(id) == hasChild.end(); };
// 根层节点parentId 为空或 parentId 不在 id 集合内(孤儿)。
auto isRootLevel = [&](const StructNode& n) {
return n.parentId.empty() || ids.find(n.parentId) == ids.end();
};
std::function<std::vector<StructTreeNode>(const std::string&, bool)> build =
[&](const std::string& parentId, bool root) {
std::vector<StructTreeNode> out;
for (const auto& n : flat) {
const bool belongs = root ? isRootLevel(n) : (n.parentId == parentId);
if (!belongs) continue;
if (n.id == parentId) continue; // 防自环
StructTreeNode t;
t.node = n;
t.isTm = isLeaf(n.id);
t.children = build(n.id, false);
out.push_back(std::move(t));
}
return out;
};
return build(std::string(), true);
}
} // namespace geopro::data::dto

View File

@ -74,3 +74,35 @@ TEST(NavDto, ParseDatasetsMapsDdCodeToDdType) {
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},
{"tm1", "测线1", "gs1", "TM", "", 2},
{"tm2", "测线2", "gs1", "TM", "", 2},
{"tmD", "直挂测线", "", "TM", "", 2}, // TM 直挂项目(无 GS
};
const auto roots = dto::buildStructTree(flat);
ASSERT_EQ(roots.size(), 2u); // gs1 + tmD
EXPECT_EQ(roots[0].node.id, "gs1");
EXPECT_FALSE(roots[0].isTm); // 非叶 = GS
ASSERT_EQ(roots[0].children.size(), 2u);
EXPECT_EQ(roots[0].children[0].node.id, "tm1");
EXPECT_TRUE(roots[0].children[0].isTm); // 叶 = TM
EXPECT_EQ(roots[1].node.id, "tmD");
EXPECT_TRUE(roots[1].isTm); // 直挂项目的叶子 = TM
}
TEST(NavDto, BuildStructTreeOrphanParentBecomesRoot) {
const std::vector<StructNode> flat = {
{"tmX", "孤儿测线", "ghost", "TM", "", 2}, // parentId 不在集合内
};
const auto roots = dto::buildStructTree(flat);
ASSERT_EQ(roots.size(), 1u);
EXPECT_EQ(roots[0].node.id, "tmX");
EXPECT_TRUE(roots[0].isTm);
}
TEST(NavDto, BuildStructTreeEmpty) {
EXPECT_TRUE(dto::buildStructTree({}).empty());
}