我正在参加「掘金·启航计划」
如图,当长时间未操作,token超时,同时调用多个接口,这几个接口的逻辑又相互独立、各不相干,就会同时返回“请重新登录”的提示信息,虽然逻辑上没有问题,但是交互效果很不好,针对这个问题,目前找到了两个解决方案,如果对你有用,那就请你点赞收藏~
方法1:axios响应拦截 + 防抖(推荐)
适用场景:
- 适合新项目,或者是项目接口不多的情况下
- 在项目中调用接口时,不需要在每个接口的catch中弹出
message信息,只需要关注业务逻辑。如果你的项目中在写业务逻辑的时候,在接口的catch中使用message提示错误信息,需要提前进行删除 - 对后端接口的要求比较高,必须保证每个接口的失败信息足够完善,也就是说在接口响应失败的情况下,必须返回符合业务场景的
msg信息
解决方案:
使用axios响应拦截,针对你们前后端共同约定好的状态码,对错误信息进行拦截,拦截到的错误信息使用前端组件,弹出错误信息。
// 前端组件:用于错误信息的弹出
import { ElMessage } from 'element-plus'
// 使用axios创建实例,
const http = axios.create({
// baseURL: import.meta.env.MODE === 'serve' ? '/' : import.meta.env.VITE_APP_API,
baseURL: '/',
headers: {
'content-type': 'application/json',
'X-XSRF-HEADER': 'valid'
},
withCredentials: false,
timeout: 10 * 1000 //设置超时时间10s
})
// 使用防抖的方式对消息进行拦截
const MsgError = debounce((message) => {
ElMessage({
message,
type: 'error',
duration: 3 * 1000
})
}, 300)
// axios响应拦截,在需要拦截错误信息的地方,调用MsgError
http.interceptors.response.use(
(response) => {
switch (Number(response.data.code)) {
case 200: // 接口正常返回
return response.data.data
case 203: // 登陆超时,
resetLogin()
break
}
const errorData: ErrorType<string> = {
code: response.data.status ?? response.status,
message: response.data.msg
}
MsgError(errorData.message)
return Promise.reject(errorData)
},
(error) => {
if (error.response) {
let message = ''
switch (error.response.status) {
case 401:
resetLogin()
break
case 400:
message = '请求错误'
break
case 404:
message = '未找到相关资源'
break
case 405:
message = '该接口不支持指定的方法'
break
}
const errorData: ErrorType<string> = {
code: error.response.data.status ?? error.response.status,
message: error.response.data.msg ?? message,
timestamp: error.response.data?.timestamp ?? new Date().getTime()
}
MsgError(errorData.message)
return Promise.reject(errorData)
}
MsgError(error.message)
// 超时
return Promise.reject(error)
}
)
在组件中使用:
// 不需要在cath中弹出错误信息,只关注业务逻辑
const getDetail = (id: string | number): void => {
$api.getMetric(id)
.then((res) => {
currentDetail.value = res
})
}
方法2:重写项目中的message组件,进行覆盖
适用场景
如果你的项目中的错误信息全是使用catch这种方法来捕捉,并且涉及面很广,那么该方法很适合你。
如果下面这样的代码遍布你整个项目,那么推荐你使用该方法:
$api.addUser(data)
.then(() => {
//...
})
.catch((err) => {
ElMessage.error(err.message)
})
该方法不需要删除业务逻辑中的catch语句,改动最小
方法1 可以从根本上解决问题,适合新项目,或者是接口不多的项目,但是有的项目已经进行过很多次迭代了,涉及到的接口有很多,改动代价比较大,针对这种情况,方法2更适合你。
解决方案
原理就是重写你的message组件,以下拿element-ui的message举例,如果你使用的是别的组件库,也是一样的道理
- 重写message组件,
src/plugins/resetElMessage.js
// src/plugins/resetElMessage.js
import { ElMessage } from 'element-plus'
let messageInstance = null
const messageFn = (options) => {
// 如果存在mesaage实例并且当前消息与之前的消息不一致,那么就关闭之前的实例
if (messageInstance && messageInstance.msg === options.message) {
messageInstance.close()
}
// 获取ElMessage组件本身的close方法,用于重写实例(主要是为了存储当前弹出的信息内容)
const { close } = ElMessage(options)
messageInstance = {
msg: options.message, // 使用 msg 存储信息
close
}
};
['error', 'success', 'info', 'warning'].forEach((type) => {
messageFn[type] = (options) => {
if (typeof options === 'string') {
options = {
message: options
}
}
messageFn.msg = options.message
options.type = type
return messageFn(options)
}
})
export const message = messageFn
- 挂载到全局、替换原来的组件
// main.js
import { message } from './plugins/resetElMessage'
app.use(ElementPlus, {
locale: zhCn
})
// 该语句一定要写在注册element组件之后,否则覆盖不了
app.config.globalProperties.$message = message