Vue 基于axios接口封装,dev环境跨域解决

Posted JIZQAQ

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue 基于axios接口封装,dev环境跨域解决相关的知识,希望对你有一定的参考价值。

先展示一下现在项目结构,红色框框圈出来的是涉及到改动的部分。

先展示一下.env.production的内容,.env.production里面VUE_APP_CURRENTMODE = 'prod',下面根据实际项目需求配置。

VUE_APP_CURRENTMODE = 'dev'
VUE_APP_BASEURL = 'https://XXXXXXX'
VUE_APP_IMGURL = 'https://XXXXXXX'

main.js 这样子用this.$api就能调用我们封装好的接口了。

import { createApp } from "vue";
import App from "./App.vue";
import axios from "axios";
import api from './request/api/index'
const app = createApp(App);
axios.defaults.baseURL = "/api";
axios.defaults.headers = {
  "Content-Type": "application/json",
};
app.config.globalProperties.$axios = axios;
app.mount("#app");
app.config.globalProperties.$api = api;

vue.config.js _(:з」∠)_这个算是我不太熟悉的地方,我开始是只在header里面设置了Access-Control-Allow-Origin和Access-Control-Allow-Headers,但是还是报跨域错误…问了下同事,用这个vue里设置proxy的办法解决了。

module.exports = {
  publicPath: "./",
  devServer: {
    open: true,
    // host: "localhost",
    // port: 8080,
    overlay: {
      warnings: false,
      errors: true,
    },
    proxy: {
      "/api": {
        target: "https://XXXXXXX",
        changeOrigin: true,
        ws: true,
        pathRewrite: {
          "^/api": "",
        },
      },
    },
  },
};

然后就是最重要的部分了,分出了Controller便于管理各个模块的接口。如果有需要复用的接口发生路径改变,也只用修改一个地方。在接口超级多的大型项目里面,非常好用。

config.js是导入我们全局变量的文件

export default {
  baseURL: process.env.VUE_APP_BASEURL,
  imgURL: process.env.VUE_APP_IMGURL,
}

index.js 如果/request/api 里面需要新增Controller的话,在index.js文件中记得也要加上。因为这边是样例就只放了一个Controller。

/**
 * api接口的统一出口
 */
// news模块接口
import newsController from './newsController',

// 导出接口
export default {
  newsController: newsController,
}

newsController.js 列出某个模块下所有的接口

/**
 *News模块接口列表
 */

import axios from '../server' // 导入server中创建的axios实例

const newsController = {
  getNewsList(params) {
    return axios.post('/news/list', params)
  },
  getNewsDetail(params) {
    return axios.post('/news/detail', params)
  },
}
export default newsController

helper.js

const helper = {
  // 根据name获取地址栏的参数值
  getQueryString(name) {
    const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`)
    const hash = window.location.hash
    const search = hash.split('?')
    const r = search[1] && search[1].match(reg)
    if (r != null) return r[2]; return ''
  },
  // 拼接参数至url
  queryString(url, query) {
    const str = []
    for (const key in query) {
      str.push(key + '=' + query[key])
    }
    const paramStr = str.join('&')
    return paramStr ? `${url}?${paramStr}` : url
  },
  bin2hex(s) {
    var i; var l; var o = ''
    var n
    s += ''
    for (i = 0, l = s.length; i < l; i++) {
      n = s.charCodeAt(i)
        .toString(16)
      o += n.length < 2 ? '0' + n : n
    }
    return o
  },
  // 判断是否过期
  timeOverdue(startTime, endTime) {
    // 可以传日期时间或时间戳
    const contrastTime = 20 * 60 * 1000
    const difference = startTime - endTime // 时间差的毫秒数
    if (difference > contrastTime) {
      return false
    } else {
      return true
    }
    // return `相差${days}天${hours}小时${minutes}分钟${seconds}秒`
  }
}
export default helper

server.js则是比较重要的部分了,简单地说就是在我们代码里调用接口时候,拦截一下,给判断下token过期没,没的话加上header,请求。过期的话,又进行什么操作。然后返回信息给我们的时候也拦截一下提前进行处理。还可以根据不同错误代码返回不同的信息什么的都写在这,这些具体看各个项目什么需求做改动了。这边只简单放一个,不带什么业务逻辑的版本。

这边用的是vant的ui,所以用的vant的Toast做例子。

import axios from 'axios'
import helper from './helper'
import CONFIG from './api/config'
import router from '../router'
import StorageDB from '../utils/storageDB'
import { Toast } from 'vant'

/**
 * 跳转登录页
 * 携带当前页面路由,以期在登录页面完成登录后返回当前页面
 */
const toLogin = () => {
  router.replace({
    path: '/login',
    query: {
      redirect: router.currentRoute.fullPath
    }
  })
}

/**
 * 请求失败后的错误统一处理
 * @param {Number} status 请求失败的状态码
 */
const errorHandle = (status, other) => {
  // 状态码判断
  switch (status) {
    // 401: 未登录状态,跳转登录页
    case 401:
      toLogin()
      break
    // 403 token过期
    // 清除token并跳转登录页
    case 403:
      Toast('登录超时');
      
      StorageDB.removeItem('TOKEN')
      StorageDB.removeItem('TOKEN_TIME')
      setTimeout(() => {
        toLogin()
      }, 1000)
      break
    // 404请求不存在
    case 404:
      Toast('请求不存在');
      break
    case 406:
      logout406()
      break
    case 500:
      Toast('网络异常');
      break
    case 502:
      Toast('网络异常');
      break
    default:
      console.log(other)
  }
}

// let loading
// 创建axios实例
const instance = axios.create({
  //这里就是dev的环境强行使用前面设置的proxy
  baseURL: process.env.VUE_APP_CURRENTMODE == 'dev'?'/api':CONFIG.baseURL,
  timeout: 1000 * 15,
  headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Headers': 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild',
    'Content-Type': 'application/json;charset=utf-8',
  }
})

/**
 * 请求拦截器
 * 每次请求前,如果存在token则在请求头中携带token
 */
instance.interceptors.request.use(
  config => {
    // 设置或者刷新 token
    //const token = StorageDB.getItem('TOKEN')
    //config.headers.Authorization = token
    return config
  },
  error => Promise.error(error))

// 响应拦截器
instance.interceptors.response.use(
  // 请求成功
  res => {
    /*此处省略好多行代码*/
    if (res.time) {
      StorageDB.setItem('TOKEN_TIME', new Date(res.time).getTime())
    }
    return res.status === 200 ? Promise.resolve(res.data) : Promise.reject(res.data)
  },
  // 请求失败
  error => {
    const { response } = error
    // loading.close()
    if (response) {
      // 请求已发出,但是不在2xx的范围
      errorHandle(response.status, response.data.message)
      return Promise.reject(response)
    } else {
      console.dir(error)
      console.log('网络服务器异常,请稍后重试')
    }
  })

// export default instance;
function apiAxios(method, url, params, isExport) {
  return instance({
    method: method,
    // 拼接参数
    url: method === 'GET' ? helper.queryString(url, params) : url,
    data: method === 'POST' || method === 'PUT' || method === 'DELETE' ? params : null,
    withCredentials: false,
    responseType: isExport ? 'blob' : ''
  })
}

let loginOutLoading = false

async function logout406() {
  if (!loginOutLoading) {
    loginOutLoading = true
  }
}

export default {
  components: {
    [Toast.name]: Toast
  },
  get: function(url, params) {
    return apiAxios('GET', url, params)
  },
  post: function(url, params) {
    return apiAxios('POST', url, params)
  },
  put: function(url, params) {
    return apiAxios('PUT', url, params)
  },
  delete: function(url, params) {
    return apiAxios('DELETE', url, params)
  },
  export: function(url, params) {
    return apiAxios('POST', url, params, true)
  },
  getExport: function(url, params) {
    return apiAxios('GET', url, params, true)
  },
  upload: function(url, params) {
    return apiAxios('post', url, params, false, true)
  }
}

到这里就已经封装好了,但是其实我的样例代码其实是没和登录那块逻辑糅在一起的…不过如果没有token需求的dev环境,也已经可以用啦。

具体使用就很简单了,在你的.vue文件里面

this.$api.newsController.getNewsList(params).then(res => {
            console.log(res)
          }).catch(function(err) {
          console.log(err);
        });

这就完全ok拉!

以上是关于Vue 基于axios接口封装,dev环境跨域解决的主要内容,如果未能解决你的问题,请参考以下文章

VUEvue在vue-cli3环境下基于axios解决跨域问题

electron-vue项目中开发环境中的axios跨域问题

vue-cli封装axios

django+vue无法设置跨域cookies

Vue---vue-cli 中的proxyTable解决开发环境中的跨域问题

vue 解决axios 跨域问题