当先后端分离时,权限问题的处理也和咱们传统的处理方式有一点差别。笔者前几天恰好在负责一个项目的权限管理模块,如今权限管理模块已经作完了,我想经过5-6篇文章,来介绍一下项目中遇到的问题以及个人解决方案,但愿这个系列可以给小伙伴一些帮助。本系列文章并非手把手的教程,主要介绍了核心思路并讲解了核心代码,完整的代码小伙伴们能够在GitHub上star并clone下来研究。另外,本来计划把项目跑起来放到网上供小伙伴们查看,可是以前买服务器为了省钱,内存只有512M,两个应用跑不起来(已经有一个V部落开源项目在运行),所以小伙伴们只能将就看一下下面的截图了,GitHub上有部署教程,部署到本地也能够查看完整效果。前端
项目地址:https://github.com/lenve/vhr vue
前面几篇文章,咱们已经基本解决了服务端的问题,并封装了前端请求,本文咱们主要来聊聊登陆以及组件的动态加载。 ios
本文是本系列的第五篇,建议先阅读前面的文章有助于更好的理解本文: git
1.SpringBoot+Vue先后端分离,使用SpringSecurity完美处理权限问题(一)
2.SpringBoot+Vue先后端分离,使用SpringSecurity完美处理权限问题(二)
3.SpringSecurity中密码加盐与SpringBoot中异常统一处理
4.axios请求封装和异常统一处理github
当用户登陆成功以后,须要将当前用户的登陆信息保存在本地,方便后面使用。具体实现以下:json
在登陆操做执行成功以后,经过commit操做将数据提交到store中,核心代码以下:axios
this.postRequest('/login', { username: this.loginForm.username, password: this.loginForm.password }).then(resp=> { if (resp && resp.status == 200) { var data = resp.data; _this.$store.commit('login', data.msg); var path = _this.$route.query.redirect; _this.$router.replace({path: path == '/' || path == undefined ? '/home' : path}); } });
store的核心代码以下:后端
export default new Vuex.Store({ state: { user: { name: window.localStorage.getItem('user' || '[]') == null ? '未登陆' : JSON.parse(window.localStorage.getItem('user' || '[]')).name, userface: window.localStorage.getItem('user' || '[]') == null ? '' : JSON.parse(window.localStorage.getItem('user' || '[]')).userface } }, mutations: { login(state, user){ state.user = user; window.localStorage.setItem('user', JSON.stringify(user)); }, logout(state){ window.localStorage.removeItem('user'); } } });
为了减小麻烦,用户登陆成功后的数据将被保存在localStorage中(防止用户按F5刷新以后数据丢失),以字符串的形式存入,取的时候再转为json。当用户注销登录时,将localStorage中的数据清除。数组
在权限管理模块中,这算是前端的核心了。服务器
用户在登陆成功以后,进入home主页以前,向服务端发送请求,要求获取当前的菜单信息和组件信息,服务端根据当前用户所具有的角色,以及角色所对应的资源,返回一个json字符串,格式以下:
[ { "id": 2, "path": "/home", "component": "Home", "name": "员工资料", "iconCls": "fa fa-user-circle-o", "children": [ { "id": null, "path": "/emp/basic", "component": "EmpBasic", "name": "基本资料", "iconCls": null, "children": [], "meta": { "keepAlive": false, "requireAuth": true } }, { "id": null, "path": "/emp/adv", "component": "EmpAdv", "name": "高级资料", "iconCls": null, "children": [], "meta": { "keepAlive": false, "requireAuth": true } } ], "meta": { "keepAlive": false, "requireAuth": true } } ]
前端在拿到这个字符串以后,作两件事:1.将json动态添加到当前路由中;2.将数据保存到store中,而后各页面根据store中的数据来渲染菜单。
核心思路并不难,下面咱们来看看实现步骤。
这个很重要。
可能会有小伙伴说这有何难,登陆成功以后请求不就能够了吗?是的,登陆成功以后,请求菜单资源是能够的,请求到以后,咱们将之保存在store中,以便下一次使用,可是这样又会有另一个问题,假如用户登陆成功以后,点击某一个子页面,进入到子页面中,而后按了一下F5进行刷新,这个时候就GG了,由于F5刷新以后store中的数据就没了,而咱们又只在登陆成功的时候请求了一次菜单资源,要解决这个问题,有两种思路:1.将菜单资源不要保存到store中,而是保存到localStorage中,这样即便F5刷新以后数据还在;2.直接在每个页面的mounted方法中,都去加载一次菜单资源。
因为菜单资源是很是敏感的,所以最好不要不要将其保存到本地,故舍弃方案1,可是方案2的工做量有点大,所以我采起办法将之简化,采起的办法就是使用路由中的导航守卫。
个人具体实现是这样的,首先在store中建立一个routes数组,这是一个空数组,而后开启路由全局守卫,以下:
router.beforeEach((to, from, next)=> { if (to.name == 'Login') { next(); return; } var name = store.state.user.name; if (name == '未登陆') { if (to.meta.requireAuth || to.name == null) { next({path: '/', query: {redirect: to.path}}) } else { next(); } } else { initMenu(router, store); next(); } } )
这里的代码很短,我来作一个简单的解释:
1.若是要去的页面是登陆页面,这个没啥好说的,直接过。
2.若是不是登陆页面的话,我先从store中获取当前的登陆状态,若是未登陆,则经过路由中meta属性的requireAuth属性判断要去的页面是否须要登陆,若是须要登陆,则跳回登陆页面,同时将要去的页面的path做为参数传给登陆页面,以便在登陆成功以后跳转到目标页面,若是不须要登陆,则直接过(事实上,本项目中只有Login页面不须要登陆);若是已经登陆了,则先初始化菜单,再跳转。
初始化菜单的操做以下:
export const initMenu = (router, store)=> { if (store.state.routes.length > 0) { return; } getRequest("/config/sysmenu").then(resp=> { if (resp && resp.status == 200) { var fmtRoutes = formatRoutes(resp.data); router.addRoutes(fmtRoutes); store.commit('initMenu', fmtRoutes); } }) } export const formatRoutes = (routes)=> { let fmRoutes = []; routes.forEach(router=> { let { path, component, name, meta, iconCls, children } = router; if (children && children instanceof Array) { children = formatRoutes(children); } let fmRouter = { path: path, component(resolve){ if (component.startsWith("Home")) { require(['../components/' + component + '.vue'], resolve) } else if (component.startsWith("Emp")) { require(['../components/emp/' + component + '.vue'], resolve) } else if (component.startsWith("Per")) { require(['../components/personnel/' + component + '.vue'], resolve) } else if (component.startsWith("Sal")) { require(['../components/salary/' + component + '.vue'], resolve) } else if (component.startsWith("Sta")) { require(['../components/statistics/' + component + '.vue'], resolve) } else if (component.startsWith("Sys")) { require(['../components/system/' + component + '.vue'], resolve) } }, name: name, iconCls: iconCls, meta: meta, children: children }; fmRoutes.push(fmRouter); }) return fmRoutes; }
在初始化菜单中,首先判断store中的数据是否存在,若是存在,说明此次跳转是正常的跳转,而不是用户按F5或者直接在地址栏输入某个地址进入的。不然就去加载菜单。拿到菜单以后,首先经过formatRoutes方法将服务器返回的json转为router须要的格式,这里主要是转component,由于服务端返回的component是一个字符串,而router中须要的倒是一个组件,所以咱们在formatRoutes方法中动态的加载须要的组件便可。数据格式准备成功以后,一方面将数据存到store中,另外一方面利用路由中的addRoutes方法将之动态添加到路由中。
最后,在Home页中,从store中获取菜单json,渲染成菜单便可,相关代码能够在Home.vue
中查看,不赘述。
OK,如此以后,不一样用户登陆成功以后就能够看到不一样的菜单了。
关注公众号,能够及时接收到最新文章: