uniapp 实现在线签合同/签名/信息认证(无插件依赖)

最近项目接到一个新的需求,需要对接一个可以在线签合同的的功能,知道需要后马上开干,经过一番斗争,终于终于下班啦
开个玩笑,废话不多说,直接上代码,因为代码是直接项目中搬出来的,没有依赖其他插件纯手工,小伙伴们要看清除那些是自己需要的,不需要的就删掉啦。

目前只测试小程序 , 其他端需要自己去适配

效果图:
在这里插入图片描述
页面有两个,一个表单提交也需要上传身份证正反面,第二个页面有合同展示,签字面板是以弹窗形式

第一个页面(表单也不需要的可以删除):


<template>
	<view>
		<form @submit="formSubmit">
			<view class="Rboy-box">
				<view class="Rboy-obverse">
					<image class="obverseimg" :src="ALMain_drawing" @click="obverse_btn" mode="aspectFill"></image>
					<input style="display: none;" name="ALMain_drawing" />
					<view class="bottom">
						<text>身份证正面照</text>
					</view>
				</view>
				<view class="Rboy-reverse">
					<image class="reverseimg" :src="ALPicture" @click="reverse_btn" mode="aspectFill"></image>
					<input style="display: none;" name="ALPicture" />
					<view class="bottom">
						<text>身份证反面照</text>
					</view>
				</view>
			</view>
			<view class="Rboy-form">
				<view class="Rboy-form-row">
					<label>姓名</label>
					<view class="form-rowDom"><input placeholder="请输入姓名" disabled name="name"
							:value="RealName.username" /></view>
				</view>
				<view class="Rboy-form-row">
					<label>身份证号码</label>
					<view class="form-rowDom"><input placeholder="请输入身份证" disabled name="certificates"
							:value="RealName.id_card" /></view>
				</view>
				<view class="Rboy-form-row">
					<label>电话号码</label>
					<view class="form-rowDom"><input placeholder="请输入电话" disabled name="phone"
							:value="RealName.phone" /></view>
				</view>
				<view class="Rboy-form-row">
					<label>支付宝账号</label>
					<view class="form-rowDom"><input placeholder="请输入电话" disabled name="phone"
							:value="RealName.alipay" /></view>
				</view>
			</view>
			<view class="agreement">
				<view class="agreement-row" @click="JumpSee">
					<view class="agreement-name">《自由职业合作服务协议》</view>
					<view class="agreement-row-label">
						<text
							:style="{ color: signStatus === false?'#f97777':'#666' }">{{ signStatus === false ? '去签字':'已签署' }}</text>
						<tui-icon name="arrowright" size="18"></tui-icon>
					</view>
				</view>
			</view>
			<view class="application">
				<view class="application-tips" @click="readValve">
					<tui-icon :color=" prevent === 'circle' ?'#999':'#fea12e' " size="14" :name="prevent"></tui-icon>
					<text style="margin-left: 10upx;">我已同意并签署《自由职业合作服务协议》</text>
				</view>
				<button form-type="submit">提交认证</button>
				<!-- <label>提交认证</label> -->
			</view>
		</form>
	</view>
</template>
<script>
	export default {
		data() {
			return {

				identity: 0, //实名状态  ban:禁止实名 true:成功实名
				ALMain_drawing: 'https://777777.com/mapp/img/comm/identity_topside.png', //身份默认证正面
				ALPicture: 'https://777777.com/mapp/img/comm/identity_backside.png', //身份默认证反面		
				RealName: {
					Main_drawing: '', //身份证正面
					Picture: '', //身份证反面	
					username: '', //姓名
					id_card: '', //身份证号
					phone: '', //手机号
					alipay: '', //支付宝
					agreement: '' //协议
				},
		 	signStatus: false, //签署状态
				prevent: 'circle',
				// InformationRealName:true
			}
		},
		async onLoad() {
			const t = this;
			const result = await t.$req('/user/getIdentityInfo', {})
			let rd = result.data;
			if (rd.code == 0) {
				let data = rd.data;
				t.RealName.username = data.username;
				t.RealName.id_card = data.id_card;
				t.RealName.phone = data.phone;
				t.RealName.alipay = data.alipay;
			}
		},

		onShow() {
			const t = this;
			uni.getStorage({
				key: 'autograph_key',
				success: function(res) {
					if (res.data !== '') {
						t.signStatus = true
						t.RealName.agreement = res.data
					}
				}
			});
		},
		methods: {
			async obverse_btn() {
				const t = this;
				uni.chooseImage({
					count: 1, //默认9
					sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
					sourceType: ['album'], //从相册选择
					success: function(res) {
						let ImgType = res.tempFilePaths[0].split(".")
						uni.getFileSystemManager().readFile({
							filePath: res.tempFilePaths[0],
							encoding: 'base64',
						 success: async (res) => {
								t.RealName.Main_drawing = 'data:image/png;base64,' + res.data;
								// t.ALMain_drawing = t.RealName.Main_drawing;
								uni.showLoading({
									title: '加载中'
								});
								// console.log(res.data)
								const result = await t.$req("/user/upload/img", {
									uid: t.user.uid,
									type: ImgType[1],
									img: res.data,
									place: 'identity'
								})
								let rd = result.data
								// console.log(rd)
								if (rd.code == 0) {
									let data = rd.data
									t.ALMain_drawing = data.url;
									uni.hideLoading();
									t.RealName.Main_drawing = data.url;
								}
							}
						})
					}
				});
			},
			async reverse_btn() {
				const t = this;
				uni.chooseImage({
					count: 1, //默认9
					sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
					sourceType: ['album'], //从相册选择
					success: function(res) {
						let ImgType = res.tempFilePaths[0].split(".")
						uni.getFileSystemManager().readFile({
							filePath: res.tempFilePaths[0],
							encoding: 'base64',
							success: async (res) => {
								t.RealName.Picture = 'data:image/png;base64,' + res.data;
								uni.showLoading({
									title: '加载中'
								});
								// console.log(res.data)
								const result = await t.$req("/user/upload/img", {
									uid: t.user.uid,
									type: ImgType[1],
									img: res.data,
									place: 'identity'
								})
								// https://ganqiao-dev.oss-cn-hangzhou.aliyuncs.com 测试环境路径
								let rd = result.data
								// console.log(rd)
								if (rd.code == 0) {
									let data = rd.data
									t.ALPicture = data.url;
									uni.hideLoading();
									t.RealName.Picture = data.url;
								}
							}
						})
					}
				});
			},
			readValve() {
				const t = this;
				if (t.prevent == "circle") {
					t.prevent = "circle-fill"
				} else {
					t.prevent = "circle"
				}
			},
			JumpSee() {
				const t = this;
				uni.navigateTo({
					url: '/pages/login/seeagreement'
				})
			},
			async formSubmit(e) {
				const t = this;
				var formdata = e.detail.value
				if (t.RealName.Main_drawing == '') {
					uni.showToast({
						title: '请上传身份证正面照',
						icon: "none",
						duration: 2000
					});
					return false
				}
				if (t.RealName.Picture == '') {
					uni.showToast({
						title: '请上传身份证反面照',
						icon: "none",
						duration: 2000
					});
					return false
				}
				if (t.RealName.agreement == '') {
					uni.showToast({
						title: '请签署《自由职业合作服务协议》',
						icon: "none",
						duration: 2000
					});
					return false
				}
				if (t.prevent == 'circle') {
					uni.showToast({
						title: '请同意勾选《自由职业合作服务协议》',
						icon: "none",
						duration: 2000
					});
					return false
				}
				let RealName = t.RealName;
				const result = await t.$req('/user/ysUpdateIdentity', {
					topside: RealName.Main_drawing,
					backside: RealName.Picture,
					agree: 1,
					user_sign: RealName.agreement
				})
				let rd = result.data
				if (rd.code == 0) {

					let data = rd.data;
					uni.showToast({
						title: '提交成功',
						icon: 'none',
						default: 200
					})
				}
			}
		}
	}
</script>
<style scoped lang="less">
page{background:#fff!important}.Rboy-box{width:100%;display:flex;flex-direction:row;padding:30upx 3%;align-items:center;justify-content:space-between}.Rboy-box,.Rboy-obverse{height:auto;box-sizing:border-box}.Rboy-obverse{width:48%;margin-bottom:0upx;position:relative;box-shadow:0 3px 13px rgba(0,0,0,.05);padding:0}.Rboy-reverse{width:80%;padding:20px 3%}.obverseimg{width:100%;height:240rpx}.bottom{text-align:center;margin:auto}.bottom,.bottom text{width:100%;height:80upx;line-height:80upx}.bottom text{display:block;background:#fff;font-size:14px;color:#777}.Rboy-reverse{width:48%;height:auto;box-sizing:border-box;margin-bottom:0upx;position:relative;box-shadow:0 3px 13px rgba(0,0,0,.05);padding:0}.reverseimg{width:100%;height:240rpx}.application{width:100%;height:auto;display:flex;flex-direction:column;justify-content:center;position:fixed;bottom:20upx;left:0;right:0;padding-bottom:20upx}.application button,.application label{width:90%;height:90upx;line-height:90upx;display:block;margin:auto;text-align:center;background:linear-gradient(180deg,#efc480,#f8dca5 25%,#ffd5a2 98%);border-radius:50px;font-weight:700;font-size:15px;color:#fd972e;box-shadow:0 5px 3px #fdb964}.application button:after{border:none}.Rboy-form{width:94%;height:auto;background:#fff;margin:auto;box-shadow:0 3px 13px rgba(0,0,0,.05;);box-sizing:border-box;padding:0upx 3%}.Rboy-form-row{width:100%;display:flex;flex-direction:row;justify-content:space-between;align-items:center;border-bottom:1px solid #f5f3f3;height:100upx;line-height:100upx}.Rboy-form-row label{width:25%;font-size:14px}.form-rowDom{width:75%}.form-rowDom input{width:100%;font-size:12px}.agreement{width:94%;height:auto;background:#fff;margin:30upx auto;box-shadow:0 3px 13px rgba(0,0,0,.05);box-sizing:border-box;padding:0 3%}.agreement-row{width:100%;height:auto;display:flex;flex-direction:row;justify-content:space-between;align-items:center}.agreement-name{width:70%;height:100upx;line-height:100upx;text-align:left;font-size:14px;color:#2ebbfe}.agreement-row-label{width:30%;height:100upx;line-height:100upx;display:flex;flex-direction:row;justify-content:flex-end;align-items:center}.agreement-row-label text{font-size:12px}.application-tips{width:90%;height:auto;margin:0 auto;line-height:80upx}.application-tips text{color:#999}
</style>



第二个页面:(签名页-请把接口改成自己的 不然容易报错)


<template>
	<view class="content">

		<view class="authentication_top">
			<image v-if="PreviewContract" :src="contractImg" mode="widthFix"></image>
			<!-- 创建合成画布 -->
			<view class="bgCoverBox" v-if="publish"><canvas :style="{width:height +'px',height:width +'px'}" canvas-id="mycontract" class="canvsborder2"></canvas></view>
			<!-- 创建合成画布 -->
		</view>
		<view class="authentication_bottom" v-if="NavAutograph" style="background:#ffe9e9" @click="agreeSign">
			<button style="color:#f00">我已阅读上述内容,同意签字>></button>
		</view>
	
		<view class="authentication_fun" v-if="Navpreservation" style="background:#fff; ">
			<!-- <button style="color: #1789ff"  @click="preservationImg">保存到本地</button>
			<button style="color: #1789ff"  @click="ReSign">重新签署</button> -->
			<button style="color:#fd972e;"  @click="SigningCompleted">签署完成,去提交>></button>
		</view>
	
		<!-- 签字弹窗 status -->
		<view class="signMask" v-if="autographStatus">
			<view class="sigh-btns">
				<button class="btn" @tap="handleCancel">取消</button>
				<button class="btn" @tap="handleReset">重写</button>
				<button class="btn" @tap="handleConfirm">确认</button>
			</view>
			<view class="sign-box">
				<canvas class="mycanvas" :style="{width:width +'px',height:height +'px'}" canvas-id="mycanvas"
					@touchstart="touchstart" @touchmove="touchmove" @touchend="touchend"></canvas>
				<canvas canvas-id="camCacnvs" :style="{width:height +'px',height:width +'px'}"
					class="canvsborder"></canvas>
			</view>
		</view>

		<!-- 签字弹窗 end -->

	</view>
</template>
<script>
	var x = 20;
	var y = 20;
	var tempPoint = []; //用来存放当前画纸上的轨迹点
	var id = 0;
	var type = '';
	let that;
	let canvasw;
	let canvash;
	export default {
		data() {
			return {
				
				contractImg: '../../static/motu.jpg', //合同路径
				ctx: '', //绘图图像
				points: [], //路径点集合,
				width: 0,
				height: 0,
				autographStatus: false,
				publish: false,
				PreviewContract:true,
				NavAutograph: true,
				Navpreservation:false
			}
		},
		onLoad(option) {
			that = this;
			id = option.id;
			type = option.type;
			this.ctx = uni.createCanvasContext('mycanvas', this); //创建绘图对象
			//设置画笔样式
			this.ctx.lineWidth = 4;
			this.ctx.lineCap = 'round';
			this.ctx.lineJoin = 'round';
			uni.getSystemInfo({
				success: function(res) {
					that.width = res.windowWidth * 0.8;
					that.height = res.windowHeight * 0.85;
				}
			});
		},
		onShow() {
			const t = this;
			uni.getStorage({
				key: 'autograph_key',
				success: function (res) {
					if( res.data !== '' ){
						t.contractImg = res.data
						t.PreviewContract = true;
						t.publish = false;
						t.NavAutograph = false
					}
				}
			});				
		},		
		
		methods: {
			agreeSign() {
				this.autographStatus = true;
			},

			//触摸开始,获取到起点
			touchstart: function(e) {
				let startX = e.changedTouches[0].x;
				let startY = e.changedTouches[0].y;
				let startPoint = {
					X: startX,
					Y: startY
				};
				/* **************************************************
				#由于uni对canvas的实现有所不同,这里需要把起点存起来
			 * **************************************************/
				this.points.push(startPoint);

				//每次触摸开始,开启新的路径
				this.ctx.beginPath();
			},
			//触摸移动,获取到路径点
			touchmove: function(e) {
				let moveX = e.changedTouches[0].x;
				let moveY = e.changedTouches[0].y;
				let movePoint = {
					X: moveX,
					Y: moveY
				};
				this.points.push(movePoint); //存点
				let len = this.points.length;
		 	if (len >= 2) {
					this.draw(); //绘制路径
				}
				tempPoint.push(movePoint);
			},
			// 触摸结束,将未绘制的点清空防止对后续路径产生干扰
			touchend: function() {
				this.points = [];
			},
			/* ***********************************************	
					#   绘制笔迹
					#   1.为保证笔迹实时显示,必须在移动的同时绘制笔迹
					#   2.为保证笔迹连续,每次从路径集合中区两个点作为起点(moveTo)和终点(lineTo)
					#   3.将上一次的终点作为下一次绘制的起点(即清除第一个点)
					************************************************ */
			draw: function() {
				let point1 = this.points[0];
				let point2 = this.points[1];
				this.points.shift();
				this.ctx.moveTo(point1.X, point1.Y);
				this.ctx.lineTo(point2.X, point2.Y);
				this.ctx.stroke();
				this.ctx.draw(true);
			},
			handleCancel() {
				uni.navigateBack({
					delta: 1
				});
			},
			//清空画布
			handleReset: function() {
				console.log('handleReset');
				that.ctx.clearRect(0, 0, that.width, that.height);
				that.ctx.draw(true);
				tempPoint = [];
			},
			//将签名笔迹上传到服务器,并将返回来的地址存到本地
			handleConfirm: function() {
				const t = this;
				if (tempPoint.length == 0) {
					uni.showToast({
						title: '请先签名',
						icon: 'none',
						duration: 2000
					});
					return;
				}
				uni.showLoading({
					title: '生成中'
				});
				uni.canvasToTempFilePath({
					canvasId: 'mycanvas',
					success: function(res) {
						let tempPath = res.tempFilePath;
						const ctx = uni.createCanvasContext('camCacnvs', that);
						ctx.translate(0, that.width);
						ctx.rotate((-90 * Math.PI) / 180);
						ctx.drawImage(tempPath, 0, 0, that.width, that.height);
						ctx.draw();
						setTimeout(() => {
							//保存签名图片到本地
							uni.canvasToTempFilePath({
									canvasId: 'camCacnvs',
									success: function(res) {
										//这是签名图片文件的本地临时地址
										let path = res.tempFilePath;
										console.log(path, "保存签名图片到本地")
										t.autographStatus = false
										// 开始合成
										var _this = this;
										t.publish = true;
										t.PreviewContract = false;
										t.NavAutograph = false;
										t.Navpreservation = true;
										const loy = uni.createCanvasContext('mycontract', this);
										// loy.clearRect(0, 0, that.width, that.height);
										// loy.translate(0, 0);
										// loy.rotate((0 * Math.PI) / 180);
										let imgGao = ''
										uni.getSystemInfo({
											success:function(res){
												imgGao = res.screenWidth 
											}
										})
										loy.drawImage(t.contractImg, 0, 0, 375 , 600);
										loy.drawImage(path, 260, 500, 100, 50);
										loy.draw();
										setTimeout(() =>{
											uni.canvasToTempFilePath({
												canvasId: 'mycontract',
												success: function(res) {
													uni.hideLoading();
													t.contractImg = res.tempFilePath;
													// console.log("合同图片",res.tempFilePath)
												}	
											})
										} ,200)
										// 合成完毕
									},
									fail: err => {
										// console.log('fail', err);
									}
								},
								this
							);
						}, 200);
					}
				});
			},
			preservationImg(){
				const t = this;
				uni.downloadFile({
					url: t.contractImg,
					success: res => {
						if (res.statusCode === 200) {
							uni.saveImageToPhotosAlbum({
								filePath: res.tempFilePath,
								success: function() {
									uni.showToast({
										title: '保存成功',
										icon: 'none',
										duration: 2000
									});
								},
								fail: function() {
									uni.showToast({
										title: '保存失败',
										icon: 'none',
										duration: 2000
									});
								}
							});
						} else {
							uni.showToast({
								title: '第三方网络错误',
								icon: 'none',
								duration: 2000
							});
						}
					}
				});
			},
			SigningCompleted(){
				const t = this;
				let ImgType = t.contractImg.split(".")
				uni.getFileSystemManager().readFile({
					  filePath: t.contractImg,
					  encoding: 'base64',
					  success: async(res) =>{
						  t.contractImg = 'data:image/png;base64,' + res.data;
						  uni.showLoading({
							  title: '提交中'
						  });
						  // console.log(res.data)
						   const result = await t.$req("/user/upload/img",{ 
							   uid: t.user.uid,
							   type: ImgType[1],
							   img: res.data,
							   place: 'sign'	
							})
							let rd = result.data
							// console.log(rd)
							if(rd.code == 0){
								let data = rd.data
								t.contractImg = data.url;								
								uni.hideLoading();
								uni.setStorage({
									key: 'autograph_key',
									data: t.contractImg,
									success: function () {
										console.log("签字",'success');
									}
								});
								uni.navigateBack({ delta: 1 });    // 返回上一页								
							}
					  }
				})				
			},
			ReSign:function(){
				// console.log(this.ctx)
				this.autographStatus = true
			}
		}
	}
</script>
<style>
.authentication_top{width:100%;height:90%;position:fixed;top:0;left:0;right:0;margin:auto;overflow-y:scroll;padding-bottom:40rpx}.authentication_top image{width:100%;display:inline-block}.authentication_bottom{width:100%;height:10%;position:fixed;bottom:0;left:0;right:0;margin:auto;display:flex;flex-direction:column;justify-content:center;align-items:center;background:#ffe9e9}.authentication_bottom button{background:0 0;border:none;font-size:15px;color:red;width:100%;text-align:center}.authentication_bottom button:after{display:none}.authentication_fun{width:100%;height:10%;position:fixed;bottom:30rpx;left:0;right:0;margin:auto;display:flex;flex-direction:row;justify-content:center;align-items:center;background:#ffe9e9;padding:0 3%;width:90%;border-radius:50px;box-shadow:0 3px 13px rgba(0,0,0,.2);border:4px solid #fd972e;box-sizing:border-box}.authentication_fun button{border:none;font-size:15px;background:0 0;width:100%;text-align:center}.authentication_fun button:after{display:none}.signMask{width:100%;height:100%;background:#fff;position:fixed;top:0;bottom:0;left:0;right:0;flex-direction:row}.sign-box,.signMask{margin:auto;display:flex}.sign-box{width:80%;height:90%;flex-direction:column;text-align:center}.sigh-btns,.sign-view{height:100%}.sigh-btns{margin:auto;display:flex;flex-direction:column;justify-content:space-around}.btn{margin:auto;padding:8rpx 40rpx;font-size:14px;transform:rotate(90deg);border:1rpx solid grey}.mycanvas{margin:auto 0rpx;background-color:#ececec}.canvsborder{border:1rpx solid #333;position:fixed;top:0;left:10000rpx}.bgCoverBox{width:100%;height:auto}.canvsborder2{height:700px!important}
	
</style>


  • 9
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 21
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值