精华内容
下载资源
问答
  • 后端控制路由

    2020-09-03 10:12:12
    后端控制路由 本篇文章是基于vue-element-admin的权限路由基础之上来做这个后端控制路由,也是因为项目需求故做的这个,话不多说,直接上代码 1.要想实现完全做到由后端控制路由 菜单显示,后台的小伙伴需要提供的接口有...

    后端控制路由

    本篇文章是基于vue-element-admin的权限路由基础之上来做这个后端控制路由,也是因为项目需求故做的这个,话不多说,直接上代码

    1.要想实现完全做到由后端控制路由 菜单显示,后台的小伙伴需要提供的接口有

    获取当前登录用户的信息接口
    获取当前用户所拥有权限菜单
    备注:当然这两个接口可以写成一个,这样也可以的,直接将用户所拥有的菜单直接返回到获取用户信息的接口里面(当然我们后端是直接把这些数据写成了一个接口的,哈哈)

    接口中需要包含的数据如下

    path
    name/title
    icon
    id
    parentId
    component
    

    2.代码:找到store文件夹下modules文件夹下的user.js,修改代码如下

    const getDefaultState = () => {
      return {
        token: getToken(),
        name: '',
        avatar: '',
        roles: [],
        onePermissionList: [] 
      }
    }
    
    const state = getDefaultState()
    
    const mutations = {
      RESET_STATE: (state) => {
        Object.assign(state, getDefaultState())
      },
      SET_TOKEN: (state, token) => {
        state.token = token
      },
      SET_NAME: (state, name) => {
        state.name = name
      },
      SET_AVATAR: (state, avatar) => {
        state.avatar = avatar
      },
      SET_ROLES: (state, roles) => {
        state.roles = roles
      },
      SET_onePermissionList: (state, onePermissionList) => {
        state.onePermissionList = onePermissionList
      }
    }
    
    const actions = {
      // user login
      login ({ commit }, userInfo) {
        const { username, password } = userInfo
        return new Promise((resolve, reject) => {
          login({ username: username.trim(), password: password }).then(response => {
            const { data } = response
            commit('SET_TOKEN', data.token)
            setToken(data.token)
            resolve()
          }).catch(error => {
            reject(error)
          })
        })
      },
    
      // get user info
      getInfo ({ commit, state }) {
        return new Promise((resolve, reject) => {
          getInfo().then(response => {
            const { data } = response
            commit('SET_NAME', nickname)
            commit('SET_AVATAR', avatar)
            commit('SET_onePermissionList', permissions) //在这里拿到后台返回的权限列表
            resolve(data)
          }).catch(error => {
            reject(error)
          })
        })
      }
    }
    
    export default {
      namespaced: true,
      state,
      mutations,
      actions
    }
    

    3.代码:找到与根目录同级的permission.js,修改代码如下

    router.beforeEach(async (to, from, next) => {
      NProgress.start()
      document.title = getPageTitle(to.meta.title)
    
      const hasToken = getToken()
      if (hasToken) {
        if (to.path === '/login') {
          next({ path: '/' })
          NProgress.done() 
        } else {
          const hasGetUserInfo = store.getters.name
          // console.log(store.getters.name)
          if (hasGetUserInfo) {
            next()
          } else {
            try {
              await store.dispatch('user/getInfo')  //获取用户信息
              const onePermissionList = store.getters.onePermissionList  //拿到后台给到的权限菜单
              const accessRoutes = await store.dispatch('permission/generateRoutes', onePermissionList)
              // 动态添加可访问路由表
              router.addRoutes(accessRoutes)
              next({ ...to, replace: true })
            } catch (error) {
              await store.dispatch('user/resetToken')
              Message.error(error || 'Has Error')
              next(`/login?redirect=${to.path}`)
              NProgress.done()
            }
          }
        }
      } else {
        /* has no token*/
        if (whiteList.indexOf(to.path) !== -1) {
          next()
        } else {
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    })
    

    4.代码:找到store文件夹下modules文件夹下的permission.js,修改代码如下:

    export function arrayToMenu (array) {
      // console.log('array++++', array)
      const nodes = []
      // 获取父级菜单
      for (let i = 0; i < array.length; i++) {
        const row = array[i]
        // exists方法用来判断有没有二级菜单
        if (!exists(array, row.parentId)) {
          nodes.push({
            path: row.path,
            component: Layout, 
            name: row.name, 
            alwaysShow: true,
            meta: { title: row.name, icon: row.icon }, 
            id: row.id 
          })
        }
      }
      const toDo = Array.from(nodes)
      while (toDo.length) {
        const node = toDo.shift()
        // 获取二级菜单
        for (let i = 0; i < array.length; i++) {
          const row = array[i]
          // 判断一下二级菜单应该在哪个一级菜单下
          if (row.parentId === node.id) {
            const child = {
              path: row.path,
              name: row.name,
              // component判断
              component: require('@/views/' + row.component),
              meta: { title: row.name, icon: row.icon},
              id: row.id
            }
            if (node.children) {
              node.children.push(child)
            } else {
              node.children = [child]
            }
            toDo.push(child)
          }
        }
      }
      console.log(nodes)
      return nodes
    }
    // 判断一下是否有二级菜单
    function exists (rows, parentId) {
      for (let i = 0; i < rows.length; i++) {
        if (rows[i].id === parentId) return true
      }
      return false
    }
    
    const state = {
      routes: [],
      addRoutes: []
    }
    
    const mutations = {
      SET_ROUTES: (state, routes) => {
        state.addRoutes = routes
        state.routes = constantRoutes.concat(routes)
      }
    }
    
    const actions = {
      generateRoutes ({ commit }, roles) {
        // console.log('roles++++', roles)
        return new Promise(resolve => {
          const accessedRoutes = arrayToMenu(roles)
          accessedRoutes.concat([{ path: '*', redirect: '/404', hidden: true }])
          commit('SET_ROUTES', accessedRoutes)
          resolve(accessedRoutes)
        })
      }
    }
    
    export default {
      namespaced: true,
      state,
      mutations,
      actions
    }
    

    总结

    以上就是我来做这个后端控制路由的具体代码实现。代码还有很多值得优化的地方,欢迎各位大佬批评指正。

    展开全文
  • 为什么设置动态路由 我们在开发的过程中,会有不同的人来操作系统...后端控制 1、相对更安全一点 2、路由表维护在数据库 一、前端控制 通过路由的meta属性role来控制加载路由 具体方法是: 1、根据登录用户...

     

    为什么设置动态路由

    我们在开发的过程中,会有不同的人来操作系统,有admin(管理员)、superAdmin(超管),还会有各种操作员、普通管理员。为了区别这些人员,我们会给不同的人分配不一样的角色,从而来展示不同的菜单,这个就必须要通过动态路由来实现。

    前端控制

    1、不用后端帮助,路由表维护在前端
    2、逻辑相对比较简单,比较容易上手

    后端控制

    1、相对更安全一点
    2、路由表维护在数据库

    一、前端控制

    通过路由的meta属性role来控制加载路由

    具体方法是:

    1、根据登录用户的账号,返回前端用户的角色
    
    2、前端根据用户的角色,跟路由表的meta.role进行匹配
    
    3、讲匹配到的路由形成可访问路由

    具体的代码逻辑:

    1、把静态路由和动态路由分别写在router.js
    
    2、在vuex维护一个state,通过配角色来控制菜单显不显示
    
    3、新建一个路由守卫函数,可以在main.js,也可以抽离出来一个文件
    
    4、侧边栏的可以从vuex里面取数据来进行渲染

    核心代码:

    1、在router.js文件(把静态路由和动态路由分别写在router.js)

    import Vue from 'vue'
    import Router from 'vue-router'
    
    Vue.use(Router)
    
    import Layout from '@/layout'
    
    // constantRoutes  静态路由,主要是登录页、404页等不需要动态的路由
    
    export const constantRoutes = [
      {
        path: '/redirect',
        component: Layout,
        hidden: true,
        children: [
          {
            path: '/redirect/:path*',
            component: () => import('@/views/redirect/index')
          }
        ]
      },
      {
        path: '/login',
        component: () => import('@/views/login/index'),
        hidden: true
      },
      {
        path: '/404',
        component: () => import('@/views/error-page/404'),
        hidden: true
      },
      {
        path: '/401',
        component: () => import('@/views/error-page/401'),
        hidden: true
      }
    ]
    
    // asyncRoutes 动态路由
    export const asyncRoutes = [
      {
        path: '/permission',
        component: Layout,
        redirect: '/permission/page',
        alwaysShow: true, 
        name: 'Permission',
        meta: {
          title: 'Permission',
          icon: 'lock',
          // 核心代码,可以通过配的角色来进行遍历,从而是否展示
          // 这个意思就是admin、editor这两个角色,这个菜单是可以显示
          roles: ['admin', 'editor']
        },
        children: [
          {
            path: 'page',
            component: () => import('@/views/permission/page'),
            name: 'PagePermission',
            meta: {
              title: 'Page Permission',
              // 这个意思就是只有admin能展示
              roles: ['admin']
            }
          }
         ]
        }
    ]
    
    const createRouter = () => new Router({
      scrollBehavior: () => ({ y: 0 }),
      routes: constantRoutes
    })
    
    const router = createRouter()
    
    // 这个是重置路由用的,很有用,别看这么几行代码
    export function resetRouter() {
      const newRouter = createRouter()
      router.matcher = newRouter.matcher 
    }
    
    export default router

    2、store/permission.js(在vuex维护一个state,通过配角色来控制菜单显不显示)

    import { asyncRoutes, constantRoutes } from '@/router'
    
    // 这个方法是用来把角色和route.meta.role来进行匹配
    function hasPermission(roles, route) {
      if (route.meta && route.meta.roles) {
        return roles.some(role => route.meta.roles.includes(role))
      } else {
        return true
      }
    }
    
    
    // 这个方法是通过递归来遍历路由,把有权限的路由给遍历出来
    export function filterAsyncRoutes(routes, roles) {
      const res = []
    
      routes.forEach(route => {
        const tmp = { ...route }
        if (hasPermission(roles, tmp)) {
          if (tmp.children) {
            tmp.children = filterAsyncRoutes(tmp.children, roles)
          }
          res.push(tmp)
        }
      })
    
      return res
    }
    
    const state = {
      routes: [],
      addRoutes: []
    }
    
    const mutations = {
      SET_ROUTES: (state, routes) => {
        // 这个地方维护了两个状态一个是addRouters,一个是routes
        state.addRoutes = routes
        state.routes = constantRoutes.concat(routes)
      }
    }
    
    const actions = {
      generateRoutes({ commit }, roles) {
        return new Promise(resolve => {
          let accessedRoutes
          if (roles.includes('admin')) {
            accessedRoutes = asyncRoutes || []
          } else {
            // 核心代码,把路由和获取到的角色(后台获取的)传进去进行匹配
            accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
          }
          // 把匹配完有权限的路由给set到vuex里面
          commit('SET_ROUTES', accessedRoutes)
          resolve(accessedRoutes)
        })
      }
    }
    
    export default {
      namespaced: true,
      state,
      mutations,
      actions
    }

    3、src/permission.js(新建一个路由守卫函数,可以在main.js,也可以抽离出来一个文件)

    这里面的代码主要是控制路由跳转之前,先查一下有哪些可访问的路由,登录以后跳转的逻辑可以在这个地方写

    // permission.js
    router.beforeEach((to, from, next) => {
      if (store.getters.token) { // 判断是否有token
        if (to.path === '/login') {
          next({ path: '/' });
        } else {
            // 判断当前用户是否已拉取完user_info信息
          if (store.getters.roles.length === 0) {
            store.dispatch('GetInfo').then(res => { // 拉取info
              const roles = res.data.role;
              // 把获取到的role传进去进行匹配,生成可以访问的路由
              store.dispatch('GenerateRoutes', { roles }).then(() => { 
                // 动态添加可访问路由表(核心代码,没有它啥也干不了)
                router.addRoutes(store.getters.addRouters)
    
                // hack方法 确保addRoutes已完成
                next({ ...to, replace: true })
              })
            }).catch(err => {
              console.log(err);
            });
          } else {
            next() //当有用户权限的时候,说明所有可访问路由已生成 如访问没权限的全面会自动进入404页面
          }
        }
      } else {
        if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
          next();
        } else {
          next('/login'); // 否则全部重定向到登录页
        }
      }
    })

    4、侧边栏的可以从vuex里面取数据来进行渲染

    核心代码是从router取可以用的路由对象,来进行侧边栏的渲染,不管是前端动态加载还是后端动态加载路由,这个代码都是一样的

    layout/components/siderbar/index.vue

    <el-menu
    :default-active="activeMenu"
    :collapse="isCollapse"
    :background-color="variables.menuBg"
    :text-color="variables.menuText"
    :unique-opened="false"
    :active-text-color="variables.menuActiveText"
    :collapse-transition="false"
    mode="vertical"
    >
        // 把取到的路由进行循环作为参数传给子组件
        <sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" />
    </el-menu>
    // 获取有权限的路由
    routes() {
      return this.$router.options.routes
    }

    layout/components/siderbar/siderbarItem.vue

    <template slot="title">
        <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
      </template>
      <sidebar-item
        v-for="child in item.children"
        :key="child.path"
        :is-nest="true"
        :item="child"
        :base-path="resolvePath(child.path)"
        class="nest-menu"
      />
    
      props: {
        // route object
        item: {
          type: Object,
          required: true
        },
        isNest: {
          type: Boolean,
          default: false
        },
        basePath: {
          type: String,
          default: ''
        }
      }

    前端控制路由,逻辑相对简单,后端只需要存这个用户的角色就可以了,前端拿用户的角色进行匹配。但是如果新增角色,就会非常痛苦,每一个都要加。

    二、后端控制路由

    具体方法是:

    1、用户登录以后,后端根据该用户的角色,直接生成可访问的路由数据,注意这个地方是数据
    
    2、前端根据后端返回的路由数据,转成自己需要的路由结构

    具体的代码逻辑:

    1、router.js里面只放一些静态的路由,login、404之类
    
    2、整理一份数据结构,存到表里
    
    3、从后端获取路由数据,写一个数据转换的方法,讲数据转成可访问的路由
    
    4、也是维护一个vuex状态,将转换好的路由存到vuex里面
    
    5、侧边栏也是从路由取数据进行渲染

    因为前段控制和后端控制,后面的流程大部分都是一样的,所以这个地方只看看前面不一样的流程

    1、store/permission.js,在vuex里面发送请求获取数据

    GenerateRoutes({ commit }, data) {
      return new Promise((resolve, reject) => {
        getRoute(data).then(res => {
         // 将获取到的数据进行一个转换,然后存到vuex里
          const accessedRouters = arrayToMenu(res.data)
          accessedRouters.concat([{ path: '*', redirect: '/404', hidden: true }])
          commit('SET_ROUTERS', accessedRouters)
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    }

    2、整理一份数据结构,存到表里

    我们知道vue的router规定的数据结构是这样的:

    {
        path: '/form',
        component: Layout,
        children: [
          {
            path: 'index',
            name: 'Form',
            component: () => import('@/views/form/index'),
            meta: { title: 'Form', icon: 'form' }
          }
        ]
    }

    所以,一级菜单有几个参数必须要有:id、path、name、component、title,二级菜单children是一个数组,是子父级的关系,所以可以给加一个fid或者parentId,来进行匹配,后面写转换方法的时候会详细解释,数据格式大概就是这样:

    // 一级菜单
    // parentId为0的就可以当做一级菜单,id最好是可以选4位数,至于为什么等你开发项目的时候就知道了
    {
        id: 1300
        parentId: 0
        title: "企业管理"
        path: "/enterprise"
        hidden: false
        component: null
        hidden: false
        name: "enterprise"
    },
    
    // 二级菜单
    // parentId不为0的,就可以拿parentId跟一级菜单的id去匹配,匹配上的就push到children里面
    {
        id: 1307
        parentId: 1300
        title: "商户信息"
        hidden: false
        path: "merchantInfo"
        component: "enterprise/merchantInfo" // 要跟本地的文件地址匹配上
        hidden: false
        name: "merchantInfo"
    }

    3、写一个转化方法,把获取到的数据转换成router结构

    刚才获取到的数据无法直接转成router进行渲染,需要一个arrayToMenu的方法,刚才也说了一些思路,下面就一起分析下这个方法:

    export function arrayToMenu(array) {
      const nodes = []
      // 获取顶级节点
      for (let i = 0; i < array.length; i++) {
        const row = array[i]
        // 这个exists方法就是判断下有没有子级
        if (!exists(array, row.parentId)) {
          nodes.push({
            path: row.path, // 路由地址
            hidden: row.hidden, // 全部携程true就行,如果后端没配
            component: Layout, // 一般就是匹配你文件的component
            name: row.name, // 路由名称
            meta: { title: row.title, icon: row.name }, // title就是显示的名字
            id: row.id, // 路由的id
            redirect: 'noredirect'
          })
        }
      }
      const toDo = Array.from(nodes)
      while (toDo.length) {
        const node = toDo.shift()
        // 获取子节点
        for (let i = 0; i < array.length; i++) {
          const row = array[i]
          // parentId等于哪个父级的id,就push到哪个
          if (row.parentId === node.id) {
            const child = {
              path: row.path,
              name: row.name,
              hidden: row.hidden,
              // 核心代码,因为二级路由的component是需要匹配页面的
              component: require('@/views/' + row.component + '/index.vue'),
              meta: { title: row.title, icon: row.name },
              id: row.id
            }
            if (node.children) {
              node.children.push(child)
            } else {
              node.children = [child]
            }
            toDo.push(child)
          }
        }
      }
      return nodes
    }
    // 看下有没有子级
    function exists(rows, parentId) {
      for (let i = 0; i < rows.length; i++) {
        if (rows[i].id === parentId) return true
      }
      return false
    }
    
     

    侧边栏的代码跟静态的代码是一样的

    展开全文
  • 关于后台管理系统的路由,想花一点时间,彻底的整理一份实现动态路由的点点滴滴。首先声明,这篇文章是基于花裤衩大神的《手摸手,带你用vue撸后台》,在他项目的基础上,帮助想要实现动态路由的小伙伴,来写的一篇...

    1817f4812f865bf3b4460ad3e8036989.png

    关于后台管理系统的路由,想花一点时间,彻底的整理一份实现动态路由的点点滴滴。

    首先声明,这篇文章是基于花裤衩大神的《手摸手,带你用vue撸后台》,在他项目的基础上,帮助想要实现动态路由的小伙伴,来写的一篇使用笔记。

    https://segmentfault.com/a/1190000009506097#item-3

    为什么要实现动态路由?

    我们在开发后台管理系统的过程中,会有不同的人来操作系统,有admin(管理员)、superAdmin(超管),还会有各种运营人员、财务人员。为了区别这些人员,我们会给不同的人分配不一样的角色,从而来展示不同的菜单,这个就必须要通过动态路由来实现。

    主流的实现方式:

    简单聊一下两种方式的优势,毕竟如果你从来没做过,说再多也看不明白,还是得看代码

    前端控制

    1、不用后端帮助,路由表维护在前端
    2、逻辑相对比较简单,比较容易上手

    2、后端控制

    1、相对更安全一点
    2、路由表维护在数据库

    一、前端控制

    花裤衩大神的方案是前端控制,他的核心是通过路由的meta属性,通过role来控制路由的加载。具体的实现方案:

    1、根据登录用户的账号,返回前端用户的角色
    
    2、前端根据用户的角色,跟路由表的meta.role进行匹配
    
    3、讲匹配到的路由形成可访问路由

    具体的代码逻辑:

    1、把静态路由和动态路由分别写在router.js
    
    2、在vuex维护一个state,通过配角色来控制菜单显不显示
    
    3、新建一个路由守卫函数,可以在main.js,也可以抽离出来一个文件
    
    4、侧边栏的可以从vuex里面取数据来进行渲染

    核心代码一共四个文件,基本都会加上源码和解释:

    1、在router.js文件(把静态路由和动态路由分别写在router.js)

    import Vue from 'vue'
    import Router from 'vue-router'
    
    Vue.use(Router)
    
    import Layout from '@/layout'
    
    // constantRoutes  静态路由,主要是登录页、404页等不需要动态的路由
    
    export const constantRoutes = [
      {
        path: '/redirect',
        component: Layout,
        hidden: true,
        children: [
          {
            path: '/redirect/:path*',
            component: () => import('@/views/redirect/index')
          }
        ]
      },
      {
        path: '/login',
        component: () => import('@/views/login/index'),
        hidden: true
      },
      {
        path: '/404',
        component: () => import('@/views/error-page/404'),
        hidden: true
      },
      {
        path: '/401',
        component: () => import('@/views/error-page/401'),
        hidden: true
      }
    ]
    
    // asyncRoutes 动态路由
    export const asyncRoutes = [
      {
        path: '/permission',
        component: Layout,
        redirect: '/permission/page',
        alwaysShow: true, 
        name: 'Permission',
        meta: {
          title: 'Permission',
          icon: 'lock',
          // 核心代码,可以通过配的角色来进行遍历,从而是否展示
          // 这个意思就是admin、editor这两个角色,这个菜单是可以显示
          roles: ['admin', 'editor']
        },
        children: [
          {
            path: 'page',
            component: () => import('@/views/permission/page'),
            name: 'PagePermission',
            meta: {
              title: 'Page Permission',
              // 这个意思就是只有admin能展示
              roles: ['admin']
            }
          }
         ]
        }
    ]
    
    const createRouter = () => new Router({
      scrollBehavior: () => ({ y: 0 }),
      routes: constantRoutes
    })
    
    const router = createRouter()
    
    // 这个是重置路由用的,很有用,别看这么几行代码
    export function resetRouter() {
      const newRouter = createRouter()
      router.matcher = newRouter.matcher 
    }
    
    export default router

    2、store/permission.js(在vuex维护一个state,通过配角色来控制菜单显不显示)

    import { asyncRoutes, constantRoutes } from '@/router'
    
    // 这个方法是用来把角色和route.meta.role来进行匹配
    function hasPermission(roles, route) {
      if (route.meta && route.meta.roles) {
        return roles.some(role => route.meta.roles.includes(role))
      } else {
        return true
      }
    }
    
    
    // 这个方法是通过递归来遍历路由,把有权限的路由给遍历出来
    export function filterAsyncRoutes(routes, roles) {
      const res = []
    
      routes.forEach(route => {
        const tmp = { ...route }
        if (hasPermission(roles, tmp)) {
          if (tmp.children) {
            tmp.children = filterAsyncRoutes(tmp.children, roles)
          }
          res.push(tmp)
        }
      })
    
      return res
    }
    
    const state = {
      routes: [],
      addRoutes: []
    }
    
    const mutations = {
      SET_ROUTES: (state, routes) => {
        // 这个地方维护了两个状态一个是addRouters,一个是routes
        state.addRoutes = routes
        state.routes = constantRoutes.concat(routes)
      }
    }
    
    const actions = {
      generateRoutes({ commit }, roles) {
        return new Promise(resolve => {
          let accessedRoutes
          if (roles.includes('admin')) {
            accessedRoutes = asyncRoutes || []
          } else {
            // 核心代码,把路由和获取到的角色(后台获取的)传进去进行匹配
            accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
          }
          // 把匹配完有权限的路由给set到vuex里面
          commit('SET_ROUTES', accessedRoutes)
          resolve(accessedRoutes)
        })
      }
    }
    
    export default {
      namespaced: true,
      state,
      mutations,
      actions
    }

    3、src/permission.js(新建一个路由守卫函数,可以在main.js,也可以抽离出来一个文件)

    这里面的代码主要是控制路由跳转之前,先查一下有哪些可访问的路由,登录以后跳转的逻辑可以在这个地方写

    // permission.js
    router.beforeEach((to, from, next) => {
      if (store.getters.token) { // 判断是否有token
        if (to.path === '/login') {
          next({ path: '/' });
        } else {
            // 判断当前用户是否已拉取完user_info信息
          if (store.getters.roles.length === 0) {
            store.dispatch('GetInfo').then(res => { // 拉取info
              const roles = res.data.role;
              // 把获取到的role传进去进行匹配,生成可以访问的路由
              store.dispatch('GenerateRoutes', { roles }).then(() => { 
                // 动态添加可访问路由表(核心代码,没有它啥也干不了)
                router.addRoutes(store.getters.addRouters)
    
                // hack方法 确保addRoutes已完成
                next({ ...to, replace: true })
              })
            }).catch(err => {
              console.log(err);
            });
          } else {
            next() //当有用户权限的时候,说明所有可访问路由已生成 如访问没权限的全面会自动进入404页面
          }
        }
      } else {
        if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
          next();
        } else {
          next('/login'); // 否则全部重定向到登录页
        }
      }
    })

    4、侧边栏的可以从vuex里面取数据来进行渲染

    核心代码是从router取可以用的路由对象,来进行侧边栏的渲染,不管是前端动态加载还是后端动态加载路由,这个代码都是一样的

    layout/components/siderbar/index.vue

    <el-menu
    :default-active="activeMenu"
    :collapse="isCollapse"
    :background-color="variables.menuBg"
    :text-color="variables.menuText"
    :unique-opened="false"
    :active-text-color="variables.menuActiveText"
    :collapse-transition="false"
    mode="vertical"
    >
        // 把取到的路由进行循环作为参数传给子组件
        <sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" />
    </el-menu>
    // 获取有权限的路由
    routes() {
      return this.$router.options.routes
    }

    layout/components/siderbar/siderbarItem.vue

    <template slot="title">
        <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
      </template>
      <sidebar-item
        v-for="child in item.children"
        :key="child.path"
        :is-nest="true"
        :item="child"
        :base-path="resolvePath(child.path)"
        class="nest-menu"
      />
    
      props: {
        // route object
        item: {
          type: Object,
          required: true
        },
        isNest: {
          type: Boolean,
          default: false
        },
        basePath: {
          type: String,
          default: ''
        }
      }

    前端控制路由,逻辑相对简单,后端只需要存这个用户的角色就可以了,前端拿用户的角色进行匹配。但是如果新增角色,就会非常痛苦,每一个都要加。

    二、后端控制路由

    后端控制路由是大部分后台管理系统的解决方案,我们公司也是通过这种方法管理路由的。具体的思路是这样的:

    1、用户登录以后,后端根据该用户的角色,直接生成可访问的路由数据,注意这个地方是数据
    
    2、前端根据后端返回的路由数据,转成自己需要的路由结构

    具体的代码逻辑:

    1、router.js里面只放一些静态的路由,login、404之类
    
    2、整理一份数据结构,存到表里
    
    3、从后端获取路由数据,写一个数据转换的方法,讲数据转成可访问的路由
    
    4、也是维护一个vuex状态,将转换好的路由存到vuex里面
    
    5、侧边栏也是从路由取数据进行渲染

    因为前段控制和后端控制,后面的流程大部分都是一样的,所以这个地方只看看前面不一样的流程:

    1、store/permission.js,在vuex里面发送请求获取数据

    GenerateRoutes({ commit }, data) {
      return new Promise((resolve, reject) => {
        getRoute(data).then(res => {
         // 将获取到的数据进行一个转换,然后存到vuex里
          const accessedRouters = arrayToMenu(res.data)
          accessedRouters.concat([{ path: '*', redirect: '/404', hidden: true }])
          commit('SET_ROUTERS', accessedRouters)
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    }

    2、整理一份数据结构,存到表里

    我们知道vue的router规定的数据结构是这样的:

    {
        path: '/form',
        component: Layout,
        children: [
          {
            path: 'index',
            name: 'Form',
            component: () => import('@/views/form/index'),
            meta: { title: 'Form', icon: 'form' }
          }
        ]
    }

    所以,一级菜单有几个参数必须要有:id、path、name、component、title,二级菜单children是一个数组,是子父级的关系,所以可以给加一个fid或者parentId,来进行匹配,后面写转换方法的时候会详细解释,数据格式大概就是这样:

    // 一级菜单
    // parentId为0的就可以当做一级菜单,id最好是可以选4位数,至于为什么等你开发项目的时候就知道了
    {
        id: 1300
        parentId: 0
        title: "企业管理"
        path: "/enterprise"
        hidden: false
        component: null
        hidden: false
        name: "enterprise"
    },
    
    // 二级菜单
    // parentId不为0的,就可以拿parentId跟一级菜单的id去匹配,匹配上的就push到children里面
    {
        id: 1307
        parentId: 1300
        title: "商户信息"
        hidden: false
        path: "merchantInfo"
        component: "enterprise/merchantInfo" // 要跟本地的文件地址匹配上
        hidden: false
        name: "merchantInfo"
    }

    3、写一个转化方法,把获取到的数据转换成router结构

    刚才获取到的数据无法直接转成router进行渲染,需要一个arrayToMenu的方法,刚才也说了一些思路,下面就一起分析下这个方法:

    export function arrayToMenu(array) {
      const nodes = []
      // 获取顶级节点
      for (let i = 0; i < array.length; i++) {
        const row = array[i]
        // 这个exists方法就是判断下有没有子级
        if (!exists(array, row.parentId)) {
          nodes.push({
            path: row.path, // 路由地址
            hidden: row.hidden, // 全部携程true就行,如果后端没配
            component: Layout, // 一般就是匹配你文件的component
            name: row.name, // 路由名称
            meta: { title: row.title, icon: row.name }, // title就是显示的名字
            id: row.id, // 路由的id
            redirect: 'noredirect'
          })
        }
      }
      const toDo = Array.from(nodes)
      while (toDo.length) {
        const node = toDo.shift()
        // 获取子节点
        for (let i = 0; i < array.length; i++) {
          const row = array[i]
          // parentId等于哪个父级的id,就push到哪个
          if (row.parentId === node.id) {
            const child = {
              path: row.path,
              name: row.name,
              hidden: row.hidden,
              // 核心代码,因为二级路由的component是需要匹配页面的
              component: require('@/views/' + row.component + '/index.vue'),
              meta: { title: row.title, icon: row.name },
              id: row.id
            }
            if (node.children) {
              node.children.push(child)
            } else {
              node.children = [child]
            }
            toDo.push(child)
          }
        }
      }
      return nodes
    }
    // 看下有没有子级
    function exists(rows, parentId) {
      for (let i = 0; i < rows.length; i++) {
        if (rows[i].id === parentId) return true
      }
      return false
    }

    侧边栏的代码跟静态的代码是一样的,就不再说一遍了

    总结

    动态路由究竟是前端控制好,还是后端控制好?只能说各有各的优势,毕竟业务场景也不一样,大家可以动手来试一下。代码还有很多值得优化的地方,欢迎各位大神批评指正。

    个人公众号:小Jerry有话说

    也欢迎关注我的个人公众号,分享一些有趣的前端知识。

    4742ccc909608e6cbce8296ced05760b.png
    展开全文
  • 做权限管理,一个核心思想就是后端做权限控制,前端做的所有工作都只是为了提高用户体验,我们不能依靠前端展示或者隐藏一个按钮来实现权限控制,这样肯定是不安全的。 就像用户注册时需要输入邮箱地址,前端校验...

    1. 一个原则

    做权限管理,一个核心思想就是后端做权限控制,前端做的所有工作都只是为了提高用户体验,我们不能依靠前端展示或者隐藏一个按钮来实现权限控制,这样肯定是不安全的。

    就像用户注册时需要输入邮箱地址,前端校验之后,后端还是要校验,两个校验目的不同,前端校验是为了提高响应速度,优化用户体验,后端校验则是为了确保数据完整性。权限管理也是如此,前端按钮的展示/隐藏都只是为了提高用户体验,真正的权限管理需要后端来实现。

    这是非常重要的一点,做前后端分离开发中的权限管理,我们首先要建立上面这样的思考框架,然后在这样的框架下,去考虑其他问题。

    因此,下文我会和大家分享两种方式实现动态菜单,这两种方式仅仅只是探讨如何更好的给用户展示菜单,而不是探讨权限管理,因为权限管理是在后端完成的,也必须在后端完成。

    2、实现方式

    2.1 后端动态返回

    用户登录成功之后,可以查询到用户的角色,再根据用户角色去查询出来用户可以操作的菜单(资源),然后把这些可以操作的资源,组织成一个 JSON 数据,返回给前端,前端再根据这个 JSON 渲染出相应的菜单。

    至于menu表需要保存哪些字段、返回给前端的JSON数据格式怎么组织,还要配合前端同学一起协商。

    这种方式的一个好处是前端的判断逻辑少一些,后端也不算复杂,就是一个 SQL 操作,前端拿到后端的返回的菜单数据,稍微处理一下就可以直接使用了。另外这种方式还有一个优势就是可以动态配置资源-角色以及用户-角色之间的关系,进而调整用户可以操作的资源(菜单)。

    2.2 前端动态渲染

    后端只需要在登录成功后返回当前用户的角色就可以了,剩下的事情则交给前端来做。前端根据角色去动态渲染菜单。不过这种方式有一个弊端就是菜单和角色的关系在前端代码中写死了,以后如果想要动态调整会有一些不方便,可能需要改代码。特别是大项目,权限比较复杂的时候,调整就更麻烦了,所以这种方式一般建议在一些简单的项目中使用。

    本人不太擅长前端,具体实现方式就不展示了,有兴趣可以网上搜一下。

    展开全文
  • Vue动态路由使用(后端控制

    千次阅读 2020-07-18 18:20:10
    使用VUE开发后台管理系统 完全由后端控制左边菜单项思路 在传统开发后台管理系统时,都会涉及权限控制这一功能需求 即:根据不同登录的角色账号来使用该账号拥有的功能,也就是说系统左边的菜单栏不是固定不变的,...
  • 前端路由后端路由

    2017-07-24 15:00:16
    后端路由 意味着 浏览器刷新页面。显然很多 webapp 的需求上是不希望这样的体验的。网速慢的话说不定屏幕全白再有新内容。 前端路由就不会有这样的问题了。随意控制,逻辑也可以都放在前端。前端慢慢复杂化,...
  • 关于后台管理系统的路由,想花一点时间,彻底的整理一份实现动态路由的点点滴滴。 首先声明,这篇文章是基于花裤衩大神的《手摸手,带你用vue撸后台》,在他项目的基础上,帮助想要实现动态路由的小伙伴,来写的一篇...
  • 从毕业之后就再也没写过博客,一...iview-admin动态路由之方法二 最近项目中遇到了细化权限模块的需求,原来的权限模块用的是router.js的access数组配合登陆时返回的user.access来渲染左侧菜单的,这样说肯定是不明...
  • 一个前端看后端路由

    2018-11-29 15:19:31
    但是在例如很多业务情景下就不适用了,例如展示广告,几乎不需要在页面上有其他逻辑,例如严谨的下单流程,后端路由可以严格控制前端不可进入页面,还有后端路由可以应用于API层面提供接口等等许多的场景都是可以的...
  • vue项目实现动态路由的方式大体可分为两种:前端控制路由:前端这边把路由写好,登录的时候根据用户的角色权限来动态展示路由后端处理路由:后台传来当前用户对应权限的路由表,前端通过调接口拿到后处理这两种方法各有...
  • vue项目实现动态路由的方式大体可分为两种:前端控制路由:前端这边把路由写好,登录的时候根据用户的角色权限来动态展示路由后端处理路由:后台传来当前用户对应权限的路由表,前端通过调接口拿到后处理这两种方法各有...
  • 添加路由守卫, 实现权限控制 router.beforeEach((to, from, next) => { // 1.如果访问的是注册或者登录, 那么就放行 if(to.path === '/login' || to.path === '/register'){ return next(); } // 2.获取当前...
  • 采取了前后端分离的开发模式,主要使用技术栈:springboot web部分+vue(路由和axios部分)+token校验 二、详细步骤 第一步 在vue路由定义中多添加自定义字段requireAuth、role、auth: 第二步 设置登录拦截...
  • 后端权限控制模型

    2018-08-07 11:06:47
    一、大体的前端路由流程图,   非登录页的外层布局userLayout代码: @connect(({ nav }) =&gt; ({ navList:nav.navList })) class Layout0 extends React.PureComponent { render() { const { ...
  • 页面登录成功后,执行下面的代码,把后端返回的路由数据用 this.$router.addRoutes() // 添加 this.$router.addRoutes([ { path: '/admin', component: () => import('../views/admin.vue') } ]), // 这样就...
  • 已提交到github,路由组件代码,代码质量一般,大家将就着看 : ) 组件思路 现在都是单入口,通过index.php配置组件,然后通过组件dispatch,来调用指定的控制器。 另外这个框架作为后端纯Api框架。没有PHP模板引擎,...
  • 很多时候我们在项目的路由都是在前端配置好的 但是有的时候为了进行全面的权限控制,会需要后台给出路由表,...//第一种 可以跟后端小哥约定一个字段,把路由的url后半段直接给后端小哥哥我们直接加载,这里面注意impo
  • 1、后端控制路由。 后台传来当前用户对应的路由表,前端通过调接口拿到处理解析成路由。(addRoutes 动态加载路由。) 2、前端控制路由。 前端写好所有的路由,后端返回当前用户的角色,然后根据事先约定好的每个...
  • 简单讲讲前端路由概念

    千次阅读 2018-01-27 17:24:20
    早期的路由都是后端直接根据 url 来 reload 页面实现的,即后端控制路由。 后来页面越来越复杂,服务器压力越来越大,随着 ajax(异步刷新技术) 的出现,页面实现非 reload 就能刷新数据,让前端也可以控制 url ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 613
精华内容 245
关键字:

后端控制路由