feat(components): 添加面包屑
This commit is contained in:
parent
667282f81a
commit
c1cdc3a9ed
@ -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 修改'],
|
||||
|
@ -72,6 +72,7 @@ soybean-admin
|
||||
│ │ ├── dark-mode.ts //windicss暗黑模式插件
|
||||
│ │ └── smooth-scroll.ts //滚动平滑插件
|
||||
│ ├── router //vue路由
|
||||
│ │ ├── menus.ts //菜单
|
||||
│ │ ├── permission.ts //路由守卫相关函数
|
||||
│ │ └── routes.ts //声明的路由
|
||||
│ ├── service //网络请求
|
||||
|
@ -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
|
||||
}
|
||||
})
|
||||
];
|
||||
|
20
index.html
20
index.html
@ -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>
|
||||
|
90
public/resource/loading.css
Normal file
90
public/resource/loading.css
Normal 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
BIN
public/resource/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
@ -9,8 +9,8 @@ interface RouteMeta {
|
||||
title?: string;
|
||||
/** 页面100%视高 */
|
||||
fullPage?: boolean;
|
||||
/** 作为菜单 */
|
||||
asMenu?: boolean;
|
||||
/** 不作为菜单 */
|
||||
isNotMenu?: boolean;
|
||||
/** 菜单和面包屑对应的图标 */
|
||||
icon?: Component;
|
||||
}
|
||||
|
@ -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>
|
@ -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 };
|
||||
|
@ -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({
|
||||
|
@ -31,7 +31,7 @@ export function transformRouteToMenu(routes: CustomRoute[]) {
|
||||
|
||||
/** 判断路由是否作为菜单 */
|
||||
function asMenu(route: CustomRoute) {
|
||||
return Boolean(route.meta?.asMenu);
|
||||
return !route.meta?.isNotMenu;
|
||||
}
|
||||
|
||||
/** 给菜单添加可选属性 */
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user