欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 社会 > 前端vue2-完全前端生成pdf->pdf-lib,html2canvas+jspdf,原生打印,三种方式(打印带有echarts图的pdf)

前端vue2-完全前端生成pdf->pdf-lib,html2canvas+jspdf,原生打印,三种方式(打印带有echarts图的pdf)

2025/5/24 9:34:54 来源:https://blog.csdn.net/Shi_haoliu/article/details/148157624  浏览:    关键词:前端vue2-完全前端生成pdf->pdf-lib,html2canvas+jspdf,原生打印,三种方式(打印带有echarts图的pdf)

pdf-lib:优点是可以控制输出内容,缺点是麻烦

html2canvas:优点是直接把html页面转成图片之后插入pdf很方便,不用过多的代码,缺点是不好控制图片大小,容易被戒断,可以把想打印的内容藏在页面外面(postion:absoult;left:-9999)

原生打印:原生浏览器打印,完全无法控制

pdfkit:尝试了一下pdfkit,一直在报错,不好用目前只有0.12.1版本能打印,但是echarts好像也放不上去,格式要求很多(字体,图片)

依赖下载

npm包下载

npm install pdf-lib
npm install jspdf
npm install html2canvas
npm install echarts
npm install @pdf-lib/fontkit

字体下载(pdf-lib使用,ttf的会用不了,得用otf的格式,第二个链接)

https://github.com/adobe-fonts/source-han-sans/tree/release/OTC
https://github.com/adobe-fonts/source-han-sans/tree/release/SubsetOTF/CN

<template><div class="pdf-container"><el-button type="primary" @click="downloadPdf">导出 PDF</el-button><el-button type="primary" @click="nativePrint">原生打印</el-button><el-button type="primary" @click="downloadPdfLib">导出 PDF(PDFKit)</el-button><div ref="pdfContent" class="pdf-content"><div class="pdf-page"><div>测试</div><div>测试</div><div>测试。</div><div ref="chartRef" id="chart" style="width: 600px; height: 280px" /><div>测试测试测试测试测试测试。</div><div>测试测试测试测试测试测试。</div><div>测试测试测试测试测试测试。</div><div ref="chartRef2" id="chart" style="width: 600px; height: 280px" /></div><div class="pdf-page"><div>测试测试测试测试测试测试。</div><div>测试测试测试测试测试测试。</div><div ref="chartRef3" id="chart" style="width: 600px; height: 280px" /><div>测试测试测试测试测试测试。</div><div>测试测试测试测试测试测试。</div><div>测试测试测试测试测试测试。</div><div ref="chartRef4" id="chart" style="width: 600px; height: 280px" /></div><div class="pdf-page"><div>(测试测试测试测试测试测试。</div><div>测试测试测试测试测试测试。</div><div ref="chartRef5" id="chart" style="width: 600px; height: 280px" /></div></div></div>
</template><script>
import html2canvas from "html2canvas";
import jsPDF from "jspdf";
// import PDFDocument from "pdfkit"; // 新增
import "@fontsource/noto-sans-sc";
// const PDFDocument = require("pdfkit/js/pdfkit.standalone");
export default {mounted() {this.initChart();},methods: {initChart() {const chart = this.$echarts.init(this.$refs.chartRef, "", {renderer: "svg", // 强制使用 SVGdevicePixelRatio: window.devicePixelRatio || 1, // 通常设为 2(Retina 屏)或更高,这样在页面上图片不会变糊了});chart.setOption({title: { text: "示例 ECharts 图表" },tooltip: {},xAxis: { data: ["A", "B", "C", "D", "E"] },yAxis: {},series: [{name: "数量",type: "bar",data: [5, 20, 36, 10, 10],},],});const chart2 = this.$echarts.init(this.$refs.chartRef2, "", {renderer: "svg", // 强制使用 SVGdevicePixelRatio: window.devicePixelRatio || 1, // 通常设为 2(Retina 屏)或更高,这样在页面上图片不会变糊了});chart2.setOption({title: { text: "示例 ECharts 图表" },tooltip: {},xAxis: { data: ["A", "B", "C", "D", "E"] },yAxis: {},series: [{name: "数量",type: "bar",data: [5, 20, 36, 10, 10],},],});const chart3 = this.$echarts.init(this.$refs.chartRef3, "", {renderer: "svg", // 强制使用 SVGdevicePixelRatio: window.devicePixelRatio || 1, // 通常设为 2(Retina 屏)或更高,这样在页面上图片不会变糊了});chart3.setOption({title: { text: "示例 ECharts 图表" },tooltip: {},xAxis: { data: ["A", "B", "C", "D", "E"] },yAxis: {},series: [{name: "数量",type: "bar",data: [5, 20, 36, 10, 10],},],});const chart4 = this.$echarts.init(this.$refs.chartRef4, "", {renderer: "svg", // 强制使用 SVGdevicePixelRatio: window.devicePixelRatio || 1, // 通常设为 2(Retina 屏)或更高,这样在页面上图片不会变糊了});chart4.setOption({title: { text: "示例 ECharts 图表" },tooltip: {},xAxis: { data: ["A", "B", "C", "D", "E"] },yAxis: {},series: [{name: "数量",type: "bar",data: [5, 20, 36, 10, 10],},],});const chart5 = this.$echarts.init(this.$refs.chartRef5, "", {renderer: "svg", // 强制使用 SVGdevicePixelRatio: window.devicePixelRatio || 1, // 通常设为 2(Retina 屏)或更高,这样在页面上图片不会变糊了});chart5.setOption({title: { text: "示例 ECharts 图表" },tooltip: {},xAxis: { data: ["A", "B", "C", "D", "E"] },yAxis: {},series: [{name: "数量",type: "bar",data: [5, 20, 36, 10, 10],},],});},downloadPdf() {const content = this.$refs.pdfContent;// 设置 A4 页面尺寸(宽度 210mm, 高度 297mm),以 pt 为单位(1mm ≈ 2.8346pt)// 使用 html2canvas 渲染时保持原始内容html2canvas(content, {allowTaint: false, // 是否允许跨域图像。会污染画布,导致无法使用canvas.toDataURL 方法backgroundColor: "#fff", // 画布背景色(如果未在DOM中指定)。设置null为透明useCORS: true, // 是否尝试使用CORS从服务器加载图像scale: 2, // 300 DPI = 3 × 96 DPIdpi: 300,}).then((canvas) => {const imgData = canvas.toDataURL("image/png");// 创建 jsPDF 实例并设置页面尺寸为 A4const pdf = new jsPDF("p", "px", "a4"); // 直接修改页面宽高// 获取页面宽高const pageWidth = pdf.internal.pageSize.getWidth();const pageHeight = pdf.internal.pageSize.getHeight();const imgWidth = pageWidth; // 设置图片宽度为 PDF 页面宽度const imgHeight = (canvas.height * imgWidth) / canvas.width;console.log(canvas, "canvas");console.log(pdf.internal.pageSize, "pdf.internal.pageSize");console.log(pageHeight, "pageHeight");console.log(imgHeight, "imgHeight");let heightLeft = imgHeight;let position = 0;let pageNum = 1;const totalPages = Math.ceil(imgHeight / pageHeight);while (heightLeft > 0) {if (pageNum > 1) pdf.addPage();// 关键修复:跳过微小残余高度if (heightLeft < 5) break;const posY = -(pageHeight * (pageNum - 1));pdf.addImage(imgData, "PNG", 0, posY, imgWidth, imgHeight);// 页码(保持原样)pdf.setFontSize(10);pdf.setTextColor(150);pdf.text(`${pageNum}/${totalPages}`, pageWidth / 2, pageHeight - 20, {align: "center",});heightLeft -= pageHeight;pageNum++;}pdf.save("export.pdf");});},nativePrint() {// 1. 克隆打印内容(避免影响原DOM)const printContent = this.$refs.pdfContent.cloneNode(true);// 2. 创建隐藏的打印容器const printContainer = document.createElement("div");printContainer.id = "print-container";printContainer.appendChild(printContent);// 3. 添加到DOM(隐藏状态)document.body.appendChild(printContainer);// 4. 调用打印window.print();// 5. 打印后移除临时容器document.body.removeChild(printContainer);},async downloadPdfLib() {const { PDFDocument, rgb } = await import("pdf-lib");const fontkit = await import("@pdf-lib/fontkit");const pdfDoc = await PDFDocument.create();pdfDoc.registerFontkit(fontkit.default);const fontBytes = await fetch("/SourceHanSansCN-Regular.otf").then((res) => res.arrayBuffer());const customFont = await pdfDoc.embedFont(fontBytes);const fontBytesBlod = await fetch("/SourceHanSansCN-Bold.otf").then((res) => res.arrayBuffer());const customFontBlod = await pdfDoc.embedFont(fontBytesBlod);const pageWidth = 595.28;const pageHeight = 841.89;const marginLeft = 50;const marginTop = pageHeight - 50;const fontSize = 12;const lineHeight = fontSize * 1.5;const maxTextWidth = pageWidth - marginLeft * 2;function splitTextToLines(text, font, size, maxWidth) {let lines = [];let currentLine = "";for (let i = 0; i < text.length; i++) {const testLine = currentLine + text[i];const width = font.widthOfTextAtSize(testLine, size);if (width < maxWidth) {currentLine = testLine;} else {if (currentLine) {lines.push(currentLine);}currentLine = text[i];}}if (currentLine) {lines.push(currentLine);}return lines;}function drawTextBlock(page,text,x,yStart,alignCenter = false,options = {}) {const {fontSize = 12,lineHeight = fontSize * 1.5,color = rgb(0, 0, 0),font = customFont,} = options;const lines = splitTextToLines(text, font, fontSize, maxTextWidth);let y = yStart;for (const line of lines) {let drawX = x;if (alignCenter) {const textWidth = font.widthOfTextAtSize(line, fontSize);drawX = (pageWidth - textWidth) / 2;}page.drawText(line, {x: drawX,y,size: fontSize,font,color,});y -= lineHeight;}return y;}function addPageNumber(page, pageNumber, totalPages) {const fontSize = 10;const margin = 20;const pageNumberText = `${pageNumber} / ${totalPages}`;const textWidth = customFont.widthOfTextAtSize(pageNumberText,fontSize);page.drawText(pageNumberText, {x: (pageWidth - textWidth) / 2,y: margin,size: fontSize,font: customFont,color: rgb(0, 0, 0),});}let _this = this;async function drawImage(page, base64Img, x, y, width, height) {const canvas = await _this.convertSvgToCanvas(base64Img, 3);const imgData = canvas.toDataURL("image/png");const imgBytes = await fetch(imgData).then((res) => res.arrayBuffer());const pngImage = await pdfDoc.embedPng(imgBytes);page.drawImage(pngImage, {x,y: y - height,width,height,});}const generateDirectory = true;let y = 0;let directoryPage;if (generateDirectory) {directoryPage = pdfDoc.addPage([pageWidth, pageHeight]);let y = pageHeight - 100;directoryPage.drawText("目录", {x: marginLeft,y: y,size: 16,font: customFont,color: rgb(0, 0, 0),});y -= lineHeight;directoryPage.drawText("一、测试测试测试测试测试测试。 .......................................... 1",{x: marginLeft,y: y,size: fontSize,font: customFont,color: rgb(0, 0, 0),});y -= lineHeight;directoryPage.drawText("二、测试测试测试测试测试测试。 .......................................... 2",{x: marginLeft,y: y,size: fontSize,font: customFont,color: rgb(0, 0, 0),});}const page1 = pdfDoc.addPage([pageWidth, pageHeight]);y = marginTop;y = drawTextBlock(page1,"测试测试测试测试测试测试。",marginLeft,y,true,{ fontSize: 16, font: customFontBlod });y -= lineHeight / 2;y = drawTextBlock(page1, "一、贸易总体情况", marginLeft, y, false, {fontSize: 14,});y -= lineHeight / 2;y = drawTextBlock(page1,"    测试测试测试测试测试测试。",marginLeft,y);const chart1 = this.$echarts.getInstanceByDom(this.$refs.chartRef);const svgData1 = chart1.getDataURL({ type: "svg" });y -= lineHeight * 1.5;await drawImage(page1, svgData1, marginLeft, y, 495, 250);y -= 250 + lineHeight;y = drawTextBlock(page1, "二、产品结构情况", marginLeft, y, false, {fontSize: 14,});y -= lineHeight / 2;y = drawTextBlock(page1, "(一)出口商品结构", marginLeft, y);y -= lineHeight / 2;y = drawTextBlock(page1,"测试测试测试测试测试测试。",marginLeft,y);y -= lineHeight * 1.5;const chart2 = this.$echarts.getInstanceByDom(this.$refs.chartRef2);const svgData2 = chart2.getDataURL({ type: "svg" });await drawImage(page1, svgData2, marginLeft, y, 495, 250);addPageNumber(page1, 1, 3);const page2 = pdfDoc.addPage([pageWidth, pageHeight]);y = marginTop;y = drawTextBlock(page2, "(二)进口商品结构", marginLeft, y);y -= lineHeight / 2;y = drawTextBlock(page2,"测试测试测试测试测试测试。",marginLeft,y);const chart3 = this.$echarts.getInstanceByDom(this.$refs.chartRef3);const svgData3 = chart3.getDataURL({ type: "svg" });y -= lineHeight * 1.5;await drawImage(page2, svgData3, marginLeft, y, 495, 250);y -= 250 + lineHeight;y = drawTextBlock(page2, "测试测试测试测试测试测试。", marginLeft, y, false, {fontSize: 14,});y -= lineHeight / 2;y = drawTextBlock(page2, "测试测试测试测试测试测试。", marginLeft, y);y -= lineHeight / 2;y = drawTextBlock(page2,"测试测试测试测试测试测试。",marginLeft,y,false,{fontSize: 12,color: rgb(0.2, 0.2, 0.6),lineHeight: 20,});const chart4 = this.$echarts.getInstanceByDom(this.$refs.chartRef4);const svgData4 = chart4.getDataURL({ type: "svg" });y -= lineHeight * 1.5;await drawImage(page2, svgData4, marginLeft, y, 495, 250);addPageNumber(page2, 2, 3);const page3 = pdfDoc.addPage([pageWidth, pageHeight]);y = marginTop;y = drawTextBlock(page3, "(二)进口商品结构", marginLeft, y);y -= lineHeight / 2;y = drawTextBlock(page3,"测试测试测试测试测试测试。",marginLeft,y);const chart5 = this.$echarts.getInstanceByDom(this.$refs.chartRef5);const svgData5 = chart5.getDataURL({ type: "svg" });y -= lineHeight * 1.5;await drawImage(page3, svgData5, marginLeft, y, 495, 250);addPageNumber(page3, 3, 3);const pdfBytes = await pdfDoc.save();const blob = new Blob([pdfBytes], { type: "application/pdf" });const link = document.createElement("a");link.href = URL.createObjectURL(blob);link.download = "测试测试测试测试测试测试.pdf";link.click();},// 将文本分割为多行splitTextIntoLines(text, maxWidth, font, fontSize) {const lines = [];let line = "";for (let i = 0; i < text.length; i++) {const testLine = line + text[i];const width = font.widthOfTextAtSize(testLine, fontSize);if (width < maxWidth) {line = testLine;} else {lines.push(line);line = text[i];}}if (line) lines.push(line);return lines;},// 使用 html2canvas 将 SVG 转换为 CanvasconvertSvgToCanvas(svgData, scale = 4) {return new Promise((resolve, reject) => {const img = new Image();img.src = svgData;img.onload = () => {const canvas = document.createElement("canvas");const ctx = canvas.getContext("2d");// 设置canvas尺寸,使用scale来增加渲染分辨率canvas.width = img.width * scale;canvas.height = img.height * scale;ctx.scale(scale, scale); // 拉伸canvas,提升图像分辨率ctx.drawImage(img, 0, 0);resolve(canvas);};img.onerror = reject;});},},
};
</script><style scoped>
.pdf-container {background: white;padding: 20px;
}
.pdf-content {width: 794px;background: #fff;/* padding: 20px; *//* margin-top: 20px; *//* border: 1px solid #eee; */
}
.pdf-page {height: 1123px;/* border: 1px solid #eee; *//* margin: 10px 0; */padding: 20px;
}
.pdf-page:last-child {height: 1100px;
}
</style>

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词