如果觉得中间废话多,请直接跳过,直接看下面代码就好了,不闲扯直接开始!
平时我们项目中调用后端接口的例子:
<script>
// 其他东西咱就省略了
// 引入插件
import axios from "axios";
export default {
data() {
return {};
},
methods: {
m_getList() {
axios({
url: "", // 接口地址
data: {}, // 请求参数
// ...其他配置
}).then(res => {
// 判断接口状态
if (res.status == 200) {
// 成功
} else {
// 失败
}
}).catch(err => {
// 出错
});
}
},
created() {
// 调用函数
this.m_getList();
},
};
</script>
这就是平时项目中,我们请求后端接口的大致代码,项目中的具体细节这里就不多数了。
相信很多人都是这么做的,也不会觉得有什么问题,实际上这没有任何错误,完全能正常跑的一段代码。那么我为什么要把他拿出来呢?
前后端分离的项目中,接口很多,没个接口都有自己不可替代的作用,但是并不是接口越多越好。很多时候,接口完全是可以复用的,这能减少后端同事,很大一部分工作量。并不是没一个页面,都要自己对应的接口,那样代码太个性,项目到后期会变得很臃肿,很难维护。
假如,有这么一个接口,同时要在多个页面进行调用,前端的同事应该怎么去处理?
就拿上面那一段代码来说,也许有的朋友会说,cv大法(复制粘贴)了。没错,可以能解决问题,很完美。但是,项目不是一层不变的,如果有一天,这个接口变了,或者无法满足需求了,需要更换,怎么办。如果一两个地方还好,三五个也还能坚持,十个八个呢?
所以,如果你是一个前端架构着,一定要考虑这个问题,特别是大项目。
对后端接口,进行分类,并统一管理。好处很多,大致列一下:
就这样,别的就不多列举了。先来个截图大家瞄瞄,效果
这是配置文档
这里是应用,只需要把参数传进去,然后就能返回数据了,如果要换接口,只需要换一下配置里的url,所有用到这个接口的地址就都变了。但是页面上不用做任何调整。
em。。。直接上代码吧。为了方便演示,我就不把配置什么的拆开了,就直接放到一起写了
import axios from "./axiosConfig.js";
// 接口配置
let apiConfig = {
systemManagement: [
{ name: "doLogin", method: "post", url: "/xy-base/base/login/doLogin", headers: null, noToken: true}, //
{ name: "getUserAuthority", method: "post", url: "/xy-base/base/authority/getUserAuthority", header: null}, // 用户角色权限
],
};
// 获取token,与后端交互时的秘钥,不用每次调用接口都传,直接在这里统一处理了
let getTokenFn = () => {
let system_loginData = localStorage.getItem("system_loginData");
if (system_loginData) {
system_loginData = JSON.parse(system_loginData) || {};
return system_loginData.token || "";
}
return "";
}
// 格式化参数
/*
当接口为get的时候,参数要拼接到url后面,
*/
let formatParamsFn = (params) => {
let str = [];
for (let key in params) {
str.push(`${key}=${params[key]}`);
}
return str.join("&");
}
// 获取地址,打包时,根据不同的环境获取对应的地址,比如:测试环境,生成环境
let getUrlFn = (params = {}, item = {}) => {
return process.env.VUE_APP_API_PATH + (item.method === 'get' ? `${item.url}?${formatParamsFn(params)}` : item.url);
}
// 工厂函数,把apiConfig下的各个模块的api配置,生成方法,等待调用
let toApiFn = (configData = null) => {
// 接口对象
let api = {};
if (configData) {
for (let modeName in configData) {
api[modeName] = {};
let modeApiConfig = configData[modeName] || [];
modeApiConfig.forEach(modeItem => {
if (modeItem && modeItem.name) {
api[modeName][modeItem.name] = (params = {}, option = {}) => {
// option 调用接口时,可以自定义一些配置项
// params 参数
let token = modeItem.noToken ? "" : getTokenFn();
return axios({
method: option.method || modeItem.method || 'post',
url: option.url || getUrlFn(params, modeItem),
data: params,
headers: Object.assign({}, modeItem.headers, option.headers, {
token
}),
}).then((res = {}) => {
let status = res.status;
// 获取到接口请求结果后,统一在这里处理,外面调用的时候,就不用管接口是否成功或者
// 失败后处理逻辑,只关注业务逻辑就好了
if (status == 200) {
let data = res.data || {};
if (data.code == 401) {
return
}
return data;
}
return {
code: res.status,
msg: res.statusText,
};
});
}
}
});
}
}
return api;
}
// 调用工厂函数,生成对象,并导出,我们可以在main.js中import,把他挂到vue的原型上就可以实现
// 子组件中直接this调用了,而不需要每次都import
export default toApiFn(apiConfig);
main.js中的代码
import Vue from "vue"
import App from "./App.vue"
import store from "./store/index.js";
import router from "./router/index.js";
import api from "./api/index.js"; // 导入api对象
Vue.config.productionTip = false
Vue.prototype.$api = api; // 挂到vue的prototype上
Vue.prototype.$util = util;
new Vue({
store,
router,
render: h => h(App),
}).$mount("#app")
子组件中使用例子
// 以登录接口为例
// 处理参数
let _parmas = Object.assign({}, this.formData, {
account: this.formData.userName.toLowerCase()
});
// 调用
this.$api.systemManagement.doLogin(_parmas).then(res => {
this.loging = false;
// 接口成功后,根据后端返回的内部的code处理业务逻辑
if (res.code == 200) {
// 具体逻辑处理
} else {
// this.$message.error(res.msg);
}
}).catch(err => {
this.loging = false;
// this.$message.error(err);
});
axiosConfig.js的代码如下
// axios默认配置,如如特需需求,不用修改
import axios from 'axios';
import qs from 'qs';
//此文件配置请求前置操作,后置操作
const instance = axios.create({
transformRequest: [function(data) {
// Do whatever you want to transform the data
return qs.stringify(data, {arrayFormat: 'indices'});
}],
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
},
timeout: 60000,
});
instance.interceptors.response.use(response => {
return response;
}, error => {
// 这里我们把错误信息扶正, 后面就不需要写 catch 了
return Promise.resolve({
isSuccess: false,
msg: error.response ? error.response.statusText : "",
exception: [error.response.status, error.response.statusText, error.response.request.responseURL].join(" "),
data: error.response.statusText,
});
});
export default instance;
大致封装就是这样了,最后让我们来看看上面那个工厂函数实际上生成了一个什么样的东西
呐,大致长这样,我的项目比较大,接口比较多。所以我们都是给接口分了模块的,如果大家的不想要,中间那个systemManagement这一层可以去掉就好了。