没有看过整个同学不推荐看这篇文章,如果对项目没有一个大致了解的花,你会感觉云里雾里
我这里就直接开是往下写了,不搞花的:
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()
}
}
})
{
path: '/redirect',
component: Layout,
hidden: true,
children: [
{
path: '/redirect/:path(.*)',
component: () => import('@/views/redirect/index')
}
]
},
这里其实就是要先是我们的默认路由可是能显示吗?忘记了我们上面所说的premission了吗?我们有taken吗显然是没有的,所以我们就得跳转到我们的登陆页面:
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,这个数据有什么用哪?那么我们就得往回走了
```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)
}
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的变化而生成,所以现在终于可以跳转到我们的默认路由了
总的来说,核心的代码就是:
router.addRoutes(accessRoutes)
之后,login中的监视属性发挥作用,生成跳转路由,最后跳转