美文网首页
Vue - vue-admin-template模板项目改造:动

Vue - vue-admin-template模板项目改造:动

作者: 西半球_ | 来源:发表于2023-10-23 11:56 被阅读0次

    GitHub Demo 地址

    在线预览

    更新:对应的vue3版的demo如下:

    GitHub Demo 地址

    在线预览

    前言

    1、demo中的项目已经添加了TagsView功能和本地权限控制
    关于TagsView功能的添加可以看:Vue - vue-admin-template模板项目改造:增加TagsView功能
    关于本地权限控制相关代码参考 vue-admin-template permission-control分支

    2、并且demo中的项目在1的基础上增加TopHeader(顶栏)功能,在顶栏中显示项目标题和用户信息,即可以支持原有方式展示,又可以通过setting配置显示顶栏
    关于增加TopHeader(顶栏)功能的添加可以看:Vue - vue-admin-template模板项目改造:增加TopHeader(顶栏)

    所以项目代码可能和原版的vue-admin-template有点差别,vue-admin-template 代码地址

    本地权限控制,具体是通过查询用户信息获取用户角色,在路由守卫中通过角色过滤本地配置的路由,把符合角色权限的路由生成一个路由数组

    动态获取菜单路由其实思路是一样的,只不过路由数组变成从服务器获取,通过查询某个角色的菜单列表,然后在路由守卫中把获取到的菜单数组转成路由数组

    动态路由实现是参考vue-element-admin的issues写的,相关issues:
    vue-element-admin/issues/167
    vue-element-admin/issues/293
    vue-element-admin/issues/3326#issuecomment-832852647

    关键点

    主要在接口菜单列表中把父componentLayout 改为字符串 'Layout',
    children的component: () => import('@/views/table/index'), 改成 字符串'table/index',然后在获取到数据后再转回来
    !!!!!!!!!!!! 接口格式可以根据项目需要自定义,不一定非得按照这里的来

    本地路由格式:

      {
        path: '/example',
        component: Layout,
        redirect: '/example/table',
        name: 'Example',
        meta: { title: 'Example', icon: 'el-icon-s-help', roles: ['admin'] },
        children: [
          {
            path: 'table',
            name: 'Table',
            component: () => import('@/views/table/index'),
            meta: { title: 'Table', icon: 'table' }
          },
          {
            path: 'tree',
            name: 'Tree',
            component: () => import('@/views/tree/index'),
            meta: { title: 'Tree', icon: 'tree' }
          }
        ]
      },
    

    接口路由格式:

      {
        path: '/example',
        component: 'Layout',
        redirect: '/example/table',
        name: 'Example',
        meta: { title: '动态Example', icon: 'el-icon-s-help', roles: ['admin'] },
        children: [
          {
            path: 'table',
            name: 'Table',
            component: 'table/index',
            meta: { title: '动态Table', icon: 'table' }
          },
          {
            path: 'tree',
            name: 'Tree',
            component: 'tree/index',
            meta: { title: '动态Tree', icon: 'tree' }
          }
        ]
      },
    

    具体实现

    1、接口

    因为是通过mock模拟的网络请求,所以需要添加一个获取用户菜单列表的接口(可以把vue-element-admin中的mock文件夹下的role文件夹直接copy到项目中,然后改造)

    mock目录下建role文件夹、index.js文件

    const Mock = require('mockjs')
    const { asyncRoutes } = require('./routes.js')
    const routes = asyncRoutes
    module.exports = [
      // mock get all routes form server
      {
        url: '/vue-element-admin/routes',
        type: 'get',
        response: _ => {
          return {
            code: 20000,
            data: routes
          }
        }
      }
    

    mock目录下建role文件夹、routes.js文件
    主要用的是asyncRoutes

    // Just a mock data
    
    const constantRoutes = [
      {
        path: '/redirect',
        component: 'Layout',
        hidden: true,
        children: [
          {
            path: '/redirect/:path(.*)',
            component: 'redirect/index'
          }
        ]
      },
      {
        path: '/login',
        component: 'login/index',
        hidden: true
      },
      {
        path: '/404',
        component: '404',
        hidden: true
      },
    
      {
        path: '/',
        component: 'Layout',
        redirect: '/dashboard',
        children: [{
          path: 'dashboard',
          name: 'Dashboard',
          component: 'dashboard/index',
          meta: { title: 'Dashboard', icon: 'dashboard', affix: true }
        }]
      }
    ]
    
    /**
     * asyncRoutes
     * the routes that need to be dynamically loaded based on user roles
     */
    const asyncRoutes = [
      {
        path: '/example',
        component: 'Layout',
        redirect: '/example/table',
        name: 'Example',
        meta: { title: '动态Example', icon: 'el-icon-s-help', roles: ['admin'] },
        children: [
          {
            path: 'table',
            name: 'Table',
            component: 'table/index',
            meta: { title: '动态Table', icon: 'table' }
          },
          {
            path: 'tree',
            name: 'Tree',
            component: 'tree/index',
            meta: { title: '动态Tree', icon: 'tree' }
          }
        ]
      },
      {
        path: '/form',
        component: 'Layout',
        meta: { roles: ['admin'] },
        children: [
          {
            path: 'index',
            name: 'Form',
            component: 'form/index',
            meta: { title: '动态Form', icon: 'form' }
          }
        ]
      },
      {
        path: '/nested',
        component: 'Layout',
        redirect: '/nested/menu1',
        name: 'Nested',
        meta: { title: '动态Nested', icon: 'nested', roles: ['admin'] },
        children: [
          {
            path: 'menu1',
            component: 'nested/menu1/index', // Parent router-view
            name: 'Menu1',
            meta: { title: '动态Menu1' },
            children: [
              {
                path: 'menu1-1',
                component: 'nested/menu1/menu1-1',
                name: 'Menu1-1',
                meta: { title: '动态Menu1-1' }
              },
              {
                path: 'menu1-2',
                component: 'nested/menu1/menu1-2',
                name: 'Menu1-2',
                meta: { title: '动态Menu1-2' },
                children: [
                  {
                    path: 'menu1-2-1',
                    component: 'nested/menu1/menu1-2/menu1-2-1',
                    name: 'Menu1-2-1',
                    meta: { title: '动态Menu1-2-1' }
                  },
                  {
                    path: 'menu1-2-2',
                    component: 'nested/menu1/menu1-2/menu1-2-2',
                    name: 'Menu1-2-2',
                    meta: { title: '动态Menu1-2-2' }
                  }
                ]
              },
              {
                path: 'menu1-3',
                component: 'nested/menu1/menu1-3',
                name: 'Menu1-3',
                meta: { title: '动态Menu1-3' }
              }
            ]
          },
          {
            path: 'menu2',
            component: 'nested/menu2/index',
            meta: { title: '动态menu2' }
          }
        ]
      },
      {
        path: 'external-link',
        component: 'Layout',
        children: [
          {
            path: 'https://panjiachen.github.io/vue-element-admin-site/#/',
            meta: { title: '动态External Link', icon: 'link' }
          }
        ]
      },
      /** when your routing map is too long, you can split it into small modules **/
      // componentsRouter,
      // chartsRouter,
    
      // 404 page must be placed at the end !!!
      { path: '*', redirect: '/404', hidden: true }
    ]
    
    module.exports = {
      constantRoutes,
      asyncRoutes
    }
    

    然后在mock/index.js,把新加的role引用一下

    const user = require('./user')
    const role = require('./role/index') // 新加的
    const table = require('./table')
    const dict = require('./demos/dict')
    const tables = require('./demos/tables')
    
    const mocks = [
      ...user,
      ...role, // 新加的
      ...table,
      ...dict,
      ...tables
    ]
    

    2、前端调用菜单接口并生成路由数组

    先在src / api / roles.js(新建roles.j,或者放到user里) 把mock接口实现一下

    import request from '@/utils/request'
    
    export function getUserMenus() {
      return request({
        url: '/vue-element-admin/routes',
        method: 'get'
      })
    }
    

    然后修改 src/store/modules/permission.js,把处理菜单数组转成路由数组的方法实现一下

    const { deepClone } = require('@/utils')
    
    // 加载路由
    export const loadView = (view) => {
      // 路由懒加载
      return (resolve) => require([`@/views/${view}`], resolve)
      // return (resolve) => require([`@${view}`], resolve)
    }
    
    /**
     * 通过递归格式化菜单路由 (配置项规则:https://panjiachen.github.io/vue-element-admin-site/zh/guide/essentials/router-and-nav.html#配置项)
     * @param routes
     * @param roles
     */
    export function filterAsyncRoutes2(routes) {
      const res = []
      routes.forEach((route) => {
        const tmp = deepClone(route)
        if (route.component === 'Layout') {
          tmp.component = Layout
        } else if (route.component) {
          tmp.component = loadView(route.component)
        }
        if (route.children && route.children.length > 0) {
          tmp.children = filterAsyncRoutes2(route.children)
        }
        res.push(tmp)
      })
      return res
    }
    

    在actions中新加一个函数

      generateDynamicRoutes({ commit }, menus) {
        return new Promise(resolve => {
          const accessedRoutes = filterAsyncRoutes2(menus)
          commit('SET_ROUTES', accessedRoutes) // Todo: 内部拼接constantRoutes,所以查出来的菜单不用包含constantRoutes
          resolve(accessedRoutes)
        })
      }
    

    然后修改src\store\modules\user.js
    添加获取用户菜单的方法

    const state = {
    // ```
      menus: [] //这个是我新增的
    }
    
    const mutations = {
        // ```
      SET_MENUS: (state, menus) => { //这里是新增的
        state.menus = menus
      }
    }
    
      getUserMenus({ commit, state }) {
        return new Promise((resolve, reject) => {
          getUserMenus(state.token).then(response => {
            const { data } = response
            if (!data) {
              reject('Verification failed, please Login again.')
            }
            const menus = data
            // roles must be a non-empty array
            if (!menus || menus.length <= 0) {
              reject('getMenus: menus must be a non-null array!')
            }
            commit('SET_MENUS', menus)
            resolve(menus)
          }).catch(error => {
            reject(error)
          })
        })
      },
    

    然后在修改src\permission.js的路由守卫
    要在 store.dispatch('permission/generateRoutes') 代码附近要改一下,原先从本地配置+roles获得用户路由,现在改从服务端获得之后再 addRoutes

    我这里在mock中加了个角色editor2,当editor2登录使用的从服务器获取动态路由,其他角色从本地获取路由

     const { roles } = await store.dispatch('user/getInfo')
     // console.log('roles', JSON.stringify(roles))
    
     var accessRoutes = []
    
     if (roles.includes('editor2')) {
       // 根据服务器获取的用户菜单生成路由
       const menus = await store.dispatch('user/getUserMenus')
       const asyncRoutes = await store.dispatch('permission/generateDynamicRoutes', menus)
       accessRoutes = asyncRoutes
     } else {
       // generate accessible routes map based on roles
       accessRoutes = await store.dispatch('permission/generateRoutes', roles)
     }
     
     // dynamically add accessible routes
     router.addRoutes(accessRoutes)
    

    至此结束。

    相关文章

      网友评论

          本文标题:Vue - vue-admin-template模板项目改造:动

          本文链接:https://www.haomeiwen.com/subject/hnltidtx.html