前端导出Excel

image.png

关于导出Excel,我想很多同学都有过接触过,比如:在一个页面点击一个导出按钮,然后就会导出一个Excel表,表里面会展示一些我们需要看的列表数据。这种实现方式呢一般前后端都可以做。但是根据我自己的经历我感觉后端做比较好,为什么呢?因为前端做导出Excel是要考虑很多因素的,比如:导出数据量有多大、导出的时间大概是需要多少时间、导出这么大数据花费这么长时间前端页面会不会挂掉,还有就是用户体验等等。
当然也不是说前端就不能做,这需要看我们项目中的需求是什么样的,如果考虑到的问题都不是什么问题,那么也可以纯前端去做。
因为我在项目中遇到好多导出Excel功能,但每次遇到都得去找,所以呢我就总结一下,做个备份。

1、导入

React项目需要去安装xlsx

npm install xlsx --save // 本次安装的版本是"^0.14.1"

组件中需要导入xlsx

import XLSX from 'xlsx';
原生js
<script src="http://oss.sheetjs.com/js-xlsx/xlsx.full.min.js"></script>
2、导出Excel需要容器及按钮
<a href="" download="导出.xlsx" id="hf">
  <span onClick={this.excelClick.bind(this)} ></span>
</a>
3、导出Excel逻辑代码

Object.assign(target, ...sources)
用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。
target 目标对象。
sources 源对象。
返回值:目标对象。
描述:如果目标对象中的属性具有相同的键,则属性将被源中的属性覆盖。后来的源的属性将类似地覆盖早先的属性。
事例:

var obj = { a: 3, b: 1 };
var copy = Object.assign({}, obj);
console.log(copy); // { a: 3, b: 1 }

array1.reduce(callbackfn[, initialValue])
对数组中的所有元素调用指定的回调函数。该回调函数的返回值为累积结果,并且此返回值在下一次调用该回调函数时作为参数提供。
concat() 用于连接两个或多个数组。

import React from 'react';
import XLSX from 'xlsx';

let tmpDown; // 导出的二进制对象

class ExportExcel extends React.Component {
    constructor() {
        super();
        this.state = {
            dataW: [], // 信息表数据
            dataT: []  // 成绩表数据
        }
    }
    componentDidMount() {
        const dataW = [{
            name: '小明',
            age: 20,
            height: 170,
            weight: 120,
            hobby: '篮球'
        }];
        const dataT = [{
            name: '小红',
            cn: 100,
            en: 105,
            sx: 102,
        }]
        this.setState({ dataW, dataT });
    }
    // 将指定的自然数转换为26进制表示。映射关系:[0-25] -> [A-Z]。
    getCharCol = (n) => {
        let s = '';
        let m = 0;
        while (n > 0) {
            m = n % 26 + 1
            s = String.fromCharCode(m + 64) + s
            n = (n - m) / 26
        }
        return s
    }
    // 导出Excel公共部分
    excelCommon = (data) => {
        let tmpObj = data[0]; // 拿到数据的第一个对象
        data.unshift({}); // 在json内添加一个对象存放表格head
        let keyMap = []; // 定义一个数组存放表格的head
        // 循环json中拿到的对象目的是为了构建head
        for (var k in tmpObj) {
            keyMap.push(k); // 将对象中的key值存放到定义的数组中
            data[0][k] = k; // 将表头的内容以key/value的形式存放到json的空对象中
        };
        let tmpdata = []; // 用来保存转换好的json
        data.map((v, i) => keyMap.map((k, j) => Object.assign({}, {
            v: v[k],
            position: (j > 25 ? this.getCharCol(j) : String.fromCharCode(65 + j)) + (i + 1)
        }))).reduce((prev, next) => prev.concat(next)).forEach((v, i) => tmpdata[v.position] = {
            v: v.v,
        });
        let outputPos = Object.keys(tmpdata); // 设置区域,比如表格从A1到D10
        return {
            outputPos,
            tmpdata
        }
    }
    // 字符串转字符流
    s2ab = (s) => {
        let buf = new ArrayBuffer(s.length);
        let view = new Uint8Array(buf);
        for (let i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
        return buf;
    }
    // 导出Excel
    excelClick = () => {
        const type = "xlsx"; // 导出的格式这里写死为xlsx
        let { dataW, dataT } = this.state;

        // 表格中表头需要显示中文,将数据的key值换成中文
        let dataWCN = [];
        let dataTCN = [];
        if (dataW.length > 0) {
            dataW.map((i) => {
                let dataWObj = {
                    "姓名": i.name,
                    "年龄": i.age || 0, // 导出数据中不能有字段值为null
                    "身高": i.height,
                    "体重": i.weight,
                    "爱好": i.hobby,
                };
                dataWCN.push(dataWObj);
            })
        } else {
            let dataWObj = {
                "姓名": '无',
                "年龄": '无',
                "身高": '无',
                "体重": '无',
                "爱好": '无'
            };
            dataWCN.push(dataWObj);
        };
        if (dataT.length > 0) {
            dataT.map((i) => {
                let dataTObj = {
                    "名称": i.name,
                    "语文": i.cn,
                    '外语': i.en,
                    "数学": i.sx
                };
                dataTCN.push(dataTObj);
            })
        } else {
            let dataTObj = {
                "名称": '无',
                "语文": '无',
                '外语': '无',
                "数学": '无'
            };
            dataTCN.push(dataTObj);
        }

        // 调取公共部分构建数据
        let upDataWCN = this.excelCommon(dataWCN);
        let upDataTCN = this.excelCommon(dataTCN);

        // 生成表格
        let tmpWB = {
            SheetNames: ['信息表', '成绩表'], // 保存的表标题
            Sheets: {
                '信息表': Object.assign({},
                    upDataWCN.tmpdata, // 表格内容
                    {
                        '!ref': upDataWCN.outputPos[0] + ':' + upDataWCN.outputPos[upDataWCN.outputPos.length - 1] // 设置填充区域
                    }),
                '成绩表': Object.assign({},
                    upDataTCN.tmpdata, // 表格内容
                    {
                        '!ref': upDataTCN.outputPos[0] + ':' + upDataTCN.outputPos[upDataTCN.outputPos.length - 1] // 设置填充区域
                    })
            }
        };
        // 创建二进制对象写入转换好的字节流
        tmpDown = new Blob([this.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);
    }
    render() {
        return(
            <a href="" download="导出.xlsx" id="hf">
                <span className="expert_modeler_BO_export" onClick={this.excelClick.bind(this)} ></span>
            </a>
        )
    }
}

export default ExportExcel;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容