From 59a4f02701ef3b232b9f1d54ba0b29a1e8764704 Mon Sep 17 00:00:00 2001
From: zhouwx <1175765986@qq.com>
Date: 星期三, 03 十二月 2025 14:54:53 +0800
Subject: [PATCH] 修改
---
src/utils/exportWord.js | 228 +++++++++++++++++++++++++++++++++++++++-----------------
1 files changed, 158 insertions(+), 70 deletions(-)
diff --git a/src/utils/exportWord.js b/src/utils/exportWord.js
index 53b0013..584f673 100644
--- a/src/utils/exportWord.js
+++ b/src/utils/exportWord.js
@@ -1,22 +1,22 @@
-//引入工具
+// 引入工具(已修正 htmlparser2 导入)
import PizZip from 'pizzip';
import Docxtemplater from 'docxtemplater';
import JSZipUtils from 'jszip-utils';
import { saveAs } from 'file-saver';
-import ImageModule from 'docxtemplater-image-module-free'; // 新增:图片处理模
+import ImageModule from 'docxtemplater-image-module-free';
+import { Parser } from 'htmlparser2';
+import CSSOM from 'cssom';
-// 加载 .docx 模板文件
+// 加载 .docx 模板文件(不变)
function loadFile(url, callback) {
JSZipUtils.getBinaryContent(url, callback);
}
-// 下载生成的文档
-export function download(file, name) {
+// 下载生成的文档(不变)
+export function download(file, name) { }
-}
-// 辅助函数:将 base64 图片转为 Uint8Array(图片模块需要此格式)
+// 辅助函数:base64 转 Uint8Array(不变)
function base64ToUint8Array(base64) {
- // 去掉 base64 前缀(如 "data:image/png;base64,")
const base64WithoutPrefix = base64.replace(/^data:image\/\w+;base64,/, '');
const binaryString = atob(base64WithoutPrefix);
const length = binaryString.length;
@@ -27,24 +27,147 @@
return uint8Array;
}
+// -------------------------- 富文本处理核心函数(不变) --------------------------
+function getFontSizeFromStyle(styleStr) {
+ if (!styleStr) return 12;
+ try {
+ const style = CSSOM.parse(`.temp { ${styleStr} }`).cssRules[0].style;
+ const fontSize = style.getPropertyValue('font-size');
+ if (!fontSize) return 12;
+ const sizeNum = parseInt(fontSize.replace(/[^\d]/g, ''), 10);
+ return isNaN(sizeNum) ? 12 : sizeNum;
+ } catch (e) {
+ return 12;
+ }
+}
-// 生成并下载 Word 文档(templatePath是word文档模版地址,data是对应的数据)
-export function generateWordDocument(templatePath, data, name) {
- loadFile(templatePath, function (error, content) {
- if (error) {
- throw error
- return;
+function htmlToDocxXml(html) {
+ let xml = '';
+ let currentFontSize = 12;
+
+ const entityMap = {
+ ' ': ' ',
+ '<': '<',
+ '>': '>',
+ '&': '&',
+ '"': '"',
+ ''': "'"
+ };
+
+ const parser = new Parser({
+ onopentag: (name, attributes) => {
+
+ switch (name) {
+ case 'p':
+ xml += '<w:p><w:r>';
+ break;
+ case 'span':
+ const fontSize = getFontSizeFromStyle(attributes.style);
+ currentFontSize = fontSize;
+ const szVal = currentFontSize * 2;
+ xml += `<w:rPr><w:sz w:val="${szVal}"/><w:szCs w:val="${szVal}"/></w:rPr>`;
+ break;
+ case 'b':
+ case 'strong':
+ xml += '<w:rPr><w:b/></w:rPr>';
+ break;
+ case 'br':
+ xml += '<w:br/>';
+ break;
}
+ },
+ ontext: (text) => {
+ const processedText = text
+ .replace(/ |<|>|&|"|'/g, match => entityMap[match])
+ .replace(/\n/g, '<w:br/>');
+ xml += `<w:t>${processedText}</w:t>`;
+ },
+ onclosetag: (name) => {
+ switch (name) {
+ case 'p':
+ xml += '</w:r></w:p>';
+ currentFontSize = 12;
+ break;
+ case 'span':
+ case 'b':
+ case 'strong':
+ xml += '</w:rPr>';
+ break;
+ }
+ }
+ }, { decodeEntities: true });
- try {
- // 新版创建解析器的正确方式(无需 import AngularParser)
- const parser = (tag, _variable) => {
+ parser.write(html);
+ parser.end();
+ return xml;
+}
+
+function processRichTextData(data, richTextFields = []) {
+ const process = (obj) => {
+ if (typeof obj !== 'object' || obj === null) return obj;
+ if (Array.isArray(obj)) {
+ return obj.map(item => process(item));
+ }
+ const processedObj = { ...obj };
+ for (const key in processedObj) {
+ if (processedObj.hasOwnProperty(key)) {
+ if (richTextFields.includes(key) && typeof processedObj[key] === 'string') {
+ const filteredHtml = processedObj[key].replace(/<p>\s*( )?\s*<\/p>/g, '');
+ processedObj[key] = htmlToDocxXml(filteredHtml);
+ } else {
+ processedObj[key] = process(processedObj[key]);
+ }
+ }
+ }
+ return processedObj;
+ };
+ return process(data);
+}
+
+// -------------------------- 核心:生成 Word 文档(修正构造函数配置) --------------------------
+export function generateWordDocument(templatePath, data, name, richTextFields = []) {
+ loadFile(templatePath, function (error, content) {
+ if (error) {
+ console.error('加载模板失败:', error);
+ return;
+ }
+
+ try {
+
+ const processedData = processRichTextData(data, richTextFields);
+
+ // 图片处理模块(不变)
+ const imageModule = new ImageModule({
+ getImage: function (tagValue, tagName) {
+ return base64ToUint8Array(tagValue);
+ },
+ getSize: function (tagValue, tagName) {
+ switch (tagName) {
+ case 'avatar':
+ return [200, 200];
+ case 'coverImg':
+ return [600, 300];
+ default:
+ return [80, 130];
+ }
+ }
+ });
+
+ // 关键修正:将 parser 直接传入构造函数 options,删除 setOptions()
+ const zip = new PizZip(content);
+ const doc = new Docxtemplater(zip, {
+ paragraphLoop: true,
+ linebreaks: true,
+ raw: true, // 富文本核心:允许插入原始 XML
+ modules: [imageModule], // 图片模块
+ nullGetter: () => '', // 空值处理
+ // 直接在这里传入 parser 配置(无需 setOptions())
+ parser: (tag, _variable) => {
return {
get(scope) {
if (scope[tag] !== undefined) {
return scope[tag] || '';
}
- // 处理特殊变量(如 $first)
if (tag.startsWith('$')) {
const varName = tag.slice(1);
return scope[varName] || '';
@@ -52,61 +175,26 @@
return '';
}
};
- };
- // 1. 配置图片处理规则(核心)
- const imageModule = new ImageModule({
- // 获取图片:根据传入的图片数据(base64)转为模块需要的格式
- getImage: function (tagValue, tagName) {
- // tagValue:data 中对应图片键的值(必须是 base64 字符串)
- // tagName:图片占位符的键名(如 "avatar")
- return base64ToUint8Array(tagValue);
- },
- // 设置图片尺寸:返回 [宽度, 高度](单位:px),可动态调整
- getSize: function (tagValue, tagName) {
+ }
+ });
- // 示例:根据不同的图片键,返回不同尺寸
- switch (tagName) {
- case 'avatar': // 头像:200x200
- return [200, 200];
- case 'coverImg': // 封面图:600x300
- return [600, 300];
- default: // 默认尺寸:400x300
- return [80, 130];
- }
- }
- });
- // 加载模板文件内容到 PizZip
- const zip = new PizZip(content);
- const doc = new Docxtemplater(zip, {
- paragraphLoop: true,
- linebreaks: true,
- modules: [imageModule] // 关键:注入图片模块
- });
+ // 删除这一行!!!v4 构造函数已包含 parser,无需手动 setOptions
+ // doc.setOptions({ parser });
- // const parser = new AngularParser();
- // doc.setOptions({ parser });
+ // 注入数据并渲染(不变)
+ doc.setData(processedData);
+ doc.render();
+ const fileWord = doc.getZip().generate({
+ type: 'blob',
+ mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ });
- // 设置模板中的占位符数据
- doc.setData(data);
+ saveAs(fileWord, name);
+ console.log('导出成功!');
- // 渲染文档
- doc.render();
-
- // 生成最终的文档 Blob
- const fileWord = doc.getZip().generate({
- type: 'blob',
- mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
- });
-
- saveAs(fileWord, name);
-
- // // 返回生成的文档 Blob
- // resolve(fileWord);
- } catch (error) {
- console.error('Error rendering document:', error);
- throw error
- }
- });
-
+ } catch (error) {
+ console.error('生成文档失败:', error);
+ }
+ });
}
--
Gitblit v1.9.2