Vue可收缩多级菜单的实现

本文以 Vue使用ivew组件实现响应式布局.为基础继续进行

1.递归组件实战

封装菜单组件
1.在components文件夹中新建side-menu文件夹
在该文件夹中新建side-menu.vue、re-submenu.vue、re-dropdown.vue、index.js文件
2.配置index.js

// 另外两个组件我们在side-menu中用,所以不用导出
import SideMenu from './side-menu.vue'
export default SideMenu

在layout.vue中引入定义SideMenu组件

import SideMenu from '_c/side-menu'
components: {
    SideMenu
  },
<Sider collapsible hide-trigger breakpoint='sm' v-model="collapsed">
        <side-menu :collapsed="collapsed"></side-menu>
</Sider>

3.在配置递归组件
3.1在side-menu.vue配置基本框架

<template>
  <div class="side-menu-wrapper">
    <!-- 插槽为了放置左上角的logo -->
    <slot></slot>
    <!-- 展开时候显示Menu -->
    <Menu width="auto" theme="dark">
    </Menu>
    <!-- 收起时显示dropdown -->
    <div>

    </div>
  </div>
</template>
<script>
export default {
  name: "SideMenu",
}
</script>
<style lang="less">
.side-menu-wrapper{
  width: 100%;
}
</style>

3.2配置props定义要从父组件接受的值

props: {
    collapsed: {
      type: Boolean,
      default: false
    },
    list: {
      type: Array,
      // 如果默认值为空数组,需要使用函数return一个值,该值为空数组
      default: () => []
    }
  }

3.3继续配置递归组件

<template>
  <div class="side-menu-wrapper">
    <!-- 插槽为了放置左上角的logo -->
    <slot></slot>
    <!-- 展开时候显示Menu -->
    <Menu width="auto" theme="dark">
      <template v-for="item in list">
        <!--此时要判断list有无子集-->
        <!-- 有子集 -->
        <Submenu v-if="item.children" :key="`menu_${item.name}`" :name="item.name">
          <!-- 在此考虑是否有未知层级 -->
        </Submenu>
        <menu-item v-else :key="`menu_${item.name}`" :name="item.name">
		<icon :type="item.icon"/>
		{{ item.title }}
</menu-item>
      </template>
    </Menu>
    <!-- 收起时显示dropdown -->
    <div>

    </div>
  </div>
</template>

3.3.1在re-submenu.vue文件中进行多级组件封装
注意submenu和menu-item为同级关系,我们先以Submenu作为递归组件名,封装好后更换

<template>

</template>
<script>
export default {
  name: 'ReSubmenu',
  props: {
    // 定义接受数据,接受的应该是含有children的数据对象
    parent: {
      type: Object,
      default: () => ({})
    }
  }
}
</script>
<style lang="less">

</style>

到此我们需要从side-menu中获取数据
3.4在side-menu.vue引入并定义ReSubmenu组件,并且把子组件需要的值传入

import ReSubmenu from './re-submenu.vue'

components: {
    ReSubmenu
  },
<Menu width="auto" theme="dark">
      <template v-for="item in list">
        <!--此时要判断list有无子集-->
        <!-- 有子集 -->
        <re-submenu
        v-if="item.children"
        :key="`menu_${item.name}`"
        :name="item.name"
        :parent="item"
        >
          <!-- 考虑是否有未知层级 -->
        </re-submenu>
        <menu-item v-else :key="`menu_${item.name}`" :name="item.name">{{ item.title }}</menu-item>
      </template>
    </Menu>

3.41继续在re-submenu.vue页面中封装递归组件,此时我们已经接受到父组件传来的数据

<template :name="parent.name">
  <Submenu>
    <template slot="title">
      <icon :type="parent.icon"/>
      {{ parent.title }}
    </template>
  </Submenu>
</template>

3.42在re-submenu.vue子组件中使用父组件的逻辑,也就是调用他自身
所以在之前要给递归组件命名
这里的list换成parent.children

<template>
  <Submenu :name="parent.name">
    <template slot="title">
      <icon :type="parent.icon"/>
      {{ parent.title }}
    </template>
    <template v-for="item in parent.children">
        <!--此时要判断list有无子集-->
        <!-- 有子集 -->
        <re-submenu
        v-if="item.children"
        :key="`menu_${item.name}`"
        :name="item.name"
        :parent="item"
        >
        </re-submenu>
        <menu-item v-else :key="`menu_${item.name}`" :name="item.name">
          <icon :type="item.icon"/>
          {{ item.title }}
          </menu-item>
      </template>
  </Submenu>
</template>

4.收缩情况下的side-menu组件
收缩后菜单有children则显示可以拓展箭头,没有children则显示小图标
4.1先来写没有children时的状态

<template>
  <div class="side-menu-wrapper">
    <!-- 插槽为了放置左上角的logo -->
    <slot></slot>
    <!-- 收起时显示dropdown -->
    <div>
        <template v-for="item in list">
       		 <!-- 有children的情况 -->
       		 
            <!-- 没有children的情况 -->
            <Tooltip transfer :content="item.title" placement="right" :key="`drop_${item.name}`">
              <span class="drop-menu-span">
                <Icon :type="item.icon" :size="20"></Icon>
              </span>
            </Tooltip>
        </template>
    </div>
  </div>
</template>

4.1.2给span标签添加样式

<style lang="less">
.side-menu-wrapper{
  width: 100%;
  .drop-menu-span, .ivu-tooltip{
    display: inline-block;
    width: 100%;
    color: #fff;
    text-align: center;
    padding: 5px 0;
  }
}
</style>

Vue可收缩多级菜单的实现_第1张图片

4.2当有需要渲染的数据children的时候
1.在re-dropdown.vue中封装递归组件

<template>
    <Dropdown>
      <!-- 这里的a标签和我们一级的标签是同级的,所以直接复制side-menu.vue中的a标签 -->
      <span class="drop-menu-span">
           <Icon :type="item.icon" :size="20"></Icon>
           <span>{{ parent.title }}</span>
      </span>
    <DropdownMenu slot="list" >
      <template v-for="item in parent.children">
        <re-dropdown v-if="item.children" :key="`drop_${item.name}`" :parent="item"></re-dropdown>
        <DropdownItem  v-else :key="`drop_${item.name}`" :name="item.name">
			<Icon :type="parent.icon" :color='iconColor' :size="20"></Icon>
			{{ item.title }}
			</DropdownItem>
      </template>
    </DropdownMenu>
   </Dropdown>
</template>
<script>
export default {
  name: 'ReDropdown',
  props: {
    parent: {
      type: Object,
      default: () => ({})
    }
  }
}
</script>

<style lang="less">

</style>

2.在side-menu.vue中注册并定义re-dropdown.vue组件,同时引入

<!-- 有children的情况 -->
            <re-dropdown v-if="item.children" :key="`drop_${item.name}`" :parent="item"></re-dropdown>
import ReDropdown from './re-dropdown.vue'

components: {
    ReSubmenu,
    ReDropdown
  },

3.在side-menu.vue中继续优化
取消样式中的color,在icon中定义

     <Icon :type="item.icon" color="#fff" :size="20"></Icon>

4.在side-menu.vue中传递颜色值icon-color给子组件

            <re-dropdown v-if="item.children" icon-color="#fff" :key="`drop_${item.name}`" :parent="item"></re-dropdown>

5.在re-dropdown.vue中定义需要从父组件的到的值并使用

props: {
    parent: {
      type: Object,
      default: () => ({})
    },
    iconColor: {
      type: String,
      default: '#515a6e'
    }
  }
           <Icon :type="parent.icon" :color='iconColor' :size="20"></Icon>

Vue可收缩多级菜单的实现_第2张图片
6.第一级不显示title,里面的内容显示title
在re-dropdown.vue组件中定义我们需要的值

showTitle: {
      type: Boolean,
      default: true
    }

在side-menu.vue中定义该属性并设置值传入子组件

            <re-dropdown v-if="item.children" :show-title="false" icon-color="#fff" :key="`drop_${item.name}`" :parent="item"></re-dropdown>

回到re-dropdown.vue中使用

           <span v-if="showTitle">{{ parent.title }}</span>

7.在re-dropdown.vue中将DropdownItem中的颜色设为定值

           <Icon :type="item.icon" color='#515a6e' :size="20"></Icon>

Vue可收缩多级菜单的实现_第3张图片

8.在re-submenu.vue给子集菜单设置居中样式
1.在re-submenu.vue中的子集菜单的一级标签绑定一个动态style

      <span class="drop-menu-span" :style="titleStyle">

2.配置计算属性

computed: {
    titleStyle () {
      return {
        textAlign: this.showTitle ? 'left' : 'center',
        paddingLeft: this.showTitle ? '16px' : ''
      }
    }
  }

4.3给主菜单logo设置居中
1在side-menu.vue中给包裹示dropdown组件的div设置class类名

<div class="drop-wrapper">

2.通过直接子集选择器找到主菜单的logo并配置样式

<style lang="less">
.side-menu-wrapper{
  width: 100%;
  .drop-menu-span, .ivu-tooltip{
    display: inline-block;
    width: 100%;
    text-align: center;
    padding: 5px 0;
  }
  .drop-wrapper > .ivu-dropdown{
    display: block;
    margin: 0 auto;
    padding: 5px;
  }
}
</style>

4.收缩展开的处理
由于收缩和展开为较为频繁的点击操作所以我们在side-menu.vue添加v-show

<!-- 展开时候显示Menu -->
    <Menu v-show="!collapsed" width="auto" theme="dark">
<!-- 收起时显示dropdown -->
    <div v-show="!collapsed" class="drop-wrapper">

5.通过绑定事件找到当前的菜单项
1.展开状态下
side-menu.vue中

    <Menu v-if="!collapsed" width="auto" theme="dark" @on-select="handleSelect">

methods: {
    handleSelect (name) {
      console.log(name)
    }
  }

2.收缩状态下
side-menu.vue中

              <span @click="handleClick(item.name)" class="drop-menu-span">

3.在re-submenu.vue中添加事件并定义

    <Dropdown @on-click="handleClick" placement="right-start">

在事件中我们抛出一个事件

methods: {
    handleClick (name) {
      if (!this.showTitle) this.$emit('on-select', name)
    }
  }

在side-menu.vue 中

 <!-- 有children的情况 -->
            <re-dropdown @on-select="handleSelect" v-if="item.children" :show-title="false" icon-color="#fff" :key="`drop_${item.name}`" :parent="item"></re-dropdown>

获得了当前菜单项信息
Vue可收缩多级菜单的实现_第4张图片

================================================

最终效果
展开状态
Vue可收缩多级菜单的实现_第5张图片
收缩状态
Vue可收缩多级菜单的实现_第6张图片

你可能感兴趣的:(Vue)