vue導(dǎo)出PDF實(shí)現(xiàn)方式(vue2,插件:pdfmake,表格)
注:文章為記錄為主,想直接看pdfmake 打印的請(qǐng)劃到最下面部分
需求
頁面有一個(gè)表格,之前導(dǎo)出excel 表(用XLSX和XLSXS制作了樣式之類的表格),現(xiàn)在需要制作帶樣式的pdf,而且格式如下,并且最上面的三行需要在每一頁的頂部出現(xiàn)作為表頭
效果圖,(換頁前面三行也會(huì)有)

script 要打印的盒子:ref="printSection" ,這個(gè)表格是用 el-table 來寫的,而且表格中存在嵌套children,一共嵌套了3層
如上圖:大寫數(shù)字(一、二)是第一層,附和大寫字母(A、B)是第二層,其他(沒有序號(hào)的的行)是第三層
<div id="pdfDom" ref="printSection">
<el-table
empty-text="暫無報(bào)告"
v-loading="loading"
:data="staffJieyuList"
@selection-change="handleSelectionChange"
default-expand-all
row-key="row"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
<el-table-column align="center" :label="subTitle">
<el-table-column :label="searchInformation.lysuoName">
<el-table-column prop="id" align="center" label="序號(hào)">
</el-table-column>
<el-table-column prop="name" align="center" label="項(xiàng)目">
</el-table-column>
<el-table-column prop="amount" align="center" label="金額">
</el-table-column>
<el-table-column prop="contractNum" align="center" label="備注">
</el-table-column>
</el-table-column>
</el-table-column>
</el-table>
</div>上面表格data對(duì)應(yīng)的staffJieyuList數(shù)據(jù)的格式如下:
staffJieyuList = [ //這里是第一層數(shù)組,里面數(shù)據(jù)是對(duì)象格式
{
id: "一",
row: 1,
name: "業(yè)務(wù)收入",
amount: "",
contractNum: "",
//這里是第二層數(shù)組,里面數(shù)據(jù)是對(duì)象格式
children: [
{
id: "",
row: 101,
name: "本月合計(jì)",
amount: data.income,
},
{
id: "",
row: 102,
name: "本年累計(jì)",
amount: data.incomeYear,
},
{
id: "附",
row: 103,
name: "累計(jì)本年未收款明細(xì)",
amount: "",
//這里是第三層數(shù)組,里面數(shù)據(jù)是對(duì)象格式
children: data.incomeUnpaidList.map((ele, index) => {
let i = {
id: "",
row: "104" + index,
name: ele.fapiaoName + "(占比:" + ele.portion + ")",
amount: ele.amount,
};
return i;
}),
},
],
},
第二個(gè)數(shù)組...
]
我要打印的主要是四列:
序號(hào)id 、項(xiàng)目name、金額amount、備注contractNum(打印出來的看最上面的圖)
一.window.print();
打印可以直接調(diào)用:window.print(); 但是這里只調(diào)用了一次,取消打印之后不再彈出,而且打印的內(nèi)容也不符合我的要求
printDiv3() {
console.log("觸發(fā)---");
const printContents = this.$refs.printSection.innerHTML;
const originalContents = document.body.innerHTML;
document.body.innerHTML = printContents;
window.print();
this.$nextTick(() => {
document.body.innerHTML = originalContents;
});
}效果圖:

二.用 iframe 來打印
printDiv2() {
const printContent = document.querySelector("#pdfDom").innerHTML;
const iframe = document.createElement("iframe");
iframe.setAttribute("style", "position: absolute; width: 0; height: 0;");
document.body.appendChild(iframe);
const iframeDoc = iframe.contentWindow.document;
// 設(shè)置打印展示方式 - 橫向展示
iframeDoc.write('<style media="print">@page {size: landscape;}</style>');
// 向 iframe 中注入 printContent 樣式
iframeDoc.write(
`<link href="./print.css" rel="external nofollow" media="print" rel="stylesheet" />`
);
// 寫入內(nèi)容
iframeDoc.write("<div>" + printContent + "</div>");
setTimeout(function () {
iframe.contentWindow.print();
document.body.removeChild(iframe);
}, 50);
},樣式?jīng)]研究,打印出來的效果圖如下,連邊框線也沒加,更別說需要算哪里的背景色,字體居中和靠左了

三.JsPDF 插件
用 JsPDF 插件來導(dǎo)出pdf (配合插件html2canvas) 導(dǎo)出的第二頁也會(huì)被截?cái)啵瑯邮轿乙矝]有研究,感興趣的朋友可以研究下,文件:htmlToPdf.js
// 導(dǎo)出頁面為PDF格式
import html2Canvas from 'html2canvas'
import JsPDF from 'jspdf'
export default {
install(Vue, options) {
Vue.prototype.getPdf = function (flieName) {
// 當(dāng)下載pdf時(shí),若不在頁面頂部會(huì)造成PDF樣式不對(duì),所以先回到頁面頂部再下載
let top = document.getElementById('pdfDom');
if (top != null) {
top.scrollIntoView();
top = null;
}
let title = flieName;
html2Canvas(document.querySelector('#pdfDom'), {
allowTaint: true
}).then(function (canvas) {
// 獲取canvas畫布的寬高
let contentWidth = canvas.width;
let contentHeight = canvas.height;
// 一頁pdf顯示html頁面生成的canvas高度;
let pageHeight = contentWidth / 841.89 * 592.28;
// 未生成pdf的html頁面高度
let leftHeight = contentHeight;
// 頁面偏移
let position = 0;
// html頁面生成的canvas在pdf中圖片的寬高(本例為:橫向a4紙[841.89,592.28],縱向需調(diào)換尺寸)
let imgWidth = 841.89;
let imgHeight = 841.89 / contentWidth * contentHeight;
// console.log('pdf寬-高-pageHeight--imgHeight',contentWidth,contentHeight,pageHeight,imgHeight);
let pageData = canvas.toDataURL('image/jpeg', 1.0);
let PDF = new JsPDF('l', 'pt', 'a4');
// 兩個(gè)高度需要區(qū)分: 一個(gè)是html頁面的實(shí)際高度,和生成pdf的頁面高度
// 當(dāng)內(nèi)容未超過pdf一頁顯示的范圍,無需分頁
if (leftHeight < pageHeight) {
PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)
} else {
while (leftHeight > 0) {
PDF.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight)
leftHeight -= pageHeight;
position -= 592.28;
// 避免添加空白頁
if (leftHeight > 0) {
PDF.addPage();
}
}
}
PDF.save(title + '.pdf')
})
}
}
}引入和使用(這里是全局注冊(cè)了的)
main.js
import htmlToPdf from '@/utils/htmlToPdf'; Vue.use(htmlToPdf);
頁面使用該組件:
<el-col :span="1.5">
<el-button type="primary" plain icon="" size="mini" @click="printDiv" >打印</el-button>
</el-col>
printDiv() {
this.$nextTick(() => {
this.getPdf(this.exportPDFtitle);
});
},
四. pdfmake 導(dǎo)出表格
pdfMake 文檔:
先解決下載安裝問題:(版本:"pdfmake": "^0.2.10",我是直接下載默認(rèn)最新的版本)
npm install pdfmake --save
下載字體和導(dǎo)入字體:
找到 node_modules 的pdfmake (下載的插件) 文件,新建 examples/fonts文件夾,導(dǎo)入需要的字體,這里的字體需要去找對(duì)應(yīng)的字體文件,我的字體是本機(jī)電腦找的,我在我的C盤找到一些,這里用的是 仿宋


把這個(gè)文件復(fù)制,下載,放到下面examples/fonts文件夾下,復(fù)制出來的名字是大寫,我改為:simfang.ttf 小寫

控制臺(tái)定位到pdfmake文件夾下,運(yùn)行命令:node build-vfs.js "./examples/fonts"

這樣就算完成了(可以導(dǎo)入多種字體,我這里沒有字體要求,只用了仿宋)
前置問題和解決方案:pdfmake 要求的表格的內(nèi)容傳入需要時(shí)一個(gè)個(gè)的對(duì)象數(shù)組,如下圖:(紅色框是我完成需求用到的一些屬性)
要解決的問題:
1). 之前嵌套的數(shù)組(樹結(jié)構(gòu))要導(dǎo)出為一維數(shù)組(我發(fā)現(xiàn)導(dǎo)出的表格只需要4列,而且是固定的,只需要給每一條的數(shù)據(jù)固定4個(gè)屬性就可以,表頭也可以單獨(dú)寫,非常方便)
下面是嵌套的數(shù)組轉(zhuǎn)為一維數(shù)組的代碼(使用遞歸)
//嵌套的多維數(shù)組 生成 一維數(shù)組
flattenData(staffJieyuList) {
// 初始化一維數(shù)組
const flattenedData = [];
// 遞歸函數(shù),處理對(duì)象及其子數(shù)組
function processObject(obj) {
const flattenedObj = {
id: obj.id || "", // 如果 id 不存在,則用空字符串填充
name: obj.name || "", // 如果 name 不存在,則用空字符串填充
amount: obj.amount || "", // 如果 amount 不存在,則用空字符串填充
contractNum: obj.contractNum || "", // 如果 contractNum 不存在,則用空字符串填充
};
// 將當(dāng)前對(duì)象添加到一維數(shù)組中
flattenedData.push(flattenedObj);
// 處理子數(shù)組
if (obj.children && obj.children.length > 0) {
obj.children.forEach((child) => {
processObject(child);
});
}
}
// 遍歷主列表中的每個(gè)對(duì)象
staffJieyuList.forEach((item) => {
processObject(item);
});
// 返回轉(zhuǎn)換后的一維數(shù)組
return flattenedData;
},當(dāng)然上面的代碼可以拆分為兩個(gè)函數(shù):(函數(shù)都在methods: {} 中)
// 輔助函數(shù),處理對(duì)象及其子數(shù)組
processObject(obj, flattenedData) {
const flattenedObj = {
id: obj.id || "",
name: obj.name || "",
amount: obj.amount || "",
contractNum: obj.contractNum || "",
};
// 將當(dāng)前對(duì)象添加到一維數(shù)組中
flattenedData.push(flattenedObj);
// 處理子數(shù)組
if (obj.children && obj.children.length > 0) {
obj.children.forEach((child) => {
this.processObject(child, flattenedData);
});
}
}
// 調(diào)用輔助函數(shù)來生成一維數(shù)組
flattenDataWithHelper(staffJieyuList) {
// 初始化一維數(shù)組
const flattenedData = [];
// 遍歷主列表中的每個(gè)對(duì)象
staffJieyuList.forEach((item) => {
this.processObject(item, flattenedData);
});
// 返回轉(zhuǎn)換后的一維數(shù)組
return flattenedData;
}2).表頭是3行,直接用下圖的屬性 headerRows: 3, 表頭占一行,可以直接:colSpan: 4,其余的列用{} 來占位
[{ text: 我是表頭內(nèi)容, colSpan: 4, }, {}, {}, {}],
3).顏色,背景色,文字顯示位置,需要 alignment: 'center', fillColor: '#91aadf' 等屬性

頁面使用:(全局跟上面一樣引入,這里是單獨(dú)頁面使用)
import pdfMake from "pdfmake/build/pdfmake";
import pdfFonts from "pdfmake/build/vfs_fonts";
pdfMake.fonts = {
simfang: {
normal: "simfang.ttf",
bold: "simfang.ttf",
italics: "simfang.ttf",
bolditalics: "simfang.ttf",
},
};
pdfMake.vfs = pdfFonts.pdfMake.vfs;打印按鈕:
<el-col :span="1.5"> <el-button type="primary" plain icon="" size="mini" @click="printDiv" >打印PDF</el-button> </el-col>
下面附上完整的pdfmake 導(dǎo)出表格的代碼:
printDiv() {
// 調(diào)用函數(shù),獲得所需的一維數(shù)組
const flattenedArray = this.flattenData(this.staffJieyuList);
console.log("flattenedArray----", flattenedArray);
var fileName = this.exportPDFtitle + ".pdf";
// 匹配大寫字母、大寫阿拉伯?dāng)?shù)字或者特定的中文字符的正則表達(dá)式
const pattern = /^(附|[A-Z]|[一二三四五六七八九十])+$/;
//除了1-3行,還有下面匹配的這些 文字居中,項(xiàng)目這里列的其他內(nèi)容要往左
const pattern2 = /^([一二三四五六七八九十])+$/
const docDefinition = {
content: [
{
table: {
headerRows: 3,
widths: [50, "auto", 60, "auto"],
body: [
[{ text: this.subTitle, colSpan: 4, alignment: 'center', fillColor: '#91aadf'}, {}, {}, {}],
[{ text: this.searchInformation.lysuoName, colSpan: 4, alignment: 'left', fillColor: '#91aadf'}, {}, {}, {}],
[
{ text: "序號(hào)", alignment: 'center', fillColor: '#91aadf' },
{ text: "項(xiàng)目", alignment: 'center', fillColor: '#91aadf' },
{ text: "金額", alignment: 'center', fillColor: '#91aadf' },
{ text: "備注", alignment: 'center', fillColor: '#91aadf' },
],
...flattenedArray.map((item) => {
const backgroundColor =
pattern.test(item.id) ? "#d9e1f4" : "#ffffff";
const fontPosition =
pattern2.test(item.id) ? "center" : "left"
return [
{ text: item.id, fillColor: backgroundColor, alignment: 'center', },
{ text: item.name, fillColor: backgroundColor, alignment: fontPosition, },
{ text: item.amount, fillColor: backgroundColor, alignment: 'center', },
{ text: item.contractNum, fillColor: backgroundColor, alignment: 'center', },
];
}),
],
},
},
],
defaultStyle: {
font: "simfang",
},
};
pdfMake.createPdf(docDefinition).download(fileName);
},效果:


總結(jié)
用pdfmake插件制作pdf確實(shí)方便,而且樣式等配置也比較齊全,(上面寫的不好的地方歡歡迎指正,相互討論學(xué)習(xí))純前端用XLSX和XLSXS制作導(dǎo)出excel 之前沒有記錄,有空再做記錄
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
詳解Vue的數(shù)據(jù)及事件綁定和filter過濾器
這篇文章主要為大家介紹了Vue的數(shù)據(jù)及事件綁定和filter過濾器,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-01-01
Vue項(xiàng)目markdown內(nèi)容預(yù)覽顯示優(yōu)化方式
在Vue項(xiàng)目中使用markdown-it組件預(yù)覽Markdown內(nèi)容時(shí),通過安裝pnpm和markdown-it-github-markdown-css依賴,并重寫markdown部分樣式,實(shí)現(xiàn)了Markdown內(nèi)容的美化,在MarkdownPreview.vue組件中,通過變量renderedMarkdown接收并渲染Markdown內(nèi)容2025-10-10
基于Vue實(shí)現(xiàn)封裝一個(gè)虛擬列表組件
正常情況下,我們對(duì)于數(shù)據(jù)都會(huì)分頁加載,最近項(xiàng)目中確實(shí)遇到了不能分頁的場(chǎng)景,如果不分頁,頁面渲染幾千條數(shù)據(jù)就會(huì)感知到卡頓,使用虛擬列表就勢(shì)在必行了。本文主要介紹了如何基于Vue實(shí)現(xiàn)封裝一個(gè)虛擬列表組件,感興趣的可以了解一下2023-03-03
vue2.0 better-scroll 實(shí)現(xiàn)移動(dòng)端滑動(dòng)的示例代碼
本篇文章主要介紹了vue2.0 better-scroll 實(shí)現(xiàn)移動(dòng)端滑動(dòng)的示例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2018-01-01

