Vue使用docx和file-saver实现Word文档导出功能

 更新时间:2026年01月21日 09:21:04   作者:51349592  
在实际的前端项目中,经常需要将数据或页面内容导出为Word文档,利用docx和file-saver库,可以实现强大的文档导出功能,所以本文介绍了如何使用docx和file-saver库在前端项目中导出Word文档,需要的朋友可以参考下

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

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

安装

# 安装最新版本
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,兼容性好推荐使用

基础示例

<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 友好,有完整的类型提示
## 浏览器环境注意事项

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

核心概念架构

文档结构模型

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

基本工作流程

1. 创建文档对象 (Document)
   ↓
2. 添加分段 (Sections)
   ↓
3. 向分段添加内容元素
   (Paragraphs, Tables, Images...)
   ↓
4. 配置元素的样式和属性
   ↓
5. 使用 Packer 转换为文件
   ↓
6. 保存或下载文件
// 导入必要的模块
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浏览器
返回值BufferBlob
存储方式二进制缓冲区二进制大对象
文件操作使用 fs 模块使用 FileSaver 等库
内存使用直接内存操作Blob URL 引用
典型用途服务器端保存文件客户端下载文件

基础组件

1. Document(文档)

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

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

2. Section(分段)

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

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文档的标准单位系统。

单位说明换算关系
twipsWord标准单位1 twip = 1/20 pt
pt1 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 中的一个段落。

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)

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.

->无序列表:

// 无序列表(项目符号)
const bulletList = [
  new Paragraph({
    text: "项目1",
    bullet: { level: 0 },  // level 表示缩进级别
  }),
  new Paragraph({
    text: "项目2",
    bullet: { level: 0 },
  }),
];

->有序列表:

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

  1. 定义编号格式(在 Documentnumbering.config 中)
  2. 应用编号引用(在 Paragraphnumbering 属性中)
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创建独立的编号序列

一个 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."

示例:

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(文本块)

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

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

5. Tables (表格)

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(图像)

// 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)

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

配置结构:

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("正文内容")
        ]
      })
    ]
  }]
});

示例:

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)

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

配置结构:

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, // 与下段同页
        },
      },
    ],
  },
});

示例:

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)

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

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. 内联样式(直接设置)

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

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)

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

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)

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. 混合使用所有方式

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

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 }  // 段落内联样式
      })
    ]
  }]
});

样式优先级示例:

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倍行距

示例:

创建一份月度工作报告:

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. 样式配置工具

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

// 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
  })
};

使用工具函数:

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行)⭐⭐⭐⭐⭐必须实现
// 渐进式生成大型文档
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. 浏览器环境优化

// 添加进度反馈的大型文档生成
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. 错误处理与用户体验

// 完整的错误处理示例
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:中英文混合字体

// 正确的字体设置
{
  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. 提供进度反馈:让用户了解生成进度

项目结构建议

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文档导出功能了!

以上就是Vue使用docx和file-saver实现Word文档导出功能的详细内容,更多关于Vue docx和file-saver实现Word导出的资料请关注脚本之家其它相关文章!

相关文章

  • 一文详解如何用Three.js和Vue 3实现3D商品展示

    一文详解如何用Three.js和Vue 3实现3D商品展示

    Three.js是一个基于JavaScript的开源库,用于在网页上创建和显示3D图形,这篇文章主要介绍了如何用Three.js和Vue 3实现3D商品展示的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-07-07
  • vuejs路由的传参及路由props配置详解

    vuejs路由的传参及路由props配置详解

    最近在学习vue router的传参,所以下面这篇文章主要给大家介绍了关于vuejs路由的传参及路由props配置的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-07-07
  • Vue 2源码阅读 Provide Inject 依赖注入详解

    Vue 2源码阅读 Provide Inject 依赖注入详解

    这篇文章主要为大家介绍了Vue 2源码阅读 Provide Inject 依赖注入详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • vuejs移动端实现div拖拽移动

    vuejs移动端实现div拖拽移动

    这篇文章主要为大家详细介绍了vuejs移动端实现div拖拽移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-07-07
  • vue3项目目录结构示例详解

    vue3项目目录结构示例详解

    更好的了解项目的目录结构,能更好的去开发项目,下面这篇文章主要给大家介绍了关于vue3项目目录结构的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-02-02
  • vue如何动态绑定img的src属性(v-bind)

    vue如何动态绑定img的src属性(v-bind)

    这篇文章主要介绍了vue如何动态绑定img的src属性(v-bind),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-04-04
  • 在Vant的基础上实现添加表单验证框架的方法示例

    在Vant的基础上实现添加表单验证框架的方法示例

    这篇文章主要介绍了在Vant的基础上实现添加验证框架的方法示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-12-12
  • TSX常见简单入门用法之Vue3+Vite

    TSX常见简单入门用法之Vue3+Vite

    Vue3的确可以直接使用tsx开发,唯一需要处理的就是children,而且处理起来还是比较不爽的,下面这篇文章主要给大家介绍了关于TSX常见简单入门用法之Vue3+Vite的相关资料,需要的朋友可以参考下
    2022-08-08
  • vue中input框的禁用和可输入问题

    vue中input框的禁用和可输入问题

    这篇文章主要介绍了vue input框的禁用和可输入问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-04-04
  • Vue.delete()删除对象的属性说明

    Vue.delete()删除对象的属性说明

    这篇文章主要介绍了Vue.delete()删除对象的属性说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-04-04

最新评论