From 31ccf971d909811daeb7f8425aae1eece5e52be9 Mon Sep 17 00:00:00 2001 From: bb_pan Date: Sat, 30 Aug 2025 10:28:52 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=B5=E5=AD=90=E7=AD=BE=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../miliu-autograph/miliu-autograph.vue | 158 ++++++++++++++---- 1 file changed, 124 insertions(+), 34 deletions(-) diff --git a/src/components/miliu-autograph/miliu-autograph.vue b/src/components/miliu-autograph/miliu-autograph.vue index f1c7550..b185fa2 100644 --- a/src/components/miliu-autograph/miliu-autograph.vue +++ b/src/components/miliu-autograph/miliu-autograph.vue @@ -68,13 +68,17 @@ if (memberStore.userInfo.nickName && memberStore.userInfo.nickName.length < 5) { nickName.value = '正楷字书写' } +const paths = ref([]) // 保存所有笔迹(每笔是一组点数组) +const PEN = { width: 6, color: '#000000' } // 若你已有类似配置可复用 + watch( () => props.modelValue, (val) => { if (val) { // 小延时确保 canvas 已经渲染 setTimeout(() => { - uni.createSelectorQuery() + uni + .createSelectorQuery() .in(_this) .select('#mycanvas') .fields({ size: true, rect: true }, (data) => { @@ -86,7 +90,7 @@ watch( .exec() }, 50) // 可以尝试 30~100ms,根据实际机型调整 } - } + }, ) const showRotateToast = (msg) => { @@ -143,43 +147,62 @@ const drawBackground = (ctx, width = 100, height = 100) => { // 触摸开始 const touchstart = (e) => { - let startX = e.changedTouches[0].x - let startY = e.changedTouches[0].y - let startPoint = { X: startX, Y: startY } - points.push(startPoint) + const x = e.changedTouches[0].x + const y = e.changedTouches[0].y + const p = { X: x, Y: y } + + // 新开一条路径并保存第一个点 + paths.value.push([p]) + + // 立即在可见画布上开始一段(方便用户看到) + canvaCtx.setLineWidth(PEN.width) + canvaCtx.setStrokeStyle(PEN.color) + canvaCtx.setLineCap('round') + canvaCtx.setLineJoin('round') + canvaCtx.beginPath() -} -// 触摸移动 -const touchmove = (e) => { - let moveX = e.changedTouches[0].x - let moveY = e.changedTouches[0].y - let movePoint = { X: moveX, Y: moveY } - points.push(movePoint) - if (points.length >= 2) { - draw() - hasDrawn.value = true // 标记已签名 - } -} -// 绘制 -const draw = () => { - if (points.length < 2) return - canvaCtx.beginPath() - canvaCtx.moveTo(points[0].X, points[0].Y) - for (let i = 1; i < points.length; i++) { - canvaCtx.lineTo(points[i].X, points[i].Y) - } + canvaCtx.moveTo(x, y) + // 画一个极短的线段以显示起点(某些设备单点才会丢失) + canvaCtx.lineTo(x + 0.1, y + 0.1) canvaCtx.stroke() canvaCtx.draw(true) - points = [points[points.length - 1]] } -// 触摸结束 + +const touchmove = (e) => { + const x = e.changedTouches[0].x + const y = e.changedTouches[0].y + const p = { X: x, Y: y } + + // 把点推入当前路径 + const cur = paths.value[paths.value.length - 1] + if (cur) cur.push(p) + + // 在可见画布上增量画这段 + canvaCtx.setLineWidth(PEN.width) + canvaCtx.setStrokeStyle(PEN.color) + canvaCtx.setLineCap('round') + canvaCtx.setLineJoin('round') + + if (cur && cur.length >= 2) { + const prev = cur[cur.length - 2] + canvaCtx.beginPath() + canvaCtx.moveTo(prev.X, prev.Y) + canvaCtx.lineTo(p.X, p.Y) + canvaCtx.stroke() + canvaCtx.draw(true) + } +} + const touchend = () => { + // 不清 paths,保留以便导出;清掉临时 points(如果你还用 points) points = [] + hasDrawn.value = paths.value.length > 0 } // 清空画布 const clear = () => { return new Promise((resolve, reject) => { - uni.createSelectorQuery() + uni + .createSelectorQuery() .in(_this) .select('#mycanvas') .fields({ size: true, rect: true }, (data) => { @@ -188,9 +211,14 @@ const clear = () => { return } const { width, height } = data + // 清空像素 canvaCtx.clearRect(0, 0, width, height) + // flush 清空,再画背景(drawBackground 会 ctx.draw) canvaCtx.draw(false, () => { - drawBackground(canvaCtx, width, height) // ⚡ 保证背景立即重绘 + // 重绘背景(你的 drawBackground 会自己 ctx.draw) + drawBackground(canvaCtx, width, height) + // 清空笔迹记录 + paths.value = [] hasDrawn.value = false resolve() }) @@ -205,11 +233,73 @@ const confirm = () => { showRotateToast('请先签名再确认') return } - uni.canvasToTempFilePath({ canvasId: 'mycanvas' }, _this, _this.parent).then((res) => { - emits('complete', res.tempFilePath) - cancel() - }) + + uni + .createSelectorQuery() + .in(_this) + .select('#mycanvas') + .fields({ size: true, rect: true }, (data) => { + if (!data) return + const { width, height } = data + + // 1) 清空整个画布(包括背景) + canvaCtx.clearRect(0, 0, width, height) + + // 2) 在画布上只重绘笔迹(不画背景) + canvaCtx.setLineWidth(PEN.width) + canvaCtx.setStrokeStyle(PEN.color) + canvaCtx.setLineCap('round') + canvaCtx.setLineJoin('round') + canvaCtx.setFillStyle(PEN.color) + + paths.value.forEach((path) => { + if (!path || !path.length) return + if (path.length === 1) { + // 单点:画小圆点 + const p = path[0] + canvaCtx.beginPath() + canvaCtx.arc(p.X, p.Y, Math.max(1, PEN.width / 2), 0, Math.PI * 2) + canvaCtx.fill() + } else { + canvaCtx.beginPath() + canvaCtx.moveTo(path[0].X, path[0].Y) + for (let i = 1; i < path.length; i++) { + canvaCtx.lineTo(path[i].X, path[i].Y) + } + canvaCtx.stroke() + } + }) + + // 3) flush 并在回调中导出(确保像素已写入) + canvaCtx.draw(false, () => { + uni + .canvasToTempFilePath( + { + canvasId: 'mycanvas', + width, + height, + destWidth: width, + destHeight: height, + fileType: 'png', + quality: 1, + }, + _this, + _this.parent, + ) + .then((res) => { + emits('complete', res.tempFilePath) + // 直接关闭(按你要求:不再恢复背景) + cancel() + }) + .catch((err) => { + console.error('导出签名失败', err) + showRotateToast('导出失败,请重试') + }) + }) + }) + .exec() } + // 取消 const cancel = () => { clear().then(() => emits('update:modelValue', false))