vue3使用axios的请求封装与跨域配置 电脑版发表于:2022/7/29 18:06 tn2>以前vue2当中的详细的请求封装以及跨域的可以参考: https://www.tnblog.net/aojiancc2/article/details/4751 其实逻辑基本都是一致的,里边封装的步骤列得比较详细 [TOC] ### 先进行请求封装 在src下创建一个common文件夹里边创建一个request.js,加入封装代码。当然文件夹放哪里根据自己的需要来就行了。 ##### 加入简单封装的代码 ``` import axios from "axios"; import qs from "qs"; // vue3下的element-plus import { ElLoading , ElMessage } from "element-plus"; const loading = { // loading加载对象 loadingInstance: null, // 打开加载 open(loadtext) { if (this.loadingInstance === null) { // 如果实例 为空,则创建 this.loadingInstance = ElLoading.service({ text: loadtext, // '加载中...', // 加载图标下的文字 spinner: "el-icon-loading", // 加载图标 background: "rgba(0, 0, 0, 0.5)", // 背景色 customClass: "loading", // 自定义样式的类名 }); } }, // 关闭加载 close() { // 不为空时, 则关闭加载窗口 if (this.loadingInstance !== null) { this.loadingInstance.close(); } this.loadingInstance = null; }, }; axios.defaults.timeout = 300000; // 设置超时时间 // 配置请求头数据格式 // axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded' // 请求拦截器 // 请求拦截器 axios.interceptors.request.use( (axiosconfig) => { // 请求地址统一增加一个webapi,因为后台那个代理配置里边有加上webapi的前缀 //axiosconfig.url = "/webapi" + axiosconfig.url; //从环境变量读取,线下访问接口增加webapi前缀,上线就不增加 // axiosconfig.url = process.env.VUE_APP_BASE_API + axiosconfig.url; axiosconfig.url = "" + axiosconfig.url; //axiosconfig.headers.Authorization = "Bearer " + token; return axiosconfig; }, (err) => { loading.close(); // 关闭加载窗口 return Promise.reject(err); } ); // 响应拦截器 axios.interceptors.response.use( (res) => { //console.log("统一响应了"); loading.close(); // 关闭加载窗口 // 如果token过期了就直接跳转到登录 // if ( // (res.code === 401 || res.code === 500 || res.code === 403) && // !sessionStorage.getItem("401show") // ) { // // to re-login // sessionStorage["401show"] = true; // ElMessage.confirm("登录状态已过期", "登录提示", { // confirmButtonText: "重新登录", // cancelButtonText: "取消", // type: "warning", // }) // .then(() => { // sessionStorage.clear("401show"); // // 跳转到登录页 // location.href = "/#/login?redirect=/school-dashboard#/zuxia/index"; // }) // .catch(() => { // sessionStorage.clear("401show"); // }); // } return res; }, (err) => { loading.close(); // 关闭加载窗口 return Promise.reject(err); } ); const request = { get(url, data, loadtext) { // 判断有没有参数,有参数就把参数加上 if (data) { url = url + "?" + qs.stringify(data); } if (loadtext && loadtext.length > 0) { loading.open(loadtext); // 打开加载窗口 } return new Promise((resolve, reject) => { axios .get(url) .then(function (response) { // console.log("封装的方法数据回来了"); // console.log(response) if (response.status === 200 && response.data.code === 200) { resolve(response.data); } else { // 上面的判断要根据实际情况修改判断对哦,不然使用else里边的reject输出,虽然结果也能正确输出,但是浏览器上会提示Uncaught (in promise),也不能继续正确的向下执行了。 reject(response.data); } }) .catch((e) => { reject(e); }); }); }, post(url, data, loadtext) { if (loadtext && loadtext.length > 0) { loading.open(loadtext); // 打开加载窗口 } return new Promise((resolve, reject) => { axios .post(url, data) .then(function (response) { if (response.status === 200 && response.data.code === 200) { resolve(response.data); } else { reject(response.data); } }) .catch((e) => { reject(e); }); }); }, }; export default request; ``` ##### 这个是更完善一点的封装方法 增加了put,delete等方法的封装 ``` import axios from 'axios' import qs from 'qs' // vue3下的element-plus import { ElLoading, ElMessage } from 'element-plus' // 后台接口返回成功的状态码,需要根据自己的接口修改 const successCode = 200 const loading = { // loading加载对象 loadingInstance: null, // 打开加载 open(loadtext) { if (this.loadingInstance === null) { // 如果实例 为空,则创建 this.loadingInstance = ElLoading.service({ text: loadtext, // '加载中...', // 加载图标下的文字 spinner: 'el-icon-loading', // 加载图标 background: 'rgba(0, 0, 0, 0.5)', // 背景色 customClass: 'loading' // 自定义样式的类名 }) } }, // 关闭加载 close() { // 不为空时, 则关闭加载窗口 if (this.loadingInstance !== null) { this.loadingInstance.close() } this.loadingInstance = null } } axios.defaults.timeout = 300000 // 设置超时时间 // 配置请求头数据格式 // axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded' // 请求拦截器 axios.interceptors.request.use( (axiosconfig) => { // 请求地址统一增加一个webapi,因为后台那个代理配置里边有加上webapi的前缀 //axiosconfig.url = "/webapi" + axiosconfig.url; //从环境变量读取,线下访问接口增加webapi前缀,上线就不增加 // axiosconfig.url = process.env.VUE_APP_BASE_API + axiosconfig.url; axiosconfig.url = '' + axiosconfig.url // console.log("请求的地址2",axiosconfig.url) // 测试token , 获取token的方法换成自己的写法即可 let token = "eyJhbGciOiJSUzI1NiIsImtpZCI6IkU0QUU1Rk...." axiosconfig.headers.Authorization = "Bearer " + token; return axiosconfig }, (err) => { loading.close() // 关闭加载窗口 return Promise.reject(err) } ) // 响应拦截器 axios.interceptors.response.use( (res) => { //console.log("统一响应了",res); loading.close() // 关闭加载窗口 if(res.data.code!==successCode) { ElMessage({ message: res.data.msg, type: 'error' }) } // 如果token过期了就直接跳转到登录 // if ( // (res.code === 401 || res.code === 500 || res.code === 403) && // !sessionStorage.getItem("401show") // ) { // // to re-login // sessionStorage["401show"] = true; // ElMessage.confirm("登录状态已过期", "登录提示", { // confirmButtonText: "重新登录", // cancelButtonText: "取消", // type: "warning", // }) // .then(() => { // sessionStorage.clear("401show"); // // 跳转到登录页 // location.href = "/#/login?redirect=/school-dashboard#/zuxia/index"; // }) // .catch(() => { // sessionStorage.clear("401show"); // }); // } return res }, (err) => { loading.close() // 关闭加载窗口 console.log(err) ElMessage({ message: err, type: 'error' }) return Promise.reject(err) } ) const request = { get(url, data, loadtext) { // 判断有没有参数,有参数就把参数加上 if (data) { url = url + '?' + qs.stringify(data) } console.log("请求的地址",url) if (loadtext && loadtext.length > 0) { loading.open(loadtext) // 打开加载窗口 } return new Promise((resolve, reject) => { axios .get(url) .then(function (response) { // console.log("封装的方法数据回来了"); // console.log(response) if (response.status === 200 && response.data.code === successCode) { resolve(response.data) } else { // 上面的判断要根据实际情况修改判断对哦,不然使用else里边的reject输出,虽然结果也能正确输出,但是浏览器上会提示Uncaught (in promise),也不能继续正确的向下执行了。 reject(response.data) } }) .catch((e) => { reject(e) }) }) }, post(url, data, loadtext) { if (loadtext && loadtext.length > 0) { loading.open(loadtext) // 打开加载窗口 } return new Promise((resolve, reject) => { axios .post(url, data) .then(function (response) { if (response.status === 200 && response.data.code === successCode) { resolve(response.data) } else { reject(response.data) } }) .catch((e) => { reject(e) }) }) }, put(url, data, loadtext) { if (loadtext && loadtext.length > 0) { loading.open(loadtext) // 打开加载窗口 } return new Promise((resolve, reject) => { axios .put(url, data) .then(function (response) { if (response.status === 200 && response.data.code === successCode) { resolve(response.data) } else { reject(response.data) } }) .catch((e) => { reject(e) }) }) }, delete(url, data, loadtext) { if (loadtext && loadtext.length > 0) { loading.open(loadtext) // 打开加载窗口 } return new Promise((resolve, reject) => { axios .delete(url, data) .then(function (response) { if (response.status === 200 && response.data.code === successCode) { resolve(response.data) } else { reject(response.data) } }) .catch((e) => { reject(e) }) }) } } export default request ``` tip:其他的封装方法比如ts版本的请求封装可以参考下面的写法 ### 在配置好跨域 vue3的跨域配置在vite.config.js中,配置方法和以前几乎完全一致,根据自己的需求修改即可。 ![](https://img.tnblog.net/arcimg/aojiancc2/89f301d0490f4bd2befa1aada15288fa.png) ### 直接使用封装的代码调用接口 在需要使用的组件引入封装的js: ``` import request from '@/common/request' ``` **把onMounted这些也引入一下:** ``` import { reactive, toRefs, onMounted } from 'vue' ``` **在onMounted中调用接口** ``` const initData = () =>{ //测试接口访问 request.get('/api/v1/Home/1',{}).then(res => { console.log("测试数据返回") console.log(res) }) } onMounted(() => { initData() }) ``` 数据被成功输出了: ![](https://img.tnblog.net/arcimg/aojiancc2/bfad64c626d84863ab42202293659d35.png) 注意要把跨域配置好,不然会报跨域相关的错误。 ### 全局引入封装的请求js 在main.js中加入相关代码 **vue2中的写法:** ``` import request from "./common/request.js"; Vue.prototype.$http = request; ``` **vue3不能这样写了,应该这样写:** ``` import request from "./common/request.js"; const app = createApp(App) // vue3中要使用下面的写法 app.config.globalProperties.$http = request ``` #### 调用方法 先引入: ``` import { reactive, toRefs, getCurrentInstance, onMounted } from 'vue' let internalInstance = getCurrentInstance() as any const $http = internalInstance.appContext.config.globalProperties.$http ``` 调用: ``` const initData = () => { //测试接口访问 $http.get('/api/v1/Home/1').then(res => { console.log("测试数据返回") console.log(res) }) } onMounted(() => { initData() }) ``` #### 也可以使用异步的方式调用接口 ``` const fetchData = async () => { const res = await $http.get('/api/v1/Home/1') console.log("测试数据返回了") console.log(res); } onMounted(() => { fetchData() }) ``` 直接使用axios调用接口也是可以异步使用的 ``` const fetchData = async () => { // const res = await $http.get('/api/v1/Home/1') // console.log("测试数据返回了") // console.log(res); const res = await axios.get('/api/v1/Home/1') console.log("测试数据返回了.") console.log(res.data); } onMounted(() => { fetchData() }) ``` ### 单独提出来api方法 其实现在调用api也可以不用做成全局的方式,更多的是使用把api请求的方法单独提出来写 **首先在src文件夹下面创建好api,然后加入需要的js文件:** ![](https://img.tnblog.net/arcimg/aojiancc2/c38fe6ed0c9c4a9e900b8c6b775046ff.png) 封装好需要调用api的方法: ``` import request from '@/common/request' /** * @method getArticle 获取文章信息 * @param {*} data * @returns */ export function getArticle(data) { return request.get('/api/v1/Home/1',data) } ``` **调用:** 先引入封装的js: ``` import { getArticle } from '@/api/article' ``` 使用异步的方式调用方法: ``` const fetchData = async () => { const res = await getArticle({}) console.log("测试数据返回了..") console.log(res); } onMounted(() => { fetchData() }) ``` 使用then的方式调用 ``` const fetchData = () => { getArticle({}).then(res => { console.log("测试数据返回了...") console.log(res) }) } onMounted(() => { fetchData() }) ``` **如果返回的数据是这种格式** ![](https://img.tnblog.net/arcimg/aojiancc2/6bc261a7163b4600a4f8eccb574cd285.png) 取list和total可以这种取: ``` const fetchData = async () => { const { data: { list, total }, } = await getInquiryInfo({}) console.log("测试数据返回了..") console.log(list); console.log(total); } ``` ### 完善请求封装 ##### 把请求封装修改为ts版本 ``` import axios from 'axios' import qs from 'qs' // vue3下的element-plus import { ElLoading, ElMessage } from 'element-plus' // 后台接口返回成功的状态码,需要根据自己的接口修改 const successCode = 0 // loading加载对象 // let loadingInstance: any const loading = { // loading加载对象 loadingInstance:null as any, // 打开加载 open(loadtext: any) { //if (this.loadingInstance === null) { // 如果实例 为空,则创建 this.loadingInstance = ElLoading.service({ text: loadtext, // '加载中...', // 加载图标下的文字 // spinner: 'el-icon-loading', // 加载图标 background: 'rgba(0, 0, 0, 0.6)', // 背景色 customClass: 'loading' // 自定义样式的类名 }) //} }, // 关闭加载 close() { // 不为空时, 则关闭加载窗口 if (this.loadingInstance) { this.loadingInstance.close() } // this.loadingInstance = null } } axios.defaults.timeout = 300000 // 设置超时时间 // 配置请求头数据格式 // axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded' // 请求拦截器 axios.interceptors.request.use( (axiosconfig) => { // 请求地址统一增加一个webapi,因为后台那个代理配置里边有加上webapi的前缀 //axiosconfig.url = "/webapi" + axiosconfig.url; //从环境变量读取,线下访问接口增加webapi前缀,上线就不增加 // axiosconfig.url = process.env.VUE_APP_BASE_API + axiosconfig.url; axiosconfig.url = '' + axiosconfig.url // console.log("请求的地址2",axiosconfig.url) // 测试token , 获取token的方法换成自己的写法即可 let token = "eyJhbGciOiJSUzI1NiIsImtpZCI" axiosconfig.headers.Authorization = "Bearer " + token; return axiosconfig }, (err) => { loading.close() // 关闭加载窗口 return Promise.reject(err) } ) // 响应拦截器 axios.interceptors.response.use( (res) => { //console.log("统一响应了",res); loading.close() // 关闭加载窗口 if (res.data.code !== successCode) { ElMessage({ message: res.data.msg, type: 'error' }) } // 如果token过期了就直接跳转到登录 // if ( // (res.code === 401 || res.code === 500 || res.code === 403) && // !sessionStorage.getItem("401show") // ) { // // to re-login // sessionStorage["401show"] = true; // ElMessage.confirm("登录状态已过期", "登录提示", { // confirmButtonText: "重新登录", // cancelButtonText: "取消", // type: "warning", // }) // .then(() => { // sessionStorage.clear("401show"); // // 跳转到登录页 // location.href = "/#/login?redirect=/school-dashboard#/zuxia/index"; // }) // .catch(() => { // sessionStorage.clear("401show"); // }); // } return res }, (err) => { loading.close() // 关闭加载窗口 console.log(err) ElMessage({ message: err, type: 'error' }) return Promise.reject(err) } ) const request = { get(url: any, data: any, loadtext: any="") { // 判断有没有参数,有参数就把参数加上 if (data) { url = url + '?' + qs.stringify(data) } if (loadtext && loadtext.length > 0) { loading.open(loadtext) // 打开加载窗口 } return new Promise((resolve, reject) => { axios .get(url) .then(function (response) { // console.log("封装的方法数据回来了"); // console.log(response) if (response.status === 200 && response.data.code === successCode) { resolve(response.data) } else { // 上面的判断要根据实际情况修改判断对哦,不然使用else里边的reject输出,虽然结果也能正确输出,但是浏览器上会提示Uncaught (in promise),也不能继续正确的向下执行了。 reject(response.data) } }) .catch((e) => { reject(e) }) }) }, post(url: any, data: any, loadtext: any="") { if (loadtext && loadtext.length > 0) { loading.open(loadtext) // 打开加载窗口 } return new Promise((resolve, reject) => { axios .post(url, data) .then(function (response) { if (response.status === 200 && response.data.code === successCode) { resolve(response.data) } else { reject(response.data) } }) .catch((e) => { reject(e) }) }) }, put(url: any, data: any, loadtext: any="") { if (loadtext && loadtext.length > 0) { loading.open(loadtext) // 打开加载窗口 } return new Promise((resolve, reject) => { axios .put(url, data) .then(function (response) { if (response.status === 200 && response.data.code === successCode) { resolve(response.data) } else { reject(response.data) } }) .catch((e) => { reject(e) }) }) }, delete(url: any, data: any, loadtext: any="") { if (loadtext && loadtext.length > 0) { loading.open(loadtext) // 打开加载窗口 } return new Promise((resolve, reject) => { axios .delete(url, data) .then(function (response) { if (response.status === 200 && response.data.code === successCode) { resolve(response.data) } else { reject(response.data) } }) .catch((e) => { reject(e) }) }) } } export default request ``` ##### 在ts中使用封装的请求也是一样的 引入封装的request之后使用方法如下。其实都是一样的,就是要注意一下ts中的类型检查,类型不确定时使用可以使用any,unknown等类型来处理,或者使用类型断言。 ``` const getTasksList = async () => { const result: any = await request.get('/growing/api/Tasks/GetTasksPage', state.pageParam, "请求") console.log(result) state.tasksList = result.data.data // 请求的api地址,以及返回数据的格式自己根据自己的修改 // request.get('/education/api/Feedback/GetSchools', state.pageParam).then((res: any) => { // console.log('获取任务') // console.log(res) // state.tasksList = res.data.data // }) } ``` ### vue3的一个模板vue3-admin-template里边自带封装的request请求 贴一下他这个封装的代码,也可以考虑借鉴: ``` import axios from 'axios' import { ElMessage, ElMessageBox } from 'element-plus' import { useUserStore } from '@/store/user' import { getCookies } from '@/utils/storage' import defaultSettings from '@/settings' // 业务请求 const request = axios.create({ baseURL: defaultSettings.isMockData ? '' : import.meta.env.VITE_APP_BASE_API, // url = base url + request url // withCredentials: true, // send cookies when cross-domain requests timeout: 5000 // request timeout }) // const request = axios.create({ // baseURL: "", // url = base url + request url // // withCredentials: true, // send cookies when cross-domain requests // timeout: 5000 // request timeout // }) // request interceptor request.interceptors.request.use( (config) => { // do something before request is sent const userStore = useUserStore() if (userStore.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'] = getCookies('Fanqie-Token') } return config }, (error) => { // do something with request error console.log(error) // for debug return Promise.reject(error) } ) // response interceptor request.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 && res.code!=200) { ElMessage({ 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 ElMessageBox.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(() => { const userStore = useUserStore() userStore.resetToken.then(() => { location.reload() }) }) } return Promise.reject(new Error(res.message || 'Error')) } else { return res } }, (error) => { console.log('err' + error) // for debug ElMessage({ message: error.message, type: 'error', duration: 5 * 1000 }) return Promise.reject(error) } ) /** * 用于请求 gitee 的数据 */ const requestA = axios.create({ baseURL: import.meta.env.VITE_APP_GITEE_BASE_API, // url = base url + request url timeout: 60 * 1000 }) requestA.interceptors.request.use( (config) => { return config }, (error) => { console.log(error) return Promise.reject(error) } ) requestA.interceptors.response.use( (response) => { const { data, status, statusText } = response if (status === 200) { return data } else { ElMessage({ message: statusText || 'Error', type: 'error', duration: 5 * 1000 }) return false } }, (error) => { console.log('err' + error) ElMessage({ message: error.message, type: 'error', duration: 5 * 1000 }) return Promise.reject(error) } ) export { request, requestA } ``` **他这个封装的api接口请求示例:** 也是像上面说的那样单独提出来一个文件写的 ``` import { request } from '@/utils/request' /** * @method addUserLogin 登录 * @param {*} data * @returns */ export function addUserLogin(data) { return request({ url: '/user-login', method: 'post', data }) } /** * @method addUserInfo 用户信息 * @param {*} data * @returns */ export function addUserInfo(data) { return request({ url: '/user-info', method: 'post', data }) } ``` 调用方法和前面的一样就不在累述了