当前位置: 首页 > 工具软件 > vue-api-query > 使用案例 >

vue-element-admin 项目运行流程梳理

巫马磊
2023-12-01

前言

没有看过整个同学不推荐看这篇文章,如果对项目没有一个大致了解的花,你会感觉云里雾里

下面就是流程梳理:

我这里就直接开是往下写了,不搞花的:

  1. 当我们运行npm run dev后自然是开始运行整个项目了,运行的入口在哪?此时我们肯定就想到main.js,没错项目的入口就是main.js,接下来我们看看main.js的代码:
import Vue from 'vue'
import Cookies from 'js-cookie'
import 'normalize.css/normalize.css' // a modern alternative to CSS resets
import Element from 'element-ui'
import './styles/element-variables.scss'
import enLang from 'element-ui/lib/locale/lang/en'// 如果使用中文语言包请默认支持,无需额外引入,请删除该依赖
import '@/styles/index.scss' // global css
import App from './App'
import store from './store'
import router from './router'
import './icons' // icon
import './permission' // permission control
import './utils/error-log' // error log
import * as filters from './filters' // global filters

/**
 * If you don't want to use mock-server
 * you want to use MockJs for mock api
 * you can execute: mockXHR()
 *
 * Currently MockJs will be used in the production environment,
 * please remove it before going online ! ! !
 */
if (process.env.NODE_ENV === 'production') {
  const { mockXHR } = require('../mock')
  mockXHR()
}

Vue.use(Element, {
  size: Cookies.get('size') || 'medium', // set element-ui default size
  locale: enLang // 如果使用中文,无需设置,请删除
})

// register global utility filters
Object.keys(filters).forEach(key => {
  Vue.filter(key, filters[key])
})

Vue.config.productionTip = false

new Vue({
  el: '#app',
  router,
  store,
  render: h => h(App)
})

其实跟其他项目一样。main.js的主要作用都是挂载app,但这里需要你注意一下的是import './permission' // permission control,这项代码的作用是引入了下面的代码,作用是对我们全局的路由访问进行限制,只有满足了特点给条件才能进行相应的页面跳转,具体作用开下面的代码:

// 这里是白名单,也就是说还没登陆获取token时可以访问的页面
const whiteList = ['/login', '/auth-redirect'] // no redirect whitelist

//这里是全局路由守卫
router.beforeEach(async(to, from, next) => {
  // 从cookie中取得token
  const hasToken = getToken()
  
  // 如果有token 也就是已经登陆的情况下
  if (hasToken) {
    // 并且要前往的路径是'/login'  则返回 '/' 
    if (to.path === '/login') {
      next({ path: '/' })
    } else {
      // 从store中取得用户的 roles, 也就是用户的权限 并且用户的权限数组必须有一个以上
      const hasRoles = store.getters.roles && store.getters.roles.length > 0
      // 有权限则直接进入
      if (hasRoles) {
        next()
      } else {
        // 没有权限的话
        try {
          // 获取用户信息
          const { roles } = await store.dispatch('user/getInfo')
          // 生成可访问路由
          const accessRoutes = await store.dispatch('permission/generateRoutes',                                  roles)
          // 将可访问路由添加到路由上
          router.addRoutes(accessRoutes)
          // 进入路由
          next({ ...to, replace: true })
        } catch (error) {
          // 如果出现异常  清空路由 
          await store.dispatch('user/resetToken')
          // Message提示错误
          Message.error(error || 'Has Error')
          // 跳到login页面重新登陆
          next(`/login?redirect=${to.path}`)
        }
      }
    }
  } else {
    // 没有token 也就是没有登陆的情况下  
    // 判断是否是白名单(也就是说不需要登陆就可以访问的路由)
    if (whiteList.indexOf(to.path) !== -1) {
      next()
    } else {
      // 其他的一律给我跳到login页面 老老实实的进行登陆
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})
  1. main.js挂载完了就开始显示页面,显示那个,看路由router的默认路由
  {
    path: '/redirect',
    component: Layout,
    hidden: true,
    children: [
      {
        path: '/redirect/:path(.*)',
        component: () => import('@/views/redirect/index')
      }
    ]
  },

这里其实就是要先是我们的默认路由可是能显示吗?忘记了我们上面所说的premission了吗?我们有taken吗显然是没有的,所以我们就得跳转到我们的登陆页面:

  1. 登陆页面中的表单验证这些我就不讲了,毕竟我这里分析的是项目的运行流程,我们直接看点击登陆后会发生什么,登陆后的回调函数:
    handleLogin() {
    //validate是表单验证函数,里面的参数未boolean值,为表单是否通过验证
      this.$refs.loginForm.validate(valid => {
        if (valid) {
        //设置登陆效果
          this.loading = true
          //这里触发了store中的action
          this.$store.dispatch('user/login', this.loginForm)
            .then(() => {
              this.$router.push({ path: this.redirect || '/', query: this.otherQuery })
              this.loading = false
            })
            .catch(() => {
              this.loading = false
            })
        } else {
          console.log('error submit!!')
          return false
        }
      })
    },

当代码执行到这里时: this.$store.dispatch('user/login', this.loginForm),哪我们就去store中看看这个函数,注意这里是将loginForm传了过去,store中的函数:

import { login, logout, getInfo } from '@/api/user'
  // user login
  login({ commit }, userInfo) {
    const { username, password } = userInfo
    return new Promise((resolve, reject) => {
      login({ username: username.trim(), password: password }).then(response => {
        const { data } = response
        commit('SET_TOKEN', data.token)
        setToken(data.token)
        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  },

显然上面的代码中有一次调用了一个引入的函数login, login({ username: username.trim(), password: password })没办法,我们只能继续往下走了,继续去/api/user查看,其实有经验的都知道api应该都是用来发送网络请求的,但我们还是去看看,这里又得注意我们将loginForm拆成了账号和密码传过去了,/api/user中login如下

import request from '@/utils/request'
export function login(data) {
  return request({
    url: '/vue-element-admin/user/login',
    method: 'post',
    data
  })
}

好了,真就无线套娃是吧,其实这个request就是我们封装好的请求,就是用来获取mock中的数据,所以我直接将mock中的代码给大家看一下算了:

  const tokens = {
    admin: {
      token: 'admin-token'
    },
    editor: {
      token: 'editor-token'
    }
  }
 

  {
    url: '/vue-element-admin/user/login',
    type: 'post',
    response: config => {
    //这里将我们的用户名取了出来
      const { username } = config.body
     //结合上面的数组可以知道token 无非就是等于token: 'admin-token'或者 token: 'editor-token'
      const token = tokens[username]

      // mock error
      if (!token) {
        return {
          code: 60204,
          message: 'Account and password are incorrect.'
        }
      }
      return {
        code: 20000,
        data: token
      }
    }
  },

所以可以知道经历了无线套娃之后我们返回数据token,这个数据有什么用哪?那么我们就得往回走了

  1. 首先我们因为我们api中的那一步就是获取数据的,所以我们就回到了store中,所以方便阅读就将代码,在再次放到下面去了
```javascript
import { login, logout, getInfo } from '@/api/user'
  // user login
  login({ commit }, userInfo) {
    const { username, password } = userInfo
    return new Promise((resolve, reject) => {
      login({ username: username.trim(), password: password }).then(response => {
        const { data } = response
        commit('SET_TOKEN', data.token)
        setToken(data.token)
        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  },

new Promise((resolve, reject)一个期约函数,并且 login({ username: username.trim(), password: password })
成功返回了一个token所以应该执行then中的respon函数,那么主要就是下面的两行代码了

  commit('SET_TOKEN', data.token)
  setToken(data.token)

首先: commit('SET_TOKEN', data.token)直接执行mutations中的函数,我们看看代码:
代码的意思就是将tooten保存到state中

import { getToken, setToken, removeToken } from '@/utils/auth'

const state = {
  token: getToken(),
  name: '',
  avatar: '',
  introduction: '',
  roles: []
}

const mutations = {
  SET_TOKEN: (state, token) => {
    state.token = token
  }
 }

紧接着就是: setToken(data.token)我们同样取看看该代码:显然他将我们的代码保存在了Cookies中

import Cookies from 'js-cookie'

const TokenKey = 'Admin-Token'

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

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

export function removeToken() {
  return Cookies.remove(TokenKey)
}
  1. 好了,上面代码执行完后,我们终于结束了套娃之旅,回到了我们的点击确认按钮,的回调函数中:同样方便阅读我把代码放到了下面:
   handleLogin() {
    //validate是表单验证函数,里面的参数未boolean值,为表单是否通过验证
      this.$refs.loginForm.validate(valid => {
        if (valid) {
        //设置登陆效果
          this.loading = true
          //这里触发了store中的action
          //通过上面的分析我们可以知道,下面代码的作用就是在cookies中保存了tooken
          this.$store.dispatch('user/login', this.loginForm)
            .then(() => {
              this.$router.push({ path: this.redirect || '/', query: this.otherQuery })
              this.loading = false
            })
            .catch(() => {
              this.loading = false
            })
        } else {
          console.log('error submit!!')
          return false
        }
      })
    },

然后我们就因该执行我们的.then函数, this.$router.push({ path: this.redirect || '/', query: this.otherQuery })

     redirect: undefined,
     otherQuery: {}

也就是说我们终于可以从login跳转到默认路由了,可是能跳吗?千万别忘记了我们的pressiom,方便阅读我把代码放到下面:

// 这里是白名单,也就是说还没登陆获取token时可以访问的页面
const whiteList = ['/login', '/auth-redirect'] // no redirect whitelist

//这里是全局路由守卫
router.beforeEach(async(to, from, next) => {
  // 从cookie中取得token
  const hasToken = getToken()
  
  // 如果有token 也就是已经登陆的情况下
  if (hasToken) {
    // 并且要前往的路径是'/login'  则返回 '/' 
    if (to.path === '/login') {
      next({ path: '/' })
    } else {
      // 从store中取得用户的 roles, 也就是用户的权限 并且用户的权限数组必须有一个以上
      const hasRoles = store.getters.roles && store.getters.roles.length > 0
      // 有权限则直接进入
      if (hasRoles) {
        next()
      } else {
        // 没有权限的话
        try {
          // 获取用户信息
          const { roles } = await store.dispatch('user/getInfo')
          // 生成可访问路由
          const accessRoutes = await store.dispatch('permission/generateRoutes',                                  roles)
          // 将可访问路由添加到路由上
          router.addRoutes(accessRoutes)
          // 进入路由
          next({ ...to, replace: true })
        } catch (error) {
          // 如果出现异常  清空路由 
          await store.dispatch('user/resetToken')
          // Message提示错误
          Message.error(error || 'Has Error')
          // 跳到login页面重新登陆
          next(`/login?redirect=${to.path}`)
        }
      }
    }
  } else {
    // 没有token 也就是没有登陆的情况下  
    // 判断是否是白名单(也就是说不需要登陆就可以访问的路由)
    if (whiteList.indexOf(to.path) !== -1) {
      next()
    } else {
      // 其他的一律给我跳到login页面 老老实实的进行登陆
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})

根据上面的不走我们可以知道:我们此时已经在浏览器中的cookies中和store中都保存了我们的token数据,所以我们要用这个token来干嘛?看下面的代码


     // 从store中取得用户的 roles, 也就是用户的权限 并且用户的权限数组必须有一个以上
      const hasRoles = store.getters.roles && store.getters.roles.length > 0

好的,那我们就去getters中看看,getters中的代码如下:roles: state => state.user.roles,继续寻着它的思路走取user中获取roles:

const state = {
  token: getToken(),
  name: '',
  avatar: '',
  introduction: '',
  roles: []
}

可以知道roles中的数据为空所以,我们将会执行perssiom中的下面的代码:作用就是先获取用户的信息,然后根据用户的信息生成对应的路由,目前代码还没体现到我们继续往下走

   try {
          // 获取用户信息
          const { roles } = await store.dispatch('user/getInfo')
          // 生成可访问路由
          const accessRoutes = await store.dispatch('permission/generateRoutes',                                  roles)
          // 将可访问路由添加到路由上
          router.addRoutes(accessRoutes)
          // 进入路由
          next({ ...to, replace: true })
        } catch (error) {
          // 如果出现异常  清空路由 
          await store.dispatch('user/resetToken')
          // Message提示错误
          Message.error(error || 'Has Error')
          // 跳到login页面重新登陆
          next(`/login?redirect=${to.path}`)
        }
      }
    }

执行该行,代码也就是说,从store中获取用户的信息:const { roles } = await store.dispatch('user/getInfo'),那我们就取store中相应的代码,代码如下:

import { login, logout, getInfo } from '@/api/user'

  // get user info
  getInfo({ commit, state }) {
    return new Promise((resolve, reject) => {
      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('SET_ROLES', roles)
        commit('SET_NAME', name)
        commit('SET_AVATAR', avatar)
        commit('SET_INTRODUCTION', introduction)
        resolve(data)
      }).catch(error => {
        reject(error)
      })
    })
  },

好了下面我们又得拿着我们之前获取到的tooken进行一系列的套娃了: getInfo(state.token),要不我在这里先讲解一下,它要干的事情:就是拿着我们的tooken去获取我们目前的登陆角色的相关信息
这里跟上面解释过得请求一样都是从mock中获取数据,所以我也直接将mock中得代码放到下面:

api/user中对应得代码如下:

import request from '@/utils/request'

export function getInfo(token) {
  return request({
    url: '/vue-element-admin/user/info',
    method: 'get',
    params: { token }
  })
}

mock中对应的代码:
这里就是根据我们的tooken返回对应的身份信息

const users = {
  'admin-token': {
    roles: ['admin'],
    introduction: 'I am a super administrator',
    avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
    name: 'Super Admin'
  },
  'editor-token': {
    roles: ['editor'],
    introduction: 'I am an editor',
    avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
    name: 'Normal Editor'
  }
}

 // get user info
  {
    url: '/vue-element-admin/user/info\.*',
    type: 'get',
    response: config => {
      const { token } = config.query
      const info = users[token]

      // mock error
      if (!info) {
        return {
          code: 50008,
          message: 'Login failed, unable to get user details.'
        }
      }

      return {
        code: 20000,
        data: info
      }
    }
  },

好了数据已经拿到了,哪样我们就继续返回待我们的store中,同样获取到信息后就将信息保存到我们的store中

import { login, logout, getInfo } from '@/api/user'

  // get user info
  getInfo({ commit, state }) {
    return new Promise((resolve, reject) => {
      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('SET_ROLES', roles)
        commit('SET_NAME', name)
        commit('SET_AVATAR', avatar)
        commit('SET_INTRODUCTION', introduction)
        //将我们的数据返回
        resolve(data)
      }).catch(error => {
        reject(error)
      })
    })
  },

经信息保存到store中后,在回到我们的perssiom中,执行下面的代码:

         const { roles } = await store.dispatch('user/getInfo')
          // generate accessible routes map based on roles
          const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
          // dynamically add accessible routes
          router.addRoutes(accessRoutes)

主要就是下面着行代码: const accessRoutes = await store.dispatch('permission/generateRoutes', roles),可是在看这行代码之前,我想让你们先看看login中的一段代码,代码如下:

    getOtherQuery(query) {
      return Object.keys(query).reduce((acc, cur) => {
        if (cur !== 'redirect') {
          acc[cur] = query[cur]
        }
        return acc
      }, {})
    }
    
 watch: {
    // 监视我们的路由的变化,因为我们的路由时可以通过权限管理进行改变的,
    // 所以这里需要对路由进行监视根据目前的权限对路由进行管理
    $route: {
      handler: function(route) {
        const query = route.query
        if (query) {
          this.redirect = query.redirect
          this.otherQuery = this.getOtherQuery(query)
        }
      },
      // 立即执行一次
      immediate: true
    }
  },

      redirect: undefined,
      otherQuery: {}

首先入上面的代码所示我们在login中监视了我们的路由,上面的代码中,query是vue router的路由对象属性,redirect 是路由的重定向属性,为 vud-element-admin 的路由配置项:“在面包屑中点击会重定向去的地址”。本来我们的redirect都是空的,所以 this.$router.push({ path: this.redirect || '/', query: this.otherQuery }),也不知到跳到哪,可是只要我们的routes一旦发生变化,就会监测到路由变化从而为我们的上面的两个参数复制,好了,了解这些之后我们再去看看store.dispatch('permission/generateRoutes', roles),它里面的代码如下,注意我们传递过去了我们的roles参数:

import { asyncRoutes, constantRoutes } from '@/router'

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
}

  generateRoutes({ commit }, roles) {
    return new Promise(resolve => {
      let accessedRoutes
      // 在这个地方就可以进行权限的控制,但下面没有显示的原因是因为这里并没有
      // 进行过滤操作而是直接将所有的router都直接显示到页面中
      if (roles.includes('admin')) {
        accessedRoutes = asyncRoutes || []
      } else {
        accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
      }
      commit('SET_ROUTES', accessedRoutes)
      // 将这个参数传给我们的then
      resolve(accessedRoutes)
    })
  }
}

上面的代码就是对我们路由权限的过滤,根据我们的角色过滤出我们可以访问的路由,然后纵欲可以回到我们的perssioms中,router.addRoutes(accessRoutes),这句代码的作用是动态的挂载路由,好了,路由挂载上去之后,login只然可以监视到,最后中医pression执行完了之后,

redirect: undefined,
otherQuery: {}

上面的两个属性,也会因为routes的变化而生成,所以现在终于可以跳转到我们的默认路由了

总结:

总的来说,核心的代码就是:

  1. pressioms控制了你是否能够访问项目,以及你的路由权限
  2. 首先需要通过账号密码获取到我们的tooken,然后根据tooken,获取我们的roles,最后根据我们的roules生成我们可以访问的路由,最后router.addRoutes(accessRoutes)之后,login中的监视属性发挥作用,生成跳转路由,最后跳转
 类似资料: