vue 打印和pdf下载

144 阅读3分钟

html-> pdf -> 下载

方式一: 图片 -> pdf

html 转 canvas, 使用html2canvas插件 canvas 转pdf, 使用jspdf插件,pdf 不可以文本选择和复制文本的, pdf 是由图片 生成的pdf

预览pdf, word功能,可以使用 blob -> 本地url -> 浏览器打开

1.html2canvas 使用克隆节点,达到更改html样式。

2.useCORS: true 允许加载在线图片

3.scale: 3 canvas 抗锯齿,放大更清晰

  1. PDF.output('blob') 生成blob, blob转本地url, 可以达到浏览器打开生成的pdf
  2. PDF.save(fileName)就是 将生成的pdf下载
import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';


/**
 * 导出 PDF 
 */
export const handleHtmlToPDF = (eleId, fileName) => {
    const A4_WIDTH = 592.28;
    const A4_HEIGHT = 841.89;

	/*
	// 克隆需要导出的节点dom,并添加到body
	const printDom = document.getElementById(eleId).cloneNode(true);
	
	printDom.setAttribute(
	    'style',
	    'font-size: 26px;font-weight: 700;'
	);
	
	document.body.appendChild(printDom)
	
	// 生成完毕删除克隆的节点
	 document.body.removeChild(printDom)
	*/
    const printDom = document.getElementById(eleId)
	

    // 获取目标元素的高度(去除滚动条时高度)
    const domScrollHeight = printDom.scrollHeight;
    const domScrollWidth = printDom.scrollWidth;
    // 根据 A4 的宽高计算 dom 页面一页应该对应的高度
    const pageHeight = (printDom.offsetWidth / A4_WIDTH) * A4_HEIGHT;

    // // 将所有不允许被截断的元素进行处理
    // const wholeNodes = document.querySelectorAll('.whole-node');

    // // 添加空白块的总高度
    // let allEmptyNodeHeight = 0;

    // for (let i = 0; i < wholeNodes.length; i++) {
    //     // 判断当前的不可分页元素是否在两页显示
    //     const topPageNum = Math.ceil(wholeNodes[i].offsetTop / pageHeight);
    //     const bottomPageNum = Math.ceil(
    //         (wholeNodes[i].offsetTop + wholeNodes[i].offsetHeight) / pageHeight,
    //     );

    //     // 说明该 dom 会被截断
    //     if (topPageNum !== bottomPageNum) {
    //         // 插入空白块使被截断元素下移
    //         const divParent = wholeNodes[i].parentNode;
    //         const newBlock = document.createElement('div');
    //         newBlock.className = 'empty-node';
    //         newBlock.style.background = '#fff';

    //         // 计算插入空白块的高度,可以适当留出空间使得内容不会太靠边,根据自己需求而定
    //         const _H = topPageNum * pageHeight - wholeNodes[i].offsetTop;
    //         newBlock.style.height = _H + 50 + 'px';
    //         divParent.insertBefore(newBlock, wholeNodes[i]);

    //         // 更新插入空白块的总高度
    //         allEmptyNodeHeight = allEmptyNodeHeight + _H + 50;
    //     }
    // }

    // // 设置打印区域的高度 (目标元素的高度 + 添加的空白块的高度)
    // printDom.setAttribute(
    //     'style',
    //     `height: ${domScrollHeight + allEmptyNodeHeight}px; width: ${domScrollWidth}px;`,
    // );


    // 以上完成 dom 层面的分页,可以转为图片进一步处理了
    return html2canvas(printDom, {
        height: printDom.offsetHeight,
        width: printDom.offsetWidth,
        allowTaint: false,
        useCORS: true, //允许canvas画布内 可以跨域请求外部链接图片, 允许跨域请求
        scale: 3,
    }).then((canvas) => {
        // // dom 已经转换为 canvas 对象,可以将插入的空白块删除了
        // const emptyNodes: any = document.querySelectorAll('.empty-node');
        // for (let i = 0; i < emptyNodes.length; i++) {
        //     emptyNodes[i].style.height = 0;
        //     emptyNodes[i].parentNode.removeChild(emptyNodes[i]);
        // }
		

        const contentWidth = canvas.width;
        const contentHeight = canvas.height;

        // 生成 pdf 的页面高度;
        const pageHeight = (contentWidth / A4_WIDTH) * A4_HEIGHT;

        // html 页面实际高度
        let htmlHeight = contentHeight;

        // 页面偏移量
        let position = 0;

        // html 页面生成的 canvas 在 pdf 中图片的宽高
        const imgWidth = A4_WIDTH;
        const imgHeight = (A4_WIDTH / contentWidth) * contentHeight;

        // 将图片转为 base64 格式
        const pageData = canvas.toDataURL('image/jpeg', 1.0);

        // 计算分页的 pdf
        const PDF = new jsPDF('', 'pt', 'a4');

        // html 页面的实际高度小于生成 pdf 的页面高度时,即内容未超过 pdf 一页显示的范围,无需分页
        if (htmlHeight <= pageHeight) {
            PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight);
        } else {
            while (htmlHeight > 0) {
                PDF.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight);
                htmlHeight -= pageHeight;
                position -= A4_HEIGHT;
                if (htmlHeight > 0) {
                    PDF.addPage();
                }
            }
        }

        PDF.save(fileName);
		// PDF.output('datauristring') 生成  base64 pdf
                //const link = window.URL.createObjectURL(PDF.output('blob'));
		// const myWindow = window.open(link)
    }).catch((error) => {
		console.log(error)
	})
}

// base64 转 blob
function toBlob(base64Data) {
  let byteString = base64Data
  if (base64Data.split(',')[0].indexOf('base64') >= 0) {
    byteString = atob(base64Data.split(',')[1]); // base64 解码
  } else {
    byteString = unescape(base64Data.split(',')[1]);
  }
  // 获取文件类型
  const mimeString = base64Data.split(';')[0].split(":")[1]; // mime类型

  // ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区
  // let arrayBuffer = new ArrayBuffer(byteString.length) // 创建缓冲数组
  // let uintArr = new Uint8Array(arrayBuffer) // 创建视图

  const uintArr = new Uint8Array(byteString.length); // 创建视图

  for (let i = 0; i < byteString.length; i += 1) {
    uintArr[i] = byteString.charCodeAt(i);
  }
  // 生成blob
  const blob = new Blob([uintArr], {
    type: mimeString
  })
  // 使用 Blob 创建一个指向类型化数组的URL, URL.createObjectURL是new Blob文件的方法,可以生成一个普通的url,可以直接使用,比如用在img.src上
  return blob;
}

方式二: 直接html -> pdf,可以复制和选择

jspdf 和 jspdf-autotable

  1. 导入字体,防止乱码。import '@/assets/simhei-normal.js',生成方法百度。pdf.setFont("simhei")
  2. jsPDF 不同字体,可以每一次都设置字体,颜色,位置。
  3. text 用来加载文本, addImage 加载图片, table 使用jspdf-autotable加载
  4. jspdf-autotable 属性设置时使用 html, 不要写多余的属性,否则会改变页面布局
  5. didDrawPage 生命周期回调,其他方法参看官网
import jsPDF from 'jspdf'
import 'jspdf-autotable'
import '@/assets/simhei-normal.js'
        
var pdf = new jsPDF("", "pt", 'a4');
pdf.setFont("simhei"); // 使用字体
pdf.setFontSize(12)
				
const {width, height} = pdf.internal.pageSize
				
let startY = 40
pdf.text('xxx', width / 2, startY, {
    align: 'center',
})
startY += 20
pdf.setFontSize(10)
				pdf.text('申请单号:xxx', 40, startY , {
					align: 'left',
				})
				pdf.text('提交时间:xxx', width - 40, startY, {
					align: 'right',
				})
				// autoTable调用 ,font:中修改为自定义字体名称,注意:fontStyle需要与addFont中的类型对应。
				pdf.autoTable({
					   html: '#meetTable',
				       styles: { 
						   fillColor: [255, 255, 255], 
						   font: 'simhei', 
						   fontStyle: 'normal', 
						   textColor: [0, 0, 0], 
						   halign: 'center', 
						   valign: 'middle', 
						}, // 表格样式
				       theme: 'grid', // 主题
					   didDrawPage: (HookData) => {
						   startY = HookData.cursor.y + 30
						   pdf.text('申请记录', width / 2, startY, {
							   align: 'center',
						   })
						},
				       startY: startY + 15, // 距离上边距离
				     })
				pdf.autoTable({ 
					   html: '#meetTableHistory',
				       styles: { 
						   fillColor: [255, 255, 255], 
						   font: 'simhei', 
						   fontStyle: 'normal', 
						   textColor: [0, 0, 0], 
						   halign: 'center', 
						   valign: 'middle', 
						}, // 表格样式
				       theme: 'grid', // 主题
					   didDrawPage: (HookData) => {
					   		startY = HookData.cursor.y + 20
					   		pdf.text('经办人确认签字', 45, startY, {
					   			align: 'left',
					   		})
					   	},
				       startY: startY + 10, // 距离上边距离
				     })
					 pdf.save("test.pdf")

打印

使用插件print-js,可以打印图片。也可以打印html, pdf, json

scanStyles: false 使用自己的css, 不使用自带的css样式

targetStyles:['*'] 打印内容使用所有HTML样式,没有设置这个属性/值,设置分页打印没有效果

代码 使用原生的代码 table , button.不能使用组件库封装代码。

将打印的内容,另存为pdf, 需要用户去选择,体验有点麻烦。pdf 可以文本选择和复制文本的

import print from 'print-js'
print({
			      	printable: 'attend-meet',
			     type: 'html',
					documentTitle: 'xxx',
					scanStyles: false,
					css: ['/static/print-style.css'],
			        targetStyles: ['*'], // 打印内容使用所有HTML样式,没有设置这个属性/值,设置分页打印没有效果
})

css样式 放在public/static 文件夹下

/* 默认A4并且无页眉页脚打印 */
@page {
  size:portrait A4;
  margin: 8mm;
}
/* page-break-after:always;   强制分页*/