index.vue 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. <!-- layout/components/Sidebar/index.vue -->
  2. <template>
  3. <div class="sidebar-container" :class="{ collapsed: isCollapse }">
  4. <el-menu
  5. :default-active="activeMenu"
  6. :collapse="isCollapse"
  7. background-color="#ffffff"
  8. text-color="#606266"
  9. active-text-color="#409eff"
  10. :collapse-transition="false"
  11. unique-opened
  12. class="sidebar-menu"
  13. >
  14. <template v-for="item in menuList" :key="item.path">
  15. <!-- 有子菜单的项 -->
  16. <el-sub-menu
  17. v-if="item.children && item.children.length > 0"
  18. :index="getFullPath(item.path)"
  19. >
  20. <template #title>
  21. <svg-icon v-if="item.meta?.icon" :icon-class="item.meta.icon" />
  22. <span>{{ item.meta?.title }}</span>
  23. </template>
  24. <!-- 二级菜单 -->
  25. <template v-for="child in item.children" :key="child.path">
  26. <el-sub-menu
  27. v-if="child.children && child.children.length > 0"
  28. :index="getFullPath(child.path, item.path)"
  29. >
  30. <template #title>
  31. <svg-icon
  32. v-if="child.meta?.icon"
  33. :icon-class="child.meta.icon"
  34. />
  35. <span>{{ child.meta?.title }}</span>
  36. </template>
  37. <!-- 三级菜单 -->
  38. <el-menu-item
  39. v-for="subChild in child.children"
  40. :key="subChild.path"
  41. :index="getFullPath(subChild.path, child.path, item.path)"
  42. @click="handleMenuClick(subChild, child, item)"
  43. >
  44. <svg-icon
  45. v-if="subChild.meta?.icon"
  46. :icon-class="subChild.meta.icon"
  47. />
  48. <span>{{ subChild.meta?.title }}</span>
  49. </el-menu-item>
  50. </el-sub-menu>
  51. <!-- 二级菜单(无子菜单) -->
  52. <el-menu-item
  53. v-else
  54. :index="getFullPath(child.path, item.path)"
  55. @click="handleMenuClick(child, item)"
  56. >
  57. <svg-icon v-if="child.meta?.icon" :icon-class="child.meta.icon" />
  58. <span>{{ child.meta?.title }}</span>
  59. </el-menu-item>
  60. </template>
  61. </el-sub-menu>
  62. <!-- 没有子菜单的一级菜单项 -->
  63. <el-menu-item
  64. v-else
  65. :index="getFullPath(item.path)"
  66. @click="handleMenuClick(item)"
  67. >
  68. <svg-icon v-if="item.meta?.icon" :icon-class="item.meta.icon" />
  69. <span>{{ item.meta?.title }}</span>
  70. </el-menu-item>
  71. </template>
  72. </el-menu>
  73. <div class="collapse-btn" @click="toggleCollapse">
  74. <span class="btn-text">{{ isCollapse ? "▶" : "◀" }}</span>
  75. </div>
  76. </div>
  77. </template>
  78. <script>
  79. import { computed } from "vue";
  80. import { useRoute, useRouter } from "vue-router";
  81. import useAppStore from "@/store/modules/app";
  82. import usePermissionStore from "@/store/modules/permission";
  83. export default {
  84. name: "Sidebar",
  85. props: {
  86. topMenuKey: {
  87. type: String,
  88. default: "",
  89. },
  90. },
  91. setup(props) {
  92. const route = useRoute();
  93. const router = useRouter();
  94. const appStore = useAppStore();
  95. const permissionStore = usePermissionStore();
  96. const isCollapse = computed(() => appStore.sidebar.isCollapse);
  97. // 当前激活的菜单
  98. const activeMenu = computed(() => {
  99. return route.path;
  100. });
  101. // 获取菜单列表
  102. const menuList = computed(() => {
  103. const routes = permissionStore.sidebarRouters || [];
  104. console.log("sidebarRouters:", routes);
  105. console.log("topMenuKey:", props.topMenuKey);
  106. if (!routes.length) return [];
  107. if (props.topMenuKey) {
  108. const current = routes.find((item) => item.path === props.topMenuKey);
  109. if (current && current.children) {
  110. return current.children.filter((child) => !child.hidden);
  111. }
  112. }
  113. const firstMenu = routes[0];
  114. if (firstMenu && firstMenu.children) {
  115. return firstMenu.children.filter((child) => !child.hidden);
  116. }
  117. return [];
  118. });
  119. // 获取完整路径
  120. const getFullPath = (path, parentPath = "", grandParentPath = "") => {
  121. if (path.startsWith("/")) {
  122. return path;
  123. }
  124. // 三级菜单:topMenuKey + 一级/二级/三级
  125. if (grandParentPath && parentPath) {
  126. return (
  127. props.topMenuKey +
  128. "/" +
  129. grandParentPath +
  130. "/" +
  131. parentPath +
  132. "/" +
  133. path
  134. );
  135. }
  136. // 二级菜单:topMenuKey + 一级/二级
  137. if (parentPath) {
  138. return props.topMenuKey + "/" + parentPath + "/" + path;
  139. }
  140. // 一级菜单:topMenuKey + 一级
  141. return props.topMenuKey + "/" + path;
  142. };
  143. // 点击菜单
  144. const handleMenuClick = (
  145. item,
  146. parentItem = null,
  147. grandParentItem = null,
  148. ) => {
  149. let fullPath = "";
  150. if (grandParentItem && parentItem) {
  151. // 三级菜单:topMenuKey/一级/二级/三级
  152. fullPath =
  153. props.topMenuKey +
  154. "/" +
  155. grandParentItem.path +
  156. "/" +
  157. parentItem.path +
  158. "/" +
  159. item.path;
  160. } else if (parentItem) {
  161. // 二级菜单:topMenuKey/一级/二级
  162. fullPath = props.topMenuKey + "/" + parentItem.path + "/" + item.path;
  163. } else {
  164. // 一级菜单:topMenuKey/一级
  165. fullPath = props.topMenuKey + "/" + item.path;
  166. }
  167. console.log("跳转路径:", fullPath);
  168. router.push(fullPath);
  169. };
  170. // 收起/展开
  171. const toggleCollapse = () => {
  172. appStore.sidebar.isCollapse = !appStore.sidebar.isCollapse;
  173. };
  174. return {
  175. menuList,
  176. isCollapse,
  177. activeMenu,
  178. getFullPath,
  179. handleMenuClick,
  180. toggleCollapse,
  181. };
  182. },
  183. };
  184. </script>
  185. <style scoped>
  186. .sidebar-container {
  187. height: 100%;
  188. display: flex;
  189. flex-direction: column;
  190. background-color: #ffffff;
  191. border-right: 1px solid #e4e7ed;
  192. }
  193. .sidebar-menu {
  194. flex: 1;
  195. border: none;
  196. overflow-y: auto;
  197. overflow-x: hidden;
  198. }
  199. :deep(.el-menu) {
  200. border-right: none;
  201. background-color: #ffffff;
  202. }
  203. :deep(.el-sub-menu__title) {
  204. color: #606266;
  205. background-color: #ffffff;
  206. }
  207. :deep(.el-sub-menu__title:hover) {
  208. background-color: #f5f7fa !important;
  209. color: #303133 !important;
  210. }
  211. :deep(.el-menu-item) {
  212. color: #606266;
  213. background-color: #ffffff;
  214. }
  215. :deep(.el-menu-item:hover) {
  216. background-color: #f5f7fa !important;
  217. color: #303133 !important;
  218. }
  219. :deep(.el-menu-item.is-active) {
  220. background-color: #ecf5ff !important;
  221. color: #409eff !important;
  222. }
  223. .sidebar-container.collapsed :deep(.el-sub-menu__title span) {
  224. display: none;
  225. }
  226. .sidebar-container.collapsed :deep(.el-menu-item span) {
  227. display: none;
  228. }
  229. .collapse-btn {
  230. height: 44px;
  231. line-height: 44px;
  232. text-align: center;
  233. color: #606266;
  234. cursor: pointer;
  235. border-top: 1px solid #e4e7ed;
  236. background-color: #ffffff;
  237. }
  238. .collapse-btn:hover {
  239. background-color: #f5f7fa;
  240. color: #303133;
  241. }
  242. .btn-text {
  243. font-size: 16px;
  244. }
  245. </style>