uniapp3 手写签名组件(vue3 语法)封装与应用

本文介绍了基于 uniapp3(vue3 语法)封装的手写签名组件。

包括父组件的调用方式,如通过条件判断展示签名图片或点击进入签名页面,以及接收签名照片的逻辑。子组件涵盖了自定义导航栏、清除、取消、确认等操作按钮,利用 canvas 实现手写签名功能,包括笔迹绘制、颜色选择、重写、图片旋转与导出等操作,同时涉及获取系统信息设置 canvas 尺寸和背景色等关键技术点,为在 uniapp3 项目中实现手写签名功能提供了完整的解决方案。


父组件 调用方式 :

<template>
	<view style="width: 100%; padding: 0 10px">
		<img class="sign-img" v-if="pageData.tempFilePath" :src="pageData.tempFilePath" />
	    <view class="sign-header" v-else @click="goSign">点击输入您的签名</view>
	</view>
	  <view
	    style="
	      width: 100vw;
	      height: 100vh;
	      position: fixed;
	      z-index: 9999;
	      top: 0;
	      left: 0;
	      background-color: #fff;
	    "
	    v-if="pageData.showSign"
	  >
	    <signaturePlugin
	      @getTempFilePath="getTempFilePath"
	      @close="closeSign"
	      style="width: 100%; height: 100%"
	    />
  </view>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import signaturePlugin from '@/components/signature.vue'
const pageData = reactive({
  tempFilePath: '',
  showSign: false
})
const closeSign = function () {
  pageData.showSign = false
}
// 点击重写,进入签名页面
const goSign = function () {
  pageData.showSign = true
}
// 签名页面返回回来,接收签名照片展示
const getTempFilePath = function (data) {
  let { tempFilePath } = JSON.parse(data)
  pageData.tempFilePath = tempFilePath
  console.log('签名页面返回回来,接收签名照片展示', tempFilePath)
}
</script>
<style scope>
.sign-header {
  display: flex;
  justify-content: center;
  align-items: center;
  color: grey;
  border: 1px solid #e6e6e6;
  border-radius: 8px;
  background-color: #fff;
  height: 300rpx;
  margin: 0 10px;
}

.sign-img {
  border: 1px solid #e6e6e6;
  border-radius: 8px;
  height: 300rpx;
  width: 100%;
}
</style>

子组件(手写签名组件)

<template>
  <view>
    <!-- 自定义导航栏 -->
    <!-- <NaviBar title="签署" :autoBack="true" /> -->
    <view class="wrapper">
      <view class="handBtn">
        <button @click="retDraw" class="delBtn">清除</button>
        <button @click="saveCanvasAsImg" class="saveBtn">取消</button>
        <button @click="subCanvas" class="subBtn">确认</button>
      </view>
      <view class="handCenter" ref="handCenter">
        <canvas
          class="handWriting"
          :disable-scroll="true"
          @touchstart="uploadScaleStart"
          @touchmove="uploadScaleMove"
          canvas-id="handWriting"
        />
        <!--用于旋转图片的canvas容器-->
        <canvas
          style="position: absolute"
          :style="{ width: cavWidth + 'px', height: cavWidth1 + 'px' }"
          canvas-id="handWriting2"
        ></canvas>
      </view>
    </view>
  </view>
</template>

<script setup>
import { ref, onMounted, reactive, getCurrentInstance, nextTick, defineEmits } from 'vue'
const instance = getCurrentInstance()
// 定义触发的事件及其数据类型
const emit = defineEmits(['close', 'getTempFilePath'])
const canvasName = ref('handWriting')
const ctx = ref('')
const startX = ref(null)
const startY = ref(null)
const canvasWidth = ref(0)
const canvasHeight = ref(0)
const selectColor = ref('black')
const lineColor = ref('#1A1A1A') // 颜色
const canvas = ref(null)
const cavWidth = ref(1000)
const cavWidth1 = ref(1000)
const lineSize = ref(5) // 笔记倍数
const location = ref(null)
const handCenter = ref(null)

onMounted(() => {
  ctx.value = uni.createCanvasContext('handWriting', instance.proxy)
  uni.getSystemInfo({
    success: function (res) {
      const windowWidth = res.windowWidth // 窗口宽度
      const windowHeight = res.windowHeight // 窗口高度
      console.log(windowWidth)
      console.log(windowHeight)
      cavWidth.value = canvasWidth.value = windowWidth
      cavWidth1.value = canvasHeight.value = windowHeight
      setCanvasBg('#fff')
    },
  })
})

// 笔迹开始
const uploadScaleStart = (e) => {
  startX.value = e.changedTouches[0].x
  startY.value = e.changedTouches[0].y
  //设置画笔参数
  //画笔颜色
  ctx.value.setStrokeStyle(lineColor.value)
  //设置线条粗细
  ctx.value.setLineWidth(lineSize.value)
  //设置线条的结束端点样式
  ctx.value.setLineCap('round') //'butt'、'round'、'square'
  //开始画笔
  ctx.value.beginPath()
}

// 笔迹移动
const uploadScaleMove = (e) => {
  //取点
  let temX = e.changedTouches[0].x
  let temY = e.changedTouches[0].y
  //画线条
  ctx.value.moveTo(startX.value, startY.value)
  ctx.value.lineTo(temX, temY)
  ctx.value.stroke()
  startX.value = temX
  startY.value = temY
  ctx.value.draw(true)
}

// 重写
const retDraw = () => {
  ctx.value.clearRect(0, 0, 700, 730)
  ctx.value.draw()
  //设置canvas背景
  setCanvasBg('#fff')
}

// 选择颜色
const selectColorEvent = (str, color) => {
  selectColor.value = str
  lineColor.value = color
}

// 确认
const subCanvas = () => {
  uni.canvasToTempFilePath(
    {
      canvasId: 'handWriting',
      fileType: 'png',
      quality: 1, //图片质量
      success: (res) => {
        console.log(res.tempFilePath, 'canvas生成图片地址')
        wx.getImageInfo({
          // 获取图片的信息
          src: res.tempFilePath,
          success: (res1) => {
            console.log('res1', res1)
            // 将canvas1的内容复制到canvas2中
            let canvasContext = uni.createCanvasContext('handWriting2', instance)
            let rate = res1.height / res1.width
            let width = 300 / rate
            let height = 300
            cavWidth.value = height
            cavWidth1.value = width
            canvasContext.translate(height / 2, width / 2)
            canvasContext.rotate((270 * Math.PI) / 180)
            canvasContext.drawImage(res.tempFilePath, -width / 2, -height / 2, width, height)
            console.log(0, canvasContext)
            canvasContext.draw(false, () => {
              // 将之前在绘图上下文中的描述(路径、变形、样式)画到 canvas 中
              uni.canvasToTempFilePath(
                {
                  // 把当前画布指定区域的内容导出生成指定大小的图片。在 draw() 回调里调用该方法才能保证图片导出成功。
                  canvasId: 'handWriting2',
                  fileType: 'png',
                  quality: 1, //图片质量
                  success: (res2) => {
                    console.log('res2', res2)
                    let data = JSON.stringify({
                      tempFilePath: res2.tempFilePath,
                    })
                    emit('getTempFilePath', data)
                    emit('close')
                  },
                },
                instance,
              )
            })
          },
        })
      },
    },
    instance,
  )
}

//旋转图片,生成新canvas实例
const rotate = (cb) => {
  wx.createSelectorQuery()
    .select('#handWriting2')
    .fields({
      node: true,
      size: true,
    })
    .exec((res) => {
      const rotateCanvas = res[0].node
      const rotateCtx = rotateCanvas.getContext('2d')
      //this.ctxW-->所绘制canvas的width
      //this.ctxH -->所绘制canvas的height
      rotateCanvas.width = canvasHeight.value
      rotateCanvas.height = canvasWidth.value
      wx.canvasToTempFilePath(
        {
          canvas: canvas.value,
          success: (res) => {
            const img = rotateCanvas.createImage()
            img.src = res.tempFilePath
            img.onload = function () {
              rotateCtx.translate(rotateCanvas.width / 2, rotateCanvas.height / 2)
              rotateCtx.rotate((270 * Math.PI) / 180)
              rotateCtx.drawImage(img, -rotateCanvas.height / 2, -rotateCanvas.width / 2)
              rotateCtx.scale(1, 1)
              cb(rotateCanvas)
            }
          },
          fail: (err) => {
            console.log(err)
          },
        },
        instance,
      )
    })
}

//取消
const saveCanvasAsImg = () => {
  retDraw()
  // uni.navigateBack()
  emit('close')
}

//设置canvas背景色  不设置  导出的canvas的背景为透明
//@params:字符串  color
const setCanvasBg = (color) => {
  /* 将canvas背景设置为 白底,不设置  导出的canvas的背景为透明 */
  //rect() 参数说明  矩形路径左上角的横坐标,左上角的纵坐标, 矩形路径的宽度, 矩形路径的高度
  //这里是 canvasHeight - 4 是因为下边盖住边框了,所以手动减了写
  ctx.value.rect(0, 0, canvasWidth.value, canvasHeight.value - 4)
  ctx.value.setFillStyle(color)
  ctx.value.fill() //设置填充
  ctx.value.draw() //开画
}
</script>

<style>
page {
  background: #fbfbfb;
  height: auto;
  overflow: hidden;
}

.wrapper {
  position: relative;
  width: 100%;
  height: 100vh;
  margin: 20rpx 0;
  overflow: hidden;
  display: flex;
  align-content: center;
  flex-direction: row;
  justify-content: center;
  font-size: 28rpx;
}

.handWriting {
  background: #fff;
  width: 100%;
  height: 100vh;
}

.handCenter {
  border-left: 2rpx solid #e9e9e9;
  flex: 5;
  overflow: hidden;
  box-sizing: border-box;
}

.handBtn button {
  font-size: 28rpx;
}

.handBtn {
  height: 100vh;
  display: inline-flex;
  flex-direction: column;
  justify-content: space-between;
  align-content: space-between;
  flex: 1;
}

.delBtn {
  width: 200rpx;
  position: absolute;
  bottom: 350rpx;
  left: -35rpx;
  transform: rotate(90deg);
  color: #666;
}

.subBtn {
  width: 200rpx;
  position: absolute;
  bottom: 52rpx;
  left: -35rpx;
  display: inline-flex;
  transform: rotate(90deg);
  background: #29cea0;
  color: #fff;
  margin-bottom: 60rpx;
  text-align: center;
  justify-content: center;
}

/*Peach - 新增 - 保存*/

.saveBtn {
  width: 200rpx;
  position: absolute;
  bottom: 590rpx;
  left: -35rpx;
  transform: rotate(90deg);
  color: #666;
}
</style>
相关推荐
傻小胖1 小时前
路由组件与一般组件的区别
前端·vue.js·react.js
Elena_Lucky_baby1 小时前
在Vue3项目中使用svg-sprite-loader
开发语言·前端·javascript
丁总学Java2 小时前
微信小程序中 “页面” 和 “非页面” 的区别
微信小程序·小程序
小万编程2 小时前
基于SpringBoot+Vue毕业设计选题管理系统(高质量源码,提供文档,免费部署到本地)
java·vue.js·spring boot·计算机毕业设计·java毕业设计·web毕业设计
重生之搬砖忍者2 小时前
uniapp使用canvas生成订单小票图片
前端·javascript·canva可画
万水千山走遍TML2 小时前
console.log封装
前端·javascript·typescript·node·log·console·打印封装
赵大仁2 小时前
uni-app 多平台分享实现指南
javascript·微信小程序·uni-app
m0_748236583 小时前
【Nginx 】Nginx 部署前端 vue 项目
前端·vue.js·nginx
m0_528723813 小时前
如何在新窗口打开pdf文件,并修改网页标题
前端·javascript·pdf
Lysun0014 小时前
react构建项目报错 `npm install --no-audit --save @testing-l
javascript·react.js·npm