SideNav.vue 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. <template>
  2. <aside v-if="sidebarDepth > 0" class="sidebar">
  3. <div v-if="topLabel" class="sb-label sb-top">{{ topLabel }}</div>
  4. <div v-if="items.length" class="sb-section">
  5. <div
  6. v-for="item in items"
  7. :key="item.id"
  8. :class="['sb-item', { active: isActive(item) }]"
  9. @click="onClick(item)"
  10. >
  11. <span class="ico">●</span>
  12. <span class="label">{{ item.name }}</span>
  13. </div>
  14. </div>
  15. <div v-else class="sb-placeholder">该分类暂无子菜单</div>
  16. </aside>
  17. </template>
  18. <script setup>
  19. import { computed, onMounted } from 'vue'
  20. import { useRouter, useRoute } from 'vue-router'
  21. import { useMenu } from '@/store/menu'
  22. const router = useRouter()
  23. const route = useRoute()
  24. const { state, load, sidebarItems, sidebarDepth, cascadeSelection, topBarLevelCount } = useMenu()
  25. // 侧边栏要展示的项由菜单 store 派生:始终是菜单树的最后一层
  26. const items = computed(() => sidebarItems.value)
  27. const sidebarVisible = computed(() => sidebarDepth.value > 0)
  28. // 侧边栏标题:取上边栏最后一行选中项的名称(即层级最深的可见项)
  29. const topLabel = computed(() => {
  30. if (!sidebarVisible.value) return ''
  31. const sel = cascadeSelection.value
  32. const idx = topBarLevelCount.value - 1
  33. return sel[idx]?.name || sel[0]?.name || ''
  34. })
  35. onMounted(() => {
  36. if (!state.loaded) load()
  37. })
  38. function isActive(item) {
  39. if (!item?.route) return false
  40. return route.path === item.route || route.path.startsWith(`${item.route}/`)
  41. }
  42. function onClick(item) {
  43. if (!item?.route) return
  44. if (route.path !== item.route) router.push(item.route)
  45. }
  46. function iconChar(item) {
  47. if (!item?.icon) return '●'
  48. const trimmed = String(item.icon).trim()
  49. if (!trimmed) return '●'
  50. // 简单把首字符作为图标占位(未引入图标库时退化为单字符)
  51. return trimmed.charAt(0).toUpperCase()
  52. }
  53. </script>
  54. <style scoped>
  55. .sidebar {
  56. width: 208px;
  57. flex: none;
  58. background: #0c2b5e;
  59. color: rgba(255, 255, 255, 0.78);
  60. padding: 14px 0;
  61. overflow-y: auto;
  62. }
  63. .sb-section {
  64. padding: 0 12px;
  65. margin-bottom: 16px;
  66. }
  67. .sb-label {
  68. font-size: 10.5px;
  69. color: rgba(255, 255, 255, 0.4);
  70. padding: 6px 14px;
  71. margin-bottom: 6px;
  72. letter-spacing: 1px;
  73. }
  74. .sb-top {
  75. font-size: 12px;
  76. color: rgba(255, 255, 255, 0.7);
  77. letter-spacing: 0.5px;
  78. }
  79. .sb-item {
  80. display: flex;
  81. align-items: center;
  82. gap: 11px;
  83. padding: 9px 14px;
  84. border-radius: 6px;
  85. font-size: 12.5px;
  86. cursor: pointer;
  87. color: rgba(255, 255, 255, 0.78);
  88. margin-bottom: 2px;
  89. transition: 0.15s;
  90. }
  91. .sb-item:hover {
  92. background: #143a80;
  93. color: #fff;
  94. }
  95. .sb-item.active {
  96. background: #fff;
  97. color: #0c2b5e;
  98. font-weight: 600;
  99. }
  100. .sb-item.active .ico {
  101. color: #1552d0;
  102. }
  103. .sb-item .ico {
  104. width: 14px;
  105. height: 14px;
  106. font-size: 13px;
  107. display: flex;
  108. align-items: center;
  109. justify-content: center;
  110. line-height: 1;
  111. color: rgba(255, 255, 255, 0.6);
  112. font-weight: 700;
  113. flex: none;
  114. }
  115. .sb-item .label {
  116. flex: 1;
  117. white-space: nowrap;
  118. overflow: hidden;
  119. text-overflow: ellipsis;
  120. }
  121. .sb-item .num {
  122. font-size: 10px;
  123. color: #fff;
  124. background: #e13b3b;
  125. padding: 1px 5px;
  126. border-radius: 8px;
  127. font-weight: 600;
  128. }
  129. .sb-divider {
  130. height: 1px;
  131. background: rgba(255, 255, 255, 0.1);
  132. margin: 8px 14px;
  133. }
  134. .sb-placeholder {
  135. padding: 12px 26px;
  136. font-size: 12px;
  137. color: rgba(255, 255, 255, 0.4);
  138. }
  139. </style>