作者文章:https://segmentfault.com/a/1190000009506097

学习的框架:https://github.com/PanJiaChen/vue-element-admin

膜拜下大神

登陆页
面定向到:

 {
        path: '',
        component: Layout,
        redirect: 'dashboard',
        children: [{
            path: 'dashboard',
            component: _import('dashboard/index'),
            name: 'dashboard',
            meta: { title: 'dashboard', icon: 'dashboard', noCache: true }
        }]
    }

我们来看这个 component: Layout,
他分为左右侧:导航和内容主体
主体又分为顶部,tab,内容

state: {
        sidebar: {
            opened: !+Cookies.get('sidebarStatus')
        },
        language: Cookies.get('language') || 'en' // 得到语言
    }
    
<template>
	<div class="app-wrapper" :class="{hideSidebar:!sidebar.opened}">
		<sidebar class="sidebar-container"></sidebar> 
		<div class="main-container">
			<navbar></navbar>  
			<!-- 顶部 -->
			<tags-view></tags-view>
			<!-- tag -->
			<app-main></app-main>
			<!-- 主体 -->
		</div>
	</div>
</template>

<script>
import { Navbar, Sidebar, AppMain, TagsView } from './components'

export default {
  name: 'layout',
  components: {
    Navbar,
    Sidebar,
    AppMain,
    TagsView
  },
  computed: {
    sidebar() {
      return this.$store.state.app.sidebar  //是否展示左侧sidebar,通过cookie获得
    }
  }
}
</script>

##左侧导航(重点)

<template>
  <scroll-bar>
    <el-menu mode="vertical" 
    :default-active="$route.path" 
    :collapse="isCollapse" 
    background-color="#304156"
     text-color="#bfcbd9" 
     active-text-color="#409EFF">
      <sidebar-item :routes="permission_routers"></sidebar-item>
    </el-menu>
  </scroll-bar>
</template>

<script>
import { mapGetters } from "vuex";
import SidebarItem from "./SidebarItem";
import ScrollBar from "@/components/ScrollBar";

export default {
  components: { SidebarItem, ScrollBar },
  computed: {
    ...mapGetters(["permission_routers", "sidebar"]),
    //permission_routers
    isCollapse() {
      return !this.sidebar.opened;
    }
  }

};
</script>

menu : http://element.eleme.io/#/zh-CN/component/menu
import { mapGetters } from “vuex” : https://vuex.vuejs.org/zh-cn/getters.html

	
 ...mapGetters(["permission_routers", "sidebar"]), //sidebar看前面

permission_routers

const getters = {
 permission_routers: state => state.permission.routers, //用户权限
 }

router/index

//https://juejin.im/post/5a97e41bf265da23a048fa20

import Vue from 'vue'
import Router from 'vue-router'
const _import = require('./_import_' + process.env.NODE_ENV)
    // in development-env not use lazy-loading, because lazy-loading too many pages will cause webpack hot update too slow. so only in production use lazy-loading;
    // detail: https://panjiachen.github.io/vue-element-admin-site/#/lazy-loading
https: //juejin.im/post/5a97e41bf265da23a048fa20
    Vue.use(Router)

/* Layout */
import Layout from '../views/layout/Layout' //主视口

/** note: submenu only apppear when children.length>=1
 *   detail see  https://panjiachen.github.io/vue-element-admin-site/#/router-and-nav?id=sidebar
 **/

/**
* hidden: true                   if `hidden:true` will not show in the sidebar(default is false)
* alwaysShow: true               if set true, will always show the root menu, whatever its child routes length
*                                if not set alwaysShow, only more than one route under the children
*                                it will becomes nested mode, otherwise not show the root menu
* redirect: noredirect           if `redirect:noredirect` will no redirct in the breadcrumb
* name:'router-name'             the name is used by <keep-alive> (must set!!!)
* meta : {
    roles: ['admin','editor']     will control the page roles (you can set multiple roles)
    title: 'title'               the name show in submenu and breadcrumb (recommend set)
    icon: 'svg-name'             the icon show in the sidebar,
    noCache: true                if true ,the page will no be cached(default is false)
  }
**/
export const constantRouterMap = [ //永远可见的路由
    { path: '/login', component: _import('login/index'), hidden: true },
    { path: '/authredirect', component: _import('login/authredirect'), hidden: true },
    { path: '/404', component: _import('errorPage/404'), hidden: true },
    { path: '/401', component: _import('errorPage/401'), hidden: true },
    {
        path: '',
        component: Layout,
        redirect: 'dashboard',
        children: [{
            path: 'dashboard',
            component: _import('dashboard/index'),
            name: 'dashboard',
            meta: { title: 'dashboard', icon: 'dashboard', noCache: true }
        }]
    },
    {
        path: '/documentation',
        component: Layout,
        redirect: '/documentation/index',
        children: [{
            path: 'index',
            component: _import('documentation/index'),
            name: 'documentation',
            meta: { title: 'documentation', icon: 'documentation', noCache: true }
        }]
    }
]

export default new Router({
    // mode: 'history', // require service support
    scrollBehavior: () => ({ y: 0 }),
    routes: constantRouterMap
})

// 在路由router.js里面声明权限为admin的路由(异步挂载的路由asyncRouterMap)
export const asyncRouterMap = [{
        path: '/permission',
        component: Layout, //组件
        redirect: '/permission/index',
        meta: { roles: ['admin'] }, // you can set roles in root nav
        children: [{
            path: 'index',
            component: _import('permission/index'),
            name: 'permission',
            meta: {
                title: 'permission',
                icon: 'lock',
                roles: ['admin'] // or you can only set roles in sub nav
            }
        }]
    },

    {
        path: '/icon',
        component: Layout,
        meta: { roles: ['admin'] }, // you can set roles in root nav
        children: [{
            path: 'index',
            component: _import('svg-icons/index'),
            name: 'icons',
            meta: { title: 'icons', icon: 'icon', noCache: true }
        }]
    },

    {
        path: '/components',
        component: Layout,
        redirect: 'noredirect',
        name: 'component-demo',
        meta: {
            title: 'components',
            icon: 'component'
        },
        children: [
            { path: 'tinymce', component: _import('components-demo/tinymce'), name: 'tinymce-demo', meta: { title: 'tinymce' } },
            { path: 'markdown', component: _import('components-demo/markdown'), name: 'markdown-demo', meta: { title: 'markdown' } },
            { path: 'json-editor', component: _import('components-demo/jsonEditor'), name: 'jsonEditor-demo', meta: { title: 'jsonEditor' } },
            { path: 'dnd-list', component: _import('components-demo/dndList'), name: 'dndList-demo', meta: { title: 'dndList' } },
            { path: 'splitpane', component: _import('components-demo/splitpane'), name: 'splitpane-demo', meta: { title: 'splitPane' } },
            { path: 'avatar-upload', component: _import('components-demo/avatarUpload'), name: 'avatarUpload-demo', meta: { title: 'avatarUpload' } },
            { path: 'dropzone', component: _import('components-demo/dropzone'), name: 'dropzone-demo', meta: { title: 'dropzone' } },
            { path: 'sticky', component: _import('components-demo/sticky'), name: 'sticky-demo', meta: { title: 'sticky' } },
            { path: 'count-to', component: _import('components-demo/countTo'), name: 'countTo-demo', meta: { title: 'countTo' } },
            { path: 'mixin', component: _import('components-demo/mixin'), name: 'componentMixin-demo', meta: { title: 'componentMixin' } },
            { path: 'back-to-top', component: _import('components-demo/backToTop'), name: 'backToTop-demo', meta: { title: 'backToTop' } }
        ]
    },

    {
        path: '/charts',
        component: Layout,
        redirect: 'noredirect',
        name: 'charts',
        meta: {
            title: 'charts',
            icon: 'chart',
            roles: ['admin']
        },
        children: [
            { path: 'keyboard', component: _import('charts/keyboard'), name: 'keyboardChart', meta: { title: 'keyboardChart', noCache: true } },
            { path: 'line', component: _import('charts/line'), name: 'lineChart', meta: { title: 'lineChart', noCache: true } },
            { path: 'mixchart', component: _import('charts/mixChart'), name: 'mixChart', meta: { title: 'mixChart', noCache: true } }
        ]
    },

    {
        path: '/example',
        component: Layout,
        redirect: '/example/table/complex-table',
        name: 'example',
        meta: {
            title: 'example',
            icon: 'example'
        },
        children: [{
                path: '/example/table',
                component: _import('example/table/index'),
                redirect: '/example/table/complex-table',
                name: 'Table',
                meta: {
                    title: 'Table',
                    icon: 'table'
                },
                children: [
                    { path: 'dynamic-table', component: _import('example/table/dynamicTable/index'), name: 'dynamicTable', meta: { title: 'dynamicTable' } },
                    { path: 'drag-table', component: _import('example/table/dragTable'), name: 'dragTable', meta: { title: 'dragTable' } },
                    { path: 'inline-edit-table', component: _import('example/table/inlineEditTable'), name: 'inlineEditTable', meta: { title: 'inlineEditTable' } },
                    { path: 'tree-table', component: _import('example/table/treeTable/treeTable'), name: 'treeTableDemo', meta: { title: 'treeTable' } },
                    { path: 'custom-tree-table', component: _import('example/table/treeTable/customTreeTable'), name: 'customTreeTableDemo', meta: { title: 'customTreeTable' } },
                    { path: 'complex-table', component: _import('example/table/complexTable'), name: 'complexTable', meta: { title: 'complexTable' } }
                ]
            },
            { path: 'tab/index', icon: 'tab', component: _import('example/tab/index'), name: 'tab', meta: { title: 'tab' } }
        ]
    },

    {
        path: '/form',
        component: Layout,
        redirect: 'noredirect',
        name: 'form',
        meta: {
            title: 'form',
            icon: 'form'
        },
        children: [
            { path: 'create-form', component: _import('form/create'), name: 'createForm', meta: { title: 'createForm', icon: 'table' } },
            { path: 'edit-form', component: _import('form/edit'), name: 'editForm', meta: { title: 'editForm', icon: 'table' } }
        ]
    },

    {
        path: '/error',
        component: Layout,
        redirect: 'noredirect',
        name: 'errorPages',
        meta: {
            title: 'errorPages',
            icon: '404'
        },
        children: [
            { path: '401', component: _import('errorPage/401'), name: 'page401', meta: { title: 'page401', noCache: true } },
            { path: '404', component: _import('errorPage/404'), name: 'page404', meta: { title: 'page404', noCache: true } }
        ]
    },

    {
        path: '/error-log',
        component: Layout,
        redirect: 'noredirect',
        children: [{ path: 'log', component: _import('errorLog/index'), name: 'errorLog', meta: { title: 'errorLog', icon: 'bug' } }]
    },

    {
        path: '/excel',
        component: Layout,
        redirect: '/excel/export-excel',
        name: 'excel',
        meta: {
            title: 'excel',
            icon: 'excel'
        },
        children: [
            { path: 'export-excel', component: _import('excel/exportExcel'), name: 'exportExcel', meta: { title: 'exportExcel' } },
            { path: 'export-selected-excel', component: _import('excel/selectExcel'), name: 'selectExcel', meta: { title: 'selectExcel' } },
            { path: 'upload-excel', component: _import('excel/uploadExcel'), name: 'uploadExcel', meta: { title: 'uploadExcel' } }
        ]
    },

    {
        path: '/zip',
        component: Layout,
        redirect: '/zip/download',
        alwaysShow: true,
        meta: { title: 'zip', icon: 'zip' },
        children: [{ path: 'download', component: _import('zip/index'), name: 'exportZip', meta: { title: 'exportZip' } }]
    },

    {
        path: '/theme',
        component: Layout,
        redirect: 'noredirect',
        children: [{ path: 'index', component: _import('theme/index'), name: 'theme', meta: { title: 'theme', icon: 'theme' } }]
    },

    {
        path: '/clipboard',
        component: Layout,
        redirect: 'noredirect',
        children: [{ path: 'index', component: _import('clipboard/index'), name: 'clipboardDemo', meta: { title: 'clipboardDemo', icon: 'clipboard' } }]
    },

    {
        path: '/i18n',
        component: Layout,
        children: [{ path: 'index', component: _import('i18n-demo/index'), name: 'i18n', meta: { title: 'i18n', icon: 'international' } }]
    },

    { path: '*', redirect: '/404', hidden: true }
]
import { asyncRouterMap, constantRouterMap } from '@/router'
// 上面是两个对象,用来显示路由
/**
 * 
 * 当用户登录后,获取用role,将role和路由表每个页面的需要的权限作比较,
 * 调用router.addRoutes(store.getters.addRouters)添加用户可访问的路由
 * ,生成最终用户可访问的路由表。路由表存在vuex里面
 * 
 * 通过meta.role判断是否与当前用户权限匹配
 * @param roles
 * @param route
 */
function hasPermission(roles, route) {
    if (route.meta && route.meta.roles) {
        return roles.some(role => route.meta.roles.indexOf(role) >= 0)
    } else {
        return true
    }
}

/**
 * 递归过滤异步路由表,返回符合用户角色权限的路由表
 * @param asyncRouterMap
 * @param roles
 */
function filterAsyncRouter(asyncRouterMap, roles) {
    const accessedRouters = asyncRouterMap.filter(route => {
        if (hasPermission(roles, route)) {
            if (route.children && route.children.length) {
                route.children = filterAsyncRouter(route.children, roles)
            }
            return true
        }
        return false
    })
    return accessedRouters
}

const permission = {
    state: { // 权限
        routers: constantRouterMap,
        addRouters: []
    },
    mutations: {
        SET_ROUTERS: (state, routers) => {
            state.addRouters = routers
            state.routers = constantRouterMap.concat(routers) //全部显示 + 权限控制的路由
        }
    },
    actions: {
        GenerateRoutes({ commit }, data) {
            return new Promise(resolve => {
                const { roles } = data
                let accessedRouters
                if (roles.indexOf('admin') >= 0) {
                    accessedRouters = asyncRouterMap
                } else {
                    accessedRouters = filterAsyncRouter(asyncRouterMap, roles)
                }
                commit('SET_ROUTERS', accessedRouters) //要新加的路由表
                resolve()
            })
        }
    }
}

export default permission

我们理一下这个运行的过程
1.进入sidebar页面,通过mapGetters映射出permission_routers的值
2.在getters.js中可以看到permission_routers =》state.permission.routers
3.在permissions.js中可以看到

state: { // 权限
        routers: constantRouterMap,
        addRouters: []
    }

4.constantRouterMap 又是从 router/index导出的这样我们就把没有权限控制的路由加载上去了

我们完成了作者大大 具体实现的第一步

我们第一节讲main.js中有

import './permission' // permission control

进度条NProgress: https://segmentfault.com/q/1010000006653683/a-1020000006656644

router.beforeEach : http://blog.csdn.net/latency_cheng/article/details/78580161

store.dispatch('GetUserInfo').then(res => { // 根据state.token来获取拉取user_info
                    const roles = res.data.roles // note: roles must be a array! such as: ['editor','develop']
                        //actions中的GenerateRoutes
                        /**
                         *   GenerateRoutes({ commit }, data) {
                                  return new Promise(resolve => {
                                      const { roles } = data
                                      let accessedRouters
                                      if (roles.indexOf('admin') >= 0) {
                                          accessedRouters = asyncRouterMap
                                      } else {
                                          accessedRouters = filterAsyncRouter(asyncRouterMap, roles)
                                      }
                                      commit('SET_ROUTERS', accessedRouters) //要新加的路由表
                                      resolve()
                                  })
                              }
                         */
                    store.dispatch('GenerateRoutes', { roles }).then(() => { // 根据roles权限生成可访问的路由表
                        router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
                        next({...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
                    })
                })

流程
1.使用actions中的GenerateRoutes,参数是roles权限
2.如果roles数组中存在admin,则把所有accessedRouters=asyncRouterMap
3.如果不存在admin,则使用filterAsyncRouter进行过滤,过滤出权限对应的路由
4.使用router.addRoutes添加路由

function hasPermission(roles, route) { //如果元素有meta且roles存在
    if (route.meta && route.meta.roles) {
        // route.meta.roles存在在roles
        return roles.some(role => route.meta.roles.indexOf(role) >= 0)
    } else {
        return true
    }
}

/**
 * 递归过滤异步路由表,返回符合用户角色权限的路由表
 * @param asyncRouterMap
 * @param roles
 */
function filterAsyncRouter(asyncRouterMap, roles) {
    const accessedRouters = asyncRouterMap.filter(route => {
        //利用filter把符合条件的数组元素返回
        if (hasPermission(roles, route)) {
            //如果有children且长度不为0 则遍历
            if (route.children && route.children.length) {
                route.children = filterAsyncRouter(route.children, roles)
            }
            return true
        }
        return false
    })
    return accessedRouters
}
router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表

我们完成作者大大说的2,3
当用户登录后,获取用role,将role和路由表每个页面的需要的权限作比较,生成最终用户可访问的路由表。
调用router.addRoutes(store.getters.addRouters)添加用户可访问的路由。

##具体实现

<template>
  <div class="menu-wrapper">
    <template v-for="item in routes" v-if="!item.hidden&&item.children"><!--满足此条件:hidden:true 和有嵌套-->
      <!-- 只有一级 -->
      <router-link v-if="item.children.length===1 && !item.children[0].children&&!item.alwaysShow" :to="item.path+'/'+item.children[0].path" :key="item.children[0].name">
        <el-menu-item :index="item.path+'/'+item.children[0].path" :class="{'submenu-title-noDropdown':!isNest}">
          <svg-icon v-if="item.children[0].meta&&item.children[0].meta.icon" :icon-class="item.children[0].meta.icon"></svg-icon>
          <span v-if="item.children[0].meta&&item.children[0].meta.title">{{generateTitle(item.children[0].meta.title)}}</span>
        </el-menu-item>
      </router-link>
      <!-- 多级 -->
      <el-submenu v-else :index="item.name||item.path" :key="item.name">
        <template slot="title">
          <svg-icon v-if="item.meta&&item.meta.icon" :icon-class="item.meta.icon"></svg-icon>
          <span v-if="item.meta&&item.meta.title">{{generateTitle(item.meta.title)}}</span>
        </template>

        <template v-for="child in item.children" v-if="!child.hidden">
          <sidebar-item :is-nest="true" class="nest-menu" v-if="child.children&&child.children.length>0" :routes="[child]" :key="child.path"></sidebar-item>

          <router-link v-else :to="item.path+'/'+child.path" :key="child.name">
            <el-menu-item :index="item.path+'/'+child.path">
              <svg-icon v-if="child.meta&&child.meta.icon" :icon-class="child.meta.icon"></svg-icon>
              <span v-if="child.meta&&child.meta.title">{{generateTitle(child.meta.title)}}</span>
            </el-menu-item>
          </router-link>
        </template>
      </el-submenu>

    </template>
  </div>
</template>

<script>
import { generateTitle } from '@/utils/i18n'

export default {
  name: 'SidebarItem',
  props: {
    routes: {
      type: Array
    },
    isNest: {
      type: Boolean,
      default: false
    }
  },
  methods: {
    generateTitle
  }
}
</script>

父组件传入各种路由信息
子组件通过props接受
如果路由中指嵌套一层children,且只有一个数组元素,则只展示该层
如果嵌套一层children,且有多个数组元素,则需要遍历

Logo

前往低代码交流专区

更多推荐