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持久化的思路是:
- 在对token进行初始化的时候先从本地取一下,优先使用本地取到的值
- 在设置token的时候除了在vuex中存一份,在本地也同步存一份
- 在删除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'] // 要做持久化的模块的名字 + }) + ] })
代码:
1. const state = { 2. token:getToken() || null // 默认从本地取 定义token 3. }
// 修改状态 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)
如果需要携带必要参数跳回到登录页面准备重新登录操作
如果选否:
不做任何操作
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失效操作
在写响应拦截器逻辑的文件中,处理响应拦截器的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失效效果:
- 修改vuex
- 找到Application面板 找到cookies把 token数据修改,然后重新刷新页面
7. 不同权限动态生成功能菜单
效果:不同用户登陆进来时,显示出来的菜单功能是不同的
思路:
1.登录成功后,进入导航守卫里。在这里面获取个人权限信息,并生成可以访问的动态路由表
2.通过addRoutes
方法动态添加路由
3.将数据保存到vuex
4.通过接口返回的权限数据对动态菜单做过滤处理,以确定完成菜单与用户权限相关
5.从actions中返回菜单项
知识补充:
将路由表拆分成动态路由表和静态路由表
静态路由表:不需要权限就可以访问的,既有权限无权限都可以访问的公共页面
动态路由表:需要做权限控制的,简单理解为有权限的放到一个数组中,加上静态表里的,组成一个新数组,然后通过遍历渲染到菜单栏,就可以达到效果
addRoutes基本使用
作用:动态添加路由配置
router.addRoutes([路由配置对象]) 或者: this.$router.addRoutes([路由配置对象])
步骤1 代码实现:获取个人权限信息,并生成可以访问的动态路由表
代码实现的效果:
- 左侧的菜单只剩下静态的首页了(后续来解决)
- 浏览器手动输入某一个动态路由地址,依旧是可用的,这证明我们其实已经把动态路由添加到我们的路由系统了。
// 引入所有的动态路由表(未经过筛选) + 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) }