当前位置: 首页 > news >正文

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

目录

简介

开始实践

难点

文件示例

效果预览

具体实现

安装

完整代码

总结


简介

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

开始实践

NPM地址 内部有中文文档

难点

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

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

文件示例

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

效果预览

具体实现

安装

npm install exceljs

完整代码

<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("; ");
};// 获取工作表维度信息
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功能很强大推荐大家自己尝试一下。

http://www.dtcms.com/a/356343.html

相关文章:

  • Excel数组学习笔记
  • 在Excel和WPS表格中隔一行插入多个空白行
  • 网络编程 04:TCP连接,客户端与服务器的区别,实现 TCP 聊天及文件上传,Tomcat 的简单使用
  • 从零开始部署 Kubernetes Dashboard:可视化管理你的集群
  • [Linux]学习笔记系列 -- mm/shrinker.c 内核缓存收缩器(Kernel Cache Shrinker) 响应内存压力的回调机制
  • 创意程序之MP3分割工具
  • sqlachemy
  • AI操作系统语言模型设计 之1 基于意识的Face-Gate-Window的共轭路径的思维-认知-情感嵌套模型
  • 【C语言】深入理解指针(2)
  • 龙迅#LT7621GX适用于两路HDMI2.1/DP1.4A转HDMI2.1混切应用,分辨率高达8K60HZ!
  • 第二阶段WinForm-11:自定义控件
  • 嵌入式Linux驱动开发:i.MX6ULL中断处理
  • 深入解析Qt节点编辑器框架:交互逻辑与样式系统(二)
  • C++基础(⑤删除链表中的重复节点(链表 + 遍历))
  • 储能变流器之LLC
  • MySQL数据库精研之旅第十四期:索引的 “潜规则”(上)
  • Unity、Unreal Engine与Godot中纹理元数据管理的比较分析
  • 嵌入式Linux LED驱动开发
  • Ubuntu22.04系统安装Opencv,无法定位包libjasper-dev libdc1394-22-dev的解决办法
  • 【C++】C++入门——(上)
  • GTSAM中gtsam::LinearContainerFactor因子详解
  • 【C++八股文】计算机网络篇
  • 【YOLO学习笔记】数据增强mosaic、Mixup、透视放射变换
  • flutter-使用url_launcher打开链接/应用/短信/邮件和评分跳转等
  • leetcode 338 比特位计数
  • rockchip温控及cpu降频配置
  • 事务和锁(进阶)
  • 使用 Docker 部署 Squid 为 Kubernetes 中的 Nexus3 提供公网代理访问
  • Windows12概念曝光,巧用远程控制工具抢先体验
  • 人脸识别“不备案“有哪些后果?