【签名组件】微信小程序H5及第三方组件库比较

以下是我为您提供的几个签名组件解决方案,您可以根据需要选择使用:

微信小程序原生签名组件 (pages/agreement/sign-integration.vue)

针对微信小程序和H5环境进行了特别优化 使用条件编译区分不同平台,适应性更强
不需要额外依赖,直接基于Canvas API实现
最适合对跨平台兼容性要求高的场景

<template>
  <view class="signature-page">
    <view class="signature-header">
      <text class="signature-title">请在下方区域签名</text>
    </view>
    
    <!-- #ifdef MP-WEIXIN -->
    <view class="signature-container">
      <canvas
        type="2d"
        id="signature-canvas"
        class="signature-canvas"
        disable-scroll="true"
        @touchstart="handleTouchStart"
        @touchmove="handleTouchMove"
        @touchend="handleTouchEnd"
      ></canvas>
    </view>
    <!-- #endif -->
    
    <!-- #ifndef MP-WEIXIN -->
    <view class="signature-container">
      <canvas
        id="signature-canvas"
        canvas-id="signature-canvas"
        class="signature-canvas"
        @touchstart="handleTouchStart"
        @touchmove="handleTouchMove"
        @touchend="handleTouchEnd"
      ></canvas>
    </view>
    <!-- #endif -->
    
    <view class="button-group">
      <button class="btn-clear" @tap="handleClear">清除</button>
      <button class="btn-save" @tap="handleSave" :disabled="isEmpty">确认</button>
    </view>
    
    <view class="tip-text">
      请确保签名清晰可辨,签名后点击"确认"按钮完成
    </view>
  </view>
</template>

<script>
export default {
              
  data() {
              
    return {
              
      ctx: null,
      canvas: null,
      isEmpty: true,
      lastX: 0,
      lastY: 0,
      isDrawing: false,
      options: {
              
        lineWidth: 4,
        lineColor: '#000000',
        canvasWidth: 0,
        canvasHeight: 0
      }
    };
  },
  
  mounted() {
              
    // 延迟初始化,确保组件已渲染
    setTimeout(() => {
              
      this.initCanvas();
    }, 100);
  },
  
  methods: {
              
    // 初始化画布
    async initCanvas() {
              
      try {
              
        // 获取设备信息
        const sysInfo = uni.getSystemInfoSync();
        this.options.canvasWidth = sysInfo.windowWidth - 40; // 留出边距
        this.options.canvasHeight = 200;
        
        // #ifdef MP-WEIXIN
        // 微信小程序使用新的2D接口
        const query = uni.createSelectorQuery().in(this);
        query.select('#signature-canvas')
          .fields({
               node: true, size: true })
          .exec((res) => {
              
            if (res[0] && res[0].node) {
              
              this.canvas = res[0].node;
              this.ctx = this.canvas.getContext('2d');
              
              // 设置画布大小
              this.canvas.width = this.options.canvasWidth;
              this.canvas.height = this.options.canvasHeight;
              
              // 初始化样式
              this.ctx.lineWidth = this.options.lineWidth;
              this.ctx.strokeStyle = this.options.lineColor;
              this.ctx.lineCap = 'round';
              this.ctx.lineJoin = 'round';
              
              // 填充白色背景
              this.ctx.fillStyle = '#ffffff';
              this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
            }
          });
        // #endif
        
        // #ifndef MP-WEIXIN
        // 其他平台使用传统Canvas接口
        this.ctx = uni.createCanvasContext('signature-canvas', this);
        
        // 设置样式
        this.ctx.lineWidth = this.options.lineWidth;
        this.ctx.strokeStyle = this.options.lineColor;
        this.ctx.lineCap = 'round';
        this.ctx.lineJoin = 'round';
        
        // 填充白色背景
        this.ctx.fillStyle = '#ffffff';
        this.ctx.fillRect(0, 0, this.options.canvasWidth, this.options.canvasHeight);
        this.ctx.draw(true);
        // #endif
      } catch (error) {
              
        console.error('初始化画布失败:', error);
        uni.showToast({
              
          title: '初始化画布失败',
          icon: 'none'
        });
      }
    },
    
    // 触摸开始
    handleTouchStart(e) {
              
      this.isDrawing = true;
      this.isEmpty = false;
      
      const touch = e.touches[0];
      
      // #ifdef MP-WEIXIN
      // 微信小程序使用新的2D接口
      if (this.ctx && touch) {
              
        this.lastX = touch.x;
        this.lastY = touch.y;
      }
      // #endif
      
      // #ifndef MP-WEIXIN
      // 其他平台使用传统Canvas接口
      if (this.ctx && touch) {
              
        this.lastX = touch.x;
        this.lastY = touch.y;
      }
      // #endif
    },
    
    // 触摸移动
    handleTouchMove(e) {
              
      if (!this.isDrawing) return;
      
      const touch = e.touches[0];
      if (!touch) return;
      
      // #ifdef MP-WEIXIN
      // 微信小程序使用新的2D接口
      if (this.ctx) {
              
        const currentX = touch.x;
        const currentY = touch.y;
        
        // 绘制线条
        this.ctx.beginPath();
        this.ctx.moveTo(this.lastX, this.lastY);
        this.ctx.lineTo(currentX, currentY);
        this.ctx.stroke();
        
        // 更新最后位置
        this.lastX = currentX;
        this.lastY = currentY;
      }
      // #endif
      
      // #ifndef MP-WEIXIN
      // 其他平台使用传统Canvas接口
      if (this.ctx) {
              
        const currentX = touch.x;
        const currentY = touch.y;
        
        // 绘制线条
        this.ctx.beginPath();
        this.ctx.moveTo(this.lastX, this.lastY);
        this.ctx.lineTo(currentX, currentY);
        this.ctx.stroke();
        this.ctx.draw(true);
        
        // 更新最后位置
        this.lastX = currentX;
        this.lastY = currentY;
      }
      // #endif
    },
    
    // 触摸结束
    handleTouchEnd() {
              
      this.isDrawing = false;
    },
    
    // 清除画布
    handleClear() {
              
      // #ifdef MP-WEIXIN
      // 微信小程序使用新的2D接口
      if (this.ctx && this.canvas) {
              
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        
        // 重新填充白色背景
        this.ctx.fillStyle = '#ffffff';
        this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
      }
      // #endif
      
      // #ifndef MP-WEIXIN
      // 其他平台使用传统Canvas接口
      if (this.ctx) {
              
        this.ctx.clearRect(0, 0, this.options.canvasWidth, this.options.canvasHeight);
        
        // 重新填充白色背景
        this.ctx.fillStyle = '#ffffff';
        this.ctx.fillRect(0, 0, this.options.canvasWidth, this.options.canvasHeight);
        this.ctx.draw(true);
      }
      // #endif
      
      this.isEmpty = true;
    },
    
    // 保存签名
    handleSave() {
              
      if (this.isEmpty) {
              
        uni.showToast({
              
          title: '请先完成签名',
          icon: 'none'
        });
        return;
      }
      
      // #ifdef MP-WEIXIN
      // 微信小程序使用新的2D接口
      const query = uni.createSelectorQuery().in(this);
      query.select('#signature-canvas')
        .fields({
               node: true, size: true })
        .exec((res) => {
              
          const canvas = res[0].node;
          
          // 将画布内容导出为图片
          wx.canvasToTempFilePath({
              
            canvas: canvas,
            success: (res) => {
              
              uni.$emit('signature-complete', res.tempFilePath);
              uni.navigateBack();
            },
            fail: (err) => {
              
              console.error('获取签名图片失败:', err);
              uni.showToast({
              
                title: '获取签名失败',
                icon: 'none'
              });
            }
          });
        });
      // #endif
      
      // #ifndef MP-WEIXIN
      // 其他平台使用传统Canvas接口
      uni.canvasToTempFilePath({
              
        canvasId: 'signature-canvas',
        success: (res) => {
              
          uni.$emit('signature-complete', res.tempFilePath);
          uni.navigateBack();
        },
        fail: (err) => {
              
          console.error('获取签名图片失败:', err);
          uni.showToast({
              
            title: '获取签名失败',
            icon: 'none'
          });
        }
      }, this);
      // #endif
    }
  }
};
</script>

<style lang="scss">
.signature-page {
              
  padding: 20rpx;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.signature-header {
              
  width: 100%;
  margin-bottom: 30rpx;
}

.signature-title {
              
  font-size: 32rpx;
  font-weight: 500;
  color: #333;
}

.signature-container {
              
  width: 100%;
  height: 400rpx;
  border: 1px solid #e0e0e0;
  border-radius: 8rpx;
  margin-bottom: 40rpx;
  overflow: hidden;
  background-color: #ffffff;
  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}

.signature-canvas {
              
  width: 100%;
  height: 100%;
  background-color: #ffffff;
}

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

.btn-clear, .btn-save {
              
  width: 45%;
  height: 80rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 40rpx;
  font-size: 28rpx;
}

.btn-clear {
              
  background-color: #f2f2f2;
  color: #666;
}

.btn-save {
              
  background-color: #3c9cff;
  color: #ffffff;
}

.btn-save[disabled] {
              
  background-color: #a0cfff;
}

.tip-text {
              
  font-size: 24rpx;
  color: #999;
  text-align: center;
  line-height: 1.5;
}
</style> 

基于Vue3的签名组件 (pages/agreement/signature-vue3.vue)

使用Vue3的组合式API开发
可与uView UI框架结合使用
代码结构清晰,易于维护和扩展
适合Vue3项目使用

<template>
  <view class="signature-page">
    <view class="signature-container">
      <canvas 
        id="signature-canvas"
        canvas-id="signature-canvas"
        class="signature-canvas"
        @touchstart="touchStart"
        @touchmove="touchMove"
        @touchend="touchEnd"
        @mousedown="mouseDown"
        @mousemove="mouseMove"
        @mouseup="mouseUp"
        @mouseleave="mouseUp"
      ></canvas>
    </view>
    <view class="tips">请在上方区域书写您的签名</view>
    
    <view class="button-group">
      <u-button type="default" text="清除" @click="clear"></u-button>
      <u-button type="primary" text="确认" @click="save" :disabled="isEmpty"></u-button>
    </view>
  </view>
</template>

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

// 状态变量
const isEmpty = ref(true);
const canvas = ref(null);
const ctx = ref(null);
const isDrawing = ref(false);
const lastPoint = ref({
             x: 0, y: 0 });

// 配置项
const canvasOptions = {
            
  lineWidth: 3,
  lineColor: '#000000',
  background: '#ffffff'
};

// 初始化画布
onMounted(() => {
            
  // 等待界面渲染完成后初始化
  setTimeout(initCanvas, 100);
});

// 清理资源
onUnmounted(() => {
            
  canvas.value = null;
  ctx.value = null;
});

// 初始化画布方法
const initCanvas = () => {
            
  try {
            
    // 判断当前环境
    const isMiniProgram = typeof wx !== 'undefined' && wx.canIUse('getSystemInfoSync');
    
    if (isMiniProgram) {
            
      // 小程序环境
      ctx.value = uni.createCanvasContext('signature-canvas');
      setBackground();
    } else {
            
      // H5环境
      const query = uni.createSelectorQuery();
      query.select('#signature-canvas')
        .fields({
             node: true, size: true })
        .exec((res) => {
            
          if (res[0]) {
            
            canvas.value = res[0].node;
            ctx.value = canvas.value.getContext('2d');
            
            // 设置画布尺寸
            const dpr = uni.getSystemInfoSync().pixelRatio;
            const rect = res[0].width ? res[0] : document.getElementById('signature-canvas').getBoundingClientRect();
            canvas.value.width = rect.width;
            canvas.value.height = rect.height;
            
            // 设置画布样式
            ctx.value.lineWidth = canvasOptions.lineWidth;
            ctx.value.strokeStyle = canvasOptions.lineColor;
            ctx.value.lineCap = 'round';
            ctx.value.lineJoin = 'round';
            
            // 填充背景色
            ctx.value.fillStyle = canvasOptions.background;
            ctx.value.fillRect(0, 0, canvas.value.width, canvas.value.height);
          }
        });
    }
  } catch (error) {
            
    console.error('初始化画布失败:', error);
    uni.showToast({
            
      title: '画布初始化失败,请重试',
      icon: 'none'
    });
  }
};

// 设置背景色 (小程序环境)
const setBackground = () => {
            
  if (!ctx.value) return;
  
  // 获取系统信息用于设置画布大小
  const sysInfo = uni.getSystemInfoSync();
  const canvasW = sysInfo.windowWidth;
  const canvasH = 200; // 设置画布高度
  
  ctx.value.fillStyle = canvasOptions.background;
  ctx.value.fillRect(0, 0, canvasW, canvasH);
  ctx.value.lineWidth = canvasOptions.lineWidth;
  ctx.value.strokeStyle = canvasOptions.lineColor;
  ctx.value.lineCap = 'round';
  ctx.value.lineJoin = 'round';
  ctx.value.draw(true);
};

// 触摸开始事件
const touchStart = (e) => {
            
  isEmpty.value = false;
  isDrawing.value = true;
  
  const touch = e.touches[0];
  const rect = getCanvasRect();
  
  // 记录起始点
  if (touch.x !== undefined) {
            
    // 小程序环境
    lastPoint.value = {
             x: touch.x, y: touch.y };
  } else {
            
    // H5环境
    lastPoint.value = {
             
      x: touch.clientX - rect.left, 
      y: touch.clientY - rect.top 
    };
  }
};

// 触摸移动事件
const touchMove = (e) => {
            
  if (!isDrawing.value) return;
  
  const touch = e.touches[0];
  const rect = getCanvasRect();
  let currentPoint;
  
  // 获取当前触摸点
  if (touch.x !== undefined) {
            
    // 小程序环境
    currentPoint = {
             x: touch.x, y: touch.y };
    drawLine(lastPoint.value, currentPoint);
  } else {
            
    // H5环境
    currentPoint = {
             
      x: touch.clientX - rect.left, 
      y: touch.clientY - rect.top 
    };
    drawLineH5(lastPoint.value, currentPoint);
  }
  
  // 更新最后一个点
  lastPoint.value = currentPoint;
};

// 触摸结束事件
const touchEnd = () => {
            
  isDrawing.value = false;
};

// 鼠标按下事件 (仅H5)
const mouseDown = (e) => {
            
  isEmpty.value = false;
  isDrawing.value = true;
  
  const rect = getCanvasRect();
  lastPoint.value = {
             
    x: e.clientX - rect.left, 
    y: e.clientY - rect.top 
  };
};

// 鼠标移动事件 (仅H5)
const mouseMove = (e) => {
            
  if (!isDrawing.value) return;
  
  const rect = getCanvasRect();
  const currentPoint = {
             
    x: e.clientX - rect.left, 
    y: e.clientY - rect.top 
  };
  
  drawLineH5(lastPoint.value, currentPoint);
  lastPoint.value = currentPoint;
};

// 鼠标释放事件 (仅H5)
const mouseUp = () => {
            
  isDrawing.value = false;
};

// 获取画布位置信息
const getCanvasRect = () => {
            
  try {
            
    const canvasEl = document.getElementById('signature-canvas');
    return canvasEl ? canvasEl.getBoundingClientRect() : {
             left: 0, top: 0 };
  } catch (e) {
            
    return {
             left: 0, top: 0 };
  }
};

// 绘制线条 (小程序)
const drawLine = (start, end) => {
            
  if (!ctx.value) return;
  
  ctx.value.beginPath();
  ctx.value.moveTo(start.x, start.y);
  ctx.value.lineTo(end.x, end.y);
  ctx.value.stroke();
  ctx.value.draw(true);
};

// 绘制线条 (H5)
const drawLineH5 = (start, end) => {
            
  if (!ctx.value) return;
  
  ctx.value.beginPath();
  ctx.value.moveTo(start.x, start.y);
  ctx.value.lineTo(end.x, end.y);
  ctx.value.stroke();
};

// 清除画布
const clear = () => {
            
  if (!ctx.value) return;
  
  const isMiniProgram = typeof wx !== 'undefined';
  
  if (isMiniProgram) {
            
    // 小程序环境
    const sysInfo = uni.getSystemInfoSync();
    ctx.value.clearRect(0, 0, sysInfo.windowWidth, 200);
    setBackground();
  } else {
            
    // H5环境
    ctx.value.clearRect(0, 0, canvas.value.width, canvas.value.height);
    ctx.value.fillStyle = canvasOptions.background;
    ctx.value.fillRect(0, 0, canvas.value.width, canvas.value.height);
  }
  
  isEmpty.value = true;
};

// 保存签名
const save = () => {
            
  if (isEmpty.value) {
            
    uni.showToast({
            
      title: '请先完成签名',
      icon: 'none'
    });
    return;
  }
  
  try {
            
    const isMiniProgram = typeof wx !== 'undefined';
    
    if (isMiniProgram) {
            
      // 小程序环境
      uni.canvasToTempFilePath({
            
        canvasId: 'signature-canvas',
        success: (res) => {
            
          uni.$emit('signature-complete', res.tempFilePath);
          uni.navigateBack();
        },
        fail: (err) => {
            
          console.error('保存签名失败:', err);
          uni.showToast({
            
            title: '保存签名失败',
            icon: 'none'
          });
        }
      });
    } else {
            
      // H5环境
      const dataURL = canvas.value.toDataURL('image/png');
      uni.$emit('signature-complete', dataURL);
      uni.navigateBack();
    }
  } catch (error) {
            
    console.error('保存签名失败:', error);
    uni.showToast({
            
      title: '保存签名失败',
      icon: 'none'
    });
  }
};
</script>

<style lang="scss">
.signature-page {
            
  padding: 20rpx;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.signature-container {
            
  width: 100%;
  height: 400rpx;
  border: 1px solid #e0e0e0;
  border-radius: 8rpx;
  margin-bottom: 20rpx;
  overflow: hidden;
}

.signature-canvas {
            
  width: 100%;
  height: 100%;
  background-color: #ffffff;
}

.tips {
            
  font-size: 28rpx;
  color: #999;
  margin-bottom: 30rpx;
}

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

.button-group :deep(.u-button) {
            
  width: 45%;
  margin: 0;
}
</style> 

vue3-esign开源组件 (pages/agreement/signature-vue3-esign.vue)

需要安装依赖:npm install vue3-esign
提供丰富的配置选项和事件 功能完善,使用简单
适合快速实现签名功能

<template>
  <view class="signature-page">
    <view class="signature-title">请在下方区域签名</view>
    
    <view class="signature-container">
      <esign
        ref="esignRef"
        :width="signatureWidth"
        :height="signatureHeight"
        :lineWidth="lineWidth"
        :lineColor="lineColor"
        :bgColor="bgColor"
        :isCrop="isCrop"
        :resultType="resultType"
        @start="handleStart"
        @end="handleEnd"
      ></esign>
    </view>
    
    <view class="button-group">
      <button class="btn-clear" @tap="handleClear">清除</button>
      <button class="btn-save" @tap="handleSave" :disabled="isEmpty">确认</button>
    </view>
    
    <view class="tip-text">
      请确保签名清晰可辨,签名后点击"确认"按钮完成
    </view>
  </view>
</template>

<script setup>
import {
               onMounted, ref } from 'vue';
import Esign from 'vue3-esign';

// 状态变量
const esignRef = ref(null);
const isEmpty = ref(true);
const screenWidth = ref(320);
const screenHeight = ref(500);

// 签名配置
const signatureWidth = ref(320);
const signatureHeight = ref(200);
const lineWidth = ref(4);
const lineColor = ref('#000000');
const bgColor = ref('#ffffff');
const isCrop = ref(true);
const resultType = ref('png');

// 生命周期
onMounted(() => {
              
  // 获取设备信息
  try {
              
    const sysInfo = uni.getSystemInfoSync();
    screenWidth.value = sysInfo.windowWidth;
    screenHeight.value = sysInfo.windowHeight;
    signatureWidth.value = screenWidth.value * 0.9; // 宽度为屏幕宽度的90%
  } catch (e) {
              
    console.error('获取设备信息失败', e);
  }
});

// 开始签名事件
const handleStart = (event) => {
              
  isEmpty.value = false;
  console.log('开始签名', event);
};

// 结束签名事件
const handleEnd = (event) => {
              
  console.log('结束签名', event);
};

// 清除签名
const handleClear = () => {
              
  if (esignRef.value) {
              
    esignRef.value.clear();
    isEmpty.value = true;
  }
};

// 保存签名
const handleSave = async () => {
              
  if (isEmpty.value) {
              
    uni.showToast({
              
      title: '请先完成签名',
      icon: 'none'
    });
    return;
  }
  
  try {
              
    if (esignRef.value) {
              
      const base64 = await esignRef.value.generate();
      if (base64) {
              
        // 发送签名数据给上一页面
        uni.$emit('signature-complete', base64);
        
        // 返回上一页
        uni.navigateBack({
              
          success: () => {
              
            console.log('签名完成,返回上一页');
          }
        });
      }
    }
  } catch (error) {
              
    console.error('获取签名图片失败', error);
    uni.showToast({
              
      title: '获取签名失败,请重试',
      icon: 'none'
    });
  }
};
</script>

<style lang="scss" scoped>
.signature-page {
              
  padding: 30rpx;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.signature-title {
              
  font-size: 32rpx;
  font-weight: 500;
  margin-bottom: 30rpx;
  color: #333;
}

.signature-container {
              
  width: 100%;
  border: 1px solid #ebedf0;
  border-radius: 12rpx;
  margin-bottom: 40rpx;
  background-color: #ffffff;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
  overflow: hidden;
}

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

.btn-clear, .btn-save {
              
  width: 45%;
  height: 80rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 40rpx;
  font-size: 28rpx;
}

.btn-clear {
              
  background-color: #f2f2f2;
  color: #666;
}

.btn-save {
              
  background-color: #3c9cff;
  color: #ffffff;
}

.btn-save[disabled] {
              
  background-color: #a0cfff;
}

.tip-text {
              
  font-size: 24rpx;
  color: #999;
  margin-top: 20rpx;
  text-align: center;
  line-height: 1.5;
}
</style> 

建议您从这三个方案中先尝试”微信小程序原生签名组件”,因为它不依赖外部库且针对多平台做了优化。如果遇到问题,再考虑使用第三方组件。
要使用这些组件,需要在相应页面中注册并引入,然后通过uni.$on(‘signature-complete’)监听签名完成事件。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容