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 | + | |
| 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 | } | ... | ... |