本节目标
点击收起
这里菜单渲染 暂时使用router/index.ts里的路由表 路由表里meta里面需要提供title icon 作为菜单项信息,icon是我们自己的svg文件名称
2-1 Sidebar导入路由表
sidebar组件导入路由表,根据routes循环SidebarItem组件
<template>
<div>
<!-- 测试展开收起 -->
<h6 @click="isCollapse=!isCollapse">展收测试</h6>
<el-menu
class="sidebar-container-menu"
mode="vertical"
router
:default-active="activeMenu"
:background-color="scssVariables.menuBg"
:text-color="scssVariables.menuText"
:active-text-color="scssVariables.menuActiveText"
:collapse="isCollapse"
:collapse-transition="true"
>
<sidebar-item
v-for="route in menuRoutes"
:key="route.path"
:item="route"
:base-path="route.path"
/>
</el-menu>
</div>
</template>
<script lang="ts">
import { defineComponent, computed, ref } from 'vue'
import { useRoute } from 'vue-router'
import variables from '@/styles/variables.scss'
// 导入路由表
import { routes } from '@/router'
import SidebarItem from './SidebarItem.vue'
export default defineComponent({
name: 'Sidebar',
components: {
SidebarItem
},
setup() {
const route = useRoute() // 等价于 this.$route
// 根据路由路径 对应 当前激活的菜单
const activeMenu = computed(() => {
const { path } = route
return path
})
// scss变量
const scssVariables = computed(() => variables)
// 展开收起状态 稍后放store
const isCollapse = ref(false)
// 渲染路由
const menuRoutes = computed(() => routes)
return {
// 不有toRefs原因 缺点在这里 variables里面变量属性感觉来源不明确 不知道有哪些变量值
// ...toRefs(variables),
scssVariables,
isCollapse,
activeMenu,
menuRoutes
}
}
})
</script>
2-2 实现SidebarItem组件
<template>
<div class="sidebar-item-container">
<!-- 一个路由下只有一个子路由的时候 只渲染这个子路由 -->
<template
v-if="theOnlyOneChildRoute && !theOnlyOneChildRoute.children"
>
<el-menu-item
:index="resolvePath(theOnlyOneChildRoute.path)"
>
<svg-icon
v-if="icon"
class="menu-icon"
:icon-class="icon"
></svg-icon>
<template #title>
<span>{{ theOnlyOneChildRoute.meta.title }}</span>
</template>
</el-menu-item>
</template>
<!-- 多个子路由时 -->
<el-submenu
v-else
:index="resolvePath(item.path)"
popper-append-to-body
>
<template #title>
<svg-icon
v-if="item.meta.icon"
class="menu-icon"
:icon-class="item.meta.icon"
></svg-icon>
<span class="submenu-title">{{ item.meta.title }}</span>
</template>
<sidebar-item
v-for="child in item.children"
:key="child.path"
:is-nest="true"
:item="child"
:base-path="resolvePath(child.path)"
>
</sidebar-item>
</el-submenu>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, computed, toRefs } from 'vue'
import { RouteRecordRaw } from 'vue-router'
import path from 'path'
export default defineComponent({
name: 'SidebarItem',
props: {
item: { // 当前路由(此时的父路由)
type: Object as PropType<RouteRecordRaw>,
required: true
},
basePath: { // 父路由路径(子路由路径如果是相对的 要基于父路径)
type: String,
required: true
}
},
setup (props) {
const { item } = toRefs(props)
// 渲染菜单主要先看子路由
// 比如我们的路由 一级路由一般都是layout组件 二级路由才是我们考虑要渲染成菜单的
// 子路由数量
const showingChildNumber = computed(() => {
// hidden路由排除掉 只算可渲染子路由
const children = (props.item.children || []).filter(child => {
if (child.meta && child.meta.hidden) return false
return true
})
return children.length
})
// 要渲染的单个路由 如果该路由只有一个子路由 默认直接渲染这个子路由
// theOnlyOneChildRoute直接通过el-menu-item组件来渲染
const theOnlyOneChildRoute = computed(() => {
// 多个children时 直接return null 多children需要用el-submenu来渲染并递归
if (showingChildNumber.value > 1) {
return null
}
// 只有一个子路由 还要筛选路由meta里有无hidden属性 hidden:true则过滤出去 不用管
// 路由meta里我们会配置hidden属性表示不渲染成菜单,比如login 401 404页面是不渲染成菜单的
if (item.value.children) {
for (const child of item.value.children) {
if (!child.meta || !child.meta.hidden) {
return child
}
}
}
// showingChildNumber === 0 无可渲染的子路由 (可能有子路由 hidden属性为true)
// 无可渲染chiildren时 把当前路由item作为仅有的子路由渲染
return {
...props.item,
path: '' // resolvePath避免resolve拼接时 拼接重复
}
})
// menu icon
const icon = computed(() => {
// 子路由 如果没有icon就用父路由的
return theOnlyOneChildRoute.value?.meta?.icon || (props.item.meta && props.item.meta.icon)
})
// 利用path.resolve 根据父路径+子路径 解析成正确路径 子路径可能是相对的
// resolvePath在模板中使用
const resolvePath = (childPath: string) => {
return path.resolve(props.basePath, childPath)
}
return {
theOnlyOneChildRoute,
icon,
resolvePath
}
}
})
</script>
<style lang="scss">
.sidebar-item-container {
.menu-icon { // icon样式调整
margin-right: 16px;
margin-left: 5px;
vertical-align: middle;
}
}
</style>
2-3 sidebar css样式调整
src/styles/sidebar.scss
#app {
.sidebar-container {
height: 100%;
background-color: $menuBg;
// menu未收起时样式
&-menu:not(.el-menu--collapse) {
width: $sideBarWidth;
}
// 菜单收起时的样式调整
.el-menu--collapse {
// 隐藏submenu title
.submenu-title {
display: none;
}
}
.el-submenu {
.el-menu {
.el-menu-item {
background-color: $subMenuBg !important;
&:hover {
background-color: $subMenuHover !important;
}
}
}
}
.el-menu {
border: none;
}
}
}
2-4 路由表里icon和title
路由表里 icon 和title名称大家改改试试
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
import Layout from '@/layout/index.vue'
// 看作是异步获取路由
export const asyncRoutes: Array<RouteRecordRaw> = [
{
path: '/documentation',
component: Layout, // 布局组件作为一级路由
redirect: '/documentation/index',
children: [
{
path: '/documentation/index',
name: 'Documentation',
component: () => import(/* webpackChunkName: "documentation" */ '@/views/documentation/index.vue'),
meta: {
title: 'Documentation',
icon: 'documentation'
}
}
]
},
{
path: '/guide',
component: Layout,
redirect: '/guide/index',
children: [
{
path: 'index',
name: 'Guide',
component: () => import(/* webpackChunkName: "guide" */ '@/views/guide/index.vue'),
meta: {
title: 'Guide',
icon: 'guide'
}
}
]
},
{
path: '/system',
component: Layout,
redirect: '/system/user',
meta: {
title: 'System',
icon: 'lock'
},
children: [
{
path: 'menu',
component: () => import(/* webpackChunkName: "menu" */ '@/views/system/menu.vue'),
meta: {
title: 'Menu Management'
}
},
{
path: 'role',
component: () => import(/* webpackChunkName: "role" */ '@/views/system/role.vue'),
meta: {
title: 'Role Management'
}
},
{
path: 'user',
component: () => import(/* webpackChunkName: "user" */ '@/views/system/user.vue'),
meta: {
title: 'User Management'
}
}
]
}
]
export const constantRoutes: Array<RouteRecordRaw> = [
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [
{
path: 'dashboard',
name: 'Dashboard',
component: () => import(/* webpackChunkName: "dashboard" */ '@/views/dashboard/index.vue'),
meta: {
title: 'Dashboard',
icon: 'dashboard'
}
}
]
}
]
export const routes = [
...constantRoutes,
...asyncRoutes
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
本节参考源码
https://gitee.com/brolly/vue3-element-admin/commit/053e7956eb2a09ac218863b57a203a7fcae0fbec
现在后不够完善继续