FileSaver.js 搭配 js-xlsx 下载 Excel
昨天在写关于下载文章的时候,本来以为关于 Excel 插件 js-xlsx 会很容易,结果一天都没弄明白。
插播
这里先插播一个知识点,说真的我没百度之前也不知道这个知识点,关于 html 中事件处理中的 this 和 event 对象。
一般情况下事件监听 this 指向这个标签,函数的参数 obj ,就是一个事件对象,通常用 event 或 e 来表示。下面的代码输出是没任何问题的。
<input type="file" onchange="changed(this)" />
<script>
let upload = document.getElementById("demo");
function changed(obj){
console.log(obj)
console.log(this)
}
upload.onchange = changed;
</script>
输出结果:
我们现在稍微改一下:
<input type="file" onchange="changed(this)" />
<script>
var a = 10;
function changed(obj){
console.log(obj)
console.log(this.a)
}
</script>
事件拿到行内监听,this 指向 window,并且没有事件对象了。如果还是严选格式的话:
<input type="file" onchange="changed(this)" />
<script>
var a = 10;
function changed(obj){
"use strict"; // 这里是严格模式
console.log(obj)
console.log(this)
}
</script>
this为 undefined 。
总结:函数直接调用时
普通函数内部的 this 分两种情况,严格模式和非严格模式。
- 非严格模式下,this 默认指向全局对象window
- 而严格模式下, this为undefined
插播结束。
一、导入 Excel
我们先来看看如何导入 Excel 表格,下面的代码网上也不知道谁抄谁的了,我也不改动了,反正我参考的链接是:纯前端利用 js-xlsx 实现 Excel 文件导入导出功能示例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script src="./xlsx.full.min.js"></script>
</head>
<body>
<input type="file" onchange="importf(this)" />
<div id="demo"></div>
<script>
/*
FileReader共有4种读取方法:
1.readAsArrayBuffer(file):将文件读取为ArrayBuffer。
2.readAsBinaryString(file):将文件读取为二进制字符串
3.readAsDataURL(file):将文件读取为Data URL
4.readAsText(file, [encoding]):将文件读取为文本,encoding缺省值为'UTF-8'
*/
var wb;//读取完成的数据
var rABS = true; //是否将文件读取为二进制字符串
function importf(obj) {//导入
if(!obj.files) {
return;
}
var f = obj.files[0];
var reader = new FileReader();
reader.onload = function(e) {
var data = e.target.result;
if(rABS) {
wb = XLSX.read(btoa(fixdata(data)), {//手动转化
type: 'base64'
});
} else {
wb = XLSX.read(data, {
type: 'binary'
});
}
//wb.SheetNames[0]是获取Sheets中第一个Sheet的名字
//wb.Sheets[Sheet名]获取第一个Sheet的数据
document.getElementById("demo").innerHTML= JSON.stringify( XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]) );
};
if(rABS) {
reader.readAsArrayBuffer(f);
} else {
reader.readAsBinaryString(f);
}
}
function fixdata(data) { //文件流转BinaryString
var o = "",
l = 0,
w = 10240;
for(; l < data.byteLength / w; ++l) o += String.fromCharCode.apply(null, new Uint8Array(data.slice(l * w, l * w + w)));
o += String.fromCharCode.apply(null, new Uint8Array(data.slice(l * w)));
return o;
}
/*代码说明*/
/*
1.rABS可切换Excel的读取方式
2.btoa字符串转化为base64,浏览器内置了这个功能
3.fixdata函数是文件流转BinaryString
4.XLSX.utils.sheet_to_json会把Excel读取成JOSN形式的数据
*/
</script>
</body>
</html>
我建的测试 Excel 表格名字为 test.xlsx ,文件内容为:
test.xlsx
我们试着上传一下看看效果:
这个数据格式不用我多讲了吧,如果要做表格展示只需要把这个数组放入表格就 OK 了。
下面来看看一些概念和方法:
sheetJS 是前端操作 Excel 以及类似的二维表的最佳选择之一,而 js-xlsx 是它的社区版本。
首先下载 js-xlsx :去 GitHub 上 js-xlsx 找到 dist 复制出 xlsx.full.min.js引入到页面中,里面还有别的版本,我们 full 这个包包含全部功能,其他的例如 xlsx.core.min.js 只包含一些核心功能其实这个就够用了。
在 node 端,使用 npm 安装如下模块:
npm install xlsx --save
然后通过 FileReader 对象读取文件利用 js-xlsx 转成 json 数据。
XLSX
读取 excel 表格主要是通过 XLSX.read(data, {type: type});
方法来实现,返回一个叫WorkBook
的对象,type
主要取值如下:
- base64: 以base64方式读取;(用到了)
- binary: BinaryString格式(byte n is data.charCodeAt(n))(用到了)
- string: UTF8编码的字符串;(用到了)
- buffer: nodejs Buffer;
- array: Uint8Array,8位无符号数组;
- file: 文件的路径(仅nodejs下支持);
workbook (wb) 里面有什么东西呢,我们打印出来看一下:
可以看到,SheetNames 里面保存了所有 sheet 的名字,然后 Sheets 则保存了每个sheet 的具体内容(我们称之为Sheet Object)。每一个 sheet 是通过类似A1这样的键值保存每个单元格的内容,我们称之为单元格对象(Cell Object):
二、导出 Excel 表格
参考:纯前端利用 js-xlsx 实现 Excel 文件导入导出功能示例(2) - 简书
这是个模拟导出的初始模板,源代码有个 bug ,就是点击下载弹出一个下载框,这时如果按 esc或取消按钮,不刷新页面的情况下下次再下载就会没多出一条数据:原因是作者在操作 json 的时候头插一个 json,结束忘了头删了。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script src="./xlsx.full.min.js"></script>
</head>
<body>
<button onclick="downloadExl(jsono)">导出Excel</button>
<!--以下a标签不需要内容-->
<a href="" download="这里是下载的文件名.xlsx" id="hf"></a>
<script>
var jsono = [
{ //测试数据1
"姓名" : "Condor Hero",
"年龄" : 18 ,
"体重" : "60kg"
},
{ //测试数据2
"姓名" : "标标",
"年龄" : 34 ,
"体重" : "70kg"
},
{ //测试数据3
"姓名" : "老冯",
"年龄" : 54 ,
"体重" : "80kg",
"性别" : "女"
}
];
var tmpDown; //导出的二进制对象
function downloadExl(json, type) {
console.log(json.length)
var tmpdata = json[0];
json.unshift({});
var keyMap = []; //获取keys
//keyMap =Object.keys(json[0]);
for (var k in tmpdata) {
keyMap.push(k);
json[0][k] = k;
}
var tmpdata = [];//用来保存转换好的json
json.map((v, i) => keyMap.map((k, j) => Object.assign({}, {
v: v[k],
position: (j > 25 ? getCharCol(j) : String.fromCharCode(65 + j)) + (i + 1)
}))).reduce((prev, next) => prev.concat(next)).forEach((v, i) => tmpdata[v.position] = {
v: v.v
});
var outputPos = Object.keys(tmpdata); //设置区域,比如表格从A1到D10
var tmpWB = {
SheetNames: ['mySheet'], //保存的表标题
Sheets: {
'mySheet': Object.assign({},
tmpdata, //内容
{
'!ref': outputPos[0] + ':' + outputPos[outputPos.length - 1] //设置填充区域
})
}
};
tmpDown = new Blob([s2ab(XLSX.write(tmpWB,
{bookType: (type == undefined ? 'xlsx':type),bookSST: false, type: 'binary'}
//这里的数据是用来定义导出的格式类型
))], {
type: ""
});
//创建二进制对象写入转换好的字节流
var href = URL.createObjectURL(tmpDown); //创建对象超链接
document.getElementById("hf").href = href; //绑定a标签
document.getElementById("hf").click(); //模拟点击实现下载
setTimeout(function() { //延时释放
URL.revokeObjectURL(tmpDown); //用URL.revokeObjectURL()来释放这个object URL
}, 100);
// 函数开始插入的{}点击完成要删掉,不然每次点击都会插入一条数据
json.shift();
}
function s2ab(s) { //字符串转字符流
var buf = new ArrayBuffer(s.length);
var view = new Uint8Array(buf);
for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
return buf;
}
// 将指定的自然数转换为26进制表示。映射关系:[0-25] -> [A-Z]。
function getCharCol(n) {
let temCol = '',
s = '',
m = 0
while (n > 0) {
m = n % 26 + 1
s = String.fromCharCode(m + 64) + s
n = (n - m) / 26
}
return s
}
</script>
</body>
</html>
仔细看上面实现是不是有点麻烦,所以原作者又优化了一下,顺便也把我发现的 bug 给解决了:
其中下载除了上面给出的 a 标签 download 下载法,还给出了利用** file-saver ** 进行下载的方法。两者选其一即可。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<button onclick="downloadExl(jsono)">导出 Excel</button>
<script src="./xlsx.full.min.js"></script>
<!--调用FileSaver的saveAs函数也可以替代 a 标签实现文件下载-->
<!-- <script src = "./FileSaver.min.js"></script> -->
<script>
//如果使用 FileSaver.js 就不要同时使用以下函数
function saveAs(obj, fileName) {
//当然可以自定义简单的下载文件实现方式
var tmpa = document.createElement("a");
tmpa.download = fileName || "下载";
tmpa.href = URL.createObjectURL(obj);
//绑定a标签
tmpa.click();
//模拟点击实现下载
setTimeout(function () {
//延时释放
URL.revokeObjectURL(obj);
//用URL.revokeObjectURL()来释放这个object URL
}, 100);
}
var jsono = [
{ //测试数据1
"姓名" : "Condor Hero",
"年龄" : 18 ,
"体重" : "60kg"
},
{ //测试数据2
"姓名" : "标标",
"年龄" : 34 ,
"体重" : "70kg"
},
{ //测试数据3
"姓名" : "老冯",
"年龄" : 54 ,
"体重" : "80kg",
"性别" : "女"
}
];
const wopts = { bookType: 'xlsx', bookSST: false, type: 'binary' };//这里的数据是用来定义导出的格式类型
// const wopts = { bookType: 'csv', bookSST: false, type: 'binary' };//ods格式
// const wopts = { bookType: 'ods', bookSST: false, type: 'binary' };//ods格式
// const wopts = { bookType: 'xlsb', bookSST: false, type: 'binary' };//xlsb格式
// const wopts = { bookType: 'fods', bookSST: false, type: 'binary' };//fods格式
// const wopts = { bookType: 'biff2', bookSST: false, type: 'binary' };//biff2格式
// const wopts = { bookType: 'biff2', bookSST: false, type: 'binary' };//xlsx格式
function downloadExl(data, type) {
const wb = { SheetNames: ['Sheet1'], Sheets: {}, Props: {} };
wb.Sheets['Sheet1'] = XLSX.utils.json_to_sheet(data);
//通过json_to_sheet转成单页(Sheet)数据
saveAs(new Blob([s2ab(XLSX.write(wb, wopts))], { type: "application/octet-stream" }), "这里是下载的文件名" + '.' + (wopts.bookType=="biff2"?"xls":wopts.bookType));
}
function s2ab(s) {
if (typeof ArrayBuffer !== 'undefined') {
var buf = new ArrayBuffer(s.length);
var view = new Uint8Array(buf);
for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
return buf;
} else {
var buf = new Array(s.length);
for (var i = 0; i != s.length; ++i) buf[i] = s.charCodeAt(i) & 0xFF;
return buf;
}
}
</script>
</body>
</html>
两个代码实现的效果是一样的,如下:
这里先明确一点,如果你的业务需求对导出文件的格式没有什么要求,不建议导出成xlsx格式的,直接导出成 csv 的就好了,真的会简单很多。创建一个 a 标签,写上data:text/csv;charset=utf-8
头,再把数据塞进去,encodeURI(csvContent)
一下就好了,详情就不展开了,大家可以借鉴这个stackoverflow回答。
纯前端利用 js-xlsx 实现 Excel 文件导入导出功能示例 - 简书 https://www.jianshu.com/p/74d405940305
如何使用JavaScript实现纯前端读取和导出excel文件 - 我是小茗同学 - 博客园 https://www.cnblogs.com/liuxianan/p/js-excel.html#¥ネᄅヤᄄ¥ᆴリ₩ヨᄍ¥ᄋᆬ¥ナᄋᄆᄏヤ゚₩ネミ