ElementUi+Vue+Php+fpdf+fpdi 实现文档在线签订(图片水印、手写签名)

in 前端 with 0 comment

先上效果

图片描述...

运用到的技术

前端实现

pdf封装

<template>
  <div>
    <pdf
      v-for="i in numPages"
      :key="i"
      :src="src"
      :page="i"
      style="display: block; width: 100%; border-bottom: 1px solid;"
      class="pdf-page"
    ></pdf>
  </div>
</template>
 
<script>
import pdf from "vue-pdf";
export default {
  props: ['url'],
  components: {
    pdf
  },
  data() {
    return {
      src: "",
      numPages: undefined 
    };
  },
  watch: {
    numPages: function(){
      this.$nextTick(() => {
        console.log('success')
      })
    }
  },
  mounted() {
    this.src = pdf.createLoadingTask(this.url);
    this.src.promise.then(pdf => {
      this.numPages = pdf.numPages;
      this.$emit('closeLoadding', false)
    });
  }
};
</script>

使用 + 签名

<template>
  <div class="pdfContainer" v-loading="loadding">
    <div class="header">
      <h2>{{ title }}</h2>
      <span class="no">contact No: {{ trno }}</span>
      <br />
    </div>
    <div class="content">

      <pdf :url="contract_pdf_url" @closeLoadding="closeLoadding"/>
      <div class="pdf">
        <img :src="signatureImg" alt="签名图片" ref="signatureImg" class="signatureImg" v-show="signatureImg">
      </div>
      <el-divider></el-divider>
      <div class="signature">
        <br />
        <strong>签字:</strong> 请在下面进行签字
        <el-divider></el-divider>
        <vue-esign
          ref="signature"
          :width="800"
          :height="230"
          :isCrop="isCrop"
          :lineWidth="lineWidth"
          :lineColor="lineColor"
          :bgColor.sync="bgColor"
        />

        <el-button type="warning" class="restore" @click="restore">清除</el-button>
        <el-button type="primary" class="button" @click="confirm">确认</el-button>
      </div>
    </div>
  </div>
</template>
<script>
import vueEsign from "vue-esign";
import { Message } from 'element-ui';
import { contractSign } from "@/api/web";
import Pdf from "@/components/Pdf/Index"
export default {
  components: { Pdf, vueEsign },
  props: ['showNext', 'contract_pdf_url'],
  data() {
    return {
      title: "Talk 100",
      trno: "TRCN-0007776669",
      src: null,
      numPages: 6,
      isCrop: false,
      positionX: 0,
      positionY: 0,
      lineColor: "#000000",
      bgColor: "",
      lineWidth: 6,
      signatureImg: "",
      loadding: true
    };
  },

  methods: {
    confirm() {
      this.$refs.signature
        .generate()
        .then(res => {
          this.signatureImg = res
          this.$refs.signature.reset();
          this.$emit('update:showNext', 1)
        })
        .catch(err => {
          Message.error(err)
        });
    },
    restore() {
        this.$refs.signature.reset();
        this.showNext = false
    },
    savePdf() {
      return new Promise((resolve) => {
        contractSign({sign: localStorage.getItem('sign'), img: this.signatureImg}).then( r => {
          if( r.code == 200) {
            resolve()
          }
        })
      })
    },

    /**
     * 控制整个页面的显示
     */
    closeLoadding(isShowLoadding) {
      this.loadding = isShowLoadding
    }
  }
};
</script>
<style lang="scss">
    html {
      font-size: 20px;
    }
    body {
      font-size: 16px;
    }
    .header {
      h2 {
        width: 100%;
        text-align: center;
      }
      .no {
        float: right;
        margin-right: 2rem;
        clear: both;
      }
    
      .prompt {
        color: #33363b;
      }
    }
    .pdfContainer {
      .pdf {
        position: relative;
        margin-top: 3rem;
        margin-left: 20%;
        span {
          display: block;
          width: 60% !important;
          left: 5rem;
        }
        img {
            width: 400px;
            height: 120px;
            position: absolute;
            right: 150px;
            bottom: 50px;
        }
      }
      .signature {
        border: 1px solid springgreen;
        width: 80%;
        margin: 0px auto;
    
        canvas {
          width: 100%;
        }
    
        .el-divider--horizontal{
            margin: 24px 0 0 0;
        }
      }
    }
</style>

前端主要进行pdf渲染到页面中,然后我们通过组件vue-esign得到一个签字画版,当签字确认后,我们可以通过方法generate()来得到一张签名图,由此我们将图片添加dom节点导pdf中

同时进行将图片传递给后端

后端实现

核心方法如下

public function contractPdf($sign_img, $user_id, $app_id)
    {
    # 处理上传的图片
    # 企业合同地址
    $train = (new CorpService())->getTrain($app_id, $user_id);
    $pdf_path = $this->createContractPdf(Arr::get($train, 'contract_path'));
    $file = str_replace('data:image/png;base64,', '', $sign_img);
    $file = base64_decode($file);
    $filename = storage_path("temp/" . Str::random(40) . '.' . "png");
    file_put_contents($filename, $file);

    # 保存pdf
    $pdf = new Fpdi();
    $pageCount = $pdf->setSourceFile($pdf_path);
       for ($pageNo = 1; $pageNo <= $pageCount; $pageNo++){
            $templateId = $pdf->importPage($pageNo);
            $size = $pdf->getTemplateSize($templateId);
            if ($size['width'] > $size['height']){
                $pdf->AddPage('L', array($size['width'], $size['height']));
            }else {
                $pdf->AddPage('P', array($size['width'], $size['height']));
            }
            $pdf->useTemplate($templateId);

            if($pageNo == $pageCount) {
                $w = $size['width']/5;
                $h = $size['height']/5;
                $center_x =  $w * 3.5; $center_y = $h * 4;
                $pdf->image($filename, $center_x, $center_y, 80);//中间水印
            }
        }

        $end_path = "contract/$user_id.pdf";
        $pdf->Output('F',storage_path("$end_path"),false);

        # 删除临时文件
        Storage::delete($filename);

        return $end_path;
    }
    

将签字后的图片进行保存写入pdf中,完成整个操作链图片描述...

Comments are closed.