Skip to content
On this page

侧边栏支持滚动

效果图

image.png 上下滚动 image.png

5-1 修改 sidebar

sidebar 组件中添加 scrollPanel 组件

src/layout/components/Sidebar/index.vue image.png

vue
<template>
  <div class="sidebar-wrapper">
    <logo v-if="showLogo" :collapse="isCollapse" />
    <scroll-panel>
      <el-menu
        class="sidebar-container-menu"
        :class="{
          'sidebar-show-logo': showLogo
        }"
        mode="vertical"
        :default-active="activeMenu"
        :background-color="scssVariables.menuBg"
        :text-color="scssVariables.menuText"
        :active-text-color="themeColor"
        :collapse="isCollapse"
        :collapse-transition="true"
      >
        <sidebar-item
          v-for="route in menuRoutes"
          :key="route.path"
          :item="route"
          :base-path="route.path"
        />
      </el-menu>
    </scroll-panel>
  </div>
</template>

<script lang="ts">
import { defineComponent, computed } from 'vue'
import { useRoute } from 'vue-router'
import variables from '@/styles/variables.scss'
import { routes } from '@/router'
import SidebarItem from './SidebarItem.vue'
import { useStore } from '@/store'
import Logo from './Logo.vue'
import ScrollPanel from '@/components/ScrollPanel.vue'

export default defineComponent({
  name: 'Sidebar',
  components: {
    Logo,
    SidebarItem,
    ScrollPanel
  },
  setup() {
    const route = useRoute()
    const store = useStore()
    // 根据路由路径 对应 当前激活的菜单
    const activeMenu = computed(() => {
      const { path, meta } = route
      // 可根据meta.activeMenu指定 当前路由激活时 让哪个菜单高亮选中
      if (meta.activeMenu) {
        return meta.activeMenu
      }
      return path
    })
    // scss变量
    const scssVariables = computed(() => variables)
    // 展开收起状态 稍后放store 当前是展开就让它收起
    const isCollapse = computed(() => !store.getters.sidebar.opened)

    // 渲染路由
    const menuRoutes = computed(() => routes)

    // 获取主题色
    const themeColor = computed(() => store.getters.themeColor)

    // 是否显示logo
    const showLogo = computed(() => store.state.settings.sidebarLogo)

    return {
      // ...toRefs(variables), // 不有toRefs原因 缺点variables里面变量属性来源不明确
      scssVariables,
      isCollapse,
      activeMenu,
      menuRoutes,
      themeColor,
      showLogo
    }
  }
})
</script>

<style lang="scss" scoped>
  .sidebar-wrapper {
    .sidebar-container-menu {
      height: 100vh;
      &.sidebar-show-logo {
        height: calc(100vh - 50px);
      }
    }
  }
</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

5-2 scrollPanel 组件

之前在 tagsview 文件夹里就有 scrollPanel 组件, 可以共用一个, 从 layout/compoents/TagsView 里 把 ScrollPanel.vue 移动到 src/compoents 里

image.png src/layout/components/TagsView/index.vue 修改导入路径 image.png

5-3 随便添加点测试路由

src/router/index.ts image.png

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',
          hidden: false, // 菜单栏不显示
          // 路由是否缓存 没有这个属性或false都会缓存 true不缓存
          noCache: false
        }
      }
    ]
  },
  {
    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'
          // 当guide路由激活时高亮选中的是 documentation/index菜单
          // activeMenu: '/documentation/index'
        }
      }
    ]
  },
  {
    path: '/system',
    component: Layout,
    redirect: '/system/user',
    meta: {
      title: 'System',
      icon: 'lock',
      alwaysShow: true // 根路由始终显示 哪怕只有一个子路由
    },
    children: [
      {
        path: 'menu',
        name: 'Menu Management',
        component: () => import(/* webpackChunkName: "menu" */ '@/views/system/menu.vue'),
        meta: {
          title: 'Menu Management',
          hidden: false,
          breadcrumb: false
        }
      },
      {
        path: 'role',
        name: 'Role Management',
        component: () => import(/* webpackChunkName: "role" */ '@/views/system/role.vue'),
        meta: {
          title: 'Role Management',
          hidden: false
        }
      },
      {
        path: 'user',
        name: 'User Management',
        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'
        }
      }
    ]
  },
  { // 404一定放在要在最后面
    path: '/:pathMatch(.*)*',
    redirect: '/404',
    meta: {
      hidden: true
    }
  }
]

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'
          icon: 'el-icon-platform-eleme',
          affix: true // 固定显示在tagsView中
        }
      }
    ]
  },
  {
    path: '/redirect',
    component: Layout,
    meta: {
      hidden: true
    },
    children: [
      { // 带参数的动态路由正则匹配
        // https://next.router.vuejs.org/zh/guide/essentials/route-matching-syntax.html#%E5%8F%AF%E9%87%8D%E5%A4%8D%E7%9A%84%E5%8F%82%E6%95%B0
        path: '/redirect/:path(.*)', // 要匹配多级路由 应该加*号
        component: () => import('@/views/redirect/index.vue')
      }
    ]
  },
  {
    path: '/401',
    component: Layout,
    children: [
      {
        path: '',
        component: () => import('@/views/error-page/401.vue'),
        meta: {
          title: '401',
          icon: '404',
          hidden: true
        }
      }
    ]
  },
  {
    path: '/404',
    component: () => import('@/views/error-page/404.vue'),
    meta: {
      hidden: true // 404 hidden掉
    }
  },
  // 以下都是测试路由 测完可以删了
  {
    path: '/menu',
    name: 'Menu Management1',
    component: () => import(/* webpackChunkName: "menu" */ '@/views/system/menu.vue'),
    meta: {
      title: 'Menu Management1',
      hidden: false,
      breadcrumb: false
    }
  },
  {
    path: '/role',
    name: 'Role Management1',
    component: () => import(/* webpackChunkName: "role" */ '@/views/system/role.vue'),
    meta: {
      title: 'Role Management1',
      hidden: false
    }
  },
  {
    path: '/user',
    name: 'User Management1',
    component: () => import(/* webpackChunkName: "user" */ '@/views/system/user.vue'),
    meta: {
      title: 'User Management1'
    }
  },
  {
    path: '/menu',
    name: 'Menu Management2',
    component: () => import(/* webpackChunkName: "menu" */ '@/views/system/menu.vue'),
    meta: {
      title: 'Menu Management2',
      hidden: false,
      breadcrumb: false
    }
  },
  {
    path: '/role',
    name: 'Role Management2',
    component: () => import(/* webpackChunkName: "role" */ '@/views/system/role.vue'),
    meta: {
      title: 'Role Management2',
      hidden: false
    }
  },
  {
    path: '/user',
    name: 'User Management2',
    component: () => import(/* webpackChunkName: "user" */ '@/views/system/user.vue'),
    meta: {
      title: 'User Management2'
    }
  },
  {
    path: '/menu',
    name: 'Menu Management3',
    component: () => import(/* webpackChunkName: "menu" */ '@/views/system/menu.vue'),
    meta: {
      title: 'Menu Management3',
      hidden: false,
      breadcrumb: false
    }
  },
  {
    path: '/role',
    name: 'Role Management3',
    component: () => import(/* webpackChunkName: "role" */ '@/views/system/role.vue'),
    meta: {
      title: 'Role Management4',
      hidden: false
    }
  },
  {
    path: '/user',
    name: 'User Management3',
    component: () => import(/* webpackChunkName: "user" */ '@/views/system/user.vue'),
    meta: {
      title: 'User Management3'
    }
  },
  {
    path: '/menu',
    name: 'Menu Management4',
    component: () => import(/* webpackChunkName: "menu" */ '@/views/system/menu.vue'),
    meta: {
      title: 'Menu Management4',
      hidden: false,
      breadcrumb: false
    }
  },
  {
    path: '/role',
    name: 'Role Management4',
    component: () => import(/* webpackChunkName: "role" */ '@/views/system/role.vue'),
    meta: {
      title: 'Role Management4',
      hidden: false
    }
  },
  {
    path: '/user',
    name: 'User Management4',
    component: () => import(/* webpackChunkName: "user" */ '@/views/system/user.vue'),
    meta: {
      title: 'User Management4'
    }
  },
  {
    path: '/role',
    name: 'Role Management5',
    component: () => import(/* webpackChunkName: "role" */ '@/views/system/role.vue'),
    meta: {
      title: 'Role Management5',
      hidden: false
    }
  },
  {
    path: '/user',
    name: 'User Management5',
    component: () => import(/* webpackChunkName: "user" */ '@/views/system/user.vue'),
    meta: {
      title: 'User Management5'
    }
  }
]

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
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299

测试完滚动 测试的路由都可以删了

沪ICP备20006251号-1