切换语言为:繁体
使用 Vue3 利用递归的方式实现无限层级菜单

使用 Vue3 利用递归的方式实现无限层级菜单

  • 爱糖宝
  • 2024-07-15
  • 2084
  • 0
  • 0

还只会使用开源的组件吗,有没有想过自己去实现一下这些开源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>

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

使用 Vue3 利用递归的方式实现无限层级菜单

基础菜单缺陷思考

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

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"判断

来看下现在的菜单效果

使用 Vue3 利用递归的方式实现无限层级菜单

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

点击展开和收起功能实现

接下来就是实现点击展开和收起了。 这个功能怎么实现呢! 思考一分钟 。。。 时间到, 开始吧! (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 没有改动

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

使用 Vue3 利用递归的方式实现无限层级菜单

点击二级菜单

使用 Vue3 利用递归的方式实现无限层级菜单

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

使用 Vue3 利用递归的方式实现无限层级菜单

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

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

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

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

<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);
}

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

使用 Vue3 利用递归的方式实现无限层级菜单

无限级菜单到这里就开发结束了,感谢收看

0条评论

您的电子邮件等信息不会被公开,以下所有项均必填

OK! You can skip this field.