u-code-input.vue 7.4 KB
<template>
	<view class="u-code-input">
		<view
			class="u-code-input__item"
			:style="[itemStyle(index)]"
			v-for="(item, index) in codeLength"
			:key="index"
		>
			<view
				class="u-code-input__item__dot"
				v-if="dot && codeArray.length > index"
			></view>
			<text
				v-else
				:style="{
					fontSize: $u.addUnit(fontSize),
					fontWeight: bold ? 'bold' : 'normal',
					color: color
				}"
			>{{codeArray[index]}}</text>
			<view
				class="u-code-input__item__line"
				v-if="mode === 'line'"
				:style="[lineStyle]"
			></view>
			<!-- #ifndef APP-PLUS -->
			<view v-if="isFocus && codeArray.length === index" :style="{backgroundColor: color}" class="u-code-input__item__cursor"></view>
			<!-- #endif -->
		</view>
		<input
			:disabled="disabledKeyboard"
			type="number"
			:focus="focus"
			:value="inputValue"
			:maxlength="maxlength"
			:adjustPosition="adjustPosition"
			class="u-code-input__input"
			@input="inputHandler"
			:style="{
				height: $u.addUnit(size) 
			}"
			@focus="isFocus = true"
			@blur="isFocus = false"
		/>
	</view>
</template>

<script>
	import props from './props.js';
	import mpMixin from '../../libs/mixin/mpMixin.js';
	import mixin from '../../libs/mixin/mixin.js';
	/**
	 * CodeInput 验证码输入
	 * @description 该组件一般用于验证用户短信验证码的场景,也可以结合uView的键盘组件使用
	 * @tutorial https://ijry.github.io/uview-plus/components/codeInput.html
	 * @property {String | Number}	maxlength			最大输入长度 (默认 6 )
	 * @property {Boolean}			dot					是否用圆点填充 (默认 false )
	 * @property {String}			mode				显示模式,box-盒子模式,line-底部横线模式 (默认 'box' )
	 * @property {Boolean}			hairline			是否细边框 (默认 false )
	 * @property {String | Number}	space				字符间的距离 (默认 10 )
	 * @property {String | Number}	value				预置值
	 * @property {Boolean}			focus				是否自动获取焦点 (默认 false )
	 * @property {Boolean}			bold				字体和输入横线是否加粗 (默认 false )
	 * @property {String}			color				字体颜色 (默认 '#606266' )
	 * @property {String | Number}	fontSize			字体大小,单位px (默认 18 )
	 * @property {String | Number}	size				输入框的大小,宽等于高 (默认 35 )
	 * @property {Boolean}			disabledKeyboard	是否隐藏原生键盘,如果想用自定义键盘的话,需设置此参数为true (默认 false )
	 * @property {String}			borderColor			边框和线条颜色 (默认 '#c9cacc' )
	 * @property {Boolean}			disabledDot			是否禁止输入"."符号 (默认 true )
	 * 
	 * @event {Function}	change	输入内容发生改变时触发,具体见上方说明			value:当前输入的值
	 * @event {Function}	finish	输入字符个数达maxlength值时触发,见上方说明	value:当前输入的值
	 * @example	<u-code-input v-model="value4" :focus="true"></u-code-input>
	 */
	export default {
		name: 'u-code-input',
		mixins: [mpMixin, mixin, props],
		data() {
			return {
				inputValue: '',
				isFocus: this.focus
			}
		},
		watch: {
			value: {
				immediate: true,
				handler(val) {
					// 转为字符串,超出部分截掉
					this.inputValue = String(val).substring(0, this.maxlength)
				}
			},
		},
		computed: {
			// 根据长度,循环输入框的个数,因为头条小程序数值不能用于v-for
			codeLength() {
				return new Array(Number(this.maxlength))
			},
			// 循环item的样式
			itemStyle() {
				return index => {
					const addUnit = uni.$u.addUnit
					const style = {
						width: addUnit(this.size),
						height: addUnit(this.size)
					}
					// 盒子模式下,需要额外进行处理
					if (this.mode === 'box') {
						// 设置盒子的边框,如果是细边框,则设置为0.5px宽度
						style.border = `${this.hairline ? 0.5 : 1}px solid ${this.borderColor}`
						// 如果盒子间距为0的话
						if (uni.$u.getPx(this.space) === 0) {
							// 给第一和最后一个盒子设置圆角
							if (index === 0) {
								style.borderTopLeftRadius = '3px'
								style.borderBottomLeftRadius = '3px'
							}
							if (index === this.codeLength.length - 1) {
								style.borderTopRightRadius = '3px'
								style.borderBottomRightRadius = '3px'
							}
							// 最后一个盒子的右边框需要保留
							if (index !== this.codeLength.length - 1) {
								style.borderRight = 'none'
							}
						}
					}
					if (index !== this.codeLength.length - 1) {
						// 设置验证码字符之间的距离,通过margin-right设置,最后一个字符,无需右边框
						style.marginRight = addUnit(this.space)
					} else {
						// 最后一个盒子的有边框需要保留
						style.marginRight = 0
					}

					return style
				}
			},
			// 将输入的值,转为数组,给item历遍时,根据当前的索引显示数组的元素
			codeArray() {
				return String(this.inputValue).split('')
			},
			// 下划线模式下,横线的样式
			lineStyle() {
				const style = {}
				style.height = this.hairline ? '2px' : '4px'
				style.width = uni.$u.addUnit(this.size)
				// 线条模式下,背景色即为边框颜色
				style.backgroundColor = this.borderColor
				return style
			}
		},
		methods: {
			// 监听输入框的值发生变化
			inputHandler(e) {
				const value = e.detail.value
				this.inputValue = value
				// 是否允许输入“.”符号
				if(this.disabledDot) {
					this.$nextTick(() => {
						this.inputValue = value.replace('.', '')
					})
				}
				// 未达到maxlength之前,发送change事件,达到后发送finish事件
				this.$emit('change', value)
				// 修改通过v-model双向绑定的值
				this.$emit('input', value)
				// 达到用户指定输入长度时,发出完成事件
				if (String(value).length >= Number(this.maxlength)) {
					this.$emit('finish', value)
				}
			}
		}
	}
</script>

<style lang="scss" scoped>
	@import "../../libs/css/components.scss";
	$u-code-input-cursor-width: 1px;
	$u-code-input-cursor-height: 40%;
	$u-code-input-cursor-animation-duration: 1s;
	$u-code-input-cursor-animation-name: u-cursor-flicker;

	.u-code-input {
		@include flex;
		position: relative;
		overflow: hidden;

		&__item {
			@include flex;
			justify-content: center;
			align-items: center;
			position: relative;

			&__text {
				font-size: 15px;
				color: $u-content-color;
			}

			&__dot {
				width: 7px;
				height: 7px;
				border-radius: 100px;
				background-color: $u-content-color;
			}

			&__line {
				position: absolute;
				bottom: 0;
				height: 4px;
				border-radius: 100px;
				width: 40px;
				background-color: $u-content-color;
			}
			/* #ifndef APP-PLUS */
			&__cursor {
				position: absolute;
				top: 50%;
				left: 50%;
				transform: translate(-50%,-50%);
				width: $u-code-input-cursor-width;
				height: $u-code-input-cursor-height;
				animation: $u-code-input-cursor-animation-duration u-cursor-flicker infinite;
			}
			/* #endif */
			
		}

		&__input {
			// 之所以需要input输入框,是因为有它才能唤起键盘
			// 这里将它设置为两倍的屏幕宽度,再将左边的一半移出屏幕,为了不让用户看到输入的内容
			position: absolute;
			left: -750rpx;
			width: 1500rpx;
			top: 0;
			background-color: transparent;
			text-align: left;
		}
	}
	
	/* #ifndef APP-PLUS */
	@keyframes u-cursor-flicker {
		0% {
		    opacity: 0;
		}
		50% {
		    opacity: 1;
		}
		100% {
		    opacity: 0;
		}
	}
	/* #endif */

</style>