x-spreadsheet带样式的导入和导出

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

在实现网页可以编辑excel表格时,选用了x-spreadsheet这个插件,结合xlsx实现导入和导出时,发现是没有带样式的,包括行高/列宽/背景颜色等,当表头字段和单元格有多行数据时,页面表格不适应宽高是不合理的,因此研究下样式问题如何优化本示例使用 vue + xlsx + x-spreadsheet


一、X-Spreadsheet

X-Spreadsheet 是一个基于 Web 的 JS 在线表格
中文文档:https://hondrytravis.com/x-spreadsheet-doc/

二、导入和导出

导入:将本地excel数据 转为 x-spreadsheet支持的格式数据
导出:将x-spreadsheet格式数据 转为 excel能识别的格式
在这里插入图片描述

1. 导入

import * as xlsx from "xlsx"
// 导入
const handleFileChange: UploadProps['onChange'] = (uploadFile, uploadFiles) => {
  const file = uploadFile as UploadRawFile; // 获取用户选择的文件
  const reader = new FileReader();// 使用FileReader读取文件内容为ArrayBuffer
  reader.onload = async (e) => {
    const ab = e.target.result; // 获取ArrayBuffer
    
    重点是这里的处理(stox是x-spreadsheet的导入函数)
    (xlsx.read是读取Excel, 其中cellStyles:true一定要设置,才能将样式保存在单元格的s字段)
    返回WordBook对象(具体数据格式可以打印出来查看)
  
    const WordBook = stox(xlsx.read(ab,{type:'array',cellStyles:true}));
    grid.value.loadData(WordBook); // 加载数据到当前表格
  };

  reader.readAsArrayBuffer(file.raw); // 读取文件内容
};

其中stox代码(样式处理分别在1234位置):

export function stox(wb) {
  var out = [];
  wb.SheetNames.forEach(function (name) {
    var o = { name: name, rows: {}, styles: []  };
    var ws = wb.Sheets[name];
    if(!ws || !ws["!ref"]) return;
    var range = XLSX.utils.decode_range(ws['!ref']);
    // sheet_to_json will lost empty row and col at begin as default
    range.s = { r: 0, c: 0 };
    var aoa = XLSX.utils.sheet_to_json(ws, {
      raw: false,
      header: 1,
      range: range
    });

    aoa.forEach(function (r, i) {
      var cells = {};
      r.forEach(function (c, j) {
        cells[j] = { text: c };

        var cellRef = XLSX.utils.encode_cell({ r: i, c: j });

        if ( ws[cellRef] != null && ws[cellRef].f != null) {
          cells[j].text = "=" + ws[cellRef].f;
        }
        
        // 1. 提取excel样式信息
        if (ws[cellRef] != null && ws[cellRef].s != null) {
          var styleIndex = o.styles.indexOf(ws[cellRef].s)
          if(styleIndex === -1){
            styleIndex = o.styles.push(ws[cellRef].s) - 1
          }
          cells[j].style = styleIndex;
        }
      });
      o.rows[i] = { cells: cells };

      // 2. 提取excel行高信息
      if (ws["!rows"] && ws["!rows"][i]) {
        o.rows[i].height = ws["!rows"][i].hpt * 1.25;
      }
    });

    o.merges = [];
    (ws["!merges"]||[]).forEach(function (merge, i) {
      //Needed to support merged cells with empty content
      if (o.rows[merge.s.r] == null) {
        o.rows[merge.s.r] = { cells: {} };
      }
      if (o.rows[merge.s.r].cells[merge.s.c] == null) {
        o.rows[merge.s.r].cells[merge.s.c] = {};
      }

      o.rows[merge.s.r].cells[merge.s.c].merge = [
        merge.e.r - merge.s.r,
        merge.e.c - merge.s.c
      ];

      o.merges[i] = XLSX.utils.encode_range(merge);
    });

    // 3. 提取excel列宽信息
    o.cols = {}
    Object.keys(ws["!cols"] || {}).forEach(function(key){
      var col = ws["!cols"][key];
      o.cols[key] = {
        width: col.wpx * 1.5
      }
    })

    out.push(o);
  });

  // 4. 在工作表处理完毕后,统一转换x-spreadsheet样式字段
  out.forEach(function(o) {
    o.styles = o.styles.map(converStyleFields);
  });

  return out;
}

function converStyleFields(style){
  if(style.fgColor){
    style.bgcolor = "#" + style.fgColor.rgb;
  }
  return style;
}

2. 导出

import XLSX from 'xlsx-js-style'; // 重点!带样式导出使用xlsx-js-style,而不是xlsx
function handleExport(){
  if(grid.value !== null){
    const data = grid.value.getData(); // 获取当前表格的数据
    const worksheet:any = xtos(Object.values(data)); // 将数据转换为适当的格式
    XLSX.writeFile(worksheet, "SheetJS.xlsx"); // 写入文件
  }
}

其中xtos代码(样式处理分别在1234位置):

export function xtos(sdata) {
  var out = XLSX.utils.book_new();
  sdata.forEach(function (xws) {
    var ws = {};
    var rowobj = xws.rows;
    var cols = xws.cols;//列宽
    var styles = xws.styles || [];//样式
    var styleMap = {};

    // 1. 初始化样式映射(只转换了基础样式,其他复杂样式根据需要添加)
    styles.forEach((style, index) => {
      let xf = {};
      if (style.color) {
        xf.font = { color: { rgb: style.color.substring(1) } };
      }
      if (style.bgcolor) {
        xf.fill = { fgColor: { rgb: style.bgcolor.substring(1) } };
      }
      if (style.bold) {
        xf.font.bold = style.bold;
      }
      if (style.size) {
        xf.font.sz = style.sz;
      }
      if(style.align || style.valign){
      	xf.alignment = { horizontal: style.align, vertical: style.valign, wrapText: true };
      }
      styleMap[index] = xf;
    });

    var minCoord = { r: 0, c: 0 }, maxCoord = { r: 0, c: 0 };
    for (var ri = 0; ri < rowobj.len; ++ri) {
      var row = rowobj[ri];
      if (!row) continue;

      Object.keys(row.cells).forEach(function (k) {
        var idx = +k;
        if (isNaN(idx)) return;

        var lastRef = XLSX.utils.encode_cell({ r: ri, c: idx });
        if (ri > maxCoord.r) maxCoord.r = ri;
        if (idx > maxCoord.c) maxCoord.c = idx;

        var cellText = row.cells[k].text, type = "s";
        if (!cellText) {
          cellText = "";
          type = "z";
        } else if (!isNaN(Number(cellText))) {
          cellText = Number(cellText);
          type = "n";
        } else if (cellText.toLowerCase() === "true" || cellText.toLowerCase() === "false") {
          cellText = Boolean(cellText);
          type = "b";
        }

        ws[lastRef] = { v: cellText, t: type };

        if (type == "s" && cellText[0] == "=") {
          ws[lastRef].f = cellText.slice(1);
        }

        // 2. 添加样式
        var styleIndex = row.cells[k].style;
        if (styleIndex in styleMap) {
          ws[lastRef].s = styleMap[styleIndex];
        }

        // 3. 设置行高
        if (row.height != null) {
          if (ws["!rows"] == null) ws["!rows"] = [];
          ws["!rows"][ri] = { hpx: row.height };
        }

        if (row.cells[k].merge != null) {
          if (ws["!merges"] == null) ws["!merges"] = [];

          ws["!merges"].push({
            s: { r: ri, c: idx },
            e: {
              r: ri + row.cells[k].merge[0],
              c: idx + row.cells[k].merge[1]
            }
          });
        }
      });
    }
    ws["!ref"] = minCoord ? XLSX.utils.encode_range({
      s: minCoord,
      e: maxCoord
    }) : "A1";

    // 4. 设置列宽
    let columns = []
    // 遍历 cols 对象的每一个键值对
    Object.keys(cols).forEach(key => {
      const colConfig = cols[key];
      if (key !== 'len' && 'width' in colConfig) {
        columns[key] = { wpx: colConfig.width };
      }
    });
    // 将列配置数组赋值给 ws['!cols']
    ws['!cols'] = columns;

    XLSX.utils.book_append_sheet(out, ws, xws.name);
  });

  return out;
}

总结

仅分享,如果有更好的方法,欢迎一起讨论和分享。
另外,在使用Vue 3集成x-spreadsheet时遇到了切换sheet表就报错的问题,欢迎共同探讨解决方案。

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值