axios封装及api调用方法

记录项目中axios的封装及api调用方法

入口

@/index.js:

1
2
3
4
5
6
7
8
9
10
import Vue from 'vue';
// 导入插件
import plugin from '@/plugins';
Vue.use(plugin);
window.app = new Vue({
el: '#app',
// store,
// router,
render: h => h(App)
});

axios封装

@/plugins/axios.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import axios from 'axios';

import { AXIOS_DEFAULT_CONFIG } from '@/config'; // axios 默认配置
import { requestSuccessFunc, requestFailFunc, responseSuccessFunc, responseFailFunc } from '@/config/interceptor/axios'; // 各个拦截器

let axiosInstance = {};
axiosInstance = axios.create(AXIOS_DEFAULT_CONFIG); // axios实例

// 注入请求拦截
axiosInstance.interceptors.request.use(requestSuccessFunc, requestFailFunc);

// 注入返回拦截
axiosInstance.interceptors.response.use(responseSuccessFunc, responseFailFunc);

export default axiosInstance;

拦截器

@/config/interceptor/axios.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
import axios from 'axios';
import { getToken } from '@/utils/auth';
import store from '@/store';
import { CONSOLE_REQUEST_ENABLE, CONSOLE_RESPONSE_ENABLE } from '../index'; // 是否开启请求、响应参数打印
import iView from 'iview';

const CancelToken = axios.CancelToken;
const source = CancelToken.source();

let loadCount = 0, // 做loadingView的唯一标识
loadingNameArray = [];

/**
* 请求成功拦截器
* @param req 请求参数
* @returns {*}
*/
export function requestSuccessFunc(req) {
//展示模态对话框
loadCount++;
let loadingName = `load${loadCount}`;
// show方法需要个 全局唯一的名字
window.loadingView.show(loadingName);
loadingNameArray.push(loadingName);
req.cancelToken = source.token; // 所有请求使用 同一个token,出现网络错误时一起取消
CONSOLE_REQUEST_ENABLE && console.info('requestInterceptorFunc', `url:${req.url}`, req);
// 自定义请求拦截逻辑,处理权限,请求发送监控等
if (store.getters.token) {
req.headers['token'] = getToken(); // 让每个请求携带token--['token']为自定义key 请根据实际情况自行修改
}

return req;
}

/**
* 请求失败拦截器
* @param reqError 失败信息
* @returns {Promise.<*>}
*/
export function requestFailFunc(reqError) {
// 关闭模态对话框
loadCount--;
if (!loadCount) {
loadingNameArray.forEach(item => window.loadingView.hide(item));
}
// 自定义请求失败逻辑,处理断网,请求发送监控等
return Promise.reject(reqError);
}

/**
* 响应成功拦截器
* @param res 返回数据
* @returns {*}
*/
export function responseSuccessFunc(response) {
// 关闭模态对话框
loadCount--;
if (!loadCount) {
loadingNameArray.forEach(item => window.loadingView.hide(item));
}
// 自定义响应成功逻辑,全局拦截接口,根据不同业务做不同处理,响应成功监控等
CONSOLE_RESPONSE_ENABLE && console.info('responseInterceptorFunc', response);

const httpStatusCode = {
300: '资源已被转移至其他位置',
301: '请求的资源已被永久移动到新URI',
302: '请求的资源已被临时移动到新URI',
305: '请求的资源必须通过代理访问',
400: '错误资源访问请求',
401: '资源访问未授权',
403: '资源禁止访问',
404: '未找到要访问的资源',
405: '请更换请求方法',
406: '无法访问',
408: '请求超时',
413: '请求实体过大',
414: '请求URI过长',
500: '内部服务器错误',
501: '未实现',
503: '服务无法获得',
504: '接口访问超时'
};

if (response.status >= 300 && response.status < 600) {
let errorMessage = '未知的访问错误,状态吗:' + String(res.status);
if (httpStatusCode[String(res.status)]) {
errorMessage = httpStatusCode[String(res.status)];
iView.Message.error(errorMessage); //展示错误信息
return Promise.reject('error:' + errorMessage);
}
}
// 自定义状态码
let statusCode = {
301: '登录失败',
303: '退出失败',
304: '用户或密码错误',
400: '未查询到角色数据',
401: '资源访问未授权',
403: '验证码错误',
405: '验证码已过期'
};
const res = response.data;
let code = res.code;
if (Object.keys(statusCode).includes(String(code))) {
// 异常处理
console.log('warning', res.message);
// iView.Message.error(res.message); //展示错误信息
iView.Message.error({
content: res.message,
duration: 10,
closable: true
}); //展示错误信息
switch (code) {
case 301:
{
location.href = '/login'; // 回退到登录界面
}
break;
case 303:
break;
case 304:
break;
case 401:
{
location.href = '/401'; // 跳转到未授权界面
}
break;
}
return Promise.reject('error:' + (response && response.data && response.data.message));
}
if (['blob', 'arraybuffer'].includes(response.request.responseType)) {
let fileNameIndex = response.headers['content-disposition'].indexOf('=');
let fileName = '';
if (fileNameIndex) {
let tmpName = response.headers['content-disposition'].slice(fileNameIndex + 1);
fileName = window.decodeURIComponent(tmpName);
}
return { data: res, fileName };
} else {
return res.data;
}
}

/**
* 响应失败拦截器
* @param resError 失败信息
* @returns {Promise.<*>}
* 请求的404 接口会返回这个handler中
* 请求超时 触发失败的回调
* 重复的接口取消
*/
export function responseFailFunc(resError) {
// 关闭模态对话框
loadCount--;
if (!loadCount) {
loadingNameArray.forEach(item => window.loadingView.hide(item));
}
resError.message = resError.toString();
if (resError.toString().indexOf('Network') > -1) {
resError.message = '网络连接异常';
//source.cancel(resError.message);
Promise.reject('网络连接异常 ');
}
console.warn(resError.message);
// iView.Message.error(resError.message); //展示错误信息
iView.Message.error({
content: resError.message,
duration: 5,
closable: true
});
// 响应失败,可根据resError信息做监控处理
return Promise.reject(resError);
}

@/utils/auth/index.js:

1
2
3
4
5
6
import Cookies from 'js-cookie';

const TokenKey = 'Admin-Token';
export function getToken() {
return Cookies.get(TokenKey);
}

@/config/index.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// axios 默认配置
export const AXIOS_DEFAULT_CONFIG = {
timeout: 20000,
maxContentLength: 20000,
headers: {
// 'Content-Type': 'application/x-www-form-urlencoded'
'Content-Type': 'application/json' // 跨域的时候会产生 options预检
},
// baseURL: 'http://localhost:8080/xiaozhi-back-api' //可以换种写法
//baseURL: process.env.NODE_ENV !== 'production' ? '/xiaozhi-back-api' : 'http://172.16.130.164:8080/xiaozhi-back-api'
baseURL: '/xiaozhi-back-api'
// baseURL: '/'
};
// 开启请求参数打印
export const CONSOLE_REQUEST_ENABLE = true;
// 开启响应参数打印
export const CONSOLE_RESPONSE_ENABLE = true;

loadingView请求中状态

@/plugins/loadingView.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/**
* loading状态效果
* 显示window.loadingView.show("loading名")
* 隐藏window.loadingView.hide("loading名")
* 注意:勿同时执行相同名称的loadingView
*/

window._loadingViewArr = [];
let animateTimer = 0;

function createdView() {
let loadingView = document.createElement('div');
loadingView.className = 'loadingView animated fadeOut';
let frame = window.document.createElement('div');
frame.className = 'loadingViewFrame animated';
frame.innerHTML = `<div class="loadingBox1 loadingBox"></div>
<div class="loadingBox2 loadingBox"></div>
<div class="loadingBox4 loadingBox"></div>
<div class="loadingBox3 loadingBox"></div>`;
loadingView.appendChild(frame);
window.document.body.appendChild(loadingView);
return loadingView;
}
function addClass(className) {
let classNameArr = this.className.replace(/[ ]+/g, ',').split(',');
let index = classNameArr.indexOf(className);
if (index < 0) {
classNameArr.push(className);
}
this.className = classNameArr.join(' ');
}
function removeClass(className) {
let classNameArr = this.className.replace(/[ ]+/g, ',').split(',');
let index = classNameArr.indexOf(className);
if (index >= 0) {
classNameArr.splice(index, 1);
}
this.className = classNameArr.join(' ');
}
function css(key, value) {
if (typeof key === 'string') {
if (value !== undefined) {
this.style[key] = value;
return;
} else {
let style = this.style[key];
if (!style) {
style = window.getComputedStyle ? window.getComputedStyle(this, null)[key] : this.currentStyle[key];
}
return style;
}
} else if (typeof key === 'object') {
for (let name in key) {
this.style[name] = key[name];
}
}
}

let node = createdView();
let showHide = function(_name, _isShow) {
if (!_name) return null;
let loadingId = _name;
let index = window._loadingViewArr.indexOf(loadingId);
if (index >= 0) {
if (_isShow) return true;
window._loadingViewArr.splice(index, 1);
} else {
if (_isShow) {
window._loadingViewArr.push(loadingId);
}
}
if (window._loadingViewArr.length) {
window.clearTimeout(animateTimer);
css.bind(node)('display', 'block');
removeClass.bind(node)('fadeOut');
addClass.bind(node)('fadeIn');
} else {
removeClass.bind(node)('fadeIn');
addClass.bind(node)('fadeOut');
window.clearTimeout(animateTimer);
animateTimer = window.setTimeout(() => {
css.bind(node)('display', 'none');
}, 1000);
}
return window._loadingViewArr.length > 0;
};
let loadingView = (window.loadingView = {
show(_name) {
return showHide(_name, true);
},
hide(_name) {
return showHide(_name, false);
}
});

export default loadingView;

样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
.loadingView {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
// margin-left: -50px;
// margin-right: -50px;
// margin-top: -50px;
z-index: 1999;
display: none;
background-color: rgba(255, 255, 255, 0.1);
// background-color: red;
.loadingViewFrame {
position: absolute;
left: 50%;
top: 50%;
margin: -20px 0 0 -20px;
width: 40px;
height: 40px;
-webkit-transform: rotateZ(45deg);
transform: rotateZ(45deg);
.loadingBox {
float: left;
width: 50%;
height: 50%;
position: relative;
-webkit-transform: scale(1.1);
-ms-transform: scale(1.1);
transform: scale(1.1);
}
.loadingBox:before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: @baseThemeColor;
-webkit-animation: foldCubeAngle 2.4s infinite linear both;
animation: foldCubeAngle 2.4s infinite linear both;
-webkit-transform-origin: 100% 100%;
-ms-transform-origin: 100% 100%;
transform-origin: 100% 100%;
}
.loadingBox2 {
-webkit-transform: scale(1.1) rotateZ(90deg);
transform: scale(1.1) rotateZ(90deg);
}
.loadingBox3 {
-webkit-transform: scale(1.1) rotateZ(180deg);
transform: scale(1.1) rotateZ(180deg);
}
.loadingBox4 {
-webkit-transform: scale(1.1) rotateZ(270deg);
transform: scale(1.1) rotateZ(270deg);
}
.loadingBox2:before {
-webkit-animation-delay: 0.3s;
animation-delay: 0.3s;
}
.loadingBox3:before {
-webkit-animation-delay: 0.6s;
animation-delay: 0.6s;
}
.loadingBox4:before {
-webkit-animation-delay: 0.9s;
animation-delay: 0.9s;
}
}
}

@/plugins/index.js:

1
2
3
4
5
6
7
import loadingView from './loadingView';
export default {
install: (Vue, options) => {
// 挂载实例
Vue.prototype.$loadingView = loadingView;
}
};

api请求方法封装

@/plugins/api.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/** @format */

import axios from './axios';
import _assign from 'lodash/assign';
import _merge from 'lodash/merge';

import { assert } from '@/utils'; // 断言
import { API_DEFAULT_CONFIG, AXIOS_DEFAULT_CONFIG } from '@/config'; // 配置
import API_CONFIG from '@/api';

/**
* 生成api接口类
*/
class Api {
constructor(options) {
this.api = {};
this.apiBuilder(options);
}

/**
* 创建工程接口
* @param sep 分隔符
* @param config 接口配置对象
* @param mock 是否开启mock
* @param debug 是否开启debug模式
* @param mockBaseURL mock接口地址
*/
apiBuilder({ sep = '/', config = {}, mock = false, debug = false, mockBaseURL = '' }) {
Object.keys(config).map(namespace => {
this._apiSingleBuilder({
namespace,
mock,
mockBaseURL,
sep,
debug,
config: config[namespace]
});
});
}

/**
* 创建单个接口
* @param sep 分隔符
* @param config 接口配置对象
* @param mock 是否开启mock
* @param debug 是否开启debug模式
* @param mockBaseURL mock接口地址
*/
_apiSingleBuilder({ namespace, sep = '/', config = {}, mock = false, debug = false, mockBaseURL = '' }) {
config.forEach(api => {
const { name, desc, params, method, path } = api;
let apiname = `${namespace}${sep}${name}`; // 接口调用名称 this.$api['apiname']({参数},{HTTP请求的配置})
let url = path; // 接口地址
const baseURL = mock ? mockBaseURL : AXIOS_DEFAULT_CONFIG.baseURL; // 接口base地址

debug && assert(name, `${url} :接口name属性不能为空`);
debug && assert(url.indexOf('/') === 0, `${url} :接口路径path,首字符应为/`);
// value可以使用函数调用符号
Object.defineProperty(this.api, `${apiname}`, {
value(outerParams, outerOptions) {
// let _data = _isEmpty(outerParams) ? params : _pick(_assign({}, params, outerParams), Object.keys(params));
// 未填的字段 使用默认值 未填的使用默认值
//todo FormData 则需要手动的设置请求头 Content-type:multipart/form-data
let _data =
Array.isArray(outerParams) || outerParams instanceof FormData
? outerParams
: _merge({}, params, outerParams);

return axios(_normoalize(_assign({ url, desc, baseURL, method }, outerOptions), _data));
}
});
});
}
}

/**
* 根据请求类型处理axios参数
* @param options
* @param data
* @returns {*}
* @private
*/
function _normoalize(options, data) {
if (options.method === 'POST') {
// 如果是 application/x-www-form-urlencoded 需要引入qs模块做参数处理
// options.data = qs.stringifly(data)
options.data = data;
} else if (options.method === 'GET') {
options.params = data;
}
return options;
}

/**
* 导出接口
*/
export default new Api({
config: API_CONFIG,
...API_DEFAULT_CONFIG
})['api'];

工具函数@/utils/index.js:

1
2
3
4
// 断言的内容
export function assert(condition, msg) {
if (!condition) throw new Error(`[Apior] ${msg}`);
}

配置@/config/index.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// axios 默认配置
export const AXIOS_DEFAULT_CONFIG = {
timeout: 20000,
maxContentLength: 20000,
headers: {
// 'Content-Type': 'application/x-www-form-urlencoded'
'Content-Type': 'application/json' // 跨域的时候会产生 options预检
},
// baseURL: 'http://localhost:8080/xiaozhi-back-api' //可以换种写法
//baseURL: process.env.NODE_ENV !== 'production' ? '/xiaozhi-back-api' : 'http://172.16.130.164:8080/xiaozhi-back-api'
baseURL: '/xiaozhi-back-api'
// baseURL: '/'
};

// API 默认配置
export const API_DEFAULT_CONFIG = {
mockBaseURL: 'http://yapi.demo.qunar.com/mock/12982/flytest/v1', // mock地址
mock: false, // 是否开启mock
debug: false, // 是否开启debug模式
sep: '/' // 接口调用分隔符
};

api接口地址

@/api/index.js:

1
2
3
4
5
6
7
import common from './common'; // 登录
import schoolCount from './school-count'; // 学校统计

export default {
...common,
...schoolCount
};

各个模块@/api/school-count/index.js:

1
2
3
4
5
import schoolContact from './school-contact';

export default {
schoolContact
};

@/api/school-count/school-contact.js:

1
2
3
4
5
6
7
8
export default [
{
name: 'teacherPageList',
method: 'POST',
desc: '获取学校联系人列表 ',
path: '/fansTeacher/teacherPageList'
}
];

使用方法

1
2
3
4
5
6
this.$api['schoolManage/schoolPageList']({
pageSize: this.page.size,
page: this.page.index
}).then(data => {
this.tableData = data.dataList;
});

当需设置额外的Content-Type时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
this.$api[this.uploadUrl](formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
timeout: 60000
})
.then(() => {
this.$Message.success({
content: `${this.title}信息文件上传成功!`,
duration: 5,
closable: true
});
})
.finally(() => {
});

注:

  1. 此封装方法只能去到成功时的data值,失败时会展示接口message信息。
  2. 此封装方法默认Content-Typeapplication/json。如果是如果是 application/x-www-form-urlencoded 需要引入qs模块做参数处理

当需要在js文件中使用时:

1
2
3
4
5
6
7
8
import api from '@/plugins/api';

api['schoolManage/schoolPageList']({
pageSize: this.page.size,
page: this.page.index
}).then(data => {
this.tableData = data.dataList;
});