You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中docxtemplater生成的docx BLOB如何转换为保留原模板的PDF

问题描述
  • 技术栈为React.js + Node.js,已通过docxtemplater实现基于用户输入生成Word文档的功能,运行正常。
  • 需求为支持用户直接下载生成内容对应的PDF格式文件,docxtemplater本身仅支持导出docx格式,无法直接生成PDF。
  • 已实现流程:
    1. 生成docx格式的BLOB文件
    2. 通过GridFS将生成的docx BLOB存入MongoDB
    3. 已掌握通过GET请求拉取MongoDB中存储的BLOB文件的逻辑
  • 卡点:拉取到docx格式的BLOB后,无法转换为完整保留原docx模板样式的PDF文件。
现有业务代码
import React, { useState, useEffect } from "react";
import Docxtemplater from "docxtemplater";
import PizZip from "pizzip";
import PizZipUtils from "pizzip/utils/index.js";
import { saveAs } from "file-saver"; 
import axios from "axios";

function loadFile(url, callback) {
  PizZipUtils.getBinaryContent(url, callback);
}

export const DocxFile = ({ formData }) => {
  const [file, setFile] = useState(null);
  const [currentlyUploading, setCurrentlyUploading] = useState(false);
  const [docxId, setDocxId] = useState(null);
  const [progress, setProgress] = useState(null);
  const [inputContainsFile, setInputContainsFile] = useState(false);
  const [template, setTemplate] = useState();


  const generateDocument = () => {
    loadFile(template, function (error, content) {
      if (error) {
        throw error;
      }

      var zip = new PizZip(content);
      var doc = new Docxtemplater(zip, {
        paragraphLoop: true,
        linebreaks: true,
      });

      doc.setData({
        company: formData.general.companyClient,
        version: formData.documentVersion,
        quote: formData.general.quoteNumber,
        HERE MY DATA //Working :)
      });
      try {
        doc.render();
      } catch (error) {
        function replaceErrors(key, value) {
          if (value instanceof Error) {
            return Object.getOwnPropertyNames(value).reduce(function (
              error,
              key
            ) {
              error[key] = value[key];
              return error;
            },
            {});
          }
          return value;
        }

        if (error.properties && error.properties.errors instanceof Array) {
          const errorMessages = error.properties.errors
            .map(function (error) {
              return error.properties.explanation;
            })
            .join("\n");
        }
        throw error;
      }
      var out = doc.getZip().generate({
        type: "blob",
        mimeType:
          "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
      });
      saveAs(
        out,
        `${name}.${documentVersion}.docx`
      );

      setFile(out);
      setInputContainsFile(true);
    });
  };

  const fileUploadHandler = () => {
    const fd = new FormData();
    fd.append("docx", file, file.name);
    axios
      .post(`api/docx/upload`, fd, {
        onUploadProgress: (ProgressEvent) => {
          setProgress((ProgressEvent.loaded / ProgressEvent.total) * 100);
          console.log(
            "upload progress",
            Math.round((ProgressEvent.loaded / ProgressEvent.total) * 100)
          );
        },
      })
      .then(({ data }) => {
        setDocxId(data);
        setFile(null);
        setInputContainsFile(false);
        setCurrentlyUploading(false);
      })
      .catch((err) => {
        console.log(err);
        if (err.response.status === 400) {
          const errMsg = err.response.data;
          if (errMsg) {
            console.log(errMsg);
          }
        } else {
          console.log("other error", err);
          setInputContainsFile(false);
          setCurrentlyUploading(false);
        }
      });
  };
  const handleClick = () => {
    if (inputContainsFile) {
      setCurrentlyUploading(true);
      fileUploadHandler();
    }
  };

  return (
    <>
      <button
        className="modify__button1 enregistrer generator__button"
        onClick={generateDocument}
      >
       Generate the docx
      </button>
      <label htmlFor="file" onClick={handleClick}>
        {file ? <>SUBMIT</> : <></>}
      </label>
    </>
  );
};

上传完成后MongoDB中存储的BLOB记录如下图:
MongoDB存储的BLOB记录截图


解决方案

不要在前端做docx转PDF,所有前端侧的转换库对docx样式的兼容度都极低,复杂模板的排版、字体、格式大概率错乱,转换逻辑必须放在Node.js后端实现。
目前开源方案里对docx样式还原度最高的方案是调用LibreOffice无头模式做转换,和本地用Word打开docx再导出PDF的效果基本一致,能100%保留原有模板的样式、表格、页眉页脚、图片等元素。

实现步骤

  1. 环境依赖安装
    服务端先安装LibreOffice:

    • Ubuntu/Debian系统执行命令:apt install libreoffice libreoffice-writer
    • CentOS/RHEL系统执行命令:yum install libreoffice libreoffice-writer
    • Windows本地开发环境直接安装LibreOffice桌面版即可,记住安装路径。
      Node.js侧安装转换封装依赖:npm install libreoffice-convert
  2. 后端转换逻辑
    从GridFS拉取到docx的Buffer数据后,直接调用转换方法即可,示例代码:

    const libre = require('libreoffice-convert');
    
    /**
     * docx Buffer转PDF Buffer
     * @param {Buffer} docxBuffer 从GridFS读取的docx文件Buffer
     * @returns {Promise<Buffer>} 转换后的PDF Buffer
     */
    async function docxToPdf(docxBuffer) {
      return new Promise((resolve, reject) => {
        libre.convert(docxBuffer, '.pdf', undefined, (err, pdfBuffer) => {
          if (err) return reject(err);
          resolve(pdfBuffer);
        });
      });
    }
    
    // 下载PDF的接口示例
    router.get('/api/docx/pdf/:id', async (req, res) => {
      try {
        // 这里是你已经实现的从GridFS读取docx的逻辑
        const docxBuffer = await getDocxFileFromGridFS(req.params.id);
        const pdfBuffer = await docxToPdf(docxBuffer);
        // 设置响应头,直接返回PDF流供前端下载
        res.setHeader('Content-Type', 'application/pdf');
        res.setHeader('Content-Disposition', 'attachment; filename="导出文档.pdf"');
        res.send(pdfBuffer);
      } catch (err) {
        res.status(500).send('文档转换失败');
      }
    });
    
  3. 前端调用下载
    前端请求接口拿到PDF Blob后,直接用已经引入的file-saver保存即可:

    const downloadPdf = async (docxId) => {
      const res = await axios.get(`/api/docx/pdf/${docxId}`, {
        responseType: 'blob'
      });
      saveAs(res.data, '导出文档.pdf');
    }
    

注意事项

  • 如果docx模板使用了特殊字体,必须把对应字体文件安装到运行LibreOffice的服务器上,否则会出现字体替换导致的排版错位。安装字体后执行fc-cache -fv刷新字体缓存即可生效。
  • 不要尝试用纯JS实现的docx转PDF库,这类库是自行解析docx结构重新排版,对表格合并、文本框、特殊缩进、页眉页脚等复杂样式的支持极差,还原度完全达不到业务要求。
  • 如果项目部署在无法安装系统依赖的Serverless环境,可以用Docker部署Gotenberg服务作为转换接口,本质还是封装了LibreOffice的转换能力,只需要传文件就能返回PDF,不需要额外装系统依赖。

内容的提问来源于stack exchange,提问作者Elodie Muller

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 17:09:14