vue树形结构折叠展开过渡组件和递归组件封装

740 阅读1分钟

概述

在日常实际开放当中,我们必不可少的会遇到树形结构的数据,然后根据数据,渲染出树形结构菜单,比如说后台管理系统的侧边栏和图书目录等等,我们一般遇到这种需求,一般会采用递归组件,渲染对应的树形结构。然后根据实际需求可以进行扩展,为了给用户更好的用户体验,如果给这种结构加上过渡动画,整个页面都会显得更加生动。以下是自定义封装的过渡动画组件。

组件代码

定义一个CommonCollapseTransition组件

<template>
  <transition
    v-on:before-enter="beforeEnter"
    v-on:enter="enter"
    v-on:after-enter="afterEnter"
    v-on:before-leave="beforeLeave"
    v-on:leave="leave"
    v-on:after-leave="afterLeave"
  >
    <slot></slot>
  </transition>
</template>

<script>
  export default {
    name: 'CommonCollapseTransition',
    methods: {
      beforeEnter(el) {
        el.classList.add('collapse-transition');
        if (!el.dataset) el.dataset = {};

        el.dataset.oldPaddingTop = el.style.paddingTop;
        el.dataset.oldPaddingBottom = el.style.paddingBottom;

        el.style.height = '0';
        el.style.paddingTop = 0;
        el.style.paddingBottom = 0;
      },
      enter(el) {
        el.dataset.oldOverflow = el.style.overflow;
        if (el.scrollHeight !== 0) {
          el.style.height = el.scrollHeight + 'px';
          el.style.paddingTop = el.dataset.oldPaddingTop;
          el.style.paddingBottom = el.dataset.oldPaddingBottom;
        } else {
          el.style.height = '';
          el.style.paddingTop = el.dataset.oldPaddingTop;
          el.style.paddingBottom = el.dataset.oldPaddingBottom;
        }

        el.style.overflow = 'hidden';
      },

      afterEnter(el) {
        el.classList.remove('collapse-transition');

        el.style.height = '';
        el.style.overflow = el.dataset.oldOverflow;
      },

      beforeLeave(el) {
        if (!el.dataset) el.dataset = {};
        el.dataset.oldPaddingTop = el.style.paddingTop;
        el.dataset.oldPaddingBottom = el.style.paddingBottom;
        el.dataset.oldOverflow = el.style.overflow;

        el.style.height = el.scrollHeight + 'px';
        el.style.overflow = 'hidden';
      },

      leave(el) {
        if (el.scrollHeight !== 0) {
          el.classList.add('collapse-transition');

          el.style.height = 0;
          el.style.paddingTop = 0;
          el.style.paddingBottom = 0;
        }
      },

      afterLeave(el) {
        el.classList.remove('collapse-transition');

        el.style.height = '';
        el.style.overflow = el.dataset.oldOverflow;
        el.style.paddingTop = el.dataset.oldPaddingTop;
        el.style.paddingBottom = el.dataset.oldPaddingBottom;
      },
    },
  };
</script>

<style lang="less">
  .collapse-transition {
    transition: all 0.3s ease-in-out;
  }
</style>

使用方法

谈到使用,我们首先应该构造一个树形结构菜单,这就需要用到递归组件,不知道啥是递归组件的,请移步到vue官方对递归组件的介绍

以下是一个递归组件目录树,数据采用mock模拟。

mock树形结构数据

export const previewBookInfoList = Mock.mock({
  'data|15-40': [
    {
      Id: '@id()',
      Title: '@ctitle()',
      'readTime|5000-30000': 5000,
      show: false,
      active: false,
      'children|4-10': [
        {
          Id: '@id()',
          Title: '@ctitle()',
          'readTime|500-5000': 200,
          show: false,
          active: false,
          'children|4-10': [
            {
              Id: '@id()',
              Title: '@ctitle()',
              'readTime|500-5000': 200,
              show: false,
              active: false,
            },
          ],
        },
      ],
    },
  ],
});

递归组件


<template>
  <ul class="aside-bar" :style="{ paddingLeft: level * 12 + 'px' }">
    <li v-for="(item, index) in list" :key="item.Id" :class="['catalog-level-' + level, 'catalog-item']">
      <div class="content-wrap">
        <span :class="['title', item.Id == currentActive ? 'active' : '']" @click.stop="getCalogBookList(item)"
          >{{ item.Title }} {{ '(' + item.readTime + ')' }}</span
        >
        <span
          class="catalog-fold icon"
          v-if="item.children && item.children.length && !item.show"
          @click.stop="openChild(item)"
        ></span>
        <span
          class="catalog-unfold icon"
          @click.stop="openChild(item)"
          v-if="item.show && item.children && item.children.length && item.show"
        ></span>
      </div>
      <!-- S 递归组件 ,在这里使用过渡组件-->
      <common-collapse-transition>
        <aside-bar
          :level="level + 1"
          :list="item.children"
          v-if="item.children && item.children.length && item.show"
          :currentActive="currentActive"
          @getCalogBookList="getCalogBookList"
        ></aside-bar>
      </common-collapse-transition>

      <!-- E 递归组件,在这里使用过渡组件 -->
    </li>
  </ul>
</template>

<script>
  export default {
    name: 'AsideBar',
    components: {},
    props: {
      level: {
        type: Number,
        default: 1,
      },
      list: {
        type: Array,
        default() {
          return [];
        },
      },
      currentActive: {
        type: String,
        default: '-1',
      },
    },
    data() {
      return {};
    },

    methods: {
      openChild(item) {
        item.show = !item.show;
      },
      getCalogBookList(item) {
        console.log(item);
        if (item.Id == this.currentActive) return;
        this.$emit('getCalogBookList', item);
      },
    },
  };
</script>

<style lang="less">
  .aside-bar {
    font-size: 12px;
    line-height: 38px;
    .catalog-level-1 {
      color: #333;
    }
    .catalog-item {
      transition: color 0.3s;
      cursor: pointer;
      .title {
        &:hover {
          color: #1a73c5;
          font-weight: bold;
        }
      }
      .title.active {
        color: #1a73c5;
        font-weight: bold;
      }
    }
    .content-wrap {
      position: relative;
      padding-left: 20px;
    }

    .icon {
      position: absolute;
      left: 0;
      top: 50%;
      transform: translate(0, -50%);
      width: 11px;
      height: 11px;
      background-repeat: no-repeat;
    }
    .catalog-fold {
      background-image: url('../../assets/images/partnerSchool/catalog-fold.png');
    }
    .catalog-unfold {
      background-image: url('../../assets/images/partnerSchool/catalog-unfold.png');
    }
  }
</style>

效果

image.png

总结

以上知识需要首先掌握递归组件,其次需要对dom元素的尺寸获取设置要知道,上面的看懂了,日常工作中的树形结构渲染,都基本上一个思路,递归很重要