当前位置: 首页 > news >正文

vue3实现web端和小程序端个人签名

<template><el-dialogv-model="visible"width="900"title="签名"append-to-body@close="closeListDialog"><div class="signature-container" pb-10px><!-- 签名区域 --><div class="signature-content"><!-- 画布区域 --><div class="canvas-wrapper"><canvasref="canvasRef":width="canvasWidth":height="canvasHeight"class="signature-canvas"></canvas><div class="signature-prompt" v-if="!hasSignature">请在此处签名</div></div><!-- <div v-if="signatureImage" style="margin-top: 20px"><p>预览(实际背景透明):</p><img :src="signatureImage" /></div> --></div><!-- 颜色选择 --><div class="color-palette" items-center h-50px><divv-for="color in colors":key="color.value"class="color-option !mr-10px":class="{ active: currentColor === color.value }":style="{ backgroundColor: color.value }"@click="setColor(color.value)"></div><div flex-1></div><el-button link size="small" class="btn undo" @click="undo">撤销</el-button><el-buttonlinksize="small"my-30pxclass="btn clear"@click="clearCanvas">清除</el-button><el-buttonsize="small"type="danger"class="btn"@click="closeListDialog">取消</el-button><el-button size="small" class="btn confirm" @click="saveSignature">完成</el-button></div></div></el-dialog>
</template><script setup lang="ts">
const emit = defineEmits<{(e: "ok", value: any): void;
}>();
const visible = ref(false);const closeListDialog = () => {clearCanvas();visible.value = false;
};const open = () => {visible.value = true;nextTick(() => {initCanvas();window.addEventListener("resize", handleResize); // 确保对话框和 Canvas 已渲染});
};
defineExpose({open,
});
// 画布尺寸
const canvasWidth = ref(850);
const canvasHeight = ref(400);
const canvasRef: any = ref(null);
const signatureImage = ref("");
const hasSignature = ref(false);
const currentColor = ref("#000000");// 颜色选项
const colors = ref([{ value: "#000000", name: "黑色" },{ value: "#FF0000", name: "红色" },{ value: "#0066FF", name: "蓝色" },
]);// 绘图状态
let ctx: any = null;
let isDrawing = false;
let lastX = 0;
let lastY = 0;
let drawingHistory: any = [];const initCanvas = () => {const canvas: any = canvasRef.value;ctx = canvas.getContext("2d");// 设置透明背景ctx.fillStyle = "rgba(0, 0, 0, 0)";ctx.fillRect(0, 0, canvas.width, canvas.height);// 设置初始画笔样式setColor(currentColor.value);// 绑定触摸事件setupTouchEvents(canvas);
};// 设置画笔颜色
const setColor = (color: any) => {currentColor.value = color;ctx.strokeStyle = color;ctx.lineWidth = 6;ctx.lineCap = "round";ctx.lineJoin = "round";
};// 绑定触摸事件
const setupTouchEvents = (canvas: any) => {canvas.addEventListener("touchstart", handleTouchStart);canvas.addEventListener("touchmove", handleTouchMove);canvas.addEventListener("touchend", handleTouchEnd);canvas.addEventListener("mousedown", handleMouseDown);canvas.addEventListener("mousemove", handleMouseMove);canvas.addEventListener("mouseup", handleMouseUp);
};// 触摸事件处理
const handleTouchStart = (e: any) => {e.preventDefault();const touch = getTouchPos(e);startDrawing(touch.x, touch.y);
};const handleTouchMove = (e: any) => {e.preventDefault();const touch = getTouchPos(e);draw(touch.x, touch.y);
};const handleTouchEnd = () => {endDrawing();
};// 鼠标事件处理(用于开发调试)
const handleMouseDown = (e: any) => {const pos = getMousePos(e);startDrawing(pos.x, pos.y);
};const handleMouseMove = (e: any) => {if (!isDrawing) return;const pos = getMousePos(e);draw(pos.x, pos.y);
};const handleMouseUp = () => {endDrawing();
};// 开始绘制
const startDrawing = (x: any, y: any) => {isDrawing = true;lastX = x;lastY = y;ctx.beginPath();ctx.moveTo(x, y);saveDrawingState();
};// 绘制过程
const draw = (x: any, y: any) => {if (!isDrawing) return;ctx.lineTo(x, y);ctx.stroke();lastX = x;lastY = y;hasSignature.value = true;
};// 结束绘制
const endDrawing = () => {isDrawing = false;
};// 保存绘图状态
const saveDrawingState = () => {const canvas = canvasRef.value;drawingHistory.push(canvas.toDataURL());if (drawingHistory.length > 20) {drawingHistory.shift();}
};// 撤销操作
const undo = () => {if (drawingHistory.length > 0) {const lastState = drawingHistory.pop();const img = new Image();img.onload = () => {ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height);ctx.drawImage(img, 0, 0);hasSignature.value = drawingHistory.length > 0;};img.src = lastState;}
};// 清除画布
const clearCanvas = () => {ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height);drawingHistory = [];hasSignature.value = false;
};// 保存签名
const saveSignature = () => {if (!hasSignature.value) {alert("请先签名");return;}// 创建临时Canvas确保导出质量const tempCanvas = document.createElement("canvas");tempCanvas.width = canvasRef.value.width;tempCanvas.height = canvasRef.value.height;const tempCtx: any = tempCanvas.getContext("2d");// 绘制签名内容tempCtx.drawImage(canvasRef.value, 0, 0);// 导出为PNGsignatureImage.value = tempCanvas.toDataURL("image/png");// 这里可以触发父组件事件或上传到服务器console.log("签名图片:", signatureImage.value);emit("ok", signatureImage.value);closeListDialog();
};// 响应式调整
const handleResize = () => {canvasWidth.value = window.innerWidth * 0.8;canvasHeight.value = window.innerHeight * 0.6;
};// 获取触摸位置(改进版)
const getTouchPos = (e: any) => {const canvas: any = canvasRef.value;const rect = canvas.getBoundingClientRect();const touch = e.touches[0] || e.changedTouches[0];const scaleX = canvas.width / rect.width;const scaleY = canvas.height / rect.height;return {x: (touch.clientX - rect.left) * scaleX,y: (touch.clientY - rect.top) * scaleY,};
};// 获取鼠标位置(改进版)
const getMousePos = (e: any) => {const canvas: any = canvasRef.value;const rect = canvas.getBoundingClientRect();const scaleX = canvas.width / rect.width;const scaleY = canvas.height / rect.height;return {x: (e.clientX - rect.left) * scaleX,y: (e.clientY - rect.top) * scaleY,};
};
</script><style scoped>
.signature-container {position: relative;/* width: 100vw;height: 100vh; */background-color: #f5f5f5;display: flex;flex-direction: column;font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,Ubuntu, Cantarell, sans-serif;
}.status-bar {display: flex;justify-content: space-between;align-items: center;padding: 5px 15px;background-color: white;font-size: 14px;
}.signature-content {display: flex;flex: 1;padding: 10px;flex-direction: column;
}.color-palette {display: flex;flex-direction: row;align-items: center;background-color: white;border-radius: 10px;margin-inline: 10px;padding-inline: 10px;
}.color-option {width: 30px;height: 30px;border-radius: 50%;margin: 8px 0;cursor: pointer;border: 2px solid #eee;
}.color-option.active {border-color: #0066ff;
}.canvas-wrapper {/* flex: 1;position: relative;background-color: white;border-radius: 10px;overflow: hidden; */
}.signature-canvas {display: block;background-color: white;
}.signature-prompt {position: absolute;top: calc(50% - 18px);left: calc(50% - 50px);color: #999;font-size: 16px;
}.action-buttons {display: flex;justify-content: space-around;padding: 15px;background-color: white;
}.btn {border: none;border-radius: 20px;font-size: 14px;cursor: pointer;
}.undo {color: #333;
}.clear {color: #333;
}.confirm {background-color: #0066ff;color: white;
}.address-bar {padding: 8px 15px;background-color: white;text-align: center;font-size: 12px;color: #0066ff;border-top: 1px solid #eee;
}
</style>使用:```javascript
<signature ref="signatureRef" @ok="getsignature" />import signature from "./signature.vue"const signatureRef: any = ref(null);
const addSignature = () => {signatureRef.value?.open();
};

在这里插入图片描述

http://www.dtcms.com/a/284074.html

相关文章:

  • 《迭代器 VS 生成器:Python 惰性计算的两种实现方案详解》
  • Zabbix 分布式监控系统架构设计与优化
  • 硅谷顶级风投发布《2025年AI实战手册》|附下载
  • 芯谷科技--双四通道模拟/数字多路复用器74HC4052
  • Tcpdump使用
  • linux_线程同步
  • 七彩喜跌倒检测仪:用科技编织银发安全的“隐形防护网”
  • 【Mobx】学习笔记
  • Python-多线程编程
  • 时序数据库
  • AWS WebRTC:RTP讲解
  • GitHub开源轻量级语音模型 Vui:重塑边缘智能语音交互的未来
  • onenote千年老bug,字体bug (calibri微软雅黑) 的解决
  • Spring之【BeanDefinition】
  • MyBatis之核心组件与配置详解
  • LVDS系列21:Xilinx 7系ISERDESE2原语(二)
  • Amazon发布Kiro AI IDE
  • Kali Linux 信息收集完全指南:从原理到实战
  • 月结7-CO模块月结关键点和步骤总结
  • EP-SPY 基於GI6E 編碼無線電通訊間諜工具
  • 串口232通讯数据传输丢失的原因、不可靠性及底层原理分析
  • 从高斯噪声的角度分析MAE和MSE
  • Mac 安装及使用sdkman指南
  • Java全栈面试实录:从电商支付到AIGC的深度技术挑战
  • YOLOv3 技术深度解析:从理论到实践的完整指南
  • Clip微调系列:《MaPLe: Multi-modal Prompt Learning》
  • kong是什么
  • Vsan关闭集群与重启集群
  • 6、docker network
  • Docker国内镜像