nodejs使用xlsx和xlsx-style导出Excel文件

nodejs 导出 Excel


安装依赖

  • npm install xlsx --save
  • npm install xlsx-style --save
  • npm install fs --save

修改文件

  • 在导出 xlsx 文件中 表格展示内容样式错误 请修改下列文件部分内容。

  • 在 node_modules/xlsx-style下的xlsx.js文件中,把 write_ws_xml_data()替换成下列这个,导出excel没有样式,直接修改方法。

function write_ws_xml_data(ws, opts, idx, wb) {
 var o = [], r = [], range = safe_decode_range(ws['!ref']), cell, ref, rr = "", cols = [], R, C,rows = ws['!rows'];
 for(C = range.s.c; C <= range.e.c; ++C) cols[C] = encode_col(C);
 for(R = range.s.r; R <= range.e.r; ++R) {
   r = [];
   rr = encode_row(R);
   for(C = range.s.c; C <= range.e.c; ++C) {
     ref = cols[C] + rr;
     if(ws[ref] === undefined) continue;
     if((cell = write_ws_xml_cell(ws[ref], ref, ws, opts, idx, wb)) != null) r.push(cell);
   }
   if(r.length > 0){
     params = ({r:rr});
     if(rows && rows[R]) {
       row = rows[R];
       if(row.hidden) params.hidden = 1;
       height = -1;
       if (row.hpx) height = row.hpx;
       else if (row.hpt) height = row.hpt;
       if (height > -1) { params.ht = height; params.customHeight = 1; }
       if (row.level) { params.outlineLevel = row.level; }
     }
     o[o.length] = (writextag('row', r.join(""), params));
   }
 }
 if(rows) for(; R < rows.length; ++R) {
   if(rows && rows[R]) {
     params = ({r:R+1});
     row = rows[R];
     if(row.hidden) params.hidden = 1;
     height = -1;
     if (row.hpx) height = row.hpx;
     else if (row.hpt) height = row.hpt;
     if (height > -1) { params.ht = height; params.customHeight = 1; }
     if (row.level) { params.outlineLevel = row.level; }
     o[o.length] = (writextag('row', "", params));
   }
 }
 return o.join("");
}

使用 导出Excel 功能

  • ExcelData :[{},{},{}] —— 需要自定义单元格样式 或者 合并单元 使用下列格式
    let ExcelData = [{
    value : String // 单元格内容
    style :Object //单元格样式
    colSpan : Number //以当前单元格为起点 合并列的单元格
    rowSpan : Number //以当前单元格为起点 合并行的单元格
    }];
  • workSheet :需要自定义行高或者列表 合并单元等等
    let workSheet = {
    "!merges" : [ {
    s: { //s为开始 c: 1,//开始列 r: 0//可以看成开始行,实际是取值范围 },
    e: { // e结束 c: 4,//结束列 r: 0//结束行 }
    }],
    "!cols" : [{wch:10},{wch:10}],
    "!rows" : [{hpx:20},{hpx:20}],
    };
// 使用 导出Excel 功能
const excel = async () => {
    let ExcelHeaders = ['标题1','标题2','标题3','标题4'];
    let ExcelData = [
        [1,11,111,1111],
        [2,22,222,2222],
        [3,33,333,3333],
        [4,44,444,4444],
    ];

    // 创建工作簿
    const buffer = await new ExportExcel()
        .createWorkBook(
            [
                { ExcelHeaders,ExcelData, sheetName: '车辆入场通知单', workSheet : {} }, // sheet1
                // { ExcelData, sheetName: '车辆入场通知单', workSheet : {} }, // sheet2
            ]
        );
    // const filename = `${DateUtils.format(new Date(), 'yyyyMMdd')}.xlsx`;
    // await fs.writeFileSync(`./${filename}`, buffer, { flag: 'w' });
}

功能实现详细代码

import * as xlsx from 'xlsx';
import * as XLSX_STYLE from 'xlsx-style';
import * as fs from 'fs';


// 导出并生成Excel
class ExportExcel {

    /**
     * 边框样式
     */
    BorderStyle = {
        border: {
            color: {auto: 1},
            top: {style: 'thin'},
            bottom: {style: 'thin'},
            left: {style: 'thin'},
            right: {style: 'thin'}
        }
    };

    /** 默认样式 */
    defaultStyle = {
        ...this.BorderStyle,
        alignment: {
            /// 自动换行
            wrapText: true,
            // 内容在单元格  居中
            horizontal: "center",
            vertical: "center",
        },
        font: {
            name: "宋体",
            sz: 12, // 字体大小
            color: {auto: 1},
            bgColor : '#fff'
        },
    };

    /**
     * 默认标题样式
     */
    defaultTitleStyle = {
        ...this.defaultStyle,
        font: {
            name: "宋体",
            sz: 12,
            color: {auto: 1},
            bold : true,  // 字体加粗
        },
    };

    /**
     * 将对象数组按指定属性顺序转为二维数组
     * @param objects 对象数组
     * @param props 对象的属性列表,属性可写为数组,数组只包含两个值,
     *          第一个值为对象属性名,第二个值为过滤函数
     */
    static objectsToRows(objects, props) {
        const rows = [];
        for (let i = 0; i < objects.length; i++) {
            const row = [];
            const item = objects[i];
            for (const key of props) {
                if (key instanceof Array) {
                    row.push(key[1](item[key[0]]));
                } else {
                    row.push(item[key]);
                }
            }
            
            rows.push(row);
        }
        return rows;
    }

    /**
     * 设置显示数据样式显示
     * @param RowData  数据
     * @param isBold   显示内容是否加粗
     * @returns {*}
     */
    setRowData(RowData = [],isBold = false,index = 0){
        for (let i = 0; i < RowData.length; i++) {
            if(RowData[i] instanceof Array){
                this.setRowData(RowData[i],isBold,(i + index));
            }
            else if(RowData[i] instanceof Object){
                if(!this.merges){
                    this.merges = [];
                }
                let {value = '', style = {}, colSpan = 0, rowSpan = 0} = RowData[i];
                RowData[i] = {v: value, s: {...this.defaultStyle, ...style}};

                if(rowSpan > 0){
                    this.merges.push({
                        s: {c: i,r: index},
                        e: {c: i,r: (index + rowSpan) }
                    })
                }
                if(rowSpan === 0 && colSpan > 0){
                    this.merges.push({
                        s: {c: i,r: index},
                        e: {c: (colSpan + i),r: index }
                    })
                }
            }
            else {
                if(isBold){
                    RowData[i] = {v: RowData[i] || '', s: this.defaultTitleStyle};
                }else {
                    RowData[i] = {v: RowData[i] || '', s: this.defaultStyle};
                }
            }
        }
        return RowData;
    }


    /** 
     * 
     ExcelData = [
     {
         value : String 单元格内容
         style :Object   单元格样式
         colSpan : Number  以当前单元格为起点  合并列的单元格
         rowSpan : Number  以当前单元格为起点  合并行的单元格
       }
     ]
     workSheet = {
     "!merges" : [ {
       s: { //s为开始 c: 1,//开始列 r: 0//可以看成开始行,实际是取值范围 },
       e: { // e结束 c: 4,//结束列 r: 0//结束行 }
       }];
   }
     */
    createSheet = async ({ExcelHeaders = [], ExcelData = [] , workSheet = {}}) => {
        const data = [];
        if(ExcelHeaders.length){
            const headers = await this.setRowData(ExcelHeaders, true );
            data.push(headers);
        }

        // 表格数据内容
        let rowData = await this.setRowData(ExcelData, false, data.length );
        data.push(...rowData);
        let sheet = xlsx.utils.aoa_to_sheet(data, {cellDates: true, cellStyles: true});

        // 表单合并情况
        sheet["!merges"] = this.merges || [];

        // 表格每列显示的列宽
        let cols = [];
        if(workSheet['cols']){
            for(let i = 0; i < workSheet['cols'].length; i++){
                cols.push({wch: workSheet['cols'][i] || 10})
            }
            workSheet['!cols'] = cols;
        }else {
            for(let i = 0; i < ExcelHeaders.length; i++){
                cols.push({wch: ExcelHeaders[i] || 10})
            }
        }
        sheet["!cols"] = cols;

        // 表格显示  行高
        let rows = [];
        if(ExcelHeaders.length) {
            rows.push({hpx: 30});
        }
        for(let i = 0; i < ExcelData.length; i++){
            rows.push({hpx: 24})
        }
        sheet['!rows'] = rows;

        return {...sheet, ...workSheet};
    };

    /**
     * 创建 Excel 工作簿
     * @param data Array 可创建一个或者多个 sheet
     * @returns {Promise<*>}
     */
    createWorkBook = async (data = []) => {
        //  创建一个工作簿
        const workBook = xlsx.utils.book_new();
        for(let i = 0; i < data.length; i++){
            let sheet = await this.createSheet(data[i]);
            if(sheet){
                let sheetName = `sheet${i}`;
                if(data[i].sheetName){
                    sheetName = data[i].sheetName;
                }
                xlsx.utils.book_append_sheet(workBook, sheet, sheetName);
            }
        }
        // 返回写入数据  buffer
        return XLSX_STYLE.write(workBook, {type: 'buffer'});
    };

}

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