1.axios的基本使用

1.1安装

  1. $ npm install axios

1.2请求示例

  1. // POST
  2. axios.post('/user', {
  3. firstName: 'Fred',
  4. lastName: 'Flintstone'
  5. })
  6. .then(function (response) {
  7. console.log(response);
  8. })
  9. .catch(function (error) {
  10. console.log(error);
  11. });
  1. // GET
  2. axios.get('/user', {
  3. params: {
  4. ID: 12345
  5. }
  6. })
  7. .then(function (response) {
  8. console.log(response);
  9. })
  10. .catch(function (error) {
  11. console.log(error);
  12. });
  1. // 执行多个并发
  2. axios.all([get1(), get2()])
  3. .then(axios.spread(function (acct, perms) {
  4. // 两个请求现在都执行完成
  5. }));
  • 可以通过向 axios 传递相关配置来创建请求

    语法: axios(config)

  1. axios({
  2. method: 'post',
  3. url: '/user/12345',
  4. data: {
  5. firstName: 'Fred',
  6. lastName: 'Flintstone'
  7. }
  8. });

1.3content-type

  • 默认情况下, 不写content-type, 是以json的方式来传递, (Content-Type: application/json;charset=UTF-8)

    1. axios({
    2. url:'/api/connect/token',
    3. method: 'post',
    4. data: {
    5. firstName: 'Fred',
    6. lastName: 'Flintstone'
    7. }
    8. }).then(res => {
    9. console.log(1234, res.data)
    10. }).catch(error => {
    11. console.log(error)
    12. })

    Headers如下:

    1. Request Payload
    2. { firstName: "Fred", lastName: "Flintstone"}
  • content-type改成x-www-form-urlencoded, 即表单提交方式

    1. axios({
    2. url:'/api/connect/token',
    3. method: 'post',
    4. data: {
    5. firstName: 'Fred',
    6. lastName: 'Flintstone'
    7. },
    8. headers: {
    9. 'Content-type': 'application/x-www-form-urlencoded'
    10. }
    11. }).then(res => {
    12. console.log(1234, res.data)
    13. }).catch(error => {
    14. console.log(error)
    15. })

    Headers如下:

    1. Form Data
    2. {"firstName":"Fred","lastName":"Flintstone"}:
  • 另一种情况, 序列化成字符串形式传递

    1. axios({
    2. url:'/api/connect/token',
    3. method: 'post',
    4. data: JSON.stringify({
    5. firstName: 'Fred',
    6. lastName: 'Flintstone'
    7. })
    8. }).then(res => {
    9. console.log(1234, res.data)
    10. }).catch(error => {
    11. console.log(error)
    12. })

    结果跟上面一致:

    1. Form Data
    2. {"firstName":"Fred","lastName":"Flintstone"}:
  • 还有一种常见情况, 通过qs库对数据进行编码(前提要安装qs)

    1. import qs from 'qs'
    2. axios({
    3. url:'/api/connect/token',
    4. method: 'post',
    5. data: qs.stringify({
    6. firstName: 'Fred',
    7. lastName: 'Flintstone'
    8. })
    9. }).then(res => {
    10. console.log(1234, res.data)
    11. }).catch(error => {
    12. console.log(error)
    13. })

    结果:

    1. Request Headers
    2. Content-Type: application/x-www-form-urlencoded
    1. Form Data
    2. firstName: Fred
    3. lastName: Flintstone

    2.qs的基本使用

    2.1 为什么要用qs

    ajax请求的get请求是通过URL传参的(以?和&符连接),而post大多是通过json传参的。qs是一个库。里面的stringify方法可以将一个json对象直接转为(以?和&符连接的形式)。在开发中,发送请求的入参大多是一个对象。在发送时,如果该请求为get请求,就需要对参数进行转化。使用该库,就可以自动转化,而不需要手动去拼接。
    axios默认的content-type是application/json
    也就是java后端经常让你把参数放在body中的那种格式
    传输的样式是
    requestbody

    1. {
    2. name:xxx,
    3. age:xxx
    4. }

    如果使用的qs进行序列化
    那么content-type就是application/x-www-form-urlencoded
    也就是常说的表单提交
    传输的样式是
    formdata

    1. name:xxx,
    2. age:xxx

    urlencoding后是

    1. name=xxx&age=xxx

    所以,实际上是否需要用qs去序列化参数完全取决于后端要怎么接受数据

    2.2 和json序列化的区别

    qs.stringify() 将对象 序列化成URL的形式,以&进行拼接。
    JSON是正常类型的JSON,请对比一下输出;

    1. var a = {name:'hehe',age:10};
    2. qs.stringify(a)
    3. // 'name=hehe&age=10'
    4. JSON.stringify(a)
    5. // '{"name":"hehe","age":10}'

2.3 qs使用

步骤:
1、首先先下载:

  1. npm i qs

2、然后引入 :

  1. import qs from 'qs'

3、qs主要有两个方法 :
方法一:将对象序列化,多个对象之间用&拼接(拼接是由底层处理,无需手动操作)

  1. qs.stringify() 转换成查询字符串
  2. let comments = {content: this.inputValue}
  3. let comValue = qs.stringify(comments)

方法二:将序列化的内容拆分成一个个单一的对象

  1. qs.parse() 转换成json对象
  2. let comValue = qs.parse(comments)

3.二次封装

3.1. 设置请求拦截和处理的逻辑

  • 新建 axiosTool.js 文件, ```javascript import Vue from ‘vue’ import axios from ‘axios’ //取消请求 let CancelToken = axios.CancelToken //设置默认请求头,如果不需要可以取消这一步 axios.defaults.headers = { ‘X-Requested-With’: ‘XMLHttpRequest’ } // 请求超时的时间限制 axios.defaults.timeout = 20000 // 开始设置请求 发起的拦截处理 // config 代表发起请求的参数的实体 axios.interceptors.request.use(config => { // 得到参数中的 requestName 字段,用于决定下次发起请求,取消对应的 相同字段的请求 // 如果没有 requestName 就默认添加一个 不同的时间戳 let requestName if(config.method === ‘post’){
    1. if(config.data && config.data.requestName){
    2. requestName = config.data.requestName
    3. }else{
    4. requestName = new Date().getTime()
    5. }
    }else{
    1. if(config.params && config.params.requestName){
    2. requestName = config.params.requestName
    3. }else{
    4. requestName = new Date().getTime()
    5. }
    } // 判断,如果这里拿到的参数中的 requestName 在上一次请求中已经存在,就取消上一次的请求 if (requestName) {
    1. if (axios[requestName] && axios[requestName].cancel) {
    2. axios[requestName].cancel()
    3. }
    4. config.cancelToken = new CancelToken(c => {
    5. axios[requestName] = {}
    6. axios[requestName].cancel = c
    7. })
    } return config }, error => { return Promise.reject(error) })

// 请求到结果的拦截处理 axios.interceptors.response.use(config => { // 返回请求正确的结果 return config }, error => { // 错误的请求结果处理,这里的代码根据后台的状态码来决定错误的输出信息 if (error && error.response) { switch (error.response.status) { case 400: error.message = ‘错误请求’ break case 401: error.message = ‘未授权,请重新登录’ break case 403: error.message = ‘拒绝访问’ break case 404: error.message = ‘请求错误,未找到该资源’ break case 405: error.message = ‘请求方法未允许’ break case 408: error.message = ‘请求超时’ break case 500: error.message = ‘服务器端出错’ break case 501: error.message = ‘网络未实现’ break case 502: error.message = ‘网络错误’ break case 503: error.message = ‘服务不可用’ break case 504: error.message = ‘网络超时’ break case 505: error.message = ‘http版本不支持该请求’ break default: error.message = 连接错误${error.response.status} } } else { error.message = “连接到服务器失败” } return Promise.reject(error.message) }) // 将axios 的 post 方法,绑定到 vue 实例上面的 $post Vue.prototype.$post = function (url, params) { return new Promise((resolve, reject) => { axios.post(url, params) .then(res => { resolve(res) }).catch(err => { reject(err) }) }) } // 将axios 的 get 方法,绑定到 vue 实例上面的 $get Vue.prototype.$get = function (url, params) { return new Promise((resolve, reject) => { axios.get(url, { params: params }).then(res => { resolve(res) // 返回请求成功的数据 data }).catch(err => { reject(err) }) }) } // 请求示例 // requestName 为多余的参数 作为请求的标识,下次发起相同的请求,就会自动取消上一次还没有结束的请求 // 比如正常的请求参数只有一个 name: ‘123’,但是这里我们需要额外再加上一个 requestName /* this.$post(url, { name: ‘123’, requestName: ‘post_1’ }) .then(res => { console.log(请求成功:${res}) }) /

export default axios

  1. <a name="vLICG"></a>
  2. ### 3.2. 引入该文件
  3. - 在main.js中引入<br />
  4. ```javascript
  5. import { axios } from './static/js/axiosTool'
  1. [
  2. ]()
  3. [
  4. ]()

3.3. 在组件中直接使用即可

  1. 1. this.$post(this.url2, {
  2. 2. name: "王",
  3. 3. docType: "pson",
  4. 4. requestName: 'name02'
  5. 5. }).then(res => {
  6. 6. console.log(res)
  7. 7. })
  8. 8.
  9. 9. this.$get(this.url, {
  10. 10. name: "李",
  11. 11. requestName: 'name01'
  12. 12. }).then(res => {
  13. 13. console.log(res)
  14. 14. })

3.3.4. 效果

3.axios的使用 - 图1

这里我们连续点击5次请求按钮,因为请求时间比较长,所以前4次会自动取消,只会响应最后一次请求的结果

4.二次封装二

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

安装

  1. npm install axios; // 安装axios

引入

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

  1. // 在http.js中引入axios
  2. import axios from 'axios'; // 引入axios
  3. import QS from 'qs'; // 引入qs模块,用来序列化post类型的数据,后面会提到
  4. // vant的toast提示框组件,大家可根据自己的ui组件更改。
  5. import { Toast } from 'vant';

环境的切换

我们的项目环境可能有开发环境、测试环境和生产环境。我们通过node的环境变量来匹配我们的默认的接口url前缀。axios.defaults.baseURL可以设置axios的默认请求地址就不多说了。

  1. // 环境的切换
  2. if (process.env.NODE_ENV == 'development') {
  3. axios.defaults.baseURL = 'https://www.baidu.com';}
  4. else if (process.env.NODE_ENV == 'debug') {
  5. axios.defaults.baseURL = 'https://www.ceshi.com';
  6. }
  7. else if (process.env.NODE_ENV == 'production') {
  8. axios.defaults.baseURL = 'https://www.production.com';
  9. }

设置请求超时

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

  1. axios.defaults.timeout = 10000;

post请求头的设置

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

  1. axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
  • 请求拦截

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

请求拦截

  1. // 先导入vuex,因为我们要使用到里面的状态对象
  2. // vuex的路径根据自己的路径去写
  3. import store from '@/store/index';
  4. // 请求拦截器axios.interceptors.request.use(
  5. config => {
  6. // 每次发送请求之前判断vuex中是否存在token
  7. // 如果存在,则统一在http请求的header都加上token,这样后台根据token判断你的登录情况
  8. // 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断
  9. const token = store.state.token;
  10. token && (config.headers.Authorization = token);
  11. return config;
  12. },
  13. error => {
  14. return Promise.error(error);
  15. })

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

响应的拦截

  1. // 响应拦截器
  2. axios.interceptors.response.use(
  3. response => {
  4. // 如果返回的状态码为200,说明接口请求成功,可以正常拿到数据
  5. // 否则的话抛出错误
  6. if (response.status === 200) {
  7. return Promise.resolve(response);
  8. } else {
  9. return Promise.reject(response);
  10. }
  11. },
  12. // 服务器状态码不是2开头的的情况
  13. // 这里可以跟你们的后台开发人员协商好统一的错误状态码
  14. // 然后根据返回的状态码进行一些操作,例如登录过期提示,错误提示等等
  15. // 下面列举几个常见的操作,其他需求可自行扩展
  16. error => {
  17. if (error.response.status) {
  18. switch (error.response.status) {
  19. // 401: 未登录
  20. // 未登录则跳转登录页面,并携带当前页面的路径
  21. // 在登录成功后返回当前页面,这一步需要在登录页操作。
  22. case 401:
  23. router.replace({
  24. path: '/login',
  25. query: {
  26. redirect: router.currentRoute.fullPath
  27. }
  28. });
  29. break;
  30. // 403 token过期
  31. // 登录过期对用户进行提示
  32. // 清除本地token和清空vuex中token对象
  33. // 跳转登录页面
  34. case 403:
  35. Toast({
  36. message: '登录过期,请重新登录',
  37. duration: 1000,
  38. forbidClick: true
  39. });
  40. // 清除token
  41. localStorage.removeItem('token');
  42. store.commit('loginSuccess', null);
  43. // 跳转登录页面,并将要浏览的页面fullPath传过去,登录成功后跳转需要访问的页面
  44. setTimeout(() => {
  45. router.replace({
  46. path: '/login',
  47. query: {
  48. redirect: router.currentRoute.fullPath
  49. }
  50. });
  51. }, 1000);
  52. break;
  53. // 404请求不存在
  54. case 404:
  55. Toast({
  56. message: '网络请求不存在',
  57. duration: 1500,
  58. forbidClick: true
  59. });
  60. break;
  61. // 其他错误,直接抛出错误提示
  62. default:
  63. Toast({
  64. message: error.response.data.message,
  65. duration: 1500,
  66. forbidClick: true
  67. });
  68. }
  69. return Promise.reject(error.response);
  70. }
  71. }
  72. });

响应拦截器很好理解,就是服务器返回给我们的数据,我们在拿到之前可以对他进行一些处理。例如上面的思想:如果后台返回的状态码是200,则正常返回数据,否则的根据错误的状态码类型进行一些我们需要的错误,其实这里主要就是进行了错误的统一处理和没登录或登录过期后调整登录页的一个操作。
要注意的是,上面的Toast()方法,是我引入的vant库中的toast轻提示组件,你根据你的ui库,对应使用你的一个提示组件。

封装get方法和post方法

我们常用的ajax请求方法有get、post、put等方法,相信小伙伴都不会陌生。axios对应的也有很多类似的方法,不清楚的可以看下文档。但是为了简化我们的代码,我们还是要对其进行一个简单的封装。下面我们主要封装两个方法:get和post。
get方法:我们通过定义一个get函数,get函数有两个参数,第一个参数表示我们要请求的url地址,第二个参数是我们要携带的请求参数。get函数返回一个promise对象,当axios其请求成功时resolve服务器返回 值,请求失败时reject错误值。最后通过export抛出get函数。

  1. /**
  2. * get方法,对应get请求
  3. * @param {String} url [请求的url地址]
  4. * @param {Object} params [请求时携带的参数]
  5. */
  6. export function get(url, params){
  7. return new Promise((resolve, reject) =>{
  8. axios.get(url, {
  9. params: params
  10. }).then(res => {
  11. resolve(res.data);
  12. }).catch(err =>{
  13. reject(err.data)
  14. })
  15. });}

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

  1. /**
  2. * post方法,对应post请求
  3. * @param {String} url [请求的url地址]
  4. * @param {Object} params [请求时携带的参数]
  5. */
  6. export function post(url, params) {
  7. return new Promise((resolve, reject) => {
  8. axios.post(url, QS.stringify(params))
  9. .then(res => {
  10. resolve(res.data);
  11. })
  12. .catch(err =>{
  13. reject(err.data)
  14. })
  15. });
  16. }

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

axios的封装基本就完成了,下面再简单说下api的统一管理。

整齐的api就像电路板一样,即使再复杂也能很清晰整个线路。上面说了,我们会新建一个api.js,然后在这个文件中存放我们所有的api接口。

  • 首先我们在api.js中引入我们封装的get和post方法
    1. /**
    2. * api接口统一管理
    3. */
    4. import { get, post } from './http'
    现在,例如我们有这样一个接口,是一个post请求:
    1. http://www.baiodu.com/api/v1/users/my_address/address_edit_before
    我们可以在api.js中这样封装:
    1. export const apiAddress = p => post('api/v1/users/my_address/address_edit_before', p);
    我们定义了一个apiAddress方法,这个方法有一个参数p,p是我们请求接口时携带的参数对象。而后调用了我们封装的post方法,post方法的第一个参数是我们的接口地址,第二个参数是apiAddress的p参数,即请求接口时携带的参数对象。最后通过export导出apiAddress。
    然后在我们的页面中可以这样调用我们的api接口:
    1. import { apiAddress } from '@/request/api';// 导入我们的api接口
    2. export default {
    3. name: 'Address',
    4. created () {
    5. this.onLoad();
    6. },
    7. methods: {
    8. // 获取数据
    9. onLoad() {
    10. // 调用api接口,并且提供了两个参数
    11. apiAddress({
    12. type: 0,
    13. sort: 1
    14. }).then(res => {
    15. // 获取数据成功后的其他操作
    16. ………………
    17. })
    18. }
    19. }
    20. }
    其他的api接口,就在pai.js中继续往下面扩展就可以了。友情提示,为每个接口写好注释哦!!!
    api接口管理的一个好处就是,我们把api统一集中起来,如果后期需要修改接口,我们就直接在api.js中找到对应的修改就好了,而不用去每一个页面查找我们的接口然后再修改会很麻烦。关键是,万一修改的量比较大,就规格gg了。还有就是如果直接在我们的业务代码修改接口,一不小心还容易动到我们的业务代码造成不必要的麻烦。
    好了,最后把完成的axios封装代码奉上。
    1. /**axios封装
    2. * 请求拦截、相应拦截、错误统一处理
    3. */
    4. import axios from 'axios';import QS from 'qs';
    5. import { Toast } from 'vant';
    6. import store from '../store/index'
    7. // 环境的切换
    8. if (process.env.NODE_ENV == 'development') {
    9. axios.defaults.baseURL = '/api';
    10. } else if (process.env.NODE_ENV == 'debug') {
    11. axios.defaults.baseURL = '';
    12. } else if (process.env.NODE_ENV == 'production') {
    13. axios.defaults.baseURL = 'http://api.123dailu.com/';
    14. }
    15. // 请求超时时间
    16. axios.defaults.timeout = 10000;
    17. // post请求头
    18. axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
    19. // 请求拦截器
    20. axios.interceptors.request.use(
    21. config => {
    22. // 每次发送请求之前判断是否存在token,如果存在,则统一在http请求的header都加上token,不用每次请求都手动添加了
    23. // 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断
    24. const token = store.state.token;
    25. token && (config.headers.Authorization = token);
    26. return config;
    27. },
    28. error => {
    29. return Promise.error(error);
    30. })
    31. // 响应拦截器
    32. axios.interceptors.response.use(
    33. response => {
    34. if (response.status === 200) {
    35. return Promise.resolve(response);
    36. } else {
    37. return Promise.reject(response);
    38. }
    39. },
    40. // 服务器状态码不是200的情况
    41. error => {
    42. if (error.response.status) {
    43. switch (error.response.status) {
    44. // 401: 未登录
    45. // 未登录则跳转登录页面,并携带当前页面的路径
    46. // 在登录成功后返回当前页面,这一步需要在登录页操作。
    47. case 401:
    48. router.replace({
    49. path: '/login',
    50. query: { redirect: router.currentRoute.fullPath }
    51. });
    52. break;
    53. // 403 token过期
    54. // 登录过期对用户进行提示
    55. // 清除本地token和清空vuex中token对象
    56. // 跳转登录页面
    57. case 403:
    58. Toast({
    59. message: '登录过期,请重新登录',
    60. duration: 1000,
    61. forbidClick: true
    62. });
    63. // 清除token
    64. localStorage.removeItem('token');
    65. store.commit('loginSuccess', null);
    66. // 跳转登录页面,并将要浏览的页面fullPath传过去,登录成功后跳转需要访问的页面
    67. setTimeout(() => {
    68. router.replace({
    69. path: '/login',
    70. query: {
    71. redirect: router.currentRoute.fullPath
    72. }
    73. });
    74. }, 1000);
    75. break;
    76. // 404请求不存在
    77. case 404:
    78. Toast({
    79. message: '网络请求不存在',
    80. duration: 1500,
    81. forbidClick: true
    82. });
    83. break;
    84. // 其他错误,直接抛出错误提示
    85. default:
    86. Toast({
    87. message: error.response.data.message,
    88. duration: 1500,
    89. forbidClick: true
    90. });
    91. }
    92. return Promise.reject(error.response);
    93. }
    94. }
    95. );
    96. /**
    97. * get方法,对应get请求
    98. * @param {String} url [请求的url地址]
    99. * @param {Object} params [请求时携带的参数]
    100. */
    101. export function get(url, params){
    102. return new Promise((resolve, reject) =>{
    103. axios.get(url, {
    104. params: params
    105. })
    106. .then(res => {
    107. resolve(res.data);
    108. })
    109. .catch(err => {
    110. reject(err.data)
    111. })
    112. });
    113. }
    114. /**
    115. * post方法,对应post请求
    116. * @param {String} url [请求的url地址]
    117. * @param {Object} params [请求时携带的参数]
    118. */
    119. export function post(url, params) {
    120. return new Promise((resolve, reject) => {
    121. axios.post(url, QS.stringify(params))
    122. .then(res => {
    123. resolve(res.data);
    124. })
    125. .catch(err => {
    126. reject(err.data)
    127. })
    128. });
    129. }

    api

    axios的封装根据需求的不同而不同。这里非常感谢评论里一些很中肯的建议,我也对此进行了思考和针对不同需求的改善。主要有以下改变:
    1.优化axios封装,去掉之前的get和post
    2.断网情况处理
    3.更加模块化的api管理
    4.接口域名有多个的情况
    5.api挂载到vue.prototype上省去引入的步骤
    http.js中axios封装的优化,先直接贴代码:
    1. /**
    2. * axios封装
    3. * 请求拦截、响应拦截、错误统一处理
    4. */
    5. import axios from 'axios';
    6. import router from '../router';
    7. import store from '../store/index';
    8. import { Toast } from 'vant';
    9. /**
    10. * 提示函数
    11. * 禁止点击蒙层、显示一秒后关闭
    12. */
    13. const tip = msg => {
    14. Toast({
    15. message: msg,
    16. duration: 1000,
    17. forbidClick: true
    18. });
    19. }
    20. /**
    21. * 跳转登录页
    22. * 携带当前页面路由,以期在登录页面完成登录后返回当前页面
    23. */
    24. const toLogin = () => {
    25. router.replace({
    26. path: '/login',
    27. query: {
    28. redirect: router.currentRoute.fullPath
    29. }
    30. });
    31. }
    32. /**
    33. * 请求失败后的错误统一处理
    34. * @param {Number} status 请求失败的状态码
    35. */
    36. const errorHandle = (status, other) => {
    37. // 状态码判断
    38. switch (status) {
    39. // 401: 未登录状态,跳转登录页
    40. case 401:
    41. toLogin();
    42. break;
    43. // 403 token过期
    44. // 清除token并跳转登录页
    45. case 403:
    46. tip('登录过期,请重新登录');
    47. localStorage.removeItem('token');
    48. store.commit('loginSuccess', null);
    49. setTimeout(() => {
    50. toLogin();
    51. }, 1000);
    52. break;
    53. // 404请求不存在
    54. case 404:
    55. tip('请求的资源不存在');
    56. break;
    57. default:
    58. console.log(other);
    59. }}
    60. // 创建axios实例
    61. var instance = axios.create({ timeout: 1000 * 12});
    62. // 设置post请求头
    63. instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
    64. /**
    65. * 请求拦截器
    66. * 每次请求前,如果存在token则在请求头中携带token
    67. */
    68. instance.interceptors.request.use(
    69. config => {
    70. // 登录流程控制中,根据本地是否存在token判断用户的登录情况
    71. // 但是即使token存在,也有可能token是过期的,所以在每次的请求头中携带token
    72. // 后台根据携带的token判断用户的登录情况,并返回给我们对应的状态码
    73. // 而后我们可以在响应拦截器中,根据状态码进行一些统一的操作。
    74. const token = store.state.token;
    75. token && (config.headers.Authorization = token);
    76. return config;
    77. },
    78. error => Promise.error(error))
    79. // 响应拦截器
    80. instance.interceptors.response.use(
    81. // 请求成功
    82. res => res.status === 200 ? Promise.resolve(res) : Promise.reject(res),
    83. // 请求失败
    84. error => {
    85. const { response } = error;
    86. if (response) {
    87. // 请求已发出,但是不在2xx的范围
    88. errorHandle(response.status, response.data.message);
    89. return Promise.reject(response);
    90. } else {
    91. // 处理断网的情况
    92. // eg:请求超时或断网时,更新state的network状态
    93. // network状态在app.vue中控制着一个全局的断网提示组件的显示隐藏
    94. // 关于断网组件中的刷新重新获取数据,会在断网组件中说明
    95. if (!window.navigator.onLine) {
    96. store.commit('changeNetwork', false);
    97. } else {
    98. return Promise.reject(error);
    99. }
    100. }
    101. });
    102. export default instance;

    这个axios和之前的大同小异,做了如下几点改变:

    1.去掉了之前get和post方法的封装,通过创建一个axios实例然后export default方法导出,这样使用起来更灵活一些。
    2.去掉了通过环境变量控制baseUrl的值。考虑到接口会有多个不同域名的情况,所以准备通过js变量来控制接口域名。这点具体在api里会介绍。
    3.增加了请求超时,即断网状态的处理。说下思路,当断网时,通过更新vuex中network的状态来控制断网提示组件的显示隐藏。断网提示一般会有重新加载数据的操作,这步会在后面对应的地方介绍。
    4.公用函数进行抽出,简化代码,尽量保证单一职责原则。

    下面说下api这块,考虑到一下需求:

    1.更加模块化
    2.更方便多人开发,有效减少解决命名冲突
    3.处理接口域名有多个情况
    这里这里呢新建了一个api文件夹,里面有一个index.js和一个base.js,以及多个根据模块划分的接口js文件。index.js是一个api的出口,base.js管理接口域名,其他js则用来管理各个模块的接口。
    先放index.js代码:
    1. /**
    2. * api接口的统一出口
    3. */
    4. // 文章模块接口
    5. import article from '@/api/article';
    6. // 其他模块的接口……
    7. // 导出接口
    8. export default {
    9. article,
    10. // ……
    11. }
    index.js是一个api接口的出口,这样就可以把api接口根据功能划分为多个模块,利于多人协作开发,比如一个人只负责一个模块的开发等,还能方便每个模块中接口的命名哦。
    base.js:
    1. /**
    2. * 接口域名的管理
    3. */
    4. const base = {
    5. sq: 'https://xxxx111111.com/api/v1',
    6. bd: 'http://xxxxx22222.com/api'
    7. }
    8. export default base;
    通过base.js来管理我们的接口域名,不管有多少个都可以通过这里进行接口的定义。即使修改起来,也是很方便的。
    最后就是接口模块的说明,例如上面的article.js:
    1. /**
    2. * article模块接口列表
    3. */
    4. import base from './base'; // 导入接口域名列表
    5. import axios from '@/utils/http'; // 导入http中创建的axios实例
    6. import qs from 'qs'; // 根据需求是否导入qs模块
    7. const article = {
    8. // 新闻列表
    9. articleList () {
    10. return axios.get(`${base.sq}/topics`);
    11. },
    12. // 新闻详情,演示
    13. articleDetail (id, params) {
    14. return axios.get(`${base.sq}/topic/${id}`, {
    15. params: params
    16. });
    17. },
    18. // post提交
    19. login (params) {
    20. return axios.post(`${base.sq}/accesstoken`, qs.stringify(params));
    21. }
    22. // 其他接口…………
    23. }
    24. export default article;
    1.通过直接引入我们封装好的axios实例,然后定义接口、调用axios实例并返回,可以更灵活的使用axios,比如你可以对post请求时提交的数据进行一个qs序列化的处理等。
    2.请求的配置更灵活,你可以针对某个需求进行一个不同的配置。关于配置的优先级,axios文档说的很清楚,这个顺序是:在 lib/defaults.js 找到的库的默认值,然后是实例的 defaults 属性,最后是请求的 config 参数。后者将优先于前者。
    3.restful风格的接口,也可以通过这种方式灵活的设置api接口地址。
    最后,为了方便api的调用,我们需要将其挂载到vue的原型上。在main.js中:
    1. import Vue from 'vue'
    2. import App from './App'
    3. import router from './router' // 导入路由文件
    4. import store from './store' // 导入vuex文件
    5. import api from './api' // 导入api接口
    6. Vue.prototype.$api = api; // 将api挂载到vue的原型上
    然后我们可以在页面中这样调用接口,eg:
    1. methods: {
    2. onLoad(id) {
    3. this.$api.article.articleDetail(id, {
    4. api: 123
    5. }).then(res=> {
    6. // 执行某些操作
    7. })
    8. }
    9. }
    再提一下断网的处理,这里只做一个简单的示例:
    1. <template>
    2. <div id="app">
    3. <div v-if="!network">
    4. <h3>我没网了</h3>
    5. <div @click="onRefresh">刷新</div>
    6. </div>
    7. <router-view/>
    8. </div>
    9. </template>
    10. <script>
    11. import { mapState } from 'vuex';
    12. export default {
    13. name: 'App',
    14. computed: {
    15. ...mapState(['network'])
    16. },
    17. methods: {
    18. // 通过跳转一个空页面再返回的方式来实现刷新当前页面数据的目的
    19. onRefresh () {
    20. this.$router.replace('/refresh')
    21. }
    22. }
    23. }
    24. </script>
    这是app.vue,这里简单演示一下断网。在http.js中介绍了,我们会在断网的时候,来更新vue中network的状态,那么这里我们根据network的状态来判断是否需要加载这个断网组件。断网情况下,加载断网组件,不加载对应页面的组件。当点击刷新的时候,我们通过跳转refesh页面然后立即返回的方式来实现重新获取数据的操作。因此我们需要新建一个refresh.vue页面,并在其beforeRouteEnter钩子中再返回当前页面。
    1. // refresh.vue
    2. beforeRouteEnter (to, from, next) {
    3. next(vm => {
    4. vm.$router.replace(from.fullPath)
    5. })
    6. }
    这是一种全局通用的断网提示,当然了,也可以根据自己的项目需求操作。具体操作就仁者见仁智者见智了。
    如果更多的需求,或者说是不一样的需求,可以根据自己的需求进行一个改进。