說明下 小編是19年1月開始接觸vue-element-admin的,一直沒時間整理筆記 就隨意整理一下吧
其實也沒什么說的 太簡單了 就說說動態路由鑒權的
原理是 兩個接口
流程其實是這樣的
1.登錄頁面按鈕點擊
2.vuex 里面的 login 方法被調用
3.vuex 里面的 login 方法被調用 完畢
4.監聽路由改變 然后獲取當前登錄的用戶角色
5.獲取當前用戶信息 獲取角色組 並保存登錄狀態,返回當前角色信息
6.通過 角色 和 所有路由 匹配出對應角色擁有的路由權限 返回路由組
7將上面獲取到的 路由權限 掛載到真實的路由上面去
然后說下參與路由權限 牽扯的文件吧
/src/view/login/index.vue 登錄頁面的入口文件
/src/store/modules/user.js vuex 的文件 全局方法
/src/permission.js 監聽路由改變后的js
/src/store/mudules/permission.js 通過 角色返回 登錄角色的對應路由列表的方法
src\views\permission\components/SwitchRoles.vue 切換角色的文件 這個登錄不走 切換角色才會走
不說了直接上代碼
///src/view/login/index.vue
///src/view/login/index.vue
<template> <div class="login-container"> <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" autocomplete="on" label-position="left"> <div class="title-container"> <h3 class="title">Login Form</h3> </div> <el-form-item prop="username"> <span class="svg-container"> <svg-icon icon-class="user" /> </span> <el-input ref="username" v-model="loginForm.username" placeholder="Username" name="username" type="text" tabindex="1" autocomplete="on" /> </el-form-item> <el-tooltip v-model="capsTooltip" content="Caps lock is On" placement="right" manual> <el-form-item prop="password"> <span class="svg-container"> <svg-icon icon-class="password" /> </span> <el-input :key="passwordType" ref="password" v-model="loginForm.password" :type="passwordType" placeholder="Password" name="password" tabindex="2" autocomplete="on" @keyup.native="checkCapslock" @blur="capsTooltip = false" @keyup.enter.native="handleLogin" /> <span class="show-pwd" @click="showPwd"> <svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" /> </span> </el-form-item> </el-tooltip> <el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">Login</el-button> <div style="position:relative"> <div class="tips"> <span>Username : admin</span> <span>Password : any</span> </div> <div class="tips"> <span style="margin-right:18px;">Username : editor</span> <span>Password : any</span> </div> <el-button class="thirdparty-button" type="primary" @click="showDialog=true"> Or connect with </el-button> </div> </el-form> <el-dialog title="Or connect with" :visible.sync="showDialog"> Can not be simulated on local, so please combine you own business simulation! ! ! <br> <br> <br> <social-sign /> </el-dialog> </div> </template> <script> import { validUsername } from '@/utils/validate' import SocialSign from './components/SocialSignin' export default { name: 'Login', components: { SocialSign }, data() { const validateUsername = (rule, value, callback) => { if (!validUsername(value)) { callback(new Error('Please enter the correct user name')) } else { callback() } } const validatePassword = (rule, value, callback) => { if (value.length < 6) { callback(new Error('The password can not be less than 6 digits')) } else { callback() } } return { loginForm: { username: 'admin', password: '111111' }, loginRules: { username: [{ required: true, trigger: 'blur', validator: validateUsername }], password: [{ required: true, trigger: 'blur', validator: validatePassword }] }, passwordType: 'password', capsTooltip: false, loading: false, showDialog: false, redirect: undefined, otherQuery: {} } }, watch: { $route: { handler: function(route) { const query = route.query if (query) { this.redirect = query.redirect this.otherQuery = this.getOtherQuery(query) } }, immediate: true } }, created() { // window.addEventListener('storage', this.afterQRScan) }, mounted() { if (this.loginForm.username === '') { this.$refs.username.focus() } else if (this.loginForm.password === '') { this.$refs.password.focus() } }, destroyed() { // window.removeEventListener('storage', this.afterQRScan) }, methods: { checkCapslock(e) { const { key } = e this.capsTooltip = key && key.length === 1 && (key >= 'A' && key <= 'Z') }, showPwd() { if (this.passwordType === 'password') { this.passwordType = '' } else { this.passwordType = 'password' } this.$nextTick(() => { this.$refs.password.focus() }) }, handleLogin() { console.info("1.登錄頁面按鈕點擊"); this.$refs.loginForm.validate(valid => { if (valid) { this.loading = true this.$store.dispatch('user/login', this.loginForm) .then(() => { console.info("3.vuex 里面的 login 方法被調用 完畢"); this.$router.push({ path: this.redirect || '/', query: this.otherQuery });//進入路由 this.loading = false }) .catch(() => { this.loading = false }) } else { console.log('error submit!!') return false } }) }, getOtherQuery(query) { return Object.keys(query).reduce((acc, cur) => { if (cur !== 'redirect') { acc[cur] = query[cur] } return acc }, {}) } // afterQRScan() { // if (e.key === 'x-admin-oauth-code') { // const code = getQueryObject(e.newValue) // const codeMap = { // wechat: 'code', // tencent: 'code' // } // const type = codeMap[this.auth_type] // const codeName = code[type] // if (codeName) { // this.$store.dispatch('LoginByThirdparty', codeName).then(() => { // this.$router.push({ path: this.redirect || '/' }) // }) // } else { // alert('第三方登錄失敗') // } // } // } } } </script> <style lang="scss"> /* 修復input 背景不協調 和光標變色 */ /* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */ $bg:#283443; $light_gray:#fff; $cursor: #fff; @supports (-webkit-mask: none) and (not (cater-color: $cursor)) { .login-container .el-input input { color: $cursor; } } /* reset element-ui css */ .login-container { .el-input { display: inline-block; height: 47px; width: 85%; input { background: transparent; border: 0px; -webkit-appearance: none; border-radius: 0px; padding: 12px 5px 12px 15px; color: $light_gray; height: 47px; caret-color: $cursor; &:-webkit-autofill { box-shadow: 0 0 0px 1000px $bg inset !important; -webkit-text-fill-color: $cursor !important; } } } .el-form-item { border: 1px solid rgba(255, 255, 255, 0.1); background: rgba(0, 0, 0, 0.1); border-radius: 5px; color: #454545; } } </style> <style lang="scss" scoped> $bg:#2d3a4b; $dark_gray:#889aa4; $light_gray:#eee; .login-container { min-height: 100%; width: 100%; background-color: $bg; overflow: hidden; .login-form { position: relative; width: 520px; max-width: 100%; padding: 160px 35px 0; margin: 0 auto; overflow: hidden; } .tips { font-size: 14px; color: #fff; margin-bottom: 10px; span { &:first-of-type { margin-right: 16px; } } } .svg-container { padding: 6px 5px 6px 15px; color: $dark_gray; vertical-align: middle; width: 30px; display: inline-block; } .title-container { position: relative; .title { font-size: 26px; color: $light_gray; margin: 0px auto 40px auto; text-align: center; font-weight: bold; } } .show-pwd { position: absolute; right: 10px; top: 7px; font-size: 16px; color: $dark_gray; cursor: pointer; user-select: none; } .thirdparty-button { position: absolute; right: 0; bottom: 6px; } @media only screen and (max-width: 470px) { .thirdparty-button { display: none; } } } </style>
/src/store/modules/user.js
import { login, logout, getInfo } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth' import router, { resetRouter } from '@/router' //狀態列表 const state = { token: getToken(), name: '', avatar: '', introduction: '', roles: [] } //修改vuex 的入口 const mutations = { SET_TOKEN: (state, token) => { state.token = token }, SET_INTRODUCTION: (state, introduction) => { state.introduction = introduction }, SET_NAME: (state, name) => { state.name = name }, SET_AVATAR: (state, avatar) => { state.avatar = avatar }, SET_ROLES: (state, roles) => { state.roles = roles } } //全局方法 const actions = { // user login 登錄方法 login({ commit }, userInfo) { console.info("2.vuex 里面的 login 方法被調用"); const { username, password } = userInfo return new Promise((resolve, reject) => { login({ username: username.trim(), password: password }).then(response => { //登錄成功后 保存 token const { data } = response commit('SET_TOKEN', data.token) setToken(data.token) resolve();//返回成功 }).catch(error => { reject(error) }) }) }, // get user info 獲取用戶信息 getInfo({ commit, state }) { return new Promise((resolve, reject) => { console.info("5.獲取當前用戶信息 獲取角色組 並保存登錄狀態,返回當前角色信息"); getInfo(state.token).then(response => { const { data } = response if (!data) { reject('Verification failed, please Login again.') } const { roles, name, avatar, introduction } = data // roles must be a non-empty array if (!roles || roles.length <= 0) { reject('getInfo: roles must be a non-null array!') } //修改用戶登錄的信息 調用 commit方法 commit('SET_ROLES', roles) commit('SET_NAME', name) commit('SET_AVATAR', avatar) commit('SET_INTRODUCTION', introduction); resolve(data) }).catch(error => { reject(error) }) }) }, // user logout 退出登錄 logout({ commit, state, dispatch }) { return new Promise((resolve, reject) => { logout(state.token).then(() => { commit('SET_TOKEN', '') commit('SET_ROLES', []) removeToken() resetRouter() // reset visited views and cached views // to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2485 dispatch('tagsView/delAllViews', null, { root: true }) resolve() }).catch(error => { reject(error) }) }) }, // remove token 清除登錄信息 token什么的 resetToken({ commit }) { return new Promise(resolve => { commit('SET_TOKEN', '') commit('SET_ROLES', []) removeToken() resolve() }) }, // dynamically modify permissions 更改角色 這個方法登錄的時候應該不調用 是在登錄成功后切換角色使用的 changeRoles({ commit, dispatch }, role) { //console.info("4.調用vuex changeRoles 方法 進入"); return new Promise(async resolve => { const token = role + '-token' commit('SET_TOKEN', token) setToken(token) const { roles } = await dispatch('getInfo');//調用這個文件里面的 getInfo方法 resetRouter() // generate accessible routes map based on roles const accessRoutes = await dispatch('permission/generateRoutes', roles, { root: true }) // dynamically add accessible routes router.addRoutes(accessRoutes) // reset visited views and cached views dispatch('tagsView/delAllViews', null, { root: true }) resolve() }) } } export default { namespaced: true, state, mutations, actions }
/src/permission.js
import router from './router'
import store from './store' import { Message } from 'element-ui' import NProgress from 'nprogress' // progress bar import 'nprogress/nprogress.css' // progress bar style import { getToken } from '@/utils/auth' // get token from cookie import getPageTitle from '@/utils/get-page-title' NProgress.configure({ showSpinner: false }) // NProgress Configuration const whiteList = ['/login', '/auth-redirect'] // no redirect whitelist router.beforeEach(async(to, from, next) => { // start progress bar NProgress.start() // set page title document.title = getPageTitle(to.meta.title) // determine whether the user has logged in const hasToken = getToken() if (hasToken) { if (to.path === '/login') { // if is logged in, redirect to the home page next({ path: '/' }) NProgress.done() } else { // determine whether the user has obtained his permission roles through getInfo const hasRoles = store.getters.roles && store.getters.roles.length > 0 if (hasRoles) { next() } else { try { // get user info // note: roles must be a object array! such as: ['admin'] or ,['developer','editor'] //監聽路由改變的時候 調用 獲取當前登錄用戶身份信息 console.info("4.監聽路由改變 然后獲取當前登錄的用戶角色"); const { roles } = await store.dispatch('user/getInfo');//拿到當前的用戶信息 角色 // generate accessible routes map based on roles const accessRoutes = await store.dispatch('permission/generateRoutes', roles); //將上面獲取到的 路由權限 掛載到真實的路由上面去 console.info("7.將上面獲取到的 路由權限 掛載到真實的路由上面去"); // dynamically add accessible routes router.addRoutes(accessRoutes) // hack method to ensure that addRoutes is complete // set the replace: true, so the navigation will not leave a history record next({ ...to, replace: true }) } catch (error) { // remove token and go to login page to re-login await store.dispatch('user/resetToken') Message.error(error || 'Has Error') next(`/login?redirect=${to.path}`) NProgress.done() } } } } else { /* has no token*/ if (whiteList.indexOf(to.path) !== -1) { // in the free login whitelist, go directly next() } else { // other pages that do not have permission to access are redirected to the login page. next(`/login?redirect=${to.path}`) NProgress.done() } } }) router.afterEach(() => { // finish progress bar NProgress.done() })
/src/store/mudules/permission.js
import { asyncRoutes, constantRoutes } from '@/router'
/**
* Use meta.role to determine if the current user has permission
* @param roles
* @param route
*/
function hasPermission(roles, route) {
if (route.meta && route.meta.roles) { return roles.some(role => route.meta.roles.includes(role)) } else { return true } } /** * Filter asynchronous routing tables by recursion * @param routes asyncRoutes * @param roles */ export function filterAsyncRoutes(routes, roles) { const res = [] routes.forEach(route => { const tmp = { ...route } if (hasPermission(roles, tmp)) { if (tmp.children) { tmp.children = filterAsyncRoutes(tmp.children, roles) } res.push(tmp) } }) return res } const state = { routes: [], addRoutes: [] } const mutations = { SET_ROUTES: (state, routes) => { state.addRoutes = routes state.routes = constantRoutes.concat(routes) } } const actions = { //通過 角色 和 所有路由 匹配出對應角色擁有的路由權限 generateRoutes({ commit }, roles) { console.info("6.通過 角色 和 所有路由 匹配出對應角色擁有的路由權限 返回路由組"); return new Promise(resolve => { let accessedRoutes if (roles.includes('admin')) { accessedRoutes = asyncRoutes || [] } else { accessedRoutes = filterAsyncRoutes(asyncRoutes, roles) } commit('SET_ROUTES', accessedRoutes) resolve(accessedRoutes) }) } } export default { namespaced: true, state, mutations, actions }
最后講下怎么動態切換身份加路由吧 權限開關
src\views\permission\components/SwitchRoles.vue
src\views\permission\page.vue
<template>
<div>
<div style="margin-bottom:15px;">
Your roles: {{ roles }}
</div>
Switch roles: <el-radio-group v-model="switchRoles"> <el-radio-button label="editor" /> <el-radio-button label="admin" /> </el-radio-group> </div> </template> <script> export default { computed: { roles() { return this.$store.getters.roles }, switchRoles: { get() { return this.roles[0] }, //設置權限 傳權限名稱 set(val) { this.$store.dispatch('user/changeRoles', val).then(() => { this.$emit('change') }) } } } } </script>
頁面里面 通過指令來區分身份 內置自帶的自定義指令
/src/views/permission/directive.vue
<template>
<div class="app-container">
<switch-roles @change="handleRolesChange" />
<div :key="key" style="margin-top:30px;">
<div>
<span v-permission="['admin']" class="permission-alert">
Only
<el-tag class="permission-tag" size="small">admin</el-tag> can see this
</span>
<el-tag v-permission="['admin']" class="permission-sourceCode" type="info"> v-permission="['admin']" </el-tag> </div> <div> <span v-permission="['editor']" class="permission-alert"> Only <el-tag class="permission-tag" size="small">editor</el-tag> can see this </span> <el-tag v-permission="['editor']" class="permission-sourceCode" type="info"> v-permission="['editor']" </el-tag> </div> <div> <span v-permission="['admin','editor']" class="permission-alert"> Both <el-tag class="permission-tag" size="small">admin</el-tag> and <el-tag class="permission-tag" size="small">editor</el-tag> can see this </span> <el-tag v-permission="['admin','editor']" class="permission-sourceCode" type="info"> v-permission="['admin','editor']" </el-tag> </div> </div> <div :key="'checkPermission'+key" style="margin-top:60px;"> <aside> In some cases, using v-permission will have no effect. For example: Element-UI's Tab component or el-table-column and other scenes that dynamically render dom. You can only do this with v-if. <br> e.g. </aside> <el-tabs type="border-card" style="width:550px;"> <el-tab-pane v-if="checkPermission(['admin'])" label="Admin"> Admin can see this <el-tag class="permission-sourceCode" type="info"> v-if="checkPermission(['admin'])" </el-tag> </el-tab-pane> <el-tab-pane v-if="checkPermission(['editor'])" label="Editor"> Editor can see this <el-tag class="permission-sourceCode" type="info"> v-if="checkPermission(['editor'])" </el-tag> </el-tab-pane> <el-tab-pane v-if="checkPermission(['admin','editor'])" label="Admin-OR-Editor"> Both admin or editor can see this <el-tag class="permission-sourceCode" type="info"> v-if="checkPermission(['admin','editor'])" </el-tag> </el-tab-pane> </el-tabs> </div> </div> </template> <script> import permission from '@/directive/permission/index.js' // 權限判斷指令 import checkPermission from '@/utils/permission' // 權限判斷函數 import SwitchRoles from './components/SwitchRoles' export default { name: 'DirectivePermission', components: { SwitchRoles }, directives: { permission }, data() { return { key: 1 // 為了能每次切換權限的時候重新初始化指令 } }, methods: { checkPermission, handleRolesChange() { this.key++ } } } </script> <style lang="scss" scoped> .app-container { /deep/ .permission-alert { width: 320px; margin-top: 15px; background-color: #f0f9eb; color: #67c23a; padding: 8px 16px; border-radius: 4px; display: inline-block; } /deep/ .permission-sourceCode { margin-left: 15px; } /deep/ .permission-tag { background-color: #ecf5ff; } } </style>
好了 重頭戲來了 動態修改角色的權限 這個可是一個好東西 直接上代碼吧