本节目标
image.png
点击收起
image.png

这里菜单渲染 暂时使用router/index.ts里的路由表 路由表里meta里面需要提供title icon 作为菜单项信息,icon是我们自己的svg文件名称

image.png

2-1 Sidebar导入路由表

sidebar组件导入路由表,根据routes循环SidebarItem组件

image.png

  1. <template>
  2. <div>
  3. <!-- 测试展开收起 -->
  4. <h6 @click="isCollapse=!isCollapse">展收测试</h6>
  5. <el-menu
  6. class="sidebar-container-menu"
  7. mode="vertical"
  8. router
  9. :default-active="activeMenu"
  10. :background-color="scssVariables.menuBg"
  11. :text-color="scssVariables.menuText"
  12. :active-text-color="scssVariables.menuActiveText"
  13. :collapse="isCollapse"
  14. :collapse-transition="true"
  15. >
  16. <sidebar-item
  17. v-for="route in menuRoutes"
  18. :key="route.path"
  19. :item="route"
  20. :base-path="route.path"
  21. />
  22. </el-menu>
  23. </div>
  24. </template>
  25. <script lang="ts">
  26. import { defineComponent, computed, ref } from 'vue'
  27. import { useRoute } from 'vue-router'
  28. import variables from '@/styles/variables.scss'
  29. // 导入路由表
  30. import { routes } from '@/router'
  31. import SidebarItem from './SidebarItem.vue'
  32. export default defineComponent({
  33. name: 'Sidebar',
  34. components: {
  35. SidebarItem
  36. },
  37. setup() {
  38. const route = useRoute() // 等价于 this.$route
  39. // 根据路由路径 对应 当前激活的菜单
  40. const activeMenu = computed(() => {
  41. const { path } = route
  42. return path
  43. })
  44. // scss变量
  45. const scssVariables = computed(() => variables)
  46. // 展开收起状态 稍后放store
  47. const isCollapse = ref(false)
  48. // 渲染路由
  49. const menuRoutes = computed(() => routes)
  50. return {
  51. // 不有toRefs原因 缺点在这里 variables里面变量属性感觉来源不明确 不知道有哪些变量值
  52. // ...toRefs(variables),
  53. scssVariables,
  54. isCollapse,
  55. activeMenu,
  56. menuRoutes
  57. }
  58. }
  59. })
  60. </script>

2-2 实现SidebarItem组件

  1. <template>
  2. <div class="sidebar-item-container">
  3. <!-- 一个路由下只有一个子路由的时候 只渲染这个子路由 -->
  4. <template
  5. v-if="theOnlyOneChildRoute && !theOnlyOneChildRoute.children"
  6. >
  7. <el-menu-item
  8. :index="resolvePath(theOnlyOneChildRoute.path)"
  9. >
  10. <svg-icon
  11. v-if="icon"
  12. class="menu-icon"
  13. :icon-class="icon"
  14. ></svg-icon>
  15. <template #title>
  16. <span>{{ theOnlyOneChildRoute.meta.title }}</span>
  17. </template>
  18. </el-menu-item>
  19. </template>
  20. <!-- 多个子路由时 -->
  21. <el-submenu
  22. v-else
  23. :index="resolvePath(item.path)"
  24. popper-append-to-body
  25. >
  26. <template #title>
  27. <svg-icon
  28. v-if="item.meta.icon"
  29. class="menu-icon"
  30. :icon-class="item.meta.icon"
  31. ></svg-icon>
  32. <span class="submenu-title">{{ item.meta.title }}</span>
  33. </template>
  34. <sidebar-item
  35. v-for="child in item.children"
  36. :key="child.path"
  37. :is-nest="true"
  38. :item="child"
  39. :base-path="resolvePath(child.path)"
  40. >
  41. </sidebar-item>
  42. </el-submenu>
  43. </div>
  44. </template>
  45. <script lang="ts">
  46. import { defineComponent, PropType, computed, toRefs } from 'vue'
  47. import { RouteRecordRaw } from 'vue-router'
  48. import path from 'path'
  49. export default defineComponent({
  50. name: 'SidebarItem',
  51. props: {
  52. item: { // 当前路由(此时的父路由)
  53. type: Object as PropType<RouteRecordRaw>,
  54. required: true
  55. },
  56. basePath: { // 父路由路径(子路由路径如果是相对的 要基于父路径)
  57. type: String,
  58. required: true
  59. }
  60. },
  61. setup (props) {
  62. const { item } = toRefs(props)
  63. // 渲染菜单主要先看子路由
  64. // 比如我们的路由 一级路由一般都是layout组件 二级路由才是我们考虑要渲染成菜单的
  65. // 子路由数量
  66. const showingChildNumber = computed(() => {
  67. // hidden路由排除掉 只算可渲染子路由
  68. const children = (props.item.children || []).filter(child => {
  69. if (child.meta && child.meta.hidden) return false
  70. return true
  71. })
  72. return children.length
  73. })
  74. // 要渲染的单个路由 如果该路由只有一个子路由 默认直接渲染这个子路由
  75. // theOnlyOneChildRoute直接通过el-menu-item组件来渲染
  76. const theOnlyOneChildRoute = computed(() => {
  77. // 多个children时 直接return null 多children需要用el-submenu来渲染并递归
  78. if (showingChildNumber.value > 1) {
  79. return null
  80. }
  81. // 只有一个子路由 还要筛选路由meta里有无hidden属性 hidden:true则过滤出去 不用管
  82. // 路由meta里我们会配置hidden属性表示不渲染成菜单,比如login 401 404页面是不渲染成菜单的
  83. if (item.value.children) {
  84. for (const child of item.value.children) {
  85. if (!child.meta || !child.meta.hidden) {
  86. return child
  87. }
  88. }
  89. }
  90. // showingChildNumber === 0 无可渲染的子路由 (可能有子路由 hidden属性为true)
  91. // 无可渲染chiildren时 把当前路由item作为仅有的子路由渲染
  92. return {
  93. ...props.item,
  94. path: '' // resolvePath避免resolve拼接时 拼接重复
  95. }
  96. })
  97. // menu icon
  98. const icon = computed(() => {
  99. // 子路由 如果没有icon就用父路由的
  100. return theOnlyOneChildRoute.value?.meta?.icon || (props.item.meta && props.item.meta.icon)
  101. })
  102. // 利用path.resolve 根据父路径+子路径 解析成正确路径 子路径可能是相对的
  103. // resolvePath在模板中使用
  104. const resolvePath = (childPath: string) => {
  105. return path.resolve(props.basePath, childPath)
  106. }
  107. return {
  108. theOnlyOneChildRoute,
  109. icon,
  110. resolvePath
  111. }
  112. }
  113. })
  114. </script>
  115. <style lang="scss">
  116. .sidebar-item-container {
  117. .menu-icon { // icon样式调整
  118. margin-right: 16px;
  119. margin-left: 5px;
  120. vertical-align: middle;
  121. }
  122. }
  123. </style>

2-3 sidebar css样式调整

src/styles/sidebar.scss
image.png

  1. #app {
  2. .sidebar-container {
  3. height: 100%;
  4. background-color: $menuBg;
  5. // menu未收起时样式
  6. &-menu:not(.el-menu--collapse) {
  7. width: $sideBarWidth;
  8. }
  9. // 菜单收起时的样式调整
  10. .el-menu--collapse {
  11. // 隐藏submenu title
  12. .submenu-title {
  13. display: none;
  14. }
  15. }
  16. .el-submenu {
  17. .el-menu {
  18. .el-menu-item {
  19. background-color: $subMenuBg !important;
  20. &:hover {
  21. background-color: $subMenuHover !important;
  22. }
  23. }
  24. }
  25. }
  26. .el-menu {
  27. border: none;
  28. }
  29. }
  30. }

2-4 路由表里icon和title

路由表里 icon 和title名称大家改改试试

  1. import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
  2. import Layout from '@/layout/index.vue'
  3. // 看作是异步获取路由
  4. export const asyncRoutes: Array<RouteRecordRaw> = [
  5. {
  6. path: '/documentation',
  7. component: Layout, // 布局组件作为一级路由
  8. redirect: '/documentation/index',
  9. children: [
  10. {
  11. path: '/documentation/index',
  12. name: 'Documentation',
  13. component: () => import(/* webpackChunkName: "documentation" */ '@/views/documentation/index.vue'),
  14. meta: {
  15. title: 'Documentation',
  16. icon: 'documentation'
  17. }
  18. }
  19. ]
  20. },
  21. {
  22. path: '/guide',
  23. component: Layout,
  24. redirect: '/guide/index',
  25. children: [
  26. {
  27. path: 'index',
  28. name: 'Guide',
  29. component: () => import(/* webpackChunkName: "guide" */ '@/views/guide/index.vue'),
  30. meta: {
  31. title: 'Guide',
  32. icon: 'guide'
  33. }
  34. }
  35. ]
  36. },
  37. {
  38. path: '/system',
  39. component: Layout,
  40. redirect: '/system/user',
  41. meta: {
  42. title: 'System',
  43. icon: 'lock'
  44. },
  45. children: [
  46. {
  47. path: 'menu',
  48. component: () => import(/* webpackChunkName: "menu" */ '@/views/system/menu.vue'),
  49. meta: {
  50. title: 'Menu Management'
  51. }
  52. },
  53. {
  54. path: 'role',
  55. component: () => import(/* webpackChunkName: "role" */ '@/views/system/role.vue'),
  56. meta: {
  57. title: 'Role Management'
  58. }
  59. },
  60. {
  61. path: 'user',
  62. component: () => import(/* webpackChunkName: "user" */ '@/views/system/user.vue'),
  63. meta: {
  64. title: 'User Management'
  65. }
  66. }
  67. ]
  68. }
  69. ]
  70. export const constantRoutes: Array<RouteRecordRaw> = [
  71. {
  72. path: '/',
  73. component: Layout,
  74. redirect: '/dashboard',
  75. children: [
  76. {
  77. path: 'dashboard',
  78. name: 'Dashboard',
  79. component: () => import(/* webpackChunkName: "dashboard" */ '@/views/dashboard/index.vue'),
  80. meta: {
  81. title: 'Dashboard',
  82. icon: 'dashboard'
  83. }
  84. }
  85. ]
  86. }
  87. ]
  88. export const routes = [
  89. ...constantRoutes,
  90. ...asyncRoutes
  91. ]
  92. const router = createRouter({
  93. history: createWebHashHistory(),
  94. routes
  95. })
  96. export default router

本节参考源码

https://gitee.com/brolly/vue3-element-admin/commit/053e7956eb2a09ac218863b57a203a7fcae0fbec

现在后不够完善继续