最近公司做了一个我的项目,次要性能是把印章在 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;
}
}
}
}