axios

lishihuan大约 9 分钟

axios

参考: https://huaweicloud.csdn.net/638eaba6dacf622b8df8d10e.htmlopen in new window

1. axios的封装

在vue项目中,和后台交互获取数据这块,我们通常使用的是axios库,它是基于promise的http库,可运行在浏览器端和node.js中。他有很多优秀的特性,例如拦截请求和响应、取消请求、转换json、客户端防御XSRF等。所以我们的尤大大也是果断放弃了对其官方库vue-resource的维护,直接推荐我们使用axios库。如果还对axios不了解的,可以移步axios文档。

2. 安装

npm install axios

3. 使用

一般我会在项目的src目录中,新建一个request文件夹,然后在里面新建一个http.js和一个api.js文件。http.js文件用来封装我们的axios,api.js用来统一管理我们的接口。

3.1 开发环境、测试环境和生产环境

通过 [.env](../vue_项目搭建/vue项目搭建.md#8. .env)

axios.defaults.baseURL = process.env.VUE_APP_BASE_API

或者使用下面的(不推荐

// 环境的切换
if (process.env.NODE_ENV == 'development') {    
    axios.defaults.baseURL = 'https://www.baidu.com';} 
else if (process.env.NODE_ENV == 'debug') {    
    axios.defaults.baseURL = 'https://www.ceshi.com';
} 
else if (process.env.NODE_ENV == 'production') {    
    axios.defaults.baseURL = 'https://www.production.com';
}

3.2 设置请求超时

通过axios.defaults.timeout设置默认的请求超时时间。例如超过了10s,就会告知用户当前请求超时,请刷新等。

axios.defaults.timeout = 10000;

3.3 post请求头的设置

post请求的时候,我们需要加上一个请求头,所以可以在这里进行一个默认的设置,即设置post的请求头为application/x-www-form-urlencoded;charset=UTF-8

axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';

3.4 其他准备

创建一个http.js h用来封装我们的axios

同时因为请求是异步的所有一般搭配 promise 使用

1. 使用 async/await

async loadData_backup () {
    const { data: res } = await this.$axios.post('login', this.loginForm)
    if (res.meta.status !== 200) { }
},

使用async await 处理reject

async loadData_backup () {
    const { data: res } = await this.$axios.post('login', this.loginForm).catch((e) => {
        console.log(e,666) // 这里返回上面的{sus: false,msg: '缺少参数'}
        return {data:'这是await返回的错误信息'} // 可以重定义错误返回
    })
    if (res.meta.status !== 200) {
        
    }
},

2. 使用 promise

2.1 promise
export const axiosApiFormdata = (url, params, method, headers) => {
    const token = JSON.parse(localStorage.getItem('token'))
    if (!headers) {
        headers = { 'Content-Type': 'application/json;charset=utf-8' } // 'x-www-form-urlencoded'
    }
    if (token != null) {
        headers.Authorization = 'Bearer ' + token
    }
    return new Promise((resolve, reject) => {
        let param
        if (method === 'post' || method === 'POST') {
            param = {
                method: method,
                url: url,
                data: params,
                headers: headers
            }
        } else if (method === 'get' || method === 'GET') {
            param = {
                method: method,
                url: url,
                params: params,
                headers: headers
            }
        } else { // put 请求
            param = {
                method: method,
                url: url,
                data: params,
                headers: headers
            }
        }
        axios(param)
            .then((res) => {
                if (res.status === 200) {
                    resolve(res.data)
                } else {
                    // 接口错误提示
                    Message({ message: res.data.message, type: 'error' })
                }
            })
            .catch((err) => {
                console.log(`${url} 网络请求异常:${err}`)
                reject(err)
            })
    })
}
2.2 promise.all()
let p1 = new Promise((resolve, reject) => {
  getNumber()
  resolve('成功了p1')
})

let p2 = new Promise((resolve, reject) => {
  getName()
  resolve('成功了p2')
})
let p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});

Promise.all([p1, p2, p3]).then((result) => {
  console.log(result) // ['成功了p1', '成功了p2', 'foo']
}).catch((error) => {
})
// 典型案例
async getCaseData (resolve) {
    const res = await this.axiosApiFormdata('/operation/case/list', { sId: this.deptId }, 'GET')
    if (res && res.code === 200 && res.data) {
        this.caseList = res.data
    } else {
        this.caseList = []
    }
    console.log('getCaseData-- 数据加载完成')
    resolve('success')
},
 async loadData () {
console.log('loadData-- 开始数据加载')
     let f1 = new Promise((resolve, reject) => {
         this.getQualificationData(resolve) // 资质文件
     });
     let f2 = new Promise((resolve, reject) => {
         this.getCp2Data(resolve) // 产品推荐
     });
     let f3 = new Promise((resolve, reject) => {
         this.getCaseData(resolve) // 典型案例
     });
     // this.getQualificationData() // 资质文件
     // this.getCp2Data() // 产品推荐
     // this.getCaseData() // 典型案例
     Promise.all([f1,f2,f3]).then((result) => {
         console.log('loadData-- 加载完成')
         this.initScroll();
     }).catch((error) => {
     })
 },    
    
submit(){
    const p1=new Promise((resolve,reject)=>{
        this.$refs['basic'].$refs['ruleForm'].validate(valid=>{
            if(valid) resolve()
        })
    })
    const p2=new Promise((resolve,reject)=>{
        this.$refs['onshelf'].$refs['ruleForm'].validate(valid=>{
            if(valid) resolve()
        })
    })
    const p3=new Promise((resolve,reject)=>{
        this.$refs['more'].$refs['ruleForm'].validate(valid=>{
            if(valid) resolve()
        })
    })
    Promise.all([p1,p2,p3]).then(()=>{
        console.log('验证通过,提交表单') 

    })
}
// 自定义校验
validateForm(callback,errorCalback) {
    const form = new Promise((resolve,reject)=>{
        this.$refs['form'].validate(valid => {
            console.log('form1' + valid);
             if(valid) resolve()
            if(!valid) reject()
        })
    })
    const form1 = new Promise((resolve,reject)=>{
        this.$refs['form1'].validate(valid => {
            console.log('form1' + valid);
            if(valid) resolve()
            if(!valid) reject()
        })
    })

    Promise.all([form,form1]).then(()=>{
        console.log('验证通过,提交表单')
        callback()
    }).catch(res => {
        console.log('all......');
        errorCalback();
    })
},
    // 表单提交
submitForm () {
    this.validateForm(() => {
        console.log('校验成功....')
    },() =>{
        console.log('失败....')
    })
},    

3.优化

axios 请求一般搭配 nprogress 进度条使用

npm install --save nprogress
// 导入 NProgress 包对应的JS和CSS
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'

4. 简单配置

/* 封装 axios */
import Vue from 'vue'
import axios from 'axios'
// 导入 NProgress 包对应的JS和CSS
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'

// 配置请求的跟路径
axios.defaults.baseURL = 'http://localhost:3001/'
// 在 request 拦截器中,展示进度条 NProgress.start()
axios.interceptors.request.use(config => {
    // console.log(config)
    NProgress.start()
    config.headers.Authorization = window.sessionStorage.getItem('token')
    // 在最后必须 return config
    return config
})
// 在 response 拦截器中,隐藏进度条 NProgress.done()
axios.interceptors.response.use(config => {
    NProgress.done()
    return config
})
Vue.prototype.$axios = axios

3.5 请求拦截

我们在发送请求前可以进行一个请求的拦截,为什么要拦截呢,我们拦截请求是用来做什么的呢?比如,有些请求是需要用户登录之后才能访问的,或者post请求的时候,我们需要序列化我们提交的数据。这时候,我们可以在请求被发送之前进行一个拦截,从而进行我们想要的操作。

axios.interceptors.request.use(config => {
    // console.log(config)
    NProgress.start()
    config.headers.Authorization = window.sessionStorage.getItem('token')
    // 在最后必须 return config
    return config
})

下面的还未整理

这里说一下token,一般是在登录完成之后,将用户的token通过localStorage或者cookie存在本地,然后用户每次在进入页面的时候(即在main.js中),会首先从本地存储中读取token,如果token存在说明用户已经登陆过,则更新vuex中的token状态。然后,在每次请求接口的时候,都会在请求的header中携带token,后台人员就可以根据你携带的token来判断你的登录是否过期,如果没有携带,则说明没有登录过。这时候或许有些小伙伴会有疑问了,就是每个请求都携带token,那么要是一个页面不需要用户登录就可以访问的怎么办呢?其实,你前端的请求可以携带token,但是后台可以选择不接收啊!

3.6 响应的拦截

没有验证过

// 响应拦截器
axios.interceptors.response.use(
    response => {
        // 如果返回的状态码为200,说明接口请求成功,可以正常拿到数据
        // 否则的话抛出错误
        if (response.status === 200) {
            return Promise.resolve(response)
        } else {
            return Promise.reject(response)
        }
    },
    // 服务器状态码不是2开头的的情况
    // 这里可以跟你们的后台开发人员协商好统一的错误状态码
    // 然后根据返回的状态码进行一些操作,例如登录过期提示,错误提示等等
    // 下面列举几个常见的操作,其他需求可自行扩展
    error => {
        if (error.response.status) {
            switch (error.response.status) {
                // 401: 未登录
                // 未登录则跳转登录页面,并携带当前页面的路径
                // 在登录成功后返回当前页面,这一步需要在登录页操作。
                case 401:
                    router.replace({
                        path: '/login',
                        query: {
                            redirect: router.currentRoute.fullPath
                        }
                    })
                    break

                // 403 token过期
                // 登录过期对用户进行提示
                // 清除本地token和清空vuex中token对象
                // 跳转登录页面
                case 403:
                    Message({
                        message: '登录过期,请重新登录',
                        type: 'error'
                    })
                    // 清除token
                    localStorage.removeItem('token')
                    store.commit('loginSuccess', null)
                    // 跳转登录页面,并将要浏览的页面fullPath传过去,登录成功后跳转需要访问的页面
                    setTimeout(() => {
                        router.replace({
                            path: '/login',
                            query: {
                                redirect: router.currentRoute.fullPath
                            }
                        })
                    }, 1000)
                    break

                // 404请求不存在
                case 404:
                    Message({
                        message: '网络请求不存在',
                        type: 'error'
                    })
                    break
                // 其他错误,直接抛出错误提示
                default:
                    Message({
                        message: error.response.data.message,
                        type: 'error'
                    })
            }
            return Promise.reject(error.response)
        }
    })

3.7 封装get方法和post方法

这里有个小细节说下,axios.get()方法和axios.post()在提交数据时参数的书写方式还是有区别的。区别就是,get的第二个参数是一个{},然后这个对象的params属性值是一个参数对象的。而post的第二个参数就是一个参数对象。两者略微的区别要留意哦!

1.get方法

get方法:我们通过定义一个get函数,get函数有两个参数,第一个参数表示我们要请求的url地址,第二个参数是我们要携带的请求参数。get函数返回一个promise对象,当axios其请求成功时resolve服务器返回 值,请求失败时reject错误值。最后通过export抛出get函数。

/**
 * get方法,对应get请求
 * @param {String} url [请求的url地址]
 * @param {Object} params [请求时携带的参数]
 */
export function get(url, params){    
    return new Promise((resolve, reject) =>{        
        axios.get(url, {            
            params: params        
        }).then(res => {
            resolve(res.data);
        }).catch(err =>{
            reject(err.data)        
    })    
});

2. post方法

post方法:原理同get基本一样,但是要注意的是,post方法必须要使用对提交从参数对象进行序列化的操作,所以这里我们通过node的qs模块来序列化我们的参数。这个很重要,如果没有序列化操作,后台是拿不到你提交的数据的。这就是文章开头我们import QS from 'qs';的原因

/** 
 * post方法,对应post请求 
 * @param {String} url [请求的url地址] 
 * @param {Object} params [请求时携带的参数] 
 */
export function post(url, params) {
    return new Promise((resolve, reject) => {
         axios.post(url, QS.stringify(params))
        .then(res => {
            resolve(res.data);
        })
        .catch(err =>{
            reject(err.data)
        })
    });
}

3.8 vue.config.js 设置代理解决跨域问题

module.exports = {
    publicPath: './',
    devServer: {
        host: '127.0.0.1',
        port: 8082,
        https: false,
        // 自动打开浏览器
        open: true,
        proxy: {
            '/SDT_IMOA': {
                target: 'http://127.0.0.1:8082/',
                changeOrigin: true,
                pathRewrite: {
                    '^/SDT_IMOA': ''
                }
            }
        }
    }
}

针对不同 的请求前缀,通过指定baseURL

 this.$axios({
                    baseURL: 'http://220.160.202.214:18080', // 自定义自己的 请求前缀 process.env.VUE_APP_CAMERA_API,
                    method: 'get',
                    url: '/api/play/start/' + item.cameracode + '/' + item.visiblelightchannel
                }).then(response => {
                    // console.error('======================', response.data)
                    if (response.data.code !== 0) {
                        this.$message.error(response.data.msg)
                    } else {
                        this.videoShow = true
                        // this.videoUrl = response.data.data.flv;
                        this.$refs.videoPlayer.play(response.data.data.flv)
                    }
                })

4 路由导航守卫

src/router/index.js 下 添加

// 挂载路由导航守卫
router.beforeEach((to, from, next) => {
  // to 将要访问的路径
  // from 代表从哪个路径跳转而来
  // next 是一个函数,表示放行
  //     next()  放行    next('/login')  强制跳转

  if (to.path === '/login') return next()
  // 获取token
  const tokenStr = window.sessionStorage.getItem('token')
  if (!tokenStr) return next('/login')
  next()
})

其他

对于其他组件二次开发,需要公用一个文件处理

场景描述:vfrom 需要对其二次开发,其中下拉选数据需要通过本地的json文件中获取,但是由于vfrom 编译后使用在自己的项目中,所以如果该json文件在vfrom 组件中,则后期维护json数据就不方便,需要的效果是2个项目都能有各自的json文件并且可以互通

将文件存放到public目录下(2个项目都要这个目录),例如 public\resource\commonInterfaceAPi.json

vfrom中 通过axios 进行获取

axios.get('/resource/commonInterfaceAPi.json').then(({data:res}) => {
    this.commonInterfaceOptions = res.data;
}).catch(error => {
    console.error(error);
});

vfrom编译后,在原项目中之间调用本项目下的 commonInterfaceAPi.json 文件,这样2个项目开发互不影响,各自维护自己的json文件即可

额外补充,解构解析

// 数据格式
{
  "selectApiData": {
    "data": [
      {
        "value": "IS_AQXY",
        "label": "是否签订安全协议",
      }
    ],
    "##": "用于加载常用api,针对radio,checkbox,select 给用户通过选择内置api进行数据加载,具体使用请参考:option-items-setting.vue"
  }
}

通过axios 获取数据

axios.get('/resource/commonInterfaceAPi.json').then(({data: { selectApiData:res } }) => {
    this.commonInterfaceOptions = res.data;
})
// 或者
axios.get('/resource/commonInterfaceAPi.json').then(({ data: { selectApiData: { data: res } } }) => { // 本质是想 selectApiData.data:res
  this.commonInterfaceOptions = res;
})