zhouwx
2025-12-03 59a4f02701ef3b232b9f1d54ba0b29a1e8764704
src/utils/exportWord.js
@@ -1,56 +1,200 @@
//引入工具
// 引入工具(已修正 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 模板文件
// 加载 .docx 模板文件(不变)
function loadFile(url, callback) {
    JSZipUtils.getBinaryContent(url, callback);
}
// 下载生成的文档
export function download(file, name) {
// 下载生成的文档(不变)
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;
}
// 生成并下载 Word 文档(templatePath是word文档模版地址,data是对应的数据)
export function generateWordDocument(templatePath, data, name) {
        loadFile(templatePath, function (error, content) {
            if (error) {
                throw error
                return;
// -------------------------- 富文本处理核心函数(不变) --------------------------
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 = {
        '&nbsp;': '&#160;',
        '&lt;': '<',
        '&gt;': '>',
        '&amp;': '&',
        '&quot;': '"',
        '&#39;': "'"
    };
    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;
            }
            try {
                // 加载模板文件内容到 PizZip
                const zip = new PizZip(content);
                const doc = new Docxtemplater(zip, {
                    paragraphLoop: true,
                    linebreaks: true,
                });
                // 设置模板中的占位符数据
                doc.setData(data);
                // 渲染文档
                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
        },
        ontext: (text) => {
            const processedText = text
                .replace(/&nbsp;|&lt;|&gt;|&amp;|&quot;|&#39;/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*(&nbsp;)?\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);
        }
    });
}