vue2 之 实现pdf电子签章 您所在的位置:网站首页 重新载入电子发票签章怎么操作 vue2 之 实现pdf电子签章

vue2 之 实现pdf电子签章

2024-07-11 23:08| 来源: 网络整理| 查看: 265

一、前情提要 1. 需求

仿照e签宝,实现pdf电子签章 => 拿到pdf链接,移动章的位置,获取章的坐标

技术 : 使用fabric + pdfjs-dist + vuedraggable

2. 借鉴

一位大佬的代码仓亏 : 地址

一位大佬写的文章 :地址

3. 优化

在大佬的代码基础上,进行了些许优化,变的更像e签宝

二、下载

ps : 怕版本不同,导致无法运行,请下载指定版本

1. fabric

fabric : 是一个功能强大且操作简单的 Javascript HTML5 canvas 工具库

npm install [email protected] 2. pdfjs-dist npm install [email protected] 问题一

注意 : 最好配置一下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

3. vuedraggable npm install [email protected] 三、代码 1. 准备pdf文件

text.pdf 可放置在 src/static 文件夹中

ps : 线上最好让后端返回pdf链接,因为存在pdf跨域问题

2. 大佬的代码

我的印章 { (percentage * 100).toFixed(0) + '%' }} + --> 上一页 下一页 {{ pageNum }}/{{ numPages }}页 跳转 删除签章 清除所有签章 提交所有签章信息 任务信息 文件主题 {{ taskInfo.title }} 发起方 {{ taskInfo.uname }} 截止时间 {{ taskInfo.endtime }} 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 = 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 = 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); } } }; /*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; } 3. 优化后的代码 

合同签章 删除签章 清空签章 提交签章 印章

将示例印章标识拖到文件相应区域即可获取签章位置

{{ (scale * 100).toFixed(0) + '%' }} /{{ defaultNumPages }} 位置信息 {{ item.name }} {{ item.page }} {{ item.left }} {{ item.top }} // 拖拽插件 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.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 { 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.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 { const obj = {}; keys.forEach((key) => { obj[key] = item[key]; }); arr.push(obj); }); // 4. 转成json字符串 this.shadowInputValue = JSON.stringify(arr); }, /** * 操作相关部分 */ // 上一页 prevPage() { 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); } } }; .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; } } } } } }



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有