使用exceljs将excel文件转化为html预览最佳实践(完整源码)

news2025/5/21 23:44:37

前言

在企业应用中,我们时常会遇到需要上传并展示 Excel 文件的需求,以实现文件内容的在线预览。经过一番探索与尝试,笔者最终借助 exceljs 这一库成功实现了该功能。本文将以 Vue 3 为例,演示如何实现该功能,代码示例可直接复制运行,希望能为大家在处理类似问题时提供新的思路和解决方案。

技术难点

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

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

富文本内容格式的处理

excel文件展示

实际实现预览效果

核心代码

exceljs提供了合并区域的数据,我们只需要根据合并区域去判断什么时候该合并,就能很好的实现基础单元格和合并单元格的混合绘制

  for (let merge of merges) {
          const [start, end] = merge.split(":");
          const startCell = worksheet.getCell(start);
          const endCell = worksheet.getCell(end);
          const startRow = startCell.row,
            startCol = startCell.col;
          const endRow = endCell.row,
            endCol = endCell.col;

          if (startRow === rowIndex && startCol === colIndex) {
            rowspan = endRow - startRow + 1;
            colspan = endCol - startCol + 1;
            isMerged = true;
            let styles = handleStyles(cell);
            allHtml += `<td rowspan="${rowspan}" colspan="${colspan}" style="${styles}">
              ${handleValue(startCell.value)}</td>`;
            break;
          }
          if (
            rowIndex >= startRow &&
            rowIndex <= endRow &&
            colIndex >= startCol &&
            colIndex <= endCol
          ) {
            isMerged = true;
            break;
          }
        }

完整源码

<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("; ");
};

// 处理上传的 Excel 文件
const handleFileUpload = async (file) => {
  const excelData = await readExcel(file.raw);
  tableHtml.value = excelData; // 更新 HTML 表格内容
};
// 处理常规单元格内容
const handleValueSimple = (value) => {
  if (value && 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 ? value : "";
};
// 处理合并单元格内容
const handleValue = (value) => {
  if (value && 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 ? value : "";
};

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); // 获取当前工作表的索引

    allHtml +=
      '<table border="1" style="border-collapse: collapse;width:100%;margin-bottom: 20px;">';
    worksheet.eachRow((row, rowIndex) => {
      allHtml += "<tr>";

      row.eachCell((cell, colIndex) => {
        let cellValue = cell.value || "";

        // 处理合并单元格
        let rowspan = 1,
          colspan = 1;
        let isMerged = false;

        for (let merge of merges) {
          const [start, end] = merge.split(":");
          const startCell = worksheet.getCell(start);
          const endCell = worksheet.getCell(end);
          const startRow = startCell.row,
            startCol = startCell.col;
          const endRow = endCell.row,
            endCol = endCell.col;

          if (startRow === rowIndex && startCol === colIndex) {
            rowspan = endRow - startRow + 1;
            colspan = endCol - startCol + 1;
            isMerged = true;
            let styles = handleStyles(cell);
            allHtml += `<td rowspan="${rowspan}" colspan="${colspan}" style="${styles}">
              ${handleValue(startCell.value)}</td>`;
            break;
          }
          if (
            rowIndex >= startRow &&
            rowIndex <= endRow &&
            colIndex >= startCol &&
            colIndex <= endCol
          ) {
            isMerged = true;
            break;
          }
        }

        if (!isMerged) {
          let styles = handleStyles(cell);
          // 生成 HTML 单元格
          allHtml += `<td ${rowspan > 1 ? `rowspan="${rowspan}"` : ""} ${
            colspan > 1 ? `colspan="${colspan}"` : ""
          } style="${styles}">${handleValueSimple(cellValue)}</td>`;
        }
      });

      allHtml += "</tr>";
    });

    allHtml += "</table>";
  });

  return allHtml;
};
</script>

拓展

exceljs这个库的作用是啥?

ExcelJS 是一个功能强大的库,用于读取、操作和写入 Excel 文件(.xlsx 和 .csv 格式)。它允许开发者通过编程方式处理 Excel 文档,包括创建新工作簿、添加数据、格式化单元格、插入图表等。这个库可以在服务器端(如 Node.js 环境)或客户端(如在浏览器中使用 Webpack 或 Rollup 等工具打包的 JavaScript 应用程序)使用。

主要特性

  • 创建工作簿和工作表:可以轻松地创建新的 Excel 文件或修改已有的文件。
  • 丰富的样式支持:支持字体、颜色、边框、对齐方式等多种样式设置。
  • 数据处理:支持从各种数据源导入数据,并能将数据导出为 Excel 文件。
  • 公式和计算:可以添加公式到单元格,并支持基本的 Excel 计算。
  • 图表支持:能够在 Excel 文件中创建图表。
  • 图片和绘图:支持向 Excel 文件中添加图片和绘制图形。

使用场景

ExcelJS 广泛应用于需要与 Excel 文件进行交互的应用程序开发中,比如:

  • 数据报告生成
  • 数据导入/导出功能实现
  • 在线 Excel 编辑器

示例代码片段

这里是一个简单的示例,展示如何使用 ExcelJS 创建一个新的工作簿并添加一些数据:

const ExcelJS = require('exceljs');

// 创建一个新的工作簿
let workbook = new ExcelJS.Workbook();
let worksheet = workbook.addWorksheet('测试工作表');

// 添加一行数据
worksheet.addRow(['姓名', '年龄', '邮箱']);
worksheet.addRow(['张三', 28, 'zhangsan@example.com']);
worksheet.addRow(['李四', 23, 'lisi@example.com']);

// 保存工作簿到文件
workbook.xlsx.writeFile('example.xlsx')
    .then(() => {
        console.log('文件保存成功');
    });

 这个例子展示了如何创建一个新的 Excel 文件,并向其中添加一些简单数据。ExcelJS 的灵活性和强大功能使其成为处理 Excel 文件的一个优秀选择。

npm上的puppeteer库是做什么的?

npm 上的 puppeteer 是一个用于自动化控制 Chrome/Chromium 浏览器的 Node.js 库,由 Google 团队开发。它通过提供高级 API,允许你以编程方式模拟用户在浏览器中的操作,适用于多种场景:

核心功能

  1. 无头浏览器控制
    可启动 Headless 模式(无界面)或完整浏览器,执行自动化任务(如点击、输入、导航等)。

  2. 动态内容抓取
    适用于爬取 JavaScript 渲染的页面(如 React/Vue 单页应用),传统爬虫工具难以直接获取动态内容。

  3. 生成截图与 PDF
    精确截取网页全屏、指定区域,或将页面导出为 PDF(保留样式)。

  4. 自动化测试
    模拟用户操作,测试网页功能(如表单提交、UI 交互),生成测试报告。

  5. 性能分析
    监控页面加载速度、资源请求,优化性能。

 典型使用场景

  • 数据抓取:爬取电商价格、社交媒体内容等动态加载的数据。

  • 自动化操作:自动填写表单、批量下载文件、定时签到等。

  • 预渲染:为 SEO 生成静态化内容,解决 SPA 首屏加载问题。

  • 生成报告:将数据可视化页面导出为 PDF 或图片存档。

 基础示例

const puppeteer = require('puppeteer');

(async () => {
  // 启动浏览器(默认 Headless 模式)
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  
  // 导航到目标页面
  await page.goto('https://example.com');
  
  // 截图并保存
  await page.screenshot({ path: 'example.png' });
  
  // 关闭浏览器
  await browser.close();
})();

安装与依赖

 通过 npm 安装(会自动下载匹配的 Chromium):

npm install puppeteer

优势

 

  • 直接操控浏览器:行为更接近真实用户,绕过反爬虫机制(需合理使用)。

  • 功能全面:支持网络请求拦截、Cookie 管理、模拟设备(如手机/平板)等。

  • 社区活跃:丰富的文档和第三方工具(如 puppeteer-cluster 多任务优化)。

若你需要处理复杂的网页交互或动态内容,Puppeteer 是一个高效且灵活的选择。

vue中为什么切换动态组件需要使用shallowRef ?

在 Vue 3 中,使用 shallowRef 来管理动态组件的切换,主要是出于性能优化和避免不必要的深度响应式追踪的考虑。以下是详细解释:

1. ref vs shallowRef 的核心区别

会对其包裹的值进行深度响应式转换(递归将对象属性转为响应式)。

仅对值的引用变化进行响应式追踪,不会深度递归转换内部属性。

2. 动态组件的场景分析

当使用 <component :is="currentComponent"> 切换组件时:

  • 组件对象本身是稳定的
    动态组件的核心操作是替换整个组件对象(如 currentComponent.value = NewComponent),而不是修改组件对象的内部属性。
  • 组件对象可能很大
    一个组件对象(如导入的 Vue 组件)通常包含大量属性(如 props、methods、生命周期等),如果使用 ref 深度响应式化,会带来额外性能开销。
  • 深度响应式可能导致问题
    某些组件属性(如函数方法)被 Vue 代理后可能产生副作用(如破坏函数内部 this 绑定,或与第三方库预期结构冲突)。

3. 为什么 shallowRef 更合适?

  • 性能优化
    避免深度遍历组件对象的所有属性,减少不必要的响应式代理。
  • 符合实际需求
    动态组件切换只需要响应组件引用的变化(整体替换),无需关心组件内部属性的变化。
  • 规避潜在问题
    防止 Vue 对组件对象内部属性(如方法、生命周期钩子)的深度代理导致意外行为。

4. 示例对比

// 使用 ref(不推荐)

import { ref } from 'vue';

import HeavyComponent from './HeavyComponent.vue';



const currentComponent = ref(HeavyComponent);

// Vue 会深度代理 HeavyComponent 的所有属性,但实际只需要引用变化触发更新



// 使用 shallowRef(推荐)

import { shallowRef } from 'vue';



const currentComponent = shallowRef(HeavyComponent);

// 仅追踪 currentComponent.value 的引用变化,高效且安全

5. 官方建议

Vue 官方文档在动态组件示例中直接使用普通变量(非响应式),但在需要响应式时建议使用 shallowRef,明确表示:

“如果你确实需要响应性,可以使用 shallowRef。”

 在动态组件切换场景中,shallowRef 通过避免深度响应式转换,在保证功能正确性的同时,提升了性能并规避了潜在问题。这正是它与 ref 的核心区别所在。

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

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

相关文章

前端面经12 函数柯里化

<script>function sum(num){return function(num2){return numnum2}}console.log(sum(1)(2))</script>面试考察 只要参数够了 达到某个数量就输出 <script>let nums[]function sum(...args){nums.push(...args)if(nums.length>5){const out (nums.slice…

告别蜘蛛池!PHP 打造你的网站专属蜘蛛导航仪

在网站优化的赛道上&#xff0c;吸引搜索引擎蜘蛛来访一直是站长和开发者关注的重点。以往借助蜘蛛池、软件等工具引蜘蛛&#xff0c;不仅存在成本高、易违规的风险&#xff0c;效果也参差不齐。现在&#xff0c;有一种更高效、更安全的方式 —— 利用 PHP 代码&#xff0c;无需…

ubuntu kubeasz 部署高可用k8s 集群

ubuntu kubeasz 部署高可用k8s 集群 测试环境主机列表软件清单kubeasz 部署高可用 kubernetes配置源配置host文件安装 ansible 并进行 ssh 免密登录:下载 kubeasz 项⽬及组件部署集群部署各组件开始安装修改 config 配置文件增加 master 节点增加 kube_node 节点登录dashboard…

芯驰科技与安波福联合举办技术研讨会,深化智能汽车领域合作交流

5月15日&#xff0c;芯驰科技与全球移动出行技术解决方案供应商安波福&#xff08;Aptiv&#xff09;在上海联合举办以“芯智融合&#xff0c;共赢未来”为主题的技术研讨会。会上&#xff0c;双方聚焦智能座舱与智能车控的发展趋势&#xff0c;展开深入交流与探讨&#xff0c;…

【论文#目标检测】End-to-End Object Detection with Transformers

目录 摘要1.引言2.相关工作2.1 集合预测2.2 Transformer和并行解码2.3 目标检测 3.DETR模型3.1 目标检测集合预测损失3.2 DETR架构 4.实验4.1 与Faster R-CNN的比较4.2 消融研究4.3 分析4.4 DETR用于全景分割 5.结论6.致谢 Author: Nicolas Carion, Francisco Massa, Gabriel S…

Elasticsearch 深入分析三种分页查询【Elasticsearch 深度分页】

前言&#xff1a; 在前面的 Elasticsearch 系列文章中&#xff0c;分享了 Elasticsearch 的各种查询&#xff0c;分页查询也分享过&#xff0c;本篇将再次对 Elasticsearch 分页查询进行专题分析&#xff0c;“深度分页” 这个名词对于我们来说是一个非常常见的业务场景&#…

DAY29 超大力王爱学Python

知识点回顾 类的装饰器装饰器思想的进一步理解&#xff1a;外部修改、动态类方法的定义&#xff1a;内部定义和外部定义 作业&#xff1a;复习类和函数的知识点&#xff0c;写下自己过去29天的学习心得&#xff0c;如对函数和类的理解&#xff0c;对python这门工具的理解等&…

Ubuntu 远程桌面配置指南

概述: 本文主要介绍在Ubuntu 22.04中通过VNC实现远程连接的方法。首先需安装图形化界面和VNC工具x11vnc,设置开机启动服务;然后在Windows客户端用VNC Viewer通过局域网IP和端口5900连接。 总结: 一、VNC配置与安装 安装图形化界面 在Ubuntu 22.04中需先安装: sudo apt …

推扫式高光谱相机VIX-N230重磅发布——开启精准成像新时代

随着各行业对高光谱成像技术需求的持续增长&#xff0c;市场对于高分辨率、高灵敏度以及快速成像的高光谱相机的需求愈发迫切。中达瑞和凭借多年的行业经验和技术积累&#xff0c;敏锐捕捉到这一市场趋势&#xff0c;正式推出全新一代推扫式可见光近红外高光谱相机——VIX-N230…

Parsec解决PnP连接失败的问题

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、准备环境二、DMZ三、端口映射1.Parsec设置固定端口2.路由器设置端口转发3.重启被控端Parsec四、多少一句1.有光猫管理员账号2.没有光猫管理员账号总结 前言…

软件I2C

软件I2C 注意&#xff1a; SDA&#xff08;串行数据线&#xff09;和SCL&#xff08;串行时钟线&#xff09;都是双向I/O线&#xff0c;接口电路为开漏输出。需通过上拉电阻接电源VCC。 软件I2C说明 说明&#xff0c;有的单片机没有硬件I2C的功能&#xff0c;或者因为电路设计…

Brooks Polycold快速循环水蒸气冷冻泵客户使用手含电路图,适用于真空室应用

Brooks Polycold快速循环水蒸气冷冻泵客户使用手含电路图&#xff0c;适用于真空室应用

关于在Unity项目中使用Post Processing插件打包到web端出现的问题

关于在Unity项目中使用Post Processing插件打包到web端出现的问题 解决方法&#xff1a;是不激活摄像机上的Post Processing有关组件&#xff0c;拉低场景中的Directional Light平行光的强度进行web端打包。 &#xff08;烘焙灯光时是可以激活。&#xff09; web端支持这个Pos…

Prompt Tuning:高效微调大模型的新利器

Prompt Tuning(提示调优)是什么 Prompt Tuning(提示调优) 是大模型参数高效微调(Parameter-Efficient Fine-Tuning, PEFT)的重要技术之一,其核心思想是通过优化 连续的提示向量(而非整个模型参数)来适配特定任务。以下是关于 Prompt Tuning 的详细解析: 一、核心概念…

C++初阶-vector的底层

目录 1.序言 2.std::sort(了解) 3.vector的底层 3.1讲解 3.2构造函数 3.3push_back函数 3.4begin()和end()函数 3.5capacity()和size()函数和max_size函数 3.5.1size&#xff08;&#xff09;函数 为什么这样写&#xff1f; 底层原理 3.5.2max_size()函数 为什么这…

C语言指针深入详解(五):回调函数、qsort函数

目录 一、回调函数 1、使用回调函数改造前 2、使用回到函数改造后 二、qsort使用举例 1、使用qsort函数排序整型数据 2、使用qsort排序结构数据 三、qsort函数模拟实现 结语 &#x1f525;个人主页&#xff1a;艾莉丝努力练剑 &#x1f353;专栏传送门&#xff1a;《…

数据结构进阶:AVL树与红黑树

目录 前言 AVL树 定义 结构 插入 AVL树插入的大致过程 更新平衡因子 旋转 右单旋 左单旋 左右双旋 右左双旋 实现 红黑树 定义 性质 结构 插入 实现 总结 前言 在学习了二叉搜索树之后&#xff0c;我们了解到其有个致命缺陷——当树的形状呈现出一边倒…

基于Spring Boot + Vue的教师工作量管理系统设计与实现

一、项目简介 随着高校信息化管理的发展&#xff0c;教师工作量管理成为教务系统中不可或缺的一部分。为此&#xff0c;我们设计并开发了一个基于 Spring Boot Vue 的教师工作量管理系统&#xff0c;系统结构清晰&#xff0c;功能完备&#xff0c;支持管理员和教师两个角色。…

完善网络安全等级保护,企业需注意:

在数字化转型加速的当下&#xff0c;网络安全成为企业发展的基石。网络安全等级保护作为保障网络安全的重要举措&#xff0c;企业必须高度重视并积极落实。以下要点&#xff0c;企业在完善网络安全等级保护工作中需格外关注&#xff1a; 一、准确开展定级备案 企业首先要依据相…

Trae 04.22版本深度解析:Agent能力升级与MCP市场对复杂任务执行的革新

我正在参加Trae「超级体验官」创意实践征文&#xff0c;本文所使用的 Trae 免费下载链接&#xff1a;Trae - AI 原生 IDE 目录 引言 一、Trae 04.22版本概览 二、统一对话体验的深度整合 2.1 Chat与Builder面板合并 2.2 统一对话的优势 三、上下文能力的显著增强 3.1 W…