Vue3+TS自定义指令系列-打印(v-print)

1,316 阅读2分钟

需求:一键打印页面上的内容

思路:只需要传入一个选择器获取就可以获取打印的内容,并呼出打印选择界面

实现:主要利用window.print()方法实现打印功能,只需要处理打印的内容以及格式问题

使用:可直接通过v-print="'#printData'"使用,也指定纸张宽度(默认采用A4纸宽度),例如:v-print.A5="'#printData'"

主要实现代码:

import type { Directive, DirectiveBinding } from 'vue';

/**
 *
 * @param el
 * @param binding
 *
 * v-print="#printData"  打印指令,绑定一个选择器 , 默认A4大小
 * v-print.A4="#printData" 可选纸张(A3、A4、A5、A6)
 */
// 纸张尺寸
const sizes = [
    { size: 'A3', width: '297mm' },
    { size: 'A4', width: '210mm' },
    { size: 'A5', width: '148mm' },
    { size: 'A6', width: '105mm' },
];
const vPrint: Directive = (el: HTMLElement, binding: DirectiveBinding) => {
    if (!binding.value) throw new Error('请绑定选择器');
    const innerHtml = document.querySelector(binding.value).innerHTML;
    el.addEventListener('click', () => {
        if (innerHtml) {
            let div: HTMLDivElement = document.createElement('div');
            div.style.width = sizes.find(s => s.size === Object.keys(binding.modifiers)[0]?.toUpperCase())?.width ?? '210mm';
            div.innerHTML = innerHtml;
            printHtml(div.outerHTML);
        }
    });
};
// 打印内容
const printHtml = (html: string) => {
    const style = setStyle();
    const container = setContainer(html);
    document.body.appendChild(style);
    document.body.appendChild(container);
    loadPromise(container).then(() => {
        window.print();
        document.body.removeChild(style);
        document.body.removeChild(container);
    });
};
// 设置样式
const setStyle = () => {
    const styleContent = `
    #print-container {
        display: none;
    }
    @media print {
        @page {
          margin: 5mm 0 0;
        }
        body > :not(.print-container) {
            display: none;
        }
        html,
        body {
            display: block;
            margin: 0 2mm;
        }
        #print-container {
            display: block;
        }
    }`;
    const style = document.createElement('style');
    style.innerHTML = styleContent;
    return style;
};
// 清空打印内容
const clearPrint = () => {
    const div = document.getElementById('print-container');
    div && document.querySelector('body')?.removeChild(div);
};
// 创建打印内容
const setContainer = (html: string) => {
    clearPrint();
    const container = document.createElement('div');
    container.setAttribute('id', 'print-container');
    container.innerHTML = html;
    return container;
};
// 处理图片加载
const loadPromise = (el: HTMLElement) => {
    const imgs: NodeListOf<HTMLImageElement> = el.querySelectorAll('img');
    if (imgs.length == 0) return Promise.resolve();
    let finishedCount: number = 0;
    return new Promise((resolve: Function, reject: Function) => {
        function check() {
            finishedCount++;
            if (finishedCount === imgs.length) resolve();
        }
        imgs.forEach(img => {
            // 不管加载成功还是失败,都表示加载完成
            img.addEventListener('load', check);
            img.addEventListener('error', check);
        });
    });
};

export default vPrint;

全局指令注册:

// 在 directives目录下 建立index.ts文件
import type { App, Directive } from 'vue';
import print from './vPrint';

interface Directives {
    print: Directive;
}

// 注意:使用时需要(v-)开头
const directives: Directives = { print };

export default {
    install(app: App) {
        Object.keys(directives).forEach(key => {
            app.directive(key, directives[key as keyof Directives]);
        });
    },
};


// 在main.ts中的操作如下
import { createApp } from 'vue';
import App from './App.vue';
import directives from './directives/index';

const app = createApp(App);
app.use(directives);

使用示例:

<el-button v-print="'#printData'">打印文章</el-button>
    <div id="printData">
        <article>
            曲曲折折的荷塘上面,弥望旳是田田的叶子。叶子出水很高,像亭亭旳舞女旳裙。层层的叶子中间,零星地点缀着些白花,有袅娜(niǎo,nuó)地开着旳,有羞涩地打着朵儿旳;正如一粒粒的明珠,又如碧天里的星星,又如刚出浴的美人。微风过处,送来缕缕清香,仿佛远处高楼上渺茫的歌声似的。这时候叶子与花也有一丝的颤动,像闪电般,霎时传过荷塘的那边去了。叶子本是肩并肩密密地挨着,这便宛然有了一道凝碧的波痕。叶子底下是脉脉(mò)的流水,遮住了,不能见一些颜色;而叶子却更见风致了
        </article>
        <img
            width="500"
            height="auto"
            style="object-fit: scale-down"
            src="https://ts1.cn.mm.bing.net/th/id/R-C.22cdb21060f95297035a380467123768?rik=ZZ5YeI3loyMu0w&riu=http%3a%2f%2fimg.pconline.com.cn%2fimages%2fupload%2fupc%2ftx%2fwallpaper%2f1307%2f10%2fc0%2f23166997_1373443405186.jpg&ehk=UuR5odrdPxwmlbjY0C5WGXyxSJdyN%2b1aYi%2bnwiG6DPQ%3d&risl=&pid=ImgRaw&r=0"
            alt=""
        />
    </div>

效果展示:

image.png 对自定义指令不了解的小伙伴可前往官网查看,如有其它问题可在评论区留言