React+html2canvas+jspdf+antd快速实现前端pdf预览及打印

简介: 文章的总结目标实际上就是一个前端pdf打印组件,由於能在往后的其他项目中得以快速上手,并能根据所在项目需要快速自定义扩展,因此組件非常简陋直白,文章是实践过程的记录产物,并不保证完全正确,仅作参考。

(1)依赖如下:

html2canvas配置参考:http://html2canvas.hertzen.com/configuration

jspdf配置参考: http://mozilla.github.io/pdf.js

"dependencies": {
"html2canvas": "1.4.1",
"jspdf": "2.5.1",
"ahooks": "^2.4.0",
"antd": "^4.5.4"  },

(2)主要流程如下:传进打印模板与模板填充参数  -> ReactDOM渲染模板到真实节点dom -> html2Canvas把节点dom生成图片 -> JsPDF把图片转成pdf -> pdf打印或下载,直接上代码,useHtml2Pdf.js如下:

importReact, { createRef, useState } from'react';
importReactDOMfrom'react-dom';
import { usePersistFn } from'ahooks';
import { Modal } from'antd';
import { FilePdfOutlined } from'@ant-design/icons';
importJsPDFfrom'jspdf';
importhtml2Canvasfrom'html2canvas';
// a4纸的尺寸(偏差 < 1pt)constA4_HEIGHT=840;
constA4_WIDTH=595;
constPdfPrint= () => {
const [loading, setloading] =useState(false);
constpdfRef=createRef();
consthandlePrint=usePersistFn(async ({ Template, isPrint, filename, options, callBack }) => {
setloading(true);
constprintDoc=document.createElement('div');
// 隐藏创建的domprintDoc.setAttribute('style', 'position:fixed;left:0;top:0;z-index: -999');
document.body.appendChild(printDoc);
// 通过React的render能力实现所需dom的渲染(包括css构建、变量填充)ReactDOM.render(<TemplatepdfRef={pdfRef} options={options} />, printDoc, async () => {consttargetDom=pdfRef.current;
// 克隆节点,默认为false,不复制方法属性,为true是全部复制。constcloneDom=targetDom.cloneNode(true);
// 设置克隆节点的css属性,因为之前的层级为0,我们只需要比被克隆的节点层级低即可。cloneDom.style.position='absolute';
cloneDom.style.top='0';
cloneDom.style.index='-999';
// cloneDom.style.height = height;cloneDom.style.display='block';
// 将克隆节点动态追加到body后面。printDoc.appendChild(cloneDom);
// 插件生成base64img图片。constcanvas=awaithtml2Canvas(cloneDom, {
useCORS: true,
y: 0, // 画布开始渲染的y坐标位置scale: window.devicePixelRatio*2,
      });
constcontentWidth=canvas.width;
constcontentHeight=canvas.height;
// 一页pdf显示html页面生成的canvas高度;constpageHeight= (contentWidth/A4_WIDTH) *A4_HEIGHT;
// html页面生成的canvas在pdf中图片的宽高constimgWidth=A4_WIDTH;
constimgHeight= (A4_WIDTH/contentWidth) *contentHeight;
constpageData=canvas.toDataURL('image/jpeg', 1.0);
constpdf=newJsPDF({
unit: 'pt',
format: 'a4',
orientation: 'p',
      });
// 未生成pdf的html页面高度letleftHeight=contentHeight;
// 页面偏移letposition=0;
// 有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(A4_HEIGHT)// 当内容未超过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-=A4_HEIGHT;
if (leftHeight>0) {
pdf.addPage();
          }
        }
      }
// 打印if (isPrint) {
pdf.output('dataurlnewwindow', {
filename: filename??'foo.pdf',
        });
      }
// 下载if (!isPrint) pdf.save(filename??'bar.pdf');
// 删除利用完的dom部分document.body.removeChild(printDoc);
// 执行回调callBack&&callBack();
setloading(false);
    });
  });
// pdf预览constshowPdf= ({ isPrint, options, Template, callBack, filename }) => {
Modal.confirm({
width: 900,
title: '預覽',
icon: <FilePdfOutlined/>,
content: (
<divstyle={{ border: '1px solid #000' }}><Templateoptions={options} /></div>      ),
okText: `${isPrint?'打印' : '下載'}`,
cancelText: '取消',
onOk: () =>handlePrint({ isPrint, options, Template, callBack, filename }),
    });
  };
/*   *@Description:参数注解   *filename(pdf名稱)   *Template(模板)   *isPreviewPdf(是否预览)   *options(模板填充变量)   *callBack(回调函数)   */return {
print: ({ Template, isPreviewPdf, options, callBack, filename }) => {
isPreviewPdf?showPdf({ Template, options, callBack, filename, isPrint: true })
        : handlePrint({ Template, options, callBack, filename, isPrint: true });
    },
download: ({ Template, isPreviewPdf, options, callBack, filename }) => {
isPreviewPdf?showPdf({ Template, options, callBack, filename, isPrint: false })
        : handlePrint({ Template, options, callBack, filename, isPrint: false });
    },
loading,
pdfRef, // 模板引用  };
};
exportdefaultPdfPrint;

(3)为了使模板看起来更整洁,同时也为了区分复杂的业务处理逻辑,本次实践把字段映射与模板分离,字段映射文件map_conf.js如下:

// 对字段进行映射及逻辑处理exportconsttemplateMap= (options) => {
return {
zk1: options.nameAndCardNoZhHant??'--',
zk2: options.applyCodeZhHant??'--',
zk3: options.payTimeZhHant??'--',
zk4: options.activityNameZhHant??'--',
zk5: [
      {
total: options.total??'--',
discount: options.discount??'--',
amount: options.amount??'--',
payType: options.payTypeZhHant??'--',
      },
    ],
zk6: options.nowDateZhHant??'--',
col1: [
      {
title: '序号',
dataIndex: 'total',
width: '20%',
      },
      {
title: '金额',
dataIndex: 'discount',
width: '20%',
      },
      {
title: '统计',
dataIndex: 'amount',
width: '20%',
      },
      {
title: '方式',
dataIndex: 'payType',
width: '20%',
      },
    ],
  };
};

(4)对于模板的样式,目前发现使用pt作为长宽单位较为友好(注意:此非结论,而是快速实践过程中某个较优解,即真实dom与html2Canvas生成的图片误差极小),以下模板及样式仅作参考。

Template.jsx如下:

importReactfrom'react';
import { Table } from'antd';
import { templateMap } from'./map_conf';
import'./Template.less';
// 模板htmlconstTemplate= (props) => {
const { pdfRef, options } =props;
const { zk1, zk2, zk3, zk4, zk5, zk6, col1 } =templateMap(options);
return (
<divclassName="pdf-root"ref={pdfRef}><divclassName="one"><divclassName="title"><span>XX收據</span></div><divclassName="count"><divclassName="row"><spanclassName="label">已收到:</span><spanclassName="value  mocaofont">{zk1}</span></div></div><divclassName="count"><divclassName="item"><spanclassName="label">申请編號:</span><spanclassName="value">{zk2}</span></div><divclassName="item"><spanclassName="label">辦理日期:</span><spanclassName="value">{zk3}</span></div></div><divclassName="count"><divclassName="row"><spanclassName="label">如下项目:</span><spanclassName="value">{zk4}</span></div></div><divclassName="table"><TablerowKey={(_record, index) =>index.toString()}
columns={col1}
dataSource={zk5}
pagination={false}
size="small"bordered/></div></div><divclassName="two"><divclassName="count"><divclassName="row"><spanclassName="label">備注:</span><spanclassName="value">{zk6}</span></div></div></div></div>  );
};
exportdefaultTemplate;

Template.less如下:

.pdf-root {
width: 595pt;
margin: 0auto;
padding: 010pt;
font-size: 8pt;
color: #000;
font-family: 'Microsoft YaHei';
font-weight: 500;
.one {
height: 200pt;
padding-top: 20pt;
.count {
.item {
line-height: 14pt;
      }
.row {
line-height: 14pt;
      }
    }
  }
.two {
height: 210pt;
padding-top: 10pt;
  }
.title {
text-align: center;
line-height: 12pt;
font-size: 12pt;
font-weight: bold;
padding-bottom: 8pt;
  }
.count {
width: 100%;
display: flex;
flex-wrap: wrap;
.item {
width: 50%;
line-height: 13pt;
.label {
font-weight: bold;
      }
    }
.row {
width: 100%;
line-height: 13pt;
.label {
font-weight: bold;
      }
    }
  }
.table {
margin: 2ptauto;
.ant-table-thead {
.ant-table-cell {
font-weight: bold;
      }
    }
.ant-table-cell {
font-size: 8pt!important;
    }
  }
}

(5)使用起来也非常便捷,例子如下:

importReactfrom'react';
import { Button } from'antd';
importuseHtml2Pdffrom'@/components/useHtml2Pdf';
importTemplatefrom'@/templates/Template.jsx';
// 使用constHowToUse= () => {
const { print, loading } =useHtml2Pdf();
consthandleClick= () => {
// mock数据constdata= {
nameAndCardNoZhHant: '张三',
applyCodeZhHant: 'ABC123456789',
payTimeZhHant: '2022-12-12 12:12:12',
activityNameZhHant: '某某收费项',
total: 1,
discount: '2.00',
amount: '5.00',
payTypeZhHant: '支付宝',
nowDateZhHant: '2022-14-14 14:14:14',
    };
// 使用print({ Template: Template, options: data });
  };
return (
<Buttonloading={loading} onClick={handleClick}>点击打印</Button>  )
};
exportdefaultHowToUse;

(6)效果图如下:

A1E47F70-7D06-42cf-87D4-25974CAEC408.png

相关文章
|
1月前
|
前端开发
PDF文件上传转成base64编码并支持预览
PDF文件上传转成base64编码并支持预览
122 12
|
3月前
|
XML 缓存 JSON
为什么浏览器中有些图片、PDF等文件点击后有些是预览,有些是下载
为什么浏览器中有些图片、PDF等文件点击后有些是预览,有些是下载
264 0
|
25天前
|
前端开发 JavaScript
除了 jsPDF,还有哪些前端库可以用于生成 PDF?
【10月更文挑战第21天】这些前端库都有各自的特点和优势,你可以根据具体的项目需求、技术栈以及对功能的要求来选择合适的库。不同的库在使用方法、性能表现以及功能支持上可能会有所差异,需要根据实际情况进行评估和选择。
|
1月前
|
XML 前端开发 JavaScript
前端开发进阶:从HTML到React.js
【10月更文挑战第9天】前端开发进阶:从HTML到React.js
|
1月前
|
JavaScript 前端开发 容器
Vue生成PDF文件攻略:html2canvas与jspdf联手,中文乱码与自动换行难题攻克
Vue生成PDF文件攻略:html2canvas与jspdf联手,中文乱码与自动换行难题攻克
94 0
|
2月前
|
JavaScript 前端开发
react字符串转为dom标签,类似于Vue中的v-html
本文介绍了在React中将字符串转换为DOM标签的方法,类似于Vue中的`v-html`指令,通过使用`dangerouslySetInnerHTML`属性实现。
94 0
react字符串转为dom标签,类似于Vue中的v-html
|
3月前
|
Web App开发 iOS开发 容器
Vue3PDF预览(vue3-pdf-app)
`vue3-pdf-app` 插件提供了一个简单而强大的 PDF 预览解决方案。通过 `&lt;a&gt;` 标签即可快速预览 PDF 文件。为满足更复杂的定制需求,提供了 `PDFViewer.vue` 组件,基于 `vue3-pdf-app@1.0.3` 封装,支持多种功能如缩放、旋转、全屏预览、打印等,并可自定义主题颜色与语言。组件属性包括文件地址 (`src`)、预览容器尺寸 (`width`, `height`)、默认缩放规则 (`pageScale`) 和主题 (`theme`) 等。适用于多种浏览器,方便集成到项目中。
598 2
Vue3PDF预览(vue3-pdf-app)
|
2月前
|
SQL 安全 数据库
用html+javascript打造公文一键排版系统2:显示源码/显示预览、清除格式
用html+javascript打造公文一键排版系统2:显示源码/显示预览、清除格式
|
2月前
|
移动开发 前端开发 JavaScript
使用html-to-image代替html2canvas,结合jspdf实现下载pdf(下载截图下载前端dom元素)
本文介绍了在前端项目中,当使用`html2canvas`遇到问题时,如何使用`html-to-image`库作为替代方案,结合`jspdf`实现将DOM元素生成为PDF文件并提供下载。文章首先讨论了`html2canvas`可能遇到的问题,并提供了该库的使用示例代码。随后,详细介绍了`html-to-image`库的安装和使用方法,展示了如何将DOM元素转换为Canvas,再利用`jspdf`生成PDF文件。最后,文章通过示例代码说明了整个转换和下载的过程,并展示了效果截图。
92 0
|
3月前
|
移动开发 资源调度 JavaScript
Vue移动端网页(H5)预览pdf文件(pdfh5和vue-pdf)
这篇文章介绍了在Vue移动端网页中使用`pdfh5`和`vue-pdf`两个插件来实现PDF文件的预览,包括滚动查看、缩放、添加水印、分页加载、跳转指定页数等功能。
2816 0
Vue移动端网页(H5)预览pdf文件(pdfh5和vue-pdf)
下一篇
无影云桌面