vue3.0+ts+element-plus多页签应用模板:侧边导航菜单(上)
目录系列链接一、目录结构二、安装sass三、编写主框架1. 添加主框架的路由2. 划分主框架的header,aside,main四、封装侧边栏组件1. 实现自由伸缩系列链接第一章——vue3.0+ts+element-plus多页签应用模板:项目搭建篇第二章——vue3.0+ts+element-plus多页签应用模板:安装vue-router与vuex篇第三章——vue3.0+ts+elemen
系列文章
- vue3.0+ts+element-plus多页签应用模板:前言
- vue3.0+ts+element-plus多页签应用模板:项目搭建
- vue3.0+ts+element-plus多页签应用模板:vue-router与vuex
- vue3.0+ts+element-plus多页签应用模板:element-plus按需引入与动态换肤
- vue3.0+ts+element-plus多页签应用模板:如何优雅地使用Svg图标
- vue3.0+ts+element-plus多页签应用模板:侧边导航菜单(上)
- vue3.0+ts+element-plus多页签应用模板:侧边导航菜单(下)
- vue3.0+ts+element-plus多页签应用模板:多级路由缓存
- vue3.0+ts+element-plus多页签应用模板:头部工具栏(上)
- vue3.0+ts+element-plus多页签应用模板:头部工具栏(中)
- vue3.0+ts+element-plus多页签应用模板:头部工具栏(下)
一、先说点什么
从这一章开始,文章描述的重点,将会从实际代码转移到思路讲述,贴出的代码只是关键部分。俗话说授之以鱼不如授之以渔,要想学会一样东西,打通思路是最重要的。就和学会武功,要打通任督二脉是一个道理。好的,那么我们就进入侧边栏的实现吧。
二、从问题开始,侧边栏是干啥的?
这个问题问得好!我们在做一个东西之前,首先要了解这个东西有啥用,你光图好看有逼格那属于浪费精力。所以,我们必须要知道我们为什么要去做侧边栏。那么侧边栏是干啥的呢?它的核心功能只有一个,那就是路由跳转。用户可以通过这样一个侧边导航,很轻松的进入一个又一个页面,这就是它最本质的功能。
所以,有了这个思路,我们就知道,我们需要将路由配置与这个组件联系起来,每当我们改动路由配置的时候,它就会自动生成菜单项。好的,既然说到了菜单项,那我们就先来研究一下,每个菜单项应该包括一些什么东西:
- key:每个菜单项的唯一标识,最好是其名称的英文翻译
- type:菜单项的类别(可包含子级菜单项的下拉菜单、可包含子级菜单项的分组菜单和普通子级菜单项)
- label:菜单项名称
- icon:svg图标名
- hidden:该路由是否显示到菜单中
- roles:能够访问该路由的账号角色,可以是字符串或字符串数组
- disabled:是否已禁用状态展示该菜单项
- children:其下包含的子级菜单项
- onClick:点击当前路由会发生什么(一般情况下是在系统内跳转路由,但有时候也需要干点别的)
这样就分析好了每个菜单项的属性了,现在让我们来与路由配置项对应一下:
{
key: name, // 菜单项的唯一标识对应路由名
type: meta.type, // 除了children之外的都可以放进meta中
label: meta.label,
icon: meta.icon,
hidden: meta.hidden,
roles: meta.roles,
disabled: meta.disabled,
onClick: meta.onClick
children // 子级菜单项对应子路由
}
由此可见,二者可以非常完美的对应起来,那么根据这个思路,我们就可以开始封装组件了。
三、封装组件之思路分析
初步分析一下,整个组件里面都有些啥:
- 一个整体:AsideBar
- 头部下面的菜单:ModuleMenu
- 菜单里的菜单项:MenuItem
- 头部Logo与标题:SystemLogo
总结一下:
- 创建一个
MenuItem.vue
作为菜单项,将路由配置项转换为菜单配置项,并且要分三种不同的类别,其中submenu和group两类中需要递归调用自身 - 创建一个
ModuleMenu.vue
作为所有菜单项的容器,负责将路由配置传递给MenuItem.vue
- 创建一个
SystemLogo.vue
作为头部的容器,展示图标和系统名 - 创建一个
AsideBar.vue
作为一个整体的容器,读取路由配置信息并传递给ModuleMenu.vue
,同时需要接收外部传来的collapse(控制自身的折叠与展开)、iconName(logo图标名)、iconColor(logo颜色)、systemName(系统名)
好的,整体分析完成。下面我们来一个一个的去实现吧。
四、封装菜单部分
1. MenuItem
我们先来看一下MenuItem的template部分:
<template>
<!-- menuItems是经过转换的菜单项列表 -->
<template v-for="item in menuItems" :key="item.key">
<!-- 通过hidden和roles字段判断是否显示当前菜单项 -->
<template v-if="getItemShow(item)">
<!-- v-if submenu -->
<el-submenu v-if="item.type === 'submenu'" :index="item.key" :disabled="item.disabled">
<template #title>
<svg-icon v-if="item.icon" :name="item.icon" size="xl" class="item-icon" />
<span>{{ item.label }}</span>
</template>
<!-- 递归调用自身,渲染子级菜单项,下面的group也是一样 -->
<template v-if="item.children">
<menu-item :route-list="item.children" />
</template>
</el-submenu>
<!-- v-if group -->
<el-menu-item-group v-if="item.type === 'group'">
<template #title>
<span>{{ item.label }}</span>
</template>
<template v-if="item.children">
<menu-item :route-list="item.children" />
</template>
</el-menu-item-group>
<!-- v-if item -->
<el-menu-item
v-if="item.type === 'item'"
:index="item.key"
@click="onMenuItemClick($event, item)"
:disabled="item.disabled"
>
<svg-icon v-if="item.icon" :name="item.icon" size="xl" class="item-icon" />
<span>{{ item.label }}</span>
</el-menu-item>
</template>
</template>
</template>
上面SvgIcon组件的实现请看同系列文章:如何优雅地使用Svg图标,然后看一下script部分:
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue'
import { RouteRecordRaw, useRouter } from 'vue-router'
import { MenuItemProps } from '../../typings'
import Variables from '@/assets/styles/variables.scss'
export default defineComponent({
name: 'MenuItem',
props: {
routeList: {
type: Array as PropType<RouteRecordRaw[]>,
required: true
}
},
emits: ['itemClick'],
setup(props, context) {
const router = useRouter()
// 处理菜单项的点击事件
const onMenuItemClick = (event: any, item: MenuItemProps) => {
// 处理onClick函数
if (item.onClick) {
// 把MouseEvent和菜单项本身传回去
item.onClick(event, item)
// 触发一个点击事件给父组件,用于再不进行路由跳转时,取消菜单项的激活状态
context.emit('itemClick', event, item)
return
}
// 没有onClick函数的时候,进行路由跳转
router.push({ name: item.key })
}
// 将路由配置转换成菜单配置
const menuItems = computed(() => {
return props.routeList.map((item) => {
let temp: MenuItemProps = { key: item.name as string }
if (item.meta) {
temp = { ...temp, ...item.meta }
}
if (item.children) {
temp.children = item.children
}
return temp
})
})
// 通过hidden和roles字段判断是否显示当前菜单项
const getItemShow = computed(() => (item: MenuItemProps) => {
/**
* 这里暂时不写关于用户权限控制的逻辑了,因为我也不知道你们会怎么存储用户的role字段
* 权限控制思路:
* 1. 假设你的role字段存在了store.userModule.role中
* 2. 使用instanceof判断一下menuitem中的roles是数组还是字符串
* 3. 如果是字符串:return store.userModule.role === item.roles
* 4. 如果是数组:return item.roles.includes(store.userModule.role)
* 5. 但是要注意修改一下分支结构,因为item.hidden的优先级在item.roles前面
*/
return !item.hidden
})
return {
menuItems,
Variables,
onMenuItemClick,
getItemShow
}
}
})
</script>
2. ModuleMenu
template部分:
<template>
<el-menu
:collapse="collapse"
:collapse-transition="false"
:background-color="Variables.ASIDE_BAR_BG_COLOR"
:text-color="Variables.ASIDE_BAR_COLOR"
:active-text-color="Variables.ASIDE_BAR_ACTIVE_COLOR"
:default-active="$route.name"
:key="menuKey"
class="asideBar-menu"
>
<menu-item :route-list="routeList" @item-click="onItemClick" />
</el-menu>
</template>
这个组件里只是简单的引用了一下ElMenu组件以及我们刚刚封装好的MenuItem组件。然后看一下script部分:
<script lang="ts">
import { defineComponent, PropType, ref } from 'vue'
import { MenuItemProps } from '../../typings'
import Variables from '@/assets/styles/variables.scss'
import Utils from '@/utils'
import MenuItem from '../MenuItem'
export default defineComponent({
name: 'ModuleMenu',
components: {
MenuItem
},
props: {
collapse: {
type: Boolean,
required: true
},
routeList: {
type: Array as PropType<MenuItemProps[]>,
required: true
}
},
setup() {
/**
* 定义key,并监听菜单项点击事件,也就是每当菜单项的onClick函数触发的时候,
* 都会强制刷新ElMenu组件,达到取消目标路由激活状态的目的
*/
const menuKey = ref(Utils.uuid())
const onItemClick = () => (menuKey.value = Utils.uuid())
return {
Variables,
menuKey,
onItemClick
}
}
})
</script>
至此,前两步的主要内容就讲述完了,后面的内容且听下回分解。
下一篇预告:vue3.0+ts+element-plus多页签应用模板:侧边导航菜单(下)
更多推荐
所有评论(0)