zoukankan      html  css  js  c++  java
  • 前端导出可修改样式的Excel表格

    最近实现了一个纯前端下载Excel,并可以修改Excel样式的功能。由于实现过程比较曲折,没搜到较完整的示例,英文文档看起来也比较吃力,所以这里分享一个完整的示例。下面是两种方法分别介绍纯前端实现不修改样式和修改样式时导出Excel的方法:

    1. 使用SheetJS/js-xlsx(https://github.com/SheetJS/js-xlsx#input-type)导出Excel表格。
      1. 优点:简单。
      2. 缺点:免费版不支持修改表格样式。
      3. 安装:npm install xlsx。
      4. 性能:经测试,导出30多列,几百条数据的表格比较快;上千条大概需要等待3-5秒。
      5. 补充:支持很多种类的数据解析和导出,这里仅涉及导入二维数组,导出xlsx。持续更新中,最新的更新日期是2019年8月。
      6. 实现:
        1. 引入:
          import XLSX from 'xlsx'
        2. 输入(数据源):
          const sheetDatas = [
             ['序号, '姓名', '性别'],
             [1, 'Lily', '女'],
             [2, 'John', '男'],
             [3, 'Mary', '女']
          ]
        3. 调用方法:
          const wb = XLSX.utils.book_new() // 创建一个工作簿
          const ws = XLSX.utils.aoa_to_sheet(sheetDatas) // 使用二维数组创建一个工作表对象
          XLSX.utils.book_append_sheet(wb, ws, '人员信息') // 向工作簿追加一个工作表
          XLSX.writeFile(wb, `人员信息${moment().format('YYYYMMDD')}.xlsx`) // 写入文件
        4. 输出(Excel):   
    2. 使用protobi/js-xlsx(https://github.com/protobi/js-xlsx)导出Excel表格。
      1. 优点:可修改表格样式(如字体、合并单元格、颜色等)。
      2. 缺点:比较复杂。
      3. 安装:npm install xlsx-style --save。
      4. 性能:经测试,导出30多列,几百条数据的表格比较快;上千条大概需要等待3-5秒。
      5. 补充:基于SheetJS/js-xlsx开发(注意:安装依赖的时候不需要装SheetJS/js-xlsx),开发停留在2017年,后面没有更新,没有维护,SheetJS/js-xlsx中的新方法不支持。
      6. 实现:
        1. 引入:
          import XLSXStyle from 'js-xlsx'
          1. 我是在Vue中引入的这个依赖,引入之后会报错,解决方法:
            1. 修改源码:修改xlsx-style/dist/cpexcel.js文件中的第807行
              var cpt = require('./cpt' + 'able');
              修改为 
              var cpt = cptable;
            2. 修改webpack配置,与entry同一级新增一个字段,如图:
        2. 输入:
          const sheetTitle = ["序号", "姓名", "性别"] // 表格第一行的标题

          const sheetDatas = [ // 表格内容
             [1, 'Lily', '女'],
             [2, 'John', '男'],
             [3, 'Mary', '女']
          ]
        3. 完整代码(在Vue中的实现):
          <template>
          <div>
            <Button type="primary" @click='handleClick'>
              <slot></slot>
            </Button>
          </div>  
          </template>
          <script>
            import moment from 'moment'
            import XLSXStyle from 'js-xlsx'
          
            export default {
              data() {
                return {
                  sheetTitle: ["序号", "姓名", "性别"],
                  sheetDatas: [
                    [1,'Lily','女'],
                    [2,'John','男'],
                    [3,'Mary','女']
                  ]
                }
              },
              methods: {
                handleClick() {
                  this.downloadExl(this.sheetDatas)
                },
                downloadExl(sourceData) {
                  const wopts = { bookType: 'xlsx', bookSST: true, type: 'binary', cellHeadStyles: true };
          
                  // excel表格样式
                  let fontBold = {
                    font: { 
                      sz: 10, 
                      bold: true, // 粗字体
                      color: { rgb: "000000" },
                      name: "Times New Roman" 
                    }
                  }
                  let fontThin = {
                    font: { 
                      sz: 10, 
                      bold: false, 
                      color: { rgb: "000000" },
                      name: "Times New Roman" 
                    }
                  }
                  let alignmentCenter = { alignment: { horizontal: 'center' } } // 居中对齐
                  let alignmentLeft = { alignment: { horizontal: 'left' } }
                  let borderStyle = { // 边框粗细和颜色
                    border: {
                      right: { 
                        style: 'thin',
                        color: { rgb: "000000" } 
                      },
                      bottom: { 
                        style: 'thin',
                        color: { rgb: "000000" } 
                      },
                    }
                  }
                  let cellBaseStyle = Object.assign({}, {
                    fill: { bgColor: { indexed: 64 }, fgColor: { rgb: "FFFFFF" } },
                  }, borderStyle, alignmentCenter)
                  let cellHeadStyle = Object.assign({}, cellBaseStyle, fontBold)
                  let cellBodyStyle = Object.assign({}, cellBaseStyle, fontThin)
          
                  // 数据
                  let tmpdata = sourceData[0],
                      keyMap = [],
                      outputPos1 = [],
                      outputPos2 = [], 
                      noteStyle = {},
                      titleStyle = {},
                      cols = Object.keys(this.sheetDatas[0]).length
          
                  if(sourceData[0] instanceof Array) sourceData.unshift({})
                  for (let k in tmpdata) {
                    keyMap.push(k)
                    sourceData[0][k] = k
                  }
                  tmpdata = [];//用来保存转换好的sourceData
                  sourceData.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,
                    s: cellBodyStyle
                  });
                  outputPos1 = Object.keys(tmpdata) // 第一部分数据区域
                  for(let i=0; i<cols; i++) {
                    let len = this.sheetDatas.length,
                        letter = i > 25 ? this.getCharCol(i) : String.fromCharCode(65 + i), // A~AI
                        noteObj = {},
                        titleObj = {}
          
                    outputPos2.push(letter+(this.sheetDatas.length+2)) // 第二部分数据区域,用于添加备注行
          
                    // 第一行标题样式 
                    titleObj = {
                      [letter+'1']: {
                        v: this.sheetTitle[i],
                        s: cellHeadStyle
                      }
                    }
                    Object.assign(titleStyle, titleObj)
          
                    // 最后一行备注信息样式
                    if(i == 0) { // 第一个cell
                      noteObj = {
                        [letter+(len+1)]: {
                          v: '备注:数据生成于' + this.sheetTime,
                          t: 's',
                          s: Object.assign({}, cellBodyStyle, alignmentLeft)
                        },
                      }
                    } else if(i == (cols-1)) { // 最后一个cell
                      noteObj = { [letter+(len+1)]: { s: borderStyle } }
                    } else {
                      noteObj = {
                        [letter+(len+1)]: {
                          s: {
                            border: {
                              bottom: { 
                                style: 'thin',
                                color: { rgb: "000000" } 
                              },
                            }
                          }
                        }
                      }
                    }
                    Object.assign(noteStyle, noteObj)
                  }
          
                  let outputPos = [...outputPos1, ...outputPos2]  //设置区域,比如表格从A1到D10
                  let tmpdataStyle = {
                    '!merges': [{
                      s: {c: 0, r: this.sheetDatas.length},
                      e: {c: cols-1, r: this.sheetDatas.length}
                    }],
                    '!cols': [ //设置列宽
                      {wpx: 45}, /*a*/  {wpx: 85}, /*b*/  {wpx: 85}
                    ]
                  };
                  let tmpdataAll = Object.assign({}, tmpdata, tmpdataStyle, noteStyle, titleStyle)
                  let tmpWB = {
                    SheetNames: ['人员信息'], //保存的表标题
                    Sheets: {
                      '人员信息': Object.assign({}, tmpdataAll, //内容
                      {
                        '!ref': outputPos[0] + ':' + outputPos[outputPos.length-1] //设置填充区域
                      })
                    }
                  };
                  let tmpDown = new Blob([this.s2ab(XLSXStyle.write(tmpWB,
                    { bookType: 'xlsx', bookSST: false, type: 'binary' } //定义导出的格式类型
                  ))], { type: "" });
                  this.saveAs(tmpDown, `人员信息${moment().format('YYYYMMDD')}` + '.' + (wopts.bookType == "biff2" ? "xlsx" : wopts.bookType));
                },
                saveAs(obj, fileName) {
                  let tmpa = document.createElement("a");
                  tmpa.download = fileName;
                  tmpa.href = URL.createObjectURL(obj);
                  tmpa.click();
                  setTimeout(function () {
                    URL.revokeObjectURL(obj);
                  }, 100);
                },
                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;
                },
                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
                }
              },
              mounted() {}
            }
          </script>
        4. 输出:
  • 相关阅读:
    第一课:数据库介绍篇
    爬虫day03 cast
    python excel
    爬虫 cast_day05_多进程_多线程_协程_selenium的使用
    进程_线程_协程回顾!
    进程_线程_协程回顾 2!
    day 06 爬虫
    看正月点灯笼老师的笔记 —动态规划2.1
    动态规划——放苹果

  • 原文地址:https://www.cnblogs.com/liyan22/p/11470934.html
Copyright © 2011-2022 走看看