场景
有的时候我们需要根据后端提供的数据,然后结合word模版来生成word。我们可以使用第三方库docxtemplater
效果
依赖说明
1、docxtemplater:这个插件可以通过预先写好的word,excel等文件模板生成对应带数据的文件
2、pizzip:这个插件用来创建,读取或编辑.zip的文件(同步的,还有一个插件是jszip,异步的)
3、jszip-utils:与jszip/pizzip一起使用,jszip-utils 提供一个getBinaryContent(path, data)接口,path即是文件的路径,支持AJAX get请求,data为读取的文件内容。
4、file-saver:适合在客户端生成文件的工具,它提供的接口saveAs(blob, "1.docx")将会使用到,方便我们保存文件。
5、docxtemplater-image-module-free:需要导出图片的话需要这个插件
代码
App.vue
<template>
<div class="app">
<el-divider content-position="center">1.基本使用</el-divider>
<el-button type="primary" @click="exportWord_1">导出word</el-button>
<el-divider content-position="center">2.带有表格的导出</el-divider>
<el-button type="primary" @click="exportWord_2">导出word</el-button>
<el-divider content-position="center">3.带有图片的导出</el-divider>
<el-button type="primary" @click="exportWord_3">导出word</el-button>
</div>
</template>
<script>
import Docxtemplater from 'docxtemplater'
import PizZip from 'pizzip'
import PizZipUtils from 'pizzip/utils/index.js'
import { saveAs } from 'file-saver'
// import ImageModule from 'docxtemplater-image-module/build/imagemodule'
import ImageModule from 'docxtemplater-image-module-free'
import logo from '@/assets/1.png'
export default {
data() {
return {}
},
methods: {
exportWord_1() {
PizZipUtils.getBinaryContent('/word/template.docx', (err, content) => {
if (err) {
throw err
}
const zip = new PizZip(content)
const doc = new Docxtemplater(zip, {
paragraphLoop: true,
linebreaks: true,
})
doc.render({
title: '邮件标题',
name: 'Coder',
content: '这是一封测试邮件',
signer: 'Me',
sign_time: '2024/07/02',
})
const out = doc.getZip().generate({
type: 'blob',
mimeType:
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
})
saveAs(out, 'Test.docx')
})
},
exportWord_2() {
PizZipUtils.getBinaryContent('/word/template.docx', (err, content) => {
if (err) {
throw err
}
const zip = new PizZip(content)
const doc = new Docxtemplater(zip, {
paragraphLoop: true,
linebreaks: true,
})
doc.render({
title: '邮件标题',
name: 'Coder',
content: '这是一封测试邮件',
signer: 'Me',
sign_time: '2024/07/02',
movie_list: [
{
name: '盗梦空间',
price: 56,
release_date: '2013-06-03',
},
{
name: '斗破苍穹',
price: 46,
release_date: '2024-01-03',
},
{
name: '疯狂的麦克斯*狂暴女神',
price: 76,
release_date: '2024-07-02',
},
],
})
const out = doc.getZip().generate({
type: 'blob',
mimeType:
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
})
saveAs(out, 'Test.docx')
})
},
exportWord_3() {
// 官方示例,尝试不成功
/*
const imageOptions = {
getImage(url) {
return new Promise(function (resolve, reject) {
PizZipUtils.getBinaryContent(url, function (error, content) {
if (error) {
return reject(error)
}
return resolve(content)
})
})
},
getSize(img, url, tagName) {
return new Promise(function (resolve, reject) {
const image = new Image()
image.src = url
image.onload = function () {
resolve([image.width, image.height])
}
image.onerror = function (e) {
console.log('img, url, tagName : ', img, url, tagName)
alert('An error occured while loading ' + url)
reject(e)
}
})
},
}
PizZipUtils.getBinaryContent('/word/template.docx', (err, content) => {
if (err) {
throw err
}
const zip = new PizZip(content)
const doc = new Docxtemplater(zip, {
paragraphLoop: true,
linebreaks: true,
modules: [new ImageModule(imageOptions)],
})
doc
.renderAsync({
logo: logo,
title: '邮件标题',
name: 'Coder',
content: '这是一封测试邮件',
signer: 'Me',
sign_time: '2024/07/02',
movie_list: [
{
name: '盗梦空间',
price: 56,
release_date: '2013-06-03',
},
{
name: '斗破苍穹',
price: 46,
release_date: '2024-01-03',
},
{
name: '疯狂的麦克斯*狂暴女神',
price: 76,
release_date: '2024-07-02',
},
],
})
.then(function () {
const out = doc.getZip().generate({
type: 'blob',
mimeType:
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
})
saveAs(out, 'Test.docx')
})
})
*/
PizZipUtils.getBinaryContent('/word/template.docx', (err, content) => {
if (err) {
throw err
}
var opts = {}
opts.centered = false
opts.getImage = function (tagValue, tagName) {
return new Promise(function (resolve, reject) {
PizZipUtils.getBinaryContent(tagValue, function (error, content) {
if (error) {
return reject(error)
}
return resolve(content)
})
})
}
opts.getSize = function (img, tagValue, tagName) {
// FOR FIXED SIZE IMAGE :
return [150, 150]
// FOR IMAGE COMING FROM A URL (IF TAGVALUE IS AN ADRESS) :
// To use this feature, you have to be using docxtemplater async
// (if you are calling setData(), you are not using async).
return new Promise(function (resolve, reject) {
var image = new Image()
image.src = url
image.onload = function () {
resolve([image.width, image.height])
}
image.onerror = function (e) {
console.log('img, tagValue, tagName : ', img, tagValue, tagName)
alert('An error occured while loading ' + tagValue)
reject(e)
}
})
}
var imageModule = new ImageModule(opts)
const zip = new PizZip(content)
const doc = new Docxtemplater()
.loadZip(zip)
.attachModule(imageModule)
.compile()
doc
.resolveData({
logo: logo,
title: '邮件标题',
name: 'Coder',
content: '这是一封测试邮件',
signer: 'Me',
sign_time: '2024/07/02',
movie_list: [
{
name: '盗梦空间',
price: 56,
release_date: '2013-06-03',
},
{
name: '斗破苍穹',
price: 46,
release_date: '2024-01-03',
},
{
name: '疯狂的麦克斯*狂暴女神',
price: 76,
release_date: '2024-07-02',
},
],
})
.then(function () {
doc.render()
var out = doc.getZip().generate({
type: 'blob',
mimeType:
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
})
saveAs(out, 'test-image.docx')
})
})
},
},
}
</script>
<style lang="less" scoped>
.app {
padding: 40px;
}
</style>
public/word/template.docx
参考文档
- https://docxtemplater.com/docs/get-started-browser/
- https://docxtemplater.com/faq/#empty-pages-generated
- https://www.npmjs.com/package/@slosarek/docxtemplater-image-module-free
- https://juejin.cn/post/7094139413248081928