最近公司做了一个我的项目,次要性能是把印章在PDF上拖拽定位的。
这个性能次要两个点:
- 块的拖拽即时显示印章内容;
- PDF回显(因波及多人操作,回显包含之前人所签的最终成果)。
应用的pdf.js@2.2.28 读取PDF文件,通过canvas 以图像的模式回显在页面上(因为不是单纯的显示,还有很多交互,最终抉择了这个计划)
开始所有都挺顺利的,拖拽计算坐标,PDF回显封装,印章属性设置。。。
PDF回显电子签章
在开发过程中没成想PDF回显深了一个大坑,开始是曾经有印章不能回显,在网上搜寻要改pdf.work.js源码,搜寻上面这个判断,都注掉就行了
// if (data.fieldType === 'Sig') {// data.fieldValue = null;// _this3.setFlags(_util.AnnotationFlag.HIDDEN);// }
尽管可能显示印章了,但在翻页时会产生只显示一半色彩的状况,钻研了好几天,更换pdf.js 版本、缩小canvas 配置项都没胜利。
忽然在一次测试中发现,要是上来就把所有都渲染进去如同就没问题,随即用了一个比拟low的办法,就是上来就把所有pdf都渲染DOM,但不显示进去(display:none),只有与以后页码雷同的才显示,目前测试还没发现问题。
另,现有几个小问题:
- 布局时尽量用 padding、box-sizing: content-box;,用margin 有时取坐标数据不好管制;
- 因为找的例子是适配挪动端的,外面有个参数window.devicePixelRatio,这个在mac 下通常为2,因为挪动端用这个计算了宽度,在mac 下应用的时候根底坐标都*2了,PC 无脑改成1;
对于 window.devicePixelRatio 能够具体参考:
张鑫旭-设施像素比devicePixelRatio简略介绍
附上pdf 的例子
外围渲染pdf 的局部也是网上收集的,依据业务再进行了批改;pdfjs-fix-sig 只是把电子签章那块解决了,顺手公布在本人的私服上了,npm 上必定没有,失常引到的名称是 pdfjs-dist
<template> <div class="pdf-box" id="pdf-box"> <div class="pdf-page-btn-box"> <y-button @click="handleSub()" :disabled="disabledSub">上一页</y-button> <div class="input-box"> <input class="input-wrap" :title="pdfCurrentPage" v-model="tunePage" @blur="pageChange" @keyup.13="pageChange"/> </div> <div class="num-page-box"> / {{pdf_pages}}</div> <y-button @click="handleAdd()" :disabled="disabledAdd">下一页</y-button> </div> <div class="pdf-view-wrap"> <div ref="canvasBox" class="canvas-box" id="canvas-box" :style="{width:`${pdf_div_width}px`,margin:'0 auto'}"> <slot name="drag-collection-box"></slot> <!-- <canvas ref="canvasPage" :id="'the-canvas'+pdfCurrentPage"--> <!-- style="border: 1px #eeeeee solid;"></canvas>--> <template v-for="index in pdf_pages"> <canvas ref="canvasPage" :id="'the-canvas'+index" v-show="index === Number(pdfCurrentPage)" style="border: 1px #eeeeee solid;"></canvas> </template> </div> </div> <ul v-if="sealPlaceShow" class="seal-place" ref="sealPlace" :style="`height: ${canvasHeight}px`"> <li v-for="index in 5"></li> </ul> </div></template><script> // 筹备PDF文件和签章数量、类型 import Vue from "vue"; let PDFJS = require('pdfjs-fix-sig'); PDFJS.GlobalWorkerOptions.workerSrc = Vue.prototype.GLOBAL.dataUrl + '/staticFront/script/pdfjs-fix-sig/build/pdf.worker.min.js'; export default { name: "pdf-box", props: { pdfSrc: { type: String }, sealPlaceShow: { type: Boolean, default: false } }, data() { return { pdf_scale: 1,//pdf放大系数 (1.33 回显的pdf 章是100,但思考到回显的pdf是按595*841展现【所对应应的章是75*75】),A4: 595 * 841 pdf_pages: null, pdfDoc: null, pdf_div_width: '595', pdf_src: null, pdfCurrentPage: 1, tunePage: 1, disabledAdd: false, disabledSub: true, // 初始不能减页 canvasWidth: 0, canvasHeight: 0, sealPlaceFlag: false, } }, watch: { sealPlaceShow: { handler(val) { this.sealPlaceFlag = val; }, immediate: true } }, created() { if (this.pdfSrc.length > 0) { this.getPdfUrl(); } else { this.$Message.error({content: '请应用正确的PDF文件'}); } }, methods: { getPdfUrl() { //取得pdf //例子:加载pdf文件示例; this.loadFile(this.pdfSrc); //线上申请 文件流(2020年11月03日 08:55:02 未测试) // this.$http.get(this.pdfSrc).then((res) => { // this.pdf_src = res.url; // this.loadFile(this.pdf_src) // }, (err) => { // console.log(err); // }); }, //初始化pdf loadFile(url) { let _this = this; let loadingTask = PDFJS.getDocument(url); loadingTask.promise.then((pdf) => { _this.pdfDoc = pdf; _this.pdf_pages = _this.pdfDoc.numPages; // 总页数 _this.$nextTick(() => { _this.setAddSubBtn(); for (let i = 1; i <= _this.pdf_pages; i++) { _this.renderPage(i) } }) }) }, //渲染pdf页 renderPage(num) { let _this = this; this.pdfDoc.getPage(num) .then((page) => { let canvas = document.getElementById('the-canvas' + num); let ctx = canvas.getContext('2d'); // let dpr = window.devicePixelRatio || 1; // mac 的 window.devicePixelRatio 为 2 let dpr = 1; let bsr = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1; let ratio = dpr / bsr; let viewport = page.getViewport({scale: this.pdf_scale}); canvas.width = viewport.width * ratio; canvas.height = viewport.height * ratio; _this.canvasWidth = canvas.width; _this.canvasHeight = canvas.height; _this.$emit('setCanvasAttr', { canvasWidth: _this.canvasWidth, canvasHeight: _this.canvasHeight, pdfCurrentPage: _this.pdfCurrentPage, numPages: this.pdfDoc.numPages }); canvas.style.width = viewport.width + 'px'; _this.pdf_div_width = viewport.width; // 定位在左边时须要canvas的宽度进行计算 canvas.style.height = viewport.height + 'px'; ctx.setTransform(ratio, 0, 0, ratio, 0, 0); let renderContext = { canvasContext: ctx, viewport: viewport }; page.render(renderContext).promise.then((res) => { // console.log(this.getBounding('canvasPage')); // console.log(this.getEleClass('layout-menu-left').clientWidth); // console.log(this.getBounding('canvasPage').right - this.getEleClass('layout-menu-left').clientWidth + 'px'); }); }); }, // 减页 handleSub() { this.pdfCurrentPage--; this.tunePage = this.pdfCurrentPage; this.setAddSubBtn(); }, // 加页 handleAdd(_str) { this.pdfCurrentPage++; this.tunePage = this.pdfCurrentPage; this.setAddSubBtn(); }, // 判断翻页按钮是否禁用并渲染canvas setAddSubBtn() { this.disabledSub = this.pdfCurrentPage <= 1; this.disabledAdd = this.pdfCurrentPage >= this.pdf_pages; // this.renderPage(this.pdfCurrentPage); this.$emit('setCanvasAttr', { canvasWidth: this.canvasWidth, canvasHeight: this.canvasHeight, pdfCurrentPage: this.pdfCurrentPage, numPages: this.pdfDoc.numPages }); }, pdfChangePage() { this.$emit('pdf-change-page', true); }, getEleClass(_str) { return document.getElementsByClassName(_str)[0]; }, getBounding(_str) { return this.$refs[_str].getBoundingClientRect(); }, pageChange() { // 输出字符容错 this.pdfCurrentPage = isNaN(Number(this.tunePage)) ? 1 : Number(this.tunePage); // 输出大数容错 this.pdfCurrentPage = this.tunePage > this.pdf_pages ? this.pdf_pages : this.tunePage; this.setAddSubBtn(); } } }</script>
父级援用
<pdfBox ref="pdfBox" :pdfSrc="doSignData.originFile" :canvasHeight="canvasHeight" :sealPlaceShow="true"> <div slot="drag-collection-box" v-if="$route.meta.viewType !== 'view'"> <!-- 这个插槽放在pdf 上显示的印章 --> </div> </pdfBox>
最初放上款式
@drag-block-size: 75px;.e-sign-wrap { .pdf-page-btn-box { position: absolute; text-align: right; font-size: 12px; right: 0; z-index: 5; padding: 7px; color: #42474f; .input-box, .num-page-box { display: inline-block; height: 22px; line-height: 22px; } .input-box { width: 30px; .input-wrap { width: 100%; padding: 0 2px; font-size: 12px; border: 1px transparent solid; border-bottom: 1px #dddee1 solid; border-radius: 2px; color: #495060; text-align: right; &:focus { outline: none; } } } .num-page-box { vertical-align: middle; } } .pdf-box { float: left; font-size: 0; position: relative; .pdf-view-wrap { position: relative; display: inline-block; .drag-box { li { cursor: move; user-select: none; position: absolute; opacity: .7; z-index: 5; border: 1px dashed transparent; .party { color: #ffffff; height: 24px; line-height: 24px; padding-left: 5px; width: 100%; font-size: 12px; } .content { width: 98px; height: 49px; line-height: 18px; padding: 5px; font-size: 14px; text-align: center; display: table-cell; vertical-align: middle; word-break: break-all; .sign-type { font-size: 12px; } } &.drag-block { width: @drag-block-size; height: @drag-block-size; background-color: #cfddfb; .party { background-color: #9bbcff; } .content { color: #78a5ff; } } &.place-block { width: @drag-block-size; height: @drag-block-size; background-color: #ffcccc; .party { background-color: #f09494; } .content { color: #f09494; line-height: 16px; padding: 0; font-size: 12px; } //.btn-box { // .btn { // width: 100%; // } //} } &.drag-block, &.place-block { &.checked::after { content: ''; width: 100%; height: 100%; border: 2px #004CC0 solid; display: block; position: absolute; top: 0; left: 0; z-index: 0; } img { -webkit-user-drag: none; } } .btn-box { cursor: pointer; .btn { //float: left; width: 100%; font-size: 12px; text-align: center; height: 24px; line-height: 24px; background-color: #004cc0; color: #ffffff; } } } } canvas { width: 595px; height: 842px; //max-width: 595px; //max-height: 841px; } } .seal-place { display: inline-block; position: absolute; right: -25px; min-height: 841px; li { width: 5px; height: 100%; display: inline-block; border: 1px #eee solid; border-left: none; } } }}