需求
- 标签打印需要进行静默打印,前端主要直接生成html文件,此时需要调用接口进行打印
需求分析
- 由于前端直接生成的模板是html文件,此时可以根据不同情况进行打印
(1)打印的类型:
-
直接传输 html 网页链接
-
直接传输 html 文件的 base64 编码
-
直接传输 pdf 文件的 base64 编码
(2)适应纸张大小,获取纸张尺寸,网页进行缩放
-
纸张尺寸:纸张长、纸张宽
-
网页缩放的比例:用于适应打印大小(范围:0.1-2)
(3)支持多份打印:
- 可传打印份数(大于等于1)
(4)支持页数打印:
- 可通过 '1-5,7-9,11’的形式指定打印页号
(5)最终以pdf形式打印
知识点补充
- 由于需求是打印html文件,所以需要分割页面,这里转成pdf就相当于预览打印了(多少页,每一页多大尺寸,打印几页都可以控制了)
- html转成pdf使用了依赖
html-pdf-node
- 使用打印协议(同样是依赖)
ipp
通过网络打印非常便捷,重点是不需要安装驱动 - 介绍一下
ipp
和html-pdf-node
-
ipp
-
可以通过
npm i ipp
安装 -
创建打印实体
const printer = ipp.Printer('http://【PrinterIp】:631')
- 这里的PrintIp是可以在配置打印机的地方查看的,631端口就默认放着(和配置打印机的端口无关)
- 这里的PrintIp是可以在配置打印机的地方查看的,631端口就默认放着(和配置打印机的端口无关)
-
这里可以访问打印机的几种操作,根据实际情况使用,传输的配置参数可以根据源码测试
- 获取打印机属性
'Get-Printer-Attributes'
- 打印工作
'Print-Job'
- 获取打印机属性
-
有了操作,在打印时简单的做法就是,先获取打印机属性,可以获取到说明打印机可连接,那么就打印吧(细节可以在测试时调整)
-
打印文件的类型,可以通过获取打印机的属性看到所有打印文件格式,比如
"document-format": "application/pdf",
-
打印返回的结果,一般这样的结果都是正常的
-
-
html-pdf-node
- 可以通过
npm i html-pdf-node
安装 - 这个函数传入html文件内容或url
html_to_pdf.generatePdf(file, options)
- 参数传入的html文件内容是字符串类型,
fs.readFile
返回的是Buffer
类型的,记得toString()
一下转成字符串 - 也可以传入html链接
- 这个函数返回的也是
Buffer
类型,刚好打印的数据传入也需要Buffer
类型的,皆大欢喜
- 参数传入的html文件内容是字符串类型,
- 可以通过
实现接口
-
接口:http://ip:3001/CommonUtilsApi/PrintFile
-
模型:PrintHtmlDto
-
源码:
import * as ipp from 'ipp'
import * as html_to_pdf from 'html-pdf-node'
//打印文件
printFile(dto:PrintHtmlDto):Promise<boolean>{
return new Promise((_res,_rej)=>{
try{
const validMsg = this.validationPrintDto(dto)
if(validMsg != '') _rej(validMsg);
const printer = ipp.Printer(`http://${dto.PrinterIp}:631`)
var msg = {
"operation-attributes-tag": {
'attributes-charset': 'utf-8',
"requesting-user-name": "666",
"job-name": "testPrint666",
"document-format": "application/pdf",
},
"job-attributes-tag":{
// // 'orientation-requested':'reverse-portrait',
// // 'page-ranges':'1',
// // 'print-scaling':'auto-fit', //打印缩放
'copies':dto.PageCopies,//打印份数
},
};
//区分类型
switch (dto.TemplateFileType) {
case TPL_FILE_TYPE.htmlFile:{
//data:text/html;base64,
const htmlFile = dto.TemplateFileStream;
if(htmlFile.startsWith('data:application/pdf;base64,')){
let base64Data = htmlFile.split(';base64,').pop();
const dataBuffer = new Buffer(base64Data, 'base64')
msg['data'] = dataBuffer;
//打印html文件(文件流)
this.printHtmlFile(dto,printer,msg).then(()=>{
_res(true);
}).catch(err=>{
_rej(err);
})
}else{
_rej(`${dto.TemplateFileType}类型的文件必须传输的是base64的编码类型!`)
}
}break;
case TPL_FILE_TYPE.htmlLink:{
if(dto.TemplateFileStream && (dto.TemplateFileStream.startsWith('https://') || dto.TemplateFileStream.startsWith('http://'))){
this.printByHtmlLink(dto,printer,msg).then(()=>{
_res(true)
}).catch(err=>{
_rej(err)
})
}else{
_rej(`${dto.TemplateFileType}类型必须传网页链接!`)
}
}break;
case TPL_FILE_TYPE.pdfFile:{
const pdfFile = dto.TemplateFileStream;
if(pdfFile.startsWith('data:application/pdf;base64,')){
let base64Data = pdfFile.split(';base64,').pop();
const dataBuffer = new Buffer(base64Data, 'base64')
msg['data'] = dataBuffer;
this.printPDFFile(printer,msg).then(()=>{
_res(true)
}).catch(err=>{
_rej(err)
})
}else{
_rej(`${dto.TemplateFileType}类型的文件必须传输的是base64的编码类型!`)
}
}break;
default: _rej('暂不支持该文件类型的打印任务!')
break;
}
}catch(err){
_rej(err)
}
})
}
//打印pdf文件
printPDFFile(printer,msg):Promise<boolean>{
return new Promise((_res,_rej)=>{
try{
// console.log("打印文件msg",msg);
// _res(true)
// writeFileSync(join(__dirname,'../../src/common-utils/data/luckysheet.pdf'),msg.data)
if(msg.data){
printer.execute("Get-Printer-Attributes", msg, function(err, res){
if(!err && res['statusCode'] && res['statusCode'] == 'successful-ok'){
printer.execute("Print-Job", msg, function(err, res){
if(err)_rej("打印失败!"+err)
_res(true)
})
}else{
_rej('未获取到打印机状态,请检查打印机是否存在'+err)
}
})
}else{
_rej('打印内容不能为空!')
}
}catch(err){
_rej('未获取到打印机状态,请检查打印机是否存在!'+err)
}
})
}
//打印网页(链接)
printByHtmlLink(dto:PrintHtmlDto,printer,msg):Promise<boolean>{
return new Promise((_res,_rej)=>{
try{
let options = {
width:dto.PageWidth?dto.PageWidth:'297mm',
height:dto.PageHeight?dto.PageHeight:'210mm',//这里的宽高设置,用于确定纸张尺寸
scale:dto.Scale?dto.Scale:1,
pageRanges:dto.PageRanges?dto.PageRanges:"1"
};
let file = { url: dto.TemplateFileStream };
html_to_pdf.generatePdf(file, options).then(pdfBuffer => {
// console.log("PDF Buffer:-", pdfBuffer);
// writeFileSync(join(__dirname,'../../src/common-utils/data/cc.pdf'),pdfBuffer)
msg.data = pdfBuffer;
this.printPDFFile(printer,msg).then(()=>{
_res(true)
}).catch(err=>{
_rej(err)
})
}).catch(err=>{
_rej(err)
})
}catch(err){
_rej(err)
}
})
}
//打印html文件
printHtmlFile(dto:PrintHtmlDto,printer,msg):Promise<boolean>{
return new Promise((_res,_rej)=>{
try{
let options = {
width:dto.PageWidth,
height:dto.PageHeight,//这里的宽高设置,用于确定纸张尺寸
scale:dto.Scale,
pageRanges:dto.PageRanges
};
let file = { content: msg.data.toString() };
html_to_pdf.generatePdf(file, options).then(pdfBuffer => {
// console.log("PDF Buffer:-", pdfBuffer);
msg.data = pdfBuffer;
this.printPDFFile(printer,msg).then(()=>{
_res(true)
}).catch(err=>{
_rej(err)
})
}).catch(err=>{
_rej(err)
})
}catch(err){
_rej(err)
}
})
}
//验证打印参数是否合法
validationPrintDto(dto:PrintHtmlDto){
if(!dto.PrinterIp) return '请输入打印机的Ip!'
if(dto.Scale<0.1 || dto.Scale>2) return '缩放比例范围不能超过0.1-2!';
if(dto.PageHeight && dto.PageWidth){
const width = dto.PageWidth
const height = dto.PageHeight
const unitW = width.substring(width.length-2,width.length);
const unitH = height.substring(width.length-2,width.length);
if(unitW !== 'mm' || unitH != 'mm') return '尺寸必须添加mm单位!'
if(Number(width.substring(0,width.length-2))<=1 || Number(height.substring(0,width.length-2))<=1 ) return '尺寸必须大于1mm!'
}else{
return '请添加纸张尺寸'
}
if(dto.PageCopies && dto.PageCopies<1) return '打印份数必须>1!'
if(String(dto.PageCopies).indexOf('.')>-1) return '打印份数不能是小数!'
if(!dto.PageRanges && dto.TemplateFileType != TPL_FILE_TYPE.pdfFile)return `请输入打印范围(格式: '1-2,6,7-9')!`
return '';
}
}