前端-Vue权限控制,菜单权限,按钮权限_一人创客的博客-CSDN博客

目录

介绍:

前端权限的概念:

前端权限的意义:

Vue权限管理的代码实现:

菜单

刷新界⾯菜单消失

标识⽤户名, ⽅便查看当前⽤户

退出登陆:

界面:

1.判断当前是否登陆

2.控制是否可以访问角色界面 (不太理解)

按钮:

请求和响应:

请求控制

响应控制

总结(重要):

菜单:

界面:

按钮控制

请求和响应控制


介绍:

前端权限的概念:

前端权限的控制本质上来说, 就是控制前端的 视图层的展示和前端所发送的请求。

前端权限的实现必须要后端提供数据⽀持, 否则⽆法实现。返回的权限数据的结构,前后端需要沟通协商, 怎样的数据使⽤起来才最⽅便.

前端权限的意义:

如果仅从能够修改服务器中数据库中的数据层⾯上讲,确实只在后端做控制就⾜够了, 那为什么越来越多的项⽬也进⾏了前端权限的控制, 主要有这⼏⽅⾯的好处:

降低⾮法操作的可能性
不怕贼偷就怕贼惦记, 在⻚⾯中展示出⼀个 就算点击了也最终会失败 的按钮, 势必会增加有⼼者
⾮法操作的可能性
尽可能排除不必要请求,减轻服务器压⼒
没必要的请求, 操作失败的请求, 不具备权限的请求, 应该压根就不需要发送, 请求少了, ⾃然也会减轻服务器的压⼒
提⾼⽤户体验
根据⽤户具备的权限为该⽤户展现⾃⼰权限范围内的内容,避免在界⾯上给⽤户带来困扰, 让⽤户专注于分内之事

Vue权限管理的代码实现:

菜单

在登录请求中, 会得到权限数据, 当然, 这个需要后端返回数据的⽀持. 前端根据权限数据, 展示对应的菜单.点击菜单,才能查看相关的界⾯.

//查看登陆之后获取的数据{"data": {"id": 500,"rid": 0, "username": "admin","mobile": "13999999999","email": ["123999@qq.com"](mailto:123999@qq.com), "token": "BearereyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjUwMCwicmlkIjowLCJpYXQiOjE1M TI1NDQyOTksImV4cCI6MTUxMjYzMDY5OX0.eGrsrvwHm- tPsO9r_pxHIQ5i5L1kX9RX444uwnRGaIM"},"rights": [{"id": 125,"authName": "⽤户管理","icon": "icon-user","children": [{"id": 110,"authName": "⽤户列表","path": "users","rights": ["view", "edit", "add", "delete"]}]}, {"id": 103,"authName": " ⻆ ⾊ 管 理 ", "icon": "icon-tijikongjian", "children": [{"id": 111,"authName": "⻆⾊列表","path": "roles","rights": ["view", "edit", "add", "delete"]}]}, {"id": 101,"authName": "商品管理","icon": "icon-shangpin","children": [{"id": 104,"authName": "商品列表","path": "goods","rights": ["view", "edit", "add", "delete"]}, {"id": 121,"authName": "商品分类", "path": "categories","rights": ["view", "edit", "add", "delete"]}]}],"meta": {"msg": "登录成功", "status": 200}}

在这部分数据中, 除了该⽤户的基本信息之外, 还有两个字段很关键

token,⽤于前端⽤户的状态保持

rights:该⽤户具备的权限数据,⼀级权限就对应⼀级菜单,⼆级权限就对应⼆级菜单。根据rights中的数据, 动态渲染左侧菜单栏, 数据在Login.vue得到, 但是在Home.vue才使⽤, 所以可以把数据⽤vuex进⾏维护:

//vuex的代码:export default new Vuex.Store({ state: {rightList:[]},mutations: { setRightList(state, data) {state.rightList = data}},actions: {},getters: {}})//login的代码:login() {this.$refs.loginFormRef.validate(async valid => {......this.$store.commit('setRightList', res.rights) this.$message.success(' 登 录 成 功 ') this.$router.push('/home')})}//home的代码import { mapState } from 'vuex' computed: {...mapState(['rightList'])}created() {this.activePath = window.sessionStorage.getItem('activePath') this.menulist = this.rightList},

刷新界⾯菜单消失

因为菜单数据是登录之后才获取到的, 获取菜单数据之后,就存放在Vuex中 ,⼀旦刷新界⾯, Vuex中的数据会重新初始化, 所以会变成空的数组。 因此, 需要将权限数据存储在sessionStorage中, 并让其和Vuex中的数据保持同步:

//vuex的代码export default new Vuex.Store({ state: {rightList:JSON.parse(sessionStorage.getItem('rightList')||'[]')},mutations: { setRightList(state, data) { state.rightList = datasessionStorage.setItem('rightList',JSON.stringify(data))}},actions: {},getters: {}})

标识⽤户名, ⽅便查看当前⽤户

//vuex的代码export default new Vuex.Store({ state: {//把数据转成对象rightList:JSON.parse(sessionStorage.getItem('rightList')||'[]'), username: sessionStorage.getItem('username')},mutations: { setRightList(state, data) {state.rightList = data sessionStorage.setItem('rightList',JSON.stringify(data))},setUsername(state, data) { state.username = datasessionStorage.setItem('username',data)}},actions: {},getters: {}})//login的代码login() {this.$refs.loginFormRef.validate(async valid => {......this.$store.commit('setRightList', res.rights) this.$store.commit('setUsername', res.data.username) this.$message.success(' 登 录 成 功 ') this.$router.push('/home') })}//home的代码computed: {...mapState(['rightList','username'])}{{username}}退出

退出登陆:

logout() {sessionStorage.clear() this.$router.push('/login') //刷新当前页面window.location.reload()},

界面:

1.判断当前是否登陆

正常的逻辑是通过登录界⾯, 登录成功之后跳转到管理平台界⾯, 但是如果⽤户直接敲⼊管理平台的地址, 也是可以跳过登录的步骤.所以应该在某个时机判断⽤户是否登录:

//如何判断是否登录sessionStorage.setItem('token', res.data.token)//判断时机|路由导航守卫router.beforeEach((to, from, next) => { if (to.path === '/login') {next()} else {const token = sessionStorage.getItem('token') if(!token) {next('/login')} else {next()} }})

2.控制是否可以访问角色界面 (不太理解)

虽然菜单项已经被控制住了, 但是路由信息还是完整的存在于浏览器,正⽐如zhangsan这个⽤户并不具备⻆⾊这个菜单, 但是他如果⾃⼰在地址栏中敲⼊/roles的地址, 依然也可以访问⻆⾊界⾯:

//router.js//登录成功之后动态添加import Vue from 'vue'import Router from 'vue-router'import Login from '@/components/Login.vue' import Home from '@/components/Home.vue' import Welcome from '@/components/Welcome.vue'import Users from '@/components/user/Users.vue' import Roles from '@/components/role/Roles.vue'import GoodsCate from '@/components/goods/GoodsCate.vue' import GoodsList from '@/components/goods/GoodsList.vue' import NotFound from '@/components/NotFound.vue'import store from '@/store' Vue.use(Router)const userRule = { path: '/users', component: Users } const roleRule = { path: '/roles', component: Roles } const goodsRule = { path: '/goods', component: GoodsList }const categoryRule = { path: '/categories', component: GoodsCate }const ruleMapping = { 'users': userRule, 'roles': roleRule, 'goods': goodsRule, 'categories': categoryRule}const router = new Router({ routes: [{path: '/', redirect: '/home'},{path: '/login', component: Login},{path: '/home', component: Home, redirect: '/welcome', children: [{ path: '/welcome', component: Welcome },// { path: '/users', component: Users },// { path: '/roles', component: Roles },// { path: '/goods', component: GoodsList },// { path: '/categories', component: GoodsCate }]},{path: '*', component: NotFound}]})router.beforeEach((to, from, next) => {if (to.path === '/login') { next()} else {const token = sessionStorage.getItem('token') if(!token) {next('/login')} else {next()} }})export function initDynamicRoutes() {const currentRoutes = router.options.routes const rightList = store.state.rightList rightList.forEach(item => {item.children.forEach(item => { currentRoutes[2].children.push(ruleMapping[item.path])})})router.addRoutes(currentRoutes)}export default router
//login.vueimport { initDynamicRoutes } from '@/router.js' login() {this.$refs.loginFormRef.validate(async valid => { if (!valid) returnconst { data: res } = await this.$http.post('login', this.loginForm)if (res.meta.status !== 200) return this.$message.error('登录失败!')this.$store.commit('setRightList', res.rights)this.$store.commit('setUsername', res.data.username) sessionStorage.setItem('token', res.data.token) initDynamicRoutes()this.$message.success('登录成功')this.$router.push('/home')})}//app.vueimport { initDynamicRoutes } from '@/router.js' export default {name: 'app', created() {initDynamicRoutes()}}

按钮:

虽然⽤户可以看到某些界⾯了, 但是这个界⾯的⼀些按钮,该⽤户可能是没有权限的.因此, 我们需要对组件中的⼀些按钮进⾏控制. ⽤户不具备权限的按钮就隐藏或者禁⽤, ⽽在这块中, 可以把该逻辑放到⾃定义指令中

//permission.jsimport Vue from 'vue'import router from '@/router.js' Vue.directive('permission', {inserted: function(el, binding){ const action = binding.value.actionconst currentRight = router.currentRoute.meta if(currentRight) {if(currentRight.indexOf(action) == -1) {// 不具备权限const type = binding.value.effect if(type === 'disabled') { el.disabled = true el.classList.add('is-disabled')} else { el.parentNode.removeChild(el)}}}}})//main.jsimport './utils/permission.js'//router.jsexport function initDynamicRoutes() {const currentRoutes = router.options.routes const rightList = store.state.rightList rightList.forEach(item => {item.children.forEach(item => {const itemRule = ruleMapping[item.path] itemRule.meta = item.rights currentRoutes[2].children.push(itemRule)})})router.addRoutes(currentRoutes)}

使用指令:

v-permission="{action:'add'}"v-permission="{action:'delete', effect:'disabled'}"

请求和响应:

如果⽤户通过⾮常规操作, ⽐如通过浏览器调试⼯具将某些禁⽤的按钮变成启⽤状态, 此时发的请求, 也应该被前端所拦截

请求控制

  • 除了登录请求都得要带上token, 这样服务器才可以鉴别你的身份
axios.interceptors.request.use(function(req){ const currentUrl = req.urlif(currentUrl !== 'login') {req.headers.Authorization = sessionStorage.getItem('token')}return req})
  • 如果发出了⾮权限内的请求, 应该直接在前端访问内组织,虽然这个请求发到服务器也会被拒绝
import axios from 'axios' import Vue from 'vue'import router from '../router'// 配置请求的跟路径, ⽬前⽤mock模拟数据, 所以暂时把这⼀项注释起来// axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/' const actionMapping = {get: 'view',post: 'add',put: 'edit', delete: 'delete'}axios.interceptors.request.use(function(req){ const currentUrl = req.urlif(currentUrl !== 'login') {req.headers.Authorization = sessionStorage.getItem('token')// 当前模块中具备的权限// 查看get请求// 增加post请求// 修改put请求// 删除delete请求const method = req.method// 根据请求, 得到是哪种操作const action = actionMapping[method]// 判断action是否存在当前路由的权限中const rights = router.currentRoute.meta if(rights && rights.indexOf(action) == -1) {// 没有权限alert('没有权限')return Promise.reject(new Error('没有权限'))}}return req})axios.interceptors.response.use(function(res){ return res})Vue.prototype.$http = axios

响应控制

  • 得到了服务器返回的状态码401, 代表token超时或者被篡改了, 此时应该强制跳转到登录界⾯
axios.interceptors.response.use(function(res){ if (res.data.meta.status === 401) {router.push('/login') sessionStorage.clear() window.location.reload()}return res})

总结(重要):

菜单:

  • 权限的数据需要在多组件之间共享, 因此采⽤vuex

  • 防⽌刷新界⾯,权限数据丢失, 所以需要存储在sessionStorage, 并且要保证两者的同步

界面:

  • 路由的导航守卫可以防⽌跳过登录界⾯

  • 动态路由可以让不具备权限的界⾯的路由规则压根就不存在

按钮控制

  • 路由规则中可以增加路由元数据meta

  • 通过路由对象可以得到当前的路由规则,以及存储在此规则中的meta数据

  • ⾃定义指令可以很⽅便的实现按钮控制

请求和响应控制

  • 请求拦截器和响应拦截器的使⽤
  • 请求⽅式的约定restful