SignaturePad.vue 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. <template>
  2. <view class="signature-container">
  3. <view class="flex justify-between items-center mb-2">
  4. <text class="text-sm text-gray-600">{{ title }}</text>
  5. <text class="text-xs text-blue-500" @click="clear">
  6. <text class="uni-icons uniui-trash mr-1"></text>
  7. 清除
  8. </text>
  9. </view>
  10. <canvas
  11. :id="canvasId"
  12. :canvas-id="canvasId"
  13. class="w-full rounded-lg border border-gray-200 bg-white"
  14. :style="{ height: height + 'px' }"
  15. @touchstart="touchStart"
  16. @touchmove="touchMove"
  17. @touchend="touchEnd"
  18. />
  19. <view v-if="hasSignature" class="mt-2 flex gap-2">
  20. <button class="flex-1 py-2 bg-gray-200 text-gray-700 rounded-lg text-sm" @click="clear">
  21. 重新签名
  22. </button>
  23. <button class="flex-1 py-2 bg-blue-500 text-white rounded-lg text-sm" @click="save">
  24. <text class="uni-icons uniui-checkmarkempty mr-1"></text>
  25. 确认签名
  26. </button>
  27. </view>
  28. </view>
  29. </template>
  30. <script setup lang="ts">
  31. import { ref, onMounted } from 'vue'
  32. interface Props {
  33. title?: string
  34. height?: number
  35. canvasId?: string
  36. }
  37. const props = withDefaults(defineProps<Props>(), {
  38. title: '请在下方签字',
  39. height: 200,
  40. canvasId: 'signature-canvas',
  41. })
  42. const emit = defineEmits<{
  43. save: [imagePath: string]
  44. }>()
  45. const hasSignature = ref(false)
  46. let ctx: UniApp.CanvasContext | null = null
  47. let isDrawing = false
  48. let points: { x: number; y: number }[] = []
  49. onMounted(() => {
  50. ctx = uni.createCanvasContext(props.canvasId)
  51. if (ctx) {
  52. ctx.setLineWidth(3)
  53. ctx.setLineCap('round')
  54. ctx.setLineJoin('round')
  55. ctx.setStrokeStyle('#333')
  56. }
  57. })
  58. function touchStart(e: any) {
  59. isDrawing = true
  60. const { x, y } = e.touches[0]
  61. points = [{ x, y }]
  62. ctx?.moveTo(x, y)
  63. }
  64. function touchMove(e: any) {
  65. if (!isDrawing) return
  66. const { x, y } = e.touches[0]
  67. points.push({ x, y })
  68. ctx?.lineTo(x, y)
  69. ctx?.stroke()
  70. ctx?.draw(true)
  71. hasSignature.value = true
  72. }
  73. function touchEnd() {
  74. isDrawing = false
  75. points = []
  76. }
  77. function clear() {
  78. ctx?.clearRect(0, 0, 1000, 1000)
  79. ctx?.draw()
  80. hasSignature.value = false
  81. }
  82. function save() {
  83. if (!hasSignature.value) {
  84. uni.showToast({ title: '请先签名', icon: 'none' })
  85. return
  86. }
  87. uni.canvasToTempFilePath({
  88. canvasId: props.canvasId,
  89. success: (res) => {
  90. emit('save', res.tempFilePath)
  91. uni.showToast({ title: '签名已保存', icon: 'success' })
  92. },
  93. fail: () => {
  94. uni.showToast({ title: '保存失败', icon: 'none' })
  95. },
  96. })
  97. }
  98. // 暴露方法给父组件
  99. defineExpose({
  100. clear,
  101. save,
  102. })
  103. </script>