Appearance
路由菜单递归 #
点击收起
这里菜单渲染 暂时使用 router/index.ts 里的路由表 路由表里 meta 里面需要提供 title icon 作为菜单项信息,icon 是我们自己的 svg 文件名称
2-1 Sidebar 导入路由表 #
sidebar 组件导入路由表,根据 routes 循环 SidebarItem 组件
vue
<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
2-2 实现 SidebarItem 组件 #
vue
<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
2-3 sidebar css 样式调整 #
src/styles/sidebar.scss
css
#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;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2-4 路由表里 icon 和 title #
路由表里 icon 和 title 名称大家改改试试
typescript
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
本节参考源码 #
https://gitee.com/brolly/vue3-element-admin/commit/053e7956eb2a09ac218863b57a203a7fcae0fbec
现在后不够完善继续