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

将权限管理应用到系统,首先做好登录, 点击登录按钮后,触发以下动作

  • vuex 中的 login 动作,设置 cookie
  • vuex 中的 getuserinfo , 获取权限、用户名、头像等
    由于目前未使用连接后端服务器,所以使用 mockjs 拦截请求并返回。
    github中查看

1 全局请求拦截

使用axios 封装好请求和响应

src/utils/request.js

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.js

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.js

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 文件

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.js

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.js

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.js

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 权限:

posted @ 2018-12-09 15:14  葡萄不吐皮  阅读(6715)  评论(0编辑  收藏  举报