Commit cf56a6c30fd2d9b8d0ce887f69895c474df818be

Authored by 吉鹏
1 parent 80a28914e8
Exists in master

init role permission

1 1
2 如果你想要根据用户角色来动态生成侧边栏和 router,你可以使用该分支[permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control)
3 2
4 ## 相关项目 3 ## 相关项目
5 4
6 - [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) 5 - [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
7 6
8 - [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin) 7 - [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)
9 8
10 - [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) 9 - [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template)
11 10
12 - [awesome-project](https://github.com/PanJiaChen/vue-element-admin/issues/2312) 11 - [awesome-project](https://github.com/PanJiaChen/vue-element-admin/issues/2312)
13 12
14 写了一个系列的教程配套文章,如何从零构建后一个完整的后台项目: 13 写了一个系列的教程配套文章,如何从零构建后一个完整的后台项目:
15 14
16 - [手摸手,带你用 vue 撸后台 系列一(基础篇)](https://juejin.im/post/59097cd7a22b9d0065fb61d2) 15 - [手摸手,带你用 vue 撸后台 系列一(基础篇)](https://juejin.im/post/59097cd7a22b9d0065fb61d2)
17 - [手摸手,带你用 vue 撸后台 系列二(登录权限篇)](https://juejin.im/post/591aa14f570c35006961acac) 16 - [手摸手,带你用 vue 撸后台 系列二(登录权限篇)](https://juejin.im/post/591aa14f570c35006961acac)
18 - [手摸手,带你用 vue 撸后台 系列三 (实战篇)](https://juejin.im/post/593121aa0ce4630057f70d35) 17 - [手摸手,带你用 vue 撸后台 系列三 (实战篇)](https://juejin.im/post/593121aa0ce4630057f70d35)
19 - [手摸手,带你用 vue 撸后台 系列四(vueAdmin 一个极简的后台基础模板,专门针对本项目的文章,算作是一篇文档)](https://juejin.im/post/595b4d776fb9a06bbe7dba56) 18 - [手摸手,带你用 vue 撸后台 系列四(vueAdmin 一个极简的后台基础模板,专门针对本项目的文章,算作是一篇文档)](https://juejin.im/post/595b4d776fb9a06bbe7dba56)
20 - [手摸手,带你封装一个 vue component](https://segmentfault.com/a/1190000009090836) 19 - [手摸手,带你封装一个 vue component](https://segmentfault.com/a/1190000009090836)
21 20
22 ## Build Setup 21 ## Build Setup
23 22
24 ```bash 23 ```bash
25 24
25
26 # 进入项目目录 26 # 进入项目目录
27 cd gulu-admin 27 cd gulu-admin
28 28
29 # 安装依赖 29 # 安装依赖
30 npm install 30 npm install
31 31
32 # 建议不要直接使用 cnpm 安装以来,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题 32 # 建议不要直接使用 cnpm 安装以来,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题
33 npm install --registry=https://registry.npm.taobao.org 33 npm install --registry=https://registry.npm.taobao.org
34 34
35 # 启动服务 35 # 启动服务
36 npm run dev 36 npm run dev
37 ``` 37 ```
38 38
39 浏览器访问 [http://localhost:9528](http://localhost:9528) 39 浏览器访问 [http://localhost:9528](http://localhost:9528)
40 40
41 ## 发布 41 ## 发布
42 42
43 ```bash 43 ```bash
44 # 构建测试环境 44 # 构建测试环境
45 npm run build:stage 45 npm run build:stage
46 46
47 # 构建生产环境 47 # 构建生产环境
48 npm run build:prod 48 npm run build:prod
49 ``` 49 ```
50 50
51 ## 其它 51 ## 其它
52 52
53 ```bash 53 ```bash
54 # 预览发布环境效果 54 # 预览发布环境效果
55 npm run preview 55 npm run preview
56 56
57 # 预览发布环境效果 + 静态资源分析 57 # 预览发布环境效果 + 静态资源分析
58 npm run preview -- --report 58 npm run preview -- --report
59 59
60 # 代码格式检查 60 # 代码格式检查
61 npm run lint 61 npm run lint
62 62
63 # 代码格式检查并自动修复 63 # 代码格式检查并自动修复
64 npm run lint -- --fix 64 npm run lint -- --fix
65 ``` 65 ```
66 66
67 更多信息请参考 [使用文档](https://panjiachen.github.io/vue-element-admin-site/zh/)
68
69 ## Demo
70
71 ![demo](https://github.com/PanJiaChen/PanJiaChen.github.io/blob/master/images/demo.gif)
72
73
1 1
2
3 ## Build Setup
4
5
2 ```bash 6 ```bash
3 7
4 # enter the project directory 8 # enter the project directory
5 cd gulu-admin 9 cd gulu-admin
6 10
7 # install dependency 11 # install dependency
8 npm install 12 npm install
9 13
10 # develop 14 # develop
11 npm run dev 15 npm run dev
12 ``` 16 ```
13 17
14 This will automatically open http://localhost:9528 18 This will automatically open http://localhost:9528
15 19
16 ## Build 20 ## Build
17 21
18 ```bash 22 ```bash
19 # build for test environment 23 # build for test environment
20 npm run build:stage 24 npm run build:stage
21 25
22 # build for production environment 26 # build for production environment
23 npm run build:prod 27 npm run build:prod
24 ``` 28 ```
25 29
26 ## Advanced 30 ## Advanced
27 31
28 ```bash 32 ```bash
29 # preview the release environment effect 33 # preview the release environment effect
30 npm run preview 34 npm run preview
31 35
32 # preview the release environment effect + static resource analysis 36 # preview the release environment effect + static resource analysis
33 npm run preview -- --report 37 npm run preview -- --report
34 38
35 # code format check 39 # code format check
36 npm run lint 40 npm run lint
37 41
38 # code format check and auto fix 42 # code format check and auto fix
39 npm run lint -- --fix 43 npm run lint -- --fix
40 ``` 44 ```
41 45
42 Refer to [Documentation](https://panjiachen.github.io/vue-element-admin-site/guide/essentials/deploy.html) for more information 46 Refer to [Documentation](https://panjiachen.github.io/vue-element-admin-site/guide/essentials/deploy.html) for more information
43 47
44 ## Demo 48 ## Demo
45 49
46 ![demo](https://github.com/PanJiaChen/PanJiaChen.github.io/blob/master/images/demo.gif) 50 ![demo](https://github.com/PanJiaChen/PanJiaChen.github.io/blob/master/images/demo.gif)
47 51
48 ## Extra 52 ## Extra
49 53
50 If you want router permission && generate menu by user roles , you can use this branch [permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control) 54 If you want router permission && generate menu by user roles , you can use this branch [permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control)
51 55
52 For `typescript` version, you can use [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) (Credits: [@Armour](https://github.com/Armour)) 56 For `typescript` version, you can use [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) (Credits: [@Armour](https://github.com/Armour))
53 57
54 ## Related Project 58 ## Related Project
55 59
56 - [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) 60 - [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
57 61
58 - [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin) 62 - [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)
59 63
60 - [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) 64 - [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template)
61 65
62 - [awesome-project](https://github.com/PanJiaChen/vue-element-admin/issues/2312) 66 - [awesome-project](https://github.com/PanJiaChen/vue-element-admin/issues/2312)
63 67
64 ## Browsers support
65 68
src/layout/components/Sidebar/index.vue
1 <template> 1 <template>
2 <div :class="{'has-logo':showLogo}"> 2 <div :class="{'has-logo':showLogo}">
3 <logo v-if="showLogo" :collapse="isCollapse" /> 3 <logo v-if="showLogo" :collapse="isCollapse" />
4 <el-scrollbar wrap-class="scrollbar-wrapper"> 4 <el-scrollbar wrap-class="scrollbar-wrapper">
5 <el-menu 5 <el-menu
6 :default-active="activeMenu" 6 :default-active="activeMenu"
7 :collapse="isCollapse" 7 :collapse="isCollapse"
8 :background-color="variables.menuBg" 8 :background-color="variables.menuBg"
9 :text-color="variables.menuText" 9 :text-color="variables.menuText"
10 :unique-opened="false" 10 :unique-opened="false"
11 :active-text-color="variables.menuActiveText" 11 :active-text-color="variables.menuActiveText"
12 :collapse-transition="false" 12 :collapse-transition="false"
13 mode="vertical" 13 mode="vertical"
14 > 14 >
15 <sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" /> 15 <sidebar-item v-for="route in permission_routes" :key="route.path" :item="route" :base-path="route.path" />
16 </el-menu> 16 </el-menu>
17 </el-scrollbar> 17 </el-scrollbar>
18 </div> 18 </div>
19 </template> 19 </template>
20 20
21 <script> 21 <script>
22 import { mapGetters } from 'vuex' 22 import { mapGetters } from 'vuex'
23 import Logo from './Logo' 23 import Logo from './Logo'
24 import SidebarItem from './SidebarItem' 24 import SidebarItem from './SidebarItem'
25 import variables from '@/styles/variables.scss' 25 import variables from '@/styles/variables.scss'
26 26
27 export default { 27 export default {
28 components: { SidebarItem, Logo }, 28 components: { SidebarItem, Logo },
29 computed: { 29 computed: {
30 ...mapGetters([ 30 ...mapGetters([
31 'permission_routes',
31 'sidebar' 32 'sidebar'
32 ]), 33 ]),
33 routes() {
34 return this.$router.options.routes
35 },
36 activeMenu() { 34 activeMenu() {
37 const route = this.$route 35 const route = this.$route
38 const { meta, path } = route 36 const { meta, path } = route
39 // if set path, the sidebar will highlight the path you set 37 // if set path, the sidebar will highlight the path you set
40 if (meta.activeMenu) { 38 if (meta.activeMenu) {
41 return meta.activeMenu 39 return meta.activeMenu
42 } 40 }
43 return path 41 return path
44 }, 42 },
45 showLogo() { 43 showLogo() {
46 return this.$store.state.settings.sidebarLogo 44 return this.$store.state.settings.sidebarLogo
47 }, 45 },
48 variables() { 46 variables() {
49 return variables 47 return variables
50 }, 48 },
51 isCollapse() { 49 isCollapse() {
52 return !this.sidebar.opened 50 return !this.sidebar.opened
53 } 51 }
54 } 52 }
55 } 53 }
56 </script> 54 </script>
src/permission.js
1 import router from './router' 1 import router from './router'
2 import store from './store' 2 import store from './store'
3 import { Message } from 'element-ui' 3 import { Message } from 'element-ui'
4 import NProgress from 'nprogress' // progress bar 4 import NProgress from 'nprogress' // progress bar
5 import 'nprogress/nprogress.css' // progress bar style 5 import 'nprogress/nprogress.css' // progress bar style
6 import { getToken } from '@/utils/auth' // get token from cookie 6 import { getToken } from '@/utils/auth' // get token from cookie
7 import getPageTitle from '@/utils/get-page-title' 7 import getPageTitle from '@/utils/get-page-title'
8 8
9 NProgress.configure({ showSpinner: false }) // NProgress Configuration 9 NProgress.configure({ showSpinner: false }) // NProgress Configuration
10 10
11 const whiteList = ['/login'] // no redirect whitelist 11 const whiteList = ['/login'] // no redirect whitelist
12 12
13 router.beforeEach(async(to, from, next) => { 13 router.beforeEach(async(to, from, next) => {
14 // start progress bar 14 // start progress bar
15 NProgress.start() 15 NProgress.start()
16 16
17 // set page title 17 // set page title
18 document.title = getPageTitle(to.meta.title) 18 document.title = getPageTitle(to.meta.title)
19 19
20 // determine whether the user has logged in 20 // determine whether the user has logged in
21 const hasToken = getToken() 21 const hasToken = getToken()
22 22
23 if (hasToken) { 23 if (hasToken) {
24 if (to.path === '/login') { 24 if (to.path === '/login') {
25 // if is logged in, redirect to the home page 25 // if is logged in, redirect to the home page
26 next({ path: '/' }) 26 next({ path: '/' })
27 NProgress.done() 27 NProgress.done()
28 } else { 28 } else {
29 const hasGetUserInfo = store.getters.name 29 // determine whether the user has obtained his permission roles through getInfo
30 if (hasGetUserInfo) { 30 const hasRoles = store.getters.roles && store.getters.roles.length > 0
31 if (hasRoles) {
31 next() 32 next()
32 } else { 33 } else {
33 try { 34 try {
34 // get user info 35 // get user info
35 await store.dispatch('user/getInfo') 36 // note: roles must be a object array! such as: ['admin'] or ,['developer','editor']
37 const { roles } = await store.dispatch('user/getInfo')
36 38
37 next() 39 // generate accessible routes map based on roles
40 const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
41
42 // dynamically add accessible routes
43 router.addRoutes(accessRoutes)
44
45 // hack method to ensure that addRoutes is complete
46 // set the replace: true, so the navigation will not leave a history record
47 next({ ...to, replace: true })
38 } catch (error) { 48 } catch (error) {
39 // remove token and go to login page to re-login 49 // remove token and go to login page to re-login
40 await store.dispatch('user/resetToken') 50 await store.dispatch('user/resetToken')
41 Message.error(error || 'Has Error') 51 Message.error(error || 'Has Error')
42 next(`/login?redirect=${to.path}`) 52 next(`/login?redirect=${to.path}`)
43 NProgress.done() 53 NProgress.done()
44 } 54 }
45 } 55 }
46 } 56 }
47 } else { 57 } else {
48 /* has no token*/ 58 /* has no token*/
49 59
50 if (whiteList.indexOf(to.path) !== -1) { 60 if (whiteList.indexOf(to.path) !== -1) {
51 // in the free login whitelist, go directly 61 // in the free login whitelist, go directly
52 next() 62 next()
53 } else { 63 } else {
54 // other pages that do not have permission to access are redirected to the login page. 64 // other pages that do not have permission to access are redirected to the login page.
55 next(`/login?redirect=${to.path}`) 65 next(`/login?redirect=${to.path}`)
56 NProgress.done() 66 NProgress.done()
57 } 67 }
58 } 68 }
59 }) 69 })
60 70
61 router.afterEach(() => { 71 router.afterEach(() => {
62 // finish progress bar 72 // finish progress bar
63 NProgress.done() 73 NProgress.done()
64 }) 74 })
65 75
src/router/index.js
1 import Vue from 'vue' 1 import Vue from 'vue'
2 import Router from 'vue-router' 2 import Router from 'vue-router'
3 3
4 Vue.use(Router) 4 Vue.use(Router)
5 5
6 /* Layout */ 6 /* Layout */
7 import Layout from '@/layout' 7 import Layout from '@/layout'
8 8
9 /** 9 /**
10 * Note: sub-menu only appear when route children.length >= 1 10 * Note: sub-menu only appear when route children.length >= 1
11 * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html 11 * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
12 * 12 *
13 * hidden: true if set true, item will not show in the sidebar(default is false) 13 * hidden: true if set true, item will not show in the sidebar(default is false)
14 * alwaysShow: true if set true, will always show the root menu 14 * alwaysShow: true if set true, will always show the root menu
15 * if not set alwaysShow, when item has more than one children route, 15 * if not set alwaysShow, when item has more than one children route,
16 * it will becomes nested mode, otherwise not show the root menu 16 * it will becomes nested mode, otherwise not show the root menu
17 * redirect: noRedirect if set noRedirect will no redirect in the breadcrumb 17 * redirect: noRedirect if set noRedirect will no redirect in the breadcrumb
18 * name:'router-name' the name is used by <keep-alive> (must set!!!) 18 * name:'router-name' the name is used by <keep-alive> (must set!!!)
19 * meta : { 19 * meta : {
20 roles: ['admin','editor'] control the page roles (you can set multiple roles) 20 roles: ['admin','editor'] control the page roles (you can set multiple roles)
21 title: 'title' the name show in sidebar and breadcrumb (recommend set) 21 title: 'title' the name show in sidebar and breadcrumb (recommend set)
22 icon: 'svg-name' the icon show in the sidebar 22 icon: 'svg-name' the icon show in the sidebar
23 breadcrumb: false if set false, the item will hidden in breadcrumb(default is true) 23 breadcrumb: false if set false, the item will hidden in breadcrumb(default is true)
24 activeMenu: '/example/list' if set path, the sidebar will highlight the path you set 24 activeMenu: '/example/list' if set path, the sidebar will highlight the path you set
25 } 25 }
26 */ 26 */
27 27
28 /** 28 /**
29 * constantRoutes 29 * constantRoutes
30 * a base page that does not have permission requirements 30 * a base page that does not have permission requirements
31 * all roles can be accessed 31 * all roles can be accessed
32 */ 32 */
33 export const constantRoutes = [ 33 export const constantRoutes = [
34 { 34 {
35 path: '/login', 35 path: '/login',
36 component: () => import('@/views/login/index'), 36 component: () => import('@/views/login/index'),
37 hidden: true 37 hidden: true
38 }, 38 },
39 39
40 { 40 {
41 path: '/404', 41 path: '/404',
42 component: () => import('@/views/404'), 42 component: () => import('@/views/404'),
43 hidden: true 43 hidden: true
44 }, 44 },
45 45
46 { 46 {
47 path: '/', 47 path: '/',
48 component: Layout, 48 component: Layout,
49 redirect: '/dashboard', 49 redirect: '/dashboard',
50 children: [{ 50 children: [{
51 path: 'dashboard', 51 path: 'dashboard',
52 name: 'Dashboard', 52 name: 'Dashboard',
53 component: () => import('@/views/dashboard/index'), 53 component: () => import('@/views/dashboard/index'),
54 meta: { title: 'Dashboard', icon: 'dashboard' } 54 meta: { title: 'Dashboard', icon: 'dashboard' }
55 }] 55 }]
56 }, 56 },
57 57
58 { 58 {
59 path: '/example', 59 path: '/example',
60 component: Layout, 60 component: Layout,
61 redirect: '/example/table', 61 redirect: '/example/table',
62 name: 'Example', 62 name: 'Example',
63 meta: { title: 'Example', icon: 'example' }, 63 meta: { title: 'Example', icon: 'example' },
64 children: [ 64 children: [
65 { 65 {
66 path: 'table', 66 path: 'table',
67 name: 'Table', 67 name: 'Table',
68 component: () => import('@/views/table/index'), 68 component: () => import('@/views/table/index'),
69 meta: { title: 'Table', icon: 'table' } 69 meta: { title: 'Table', icon: 'table' }
70 }, 70 },
71 { 71 {
72 path: 'tree', 72 path: 'tree',
73 name: 'Tree', 73 name: 'Tree',
74 component: () => import('@/views/tree/index'), 74 component: () => import('@/views/tree/index'),
75 meta: { title: 'Tree', icon: 'tree' } 75 meta: { title: 'Tree', icon: 'tree' }
76 } 76 }
77 ] 77 ]
78 }, 78 },
79 79
80 { 80 {
81 path: '/form', 81 path: '/form',
82 component: Layout, 82 component: Layout,
83 children: [ 83 children: [
84 { 84 {
85 path: 'index', 85 path: 'index',
86 name: 'Form', 86 name: 'Form',
87 component: () => import('@/views/form/index'), 87 component: () => import('@/views/form/index'),
88 meta: { title: 'Form', icon: 'form' } 88 meta: { title: 'Form', icon: 'form' }
89 } 89 }
90 ] 90 ]
91 }, 91 }
92 ]
92 93
94 /**
95 * asyncRoutes
96 * the routes that need to be dynamically loaded based on user roles
97 */
98 export const asyncRoutes = [
93 { 99 {
94 path: '/nested', 100 path: '/nested',
95 component: Layout, 101 component: Layout,
96 redirect: '/nested/menu1', 102 redirect: '/nested/menu1',
97 name: 'Nested', 103 name: 'Nested',
98 meta: { 104 meta: {
99 title: 'Nested', 105 title: 'Nested',
100 icon: 'nested' 106 icon: 'nested'
101 }, 107 },
102 children: [ 108 children: [
103 { 109 {
104 path: 'menu1', 110 path: 'menu1',
105 component: () => import('@/views/nested/menu1/index'), // Parent router-view 111 component: () => import('@/views/nested/menu1/index'), // Parent router-view
106 name: 'Menu1', 112 name: 'Menu1',
107 meta: { title: 'Menu1' }, 113 meta: { title: 'Menu1' },
108 children: [ 114 children: [
109 { 115 {
110 path: 'menu1-1', 116 path: 'menu1-1',
111 component: () => import('@/views/nested/menu1/menu1-1'), 117 component: () => import('@/views/nested/menu1/menu1-1'),
112 name: 'Menu1-1', 118 name: 'Menu1-1',
113 meta: { title: 'Menu1-1' } 119 meta: { title: 'Menu1-1' }
114 }, 120 },
115 { 121 {
116 path: 'menu1-2', 122 path: 'menu1-2',
117 component: () => import('@/views/nested/menu1/menu1-2'), 123 component: () => import('@/views/nested/menu1/menu1-2'),
118 name: 'Menu1-2', 124 name: 'Menu1-2',
119 meta: { title: 'Menu1-2' }, 125 meta: { title: 'Menu1-2' },
120 children: [ 126 children: [
121 { 127 {
122 path: 'menu1-2-1', 128 path: 'menu1-2-1',
123 component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1'), 129 component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1'),
124 name: 'Menu1-2-1', 130 name: 'Menu1-2-1',
125 meta: { title: 'Menu1-2-1' } 131 meta: { title: 'Menu1-2-1' }
126 }, 132 },
127 { 133 {
128 path: 'menu1-2-2', 134 path: 'menu1-2-2',
129 component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2'), 135 component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2'),
130 name: 'Menu1-2-2', 136 name: 'Menu1-2-2',
131 meta: { title: 'Menu1-2-2' } 137 meta: { title: 'Menu1-2-2' }
132 } 138 }
133 ] 139 ]
134 }, 140 },
135 { 141 {
136 path: 'menu1-3', 142 path: 'menu1-3',
137 component: () => import('@/views/nested/menu1/menu1-3'), 143 component: () => import('@/views/nested/menu1/menu1-3'),
138 name: 'Menu1-3', 144 name: 'Menu1-3',
139 meta: { title: 'Menu1-3' } 145 meta: { title: 'Menu1-3' }
140 } 146 }
141 ] 147 ]
142 }, 148 },
143 { 149 {
144 path: 'menu2', 150 path: 'menu2',
145 component: () => import('@/views/nested/menu2/index'), 151 component: () => import('@/views/nested/menu2/index'),
146 meta: { title: 'menu2' } 152 meta: { title: 'menu2' }
147 } 153 }
148 ] 154 ]
149 }, 155 },
150 156
151 { 157 {
152 path: 'external-link', 158 path: 'external-link',
153 component: Layout, 159 component: Layout,
154 children: [ 160 children: [
155 { 161 {
156 path: 'https://panjiachen.github.io/vue-element-admin-site/#/', 162 path: 'https://panjiachen.github.io/vue-element-admin-site/#/',
157 meta: { title: 'External Link', icon: 'link' } 163 meta: { title: 'External Link', icon: 'link' }
158 } 164 }
159 ] 165 ]
160 }, 166 },
161 167
162 // 404 page must be placed at the end !!! 168 // 404 page must be placed at the end !!!
163 { path: '*', redirect: '/404', hidden: true } 169 { path: '*', redirect: '/404', hidden: true }
164 ] 170 ]
165 171
166 const createRouter = () => new Router({ 172 const createRouter = () => new Router({
167 // mode: 'history', // require service support 173 // mode: 'history', // require service support
168 scrollBehavior: () => ({ y: 0 }), 174 scrollBehavior: () => ({ y: 0 }),
169 routes: constantRoutes 175 routes: constantRoutes
170 }) 176 })
171 177
172 const router = createRouter() 178 const router = createRouter()
173 179
174 // Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465 180 // Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
175 export function resetRouter() { 181 export function resetRouter() {
176 const newRouter = createRouter() 182 const newRouter = createRouter()
177 router.matcher = newRouter.matcher // reset router 183 router.matcher = newRouter.matcher // reset router
178 } 184 }
179 185
180 export default router 186 export default router
181 187
src/store/getters.js
1 const getters = { 1 const getters = {
2 sidebar: state => state.app.sidebar, 2 sidebar: state => state.app.sidebar,
3 device: state => state.app.device, 3 device: state => state.app.device,
4 token: state => state.user.token, 4 token: state => state.user.token,
5 avatar: state => state.user.avatar, 5 avatar: state => state.user.avatar,
6 name: state => state.user.name 6 name: state => state.user.name,
7 roles: state => state.user.roles,
8 permission_routes: state => state.permission.routes
7 } 9 }
8 export default getters 10 export default getters
9 11
src/store/index.js
1 import Vue from 'vue' 1 import Vue from 'vue'
2 import Vuex from 'vuex' 2 import Vuex from 'vuex'
3 import getters from './getters' 3 import getters from './getters'
4 import app from './modules/app' 4 import app from './modules/app'
5 import permission from './modules/permission'
5 import settings from './modules/settings' 6 import settings from './modules/settings'
6 import user from './modules/user' 7 import user from './modules/user'
7 8
8 Vue.use(Vuex) 9 Vue.use(Vuex)
9 10
10 const store = new Vuex.Store({ 11 const store = new Vuex.Store({
11 modules: { 12 modules: {
12 app, 13 app,
14 permission,
13 settings, 15 settings,
14 user 16 user
15 }, 17 },
16 getters 18 getters
17 }) 19 })
18 20
19 export default store 21 export default store
20 22
src/store/modules/permission.js
File was created 1 import { asyncRoutes, constantRoutes } from '@/router'
2
3 /**
4 * Use meta.role to determine if the current user has permission
5 * @param roles
6 * @param route
7 */
8 function hasPermission(roles, route) {
9 if (route.meta && route.meta.roles) {
10 return roles.some(role => route.meta.roles.includes(role))
11 } else {
12 return true
13 }
14 }
15
16 /**
17 * Filter asynchronous routing tables by recursion
18 * @param routes asyncRoutes
19 * @param roles
20 */
21 export function filterAsyncRoutes(routes, roles) {
22 const res = []
23
24 routes.forEach(route => {
25 const tmp = { ...route }
26 if (hasPermission(roles, tmp)) {
27 if (tmp.children) {
28 tmp.children = filterAsyncRoutes(tmp.children, roles)
29 }
30 res.push(tmp)
31 }
32 })
33
34 return res
35 }
36
37 const state = {
38 routes: [],
39 addRoutes: []
40 }
41
42 const mutations = {
43 SET_ROUTES: (state, routes) => {
44 state.addRoutes = routes
45 state.routes = constantRoutes.concat(routes)
46 }
47 }
48
49 const actions = {
50 generateRoutes({ commit }, roles) {
51 return new Promise(resolve => {
52 let accessedRoutes
53 if (roles.includes('admin')) {
54 accessedRoutes = asyncRoutes || []
55 } else {
56 accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
57 }
58 commit('SET_ROUTES', accessedRoutes)
59 resolve(accessedRoutes)
60 })
61 }
62 }
63
64 export default {
65 namespaced: true,
66 state,
67 mutations,
68 actions
69 }
70
src/store/modules/user.js
1 import { login, logout, getInfo } from '@/api/user' 1 import { login, logout, getInfo } from '@/api/user'
2 import { getToken, setToken, removeToken } from '@/utils/auth' 2 import { getToken, setToken, removeToken } from '@/utils/auth'
3 import { resetRouter } from '@/router' 3 import { resetRouter } from '@/router'
4 4
5 const getDefaultState = () => { 5 const getDefaultState = () => {
6 return { 6 return {
7 token: getToken(), 7 token: getToken(),
8 name: '', 8 name: '',
9 avatar: '' 9 avatar: '',
10 roles: []
10 } 11 }
11 } 12 }
12 13
13 const state = getDefaultState() 14 const state = getDefaultState()
14 15
15 const mutations = { 16 const mutations = {
16 RESET_STATE: (state) => { 17 RESET_STATE: (state) => {
17 Object.assign(state, getDefaultState()) 18 Object.assign(state, getDefaultState())
18 }, 19 },
19 SET_TOKEN: (state, token) => { 20 SET_TOKEN: (state, token) => {
20 state.token = token 21 state.token = token
21 }, 22 },
22 SET_NAME: (state, name) => { 23 SET_NAME: (state, name) => {
23 state.name = name 24 state.name = name
24 }, 25 },
25 SET_AVATAR: (state, avatar) => { 26 SET_AVATAR: (state, avatar) => {
26 state.avatar = avatar 27 state.avatar = avatar
28 },
29 SET_ROLES: (state, roles) => {
30 state.roles = roles
27 } 31 }
28 } 32 }
29 33
30 const actions = { 34 const actions = {
31 // user login 35 // user login
32 login({ commit }, userInfo) { 36 login({ commit }, userInfo) {
33 const { username, password } = userInfo 37 const { username, password } = userInfo
34 return new Promise((resolve, reject) => { 38 return new Promise((resolve, reject) => {
35 login({ username: username.trim(), password: password }).then(response => { 39 login({ username: username.trim(), password: password }).then(response => {
36 const { data } = response 40 const { data } = response
37 commit('SET_TOKEN', data.token) 41 commit('SET_TOKEN', data.token)
38 setToken(data.token) 42 setToken(data.token)
39 resolve() 43 resolve()
40 }).catch(error => { 44 }).catch(error => {
41 reject(error) 45 reject(error)
42 }) 46 })
43 }) 47 })
44 }, 48 },
45 49
46 // get user info 50 // get user info
47 getInfo({ commit, state }) { 51 getInfo({ commit, state }) {
48 return new Promise((resolve, reject) => { 52 return new Promise((resolve, reject) => {
49 getInfo(state.token).then(response => { 53 getInfo(state.token).then(response => {
50 const { data } = response 54 const { data } = response
51 55
52 if (!data) { 56 if (!data) {
53 reject('Verification failed, please Login again.') 57 reject('Verification failed, please Login again.')
54 } 58 }
55 59
56 const { name, avatar } = data 60 const { roles, name, avatar } = data
61
62 // roles must be a non-empty array
63 if (!roles || roles.length <= 0) {
64 reject('getInfo: roles must be a non-null array!')
65 }
57 66
67 commit('SET_ROLES', roles)
58 commit('SET_NAME', name) 68 commit('SET_NAME', name)
59 commit('SET_AVATAR', avatar) 69 commit('SET_AVATAR', avatar)
60 resolve(data) 70 resolve(data)
61 }).catch(error => { 71 }).catch(error => {
62 reject(error) 72 reject(error)
63 }) 73 })
64 }) 74 })
65 }, 75 },
66 76
67 // user logout 77 // user logout
68 logout({ commit, state }) { 78 logout({ commit, state }) {
69 return new Promise((resolve, reject) => { 79 return new Promise((resolve, reject) => {
70 logout(state.token).then(() => { 80 logout(state.token).then(() => {
71 removeToken() // must remove token first 81 removeToken() // must remove token first
72 resetRouter() 82 resetRouter()
73 commit('RESET_STATE') 83 commit('RESET_STATE')
74 resolve() 84 resolve()
75 }).catch(error => { 85 }).catch(error => {
76 reject(error) 86 reject(error)
77 }) 87 })
78 }) 88 })
79 }, 89 },
80 90
81 // remove token 91 // remove token
82 resetToken({ commit }) { 92 resetToken({ commit }) {
83 return new Promise(resolve => { 93 return new Promise(resolve => {
84 removeToken() // must remove token first 94 removeToken() // must remove token first
85 commit('RESET_STATE') 95 commit('RESET_STATE')
86 resolve() 96 resolve()
87 }) 97 })
88 } 98 }
89 } 99 }
90 100
91 export default { 101 export default {
92 namespaced: true, 102 namespaced: true,
93 state, 103 state,
94 mutations, 104 mutations,
95 actions 105 actions
96 } 106 }
97 107
98 108
src/views/dashboard/index.vue
1 <template> 1 <template>
2 <div class="dashboard-container"> 2 <div class="dashboard-container">
3 <div class="dashboard-text">name: {{ name }}</div> 3 <div class="dashboard-text">name: {{ name }}</div>
4 <div class="dashboard-text">roles: <span v-for="role in roles" :key="role">{{ role }}</span></div>
4 </div> 5 </div>
5 </template> 6 </template>
6 7
7 <script> 8 <script>
8 import { mapGetters } from 'vuex' 9 import { mapGetters } from 'vuex'
9 10
10 export default { 11 export default {
11 name: 'Dashboard', 12 name: 'Dashboard',
12 computed: { 13 computed: {
13 ...mapGetters([ 14 ...mapGetters([
14 'name' 15 'name',
16 'roles'
15 ]) 17 ])
16 } 18 }
17 } 19 }
18 </script> 20 </script>
19 21
20 <style lang="scss" scoped> 22 <style lang="scss" scoped>
21 .dashboard { 23 .dashboard {
22 &-container { 24 &-container {
23 margin: 30px; 25 margin: 30px;
24 } 26 }
25 &-text { 27 &-text {
26 font-size: 30px; 28 font-size: 30px;
27 line-height: 46px; 29 line-height: 46px;
28 } 30 }
29 } 31 }
30 </style> 32 </style>
31 33