一般的中后台管理系统都有着一个统一的页面布局,而且我们使用的element-plus组件库已经给我们提供了比较好的布局组件,我们要做的就很简单了。
一、实现页面布局
vue
<script setup lang="ts">
import Header from './components/header.vue'
</script>
<template>
<div class="common-layout">
<el-container>
<el-header>
<Header />
</el-header>
<el-container>
<el-aside width="200px">
<el-menu
default-active="/"
class="el-menu-vertical-demo"
background-color="#545c64"
router
>
<el-menu-item index="/">项目描述</el-menu-item>
<el-menu-item index="/user">用户列表</el-menu-item>
<el-menu-item index="role">角色列表</el-menu-item>
<el-menu-item index="/auth">权限列表</el-menu-item>
</el-menu>
</el-aside>
<el-main>
<router-view />
</el-main>
</el-container>
</el-container>
</div>
</template>
<style lang="less" scoped>
.common-layout {
height: 100vh;
.el-container {
height: 100%;
}
.el-aside {
background-color: #545c64;
}
}
.el-header {
padding: 0;
}
.el-menu {
border-right: none;
}
</style>
vue
<template>
<div class="header">
<div class="flex-center">logo</div>
<div class="flex-grow"></div>
<div class="flex-center color-dark-black m05">
<i-ep-user />
{{ username }}
</div>
<div
class="flex-center color-dark-black m05 setting"
@click="handleSetting"
>
<i-ep-setting />
</div>
</div>
<el-drawer v-model="setting" :show-close="false" :with-header="false">
<div class="setting-header">
<h2>项目配置</h2>
<i-ep-close @click="setting = false" />
</div>
<div class="out">
<el-button type="primary" @click="logout">退出</el-button>
</div>
</el-drawer>
</template>
<style lang="less" scoped>
.header {
display: flex;
align-items: center;
padding: 0 15px;
width: 100%;
height: 60px;
}
.setting {
cursor: pointer;
}
.setting-header {
display: flex;
justify-content: space-between;
padding: 0 15px;
height: 60px;
color: var(--ry-color-black);
}
.out {
padding: 15px;
text-align: center;
.el-button {
width: 100%;
}
}
</style>
通过以上代码,我们实现的页面基本布局如下所示: 点击设置图标还会唤醒右侧的抽屉组件,在这个抽屉组件里我们可以点击退出
二、重写路由
2.1 重写路由配置
写完上面的布局之后,我们需要重新调整一下,路由的配置,让这个基本布局作为整个页面的根组件,其他页面都在这个页面中显示,具体的修改如下:
vue
export default {
path: '/',
name: 'Layout',
component: () => import('@/layout/index.vue'),
redirect: '/home',
children: [
{
path: 'home',
name: 'Home',
component: () => import('@/views/home/index.vue'),
meta: {},
},
{
path: 'user',
name: 'User',
component: () => import('@/views/user/index.vue'),
meta: {},
},
{
path: 'role',
name: 'Role',
component: () => import('@/views/role/index.vue'),
meta: {},
},
{
path: 'auth',
name: 'Auth',
component: () => import('@/views/auth/index.vue'),
meta: {},
},
],
}
2.2 添加路由守卫
在用户登录之后,我们应该给路由添加一些权限,只有登录过的用户才能访问,没有登录过的一律要返回登录页进行登录,在vue中vue-router给我们提供了路由守卫的钩子函数beforeEach和afterEach,在这里我们使用beforeEach这个钩子来实现路由守卫。
typescript
const whiteList = ['/login', '/404']
// @ts-expect-error from is optional
router.beforeEach((to, from, next) => {
NProgress.start()
const token = sessionStorage.getItem('user')
if (whiteList.includes(to.path) || token) {
next()
} else {
next('/login')
}
})
在路由守卫中我们获取登录时就已经持久化在本地的token值,根据token是否有值来决定是否放行,除此之外,我们还可以添加一些路由白名单,不需要经过路由守卫的检测用户就能进入,比如登录页等。
2.3 使用store修复刷新时左侧菜单啊选中项丢失的问题
element-plus组件库的el-menu组件给我们提供了一个属性default-active,它表示菜单的默认选中项,除此之外,还给我们提供了一个事件select,因此,我们可以利用这两个东西以及store修复刷新时选中项丢失的问题。
vue
<el-menu
:default-active="path"
class="el-menu-vertical-demo"
background-color="#545c64"
router
@select="handleSelect"
>
<el-menu-item index="/">项目描述</el-menu-item>
<el-menu-item index="/user">用户列表</el-menu-item>
<el-menu-item index="role">角色列表</el-menu-item>
<el-menu-item index="/auth">权限列表</el-menu-item>
</el-menu>
typescript
import { defineStore } from 'pinia'
import { IUserState } from '@/store/user/type'
import { loginRequest } from '@/api/user/type'
import { userLogin } from '@/api/user/user'
import { ElMessage } from 'element-plus'
const useUserStore = defineStore('user', {
state: (): IUserState => ({
username: '',
accessToken: '',
refreshToken: '',
roles: [],
path: '/',
}),
actions: {
async userLoginAction(data: loginRequest) {
const userLoginResult = await userLogin(data)
if (userLoginResult.code === 0) {
this.username = userLoginResult.data.username
this.accessToken = userLoginResult.data.accessToken
this.roles = userLoginResult.data.roles
ElMessage.success(userLoginResult.message)
} else {
ElMessage.error(userLoginResult.message)
}
},
setPath(path: string) {
this.path = path
},
},
persist: {
key: 'user',
paths: ['accessToken', 'path'],
storage: sessionStorage,
},
})
export default useUserStore
因为store也存在刷新时丢失的问题,我们需要用到持久化存储,并且因为继承了pinia-plugin-persistedstate
插件,因此只需要在persist对象的paths属性添加我们要持久化存储的路径数据path了。还有,我在actions里写了一个方法来修改保存在state中的path。
vue
<script setup lang="ts">
import Header from './components/header.vue'
import useUserStore from '@/store/user'
import { storeToRefs } from 'pinia'
const userStore = useUserStore()
const { path } = storeToRefs(userStore)
const handleSelect = (index: string) => {
userStore.setPath(index)
}
</script>
通过以上代码,就可以实现路径的永久保存了,这样用户在刷新也不会丢失了。
三、实现退出功能
vue
<script setup lang="ts">
import { ref } from 'vue'
import useUserStore from '@/store/user'
import { storeToRefs } from 'pinia'
import { useRouter } from 'vue-router'
const setting = ref(false)
const userStore = useUserStore()
const { username } = storeToRefs(userStore)
const router = useRouter()
const handleSetting = () => {
setting.value = true
}
const logout = () => {
sessionStorage.removeItem('user')
router.push('/login')
}
</script>