SidebarItem.vue 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. <!-- layout/components/Sidebar/SidebarItem.vue -->
  2. <template>
  3. <template v-if="!item.hidden">
  4. <!-- 有子菜单 -->
  5. <div v-if="item.children && item.children.length > 0" class="sub-menu">
  6. <div class="sub-menu-title" @click="toggleSubGroup">
  7. <svg-icon
  8. v-if="item.meta?.icon"
  9. :icon-class="item.meta.icon"
  10. class="menu-icon"
  11. />
  12. <span class="menu-text" v-if="!isCollapse">{{ item.meta?.title }}</span>
  13. <span class="sub-arrow" v-if="!isCollapse">
  14. {{ expandedSubGroup ? "▼" : "▶" }}
  15. </span>
  16. </div>
  17. <div class="sub-menu-items" v-show="expandedSubGroup || isCollapse">
  18. <sidebar-item
  19. v-for="child in item.children"
  20. :key="child.path"
  21. :item="child"
  22. :parent-path="getChildFullPath(child.path)"
  23. :is-collapse="isCollapse"
  24. :active-path="activePath"
  25. @menu-click="handleMenuClick"
  26. />
  27. </div>
  28. </div>
  29. <!-- 没有子菜单 -->
  30. <div
  31. v-else
  32. :class="[
  33. 'sub-menu-item',
  34. {
  35. active:
  36. activePath === getFullPath ||
  37. activePath.startsWith(getFullPath + '/'),
  38. },
  39. ]"
  40. @click="handleMenuClick(getFullPath)"
  41. >
  42. <svg-icon
  43. v-if="item.meta?.icon"
  44. :icon-class="item.meta.icon"
  45. class="menu-icon"
  46. />
  47. <span class="menu-text">{{ item.meta?.title }}</span>
  48. </div>
  49. </template>
  50. </template>
  51. <script>
  52. import { ref, watch } from "vue";
  53. export default {
  54. name: "SidebarItem",
  55. props: {
  56. item: {
  57. type: Object,
  58. required: true,
  59. },
  60. parentPath: {
  61. type: String,
  62. default: "",
  63. },
  64. isCollapse: {
  65. type: Boolean,
  66. default: false,
  67. },
  68. activePath: {
  69. type: String,
  70. default: "",
  71. },
  72. },
  73. emits: ["menu-click"],
  74. setup(props, { emit }) {
  75. const expandedSubGroup = ref(false);
  76. const getFullPath = computed(() => {
  77. let path = props.item.path;
  78. if (!path.startsWith("/") && props.parentPath) {
  79. path = props.parentPath + "/" + path;
  80. }
  81. return path;
  82. });
  83. const getChildFullPath = (childPath) => {
  84. if (childPath.startsWith("/")) {
  85. return childPath;
  86. }
  87. return getFullPath.value;
  88. };
  89. const toggleSubGroup = () => {
  90. expandedSubGroup.value = !expandedSubGroup.value;
  91. };
  92. const handleMenuClick = (path) => {
  93. emit("menu-click", path);
  94. };
  95. // 自动展开当前路由对应的父级菜单
  96. watch(
  97. () => props.activePath,
  98. (newPath) => {
  99. if (
  100. newPath === getFullPath.value ||
  101. newPath.startsWith(getFullPath.value + "/")
  102. ) {
  103. expandedSubGroup.value = true;
  104. }
  105. },
  106. { immediate: true },
  107. );
  108. return {
  109. expandedSubGroup,
  110. getFullPath,
  111. getChildFullPath,
  112. toggleSubGroup,
  113. handleMenuClick,
  114. };
  115. },
  116. };
  117. </script>
  118. <style scoped>
  119. .sub-menu {
  120. margin-bottom: 2px;
  121. }
  122. .sub-menu-title {
  123. padding: 0 20px 0 44px;
  124. height: 40px;
  125. line-height: 40px;
  126. color: #bfcbd9;
  127. cursor: pointer;
  128. display: flex;
  129. align-items: center;
  130. gap: 12px;
  131. position: relative;
  132. }
  133. .sub-menu-title:hover {
  134. background-color: #263445;
  135. color: #fff;
  136. }
  137. .sub-arrow {
  138. position: absolute;
  139. right: 20px;
  140. font-size: 12px;
  141. }
  142. .sub-menu-items {
  143. overflow: hidden;
  144. }
  145. .sub-menu-item {
  146. padding: 0 20px 0 68px;
  147. height: 36px;
  148. line-height: 36px;
  149. color: #bfcbd9;
  150. cursor: pointer;
  151. white-space: nowrap;
  152. display: flex;
  153. align-items: center;
  154. gap: 12px;
  155. }
  156. .sub-menu-item:hover {
  157. background-color: #263445;
  158. color: #fff;
  159. }
  160. .sub-menu-item.active {
  161. background-color: #409eff;
  162. color: #fff;
  163. }
  164. /* 收起时的样式 */
  165. .sidebar-container.collapsed .sub-menu-title,
  166. .sidebar-container.collapsed .sub-menu-item {
  167. justify-content: center;
  168. padding: 0;
  169. }
  170. .sidebar-container.collapsed .menu-text,
  171. .sidebar-container.collapsed .sub-arrow {
  172. display: none;
  173. }
  174. </style>