vue项目+xlsx+xlsx-style 实现table导出为excel的功能

最近遇到一个需求,后端提供一组数据,根据这组数据,导出为一个excel表格。

步骤如下:

1. 导出内容的预览如下:包含单元格的合并等操作。

在这里插入图片描述

2. 点击右下角导出按钮,可以直接下载一个excel表格。

在这里插入图片描述
由于我之前有用过csdn版本的xlsx-style,因此首先想到的就是使用xlsx来实现此功能。

下面介绍详细步骤:

1.安装xlsxxlsx-style

依次安装以下的内容:
npm install xlsx --save
npm install xlsx-style --save
在这里插入图片描述

2.局部注册使用

由于我这边是单个页面使用此功能,因此并没有在main.js中全局注册

局部注册的方法:

import * as XLSX from 'xlsx';
import * as XLSX2 from 'xlsx-style';

注意:一定要重命名其中的方法,就是用as 重命名,因为两个插件导出的名字默认都叫XLSX,如果不进行重命名,则会报错

3. vue.config.js中需要添加以下配置

在这里插入图片描述
如果不添加下面的代码,则运行时,代码会报错。

config.externals = {
  './cptable': 'var cptable',
};

4.html预览用table组件来实现(VUE)

在这里插入图片描述
我对上面的表格进行了拆分,一个table里面包含了4组<thead></thead><tbody></tbody>
合并单元格 我使用的是colspan="n"

<table border="1" width="100%" cellspacing="0" cellpadding="0" id="tableId">
      <thead>
        <tr>
          <th colspan="7" class="headerCls">
            {{ info.saleOrderCode }}采购需求清单
          </th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>计划套数</td>
          <td colspan="2">{{ info.producePlansCount }}</td>
          <td>付款时间</td>
          <td colspan="3">{{ info.lastPaidTime }}</td>
        </tr>
        <tr>
          <td>申请时间</td>
          <td colspan="2">{{ info.applicantTime }}</td>
          <td>申请人员</td>
          <td colspan="3">{{ info.applicantName }}</td>
        </tr>
        <tr>
          <td>计划交期</td>
          <td colspan="2">{{ info.deliveryTime }}</td>
          <td>打印时间</td>
          <td colspan="3">{{ info.printTime }}</td>
        </tr>
      </tbody>
      <thead>
        <tr>
          <th colspan="5" class="leftTitCls">板材需求明细</th>
          <th>板材系数</th>
          <th>1.2</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>序号</td>
          <td>物品名称</td>
          <td>型号规格</td>
          <td>数量</td>
          <td>单位</td>
          <td>到货时间</td>
          <td>备注</td>
        </tr>
        <tr v-for="(item, index) in list1" :key="item.materialName">
          <td>{{ index + 1 }}</td>
          <td>{{ item.materialName }}</td>
          <td>{{ item.specification }}</td>
          <td>{{ item.procurementCount }}</td>
          <td>{{ item.unit }}</td>
          <td>{{ item.arrivalTime }}</td>
          <td>{{ item.remark }}</td>
        </tr>
      </tbody>
      <thead>
        <tr>
          <th colspan="5" class="leftTitCls">塑粉需求明细</th>
          <th>喷涂系数</th>
          <th>0.8</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>序号</td>
          <td>物品名称</td>
          <td>型号规格</td>
          <td>数量</td>
          <td>单位</td>
          <td>到货时间</td>
          <td>备注</td>
        </tr>
        <tr v-for="(item, index) in list2" :key="item.materialName">
          <td>{{ index + 1 }}</td>
          <td>{{ item.materialName }}</td>
          <td>{{ item.specification }}</td>
          <td>{{ item.procurementCount }}</td>
          <td>{{ item.unit }}</td>
          <td>{{ item.arrivalTime }}</td>
          <td>{{ item.remark }}</td>
        </tr>
      </tbody>
      <thead>
        <tr>
          <th colspan="7" class="leftTitCls">外购件需求明细</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>序号</td>
          <td>物品名称</td>
          <td>型号规格</td>
          <td>数量</td>
          <td>单位</td>
          <td>到货时间</td>
          <td>备注</td>
        </tr>
        <tr v-for="(item, index) in list3" :key="item.materialName">
          <td>{{ index + 1 }}</td>
          <td>{{ item.materialName }}</td>
          <td>{{ item.specification }}</td>
          <td>{{ item.procurementCount }}</td>
          <td>{{ item.unit }}</td>
          <td>{{ item.arrivalTime }}</td>
          <td>{{ item.remark }}</td>
        </tr>
      </tbody>
    </table>

5.导出功能(!!!重要)

数据渲染是通过数组遍历的。
在这里插入图片描述

//获取当前时间,用于命名excel表格
let time = this.moment(new Date()).format('YYYY-MM-DD');
//获取整个表格的行数。三个数组长度+其他固定行数(10)
let len = this.list1.length + this.list2.length + this.list3.length + 10;
this.tableToExcel(
  'tableId',
  `${this.info.saleOrderCode}采购清单需求表${time}`,
  7,
  len,
  1
);

5.1 tableToExcel方法——入参(表格id,导出文档名称,列数,行数,表头行数)

this.tableToExcel( 'tableId', `采购清单需求表${time}`, 7,len, 1);
tableToExcel(tableId, fileName, headLength, colsLength, headColsLength) {
      var sheet = XLSX.utils.table_to_sheet(
        document.querySelector('#' + tableId)
      );
      //根据tableId获取到元素,通过XLSX.utils.table_to_sheet 获取到sheet表格属性
      console.log(sheet);
      //下面是固定写法,可以直接照搬
      var arr = [];
      for (let i = 0; i < headLength; i++) {
        if (i < 26) {
          arr.push(String.fromCharCode(65 + i).toUpperCase());
        } else {
          arr.push('A' + String.fromCharCode(65 + (i - 26)).toUpperCase());
        }
      }
      for (let i = 0; i < arr.length; i++) {
      //如果是多级表头的话,设置单元格宽度
        if (headColsLength > 1) {
          for (let j = 0; j < headColsLength; j++) {
            if (j < headColsLength && sheet[arr[i] + j]) {
              if (
                !sheet[arr[i] + (j + 1)] ||
                !sheet[arr[i] + (j - 1)] ||
                (!sheet[arr[i] + (j - 1)] && !sheet[arr[i] + (j + 1)])
              ) {
                sheet['!cols'].push({
                  wch: sheet[arr[i] + j].v.length * 2 + 3,
                });
              }
            }
          }
        } else {
        //由于我这边是单个表头,因此走这块代码
          if (!sheet['!cols']) {
            sheet['!cols'] = [];
          }
          //我这边是对 A D E三列要求宽度窄一些,其他的宽一些,主要是根据内容的多少来,也可以根据内容的长度来处理
          if (
            (arr[i] + '1').indexOf('A') > -1 ||
            (arr[i] + '1').indexOf('D') > -1 ||
            (arr[i] + '1').indexOf('E') > -1
          ) {
            sheet['!cols'].push({
              wch: 8,
            });
          } else {
            sheet['!cols'].push({
              wch: 14,
            });
          }
        }
		//由于我这边合并单元格后,导致部分边框不展示了,因此通过这个方法来补充边框。
        sheet = this.addRangeBorder(sheet['!merges'], sheet);
        //如果仅仅用上面的方法,发现还是有缺失的边框,因此下面又进行了一遍处理。添加边框及其他样式的处理。
        for (let key in sheet) {
          if (sheet[key] instanceof Object) {
            sheet[key].s = {
              border: {
                top: {
                  style: 'thin',
                },
                bottom: {
                  style: 'thin',
                },
                left: {
                  style: 'thin',
                },
                right: {
                  style: 'thin',
                },
              },
              alignment: {
                horizontal: 'center',
                vertical: 'center',
                wrap_text: true,
              },
              font: {
                sz: 11,
              },
              bold: true,
              numFmt: 0,
            };
          }
        }
      }
      this.downloadExcel(this.sheet2blob(sheet), `${fileName}.xlsx`); //下载
    },

5.2 解决合并单元格后部分边框消失问题——addRangeBorder

addRangeBorder(range, ws) {
      // s:起始位置,e:结束位置
      var arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
      range.forEach((item) => {
        var startRowNumber = Number(item.s.r),
          startColumnNumber = Number(item.s.c),
          endColumnNumber = Number(item.e.c),
          endRowNumber = Number(item.e.r);
        console.log(
          'startColumnNumber',
          startColumnNumber,
          endColumnNumber,
          startRowNumber,
          endRowNumber
        );
        // 合并单元格时会丢失边框样式,例如A1->A4 此时内容在A1 而range内获取到的是从0开始的,所以开始行数要+2
        for (var i = startColumnNumber; i <= endColumnNumber + 2; i++) {
          for (var j = startRowNumber; j <= endRowNumber + 2; j++) {
            if (ws[arr[i] + j]) {
              ws[arr[i] + j].s = {
                border: {
                  top: { style: 'thin' },
                  left: { style: 'thin' },
                  bottom: { style: 'thin' },
                  right: { style: 'thin' },
                },
                alignment: {
                  horizontal: 'center',
                  vertical: 'center',
                  wrap_text: true,
                },
                font: {
                  sz: 11,
                },
                bold: true,
                numFmt: 0,
              };
            } else {
              ws[arr[i] + j] = {
                s: {
                  border: {
                    top: { style: 'thin' },
                    left: { style: 'thin' },
                    bottom: { style: 'thin' },
                    right: { style: 'thin' },
                  },
                  alignment: {
                    horizontal: 'center',
                    vertical: 'center',
                    wrap_text: true,
                  },
                  font: {
                    sz: 11,
                  },
                  bold: true,
                  numFmt: 0,
                },
                t: '',
                v: '',
              };
            }
          }
        }
      });
      return ws;
    },

5.3 sheet表格转化为文档流blob

 sheet2blob(sheet, sheetName) {
      sheetName = sheetName || 'sheet1';
      var workbook = {
        SheetNames: [sheetName],
        Sheets: {},
      };
      workbook.Sheets[sheetName] = sheet;

      var wopts = {
        bookType: 'xlsx',
        bookSST: false,
        type: 'binary',
      };
      var wbout = XLSX2.write(workbook, wopts);
      var blob = new Blob([this.s2ab(wbout)], {
        type: 'application/octet-stream',
      });
      return blob;
    },
    s2ab(s) {
      var buf = new ArrayBuffer(s.length);
      var view = new Uint8Array(buf);
      for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xff;
      return buf;
    },

5.4 下载excel文档

 downloadExcel(url, saveName) {
      var blob = new Blob([url], {
        type: 'application/octet-stream',
      });
      var a = document.createElement('a');
      a.id = 'downloadFtsetBtn';
      a.style.display = 'none';
      a.target = '_blank';
      document.body.appendChild(a);
      try {
        var URL = window.URL || window.webkitURL;
        a.href = URL.createObjectURL(blob);
        a.download = saveName;
        if (typeof navigator.msSaveBlob == 'function') {
          //IE
          navigator.msSaveBlob(blob, saveName);
        }
        a.click();
      } catch (e) {
        console.log(e);
      }
    },

完成!!!多多积累,多多收获!!!

Logo

前往低代码交流专区

更多推荐