海信医疗-远程超声管理平台-信创国产化
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

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