以前觉得“永远相信美好的事情即将发生” 这句话很矫情,现在却很喜欢这句让人信心满满的话。“ 这个孩子在以后,不管遇到任何的对他的贬低、羞辱,说你不行。这孩子都会自信的像鲁豫一样,真的吗?我不信。” 哈哈哈。
言归正传,这篇博客主要记录的是前端权限逻辑,以最简单的代码还原其本质。这里主要用到了 Vue-Router Vuex sessionStorage
的内容。效果:
流程图如下:
登录权限
sessionStorage
中保存的数据会保存在浏览器中,直到我们关闭了相应的 tab 页面。根据这个特点,当用户完成登录后,设置 sessionStorage.setItem('login', "login");
表示用户已经完成登录。刷新页面的时候也是根据 sessionStorage
中是否有 login
来判断用户是否已经登录即可。
Vue Router
Vue Router 用来管理我们的前端路由。分为 hash 和 history 两种模式。这篇文章有介绍两种路由模式的原理和区别。
- 用到了全局导航守卫函数
router.beforeEach((to, from, next)=>{})
进行权限路由逻辑判断。 - 利用
router.addRoutes(menus);
动态添加路由 - next(); 和 next({ ...to}) 是不一样的两个操作,虽然它们两个跳转的地址是一样的。next();是继续当前路由;next({ ...to})是终止当前路由,重新跳转到
to
要去的地址。利用 next({ ...to}) 可以等待router.addRoutes(menus);
添加完路由后,再进行跳转操作。
将路由分为 router.js 和 appRouter.js 两个文件。router.js 存放不需要权限的路由,包括登录、首页和 notFound 页面。其中登录和首页是不需要权限就可以访问的页面。appRouter.js 主要存放的是完成登录后,动态添加到路由对象中的路由列表。需要将 path: '*', redirect: '/notFound'
放在 appRouter.js 文件最后一个,用来匹配用户随意修改页面地址栏不能匹配到路由需要跳转的地址。我们将路由处理逻辑放在 router.js 中。
//router.js
const whiteList = ['/login', '/index']
router.beforeEach(async(to, from, next) => {
//判断是否登录
const hasLogin = sessionStorage.getItem('login') ? true : false;
document.title = to.meta.title;
if(!hasLogin){
if(whiteList.indexOf(to.path)!==-1){
next();
}else{
next({path: "/login"});
}
}else{
if(to.path==='/login'){
//相当于退出登录
sessionStorage.removeItem("login");
sessionStorage.removeItem("menu");
sessionStorage.removeItem("roles");
sessionStorage.removeItem("curPath");
next();
}else{
//判断是否是刷新页面
const hasRoles = store.state.user.roles && store.state.user.roles.length > 0;
if(hasRoles){
next();
}else{
//重新获取用户的权限(角色), 请看 Vuex 部分
const roles = await store.dispatch('getRoles');
const menus = await store.dispatch('getMenus');
router.addRoutes(menus);
next({ ...to, replace: true });
}
}
}
});
Vuex
将 state 根据需要分为 user
和 menu
两个模块。完成登录操作后,我们可以从后端获取当前用户的角色或者当前用户的路由列表。
角色信息存储在 user
模块中。这些信息可以在 router.js 文件中被访问到,用来判断是否是刷新页面。因为一旦刷新页面,Vuex 存在内存的信息会被清空。这个时候应该从后端从新获取当前用户的角色或者可访问的路由列表。本例子中直接设置为 admin 的角色;不将动态路由组件放在 appRouter.js 文件中。
动态路由在完成登录后会被存储在menu
模块中。 app.vue
会根据这些信息动态生成对应的路由跳转菜单。
store.dispatch
操作后会返回一个 Promise
对象。完成异步操作的处理。
// user.js
const user = {
state: {
roles: []
},
mutations: {
setRoles: (state, val)=>{
state.roles.push(val);
sessionStorage.setItem("roles", val);
}
},
actions: {
setRoles: (context, val)=>{
context.commit("setRoles", val);
},
getRoles: (context)=>{
return new Promise((resolve, reject)=>{
//模拟请求后端当前用户的角色或者权限
const roles = sessionStorage.getItem("roles");
context.commit("setRoles", roles);
resolve();
})
}
}
}
export default user;
//menu.js
import appRouter from '../../route/appRouter'
const menu = {
state: {
menuList: []
},
mutations: {
setMenus: (state, menus) => {
state.menuList = menus;
sessionStorage.setItem('menu', JSON.stringify(menus));
}
},
actions: {
getMenus: (context)=>{
//这里可以请求后端;或者根据后端返回的数据对 appRouter 进行过滤,只用有权限的组件。
return new Promise((resolve, reject)=>{
context.commit("setMenus", appRouter);
resolve(appRouter);
})
}
}
}
export default menu
源码
如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎点赞,对作者也是一种鼓励。