前情:需要在app内嵌的weixin项目将页面转成PDF并下载。
使用技术:html2canvas插件 + jspdf插件
实现思路:1)将h5页面用canvas画成图片
2)利用jspdf将图片插入pdf文件并下载
缺点:生成的pdf是由图片拼接而成的,不能实现复制
实现版本:
第一版:将h5页面转成一张长图,再根据A4值的高度将长图截成多个页面
缺点:使用起来不灵活。没办法在每个页面上插入页眉页脚
实现代码:
1)weixin项目的 pdftemplate.vue文件
(由于是内嵌微信项目,所以在微信项目页面能操作dom)
<template>
<view v-if="calData" ref="refContent">
.......
</view>
</template>
import { downloadPdf } from '@/common/utils/insPdfUtil' //实现转换的页面
export default {
......
mounted(){
if (this.calData && this.calData.userInfo && this.calData.userInfo.name) {
this.htmlTitle = this.calData.userInfo.name + '的计划书'
}
//this.$refs.refContent.$el:需要转化的页面内容
//this.htmlTitle:文件名
downloadPdf(this.$refs.refContent.$el, this.htmlTitle)
}
}
2)insPdfUtil.js文件
import html2canvas from 'html2canvas'
import JsPDF from 'jspdf'
/**
* 创建并下载pdf
* @param {HTMLElement} targetDom dom元素
* @param {String} title pdf保存名字
* @param {function} callback 回调函数
*/
const downloadPdf = (targetDom, title, callback) => {
html2canvas(targetDom, {
useCORS: true
}).then(function(canvas) {
const contentWidth = canvas.width
const contentHeight = canvas.height
const pageHeight = (contentWidth / 592.28) * 841.89
let leftHeight = contentHeight
let position = 0
const imgWidth = 595.28
const imgHeight = (592.28 / contentWidth) * contentHeight
const pageData = canvas.toDataURL('image/jpeg', 1.0)
const PDF = new JsPDF('', 'pt', 'a4')
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 -= 841.89
if (leftHeight > 0) {
PDF.addPage() //添加pdf页树
}
}
}
PDF.save(title + '.pdf') //pdf下载
if (typeof callback === 'function') {
callback()
}
})
}
export { downloadPdf }
第二版:在h5页面上先设置好每一页要显示的数据存为对象传给insPdfUtil页面遍历生成PDF
缺点:移动设备部分下载失败
知识点:html2canvas插件可以绘制$el虚拟dom,也可直接传真实DOM(注:需要注册到页面上,可以在页面上定义一个div占位,在操作dom元素将元素插入该位置)
实现代码:
1)weixin项目的 pdftemplate.vue文件
1、使用ref获取虚拟dom的方式
2、使用操作dom的方式
3、动态生成dom,必须要挂载到页面上,可以先在页面写一个占位的标签
<template>
<view v-if="calData">
<!-- 使用ref获取虚拟dom的方式 -->
<view ref="topHead"> ... </view>
<!-- 使用操作dom的方式 -->
<view class="topBody"> ... </view>
<!-- 动态生成dom,必须要挂载到页面上,可以先在页面写一个占位的标签 -->
<view class="perch"> ... </view>
.......
</view>
</template>
import { downloadPdf } from '@/common/utils/insPdfUtil' //实现转换的页面
export default {
......
//--定义为数组遍历
data(){
refList: []
}
mounted(){
if (this.calData && this.calData.userInfo && this.calData.userInfo.name) {
this.htmlTitle = this.calData.userInfo.name + '的计划书'
}
//this.$refs.refContent.$el:需要转化的页面内容
//this.htmlTitle:文件名
this.refList.push(this.$refs.refContent.$el)
let topBody = document.queryselect('.topBody')
this.refList.push(topBody)
//--动态生成dom,必须要挂载到页面上,可以先在页面写一个占位的标签
let div = document.createElement("div")
div.innerHtml = '动态生成的div'
let perch = document.createElement("perch").appendChild(div)
this.refList.push(div)
downloadPdf(this.refList, this.htmlTitle, () => {回调函数})
}
}
2)insPdfUtil.js文件
import html2canvas from 'html2canvas'
import JsPDF from 'jspdf'
/**
* 创建并下载pdf
* html2canvas画是异步画图,所以这里需要用到promise.all
* @param {HTMLElement} targetDom dom元素
* @param {String} title pdf保存名字
* @param {function} callback 回调函数
*/
const downloadPdf = (targetList, title, callback) => {
var pdf = new JsPDF('', 'pt', 'a4', true)
const promiseList = []
targetList.forEach((item) => {
promiseList.push(
html2canvas(item, {
useCORS: true,
scale: 1
})
)
})
Promise.all(promiseList).then((resList) => {
resList.forEach((item, index) => {
var contentWidth = item.width
var contentHeight = item.height
// a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
var imgWidth = 595.28
var imgHeight = (592.28 / contentWidth) * contentHeight
var pageData = item.toDataURL('', 1)
pdf.addImage(pageData, 'JPEG', 0, 10, imgWidth, imgHeight)
if (index < resList.length - 1) {
pdf.addPage()
}
if (index === resList.length - 1) {
PDF.save(title + '.pdf') //pdf下载
if (typeof callback === 'function') {
callback()
}
}
})
})
}
export { downloadPdf }
第三版:利用调后端上传文件的接口生成下载链接,PDF下载更改为a标签下载的方式
缺点:没有解决IOS下载失败的问题
实现代码:insPdfUtil.js文件
import html2canvas from 'html2canvas'
import JsPDF from 'jspdf'
//调接口
import constant from '@/common/constant'
import store from '@/store'
/**
* 创建并下载pdf
* html2canvas画是异步画图,所以这里需要用到promise.all
* @param {HTMLElement} targetDom dom元素
* @param {String} title pdf保存名字
* @param {function} callback 回调函数
*/
const downloadPdf = (targetList, title, callback) => {
var pdf = new JsPDF('', 'pt', 'a4', true)
const promiseList = []
targetList.forEach((item) => {
promiseList.push(
html2canvas(item, {
useCORS: true,
scale: 1
})
)
})
Promise.all(promiseList).then((resList) => {
resList.forEach((item, index) => {
var contentWidth = item.width
var contentHeight = item.height
// a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
var imgWidth = 595.28
var imgHeight = (592.28 / contentWidth) * contentHeight
var pageData = item.toDataURL('', 1)
pdf.addImage(pageData, 'JPEG', 0, 10, imgWidth, imgHeight)
if (index < resList.length - 1) {
pdf.addPage()
}
if (index === resList.length - 1) {
const file = pdf.output('blob')
// 这样子后台就有名字了
const fileObject = new File([file], title + '.pdf', { type: 'application/pdf' })
uploadFileXHR(fileObject, (downloadUrl) => {
// 利用a标签的download属性下载pdf,IOS不适用
const a = document.createElement('a')
a.setAttribute('href', downloadUrl)
a.download = title + '.pdf'
a.click()
if (typeof callback === 'function') {
callback()
}
})
}
})
})
}
// --调后端接口的代码
const uploadFileXHR = (file, successUpload) => {
const uploadUrl = `${constant.reqBaseUrl}/ins/file/tenantUploadFile`
const data = {
tenant: 'AKIAIOSFODNN7EXAMPLE',
fileType: '5e25920b5b5b11e9bf97080027e99028',
bucketName: 'papers'
}
const header = {
token: store.getters.token
}
const formData = new FormData()
for (const keys in data) {
formData.append(keys, data[keys])
}
formData.append('upload', file)
const xhr = new XMLHttpRequest()
xhr.open('POST', uploadUrl, true)
for (const keys in header) {
xhr.setRequestHeader(keys, header[keys])
}
xhr.onreadystatechange = (ev) => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
const data = JSON.parse(xhr.responseText)
if (data.resCode === '0') {
successUpload(data.data.preview_url)
}
} else {
//
}
}
}
xhr.send(formData)
}
export { downloadPdf }
第四版:将后端生成的pdf文件地址通过webview通信传回APP,利用uniapp的uni.downloadFile和uni.saveFile下载文件
实现代码:1) insPdfUtil.js文件
.....
// 封装的webview通信代码
import { postmessageJumpOnApp } from '@/common/utils/insWebviewUtil'
.....
if (index === resList.length - 1) {
const file = pdf.output('blob')
// 这样子后台就有名字了
const fileObject = new File([file], title + '.pdf', { type: 'application/pdf' })
uploadFileXHR(fileObject, (downloadUrl) => {
/* const a = document.createElement('a')
a.setAttribute('href', downloadUrl)
a.download = title + '.pdf'
a.click() */
// ----------------
postmessageJumpOnApp(downloadUrl)
if (typeof callback === 'function') {
callback()
}
})
}
.....
2) app项目页面 pdfpages.vue
<template>
<view v-if="src">
<view v-if="!pageData.customerVisible && show">
<web-view :src="src" @message="message" />
<!-- -->
</view>
</view>
.......
</template>
<script>
......
methods: {
message(e) {
if (e &&e.detail && e.detail.data &&e.detail.data.length &&e.detail.data[0].jumpTo) {
// 接受到weixin项目传过来的pdf文件地址
uni.downloadFile({
url: e.detail.data[0].jumpTo,
success: (res) => {
if (res.statusCode === 200) {
var filePath = res.tempFilePath;
uni.saveFile({
tempFilePath: filePath,
success: function (res) {
var savedFilePath = res.savedFilePath;
uni.showToast({
icon:'none',
mask:true,
title:'文件已保存并即将打开',
duration:1000
})
setTimeout(()=>{
uni.openDocument({
filePath: savedFilePath,
success: function (res) {
console.log('打开文档成功');
}
});
},1000)
},
fail:(err)=>{
uni.showToast({
icon:'none',
mask:true,
title:'文件下载失败',
duration:1000
})
}
});
}
}
})
return
}
},
}
......
</script>