封装组件库-导航菜单
2023, Feb 28
实现导航菜单的封装
基于Element-plus框架的Menu组件,封装为基础导航菜单和无限递归菜单。
初始配置
- 注册路由组件menu,配置为Container组件的子路由组件
- 采用路由懒加载,箭头函数异步引入组件。
- 注册通用组件menu,为路由组件的子组件,以便组件间通信。
- 将通用组件menu注册为全局组件
基础组件my-menu的封装
- 接口类型定义:src/components/menu/src/types.ts
export interface menuItem {
//导航图标
icon?: string,
//导航名称
name: string,
//导航标识
index: string,
//导航子菜单
children?: menuItem[]
}
- 子组件的接收项配置,设计自定义键名以便修改后端传递数据的变量名
let props = defineProps({
//导航菜单的数据
data: {
type: Array as PropType<any[]>,
required: true
},
//默认选中的菜单
defaultActive: {
type: String,
default: ''
},
//是否是路由模式
router: {
type: Boolean,
default: false
},
//键名
//菜单标题键名
name: {
type: String,
default: 'name'
},
//菜单图标键名
icon: {
type: String,
default: 'icon'
},
//菜单标识键名
index: {
type: String,
default: 'index'
},
//子菜单键名
children: {
type: String,
default: 'children'
},
})
- 父组件,data数据在script里定义
<my-menu :data=”data” defaultActive=”2” name=”a” index=”b” icon=”c” children=”d”></my-menu>
子组件数据处理
-
组件为单一导航, 为含有子导航
<!-- default-active默认高亮选项,router是否为路由模式 -->
<!-- v-bind="$attrs":props未接收的放置在$attrs上,绑定在整个导航上 -->
<el-menu :default-active="defaultActive" :router="router" v-bind="$attrs">
<template v-for="(item,i) in data" :key="i">
<el-menu-item
<!-- 表示没有子导航,或子导航数组为空 -->
<!-- item[]表示变量,item.children为固定值 -->
v-if=" !item[children] || !item[children].length"
:index="item[index]"
>
<component v-if="item[icon]" :is="`qt-icon-${toLine(item[icon])}`"/>
<span></span>
</el-menu-item>
<el-sub-menu
<!-- 有子导航,子数组不为空 -->
v-if="item[children] && item[children].length"
:index="item[index]"
>
<!-- 插槽表示所属父导航 -->
<template #title>
<component v-if="item[icon]" :is="`qt-icon-${toLine(item[icon])}`"/>
<span></span>
</template>
<el-menu-item
v-for="(item1,index1) in item[children]" :key="index1"
:index="item1[index]"
>
<component v-if="item1[icon]" :is="`qt-icon-${toLine(item1[icon])}`"/>
<span></span>
</el-menu-item>
</el-sub-menu>
</template>
</el-menu>
无限递归组件my-infinite-menu的封装
- 无限递归组件需要对数据进行递归处理并渲染,基于vue的模版方法不便实现。考虑使用jsx形式实现该组件
- npm i -D @vitejs/plugin-vue-jsx@1.3 安装插件. vite.config.ts:
import vueJsx from '@vitejs/plugin-vue-jsx'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(),vueJsx()],
server: {
port: 8080
}
})
- 编写tsx文件,路径:src/components/menu/src/menu.tsx
import { defineComponent,PropType } from "vue"
import * as Icon from '@element-plus/icons'
import './types.ts'
<!-- 配置icon样式 -->
import './style.css'
export default defineComponent({
props: {
data: {
type: Array as PropType<menuItem[]>,
required: true
},
//默认选中的菜单
defaultActive: {
type: String,
default: ''
},
//是否是路由模式
router: {
type: Boolean,
default: false
},
//键名
//菜单标题键名
name: {
type: String,
default: 'name'
},
//菜单图标键名
icon: {
type: String,
default: 'icon'
},
//菜单标识键名
index: {
type: String,
default: 'index'
},
//子菜单键名
children: {
type: String,
default: 'children'
},
},
setup(props, ctx) {
//封装渲染一个无限层级菜单的方法,实现键名自定义,定义为any类型
let renderMenu = (data: any[])=>{
//返回jsx代码
return data.map((item: any)=>{
//jsx无法默认指定props,所以item[props.icon!]传值
//自定义icon无效,引入图标
item.i = (Icon as any)[item[props.icon!]]
//插槽 sub-menu,jsx的插槽需采用对象形式,#title
let slots = {
title: ()=>{
return <>
<item.i></item.i>
<span>{item[props.name]}</span>
</>
}
}
//递归渲染children
if(item[props.children!] && item[props.children!].length){
return (
//调用renderMenu,进行递归
<el-sub-menu index={item[props.index]} v-slots={slots}>
{renderMenu(item[props.children])}
</el-sub-menu>
)
}
//无children项,渲染为单一导航
return (
<el-menu-item index={item[props.index]}>
<item.i></item.i>
<span>{item[props.name]}</span>
</el-menu-item>
)
})
}
//获取attrs
let attrs = useAttrs()
return ()=>{
return (
<el-menu
default-active={props.defaultActive}
router={props.router}
//配置attrs
{...attrs}
>
//渲染
{renderMenu(props.data)}
</el-menu>
)
}
},
})
- 父组件
//可进行attrs配置
<my-infinite-menu :data="data1" name="a" index="b" icon="c" children="d" background-color='red'></my-infinite-menu>
首页侧边导航栏配置
- 基于my-menu组件配置导航栏,并实现路由模式
//传入数据,设置为router模式,默认高亮设置为当前路由的路径(index),配置可伸缩和样式
<my-menu :data="data" router :default-active="$route.path" :collapse='collapse' class="el-menu-vertical-demo"></my-menu>
// 导航的数据
let data = [
{
icon: 'HomeFilled',
name: '首页',
index: '/'
},
{
icon: 'Check',
name: '图标选择器',
index: '/chooseIcon'
},
{
icon: 'Location',
name: '省市区选择器',
index: '/chooseArea'
},
{
icon: 'Sort',
name: '趋势标记',
index: '/trend'
},
{
icon: 'Bell',
name: '通知菜单',
index: '/notification'
},
{
icon: 'Position',
name: '导航菜单',
index: '/menu'
}
]