一、痛点
word 导出
这种功能其实之前都是后端实现的,但最近有个项目没得后端。所以研究下前端导出。
ps: 前端还可以导出 pdf,但是其分页问题需要话精力去计算才可能实现,并且都不是很完善。可参考之前的文章:利用 html2canvas 和 jspdf 导出 echarts ( html页面 )为pdf
二、依赖安装
// 实现word下载的主要三方库
npm install docxtemplater pizzip --save
// 文件操作;大佬们可以不需要,自己用fs、path等模块实现
npm install jszip jszip-utils --save
// 文件存储
npm install file-saver --save
// 图片处理模块,没有图片需求可以不装
npm install docxtemplater-image-module-free --save
三、创建导出word的公用方法 exportWord.js
ps:这个方法大同小异,网上很多
import PizZip from 'pizzip'
import Docxtemplater from 'docxtemplater'
import JSZipUtils from 'jszip-utils'
import { saveAs } from 'file-saver'
// 将图片地址转为base64,导出word图片只能是base64
export function getBase64Sync(imgUrl) {
return new Promise(function (resolve, reject) {
// 一定要设置为let,不然图片不显示
let image = new Image();
// 解决跨域问题
image.crossOrigin = 'anonymous';
//图片地址
image.src = imgUrl;
// image.onload为异步加载
image.onload = function () {
let canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
let context = canvas.getContext('2d');
context.drawImage(image, 0, 0, image.width, image.height);
//图片后缀名
let ext = image.src
.substring(image.src.lastIndexOf('.') + 1)
.toLowerCase();
//图片质量
let quality = 0.8;
//转成base64
let dataurl = canvas.toDataURL('image/' + ext, quality);
//返回
resolve(dataurl);
};
});
}
/**
* 将base64格式的数据转为ArrayBuffer
* @param {Object} dataURL base64格式的数据
*/
function base64DataURLToArrayBuffer(dataURL) {
const base64Regex = /^data:image\/(png|jpg|jpeg|svg|svg\+xml);base64,/;
if (!base64Regex.test(dataURL)) {
return false;
}
const stringBase64 = dataURL.replace(base64Regex, '');
let binaryString;
if (typeof window !== 'undefined') {
binaryString = window.atob(stringBase64);
} else {
binaryString = new Buffer(stringBase64, 'base64').toString('binary');
}
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
const ascii = binaryString.charCodeAt(i);
bytes[i] = ascii;
}
return bytes.buffer;
}
/**
* 导出word,支持图片
* @param {Object} tempDocxPath 模板文件路径
* @param {Object} wordData 导出数据
* @param {Object} fileName 导出文件名
* @param {Object} imgSize 预留,自定义图片尺寸 => 暂没使用
*/
export const exportWord = (tempDocxPath, wordData, fileName, imgSize) => {
// 这里要引入处理图片的插件
var ImageModule = require('docxtemplater-image-module-free');
JSZipUtils.getBinaryContent(tempDocxPath, function (error, content) {
if (error) {
throw error;
}
// 图片处理
let opts = {};
opts = {
centered: true, //图像是否居中,true:在word中图片居中
getImage: (chartId) => {
// 将base64转成ArrayBuffer
return base64DataURLToArrayBuffer(chartId);
},
//自定义指定图像大小,此处可动态调试各别图片的大小
getSize: (img, tagValue, tagName) => {
// tagName 是指我们自己定义图片使用的字段名,如path、url等
// if (tagName === 'imgurl') return [700, 350]; //设置图片宽高,tagName :传入的变量
// return [200, 150];
if (Object.prototype.hasOwnProperty.call(imgSize, tagName)) {
return imgSize[tagName];
} else {
return [150, 150];
}
}
};
// 创建一个PizZip实例,内容为模板的内容
let zip = new PizZip(content);
// 创建并加载docxtemplater实例对象
let doc = new Docxtemplater();
doc.attachModule(new ImageModule(opts));
doc.loadZip(zip);
// 设置模板变量的值
doc.setData(wordData);
try {
// 用模板变量的值替换所有模板变量
doc.render();
} catch (error) {
// 抛出异常
let e = {
message: error.message,
name: error.name,
stack: error.stack,
properties: error.properties
};
console.log(JSON.stringify({ error: e }));
throw error;
}
// 生成一个代表docxtemplater对象的zip文件(不是一个真实的文件,而是在内存中的表示)
let out = doc.getZip().generate({
type: 'blob',
mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
});
// 将目标文件对象保存为目标类型的文件,并命名
saveAs(out, fileName);
});
}
四、组件中调用
前几章都是基础,调用才是重点。
1. 创建模板
导出word其实就是解析我们提供的模板,然后将对应字段填入,最新进行导出即可。所以,模板 至关重要
!
- 创建
.docx
文件
该文件只能直接创建为docx
或者另存为docx
;不能直接修改后缀名。 - vue2 将模板放在static文件下;vue3 将模板放在public文件下
2. 模板语法
语法用 { } 即可
- 普通字段直接填入字段名即可
- 如果字段是图片地址,需要加上 %,例如:
{#imgList}
{%pathUrl}
{/imgList}
踩坑:图片这里我一直报错‘%imgUrl’,最后发现必须要换行写,而其他数组可以在一行写。
- 遍历列表 以
{#list} 开头
… 列表元素字段名 …{/list} 结尾
list: [
{name:'张三', age:'18'},
{name:'李四', age:'28'}
]
模板:
导出实际结果:
3. 组件调用
<template>
<div>
<!-- 页面只有一个echarts 和 导出按钮 -->
<div id="myChart6" :style="{ width: '800px', height: '800px' }"></div>
<el-button type="primary" @click="exprodWord">导出word</el-button>
</div>
</template>
<script>
import * as echarts from 'echarts';
import { onMounted } from 'vue'
import { getBase64Sync, exportWord } from './exportFile'
export default {
name: 'WordTemplate',
setup () {
let myChartDom = null;
const wordData = {
title: '环境工业风险审核报告',
des: '对于需要判断显示的要用{#isProblem}开始,{/isProblem}结束,isProblem的类型是Boolean,true的时候是显示。如下图,isFull==true的时候,才显示下面这句话',
userList: [
{
indexNo: 1,
name: '张三',
age: '18',
address: '上海',
imgList: [
{
url: 'https://i.postimg.cc/qqcRNJ1y/b3c2e029c5deda297e29680e26a5c48c.jpg'
},
{
url: 'https://i.postimg.cc/9Q5b3J7k/797c9c2bbf47b1ad4632670e508e0d5d.jpg'
}
],
status: 1,
},
{
indexNo: 2,
name: '李四',
age: '28',
address: '四川',
imgList: [],
status: 1,
},
{
indexNo: 3,
name: '王五',
age: '38',
address: '北京',
imgList: [],
status: 0,
},
{
indexNo: 4,
name: '张柳',
age: '48',
address: '成都',
imgList: [],
status: 0,
}
]
}
const exprodWord = async () => {
const chartPath = getChartImg(); // 获取到echarts的图片地址
const renderData = JSON.parse(JSON.stringify(wordData))
renderData.chartPath = chartPath
// 将图片转成base64是异步操作,需要等待图片base64返回,所以使用Promise.all
renderData.userList = await Promise.all(
renderData.userList.map(async item => {
return {
...item,
imgList: await Promise.all(
item.imgList.map(async em => {
return {
...em,
path: await getBase64Sync(em.url)
}
})
)
};
})
)
let imgSize = {
imgurl: [200, 200], // 定义图片字段名为 'imgurl' 的尺寸, 该实例中没有图片字段名是imgurl,所以不生效
chartPath: [1000, 800] // 定义图片字段名为 'chartPath' 的尺寸, 即该实例中的echarts图片
// ... 更多
}
console.log('------------renderData', renderData)
exportWord('template.docx', renderData, '环境工业风险审核报告.docx', imgSize)
}
// 基于准备好的dom,初始化echarts实例
const initChart = () => {
myChartDom = echarts.init(document.getElementById('myChart6'));
// 绘制图表
myChartDom.setOption({
title: {
text: 'ECharts 入门示例'
},
tooltip: {},
xAxis: {
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
},
yAxis: {},
series: [
{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}
]
});
}
// 获取图表base64图
const getChartImg = () => {
return myChartDom.getDataURL({
pixelRatio: 2, // 解决模糊
backgroundColor: '#fff'
});
}
onMounted(() => {
initChart()
})
return {
exprodWord
}
}
}
</script>
<style lang='scss' scoped>
</style>
注意:导出操作可能涉及异步操作,请多使用 Promise.all、nextTick
等异步方法,尽量少使用setTimeout
。
五、导出word 结果
文章仅为本人学习过程的一个记录,仅供参考,如有问题,欢迎指出!