首页 > 其他分享 >vue 电子盖章

vue 电子盖章

时间:2024-01-22 16:11:08浏览次数:22  
标签:盖章 canvas vue obj width top 电子 height item

 

 

 

<!-- //?模块说明 =>  合同盖章模块 addToTab-->
<template>
  <div class="contract-signature-view">
    <!-- <div class="title-operation">
      <h2 class="title">图片盖章</h2>
      
    </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" @blur="pageNum = pageNum.replace(/[^\d]/g, '')" />
            <span class="default-text">/{{ defaultNumPages }}</span>
            <i class="icon el-icon-arrow-right" @click="nextPage" />
          </div>
          <div class="operation">
            <el-button class="btn-sc btn-red" @click="removeSignature">删除印章</el-button>
            <el-button class="btn-plzf btn-red" @click="clearSignature">清空印章</el-button>
            <el-button class="btn-tj" @click="submitSignature">保存盖章</el-button>
          </div>
        </div>
        <div id='pop'></div>
        <!-- 画图 -->
        <div ref="pdfView" id="pdf-view" class="out-view" :class="{ 'is-show': isShowPdf }">
          <div class="canvas-layout" v-for="item in numPages" :key="item">
            <!-- pdf部分 -->
            <canvas class="the-canvas"></canvas>
            <!-- 盖章部分 -->
            <canvas :ref="'ele-canvas'+item" 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';
import api from "../../public/config/api/api";
// pdf插件
import { fabric } from 'fabric';
import html2canvas from "html2canvas";
import JsPDF from "jspdf";
import workerSrc from 'pdfjs-dist/legacy/build/pdf.worker.entry';
import { getBase64FromUrl, changeImageToBase64 } from "./download"
import { Loading } from 'element-ui'
const pdfjsLib = require('pdfjs-dist/legacy/build/pdf.js');
pdfjsLib.GlobalWorkerOptions.workerSrc = workerSrc;

export default {
  components: { draggable },
  props: {
    // pdfUrl
    pdfUrl: {
      type: String,
      default: "",
    }
  },
  data () {
    return {
      // pdf地址
      // 左侧盖章列表
      mainImagelist: [],
      // 右侧坐标数据
      coordinateList: [{ name: '印章名称', page: '所在页面', left: 'x坐标', top: 'Y坐标' }],
      // 总页数
      numPages: 1,
      defaultNumPages: 1,
      // 当前页
      pageNum: 1,
      // 缩放比例
      scale: 1.5,
      // 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: '',
      loadingInstance: '',
      pdfBase64: '',
      accessToken: '',
      position: 0,
      judge: true,
      //循环清空pdfDoc避免内存溢出
      pdfDocNullTime: null,
    };
  },
  async created () {
    this.outViewScrollClose()
    this.accessToken = JSON.parse(localStorage.getItem('token'))?.access_token || ''
    this.mainImagelist = [];
    this.getSignChapter();
    this.showpdf(this.pdfUrl)
  },
  beforeUnload () {
    // 在组件即将被销毁之前执行的清理工作或操作
    this.judge = false
  },
  mounted () {

  },
  methods: {
    // 提交数据
    submitSignature () {
      this.removeActive();//清除所有的盖章选中状态
      if (this.coordinateList.length > 1) {
        this.formatSeal()
        setTimeout(() => {
          this.saveSign()
        }, 100);
      } else {
        this.$message.warning("至少添加一个印章!")
      }
    },
    // 保存前格式印章
    formatSeal () {
      let arr = []
      const keys = ['width', 'height', 'top', 'left', 'angle', 'scaleX', 'scaleY', 'absTop', 'widthX', 'heightY'];
      this.canvasEle.forEach((item) => {
        let object = item.getObjects();
        if (object.length > 0) {
          object.forEach((obj) => {
            for (let i = 0; i < this.coordinateList.length; i++) {
              var coord = this.coordinateList[i];
              if (coord.cacheKey == obj.cacheKey) {
                coord.absTop = Math.abs(obj.top - (obj.canvas.height - obj.getBoundingRect().height))
                coord.widthX = obj.width * obj.scaleX
                coord.heightY = obj.height * obj.scaleY
                coord.width = obj.width
                coord.height = obj.height
                coord.top = obj.top
                coord.left = obj.left
                keys.forEach((item) => {
                  coord[item] = Math.ceil(coord[item] / this.scale);
                });
                break;
              }
            }
          })
        }
      });
    },
    //保存并提交盖章数据
    async saveSign () {
      this.loadingInstance = Loading.service({ fullscreen: true })
      let sealList = [...this.coordinateList]
      sealList.splice(0, 1)
      for (let seal of sealList) {
        seal.top = seal.absTop
        seal.width = seal.widthX
        seal.height = seal.heightY
      }
      let data = {
        pdfUrl: this.pdfUrl,
        sealList: sealList
      }
      this.$parent.$parent.$parent.saveSignSeal(data);
    },
    closeLoading () {
      this.loadingInstance.close();
    },
    // 获取文件详情
    async getSignChapter () {
      var that = this;
      const { data: res } = await this.$http(api.getSignChapter)
      if (!res.success) {
        this.$message.warning('请添加图片章!');
        return
      }
      for (const key in res.data) {
        var path = res.data[key].fileUrl     //这里的图片URL链接跟pdf的要同域
        changeImageToBase64(path).then((data) => {
          var name = res.data[key].name;
          that.mainImagelist.push({ name: name, img: data, path: path })
        }).catch((err) => {
          // console.log('输出reject的结果:',err) //输出reject的结果:数字大于5
        })
      }
    },
    // 解析pdf
    async  showpdf (data) {
      //文字无法正常显示,需要添加PDFJS.cMaoUrl PDFJS.cMapPacked 这两个值
      await pdfjsLib
        .getDocument({
          url: data,
          rangeChunkSize: 65536 * 16,
          disableAutoFetch: true,
          disableStream: true
        })
        .promise.then((pdfDoc_) => {
          this.pdfDoc = pdfDoc_;
          this.numPages = this.pdfDoc.numPages;
          this.defaultNumPages = this.pdfDoc.numPages;
          this.$nextTick(() => {
            var ratio = window.devicePixelRatio || 1; //设置设备像素比 devicePixelRatio
            this.canvas = document.querySelectorAll('.the-canvas');
            this.canvas.forEach((item) => {
              var ctx = item.getContext('2d', { willReadFrequently: true })
              ctx.scale(ratio, ratio);
              ctx.font = (48 * ratio) + 'px Microsoft YaHei';
              this.ctx.push(ctx);
            });
            // 循环渲染pdf
            let i = 1

            this.forPdfShow(i);

          });
        });
    },
    async forPdfShow (i) {
      if (!this.judge) {
        return
      }
      try {
        await this.renderPage(i).then(() => {
          this.renderPdf({
            width: this.canvas[i - 1].width,
            height: this.canvas[i - 1].height
          });
          i++;
          if (i <= this.numPages) {
            setTimeout(() => {
              this.forPdfShow(i)
            }, 1);
          }

          if (i - 1 == this.numPages) {
            setTimeout(() => {
              this.renderFabric();
              this.canvasEvents();
            }, 100);
          }
        });
      } catch (error) {
        console.log(error);
        this.pdfDoc = null;
        this.judge = false
      }
    },
    beforeDom () {
      this.pdfDoc = null
    },
    // 设置pdf宽高,缩放比例,渲染pdf
    renderPage (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 () {
      let width = 0
      let height = 0
      var _that = this;
      // 1. 拿到全部的canvas-layout
      const canvasLayoutDom = document.querySelectorAll('.canvas-layout');
      // 2. 循环遍历 
      for (let i = 0; i < canvasLayoutDom.length; i++) {
        const item = canvasLayoutDom[i];
        this.canvasLayoutTopList.push({ obj: item, top: item.offsetTop });
        // 5. 拿到pdf的canvas
        const pCenter = item.querySelector('.the-canvas');
        width = pCenter.clientWidth
        height = pCenter.clientHeight
        // 3. 设置宽高和居中
        item.style.width = 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');

        // 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';

        if (i == canvasLayoutDom.length - 1) {
          // 现形
          setTimeout(() => {
            this.pdfDoc = null
            this.isFirst = false;
            this.isShowPdf = true;
            this.outViewDom = document.querySelector('.out-view');
            // 开启监听窗口滚动
            this.outViewScroll();
            this.$nextTick(() => {
              _that.pdfDoc = null
              let j = 0
              this.pdfDocNullTime = setInterval(() => {
                _that.pdfDoc = null
                j++;
                if (j > 15) {
                  clearInterval(_that.pdfDocNullTime);
                }
              }, 1000);
            })
          }, 100);
        }
      }


      const divElement = this.$refs.pdfView;
      divElement.style.width = width + 'px'; // 修改div的宽度

    },
    // 开启监听窗口滚动
    outViewScroll () {
      this.outViewDom.addEventListener('scroll', this.outViewRun);
    },
    // 关闭监听窗口滚动
    outViewScrollClose () {
      try {
        this.outViewDom.removeEventListener('scroll', this.outViewRun);
      } catch (error) {
        // 
      }

    },
    // 窗口滚动
    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
            );
          }
          obj.absTop = Math.abs(obj.top - (obj.canvas.height - obj.getBoundingRect().height))
          obj.widthX = obj.width * obj.scaleX
          obj.heightY = obj.height * obj.scaleY
          const findIndex = this.coordinateList
            .slice(1)
            .findIndex((coord) => coord.cacheKey == obj.cacheKey);
          const keys = ['width', 'height', 'top', 'left', 'angle', 'scaleX', 'scaleY', 'absTop', 'widthX', 'heightY'];
          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);

      let absTop = ''
      this.addSeal({
        sealUrl: this.mainImagelist[e.newDraggableIndex].path,
        left,
        top,
        absTop,
        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];
      currentSignInfo.absTop = Math.abs(currentSignInfo.top - (currentSignInfo.canvas.height - currentSignInfo.getBoundingRect().height))
      currentSignInfo.widthX = currentSignInfo.width * currentSignInfo.scaleX
      currentSignInfo.heightY = currentSignInfo.height * currentSignInfo.scaleY
      // 2. 拼接数据
      const keys = ['width', 'height', 'top', 'left', 'angle', 'scaleX', 'scaleY', 'absTop', 'widthX', 'heightY'];
      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}`;
      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.warning('请选择要删除的印章');
      // 3. 判断选中盖章的个数是否大于1
      if (findItem.length > 1) {
        this.removeActive();
        return this.$message.warning('只能选择删除一个印章,请重新选择');
      }
      // 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();
    },

  }
};
</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;
            width: 100px;
            .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;
            margin-left: 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: unset;
        }
      }
      .loading {
        width: 20px;
        height: 20px;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        z-index: 999;
        .el-loading-mask {
          background-color: transparent;
        }
      }
    }
    .position-info {
      width: 420px;
      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;
          }
        }
      }
    }
  }

  .eui-three-btn .el-button span::before {
  }
}
</style>

 

标签:盖章,canvas,vue,obj,width,top,电子,height,item
From: https://www.cnblogs.com/aknife/p/17980272

相关文章

  • gin-vue-admin部署
    前言gin-vue-admin是什么gin-vue-admin是一个基于Gin和Vue.js的全栈前后端分离框架。它提供了一套完整的开发工具和模板,用于快速搭建企业级Web应用程序。gin-vue-admin后端使用Go语言和Gin框架实现,前端使用Vue.js和ElementUI组件库,通过RESTfulAPI进行通......
  • Ubuntu下安装Vue
    参考来源https://www.cnblogs.com/architectforest/p/14913505.htmlhttps://blog.csdn.net/haonan_z/article/details/122608063一、下载NodeNodejs官网手动安装最新的LTS版本选择版本进行下载,下载后解压缩使用命令或者界面操作均可安装Nodejs移动到软件安装目录li......
  • 开放签电子签章工具版升级至 1.1 版本,解决跨平台、跨语言集成使用
    本周开放签开源工具版增加了SDK与API能力,更新至1.1版本,使开放签电子签章工具能力进一步提升。SDK将便于java用户直接使用CA证书颁发和签名能力。API接口采用HTTP(S)通讯,JSON报文格式,具有跨平台、跨语言特性,专为各类开发语言用户提供服务,便于其他语言的开发者快速集成和应用电子签......
  • npm ERR! code 1 npm ERR! path E:\20231213\vue-element-admin\node_modules\nod
    执行npminstall报错,根据下面报错信息可知,是由于nodejs和node-sass版本不一致造成的,也就是当前项目比较旧,而我安装的nodejs比较新。PSE:\20231213\vue-element-admin>PSE:\20231213\vue-element-admin>PSE:\20231213\vue-element-admin>PSE:\20231213\vue-element-adm......
  • vue3中Fragment特性的一个bug,需要留意的注意事项
    vue3中的Fragment模版碎片特性是什么,简单的理解就是template模板代码不在像vue2中那样必须在根节点在包裹一层节点了。vue2写法<template><div><h1>标题</h1><p>正文内容</p></div></template>vue3写法<template><h1>标题</h1>......
  • 零基础入门Vue之窥探大法——计算与侦听
    前言在上一小节我介绍了我学习vue入门插值语法的过程。在本篇,我将记录我对vue的计算属性和侦听器的学习记录注:本篇对于”侦听“和”监听“是一个意思一、计算属性在官网上,可以看到这样一个例子:<divid="example">{{message.split('').reverse().join('')}}</di......
  • Vue3与Vue2的深度对比:你不可不知的差异!
    Vue3框架的优点特点首次渲染更快diff算法更快内存占用更少打包体积更小更好的Typescript支持CompositionAPI 组合API一、生命周期对于生命周期来说,整体上变化不大,只是大部分生命周期钩子名称上+“on”,功能上是类似的。不过有一点需要注意,Vue3在组合式API(CompositionAPI,下......
  • VUE框架MVVM架构思想解析与实现封装dom对象------VUE框架
    <!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width,initial-scale=1.0"><title>Document</title>......
  • 电子行业涵盖方向
    电子行业是一个快速发展的行业,涵盖了从电子设备制造到软件开发和服务的各个方面。这个行业的主要驱动力包括技术创新、消费者需求的增长以及全球经济的扩张。在过去的几年中,电子行业的发展主要集中在以下几个方面:人工智能和机器学习:这些技术正在改变我们的生活方式,从智能家居设备到......
  • vue3
    Vue31新建项目1.1vue-cli创建vue-V查看vue版本,必须高于4.5.0启动测试cdvue3_testnpmrunserve运行成功1.2vite创建命令1.3分析工程结构main.js//引入的不再是Vue构造函数了,引入的是一个名为createApp的工厂函数import{createApp}from'vue'......