功能需求
-
法律文书签字:在远程取证产品中,要求实现用户对法律文书签写姓名、日期、备注(可多页签写),同时要求能够支持自定义签名类型,例如有些文书仅需签写姓名和日期等多种情况。
-
证据签字:证据图片要求签写姓名和日期。
-
笔录签字:笔录要求签写法律条款、姓名、日期。
设计分析
1、签字类型
归纳签字类型分别为姓名、日期、备注、法律条款,其中姓名和日期是相对简单的,只需要单页签字,法律条款(每页签一个字)和备注都需要支持多页签字。定义签字类型如下:
const signType=[{
name:'姓名',placeholder:'请签写姓名'
},{
name:'日期',placeholder:'请在此签日期(今日:' +this.data.year+'年'+this.data.month+'月'+this.data.day+'日)'
},{
name:'法律条款',clauseContent:data.clauseContent.split(""),multi:true //条款文字字符串转数组
},{
name:'备注',placeholder:'请签写备注,写不下请点击继续签写',multi:true //multi是否多页签写
}]
2、功能按钮
清除:清除canvas绘制图形,若canvas已转为图片则清空数组图片。
上一步:上一步包含了上一个(法律条款)和上一页(多页备注)功能。
下一步:按类型划分的大步骤,最后一步不展示。
确认提交:签字完成后,提交签字信息至服务端。
继续签写:类型为备注时特有,实际功能为下一页
下一个:类型为法律条款时特有,新的一个字
3、数据定义
data: {
currentSignIndex:0, //当前签字步骤
subCurrentSignIndex:0, //当前签字子步骤(备注、法律条款)
imgUrlPath:[], //二维数组 canvas转图片展示
imgUrlBase64:[], //二维数组 图片转base64,供后端使用
signCxt:[], //二维数组 绘图上下文 [CanvasContext](https://developers.weixin.qq.com/miniprogram/dev/api/canvas/CanvasContext.html) 对象
isShowPlaceholder:true, //未签字前的提示占位符
},
签字功能关键代码实现
canvas页面wxml
<canvas
canvas-id="{{currentSignIndex+'signCanvas'+subCurrentSignIndex}}"
id="{{currentSignIndex+'signCanvas'+subCurrentSignIndex}}"
class="sign_area"
disable-scroll="{{true}}"
bindtouchstart="signTouchStart"
bindtouchmove="signTouchMove"
wx:if="{{lawSignType[currentSignIndex].multi&&!imgUrlPath[currentSignIndex][subCurrentSignIndex]}}"
>
<text wx:if="{{isShowLawPlaceholder}}">{{lawSignType[currentSignIndex].placeholder}}</text>
</canvas>
<canvas
canvas-id="{{currentSignIndex+'signCanvas'}}"
id="{{currentSignIndex+'signCanvas'}}"
class="sign_area"
disable-scroll="{{true}}"
bindtouchstart="signTouchStart"
bindtouchmove="signTouchMove"
wx:elif="{{!imgUrlPath[currentSignIndex]}}"
>
<text wx:if="{{isShowLawPlaceholder}}">{{lawSignType[currentSignIndex].placeholder}}</text>
</canvas>
<image
style="width:100%;height:100%;"
mode="aspectFill"
src="{{lawSignType[currentSignIndex].multi?imgUrlPath[currentSignIndex][subCurrentSignIndex]:imgUrlPath[currentSignIndex]}}"
wx:else
/>
创建canvas绘图上下文
创建 canvas 的绘图上下文 CanvasContext 对象,构造二维数组。
function initCanvas(that, idName) {
const context = wx.createCanvasContext(idName,that)
let num,subNum
num=Number(idName.slice(0,1)) //签字步骤
subNum=idName.substring(11) //签字子步骤
const signCxt=that.data.signCxt
const imgUrlPath=that.data.imgUrlPath
const imgUrlBase64=that.data.imgUrlBase64
if(subNum){
subNum=Number(subNum)
if(subNum===0){
signCxt[num]=[context]
imgUrlPath[num]=[null]
imgUrlBase64[num]=[null]
}else{
signCxt[num].push(context)
imgUrlPath[num].push(null)
imgUrlBase64[num].push(null)
}
}else{
signCxt[num]=context
imgUrlPath[num]=null
imgUrlBase64[num]=null
}
that.setData({signCxt})
that.setData({imgUrlPath,imgUrlBase64})
context.setLineCap('round') //设置签字线条端点样式
context.setLineWidth(4) //设置签字线条宽度
}
开始绘制
.moveTo()方法把路径移动到画布中的指定点,不创建线条。用 stroke 方法来画线条
signTouchStart(e) {
this.setData({isShowLawPlaceholder:false})
const {lawSignType,currentSignIndex,subCurrentSignIndex}=this.data
if(lawSignType[currentSignIndex].multi){
this.data.signCxt[currentSignIndex][subCurrentSignIndex].moveTo(e.changedTouches[0].x, e.changedTouches[0].y)
}else{
this.data.signCxt[currentSignIndex].moveTo(e.changedTouches[0].x, e.changedTouches[0].y)
}
},
过程绘制
.lineTo()增加一个新点,然后创建一条从上次指定点到目标点的线。用 stroke 方法来画线条
.draw(boolean reserve, function callback),reserve为true时保留上一次结果,false则不保留(可用于清除功能)。
signTouchMove(e) {
const {lawSignType,signCxt,currentSignIndex,subCurrentSignIndex}=this.data
if(lawSignType[currentSignIndex].multi){
signCxt[currentSignIndex][subCurrentSignIndex]=func.writeText(e, this.data.signCxt[currentSignIndex][subCurrentSignIndex])
}else{
signCxt[currentSignIndex]=func.writeText(e, this.data.signCxt[currentSignIndex])
}
this.setData({signCxt:signCxt})
},
function writeText(value, obj) {
obj.lineTo(value.changedTouches[0].x, value.changedTouches[0].y);
obj.stroke();
obj.draw(true);
obj.moveTo(value.changedTouches[0].x, value.changedTouches[0].y);
return obj;
}
canvas转图片
canvas转本地图片,本地图片读取转base64供后续上传至服务端
function picToBaseFormat(canvasId, callback,that) {
wx.canvasToTempFilePath({
x: 0,
y: 0,
fileType: 'png',
canvasId,
success({ tempFilePath }) {
wx.getFileSystemManager().readFile({
filePath: tempFilePath,
encoding: 'base64',
success({ data, errMsg }) {
if (errMsg.split(':')[1] === 'ok') {
callback(data, tempFilePath)
}
},
fail(err) {
console.log('err', err);
}
})
},
fail(err) {
console.log('err', err);
}
},that)
}
清除canvas
clearSign(){
const {signCxt,imgUrlPath,imgUrlBase64,currentSignIndex,subCurrentSignIndex}=this.data
if(this.data.lawSignType[currentSignIndex].multi){
signCxt[currentSignIndex][subCurrentSignIndex].draw();
signCxt[currentSignIndex][subCurrentSignIndex].setLineCap('round')
signCxt[currentSignIndex][subCurrentSignIndex].setLineWidth(4);
imgUrlPath[currentSignIndex][subCurrentSignIndex]=null
imgUrlBase64[currentSignIndex][subCurrentSignIndex]=null
}else{
signCxt[currentSignIndex].draw();
signCxt[currentSignIndex].setLineCap('round')
signCxt[currentSignIndex].setLineWidth(4);
imgUrlPath[currentSignIndex]=null
imgUrlBase64[currentSignIndex]=null
}
this.setData({
isShowLawPlaceholder: true,
signCxt,
imgUrlPath,
imgUrlBase64
})
},
步骤逻辑实现
由于每个步骤都需要考虑当前页所在的类型是否支持多页签写,所以判断逻辑较多。实现上由于时间较急,优先确保了逻辑清晰,写法上后续会再优化。
下一步
NextStep(){
const {lawSignType,currentSignIndex,subCurrentSignIndex,imgUrlPath,imgUrlBase64}=this.data
if(lawSignType[currentSignIndex].multi){
if(!imgUrlPath[currentSignIndex][subCurrentSignIndex]){
func.picToBaseFormat(currentSignIndex+'signCanvas'+subCurrentSignIndex, (data, tempFilePath) => {
imgUrlPath[currentSignIndex][subCurrentSignIndex]=tempFilePath
imgUrlBase64[currentSignIndex][subCurrentSignIndex]=data
this.setData({imgUrlPath,imgUrlBase64})
this.setData({currentSignIndex:currentSignIndex+1,isShowLawPlaceholder:true})
this.creatNextCanvas()
},this)
}else{
this.setData({currentSignIndex:currentSignIndex+1,isShowLawPlaceholder:true})
this.creatNextCanvas()
}
}else{
if(!imgUrlPath[currentSignIndex]){
func.picToBaseFormat(currentSignIndex+'signCanvas', (data, tempFilePath) => {
const {imgUrlPath,imgUrlBase64}=this.data
imgUrlPath[currentSignIndex]=tempFilePath
imgUrlBase64[currentSignIndex]=data
this.setData({imgUrlPath,imgUrlBase64})
this.setData({currentSignIndex:currentSignIndex+1,isShowLawPlaceholder:true})
this.creatNextCanvas()
},this)
}else{
this.setData({currentSignIndex:currentSignIndex+1,isShowLawPlaceholder:true})
this.creatNextCanvas()
}
}
},
确认提交
makeSureSubmit(){
const {lawSignType,currentSignIndex,subCurrentSignIndex,imgUrlPath,imgUrlBase64,signCxt}=this.data
if(lawSignType[currentSignIndex].multi){
if(signCxt[currentSignIndex][subCurrentSignIndex].path.length === 0 && !imgUrlPath[currentSignIndex][subCurrentSignIndex]){
wx.showToast({
title: '请先签写备注信息',
icon: 'none',
duration: 2000
})
return
}
if(!imgUrlPath[currentSignIndex][subCurrentSignIndex]){
func.picToBaseFormat(currentSignIndex+'signCanvas'+subCurrentSignIndex, (data, tempFilePath) => {
imgUrlPath[currentSignIndex][subCurrentSignIndex]=tempFilePath
imgUrlBase64[currentSignIndex][subCurrentSignIndex]=data
this.setData({imgUrlPath,imgUrlBase64})
this.triggerEvent('signsubmit', imgUrlBase64)
},this)
}else{
this.triggerEvent('signsubmit', imgUrlBase64)
}
}else{
if(!imgUrlPath[currentSignIndex]){
func.picToBaseFormat(currentSignIndex+'signCanvas', (data, tempFilePath) => {
const {imgUrlPath,imgUrlBase64}=this.data
imgUrlPath[currentSignIndex]=tempFilePath
imgUrlBase64[currentSignIndex]=data
this.setData({imgUrlPath,imgUrlBase64})
this.triggerEvent('signsubmit', imgUrlBase64)
},this)
}else{
this.triggerEvent('signsubmit', imgUrlBase64)
}
}
}
},
上一步
lawFrontStep(){
const {currentSignIndex,subCurrentSignIndex}=this.data
if(this.data.subCurrentSignIndex>0){
this.data.signCxt[currentSignIndex][subCurrentSignIndex].path=[]
this.setData({subCurrentSignIndex:this.data.subCurrentSignIndex-1,signCxt:this.data.signCxt})
}else{
this.data.signCxt[currentSignIndex].path=[]
this.setData({currentSignIndex:this.data.currentSignIndex-1,signCxt:this.data.signCxt})
}
},
继续签写/下一个
continueSign(){
const {imgUrlPath,currentSignIndex,subCurrentSignIndex,imgUrlBase64}=this.data
if(!imgUrlPath[currentSignIndex][subCurrentSignIndex]){
func.picToBaseFormat(currentSignIndex+'signCanvas'+subCurrentSignIndex, (data, tempFilePath) => {
imgUrlPath[currentSignIndex][subCurrentSignIndex]=tempFilePath
imgUrlBase64[currentSignIndex][subCurrentSignIndex]=data
this.setData({imgUrlPath,imgUrlBase64})
this.setData({subCurrentSignIndex:subCurrentSignIndex+1,isShowLawPlaceholder:true})
this.createNextPageCanvas()
},this)
}else{
this.setData({subCurrentSignIndex:subCurrentSignIndex+1,isShowLawPlaceholder:true})
this.createNextPageCanvas()
}
},
组件的使用
签字组件在使用上只需要参考设计分析中的签字类型,按要求确认应用的需求类型及结构。输出为一组签字图片,顺序同输入类型的顺序,如有类型为多页签写,则输出一组二维数组图片。