// 引入工具(已修正 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 { Parser } from 'htmlparser2';
|
import CSSOM from 'cssom';
|
|
// 加载 .docx 模板文件(不变)
|
function loadFile(url, callback) {
|
JSZipUtils.getBinaryContent(url, callback);
|
}
|
|
// 下载生成的文档(不变)
|
export function download(file, name) { }
|
|
// 辅助函数:base64 转 Uint8Array(不变)
|
function base64ToUint8Array(base64) {
|
const base64WithoutPrefix = base64.replace(/^data:image\/\w+;base64,/, '');
|
const binaryString = atob(base64WithoutPrefix);
|
const length = binaryString.length;
|
const uint8Array = new Uint8Array(length);
|
for (let i = 0; i < length; i++) {
|
uint8Array[i] = binaryString.charCodeAt(i);
|
}
|
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;
|
}
|
}
|
|
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 });
|
|
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] || '';
|
}
|
if (tag.startsWith('$')) {
|
const varName = tag.slice(1);
|
return scope[varName] || '';
|
}
|
return '';
|
}
|
};
|
}
|
});
|
|
// 删除这一行!!!v4 构造函数已包含 parser,无需手动 setOptions
|
// doc.setOptions({ parser });
|
|
// 注入数据并渲染(不变)
|
doc.setData(processedData);
|
doc.render();
|
|
const fileWord = doc.getZip().generate({
|
type: 'blob',
|
mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
});
|
|
saveAs(fileWord, name);
|
console.log('导出成功!');
|
|
} catch (error) {
|
console.error('生成文档失败:', error);
|
}
|
});
|
}
|