一.技術
exceljs,luckysheet
二.實現
參考網上博文exceljs對導出lucksheet表格的實現,發現存在一些問題并給予修復:
1.字體顏色、字號,加粗等適配的問題.
2.單元格對齊方式不生效;
3.單元格邊框無法繪制;
4.單元格邊框顏色及線型錯亂;
5.單元格列寬處理;
6.合并單元格導出錯亂;
7.其他的一些BUG
三.注意事項
1、由于luckysheet在網頁端和excel分辨率無法保持完全一致,所以導出到excel中的時候,可能存在單元格大小與原表格不一致的情況,需要在setStyleAndValue中對單元格大小進行手動調整,具體可查看代碼注釋。后續也會逐漸進行自動適配。
2、目前僅支持表格,數據透視表的導出;不支持圖片,圖表的導出,后續有時間慢慢完善。
四.使用教程
1、安裝 exceljs、
file-saver
使用以下命令通過 npm
安裝 exceljs
和 file-saver
npm install exceljs file-saver
2、引用導出Excel文件
安裝完成后把找到 exceljs.min.js 和?FileSaver.min.js 文件拷貝到自己的項目中,并添加引用
D:\project\ExcelJS-demo\node_modules\exceljs\dist\exceljs.min.js
D:\project\ExcelJS-demo\node_modules\file-saver\dist\FileSaver.min.js
<script type="text/javascript" src="luckysheet/exceljs/exceljs.min.js"></script>
<script type="text/javascript" src="luckysheet/exceljs/FileSaver.min.js"></script>
3、調用導出Excel函數
這個函數是我自己封裝的版本
在項目里新建一個js文件,名為:exportExcel.js (可自定義),把下面這段導出的代碼粘貼進去
// 導出 Luckysheet 內容為 Excel(ExcelJS)
async function exportLuckysheetToExcelByExcelJS() {//創建工作簿const workbook = new ExcelJS.Workbook();// 啟用樣式支持(關鍵配置)workbook.useStyles = true;// 拿到當前激活頁的配置對象const activeSheet = luckysheet.getSheet();const originalSheetIndex = activeSheet.order ?? 0;// 激活每個 sheet,確保數據初始化(特別是數據透視表)const sheets = luckysheet.getAllSheets();for (let i = 0; i < sheets.length; i++) {luckysheet.setSheetActive(i);//每切換一次表格,延遲1ms,為了讓表格數據能夠正常加載和渲染。await new Promise(resolve => setTimeout(resolve, 1));if (i == sheets.length - 1) {// 恢復原始激活的 sheetluckysheet.setSheetActive(originalSheetIndex);}}// 重新獲取激活后的所有工作表const initializedSheets = luckysheet.getAllSheets();initializedSheets.forEach(sheet => {const worksheet = workbook.addWorksheet(sheet.name);// 1. 填充數據與樣式sheet.data.forEach((row, rowIndex) => {row.forEach((cell, colIndex) => {if (!cell) return;const excelCell = worksheet.getCell(rowIndex + 1, colIndex + 1);// 值或公式excelCell.value = cell.f ? { formula: cell.f, result: cell.v } : cell.v;// 字體樣式(字號、顏色、加粗、斜體、下劃線、字體名)const fontSizePx = cell.fs !== undefined ? cell.fs : 10;const font = { size: fontSizePx };if (cell.fc) font.color = { argb: hexToARGB(cell.fc) };if (cell.bl === 1) font.bold = true;if (cell.cl === 1) font.italic = true;if (cell.ul === 1) font.underline = true;if (cell.ff) font.name = cell.ff;excelCell.font = font;// 背景色if (cell.bg) {excelCell.fill = {type: 'pattern',pattern: 'solid',fgColor: { argb: hexToARGB(cell.bg) }};}// 對齊方式const hAlignMap = { 0: 'center', 1: 'left', 2: 'right' };const vAlignMap = { 0: 'middle', 1: 'top', 2: 'bottom' };const alignment = {};if (cell.ht !== undefined) alignment.horizontal = hAlignMap[cell.ht];if (cell.vt !== undefined) alignment.vertical = vAlignMap[cell.vt];if (Object.keys(alignment).length > 0) excelCell.alignment = alignment;});});// 2. 合并單元格const mergedMap = new Set();Object.values(sheet.config?.merge || {}).forEach(merge => {const r1 = merge.r + 1, c1 = merge.c + 1;const r2 = merge.r + merge.rs, c2 = merge.c + merge.cs;const mergeKey = `${r1},${c1},${r2},${c2}`;if (mergedMap.has(mergeKey)) return;mergedMap.add(mergeKey);try {worksheet.mergeCells(r1, c1, r2, c2);} catch (e) {console.warn(`跳過已合并區域:${mergeKey}`, e);}});// 3. 邊框處理(透視表默認細邊框)if (!sheet.config?.borderInfo && sheet.isPivotTable) {const { maxRow, maxCol } = getUsedRange(sheet);sheet.config = sheet.config || {};sheet.config.borderInfo = [{rangeType: "range",borderType: "border-all",style: "1",color: "#000000",range: [{ row: [0, maxRow - 1], column: [0, maxCol - 1] }]}];}(sheet.config?.borderInfo || []).forEach(border => {const rawColor = border.color === '#000' ? '#000000' : border.color;const color = hexToARGB(rawColor || '#000000');const borderStyleMap = {"1": "thin", // 細線"2": "dashed",// 虛線"3": "dotted", // 點線"4": "thick",// 粗線"5": "thick",// 粗線"6": "dashed",// 虛線"7": "dotted", // 點線"8": "medium",// 中等"9": "dashed",// 虛線"10": "thick"// 粗線};const styleName = borderStyleMap[border.style] || 'thin';const style = { style: styleName, color: { argb: color } };(border.range || []).forEach(range => {const r1 = range.row[0], r2 = range.row[1];const c1 = range.column[0], c2 = range.column[1];for (let r = r1; r <= r2; r++) {for (let c = c1; c <= c2; c++) {const cell = worksheet.getCell(r + 1, c + 1);const oldBorder = cell.border || {};let newBorder = { ...oldBorder };switch (border.borderType) {case 'left': newBorder.left = style; break;case 'right': newBorder.right = style; break;case 'top': newBorder.top = style; break;case 'bottom': newBorder.bottom = style; break;case 'border-all':case 'all':newBorder = {top: style, bottom: style,left: style, right: style};break;}cell.border = newBorder;}}});});// 4. 列寬設置const colConfig = sheet.config?.columnlen || {};const colCount = sheet.data?.[0]?.length || 0;for (let c = 0; c < colCount; c++) {const excelCol = worksheet.getColumn(c + 1);const luckysheetWidth = colConfig[c];if (luckysheetWidth !== undefined) {excelCol.width = Math.round(luckysheetWidth / 7 * 100) / 100;} else {excelCol.width = 10;}}});// 5. 生成文件并下載const buffer = await workbook.xlsx.writeBuffer();saveAs(new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }),'onlieExcel.xlsx');
}// 轉換顏色為 ExcelJS ARGB 格式
function hexToARGB(hex) {if (!hex || !hex.startsWith('#')) return undefined;const rgb = hex.slice(1).toUpperCase();return 'FF' + rgb;
}// 獲取使用區域的最大行列
function getUsedRange(sheet) {let maxRow = 0;let maxCol = 0;sheet.data.forEach((row, rowIndex) => {if (row) {row.forEach((cell, colIndex) => {if (cell && cell.v !== undefined && cell.v !== null && cell.v !== '') {maxRow = Math.max(maxRow, rowIndex);maxCol = Math.max(maxCol, colIndex);}});}});return { maxRow: maxRow + 1, maxCol: maxCol + 1 };
}
在頁面里引用?exportExcel.js 文件
<script type="text/javascript" src="luckysheet/exceljs/exportExcel.js"></script>
調用?exportLuckysheetToExcelByExcelJS() 方法實現導出
<a href="javascript:exportLuckysheetToExcelByExcelJS()" id="btnExport">導出Xlsx</a>
歷時3天的勞動成果終于結束,收官,撒花 ??ヽ(°▽°)ノ?
五.源碼下載
luckysheet demo 完整代碼,包括以下功能:
1、Luckysheet 本地引入方式,已解決斷網報錯,字體圖標不顯示的問題
2、使用SheetJS實現導入到luckysheet中,純前端,支持離線使用
3、使用ExcelJS實現導出luckysheet表格,純前端,支持離線使用
點擊 下載demo
?