signature/signature.wxml
<!-- index.wxml -->
<view>
<view class="content" style="padding-left: {{deviceInFo.safeArea.left || 10}}px">
<view class="canvas_box">
<!-- 定位到canvas画布的下方作为背景 -->
<view class="canvas_tips">
签 名 区
</view>
<!-- canvas画布 -->
<canvas class="canvas_content" type="2d" style='width:{{canvasWidth}}px; height:{{canvasHeight}}px' id="myCanvas" bindtouchstart="bindtouchstart" bindtouchmove="bindtouchmove" bindtouchend="bindtouchend" bindtouchcancel="bindtouchcancel"></canvas>
</view>
</view>
<!-- footer -->
<view class="footer">
<van-button plain class="item" block bind:click="overwrite">清空</van-button>
<van-button plain class="item" block bind:click="prev">撤销</van-button>
<van-button class="item" block bind:click="confirm" type="info">提交</van-button>
</view>
</view>
signature/signature.wxss
点击展开/折叠
.content {
box-sizing: border-box;
width: 100%;
height: 100%;
padding: 10px;
}
.canvas_box {
width: 100%;
height: 100%;
background-color: #E8E9EC;
position: relative;
}
/* 定位到canvas画布的下方作为背景 */
.canvas_tips {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
font-size: 80px;
color: #E2E2E2;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
}
/* 底部按钮 */
.footer {
box-sizing: border-box;
z-index: 2;
padding: 15px 0;
background-color: #ffffff;
position: fixed;
width: 100%;
box-shadow: 0 0 15rpx rgba(0, 0, 0, 0.1);
left: 0;
bottom: 0;
display: flex;
justify-content: center;
}
.item {
width: 100px;
margin: 0 20px;
}
.van-button {
height: 40px !important;
border-radius: 20px !important;
font-size: 16px !important;
}
.weapp-button-index--van-button--plain {
color: #333 !important;
border-color: #ccc !important;
}
signature/signature.js
const {
uploadFile
} = require("../../utils/file.js");
Page({
data: {
deviceInFo: {}, // 设备信息
canvasWidth: '', // 画布宽
canvasHeight: '', // 画布高
canvas: null, // Canvas 对象实例
ctx: null, // Canvas 对象上下文实例
historyImage: [], // 历史记录,每一笔动作完成后的图片数据,用于每一次回退上一步是当作图片绘制到画布上
initialCanvasImg: '', // 初始画布图,解决非ios设备重设置宽高不能清空画布的问题
inspectionOrderId: ''
},
onReady() {
// 获取可使用窗口的宽高,赋值给Canvas(宽高要减去上下左右padding的20,以及高度要减去footer区域)
wx.createSelectorQuery()
.select('.footer') // canvas获取节点
.fields({
node: true,
size: true
}) // 获取节点的相关信息,node:是否返回节点对应的 Node 实例,size:是否返回节点尺寸
.exec((res) => {
// 获取手机左侧安全区域(刘海)
const deviceInFo = wx.getSystemInfoSync()
const canvasWidth = deviceInFo.windowWidth - 20 - deviceInFo?.safeArea?.left || 0
const canvasHeight = deviceInFo.windowHeight - res[0].height - 20
this.setData({
deviceInFo,
canvasWidth,
canvasHeight
})
this.initCanvas('init')
})
},
onLoad(option) {
this.data.inspectionOrderId = option.inspectionOrderId
},
// 初始化Canvas画布
initCanvas(type) {
let ctx = null
let canvas = null
let {
historyImage,
canvasWidth,
canvasHeight,
deviceInFo,
initialCanvasImg
} = this.data
// 获取Canvas画布以及渲染上下文
wx.createSelectorQuery()
.select('#myCanvas') // canvas获取节点
.fields({
node: true,
size: true
}) // 获取节点的相关信息,node:是否返回节点对应的 Node 实例,size:是否返回节点尺寸
.exec((res) => { // 执行所有的请求。请求结果按请求次序构成数组
// Canvas 对象实例
canvas = res[0].node
// Canvas 对象上下文实例(动画动作绘图等都是在他的身上完成)
ctx = canvas.getContext('2d')
// Canvas 画布的实际绘制宽高
const width = res[0].width
const height = res[0].height
// 获取设备像素比
const dpr = wx.getWindowInfo().pixelRatio
// 初始化画布大小
canvas.width = width * dpr
canvas.height = height * dpr
// 画笔的颜色
ctx.fillStyle = 'rgb(200, 0, 0)';
// 指定了画笔(绘制线条)操作的线条宽度
ctx.lineWidth = 5
// 如果存在历史记录,则将历史记录最新的一张图片拿出来进行绘制。非ios时直接加载一张初始的空白图片
if (historyImage.length !== 0 || (deviceInFo.platform !== 'ios' && type !== 'init')) {
// 图片对象
const image = canvas.createImage()
// 图片加载完成回调
image.onload = () => {
// 将图片绘制到 canvas 上
ctx.drawImage(image, 0, 0, canvasWidth, canvasHeight)
}
// 设置图片src
image.src = historyImage[historyImage.length - 1] || initialCanvasImg;
}
// 缩小/放大图像
ctx.scale(dpr, dpr)
this.setData({
canvas,
ctx
})
// 保存一张初始空白图片
if (type === 'init') {
wx.canvasToTempFilePath({
canvas,
png: 'png',
success: res => {
// 生成的图片临时文件路径
const tempFilePath = res.tempFilePath
this.setData({
initialCanvasImg: tempFilePath
})
},
})
}
})
},
// 手指触摸动作开始
bindtouchstart(event) {
this.addPathDrop(event)
},
// 手指触摸后移动
bindtouchmove(event) {
this.addPathDrop(event)
},
// 手指触摸动作结束
bindtouchend(event) {
this.addPathDrop(event)
},
// 手指触摸动作被打断,如来电提醒,弹窗
bindtouchcancel(event) {
this.addPathDrop(event)
},
// 添加路径点
addPathDrop(event) {
let {
ctx,
historyImage,
canvas
} = this.data
let {
type,
changedTouches
} = event
let {
x,
y
} = changedTouches[0]
if (type === 'touchstart') { // 每次开始都是一次新动作
// 最开始点
ctx.moveTo(x, y) // 设置绘图起始坐标。
} else {
// 上一段终点
ctx.lineTo(x, y) // 从最后一点到点(x,y)绘制一条直线。
// 绘制
ctx.stroke();
// 下一段起点
ctx.moveTo(x, y) // 设置绘图起始坐标。
}
// 每一次结束或者意外中断,保存一份图片到历史记录中
if (type === 'touchend' || type === 'touchcancel') {
wx.canvasToTempFilePath({
canvas,
png: 'png',
success: res => {
// 生成的图片临时文件路径
const tempFilePath = res.tempFilePath
historyImage.push(tempFilePath)
this.setData(historyImage)
},
})
}
},
// 上一步
prev() {
this.setData({
historyImage: this.data.historyImage.slice(0, this.data.historyImage.length - 1)
})
this.initCanvas()
},
// 重写
overwrite() {
this.setData({
historyImage: []
})
this.initCanvas()
},
// 提交
confirm() {
const {
canvas,
historyImage
} = this.data
if (historyImage.length === 0) {
wx.showToast({
title: "请先签名再提交",
icon: 'none',
});
return
}
// 生成图片
wx.canvasToTempFilePath({
canvas,
png: 'png',
success: (res) => {
this.handleUpload(res.tempFilePath)
},
})
},
// 上传附件并提交
handleUpload(fileUrl) {
uploadFile({
fileUrl,
requestUrl: 'uploadFile'
}).then(res => {
const {
fileId,
url,
} = res
wx.showToast({
title: "上传服务器获取到url",
icon: 'none',
})
})
},
})
signature/signature.json
{
"navigationBarTitleText": "供应商签名确认",
"backgroundTextStyle": "dark",
"pageOrientation": "landscape",
"disableScroll": true,
"usingComponents": {
"van-button": "@vant/weapp/button/index"
}
}
如果你觉得这篇文章对你有用,可以看看作者封装的库xtt-utils,里面封装了非常实用的js方法。如果你也是vue开发者,那更好了,除了常用的api,还有大量的基于element-ui组件库二次封装的使用方法和自定义指令等,帮你提升开发效率。不定期更新,欢迎交流~