之前开发项目中使用的打印的插件是easy-print,这个插件是基于以hiprint(http://hiprint.io/)的打印插件,我们的项目中主要打印的是表格数据,在测试阶段发现了调用打印会使页面卡死,排查问题,发现原因是因为这个hiprint打印表格类型的时候,如果一行数据展示不下会分页,不会中间截断,但是出现某个字段值太长的情况,一条数据一页放不下,找了半天也没找到相关配置😂(可能是自己的原因没找到吧。。),后来还加了qq群,但是群里也是,怎么说呢,就是问题不一定会得到答复,不过好像还有VIP群,要money才能进😑,我也不确定会不会解决我的问题,所以就,决定还是自己写个简单的。
使用的实现方法是基于window.print,实现思路大概是在当前页面创建一个iframe(这里的iframe使用绝对定位或者固定定位宽高等方式让它在用户可视区域看不到的地方,当然如果说需要做页面预览的可以先生成一个预览页面),将打印的html内容放到iframe里,使iframe作为打印的容器,调用打印,之后再删除iframe。提前说一声,我这里主要用的是文件写入的方式,所以里边的内容都是使用字符串拼接这样的,写的只要是实现表格,所以别的打印内容做了简单的处理,这里如果说需要做统一处理另一种格式的,可以自定义再添加type~
实现打印具体的代码:
export const getPrintDom = (data) => { const printDom = []; data.forEach((itemObj) => { let itemDom = ''; switch (itemObj.type) { case 'domstr': itemDom = `<div style=${itemObj.renderStyle}>${itemObj.printElement.domStr}</div>`; break; case 'table': const tableHeadTh = itemObj.printElement.columns.map(item => `<th style='border:1px solid #000;border-top:none;border-left:none;width:${item.width}'>${item.title}</th>`); // 表头 const tableData = itemObj.printElement.data.map((item) => { const itemTd = itemObj.printElement.columns.map((citem) => { return `<td style='border:1px solid #000;border-top:none;border-left:none;width:${citem.width};word-break:break-all'>${citem.formatter ? citem.formatter(citem.field, item[citem.field], item) : item[citem.field]}</td>`; }); const itemTr = `<tr>${itemTd.join('')}</tr>`; return itemTr; }); // 表数据 itemDom = `<table style='font-size:12px;border:1px solid #000;border-bottom:none;border-right:none;text-align:center;${itemObj.renderStyle}' cellspacing='0'> <tr>${tableHeadTh.join('')}</tr> ${tableData.join('')} </table>`; break; default: break; } printDom.push(itemDom); }); let printDomStr = ''; // 最终打印的结构及样式 printDom.forEach((item, index) => { if (data[index].displayInBlock && data[index].displayInBlock.begin) { printDomStr += `<div style='display:flex;align-items:center;flex-wrap:wrap;justify-content:space-between;width:100%;text-align:center;font-size:12px'>${item}`; } else if (data[index].displayInBlock && data[index].displayInBlock.end) { printDomStr += `${item}</div>`; } else { printDomStr += item; } }); const iframe = document.createElement('IFRAME'); // 创建iframe let doc = null; // 打印的文档 iframe.setAttribute('style', 'position:absolute;width:0px;height:0px;left:0;top:0;'); // 设置iframe的样式 document.body.appendChild(iframe); // 将iframe追加进body doc = iframe.contentWindow.document; // 获取iframe的文档 doc.open(); // 开始写入iframe doc.write(printDomStr); // 写入打印的dom数据 data.forEach((item) => { if (item.type === 'domstr' && item.printElement.id) { const domEl = doc.getElementById('barCode'); // 获取dom元素 domEl.setAttribute('width', '100%'); // 调整dom的宽度(可能是因为条形码比较特殊,如果不设置宽度就和在页面是一样的宽度,不会自适应,所以这里调整一下) } }); doc.close(); // 结束写入,关闭文档 iframe.contentWindow.focus(); // 使iframe作为打印的容器 iframe.contentWindow.print(); // 调起打印 setTimeout(() => { document.body.removeChild(iframe); // 删除iframe,这里写异步是因为有的时候还没有调用打印就删除了 },200) };
使用打印方法的时候数据传参是有参考之前的hiprint,总体大的参数是数组类型,参数结构如下:
const renderData = [ { displayInBlock: { begin: boolean // end同行的最后一个元素,begin同行的第一个元素 } // 是否和某个打印的数据在同一行展示 非必传 type: 'table', // 打印的元素的类型,是domstr/table,目前只定义了这两种,必传 printElement: { columns: columnsData, // 表格类型必传,表头 data: tableData: // 表格类型必传,表格数据 domStr: htmlStr // html字符串 domstr类型必传 }, renderStyle: string // 额外的一些渲染样式,非必传 } ]
columnsData的格式:
const columnsData = [ { title: '性别', // 表头 width: '25%', // 列宽 field: 'sex', // 对应的数据字段的key formatter: (field, value, row) => {} // 自定义展示函数,若不写,则默认展示字段的值 } ]
具体使用case参考:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <button id='actionBtn'>打印</button> </body> <script> // 打印的数据 // 表头 const testTableColumns = [ { title: '账号', width: '25%', field: 'id', }, { title: '姓名', width: '25%', field: 'name', formatter: (field, value, row) => { const returnVal = row.sex === 0 ? `${value}先生` : `${value}女士`; return returnVal; } }, { title: '性别', width: '25%', field: 'sex', formatter: (field, value, row) => { let domStr = `<p style='color: red'>女</p>`; if (value === 0) { domStr = `<p>男</p>` } return domStr; } }, { title: '年龄', width: '25%', field: 'age' } ]; // 表数据 const testTableData = [ { id: '001', name: 'Andy', sex: 1, age: '22' }, { id: '002', name: 'Bob', sex: 0, age: '23' }, { id: '003', name: 'Tom', sex: 0, age: '20' }, ]; // 打印方法 const getPrintDom = (data) => { const printDom = []; data.forEach((itemObj) => { let itemDom = ''; switch (itemObj.type) { case 'domstr': itemDom = `<div style=${itemObj.renderStyle}>${itemObj.printElement.domStr}</div>`; break; case 'table': const tableHeadTh = itemObj.printElement.columns.map(item => `<th style='border:1px solid #000;border-top:none;border-left:none;width:${item.width}'>${item.title}</th>`); // 表头 const tableData = itemObj.printElement.data.map((item) => { const itemTd = itemObj.printElement.columns.map((citem) => { return `<td style='border:1px solid #000;border-top:none;border-left:none;width:${citem.width};word-break:break-all'>${citem.formatter ? citem.formatter(citem.field, item[citem.field], item) : item[citem.field]}</td>`; }); const itemTr = `<tr>${itemTd.join('')}</tr>`; return itemTr; }); // 表数据 itemDom = `<table style='font-size:12px;border:1px solid #000;border-bottom:none;border-right:none;text-align:center;${itemObj.renderStyle}' cellspacing='0'> <tr>${tableHeadTh.join('')}</tr> ${tableData.join('')} </table>`; break; default: break; } printDom.push(itemDom); }); let printDomStr = ''; // 最终打印的结构及样式 printDom.forEach((item, index) => { if (data[index].displayInBlock && data[index].displayInBlock.begin) { printDomStr += `<div style='display:flex;align-items:center;flex-wrap:wrap;justify-content:space-between;width:100%;text-align:center;font-size:12px'>${item}`; } else if (data[index].displayInBlock && data[index].displayInBlock.end) { printDomStr += `${item}</div>`; } else { printDomStr += item; } }); const iframe = document.createElement('IFRAME'); // 创建iframe let doc = null; // 打印的文档 iframe.setAttribute('style', 'position:absolute;width:0px;height:0px;left:0;top:0;'); // 设置iframe的样式 document.body.appendChild(iframe); // 将iframe追加进body doc = iframe.contentWindow.document; // 获取iframe的文档 doc.open(); // 开始写入iframe doc.write(printDomStr); // 写入打印的dom数据 data.forEach((item) => { if (item.type === 'domstr' && item.printElement.id) { const domEl = doc.getElementById('barCode'); // 获取dom元素 domEl.setAttribute('width', '100%'); // 调整dom的宽度(可能是因为条形码比较特殊,如果不设置宽度就和在页面是一样的宽度,不会自适应,所以这里调整一下) } }); doc.close(); // 结束写入,关闭文档 iframe.contentWindow.focus(); // 使iframe作为打印的容器 iframe.contentWindow.print(); // 调起打印 setTimeout(() => { document.body.removeChild(iframe); // 删除iframe,这里写异步是因为有的时候还没有调用打印就删除了 },200) }; // 调用打印 const doPrint = () => { const printDatas = [ { // 第一行左边 type: 'domstr', printElement: { domStr: `<p>学校:市实验中学 班级:高274</p>` }, renderStyle: 'width: 50%;', displayInBlock: { begin: true }, }, { // 第一行右边 type: 'domstr', printElement: { domStr: '<div style="display: flex; justify-content: space-between;color:red"><p style="border: 1px solid #000; padding: 4px 10px">班主任</p><p style="border: 1px solid #000;padding: 4px 10px">胡辣辣</p></div>' }, renderStyle: 'width: 50%;', displayInBlock: { end: true }, }, { // 表格 type: 'table', printElement: { columns: testTableColumns, data: testTableData }, renderStyle: 'width:100%;margin-top:20px;' } ] getPrintDom(printDatas); // 调用打印 } // 给button绑定打印事件 const btnEl = document.getElementById('actionBtn'); btnEl.addEventListener('click', doPrint); </script> </html>
效果:
暂时就是这些,写的比较简陋,主要是用来展示打印表格。如果有更好的方法和插件或者意见,欢迎评论~