js大屏导出图片_前端生成pdf?jspdf+html2canvas实现pdf预览和导出

最近做后台系统遇到挺多复杂的需求,比如导出pdf,word,excel

一般这种需求后端如果存文件,然后传个流过来,前端就可以下载导出了。

但是如果后端不存文件,只返回字符串(富文本字符串),这时候咋办?

= =不知道,但是我遇到了,也只能头铁干了。

吃货镇楼

路还是有的,讲一下实现方式:

html2canvas+jspdf

具体需求是在弹窗内预览,然后点击下载可以生成对应pdf,预览pdf如果后端没有给文件地址,只返回富文本字符串(类似"

123

"),比较难做,但是办法还是有的,个人感觉难点在于html2canvas生成完整截图的时机和pdf的分页。

贴一下效果:

预览弹窗

生成的pdf文件

思路:由于pdf不可直接编辑,个人思路是先将html截屏转化成图片,再把图片嵌入生成pdf。

html2canvas:直接npm i html2canvas -S ,用法是截图dom然后转化为canvas,具体api可以去github上看。

直接贴代码(项目用的是elementUI,核心代码在preview2pdf这个方法):

正在生成

我是猪扒封面

找不到pdf文件

下载

确定

import '@/utlis/html2canvas'

import '@/utlis/jspdf.debug'

import moment from "moment";

/*

*@description: 预览pdf弹窗

*@version V1.0

*@API:

*@ 参数 二选一, 二选一, 二选一

*previewDom 页面中可看见的预览目标类名 (比如富文本在页面中显示,其容器div类名为'.fuwenben',直接传'.fuwenben'就可以生成预览页面dom的pdf了)

*domData 页面中看不见的dom字符串(比如后台返回富文本字符串'

111
',直接传进来就可以生成pdf)

*@ 事件

* 需要在父组件指定关闭事件 onModalHidden

* onModalHidden(){

this.previewDialogVisible = false

},

*/

export default {

name:'PreviewModal',

props:{

title: {

type: String,

default: ''

},

width: {

type: String,

default: ''

},

isVisible: {

type: Boolean,

default: false

},

// 预览目标类名

previewDom:{

type: String,

default: ''

},

pdfName:{

type: String,

default:'pdf'

},

domData:{

type:String,

default:''

},

dateTime: {

type: Array,

default: () => {

return [];

},

}

},

data(){

return{

visible: this.isVisible, // 将props 的属性备份到data中

pdfFile:null,

isLoading:true,

}

},

methods: {

//当前日期

getDate() {

let date = new Date();

const month =

date.getMonth() + 1 > 9

? date.getMonth() + 1

: 0 + (date.getMonth() + 1);

return date.getFullYear() + "年" + month + "月" + date.getDate() + "日";

},

getDateTime() {

return (

this.getSplit(this.dateTime[0]) + "至" + this.getSplit(this.dateTime[1])

);

},

getSplit(date) {

let arr = moment(date)

.format("YYYY-MM-DD")

.split("-");

return arr[0] + "年" + arr[1] + "月" + arr[2] + "日";

},

/**

* 显示对话框

*/

showModal() {

// 如果是隐藏中才显示

if (!this.visible) { this.visible = true }

},

/**

* 隐藏对话框

*/

hiddenModal() {

// 如果是显示中才隐藏

this.visible = false

this.isLoading = true

this.$emit('onModalHidden')

// console.log(this.visible)

},

openModal(){

this.$emit('onModalOpen')

this.preview2pdf()

},

downloadPDF(){

this.pdfFile.save(this.pdfName);

this.$emit('downloadPDF');

},

// 预览转pdf

preview2pdf(){

// 非法dom直接返回

if (!this.previewDom && !this.domData) {

this.isLoading = false

return

}

const parentDom = document.querySelector('.preview-modal')

const contentDom = parentDom.querySelector('.preview-content')

// 进行截图的dom

const canvasDom = document.querySelector('.preview-content .preview-data')

// 找不到这个dom元素,返回

if(!canvasDom) {

this.isLoading = false

return

}

// 传入富文本字符串,添加到原有的子节点中

const mainBody = canvasDom.querySelector('.edit-content')

if(mainBody) {

// 添加内容

mainBody.innerHTML = `

${this.domData}
`

} else {

// 外部传进来的dom元素

const previewDom = document.querySelector(this.previewDom)

mainBody.append(previewDom)

}

// 新建ifame标签在线展示pdf

const iframe = document.createElement('iframe')

iframe.height = '99%'

iframe.width = '100%'

// 进行dom截图 必须让dom更新完再调用

this.$nextTick(()=>{

html2canvas(canvasDom, {

allowTaint: true,

useCORS: true,

}).then((canvas)=>{

// 用iframe标签展示pdf生成预览效果

contentDom.appendChild(iframe)

var contentWidth = canvas.width;

var contentHeight = canvas.height;

//一页pdf显示html页面生成的canvas高度;

var pageHeight = contentWidth / 592.28 * 841.89;

//未生成pdf的html页面高度

var leftHeight = contentHeight;

//页面偏移

var position = 0;

//a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高

var imgWidth = 555.28;

var imgHeight = 555.28/contentWidth * contentHeight;

var pageData = canvas.toDataURL('image/jpeg', 1.0);

// 取消生成状态

this.isLoading = false

var pdf = new jsPDF('', 'pt', 'a4');

//有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)

//当内容未超过pdf一页显示的范围,无需分页

if (leftHeight < pageHeight) {

imgWidth = 555.28;

imgHeight = 555.28/contentWidth * contentHeight;

pdf.addImage(pageData, 'JPEG', 20, 20, imgWidth, imgHeight );

} else {

while(leftHeight > 0) {

leftHeight -= pageHeight;

pdf.addImage(pageData, 'JPEG', 20, position, imgWidth, imgHeight)

position -= 841.89;

//避免添加空白页

if(leftHeight > 0) {

pdf.addPage();

}

}

}

// 保存pdf对象

this.pdfFile = pdf

// 生成外链让iframe标签展示

iframe.src = pdf.output('datauristring')

})

})

}

},

watch:{

isVisible(){

this.visible = this.isVisible;

}

}

}

.preview-modal{

height:100%;

.common-dialog{

// height:100%;

.el-dialog{

margin-top: 0!important;

height: 100%

}

.common-modal-title{

width: 100%;

height: 50px;

margin: 10px auto 0;

line-height: 48px;

border-bottom: 2px solid #d8d8d8;

box-sizing: border-box;

font-size: 20px;

color: #333;

span {

display: inline-block;

border-bottom: 3px solid #43baca;

}

}

.el-dialog__body{

padding: 10px 40px !important;

height: calc(100% - 75px)

}

.el-dialog__header{

// padding: 30px 40px 10px;

padding: 0;

}

// 预览对话框

.preview-content{

overflow:hidden;

position: relative;

height:100%;

.preview-data{

// 盖住隐藏dom height 和 width 可控制元素在pdf页面的大小

// min-height: 2600px;

width: 48%;

// width: 50%;

font-size: 20px;

z-index: -1;

position: fixed;

margin-top: -9999px;

.edit-content{

}

}

.error-pdf{

height:100%;

width:100%;

display:flex;

justify-content:center;

align-items:center;

font-size:20px;

.preview-icon{

font-size:36px;

padding-right:15px

}

}

// background-color:#ff0

}

.loading{

display: flex;

justify-content: center;

align-items: center;

// text-align: center;

height:100%;

// 盖在隐藏的canvasDom上面

z-index: 2;

font-size: 20px;

i{

color: #409EFF;

font-size: 30px;

padding-right: 20px;

}

}

.el-dialog__footer{

text-align: center;

padding: 0 20px;

}

.dialog-footer{

display:flex;

justify-content: space-around;

}

}

}

母猪焊接

遇到坑,注意的点

控制pdf页面大小取决于html2canvas截图dom的样式,例子中是 .preview-data 这个类,可以观察其中的css样式,其中z-index为负一的原因是html2canvas截图只能截可视dom元素,如果display:none或者是克隆出来的虚拟dom,都截不了,所以只能采取让元素看不见的方法来取巧。

由于后台返回的是富文本字符串,所以渲染的内容代码用innerHTML赋值了,赋值后dom还未渲染,此时不能立即使用html2canvas截取,需要等dom更新完成再截取,这就是调用vue.$nextTick的原因。

关于pdf分页问题:position 这个变量控制第二页的偏移位置,即利用偏移制造假分页,实际上pdf渲染出来的东西都在同一页上,只是按高度切割后,把剩余的内容合理偏移,使得看来像分页了而已。

分页参考: https://blog.csdn.net/weixin_43720095/article/details/87358705

4.预览和下载pdf:jspdf很强大,有一个output('datauristring')的方法,可以生成一个dataurl外链,把它带给iframe标签或者embed标签src就可以在线预览(后台直接返回pdf地址也是这种方法预览),下载则更为简单,调用save方法即可。

你可能感兴趣的:(js大屏导出图片)