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 适合:
- 简单的文档生成
- 结构固定的报表
- 需要程序完全控制的场景
- 不需要复杂样式的文档
- 批量生成标准化文档
docxtemplater 适合:
- 复杂格式的文档
- 需要频繁调整样式的文档
- 需要非技术人员参与维护的场景
- 基于模板的文档生成
- 需要保持 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);