vue实现菜单权限控制
大家在做后台管理系统时一般都会涉及到菜单的权限控制问题。当然解决问题的方法无非两种——前端控制和后端控制。我们公司这边的产品迭代速度较快,所以我们是从前端控制路由迭代到后端控制路由。下面我会分别介绍这两种方法的优缺点以及如何实现(不熟悉vue-router API的同学可以先去官网看一波API哈)。
我先简单说下项目的需求:如下图所示,有一级菜单和二级菜单,然后不同的人登录进去会展示不同的菜单。
前端控制路由的思路:将所有的路由映射表都拿到前端来维护,就是我的router.js里面将所有的菜单path与对应的components全部写进去,后面我会提到全部写进去的弊端。然后我的左侧菜单写成一个组件(sidebar.vue),在这个组件里写一份类似这样的data数据,然后通过登录时获取的level值来给data中固定的菜单加hidden,然后前端就根据hidden展示菜单。
// router.js 伪代码 const Login = r => require.ensure([],()=>r(require('../page/login/Login.vue')),'login');
const Home = r => require.ensure([],()=>r(require('../page/Home.vue')),'home');
const Forbidden = r => require.ensure([],()=>r(require('../page/403.vue')),'forbidden');
const NotFound = r => require.ensure([],()=>r(require('../page/404.vue')),'notfound');
const Dashboard = r => require.ensure([],()=>r(require('../page/dashboard/Dashboard.vue')),'dashboard');
const SplashScreen = r => require.ensure([],()=>r(require('../page/splashScreen/SplashScreen.vue')),'splashScreen');
const AddSplashScreen = r => require.ensure([],()=>r(require('../page/splashScreen/AddSplashScreen.vue')),'addSplashScreen'); const routes = [
{
path: '/',
redirect: '/login'
},{
path: '/login',
component: Login
},{
path: '/404',
component: NotFound
},{
path: '/home',
component: Home,
redirect: '/home/splashScreen',
children: [
{
path: '/home/splashScreen',
component: SplashScreen,
meta: {
title: '闪屏广告配置'
}
},{
path: '/home/addSplashScreen',
component: AddSplashScreen,
meta: {
title: '新增闪屏广告'
}
}
]
}
];
下面是菜单组件的伪代码
// sidebar.vue
<template>
<div class="sidebar">
<el-menu>
...
</el-menu>
</div>
</template>
<script>
export default {
data() {
return {
routes: [
{
index: '1',
title: '广告管理',
icon: 'iconfont icon-guanggao',
children: [
{
index: 'splashScreen',
title: '闪屏广告配置',
children: []
},
]
},
{
index: '2',
title: '推送管理',
icon:'iconfont icon-tuisongguanli-',
}
]
}
},
methods: {
getLevel(){
const level = sessionStorage.getItem('level');
if(level === '0'){
this.routes.forEach(function(value){
if(value.title == "车机管理"){
value.hidden = true;
value.children.forEach(function(value){
if(value.title=="车机解绑"){
value.hidden = true;
}
})
}
})
}else if(level === '1'){
this.routes.forEach(function(value){
value.hidden = true
value.children.forEach(function(value){
value.hidden = true;
})
})
}
}
},
created(){
this.getLevel();
}
}
</script>
虽然说这样可以实现权限功能,但有两个问题。
1. session里存的是level,我们可以打开浏览器控制台人为控制level,这样就失去了权限的意义。
2. 我们如果记住了path,可以直接在浏览器网址栏中手动输入path,然后回车就可以看到任何页面。这也是前端router.js写死所有路由的弊端。
在这里面前端只是通过后端传回的level来给router显示/隐藏,这样前端维护整个路由是比较复杂的而且是有重大隐患的。
现在呢我们来讲讲后端控制路由。先从操作流程来说,我们这边加入了一个dashboard中间页,这个页面只展示不同level下的一级路由,通过点击相应的一级路由进到对应的Page页面,该page页面也只展示相对应的所有的二级路由。
这里面出现了两个个新的概念叫 “动态添加路由”和“导航守卫”,就是我前端router.js中只写所有人可以访问的路由表,比如login和404页面等。其他所有的组件资源全部写到一个新的components.js文件中,然后通过后端返回的menuData去映射符合components.js中的key,如果有对应的,就把它动态添加到router中,通过addRoutes添加。动态添加路由这个方法要写到导航守卫beforeEach这个钩子函数中。导航守卫的意思是我路由跳转到下个页面之前要做些什么。就是说我们登录后会跳到dashboard页面,在进到这个页面之前我们需要将后端请求回来的menuData进行二次封装,把他根据权限返回回来的data与我们前端components.js去做map匹配,将最终的数据通过addRoutes来push到我们的路由中,之后才能进到我们的dashborad页面,再通过dashborad页面进到对应的page页面,就是说我们把所有的权限控制全在dashboard页面进入之前就做完了。这里面还有一个小的优化的点:当我们通过前面说的浏览器菜单栏访问到非权限页面或者不存在的页面时,需要根据vue-router中的匹配优先级来最后addRoutes 404和*这个页面,这样就可以直接到达404页面而非空页面。
// components.js 所有的页面资源
const home = () => import('../page/Home.vue');
const splashScreen = () => import('../page/splashScreen/SplashScreen.vue');
const addSplashScreen = () => import('../page/splashScreen/AddSplashScreen.vue');
const editSplashScreen = () => import('../page/splashScreen/EditSplashScreen.vue'); export default {
home,
splashScreen,
addSplashScreen,
editSplashScreen, };
// router.js 看,只写通用的页面是不是很清爽
import Vue from 'vue';
import Router from 'vue-router'; Vue.use(Router); const Login = () => import('../page/login/Login.vue');
const Home = () => import('../page/Home.vue');
const Forbidden = () => import('../page/403.vue');
const Dashboard = () => import('../page/dashboard/Dashboard.vue');
const routes = [
{
path: '/',
redirect: '/login'
},{
path: '/login',
component: Login
},{
path: '/403',
component: Forbidden
},
{
path: '/dashboard',
component: Dashboard,
},
];
export default new Router({
mode: 'history',
routes: routes,
base: __dirname,
linkActiveClass: 'link-active' })
// main.js 伪代码 只保留具体的相关逻辑
import routeMap from './router/component.js';
const NotFound = () => import('./page/404.vue');
const formatRoutes = function (routes, routeData) {
if (!routeData) {
routeData = {
name: 'home',
path: '/home',
// 组件匹配成功的话才可以访问具体的页面
component: routeMap['home'],
children: [],
};
}
routes.length && routes.forEach(route => {
if(route.component) {
route.component = routeMap[route.component];
routeData.children.push({
path: route.path,
name: route.index,
component: route.component,
meta: {
title: route.title,
},
})
}
if (route.children && route.children.length) {
formatRoutes(route.children, routeData);
}
});
return routeData;
}; let isFetchRemote = true; //使用钩子函数对路由进行权限跳转
router.beforeEach((to, from, next) => {
const username = sessionStorage.getItem('username');
if(!username && to.path !== '/login'){
next({path: '/login'});
}
else if (isFetchRemote && to.path !== '/login') {
ajaxPost('/resourceAPI/getMenuData').then(res =>{
if (res.status === 200 && res.data.errno === 0) {
isFetchRemote = false;
const menuData = res.data.result;
localStorage.setItem('menudata', JSON.stringify(menuData));
const routeData = formatRoutes(menuData);
resourceApp.$router.addRoutes([routeData].concat([
{name:'404',path:'/404',component:NotFound},
{path:'*',redirect: '/404'}]));
resourceApp.$router.push({
path: to.path,
query: to.query
});
}
else {
isFetchRemote = true;
}
next();
})
.catch(err => {
console.log(err);
});
}
else {
next();
}
}); const resourceApp = new Vue({
router,
render: h => h(App)
}).$mount('#app');
// menuData请求数据
// 一级菜单与二级菜单的区别是一级菜单带有component这个值,比如下面的短信管理就是只有一级菜单
{
"errno": 0,
"errmsg": "获取权限成功",
"result": [
{
"index": "1",
"title": "广告管理",
"icon": "iconfont icon-guanggao",
"children": [
{
"index": "splashScreen",
"icon": "",
"title": "闪屏关羽配置",
"path": "/home/splashAdverse",
"component": "splashAdverse",
"isShow": true
},
{
"index": "addSplashScreen",
"icon": "",
"title": "新增关羽广告",
"path": "/home/addAdverse",
"component": "addAdverse",
"isShow": false
}, ]
},
{
"index": "message",
"title": "短信管理",
"icon": "iconfont icon-duanxinguanli",
"path": "/home/message",
"component": "message",
"children": [
{
"index": "addMessage",
"title": "新增短信",
"icon": "",
"path": "/home/addMessage",
"component": "addMessage",
"isShow": false
} ]
}
]
}
而sidebar和dashboard这两个组件都只需要通过session拿到后端的menudate就可以。
// dashboard 伪代码
<template>
<div class="nav_list">
<div class="nav_list_item" v-for="item in navList" @click="goPage(item)">
<i :class="item.icon"></i>
<h2>{{item.title}}</h2>
</div>
</div>
</template> <script>
created(){
const routeArr = JSON.parse(localStorage.getItem('menudata'));
this.navList = routeArr;
},
methods: {
goPage(item){
// 只有一级菜单
if(item.component){
this.$router.push(item.path);
}else{
// 二级菜单的数据结构中只在children中有path
this.$router.push(item.children[0]['path']);
}
}
}
</script>
// sidebar 伪代码
<script>
export default {
data() {
return {
routes: [],
}
},
methods: {
bouncer(arr){
return arr.filter(function(val){
return !(!val || val === "");
});
}
},
created(){
const menuData = JSON.parse(localStorage.getItem('menudata'));
// 通过当前router的path来map对应的整个路由数组
let routes = menuData.map((item)=>{ // 只有一级路由
if(item.component && item.path == this.$route.path){
console.log(item)
return item;
}else{
if(item.children[0]['path'] == this.$route.path){
console.log(item)
return item;
}
}
})
// 去掉数组中的undefined、null 等空值 假值
this.routes = this.bouncer(routes);
}
}
</script>
通过这种方式来控制权限,我们如果在浏览器控制台改了session中的level或者在浏览器导航栏改path,都会回归到导航守卫中,就是发请求重新获取menuData,当我addRoutes后如果没有匹配到这个值就回到404,当然通过改level也不会达到修改权限的控制,因为我们是动态获取路由,不是之前的前端控制路由。
目前为止,我感觉通过后端控制权限这种实现方式应该是最理想的一种吧,当然大家有更好的方法或者对此文有任何问题,欢迎大家留言哈。
vue实现菜单权限控制的更多相关文章
- VUE 后台管理系统权限控制
谈一谈VUE 后台管理系统权限控制 前端权限从本质上来讲, 就是控制视图层的展示,比如说是某个页面或者某个按钮,后端权限可以控制某个用户是否能够查询数据, 是否能够修改数据等操作,后端权限大部分是基于 ...
- C#winform菜单权限分配,与菜单同步的treeView树状菜单权限控制使用心得
在网上查了很多,发现没有讲述关于--C#winform菜单权限分配,与菜单同步的treeView树状菜单权限控制使用--的资料 自己研究了一个使用方法.下面来看看. 我有两个窗体:LOGINFRM,M ...
- pc vue 项目中的菜单权限控制
在pc 管理系统这种类型的产品,通常会涉及到账号权限的控制,不同的账号权限能浏览的功能模块是不同的,对应侧边栏菜单模块的显示也会不同. 场景一.(电商类管理系统) 登录 登录后,依次获取账号 toke ...
- vue+elementui搭建后台管理界面(6登录和菜单权限控制)
不同的权限对应不同的路由(菜单),同时侧边栏也根据权限异步生成,实现登录和鉴权思路如下: 登录:点击登录,服务器验证通过后返回一个 token ,然后存到 cookie,再根据 token 拉取用户权 ...
- vue+elementui搭建后台管理界面(6登录和菜单权限控制[二])
根据权限计算路由的代码 /** * 通过meta.role判断是否与当前用户权限匹配 * @param roles * @param route */ function hasRoles (roles ...
- vue路由菜单权限设置就button权限设置
路由权限的设计思路: 首先,我们的需要校验权限的路由的 url,全部由后端返回,后端会返回当前用户的路由树数组.前端在进入页面前请求接口,把数据拿到: 其次,前端会维护一个路由映射组件的列表,如果路由 ...
- 如何优雅的在 vue 中添加权限控制
前言 在一个项目中,一些功能会涉及到重要的数据管理,为了确保数据的安全,我们会在项目中加入权限来限制每个用户的操作.作为前端,我们要做的是配合后端给到的权限数据,做页面上的各种各样的限制. 需求 因为 ...
- 如何在 vue 中添加权限控制管理?---vue中文社区
前言 在一个项目中,一些功能会涉及到重要的数据管理,为了确保数据的安全,我们会在项目中加入权限来限制每个用户的操作.作为前端,我们要做的是配合后端给到的权限数据,做页面上的各种各样的限制. 需求 因为 ...
- vue路由登录拦截(vue router登录权限控制)
实现原理: 哪些路由需要验证需要在路由文件router/index.js中指定: { path: '/', component: Home, name: 'Home', iconCls: 'fa fa ...
随机推荐
- Python变量常量及注释
一.变量命名规则1.有字母.数字.下划线搭配组合而成2.不能以数字开头,更不能全为数字3.不能用Python的关键字4.不要太长5.名字要有意义6.不要用中文7.区分大小写8.采用驼峰体命名(多个单词 ...
- html5 canvas绘制环形进度条,环形渐变色仪表图
html5 canvas绘制环形进度条,环形渐变色仪表图 在绘制圆环前,我们需要知道canvas arc() 方 ...
- C语言--链表基础模板
1.建立结构体 struct ST { int num;///学号 int score;///成绩 struct ST*next; };///结构体 2.空链表的创建 struct ST creatN ...
- Thunder团队第六周 - Scrum会议5
Scrum会议5 小组名称:Thunder 项目名称:i阅app Scrum Master:翟宇豪 工作照片: 胡佑蓉同学在拍照,所以不在照片内. 参会成员: 王航:http://www.cnblog ...
- fuck the browser mode
使用了source insight 4有一段时间了,今天用着突然发现我的鼠标移动到变量.函数.自定义的类型上时,单击鼠标左键直接就跳到了定义处,很像是按住了Ctrl再单击鼠标,用得极其不舒服,开始怀疑 ...
- week1技术随笔
2016-09-06 2016年9月3日 类别c 内容c 开始时间s 结束时间e 被打断时间I 总计(min) 读书 读构建之法 8:40 10:00 11 69 读书 构建之法-个人能力 , ...
- BAT批处理(六)
字符串处理 批处理有着具有非常强大的字符串处理能力,其功能绝不低于C语言里面的字符串函数集.批处理中可实现的字符串处理功能有:截取字符串内容.替换字符串特定字段.合并字符串.扩充字符串等功能.下面对这 ...
- 【Docker 命令】- rmi命令
docker rmi : 删除本地一个或多个镜像. 语法 docker rmi [OPTIONS] IMAGE [IMAGE...] OPTIONS说明: -f :强制删除: --no-prune : ...
- 使用source创建一个新项目(将本地项目文件和github远程库链接)
1. 本地创建项目文件夹 2. 将本地的项目添加到source中(我使用的source版本为2.4.7.0) 3. github创建远程库 4. 关联本地项目文件和github库 确定添加就可以了. ...
- usaco中遇到的问题
numbers are integers with unique digits 意思是数字中的每一个数字都是不一样的& 让一个图成为强连通图只需添加max(出度为0,入度为0)的点,然后如果图 ...