Vue+ElementUI实现el-table内容的导出

4,325 阅读6分钟

前言

  • 在许多的后台系统中少不了导出Excel表格的功能,一般来说很多的表格导出都是后端对表格的数据进行处理返回文件进行下载导出。不过难免有些时候遇到前端来做表格导出的时候,查阅了一些方法,将前端做表格导出的具体方法做了一个总结。
  • 下面就是我在实际的项目中纯前端使用xlsx插件来实现简单Excel表格的导出功能;以及导出表格的样式修改,单元格的合并和对齐方式等。如有描述不当,欢迎指正,补充。
  • 用到的依赖:xlsxfile-saverxlsx-style

可能遇到的问题

  • 如果存在分页,导出时却只导出当前table绑定的数据,假如我们设置的table每页只有10条数据,导出时只导出了10条,并非所有符合条件的数据。
    • 原因:此插件只导出当前table中所有的数据。
    • 解决办法:在HTML代码中再加一个el-table标签,这个table专门用来导出数据,且此table一直隐藏着,当查询条件发生变化时,根据后台返回的所有符合条件的数据总量total值,然后重新设置获取后台数据方法的参数,拉取到所有符合条件的数据绑定进来,这样导出的就是想要的数据了。
  • 第一次导出时,导出的excel表格只有表头,没有数据内容。
    • 解决方法:给导出到excel表格的函数exportExcel()设置一个延时执行即可。
  • 导出的数据可能会出现两份的情况。
    • 原因:因为element ui下的el-table被渲染出来后有两个table标签,目的是方便进行数据交互使用,所以如果el-column存在fixed属性时,导出时会出现两份数据的情况。
    • 解决方法:将隐藏table中的el-table-column上的fixed属性去掉即可。这个table是隐藏起来的,直接去掉el-table-column上的fixed属性最便捷。
  • 导出的excel表格,如果出现数字字符比较长的,在导出表格中会变成科学计数那样的情况。
// 解决方法:在导出方法中设置{raw: true},例如: 
var wb = this.XLSX.utils.table_to_book(document.querySelector("#out-table"),{raw:true});
  • 关于表格导出对齐样式的设置。
    • 默认对齐样式,所有单元格全部居中对齐。
    • 修改方式:导出使用的方法中可加入参数配置@click="exportExcel(['A'],['C','D'])",不加参数时默认居中对齐。(参数类型均为数组)
    • 第一个参数:['A'] ----- 靠左对齐,参数值为大写英文字母,A代表第一列的对齐方式,以此类推。(数组中的字母应为字符串类型,数组中的参数值应不大于表格的列数)
    • 第二个参数:['C','D'] ---- 靠右对齐,同样值为大写字母,C、D代表第三、四列的对齐方式。

使用方法

首先,安装相关excel依赖

npm install xlsx --save

其次,安装导出文件依赖

npm install file-saver --save

安装样式依赖

npm install xlsx-style --save

安装完依赖后,修改xlsx-style 源码(否则会报错,导致项目无法启动)。

  • 在项目根目录下找到 node_modules\xlsx-style\dist\cpexcel.js 文件。
var cpt = require('./cpt' + 'able'); 改为 var cpt = cptable;
  • 在项目根目录下找到 node_modules\xlsx-style\dist\xlsx.js 文件,将write_ws_xml_data 方法替换成如下代码(修改后可支持行高设置)。
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 = px2pt(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 = px2pt(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('');
}

在main.js中引入

// 导出Excel表格全局挂载
import FileSaver from "file-saver";
import XLSX from "xlsx";
import XLSXS from "xlsx-style";
Vue.prototype.FileSaver = FileSaver;
Vue.prototype.XLSX = XLSX;
Vue.prototype.XLSXS = XLSXS;	

实际使用时需要用到五个方法,如下:

  • 表格导出(需要传入导出表格的id
handleExportTable(leftArr,rightArr){
  if(this.tableData.length>0){
    let xlsxParam = { raw: true }   //这个保证表格只进行解析 不做运算
    let wb = this.XLSX.utils.table_to_sheet(document.querySelector("#export-table"),xlsxParam);//export-table为表格的id名
    // 根据需要设置sheet的每一行,每一列-格式
    // wb[sheets]['B2"] = { t:"d", v: 1000 }//第一行,第二列
    //t表示类型,s是字符串,n是数字,d是日期类型
    this.setExlStyle(wb,[leftArr,rightArr])//设置excel样式
    // 表格有合并单元格时执行-只支持表头合并单元格
    // var data = this.addRangeBorder(wb['!merges'],wb) //合并项添加边框
    // var filedata = this.sheet2blob(data)
    // 常规表格执行-无合并单元格
    let filedata = this.sheet2blob(wb)
    this.openDownloadDialog(filedata,this.operateTime+".xlsx")
  }else{
    this.$message({
      type:'error',
      message:'暂时没有可导出的数据'
    })
  }
}
  • 将一个sheet转成最终的excel文件的blob对象,然后利用URL.createObjectURL下载
sheet2blob(sheet, sheetName) {
  sheetName = sheetName || 'sheet1';
  var workbook = {
    SheetNames: [sheetName],
    Sheets: {}
  };
  workbook.Sheets[sheetName] = sheet; // 生成excel的配置项
  var wopts = {
    bookType: 'xlsx', // 要生成的文件类型
    bookSST: false, // 是否生成Shared String Table,官方解释是,如果开启生成速度会下降,但在低版本IOS设备上有更好的兼容性
    type: 'binary'
  };
  var wbout = this.XLSXS.write(workbook, wopts);
  var blob = new Blob([s2ab(wbout)], {
    type: "application/octet-stream"
  }); // 字符串转ArrayBuffer
  function 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;
  }
  return blob;
}
  • excel命名+下载
openDownloadDialog(url, saveName) {
  if (typeof url == 'object' && url instanceof Blob) {
    url = URL.createObjectURL(url); // 创建blob地址
  }
  var aLink = document.createElement('a');
  aLink.href = url;
  aLink.download = saveName || ''; // HTML5新增的属性,指定保存文件名,可以不要后缀,注意,file:///模式下不会生效
  var event;
  if (window.MouseEvent) event = new MouseEvent('click');
  else {
    event = document.createEvent('MouseEvents');
    event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
  }
  aLink.dispatchEvent(event);
}
  • 设置导出excel样式
setExlStyle(data,alignArr) {
  if(!alignArr[0])alignArr[0] = []; // 不存在左对齐时为空
  if(!alignArr[1])alignArr[1] = []; // 不存在右对齐时为空
  //设置列宽度
  for(var i = 0;i<50;i++){
    // console.log(data["!cols"])
    data["!cols"][i]={wpx:180}
  }
  // https://www.npmjs.com/package/xlsx-style-样式的文档地址
  //设置通用样式
  let styleAll = {
    font:{//字体设置
      sz:13,
      bold:false,
      color:{
        rgb:'000000'//文字颜色设置-十六进制,不带#
      }
    },
    alignment:{//文字居中
      horizontal:'center',//水平居中
      vertical:'center',//垂直居中
      wrap_text:true
    },
    border: { // 设置边框
      top: { style: 'thin' },
      bottom: { style: 'thin' },
      left: { style: 'thin' },
      right: { style: 'thin' }
    }
  };
  for (const key in data) {
    if(key.indexOf('!') === -1){
      // 给指定单元格添加特定样式-如下代码以添加背景色为例
      if ((key.split('')[1] === '1') && (key.split('').length === 2) && (key.split('')[0] !== 'I')) {
        var styleS=JSON.stringify(styleAll)//必须要转一下格式,否则styleAll也会跟着styleS一起改变
        styleS=JSON.parse(styleS)
        styleS.fill={
          fgColor: {rgb: "EEF4FA"}//设置背景色
        }
        data[key].s= styleS
      }else if(key.split("")[0] === "E"){
        data[key].t='d'  // 单独对时间列进行格式的处理
        data[key].z='yyyy-mm-dd HH:mm:ss'
        data[key].s = styleAll;
      }else if((key.split('')[0] !== 'I')) {
        //其他单元格设置通用样式
        data[key].s =styleAll
        let keyVal = key.split('')[0];
        if(alignArr[0].length>0){ // 左对齐
          let styleL = JSON.stringify(styleAll); //必须要转一下格式,否则styleAll也会跟着styleL一起改变
          styleL = JSON.parse(styleL);
          styleL.alignment = {
            horizontal: "left",
          };
          alignArr[0].forEach((ele)=>{
            if(ele == keyVal){
              data[key].s = styleL;
            }
          })
        }
        if(alignArr[1].length>0){ // 右对齐
          let styleR = JSON.stringify(styleAll); //必须要转一下格式,否则styleAll也会跟着styleR一起改变
          styleR = JSON.parse(styleR);
          styleR.alignment = {
            horizontal: "right",
          };
          alignArr[1].forEach((ele)=>{
            if(ele == keyVal){
              data[key].s = styleR;
            }
          })
        }
      }
    }
  }
}
  • 存在合并单元格的表格添加边框-只对表头有效
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 startRowNumber = Number(item.s.c),
      endRowNumber = Number(item.e.c);
    for (let i = startRowNumber+1; i <= endRowNumber; i++) {
      console.log(arr[i])
      console.log(arr[startRowNumber])
      console.log(arr[i] + (Number(item.e.r)+1))
      const name='A3'
      ws[name] ={
        s: {
          border: {
            top: {style: 'thin'},
            left: {style: 'thin'},
            bottom: {style: 'thin'},
            right: {style: 'thin'}
          }
        }
      }
      ws[arr[i] + (Number(item.e.r)+1)] = {
        s: {
          border: {
            top: {style: 'thin'},
            left: {style: 'thin'},
            bottom: {style: 'thin'},
            right: {style: 'thin'}
          }
        }
      };
        console.log(ws)
    }

  })
  console.log(ws)
  return ws;
}

具体关于表格导出的介绍就到这里,喜欢的话就点个赞吧!