移动端签名组件封装 借用插件 vue-esign

news2024/12/23 7:00:51

目录

  • 需求
  • 实现讲解
  • 工具 - 图片旋转、base64 转换为 file 对象
  • 组件封装
  • 组件全局注册
  • 组件使用
    • 效果展示

需求

移动端需要实现手机横屏手写签名并上传签名图片功能。

实现讲解

vue-esign 插件文档地址 https://www.npmjs.com/package/vue-esign
SignCanvas 组件封装原理:

  1. 页面分为左右两部分:左-按钮区域,右-签名区域
  2. 按钮区域:将按钮进行旋转,视觉上制造手机横屏的效果
  3. 签名区域:由于是横屏签名,所以在签名结束提交签名时需要将签名图片进行逆时针90°旋转

工具 - 图片旋转、base64 转换为 file 对象

@/utils/index

/**
 * 图片旋转
 */
export function rotateBase64Img(src, edg, fileName, fileType, 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的倍数!')
    return '旋转角度必须是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 = () => {
    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(dataURLtoFile(canvas.toDataURL(), fileName, fileType))
    // callback(canvas.toDataURL())
  }
}
/**
 * 将 base64 转换为 file 对象
 *    dataURL:base64 格式
 *    fileName:文件名
 *    fileType:文件格式
 */
export function dataURLtoFile(dataURL, fileName, fileType) {
  const arr = dataURL.split(',')
  const mime = arr[0].match(/:(.*?);/)[1]
  const bstr = atob(arr[1])
  let n = bstr.length
  const u8arr = new Uint8Array(n)
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n)
  }
  return new File([u8arr], fileName, { type: fileType || 'image/jpg' })
}

组件封装

@/components/SignCanvas.vue

<!-- 签名组件 -->
<template>
  <div class="signContainer">
    <div class="btns">
      <van-button type="default" round @click="resetHandler" class="reset">重签</van-button>
      <van-button type="info" round @click="sureHandler">确认</van-button>
    </div>
    <vue-esign
      ref="VueEsignRef"
      class="vue-esign"
      :width="width"
      :height="height"
      :lineWidth="lineWidth"
      :lineColor="lineColor"
      :bgColor="bgColor"
      :isCrop="isCrop"
      :isClearBgColor="isClearBgColor"
      :format="format"
      :quality="quality"
    />
    <div :style="{ '--width': height + 'px' }" class="tipText"><span v-if="signName">{{ ` ${signName} ` }}</span
      >在此区域内签名
    </div>
  </div>
</template>

<script>
import { rotateBase64Img } from '@/utils/index'

export default {
  name: 'SignCanvas',
  components: {},

  props: {
    // 画布宽度,即导出图片的宽度
    width: {
      type: Number,
      default: () => {
        const dom = document.querySelector('#app')
        const width = dom && dom.offsetWidth
        return width ? width - 60 : 300 // 减去按钮区域的宽度
      }
    },
    // 画布高度,即导出图片的高度
    height: {
      type: Number,
      default: () => {
        const dom = document.querySelector('#app')
        return (dom && dom.offsetHeight) || 800
      }
    },
    // 画笔粗细
    lineWidth: {
      type: Number,
      default: 6
    },
    // 画笔颜色
    lineColor: {
      type: String,
      default: '#000'
    },
    // 画布背景色,为空时画布背景透明,支持多种格式 '#ccc','#E5A1A1','rgb(229, 161, 161)','rgba(0,0,0,.6)','red'
    bgColor: {
      type: String,
      default: ''
    },
    // 是否裁剪,在画布设定尺寸基础上裁掉四周空白部分
    isCrop: {
      type: Boolean,
      default: false
    },
    // 清空画布时(reset)是否同时清空设置的背景色(bgColor)
    isClearBgColor: {
      type: Boolean,
      default: true
    },
    // 生成图片格式 image/jpeg(jpg格式下生成的图片透明背景会变黑色请慎用或指定背景色)、 image/webp
    format: {
      type: String,
      default: 'image/png'
    },
    // 生成图片质量;在指定图片格式为 image/jpeg 或 image/webp的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略。
    quality: {
      type: Number,
      default: 1
    },
    // 未签名时提示信息
    noSignTipText: {
      type: String,
      default: '请确保已签名!'
    },
    // 需要签名的姓名
    signName: {
      type: String,
      default: ''
    }
  },

  methods: {
    resetHandler() {
      this.$refs.VueEsignRef.reset() // 清空画布
    },
    sureHandler() {
      // 可选配置参数 ,在未设置format或quality属性时可在生成图片时配置 例如: {format:'image/jpeg', quality: 0.5}
      // this.$refs.esign.generate({format:'image/jpeg', quality: 0.5})
      this.$refs.VueEsignRef.generate()
        .then(res => {
          /**
           * res:base64图片
           */
          rotateBase64Img(res, 270, `${this.signName ? this.signName + '-签名.jpg' : 'sign.jpg'}`, '', data => {
            this.$emit('sureHandler', data)
          })
        })
        .catch(err => {
          console.log('err----', err)
          this.$dialog.alert({
            message: this.noSignTipText
          })
        })
    }
  }
}
</script>

<style lang='scss' scoped>
.signContainer {
  width: 100%;
  height: 100vh;
  display: flex;
  background-color: #fff;

  .btns {
    width: 55px;
    background-color: #f8f8f8;
    display: flex;
    flex-direction: column;
    justify-content: center;
    .reset {
      margin-bottom: 70px;
    }
  }
  .vue-esign {
    z-index: 2;
  }
  .tipText {
    position: absolute;
    top: 50%;
    width: var(--width);
    left: calc(50% + 55px);
    transform: translateX(-50%) translateY(-50%) rotateZ(90deg);
    text-align: center;
    color: #ddd;
    letter-spacing: 2px;
  }
}
::v-deep .van-button {
  width: 85px !important;
  height: 35px;
  transform: rotate(90deg) translateY(15px);
  text-align: center;
  .van-button__text {
    letter-spacing: 5px;
  }
}
</style>

组件全局注册

main.js

import vueEsign from 'vue-esign' // 需要 npm 包下载 npm install vue-esign
Vue.use(vueEsign)

import SignCanvas from '@/components/SignCanvas'
Vue.component('SignCanvas', SignCanvas)
// ...

组件使用

<!-- XXXX签名 -->
<template>
  <SignCanvas ref="SignCanvasRef" :signName="nameList[nameIndex]" @sureHandler="sureSignHandler" />
</template>

<script>
export default {
  name: 'BloodRegisterSign',
  components: {},

  data() {
    return {
      // ...
      inputData: {}, // 该数据中 cxmjView 为需要签名的人员姓名
      nameIndex: 0, // 当前签名为第几个人签名
      signFileList: [] // 签名图片列表
    }
  },

  computed: {
    nameList() {
      return this.inputData.cxmjView ? this.inputData.cxmjView.split(',') : [] // 需要有多个签名
    }
  },

  watch: {},

  created() {
    console.log('this.$route----', this.$route)
    this.inputData = JSON.parse(this.$route.query.inputData || '{}')
	// ...
  },

  methods: {
    sureSignHandler(data) {
      this.signFileList.push(data)
      if (this.nameIndex < this.nameList.length - 1) {
        this.nameIndex++
        this.$refs.SignCanvasRef.resetHandler()
      } else {
        this.submitHandler()
      }
    },
    submitHandler() {
      // TODO:调用接口,提交签名图片等数据
    }
  }
}
</script>

<style lang='scss' scoped>
</style>

效果展示

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1106452.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构】830+848真题易错题汇总(自用)

【数据结构】830848易错题汇总(10-23) 文章目录 【数据结构】830848易错题汇总(10-23)选择题填空题判断题简答题&#xff1a;应用题&#xff1a;算法填空题&#xff1a;算法设计题&#xff1a;(待补) 选择题 1、顺序栈 S 的 Pop(S, e)操作弹出元素 e&#xff0c;则下列(C )是正…

虹科分享 | 选择SAS还是NVMe?虹科网络基础带您一探究竟!

存储架构师需要通过确保他们选择的存储解决方案提供支持其生态系统所需的安全性、稳定性、可扩展性和管理特性来应对当今的业务挑战。当他们考虑采用新的存储技术时&#xff0c;在采用新技术之前&#xff0c;他们应该权衡和审查一些基本的考虑因素。新的存储协议不断进入市场&a…

Postman for Mac - 轻松进行API测试的利器

在当今的数字化时代&#xff0c;应用程序编程接口(API)已成为推动软件创新和互操作性的核心动力。API测试作为确保服务质量的重要一环&#xff0c;也越来越受到开发者的重视。其中&#xff0c;Postman作为一款极其流行的API测试工具&#xff0c;其简洁易用的界面和强大的功能&a…

美妆品牌如何有效利用软文推广引流获客

近年来随着美妆品牌的转型升级和居民消费观念的转变&#xff0c;美妆行业取得了更大发展空间&#xff0c;新产品不断涌现&#xff0c;消费者拥有更多选择&#xff0c;那么在竞争激烈的市场中美妆品牌如何才能突破重围&#xff0c;找出新的价值增长点呢&#xff1f; 一、 细分消…

NewStarCTF2023week3-阳光开朗大男孩

下载附件解压得到两个txt文本 secret.txt一看很明显是核心价值观编码 解码得到 this_password_is_s000_h4rd_p4sssw0rdddd flag.txt最开始没看出来是什么&#xff0c;主要是之前没遇到过 题目提示&#xff1a;我是阳光开朗大男孩&#xff5e;阳光开朗大男孩&#xff5e; 我…

线程池工作原理

1&#xff1a;处理Runnable 任务的方法 package ThreadPooITest;import java.util.concurrent.*;//目标&#xff1a;线程池创建 public class ThreadPoolTest {public static void main(String[] args) {//1:通过ThreadPoolExectorExecutorService pool new ThreadPoolExecuto…

数据模型设计必读方法论!很实用

数据架构的重要构件之一是数据模型&#xff0c;当然从数据架构的视角来说的数据模型是指企业级数据模型。本篇文章更多是讨论如何设计和管理数据模型&#xff0c;此处的数据模型是泛指在组织中通过数据建模的过程&#xff0c;来发现、分析和确定数据需求范围&#xff0c;并用于…

6.(vue3.x+vite)路由传参query与params区别

前端技术社区总目录(订阅之前请先查看该博客) 效果截图 一:路由传参有两种方式:params与query params与query区别 1:param,路由带“/”,query带“?” 2:query传过来的参数会显示到地址栏中 而params传过来的参数可以显示参数或隐藏参数到地址栏中(vue-router 4.1.4不…

MSVCR80.DLL 丢失修复方法:完美解决你的问题!

MSVCR80.DLL 是 Microsoft Visual C Redistributable Package 中的一个动态链接库文件&#xff0c;它扮演着非常重要的角色。然而&#xff0c;当你在运行某些应用程序时&#xff0c;可能会遇到“MSVCR80.DLL 丢失”错误。这时候你就需要采取一些措施来解决这个问题了&#xff0…

待办任务清单app哪个软件好用?

手机成了我们生活的延伸&#xff0c;而对于繁忙的工作节奏&#xff0c;一款好用的任务提醒软件就如同一位贴心的助手&#xff0c;时刻提醒我们的待办任务。在众多软件中&#xff0c;敬业签是一款比较不错的记录待办任务清单的工具。 在手机上&#xff0c;打开敬业签&#xff0…

swagger gin 文档接口排序,写了一个小工具,自定义接口排序

起因没找到swagger 自定义接口排序 代码原理就是替换 swag init 生成的docs.go paths 部分 &#xff0c;取到swagger.json paths 部分排序&#xff0c;正则匹配docs.go paths 部分&#xff0c;然后通过自定义排序&#xff0c;替换paths部分&#xff0c;这个根据自定义的需求来…

Stable Diffusion WebUI报错RuntimeError: Torch is not able to use GPU解决办法

新手在安装玩Stable Diffusion WebUI之后会遇到各种问题&#xff0c; 接下来会慢慢和你讲解如何解决这些问题。 在我们打开Stable Diffusion WebUI时会报错如下&#xff1a; RuntimeError: Torch is not able to use GPU&#xff1b;add --skip-torch-cuda-test to COMMANDL…

操作系统面试常问问题--保研及考研复试

前言&#xff1a; Hello大家好&#xff0c;我是Dream。今年保研上岸山东大学人工智能专业 &#xff08;经验贴&#xff09;&#xff0c;现在将我自己的专业课备考知识点整理出来&#xff0c;分享给大家&#xff0c;希望可以帮助到大家&#xff01;这是重点知识总结&#xff0c;…

基于Pix4D使用无人机光学影像制作正射影像(DOM)和数字表面模型(DSM) 操作步骤

基于Pix4D使用无人机光学影像制作正射影像&#xff08;DOM&#xff09;和数字表面模型&#xff08;DSM&#xff09; 操作步骤 0. 前言1.获取无人机光学影像2.DOM和DSM3.操作步骤3.1 初始界面3.2 新建项目3.3查看处理过程报告3.4查看处理进度和成果 4.在ArcMap中打开DSM和DOM 0.…

Linux高性能服务器编程——ch1笔记

第1章 TCP/IP 协议族 1.1 TCP/IP 协议族体系结构以及主要协议 数据链路层 网卡接口的网络驱动程序&#xff0c;以处理数据在物理媒介&#xff08;比如以太网、令牌环等&#xff09;上的传输。 协议&#xff1a;ARP、RARP&#xff0c;实现IP地址和机器物理地址之间的转换。 网络…

适用于小型企业的远程控制软件分享!

远程控制软件对小型企业的好处 远程控制软件允许用户从远程位置连接到计算机&#xff0c;然后访问和使用远程计算机上的资源。这对于需要为客户提供远程技术支持的企业来说&#xff0c;是一个非常重要的工具。 借助远程控制软件&#xff0c;小型企业人员在远程工…

同城代驾开源版小程序开发

同城代驾开源版小程序开发 功能特性描述&#xff1a; 定价模式&#xff1a;本系统支持灵活的计价模式&#xff0c;包括白天和夜晚的起步价、起步里程、每公里价以及超时费用&#xff0c;从而满足不同时段的定价需求。 实时路径计算&#xff1a;通过集成腾讯地图的软件开发工…

学习c#桌面应用编程 --- 我的第一个游戏

场景 我需要做一个c#桌面窗口软件&#xff0c;但是我曾经都是专职于java开发&#xff0c;但是java对windows并不是特别友好(awt除外)&#xff0c;于是必须需要掌握c#桌面编程&#xff0c;所以我需要手动做一个小游戏&#xff0c;来学习c#的一些基本桌面应用的知识。 开始 这…

2023年底,软件测试行业的几大发展趋势,你关注到几个?

以下为作者的观点&#xff1a; 现在是2023年&#xff0c;技术继续快速发展&#xff1b;软件测试领域也在不断发展扩大。从功能到自动化&#xff0c;再到到人工智能&#xff0c;软件测试的未来看起来与过去截然不同。软件测试对于任何高质量、可靠软件的开发都是至关重要的。然…

算法刷题总结(全)

刷题总结 by lds 2023-9-5 文章目录 1.数组/字符串1.1 合并两个有序数组【easy】1.2 移除元素【easy】1.3 删除有序数组中的重复项【easy】1.4 删除有序数组中的重复项II【mid】1.5 多数元素【easy】1.6 大数相加---【美团面试手撕题目】1.7 轮转数组【mid】1.8 买卖股票的最佳…