封装 axios 模块
封装背景
使用axios发起一个请求是比较简单的事情,但是axios没有进行封装复用,项目越来越大,会引起越来越多的代码冗余,让代码变得越来越难维护。所以我们在这里先对 axios 进行二次封装,使项目中各个组件能够复用请求,让代码变得更容易维护。
封装要点
- 统一 url 配置
- 统一 api 请求
- request (请求) 拦截器,例如:带上token等,设置请求头
- response (响应) 拦截器,例如:统一错误处理,页面重定向等
- 根据需要,结合 Vuex 做全局的 loading 动画,或者错误处理
- 将 axios 封装成 Vue 插件使用
文件结构
在 src 目录下,新建一个 http 文件夹,用来存放 http 交互 api 代码。
config.js:axios 默认配置,包含基础路径等信息。
axios.js:二次封装 axios 模块,包含拦截器等信息。
interface.js :请求接口汇总模块,聚合模块 API。
index.js:将 axios 封装成插件,按插件方式引入。
config.js
export default {
method: \'get\',
// 基础url前缀
baseURL: \'http://localhost:8080/\',
// 请求头信息
headers: {
\'Content-Type\': \'application/json;charset=UTF-8\'
},
// 参数
data: {},
// 设置超时时间
timeout: 10000,
// 携带凭证
withCredentials: true,
// 返回数据类型
responseType: \'json\'
}
axios.js
import axios from \'axios\';
import config from \'./config\';
import qs from \'qs\';
import Cookies from "js-cookie";
import router from \'@/router\'
// 使用vuex做全局loading时使用
// import store from \'@/store\'
export default function $axios(options) {
return new Promise((resolve, reject) => {
const instance = axios.create({
baseURL: config.baseURL,
headers: {},
transformResponse: [function (data) {
}]
})
// request 拦截器
instance.interceptors.request.use(
config => {
let token = Cookies.get(\'token\')
// 1. 请求开始的时候可以结合 vuex 开启全屏 loading 动画
// console.log(store.state.loading)
// console.log(\'准备发送请求...\')
// 2. 带上token
if (token) {
config.headers.accessToken = token
} else {
// 重定向到登录页面
router.push(\'/login\')
}
// 3. 根据请求方法,序列化传来的参数,根据后端需求是否序列化
if (config.method === \'post\') {
if (config.data.__proto__ === FormData.prototype
|| config.url.endsWith(\'path\')
|| config.url.endsWith(\'mark\')
|| config.url.endsWith(\'patchs\')
) {
} else {
config.data = qs.stringify(config.data)
}
}
return config
},
error => {
// 请求错误时
console.log(\'request:\', error)
// 1. 判断请求超时
if (error.code === \'ECONNABORTED\' && error.message.indexOf(\'timeout\') !== -1) {
console.log(\'timeout请求超时\')
// return service.request(originalRequest);// 再重复请求一次
}
// 2. 需要重定向到错误页面
const errorInfo = error.response
console.log(errorInfo)
if (errorInfo) {
error = errorInfo.data // 页面那边catch的时候就能拿到详细的错误信息,看最下边的Promise.reject
const errorStatus = errorInfo.status; // 404 403 500 ...
router.push({
path: `/error/${errorStatus}`
})
}
return Promise.reject(error) // 在调用的那边可以拿到(catch)你想返回的错误信息
}
)
// response 拦截器
instance.interceptors.response.use(
response => {
let data;
// IE9时response.data是undefined,因此需要使用response.request.responseText(Stringify后的字符串)
if (response.data == undefined) {
data = JSON.parse(response.request.responseText)
} else {
data = response.data
}
// 根据返回的code值来做不同的处理
switch (data.rc) {
case 1:
console.log(data.desc)
break;
case 0:
store.commit(\'changeState\')
// console.log(\'登录成功\')
default:
}
// 若不是正确的返回code,且已经登录,就抛出错误
// const err = new Error(data.desc)
// err.data = data
// err.response = response
// throw err
return data
},
err => {
if (err && err.response) {
switch (err.response.status) {
case 400:
err.message = \'请求错误\'
break
case 401:
err.message = \'未授权,请登录\'
break
case 403:
err.message = \'拒绝访问\'
break
case 404:
err.message = `请求地址出错: ${err.response.config.url}`
break
case 408:
err.message = \'请求超时\'
break
case 500:
err.message = \'服务器内部错误\'
break
case 501:
err.message = \'服务未实现\'
break
case 502:
err.message = \'网关错误\'
break
case 503:
err.message = \'服务不可用\'
break
case 504:
err.message = \'网关超时\'
break
case 505:
err.message = \'HTTP版本不受支持\'
break
default:
}
}
console.error(err)
return Promise.reject(err) // 返回接口返回的错误信息
}
)
// 请求处理
instance(options).then(res => {
resolve(res)
return false
}).catch(error => {
reject(error)
})
})
}
interface.js
import axios from \'./axios\'
/*
* 将所有接口统一起来便于维护
* 如果项目很大可以将 url 独立成文件,接口分成不同的模块
*/
// 单独导出
export const login = () => {
return axios({
url: \'/login\',
method: \'get\'
})
}
export const getUser = () => {
return axios({
url: \'/user\',
method: \'get\'
})
}
export const getMenu = data => {
return axios({
url: \'/menu\',
method: \'post\',
data
})
}
// 默认全部导出
export default {
login,
getUser,
getMenu
}
index.js
// 导入所有接口
import apis from \'./interface\'
const install = Vue => {
if (install.installed)
return;
install.installed = true;
Object.defineProperties(Vue.prototype, {
// 注意,此处挂载在 Vue 原型的 $api 对象上
$api: {
get() {
return apis
}
}
})
}
export default install
安装 js-cookie
上面 axios.js 中,会用到 Cookie 获取 token,所以需要把相关依赖安装一下。
执行以下命令,安装依赖包。
yarn add js-cookie
代码实例
1.引入插件
在 main.js 中以 vue 插件的形式引入 axios,这样在其他地方就可通过 this.$api 调用相关的接口了。
2.编写接口
在 interface.js 中添加 login 接口。
3.调用接口
在登录界面 Login.vue 中,添加一个登录按钮,点击处理函数通过 axios 调用 login 接口返回数据。
成功返回之后,将 token 放入 Cookie 并跳转到主页。
<template>
<div class="page">
<h2>Login Page</h2>
<el-button type="primary" @click="login()">登录</el-button>
</div>
</template>
<script>
import mock from \'@/mock/mock.js\';
import Cookies from "js-cookie";
import router from \'@/router\'
export default {
name: \'Login\',
methods: {
login() {
this.$api.login().then(function(res) {
alert(res.data.token)
Cookies.set(\'token\', res.data.token) // 放置token到Cookie
router.push(\'/\') // 登录成功,跳转到主页
}).catch(function(res) {
alert(res);
});
}
}
}
</script>
4.mock 接口
在 mock.js 中添加 login 接口进行拦截,返回一个 token。
启动测试
浏览器访问:http://localhost:8080/#/login,显示登录界面。
点击登录按钮,首先弹出框,显示返回的 token 信息。
点击确定关掉弹出框后,跳转到主页。点击用户、菜单按钮,接口调用正常。
封装 mock 模块
为了统一可以统一管理和集中控制数据模拟接口,我们对 mock 模块进行了封装,可以方便的定制模拟接口的统一开关和个体开关。
文件结构
在 mock 目录下新建一个 index.js ,创建 modules 目录并在里面创建三个模块 *.js 文件。
index.js:模拟接口模块聚合文件
login.js:登录相关的接口模拟
user.js:用户相关的接口模拟
menu.js:菜单相关的接口模拟
index.js
import Mock from \'mockjs\'
import * as login from \'./modules/login\'
import * as user from \'./modules/user\'
import * as menu from \'./modules/menu\'
// 1. 开启/关闭[业务模块]拦截, 通过调用fnCreate方法[isOpen参数]设置.
// 2. 开启/关闭[业务模块中某个请求]拦截, 通过函数返回对象中的[isOpen属性]设置.
fnCreate(login, true)
fnCreate(user, true)
fnCreate(menu, true)
/**
* 创建mock模拟数据
* @param {*} mod 模块
* @param {*} isOpen 是否开启?
*/
function fnCreate (mod, isOpen = true) {
if (isOpen) {
for (var key in mod) {
((res) => {
if (res.isOpen !== false) {
Mock.mock(new RegExp(res.url), res.type, (opts) => {
opts[\'data\'] = opts.body ? JSON.parse(opts.body) : null
delete opts.body
console.log(\'\n\')
console.log(\'%cmock拦截, 请求: \', \'color:blue\', opts)
console.log(\'%cmock拦截, 响应: \', \'color:blue\', res.data)
return res.data
})
}
})(mod[key]() || {})
}
}
}
login.js
// 登录接口
export function login () {
return {
// isOpen: false,
url: \'http://localhost:8080/login\',
type: \'get\',
data: {
\'msg\': \'success\',
\'code\': 0,
\'data\': {
\'token\': \'4344323121398\'
// 其他数据
}
}
}
}
user.js
// 获取用户信息
export function getUser () {
return {
// isOpen: false,
url: \'http://localhost:8080/user\',
type: \'get\',
data: {
\'msg\': \'success\',
\'code\': 0,
\'data\': {
\'id\': \'@increment\',
\'name\': \'@name\', // 随机生成姓名
\'email\': \'@email\', // 随机生成姓名
\'age|10-20\': 12
// 其他数据
}
}
}
}
menu.js
// 获取菜单信息
export function getMenu () {
return {
// isOpen: false,
url: \'http://localhost:8080/menu\',
type: \'get\',
data: {
\'msg\': \'success\',
\'code\': 0,
\'data\': {
\'id\': \'@increment\',
\'name\': \'menu\', // 随机生成姓名
\'order|10-20\': 12
// 其他数据
}
}
}
}
修改引入
Login.vue
Home.vue
启动测试
浏览器访问:http://localhost:8080/#/,按照先前流程走一遍,没有问题。