先展示一下现在项目结构,红色框框圈出来的是涉及到改动的部分。
先展示一下.env.production的内容,.env.production里面VUE_APP_CURRENTMODE = 'prod',下面根据实际项目需求配置。
VUE_APP_CURRENTMODE = 'dev'
VUE_APP_BASEURL = 'https://XXXXXXX'
VUE_APP_IMGURL = 'https://XXXXXXX'
main.js 这样子用this.$api就能调用我们封装好的接口了。
import { createApp } from "vue";
import App from "./App.vue";
import axios from "axios";
import api from './request/api/index'
const app = createApp(App);
axios.defaults.baseURL = "/api";
axios.defaults.headers = {
"Content-Type": "application/json",
};
app.config.globalProperties.$axios = axios;
app.mount("#app");
app.config.globalProperties.$api = api;
vue.config.js _(:з」∠)_这个算是我不太熟悉的地方,我开始是只在header里面设置了Access-Control-Allow-Origin和Access-Control-Allow-Headers,但是还是报跨域错误…问了下同事,用这个vue里设置proxy的办法解决了。
module.exports = {
publicPath: "./",
devServer: {
open: true,
// host: "localhost",
// port: 8080,
overlay: {
warnings: false,
errors: true,
},
proxy: {
"/api": {
target: "https://XXXXXXX",
changeOrigin: true,
ws: true,
pathRewrite: {
"^/api": "",
},
},
},
},
};
然后就是最重要的部分了,分出了Controller便于管理各个模块的接口。如果有需要复用的接口发生路径改变,也只用修改一个地方。在接口超级多的大型项目里面,非常好用。
config.js是导入我们全局变量的文件
export default {
baseURL: process.env.VUE_APP_BASEURL,
imgURL: process.env.VUE_APP_IMGURL,
}
index.js 如果/request/api 里面需要新增Controller的话,在index.js文件中记得也要加上。因为这边是样例就只放了一个Controller。
/**
* api接口的统一出口
*/
// news模块接口
import newsController from './newsController',
// 导出接口
export default {
newsController: newsController,
}
newsController.js 列出某个模块下所有的接口
/**
*News模块接口列表
*/
import axios from '../server' // 导入server中创建的axios实例
const newsController = {
getNewsList(params) {
return axios.post('/news/list', params)
},
getNewsDetail(params) {
return axios.post('/news/detail', params)
},
}
export default newsController
helper.js
const helper = {
// 根据name获取地址栏的参数值
getQueryString(name) {
const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`)
const hash = window.location.hash
const search = hash.split('?')
const r = search[1] && search[1].match(reg)
if (r != null) return r[2]; return ''
},
// 拼接参数至url
queryString(url, query) {
const str = []
for (const key in query) {
str.push(key + '=' + query[key])
}
const paramStr = str.join('&')
return paramStr ? `${url}?${paramStr}` : url
},
bin2hex(s) {
var i; var l; var o = ''
var n
s += ''
for (i = 0, l = s.length; i < l; i++) {
n = s.charCodeAt(i)
.toString(16)
o += n.length < 2 ? '0' + n : n
}
return o
},
// 判断是否过期
timeOverdue(startTime, endTime) {
// 可以传日期时间或时间戳
const contrastTime = 20 * 60 * 1000
const difference = startTime - endTime // 时间差的毫秒数
if (difference > contrastTime) {
return false
} else {
return true
}
// return `相差${days}天${hours}小时${minutes}分钟${seconds}秒`
}
}
export default helper
server.js则是比较重要的部分了,简单地说就是在我们代码里调用接口时候,拦截一下,给判断下token过期没,没的话加上header,请求。过期的话,又进行什么操作。然后返回信息给我们的时候也拦截一下提前进行处理。还可以根据不同错误代码返回不同的信息什么的都写在这,这些具体看各个项目什么需求做改动了。这边只简单放一个,不带什么业务逻辑的版本。
这边用的是vant的ui,所以用的vant的Toast做例子。
import axios from 'axios'
import helper from './helper'
import CONFIG from './api/config'
import router from '../router'
import StorageDB from '../utils/storageDB'
import { Toast } from 'vant'
/**
* 跳转登录页
* 携带当前页面路由,以期在登录页面完成登录后返回当前页面
*/
const toLogin = () => {
router.replace({
path: '/login',
query: {
redirect: router.currentRoute.fullPath
}
})
}
/**
* 请求失败后的错误统一处理
* @param {Number} status 请求失败的状态码
*/
const errorHandle = (status, other) => {
// 状态码判断
switch (status) {
// 401: 未登录状态,跳转登录页
case 401:
toLogin()
break
// 403 token过期
// 清除token并跳转登录页
case 403:
Toast('登录超时');
StorageDB.removeItem('TOKEN')
StorageDB.removeItem('TOKEN_TIME')
setTimeout(() => {
toLogin()
}, 1000)
break
// 404请求不存在
case 404:
Toast('请求不存在');
break
case 406:
logout406()
break
case 500:
Toast('网络异常');
break
case 502:
Toast('网络异常');
break
default:
console.log(other)
}
}
// let loading
// 创建axios实例
const instance = axios.create({
//这里就是dev的环境强行使用前面设置的proxy
baseURL: process.env.VUE_APP_CURRENTMODE == 'dev'?'/api':CONFIG.baseURL,
timeout: 1000 * 15,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild',
'Content-Type': 'application/json;charset=utf-8',
}
})
/**
* 请求拦截器
* 每次请求前,如果存在token则在请求头中携带token
*/
instance.interceptors.request.use(
config => {
// 设置或者刷新 token
//const token = StorageDB.getItem('TOKEN')
//config.headers.Authorization = token
return config
},
error => Promise.error(error))
// 响应拦截器
instance.interceptors.response.use(
// 请求成功
res => {
/*此处省略好多行代码*/
if (res.time) {
StorageDB.setItem('TOKEN_TIME', new Date(res.time).getTime())
}
return res.status === 200 ? Promise.resolve(res.data) : Promise.reject(res.data)
},
// 请求失败
error => {
const { response } = error
// loading.close()
if (response) {
// 请求已发出,但是不在2xx的范围
errorHandle(response.status, response.data.message)
return Promise.reject(response)
} else {
console.dir(error)
console.log('网络服务器异常,请稍后重试')
}
})
// export default instance;
function apiAxios(method, url, params, isExport) {
return instance({
method: method,
// 拼接参数
url: method === 'GET' ? helper.queryString(url, params) : url,
data: method === 'POST' || method === 'PUT' || method === 'DELETE' ? params : null,
withCredentials: false,
responseType: isExport ? 'blob' : ''
})
}
let loginOutLoading = false
async function logout406() {
if (!loginOutLoading) {
loginOutLoading = true
}
}
export default {
components: {
[Toast.name]: Toast
},
get: function(url, params) {
return apiAxios('GET', url, params)
},
post: function(url, params) {
return apiAxios('POST', url, params)
},
put: function(url, params) {
return apiAxios('PUT', url, params)
},
delete: function(url, params) {
return apiAxios('DELETE', url, params)
},
export: function(url, params) {
return apiAxios('POST', url, params, true)
},
getExport: function(url, params) {
return apiAxios('GET', url, params, true)
},
upload: function(url, params) {
return apiAxios('post', url, params, false, true)
}
}
到这里就已经封装好了,但是其实我的样例代码其实是没和登录那块逻辑糅在一起的…不过如果没有token需求的dev环境,也已经可以用啦。
具体使用就很简单了,在你的.vue文件里面
this.$api.newsController.getNewsList(params).then(res => {
console.log(res)
}).catch(function(err) {
console.log(err);
});
这就完全ok拉!