SideBar.vue 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. <script setup lang="ts">
  2. import type { ComputedRef, Ref } from 'vue'
  3. import type { AntdIconType } from '@ant-design/icons-vue/lib/components/AntdIcon'
  4. import type { IconComponentProps } from '@ant-design/icons-vue/es/components/Icon'
  5. import type { Key } from 'ant-design-vue/es/_util/type'
  6. import Logo from '@/components/Logo/Logo.vue'
  7. import { routes } from '@/routes'
  8. import EnvIndicator from '@/components/EnvIndicator/EnvIndicator.vue'
  9. const route = useRoute()
  10. const openKeys = ref([openSub()])
  11. const selectedKey = ref([route.name]) as Ref<Key[]>
  12. function openSub() {
  13. const path = route.path
  14. const lastSepIndex = path.lastIndexOf('/')
  15. return path.substring(1, lastSepIndex)
  16. }
  17. watch(route, () => {
  18. selectedKey.value = [route.name as Key]
  19. const sub = openSub()
  20. const p = openKeys.value.indexOf(sub)
  21. if (p === -1)
  22. openKeys.value.push(sub)
  23. })
  24. const sidebars = computed(() => {
  25. return routes[0].children
  26. })
  27. interface meta {
  28. icon: AntdIconType
  29. hiddenInSidebar: boolean
  30. hideChildren: boolean
  31. name: () => string
  32. }
  33. interface sidebar {
  34. path: string
  35. name: () => string
  36. meta: meta
  37. children: sidebar[]
  38. }
  39. const visible: ComputedRef<sidebar[]> = computed(() => {
  40. const res: sidebar[] = [];
  41. (sidebars.value || []).forEach(s => {
  42. if (s.meta && s.meta.hiddenInSidebar)
  43. return
  44. const t: sidebar = {
  45. path: s.path,
  46. name: s?.meta?.name ?? (() => ''),
  47. meta: s.meta as unknown as meta,
  48. children: [],
  49. };
  50. (s.children || []).forEach(c => {
  51. if (c.meta && c.meta.hiddenInSidebar)
  52. return
  53. t.children.push((c as unknown as sidebar))
  54. })
  55. res.push(t)
  56. })
  57. return res
  58. })
  59. </script>
  60. <template>
  61. <div class="sidebar">
  62. <Logo />
  63. <AMenu
  64. v-model:openKeys="openKeys"
  65. v-model:selectedKeys="selectedKey"
  66. mode="inline"
  67. >
  68. <EnvIndicator />
  69. <template v-for="s in visible">
  70. <AMenuItem
  71. v-if="s.children.length === 0 || s.meta.hideChildren"
  72. :key="s.name"
  73. @click="$router.push(`/${s.path}`).catch(() => {})"
  74. >
  75. <Component :is="s.meta.icon as IconComponentProps" />
  76. <span>{{ s.meta?.name() }}</span>
  77. </AMenuItem>
  78. <ASubMenu
  79. v-else
  80. :key="s.path"
  81. >
  82. <template #title>
  83. <Component :is="s.meta.icon as IconComponentProps" />
  84. <span>{{ s?.meta?.name() }}</span>
  85. </template>
  86. <AMenuItem
  87. v-for="child in s.children"
  88. :key="child.name"
  89. >
  90. <RouterLink :to="`/${s.path}/${child.path}`">
  91. {{ child?.meta?.name() }}
  92. </RouterLink>
  93. </AMenuItem>
  94. </ASubMenu>
  95. </template>
  96. </AMenu>
  97. </div>
  98. </template>
  99. <style lang="less">
  100. .sidebar {
  101. position: sticky;
  102. top: 0;
  103. .logo {
  104. display: inline-flex;
  105. justify-content: center;
  106. align-items: center;
  107. img {
  108. margin-left: -18px;
  109. }
  110. }
  111. }
  112. .ant-layout-sider-collapsed .logo {
  113. overflow: hidden;
  114. }
  115. .ant-menu-inline, .ant-menu-vertical, .ant-menu-vertical-left {
  116. border-right: unset;
  117. }
  118. .ant-layout-sider-collapsed {
  119. .logo {
  120. img {
  121. margin-left: 0;
  122. }
  123. .text {
  124. display: none;
  125. }
  126. }
  127. }
  128. </style>