思路:在login.vue中登录成功时,后端会返回token和该用户对应的权限,前端根据权限数据, 展示对应的菜单。点击菜单, 才能查看相关的界面。
但是在login.vue获得的权限数据要在home.vue中使用,所以要把请求来的权限数据保存到vuex中
login方法和权限方法
export const login = data => {
return request({
url: '/login',
method: 'POST',
data
})
}
// 获取用户权限
export const getRoles = data => {
return request({
url: '/roles',
method: 'POST',
data
})
}
login方法调用
login(this.form).then(res => {
// console.log(res, 'login=>res')
// 将用户身份存入vuex 普通用户身份: student 管理员用户身份: admin
this.$store.commit('setRole', res.data.role)
this.$store.commit('setUsername', res.data.username)
this.$store.commit('setPhoto', res.data.photo)
sessionStorage.setItem('token', res.data.token)
getRoles(res.data.role).then(ret => {
// console.log(ret.data, 'getRoles=>ret.data')
// 将对应身份下的路由存储到vuex
this.$store.commit('setRightList', ret.data)
this.loading = false
this.$message.success('登陆成功')
// 根据用户所具备的权限 动态添加路由规则
initDynamicRoutes()
this.$router.push('/')
})
})
vuex
注意这里为什么要把数据保存到sessionStorage?
因为这样当在home.vue刷新时,页面的权限数据不会丢失。不然直接保存数据页面刷新重新获取的时候是没有login的登录请求的,所以请求不到数据。
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
role: sessionStorage.getItem('role'),
rightList: JSON.parse(sessionStorage.getItem('rightList') || '[]'),
username: sessionStorage.getItem('username'),
photo: sessionStorage.getItem('photo')
},
mutations: {
setRole (state, data) {
state.role = data
sessionStorage.setItem('role', data)
},
setRightList (state, data) {
state.rightList = data
sessionStorage.setItem('rightList', JSON.stringify(data))
},
setUsername (state, data) {
state.username = data
sessionStorage.setItem('username', data)
},
setPhoto (state, data) {
state.photo = data
sessionStorage.setItem('photo', data)
}
},
actions: {},
getters: {}
})
在home.vue使用权限数据是通过mapState结合computed使用
mapState作用就是把权限数据映射到home.vue中
import { mapState } from 'vuex'
computed: {
...mapState(['username']), // 用户名
...mapState(['photo']) // 用户头像
},
退出登录逻辑
清除sessionStorage和Vuex的数据即可
这里注意,清除vuex的数据是删除完sessionStorage,然后将页面刷新即可
因为vuex的数据是通过sessionStorage取出的
logout () {
sessionStorage.clear()
this.$router.push({ name: 'login' })
// 删除vuex中的数据 让当前页面刷新
window.location.reload()
}
登录成功后,将token数据存储在sessionStorage中,判断是否登录
问题: 这样用户在登录之后就可以访问其他界面了,但如果用户A登录之后他只能访问a页面,他不能访问b页面,但是这时候他还是可以通过地址栏输入进入到b页面
解决: 当然我们也可以设置路由导航守卫,但是如果有多个页面,设置会非常不方便,并且对于用户A来说,它是不用访问b页面的,这时候我们何不对A不显示b页面,这个时候我们就用到了动态路由
2. 动态路由
router.js
根据当前用户所拥有的的权限数据来动态添加所需要的路由
先定义好所有的路由规则
定义路由规则和字符串的映射关系
ruleMapping的键是后端返回的path,值是路由规则名
const ruleMapping = {
table: tableRule,
users: userRule,
image: imageRule
}
登录成功之后动态添加路由,注意这个initDynamicRoutes的方法需要暴露出去在登录页面调用
currentRoutes[1].children就是home页面的子路由
export function initDynamicRoutes () {
// console.log(router)
// 根据二级权限 对路由规则进行动态的添加
const currentRoutes = router.options.routes
// currentRoutes[2].children.push()
const rightList = store.state.rightList
// console.log(rightList)
rightList.forEach(item => { // 如果是没有子路由的话 就直接添加进去 如果有子路由的话就进入二级权限遍历
// console.log(item, 'item-1')
if (item.path) {
const temp = ruleMapping[item.path]
// 路由规则中添加元数据meta
temp.meta = item.rights
currentRoutes[1].children.push(temp)
}
item.children.forEach(item => {
// item 二级权限
// console.log(item, 'item-2')
const temp = ruleMapping[item.path]
// 路由规则中添加元数据meta
temp.meta = item.rights
currentRoutes[1].children.push(temp)
})
})
// console.log(currentRoutes)
router.addRoutes(currentRoutes)
}
admin用户的rightLIst包含两项,基本页面和用户权限,其children属性包含了二级路由
[{id: 1, authName: “基本页面”, icon: “el-icon-connection”,…},…]
0: {id: 1, authName: “基本页面”, icon: “el-icon-connection”,…}
authName: “基本页面”
children: [{id: 11, authName: “表格页面”, icon: “el-icon-s-grid”, path: “table”,…},…]
0: {id: 11, authName: “表格页面”, icon: “el-icon-s-grid”, path: “table”,…}
authName: “表格页面”
icon: “el-icon-s-grid”
id: 11
path: “table”
rights: [“view”, “edit”, “add”, “delete”]
1: {id: 12, authName: “素材页面”, icon: “el-icon-s-marketing”, path: “image”,…}
authName: “素材页面”
icon: “el-icon-s-marketing”
id: 12
path: “image”
rights: [“view”, “edit”, “add”, “delete”]
icon: “el-icon-connection”
id: 1
1: {id: 2, authName: “用户权限”, icon: “el-icon-set-up”,…}
authName: “用户权限”
children: [{id: 21, authName: “权限页面”, icon: “el-icon-s-custom”, path: “users”,…}]
0: {id: 21, authName: “权限页面”, icon: “el-icon-s-custom”, path: “users”,…}
authName: “权限页面”
icon: “el-icon-s-custom”
id: 21
path: “users”
rights: [“view”, “edit”, “add”, “delete”]
icon: “el-icon-set-up”
id: 2
这样当用户A在地址栏输入自己不能访问的路由时,则不会跳转到该页面,跳转到404页面
问题: 如果我们重新刷新的话动态路由就会消失,动态路由是在登录成功之后才会调用的,刷新的时候并没有调用,所以动态路由没有添加上
解决: 可以在app.vue中的created中调用添加动态路由的方法
按钮的控制
虽然用户可以看到某些界面了, 但是这个界面的一些按钮该用户可能是没有权限的。 因此, 我们需要对组件中的一些按钮进行控制, 用户不具备权限的按钮就隐藏或者禁用, 而在这块的实现中, 可以把该逻辑放到自定义指令中
比如我们可以根据后端返回的数据right来判断用户有什么权限,如下图
添加自定义指令 控制按钮permission.js放到utils文件夹里面
请求和相应的控制
除了登录请求都得要带上token , 这样服务器才可以鉴别你的身份
这块使用的就是asiox的请求拦截器设置
如果发出了非权限内的请求, 应该直接在前端范围内阻止, 虽然这个请求发到服务器也会被拒绝
非权限内的请求:比如a用户是不能够操作该页面的按钮的,但是他通过f12调试把按钮改为可点击,如果我们不对这个请求进行处理,那么这个请求就会发送出去
响应控制
到了服务器返回的状态码401, 代表token 超时或者被篡改了,此时应该强制跳转到登录界面
小结
前端权限的实现之须要后端提供数据支持, 否则无法实现。
返回的权限数据的结构, 前后端需要沟通协商怎样的数据便用起来才最方便
菜单控制
权限的数据需要在多组件之间共享, 因此采用vuex
防止刷新界面, 权限数据丢失, 所以需要存在sessionStorage, 并目要保证两者的同步
界面控制
路由的导航守卫可以防止跳过登录界面
动态路由可以让不具备权限的界面的路由规则压根就不存在
按钮控制
路由规则中可以增加路由元数据meta
通过路由对象可以得到当前的路由规则以及存在此规则中的meta 数据
自定义指令可以很方便的实现按钮控制
请求和响应控制
请求拦截器和响应拦截器的使用
请求方式的约定restful