image.png
image.png

2-1 SidebarItem

src/layout/components/Sidebar/SidebarItem.vue

主要是判断

image.png

  1. <template>
  2. <div
  3. v-if="!item.meta || !item.meta.hidden"
  4. class="sidebar-item-container"
  5. >
  6. <!-- 只渲染一个路由 并且路由只有一个子路由时直接渲染这个子路由 -->
  7. <template
  8. v-if="theOnlyOneChildRoute && (!theOnlyOneChildRoute.children || noShowingChildren)"
  9. >
  10. <sidebar-item-link
  11. v-if="theOnlyOneChildRoute.meta"
  12. :to="resolvePath(theOnlyOneChildRoute.path)"
  13. >
  14. <el-menu-item
  15. :index="resolvePath(theOnlyOneChildRoute.path)"
  16. >
  17. <i v-if="icon && icon.includes('el-icon')" :class="icon"></i>
  18. <svg-icon
  19. v-else-if="icon"
  20. class="menu-icon"
  21. :icon-class="icon"
  22. ></svg-icon>
  23. <template #title>
  24. <span>{{ theOnlyOneChildRoute.meta.title }}</span>
  25. </template>
  26. </el-menu-item>
  27. </sidebar-item-link>
  28. </template>
  29. <!-- 有多个子路由时 -->
  30. <el-submenu
  31. v-else
  32. :index="resolvePath(item.path)"
  33. popper-append-to-body
  34. >
  35. <template #title>
  36. <i
  37. v-if="item.meta && item.meta.icon.includes('el-icon')"
  38. :class="icon"
  39. ></i>
  40. <svg-icon
  41. v-else-if="item.meta && item.meta.icon"
  42. class="menu-icon"
  43. :icon-class="item.meta.icon"
  44. ></svg-icon>
  45. <span v-if="item.meta" class="submenu-title">{{ item.meta.title }}</span>
  46. </template>
  47. <template v-if="item.children">
  48. <sidebar-item
  49. v-for="child in item.children"
  50. :key="child.path"
  51. :is-nest="true"
  52. :item="child"
  53. :base-path="resolvePath(child.path)"
  54. >
  55. </sidebar-item>
  56. </template>
  57. </el-submenu>
  58. </div>
  59. </template>
  60. <script lang="ts">
  61. import path from 'path'
  62. import { defineComponent, PropType, computed, toRefs } from 'vue'
  63. import SidebarItemLink from './SidebarItemLink.vue'
  64. import { isExternal } from '@/utils/validate'
  65. import { MenuItemRouter } from '@/router/type'
  66. export default defineComponent({
  67. name: 'SidebarItem',
  68. components: {
  69. SidebarItemLink
  70. },
  71. props: {
  72. item: {
  73. type: Object as PropType<MenuItemRouter>,
  74. required: true
  75. },
  76. basePath: {
  77. type: String,
  78. required: true
  79. }
  80. },
  81. setup (props) {
  82. const { item } = toRefs(props)
  83. // 子路由数量
  84. const showingChildNumber = computed(() => {
  85. const children = (props.item.children || []).filter(child => {
  86. // hidden属性控制路由是否渲染成菜单 像login 401 404等路由都不需要渲染成菜案
  87. if (child.meta && child.meta.hidden) return false
  88. return true
  89. })
  90. return children.length
  91. })
  92. // 只有一个可渲染的子路由直接渲染这个子路由 (由于我们有的路由 layout布局组件是一级路由 二级路由才是我们要渲染成菜单)
  93. const theOnlyOneChildRoute = computed(() => {
  94. // 多个children
  95. if (showingChildNumber.value > 1) {
  96. return null
  97. }
  98. // 子路由只有一个时 并且做个hidden筛选
  99. if (item.value.children) {
  100. for (const child of item.value.children) {
  101. // hidden属性控制路由是否渲染成菜单 像login 401 404等路由都不需要渲染成菜单
  102. if (!child.meta || !child.meta.hidden) {
  103. return child
  104. }
  105. }
  106. }
  107. // showingChildNumber === 0
  108. // 没有可渲染chiildren时 就渲染当前父路由item
  109. return {
  110. ...props.item,
  111. path: '' // resolvePath避免resolve拼接时 拼接重复
  112. }
  113. })
  114. // 是否有可渲染子路由
  115. const noShowingChildren = computed(() => showingChildNumber.value === 0)
  116. // menu icon
  117. const icon = computed(() => {
  118. // 子路由 如果没有icon就用父路由的
  119. return (theOnlyOneChildRoute.value?.meta?.icon || (props.item.meta && props.item.meta.icon)) as string
  120. })
  121. // 拼接路径 父路径+子路径(相对路径)
  122. const resolvePath = (childPath: string) => {
  123. // 如果是带协议外链 直接返回
  124. if (isExternal(childPath)) {
  125. return childPath
  126. }
  127. // 如果不是外链 需要和basePath拼接
  128. return path.resolve(props.basePath, childPath)
  129. }
  130. return {
  131. theOnlyOneChildRoute,
  132. icon,
  133. resolvePath,
  134. noShowingChildren
  135. }
  136. }
  137. })
  138. </script>
  139. <style lang="scss">
  140. .sidebar-item-container {
  141. .menu-icon {
  142. margin-right: 16px;
  143. margin-left: 5px;
  144. vertical-align: middle;
  145. }
  146. }
  147. </style>

2-2 路由meta对象类型处理

在router目录下typings.d.ts 针对 RouteMeta进行类型补充

RouteMeta类型说明
src/router/typings.d.ts

  1. import 'vue-router'
  2. declare module 'vue-router' {
  3. interface RouteMeta {
  4. title?: string; // 路由菜单title
  5. icon?: string; // 路由菜单icon
  6. hidden?: boolean; // 菜单栏不显示
  7. // 路由是否缓存 没有这个属性或false都会缓存 true不缓存
  8. noCache?: boolean;
  9. activeMenu?: string; // 指定菜单激活
  10. breadcrumb?: boolean; // 该路由是否显示面包屑
  11. affix?: boolean; // 固定显示在tagsView中
  12. alwaysShow?: boolean; // 菜单是否一直显示根路由
  13. }
  14. }

SidebarItem中router meta正常使用

src/layout/components/Sidebar/SidebarItem.vue
image.png
icon断言为string
src/layout/components/Sidebar/SidebarItem.vue
image.png

本节参考源码

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