Angular前端xlsx-style导出Excel

项目需要在前端导出Excel,数据导出很容易,但是导出表格的样式设置折腾了好久终于搞定。记录如下:
1、在Angular中安装xlsx和xlsx-style

"file-saver": "^1.3.3",
"xlsx": "^0.10.5",
"xlsx-style": "^0.8.13",

2、实现代码

//表格样式
defaultCellStyle = {
    font: {
      name: "宋体", sz: 9, color: { auto: 1 } , bold: true
    },
    border: {
      color: { auto: 1 },
      top: { style: 'thin' },
      bottom: { style: 'thin'},
      left: { style: 'thin' },
      right: { style: 'thin' }
    },
    alignment: {
      /// 自动换行
      wrapText: 1,
        // 居中
      horizontal: "center",
      vertical: "center",
      indent: 0
    }
  };

  titleStyle = {
    font:{name:"宋体",sz:22,bold:true},
    alignment: {
      wrapText: 1,
      horizontal: "center",
      vertical: "center",
      indent: 0
  }};

  subTitleStyle = {
    font:{name:"宋体",sz:9},
    alignment: {
      wrapText: 1,
      horizontal: "right",
      vertical: "center",
      indent: 0
    }
  };

  commonStyle = {
    font:{name:"宋体",sz:9},
    border: {
      color: { auto: 1 },
      top: { style: 'thin' },
      bottom: { style: 'thin'},
      left: { style: 'thin' },
      right: { style: 'thin' }
    },
    alignment: {
      wrapText: 1,
      horizontal: "center",
      vertical: "center",
      indent: 0
  }};
//导出文件
exportFilterExcel() {
    // 表头信息 要合并的字段用null代替
    let aoa = [
        ['内江市2020年1-4月重点项目完成情况表(续建)'],
        ['单位:个、亿元、亩'],
        ['序号', '项目名称', '建设地址', '牵头领导','责任单位','业主单位','开工年月','竣工年月','总投资','建设规模及内容','预计2020年底累计完成投资','2021年',null,'2020年1-4月',null,null,'已落实土地','已落实资金','存在的问题','备注'],
        [null, null, null, null, null, null, null, null, null, null, null, '预计投资', '计划达到的形象进度', '累计完成投资', '占年计划%', '截至本月达到的形象进度',null,null,null,null],
    ];
      
    let sheet = this.sheet_from_array_of_arrays(aoa);
      
      // 表头合并: r: row 行;c:column 列
    const mergeTitle = [
      { s: { c: 0, r: 0 }, e: { c: 19, r: 0 } },
      { s: { c: 0, r: 1 }, e: { c: 19, r: 1 } },
      { s: { c: 0, r: 2 }, e: { c: 0, r: 3 } },
      { s: { c: 1, r: 2 }, e: { c: 1, r: 3 } },
      { s: { c: 2, r: 2 }, e: { c: 2, r: 3 } },
      { s: { c: 3, r: 2 }, e: { c: 3, r: 3 } },
      { s: { c: 4, r: 2 }, e: { c: 4, r: 3 } },
      { s: { c: 5, r: 2 }, e: { c: 5, r: 3 } },
      { s: { c: 6, r: 2 }, e: { c: 6, r: 3 } },
      { s: { c: 7, r: 2 }, e: { c: 7, r: 3 } },
      { s: { c: 8, r: 2 }, e: { c: 8, r: 3 } },
      { s: { c: 9, r: 2 }, e: { c: 9, r: 3 } },
      { s: { c: 10, r: 2 }, e: { c: 10, r: 3 } },
      { s: { c: 11, r: 2 }, e: { c: 12, r: 2 } },
      { s: { c: 13, r: 2 }, e: { c: 15, r: 2 } },
      { s: { c: 16, r: 2 }, e: { c: 16, r: 3 } },
      { s: { c: 17, r: 2 }, e: { c: 17, r: 3 } },
      { s: { c: 18, r: 2 }, e: { c: 18, r: 3 } },
      { s: { c: 19, r: 2 }, e: { c: 19, r: 3 } }
    ]
    sheet['!merges'] = mergeTitle;
    // 冻结前6行和第一列,右下可以滑动
    sheet["!freeze"] = {
      xSplit: "1",
      ySplit: "6",
      topLeftCell: "B7",
      activePane: "bottomRight",
      state: "frozen",
    }
    sheet["!margins"] = { left: 1.0, right: 1.0, top: 1.0, bottom: 1.0, header: 0.5, footer: 0.5 }
    // 列宽 使用的不是像素值
    const sheetCols = [
      { wch: 8} ,
      { wch: 24 }, 
      { wch: 20 }, 
      { wch: 9 }, 
      { wch: 8 }, 
      { wch: 18 }, 
      { wch: 15 }, 
      { wch: 9 }, 
      { wch: 9 }, 
      { wch: 12 },
      { wch: 9 }, 
      { wch: 9 },
      { wch: 10 }, 
      { wch: 10 },
      { wch: 10 }, 
      { wch: 27 },
    ];
    sheet['!cols'] = sheetCols;
    this.addRangeBorder(mergeTitle, sheet);
    const wbBlob = this.sheet2blob(sheet, '续建')
    // 保存下载
    FileSaver.saveAs(wbBlob, 'd.xlsx')
  }

//生成表头
sheet_from_array_of_arrays(data) {
    const ws = {};
    const range = {s: {c:10000000, r:10000000}, e: {c:0, r:0 }};
      for(let R = 0; R < data.length; R++) {
      for(let C = 0; C < data[R].length; C++) {
        if(range.s.r > R) range.s.r = R;
        if(range.s.c > C) range.s.c = C;
        if(range.e.r < R) range.e.r = R;
        if(range.e.c < C) range.e.c = C;
        /// 这里生成cell的时候,使用上面定义的默认样式
        let cell;
        switch (R) {
          case 0:
            cell = {v: data[R][C], s: this.titleStyle};
            break;
          case 1:
            cell = {v: data[R][C], s: this.subTitleStyle};
            break;
          case 2: case 3:
            cell = {v: data[R][C], s: this.defaultCellStyle};
            break;
          default:
            cell = {v: data[R][C], s: this.commonStyle};
            break;
        }
        if(cell['v'] == null) continue;
        const cell_ref = XLSX.utils.encode_cell({c:C,r:R});
  
        /* TEST: proper cell types and value handling */
        if(typeof cell['v'] === 'number') {
          cell['t'] = 'n';
        }
        else if(typeof cell['v'] === 'boolean') cell['t']= 'b';
        else if(cell['v'] instanceof Date) {
          cell['t'] = 'n'; 
          cell['z'] = XLSX.SSF._table[19];
          cell['v'] = cell.v;
        }
        else cell['t'] = 's';
        ws[cell_ref] = cell;
      }
    }
  
    /* TEST: proper range */
    if(range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range);
    return ws;
  }

  sheet2blob(sheet, sheetName) {
    sheetName = sheetName || 'sheet1';
    const workbook = {
      SheetNames: [sheetName],
      Sheets: {}
    };
    workbook.Sheets[sheetName] = sheet
  
    window.console.log(workbook)
    // 生成excel的配置项
    const wopts = {
      bookType: 'xlsx', // 要生成的文件类型
      bookSST: false, // 是否生成Shared String Table,官方解释是,如果开启生成速度会下降,但在低版本IOS设备上有更好的兼容性
      type: 'binary'
    };
 
    const wbout = XLSX.write(workbook, wopts, { defaultCellStyle: this.commonStyle });
    const blob = new Blob([s2ab(wbout)], {type: "application/octet-stream"});
    // 字符串转ArrayBuffer
    function s2ab(s) {
      const buf = new ArrayBuffer(s.length);
      const view = new Uint8Array(buf);
      for (let i=0; i!==s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
      return buf;
    }
    return blob;
  }
//合并的单元格边框无效,所以需要重新进行设置
  addRangeBorder(range,ws){
    let arr = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"];
    range.forEach(item=>{
      let startColNumber = Number(item.s.r), endColNumber = Number(item.e.r);
      let startRowNumber = Number(item.s.c), endRowNumber = Number(item.e.c);
      const test = ws[arr[startRowNumber] + (startColNumber + 1)];
      for(let col = startColNumber ; col <= endColNumber ; col++)
      {
        for(let row = startRowNumber; row <= endRowNumber ; row++)
        {
          ws[arr[row] + (col + 1)] = test;
        }
      }
    })
    return ws;
  }

3、运行后报错:

Can't resolve 'fs' in '/node_modules/xlsx-style/ods.js'

解决方法:在package.json中加入

"browser": {
    "fs": false
}

4、运行结果


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