因?yàn)轫?xiàng)目報告導(dǎo)出pdf斷頁問題不能得到很好的解決,所以決定導(dǎo)出word师脂。中間踩過的坑,今天聊作記錄江锨,分享與復(fù)盤吃警。
其中主要用得到的是docxTemplater,docxTemplater是一個通過模板文件生成word的庫啄育,它能最大程度的保證最終生成的word的樣式的完整和還原酌心。
1、 依賴安裝
// 安裝 docxtemplater
npm install docxtemplater pizzip --save
// 安裝 jszip-utils
npm install jszip-utils --save
// 安裝 jszip
npm install jszip --save
// 安裝 FileSaver
npm install file-saver --save
// 安裝 angular-expressions
npm install angular-expressions --save
// 安裝 image-size
npm install image-size --save
// 圖片模塊挑豌,沒有圖片需求可以不裝
npm install docxtemplater-image-module-free
2安券、創(chuàng)建導(dǎo)出word的js,exportFile.js,目錄自定義
import PizZip from 'pizzip'
import docxtemplater from 'docxtemplater'
import JSZipUtils from 'jszip-utils'
import { saveAs } from 'file-saver'
export function getBase64Sync(imgUrl) {
return new Promise(function (resolve, reject) {
// 一定要設(shè)置為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();
//圖片質(zhì)量
let quality = 0.8;
//轉(zhuǎn)成base64
let dataurl = canvas.toDataURL("image/" + ext, quality);
//返回
resolve(dataurl);
};
});
}
/**
* 將base64格式的數(shù)據(jù)轉(zhuǎn)為ArrayBuffer
* @param {Object} dataURL base64格式的數(shù)據(jù)
*/
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;
}
/**
* 導(dǎo)出word,支持圖片
* @param {Object} tempDocxPath 模板文件路徑
* @param {Object} wordData 導(dǎo)出數(shù)據(jù)
* @param {Object} fileName 導(dǎo)出文件名
* @param {Object} imgSize 自定義圖片尺寸
*/
export const exportWord = (tempDocxPath, wordData, fileName, imgSize) => {
// 這里要引入處理圖片的插件
var ImageModule = require('docxtemplater-image-module-free');
const expressions = require("angular-expressions");
// 讀取并獲得模板文件的二進(jìn)制內(nèi)容
JSZipUtils.getBinaryContent(tempDocxPath, function (error, content) {
console.log('tempDocxPath', tempDocxPath)
if (error) {
throw error;
}
expressions.filters.size = function (input, width, height) {
return {
data: input,
size: [width, height],
};
};
// function angularParser (tag) {
// const expr = expressions.compile(tag.replace(/’/g, "'"));
// return {
// get (scope) {
// return expr(scope);
// },
// };
// }
// 圖片處理
let opts = {}
opts = {
// 圖像是否居中
centered: false
};
opts.getImage = (chartId) => {
// console.log(chartId);//base64數(shù)據(jù)
// 將base64的數(shù)據(jù)轉(zhuǎn)為ArrayBuffer
return base64DataURLToArrayBuffer(chartId);
}
opts.getSize = function (img, tagValue, tagName) {
// console.log(img);//ArrayBuffer數(shù)據(jù)
// console.log(tagValue);//base64數(shù)據(jù)
// console.log(tagName);//wordData對象的圖像屬性名
// 自定義指定圖像大小
if (imgSize.hasOwnProperty(tagName)) {
return imgSize[tagName];
} else {
return [150, 150];
}
}
// 創(chuàng)建一個PizZip實(shí)例氓英,內(nèi)容為模板的內(nèi)容
let zip = new PizZip(content);
// 創(chuàng)建并加載docxtemplater實(shí)例對象
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文件(不是一個真實(shí)的文件侯勉,而是在內(nèi)存中的表示)
let out = doc.getZip().generate({
type: "blob",
mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
});
// 將目標(biāo)文件對象保存為目標(biāo)類型的文件,并命名
saveAs(out, fileName);
});
}
3铝阐、在頁面中調(diào)用址貌,導(dǎo)出word
- 引入exportWord
// 如果有圖片渲染的需求,要調(diào)用getBase64Sync 方法
import { exportWord, getBase64Sync } from '@/utils/exportFile.js'
- 數(shù)據(jù)結(jié)構(gòu)
wordData: {
companyName:'',
enName:'',
wmData:[
title:'',
score:'',
objective:'',
isFull:false,
children:[
{
isFullMark:true,
serialNumber:'',
findDesc:'',
picUrl:[
{
src:'',
}
],
solution:''
}
]
]
},
- 方法中
// wordData是模板需要的數(shù)據(jù)
// 圖片需要轉(zhuǎn)base64才能在word顯示
async toWord() {
this.wordData.src = await getBase64Sync(this.url)
exportWord('/template1.docx', this.wordData, '環(huán)境工業(yè)風(fēng)險審核報告.docx', imgSize)
}
4徘键、word模板中
-
對于直接顯示的字段练对,用{}表示
微信截圖_20221114094738.png -
對于需要判斷顯示的要用{#isProblem}開始,{/isProblem}結(jié)束吹害,isProblem的類型是Boolean螟凭,true的時候是顯示。如下圖赠制,isFull==true的時候赂摆,才顯示下面這句話
微信截圖_20221114100320.png -
對于循環(huán)顯示的數(shù)據(jù),也是{#wdM1}開始钟些,{/wdM1}結(jié)束烟号,中間是需要顯示的字段
微信截圖_20221114100917.png -
對于數(shù)據(jù)中的多重遍歷,例如我是wordData的wmData中的children遍歷,需要這樣寫:{#wmData}{#children}{/children}{/wmData}政恍,標(biāo)簽要一一對應(yīng)汪拥,不能寫錯位置。
微信截圖_20221114101629.png - 對于圖片的顯示,字段要加%
{#imgList} {%src}{/imgList}
5篙耗、遇到的問題
-
找不到文件
Can't find end of central directory : is this a zip file ?
微信截圖_20221114102336.png
這個報錯是沒有找到模板迫筑,如果是vue-cli2版本,要把模板文件放在static目錄下脯燃,如果是vue-cli3版本,就放在public目錄下辕棚,并且一定是docx文件欲主,如果不是要另存為docx逝嚎。如果你的文件直接改名也會報這個錯誤,一定要另存為才可以 圖片不顯示
圖片要轉(zhuǎn)成base64才能顯示word模板中的文字換行补君,textarea返回的字段,在word模板中沒有自動換行挽铁,在這里需要處理一下數(shù)據(jù)
this.wordData.auditResult = this.submitInfo.auditResult.split('\n')
word模板中要這樣寫
就可以了。
這是biying上搜到的答案屿储,當(dāng)看到分段成功的那一刻還是很驚喜的贿讹,致敬那些無私分享的前輩們够掠!
至此,我這里的項(xiàng)目需求都滿足了疯潭,開心撒花。