Vue项目中导出word的2种方法


1.docx 库

优点:

// 1. 完全通过代码控制文档结构和样式
const doc = new Document({
    sections: [
        {
            properties: {},
            children: [
                new Paragraph({
                    children: [
                        new TextRun({
                            text: "标题",
                            bold: true,
                            size: 32,
                        }),
                    ],
                }),
            ],
        },
    ],
});
  • 完全编程控制,灵活性高
  • 可以精确控制每个元素的样式和属性
  • 适合生成结构固定的文档
  • 不需要预先准备模板文件
  • 代码即文档,便于版本控制

缺点:

// 复杂样式需要大量代码
new Paragraph({
    children: [
        new TextRun({
            text: "复杂样式",
            bold: true,
            size: 28,
            color: "FF0000",
            font: "Arial",
            highlight: "yellow",
        }),
    ],
    spacing: { before: 200, after: 200 },
    alignment: AlignmentType.CENTER,
});
  • 需要编写大量代码来实现复杂样式
  • 调整样式需要修改代码
  • 不适合非技术人员维护文档样式
  • 复杂布局实现困难
  • 难以实现某些高级 Word 功能

2.docxtemplater + pizzip

优点:

// 1. 使用现成的 Word 模板,只需要替换变量
const doc = new Docxtemplater(zip, {
    paragraphLoop: true,
    linebreaks: true,
});

// 2. 简单的数据填充
doc.render({
    name: "张三",
    age: 25,
    items: [
        { title: "项目1", desc: "描述1" },
        { title: "项目2", desc: "描述2" },
    ],
});
  • 可以使用 Word 直接编辑模板
  • 完全保留 Word 的样式和格式
  • 非技术人员可以维护模板
  • 支持复杂的 Word 格式和功能
  • 适合生成样式复杂的文档
  • 模板和代码分离,职责清晰

缺点:

// 需要预先准备模板文件
// template.docx 中的内容:
/*
姓名:{name}
年龄:{age}
项目经验:
{#items}
- {title}: {desc}
{/items}
*/
  • 需要预先准备模板文件
  • 模板文件的管理和版本控制较复杂
  • 不能完全通过代码控制样式
  • 模板语法有一定学习成本
  • 调试相对困难

3.适用场景对比

docx 适合:

  1. 简单的文档生成
  2. 结构固定的报表
  3. 需要程序完全控制的场景
  4. 不需要复杂样式的文档
  5. 批量生成标准化文档

docxtemplater 适合:

  1. 复杂格式的文档
  2. 需要频繁调整样式的文档
  3. 需要非技术人员参与维护的场景
  4. 基于模板的文档生成
  5. 需要保持 Word 原生样式的场景

4.代码示例对比

docx 实现表格:

const table = new Table({
    rows: [
        new TableRow({
            children: [
                new TableCell({
                    children: [new Paragraph("标题1")],
                }),
                new TableCell({
                    children: [new Paragraph("标题2")],
                }),
            ],
        }),
    ],
});

docxtemplater 实现表格:

// 在 Word 模板中创建表格,然后:
doc.render({
    table: [
        { col1: "数据1", col2: "数据2" },
        { col1: "数据3", col2: "数据4" },
    ],
});

5.使用建议

1.文档格式复杂:

  • 格式复杂
  • 需要频繁调整样式
  • 需要非技术人员参与维护

→ 选择 docxtemplater

2.文档结构简单固定:

  • 结构简单固定
  • 主要是文本和简单表格
  • 完全由程序控制

→ 选择 docx

3.混合使用:

  • 简单报表用 docx
  • 复杂文档用 docxtemplater

6.官方文档

7.附详细代码

前期准备

默认你的项目使用了 electron+vue,且对 dialog 进行了简单的封装,见下:

封装 dialog

// main.js
const { ipcMain, dialog } = require("electron");
// ... 其他 electron 代码 ...

/**
 * 主进程打开dialog
 * 该方法对dialog调用进行了封装
 */
ipcMain.handle("openDialog", (event, ...args) => {
    const [type, ...option] = args;
    return dialog[type](win, ...option);
});
// dialogUtils.js
import { ipcRenderer } from "electron";

/***
 * 调用原生文件对话框
 * @param type 'showOpenDialog', 'showSaveDialog'
 * @param option 详见electron dialog https://www.electronjs.org/zh/docs/latest/api/dialog
 * @returns {Promise<any>|null}
 */
export function openDialog(type, option) {
    const types = ["showOpenDialog", "showSaveDialog"];
    if (!types.includes(type)) {
        console.log("打开对话框的类型,不在当前定义范围内");
        return new Promise((_, reject) => reject("打开对话框的类型,不在当前定义范围内"));
    }
    return ipcRenderer.invoke("openDialog", type, option);
}

1.docx 库

安装 docx

npm install docx

封装 docx 方法

// exportDocx.js
const { Document, Paragraph, TextRun, Table, TableRow, TableCell, WidthType, Packer } = require("docx");
const fs = require("fs");
// 导出docx文件
export const outFileDocx = async (filePath, data) => {
    try {
        if (!filePath) {
            return { success: false, message: "已取消导出" };
        }
        // 创建文档内容(这里示例使用传入的数据)
        const doc = new Document({
            sections: [
                {
                    properties: {},
                    children: [
                        new Paragraph({
                            children: [
                                new TextRun({
                                    text: data.title || "导出文档",
                                    bold: true,
                                    size: 32,
                                }),
                            ],
                        }),
                        // 如果有表格数据,创建表格
                        // 表格具体位置需要自定义
                        createTable(data.tableData),
                        new Paragraph({
                            children: [
                                new TextRun({
                                    text: data.content || "",
                                }),
                            ],
                        }),
                    ],
                },
            ],
        });

        // 生成并保存文档
        const buffer = await Packer.toBuffer(doc);
        fs.writeFileSync(filePath, buffer);

        return { success: true, message: "文档导出成功" };
    } catch (error) {
        console.error("导出文档错误:", error);
        return { success: false, message: error.message };
    }
};

// 创建表格的辅助函数
function createTable(data) {
    if (!data || !data.length) return new Paragraph("");
    const rows = data.map((row) => {
        return new TableRow({
            children: Object.values(row).map((cellText) => {
                return new TableCell({
                    children: [
                        new Paragraph({
                            children: [new TextRun(String(cellText))],
                        }),
                    ],
                });
            }),
        });
    });

    return new Table({
        width: {
            size: 100,
            type: WidthType.PERCENTAGE,
        },
        rows: rows,
    });
}

页面使用

// vue内使用
const formData = ref({
    title: "",
    content: "",
    tableData: [
        { name: "张三", age: "25", department: "技术部" },
        { name: "李四", age: "30", department: "市场部" },
    ],
});

// 导出文档
const outFile = (data) => {
    openDialog("showSaveDialog", {
        title: "导出文档",
        defaultPath: `${data.title || "导出文档"}_${new Date().getTime()}.docx`,
        // 在 Windows 和 Linux 上, 打开对话框不能同时是文件选择器和目录选择器, 因此如果在这些平台上将 properties 设置为["openFile"、"openDirectory"], 则将显示为目录选择器。
        properties: ["openFile", "openDirectory"],
        // 文件下载扩展名
        filters: [{ name: "Word Documents", extensions: ["docx"] }],
        // 点击保存回调
    }).then(({ filePath }) => {
        if (filePath) {
            // 检测文件扩展名是否正确
            outFileDocx(filePath, data);
        }
    });
};

// outFile(formData.value);

2.docxtemplater+PizZip

安装 docxtemplater、pizzip

npm install docxtemplater pizzip

模版内容,格式为.docx

{#list}{.}{/list}

封装 exportWordDocx 方法

import Docxtemplater from "docxtemplater";
import PizZipUtils from "pizzip/utils/index.js";
import PizZip from "pizzip";
// 其中 PizZipUtils 可以使用JSZipUtils ,安装、使用如下:
// npm install  jszip jszip-utils
// import JSZipUtils from "jszip-utils";
// JSZipUtils.getBinaryContent(url, callback);

const loadFile = (url, callback) => {
    PizZipUtils.getBinaryContent(url, callback);
};

/**
 * 导出word
 * @param demoUrl 模版地址
 * @param docxStringData 导出内容,当前为字符串
 */
export const exportWordDocx = (filePath, demoUrl, docxStringData) => {
    // 读取并获得模板文件的二进制内容
    loadFile(demoUrl, async (error, content) => {
        // 抛出异常
        if (error) {
            throw error;
        }

        // 创建一个PizZip实例,内容为模板的内容
        let zip = new PizZip(content);
        // 创建并加载docxtemplater实例对象
        let doc = new Docxtemplater()
            .loadZip(zip)
            // 去除未定义值所显示的undefined
            .setOptions({
                nullGetter: function () {
                    return "";
                },
            })
            // 设置角度解析器
            // 设置模板变量的值,对象的键需要和模板上的变量名一致,值就是你要放在模板上的值
            .setData({
                list: [docxStringData],
            });

        try {
            // 用模板变量的值替换所有模板变量
            doc.render();

            // 生成一个代表docxtemplater对象的zip文件(不是一个真实的文件,而是在内存中的表示)
            let out = doc.getZip().generate({
                type: "blob",
                mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
            });
            const buffer = Buffer.from(await out.arrayBuffer());
            fs.writeFileSync(filePath, buffer);
            return { success: true, message: "文档导出成功" };
        } catch (error) {
            // 抛出异常
            let e = {
                message: error.message,
                name: error.name,
                stack: error.stack,
                properties: error.properties,
            };
            console.log(JSON.stringify({ error: e }));
            return { success: false, message: error.message };
        }
    });
};

页面使用

// vue内使用
const stringData = ref("");

// 导出文档
const outFile = (data) => {
    openDialog("showSaveDialog", {
        title: "导出文档",
        defaultPath: `${data.title || "导出文档"}_${new Date().getTime()}.docx`,
        // 在 Windows 和 Linux 上, 打开对话框不能同时是文件选择器和目录选择器, 因此如果在这些平台上将 properties 设置为["openFile"、"openDirectory"], 则将显示为目录选择器。
        properties: ["openFile", "openDirectory"],
        // 文件下载扩展名
        filters: [{ name: "Word Documents", extensions: ["docx"] }],
        // 点击保存回调
    }).then(({ filePath }) => {
        if (filePath) {
            // 检测文件扩展名是否正确
            exportWordDocx(filePath, "模版.docx", data);
        }
    });
};

// outFile(stringData.value);

文章作者: 弈心
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 弈心 !
评论
  目录