基于Handsontable.js + Excel.js实现表格预览和导出功能(公式渲染)

news2024/10/12 8:14:41

本文记录在html中基于Handsontable.js + Excel.js实现表格预览功能。

Handsontable官方文档

一、开发前的准备引入相关依赖库
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>基于Handsontable.js + Excel.js实现表格预览功能</title>
  <!-- handsontable的css文件 https://cdn.jsdelivr.net/npm/handsontable/dist/handsontable.full.min.css -->
  <link rel="stylesheet" href="./lib/handsontable.full.min.css">
</head>
<body>
  <!-- https://cdn.jsdelivr.net/npm/handsontable/dist/handsontable.full.min.js -->
  <script src="./lib/handsontable.full.min.js"></script>
  <!-- https://cdn.jsdelivr.net/npm/handsontable/dist/languages/zh-CN.js -->
  <script src="./lib/zh-CN.js"></script>
  <!-- https://cdn.jsdelivr.net/npm/color-js -->
  <script src="./lib/color-js.js"></script>
  <!-- https://cdnjs.cloudflare.com/ajax/libs/hyperformula/1.4.0/hyperformula.min.js -->
  <script src="./lib/hyperformula.full.min.js"></script>
  <!-- https://cdn.jsdelivr.net/npm/exceljs/dist/exceljs.min.js -->
  <script src="./lib/exceljs.min.js"></script>
  <!-- https://cdn.jsdelivr.net/npm/xlsx/dist/xlsx.full.min.js -->
  <script src="./lib/xlsx.full.min.js"></script>
  <!-- https://cdn.jsdelivr.net/npm/fxparser@1.0.0/dist/fxparser.min.js -->
  <script src="./lib/fxparser.min.js"></script>
</body>
</html>
二、编写页面布局
<body>
  <input type="file" id="file">
  <button id="btn">预览</button>
  <button id="export">导出</button>
</body>
三、编写预览核心代码
<script>
  var hot; //handsontable实例
  var themeJson; //主题json
  var sheet; //当前sheet
  var Color = net.brehaut.Color; //引入color-js库
  
  // 自定义渲染器函数
  function customRenderer(hotInstance, td, row, column, prop, value, cellProperties) {
    Handsontable.renderers.TextRenderer(hotInstance, td, row, column, prop, value, cellProperties);
    // 填充样式
    if ("fill" in cellProperties) {
      // 背景颜色
      if ("fgColor" in cellProperties.fill && cellProperties.fill.fgColor) {
        td.style.background = getColor(cellProperties.fill.fgColor,  themeJson);
      }
    }
    // 字体样式
    if ("font" in cellProperties) {
      // 加粗
      if ("bold" in cellProperties.font && cellProperties.font.bold) {
        td.style.fontWeight = "700";
      }
      // 字体颜色
      if ("color" in cellProperties.font && cellProperties.font.color) {
        td.style.color = getColor(cellProperties.font.color, themeJson);
      }
      // 字体大小
      if ("size" in cellProperties.font && cellProperties.font.size) {
        td.style.fontSize = cellProperties.font.size + "px";
      }
      // 字体类型
      if ("name" in cellProperties.font && cellProperties.font.name) {
        td.style.fontFamily = cellProperties.font.name;
      }
      // 字体倾斜
      if ("italic" in cellProperties.font && cellProperties.font.italic) {
        td.style.fontStyle = "italic";
      }
      // 下划线
      if (
        "underline" in cellProperties.font &&
        cellProperties.font.underline
      ) {
        // 其实还有双下划线,但是双下划綫css中没有提供直接的设置方式,需要使用额外的css设置,所以我也就先懒得弄了
        td.style.textDecoration = "underline";
        // 删除线
        if ("strike" in cellProperties.font && cellProperties.font.strike) {
          td.style.textDecoration = "underline line-through";
        }
      } else {
        // 删除线
        if ("strike" in cellProperties.font && cellProperties.font.strike) {
          td.style.textDecoration = "line-through";
        }
      }
    }
    // 对齐
    if ("alignment" in cellProperties) {
        if ("horizontal" in cellProperties.alignment) {
          // 水平
          // 这里我直接用handsontable内置类做了,设置成类似htLeft的样子。
          //(handsontable)其实至支持htLeft, htCenter, htRight, htJustify四种,但是其是它还有centerContinuous、distributed、fill,遇到这几种就会没有效果,也可以自己设置,但是我还是懒的弄了,用到的时候再说吧
          const name =
            cellProperties.alignment.horizontal.charAt(0).toUpperCase() +
            cellProperties.alignment.horizontal.slice(1);
            td.classList.add(`ht${name}`);
        }
        if ("vertical" in cellProperties.alignment) {
          // 垂直
          // 这里我直接用handsontable内置类做了,设置成类似htTop的样子。
          const name =
            cellProperties.alignment.vertical.charAt(0).toUpperCase() +
            cellProperties.alignment.vertical.slice(1);
            td.classList.add(`ht${name}`);
        }
    }
    // 边框
    if ("border" in cellProperties) {
        if ("left" in cellProperties.border && cellProperties.border.left) {
          // 左边框
          const [borderWidth, borderStyle] = setBorder(
            cellProperties.border.left.style
          );
          let color = "";
          // console.log(row, column, borderWidth, borderStyle);
          if (cellProperties.border.left.color) {
            color = getColor(cellProperties.border.left.color, themeJson);
          }
          td.style.borderLeft = `${borderStyle} ${borderWidth} ${color}`;
        }
        if ("right" in cellProperties.border && cellProperties.border.right) {
          // 左边框
          const [borderWidth, borderStyle] = setBorder(
            cellProperties.border.right.style
          );
          // console.log(row, column, borderWidth, borderStyle);
          let color = "";
          if (cellProperties.border.right.color) {
            color = getColor(cellProperties.border.right.color, themeJson);
          }
          td.style.borderRight = `${borderStyle} ${borderWidth} ${color}`;
        }
        if ("top" in cellProperties.border && cellProperties.border.top) {
          // 左边框
          const [borderWidth, borderStyle] = setBorder(
            cellProperties.border.top.style
          );
          let color = "";
          // console.log(row, column, borderWidth, borderStyle);
          if (cellProperties.border.top.color) {
            color = getColor(cellProperties.border.top.color, themeJson);
          }
          td.style.borderTop = `${borderStyle} ${borderWidth} ${color}`;
        }
      if ("bottom" in cellProperties.border && cellProperties.border.bottom) {
        // 左边框
        const [borderWidth, borderStyle] = setBorder(
          cellProperties.border.bottom.style
        );
        let color = "";
        // console.log(row, column, borderWidth, borderStyle);
        if (cellProperties.border.bottom.color) {
          color = getColor(cellProperties.border.bottom.color, themeJson);
        }
        td.style.borderBottom = `${borderStyle} ${borderWidth} ${color}`;
      }
    }
  }

  // 在 Handsontable 初始化之前注册渲染器
  Handsontable.renderers.registerRenderer('customStylesRenderer', customRenderer);

  // 点击预览按钮
  document.getElementById('btn').addEventListener('click', async function () {
    var hotData = null; // Handsontable 数据
    var file = document.getElementById('file').files[0]; // 获取文件对象
    const workbook = new ExcelJS.Workbook(); // 创建一个工作簿对象
    await workbook.xlsx.load(file); // 加载Excel文件
    const worksheet = workbook.getWorksheet(1); // 获取第一个工作表
    sheet = worksheet; // 将工作表赋值给全局变量

    // 遍历工作表中的所有行(包括空行)
    const sheetData = [];
    worksheet.eachRow({ includeEmpty: true }, function (row, rowNumber) {
      const row_values = row.values.slice(1); // 获取行数据,并排除第一列为null的数据
      const newRowValue = [...row_values];
      // 将行数据添加到sheetData数组中
      sheetData.push(newRowValue);
    });
    // 将数据赋值给Handsontable
    hotData = sheetData; 

    // 将主题xml转换成json
    const themeXml = workbook._themes.theme1;
    const options = {
      ignoreAttributes: false,
      attributeNamePrefix: "_",
    };
    const parser = new XMLParser(options);
    const json = parser.parse(themeXml);
    themeJson = json

    // 获取合并的单元格
    const mergeCells = [];
    for (let i in worksheet._merges) {
      const { top, left, bottom, right } = worksheet._merges[i].model;
      mergeCells.push({
        row: top - 1,
        col: left - 1,
        rowspan: bottom - top + 1,
        colspan: right - left + 1
      });
    };

    // 将数据加载到HyperFormula
    hot = new Handsontable(document.getElementById('hot'), {
      // 数据
      data: hotData,
      colHeaders: true,
      rowHeaders: true,
      language: 'zh-CN',
      readOnly: true,
      width: '100%',
      height: 'calc(100% - 25px)',
      //handsontable的许可证
      licenseKey: 'non-commercial-and-evaluation',
      // 行高
      rowHeights: function (index) {
        if (sheet.getRow(index + 1).height) {
          // exceljs获取的行高不是像素值,事实上,它是23px - 13.8 的一个映射。所以需要将它转化为像素值
          return sheet.getRow(index + 1).height * (23 / 13.8);
        }
        return 23; // 默认
      },
      // 列宽
      colWidths: function (index) {
        if (sheet.getColumn(index + 1).width) {
          // exceljs获取的列宽不是像素值,事实上,它是81px - 8.22 的一个映射。所以需要将它转化为像素值
          return sheet.getColumn(index + 1).width * (81 / 8.22);
        }
        return 81; // 默认
      },
      // 自定义单元格样式
      cells: function (row, col, prop) {
        const cellProperties = {};
        const cellStyle = sheet.getCell(row + 1, col + 1).style;

        if (JSON.stringify(cellStyle) !== "{}") {
          // console.log(row+1, col+1, cellStyle);
          for (let key in cellStyle) {
              cellProperties[key] = cellStyle[key];
          }
        }
        return { ...cellProperties, renderer: "customStylesRenderer" };
      },
      // 合并单元格
      mergeCells: mergeCells
    });
    //8.将handsontable实例渲染到页面上
    hot.render();
  });

  // 根据主题和明暗度themeId获取颜色
  function getThemeColor(themeJson, themeId, tint) {
    let color = "";
    const themeColorScheme =themeJson["a:theme"]["a:themeElements"]["a:clrScheme"];
    switch (themeId) {
      case 0:
        color = themeColorScheme["a:lt1"]["a:sysClr"]["_lastClr"];
        break;
      case 1:
        color = themeColorScheme["a:dk1"]["a:sysClr"]["_lastClr"];
        break;
      case 2:
        color = themeColorScheme["a:lt2"]["a:srgbClr"]["_val"];
        break;
      case 3:
        color = themeColorScheme["a:dk2"]["a:srgbClr"]["_val"];
        break;
      default:
        color = themeColorScheme[`a:accent${themeId - 3}`]["a:srgbClr"]["_val"];
        break;
    }
    // 根据tint修改颜色深浅
    color = "#" + color;
    const colorObj = Color(color);
    if (tint) {
      if (tint > 0) {
        // 淡色
        color = colorObj.lighten(tint).hex();
      } else {
        // 深色
        color = colorObj.darken(Math.abs(tint)).hex();
      }
    }
    return color;
  }
  
  // 获取颜色
  function getColor(obj, themeJson) {
    if ("argb" in obj) {
      // 标准色
      // rgba格式去掉前两位: FFFF0000 -> FF0000
      return "#" + obj.argb.substring(2);
    } else if ("theme" in obj) {
      // 主题颜色
      if ("tint" in obj) {
        return getThemeColor(themeJson, obj.theme, obj.tint);
      } else {
        return getThemeColor(themeJson, obj.theme, null);
      }
    }
  }

  // 设置边框
  function setBorder(style) {
      let borderStyle = "solid";
      let borderWidth = "1px";
      switch (style) {
        case "thin":
          borderWidth = "thin";
          break;
        case "dotted":
          borderStyle = "dotted";
          break;
        case "dashDot":
          borderStyle = "dashed";
          break;
        case "hair":
          borderStyle = "solid";
          break;
        case "dashDotDot":
          borderStyle = "dashed";
          break;
        case "slantDashDot":
            borderStyle = "dashed";
            break;
        case "medium":
          borderWidth = "2px";
          break;
        case "mediumDashed":
          borderStyle = "dashed";
          borderWidth = "2px";
          break;
        case "mediumDashDotDot":
          borderStyle = "dashed";
          borderWidth = "2px";
          break;
        case "mdeiumDashDot":
          borderStyle = "dashed";
          borderWidth = "2px";
          break;
        case "double":
          borderStyle = "double";
          break;
        case "thick":
          borderWidth = "3px";
          break;
        default:
          break;
    }
    // console.log(borderStyle, borderWidth);
    return [borderStyle, borderWidth];
  }
</script>

如下图所示:

在这里插入图片描述

通过以上代码,我们成功将Excel文件中的数据、样式、合并单元格等信息加载到了Handsontable中,并渲染到了页面上。

对于单元格中带有公式的,按照以上代码会出现问题,就是带有公式的单元格会渲染成"[object Object]",如下图所示:

请添加图片描述

所以我们需要对公式进行处理,将公式替换为对应的值。请看以下处理公式的方法。

在遍历单元格时我们可以打印看下读取到单元格的数据具体是什么样的,然后根据具体情况进行处理,如下图所示:

在这里插入图片描述
由图可以看到带有公式的单元格是一个对象,这就是带有公式的单元格渲染后是"[object Object]"的原因。解决方法就是将带有公式的单元格替换为对应的值,请看以下代码:

worksheet.eachRow({ includeEmpty: true }, function (row, rowNumber) {
  const row_values = row.values.slice(1); // 获取行数据,并排除第一列为null的数据
  const newRowValue = [...row_values];
  newRowValue.forEach(function(item, index) {
    if(Object.prototype.toString.call(item) === "[object Object]") {
      if(item.formula) {
        // 如果是公式,则保留公式,否则将结果作为值
        newRowValue[index] = item.formula.includes("=") ? item.formula : "=" + item.result;
      }
    };
  })
  sheetData.push(newRowValue);
});
通过处理后,带有公示的单元格不在渲染为"[object Object]“,但是会渲染出具体使用了什么公式,如:”=SUM(B1:B2)“会渲染为”=2",如下图所示:

在这里插入图片描述
解决方法请看以下代码:

// 定义HyperFormula 公式配置配置
const hyperformulaInstance = HyperFormula.buildEmpty({
  licenseKey: 'internal-use-in-handsontable'
});
hot = new Handsontable(document.getElementById('hot'), {
  // 数据
  data: hotData,
  colHeaders: true,
  rowHeaders: true,
  language: 'zh-CN',
  readOnly: true,
  // 开启公式
  formulas: {
    engine: hyperformulaInstance,
    sheetName: "Sheet1",
  },
  width: '100%',
  height: 'calc(100% - 25px)',
  //handsontable的许可证
  licenseKey: 'non-commercial-and-evaluation',
})
我们开启公式后,带有公示的单元格会渲染为对应的值,如下图所示:

在这里插入图片描述
至此,我们成功将Excel文件中的数据、样式、合并单元格等信息加载到了Handsontable中,并渲染到了页面上。

使用Handsontable导出Excel文件,请看以下代码:
// 将handsontable实例导出为excel文件
document.getElementById('export').addEventListener('click', function () {
  var exportData = Handsontable.helper.createEmptySpreadsheetData(100, 100);
  hot.getData().forEach(function (row, rowIndex) {
    row.forEach(function (cell, colIndex) {
      exportData[rowIndex][colIndex] = cell;
    });
  });
  var workbook = XLSX.utils.book_new();
  var worksheet = XLSX.utils.aoa_to_sheet(exportData);
  XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
  XLSX.writeFile(workbook, 'export.xlsx');
});

此处贴出整体代码

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>基于Handsontable.js + Excel.js实现表格预览功能</title>
  <!-- handsontable的css文件 https://cdn.jsdelivr.net/npm/handsontable/dist/handsontable.full.min.css -->
  <link rel="stylesheet" href="./lib/handsontable.full.min.css">
  <style>
    * {
        margin: 0;
        padding: 0;
    }

    html,
    body {
        width: 100%;
        height: 100%;
    }
  </style>
</head>

<body>
  <input type="file" id="file">
  <button id="btn">预览</button>
  <button id="export">导出</button>
  <!-- handsontable的容器 -->
  <div id="hot"></div>
  <!-- https://cdn.jsdelivr.net/npm/color-js -->
  <script src="./lib/color-js.js"></script>
  <!-- https://cdn.jsdelivr.net/npm/handsontable/dist/handsontable.full.min.js -->
  <script src="./lib/handsontable.full.min.js"></script>
  <!-- https://cdn.jsdelivr.net/npm/handsontable/dist/languages/zh-CN.js -->
  <script src="./lib/zh-CN.js"></script>
  <!-- https://cdnjs.cloudflare.com/ajax/libs/hyperformula/1.4.0/hyperformula.min.js -->
  <script src="./lib/hyperformula.full.min.js"></script>
  <!-- https://cdn.jsdelivr.net/npm/exceljs/dist/exceljs.min.js -->
  <script src="./lib/exceljs.min.js"></script>
  <!-- https://cdn.jsdelivr.net/npm/xlsx/dist/xlsx.full.min.js -->
  <script src="./lib/xlsx.full.min.js"></script>
  <!-- https://cdn.jsdelivr.net/npm/fxparser@1.0.0/dist/fxparser.min.js -->
  <script src="./lib/fxparser.min.js"></script>
    
  <script>
      //在html中使用excel+handsontable实现excel预览功能
      window.onload = function () {
        //1.引入handsontable的css和js文件
        //3.获取excel文件
        var hot; //handsontable实例
        var themeJson; //主题json
        var sheet; //当前sheet
        var Color = net.brehaut.Color; //引入color-js库
        
        // 自定义渲染器函数
        function customRenderer(hotInstance, td, row, column, prop, value, cellProperties) {
            Handsontable.renderers.TextRenderer(hotInstance, td, row, column, prop, value, cellProperties);
            // 填充样式
            if ("fill" in cellProperties) {
              // 背景颜色
              if ("fgColor" in cellProperties.fill && cellProperties.fill.fgColor) {
                td.style.background = getColor(
                  cellProperties.fill.fgColor,
                  themeJson
                );
              }
            }
            // 字体样式
            if ("font" in cellProperties) {
              // 加粗
              if ("bold" in cellProperties.font && cellProperties.font.bold) {
                td.style.fontWeight = "700";
              }
              // 字体颜色
              if ("color" in cellProperties.font && cellProperties.font.color) {
                td.style.color = getColor(cellProperties.font.color, themeJson);
              }
              // 字体大小
              if ("size" in cellProperties.font && cellProperties.font.size) {
                td.style.fontSize = cellProperties.font.size + "px";
              }
              // 字体类型
              if ("name" in cellProperties.font && cellProperties.font.name) {
                td.style.fontFamily = cellProperties.font.name;
              }
              // 字体倾斜
              if ("italic" in cellProperties.font && cellProperties.font.italic) {
                td.style.fontStyle = "italic";
              }
              // 下划线
              if (
                "underline" in cellProperties.font &&
                cellProperties.font.underline
              ) {
                // 其实还有双下划线,但是双下划綫css中没有提供直接的设置方式,需要使用额外的css设置,所以我也就先懒得弄了
                td.style.textDecoration = "underline";
                // 删除线
                if ("strike" in cellProperties.font && cellProperties.font.strike) {
                  td.style.textDecoration = "underline line-through";
                }
              } else {
                // 删除线
                if ("strike" in cellProperties.font && cellProperties.font.strike) {
                  td.style.textDecoration = "line-through";
                }
              }
            }
            // 对齐
            if ("alignment" in cellProperties) {
              if ("horizontal" in cellProperties.alignment) {
                // 水平
                // 这里我直接用handsontable内置类做了,设置成类似htLeft的样子。
                //(handsontable)其实至支持htLeft, htCenter, htRight, htJustify四种,但是其是它还有centerContinuous、distributed、fill,遇到这几种就会没有效果,也可以自己设置,但是我还是懒的弄了,用到的时候再说吧
                const name = cellProperties.alignment.horizontal.charAt(0).toUpperCase() +cellProperties.alignment.horizontal.slice(1);
                td.classList.add(`ht${name}`);
              }
              if ("vertical" in cellProperties.alignment) {
                // 垂直
                // 这里我直接用handsontable内置类做了,设置成类似htTop的样子。
                const name =
                cellProperties.alignment.vertical.charAt(0).toUpperCase() +
                cellProperties.alignment.vertical.slice(1);
                td.classList.add(`ht${name}`);
              }
            }
            // 边框
            if ("border" in cellProperties) {
                if ("left" in cellProperties.border && cellProperties.border.left) {
                  // 左边框
                  const [borderWidth, borderStyle] = setBorder(
                    cellProperties.border.left.style
                  );
                  let color = "";
                  // console.log(row, column, borderWidth, borderStyle);
                  if (cellProperties.border.left.color) {
                    color = getColor(cellProperties.border.left.color, themeJson);
                  }
                  td.style.borderLeft = `${borderStyle} ${borderWidth} ${color}`;
                }
                if ("right" in cellProperties.border && cellProperties.border.right) {
                  // 左边框
                  const [borderWidth, borderStyle] = setBorder(
                    cellProperties.border.right.style
                  );
                  // console.log(row, column, borderWidth, borderStyle);
                  let color = "";
                  if (cellProperties.border.right.color) {
                    color = getColor(cellProperties.border.right.color, themeJson);
                  }
                  td.style.borderRight = `${borderStyle} ${borderWidth} ${color}`;
                }
                if ("top" in cellProperties.border && cellProperties.border.top) {
                  // 左边框
                  const [borderWidth, borderStyle] = setBorder(
                    cellProperties.border.top.style
                  );
                  let color = "";
                  // console.log(row, column, borderWidth, borderStyle);
                  if (cellProperties.border.top.color) {
                    color = getColor(cellProperties.border.top.color, themeJson);
                  }
                  td.style.borderTop = `${borderStyle} ${borderWidth} ${color}`;
                }
                if ("bottom" in cellProperties.border && cellProperties.border.bottom) {
                  // 左边框
                  const [borderWidth, borderStyle] = setBorder(
                    cellProperties.border.bottom.style
                  );
                  let color = "";
                  // console.log(row, column, borderWidth, borderStyle);
                  if (cellProperties.border.bottom.color) {
                    color = getColor(cellProperties.border.bottom.color, themeJson);
                  }
                  td.style.borderBottom = `${borderStyle} ${borderWidth} ${color}`;
                }
            }
        }

        // 在 Handsontable 初始化之前注册渲染器
        Handsontable.renderers.registerRenderer('customStylesRenderer', customRenderer);
          
        // 点击预览
        document.getElementById('btn').addEventListener('click', async function () {
          var hotData = null; // Handsontable 数据
          var file = document.getElementById('file').files[0]; // 获取文件对象
          const workbook = new ExcelJS.Workbook(); // 创建一个工作簿对象
          await workbook.xlsx.load(file); // 加载Excel文件
          const worksheet = workbook.getWorksheet(1); // 获取第一个工作表
          sheet = worksheet; // 将工作表赋值给全局变量

          // 遍历工作表中的所有行(包括空行)
          const sheetData = [];
          worksheet.eachRow({ includeEmpty: true }, function (row, rowNumber) {
              const row_values = row.values.slice(1); // 获取行数据,并排除第一列为null的数据
              const newRowValue = [...row_values];
              newRowValue.forEach(function(item, index) {
                  if(Object.prototype.toString.call(item) === "[object Object]") {
                      if(item.formula) {
                          // 如果是公式,则保留公式,否则将结果作为值
                          newRowValue[index] = item.formula.includes("=") ? item.formula : "=" + item.result;
                      }
                  };
              })
              sheetData.push(newRowValue);
          });
          // 将数据赋值给Handsontable
          hotData = sheetData; 

          // 将主题xml转换成json
          const themeXml = workbook._themes.theme1;
          const options = {
              ignoreAttributes: false,
              attributeNamePrefix: "_",
          };
          const parser = new XMLParser(options);
          const json = parser.parse(themeXml);
          themeJson = json

          // 获取合并的单元格
          const mergeCells = [];
          for (let i in worksheet._merges) {
              const { top, left, bottom, right } = worksheet._merges[i].model;
              mergeCells.push({
                  row: top - 1,
                  col: left - 1,
                  rowspan: bottom - top + 1,
                  colspan: right - left + 1,
              });
          };

          // 定义HyperFormula配置
          const hyperformulaInstance = HyperFormula.buildEmpty({
            licenseKey: 'internal-use-in-handsontable'
          });
          // 将数据加载到HyperFormula
          hot = new Handsontable(document.getElementById('hot'), {
              // 数据
              data: hotData,
              colHeaders: true,
              rowHeaders: true,
              language: 'zh-CN',
              readOnly: true,
              // 公式
              formulas: {
                  engine: hyperformulaInstance,
                  sheetName: "Sheet1",
              },
              width: '100%',
              height: 'calc(100% - 25px)',
              //handsontable的许可证
              licenseKey: 'non-commercial-and-evaluation',
              // 行高
              rowHeights: function (index) {
                  if (sheet.getRow(index + 1).height) {
                      // exceljs获取的行高不是像素值,事实上,它是23px - 13.8 的一个映射。所以需要将它转化为像素值
                      return sheet.getRow(index + 1).height * (23 / 13.8);
                  }
                  return 23; // 默认
              },
              // 列宽
                colWidths: function (index) {
                  if (sheet.getColumn(index + 1).width) {
                  // exceljs获取的列宽不是像素值,事实上,它是81px - 8.22 的一个映射。所以需要将它转化为像素值
                  return sheet.getColumn(index + 1).width * (81 / 8.22);
                }
                return 81; // 默认
              },
              // 自定义单元格样式
              cells: function (row, col, prop) {
                  const cellProperties = {};
                  const cellStyle = sheet.getCell(row + 1, col + 1).style;

                  if (JSON.stringify(cellStyle) !== "{}") {
                      // console.log(row+1, col+1, cellStyle);
                      for (let key in cellStyle) {
                          cellProperties[key] = cellStyle[key];
                      }
                  }
                  return { ...cellProperties, renderer: "customStylesRenderer" };
              },
            // 合并单元格
              mergeCells: mergeCells
            });
            //将handsontable实例渲染到页面上
            hot.render();
          });
          
        // 将handsontable实例导出为excel文件
        document.getElementById('export').addEventListener('click', function () {
          var exportData = Handsontable.helper.createEmptySpreadsheetData(100, 100);
          hot.getData().forEach(function (row, rowIndex) {
              row.forEach(function (cell, colIndex) {
                exportData[rowIndex][colIndex] = cell;
              });
          });
          var workbook = XLSX.utils.book_new();
          var worksheet = XLSX.utils.aoa_to_sheet(exportData);
          XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
          XLSX.writeFile(workbook, 'export.xlsx');
        });
          
        // 根据主题和明暗度themeId获取颜色
        function getThemeColor(themeJson, themeId, tint) {
            let color = "";
            const themeColorScheme = themeJson["a:theme"]["a:themeElements"]["a:clrScheme"];
            switch (themeId) {
              case 0:
                color = themeColorScheme["a:lt1"]["a:sysClr"]["_lastClr"];
                break;
              case 1:
                color = themeColorScheme["a:dk1"]["a:sysClr"]["_lastClr"];
                break;
              case 2:
                color = themeColorScheme["a:lt2"]["a:srgbClr"]["_val"];
                break;
              case 3:
                color = themeColorScheme["a:dk2"]["a:srgbClr"]["_val"];
                break;
              default:
                color = themeColorScheme[`a:accent${themeId - 3}`]["a:srgbClr"]["_val"];
                break;
            }
            // 根据tint修改颜色深浅
            color = "#" + color;
            const colorObj = Color(color);
            if (tint) {
              if (tint > 0) {
                // 淡色
                color = colorObj.lighten(tint).hex();
              } else {
                // 深色
                color = colorObj.darken(Math.abs(tint)).hex();
              }
          }
          return color;
        }
          
        // 获取颜色
        function getColor(obj, themeJson) {
          if ("argb" in obj) {
            // 标准色
            // rgba格式去掉前两位: FFFF0000 -> FF0000
            return "#" + obj.argb.substring(2);
          } else if ("theme" in obj) {
            // 主题颜色
            if ("tint" in obj) {
              return getThemeColor(themeJson, obj.theme, obj.tint);
            } else {
              return getThemeColor(themeJson, obj.theme, null);
            }
          }
        }

        // 设置边框
        function setBorder(style) {
          let borderStyle = "solid";
          let borderWidth = "1px";
          switch (style) {
            case "thin":
              borderWidth = "thin";
              break;
            case "dotted":
              borderStyle = "dotted";
              break;
            case "dashDot":
              borderStyle = "dashed";
              break;
            case "hair":
              borderStyle = "solid";
              break;
            case "dashDotDot":
              borderStyle = "dashed";
              break;
            case "slantDashDot":
              borderStyle = "dashed";
              break;
            case "medium":
              borderWidth = "2px";
              break;
            case "mediumDashed":
              borderStyle = "dashed";
              borderWidth = "2px";
              break;
            case "mediumDashDotDot":
              borderStyle = "dashed";
              borderWidth = "2px";
              break;
            case "mdeiumDashDot":
              borderStyle = "dashed";
              borderWidth = "2px";
              break;
            case "double":
              borderStyle = "double";
              break;
            case "thick":
              borderWidth = "3px";
              break;
            default:
              break;
          }
          // console.log(borderStyle, borderWidth);
          return [borderStyle, borderWidth];
        }
      }
  </script>
</body>

</html>

参考文献:
前端实现(excel)xlsx文件预览

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2207492.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

openpdf

1、简介 2、示例 2.1 引入依赖 <dependency><groupId>com.github.librepdf</groupId><artifactId>openpdf</artifactId><version>1.3.34</version></dependency><dependency><groupId>com.github.librepdf</…

正点原子STM32F407ZG 开发板简介

1. STM32F407ZG 学习资料 1&#xff09;ST 官方的学习资料 ST 官方资料有两个网址&#xff1a; www.stmcu.org.cn 和 www.st.com 。 www.stmcu.org.cn 是 ST 中文社区&#xff0c;里面的资料全部由 ST 中国区的人负责更新和整理&#xff0c;包含了所有 ST 公司的 …

计算机的错误计算(一百二十)

摘要 探讨在许多应用中出现的函数 的计算精度问题。 例1. 考虑在许多应用中出现的函数 计算 不妨在Python下计算&#xff1a; 若用下列Rust代码在线计算&#xff1a; fn f(x: f64) -> f64 {(x.exp() - 1.0) / x }fn main() {let result f(0.9e-13);println!("…

微知-Bluefield DPU命名规则各字段作用?BF2 BF3全系列命名大全

文章目录 背景字段命名C是bmc的意思NOT的N是是否加密S表示不加密但是secureboot enable倒数第四个都是E倒数第五个是速率 V和H是200GM表示E serials&#xff0c;H表示P serials&#xff08;区别参考兄弟篇&#xff1a;[more](https://blog.csdn.net/essencelite/article/detail…

【通信协议讲解】单片机基础重点通信协议解析与总结(IIC,CAN,MODBUS...)

目录 一.IIC总线 基础特性&#xff1a; 配置特性&#xff1a; 时序特性&#xff1a; 二.SPI总线 基础特性&#xff1a; 配置特性&#xff1a; 时序特性&#xff1a; 三.串口通信 基础特性&#xff1a; 配置特性&#xff1a; 时序特性&#xff1a; 四.CAN总线 基础特性…

vue后台管理系统从0到1(5)

文章目录 vue后台管理系统从0到1&#xff08;5&#xff09;完善侧边栏修改bug渲染header导航栏 vue后台管理系统从0到1&#xff08;5&#xff09; 接上一期&#xff0c;我们需要完善我们的侧边狼 完善侧边栏 我们在 element 组件中可以看见&#xff0c;这一个侧边栏是符合我们…

I/O进程(Day26)

一、学习内容 I/O进程 标准IO 概念 针对文件的读写操作 文件IO最终达成的目的&#xff1a;将一个临时存在于内存中的数据&#xff0c;永久性的存放于磁盘当中 操作 文件IO的操作&#xff0c;需要这样的2个指针 一个指针&#xff1a;指向源数据&#xff0c;提供读取操作的指针 …

复杂系统学习

一、复杂网络分析在复杂性研究中的地位 1.复杂系统 系统中存在的复杂度从两个维度来看 ①系统自由度&#xff08;系统组成成分的数目&#xff09; ②相互作用&#xff08;线性到非线性的转换&#xff09; 复杂网络是复杂系统的骨架 复杂系统可以抽象成一个网络&#xff0…

大数据新视界 --大数据大厂之 Dremio:改变大数据查询方式的创新引擎

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

【JVM】如何判断对象是否可以被回收

引用计数法&#xff1a; 在对象中添加一个引用计数器&#xff0c;每当有一个地方引用它时&#xff0c;计数器值就加一&#xff1b;当引用失效时&#xff0c;计数器值就减一&#xff1b;任何时刻计数器为零的对象就是不可能再被使用的。 优点&#xff1a;实现简单&#xff0c;判…

Visual Studio--VS安装配置使用教程

Visual Studio Visual Studio 是一款功能强大的开发人员工具&#xff0c;可用于在一个位置完成整个开发周期。 它是一种全面的集成开发环境 (IDE)。对新手特别友好&#xff0c;使用方便&#xff0c;不需要复杂的去配置环境。用它学习很方便。 Studio安装教程 Visual Studio官…

从这里看BD仓储如何改变物流效率?

BD仓储物流建设成为当代物流领域的核心要素&#xff0c;推动着整个行业朝向高效性与智能化水平不断提升。在BD仓储物流的创新浪潮中&#xff0c;RFID技术犹如一颗耀眼的明珠&#xff0c;凭借其无可比拟的特性获得了业界的广泛推崇与广泛应用。该技术通过无线信号与电子标签的互…

Python剪辑视频

import os from moviepy.editor import VideoFileClipvideo_dir r"E:\学习\视频剪辑" s_video_file "1.mp4" d_video_file "剪辑片段1.mp4" s_video_path os.path.join(video_dir, s_video_file) # 原视频文件路径 d_video_path os.path…

FDTD Solutions(时域有限差分)仿真技术与应用

FDTD Solutions是一款非常好用的微纳光学设计工具。该软件提供了丰富的设计功能&#xff0c;支持CMOS图像传感器&#xff0c;OLED和液晶&#xff0c;表面计量&#xff0c;表面等离子体&#xff0c;石墨烯&#xff0c;太阳能电池&#xff0c;集成光子组件&#xff0c;超材料&…

排序|归并排序|递归|非递归|计数排序(C)

归并排序 如果数组的左半区间有序&#xff0c;右半区间有序&#xff0c;可以直接进行归并 基本思想 快排是一种前序&#xff0c;归并是后序 每次取小尾插 void _MergeSort(int* a, int* tmp, int begin, int end) {if (end < begin)return;int mid (end begin) / 2;/…

go开发环境设置-安装与交叉编译

1. 引言 Go语言&#xff0c;又称Golang&#xff0c;是Google开发的一门编程语言&#xff0c;以其高效、简洁和并发编程的优势受到广泛欢迎。作为一门静态类型、编译型语言&#xff0c;Go在构建网络服务器、微服务和命令行工具方面表现突出。 在开发过程中&#xff0c;开发者常…

PyCharm打开及配置现有工程(详细图解)

本文详细介绍了如何利用Pycharm打开一个现有的工程&#xff0c;其中包括编译器的配置。 PyCharm打开及配置现有工程 1、打开工程2、配置编译器 1、打开工程 双击PyCharm软件&#xff0c;点击左上角 文件 >> 打开(O)… 选中想要打开的项目之后点击“确定” 2、配置编译器…

STM32学习--3-5 光敏控制传感器控制蜂鸣器

接线图 Buzzer.c #include "stm32f10x.h" // Device header void Buzzer_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode GPIO_Mode_O…

Microsoft Visual Studio安装gtest

1. 参考【Windows Visual Studio下安装和使用google test&#xff08;gtest&#xff09;】 https://blog.csdn.net/Bule_Zst/article/details/78420894 2. 编译gtest使用Win32模式。 3. 配置属性&#xff0c;C/C&#xff0c;常规&#xff0c;附加包含目录 …

【画质模组】古墓丽影mod,调色并修改光影,游戏画质大提升

大家好&#xff0c;今天小编我给大家继续引入一款游戏mod&#xff0c;这次这个模组主要是针对雷神之锤4进行修改&#xff0c;如果你觉得游戏本身光影有缺陷&#xff0c;觉得游戏色彩有点失真的话&#xff0c;或者说你想让雷神之锤4这款游戏增加对光线追踪的支持的话&#xff0c…