浏览代码

请假审核

wkyy 2 年之前
父节点
当前提交
539ade63cd

+ 2 - 2
pages/teacher/leaveApproval/leaveApproval.vue

@@ -54,8 +54,8 @@
 				current: 0
 				current: 0
 			}
 			}
 		},
 		},
-		onReady() {
-			this.getLoadLeaveLogForAudit();
+		onShow() {
+			this.getLoadLeaveLogForAudit(true);
 		},
 		},
 		onReachBottom() {
 		onReachBottom() {
 			if (this.reCoursesList.length < this.recordsTotal) {
 			if (this.reCoursesList.length < this.recordsTotal) {

+ 177 - 38
pages/teacher/leaveApproval/leaveDetails.vue

@@ -3,16 +3,17 @@
 		<u-navbar title="请假条详情"></u-navbar>
 		<u-navbar title="请假条详情"></u-navbar>
 		<view class="head">
 		<view class="head">
 			<view class="photo">
 			<view class="photo">
-				<u-avatar class="avatar" mode="square" src="" size="84">
+				<u-avatar class="avatar" mode="square"
+					:src="leaveLog.headPhoto != null ? leaveLog.headPhoto : '../../assets/img/head.png'" size="84">
 				</u-avatar>
 				</u-avatar>
 			</view>
 			</view>
 			<view class="title">
 			<view class="title">
-				周梓轩的请假条
+				{{leaveLog.studentName}}的请假条
 			</view>
 			</view>
-			<view class="state1" v-show="state==false">
+			<view class="state1" v-if="leaveLog.status == 0">
 				待处理
 				待处理
 			</view>
 			</view>
-			<view class="state2" v-show="state==true">
+			<view class="state2" v-if="leaveLog.status == 1">
 				已审核
 				已审核
 			</view>
 			</view>
 
 
@@ -24,7 +25,7 @@
 					所属班级
 					所属班级
 				</view>
 				</view>
 				<view class="value">
 				<view class="value">
-					郭健康毛笔楷书班
+					{{leaveLog.className}}
 				</view>
 				</view>
 			</view>
 			</view>
 			<view class="item">
 			<view class="item">
@@ -32,7 +33,7 @@
 					请假日期
 					请假日期
 				</view>
 				</view>
 				<view class="value">
 				<view class="value">
-					2023-01-03 08:30-10:00
+					{{leaveLog.courseDate}}
 				</view>
 				</view>
 			</view>
 			</view>
 			<view class="item">
 			<view class="item">
@@ -40,7 +41,7 @@
 					请假课次
 					请假课次
 				</view>
 				</view>
 				<view class="value">
 				<view class="value">
-					第20节
+					{{leaveLog.coursePeriods}}
 				</view>
 				</view>
 			</view>
 			</view>
 
 
@@ -49,7 +50,7 @@
 					请假事由
 					请假事由
 				</view>
 				</view>
 				<view class="value">
 				<view class="value">
-					阳了!
+					{{leaveLog.leaveReason}}
 				</view>
 				</view>
 			</view>
 			</view>
 
 
@@ -59,7 +60,7 @@
 					提交时间
 					提交时间
 				</view>
 				</view>
 				<view class="value">
 				<view class="value">
-					2023-01-01 18:00
+					{{leaveLog.createTime}}
 				</view>
 				</view>
 			</view>
 			</view>
 		</view>
 		</view>
@@ -70,30 +71,30 @@
 					授课老师
 					授课老师
 				</view>
 				</view>
 				<view class="value">
 				<view class="value">
-					郭健康
+					{{leaveLog.teacherName}}
 				</view>
 				</view>
 			</view>
 			</view>
-			<view class="item" v-show="state==true">
+			<view class="item" v-if="leaveLog.status == 1">
 				<view class="title">
 				<view class="title">
 					回复结果
 					回复结果
 				</view>
 				</view>
 				<view class="value">
 				<view class="value">
-					同意
+					{{leaveLog.auditComm}}
 				</view>
 				</view>
 			</view>
 			</view>
-			<view class="item" v-show="state==true">
+			<view class="item" v-if="leaveLog.status == 1">
 				<view class="title">
 				<view class="title">
 					回复时间
 					回复时间
 				</view>
 				</view>
 				<view class="value">
 				<view class="value">
-					2023-01-02 12:00
+					{{getFormatDate(leaveLog.auditTime)}}
 				</view>
 				</view>
 			</view>
 			</view>
 		</view>
 		</view>
 
 
-    <view class="bottom">
-    	<u-button @click="show = true;">填写意见</u-button>
-    </view>
+		<view class="bottom">
+			<u-button @click="show = true;" v-if="leaveLog.status == 0">填写意见</u-button>
+		</view>
 		<!-- 弹窗 -->
 		<!-- 弹窗 -->
 		<view class="">
 		<view class="">
 			<u-popup mode="bottom" v-model="show">
 			<u-popup mode="bottom" v-model="show">
@@ -104,16 +105,17 @@
 								填写意见
 								填写意见
 							</view>
 							</view>
 							<view class="opinion">
 							<view class="opinion">
-								<textarea class="opinion-textarea" placeholder="请填写意见"></textarea>
+								<textarea v-model="info.auditComm" class="opinion-textarea" placeholder="请填写意见"></textarea>
 							</view>
 							</view>
 
 
 							<view class="sign">
 							<view class="sign">
 								<view class="sign-title">
 								<view class="sign-title">
 									请在下方空白区域使用正楷字体进行电子签名
 									请在下方空白区域使用正楷字体进行电子签名
 								</view>
 								</view>
-								<textarea class="sign-textarea" name="" id="" cols="30" rows="10"></textarea>
+								<l-signature disableScroll backgroundColor="#ddd" ref="signatureRef"
+									:penColor="penColor" :penSize="penSize" :openSmooth="openSmooth"></l-signature>
 								<view class="button">
 								<view class="button">
-									<view class="clear">
+									<view class="clear" @click="onClick('clear')">
 										<view class="img">
 										<view class="img">
 											<img src="../../../assets/img/riLine-eraser-line Copy@1x.png" alt="">
 											<img src="../../../assets/img/riLine-eraser-line Copy@1x.png" alt="">
 										</view>
 										</view>
@@ -121,7 +123,7 @@
 											清除
 											清除
 										</view>
 										</view>
 									</view>
 									</view>
-									<view class="save">
+									<view class="save" @click="onClick('save')">
 										<view class="img">
 										<view class="img">
 											<img src="../../../assets/img/riLine-save-line@1x.png" alt="">
 											<img src="../../../assets/img/riLine-save-line@1x.png" alt="">
 										</view>
 										</view>
@@ -134,7 +136,7 @@
 						</view>
 						</view>
 					</scroll-view>
 					</scroll-view>
 					<view class="confrim-btn">
 					<view class="confrim-btn">
-						<u-button @click="show = false;state=true">确定</u-button>
+						<u-button @click="leaveSure">确定</u-button>
 					</view>
 					</view>
 				</view>
 				</view>
 			</u-popup>
 			</u-popup>
@@ -149,11 +151,148 @@
 		data() {
 		data() {
 			return {
 			return {
 				show: false,
 				show: false,
-				state: false
+				state: false,
+				info: {
+					leaveId: '',
+					passIf: true,
+					auditComm: '',
+					signUrl: ''
+				},
+				leaveLog: {},
+				title: 'Hello',
+				penColor: 'red',
+				penSize: 5,
+				url: '',
+				openSmooth: true
+			}
+		},
+		onLoad(op) {
+			if (op.id) {
+				this.info.leaveId = op.id;
+				this.getLeaveLog();
 			}
 			}
 		},
 		},
 		methods: {
 		methods: {
+			leaveSure() {
+				uni.showLoading({
+					title: "加载中",
+					mask: true,
+				})
+				leaveApprovalApi.auditLeaveLog(this.info).then((res) => {
+					uni.hideLoading();
+					this.show = false;
+					this.getLeaveLog();
+				}).catch(error => {
+					uni.showToast({
+						title: error,
+						icon: "none"
+					})
+				})
+			},
+			onClick(type) {
+				if (type == 'openSmooth') {
+					this.openSmooth = !this.openSmooth
+					return
+				}
+				if (type == 'save') {
+					this.$refs.signatureRef.canvasToTempFilePath({
+						success: (res) => {
+							if (res.isEmpty) {
+								this.url = "";
+							} else {
+								this.url = res.tempFilePath
+
+								// 生成图片的临时路径
+								// app | H5 | 微信小程序 生成的是base64
+								//this.uploadpic()
+
+								uni.showLoading({
+									title: '上传中'
+								});
+
+								this.token = this.carhelp.getToken();
+								this.serverUrl = process.car.BASE_URL;
+								var time = ""; //parseUnixTime(new Date(), '{y}{m}{d}-{h}-{i}');
+								
+								uni.request({
+									url: this.serverUrl + "/mobile/student/uploadBase64",
+									method: "POST",
+									header: {
+										'Authorization': this.token,
+										'content-type': 'application/x-www-form-urlencoded'
+									},
+									data: {
+										photoBase64Data: this.url
+									},
+									success: (res) => {
+										uni.hideLoading();
+										console.log(res)
+										if (res.data.result) {
+											uni.showToast({
+												title: '签名保存成功!',
+												duration: 2000
+											});
+											
+											this.info.signUrl = res.data.data;
+										} else {
+											uni.showToast({
+												title: '签名保存失败!' + JSON.stringify(res),
+												duration: 2000
+											});
+										}
+									}
+								})
+							}
+						}
+					})
+					return
+				}
+				if (this.$refs.signatureRef)
+					this.$refs.signatureRef[type]()
+			},
+			getLeaveLog() {
+				uni.showLoading({
+					title: "加载中",
+					mask: true,
+				})
+				leaveApprovalApi.getLeaveLog({
+					leaveId: this.info.leaveId
+				}).then((res) => {
+					uni.hideLoading();
+					this.leaveLog = res.data;
+				}).catch(error => {
+					uni.showToast({
+						title: error,
+						icon: "none"
+					})
+				})
+			},
+			getFormatDate(val) {
+				let date = new Date(val);
+				let myyear = date.getFullYear();
+				let mymonth = date.getMonth() + 1;
+				let myweekday = date.getDate();
+				let hour = date.getHours();
+				let minute = date.getMinutes();
+				let second = date.getSeconds();
+				if (mymonth < 10) {
+					mymonth = '0' + mymonth;
+				}
+				if (myweekday < 10) {
+					myweekday = '0' + myweekday;
+				}
+				if(hour < 10) {
+					hour = '0' + hour;
+				}
+				if(minute < 10) {
+					minute = '0' + minute;
+				}
+				if(second < 10) {
+					second = '0' + second;
+				}
 			
 			
+				return (myyear + '-' + mymonth + '-' + myweekday + ' ' + hour + ':' + minute + ':' + second);
+			},
 		}
 		}
 	}
 	}
 </script>
 </script>
@@ -346,20 +485,20 @@
 		}
 		}
 	}
 	}
 
 
-   .bottom{
-	   background-color: #fff;
-	   padding: 20rpx 32rpx;
-	   position: fixed;
-	   left: 0;
-	   right: 0;
-	   bottom: 0;
-	   .u-btn{
-		   border-radius: 8px;
-		   background-color: rgba(0, 187, 170, 1);
-		   color: rgba(255, 255, 255, 1);
-		   font-size: 18px;
-	   }
-	   
-   }
+	.bottom {
+		background-color: #fff;
+		padding: 20rpx 32rpx;
+		position: fixed;
+		left: 0;
+		right: 0;
+		bottom: 0;
+
+		.u-btn {
+			border-radius: 8px;
+			background-color: rgba(0, 187, 170, 1);
+			color: rgba(255, 255, 255, 1);
+			font-size: 18px;
+		}
 
 
+	}
 </style>
 </style>

+ 22 - 0
uni_modules/lime-signature/changelog.md

@@ -0,0 +1,22 @@
+## 1.0.0(2022-10-27)
+- feat: 增加背景色
+- feat: 修复 app canvasToTempFilePath 无操作只能执行一次的问题
+## 0.8.0(2022-08-22)
+- feat: 增加beforeDelay 延时初始化,可用于手写板在弹窗里时
+## 0.7.0(2022-08-16)
+- fix: 修复缺少 canvasWidth
+## 0.6.0(2022-07-16)
+- fix: 修复 success is no defined
+## 0.5.0(2022-07-09)
+- feat: canvasToTempFilePath success 增加返回 isEmpty
+- fix: 修复 微信小程序 canvasToTempFilePath 无效问题
+## 0.4.0(2022-07-04)
+- fix: 生成图片缺少最后一笔
+## 0.3.0(2022-05-24)
+- chore: 支持多端 H5 小程序 APP APP-NVUE
+## 0.2.0(2021-07-09)
+- chore: 统一命名规范,无须主动引入组件
+- fix: 修复错位问题
+## 0.1.0(2021-03-07)
+- 首次上传
+- 撤消、清空、保存、模拟压感等功能

+ 66 - 0
uni_modules/lime-signature/components/l-signature/context.js

@@ -0,0 +1,66 @@
+export const uniContext = (ctx) => {
+	const ALIAS_ATTRS_MAP = [
+		'lineCap',
+		'strokeStyle',
+		'lineWidth',
+		'fillStyle',
+	]
+	ALIAS_ATTRS_MAP.forEach(style => {
+		Object.defineProperty(ctx, style, {
+			set: value => {
+				if(value)
+				ctx[`set${style.charAt(0).toUpperCase()}${style.slice(1)}`](value)
+			}
+		})
+	})
+	ctx.uniDrawImage = ctx.drawImage
+	ctx.drawImage = (image,...agrs) => {
+		ctx.uniDrawImage(image.src, ...agrs)
+	}
+	return ctx
+}
+
+class Image {
+	constructor() {
+		this.currentSrc = null
+		this.naturalHeight = 0
+		this.naturalWidth = 0
+		this.width = 0
+		this.height = 0
+		this.tagName = 'IMG'
+	}
+	set src(src) {
+		this.currentSrc = src
+		uni.getImageInfo({
+			src,
+			success: (res) => {
+				this.naturalWidth = this.width = res.width
+				this.naturalHeight = this.height = res.height
+				this.onload()
+			},
+			fail: () => {
+				this.onerror()
+			}
+		})
+	}
+	get src() {
+		return this.currentSrc
+	}
+}
+
+export const createImage = () => {
+	return new Image()
+}
+
+export const toDataURL = (canvasId, com) => {
+	return new Promise((resolve, reject) => {
+		uni.canvasToTempFilePath({
+			canvasId,
+			success: (res) => {
+				resolve(res.tempFilePath)
+			},
+			fail: reject
+		}, com)
+	})
+	
+}

+ 558 - 0
uni_modules/lime-signature/components/l-signature/l-signature.vue

@@ -0,0 +1,558 @@
+<template>
+	<view class="lime-signature" :style="[canvasStyle, styles]" ref="limeSignature">
+		<!-- #ifndef APP-VUE || APP-NVUE -->
+		<canvas 
+		v-if="useCanvas2d"
+		class="lime-signature__canvas"
+		:id="canvasId" 
+		type="2d"
+		:disableScroll="disableScroll"
+		@touchstart="touchStart"
+		@touchmove="touchMove"
+		@touchend="touchEnd"
+		></canvas>
+		<canvas 
+		v-else
+		:disableScroll="disableScroll"
+		class="lime-signature__canvas"
+		:canvas-id="canvasId" 
+		:id="canvasId"
+		@touchstart="touchStart"
+		@touchmove="touchMove"
+		@touchend="touchEnd"
+		@mousedown="touchStart"
+		@mousemove="touchMove"
+		@mouseup="touchEnd"
+		></canvas>
+		<!-- #endif -->
+		<!-- #ifdef APP-VUE -->
+		<view 
+		:id="canvasId"
+		:disableScroll="disableScroll"
+		:rparam="param"
+		:change:rparam="sign.update"
+		
+		:rclear="rclear"
+		:change:rclear="sign.clear"
+		
+		:rundo="rundo"
+		:change:rundo="sign.undo"
+		
+		:rsave="rsave"
+		:change:rsave="sign.save"
+		
+		:rempty="rempty"
+		:change:rempty="sign.isEmpty"
+		
+		></view>	
+		<!-- #endif -->
+		<!-- #ifdef APP-NVUE -->
+		<web-view 
+		src="/uni_modules/lime-signature/static/index.html"
+		class="lime-signature__canvas"
+		ref="webview"
+		@pagefinish="onPageFinish"
+		@error="onError"
+		@onPostMessage="onMessage"
+		></web-view>
+		<!-- #endif -->
+	</view>
+</template>
+<!-- #ifdef APP-VUE -->
+<script module="sign" lang="renderjs">
+// #ifdef APP-VUE 
+// import { Signature } from '@signature'
+import { Signature } from './signature'
+// import {base64ToPath} from './utils'
+
+export default {
+	data() {
+		return {
+			canvasid: null,
+			signature: null,
+			observer: null,
+			options: {},
+			saveCount: 0,
+		}
+	},
+	mounted() {
+		this.$nextTick(this.init)
+	},
+	methods: {
+		init() {
+			const el = this.$refs.limeSignature;
+			const canvas = document.createElement('canvas')
+			canvas.style = 'width:100%; height: 100%;'
+			el.appendChild(canvas)
+			this.signature = new Signature({el: canvas})
+			this.signature.pen.setOption(this.options)
+			const width = this.signature.canvas.get('width')
+			const height = this.signature.canvas.get('height')
+			
+			this.emit({
+				changeSize: {width, height}
+			})
+		},
+		undo(v) {
+			if(v && this.signature) {
+				this.signature.undo()
+			}
+		},
+		clear(v) {
+			if(v && this.signature) {
+				this.signature.clear()
+			}
+		},
+		save(v) {
+			if(v !== this.saveCount) {
+				this.saveCount = v;
+				const image = this.signature.canvas.get('el').toDataURL()
+				const {backgroundColor } = this.options
+				if(backgroundColor) {
+					const canvas = document.createElement('canvas')
+					const width = this.signature.canvas.get('width')
+					const height = this.signature.canvas.get('height')
+					const pixelRatio = this.signature.canvas.get('pixelRatio')
+					canvas.width = width * pixelRatio
+					canvas.height = height * pixelRatio
+					const context = canvas.getContext('2d')
+					context.scale(pixelRatio, pixelRatio)
+					context.fillStyle = backgroundColor
+					context.fillRect(0,0, width, height)
+					context.drawImage(this.signature.canvas.get('el'), 0, 0, width, height)
+					this.emit({save: canvas.toDataURL()})
+					canvas.remove()
+				} else {
+					this.emit({save: image})
+				}
+				
+				
+				
+				// base64ToPath(image).then((res) => {
+				// 	this.emit({save: res})
+				// })
+			}
+		},
+		isEmpty(v) {
+			if(v && this.signature) {
+				const isEmpty = this.signature.isEmpty()
+				this.emit({isEmpty})
+			}
+		},
+		emit(event) {
+			this.$ownerInstance.callMethod('onMessage', {
+				detail: {
+					data: [
+						{
+							event
+						}
+					]
+				}
+			})
+		},
+		update(v) {
+			if(v) {
+				if(this.signature) {
+					this.options = v
+					this.signature.pen.setOption(v)
+				} else {
+					this.options = v
+				}
+			}
+		}
+	}
+}
+// #endif
+</script>		
+<!-- #endif -->
+
+
+<script>
+	// #ifndef APP-NVUE
+	import {getCanvas2d, wrapEvent, requestAnimationFrame, sleep} from './utils'
+	import {Signature} from './signature'
+	// import {Signature} from '@signature';
+	import {uniContext, createImage, toDataURL} from './context'
+	// #endif
+	import {base64ToPath} from './utils'
+	export default {
+		props: {
+			styles: String,
+			disableScroll: Boolean,
+			type: {
+				type: String,
+				default: '2d'
+			},
+			// 画笔颜色
+			penColor: {
+				type: String,
+				default: 'black'
+			},
+			penSize: {
+				type: Number,
+				default: 2
+			},
+			// 画板背景颜色
+			backgroundColor: String,
+			// 笔锋
+			openSmooth: Boolean,
+			// 画笔最小值
+			minLineWidth: {
+				type: Number,
+				default: 2
+			},
+			// 画笔最大值
+			maxLineWidth: {
+				type: Number,
+				default: 6
+			},
+			// 画笔达到最小宽度所需最小速度(px/ms),取值范围1.0-10.0,值越小,画笔越容易变细,笔锋效果会比较明显,可以自行调整查看效果,选出自己满意的值。
+			minSpeed: {
+				type: Number,
+				default: 1.5
+			},
+			// 相邻两线宽度增(减)量最大百分比,取值范围1-100,为了达到笔锋效果,画笔宽度会随画笔速度而改变,如果相邻两线宽度差太大,过渡效果就会很突兀,使用maxWidthDiffRate限制宽度差,让过渡效果更自然。可以自行调整查看效果,选出自己满意的值。
+			maxWidthDiffRate: {
+				type: Number,
+				default: 20
+			},
+			// 限制历史记录数,即最大可撤销数,传入0则关闭历史记录功能
+			maxHistoryLength: {
+				type: Number,
+				default: 20
+			},
+			beforeDelay: {
+				type: Number,
+				default: 0
+			}
+		},
+		data() {
+			return {
+				canvasWidth: null,
+				canvasHeight: null,
+				useCanvas2d: true,
+				// #ifdef APP-PLUS
+				rclear: 0,
+				rundo: 0,
+				rsave: 0,
+				rempty: 0,
+				risEmpty: true,
+				toDataURL: null,
+				tempFilePath: [],
+				// #endif
+			}
+		},
+		computed: {
+			canvasId() {
+				return `lime-signature${this._uid||this._.uid}`
+			},
+			canvasStyle() {
+				const {canvasWidth, canvasHeight, backgroundColor} = this
+				return {
+					width: canvasWidth && (canvasWidth + 'px'),
+					height: canvasHeight && (canvasHeight + 'px'),
+					background: backgroundColor
+				}
+			},
+			param() {
+				const {penColor, penSize, backgroundColor, openSmooth, minLineWidth, maxLineWidth, minSpeed, maxWidthDiffRate, maxHistoryLength, disableScroll} = this
+				return JSON.parse(JSON.stringify({penColor, penSize, backgroundColor, openSmooth, minLineWidth, maxLineWidth, minSpeed, maxWidthDiffRate, maxHistoryLength, disableScroll}))
+			}
+		},
+		// #ifdef APP-NVUE
+		watch: {
+			param(v) {
+				this.$refs.webview.evalJS(`update(${JSON.stringify(v)})`)
+			}
+		},
+		// #endif
+		// #ifndef APP-PLUS
+		created() {
+			this.useCanvas2d = this.type=== '2d' && getCanvas2d()
+		},
+		// #endif
+		// #ifndef APP-PLUS
+		async mounted() {
+			if(this.beforeDelay) {
+				await sleep(this.beforeDelay)
+			}
+			const config = await this.getContext()
+			this.signature = new Signature(config)
+			this.canvasEl =  this.signature.canvas.get('el')
+			this.canvasWidth = this.signature.canvas.get('width')
+			this.canvasHeight = this.signature.canvas.get('height')
+			
+			this.stopWatch = this.$watch('param' , (v) => {
+				this.signature.pen.setOption(v)
+			}, {immediate: true})
+		},
+		// #endif
+		// #ifndef APP-PLUS
+		// #ifdef VUE3
+		beforeUnmount() {
+			this.stopWatch()
+			this.signature.destroy()
+		},
+		// #endif
+		// #ifdef VUE2
+		beforeDestroy() {
+			this.stopWatch()
+			this.signature.destroy()
+		},
+		// #endif
+		// #endif
+		methods: {
+			// #ifdef APP-PLUS
+			onPageFinish() {
+				this.$refs.webview.evalJS(`update(${JSON.stringify(this.param)})`)
+			},
+			onMessage(e = {}) {
+				const {detail: {data: [res]}} = e
+				if(res.event?.save) {
+					 this.toDataURL = res.event.save
+				}
+				if(res.event?.changeSize) {
+					const {width, height} = res.event.changeSize
+				}
+				if(res.event.hasOwnProperty('isEmpty')) {
+					this.risEmpty = res.event.isEmpty
+				}
+				if (res.event?.file) {
+					this.tempFilePath.push(res.event.file)
+					if (this.tempFilePath.length > 7) {
+						this.tempFilePath.shift()
+					}
+					return
+				}
+				if (res.event?.success) {
+					if (res.event.success) {
+						this.tempFilePath.push(res.event.success)
+						if (this.tempFilePath.length > 8) {
+							this.tempFilePath.shift()
+						}
+						this.toDataURL = this.tempFilePath.join('')
+						this.tempFilePath = []
+						// base64ToPath(this.tempFilePath.join('')).then(res => {
+							
+						// })
+					} else {
+						this.$emit('fail', 'canvas no data')
+					}
+					return
+				}
+			},
+			// #endif
+			undo() {
+				// #ifdef APP-VUE || APP-NVUE
+				this.rundo += 1
+				// #endif
+				// #ifdef APP-NVUE
+				this.$refs.webview.evalJS(`undo()`)
+				// #endif
+				// #ifndef APP-VUE
+				if(this.signature)
+					this.signature.undo()
+				// #endif
+			},
+			clear() {
+				// #ifdef APP-VUE || APP-NVUE
+				this.rclear += 1
+				// #endif
+				// #ifdef APP-NVUE
+				this.$refs.webview.evalJS(`clear()`)
+				// #endif
+				// #ifndef APP-VUE
+				if(this.signature)
+					this.signature.clear()
+				// #endif
+			},
+			isEmpty() {
+				// #ifdef APP-NVUE
+				this.$refs.webview.evalJS(`isEmpty()`)
+				// #endif
+				// #ifdef APP-VUE || APP-NVUE
+				this.rempty += 1
+				// #endif
+				// #ifndef APP-VUE || APP-NVUE
+				return this.signature.isEmpty()
+				// #endif
+			},
+			canvasToTempFilePath(param) {
+				const isEmpty = this.isEmpty()
+				// #ifdef APP-NVUE
+				this.$refs.webview.evalJS(`save()`)
+				// #endif
+				// #ifdef APP-VUE || APP-NVUE
+				const stopURLWatch = this.$watch('toDataURL', (v, n) => {
+					if(v && v !== n) {
+						if(param.pathType == 'url') {
+							base64ToPath(v).then(res => {
+								param.success({tempFilePath: res,isEmpty: this.risEmpty })
+							})
+						} else {
+							param.success({tempFilePath: v,isEmpty: this.risEmpty })
+						}
+						this.toDataURL = ''
+					}
+					stopURLWatch && stopURLWatch()
+				})
+				this.rsave += 1
+				// #endif
+				// #ifndef APP-VUE || APP-NVUE
+				const success = (success) => param.success && param.success(success)
+				const fail = (fail) => param.fail && param.fail(err)
+				const {canvas} = this.signature.canvas.get('el')
+				const context = this.signature.canvas.get('context')
+				const {backgroundColor} =  this
+				const width = this.signature.canvas.get('width')
+				const height = this.signature.canvas.get('height')
+				
+				if(this.useCanvas2d) {
+					try{
+						// #ifndef MP-ALIPAY
+						const tempFilePath = canvas.toDataURL()
+						if(backgroundColor) {
+							const image = canvas.createImage()
+							image.src = tempFilePath
+							image.onload = () => {
+								context.fillStyle = backgroundColor
+								context.fillRect(0, 0, width, height)
+								context.drawImage(image, 0, 0, width, height);
+								const tempFilePath = canvas.toDataURL()
+								success({tempFilePath, isEmpty})
+								context.clearRect(0,0, width, height)
+								context.drawImage(image, 0, 0, width, height);
+							}
+						} else {
+							success({tempFilePath, isEmpty})
+						}
+						// #endif
+						// #ifdef MP-ALIPAY
+						canvas.toTempFilePath({
+							canvasid: this.canvasid,
+							success(res){
+								if(backgroundColor) {
+									const image = canvas.createImage()
+									image.src = tempFilePath
+									image.onload = () => {
+										canvas.toTempFilePath({
+											canvasid: this.canvasid,
+											success(res) {
+												context.fillStyle = backgroundColor
+												context.fillRect(0, 0, width, height)
+												context.drawImage(image, 0, 0, width, height);
+												success({tempFilePath, isEmpty})
+												context.clearRect(0,0, width, height)
+												context.drawImage(image, 0, 0, width, height);
+											}
+										})
+										
+									}
+								} else {
+									success({tempFilePath: res, isEmpty})
+								}
+							},
+							fail
+						})
+						// #endif
+					}catch(err){
+						console.warn(err)
+						fail(err)
+					}
+				} else {
+					toDataURL(this.canvasId, this).then(res => {
+						if(backgroundColor) {
+							const image = createImage()
+							image.src = res
+							image.onload = () => {
+								context.fillStyle = backgroundColor
+								context.fillRect(0, 0, width, height)
+								context.drawImage(image, 0, 0, width, height);
+								context.draw && context.draw(true, () => {
+									toDataURL(this.canvasId, this).then(res => {
+										success({tempFilePath: res, isEmpty})
+										context.clearRect(0,0, width, height)
+										context.drawImage(image, 0, 0, width, height);
+										context.draw && context.draw(true)
+									})
+								});
+								
+							}
+						} else {
+							success({tempFilePath: res, isEmpty})
+						}
+					}).catch(err => {
+						console.warn(err)
+						fail(err)
+					})
+				}
+				// #endif
+			},
+			// #ifndef APP-PLUS
+			getContext() {
+				const {pixelRatio} = uni.getSystemInfoSync()
+				return new Promise(resolve => {
+					if(this.useCanvas2d) {
+						uni.createSelectorQuery().in(this)
+						.select(`#${this.canvasId}`)
+						.fields({
+							node: true,
+							size: true,
+							rect: true,
+						})
+						.exec(res => {
+							if(res) {
+								const {width, height, node, left, top, right} = res[0]
+								const context = node.getContext('2d')
+								node.width = width * pixelRatio;
+								node.height = height * pixelRatio;
+								resolve({ left, top, right, width, height, context, canvas: node, pixelRatio})
+							}
+						})
+					} else {
+						uni.createSelectorQuery().in(this)
+						.select(`#${this.canvasId}`)
+						.boundingClientRect()
+						.exec(res => {
+							if(res) {
+								const {width, height,  left, top, right} = res[0]
+								const context = uniContext(uni.createCanvasContext(this.canvasId, this))
+								const canvas = {
+									createImage, 
+									toDataURL: () => toDataURL(this.canvasId, this), 
+									requestAnimationFrame
+								}
+								resolve({ left, top, right, width, height, context, pixelRatio:1, canvas})
+							}
+						})
+					}
+				})
+			},
+			touchStart(e) {
+				if(!this.canvasEl) return
+				this.isStart = true
+				this.canvasEl.dispatchEvent('touchstart', wrapEvent(e))
+			},
+			touchMove(e) {
+				if(!this.canvasEl || !this.isStart && this.canvasEl) return
+				this.canvasEl.dispatchEvent('touchmove', wrapEvent(e))
+			},
+			touchEnd(e) {
+				if(!this.canvasEl) return
+				this.isStart = false
+				this.canvasEl.dispatchEvent('touchend', wrapEvent(e))
+			},
+			// #endif
+		}
+	}
+</script>
+<style lang="stylus">
+	.lime-signature,.lime-signature__canvas
+		// #ifndef APP-NVUE
+		width: 100%;
+		height: 100%
+		// #endif
+		// #ifdef APP-NVUE
+		flex: 1;
+		// #endif
+</style>

文件差异内容过多而无法显示
+ 0 - 0
uni_modules/lime-signature/components/l-signature/signature.js


+ 95 - 0
uni_modules/lime-signature/components/l-signature/utils.js

@@ -0,0 +1,95 @@
+export function compareVersion(v1, v2) {
+	v1 = v1.split('.')
+	v2 = v2.split('.')
+	const len = Math.max(v1.length, v2.length)
+	while (v1.length < len) {
+		v1.push('0')
+	}
+	while (v2.length < len) {
+		v2.push('0')
+	}
+	for (let i = 0; i < len; i++) {
+		const num1 = parseInt(v1[i], 10)
+		const num2 = parseInt(v2[i], 10)
+
+		if (num1 > num2) {
+			return 1
+		} else if (num1 < num2) {
+			return -1
+		}
+	}
+	return 0
+}
+
+
+export const getCanvas2d = () => {
+	let {SDKVersion, uniPlatform} = uni.getSystemInfoSync()
+	
+	if(!uniPlatform) {
+		// #ifdef MP-WEIXIN
+		uniPlatform = 'mp-weixin'
+		// #endif
+		// #ifdef MP-MP-ALIPAY
+		SDKVersion = my.SDKVersion
+		uniPlatform = 'mp-alipay'
+		// #endif
+		// #ifdef MP-MP-ALIPAY
+		uniPlatform = 'mp-toutiao'
+		// #endif
+	}
+	
+	const MAP = {
+		'mp-weixin': '2.9.7',
+		'mp-toutiao': '1.78.0',
+		'mp-alipay': '2.7.0'
+	}[uniPlatform]
+	return MAP && SDKVersion && compareVersion(SDKVersion, MAP) >= 1
+}
+
+export const wrapEvent = (e) => {
+  if (!e) return;
+  if (!e.preventDefault) {
+    e.preventDefault = function() {};
+  }
+  return e;
+}
+
+export const requestAnimationFrame = (cb) => {
+	setTimeout(cb, 30)
+}
+
+
+/**
+ * base64转路径
+ * @param {Object} base64
+ */
+export function base64ToPath(base64) {
+	const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64) || [];
+	return new Promise((resolve, reject) => {
+		const bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
+		bitmap.loadBase64Data(base64, () => {
+			if (!format) {
+				reject(new Error('ERROR_BASE64SRC_PARSE'))
+			}
+			const time = new Date().getTime();
+			const filePath = `_doc/uniapp_temp/${time}.${format}`
+			bitmap.save(filePath, {},
+				() => {
+					bitmap.clear()
+					resolve(filePath)
+				},
+				(error) => {
+					bitmap.clear()
+					reject(error)
+				})
+		}, (error) => {
+			bitmap.clear()
+			reject(error)
+		})
+	})
+}
+
+
+export function sleep(delay) {
+	return new Promise(resolve => setTimeout(resolve, delay))
+}

+ 8 - 0
uni_modules/lime-signature/components/lime-signature/index.vue

@@ -0,0 +1,8 @@
+<template>
+</template>
+
+<script>
+</script>
+
+<style>
+</style>

+ 77 - 0
uni_modules/lime-signature/package.json

@@ -0,0 +1,77 @@
+{
+  "id": "lime-signature",
+  "displayName": "手写板-签名 签字",
+  "version": "1.0.0",
+  "description": "手写板签名组件: 一款能跑在uniapp各端中的签名插件,支持签字颜色笔画大小等功能",
+  "keywords": [
+    "canvas",
+    "写字",
+    "签名",
+    "签字",
+    "涂鸦"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.5.4"
+  },
+"dcloudext": {
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": "",
+    "type": "component-vue"
+  },
+  "uni_modules": {
+    "dependencies": [],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y"
+      },
+      "client": {
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "y"
+        },
+        "H5-mobile": {
+          "Safari": "y",
+          "Android Browser": "y",
+          "微信浏览器(Android)": "y",
+          "QQ浏览器(Android)": "y"
+        },
+        "H5-pc": {
+          "Chrome": "y",
+          "IE": "u",
+          "Edge": "u",
+          "Firefox": "y",
+          "Safari": "y"
+        },
+        "小程序": {
+          "微信": "y",
+          "阿里": "u",
+          "百度": "u",
+          "字节跳动": "u",
+          "QQ": "u"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        }
+      }
+    }
+  }
+}

+ 100 - 0
uni_modules/lime-signature/readme.md

@@ -0,0 +1,100 @@
+# signature 写字板
+> uniapp 写字板,可用业务签名等场景  
+> [查看更多 站点1](https://limeui.qcoon.cn/#/signature) <br> 
+> [查看更多 站点2](http://liangei.gitee.io/limeui/#/signature)  
+> Q群:1169785031
+
+
+## 平台兼容
+
+| H5  | 微信小程序 | 支付宝小程序 | 百度小程序 | 头条小程序 | QQ 小程序 | App |
+| --- | ---------- | ------------ | ---------- | ---------- | --------- | --- |
+| √   | √          | 未测         | 未测       | 未测          | 未测      | √    |
+
+
+## 代码演示
+
+### 基本用法
+```html
+<view style="width: 750rpx ;height: 750rpx;">
+	<l-signature disableScroll backgroundColor="#ddd" ref="signatureRef" :penColor="penColor" :penSize="penSize" :openSmooth="openSmooth" ></l-signature>
+</view>
+<view>
+	<button @click="onClick('clear')">清空</button>
+	<button @click="onClick('undo')">撤消</button>
+	<button @click="onClick('save')">保存</button>
+	<button @click="onClick('openSmooth')">压感{{openSmooth?'开':'关'}}</button>
+</view>
+```
+
+```js
+export default {
+	data() {
+		return {
+			title: 'Hello',
+			penColor: 'red',
+			penSize: 5,
+			url: '',
+			openSmooth: true
+		}
+	},
+	methods: {
+		onClick(type) {
+			 if(type == 'openSmooth') {
+				 this.openSmooth = !this.openSmooth
+				 return
+			 }
+			if (type == 'save') {
+				this.$refs.signatureRef.canvasToTempFilePath({
+					success: (res) => {
+						// 是否为空画板 无签名
+						console.log(res.isEmpty)
+						// 生成图片的临时路径
+						// app | H5 | 微信小程序 生成的是base64
+						this.url = res.tempFilePath
+					}
+				})
+				return
+			}
+			if (this.$refs.signatureRef)
+				this.$refs.signatureRef[type]()
+		}
+	}
+}
+
+```
+
+
+
+## API
+### Props
+
+| 参数             | 说明                  | 类型              | 默认值        |
+| --------------   | ------------         | ----------------  | ------------ |
+| penSize          | 画笔大小              | <em>number</em>   |    `2`           |
+| minLineWidth     | 线条最小宽            | <em>number</em>    | `2`        |
+| maxLineWidth     | 线条最大宽            | <em>number</em>    | `6`        |
+| penColor         | 画笔颜色              | <em>string</em>    | `black`      |
+| backgroundColor  | 背景颜色              | <em>string</em>    | ``      |
+| type             | 指定 canvas 类型  | <em>string</em> | `2d`  |
+| openSmooth       | 是否模拟压感           | <em>boolean</em>   | `false`       |
+| beforeDelay       | 延时初始化,在放在弹窗里可以使用 (毫秒)          | <em>number</em>   | `0`       |
+| maxHistoryLength   | 限制历史记录数,即最大可撤销数,传入0则关闭历史记录功能           | <em>boolean</em>   | `20`       |
+
+
+### 事件 Events
+
+| 事件名  | 说明         | 回调           |
+| ------- | ------------ | -------------- |
+| undo | 撤消,回退到上一步 |  |
+| clear | 清空,清空画板 |  |
+| canvasToTempFilePath | 保存,生成图片,与官方保持一致,但不需要传canvasId |  |
+
+### 常见问题
+- 放在弹窗里时,尺寸不对 可以延时手写板出现时机,给手写板加vif或beforeDelay="300"
+
+### 打赏
+如果你觉得本插件,解决了你的问题,赠人玫瑰,手留余香。  
+
+![输入图片说明](https://static-6d65bd90-8508-4d6c-abbc-a4ef5c8e49e7.bspapp.com/image/222521_bb543f96_518581.jpeg "微信图片编辑_20201122220352.jpg")
+![输入图片说明](https://static-6d65bd90-8508-4d6c-abbc-a4ef5c8e49e7.bspapp.com/image/wxplay.jpg "wxplay.jpg")

+ 122 - 0
uni_modules/lime-signature/static/index.html

@@ -0,0 +1,122 @@
+<!DOCTYPE html>
+<html lang="zh">
+	<head>
+		<meta charset="UTF-8" />
+		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+		<meta http-equiv="X-UA-Compatible" content="ie=edge" />
+		<title></title>
+		<style type="text/css">
+			html,
+			body,
+			canvas {
+				padding: 0;
+				margin: 0;
+				width: 100%;
+				height: 100%;
+				overflow-y: hidden;
+				background-color: transparent;
+			}
+		</style>
+	</head>
+
+	<body>
+		<canvas id="lime-signature"></canvas>
+		<script type="text/javascript" src="./uni.webview.1.5.3.js"></script>
+		<script type="text/javascript" src="./signature.js"></script>
+		<script>
+			var signature = null;
+			var timer = null;
+			var isStart = false;
+			var options = null
+			console.log = function(...args) {
+				postMessage(args);
+			};
+			// function stringify(key, value) {
+			// 	if (typeof value === 'object' && value !== null) {
+			// 		if (cache.indexOf(value) !== -1) {
+			// 			return;
+			// 		}
+			// 		cache.push(value);
+			// 	}
+			// 	return value;
+			// };
+			function emit(event, data) {
+				postMessage({
+					event,
+					data: typeof data !== "object" && data !== null ? data : JSON.stringify(data),
+				});
+				// cache = [];
+			}
+
+			function postMessage(data) {
+				uni.postMessage({
+					data
+				});
+			}
+
+			function update(v = {}) {
+				if (signature) {
+					options = v
+					signature.pen.setOption(v);
+				} else {
+					signature = new Signature.Signature({el: "lime-signature"});
+					canvasEl = signature.canvas.get("el");
+					options = v
+					signature.pen.setOption(v)
+					const width = signature.canvas.get("width");
+					const height = signature.canvas.get("height");
+					
+					emit({changeSize: {width,height}})
+				}
+			}
+
+			function clear() {
+				signature.clear()
+			}
+
+			function undo() {
+				signature.undo()
+			}
+			function isEmpty() {
+				const isEmpty = signature.isEmpty()
+				emit({isEmpty});
+			}
+			function save(args) {
+				// delete args.success;
+				// delete args.fail;
+				clearTimeout(timer);
+				timer = setTimeout(() => {
+					let path = canvasEl.toDataURL()
+					if(options.backgroundColor) {
+						const canvas = document.createElement('canvas')
+						const width = signature.canvas.get('width')
+						const height = signature.canvas.get('height')
+						const pixelRatio = signature.canvas.get('pixelRatio')
+						canvas.width = width * pixelRatio
+						canvas.height = height * pixelRatio
+						const context = canvas.getContext('2d')
+						context.scale(pixelRatio, pixelRatio)
+						context.fillStyle = backgroundColor
+						context.fillRect(0,0, width, height)
+						context.drawImage(signature.canvas.get('el'), 0, 0, width, height)
+						path = canvas.toDataURL()
+						canvas.remove()
+					} 
+					if (typeof path == "string") {
+						const index = Math.ceil(path.length / 8);
+						for (var i = 0; i < 8; i++) {
+							if (i == 7) {
+								emit({"success": path.substr(i * index, index)});
+							} else {
+								emit({"file": path.substr(i * index, index)});
+							}
+						}
+					} else {
+						console.error("canvas no data");
+						emit({"fail": "canvas no data"});
+					}
+				}, 30);
+			}
+		</script>
+	</body>
+</html>

文件差异内容过多而无法显示
+ 0 - 0
uni_modules/lime-signature/static/signature.js


文件差异内容过多而无法显示
+ 0 - 0
uni_modules/lime-signature/static/uni.webview.1.5.3.js


部分文件因为文件数量过多而无法显示