前景:这个签名之前就存在了一个web端的鼠标绘制,但是因反馈操作不便,后又迭代了一个手机可以进行扫码签名了h5页面。
不得不说,这个签名颠倒真的是折磨了wo超级无敌久...期间我还去请教了之前写过类似签名的同事,参考了下之前的代码。。。但是当时实在是被折磨了太久了,思维一直局限在自己最开始写出来那种想法。
最近主要是在跟着修复问题,也是遇到瓶颈了,就想再看下这个问题。才发现。。。我把两种不同的想法糅合在一起,那一定会有问题存在的!只想告诫自己,切记要跳出固定思维,多思考,哪里不会不明白就console哪里。
一、二维码的实现
二、h5页面签名的绘制
总的来讲,也是借用了不知名大佬的好的方法,握拳感谢。
-
getHorizontalStyle方法 - 引入的
draw.js
主要代码如下:
<template>
<div class="sign-wrapper">
<div
class="canvas-box"
id="canvasBig"
:style="getHorizontalStyle">
<canvas></canvas>
<!-- 画布操作按钮 -->
<div class="btn_box">
<span class="btn clear-btn"
@touchstart.stop="clear"
@mousedown.stop="clear"
>清空
</span>
<span class="btn commit-btn"
@touchstart.stop="submitPNG"
@mousedown.stop="submitPNG"
>提交
</span>
</div>
</div>
</div>
</template>
<script>
// import VConsole from 'vconsole/dist/vconsole.min.js' //引入vconsole
// let vConsole = new VConsole() // 初始化
import Draw from "@/utils/draw";
// 上传图片到阿里云
let OSS = require('ali-oss')
let client = new OSS({
region: 'oss-cn-beijing',
accessKeyId: 'XXXXX',
accessKeySecret: 'XXXXXXXXXXXXX',
bucket: 'vueupdate'
})
/** 解决ios 12版本的post请求报错问题 */ //补充,这个仍然没有解决。。。。。
import axios from 'axios';
import qs from "qs";
import base from '@/api/base';
/** End */
// 引入vant中的轻提示组件
import Toast from 'vant/lib/toast';
import 'vant/lib/toast/style';
export default {
name: '',
components: {
},
data() {
return {
viewScanCodeUrl: '', // 签名地址
orderListId: '', //订单id
degree: 90, //旋转度数
}
},
mounted() {
console.log('========进入挂载==== mounted')
this.canvasBox = document.getElementById('canvasBig');
this.initCanvas();
},
getHorizontalStyle()方法如下:
computed: {
//计算画布大小
getHorizontalStyle() {
const d = document;
const w = window.innerWidth || d.documentElement.clientWidth || d.body.clientWidth;
const h = window.innerHeight || d.documentElement.clientHeight || d.body.clientHeight;
let length = (h - w) / 2;
let width = w;
let height = h;
switch (this.degree) {
case -90:
length = -length;
case 90:
width = h;
height = w;
break;
default:
length = 0;
}
if (this.canvasBox) {
this.canvasBox.removeChild(document.querySelector('canvas'));
this.canvasBox.appendChild(document.createElement('canvas'));
console.log('this.canvasBox333333:',this.canvasBox)
setTimeout(() => {
this.initCanvas();
}, 200);
}
return {
transform: `rotate(${this.degree}deg) translate(${length}px,${length}px)`,
width: `${width}px`,
height: `${height}px`,
transformOrigin: 'center center',
};
},
},
methods: {
//初始化签名画布
initCanvas() {
let canvas = document.querySelector('canvas');
this.draw = new Draw(canvas, -this.degree);
console.log('初始化画布11111canvas-------',canvas)
console.log('this.draw111111111',this.draw)
},
//下载图片
download() {
this.draw.downloadPNGImage(this.draw.getPNGImage());
},
//清空画布
clear() {
this.draw.clear();
},
//上传签名
async submitPNG() {
console.log('点击进行提交1111:',this.draw.isDraw);
if (!this.draw.isDraw) {
Toast('请先进行签署');
return
}
this.signImage = this.draw.getPNGImage();
// 返回blob文件对象
const dataURLtoFile = (dataurl, filename) => {
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 });
};
//上传base64
const uploadBase64Img = async () => {
const filename = `${new Date().getTime()}img.png`;
const imgfile = dataURLtoFile(this.signImage, filename);
const {res:{requestUrls:[imgUrl]}} = await client.multipartUpload(`${new Date().getTime()}img.png`, imgfile);
return imgUrl
}
const imgUrl = await uploadBase64Img();
this.viewScanCodeUrl = imgUrl; //签名地址
console.log('上传的签名地址:',this.viewScanCodeUrl);
this.uploadPhoto(); //上传图片
},
//提交签名
uploadPhoto() {
// let uploadData = {
// orderViewId: this.orderListId,
// autographPhoto: this.viewScanCodeUrl
// }
console.log('上传图片111111--uploadPhoto')
let _that = this
console.log('uploadData====点击提交上传的图片')
// 调取提交
var apiUrl = `${base.Microservice}/cloud/consultation/api/v1/consultation/report/upLoadAutographPhoto`;
axios.post(apiUrl, qs.stringify({
orderViewId: this.orderListId,
autographPhoto: this.viewScanCodeUrl
}), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
}
}).then(res => {
console.log('headers====请求成功--上传签名成功',res)
_that.$router.push({
path: "/signSuccess",
query: {
id: _that.orderListId, //订单id
}
})
}).catch(err => {
console.log('请求失败=========',err)
return Toast('提交失败,请重新提交');
}
)
}
},
created() {
this.orderListId = this.$route.query.id
console.log('this.$route.query.id',this.$route.query.id)
//console.log('进入签名created=====横屏竖屏',window.orientation,this.orderListId)
//判断手机横竖屏状态:
//window.addEventListener("onorientationchange" in window ? "orientationchange" : "resize", function() {
// if (window.orientation === 180 || window.orientation === 0) {
//console.log('竖屏状态!')
//}
//if (window.orientation === 90 || window.orientation === -90 ){
//console.log('横屏状态!')
//}
//}, false);
}
}
</script>
<style lang="less" scoped>
.sign-wrapper {
height: 100%;
width: 100%;
background: #fff;
//手写签名 canvas
.canvas-box {
height: 100%;
display: flex;
}
}
canvas {
flex: 1;
// cursor: crosshair;
}
// 操作按钮
.btn {
width: 10.8rem;
height: 4.5rem;
position: absolute;
right: 3.6rem;
bottom: 2.3rem;
display: inline-block;
display: flex;
align-items: center;
justify-content: center;
border-radius: .25rem;
color: #fff;
font: 1.6rem '苹方-简';
z-index: 999;
// width: 8.8rem;
// height: 3.5rem;
// position: absolute;
// right: 1.875rem;
// bottom: 1.25rem;
border: none
}
.clear-btn {
background: #999999;
right: 16rem;
}
.commit-btn {
background: #3978E1
}
</style>
引入的draw.js,这个我后来也有搜到别人有用,应该是某位大佬写的,非常感谢。但是我也不确定来源,我搜索了下louizhai都不太确定作者,总之感谢
/**
* Created by louizhai on 17/6/30.
* description: Use canvas to draw.
*/
function Draw(canvas, degree, config = {}) {
this.isDraw = false;
if (!(this instanceof Draw)) {
return new Draw(canvas, config);
}
if (!canvas) {
return;
}
let { width, height } = window.getComputedStyle(canvas, null);
width = width.replace('px', '');
height = height.replace('px', '');
this.canvas = canvas;
this.context = canvas.getContext('2d');
this.width = width;
this.height = height;
const context = this.context;
// 根据设备像素比优化canvas绘图
const devicePixelRatio = window.devicePixelRatio;
if (devicePixelRatio) {
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
canvas.height = height * devicePixelRatio;
canvas.width = width * devicePixelRatio;
context.scale(devicePixelRatio, devicePixelRatio);
} else {
canvas.width = width;
canvas.height = height;
}
context.lineWidth = 6;
context.strokeStyle = 'black';
context.lineCap = 'round';
context.lineJoin = 'round';
Object.assign(context, config);
const { left, top } = canvas.getBoundingClientRect();
const point = {};
const isMobile = /phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone/i.test(navigator.userAgent);
// 移动端性能太弱, 去掉模糊以提高手写渲染速度
if (!isMobile) {
context.shadowBlur = 1;
context.shadowColor = 'black';
}
let pressed = false;
const paint = (signal) => {
switch (signal) {
case 1:
context.beginPath();
context.moveTo(point.x, point.y);
case 2:
context.lineTo(point.x, point.y);
context.stroke();
break;
default:
}
};
const create = signal => (e) => {
this.isDraw = true;
e.preventDefault();
if (signal === 1) {
pressed = true;
}
if (signal === 1 || pressed) {
e = isMobile ? e.touches[0] : e;
point.x = e.clientX - left;
point.y = e.clientY - top;
paint(signal);
}
};
const start = create(1);
const move = create(2);
const requestAnimationFrame = window.requestAnimationFrame;
const optimizedMove = requestAnimationFrame ? (e) => {
requestAnimationFrame(() => {
move(e);
});
} : move;
if (isMobile) {
canvas.addEventListener('touchstart', start);
canvas.addEventListener('touchmove', optimizedMove);
} else {
canvas.addEventListener('mousedown', start);
canvas.addEventListener('mousemove', optimizedMove);
['mouseup', 'mouseleave'].forEach((event) => {
canvas.addEventListener(event, () => {
pressed = false;
});
});
}
// 重置画布坐标系
if (typeof degree === 'number') {
this.degree = degree;
context.rotate((degree * Math.PI) / 180);
switch (degree) {
case -90:
context.translate(-height, 0);
break;
case 90:
context.translate(0, -width);
break;
case -180:
case 180:
context.translate(-width, -height);
break;
default:
}
}
}
Draw.prototype = {
scale(width, height, canvas = this.canvas) {
const w = canvas.width;
const h = canvas.height;
width = width || w;
height = height || h;
if (width !== w || height !== h) {
const tmpCanvas = document.createElement('canvas');
const tmpContext = tmpCanvas.getContext('2d');
tmpCanvas.width = width;
tmpCanvas.height = height;
tmpContext.drawImage(canvas, 0, 0, w, h, 0, 0, width, height);
canvas = tmpCanvas;
}
return canvas;
},
rotate(degree, image = this.canvas) {
degree = ~~degree;
if (degree !== 0) {
const maxDegree = 180;
const minDegree = -90;
if (degree > maxDegree) {
degree = maxDegree;
} else if (degree < minDegree) {
degree = minDegree;
}
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const height = image.height;
const width = image.width;
const degreePI = (degree * Math.PI) / 180;
switch (degree) {
// 逆时针旋转90°
case -90:
canvas.width = height;
canvas.height = width;
context.rotate(degreePI);
context.drawImage(image, -width, 0);
break;
// 顺时针旋转90°
case 90:
canvas.width = height;
canvas.height = width;
context.rotate(degreePI);
context.drawImage(image, 0, -height);
break;
// 顺时针旋转180°
case 180:
canvas.width = width;
canvas.height = height;
context.rotate(degreePI);
context.drawImage(image, -width, -height);
break;
default:
}
image = canvas;
}
return image;
},
getPNGImage(canvas = this.canvas) {
return canvas.toDataURL('image/png');
},
getJPGImage(canvas = this.canvas) {
return canvas.toDataURL('image/jpeg', 0.5);
},
downloadPNGImage(image) {
const url = image.replace('image/png', 'image/octet-stream;Content-Disposition:attachment;filename=test.png');
window.location.href = url;
},
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 });
},
clear() {
this.isDraw = false;
let width;
let height;
switch (this.degree) {
case -90:
case 90:
width = this.height;
height = this.width;
break;
default:
width = this.width;
height = this.height;
}
this.context.clearRect(0, 0, width, height);
},
upload(blob, url, success, failure) {
const formData = new FormData();
const xhr = new XMLHttpRequest();
xhr.withCredentials = true;
formData.append('image', blob, 'sign');
xhr.open('POST', url, true);
xhr.onload = () => {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
success(xhr.responseText);
} else {
failure();
}
};
xhr.onerror = (e) => {
if (typeof failure === 'function') {
failure(e);
} else {
console.log(`upload img error: ${e}`);
}
};
xhr.send(formData);
},
};
export default Draw;