动态加载权限管理模块中的Vue组件
本文我们主要来聊聊登录以及组件的动态加载。
登录状态保存
当用户登录成功之后,需要将当前用户的登录信息保存在本地,方便后面使用。具体实现如下:
登录成功保存数据
在登录操作执行成功之后,通过commit操作将数据提交到store中,核心代码如下:
this.postRequest('/login',{ username:this.loginForm.username, password:this.loginForm.password }).then(resp=>{ if(resp&&resp.status==200){ vardata=resp.data; _this.$store.commit('login',data.msg); varpath=_this.$route.query.redirect; _this.$router.replace({path:path=='/'||path==undefined?'/home':path}); } });
store
store的核心代码如下:
exportdefaultnewVuex.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":"fafa-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; } varname=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页面不需要登录);如果已经登录了,则先初始化菜单,再跳转。
初始化菜单的操作如下:
exportconstinitMenu=(router,store)=>{ if(store.state.routes.length>0){ return; } getRequest("/config/sysmenu").then(resp=>{ if(resp&&resp.status==200){ varfmtRoutes=formatRoutes(resp.data); router.addRoutes(fmtRoutes); store.commit('initMenu',fmtRoutes); } }) } exportconstformatRoutes=(routes)=>{ letfmRoutes=[]; routes.forEach(router=>{ let{ path, component, name, meta, iconCls, children }=router; if(children&&childreninstanceofArray){ children=formatRoutes(children); } letfmRouter={ path:path, component(resolve){ if(component.startsWith("Home")){ require(['../components/'+component+'.vue'],resolve) }elseif(component.startsWith("Emp")){ require(['../components/emp/'+component+'.vue'],resolve) }elseif(component.startsWith("Per")){ require(['../components/personnel/'+component+'.vue'],resolve) }elseif(component.startsWith("Sal")){ require(['../components/salary/'+component+'.vue'],resolve) }elseif(component.startsWith("Sta")){ require(['../components/statistics/'+component+'.vue'],resolve) }elseif(component.startsWith("Sys")){ require(['../components/system/'+component+'.vue'],resolve) } }, name:name, iconCls:iconCls, meta:meta, children:children }; fmRoutes.push(fmRouter); }) returnfmRoutes; }
在初始化菜单中,首先判断store中的数据是否存在,如果存在,说明这次跳转是正常的跳转,而不是用户按F5或者直接在地址栏输入某个地址进入的。否则就去加载菜单。拿到菜单之后,首先通过formatRoutes方法将服务器返回的json转为router需要的格式,这里主要是转component,因为服务端返回的component是一个字符串,而router中需要的却是一个组件,因此我们在formatRoutes方法中动态的加载需要的组件即可。数据格式准备成功之后,一方面将数据存到store中,另一方面利用路由中的addRoutes方法将之动态添加到路由中。
菜单渲染
最后,在Home页中,从store中获取菜单json,渲染成菜单即可,相关代码可以在Home.vue中查看,不赘述。
OK,如此之后,不同用户登录成功之后就可以看到不同的菜单了。