| | |
| | | <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)" |
| | | > |
| | | <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> |
| | | <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> |
| | |
| | | </template> |
| | | |
| | | <script> |
| | | import { customUploadRequest, getUploadedFilePath } from '@/utils/utils' |
| | | import {editSignPicture} from './service' |
| | | export default { |
| | | name: 'SignatureCanvas', |
| | | props: { |
| | |
| | | }, |
| | | data() { |
| | | return { |
| | | roleType: JSON.parse(sessionStorage.getItem('userInfo')).roleType || 1, // 1.超级管理员 2.审批人 3.工程师 4.实验员 |
| | | isDrawing: false, |
| | | context: null, |
| | | lastX: 0, |
| | | lastY: 0 |
| | | lastY: 0, |
| | | hasDrawn: false |
| | | } |
| | | }, |
| | | computed: { |
| | |
| | | if (val) { |
| | | this.$nextTick(() => { |
| | | this.initCanvas() |
| | | // 如果是实验员,加载保存的签名 |
| | | if (this.roleType == 4 || this.roleType == 3) { |
| | | this.loadSavedSignature() |
| | | } |
| | | // 添加窗口大小改变的监听 |
| | | window.addEventListener('resize', this.handleResize) |
| | | }) |
| | |
| | | // 窗口大小改变时重新初始化画布 |
| | | this.$nextTick(() => { |
| | | this.initCanvas() |
| | | // 如果是实验员,重新加载保存的签名 |
| | | if ((this.roleType === 4 || this.roleType == 3) && this.savedSignature) { |
| | | this.loadSavedSignature() |
| | | } |
| | | }) |
| | | }, |
| | | initCanvas() { |
| | |
| | | this.context.lineWidth = 2 |
| | | this.context.lineCap = 'round' |
| | | this.context.lineJoin = 'round' |
| | | |
| | | |
| | | // 清空画布并绘制虚线边框 |
| | | this.clearCanvas() |
| | | }, |
| | | |
| | | |
| | | drawDashedBorder() { |
| | | const ctx = this.context |
| | | ctx.setLineDash([5, 5]) |
| | |
| | | |
| | | 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) { |
| | |
| | | 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() { |
| | |
| | | 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) |
| | | |
| | | // 导出图片 |
| | | const signatureImage = canvas.toDataURL('image/png') |
| | | this.$emit('confirm', signatureImage) |
| | | } |
| | | // 导出图片为base64 |
| | | const base64Data = canvas.toDataURL('image/png'); |
| | | |
| | | // 如果是实验员,保存签名到sessionStorage |
| | | if (this.roleType == 4 || this.roleType == 3) { |
| | | // 保存当前画布内容(包括背景色) |
| | | // 保存到sessionStorage |
| | | sessionStorage.setItem('experimenterSignature', base64Data) |
| | | } |
| | | |
| | | // 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) => { |
| | | const url = getUploadedFilePath(res) |
| | | if (!url) { |
| | | this.$message.error('上传接口未返回文件地址') |
| | | return |
| | | } |
| | | editSignPicture({ signPicture: url }).then(result => { |
| | | if (result.code == 200) { |
| | | this.$message.success('签名成功'); |
| | | this.$emit('confirm', url); |
| | | } |
| | | }) |
| | | }, |
| | | onError: (err) => { |
| | | this.$message && this.$message.error ? this.$message.error('上传签名失败') : alert('上传签名失败'); |
| | | } |
| | | }); |
| | | }, |
| | | // 加载保存的签名到画布(允许修改) |
| | | loadSavedSignature() { |
| | | const savedData = sessionStorage.getItem('experimenterSignature'); |
| | | console.log('savedData:,', savedData) |
| | | if (savedData) { |
| | | const canvas = this.$refs.signatureCanvas |
| | | const ctx = this.context |
| | | const img = new Image() |
| | | img.onload = () => { |
| | | // 清空画布 |
| | | ctx.clearRect(0, 0, canvas.width, canvas.height) |
| | | // 绘制保存的签名 |
| | | ctx.drawImage(img, 0, 0, canvas.width, canvas.height) |
| | | // 重新绘制虚线边框 |
| | | this.drawDashedBorder() |
| | | this.hasDrawn = true |
| | | this.savedSignature = savedData |
| | | // 不锁定画布,允许修改 |
| | | } |
| | | img.src = savedData |
| | | } |
| | | }, |
| | | // 新增: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() { |
| | | // 组件销毁前移除监听 |
| | |
| | | :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); |
| | |
| | | height: 100%; |
| | | } |
| | | } |
| | | |
| | | |
| | | .signature-footer { |
| | | padding: 20px; |
| | | border-top: 1px solid #dcdfe6; |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 12px; |
| | | button{ |
| | | |
| | | button { |
| | | width: 150px; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | | </style> |