还只会使用开源的组件吗,有没有想过自己去实现一下这些开源ui框架的某个组件的功能呢。今天就用vue3 + 递归思想实现一个无限极的菜单
无限级菜单结构实现
首先来思考 代码的结构
首先有个Menu 文件夹
menu 文件夹下面有个index.vue出口文件
menu MenuItem.vue 循环的菜单项
循环的菜单里面又可能有子菜单,所以就又要去引用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>
一个简单的无限级菜单就基本形成了,来看下效果
基础菜单缺陷思考
是不是很开心呢!别高兴太早,还有些功能还没实现呢!
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"
判断
来看下现在的菜单效果
已经实现我们的第一个功能, 默认只显示第一级菜单了
点击展开和收起功能实现
接下来就是实现点击展开和收起了。 这个功能怎么实现呢! 思考一分钟 。。。 时间到, 开始吧! (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 没有改动
来看下效果,点击一级菜单
点击二级菜单
再次点击二级菜单进行收起
到此一个完整的菜单基本上就开发完成了。
菜单显示细节优化--- 根据状态添加收缩标识
还有没有什么缺陷呢? 其实还有, 就是现在看不出来哪些菜单是有子菜单的
来优化下吧,主要就是根据状态增加一个图标,来看下具体代码
<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); }
到此菜单就基本接近完美了。来看下具体的效果吧
无限级菜单到这里就开发结束了,感谢收看