小程序手写电子签名&&身份证照片到远程服务器
- 前言
又来写一篇小程序的技能文章
主要是对工作的经验梳理和积累, 现在的公司平台比较小,属于初创型公司
我目前用到的技术栈主要是uniapp (vue2) 负责了两个TOC的小程序 及他们各自的后端代码(仅限于维护)
工作还是比较轻松的,已经在考虑未来的发展和规划了
好了废话不多说 正式开始
1.0 先说需求
之前做的功能模块是老拉新活动,分别给新老用户一定的优惠,优惠的发放方式是这样:
将新用户拉进企业微信群,群的二维码由我们自己生成,放到OSS文件列表,前端只负责获取二维码链接
这样的方式显然比较不合规,比较老套。主要这样处理对用户来说,并不直观,积极性也很差,所以这次需求有所改动
- 新的发放方式是这样,公司对接了易税平台,
- 我们需要获取用户的真实姓名、手机号(银行卡预留手机号)、银行卡号,及用户的电子签名,
- 让用户和易税签订一个合同,这样可以直接打款到用户的银行卡。也就是所谓的返现。
前端遇到的问题并不多,我遇到比较麻烦的问题就是上传
在这里看一下最终的实现效果吧
注意: 目前在用电脑的模拟器在上传(图片和身份证本质上一样,在前端不会做身份证的校验,都是上传图片到OSS列表) 且上传身份证的话 涉及到个人隐私 不打码不合适 等过几天公司项目正式测试的时候 我就换成测试的版本
1.1 电子签名
知道需求之后,我回到工位,就开始百度、csdn、掘金等各个平台先看看有没有先例,
结果大多是驴唇不对马嘴,我就知道可能要自己努力努力了
在网上还是找到了很多类似的例子,看了N个人的实现效果之后,我也开始了自己的代码编写(主要是cv ,取其精华,弃其糟粕。 hhhhh 说出来真的不怕你们笑话)
1.1.1 思路
- 思路大概是这样给一个小片区域用来放图片(预览效果),和一个提交按钮
- 点击外部盒子的时候,激活
page-container组件 ,这个组件是小程序内部封装好的 简单说就是充满全屏。 - 然后再
page-container组件里分别放上 取消、重新签名、完成签名这三个按钮 (还没有做到撤销,那么高端的功能)。 - 其他区域进行绘制(其实就是签名,做成了横板签名的效果,因为本人的笔画很多,竖屏实在是困难,加上我看到公司的用户里有少数民族,其中见过迪丽热巴这个姓氏,不知道她要怎么签名,坐等客服跟我反馈!)
- 写好名字点击提交时,将图片的地址发给服务器就可以了
- page-container文档 微信和支付宝一样
1.1.2 代码实现
<view class="image-container" @click="cli">
<image mode="aspectFit" :src="sign" />
<text v-if="!sign" class="text-center">签名区域</text>
</view>
<button style="margin-top: 30rpx;" @click="submitSignature">提交保存</button>
data(){
return{
sign: null,
isSignatureFinish: false,
moveLength: 0, //移动一定距离后才能提交保存
showPage: false,
canvasTempWidth: uni.getSystemInfoSync().screenWidth,
canvasTempHeight: uni.getSystemInfoSync().screenWidth * 0.5,
img_1:'', //身份证正面照片
}
},
onShow() {
//第一个canvas 创建画布
this.canvasContext = uni.createCanvasContext('canvas');
console.log('this.canvasContext',this.canvasContext);
this.initCanvas()
//第二个canvas
this.ctx2 = uni.createCanvasContext('canvas-temp');
},
methods:{
// 封装上传到oss列表方法
async uploadOss(data,type){
const res = await payRequest({
path:'/api/yishui/img/upload',
method:'POST',
data:{
type
}
},1)
const {host , OSSAccessKeyId:ossAccessKeyId, signature,key,policy} = res[1].data
uni.uploadFile({
url: host,
fileType: 'image',
// #ifdef MP-ALIPAY
fileName: 'file',
// #endif
// #ifdef MP-WEIXIN
name: 'file',
// #endif
filePath:data,
formData: {
key,
policy,
OSSAccessKeyId: ossAccessKeyId,
signature,
success_action_status: '200',
},
success: (res) => {
// 将上传成功状态码设置为200,默认状态码为204。
if (res.statusCode === 200) {
console.log('上传成功');
}
uni.showToast({ title: '上传成功!'});
data = `${host}/${key}`// 回填数据 让图片回填到页面
console.log(data,this.img_1, 'data,');
},
fail: err => {
console.log(err,'err上传失败了');
}
})
},
// 提交签名
async submitSignature(){
if(this.sign){
this.uploadOss(this.sign,'signImg')
}else{
uni.showToast({
icon:'none',
title: '请先完成电子签名,再提交保存',
})
}
},
// 打开/关闭电子签名区域
cli(){
this.showPage = !this.showPage
console.log(this.showPage);
},
drawBackground (ctx, color = 'white', width = 1000, height = 3000, x = 0, y = 0) {
ctx.rect(x, y, width, height)
ctx.setFillStyle(color)
ctx.fill()
},
// 完成签名
confirmSign() {
let _this = this
_this.generateSignImage('canvas').then(res => {
console.log('图片地址:---------', res)
uni.getImageInfo({
src: res,
success(info) {
let width = info.width
let height = info.height
//先重置canvas-temp
_this.ctx2.draw(false, ()=>{
let dx = _this.canvasTempWidth
let dy = _this.canvasTempHeight
//把原点移动到中心点位置
_this.ctx2.translate(dx / 2, dy / 2)
_this.ctx2.rotate(270 * Math.PI / 180)
let dWidth = dx / height * width
let dHeight = dx
//drawImage参数说明
//imageResource
//imageResource的左上角在目标 canvas 上 x 轴的位置
//imageResource的左上角在目标 canvas 上 y 轴的位置
//在目标画布上绘制 imageResource 的宽度,允许对绘制的 imageResource 进行缩放
//在目标画布上绘制 imageResource 的高度,允许对绘制的 imageResource 进行缩放
_this.ctx2.drawImage(res, -dWidth / 2,-dHeight / 2, dWidth, dHeight)
//canvas-temp图片绘制完成后
_this.ctx2.draw(false, ()=>{
_this.generateSignImage('canvas-temp').then(res => {
console.log('图片地址:!!!!!!!', res)
_this.sign= res
_this.showPage = false
})
})
})
}
})
})
},
initCanvas() {
/* 设置线条颜色 */
this.canvasContext.setStrokeStyle('#0081ff'); //2A2A2A
/* 设置线条粗细 */
this.canvasContext.setLineWidth(2);
/* 设置线条的结束端点样式 */
this.canvasContext.setLineCap('round');
},
/* 触摸开始 */
handleTouchStart(e) {
console.log(e)
this.drawStartX = e.changedTouches[0].x;
this.drawStartY = e.changedTouches[0].y;
this.canvasContext.beginPath();
},
/* 触摸移动 */
handleTouchMove(e) {
/* 记录当前位置 */
const tempX = e.changedTouches[0].x;
const tempY = e.changedTouches[0].y;
this.moveLength += Math.abs(this.drawStartX - tempX) + Math.abs(this.drawStartY - tempY)
/* 画线 */
this.canvasContext.moveTo(this.drawStartX, this.drawStartY);
this.canvasContext.lineTo(tempX, tempY);
this.canvasContext.stroke();
/* 旧版draw方法,新版本不需要draw */
this.canvasContext.draw(true);
/* 重新记录起始位置 */
this.drawStartX = tempX;
this.drawStartY = tempY;
},
/* 触摸结束 */
handleTouchEnd(e) {
console.log('触摸结束')
this.canvasContext.save();
if(this.moveLength > 100) this.isSignatureFinish = true
},
/* 触摸取消 */
handleTouchCancel(e) {
console.log('触摸取消')
this.canvasContext.save();
},
/* 清空画布 */
clearCanvas() {
console.log('清空画布')
this.canvasContext.draw()
this.initCanvas()
this.moveLength = 0
this.isSignatureFinish= false
},
/* 生成签名图片 */
generateSignImage(canvasId) {
console.log('Canvas生成图片')
return new Promise((resolve, reject) => {
uni.canvasToTempFilePath({
x: 0,
y: 0,
canvasId: canvasId, // 旧版使用id
fileType: 'png',
quality: 1,
success: res => {
resolve(res.tempFilePath)
},
fail: err => {
reject(err);
}
})
})
},
}
- 代码量还是比较多的 尤其是绘制的时候 实话实说,不是完全自己写的,就不逐行解释了. 各位吴彦祖和刘亦菲们 应该都看得懂!
- oss上传文件的问题 一会单独说一下
1.2 身份证照片上传
- 需求虽然是身份证照片上传,但我问过产品了,上传的时候,我们公司不会坐识别校验是否合规,易税会统一审核,如果被驳回说明不合规,所以上传的就是一张图片。上边的效果也只是我的测试,等项目上线,会换成公司的项目gif文件
1.2.1 思路
- 根据我之前上传身份证的经验来说,用户会有两个选择,拍照或者上传(需要获取用户相册权限。如果遭拒,需要提醒)。
- 将用户上传的文件类型需要做限制,必须是图片且只能上传一张
- 上传到oss文件列表后,再次拿到图片的url地址做展示
1.2.2 代码实现
<view class="img" style="width:100%;height: 400rpx;">
<image :src="img_1" style="width:100%;height:100%" mode="aspectFit" />
</view>
<button style="margin-top: 30rpx;" @click="submitCard">提交身份证</button>
data(){
return{
img_1:'', //身份证正面照片
}
}
methods:{
// 提交身份证正面
submitCard(){
// #ifdef MP-ALIPAY
my.chooseImage({
success: res => {
console.log(res, '查看res');
this.img_1 = res.tempFilePaths[0]
my.previewImage({
urls: res.tempFilePaths, // 使用所选中图片的本地临时文件路径列表
});
this.uploadOss(this.img_1,'cerFrontImg')
}
});
// #endif
// #ifdef MP-WEIXIN
wx.chooseMedia({
count:1,
mediaType:['image'],
success:res=>{
console.log(res);
this.img_1 = res.tempFiles[0].tempFilePath
this.uploadOss(this.img_1,'cerFrontImg')
}
})
// #endif
},
// 封装上传到oss列表方法
async uploadOss(data,type){
const res = await payRequest({
path:'/api/yishui/img/upload',
method:'POST',
data:{
type
}
},1)
const {host , OSSAccessKeyId:ossAccessKeyId, signature,key,policy} = res[1].data
uni.uploadFile({
url: host,
fileType: 'image',
// #ifdef MP-ALIPAY
fileName: 'file',
// #endif
// #ifdef MP-WEIXIN
name: 'file',
// #endif
filePath:data,
formData: {
key,
policy,
OSSAccessKeyId: ossAccessKeyId,
signature,
success_action_status: '200',
},
success: (res) => {
// 将上传成功状态码设置为200,默认状态码为204。
if (res.statusCode === 200) {
console.log('上传成功');
}
uni.showToast({ title: '上传成功!'});
data = `${host}/${key}`
console.log(data,this.img_1, 'data,');
},
fail: err => {
console.log(err,'err上传失败了');
}
})
},
}
1.3 OSS小程序直传实践
这里可能有同学要问 封装的oss上传方法怎么来的
那这里我就告诉你
没有阿里云 RAM账号会禁止访问 ,我在放一下截图吧
- 前面还有三个步骤,都是用来配置跨域 设置白名单
- 从文档可以看到,最需要的就是
key,policy,OSSAccessKeyId,signature这几个参数,我和老大沟通之后,他给了我接口,我发请求(签名、身份证正面、身份证反面都会返给我对应的参数),于是我封装的方法,便应运而生了
// 封装上传到oss列表方法
/*
data是需要上传的数据(图片文件)
type是类型(我老大对签名、身份证正面、身份证反面这三种做了分类)
分别传入调用就可以了
*/
async uploadOss(data,type){
const res = await payRequest({
path:'/api/yishui/img/upload',
method:'POST',
data:{
type
}
},1)
const {host , OSSAccessKeyId:ossAccessKeyId, signature,key,policy} = res[1].data
uni.uploadFile({
url: host,
fileType: 'image',
// #ifdef MP-ALIPAY
fileName: 'file',
// #endif
// #ifdef MP-WEIXIN
name: 'file',
// #endif
filePath:data,
formData: {
key,
policy,
OSSAccessKeyId: ossAccessKeyId,
signature,
success_action_status: '200',
},
success: (res) => {
// 将上传成功状态码设置为200,默认状态码为204。
if (res.statusCode === 200) {
console.log('上传成功');
}
uni.showToast({ title: '上传成功!'});
data = `${host}/${key}`
console.log(data,this.img_1, 'data,');
},
fail: err => {
console.log(err,'err上传失败了');
}
})
},
最后介绍 几个小程序的api
my.chooseImage:拍照或从本地相册中选择图片的 API(支付宝)
wx.chooseMedia:拍摄或从手机相册中选择图片或视频(微信)
uni.uploadFile:将本地资源上传到开发者服务器(通用)