Breadcrumb.vue 6.13 KB
<template>
  <div :class="[prefixCls, `${prefixCls}--${theme}`]">
    <a-breadcrumb :routes="routes">
      <template #itemRender="{ route, routes, paths }">
        <Icon :icon="route.meta.icon" v-if="getShowBreadCrumbIcon && route.meta.icon" />
        <span v-if="!hasRedirect(routes, route)">
          {{ t(route.meta.title) }}
        </span>
        <router-link v-else to="" @click="handleClick(route, paths, $event)">
          {{ t(route.meta.title) }}
        </router-link>
      </template>
    </a-breadcrumb>
  </div>
</template>
<script lang="ts">
  import type { RouteLocationMatched } from 'vue-router';

  import { defineComponent, ref, toRaw, watchEffect } from 'vue';
  import { Breadcrumb } from 'ant-design-vue';

  import { useRouter } from 'vue-router';
  import { filter } from '/@/utils/helper/treeHelper';
  import { REDIRECT_NAME } from '/@/router/constant';
  import Icon from '/@/components/Icon';

  import { PageEnum } from '/@/enums/pageEnum';

  import { useDesign } from '/@/hooks/web/useDesign';
  import { useRootSetting } from '/@/hooks/setting/useRootSetting';

  import { propTypes } from '/@/utils/propTypes';
  import { useGo } from '/@/hooks/web/usePage';
  import { isString } from '/@/utils/is';
  import { useI18n } from '/@/hooks/web/useI18n';
  import { getMenus } from '/@/router/menus';

  export default defineComponent({
    name: 'LayoutBreadcrumb',
    components: { Icon, [Breadcrumb.name]: Breadcrumb },
    props: {
      theme: propTypes.oneOf(['dark', 'light']),
    },
    setup() {
      const routes = ref<RouteLocationMatched[]>([]);
      const { currentRoute } = useRouter();
      const { prefixCls } = useDesign('layout-breadcrumb');
      const { getShowBreadCrumbIcon } = useRootSetting();

      const { t } = useI18n();
      watchEffect(async () => {
        if (currentRoute.value.name === REDIRECT_NAME) return;

        const matched = currentRoute.value?.matched;
        if (!matched || matched.length === 0) return;

        let breadcrumbList = filterItem(toRaw(matched));

        const filterBreadcrumbList = breadcrumbList.filter(
          (item) => item.path !== PageEnum.BASE_HOME
        );

        if (filterBreadcrumbList.length === breadcrumbList.length) {
          filterBreadcrumbList.unshift(({
            path: PageEnum.BASE_HOME,
            meta: {
              title: t('layout.header.home'),
              isLink: true,
            },
          } as unknown) as RouteLocationMatched);
        }

        if (currentRoute.value.meta?.currentActiveMenu) {
          filterBreadcrumbList.push((currentRoute.value as unknown) as RouteLocationMatched);
        }
        routes.value = subRouteExtraction(filterBreadcrumbList);
      });

      function subRouteExtraction(routeList: RouteLocationMatched[]) {
        const resultRoutes: RouteLocationMatched[] = [];
        routeList.forEach((route) => {
          if (route.children?.length === 1) {
            const subRoute = route.children[0] as RouteLocationMatched;
            const subRouteName = subRoute.name as string;
            const routeName = route.name;
            if (subRouteName && `${subRouteName}Parent` === routeName) {
              route = subRoute;
            }
          }
          resultRoutes.push(route);
        });
        return resultRoutes;
      }

      function filterItem(list: RouteLocationMatched[]) {
        let resultList = filter(list, (item) => {
          const { meta } = item;

          if (!meta) {
            return false;
          }

          const { title, hideBreadcrumb, hideMenu } = meta;
          if (!title || hideBreadcrumb || hideMenu) {
            return false;
          }
          return true;
        }).filter((item) => !item.meta?.hideBreadcrumb || !item.meta?.hideMenu);

        return resultList;
      }

      function handleClick(route: RouteLocationMatched, paths: string[], e: Event) {
        e?.preventDefault();
        const {
          children,
          redirect,
          meta,

          // components
        } = route;

        // const isParent =
        //   components?.default?.name === 'DefaultLayout' || (components?.default as any)?.parentView;

        if (
          children?.length &&
          !redirect
          // && !isParent
        ) {
          e?.stopPropagation();
          return;
        }
        if (meta?.carryParam) {
          return;
        }

        const go = useGo();
        if (redirect && isString(redirect)) {
          go(redirect);
        } else {
          let goPath = '';
          if (paths.length === 1) {
            goPath = paths[0];
          } else {
            const ps = paths.slice(1);
            const lastPath = ps.pop() || '';
            const parentPath = ps.pop() || '';
            goPath = `${parentPath}/${lastPath}`;
          }
          goPath = /^\//.test(goPath) ? goPath : `/${goPath}`;
          go(goPath);
        }
      }

      function hasRedirect(routes: RouteLocationMatched[], route: RouteLocationMatched) {
        if (routes.indexOf(route) === routes.length - 1) {
          return false;
        }

        // if (route?.meta?.isLink) {
        //   return true;
        // }

        return true;
      }

      return { routes, t, prefixCls, getShowBreadCrumbIcon, handleClick, hasRedirect };
    },
  });
</script>
<style lang="less">
  @prefix-cls: ~'@{namespace}-layout-breadcrumb';

  .@{prefix-cls} {
    display: flex;
    padding: 0 8px;
    align-items: center;

    .ant-breadcrumb-link {
      .anticon {
        margin-right: 4px;
        margin-bottom: 2px;
      }
    }

    &--light {
      .ant-breadcrumb-link {
        color: @breadcrumb-item-normal-color;

        a {
          color: rgba(0, 0, 0, 0.65);

          &:hover {
            color: @primary-color;
          }
        }
      }

      .ant-breadcrumb-separator {
        color: @breadcrumb-item-normal-color;
      }
    }

    &--dark {
      .ant-breadcrumb-link {
        color: rgba(255, 255, 255, 0.6);

        a {
          color: rgba(255, 255, 255, 0.8);

          &:hover {
            color: @white;
          }
        }
      }

      .ant-breadcrumb-separator,
      .anticon {
        color: rgba(255, 255, 255, 0.8);
      }
    }
  }
</style>