vue-element-admin 服务器返回数据生成动态路由

文章描述了一个Vue.js项目(vue-element-admin)如何改变路由配置,使其根据用户角色从服务器获取并动态挂载菜单路由。流程包括获取用户角色,根据角色从服务器查询过滤后的路由,前端组装并挂载这些路由。这样不同角色的用户看到的侧边栏会因角色不同而变化,减少了代码维护的工作量。
摘要由CSDN通过智能技术生成

0. 背景

vue-element-admin 这个项目路由最初是配置在 src/route/index.js 文件中,再根据src/store/modules/permission.js#generateRoutes 方法按角色过滤出对应的路由并动态挂载。

我希望有个新效果:服务器根据系统用户拥有的角色获得对应的菜单路由,再去动态挂载路由。

这样不同角色的用户登录系统看到的侧边栏就不同,日后新增页面等操作只需要操作数据库相关表而不需要修改路由文件。

1. 流程

基本思路:

获得用户角色
根据用户发送请求查询路由
前端组装路由并挂载

我这里 根据用户查询路由中的路由已经是过滤的,也就是说服务器返回的不是完整的路由是已经根据角色过滤好的!你想返回完整菜单由前端过滤也是可行的

后端路由表一条记录形式为(

路径组件sorttitleparent……
/systemLayout1系统页面0……

路由表字段看着src/router/index 文件设置字段就行了,父子组件的判断通过parent这个字段,为0说明自己是父组件(layout),非0值存的就是父组件的id

在后端参考

public class JobMenuServiceImpl implements JobMenuService {
    @Resource
    PermissionMapper permissionMapper;

    @Override
    public List<JobMenu> transferMenu2RouterJSON(String role_key) {
        // 根据角色获取对应的菜单路由
        List<JobMenu> jobMenusList = permissionMapper.getUserMenuListByRole(role_key);
        return jobMenusList.stream()
                .filter(menu -> menu.getParent() == 0)
                .peek(menu -> menu.setChildren(getChildren(menu, jobMenusList)))
                .sorted(Comparator.comparing(JobMenu::getSort))
                .collect(Collectors.toList());
    }

    public List<JobMenu> getChildren(JobMenu root, List<JobMenu> allMenus) {
        return allMenus.stream()
                .filter(menu -> isIDEqual(menu.getParent(), root.getId()))
                .peek(menu -> menu.setChildren(getChildren(menu, allMenus)))
                .sorted(Comparator.comparing(JobMenu::getSort))
                .collect(Collectors.toList());
    }

    public boolean isIDEqual(int a, int b) {
        return (a == b);
    }
}

jobmenu 这个entity在数据库没有children字段,所以xml里记得自行限制下select列

@Data
public class JobMenu {
    private int id;
    private int parent;
    private int sort;
    private String path;
    private String title;
    private String name;
    private String component;
    private int type;
    private String permission;
    private String icon;
    private int hidden;
    private int nocache;
    private List<JobMenu> children;
}   

xml参考,roleKey就是前端传来的roles

<mapper namespace="com.wugui.datax.admin.mapper.JobMenuMapper">

    <sql id="base_column">
        jm.id,
        jm.parent,
        jm.sort,
        jm.path,
        jm.title,
        jm.name,
        jm.component,
        jm.icon,
        jm.hidden,
        jm.nocache
    </sql>

    <select id="getMenusByRouteRole" parameterType="java.util.HashMap" resultType="com.wugui.datax.admin.entity.JobMenu">
        select <include refid="base_column"/>
        from job_role jr
        left join job_role_menu jrm on jr.id=jrm.role_id
        left join job_menu jm on jm.id=jrm.menu_id
        <where>
            <if test="roleKey!=null and roleKey!=''">
                jr.role_key = #{roleKey}
            </if>
        </where>
    </select>
</mapper>

1.1 src/permission.js

原本的代码:

// get user info
// note: roles must be a object array! such as: ['admin'] or ,['developer','editor']
const { roles } = await store.dispatch('user/getInfo')

// generate accessible routes map based on roles
const accessRoutes = await store.dispatch('permission/generateRoutes', roles)

// dynamically add accessible routes
router.addRoutes(accessRoutes)

// hack method to ensure that addRoutes is complete
// set the replace: true, so the navigation will not leave a history record
next({ ...to, replace: true })

//roles如果返回多个权限,需要过滤出我们想要的。能确保只有一个role就不需要该步骤!
// join将数组元素放到字符串中!
const routeRoleArray = [‘ROLE_USER’, ‘ROLE_ADMIN’]
const currentUserRouteRole = roles.filter(item => { return routeRoleArray.includes(item)}.join()

假定 服务器可以通过 roles 常量 返回对应菜单记录。此时menus是已经根据角色过滤好的!

const menus = await store.dispatch(‘user/getUserMenuListByRole’, roles)

我们根据返回结果生成vuex能识别的路由数据(前端组装)

const accessRoutes = await store.dispatch(‘permission/generateRoutes’, { roles, menus })

最终src/permission.js 代码

 // get user info
 const { roles } = await store.dispatch('user/getInfo')
 // 从返回的角色组中过滤出目录角色
 const currentUserRouteRole = roles.filter(item => {
   return routeRoleArray.includes(item)
 }).join()
 // 后端根据角色获取原始菜单列表
 const menus = await store.dispatch('user/getUserMenuListByRole', currentUserRouteRole)
 // 根据角色和原始菜单列表生成可访问的路由
 // dispatch只有两个参数,大于两个参数用{}对象形式传入
 const accessRoutes = await store.dispatch('permission/generateRoutes', { roles, menus })
 console.log(accessRoutes)
 // 动态添加可访问的路由
 router.addRoutes(accessRoutes)

 next({ ...to, replace: true })

getUserMenuListByRole方法参考(generateRoutes在下面)

  getUserMenuListByRole({ commit }, currentUserRouteRole) {
    return new Promise((resolve, reject) => {
      getUserMenuListByRole({ 'role_key': currentUserRouteRole }).then(response => {
        const menus = response.content
        commit('SET_MENUS', menus)
        resolve(menus)
      }).catch(error => {
        reject(error)
      })
    })
  },
1.2 src/store/modules/permission.js

1.1 组装路由的逻辑在src/store/modules/permission.js ,generateRoutes 原内容我们直接注释,编写新逻辑

 generateRoutes({ commit }, data) {
    const { roles, menus } = data // eslint-disable-line no-unused-vars
    return new Promise(resolve => {
      let accessedRoutes = [] // eslint-disable-line prefer-const
      generateMenu(accessedRoutes, menus)
      // localStorage.setItem('user_route', JSON.stringify(accessedRoutes))
      commit('SET_ROUTES', accessedRoutes)
      accessedRoutes.push({ path: '*', redirect: '/404', hidden: true })
      resolve(accessedRoutes)
    })
  }

最后accessedRoutes.push 404一定要加上,源项目也说了。generateMenu也是我们新加的方法用来组装路由:

export function generateMenu(routes, menus) {
  menus.forEach(item => {
    const menu = {
      path: item.path,
      component: item.component === 'Layout' ? Layout : (resolve) => require([`@/views${item.component}`], resolve),
      redirect: '',
      name: item.name,
      meta: { title: item.title, icon: item.icon },
      // 数据库字段hidden 0 隐藏,1 显示 默认显示
      hidden: item.hidden === 0,
      // 数据库字段nocache 0 不缓存 1 缓存 默认缓存
      nocache: item.nocache === 1,
      children: []
    }
    if (item.children && item.children.length > 0) {
      menu.redirect = item.children[0].path
      generateMenu(menu.children, item.children)
    } else {
      delete menu.redirect
      delete menu.children
    }
    routes.push(menu)
  })
}

注意点:

component: item.component === ‘Layout’ ? Layout : (resolve) => require([@/views${item.component}], resolve)

这句需要导入Layout ,同时不能再使用原来的 ()=>import(‘@/views/xx’),需要模板字符串和resolve 解决。

import Layout from ‘@/layout’

完成。

完整的 src/store/modules/permission.js

import { constantRoutes } from '@/router'
// import { asyncRoutes,constantRoutes } from '@/router'
import Layout from '@/layout'

/**
 * Use meta.role to determine if the current user has permission
 * @param roles
 * @param route
 */
function hasPermission(roles, route) {
  if (route.meta && route.meta.roles) {
    return roles.some(role => route.meta.roles.includes(role))
  } else {
    return true
  }
}

/**
 * Filter asynchronous routing tables by recursion
 * @param routes asyncRoutes
 * @param roles
 */
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
}

// 根据后端得到的原始路由json转换成vue可以挂载的路由json
export function generateMenu(routes, menus) {
  menus.forEach(item => {
    const menu = {
      path: item.path,
      component: item.component === 'Layout' ? Layout : (resolve) => require([`@/views${item.component}`], resolve),
      redirect: '',
      name: item.name,
      meta: { title: item.title, icon: item.icon },
      // 数据库字段hidden 0 隐藏,1 显示 默认显示
      hidden: item.hidden === 0,
      // 数据库字段nocache 0 不缓存 1 缓存 默认缓存
      nocache: item.nocache === 1,
      children: []
    }
    if (item.children && item.children.length > 0) {
      menu.redirect = item.children[0].path
      generateMenu(menu.children, item.children)
    } else {
      delete menu.redirect
      delete menu.children
    }
    routes.push(menu)
  })
}

const state = {
  routes: [],
  addRoutes: []
}

const mutations = {
  SET_ROUTES: (state, routes) => {
    state.addRoutes = routes
    state.routes = constantRoutes.concat(routes)
  }
}

const actions = {
  generateRoutes({ commit }, data) {
    const { roles, menus } = data // eslint-disable-line no-unused-vars
    return new Promise(resolve => {
      let accessedRoutes = [] // eslint-disable-line prefer-const
      generateMenu(accessedRoutes, menus)
      localStorage.setItem('user_route', JSON.stringify(accessedRoutes))
      commit('SET_ROUTES', accessedRoutes)
      accessedRoutes.push({ path: '*', redirect: '/404', hidden: true })
      resolve(accessedRoutes)

      // 如果角色包含管理员字符串 直接添加动态路由
      // if (roles.includes('ROLE_ADMIN')) {
      //   accessedRoutes = asyncRoutes || []
      // } else {
      //   // 否则对动态路由进行过滤
      //   accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
      // }
      // commit('SET_ROUTES', accessedRoutes)
      // resolve(accessedRoutes)
    })
  }
}

export default {
  namespaced: true,
  state,
  mutations,
  actions
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值