您的位置:首页 > 产品设计 > UI/UE

学习vue后台管理框架3(权限控制)

2018-03-07 16:33 543 查看
作者文章:https://segmentfault.com/a/1190000009506097

登陆页面定向到:

{
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,且有多个数组元素,则需要遍历
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: