<template>
|
<el-dialog title="确认签字" :visible.sync="visible" :width="dialogWidth" :close-on-click-modal="false"
|
custom-class="signature-dialog" append-to-body @close="$emit('update:visible', false)">
|
<div class="signature-container">
|
<div class="signature-content">
|
<canvas ref="signatureCanvas" :width="canvasWidth" :height="canvasHeight" @mousedown="startDrawing"
|
@mousemove="draw" @mouseup="stopDrawing" @mouseleave="stopDrawing" @touchstart="handleTouchStart"
|
@touchmove="handleTouchMove" @touchend="stopDrawing"></canvas>
|
</div>
|
<div class="signature-footer">
|
<el-button type="default" @click="clearCanvas">重置</el-button>
|
<el-button type="primary" @click="confirmSignature">确认</el-button>
|
</div>
|
</div>
|
</el-dialog>
|
</template>
|
|
<script>
|
import { customUploadRequest, getFullUrl } from '@/utils/utils'
|
import {editSignPicture} from './service'
|
export default {
|
name: 'SignatureCanvas',
|
props: {
|
visible: {
|
type: Boolean,
|
default: false
|
}
|
},
|
data() {
|
return {
|
isDrawing: false,
|
context: null,
|
lastX: 0,
|
lastY: 0,
|
hasDrawn: false
|
}
|
},
|
computed: {
|
dialogWidth() {
|
// 获取屏幕宽度的80%
|
return window.innerWidth * 0.8 + 'px'
|
},
|
canvasWidth() {
|
// 获取弹窗宽度的90%
|
return window.innerWidth * 0.8 * 0.9
|
},
|
canvasHeight() {
|
// 设置画布高度为宽度的1/3
|
return this.canvasWidth / 3
|
}
|
},
|
watch: {
|
visible(val) {
|
if (val) {
|
this.$nextTick(() => {
|
this.initCanvas()
|
// 添加窗口大小改变的监听
|
window.addEventListener('resize', this.handleResize)
|
})
|
} else {
|
// 移除监听
|
window.removeEventListener('resize', this.handleResize)
|
}
|
}
|
},
|
methods: {
|
handleResize() {
|
// 窗口大小改变时重新初始化画布
|
this.$nextTick(() => {
|
this.initCanvas()
|
})
|
},
|
initCanvas() {
|
const canvas = this.$refs.signatureCanvas
|
this.context = canvas.getContext('2d')
|
this.context.strokeStyle = 'rgba(4, 156, 154, 1)'
|
this.context.lineWidth = 2
|
this.context.lineCap = 'round'
|
this.context.lineJoin = 'round'
|
|
// 清空画布并绘制虚线边框
|
this.clearCanvas()
|
},
|
|
drawDashedBorder() {
|
const ctx = this.context
|
ctx.setLineDash([5, 5])
|
ctx.strokeStyle = '#dcdfe6'
|
ctx.strokeRect(0, 0, this.canvasWidth, this.canvasHeight)
|
ctx.setLineDash([])
|
ctx.strokeStyle = 'rgba(4, 156, 154, 1)'
|
},
|
|
startDrawing(event) {
|
this.isDrawing = true
|
const rect = this.$refs.signatureCanvas.getBoundingClientRect()
|
this.lastX = event.clientX - rect.left
|
this.lastY = event.clientY - rect.top
|
},
|
|
draw(event) {
|
if (!this.isDrawing) return
|
|
const rect = this.$refs.signatureCanvas.getBoundingClientRect()
|
const x = event.clientX - rect.left
|
const y = event.clientY - rect.top
|
|
this.context.beginPath()
|
this.context.moveTo(this.lastX, this.lastY)
|
this.context.lineTo(x, y)
|
this.context.stroke()
|
|
this.lastX = x
|
this.lastY = y
|
this.hasDrawn = true
|
},
|
|
handleTouchStart(event) {
|
event.preventDefault()
|
const touch = event.touches[0]
|
const rect = this.$refs.signatureCanvas.getBoundingClientRect()
|
this.lastX = touch.clientX - rect.left
|
this.lastY = touch.clientY - rect.top
|
this.isDrawing = true
|
},
|
|
handleTouchMove(event) {
|
event.preventDefault()
|
if (!this.isDrawing) return
|
|
const touch = event.touches[0]
|
const rect = this.$refs.signatureCanvas.getBoundingClientRect()
|
const x = touch.clientX - rect.left
|
const y = touch.clientY - rect.top
|
|
this.context.beginPath()
|
this.context.moveTo(this.lastX, this.lastY)
|
this.context.lineTo(x, y)
|
this.context.stroke()
|
|
this.lastX = x
|
this.lastY = y
|
this.hasDrawn = true
|
},
|
|
stopDrawing() {
|
this.isDrawing = false
|
},
|
|
clearCanvas() {
|
this.context.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
|
this.drawDashedBorder()
|
this.hasDrawn = false
|
},
|
|
confirmSignature() {
|
const canvas = this.$refs.signatureCanvas
|
const ctx = this.context
|
|
// 校验是否签名
|
if (this.isCanvasBlank()) {
|
this.$message && this.$message.warning('请先确认签名')
|
return
|
}
|
// 保存当前画布内容
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
|
|
// 先填充背景色
|
ctx.fillStyle = 'rgba(239, 248, 250, 1)'
|
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
|
// 创建一个临时画布来保存签名内容
|
const tempCanvas = document.createElement('canvas')
|
tempCanvas.width = canvas.width
|
tempCanvas.height = canvas.height
|
const tempCtx = tempCanvas.getContext('2d')
|
tempCtx.putImageData(imageData, 0, 0)
|
|
// 将签名内容绘制到主画布上
|
ctx.drawImage(tempCanvas, 0, 0)
|
// 导出图片为base64
|
const base64Data = canvas.toDataURL('image/png');
|
// base64转blob
|
const blob = this.dataURLtoBlob(base64Data);
|
// 新增:生成带时间戳的文件名
|
const timestamp = Date.now();
|
const fileName = `signature_${timestamp}.png`;
|
const file = new File([blob], fileName, { type: blob.type });
|
// 上传
|
customUploadRequest({
|
file,
|
onSuccess: (res) => {
|
// 假设返回的图片路径为 res.url
|
const url = res.msg || res.data || res
|
editSignPicture({ signPicture: res.msg }).then(res => {
|
if (res.code == 200) {
|
this.$message.success('签名成功');
|
this.$emit('confirm', url);
|
}
|
})
|
},
|
onError: (err) => {
|
this.$message && this.$message.error ? this.$message.error('上传签名失败') : alert('上传签名失败');
|
}
|
});
|
},
|
|
// 新增:base64转blob
|
dataURLtoBlob(dataurl) {
|
const arr = dataurl.split(',');
|
const mime = arr[0].match(/:(.*?);/)[1];
|
const bstr = atob(arr[1]);
|
let n = bstr.length;
|
const u8arr = new Uint8Array(n);
|
while (n--) {
|
u8arr[n] = bstr.charCodeAt(n);
|
}
|
return new Blob([u8arr], { type: mime });
|
},
|
|
// 新增方法:判断画布是否为空白
|
isCanvasBlank() {
|
return !this.hasDrawn
|
}
|
},
|
beforeDestroy() {
|
// 组件销毁前移除监听
|
window.removeEventListener('resize', this.handleResize)
|
}
|
}
|
</script>
|
|
<style lang="less" scoped>
|
.signature-dialog {
|
:deep(.el-dialog__body) {
|
padding: 0;
|
}
|
|
:deep(.el-dialog) {
|
margin-top: 10vh !important;
|
}
|
}
|
|
.signature-container {
|
background: #fff;
|
border-radius: 4px;
|
|
.signature-content {
|
padding: 20px;
|
display: flex;
|
justify-content: center;
|
|
canvas {
|
border-radius: 4px;
|
background: rgba(239, 248, 250, 1);
|
width: 100%;
|
height: 100%;
|
}
|
}
|
|
.signature-footer {
|
padding: 20px;
|
border-top: 1px solid #dcdfe6;
|
display: flex;
|
justify-content: flex-end;
|
gap: 12px;
|
|
button {
|
width: 150px;
|
}
|
}
|
}
|
</style>
|