uniapp h5端电子签名

由于uniapp h5 canvas touchstart事件不执行,市场上电子签名应用基本上PC端都无法使用。

主要因canvas标签会被uniapp编译时再套一uni-canvas

以下是居于svg控件电子签名解决方案

<template>
  <div class="signature-container">
    <div 
      class="signature-pad"
      ref="padRef"
      @mousedown="startDrawing"
      @mousemove="draw"
      @mouseup="stopDrawing"
      @mouseleave="stopDrawing"
      @touchstart="handleTouchStart"
      @touchmove="handleTouchMove"
      @touchend="stopDrawing"
    >
      <svg 
        xmlns="http://www.w3.org/2000/svg" 
        :width="width" 
        :height="height"
        ref="svgRef"
      >
        <path 
          v-for="(path, index) in paths" 
          :key="index"
          :d="path.d" 
          :stroke="path.color" 
          :stroke-width="path.width" 
          fill="none" 
          stroke-linecap="round"
          stroke-linejoin="round"
        />
      </svg>
    </div>
    <div class="button-group">
      <button @click="clearSignature">清除</button>
      <button @click="saveSignature">提交</button>
    </div>
  </div>
</template>

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

const padRef = ref(null);
const svgRef = ref(null);
const width = ref(0);
const height = ref(0);
const isDrawing = ref(false);
const currentPath = ref([]);
const paths = ref([]);

// 配置项
const strokeColor = ref('#000000');
const strokeWidth = ref(2);

const emit=defineEmits(['submit']);

onMounted(() => {
  resizeSignaturePad();
  window.addEventListener('resize', resizeSignaturePad);
});

const resizeSignaturePad = () => {
  if (padRef.value) {
    width.value = padRef.value.clientWidth;
    height.value = padRef.value.clientHeight;
  }
};

const startDrawing = (e) => {
  isDrawing.value = true;
  const point = getPosition(e);
  currentPath.value = [{
    x: point.x,
    y: point.y,
    color: strokeColor.value,
    width: strokeWidth.value
  }];
};

const draw = (e) => {
  if (!isDrawing.value) return;
  
  const point = getPosition(e);
  currentPath.value.push({
    x: point.x,
    y: point.y,
    color: strokeColor.value,
    width: strokeWidth.value
  });
  
  // 更新路径数据
  updatePaths();
};

const handleTouchStart = (e) => {
  e.preventDefault();
  startDrawing(e.touches[0]);
};

const handleTouchMove = (e) => {
  e.preventDefault();
  draw(e.touches[0]);
};

const stopDrawing = () => {
  if (!isDrawing.value) return;
  
  isDrawing.value = false;
  if (currentPath.value.length > 1) {
    paths.value.push({
      d: generatePathData(currentPath.value),
      color: strokeColor.value,
      width: strokeWidth.value
    });
  }
  currentPath.value = [];
};

const getPosition = (e) => {
  const rect = padRef.value.getBoundingClientRect();
  return {
    x: e.clientX - rect.left,
    y: e.clientY - rect.top
  };
};

const generatePathData = (points) => {
  if (points.length === 0) return '';
  
  let d = `M ${points[0].x} ${points[0].y}`;
  
  for (let i = 1; i < points.length; i++) {
    d += ` L ${points[i].x} ${points[i].y}`;
  }
  
  return d;
};

const updatePaths = () => {
  if (currentPath.value.length > 1) {
    const tempPaths = [...paths.value];
    tempPaths.push({
      d: generatePathData(currentPath.value),
      color: strokeColor.value,
      width: strokeWidth.value
    });
    paths.value = tempPaths;
  }
};

const clearSignature = () => {
  paths.value = [];
  currentPath.value = [];
};

const saveSignature = () => {
  if (paths.value.length === 0) {
    alert('请先签名');
    return;
  }
  
  const svgData = `
    <svg xmlns="http://www.w3.org/2000/svg" width="${width.value}" height="${height.value}" viewBox="0 0 ${width.value} ${height.value}">
      ${paths.value.map(path => `<path d="${path.d}" stroke="${path.color}" stroke-width="${path.width}" fill="none" stroke-linecap="round" stroke-linejoin="round"/>`).join('')}
    </svg>
  `;
  
  const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
  const svgUrl = URL.createObjectURL(svgBlob);
  
  // 触发保存事件
  // emit('submit', {
  //   // svg: svgData,
  //   url: svgUrl
  // });
  
  // 或者可以直接转换为PNG
  convertToPng(svgUrl);
};

// 可选:将SVG转换为PNG
const convertToPng = (svgUrl) => {
  const img = new Image();
  img.onload = () => {
    const canvas = document.createElement('canvas');
    canvas.width = width.value;
    canvas.height = height.value;
    const ctx = canvas.getContext('2d');
    ctx.drawImage(img, 0, 0);
    
    const pngUrl = canvas.toDataURL('image/png');
    emit('submit', pngUrl);
    URL.revokeObjectURL(svgUrl);
  };
  img.src = svgUrl;
};
</script>

<style scoped>
.signature-container {
  width: 100%;
  max-width: 600px;
  margin: 0 auto;
}

.signature-pad {
  width: 100%;
  height: 300rpx;
  border: 1px solid #ddd;
  background-color: #f9f9f9;
  touch-action: none;
}

svg {
  width: 100%;
  height: 100%;
  display: block;
}

.button-group {
  margin-top: 10px;
  display: flex;
  gap: 10px;
}

button {
  padding: 8px 16px;
  background-color: #4CAF50;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  flex: 1;
}

button:hover {
  background-color: #45a049;
}
</style>


uniapp h5端电子签名
https://unnzz.com/archives/jie-jue-uniapp-h5duan-dian-zi
作者
管理员
发布于
2025年07月05日
许可协议