Menu.vue 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. <script setup lang="ts">
  2. import type { BaseMenu } from '@/types/Promotion/Menu'
  3. import { computed, nextTick, ref, watch } from 'vue'
  4. import { useRoute } from 'vue-router'
  5. import { useAttrs } from 'vue'
  6. import router from '@/router'
  7. import CIcon from '../cIcon/CIcon.vue'
  8. interface MenuProps {
  9. menuStyle?: string
  10. menuItemStyle?: string
  11. defaultActive?: string
  12. menuList: BaseMenu[]
  13. }
  14. const props = withDefaults(defineProps<MenuProps>(), {})
  15. const routes = useRoute()
  16. const attrs = useAttrs()
  17. const emits = defineEmits(['activeChange'])
  18. // 纵向菜单折叠,仅在mode为vertical时生效
  19. const isCollapse = ref(false)
  20. // 高亮菜单的name
  21. const highlightMenu = ref()
  22. /**
  23. * @description: 菜单项改变
  24. * @param {*} newVal
  25. * @return {*}
  26. */
  27. const changeSelect = (newVal: string) => {
  28. emits('activeChange', newVal)
  29. }
  30. /**
  31. * @description: 改变箭头颜色
  32. * @return {*}
  33. */
  34. const changeArrow = () => {
  35. nextTick(() => {
  36. // 只有被加上这两个特殊类的箭头才去处理
  37. let activeItem = document.querySelector(
  38. '.activeItem .el-sub-menu__icon-arrow',
  39. ) as HTMLElement
  40. let noActiveItem = document.querySelector(
  41. '.noActiveItem .el-sub-menu__icon-arrow',
  42. ) as HTMLElement
  43. if (activeItem) {
  44. activeItem.style.color = '#409eff'
  45. }
  46. if (noActiveItem) {
  47. noActiveItem.style.color = 'unset'
  48. }
  49. })
  50. }
  51. /**
  52. * @description: 根据当前路由,高亮父菜单
  53. * @return {*}
  54. */
  55. watch(
  56. () => routes.path,
  57. (newval: string) => {
  58. props.menuList.forEach(item => {
  59. let result = item.children?.find(child => child.path === newval)
  60. // 有可能该选项卡没有子菜单,但他本身也可以高亮
  61. if (result || item.path === newval) highlightMenu.value = item.name
  62. })
  63. // 去调整箭头颜色
  64. changeArrow()
  65. },
  66. { deep: true, immediate: true },
  67. )
  68. /**
  69. * @description: 在使用router模式下的默认选中
  70. * @return {*}
  71. */
  72. const routerDefaultActive = computed(() => {
  73. if (attrs.router) {
  74. let menuItem = props.menuList.find(item => {
  75. let basePath = item.path // 你可以动态设置这个值
  76. if (basePath) {
  77. const escapedBasePath = basePath.replace(/\//g, '\\/') // 转义斜杠
  78. const regex = new RegExp(`^${escapedBasePath}(?:\/.*)?$`)
  79. if (regex.test(routes.path)) return true
  80. }
  81. return false
  82. })
  83. return menuItem?.path
  84. } else {
  85. return ''
  86. }
  87. })
  88. </script>
  89. <template>
  90. <div
  91. class="menuContainer"
  92. :class="[
  93. $attrs.mode === 'vertical' ? 'verticalContainer' : 'horizontalContainer',
  94. $attrs.mode === 'vertical' ? 'borderR' : '',
  95. ]"
  96. >
  97. <el-menu
  98. v-bind="{ ...$attrs }"
  99. class="menu"
  100. :collapse="isCollapse"
  101. :style="menuStyle"
  102. :ellipsis="false"
  103. :default-active="
  104. props.defaultActive ? props.defaultActive : routerDefaultActive
  105. "
  106. @select="changeSelect"
  107. >
  108. <template v-for="menuItem in menuList" :key="menuItem.name">
  109. <!-- 有子菜单 -->
  110. <el-sub-menu
  111. v-if="menuItem.children"
  112. :class="
  113. highlightMenu === menuItem.name ? 'activeItem' : 'noActiveItem'
  114. "
  115. :index="menuItem.name"
  116. >
  117. <template #title>
  118. <CIcon
  119. v-if="menuItem.iconDefault && highlightMenu !== menuItem.name"
  120. :src="menuItem.iconDefault"
  121. class="menuIcon"
  122. ></CIcon>
  123. <CIcon
  124. v-if="menuItem.iconActive && highlightMenu === menuItem.name"
  125. class="menuIcon"
  126. :src="menuItem.iconActive"
  127. ></CIcon>
  128. <span
  129. class="menuTitle"
  130. :class="{ menuActiveColor: highlightMenu === menuItem.name }"
  131. >{{ menuItem.title }}</span
  132. >
  133. </template>
  134. <el-menu-item v-for="item in menuItem.children" :index="item.path">
  135. <CIcon
  136. class="menuChildIcon"
  137. v-if="item.iconDefault"
  138. :src="item.iconDefault"
  139. ></CIcon>
  140. <span>{{ item.title }}</span>
  141. </el-menu-item>
  142. </el-sub-menu>
  143. <!-- 无子菜单 -->
  144. <el-menu-item
  145. :style="menuItemStyle"
  146. :index="$attrs.router ? menuItem.path : menuItem.name"
  147. v-else
  148. >
  149. <CIcon
  150. class="menuIcon"
  151. v-if="menuItem.iconDefault"
  152. :src="menuItem.iconDefault"
  153. ></CIcon>
  154. <span>{{ menuItem.title }}</span>
  155. </el-menu-item>
  156. </template>
  157. </el-menu>
  158. <div
  159. v-if="$attrs.mode === 'vertical'"
  160. class="sideBarFold"
  161. @click="isCollapse = !isCollapse"
  162. :style="{ textAlign: isCollapse ? 'center' : 'right' }"
  163. >
  164. <el-icon class="icon" :size="20"><Fold /></el-icon>
  165. </div>
  166. </div>
  167. </template>
  168. <style scoped>
  169. .menuContainer {
  170. position: relative;
  171. height: 100%;
  172. }
  173. .borderR {
  174. border-right: 1px solid #e8eaec;
  175. }
  176. /* 垂直样式 */
  177. .verticalContainer {
  178. .menu {
  179. position: relative;
  180. height: calc(100% - 40px);
  181. /* min-width: 180px; */
  182. border-right: none;
  183. }
  184. .sideBarFold {
  185. position: relative;
  186. bottom: 0px;
  187. /* width: 100%; */
  188. height: 40px;
  189. line-height: 40px;
  190. margin-right: 6px;
  191. cursor: pointer;
  192. background-color: white;
  193. /* text-align: right; */
  194. }
  195. .menuIcon {
  196. margin-right: 5px;
  197. }
  198. .menuChildIcon {
  199. margin-right: 2px;
  200. }
  201. .menuActiveColor {
  202. color: #409eff;
  203. }
  204. .menuTitle {
  205. font-weight: 700;
  206. }
  207. }
  208. /* 水平样式 */
  209. .horizontalContainer {
  210. .menu {
  211. /* min-width: 0; */
  212. padding: 0 20px;
  213. }
  214. .sideBarFold {
  215. position: absolute;
  216. right: 20px;
  217. bottom: 20px;
  218. cursor: pointer;
  219. }
  220. }
  221. </style>