在vue项目中,我们一般会封装一个request.js的请求工具(一般在utils/request.js),然后整个项目的api请求都使用这个封装的request,然后在request中写一些请求的公共配置,比如加密、请求头带上token、反馈请求结果等等

下面是element-admin的request.js文件内容

// request.js
import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'

// create an axios instance
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
  // withCredentials: true, // send cookies when cross-domain requests
  timeout: 5000 // request timeout
})

// request interceptor
service.interceptors.request.use(
  config => {
    // do something before request is sent

    if (store.getters.token) {
      // let each request carry token
      // ['X-Token'] is a custom headers key
      // please modify it according to the actual situation
      config.headers['X-Token'] = getToken()
    }
    return config
  },
  error => {
    // do something with request error
    console.log(error) // for debug
    return Promise.reject(error)
  }
)

// response interceptor
service.interceptors.response.use(
  /**
   * If you want to get http information such as headers or status
   * Please return  response => response
  */

  /**
   * Determine the request status by custom code
   * Here is just an example
   * You can also judge the status by HTTP Status Code
   */
  response => {
    const res = response.data

    // if the custom code is not 20000, it is judged as an error.
    if (res.code !== 20000) {
      Message({
        message: res.message || 'Error',
        type: 'error',
        duration: 5 * 1000
      })

      // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
        // to re-login
        MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
          confirmButtonText: 'Re-Login',
          cancelButtonText: 'Cancel',
          type: 'warning'
        }).then(() => {
          store.dispatch('user/resetToken').then(() => {
            location.reload()
          })
        })
      }
      return Promise.reject(new Error(res.message || 'Error'))
    } else {
      return res
    }
  },
  error => {
    console.log('err' + error) // for debug
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)

export default service

效果:(在请求成功或失败时会在右上角反馈信息)

 这个模板使用的是vue2 + element-ui

我自己写的时候用的是vue3 + primevue,所有写的时候就遇到了很多问题,在这里记录下

先看primevue里面的Toast用法

main.js

// main.js
import {createApp} from 'vue';
import ToastService from 'primevue/toastservice';

const app = createApp(App);
app.use(ToastService);
<!-- app.vue -->
<script setup>
import Toast from 'primevue/toast';
</script>

<template>
	<Toast />
	<router-view></router-view>  
</template>

<style scoped>
</style>

使用

// 用法1
export default {
    mounted() {
        this.$toast.add({severity:'success', summary: 'Success Message', detail:'Order submitted', life: 3000});
    }
}


// 用法2
import { defineComponent } from "vue";
import { useToast } from "primevue/usetoast";

export default defineComponent({
    setup() {
        const toast = useToast();
        toast.add({severity:'info', summary: 'Info Message', detail:'Message Content', life: 3000});
    }
})

可以看出有俩种用法,第一种是通过vue实例获取$toast(需要获取vue实例),第二种是使用useToast方法得到toast(需要在setup中),只要得到了toast就可以调用add方法添加消息了

那么问题就转为了怎么在request.js中获得toast对象了

// request.js
import axios from 'axios'
import store from '@/store'
import { getToken } from '@/utils/auth'
import toast from '@/utils/toast'

// create an axios instance
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
  // withCredentials: true, // send cookies when cross-domain requests
  timeout: 5000 // request timeout
})

// request interceptor
service.interceptors.request.use(
  config => {
    // do something before request is sent

    if (store.getters.token) {
      // let each request carry token
      // ['X-Token'] is a custom headers key
      // please modify it according to the actual situation
      config.headers['X-Token'] = getToken()
    }
    return config
  },
  error => {
    // do something with request error
    console.log(error) // for debug
    return Promise.reject(error)
  }
)

// response interceptor
service.interceptors.response.use(
  /**
   * If you want to get http information such as headers or status
   * Please return  response => response
  */

  /**
   * Determine the request status by custom code
   * Here is just an example
   * You can also judge the status by HTTP Status Code
   */
  response => {
    const res = response.data

	if( res.code !== 200 ) {
		if(res.code === 401) {
			toast.add({severity:'error', 
               summary: '未登录', 
               detail:'请先登录', 
               life: 3000
            });
			store.dispatch('user/resetToken').then(() => {
				 //location.reload()
			})
		}
		return Promise.reject(new Error(res.message))
	} else {
		return res;
	}
  },
  error => {
    console.log('err' + error) // for debug
    toast.add({severity:'error', 
       summary: '错误信息', 
       detail:  error.message, 
       life: 5000
    });
    return Promise.reject(error)
  }
)

export default service

方式一  useToast(不行)

// toast.js
import { useToast } from "primevue/useToast";
const toast = useToast()
console.log(toast  )

export default toast

控制台会直接报错, 可以看出这个方法不可行

方式二 (关键点在于获取vue实例)

在vue2中可以用Vue.prototype获取到toast,而vue3改为了app.config.globalProperties,因此获取到app实例就可以通过app.config.globalProperties.$toast获取到toast了

vue2中可以通过import Vue from 'vue' 直接获取Vue实例,而vue3里面以我的水平还没找到哪个好的接口能直接获取vue实例的

1 用this (不行)

// toast.js
const app = this
const toast = app.config.globalProperties.$toast
export default toast

可以看出this是undefined ,前面的this指的是vue的实例,在普通的组件中可以用,但是在js文件中不能用this

2 getCurrentInstance(不行)

// toast.js
import {getCurrentInstance} from 'vue'
const app = getCurrentInstance()
const toast = app.config.globalProperties.$toast
export default toast

 可以看到这里通过getCurrentInstance()获取到的是null

3 导出app

这个方法是我自己想出来的解决方案,vue3的app是通过createApp得到的,我觉得我们可以通过export把他导出来给其他文件使用

// main.js
import {createApp} from 'vue'
import App from './app.vue'

import PrimeVue from 'primevue/config';
import 'primevue/resources/primevue.min.css';
import 'primeicons/primeicons.css'
import 'primevue/resources/themes/saga-blue/theme.css'

import ToastService from 'primevue/toastservice';

const app = createApp(App)
app.use(PrimeVue)
   .use(ToastService)
app.mount('#app')
export default app
// toast.js
import app from '@/main'
const toast = app.config.globalProperties.$toast
export default toast

 可以看出app是可以拿到的,但是这个时候app还在初始化,会报错,其实我们不需要这么快用app,只是要一个访问他的方法而已,所以可以对他进行封装,等调用的时候才去使用app

// toast.js
import app from '@/main'
getToast = () => app.config.globalProperties.$toast
const toast = {
	add(param) {
		getToast().add(param)
	}
}
export default toast

 可以看到当请求失败时页面能够弹出错误提示信息,问题解决,但还是有一个小bug,原因不明

用的vite启动项目是有热部署的,每次修改了当前页面保存时,浏览器会自动更新页面,这时页面会白屏,但是按F5刷新页面又恢复正常,查看控制台

 得到这样的信息,经过一系列排查,最终发现是不能import 'main.js',既然这样那就把app的创建放在其他文件里面去,再导出,main.js和其他需要app的文件里面导入app,如下

最终解决方案:

// app.js
import {createApp} from 'vue'
import App from './app.vue'

import PrimeVue from 'primevue/config';
import 'primevue/resources/primevue.min.css';
import 'primeicons/primeicons.css'
import 'primevue/resources/themes/saga-blue/theme.css'

import ToastService from 'primevue/toastservice';

const app = createApp(App)
app.use(PrimeVue)
   .use(ToastService)

export default app
// main.js
import app from './app'
app.mount('#app')
// toast.js
import app from '@/app'
const getToast = () => app.config.globalProperties.$toast
const toast = {
	add(param) {
		getToast().add(param)
	}
}
export default toast
// request.js
import axios from 'axios'
import store from '@/store'
import { getToken } from '@/utils/auth'
import toast from '@/utils/toast'

// create an axios instance
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
  // withCredentials: true, // send cookies when cross-domain requests
  timeout: 5000 // request timeout
})

// request interceptor
service.interceptors.request.use(
  config => {
    // do something before request is sent

    if (store.getters.token) {
      // let each request carry token
      // ['X-Token'] is a custom headers key
      // please modify it according to the actual situation
      config.headers['X-Token'] = getToken()
    }
    return config
  },
  error => {
    // do something with request error
    console.log(error) // for debug
    return Promise.reject(error)
  }
)

// response interceptor
service.interceptors.response.use(
  /**
   * If you want to get http information such as headers or status
   * Please return  response => response
  */

  /**
   * Determine the request status by custom code
   * Here is just an example
   * You can also judge the status by HTTP Status Code
   */
  response => {
    const res = response.data

	if( res.code !== 200 ) {
		if(res.code === 401) {
			toast.add({severity:'error', 
               summary: '未登录', 
               detail:'请先登录', 
               life: 3000
            });
			store.dispatch('user/resetToken').then(() => {
				 //location.reload()
			})
		}
		return Promise.reject(new Error(res.message))
	} else {
		return res;
	}
  },
  error => {
    console.log('err' + error) // for debug
    toast.add({severity:'error', 
       summary: '错误信息', 
       detail:  error.message, 
       life: 5000
    });
    return Promise.reject(error)
  }
)

export default service

问题全部解决,即使修改当前访问的页面保存,页面也不会出现白屏了。

以上就是本次解决问题的全部过程及思路,技术文笔不好,有错误望大佬指出。

Logo

前往低代码交流专区

更多推荐