目录

1.大量的数据替换问题

2.前端解决跨域问题

3.保存token到vuex及token持久化


4.实现用户退出功能

5.退出在进入能回到原来的页面 

6. Token失效处理

         7. 不同权限动态生成功能菜单

                  addRoutes基本使用

                  从actions中返回菜单项

                  在permission.js中获取action的返回值并过滤


1.大量的数据替换问题


场景:当整个项目中的某个变量需要换个名字,或者某个变量需要全部删除。

思路:我们需要做一个统一修改,一个个去找既麻烦又容易出错,借助工具实现

解决:在vscode中,按Ctrl+F全文查找(说的是当前文件,全部的点放大镜,这里不说此种情况),再按Ctrl+H全文替换


2.前端解决跨域问题


原因:浏览器同源策略 && 请求是ajax类型

思路:1.前端用JSONP方式去发请求(jsonp不是ajax请求)

           2.后端写代码(CORS)在响应中添加必要的响应头,让响应回来之后浏览器不报错

解决:这里使用vue脚手架自带的Vue-Cli配置代理转发

步骤:在vue.config.js配置文件中,有一项是devServer,它就是我们下边要操作的主角。

module.exports = {
  devServer: {
    // ... 省略
   // 代理配置
    proxy: {
        // 如果请求地址以/api打头,就出触发代理机制
        // http://localhost:9588/api/login -> http://localhost:3000/api/login
        '/api': {
          target: 'http://localhost:3000' // 我们要代理的真实接口地址
        }
      }
    }
  }
}

3.保存token到vuex及token持久化


原因:作为前端常用的方法,使用频率很高

手动思路:

首先是保存到vuex:

 在state中定义,再使用mutation去设置token;


token持久化的思路是:

  1. 在对token进行初始化的时候先从本地取一下,优先使用本地取到的值
  2. 在设置token的时候除了在vuex中存一份,在本地也同步存一份
  3. 在删除token的时候除了把vuex中的删除掉,把本地的也一并删除

使用插件:vuex-persistedstate插件,让在vuex中管理的状态数据同时存储在本地。  

步骤1:安装:npm i vuex-persistedstate

步骤2:store下面的index.js 引入

import createPersistedstate from 'vuex-persistedstate'
// 装包做持久化
//位置:store下的index.js文件

步骤3: 配置

export default createStore({
  modules: {
    user,
    cart,
    category
  },
+  plugins: [
+    createPersistedstate({
+      key: 'erabbit-client-pc-store',
+      paths: ['user', 'cart'] // 要做持久化的模块的名字
+    })
+  ]
})

代码:

const state = {
  token:getToken() || null // 默认从本地取  定义token
}
// 修改状态
const mutations = {
  // 设置token
  setToken(state, newToken) {
    state.token = token // 设置token
// 本地存储token
      setToken(newToken) 这里使用的是我封装的方法 设置token用setitem()
  },
  // 删除token
  removeToken(state) {
    state.token = null // 删除vuex的token
  }
}

 页面中调用commit保存后台返回的token

this.$store.commit('user/setToken', res.data)

4.实现用户退出功能


场景:常见的业务场景,且一般的处理逻辑都是一样的


思路:

弹窗提示用户是否确认退出登录:

如果选是:

如果有登出接口,就调用(注意:并不是所有的项目中,都有退出接口)

退出接口成功调用之后清空本地用户信息(token、userInfo)

如果需要携带必要参数跳回到登录页面准备重新登录操作

如果选否:

不做任何操作

vue前端rem方案_路由表


5.退出在进入能回到原来的页面 


场景:常见的业务场景。如我从某页面退出,还能回到之前的页面


思路:

1.登录成功之后,进入指定的目标页面(不要每次都进入主页)

2.用户退出,跳入登录页时,携带目标页面地址告诉登录页

登录成功后去指定的页面代码举例:

async doLogin() {
      try {
        // 在组件中调用带命名空间的action
        // dispatch是异步的,需要加async await
        await this.$store.dispatch('user/userLogin', this.loginForm)
        // 登录成功,路由跳转
++     this.$router.push(this.$route.query.return_url || '/')
      } catch (err) {
        alert('用户登录,失败')
        console.log('用户登录,失败', err)
      }
    },

退出时回传当前路径代码举例:

logout() {
      // 弹层询问,是否退出
      this.$confirm('你确定要离开吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(async() => {
        // 确认
        // 删除信息
        await this.$store.dispatch('user/userLogout')
        // 去到登录页
        // this.$router.push('/login?return_url=当前的路径')
        // 跳转路由-回登陆
        // 如何获取当前页面的地址 : this.$route.fullPath
        // this.$route.path只有路径的信息
        // this.$route.fullPath:路径+查询参数的信息
+       this.$router.push('/login?return_url=' + encodeURIComponent(this.$route.fullPath))
      }).catch(() => {

      })
    }

注意:

1.$route.path:只有路径的信息

2.$route.fullPath:路径+查询参数的信息

3.return_url: 这个名字是自己约定的,它要和login/index.vue中跳转代码保持一致。


6. Token失效处理


场景:常见的业务场景。token作为用户的关键令牌信息不是长久有效的,一般都会有一个失效时间(由后端来决定什么时长后失效),如果超过失效时间,当前token就不能再作为用户标识请求数据了,这时候我们需要做一些额外的失效处理


思路:

后端:收到用户访问某个接口时,检查当前token是否失效,如果token已经失效,返给前端一个约定好的状态码 10002

前端:在响应拦截器中,分析接口的返回值,如果状态码为10002, 则进行token失效操作

vue前端rem方案_List_02

写响应拦截器逻辑的文件中,处理响应拦截器的error时,补充自定义的逻辑  


代码举例:

由于页面跳转要用到路由,这里先引入

// 引入路由
import router from '@/router'

代码:

// 响应拦截器中
//  1. 根据后端返回数据判断本次操作是否成功,不成功主动报错
//  2. 如果成功,只返回有效数据
service.interceptors.response.use(
  response => {
    // 后端和前端的约定:success=true表示请求成功
    if (response.data.success) {
      return response.data
    } else {
      // 如果success为false 业务出错,直接触发reject
      // 被catch分支捕获
      return Promise.reject(new Error(response.data.message))
    }
  },
  async error => {
    console.log('请求出错啦', error)
    if (error.response.data.code === 10002) {
      console.log('token失效')
      await store.dispatch('user/logout')
      // .vue -- this.$route.fullPath
      //  .js -- router.currentRoute.fullPath

      router.push('/login?return_url=' + encodeURIComponent(router.currentRoute.fullPath))
    }
    console.dir(error)
    return Promise.reject(error)
  }
)

以上方案为后端主导的方案,前端只需要拿到错误码做业务处理即可,此方案也是常用且安全的最优方案

为验证逻辑正确,做模拟token失效效果:

  1. 修改vuex
  2. 找到Application面板 找到cookies把 token数据修改,然后重新刷新页面

vue前端rem方案_vue前端rem方案_03


7. 不同权限动态生成功能菜单


效果:不同用户登陆进来时,显示出来的菜单功能是不同的


思路:

1.登录成功后,进入导航守卫里。在这里面获取个人权限信息,并生成可以访问的动态路由表

 2.通过addRoutes 方法动态添加路由

3.将数据保存到vuex

4.通过接口返回的权限数据对动态菜单做过滤处理,以确定完成菜单与用户权限相关

5.从actions中返回菜单项


知识补充:

将路由表拆分成动态路由表和静态路由表

静态路由表:不需要权限就可以访问的,既有权限无权限都可以访问的公共页面

动态路由表:需要做权限控制的,简单理解为有权限的放到一个数组中,加上静态表里的,组成一个新数组,然后通过遍历渲染到菜单栏,就可以达到效果

addRoutes基本使用

作用:动态添加路由配置

router.addRoutes([路由配置对象])
或者:
this.$router.addRoutes([路由配置对象])

获取个人权限信息,并生成可以访问的动态路由表


代码实现的效果:

  1. 左侧的菜单只剩下静态的首页了(后续来解决)
  2. 浏览器手动输入某一个动态路由地址,依旧是可用的,这证明我们其实已经把动态路由添加到我们的路由系统了。
// 引入所有的动态路由表(未经过筛选)
+ import router, { asyncRoutes } from '@/router'

const whiteList = ['/login', '/404']
router.beforeEach(async(to, from, next) => {
  // 开启进度条
  NProgress.start()
  // 获取本地token 全局getter
  const token = store.getters.token
  if (token) {
    // 有token
    if (to.path === '/login') {
      next('/')
    } else {
      if (!store.getters.userId) {
        await store.dispatch('user/getUserInfo')
        // 改写成动态添加的方式
++      router.addRoutes(asyncRoutes)
      }
      next()
    }
  } else {
    // 没有token
    if (whiteList.includes(to.path)) {
      next()
    } else {
      next('/login')
    }
  }
  // 结束进度条
  NProgress.done()
})

步骤2 代码:改写菜单保存位置


当前的菜单渲染使用的数据:this.$router.options.routes 这个数据是固定,我们通过addRoutes添加的路由表只存在内存中,并不会改变this.$router.options.routes

如果我们希望在调用addRoutes方法之后,要路由数据立刻反映到菜单中,我们需要想一个额外的方法,思考一下,vue开发中,哪个技术可以保证响应式特性还可以动态修改? vuex!


补充模块。在src/store/modules下补充menu.js模块:

  • 定义数据menuList
  • 修改数据的方法setMenuList
// 导入静态路由
import { constantRoutes } from '@/router'
export default {
  namespaced: true,
  state: {
    // 先以静态路由作为菜单数据的初始值
    menuList: [...constantRoutes]
  },
  mutations: {
    setMenuList(state, asyncRoutes) {
      // 将动态路由和静态路由组合起来
      state.menuList = [...constantRoutes, ...asyncRoutes]
    }
  }
}

当然,要在src/store/index.js中注册这个模块

+ import menu from './modules/menu'

Vue.use(Vuex)

const store = new Vuex.Store({
  modules: {
    app,
    settings,
    user,
+   menu
  },
  getters
})

提交setMenuList生成完整的菜单数据

修改src/permission.js中的代码

if (!store.getters.userId) {
        await store.dispatch('user/getUserInfo')

        // 动态添加可以访问的路由设置
        router.addRoutes(asyncRoutes)

        // 根据用户实际能访问几个页面来决定从整体8个路由设置
        // 中,过滤中出来几个,然后保存到vuex中
        store.commit('menu/setMenuList', asyncRoutes)
      }

菜单生成部分改写使用vuex中的数据

在src\layout\components\Sidebar\index.vue文件中,修改

routes() {
  // 拿到的是一个完整的包含了静态路由和动态路由的数据结构
  // return this.$router.options.routes
  return this.$store.state.menu.menuList
}

上一步我们实现了:

  • 把动态路由通过addRoutes动态添加到了路由系统里
  • 把动态路由保存到vuex的menu中

但是我们没有和权限数据做搭配,接下来我们通过接口返回的权限数据对动态菜单做过滤处理,以确定完成菜单与用户权限相关。

从actions中返回菜单项

用户能访问哪些页面是通过actions获取到的,只需要从action中返回即可。

修改 store/modules/user.js ,补充return语句。

// 用来获取用户信息的action
    async getUserInfo(context) {
      // 1. ajax获取基本信息,包含用户id
      const rs = await getUserInfoApi()
      console.log('用来获取用户信息的,', rs)
      // 2. 根据用户id(rs.data.userId)再发请求,获取详情(包含头像)
      const info = await getUserDetailById(rs.data.userId)
      console.log('获取详情', info.data)
      // 把上边获取的两份合并在一起,保存到vuex中
      context.commit('setUserInfo', { ...info.data, ...rs.data })
+     return rs.data.roles.menus
    },

在permission.js中获取action的返回值并过滤

src/permission.js

if (!store.getters.userId) {
        // 有token,要去的不是login,就直接放行
        // 进一步获取用户信息
        // 发ajax---派发action来做
        const menus = await store.dispatch('user/getUserInfo')
        console.log('当前用户能访问的页面', menus)
        console.log('当前系统功能中提供的所有的动态路由页面是', asyncRoutes)
        // 根据本用户实际的权限menus去 asyncRoutes 中做过滤,选出本用户能访问的页面

        const filterRoutes = asyncRoutes.filter(route => {
          const routeName = route.children[0].name
          return menus.includes(routeName)
        })

        // 一定要在进入主页之前去获取用户信息

        // addRoutes用来动态添加路由配置
        // 只有在这里设置了补充了路由配置,才可能去访问页面
        // 它们不会出现左侧
        router.addRoutes(filterRoutes)

        // 把它们保存在vuex中,在src\layout\components\Sidebar\index.vue
        // 生成左侧菜单时,也应该去vuex中拿
        store.commit('menu/setMenuList', filterRoutes)
      }