概括:
以上树图包含三层;第二层和第三层需要在相同宽度的div内,且点击第二层的div展示出第三层的数据和结构.
一.创建子组件TreeItem
1.以下是html的结构,创建需要展示的三层结构,第二层需要有个点击事件toggleSecondItemAfter()
<template>
<div id="TreeItem">
<--第一层-->
<div class="first_floor same">
<div class="item first_item" v-for="(item, key) in level1" :key="'level1' + key">
<div class="name">
{{ item.name }}
</div>
<div class="content">
{{ item.content }}
</div>
<div class="rate">
{{ item.rate }}
</div>
</div>
</div>
<--第2层-->
<div class="second_floor same">
<div
class="item second_item"
v-for="(item, key) in level2"
:key="'level2' + key"
:style="{ '--after-display': item.id === activeSecondItemId ? 'block' : 'none' }"
@click="toggleSecondItemAfter(item.id)"
>
<div class="name">
{{ item.name }}
</div>
<div class="content">
{{ item.content }}
</div>
<div class="rate">
{{ item.rate }}
</div>
</div>
</div>
<--第3层-->
<div class="third_floor same" v-if="showThirdFloor">
<div class="item third_item" v-for="(item, key) in level3" :key="'level3' + key">
<div class="name">
{{ item.name }}
</div>
<div class="content">
<div class="content">
{{ item.content }}
</div>
<div class="rate">
{{ item.rate }}
</div>
</div>
</div>
</div>
<div v-else></div>
</div>
</template>
2.完善css;图中的指引线是是用伪元素来实现的(作者习惯用less)
第二层和第三层的::after微伪元素的宽度,都是通过width: var(--after-width3, 100%)这种变量的方式来获取宽度,具体获取方法在js代码里面.
<style lang="less" scoped>
#TreeItem {
.same {
height: 200px;
display: flex;
justify-content: space-evenly;
.item {
width: 200px;
height: 100px;
background-color: #fff;
border: 1px solid #000;
}
}
.first_floor {
position: relative;
.first_item {
.name {
border-bottom: 1px solid #000;
background-color: blue;
color: #000;
}
&::before {
content: '';
height: 50px;
width: 1px;
position: absolute;
top: 50%;
left: 50%;
background-color: #000;
}
&::after {
content: '';
height: 1px;
width: var(--after-width2, 100%); /* 使用变量 */
// width: 100%;
position: absolute;
top: 75%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #000;
}
}
}
.second_floor {
.second_item {
.name {
border-bottom: 1px solid #000;
background-color: green;
color: #fff;
}
position: relative;
&::before {
content: '';
height: 52px;
width: 1px;
position: absolute;
top: -50%;
left: 50%;
background-color: #000;
}
&::after {
content: '';
height: 52px;
width: 1px;
position: absolute;
top: 100%;
left: 50%;
background-color: #000;
display: var(--after-display, none);
}
}
}
.third_floor {
position: relative;
.name {
border-bottom: 1px solid #000;
background-color: orange;
color: #fff;
}
.third_item {
position: relative;
&::before {
content: '';
height: 52px;
width: 1px;
position: absolute;
top: -52%;
left: 50%;
background-color: #000;
}
}
&::after {
content: '';
height: 1px;
width: var(--after-width3, 100%); /* 使用变量 */
// width: 100%;
position: absolute;
top: -25%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #000;
}
}
}
</style>
3.这是js代码,点击事件toggleSecondItemAfter,以及获取引导线宽度的计算公式
<script>
export default {
name: 'TreeItem',
//接收父组件传过来的树形结构数据
props: {
treeData: {
type: Object,
},
},
data() {
return {
showSecondItemAfter: false,
showThirdFloor: false, // 控制第三层显示的状态
activeSecondItemId: null,
thirdList: [],
secondList: [],
level1: [],
level2: [],
level3: [],
}
},
watch: {
level3: {
handler(newVal, oldVal) {
if (newVal !== oldVal) {
this.$nextTick(() => {
this.updateAfterWidth()
})
}
},
deep: true,
immediate: true,
},
},
computed: {
thirdFloorItemCount() {
return this.$el.querySelectorAll('.third_floor .item').length
},
secondFloorItemCount() {
return this.$el.querySelectorAll('.second_floor .item').length
},
},
mounted() {
console.log(this.treeData, 'this.treeData')
this.sortIntoLevels(this.treeData)
this.$nextTick(() => {
this.updateAfterWidth()
})
},
methods: {
sortIntoLevels(node) {
if (node.level === 1) this.level1.push(node)
if (node.level === 2) this.level2.push(node)
// if (node.level === 3) this.level3.push(node)
if (node.children) {
node.children.forEach(child => this.sortIntoLevels(child))
}
},
//点击第二层格子
toggleSecondItemAfter(itemId) {
let result = this.level2.filter(item => item.id === itemId)
// if (result.length > 0) {
if (result[0]?.children?.length > 0) {
this.activeSecondItemId = itemId
console.log(result, 'result')
this.level3 = result[0].children
this.showThirdFloor = true // 显示第三层
this.updateAfterWidth() //获取引导线宽度
} else {
this.showThirdFloor = false
this.level3 = []
this.activeSecondItemId = null
}
},
//获取引导线宽度的方法
updateAfterWidth() {
const firstFloor = this.$el.querySelector('.first_floor')
const thirdFloor = this.$el.querySelector('.third_floor')
if (thirdFloor) {
console.log(thirdFloor.offsetWidth, 'thirdFloor')
const thirdFloorWidth = thirdFloor.offsetWidth
const itemCount = this.$el.querySelectorAll('.third_floor .item').length //div个数
console.log(itemCount, 'itemCount')
//获取宽度的计算公式
const width =
((thirdFloorWidth - itemCount * 200) / (itemCount + 1) + 200) * (itemCount - 1) + 'px'
console.log(width, 'width3')
thirdFloor.style.setProperty('--after-width3', width)
}
this.$nextTick(() => {
const secondFloor = this.$el.querySelector('.second_floor')
if (secondFloor) {
console.log(secondFloor.offsetWidth, 'secondFloor')
const secondFloorWidth = secondFloor.offsetWidth
const itemCount = this.secondFloorItemCount //div个数
const width =
((secondFloorWidth - itemCount * 200) / (itemCount + 1) + 200) * (itemCount - 1) + 'px'
console.log(width, 'width2')
firstFloor.style.setProperty('--after-width2', width)
}
})
},
},
}
</script>
二.以下的子组件TreeItem中的完整代码:
<template>
<div id="TreeItem">
<div class="first_floor same">
<div class="item first_item" v-for="(item, key) in level1" :key="'level1' + key">
<div class="name">
{{ item.name }}
</div>
<div class="content">
{{ item.content }}
</div>
<div class="rate">
{{ item.rate }}
</div>
</div>
</div>
<div class="second_floor same">
<div
class="item second_item"
v-for="(item, key) in level2"
:key="'level2' + key"
:style="{ '--after-display': item.id === activeSecondItemId ? 'block' : 'none' }"
@click="toggleSecondItemAfter(item.id)"
>
<div class="name">
{{ item.name }}
</div>
<div class="content">
{{ item.content }}
</div>
<div class="rate">
{{ item.rate }}
</div>
</div>
</div>
<div class="third_floor same" v-if="showThirdFloor">
<div class="item third_item" v-for="(item, key) in level3" :key="'level3' + key">
<div class="name">
{{ item.name }}
</div>
<div class="content">
<div class="content">
{{ item.content }}
</div>
<div class="rate">
{{ item.rate }}
</div>
</div>
</div>
</div>
<div v-else></div>
</div>
</template>
<script>
export default {
name: 'TreeItem',
props: {
treeData: {
type: Object,
},
},
data() {
return {
showSecondItemAfter: false,
showThirdFloor: false, // 控制第三层显示的状态
activeSecondItemId: null,
thirdList: [],
secondList: [],
level1: [],
level2: [],
level3: [],
}
},
watch: {
level3: {
handler(newVal, oldVal) {
if (newVal !== oldVal) {
this.$nextTick(() => {
this.updateAfterWidth()
})
}
},
deep: true,
immediate: true,
},
},
computed: {
thirdFloorItemCount() {
return this.$el.querySelectorAll('.third_floor .item').length
},
secondFloorItemCount() {
return this.$el.querySelectorAll('.second_floor .item').length
},
},
mounted() {
console.log(this.treeData, 'this.treeData')
this.sortIntoLevels(this.treeData)
this.$nextTick(() => {
this.updateAfterWidth()
})
},
methods: {
sortIntoLevels(node) {
if (node.level === 1) this.level1.push(node)
if (node.level === 2) this.level2.push(node)
// if (node.level === 3) this.level3.push(node)
if (node.children) {
node.children.forEach(child => this.sortIntoLevels(child))
}
},
toggleSecondItemAfter(itemId) {
let result = this.level2.filter(item => item.id === itemId)
// if (result.length > 0) {
if (result[0]?.children?.length > 0) {
this.activeSecondItemId = itemId
console.log(result, 'result')
this.level3 = result[0].children
this.showThirdFloor = true // 显示第三层
this.updateAfterWidth()
} else {
this.showThirdFloor = false
this.level3 = []
this.activeSecondItemId = null
}
},
updateAfterWidth() {
const firstFloor = this.$el.querySelector('.first_floor')
const thirdFloor = this.$el.querySelector('.third_floor')
if (thirdFloor) {
console.log(thirdFloor.offsetWidth, 'thirdFloor')
const thirdFloorWidth = thirdFloor.offsetWidth
const itemCount = this.$el.querySelectorAll('.third_floor .item').length //div个数
console.log(itemCount, 'itemCount')
const width =
((thirdFloorWidth - itemCount * 200) / (itemCount + 1) + 200) * (itemCount - 1) + 'px'
console.log(width, 'width3')
thirdFloor.style.setProperty('--after-width3', width)
}
this.$nextTick(() => {
const secondFloor = this.$el.querySelector('.second_floor')
if (secondFloor) {
console.log(secondFloor.offsetWidth, 'secondFloor')
const secondFloorWidth = secondFloor.offsetWidth
const itemCount = this.secondFloorItemCount //div个数
const width =
((secondFloorWidth - itemCount * 200) / (itemCount + 1) + 200) * (itemCount - 1) + 'px'
console.log(width, 'width2')
firstFloor.style.setProperty('--after-width2', width)
}
})
},
},
}
</script>
<style lang="less" scoped>
#TreeItem {
.same {
height: 200px;
display: flex;
justify-content: space-evenly;
.item {
width: 200px;
height: 100px;
background-color: #fff;
border: 1px solid #000;
}
}
.first_floor {
position: relative;
.first_item {
.name {
border-bottom: 1px solid #000;
background-color: blue;
color: #000;
}
&::before {
content: '';
height: 50px;
width: 1px;
position: absolute;
top: 50%;
left: 50%;
background-color: #000;
}
&::after {
content: '';
height: 1px;
width: var(--after-width2, 100%); /* 使用变量 */
// width: 100%;
position: absolute;
top: 75%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #000;
}
}
}
.second_floor {
.second_item {
.name {
border-bottom: 1px solid #000;
background-color: green;
color: #fff;
}
position: relative;
&::before {
content: '';
height: 52px;
width: 1px;
position: absolute;
top: -50%;
left: 50%;
background-color: #000;
}
&::after {
content: '';
height: 52px;
width: 1px;
position: absolute;
top: 100%;
left: 50%;
background-color: #000;
display: var(--after-display, none);
}
}
}
.third_floor {
position: relative;
.name {
border-bottom: 1px solid #000;
background-color: orange;
color: #fff;
}
.third_item {
position: relative;
&::before {
content: '';
height: 52px;
width: 1px;
position: absolute;
top: -52%;
left: 50%;
background-color: #000;
}
}
&::after {
content: '';
height: 1px;
width: var(--after-width3, 100%); /* 使用变量 */
// width: 100%;
position: absolute;
top: -25%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #000;
}
}
}
</style>
三.以下是父组件里调用的代码
<template>
<div id="app">
<tree-item :treeData="list"></tree-item>
</div>
</template>
<script>
import TreeItem from './components/TreeItem.vue'
export default {
name: 'App',
components: {
TreeItem,
},
data() {
return {
treeData: {},
list: {
id: 1,
name: '一层',
content: '1800万',
rate: '89%',
level: 1,
children: [
{
id: 2,
name: '2层xxx',
level: 2,
content: '1800万',
rate: '89%',
children: [
{
id: 2.1,
name: '3层xxx',
level: 3,
content: '1800万',
rate: '89%',
},
{ id: 2.2, name: '3层xxx', level: 3, content: '1800万', rate: '89%' },
{ id: 2.3, name: '3层xxx', level: 3, content: '1800万', rate: '89%' },
{ id: 2.4, name: '3层xxx', level: 3, content: '1800万', rate: '89%' },
{ id: 2.5, name: '3层xxx', level: 3, content: '1800万', rate: '89%' },
{ id: 2.6, name: '3层xxx', level: 3, content: '1800万', rate: '89%' },
],
},
{
id: 3,
name: '2层xxx',
level: 2,
content: '1800万',
rate: '89%',
children: [
{
id: 3.1,
name: '3层xxx',
level: 3,
content: '1800万',
rate: '89%',
},
{ id: 3.2, name: '3层xxx', level: 3, content: '1800万', rate: '89%' },
{ id: 3.3, name: '3层xxx', level: 3, content: '1800万', rate: '89%' },
{ id: 3.4, name: '3层xxx', level: 3, content: '1800万', rate: '89%' },
],
},
{
id: 4,
name: '2层xxx',
level: 2,
content: '1800万',
rate: '89%',
children: [
{
id: 4.1,
name: '3层xxx',
level: 3,
content: '1800万',
rate: '89%',
},
{ id: 4.2, name: '3层xxx', level: 3, content: '1800万', rate: '89%' },
{ id: 4.3, name: '3层xxx', level: 3, content: '1800万', rate: '89%' },
],
},
{
id: 5,
name: '2层xxx',
level: 2,
content: '1800万',
rate: '89%',
children: [
{
id: 5.1,
name: '3层xxx',
level: 3,
content: '1800万',
rate: '89%',
},
{ id: 5.2, name: '3层xxx', level: 3, content: '1800万', rate: '89%' },
],
},
{
id: 6,
name: '2层xxx',
level: 2,
content: '1800万',
rate: '89%',
},
{
id: 7,
name: '2层xxx',
level: 2,
content: '1800万',
rate: '89%',
},
],
},
}
},
mounted() {},
methods: {},
}
</script>