- 本地开发前后端如何跨域调用
- 全局请求、响应处理拦截器处理
- 封装HTTP请求模块
- 编写API请求映射到后端API
- 数据的状态管理
众所周知,只要前端和后端的域名或端口不一样,就存在跨域访问,例如:前端运行后通过http://localhost:3000访问,后端运行后通过http://localhost:8080访问,就存在跨域,跨域直接http调用会失败,因此要解决跨域访问,需要使用proxy
proxyTable: {
'/api': {
target: 'http://127.0.0.1:8080/api',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
},
'/login': {
target: 'http://127.0.0.1:8080/login',
changeOrigin: true,
pathRewrite: {
'^/login': ''
}
}
},
/api
: 代理路径
用过nginx的比较容易理解,含义是类似的,当前端调用的接口路径以/api/xxx形式,会命中这条代理规则
target
: 代理的目标地址
代理的后端地址,比如前端访问http://127.0.0.1:3000/api/v1/division,那么会代理到后端的http://127.0.0.1:8080/api/v1/division
changeOrigin
: 是否开启代理pathRewrite
: 路径改写
如果前端访问的是/api/v1/division,后端的接口是/api/v1/division,若上面的pathRewrite写法是’^/api’: ‘api’,那么代理到的目标地址是http://127.0.0.1:8080/api/api/vi/division,因此路径改写需结合target写法以及后端实际接口地址进行改写
通过配置proxyTable,就具备了跨域请求,全栈开发就可以直接本地的前端调用本地的后端接口服务,接下来进行全局的请求响应处理
几乎每个项目都会在所有的请求之前做一些公共的配置或操作,也会在所有的响应中做一些统一数据结构的封装或其他操作
Vue请求拦截器可以在所有请求发送到后端之前做一些处理,前后端分离常见的比如设置token
创建src/util/request.js
import config from '@/config';
const _axios = axios.create(config);
_axios.interceptors.request.use(
config => {
// 请求前设置token
let authKey = 'X-Auth-Token';
let token = localStorage.getItem(authKey);
if (token) {
config.headers[authKey] = token;
}
return config;
},
err => {
return Promise.reject(err);
}
);
同样在src/util/request.js中配置全局响应拦截器,这里的逻辑有
_axios.interceptors.response.use(
res => {
// 这里处理响应
let token = res.headers.get('X-Auth-Token');
if (token) {
console.log(token);
localStorage.setItem('X-Auth-Token', token);
}
return res;
},
err => {
if (err) {
console.log(err);
let title = '错误码: ' + (err.response.status || err.response.data.code);
let msg = (err.response.data.msg || err.response.statusText);
if (err.response.data.code === 403) {
router.push('/Login');
} else {
notice.error(title, msg);
return Promise.reject(err);
}
}
}
);
在src/util/request.js中创建GET、POST、PUT等抽象方法,并导出http,在其他地方可以import后使用
const http = {
get (url, params) {
return new Promise((resolve, reject) => {
_axios({
url,
params,
headers: {'Content-Type': 'application/json;charset=UTF-8'},
method: 'GET'
}).then(res => {
resolve(res.data);
return res;
}).catch(err => {
reject(err);
});
});
}
};
export default http;
const http = {
post (url, body) {
return new Promise((resolve, reject) => {
_axios({
url,
data: body || {},
headers: {'Content-Type': 'application/json;charset=UTF-8'},
method: 'POST'
}).then(res => {
resolve(res.data);
return res;
}).catch(err => {
reject(err);
});
});
},
};
export default http;
const http = {
put (url, body) {
return new Promise((resolve, reject) => {
_axios({
url,
data: body || {},
headers: {'Content-Type': 'application/json;charset=UTF-8'},
method: 'PUT'
}).then(res => {
resolve(res.data);
return res;
}).catch(err => {
reject(err);
});
});
}
};
export default http;
前面封装的http各种请求与业务无关,是抽象封装的,接下来创建业务调用模块
import http from '@/util/request';
const division = {};
const login = {};
const user = {};
division.getFlatCities = () => {
return http.get(
'/api/v1/division/flat_cities',
{}
);
};
user.register = (data) => {
return http.post(
'/api/v1/register',
data
);
};
login.login = (data) => {
return http.post(
'/login',
data
);
};
login.logout = () => {
return http.post(
'/api/v1/logout'
);
};
这样,在其他地方import apis from ‘@/src/api/apis’后就可以调用业务后端请求了,这样可以统一在apis.js中配置前端到后端的请求映射,单独在vue组件中使用axios也可以直接调用接口,只是这样非常的不友好。
有时候,我们需要把后端接口请求到的数据缓存起来,在其他各个vue组件中使用,那么这里就会有额外的操作(本地数据缓存),为了统一,我们使用vuex中的actions来封装一下请求后端接口的逻辑,举例:获取行政区划的平铺数据
刚刚apis.js的定义是这样:
division.getFlatCities = () => {
return http.get(
'/api/v1/division/flat_cities',
{}
);
};
我们在src/vuex/store.js中添加一个变量,用于记录行政区划的平铺数据:
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
import * as mutations from './mutations';
import * as getters from './getters';
export default new Vuex.Store({
state: {
flatCities: {},
}
}
然后在/src/vuex/mutations.js中创建修改store.state.flatCities的方法,理论上,mutations是唯一能修改store.state的方式,参数一就是store.state,参数二是调用SET_FLAT_CITIES方法的入参
export const SET_FLAT_CITIES = (state, flatCities) => {
state.flatCities = flatCities;
};
继续在/src/vuex/actions.js中定义含缓存操作的业务请求方法
export const loadFlatCities = ({commit, state}) => {
let cacheData = localStorage.getItem('flatCities');
if (cacheData) {
// 有缓存,直接取缓存
commit('SET_FLAT_CITIES', JSON.parse(cacheData));
} else {
apis.division.getFlatCities().then(res => {
commit('SET_FLAT_CITIES', res.body);
localStorage.setItem('flatCities', JSON.stringify(res.body));
});
}
};
vue组件中通过…mapActions映射到loadFlatCities方法
vue组件中从vuex引入mappActions
import {mapState, mapActions} from 'vuex';
通过…mapActions中添加loadFlatCities将其映射进来,这样就可以通过this.loadFlatCities()进行调用,和methos()中定义的方法调用方式一样。
methods: {
...mapActions(['logout', 'isLogin', 'loadFlatCities', 'autoLocation', 'changeCurrentLocation']),
showLocation () {
this.isShowLocation = true;
},
省略
当在组件的created()中调用了this.loadFlatCities()时,localStorage和$store.state.flatCities都有后端返回的行政区划平铺,可以直接在组件中进行渲染了。
<div v-for="(flatDivision, fIndex) in $store.state.flatCities" :key="fIndex">
<Row>
<Col span="1">
<div class="province-first-letter">{{ flatDivision.letter }}</div>
</Col>
<Col span="23">
<div v-for="(province, index) in flatDivision.provinces" class="province-item" :key="index">
<Row>
<Col span="1">
{{ province.shortName }}
</Col>
<Col span="23">
<span v-for="(ct, index) in province.children" :key="index" class="city-item"
@click="changeCity(province, ct)">
{{ ct.shortName }}
</span>
</Col>
</Row>
</div>
</Col>
</Row>
</div>