Luckysheet 在 Vue 中 is not defined 与刷新白屏修复
1. 报错现象还原两个问题其实是一条链Vue 项目里接 Luckysheet 做在线表格本地导入之后控制台甩出一句Uncaught ReferenceError: luckysheet is not defined好不容易首页能跑了一按 F5 刷新或者从列表页跳进子路由再刷新那个表格区域直接白板——这两个现象在社群里几乎天天有人问。我前后在三个项目里踩过这套坑最后发现它们并不是两个独立 bug而是同一条因果链上的两个断点引入方式选错了导致库压根没挂到全局路径和时序又没处理干净导致刷新之后连挂载的机会都没有。先把场景说清楚。Luckysheet 是一套纯 JavaScript 实现的在线表格组件能力覆盖公式计算、单元格样式、冻结行列、复制粘贴、导入导出 Excel 等很多管理系统、报表平台、数据填报工具都会拿它当底层表格引擎。它的特点是开箱即用、功能全但代价是——它不是一个标准的 ES Module官方的dist目录是一份 UMD 打包产物整套 API 全部挂在window.luckysheet这个全局变量上。你在代码里写luckysheet.create(...)本质是在访问全局对象。这个前提决定了后面所有的坑。前端工程化走到今天大家习惯了import xxx from xxx习惯了打包器帮你处理依赖可 Luckysheet 偏偏不吃这一套。你直接import luckysheet from luckysheet打包器会尝试把 UMD 改写成 ESM改写完之后window.luckysheet可能压根没被赋值于是你在组件里调用时浏览器告诉你这个名字我不知道。而页面刷新这条线问题出在 HTML 里的script src./ ...用的是相对路径一旦路由带上了层级相对路径解析出来的地址就变了脚本请求 404全局变量自然也不存在。所以这篇东西我会按先定位根因、再选方案、然后落地实操、最后讲排查的顺序讲。适合正在用 Vue2 或 Vue3 接 Luckysheet 的同学也适合那些首页能跑、刷新就崩卡了半天找不到北的朋友。下面所有步骤我都按能直接抄作业的粒度写路径、代码、顺序都会明确。1.1 首次进入正常、刷新就崩问题出在哪一环要理解刷新丢失得先理解浏览器加载一个 Vue 单页应用的顺序。你打开http://xxx.com/report/detail服务器把这个请求重写成index.html返回浏览器开始解析 HTML先碰到几个link标签加载 CSS再碰到几个script标签加载 jQuery、Luckysheet 的插件包和luckysheet.umd.js全部加载执行完之后window.luckysheet才被真正赋值。这个过程和 Vue 应用的挂载是并行的谁先谁后没有保证。如果你在index.html里写的是script src./plugins/js/plugin.js/script首次从首页点进来时浏览器地址栏是http://xxx.com/相对路径解析成http://xxx.com/plugins/js/plugin.js请求成功。但你在http://xxx.com/report/detail这个地址上按刷新浏览器会以当前路径为基准去解析相对路径实际请求变成http://xxx.com/report/detail/plugins/js/plugin.js服务器上根本没有这个文件返回 404 或者返回了 index.html取决于你的 Nginx 配置。脚本没执行全局变量没挂上页面里那句luckysheet.create就抛is not defined。这就是首页好好的刷新就炸最典型的原因。很多人第一反应是怀疑 Vue 的keep-alive、怀疑路由守卫其实问题在 HTML 模板的那一个点号上。1.2 控制台里三种不同的报错长相对应三种不同的病同样是luckysheet is not defined实际背后的成因差别很大我整理了一下常见的三种长相你可以对照自己的控制台看一眼。第一种是纯粹的Uncaught ReferenceError: luckysheet is not defined没有任何网络错误Network 面板里也看不到脚本 404。这种多半是你用了import luckysheet from luckysheet打包器把 UMD 转换掉了全局变量没生成。第二种是网络面板里有一条红色的 404脚本地址明显和你预期的不一样比如多了两层路由前缀。这就是前面说的相对路径问题改绝对路径即可。第三种是脚本 200 加载成功但报错发生在onMounted/mounted里并且你多刷新几次有概率成功。这是典型的时序问题DOM 挂载的时机早于脚本加载完成第一次访问时浏览器缓存还没建立脚本加载慢就撞上了。这三种症状的判断顺序我建议是先看 Network再看引入方式最后看调用时机。因为网络问题是最好排除的改个路径就行引入方式的问题需要动目录结构时序问题需要加等待逻辑。按这个顺序排查能省掉大量来回试错的时间。1.3 一句话记住根因Luckysheet 活在全局作用域我把这件事总结成一句能记一辈子的话Luckysheet 是全局变量驱动的库不是模块导入的库。它的所有 API 都通过window.luckysheet暴露包括create、destroy、getAllSheets、setCellValue、toJson这些你在业务里天天要用的方法。理解了这一层很多事就顺了。为什么放public目录能解决大部分问题因为public目录的文件不会被任何打包器处理原样复制到产物里你写什么路径就是什么路径。为什么不能放src/assets因为那个目录里的东西会被打包器当成模块来处理URL 会被改写、文件名会带 hashUMD 包也会被强行转换。为什么必须在onMounted之后还要加等待因为全局变量是脚本执行时才赋值的DOM 挂载和脚本执行是两个独立的时间线。下面这张表是我做的初步诊断对照你可以先对号入座。症状典型报错大概率原因优先级无网络错误纯引用报错Uncaught ReferenceError: luckysheet is not defined用了 import 引入 UMD 包高Network 里有 404脚本地址带路由前缀script src 用了相对路径高时好时坏、偶发报错报错在 mounted 内部脚本加载与挂载时序竞争中打包前正常、打包后崩产物里找不到 luckysheet 目录文件放进 src/assets 被处理了高2. 为什么用 import 引入 Luckysheet 必然翻车这一节专门讲清楚为什么因为只给结论不给原因的教程下次换个库你还是会踩。很多人看网上说把 luckysheet 放 public 目录照做了好了但下次遇到另一个 UMD 库又不知道该怎么办。理解了打包器对模块格式的处理逻辑这类问题你能自己推。2.1 UMD 被转换成 ESM 之后到底发生了什么Luckysheet 官方 npm 包的package.json里main字段指向的是dist/luckysheet.umd.js。这个文件的开头大致长这样结构示意(function (root, factory) { if (typeof define function define.amd) { define([], factory); } else if (typeof exports object) { module.exports factory(); } else { root.luckysheet factory(); } })(typeof self ! undefined ? self : this, function () { // 库的内部实现 return luckysheetInstance; });它的判断逻辑是分三支的AMD 环境下走defineCommonJS 环境下走module.exports其他情况也就是直接用script标签才走root.luckysheet factory()把实例挂到全局。问题就出在这里。当你在 Vite 或 Webpack 项目里写import luckysheet from luckysheet打包器会识别出这是一个 CommonJS/UMD 模块走预构建或者 CJS 转换逻辑让它走module.exports那一支。结果是实例确实被导出了但window.luckysheet没有被赋值。你在组件里写luckysheet.create(...)如果从 import 拿到的是这个名字还好但如果你像大多数人一样直接裸写luckysheet.create()因为官方文档就是这么写的那这个标识符在模块作用域里并不存在直接 ReferenceError。更麻烦的是有些打包配置下转换出来的exports对象结构和原库的实例不一致你即使 import 进了变量调create也可能报不是一个函数。所以这条路走起来非常不稳定不同打包器版本、不同依赖预构建策略结果都不一样。2.2 相对路径在 history 路由下的解析规则这个是纯前端基础题但确实有人栽在这上面。浏览器解析相对 URL 时是以文档的基础 URL 为基准的而基础 URL 默认就是当前页面的地址。你的页面地址是http://xxx.com/report/detail那么./a.js解析成http://xxx.com/report/a.js../a.js解析成http://xxx.com/a.jsa.js解析成http://xxx.com/report/a.js/a.js解析成http://xxx.com/a.js只有当你的路由是完全的单层结构比如全是#/开头的 hash 路由或者你只在根路径刷新相对路径才碰巧正确。一旦用了 history 模式、路由有了层级问题立刻暴露。所以正确答案很简单在 index.html 里引用静态资源一律用绝对路径从根斜杠开头。提示如果你的应用部署在子目录下比如http://xxx.com/admin/那绝对路径也要带上子目录前缀或者用vite.config.js/vue.config.js里的base配置统一处理。别硬编码斜杠容易在子目录部署时再翻一次车。2.3 onMounted 跑得比 script 快这不是 bug 是必然Vue 的挂载生命周期和 HTML 里脚本的加载是两条独立的线。script标签如果是普通同步标签理论上会阻塞 HTML 解析直到执行完毕这种情况下能保证window.luckysheet在 Vue 挂载前就绪。但实际项目里为了性能往往会加defer、async或者为了兼容把脚本挂在/body前甚至有人用动态document.createElement(script)的方式按需加载。这些做法都会打破脚本先于挂载的假设。再加上打包后的 Vue 应用JS 产物本身也是一个script加载顺序变成Luckysheet 的几个脚本 → jQuery → 插件包 → Vue 应用产物。如果 Luckysheet 的脚本体积大、走了 CDN、或者浏览器做了脚本调度Vue 应用产物先执行的可能是存在的。这时候onMounted里访问window.luckysheet就是 undefined。结论是不要假定全局变量一定就绪要么保证引入顺序要么加等待逻辑。我个人的做法是两者都做顺序保证 99% 的场景等待逻辑兜底剩下 1% 的极端情况。3. 正确姿势把 Luckysheet 放进 public 目录聊完原因进入实操。这一节给出我认为最稳的方案把 Luckysheet 的整个 dist 目录丢进public下用 index.html 的 script 标签引入用绝对路径引用。这套方案在 Vite、Vue CLI、Webpack 手配项目里都通用。3.1 目录结构和必须存在的文件清单先从官方仓库或者发行包拿到dist目录它的结构大致是这样dist/ ├── luckysheet.umd.js ├── luckysheet.css ├── plugins/ │ ├── css/ │ │ ├── pluginsCss.css │ │ └── ... │ ├── js/ │ │ └── plugin.js │ └── ... ├── assets/ │ └── iconfont/ │ ├── iconfont.css │ └── ... ── exp/ └── ...在你的项目里新建一个目录比如public/luckysheet/把上述所有内容整体拷进去。注意是整体plugins和assets这两个子目录必须保留CSS 和字体图标都依赖它们少一个文件表格就会长得很奇怪——比如工具栏图标变成方块、下拉菜单错位。最终的目录长这样项目根目录/ ├── public/ │ └── luckysheet/ │ ├── luckysheet.umd.js │ ├── luckysheet.css │ ├── plugins/ │ └── assets/ ├── src/ ├── index.html └── vite.config.js这里有个细节要强调public目录下的文件在构建时会原封不动复制到产物根目录。也就是说构建完成后dist/luckysheet/luckysheet.umd.js会存在你 index.html 里写/luckysheet/luckysheet.umd.js就能访问到。这是它和src/assets最本质的区别——后者会被打包器处理路径和文件名都会变。3.2 index.html 里的引入顺序jQuery 必须排在前面Luckysheet 内部依赖 jQuery还依赖一批 jQuery 插件。顺序错了你会在控制台看到$ is not defined或者jQuery is not defined然后连锁反应导致luckysheet is not defined。正确的顺序如下!DOCTYPE html html langzh-CN head meta charsetUTF-8 / meta nameviewport contentwidthdevice-width, initial-scale1.0 / title报表平台/title !-- Luckysheet 样式顺序不敏感但建议放 head -- link relstylesheet href/luckysheet/plugins/css/pluginsCss.css / link relstylesheet href/luckysheet/plugins/plugins.css / link relstylesheet href/luckysheet/luckysheet.css / link relstylesheet href/luckysheet/assets/iconfont/iconfont.css / /head body div idapp/div !-- 脚本顺序jQuery 依赖包 - 核心包全部放在 body 结尾 -- script src/luckysheet/plugins/js/plugin.js/script script src/luckysheet/luckysheet.umd.js/script !-- Vue 应用入口放最后 -- script typemodule src/src/main.js/script /body /html注意plugin.js里面已经打包了 jQuery 和几个插件通常不需要你单独再引一个 jQuery。但如果你项目里其他地方也用 jQuery请确保版本兼容别出现两份 jQuery 互相覆盖$的情况。另外一个非常关键的点Vue 应用的入口脚本要放在 Luckysheet 之后。因为typemodule的脚本默认是延迟执行的实际执行时机在 HTML 解析完成之后而普通脚本会立即执行。所以从顺序上Luckysheet 的普通脚本已经执行完了Vue 才启动onMounted时全局变量已经就绪。这就是顺序保证 99% 场景的含义。3.3 绝对路径是底线别在这上面省事前面已经讲过相对路径的解析规则这里再强调一次所有引用静态资源的地方一律以/开头。包括 CSS 里的url()引用、字体图标路径、图片路径全都用绝对路径。Luckysheet 的iconfont.css里的字体路径是相对自身位置写的所以只要你把整个目录结构保持原样它就能正确找到字体文件。但如果你手动挪动了assets的位置字体就会 404工具栏图标全变方块。如果你的项目部署在子目录比如https://example.com/report/那有两个做法一是在 index.html 里写/report/luckysheet/luckysheet.umd.js二是在构建配置里把base设成/report/然后在 index.html 里用% BASE_URL %luckysheet/...这种模板变量Vue CLI或者import.meta.env.BASE_URLVite来拼接。我一般推荐第二种配置集中换部署路径时改一个地方就行。3.4 Vite 和 Vue CLI 在这件事上的差异两种构建工具在public目录的处理逻辑上是一致的——原样拷贝。差异主要体现在 index.html 的位置和模板变量上。对比项ViteVue CLIWebpackpublic 目录项目根public/项目根public/产物目录dist/dist/index.html 位置项目根目录public/index.html模板变量%BASE_URL%或import.meta.env.BASE_URL% BASE_URL %基础路径配置vite.config.js的basevue.config.js的publicPath有一点要注意Vue CLI 的 index.html 本身就是放在public里的所以你往public/luckysheet/放文件时两个目录是平级的不要嵌套错位置。我见过有人把luckysheet放进了public/index.html所在的目录下又建了一层结果路径全乱排查了半天。4. 组件层落地等待全局变量就绪再 create库引进来只是第一步组件里怎么用同样有讲究。这一节讲封装等待逻辑、在 Vue2 和 Vue3 里的正确调用位置、容器尺寸的处理以及销毁和重建。4.1 封装一个 waitForLuckysheet 工具函数即便前面顺序保证了脚本先执行加一层等待逻辑依然是划算的。它解决三种边界情况脚本加载异常慢、动态加载脚本、以及某些打包环境下的执行顺序抖动。函数很简单用轮询加超时// src/utils/waitForLuckysheet.js export function waitForLuckysheet(timeout 8000, interval 50) { return new Promise((resolve, reject) { // 已经就绪直接返回 if (window.luckysheet typeof window.luckysheet.create function) { resolve(window.luckysheet) return } const startTime Date.now() const timer setInterval(() { if (window.luckysheet typeof window.luckysheet.create function) { clearInterval(timer) resolve(window.luckysheet) return } if (Date.now() - startTime timeout) { clearInterval(timer) reject(new Error([Luckysheet] 全局对象加载超时请检查脚本是否引入成功)) } }, interval) }) }这个函数有两个设计点值得说一下。第一判断条件是typeof window.luckysheet.create function而不是单纯判断window.luckysheet存在。因为 UMD 包装在极端情况下可能先赋了个空对象光判断存在会误判。第二超时时间给 8 秒不算长因为正常加载远小于这个时间但也不算短能覆盖弱网场景。超时后抛出明确错误比让用户看到一句is not defined友好得多日志里也能直接定位。4.2 Vue3 与 Vue2 里各自该把 create 写在哪Vue3 的script setup里生命周期钩子是onMounted。关键点是必须等 DOM 真的渲染出来容器元素存在再去 create。因为 Luckysheet 的container参数接收的是一个元素 id 字符串它内部会去document.getElementById找这个容器找不到就报错或者渲染空白。template div classsheet-wrapper div idluckysheet-container classsheet-container/div /div /template script setup import { onMounted, onBeforeUnmount } from vue import { waitForLuckysheet } from /utils/waitForLuckysheet let sheetReady false onMounted(async () { try { const luckysheet await waitForLuckysheet() luckysheet.create({ container: luckysheet-container, lang: zh, showinfobar: false, showtoolbar: true, showsheetbar: true, data: [{ name: Sheet1, celldata: [] }] }) sheetReady true } catch (err) { console.error(err) } }) onBeforeUnmount(() { if (sheetReady window.luckysheet) { window.luckysheet.destroy() sheetReady false } }) /scriptVue2 的写法几乎一样只是生命周期换成mounted和beforeDestroyasync同样可用。要注意的是 Vue2 的mounted里用async是没问题的但别在created里做这件事因为那时候 DOM 还不存在。4.3 容器尺寸不给高度表格就是一片空白这是新手最容易忽略的一点Luckysheet 的容器必须有明确的高度否则不管你怎么 create页面都是白的控制台也不报错特别诡异。原因是它内部用绝对定位铺满容器容器高度是 0内容自然看不见。正确的容器样式.sheet-wrapper { width: 100%; height: calc(100vh - 120px); /* 减去头部和操作栏的高度 */ } .sheet-container { position: relative; width: 100%; height: 100%; }不要用min-height或者干脆不写高度一定要有确定的高度值。用100vh减去顶部导航高度是常见做法也可以用 flex 布局让父容器撑开。我试过用 flex 配合flex: 1加overflow: hidden效果也很稳尤其适合嵌套在复杂布局里的场景。4.4 销毁和重建避免重复 create 的坑单页应用里用户可能反复进出这个页面。如果你不做清理每次进入都create一次Luckysheet 会在同一个容器里叠加渲染表现是工具栏重复、数据错乱、内存持续增长。所以onBeforeUnmount里的destroy是必须的。还有一个更隐蔽的场景用了keep-alive缓存路由组件。这时候组件不会触发beforeDestroy再次进入页面激活时也不会触发mounted而是触发activated。如果你只在mounted里 create那第二次进入刷新数据就会失败。正确做法是同时监听activated和deactivatedonActivated(() { // 组件被激活时如果之前销毁过需要重新 create if (!sheetReady window.luckysheet) { window.luckysheet.create({ /* ... */ }) sheetReady true } }) onDeactivated(() { if (sheetReady window.luckysheet) { window.luckysheet.destroy() sheetReady false } })这套组合我用在数据填报系统里跑了很久反复切换路由不会有残留也不会重复创建。判断标志位用组件内的普通变量就行不要用window上的全局标志因为多个表格实例共存时全局标志会互相干扰。5. 刷新丢失与打包部署的问题速查前面讲的是正着走的路径这一节反过来把你可能撞上的各种异常做一个梳理配一张速查表方便你出问题时直接查。5.1 刷新后失效先分清是全失效还是只有子路由失效全失效指的是首页刷新也报luckysheet is not defined说明脚本压根没加载成功或者加载了但全局变量没挂上。这种情况优先查两件事Network 里脚本请求是否 200控制台里有没有$ is not defined这类前置依赖错误。只有子路由刷新才失效问题基本锁定在路径解析或者服务器重写规则上。路径问题按第 3.3 节改成绝对路径。如果路径改对了还是不行就要看服务器配置了——用 Nginx 部署 history 路由时必须配置所有非静态资源请求重写到index.html否则子路由刷新会直接 404页面都出不来location / { try_files $uri $uri/ /index.html; }这里有个坑要提醒try_files的重写规则如果写得太宽泛可能会把静态资源请求也重写到 index.html导致luckysheet.umd.js返回的是一个 HTML 文件控制台报Unexpected token 。合理的写法是给静态资源目录单独加location规则或者把静态资源放在独立的路径前缀下比如/static/避免和路由冲突。5.2 打包后 dist 里找不到 luckysheet 目录如果你构建完发现dist下没有luckysheet文件夹八成是文件放错了位置——放进了src/assets或者src/static。这两个目录在构建时都会被处理前者走打包器后者在 Vue CLI 里有一定特殊处理但也不推荐。唯一可靠的答案是public目录。还有一个变体文件确实在public下构建后也复制了但引用路径写错了。比如构建配置里base设成了./那么 index.html 里的绝对路径/luckysheet/...在子目录部署时就不对了。这种情况统一改成相对base拼接的方式或者把base设成明确的子目录路径。5.3 常见报错速查表报错 / 现象触发条件排查方向处理动作luckysheet is not defined无网络错误import 引入 UMD 包看 import 语句移入 public改 script 标签同上报错Network 有 404子路由刷新看脚本请求地址改绝对路径或配 base$ is not definedjQuery 缺失或顺序错看脚本顺序jQuery 放最前表格区域全白容器无高度检查 CSS给容器明确高度工具栏图标变方块字体图标路径错看 iconfont 请求保持 assets 目录结构偶发is not defined加载时序竞争刷新几次看是否复现加 waitForLuckysheet重复进入页面后数据错乱未销毁重复 create看生命周期是否有 destroy补 onBeforeUnmount 清理keep-alive 下二次进入不渲染只监听 mounted看是否用了 keep-alive改用 activated / deactivated导入 Excel 时报错未引入 Luckyexcel 插件看是否只引了核心包额外引入扩展包并注册6. 几个我踩过的坑和生产环境加固建议最后一节说点文档里不会写的。这些问题不影响功能跑通但在真实项目里迟早会碰提前知道能省不少时间。6.1 CSS 少一个文件表格长得像个半成品我第一次接 Luckysheet 的时候只引了luckysheet.css忘了pluginsCss.css和plugins.css结果表格能显示、数据能填但工具栏是错位的、右键菜单弹不出来、下拉选择框样式全无。排查了很久才意识到是 CSS 缺失。教训是引入文件要按官方完整清单来不要自己删减。哪怕你暂时不用某个功能把 CSS 全引上成本也极低一两个请求的事。字体图标同理。assets/iconfont/目录下有.ttf、.woff、.woff2等好几种格式服务器要配置正确的 MIME 类型否则浏览器拒绝加载字体图标就会变成一个个空心方块。常见的服务器默认配置一般没问题但如果你用了自定义的静态资源服务记得检查一下。6.2 keep-alive 缓存下重复 create 的隐蔽表现前面提了 keep-alive 的处理这里补充一个更隐蔽的表现控制台不报错页面也能显示但你会发现输入的数据在下一次进入时还在你以为缓存生效了其实是重复创建叠加了。判断方法很简单看luckysheet.getAllSheets()返回的数组长度是不是翻倍了或者右键菜单里出现重复项。还有一种情况是列表页和详情页共用同一个路由组件参数不同。这种情况下 Vue 默认会复用组件实例不会重新触发mounted。你需要监听路由参数变化在watch里主动destroy再createwatch(() route.params.id, async (newId) { if (window.luckysheet) { window.luckysheet.destroy() } await nextTick() const luckysheet await waitForLuckysheet() luckysheet.create({ container: luckysheet-container, data: await fetchSheetData(newId) }) })nextTick是必要的因为 destroy 之后 DOM 需要一次渲染周期才能回到干净状态。6.3 大数据量下的性能处理经验Luckysheet 在几千行数据下表现还行上万行就开始明显卡顿尤其是初始化加载。我实际项目里遇到过一次 5 万行的报表直接 create 的话页面会卡死十几秒。后来改成分片加载先 create 一个空表然后用setCellValue分批写入每批 500 行用requestAnimationFrame错开执行用户体验就顺多了。另外不要频繁调用全量刷新方法。有些同学每次单元格修改后都调getAllSheets再重建这是性能杀手。正确做法是监听 Luckysheet 的钩子或者用setCellValue做增量更新。导出数据时再统一getAllSheets拿全量。注意分片写入时如果用户在写入过程中开始编辑可能出现数据覆盖。我的处理方式是写入期间加一个遮罩层或者用一个 loading 状态禁止交互。别省这一步数据被覆盖比等两秒严重得多。6.4 内网离线部署的几个注意点很多企业的报表系统是部署在内网、不能访问外网的这意味着你不能用任何外部资源。Luckysheet 的 dist 目录本身不依赖外部网络所以离线部署没问题但要确保这几个细节第一不要用任何 CDN 地址引入 jQuery 或字体全部本地化。第二字体图标如果是通过import引入的要检查内联的字体路径有些版本的iconfont.css里会有字体路径的变体需要确认所有格式都指向了本地文件。第三构建产物的资源清单要核对一遍确认dist/luckysheet/下的所有文件都存在别因为.gitignore或者构建脚本的排除规则漏掉了某些扩展名的文件。我自己就遇到过一次.woff2被构建脚本的清理规则删掉导致图标失效排查了半天才发现是打包脚本的问题而不是 Luckysheet 本身。所以部署前把dist目录整体扫一遍看文件数量和源目录是否一致是个很划算的习惯。6.5 关于本地导入 Excel 数据这条线最后补一句如果你说的本地导入是指把本地 Excel 文件导进 Luckysheet 显示那用的是 Luckyexcel 扩展包它和核心包是两个东西。用法是先引入luckyexcel.umd.js然后import { readFile } from /utils/waitForLuckyexcel const file event.target.files[0] const arrayBuffer await file.arrayBuffer() const workbook await window.LuckyExcel.transformExcelToLucky(arrayBuffer, (exportJson) { return exportJson }) window.luckysheet.create({ container: luckysheet-container, data: workbook.sheets, title: file.name })这套流程里同样存在全局变量是否就绪的问题所以等待函数也要用上。window.LuckyExcel和window.luckysheet建议分别判断因为它们的加载顺序不一定一致。我一般把两个等待合并成一个Promise.all全部就绪后再执行导入逻辑这样最稳。踩过几次坑之后我的判断是凡是这种挂在全局的库就按先确认存在、再执行业务的套路写别指望运气好顺序就一定对。