使用uniapp的语法写一个vue-signature-pad vue2版本的电子签名

1.创建签名组件 (SignaturePad.vue)

点击查看代码
<template>
  <view class="signature-container">
    <canvas 
      class="signature-canvas"
      :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
      canvas-id="signatureCanvas"
      @touchstart="handleTouchStart"
      @touchmove="handleTouchMove"
      @touchend="handleTouchEnd"
    ></canvas>
    
    <view class="button-group">
      <button @click="clearSignature" class="btn-clear">清除签名</button>
      <button @click="saveSignature" class="btn-save">保存签名</button>
    </view>
  </view>
</template>

<script>
export default {
  props: {
    width: {
      type: Number,
      default: 300
    },
    height: {
      type: Number,
      default: 200
    },
    lineWidth: {
      type: Number,
      default: 2
    },
    lineColor: {
      type: String,
      default: '#000000'
    }
  },
  data() {
    return {
      canvasWidth: this.width,
      canvasHeight: this.height,
      ctx: null,
      isDrawing: false,
      lastX: 0,
      lastY: 0
    }
  },
  mounted() {
    this.initCanvas();
  },
  methods: {
    initCanvas() {
      this.ctx = uni.createCanvasContext('signatureCanvas', this);
      this.ctx.setStrokeStyle(this.lineColor);
      this.ctx.setLineWidth(this.lineWidth);
      this.ctx.setLineCap('round');
      this.ctx.setLineJoin('round');
    },
    
    handleTouchStart(e) {
      const touch = e.touches[0];
      this.isDrawing = true;
      this.lastX = touch.x;
      this.lastY = touch.y;
    },
    
    handleTouchMove(e) {
      if (!this.isDrawing) return;
      
      const touch = e.touches[0];
      this.ctx.beginPath();
      this.ctx.moveTo(this.lastX, this.lastY);
      this.ctx.lineTo(touch.x, touch.y);
      this.ctx.stroke();
      this.ctx.draw(true);
      
      this.lastX = touch.x;
      this.lastY = touch.y;
    },
    
    handleTouchEnd() {
      this.isDrawing = false;
    },
    
    clearSignature() {
      this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
      this.ctx.draw(true);
      this.$emit('cleared');
    },
    
    saveSignature() {
      uni.canvasToTempFilePath({
        canvasId: 'signatureCanvas',
        success: (res) => {
          this.$emit('saved', res.tempFilePath);
          uni.showToast({
            title: '签名保存成功',
            icon: 'success'
          });
        },
        fail: (err) => {
          console.error('保存签名失败:', err);
          uni.showToast({
            title: '保存失败',
            icon: 'none'
          });
        }
      }, this);
    }
  }
}
</script>

<style scoped>
.signature-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin: 20rpx;
}

.signature-canvas {
  border: 1px solid #ddd;
  background-color: #fff;
  margin-bottom: 20rpx;
}

.button-group {
  display: flex;
  justify-content: space-between;
  width: 100%;
}

.btn-clear {
  background-color: #f8f8f8;
  color: #333;
  margin-right: 10rpx;
}

.btn-save {
  background-color: #07c160;
  color: #fff;
}
</style>

2.在页面中使用签名组件
点击查看代码
<template>
  <view class="container">
    <view class="title">请在下方签名</view>
    <signature-pad 
      ref="signaturePad"
      :width="300" 
      :height="200"
      @saved="handleSignatureSaved"
      @cleared="handleSignatureCleared"
    />
    
    <view class="signature-preview" v-if="signatureImage">
      <image :src="signatureImage" mode="aspectFit" class="preview-image" />
      <text class="preview-text">签名预览</text>
    </view>
  </view>
</template>

<script>
import SignaturePad from '@/components/SignaturePad.vue';

export default {
  components: {
    SignaturePad
  },
  data() {
    return {
      signatureImage: ''
    }
  },
  methods: {
    handleSignatureSaved(tempFilePath) {
      this.signatureImage = tempFilePath;
      // 这里可以添加将签名图片上传服务器的逻辑
      // this.uploadSignature(tempFilePath);
    },
    
    handleSignatureCleared() {
      this.signatureImage = '';
      uni.showToast({
        title: '签名已清除',
        icon: 'none'
      });
    },
    
    uploadSignature(filePath) {
      uni.uploadFile({
        url: 'https://your-api-endpoint.com/upload',
        filePath: filePath,
        name: 'signature',
        success: (res) => {
          console.log('上传成功', res);
        },
        fail: (err) => {
          console.error('上传失败', err);
        }
      });
    }
  }
}
</script>

<style scoped>
.container {
  padding: 30rpx;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.title {
  font-size: 36rpx;
  margin-bottom: 30rpx;
  color: #333;
}

.signature-preview {
  margin-top: 40rpx;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.preview-image {
  width: 300rpx;
  height: 200rpx;
  border: 1rpx solid #eee;
}

.preview-text {
  margin-top: 10rpx;
  font-size: 28rpx;
  color: #666;
}
</style>

3.完整功能的签名组件
点击查看代码
<template>
  <view class="signature-container">
    <view class="canvas-wrapper">
      <canvas 
        class="signature-canvas"
        :style="{ 
          width: canvasWidth + 'px', 
          height: canvasHeight + 'px',
          border: `${borderWidth}px solid ${borderColor}`,
          backgroundColor: backgroundColor
        }"
        canvas-id="signatureCanvas"
        @touchstart="handleTouchStart"
        @touchmove="handleTouchMove"
        @touchend="handleTouchEnd"
      ></canvas>
    </view>
    
    <view class="controls">
      <view class="button-group">
        <button @click="clearSignature" class="btn-clear">清除</button>
        <button @click="undoLastStroke" class="btn-undo" :disabled="history.length === 0">撤销</button>
        <button @click="saveSignature" class="btn-save">保存</button>
      </view>
      
      <view class="config-group" v-if="showConfig">
        <slider :value="lineWidth" min="1" max="10" @change="setLineWidth" />
        <picker mode="selector" :range="colorOptions" @change="setLineColor">
          <view class="color-picker">选择颜色</view>
        </picker>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  props: {
    width: { type: Number, default: 300 },
    height: { type: Number, default: 200 },
    initialLineWidth: { type: Number, default: 2 },
    initialLineColor: { type: String, default: '#000000' },
    backgroundColor: { type: String, default: '#FFFFFF' },
    borderColor: { type: String, default: '#DDDDDD' },
    borderWidth: { type: Number, default: 1 },
    showConfig: { type: Boolean, default: true }
  },
  data() {
    return {
      canvasWidth: this.width,
      canvasHeight: this.height,
      lineWidth: this.initialLineWidth,
      lineColor: this.initialLineColor,
      ctx: null,
      isDrawing: false,
      lastX: 0,
      lastY: 0,
      history: [],
      colorOptions: [
        '#000000', '#FF0000', '#00FF00', 
        '#0000FF', '#FFFF00', '#FF00FF'
      ]
    }
  },
  mounted() {
    this.initCanvas();
  },
  methods: {
    initCanvas() {
      this.ctx = uni.createCanvasContext('signatureCanvas', this);
      this.setLineStyle();
    },
    
    setLineStyle() {
      this.ctx.setStrokeStyle(this.lineColor);
      this.ctx.setLineWidth(this.lineWidth);
      this.ctx.setLineCap('round');
      this.ctx.setLineJoin('round');
    },
    
    handleTouchStart(e) {
      const touch = e.touches[0];
      this.isDrawing = true;
      [this.lastX, this.lastY] = [touch.x, touch.y];
    },
    
    handleTouchMove(e) {
      if (!this.isDrawing) return;
      
      const touch = e.touches[0];
      this.drawLine(this.lastX, this.lastY, touch.x, touch.y);
      [this.lastX, this.lastY] = [touch.x, touch.y];
    },
    
    drawLine(x1, y1, x2, y2) {
      this.ctx.beginPath();
      this.ctx.moveTo(x1, y1);
      this.ctx.lineTo(x2, y2);
      this.ctx.stroke();
      this.ctx.draw(true);
      
      this.history.push({
        startX: x1, startY: y1,
        endX: x2, endY: y2,
        color: this.lineColor,
        width: this.lineWidth
      });
    },
    
    handleTouchEnd() {
      this.isDrawing = false;
    },
    
    clearSignature() {
      this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
      this.ctx.draw(true);
      this.history = [];
      this.$emit('cleared');
    },
    
    undoLastStroke() {
      if (this.history.length === 0) return;
      
      this.history.pop();
      this.redrawCanvas();
    },
    
    redrawCanvas() {
      this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
      
      this.history.forEach(stroke => {
        this.ctx.setStrokeStyle(stroke.color);
        this.ctx.setLineWidth(stroke.width);
        this.ctx.beginPath();
        this.ctx.moveTo(stroke.startX, stroke.startY);
        this.ctx.lineTo(stroke.endX, stroke.endY);
        this.ctx.stroke();
      });
      
      this.setLineStyle();
      this.ctx.draw(true);
    },
    
    saveSignature() {
      uni.canvasToTempFilePath({
        canvasId: 'signatureCanvas',
        fileType: 'png',
        quality: 1,
        success: (res) => {
          this.$emit('saved', {
            tempFilePath: res.tempFilePath,
            asBase64: () => this.getBase64(res.tempFilePath)
          });
        },
        fail: (err) => {
          console.error('保存失败:', err);
          this.$emit('error', err);
        }
      }, this);
    },
    
    getBase64(tempFilePath) {
      return new Promise((resolve, reject) => {
        uni.getFileSystemManager().readFile({
          filePath: tempFilePath,
          encoding: 'base64',
          success: (res) => {
            resolve(`data:image/png;base64,${res.data}`);
          },
          fail: reject
        });
      });
    },
    
    setLineWidth(e) {
      this.lineWidth = e.detail.value;
      this.setLineStyle();
    },
    
    setLineColor(e) {
      this.lineColor = this.colorOptions[e.detail.value];
      this.setLineStyle();
    }
  }
}
</script>

<style scoped>
.signature-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
}

.canvas-wrapper {
  margin: 20rpx 0;
  box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.1);
}

.signature-canvas {
  background-color: #fff;
}

.controls {
  width: 100%;
  padding: 0 20rpx;
}

.button-group {
  display: flex;
  justify-content: space-between;
  margin-bottom: 20rpx;
}

.btn-clear, .btn-undo, .btn-save {
  flex: 1;
  margin: 0 10rpx;
  height: 70rpx;
  line-height: 70rpx;
  border-radius: 8rpx;
  font-size: 28rpx;
}

.btn-clear {
  background-color: #f8f8f8;
  color: #333;
}

.btn-undo {
  background-color: #ff976a;
  color: #fff;
}

.btn-undo[disabled] {
  opacity: 0.6;
}

.btn-save {
  background-color: #07c160;
  color: #fff;
}

.config-group {
  background-color: #f7f7f7;
  padding: 20rpx;
  border-radius: 8rpx;
}

.color-picker {
  padding: 10rpx;
  background-color: #fff;
  border: 1rpx solid #ddd;
  border-radius: 4rpx;
  text-align: center;
  margin-top: 20rpx;
}
</style>

转自[https://www.cnblogs.com/diongk]()
posted @ 2025-08-25 17:16  李秋风  阅读(205)  评论(0)    收藏  举报