在Vue.js项目中使用docx和file-saver实现Word文档导出

在实际的前端项目中,经常需要将数据或页面内容导出为Word文档。利用docxfile-saver库,可以实现强大的文档导出功能,满足报表生成、数据导出、文档打印等需求。

  • docx:纯JavaScript的Word文档生成库,功能全面,无依赖
  • file-saver:简化文件保存操作,兼容性好

安装

bash 复制代码
# 安装最新版本
npm install docx file-saver

# 或使用yarn
yarn add docx file-saver

# 或使用pnpm
pnpm add docx file-saver

版本兼容性

版本 特点 建议
docx 8.x 传统API,文档丰富 已有项目维护
docx 9.x 新API,直接配置(本文基于此) 新项目首选
file-saver 2.x 标准API,兼容性好 推荐使用

基础示例

vue 复制代码
<template>
  <div>
    <button @click="exportSimpleDocument" :disabled="loading">
      {{ loading ? "生成中..." : "导出Word文档" }}
    </button>
  </div>
</template>

<script setup>
import { ref } from "vue";
import { Document, Packer, Paragraph, TextRun } from "docx";
import { saveAs } from "file-saver";

const loading = ref(false);

async function exportSimpleDocument() {
  loading.value = true;

  try {
    // 1. 创建文档对象
    const doc = new Document({
      sections: [
        {
          children: [
            new Paragraph({
              children: [new TextRun("欢迎使用文档导出功能")],
            }),
            new Paragraph({
              children: [new TextRun("这是一个简单的示例文档。")],
            }),
          ],
        },
      ],
    });

    // 2. 生成Blob
    const blob = await Packer.toBlob(doc);

    // 3. 保存文件
    saveAs(blob, "示例文档.docx");
  } catch (error) {
    console.error("导出失败:", error);
  } finally {
    loading.value = false;
  }
}
</script>

docx库详解

什么是docx?

docx 是一个纯 JavaScript 库,用于 创建和操作 Microsoft Word (.docx) 文件。它可以在 Node.js 和浏览器环境中运行,不需要安装 Office 软件。

主要特性

  • 完整的 Word 文档功能支持
  • 纯 JavaScript 实现,无外部依赖
  • 支持 Node.js 和浏览器
  • 丰富的样式和格式选项
  • 表格、图像、列表等元素支持
  • TypeScript 友好,有完整的类型提示
markdown 复制代码
## 浏览器环境注意事项

1. 文件大小限制:浏览器中生成大文档可能导致内存问题
2. 中文支持:必须显式设置中文字体
3. 图片处理:浏览器中需要使用不同的图片加载方式

核心概念架构

文档结构模型

text 复制代码
文档 (Document)
    ↓
分段 (Section) [可以有多个]
    ↓
各种元素 (Paragraph, Table, Image...)

基本工作流程

text 复制代码
1. 创建文档对象 (Document)
   ↓
2. 添加分段 (Sections)
   ↓
3. 向分段添加内容元素
   (Paragraphs, Tables, Images...)
   ↓
4. 配置元素的样式和属性
   ↓
5. 使用 Packer 转换为文件
   ↓
6. 保存或下载文件
js 复制代码
// 导入必要的模块
import { 
  Document,     // 文档
  Paragraph,    // 段落
  TextRun,      // 文本块
  Packer        // 打包器(用于生成文件)
} from "docx";

async function exportDocument() {
  try {
    // 1. 创建文档对象
    const doc = new Document({
      // 2. 文档内容分段(可以包含多个分段)
      sections: [
        {
          properties: {}, // 分段属性(页面大小、边距等)
          children: [
            // 3. 分段中的内容元素
            new Paragraph({
              // 4. 段落
              children: [
                // 5. 段落中的文本块
                new TextRun("Hello, World!"),
              ],
            }),
          ],
        },
      ],
    });
    // 6. 将文档对象转换为文件
    const buffer = await Packer.toBuffer(doc); // Node.js 环境
    const blob = await Packer.toBlob(doc); // 浏览器环境
  } catch (error) {
    console.error("导出失败:", error);  // 错误提示
  }
}

Packer工具对比

docx 库中,Packer 类是用于将文档对象序列化为不同格式的工具。toBuffer()toBlob() 是最常用的两个方法,它们用于不同的运行环境。

特性 Packer.toBuffer() Packer.toBlob()
运行环境 Node.js 浏览器
返回值 Buffer Blob
存储方式 二进制缓冲区 二进制大对象
文件操作 使用 fs 模块 使用 FileSaver 等库
内存使用 直接内存操作 Blob URL 引用
典型用途 服务器端保存文件 客户端下载文件

基础组件

1. Document(文档)

文档是最顶层的容器,相当于一个Word文件。

js 复制代码
const doc = new Document({
  creator: "作者名称",      // 创建者
  title: "文档标题",        // 文档标题
  description: "文档描述",  // 描述
  sections: []            // 包含的分段
});

2. Section(分段)

文档由多个分段组成,每个分段可以有不同的页面设置。

js 复制代码
const section = {
  properties: {
    page: {
      size: {
        width: 12240,  // A4 宽度 (单位:twips)
        height: 15840, // A4 高度
      },
      margin: {
        top: 1440,     // 上边距 2.54cm
        right: 1440,   // 右边距
        bottom: 1440,  // 下边距
        left: 1440,    // 左边距
      }
    }
  },
  children: []  // 这个分段中的内容
};

单位解释docx使用twips作为Word文档的标准单位系统。

单位 说明 换算关系
twips Word标准单位 1 twip = 1/20 pt
pt 1 pt = 20 twips
inch 英寸 1 inch = 1440 twips
cm 厘米 1 cm ≈ 567 twips

常用预设值

  • 单倍行距:240 twips
  • 1.5倍行距:360 twips
  • 2倍行距:480 twips
  • A4宽度:12240 twips (21cm)
  • A4高度:15840 twips (29.7cm)

3. Paragraph(段落)

段落是文档的基本内容单元,相当于 Word 中的一个段落。

js 复制代码
const paragraph = new Paragraph({
  children: [ /* 文本块或其他内联元素 */ ],
  alignment: "left",  // 对齐方式:left, center, right, both
  spacing: {         // 间距
    before: 200,     // 段前间距
    after: 200,      // 段后间距
    line: 240        // 行间距(240 = 单倍行距)
  },
  indent: {          // 缩进
    firstLine: 720   // 首行缩进 720twips = 0.5英寸
  }
});
1). 标题 (Headings)
js 复制代码
import { HeadingLevel } from "docx";

const headings = [
  new Paragraph({
    text: "一级标题",
    heading: HeadingLevel.HEADING_1,
  }),
  new Paragraph({
    text: "二级标题",
    heading: HeadingLevel.HEADING_2,
  }),
  new Paragraph({
    text: "三级标题",
    heading: HeadingLevel.HEADING_3,
  })
];
2). 列表 (Lists)

列表类型概览

列表类型 描述 配置方式 示例
有序列表 带数字/字母编号 numbering属性 1., 2., 3.
无序列表 带项目符号 bullet属性 • 项目1
多级列表 嵌套的列表 组合使用 1.1., 1.2.
->无序列表:
js 复制代码
// 无序列表(项目符号)
const bulletList = [
  new Paragraph({
    text: "项目1",
    bullet: { level: 0 },  // level 表示缩进级别
  }),
  new Paragraph({
    text: "项目2",
    bullet: { level: 0 },
  }),
];
->有序列表:

有序列表(编号列表)需要两步配置:

  1. 定义编号格式 (在 Documentnumbering.config 中)
  2. 应用编号引用 (在 Paragraphnumbering 属性中)
js 复制代码
const doc = new Document({
  numbering: {
    config: [
      // 配置1:数字编号 (1., 2., 3.)
      {
        reference: "decimal-list",	// 唯一引用标识
        levels: [{
          level: 0,				  // 列表级别
          format: "decimal",      // 格式类型
          text: "%1.",            // 显示模板
          alignment: "left",      // 对齐方式
          style: {
            paragraph: {
              indent: { 
                left: 720,       // 左缩进 (0.5英寸)
                hanging: 360     // 悬挂缩进 (0.25英寸)
              }
            }
          }
        }]
      },
      // 配置2:大写字母编号 (A., B., C.)
      {
        reference: "upper-letter-list",
        levels: [{
          level: 0,
          format: "upperLetter",
          text: "%1.",
          alignment: "left"
        }]
      },
      // 配置3:罗马数字编号 (I., II., III.)
      {
        reference: "roman-list",
        levels: [{
          level: 0,
          format: "upperRoman",
          text: "%1.",
          alignment: "left"
        }]
      }
    ]
  },
  sections: [{
    children: [
      // 使用不同格式的有序列表
      new Paragraph({
        text: "数字列表项",
        numbering: { 
          reference: "decimal-list",   // 编号引用名称
          level: 0 				  	   // 缩进级别
        }
      }),
      new Paragraph({
        text: "字母列表项",
        numbering: { reference: "upper-letter-list", level: 0 }
      }),
      new Paragraph({
        text: "罗马数字列表项",
        numbering: { reference: "roman-list", level: 0 }
      })
    ]
  }]
});

instance 属性:

  • 在有序列表中可以使用instance创建独立的编号序列

    text 复制代码
    一个 reference + level 定义一个编号格式
    ┌─────────────────────────────────────┐
    │ reference: "demo-list", level: 0      │
    │ format: "decimal", text: "%1."      │
    └─────────────────────────────────────┘
        │
        ├─ instance: 1 (实例1)
        │     ├─ 段落1 → 显示 "1."
        │     ├─ 段落2 → 显示 "2."
        │     └─ 段落3 → 显示 "3."
        │
        ├─ instance: 2 (实例2)  
        │     ├─ 段落1 → 显示 "1." ← 重新开始!
        │     ├─ 段落2 → 显示 "2."
        │     └─ 段落3 → 显示 "3."
        │
        └─ instance: 3 (实例3)
              ├─ 段落1 → 显示 "1." ← 又重新开始!
              └─ 段落2 → 显示 "2."
  • 示例:

    js 复制代码
    const doc = new Document({
      // 定义编号格式
      numbering: {
        config: [
          {
            reference: "demo-list",
            levels: [{ level: 0, format: "decimal", text: "%1." }],
          },
        ],
      },
      sections: [
        {
          properties: {},
          children: [
            // 实例1
            new Paragraph({
              text: "实例1-第一项",
              numbering: { reference: "demo-list", level: 0, instance: 1 },
            }),
    
            new Paragraph({
              text: "实例1-第二项",
              numbering: { reference: "demo-list", level: 0, instance: 1 },
            }),
            // 实例2(重新开始编号)
            new Paragraph({
              text: "实例2-第一项",
              numbering: { reference: "demo-list", level: 0, instance: 2 },
            }),
    
            new Paragraph({
              text: "实例2-第二项",
              numbering: { reference: "demo-list", level: 0, instance: 2 }, // 继续实例2
            }),
          ],
        },
      ],
    });
    // 结果:1., 2., 1., 2.
  • 重要提示

    • 每个instance值创建一个独立的编号序列;
    • 相同的instance值共享编号计数;
    • 不同的instance值各自从1开始编号;
    • 在docx 9.x版本中,不指定instance时行为不确定,可能不会自动继续使用前一个实例,因此建议明确指定;`

4. TextRun(文本块)

文本块是段落中的具体文本内容,可以设置样式。

js 复制代码
const textRun = new TextRun({
  text: "这是文本内容",
  bold: true,        // 加粗
  italics: false,    // 斜体
  underline: {},     // 下划线(空对象表示单下划线)
  color: "FF0000",   // 颜色(十六进制,不带#)
  font: "宋体",      // 字体
  size: 24,          // 字体大小(24 = 12pt)
  highlight: "yellow" // 高亮
});

5. Tables (表格)

js 复制代码
import { Table, TableRow, TableCell } from "docx";

const table = new Table({
  rows: [
    // 表头行
    new TableRow({
      children: [
        new TableCell({
          children: [new Paragraph("姓名")],
          width: { size: 30, type: "pct" },  // 宽度占30%
        }),
        new TableCell({
          children: [new Paragraph("年龄")],
          width: { size: 20, type: "pct" },
        }),
      ],
    }),
    // 数据行
    new TableRow({
      children: [
        new TableCell({
          children: [new Paragraph("张三")],
        }),
        new TableCell({
          children: [new Paragraph("25")],
        }),
      ],
    }),
  ],
  width: { size: 100, type: "pct" },  // 表格宽度占100%
});

6. Images(图像)

js 复制代码
// Node.js环境
import fs from "fs";

const image = new ImageRun({
  data: fs.readFileSync("path/to/image.png"),
  transformation: {
    width: 200,
    height: 200,
  },
});

// 浏览器环境
const image = new ImageRun({
  data: await fetch('image.png').then(res => res.arrayBuffer()),
  transformation: {
    width: 200,
    height: 200,
  },
});

样式设置

stylesparagraphStylesdocx 库中管理文档样式的两个核心配置项,它们有不同的用途和层级关系。

方式 配置位置 作用范围 优先级 适用场景
全局默认样式 Document.styles.default 整个文档 设置文档基础样式
自定义段落样式 Document.styles.paragraphStyles 特定段落 创建可重用样式
字符样式 Document.styles.characterStyles 文本片段 文本级样式复用
表格样式 Document.styles.tableStyles 表格 表格统一样式
内联样式 Paragraph, TextRun 参数 单个元素 特定元素样式
样式继承链 basedOn, next 样式间 - 样式复用和关联

样式层级结构:

复制代码
Document (文档)
├── styles (文档样式)
│   ├── default (默认样式)
│   ├── characterStyles (字符样式)
│   ├── paragraphStyles (段落样式)
│   └── tableStyles (表格样式)
└── sections (内容分段)
    └── Paragraph (段落)
        ├── style (引用paragraphStyles中的样式)
        └── children (内容)

常用样式属性

属性 说明 示例值
font 字体 "宋体", "微软雅黑"
size 字号 24 (12pt), 28 (14pt)
bold 加粗 true / false
italics 斜体 true / false
color 颜色 "FF0000" (红色)
underline 下划线 {} (单线)
highlight 高亮 "yellow", "green"

1. 全局默认样式 (styles.default)

这是最基础的样式配置方式,为整个文档设置默认样式。

配置结构:

js 复制代码
const doc = new Document({
  styles: {
    default: {
      // 文档默认字符样式
      document: {
        run: {
          font: "宋体",      // 字体
          size: 24,         // 字号
          color: "000000",  // 颜色
        },
        paragraph: {
          spacing: { line: 360 },  // 行距
        }
      }, 
      // 标题样式
      heading1: {
        run: {
          font: "宋体",
          size: 32,
          bold: true,
        },
        paragraph: {
          spacing: { before: 240, after: 120 },
        }
      },
      heading2: {
        run: {
          font: "宋体",
          size: 28,
          bold: true,
        }
      },
      heading3: {
        run: {
          font: "宋体",
          size: 26,
          bold: true,
        }
      },
      // 标题样式
      title: {
        run: {
          font: "宋体",
          size: 36,
          bold: true,
        }
      },
      // 列表样式
      listParagraph: {
        run: {
          font: "宋体",
          size: 24,
        }
      }
    }
  },
  sections: [{
    children: [
      // 自动应用 heading1 样式
      new Paragraph({
        text: "文档标题",
        heading: "Heading1"
      }),
      
      // 自动应用全局默认样式
      new Paragraph({
        children: [
          new TextRun("正文内容")
        ]
      })
    ]
  }]
});

示例:

js 复制代码
import { Document, Paragraph, TextRun, Packer } from "docx";

const doc = new Document({
  styles: {
    default: {
      // 设置文档全局样式
      document: {
        run: {
          font: "宋体",
          size: 24,        // 小四
          color: "333333",
        },
        paragraph: {
          spacing: { 
            line: 360,     // 1.5倍行距
            after: 100     // 段落间距
          },
        }
      },
      // 标题样式
      heading1: {
        run: {
          font: "黑体",
          size: 32,
          color: "000080", // 深蓝色
          bold: true,
        },
        paragraph: {
          spacing: { before: 240, after: 120 },
          alignment: "left",
        }
      },
      heading2: {
        run: {
          font: "黑体",
          size: 28,
          color: "000080",
          bold: true,
        }
      }
    }
  },
  
  sections: [{
    children: [
      // 自动应用 heading1 样式
      new Paragraph({
        text: "文档标题",
        heading: "Heading1"
      }),
      
      // 自动应用全局默认样式
      new Paragraph({
        children: [
          new TextRun("正文内容")
        ]
      })
    ]
  }]
});

2. 自定义段落样式 (paragraphStyles)

这是最常用和最灵活的方式,用于定义可重用的段落样式。

配置结构:

js 复制代码
const doc = new Document({
  styles: {
    default:{},
    paragraphStyles: [
      {
        id: "样式ID", // 样式ID,必填,用于引用
        name: "样式名称", // 样式名称,在Word中显示的名称
        basedOn: "Normal", // 基于哪个样式(可选)
        next: "Normal", // 按Enter后的样式(可选)
        quickFormat: true, // 是否在快速样式库显示
        // 字符样式
        run: {
          font: "宋体",
          size: 24,
          color: "000000",
          bold: false,
          italics: false,
          underline: {},
        },
        // 段落样式
        paragraph: {
          spacing: {
            line: 360, // 行间距
            before: 0, // 段前间距
            after: 100, // 段后间距
          },
          indent: {
            firstLine: 720, // 首行缩进
            left: 0, // 左缩进
            right: 0, // 右缩进
          },
          alignment: "left", // 对齐:left, center, right, both
          border: {
            // 边框
            top: { style: "single", size: 4, color: "000000" },
          },
          shading: {
            // 底纹
            fill: "F5F5F5",
          },
          pageBreakBefore: false, // 是否段前分页
          keepLines: true, // 保持在同一页
          keepNext: true, // 与下段同页
        },
      },
    ],
  },
});

示例:

js 复制代码
import { Document, Paragraph } from "docx";

const doc = new Document({
  styles: {
    paragraphStyles: [
      {
        id: "ReportTitle",
        name: "报告标题",
        run: {
          font: "黑体",
          size: 36,
          color: "000080",
          bold: true,
        },
        paragraph: {
          alignment: "center",
          spacing: { before: 400, after: 300 },
        },
      },
      {
        id: "Important",
        name: "重点强调",
        run: {
          color: "FF0000",
          bold: true,
          highlight: "yellow",
        },
        paragraph: {
          shading: { fill: "FFF8DC" },
        },
      },
    ],
  },
  sections: [
    {
      children: [
        new Paragraph({
          text: "年度技术报告",
          style: "ReportTitle",
        }),
        new Paragraph({
          text: "⚠️ 注意:此文档为机密文件",
          style: "Important",
        }),
      ],
    },
  ],
});

3. 字符样式 (characterStyles)

用于定义可重用的文本片段样式,适用于多个段落中的相同文本样式。

js 复制代码
const doc = new Document({
  styles: {
    default:{},
    paragraphStyles:[],
    characterStyles: [
      {
        id: "RedBold",
        name: "红字加粗",
        run: {
          color: "FF0000",
          bold: true,
        }
      },
      {
        id: "Highlight",
        name: "高亮",
        run: {
          highlight: "yellow",
          bold: true,
        }
      },
      {
        id: "LinkStyle",
        name: "链接样式",
        run: {
          color: "0000FF",
          underline: {},
        }
      }
    ]
  },
  sections: [{
    children: [
      new Paragraph({
        children: [
          new TextRun({
            text: "重要通知:",
            style: "RedBold"  // 应用字符样式
          }),
          new TextRun("请及时查看最新消息")
        ]
      })
    ]
  }]
});

4. 内联样式(直接设置)

最高优先级的方式,直接在创建元素时设置样式。

js 复制代码
import { Paragraph, TextRun } from "docx";

// 1. 在 Paragraph 中直接设置
const paragraph1 = new Paragraph({
  text: "这个段落有直接样式",
  alignment: "center",           // 对齐方式
  spacing: { 
    line: 400,                   // 行间距
    before: 200,                 // 段前
    after: 200                   // 段后
  },
  indent: {
    firstLine: 720,              // 首行缩进
  },
  border: {
    bottom: {                    // 下边框
      style: "single",
      size: 4,
      color: "000000"
    }
  },
  shading: {                     // 背景色
    fill: "F0F0F0"
  }
});

// 2. 在 TextRun 中直接设置
const paragraph2 = new Paragraph({
  children: [
    new TextRun({
      text: "这段文本",
      font: "黑体",               // 字体
      size: 28,                  // 字号
      bold: true,                // 加粗
      italics: false,            // 斜体
      underline: {},             // 下划线
      color: "FF0000",           // 颜色
      highlight: "yellow",       // 高亮
      shading: {                 // 文字底纹
        fill: "FFCCCC"
      },
      strike: true,              // 删除线
      doubleStrike: false,       // 双删除线
      allCaps: false,            // 全部大写
      smallCaps: false           // 小型大写
    })
  ]
});

5. 样式继承和关联

1. 基于现有样式 (basedOn)

优点:保持样式一致性和便于批量修改。

js 复制代码
const doc = new Document({
  paragraphStyles: [
    // 基础样式
    {
      id: "BaseStyle",
      name: "基础样式",
      run: {
        font: "宋体",
        size: 24,
        color: "333333",
      },
      paragraph: {
        spacing: { line: 360, after: 100 },
      }
    },
    // 继承基础样式,只修改需要的属性
    {
      id: "WarningStyle",
      name: "警告样式",
      basedOn: "BaseStyle",  // 继承 BaseStyle
      run: {
        color: "FF0000",     // 只修改颜色
        bold: true,          // 添加加粗
      }
      // 其他属性自动从 BaseStyle 继承
    },
    // 多层继承
    {
      id: "CriticalWarning",
      name: "严重警告",
      basedOn: "WarningStyle",  // 继承 WarningStyle
      run: {
        highlight: "yellow",    // 添加高亮
        size: 28,               // 修改字号
      },
      paragraph: {
        shading: {              // 添加背景色
          fill: "FFEEEE"
        }
      }
    }
  ]
});
2. 样式链 (next)
js 复制代码
const doc = new Document({
  paragraphStyles: [
    {
      id: "TitleStyle",
      name: "标题样式",
      basedOn: "Title",
      next: "ChapterIntro",  // 按Enter后自动使用 ChapterIntro
      // ...
    },
    {
      id: "ChapterIntro",
      name: "章节引言",
      basedOn: "Normal",
      next: "Normal",        // 再按Enter回到正文
      // ...
    }
  ],
  sections: [{
    children: [
      new Paragraph({
        text: "文档标题",
        style: "TitleStyle"
      }),
      // 按Enter后,下一段落会自动使用 ChapterIntro 样式
    ]
  }]
});

6. 混合使用所有方式

实际项目中,通常需要混合使用多种方式:

js 复制代码
const doc = new Document({
  // 1. 全局默认样式
  styles: {
    default: {
      document: {
        run: {
          font: "宋体",
          size: 24,
          color: "333333",
        },
        paragraph: {
          spacing: { line: 360 },
        }
      }
    },
    // 字符样式
    characterStyles: [
      {
        id: "Keyword",
        name: "关键词",
        run: {
          bold: true,
          color: "0000FF",
        }
      }
    ]
  },
  // 2. 自定义段落样式
  paragraphStyles: [
    {
      id: "CompanyReport",
      name: "公司报告样式",
      basedOn: "Normal",
      run: {
        font: "微软雅黑",
        size: 21,
        color: "000000",
      },
      paragraph: {
        spacing: { line: 315, after: 80 },
        indent: { firstLine: 420 },
      }
    }
  ],
  sections: [{
    children: [
      // 3. 应用自定义段落样式
      new Paragraph({
        text: "2024年度报告",
        style: "CompanyReport"
      }),
      // 4. 内联样式 + 字符样式
      new Paragraph({
        children: [
          new TextRun({
            text: "关键词:",
            style: "Keyword"  // 字符样式
          }),
          new TextRun({
            text: "数字化转型",
            font: "黑体",     // 内联样式
            size: 26,
            bold: true,
            color: "FF0000"
          })
        ],
        spacing: { line: 400 }  // 段落内联样式
      })
    ]
  }]
});

样式优先级示例:

js 复制代码
const doc = new Document({
  // 1. 全局默认样式(最低优先级)
  styles: {
    default: {
      document: {
        run: {
          font: "宋体",      // 会被覆盖
          size: 24,          // 会被覆盖
          color: "000000",   // 会被覆盖
        }
      }
    }
  },
  // 2. 段落样式(中等优先级)
  paragraphStyles: [
    {
      id: "MyParaStyle",
      name: "段落样式",
      run: {
        font: "黑体",        // 会被TextRun覆盖
        size: 28,            // 会被TextRun覆盖
        color: "0000FF",     // 会被TextRun覆盖
      }
    }
  ],
  sections: [{
    children: [
      // 3. 段落直接样式 + 引用段落样式
      new Paragraph({
        text: "示例文本",
        style: "MyParaStyle",  // 应用段落样式
        alignment: "center",   // 段落直接样式
        spacing: { line: 400 }, // 段落直接样式
        
        // 4. TextRun 内联样式(最高优先级)
        children: [
          new TextRun({
            text: "部分文字",
            font: "楷体",      // 覆盖段落样式
            size: 32,          // 覆盖段落样式
            color: "FF0000",   // 覆盖段落样式
            bold: true,        // 新增属性
          }),
          new TextRun("其他文字")  // 使用段落样式
        ]
      })
    ]
  }]
});
// 最终效果:
// - "部分文字": 楷体、32号、红色、加粗
// - "其他文字": 黑体、28号、蓝色
// - 整个段落: 居中、1.67倍行距

示例:

创建一份月度工作报告:

js 复制代码
import {
  Document,
  Packer,
  Paragraph,
  TextRun,
  HeadingLevel,
  Table,
  TableRow,
  TableCell,
  AlignmentType
} from "docx";

async function createMonthlyReport(data) {
  // 1. 创建文档
  const doc = new Document({
    creator: data.creator,
    title: `${data.month}月度工作报告`,
    description: `${data.year}年${data.month}月工作报告`,
    
    // 样式配置
    styles: {
      default: {
        document: {
          run: {
            font: "微软雅黑",
            size: 24,
            color: "333333",
          }
        }
      }
    },
      
    sections: [{
      properties: {
        page: {
          size: { width: 12240, height: 15840 },  // A4
          margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 }
        }
      },
      children: [
        // 标题
        new Paragraph({
          text: `${data.month}月度工作报告`,
          heading: HeadingLevel.TITLE,
          alignment: AlignmentType.CENTER,
          spacing: { after: 400 }
        }),
        
        // 基本信息
        new Paragraph({
          children: [
            new TextRun({ text: "报告人:", bold: true }),
            new TextRun(data.creator)
          ],
          spacing: { after: 100 }
        }),
        
        // 工作完成情况
        new Paragraph({
          text: "一、工作完成情况",
          heading: HeadingLevel.HEADING_1,
          spacing: { before: 200, after: 100 }
        }),
        
        // 任务列表
        ...data.tasks.map(task => 
          new Paragraph({
            text: `✅ ${task.description}`,
            bullet: { level: 0 },
            spacing: { after: 40 }
          })
        ),
        
        // 数据表格
        new Table({
          rows: [
            // 表头
            new TableRow({
              children: [
                new TableCell({ 
                  children: [new Paragraph("项目")],
                  shading: { fill: "F0F0F0" }
                }),
                new TableCell({ 
                  children: [new Paragraph("进度")],
                  shading: { fill: "F0F0F0" }
                }),
                new TableCell({ 
                  children: [new Paragraph("负责人")],
                  shading: { fill: "F0F0F0" }
                })
              ]
            }),
            // 数据行
            ...data.projects.map(project => 
              new TableRow({
                children: [
                  new TableCell({ children: [new Paragraph(project.name)] }),
                  new TableCell({ 
                    children: [new Paragraph({
                      children: [
                        new TextRun({
                          text: `${project.progress}%`,
                          color: project.progress >= 80 ? "008000" : 
                                 project.progress >= 50 ? "FFA500" : "FF0000"
                        })
                      ]
                    })]
                  }),
                  new TableCell({ children: [new Paragraph(project.owner)] })
                ]
              })
            )
          ],
          width: { size: 100, type: "pct" }
        })
      ]
    }]
  });
  
  // 生成文件
  const buffer = await Packer.toBuffer(doc);
  return buffer;
}

// 使用示例
const reportData = {
  creator: "张三",
  month: "1月",
  year: "2024",
  tasks: [
    { description: "完成项目需求分析" },
    { description: "完成核心功能开发" },
    { description: "完成单元测试" }
  ],
  projects: [
    { name: "项目A", progress: 80, owner: "张三" },
    { name: "项目B", progress: 60, owner: "李四" },
    { name: "项目C", progress: 95, owner: "王五" }
  ]
};

const docBuffer = await createMonthlyReport(reportData);

优化建议:

1. 样式配置工具

使用工具函数统一管理样式:

js 复制代码
// styles/config.js
export const STYLE_CONFIG = {
  // 颜色配置
  colors: {
    primary: "#000080",    // 主色
    secondary: "#800000",  // 辅色
    success: "#008000",    // 成功
    warning: "#FFA500",    // 警告
    danger: "#FF0000",     // 危险
    info: "#008080",       // 信息
    light: "#F5F5F5",      // 浅色
    dark: "#333333",       // 深色
  },
  
  // 字体配置
  fonts: {
    chinese: "宋体",
    heading: "黑体",
    code: "Consolas",
    english: "Arial",
  },
  
  // 字号配置(单位:磅的2倍)
  sizes: {
    title: 36,     // 18pt
    h1: 32,        // 16pt
    h2: 28,        // 14pt
    h3: 26,        // 13pt
    normal: 24,    // 12pt (小四)
    small: 21,     // 10.5pt (五号)
    tiny: 18,      // 9pt (小五)
  },
  
  // 间距配置(单位:twips)
  spacing: {
    line: {
      single: 240,     // 单倍
      oneHalf: 360,    // 1.5倍
      double: 480,     // 2倍
    },
    paragraph: {
      before: 200,
      after: 100,
    }
  }
};

// 样式工厂函数
export function createStyle(id, name, overrides = {}) {
  const baseStyle = {
    id,
    name,
    basedOn: "Normal",
    next: "Normal",
    quickFormat: true,
    run: {
      font: STYLE_CONFIG.fonts.chinese,
      size: STYLE_CONFIG.sizes.normal,
      color: STYLE_CONFIG.colors.dark.replace("#", ""),
    },
    paragraph: {
      spacing: {
        line: STYLE_CONFIG.spacing.line.oneHalf,
        before: 0,
        after: STYLE_CONFIG.spacing.paragraph.after,
      }
    },
    ...overrides
  };
  
  return baseStyle;
}

// 预设样式
export const PRESET_STYLES = {
  // 标题样式
  title: (options = {}) => createStyle("TitleStyle", "标题", {
    basedOn: "Title",
    run: {
      font: STYLE_CONFIG.fonts.heading,
      size: STYLE_CONFIG.sizes.title,
      color: STYLE_CONFIG.colors.primary.replace("#", ""),
      bold: true,
    },
    paragraph: {
      alignment: "center",
      spacing: { before: 400, after: 300 },
    },
    ...options
  }),
  
  // 警告样式
  warning: (options = {}) => createStyle("WarningStyle", "警告", {
    run: {
      color: STYLE_CONFIG.colors.danger.replace("#", ""),
      bold: true,
      highlight: "yellow",
    },
    paragraph: {
      shading: { fill: "FFF8DC" },
      indent: { left: 360, right: 360 },
    },
    ...options
  }),
  
  // 代码样式
  code: (options = {}) => createStyle("CodeStyle", "代码", {
    run: {
      font: STYLE_CONFIG.fonts.code,
      size: STYLE_CONFIG.sizes.small,
      color: STYLE_CONFIG.colors.success.replace("#", ""),
    },
    paragraph: {
      shading: { fill: "F5F5F5" },
      indent: { left: 720 },
      spacing: { line: 280 },
    },
    ...options
  })
};

使用工具函数:

js 复制代码
import { Document } from "docx";
import { PRESET_STYLES, createStyle } from "./styles/config";

const doc = new Document({
  paragraphStyles: [
    // 使用预设样式
    PRESET_STYLES.title(),
    // 使用工厂函数创建自定义样式
    createStyle("CustomStyle", "自定义样式", {
      run: {
        font: "楷体",
        size: 26,
        italics: true,
      },
      paragraph: {
        alignment: "right",
        shading: { fill: "F0F8FF" }
      }
    }),
    // 修改预设样式
    PRESET_STYLES.warning({
      id: "CriticalWarning",
      name: "严重警告",
      run: {
        size: 28,
        underline: {},
      }
    })
  ],
  // 全局默认样式
  styles: {
    default: {
      document: {
        run: {
          font: "宋体",
          size: 24,
        },
        paragraph: {
          spacing: { line: 360 },
        }
      }
    }
  }
});

2.文档生成策略(分块处理)

分块处理的目的 :避免在浏览器中生成大型文档时出现内存溢出、界面卡死、用户无响应等问题。

场景 需要程度 说明
小文档(< 100行) 完全不需要
中等文档(100-500行) ⭐⭐ 可考虑,但不是必须
大型报表(500-2000行) ⭐⭐⭐ 建议实现
大数据量(> 2000行) ⭐⭐⭐⭐⭐ 必须实现
js 复制代码
// 渐进式生成大型文档
async function generateLargeDocument(data, chunkSize = 50) {
  const sections = [];
  
  // 分段处理避免内存溢出
  for (let i = 0; i < data.length; i += chunkSize) {
    const chunk = data.slice(i, i + chunkSize);
    const section = await createSection(chunk);
    sections.push(section);
  }
  
  return new Document({ sections });
}

3. 浏览器环境优化

js 复制代码
// 添加进度反馈的大型文档生成
async function exportDocumentWithProgress(data, onProgress) {
  const totalSteps = data.length;
  let completed = 0;
  
  const sections = [];
  for (const item of data) {
    const section = await createSection(item);
    sections.push(section);
    
    completed++;
    if (onProgress) {
      onProgress(Math.round((completed / totalSteps) * 100));
    }
  }
  
  const doc = new Document({ sections });
  const blob = await Packer.toBlob(doc);
  saveAs(blob, "document.docx");
}

4. 错误处理与用户体验

js 复制代码
// 完整的错误处理示例
async function safeExportDocument(data) {
  try {
    // 验证数据
    if (!data || data.length === 0) {
      throw new Error("没有数据可导出");
    }
    
    // 检查数据大小(浏览器内存限制)
    if (data.length > 1000) {
      console.warn("数据量较大,建议分批次导出");
      // 可以提示用户或自动分批
    }
    
    // 生成文档
    const doc = await createDocument(data);
    const blob = await Packer.toBlob(doc);
    
    // 检查Blob大小
    if (blob.size > 10 * 1024 * 1024) { // 10MB
      console.warn("文档较大,下载可能需要时间");
    }
    
    // 保存文件
    saveAs(blob, `导出文档_${new Date().toISOString().slice(0, 10)}.docx`);
    
    return { success: true, size: blob.size };
    
  } catch (error) {
    console.error("文档导出失败:", error);
    
    // 用户友好的错误提示
    const errorMessage = {
      "没有数据可导出": "请先添加数据再导出",
      "NetworkError": "网络连接失败,请检查网络",
      "QuotaExceededError": "文档太大,请减少数据量"
    }[error.name] || "文档导出失败,请重试";
    
    alert(errorMessage);
    return { success: false, error: error.message };
  }
}

常见问题及解决方案

问题1:样式不生效

检查清单

  1. 样式ID是否正确引用
  2. 样式优先级是否正确(内联样式 > 段落样式 > 全局样式)
  3. 样式配置语法是否正确
  4. 是否拼写错误

问题2:中英文混合字体

js 复制代码
// 正确的字体设置
{
  id: "MixedFont",
  name: "混合字体",
  run: {
    // 分别设置不同字符集的字体
    font: {
      ascii: "Times New Roman",  // 英文字体
      eastAsia: "宋体",          // 中文字体
      cs: "宋体",                // 复杂字符
      hint: "eastAsia",          // 提示使用东亚字体
    }
  }
}

问题3:编号不正确

现象:有序列表编号混乱

解决方案

  1. 明确指定instance:不要依赖默认行为
  2. 避免中断实例:相同instance的段落放在一起
  3. 使用不同reference:完全独立的列表使用不同reference

问题4:浏览器内存不足

现象:大文档生成失败

优化策略

  1. 分批次生成:将大数据分块处理
  2. 压缩图片:减少图片大小
  3. 简化样式:避免过度复杂的样式嵌套
  4. 提供进度反馈:让用户了解生成进度

项目结构建议

text 复制代码
project/
├── src/
│   ├── components/
│   │   └── DocExporter.vue          # 导出组件
│   ├── utils/
│   │   ├── doc-generator/
│   │   │   ├── config/              # 样式配置
│   │   │   │   ├── styles.js        # 样式定义
│   │   │   │   └── templates.js     # 文档模板
│   │   │   ├── builders/            # 组件构建器
│   │   │   │   ├── table-builder.js
│   │   │   │   ├── list-builder.js
│   │   │   │   └── style-builder.js
│   │   │   └── index.js             # 主入口
│   │   └── file-utils.js            # 文件工具
│   └── views/
│       └── ReportView.vue           # 报表页面
└── package.json

总结:

  1. 分层配置
    • 使用 styles.default 设置全局基础样式
    • 使用 paragraphStyles 定义可重用段落样式
    • 使用内联样式处理特殊情况
  2. 样式规划
    • 先规划样式体系,再开始编码
    • 创建样式变量,方便维护
    • 使用样式继承减少重复
  3. 性能优化
    • 避免过多内联样式
    • 复用样式定义
    • 使用样式工厂函数
  4. 兼容性考虑
    • 指定完整字体链
    • 测试不同环境下的显示效果
    • 提供样式回退机制

现在你可以在Vue.js项目中轻松实现强大的Word文档导出功能了!

扩展资源

相关推荐
AC赳赳老秦3 小时前
Prometheus + DeepSeek:自动生成巡检脚本与告警规则配置实战
前端·javascript·爬虫·搜索引擎·prometheus·easyui·deepseek
接着奏乐接着舞。3 小时前
前端大数据渲染性能优化:Web Worker + 分片处理 + 渐进式渲染
大数据·前端·性能优化
Beginner x_u3 小时前
CSS 中的高度、滚动与溢出:从 height 到 overflow 的完整理解
前端·css·overflow·min-height
vx1_Biye_Design3 小时前
基于web的物流管理系统的设计与实现-计算机毕业设计源码44333
java·前端·spring boot·spring·eclipse·tomcat·maven
tqs_123453 小时前
倒排索引数据结构
java·前端·算法
a程序小傲3 小时前
听说前端又死了?
开发语言·前端·mysql·算法·postgresql·深度优先
Yan.love3 小时前
【CSS-布局】终极方案:Flexbox 与 Grid 的“降维打击”
前端·css
请叫我聪明鸭4 小时前
基于 marked.js 的扩展机制,创建一个自定义的块级容器扩展,让内容渲染为<div>标签而非默认的<p>标签
开发语言·前端·javascript·vue.js·ecmascript·marked·marked.js插件
悟能不能悟4 小时前
Gson bean getxxx,怎么才能返回给前端
java·前端