一个项目首先要解决的问题,同时也是核心的问题就是登陆和权限的问题,vue-element-admin将路由和左边侧栏进行了绑定,所以我们要解决的就是根据不同的role,生成不同的路由,然后动态渲染不同的侧边栏,同时还要解决在后台可以通过tree控件动态设置权限的效果,所以,先撸一下整体实现思路:

首先要说一下,vue-element-admin是怎么与后端进行交互的:用户端有交互提交---->>调用api下的.js请求函数(有些请求函数我单独放到service里了)--->>调用untils下的request.js(封装好的请求axiso)--->>获取到后端返回的response--->>进行客户端data渲染(后面我会细说一下request.js里封装的axiso)

1.登陆:当用户完成登陆后,要实现两个功能:一是:会向后端进行user_info的获取,并把相关信息写入cookie中,以实现整个系统的一个角色绑定。二是进行页面的跳转

2.权限:登陆后在permission.js中router.beforeEach方法中利用写在请求头里的cookie向后端获取menuList,然后利用vuex进行路由的拼接,最后通过router.addRouter挂载所计算好的路由并实现侧边栏的动态渲染

ok,下面撸一下具体的实现操作:

1,login页面的click事件中写:

handleLogin() {
      this.$refs.loginForm.validate(async valid => { //进行登陆验证
        if (valid) { //验证通过
          this.loading = true
          this.logins='登录中...'
          const { username, password } = this.loginForm
          try { //像后端获取user_info并存储到cookie中
            const res = await login({ user_name: username, passwd: password })
            setUserInfo(res.data) //user_info存储到cookie中
            this.loading = false
            this.logins='登录'
            this.$router.push({ path: '/' }) //跳转到首页
          } catch (e) {
            this.loading = false
            this.logins='登录'  
          }
        } else {
          console.log('error submit!!')
          return false
        }
      })
    }

其中setUserInfo()方法在utils下的auth.js,其中这里面可以写一些cookie的相关函数,并export出去,以供复用

// 设置Role_id
export function setRoleId(roleId) {
  return Cookies.set(RoleKey, roleId)
}  //这里只写一个,其他的set方法都类似于此

// 保存用户信息
export function setUserInfo(user) {
  const { role_id, agent_id, user_name,
    agent_name, province_id, level, price, city_name,
    city_ip
  } = user
  setRoleId(role_id)
  setAgentId(agent_id)
  setUserName(user_name)
  setAgentName(agent_name)
  setProvinceId(province_id)
  setLevel(level)
  setPrice(price / 100)
  setCityName(city_name)
  setCityIp(city_ip)
}

// 清除用户信息
export function removeUserInfo() {
  removeRoleId()
  removeAgentId()
  removeUserName()
  removeAgentName()
  removeProvinceId()
  removeLevel()
  removePrice()
  removeCityName()
  removeCityIp()
}

2.然后在在permission.js中router.beforeEach,一个是实现渲染侧边栏之前的一个token拦截,二是实现从后端获取menuList以供拼接动态路由

router.beforeEach(async(to, from, next) => {
  NProgress.start() // start progress bar
  if (getToken()) { // determine if there has token
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done() // if current page is dashboard will not trigger	afterEach hook, so manually handle it
    } else {
      if (!menuList) { //根据之前绑定的角色,通过cookie中的role向后端获取menulist
        const res = await fetchMenuList()
        menuList = res.data
        store.dispatch('GenerateRoutes', menuList)  //利用vuex,在store中的premission.js中进行路由的拼接
      }
      next()
    }
  } else {
    /* has no token*/
    if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
      next()
    } else {
      next('/login') // 否则全部重定向到登录页
      NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it
    }
  }
})

3.在store中的premission.js中进行路由的拼接,思路是在后端根据role获取到id,然后通过获取到的id与写在route.index里的动态路由表进行比对拼接,如果包含获取到的id的话就将路由表中的hidden设置为false,否则设为true来控制侧边栏是否显示这个menu

const checkedId = [] //设置一个数组,用来储存权限获取到的route的id
function getCheckedId(menulist) { //将根据角色从后端获取到的id进行foreach后存储到checkedId中
  menulist.forEach(item => {
    checkedId.push(item.id)
    if (item.childs.length) {
      getCheckedId(item.childs)
    }
  })
}

function findIdRoute(asyncRouterMap, id) { //通过比对checkedId和写在route中的路由表的id来进行hidden参数的true/false编写
  asyncRouterMap.forEach(item => {
    if (id === item.id) {
      item.hidden = false
    }
    if (item.children) {
      findIdRoute(item.children, id)
    }
  })
}

function getRoutes(asyncRouterMap) {  //调用findidroute函数
  checkedId.forEach(id => {
    findIdRoute(asyncRouterMap, +id)
  })
}

const permission = {  //vuex
  state: {
    routers: constantRouterMap,
    addRouters: []
  },
  mutations: {
    SET_ROUTERS: (state, routers) => { //将动态路由与静态路由进行concat拼接,形成完整路由表
      state.addRouters = routers
      state.routers = constantRouterMap.concat(routers)
    }
  },
//路由拼接
actions: {
    GenerateRoutes({ commit }, menulist) {
      getCheckedId(menulist)
      getRoutes(asyncRouterMap)
      return new Promise(resolve => {
        const accessedRouters = asyncRouterMap
        commit('SET_ROUTERS', accessedRouters)
        resolve()
      })
    }
  }

ok至此,路由已经根据role拼接完毕,下面介绍下侧边栏如何去渲染出来,其原理就是遍历之前算出来的permission_routers,然后通过vuex拿到之后动态v-for渲染而已

1:在sliderba的index中书写如下

<sidebar-item v-for="route in permission_routers" :key="route.name" :item="route" :base-path="route.path"/>
<script>
import { mapGetters } from 'vuex'
import SidebarItem from './SidebarItem'

export default {
  components: { SidebarItem },
  computed: {
    ...mapGetters([
      'permission_routers',
      'sidebar'
    ]),
    isCollapse() {
      return !this.sidebar.opened
    }
  }
}
</script>

2.在sliderba的sidebaritem中书写如下

hasOneShowingChild(children) {
      const showingChildren = children.filter(item => {
        if (item.hidden) {
          return false
        } else {
          // temp set(will be used if only has one showing child )
          this.onlyOneChild = item
          return true
        }
      })
      if (showingChildren.length === 1) {
        return true
      }
      return false
    },
    resolvePath(...paths) {
      return path.resolve(this.basePath, ...paths)
    }

下面讲一下前端登出:登出需要做两件事,一是清除token,一是将vuex管理的用户信息state清空

1.在view层面设置点击函数

//登出
logout() {
      this.$store.dispatch('FedLogOut').then(() => {
        location.reload()// In order to re-instantiate the vue-router object to avoid bugs
      })
    }

2.在store的user.js中配置user相关的state和书写登出actions

import { getToken, removeToken, removeUserInfo } from '@/utils/auth' //用于获取token和userinfo
import { logout } from '../../service/common' //用于后端登出请求

const user = {
  state: {
    token: getToken(),
    agent_id: '',
    agent_name: '',
    role_id: '', // 角色id
    role_name: '',
    user_name: '',
    user_id: ''
  },

  mutations: {
    SET_USERINFO: (state, user) => {
      state = {
        ...state,
        ...user
      }
      console.log(state)
    },
    CLEAR_USERINFO: (state) => {
      state.agent_id = ''
      state.agent_name = ''
      state.role_id = ''
      state.role_name = ''
      state.user_id = ''
      state.user_name = ''
    }
  },

  actions: {
    saveUserinfo({ commit, state }, user) {
      commit('SET_USERINFO', user)
    },
    // 后端登出
    LogOut({ commit, state }) {
      return new Promise((resolve, reject) => {
        logout().then(() => {
          commit('CLEAR_USERINFO')
          removeToken()
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },
    // 前端 登出
    FedLogOut({ commit }) {
      return new Promise(resolve => {
        commit('CLEAR_USERINFO') //请求mutations
        removeToken() //清除token
        removeUserInfo() //清除cookes
        resolve()
      })
    }
  }
}

export default user

好了,这样登陆,退出和权限,以及动态路由已经实现,会继续下更,如tree组件时间动态更改权限,以及其他一些vue-element-admind自带的组件的使用!