• 【vue】iView-admin2.0动态菜单路由【版1】


    vue项目实现动态路由有俩种方式

    一.前端在routers中写好--所有--路由表 <前端控制路由>,登录时根据用户的角色权限来动态的显示菜单路由

    二.前端通过调用接口请求拿到当前用户--对应权限的--路由表  <后端处理路由返回>,以动态的显示菜单路由 

    介绍第二种 (参考资料 segmentfault-大师兄)

    左侧菜单可通过 ①本地mock假数据 ②easymock假数据 ③从后台请求返回的数据  方式之一请求而来

    介绍方式①本地mock假数据

    1.iview-admin的src->mock->data目录下新增菜单路由数据menus-data.js (字段可参照src->router->routers.js中设置)

    menus-data.js

    export const mockMenuData = [
      {
        'path': '/multilevel',
        'name': 'multilevel',
        'meta': {
          'icon': 'md-menu',
          'title': '多级菜单'
        },
        'component': 'Main',
        'children': [
          {
            'path': '/level_2_1',
            'name': 'level_2_1',
            'meta': {
              'icon': 'md-funnel',
              'title': '二级-1'
            },
            'component': 'multilevel/level-2-1'
          },
          {
            'path': '/level_2_2',
            'name': 'level_2_2',
            'meta': {
              'icon': 'md-funnel',
              'showAlways': true,
              'title': '二级-2'
            },
            'component': 'parentView',
            'children': [
              {
                'path': '/level_2_2_1',
                'name': 'level_2_2_1',
                'meta': {
                  'icon': 'md-funnel',
                  'title': '三级'
                },
                'component': 'multilevel/level-2-2/level-2-2-1'
              },
              {
                'path': '/level_2_2_2',
                'name': 'level_2_2_2',
                'meta': {
                  'icon': 'md-funnel',
                  'title': '三级'
                },
                'component': 'multilevel/level-2-2/level-2-2-2'
              }
            ]
          },
          {
            'path': '/level_2_3',
            'name': 'level_2_3',
            'meta': {
              'icon': 'md-funnel',
              'title': '二级-3'
            },
            'component': 'multilevel/level-2-3'
          }
        ]
      },
      {
        'path': '/auth',
        'name': 'auth',
        'meta': {
          'icon': 'md-menu',
          'title': '权限设置',
          'access': ['super_admin']
        },
        'component': 'Main',
        'children': [
          {
            'path': '/role',
            'name': 'role',
            'meta': {
              'icon': 'ios-paper-outline',
              'title': '角色'
            },
            'component': 'auth/role',
            'permission': ['add', 'edit']
          },
          {
            'path': '/cmenu',
            'name': 'cmenu',
            'meta': {
              'icon': 'ios-paper-outline',
              'title': '菜单'
            },
            'component': 'auth/cmenu',
            'permission': ['add', 'del']
          },
          {
            'path': '/account',
            'name': 'account',
            'meta': {
              'icon': 'ios-paper-outline',
              'title': '账号'
            },
            'component': 'auth/account'
          }
        ]
      },
      {
        'path': '/lesmessage',
        'name': 'lesmessage',
        'meta': {
          'icon': 'ios-paper',
          'title': '留言管理'
        },
        'component': 'Main',
        'children': [
          {
            'path': '/list',
            'name': 'list',
            'meta': {
              'icon': 'ios-paper',
              'title': '数据列表'
            },
            'component': 'lesmessage/list'
          }
        ]
      }
    ]
    View Code

    2/3/4步骤官方文档

    2.src->api->data.js中添加menus-data接口定义(参照iview-admin原有的mock数据接口封装写法)

    export const getMockMenuData = () => {
      return axios.request({
        url: 'get_mock_menu_data',
        method: 'post'
      })
    }
    View Code

    3.src->mock->data.js中添加menus-data请求函数(参照iview-admin原有的mock数据请求函数封装写法)

    export const getMockMenuData = req => {
      return mockMenuData
    }
    View Code

    4.src->mock->index.js中添加menus-data的Mock导出封装

    import { ... , getMockMenuData } from './data'
    
    .
    .
    .
    
    Mock.mock(//get_mock_menu_data/, getMockMenuData) 
    View Code

     以上,mock数据定义好了请求接口get_mock_menu_data

    5.src->libs目录下新增router-util.js ,  用于转化请求的menus-data数据为能被vue识别的路由格式

    router-util.js

    /**
     * ①添
     * @@新增 定义初始化菜单
     */
    import store from '@/store'
    import { getToken, localSave, localRead } from '@/libs/util'
    // import config from '@/config'
    import { lazyLoadingCop } from '@/libs/tools'
    import { getMockMenuData } from '@/api/data'
    import Main from '@/components/main' // Main 是架构组件,不在后台返回,在文件里单独引入
    import parentView from '@/components/parent-view' // parentView 是二级架构组件,不在后台返回,在文件里单独引入
    const _import = require('@/router/_import_' + process.env.NODE_ENV)// 获取组件的方法
    
    var gotRouter
    // 初始化路由
    export const initRouter = (vm) => {
      if (!getToken()) {
        return
      }
      var routerData
      console.log(gotRouter,!gotRouter, vm,store, 'initRouter')
      if (!gotRouter) {
        getMockMenuData().then(res => {
          routerData = res.data // 后台拿到路由
          localSave('dynamicRouter', JSON.stringify(routerData)) // 存储路由到localStorage
          gotRouter = filterAsyncRouter(routerData) // 过滤路由,路由组件转换
          console.log(gotRouter, 'filterAsyncRouter')
          store.commit('updateMenuList', gotRouter);
          dynamicRouterAdd()
        })
      } else {
        gotRouter = dynamicRouterAdd()
      }
      return gotRouter
    }
    
    // 加载路由菜单,从localStorage拿到路由,在创建路由时使用
    export const dynamicRouterAdd = () => {
      let dynamicRouter = []
      let data = localRead('dynamicRouter')
      if (!data) {
        return dynamicRouter
      }
      dynamicRouter = filterAsyncRouter(JSON.parse(data))
      return dynamicRouter
    }
    
    // @函数: 遍历后台传来的路由字符串,转换为组件对象
    export const filterAsyncRouter = (asyncRouterMap) => {
      const accessedRouters = asyncRouterMap.filter(route => {
        if (route.component) {
          if (route.component === 'Main') { // Main组件特殊处理
            route.component = Main
          } else if (route.component === 'parentView') { // parentView组件特殊处理
            route.component = parentView
          } else {
            // route.component = _import(route.component)
            route.component = lazyLoadingCop(route.component)
          }
        }
        if (route.children && route.children.length) {
          route.children = filterAsyncRouter(route.children)
        }
        return true
      })
      return accessedRouters
    }
    View Code

    附: src->libs->toos.js中添加 引入.vue组件的封装函数 (不分环境可用)

    //  @函数: 引入组件
    export const lazyLoadingCop = file => require('@/view/' + file + '.vue').default
    View Code

     附:src-->router中新增_import_development.js和_import_production.js为引入.vue组件的封装 (俩种环境)

    _import_development.js

    module.default = file => require('@/view/' + file + '.vue').default // vue-loader at least v13.0.0+
    View Code

    _import_production.js

    module.exports = file => () => import('@/view/' + file + '.vue')
    View Code

    vux部分updateMenuList更新菜单数据

     附:src->store->module->app.js中的mutations添加 updateMenuList 操作 state中的 menuList:[] (添加) 、   getters中menuList修改的获取方式

        updateMenuList (state, routes) { // ①添 接受前台数组,刷新菜单
          router.addRoutes(routes); // 动态添加路由
          state.menuList = routes;
          console.log('①updateMenuList添menuList', this);
        }
    View Code
      getters: {
        // menuList: (state, getters, rootState) => getMenuByRouter(routers, rootState.user.access),
        // menuList: (state, getters, rootState) => getMenuByRouter(dynamicRouterAdd(), rootState.user.access), // ①改 通过路由列表得到菜单列表
        menuList: (state, getters, rootState) => getMenuByRouter(state.menuList, rootState.user.access), // ①改 通过路由列表得到菜单列表
     ...
      },
    View Code

     

    src->router->routers.js   主要是左侧菜单的加入

    import Main from '@/components/main'
    import { dynamicRouterAdd } from '@/libs/router-util' // ①添 引入加载菜单
    
    /**
     * iview-admin中meta除了原生参数外可配置的参数:
     * meta: {
     *  title: { String|Number|Function }
     *         显示在侧边栏、面包屑和标签栏的文字
     *         使用'{{ 多语言字段 }}'形式结合多语言使用,例子看多语言的路由配置;
     *         可以传入一个回调函数,参数是当前路由对象,例子看动态路由和带参路由
     *  hideInBread: (false) 设为true后此级路由将不会出现在面包屑中,示例看QQ群路由配置
     *  hideInMenu: (false) 设为true后在左侧菜单不会显示该页面选项
     *  notCache: (false) 设为true后页面在切换标签后不会缓存,如果需要缓存,无需设置这个字段,而且需要设置页面组件name属性和路由配置的name一致
     *  access: (null) 可访问该页面的权限数组,当前路由设置的权限会影响子路由
     *  icon: (-) 该页面在左侧菜单、面包屑和标签导航处显示的图标,如果是自定义图标,需要在图标名称前加下划线'_'
     *  beforeCloseName: (-) 设置该字段,则在关闭当前tab页时会去'@/router/before-close.js'里寻找该字段名对应的方法,作为关闭前的钩子函数
     * }
     */
    // 不作为Main组件的子页面展示的页面单独写
    export const otherRouter = [{
      path: '/login',
      name: 'login',
      meta: {
        title: 'Login - 登录',
        hideInMenu: true
      },
      component: () => import('@/view/login/login.vue')
    }, {
      path: '/401',
      name: 'error_401',
      meta: {
        hideInMenu: true
      },
      component: () => import('@/view/error-page/401.vue')
    }, {
      path: '/500',
      meta: {
        title: '500-服务端错误'
      },
      name: 'error_500',
      component: () => import('@/view/error-page/500.vue')
    }
    ];
    
    // 作为Main组件的子页面展示但是不在左侧菜单显示的路由写在mainRouter里
    export const mainRouter = [{
      path: '/',
      name: '_home',
      redirect: '/home',
      component: Main,
      meta: {
        hideInMenu: true,
        notCache: true
      },
      children: [
        {
          path: '/home',
          name: 'home',
          meta: {
            hideInMenu: true,
            title: '首页',
            notCache: true,
            icon: 'md-home'
          },
          component: () => import('@/view/single-page/home')
        }
      ]
    }, {
      path: '/message',
      name: 'message',
      component: Main,
      meta: {
        hideInBread: true,
        hideInMenu: true
      },
      children: [
        {
          path: 'message_page',
          name: 'message_page',
          meta: {
            icon: 'md-notifications',
            title: '消息中心'
          },
          component: () => import('@/view/single-page/message/index.vue')
        }
      ]
    }];
    
    // 作为Main组件的子页面展示并且在左侧菜单显示的路由写在appRouter里
    export const appRouter = [...dynamicRouterAdd()];
    
    export const routes = [
      ...otherRouter,
      ...mainRouter,
      ...appRouter
    ]
    
    // 所有上面定义的路由都要写在下面输出
    export default routes
    View Code

     src->main.js 实例化对象 添加挂载时的动态路由调用

    .
    .
    .
    import { initRouter } from '@/libs/router-util' // ①新增  引入动态菜单渲染
    .
    .
    .
    new Vue({
    .
    .
    .
      mounted() {
        initRouter(this);  // ①新增 调用方法,动态生成路由
      },
    .
    })
    View Code

    登录菜单未渲染,可在路由跳转前执行一次initRouter (此方案舍去)  src->router->routers.js

    import { initRouter } from '@/libs/router-util'
    .
    .
    .
    
    const turnTo = (to, access, next) => {
      initRouter();
      ...
    };
    View Code

    登录菜单未渲染,可在路由跳转前执行一次initRouter(此方案体验更佳,解决附加6的问题)   src->router->routers.js     

    import { InitRouter } from '@/libs/router-util'
    ...
    router.beforeEach((to, from, next) => {
      .
      if (!token && to.name !== LOGIN_PAGE_NAME) {
        // 未登录且要跳转的页面不是登录页
          .
      } else if (!token && to.name === LOGIN_PAGE_NAME) {
        // 未登陆且要跳转的页面是登录页
        InitRouter()  // 登录页刷新重新获取,确保路由跳转前即beforeEach获取到动态路由表
        next() // 跳转
      } else if (token && to.name === LOGIN_PAGE_NAME) {
        // 已登录且要跳转的页面是登录页
          .
      } else {
         .
        }
    })
    View Code

    以上。动态载入请求本地mock菜单路由结束。

    介绍方式 ②easymock假数据

    1.使用easy-mock,创建项目,复制项目Base URL

    2.配置proxy (在vue.config.js中) 

      devServer: {
        proxy: {
          '/mock': {   // easymock跨域请求配置
            target: 'easy-mock项目的Base URL',
            changeOrigin: true,
            pathRewrite: {'^/mock': '/'}
          }
        }
      }
    View Code

    3.配置package.json脚本

    "easy-mock": "node build/dev-server.js mock",

    4.配置api请求基础路径baseUrl(在src->config->index.js中)

      /**
       * @description api请求基础路径
       */
      baseUrl: {
        dev: '步骤2中的easymock项目地址Base URL',
        pro: 'https://produce.com'
      },
    View Code

    之后就是接口封装和使用了。(可参照iview-admin原本写mock假数据的封装写法及使用)

    5.附加 easy-mock工具使用

    6.附加  vue 解决addRoutes动态添加路由后,刷新失效问题   

    备注:这篇文章属于一边摸索一边写下的,有出现问题会修改一些步骤,基本原理已经表现。

    在此文章基础上加写一篇,iView-admin2.0动态菜单路由【版2】,解决这篇文章中忽略的/没讲清的点。

  • 相关阅读:
    Beta版软件说明书
    团队项目第五天
    cnblogs用户体验评价
    团队项目测试计划
    Alpha版总结会议
    软件使用说明书
    团队绩效考核
    各个小组对于“我爱淘”的评价
    软件工程团队项目评价
    丹佛机场行李系统处理方案
  • 原文地址:https://www.cnblogs.com/smilexumu/p/10521612.html
Copyright © 2020-2023  润新知