vue3笔记(19-1)文件下载

文件下载方法很多,开发过程也走了几个坑,特别是部署到测试环境后,出现了本地开发不曾出现过的问题,本篇记录一下各种写法和实际问题解决。

文件格式

后端读取文件后以流的形式传给我,从前端的角度理解就是Blob形式。
打开浏览器调试工具,resonpse需要携带这样的头:
response
注意在请求接口里面需要添加一个responseType

1
2
3
4
5
6
7
8
9
10
export function downloadFile(url: string, json: object) {
return axios({
url,
method: "post",
data: json,
responseType: "blob",
})
.then((res) => res)
.catch((error) => error);
}

使用前端直接下载

在表格row上绑定导出方法,并将数据传入。

1
2
3
4
5
6
7
import Export from "../../utils/export";
const handleExport = (index, row) => {
let data = [];
data[0] = JSON.parse(JSON.stringify(toRaw(row)));
console.log("export", data);
Export(data, fields, "导出名字");
}

export.ts中提前配置好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import fs from "file-saver";
import * as XLSX from "xlsx";
export default (json, fields, filename = "测试数据.xlsx") => {
json.forEach((item) => {
for (const i in item) {
if (fields.hasOwnProperty(i)) {
item[fields[i]] = item[i];
}
delete item[i]; //删除原先的对象属性
}
});

const sheetName = filename; //excel的文件名称
const wb = XLSX.utils.book_new(); //工作簿对象包含一SheetNames数组,以及一个表对象映射表名称到表对象。XLSX.utils.book_new实用函数创建一个新的工作簿对象。
const ws = XLSX.utils.json_to_sheet(json, { header: Object.values(fields) }); //将JS对象数组转换为工作表。
wb.SheetNames.push(sheetName);
wb.Sheets[sheetName] = ws;
const defaultCellStyle = {
font: { name: "Verdana", sz: 13, color: "FF00FF88" },
fill: { fgColor: { rgb: "FFFFAA00" } },
}; //设置表格的样式
const wopts = {
bookType: "xlsx",
bookSST: false,
type: "binary",
cellStyles: true,
defaultCellStyle: defaultCellStyle,
showGridLines: false,
}; //写入的样式
const wbout = XLSX.write(wb, wopts);
const blob = new Blob([s2ab(wbout)], { type: "application/octet-stream" });
fs.saveAs(blob, filename + ".xlsx");
};
const s2ab = (s) => {
if (typeof ArrayBuffer !== "undefined") {
const buf = new ArrayBuffer(s.length);
const view = new Uint8Array(buf);
for (let i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xff;
return buf;
} else {
const buf = new Array(s.length);
for (let i = 0; i != s.length; ++i) buf[i] = s.charCodeAt(i) & 0xff;
return buf;
}
};

使用后端接口获取Blob数据下载

文件使用Blob流表示。

1
2
3
4
5
6
7
8
9
10
11
12
const handleExport = (index, row) => {
const ids = [];
ids.push(row.id);
exportBatchStandard({ ids: ids })
.then((res) => {
fileDownloadFun(res);
})
.catch((error) => {
ElMessage.error("导出错误!");
console.log(error)
});
};

index.ts中两种下载方式
方法一:使用fileDownload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 下载文件
export const fileDownloadFun = (res: any, name?: string) => {
if (name) {
fileDownload(res.data, name)
} else {
let fileName = ''
let contentDisposition = ''
if (res.headers['content-disposition']) contentDisposition = res.headers['content-disposition']
if (res.headers['Content-Disposition']) contentDisposition = res.headers['Content-Disposition']
const result = contentDisposition.split("filename*=utf-8''")[1]
console.log('result=', result)
if (result == undefined) {
// fileName = '导出文件.xlsx'
ElMessage.error('下载文件失败')
} else {
fileName = decodeURIComponent(result)
console.log('download=', res.data, 'file=', fileName)
fileDownload(res.data, fileName)
}
}
}

方法二:使用标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
export const download = (res: any, name?: string) => {
const data = res.data;
if (!res.data) {
return;
}
// 设置下载文件名称,使用正则取出名称
const pat = new RegExp("(?<=filename=).*");
let contentDisposition = "";
//浏览器问题可能会出现 content-disposition 匹配不到
if (res.headers["content-disposition"])
contentDisposition = res.headers["content-disposition"];
if (res.headers["Content-Disposition"])
contentDisposition = res.headers["Content-Disposition"];
const result = pat.exec(contentDisposition);
let fileName = result && result[0];
if (fileName == undefined) {
fileName = name;
} else {
fileName = decodeURIComponent(fileName);
}
const url = window.URL.createObjectURL(new Blob([data]));
const link = document.createElement("a");
link.style.display = "none";
link.href = url;
link.setAttribute("download", fileName);
document.body.appendChild(link);
link.click();
};

注意:

  1. 一定要让后端添加暴露header的字段,不然前端可能获取不到content-disposition(本地环境代理没问题,提交到线上环境出现了问题)
  2. 约定好formdata格式,前端可能需要添加hearder(根据框架封装程度而定)
    1
    2
    response.setHeader("Access-Control-Expose-Headers", "Content-Disposition")
    response.setHeader("Content-Disposition", ...)

vue3笔记(19-1)文件下载
https://guoningyan.com/2023/04/04/vue3笔记(19-1)文件下载/
作者
Ningyan Guo
发布于
2023年4月4日
许可协议