Breadcrumb.vue 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. <template>
  2. <div :class="[prefixCls, `${prefixCls}--${theme}`]">
  3. <a-breadcrumb :routes="routes">
  4. <template #itemRender="{ route, routes, paths }">
  5. <Icon :icon="getIcon(route)" v-if="getShowBreadCrumbIcon && getIcon(route)" />
  6. <span v-if="!hasRedirect(routes, route)">
  7. {{ t(route.name || route.meta.title) }}
  8. </span>
  9. <router-link v-else to="" @click="handleClick(route, paths, $event)">
  10. {{ t(route.name || route.meta.title) }}
  11. </router-link>
  12. </template>
  13. </a-breadcrumb>
  14. </div>
  15. </template>
  16. <script lang="ts">
  17. import type { RouteLocationMatched } from 'vue-router'
  18. import type { Menu } from '/@/router/types'
  19. import { defineComponent, ref, watchEffect } from 'vue'
  20. import { Breadcrumb } from 'ant-design-vue'
  21. import Icon from '/@/components/Icon'
  22. import { useDesign } from '/@/hooks/web/useDesign'
  23. import { useRootSetting } from '/@/hooks/setting/useRootSetting'
  24. import { useGo } from '/@/hooks/web/usePage'
  25. import { useI18n } from '/@/hooks/web/useI18n'
  26. import { useRouter } from 'vue-router'
  27. import { propTypes } from '/@/utils/propTypes'
  28. import { isString } from '/@/utils/is'
  29. import { filter } from '/@/utils/helper/treeHelper'
  30. import { getMenus } from '/@/router/menus'
  31. import { REDIRECT_NAME } from '/@/router/constant'
  32. import { getAllParentPath } from '/@/router/helper/menuHelper'
  33. export default defineComponent({
  34. name: 'LayoutBreadcrumb',
  35. components: { Icon, [Breadcrumb.name]: Breadcrumb },
  36. props: {
  37. theme: propTypes.oneOf(['dark', 'light'])
  38. },
  39. setup() {
  40. const routes = ref<RouteLocationMatched[]>([])
  41. const { currentRoute } = useRouter()
  42. const { prefixCls } = useDesign('layout-breadcrumb')
  43. const { getShowBreadCrumbIcon } = useRootSetting()
  44. const go = useGo()
  45. const { t } = useI18n()
  46. watchEffect(async () => {
  47. if (currentRoute.value.name === REDIRECT_NAME) return
  48. const menus = await getMenus()
  49. const routeMatched = currentRoute.value.matched
  50. const cur = routeMatched?.[routeMatched.length - 1]
  51. let path = currentRoute.value.path
  52. if (cur && cur?.meta?.currentActiveMenu) {
  53. path = cur.meta.currentActiveMenu as string
  54. }
  55. const parent = getAllParentPath(menus, path)
  56. const filterMenus = menus.filter(item => item.path === parent[0])
  57. const matched = getMatched(filterMenus, parent) as any
  58. if (!matched || matched.length === 0) return
  59. const breadcrumbList = filterItem(matched)
  60. if (currentRoute.value.meta?.currentActiveMenu) {
  61. breadcrumbList.push({
  62. ...currentRoute.value,
  63. name: currentRoute.value.meta?.title || currentRoute.value.name
  64. } as unknown as RouteLocationMatched)
  65. }
  66. routes.value = breadcrumbList
  67. })
  68. function getMatched(menus: Menu[], parent: string[]) {
  69. const metched: Menu[] = []
  70. menus.forEach(item => {
  71. if (parent.includes(item.path)) {
  72. metched.push({
  73. ...item,
  74. name: item.meta?.title || item.name
  75. })
  76. }
  77. if (item.children?.length) {
  78. metched.push(...getMatched(item.children, parent))
  79. }
  80. })
  81. return metched
  82. }
  83. function filterItem(list: RouteLocationMatched[]) {
  84. let resultList = filter(list, item => {
  85. const { meta, name } = item
  86. if (!meta) {
  87. return !!name
  88. }
  89. const { title, hideBreadcrumb, hideMenu } = meta
  90. if (!title || hideBreadcrumb || hideMenu) {
  91. return false
  92. }
  93. return true
  94. }).filter(item => !item.meta?.hideBreadcrumb || !item.meta?.hideMenu)
  95. return resultList
  96. }
  97. function handleClick(route: RouteLocationMatched, paths: string[], e: Event) {
  98. e?.preventDefault()
  99. const { children, redirect, meta } = route
  100. if (children?.length && !redirect) {
  101. e?.stopPropagation()
  102. return
  103. }
  104. if (meta?.carryParam) {
  105. return
  106. }
  107. if (redirect && isString(redirect)) {
  108. go(redirect)
  109. } else {
  110. let goPath = ''
  111. if (paths.length === 1) {
  112. goPath = paths[0]
  113. } else {
  114. const ps = paths.slice(1)
  115. const lastPath = ps.pop() || ''
  116. goPath = `${lastPath}`
  117. }
  118. goPath = /^\//.test(goPath) ? goPath : `/${goPath}`
  119. go(goPath)
  120. }
  121. }
  122. function hasRedirect(routes: RouteLocationMatched[], route: RouteLocationMatched) {
  123. if (routes.indexOf(route) === routes.length - 1) {
  124. return false
  125. }
  126. return true
  127. }
  128. function getIcon(route) {
  129. return route.icon || route.meta?.icon
  130. }
  131. return { routes, t, prefixCls, getIcon, getShowBreadCrumbIcon, handleClick, hasRedirect }
  132. }
  133. })
  134. </script>
  135. <style lang="less">
  136. @prefix-cls: ~'@{namespace}-layout-breadcrumb';
  137. .@{prefix-cls} {
  138. display: flex;
  139. padding: 0 8px;
  140. align-items: center;
  141. .ant-breadcrumb-link {
  142. .anticon {
  143. margin-right: 4px;
  144. margin-bottom: 2px;
  145. }
  146. }
  147. &--light {
  148. .ant-breadcrumb-link {
  149. color: @breadcrumb-item-normal-color;
  150. a {
  151. color: rgba(0, 0, 0, 0.65);
  152. &:hover {
  153. color: @primary-color;
  154. }
  155. }
  156. }
  157. .ant-breadcrumb-separator {
  158. color: @breadcrumb-item-normal-color;
  159. }
  160. }
  161. &--dark {
  162. .ant-breadcrumb-link {
  163. color: rgba(255, 255, 255, 0.6);
  164. a {
  165. color: rgba(255, 255, 255, 0.8);
  166. &:hover {
  167. color: @white;
  168. }
  169. }
  170. }
  171. .ant-breadcrumb-separator,
  172. .anticon {
  173. color: rgba(255, 255, 255, 0.8);
  174. }
  175. }
  176. }
  177. </style>