From f09399f5f0fa9d21e1e0a5826e3f6640c51d6d6b Mon Sep 17 00:00:00 2001 From: zlwu Date: Sat, 5 Sep 2026 15:38:54 +0800 Subject: [PATCH] fix(web-admin): prevent blank Word export from broken seal img tags Self-closing slash before style produced invalid HTML that caused html-to-docx to emit an empty document. Co-authored-by: Cursor --- .../src/utils/crm/contract-html-to-docx.test.ts | 9 +++++++++ web-admin/src/utils/crm/contract-html-to-docx.ts | 13 +++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/web-admin/src/utils/crm/contract-html-to-docx.test.ts b/web-admin/src/utils/crm/contract-html-to-docx.test.ts index fff917f59..3c62c8417 100644 --- a/web-admin/src/utils/crm/contract-html-to-docx.test.ts +++ b/web-admin/src/utils/crm/contract-html-to-docx.test.ts @@ -100,6 +100,15 @@ describe('normalizeSealImagesForDocx', () => { assert.equal(out.includes('position: absolute'), false) assert.equal(out.includes('float:left'), true) assert.equal(out.includes('alt="印章"'), true) + assert.equal(out.includes('/ style='), false) + assert.match(out, /]*style="float:left[^"]*"\s*\/>/) + }) + + it('does not emit broken self-closing slash before style', () => { + const html = + '印章' + const out = normalizeSealImagesForDocx(html) + assert.equal(/]*\/\s+style=/.test(out), false) }) }) 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 3bfad369b..b5d7167e6 100644 --- a/web-admin/src/utils/crm/contract-html-to-docx.ts +++ b/web-admin/src/utils/crm/contract-html-to-docx.ts @@ -259,14 +259,19 @@ export function normalizeHeaderMetaFlexLayoutForDocx(html: string): string { /** * 印章在预览为 absolute 居中叠字;Word 不认 absolute,改为 float + 负 margin 浮于后续文字上方。 * 供方/需方标题下的横向分割线(border-bottom)保持不变。 + * + * 注意:必须去掉原标签里的自闭合 `/`,否则会生成非法的 ``, + * html-to-docx 会解析失败并导出空白文档。 */ export function normalizeSealImagesForDocx(html: string): string { - return html.replace(/]*alt\s*=\s*["']印章["'][^>]*)>/gi, (_full, attrs: string) => { - let rest = String(attrs).replace(/\s*style\s*=\s*(["'])[\s\S]*?\1/i, '') - rest = rest.replace(/\s*style\s*=\s*{[^}]*}/i, '') + return html.replace(/]*alt\s*=\s*["']印章["'][^>]*)\/?\s*>/gi, (_full, attrs: string) => { + let rest = String(attrs) + .replace(/\s*style\s*=\s*(["'])[\s\S]*?\1/i, '') + .replace(/\/\s*$/g, '') + .trim() const style = 'float:left;width:140px;height:140px;margin:4px 12px -120px 24px;opacity:0.85;' - return `` + return `` }) }