javascript
复制代码
<template>
<el-dialog
v-model="visible"
width="900"
title="签名"
append-to-body
@close="closeListDialog"
>
<div class="signature-container" pb-10px>
<!-- 签名区域 -->
<div class="signature-content">
<!-- 画布区域 -->
<div class="canvas-wrapper">
<canvas
ref="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>
<div
v-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-button
link
size="small"
my-30px
class="btn clear"
@click="clearCanvas"
>
清除
</el-button>
<el-button
size="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);
// 导出为PNG
signatureImage.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();
};