312 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Vue
		
	
	
	
		
		
			
		
	
	
			312 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Vue
		
	
	
	
|  | <template> | |||
|  | 	<view class="u-char-box"> | |||
|  | 		<view class="u-char-flex"> | |||
|  | 			<input :disabled="disabledKeyboard" :value="valueModel" type="number" :focus="focus" :maxlength="maxlength" class="u-input" @input="getVal"/> | |||
|  | 			<view v-for="(item, index) in loopCharArr" :key="index"> | |||
|  | 				<view :class="[breathe && charArrLength == index ? 'u-breathe' : '', 'u-char-item', | |||
|  | 				charArrLength === index && mode == 'box' ? 'u-box-active' : '', | |||
|  | 				mode === 'box' ? 'u-box' : '']" :style="{ | |||
|  | 					fontWeight: bold ? 'bold' : 'normal', | |||
|  | 					fontSize: fontSize + 'rpx', | |||
|  | 					width: width + 'rpx', | |||
|  | 					height: width + 'rpx', | |||
|  | 					color: inactiveColor, | |||
|  | 					borderColor: charArrLength === index && mode == 'box' ? activeColor : inactiveColor | |||
|  | 				}"> | |||
|  | 					<view class="u-placeholder-line" :style="{ | |||
|  | 							display: charArrLength === index ? 'block' : 'none', | |||
|  | 							height: width * 0.5 +'rpx' | |||
|  | 						}" | |||
|  | 						v-if="mode !== 'middleLine'" | |||
|  | 					></view> | |||
|  | 					<view v-if="mode === 'middleLine' && charArrLength <= index" :class="[breathe && charArrLength == index ? 'u-breathe' : '', charArrLength === index ? 'u-middle-line-active' : '']" | |||
|  | 					 class="u-middle-line" :style="{height: bold ? '4px' : '2px', background: charArrLength === index ? activeColor : inactiveColor}"></view> | |||
|  | 					<view v-if="mode === 'bottomLine'" :class="[breathe && charArrLength == index ? 'u-breathe' : '', charArrLength === index ? 'u-buttom-line-active' : '']" | |||
|  | 					 class="u-bottom-line" :style="{height: bold ? '4px' : '2px', background: charArrLength === index ? activeColor : inactiveColor}"></view> | |||
|  | 					<block v-if="!dotFill"> {{ charArr[index] ? charArr[index] : ''}}</block> | |||
|  | 					<block v-else> | |||
|  | 						<text class="u-dot">{{ charArr[index] ? '●' : ''}}</text> | |||
|  | 					</block> | |||
|  | 				</view> | |||
|  | 			</view> | |||
|  | 		</view> | |||
|  | 	</view> | |||
|  | </template> | |||
|  | 
 | |||
|  | <script> | |||
|  | 	/** | |||
|  | 	 * messageInput 验证码输入框 | |||
|  | 	 * @description 该组件一般用于验证用户短信验证码的场景,也可以结合uView的键盘组件使用 | |||
|  | 	 * @tutorial https://www.uviewui.com/components/messageInput.html
 | |||
|  | 	 * @property {String Number} maxlength 输入字符个数(默认4) | |||
|  | 	 * @property {Boolean} dot-fill 是否用圆点填充(默认false) | |||
|  | 	 * @property {String} mode 模式选择,见上方"基本使用"说明(默认box) | |||
|  | 	 * @property {String Number} value 预置值 | |||
|  | 	 * @property {Boolean} breathe 是否开启呼吸效果,见上方说明(默认true) | |||
|  | 	 * @property {Boolean} focus 是否自动获取焦点(默认false) | |||
|  | 	 * @property {Boolean} bold 字体和输入横线是否加粗(默认true) | |||
|  | 	 * @property {String Number} font-size 字体大小,单位rpx(默认60) | |||
|  | 	 * @property {String} active-color 当前激活输入框的样式(默认#2979ff) | |||
|  | 	 * @property {String} inactive-color 非激活输入框的样式,文字颜色同此值(默认#606266) | |||
|  | 	 * @property {String | Number} width 输入框宽度,单位rpx,高等于宽(默认80) | |||
|  | 	 * @property {Boolean} disabled-keyboard 禁止点击输入框唤起系统键盘(默认false) | |||
|  | 	 * @event {Function} change 输入内容发生改变时触发,具体见官网说明 | |||
|  | 	 * @event {Function} finish 输入字符个数达maxlength值时触发,见官网说明 | |||
|  | 	 * @example <u-message-input mode="bottomLine"></u-message-input> | |||
|  | 	 */ | |||
|  | 	export default { | |||
|  | 		name: "u-message-input", | |||
|  | 		props: { | |||
|  | 			// 最大输入长度
 | |||
|  | 			maxlength: { | |||
|  | 				type: [Number, String], | |||
|  | 				default: 4 | |||
|  | 			}, | |||
|  | 			// 是否用圆点填充
 | |||
|  | 			dotFill: { | |||
|  | 				type: Boolean, | |||
|  | 				default: false | |||
|  | 			}, | |||
|  | 			// 显示模式,box-盒子模式,bottomLine-横线在底部模式,middleLine-横线在中部模式
 | |||
|  | 			mode: { | |||
|  | 				type: String, | |||
|  | 				default: "box" | |||
|  | 			}, | |||
|  | 			// 预置值
 | |||
|  | 			value: { | |||
|  | 				type: [String, Number], | |||
|  | 				default: '' | |||
|  | 			}, | |||
|  | 			// 当前激活输入item,是否带有呼吸效果
 | |||
|  | 			breathe: { | |||
|  | 				type: Boolean, | |||
|  | 				default: true | |||
|  | 			}, | |||
|  | 			// 是否自动获取焦点
 | |||
|  | 			focus: { | |||
|  | 				type: Boolean, | |||
|  | 				default: false | |||
|  | 			}, | |||
|  | 			// 字体是否加粗
 | |||
|  | 			bold: { | |||
|  | 				type: Boolean, | |||
|  | 				default: false | |||
|  | 			}, | |||
|  | 			// 字体大小
 | |||
|  | 			fontSize: { | |||
|  | 				type: [String, Number], | |||
|  | 				default: 60 | |||
|  | 			}, | |||
|  | 			// 激活样式
 | |||
|  | 			activeColor: { | |||
|  | 				type: String, | |||
|  | 				default: '#2979ff' | |||
|  | 			}, | |||
|  | 			// 未激活的样式
 | |||
|  | 			inactiveColor: { | |||
|  | 				type: String, | |||
|  | 				default: '#606266' | |||
|  | 			}, | |||
|  | 			// 输入框的大小,单位rpx,宽等于高
 | |||
|  | 			width: { | |||
|  | 				type: [Number, String], | |||
|  | 				default: '80' | |||
|  | 			}, | |||
|  | 			// 是否隐藏原生键盘,如果想用自定义键盘的话,需设置此参数为true
 | |||
|  | 			disabledKeyboard: { | |||
|  | 				type: Boolean, | |||
|  | 				default: false | |||
|  | 			} | |||
|  | 		}, | |||
|  | 		watch: { | |||
|  | 			// maxlength: {
 | |||
|  | 			// 	// 此值设置为true,会在组件加载后无需maxlength变化就会执行一次本监听函数,无需再created生命周期中处理
 | |||
|  | 			// 	immediate: true,
 | |||
|  | 			// 	handler(val) {
 | |||
|  | 			// 		this.maxlength = Number(val);
 | |||
|  | 			// 	}
 | |||
|  | 			// }, 
 | |||
|  | 			value: { | |||
|  | 				immediate: true, | |||
|  | 				handler(val) { | |||
|  | 					// 转为字符串
 | |||
|  | 					val = String(val); | |||
|  | 					// 超出部分截掉
 | |||
|  | 					this.valueModel = val.substring(0, this.maxlength); | |||
|  | 				} | |||
|  | 			}, | |||
|  | 		}, | |||
|  | 		data() { | |||
|  | 			return { | |||
|  | 				valueModel: "" | |||
|  | 			} | |||
|  | 		}, | |||
|  | 		computed: { | |||
|  | 			// 是否显示呼吸灯效果
 | |||
|  | 			animationClass() { | |||
|  | 				return (index) => { | |||
|  | 					if (this.breathe && this.charArr.length == index) return 'u-breathe'; | |||
|  | 					else return ''; | |||
|  | 				} | |||
|  | 			}, | |||
|  | 			// 用于显示字符
 | |||
|  | 			charArr() { | |||
|  | 				return this.valueModel.split(''); | |||
|  | 			}, | |||
|  | 			charArrLength() { | |||
|  | 				return this.charArr.length; | |||
|  | 			}, | |||
|  | 			// 根据长度,循环输入框的个数,因为头条小程序数值不能用于v-for
 | |||
|  | 			loopCharArr() { | |||
|  | 				return new Array(this.maxlength); | |||
|  | 			} | |||
|  | 		}, | |||
|  | 		methods: { | |||
|  | 			getVal(e) { | |||
|  | 				let { | |||
|  | 					value | |||
|  | 				} = e.detail | |||
|  | 				this.valueModel = value; | |||
|  | 				// 判断长度是否超出了maxlength值,理论上不会发生,因为input组件设置了maxlength属性值
 | |||
|  | 				if (String(value).length > this.maxlength) return; | |||
|  | 				// 未达到maxlength之前,发送change事件,达到后发送finish事件
 | |||
|  | 				this.$emit('change', value); | |||
|  | 				if (String(value).length == this.maxlength) { | |||
|  | 					this.$emit('finish', value); | |||
|  | 				} | |||
|  | 			} | |||
|  | 		} | |||
|  | 	} | |||
|  | </script> | |||
|  | 
 | |||
|  | <style scoped lang="scss"> | |||
|  | 	@import "../../libs/css/style.components.scss"; | |||
|  | 
 | |||
|  | 	@keyframes breathe { | |||
|  | 		0% { | |||
|  | 			opacity: 0.3; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		50% { | |||
|  | 			opacity: 1; | |||
|  | 		} | |||
|  | 
 | |||
|  | 		100% { | |||
|  | 			opacity: 0.3; | |||
|  | 		} | |||
|  | 	} | |||
|  | 
 | |||
|  | 	.u-char-box { | |||
|  | 		text-align: center; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	.u-char-flex { | |||
|  | 		@include vue-flex; | |||
|  | 		justify-content: center; | |||
|  | 		flex-wrap: wrap; | |||
|  | 		position: relative; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	.u-input { | |||
|  | 		position: absolute; | |||
|  | 		top: 0; | |||
|  | 		left: -100%; | |||
|  | 		width: 200%; | |||
|  | 		height: 100%; | |||
|  | 		text-align: left; | |||
|  | 		z-index: 9; | |||
|  | 		opacity: 0; | |||
|  | 		background: none; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	.u-char-item { | |||
|  | 		position: relative; | |||
|  | 		width: 90rpx; | |||
|  | 		height: 90rpx; | |||
|  | 		margin: 10rpx 10rpx; | |||
|  | 		font-size: 60rpx; | |||
|  | 		font-weight: bold; | |||
|  | 		color: $u-main-color; | |||
|  | 		line-height: 90rpx; | |||
|  | 		@include vue-flex; | |||
|  | 		justify-content: center; | |||
|  | 		align-items: center; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	.u-middle-line { | |||
|  | 		border: none; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	.u-box { | |||
|  | 		box-sizing: border-box; | |||
|  | 		border: 2rpx solid #cccccc; | |||
|  | 		border-radius: 6rpx; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	.u-box-active { | |||
|  | 		overflow: hidden; | |||
|  | 		animation-timing-function: ease-in-out; | |||
|  | 		animation-duration: 1500ms; | |||
|  | 		animation-iteration-count: infinite; | |||
|  | 		animation-direction: alternate; | |||
|  | 		border: 2rpx solid $u-type-primary; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	.u-middle-line-active { | |||
|  | 		background: $u-type-primary; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	.u-breathe { | |||
|  | 		animation: breathe 2s infinite ease; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	.u-placeholder-line { | |||
|  | 		/* #ifndef APP-NVUE */ | |||
|  | 		display: none; | |||
|  | 		/* #endif */ | |||
|  | 		position: absolute; | |||
|  | 		left: 50%; | |||
|  | 		top: 50%; | |||
|  | 		transform: translate(-50%, -50%); | |||
|  | 		width: 2rpx; | |||
|  | 		height: 40rpx; | |||
|  | 		background: #333333; | |||
|  | 		animation: twinkling 1.5s infinite ease; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	.u-animation-breathe { | |||
|  | 		animation-name: breathe; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	.u-dot { | |||
|  | 		font-size: 34rpx; | |||
|  | 		line-height: 34rpx; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	.u-middle-line { | |||
|  | 		height: 4px; | |||
|  | 		background: #000000; | |||
|  | 		width: 80%; | |||
|  | 		position: absolute; | |||
|  | 		border-radius: 2px; | |||
|  | 		top: 50%; | |||
|  | 		left: 50%; | |||
|  | 		transform: translate(-50%, -50%); | |||
|  | 	} | |||
|  | 
 | |||
|  | 	.u-buttom-line-active { | |||
|  | 		background: $u-type-primary; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	.u-bottom-line { | |||
|  | 		height: 4px; | |||
|  | 		background: #000000; | |||
|  | 		width: 80%; | |||
|  | 		position: absolute; | |||
|  | 		border-radius: 2px; | |||
|  | 		bottom: 0; | |||
|  | 		left: 50%; | |||
|  | 		transform: translate(-50%); | |||
|  | 	} | |||
|  | </style> |