单例模式实现一个message提示插件应用在axios请求中

337 阅读3分钟

我们一般在项目中封装axios的时候会做全局的loading、message来做接口的提示加载状态和错误提示等。但是element-ui提供的message样式难看的一匹。在不改改变其全局样式的情况下我们想自定义个message插件实现这个效果。 如图:

20210618154409826.png

微信截图_20211023110554.png 在项目下定义一个message.js文件

  class Mes {
    constructor() {
      this.ele = document.createElement('div')
      this.ele.className = 'mes-box'
      document.body.appendChild(this.ele)
    }
    setContent(opts) {
      this.ele.classList.add('fadeIn')
      const type = opts.type || 'default'
      let iconClassList = ''
      switch (type) {
        case 'default':
          iconClassList = 'iconfont icon-loading rotate default inBlock'
          break
        case 'success':
          iconClassList = 'iconfont icon-success success'
          break
        case 'warning':
          iconClassList = 'iconfont icon-warning warning'
          break
        case 'error':
          iconClassList = 'iconfont icon-error error'
          break
        default:
          iconClassList = 'iconfont icon-loading inBlock ratate'
      }
      this.ele.innerHTML = `
            <div class="mes-icon-box ${type}">
                <i class="${iconClassList}"></i>
            </div>
            <div class="mes-cont">${opts.text || '加载中,请稍后...'}</div>
           `
      if (opts.duration) {
        setTimeout(() => {
          this.ele.classList.remove('fadeIn')
        }, opts.duration)
      }
    }
    close() {
      this.ele.classList.remove('fadeIn')
    }
  }
  let instance = null
  return function(options = { duration: '', icon: '', text: '', type: 'default' }) {
    if (!instance) instance = new Mes()
    instance.setContent(options)
    return instance
  }
})()

export default Message

当然message的样式我们需要在全局的css中去做相关设置,我们新建css样式在main.js中引入

.mes-box {
  display: flex;
  position: fixed;
  top: 50%;
  left: 50%;
  padding: 16px;
  min-width: 220px;
  transform: translate(-50%, -50%);
  box-shadow: 0 0 10px 4px #ccc;
  transition: all 0.2s ease-out;
  font-size: 14px;
  z-index: 3000;
  opacity: 0;
  background: #fff;
}
.inBlock {
  display: inline-block;
}
.fadeIn {
  opacity: 1;
}
.mes-icon-box {
  width: 60px;
  text-align: center;
}
.mes-cont {
  flex: 1
}
.rotate {
  animation: ani 1s ease-in infinite;
}
@keyframes ani {
  0% {
    -webkit-transform: rotate(0deg);
  }
  100% {
    -webkit-transform: rotate(360deg);
  }
}
.slide-enter-active,
.slide-leave-active {
  transition: all 0.2s;
  top: 100px;
  opacity: 1;
}
.slide-enter,
.slide-leave-active {
  opacity: 0;
  top: -100px;
}
.default {
  color: #409EFF
}
.error {
  color: #f00
}
.warning {
  color: #ff7a33
}
.success {
  color: #63deaa
}

第三步我们将message应用在axios的封装中

import router from '@/router'
import qs from 'qs
import Message from '@/plugins/message'

let loadingInstance = null
console.log('process.env:', process.env)
const config = {
  timeout: 5000,
  baseURL: process.env.VUE_APP_BASE_API
}
const Http = axios.create(config)

// 添加请求拦截器

Http.interceptors.request.use((config) => {
  const Token = localStorage.getItem("token")
  if (Token) {
    config.headers.token = Token
  }
  if (config.method === 'post' || config.method === 'put') {
    if (config.data.loading) {
      console.log('loadingInstance:', loadingInstance)
      loadingInstance = new Message()
    }

  } else {
    if (config.params.loading) {
      loadingInstance = new Message()
    }
  }
  return config
})

// 添加响应拦截器

Http.interceptors.response.use(
  (response) => {
    if (response.data.code === 0) {
      if (loadingInstance) {
        loadingInstance.close()
      }
    } else {
      loadingInstance = new Message({
        type: 'error', duration: 3000,
        text: '程序异常,请联系管理员'
      })
    }
    return response.data
  },

  (error) => {
    console.log('error:', error)
    if (
      error.code === 'ECONNABORTED' ||
      error.message === 'Network Error' ||
      error.message.includes('timeout')
    ) {
      new Message({ type: 'error', text: '当前网络错误', duration: 3000 })
    }
    // 错误处理
    const code = error.response.status
    if (code === 200) {
    } else if (code === 403) {
      // 处理token过期问题
      loadingInstance = new Message({
        type: 'error', duration: 3000,
        text: '登录已过期,请重新登录'
      })

      localStorage.clear()
      router.replace('/')

    } else {
      loadingInstance = new Message({
        type: 'error', duration: 3000,
        text: error.response.data
      })
    }
    return Promise.reject(error)
  }
)

// loading:  true:请求接口时出现加载提示,false反之

function request(url, method = 'GET', params = {}, loading = false, headerConfig = {}) {
  const Method = method.toLowerCase()
  if (Method === 'get' || Method === 'delete') {
    params = { ...params, loading }
    return Http[Method](url, { params })
  } else if (Method === 'post' || Method === 'put') {
    let data = {}
    if (Object.keys(headerConfig).length > 0) {
      data = qs.stringify({ ...params, loading })
    } else {
      data = Object.assign(params, { loading }, headerConfig)
    }
    return Http[Method](url, data, headerConfig)
  }
}
export default request

为什么需要单例模式,因为我们在axios拦截中请求成功,需要关闭这个message,所以所有的打开与关闭,针对的都是同一个实例对象。这样就可以防止页面中多个message出现干扰 `因为我们的dom中一直都会存在这个mes-box,而且我们对它的隐藏实际是是通过opactity:0来实现的,但是它的z-index很大,导致虽然在视图上看不见它,但是它会干扰到我们正常的点击。也就是如果有一个按钮的位置刚好和messagebox重合,但是由于层级关系,messagebox会遮挡住页面内容导致无法点击。其实解决办法也很简单:我们给messagebox默认设置一个很低的层级比如z-index:-400,在fadeIn的时候给它把层级提上去。

在fadeIn的css里做如下设置: .fadeIn{ opacity:1; z-index:3000 } 即可解决这个问题