解决Message信息出现多个的问题

653 阅读3分钟

我正在参加「掘金·启航计划」 image (12).png 如图,当长时间未操作,token超时,同时调用多个接口,这几个接口的逻辑又相互独立、各不相干,就会同时返回“请重新登录”的提示信息,虽然逻辑上没有问题,但是交互效果很不好,针对这个问题,目前找到了两个解决方案,如果对你有用,那就请你点赞收藏~

方法1:axios响应拦截 + 防抖(推荐)

适用场景:

  1. 适合新项目,或者是项目接口不多的情况下
  2. 在项目中调用接口时,不需要在每个接口的catch中弹出message信息,只需要关注业务逻辑。如果你的项目中在写业务逻辑的时候,在接口的catch中使用message提示错误信息,需要提前进行删除
  3. 对后端接口的要求比较高,必须保证每个接口的失败信息足够完善,也就是说在接口响应失败的情况下,必须返回符合业务场景的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-uimessage举例,如果你使用的是别的组件库,也是一样的道理

  1. 重写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
  1. 挂载到全局、替换原来的组件
// main.js
import { message } from './plugins/resetElMessage'
app.use(ElementPlus, {
  locale: zhCn
})
// 该语句一定要写在注册element组件之后,否则覆盖不了
app.config.globalProperties.$message = message