「VUE」前端实现真正的无感打印(可打印dom、file、url) CSDN首发

时间:2020-8-21 作者:admin


前言

功能

你好! 如果你是一名纯前端,那么阅读完这篇文章即可以解决无感打印的问题!
目前可以实现的功能有:

  1. 前端无任何弹窗直接调用打印机执行打印
  2. 打印pdf流、打印在线文件、调用斑马打印服务(斑马打印正在测试,已集成到当前版本,若要使用请留言)
  3. 设置纸张大小
  4. 可分页打印
  5. 输出打印日志
  6. 获取当前用户电脑外接的所有打印机
  7. 获取用户的默认打印机,同时可选择使用哪台打印机执行打印
  8. 附加功能:HTML转成pdf的file/blob流文件、生成分页pdf文件

说到前面

相信大家在搜到我的这篇文章前已经在网上搜索过大量前端无感打印的文章。

先谈谈目前前端常见的两种方法

  • 第一种 JS的print方法,这种方法一定会弹出打印预览窗,且如果需要打印页面内部分dom内容需要重新write页面等等弊端。
  • 第二种 LODOP(露肚皮)不得不说这是一款优秀的打印插件, 但是如果不买,打印出来有水印~

这篇文章如果教你使用以上两种方法,意义不大,论坛里有很多很棒的教程。 接下来你将接触到非常简单且免费(接下来会会考虑开源)的前端无感打印!

在这里先介绍VUE下无感打印的准备工作(其他框架原理一样的,最近比较忙,之后我会慢慢补充)

  • 插件我这里使用了html2canvas和jspdf
  • 安装我们开发的exe文件 点击下载 提取码: u582

准备工作

  • 安装依赖:npm install html2canvas jspdf --save
  • 下载服务组件并安装: 点击下载 提取码: u582
  • 新建一个html2pdf.js文件,代码见code 1 (有一些额外的功能好奇的可以逐行的取消下面的注释,看看效果)
  • 新建接口文件localPrint.js ,这里使用了axios,也可以使用原生,具体方法随你. 代码见code 2
  • 新建方法库pdf2Print.js,这里加载了之前的两个js文件。代码见code 3
  • 在mian.js中引入:import htmlToPdf from ‘…/XXX/html2pdf’(注意路径)
  • 在mian.js中引入:import dom2Print from ‘…/XXX/pdf2Print’(注意路径)

「VUE」前端实现真正的无感打印(可打印dom、file、url) CSDN首发文件最好都放在同一文件夹下,这样可以省着你来改js里面的的引入路径。

这套东西写的比较粗糙, 后期我会提交到npm上方便大家更方便的使用。

code1:
// dom转pfd的file流
import html2canvas from 'html2canvas'
import JSPDF from 'jspdf'

export default {
  install (Vue, options) {
    Vue.prototype.ExportSavePdf = function (htmlTitle, currentTime, callBack) {
      const element = document.getElementById('pdfCentent')
      html2canvas(element, {
        logging: false
      }).then(function (canvas) {
        const pdf = new JSPDF('p', 'mm', 'a4') // A4纸,纵向
        const ctx = canvas.getContext('2d')
        const a4w = 210; const a4h = 297 // A4大小,210mm x 297mm,四边各保留20mm的边距,显示区域170x257
        const imgHeight = Math.floor(a4h * canvas.width / a4w) // 按A4显示比例换算一页图像的像素高度
        let renderedHeight = 0

        while (renderedHeight < canvas.height) {
          const page = document.createElement('canvas')
          page.width = canvas.width
          page.height = Math.min(imgHeight, canvas.height - renderedHeight)// 可能内容不足一页

          // 用getImageData剪裁指定区域,并画到前面创建的canvas对象中
          page.getContext('2d').putImageData(ctx.getImageData(0, renderedHeight, canvas.width, Math.min(imgHeight, canvas.height - renderedHeight)), 0, 0)
          pdf.addImage(page.toDataURL('image/jpeg', 1.0), 'JPEG', 0, 0, a4w, Math.min(a4h, a4w * page.height / page.width)) // 添加图像到页面,保留10mm边距

          renderedHeight += imgHeight
          if (renderedHeight < canvas.height) { pdf.addPage() }// 如果后面还有内容,添加一个空页
          // delete page;
        }
        // pdf.save(htmlTitle + currentTime)
        // console.log(pdf.output('blob'), pdf.output(), 11111)
        // console.log(pdf.output(), 'blob对象')
        // console.log(new File([pdf.output()], htmlTitle + '.pdf'), 'file对象')
        const file = new File([pdf.output('blob')], htmlTitle + '.pdf')
        Vue.nextTick(() => {
          callBack(file)
        })
      })
    }
  }
}

code2:
import { axios } from '@/utils/request'

export function getPrinters (parameter) {
  return axios({
    url: 'http://127.0.0.1:20730/getPrinters?_m=' + Math.random(),
    method: 'get'
  })
}
export function getDefaultPrinter (parameter) {
  return axios({
    url: 'http://127.0.0.1:20730/getDefaultPrinter?_m=' + Math.random(),
    method: 'get'
  })
}

export function printPdf (parameter) {
  return axios({
    url: 'http://127.0.0.1:20730/printPdf?_m=' + Math.random(),
    method: 'post',
    data: parameter
  })
}

export function printZebra (parameter) {
  return axios({
    url: 'http://127.0.0.1:20730/printZebra?_m=' + Math.random(),
    method: 'post',
    data: parameter
  })
}
code3
/* eslint-disable no-unused-vars */
import htmlToPdf from './html2pdf'
import { getPrinters, getDefaultPrinter, printPdf, printZebra } from '@/api/localPrint'

console.log(htmlToPdf)
export default {
  install (Vue, options) {
    class Dom2Print {
      constructor () {
        this.name = 'dom2Pdf'
        this.contentData = {}
        this.alertFlag = false
      }
      onblur () {}
      onfocus () {}
      addAlertListen (startEvent, loadingEvent, connectionEvent, faildEvent, faild) {
        const that = this
        this.onblur = function () {
          console.log('sj')
          that.alertFlag = true
        }
        this.onfocus = function () {
          setTimeout(() => {
            console.log('jj')
            that.connectionState(startEvent, loadingEvent, connectionEvent, faildEvent)
          }, 2000)
        }
        getPrinters().then(res => {
          that.connectionState(startEvent, loadingEvent, connectionEvent, faildEvent)
        }).catch(() => {
          window.addEventListener('blur', that.onblur)
          window.addEventListener('focus', that.onfocus)
          const a = document.createElement('a')
          a.href = 'SLIENTPRINT://'
          a.click()
          setTimeout(() => {
            if (!that.alertFlag) {
              faild()
              window.removeEventListener('focus', that.onfocus)
              window.removeEventListener('blur', that.onblur)
            }
          }, 5000)
        })
      }
      // 调用斑马打印机
      printWithZebra (data, succe, err) {
        // data为对象 其余两参数为方法
        // 第一参数需要传的值
        // zebraCode    斑马zpl字符串
        // zebraPrinterIp    斑马打印机ip
        // zebraPrinterPort    斑马打印机端口
        // zebraPrinterName    斑马打印机名称
        if (data.zebraCode) {
          if (data.zebraPrinterIp) {
            if (data.zebraPrinterPort) {
              printZebra(data).then(res => {
                succe(res)
              }).catch(() => {
                err()
              })
            } else {
              console.error('若传zebraPrinterIp 那么zebraPrinterPort必传')
            }
          } else {
            if (data.zebraPrinterName) {
              printZebra(data).then(res => {
                succe(res)
              }).catch(() => {
                err()
              })
            } else {
              console.error('zebraPrinterIp + zebraPrinterPort 或者 zebraPrinterName 二组选一个 为必传')
            }
          }
        } else {
          console.error('zebraCode斑马zpl字符串 为必传')
        }
      }
      printDom (that, size, choosePrint, success, faild, url) {
        that.ExportSavePdf('油耗标pdf', '.pdf', (fileData) => {
          const formData = new FormData()
          formData.append('file', fileData)
          formData.append('copies', '1')
          formData.append('url', url || '')
          formData.append('paperSize', size)
          formData.append('printer', choosePrint)

          printPdf(formData).then(res => {
            if (res.result === 'success') {
              console.log('打印成功')
              success()
              // this.$message.success('打印成功', 3)
            } else if (res.result === 'failure') {
              console.log('打印失败')
              faild(1)
              // this.$message.error('打印失败', 3)
            } else if (res.result === 'unavailable') {
              console.log('服务未开启')
              faild(2)
              // this.$message.error('服务未开启', 3)
            }
          }).catch(() => {
            faild(3)
          })
        })
      }
      // 打印url
      printUrl (url, size, success, failed) {
        const formData = new FormData()
        formData.append('copies', 1)
        formData.append('duplex', false)
        formData.append('url', url)
        formData.append('paperSize', size || 'A4')

        printPdf(formData).then(res => {
          if (res.result === 'success') {
            console.log('打印成功')
            success()
            // this.$message.success('打印成功', 3)
          } else if (res.result === 'failure') {
            console.log('打印失败')
            failed(1)
            // this.$message.error('打印失败', 3)
          } else if (res.result === 'unavailable') {
            console.log('服务未开启')
            failed(2)
            // this.$message.error('服务未开启', 3)
          }
        }).catch(() => {
          failed(3)
        })
      }
      getPrintList (a) {
        getPrinters().then(res => {
          a(res)
        })
      }
      getDefaultPrint (a) {
        getDefaultPrinter().then(res => {
          a(res)
        })
      }
      openEXE (callBack) {
        const e = callBack || function () {}
        const a = document.createElement('a')
        a.href = 'SLIENTPRINT://'
        getDefaultPrinter().then(res => {
          e(true)
        }).catch(() => {
          a.click()
          setTimeout(() => {
            getDefaultPrinter().then(res => {
              e(true)
            }).catch(() => {
              e(false)
            })
          }, 5000)
        })
      }
      connectionState (startEvent, loadingEvent, connectionEvent, faildEvent) {
        this.contentData = {
          startEvent, loadingEvent, connectionEvent, faildEvent
        }
        const that = this
        const a = document.createElement('a')
        a.href = 'SLIENTPRINT://'
        startEvent({ isLink: false, linkLoading: true, state: 0 })
        // this.linkLoading = true
        getDefaultPrinter().then(res => {
          getPrinters().then(list => {
            connectionEvent({ isLink: true, linkLoading: false, choosePrint: res, printList: list, state: 1 })
            window.removeEventListener('focus', that.onfocus)
            window.removeEventListener('blur', that.onblur)
          })
        }).catch(() => {
          const a = document.createElement('a')
          a.href = 'SLIENTPRINT://'
          a.click()
          loadingEvent({ isLink: false, linkLoading: true, state: 0 })
          setTimeout(() => {
            getDefaultPrinter().then(res => {
              getPrinters().then(list => {
                this.printList = list
                connectionEvent({ isLink: true, linkLoading: false, choosePrint: res, printList: list, state: 1 })
                window.removeEventListener('focus', that.onfocus)
                window.removeEventListener('blur', that.onblur)
              })
            }).catch(() => {
              faildEvent({ isLink: false, linkLoading: false, state: 2 })
              window.removeEventListener('focus', that.onfocus)
              window.removeEventListener('blur', that.onblur)
            })
          }, 5000)
        })
      }
    }
    Vue.prototype.Dom2Print = Dom2Print
  }
}

到此为止,所有准备工作就绪!

开始使用

vue:

data : printClass: new this.Dom2Print()

获取默认打印机

this.printClass.getDefaultPrint((name) => {这里的name就是默认打印机名称})

获取打印机列表

this.printClass.getPrintList((list) => {这里的list就是打印机列表})

打印DOM

<div style="overflow:hidden; width:1px; height:1px">
    <div id="pdfCentent" style="width:583px;margin: 0 auto; background-size: 583px 826px; position: relative; font-size: 15px; font-family:'黑体' ">
		要被打印的dom结构~
    </div>
</div>
this.printClass.printDom(this, 'A4'或者'A5', '打印机名称', 成功后执行函数, 失败后执行的函数)
// 打印机名称可通过 this.printClass.getDefaultPrint((name) => {这里的name就是默认打印机名称})

打印url(目前仅支持pdf)

this.printClass.printUrl(url, size, 成功后执行函数, 失败后执行的函数)
// size 为A4 或者 A5 这两种的字符串

唤醒打印服务(针对不小心关闭打印服或未开启打印服务)

this.printClass.openEXE((e) => {e为true时唤醒成功, false时唤醒失败}

检测用户是否安装打印服务,若安装则启动打印服务

this.printClass.connectionState(开始启动时执行的函数, 启中执行的函数, 启动打印成功的函数, 检测到用户未安装执行的函数)

总结

  由于最近一直比较忙,这套东西写的比较急,代码不够简洁, 但目前没有什么影响流程的bug。
  
  在csdn做伸手党也很久了,也是最近刚刚开始在博客上更新自己的原创文章。
  关于无感打印这个东西,虽然业务上用到的地方不多,但我个人觉得这个思路还是挺有意思的,当然我会在有空闲时
间的时候优化上传到npm里面。 同时我会在csdn更新我这边打印功能的进度。
  
  当然有问题也欢迎大家随时留言。(尤其是新功能需求!)

严禁商用,转载请注明出处。

「VUE」前端实现真正的无感打印(可打印dom、file、url) CSDN首发
微信扫码加作者进群,白天也许会在,欢迎交流及提问。

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。