前言
本文主要是对文件上传,下载,导出等常用的功能进行了描述,借用了 ant-design-vue(antdv) 组件库中的 upload 来实现(经观察 element-ui ,view-design 同样适用,属性和方法基本一致)。如果遇到类似文件上传开发需求或不太熟悉文件上传的同学,可以放心食用。
上传文件
需求:文件多选,前端进行文件解析名单,计算总数和重复数等,仅支持xlsx、csv格式,上传后需要能够预览或者下载,名单上传给后端的数据要求是字符串文本,而不是文件,即"chinidenaiyou@qq.com\nchinidenaiyou@qq.com\n"。注意:\n用于换行
.xlsx上传
template部分
js
<a-upload-dragger
v-model:fileList="fileList"
accept=".xlsx, .csv" // 支持啥后缀写啥后缀,','用来隔开
:beforeUpload="beforeFileUpload"
@change="handleChange"
>
--框内的,爱放啥放啥 --
</a-upload-dragger>
处理表格信息
先借助 xlsx
第三方依赖库把文件转化成数组
js
// 1.js
const reader = new FileReader();
reader.readAsBinaryString(item.originFileObj);
reader.onload = (e: any) => {
// 拿到文件的内容
const data = e.target.result;
// 以二进制格式读取文件信息,即设置type为binary
【1】const workbook = XLSX.read(data, { type: "binary" });
【2】let sheetData = XLSX.utils
.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]], {
header: ["data"],
})
.map((t: any) => Object.values(t)[0]);
resolve(sheetData);
};
【1】workbook 的部分信息
【2】通过 workbook 拿到所有的工作表,即 workbook.Sheets ,但通常我们拿的都是第一张表,即 workbook.SheetNames[0] ,因此通过 XLSX 把表格转化成了 json 对象,再通过 Object.values 把 json 对象转化成数组,方便后续计算长度数量。
注意:此时我们需要设置表头为"data",如果不进行设置的话,会把第一行默认为表头,即会丢失第一行的数据.
但需要了解的是,onload
是异步任务,因此需要进行 promise
处理。 每个 promise
返回的都是一个数组,因此 promise.all
后的结果是个二维数组,借助 flat
方法进行扁平化。
js
// 2.js
let promises = fileList
.map((item: any) => {
return new Promise((resolve) => {
...上述代码,即1.js
...
...
})
return Promise.all(promises).then((results) => {
let sumArray = results.flat(); // 合并数组
return sumArray;
});
获取到了最终的数组结果,就需要根据产品提出的需求进行相应的计算了
js
const readAndCombineFiles = (fileList: any) => {
... 2.js
}
const handleChange = (info) => {
readAndCombineFiles(info.fileList).then((sumArray: any) => {
let uniqueArray = [...new Set(sumArray)]; // 进行去重处理
receiverCnt.value = sumArray.length; // 名单总人数
uniqueCnt.value = uniqueArray.length; // 去重后的人数
res = uniqueArray.join('\n') // 文本字符串,即最终的数据
}
}
.csv 或者 .xlsx 文件下载
产品要求上传后需要有下载或者预览,所以在上传之前给文件加了url,使其能够下载。
js
const beforeFileUpload = (file: any) => {
// 创建一个文件类型的blob对象
const blob = new Blob([file], { type: file.type });
// 创建一个本地临时的下载链接
file.url = URL.createObjectURL(blob);
return false;
};
html 文件上传
由于要求单选,所以 multiple
属性得设为 false
,但这只是针对选择文件时的控制(即不能按住 ctrl
进行多选),如果选完上一个再接着选择一个文件,展示的文件列表数据仍然是多个,所以需要对文件列表进行控制。
js
const handleChange = (info) => {
// 拿到所有文件列表
let resFileList = [...info.fileList];
// 拿到最后即最新的文件列表
resFileList = resFileList.slice(-1);
// 把它赋值给antdv组件中的文件列表值
fileList.value = resFileList;
// 然后传给后端最新的文件,拿到token(保存的时候需要用到)
const file = resFileList[0]?.originFileObj;
postTemplate(file).then((resp: any) => {
mailTemplate.value = resp.upload_key;
});
}
然后再把文件以formData的形式传给后端
js
export function postTemplate(file: File) {
const formdata = new FormData();
formdata.append("file", file);
return instance.post(`/apis/v1/upload/email`, formdata, {
headers: {
"Content-Type": "multipart/form-data;charset=UTF-8",
},
});
}
.html 预览
同 .csv
预览
下载文件
有上传的功能就必有下载的需求,点击下载后,后端不返回文件流,而是返回字符串信息,例如 html
文档的字符串,表格数据文本的字符串。我们需要做的就是把字符串信息转成相应的 blob
对象,再利用 a
标签创建一个临时的本地地址提供下载。
js
<a-form-item label="邮件内容" class="emailDetai-form-item">
<a @click.prevent="downLoadFile(rowDetail?.mailTemplate, 'template')">
{{str.substring(str.length - 15)}}
<DownloadOutlined/>
</a>
</a-form-item>
.html文件的下载
path: 该路径表示的是存放在阿里云服务器中的文件路径
js
其中url为路径,tp表示下载的类型
if (tp === "template") {
// 获取到相应的字符串信息后进行处理
dowanload({ path: url, tp }).then((resp: any) => {
// 将字符串内容保存为Blob对象
// resp.data为html文档字符串
const blob = new Blob([resp.data], {
type: "text/html;charset=utf-8;",
});
// 创建一个下载链接
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
// 设置文件名,要求保留文件名保留10位数
link.download = url.substring(url.length - 15);
// 将链接添加到DOM中,模拟点击触发下载
document.body.appendChild(link);
link.click();
// 清理临时链接
document.body.removeChild(link);
});
}
.csv文件的下载
js
else {
dowanload({ path: url, tp }).then((resp: any) => {
// 将字符串内容保存为Blob对象
// resp.data为字符串文本,带换行的那种,即'\n'
const blob = new Blob([resp.data], {
type: "text/csv;charset=utf-8;",
});
// 后续代码同上,即一样创建a标签进行下载
});
}
编辑时文件上传的页面
在页面初始的时候发送请求拿到数据,然后把该数据转化成本地的下载链接,对组件库的列表进行赋值,其中 name
为文件名,status
为上传的状态,url
为下载的地址,也就是主动创建了一个初始化的文件列表。
js
dowanload({ path: value, tp: "receive" }).then((resp: any) => {
// 将字符串内容保存为Blob对象
const blob = new Blob([resp.data], {
type: "text/csv;charset=utf-8;",
});
fileList.value = [
{
uid: "-1",
name: str.substring(str.length - 15),
status: "done",
url: URL.createObjectURL(blob),
},
];
});
导出文件
其实和文件下载一样,都是后端直接返回,不过由于是列表数据,我们需要将数据转成表格,再进行 a
标签下载。
js
const exportToExcel = () => {
// 定义中文表头
【1】const header = {
receiver: "接收者",
campaign_id: "活动ID",
unsubscribe_time: "取消订阅时间",
};
【2】const ws = XLSX.utils.json_to_sheet([header, ...resData.value], {
skipHeader: true,
});
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
const blob = XLSX.write(wb, {
bookType: "xlsx", // 工作簿类型
bookSST: true,
type: "array", // 工作簿数据类型,即json_to_sheet中的第一个参数
});
const link = document.createElement("a");
link.href = URL.createObjectURL(new Blob([blob]));
link.download = "取消订阅列表数据.xlsx";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
【1】header 为创建表格的表头,自定义的中文表头代替数据中的 key ,即 key:"自定义的中文表头",如果不设置表头,会默认的以数据中的 key 为表头,并且表头设置了几个就会展示几列数据。
【2】通过 XLSX 库中的 json_to_sheet 方法,把 json 数据转化成表格,第一个参数 [header, ...resData.value] 中数组第一个参数 header 为自定义的表头,而数组第二个参数为后端返回的数据,需要展开。第二个参数中的 skipHeader 为是否跳过默认的表头,如果不设置的话,则会由默认的表头,如下图所示。
book_new 方法为创建一个新的工作簿,book_append_sheet 则是把新的工作簿,数据,以及工作簿的名字添加到 sheet 表格中,然后把有数据的工作簿创建成一个 blob 对象,再利用a标签进行下载。
遇到的问题
取消 andv 组件库中 action 属性默认发送上传请求
由于产品设计要求,因此想单纯的利用了 antdv 的文件上传组件,但发现,不管你设不设置 action
属性,它都会文件上传发送请求,区别在于本地还是你设置的
这里我没有设置 action
属性或者 action
属性为空,它却自发的发送了请求。
关键在于你发送请求还是勉强可以接受,但上传文件后一直报红,就很难受,因为根本不需要上传后的状态
并且,此时使用 antdv 上传组件,会发现 change
事件触发了三次。(上传中、完成、失败都会调用这个函数。)
解决办法: 在文件上传前返回false,停止文件上传的请求
js
const beforeFileUpload = (file) => {
return false;
}