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

前言

在企业应用中,我们时常会遇到需要上传并展示 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-uploadaction="":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.mzph.cn/diannao/83304.shtml

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

相关文章

PMP-第十二章 项目采购管理

项目采购管理核心概念 项目采购管理包括从项目团队外部采购或获取所需产品、服务或成果的各个过程项目组织既可以是买方&#xff08;甲方&#xff09; &#xff0c;也可以是卖方&#xff08;乙 方&#xff09;项目采购管理过程围绕协议来进行&#xff0c;协议是买卖双方之间具…

maven和npm区别是什么

这是一个很容易搞糊涂新手的问题&#xff0c;反正我刚开始从课堂的知识转向项目网站开发时&#xff0c;被这些问题弄得晕头转向&#xff0c;摸不着头脑&#xff0c;学的糊里糊涂&#xff0c;所以&#xff0c;写了这么久代码&#xff0c;也总结一下&#xff0c;为后来者传授下经…

Leetcode76覆盖最小子串

覆盖最小子串 代码来自b站左程云 class Solution {public String minWindow(String str, String tar) {char[] s str.toCharArray();char[] t tar.toCharArray();int[] cnt new int[256];for (char cha : t) { cnt[cha]--;}int len Integer.MAX_VALUE;int debt t.length…

Linux du 命令终极指南:从基础到精通

文章目录 Linux du 命令终极指南&#xff1a;从基础到精通du 命令简介常用参数详解常见用法示例查看当前目录总大小查看当前目录及其子目录占用空间只显示当前目录总占用空间查看目录下每个文件和子目录的大小查看某目录深度为 1 的大小分布查看某目录并排除日志文件查看多个目…

sychronized原理(嚼碎了喂版)

先说一下心得吧&#xff0c;我们知道硬软不分家&#xff0c;在学习底层原理的时候我们不需要死扣到底&#xff0c;没必要把硬件方面全吃透&#xff0c;点到为止&#xff0c;学到能够帮助理解代码即可&#xff0c;我们的目标是写出高性能的代码&#xff0c;而不是创造出硬软一体…

Ngrok 配置:实现 Uniapp 前后端项目内网穿透

文章目录 一、下载并安装 ngrok二、配置 ngrok Authtoken三、启动本地 uniapp 项目四、使用 ngrok 暴露本地服务五、通过公网 URL 访问项目六、后端API项目的穿透问题排查 (uni-app 后端 API 示例)交互流程图示 七、ngrok Web 界面 (本地监控)八、停止 ngrok总结 ngrok 是一款…

k8s灰度发布

基于 Traefik 的加权灰度发布-腾讯云开发者社区-腾讯云 Traefik | Traefik | v1.7 Releases traefik/traefik GitHub 从上面连接下载后上传到harbor虚拟机 vagrant upload /C/Users/HP280/Downloads/traefik 下载配置文件 wget -c http://raw.githubusercontent.com/conta…

win10-django项目与mysql的基本增删改查

以下都是在win10系统下&#xff0c;django项目的orm框架对本地mysql的表的操作 models.py----->即表对应的类所在的位置 在表里新增数据 1.引入表对应的在models.py中的类class 2.在views.py中使用函数&#xff1a;类名.objects.create(字段名值,字段名"值"。。。…

`ParameterizedType` 和 `TypeVariable` 的区别

在 Java 的泛型系统中&#xff0c;ParameterizedType 和 TypeVariable 是两个不同的类型表示&#xff0c;它们都属于 java.lang.reflect.Type 接口的子接口。两者都在反射&#xff08;Reflection&#xff09;中用于描述泛型信息&#xff0c;但用途和含义不同。 &#x1f31f; 一…

PR-2021

推荐深蓝学院的《深度神经网络加速&#xff1a;cuDNN 与 TensorRT》&#xff0c;课程面向就业&#xff0c;细致讲解CUDA运算的理论支撑与实践&#xff0c;学完可以系统化掌握CUDA基础编程知识以及TensorRT实战&#xff0c;并且能够利用GPU开发高性能、高并发的软件系统&#xf…

unity使用ZXing.Net生成二维码

下载链接 https://github.com/micjahn/ZXing.Net 放到Plugins下即可使用

Ubuntu 编译SRS和ZLMediaKit用于视频推拉流

SRS实现视频的rtmp webrtc推流 ZLMediaKit编译生成MediaServer实现rtsp推流 SRS指定某个固定网卡&#xff0c;修改程序后重新编译 打开SRS-4.0.0/trunk/src/app/srs_app_rtc_server.cpp&#xff0c;在 232 行后面添加&#xff1a; ZLMediaKit编译后文件存放在ZLMediakit/rele…

如何备考GRE?

1.引言 GRE和雅思不太相同&#xff0c;首先GRE是美国人的考试&#xff0c;思维方式和很多细节和英系雅思不一样。所以底层逻辑上我觉得有点区别。 难度方面&#xff0c;我感觉GRE不容易考低分&#xff0c;但考高分较难。雅思就不一样了不仅上限难突破&#xff0c;下限还容易6…

uniapp|商品列表加入购物车实现抛物线动画效果、上下左右抛入、多端兼容(H5、APP、微信小程序)

以uniapp框架为基础,详细解析商品列表加入购物车抛物线动画的实现方案。通过动态获取商品点击位置与购物车坐标,结合CSS过渡动画模拟抛物线轨迹,实现从商品图到购物车图标的动态效果。 目录 核心实现原理坐标动态计算抛物线轨迹模拟​动画元素控制代码实现详解模板层设计脚本…

React中使用openLayer画地图

OpenLayers&#xff08;简称ol&#xff09;是一个‌开源的WebGIS前端开发库‌&#xff0c;基于JavaScript实现&#xff0c;主要用于在网页中嵌入动态二维地图。 官方网站&#xff1a; https://openlayers.org 中文官网&#xff1a; https://openlayers.vip 大家可以去参考学习…

WHAT - 缓存命中 Cache Hit 和缓存未命中 Cache Miss

文章目录 一、什么是缓存命中&#xff1f;二、前端开发要知道哪些缓存机制&#xff08;以及命中条件&#xff09;&#xff1f;1. 浏览器缓存&#xff08;主要针对静态资源&#xff09;常见的缓存位置关键 HTTP 头字段&#xff08;决定命中与否&#xff09; 2. 前端应用层缓存&a…

10 个可靠的 Android 文件传输应用程序

Android 文件传输是 Android 用户的常见需求。我们经常需要将文件从一台 Android 设备传输到 PC 或 Mac。但我们怎样才能做到这一点呢&#xff1f;俗话说&#xff0c;工欲善其事&#xff0c;必先利其器。因此&#xff0c;首先了解 10 个锋利的 Android 文件传输应用程序&#x…

AlphaEvolve:LLM驱动的算法进化革命与科学发现新范式

AlphaEvolve&#xff1a;LLM驱动的算法进化革命与科学发现新范式 本文聚焦Google DeepMind最新发布的AlphaEvolve&#xff0c;探讨其如何通过LLM与进化算法的结合&#xff0c;在数学难题突破、计算基础设施优化等领域实现革命性进展。从48次乘法优化44矩阵相乘到数据中心资源利…

Java大师成长计划之第24天:Spring生态与微服务架构之分布式配置与API网关

&#x1f4e2; 友情提示&#xff1a; 本文由银河易创AI&#xff08;https://ai.eaigx.com&#xff09;平台gpt-4-turbo模型辅助创作完成&#xff0c;旨在提供灵感参考与技术分享&#xff0c;文中关键数据、代码与结论建议通过官方渠道验证。 在微服务架构中&#xff0c;如何管理…

eSwitch manager 简介

eSwitch manager 的定义和作用 eSwitch manager 通常指的是能够配置和管理 eSwitch&#xff08;嵌入式交换机&#xff09;的实体或接口。在 NVIDIA/Mellanox 的网络架构中&#xff0c;Physical Function&#xff08;PF&#xff09;在 switchdev 模式下充当 eSwitch manager&am…