其實在調(diào)研之初是知道幾個插件的讼撒,但大部分都不在維護(hù)了且導(dǎo)出效果不理想炎滞。
本文主要使用docxtemplater導(dǎo)出word文檔并記錄以下詳細(xì)步驟震嫉。
實現(xiàn)效果:
原始頁面是有一個列表和兩個echarts圖表
導(dǎo)出的word效果:
其中列表是可修改的,echarts是兩張圖片不可修改。
詳細(xì)實現(xiàn)步驟
1.安裝依賴
pnpm install docxtemplater pizzip --save
pnpm install jszip-utils --save
pnpm install jszip --save
pnpm install file-saver --save
pnpm install docxtemplater-image-module-free
docxtemplater:這個插件可以通過預(yù)先寫好的word官硝,excel等文件模板生成對應(yīng)帶數(shù)據(jù)的文件
pizzip:這個插件用來創(chuàng)建朋魔,讀取或編輯.zip的文件(同步的,還有一個插件是jszip拓售,異步的)
jszip-utils:與jszip/pizzip一起使用哨苛,jszip-utils 提供一個getBinaryContent(path, data)接口亿蒸,path即是文件的 路徑,支持AJAX get請求盆色,data為讀取的文件內(nèi)容。
file-saver:適合在客戶端生成文件的工具,它提供的接口saveAs(blob, "1.docx")將會使用到,方便我們保存 文件抖拴。
docxtemplater-image-module-free:導(dǎo)出圖片的話需要這個插件
2.創(chuàng)建word模板文件
創(chuàng)建名為test的word模板洒放,放至項目的public文件夾內(nèi):public/test.docx
- docxtemplater 語法
{%img} 圖片
{#list}{/list} 循環(huán)惨好、if判斷
{#list}{/list}{^list}{/list} if else
{str} 文字
這一步將說明如何在test.docx內(nèi)編寫模板
列表部分需要自己先在word內(nèi)插入一張表格回论,然后表頭自己定義好。如果有什么特殊樣式自己都可以在word內(nèi)提前編輯好谱净。
列表數(shù)據(jù)對應(yīng)字段是logList李请,列表循環(huán)以{#logList}開始白翻,又以{/logList}結(jié)尾。注意這里的所有字段都要與代碼傳入的一致。{%leftImage}和{%rightImage}就是那兩張echarts圖所在的位置纯续。
4.代碼實現(xiàn)部分
import JSZipUtils from "jszip-utils";
import docxtemplater from "docxtemplater";
import PizZip from "pizzip";
import saveAs from "file-saver"
import ImageModule from "docxtemplater-image-module-free"
由于需要拿到echarts相關(guān)dom所以需要定義ref并綁定
<div class="h-400px flex">
<LineChart
id="item0"
:options="chatOptions"
height="400px"
width="100%"
ref="leftChartRef"
class="bg-[var(--el-bg-color-overlay)]"/>
<LineChart
id="item1"
:options="chatOptions2"
height="400px"
width="100%"
ref="rightChartRef"
class="bg-[var(--el-bg-color-overlay)]"/>
</div>
const leftChartRef = ref(HTMLDivElement)
const leftImage = ref(null) //存儲圖片資源
const rightChartRef = ref(HTMLDivElement)
const rightImage = ref(null)
const logList = ref<LogPageVO[]>(); //接口獲取的表格數(shù)據(jù)
導(dǎo)出按鈕點擊和調(diào)用的相關(guān)方法
//導(dǎo)出點擊
const exportWordClick = ()=> {
leftImage.value = leftChartRef.value.getChartsImage();
rightImage.value = rightChartRef.value.getChartsImage();
let docxData= {
logList: logList.value //列表數(shù)據(jù),類似于[{name:'系統(tǒng)管理員'逢唤,ip:'114.0.57....'}]
}
downLoadDoc('public/test.docx',docxData,"下載測試模板")
}
const downLoadDoc = (demoUrl, docxData, fileName)=>{
// 讀取并獲得模板文件的二進(jìn)制內(nèi)容
JSZipUtils.getBinaryContent(
demoUrl,
function (error, content) {
// 拋出異常
if (error) {
throw error;
}
// 圖片處理
let opts = { centered: false }
opts.getImage = chartId => {
return base64DataURLToArrayBuffer(chartId)
}
opts.getSize = (img, tagValue, tagName)=> {
//自定義指定圖像大小魔慷,此處可動態(tài)調(diào)試各別圖片的大小
return [500, 300] //例子:寬500px 高度300px
}
// 創(chuàng)建一個PizZip實例邀摆,內(nèi)容為模板的內(nèi)容
let zip = new PizZip(content);
// 創(chuàng)建并加載docxtemplater實例對象
let doc = new docxtemplater().loadZip(zip).attachModule(new ImageModule(opts));
// 去除未定義值所顯示的undefined
doc.setOptions({
nullGetter: function () {
return "";
}
});
// 設(shè)置模板變量的值贞盯,對象的鍵需要和模板上的變量名一致,值就是你要放在模板上的值
doc.setData({
...docxData,
leftImage: leftImage.value,
rightImage: rightImage.value
});
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文件(不是一個真實的文件话侧,而是在內(nèi)存中的表示)
let out = doc.getZip().generate({
type: "blob",
mimeType:
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
});
// 將目標(biāo)文件對象保存為目標(biāo)類型的文件栗精,并命名
saveAs(out, fileName);
}
);
}
const base64DataURLToArrayBuffer = (dataURL)=> {
const base64Regex = /^data:image\/(png|jpg|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)出點擊方法內(nèi)
leftImage.value = leftChartRef.value.getChartsImage();
rightImage.value = rightChartRef.value.getChartsImage();
這也是我們?yōu)槭裁炊xref的原因,因為需要將echarts圖片轉(zhuǎn)為base64格式瞻鹏。
echarts子組件內(nèi)
//將echarts圖片轉(zhuǎn)為base64格式
const getChartsImage = ()=>{
return chart.value.getDataURL({
pixelRatio: 2, // 導(dǎo)出的圖片分辨率比例术羔,默認(rèn)為 1。
backgroundColor: 'transparent' // 導(dǎo)出的圖片背景色乙漓,默認(rèn)使用 option 里的 backgroundColor
})
}
/**
* 當(dāng)使用 <script setup> 寫法會導(dǎo)致父組件無法訪問到子組件中的屬性和方法级历。
* 使用 <script setup> 的組件,想要讓父組件訪問到它的屬性和方法需要借助與defineExpose來指定需要暴露給父組件的屬性叭披。
* */
defineExpose({
getChartsImage
})
5.結(jié)語
至此寥殖,我們在vue3中使用docxtemplater導(dǎo)出word文檔的實踐就告一段落了。文章從介紹docxtemplater基本使用語法涩蜘,再到完整的代碼示例實現(xiàn)表格和echarts圖表的導(dǎo)出功能嚼贡。如果有什么想法也可以繼續(xù)留言交流,感謝同诫!