UNIAPP手写签名

Lirioing2025/09/03UNIAPPFEVUEUNIAPP

前言

在项目开发过程中遇到需要签名的地方,就做了一个签名组件

签名组件

<template>
  <view v-if="modelValue" class="lee-signature" @touchmove.prevent>
    <view class="lee-signature__box">
      <view class="lee-signature__opreate">
        <button @click="close" class="lee-signature__opreate--back">返回</button>
        <button @click="clearSignature" class="lee-signature__opreate--fresh">重写</button>
        <button @click="saveSignature" class="lee-signature__opreate--finish primary">完成</button>
      </view>
      <canvas 
        canvas-id="signatureCanvas"
        class="signature-canvas lee-signature__canvas"
        @touchstart="handleTouchStart"
        @touchmove="handleTouchMove"
        @touchend="handleTouchEnd">
      </canvas>
      <view class="lee-signature__opreate--title">请在下方签名</view>
    </view>
  </view>
</template>

<script setup>
import { ref, watch, nextTick } from 'vue'

const ctx = ref(null)
const points = ref([])
const isDrawing = ref(false)
const canvasWidth = ref(0)
const canvasHeight = ref(0)
const isEmpty = ref(true)

const props = defineProps({
  modelValue: {
    type: Boolean,
    default: false
  }
});
const emit = defineEmits(['update:modelValue', 'success']);

const close = () => {
  emit('update:modelValue', false);
};

watch(() => props.modelValue, (newVal) => {
  if (newVal) {
    nextTick(() => {
      initCanvas()
      getCanvasSize()
    })
  } else {
    // 清空画布
    clearSignature()
  }
})
  
const initCanvas = () => {
  ctx.value = uni.createCanvasContext('signatureCanvas', this)
  
  // 设置画布样式
  ctx.value.setStrokeStyle('#000000')
  ctx.value.setLineWidth(4)
  ctx.value.setLineCap('round')
  ctx.value.setLineJoin('round')

  // 初始填充白色背景
  ctx.value.setFillStyle('#ffffff')
  ctx.value.fillRect(0, 0, canvasWidth.value, canvasHeight.value)
  ctx.value.draw(true)
}

// 获取画布实际尺寸
const getCanvasSize = () => {
  uni.createSelectorQuery()
    .select('.signature-canvas')
    .boundingClientRect(res => {
      if (res) {
        
        canvasWidth.value = res.width
        canvasHeight.value = res.height
        console.log(canvasHeight.value, canvasWidth.value, "ssssssssssssssssssss")
        // 初始填充白色背景
        ctx.value.setFillStyle('#ffffff')
        ctx.value.fillRect(0, 0, canvasWidth.value, canvasHeight.value)
        ctx.value.draw(true)
        
        // 设置canvas的实际绘制尺寸(解决高清屏模糊问题)
        // const pixelRatio = uni.getSystemInfoSync().pixelRatio
        // const canvas = document.querySelector('.signature-canvas')
        // if (canvas) {
        //   canvas.width = res.width * pixelRatio
        //   canvas.height = res.height * pixelRatio
        //   ctx.value.scale(pixelRatio, pixelRatio)
        // }
      }
    })
    .exec()
}

const handleTouchStart = (e) => {
  isDrawing.value = true
  const point = {
    x: e.touches[0].x,
    y: e.touches[0].y
  }
  points.value = [point]
  
  // 开始路径
  ctx.value.beginPath()
  ctx.value.moveTo(point.x, point.y)
}

const handleTouchMove = (e) => {
  if (!isDrawing.value) return
  
  const point = {
    x: e.touches[0].x,
    y: e.touches[0].y
  }
  points.value.push(point)
  
  if (points.value.length >= 2) {
    isEmpty.value = false
    ctx.value.moveTo(points.value[points.value.length - 2].x, points.value[points.value.length - 2].y)
    ctx.value.lineTo(point.x, point.y)
    ctx.value.stroke()
    ctx.value.draw(true)
  }
}

const handleTouchEnd = () => {
  isDrawing.value = false
  points.value = []
}
// 清除签名内容
const clearSignature = () => {
  ctx.value.clearRect(0, 0, canvasWidth.value, canvasHeight.value)
  ctx.value.setFillStyle('#ffffff')
  ctx.value.draw(true)
  isEmpty.value = true
}
// 完成签名
const saveSignature = () => {
  if (isEmpty.value) {
    return uni.showToast({title: '请签名', icon: 'none'})
  }
  uni.canvasToTempFilePath({
    canvasId: 'signatureCanvas',
    success: (res) => {
      emit('success', res.tempFilePath)
      close()
    },
    fail: (err) => {
      console.error('生成图片失败:', err)
    }
  })
}
</script>

<style lang="scss" scoped>
.lee-signature {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  width: 100%;
  height: 100%;
  z-index: 9999; /* 确保在最上层 */
  background-color: $uni-bg-color-grey;
  &__box {
    box-sizing: border-box;
    position: relative;
    height: 100%;
    display: flex;
    align-items: center;
    padding: 20rpx 0 20rpx 50px;
    overflow: hidden;
  }
  &__canvas {
    width: 600rpx;
    height: 100%;
    background-color: #fff;
    border-radius: 8rpx;
    box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
  }
  &__opreate {
    &--title {
      width: 100px;
      height: 30px;
      transform: rotate(90deg) translateY(-34px);
      transform-origin: center;
      position: absolute;
      right: 0;
    }
    &--back {
      position: absolute;
      top: 0;
      left: 0;
      width: 80px;
      height: 36px;
      line-height: 36px;
      transform: rotate(90deg) translateY(15px) translateX(34px);
      font-size: 28rpx;
      border-radius: 8rpx;
      background-color: #f0f0f0;
      color: #333;
      border: none;
    }
    &--fresh {
      position: absolute;
      top: 0;
      left: 0;
      width: 80px;
      height: 36px;
      line-height: 36px;
      transform: rotate(90deg) translateY(15px) translateX(120px);
      font-size: 28rpx;
      border-radius: 8rpx;
      background-color: #f0f0f0;
      color: #333;
      border: none;
    }
    &--finish {
      position: absolute;
      left: 0;
      bottom: 0;
      width: 120px;
      height: 36px;
      line-height: 36px;
      transform: rotate(90deg) translateY(36px) translateX(-53px);
      font-size: 28rpx;
      border-radius: 8rpx;
      background-color: #f0f0f0;
      color: #333;
      border: none;
    }
  }
}
.primary {
  background-color: #007aff;
  color: #fff;
}
</style>

使用签名组件

<template>
  <view class="signature">
    <button class="signature-button" @click="openSignature">点击签名</button>
    <image class="signature-image" v-if="signatureUrl" :src="signatureUrl"></image>
    <lee-signature v-model="showSignature" @success="getSignatureUrl"></lee-signature>
  </view>
</template>

<script setup>
import { ref } from 'vue';


const showSignature = ref(false)
const signatureUrl = ref('')
const openSignature = () => {
  showSignature.value = true
}
// 获取签名图片地址
const getSignatureUrl = (image) => {
  signatureUrl.value = image
}
</script>

<style lang="scss" scoped>
.signature {
  padding: 30rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
}
.signature-button {
  width: 260rpx;
  height: 70rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  background: $uni-color-primary;
}
.signature-image {
  box-sizing: border-box;
  margin-top: 30rpx;
  width: 344rpx;
  height: 909rpx;
}
</style>

效果展示

image-20250903174536457

使用时需要注意的地方

  • 由于签名是横版签的,所以在页面展示时需要旋转一下
上次更新 2025/12/6 16:52:48