feat(projects): 面包屑

This commit is contained in:
Soybean 2022-01-11 19:03:42 +08:00
parent e25afe2fad
commit 09c7658c21
8 changed files with 134 additions and 10 deletions

View File

@ -1,7 +1,18 @@
import type { MenuOption } from 'naive-ui'; import type { MenuOption, DropdownOption } from 'naive-ui';
/** 菜单项配置 */ /** 菜单项配置 */
export type GlobalMenuOption = MenuOption & { export type GlobalMenuOption = MenuOption & {
routeName: string; routeName: string;
routePath: string; routePath: string;
}; };
/** 面包屑 */
export type GlobalBreadcrumb = DropdownOption & {
key: string;
label: string;
disabled: boolean;
routeName: string;
hasChildren: boolean;
iconName?: string;
children?: GlobalBreadcrumb[];
};

View File

@ -0,0 +1,43 @@
<template>
<n-breadcrumb class="px-12px">
<template v-for="breadcrumb in breadcrumbs" :key="breadcrumb.key">
<n-breadcrumb-item>
<n-dropdown v-if="breadcrumb.hasChildren" :options="breadcrumb.children" @select="dropdownSelect">
<span>
<component :is="breadcrumb.icon" v-if="theme.header.crumb.showIcon" class="inline-block mr-4px text-16px" />
<span>{{ breadcrumb.label }}</span>
</span>
</n-dropdown>
<template v-else>
<component :is="breadcrumb.icon" v-if="theme.header.crumb.showIcon" class="inline-block mr-4px text-16px" />
<span>{{ breadcrumb.label }}</span>
</template>
</n-breadcrumb-item>
</template>
</n-breadcrumb>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import { NBreadcrumb, NBreadcrumbItem, NDropdown } from 'naive-ui';
import { routePath } from '@/router';
import { useThemeStore, useRouteStore } from '@/store';
import { useRouterPush } from '@/composables';
import { getBreadcrumbByRouteKey } from '@/utils';
import type { GlobalMenuOption } from '@/interface';
const route = useRoute();
const theme = useThemeStore();
const routeStore = useRouteStore();
const { routerPush } = useRouterPush();
const breadcrumbs = computed(() =>
getBreadcrumbByRouteKey(route.name as string, routeStore.menus as GlobalMenuOption[], routePath('root'))
);
function dropdownSelect(key: string) {
routerPush({ name: key });
}
</script>
<style scoped></style>

View File

@ -0,0 +1,26 @@
<template>
<n-menu :value="activeKey" mode="horizontal" :options="menus" @update:value="handleUpdateMenu" />
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import { NMenu } from 'naive-ui';
import type { MenuOption } from 'naive-ui';
import { useRouteStore } from '@/store';
import { useRouterPush } from '@/composables';
import type { GlobalMenuOption } from '@/interface';
const route = useRoute();
const routeStore = useRouteStore();
const { routerPush } = useRouterPush();
const menus = computed(() => routeStore.menus as GlobalMenuOption[]);
const activeKey = computed(() => route.name as string);
function handleUpdateMenu(_key: string, item: MenuOption) {
const menuItem = item as GlobalMenuOption;
routerPush(menuItem.routePath);
}
</script>
<style scoped></style>

View File

@ -1,7 +1,9 @@
import MenuCollapse from './MenuCollapse.vue'; import MenuCollapse from './MenuCollapse.vue';
import GlobalBreadcrumb from './GlobalBreadcrumb.vue';
import HeaderMenu from './HeaderMenu.vue';
import GithubSite from './GithubSite.vue'; import GithubSite from './GithubSite.vue';
import FullScreen from './FullScreen.vue'; import FullScreen from './FullScreen.vue';
import ThemeMode from './ThemeMode.vue'; import ThemeMode from './ThemeMode.vue';
import UserAvatar from './UserAvatar.vue'; import UserAvatar from './UserAvatar.vue';
export { MenuCollapse, GithubSite, FullScreen, ThemeMode, UserAvatar }; export { MenuCollapse, GlobalBreadcrumb, HeaderMenu, GithubSite, FullScreen, ThemeMode, UserAvatar };

View File

@ -3,13 +3,11 @@
<global-logo v-if="showLogo" :show-title="true" class="h-full" :style="{ width: theme.sider.width + 'px' }" /> <global-logo v-if="showLogo" :show-title="true" class="h-full" :style="{ width: theme.sider.width + 'px' }" />
<div v-if="!showHeaderMenu" class="flex-1-hidden flex-y-center h-full"> <div v-if="!showHeaderMenu" class="flex-1-hidden flex-y-center h-full">
<menu-collapse v-if="showMenuCollape" /> <menu-collapse v-if="showMenuCollape" />
<!-- <global-breadcrumb v-if="theme.header.crumb.visible" /> --> <global-breadcrumb v-if="theme.header.crumb.visible" />
</div>
<div v-else class="flex-1-hidden flex-y-center h-full" :style="{ justifyContent: theme.menu.horizontalPosition }">
<header-menu />
</div> </div>
<div
v-else
class="flex-1-hidden flex-y-center h-full"
:style="{ justifyContent: theme.menu.horizontalPosition }"
></div>
<div class="flex justify-end h-full"> <div class="flex justify-end h-full">
<github-site /> <github-site />
<full-screen /> <full-screen />
@ -24,7 +22,15 @@ import { DarkModeContainer } from '@/components';
import { useThemeStore } from '@/store'; import { useThemeStore } from '@/store';
import type { GlobalHeaderProps } from '@/interface'; import type { GlobalHeaderProps } from '@/interface';
import GlobalLogo from '../GlobalLogo/index.vue'; import GlobalLogo from '../GlobalLogo/index.vue';
import { MenuCollapse, GithubSite, FullScreen, ThemeMode, UserAvatar } from './components'; import {
MenuCollapse,
GlobalBreadcrumb,
HeaderMenu,
GithubSite,
FullScreen,
ThemeMode,
UserAvatar
} from './components';
interface Props { interface Props {
/** 显示logo */ /** 显示logo */

View File

@ -54,7 +54,7 @@ export const constantRoutes: AuthRoute.Route[] = [
singleLayout: 'blank' singleLayout: 'blank'
} }
}, },
// 匹配无效的路径重定向not-found的页面 // 匹配无效路径的路由
{ {
name: 'not-found-page', name: 'not-found-page',
path: '/:pathMatch(.*)*', path: '/:pathMatch(.*)*',

View File

@ -0,0 +1,35 @@
import type { GlobalMenuOption, GlobalBreadcrumb } from '@/interface';
/**
*
* @param activeKey - key
* @param menus -
* @param rootPath -
*/
export function getBreadcrumbByRouteKey(activeKey: string, menus: GlobalMenuOption[], rootPath: string) {
return menus.map(menu => getBreadcrumbItem(activeKey, menu, rootPath)).flat(1);
}
function getBreadcrumbItem(activeKey: string, menu: GlobalMenuOption, rootPath: string) {
const list: GlobalBreadcrumb[] = [];
if (activeKey.includes(menu.routeName)) {
const breadcrumb: GlobalBreadcrumb = {
key: menu.routeName,
label: menu.label as string,
routeName: menu.routeName,
disabled: menu.routePath === rootPath,
hasChildren: false
};
if (menu.icon) {
breadcrumb.icon = menu.icon;
}
if (menu.children && menu.children.length) {
breadcrumb.hasChildren = true;
breadcrumb.children = menu.children
.map(item => getBreadcrumbItem(activeKey, item as GlobalMenuOption, rootPath))
.flat(1);
}
list.push(breadcrumb);
}
return list;
}

View File

@ -1,3 +1,4 @@
export * from './helpers'; export * from './helpers';
export * from './menu'; export * from './menu';
export * from './breadcrumb';
export * from './regexp'; export * from './regexp';