ExcelJS实现导入转换HTML展示(附源码可直接使用)

发布于:2025-08-30 ⋅ 阅读:(14) ⋅ 点赞:(0)

目录

简介

开始实践

难点

文件示例

效果预览

具体实现

安装

完整代码

总结


简介

在日常工作中,我们可能会遇到需要上传并展示 Excel 文件的需求,实现文件内容的在线预览。 这里给大家接收一个组件库exceljs,这个组件库进过实践发现可以实现我们需要的功能。在这里我为了方便使用了(技术栈 Vue 3 + Element Ui 笔者自己使用的项目模板)来实现该功能。

开始实践

NPM地址 内部有中文文档

难点

基础单元格和合并单元格和空白单元格的混合处理

文字样式和背景样式的读取映射

文件示例

这里直接造了一个两个sheet的excel做测试

效果预览

具体实现

安装

npm install exceljs

完整代码

<template>
  <div>
    <el-upload
      action=""
      :auto-upload="false"
      :show-file-list="true"
      :on-change="handleFileUpload"
      accept=".xlsx,.xls"
    >
      <el-button type="primary"> 上传 Excel </el-button>
    </el-upload>
 
    <!-- 渲染 Excel 生成的 HTML 表格 -->
    <div v-html="tableHtml" />
  </div>
</template>
 
<script setup>
import { ref } from "vue";
import * as ExcelJS from "exceljs";
 
const tableHtml = ref(""); // 存储 HTML 表格内容
const themeColors = {
  0: "#FFFFFF", // 白色 √
  1: "#000000", // 黑色 √
  2: "#C9CDD1", // 灰色 √
  3: "#4874CB", // 蓝色 √
  4: "#D9E1F4", // 浅蓝 √
  5: "#F9CBAA", // 橙色 √
  6: "#F2BA02", // 浅橙 √
  7: "#00FF00", // 浅绿 √
  8: "#30C0B4", // 青色 √
  9: "#E54C5E", // 红色 √
  10: "#FFC7CE", // 浅红
  11: "#7030A0", // 紫色
};
 
// 获取单元格颜色
const getCellColor = (cell) => {
  if (cell.fill && cell.fill.fgColor) {
    if (cell.fill.fgColor.argb) {
      return `#${cell.fill.fgColor.argb.substring(2)}`; // ARGB 转 RGB
    }
    if (cell.fill.fgColor.theme !== undefined) {
      return themeColors[cell.fill.fgColor.theme] || "#FFFFFF"; // 主题色转换
    }
  }
 
  return ""; // 无颜色
};
// 获取单元格字体颜色
const getCellFontColor = (cell) => {
  if (cell.font && cell.font.color && cell.font.color.argb) {
    return `#${cell.font.color.argb.substring(2)}`; // ARGB 转 RGB
  }
  if (cell.font && cell.font.color && cell.font.color.theme) {
    return themeColors[cell.font.color.theme] || "#000"; // 主题色转换
  }
 
  return "#000"; // 默认黑色
};
const handleStyles = (cell) => {
  let styles = [];
 
  // 读取字体颜色
  styles.push(`color: ${getCellFontColor(cell)}`);
  // 读取背景色
  styles.push(`background-color: ${getCellColor(cell)}`);
 
  // 加粗
  if (cell.font && cell.font.bold) {
    styles.push("font-weight: bold");
  }
 
  // 文字对齐
  if (cell.alignment) {
    if (cell.alignment.horizontal) {
      styles.push(`text-align: ${cell.alignment.horizontal}`);
    }
    if (cell.alignment.vertical) {
      styles.push(`vertical-align: ${cell.alignment.vertical}`);
    }
  }
 
  return styles.join("; ");
};
 
// 获取工作表维度信息
const getWorksheetDimensions = (worksheet) => {
  let maxRow = 0;
  let maxCol = 0;
  
  worksheet.eachRow((row, rowIndex) => {
    maxRow = Math.max(maxRow, rowIndex);
    row.eachCell((cell, colIndex) => {
      maxCol = Math.max(maxCol, colIndex);
    });
  });
  
  return { maxRow, maxCol };
};

// 处理上传的 Excel 文件
const handleFileUpload = async (file) => {
  const excelData = await readExcel(file.raw);
  tableHtml.value = excelData; // 更新 HTML 表格内容
};
// 处理常规单元格内容
const handleValueSimple = (value) => {
  if (!value) return "&nbsp;";
  
  if (typeof value === "object" && value.richText) {
    const valueStr = value.richText.reduce((acc, curr) => {
      let colorValue = "";
      if (curr.font && curr.font.color && curr.font.color.theme) {
        colorValue = getCellFontColor(curr) || `#000`;
      }
      if (curr.font && curr.font.color && curr.font.color.argb) {
        colorValue = `#${curr.font.color.argb.substring(2)}`;
      } else {
        colorValue = `#000`;
      }
 
      return acc + `<span style="color:${colorValue}">${curr.text}</span>`;
    }, "");
 
    return valueStr;
  }
 
  return value.toString();
};
// 处理合并单元格内容
const handleValue = (value) => {
  if (!value) return "&nbsp;";
  
  if (typeof value === "object" && value.richText) {
    const valueArr = value.richText.reduce((acc, curr) => {
      let colorValue = "";
      if (curr.font && curr.font.color && curr.font.color.argb) {
        colorValue = `#${curr.font.color.argb.substring(2)}`;
      } else {
        colorValue = `#000`;
      }
 
      const newData = curr.text
        .split(/\r/)
        .map((item) => `<p style="color:${colorValue}">${item}</p>`);
      return acc.concat(newData);
    }, []);
 
    return valueArr.join("").replace(/\n/g, "<br />");
  }
 
  return value.toString();
};
 
let worksheetIds = [];
// 读取 Excel 并转换成 HTML
const readExcel = async (file) => {
  const workbook = new ExcelJS.Workbook();
  const arrayBuffer = await file.arrayBuffer();
  const { worksheets } = await workbook.xlsx.load(arrayBuffer);
  worksheetIds = worksheets.map((v) => v.id); // 获取工作表 ID集合
 
  let allHtml = "";
  workbook.eachSheet(function (worksheet, sheetId) {
    // 处理合并单元格
    const merges = worksheet?.model?.merges || [];
    const currentSheetIndex = worksheetIds.indexOf(sheetId); // 获取当前工作表的索引
    
    // 获取工作表维度
    const { maxRow, maxCol } = getWorksheetDimensions(worksheet);
 
    allHtml +=
      '<table border="1" style="border-collapse: collapse;width:100%;margin-bottom: 20px;">';
    
    // 使用双重循环确保每个单元格位置都被处理
    for (let rowIndex = 1; rowIndex <= maxRow; rowIndex++) {
      allHtml += "<tr>";
      
      for (let colIndex = 1; colIndex <= maxCol; colIndex++) {
        const cell = worksheet.getCell(rowIndex, colIndex);
        let cellValue = cell.value || "";
        
        // 检查当前单元格是否在合并范围内
        let isInMerge = false;
        let isMergeStart = false;
        let rowspan = 1;
        let colspan = 1;
        
        for (let merge of merges) {
          const [start, end] = merge.split(":");
          const startCell = worksheet.getCell(start);
          const endCell = worksheet.getCell(end);
          const startRow = startCell.row;
          const startCol = startCell.col;
          const endRow = endCell.row;
          const endCol = endCell.col;
          
          if (rowIndex >= startRow && rowIndex <= endRow && 
              colIndex >= startCol && colIndex <= endCol) {
            isInMerge = true;
            
            if (rowIndex === startRow && colIndex === startCol) {
              isMergeStart = true;
              rowspan = endRow - startRow + 1;
              colspan = endCol - startCol + 1;
            }
            break;
          }
        }
        
        // 如果是合并单元格的起始位置,创建合并单元格
        if (isMergeStart) {
          let styles = handleStyles(cell);
          const mergeValue = cellValue || "&nbsp;";
          allHtml += `<td rowspan="${rowspan}" colspan="${colspan}" style="${styles}">
            ${handleValue(mergeValue)}</td>`;
        }
        // 如果不在合并范围内,创建普通单元格
        else if (!isInMerge) {
          let styles = handleStyles(cell);
          const displayValue = cellValue ? handleValueSimple(cellValue) : "&nbsp;";
          allHtml += `<td style="${styles}">${displayValue}</td>`;
        }
        // 如果单元格在合并范围内但不是起始位置,跳过(由合并单元格处理)
      }
      
      allHtml += "</tr>";
    }
 
    allHtml += "</table>";
  });
 
  return allHtml;
};
</script>

总结

exceljs功能很多,这里给大家介绍了execljs的一种用法,实现导入转换html页面显示,便于浏览。大家感兴趣可以去翻翻文档  NPM地址 内部有中文文档,exceljs功能很强大推荐大家自己尝试一下。