目录
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持久化的思路是:
- 在对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'] // 要做持久化的模块的名字
+ })
+ ]
})
代码:
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)
如果需要携带必要参数跳回到登录页面准备重新登录操作
如果选否:
不做任何操作
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([路由配置对象])
获取个人权限信息,并生成可以访问的动态路由表
代码实现的效果:
- 左侧的菜单只剩下静态的首页了(后续来解决)
- 浏览器手动输入某一个动态路由地址,依旧是可用的,这证明我们其实已经把动态路由添加到我们的路由系统了。
// 引入所有的动态路由表(未经过筛选)
+ 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)
}