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月前
|
前端开发
开发过程中遇到过的docx、pptx、xlsx、pdf文件预览多种方式
开发过程中遇到过的docx、pptx、xlsx、pdf文件预览多种方式
46 0
|
1月前
|
JSON 前端开发 JavaScript
【2024-04-22 源码】最新PDF批注注释插件库,pdf.js插件库,纯前端离线JavaScript库(PDF高亮、下划线、橡皮擦、文本框、画笔、历史记录)
一款基于 pdf.js 开发的PDF批注插件库,支持纯离线内网部署,功能完善、强大且在不断升级,极易上手,欢迎关注!
77 4
【2024-04-22 源码】最新PDF批注注释插件库,pdf.js插件库,纯前端离线JavaScript库(PDF高亮、下划线、橡皮擦、文本框、画笔、历史记录)
|
1月前
|
前端开发 JavaScript 开发者
【专栏:HTML与CSS前端技术趋势篇】前端框架(React/Vue/Angular)与HTML/CSS的结合使用
【4月更文挑战第30天】前端框架React、Vue和Angular助力UI开发,通过组件化、状态管理和虚拟DOM提升效率。这些框架与HTML/CSS结合,使用模板语法、样式管理及组件化思想。未来趋势包括框架简化、Web组件标准采用和CSS在框架中角色的演变。开发者需紧跟技术发展,掌握新工具,提升开发效能。
|
1月前
|
前端开发 JavaScript 搜索推荐
react-app框架——使用monaco editor实现online编辑html代码编辑器
react-app框架——使用monaco editor实现online编辑html代码编辑器
70 3
|
1月前
|
编解码 前端开发 JavaScript
node实战——koa实现文件下载和图片/pdf/视频预览(node后端储备知识)
node实战——koa实现文件下载和图片/pdf/视频预览(node后端储备知识)
58 1
|
1月前
|
前端开发 JavaScript
React中渲染html结构---dangerouslySetInnerHTML
React中渲染html结构---dangerouslySetInnerHTML
29 0
|
1月前
|
前端开发 UED
🌟前端分页加载/懒加载预览PDF🌟
🌟前端分页加载/懒加载预览PDF🌟
|
1月前
|
前端开发 JavaScript API
react搭建在线编辑html的站点——引入grapes实现在线拖拉拽编辑html
react搭建在线编辑html的站点——引入grapes实现在线拖拉拽编辑html
23 0
|
1月前
|
前端开发 IDE 开发工具
csdn新星计划vue3+ts+antd赛道——利用inscode搭建vue3(ts)+antd前端模板
csdn新星计划vue3+ts+antd赛道——利用inscode搭建vue3(ts)+antd前端模板
16 0
|
1月前
|
前端开发 JavaScript 安全
如何在React项目中动态插入HTML内容
如何在React项目中动态插入HTML内容
79 0