赞
踩
本文主要针对后台管理系统的权限问题,即不同权限对应着不同的路由,同时侧边栏的路由也需要根据权限的不同异步生成。我们知道,权限那肯定是对应用户的,那么就会涉及到用户登录模块,所以这里也简单说一下实现登录的和权限验证的思路。
由于这篇文章我主要是想讲讲和权限相关的,所以对于登录的部分详细的过程我这边就此略过哈,我只讲讲大概的过程,具体实现的话网上也会有很多的文章可供参考。
拿到相应用户的role是以用户登录为前提的,因此大致的登录流程如下:
经过以上的登录操作,我们就能拿到指定用户的token、用户role以及用户其他详细信息
先说一下我权限控制的主体思路,首先前端会先定义一份通用的路由,比如一些不需要权限控制的路由:Home、404、NotFount等等。然后用户登录之后,拿到用户的的信息,其中就有包括用户uid;前端拿到uid去向后台请求user_router_auth该uid下对应的路由,然后后台会根据这个uid去对比用户表中的数据,去判断该uid下有什么样的的权限,比如uid=2下的role=[3,4,2,5,6],然后这时候后天也会根据这个role去一一对比路由数据库中的id,然后进行过滤,最后返回含有role的路由对象。
这里使用的是koa搭建的服务器
简单举例给前端返回的数据router.js
- //router.js
- module.exports = [
- {
- id: 2,
- pid: 0,
- path: '/course',
- name: 'Course',
- title: '课程管理'
- },
- {
- id: 3,
- pid: 2,
- path: 'operate',
- name: 'CourseOperate',
- link: '/course/operate',
- title: '课程操作'
- },
- {
- id: 4,
- pid: 3,
- path: 'info_data',
- name: 'CourseInfoData',
- link: '/course/operate/info_data',
- title: '课程数据'
- },
- {
- id: 5,
- pid: 2,
- path: 'add',
- name: 'CourseAdd',
- link: '/course/add',
- title: '增加课程'
- },
- {
- id: 6,
- pid: 0,
- path: '/student',
- name: 'Student',
- title: '学生管理'
- },
- {
- id: 7,
- pid: 6,
- path: 'operate',
- name: 'StudentOperate',
- link: '/student/operate',
- title: '学生操作'
- },
- {
- id: 8,
- pid: 6,
- path: 'add',
- name: 'SudentAdd',
- link: '/student/add',
- title: '增加学生'
- },
- ];

user.js主要是后天用来根据前台请求中携带的uid进行映射判断有哪些权限的
- //user.js
-
- module.exports = [
- {
- id: 1,
- name: 'zhangsan',
- auth: [2,3,6,7]
- },
- {
- id: 2,
- name: 'lisi',
- auth: [2,3,5,6,7,8]
- },
- {
- id: 3,
- name: 'wangwu',
- auth: [2,3,4,5,6,7,8]
- }
- ]

前端请求后端路由接口的主要逻辑:
- const router = require('koa-router')()
- const users = require('../data/user');//存储用户所有用户数据id下对应的权限数据
- const routers = require('../data/router');//路由数据
- router.post('/user_router_auth', async (ctx, next) => {
- // 拿到前端进行该请求时候的参数,主要是uid
- const { uid } = ctx.request.body;
- // 当uid存在时
- if (uid) {
- //定义最后向前台返回最后的路由的容器
- let authRouterInfo = [];
- const userInfo = users.filter(user => user.id == uid)[0];//因为users是一个数组,所以拿返回的数组的第一项(最终过滤出来的项也只会有一个)比如:[2,3,4,5]
- //
- userInfo.auth.map((rid) => {
- // 拿到该uid对应的role数组之后,然后去跟router下的每一项得到id进行遍历判断
- routers.map((router) => {
- //只有当role中的id与router下的id相等的时候,将该routerpush到authRouterInfo中
- if (router.id === rid) {
- authRouterInfo.push(router);
- }
- })
- })
- ctx.body = authRouterInfo;
- } else {
- next();
- }
- })
-
- module.exports = router

这里先大致提一下前端实现的答题流程
怎么创建项目以及项目所需要的依赖等我就不在这里说明了,大家自行搭建哈。那么我们就进行主题部分的讲解。首先后天管理系统无疑是分为头部、侧边栏(导航项)、页面主体部分,因此我们可以在components目录下新建如下页面,用于我们后台管理的主体。
注意:由于页面主体部分是显示路由的主要入口,因此我们需要在这个页面上使用<router-view>来显示路由页面
我这里只是举例说明哈,所以我这里只是定义了两个不需要权限的路由,Home、NotFound页面,具体如下:router/index.js
- import Vue from 'vue'
- import VueRouter from 'vue-router'
- import Home from '@/views/Home.vue'
- Vue.use(VueRouter)
- const routes = [
- {
- path: '/',
- name:"Home",
- component: Home,
- },
- {
- path:'*',
- name:'NotFound',
- component:()=>import('@/views/NotFound.vue')
- }
- ]
- const router = new VueRouter({
- mode: 'history',
- base: process.env.BASE_URL,
- routes
- })
- export default router

在根目录下新建request/index.js文件,用于存储所有后台的请求(再次说明,由于项目比较简单,而且主要是理解动态路由的思想,这里的请求封装我就不进行演示了哈,直接用的axios),具体如下:
- import axios from "axios";
- import qs from 'qs'
- function getUserRouters(uid){
- return axios({
- // 对应koa服务器下的请求
- url:'http://localhost:3000/user_router_auth',
- method:'post',
- header:{
- 'Conten-type':'applicaton/x-wwww-form-urlencoded'
- },
- data:qs.stringify({uid})
- }).then((res)=>{
- // 拿到数据直接返回
- return res.data
- }).catch((err)=>{
- throw err;
- })
- }
- export {
- getUserRouters
- }

在这个demo下,主要是使用store来存储数据的,包括用户uid以及路由,因此需要配置如下文件,
我们都知道,在Vuex中,state主要用来存储数据状态的,mutations是用来改变state数据状态的,actions是用来派发事件的,即是通知mutations进行数据的更新,下面先说明一下state和mutations的具体
store/index.js内容如下:
- import Vue from 'vue'
- import Vuex from 'vuex'
- import state from './state'
- import actions from './actions'
- import mutations from './mutations'
-
- Vue.use(Vuex)
-
- export default new Vuex.Store({
- state,
- actions,
- mutations
- })
由于在这个demo中,我们需要存储到全局的数据有:用户登录之后的id、用户是都有权限的判断、该用户权限下的路由tree,因此在index/state.js中存储数据如下:
- export default{
- uid:2,//userId
- hasAuth:false,//是否有权限
- userRouters:[]//权限树
- }
这里uid为什么是2呢?我在前文也有提及到,我这里不进行用户登录的演示,这个uid你可以给个初始值null也行,到时候你登录之后将后台返回的id存到这个state.uid下即可
这个demo中我们只需要维护三个state数据状态,因此在mutation中也是对应三个更新state的mutation_type ,store/mutaions.js内容主要如下:
- export default{
- setUserRouter(state,userRouters){
- state.userRouters=userRouters
- },
- setAuth(state,hasAuth){
- state.hasAuth=hasAuth
- },
- setUid(state,uid){
- state.uid=uid
- }
- }
紧接着是actions,在actions中,我们主要是进行异步请求,向后台请求数据,然后对后台返回的数据进行进一步的处理,然后将处理后的数据commit通知给mutaions进行state更新,进而我们可以通过$store.state.xx访问到最新的state数据状态。
这里主要演示向后台请求路由表数据,然后进行格式化最后通知mutaions进行更新的步骤
一开始,发送请求之后得到的数据,这里的数据是扁平化,我们需要将其格式化为路由Tree的形式:
因此这里封装了格式化后台返回数据的方法lib/util.js
- //将后端返回的扁平的结果格式化成树形结构的数据
- function formatRouterTree(data){
- let parants = data.filter(p=>p.pid==0),
- children = data.filter(p=>p.pid!==0)
- dataToTree(parants,children)
- function dataToTree(parants,children){
- // 这里遍历父路由和子路由,对比pid与id
- parants.map((p)=>{
- children.map((c,i)=>{
- if(p.id===c.pid){
- // 则证明此时遍历的c则是p的子路由
- //这里需要再次递归,因为c下面可能还会有其子路由
- //先将children进行深拷贝一份
- let _c=JSON.parse(JSON.stringify(children))
- //由于c要做父级路由,所以要先将其删除,则_c里面就不会有我原本遍历的东西了
- _c.splice(i,1)//当当前的c删除
- // 将当前的c作为父级[c]然后与剩下的children作比较
- dataToTree([c],_c)
- // 若之前已经有添加过children项了,则直接而push
- if(p.children){
- p.children.push(c)
- }else {
- // 之前没有添加过,则直接将这个子元素=p.children
- // 表示第一次添加
- p.children=[c]
- }
- }
- })
- })
- }
- return parants
- }
-
-

格式化的结果:
第一个方法formatRouterTree格式化的数据类型如下:
store/actions.js
- import {getUserRouters} from '../request/index.js'
- import {formatRouterTree} from '../lib/util'
- export default{
- async setUserRouters({commit,state}){
- //根据用户登录之后的uid想后台请求路由数据
- const userRouters= await getUserRouters(state.uid)
- // 接下来将要处理这个router使得其变为树形结构
- const payload=formatRouterTree(userRouters)
- console.log('后台返回的数据',generateRouter(payload))
- //将格式化后的数据通知mutations进行更新
- commit('setUserRouter',payload)
- //有上面这个路由tree同时也说明有权限了
- commit('setAuth',true)
- }
- }
经过以上,得到路由tree数据,且你会发现目前为止,还有进行router.addRouters的操作。其中有两个主要原因。一是我们在actions中只对数据进行转成tree的操作,对于router.addRouters()的操作一般是放在permission中,即是路由拦截,但是由于这个demo比较简单,因此这里是先放在main.js中。
在进行router.addRouters()之前,我们也需要封装另外一个方法,即是将上面的路由tree格式化成我们router需要的格式
第二个方法generateRouter格式化后的数据形式,在ilb/util.js中再添加如下方法:
- /**
- * 将上面的树形treeRouter改变成我们想要的路由形式,有过滤操作,保留我们想要的
- * 最终会被加载到router中:router.addRouters()
- * [{path:'',name:'',component:'',children:[{}]}]
- *
- */
- function generateRouter(userRouters){
- let newRouter=userRouters.map((item)=>{
- let routes={
- path:item.path,
- name:item.name,
- component:()=>import(`@/views/${item.name}`)
- }
- if(item.children){
- routes.children=generateRouter(item.children)
- }
- //由于递归之后又会返回一个routes,放到children中
- return routes
- })
- return newRouter
- }
-
- export{
- formatRouterTree,
- generateRouter
- }

最后,我们可以在main.js中对我们的路由进行拦截,根据你自己的逻辑去判断进行相应的处理,这个demo主要是首选判断用户有没有hasAuth权限(这里我们在上面actions中已经固定写死了commit('setAuth',true)你可以根据你的业务来定),然后dispatch异步actions获得路由,并在actions中初步格式化,最后拿到这个state中的数据做最后的格式化变成我们想要的路由tree,最后添加到路由表中router.addRoutes(),然后用户就可以在url栏上输入相应的path进行路由访问(当然前提是你有hasAuth权限,如果没有权限这边会给你返回NotFound页面)
- import Vue from 'vue'
- import App from './App.vue'
- import router from './router'
- import './assets/css/common.css'
- import store from './store/index'
- import {generateRouter} from './lib/util'
- // 路由前卫
- router.beforeEach(async (to,form,next)=>{
- if(!store.state.hasAuth){
- //dispatch action的异步操作,在这异步操作中已经同时commit给mutaions进行更新state的处理
- await store.dispatch('setUserRouters')
- //接着我们便可以拿到state.userRouters数据进行下一步的格式化,变成我们真正想要的路由tree
- const newRoutes=generateRouter(store.state.userRouters);
- //这时候可以使用router中的api将其添加到router中
- router.addRoutes(newRoutes)
- // 有了权限之后,用户点击其相对应的路由则放行
- next({path:to.path})
- }else{
- //无权限的情况下则不用去请求
- next()
- }
- })
- Vue.config.productionTip = false
- new Vue({
- router,
- store,
- render: h => h(App)
- }).$mount('#app')

侧边栏主要的设置如下,详细的说明也以及注释待代码中,有兴趣可以看一下哈,主要是在MenuItem页面中用到递归的思想
SiderBar.vue页面如下:
- <template>
- <div class="side-bar">
- <!-- 首页不需要权限,所有用户都可以访问 -->
- <ul>
- <li>
- <router-link to="/">首页</router-link>
- </li>
- </ul>
- <!-- 下面则是动态遍历有权限的路由,进行渲染 -->
- <template v-for="(item,index) of $store.state.userRouters">
- <!--MenuItem会拿到每一个item进行遍历 -->
- <MenuItem :item="item" :key="index"> </MenuItem>
- </template>
-
- </div>
- </template>
-
- <script>
- // import store from '@/store'
- import MenuItem from './MenuItem.vue'
- export default {
- name:'SiderBar',
- data(){
- return{
-
- }
- },
- components:{
- MenuItem
- },
- mounted(){
- // console.log('嘿嘿',this.$store.state.userRouters)
- }
- }
- </script>
-
- <style>
- .side-bar{
- position: fixed;
- width: 200px;
- left: 0;
- top: 0;
- height: 100%;
- /* 因为上面头部60,然后需要给个内边距 */
- padding-top:90px;
- z-index: 1;
- box-sizing: border-box;
- background-color: #ededed;
- }
- </style>

MenuItem.vue页面主要如下
- <template>
- <div>
- <!-- 渲染有children的多级路由 -->
- <ul v-if="item.children && item.children.length>0">
- <li>
- <!-- 这里再判断一层的原因是后端最外层的路由没有返回item.link,那么可以使用path替代,如果有返回则不需要 -->
- <router-link :to="item.link || item.path">{{item.title}}</router-link>
- <!-- 遍历的思想,再次遍历item下的children -->
- <template v-for="(v,i) in item.children">
- <MenuItem :item="v" :key="i"></MenuItem>
- </template>
- </li>
- </ul>
- <!-- 没有children的路由则直接显示 -->
- <ul v-else>
- <router-link :to="item.link || item.path">{{item.title}}</router-link>
- </ul>
- </div>
- </template>
- <script>
- export default {
- name:'MenuItem',
- data(){
- return {
-
- }
- },
- props:{
- item:Object
- },
- }
- </script>
-
- <style lang="" scoped>
-
- </style>

这里我需要说明一点哈,路由页面所属目录主要是看你自己是怎么定义的路径的,比如这里是直接统一定在view目录下,想必然这样肯定是不好的,那么你可以自己进行扩展,比如在外面相应放一层父级目录,在该目录下放其所有的子路由页面,你在这里components自己定义好路径即可
当uid2=时,其拥有的权限是role:[2,3,5,6,7,8],对应如下:
当uid=3时,其拥有的权限role:[2,3,4,5,6,7,8],对应如下:
当uid=1时,其拥有的权限role:auth: [2,3,6,7],对应如下:
以上便是《前后端动态路由权限》的所有内容啦,如果文章中有错误的地方,欢迎大家指正相互讨论与学习,当然,如果对这部分内容源码感兴趣的朋友,也可以滴滴我给你发哈~
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。