1.创建目录
-
BasicLayout——全局布局
-
components——布局组件
GlobalContent
:全局内容GlobalHeader
:全局头部页面
2.处理GlobalHeader
创建HeaderMenu——头部菜单
声明相关类型
在typings
目录下创建system.d.ts
declare namespace App {/** 全局头部属性 */interface GlobalHeaderProps {/** 显示logo */showLogo: boolean;/** 显示头部菜单 */showHeaderMenu: boolean;/** 显示菜单折叠按钮 */showMenuCollapse: boolean;}/** 菜单项配置 */type GlobalMenuOption = import('naive-ui').MenuOption & {key: string;label: string;routeName: string;routePath: string;icon?: () => import('vue').VNodeChild;children?: GlobalMenuOption[];};/** 面包屑 */type GlobalBreadcrumb = import('naive-ui').DropdownOption & {key: string;label: string;disabled: boolean;routeName: string;hasChildren: boolean;children?: GlobalBreadcrumb[];};/** 多页签Tab的路由 */interface GlobalTabRouteextends Pick<import('vue-router').RouteLocationNormalizedLoaded, 'name' | 'fullPath' | 'meta'> {/** 滚动的位置 */scrollPosition: {left: number;top: number;};}interface MessageTab {/** tab的key */key: number;/** tab名称 */name: string;/** badge类型 */badgeProps?: import('naive-ui').BadgeProps;/** 消息数据 */list: MessageList[];}interface MessageList {/** 数据唯一值 */id: number;/** 头像 */avatar?: string;/** 消息icon */icon?: string;svgIcon?: string;/** 消息标题 */title: string;/** 消息发送时间 */date?: string;/** 消息是否已读 */isRead?: boolean;/** 消息描述 */description?: string;/** 标签名称 */tagTitle?: string;/** 标签props */tagProps?: import('naive-ui').TagProps;}}
配置路由相关状态管理
import { constRouter } from '@/router';import { defineStore } from 'pinia';
import { computed } from 'vue';
import { useRoute } from 'vue-router';interface RouteState {/** 菜单 */menus: App.GlobalMenuOption[];}export const useRouteStore = defineStore('route-store', {state:():RouteState => ({menus:[],}),actions:{transformRouteToMenu(){let menu:App.GlobalMenuOption[] = [] ;constRouter.forEach(route => {const{name , path} = routeconst menuItem : App.GlobalMenuOption = {key:path,label:String(name)}if(path != '/'){menu.push(menuItem);}})return menu},getRoute(){(this.menus as App.GlobalMenuOption[]) = this.transformRouteToMenu();},isLogin(){const route = useRoute();const isLogin = computed(() => route.fullPath === '/')return isLogin.value}}});
添加路由守卫
在router
->guard
下创建dynamic.ts
import type { Router, RouteLocationNormalized, NavigationGuardNext } from 'vue-router';
import { useRouteStore } from '@/store';/*** 动态路由*/
export async function createDynamicRouteGuard(to: RouteLocationNormalized,_from: RouteLocationNormalized,next: NavigationGuardNext,router: Router
) {const route = useRouteStore();await route.getRoute();next()}
修改路由配置
修改路由守卫的简单next
import type { Router } from 'vue-router';
import { useTitle } from '@vueuse/core';
import { createDynamicRouteGuard } from './dynamic';/*** 路由守卫函数* @param router - 路由实例*/
export function createRouterGuard(router: Router) {router.beforeEach(async (to, from, next) => {// 开始 loadingBarwindow.$loadingBar?.start();// 页面跳转权限处理createDynamicRouteGuard(to, from, next, router)});router.afterEach(to => {// 设置document titleuseTitle(to.name);// 结束 loadingBarwindow.$loadingBar?.finish();});
}
创建HeaderMenu组件
<template><div class="flex-1-hidden h-full px-10px"><n-scrollbar :x-scrollable="true" class="flex-1-hidden h-full" content-class="h-full"><div class="flex-y-center h-full"><n-menu:value="activeKey"mode="horizontal":options="menus"@update:value="handleUpdateMenu"/></div></n-scrollbar></div>
</template><script setup lang="ts">
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import type { MenuOption } from 'naive-ui';
import { useRouteStore } from '@/store';
import { useRouterPush } from '@/utils/composables/router';const route = useRoute();
const routeStore = useRouteStore();
const { routerPush } = useRouterPush();const menus = computed(() => routeStore.menus as App.GlobalMenuOption[]);
const activeKey = computed(() => route.name as string);function handleUpdateMenu(_key: string, item: MenuOption) {const menuItem = item as App.GlobalMenuOption;routerPush(menuItem.key);
}
</script><style scoped>
:deep(.n-menu-item-content-header) {overflow: inherit !important;
}
</style>
创建index.ts导出
import HeaderMenu from './HeaderMenu.vue';export{HeaderMenu,
}
创建GlobalHeader
<template><div class="flex-1-hidden flex-y-center h-full"><header-menu /></div></template><script setup lang="ts">import {HeaderMenu,} from './components';</script><style scoped>.global-header {box-shadow: 0 1px 2px rgb(0 21 41 / 8%);}</style>
3.全局布局组件
在BasicLayout
目录下创建简单布局组件。
LayoutHeader
<template><header class="job-collect_header" :style="style"><slot></slot></header>
</template><script setup lang='ts'>
import { computed } from 'vue';interface Props {/** 开启fixed布局 */fixed?: boolean,/** fixed布局的层级 */zIndex?: number,/** 最小宽度 */minWidth?: number,/** 高度 */height?: number,/** 左侧内边距 */paddingLeft?: number,/** 动画过渡时间 */transitionDuration?: number,/** 动画过渡函数 */transitionTimingFunction?: string,
}const props = withDefaults(defineProps<Props>(), {fixed: true,zIndex: 1001,minWidth: 900,height: 56,paddingLeft: 0,transitionDuration: 300,transitionTimingFunction: 'ease-in-out',
})const style = computed(() => {const {fixed,zIndex,minWidth,height,paddingLeft,transitionDuration,transitionTimingFunction,} = propsconst position = fixed ? 'fixed' : 'staic';return `position:${position};z-index:${zIndex};min-width:${minWidth}px;height:${height}px;padding-left:${paddingLeft}px;transition-duration:${transitionDuration};transition-timing-function:${transitionTimingFunction};`
})</script><style scoped>
.job-collect_header {left: 0;top: 0;flex-shrink: 0;width: 100%;transition-property: padding-left;
}
</style>
LayoutMain
<template><main class="job-collect_main" :style="style"><slot></slot></main>
</template><script setup lang="ts">
import { computed } from "vue";interface Props {/** 顶部内边距 与 头部高度一致 */paddingTop?: number;/** 底部内边距 */paddingBottom?: number;/** 左侧内边距 */paddingLeft?: number;/** 动画过渡时间 */transitionDuration?: number;/** 动画过渡函数 */transitionTimingFunction?: string;
}const props = withDefaults(defineProps<Props>(), {paddingTop: 56,paddingBottom: 0,paddingLeft: 0,transitionDuration: 300,transitionTimingFunction: "ease-in-out",
});const style = computed(() => {const {paddingTop,paddingBottom,paddingLeft,transitionDuration,transitionTimingFunction,} = props;return `padding-top:${paddingTop}px;padding-bottom:${paddingBottom}px;padding-left:${paddingLeft}px;transition-duration:${transitionDuration};transition-timing-function:${transitionTimingFunction}`;
});
</script><style scoped>
.job-collect_main {flex-grow: 1;width: 100%;
}
</style>
layout组件
仅针对login不显示menu
<template><div class="job-collect" :style="{ minWidth: minWidth + 'px' }"><layout-header:padding-left="headerPaddingLeft"v-if="paddingTop"><slot name="header"></slot></layout-header><layout-content :padding-left="mainPaddingLeft" :padding-top="paddingTop"><slot></slot></layout-content></div>
</template><script setup lang="ts">
import { useRouteStore } from "@/store";
import { computed } from "vue";
import { LayoutHeader, LayoutContent } from "./";const route = useRouteStore()const isLogin = computed(() => route.isLogin)const paddingTop = computed(() => {if(isLogin.value()){return 0}return 56
})interface Props {/** 最小宽度 */minWidth?: number;/** 头部偏移左侧高度 */headerPaddingLeft?:number,/** 左侧头部高度 */siderPaddingTop?:number/** main左侧偏移 */mainPaddingLeft?:number/** 侧边可见 */siderVisible?: boolean;
}withDefaults(defineProps<Props>(), {minWidth: 900,headerPaddingLeft: 0,siderPaddingTop: 56,siderVisible: false,
});</script><style scoped>
.job-collect {display: flex;flex-direction: column;width: 100%;height: 100%;
}
</style>
全局布局组件
<template><admin-layout><template #header><global-header /></template><global-content /></admin-layout></template><script setup lang="ts">import AdminLayout from './components/layout.vue';import {GlobalContent,GlobalHeader,} from '../components';
</script><style scoped></style>