Vue3 递归无限级菜单

886 阅读5分钟

Vue 其他相关文章:

Vue3 异步组件, 优化你的页面体验

Vue Router 全面详解--【一、从零开始搭建Vue Router,一步一步的掌握Vue Router】

还只会使用开原的组件吗,有没有想过自己去实现一下这些开源ui框架的某个组件的功能呢。今天就用vue3 + 递归思想实现一个无限极的菜单

无限级菜单结构实现

首先来思考 代码的结构

  1. 首先有个Menu 文件夹
  2. menu 文件夹下面有个index.vue出口文件
  3. menu MenuItem.vue 循环的菜单项
  4. 循环的菜单里面又可能有子菜单,所以就又要去引用menu 下面的index.vue,递归就开始形成了

来看代码的实现: Menu 下面的 index.vue

<template>
  <div class="menu-list">
    <MenuItem v-for="item in menuList" :key="item.title" :menuData="item" />
  </div>
</template>
<script setup>
import MenuItem from './MenuItem.vue'
const props = defineProps({
  menuList: {
    type: Array
  }
})
</script>
<style>
.menu-list {
  background: #181818;
  color: #fff;
  padding-left: 20px;
}
</style>

menu 下面的MenuItem.vue

<template>
  <div class="menu-item">
    <p class="menu-text">{{ menuData.title }}</p>
    <Menu :menuList="menuData.children" v-if="menuData.children && menuData.children.length > 0" />
  </div>
</template>
<script setup>
import Menu from './index.vue'
const props = defineProps({
  menuData: {
    type: Object,
    default () {
      return []
    }
  }
})
</script>
<style>
.menu-text {
  margin: 0;
  padding: 10px;
  cursor: pointer;
}
.menu-text:hover {
  background: #333333;
}
</style>

菜单结构数据:

export const menuList = [
  {
    title: '菜单一级',
    children: [
      {
        title: '菜单二级1'
      },
      {
        title: '菜单二级2'
      },
      {
        title: '菜单二级3',
        children: [
          {
            title: '菜单三级1'
          },
          {
            title: '菜单三级1'
          },
        ]
      }
    ]
  },
  {
    title: '菜单一级2',
  },
  {
    title: '菜单一级3',
  },
  {
    title: '菜单一级4',
  }
]

页面使用

<template>
  <div class="book-content">
    <Menu :menuList="menuList"/>
  </div>
</template>
<script setup>
import Menu from '@/components/Menu/index.vue'
import { menuList } from './data.js'
</script>
<style>
</style>

一个简单的无限级菜单就基本形成了,来看下效果

image.png

基础菜单缺陷思考

是不是很开心呢!别高兴太早,还有些功能还没实现呢!

1.菜单默认一般除第一级菜单外都是收起来的,

2.菜单一般都是可以点击收缩和展开的,现在菜单还没法点击呢!

菜单交互功能实现

接下来就来实现这两个功能

首先来看默认只显示一级菜单, 一级以下菜单收起的实现 怎么实现呢?思考一分钟。。。

默认只显示一级菜单实现

一分钟到:开始吧! 要实现默认只展开第一级,那第一步肯定就是要知道怎么区分第一级和其他级。 那怎么区分呢!让使用菜单组件的人在数据结构里面加几个层级吗!可以是可以,但是增加了使用成本,菜单数据的每一个项都需要加一个字段。显得麻烦,那该怎么办呢! 。。。。 想到了一个好办法,递归调用的时候从MenuItem传一个标识过来到index.vue,就叫menu-item-parent 吧, 再在index.vue 进行判断有没有在这个参数, 有说明不是第一级,没有则说明是第一级。 来看下现在的MenuItem.vue

<template>
  <div class="menu-item">
    <p class="menu-text">{{ menuData.title }}</p>
    <Menu 
      :menuList="menuData.children" 
      v-if="menuData.children && menuData.children.length > 0"
      :menu-item-parent="true"
     />
  </div>
</template>
<script setup>
import Menu from './index.vue'
const props = defineProps({
  menuData: {
    type: Object,
    default () {
      return []
    }
  }
})
</script>
<style>
.menu-text {
  margin: 0;
  padding: 10px;
  cursor: pointer;
}
.menu-text:hover {
  background: #333333;
}
</style>

关键:增加了:menu-item-parent="true" Menu 下面的index.vue .menu-list { background: #181818; color: #fff; padding-left: 20px; }

关键: (1)增加了menuItemParent: { type: String, default: '' } 属性接收 (2)增加了v-if="!menuItemParent"判断

来看下现在的菜单效果

image.png

已经实现我们的第一个功能, 默认只显示第一级菜单了

点击展开和收起功能实现

接下来就是实现点击展开和收起了。 这个功能怎么实现呢! 思考一分钟 。。。 时间到, 开始吧! (1)要实现这个功能,肯定要添加点击事件, 所以在 MenuItem.vue 的 menu-text 上增加一个点击事件,就叫toggle吧

(2)声明一个变量来进行菜单的显示隐藏判断, 就叫clickToMenu 吧

(3)menu-item-parent 这个属性值也改为一个变量来控制,因为点击的时候需要把他变为false , 显示隐藏完全由点击的这个变量来控制

(4) 点击的时候 clickToMenu.value = !clickToMenu.value

menuItemParent.value = false

来看下具体代码的实现 MenuItem.vue

<template>
  <div class="menu-item">
    <p class="menu-text" @click="toggle">{{ menuData.title }}</p>
    <Menu 
      :menuList="menuData.children" 
      v-if="menuData.children && menuData.children.length > 0 && clickToMenu"
      :menu-item-parent="menuItemParent"
     />
  </div>
</template>
<script setup>
import { ref } from 'vue' 
import Menu from './index.vue'
const props = defineProps({
  menuData: {
    type: Object,
    default () {
      return []
    }
  }
})
const menuItemParent = ref(true)
const clickToMenu = ref(false)
function toggle () {
  clickToMenu.value = !clickToMenu.value
  menuItemParent.value = false
}
</script>
<style>
.menu-text {
  margin: 0;
  padding: 10px;
  cursor: pointer;
}
.menu-text:hover {
  background: #333333;
}
</style>

关键:增加了

const menuItemParent = ref(true)
const clickToMenu = ref(false)
function toggle () {
  clickToMenu.value = !clickToMenu.value
  menuItemParent.value = false
}

模板if 判断增加 menuData.children && menuData.children.length > 0 && clickToMenu

index.vue 没有改动

来看下效果,点击一级菜单

X_O6~3FC3OVWQ~FJO%Z7Q.png

点击二级菜单

7{_KPFG~`CD)KU_SXRAFNHT.png

再次点击二级菜单进行收起

image.png

到此一个完整的菜单基本上就开发完成了。

菜单显示细节优化--- 根据状态添加收缩标识

还有没有什么缺陷呢? 其实还有, 就是现在看不出来哪些菜单是有子菜单的

来优化下吧,主要就是根据状态增加一个图标,来看下具体代码

<template>
  <div class="menu-item">
    <p class="menu-text" 
      :class="{ 'menu-icon': menuData.children && menuData.children.length > 0, up: !clickToMenu}"
      @click="toggle"
    >{{ menuData.title }}</p>
    <Menu 
      :menuList="menuData.children" 
      v-if="menuData.children && menuData.children.length > 0 && clickToMenu"
      :menu-item-parent="menuItemParent"
     />
  </div>
</template>
<script setup>
import { ref } from 'vue' 
import Menu from './index.vue'
const props = defineProps({
  menuData: {
    type: Object,
    default () {
      return []
    }
  }
})
const menuItemParent = ref(true)
const clickToMenu = ref(false)
function toggle () {
  clickToMenu.value = !clickToMenu.value
  menuItemParent.value = false
}
</script>
<style>
.menu-text {
  margin: 0;
  padding: 10px;
  cursor: pointer;
  position: relative;
}
.menu-text:hover {
  background: #333333;
}
.menu-icon::after{
  content: '';
  width: 20px;
  height: 20px;
  position: absolute;
  display: inline-block;
  right: 10px;
  top: 50%;
  transform: translateY(-50%) rotate(180deg);
  background: url("@/assets/images/menu-arrow.svg") no-repeat;
  background-size: contain;
}
.menu-icon.up::after{
  transform: rotate(0deg);
}
</style>

关键增加:class="{ 'menu-icon': menuData.children && menuData.children.length > 0, up: !clickToMenu}"

增加样式:

.menu-icon::after{
  content: '';
  width: 20px;
  height: 20px;
  position: absolute;
  display: inline-block;
  right: 10px;
  top: 50%;
  transform: translateY(-50%) rotate(180deg);
  background: url("@/assets/images/menu-arrow.svg") no-repeat;
  background-size: contain;
}
.menu-icon.up::after{
  transform: rotate(0deg);
}

到此菜单就基本接近完美了。来看下具体的效果吧

image.png

无限级菜单到这里就开发结束了,感谢收看,希望能和你一起交流技术

Vue 其他相关文章:

Vue3 异步组件, 优化你的页面体验

Vue Router 全面详解--【一、从零开始搭建Vue Router,一步一步的掌握Vue Router】