前端局部打印(pdf分页)

技术:htmlcanvas和jspdf

可实现效果:不切割pdf

效果图:

<template>
    <div style="background: #f5f5f5;">
        <div class="flex-end button-wrap">
            <el-button
                :disabled="loading"
                @click="handlePrint">
                打印
            </el-button>
        </div>
        <div
            v-loading.body.fullscreen="loading"
            class="printForm"
        >
            <div id="form-main">
                <div style="margin-top:32px" />
                <template v-for="item in 25">
                    <div
                        :key="item"
                        class="item">
                        <div
                            class="line"
                            :style="{'min-height':item %2 ?'160px' : '40px'}">
                            <span style="color:red">{{ item }}</span>
                            <span> {{ deptLeaderSignTime }}{{ deptLeaderSignTime }}</span>
                            <div
                                v-if="item==11"
                                style="min-height:800px">
                                {{ deptLeaderSignTime }}{{ deptLeaderSignTime }}
                            </div>
                        </div>
                    </div>
                </template>
            </div>
        </div>
    </div>
</template>

<script>
import zhengli from "./zhengli.js";
export default {
    mixins: [zhengli],
    data() {
        return {
            isPrint: false,
            loading: false,
            deptLeaderSignTime:'广东省工业和信息化厅发文表广东省工业和信息化厅发文表广东省工业和信息化厅发文表广东省工业和信息化厅发文表'
        };
    },
    computed: {},
    async created() {
        this.loading = true
        this.$nextTick(async()=>{
            this.convertPdf("打印单", document.querySelector("#form-main"), false);
        });
    },
    async mounted() {
        window.addEventListener("afterprint", e => {
            location.reload();
        });
        window.addEventListener("message", (e) => {
            this.isPrint = false
            this.$forceUpdate()
        });

    },
    beforeDestroy() {
        window.removeEventListener("afterprint",'');
    },
    methods: {
    }
};
</script>
<style scoped>

.flex-end {
  display: flex;
  justify-content: flex-end;
}
.button-wrap {
    height: 60px;
    box-sizing: border-box;
    padding: 8px 24px;
    background: #fff;
}
#form-main {
    width: 1200px;
    margin: 0 auto;
    padding: 24px;
    box-sizing: border-box;
    position: relative;
    font-size: 16px;
    line-height: 24px;
}
.printForm {
    background: #ffff;
    margin: 16px 24px;
    height:calc(100vh - 100px);
    overflow:auto
}
.line {
    padding: 10px;
    border: 1px solid #000;
}

.item+.item .line{
    border-top: 0px solid transparent;
}
</style>
// zhengli.js

import html2Canvas from 'html2canvas';
import JsPDF from 'jspdf';
const zhengli = {
    data() {
        return {
            pdfFile: null,
            canvasHeight: 0,
            imgHeight:0,
            setPadingIndex: [], //由于分页导致需要增加padding的节点的序号,后续为了把padding改回去

        };
    },
    methods: {
        async convertPdf(title, html, isBlob) {
            // 2400是需要打印的html表单的宽度一半(我这里设置了1200px,这个值得根据具体情况修改),因为html2Canvas增加了分辨力一倍
            this.imgHeight = Math.floor((280 * 2400) / 180) - 30
            // res:这里记录最后一页的节点距离顶部的值,只要用到改数组的长度来决定是否需要新增一页pdf
            let res = []
            if( this.isPrint ) {
                res =this.setNodeStyle( )
            }
            return new Promise((resolve) => {
                html2Canvas(html, {
                    //allowTaint:是否允许图片跨域
                    allowTaint: false,
                    taintTest: false,
                    logging: false,
                    //useCORS:是否尝试从服务器中使用cors来加载图片
                    useCORS: true,
                    windowWidth: '820px',
                    // dpi:将分辨率提高到特定的DPI
                    dpi: window.devicePixelRatio * 2,
                    scale: 2 // 按比例增加分辨率
                }).then((canvas) => {
                    this.pdfFile = new JsPDF('p', 'mm', 'a4'); // A4纸,纵向
                    const ctx = canvas.getContext('2d');
                    const a4w = 180;
                    const a4h = 280;
                    // imgHeight:按A4显示比例换算一页图像的像素高度
                    const imgHeight =  this.imgHeight;
                    let top = canvas.height
                    let renderedHeight = 0;
                    res.forEach((item,index) =>{
                        const page = document.createElement('canvas');
                        page.width = canvas.width;
                        page.height  = Math.min(imgHeight, top - renderedHeight);
                        // 用getImageData剪裁指定区域,并画到前面创建的canvas对象中
                        let imageData = ctx.getImageData(
                            0,
                            renderedHeight,
                            canvas.width,
                            Math.min(imgHeight, top - renderedHeight)
                        )
                        for(var i = 0; i < imageData.data.length; i += 4) {
                            // 当该像素是透明的,则设置成白色
                            if(imageData.data[i + 3] == 0) {
                                imageData.data[i] = 255;
                                imageData.data[i + 1] = 255;
                                imageData.data[i + 2] = 255;
                                imageData.data[i + 3] = 255;
                            }
                        }
                        page.getContext('2d').putImageData(
                            imageData,
                            0,
                            0,
                        );
                        /* addImage:
                            1、image:表示要插入的图片资源,可以是图片文件的路径或者base64编码字符串。
                            2、format:表示要插入的图片格式,包括:‘JPEG’, ‘PNG’, ‘GIF’, ‘BMP’, ‘TIFF’, ‘RAW’, ‘JPEG2000’。
                            3、x:图片在PDF中的x轴坐标,单位为pt(点)。
                            4、y:图片在PDF中的y轴坐标,单位为pt(点)。
                            5、width:图片在PDF中的宽度,单位为pt(点)。
                            6、height:图片在PDF中的高度,单位为pt(点)。
                            7、alias(可选):指定图片资源的别名。
                            8、compression(可选):指定图片的压缩质量,取值为0-1之间的浮点数。
                            9、rotation(可选):指定图片的旋转角度,取值范围为0-360之间的整数。
                        */
                        this.pdfFile.addImage(
                            page.toDataURL('image/jpeg', 1),
                            'JPEG',
                            15,
                            10,
                            a4w,
                            Math.min(a4h, (a4w * page.height) / page.width)
                        );
                        renderedHeight += imgHeight
                        if(res[index+1]) {
                            this.pdfFile.addPage(); // 如果后面还有内容,添加一个空页
                        }
                    })

                    // 保存文件
                    if (isBlob) {
                        this.pdfFile.save(`${title}.pdf`);
                    }
                    // 获取 base64
                    const pdfData = this.pdfFile.output('datauristring');
                    this.loading = false
                    resolve(pdfData);
                });
            });
        },
        // 设置分页地方的边框和padding
        setNodeStyle() {
            let height = 0
            let data = []
            let count = 1
            // element: items是每一行的样式名,作为不可被切割的部分。
            let element = document.getElementsByClassName("item")
            this.setPadingIndex = []
            for(let i = 0; i<element.length;i++) {
                height = (element[i].offsetTop+element[i].offsetHeight)*2
                if( height >  this.imgHeight*count) {
                    let bott = (this.imgHeight*count -(element[i-1].offsetTop+element[i-1].offsetHeight)*2)/2
                    element[i-1].style.paddingBottom =  bott+15 +'px'
                    // 这里需要依照自己的样式设置需要增加边框的地方
                    element[i].style.borderTop=  '1px solid #000'
                    // setPadingIndex:记录这个是为了出现打印弹框之后能及时的把表单样式复原
                    this.setPadingIndex.push(i-1)
                    data.push((element[i-1].offsetTop+element[i-1].offsetHeight)*2)
                    count+=1
                    if( i == element.length-1 ) {
                        data.push((element[i].offsetTop+element[i].offsetHeight)*2)
                    }
                } else if( i == element.length-1 ) {
                    data.push((element[i].offsetTop+element[i].offsetHeight)*2)
                }
            }
            return data

        },
        async handlePrint() {
            this.isPrint = true
            this.loading = true
            this.$nextTick( async()=>{
                this.pdfFile = null
                // #form-main 是需要打印的html区域
                await this.convertPdf("打印单", document.querySelector("#form-main"), false)
                let pdfFlie = this.pdfFile.output('datauristring');
                let printFile = this.dataURLtoFile(pdfFlie, 'pdf文件.pdf');
                this.pdfUrl = window.URL.createObjectURL(new Blob([printFile], { type: 'application/pdf' }));
                var date = new Date().getTime();
                var ifr = document.createElement('iframe');
                ifr.style.frameborder = 'no';
                ifr.style.display = 'none';
                ifr.style.pageBreakBefore = 'always';
                ifr.setAttribute('id', 'printPdf' + date);
                ifr.setAttribute('name', 'printPdf' + date);
                ifr.src = this.pdfUrl;
                document.body.appendChild(ifr);
                this.loading = false
                // 打印弹窗
                this.doPrint('printPdf' + date);
                // 释放URL 对象
                window.URL.revokeObjectURL(ifr.src);
                setTimeout(()=>{
                    //把分页的位置复原回去
                    let element = document.getElementsByClassName("item")
                    for(let i = 0; i<element.length;i++) {
                        if(this.setPadingIndex.includes(i)) {
                            element[i].style.paddingBottom =  0
                            element[i+1].style.borderTop=  '0px solid #000'
                        }
                    }
                    this.isPrint = false
                    this.$forceUpdate()
                },1000)
            })
        },
        doPrint(val) {
            var ordonnance = document.getElementById(val).contentWindow;
            setTimeout(() => {
                this.isPrint = true
                ordonnance.print();
                this.pdfLoading = false;
                while(true) {
                    // 判断,找到要找的父window,可以通过在父的window上绑定属性来实现
                    if (ordonnance.isParent = true) {
                        ordonnance.postMessage('isPrint', "*")
                    }
                    if(ordonnance == window.top) {
                        break;// 防止死循环
                    } else {
                        ordonnance = ordonnance.parent;
                    }
                }

            }, 100);
        },
        // 转成文件流
        dataURLtoFile(dataurl, filename) {
            var arr = dataurl.split(','),
                mime = arr[0].match(/:(.*?);/)[1],
                bstr = atob(arr[1]),
                n = bstr.length,
                u8arr = new Uint8Array(n);
            while (n--) {
                u8arr[n] = bstr.charCodeAt(n);
            }

            return new File([u8arr], filename, { type: mime });
        },

    }
};
export default zhengli;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值