基于vue3+vite的项目实现导出带有样式的excel表格,框架用的是vben,所以表格用的是ant的table组件数据源,如果用原生表格需要用到备注的另外方法。

  1. 首先需要下载xlsx、xlsx-style、file-saver这3个依赖。
    npm i xlsx xlsx-style file-saver

我的版本为
“xlsx”: “^0.16.9”,
“xlsx-style”: “^0.8.13”
“file-saver”: “^2.0.5”,

  1. 下载完成后xlsx-style会有很多报错,具体的解决方法可以参考xlsx-style坑记录,我按照他的方式所有都改了一遍,没有出现过报错问题。
    另外它的样式设置无法修改行高,所以需要再修改一下源码,修改xlsx-style文件夹下面的xlsx.js文件 替换write_ws_xml_data以下方法
var DEF_PPI = 96, PPI = DEF_PPI;
function px2pt(px) { return px * 96 / PPI; }
function pt2px(pt) { return pt * PPI / 96; }
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("");
}
  1. 完成上述步骤就可以直接使用,完整引用代码以及方法如下:
import XLSX from 'xlsx';
import XLSXS from 'xlsx-style';
import FileSaver from 'file-saver';

// 导出excel方法
const exportExcel = () => {
  // 第一种是经过处理后的数据
  tableData = [
  	['第一行第一列', '第一行第二列', '第一行第三列'],
  	['第二行第一列', '第二行第二列', '第二行第三列'],
  	['第三行第一列', '第三行第二列', '第三行第三列'],
  ]
  const wsa = XLSX.utils.aoa_to_sheet(tableData);
  const wb = XLSX.utils.book_new();
  XLSX.utils.book_append_sheet(wb, wsa, '数据表');
  
  // 第二种是原生table的数据格式,需要dom获取表格数据
  // let wsa = XLSX.utils.table_to_book(this.$refs.table, { sheet: "数据表" });

  // 样式添加
  xlsxAddStyle(wsa, tableData[0].length);
  var wopts = {
    bookType: 'xlsx', // 要生成的文件类型
    bookSST: false, // 是否生成Shared String Table,官方解释是,如果开启生成速度会下降,但在低版本IOS设备上有更好的兼容性
    type: 'binary',
  };
  var ws = XLSXS.write(wb, wopts);
  try {
    FileSaver.saveAs(new Blob([s2ab(ws)], { type: 'application/octet-stream' }), `配置.xlsx`);
  } catch (e) {
    if (typeof console !== 'undefined') console.log(e, ws);
  }
  // 字符串转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;
  }
};

// 表格样式的设置
const xlsxAddStyle = (sheet, rowCLength) => {
  const mergeArr = []; // 合并的单元格
  const rowH = []; // 表格每列高度
  const rowC = []; // 表格每列宽度
  // 单元格外侧有框线
  const borderAll = {
    top: { style: 'thin' },
    bottom: { style: 'thin' },
    left: { style: 'thin' },
    right: { style: 'thin' },
  };
  // 单元格外侧无框线
  const noBorder = {
    top: { style: '' },
    bottom: { style: '' },
    left: { style: '' },
    right: { style: '' },
  };
  for (const key in sheet) {
    if (Object.hasOwnProperty.call(sheet, key)) {
      const element = sheet[key];
      if (typeof element === 'object') {
        const index = Number(letter(key)) - 1;
        rowH[index] = { hpx: 20 };
        element.s = {
          alignment: {
            horizontal: 'center', // 所有单元格水平居中
            vertical: 'center', // 所有单元格垂直居中
          },
          font: {
            name: '宋体',
            sz: 12,
            italic: false,
            underline: false,
          },
          border: borderAll,
          fill: {
            fgColor: { rgb: 'FFFFFFFF' },
          },
        };
        // 指标值表格的样式
        // if (key.indexOf('C') > -1) {
        //   element.s.alignment.horizontal = 'right';
        // }
        // 标题的样式
        if (index === 0) {
          element.s.font.bold = true;
          element.s.fill.fgColor = { rgb: 'FFCCFFFF' };
        }
        // 处理合并单元格数组 s开始 e结束 r行 c列
        // if (element.v === '所属设备') {
        //   mergeArr.push({
        //     s: { r: 0, c: 0 },
        //     e: { r: 2, c: 1 },
        //   });
        // }
      }
    }
  }
  // 单元格的列宽,根据传入的列数进行逐个添加
  for (let i = 0; i < rowCLength; i++) {
    rowC.push({ wpx: 180 });
  }
  sheet['!cols'] = rowC;
  // 单元格的行宽
  sheet['!rows'] = rowH;
  // 合并单元格
  sheet['!merges'] = mergeArr;
  return sheet;
};

// 去除字母方法,当列的数量超过25的时候会变成AA所以需要用正则匹配去掉所有的字母,剩下数字
const letter = (str) => {
  let result;
  const reg = /[a-zA-Z]+/;
  while ((result = str.match(reg))) {
    //判断str.match(reg)是否没有字母了
    str = str.replace(result[0], ''); //替换掉字母  result[0] 是 str.match(reg)匹配到的字母
  }
  return str;
};
Logo

前往低代码交流专区

更多推荐