|
|
@@ -1,89 +1,713 @@
|
|
|
<template>
|
|
|
<div class="signature-container">
|
|
|
- <!-- 签字按钮 -->
|
|
|
- <button @click="showSignature = true" class="sign-btn">点击签字</button>
|
|
|
-
|
|
|
- <!-- 签名弹窗 -->
|
|
|
- <div v-if="showSignature" class="modal">
|
|
|
- <div class="modal-content">
|
|
|
- <h3>请在此处签字</h3>
|
|
|
- <div class="canvas-container">
|
|
|
- <canvas
|
|
|
- ref="canvas"
|
|
|
- width="400"
|
|
|
- height="200"
|
|
|
- @touchstart.prevent="startDrawing"
|
|
|
- @touchmove.prevent="draw"
|
|
|
- @touchend="stopDrawing"
|
|
|
- @mousedown="startDrawing"
|
|
|
- @mousemove="draw"
|
|
|
- @mouseup="stopDrawing"
|
|
|
- @mouseleave="stopDrawing"
|
|
|
- ></canvas>
|
|
|
+ <!-- 签名触发区域 -->
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ @click="startSignature"
|
|
|
+ style="margin-bottom: 20px"
|
|
|
+ v-if="!signatureStarted"
|
|
|
+ >
|
|
|
+ {{signatureData?$t(`重新签名`):$t(`点击签字`)}}
|
|
|
+ </el-button>
|
|
|
+
|
|
|
+ <!-- 签名画布区域 -->
|
|
|
+ <div class="signature-canvas-container" v-if="signatureStarted">
|
|
|
+ <div class="canvas-header">
|
|
|
+ <span class="prompt-text">请在下方区域签名</span>
|
|
|
+ <div class="toolbar">
|
|
|
+ <el-button-group>
|
|
|
+ <el-button
|
|
|
+ size="small"
|
|
|
+ :type="penColor === 'black' ? 'primary' : ''"
|
|
|
+ @click="setPenColor('black')"
|
|
|
+ >
|
|
|
+ 黑色
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ size="small"
|
|
|
+ :type="penColor === 'blue' ? 'primary' : ''"
|
|
|
+ @click="setPenColor('blue')"
|
|
|
+ >
|
|
|
+ 蓝色
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ size="small"
|
|
|
+ :type="penColor === 'red' ? 'primary' : ''"
|
|
|
+ @click="setPenColor('red')"
|
|
|
+ >
|
|
|
+ 红色
|
|
|
+ </el-button>
|
|
|
+ </el-button-group>
|
|
|
+
|
|
|
+ <el-button-group style="margin-left: 10px;">
|
|
|
+ <el-button
|
|
|
+ size="small"
|
|
|
+ :type="penSize === 2 ? 'primary' : ''"
|
|
|
+ @click="setPenSize(2)"
|
|
|
+ >
|
|
|
+ 细
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ size="small"
|
|
|
+ :type="penSize === 4 ? 'primary' : ''"
|
|
|
+ @click="setPenSize(4)"
|
|
|
+ >
|
|
|
+ 中
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ size="small"
|
|
|
+ :type="penSize === 6 ? 'primary' : ''"
|
|
|
+ @click="setPenSize(6)"
|
|
|
+ >
|
|
|
+ 粗
|
|
|
+ </el-button>
|
|
|
+ </el-button-group>
|
|
|
+ <el-button
|
|
|
+ @click="clearCanvas"
|
|
|
+ :disabled="!hasSignature"
|
|
|
+ size="small"
|
|
|
+ >
|
|
|
+ <i class="el-icon-delete"></i> 清空
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ @click="saveSignature"
|
|
|
+ :disabled="!hasSignature"
|
|
|
+ size="small"
|
|
|
+ :loading="saving"
|
|
|
+ >
|
|
|
+ <i class="el-icon-check"></i> {{ saving ? '保存中...' : '确认签名' }}
|
|
|
+ </el-button>
|
|
|
</div>
|
|
|
- <div class="controls">
|
|
|
- <button @click="clearCanvas">清空</button>
|
|
|
- <button @click="saveSignature">确认签字</button>
|
|
|
- <button @click="showSignature = false">取消</button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 画布 -->
|
|
|
+ <div
|
|
|
+ class="signature-canvas"
|
|
|
+ @mousedown="startDrawing"
|
|
|
+ @mousemove="draw"
|
|
|
+ @mouseup="stopDrawing"
|
|
|
+ @mouseleave="stopDrawing"
|
|
|
+ @touchstart="startDrawingTouch"
|
|
|
+ @touchmove="drawTouch"
|
|
|
+ @touchend="stopDrawing"
|
|
|
+ ref="canvasContainer"
|
|
|
+ >
|
|
|
+ <canvas
|
|
|
+ ref="signatureCanvas"
|
|
|
+ :width="canvasWidth"
|
|
|
+ :height="canvasHeight"
|
|
|
+ ></canvas>
|
|
|
+
|
|
|
+ <!-- 画布提示 -->
|
|
|
+ <div class="canvas-hint" v-if="!hasSignature">
|
|
|
+ <div class="hint-icon">
|
|
|
+ <i class="el-icon-edit"></i>
|
|
|
+ </div>
|
|
|
+ <p class="hint-text">请在此处签名</p>
|
|
|
+ <p class="hint-subtext">支持鼠标或触摸操作</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+
|
|
|
+ <!-- 预览区域 -->
|
|
|
+ <div class="preview-area" v-if="signatureData">
|
|
|
+ <div class="preview-header">
|
|
|
+ <span class="preview-title">签名预览</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="preview-content">
|
|
|
+ <div class="preview-image-container">
|
|
|
+ <img :src="signatureData" alt="签名预览" class="preview-image" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+<!-- <div class="preview-actions">-->
|
|
|
+<!-- <el-button-->
|
|
|
+<!-- type="success"-->
|
|
|
+<!-- @click="uploadToHuaweiCloud"-->
|
|
|
+<!-- :loading="uploading"-->
|
|
|
+<!-- :disabled="uploading"-->
|
|
|
+<!-- >-->
|
|
|
+<!-- <i class="el-icon-upload"></i>-->
|
|
|
+<!-- {{ uploading ? '上传中...' : '上传至华为云' }}-->
|
|
|
+<!-- </el-button>-->
|
|
|
+
|
|
|
+<!-- <el-button-->
|
|
|
+<!-- type="info"-->
|
|
|
+<!-- @click="downloadSignature"-->
|
|
|
+<!-- :disabled="!signatureData"-->
|
|
|
+<!-- >-->
|
|
|
+<!-- <i class="el-icon-download"></i> 下载签名-->
|
|
|
+<!-- </el-button>-->
|
|
|
+
|
|
|
+<!-- <el-button-->
|
|
|
+<!-- type="warning"-->
|
|
|
+<!-- @click="restartSignature"-->
|
|
|
+<!-- >-->
|
|
|
+<!-- <i class="el-icon-refresh-left"></i> 重新签名-->
|
|
|
+<!-- </el-button>-->
|
|
|
+<!-- </div>-->
|
|
|
+
|
|
|
+ <!-- 上传状态 -->
|
|
|
+ <div class="upload-status" v-if="uploadStatus">
|
|
|
+ <el-alert
|
|
|
+ :title="uploadStatus.title"
|
|
|
+ :type="uploadStatus.type"
|
|
|
+ :description="uploadStatus.message"
|
|
|
+ :closable="false"
|
|
|
+ show-icon
|
|
|
+ >
|
|
|
+ </el-alert>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
+
|
|
|
+ <!-- 上传进度弹窗 -->
|
|
|
+ <el-dialog
|
|
|
+ title="上传华为云"
|
|
|
+ :visible.sync="uploadDialogVisible"
|
|
|
+ width="400px"
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ :show-close="false"
|
|
|
+ center
|
|
|
+ >
|
|
|
+ <div class="upload-progress-container">
|
|
|
+ <el-progress
|
|
|
+ :percentage="uploadProgress"
|
|
|
+ :status="uploadStatusClass"
|
|
|
+ :stroke-width="16"
|
|
|
+ ></el-progress>
|
|
|
+ <p class="progress-text">
|
|
|
+ {{ progressText }}
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ <span slot="footer" class="dialog-footer">
|
|
|
+ <el-button
|
|
|
+ @click="cancelUpload"
|
|
|
+ :disabled="uploadProgress === 100"
|
|
|
+ >
|
|
|
+ 取消上传
|
|
|
+ </el-button>
|
|
|
+ </span>
|
|
|
+ </el-dialog>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
export default {
|
|
|
name: "index",
|
|
|
+ props:['sa_workorder_confirmationid'],
|
|
|
data() {
|
|
|
return {
|
|
|
- showSignature: false,
|
|
|
- isDrawing: false,
|
|
|
- ctx: null,
|
|
|
+ // 签名状态
|
|
|
+ signatureStarted: false,
|
|
|
+ signatureData: null,
|
|
|
+ hasSignature: false,
|
|
|
+ saving: false,
|
|
|
+
|
|
|
+ // 画布相关
|
|
|
canvas: null,
|
|
|
- signatureData: null // 保存 base64 图片
|
|
|
+ ctx: null,
|
|
|
+ isDrawing: false,
|
|
|
+ lastX: 0,
|
|
|
+ lastY: 0,
|
|
|
+ penColor: 'black',
|
|
|
+ penSize: 4,
|
|
|
+
|
|
|
+ // 上传相关
|
|
|
+ uploading: false,
|
|
|
+ uploadDialogVisible: false,
|
|
|
+ uploadProgress: 0,
|
|
|
+ uploadStatus: null,
|
|
|
+ cancelToken: null,
|
|
|
+
|
|
|
+ // 弹窗
|
|
|
+ previewDialogVisible: false,
|
|
|
+ folderid:JSON.parse(sessionStorage.getItem('folderid')),
|
|
|
+
|
|
|
+ params: {
|
|
|
+ "classname": "system.attachment.huawei.OBS",
|
|
|
+ "method": "getFileName",
|
|
|
+ "content": {
|
|
|
+ "filename": '客户签字',
|
|
|
+ "filetype": 'png',
|
|
|
+ "parentid": ""//归属文件夹ID
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ bindData:{
|
|
|
+ "ownertable": 'sa_workorder_confirmation',
|
|
|
+ "ownerid": 0,
|
|
|
+ "usetype": 'signature',
|
|
|
+ }
|
|
|
+
|
|
|
};
|
|
|
},
|
|
|
+ computed: {
|
|
|
+ canvasWidth() {
|
|
|
+ return this.$refs.canvasContainer ?
|
|
|
+ this.$refs.canvasContainer.clientWidth : 1800
|
|
|
+ },
|
|
|
+ canvasHeight() {
|
|
|
+ return 400
|
|
|
+ },
|
|
|
+
|
|
|
+ uploadStatusClass() {
|
|
|
+ if (this.uploadProgress < 30) return ''
|
|
|
+ if (this.uploadProgress < 70) return 'warning'
|
|
|
+ if (this.uploadProgress < 100) return 'success'
|
|
|
+ return 'success'
|
|
|
+ },
|
|
|
+
|
|
|
+ progressText() {
|
|
|
+ if (this.uploadProgress === 0) return '准备上传...'
|
|
|
+ if (this.uploadProgress === 100) return '上传完成!'
|
|
|
+ return `正在上传 ${this.uploadProgress}%`
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
mounted() {
|
|
|
- this.initCanvas();
|
|
|
+ if (this.autoStart) {
|
|
|
+ this.startSignature()
|
|
|
+ }
|
|
|
},
|
|
|
methods: {
|
|
|
+ // 开始签名
|
|
|
+ startSignature() {
|
|
|
+ this.signatureData = ''
|
|
|
+ this.signatureStarted = true
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.initCanvas()
|
|
|
+ })
|
|
|
+ this.$emit('signature-start')
|
|
|
+ },
|
|
|
+
|
|
|
+ // 取消签名
|
|
|
+ cancelSignature() {
|
|
|
+ this.$confirm('确定要取消签名吗?签名内容将不会被保存。', '提示', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ }).then(() => {
|
|
|
+ this.signatureStarted = false
|
|
|
+ this.signatureData = null
|
|
|
+ this.hasSignature = false
|
|
|
+ this.$emit('signature-cancel')
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 重新签名
|
|
|
+ restartSignature() {
|
|
|
+ this.$confirm('重新签名将清除当前签名,是否继续?', '提示', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ }).then(() => {
|
|
|
+ this.signatureData = null
|
|
|
+ this.hasSignature = false
|
|
|
+ this.signatureStarted = true
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.initCanvas()
|
|
|
+ })
|
|
|
+ this.$emit('signature-restart')
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 初始化画布
|
|
|
initCanvas() {
|
|
|
- this.canvas = this.$refs.canvas;
|
|
|
- this.ctx = this.canvas.getContext('2d');
|
|
|
- this.ctx.lineCap = 'round';
|
|
|
- this.ctx.lineJoin = 'round';
|
|
|
- this.ctx.lineWidth = 3;
|
|
|
- this.ctx.strokeStyle = '#000';
|
|
|
+ if (!this.$refs.signatureCanvas) return
|
|
|
+
|
|
|
+ this.canvas = this.$refs.signatureCanvas
|
|
|
+ this.ctx = this.canvas.getContext('2d')
|
|
|
+
|
|
|
+ // 设置白色背景
|
|
|
+ this.ctx.fillStyle = 'white'
|
|
|
+ this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height)
|
|
|
+
|
|
|
+ // 设置绘制样式
|
|
|
+ this.ctx.strokeStyle = this.penColor
|
|
|
+ this.ctx.lineWidth = this.penSize
|
|
|
+ this.ctx.lineCap = 'round'
|
|
|
+ this.ctx.lineJoin = 'round'
|
|
|
+
|
|
|
+ // 添加网格背景
|
|
|
+ this.drawGridBackground()
|
|
|
},
|
|
|
+
|
|
|
+ // 绘制网格背景
|
|
|
+ drawGridBackground() {
|
|
|
+ const gridSize = 20
|
|
|
+ const gridColor = '#f0f0f0'
|
|
|
+
|
|
|
+ this.ctx.save()
|
|
|
+ this.ctx.strokeStyle = gridColor
|
|
|
+ this.ctx.lineWidth = 1
|
|
|
+
|
|
|
+ // 绘制水平线
|
|
|
+ for (let y = gridSize; y < this.canvas.height; y += gridSize) {
|
|
|
+ this.ctx.beginPath()
|
|
|
+ this.ctx.moveTo(0, y)
|
|
|
+ this.ctx.lineTo(this.canvas.width, y)
|
|
|
+ this.ctx.stroke()
|
|
|
+ }
|
|
|
+
|
|
|
+ // 绘制垂直线
|
|
|
+ for (let x = gridSize; x < this.canvas.width; x += gridSize) {
|
|
|
+ this.ctx.beginPath()
|
|
|
+ this.ctx.moveTo(x, 0)
|
|
|
+ this.ctx.lineTo(x, this.canvas.height)
|
|
|
+ this.ctx.stroke()
|
|
|
+ }
|
|
|
+
|
|
|
+ this.ctx.restore()
|
|
|
+ },
|
|
|
+
|
|
|
+ // 设置笔颜色
|
|
|
+ setPenColor(color) {
|
|
|
+ this.penColor = color
|
|
|
+ this.ctx.strokeStyle = color
|
|
|
+ },
|
|
|
+
|
|
|
+ // 设置笔粗细
|
|
|
+ setPenSize(size) {
|
|
|
+ this.penSize = size
|
|
|
+ this.ctx.lineWidth = size
|
|
|
+ },
|
|
|
+
|
|
|
+ // 清空画布
|
|
|
+ clearCanvas() {
|
|
|
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
|
|
+ this.ctx.fillStyle = 'white'
|
|
|
+ this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height)
|
|
|
+ this.drawGridBackground()
|
|
|
+ this.hasSignature = false
|
|
|
+ this.$emit('clear')
|
|
|
+ },
|
|
|
+
|
|
|
+ // 清空签名(完全重置)
|
|
|
+ clearSignature() {
|
|
|
+ this.clearCanvas()
|
|
|
+ this.signatureData = null
|
|
|
+ },
|
|
|
+
|
|
|
+ // 鼠标事件处理
|
|
|
startDrawing(e) {
|
|
|
- this.isDrawing = true;
|
|
|
- const rect = this.canvas.getBoundingClientRect();
|
|
|
- const x = (e.clientX || e.touches[0].clientX) - rect.left;
|
|
|
- const y = (e.clientY || e.touches[0].clientY) - rect.top;
|
|
|
- this.ctx.moveTo(x, y);
|
|
|
+ this.isDrawing = true
|
|
|
+ const rect = this.canvas.getBoundingClientRect()
|
|
|
+ this.lastX = e.clientX - rect.left
|
|
|
+ this.lastY = e.clientY - rect.top
|
|
|
},
|
|
|
+
|
|
|
draw(e) {
|
|
|
- if (!this.isDrawing) return;
|
|
|
- const rect = this.canvas.getBoundingClientRect();
|
|
|
- const x = (e.clientX || e.touches[0].clientX) - rect.left;
|
|
|
- const y = (e.clientY || e.touches[0].clientY) - rect.top;
|
|
|
- this.ctx.lineTo(x, y);
|
|
|
- this.ctx.stroke();
|
|
|
+ if (!this.isDrawing) return
|
|
|
+
|
|
|
+ const rect = this.canvas.getBoundingClientRect()
|
|
|
+ const currentX = e.clientX - rect.left
|
|
|
+ const currentY = e.clientY - rect.top
|
|
|
+
|
|
|
+ this.ctx.beginPath()
|
|
|
+ this.ctx.moveTo(this.lastX, this.lastY)
|
|
|
+ this.ctx.lineTo(currentX, currentY)
|
|
|
+ this.ctx.stroke()
|
|
|
+
|
|
|
+ this.lastX = currentX
|
|
|
+ this.lastY = currentY
|
|
|
+
|
|
|
+ // 标记已有签名
|
|
|
+ if (!this.hasSignature) {
|
|
|
+ this.hasSignature = true
|
|
|
+ }
|
|
|
},
|
|
|
+
|
|
|
stopDrawing() {
|
|
|
- if (this.isDrawing) {
|
|
|
- this.isDrawing = false;
|
|
|
- this.ctx.closePath();
|
|
|
- }
|
|
|
+ this.isDrawing = false
|
|
|
},
|
|
|
- clearCanvas() {
|
|
|
- this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
|
+
|
|
|
+ // 触摸事件处理
|
|
|
+ startDrawingTouch(e) {
|
|
|
+ e.preventDefault()
|
|
|
+ const touch = e.touches[0]
|
|
|
+ const mouseEvent = new MouseEvent('mousedown', {
|
|
|
+ clientX: touch.clientX,
|
|
|
+ clientY: touch.clientY
|
|
|
+ })
|
|
|
+ this.startDrawing(mouseEvent)
|
|
|
},
|
|
|
+
|
|
|
+ drawTouch(e) {
|
|
|
+ e.preventDefault()
|
|
|
+ const touch = e.touches[0]
|
|
|
+ const mouseEvent = new MouseEvent('mousemove', {
|
|
|
+ clientX: touch.clientX,
|
|
|
+ clientY: touch.clientY
|
|
|
+ })
|
|
|
+ this.draw(mouseEvent)
|
|
|
+ },
|
|
|
+
|
|
|
+ // 保存签名
|
|
|
saveSignature() {
|
|
|
- this.signatureData = this.canvas.toDataURL('image/png');
|
|
|
- this.showSignature = false;
|
|
|
- // 将签名数据传递给父组件或其他处理逻辑
|
|
|
- this.$emit('signature-complete', this.signatureData);
|
|
|
+ if (!this.hasSignature) {
|
|
|
+ this.$message.warning('请先完成签名')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ this.saving = true
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 创建临时画布
|
|
|
+ const tempCanvas = document.createElement('canvas')
|
|
|
+ const tempCtx = tempCanvas.getContext('2d')
|
|
|
+
|
|
|
+ tempCanvas.width = this.canvas.width
|
|
|
+ tempCanvas.height = this.canvas.height
|
|
|
+
|
|
|
+ // 白色背景
|
|
|
+ tempCtx.fillStyle = 'white'
|
|
|
+ tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height)
|
|
|
+
|
|
|
+ // 绘制签名
|
|
|
+ tempCtx.drawImage(this.canvas, 0, 0)
|
|
|
+
|
|
|
+ // 转换为Base64
|
|
|
+ this.signatureData = tempCanvas.toDataURL('image/png')
|
|
|
+ try {
|
|
|
+ // 移除Base64前缀
|
|
|
+ const base64WithoutPrefix = this.signatureData.replace(/^data:image\/(png|jpeg|jpg);base64,/, '')
|
|
|
+
|
|
|
+ // 将Base64转换为二进制字符串
|
|
|
+ const binaryString = window.atob(base64WithoutPrefix)
|
|
|
+
|
|
|
+ // 创建Uint8Array
|
|
|
+ const bytes = new Uint8Array(binaryString.length)
|
|
|
+ for (let i = 0; i < binaryString.length; i++) {
|
|
|
+ bytes[i] = binaryString.charCodeAt(i)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建Blob对象
|
|
|
+ const blob = new Blob([bytes], {
|
|
|
+ type: 'image/png'
|
|
|
+ })
|
|
|
+ console.log(blob,'2222输出blob数据')
|
|
|
+
|
|
|
+ // 生成文件名
|
|
|
+ const timestamp = new Date().getTime()
|
|
|
+ const fileName = `signature_${timestamp}.png`
|
|
|
+
|
|
|
+ // 创建File对象
|
|
|
+ const file = new File([blob], fileName, {
|
|
|
+ type: 'image/png',
|
|
|
+ lastModified: Date.now()
|
|
|
+ })
|
|
|
+ console.log(file,'file输出3333')
|
|
|
+ const index = file.name.lastIndexOf(".")
|
|
|
+ const ext = file.name.substr(index + 1)
|
|
|
+
|
|
|
+ const formData = new FormData()
|
|
|
+ formData.append('file', file)
|
|
|
+ formData.append('type', 'signature')
|
|
|
+ formData.append('uploadTime', new Date().toISOString())
|
|
|
+ formData.append('source', 'web_signature_pad')
|
|
|
+
|
|
|
+ this.getUploadUrl(file,ext,formData)
|
|
|
+ } catch (error) {
|
|
|
+ console.error('转换Base64为Blob失败:', error)
|
|
|
+ throw new Error('图片格式转换失败')
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成缩略图
|
|
|
+ const thumbnail = this.generateThumbnail(tempCanvas)
|
|
|
+
|
|
|
+ this.$emit('save', {
|
|
|
+ full: this.signatureData,
|
|
|
+ thumbnail: thumbnail,
|
|
|
+ timestamp: new Date().getTime()
|
|
|
+ })
|
|
|
+ this.$message.success('签名保存成功!')
|
|
|
+ this.signatureStarted = false
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('保存签名失败:', error)
|
|
|
+ this.$message.error('保存失败,请重试')
|
|
|
+ } finally {
|
|
|
+ this.saving = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 生成缩略图
|
|
|
+ generateThumbnail(canvas) {
|
|
|
+ const thumbnailCanvas = document.createElement('canvas')
|
|
|
+ const ctx = thumbnailCanvas.getContext('2d')
|
|
|
+
|
|
|
+ thumbnailCanvas.width = 200
|
|
|
+ thumbnailCanvas.height = 100
|
|
|
+
|
|
|
+ ctx.drawImage(canvas, 0, 0, 200, 100)
|
|
|
+
|
|
|
+ return thumbnailCanvas.toDataURL('image/png')
|
|
|
+ },
|
|
|
+
|
|
|
+ // 显示预览弹窗
|
|
|
+ showPreviewDialog() {
|
|
|
+ if (this.signatureData) {
|
|
|
+ this.previewDialogVisible = true
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 获取华为云上传地址
|
|
|
+ async getUploadUrl (file, ext,formData) {
|
|
|
+ this.params.content.parentid = this.folderid
|
|
|
+ const res = await this.$api.requested(this.params)
|
|
|
+ let url = res.data.uploadurl
|
|
|
+ let obsfilename = res.data.serialfilename
|
|
|
+
|
|
|
+ this.upoladFileToServer(url, file, ext, obsfilename,formData,res.data)
|
|
|
+ },
|
|
|
+ // 上传到华为云
|
|
|
+ async upoladFileToServer (url, file, ext, obsfilename,formData,data) {
|
|
|
+ this.$axios({
|
|
|
+ url:url,
|
|
|
+ method:'PUT',
|
|
|
+ data: file,
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/octet-stream'
|
|
|
+ }
|
|
|
+ }).then((res) => {
|
|
|
+ console.log(res,'输出res的数据')
|
|
|
+ this.createFileRecord(obsfilename)
|
|
|
+ })
|
|
|
+ },
|
|
|
+ // 上传成功以后生成附件记录
|
|
|
+ async createFileRecord (obsfilename,attinfos) {
|
|
|
+ this.bindData.ownerid = this.sa_workorder_confirmationid
|
|
|
+ let obj = {
|
|
|
+ "serialfilename": obsfilename
|
|
|
+ }
|
|
|
+ obj = Object.assign({},obj,this.bindData)
|
|
|
+ console.log(this.sa_workorder_confirmationid)
|
|
|
+ let param = {
|
|
|
+ "classname": "system.attachment.huawei.OBS",
|
|
|
+ "method": "uploadSuccess",
|
|
|
+ "content":obj
|
|
|
+ }
|
|
|
+ const res = await this.$api.requested(param)
|
|
|
+ console.log(res.data,'上传成功')
|
|
|
+ // if (res.code === 1) {
|
|
|
+ // this.$emit('onSuccess',res)
|
|
|
+ // }
|
|
|
+ },
|
|
|
+ // 上传至华为云
|
|
|
+ async uploadToHuaweiCloud() {
|
|
|
+ if (!this.signatureData) {
|
|
|
+ this.$message.warning('请先完成签名并保存')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ this.uploading = true
|
|
|
+ this.uploadDialogVisible = true
|
|
|
+ this.uploadProgress = 0
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 模拟上传进度
|
|
|
+ const progressInterval = setInterval(() => {
|
|
|
+ if (this.uploadProgress < 90) {
|
|
|
+ this.uploadProgress += 10
|
|
|
+ }
|
|
|
+ }, 200)
|
|
|
+
|
|
|
+ // 这里应该调用后端接口
|
|
|
+ // 注意:实际项目中应该通过后端上传,而不是前端直接上传
|
|
|
+ await this.uploadToBackend()
|
|
|
+
|
|
|
+ clearInterval(progressInterval)
|
|
|
+ this.uploadProgress = 100
|
|
|
+
|
|
|
+ // 上传成功状态
|
|
|
+ this.uploadStatus = {
|
|
|
+ type: 'success',
|
|
|
+ title: '上传成功',
|
|
|
+ message: '签名已成功上传至华为云存储'
|
|
|
+ }
|
|
|
+
|
|
|
+ this.$emit('upload-success', {
|
|
|
+ url: 'https://example.com/signature.png',
|
|
|
+ timestamp: new Date().getTime()
|
|
|
+ })
|
|
|
+
|
|
|
+ // 3秒后关闭弹窗
|
|
|
+ setTimeout(() => {
|
|
|
+ this.uploadDialogVisible = false
|
|
|
+ this.uploading = false
|
|
|
+ }, 3000)
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('上传失败:', error)
|
|
|
+
|
|
|
+ this.uploadStatus = {
|
|
|
+ type: 'error',
|
|
|
+ title: '上传失败',
|
|
|
+ message: error.message || '上传过程中出现错误'
|
|
|
+ }
|
|
|
+
|
|
|
+ this.$emit('upload-error', error)
|
|
|
+ this.uploading = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 上传到后端接口
|
|
|
+ uploadToBackend() {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ // 实际项目中应该调用后端接口
|
|
|
+ // 这里使用setTimeout模拟网络请求
|
|
|
+ setTimeout(() => {
|
|
|
+ // 模拟成功或失败
|
|
|
+ const success = Math.random() > 0.2
|
|
|
+ if (success) {
|
|
|
+ resolve()
|
|
|
+ } else {
|
|
|
+ reject(new Error('网络请求失败,请检查网络连接'))
|
|
|
+ }
|
|
|
+ }, 1500)
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 取消上传
|
|
|
+ cancelUpload() {
|
|
|
+ if (this.uploadProgress < 100) {
|
|
|
+ this.$confirm('确定要取消上传吗?', '提示', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ }).then(() => {
|
|
|
+ this.uploadDialogVisible = false
|
|
|
+ this.uploading = false
|
|
|
+ this.uploadProgress = 0
|
|
|
+ this.uploadStatus = null
|
|
|
+ this.$message.info('上传已取消')
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ this.uploadDialogVisible = false
|
|
|
+ this.uploading = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 下载签名
|
|
|
+ downloadSignature() {
|
|
|
+ if (!this.signatureData) {
|
|
|
+ this.$message.warning('没有可下载的签名')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const link = document.createElement('a')
|
|
|
+ const timestamp = new Date().getTime()
|
|
|
+ const fileName = `客户签名_${timestamp}.png`
|
|
|
+
|
|
|
+ link.href = this.signatureData
|
|
|
+ link.download = fileName
|
|
|
+ document.body.appendChild(link)
|
|
|
+ link.click()
|
|
|
+ document.body.removeChild(link)
|
|
|
+
|
|
|
+ this.$message.success('签名下载成功')
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -91,72 +715,323 @@ export default {
|
|
|
|
|
|
<style scoped>
|
|
|
.signature-container {
|
|
|
- padding: 20px;
|
|
|
+ min-height: 500px;
|
|
|
}
|
|
|
|
|
|
-.sign-btn {
|
|
|
- background-color: #007bff;
|
|
|
+/* 触发区域样式 */
|
|
|
+.trigger-area {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ min-height: 400px;
|
|
|
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
+ border-radius: 12px;
|
|
|
+ padding: 40px;
|
|
|
+}
|
|
|
+
|
|
|
+.trigger-content {
|
|
|
+ text-align: center;
|
|
|
color: white;
|
|
|
- border: none;
|
|
|
- padding: 10px 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.start-signature-btn {
|
|
|
+ padding: 20px 40px;
|
|
|
+ font-size: 18px;
|
|
|
+ border-radius: 50px;
|
|
|
+ background: rgba(255, 255, 255, 0.2);
|
|
|
+ border: 2px solid rgba(255, 255, 255, 0.5);
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.start-signature-btn:hover {
|
|
|
+ background: rgba(255, 255, 255, 0.3);
|
|
|
+ transform: translateY(-2px);
|
|
|
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
|
|
|
+}
|
|
|
+
|
|
|
+.hint-text {
|
|
|
+ margin-top: 20px;
|
|
|
font-size: 16px;
|
|
|
- cursor: pointer;
|
|
|
- border-radius: 4px;
|
|
|
+ opacity: 0.9;
|
|
|
+}
|
|
|
+
|
|
|
+.signature-instructions {
|
|
|
+ margin-top: 40px;
|
|
|
+ background: rgba(255, 255, 255, 0.1);
|
|
|
+ padding: 20px;
|
|
|
+ border-radius: 10px;
|
|
|
+ text-align: left;
|
|
|
+ max-width: 400px;
|
|
|
+ margin-left: auto;
|
|
|
+ margin-right: auto;
|
|
|
+}
|
|
|
+
|
|
|
+.signature-instructions h4 {
|
|
|
+ margin-bottom: 10px;
|
|
|
+ color: white;
|
|
|
+}
|
|
|
+
|
|
|
+.signature-instructions ul {
|
|
|
+ list-style: none;
|
|
|
+ padding-left: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.signature-instructions li {
|
|
|
+ padding: 5px 0;
|
|
|
+ color: rgba(255, 255, 255, 0.9);
|
|
|
+ position: relative;
|
|
|
+ padding-left: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.signature-instructions li:before {
|
|
|
+ content: '✓';
|
|
|
+ position: absolute;
|
|
|
+ left: 0;
|
|
|
+ color: #67C23A;
|
|
|
}
|
|
|
|
|
|
-.modal {
|
|
|
- position: fixed;
|
|
|
+/* 签名区域样式 */
|
|
|
+.signature-area {
|
|
|
+ background: white;
|
|
|
+ border-radius: 12px;
|
|
|
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
|
|
+ overflow: hidden;
|
|
|
+ margin-bottom: 60px;
|
|
|
+}
|
|
|
+
|
|
|
+.signature-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 20px;
|
|
|
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
|
|
+ border-bottom: 1px solid #e4e7ed;
|
|
|
+}
|
|
|
+
|
|
|
+.header-left {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.signature-title {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #303133;
|
|
|
+}
|
|
|
+
|
|
|
+.canvas-header {
|
|
|
+ padding: 15px 20px;
|
|
|
+ background: #f8f9fa;
|
|
|
+ border-bottom: 1px solid #ebeef5;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.prompt-text {
|
|
|
+ color: #606266;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.toolbar {
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.signature-canvas-container {
|
|
|
+ padding: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+.signature-canvas {
|
|
|
+ position: relative;
|
|
|
+ border: 2px solid #dcdfe6;
|
|
|
+ border-radius: 8px;
|
|
|
+ background: white;
|
|
|
+ cursor: crosshair;
|
|
|
+ margin-bottom: 100px;
|
|
|
+ min-height: 400px;
|
|
|
+ overflow: hidden;
|
|
|
+ transition: border-color 0.3s;
|
|
|
+}
|
|
|
+
|
|
|
+.signature-canvas:hover {
|
|
|
+ border-color: #409eff;
|
|
|
+}
|
|
|
+
|
|
|
+.signature-canvas canvas {
|
|
|
+ display: block;
|
|
|
+ background: white;
|
|
|
+ position: relative;
|
|
|
+ z-index: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.canvas-hint {
|
|
|
+ position: absolute;
|
|
|
top: 0;
|
|
|
left: 0;
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- background-color: rgba(0, 0, 0, 0.5);
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
display: flex;
|
|
|
+ flex-direction: column;
|
|
|
justify-content: center;
|
|
|
align-items: center;
|
|
|
- z-index: 999;
|
|
|
+ z-index: 0;
|
|
|
+ pointer-events: none;
|
|
|
+}
|
|
|
+
|
|
|
+.hint-icon {
|
|
|
+ font-size: 48px;
|
|
|
+ color: #dcdfe6;
|
|
|
+ margin-bottom: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.hint-text {
|
|
|
+ color: #909399;
|
|
|
+ font-size: 16px;
|
|
|
+ margin-bottom: 5px;
|
|
|
+}
|
|
|
+
|
|
|
+.hint-subtext {
|
|
|
+ color: #c0c4cc;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.signature-actions {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 20px;
|
|
|
+ padding: 20px 0;
|
|
|
}
|
|
|
|
|
|
-.modal-content {
|
|
|
+/* 预览区域样式 */
|
|
|
+.preview-area {
|
|
|
+ margin-top: 30px;
|
|
|
background: white;
|
|
|
+ border-radius: 12px;
|
|
|
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
padding: 20px;
|
|
|
- border-radius: 8px;
|
|
|
- width: 500px;
|
|
|
- max-width: 90%;
|
|
|
+ background: linear-gradient(135deg, #f0f9eb 0%, #e1f3d8 100%);
|
|
|
+ border-bottom: 1px solid #e1f3d8;
|
|
|
}
|
|
|
|
|
|
-.canvas-container {
|
|
|
- margin: 20px 0;
|
|
|
- border: 1px solid #ccc;
|
|
|
- border-radius: 4px;
|
|
|
+.preview-title {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #303133;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-content {
|
|
|
+ padding: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-image-container {
|
|
|
+ position: relative;
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ border-radius: 8px;
|
|
|
overflow: hidden;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ background: #fafafa;
|
|
|
+ text-align: center;
|
|
|
+ min-height: 200px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-image {
|
|
|
+ max-width: 100%;
|
|
|
+ max-height: 300px;
|
|
|
+ object-fit: contain;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-overlay {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 10px;
|
|
|
+ right: 10px;
|
|
|
}
|
|
|
|
|
|
-.controls {
|
|
|
+.preview-actions {
|
|
|
display: flex;
|
|
|
- gap: 10px;
|
|
|
justify-content: center;
|
|
|
+ gap: 15px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
-.controls button {
|
|
|
- padding: 8px 16px;
|
|
|
- border: none;
|
|
|
- border-radius: 4px;
|
|
|
- cursor: pointer;
|
|
|
+.upload-status {
|
|
|
+ margin-top: 20px;
|
|
|
}
|
|
|
|
|
|
-.controls button:nth-child(1) {
|
|
|
- background-color: #dc3545;
|
|
|
- color: white;
|
|
|
+/* 上传进度样式 */
|
|
|
+.upload-progress-container {
|
|
|
+ padding: 20px;
|
|
|
+ text-align: center;
|
|
|
}
|
|
|
|
|
|
-.controls button:nth-child(2) {
|
|
|
- background-color: #28a745;
|
|
|
- color: white;
|
|
|
+.progress-text {
|
|
|
+ margin-top: 15px;
|
|
|
+ color: #606266;
|
|
|
+ font-size: 14px;
|
|
|
}
|
|
|
|
|
|
-.controls button:nth-child(3) {
|
|
|
- background-color: #6c757d;
|
|
|
- color: white;
|
|
|
+/* 全屏预览样式 */
|
|
|
+.full-preview {
|
|
|
+ text-align: center;
|
|
|
+ padding: 20px;
|
|
|
+ background: #f5f7fa;
|
|
|
+ border-radius: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.full-preview img {
|
|
|
+ max-width: 100%;
|
|
|
+ max-height: 70vh;
|
|
|
+ object-fit: contain;
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
+ border-radius: 4px;
|
|
|
+ background: white;
|
|
|
+}
|
|
|
+
|
|
|
+/* 响应式调整 */
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .trigger-area {
|
|
|
+ padding: 20px;
|
|
|
+ min-height: 300px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .start-signature-btn {
|
|
|
+ padding: 15px 30px;
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .signature-instructions {
|
|
|
+ padding: 15px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .canvas-header {
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 10px;
|
|
|
+ align-items: stretch;
|
|
|
+ }
|
|
|
+
|
|
|
+ .toolbar {
|
|
|
+ justify-content: center;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .preview-actions {
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+
|
|
|
+ .preview-actions .el-button {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
}
|
|
|
</style>
|