一、新建layout模块全局状态
- @/store/modules目录下新建layout.ts
import { defineStore } from 'pinia';
const useLayoutStore = defineStore('layout', {
state: () => {
return {
menuCollapsed: false,
};
},
actions: {
menuTrigger() {
this.menuCollapsed = !this.menuCollapsed;
},
},
});
export default useLayoutStore;
- 修改@/store/index.ts
import useUserStore from './modules/user';
import useLoadingStore from './modules/loading';
import useLayoutStore from './modules/layout';
export { useUserStore, useLoadingStore, useLayoutStore };
二、解决layout图标按需加载的错误提示
- 执行命令
npm i -S @ant-design/icons-vue
三、将layout中页面头部模块化
- 在@/layout目录下新建modules目录
- 在@/layout/modules目录下新建Header.vue文件
<template>
<a-layout-header style="background: #fff; padding: 0 20px">
<menu-unfold-outlined v-if="layoutStore.menuCollapsed" class="trigger" @click="menuTrigger" />
<menu-fold-outlined v-else class="trigger" @click="menuTrigger" />
</a-layout-header>
</template>
<script lang="ts" setup>
import { MenuUnfoldOutlined, MenuFoldOutlined } from '@ant-design/icons-vue';
import { useLayoutStore } from '@/store';
const layoutStore = useLayoutStore();
const menuTrigger = () => {
layoutStore.menuTrigger();
};
</script>
<style lang="less" scoped>
.trigger {
font-size: 18px;
line-height: 64px;
padding: 0 24px;
cursor: pointer;
transition: color 0.3s;
}
.trigger:hover {
color: #1890ff;
}
</style>
- 引入头部组件Header到layout中
- 修改@/layout/index.vue
<template>
<a-layout class="layout">
<a-layout-sider v-model:collapsed="layoutStore.menuCollapsed" :trigger="null" collapsible>
<div class="logo" />
<a-menu theme="dark" mode="inline" v-model:selectedKeys="selectedKeys">
<a-menu-item key="/">
<dashboard-outlined />
<span>首页</span>
</a-menu-item>
</a-menu>
</a-layout-sider>
<a-layout>
<Header />
<a-layout-content style="margin: 24px 16px; padding: 24px; background: #fff; min-height: 280px">
<a-spin :spinning="loadingStore.loadingState" :delay="300" size="large">
<router-view />
</a-spin>
</a-layout-content>
</a-layout>
</a-layout>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { DashboardOutlined } from '@ant-design/icons-vue';
import { useLoadingStore, useLayoutStore } from '@/store';
import Header from './modules/Header.vue';
const loadingStore = useLoadingStore();
const layoutStore = useLayoutStore();
const selectedKeys = ref<string[]>(['/']);
</script>
<style lang="less" scoped>
.layout {
box-sizing: border-box;
width: 100%;
height: 100%;
}
.logo {
margin: 20px auto;
width: 80%;
height: 40px;
background: #334454;
}
#components-layout-demo-custom-trigger .logo {
height: 32px;
background: rgba(255, 255, 255, 0.3);
margin: 16px;
}
.site-layout .site-layout-background {
background: #fff;
}
</style>
四、layout中页面头部加入用户显示
- 修改@/layout/modules/Header.vue
<template>
<a-layout-header class="header">
<div class="left">
<div class="item" @click="menuTrigger">
<menu-unfold-outlined v-if="layoutStore.menuCollapsed" class="trigger" />
<menu-fold-outlined v-else class="trigger" />
</div>
</div>
<div class="right">
<div class="item">
<a-dropdown placement="bottom">
<div class="user">
<a-avatar class="avatar" :src="userStore.userInfo.avatar">登录</a-avatar>
<span class="name">{{ userStore.userInfo.name }}</span>
</div>
<template #overlay>
<a-menu>
<a-menu-item>
<poweroff-outlined style="margin-right: 5px; color: #f5222d" />
<span>退出系统</span>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div>
</div>
</a-layout-header>
</template>
<script lang="ts" setup>
import { MenuUnfoldOutlined, MenuFoldOutlined, PoweroffOutlined } from '@ant-design/icons-vue';
import { useLayoutStore, useUserStore } from '@/store';
const layoutStore = useLayoutStore();
const userStore = useUserStore();
const menuTrigger = () => {
layoutStore.menuTrigger();
};
</script>
<style lang="less" scoped>
.header {
height: 48px;
padding: 0 20px;
display: flex;
justify-content: space-between;
align-items: center;
background: #fff;
color: #666;
font-size: 18px;
.item {
cursor: pointer;
height: 48px;
display: flex;
align-items: center;
margin: 0 10px;
padding: 0 10px;
transition: color 0.3s;
&:hover {
background: rgba(0, 0, 0, 0.05);
color: #1890ff;
}
}
.user {
display: flex;
align-items: center;
.avatar {
margin-right: 10px;
}
.name {
font-size: 14px;
}
}
}
</style>
五、layout中页面头部加入退出功能
- Mock login的userInfo加入用户头像
- 修改Mock/login.ts
import Mock from 'mockjs';
export default [
{
// http://mockjs.com/examples.html
url: '/mock/api/login',
method: 'post',
timeout: 500,
// statusCode: 500,
response: ({ body }) => {
return {
code: 200,
success: true,
message: 'ok',
data: {
// query: body,
token: Mock.Random.string('lower', 200),
userInfo: {
id: Mock.Random.id(),
name: Mock.Random.cname(),
email: Mock.Random.email(),
gender: Mock.Random.natural(1, 2),
age: Mock.Random.natural(18, 30),
avatar: Mock.Random.image('800x800'),
},
},
};
},
},
];
- 修改@/store/modules/user.ts
import { defineStore } from 'pinia';
const useUserStore = defineStore('user', {
state: () => {
return {
token: '',
userInfo: {},
};
},
actions: {
setToken(token: string) {
this.token = token;
},
setUserInfo(userInfo) {
this.userInfo = userInfo;
},
clearToken() {
this.token = '';
},
clearUser() {
this.userInfo = {};
},
},
persist: {
enabled: true,
strategies: [
{ key: 'token', storage: localStorage, paths: ['token'] },
{ key: 'userInfo', storage: localStorage, paths: ['userInfo'] },
],
},
});
export default useUserStore;
- 修改@/router/index.ts
// @/router/index.ts
//......
router.beforeEach((to, from, next) => {
const userStore = useUserStore();
if (to.name !== 'Login' && !userStore.token) {
next({ name: 'Login' });
} else {
if (to.name === 'Login') {
userStore.clearToken();
userStore.clearUser();
}
next();
}
});
export default router;
- 增加退出登录的逻辑
- 修改@/layout/modules/Header.vue
<template>
<a-layout-header class="header">
<div class="left">
<div class="item" @click="menuTrigger">
<menu-unfold-outlined v-if="layoutStore.menuCollapsed" class="trigger" />
<menu-fold-outlined v-else class="trigger" />
</div>
</div>
<div class="right">
<div class="item">
<a-dropdown placement="bottom">
<div class="user">
<a-avatar class="avatar" :src="userStore.userInfo.avatar">登录</a-avatar>
<span class="name">{{ userStore.userInfo.name }}</span>
</div>
<template #overlay>
<a-menu>
<a-menu-item @click="outLogin">
<poweroff-outlined style="margin-right: 5px; color: #f5222d" />
<span>退出系统</span>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div>
</div>
</a-layout-header>
</template>
<script lang="ts" setup>
//......
const outLogin = () => {
router.push('/login');
};
</script>
<style lang="less" scoped>
......
</style>
六、源代码地址
https://github.com/jiangzetian/vue3-admin-template
七、视频演示及源码
本文演示视频:点击浏览
更多前端内容欢迎关注公众号:天小天个人网