在上一篇文章中,以Vue2的选项式 API风格,封装了递归型菜单组件,但是这其中存在着一些问题,例如:
【1】子组件menuList.vue中,通过this.$emit()方式定义的事件,在Vue3组合式API风格父组件中可能会被执行多次。之所以说是可能,是因为我这测试中确实遇到了这个问题,子组件中确实只执行了一次,父组件中却执行了两次。
【2】父子组件之间的props传值风格不同,vue3中,我们通常是通过defineProps API明确定义要传递的props属性,而Vue2中选项式API风格的组件,则直接可以通过export default导出的配置参数进行声明。这种不一致的写法在我写个人项目中,一向是不太赞同的。
基于以上原因,我将上一篇文章《Vue:Elemenu-Plus递归型菜单组件封装》中的组件进行了改写,改为Vue3组合式API风格的组件,并对上述问题进行了解决。
示例代码如下,
菜单子组件定义
示例代码如下,
<!-- 多级菜单组件抽取 -->
<template>
<el-menu :default-active="props.activeIndex" :class="props.customMenuClass" background-color="transparent"
text-color="#fff" active-text-color="#ffef40" :mode="props.mode" @select="onSelect" :ellipsis="false">
<template v-for="(item) in props.items">
<template v-if="item.children">
<el-sub-menu :index="item.index" popper-class="el-sub-menu-popper-class">
<template #title>
<router-link :to="item.path">
<el-icon>
<component :is="item.meta.icon" :size="24"></component>
</el-icon>
<span>{{ item.meta.title }}</span>
</router-link>
</template>
<!-- 直接使用文件名即可,不用考虑name的问题 -->
<!-- <menu-list :items="item.children" :mode="item.meta.mode" @select="onSelect"></menu-list> -->
<index :items="item.children" :mode="item.meta.mode" @select="onSelect"></index>
</el-sub-menu>
</template>
<template v-else>
<el-menu-item :index="item.index" :key="item.path">
<router-link :to="item.path">
<el-icon>
<component :is="item.meta.icon" :size="24"></component>
</el-icon>
<span>{{ item.meta.title }}</span>
</router-link>
</el-menu-item>
</template>
</template>
</el-menu>
</template>
<script setup>
import { toRef, watch } from 'vue'
//declare props from parent-component
const props = defineProps({
customMenuClass: {
type: String,
required: false,
default: "el-menu-class",
},
mode: {
type: String,
default: "horizontal",
},
items: {
type: Array,
required: true,
},
activeIndex: {
type: String,
required: false,
default: "",
}
})
//declare emtits by current-component
const emits = defineEmits(["select"])
//declare watch-calls
watch(() => props.activeIndex, (value, oldValue) => {
console.log('新的activeIndex,', value)
})
//declare methods
const onSelect = (key, keyPath, item) => {
emits("select", { value: true, key, keyPath, item })
}
</script>
<style lang="scss" scoped>
.el-menu-class {
.el-menu-item:not(.is-disabled):hover {
background-color: rgba(127, 255, 212, .3);
}
}
</style>
<style lang="scss">
.el-sub-menu-popper-class {
background-color: $base-background-color !important;
}
</style>
父组件调用
<menu-list :items="menuList" :active-index="activeIndex" mode="horizontal" @select="clickHandler" />
//其中,clickHandler为emit方式定义的子组件事件,函数定义如下
const clickHandler = ({ value, key, keyPath, item }) => {
console.log('clickHandler', value, key, keyPath)
console.log("item-value", item)
}
在此之上,我们还可以完成一些其他操作,例如:菜单路由切换的本地缓存,以保证每次进入项目主页时,路由会自动跳转到上一次操作的页面;结合后端API接口调用,实现真正意义上的动态菜单;通过自定义指令,实现前端页面操作的权限控制等等。