将HTML页面自动保存为PDF文件并上传的两种方式(一)-前端(react)方式
2018-10-17 17:34
1481 查看
一、业务场景
公司的样本检测报告以React页面的形式生成,已调整为A4大小的样式并已实现分页,业务上需要将这个网页生成PDF文件,并上传到服务器,后续会将这个文件发送给客户(这里不考虑)。二、原来的实现形式
浏览器原生方法:window.print()可以将网页保存为PDF文件,由于检测报告的网页已经调整为A4的样式,所以保存下来后即是一个标准的PDF文档,然后将保存下来的PDF文件上传到服务器,即可实现需求。三、存在的问题
调用window.print()方法后需要手动保存PDF到本地,然后手动上传到服务器。所以本文的目的是点击上传PDF后自动将网页生成PDF,然后自动上传到服务器,省略操作者手动保存、手动上传这两个步骤。四、解决方法
根据“自动”这个需求,找到了两种实现方式:- 纯前端方式,前端生成pdf后通过接口上传到服务器
- 后端(node)方式,通过另起一个node服务来生成pdf并上传(推荐,以后介绍)
四、纯前端方法
前端采用了React框架。另需要html2canvas,jspdf两个库。1、场景1-上传一个尚未打开的React页面,这种情况下需要将需要上传的页面通过iframe以visiblity:hidden的形式打开或者被遮挡在看不到的地方,不可以display:none,因为这样获取到的DOM元素样式不正确,html2canvas会表现不正常。
由于流程较多,直接见代码吧,说明见注释:// 生成或者获取报告页面的外部容器 const getIframeContainer = () => { const ic = document.getElementById("iframeContainer"); if (!ic) { const iframeContainer = document.createElement("div"); iframeContainer.id = "iframeContainer"; iframeContainer.style.visibility = "hidden"; document.body.appendChild(iframeContainer); return iframeContainer; } return ic; }; class SendModal extends React.Component { // ... // 点击开始上传 handleUpload = () => { // 获取iframe容器和这个报告的ID const iframeContainer = getIframeContainer(); const iframeId = `iframe_${this.state.id}`; // iframe的load事件回调,执行该回调后开始执行this.createAndUpload() const onloadCallback = () => { this.createAndUpload(iframeId).then( // resolve和reject后移除报告iframe () => { ReactDOM.unmountComponentAtNode(iframeContainer); }, errMsg => { ReactDOM.unmountComponentAtNode(iframeContainer); console.error(errMsg); } ); }; // 开始渲染报告的iframe ReactDOM.render( <ReportIframe id={iframeId} src={reportURL} onLoad={onloadCallback} key={iframeId} />, iframeContainer ); }; createAndUpload = iframeId => { return new Promise((resolve, reject) => { // 从iframe中获取需要保存为PDF的DOM元素 let pages = Array.from( document .getElementById(iframeId) .contentDocument.querySelectorAll(".pdfpage") ); console.log(pages); const pagesLen = pages.length; if (!pagesLen) { reject("打开报告失败!"); } // 初始化一个pdf待用 const doc = new jsPDF("p", "mm", "a4"); const imgArr = []; console.log("成功抓取pages"); // 将每个元素作为一个页面处理 pages.forEach((page, idx) => { console.log(`正在绘制canvas[${idx}]`); html2canvas(page, { scale: 2, logging: false, useCORS: true, imageTimeout: 60000 }).then(canvas => { // canvas保存为图片 let imgData = canvas.toDataURL("image/jpeg", 1.0); imgArr.push({ index: idx, value: imgData }); if (imgArr.length === pagesLen) { console.log("canvas绘制完成,正在生成pdf"); // 通过idx保证页面顺序 let sortedArr = imgArr.sort((a, b) => a.index - b.index); sortedArr = sortedArr.map(item => item.value); sortedArr.forEach((img, idx) => { // 将图片放入pdf文件中 if (idx > 0) { doc.addPage(); } doc.addImage(img, "JPEG", 0, 0, 210, 297); if (idx + 1 === pagesLen) { // 全部放入pdf文件后,保存并上传 const pdf = doc.output("blob"); console.log("成功生成pdf,正在上传"); const formData = new FormData(); formData.append("file", pdf); fetch(`uploadURL`, { method: "post", body: formData }) .then(response => response.json()) .then(resp => { if (resp.Status === 0) { console.log("上传成功"); resolve("success"); } else { console.log("上传失败"); reject("上传报告失败!"); } }); } }); } }); }); }); }; // ... } class ReportIframe extends React.Component { // React通过js渲染页面,所以iframe触发onload后可能页面是一个空白页面,所以通过getPages方法确保React渲染完成后出发onLoad回调 getPages = (e, times = 1) => { const pages = Array.from( this.iframe.contentDocument.querySelectorAll(".pdfpage") ); if (pages.length || times >= 5) { this.props.onLoad(); this.iframe.removeEventListener("load", this.getPages); } else { setTimeout(() => { times++; this.getPages(e, times); }, 1000); } }; componentDidMount() { this.iframe.addEventListener("load", this.getPages, false); } render() { return ( <iframe id={this.props.id} src={this.props.src} ref={node => (this.iframe = node)} /> ); } }
2、场景2-在已打开页面中生成pdf并上传,代码同上,直接执行createAndUpload即可,不考虑iframe的相关处理。
五、效果演示
首先在报告列表页点击发送按钮,将进入待发送页面:![](https://oscdn.geek-share.com/Uploads/Images/Content/202003/06/bad5cc7cfd0fe58dee400a878d73b206.png)
↑点击确认发送将会以iframe的形式自动打开页面并保存为pdf上传到服务器然后发送到客户。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202003/06/2ed71aa1ab69ec0538eefc95124e79d2.png)
↑生成的iframe元素
![](https://oscdn.geek-share.com/Uploads/Images/Content/202003/06/94b275782949b24032322ea042d41520.png)
↑上传流程
六、遇到的坑及说明
1、生成的pdf模糊
html2canvas设置scale:2可解决,即使用2倍图保证清晰度。2、页面中每页的顺序已排好,但是生成pdf后乱了
由于canvas生成图片这个过程是异步的,所以我没有直接将生成的图片插入pdf中,而是通过idx排序后统一插入pdf。3、图片跨域
公司使用的阿里云OSS,所以将图片设置了Access-Control-Allow-Origin:*即可解决,如果是外部图片,需要使用代理,具体使用见html2canvas相关文档。4、页面中有虚线,但是html2canvas生成的是实线
见我之前的文章5、新建iframe后getPages作用是什么
React通过js渲染页面,所以iframe触发onload后可能页面是一个空白页面,所以通过getPages方法确保React渲染完成后出发onLoad回调七、前端生成PDF总结
前端生成pdf并上传的流程:获取将要作为PDF页面的DOM元素 -> 将DOM元素生成canvas -> 将canvas转为图片 -> 将图片插入pdf中 -> 将pdf上传由于是通过转成图片生成的PDF,即使是2倍图,清晰度依然不如原生PDF,且无法选择文字,所以这种方式生成PDF并不是最优解。
可能写的比较乱,可能属于自己知道咋回事但是说不出来那种……
相关文章推荐
- html页面保存数据的两种方式
- 自动把动态的jsp页面(或静态html)生成PDF文档,并且上传至服务器
- html页面保存数的两种方式
- 在C#.net中做页面上传的程序。用Dhtml的控件:(创建文件上载控件,该控件带有一个文本框和一个浏览按钮。)和类HtmlInputFile的两种方法
- iOS将HTML页面转换成PDF文件保存到本地并分享传输文件 - 简书
- C# 单张图片的保存[BLOB保存与文件上传两种方式]与展示
- JavaScript+Java实现HTML页面转为PDF文件保存的方法
- HTML转PDF,再转图片、影像文件,并压缩及上传到ftp服务器
- 使用python和java两种方式来完成下载网页,并保存成文件,
- ASP.NET Core 1.0中实现文件上传的两种方式(提交表单和采用AJAX)
- java 两种上传文件(图片)方式(app通过流上传)+服务端获取方式(多图上传,普通文本上传,图片压缩)
- springmvc 上传文件两种方式比较以及上传问题解决
- html 上传文件时 选中文件后自动开始上传
- 【文件上传 前端】文件上传 前端 Part3 —— HTML5 文件流方式
- Html页面自动刷新///客户端实现方式——不断发送请求
- springMVC两种方式实现多文件上传及效率比较
- java 读取文件——按照行取出(使用BufferedReader和一次将数据保存到内存两种实现方式)
- java 在前端页面上传图片文件,验证是否为合法的图片
- html页面输出pdf格式文件(一步步很详细)
- 例子:自动生成可以保存数据的HTML页面