前言
作为一个管理后台项目,权限控制是最基础的功能。这篇文章主要介绍了下页面级别的权限控制,即登录用户只能进入有权限的页面,菜单栏也只显示有权限的路径。下面来简单的说下实现思路:
- 用户登入成功之后,获取后台给的登录凭证
token - 在路由守卫
beforeEach中判断是否获取了用户的权限列表 - 获取成功后使用
vue-router的addRoute动态挂载有权限的路由 - 根据权限列表,返回左侧菜单栏
准备工作
因为这个项目是个纯前端项目,项目中所有的请求接口都只能靠自己模拟了,我这里用mockjs文档模拟接口返回
- 先安装
yarn add mockjs - 创建存放模拟数据文件
// src/api/mock/baseApi.js
import Mock from 'mockjs'
Mock.mock('/test/login', {
code: 200,
message: '成功',
data: {
'userName|1': Mock.Random.cname(),
token: /\d{10,20}/,
},
})
// ... other
开始
路由改造
- 在路由meta的配置中添加
noAauth来判断当前路由是否需要进行权限认证,请注意:登录页面一定不要开启权限认证
// src/router/public.js
const routers = [{
path: '/',
meta: {noAauth: true },
// ... other
}, {
path: '/setting',
// ... other
}, {
path: '/login',
meta: {noAauth: true},
// ... other
}, ];
export default routers;
- 挂载不需要权限认证的路由
// src/router/index.js
import publics from './public';
const routesList = [...publicst];
const routes = routesList.filter((i) => { // 过滤掉需要权限认证的路由
return i.meta ? i.meta.noAauth : false;
});
const router = createRouter({ // 挂载不需要权限认证的路由
history: createWebHashHistory(),
routes,
});
// ... other
登入页面
登录成功获取token
const login = async () => {
if (active.value === 1) { // 手机号登录
let res = await api.BaseApi.login(phone.value, verifCode.value)
if (res.code === 200) {
publicStore.setUserMsg(res.data) // 出发pinia中的action 缓存用户信息
axios.defaults.headers.common['token'] = res.data.token; // 设置全局的请求token
router.push('/') // 跳转到首页 如果首页也开启了权限认证 这里需要改成有权限的路径。
} else {
ElMessage({ type: 'error', message: res.message ? res.message : res })
}
} else { // 密码登录
}
}
路由拦截
在上一步登录成功后跳转到首页,在跳转的过程中要获取用户的权限并动态加载路由
// src/router/index.js
import { PublicStore } from '@/store/Public';
// ... other
const getRoutesAuth = (power) => { // 添加路由
let powerList = routesList.filter((i) => { // 过滤掉不需要权限的路由 因为已经挂载过一次
return !i.meta || !i.meta.noAauth
})
powerList.forEach(e => {
if (power === 1 || power.includes(e.path)) { //power(1)表示管理员权限全部挂载 判断权限列表是否包含当前路由 包含则挂载
router.addRoute(e)
}
})
};
router.beforeEach(async(to, from, next) => {
if (to.path !== '/login' && !localStorage.get('token')) { // 判断是否登录 非`login`页面无`token`的话跳转到登录页面
next({ path: '/login' });
} else {
const stores = PublicStore() // pinia 状态管理
if (to.path !== '/login' && stores.power.length === 0) { // 判断是否获取过权限
let res = await stores.setPower() // 获取权限列表
if (res) {
getRoutesAuth(stores.power) // 根据权限列表挂载路由
next({ path: to.fullPath }) // 跳转页面
} else { // 获取权限失败返回到来源页面
next({ path: from.path, query: { hasPower: 1 } })
}
} else if (router.hasRoute(to.name)) { // 判断路由是否存在
next()
} else { // 路由不存在返回原页面
next({ path: from.path, query: { hasPower: 1 } })
}
}
});
export default router;
获取权限
在pinia的setPower中获取用户权限并生成左侧的菜单栏
// src/store/Public.js
import { defineStore } from 'pinia'
import api from '@/api'
//定义左边菜单列表
let menuList = [
{ router: '/', title: '首页', noAauth: true, icon: 'iconzhanghao', ch: [] },
{ router: '/adminSet', title: '用户管理', icon: 'iconzizhanghaoguanli', ch: [{ router: '/adminSet/accountSet', title: '权限配置' }] },
{ router: '/setting', title: '系统设置', icon: 'iconshezhi', ch: [] }
];
export const PublicStore = defineStore('Public', {
state: () => {
return {
power: [],
leftMenu: []
}
},
actions: {
async setPower() {
let res = await api.BaseApi.getPower() // 获取权限
if (res.code === 200) {
this.power = res.data.length === 0 ? 1 : res.data // 权限列表返回为空 赋值为1表示拥有全部权限
this.setLeftMenu()
return true
} else {
return false
}
},
setLeftMenu() { // 左边菜单栏
let list = cloneDeep(menuList); // 深拷贝一份 防止不刷新时 权限改变后重新登入有问题
if (this.power === 1) { // 超级管理员返回全部路由
this.leftMenu = list;
} else {
// 设置已经添加的权限 `noAauth`true 表示无需进行权限控制
list.forEach((i) => {
if (i.ch.length > 0) {
i.ch = i.ch.filter((y) => {
return this.power.includes(y.router) || y.noAauth; // 返回不需要权限控制和授予过权限的菜单
});
if (i.ch.length === 0) { // 如果子菜单为空 一级菜单置空
i.router = undefined
}
} else {
if (!this.power.includes(i.router) && !i.noAauth) { // 需要进行权限控制的未授权页面
i.router = undefined // 一级菜单置空
}
}
});
this.leftMenu = list.filter((i) => { // 过滤掉没有权限的一级菜单
return !!i.router;
});
}
}
}
})
生成左侧菜单
获取 setLeftMenu设置的 leftMenu的值,循环生成菜单
// js
import { PublicStore } from '@/store/Public'
import { storeToRefs } from 'pinia'
const { leftMenu } = storeToRefs(PublicStore())
// vue
<el-menu>
<template v-for="item in leftMenu" :key="item.router">
<el-sub-menu v-if="item.ch && item.ch.length > 0" :index="item.router">
<template #title>
<i class="iconfont" :class="item.icon"></i>
<span>{{ item.title }}</span>
</template>
<el-menu-item v-for="i in item.ch" :key="i.router" :index="i.router">{{ i.title }}</el-menu-item>
</el-sub-menu>
<el-menu-item v-else :index="item.router">
<i class="iconfont" :class="item.icon"></i>
<template #title>{{ item.title }}</template>
</el-menu-item>
</template>
</el-menu>
最后
以上的代码是部分核心代码,全部的代码戳这里 vueAdmin