## 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 GlobalTabRoute
extends 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} = route
const 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) => {
// 开始 loadingBar
window.$loadingBar?.start();
// 页面跳转权限处理
createDynamicRouteGuard(to, from, next, router)
});
router.afterEach(to => {
// 设置document title
useTitle(to.name);
// 结束 loadingBar
window.$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,
} = props
const 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>