pdf文件展示盖章及下载
                                                            生活随笔
收集整理的這篇文章主要介紹了
                                pdf文件展示盖章及下载
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.                        
                                項(xiàng)目場景:
導(dǎo)入pdf文件將其導(dǎo)出展示, 選擇印章拖拽遮蓋位置,并下載
不想看后面的直接下載項(xiàng)目:?https://github.com/Lingyv77/pdf-stamp
效果: 之前的效果圖現(xiàn)在更好看了點(diǎn)
使用工具及依賴包:
主要:vue, vue-cli,vue-router, pdfjs-dist,
下載vue和vue-cli 創(chuàng)建項(xiàng)目和vue-router就不說了, 使用pdfjs-dis @2.0.943版本下次在換新版本看看
npm install --save pdfjs-dis@2.0.943更改文件:將App.vue 更改 :
HTML和CSS部分:
注意:css, html 部分更改添加請(qǐng)適當(dāng).
之前的定位問題已經(jīng)優(yōu)化 ,如果還出現(xiàn)了問題評(píng)論上提醒我.
<template><div class="pdf-stamp" onbeforecopy='return false' onselect='document.selection.empty()' ondragstart='return false' onselectstart ='return false' ><div class="scroll-box" @scroll="onScroll"><div class="scroll-warp"><div class="seal-list"><div class="title"> 印章 </div><div class="seal-img"><div class="seal-img-content"><div v-for="(item, index) of sealOfTheList" :key="index" class="seal-item"><div class="img-name"> {{ item.name }} </div><div class="img-content"> <img class="img" :src="item.img"@mousedown.stop="moveDown" /> </div></div></div></div></div><div class="content-box"><div class="with-file"><input type="file" class="file" id="file" ref="fielinput" @change="uploadFile" style="display: none;"/><label class="select-file" for="file">選擇文件</label> <span class="file-name"> {{ fileName }} </span><button class="save-down" @click.stop="saveDown">立即下載</button></div><div class="canvas-box-border"><div class="canvas-content" ref="canvasBox"><canvas ref="pdfCanvas" class="canvas-pdf"> </canvas></div></div><div class="foot-bar"><button @click="clickPre">上一頁</button><span>第 {{ pageNo }} / {{ pdfPageNumber }} 頁</span><button @click="clickNext">下一頁</button></div></div> </div></div></div> </template><style scoped> .pdf-stamp {width: 100vw;height: 100vh;background-color: white;overflow: hidden;position: relative;z-index: 1; }.scroll-box {width: 100vw;height: 100vh;overflow-x: hidden;overflow-y: scroll;position: relative;}.scroll-box::-webkit-scrollbar {display: none;}.scroll-warp {display: flex;position: relative;}.seal-list {height: 400px;text-align: center;border: 2px solid #d3cece;background-color: #e7e7e7;display: flex;flex-direction: column;margin: 50px 100px;border-radius: 5px;}.title {font-size: 20px;margin: 0 10px;font-weight: 600;padding: 5px;color: #7a7a7a;border-bottom: 1px solid #d6d6d6;}.seal-img {flex: 1;overflow-x: hidden;overflow-y: scroll;}.seal-img::-webkit-scrollbar {display: none;}.seal-img-content {padding: 0 10px;}.seal-item {margin: 10px 0;}.img-name {color: #b6b6b6;font-weight: 600;padding: 10px;background-color: #fffdf9;border-radius: 5px;}.img-content {height: 100px;display: flex;justify-content: center;align-items: center;}.img-content .img {width: 100px;}.content-box {text-align: center;position: relative;margin: 50px;background-color: #f3efe6;padding: 10px 25px;border-radius: 5px;}.with-file {display: flex;align-items: center;justify-content: space-between;padding-bottom: 10px;}.select-file {display: block;padding: 10px 50px;background-color: #e9d2ff;color: #000;border-radius: 5px;}.save-down {padding: 10px 50px;border-style: none;display: block;background-color: #e9d2ff;border-radius: 5px;}.file-name {color: #919191;font-size: 18px;font-weight: 600;}.canvas-box-border {border: 4px double black;}.canvas-content {width: 500px;height: 700px;position: relative; }.canvas-pdf {width: 100%;height: 100%;}.foot-bar {position: relative;padding: 10px;display: flex;justify-content: space-around;align-items: center;}.foot-bar button {border-style: none;background-color: #efc9aa;display: block;padding: 10px 50px;border-radius: 5px;}.foot-bar span {color: #186666;} </style>?注意:
不要試圖更改class 為 with-file內(nèi)的元素屬性行內(nèi)屬性 肯能會(huì)js導(dǎo)致獲取不到DOM效果失效
javaScript部分:
提示:有注釋哪里的東西可以自由配置選項(xiàng) 不要更改其他地方的哦!
注意: 大多地方都寫了注釋說明了就不多說了
<script>import pdfJS from "pdfjs-dist";import "pdfjs-dist/build/pdf.worker.entry";export default {name: 'PdfStamp',data() {return {pageNo: 0,pdfPageNumber: 0,renderingPage: false,pdfData: null, // PDF的base64sealDomList: [], //儲(chǔ)存印章domscrollTop: 0, //scrollToponce: false, //執(zhí)行一次獲取總之/*** option 設(shè)置項(xiàng)*/downFileText: "pdf_down_file", //下載文件名fileStamp: true, //是否需要給文件蓋章 才可下載zIndex: 100, //給一個(gè)z-index 防止被其他元素遮蓋導(dǎo)致立馬觸發(fā)mousedown 或者 mouseleave 刪除元素sealOfTheList: [{ name: "攜程旅行", img: "https://webresource.c-ctrip.com/ares2/nfes/pc-home/1.0.65/default/image/logo.png" },{ name: "虎牙直播", img: "https://a.msstatic.com/huya/main3/static/img/logo.png" },{ name: "廈門VG", img: "https://livewebbs2.msstatic.com/avatar_1_d52819f40bc198fbd1098b30dc1edacf.png" },{ name: "畫壓印", img: "http://shopxmhs.oss-cn-beijing.aliyuncs.com/3e6ae202206221409453342.png" },{ name: "招聘", img: "http://shopxmhs.oss-cn-beijing.aliyuncs.com/1daa8202206221409468728.png" },{ name: "誠邀", img: "http://shopxmhs.oss-cn-beijing.aliyuncs.com/2c0ba202206221409472433.png" },{ name: "廣州TTG", img: "https://livewebbs2.msstatic.com/avatar_1_bf8ba03e1f78144d84f3538672ca282b.png" },{ name: "成都AG超玩會(huì)", img: "https://esports-cdn.namitiyu.com/kog/team/FpDfD5z0hFN3N2gMpQHWx38qwmeF" },],scale: 3, // 縮放值maxseal: 3, //最大seal數(shù)量fileName: "尚未選擇文件", //初始文件名};},methods: {/*** 環(huán)境函數(shù)回調(diào)********//*** outMax() * max: 設(shè)置的最大值 newVale 觸發(fā)執(zhí)行的值(max+1) * 對(duì)應(yīng)maxseal配置項(xiàng)*/outMax(max, /*newVal*/) {console.log(`超出最大數(shù)量:${max}`); },/*** notSelectFile 尚未選擇文件回調(diào) * 無參數(shù)*/notSelectFile() {console.log('請(qǐng)選擇文件');},/*** notStamp 尚未選擇文件回調(diào) * 無參數(shù)* 對(duì)應(yīng)fileStamp配置項(xiàng)*/notStamp() {console.log('請(qǐng)給文件蓋章');},/*** 展示file*/uploadFile() {this.once = false;let fileInput = this.$refs.fielinput;let fileData = fileInput.files[0];this.fileName = fileData.name;let reader = new FileReader(); //文件讀取reader.readAsDataURL(fileData); //得到讀取的文件reader.onload = () => { //文件加載let data = atob(reader.result.substring(reader.result.indexOf(",") + 1) //取找到 ',' 符號(hào)后一個(gè)索引開始的所有數(shù)據(jù) 就是文件base64數(shù)據(jù) /** reader = data:application/pdf;base64,(JVBERi0xLj... = data) data文件base64數(shù)據(jù)atob() 函數(shù)源碼: globalScope.atob = function (input) { return Buffer.from(input, 'base64').toString('binary'); 'binary' 轉(zhuǎn)換'utf8'編碼格式: 返回字符串}*/);this.loadPdfData(data);};},loadPdfData(data) {// 引入pdf.js的字體let CMAP_URL = "https://unpkg.com/pdfjs-dist@2.0.943/cmaps/";//讀取base64的pdf流文件 返回pdf實(shí)例對(duì)象this.pdfData = pdfJS.getDocument({data: data, // PDF base64編碼cMapUrl: CMAP_URL,cMapPacked: true,});this.renderPage(1);}, // 根據(jù)頁碼渲染相應(yīng)的PDFrenderPage(num, callback) { //num傳入頁 返回對(duì)應(yīng)頁的pdf數(shù)據(jù)this.renderingPage = true;this.pdfData.promise.then((pdf) => {if (!this.once) {this.once = true;this.pdfPageNumber = pdf.numPages; //pdf.numPages 文件總頁數(shù)}pdf.getPage(num).then((page) => {// 獲取DOM中為預(yù)覽PDF準(zhǔn)備好的canvasDOM對(duì)象 繪制內(nèi)容let canvas = this.$refs.pdfCanvas;let viewport = page.getViewport(this.scale); //獲取窗口屬性canvas.height = viewport.height;canvas.width = viewport.width; let ctx = canvas.getContext("2d");let renderContext = {canvasContext: ctx, //將對(duì)應(yīng)ctx賦給renderContext.canvasContext 調(diào)用page.render(renderContext) 后內(nèi)部 對(duì)應(yīng)ctx.fillText() 繪制內(nèi)容viewport: viewport,};page.render(renderContext).then(() => { //渲染當(dāng)前頁內(nèi)容if (typeof(callback) === 'function') {callback(ctx);}this.renderingPage = false;this.pageNo = num; //獲取當(dāng)頁內(nèi)容});});});},//上一頁clickPre() {if (this.pdfPageNumber - 1 >= 1) {this.renderPage(this.pageNo - 1);}},//下一頁clickNext() {if (this.pageNo + 1 <= this.pdfPageNumber) {this.renderPage(this.pageNo + 1);}},/*** 創(chuàng)建seal dom*///按下moveDown(event) {let _this = this;let targetImg = event.srcElement; //觸發(fā)的imglet yDistance = (targetImg.offsetHeight/2);let xDistance = (targetImg.offsetWidth/2);let sealDomList = this.sealDomList;let addIndex = sealDomList.length;//創(chuàng)建imglet img = targetImg.cloneNode(true);img.tabIndex = addIndex;img.style.position = 'absolute';img.style.zIndex = this.zIndex;img.style.width = targetImg.offsetWidth + 'px';img.style.height = targetImg.offsetHeight + 'px';img.style.backgroundPosition = 'center';img.style.backgroundRepeat = 'no-repeat';img.style.backgroundSize = '100%'; img.style.backgroundSize = '100%'; let xy = this.getCanvasBoxXY();let canLeft = xy[0];let canTop = xy[1];_this.moveNode(img, (event.x - xDistance - canLeft), (event.y - yDistance - canTop + _this.scrollTop));//移動(dòng)document.onmousemove = function(event) {_this.moveNode(img, (event.x - xDistance - canLeft), (event.y - yDistance - canTop + _this.scrollTop));}//放下)document.onmouseup = function () {document.onmousemove = null;document.onmouseup = null;Promise.resolve(_this.clearDOM(img, _this.$refs.canvasBox)).then(res => {if(!res) {img.addEventListener( 'mousedown', _this.down, true);img.addEventListener( 'mouseup', _this.up, true);img.addEventListener( 'mouseleave', _this.leave, true);}})}//插入元素this.$refs.canvasBox.appendChild(img);_this.sealDomList.push(img); //儲(chǔ)存seal dom},/*canvasBox面向內(nèi)部成員 view 定位 x, y返回: 數(shù)組[x, y]*/getCanvasBoxXY() {let canvasBox = this.$refs.canvasBox; //iamge放置定位盒子let canLeft = this.getDomLeft(canvasBox, "offsetLeft");let canTop = this.getDomLeft(canvasBox, "offsetTop");return [canLeft, canTop];},//按下down(e) { //拖拽 和 是否創(chuàng)建印章let _this = this;let ev = e.srcElement;ev.style.zIndex = this.zIndex + 1; //我們希望拖拽印章的時(shí)候, 不會(huì)因?yàn)槠渌蓡T遮蓋影響let yDistance = (ev.offsetHeight/2);let xDistance = (ev.offsetWidth/2);let xy = this.getCanvasBoxXY();let canLeft = xy[0];let canTop = xy[1];_this.moveNode(ev, (e.x - xDistance - canLeft), (e.y - yDistance - canTop + _this.scrollTop));ev.onmousemove = function (event) {_this.moveNode(ev, (event.x - xDistance - canLeft), (event.y - yDistance - canTop + _this.scrollTop));}},//放下up(event) { //停止拖拽且是否刪除印章let target = event.srcElement;target.style.zIndex = this.zIndex; //我們希望結(jié)束拖拽操作后 印章的時(shí)候回到初始層級(jí);target.onmousemove = null;this.clearDOM(target, this.$refs.canvasBox);},//離開leave(event) { //停止拖拽event.srcElement.onmousemove = null;},//定位moveNode(event, x, y) {event.style.left = x + 'px';event.style.top = y + 'px';},/*** 是否出界需清除* 返回: 布爾值 是否被刪除*/clearDOM(node, box) {//node domlet target = node;let tarTop = target.offsetTop;let tarLeft = target.offsetLeft;let tarBottom = tarTop + target.offsetHeight;let tarRight= tarLeft + target.offsetWidth;//box domlet fileDom = box;let height = fileDom.offsetHeight;let width = fileDom.offsetWidth;if (tarBottom < 0 || tarTop > height) {this.removeSealChild(target);return true;}else if (tarRight < 0 || tarLeft > width) {this.removeSealChild(target);return true;}if (this.sealDomList.length > this.maxseal) { //最seal大數(shù)量this.outMax(this.maxseal, this.sealDomList.length );this.removeSealChild(node);return true;}return false;},//移除元素removeSealChild(node) {this.$refs.canvasBox.removeChild(node);this.sealDomList.splice(node.tabIndex, 1); for (let i = 0; i < this.sealDomList.length; i++) { //重新排序tabIndex標(biāo)識(shí)this.sealDomList[i].tabIndex = i;}},/*** canvas下載*/saveDown() {if (!this.pageNo) {return this.notSelectFile();}else if (!this.sealDomList.length && this.fileStamp) {return this.notStamp();}else{this.drawImage(this.sealDomList);}},//繪制圖片drawImage(imageList) {let canvas = this.$refs.pdfCanvas;let canvasBox = this.$refs.canvasBox;let _this = this;if (!this.fileStamp && !this.sealDomList.length) { //跳過印章繪制_this.canvasFile();return _this.backInitialState(_this.sealDomList);}function func(ctx) {let ratioX = canvas.width / canvasBox.offsetWidth;let ratioY = canvas.height / canvasBox.offsetHeight; let count = 0; //當(dāng)前進(jìn)度let totalCount = imageList.length; //總進(jìn)度for (let image of imageList) {let imgLeft = image.offsetLeft;let imgTop = image.offsetTop;let x = imgLeft * ratioX;let y = imgTop * ratioY;let img = new Image(20, 10);img.crossOrigin = 'anonymous';img.onload = () => {count++;ctx.drawImage(img, x, y, image.offsetWidth*ratioX, image.offsetHeight*ratioY);if (count === totalCount) {_this.canvasFile();_this.backInitialState(_this.sealDomList);}};img.src = image.src;}}this.renderPage(this.pageNo, func);},//canvas 文件數(shù)據(jù) 下載canvasFile() {let canvas = this.$refs.pdfCanvas;let dataURL = canvas.toDataURL('image/png'); //canva文件數(shù)據(jù)this.downLoad(dataURL);},//下載文件downLoad(url) {let note = document.createElement('a');note.download = this.downFileText; // 設(shè)置下載的文件名,默認(rèn)是'下載'note.href = url;document.body.appendChild(note);note.click();note.remove();},//下載成功 清空印章backInitialState(domList) {this.renderPage(this.pageNo);let len = domList.length;for (let i = 0; i < len; i++) {this.removeSealChild(domList[0]);}},/*** scrollTop*/onScroll(event) {let target = event.srcElement;this.scrollTop = target.scrollTop;},/*** 查找DOM 的 style屬性*/getStyleVal(node, styleStr) { let style;// let parent = node.parentNode;if (node === document) { //window.getComputedStyle方法 不可調(diào)用 document 我們不對(duì)他查詢style = null;}else {style = window.getComputedStyle(node)[styleStr];}return style;}, /*** 去除單位得到數(shù)值*/matchNum(str) {const regexp = /\d+(\.\d+)?/g; //匹配數(shù)字return Number((str+"").match(regexp)[0]) >>> 0;},/*** getDom 遞歸檢測DOM 確定定位多次賦值 得到總真實(shí)offsetLeft 和 offsetTop* key: 可選 offsetLeft 和 offsetTop*/getDomLeft(node) {let _this = this;let valueXY = [0, 0]; //儲(chǔ)存值let parent = node.parentNode;let uncertain = ["static", "initial", "revert" , "unset" ]; //定位被確定function dg(node, parent) {/*** //是否需要scrollXY (注銷注釋將不調(diào)用this.onScroll)* valueXY[0] -= parent.scrollLeft; //scrollLeft* valueXY[1] -= parent.scrollTop; //scrollTop*/if (!~uncertain.indexOf(_this.getStyleVal(parent, "position"))) { valueXY[0] += node.offsetLeft + _this.matchNum(_this.getStyleVal(parent, "borderLeft"));valueXY[1] += node.offsetTop + _this.matchNum(_this.getStyleVal(parent, "borderTop"));return dg(parent, parent.parentNode); //多次上級(jí)訪問找找到父節(jié)確定定位的元素 做 坐標(biāo) 位置重新規(guī)劃為定位后的元素 進(jìn)行下次訪問再取坐標(biāo)}else {let grandParentNode = parent.parentNodeif (grandParentNode !== document) {return dg(node, parent.parentNode); //如果沒找到一直上級(jí)查找 知道抵達(dá)父級(jí)為 document查詢結(jié)束}else { //到達(dá)documen時(shí)候立即停止valueXY[0] += node.offsetLeft;valueXY[1] += node.offsetTop;return valueXY;}}}return dg(node, parent);}}} </script>?注意這幾個(gè)印章路徑可能存在跨域, 更換圖片印章路徑更改sealOfTheList數(shù)據(jù)對(duì)應(yīng)路徑即可。
總結(jié)
以上是生活随笔為你收集整理的pdf文件展示盖章及下载的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 手眼标定方法汇总
- 下一篇: 从自己的角度比较《天书夜读》和《寒江独钓
