vue中实现签名
封装的组件
<template>
<div class="canvas">
<!-- <div class="rotate canvas_header">手写签名</div> -->
<div class="canvas_canvas">
<canvas id="myCanvas"></canvas>
</div>
<!-- <div @click="clearArea()" class="rotate canvas_footer_tip">*请手写签字,确认后提交。一旦提交,将无法再次对面试情况进行评价!</div> -->
<div class="rotate canvas_footer">
<div @click="back()" class=" canvas_footer_back">返回打分页面</div>
<div @click="clearArea()" class=" canvas_footer_re">重写</div>
<div @click="saveImageInfo()" class="canvas_footer_su">提交</div>
</div>
<!-- 颜色选择器 -->
<div class="canvas_color" v-show="showColor == 2">
<div>
<input v-model="strokeStyle" type="color" />
</div>
</div>
</div>
</template>
<script>
export default {
props: {
isWeichat: {
type: Boolean,
default:()=>true
},
isWeixinBrowser: {
type: String,
default:()=>'true'
}
},
data() {
return {
touchPressed: false,
ctx: null,
strokeStyle: "#000000", //书写颜色
lineWidth: 4, //线条宽度
lastX: null,
lastY: null,
canvas: null,
showColor: 1, //显示颜色
sColor: 0, //显示颜色
};
},
mounted() {
this.$nextTick(() => {
let canvas = document.getElementById("myCanvas");
let canvasBox = document.getElementsByClassName("canvas_canvas")[0];
this.canvas = canvas;
this.ctx = canvas.getContext("2d");
let winW = window.innerWidth - 85;
let winH = window.innerHeight - 40;
if(this.isWeixinBrowser === 'false') {
winH = window.innerHeight - document.getElementsByClassName('nav-bar')[0].clientHeight -40
}
if(window.innerHeight/window.innerWidth < 1.5) {
// pad端
winW = window.innerWidth - 135;
canvasBox.style.marginLeft = '3.5rem'
}
this.canvas.width = winW;
this.canvas.height = winH;
canvasBox.style.width = winW + 'px'
canvasBox.style.height = winH + 'px'
this.Init();
});
},
methods: {
Init() {
// 移动前
this.canvas.addEventListener(
"touchstart",
(event) => {
if (event.targetTouches.length == 1) {
event.preventDefault(); // 阻止浏览器默认事件,重要
let touch = event.targetTouches[0];
this.touchPressed = true;
this.draw(
touch.pageX - this.canvas.offsetLeft,
touch.pageY - this.canvas.offsetTop,
false
);
}
},
false
);
// 移动中
this.canvas.addEventListener(
"touchmove",
(event) => {
if (event.targetTouches.length == 1) {
event.preventDefault(); // 阻止浏览器默认事件,重要
let touch = event.targetTouches[0];
if (this.touchPressed) {
this.draw(
touch.pageX - this.canvas.offsetLeft,
touch.pageY - this.canvas.offsetTop,
true
);
}
}
},
false
);
// 移动结束
this.canvas.addEventListener(
"touchend",
(event) => {
if (event.targetTouches.length == 1) {
event.preventDefault(); // 阻止浏览器默认事件,防止手写的时候拖动屏幕,重要
this.touchPressed = false;
}
},
false
);
},
draw(x, y, isDown) {
let ctx = this.ctx;
if (isDown) {
ctx.beginPath();
ctx.strokeStyle = this.strokeStyle;
ctx.lineWidth = this.lineWidth;
ctx.lineJoin = "round";
ctx.moveTo(this.lastX, this.lastY);
ctx.lineTo(x, y);
ctx.closePath();
ctx.stroke();
}
this.lastX = x;
this.lastY = y;
},
// 重写
clearArea() {
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
},
// 保存本地
saveImage() {
// let a = document.createElement("a");
this.rotateBase64Img(this.canvas.toDataURL(), -90, this.download)
// a.href = this.canvas.toDataURL();
// a.download = "sign";
// a.click(); //保存
},
download(data) {
let a = document.createElement("a");
a.href = data;
a.download = "sign";
a.click(); //保存
},
//验证canvas画布是否为空函数
isCanvasBlank(canvas) {
let blank = document.createElement('canvas');//系统获取一个空canvas对象
blank.width = canvas.width;
blank.height = canvas.height;
return canvas.toDataURL() == blank.toDataURL();//比较值相等则为空
},
// 保存服务器
saveImageInfo() {
if(this.isCanvasBlank(this.canvas)) {
this.$toast('请签名后提交')
return
}
this.rotateBase64Img(this.canvas.toDataURL(), -90, this.dataURLtoFile)
},
dataURLtoFile(dataurl) {//将base64转换为文件,dataurl为base64字符串,filename为文件名(必须带后缀名,如.jpg,.png)
let arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while(n--){
u8arr[n] = bstr.charCodeAt(n);
}
let fileList = new File([u8arr], 'signature.png', {type:mime});
// this.update(fileList)//图片上传
this.$emit('upload', fileList)
return fileList;
},
//签完名的图片旋转处理
// src为你、base64编码;edg为角度,0-360;callback回调
rotateBase64Img(src, edg, callback) {
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
let imgW;//图片宽度
let imgH;//图片高度
let size;//canvas初始大小
if (edg % 90 != 0) {
console.error("旋转角度必须是90的倍数!");
throw '旋转角度必须是90的倍数!';
}
(edg < 0) && (edg = (edg % 360) + 360)
const quadrant = (edg / 90) % 4; //旋转象限
const cutCoor = {sx: 0, sy: 0, ex: 0, ey: 0}; //裁剪坐标
let image = new Image();
image.crossOrigin = "anonymous"
image.src = src;
image.onload = function () {
imgW = image.width;
imgH = image.height;
size = imgW > imgH ? imgW : imgH;
canvas.width = size * 2;
canvas.height = size * 2;
switch (quadrant) {
case 0:
cutCoor.sx = size;
cutCoor.sy = size;
cutCoor.ex = size + imgW;
cutCoor.ey = size + imgH;
break;
case 1:
cutCoor.sx = size - imgH;
cutCoor.sy = size;
cutCoor.ex = size;
cutCoor.ey = size + imgW;
break;
case 2:
cutCoor.sx = size - imgW;
cutCoor.sy = size - imgH;
cutCoor.ex = size;
cutCoor.ey = size;
break;
case 3:
cutCoor.sx = size;
cutCoor.sy = size - imgW;
cutCoor.ex = size + imgH;
cutCoor.ey = size + imgW;
break;
}
ctx.translate(size, size);
ctx.rotate(edg * Math.PI / 180);
ctx.drawImage(image, 0, 0);
let imgData = ctx.getImageData(cutCoor.sx, cutCoor.sy, cutCoor.ex, cutCoor.ey);
if (quadrant % 2 == 0) {
canvas.width = imgW;
canvas.height = imgH;
} else {
canvas.width = imgH;
canvas.height = imgW;
}
ctx.putImageData(imgData, 0, 0);
callback(canvas.toDataURL())
};
},
back() {
this.$router.go(-1)
}
//end
},
watch: {
strokeStyle(newColor,oldColor) {
console.log(newColor)
},
},
};
</script>
<style>
.canvas {
width: 100%;
/* height: 100%; */
}
.canvas_header {
width: 100%;
text-align: center;
line-height: 50px;
font-weight: 800;
font-size: 20px;
}
.canvas_canvas {
width: 100%;
height: 100%;
margin: 0 auto;
margin-top: .625rem;
margin-left: 4.0625rem;
border: .125rem solid #aaa;
}
.canvas_info {
margin: 10px 8%;
font-size: 12px;
}
.canvas_footer {
width: 100%;
/* max-width: 140%; */
margin: 0;
display: flex;
justify-content: space-between;
line-height: 40px;
text-align: center;
color: white;
/* transform: translate(10px, 10px); */
font-weight: 800;
position: fixed;
left: -42%;
top: 50%;
}
.canvas_footer_back {
width: 8.25rem;
margin-right: .625rem;
font-weight: 400;
background: #ccc;
/* background: #c9b2b2; */
border-radius: 10px;
}
.canvas_footer_re {
width: 5.625rem;
margin-right: .625rem;
background: #ccc;
/* background: #c9b2b2; */
border-radius: 10px;
}
.canvas_footer_tip {
/* width: 15.625rem; */
/* background: #1989f1; */
position: fixed;
left: -45%;
top: 37%;
color: #ccc;
font-size: .625rem;
line-height: 1.3;
border-radius: 10px;
}
.canvas_footer_su {
width: 5.625rem;
background: #5471d5;
border-radius: 10px;
}
.rotate {
transform: rotate(90deg);
transform-origin: 50% 50%;
}
</style>
其他组件调用
<template>
<div class="signature-box">
<nav-bar v-if="isWeixinBrowser=='false'"></nav-bar>
<Signature :style="getContentStyle" :isWeixinBrowser="isWeixinBrowser" @upload="upload"/>
</div>
</template>
<script>
import NavBar from "@/components/NavBar.vue";
import Signature from '@/components/Signature.vue'
import { Dialog } from 'vant'
export default {
components: { NavBar, Signature },
data() {
return {
isWeixinBrowser: true,
votingId: '',
voterId: '',
};
},
created() {
this.voterId = this.$route.query.voterId
this.votingId = this.$route.query.votingId
this.isWeixinBrowser = sessionStorage.getItem("isWeixinBrowser")
if (this.isWeixinBrowser) {
var $body = $('body');
document.title = sessionStorage.getItem('mobileTitle')
var $iframe = $('<iframe src="/favicon.ico"></iframe>');
$iframe.on('load',function() {
setTimeout(function() {
$iframe.off('load').remove();
}, 0);
}).appendTo($body);
} else {
this.isWeichat()
}
},
mounted() {},
computed: {
getContentStyle() {
this.$nextTick(() => {
let height = '100%'
if(this.isWeixinBrowser === 'false') {
height = `height: calc(100% - ${document.getElementsByClassName('nav-bar')[0].clientHeight}px)`
}
return `width: 100%;height: ${height}`
})
return ''
}
},
methods: {
/**
* 判断是否微信登录
*/
isWeichat() {
var ua = window.navigator.userAgent.toLowerCase();
if(ua.match(/MicroMessenger/i) == 'micromessenger'){
this.isWeixinBrowser = 'true';
}else{
this.isWeixinBrowser = 'false';
}
},
/**
* 上传
*/
upload(file){
Dialog.confirm({
title: '签字确认',
message: '签字一旦提交,将无法再次对面试情况进行评价!确认是否继续提交签字',
})
.then(() => {
let param = new FormData(); //创建form对象
param.append('file',file,file.name);//通过append向form对象添加数据
param.append('voterId',this.voterId);//通过append向form对象添加数据
this.$api
.uploadImg(param)
.then((res) => {
this.$toast('签名提交成功')
this.$router.replace({ name: 'success',query:{
voterId: this.voterId,
votingId: this.votingId,
}})
})
.catch((err) => {
this.$toast('签名文件上传失败,请重试')
})
})
.catch(() => {})
},
}
};
</script>
<style scoped>
.signature-box {
width: 100%;
height: 100%;
}
</style>