做外卖报表导出功能,前端使用fetch请求,后端在正常情况向会返回csv文件,在异常时会返回对应的异常码;前端的请求都是使用request.js做了统一拦截和错误提示,但是不支持文件下载,于是对原有内容稍作改造,支持文件下载。
前端是怎么实现文件下载的?
// 使用fetch发送请求并拿到返回值
const response = await fetch(newUrl, responseBody);
// 将文件流转为blob对象,并获取本地文件链接
response.blob().then((blob) => {
const a = window.document.createElement('a');
const downUrl = window.URL.createObjectURL(blob);// 获取 blob 本地文件连接 (blob 为纯二进制对象,不能够直接保存到磁盘上)
const filename = response.headers.get('Content-Disposition').split('filename=')[1].split('.');
a.href = downUrl;
a.download = `${decodeURI(filename[0])}.${filename[1]}`;
a.click();
window.URL.revokeObjectURL(downUrl);
});
实际场景中需要区分返回的是否是文件,文件走下载方法,数据走原有的request.js中定义的方法,这里需要使用响应头的Content-Type
来判断,这里后端直返回CSV文件所以使用response.headers.get('Content-Type').indexOf('application/msexcel') > -1
来判断是否为文件下载(响应头与后端约定好了)。
if (response.headers.get('Content-Type').indexOf('application/msexcel') > -1) {
response.blob().then(
执行文件下载
)
} else {
response.json();
原有数据处理方法
}
request.js
import fetch from 'dva/fetch';
import { stringify } from 'qs';
import { message } from 'antd';
import { getFormData } from './index';
/**
* Requests a URL, returning a promise.
*
* @param {string} url The URL we want to request
* @param {object} [options] The options we want to pass to "fetch"
* @return {object} An object containing either "data" or "err"
*
/* message.config({
top: 10,
duration: 5,
getContainer: () => window.document.getElementById('root'),
});*/
export default async function request(url, options) {
let newOptions;
let newUrl = url;
let responseBody = {};
let data = {};
const { body, params } = options;
// 判断请求类型
if ((!(typeof body === 'string')) && body) {
newOptions = Object.assign({}, options, { body: getFormData(body) });
responseBody = {
credentials: 'same-origin',
...newOptions,
};
} else {
newOptions = options;
responseBody = {
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
},
...newOptions,
};
}
// get
if (params) {
newUrl += `?${stringify(params)}`;
}
const response = await fetch(newUrl, responseBody);
// 报表导出 Content-Type为application/msexcel时,为文件流,进行下载操作
if (response.headers.get('Content-Type').indexOf('application/msexcel') > -1) {
response.blob().then((blob) => {
const a = window.document.createElement('a');
const downUrl = window.URL.createObjectURL(blob);// 获取 blob 本地文件连接 (blob 为纯二进制对象,不能够直接保存到磁盘上)
const filename = response.headers.get('Content-Disposition').split('filename=')[1].split('.');
a.href = downUrl;
a.download = `${decodeURI(filename[0])}.${filename[1]}`;
a.click();
window.URL.revokeObjectURL(downUrl);
});
return data;
}
if (response.status !== 200) {
message.error('网络或服务器异常!');
return {
data: {
code: response.status,
},
};
}
data = await response.json();
if (data.code !== '200') {
// 状态码不为304或302的时候报错
if (data.code !== '304' && data.code !== '302') {
console.log(data.msg);
message.error(data.msg);
}
// 状态码为304的时候,请求登录接口
if (data.code === '304') {
request('/login', {
method: 'post',
body: {
...data.data,
redirect_uri: encodeURIComponent(window.location.href),
},
});
}
// 状态码为302的时候,进行跳转
if (data.code === '302') {
window.location = data.msg;
}
}
return { data };
}