架構二次封裝需要實現如下功能
- API下發
- 取消多余請求 - 統一處理
- HTTP錯誤統一處理
- 服務器特殊錯誤處理
- API-mock方案
封裝目標:
1)頁面只管API調用,與服務端的接口定義限制在封裝結構中。
2)在封裝結構中設計開關,切換api-mock時完全不需要頁面進行修改。
3)前后端分離的更干脆,前端頁面更干凈,接口簡潔易懂,只處理返回邏輯。
?
上代碼
# Axios二次封裝Service
文件 api/service.js
import axios from 'axios'
import { Message } from 'element-ui'
const getUrlKey = config => `SYSNAME_PC_${config.url}`
const cancelToken = {} // 被取消的請求池
const CancelToken = axios.CancelToken
/**
* @func 取消多余請求-統一處理
* @desc type 操作類別
* @desc config 當前請求的配置
*/
const cancelThis = (type, config) => {
const key = getUrlKey(config)
if (type === 'check') {
if (cancelToken[key]) {
cancelToken[key]()
} else {
config.cancelToken = new CancelToken(c => {
cancelToken[key] = c
})
}
} else if (type === 'remove') {
delete cancelToken[key]
}
}
const cancelTokenWhiteList = []
/**
* @func axios二次封裝為service
*/
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_URL,
withCredentials: true, // 跨域攜帶 cookie
timeout: 15000
})
// request interceptor
axios.interceptors.request.use(
config => {
if (cancelTokenWhiteList.indexOf(config.url) !== -1) {
cancelThis('check', config)
}
config.crossDomain = true
config.headers.common = {
'Content-Type': 'application/json;charset=UTF-8',
'X-ER-System-Info': 'default',
'Accept-Language': 'zh-CN'
}
return config
},
err => {
console.log('request interceptor err:', err)
return Promise.reject(err)
}
)
// response interceptor
axios.interceptors.response.use(
async response => {
cancelThis('remove', response.config)
const result = response.data || {}
const { code, data, message } = result
if (code === '9999' || code === '-1') {
Message.closeAll()
Message.error(message || '服務器異常,請稍后重試或聯系我們')
return Promise.reject(new Error(message || 'Error'))
} else {
return data
}
},
// http request err
err => {
console.log('response interceptor error: ', err)
const res = err.response
// 請求執行成功,屬Http Code錯誤
if (res) {
const { status, data } = JSON.parse(JSON.stringify(res)) || {}
if (Number(status) === 500) {
Message.error(data.message || '服務器異常,請您稍后重試或聯系我們')
} else {
handleHttpCodeError(Number(status))
}
} else {
const ErrStr = err.toString() || ''
if (ErrStr.toString().indexOf('Cancel') !== -1) { // 請求被主動取消,忽略
return
} else {
// 請求執行失敗,斷網/超時
handleNotExceptError(ErrStr)
}
}
}
)
/**
* @func 通用的HttpCode異常處理
* @param {Number} code http異常碼
*/
function handleHttpCodeError(code) {
const HttpError = {
400: '請求錯誤',
403: '拒絕訪問',
404: '請求地址錯誤',
408: '請求超時',
500: '服務器開個小差,請稍后再試',
501: '服務器開個小差,請稍后再試',
502: '服務器開個小差,請稍后再試',
503: '服務器開個小差,請稍后再試',
504: '服務器開個小差,請稍后再試',
505: 'HTTP版本不受支持'
}
Message.error(HttpError[code])
}
/**
* @func 請求執行失敗,斷網/超時/未知錯誤
* @param {Object} err 錯誤信息載體
*/
function handleNotExceptError(ErrStr) {
Message.closeAll()
if (ErrStr.indexOf('Request failed') !== -1) {
Message.error('當前網絡不可用,請檢查網絡設置')
} else if (ErrStr.indexOf('timeout') !== -1) {
Message.error('您當前網絡較差,請切換網絡活稍后重試')
} else {
Message.error('發生未知錯誤,請稍后重試')
}
}
export default service
?
# API封裝
同級文件, api/login.js
// api/login.js
import service from './service.js'
//實現api-mock
import {
initLoginMock,
getUserInfoMock
} from './api-mock/login.js'
const mock = process.env.VUE_APP_MOCK === 'true'
// post
export function initLogin(data) {
// mock-api策略仁者見仁、智者見智,也可以通過url匹配取值,本方案個人比較喜歡。
if (mock) return initLoginMock()
return service({
url: 'v1/user/login',
method: 'post',
data
})
}
// get
export function getUserInfo(params) {
if (mock) return getUserInfoMock()
return service({
url: 'v1/user/getUserInfo',
method: 'get',
params
})
}
?
# API調用
頁面 login.vue
<script>
import { initLogin, getUserInfo } from '@/api/login.js'
// 登錄頁初始化
initLogin({macAdress: 'adress'}).then(data => {
console.log(data)
}).catch(e => {
console.log(e)
})
</script>