Vue3?封裝?Element?Plus?Menu?無限級菜單組件功能的詳細(xì)代碼
本文分別使用 SFC(模板方式)和 tsx 方式對 Element Plus el-menu 組件進(jìn)行二次封裝,實(shí)現(xiàn)配置化的菜單,有了配置化的菜單,后續(xù)便可以根據(jù)路由動態(tài)渲染菜單。
1 數(shù)據(jù)結(jié)構(gòu)定義
1.1 菜單項(xiàng)數(shù)據(jù)結(jié)構(gòu)
使用 element-plus el-menu 組件實(shí)現(xiàn)菜單,主要包括三個組件:
el-menu:整個菜單;
el-sub-menu:含有子菜單的菜單項(xiàng);
el-sub-menu:沒有子菜單的菜單項(xiàng)(最末級);
結(jié)合菜單的屬性和展示效果,可以得到每個菜單項(xiàng)包括:菜單名稱、菜單圖標(biāo)、菜單唯一標(biāo)識、子菜單列表四個屬性。于是可得到菜單項(xiàng)結(jié)構(gòu)定義如下:
/**
* 菜單項(xiàng)
*/
export interface MenuItem {
/**
* 菜單名稱
*/
title: string;
/**
* 菜單編碼(對應(yīng) el-menu-item / el-sub-menu 組件的唯一標(biāo)識 index 字段)
*/
code: string;
/**
* 菜單的圖標(biāo)
*/
icon?: string;
/**
* 子菜單
*/
children?: MenuItem[]
}傳入 MenuItem 數(shù)組,使用該數(shù)組渲染出菜單。但有時(shí)候數(shù)據(jù)字段不一定為上面結(jié)構(gòu)定義的屬性名,如 菜單名稱 字段,上面定義的屬性為 title,但實(shí)際開發(fā)過程中后端返回的是 name,此時(shí)字段名不匹配。一種處理方式是前端開發(fā)獲取到后臺返回的數(shù)據(jù)后,遍歷構(gòu)造上述結(jié)構(gòu),由于是樹形結(jié)構(gòu),遍歷起來麻煩。另一種方式是由用戶指定字段的屬性名,分別指定菜單名稱、菜單編碼等分別對應(yīng)用戶傳遞數(shù)據(jù)的什么字段。所以需要再定義一個結(jié)構(gòu),由用戶來配置字段名稱。
1.2 菜單配置數(shù)據(jù)結(jié)構(gòu)
首先定義菜單項(xiàng)字段配置的結(jié)構(gòu):
/**
* 菜單項(xiàng)字段配置結(jié)構(gòu)
*/
export interface MenuOptions {
title?: string;
code?: string;
icon?: string;
children?: string;
}再定義菜單項(xiàng)結(jié)構(gòu)默認(rèn)字段名:
/**
* 菜單項(xiàng)默認(rèn)字段名稱
*/
export const defaultMenuOptions: MenuOptions = {
title: 'title',
code: 'code',
icon: 'icon',
children: 'children'
}2 使用 tsx 實(shí)現(xiàn)封裝
2.1 tsx 基本結(jié)構(gòu)
通常使用 tsx 封裝組件的結(jié)構(gòu)如下:
import { defineComponent } from 'vue'
export default defineComponent({
name: 'yyg-menu',
// 屬性定義
props: {
},
setup (props, context) {
console.log(props, context)
return () => (
<div>yyg-menu</div>
)
}
})2.2 定義 prop
首先定義兩個屬性:菜單的數(shù)據(jù)、菜單數(shù)據(jù)的字段名。
// 屬性定義
props: {
data: {
type: Array as PropType<MenuItem[]>,
required: true
},
menuOptions: {
type: Object as PropType<MenuOptions>,
required: false,
default: () => ({})
}
},除了上面定義的兩個屬性,el-menu 中的屬性我們也希望能夠暴露出去使用:

但 el-menu 的屬性太多,一個個定義不太現(xiàn)實(shí),在 tsx 中可以使用 context.attrs 來獲取。
context.attrs 會返回當(dāng)前組件定義的屬性之外、用戶傳入的其他屬性,也就是返回沒有在 props 中定義的屬性。
2.3 遞歸實(shí)現(xiàn)組件
在 setup 中 遞歸 實(shí)現(xiàn)菜單的無限級渲染。封裝函數(shù) renderMenu,該函數(shù)接收一個數(shù)組,遍歷數(shù)組:
- 如果沒有子節(jié)點(diǎn),則使用 el-menu-item 渲染
- 如果有子節(jié)點(diǎn),先使用 el-sub-menu 渲染,el-sub-menu 中的內(nèi)容又繼續(xù)調(diào)用 renderMenu 函數(shù)繼續(xù)渲染。
整個組件實(shí)現(xiàn)如下 infinite-menu.tsx:
import { DefineComponent, defineComponent, PropType } from 'vue'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import { defaultMenuOptions, MenuItem, MenuOptions } from './types'
export default defineComponent({
name: 'yyg-menu-tsx',
// 屬性定義
props: {
data: {
type: Array as PropType<MenuItem[]>,
required: true
},
menuOptions: {
type: Object as PropType<MenuOptions>,
required: false,
default: () => ({})
}
},
setup (props, context) {
console.log(props, context)
// 合并默認(rèn)的字段配置和用戶傳入的字段配置
const options = {
...defaultMenuOptions,
...props.menuOptions
}
// 渲染圖標(biāo)
const renderIcon = (icon?: string) => {
if (!icon) {
return null
}
const IconComp = (ElementPlusIconsVue as { [key: string]: DefineComponent })[icon]
return (
<el-icon>
<IconComp/>
</el-icon>
)
}
// 遞歸渲染菜單
const renderMenu = (list: any[]) => {
return list.map(item => {
// 如果沒有子菜單,使用 el-menu-item 渲染菜單項(xiàng)
if (!item[options.children!] || !item[options.children!].length) {
return (
<el-menu-item index={item[options.code!]}>
{renderIcon(item[options.icon!])}
<span>{item[options.title!]}</span>
</el-menu-item>
)
}
// 有子菜單,使用 el-sub-menu 渲染子菜單
// el-sub-menu 的插槽(title 和 default)
const slots = {
title: () => (
<>
{renderIcon(item[options.icon!])}
<span>{item[options.title!]}</span>
</>
),
default: () => renderMenu(item[options.children!])
}
return <el-sub-menu index={item[options.code!]} v-slots={slots} />
})
}
return () => (
<el-menu {...context.attrs}>
{renderMenu(props.data)}
</el-menu>
)
}
})3 使用 SFC 實(shí)現(xiàn)菜單封裝
SFC 即 Single File Component,可以理解為 .vue 文件編寫的組件。上面使用 tsx 可以很方便使用遞歸,模板的方式就不太方便使用遞歸了,需要使用兩個組件來實(shí)現(xiàn)。
3.1 封裝菜單項(xiàng)的渲染
infinite-menu-item.vue:
<template>
<!-- 沒有子節(jié)點(diǎn),使用 el-menu-item 渲染 -->
<el-menu-item v-if="!item[menuOptions.children] || !item[menuOptions.children].length"
:index="item[menuOptions.code]">
<el-icon v-if="item[menuOptions.icon]">
<Component :is="ElementPlusIconsVue[item[menuOptions.icon]]"/>
</el-icon>
<span>{{ item[menuOptions.title] }}</span>
</el-menu-item>
<!-- 有子節(jié)點(diǎn),使用 el-sub-menu 渲染 -->
<el-sub-menu v-else
:index="item[menuOptions.code]">
<template #title>
<el-icon v-if="item[menuOptions.icon]">
<Component :is="ElementPlusIconsVue[item[menuOptions.icon]]"/>
</el-icon>
<span>{{ item[menuOptions.title] }}</span>
</template>
<!-- 循環(huán)渲染 -->
<infinite-menu-item v-for="sub in item[menuOptions.children]"
:key="sub[menuOptions.code]"
:item="sub"
:menu-options="menuOptions"/>
</el-sub-menu>
</template>
<script lang="ts" setup>
import { defineProps, PropType } from 'vue'
import { MenuOptions } from './types'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
defineProps({
item: {
type: Object,
required: true
},
menuOptions: {
type: Object as PropType<MenuOptions>,
required: true
}
})
</script>
<style scoped lang="scss">
</style>3.2 封裝菜單組件
infinite-menu-sfc.vue
<template>
<el-menu v-bind="$attrs">
<infinite-menu-item v-for="(item, index) in data"
:key="index"
:item="item"
:menu-options="options"/>
</el-menu>
</template>
<script lang="ts" setup>
import InfiniteMenuItem from './infinite-menu-item.vue'
import { defineProps, onMounted, PropType, ref } from 'vue'
import { defaultMenuOptions, MenuItem, MenuOptions } from './types'
const props = defineProps({
data: {
type: Array as PropType<MenuItem[]>,
required: true
},
menuOptions: {
type: Object as PropType<MenuOptions>,
required: false,
default: () => ({})
}
})
const options = ref({})
onMounted(() => {
options.value = {
...defaultMenuOptions,
...props.menuOptions
}
})
</script>
<style scoped lang="scss">
</style>4 測試組件
4.1 菜單測試數(shù)據(jù)
menu-mock-data.ts
export const mockData = [{
title: '系統(tǒng)管理',
id: 'sys',
logo: 'Menu',
children: [{
title: '權(quán)限管理',
id: 'permission',
logo: 'User',
children: [
{ title: '角色管理', id: 'role', logo: 'User' },
{ title: '資源管理', id: 'res', logo: 'User' }
]
}, {
title: '字典管理', id: 'dict', logo: 'User'
}]
}, {
title: '營銷管理', id: '2', logo: 'Menu'
}, {
title: '測試',
id: 'test',
logo: 'Menu',
children: [{
title: '測試-1',
id: 'test-1',
logo: 'Document',
children: [{ title: '測試-1-1', id: 'test-1-1', logo: 'Document', children: [{ title: '測試-1-1-1', id: 'test-1-1-1', logo: 'Document' }]}, { title: '測試-1-2', id: 'test-1-2', logo: 'Document' }]
}]
}]4.2 測試頁面
<template>
<div class="menu-demo">
<div>
<h3>tsx</h3>
<yyg-infinite-menu-tsx
:data="mockData"
active-text-color="red"
default-active="1"
:menu-options="menuOptions"/>
</div>
<div>
<h3>sfc</h3>
<yyg-infinite-menu-sfc
:data="mockData"
active-text-color="red"
default-active="1"
:menu-options="menuOptions"/>
</div>
</div>
</template>
<script lang="ts" setup>
import YygInfiniteMenuTsx from '@/components/infinite-menu'
import YygInfiniteMenuSfc from '@/components/infinite-menu-sfc.vue'
import { mockData } from '@/views/data/menu-mock-data'
const menuOptions = { title: 'title', code: 'id', icon: 'logo' }
</script>
<style scoped lang="scss">
.menu-demo {
display: flex;
> div {
width: 250px;
margin-right: 30px;
}
}
</style>4.3 運(yùn)行效果

總結(jié):
- 在之前的文章中有讀者問我為什么要使用 tsx,從這個例子可以看出,如果控制流程復(fù)雜或有遞歸等操作時(shí),tsx 會比 sfc 更容易實(shí)現(xiàn);
- tsx 和 sfc 中動態(tài)組件的使用;
- tsx 中的 context.attrs 和 sfc 中的 v-bind="$attrs" 的使用。
到此這篇關(guān)于Vue3 封裝 Element Plus Menu 無限級菜單組件的文章就介紹到這了,更多相關(guān)Vue3 無限級菜單組件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- vue3+ElementPlus封裝函數(shù)式彈窗組件詳解
- Vue3+ElementPlus 表單組件的封裝實(shí)例
- Vue3+Vite+TS實(shí)現(xiàn)二次封裝element-plus業(yè)務(wù)組件sfasga
- Vue使用element-ui實(shí)現(xiàn)菜單導(dǎo)航
- ant design vue導(dǎo)航菜單與路由配置操作
- Vue實(shí)現(xiàn)導(dǎo)航欄菜單
- vue二級菜單導(dǎo)航點(diǎn)擊選中事件的方法
- Vue-路由導(dǎo)航菜單欄的高亮設(shè)置方法
- vue3封裝Element導(dǎo)航菜單的實(shí)例代碼
相關(guān)文章
Vue3中關(guān)于ref和reactive的區(qū)別分析
這篇文章主要介紹了vue3關(guān)于ref和reactive的區(qū)別分析,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-06-06
Vue 頁面切換效果之 BubbleTransition(推薦)
使用 vue,vue-router,animejs 來講解如何實(shí)現(xiàn)vue頁面切換效果之 BubbleTransition,需要的朋友參考下吧2018-04-04
詳解如何使用Vue實(shí)現(xiàn)圖像識別和人臉對比
隨著人工智能的發(fā)展,圖像識別和人臉識別技術(shù)已經(jīng)被廣泛應(yīng)用于各種應(yīng)用程序中,Vue為我們提供了許多實(shí)用工具和庫,可以幫助我們在應(yīng)用程序中進(jìn)行圖像識別和人臉識別,在本文中,我們將介紹如何使用Vue進(jìn)行圖像識別和人臉對比,需要的朋友可以參考下2023-06-06
Vue實(shí)現(xiàn)計(jì)算器計(jì)算效果
這篇文章主要為大家詳細(xì)介紹了Vue實(shí)現(xiàn)計(jì)算器計(jì)算效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-08-08
在vs code 中如何創(chuàng)建一個自己的 Vue 模板代碼
這篇文章主要介紹了在vs code 中如何創(chuàng)建一個自己的 Vue 模板代碼,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11
vue實(shí)現(xiàn)點(diǎn)擊展開點(diǎn)擊收起效果
這篇文章主要介紹了vue實(shí)現(xiàn)點(diǎn)擊展開,點(diǎn)擊收起效果,首先我們需要定義data里面的數(shù)據(jù),使用computed對data進(jìn)行處理,需要的朋友可以參考下2018-04-04

