Commit cf56a6c30fd2d9b8d0ce887f69895c474df818be
1 parent
80a28914e8
Exists in
master
init role permission
Showing
10 changed files
with
124 additions
and
16 deletions
Show diff stats
README-zh.md
1 | 1 | |
2 | -如果你想要根据用户角色来动态生成侧边栏和 router,你可以使用该分支[permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control) | |
3 | 2 | |
4 | 3 | ## 相关项目 |
5 | 4 | |
... | ... | @@ -23,6 +22,7 @@ |
23 | 22 | |
24 | 23 | ```bash |
25 | 24 | |
25 | + | |
26 | 26 | # 进入项目目录 |
27 | 27 | cd gulu-admin |
28 | 28 | |
... | ... | @@ -64,3 +64,10 @@ npm run lint |
64 | 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 | + | ... | ... |
README.md
1 | 1 | |
2 | + | |
3 | +## Build Setup | |
4 | + | |
5 | + | |
2 | 6 | ```bash |
3 | 7 | |
4 | 8 | # enter the project directory |
... | ... | @@ -61,6 +65,4 @@ For `typescript` version, you can use [vue-typescript-admin-template](https://gi |
61 | 65 | |
62 | 66 | - [awesome-project](https://github.com/PanJiaChen/vue-element-admin/issues/2312) |
63 | 67 | |
64 | -## Browsers support | |
65 | 68 | |
66 | -Modern browsers and Internet Explorer 10+. | ... | ... |
src/layout/components/Sidebar/index.vue
... | ... | @@ -12,7 +12,7 @@ |
12 | 12 | :collapse-transition="false" |
13 | 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 | 16 | </el-menu> |
17 | 17 | </el-scrollbar> |
18 | 18 | </div> |
... | ... | @@ -28,11 +28,9 @@ export default { |
28 | 28 | components: { SidebarItem, Logo }, |
29 | 29 | computed: { |
30 | 30 | ...mapGetters([ |
31 | + 'permission_routes', | |
31 | 32 | 'sidebar' |
32 | 33 | ]), |
33 | - routes() { | |
34 | - return this.$router.options.routes | |
35 | - }, | |
36 | 34 | activeMenu() { |
37 | 35 | const route = this.$route |
38 | 36 | const { meta, path } = route | ... | ... |
src/permission.js
... | ... | @@ -26,15 +26,25 @@ router.beforeEach(async(to, from, next) => { |
26 | 26 | next({ path: '/' }) |
27 | 27 | NProgress.done() |
28 | 28 | } else { |
29 | - const hasGetUserInfo = store.getters.name | |
30 | - if (hasGetUserInfo) { | |
29 | + // determine whether the user has obtained his permission roles through getInfo | |
30 | + const hasRoles = store.getters.roles && store.getters.roles.length > 0 | |
31 | + if (hasRoles) { | |
31 | 32 | next() |
32 | 33 | } else { |
33 | 34 | try { |
34 | 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 | 48 | } catch (error) { |
39 | 49 | // remove token and go to login page to re-login |
40 | 50 | await store.dispatch('user/resetToken') | ... | ... |
src/router/index.js
... | ... | @@ -88,8 +88,14 @@ export const constantRoutes = [ |
88 | 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 | 100 | path: '/nested', |
95 | 101 | component: Layout, | ... | ... |
src/store/getters.js
... | ... | @@ -3,6 +3,8 @@ const getters = { |
3 | 3 | device: state => state.app.device, |
4 | 4 | token: state => state.user.token, |
5 | 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 | 10 | export default getters | ... | ... |
src/store/index.js
... | ... | @@ -2,6 +2,7 @@ import Vue from 'vue' |
2 | 2 | import Vuex from 'vuex' |
3 | 3 | import getters from './getters' |
4 | 4 | import app from './modules/app' |
5 | +import permission from './modules/permission' | |
5 | 6 | import settings from './modules/settings' |
6 | 7 | import user from './modules/user' |
7 | 8 | |
... | ... | @@ -10,6 +11,7 @@ Vue.use(Vuex) |
10 | 11 | const store = new Vuex.Store({ |
11 | 12 | modules: { |
12 | 13 | app, |
14 | + permission, | |
13 | 15 | settings, |
14 | 16 | user |
15 | 17 | }, | ... | ... |
src/store/modules/permission.js
... | ... | @@ -0,0 +1,69 @@ |
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 | +} | ... | ... |
src/store/modules/user.js
... | ... | @@ -6,7 +6,8 @@ const getDefaultState = () => { |
6 | 6 | return { |
7 | 7 | token: getToken(), |
8 | 8 | name: '', |
9 | - avatar: '' | |
9 | + avatar: '', | |
10 | + roles: [] | |
10 | 11 | } |
11 | 12 | } |
12 | 13 | |
... | ... | @@ -24,6 +25,9 @@ const mutations = { |
24 | 25 | }, |
25 | 26 | SET_AVATAR: (state, avatar) => { |
26 | 27 | state.avatar = avatar |
28 | + }, | |
29 | + SET_ROLES: (state, roles) => { | |
30 | + state.roles = roles | |
27 | 31 | } |
28 | 32 | } |
29 | 33 | |
... | ... | @@ -53,8 +57,14 @@ const actions = { |
53 | 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 | 68 | commit('SET_NAME', name) |
59 | 69 | commit('SET_AVATAR', avatar) |
60 | 70 | resolve(data) | ... | ... |
src/views/dashboard/index.vue
1 | 1 | <template> |
2 | 2 | <div class="dashboard-container"> |
3 | 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 | 5 | </div> |
5 | 6 | </template> |
6 | 7 | |
... | ... | @@ -11,7 +12,8 @@ export default { |
11 | 12 | name: 'Dashboard', |
12 | 13 | computed: { |
13 | 14 | ...mapGetters([ |
14 | - 'name' | |
15 | + 'name', | |
16 | + 'roles' | |
15 | 17 | ]) |
16 | 18 | } |
17 | 19 | } | ... | ... |