From f872333df62043d42eb0cd2b01a036e5f0b6d09f Mon Sep 17 00:00:00 2001 From: zlwu Date: Sat, 5 Sep 2026 15:06:20 +0800 Subject: [PATCH] fix(web-admin): polyfill Buffer/global for contract Word export in Vite html-to-docx still reads Node globals after Vite rebundles; inject buffer polyfill, map global to globalThis, and alias the package to the browser build. Co-authored-by: Cursor --- web-admin/package.json | 1 + web-admin/pnpm-lock.yaml | 11 ++++++++ web-admin/src/env.d.ts | 12 +++++++++ .../src/utils/crm/contract-html-to-docx.ts | 27 ++++++++++++++++--- web-admin/vite.config.ts | 12 +++++++-- 5 files changed, 58 insertions(+), 5 deletions(-) diff --git a/web-admin/package.json b/web-admin/package.json index dc90f2b8a..7c4ebd18b 100644 --- a/web-admin/package.json +++ b/web-admin/package.json @@ -67,6 +67,7 @@ "@wangeditor/editor": "^5.1.23", "@wangeditor/editor-for-vue": "next", "axios": "^1.12.2", + "buffer": "^6.0.3", "crypto-js": "^4.2.0", "dagre": "^0.8.5", "echarts": "^6.0.0", diff --git a/web-admin/pnpm-lock.yaml b/web-admin/pnpm-lock.yaml index f04841880..5bbe2d008 100644 --- a/web-admin/pnpm-lock.yaml +++ b/web-admin/pnpm-lock.yaml @@ -47,6 +47,9 @@ importers: axios: specifier: ^1.12.2 version: 1.12.2 + buffer: + specifier: ^6.0.3 + version: 6.0.3 crypto-js: specifier: ^4.2.0 version: 4.2.0 @@ -1743,6 +1746,9 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + buffers@0.1.1: resolution: {integrity: sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==} engines: {node: '>=0.2.0'} @@ -6089,6 +6095,11 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + buffers@0.1.1: {} bundle-name@4.1.0: diff --git a/web-admin/src/env.d.ts b/web-admin/src/env.d.ts index fb7c421b5..3472a6fa0 100755 --- a/web-admin/src/env.d.ts +++ b/web-admin/src/env.d.ts @@ -17,6 +17,18 @@ declare module 'vue-img-cutter' declare module 'file-saver' +declare module 'buffer' + +declare module '@turbodocx/html-to-docx' { + const HTMLtoDOCX: ( + htmlString: string, + headerHTMLString?: string | null, + documentOptions?: Record, + footerHTMLString?: string | null + ) => Promise + export default HTMLtoDOCX +} + declare module '@turbodocx/html-to-docx/dist/html-to-docx.browser.esm.js' { const HTMLtoDOCX: ( htmlString: string, diff --git a/web-admin/src/utils/crm/contract-html-to-docx.ts b/web-admin/src/utils/crm/contract-html-to-docx.ts index 32b80c704..4917799bb 100644 --- a/web-admin/src/utils/crm/contract-html-to-docx.ts +++ b/web-admin/src/utils/crm/contract-html-to-docx.ts @@ -1,5 +1,7 @@ /** 合同打印弹窗:HTML → DOCX 预处理与生成 */ +import { Buffer } from 'buffer' + const DOCX_MIME = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' type HtmlToDocxFn = ( @@ -9,13 +11,30 @@ type HtmlToDocxFn = ( footerHTMLString?: string | null ) => Promise +/** + * html-to-docx(含 browser 包)在 Vite 二次打包后仍会直接读全局 Buffer/global。 + * 必须在调用转换前注入,否则 buildImage 报 Buffer is not defined,随后 global is not defined。 + */ +function ensureDocxNodeGlobals(): void { + const g = globalThis as typeof globalThis & { + Buffer?: typeof Buffer + global?: typeof globalThis + process?: { env?: Record } + } + if (!g.Buffer) g.Buffer = Buffer + if (!g.global) g.global = g + if (!g.process) g.process = { env: {} } + else if (!g.process.env) g.process.env = {} +} + /** * 懒加载 browser 构建。 - * Vite 默认会解析到 Node ESM(依赖 Buffer/global),浏览器会报错; - * 顶层静态 import browser 包又会让 Node 单测因 XMLHttpRequest 初始化失败。 + * Vite 默认会解析到 Node ESM(依赖 Buffer/global);显式 browser 入口 + 上方 globals 注入。 */ async function loadHtmlToDocx(): Promise { - const mod = await import('@turbodocx/html-to-docx/dist/html-to-docx.browser.esm.js') + ensureDocxNodeGlobals() + // 经 vite alias 指向 browser.esm.js + const mod = await import('@turbodocx/html-to-docx') const candidate = typeof mod === 'function' ? mod : (mod as { default?: unknown }).default if (typeof candidate !== 'function') { throw new Error('html-to-docx 加载失败:未获得可调用的转换函数') @@ -84,9 +103,11 @@ export async function generateContractDocxBlob( preparedHtml: string, title?: string ): Promise { + ensureDocxNodeGlobals() const HTMLtoDOCX = await loadHtmlToDocx() const htmlWithImages = await inlineRemoteImagesAsDataUrls(preparedHtml) const fullHtml = `${htmlWithImages}` + ensureDocxNodeGlobals() const result = await HTMLtoDOCX(fullHtml, null, { orientation: 'portrait', title: title || '合同', diff --git a/web-admin/vite.config.ts b/web-admin/vite.config.ts index 4087310a4..a3db01acf 100644 --- a/web-admin/vite.config.ts +++ b/web-admin/vite.config.ts @@ -22,7 +22,9 @@ export default ({ mode }: { mode: string }) => { return defineConfig({ define: { - __APP_VERSION__: JSON.stringify(VITE_VERSION) + __APP_VERSION__: JSON.stringify(VITE_VERSION), + // html-to-docx 等依赖会读 Node 全局;浏览器映射到 globalThis + global: 'globalThis' }, base: VITE_BASE_URL, server: { @@ -56,7 +58,12 @@ export default ({ mode }: { mode: string }) => { '@icons': resolvePath('src/assets/icons'), '@utils': resolvePath('src/utils'), '@stores': resolvePath('src/store'), - '@styles': resolvePath('src/assets/styles') + '@styles': resolvePath('src/assets/styles'), + // 合同 Word 导出:强制 browser 构建,避免 Vite 落到 Node ESM + '@turbodocx/html-to-docx': resolvePath( + 'node_modules/@turbodocx/html-to-docx/dist/html-to-docx.browser.esm.js' + ), + buffer: 'buffer/' } }, build: { @@ -140,6 +147,7 @@ export default ({ mode }: { mode: string }) => { 'xgplayer', 'crypto-js', 'file-saver', + 'buffer', 'vue-img-cutter', 'element-plus/es', 'element-plus/es/components/*/style/css',