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 转换方案适合需要复杂样式和布局的场景。