问题:有个功能是将tabel数据导出,并且后端写了个接口,这个接口返回你要下载的excel文件数据了。前端请求接口就行,然后下载下来,但前端该怎么操作(发起请求呢)
/**
* 导出文件
* @param {string} url - 导出文件的 API 地址
* @param {Object} data - 导出文件的请求参数
*/
export function exportFile(url, data) {
const reqData = {
...data,
...initReqData(),//一些必要固定参数
};
axios({
url,
baseURL: import.meta.env.VITE_BASE_URL,
data: reqData,
responseType: 'blob',//设置为'blob',表示期望服务器响应的数据类型为二进制大对象(Blob),这对于下载文件特别有用。
method: 'post',
}).then(res => {
if (res.data.type === 'application/json') {
throw res;
}
const link = document.createElement('a');
const blob = new Blob([res.data], { type: 'application/vnd.ms-excel' });
link.style.display = 'none';
link.href = URL.createObjectURL(blob);
const contentDisposition = res.headers['content-disposition'];
let needle = 'filename*=utf-8\'\'';
let index = contentDisposition.indexOf(needle);
if (index < 0) {
needle = 'filename=';
index = contentDisposition.indexOf(needle);
}
let fileName = contentDisposition.substring(index + needle.length);
fileName = decodeURI(fileName);
link.setAttribute('download', `${fileName}`);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
message.success('导出成功');
}).catch(error => {
try {
const reader = new FileReader();
reader.readAsText(error.data, 'utf-8');
reader.onload = () => {
const errData = JSON.parse(reader.result);
message.error(errData.msg);
};
} catch (e) {
message.error('导出错误,请联系管理员');
}
});
}
请求发送
- 请求配置:
url
:请求的URL,这里假设它已经在外部定义好了。baseURL
:通过import.meta.env.VITE_BASE_URL
获取基础URL,这是Vite环境变量的一种用法,用于在开发、测试和生产环境中配置不同的基础URL。data
:reqData
,这是要发送到服务器的数据。responseType
:设置为'blob'
,表示期望服务器响应的数据类型为二进制大对象(Blob),这对于下载文件特别有用。method
:'post'
,表示这是一个POST请求。
- 请求发送:使用axios发送配置好的请求。
响应处理
- 检查响应类型:
- 首先,检查响应的
Content-Type
。如果响应类型是'application/json'
,则抛出异常,因为预期是下载文件而不是接收JSON数据。
- 首先,检查响应的
- 处理文件下载:
- 创建一个Blob对象,将响应数据(
res.data
)作为其内容,并设置MIME类型为'application/vnd.ms-excel'
,这是Excel文件的MIME类型。 - 创建一个隐藏的
<a>
标签,设置其href
属性为Blob对象的URL(通过URL.createObjectURL(blob)
生成),并设置download
属性为文件名(从Content-Disposition
响应头中提取)。 - 将
<a>
标签添加到文档中,模拟点击以触发下载,然后立即从文档中移除该标签。 - 显示“导出成功”的消息。
- 创建一个Blob对象,将响应数据(
错误处理
- 捕获异常:
- 如果在下载过程中发生错误(如服务器返回JSON格式的错误信息),则尝试读取错误响应的
data
部分作为文本。 - 使用
FileReader
读取响应数据,并将其解析为JSON对象。 - 如果解析成功,则显示错误消息(
errData.msg
)。 - 如果在读取或解析过程中发生任何异常,则显示一般的错误消息“导出错误,请联系管理员”。
- 如果在下载过程中发生错误(如服务器返回JSON格式的错误信息),则尝试读取错误响应的
部分代码解析:
主要用于从HTTP响应的Content-Disposition
头部中提取文件名,并设置给一个<a>
标签的download
属性,以便在点击该链接时以下载的形式保存文件。下面是对这几部分代码的详细讲解:
1. 提取Content-Disposition
头部
const contentDisposition = res.headers['content-disposition']; |
这行代码从HTTP响应的头部信息中获取Content-Disposition
字段的值。这个字段通常用于指示响应的内容应该如何显示,对于文件下载来说,它还可能包含文件名。
2. 处理文件名编码
HTTP标准允许Content-Disposition
中的文件名使用不同的编码方式,特别是当文件名包含非ASCII字符时。这里,代码首先尝试找到使用RFC 5987编码的文件名(即filename*=utf-8''
这种格式),如果没有找到(indexOf
返回-1),则回退到较旧的filename=
格式。
let needle = 'filename*=utf-8\'\''; | |
let index = contentDisposition.indexOf(needle); | |
if (index < 0) { | |
needle = 'filename='; | |
index = contentDisposition.indexOf(needle); | |
} |
needle
变量用于存储要搜索的字符串(即文件名前的标识符)。indexOf
方法用于查找needle
在contentDisposition
字符串中的位置。- 如果找不到使用RFC 5987编码的文件名(
index < 0
),则更改needle
为filename=
并再次搜索。
3. 提取文件名
let fileName = contentDisposition.substring(index + needle.length); |
一旦找到了文件名的起始位置(index + needle.length
),就使用substring
方法从该位置开始提取剩余的字符串作为文件名。这包括了文件名本身以及可能存在的任何引号(如果使用的是filename=
格式,则文件名可能会被引号包围)。
4. 解码文件名
fileName = decodeURI(fileName); |
由于文件名可能是经过URI编码的(特别是当包含特殊字符或空格时),因此使用decodeURI
函数对其进行解码,以确保文件名在下载时正确显示。
5. 设置下载链接
link.setAttribute('download', `${fileName}`); | |
document.body.appendChild(link); | |
link.click(); | |
document.body.removeChild(link); |
- 使用
setAttribute
方法将解码后的文件名设置为<a>
标签的download
属性。这告诉浏览器,当用户点击该链接时,应以下载的形式保存内容,并使用指定的文件名。 - 将
<a>
标签添加到文档的body
中,以便可以触发其点击事件。 - 调用
click
方法模拟点击事件,触发下载。 - 下载完成后,立即从文档中移除
<a>
标签,以清理DOM。
这样,当用户通过这段代码触发下载时,浏览器会根据Content-Disposition
头部中的信息(特别是文件名)来保存文件。