效果图:
业务需求:点击签字空白处,调起签字版(横屏展示),手写完之后点击确定回显签字内容
签名是使用canvas实现的
签名确认之后生成的一个临时图片
后台接口逻辑是生成的图片先上传到文件服务器,然后获取返回的文件数据,跟其他数据一块提交到后台。(可根据自己项目情况修改)
签名单独封装成了组件
具体代码如下:
signature.wxml
<view class="modal-content-area">
<view class="modal-content">
<view class="toast" wx:if="{{!hasDraw}}">
请签写您的名字
</view>
<canvas canvas-id="signature" class="modal-canvas" disable-scroll="{{true}}" id="writeCanvas" bindtouchstart="scaleStart" bindtouchmove="scaleMove" bindtouchend="scaleEnd"></canvas>
<view class="modal-bottom">
<view class="clear_signature" bindtap=""></view>
<view class="flex_btn">
<view class="modal-btn modal-clear" bindtap="clearCanvas">清空</view>
<view class="modal-btn modal-confirm" bindtap="save">确定</view>
</view>
</view>
</view>
</view>
signature.wxss
.modal-content-area {
z-index: 5;
width: 100%;
height: 100%;
background: #F5F7FB;
position: absolute;
}
.modal-content {
width: 100%;
height: 100%;
background: #ffffff;
}
.modal-canvas {
width: 100vw;
height: 83vh;
background: #F5F7FB !important;
border: 1rpx solid rgba(0, 0, 0, 0.08);
border-radius: 4rpx;
z-index: 1;
}
.modal-bottom {
position: absolute;
bottom: 15rpx;
display: flex;
align-items: center;
justify-content: space-between;
z-index: 100;
width: 100vw;
box-sizing: border-box;
padding: 0 32rpx;
}
.modal-btn {
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
width: 85rpx;
height: 30rpx;
background: rgba(103, 149, 255, 0.2);
}
.modal-clear {
font-size: 13rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #3670F5;
margin-right: 12rpx;
}
.modal-confirm {
background: #3670F5;
font-size: 13rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #FFFFFF;
}
.modal-btn:nth-child(1) {
border-right: 1rpx solid #ffffff;
}
.clear_signature {
/* position: absolute; */
top: 40rpx;
right: 15rpx;
display: flex;
align-items: center;
z-index: 1;
font-size: 13rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #3670F5;
/* background-color: #F5F7FB; */
z-index: 100;
}
.clear_signature>text {
height: 17rpx;
}
.clear_signature>image {
width: 15rpx;
height: 15rpx;
margin-right: 2rpx;
}
.toast {
position: absolute;
top: 40%;
left: 42%;
transform: rotate(-50%, -50%);
z-index: 100;
font-size: 28rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: rgba(58, 65, 85, 0.4);
}
.flex_btn {
display: flex;
align-items: center;
}
signature.js
Component({
/**
* 组件的初始数据
*/
data: {
tempFilePath: '',
hideModal: false,
hasDraw: false,
canvasName: '#writeCanvas',
ctx: '',
canvasWidth: 0,
canvasHeight: 0,
startPoint: {
x: 0,
y: 0,
},
selectColor: 'black',
lineColor: '#1A1A1A', // 颜色
lineSize: 1, // 笔记倍数
radius: 5, //画圆的半径
},
// 初始化画布
ready() {
this.setData({
hideModal: false
})
let that = this
let query = wx.createSelectorQuery().in(this);
//获取自定义组件的SelectQuery对象
this.canvasCtx = wx.createCanvasContext('signature', that)
// 设置线的样式
this.canvasCtx.setLineCap("round");
this.canvasCtx.setLineJoin("round");
// 初始化颜色
this.canvasCtx.setStrokeStyle(that.data.selectColor);
// 初始化粗细
query.select('.modal-canvas').boundingClientRect(rect => {
this.setData({
canvasWidth: rect.width,
canvasHeight: rect.height,
});
}).exec();
},
// 方法列表
methods: {
scaleStart(event) {
if (event.type != 'touchstart') return false;
let currentPoint = {
x: event.touches[0].x,
y: event.touches[0].y
}
// this.data.ctx.moveTo(currentPoint.x, currentPoint.y)
this.drawCircle(currentPoint);
this.setData({
startPoint: currentPoint,
hasDraw: true, //签字了
});
},
mouseDown() {},
scaleEnd(e) {
this.setData({
isStart: true
})
},
scaleMove(event) {
if (event.type != "touchmove") return false;
let {
startPoint
} = this.data
let currentPoint = {
x: event.touches[0].x,
y: event.touches[0].y
}
this.drawLine(startPoint, currentPoint)
this.setData({
startPoint: currentPoint
})
},
drawCircle(point) { //这里负责点
let ctx = this.canvasCtx;
ctx.beginPath();
ctx.setFillStyle(this.data.lineColor)
//笔迹粗细由圆的大小决定
ctx.arc(point.x, point.y, this.data.radius, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath();
ctx.draw(true)
},
drawLine(sourcePoint, targetPoint) {
let ctx = this.canvasCtx;
this.drawCircle(targetPoint);
ctx.beginPath();
ctx.setStrokeStyle(this.data.lineColor)
ctx.setLineWidth(this.data.radius * 2)
ctx.moveTo(sourcePoint.x, sourcePoint.y);
ctx.lineTo(targetPoint.x, targetPoint.y);
ctx.stroke();
ctx.closePath();
},
clearCanvas() {
//清空画布
let ctx = this.canvasCtx;
ctx.clearRect(0, 0, this.data.canvasWidth, this.data.canvasHeight);
ctx.fillStyle = 'rgba(0, 0, 0, .1)';
ctx.draw()
this.setData({
hasDraw: false //未签字
})
},
save() {
let {
hasDraw,
} = this.data
if (!hasDraw) {
wx.showToast({
title: '您还未签名!',
icon: 'none',
mask: true
})
return
} else {
this.saveToImage();
}
},
saveToImage() {
let that = this;
let {
canvasHeight,
canvasWidth
} = that.data;
wx.canvasToTempFilePath({
x: 0,
y: 0,
width: canvasWidth,
height: canvasHeight,
canvasId: 'signature',
success(res) {
if (res.tempFilePath) {
that.triggerEvent('saveToImageEvent', res.tempFilePath)
}
},
fail(err) {
console.log(err);
}
}, that)
}
}
})
业务代码:
index.wxml
<view>
<signature bind:saveToImageEvent="saveToImageEvent"></signature>
</view>
index.js
import { config } from '@/config';
Page({
data: {
signFile: {}
},
saveToImageEvent(e){
let that = this;
// 上传到文件服务器返回文件信息
wx.uploadFile({
url: `${config.sfss_url}/space`, //仅为示例,非真实的接口地址
filePath: e.detail,
name: 'file',
success (res){
that.setData({
signFile: JSON.parse(res.data)
})
let pages = getCurrentPages();//获取page
let prevPage = pages[pages.length-2];//上一个页面(父页面)
prevPage.setData({
tempFilePath: e.detail,
['storageInfo.signature']: that.data.signFile.id
})
//返回上一页面
wx.navigateBack({
delta: 1
})
}
})
},
onLoad(options) {
},
onReady() {
},
onShow() {
},
onHide() {
},
onUnload() {
},
onShareAppMessage() {
return {
title: '',
};
},
});
index.json
{
"navigationBarTitleText": "",
"pageOrientation": "landscape",
"usingComponents": {
"signature": "/components/signature/signature"
}
}