pdf签章拖拽

效果就是如下图这样,左边一个签章的名字,拖拽到中间部分的pdf上, 把拖拽的位置存下来点确定的时候把位置和签章的图片信息发给后台。

可参考的网址:https://blog.csdn.net/blue__k/article/details/126723437

引用部分

      <dragSign v-if="active==1" ref="dragSign" :signDataList="signDataList" :curfilesListId="curfilesListId" :pdfUrl="pdfUrls" :defaultSign="defaultSign" @submitSign="submitSign" >
        <template v-slot:subBtn v-if="showSubmitBtn">
          <el-button type="primary" @click="handleSubmit">提交所有签章</el-button>
        </template>
        
      </dragSign>

 

封装的组件

<template>
    <el-row  :gutter="8">
      <!-- 左侧签章可拖拽的按钮部分 -->
      <el-col :span="6" style="border:1px solid red">
        <div class="sign-img-box">
          <div v-for="(item, i) in signDataList" :key="i">
            <div  class="sign-img labelWidth">{{item.label}}</div>
            <div class="btnsWrapper">
              <div v-for="(res, index) in item.data" :key="res.id" class="sign-img signBtns">
                <el-button  :class="stars(res)?'common-button-hightlight':''"
                @mousedown="sealPic(res, index)">
                <el-icon v-show="stars(res)"><Check /></el-icon>
                {{ res.name}}</el-button>
              </div>
            </div>
          </div>
      </div> 
      </el-col>
      <!-- 中间展示pdf和拖拽签章图片的部分 -->
      <el-col :span="18"  style="border:1px solid red">
        <div id="signContract" style="display: flex;flex-direction: column; align-items: center; margin-top: 29px;">
        <div v-for="page in pages" :key="page" class="pdf-container">
          <div>
            <div  v-show="currentPage === page"  :id="'pdf-box' + page" class="pdf-box">
            <el-input-number :min="40"  :max="150" v-show="sliderShow" class="sliderWrapper" @change="changeSlider" v-model="sliderValue" :step="10"></el-input-number>
                  <div class="pageTab">
                    <el-icon class="el-icon-arrow-left" @click="handleUpPage"><ArrowLeftBold /></el-icon>{{currentPage}}/{{pages}}
                    <el-icon class="el-icon-arrow-left" @click="handleNextPage"><ArrowRightBold/></el-icon>
                    </div>
              <canvas :id="'the-canvas' + page" v-loading="isPdfLoading" />
            </div>
          </div>
        </div>
      </div>
      </el-col>
    </el-row>
    <!-- 操作按鈕部分 -->
    <el-row style="position:relative">
      <div class="my-dialog-footer" >
        <el-button @click="upStep">上一步</el-button>
        <el-button type="primary" @click="handleSubmit">确定</el-button>
        <slot name="subBtn"></slot>
      </div>
    </el-row>
</template>
<script>
import VuePdfEmbed from "vue-pdf-embed";
 import * as PDF from "pdfjs-dist";
// 页面报错解决,在将 pdfjs-dist/build/pdf.worker.js 复制一份放到项目 public 目录下后引入 ----  避免获取总页数错误的问题


// import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry';
// PDFJS.workerSrc = pdfjsWorker;
 import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.min'
//PDFJS.GlobalWorkerOptions.workerSrc = pdfjsWorker
// import * as PDFJS from "pdfjs-dist";
// // 页面报错解决,在将 pdfjs-dist/build/pdf.worker.js 复制一份放到项目 public 目录下后引入 ----  避免获取总页数错误的问题
import * as PDFJS from 'pdfjs-dist/legacy/build/pdf.js' // 注意导入的写法
 PDFJS.GlobalWorkerOptions.workerSrc = "pdf.worker.js";
// import { createLoadingTask } from "vue3-pdfjs/esm"; // 获得总页数
import uploadPdf from "../../common/uploadOnePDFAndView";
export default {
  name: "dragSign",
  components: {uploadPdf,VuePdfEmbed},
  computed:{
      getPDFurl(){
        return this.pdfUrl?this.basicUrl+this.pdfUrl:null
      },
      stars() {
          return (star)=>{
          let index=this.signaturePositions.findIndex(res=>{return res.id==star.id})
          if(index>-1){
          return true
          }else{
            return false
          }
        }
      }
    },
    props: {
      defaultSign:{//回显签章的数据
        type:Array,
        default:[],
      },
      signDataList:{//选好签名 和签章的数据,可用于渲染按钮和拖到出图片的
        type:Array,
        default:[],
      },
      pdfUrl:{//pdf的路径
        type: String,
      default:null,
      },
    reportData: {
      type: Object,
      default: {},
    },
    id: {
      type: String,
      default: null,
    },
    handleType: {//操作类型, 是查看还是添加或者签章
      type: String,
      default: "add",
    },
    curfilesListId: {
      type: String,
      default: null,
    },
    },
  data() {
    return {
      basicUrl: import.meta.env.VITE_APP_BASE_API,
      loading:true,
      signList:[],
      filesList:[],
      sliderShow:false,sliderValue:150,
      curNode:null,
      orderId:null,
      filesListIndex:0,
      nemeList:[],
      signaturePositions: [],
      userSignDto:[],
      isPdfLoading: false,
      pdfDoc:null,
      pdfurls:null,
      pages: [],//pdfjs插件生成的总共页数
      currentPage: 1,//pdf当前的页码
      clientPageX: 0,
      clientPageY: 0,
      num: 0,//签章的第几个
      numName:0,//名字的第几个
      dom: '',//当鼠标点下签章js创建div元素,用于存储签章的图片,样式,位置等临时dom内容
      ishightlight: null,//签章按钮高亮的判断标识
      namehightlight: null,//签名按钮高亮的判断标识
      signatureList:[],//选择后的签章,即需要拖拽的签章集合
      active:0,//控制页面上一页下一页
      uploadFileList:[],
      uploadForm:{
        organId:'',
        auditId:'',
        appId:'',
        signNames:[],
      },
      reportAudit:[],
      authorizerList:[],
      organId:'',
      auditId:'',
      appId:'',
      allUserObj:{},
    };
  },
  methods: {
    //isDedaultSign 是判断是否是回显的数据调用的这个接口
    sealPic(item, i,isDedaultSign) {
      this.dom = document.createElement('div')
      this.num=this.num+1
      this.dom.setAttribute('class', 'seal-img mark ' + item.id + ' '+item.type+' ' + this.num)
      this.dom.setAttribute('id',this.num)
   if(isDedaultSign){//如果是打开这个页面时有回显的数据,那么走这里
    this.dom.setAttribute('style', 'position: absolute;left:'+item.sxaxis+ 'px;top:'+item.syaxis+'px')
    this.dom.innerHTML = `
        <div id="zoomPicture" style="cursor:pointer;"></div>
         <div id="delgongzhang"> ×</div>
        <div id="suregongzhang"> √</div>
        <img id="qz-img" src="${import.meta.env.VITE_APP_BASE_API + item.file}" class="${item.file}" style="height:${item.height}px">
         <div id="qz-img-cover"  style="width:100%;height:100%;position: absolute;left: 0px;top: 0px"></div>`
      this.dom.style.height = item.height+'px'
   }else{//这里是点击左侧签章按钮时根据签章的信息生成一个dom,然后给DOM加上删除  确定 签章图片 样式等
    this.dom.src = item.file
        item.isActive=true
      this.ishightlight = i
      this.dom.innerHTML = `
       <div id="zoomPicture" style="cursor:pointer;"></div>
      <div id="delgongzhang"> ×</div>
        <div id="suregongzhang">√</div>
        <img id="qz-img" src="${import.meta.env.VITE_APP_BASE_API+item.file}" class="${item.file}">
        <div id="qz-img-cover" style="width:100%;height:100%;position: absolute;left: 0;top: 0"></div>   `
   }
      // 鼠标抬开
      const dom1 = this.dom.querySelector('#delgongzhang')
      const dom2 = this.dom.querySelector('#suregongzhang')
      const dom3 = this.dom.querySelector('#zoomPicture')
      const appendDemo=()=>{
        document.querySelector('#pdf-box' + this.currentPage).appendChild(this.dom)
        document.onmousemove = null
        document.onmouseup = null
        this.dom.onmousedown = this.moveTo //创建的这个DOM鼠标按下时
        dom1.onclick = this.onclcikdel
        dom2.onclick = this.onclciksure
        dom3.onclick = this.onclcikZoom //点击放大缩小的按钮触发的方法
        this.dom.onmouseenter = this.mouseenter//鼠标指上这个DOM时,× √显示
        this.dom.onmouseleave = this.mouseleave//鼠标离开这个DOM时× √隐藏
      }
      if(isDedaultSign){
        appendDemo()
      }else{
        document.onmouseup = e => {
        appendDemo()
      }
      }
    },
    moveTo(e) {
      if (e.target.id ==='qz-img-cover') {
           e.target.parentNode.childNodes[5].style.display = 'block'//章子图片上的确定图标显示
        e.target.style.boxShadow = 'none'
        const odiv = e.currentTarget
        const disX = e.clientX - odiv.offsetLeft//鼠标x轴的位置-目标元素离父元素的左边距离
        const disY = e.clientY - odiv.offsetTop//鼠标Y轴的位置-目标元素离父元素的上边距离
        //给点击的当前DOM计算距左距上的style位置
        document.onmousemove = e => {
        e.preventDefault();
          let left = e.clientX - disX
          let top = e.clientY - disY
          if (left <= 0) {
            left = 0
          } else if (left >= document.querySelector('#pdf-box' + this.currentPage).clientWidth - odiv.clientWidth) {
            left = document.querySelector('#pdf-box' + this.currentPage).clientWidth - odiv.clientWidth
          } else {
            left = left - 10
          }
  
          if (top <= 0) {
            top = 0
          } else if (top >= document.querySelector('#pdf-box' + this.currentPage).clientHeight - odiv.clientHeight) {
            top = document.querySelector('#pdf-box' + this.currentPage).clientHeight - odiv.clientHeight
          } else {
            top = top - 10
          }
          odiv.style.left = left + 'px'
          odiv.style.top = top + 'px'
        }
        document.onmouseup = e => {
          if (e.target.id !== 'qz-img') {
            document.onmousemove = null
            document.onmouseup = null
            return
          }
          e.target.parentNode.childNodes[3].style.display = 'block'
          e.target.parentNode.childNodes[5].style.display = 'block'
          e.target.parentNode.style.boxShadow = 'none'
          e.target.parentNode.children[0].style.display = 'none'
          let index = null
          this.signaturePositions.map((item, i) => {
            if (item.num === this.currentPage + '' && item.id ===e.target.parentNode.classList[2] && item.signNum === e.target.parentNode.classList[3]) {
              index = i
            }
          })
          if (index) {
            this.signaturePositions.splice(index, 1)
          }
          if (index === 0) {
            this.signaturePositions.splice(index, 1)
          }
      
          this.clientPageX = Number(e.clientX - disX)
          this.clientPageY = Number(e.clientY - disY)
          document.onmousemove = null
          document.onmouseup = null
        }
      }
    },
    onclciksure(e) {
      this.sliderShow=false
      e.target.parentNode.childNodes[5].style.display = 'none'
      const odiv = e.target.parentNode // 获取目标元素
      // 算出鼠标相对元素的位置
      const clientX = e.clientX - 50
      const clientY = e.clientY - 50
      const disX = clientX - odiv.offsetLeft
      const disY = clientY - odiv.offsetTop
      let boom = false
      let index = null

   if (e.target.parentNode.classList[2]) {//章子上确定按钮的class里有页码 有id
  //    if (e.path[1].classList[3] && e.path[1].classList[2]) {//章子上确定按钮的class里有页码 有id
        this.signaturePositions.map((item, i) => {
          if (item.num === this.currentPage + '' && item.id ===e.target.parentNode.classList[2] && item.signNum === (e.target.parentNode.classList[4]||e.target.parentNode.id)) {
            boom = true
            index = i
          }
        })
        const width = odiv.childNodes[7].offsetWidth;
        const height = odiv.childNodes[7].offsetHeight;
        if (boom) {
          this.signaturePositions.splice(index, 1, {
            signNum:e.target.parentNode.classList[4]||e.target.parentNode.id,//第几个章
             id: e.target.parentNode.classList[2],
            sxaxis: Number(clientX - disX),
            syaxis: Number(clientY - disY),
           num: this.currentPage + '',
           width:width,
           height:height,
           type: e.target.parentNode.classList[3],
           file:e.target.nextSibling.nextSibling.className,
          })
        } else {
          this.signaturePositions.push({
                 signNum:e.target.parentNode.classList[4]||e.target.parentNode.id,//从class类名得到是第几个章,章的顺序
                id: e.target.parentNode.classList[2],//对钩上的class为id的
                sxaxis: Number(clientX - disX),
                syaxis: Number(clientY - disY),
                num: this.currentPage + '',//第几页
                width:width,
                height:height,
                type: e.target.parentNode.classList[3],
                file:e.target.nextSibling.nextSibling.className,
          })
        }
      }
    },
    onclcikdel(e) {
      this.sliderShow=false
      e.target.parentNode.style.display = 'none'//章子上删除按钮影藏
      let index = null
      this.signaturePositions.map((item, i) => {
        if (item.num === this.currentPage + '' && item.id ===e.target.parentNode.classList[2] && item.signNum === (e.target.parentNode.classList[4]||e.target.parentNode.id)) {
          index = i
        }
      })
      if (index) {
        this.signaturePositions.splice(index, 1)
      }
      if (index === 0) {
        this.signaturePositions.splice(index, 1)
      }
    },
    mouseenter(e) {
        e.currentTarget.childNodes[1].style.display = 'block'
      e.currentTarget.childNodes[3].style.display = 'block'
    },
    mouseleave(e) {
       e.currentTarget.childNodes[1].style.display = 'none'
      e.currentTarget.childNodes[3].style.display = 'none'
    },
    changeSlider(val){
      this.curNode.target.parentNode.style.height=val+'px'
      this.curNode.target.parentNode.style.border="1px solid red"
      this.curNode.target.parentNode.children[3].style.height=val+'px'
      let zoomWidth = 40-(((150-val)/10)*2);
      if(zoomWidth < 20){
        zoomWidth = 20;
      }
      this.curNode.target.parentNode.children[0].style.height=zoomWidth+'px'
      this.curNode.target.parentNode.children[0].style.width=zoomWidth+'px'
    },
    onclcikZoom(e){
      this.curNode = e
      this.sliderValue = e.target.parentNode.offsetHeight;
      this.sliderShow=true
      let borderArr =document.getElementsByClassName('mark')
      for (let index = 0; index < borderArr.length; index++) {
        const element = borderArr[index];
        element.style.border='none'
      }
    },
    handleNextPage() {
      if (this.currentPage < this.pages) {
        this.currentPage++
        let curPageData=this.defaultSign[0]&&this.defaultSign.filter(res=>{return res.num==this.currentPage})
        curPageData.map((sign, index) => {
                this.sealPic(sign, index,true)
              })
      } else {
        this.$message.warning('已经是最后一页了')
      }
    },
    handleUpPage() {
      if (this.currentPage > 1) {
        this.currentPage--
      } else {
        this.$message.warning('已经是第一页了')
      }
    },
    _loadFile(url) {
      if(url){
        //根据传进来的pdf路径, 解析pdf,
        PDFJS.getDocument(url).promise.then((pdf) => {
      this.pdfDoc = pdf
      this.pages = pdf.numPages
      this.$nextTick(res=>{
        this._renderPage(1,pdf)
      })
        })
      }
    },
    //渲染pdf
    _renderPage(num,pdf) {
      this.isPdfLoading = true
      pdf.getPage(num).then((page) => {
        const canvas = document.getElementById('the-canvas' + num)
        if(!canvas){return }
        const ctx = canvas.getContext('2d')
        const dpr = window.devicePixelRatio || 1//当前显示设备的物理像素分辨率与CSS像素分辨率的比值
        const bsrs =
          ctx.webkitBackingStorePixelRatio ||
          ctx.mozBackingStorePixelRatio ||
          ctx.msBackingStorePixelRatio ||
          ctx.oBackingStorePixelRatio ||
          ctx.backingStorePixelRatio ||
          1
        const ratio = dpr / bsrs//设备像素比

        // const viewport = page.getViewport(
        //   screen.availWidth / page.getViewport(1).width
        // )
           const viewport = page.getViewport({ scale: 1 })
              
        /*
        *将 canvas 的高和宽分别乘以 ratio 将其放大,
        *然后再用 css 将其样式高和宽限制成初始的大小。
        */
        canvas.width = viewport.width * ratio
        canvas.height = viewport.height * ratio
     
        // canvas.style.width = viewport.width * 0.5 + 'px'
        // canvas.style.height = viewport.height * 0.5 + 'px'
        // canvas.style.width = 21 + 'cm'
        // canvas.style.height = 29.7 + 'cm'
        ctx.setTransform(ratio, 0, 0, ratio, 0, 0)
        const renderContext = {
          canvasContext: ctx,
          viewport: viewport
        }
     page.render(renderContext);
        if (this.pages > num) {
          this._renderPage(num + 1,pdf)
        }
      })
        .finally(() => {
          this.isPdfLoading = false
        })
    },
    upStep(){
            this.$confirm('点击确定后拖动过的章子或签名无法回显,是否确定上一步', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
       this.active=this.active-1
      }).catch(() => {

      })
    },
        // 提交
    handleSubmit() {
      if (this.signaturePositions.length === 0) {
          this.$message.warning('请选择签章')
          return
      } 
      this.$emit("submitSign",this.signaturePositions,this.curfilesListId);
      return
    },
  },
  mounted(){
    this._loadFile(this.getPDFurl);
    setTimeout(() => {
            // 签章回显
            if (this.defaultSign.length > 0) {
              this.signaturePositions=this.defaultSign
              let curPageData=this.defaultSign[0]&&this.defaultSign.filter(res=>{return res.num=='1'})
              curPageData.map((sign, index) => {
                this.sealPic(sign, index,true)
              })
            }
          }, 200);
  },
  created() {
  },

};
</script>
<style scoped lang="scss">
.my-dialog-footer{
  top:10px;
}

.sign-img-box{
  display: flex;
  flex-direction: column;
.labelWidth{
  min-width: 40px;
}
.btnsWrapper{
  display: flex;
  flex-flow: wrap;
}
.signBtns{
  display: flex;
  flex-direction: row;
}
  >div{
    display: flex;
    flex-direction: row;
  }.sign-img{
    margin: 10px;
    line-height: 30px;
    font-size: 14px;
  .common-button-hightlight{
        // padding:0 21px;
        height: 32px;
        background-color: #409eff;
        font-size: 14px;
        color: #ffffff;
        //border:1px solid $light-button-color;
    }
  }

}

.pdf-box{
  position: relative;
  .pageTab{
    position: absolute;
  display: flex;
  align-items: center;
  right: 0px;
    top: -20px;
    i{
      cursor:pointer;
    }
    i:hover{
      color:#1890ff;
    }
}
}
  .sliderWrapper{
    position: absolute;
top: -35px;
    // width: 50%;
  }
:deep(div){
   .seal-img{
     position:absolute;
     top:0px;
     left:0px;
     height:150px;
   }
   #zoomPicture{
      position: absolute;
      top:0px;
      left:0px;
      z-index:999;
      background: rgba(0, 0, 0, .8);
      padding: 5px 5px 2px;
      border-radius: 4px;
      display: none;
      background-image: url('@/assets/images/zoom.png');
      width: 40px;
      height: 40px;
      background-position: center;
      background-repeat: no-repeat;
      background-size: 80%;
}
#delgongzhang{
          position: absolute;
            top:0px;
            right:0px;
            width:20px;
            height: 20px;
            z-index:999;
       
            font-weight: 800;
            text-align: center;
            line-height: 16px;
            border-radius: 50%;
            cursor: pointer;
}
#suregongzhang{
            position: absolute;
            bottom:0px;
            right:0px;
            width:20px;
            height: 20px;
            z-index:999;
            border: 2px solid green;
            color: green;
            font-weight: 900;
            text-align: center;
            border-radius: 50%;
            line-height: 19px;
            cursor: pointer;
}
#qm-img-cover{
  width:100%;height:100%;position: absolute;left: 0;top: 0;
}
#qz-img{
  height:150px;
  cursor:pointer;
   backgroundSize:cover;
   borderRadius:50%;
   box-shadow: 0 8px 16px #ccc;
}
#qm-img{
  height:150px;cursor:pointer;backgroundSize:cover;borderRadius:50%;box-shadow: 0 8px 16px #ccc;
}
}
</style>
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值