基础前端VUE

FileSaver.js 搭配 js-xlsx 下载 Excel

2019-11-03  本文已影响0人  CondorHero

昨天在写关于下载文章的时候,本来以为关于 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 分两种情况,严格模式和非严格模式。

插播结束。

一、导入 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主要取值如下:

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#¥ネᄅ￧ヤᄄ¥ᆴリ₩ヨᄍ¥ᄋᆬ¥ナᄋ￧ᄆᄏ￧ヤ゚₩ネミ

上一篇下一篇

猜你喜欢

热点阅读