Angular前端xlsx-style导出Excel

2020-10-26  本文已影响0人  我爱福尔摩斯呀

项目需要在前端导出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
上一篇下一篇

猜你喜欢

热点阅读