vue2框架下的原生html+css+js的树形图结构

概括:

以上树图包含三层;第二层和第三层需要在相同宽度的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>


 

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值