Skip to content

前端表单填入打印

687字约2分钟

webgelcanvas

2022-12-07

背景需求

客户提供了给了一张模糊不清又十分混乱的打印示例模板,要求前端定制打印出这张表单,并要求把系统里的数据项填入到打✔的格子里。

需求分析

表单格式比较复杂,如果使用 HTML 进行排版会有工作量大、细节修改不便调整、不同浏览器或分辨率导致呈现效果达不到预期等等问题。 所以决定采取以下工序:

  1. 使用 Word 排版,可以快速高度还原版式,保留排版 docx 源文件。

  2. Word 导出为 PDF 文件。

  3. 使用 PHOTOSHOP 导入 PDF,分辨率设置为 300 以保证高保真打印效果,导出为标准 A4 纸张尺寸的图片文件,提供给用户,用户再次提出修改意见后修改排版完成定稿,以保证最终实现打印效果所见即所得。

  4. 前端将图片文件写入同尺寸的canvas,将字段数据填入canvas中正确的位置,再读取出为图片数据,执行打印。位置测量可以使用 PHOTOSHOP 的标尺工具,也可使用其他像素测量小工具,又或者找设计同事的协助测量。

前端实现通用方案

    
    // 创建 Image 对象
    this.paper = require('../../public/static/paper.jpg') // 模板图片文件
    this.paperImg = new Image()
    this.paperImg.setAttribute('crossOrigin', 'anonymous')
    this.paperImg.src = this.paper

    // 创建canvas画布绘入图像
    var canvas = document.createElement('canvas')
    canvas.width = this.paperImg.width
    canvas.height = this.paperImg.height
    const context = canvas.getContext('2d')
    context.drawImage(this.paperImg, 0, 0)

    // 在指定位置写入数据字段
    context.font = '38px Microsoft-YaHei'
    const textList = [
        {prop: 'name', x: '1816', y: '806'}
    ]
    textList.forEach(txt => context.fillText(data[txt.prop], txt.x, txt.y))
    const base64 = canvas.toDataURL('image/png')

这样就获取到了加入了数据变量的表单图像数据 base64,这个数据可以使用类似 printJS 的开源控件进行打印,但缺点是无法跳过浏览器的预览弹窗,如果用户需求是要求点击打印按钮后,不用任何弹窗直接开始打印,就需要依赖类似 lodop 的打印客户端插件,也需要注意一些三方插件的商用权是需要付费购买的。

不引用任何三方库也是可行的,通过虚拟框架窗口放入撑满的图片,再执行打印虚拟窗口即可:

function printImg(imgSrc) {
    let iframe = document.createElement('IFRAME')
    let doc = null
    iframe.setAttribute('class', 'print-iframe')
    iframe.setAttribute('style', 'position:absolute;width:0px;height:0px;left:-9999px;top:-9999px;')
    document.body.appendChild(iframe)
    doc = iframe.contentWindow.document
    doc.imageLoad = () => {
        iframe.contentWindow.print()
        if (navigator.userAgent.indexOf('MSIE') > 0) {
            document.body.removeChild(iframe)
        }
    }
    doc.write('<div style="height: 100%;width: 100%;">' + `<img src="${imgSrc}" style="max-height:100%;max-width: 100%;" onload="imageLoad()"/>` + '</div>')
    doc.close()
    iframe.contentWindow.focus()
}