Vue项目中axios请求及API接口的封装

在实际的项目中,和后台的数据交互是少不了的,我通常使用的是 axios 库,所以以下示例也是以 axios 为基础来进行封装的。如果对 axios 不了解的,请看这里 axios 文档

1、安装

首先是 npm 安装 axios 很简单:npm install axios

2、没有封装存在的问题

如果在没有封装接口的项目中,在文件中随处可以看到如下的接口调用方法:

this.$axios.post("/user/add", {
    params: {
        name: this.name,
        age: this.age
    }
})
.then(res => {
    console.log(res)
})
.then(err => {
    console.log(res)
})

这样写不是不可以,但是存在一些缺陷,接口请求的 url 散布在各个文件中,如果需要在接口调用成功或失败时做一些处理,就需要更改每个文件。所以把这些接口请求统一集中起来,如果有调整,直接在集中文件中找到修改就好了,而不用再去查每个文件。

3、创建文件

首先在项目的 src 目录中,新建文件夹及文件目录结构如下:

├── src 源码目录
│ ├── apis 接口文件目录
│ │ ├── login.api.js 登录模块的接口 api
│ │ └── user.api.js 用户模块的接口 api
│ ├── services 请求相关文件目录
│ │ ├── address.js 请求地址配置文件
│ │ └── request.js axios封装,请求拦截、响应码处理等操作

api接口文件模块的划分,大可以根据自己的实际项目,按业务功能或业务逻辑或其他形式划分。

4、请求地址配置

一般我们的项目环境都会有多个,少的也会有开发环境和生产环境。正常情况下,在开发环境下和生产模式下有着不同的 baseURL,所以,我们需要根据不同的环境切换不同的 baseURL。

address.js 文件:

// 根据 process.env.NODE_ENV 切换不同的 baseURL
const isPro = process.env.NODE_ENV === 'production'
​
module.exports = {
    // 'apis':vue.config.js中proxy设置的代理
    baseURL: isPro ? 'http://192.168.100.120/ceds' : '/apis' 
}

5、axios 配置,设置请求头及响应码处理

大体思路是通过封装一个request类,其中包含了get、post等请求方法,这些请求方法又都会去调用 request 方法,该方法通过传入的不同参数调用原始的 axios 请求,然后返回一个 Promise。

request.js 文件:

import axios from 'axios'
import Qs from 'qs'
import Vue from 'vue'
import { getToken } from '@Utils/session.utils' // 存储获取token文件
import address from './address' // 请求地址
​
class Request {
    constructor () {
        // 创建axios实例
        this._axios = axios.create({
            baseURL: address.baseURL,
            timeout: 1000 * 5, // 请求超时时间
            headers: {}
        })
        // 请求拦截
        this._axios.interceptors.request.use(
            config => {
                const requestHeader = {
                    'X-Requested-With': 'XMLHttpRequest',
                    'Content-Type': 'application/json; charset=UTF-8',
                    'Access-Control-Allow-Origin': '*',
                    token: getToken() // 请求头统一添加token
                }
                config.headers = Object.assign(config.headers, requestHeader)
                return config
            },
            error => {
                Promise.reject(error)
            }
        )
    }
    
    // 根据请求方式,判断参数是放在query中还是body中。
    // 最直观的区别,比如GET请求把参数包含在url中,而POST则通过request body把参数放置在body体中,所以在提交时的参数形式是有区别的
    // 以下列了四种我一般常用请求方式的参数形式,大家可以自行调整
    /**
      * 发送get请求
      * @param {String} url地址
      * @param {Object} query 查询参数
      * @return json数据
      */
    get (url, query = {}) {
        return this._request('get')(url, {
            ...query
        })
    }
    /**
      * 发送post请求
      * @param {String} url地址
      * @param {Object} body 查询参数
      * @return json数据
      */
    post(url, body = {}, headers) {
        let data;
        if(this.isFormData(body)) {
            data = body
        } else if(Array.isArray(body)) {
            data = body
        } else {
            data = { ...body }
        }
        return this._request('post')(url, headers)(url, data);
    }
    put (url, body = {}) {
        return this._request('put')(url, {
            ...body
        });
    }
    delete(url, body = {}) {
        return this._request('delete')(url, {
            ...body
        });
    }
​
    isFormData = v => {
        return Object.prototype.toString.call(v) === '[object FormData]'
    }
​
​
    /**
      * 设置请求头
      * @param {Object} header 请求头
      */
    setHeaders (header) {
        Object.keys(header).forEach(key => {
            this._axios.defaults.headers[key] = header[key]
        })
    }
​
    // 处理请求头 headers
    handleHeaders () {
        const headers = {}
        headers['XMIME-TYPE'] = '3'
        Headers['Content-Type'] = 'application/json; charset=UTF-8'
        return headers
    }
​
    /**
      * 发送请求
      * @param {String} method 请求方法类型
      * @param headers
      * @returns {function(*=, *=):Promise<unknown>}
      * @private
      */
    _request (method, headers) {
        this.setHeaders(this.handleHeaders()) // 设置统一的请求头
        if (headers) {
            this.setHeaders(headers) // 自定义请求头
        }
        
        return (url, data, timeout) => {
            const config = {
                url,
                method,
                timeout: timeout || this._axios.defaults.timeout
            } // 构造请求 config
​
            // 判断请求类型 get post
            const paramType = ['get', 'delete'].indexOf(method) !== -1 ? 'params' : 'data'
            config[paramType] = data
            //参数序列化
            config.paramsSerializer = params => {
                return Qs.stringify(params, { arrayFormat: 'repeat' });
            }
            
            return new Promise((resolve, reject) => {
                // 发送真正的请求,验证权限,检查404等status
                this._axios
                    .request(config)
                    .then(response => {
                        if (this.handleSuccessStatus(response.data.code, response.data)) {
                            if (response.headers['content-type'] !== 'text/plain; charset=urf-8') {
                            resolve(
                                    // 对响应结果二次包装
                                    Object.assign(
                                      {
                                           success: Number(response.data.code) === 200,
                                            data: response.data.data,
                                            msg: response.data.msg
                                        },
                                       response.data
                                    )
                                ) // 处理返回结果
                            } else {
                                resolve(response.data)
                            }
                        } 
                    }, response => {
                        // 处理错误码
                      if(response.response) {
                            const statusCode = response.response.status
                            this.handleErrorStatus(statusCode)
                        } else {
                           Vue.prototype.$message.error(response.message)
                        }
                        reject(response)
                    })
                    .catch(err => {
                        reject(err)
                    })
                })
            }
        }
    }
​
    // 请求成功,返回错误码
    // 具体状态码跟后台开发人员统一,然后根据状态码进行相应提示
    // 下面是我在项目中的操作,大家可自行调整扩展
    handleSuccessStatus (code, data) {
        let result = ''
        let flag = false
        switch (code) {
            case '20007':
                result = '未查找到二次认证密码!'
                flag = true
                break
            case '20008':
                result = '您的二次认证密码还未修改,请先修改!'
                flag = true
                break
            case '20009':
                result = '您还未开启二次认证,请联系管理员!'
                flag = true
                break
            case '90001':
                result = '请输入二次认证密码!'
                flag = true
                break
            case '90002':
                result = '无操作权限!'
                flag = true
                break
            default:
                break
        }
​
        // 进行通知
        // $message方法是我按需引入的element-ui中的提示组件,你可以替换成自己的提示组件
        if (result) {
            Vue.prototype.$message.error(result)
        }
        return flag
    }
    // 根据错误码获取错误提示
    handleErrorStatus (statusCode) {
        let errorMsg = ''
        if (statusCode === 500) {
            errorMsg = '数据请求失败,请联系管理员!'
        } else if (statusCode === 404) {
            errorMsg = '请求地址错误!'
        } else if (statusCode === 402) {
            errorMsg = '当前您没有权限操作该数据!'
        } else {
            errorMsg = '请求出错!'
        }
        // 进行通知
        Vue.prototype.$message.error(errorMsg)
    }
}
​
export default new Request() 

6、使用

我们在接口管理文件中,通过调用上面封装的 request 类,传入对应的参数即可。

user.api.js 文件:

import http from '../services/request'
​
/**
 * @description 获取用户列表
 * @param {*} params 请求接口的参数
 */
// 此处定义的reqUserList方法会调用我们封装的request中的get方法,get方法的第一个参数是请求地址,第二参数是query参数
export const reqUserList = params => http.get('/user/list', params) 

在调用的 .vue 文件中,引入该方法并传入参数即可

import { reqUserList } from '@Apis/user.api' // 导入api
​
export default {
    name: 'UserList',
    ... ...
    created() {
    
    },
    methods: {
        async getUsers() {
            // 调用api接口,并传入参数
            const res = await reqUserList({
                page: 1,
                size: 10
            })
            console.log(res) // 获取的响应结果
        }
    }
}

如此,就完成了对接口的封装及基本使用。

PS:以上这些文件名、文件夹名、方法名、路径等都是我自己取得,你可以按照自己的代码风格习惯进行调整。

以上,是我在项目中一些写法,有的地方可能不完善,如有问题欢迎大家指正,感谢 :)