目录
简介
在日常工作中,我们可能会遇到需要上传并展示 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 " ";
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 " ";
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 || " ";
allHtml += `<td rowspan="${rowspan}" colspan="${colspan}" style="${styles}">
${handleValue(mergeValue)}</td>`;
}
// 如果不在合并范围内,创建普通单元格
else if (!isInMerge) {
let styles = handleStyles(cell);
const displayValue = cellValue ? handleValueSimple(cellValue) : " ";
allHtml += `<td style="${styles}">${displayValue}</td>`;
}
// 如果单元格在合并范围内但不是起始位置,跳过(由合并单元格处理)
}
allHtml += "</tr>";
}
allHtml += "</table>";
});
return allHtml;
};
</script>
总结
exceljs功能很多,这里给大家介绍了execljs的一种用法,实现导入转换html页面显示,便于浏览。大家感兴趣可以去翻翻文档 NPM地址 内部有中文文档,exceljs功能很强大推荐大家自己尝试一下。