临沂市万年长

Vue3实现前端生成Word并下载的全过程

2026-03-31 06:11:01 浏览次数:1
详细信息

Vue3 实现前端生成 Word 并下载

下面我将详细介绍如何在 Vue3 项目中实现前端生成 Word 文档并下载的功能。

方案选择

主要有两种方案:

使用 docx 库 - 适合生成结构化的 Word 文档 使用 html-docx-js - 将 HTML 转换为 Word

方案一:使用 docx 库(推荐)

1. 安装依赖
npm install docx file-saver
# 或者
yarn add docx file-saver
2. 创建 Word 生成工具
// utils/wordGenerator.js
import { Document, Paragraph, TextRun, HeadingLevel, Packer } from 'docx';
import { saveAs } from 'file-saver';

/**
 * 生成简单的 Word 文档
 */
export const generateSimpleWord = async (data) => {
  // 创建文档
  const doc = new Document({
    sections: [{
      properties: {},
      children: [
        // 标题
        new Paragraph({
          text: data.title || '默认标题',
          heading: HeadingLevel.HEADING_1,
          spacing: { after: 200 }
        }),

        // 内容段落
        new Paragraph({
          children: [
            new TextRun({
              text: data.content || '这里是文档内容',
              bold: true,
              size: 24
            })
          ],
          spacing: { after: 200 }
        }),

        // 表格示例
        new Paragraph({
          text: '表格示例:',
          heading: HeadingLevel.HEADING_2
        }),

        // 更多内容...
      ]
    }]
  });

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

  // 保存文件
  saveAs(blob, `${data.filename || 'document'}.docx`);
};

/**
 * 生成带表格的复杂文档
 */
export const generateComplexWord = async (data) => {
  const { Table, TableRow, TableCell } = await import('docx');

  const doc = new Document({
    sections: [{
      children: [
        // 创建表格
        new Table({
          rows: [
            new TableRow({
              children: [
                new TableCell({ children: [new Paragraph('姓名')] }),
                new TableCell({ children: [new Paragraph('年龄')] }),
                new TableCell({ children: [new Paragraph('职位')] })
              ]
            }),
            ...data.tableData.map(item => 
              new TableRow({
                children: [
                  new TableCell({ children: [new Paragraph(item.name)] }),
                  new TableCell({ children: [new Paragraph(item.age.toString())] }),
                  new TableCell({ children: [new Paragraph(item.position)] })
                ]
              })
            )
          ]
        })
      ]
    }]
  });

  const blob = await Packer.toBlob(doc);
  saveAs(blob, 'table-document.docx');
};
3. Vue3 组件中使用
<template>
  <div class="word-generator">
    <h1>Word 文档生成器</h1>

    <div class="form-container">
      <el-form :model="formData" label-width="80px">
        <el-form-item label="文档标题">
          <el-input v-model="formData.title" placeholder="请输入标题" />
        </el-form-item>

        <el-form-item label="文档内容">
          <el-input
            v-model="formData.content"
            type="textarea"
            :rows="4"
            placeholder="请输入内容"
          />
        </el-form-item>

        <el-form-item label="文件名">
          <el-input v-model="formData.filename" placeholder="输入文件名" />
        </el-form-item>

        <el-form-item label="表格数据">
          <div class="table-controls">
            <el-button @click="addTableRow">添加行</el-button>
            <el-button @click="removeTableRow">删除行</el-button>
          </div>

          <el-table :data="formData.tableData" border>
            <el-table-column prop="name" label="姓名">
              <template #default="scope">
                <el-input v-model="scope.row.name" />
              </template>
            </el-table-column>
            <el-table-column prop="age" label="年龄">
              <template #default="scope">
                <el-input v-model="scope.row.age" type="number" />
              </template>
            </el-table-column>
            <el-table-column prop="position" label="职位">
              <template #default="scope">
                <el-input v-model="scope.row.position" />
              </template>
            </el-table-column>
          </el-table>
        </el-form-item>
      </el-form>

      <div class="action-buttons">
        <el-button type="primary" @click="generateSimpleDoc">
          生成简单文档
        </el-button>

        <el-button type="success" @click="generateComplexDoc">
          生成表格文档
        </el-button>

        <el-button @click="generateFromTemplate">
          使用模板生成
        </el-button>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue'
import { generateSimpleWord, generateComplexWord } from '@/utils/wordGenerator'
import { ElMessage } from 'element-plus'

// 表单数据
const formData = reactive({
  title: '示例文档',
  content: '这是通过 Vue3 生成的 Word 文档内容。',
  filename: 'generated-document',
  tableData: [
    { name: '张三', age: 25, position: '前端工程师' },
    { name: '李四', age: 30, position: '后端工程师' }
  ]
})

// 添加表格行
const addTableRow = () => {
  formData.tableData.push({ name: '', age: '', position: '' })
}

// 删除表格行
const removeTableRow = () => {
  if (formData.tableData.length > 1) {
    formData.tableData.pop()
  }
}

// 生成简单文档
const generateSimpleDoc = async () => {
  try {
    await generateSimpleWord(formData)
    ElMessage.success('文档生成成功!')
  } catch (error) {
    ElMessage.error('生成失败:' + error.message)
  }
}

// 生成复杂文档
const generateComplexDoc = async () => {
  try {
    await generateComplexWord({
      tableData: formData.tableData
    })
    ElMessage.success('表格文档生成成功!')
  } catch (error) {
    ElMessage.error('生成失败:' + error.message)
  }
}

// 使用模板生成
const generateFromTemplate = async () => {
  // 可以在这里添加模板逻辑
  const templateData = {
    title: '模板生成文档',
    content: '这是通过模板生成的文档内容。',
    filename: 'template-document'
  }

  try {
    await generateSimpleWord(templateData)
    ElMessage.success('模板文档生成成功!')
  } catch (error) {
    ElMessage.error('生成失败:' + error.message)
  }
}
</script>

<style scoped>
.word-generator {
  padding: 20px;
  max-width: 800px;
  margin: 0 auto;
}

.form-container {
  margin-top: 30px;
  padding: 20px;
  border: 1px solid #e4e7ed;
  border-radius: 8px;
  background: #fff;
}

.table-controls {
  margin-bottom: 10px;
}

.action-buttons {
  margin-top: 30px;
  display: flex;
  gap: 10px;
  justify-content: center;
}
</style>

方案二:使用 html-docx-js

1. 安装依赖
npm install html-docx-js file-saver
2. HTML 转 Word 工具
// utils/htmlToWord.js
import htmlDocx from 'html-docx-js/dist/html-docx'
import { saveAs } from 'file-saver'

/**
 * 将 HTML 字符串转换为 Word 文档
 */
export const htmlToWord = (htmlString, filename = 'document') => {
  // 包装成完整的 HTML 文档
  const fullHtml = `
    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="UTF-8">
      <style>
        body { font-family: 'Microsoft YaHei', Arial, sans-serif; }
        h1 { color: #333; }
        table { border-collapse: collapse; width: 100%; }
        th, td { border: 1px solid #ddd; padding: 8px; }
        th { background-color: #f2f2f2; }
      </style>
    </head>
    <body>
      ${htmlString}
    </body>
    </html>
  `

  // 转换为 Word
  const converted = htmlDocx.asBlob(fullHtml)

  // 保存文件
  saveAs(converted, `${filename}.docx`)
}

/**
 * 从 DOM 元素生成 Word
 */
export const elementToWord = (elementId, filename = 'document') => {
  const element = document.getElementById(elementId)
  if (!element) {
    throw new Error(`元素 #${elementId} 不存在`)
  }

  const html = element.innerHTML
  htmlToWord(html, filename)
}
3. 在组件中使用
<template>
  <div>
    <div id="print-area" class="print-content">
      <h1>{{ formData.title }}</h1>
      <div v-html="formData.content"></div>

      <table v-if="formData.tableData.length">
        <thead>
          <tr>
            <th>姓名</th>
            <th>年龄</th>
            <th>职位</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="(item, index) in formData.tableData" :key="index">
            <td>{{ item.name }}</td>
            <td>{{ item.age }}</td>
            <td>{{ item.position }}</td>
          </tr>
        </tbody>
      </table>
    </div>

    <button @click="exportFromHtml">导出为 Word</button>
  </div>
</template>

<script setup>
import { htmlToWord } from '@/utils/htmlToWord'

const formData = reactive({
  title: 'HTML 生成的文档',
  content: '<p>这是 <strong>HTML 内容</strong> 生成的 Word 文档。</p>',
  tableData: [...]
})

const exportFromHtml = () => {
  htmlToWord(document.getElementById('print-area').innerHTML, 'html-document')
}
</script>

高级功能:使用模板系统

// utils/wordTemplate.js
import { Document, Paragraph, TextRun, HeadingLevel } from 'docx'

export class WordTemplate {
  constructor() {
    this.doc = new Document()
  }

  // 添加标题
  addTitle(text, level = 1) {
    const paragraph = new Paragraph({
      text,
      heading: HeadingLevel[`HEADING_${level}`],
      spacing: { after: 200 }
    })
    this.doc.addSection({ children: [paragraph] })
    return this
  }

  // 添加段落
  addParagraph(text, options = {}) {
    const paragraph = new Paragraph({
      children: [new TextRun({ text, ...options })],
      spacing: { after: 100 }
    })
    this.doc.addSection({ children: [paragraph] })
    return this
  }

  // 添加表格
  async addTable(data, headers) {
    const { Table, TableRow, TableCell } = await import('docx')

    const rows = [
      new TableRow({
        children: headers.map(header => 
          new TableCell({ children: [new Paragraph(header)] })
        )
      }),
      ...data.map(row => 
        new TableRow({
          children: Object.values(row).map(cell => 
            new TableCell({ children: [new Paragraph(cell.toString())] })
          )
        })
      )
    ]

    const table = new Table({ rows })
    this.doc.addSection({ children: [table] })
    return this
  }

  // 生成文档
  async generate(filename = 'document') {
    const { Packer } = await import('docx')
    const { saveAs } = await import('file-saver')

    const blob = await Packer.toBlob(this.doc)
    saveAs(blob, `${filename}.docx`)
  }
}

// 使用示例
const template = new WordTemplate()
  .addTitle('报告标题', 1)
  .addParagraph('这是报告正文内容', { bold: true })
  .addTable(
    [{ name: '张三', score: 95 }, { name: '李四', score: 88 }],
    ['姓名', '分数']
  )
  .generate('report')

处理图片和样式

// 添加图片到文档
async function addImageToDoc(imageUrl) {
  const { ImageRun } = await import('docx')

  const response = await fetch(imageUrl)
  const blob = await response.blob()
  const arrayBuffer = await blob.arrayBuffer()

  const image = new ImageRun({
    data: arrayBuffer,
    transformation: {
      width: 200,
      height: 200
    }
  })

  return new Paragraph({ children: [image] })
}

注意事项

兼容性:docx 库生成的是真正的 .docx 文件,兼容性较好 性能:对于大量数据,建议分页生成 样式:docx 的样式有限,复杂样式建议使用 HTML 转换方案 文件大小:添加图片会增加文件大小 中文支持:确保设置正确的字体

完整示例项目结构

src/
├── components/
│   └── WordGenerator.vue
├── utils/
│   ├── wordGenerator.js
│   ├── wordTemplate.js
│   └── htmlToWord.js
└── App.vue

这个实现方案提供了灵活的选择,可以根据具体需求选择合适的方案。docx 方案适合需要精确控制文档结构的场景,而 HTML 转换方案适合需要复杂样式和布局的场景。

相关推荐