前端面试常考之——Vue前端路由权限控制(vuex版本)

文章目录

前端权限控制思路

1. 菜单的权限控制
  • 菜单的控制
    在登录请求中,会得到权限数据,当然,这个需要后端返回数据的支持。前端根据权限数据展示对应的菜单,点击菜单才能查看相关的界面
  • 界面的控制
    如果用户没有登录,手动在地址栏敲入管理界面的地址,则需要跳转到登录页。如用户已经登录,可是手动敲入非权限内的地址,则需要跳转404界面
  • 按钮的控制
    在某个菜单的界面,还得根据权限数据,展示出可进行操作的按钮,比如删除,修改增加
  • 请求和响应的控制
    如果用户通过非常规的操作,比如通过浏览器调试工具将某些禁用的按钮变成启用状态,此时发的请求,也应当被前端所拦截

Vue的权限控制实现

1. 菜单的控制
  • 查看登录之后获取到的数据

    json 复制代码
    {
        "data": {
            "id": 500,
            "rid": 0,
            "username": "admin",
            "mobile": "13999999999",
            "email": "123999@qq.com",
            "token": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjUwMCwicmlkIjowLCJpYXQiOjE1MTI1NDQyOTksImV4cCI6MTUxMjYzMDY5OX0.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------index.js

      js 复制代码
      import Vue from 'vue'
      import Vuex from 'vuex'
      
      Vue.use(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 = data
            sessionStorage.setItem('username', data)
          }
        },
        actions: {
      
        },
        getters: {
      
        }
      })
    • Login.vue

      js 复制代码
      login() {
            this.$refs.loginFormRef.validate(async (valid) => {
              if (!valid) return
              const { data: res } = await this.$http.post('login', this.loginForm)
              if (res.meta.status !== 200) return this.$message.error('登录失败!')
              console.log(res)
              this.$store.commit('setRightList', res.rights)
              this.$store.commit('setUsername', res.data.username)
              this.$message.success('登录成功')
              this.$router.push('/home')
            })
          },
    • Home.vue

      js 复制代码
      import { mapState } from 'vuex'
      computed: {
          ...mapState(['rightList', 'username'])
        },
      created() {
          this.activePath = window.sessionStorage.getItem('activePath')
          // 初始化menulist菜单栏的数据
          this.menulist = this.rightList
        },
  • 刷新界面菜单消失

    • 原因分析

      复制代码
      因为菜单数据是登录之后才获取的,存放在vuex中
      一旦刷新界面,vuex中的数据会初始化为空
      因此,需要将权限数据存储在sessionStorage中,并让其和vuex中的数据保持同步
    • 代码解决

      js 复制代码
      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 = data
            sessionStorage.setItem('username', data)
          }
        },
        actions: {
      
        },
        getters: {
      
        }
      })
    • 退出按钮的逻辑

      js 复制代码
      logout() {
            // 删除sessionStorage中的数据
            sessionStorage.clear()
            this.$router.push('/login')
            // 删除vuex中的数据,让当前的界面刷新即可
            window.location.reload()
          },
2. 界面的控制
  1. 正常的逻辑是通过登录界面,登录成功之后跳转到管理平台界面,但是如果用户直接敲管理平台的地址,也是可以跳过登录的步骤,所以应该在某个时机判断用户是否登录
  • 如何判断是否登录

    js 复制代码
    login() {
        	// 登录时存储token
            sessionStorage.setItem('token', res.data.token)
        },
  • 什么时机

    • 路由导航守卫

      js 复制代码
      router.beforeEach((to, from, next) => {
        if (to.path === '/login') {
          next()
        } else {
          const token = sessionStorage.getItem('token')
          if (!token) {
            next('/login')
          } else {
            next()
          }
        }
      })
  1. 虽然菜单项已经被控制住了,但是路由信息还是完整的存在于浏览器,正比如zhangshan这个用户并不具备角色这个菜单,但是在地址栏中敲入/roles的地址,依然可以访问角色界面。
  • 路由导航守卫

    路由导航守卫固然可以在每次路由地址发生变化的时候,从vuex中取出rightList判断用户将要访问的界面有没有权限。不过从另一个角度来说,这个用户不具备权限的路由,是否也应该压根就不存在呢?

  • 动态路由

    • 登录成功之后动态添加
    js 复制代码
    // 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 goodRule = { path: '/goods', component: GoodsList }
    const categoryRule = { path: '/categories', component: GoodsCate }
    
    const ruleMapping = {
      'users': userRule,
      'roles': roleRule,
      'goods': goodRule,
      'categories': categoryRule
    }
    
    const router = new Router({
      routes: [
        {
          path: '/',
          redirect: '/welcome'
        },
        {
          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
        }
      ]
    })
    // 动态路由
    export function initDynamicRoutes() {
      // 根据二级权限,对路由规则进行动态的添加
      console.log(router)
      const currentRoutes = router.options.routes
      const rightList = store.state.rightList
      rightList.forEach(item => {
        item.children.forEach(item => {
          // item 二级权限
          const temp = ruleMapping[item.path]
          currentRoutes[2].children.push(temp)
        })
      })
      currentRoutes.forEach(item => {
        router.addRoute(item)
      })
    }
    // 路由导航守卫 拦截没登录时的权限路由
    router.beforeEach((to, from, next) => {
      if (to.path === '/login') {
        next()
      } else {
        const token = sessionStorage.getItem('token')
        if (!token) {
          next('/login')
        } else {
          next()
        }
      }
    })
    
    export default router
    js 复制代码
    // Login.vue
    import { initDynamicRoutes } from "@/router.js"
    login() {
          this.$refs.loginFormRef.validate(async (valid) => {
            if (!valid) return
            const { data: res } = await this.$http.post('login', this.loginForm)
            if (res.meta.status !== 200) return this.$message.error('登录失败!')
            console.log(res)
            this.$store.commit('setRightList', res.rights)
            this.$store.commit('setUsername', res.data.username)
            sessionStorage.setItem('token', res.data.token)
            this.$message.success('登录成功')
    
            // 根据用户所具备的权限,动态添加路由规则
            initDynamicRoutes()
            
            this.$router.push('/home')
          })
        },
    • App.vue中添加,防止登录后再次刷新后重新路由规则重新加载,菜单被初始化
    js 复制代码
    export default {
      name: 'app',
      created() {
        initDynamicRoutes() //动态添加路由规则
      }
    }
3. 按钮的控制

​ 虽然用户可以看到某些界面了,但是这个界面的一些按钮,该用户可能是没有权限的,因此,我们需要对组件中的一些按钮进行控制。用户不具权限的按钮就隐藏或者禁用,而在这块中,可以把该逻辑放到自定义指令中

  • permission.js 注册自定义指令

    js 复制代码
    import Vue from 'vue'
    import router from '@/router.js'
    Vue.directive('permission', {
      inserted(el, binding) {
        console.log(binding)
        const action = binding.value.action
        const effect = binding.value.effect
        // 判断 当前路由所对应的组件中,如何判断用户是否具备action的权限
        console.log(router.currentRoute.meta)
        if (router.currentRoute.meta.indexOf(action) == -1) {
          if (effect === 'disabled') {
            el.disabled = true
            el.classList.add('is-disabled')
          } else {
            el.parentNode.removeChild(el)
          }
        }
      }
    })
  • main.js

    js 复制代码
    import './utils/permission.js' //引入到入口文件permission才会被加载
  • router.js 把路由元信息添加进来

    js 复制代码
    export function initDynamicRoutes() {
      // 根据二级权限,对路由规则进行动态的添加
      console.log(router)
      const currentRoutes = router.options.routes
      const rightList = store.state.rightList
      rightList.forEach(item => {
        item.children.forEach(item => {
          // item 二级权限
          const temp = ruleMapping[item.path]
          // 把路由元信息添加进来
          temp.meta = item.rights
          currentRoutes[2].children.push(temp)
        })
      })
      currentRoutes.forEach(item => {
        router.addRoute(item)
      })
    }
  • 使用自定义指令

    js 复制代码
    v-permission="{action:'add'}"
    v-permission="{action:'edit', effect:'disabled'}"
4. 请求和响应的控制
请求控制
  • 除了登录请求都要带上token,这样服务器才可以鉴别你的身份

    js 复制代码
    // http.js
    import axios from 'axios'
    import Vue from 'vue'
    // 配置请求的跟路径, 目前用mock模拟数据, 所以暂时把这一项注释起来
    // axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/'
    
    axios.interceptors.request.use(req => {
      // console.log(req.url, req.method)
      if (req.url !== 'login') {
        // 不是登录的请求,我们应该在请求头中加入token数据
        req.headers.Authorization = sessionStorage.getItem('token')
      }
      return req
    })
    Vue.prototype.$http = axios
  • 如果发出了非权限内的请求,应该直接在前端范围内组织,虽然这个请求发送到服务器也会被拒绝

    js 复制代码
    // http.js
    import axios from 'axios'
    import Vue from 'vue'
    import router from '@/router.js'
    // 配置请求的跟路径, 目前用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(req => {
      // console.log(req.url, req.method)
      if (req.url !== 'login') {
        // 不是登录的请求,我们应该在请求头中加入token数据
        req.headers.Authorization = sessionStorage.getItem('token')
    
        // 判断非权限范围内的请求
        // router.currentRoute.meta
        // resful风格请求
        /*
          get请求     view
          post请求    add
          put请求     edit
          delete请求  delete
          [add view edit delete]
        */
        const action = actionMapping[req.method]
        // 判断 action 是否存在当前路由的权限中
        const rights = router.currentRoute.meta
        if (rights && rights.indexOf(action) === -1) {
          // 没有权限
          alert('没有权限')
          return Promise.reject(new Error('没有权限'))
        }
      }
      return req
    })
    Vue.prototype.$http = axios
响应控制
  • 得到了服务器返回的状态码401,代表token超时或者被篡改了,此时应该强制跳转登录页

    js 复制代码
    axios.interceptors.response.use((res) => {
      if (res.data.meta.status === 401) {
        router.push('/login')
        sessionStorage.clear()
        // 通过Vuex的actions或mutations来清空或重置存储在store中的登录相关状态。
        this.$store.dispatch('logout')
      }
      return res
    })

小结

前端权限的实现必须要后端提供数据支持,否则无法实现.

返回的权限数据的结构,前后端需要沟通协商,怎样的数据使用起来才最方便.

4.1.菜单控制

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

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

4.2.界面控制

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

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

4.3.按钮控制

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

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

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

4.4.请求和响应控制

  • 请求拦截器和响应拦截器的使用.

  • 请求方式的约定restful

相关推荐
鹧鸪yy2 分钟前
认识Node.js及其与 Nginx 前端项目区别
前端·nginx·node.js
跟橙姐学代码3 分钟前
学Python必须迈过的一道坎:类和对象到底是什么鬼?
前端·python
汪子熙5 分钟前
浏览器里出现 .angular/cache/19.2.6/abap_test/vite/deps 路径究竟说明了什么
前端·javascript·面试
Benzenene!6 分钟前
让Chrome信任自签名证书
前端·chrome
yangholmes88886 分钟前
如何在 web 应用中使用 GDAL (二)
前端·webassembly
jacy8 分钟前
图片大图预览就该这样做
前端
林太白10 分钟前
Nuxt3 功能篇
前端·javascript·后端
YuJie12 分钟前
webSocket Manager
前端·javascript
Mapmost27 分钟前
Mapmost SDK for UE5 内核升级,三维场景渲染效果飙升!
前端
Mapmost29 分钟前
重磅升级丨Mapmost全面兼容3DTiles 1.1,3DGS量测精度跃升至亚米级!
前端·vue.js·three.js