uniapp小程序自定义签名面板组件,小程序页面引用实现横屏签字(亲测有效)

小程序 0

需求: uniapp小程序自定义签字面板组件, canvas手写签名画板, 小程序页面引用实现横屏签字

实现效果:
在这里插入图片描述
在这里插入图片描述

一、自定义组件

在这里插入图片描述
在项目中创建components文件夹, 在文件夹下创建my-sign组件, 组件下创建my-sign.vue和index.js
my-sign.vue组件代码:

<template>	<view class="signature-wrap">		<canvas			:canvas-id="cid"			:id="cid"			@touchstart="onTouchStart"			@touchmove="onTouchMove"			@touchend="onTouchEnd"			disable-scroll			:style="[				{					width: width && formatSize(width),					height: height && formatSize(height)				},				customStyle			]"		></canvas>		<slot />	</view></template><script>/** * sign canvas 手写签名 * @description 设置线条宽度、颜色,撤回,清空 * @tutorial * @property {String} cid canvas id 不设置则默认为 v-sign-时间戳 * @property {String, Number} width canvas 宽度 * @property {String, Number} height canvas 高度 * @property {bgColor} bgColor 画布背景颜色 * @property {Object} customStyle canvas 自定义样式 * @property {String} lineWidth 画笔大小,权重小于 v-sign-pen 组件设置的画笔大小 * @property {Number} lineColor 画笔颜色,权重小于 v-sign-pen 组件设置的画笔大小 * @event {Function} init 当创建完 canvas 实例后触发,向外提供 canvas实例,撤回,清空方法 * @example <v-sign @init="signInit"></v-sign> */import { formatSize } from './index.js'export default {	name: 'my-sign',	props: {		// canvas id		cid: {			type: String,			default: `v-sign-${Date.now()}`			// required: true		},		// canvas 宽度		width: {			type: [String, Number]		},		// canvas 高度		height: {			type: [String, Number]		},		// 画笔大小,权重小于 v-sign-pen 组件设置的画笔大小 penLineWidth		lineWidth: {			type: Number,			default: 4		},		// 线颜色,权重小于 v-sign-color 组件设置的画笔颜色 penLineColor		lineColor: {			type: String,			default: '#333'		},		// 画布背景颜色		bgColor: {			type: String,			default: '#fff'		},		// canvas自定义样式		customStyle: {			type: Object,			default: () => ({})		}	},	provide() {		return {			getSignInterface: this.provideSignInterface		}	},	data() {		return {			formatSize,			lineData: [],			winWidth: 0,			winHeight: 0,			penLineWidth: null, // v-sign-pen 组件设置的画笔大小			penLineColor: null // v-sign-color 组件设置的颜色		}	},	created() {		// 获取窗口宽高		const { windowWidth, windowHeight } = uni.getSystemInfoSync()		this.winWidth = windowWidth		this.winHeight = windowHeight	},	mounted() {		this.canvasCtx = uni.createCanvasContext(this.cid, this)		// h5 需延迟绘制,否则绘制失败		// #ifdef H5		setTimeout(() => {			// #endif			this.setBackgroundColor(this.bgColor)			// #ifdef H5		}, 10)		// #endif		// 初始化完成,触发 init 事件		this.$emit('init', this.provideSignInterface())	},	methods: {		onTouchStart(e) {			const pos = e.touches[0]			this.lineData.push({				style: {					color: this.penLineColor || this.lineColor,					width: this.penLineWidth || this.lineWidth				},				// 屏幕坐标				coordinates: [					{						type: e.type,						x: pos.x,						y: pos.y					}				]			})			this.drawLine()		},		onTouchMove(e) {			const pos = e.touches[0]			this.lineData[this.lineData.length - 1].coordinates.push({				type: e.type,				x: pos.x,				y: pos.y			})			this.drawLine()		},		onTouchEnd(e) {			this.$emit('end', this.lineData)		},		// 清空画布		clear() {			this.lineData = []			this.canvasCtx.clearRect(0, 0, this.winWidth, this.winHeight)			this.canvasCtx.draw()			this.setBackgroundColor(this.bgColor)			this.$emit('clear')		},		// 撤销		revoke() {			this.setBackgroundColor(this.bgColor)			this.lineData.pop()			this.lineData.forEach((item, index) => {				this.canvasCtx.beginPath()				this.canvasCtx.setLineCap('round')				this.canvasCtx.setStrokeStyle(item.style.color)				this.canvasCtx.setLineWidth(item.style.width)				if (item.coordinates.length < 2) {					const pos = item.coordinates[0]					this.canvasCtx.moveTo(pos.x, pos.y)					this.canvasCtx.lineTo(pos.x + 1, pos.y)				} else {					item.coordinates.forEach(pos => {						if (pos.type == 'touchstart') {							this.canvasCtx.moveTo(pos.x, pos.y)						} else {							this.canvasCtx.lineTo(pos.x, pos.y)						}					})				}				this.canvasCtx.stroke()			})			this.canvasCtx.draw(true)			this.$emit('revoke', this.lineData)		},		// 绘制线条		drawLine() {			const lineDataLen = this.lineData.length			if (!lineDataLen) return			const currentLineData = this.lineData[lineDataLen - 1]			const coordinates = currentLineData.coordinates			const coordinatesLen = coordinates.length			if (!coordinatesLen) return			let startPos			let endPos			if (coordinatesLen < 2) {				// only start, no move event				startPos = coordinates[coordinatesLen - 1]				endPos = {					x: startPos.x + 1,					y: startPos.y				}			} else {				startPos = coordinates[coordinatesLen - 2]				endPos = coordinates[coordinatesLen - 1]			}			const style = currentLineData.style			this.canvasCtx.beginPath()			this.canvasCtx.setLineCap('round')			this.canvasCtx.setStrokeStyle(style.color)			this.canvasCtx.setLineWidth(style.width)			this.canvasCtx.moveTo(startPos.x, startPos.y)			this.canvasCtx.lineTo(endPos.x, endPos.y)			// const P1 = this.caculateBezier(startPos, endPos, centerPos)			// console.log(P1.x, P1.y)			// this.canvasCtx.moveTo(startPos.x, startPos.y)			// this.canvasCtx.quadraticCurveTo(P1.x, P1.y, endPos.x, endPos.y)			this.canvasCtx.stroke()			this.canvasCtx.draw(true)		},		// 保存png图片,文件名配置 filename 仅支持 h5		async saveImage(filename = '签名') {			const tempFilePath = await this.canvasToTempFilePath()			return new Promise((resolve, reject) => {				// #ifdef H5				try {					const a = document.createElement('a')					a.href = tempFilePath					a.download = filename					document.body.appendChild(a)					a.click()					a.remove()					resolve({						errMsg: 'saveImageH5:ok'					})				} catch (e) {					console.error(e)					reject(e)				}				// #endif				// #ifndef H5				uni.saveImageToPhotosAlbum({					filePath: tempFilePath,					success(resObj) {						resolve(resObj)					},					fail(err) {						reject(err)					}				})				// #endif			})		},		// canvas 保存为临时图片路径,h5返回 base64		canvasToTempFilePath(conf = {}) {			return new Promise((resolve, reject) => {				uni.canvasToTempFilePath(					{						canvasId: this.cid,						...conf,						success: res => {							resolve(res.tempFilePath)						},						fail: err => {							console.log('fail', err)							reject(err)						}					},					this				)			})		},		setBackgroundColor(color = '#fff') {			this.canvasCtx.beginPath()			this.canvasCtx.setFillStyle(color)			this.canvasCtx.fillRect(0, 0, this.winWidth, this.winHeight)			this.canvasCtx.fill()			this.canvasCtx.draw(true)		},		setLineWidth(numberVal) {			this.penLineWidth = numberVal		},		setLineColor(strValue) {			this.penLineColor = strValue		},		// 向外暴露内部方法		provideSignInterface() {			return {				cid: this.cid,				ctx: this.canvasCtx,				clear: this.clear,				revoke: this.revoke,				saveImage: this.saveImage,				canvasToTempFilePath: this.canvasToTempFilePath,				setLineWidth: this.setLineWidth,				setLineColor: this.setLineColor,				setBackgroundColor: this.setBackgroundColor,				getLineData: () => this.lineData			}		},		/**		 * 计算二次贝塞尔曲线 控制点 P1		 * 起点 P0(x0,y0)、控制点P1(x1, y1)、P2(x2, y2)、曲线上任意点B(x, y)		 * 二次贝塞尔公式:B(t) = (1-t)²P0 + 2t(1-t)P1 + t²P2		 * 代入坐标得:		 * x = (1-t)²*x0 + 2t(1-t)*x1 + t²*x2		 * y = (1-t)²*y0 + 2t(1-t)*y1 + t²*y2		 */		caculateBezier(P0, P2, B, t = 0.5) {			const { x: x0, y: y0 } = P0			const { x: x2, y: y2 } = P2			const { x, y } = B			let x1 = (x - (1 - t) * (1 - t) * x0 - t * t * x2) / (2 * t * (1 - t))			let y1 = (y - (1 - t) * (1 - t) * y0 - t * t * y2) / (2 * t * (1 - t))			return {				x: x1,				y: y1			}		}	}}</script><style lang="scss" scoped>.signature-wrap {	position: relative;}</style>

index.js代码:

/** * 判断是否未数值 * @param {Object} val */export function isNumber(val) {	return !isNaN(Number(val))}/** * 处理大小单位 * @param {Object} val */export function formatSize(val, unit = 'rpx') {	return isNumber(val) ? `${val}${unit}` : val}

二、配置小程序页面横屏

在pages.json中添加"pageOrientation": “landscape”, pageOrientation 设置为 landscape ,表示固定为横屏显示

在这里插入图片描述

{	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages		{			"path": "pages/index/index",			"style": {				"navigationBarTitleText": "签字",				"enablePullDownRefresh": false,				"pageOrientation": "landscape",				"backgroundColor": "#f8f8f8",				"navigationStyle": "custom"			}		}	],	"globalStyle": {		"navigationBarTextStyle": "black",		"navigationBarTitleText": "uni-app",		"navigationBarBackgroundColor": "#F8F8F8",		"backgroundColor": "#F8F8F8"	},	"uniIdRouter": {}}

三、在页面中使用

在这里插入图片描述
在这里插入图片描述

代码:

<template>	<view class="sign-contain">		<view class="sign-top">			请在空白处签字		</view>		<my-sign @init="onSignInit" @end="endConfirm" bgColor="#fff" width="100%" :height="signHeight">		</my-sign>		<!-- 按钮 -->		<view class="signBtn-box">			<view class="signBtn-item1">				<button type="default" plain="true" class="lnvestor-btn" hover-class="hover"					@click="cancelBtn">取消</button>			</view>			<view class="signBtn-item2">				<button type="default" plain="true" class="lnvestor-btn1" hover-class="hover"					@click="clear">清空重写</button>				<button type="primary" class="lnvestor-btn2" hover-class="hover"					@click="submitBtn" :disabled="vsignDisabled">提交签名</button>			</view>		</view>	</view></template><script>	export default {		data() {			return {				signHeight: '375px',				vsignDisabled: true			}		},		onLoad() {			var that = this;			uni.getSystemInfo({				success: function(res) {					console.log('屏幕信息', res)					that.signHeight = (res.windowHeight-130)+"px";				}			})		},		methods: {			submitBtn(){				uni.redirectTo({					url: '/qualifyLnvestor/qualifyLnvestor/result'				})			},			// 取消			cancelBtn(){				uni.navigateBack({					delta: 1				})			},			// 清除			clear() {				this.signCtx.clear();				this.vsignDisabled = true;			},			onSignInit(signCtx) {				this.signCtx = signCtx			},			// 绘画结束触发			endConfirm() {				this.vsignDisabled = false;			}		}	}</script><style lang="scss">	.sign-contain {		padding-left: 35rpx;		padding-right: 35rpx;		.sign-top {			width: 100%;			height: 50px;			line-height: 50px;			font-size: 16px;			text-align: center;			color: #999999;		}		.signBtn-box {			display: flex;			justify-content: space-between;			align-items: center;			.signBtn-item1 {				// 按钮样式				.lnvestor-btn {					margin-top: 11px;					width: 94px;					height: 40px;					border-radius: 20px;					display: flex;					justify-content: center;					align-items: center;					font-size: 16px;				}				.hover {					border: 1px solid #ccc !important;					color: #ccc !important;					font-size: 16px !important;				}			}			.signBtn-item2 {				display: flex;				// 按钮样式				.lnvestor-btn1 {					margin-top: 11px;					width: 128px;					height: 40px;					border-radius: 20px;					display: flex;					justify-content: center;					align-items: center;					font-size: 16px;					margin-right: 16px;				}				.lnvestor-btn2 {					margin-top: 11px;					width: 128px;					height: 40px;					border-radius: 20px;					display: flex;					justify-content: center;					align-items: center;					background: #b99c65;					font-size: 16px;				}				.hover {					border: 1px solid #ccc !important;					color: #ccc !important;					font-size: 16px !important;				}			}		}	}</style>

效果:
在这里插入图片描述

也许您对下面的内容还感兴趣: