当前位置: 首页 > article >正文

vue-signature-pad插件实现移动端签字功能(css,js)+将签名照片旋转90度之后的base64码传给后端

效果图
在这里插入图片描述
代码

<template>
  <div class="sign-finish">
    <NavBar class="navBar" title="电子签名"></NavBar>
    <div class="wrap">
      <div class="actionsWrap">
        <div class="actions">
          <van-button plain @click="cancel" type="info">取消</van-button>
          <van-button
            class="action-button"
            @click="submit"
            type="info"
            :loading="isLoading"
            >提交</van-button
          >
        </div>
      </div>
      <div></div>
      <div class="topbtn">
        <img
          src="@/assets/images/workApplyImg/cancel.png"
          alt="Logo"
          class="logo"
          @click="undo"
        /><a @click="undo">撤销</a
        ><img
          src="@/assets/images/workApplyImg/write.png"
          alt="Logo"
          class="logo"
          @click="clearSignature"
        /><a @click="clearSignature">重写</a>
      </div>
      <div class="content">请在空白区域内手写签名确认</div>
      <VueSignaturePad
        class="canvas"
        ref="signaturePad"
        :options="options"
        @begin="handleBegin"
        @end="handleEnd"
      ></VueSignaturePad>
    </div>
  </div>
</template>

<script>
import { VueSignaturePad } from "vue-signature-pad";
import NavBar from "@/components/NavBar";
import { signatureApi } from "@/api/signature";
import { Toast, Dialog } from "vant";
import { useUserStore } from "@/store/user";
import dayjs from "dayjs";
export default {
  components: {
    VueSignaturePad,
    NavBar,
  },
  data() {
    return {
      isLoading: false,
      imgSrc: "",
      searchData: {},
      options: {
        // 这里可以配置signature_pad的选项,例如宽高、背景色等
        penColor: "rgb(0, 0, 0)", // 笔的颜色
        penWidth: 2, // 笔的宽度
        backgroundColor: "#F4F6FA", // 背景颜色
        canvasWidth: 600, // canvas的宽度
        canvasHeight: 300, // canvas的高度
        rotate: -90,
      },
    };
  },
  created() {
    const { userInfo } = useUserStore();
    this.searchData.loginAccount = userInfo.empCode;
    this.searchData.loginName = userInfo.empName;
  },
  methods: {
    handleBegin() {
      console.log("开始签名");
    },
    handleEnd() {
      console.log("结束签名");
    },
    // 撤销
    undo() {
      this.$refs.signaturePad.undoSignature();
    },
    // 取消
    cancel() {
      this.$router.push({
        path: "/main/home",
      });
    },
    // 清除
    clearSignature() {
      this.$refs.signaturePad.clearSignature();
    },
    saveSignature() {
      const data = this.$refs.signaturePad.save(); // 保存签名数据到dataURL或base64字符串中
    },
    clsoePage(res) {
      Dialog.confirm({
        message: res.message,
        confirmButtonText: "关闭页面",
        type: "success",
        center: true,
        showCancelButton: false,
      }).then(() => {
        this.cancel();
      });
    },
    // 提交
    async submit() {
      this.isLoading = true;
      const { isEmpty, data } = this.$refs.signaturePad.saveSignature();
      if (isEmpty) {
        this.isLoading = false;
        Toast("签名为空,请书写后再进行添加");
        return;
      }
      await this.rotateBase64Img(data, -90, (res) => {
        this.imgSrc = res.split(",");
        const parasms = {
          signId: this.$route.query.fileid,
          signPhoto: this.imgSrc[1],
          jackdaw_id: this.$route.query.jackdaw_id,
          qsdt: dayjs().format("YYYY-MM-DD"),
          ...this.searchData,
        };
        let res = signatureApi.signatureSub(parasms);
        if (res.statusCode === 200) {
          this.clsoePage(res);
          this.isLoading = false;
        } else {
          this.$notify({ type: "danger", message: res });
          this.isLoading = false;
        }
      });
    },
    // 将写完的签名图片顺时针旋转90度,拿到旋转之后base64码传给后端
    rotateBase64Img(src, edg, callback) {
      var canvas = document.createElement("canvas");
      var ctx = canvas.getContext("2d");

      var imgW; //图片宽度
      var imgH; //图片高度
      var size; //canvas初始大小

      if (edg % 90 != 0) {
        console.error("旋转角度必须是90的倍数!");
        throw "旋转角度必须是90的倍数!";
      }
      edg < 0 && (edg = (edg % 360) + 360);
      const quadrant = (edg / 90) % 4; //旋转象限
      const cutCoor = { sx: 0, sy: 0, ex: 0, ey: 0 }; //裁剪坐标

      var image = new Image();

      image.crossOrigin = "anonymous";
      image.src = src;

      image.onload = function () {
        imgW = image.width;
        imgH = image.height;
        size = imgW > imgH ? imgW : imgH;

        canvas.width = size * 2;
        canvas.height = size * 2;
        switch (quadrant) {
          case 0:
            cutCoor.sx = size;
            cutCoor.sy = size;
            cutCoor.ex = size + imgW;
            cutCoor.ey = size + imgH;
            break;
          case 1:
            cutCoor.sx = size - imgH;
            cutCoor.sy = size;
            cutCoor.ex = size;
            cutCoor.ey = size + imgW;
            break;
          case 2:
            cutCoor.sx = size - imgW;
            cutCoor.sy = size - imgH;
            cutCoor.ex = size;
            cutCoor.ey = size;
            break;
          case 3:
            cutCoor.sx = size;
            cutCoor.sy = size - imgW;
            cutCoor.ex = size + imgH;
            cutCoor.ey = size + imgW;
            break;
        }

        ctx.translate(size, size);
        ctx.rotate((edg * Math.PI) / 180);
        ctx.drawImage(image, 0, 0);

        var imgData = ctx.getImageData(
          cutCoor.sx,
          cutCoor.sy,
          cutCoor.ex,
          cutCoor.ey
        );

        if (quadrant % 2 == 0) {
          canvas.width = imgW;
          canvas.height = imgH;
        } else {
          canvas.width = imgH;
          canvas.height = imgW;
        }
        ctx.putImageData(imgData, 0, 0);
        callback(canvas.toDataURL());
      };
    },
  },
};
</script>
<style lang="less" scoped>
.sign-finish {
  width: 100vw;
  height: 100vh;
  background-color: #fff;
  overflow: hidden;
  .topbtn {
    position: absolute;
    display: flex;
    transform: rotate(90deg);
    bottom: 14vh;
    right: -20px;
    a {
      color: #358cf1;
      font-size: 15px;
    }
    img {
      transform: rotate(-90deg);
      margin-left: 15px;
      margin-right: 5px;
    }
  }
  .content {
    position: absolute;
    transform: rotate(90deg);
    top: 50%;
    font-size: 20px;
    right: 5vw;
    color: #999999;
  }
  .wrap {
    padding: 15px;
    height: 92.5vh;
    display: flex;
    justify-content: center;
    overflow: hidden;
    .actionsWrap {
      width: 50px;
      display: flex;
      justify-content: center;
      align-items: center;
      position: relative;
      top: 37%;
    }
    .canvas {
      flex: 1;
    }
    .actions {
      margin-right: 10px;
      white-space: nowrap;
      transform: rotate(90deg);
      .action-button {
        margin-left: 15px;
      }
    }
  }
}
</style>


http://www.kler.cn/a/561413.html

相关文章:

  • npm i 失败权限问题
  • 详解:用Python OpenCV库来处理图像并测量物体的长度
  • 计算机考研之数据结构:斐波那契数列专题(1)
  • Linux设备驱动开发-SPI驱动开发详解(包含设备树处理详细过程)
  • 第4章 Unicode 文本和字节序列
  • 神经网络 - 神经元
  • 【压力测试】要不要做全链路压测?
  • Jasper AI技术浅析(四):自然语言处理(NLP)与生成技术
  • 4部署kibana:5601
  • 【Python】2.获取pypi的api token 并把自己写好的库上传到pypi(保姆级图文)
  • 在 Windows 下的 Docker 中安装 R语言
  • Linux下安装Nginx服务及systemctl方式管理nginx详情
  • 编写一个程序,输入一个字符串并输出其长度(Java版)
  • Redis|持久化
  • 栅格地图路径规划:基于雪橇犬优化算法(Sled Dog Optimizer,SDO)的移动机器人路径规划(提供MATLAB代码)
  • day02
  • 推荐几款开源免费的 .NET MAUI 组件库
  • DeepSeek技术提升,Linux本地部署全攻略
  • 什么是可重入可重入锁?
  • AI算力加持,AORO M6 Pro智能对讲机构筑安全通信“数字化长城”