|
|
|
|
<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>
|