From 67c6ba3b136954fbd95d8f2d87f6037a7eee0e1e Mon Sep 17 00:00:00 2001 From: "Spencer.Chang" Date: Mon, 29 Jun 2026 11:20:50 +0800 Subject: [PATCH] Optimize LuckyExcel xlsx preview conversion with Web Worker (#766) * Optimize LuckyExcel conversion with worker * Fallback LuckyExcel worker errors to main thread --- .../static/xlsx/luckyexcel-worker.js | 54 ++++++ .../resources/static/xlsx/luckyexcel.umd.js | 2 +- server/src/main/resources/web/officeweb.ftl | 171 ++++++++++++------ 3 files changed, 168 insertions(+), 59 deletions(-) create mode 100644 server/src/main/resources/static/xlsx/luckyexcel-worker.js diff --git a/server/src/main/resources/static/xlsx/luckyexcel-worker.js b/server/src/main/resources/static/xlsx/luckyexcel-worker.js new file mode 100644 index 00000000..ac160e66 --- /dev/null +++ b/server/src/main/resources/static/xlsx/luckyexcel-worker.js @@ -0,0 +1,54 @@ +// LuckyExcel's bundled getBinaryContent reads window.XMLHttpRequest. +// Web Worker exposes XMLHttpRequest on self, so provide a minimal window alias +// before loading the UMD bundle. +self.window = self; + +importScripts('./luckyexcel.umd.js'); + +self.console.log = function () {}; + +self.onmessage = function (event) { + var data = event.data || {}; + var url = data.url; + var name = data.name; + + if (!url) { + self.postMessage({ + type: 'error', + message: '文件URL为空' + }); + return; + } + + try { + LuckyExcel.transformExcelToLuckyByUrl( + url, + name, + function (exportJson, luckysheetfile) { + if (!exportJson || !exportJson.sheets || exportJson.sheets.length === 0) { + self.postMessage({ + type: 'error', + message: '读取excel文件内容失败!' + }); + return; + } + + self.postMessage({ + type: 'success', + exportJson: exportJson + }); + }, + function (error) { + self.postMessage({ + type: 'error', + message: error && error.message ? error.message : String(error) + }); + } + ); + } catch (error) { + self.postMessage({ + type: 'error', + message: error && error.message ? error.message : String(error) + }); + } +}; diff --git a/server/src/main/resources/static/xlsx/luckyexcel.umd.js b/server/src/main/resources/static/xlsx/luckyexcel.umd.js index 85d33de1..17065b8c 100644 --- a/server/src/main/resources/static/xlsx/luckyexcel.umd.js +++ b/server/src/main/resources/static/xlsx/luckyexcel.umd.js @@ -3938,7 +3938,7 @@ var LuckySheet = /** @class */function (_super) { _this.sheetList = allFileOption.sheetList; _this.imageList = allFileOption.imageList; _this.hide = allFileOption.hide; - console.log(allFileOption, 'allFileOption'); + // console.log(allFileOption, 'allFileOption'); _this.dataVerificationSelectCount = allFileOption.dataVerificationSelectCount; //Output _this.name = sheetName; diff --git a/server/src/main/resources/web/officeweb.ftl b/server/src/main/resources/web/officeweb.ftl index 5c52f129..b1ed2227 100644 --- a/server/src/main/resources/web/officeweb.ftl +++ b/server/src/main/resources/web/officeweb.ftl @@ -205,37 +205,40 @@ // 异步加载Excel文件 async function loadTextAsync() { if (isLoading) return; - + isLoading = true; updateProgress(10); - + try { initWaterMark(); - + const value = url; const name = '${file.name}'; - + if (!value) { showError('文件URL为空'); return; } updateProgress(30); - + // 使用异步方式加载 await new Promise(resolve => setTimeout(resolve, 100)); // 给UI更新一点时间 - - // 或者使用现有的同步方法,但放在setTimeout中避免阻塞 - await transformWithTimeout(value, name); - + + const exportJson = await transformWithWorker(value, name); + + updateProgress(80); + + await createLuckysheet(exportJson); + updateProgress(100); - + // 延迟隐藏加载界面,让用户看到加载完成 setTimeout(() => { hideLoading(); isLoading = false; }, 500); - + } catch (error) { console.error('加载Excel失败:', error); showError('加载失败: ' + error.message); @@ -243,35 +246,93 @@ } } - // 使用setTimeout将同步任务拆分 - function transformWithTimeout(value, name) { + function transformWithWorker(value, name) { return new Promise((resolve, reject) => { updateProgress(50); - - // 将转换过程放在setTimeout中,避免阻塞主线程 - setTimeout(() => { + + if (!window.Worker) { + transformOnMainThread(value, name, resolve, reject); + return; + } + + let worker; + try { + worker = new Worker('xlsx/luckyexcel-worker.js'); + } catch (error) { + transformOnMainThread(value, name, resolve, reject); + return; + } + + let settled = false; + const fallbackToMainThread = function(error) { + if (settled) { + return; + } + settled = true; + worker.terminate(); + if (error) { + console.warn('Excel Worker转换失败,回退主线程转换:', error); + } + transformOnMainThread(value, name, resolve, reject); + }; + + worker.onmessage = function(event) { + const data = event.data || {}; + + if (data.type === 'success') { + settled = true; + worker.terminate(); + resolve(data.exportJson); + return; + } + + if (data.type === 'error') { + fallbackToMainThread(data.message || 'Excel转换失败'); + } + }; + + worker.onerror = function(error) { + fallbackToMainThread(error && error.message ? error.message : error); + }; + + worker.postMessage({ + url: value, + name: name + }); + }); + } + + function transformOnMainThread(value, name, resolve, reject) { + try { + LuckyExcel.transformExcelToLuckyByUrl(value, name, function(exportJson, luckysheetfile) { + if (!exportJson || !exportJson.sheets || exportJson.sheets.length === 0) { + reject(new Error("读取excel文件内容失败!")); + return; + } + + resolve(exportJson); + }, function(error) { + reject(error); + }); + } catch (error) { + reject(error); + } + } + + function createLuckysheet(exportJson) { + return new Promise((resolve, reject) => { + requestAnimationFrame(() => { try { - LuckyExcel.transformExcelToLuckyByUrl(value, name, function(exportJson, luckysheetfile){ - if(exportJson.sheets==null || exportJson.sheets.length==0){ - reject(new Error("读取excel文件内容失败!")); - return; - } - - updateProgress(80); - - // 使用requestAnimationFrame来更新UI,避免阻塞 - requestAnimationFrame(() => { - try { - window.luckysheet.destroy(); - window.luckysheet.create({ - container: 'luckysheet', - lang: "zh", - showtoolbarConfig:{ - image: true, - print: true, - exportXlsx: true, - }, - allowCopy: true, // 是否允许拷贝 + window.luckysheet.destroy(); + window.luckysheet.create({ + container: 'luckysheet', + lang: "zh", + showtoolbarConfig:{ + image: true, + print: true, + exportXlsx: true, + }, + allowCopy: true, // 是否允许拷贝 showtoolbar: ${xlsxshowtoolbar?string('true','false')}, // 是否显示工具栏 showinfobar: true, // 是否显示顶部信息栏 // myFolderUrl: "/",//作用:左上角<返回按钮的链接 @@ -287,29 +348,23 @@ sheetFormulaBar: false, // 是否显示公式栏 enableAddBackTop: true,//返回头部按钮 forceCalculation: false, //下面是导出插件 默认关闭 - data: exportJson.sheets, - title: exportJson.info.name, - userInfo: exportJson.info.name.creator, - // 添加加载完成的回调 - hook: { - workbookCreateAfter: function() { - resolve(); - } - } - }); - - updateProgress(90); - - } catch (err) { - reject(err); + data: exportJson.sheets, + title: exportJson.info.name, + userInfo: exportJson.info.name.creator, + // 添加加载完成的回调 + hook: { + workbookCreateAfter: function() { + resolve(); } - }); + } }); - - } catch (error) { - reject(error); + + updateProgress(90); + + } catch (err) { + reject(err); } - }, 100); + }); }); } @@ -330,4 +385,4 @@ }); - \ No newline at end of file +