携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第7天,点击查看活动详情
名称:useExport
业务需求:
点击一个导出按钮,导出选中的数据或者默认全部数据成为excel下载下来
封装原因:
业务需求中充斥着大量臃肿的代码,以下为项目上的逻辑功能块
Request.$httpXMLInstance({
url: Service.parse("exportProjectFileInfo"),
method: "post",
headers: {
"Content-Type": "application/json",
},
data,
}).then((res) => {
let fileName = "项目文件" + DateHelper.currentDate() + ".xlsx";
let fileType = "application/vnd.ms-excel;charset=utf-8";
var blob = new Blob([res], {
type: fileType,
});
if ("download" in document.createElement("a")) {
var downloadElement = document.createElement("a");
var href = window.URL.createObjectURL(blob); // 创建下载的链接
downloadElement.href = href;
downloadElement.download = fileName; // 下载后文件名
document.body.appendChild(downloadElement);
downloadElement.click(); // 点击下载
document.body.removeChild(downloadElement); // 下载完成移除元素
window.URL.revokeObjectURL(href); // 释放掉blob对象
} else {
// IE10+下载
navigator.msSaveBlob(blob, fileName);
}
实现一个导出的功能,逻辑写这么多,麻烦,思考下,我们能不能这么干:
useXXX('接口别名xxx', data)
是不是很爽呢?
Hooks设计
那我们开始设计
设计思路:
1、请求入参需要兼容字符串和对象方式,方便业务兼容调用
2、接口返回的二进制文件流,需要转换成浏览器的Blob,然后使用a标签下载
参数逻辑处理
进行了多次判断,如果是对象从对象的key中获取
String
- 接口别名
Object
- name 接口别名
- headers 请求头信息
- fileName 文件名称
- fileType 文件类型
if(typeof options == 'object' ){
name = options.name ?? ''
options.headers && (headers = { ...exportheaders, ...options.headers })
//文件名:判断是否为函数
if (typeof options.fileName == 'function') {
fileName = options.fileName && options.fileName()
} else {
fileName = options.fileName || getDefaultName()
}
// 文件类型
fileType = options?.fileType
}else{
name = options
fileName = getDefaultName()
}
/**
* 获取默认文件名
* @returns 文件名称 xxx.
*/
const getDefaultName = () => {
// fileName = getHashCode(randomString(5)) + '.xlsx'
return DateHelper.currentDate() + '.xlsx'
}
默认文件名
调用 getDefaultName方法,考虑了2种方式
- 生成5位随机数,随机数生成HashCode
- 根据当前日期生成,代码都已提供,目前采用当前日期方式
返回二进制文件流-下载
// 返回二进制文件流
const result = await Service.useHttpXML({ name, headers }, data)
// 文件流转换后进行下载
download(formatToBlob(result, fileType), fileName)
Blob 解释
- Blob: 前端的一个专门用于支持文件操作的二进制对象
- ArrayBuffer:前端的一个通用的二进制缓冲区,类似数组,但在API和特性上却有诸多不同
- Buffer:Node.js提供的一个二进制缓冲区,常用来处理I/O操作
关于前端文件流的参考文件: www.cnblogs.com/penghuwan/p…
代码实践:
import { Service } from '@basic-library'
import { DateHelper } from '@basic-utils';
const exportheaders = {
"Content-Type": "application/json"
}
/**
* 格式化成为 Blob
* @param {*} result
* @param {*} result
* @returns
*/
const formatToBlob = (result, fileType) => {
const type = fileType || 'application/vnd.ms-excel;charset=utf-8'
return new Blob([result], { type })
}
/**
* 文件流-下载
* @param {*} blob 文件流
* @param {*} fileName 文件名称
*/
const download = (blob, fileName) => {
if ('download' in document.createElement('a')) {
var downloadElement = document.createElement('a')
var href = window.URL.createObjectURL(blob) // 创建下载的链接
downloadElement.href = href
downloadElement.download = fileName
// 下载后文件名
document.body.appendChild(downloadElement)
// 点击下载
downloadElement.click()
// 下载完成移除元素
document.body.removeChild(downloadElement)
// 释放掉blob对象
window.URL.revokeObjectURL(href)
} else { // IE10+下载
navigator.msSaveBlob(blob, fileName)
}
}
/**
* 生成随机数
* @param {*} e 位数
* @returns
*/
const randomString =(e)=>{
e = e || 32;
var t = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678",
a = t.length,
n = "";
for (i = 0; i < e; i++) n += t.charAt(Math.floor(Math.random() * a));
return n
}
/**
* 获取字符串的哈希值
* @param {String} str
* @param {Boolean} caseSensitive
* @return {Number} hashCode
*/
const getHashCode =(str,caseSensitive)=>{
if(!caseSensitive){
str = str.toLowerCase();
}
// 1315423911=b'1001110011001111100011010100111'
var hash = 1315423911,i,ch;
for (i = str.length - 1; i >= 0; i--) {
ch = str.charCodeAt(i);
hash ^= ((hash << 5) + ch + (hash >> 2));
}
return (hash & 0x7FFFFFFF);
}
/**
* 获取默认文件名
* @returns 文件名称 xxx.
*/
const getDefaultName = () => {
// fileName = getHashCode(randomString(5)) + '.xlsx'
return DateHelper.currentDate() + '.xlsx'
}
/**
* 文件下载
* @param {*} options String|Object
* --String
* 接口别名
* --Object
* name 接口别名
* headers 请求头信息
* fileName 文件名称
* fileType 文件类型
* @param {*} data 接口传递参数
*/
const useExport = async (options, data) => {
// 请求头信息
let headers = ""
// 接口别名
let name = ""
// 默认'application/vnd.ms-excel;charset=utf-8'
let fileType = ""
if(typeof options == 'object' ){
name = options.name ?? ''
options.headers && (headers = { ...exportheaders, ...options.headers })
//文件名:判断是否为函数
if (typeof options.fileName == 'function') {
fileName = options.fileName && options.fileName()
} else {
fileName = options.fileName || getDefaultName()
}
// 文件类型
fileType = options?.fileType
}else{
name = options
fileName = getDefaultName()
}
// 返回二进制文件流
const result = await Service.useHttpXML({ name, headers }, data)
// 文件流转换后进行下载
download(formatToBlob(result, fileType), fileName)
}
export default useExport
业务使用:
useExport('接口别名xxx', data)
useExport({
name: '接口别名xxx',
fileName: '岗位标准.xlsx',
}, data)
useExport({
name: '接口别名xxx',
fileName: () => '岗位标准' + DateHelper.currentDate() + '.xlsx',
}, data)
封装,完毕~~!还没测试哦....