Appearance
路由支持外链
点击跳转百度
2-1 创建 SidebarItemLink. 组件 #
在这个组件里来判断 是否是带协议链接 如果是带协议链接 menu-item 里,就用 a 标签渲染 否则用 router-link 渲染 (注意 我们要把 el-menu 路由模式关掉 去掉 el-menu 组件上 router 属性)
src/layout/components/Sidebar/SidebarItemLink.vue
用到了 component 动态组件 并以插槽形式包裹 SIdebarItem 组件
vue
<template>
<component :is="type" v-bind="linkProps">
<slot />
</component>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue'
import { isExternal } from '@/utils/validate'
// 针对路径是外链 就渲染为a标签 如果是正常路由路径 就渲染为 router-link
// el-menu组件的router属性去掉了 不开启路由模式
export default defineComponent({
name: 'SidebarItemLink',
props: {
to: {
type: String,
required: true
}
},
setup(props) {
// 判断接收的路径 是不是外链
const isExt = computed(() => isExternal(props.to))
const type = computed(() => {
if (isExt.value) {
return 'a'
}
return 'router-link'
})
const linkProps = computed(() => {
if (isExt.value) {
return { // a 标签的一些原生属性
href: props.to,
target: '_blank',
rel: 'noopener'
}
}
// router-link只需一个to props
return {
to: props.to
}
})
return {
type,
linkProps
}
}
})
</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
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
2-2 SidebarItem 中使用 SidebarItemLink 组件 #
src/layout/components/Sidebar/SidebarItem.vue
vue
<template>
<div class="sidebar-item-container">
<!-- 只渲染一个路由 并且路由只有一个子路由时直接渲染这个子路由 -->
<template
v-if="theOnlyOneChildRoute && !theOnlyOneChildRoute.children"
>
<sidebar-item-link
v-if="theOnlyOneChildRoute.meta"
:to="resolvePath(theOnlyOneChildRoute.path)"
>
<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>
</sidebar-item-link>
</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 path from 'path'
import { defineComponent, PropType, computed, toRefs } from 'vue'
import { RouteRecordRaw } from 'vue-router'
import SidebarItemLink from './SidebarItemLink.vue'
import { isExternal } from '@/utils/validate'
export default defineComponent({
name: 'SidebarItem',
components: {
SidebarItemLink
},
props: {
item: {
type: Object as PropType<RouteRecordRaw>,
required: true
},
basePath: {
type: String,
required: true
}
},
setup (props) {
const { item } = toRefs(props)
// 子路由数量
const showingChildNumber = computed(() => {
const children = (props.item.children || []).filter(child => {
if (child.meta && child.meta.hidden) return false
return true
})
return children.length
})
// 只有一个可渲染的子路由直接渲染这个子路由 (由于我们有的路由 layout布局组件是一级路由 二级路由才是我们要渲染成菜单)
const theOnlyOneChildRoute = computed(() => {
// 多个children
if (showingChildNumber.value > 1) {
return null
}
// 子路由只有一个时 并且做个hidden筛选
if (item.value.children) {
for (const child of item.value.children) {
if (!child.meta || !child.meta.hidden) { // hidden属性控制路由是否渲染成菜单
return child
}
}
}
// showingChildNumber === 0
// 没有可渲染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)
})
// 拼接路径 父路径+子路径(相对路径)
const resolvePath = (childPath: string) => {
// 如果是带协议外链 直接返回
if (isExternal(childPath)) {
return childPath
}
// 如果不是外链 需要和basePath拼接
return path.resolve(props.basePath, childPath)
}
return {
theOnlyOneChildRoute,
icon,
resolvePath
}
}
})
</script>
<style lang="scss">
.sidebar-item-container {
.menu-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
136
137
138
139
140
141
142
143
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
136
137
138
139
140
141
142
143
2-3 关掉 el-menu 路由模式 #
2-4 添加外链路由 #
#
src/router/index.ts
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: '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'
}
}
]
},
{ // 外链路由
path: '/external-link',
component: Layout,
children: [
{
path: 'https://www.baidu.com/',
redirect: '/',
meta: {
title: 'External Link',
icon: 'link'
}
}
]
}
]
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
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
2-5 去掉 a 标签默认样式 #
src/styles/index.scss
css
@import './variables.scss';
@import './sidebar.scss';
html {
height: 100%;
box-sizing: border-box;
}
body {
height: 100%;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
}
#app {
height: 100%;
}
// a标签默认样式调整
a:focus,
a:active {
outline: none;
}
a,
a:focus,
a:hover {
cursor: pointer;
color: inherit;
text-decoration: 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
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
本节参考源码 #
https://gitee.com/brolly/vue3-element-admin/commit/501926e86ea911b5d5d8c0744131a6c9bc245962