当前位置: 首页 > 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.lryc.cn/news/591086.html

相关文章:

  • [RAG] LLM 交互层 | 适配器模式 | 文档解析器(`docling`库, CNN, OCR, OpenCV)
  • docker安装与简单项目上手
  • 如何实现微信小程序引导组件【添加到我的小程序】+ 附源码
  • wx小程序原生开发使用高德地图api
  • 大语言模型任务分解与汇总:从认知瓶颈到系统化解决方案
  • 分布式分片策略中,分片数量的评估与选择
  • SAP-ABAP:SAP的‘cl_http_utility=>escape_url‘对URL进行安全编码方法详解
  • 2025毫米波雷达技术白皮书:智能汽车与物联网的感知核心
  • 【web安全】DVWA存储型XSS分析与利用
  • 【Linux系统】进程地址空间
  • 一款基于PHP开发的不良事件上报系统源码,适用于医院安全管理。系统提供10类事件类别、50余种表单,支持在线填报、匿名上报及紧急报告。
  • 亚马逊广告进阶指南:广告成本预算怎么设置合理
  • Ubuntu20.04 安装qt5.12.8
  • Unity_通过鼠标点击屏幕移动屏幕里的一个对象
  • Django 实战:静态文件与媒体文件从开发配置到生产部署
  • 贴吧项目总结二
  • 基于Rust Softplus 函数实践方法
  • 【项目经验】小智ai源码学习记录
  • Webpack5 新特性与详细配置指南
  • 基于LSTM的机场天气分析及模型预测
  • Python eval函数详解 - 用法、风险与安全替代方案
  • Go语言学习日志(一)
  • Python应用进阶DAY7--面向对象编程基本特性和super函数
  • 电子电路中的电压符号命名约定
  • FreeSWITCH配置文件解析(6) mod_format_cdr 话单中字段解析
  • 浅谈自动化设计最常用的三款软件catia,eplan,autocad
  • 云服务器如何设置防火墙和安全组规则?
  • Linux内核网络栈深度剖析:inet_connection_sock.c的服务器端套接字管理
  • 【算法训练营Day13】二叉树part3
  • 华为P30/pro (ELE-AL00) 鸿蒙4.2降级 EMUI 9