最近写了几个导出word的需求,于是总结一波。
'use strict';
import React, { useState } from 'react';
import Docxtemplater from 'docxtemplater';
import PizZip from 'pizzip';
import expressionParser from 'docxtemplater/expressions';
var ImageModule = require('docxtemplater-image-module-free');
import FileSaver from 'file-saver';
import { Button, message } from 'antd';
import * as echarts from 'echarts';
function Export2Word (props) {
const {
btnDescribe = '生成报告',
templateWordUrl,
ratio = 1,
OutPutName = '报告',
obtainData,
} = props;
const [loading, setLoading] = useState(false);
const generateReport = async () => {
try {
setLoading(true);
const generateData=await obtainData();
//处理数据
let newGenerateData = await handleData(generateData);
let tempContent = await loadTemplate(templateWordUrl);
const zip = new PizZip(tempContent);
let opts = {
centered: false,
fileType: 'docx',
getImage(id) {
return base64DataURLToArrayBuffer(id);
},
getSize(img, url, tagName) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.src = url;
image.onload = function () {
//图片缩放比例
resolve([image['width'] * ratio, image['height'] * ratio]);
};
image.onerror = function (e) {
alert('An error occured while loading ' + url);
reject(e);
};
});
},
};
let out = null;
let ImageM = new ImageModule(opts);
const doc = new Docxtemplater(zip, {
modules: [ImageM],
paragraphLoop: true,
linebreaks: true,
parser: expressionParser,
});
const result = await doc.renderAsync(newGenerateData);
if (result?.errors?.length === 0) {
out = doc.getZip().generate({
type: 'blob',
mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
});
FileSaver.saveAs(out, `${OutPutName}.docx`);
setLoading(false);
message.success('生成报表成功!');
} else {
message.error('生成报表失败!');
setLoading(false);
throw new Error('生成报表失败!!!');
}
setLoading(false);
} catch (error) {
message.error('生成报表失败!');
console.log('生成报表失败原因', error);
setLoading(false);
}
};
//处理数据,将图片数据处理成base64位字符串
const handleData = async (generateData) => {
let newGenerateData = {};
for (let key in generateData) {
if (Object.prototype.toString.call(generateData?.[key]) === '[object Object]') {
if (generateData[key].type === 'img') {
//值可以是数组或者字符串
if (Object.prototype.toString.call(generateData?.[key]?.value) === '[object Array]') {
if (generateData?.[key]?.value?.length) {
let handledImgUrl = await Promise.all(generateData?.[key]?.value?.map(getBase64Sync));
//需要将数组图片处理成对象,以便于word中遍历
newGenerateData[key] = handledImgUrl?.map((item) => ({
[generateData?.[key]?.['url']]: item,
}));
continue;
} else {
newGenerateData[key] = '';
continue;
}
}
newGenerateData[key] = await getBase64Sync(generateData[key]?.value);
continue;
}
if (generateData[key].type === 'echarts') {
const dom = document.createElement('div');
let parent=document.getElementById('chart');
parent.appendChild(dom);
dom.style.width = (generateData[key].width ? `${generateData[key].width}px` : '300px');
dom.style.height = (generateData[key].height ? `${generateData[key].height}px` : '300px');
dom.style.visibility = 'hidden';
// dom.style.visibility = 'visible';
const chart = echarts.init(dom);
chart.setOption({ ...generateData[key].option, animation: false });
chart.resize();
let chartBase64 = chart.getDataURL({
type: generateData[key]['type']&&generateData[key]['type'] || 'png',
pixelRatio: generateData[key]['ratio']&&generateData[key]['ratio'] || 1,
backgroundColor: generateData[key]['color']&&generateData[key]['color'] || '#fff',
});
newGenerateData[key] = chartBase64
document.getElementById('chart').removeChild(dom);
continue;
}
newGenerateData[key] = generateData[key].value;
continue;
}
newGenerateData[key] = generateData[key];
}
return newGenerateData;
};
return (
<div id='chart'>
<Button loading={loading} onClick={generateReport}>
{btnDescribe}
</Button>
</div>
);
};
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 = Buffer.from(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;
}
const getBase64Sync = (imgUrl) => {
return new Promise(function (resolve, reject) {
if (imgUrl) {
let image = new Image();
image.src = imgUrl;
image.setAttribute('crossOrigin', '*');
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;
let dataurl = canvas.toDataURL('image/' + ext, quality);
resolve(dataurl);
};
} else {
resolve('');
}
});
};
async function loadTemplate(url) {
const response = await fetch(url);
const blob = await response.blob();
const arrayBuffer = await blob.arrayBuffer();
return arrayBuffer;
}
//generateData格式example
/**
let generateData = {
name: '张三',
//图片需要写成对象形式,其type必须为img
photo: { type: 'img',
value: 'https://www.baidu.com/img/bd_logo1.png'
},
photo: {
type: 'img',
value: ['https://www.baidu.com/img/bd_logo1.png', 'https://www.baidu.com/img/bd_logo1.png'],
url: 'phurl',//模板中遍历图片的key
},
echartsPhoto: {
type: 'echarts',//必填
option: {},//必填,就是echarts的option
color: '',//导出echarts颜色,不必填,默认值#fff
width: 300,//导出echarts宽度,不必填,默认值300
height: 300,//导出echarts高度,不必填,默认值300
type: 'png',//导出echarts图片格式,不必填,默认值png
ratio: 1,//导出echarts图片缩放比例,不必填,默认值1
},
};
*/
export default Export2Word
上面是导出组件的代码 下面代码是使用示例
<Export2Word
btnDescribe='确认'
templateWordUrl='/assets/template/return_repair_report_example.docx'
obtainData={obtainData}
/>
obtainData需要写成异步的形式,适用于获取word模板中的变量的方法。简单说就是说,如果是普通字段就直接整型或者字符串,如果是图片需要传递对象并标记其type为img,如果是需要生成echarts的图片,需要传递对象,并且标记其type为echarts。
word模板中编写说明:
1.对于简单类型
2.单图
3.多图,相当于你需要将这个组装成数组,每一项的url放在,你遍历的变量上。数据遍历也是如此。