Ben Lin
2024-10-22 969725647117eb7ca148b2e8cfa9ec8b5eb432fa
src/layouts/default/header/components/Breadcrumb.vue
@@ -1,153 +1,167 @@
<template>
  <div :class="[prefixCls, `${prefixCls}--${theme}`]">
    <a-breadcrumb :routes="routes">
      <template #itemRender="{ route, routes: routesMatched, paths }">
        <Icon :icon="getIcon(route)" v-if="getShowBreadCrumbIcon && getIcon(route)" />
        <span v-if="!hasRedirect(routesMatched, route)">
          {{ t(route.name || route.meta.title) }}
        </span>
        <router-link v-else to="" @click="handleClick(route, paths, $event)">
          {{ t(route.name || route.meta.title) }}
        </router-link>
    <Breadcrumb>
      <template v-for="routeItem in routes" :key="routeItem.name">
        <BreadcrumbItem>
          <Icon :icon="getIcon(routeItem)" v-if="getShowBreadCrumbIcon && getIcon(routeItem)" />
          <span v-if="!hasRedirect(routes, routeItem)">
            {{ t((routeItem.meta.title || routeItem.name) as string) }}
          </span>
          <router-link v-else to="" @click="handleClick(routeItem)">
            {{ t((routeItem.meta.title || routeItem.name) as string) }}
          </router-link>
          <template v-if="routeItem.children && !routeItem.meta?.hideChildrenInMenu" #overlay>
            <Menu>
              <template v-for="childItem in routeItem.children" :key="childItem.name">
                <MenuItem>
                  <Icon
                    :icon="getIcon(childItem)"
                    v-if="getShowBreadCrumbIcon && getIcon(childItem)"
                  />
                  <span v-if="!hasRedirect(routes, childItem)">
                    {{ t((childItem.meta?.title || childItem.name) as string) }}
                  </span>
                  <router-link v-else to="" @click="handleClick(childItem)">
                    {{ t((childItem.meta?.title || childItem.name) as string) }}
                  </router-link>
                </MenuItem>
              </template>
            </Menu>
          </template>
        </BreadcrumbItem>
      </template>
    </a-breadcrumb>
    </Breadcrumb>
  </div>
</template>
<script lang="ts">
<script lang="ts" setup>
  import type { RouteLocationMatched } from 'vue-router';
  import { useRouter } from 'vue-router';
  import type { Menu } from '/@/router/types';
  import { defineComponent, ref, watchEffect } from 'vue';
  import { ref, watchEffect } from 'vue';
  import { Breadcrumb } from 'ant-design-vue';
  import { Breadcrumb, BreadcrumbItem, Menu, MenuItem } from 'ant-design-vue';
  import Icon from '@/components/Icon/Icon.vue';
  import { useDesign } from '/@/hooks/web/useDesign';
  import { useRootSetting } from '/@/hooks/setting/useRootSetting';
  import { useGo } from '/@/hooks/web/usePage';
  import { useI18n } from '/@/hooks/web/useI18n';
  import { useDesign } from '@/hooks/web/useDesign';
  import { useRootSetting } from '@/hooks/setting/useRootSetting';
  import { useGo } from '@/hooks/web/usePage';
  import { useI18n } from '@/hooks/web/useI18n';
  import { propTypes } from '/@/utils/propTypes';
  import { isString } from '/@/utils/is';
  import { filter } from '/@/utils/helper/treeHelper';
  import { getMenus } from '/@/router/menus';
  import { propTypes } from '@/utils/propTypes';
  import { isString } from '@/utils/is';
  import { filter } from '@/utils/helper/treeHelper';
  import { getMenus } from '@/router/menus';
  import { REDIRECT_NAME } from '/@/router/constant';
  import { getAllParentPath } from '/@/router/helper/menuHelper';
  import { REDIRECT_NAME } from '@/router/constant';
  import { getAllParentPath } from '@/router/helper/menuHelper';
  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 go = useGo();
  defineOptions({ name: 'LayoutBreadcrumb' });
      const { t } = useI18n();
      watchEffect(async () => {
        if (currentRoute.value.name === REDIRECT_NAME) return;
        const menus = await getMenus();
        const routeMatched = currentRoute.value.matched;
        const cur = routeMatched?.[routeMatched.length - 1];
        let path = currentRoute.value.path;
        if (cur && cur?.meta?.currentActiveMenu) {
          path = cur.meta.currentActiveMenu as string;
        }
        const parent = getAllParentPath(menus, path);
        const filterMenus = menus.filter((item) => item.path === parent[0]);
        const matched = getMatched(filterMenus, parent) as any;
        if (!matched || matched.length === 0) return;
        const breadcrumbList = filterItem(matched);
        if (currentRoute.value.meta?.currentActiveMenu) {
          breadcrumbList.push({
            ...currentRoute.value,
            name: currentRoute.value.meta?.title || currentRoute.value.name,
          } as unknown as RouteLocationMatched);
        }
        routes.value = breadcrumbList;
      });
      function getMatched(menus: Menu[], parent: string[]) {
        const metched: Menu[] = [];
        menus.forEach((item) => {
          if (parent.includes(item.path)) {
            metched.push({
              ...item,
              name: item.meta?.title || item.name,
            });
          }
          if (item.children?.length) {
            metched.push(...getMatched(item.children, parent));
          }
        });
        return metched;
      }
      function filterItem(list: RouteLocationMatched[]) {
        return filter(list, (item) => {
          const { meta, name } = item;
          if (!meta) {
            return !!name;
          }
          const { title, hideBreadcrumb, hideMenu } = meta;
          if (!title || hideBreadcrumb || hideMenu) {
            return false;
          }
          return true;
        }).filter((item) => !item.meta?.hideBreadcrumb);
      }
      function handleClick(route: RouteLocationMatched, paths: string[], e: Event) {
        e?.preventDefault();
        const { children, redirect, meta } = route;
        if (children?.length && !redirect) {
          e?.stopPropagation();
          return;
        }
        if (meta?.carryParam) {
          return;
        }
        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() || '';
            goPath = `${lastPath}`;
          }
          goPath = /^\//.test(goPath) ? goPath : `/${goPath}`;
          go(goPath);
        }
      }
      function hasRedirect(routes: RouteLocationMatched[], route: RouteLocationMatched) {
        return routes.indexOf(route) !== routes.length - 1;
      }
      function getIcon(route) {
        return route.icon || route.meta?.icon;
      }
      return { routes, t, prefixCls, getIcon, getShowBreadCrumbIcon, handleClick, hasRedirect };
    },
  defineProps({
    theme: propTypes.oneOf(['dark', 'light']),
  });
  const routes = ref<RouteLocationMatched[]>([]);
  const { currentRoute } = useRouter();
  const { prefixCls } = useDesign('layout-breadcrumb');
  const { getShowBreadCrumbIcon } = useRootSetting();
  const go = useGo();
  const { t } = useI18n();
  watchEffect(async () => {
    if (currentRoute.value.name === REDIRECT_NAME) return;
    const menus = await getMenus();
    const routeMatched = currentRoute.value.matched;
    const cur = routeMatched?.[routeMatched.length - 1];
    let path = currentRoute.value.path;
    if (cur && cur?.meta?.currentActiveMenu) {
      path = cur.meta.currentActiveMenu as string;
    }
    const parent = getAllParentPath(menus, path);
    const filterMenus = menus.filter((item) => item.path === parent[0]);
    const matched = getMatched(filterMenus, parent) as any;
    if (!matched || matched.length === 0) {
      routes.value = [];
      return;
    }
    const breadcrumbList = filterItem(matched);
    if (currentRoute.value.meta?.currentActiveMenu && !currentRoute.value.meta?.hideBreadcrumb) {
      breadcrumbList.push({
        ...currentRoute.value,
        name: currentRoute.value.meta?.title || currentRoute.value.name,
      } as unknown as RouteLocationMatched);
    }
    routes.value = breadcrumbList;
  });
  function getMatched(menus, parent: string[]) {
    const matched: any[] = [];
    menus.forEach((item) => {
      if (parent.includes(item.path)) {
        matched.push({
          ...item,
          name: item.meta?.title || item.name,
        });
      }
      if (item.children?.length) {
        matched.push(...getMatched(item.children, parent));
      }
    });
    return matched;
  }
  function filterItem(list: RouteLocationMatched[]) {
    return filter(list, (item) => {
      const { meta, name } = item;
      if (!meta) {
        return !!name;
      }
      const { title, hideBreadcrumb, hideMenu } = meta;
      if (!title || hideBreadcrumb || hideMenu) {
        return false;
      }
      return true;
    }).filter((item) => !item.meta?.hideBreadcrumb);
  }
  function handleClick(route) {
    const { children, redirect, meta } = route;
    if (children?.length && !redirect) {
      return;
    }
    if (meta?.carryParam) {
      return;
    }
    if (redirect && isString(redirect)) {
      go(redirect);
    } else {
      let goPath = '';
      if (route.path) {
        goPath = route.path;
      } else {
        const lastPath = '';
        goPath = `${lastPath}`;
      }
      goPath = /^\//.test(goPath) ? goPath : `/${goPath}`;
      go(goPath);
    }
  }
  function hasRedirect(routes, route) {
    return routes.indexOf(route) !== routes.length - 1;
  }
  function getIcon(route) {
    return route.icon || route.meta?.icon;
  }
</script>
<style lang="less">
  @prefix-cls: ~'@{namespace}-layout-breadcrumb';