vue里面使用pdfjs-dist+fabric实现pdf电子签章!!!

news/2024/11/7 10:39:41/

2022.9.6

一、需求

最近领导提了一个新需求:仿照e签宝,实现pdf电子签章!

最终实现效果图

这是做出来的效果图,当然还有很多待修改
在这里插入图片描述

二、思路

然后我就去看了下人家e签宝的操作界面,左侧是印章,右侧是pdf,然后拖拽印章到pdf上面,点击保存,下次打开时显示印章的位置。
思路:我首先想到了拖拽、pdf预览、坐标;分工明确,前端来实现拖拽,pdf预览及把印章信息和坐标传给后端,后端只需要把信息和坐标保存下来就可以了。

三、使用插件

之前实现pdf预览就是通过window.open,打开一个窗口,显示pdf,功能很多,但是和需求不符,需要做的事是把pdf显示出来,同时可以可以拖拽印章到上面去,也不要放大与缩小及其他的功能。百度下了,说是用pdfjs-dist,这个pdf插件可以自定义很多的功能,但是实际用起来,发现好坑。。最后去百度了下,vue实现pdf电子签章, 看有没有现成的,然后还真找到了一个。js处理pdf展示、分页和签章等功能,下载到本地(只许查看index.html文件即可)后发现大佬用的不是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链接,可以使用但会加载超时 */ 
// let pdfjsLib = window["pdfjs-dist/build/pdf"];
// pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://mozilla.github.io/pdf.js/build/pdf.worker.js';
/* 下载到本地,看着官方文档引用,报个莫名其妙的错 */
// import pdfjsLib from 'pdfjs-dist';
// pdfjsLib.GlobalWorkerOptions.workerSrc='pdfjs-dist/build/pdf.worker.js';
/* 下载到本地,照着大佬的方式引用,完美! */
let pdfjsLib =require("pdfjs-dist/legacy/build/pdf.js");
import workerSrc from "pdfjs-dist/legacy/build/pdf.worker.entry";
pdfjsLib.GlobalWorkerOptions.workerSrc = workerSrc;

这是大佬的博客链接pdf.js 使用攻略及错误集合

不过这项目的电子签章有些与众不同,用户打开的pdf,是可以自定义的,即用户打开弹窗,在tinymac编辑器里面输入内容,然后切换tab,会立即生成一个pdf,接下来才是用户使用电子签章的过程

以下是电子签章的主要代码,和大佬的index.html的代码差不多,就是做了点修改(ps:目前印章的位置和坐标保存,使用的得本地缓存,便于调试,后期会保存到接口里面!)

代码部分

首先 引入pdfjs-dist插件和fabric插件

npm install pdfjs-dist
npm i fabric --save

html部分

<div id="elesign" class="elesign"><el-row><el-col :span="4" style="margin-top:1%;"><div class="left-title">我的印章</div><draggable v-model="mainImagelist" :group="{ name: 'itext', pull: 'clone' }" :sort="false" @end="end"><transition-group type="transition"><li v-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"><div class="page"><!-- <el-button class="btn-outline-dark" @click="zoomIn">-</el-button><span style="color:red;">{{(percentage*100).toFixed(0)+'%'}}</span><el-button class="btn-outline-dark" @click="zoomOut">+</el-button> --><el-button class="btn-outline-dark" @click="prevPage">上一页</el-button><el-button class="btn-outline-dark" @click="nextPage">下一页</el-button><el-button class="btn-outline-dark">{{ pageNum }}/{{ numPages }}</el-button><el-input-number style="margin:0 5px;border-radius:5px;" class="btn-outline-dark"  v-model="pageNum" :min="1" :max="numPages" label="输入页码"></el-input-number><el-button class="btn-outline-dark" @click="cutover">跳转</el-button></div><canvas id="the-canvas" /><!-- 盖章部分 --><canvas id="ele-canvas"></canvas><div class="ele-control" style="margin-bottom:2%;"><el-button class="btn-outline-dark" @click="removeSignature"> 删除签章</el-button><el-button class="btn-outline-dark" @click="clearSignature"> 清除所有签章</el-button><el-button class="btn-outline-dark" @click="submitSignature">提交所有签章信息</el-button></div></el-col><el-col :span="4" style="margin-top:1%;"><div class="left-title">任务信息</div><div style="text-align:center;"><div><div class="right-item"><div class="right-item-title">文件主题</div><div class="detail-item-desc">{{ taskInfo.title }}</div></div><div class="right-item"><div class="right-item-title">发起方</div><div class="detail-item-desc">{{ taskInfo.uname }}</div></div><div class="right-item"><div class="right-item-title">截止时间</div><div class="detail-item-desc">{{ taskInfo.endtime }}</div></div></div></div></el-col></el-row>
</div>

js部分

import {fabric} from 'fabric';
let pdfjsLib =require("pdfjs-dist/legacy/build/pdf.js");
import workerSrc from "pdfjs-dist/legacy/build/pdf.worker.entry";
pdfjsLib.GlobalWorkerOptions.workerSrc = workerSrc;
import draggable from "vuedraggable";
export default {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() {return this.canvasEle && this.canvasEle.getObjects()[0] ? true : false;},},created(){var that = 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;return this.pdfDoc.getPage(num).then((page) => {let viewport = page.getViewport({ scale: _this.scale });//设置视口大小_this.canvas.height = viewport.height;_this.canvas.width = viewport.width;// Render PDF page into canvas contextlet renderContext = {canvasContext: _this.ctx,viewport: viewport,};let renderTask = page.render(renderContext);// Wait for rendering to finishrenderTask.promise.then(() => {_this.pageRendering = false;if (_this.pageNumPending !== null) {// New page rendering is pendingthis.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) {let caches = JSON.parse(localStorage.getItem('signs')); //获取缓存字符串后转换为对象console.log(caches);if(caches == null) return false;let datas = caches[this.pageNum];if(datas != null && datas != undefined) {for (let index in datas) {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() {let canvaEle = document.querySelector("#ele-canvas");let pCenter=document.querySelector(".pCenter");canvaEle.width = pCenter.clientWidth;// canvaEle.height = (this.whDatas.height)*(this.scale);canvaEle.height = this.whDatas.height;this.canvasEle = new fabric.Canvas(canvaEle);let container = document.querySelector(".canvas-container");container.style.position = "absolute";container.style.top = "50px";// container.style.left = "30%";},// 相关事件操作哟canvasEvents() {// 拖拽边界 不能将图片拖拽到绘图区域外this.canvasEle.on("object:moving", function (e) {var obj = e.target;// if object is too big ignoreif(obj.currentHeight > obj.canvas.height || obj.currentWidth > obj.canvas.width){return;}obj.setCoords();// top-left  cornerif(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-right cornerif(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()); //清空页面所有签章let caches = JSON.parse(localStorage.getItem('signs')); //获取缓存字符串后转换为对象console.log(caches);if(caches == null) return false;let datas = caches[this.pageNum];if(datas != null && datas != undefined) {for (let index in datas) {this.addSeal(datas[index].sealUrl, datas[index].left, datas[index].top, datas[index].index);}}},//确认签章位置并保存到缓存confirmSignature() {let data = this.canvasEle.getObjects(); //获取当前页面内的所有签章信息let caches = JSON.parse(localStorage.getItem('signs')); //获取缓存字符串后转换为对象let signDatas = {}; //存储当前页的所有签章信息let i = 0;// let sealUrl = '';for(var val of data) {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();let caches = localStorage.getItem('signs');console.log(JSON.parse(caches));return false},//清空数据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() {const loading = 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();let eleCanvas=document.querySelector("#ele-canvas");eleCanvas.style="border:1px solid #5ea6ef";}},},pageNum: function() {this.commonSign(this.pageNum);this.queueRenderPage(this.pageNum);}}
},

css部分

<style scoped>/*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:0 auto;margin-top: 1%;
}
#ele-canvas {/* border: 1px solid #5ea6ef; */overflow: hidden;
}
.ele-control {text-align: center;margin-top: 3%;
}
#page-input {width: 7%;
}@keyframes ani-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: 5px 5px;/*border: 1px solid #eee;*//*border-radius: 4px;*/
}
.left-title {text-align:center;padding-bottom: 10px;border-bottom: 1px solid #eee;
}
li {list-style-type:none;padding: 10px;
}
.imgstyle{vertical-align: middle;width: 130px;border: solid 1px #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: 1px solid #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:1px solid #3e4b5b;
}.btn-outline-dark:hover {color: #fff;background-color: #3e4b5b;border-color: #3e4b5b;
}

2022.9.13修改
使用的时候,发现在pdf第一页添加的印章,下次再打开时,不在显示,本地缓存的也是显示{},所以琢磨了下,应该是每次打开pdf页面重置了,代码做了以下修改

在这里插入图片描述
将选中部分改为以下代码
在这里插入图片描述


http://www.ppmy.cn/news/356738.html

相关文章

使用Java对PDF进行电子签章

使用Java对PDF进行电子签章 开始之前前期准备开始生成keystore证书来张材料全家福编码项目结构签署工具类 开始之前 公司近期做的项目用到了电子签章&#xff08;给PDF盖章签名&#xff09;&#xff0c;这过程真是曲折。恰逢现在时间比较空闲&#xff08;有时间摸鱼&#xff0…

1024程序员节|历经一个月总结使用java实现pdf文件的电子签字+盖章+防伪二维码+水印+PDF文件加密的全套解决方案

&#x1f345;程序员小王的博客&#xff1a;程序员小王的博客 &#x1f345;CSDN地址&#xff1a;程序员小王java &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 如有编辑错误联系作者&#xff0c;如果有比较好的文章欢迎分享给我&#xff0c…

怎样在PDF文件上添加印章

1、首先将需要添加制公司的印章的文件转换成.pdf文件 2、从Adobe 官网上下载Adobe Acrobat XI Pro 这款软件并安装到我们的电脑上&#xff0c;安装完后打开软件&#xff0c;界面如下&#xff1a; 3、准备好我们要添加水印的pdf文档&#xff0c;用Adobe Acrobat XI打开&#xf…

Word盖章和PDF盖章

一、电子签章的作用 对文档进行数字签名与签署纸质文档的原因大致相同&#xff0c;数字签名通过使用计算机加密来验证 &#xff08;身份验证:验证人员和产品所声明的身份是否属实的过程。例如&#xff0c;通过验证用于签名代码的数字签名来确认软件发行商的代码来源和完整性。…

css移动端

目录 谷歌模拟器 屏幕分辨率 视口 二倍图 适配方案 rem 简介 问题 媒体查询 移动端 设备宽度不同&#xff0c;HTML标签字号设置多少合适 flexible.js rem-移动端适配 less 注释 运算 嵌套 变量 导入 导出 禁止导出 谷歌模拟器 模拟移动设备&#xff0c;方…

诚迈科技智能汽车软件产业峰会落幕,智达诚远峰昇操作系统FusionOS发布!

4月6日&#xff0c;由诚迈科技、智达诚远共同主办&#xff0c;苏州工业园区投资促进委员会协办&#xff0c;苏州工业园区智能网联产业促进会大力支持的中国&#xff08;苏州&#xff09;智能汽车软件产业峰会在苏州盛大举行。本次活动以“创新融合 聚力赋能”为主题&#xff0c…

Antv/L7中使用高德地图插件

L7可以使用高德地图作为底图&#xff0c;也可以使用高德地图提供的插件&#xff0c;如工具栏&#xff0c;缩放条等。 官方文档中的引入方式如下const sc new Scene({id: xxxx-map,logoVisible: false,map: new GaodeMap({style: default,pitch: 0,zoom: 13.056,plugin: [AMap…

高德地图——货车导航

高德地图——货车导航 插件&#xff1a;pluginAMap.TruckDriving 第一种方法&#xff1a;使用坐标&#xff0c;比驾驶和骑行多了city和size属性&#xff0c;且需要放入的是json数据 new AMap.TruckDriving({map:map,panel:panel,city:beijing,//城市size:1 //大小}).search(…