vue+elementui搭建後臺管理界面(7 vuex和mockjs的使用)

將權限管理應用到系統,首先作好登陸, 點擊登陸按鈕後,觸發如下動做css

  • vuex 中的 login 動做,設置 cookie
  • vuex 中的 getuserinfo , 獲取權限、用戶名、頭像等
    因爲目前未使用鏈接後端服務器,因此使用 mockjs 攔截請求並返回。
    github中查看

1 全局請求攔截

使用axios 封裝好請求和響應前端

src/utils/request.jsvue

import axios from 'axios'

const clearRequest = {
  source: {
    token: null,
    cancel: null
  }
}
const cancelToken = axios.CancelToken
const source = cancelToken.source()

// 建立 axios 實例
const service = axios.create({
  cancelToken: source.token,
  timeout: 6000,  // 請求超時時間
})

// request 攔截器
service.interceptors.request.use(
  config => {
    config.cancelToken = clearRequest.source.token
    return config
  },
  error => {
    return Promise.reject(error)
  }
)

// response 攔截器
service.interceptors.response.use(
  resp => resp,
  error => {
    return Promise.reject(error)
  }
)

export { clearRequest }
export default service

2 封裝一些經常使用的函數

src/utils/index.jsios

export function param2Obj(url){
  const search = url.split('?')[1]
  if(!search){
      return {}
  }
  return JSON.parse(
      '{"' +
      decodeURIComponent(search)
          .replace(/"/g, '\\"')
          .replace(/&/g, '","')
          .replace(/=/g, '":"') + 
      '"}'
  )
}

src/utils/auth.jsgit

import Cookies from 'js-cookie'

const tokenKey = 'X-Token'

export function getToken(){
  return Cookies.get(tokenKey)
}

export function setToken(token){
  return Cookies.set(tokenKey, token)
}

export function removeToken(){
  return Cookies.remove(tokenKey)
}

3 編寫登陸api

src/api/login.js 文件github

import request from '@/utils/request'

export function loginByUsernameApi(username, password){
  return request({
    url: '/api/auth/api-token-auth',
    method: 'post',
    data: {
      username,
      password
    }
  })
}

export function getUserInfoApi(token){
  return request({
    url: '/api/userinfo',
    method: 'get',
    params: {token}
  })
}

export function logoutApi(){
  return request({
    url: '/api/auth/logout',
    method: 'post'
  })
}

4 mock 攔截

src/mock/index.jsvuex

import Mock from 'mockjs'
import login from './login'

// 登陸相關
Mock.mock(/\/api\/auth\/api-token-auth/, 'post', login.loginByUsername)
Mock.mock(/\/api\/auth\/logout/, 'post', login.logout)
Mock.mock(/\/api\/userinfo/, 'get', login.getUserInfo)

export default Mock

src/mock/login.jsaxios

import { param2Obj } from '@/utils'

const usermap = {
  admin: {
    token: 'admin',
    introduction: '我是超級管理員',
    name: 'Super Admin',
    pass: 'e10adc3949ba59abbe56e057f20f883e',
    roles: ['admin']
  },
  developer: {
    token: 'developer',
    introduction: '我是開發',
    name: '工程師小王',
    pass: 'e10adc3949ba59abbe56e057f20f883e',
    roles: ['/system', '/system/permit', '/system/permit/account']
  }
}

export default {
  loginByUsername: config => {
    const { username, password } = JSON.parse(config.body)
    console.log('loginByUsername username, password: ', username, password)
    if(username === 'admin'){
      if(usermap[username].pass === password){
      return usermap['admin']
      }else{
        return []
      }
    }
    return usermap[username]
  },
  getUserInfo: config => {
    console.log('getUserInfo config: ', config)
    const { token } = param2Obj(config.url)
    let tok = false
    for(let key in usermap){
      if(token.indexOf(usermap[key].token) !== -1){
        tok = usermap[key]
        break;
      }
    }
    return tok

  },
  logout: () => 'success'
}

5 vuex 的使用

src/store/index.js後端

import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import permission from './modules/permissions'
import getters from './getters'

Vue.use(Vuex)

const store = new Vuex.Store({
  modules: {
    permission,
    user,
  },
  getters
})
export default store

src/store/modules/user.jsapi

import { loginByUsernameApi, logoutApi, getUserInfoApi } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'

const user = {
  state: {
    token: getToken(),
    name: '',
    avatar: '',
    roles: []
  },
  mutations: {
    SET_TOKEN: (state, token) => {
      state.token = token
    },
    SET_NAME: ( state, name) => {
      state.name = name
    },
    SET_AVATAR: (state, avatar) => {
        state.avatar = avatar
    },
    SET_ROLES: (state, roles) => {
      state.roles = roles
    }
  },
  actions: {
    // 登入
    LoginByUsername({commit}, userinfo){
      const username = userinfo.username.trim()
      return new Promise((resolve, reject) => {
        loginByUsernameApi(username, userinfo.password)
        .then(resp => {
          const data = resp.data
          setToken(data.token)
          console.log('in LoginByUsername, setToken: ', data.token)
          commit('SET_TOKEN', data.token)
          resolve()
        })
        .catch(err => {
          reject(err)
        })
      })
    },
    // 獲取用戶權限等
    GetUserInfo({commit, state}) {
      console.log('in GetUserInfo')
      return new Promise((resolve, reject) => {
        getUserInfoApi(state.token)
        .then(resp => {
          if(!resp.data){
            reject('error')
          }
          const data = resp.data
          if(data.roles && data.roles.length){
            commit('SET_ROLES', data.roles)
          }else {
            reject('getUserInfoApi: roles must be a non-null array!')
          }
          if(data.name){
            commit('SET_NAME', data.name)
          }
          if(data.avatar){
            commit('SET_AVATAR', data.avatar)
          }
          resolve(resp)
        })
        .catch(err => {
          reject(err)
        })
      })
    },
    // 登出
    LogOut({commit, state}){
      return new Promise((resolve, reject) => {
        logoutApi(state.token)
        .then(() => {
          commit('SET_TOKEN', '')
          commit('SET_ROLES', [])
          removeToken()
          resolve()
        })
        .catch(err => {
          reject(err)
        })
      })
    },
    // 前端登出
    FedLogOut({commit}){
      return new Promise(resolve => {
        commit('SET_TOKEN', '')
        removeToken()
        resolve()
      })
    }
  }
}

export default user

src/store/modules/permissions.js

import { asyncRouterMap, constantRouterMap } from '@/router'

/**
 * 經過 meta.role 判斷是否與當前用戶權限匹配
 */
function hasRoles (roles, route){
  if(route.meta && route.meta.roles){
    return roles.some(role => route.meta.roles.includes(role))
  }else{
    return true
  }
}

/**
 * 遞歸過濾異步路由表,返回符合用戶角色權限的路由表
 */
function filterAsyncRouter(asyncRouterMap, roles){
  const accessedRouters = asyncRouterMap.filter(route => {
    // 404
    if(route.path === '*'){
      return true 
    }else if(hasRoles(roles, route)){
      if(route.children && route.children.length){
        route.children = filterAsyncRouter(route.children, roles)
      }
      return true
    }
    return false
  })
  return accessedRouters
}

// 在有權限的路由表裏,查找是否有到目標path的路由
// 爲了保持路由惟一性,拼接父子路由
function hasDestRoute (froute, permitRouterMap, to) {
  let r = froute === '/' ? '' : froute
  return permitRouterMap.some(route => {
    let path = r + '/' + route.path
    if (to.path.indexOf(path) !== -1) {
      return true;
    }
    if (route.children && route.children.length) { //若是有孩子就遍歷孩子
      return hasDestRoute(path, route.children, to)
    }
  })
}

const permission = {
  state: {
    routers: constantRouterMap,
    addRouters: [],
    sidebar_routers: {},
  },
  mutations: {
    SET_ROUTERS: (state, routers) => {
      state.addRouters = routers,
      state.routers = constantRouterMap.concat(routers)
    },
    SET_NOW_ROUTERS: (state, to) => {
      console.log('in SET_NOW_ROUTERS')
      // 因爲首頁重定向到 /dashboard,而且不參與權限控制,特殊處理
      if(to.path === '/dashboard'){
        let dashboard = state.routers.filter(v => v.path === '/' )
        state.sidebar_routers = dashboard[0]
      }else{
          // 遞歸訪問 accessedRouters,找到包含to 的那個路由對象,設置給 sidebar_routers
          state.addRouters.forEach(e => {
          if (e.children && e.children.length) {
              if ( hasDestRoute(e.path, e.children, to)){
                  if(state.sidebar_routers.path){
                      // 存在 sidebar_routers 且與目標路由不一樣
                      if(state.sidebar_routers.path !== e.path){
                          state.sidebar_routers = e;
                      }
                  }else{
                      state.sidebar_routers = e;
                  }
              }
          }
          })
      }
    }
  },
  actions: {
    GenerateRoutes({commit}, data) {
      console.log('in GenerateRoutes')
      return new Promise(resolve => {
        const {roles} = data
        let accessedRouters
        if(roles.includes('admin')){
          accessedRouters = asyncRouterMap
        }else{
          accessedRouters = filterAsyncRouter(asyncRouterMap, roles)
        }
        commit('SET_ROUTERS', accessedRouters)
        resolve()
      })
    },
    GenSidebarRoutes({commit}, data) {
      console.log('in GenSidebarRoutes')
      return new Promise(resolve => {
        commit('SET_NOW_ROUTERS', data)
        resolve()
      })
    }
  }
}

export default permission

src/store/getters.js

const getters = {
  token: state => state.user.token,
  roles: state => state.user.roles,
  avatar: state => state.user.avatar,
  name: state => state.user.name,
  addRouters: state => state.permission.addRouters,
  permission_routers: state => state.permission.routers,
  sidebar_routers: state => state.permission.sidebar_routers,
}

export default getters

6 修改 login 頁面

src/views/TheLogin.vue

handleSubmit(event) {
  this.$refs.ruleForm2.validate((valid) => {
    if (valid) {
      this.logining = true;
      // 觸發 vuex 中 LoginByUsername 事件
      const username = this.ruleForm2.username
      let password = md5(this.ruleForm2.password)
      if(this.hidePassword !== '' && this.ruleForm2.password === '********'){
        password = this.hidePassword
      }
      this.$store.dispatch('LoginByUsername', {'username': username, 'password': password})
        .then(() => {
          this.logining = false
          if(this.rememberme){
            this.setCookie(this.ruleForm2.username, password, 7)
          }else{
            this.clearCookie()
          }
          // 重定向到首頁
          this.$router.push({ path: this.redirect || '/' })
        })
        .catch(err => {
          this.logining = false
          this.$alert(err, {
            type: 'warning',
            confirmButtonText: 'ok'
          })
        })
    } else {
      console.log('error submit!');
      return false;
    }
  })
}

7 修改main 主入口

src/main.js

/** ...*/
import store from './store'
import '@/login'
import '@/mock'

Vue.use(ElementUI)
Vue.config.productionTip = false


/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,
  render: h => h(App)
})

8 關鍵的權限控制

src/login.js

import router from './router'
import NProgress from 'nprogress'
import {clearRequest} from '@/utils/request'
import axios from 'axios'
import store from './store'

function hasPermission(roles, permissionRoles){
  if(roles.indexOf('admin') >= 0){
    return true  // admin 權限  直接經過
  }
  // 沒有配置權限的菜單直接進入
  if(! permissionRoles){
    return true
  }
  return roles.some(role => permissionRoles.indexOf(role) >= 0)
}

const whiteList = ['/login', ]   // 不重定向白名單
router.beforeEach((to, from, next) => {
  console.log('to.path: ' + to.path)
  console.log('store.getters.token: ' + store.getters.token)
  // 切換路由時清空上個路由未完成的全部請求
  const cancelToken = axios.CancelToken
  clearRequest.source.cancel && clearRequest.source.cancel()
  clearRequest.source = cancelToken.source()

  if(whiteList.indexOf(to.path) !== -1){
    next()
  }else{
    if(store.getters.token){
      if(to.path === '/login'){
        next({path: '/'})
        NProgress.done()
      }else{
        // 拉取用戶信息
        if(store.getters.roles.length === 0){
          store.dispatch('GetUserInfo')
          .then(resp => {
            const roles = resp.data.roles
            console.log('roles: ', roles)
            // 根據權限生成可訪問的路由表
            store.dispatch('GenerateRoutes', {roles})
            .then(() => {
              // 動態添加路由表
              router.addRoutes(store.getters.addRouters)
              next({...to, replace: true})  // 確保 addRouters 已完成
            })
          })
          .catch(err => {
            store.dispatch('FedLogOut')
            .then(() => {
              console.log('認證失敗,請從新登錄')
              next({path: '/login'})
            })
          })
        }else{
          console.log('call GenSidebarRoutes')
          store.dispatch('GenSidebarRoutes', to)
          .then(() => {
            if(hasPermission(store.getters.roles, to.meta.role)){
              next()
            }else{
              next({path: '/', query: {noGoBack: true}})
            }
          })
        }
      }
    }else{
      // 重定向到 /login
      next({path: '/login', query: {redirect: to.fullpath}})
      NProgress.done()
    }
  }
})

router.afterEach(() => {
  NProgress.done()
})

目錄結構

│  App.vue
│  login.js
│  main.js
├─api
│      login.js
│      system.js
├─assets
│      logo.png
├─components
│      HelloWorld.vue
│      Navbar.vue
│      NavbarItem.vue
│      Sidebar.vue
│      SidebarItem.vue
├─containers
│      Container.vue
├─mock
│      article.js
│      index.js
│      login.js
│      system.js
├─router
│  │  index.js
│  │
│  └─modules
│          system.js
├─store
│  │  getters.js
│  │  index.js
│  │
│  └─modules
│          permissions.js
│          user.js
├─styles
│      animate.scss
│      browser-prefix.scss
│      index.scss
├─utils
│      auth.js
│      index.js
│      request.js
└─views
    │  404.vue
    │  Home.vue
    │  TheLogin.vue
    ├─article
    │      index.vue
    ├─dashboard
    │      index.vue
    ├─SidebarMenu
    │      index.vue
    └─system
        └─permit
            │  account.vue
            │  accountgroup.vue
            │  authorize.vue
            │  index.vue
            │  role.vue
            └─components
                    DialogRoleMenu.vue

admin 用戶權限:

developer 權限:

相關文章
相關標籤/搜索