1、我们的目标是:无限级分权限和菜单
这是我们集团开发的一个CRM系统
1.目的是总部可以控制加盟企业开通的模块
2.加盟企业可以控制旗下园区的模块
3.园区里的管理员可以分配多个管理员(超管和普通管理员)
4.管理员还可以分不同的角色,还可以控制到角色不同的按钮。
2、如何实现控制模块和控制按钮
1.模块=>菜单=>路由 , 这三个概念合三为一,反过来看就是 : 路由别名=>模块别名=>菜单别名,这样就可以确定,只要有路由地址的页面模块统一都有个名字,这里我们就可以用来判断权限有无了。
2.假如页面有一条数据有删除、发布、存草稿这一类没有页面跳转的按钮(没有路由别名,对应没有模块名),这种我们直接取个“别名”!
注意 :我们都知道每个路由都有别名name,且唯一,所以我们给按钮取“别名”的时候一定不要和路由别名重复,也不要和其它按钮重复,保持唯一
把我们的“别名”一条一条添加在权限表里
3、权限表都记录哪些信息?
毕竟我们前端只能控制显示隐藏,有和无,但是具体人家接口怎么控制权限,你总的有个东西控制吧!这时候我们就用到了API路由了。
1.添加菜单收集的内容如下,这里可以设置图标,菜单名字,我的图标是用了element里的icon,当然,你也可以用iconfont中的svg,点击下载图标,点击复制svg然后放在这里,然后输出svg图标,但是被后台同学说存的太大,实在不行我们可以做成上传图片。
2.添加权限收集的内容如下,这里权限应该挂在菜单上,然后取一个操作名称(例如:删除文章),这里的路由/api别名,其实就是如果是跳转页面,那就是跳转页面的别名,如果不是跳转页面,只是一个按钮请求api,那么就给这个按钮取个名字,或者你给叫给这个操作取“别名”也行。
注意一点:有路由的用路由别名,没有的,自己按照路由规则自取自定一个,假装它有路由。
4、如何利用权限表给集团分模块?
当然这里只是分模块,如果你想的话,你甚至可以控制到某个按钮有没有。集团如何给园区分模块也是同理。
5、园区如何建立角色?
管理员分配、角色分配、等等都可以分配到具体某个操作按钮。
6、说了那么多,代码如何实现?
在我们登录的时候,我们第一件事就是保持用户的user_info,第二件事就是保持别名列表到全局状态nameList这个状态里。(往下翻有技巧保证刷新不丢失。这里先记住往里存。)
这个nameList是菜单+操作的混合体,所以需要自己判断树结构层级,以及从中脱离操作按钮还是菜单。
// 后台的数据结构
{
name:'news' // 路由别名,或者按钮别名
parent_id:0, // 父id
title:"公司新闻", // 菜单名
sort:1, // 排序
type:1 //1=菜单,2=操作
}
1.关于菜单有无、路由跳转
我们的路由别名存到后台,都是一串别名,所以,这个别名应该是有一个完整的树结构的,这样就构成了我们的菜单结构,以及根据路由别名来做跳转地址。
// 首先这里数据来源肯定是你登录的时候保存到Store,这里肯定用到VUEX,这里不讲了
computed: {
// 过滤出菜单,排序、组合树结构省略
menu() {
return this.$store.getters.nameList.filter(i=>i.type===1);
},
// 获得别名列表,过滤省略
name() {
return this.$store.getters.nameList.map(i=>i.name);
}
},
// 循环 menu 菜单绑定上路由跳转和菜单名字,省略循环
{{ list.title }}
路由拦截,什么页面可以访问,什么不可以访问,不可以访问跳转到哪儿?
因为不是所有路由都是被拦截的,路由白名单也分登录前和登录后!
// 用一个专门的文件管理
let name = {
// 普通白名单
whiteNameList: ["login", "forgetPwd", "page404", "page403", "demo"],
// 登录后的白名单
loginCallbackNameList: ["home"],
}
// 路由拦截写法如下
let roleRouter = [
...store.getters.nameList.map(i=>i.name)
...name.whiteNameList
];
router.beforeEach((to, from, next) = >{
if (!to.name || roleRouter.includes(to.name)) {
next();
} else {
if (to.name === "home") {
next({
path: "/login",
replace: true
});
} else {
next({
path: "/403",
replace: true,
query: {
noGoBack: true
}
});
}
}
}
1.如何控制类似“删除”这种按钮显示隐藏
最好的方式是利用 mixins.js混入全局,而且此处不建议做成指令,而是全局methods混入一个$has,
methods: {
$has(value) {
return this.$store.getters.nameList.map(i=>i.name).includes(value);
},
}
如果你的指令写的很强大你可以用指令,如果没那么强大还是建议使用如下写法,但是免不了你要在页面各种判断权限有无来显示不同页面之类的,尤其按钮组合,我就是从用指令,后来弄成指令和methods方法一起用,最后还是觉得methods里的方法好用,看个人爱好了。
v-if="$has('news_delete')"
7、如果页面刷新了,如何保证全局状态nameList不丢失?
这个问题最初也是挺苦恼的,最后我给做了小手脚,当然其它很多状态你也可以利用这个小技巧,用来保持刷新后状态不丢失。
保存别名列表,把它放进本地缓存
mutations: {
SET_ROLEROUTER: (state, nameList) => {
state.nameList = nameList;
window.sessionStorage.setItem(
"nameList",
JSON.stringify(nameList)
);
},
}
获取的时候,判断state里有没有,如果没有,就从缓存再拉出来,getters.js代码片段如下
nameList: state => {
let stroageNameList = JSON.parse(
window.sessionStorage.getItem("nameList")
);
if (
state.user.nameList.length === 0 &&
(stroageNameList && stroageNameList.length > 0)
) {
store.commit("SET_ROLEROUTER", stroageNameList);
}
return state.user.nameList;
},
8、总结原理
1.利用路由别名做两件事,判断有无模块,控制跳转404还是继续前进。
2.给按钮也取一个别名,判断有无。