一、痛點
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;//轉成base64let 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轉成ArrayBufferreturn 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.allrenderData.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 結果
文章僅為本人學習過程的一個記錄,僅供參考,如有問題,歡迎指出!