2022.9.6
一、需求
最近领导提了一个新需求:仿照e签宝,实现pdf电子签章!
最终实现效果图
这是做出来的效果图,当然还有很多待修改
二、思路
然后我就去看了下人家e签宝的操作界面,左侧是印章,右侧是pdf,然后拖拽印章到pdf上面,点击保存,下次打开时显示印章的位置。
思路:我首先想到了拖拽、pdf预览、坐标;分工明确,前端来实现拖拽,pdf预览及把印章信息和坐标传给后端,后端只需要把信息和坐标保存下来就可以了。
三、使用插件
之前实现pdf预览就是通过window.open,打开一个窗口,显示pdf,功能很多,但是和需求不符,需要做的事是
把pdf显示出来,同时可以可以拖拽印章到上面去
,也不要放大与缩小及其他的功能。百度下了,说是用
pdfjs-dist
,这个pdf插件可以自定义很多的功能,但是实际用起来,发现好坑。。最后去百度了下,vue实现pdf电子签章, 看有没有现成的,然后还真找到了一个。js处理pdf展示、分页和签章等功能,下载到本地(只许查看
index.htm
l文件即可)后发现大佬用的不是
vue-cli
脚手架,是引用的cdn链接,然后就cv到项目里面了,跟着步骤,安装了
pdfjs-dist
插件(pdf插件)和
fabric
插件(专门处理印章的插件)这两个插件,但是项目本地运行后,报错了。。
四、遇到的问题
**1.
TypeError: Cannot read properties of undefined( reading 'Globalworkeroptions ')
**
百思不得其解啊,照着步骤来的啊,为啥呢,然后又回去看了下大佬的代码,发现他的pdf.js不是用的
pdfjs-dist
,而是引入的pdf的cdn链接
然后我就在项目的public/index.html下面引入这个链接
pdf路径则是使用的一个在线的pdf链接,
https://www.gjtool.cn/pdfh5/git.pdf
,发现可以打开了(样式做了些修改)
**2.
Dev Tools failed to load source map: Could not load content for https //mozilla github.ia/pdf js/build/pdf js map: Load canceled due to loadtimeout
**
开始觉得似乎已经大功告成了,到时候和后端商量下返回数据的格式就完事了的,谁知道还是有问题的。。多次打开关闭pdf后,有时候pdf会不加载出来了。人麻了,然后看了下提示,加载超时了,取消加载。
明显是cdn链接的问题,那就把pdf.js文件下载到本地呗,本地加载快,应该不会出现加载超时的问题,结果还是有问题。
唉,真的是服了,使用cdn链接吧,会加载超时,下载到本地引用吧,又会报这么个莫名其妙的问题,然后今天浏览博客时,发现一个兄弟碰到了一样的问题,哈哈,发现还是引入pdf方式的问题
/*引用cdn链接,可以使用但会加载超时*///letpdfjsLib=window["pdfjs-dist/build/pdf"];//pdfjsLib.GlobalWorkerOptions.workerSrc='https://mozilla.github.io/pdf.js/build/pdf.worker.js';/*下载到本地,看着官方文档引用,报个莫名其妙的错*///importpdfjsLibfrom'pdfjs-dist';//pdfjsLib.GlobalWorkerOptions.workerSrc='pdfjs-dist/build/pdf.worker.js';/*下载到本地,照着大佬的方式引用,完美!*/letpdfjsLib=require("pdfjs-dist/legacy/build/pdf.js");importworkerSrcfrom"pdfjs-dist/legacy/build/pdf.worker.entry";pdfjsLib.GlobalWorkerOptions.workerSrc=workerSrc;
这是大佬的博客链接pdf.js 使用攻略及错误集合
不过这项目的电子签章有些与众不同,用户打开的pdf,是可以自定义的,即用户打开弹窗,在tinymac编辑器里面输入内容,然后切换tab,会立即生成一个pdf,接下来才是用户使用电子签章的过程
以下是电子签章的主要代码,和大佬的index.html的代码差不多,就是做了点修改(ps:目前印章的位置和坐标保存,使用的得本地缓存,便于调试,后期会保存到接口里面!)
代码部分
首先 引入pdfjs-dist插件和fabric插件
npminstallpdfjs-dist
npmifabric--save
html部分
<divid="elesign"class="elesign"><el-row><el-col:span="4"style="margin-top:1%;"><divclass="left-title">我的印章</div><draggablev-model="mainImagelist":group="{ name: 'itext', pull: 'clone' }":sort="false"@end="end"><transition-grouptype="transition"><liv-for="item in mainImagelist":key="item"class="item"style="text-align:center;"><img:src="item"width="100%;"height="100%"class="imgstyle"/></li></transition-group></draggable></el-col><el-col:span="16"style="text-align:center;"class="pCenter"><divclass="page"><!--<el-buttonclass="btn-outline-dark"@click="zoomIn">-</el-button><spanstyle="color:red;">{{(percentage*100).toFixed(0)+'%'}}</span><el-buttonclass="btn-outline-dark"@click="zoomOut">+</el-button>--><el-buttonclass="btn-outline-dark"@click="prevPage">上一页</el-button><el-buttonclass="btn-outline-dark"@click="nextPage">下一页</el-button><el-buttonclass="btn-outline-dark">{{pageNum}}/{{numPages}}页</el-button><el-input-numberstyle="margin:0 5px;border-radius:5px;"class="btn-outline-dark"v-model="pageNum":min="1":max="numPages"label="输入页码"></el-input-number><el-buttonclass="btn-outline-dark"@click="cutover">跳转</el-button></div><canvasid="the-canvas"/><!--盖章部分--><canvasid="ele-canvas"></canvas><divclass="ele-control"style="margin-bottom:2%;"><el-buttonclass="btn-outline-dark"@click="removeSignature">删除签章</el-button><el-buttonclass="btn-outline-dark"@click="clearSignature">清除所有签章</el-button><el-buttonclass="btn-outline-dark"@click="submitSignature">提交所有签章信息</el-button></div></el-col><el-col:span="4"style="margin-top:1%;"><divclass="left-title">任务信息</div><divstyle="text-align:center;"><div><divclass="right-item"><divclass="right-item-title">文件主题</div><divclass="detail-item-desc">{{taskInfo.title}}</div></div><divclass="right-item"><divclass="right-item-title">发起方</div><divclass="detail-item-desc">{{taskInfo.uname}}</div></div><divclass="right-item"><divclass="right-item-title">截止时间</div><divclass="detail-item-desc">{{taskInfo.endtime}}</div></div></div></div></el-col></el-row></div>
js部分
import{fabric}from'fabric';letpdfjsLib=require("pdfjs-dist/legacy/build/pdf.js");importworkerSrcfrom"pdfjs-dist/legacy/build/pdf.worker.entry";pdfjsLib.GlobalWorkerOptions.workerSrc=workerSrc;importdraggablefrom"vuedraggable";exportdefault{components:{draggable},data(){return{//pdf预览pdfUrl:'',pdfDoc:null,numPages:1,pageNum:1,scale:2.2,pageRendering:false,pageNumPending:null,sealUrl:'',signUrl:'',canvas:null,ctx:null,canvasEle:null,whDatas:null,mainImagelist:[],taskInfo:{},}},computed:{hasSigna(){returnthis.canvasEle&&this.canvasEle.getObjects()[0]?true:false;},},created(){varthat=this;that.mainImagelist=[require('./sign.png'),require('./seal.png')];that.taskInfo={'title':'测试盖章',uname:'张三',endtime:'2021-09-01 17:59:59'};},methods:{//pdf预览//zoomIn(){//console.log("缩小");//if(this.scale<=0.5){//this.$message.error("已经显示最小比例")//}else{//this.scale-=0.1;//this.percentage-=0.1;//this.renderPage(this.pageNum);//this.renderFabric();//}//},//zoomOut(){//console.log("放大")//if(this.scale>=2.2){//this.$message.error('已经显示最大比例')//}else{//this.scale+=0.1;//this.percentage+=0.1;//this.renderPage(this.pageNum);//this.renderFabric();//}//},renderPage(num){let_this=this;this.pageRendering=true;returnthis.pdfDoc.getPage(num).then((page)=>{letviewport=page.getViewport({scale:_this.scale});//设置视口大小_this.canvas.height=viewport.height;_this.canvas.width=viewport.width;//RenderPDFpageintocanvascontextletrenderContext={canvasContext:_this.ctx,viewport:viewport,};letrenderTask=page.render(renderContext);//WaitforrenderingtofinishrenderTask.promise.then(()=>{_this.pageRendering=false;if(_this.pageNumPending!==null){//Newpagerenderingispendingthis.renderPage(_this.pageNumPending);_this.pageNumPending=null;}});});},queueRenderPage(num){if(this.pageRendering){this.pageNumPending=num;}else{this.renderPage(num);}},prevPage(){this.confirmSignature();if(this.pageNum<=1){return;}this.pageNum--;},nextPage(){this.confirmSignature();if(this.pageNum>=this.numPages){return;}this.pageNum++;},cutover(){this.confirmSignature();},//渲染pdf,到时还会盖章信息,在渲染时,同时显示出来,不应该在切换页码时才显示印章信息showpdf(pdfUrl){letcaches=JSON.parse(localStorage.getItem('signs'));//获取缓存字符串后转换为对象console.log(caches);if(caches==null)returnfalse;letdatas=caches[this.pageNum];if(datas!=null&&datas!=undefined){for(letindexindatas){this.addSeal(datas[index].sealUrl,datas[index].left,datas[index].top,datas[index].index);}}this.canvas=document.getElementById("the-canvas");this.ctx=this.canvas.getContext("2d");pdfjsLib.getDocument({url:pdfUrl,rangeChunkSize:65536,disableAutoFetch:false}).promise.then((pdfDoc_)=>{this.pdfDoc=pdfDoc_;this.numPages=this.pdfDoc.numPages;this.renderPage(this.pageNum).then(()=>{this.renderPdf({width:this.canvas.width,height:this.canvas.height,});});this.commonSign(this.pageNum,true);});},/***盖章部分开始*///设置绘图区域宽高renderPdf(data){this.whDatas=data;//document.querySelector("#elesign").style.width=data.width+"px";},//生成绘图区域renderFabric(){letcanvaEle=document.querySelector("#ele-canvas");letpCenter=document.querySelector(".pCenter");canvaEle.width=pCenter.clientWidth;//canvaEle.height=(this.whDatas.height)*(this.scale);canvaEle.height=this.whDatas.height;this.canvasEle=newfabric.Canvas(canvaEle);letcontainer=document.querySelector(".canvas-container");container.style.position="absolute";container.style.top="50px";//container.style.left="30%";},//相关事件操作哟canvasEvents(){//拖拽边界不能将图片拖拽到绘图区域外this.canvasEle.on("object:moving",function(e){varobj=e.target;//ifobjectistoobigignoreif(obj.currentHeight>obj.canvas.height||obj.currentWidth>obj.canvas.width){return;}obj.setCoords();//top-leftcornerif(obj.getBoundingRect().top<0||obj.getBoundingRect().left<0){obj.top=Math.max(obj.top,obj.top-obj.getBoundingRect().top);obj.left=Math.max(obj.left,obj.left-obj.getBoundingRect().left);}//bot-rightcornerif(obj.getBoundingRect().top+obj.getBoundingRect().height>obj.canvas.height||obj.getBoundingRect().left+obj.getBoundingRect().width>obj.canvas.width){obj.top=Math.min(obj.top,obj.canvas.height-obj.getBoundingRect().height+obj.top-obj.getBoundingRect().top);obj.left=Math.min(obj.left,obj.canvas.width-obj.getBoundingRect().width+obj.left-obj.getBoundingRect().left);}});},//添加公章addSeal(sealUrl,left,top,index){fabric.Image.fromURL(sealUrl,(oImg)=>{oImg.set({left:left,top:top,//angle:10,scaleX:0.8,scaleY:0.8,index:index,});//oImg.scale(0.5);//图片缩小一this.canvasEle.add(oImg);});},//删除签章removeSignature(){this.canvasEle.remove(this.canvasEle.getActiveObject())},//翻页展示盖章信息commonSign(pageNum,isFirst=false){if(isFirst==false)this.canvasEle.remove(this.canvasEle.clear());//清空页面所有签章letcaches=JSON.parse(localStorage.getItem('signs'));//获取缓存字符串后转换为对象console.log(caches);if(caches==null)returnfalse;letdatas=caches[this.pageNum];if(datas!=null&&datas!=undefined){for(letindexindatas){this.addSeal(datas[index].sealUrl,datas[index].left,datas[index].top,datas[index].index);}}},//确认签章位置并保存到缓存confirmSignature(){letdata=this.canvasEle.getObjects();//获取当前页面内的所有签章信息letcaches=JSON.parse(localStorage.getItem('signs'));//获取缓存字符串后转换为对象letsignDatas={};//存储当前页的所有签章信息leti=0;//letsealUrl='';for(varvalofdata){signDatas[i]={width:val.width,height:val.height,top:val.top,left:val.left,angle:val.angle,translateX:val.translateX,translateY:val.translateY,scaleX:val.scaleX,scaleY:val.scaleY,pageNum:this.pageNum,sealUrl:this.mainImagelist[val.index],index:val.index}i++;}if(caches==null){caches={};caches[this.pageNum]=signDatas;}else{caches[this.pageNum]=signDatas;}localStorage.setItem('signs',JSON.stringify(caches));//对象转字符串后存储到缓存},//提交数据submitSignature(){this.confirmSignature();letcaches=localStorage.getItem('signs');console.log(JSON.parse(caches));returnfalse},//清空数据clearSignature(){this.canvasEle.remove(this.canvasEle.clear());//清空页面所有签章localStorage.removeItem('signs');//清除缓存},end(e){this.addSeal(this.mainImagelist[e.newDraggableIndex],e.originalEvent.layerX,e.originalEvent.layerY,e.newDraggableIndex)},//设置PDF预览区域高度setPdfArea(){this.pdfUrl='https://www.gjtool.cn/pdfh5/git.pdf';this.pdfurl=res.data.data.pdfurl;this.$nextTick(()=>{this.showpdf(this.pdfUrl);//接口返回的应该还有盖章信息,不只是pdf});},},watch:{whDatas:{handler(){constloading=this.$loading({lock:true,text:'Loading',spinner:'el-icon-loading',background:'rgba(0, 0, 0, 0.7)'});if(this.whDatas){console.log(this.whDatas)loading.close();this.renderFabric();this.canvasEvents();leteleCanvas=document.querySelector("#ele-canvas");eleCanvas.style="border:1px solid #5ea6ef";}},},pageNum:function(){this.commonSign(this.pageNum);this.queueRenderPage(this.pageNum);}}},
css部分
<stylescoped>/*pdf部分*/.pCenter{overflow-x:hidden;}#the-canvas{margin-top:10px;}html:fullscreen{background:white;}.elesign{display:flex;flex:1;flex-direction:column;position:relative;/*padding-left:180px;*/margin:auto;/*width:600px;*/}.page{text-align:center;margin:0auto;margin-top:1%;}#ele-canvas{/*border:1pxsolid#5ea6ef;*/overflow:hidden;}.ele-control{text-align:center;margin-top:3%;}#page-input{width:7%;}@keyframesani-demo-spin{from{transform:rotate(0deg);}50%{transform:rotate(180deg);}to{transform:rotate(360deg);}}/*.loadingclass{position:absolute;top:30%;left:49%;z-index:99;}*/.left{position:absolute;top:42px;left:-5px;padding:5px5px;/*border:1pxsolid#eee;*//*border-radius:4px;*/}.left-title{text-align:center;padding-bottom:10px;border-bottom:1pxsolid#eee;}li{list-style-type:none;padding:10px;}.imgstyle{vertical-align:middle;width:130px;border:solid1px#e8eef2;background-image:url("tuo.png");background-repeat:no-repeat;}.right{position:absolute;top:7px;right:-177px;margin-top:34px;padding-top:10px;padding-bottom:20px;width:152px;/*border:1pxsolid#eee;*//*border-radius:4px;*/}.right-item{margin-bottom:15px;margin-left:10px;}.right-item-title{color:#777;height:20px;line-height:20px;font-size:12px;font-weight:400;text-align:left!important;}.detail-item-desc{color:#333;line-height:20px;width:100%;font-size:12px;display:inline-block;text-align:left;}.btn-outline-dark{color:#0f1531;background-color:transparent;background-image:none;border:1pxsolid#3e4b5b;}.btn-outline-dark:hover{color:#fff;background-color:#3e4b5b;border-color:#3e4b5b;}
2022.9.13修改
使用的时候,发现在pdf第一页添加的印章,下次再打开时,不在显示,本地缓存的也是显示{},所以琢磨了下,应该是每次打开pdf页面重置了,代码做了以下修改
将选中部分改为以下代码
版权归原作者 coderdwy 所有, 如有侵权,请联系我们删除。