2023-04-17

WechatIMG11.jpeg

一、 需求期望

根据需求生成指定html结构,通过web端调用打印功能将其或者指定区域打印出来

二、 打印方式

1. 预览打印

  点击打印按钮以后,出现浏览器自带的打印预览设置面板,点击确认,进行页面打印

2. 直接打印(静默打印)

  无预览页面展示,直接调用打印机,打印相应内容

  注:预览打印方式在交互方式上不支持批量打印

三、 预览打印方式—实现方案

1. 基于Dom结构预览打印

  a) 整个页面全部打印

    i. 原理:调用window.print()

    ii. 优点:

      1. 无兼容性问题,展示效果完美,页面整体打印

    iii. 缺点:

      1. 无法指定区域打印,会打印当前全页面内容

  b) 预览打印指定区域

    i. 当前窗口直接打印指定区域

      1. 原理:

代码示例:


//(1)首先获得元素的html内容(这里建议如果有样式最好是用内联样式的方式) 

var newstr = document.getElementById(myDiv).innerHTML; //得到需要打印的元素HTML 

//(2)保存当前页面的整个html,因为window.print()打印操作是打印当前页的所有内容,所以先将当前页面保存起来,之后便于恢复。 

var oldstr = document.body.innerHTML; //保存当前页面的HTML 

//(3)把当前页面替换为打印内容HTML 

document.body.innerHTML = newstr; 

//(4)执行打印操作 

window.print(); 

//(5)还原当前页面 

document.body.innerHTML = oldstr; 

}, 

      2. 缺点:

        a) 实现方式的特殊性,会导致当前页面状态重置,用户体验不好

    ii. 打开新窗口,在其中预览指定内容然后打印

    iii. 当前窗口内嵌iframe,或者弹框iframe,在iframe中预览指定内容,然后打印

      1. 原理:

代码示例:


//id-str 内容中的id

printPart( id_str ) { 

    var cssStr = this.getCssBlock(); 

    console.log(cssStr); 

    var cssStyle = document.createElement("style"); 

    cssStyle.type = "text/css"; 

    cssStyle.innerHTML = cssStr; 

    var el = document.getElementById(id_str); 

    var iframe = document.createElement("IFRAME"); 

    var doc = null; 

    iframe.setAttribute( 

    "style", 

    "position:absolute;width:0px;height:0px;left:-500px;top:-500px;" 

    ); 

    document.body.appendChild(iframe); 

    doc = iframe.contentWindow.document; 

    doc.getElementsByTagName("head")[0].appendChild(cssStyle); 

    doc.body.innerHTML = "<div id=" + id_str + ">" + el.innerHTML + "</div>"; 

    // doc.write("<div id="+id_str+">" + el.innerHTML + "</div>"); 

    doc.close(); 

    iframe.contentWindow.focus(); 

    iframe.contentWindow.print(); 

    if (navigator.userAgent.indexOf("MSIE") > 0) { 

    console.log("------"); 

    document.body.removeChild(iframe); 

} 

document.body.removeChild(iframe); 

}, 

// 获取vue文件中的style标签中的样式 

getCssBlock() { 

    const cssBlock = document.styleSheets; 

    console.log(cssBlock); 

    const styleData = [...cssBlock].reverse().find(({ cssRules }) => { 

    return [...cssRules].find((rule) => { 

    return ["#test-p"].includes(rule.selectorText); 

    }); 

    }); 

    return styleData.ownerNode.innerText; 

}, 

      2. 优点

        a) 打开新窗口和当前窗口内嵌iframe方式,可以指定打印区域

        b) 不会影响当前页面的页面状态

      3. 缺点

        a) 打开新窗口和当前窗口内嵌iframe方式,都是产生了新窗口,新窗口不会复用当前窗口的css样式,需要为新窗口和iframe注入打印内容的css样式

        b) 获取指定打印区域css样式,有很多不确定因素

    iv. 使用print.js插件

      1. Print.js:

        a) 大小:128 kB

        b) Github-star:3.6k

      c) 仓库地址:https://github.com/crabbly/Print.js

      文档地址:https://printjs.crabbly.com/#documentation

      2. 原理:

        原生js,将打印模块的dom和style样式(style/link)写入iframe,然后调用document.execCommand(”print”)进行打印

代码示例:


// print-js 分页样式表现比较好 

printMedical(idStr) { 

    // 调用打印插件,配置项参考官网:https://printjs.crabbly.com/ 

    print({ 

    // printable为需要打印的DOM的id 

    printable: idStr, 

    // type为需要打印的类型 

    type: "html", 

    // css为样式文件或者直接css样式,支持导入整个css文件,或者css文件数组 

    // css: medicalStyle, 

    // 可选项,这样配置意味着应用所有导入的css文件 

    targetStyles: ["*"], 

    }); 

}, 

      3. 优点:

        a) 逻辑简单,展示效果完美

        b) 支持打印的类型多:PDF、HTML、IMAGE、JSON

        c) 支持行内样式与外联样式,再也不用在DOM元素上写满样式了

        d) 兼容性好,除了IE不支持PDF和IMAGE打印外,其余主流浏览器全部支持

      4. 缺点:

        a) 需要引入第三方插件,受控性不友好

        b) 可能会出现的样式异常

        c) 无法直接打印/静默打印,因为是调用chrome打印,还是会弹出打印预页面

2. 将Dom结构转换为pdf预览打印

    a) 原理:

      html2canvas+jsPDF 通过html2canvas将遍历页面打印元素,并渲染生成canvas,然后将canvas图片格式添加到jsPDF实例,生成pdf,插入到iframe,然后打印

代码示例:


downPdf(idStr) { 

html2Canvas(document.querySelector(idStr), { 

    // onrendered: 

    background: "#0B1A48", 

    }).then((canvas) => { 

    var contentWidth = canvas.width; 

    var contentHeight = canvas.height; 

    // 一页pdf显示html页面生成的canvas高度; 

    var pageHeight = (contentWidth / 592.28) * 841.89; 

    // 未生成pdf的html页面高度 

    var leftHeight = contentHeight; 

    // pdf页面偏移 

    var position = 0; 

    // a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高 

    var imgWidth = 595.28; 

    var imgHeight = (592.28 / contentWidth) * contentHeight; 

    var pageData = canvas.toDataURL("image/jpeg", 1.0); 

    // eslint-disable-next-line 

    var pdf = new jsPDF("", "pt", "a4"); 

    // 有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89) 

    // 当内容未超过pdf一页显示的范围,无需分页 

    if (leftHeight < pageHeight) { 

    pdf.addImage(pageData, "JPEG", 0, 0, imgWidth, imgHeight); 

    } else { 

    while (leftHeight > 0) { 

    pdf.addImage(pageData, "JPEG", 0, position, imgWidth, imgHeight); 

    leftHeight -= pageHeight; 

    position -= 841.89; 

    // 避免添加空白页 

    if (leftHeight > 0) { 

    pdf.addPage(); 

    } 

    } 

    } 

    // 导出pdf文件命名 

    // pdf.save("report_pdf_" + new Date().getTime() + ".pdf"); 

    const iframe = document.createElement("iframe"); 

    iframe.setAttribute( 

    "style", 

    // "position:absolute;width:0px;height:0px;left:-500px;top:-500px;" 

    "display:none" 

    ); 

    iframe.src = pdf.output("bloburl"); 

    document.body.appendChild(iframe); 

    iframe.contentWindow.focus(); 

    iframe.contentWindow.print(); 

    // iframe.contentWindow.document.close(); 

    // document.body.removeChild(iframe); 

    // const myWindow = window.open(pdf.output("bloburl")); 

    // myWindow.print(); 

    }); 

}, 

    b) 优点:

      i. 可以设置分页和不分页

      ii. 可以对多页内容添加页眉页脚

    c) 缺点:

      i. 需要引入两个第三方插件html2canvas+jsPDF

      ii. 前端生成pdf不太友好,会有一定的性能问题

      iii. 由于浏览器的样式差异化,转换后可能会出现样式不统一问题

      iv. 分页逻辑不好控制,会出现展示效果不理想

3.将Dom结构转换为image,然后预览打印

  a) 原理:

    将打印内容html利用html2canvas生成canvas 通过canvas.toDataURL('image/jpeg', 1.0)生成图片,插入到iframe中预览打印

代码示例:


// 生成图片插入到iframe中,然后预览打印 

printBill() { 

    this.printDisabled = true; // 点击打印按钮禁止重复点击 

    setTimeout(() => { 

    // 按钮显示为禁止了再去执行截图功能 

    html2Canvas(this.$refs.reconciliationWrapper, { 

    backgroundColor: null, 

    // scale: 1, 

    }).then((canvas) => { 

    let dataURL = canvas.toDataURL("image/png"); 

    this.$refs.iframe.contentWindow.document.body.innerHTML = ""; // 清空上一次打印的内容 

    this.$refs.iframe.contentWindow.document.write( 

    '<html><head><style media="print">@page { margin: 0mm 10mm; }body{margin-top: 50px; text-align: center;}</style></head><body><img src=' + 

    dataURL + 

    "></body></html>" 

    ); 

    setTimeout(() => { 

    this.$refs.iframe.contentWindow.print(); 

    }, 0); 

    this.printDisabled = false; 

    }); 

    }, 100); 

}

  b) 优点:无

  c) 缺点:

    i. 转换的图片,样式差异化很大,不推荐

    ii. 需要引入第三方插件html2canvas

四、 直接打印方式—实现方案

  1. 前端将需要打印文件转换为pdf,然后导出blob流,然后通过post请求,将Blob流传给后端服务,调用后端部署的打印服务

  a) 优点:

    i. 可以直接打印,无需预览

  b) 缺点:

    i. 前端需要将打印内容生成为blob流,需要后端部在服务器部署打印机直接进行静默打印。

    参考:https://blog.csdn.net/ma_nong33/article/details/128974474

  1. 利用第三方插件

  a) LODOP官方地址

  b) 使用参考:文档1文档2

  c) 优点:

    1.支持直接打印

    2.支持打印类型丰富:HTML、TABLE、URL、TEXT、文档模板

    3.配置项十分丰富,大到是否显示打印预览,小到分页设置、边框设置等等

    4.兼容性好,除了兼容各类浏览器外,甚至还有LINUX版本

  d) 缺点

    1.直接打印功能需要付费解锁,但价格不算离谱,210RMB起

    2.需要单独下载exe安装到电脑上,不过也没办法,能实现如此强大的打印功能,只有此途径

    3.只支持内联样式,样式只能卸载DOM元素上

3.示例代码:


import { getLodop } from "@/assets/lodop/LodopFuncs"; 

// 生成样式文件 

createPrintHtml(idStr) { 

    var cssStr = this.getCssBlock(); 

    console.log(cssStr); 

    var cssStyle = document.createElement("style"); 

    cssStyle.type = "text/css"; 

    cssStyle.innerHTML = cssStr; 

    const strCss = "<style>" + cssStr + "</style>"; 

    const printHtml = strCss + '<body>'+ document.getElementById(idStr).outerHTML + '</body>' 

    return printHtml 

    }, 

    printLodop(idStr) { 

    // 获取打印对象 

    const LODOP = getLodop(); 

    // 打印初始化 

    LODOP.PRINT_INIT("打印任务名"); 

    console.log(idStr); 

    // 设定纸张大小,指定需要打印的DOM元素 

    LODOP.ADD_PRINT_HTM( 

    0, 

    0, 

    "100%", 

    "100%", 

    // document.getElementById(idStr).innerHTML 

    this.createPrintHtml(idStr) 

    ); 

    LODOP.PREVIEW(); 

    // 执行打印-直接打印 

    // LODOP.PRINT(); 

}

五、 调研方案总结

对以上预览打印和直接打印两种方式的实现方案优缺点分析:

1.基于预览打印实现方案;

  需要支持指定区域打印,无需单独处理样式打印模块样式,不需要前端进行打印内容文件转换,兼容性以及稳定性,支持指定打印内容是多页,推荐使用Printjs插件打印方式

PrintJS:

  基于Dom直接渲染到iframe,预览打印

  无打印内容格式转换过程

  大小:128 kB

  Github-star:3.6k

2.基于直接预览实现方案:

  1.由于LODOP无预览打印模式需要付费,且需客户安装打印程序,不推荐。

  2.前端将需要打印文件转换为pdf,然后导出blob流,然后通过post请求,将Blob流传给后端服务,调用后端部署的打印服务这种方式,需要前端转换文件格式(需要插件),存在性能和转换出错问题,后端需要部署打印机服务,安装打印服务,不确定后端是否可以实现。(暂不推荐,需要和服务端确认)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容