feat(components): 添加面包屑

This commit is contained in:
Soybean 2021-09-16 08:25:07 +08:00
parent 667282f81a
commit c1cdc3a9ed
12 changed files with 195 additions and 23 deletions

View File

@ -2,7 +2,7 @@ module.exports = {
// type 类型(定义之后,可通过上下键选择)
types: [
{ value: 'feat', name: 'feat: 新增功能' },
{ value: 'fix', name: 'fix: 修复 bug' },
{ value: 'fix', name: 'fix: 修复bug' },
{ value: 'docs', name: 'docs: 文档变更' },
{ value: 'style', name: 'style: 代码格式(不影响功能,例如空格、分号等格式修正)' },
{ value: 'refactor', name: 'refactor: 代码重构(不包括 bug 修复、功能新增)' },
@ -20,7 +20,7 @@ module.exports = {
['components', '组件相关'],
['hooks', 'hook 相关'],
['utils', 'utils 相关'],
['element-ui', '对 element-ui 的调整'],
['types', 'ts类型相关'],
['styles', '样式相关'],
['deps', '项目依赖'],
['auth', '对 auth 修改'],

View File

@ -72,6 +72,7 @@ soybean-admin
│ │ ├── dark-mode.ts //windicss暗黑模式插件
│ │ └── smooth-scroll.ts //滚动平滑插件
│ ├── router //vue路由
│ │ ├── menus.ts //菜单
│ │ ├── permission.ts //路由守卫相关函数
│ │ └── routes.ts //声明的路由
│ ├── service //网络请求

View File

@ -6,7 +6,7 @@ export default [
injectHtml({
injectData: {
title: viteEnv.VITE_APP_TITLE,
appName: viteEnv.VITE_APP_TITLE_Label
appName: viteEnv.VITE_APP_TITLE_LABEL
}
})
];

View File

@ -8,7 +8,25 @@
</head>
<body>
<div id="appProvider" style="display: none"></div>
<div id="app"></div>
<div id="app">
<!-- 页面渲染之前加载动画 -->
<div class="app-loading">
<img class="app-loading_logo" src="/resource/logo.png" />
<div class="app-loading__dot-wrapper">
<div class="app-loading__dot">
<i class="left top"></i>
<i class="left bottom delay-400"></i>
<i class="right top delay-800"></i>
<i class="right bottom delay-1200"></i>
</div>
</div>
<h2 class="app-loading_title"><%= appName %></h2>
<style>
@import '/resource/loading.css';
</style>
</div>
<!-- End -->
</div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@ -0,0 +1,90 @@
.app-loading {
position: fixed;
left: 0;
top: 0;
z-index: -1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
}
.app-loading_logo {
width: 128px;
height: 128px;
}
.app-loading__dot-wrapper {
width: 56px;
height: 56px;
margin: 36px 0;
}
.app-loading__dot {
position: relative;
height: 100%;
transform: rotate(45deg);
animation-name: loadingRotate;
animation-duration: 1.2s;
animation-iteration-count: infinite;
animation-timing-function: linear;
}
@keyframes loadingRotate {
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
}
@-webkit-keyframes loadingRotate {
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
}
.app-loading__dot > i {
position: absolute;
display: block;
width: 18px;
height: 18px;
background: #1890ff;
border-radius: 50%;
-webkit-transform: scale(0.75);
transform: scale(0.75);
transform-origin: 50% 50%;
opacity: 0.3;
animation: spinOpacity 1s infinite linear alternate;
}
@keyframes spinOpacity {
to {
opacity: 1;
}
}
@-webkit-keyframes spinOpacity {
to {
opacity: 1;
}
}
.delay-400 {
animation-delay: 0.4s !important;
}
.delay-800 {
animation-delay: 0.8s !important;
}
.delay-1200 {
animation-delay: 1.2s !important;
}
.left {
left: 0;
}
.right {
right: 0;
}
.top {
top: 0;
}
.bottom {
bottom: 0;
}
.app-loading_title {
font-size: 28px;
color: #646464;
}

BIN
public/resource/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -9,8 +9,8 @@ interface RouteMeta {
title?: string;
/** 页面100%视高 */
fullPage?: boolean;
/** 作为菜单 */
asMenu?: boolean;
/** 作为菜单 */
isNotMenu?: boolean;
/** 菜单和面包屑对应的图标 */
icon?: Component;
}

View File

@ -0,0 +1,66 @@
<template>
<n-breadcrumb class="px-12px">
<template v-for="breadcrumb in breadcrumbList" :key="breadcrumb.key">
<n-breadcrumb-item>
<n-dropdown v-if="breadcrumb.hasChildren" :options="breadcrumb.children" @select="dropdownSelect">
<span>{{ breadcrumb.label }}</span>
</n-dropdown>
<span v-else>{{ breadcrumb.label }}</span>
</n-breadcrumb-item>
</template>
</n-breadcrumb>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { NBreadcrumb, NBreadcrumbItem, NDropdown } from 'naive-ui';
import type { DropdownOption } from 'naive-ui';
import type { RouteLocationMatched } from 'vue-router';
import { EnumRoutePath } from '@/enum';
import type { RoutePathKey } from '@/interface';
type Breadcrumb = DropdownOption & {
key: string;
label: string;
disabled: boolean;
routeName: RoutePathKey;
hasChildren: boolean;
children?: Breadcrumb[];
};
const route = useRoute();
const router = useRouter();
const breadcrumbList = computed<Breadcrumb[]>(() => generateBreadcrumb());
function generateBreadcrumb() {
const { matched } = route;
return recursionBreadcrumb(matched);
}
/** 递归匹配路由获取面包屑数据 */
function recursionBreadcrumb(routeMatched: RouteLocationMatched[]) {
return routeMatched.map(item => {
const routeName = item.name as RoutePathKey;
const breadcrumItem: Breadcrumb = {
key: routeName,
label: (item.meta?.title as string) || '',
disabled: item.path === EnumRoutePath.root,
routeName,
hasChildren: false
};
if (item.children && item.children.length) {
breadcrumItem.hasChildren = true;
breadcrumItem.children = recursionBreadcrumb(item.children as RouteLocationMatched[]);
}
return breadcrumItem;
});
}
function dropdownSelect(optionKey: string) {
const key = optionKey as RoutePathKey;
router.push({ name: key });
}
</script>
<style scoped></style>

View File

@ -1,3 +1,4 @@
import GlobalBreadcrumb from './GlobalBreadcrumb.vue';
import UserAvatar from './UserAvatar.vue';
import MenuCollapse from './MenuCollapse.vue';
import FullScreen from './FullScreen.vue';
@ -5,4 +6,4 @@ import SettingDrawerButton from './SettingDrawerButton.vue';
import GihubSite from './GihubSite.vue';
import HeaderItem from './HeaderItem.vue';
export { UserAvatar, MenuCollapse, FullScreen, SettingDrawerButton, GihubSite, HeaderItem };
export { GlobalBreadcrumb, UserAvatar, MenuCollapse, FullScreen, SettingDrawerButton, GihubSite, HeaderItem };

View File

@ -5,7 +5,10 @@
<div v-if="!theme.isVerticalNav" class="menu-width h-full">
<global-logo />
</div>
<menu-collapse />
<div class="flex-y-center h-full">
<menu-collapse />
<global-breadcrumb v-if="theme.crumbsStyle.visible" />
</div>
<div class="flex-1 flex justify-end h-full">
<gihub-site />
<full-screen />
@ -20,7 +23,7 @@
import { computed } from 'vue';
import { NLayoutHeader } from 'naive-ui';
import { useThemeStore } from '@/store';
import { UserAvatar, MenuCollapse, FullScreen, GihubSite, SettingDrawerButton } from './components';
import { GlobalBreadcrumb, UserAvatar, MenuCollapse, FullScreen, GihubSite, SettingDrawerButton } from './components';
import { GlobalLogo } from '../common';
defineProps({

View File

@ -31,7 +31,7 @@ export function transformRouteToMenu(routes: CustomRoute[]) {
/** 判断路由是否作为菜单 */
function asMenu(route: CustomRoute) {
return Boolean(route.meta?.asMenu);
return !route.meta?.isNotMenu;
}
/** 给菜单添加可选属性 */

View File

@ -91,7 +91,7 @@ export const customRoutes: CustomRoute[] = [
path: EnumRoutePath.root,
redirect: { name: RouteNameMap.get('dashboard-analysis') },
meta: {
asMenu: false
isNotMenu: true
}
},
{
@ -101,7 +101,6 @@ export const customRoutes: CustomRoute[] = [
redirect: { name: RouteNameMap.get('dashboard-analysis') },
meta: {
title: EnumRouteTitle.dashboard,
asMenu: true,
icon: Dashboard
},
children: [
@ -110,8 +109,7 @@ export const customRoutes: CustomRoute[] = [
path: EnumRoutePath['dashboard-analysis'],
component: () => import('@/views/dashboard/analysis/index.vue'),
meta: {
title: EnumRouteTitle['dashboard-analysis'],
asMenu: true
title: EnumRouteTitle['dashboard-analysis']
}
},
{
@ -119,8 +117,7 @@ export const customRoutes: CustomRoute[] = [
path: EnumRoutePath['dashboard-workbench'],
component: () => import('@/views/dashboard/workbench/index.vue'),
meta: {
title: EnumRouteTitle['dashboard-workbench'],
asMenu: true
title: EnumRouteTitle['dashboard-workbench']
}
}
]
@ -131,7 +128,6 @@ export const customRoutes: CustomRoute[] = [
component: BasicLayout,
meta: {
title: EnumRouteTitle.exception,
asMenu: true,
icon: ExceptionOutlined
},
children: [
@ -141,8 +137,7 @@ export const customRoutes: CustomRoute[] = [
component: () => import('@/views/system/exception/403.vue'),
meta: {
title: EnumRouteTitle['exception-403'],
fullPage: true,
asMenu: true
fullPage: true
}
},
{
@ -151,8 +146,7 @@ export const customRoutes: CustomRoute[] = [
component: () => import('@/views/system/exception/404.vue'),
meta: {
title: EnumRouteTitle['exception-404'],
fullPage: true,
asMenu: true
fullPage: true
}
},
{
@ -161,8 +155,7 @@ export const customRoutes: CustomRoute[] = [
component: () => import('@/views/system/exception/500.vue'),
meta: {
title: EnumRouteTitle['exception-500'],
fullPage: true,
asMenu: true
fullPage: true
}
}
]