Vue3使用exceljs將excel文件轉(zhuǎn)化為html預(yù)覽最佳方案
前言
在企業(yè)應(yīng)用中,我們時常會遇到需要上傳并展示 Excel 文件的需求,以實現(xiàn)文件內(nèi)容的在線預(yù)覽。經(jīng)過一番探索與嘗試,筆者最終借助 exceljs 這一庫成功實現(xiàn)了該功能。本文將以 Vue 3 為例,演示如何實現(xiàn)該功能,代碼示例可直接復(fù)制運行,希望能為大家在處理類似問題時提供新的思路和解決方案。
技術(shù)難點
基礎(chǔ)單元格和合并單元格的混合處理
文字樣式和背景樣式的讀取映射
富文本內(nèi)容格式的處理
excel文件展示

實際實現(xiàn)預(yù)覽效果

核心代碼
exceljs提供了合并區(qū)域的數(shù)據(jù),我們只需要根據(jù)合并區(qū)域去判斷什么時候該合并,就能很好的實現(xiàn)基礎(chǔ)單元格和合并單元格的混合繪制
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 表格內(nèi)容
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 轉(zhuǎn) RGB
}
if (cell.fill.fgColor.theme !== undefined) {
return themeColors[cell.fill.fgColor.theme] || "#FFFFFF"; // 主題色轉(zhuǎn)換
}
}
return ""; // 無顏色
};
// 獲取單元格字體顏色
const getCellFontColor = (cell) => {
if (cell.font && cell.font.color && cell.font.color.argb) {
return `#${cell.font.color.argb.substring(2)}`; // ARGB 轉(zhuǎn) RGB
}
if (cell.font && cell.font.color && cell.font.color.theme) {
return themeColors[cell.font.color.theme] || "#000"; // 主題色轉(zhuǎn)換
}
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 表格內(nèi)容
};
// 處理常規(guī)單元格內(nèi)容
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 : "";
};
// 處理合并單元格內(nèi)容
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 并轉(zhuǎn)換成 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 格式)。它允許開發(fā)者通過編程方式處理 Excel 文檔,包括創(chuàng)建新工作簿、添加數(shù)據(jù)、格式化單元格、插入圖表等。這個庫可以在服務(wù)器端(如 Node.js 環(huán)境)或客戶端(如在瀏覽器中使用 Webpack 或 Rollup 等工具打包的 JavaScript 應(yīng)用程序)使用。
主要特性
- 創(chuàng)建工作簿和工作表:可以輕松地創(chuàng)建新的 Excel 文件或修改已有的文件。
- 豐富的樣式支持:支持字體、顏色、邊框、對齊方式等多種樣式設(shè)置。
- 數(shù)據(jù)處理:支持從各種數(shù)據(jù)源導(dǎo)入數(shù)據(jù),并能將數(shù)據(jù)導(dǎo)出為 Excel 文件。
- 公式和計算:可以添加公式到單元格,并支持基本的 Excel 計算。
- 圖表支持:能夠在 Excel 文件中創(chuàng)建圖表。
- 圖片和繪圖:支持向 Excel 文件中添加圖片和繪制圖形。
使用場景
ExcelJS 廣泛應(yīng)用于需要與 Excel 文件進行交互的應(yīng)用程序開發(fā)中,比如:
- 數(shù)據(jù)報告生成
- 數(shù)據(jù)導(dǎo)入/導(dǎo)出功能實現(xiàn)
- 在線 Excel 編輯器
示例代碼片段
這里是一個簡單的示例,展示如何使用 ExcelJS 創(chuàng)建一個新的工作簿并添加一些數(shù)據(jù):
const ExcelJS = require('exceljs');
// 創(chuàng)建一個新的工作簿
let workbook = new ExcelJS.Workbook();
let worksheet = workbook.addWorksheet('測試工作表');
// 添加一行數(shù)據(jù)
worksheet.addRow(['姓名', '年齡', '郵箱']);
worksheet.addRow(['張三', 28, 'zhangsan@example.com']);
worksheet.addRow(['李四', 23, 'lisi@example.com']);
// 保存工作簿到文件
workbook.xlsx.writeFile('example.xlsx')
.then(() => {
console.log('文件保存成功');
}); 這個例子展示了如何創(chuàng)建一個新的 Excel 文件,并向其中添加一些簡單數(shù)據(jù)。ExcelJS 的靈活性和強大功能使其成為處理 Excel 文件的一個優(yōu)秀選擇。
npm上的puppeteer庫是做什么的?
npm 上的 puppeteer 是一個用于自動化控制 Chrome/Chromium 瀏覽器的 Node.js 庫,由 Google 團隊開發(fā)。它通過提供高級 API,允許你以編程方式模擬用戶在瀏覽器中的操作,適用于多種場景:
核心功能
無頭瀏覽器控制
可啟動 Headless 模式(無界面)或完整瀏覽器,執(zhí)行自動化任務(wù)(如點擊、輸入、導(dǎo)航等)。動態(tài)內(nèi)容抓取
適用于爬取 JavaScript 渲染的頁面(如 React/Vue 單頁應(yīng)用),傳統(tǒng)爬蟲工具難以直接獲取動態(tài)內(nèi)容。生成截圖與 PDF
精確截取網(wǎng)頁全屏、指定區(qū)域,或?qū)㈨撁鎸?dǎo)出為 PDF(保留樣式)。自動化測試
模擬用戶操作,測試網(wǎng)頁功能(如表單提交、UI 交互),生成測試報告。性能分析
監(jiān)控頁面加載速度、資源請求,優(yōu)化性能。
典型使用場景
數(shù)據(jù)抓取:爬取電商價格、社交媒體內(nèi)容等動態(tài)加載的數(shù)據(jù)。
自動化操作:自動填寫表單、批量下載文件、定時簽到等。
預(yù)渲染:為 SEO 生成靜態(tài)化內(nèi)容,解決 SPA 首屏加載問題。
生成報告:將數(shù)據(jù)可視化頁面導(dǎo)出為 PDF 或圖片存檔。
基礎(chǔ)示例
const puppeteer = require('puppeteer');
(async () => {
// 啟動瀏覽器(默認 Headless 模式)
const browser = await puppeteer.launch();
const page = await browser.newPage();
// 導(dǎo)航到目標頁面
await page.goto('https://example.com');
// 截圖并保存
await page.screenshot({ path: 'example.png' });
// 關(guān)閉瀏覽器
await browser.close();
})();安裝與依賴
通過 npm 安裝(會自動下載匹配的 Chromium):
npm install puppeteer
優(yōu)勢
直接操控瀏覽器:行為更接近真實用戶,繞過反爬蟲機制(需合理使用)。
功能全面:支持網(wǎng)絡(luò)請求攔截、Cookie 管理、模擬設(shè)備(如手機/平板)等。
社區(qū)活躍:豐富的文檔和第三方工具(如
puppeteer-cluster多任務(wù)優(yōu)化)。
若你需要處理復(fù)雜的網(wǎng)頁交互或動態(tài)內(nèi)容,Puppeteer 是一個高效且靈活的選擇。
vue中為什么切換動態(tài)組件需要使用shallowRef ?
在 Vue 3 中,使用 shallowRef 來管理動態(tài)組件的切換,主要是出于性能優(yōu)化和避免不必要的深度響應(yīng)式追蹤的考慮。以下是詳細解釋:
1. ref vs shallowRef 的核心區(qū)別
會對其包裹的值進行深度響應(yīng)式轉(zhuǎn)換(遞歸將對象屬性轉(zhuǎn)為響應(yīng)式)。
僅對值的引用變化進行響應(yīng)式追蹤,不會深度遞歸轉(zhuǎn)換內(nèi)部屬性。
2. 動態(tài)組件的場景分析
當使用 <component :is="currentComponent"> 切換組件時:
- 組件對象本身是穩(wěn)定的
動態(tài)組件的核心操作是替換整個組件對象(如 currentComponent.value = NewComponent),而不是修改組件對象的內(nèi)部屬性。
- 組件對象可能很大
一個組件對象(如導(dǎo)入的 Vue 組件)通常包含大量屬性(如 props、methods、生命周期等),如果使用 ref 深度響應(yīng)式化,會帶來額外性能開銷。
- 深度響應(yīng)式可能導(dǎo)致問題
某些組件屬性(如函數(shù)方法)被 Vue 代理后可能產(chǎn)生副作用(如破壞函數(shù)內(nèi)部 this 綁定,或與第三方庫預(yù)期結(jié)構(gòu)沖突)。
3. 為什么 shallowRef 更合適?
- 性能優(yōu)化
避免深度遍歷組件對象的所有屬性,減少不必要的響應(yīng)式代理。
- 符合實際需求
動態(tài)組件切換只需要響應(yīng)組件引用的變化(整體替換),無需關(guān)心組件內(nèi)部屬性的變化。
- 規(guī)避潛在問題
防止 Vue 對組件對象內(nèi)部屬性(如方法、生命周期鉤子)的深度代理導(dǎo)致意外行為。
4. 示例對比
// 使用 ref(不推薦)
import { ref } from 'vue';
import HeavyComponent from './HeavyComponent.vue';
const currentComponent = ref(HeavyComponent);
// Vue 會深度代理 HeavyComponent 的所有屬性,但實際只需要引用變化觸發(fā)更新
// 使用 shallowRef(推薦)
import { shallowRef } from 'vue';
const currentComponent = shallowRef(HeavyComponent);
// 僅追蹤 currentComponent.value 的引用變化,高效且安全5. 官方建議
Vue 官方文檔在動態(tài)組件示例中直接使用普通變量(非響應(yīng)式),但在需要響應(yīng)式時建議使用 shallowRef,明確表示:
“如果你確實需要響應(yīng)性,可以使用 shallowRef。”
在動態(tài)組件切換場景中,shallowRef 通過避免深度響應(yīng)式轉(zhuǎn)換,在保證功能正確性的同時,提升了性能并規(guī)避了潛在問題。這正是它與 ref 的核心區(qū)別所在。
以上就是Vue3使用exceljs將excel文件轉(zhuǎn)化為html預(yù)覽最佳方案的詳細內(nèi)容,更多關(guān)于Vue3 exceljs將excel轉(zhuǎn)html預(yù)覽的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解Vue基于vue-quill-editor富文本編輯器使用心得
這篇文章主要介紹了Vue基于vue-quill-editor富文本編輯器使用心得,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-01-01
vue+watermark-dom實現(xiàn)頁面水印效果(示例代碼)
watermark.js 是基于 DOM 對象實現(xiàn)的 BS 系統(tǒng)的水印,確保系統(tǒng)保密性,安全性,降低數(shù)據(jù)泄密風險,簡單輕量,支持多屬性配置,本文將通過 vue 結(jié)合 watermark-dom 庫,教大家實現(xiàn)簡單而有效的頁面水印效果,感興趣的朋友跟隨小編一起看看吧2024-07-07
vue使用動態(tài)添加路由(router.addRoutes)加載權(quán)限側(cè)邊欄的方式
這篇文章主要介紹了vue使用動態(tài)添加路由(router.addRoutes)加載權(quán)限側(cè)邊欄的方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06
基于webpack4+vue-cli3項目實現(xiàn)換膚功能
這篇文章主要介紹了基于webpack4+vue-cli3項目的換膚功能,文中是通過scss+style-loader/useable做換膚功能,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2019-07-07

