赞
踩
仿照e签宝,实现pdf电子签章 => 拿到pdf链接,移动章的位置,获取章的坐标
技术 : 使用fabric + pdfjs-dist + vuedraggable
一位大佬的代码仓亏 : 地址
一位大佬写的文章 :地址
在大佬的代码基础上,进行了些许优化,变的更像e签宝
ps : 怕版本不同,导致无法运行,请下载指定版本
fabric : 是一个功能强大且操作简单的 Javascript HTML5 canvas 工具库
npm install fabric@5.3.0
npm install pdfjs-dist@2.5.207
注意 : 最好配置一下babel,因为打包的时候可能会报错
因为babel默认不会转化node_modules中的包,但是pdfjs-dist用了es6的东东
- // 安装包
- npm install babel-loader @babel/core @babel/preset-env -D
在webpack.config.js中配置
- {
- test: /\.js$/,
- loader: 'babel-loader',
- include: [
- resolve('src'),
- // 转化pdfjs-dist,之所以分开写,是因为pdfjs-dist里面有很多es6的语法,但是我们只需要转化pdfjs-dist里面的web文件夹下的js文件
- resolve('node_modules/pdfjs-dist/web/pdf_viewer.js'),
- resolve('node_modules/pdfjs-dist/build/pdf.js'),
- resolve('node_modules/pdfjs-dist/build/pdf.worker.js'),
- resolve('node_modules/pdfjs-dist/build/pdf.worker.entry.js') ]
- },
pdf.js文件过大,可以给 .babelrc 加上属性,"compact": false
npm install vuedraggable@2.24.3
text.pdf 可放置在 src/static 文件夹中
ps : 线上最好让后端返回pdf链接,因为存在pdf跨域问题
- <!-- //?模块说明 => 合同签章模块 -->
- <template>
- <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>
- </template>
- <script>
- import draggable from 'vuedraggable';
- import { fabric } from 'fabric';
- import workerSrc from 'pdfjs-dist/es5/build/pdf.worker.entry';
- import * as pdfjsViewer from 'pdfjs-dist/web/pdf_viewer';
- const pdfjsLib = require('pdfjs-dist/es5/build/pdf.js');
- pdfjsLib.GlobalWorkerOptions.workerSrc = workerSrc;
- 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: {}
- // percentage: 1
- };
- },
- computed: {
- hasSigna() {
- if (this.canvasEle && this.canvasEle.getObjects()[0]) {
- return true;
- } else {
- return false;
- }
- }
- },
- created() {
- var that = this;
- that.mainImagelist = [require('@/assets/img/projectCenter/sign.png'), require('@/assets/img/projectCenter/seal.png')];
- that.taskInfo = { title: '测试盖章', uname: '张三', endtime: '2021-09-01 17:59:59' };
- this.setPdfArea();
- },
- mounted() {
- // this.showpdf(this.pdfUrl);
- if (!pdfjsLib.getDocument || !pdfjsViewer.PDFViewer) {
- // eslint-disable-next-line no-alert
- alert('Please build the pdfjs-dist library using\n `gulp dist-install`');
- }
- },
- 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 context
- let renderContext = {
- canvasContext: _this.ctx,
- viewport: viewport
- };
- let renderTask = page.render(renderContext);
- // Wait for rendering to finish
- renderTask.promise.then(() => {
- _this.pageRendering = false;
- if (_this.pageNumPending !== null) {
- // New page rendering is pending
- this.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) {
- 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 ignore
- if (obj.currentHeight > obj.canvas.height || obj.currentWidth > obj.canvas.width) {
- return;
- }
- obj.setCoords();
- // top-left corner
- if (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 corner
- if (
- 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 = './static/text.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;margin-top: 10px;';
- }
- }
- },
- pageNum: function () {
- this.commonSign(this.pageNum);
- this.queueRenderPage(this.pageNum);
- }
- }
- };
- </script>
- <style lang="scss" scoped>
- /*pdf部分*/
- #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('~@/assets/img/projectCenter/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;
- }
- </style>

- <!-- //?模块说明 => 合同签章模块 addToTab-->
- <template>
- <div class="contract-signature-view">
- <div class="title-operation">
- <h2 class="title">合同签章</h2>
- <div class="operation">
- <el-button type="danger" @click="removeSignature">删除签章</el-button>
- <el-button type="danger" @click="clearSignature">清空签章</el-button>
- <el-button type="primary" @click="submitSignature">提交签章</el-button>
- </div>
- </div>
- <div class="section-box">
- <!-- 签章图片 -->
- <aside class="signature-img">
- <div class="info">
- <h3 class="name">印章</h3>
- <p class="text">将示例印章标识拖到文件相应区域即可获取签章位置</p>
- </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.img"
- class="item"
- style="text-align: center"
- >
- <img :src="item.img" width="100%;" height="100%" class="img" />
- </li>
- </transition-group>
- </draggable>
- </aside>
- <!-- 主体区域 -->
- <section class="main-layout" :class="{ 'is-first': isFirst }">
- <!-- 操作 -->
- <div class="operate-box">
- <div class="slider-box">
- <el-slider
- class="slider"
- v-model="scale"
- :min="0.5"
- :max="2"
- :step="0.1"
- :show-tooltip="false"
- @change="sliderChange"
- />
- <span class="scale-value">{{ (scale * 100).toFixed(0) + '%' }}</span>
- </div>
- <div class="page-change">
- <i class="icon el-icon-arrow-left" @click="prevPage" />
- <!-- :min="1" -->
- <el-input
- class="input-box"
- v-model.number="pageNum"
- :max="defaultNumPages"
- @change="cutover"
- />
- <span class="default-text">/{{ defaultNumPages }}</span>
- <i class="icon el-icon-arrow-right" @click="nextPage" />
- </div>
- </div>
- <!-- 画图 -->
- <div class="out-view" :class="{ 'is-show': isShowPdf }">
- <div class="canvas-layout" v-for="item in numPages" :key="item">
- <!-- pdf部分 -->
- <canvas class="the-canvas" />
- <!-- 盖章部分 -->
- <canvas class="ele-canvas"></canvas>
- </div>
- </div>
- <i class="loading" v-loading="!isShowPdf" />
- </section>
- <!-- 位置信息 -->
- <div class="position-info">
- <h3 class="title">位置信息</h3>
- <ul class="nav">
- <li class="item" v-for="(item, index) in coordinateList" :key="index">
- <span>{{ item.name }}</span>
- <span>{{ item.page }}</span>
- <span>{{ item.left }}</span>
- <span>{{ item.top }}</span>
- </li>
- </ul>
- </div>
- </div>
- </div>
- </template>
- <script>
- // 拖拽插件
- import draggable from 'vuedraggable';
- // pdf插件
- import { fabric } from 'fabric';
- import workerSrc from 'pdfjs-dist/es5/build/pdf.worker.entry';
- const pdfjsLib = require('pdfjs-dist/es5/build/pdf.js');
- pdfjsLib.GlobalWorkerOptions.workerSrc = workerSrc;
-
- export default {
- components: { draggable },
- data() {
- return {
- // pdf地址
- pdfUrl: '',
- // 左侧签章列表
- mainImagelist: [],
- // 右侧坐标数据
- coordinateList: [{ name: '名称', page: '所在页面', left: 'x坐标', top: 'Y坐标' }],
- // 总页数
- numPages: 1,
- defaultNumPages: 1,
- // 当前页
- pageNum: 1,
- // 缩放比例
- scale: 1,
- // pdf是否显示
- isFirst: true,
- isShowPdf: false,
- // pdf最外层的out-view
- outViewDom: null,
- // 各页pdf的canvas-layout
- canvasLayoutTopList: [],
- // 用来签章的canvas数组
- canvasEle: [],
- // 绘图区域的宽高
- whDatas: null,
- // pdf渲染的canvas数组
- canvas: [],
- // pdf渲染的canvas的ctx数组
- ctx: [],
- // pdf渲染的canvas的宽高
- pdfDoc: null,
- // 隐藏的input,用来提交数据
- shadowInputValue: ''
- };
- },
- created() {
- this.mainImagelist = [
- { name: '印章', img: require('@/assets/img/projectCenter/contract-sign-img.png') }
- // { name: '印章', img: require('./sign.png') },
- // { name: '红章', img: require('@/assets/img/projectCenter/seal.png') }
- ];
- this.setPdfArea();
- },
- mounted() {},
- methods: {
- /**
- * pdf相关部分
- */
- // 设置PDF地址
- setPdfArea() {
- // // 1. 获取地址栏
- // const urlString = window.location.href;
- // // 2. 截取地址栏
- // const pdfStr = urlString.split('?')[1];
- // // 3. 截取pdf地址并解码
- // this.pdfUrl = decodeURIComponent(pdfStr.split('=')[1]);
-
- this.pdfUrl = './static/text.pdf';
- this.$nextTick(() => {
- this.showpdf(this.pdfUrl); // 接口返回的应该还有盖章信息,不只是pdf
- });
- },
- // 解析pdf
- showpdf(pdfUrl) {
- pdfjsLib
- .getDocument({ url: pdfUrl, rangeChunkSize: 65536, disableAutoFetch: false })
- .promise.then((pdfDoc_) => {
- this.pdfDoc = pdfDoc_;
- this.numPages = this.pdfDoc.numPages;
- this.defaultNumPages = this.pdfDoc.numPages;
- this.$nextTick(() => {
- this.canvas = document.querySelectorAll('.the-canvas');
- this.canvas.forEach((item) => {
- this.ctx.push(item.getContext('2d'));
- });
- // 循环渲染pdf
- for (let i = 1; i <= this.numPages; i++) {
- this.renderPage(i).then(() => {
- this.renderPdf({
- width: this.canvas[i - 1].width,
- height: this.canvas[i - 1].height
- });
- });
- }
- setTimeout(() => {
- this.renderFabric();
- this.canvasEvents();
- }, 1000);
- });
- });
- },
- // 设置pdf宽高,缩放比例,渲染pdf
- renderPage(num) {
- // console.log('this.canvas', this.canvas[num], num);
- return this.pdfDoc.getPage(num).then((page) => {
- const viewport = page.getViewport({ scale: this.scale }); // 设置视口大小
- this.canvas[num - 1].height = viewport.height;
- this.canvas[num - 1].width = viewport.width;
- // Render PDF page into canvas context
- const renderContext = {
- canvasContext: this.ctx[num - 1],
- viewport: viewport
- };
- page.render(renderContext);
- });
- },
- // 设置绘图区域宽高
- renderPdf(data) {
- this.whDatas = data;
- },
- // 生成绘图区域
- renderFabric() {
- // 1. 拿到全部的canvas-layout
- const canvasLayoutDom = document.querySelectorAll('.canvas-layout');
- // 2. 循环遍历
- canvasLayoutDom.forEach((item) => {
- this.canvasLayoutTopList.push({ obj: item, top: item.offsetTop });
- // 3. 设置宽高和居中
- item.style.width = this.whDatas.width + 'px';
- item.style.height = this.whDatas.height + 'px';
- item.style.margin = '0 auto 18px';
- item.style.boxShadow = '4px 4px 4px #e9e9e9';
-
- // 4. 拿到盖章canvas
- const canvasEle = item.querySelector('.ele-canvas');
- // 5. 拿到pdf的canvas
- const pCenter = item.querySelector('.the-canvas');
- // 6. 设置盖章canvas的宽高
- canvasEle.width = pCenter.clientWidth;
- canvasEle.height = this.whDatas.height;
- // 7. 创建fabric对象并存储
- this.canvasEle.push(new fabric.Canvas(canvasEle));
- // 8. 设置盖章canvas的样式
- const container = item.querySelector('.canvas-container');
- container.style.position = 'absolute';
- container.style.left = '50%';
- container.style.transform = 'translateX(-50%)';
- container.style.top = '0px';
- });
-
- // 现形
- this.isFirst = false;
- this.isShowPdf = true;
- this.outViewDom = document.querySelector('.out-view');
- // 开启监听窗口滚动
- this.outViewScroll();
- },
- // 开启监听窗口滚动
- outViewScroll() {
- this.outViewDom.addEventListener('scroll', this.outViewRun);
- },
- // 关闭监听窗口滚动
- outViewScrollClose() {
- this.outViewDom.removeEventListener('scroll', this.outViewRun);
- },
- // 窗口滚动
- outViewRun() {
- const scrollTop = this.outViewDom.scrollTop;
- const topList = this.canvasLayoutTopList.map((item) => item.top);
- // 增加一个最大值
- topList.push(Number.MAX_SAFE_INTEGER);
- for (let index = 0; index < topList.length; index++) {
- const element = topList[index];
- if (element <= scrollTop && scrollTop < topList[index + 1]) {
- this.pageNum = index + 1;
- break;
- }
- }
- },
- // scale滑块,重新渲染整个pdf
- sliderChange() {
- this.pageNum = 1;
- this.numPages = 0;
- this.canvasLayoutTopList = [];
- this.canvasEle = [];
- this.ctx = [];
- this.canvas = [];
- this.isShowPdf = false;
- // this.outViewScrollClose();
- this.whDatas = null;
- this.coordinateList = [{ name: '名称', page: '所在页面', left: 'x坐标', top: 'Y坐标' }];
- this.getSignatureJson();
- setTimeout(() => {
- this.numPages = this.pdfDoc.numPages;
- this.$nextTick(() => {
- this.canvas = document.querySelectorAll('.the-canvas');
- this.canvas.forEach((item) => {
- this.ctx.push(item.getContext('2d'));
- });
- // 循环渲染pdf
- for (let i = 1; i <= this.numPages; i++) {
- this.renderPage(i).then(() => {
- this.renderPdf({
- width: this.canvas[i - 1].width,
- height: this.canvas[i - 1].height
- });
- });
- }
- setTimeout(() => {
- this.renderFabric();
- this.canvasEvents();
- }, 1000);
- });
- }, 1000);
- },
- /**
- * 签章相关部分
- */
- // 签章拖拽边界处理,不能将图片拖拽到绘图区域外
- canvasEvents() {
- this.canvasEle.forEach((item) => {
- item.on('object:moving', (e) => {
- const obj = e.target;
- // if object is too big ignore
- if (obj.currentHeight > obj.canvas.height || obj.currentWidth > obj.canvas.width) {
- return;
- }
- obj.setCoords();
- // top-left corner
- if (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 corner
- if (
- 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
- );
- }
- // console.log('obj.cacheKey',obj.cacheKey);
- const findIndex = this.coordinateList
- .slice(1)
- .findIndex((coord) => coord.cacheKey == obj.cacheKey);
- const keys = ['width', 'height', 'top', 'left', 'angle', 'scaleX', 'scaleY'];
- keys.forEach((item) => {
- this.coordinateList[findIndex + 1][item] = Math.ceil(obj[item] / this.scale);
- });
- this.getSignatureJson();
- });
- });
- },
- // 拖拽结束
- end(e) {
- // 找到当前拖拽到哪一个canvas-layout上
- const currentCanvasLayout = e.originalEvent.target.parentElement.parentElement;
- const findIndex = this.canvasLayoutTopList.findIndex(
- (item) => item.obj == currentCanvasLayout
- );
- if (findIndex == -1) return false;
- // 取整
- const left = e.originalEvent.layerX < 0 ? 0 : Math.ceil(e.originalEvent.layerX / this.scale);
- const top = e.originalEvent.layerY < 0 ? 0 : Math.ceil(e.originalEvent.layerY / this.scale);
- // console.log('e', e, findIndex);
- this.addSeal({
- sealUrl: this.mainImagelist[e.newDraggableIndex].img,
- left,
- top,
- index: e.newDraggableIndex,
- pageNum: findIndex
- });
- },
- // 添加公章
- addSeal({ sealUrl, left, top, index, pageNum }) {
- fabric.Image.fromURL(sealUrl, (oImg) => {
- oImg.set({
- // 距离左边的距离
- left: left,
- // 距离顶部的距离
- top: top,
- // 角度
- // angle: 10,
- // 缩放比例,需要乘以scale
- scaleX: 0.8 * this.scale,
- scaleY: 0.8 * this.scale,
- index,
- // 禁止缩放
- lockScalingX: true,
- lockScalingY: true,
- // 禁止旋转
- lockRotation: true
- });
- this.canvasEle[pageNum].add(oImg);
- // 保存签章信息
- this.saveSignature({ pageNum, index, sealUrl });
- });
- // this.removeActive();
- },
- // 保存签章
- saveSignature({ pageNum, index, sealUrl }) {
- // 1. 拿到当前签章的信息
- let length = 0;
- let pageConfig = this.coordinateList.filter((item) => item.page - 1 == pageNum);
- if (pageConfig) length = pageConfig.length;
- const currentSignInfo = this.canvasEle[pageNum].getObjects()[length];
- // 2. 拼接数据
- const keys = ['width', 'height', 'top', 'left', 'angle', 'scaleX', 'scaleY'];
- const obj = {};
- keys.forEach((item) => {
- obj[item] = Math.ceil(currentSignInfo[item] / this.scale);
- });
- obj.cacheKey = currentSignInfo.cacheKey;
- obj.sealUrl = sealUrl;
- obj.index = index;
- obj.name = `${this.mainImagelist[index].name}${this.coordinateList.length}`;
- obj.page = pageNum + 1;
- this.coordinateList.push(obj);
- this.getSignatureJson();
- },
- // 签章生成json字符串
- getSignatureJson() {
- // 1. 判断是否有签章
- if (this.coordinateList.length <= 1) return (this.shadowInputValue = '');
- // 2. 拿到签章的信息,去除第一条
- const signatureList = this.coordinateList.slice(1);
- // 3. 拼接数据,只要left和top和page
- const keys = ['page', 'left', 'top'];
- const arr = [];
- signatureList.forEach((item) => {
- const obj = {};
- keys.forEach((key) => {
- obj[key] = item[key];
- });
- arr.push(obj);
- });
- // 4. 转成json字符串
- this.shadowInputValue = JSON.stringify(arr);
- },
- /**
- * 操作相关部分
- */
- // 上一页
- prevPage() {
- if (this.pageNum <= 1) return;
- this.pageNum--;
- // 滚动到指定位置
- this.outViewDom.scrollTop = this.canvasLayoutTopList[this.pageNum - 1].top;
- },
- // 下一页
- nextPage() {
- if (this.pageNum >= this.numPages) return;
- this.pageNum++;
- // 滚动到指定位置
- this.outViewDom.scrollTop = this.canvasLayoutTopList[this.pageNum - 1].top;
- },
- // 切换页码
- cutover() {
- this.outViewScrollClose();
- if (this.pageNum < 1) {
- this.pageNum = 1;
- } else if (this.pageNum > this.numPages) {
- this.pageNum = this.numPages;
- }
- // 滚动到指定位置
- this.outViewDom.scrollTop = this.canvasLayoutTopList[this.pageNum - 1].top;
- setTimeout(() => {
- this.outViewScroll();
- }, 500);
- },
- // 删除所有的签章选中状态
- removeActive() {
- this.canvasEle.forEach((item) => {
- item.discardActiveObject().renderAll();
- });
- },
- // 删除签章
- removeSignature() {
- // 1. 判断是否有选中的签章
- const findItem = this.canvasEle.filter((item) => item.getActiveObject());
- // 2. 判断选中签章的个数
- if (findItem.length == 0) return this.$message.error('请选择要删除的签章');
- // 3. 判断选中签章的个数是否大于1
- if (findItem.length > 1) {
- this.removeActive();
- return this.$message.error('只能选择删除一个签章,请重新选择');
- }
- // 4. 拿到选中的签章的cacheKey
- const activeObj = findItem[0].getActiveObject();
- const findIndex = this.coordinateList.findIndex(
- (item) => item.cacheKey == activeObj.cacheKey
- );
- // 5. 删除选中的签章
- findItem[0].remove(activeObj);
- // 6. 删除选中的签章的信息
- this.coordinateList.splice(findIndex, 1);
- this.getSignatureJson();
- },
- // 清空签章
- clearSignature() {
- this.canvasEle.forEach((item) => {
- item.clear();
- });
- this.coordinateList = [{ name: '名称', page: '所在页面', left: 'x坐标', top: 'Y坐标' }];
- this.getSignatureJson();
- },
- // 提交数据
- submitSignature() {
- console.log('this.coordinateList', this.coordinateList);
- }
- }
- };
- </script>
- <style lang="scss" scoped>
- .contract-signature-view {
- /*pdf部分*/
- .ele-canvas {
- overflow: hidden;
- }
- .title-operation {
- height: 80px;
- padding: 20px 40px;
- display: flex;
- align-items: center;
- justify-content: space-between;
- .title {
- font-size: 20px;
- font-weight: 600;
- }
- border-bottom: 1px solid #e4e4e4;
- }
- .section-box {
- position: relative;
- display: flex;
- height: calc(100vh - 60px);
-
- .signature-img {
- width: 240px;
- min-width: 240px;
- background-color: #fff;
- padding: 40px 15px;
-
- border-right: 1px solid #e4e4e4;
- .info {
- margin-bottom: 38px;
- .name {
- font-size: 18px;
- font-weight: 600;
- color: #000000;
- line-height: 25px;
- margin-bottom: 20px;
- }
- .text {
- font-size: 14px;
- color: #000000;
- line-height: 20px;
- }
- }
- .item {
- padding: 10px;
- border: 1px dashed rgba(0, 0, 0, 0.3);
- &:not(:last-child) {
- margin-bottom: 10px;
- }
- .img {
- vertical-align: middle;
- width: 120px;
- background-repeat: no-repeat;
- }
- }
- }
- .main-layout {
- flex: 1;
- background-color: #f7f8fa;
- position: relative;
-
- &.is-first {
- .operate-box {
- opacity: 0;
- }
- }
- .operate-box {
- opacity: 1;
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 40px;
- background-color: #fff;
- border-bottom: 1px solid #e4e4e4;
- display: flex;
- justify-content: center;
- align-items: center;
- .slider-box {
- width: 230px;
- display: flex;
- justify-content: center;
- align-items: center;
- border-left: 1px solid #e4e4e4;
- border-right: 1px solid #e4e4e4;
- .slider {
- width: 120px;
- }
- .scale-value {
- margin-left: 24px;
- font-size: 16px;
- color: #000000;
- line-height: 22px;
- }
- }
- .page-change {
- display: flex;
- align-items: center;
- margin-left: 30px;
- .icon {
- cursor: pointer;
- padding: 0 5px;
- color: #c1c1c1;
- }
- .input-box {
- border: none;
- /deep/ .el-input__inner {
- width: 34px;
- height: 20px;
- border: none;
- padding: 0;
- text-align: center;
- border-bottom: 1px solid #e4e4e4;
- }
- }
- .default-text {
- display: flex;
- line-height: 22px;
- margin-right: 5px;
- }
- }
- }
- .out-view {
- height: calc(100vh - 100px);
- margin: 40px auto;
- overflow-x: auto;
- overflow-y: auto;
- padding-top: 20px;
- text-align: center;
- opacity: 0;
- transition: all 0.5s;
- &.is-show {
- opacity: 1;
- }
- .canvas-layout {
- position: relative;
- text-align: center;
- margin: 0 auto 18px;
- }
- }
- .loading {
- width: 20px;
- height: 20px;
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- z-index: 999;
- /deep/ .el-loading-mask {
- background-color: transparent;
- }
- }
- }
- .position-info {
- width: 355px;
- min-width: 355px;
- border-left: 1px solid #e4e4e4;
- background-color: #fff;
- padding: 14px 15px;
- .title {
- font-size: 14px;
- font-weight: 400;
- color: #000000;
- line-height: 20px;
- padding-bottom: 18px;
- }
- .nav {
- display: flex;
- flex-direction: column;
- .item {
- display: flex;
- justify-content: space-between;
- padding: 10px 0;
- border-bottom: 1px solid #eee;
- &:first-child {
- background-color: #f7f8fa;
- }
- span {
- flex: 1;
- text-align: center;
- font-size: 12px;
- color: #000000;
- line-height: 20px;
- }
- }
- }
- }
- }
- }
- </style>

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。