Blame view

src/layouts/default/header/components/Breadcrumb.vue 5.78 KB
vben authored
1
<template>
vben authored
2
  <div :class="[prefixCls, `${prefixCls}--${theme}`]">
vben authored
3
    <a-breadcrumb :routes="routes">
4
      <template #itemRender="{ route, routes: routesMatched, paths }">
5
        <Icon :icon="getIcon(route)" v-if="getShowBreadCrumbIcon && getIcon(route)" />
6
        <span v-if="!hasRedirect(routesMatched, route)">
7
          {{ t(route.name || route.meta.title) }}
vben authored
8
        </span>
9
        <router-link v-else to="" @click="handleClick(route, paths, $event)">
10
          {{ t(route.name || route.meta.title) }}
vben authored
11
12
13
14
15
16
        </router-link>
      </template>
    </a-breadcrumb>
  </div>
</template>
<script lang="ts">
vben authored
17
  import type { RouteLocationMatched } from 'vue-router';
18
  import { useRouter } from 'vue-router';
19
  import type { Menu } from '/@/router/types';
vben authored
20
21
  import { defineComponent, ref, watchEffect } from 'vue';
vben authored
22
23
  import { Breadcrumb } from 'ant-design-vue';
vben authored
24
  import Icon from '@/components/Icon/Icon.vue';
vben authored
25
vben authored
26
27
  import { useDesign } from '/@/hooks/web/useDesign';
  import { useRootSetting } from '/@/hooks/setting/useRootSetting';
28
29
  import { useGo } from '/@/hooks/web/usePage';
  import { useI18n } from '/@/hooks/web/useI18n';
vben authored
30
31

  import { propTypes } from '/@/utils/propTypes';
32
  import { isString } from '/@/utils/is';
33
  import { filter } from '/@/utils/helper/treeHelper';
34
  import { getMenus } from '/@/router/menus';
vben authored
35
36
37
38
  import { REDIRECT_NAME } from '/@/router/constant';
  import { getAllParentPath } from '/@/router/helper/menuHelper';
vben authored
39
40
  export default defineComponent({
    name: 'LayoutBreadcrumb',
41
    components: { Icon, [Breadcrumb.name]: Breadcrumb },
vben authored
42
    props: {
vben authored
43
      theme: propTypes.oneOf(['dark', 'light']),
vben authored
44
45
46
47
    },
    setup() {
      const routes = ref<RouteLocationMatched[]>([]);
      const { currentRoute } = useRouter();
vben authored
48
49
      const { prefixCls } = useDesign('layout-breadcrumb');
      const { getShowBreadCrumbIcon } = useRootSetting();
50
      const go = useGo();
vben authored
51
52

      const { t } = useI18n();
53
      watchEffect(async () => {
54
        if (currentRoute.value.name === REDIRECT_NAME) return;
55
        const menus = await getMenus();
56
57
58
59
60
61
62
63
        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;
        }
64
65
66
67

        const parent = getAllParentPath(menus, path);
        const filterMenus = menus.filter((item) => item.path === parent[0]);
        const matched = getMatched(filterMenus, parent) as any;
68
vben authored
69
70
        if (!matched || matched.length === 0) return;
71
        const breadcrumbList = filterItem(matched);
vben authored
72
73
        if (currentRoute.value.meta?.currentActiveMenu) {
Vben authored
74
          breadcrumbList.push({
75
76
            ...currentRoute.value,
            name: currentRoute.value.meta?.title || currentRoute.value.name,
Vben authored
77
          } as unknown as RouteLocationMatched);
78
        }
79
        routes.value = breadcrumbList;
vben authored
80
81
      });
82
83
84
85
      function getMatched(menus: Menu[], parent: string[]) {
        const metched: Menu[] = [];
        menus.forEach((item) => {
          if (parent.includes(item.path)) {
86
87
88
89
            metched.push({
              ...item,
              name: item.meta?.title || item.name,
            });
90
91
92
93
94
95
96
97
          }
          if (item.children?.length) {
            metched.push(...getMatched(item.children, parent));
          }
        });
        return metched;
      }
98
      function filterItem(list: RouteLocationMatched[]) {
99
        return filter(list, (item) => {
100
          const { meta, name } = item;
101
          if (!meta) {
102
            return !!name;
103
          }
104
105
          const { title, hideBreadcrumb, hideMenu } = meta;
          if (!title || hideBreadcrumb || hideMenu) {
106
107
108
            return false;
          }
          return true;
109
        }).filter((item) => !item.meta?.hideBreadcrumb);
110
111
112
113
      }

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

        if (children?.length && !redirect) {
117
118
119
120
121
122
123
124
125
126
          e?.stopPropagation();
          return;
        }
        if (meta?.carryParam) {
          return;
        }

        if (redirect && isString(redirect)) {
          go(redirect);
        } else {
127
128
129
130
131
132
          let goPath = '';
          if (paths.length === 1) {
            goPath = paths[0];
          } else {
            const ps = paths.slice(1);
            const lastPath = ps.pop() || '';
133
            goPath = `${lastPath}`;
134
135
136
          }
          goPath = /^\//.test(goPath) ? goPath : `/${goPath}`;
          go(goPath);
137
138
139
140
        }
      }

      function hasRedirect(routes: RouteLocationMatched[], route: RouteLocationMatched) {
141
        return routes.indexOf(route) !== routes.length - 1;
142
143
      }
144
145
146
147
148
      function getIcon(route) {
        return route.icon || route.meta?.icon;
      }

      return { routes, t, prefixCls, getIcon, getShowBreadCrumbIcon, handleClick, hasRedirect };
vben authored
149
150
151
    },
  });
</script>
vben authored
152
153
154
155
156
157
<style lang="less">
  @prefix-cls: ~'@{namespace}-layout-breadcrumb';

  .@{prefix-cls} {
    display: flex;
    align-items: center;
vben authored
158
    padding: 0 8px;
vben authored
159
160
161
162
163
164
165
166
167
168
169
170
171

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

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

        a {
vben authored
172
          color: rgb(0 0 0 / 65%);
vben authored
173
174
175
176
177
178
179
180
181
182
183
184
185
186

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

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

    &--dark {
      .ant-breadcrumb-link {
vben authored
187
        color: rgb(255 255 255 / 60%);
vben authored
188
189

        a {
vben authored
190
          color: rgb(255 255 255 / 80%);
vben authored
191
192
193
194
195
196
197
198
199

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

      .ant-breadcrumb-separator,
      .anticon {
vben authored
200
        color: rgb(255 255 255 / 80%);
vben authored
201
202
203
204
      }
    }
  }
</style>