1.先看效果图

2.数据结构
直接将样式封装成一个组件后在父页面调用
下面是数据结构
name:是部门名称,id:是部门id,parent:是父级id,通过父级id对数组进行处理,当parent为null时为最高级
[
{
"name": "测试总部",
"id": 1,
"parent": null
},
{
"name": "测试部门2-2",
"id": 38,
"parent": 33
},
]
3.数据处理
对数据结构进行处理并存储在treeData数组里面,调用buildTree方法将扁平数组转换成树状结构
data() {
return {
treeData: [],
rawData:[
{
"name": "测试部门2-4",
"id": 40,
"parent": 35
},
{
"name": "新组织",
"id": 46,
"parent": 36
},
{
"name": "测试部门1-6",
"id": 45,
"parent": 1
},
{
"name": "测试部门2-8",
"id": 44,
"parent": 34
},
{
"name": "测试部门2-7",
"id": 43,
"parent": 35
},
{
"name": "测试部门2-6",
"id": 42,
"parent": 34
},
{
"name": "测试部门2-5",
"id": 41,
"parent": 34
},
{
"name": "测试总部",
"id": 1,
"parent": null
},
{
"name": "测试部门2-3",
"id": 39,
"parent": 33
},
{
"name": "测试部门2-2",
"id": 38,
"parent": 33
},
{
"name": "测试部门2-1",
"id": 37,
"parent": 33
},
{
"name": "测试部门1-5",
"id": 36,
"parent": 1
},
{
"name": "测试部门1-4",
"id": 35,
"parent": 1
},
{
"name": "测试部门1-3",
"id": 34,
"parent": 1
},
{
"name": "测试部门1-2",
"id": 33,
"parent": 1
},
{
"name": "测试部门1-1",
"id": 32,
"parent": 1
}
]
};
},
mounted() {
this.treeData = this.buildTree(this.rawData);
},
methods: {
// 转换数组 -> 树
buildTree(data, parentId = null) {
// 筛选出当前层级的所有节点,这些节点的 parent 属性等于 parentId
return data
.filter((item) => item.parent === parentId) // 筛选出当前层级的节点
.map((item) => ({
...item, // 复制当前节点的所有属性
children: this.buildTree(data, item.id) // 递归调用 buildTree 方法,构建当前节点的子节点
}));
}
}
转换后的treeData数组是这个样子
[
{
"name": "测试总部",
"id": 1,
"parent": null,
"children": [
{
"name": "测试部门1-6",
"id": 45,
"parent": 1,
"children": []
},
{
"name": "测试部门1-5",
"id": 36,
"parent": 1,
"children": []
},
{
"name": "测试部门1-4",
"id": 35,
"parent": 1,
"children": [
{
"name": "测试部门2-4",
"id": 40,
"parent": 35,
"children": []
},
{
"name": "测试部门2-7",
"id": 43,
"parent": 35,
"children": []
}
]
},
{
"name": "测试部门1-3",
"id": 34,
"parent": 1,
"children": [
{
"name": "测试部门2-8",
"id": 44,
"parent": 34,
"children": []
},
{
"name": "测试部门2-6",
"id": 42,
"parent": 34,
"children": []
},
{
"name": "测试部门2-5",
"id": 41,
"parent": 34,
"children": []
}
]
},
{
"name": "测试部门1-2",
"id": 33,
"parent": 1,
"children": [
{
"name": "测试部门2-3",
"id": 39,
"parent": 33,
"children": []
},
{
"name": "测试部门2-2",
"id": 38,
"parent": 33,
"children": []
},
{
"name": "测试部门2-1",
"id": 37,
"parent": 33,
"children": []
}
]
},
{
"name": "测试部门1-1",
"id": 32,
"parent": 1,
"children": []
}
]
}
]
4.将处理好的数据传递给组件
<div ref="treeContainer" class="tree-container">
<TreeNode v-for="root in treeData" :key="root.id" :node="root" :level="1" />
</div>
5.设置层级样式
组件页面接受数据,显示数据,想设置后面的层级可以自己加level-X样式
<template>
<div class="node">
<!-- 节点主体 -->
<div class="box" :class="`level-${level}`">
{{ node.name }}
</div>
<!-- 子节点 -->
<div v-if="node.children && node.children.length" class="children">
<TreeNode v-for="child in node.children" :key="child.id" :node="child" :level="level + 1" />
</div>
</div>
</template>
<script>
export default {
name: "TreeNode",
props: {
node: Object,
level: { type: Number, default: 1 },
},
};
</script>
<style scoped>
.node {
display: flex;
flex-direction: column;
align-items: center;
/* 父节点与子节点整体居中 */
}
.box {
padding: 10px 10px;
border-radius: 6px;
color: white;
font-weight: bold;
white-space: nowrap;
/* width: 100px; */
text-align: center;
}
/* 各层节点样式(可自行换颜色) */
.level-1 {
background: #4a90e2;
/* 蓝色 */
margin: 10px 0;
}
.level-2 {
background: #27ae60;
/* 绿色 */
margin: 5px;
}
.level-3 {
background: #16a085;
/* 灰色 */
margin: 5px;
}
.level-4 {
background: #f39c12;
/* 橙色 */
margin: 5px;
}
.level-5 {
background: #9b59b6;
/* 紫色 */
margin: 5px;
}
/* 子节点容器 */
.children {
display: flex;
justify-content: center;
flex-wrap: nowrap;
gap: 10px;
margin-top: 20px;
padding: 0 20px;
/* 防止两边节点贴边 */
}
</style>
6.居中显示
数据过多我们得在页面居中展示,需要设置样式和居中计算
样式
//样式
.tree-container {
height: 400px;
overflow-x: auto;
overflow-y: hidden;
border: 1px solid #e4e7ed;
border-radius: 4px;
/* padding: 10px; */
display: flex;
justify-content: flex-start;
/* ✅ 让根节点组在容器中心 */
align-items: flex-start;
box-sizing: border-box;
padding: 10px 40px;
/* ⚠️ 关键修复 */
position: relative;
}
方法
centerTree() {
const container = this.$refs.treeContainer;
if (!container) return;
this.$nextTick(() => {
const scrollWidth = container.scrollWidth;
const clientWidth = container.clientWidth;
// 居中整个内容区域
container.scrollLeft = (scrollWidth - clientWidth) / 2;
});
},
6.完整代码
有父页面和组件
父页面
<template>
<div ref="treeContainer" class="tree-container">
<TreeNode v-for="root in treeData" :key="root.id" :node="root" :level="1" />
</div>
</template>
<script>
export default {
data() {
return {
treeData: [],
rawData: [
{
"name": "测试部门2-4",
"id": 40,
"parent": 35
},
{
"name": "新组织",
"id": 46,
"parent": 36
},
{
"name": "测试部门1-6",
"id": 45,
"parent": 1
},
{
"name": "测试部门2-8",
"id": 44,
"parent": 34
},
{
"name": "测试部门2-7",
"id": 43,
"parent": 35
},
{
"name": "测试部门2-6",
"id": 42,
"parent": 34
},
{
"name": "测试部门2-5",
"id": 41,
"parent": 34
},
{
"name": "测试总部",
"id": 1,
"parent": null
},
{
"name": "测试部门2-3",
"id": 39,
"parent": 33
},
{
"name": "测试部门2-2",
"id": 38,
"parent": 33
},
{
"name": "测试部门2-1",
"id": 37,
"parent": 33
},
{
"name": "测试部门1-5",
"id": 36,
"parent": 1
},
{
"name": "测试部门1-4",
"id": 35,
"parent": 1
},
{
"name": "测试部门1-3",
"id": 34,
"parent": 1
},
{
"name": "测试部门1-2",
"id": 33,
"parent": 1
},
{
"name": "测试部门1-1",
"id": 32,
"parent": 1
}
]
}
},
mounted() {
this.treeData = this.buildTree(this.rawData);
},
methods: {
// 转换数组 -> 树
buildTree(data, parentId = null) {
// 筛选出当前层级的所有节点,这些节点的 parent 属性等于 parentId
return data
.filter((item) => item.parent === parentId) // 筛选出当前层级的节点
.map((item) => ({
...item, // 复制当前节点的所有属性
children: this.buildTree(data, item.id) // 递归调用 buildTree 方法,构建当前节点的子节点
}));
},
centerTree() {
const container = this.$refs.treeContainer;
if (!container) return;
this.$nextTick(() => {
const scrollWidth = container.scrollWidth;
const clientWidth = container.clientWidth;
// 居中整个内容区域
container.scrollLeft = (scrollWidth - clientWidth) / 2;
});
},
}
};
</script>
<style scoped>
.tree-container {
height: 400px;
overflow-x: auto;
overflow-y: hidden;
border: 1px solid #e4e7ed;
border-radius: 4px;
/* padding: 10px; */
display: flex;
justify-content: flex-start;
/* 让根节点组在容器中心 */
align-items: flex-start;
box-sizing: border-box;
padding: 10px 40px;
/* 关键修复 */
position: relative;
}
</style>
组件
<template>
<div class="node">
<!-- 节点主体 -->
<div class="box" :class="`level-${level}`">
{{ node.name }}
</div>
<!-- 子节点 -->
<div v-if="node.children && node.children.length" class="children">
<TreeNode v-for="child in node.children" :key="child.id" :node="child" :level="level + 1" />
</div>
</div>
</template>
<script>
export default {
name: "TreeNode",
props: {
node: Object,
level: { type: Number, default: 1 },
},
};
</script>
<style scoped>
.node {
display: flex;
flex-direction: column;
align-items: center;
/* 父节点与子节点整体居中 */
}
.box {
padding: 10px 10px;
border-radius: 6px;
color: white;
font-weight: bold;
white-space: nowrap;
/* width: 100px; */
text-align: center;
}
/* 各层节点样式(可自行换颜色) */
.level-1 {
background: #4a90e2;
/* 蓝色 */
margin: 10px 0;
}
.level-2 {
background: #27ae60;
/* 绿色 */
margin: 5px;
}
.level-3 {
background: #16a085;
/* 灰色 */
margin: 5px;
}
.level-4 {
background: #f39c12;
/* 橙色 */
margin: 5px;
}
.level-5 {
background: #9b59b6;
/* 紫色 */
margin: 5px;
}
/* 子节点容器 */
.children {
display: flex;
justify-content: center;
flex-wrap: nowrap;
gap: 10px;
margin-top: 20px;
padding: 0 20px;
/* 防止两边节点贴边 */
}
</style>
4587

被折叠的 条评论
为什么被折叠?



