還只會使用開源的元件嗎,有沒有想過自己去實現一下這些開源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); }
到此選單就基本接近完美了。來看下具體的效果吧
無限級選單到這裏就開發結束了,感謝收看