You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
321 lines
7.3 KiB
321 lines
7.3 KiB
<template> |
|
<el-dialog |
|
title="电子签名" |
|
:visible.sync="visible" |
|
width="49%" |
|
:show-close="true" |
|
@close="handleClose" |
|
@opened="initCanvas" |
|
qappend-to-body |
|
> |
|
<el-form label-width="80px"> |
|
<el-form-item label="签名区域"> |
|
<div style="text-align: right"> |
|
<el-button type="primary" @click="undo"> 撤销 </el-button> |
|
<el-button type="primary" @click="clear"> 清除 </el-button> |
|
</div> |
|
</el-form-item> |
|
</el-form> |
|
<div class="canvas-container"> |
|
<canvas |
|
ref="signatureCanvas" |
|
class="signature-canvas" |
|
@mousedown="startDraw" |
|
@mousemove="drawing" |
|
@mouseup="stopDraw" |
|
@mouseleave="stopDraw" |
|
@touchstart.prevent="handleTouchStart" |
|
@touchmove.prevent="handleTouchMove" |
|
@touchend.prevent="stopDraw" |
|
></canvas> |
|
</div> |
|
<div slot="footer"> |
|
<el-button type="primary" @click="uploadLocalFile"> |
|
上传本地文件 |
|
</el-button> |
|
<el-button type="primary" @click="submit"> 上传 </el-button> |
|
<el-button @click="handleClose">取消</el-button> |
|
</div> |
|
<input |
|
ref="fileInput" |
|
type="file" |
|
accept="image/*" |
|
class="file-input" |
|
@change="handleFileChange" |
|
/> |
|
</el-dialog> |
|
</template> |
|
|
|
<script> |
|
export default { |
|
name: "ESignatureDialog", |
|
props: { |
|
visible: { |
|
type: Boolean, |
|
default: false, |
|
}, |
|
}, |
|
data() { |
|
return { |
|
ctx: null, |
|
isDrawing: false, |
|
lastX: 0, |
|
lastY: 0, |
|
history: [], |
|
signatureData: null, |
|
canvasInited: false, |
|
}; |
|
}, |
|
watch: { |
|
visible(newVal) { |
|
if (newVal) { |
|
this.$nextTick(() => { |
|
this.initCanvas(); |
|
}); |
|
} else { |
|
this.clearCanvas(); |
|
} |
|
}, |
|
}, |
|
methods: { |
|
initCanvas() { |
|
const canvas = this.$refs.signatureCanvas; |
|
if (!canvas) return; |
|
|
|
// 获取 canvas 容器的实际尺寸 |
|
const container = canvas.parentElement; |
|
const rect = container.getBoundingClientRect(); |
|
|
|
// 设置 canvas 实际像素尺寸 |
|
canvas.width = rect.width; |
|
canvas.height = rect.height; |
|
|
|
// 获取 2D 上下文 |
|
this.ctx = canvas.getContext("2d"); |
|
|
|
// 设置画笔样式 |
|
this.ctx.strokeStyle = "#000000"; |
|
this.ctx.lineWidth = 2; |
|
this.ctx.lineCap = "round"; |
|
this.ctx.lineJoin = "round"; |
|
|
|
// 清除画布 |
|
this.ctx.clearRect(0, 0, canvas.width, canvas.height); |
|
|
|
// 保存初始状态 |
|
this.history = []; |
|
this.saveState(); |
|
|
|
this.canvasInited = true; |
|
}, |
|
|
|
saveState() { |
|
const canvas = this.$refs.signatureCanvas; |
|
if (canvas && this.ctx) { |
|
this.history.push(canvas.toDataURL()); |
|
// 限制历史记录数量 |
|
if (this.history.length > 20) { |
|
this.history.shift(); |
|
} |
|
} |
|
}, |
|
|
|
startDraw(e) { |
|
if (!this.ctx) return; |
|
|
|
this.isDrawing = true; |
|
const pos = this.getPosition(e); |
|
this.lastX = pos.x; |
|
this.lastY = pos.y; |
|
|
|
// 开始新路径 |
|
this.ctx.beginPath(); |
|
this.ctx.moveTo(this.lastX, this.lastY); |
|
}, |
|
|
|
drawing(e) { |
|
if (!this.isDrawing || !this.ctx) return; |
|
|
|
e.preventDefault(); |
|
|
|
const pos = this.getPosition(e); |
|
|
|
this.ctx.lineTo(pos.x, pos.y); |
|
this.ctx.stroke(); |
|
|
|
this.lastX = pos.x; |
|
this.lastY = pos.y; |
|
}, |
|
|
|
stopDraw() { |
|
if (this.isDrawing) { |
|
this.isDrawing = false; |
|
this.saveState(); |
|
} |
|
}, |
|
|
|
getPosition(e) { |
|
const canvas = this.$refs.signatureCanvas; |
|
const rect = canvas.getBoundingClientRect(); |
|
|
|
let clientX, clientY; |
|
|
|
if (e.touches && e.touches.length > 0) { |
|
clientX = e.touches[0].clientX; |
|
clientY = e.touches[0].clientY; |
|
} else { |
|
clientX = e.clientX; |
|
clientY = e.clientY; |
|
} |
|
|
|
return { |
|
x: clientX - rect.left, |
|
y: clientY - rect.top, |
|
}; |
|
}, |
|
|
|
handleTouchStart(e) { |
|
if (e.touches.length === 1) { |
|
this.startDraw(e); |
|
} |
|
}, |
|
|
|
handleTouchMove(e) { |
|
if (e.touches.length === 1) { |
|
this.drawing(e); |
|
} |
|
}, |
|
|
|
undo() { |
|
if (this.history.length > 1 && this.ctx) { |
|
this.history.pop(); |
|
const lastState = this.history[this.history.length - 1]; |
|
const img = new Image(); |
|
img.onload = () => { |
|
const canvas = this.$refs.signatureCanvas; |
|
this.ctx.clearRect(0, 0, canvas.width, canvas.height); |
|
this.ctx.drawImage(img, 0, 0); |
|
}; |
|
img.src = lastState; |
|
} |
|
}, |
|
|
|
clear() { |
|
if (this.ctx) { |
|
const canvas = this.$refs.signatureCanvas; |
|
this.ctx.clearRect(0, 0, canvas.width, canvas.height); |
|
this.saveState(); |
|
} |
|
}, |
|
|
|
clearCanvas() { |
|
if (this.ctx) { |
|
const canvas = this.$refs.signatureCanvas; |
|
this.ctx.clearRect(0, 0, canvas.width, canvas.height); |
|
} |
|
this.history = []; |
|
this.signatureData = null; |
|
this.canvasInited = false; |
|
}, |
|
|
|
uploadLocalFile() { |
|
this.$refs.fileInput.click(); |
|
}, |
|
|
|
handleFileChange(e) { |
|
const file = e.target.files[0]; |
|
if (file && this.ctx) { |
|
const reader = new FileReader(); |
|
reader.onload = (event) => { |
|
const img = new Image(); |
|
img.onload = () => { |
|
const canvas = this.$refs.signatureCanvas; |
|
|
|
// 清除画布 |
|
this.ctx.clearRect(0, 0, canvas.width, canvas.height); |
|
|
|
// 计算缩放比例,保持图片比例 |
|
const scale = Math.min( |
|
canvas.width / img.width, |
|
canvas.height / img.height, |
|
1 |
|
); |
|
const x = (canvas.width - img.width * scale) / 2; |
|
const y = (canvas.height - img.height * scale) / 2; |
|
|
|
this.ctx.drawImage( |
|
img, |
|
x, |
|
y, |
|
img.width * scale, |
|
img.height * scale |
|
); |
|
this.saveState(); |
|
}; |
|
img.src = event.target.result; |
|
}; |
|
reader.readAsDataURL(file); |
|
} |
|
// 清空 input 值,允许重复选择同一文件 |
|
e.target.value = ""; |
|
}, |
|
|
|
submit() { |
|
const canvas = this.$refs.signatureCanvas; |
|
if (!canvas || !this.ctx) return; |
|
|
|
// 检查是否有签名内容 |
|
const imageData = this.ctx.getImageData( |
|
0, |
|
0, |
|
canvas.width, |
|
canvas.height |
|
); |
|
let hasContent = false; |
|
for (let i = 3; i < imageData.data.length; i += 4) { |
|
if (imageData.data[i] !== 0) { |
|
hasContent = true; |
|
break; |
|
} |
|
} |
|
|
|
if (!hasContent) { |
|
this.$message.warning("请先进行签名"); |
|
return; |
|
} |
|
|
|
this.signatureData = canvas.toDataURL("image/png"); |
|
this.$message.success("签名上传成功"); |
|
this.$emit("submit", this.signatureData); |
|
this.handleClose(); |
|
}, |
|
|
|
handleClose() { |
|
this.$emit("update:visible", false); |
|
}, |
|
}, |
|
}; |
|
</script> |
|
|
|
<style lang="scss" scoped> |
|
.canvas-container { |
|
width: 100%; |
|
height: 300px; |
|
border: 2px dashed #00c4b6; |
|
border-radius: 4px; |
|
overflow: hidden; |
|
background-color: #fff; |
|
|
|
.signature-canvas { |
|
width: 100%; |
|
height: 100%; |
|
cursor: crosshair; |
|
display: block; |
|
touch-action: none; |
|
} |
|
} |
|
|
|
.file-input { |
|
display: none; |
|
} |
|
</style>
|
|
|