npm i pdfjs-dist
npm i html2canvas
npm i signature_pad
npm i jspdf
npm i html2canvas
npm i axios
npm i vant
这里需要用到pdfjs-dist
依赖,首先将pdf文件放到文件目录中(这里我放的是vue的public目录中)
<template>
<div class="pdf">
<van-popup :close-on-click-overlay="false" v-model="loadingShow"><van-loading type="spinner" size="24px" color="#fff">文件生成中...van-loading>van-popup>
<div class="box">
<canvas
v-for="page in pdf_pages" :id="'render_canvas'+page" :key="page" ref="pdfFiles" class="pdfCls">
canvas>
<div class="oprate">
<van-field v-model="userName" label="乙方(姓名)" placeholder="请输入姓名" />
<van-field v-model="userPhone" label="乙方(手机号码)" placeholder="请输入手机号码" error-message="" type="tel" maxlength="11" />
<van-field v-model="userCode" label="乙方(身份证)" placeholder="请输入姓身份证" />
<van-field v-model="userAddress" label="乙方(合同地址)" placeholder="请输入合同地址" />
<div class="write">请签名:div>
<canvas style="box-shadow: 0 5px 5px 0 gray;margin-left: 1rem;" className='canvas' id="canvasWrite" ref='canvasDom' width="340" height="150" />
<div class="btn">
<van-button type="info" @click="yes">确定van-button>
<van-button type="info" @click="reset(1)">重置van-button>
div>
div>
div>
div>
template>
<style lang="scss" scoped>
.pdf{
display: flex;
flex-direction: column;
align-items: center;
padding-bottom: 1rem;
overflow: hidden;
font-family: Helvetica, Tahoma, Arial, 'PingFang SC', 'Hiragino Sans GB', 'Heiti SC', STXihei, 'Microsoft YaHei', SimHei;
.van-popup--center{
background-color: transparent;
overflow: hidden;
height: 2rem;
}
.btn{
display: flex;
padding-top: 1rem;
.van-button{
width: 10rem;
margin-left: .5rem;
}
}
.box{
height: 100vh;
overflow: scroll;
.oprate{
width: 50%;
margin-left:4rem;
.write{
margin-left: 1rem;
}
.btn{
.van-button{
margin-left: 3rem;
}
}
}
}
}
style>
<style lang="scss">
.pdf {
.van-field__label{
width: 7rem;
}
.van-field{
width: 20rem;
}
}
style>
import PDFJS from 'pdfjs-dist'
data(){
return{
pdf_pages:0,
pdf_div_width:'100vw',
beginX:0,
beginY:0,
userName:'',
userPhone:'',
userCode:'',
userAddress:'',
count:0,//pdf接口提交次数
pdf_url:[],//pdf阿里路径
loadingShow:false,//加载弹出层
pdfObj:[],//pdf文件对象
imgData:[],//图片宽高数据
s_y:'2023',//开始年
s_m:'01',//开始月
s_d:'11',//开始日
e_y:'2024',//结束年
e_m:'01',//结束月
e_d:'11',//结束日
pageNum_all:0,
PromiseArr:[],
PagesAll:0,
PagesAllCope:0,
callbackNum:0,
myid:0,
PromiseRes:[],
upFile:null,//上传的文件
sp:null,//签名对象
yiSp:'',//乙方签名
computerYN:'',//电脑or手机
}
}
mounted(){
const systemName = navigator.platform
this.computerYN = systemName.includes("Win") || systemName.includes("Mac")//判断是电脑还是手机
this.loadTestCanvas()
}
//加载pdf
loadCanvas() {
const url = '/pdf/demo.pdf' //pdf地址
let loadingTask = PDFJS.getDocument(url) //url的方式,下面介绍base64方式
loadingTask.promise
.then((pdf) => {
this.pdfDoc = pdf
this.pdf_pages = this.pdfDoc.numPages
this.$nextTick(() => {
//这边pdf就一页,默认为1页
this.renderPage(1)
})
})
},
//渲染pdf页
renderPage(num) {
this.pdfDoc.getPage(num)
.then((page) => {
let canvas = document.getElementById('render_canvas' + num)
let ctx = canvas.getContext('2d')
let dpr = window.devicePixelRatio || 1
let bsr = ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio || 1
let ratio = dpr / bsr
//判断是电脑还是手机,缩放比例:电脑2,手机(ios浏览器最好<=1倍缩放,不然会出现白屏情况)
let viewport = page.getViewport({scale: this.computerYN?2:.8})
canvas.width = viewport.width * ratio
canvas.height = viewport.height * ratio
canvas.style.width = viewport.width + 'px'
this.pdf_div_width = viewport.width + 'px'
canvas.style.height = viewport.height + 'px'
ctx.setTransform(ratio, 0, 0, ratio, 0, 0)
let renderContext = {
canvasContext: ctx,
viewport: viewport
}
page.render(renderContext)
if (this.pdf_pages > num) {
this.renderPage(num + 1)
}
})
},
这里可以使用signaturePad
插件初始化canvas
init(){
const canvas = document.querySelector("canvas");
this.sp = new signaturePad(canvas);
},
canvas1.fillText('内容',700,470)
后面两个值为当前canvas x,y轴的定位
ctx1.clearRect(132,458,44,12)
前面两个值为,xy轴位置,后面两个值为从当前位置开始被覆盖的宽高
//放置签名生成的图片到指定位置
let image = new Image()
image.src = this.sp.toDataURL('image/png')
image.onload =()=>{
this.computerYN?canvas3.drawImage(image,360,765,100,40):canvas3.drawImage(image,144,303,40,16)
}
这里会用到html2Canvas进行转化,但是会将转化的对象放进数组,最后调用方法统一合并,这是我目前能想到的方法
//打印
printPdf(dom,ind){
html2Canvas(dom, {
allowTaint: true,
width: dom.offsetWidth, //设置获取到的canvas宽度
height: dom.offsetHeight, //设置获取到的canvas高度
x: 0, //页面在水平方向滚动的距离
y: 0, //页面在垂直方向滚动的距离
}).then((canvas) => {
let canvasWidth = canvas.width;
let canvasHeight = canvas.height;
let pageHeight = (canvasWidth / 592.28) * 841.89; // 一页A4 pdf能显示的canvas高度
let imgWidth = 595.28; // 设置图片宽度和A4纸宽度相等
let imgHeight = (592.28 / canvasWidth) * canvasHeight;//等比例换算成A4纸的高度
let totalHeight = imgHeight; // 需要打印的图片总高度,初始状态和图片高度相等
let pageData = canvas.toDataURL('image/png', 1.0);
this.imgData.push({
h:imgHeight,//图片高
w:imgWidth,//图片宽
img:pageData,//图片数据
})
let PDF = new JsPDF('p', 'pt', 'a4', true);
if (totalHeight < pageHeight) {
PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight); // 从顶部开始打印
} else {
let top = 0; // 打印初始区域
while (totalHeight > 0) {
PDF.addImage(pageData, 'JPEG', 0, top, imgWidth, imgHeight); // 从图片顶部往下top位置开始打印
totalHeight -= pageHeight;
top -= 841.89;
if (totalHeight > 0) {
PDF.addPage();
}
}
}
this.pdfObj.push(PDF)
this.count += 1;
if(this.count >= 3){//pdf打印的张数
this.createPDF()//合并
}
});
},
//重置
reset(type){
let ctx1 = this.$refs.pdfFiles[0].getContext('2d')
let ctx3 = this.$refs.pdfFiles[2].getContext('2d')
type === 1?this.sp.clear():''
if(this.computerYN){
//以下代码中的坐标都是可以更改的,这只是我的文件的坐标,请自行更改
ctx3.clearRect(790,855,250,30)
ctx1.clearRect(250,398,250,30)
ctx1.clearRect(290,444,250,30)
ctx1.clearRect(700,444,250,30)
ctx1.clearRect(330,1144,110,30)
ctx1.clearRect(460,1144,70,30)
ctx1.clearRect(550,1144,60,30)
ctx1.clearRect(655,1144,70,30)
ctx1.clearRect(750,1144,60,30)
ctx1.clearRect(830,1144,120,30)
ctx3.clearRect(350,755,240,50)
return
}
// ctx3.clearRect(316,342,100,12)覆盖图片(手机端)
ctx1.clearRect(100,159,100,12)
ctx1.clearRect(116,177,100,12)
ctx1.clearRect(280,177,100,12)
ctx1.clearRect(132,458,44,12)
ctx1.clearRect(184,458,28,12)
ctx1.clearRect(220,458,24,12)
ctx1.clearRect(262,458,28,12)
ctx1.clearRect(300,458,24,12)
ctx1.clearRect(332,458,48,12)
ctx3.clearRect(315,335,96,20)//签署日期
ctx3.clearRect(138,300,100,30)//覆盖签名
},
//确定
yes(){
if(!(this.userName&&this.userCode&&this.userPhone&&this.userAddress)){
return this.$toast('请将信息填写完整')
}
this.reset()//重置
const canvas3 = document.getElementById("render_canvas3").getContext('2d');
let image = new Image()
image.src = this.sp.toDataURL('image/png')
image.onload =()=>{
this.computerYN?canvas3.drawImage(image,360,765,100,40):canvas3.drawImage(image,144,303,40,16)
canvas3.font = this.computerYN?'18px serif':'10px serif'
let nowDates = new Date().getTime()
this.computerYN?canvas3.fillText(this.format(nowDates),800,875):canvas3.fillText(this.format(nowDates),315,351)//姓名(签字)
const canvas1 = document.getElementById("the-canvas1").getContext('2d');
canvas1.font = this.computerYN?'22px serif':'10px serif'
if(this.computerYN){
canvas1.fillText(this.userName,250,425)
canvas1.fillText(this.userCode,290,470)
canvas1.fillText(this.userPhone,700,470)
}else{
canvas1.fillText(this.userName,100,170)
canvas1.fillText(this.userCode,116,188)
canvas1.fillText(this.userPhone,280,188)
}
let nowDate = new Date().getTime() // 合同开始时间
let yestDate = nowDate + 31536000000 // 合同结束时间
this.format(nowDate,0)
this.format(yestDate,1)
if(this.computerYN){
//以下代码中的坐标都是可以更改的,这只是我的文件的坐标,请自行更改
canvas1.fillText(this.s_y,330,1170)
canvas1.fillText(this.s_m,500,1170)
canvas1.fillText(this.s_d,570,1170)
canvas1.fillText(this.e_y,660,1170)
canvas1.fillText(this.e_m,760,1170)
canvas1.fillText(this.e_d,900,1170)
}else{
canvas1.fillText(this.s_y,135,468)
canvas1.fillText(this.s_m,195,468)
canvas1.fillText(this.s_d,228,468)
canvas1.fillText(this.e_y,265,468)
canvas1.fillText(this.e_m,305,468)
canvas1.fillText(this.e_d,360,468)
}
this.loadingShow = true
this.$refs.pdfFiles.forEach((val,index)=>{
this.printPdf(val,index)
})
}
},
add0(m){
return m<10?'0'+m:m
},
//type:0开始时间 1结束时间
format(date,type) {
if(!date) return;
//时间戳是整数,否则要parseInt转换
var time = new Date(date);
var y = time.getFullYear();
var m = time.getMonth()+1;
var d = time.getDate();
// var h = time.getHours();
// var mm = time.getMinutes();
// var s = time.getSeconds();
if(type === 0){
this.s_y = y
this.s_m = this.add0(m)
this.s_d = this.add0(d)
}else if(type === 1){
this.e_y = y
this.e_m = this.add0(m)
this.e_d = this.add0(d)
}else{
return y+'年'+this.add0(m)+'月'+this.add0(d)+'日';
}
},
//合并pdf
createPDF() {
let doc = new JsPDF('p', 'pt', 'a4', true);
this.$refs.pdfFiles.forEach((canvas,index) => {
if (index > 0) doc.addPage() //添加页
//a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
doc.addImage(this.imgData[index].img, 'JPEG', 0, 0, this.imgData[index].w, this.imgData[index].h)
})
this.upFile = this.base64ConvertFile(doc.output("dataurlstring"),'测试文件')
// console.log(this.upFile);
let fom = new FormData()
fom.append('file',this.upFile,this.upFile.name)
fom.append('mobile',this.userPhone)
//发送文件给oss
Axios.post('你的文件上传的地址',fom,{
headers:{'Content-Type':'multipart/form-data'}
}).then(result =>{
// console.log(result);
doc.save('合同.pdf')
this.$toast('生成成功')
this.count = 0
this.loadingShow = false
}).catch(err=>{this.loadingShow = false})
},
// base64转file
base64ConvertFile (urlData, filename) {
var arr = urlData.split(',');
var type = arr[0].match(/:(.*?);/)[1];
var fileExt = type.split('/')[1];
var bstr = atob(arr[1]);
var n = bstr.length;
var u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename + "." + fileExt, {
type: type
});
},
到这里基本就完成了,要是小伙伴们有更好的解决方案或者其它问题可在下方留言,一起探讨技术问题。