云计算百科
云计算领域专业知识百科平台

微信小程序 手动签字功能

1.复制代码

<template>
<view class="signature-container">
<canvas :canvas-id="canvasId" class="signature-canvas"
style="width: 100%; height: 100%; background-color: #ffffff;" @touchstart="onTouchStart"
@touchmove="onTouchMove" @touchend="onTouchEnd"></canvas>
</view>
</template>

<script setup>
import {
ref,
getCurrentInstance
} from 'vue' // 替换onMounted为onReady,无需getCurrentPages
import {
onLoad,
onShow,
onReady,
onHide,
onUnload
} from '@dcloudio/uni-app'
const instance = getCurrentInstance() // 仅用组件实例,不依赖页面实例
const canvasId = ref('signatureCanvas')
const ctx = ref(null)
const isDrawing = ref(false)
const lastX = ref(0)
const lastY = ref(0)
const canvasRect = ref({
left: 0,
top: 0,
width: 0,
height: 0
})
const hasValidDraw = ref(false) // 新增:标记是否有有效绘制(避免保存空画布)
const signType = ref(true)//签名状态
// 初始化CanvasContext(仅用组件实例,无getCurrentPages)
const initCanvasContext = () => {
return new Promise((resolve, reject) => {
try {
console.log('开始初始化CanvasContext…')
// 仅绑定组件实例,无需页面实例
ctx.value = uni.createCanvasContext(canvasId.value, instance)
if (ctx.value) {
setupCanvas()
resolve()
} else {
reject(new Error('CanvasContext创建失败'))
}
} catch (error) {
console.error('创建CanvasContext时出错:', error)
reject(error)
}
})
}

// 设置Canvas(优化尺寸兜底)
const setupCanvas = () => {
if (!ctx.value) return
console.log('开始设置Canvas…')
// 设置线条样式
ctx.value.setStrokeStyle('#000000')
ctx.value.setLineWidth(3)
ctx.value.setLineCap('round')
ctx.value.setLineJoin('round')

// 获取Canvas尺寸(兜底300×300,避免尺寸为0)
getCanvasRect().then(rect => {
canvasRect.value = rect
console.log('Canvas尺寸:', rect)
// 绘制白色背景(确保画布有基础内容,避免空画布)
ctx.value.setFillStyle('#ffffff')
ctx.value.fillRect(0, 0, rect.width, rect.height)
// 同步绘制(非增量),确保背景先渲染
ctx.value.draw(false, () => {
console.log('Canvas背景绘制完成')
})
})
}

// 获取Canvas位置和尺寸(仅绑定组件实例)
const getCanvasRect = () => {
return new Promise((resolve) => { // 简化reject,直接兜底
const query = uni.createSelectorQuery()
// 仅绑定组件实例,无页面实例
if (instance) {
query.in(instance)
}
query.select(`.signature-canvas`)
.boundingClientRect((rect) => {
if (rect && rect.width > 0 && rect.height > 0) {
resolve({
left: rect.left,
top: rect.top,
width: rect.width,
height: rect.height
})
} else {
console.warn('未找到Canvas元素,使用兜底尺寸')
// 兜底尺寸(关键:避免width/height为0导致空画布)
resolve({
left: 0,
top: 0,
width: 300,
height: 300
})
}
})
.exec()
})
}

// 绘制线条(优化:draw回调标记有效绘制)
const drawLine = (x1, y1, x2, y2) => {
if (!ctx.value) return
// 过滤极小移动(避免无效绘制)
if (Math.abs(x1 – x2) < 1 && Math.abs(y1 – y2) < 1) return

console.log(`绘制线条: (${x1.toFixed(2)}, ${y1.toFixed(2)}) -> (${x2.toFixed(2)}, ${y2.toFixed(2)})`)
try {
ctx.value.beginPath()
ctx.value.setStrokeStyle('#000000')
ctx.value.setLineWidth(3)
ctx.value.setLineCap('round')
ctx.value.setLineJoin('round')
ctx.value.moveTo(x1, y1)
ctx.value.lineTo(x2, y2)
ctx.value.stroke()
// 增量绘制 + 回调标记(关键:确保绘制完成后标记)
ctx.value.draw(true, () => {
hasValidDraw.value = true // 有有效绘制才允许保存
console.log('线条绘制完成(异步回调)')
})
} catch (error) {
console.error('绘制线条时出错:', error)
}
}

// 触摸开始(优化)
const onTouchStart = async (e) => {
if (!ctx.value) return
const touch = e.touches[0]
await getCanvasRect() // 确保尺寸已获取
const rect = canvasRect.value
const x = touch.clientX – rect.left
const y = touch.clientY – rect.top

isDrawing.value = true
lastX.value = x
lastY.value = y
ctx.value.beginPath()
ctx.value.moveTo(x, y)
}

// 触摸移动(优化)
const onTouchMove = async (e) => {
if (!isDrawing.value || !ctx.value) return
const touch = e.touches[0]
await getCanvasRect()
const rect = canvasRect.value
const x = touch.clientX – rect.left
const y = touch.clientY – rect.top

drawLine(lastX.value, lastY.value, x, y)
lastX.value = x
lastY.value = y
}

// 触摸结束(优化:等待最后一笔绘制)
const onTouchEnd = () => {
isDrawing.value = false
if (ctx.value) ctx.value.closePath()
// 等待最后一次draw完成(100ms足够)
setTimeout(() => {
console.log('最后一笔绘制完成,可保存')
}, 100)
}

// 清空画布(重置绘制标记)
const clearCanvas = () => {
if (!ctx.value) return
getCanvasRect().then(rect => {
ctx.value.clearRect(0, 0, rect.width, rect.height)
ctx.value.setFillStyle('#ffffff')
ctx.value.fillRect(0, 0, rect.width, rect.height)
ctx.value.setStrokeStyle('#000000')
ctx.value.setLineWidth(3)
ctx.value.draw(false, () => {
hasValidDraw.value = false // 重置绘制标记
console.log('画布已清空')
})
})
}

// 保存为图片(核心修复:仅绑定组件实例,无getCurrentPages)
const saveToImage = () => {
return new Promise((resolve, reject) => {
// 先校验:无有效绘制直接拒绝
if (!hasValidDraw.value) {
uni.showToast({
title: '请先签名后再提交',
icon: 'none',
duration: 2000
});
reject(new Error('画布为空,请先绘制'))
return
}

// 关键:延迟200ms,等待所有draw异步绘制完成
setTimeout(async () => {
const rect = await getCanvasRect()
console.log('保存Canvas尺寸:', rect)

uni.canvasToTempFilePath({
canvasId: canvasId.value,
x: 0,
y: 0,
width: rect.width,
height: rect.height,
destWidth: rect.width * 2,
destHeight: rect.height * 2,
fileType: 'png',
quality: 1,
// 核心修复1:绑定组件实例(instance),无需页面实例
success: (res) => {
console.log('图片保存成功:', res.tempFilePath)
resolve(res.tempFilePath)
},
fail: (err) => {
console.error('图片保存失败:', err)
// 兜底重试(仍绑定组件实例)
setTimeout(() => {
uni.canvasToTempFilePath({
canvasId: canvasId.value,
width: rect.width,
height: rect.height,
fileType: 'png',
success: resolve,
fail: reject,
}, instance) // 仅绑定组件实例
}, 500)
}
}, instance) // 核心:绑定组件实例(解决canvas-id找不到的问题)
}, 500) // 等待最后一次draw完成(关键)
})
}

// 测试绘制(用于验证)
const testDraw = () => {
// if (!ctx.value) return
// getCanvasRect().then(rect => {
// ctx.value.beginPath()
// ctx.value.setStrokeStyle('#ff0000')
// ctx.value.setLineWidth(5)
// ctx.value.moveTo(50, 50)
// ctx.value.lineTo(100, 100)
// ctx.value.stroke()
// ctx.value.draw(false, () => {
// hasValidDraw.value = true // 标记有有效绘制
// console.log('测试绘制完成')
// })
// })
}

// 生命周期优化:onReady比mounted晚,确保Canvas已注册(无需getCurrentPages)
onReady(() => {
console.log('组件onReady,开始初始化Canvas…')
// 缩短延迟(onReady已足够晚)
setTimeout(() => {
initCanvasContext()
.then(() => {
console.log('Canvas初始化成功')
// 测试绘制(验证保存)
setTimeout(testDraw, 1000)
})
.catch(err => {
console.error('Canvas初始化失败:', err)
})
}, 300)
})

// 暴露方法
defineExpose({
clearCanvas,
saveToImage,
testDraw
})
</script>

<style scoped>
.signature-container {
width: 100%;
height: 100%;
position: relative;
min-height: 300px;
/* 关键:兜底高度,避免Canvas尺寸为0 */
}

.signature-canvas {
width: 100%;
height: 100%;
border: 1px solid #ccc;
touch-action: none;
position: relative;
z-index: 1;
/* 确保触摸事件不被拦截 */
}
</style>

2.放到components里,文件名SignaturePad.js

3.引入你的页面

import SignaturePad from '@/components/SignaturePad.vue'

4.使用

<template>
<SignaturePad ref="signatureRef" />
</template>

<script setup>
import {
ref
} from 'vue'
const signatureRef = ref()
// 清除签名
const handleReset = () => {
signatureRef.value.clearCanvas()
}
// 提交签名
const handleSubmit = async () => {
try {
const result = await signatureRef.value.saveToImage();
tabList.value.forEach((item, index) => {
item.check = (index === activeTab.value);
});
} catch (error) {
console.error('保存失败:', error);
}
};
</script>

5.注意要有访问图片域名在微信开发者里设置,要不然后返回的图片看不了。

赞(0)
未经允许不得转载:网硕互联帮助中心 » 微信小程序 手动签字功能
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!